~ruther/ctu-fee-eoa

a5d7436a642358e39c1d3f3280c77fbc8ee73558 — Rutherther 5 days ago e72ea86
chore: update report
1 files changed, 112 insertions(+), 71 deletions(-)

M codes/report.md
M codes/report.md => codes/report.md +112 -71
@@ 1,4 1,4 @@
% EOA HW01 - Solving TSP via evolutionary algorithms
% EOA HW02 - Solving constrained problems using evolutionary algorithms
% František Boháček
% 2025



@@ 18,25 18,35 @@ algorithms and the results on problems taken from "Problem Definitions and Evalu

## Stochastic ranking

One of the methods to solve constrained problems.
This is one method to solve constrained problems.
Stochastic ranking first sorts the individuals based on a stochastic
sorting. Then, those ranked individuals are put to a tournament selection
with their new rankings, instead of using their original evaluation.

Stochastic ranking uses similar structure as bubble sort. It iterates through the population
Similar algorithms structure as bubble sort is utilized. It iterates through the population
and swaps to elements when one is better than the other (lower fitness).
But it does that only with probability `p`. With probability `1 - p` it instead compares
the constraint violations of both elements.

Then, the selection takes into account ranking based on the list sorted through stochastic ranking.
Generational replacement has been used to easily ensure that the stochastic ranking order is still respected.
Generational replacement has been used to easily ensure that the stochastic ranking order is still respected
during replacement.
It would also be possible to use other replacement strategies, but then the stochastic ranking would have
to run twice after the offsprings are made.

Stochastic ranking is implemented in `eoa_lib/src/constraints.rs`
Stochastic ranking is implemented in `eoa_lib/src/constraints.rs`.

## Unconstrained multi objective evolution (NSGA-II)

As second method, NSGA-II has been used to solve the constrained problems.
As a second method, NSGA-II has been used to solve the constrained problems.
It's mainly meant to solve multi-objective problems. But the constraint problem
can be mapped to it by making the original objective the first objective and
weighted sum of constraints as second objective.
It works based on non-dominated fronts.
All elements in one front are not dominated between each other. A dominates B if
and only if A is not worse than B in all objectives and A is better than B in at least
one objective.
one objective. To obtain the first front, non-dominates solutions are obtained from
the whole population. Then they are removed and the process is repeated to get second
front and so on.

NSGA-II first sorts the elements based on the non dominated fronts. Then, within each front,
crowding distance is calculated. Crowding distance is used to discourage solutions that are


@@ 45,10 55,18 @@ to the front number and within same front, according to the crowding distance. H
distance is preferred.

As part of NSGA-II, tournament selection is used and for replacement, best replacement is used,
keeping only the best individuals. Specific parameters of using 5 candidates for tournament selection
and probability of 0.99 for choosing the best candidate out of 5.

Multi objective evolution is implemented in `eoa_lib/src/multi_objective_evolution.rs`
keeping only the best individuals from both original population and newly made offsprings.
Specific parameters of using 5 candidates for tournament selection and probability of 0.99
for choosing the best candidate out of 5.

Multi objective evolution is implemented in `eoa_lib/src/multi_objective_evolution.rs`.
Note that at first the author has used the original evolution function as base, but later realized
it's wrong. In NSGA-II, the whole population has to be reevaluated after offsprings
are made. The original function evaluated offsprings only to save on count of function
evaluations. But that meant two different front sortings were being compared in the replacement,
one for the current population and the other for offsprings.
Consider `population.evaluate(); offsprings.evaluate();` vs `population.append(offsprings).evaluate()`,
where in case of NSGA-II `evaluate()` means to obtain the non-dominated fronts and crowding distances.

# Used operators



@@ 56,64 74,94 @@ Since the chosen problems are real problems, real representation has been used.

For mutation, the solution is changed with random sample from normal distribution
with zero mean.
The standard mean has been chosen differently based on the problems.
The standard deviation has been chosen differently based on the problem.

