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
6 changes: 5 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, BreederGA, CellularGA, OnePlusLambdaLambdaGA // GA variants still work-in-progress.
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA // 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 @@ -1556,6 +1556,10 @@ class EMConfig {
@Probability
var fixedRateMutation = 0.04

@Cfg("Define the number of offspring (λ) generated per generation in (μ,λ) Evolutionary Algorithm")
@Min(1.0)
var muLambdaOffspringSize = 30

@Cfg("Define the maximum number of tests in a suite in the search algorithms that evolve whole suites, e.g. WTS")
@Min(1.0)
var maxSearchSuiteSize = 50
Expand Down
8 changes: 8 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,8 @@ class Main {
EMConfig.Algorithm.StandardGA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<GraphQLIndividual>>() {})

EMConfig.Algorithm.MuLambdaEA ->
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.MuLambdaEvolutionaryAlgorithm<GraphQLIndividual>>(){})
EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<GraphQLIndividual>>() {})

Expand Down Expand Up @@ -679,6 +681,8 @@ class Main {

EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RPCIndividual>>() {})
EMConfig.Algorithm.MuLambdaEA ->
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.MuLambdaEvolutionaryAlgorithm<RPCIndividual>>(){})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<RPCIndividual>>() {})
Expand Down Expand Up @@ -712,6 +716,8 @@ class Main {

EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<WebIndividual>>() {})
EMConfig.Algorithm.MuLambdaEA ->
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.MuLambdaEvolutionaryAlgorithm<WebIndividual>>(){})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<WebIndividual>>() {})
Expand Down Expand Up @@ -754,6 +760,8 @@ class Main {

EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RestIndividual>>() {})
EMConfig.Algorithm.MuLambdaEA ->
Key.get(object : TypeLiteral<org.evomaster.core.search.algorithms.MuLambdaEvolutionaryAlgorithm<RestIndividual>>(){})

EMConfig.Algorithm.BreederGA ->
Key.get(object : TypeLiteral<BreederGeneticAlgorithm<RestIndividual>>() {})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.evomaster.core.search.algorithms

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

