Skip to content
Draft
28 changes: 27 additions & 1 deletion core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ class EMConfig {

enum class Algorithm {
DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW,
StandardGA, MonotonicGA, SteadyStateGA // These 3 are still work-in-progress
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA // GA variants still work-in-progress.
}

@Cfg("The algorithm used to generate test cases. The default depends on whether black-box or white-box testing is done.")
Expand Down Expand Up @@ -2742,6 +2742,32 @@ class EMConfig {
@Min(0.0)
var elitesCount: Int = 1

// Cellular GA neighborhood configuration
enum class CGANeighborhoodModel {
RING, L5, C9, C13
}

@Experimental
@Cfg("Cellular GA: neighborhood model (RING, L5, C9, C13)")
var cgaNeighborhoodModel: CGANeighborhoodModel = CGANeighborhoodModel.RING

/**
* Breeder GA: truncation fraction to build parents pool P'. Range (0,1].
*/
@Experimental
@Min(0.0)
@Max(1.0)
@Cfg("Breeder GA: fraction of top individuals to keep in parents pool (truncation).")
var breederTruncationFraction: Double = 0.5

/**
* Breeder GA: minimum number of parents to keep after truncation.
*/
@Experimental
@Min(1.0)
@Cfg("Breeder GA: minimum number of individuals in parents pool after truncation.")
var breederParentsMin: Int = 2

@Experimental
@Cfg("In REST APIs, when request Content-Type is JSON, POJOs are used instead of raw JSON string. " +
"Only available for JVM languages")
Expand Down
24 changes: 24 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,12 @@ class Main {
EMConfig.Algorithm.StandardGA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<GraphQLIndividual>>() {})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<GraphQLIndividual>>() {})

EMConfig.Algorithm.CellularGA ->
Key.get(object : TypeLiteral<CellularGeneticAlgorithm<GraphQLIndividual>>() {})


else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
Expand All @@ -670,6 +676,12 @@ class Main {

EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RPCIndividual>>() {})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<RPCIndividual>>() {})

EMConfig.Algorithm.CellularGA ->
Key.get(object : TypeLiteral<CellularGeneticAlgorithm<RPCIndividual>>() {})
else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand All @@ -694,6 +706,12 @@ class Main {

EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<WebIndividual>>() {})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<WebIndividual>>() {})

EMConfig.Algorithm.CellularGA ->
Key.get(object : TypeLiteral<CellularGeneticAlgorithm<WebIndividual>>() {})
else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand Down Expand Up @@ -728,6 +746,12 @@ class Main {
EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RestIndividual>>() {})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<RestIndividual>>() {})

EMConfig.Algorithm.CellularGA ->
Key.get(object : TypeLiteral<CellularGeneticAlgorithm<RestIndividual>>() {})

else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.evomaster.core.search.algorithms

import org.evomaster.core.EMConfig
import org.evomaster.core.search.Individual
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
import kotlin.math.max

