Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ abstract class AbstractGeneticAlgorithm<T> : SearchAlgorithm<T>() where T : Indi
observers.remove(observer)
}

/** Call at the start of each searchOnce to begin a new generation scope. */
protected fun beginGeneration() {
observers.forEach { it.onGenerationStart() }
}

/** Call at the end of each searchOnce to report generation aggregates. */
protected fun endGeneration() {
val snapshot = population.toList()
val bestScore = snapshot.maxOfOrNull { score(it) } ?: 0.0
observers.forEach { it.onGenerationEnd(snapshot, bestScore) }
}

/** Start a new step inside current iteration. */
protected fun beginStep() {
observers.forEach { it.onStepStart() }
}

/** End current step and report aggregates. */
protected fun endStep() {
observers.forEach { it.onStepEnd() }
}

/**
* Called once before the search begins. Clears any old population and initializes a new one.
*/
Expand Down Expand Up @@ -184,12 +206,9 @@ abstract class AbstractGeneticAlgorithm<T> : SearchAlgorithm<T>() where T : Indi
/**
* Combined fitness of a suite computed only over [frozenTargets] when set; otherwise full combined fitness.
*/
protected fun score(w: WtsEvalIndividual<T>): Double {
if (w.suite.isEmpty()) return 0.0

// Explicitly use full combined fitness when solution source is POPULATION
if (config.gaSolutionSource == EMConfig.GASolutionSource.POPULATION) {
return w.calculateCombinedFitness()
public fun score(w: WtsEvalIndividual<T>): Double {
if (w.suite.isEmpty()) {
return 0.0
}

if (frozenTargets.isEmpty()) {
Expand All @@ -202,7 +221,9 @@ abstract class AbstractGeneticAlgorithm<T> : SearchAlgorithm<T>() where T : Indi
var sum = 0.0
frozenTargets.forEach { t ->
val comp = view[t]
if (comp != null) sum += comp.score
if (comp != null){
sum += comp.score
}
}
return sum
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import kotlin.math.max
* This is a more conservative variant compared to standard GAs, aiming to preserve
* the quality of solutions across generations and avoid degradation in performance.
*
* This class builds on top of [StandardGeneticAlgorithm] but overrides
* population update logic to enforce the monotonic condition.
* This class relies on the common GA utilities provided by
* [AbstractGeneticAlgorithm] (selection, crossover, mutation, population
* management), and overrides the generation update logic to enforce the
* monotonic condition.
*/
class MonotonicGeneticAlgorithm<T> : StandardGeneticAlgorithm<T>() where T : Individual {
class MonotonicGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {

override fun getType(): EMConfig.Algorithm {
return EMConfig.Algorithm.MonotonicGA
Expand Down Expand Up @@ -47,11 +49,16 @@ class MonotonicGeneticAlgorithm<T> : StandardGeneticAlgorithm<T>() where T : Ind
* or the time budget is exhausted.
*/
override fun searchOnce() {
beginGeneration()
// Freeze objectives for this generation
frozenTargets = archive.notCoveredTargets()
val n = config.populationSize

val nextPop: MutableList<WtsEvalIndividual<T>> = mutableListOf()
// Start next population with elites (elitism)
val nextPop: MutableList<WtsEvalIndividual<T>> = formTheNextPopulation(population)

while (nextPop.size < n) {
beginStep()
val p1 = tournamentSelection()
val p2 = tournamentSelection()

Expand All @@ -74,8 +81,8 @@ class MonotonicGeneticAlgorithm<T> : StandardGeneticAlgorithm<T>() where T : Ind

// Monotonic replacement rule:
// Keep offspring only if they're better than the parents
if (max(o1.calculateCombinedFitness(), o2.calculateCombinedFitness()) >
max(p1.calculateCombinedFitness(), p2.calculateCombinedFitness())
if (max(score(o1), score(o2)) >
max(score(p1), score(p2))
) {
nextPop.add(o1)
nextPop.add(o2)
Expand All @@ -85,12 +92,15 @@ class MonotonicGeneticAlgorithm<T> : StandardGeneticAlgorithm<T>() where T : Ind
}

if (!time.shouldContinueSearch()) {
endStep()
break
}
endStep()
}

// Replace the current population with the newly formed one
population.clear()
population.addAll(nextPop)
endGeneration()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ open class StandardGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T :
* Terminates early if the time budget is exceeded.
*/
override fun searchOnce() {
beginGeneration()
// Freeze objectives for this generation
frozenTargets = archive.notCoveredTargets()
val n = config.populationSize
Expand All @@ -50,6 +51,7 @@ open class StandardGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T :
val nextPop = formTheNextPopulation(population)

while (nextPop.size < n) {
beginStep()
val sizeBefore = nextPop.size

// Select two parents
Expand Down Expand Up @@ -78,12 +80,15 @@ open class StandardGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T :

// Stop early if time budget is exhausted
if (!time.shouldContinueSearch()) {
endStep()
break
}
endStep()
}

// Replace current population with the new one
population.clear()
population.addAll(nextPop)
endGeneration()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,30 @@ import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
* Default methods are no-ops so listeners can implement only what they need.
*/
interface GAObserver<T : Individual> {
/** Called at the start of a generation (one call to searchOnce). */
fun onGenerationStart() {}

/** Called at the start of a step inside a generation. */
fun onStepStart() {}

/** Called when one parent is selected. */
fun onSelection(sel: WtsEvalIndividual<T>) {}
/** Called immediately after crossover is applied to [x] and [y]. */
fun onCrossover(x: WtsEvalIndividual<T>, y: WtsEvalIndividual<T>) {}

/** Called immediately after mutation is applied to [wts]. */
fun onMutation(wts: WtsEvalIndividual<T>) {}

/**
* Called at the end of a generation (one call to searchOnce), with a snapshot of the final population
* and the best score according to the GA's internal scoring (e.g., frozen targets).
*/
fun onGenerationEnd(population: List<WtsEvalIndividual<T>>, bestScore: Double) {}

/**
* Called at the end of a step inside a generation.
*/
fun onStepEnd() {}
}


Loading