/**
* (μ, λ) Evolutionary Algorithm.
*
* Population P of size μ is evolved by generating exactly λ offspring via mutation
* and selecting the best μ individuals only from the offspring set.
*/
class MuLambdaEvolutionaryAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {

override fun getType(): EMConfig.Algorithm = EMConfig.Algorithm.MuLambdaEA

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

val mu = config.populationSize
val lambda = config.muLambdaOffspringSize

val offspring: MutableList<WtsEvalIndividual<T>> = mutableListOf()

val perParent = lambda / mu
for (p in population) {
for (i in 0 until perParent) {
beginStep()
val o = p.copy()
if (randomness.nextBoolean(config.fixedRateMutation)) {
mutate(o)
}
offspring.add(o)
if (!time.shouldContinueSearch()) {
endStep()
break
}
endStep()
}
if (!time.shouldContinueSearch()) break
}

// Select best μ only from offspring
val next = offspring.sortedByDescending { score(it) }
.take(mu)
.map { it.copy() }
.toMutableList()

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


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

import com.google.inject.Injector
import com.google.inject.Key
import com.google.inject.Module
import com.google.inject.TypeLiteral
import com.netflix.governator.guice.LifecycleInjector
import org.evomaster.core.BaseModule
import org.evomaster.core.EMConfig
import org.evomaster.core.TestUtils
import org.evomaster.core.search.algorithms.observer.GARecorder
import org.evomaster.core.search.algorithms.onemax.OneMaxIndividual
import org.evomaster.core.search.algorithms.onemax.OneMaxModule
import org.evomaster.core.search.algorithms.onemax.OneMaxSampler
import org.evomaster.core.search.service.ExecutionPhaseController
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class MuLambdaEvolutionaryAlgorithmTest {

private lateinit var injector: Injector

@BeforeEach
fun setUp() {
injector = LifecycleInjector.builder()
.withModules(* arrayOf<Module>(OneMaxModule(), BaseModule()))
.build().createInjector()
}

// Verifies that the (μ,λ) EA can find the optimal solution for the OneMax problem
@Test
fun testMuLambdaEAFindsOptimum() {
TestUtils.handleFlaky {
val ea = injector.getInstance(
Key.get(object : TypeLiteral<MuLambdaEvolutionaryAlgorithm<OneMaxIndividual>>() {})
)

val config = injector.getInstance(EMConfig::class.java)
config.populationSize = 5
config.muLambdaOffspringSize = 10
config.maxEvaluations = 10000
config.stoppingCriterion = EMConfig.StoppingCriterion.ACTION_EVALUATIONS

val epc = injector.getInstance(ExecutionPhaseController::class.java)
epc.startSearch()
val solution = ea.search()
epc.finishSearch()

assertEquals(1, solution.individuals.size)
assertEquals(OneMaxSampler.DEFAULT_N.toDouble(), solution.overall.computeFitnessScore(), 0.001)
}
}

// Edge Case: CrossoverProbability=0 and MutationProbability=1
@Test
fun testNoCrossoverWhenProbabilityZero_MuLambdaEA() {
TestUtils.handleFlaky {
val ea = injector.getInstance(
Key.get(object : TypeLiteral<MuLambdaEvolutionaryAlgorithm<OneMaxIndividual>>() {})
)

val rec = GARecorder<OneMaxIndividual>()
ea.addObserver(rec)

val config = injector.getInstance(EMConfig::class.java)
config.gaSolutionSource = EMConfig.GASolutionSource.POPULATION
config.maxEvaluations = 100_000
config.stoppingCriterion = EMConfig.StoppingCriterion.ACTION_EVALUATIONS
config.populationSize = 5
config.muLambdaOffspringSize = 10 // divisible by mu
config.xoverProbability = 0.0 // no crossover used in (μ,λ)
config.fixedRateMutation = 1.0 // force mutation

ea.setupBeforeSearch()
ea.searchOnce()

val nextPop = ea.getViewOfPopulation()
assertEquals(config.populationSize, nextPop.size)

// crossover unused
assertEquals(0, rec.xoCalls.size)
// offspring mutated: perParent * µ == λ when divisible
val perParent = config.muLambdaOffspringSize / config.populationSize
assertEquals(perParent * config.populationSize, rec.mutated.size)
}
}

// Edge Case: MutationProbability=0 and CrossoverProbability=1
@Test
fun testNoMutationWhenProbabilityZero_MuLambdaEA() {
TestUtils.handleFlaky {
val ea = injector.getInstance(
Key.get(object : TypeLiteral<MuLambdaEvolutionaryAlgorithm<OneMaxIndividual>>() {})
)

val rec = GARecorder<OneMaxIndividual>()
ea.addObserver(rec)

val config = injector.getInstance(EMConfig::class.java)
config.gaSolutionSource = EMConfig.GASolutionSource.POPULATION
config.maxEvaluations = 100_000
config.stoppingCriterion = EMConfig.StoppingCriterion.ACTION_EVALUATIONS
config.populationSize = 5
config.muLambdaOffspringSize = 10
config.xoverProbability = 1.0 // irrelevant for (μ,λ)
config.fixedRateMutation = 0.0 // disable mutation

ea.setupBeforeSearch()
ea.searchOnce()

val nextPop = ea.getViewOfPopulation()
assertEquals(config.populationSize, nextPop.size)
assertEquals(0, rec.xoCalls.size)
assertEquals(0, rec.mutated.size)
}
}

// One iteration properties: population size, best-µ selection from offspring, mutation count
@Test
fun testNextGenerationIsTheBestMuFromOffspringOnly() {
TestUtils.handleFlaky {
val ea = injector.getInstance(
Key.get(object : TypeLiteral<MuLambdaEvolutionaryAlgorithm<OneMaxIndividual>>() {})
)

val rec = GARecorder<OneMaxIndividual>()
ea.addObserver(rec)

val config = injector.getInstance(EMConfig::class.java)
config.populationSize = 5
config.muLambdaOffspringSize = 10 // divisible by mu -> perParent = 2
config.xoverProbability = 0.0
config.fixedRateMutation = 1.0

ea.setupBeforeSearch()
ea.searchOnce()

val finalPop = ea.getViewOfPopulation()
val mu = config.populationSize

// 1) population size remains µ
assertEquals(mu, finalPop.size)

// 2) final population equals best-µ from offspring only
val offspring = rec.mutated.toList()
val expectedScores = offspring
.map { ea.score(it) }
.sortedDescending()
.take(mu)
val finalScores = finalPop
.map { ea.score(it) }
.sortedDescending()
assertEquals(expectedScores, finalScores)

// 3) mutation count equals number of created offspring
val perParent = config.muLambdaOffspringSize / config.populationSize
assertEquals(perParent * config.populationSize, rec.mutated.size)
}
}
}


3 changes: 2 additions & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ There are 3 types of options:
|`addPreDefinedTests`| __Boolean__. Add predefined tests at the end of the search. An example is a test to fetch the schema of RESTful APIs. *Default value*: `true`.|
|`addTestComments`| __Boolean__. Add summary comments on each test. *Default value*: `true`.|
|`advancedBlackBoxCoverage`| __Boolean__. Apply more advanced coverage criteria for black-box testing. This can result in larger generated test suites. *Default value*: `true`.|
|`algorithm`| __Enum__. The algorithm used to generate test cases. The default depends on whether black-box or white-box testing is done. *Valid values*: `DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW, StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA`. *Default value*: `DEFAULT`.|
|`algorithm`| __Enum__. The algorithm used to generate test cases. The default depends on whether black-box or white-box testing is done. *Valid values*: `DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW, StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA`. *Default value*: `DEFAULT`.|
|`allowInvalidData`| __Boolean__. When generating data, allow in some cases to use invalid values on purpose. *Default value*: `true`.|
|`appendToStatisticsFile`| __Boolean__. Whether should add to an existing statistics file, instead of replacing it. *Default value*: `false`.|
|`archiveAfterMutationFile`| __String__. Specify a path to save archive after each mutation during search, only useful for debugging. *DEBUG option*. *Default value*: `archive.csv`.|
Expand Down Expand Up @@ -163,6 +163,7 @@ There are 3 types of options:
|`minimizeThresholdForLoss`| __Double__. Losing targets when recomputing coverage is expected (e.g., constructors of singletons), but problematic if too much. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.2`.|
|`minimizeTimeout`| __Int__. Maximum number of minutes that will be dedicated to the minimization phase. A negative number mean no timeout is considered. A value of 0 means minimization will be skipped, even if minimize=true. *Default value*: `5`.|
|`minimumSizeControl`| __Int__. Specify minimum size when bloatControlForSecondaryObjective. *Constraints*: `min=0.0`. *Default value*: `2`.|
|`muLambdaOffspringSize`| __Int__. Define the number of offspring (λ) generated per generation in (μ,λ) Evolutionary Algorithm. *Constraints*: `min=1.0`. *Default value*: `30`.|
|`mutatedGeneFile`| __String__. Specify a path to save mutation details which is useful for debugging mutation. *DEBUG option*. *Default value*: `mutatedGeneInfo.csv`.|
|`nameWithQueryParameters`| __Boolean__. Specify if true boolean query parameters are included in the test case name. Used for test case naming disambiguation. Only valid for Action based naming strategy. *Default value*: `true`.|
|`namingStrategy`| __Enum__. Specify the naming strategy for test cases. *Valid values*: `NUMBERED, ACTION`. *Default value*: `ACTION`.|
Expand Down