~ruther/ctu-fee-eoa

f4e5737f84743bce957c891b4ea569cb4d067dab — Rutherther a month ago fb3ac87
chore: finalize report
1 files changed, 109 insertions(+), 19 deletions(-)

M codes/report.md
M codes/report.md => codes/report.md +109 -19
@@ 1,3 1,7 @@
% EOA HW01 - Solving TSP via evolutionary algorithms
% František Boháček
% 2025

# Intro

This is a report for hw01 for EOA course on CTU FEE. The goal


@@ 34,6 38,20 @@ one epoch for better granularity. This also ensures that if best candidate does 
into the next population, it is still captured by the algorithm and can be returned as
best candidate at the end. The implementation is in `eoa_lib/src/evolution.rs`.

For selections those algorithms have been implemented and tried:

- Roulette wheel selection
- Tournament selection
    - Here two parameters have been used, number of parents to choose from and probability to choose the best parent
    - When probability is set at 1, always the parent with best fitness wins, but if it's lower, it gives chance to worse parents as well
- Best by fitness selection

For replacmement, tournament and best by fitness has been implemented
and tried.

For pairing, only adjacent pairing has been implemented (taking parents
from selection and pairing first with second, third with fourth and so on)

## Local search
The local search implementation is implementation of the first improving strategy,
where the current best solution is being perturbated, until a better solution is found.


@@ 200,7 218,7 @@ To initialize the whole population:

## Minimal spanning tree
For using a minimal spanning tree the algorithm of Christofides and Serdyukov has been
chosen as an inspiration. But instead of trying to find an Eulerian tour on the minimal
chosen as an inspiration. But instead of trying to find best Eulerian tour on the minimal
spanning tree, a slightly different randomized approach has been taken. Specifically,
random node is chosen as the starting point and then edges are chosen out of the minimal
spanning tree to go through. If there are more edges to travel through, random one is chosen.


@@ 243,64 261,111 @@ The instances chosen from TSPLIB, all 2D Euclidean:
- ts225
- a280

For all the evolutionary algorithm runs, roulette wheel has been used as selection and truncation of best has
been selected for replacement. This is because they seemed to provide the best results.

During runs of the EA, the probabilities of mutations were changed throughout the run, by this formula:
`P = 0.5 * (1.0 + b / e)`, where `b` is iterations since better solution has been found and `e` is number
of iterations till the end (`max_iterations - current_iteration`), this makes the mutations more aggressive
later in the algorithm, as long as no better solutions are found, reaching even 100 % for some cases.

\newpage

## Comparing algorithms
To compare the algorithms,
first it has been ensured the algorithms were tweaked to produce the best results (best that the author
has been capable of). Then, they were run on 10 instances of TSP and averaged in the following chart:

![Best so far fitness of EA, LS, RS on eil76 instance.](./tsp_plotter/plots/algorithms_fitness_eil76.svg)
![Probability of reaching a target for EA, LS, RS on all instances.](./tsp_plotter/plots/algorithms_probability_all.svg){latex-placement="H"}
\newpage

Here is a an averaged fitness sample on eil76 instance:

![Probability of reaching a target for EA, LS, RS on all instances.](./tsp_plotter/plots/algorithms_probability_all.svg)
![Best so far fitness of EA, LS, RS on eil76 instance.](./tsp_plotter/plots/algorithms_fitness_eil76.svg){latex-placement="H"}

From the results, the LS is capable of finding a better solution early on, but then, the EA
catches up and produces a slightly better solution than LS. LS converges earlier than EA.

\newpage

## Comparing perturbations on LS
Four perturbations have been evaluated:

- Moving single city
- Swapping of two cities
- Reversing subsequence
- Combination of the above (specifically single random perturbation has been chosen to run)
- Combination of the above (each cycle, single random perturbation has been chosen to run from the above three)

![Best so far fitness of various LS on eil76 instance.](./tsp_plotter/plots/perturbations_fitness_eil76.json)
![Best so far fitness of various LS on eil76 instance.](./tsp_plotter/plots/perturbations_fitness_eil76.svg){latex-placement="H"}

![Probability of reaching a target for various LS on all instances.](./tsp_plotter/plots/perturbations_probability_all.json)
\newpage