As for crossover, arithmetical crossover is used.
Arithmetical crossover chooses random `\alpha` between
As for crossover, arithmetical crossover is utilized.
Arithmetical crossover chooses random $\alpha$ between
0 and 1. Then, the offspring's i-th component is
`\alpha \cdot p_{1i} + (1 - \alpha) \cdot p_{2i}`,
where `p_{1i}` (`p_{2i}`) is first (second) parent's i-th
$\alpha \cdot p_{1i} + (1 - \alpha) \cdot p_{2i}$,
where $p_{1i}$ ($p_{2i}$) is first (second) parent's i-th
component respectively.

# Making NSGA-II behave better
# Making NSGA-II behave better for constrained problems

In the default implementation, NSGA-II is not suitable for solving constrained
In the default implementation, NSGA-II is not so suitable for solving constrained
problems. This is because all the feasible individuals will be at 0 in the
objective with constraints sum.
objective with constraints sum and each front will have only one feasible candidate.

Because of this, a very simple idea can arise to make it better - if multiple feasible solutions
are sufficiently distant, their constraint function evaluations could also differ
if they are allowed to go below zero. That feasible solutions can have higher crowding distances.
But this seems to make sense only for cases where there is objective per constraint. Otherwise
a constraint that is far below zero would win over and the other constraints might not have chance
to get satisfied.
## Custom solution

# Results
To overcome the limitations, a solution of using an archive of feasible solutions is proposed.
The idea of archive is similar to the SPEA2 algorithm, but the solutions kept in it are different.
Specifically it has been chosen that the archive will have only feasible solutions. The archive
has a fixed size and it contains the best known solutions (based on the original problem objective).
After each iteration it's updated, joining the previous archive and new population and leaving only
the best feasible candidates.

Then, during pairing/crossover infesiable solutions might get replaced. The logic is as follows:

1. If only one of the parents is infeasible, replace it with a feasible with probability $p_1$
2. If both parents are infesiable, replace the first one with probability $p_{21}$ and then, if the
first one is replaced, replace the second one with probability $p_{22}$.

The probabilities were chosen as: $p_1 = 0.4, p_{21} = 0.6, p_{22} = 0.3$. The idea behind these
particular values has been to have majority of pair with at least one feasible solution.

In the code this is implemented as a crossover wrapper, because it was the easiest, even though logically
it would make more sense to put this to pairing step. But the implementation for pairing step is that
it produces indices in the original population. Because of that it is not possible to add new individuals.
The implementation is located in `src/constr_hw02/feasible_crossover_wrapper.rs`

## Using NSGA-II for constrained problems

NSGA-II can be modified for constrained problems. While the idea is mainly that
multiple objectives are used and on top of that, there are constraints, the idea
can be expanded to the problem that's solved here. Specifically still the two
objectives can be used, but the definition of `i` dominates `j`
is changed as follows, let's say we have solutions `i` and `j`, solution
`i` constraint-dominates `j` if any of the following holds:

1. Solution `i` and `j` are both feasible and solution `i` dominates `j`.
2. Solution `i` is feasible and solution `j` is not.
3. Solutions `i` and `j` are both infesiable, but each constraint is violated
less by `i` than by `j`.

Every problem with given configuration has been ran 10 times. The initialization
has been done randomly, but within given bounds different for each problem:
It would be possible to use just one objective function, being the original objective,
but then we no longer have a multi objective problem.

- g06 - `0, 0` `50, 50`
- g08 - `0, 0` `10, 10`
- g11 - `-1, -1` `1, 1`
- g24 - `0, 0` `3, 4`
This is implemented along with NSGA-II at `src/eoa_lib/multi_objective_evolution.rs`.

First also bounding the crossover result and mutation has been tried,
but it turned out it doesn't even help much, so that has been removed.
(the method for bounding has been to first try the mutation/crossover
maximally 5 times if it's out of bounds and after last try, it has
been bounded forcibly)
# Results

Every configuration has been ran 10 times to accomodate for inherent randomness. The initialization
has been done randomly, uniformly within bounds given in the definitions of the problems.
Later in the algorithm run, candidates could escape those bounds, they weren't penalized for it
nor disqualified in the results. It has been tried to also bound the results of mutation and
crossover, but it was abandoned as it seems the algorithms still stay within the bounds, because
they contain better solutions. This gives more challenge to the algorithms.

