Skip to content

Update the evolutionary core to the fitness order, the selection inventory and a termination module - #110

Merged
FelixLaarmann merged 1 commit into
developfrom
feature/ea-core
Sep 2, 2026
Merged

Update the evolutionary core to the fitness order, the selection inventory and a termination module#110
FelixLaarmann merged 1 commit into
developfrom
feature/ea-core

Conversation

@FelixLaarmann

Copy link
Copy Markdown
Member

Problem:

The evolutionary package carries a driver, a fitness adapter, and one selection method per role, and
all of them read the fitness as a number. A search that compares several objectives has no place
there: ParetoFitnessComparator reports 0 for both a tie and an incomparable pair, its
scalarize sums the objectives, and every component that sorts reads that sum, so a Pareto run is
a weighted-sum run under another name. The best-so-far bookkeeping asks for the fittest member
rather than for a member that beats the incumbent. A termination condition is a closure the caller
writes by hand, and nothing states what a fitness value may be, so a measurement that failed and one
that is infinitely good travel the same path.

Changes:

  • The fitness of an individual is a value in a partially ordered set, and a comparator decides the
    order. compare answers GREATER, LESS, EQUAL or INCOMPARABLE, so a tie and an
    incomparable pair are no longer the same answer. ScalarFitnessComparator is the total order
    over reals and reports INCOMPARABLE for nan, ParetoFitnessComparator is the componentwise
    order over vectors.
  • A scalarization is no longer part of a comparator. It maps into the strictly positive reals and
    is passed explicitly to the components that draw proportionally, and only to those, so choosing
    one is a visible decision rather than a summed default.
  • The selection inventory covers the three roles: FitnessProportionalSelection,
    TournamentSelection and RankBasedSelection for parents, FitnessBasedReplacement and
    GenerousConservativeReplacement for survivors. dominance_fronts is the shared construction,
    the canonical linearization of a partial order into ranks.
  • RankBasedSelection keeps the ranking of the population it was last asked about. A driver asks
    for one pair per variation pass, every pass of a generation over the same population, and
    building the fronts is quadratic, so answering each pass from scratch multiplies that cost by the
    number of passes. The stored ranking answers again only if the population holds the same
    individuals in the same order and the fitness mapping and the comparator are the very objects of
    the previous call. On the shipped example, this is 2.3 seconds instead of 37.6 for three
    generations of 250 individuals, measured on one machine in one session.
  • A new module termination with Generations, TargetFitness, NoImprovement and Any/All
    combinators, so a condition is a component rather than a closure.
  • The best-so-far is read from the parents and the offspring together rather than from the
    survivors. A survivor selection keeps mu individuals and is a parameter of the search, so under
    a partial order it may drop an offspring that beats the incumbent, and a dropped individual never
    gets a second chance to be reported. Among the members that do beat the incumbent, a maximal one
    is taken, which keeps the incumbent monotone where a scan for one maximal element would not.
  • ExpScalarization reports a finite fitness that overflows the exponential instead of returning
    infinity. A proportional draw reads infinity as an infinitely good individual and gives it the
    whole mass. The underflowing end is reported by the drawing component, which already refuses a
    weight of zero.
  • The driver checks what its components return: the initializer for the population size, the
    survivor selection for the size and for closure over parents and offspring.
  • The shipped example runs 100 generations from seed 4 and reaches a training error of 1.3e-07,
    where the previous configuration kept the error of its initial population. Truncation over
    parents and offspring together gives every copy of an individual a place of its own, and a pass
    that neither recombines nor mutates hands a parent on unchanged, so the multiplicity of the
    fittest individuals grows and a run can reach a population that holds few distinct terms. Which
    seed is used is a property of the run rather than of the components.
  • Breaking: SimpleGeneticProgramming is replaced by EvolutionarySearch, and Evolutionary,
    Selection and AgeBasedReplacement are gone. compare returns a Comparison rather than an
    int, and a comparator no longer carries scalarize or sort_key.
  • Behavior: a seeded run does not reproduce its previous results, since the selection methods are
    different components and the bookkeeping reads a different set. evolutionary_best can return an
    offspring the survivor selection did not keep, and last_improvement can advance in a generation
    whose population did not improve, so NoImprovement stops such a run at a different point.
    ExpScalarization raises where it returned infinity before.

…ntory and a termination module

Problem:

The evolutionary package carries a driver, a fitness adapter and one selection method per role, and
all of them read the fitness as a number. A search that compares several objectives has no place
there: selection ranks by a scalar, the driver's best-so-far bookkeeping asks for the fittest member
rather than for a member that beats the incumbent, and a termination condition is a closure the
caller writes by hand. The components also state no contract about what a fitness value may be, so a
measurement that failed and a measurement that is infinitely good travel through the same path.

Changes:

- The fitness of an individual is a value in a partially ordered set, and a comparator decides the
  order. ScalarFitnessComparator is the total order over reals, ParetoFitnessComparator the
  componentwise order over vectors. A scalarization maps into the strictly positive reals where a
  component draws proportionally, and only there.
- The selection inventory covers the three roles: FitnessProportionalSelection, TournamentSelection
  and RankBasedSelection for parents, FitnessBasedReplacement and GenerousConservativeReplacement
  for survivors. dominance_fronts is the shared construction, the canonical linearization of a
  partial order into ranks.
