First test implementation of TSP

This commit is contained in:
Dennis Eichhorn 2015-12-25 13:56:31 +01:00
parent a11e372b3a
commit cab1ed16b2
7 changed files with 401 additions and 0 deletions

View File

@ -0,0 +1,48 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
class BruteForce
{
const LIMIT = 22;
private $cityPool = null;
public function __construct(CityPool $pool)
{
$this->cityPool = $pool;
if ($this->cityPool->count() > self::LIMIT) {
throw new \Exception('64 bit overflow');
}
}
public function getSolution(\int $limit = 1) : Population
{
$population = new Population($this->cityPool, $limit, true);
$cities = $this->cityPool->getCities();
$this->bruteForce($cities, new Tour($this->cityPool, false), $population);
return $population;
}
private function bruteForce(array $cities, Tour $tour, Population $population)
{
if (count($cities) === 0) {
$population->addTour($tour);
}
for ($i = 0; $i < count($cities); $i++) {
$extended = clone $tour;
$extended->addCity($cities[$i]);
unset($cities[$i]);
if ($population->getUnfittest()->getFitness() > $extended->getFitness()) {
continue;
}
$this->bruteForce($cities, $extended, $population);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
use phpOMS\Math\Shape\D3\Sphere;
class City
{
private $name = '';
private $long = 0.0;
private $lat = 0.0;
public function __construct(\float $lat, \float $long, \string $name)
{
$this->long = $long;
$this->lat = $lat;
$this->name = $name;
}
public function getLongitude() : \float
{
return $this->long;
}
public function getLatitude() : \float
{
return $this->lat;
}
public function getName() : \string
{
return $this->name;
}
public function equals(City $city) : \bool
{
return $this->name === $city->getName() && $this->lat === $city->getLatitude() && $this->long === $city->getLatitude();
}
public function getDistanceTo(City $city) : \float
{
return Sphere::distance2PointsOnSphere($this->lat, $this->long, $city->getLatitude(), $city->getLongitude());
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
class CityPool implements \Countable
{
private $cities = [];
public function __construct($cities = [])
{
$this->cities = $cities;
}
public function addCity(City $city)
{
$this->cities[$city->getName() . $city->getLatitude() . $city->getLongitude()] = $city;
}
public function getCity($index) : City
{
return array_values($this->cities)[$index];
}
public function getCities() : array
{
return $this->cities;
}
public function hasCity(City $city) : \bool
{
foreach ($this->cities as $c) {
if ($c->equals($city)) {
return true;
}
}
return false;
}
public function count() : \int
{
return count($this->cities);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
class GA
{
const MUTATION = 15; /* 1000 = 100% */
const TOURNAMENT = 5;
const ELITISM = true;
private $cityPool = null;
public function __construct(CityPool $pool)
{
$this->cityPool = $pool;
}
public function evolvePopulation(Population $population) : Population
{
$shift = self::ELITISM ? 1 : 0;
$newPopulation = new Population($this->cityPool, $count = $population->count(), false);
$newPopulation->addTour($population->getFittest());
for ($i = $shift; $i < $count; $i++) {
$parent1 = $this->tournamentSelection($population);
$parent2 = $this->tournamentSelection($population);
$child = $this->crossover($parent1, $parent2);
$newPopulation->setTour($i, $child);
}
$count = $newPopulation->count();
for ($i = $shift; $i < $count; $i++) {
$this->mutate($newPopulation->getTour($i));
}
return $newPopulation;
}
public function crossover(Tour $tour1, Tour $tour2) : Tour
{
$child = new Tour($this->cityPool, false);
$start = mt_rand(0, $tour1->count());
$end = mt_rand(0, $tour1->count());
$count = $child->count(); /* $tour1->count() ???!!!! */
for ($i = 0; $i < $count; $i++) {
if ($start < $end && $i > $start && $i < $end) {
$child->setCity($i, $tour1->getCity($i));
} elseif ($start > $end && !($i < $start && $i > $end)) {
$child->setCity($i, $tour1->getCity($i));
}
}
$count = $tour2->count();
for ($i = 0; $i < $count; $i++) {
if (!$child->hasCity($tour2->getCity($i))) {
for ($j = 0; $j < $child->count(); $j++) {
if ($child->getCity($j) === null) {
$child->setCity($j, $tour2->getCity($i));
break;
}
}
}
}
return $child;
}
private function mutate(Tour $tour)
{
$count = $tour->count();
for ($pos1 = 0; $pos1 < $count; $pos1++) {
if (mt_rand(0, 1000) < self::MUTATION) {
$pos2 = mt_rand(0, $tour->count());
/* Could be same pos! */
$city1 = $tour->getCity($pos1);
$city2 = $tour->getCity($pos2);
/* swapping */
$tour->setCity($pos1, $city2);
$tour->setCity($pos2, $city1);
}
}
}
private function tournamentSelection(Population $population) : Tour
{
$tournament = new Population($this->cityPool, self::TOURNAMENT, false);
$populationSize = $population->count();
for ($i = 0; $i < self::TOURNAMENT; $i++) {
$tournament->addTour($population->getTour(mt_rand(0, $populationSize)));
}
return $tournament->getFittest();
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
class Population implements \Countable
{
private $tours = [];
public function __construct(CityPool $pool, \int $size, \bool $initialise = false)
{
if ($initialise) {
for ($i = 0; $i < $size; $i++) {
$this->tours[] = new Tour($pool, true);
}
}
}
public function insertTourAt(\int $index, Tour $tour)
{
$this->tours = array_slice($this->tours, 0, $index) + [$tour] + array_slice($this->tours, $index);
}
public function setTour(\int $index, Tour $tour)
{
$this->tours[$index] = $tour;
asort($this->tours);
}
public function addTour(Tour $tour)
{
$this->tours[] = $tour;
}
public function getTour(\int $index)
{
return $this->tours[$index] ?? null;
}
public function getFittest() : Tour
{
$fittest = $this->tours[0];
$count = count($this->tours);
for ($i = 1; $i < $count; $i++) {
if ($fittest->getFitness() <= $this->tours[$i]->getFitness()) {
$fittest = $this->tours[$i];
}
}
return $fittest;
}
public function getUnfittest() : Tour
{
$unfittest = $this->tours[0];
$count = count($this->tours);
for ($i = 1; $i < $count; $i++) {
if ($unfittest->getFitness() >= $this->tours[$i]->getFitness()) {
$unfittest = $this->tours[$i];
}
}
return $unfittest;
}
public function count() : \int
{
return count($this->tours);
}
}

View File

View File

@ -0,0 +1,88 @@
<?php
namespace phpOMS\Math\Optimization\TSP;
class Tour implements \Countable
{
private $cities = [];
private $fitness = 0.0;
private $distance = 0.0;
private $cityPool = null;
public function __construct(CityPool $pool, \bool $initialise = false)
{
$this->cityPool = $pool;
if ($initialise) {
$this->cities = $this->cityPool->getCities();
shuffle($this->cities);
}
}
public function getCity($index)
{
return array_values($this->cities)[$index] ?? null;
}
public function getFitness() : \float
{
if ($this->fitness === 0.0 && ($distance = $this->getDistance()) !== 0.0) {
$this->fitness = 1 / $distance;
}
return $this->fitness;
}
public function addCity(City $city)
{
$this->cities[] = $city;
$this->fitness = 0.0;
$this->distance = 0.0;
}
public function setCity(\int $index, City $city)
{
$this->cities[$index] = $city;
asort($this->cities);
$this->fitness = 0.0;
$this->distance = 0.0;
}
public function getDistance() : \float
{
if ($this->distance === 0.0) {
$distance = 0.0;
$count = count($this->cities);
for ($i = 0; $i < $count; $i++) {
$dest = ($i + 1 < $count) ? $this->cities[$i + 1] : $this->cities[0];
$distance += $this->cities[$i]->getDistanceTo($dest);
}
$this->distance = $distance;
}
return $this->distance;
}
public function hasCity(City $city) : \bool
{
foreach ($this->cities as $c) {
if ($c->equals($city)) {
return true;
}
}
return false;
}
public function count() : \int
{
return count($this->cities);
}
}