/**
* Breeder Genetic Algorithm (BGA)
*
* Differences vs Standard GA:
* - Uses truncation selection to build a parents pool P'.
* - At each step, creates two offspring from two random parents in P',
* then randomly selects ONE of the 2 offspring to add to the next population.
*/
class BreederGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {

override fun getType(): EMConfig.Algorithm {
return EMConfig.Algorithm.BreederGA
}

override fun searchOnce() {
beginGeneration()
frozenTargets = archive.notCoveredTargets()
val n = config.populationSize

// Elitism base for next generation
val nextPop = formTheNextPopulation(population)

// Build parents pool P' by truncation on current population
val parentsPool = buildParentsPoolByTruncation(population)

while (nextPop.size < n) {
beginStep()
val p1 = randomness.choose(parentsPool)
val p2 = randomness.choose(parentsPool)

// Work on copies
val o1 = p1.copy()
val o2 = p2.copy()

if (randomness.nextBoolean(config.xoverProbability)) {
xover(o1, o2)
}
if (randomness.nextBoolean(config.fixedRateMutation)) {
mutate(o1)
}
if (randomness.nextBoolean(config.fixedRateMutation)) {
mutate(o2)
}

// Randomly pick one child to carry over
var chosen = o1
if (!randomness.nextBoolean()) {
chosen = o2
}
nextPop.add(chosen)

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

population.clear()
population.addAll(nextPop)
endGeneration()
}

private fun buildParentsPoolByTruncation(pop: List<WtsEvalIndividual<T>>): List<WtsEvalIndividual<T>> {
if (pop.isEmpty()) {
return pop
}

val sorted = pop.sortedByDescending { score(it) }
val k = max(config.breederParentsMin, (sorted.size * config.breederTruncationFraction).toInt())
return sorted.take(k)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.evomaster.core.search.algorithms

import org.evomaster.core.EMConfig
import org.evomaster.core.search.Individual
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual

/**
* Cellular GA faithful to the standard pseudocode.
* Neighborhood is mocked as a simple ring [left, self, right] for now.
*/
class CellularGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {

override fun getType(): EMConfig.Algorithm {
return EMConfig.Algorithm.CellularGA
}

override fun searchOnce() {
beginGeneration()
// Freeze targets for current generation
frozenTargets = archive.notCoveredTargets()

val n = population.size
val next: MutableList<WtsEvalIndividual<T>> = mutableListOf()

for (i in 0 until n) {
beginStep()
val p = population[i]

val neighbors = getNeighborhood(i)

// Cellular tournament selection within neighborhood
val x = neighborhoodTournament(neighbors)
val y = neighborhoodTournament(neighbors)

val o1 = x.copy()
val o2 = y.copy()
if (randomness.nextBoolean(config.xoverProbability)) {
xover(o1, o2)
}

var o: WtsEvalIndividual<T>
if (score(o1) >= score(o2)) {
o = o1
} else {
o = o2
}

if (randomness.nextBoolean(config.fixedRateMutation)) {
mutate(o)
}

var bestLocal: WtsEvalIndividual<T>
if (score(o) >= score(p)) {
bestLocal = o
} else {
bestLocal = p
}
next.add(bestLocal)
endStep()
}

population.clear()
population.addAll(next)
endGeneration()
}

/**
* Runs tournament selection restricted to a neighborhood subset.
*/
private fun neighborhoodTournament(neighbors: List<WtsEvalIndividual<T>>): WtsEvalIndividual<T> {
val sel = selectionStrategy.select(neighbors, config.tournamentSize, randomness, ::score)
observers.forEach { it.onSelection(sel) }
return sel
}

/**
* Returns the neighborhood list for a given index based on the configured model.
* The returned list always includes the current cell ("center") and neighbors,
* in the order customary for the chosen model.
*/
private fun getNeighborhood(index: Int): List<WtsEvalIndividual<T>> {
val model = config.cgaNeighborhoodModel
val neighborhood = Neighborhood<T>(population.size)
if (model == EMConfig.CGANeighborhoodModel.RING) {
return neighborhood.ringTopology(population, index)
}
if (model == EMConfig.CGANeighborhoodModel.L5) {
return neighborhood.linearFive(population, index)
}
if (model == EMConfig.CGANeighborhoodModel.C9) {
return neighborhood.compactNine(population, index)
}
return neighborhood.compactThirteen(population, index)
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.evomaster.core.search.algorithms

/**
* An interface that defines the four neighbourhood models used with the cGA
*/
interface NeighborModels<T> {

fun ringTopology(collection: List<T>, position: Int): List<T>

fun linearFive(collection: List<T>, position: Int): List<T>

fun compactNine(collection: List<T>, position: Int): List<T>

fun compactThirteen(collection: List<T>, position: Int): List<T>
}




Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.evomaster.core.search.algorithms

import org.evomaster.core.search.Individual
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
import kotlin.math.sqrt

/**
* Neighborhood utilities inspired by EvoSuite's Neighbourhood for Cellular GA.
* Returns original references (no copies) to be compatible with identity checks in tests.
*/
class Neighborhood<T : Individual>(private val populationSize: Int) : NeighborModels<WtsEvalIndividual<T>> {

/**
* Map grid coordinate (rowIndex, colIndex) to a valid 1D index in [0, populationSize),
* using wrap-around on both axes.
*/
private fun toWrappedLinearIndex(numCols: Int, rowIndex: Int, colIndex: Int): Int {
val numCells = populationSize
val numRows = (numCells + numCols - 1) / numCols
val wrappedRow = (rowIndex + numRows) % numRows
val wrappedCol = (colIndex + numCols) % numCols
val linearIndex = wrappedRow * numCols + wrappedCol
if (linearIndex < numCells) {
return linearIndex
}
return linearIndex % numCells
}

override fun ringTopology(population: List<WtsEvalIndividual<T>>, index: Int): List<WtsEvalIndividual<T>> {
val n = populationSize
val left = population[(index - 1 + n) % n]
val self = population[index]
val right = population[(index + 1) % n]
return listOf(left, self, right)
}

override fun linearFive(population: List<WtsEvalIndividual<T>>, index: Int): List<WtsEvalIndividual<T>> {
val n = populationSize
val cols = maxOf(1, sqrt(n.toDouble()).toInt())
val row = index / cols
val col = index % cols

val north = population[toWrappedLinearIndex(cols, row - 1, col)]
val south = population[toWrappedLinearIndex(cols, row + 1, col)]
val east = population[toWrappedLinearIndex(cols, row, col + 1)]
val west = population[toWrappedLinearIndex(cols, row, col - 1)]
val self = population[index]
return listOf(north, south, east, west, self)
}

override fun compactNine(population: List<WtsEvalIndividual<T>>, index: Int): List<WtsEvalIndividual<T>> {
val n = populationSize
val cols = maxOf(1, sqrt(n.toDouble()).toInt())
val row = index / cols
val col = index % cols

val n1 = population[toWrappedLinearIndex(cols, row - 1, col)]
val s1 = population[toWrappedLinearIndex(cols, row + 1, col)]
val e1 = population[toWrappedLinearIndex(cols, row, col + 1)]
val w1 = population[toWrappedLinearIndex(cols, row, col - 1)]
val nw = population[toWrappedLinearIndex(cols, row - 1, col - 1)]
val sw = population[toWrappedLinearIndex(cols, row + 1, col - 1)]
val ne = population[toWrappedLinearIndex(cols, row - 1, col + 1)]
val se = population[toWrappedLinearIndex(cols, row + 1, col + 1)]
val self = population[index]
return listOf(n1, s1, e1, w1, nw, sw, ne, se, self)
}

override fun compactThirteen(population: List<WtsEvalIndividual<T>>, index: Int): List<WtsEvalIndividual<T>> {
val n = populationSize
val cols = maxOf(1, sqrt(n.toDouble()).toInt())
val row = index / cols
val col = index % cols

val n1 = population[toWrappedLinearIndex(cols, row - 1, col)]
val s1 = population[toWrappedLinearIndex(cols, row + 1, col)]
val e1 = population[toWrappedLinearIndex(cols, row, col + 1)]
val w1 = population[toWrappedLinearIndex(cols, row, col - 1)]
val nw = population[toWrappedLinearIndex(cols, row - 1, col - 1)]
val sw = population[toWrappedLinearIndex(cols, row + 1, col - 1)]
val ne = population[toWrappedLinearIndex(cols, row - 1, col + 1)]
val se = population[toWrappedLinearIndex(cols, row + 1, col + 1)]
val nn = population[toWrappedLinearIndex(cols, row - 2, col)]
val ss = population[toWrappedLinearIndex(cols, row + 2, col)]
val ee = population[toWrappedLinearIndex(cols, row, col + 2)]
val ww = population[toWrappedLinearIndex(cols, row, col - 2)]
val self = population[index]
return listOf(n1, s1, e1, w1, nw, sw, ne, se, nn, ss, ee, ww, self)
}
}


Loading