Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard Genetic Algorithm #1079

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d5f37fa
Implement Genetic Algorithm
amidgol May 29, 2024
f38c5ff
Implement crossover
amidgol May 29, 2024
f10cd0e
refactored with parametererized tests
arcuri82 May 29, 2024
eaecfbb
Modify GA and make it pass the test
amidgol Jun 12, 2024
e228941
Implement monotonic GA
amidgol Jun 25, 2024
f52bcc4
Inherit Monotonic from Wts
amidgol Jul 17, 2024
7498a14
Add SteadyState GA
amidgol Jul 17, 2024
23c6887
Remove redundant GeneticAlgorithm
amidgol Jul 17, 2024
206fa10
Merge branch 'master' into feat-steadyStateGA
amidgol Jul 22, 2024
308de02
Add StandardGA
amidgol Jul 22, 2024
3d94fe4
Implement Elitism in StandardGA
amidgol Jul 25, 2024
2d3a8eb
Add elitesCount to EMConfig
amidgol Jul 25, 2024
12575f0
Fix forming the next population
amidgol Aug 14, 2024
a24653d
Assert if the best solution's fitness does not decrease
amidgol Aug 14, 2024
f2c889e
Keep the Standard GA
amidgol Sep 17, 2024
60fcd2b
Address some issues
amidgol Sep 19, 2024
2681001
Implement tournament selection inside standard GA
amidgol Sep 20, 2024
e052e19
Add a todo
amidgol Sep 20, 2024
d0cbc6c
Add AbstractGA
amidgol Oct 9, 2024
3442144
Update StandardGeneticAlgorithm
amidgol Oct 10, 2024
9c86ac9
Fix issue in StandardGA's name
amidgol Oct 11, 2024
343b2d6
Update AbstractGeneticAlgorithm.kt
amidgol Oct 11, 2024
aa48470
Change mutation strategy
amidgol Oct 16, 2024
c8212ec
Add an incomplete test for Standard GA
amidgol Oct 16, 2024
77ef833
Merge branch 'master' into feat-standardGA
amidgol Dec 11, 2024
64f1079
Update StandardGeneticAlgorithmTest.kt
amidgol Dec 17, 2024
8787bbf
improved test readability
arcuri82 Dec 18, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,4 @@ Migrations/
/e2e-tests/emb-json/target/
/process_data/
/e2e-tests/spring-rest-multidb/target/
.DS_Store
9 changes: 8 additions & 1 deletion core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ class EMConfig {
var avoidNonDeterministicLogs = false

enum class Algorithm {
DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW
DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW, STANDARD_GA
}

@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 @@ -1308,6 +1308,10 @@ class EMConfig {
@Min(1.0)
var populationSize = 30

@Cfg("Fixed mutation rate")
@Probability
var fixedRateMutation = 0.04

@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 Expand Up @@ -2406,6 +2410,9 @@ class EMConfig {
@Cfg("Max length for test comments. Needed when enumerating some names/values, making comments too long to be" +
" on a single line")
var maxLengthForCommentLine = 80
@Cfg(description = "Number of elite individuals to be preserved when forming the next population in population-based search algorithms that do not use an archive, like for example Genetic Algorithms")
@Min(0.0)
var elitesCount: Int = 1

fun getProbabilityUseDataPool() : Double{
return if(blackBox){
Expand Down
16 changes: 15 additions & 1 deletion core/src/main/kotlin/org/evomaster/core/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import org.evomaster.core.AnsiColor.Companion.inRed
import org.evomaster.core.AnsiColor.Companion.inYellow
import org.evomaster.core.config.ConfigProblemException
import org.evomaster.core.logging.LoggingUtil
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.output.Termination
import org.evomaster.core.output.TestSuiteSplitter
import org.evomaster.core.output.clustering.SplitResult
Expand All @@ -35,6 +34,7 @@ import org.evomaster.core.remote.service.RemoteController
import org.evomaster.core.remote.service.RemoteControllerImplementation
import org.evomaster.core.search.Solution
import org.evomaster.core.search.algorithms.*
import org.evomaster.core.search.algorithms.*
import org.evomaster.core.search.service.*
import org.evomaster.core.search.service.monitor.SearchProcessMonitor
import org.evomaster.core.search.service.mutator.genemutation.ArchiveImpactSelector
Expand Down Expand Up @@ -492,6 +492,10 @@ class Main {
Key.get(object : TypeLiteral<MosaAlgorithm<GraphQLIndividual>>() {})
EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<GraphQLIndividual>>() {})
EMConfig.Algorithm.STANDARD_GA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<GraphQLIndividual>>() {})


else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand All @@ -511,6 +515,10 @@ class Main {
Key.get(object : TypeLiteral<MosaAlgorithm<RPCIndividual>>() {})
EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RPCIndividual>>() {})
EMConfig.Algorithm.STANDARD_GA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<RPCIndividual>>() {})


else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand All @@ -530,6 +538,9 @@ class Main {
Key.get(object : TypeLiteral<MosaAlgorithm<WebIndividual>>() {})
EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<WebIndividual>>() {})
EMConfig.Algorithm.STANDARD_GA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<WebIndividual>>() {})

else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
}
}
Expand All @@ -549,6 +560,9 @@ class Main {
Key.get(object : TypeLiteral<MosaAlgorithm<RestIndividual>>() {})
EMConfig.Algorithm.RW ->
Key.get(object : TypeLiteral<RandomWalkAlgorithm<RestIndividual>>() {})
EMConfig.Algorithm.STANDARD_GA ->
Key.get(object : TypeLiteral<StandardGeneticAlgorithm<RestIndividual>>() {})

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

import org.evomaster.core.search.Individual
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
import org.evomaster.core.search.service.SearchAlgorithm

abstract class AbstractGeneticAlgorithm<T>: SearchAlgorithm<T>() where T : Individual {

private val population: MutableList<WtsEvalIndividual<T>> = mutableListOf()

override fun setupBeforeSearch() {
population.clear()

initPopulation()
}

protected open fun initPopulation() {

val n = config.populationSize

for (i in 1..n) {
population.add(sampleSuite())

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

protected fun formTheNextPopulation(population: MutableList<WtsEvalIndividual<T>>): MutableList<WtsEvalIndividual<T>> {

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

if (config.elitesCount > 0 && population.isNotEmpty()) {
var sortedPopulation = population.sortedByDescending { it.calculateCombinedFitness() }

var elites = sortedPopulation.take(config.elitesCount)

nextPop.addAll(elites)
}

return nextPop
}

protected fun mutate(wts: WtsEvalIndividual<T>) {

val op = randomness.choose(listOf("del", "add", "mod"))
val n = wts.suite.size
when (op) {
"del" -> if (n > 1) {
val i = randomness.nextInt(n)
wts.suite.removeAt(i)
}
"add" -> if (n < config.maxSearchSuiteSize) {
ff.calculateCoverage(sampler.sample(), modifiedSpec = null)?.run {
archive.addIfNeeded(this)
wts.suite.add(this)
}
}
"mod" -> {
val i = randomness.nextInt(n)
val ind = wts.suite[i]

getMutatator().mutateAndSave(ind, archive)
?.let { wts.suite[i] = it }
}
}
}

protected fun xover(x: WtsEvalIndividual<T>, y: WtsEvalIndividual<T>) {

val nx = x.suite.size
val ny = y.suite.size

val splitPoint = randomness.nextInt(Math.min(nx, ny))

(0..splitPoint).forEach {
val k = x.suite[it]
x.suite[it] = y.suite[it]
y.suite[it] = k
}
}

protected fun tournamentSelection(): WtsEvalIndividual<T>{
val selectedIndividuals = randomness.choose(population, config.tournamentSize)
val bestIndividual = selectedIndividuals.maxByOrNull { it.calculateCombinedFitness() }
return bestIndividual ?: randomness.choose(population)
}

private fun sampleSuite(): WtsEvalIndividual<T> {

val n = 1 + randomness.nextInt(config.maxSearchSuiteSize)

val suite = WtsEvalIndividual<T>(mutableListOf())

for (i in 1..n) {
ff.calculateCoverage(sampler.sample(), modifiedSpec = null)?.run {
archive.addIfNeeded(this)
suite.suite.add(this)
}

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

return suite
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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

/**
* An implementation of the Standard Genetic algorithm,
*/
open class StandardGeneticAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {


private val population: MutableList<WtsEvalIndividual<T>> = mutableListOf()

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

override fun searchOnce() {

val n = config.populationSize

val nextPop = formTheNextPopulation(population)

while (nextPop.size < n) {

val sizeBefore = nextPop.size

val x = tournamentSelection()
val y = tournamentSelection()

if (randomness.nextBoolean(config.xoverProbability)) {
xover(x, y)
}

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

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

nextPop.add(x)
nextPop.add(y)

assert(nextPop.size == sizeBefore + 2)

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

population.clear()
population.addAll(nextPop)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import org.evomaster.core.search.service.SearchAlgorithm
* of search algorithms, and not really something to
* use regularly in EvoMaster
*/
class WtsAlgorithm<T> : SearchAlgorithm<T>() where T : Individual {
open class WtsAlgorithm<T> : SearchAlgorithm<T>() where T : Individual {
Copy link
Collaborator

Choose a reason for hiding this comment

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

have we clarified the difference between StandardGA and WTS? let's discuss it in meeting, with pseudo-code from paper



private val population: MutableList<WtsEvalIndividual<T>> = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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.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.Test

import org.junit.jupiter.api.Assertions.*

class StandardGeneticAlgorithmTest {

val injector: Injector = LifecycleInjector.builder()
.withModules(* arrayOf<Module>(OneMaxModule(), BaseModule()))
.build().createInjector()

@Test
fun testStandardGeneticAlgorithm() {
TestUtils.handleFlaky {
val standardGeneticAlgorithm = injector.getInstance(
Key.get(
object : TypeLiteral<StandardGeneticAlgorithm<OneMaxIndividual>>() {})
)

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

val epc = injector.getInstance(ExecutionPhaseController::class.java)
epc.startSearch()

val solution = standardGeneticAlgorithm.search()

epc.finishSearch()

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import org.evomaster.core.search.service.Sampler

class OneMaxSampler : Sampler<OneMaxIndividual>(){

var n = 3
companion object{
const val DEFAULT_N = 3
}


var n = DEFAULT_N

override fun sampleAtRandom(): OneMaxIndividual {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.evomaster.core.problem.rest.RestIndividual;
import org.evomaster.core.search.Solution;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;

Expand All @@ -14,32 +17,18 @@
public class PIEMTest extends PITestBase {



@Test
public void testMIO() throws Throwable {
testRunEM(EMConfig.Algorithm.MIO, 1000);
}

@Test
public void testRand() throws Throwable {
testRunEM(EMConfig.Algorithm.RANDOM, 20);
@ParameterizedTest
@EnumSource(EMConfig.Algorithm.class)
public void testAlgorithms(EMConfig.Algorithm alg) throws Throwable {
testRunEM(alg, 1000);// high value, just to check if no crash
}

@Test
public void testWTS() throws Throwable {
testRunEM(EMConfig.Algorithm.WTS, 2_000); // high value, just to check if no crash
}

@Test
public void testMOSA() throws Throwable {
testRunEM(EMConfig.Algorithm.MOSA, 2_000); // high value, just to check if no crash
}


private void testRunEM(EMConfig.Algorithm alg, int iterations) throws Throwable {

String outputFolderName = "PIEM_" + alg.toString();
ClassName className = new ClassName("org.PIEM_Run_" + alg.toString());
ClassName className = new ClassName("org.PIEM_Run_" + alg);
clearGeneratedFiles(outputFolderName, className);

handleFlaky(() -> {
Expand Down
Loading