Skip to content

Conversation

@francastagna
Copy link
Collaborator

PR Summary

We implemented changes to the StandardGeneticAlgorithm:

  • With and without archive: allows selecting the source of the final solution.
  • Fitness update: introduces generation-frozen objectives (“frozen targets”).

We implemented changes to the AbstractGeneticAlgorithm:

  • GA Recorder/Observer: adds observer hooks to record GA events (selection, crossover and mutation events).

Main Changes

  • Configuration: added EMConfig.GASolutionSource and EMConfig.gaSolutionSource to choose the source of the final solution: ARCHIVE or POPULATION.
  • Base GA: rewrote the core logic in AbstractGeneticAlgorithm (population, elitism, tournament, mutate, xover, sampleSuite).
  • score function: computes fitness using generation-frozen objectives (frozenTargets).
  • Dual support: different behavior when the final solution is built from the archive vs. from the population.
  • StandardGeneticAlgorithm.searchOnce():
    • Freezes objectives at the start of each generation: frozenTargets = archive.notCoveredTargets().
    • Builds the next population via tournament selection, crossover, and mutation, respecting the configured probabilities.
  • GA Recorder/Observer:
    • Introduces observer hooks in the GA core to record operator metrics.
    • Enhances debugging and testing capabilities without altering the GA behaviour.

Fitness Update and Use of frozenTargets

Why

We align with Campos’ paper recommendation to keep objectives fixed within a generation so that all individuals compete under the same set of pending goals.
This prevents fitness drift when the objective set changes mid-generation (due to new coverages), which would make comparisons between individuals inconsistent.

How

  • At the start of each generation, capture frozenTargets = archive.notCoveredTargets().
  • The score of a test suite is computed only over those frozen objectives throughout the generation.
  • At the end of the generation (or at a snapshot), the set can be updated.

Advantages

  • Intra-generation consistency for tournament/selection.
  • Fair and stable comparisons; avoids individual evaluation depending on the exact timing of its assessment.

@jgaleotti jgaleotti requested a review from arcuri82 October 15, 2025 16:30
Copy link
Collaborator

@arcuri82 arcuri82 left a comment

Choose a reason for hiding this comment

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

@fncastagna thx for this first PR ;) see my comments

* - add: if size < maxSearchSuiteSize, sample + evaluate; add to suite (and archive GASolutionSource = ARCHIVE).
* - mod: mutate one random element; re-evaluate (or mutateAndSave if GASolutionSource = ARCHIVE).
*/
class DefaultMutationOperator : MutationOperator {
Copy link
Collaborator

Choose a reason for hiding this comment

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

the name might be misleading, as mutation here is just at test suite level and not test case level, isn't it? if so, either "suite" should be in the package name, or class name, somehow

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right it can be misleading, I'll add suite to the package name.

private const val OP_MOD = "mod"
}

override fun <T : Individual> mutateIndividual(
Copy link
Collaborator

Choose a reason for hiding this comment

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

i don't like the name of this function. this is doing a lot of things, and not just "mutate". name should be more descriptive

Copy link
Collaborator

Choose a reason for hiding this comment

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

however, looking at the old code, it was named like that as well... :) but i guess it is a good time now for a name update.

import org.evomaster.core.search.service.Sampler
import org.evomaster.core.search.service.mutator.Mutator

interface MutationOperator {
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this really a mtation operator? you are also evaluating fitness, and saving to archive based on that

score: (WtsEvalIndividual<T>) -> Double
): WtsEvalIndividual<T> {
val selected = randomness.choose(population, tournamentSize)
return selected.maxByOrNull { score(it) } ?: randomness.choose(population)
Copy link
Collaborator

Choose a reason for hiding this comment

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

how can this be null? would that be a bug, or something that could actually happen? here, if it happens it silently handled

/**
* Source to build the final GA solution.
* ARCHIVE: use current behavior (take tests from the archive).
* POPULATION: for GA algorithms, take the best individual from the final population.
Copy link
Collaborator

Choose a reason for hiding this comment

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

clarify here when evolving full test suites, and not tests in isolation


class StandardGeneticAlgorithmTest {

val injector: Injector = LifecycleInjector.builder()
Copy link
Collaborator

Choose a reason for hiding this comment

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

if you are having more than 1 test in this class, this is problematic. shared injector creates dependencies. should be recreated for each test, eg, in a @BeforeEach method


val epc = injector.getInstance(ExecutionPhaseController::class.java)
if (epc.isInSearch()) {
epc.finishSearch()
Copy link
Collaborator

Choose a reason for hiding this comment

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

not needed if injector is recreated per test

val solution = standardGeneticAlgorithm.search()

standardGeneticAlgorithm.search()
} finally {
Copy link
Collaborator

Choose a reason for hiding this comment

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

likely not needed if injector is recreated per test

@francastagna francastagna requested a review from arcuri82 October 17, 2025 16:53
@arcuri82 arcuri82 changed the base branch from master to pr-remote-francastagna October 20, 2025 11:12
@arcuri82 arcuri82 merged commit 3efeb01 into WebFuzzing:pr-remote-francastagna Oct 20, 2025
@arcuri82 arcuri82 mentioned this pull request Oct 20, 2025
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.

3 participants