mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-02-12 23:08:41 +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