![Probability of reaching a target for various LS on all instances. Standard deviation is not shown to make the plot more readable. While the swap average is barely above 0, its standard deviation would show it's capable of reaching the targets on some instances.](./tsp_plotter/plots/perturbations_probability_all.svg){latex-placement="H"}

from the experiment it seems the reversal of a subsequence is the best singular perturbation,
but the combination is even better. That could be explained by the fact that in cases the subsequence
reversal gets stuck somewhere, the other perturbations are able to recover.

\newpage

## Comparing crossovers
During evaluation of the various crossovers, it has become apparent that with the currently
chosen parameters of the algorithms, the dominant parts that are responsible for the good
convergence are: selection, perturbation and replacement, but not the crossover itself.
In other words, just replacing the crossovers produced similar results. For this reason,
the mutations probabilities have been lowered significantly for this comparison. This
then allows for comparison between the crossovers themselves.
It was even tried to use no crossover at all, passing through the parents and the convergence
has been similar.
For this reason, the mutations probabilities have been lowered significantly for this comparison.
This then allows for comparison between the crossovers themselves. Specifically the probability
of mutation is 0.001.

![Probability of reaching a target for various EA crosovers on all instances.](./tsp_plotter/plots/crossovers_probability_all.svg){latex-placement="H"}

From the runs, the edge recombination has produced the best results, with partially
mapped crossover being the second and cycle crossover being the worst.
mapped crossover being the second and cycle crossover being the worst. This could be explained
by the fact that edge recombination keeps all of the edges of parents, while mapped crossover makes sure
to keep edges only inside the two cross point interval. And cycle crossover might miss even more edges.

![Probability of reaching a target for various EA crosovers on all instances.](./tsp_plotter/plots/crossovers_probability_all.json)
While at the end CX and PMX are cathing up, I think this is mostly thanks to the mutations rather than
the crossovers themselves. When mutations are turned off completely, PMX and CX never reach targets as high as ERX.

\newpage

## Comparing representations

![Best so far fitness of various LS on eil76 instance.](./tsp_plotter/plots/representations_probability_all.json)
![Best so far fitness of binary and permutation representations on eil76 instance.](./tsp_plotter/plots/representations_fitness_eil76.svg){latex-placement="H"}

![Probability of reaching a target for binary and norma representations on all instances.](./tsp_plotter/plots/representations_probability_all.json)
![Probability of reaching a target for binary and permutation representations on all instances.](./tsp_plotter/plots/representations_probability_all.svg){latex-placement="H"}

On one hand the binary string representation is simpler, because it doesn't require special treatment
for crossovers. Any crossover leads to valid result. On the other hand since it's not so specific to the
given problem, it seems to lead to worse results than the node permutation. It seems that mainly the reverse
subsequence perturbation is making it that much better, as can be seen from comparison of various LS perturbations.
subsequence perturbation is making node permutation representation that much better,
as can be seen from comparison of various LS perturbations.

On top of its worse performance, the whole run is also slower for the same number of iterations, but this
might be just due to the poor implementation technique chosen for the binary string conversion to node
permutation.

\newpage

## Comparing heuristics
Both of the heuristics are capable of making initial solutions much better
than random ones. Here is a fitness graph with only the two heuristics.

![Probability of reaching a target for two heuristics on all instances.](./tsp_plotter/plots/heuristics_probability_all.json)
![Probability of reaching a target for two heuristics on all instances. The graph doesn't include standard deviation so that it is readable.](./tsp_plotter/plots/heuristics_probability_all.svg){latex-placement="H"}

From the results it can be seen the nearest neighbor heuristic performs better.
But this could be caused by the fact that minimal spanning tree has been used
along with a randomized approach. If more deterministic approach of finding
the best Eulerian path has been chosen, it's possible it would perform better.
From the results it can be seen the nearest neighbor heuristic performs slightly better
for the initialization, but at the end both heuristics lead to very similar results.
If the heuristic with minimal spanning tree has been enhanced by searching for better
solutions instead of randomly, it might outperform nearest neighbor.

The evolutionary algorithm still provides a visible improvement even for the heuristics
that are already very good starting points.
Moreover it has to be noted that the results show that with those heuristics, it's possible
to obtain a better result with the evolutionary algorithm overall that hasn't been reached
with algorithm with random initialization. This shows possible problem with the configuration,
the population stagnates. It could be possible to come up with ways to make the population not
stagnate so much so that the EA algorithm doesn't converge so early.

# Things done above minimal requirements



@@ 324,6 389,19 @@ the solutions are put to `tsp_hw01/solutions`.

`tsp_plotter` contains hard-coded presets for charts to create for the report.

As part of the work, apart from the standard library, third party libraries have been used, namely:
- nalgebra for working with vertices and matrices,
- plotters for plotting
- rand
- rand_distr
- thiserror for easing work with errors
- serde, serde_json for loading json files
- csv for loading csv files

## Running the algorithms

For run instructions, including generation of the plots in this report, see README.md.

# Usage of LLM

While I was working on this homework, I have used LLM for writing certain parts of the code,


@@ 350,3 428,15 @@ like outputs of ran commands. This means the LLM is able to iterate by
itself, without my intervention, for example when fixing errors.
On the other hand it can use a lot of tokens quickly unless I make sure
to run compact often.

# Resources

- Lectures of EOA
- Claude LLM
- TSPLIB (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)
- Genetic Algorithm Solution of the TSP Avoiding Special Crossover and Mutation from Gokturk Ucoluk (https://cw.fel.cvut.cz/wiki/_media/courses/a0m33eoa/du/ucoluk2013tspnew.pdf)
- Segregation of Various Crossover Operators in TSP using G.A by Jyoti Naveen and Rama Chawla (https://cw.fel.cvut.cz/b231/_media/courses/a0m33eoa/du/naween2013crossoversfortsp.pdf)
- https://en.wikipedia.org/wiki/2-opt
- https://en.wikipedia.org/wiki/Edge_recombination_operator
- https://ultraevolution.org/blog/pmx_crossover/
- https://en.wikipedia.org/wiki/Crossover_(evolutionary_algorithm)