Skip to content

Commit a53c70d

Browse files
committed
working on cro algorithm
1 parent 0240a08 commit a53c70d

3 files changed

Lines changed: 241 additions & 1 deletion

File tree

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,8 @@ class EMConfig {
11621162

11631163
enum class Algorithm {
11641164
DEFAULT, SMARTS, MIO, RANDOM, WTS, MOSA, RW,
1165-
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA // GA variants still work-in-progress.
1165+
StandardGA, MonotonicGA, SteadyStateGA, BreederGA, CellularGA, OnePlusLambdaLambdaGA, MuLambdaEA,
1166+
CRO // Chemical Reaction Optimization
11661167
}
11671168

11681169
@Cfg("The algorithm used to generate test cases. The default depends on whether black-box or white-box testing is done.")
@@ -1572,6 +1573,27 @@ class EMConfig {
15721573
@Min(1.0)
15731574
var tournamentSize = 10
15741575

1576+
// --- Chemical Reaction Optimization (CRO) parameters ---
1577+
@Cfg("CRO: Molecular collision rate c_r (probability of binary reactions)")
1578+
@Probability
1579+
var croMolecularCollisionRate: Double = 0.2
1580+
1581+
@Cfg("CRO: Kinetic energy loss rate k_r (lower bound of retained fraction after on-wall)")
1582+
@Probability
1583+
var croKineticEnergyLossRate: Double = 0.2
1584+
1585+
@Cfg("CRO: Initial kinetic energy assigned to each molecule")
1586+
@Min(0.0)
1587+
var croInitialKineticEnergy: Double = 1000.0
1588+
1589+
@Cfg("CRO: Decomposition threshold d_t (min number of collisions before decomposition)")
1590+
@Min(0.0)
1591+
var croDecompositionThreshold: Int = 500
1592+
1593+
@Cfg("CRO: Synthesis KE threshold s_t (molecule can synthesize if KE ≤ s_t)")
1594+
@Min(0.0)
1595+
var croSynthesisThreshold: Double = 10.0
1596+
15751597
@Cfg("When sampling new test cases to evaluate, probability of using some smart strategy instead of plain random")
15761598
@Probability
15771599
var probOfSmartSampling = 0.95

core/src/main/kotlin/org/evomaster/core/Main.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,9 @@ class Main {
772772
EMConfig.Algorithm.OnePlusLambdaLambdaGA ->
773773
Key.get(object : TypeLiteral<OnePlusLambdaLambdaGeneticAlgorithm<RestIndividual>>() {})
774774

775+
EMConfig.Algorithm.CRO ->
776+
Key.get(object : TypeLiteral<CroAlgorithm<RestIndividual>>() {})
777+
775778
else -> throw IllegalStateException("Unrecognized algorithm ${config.algorithm}")
776779
}
777780
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package org.evomaster.core.search.algorithms
2+
3+
import org.evomaster.core.EMConfig
4+
import org.evomaster.core.search.Individual
5+
import org.evomaster.core.search.algorithms.wts.WtsEvalIndividual
6+
import kotlin.math.abs
7+
8+
/**
9+
* Chemical Reaction Optimization (CRO)
10+
*
11+
* Each molecule corresponds to a [WtsEvalIndividual] (a test suite). Potential energy (PE)
12+
* is defined as the negative of the combined fitness of the suite, so that minimization
13+
* semantics of the CRO equations align with EvoMaster's higher-is-better fitness.
14+
*/
15+
class CroAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
16+
17+
private data class Molecule<T : Individual>(
18+
var suite: WtsEvalIndividual<T>,
19+
var kineticEnergy: Double,
20+
var numCollisions: Int
21+
)
22+
23+
private val molecules: MutableList<Molecule<T>> = mutableListOf()
24+
25+
private var buffer: Double = 0.0
26+
private var initialEnergy: Double = 0.0
27+
28+
override fun getType(): EMConfig.Algorithm = EMConfig.Algorithm.CRO
29+
30+
override fun setupBeforeSearch() {
31+
// Reuse GA population initialization to sample and evaluate initial suites
32+
molecules.clear()
33+
buffer = 0.0
34+
35+
// Initialize the underlying GA population to reuse sampling utilities
36+
super.setupBeforeSearch()
37+
38+
// Convert GA population to CRO molecules with initial KE
39+
getViewOfPopulation().forEach { w ->
40+
molecules.add(Molecule(w.copy(), config.croInitialKineticEnergy, 0))
41+
}
42+
43+
initialEnergy = getCurrentEnergy()
44+
}
45+
46+
override fun searchOnce() {
47+
if (molecules.isEmpty()) return
48+
49+
val binary = randomness.nextDouble() <= config.croMolecularCollisionRate && molecules.size > 1
50+
51+
if (!binary) {
52+
// Uni-molecular collision
53+
val idx = randomness.nextInt(molecules.size)
54+
val m = molecules[idx]
55+
56+
if (decompositionCheck(m)) {
57+
decomposition(m)?.let { offsprings ->
58+
molecules.removeAt(idx)
59+
molecules.addAll(offsprings)
60+
}
61+
} else {
62+
onWallIneffectiveCollision(m)?.let { nm ->
63+
molecules[idx] = nm
64+
}
65+
}
66+
} else {
67+
// Inter-molecular collision
68+
val i1 = randomness.nextInt(molecules.size)
69+
var i2 = randomness.nextInt(molecules.size)
70+
while (i2 == i1) i2 = randomness.nextInt(molecules.size)
71+
72+
val m1 = molecules[i1]
73+
val m2 = molecules[i2]
74+
75+
if (synthesisCheck(m1) && synthesisCheck(m2)) {
76+
synthesis(m1, m2)?.let { off ->
77+
val low = minOf(i1, i2)
78+
val high = maxOf(i1, i2)
79+
molecules[low] = off
80+
molecules.removeAt(high)
81+
}
82+
} else {
83+
intermolecularIneffectiveCollision(m1, m2)?.let { (n1, n2) ->
84+
molecules[i1] = n1
85+
molecules[i2] = n2
86+
}
87+
}
88+
}
89+
90+
// Adjust buffer if external factors changed fitness values, to conserve energy
91+
val current = getCurrentEnergy()
92+
if (abs(current - initialEnergy) > 1e-9) {
93+
val delta = current - initialEnergy
94+
buffer -= delta
95+
}
96+
}
97+
98+
private fun potential(w: WtsEvalIndividual<T>): Double = -w.calculateCombinedFitness()
99+
100+
private fun decompositionCheck(m: Molecule<T>): Boolean = m.numCollisions > config.croDecompositionThreshold
101+
102+
private fun synthesisCheck(m: Molecule<T>): Boolean = m.kineticEnergy <= config.croSynthesisThreshold
103+
104+
private fun onWallIneffectiveCollision(m: Molecule<T>): Molecule<T>? {
105+
val pe = potential(m.suite)
106+
val ke = m.kineticEnergy
107+
val newM = m.copy(suite = m.suite.copy(), numCollisions = m.numCollisions + 1)
108+
109+
// mutate and evaluate in-place
110+
mutate(newM.suite)
111+
112+
val peNew = potential(newM.suite)
113+
return if (pe + ke >= peNew) {
114+
val a = randomness.nextDouble(config.croKineticEnergyLossRate, 1.0)
115+
val surplus = (pe - peNew + ke)
116+
newM.kineticEnergy = surplus * a
117+
buffer += surplus * (1.0 - a)
118+
newM
119+
} else null
120+
}
121+
122+
private fun decomposition(m: Molecule<T>): List<Molecule<T>>? {
123+
val pe = potential(m.suite)
124+
val ke = m.kineticEnergy
125+
126+
val o1 = Molecule(m.suite.copy(), ke = 0.0, numCollisions = 0)
127+
val o2 = Molecule(m.suite.copy(), ke = 0.0, numCollisions = 0)
128+
129+
mutate(o1.suite)
130+
mutate(o2.suite)
131+
132+
val pe1 = potential(o1.suite)
133+
val pe2 = potential(o2.suite)
134+
135+
var eDec = (pe + ke) - (pe1 + pe2)
136+
if (eDec < 0) {
137+
val d1 = randomness.nextDouble()
138+
val d2 = randomness.nextDouble()
139+
val canBorrow = d1 * d2 * buffer
140+
if (eDec + canBorrow >= 0) {
141+
buffer *= (1.0 - d1 * d2)
142+
eDec += canBorrow
143+
} else {
144+
m.numCollisions += 1
145+
return null
146+
}
147+
}
148+
149+
// distribute kinetic energy and reset collisions
150+
val d3 = randomness.nextDouble()
151+
o1.kineticEnergy = eDec * d3
152+
o2.kineticEnergy = eDec * (1.0 - d3)
153+
o1.numCollisions = 0
154+
o2.numCollisions = 0
155+
return listOf(o1, o2)
156+
}
157+
158+
private fun intermolecularIneffectiveCollision(m1: Molecule<T>, m2: Molecule<T>): Pair<Molecule<T>, Molecule<T>>? {
159+
val pe1 = potential(m1.suite)
160+
val ke1 = m1.kineticEnergy
161+
val pe2 = potential(m2.suite)
162+
val ke2 = m2.kineticEnergy
163+
164+
val n1 = m1.copy(suite = m1.suite.copy(), numCollisions = m1.numCollisions + 1)
165+
val n2 = m2.copy(suite = m2.suite.copy(), numCollisions = m2.numCollisions + 1)
166+
167+
mutate(n1.suite)
168+
mutate(n2.suite)
169+
170+
val pe1n = potential(n1.suite)
171+
val pe2n = potential(n2.suite)
172+
173+
val eInter = (pe1 + pe2 + ke1 + ke2) - (pe1n + pe2n)
174+
return if (eInter >= 0) {
175+
val d4 = randomness.nextDouble()
176+
n1.kineticEnergy = eInter * d4
177+
n2.kineticEnergy = eInter * (1.0 - d4)
178+
Pair(n1, n2)
179+
} else null
180+
}
181+
182+
private fun synthesis(m1: Molecule<T>, m2: Molecule<T>): Molecule<T>? {
183+
val pe1 = potential(m1.suite)
184+
val ke1 = m1.kineticEnergy
185+
val pe2 = potential(m2.suite)
186+
val ke2 = m2.kineticEnergy
187+
188+
val o1 = Molecule(m1.suite.copy(), 0.0, 0)
189+
val o2 = Molecule(m2.suite.copy(), 0.0, 0)
190+
191+
// crossover suites
192+
xover(o1.suite, o2.suite)
193+
194+
// choose the better offspring (higher combined fitness => lower potential energy)
195+
val best = if (o1.suite.calculateCombinedFitness() >= o2.suite.calculateCombinedFitness()) o1 else o2
196+
val pe = potential(best.suite)
197+
return if (pe1 + pe2 + ke1 + ke2 >= pe) {
198+
best.kineticEnergy = (pe1 + pe2 + ke1 + ke2) - pe
199+
best.numCollisions = 0
200+
best
201+
} else {
202+
m1.numCollisions += 1
203+
m2.numCollisions += 1
204+
null
205+
}
206+
}
207+
208+
private fun getCurrentEnergy(): Double {
209+
var energy = buffer
210+
molecules.forEach { energy += potential(it.suite) + it.kineticEnergy }
211+
return energy
212+
}
213+
}
214+
215+

0 commit comments

Comments
 (0)