- RankBasedSelection keeps the ranking of the population it was last asked about. A driver asks for
  one pair per variation pass, every pass of a generation over the same population, and building
  the fronts is quadratic, so answering each pass from scratch multiplied that cost by the number
  of passes. The stored ranking answers again only if the population holds the same individuals in
  the same order and the fitness mapping and the comparator are the very objects of the previous
  call. On the shipped example this is 2.3 seconds instead of 37.6 for three generations of 250
  individuals, measured on one machine in one session.
- A new module termination with Generations, TargetFitness, NoImprovement and Any/All combinators,
  so a condition is a component rather than a closure.
- The best-so-far is read from the parents and the offspring together rather than from the
  survivors. A survivor selection keeps mu individuals and is a parameter of the search, so under a
  partial order it may drop an offspring that beats the incumbent, and a dropped individual never
  gets a second chance to be reported. Among the members that do beat the incumbent a maximal one
  is taken, which keeps the incumbent monotone where a scan for one maximal element would not.
- ExpScalarization reports a finite fitness that overflows the exponential instead of returning
  infinity. A proportional draw reads infinity as an infinitely good individual and gives it the
  whole mass. The underflowing end is reported by the drawing component, which already refuses a
  weight of zero.
- The driver checks what its components return: the initializer for the population size, the
  survivor selection for the size and for closure over parents and offspring.
- The shipped example runs 100 generations from seed 4 and reaches a training error of 1.3e-07,
  where the previous configuration kept the error of its initial population. Truncation over
  parents and offspring together gives every copy of an individual a place of its own, and a pass
  that neither recombines nor mutates hands a parent on unchanged, so the multiplicity of the
  fittest individuals grows and a run can reach a population that holds few distinct terms. Which
  seed does that is a property of the run rather than of the components.

Behavior:

- A seeded run does not reproduce its previous results. The selection methods are different
  components, and the driver's bookkeeping reads a different set.
- evolutionary_best can return an offspring the survivor selection did not keep, and
  last_improvement can advance in a generation whose population did not improve, so NoImprovement
  stops such a run at a different point.
- ExpScalarization raises where it returned infinity before.

@tudo-seal-workflows tudo-seal-workflows Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark CoSy

Details
Benchmark suite Current: ef90aef Previous: be71b5d Ratio
benchmarks/test_benchmark_maximal_elements.py::test_benchmark_maximal_elements 9.535558257894504 iter/sec (stddev: 0.0004587768100170705) 9.608281351562356 iter/sec (stddev: 0.010017085420616013) 1.01
benchmarks/test_benchmark_maze.py::test_benchmark_maze 3.5747029431505406 iter/sec (stddev: 0.02532070555591522) 3.9235166334572096 iter/sec (stddev: 0.018797551923491352) 1.10
benchmarks/test_benchmark_maze_contains.py::test_benchmark_maze_contains 3.3518636219954354 iter/sec (stddev: 0.024308568544443042) 3.5391214334611916 iter/sec (stddev: 0.025877608823890046) 1.06
benchmarks/test_benchmark_maze_loopfree.py::test_benchmark_maze_loopfree 3.700453691164463 iter/sec (stddev: 0.01563164705813299) 3.8477221717914887 iter/sec (stddev: 0.02072136799063595) 1.04

This comment was automatically generated by workflow using github-action-benchmark.

@codecov-commenter

codecov-commenter commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.90388% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.37%. Comparing base (1a6dcbf) to head (ef90aef).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/cosy/evolutionary_algorithms/fitness.py 94.50% 4 Missing and 1 partial ⚠️
tests/test_evolutionary.py 98.84% 2 Missing and 2 partials ⚠️
src/cosy/evolutionary_algorithms/selection.py 97.79% 2 Missing and 1 partial ⚠️
src/cosy/evolutionary_algorithms/termination.py 95.83% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #110      +/-   ##
===========================================
+ Coverage    88.76%   91.37%   +2.60%     
===========================================
  Files           69       73       +4     
  Lines         7435     8165     +730     
  Branches       829      844      +15     
===========================================
+ Hits          6600     7461     +861     
+ Misses         739      627     -112     
+ Partials        96       77      -19     
Flag Coverage Δ
macos-latest-3.10 91.34% <98.90%> (+2.61%) ⬆️
macos-latest-3.11 91.36% <98.90%> (+2.60%) ⬆️
macos-latest-3.12 91.36% <98.90%> (+2.60%) ⬆️
macos-latest-3.13 91.36% <98.90%> (+2.60%) ⬆️
ubuntu-latest-3.10 91.34% <98.90%> (+2.61%) ⬆️
ubuntu-latest-3.11 91.36% <98.90%> (+2.60%) ⬆️
ubuntu-latest-3.12 91.36% <98.90%> (+2.60%) ⬆️
ubuntu-latest-3.13 91.36% <98.90%> (+2.60%) ⬆️
windows-latest-3.10 ?
windows-latest-3.11 ?
windows-latest-3.12 91.36% <98.90%> (+2.60%) ⬆️
windows-latest-3.13 ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@FelixLaarmann
FelixLaarmann merged commit 5b96284 into develop Sep 2, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants