Skip to content

Commit 226eb78

Browse files
committed
improved lips algorithm && created LIPSAlgorithmTest
1 parent 3a17466 commit 226eb78

2 files changed

Lines changed: 106 additions & 58 deletions

File tree

core/src/main/kotlin/org/evomaster/core/search/algorithms/LIPSAlgorithm.kt

Lines changed: 48 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ import org.evomaster.core.search.service.IdMapper
1616
* - Maintains a current branch target.
1717
* - Per-target budget is a fair share of the global TIME/ACTIONS budget; switches target when the target is covered or its budget is exhausted.
1818
*/
19-
20-
2119
class LIPSAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
2220

2321
private var currentTarget: Int? = null
24-
private var budgetLeftForCurrentTarget: Int = 0
22+
private lateinit var budget: LipsBudget
2523

2624
override fun getType(): EMConfig.Algorithm = EMConfig.Algorithm.LIPS
2725

@@ -34,32 +32,51 @@ class LIPSAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
3432
while (population.size < config.populationSize) {
3533
population.add(sampleSuite())
3634
}
35+
// Initialize budget manager and first per-target budget using current uncovered targets
36+
budget = LipsBudget(config, time)
37+
val initUncoveredSize = archive.notCoveredTargets().size
38+
budget.budgetLeftForCurrentTarget = budget.computePerTargetBudget(initUncoveredSize)
39+
println("[LIPS DEBUG] initPopulation: initial per-target budget set using uncoveredSize=$initUncoveredSize -> budget=${budget.budgetLeftForCurrentTarget}")
3740
}
3841

3942
override fun searchOnce() {
4043
beginGeneration()
44+
println("[LIPS DEBUG] searchOnce: ENTER")
4145
// record budget usage for this generation
4246
val startActions = time.evaluatedActions
4347
val startSeconds = time.getElapsedSeconds()
4448

4549
// Compute uncovered goals
4650
val uncovered = archive.notCoveredTargets()
51+
println("[LIPS DEBUG] uncovered targets size = ${uncovered.size}")
4752

4853
// current target is null if covered by previous generation or out of budget
4954
// Pick target if null, or if previously covered (check coverage directly)
5055
val needNewTarget = currentTarget == null || archive.isCovered(currentTarget!!)
56+
println("[LIPS DEBUG] needNewTarget=$needNewTarget currentTarget=${currentTarget}")
5157
if (needNewTarget) {
52-
val target = firstUncoveredBranch()
58+
println("[LIPS DEBUG] selecting new target from archive snapshot")
59+
val target = lastUncoveredBranchTargetId()
5360
currentTarget = target
61+
println("[LIPS DEBUG] currentTarget set to $currentTarget")
5462
// Initialize budget for this NEW target
55-
budgetLeftForCurrentTarget = calculatePerTargetBudget(uncovered.size)
63+
println("[LIPS DEBUG] calculating per-target budget with uncoveredSize=${uncovered.size}")
64+
budget.budgetLeftForCurrentTarget = budget.computePerTargetBudget(uncovered.size)
65+
println("[LIPS DEBUG] selectTarget: target=$target uncovered=${uncovered.size} budgetLeftForCurrentTarget=${budget.budgetLeftForCurrentTarget}")
5666
}
5767

58-
// Focus scoring on the single selected target
59-
frozenTargets = setOf(currentTarget!!)
68+
// Focus scoring on the single selected target if present; otherwise use global fitness
69+
if (currentTarget == null) {
70+
frozenTargets = emptySet()
71+
println("[LIPS DEBUG] no currentTarget; using global fitness (frozenTargets empty)")
72+
} else {
73+
frozenTargets = setOf(currentTarget!!)
74+
println("[LIPS DEBUG] frozenTargets=${frozenTargets}")
75+
}
6076

6177
val n = config.populationSize
6278
val nextPop: MutableList<WtsEvalIndividual<T>> = formTheNextPopulation(population)
79+
println("[LIPS DEBUG] formed base nextPop with elites size=${nextPop.size} target=$currentTarget")
6380

6481
while (nextPop.size < n) {
6582
beginStep()
@@ -84,9 +101,11 @@ class LIPSAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
84101
nextPop.add(o2)
85102

86103
// Stop if global budget or target budget is up
87-
val usedForTarget = usedForCurrentTarget(startActions, startSeconds)
88-
89-
if (!time.shouldContinueSearch() || usedForTarget >= budgetLeftForCurrentTarget) {
104+
val usedForTarget = budget.usedForCurrentTarget(startActions, startSeconds)
105+
if ((nextPop.size % 10) == 0 || usedForTarget >= budget.budgetLeftForCurrentTarget) {
106+
println("[LIPS DEBUG] innerLoop: nextSize=${nextPop.size} usedForTarget=$usedForTarget budgetLeft=${budget.budgetLeftForCurrentTarget}")
107+
}
108+
if (!time.shouldContinueSearch() || usedForTarget >= budget.budgetLeftForCurrentTarget) {
90109
endStep()
91110
break
92111
}
@@ -97,69 +116,40 @@ class LIPSAlgorithm<T> : AbstractGeneticAlgorithm<T>() where T : Individual {
97116
population.addAll(nextPop)
98117

99118
// Update budget usage for this target
100-
updatePerTargetBudget(startActions, startSeconds)
119+
budget.updatePerTargetBudget(startActions, startSeconds)
120+
println("[LIPS DEBUG] afterGen: budgetLeftForCurrentTarget=${budget.budgetLeftForCurrentTarget}")
101121

102122
// Switch target if covered or out of budget
103-
if (shouldSwitchTarget()) currentTarget = null
123+
val coveredNow = population.any { score(it) >= 1.0 }
124+
val switching = budget.shouldSwitchTarget(coveredNow)
125+
println("[LIPS DEBUG] shouldSwitchTarget=$switching currentTarget=$currentTarget")
126+
if (switching) currentTarget = null
104127

105128
endGeneration()
129+
println("[LIPS DEBUG] searchOnce: EXIT")
106130
}
107131

108-
/**
109-
* Calculate per-target budget based on the current stopping criterion.
110-
* Returns the fair share: total budget divided by number of uncovered targets.
111-
*/
112-
private fun calculatePerTargetBudget(uncoveredSize: Int): Int {
113-
return when (config.stoppingCriterion) {
114-
EMConfig.StoppingCriterion.ACTION_EVALUATIONS -> {
115-
val remaining = (config.maxEvaluations - time.evaluatedActions).coerceAtLeast(0)
116-
remaining / uncoveredSize
117-
}
118-
EMConfig.StoppingCriterion.TIME -> {
119-
val remaining = (config.timeLimitInSeconds() - time.getElapsedSeconds()).coerceAtLeast(0)
120-
remaining / uncoveredSize
121-
}
122-
else -> Int.MAX_VALUE
123-
}
124-
}
125-
126-
/**
127-
* Compute the budget spent since the start of the current generation,
128-
* based on the active stopping criterion.
129-
*/
130-
private fun usedForCurrentTarget(startActions: Int, startSeconds: Int): Int {
131-
return when (config.stoppingCriterion) {
132-
EMConfig.StoppingCriterion.ACTION_EVALUATIONS -> time.evaluatedActions - startActions
133-
EMConfig.StoppingCriterion.TIME -> time.getElapsedSeconds() - startSeconds
134-
else -> 0
135-
}
136-
}
137-
138-
private fun updatePerTargetBudget(actionsAtGenStart: Int, secondsAtGenStart: Int) {
139-
val usedForTarget = usedForCurrentTarget(actionsAtGenStart, secondsAtGenStart)
140-
budgetLeftForCurrentTarget -= usedForTarget
141-
}
142-
143-
private fun shouldSwitchTarget(): Boolean {
144-
val coveredNow = population.any { score(it) >= 1.0 }
145-
val outOfBudget = budgetLeftForCurrentTarget <= 0
146-
return coveredNow || outOfBudget
147-
}
148-
149-
fun firstUncoveredBranch(): Int? {
150-
if (populations.isEmpty()) return null
132+
fun lastUncoveredBranchTargetId(): Int? {
133+
val snapshot = archive.getSnapshotOfBestIndividuals()
134+
if (snapshot.isEmpty()) return null
151135

152136
// Iterate targets by numeric id in descending order
153-
val orderedIds = populations.keys.sortedDescending()
137+
val orderedIds = snapshot.keys.sortedDescending()
138+
println("[LIPS DEBUG] lastUncoveredBranch: snapshotSize=${snapshot.size} orderedIdsCount=${orderedIds.size}")
154139

155140
for (targetId in orderedIds) {
156141
val description = archive.getIdMapper().getDescriptiveId(targetId)
157-
if (description.startsWith(ObjectiveNaming.BRANCH)) {
158-
if (!isCovered(targetId)) {
142+
val isBranch = description.startsWith(ObjectiveNaming.BRANCH)
143+
val covered = archive.isCovered(targetId)
144+
println("[LIPS DEBUG] candidate targetId=$targetId desc=$description isBranch=$isBranch covered=$covered")
145+
if (isBranch) {
146+
if (!covered) {
147+
println("[LIPS DEBUG] lastUncoveredBranch: selected targetId=$targetId")
159148
return targetId
160149
}
161150
}
162151
}
152+
println("[LIPS DEBUG] lastUncoveredBranch: no candidate found")
163153
return null
164154
}
165155
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.evomaster.core.search.algorithms
2+
3+
import com.google.inject.Injector
4+
import com.google.inject.Key
5+
import com.google.inject.Module
6+
import com.google.inject.TypeLiteral
7+
import com.netflix.governator.guice.LifecycleInjector
8+
import org.evomaster.core.BaseModule
9+
import org.evomaster.core.EMConfig
10+
import org.evomaster.core.TestUtils
11+
import org.evomaster.core.search.algorithms.onemax.OneMaxIndividual
12+
import org.evomaster.core.search.algorithms.onemax.OneMaxModule
13+
import org.evomaster.core.search.algorithms.onemax.OneMaxSampler
14+
import org.evomaster.core.search.service.ExecutionPhaseController
15+
import org.junit.jupiter.api.Assertions.assertEquals
16+
import org.junit.jupiter.api.Assertions.assertTrue
17+
import org.junit.jupiter.api.BeforeEach
18+
import org.junit.jupiter.api.Test
19+
20+
class LIPSAlgorithmTest {
21+
22+
private lateinit var injector: Injector
23+
24+
@BeforeEach
25+
fun setUp() {
26+
injector = LifecycleInjector.builder()
27+
.withModules(* arrayOf<Module>(OneMaxModule(), BaseModule()))
28+
.build().createInjector()
29+
}
30+
31+
// Verifies that the LIPS algorithm can find the optimal solution for the OneMax problem
32+
@Test
33+
fun testLipsFindsOptimumOnOneMax() {
34+
35+
val lips = injector.getInstance(
36+
Key.get(object : TypeLiteral<LIPSAlgorithm<OneMaxIndividual>>() {})
37+
)
38+
39+
val config = injector.getInstance(EMConfig::class.java)
40+
config.maxEvaluations = 10_000
41+
config.stoppingCriterion = EMConfig.StoppingCriterion.ACTION_EVALUATIONS
42+
43+
val epc = injector.getInstance(ExecutionPhaseController::class.java)
44+
epc.startSearch()
45+
val solution = lips.search()
46+
epc.finishSearch()
47+
48+
assertTrue(solution.individuals.size == 1)
49+
assertEquals(
50+
OneMaxSampler.DEFAULT_N.toDouble(),
51+
solution.overall.computeFitnessScore(),
52+
0.001
53+
)
54+
}
55+
56+
}
57+
58+

0 commit comments

Comments
 (0)