mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 09:48:40 +00:00
First test implementation of TSP
This commit is contained in:
parent
a11e372b3a
commit
cab1ed16b2
48
Math/Optimization/TSP/BruteForce.php
Normal file
48
Math/Optimization/TSP/BruteForce.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Math/Optimization/TSP/City.php
Normal file
44
Math/Optimization/TSP/City.php
Normal 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());
|
||||
}
|
||||
}
|
||||
44
Math/Optimization/TSP/CityPool.php
Normal file
44
Math/Optimization/TSP/CityPool.php
Normal 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);
|
||||
}
|
||||
}
|
||||
106
Math/Optimization/TSP/GA.php
Normal file
106
Math/Optimization/TSP/GA.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
71
Math/Optimization/TSP/Population.php
Normal file
71
Math/Optimization/TSP/Population.php
Normal 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);
|
||||
}
|
||||
}
|
||||
0
Math/Optimization/TSP/TSPAbstract.php
Normal file
0
Math/Optimization/TSP/TSPAbstract.php
Normal file
88
Math/Optimization/TSP/Tour.php
Normal file
88
Math/Optimization/TSP/Tour.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user