-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAntColonyOptimization.php
114 lines (94 loc) · 3.57 KB
/
AntColonyOptimization.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php
namespace App\Module\TSP\Application\Problem;
use App\Module\TSP\Domain\Constant;
use App\Module\TSP\Domain\ResultItem;
use App\Shared\Domain\DistanceMap;
use Ramsey\Collection\Collection;
use Ramsey\Collection\CollectionInterface;
use UnexpectedValueException;
/**
* @see https://www.baeldung.com/java-ant-colony-optimization
*/
readonly class AntColonyOptimization
{
private PheromoneMatrix $pheromoneMatrix;
public function __construct(
private AntStrategyInterface $antStrategy,
private string $initialLocation,
private DistanceMap $distanceMap,
private float $alpha = Constant::DEFAULT_ALPHA,
private float $beta = Constant::DEFAULT_BETA,
private int $distanceCoefficient = Constant::DEFAULT_DISTANCE_COEFFICIENT,
private float $evaporation = Constant::DEFAULT_EVAPORATION,
private float $antFactor = Constant::DEFAULT_ANT_FACTOR,
float $c = Constant::DEFAULT_PHEROMONE_INITIAL_VALUE,
) {
$this->pheromoneMatrix = PheromoneMatrix::createFromLocations($distanceMap->keys(), $c);
}
/**
* @return CollectionInterface<ResultItem>
*/
public function optimize(int $iterations = Constant::DEFAULT_ITERATIONS): CollectionInterface
{
if ($iterations < 1) {
throw new UnexpectedValueException('Iterations must be a positive number!');
}
$result = new Collection(ResultItem::class);
for ($i = 0; $i < $iterations; ++$i) {
$ants = $this->createAnts();
foreach ($ants as $ant) {
$ant->start();
$result->add(new ResultItem(clone $ant->getPath(), $ant->getLength()));
$this->updatePheromones($ant);
}
$this->evaporatePheromones();
}
return $result->sort('length');
}
public function getPheromones(): PheromoneMatrix
{
return clone $this->pheromoneMatrix;
}
/**
* @return CollectionInterface<AntInterface>
*/
private function createAnts(): CollectionInterface
{
$ants = new Collection(AntInterface::class);
$numAnts = (int) ceil($this->distanceMap->count() * $this->antFactor);
if ($numAnts < 1) {
throw new UnexpectedValueException('Ants must be a positive number!');
}
for ($i = 0; $i < $numAnts; ++$i) {
$ants->add($this->antStrategy->createAnt(
initialLocation: $this->initialLocation,
distanceMap: $this->distanceMap,
pheromoneMatrix: $this->pheromoneMatrix,
alpha: $this->alpha,
beta: $this->beta,
distanceCoefficient: $this->distanceCoefficient,
));
}
return $ants;
}
private function updatePheromones(AntInterface $ant): void
{
$path = $ant->getPath();
$lengthCoefficient = $ant->getLength() / $this->distanceCoefficient;
if ($lengthCoefficient == 0) {
$lengthCoefficient = 0.000001;
}
for ($i = 0; $i < $path->count() - 1; ++$i) {
$this->pheromoneMatrix->increasePheromone($path[$i], $path[$i + 1], 1 / $lengthCoefficient);
$this->pheromoneMatrix->increasePheromone($path[$i + 1], $path[$i], 1 / $lengthCoefficient);
}
}
private function evaporatePheromones(): void
{
foreach ($this->pheromoneMatrix as $aId => $cols) {
foreach ($cols as $bId => $value) {
$this->pheromoneMatrix->updatePheromone($aId, $bId, $value * (1 - $this->evaporation));
}
}
}
}