For stochastic ranking approach, following parameters have been used:
arithmetic crossover, normal distribution mutation with probability 0.1,
tournament selection with 5 individuals and 0.95 probability
of choosing the best one. Weights of 1e9 have been chosen as with 1.0 there
have been problems with g11, probably due to very small numbers that were being
summed together and rounded.
of choosing the best one. All weights have been chosen as `1.0`.

As for runtime parameters, population had 500 individuals, there are 1000 iterations,
500 parents in each iteration. The number of iterations in stochastic ranking is 2 * population.
Probability of comparing fitness is 0.45. This is all with exception of g11, where different parameters
have been used to so that feasible solution is found. Specifically epsilon has been chosen to be `0.01`,
population size 1250, 1000 parents, 500 iterations, 2000 for number of iterations of stochastic
ranking and the probability 0.05. Still, feasible solution has not been found on every run. The results
include only runs where solution has been found.
The following standard deviations have been chosen:
Arithmetic crossover is used.
The offsprings mutate with probability of `0.1`.
Mutation uses a sample from normal distribution with mean of `0.0`
and standard deviation given according to the following table:

- g04 - `1.0`
- g05 - `10.0`
- g06 - `1.0`
- g08 - `0.5`
- g11 - `0.01 / 50` (epsilon divided by 50)
- g09 - `1.0`
- g11 - `0.01` (epsilon is `0.00015`)
- g24 - `0.1`
- g21 - `10.0`

TODO parameters
As for runtime parameters, population had 500 individuals, there are 1000 iterations,
500 parents in each iteration. The number of iterations in stochastic ranking is 2 * population.
Probability of comparing fitness in stochastic ranking is 0.45.

For NSGA-II
TODO


@@ 125,38 173,31 @@ The following plot compares:
- Stochastic ranking
- NSGA-II
- NSGA-II with multiple objectives
- NSGA-II with multiple objectives, allowing constraints under 0

As can be seen, NSGA with multiple objectives behaves worse. This is
possibly because the algorithm can lower second and third objective
more easily than lowering the first one or lowering all three at the same
time. This is then even more visible for allowing the constraints to
go under 0. So the simple idea has failed.
- NSGA-II customized archive
- NSGA-II constrain-optimized tournament

TODO add the plot
TODO add discussion

## Comparing g11 with g06

`g11` is special in the regard that it has an
equality condition. Because of that, it can make
sense to compare it with other ones. Here's the result:

As can be observed, `g11` cannot be optimized easily
as most of the solutions are going to be infeasible.
Many of the runs, no feasible solutions are found. And
since there is only a small range of values to go to when
in feasible space, it can easily happen mutated solution
again goes outside of feasible space. Because of this, the
`p` parameter had to be significantly lowered and this leads
to optimizing for feasible solutions more than for better solutions.
equality condition that is harder to satisfy.
So here only `g11` is compared with `g06`.
A fraction of feasible solutions for each iteration
has been obtained, as well as average constraint violation.

Here is a plot with fraction of feasible solutions in iteration for both
problems:

## Comparing TODO insert hard problem with g11

# Tasks done above minimal requirements

- 1 point - Multi-objective approach with more objectives
- Tried to improve NSGA with very simple approach, but failed
- 1 point - Improve NSGA with archive
- 1 point - Comparison on more complex problems
- 1 point - NSGA-II with modified tournament operator (constrained domination)

# Code structure
Rust has been chosen as the language. There are four subdirectories, `eoa_lib`, `tsp_hw01`, `constr_hw02` and `tsp_plotter`.


@@ 189,7 230,7 @@ For run instructions, including generation of the plots in this report, see READ

LLM has been used similarly to how in HW01. The only thing that changed
is that Gemini has also been utilized, not only Claude.
(Students got it now for free for a year :))
(there was an offer for students to get it for free for a year :))

# Resources