EvolveWorkedExample.kt
src/jvmMain/kotlin/afield/examples/functions/EvolveWorkedExample.kt · 121 lines
package afield.examples.functions
import afield.math.functions.FnEvolveRun
import afield.math.functions.FnVarietyRun
import afield.math.functions.Functions
import afield.math.functions.evolve
import afield.math.functions.evolveVariety
import afield.math.functions.graphSignature
import afield.math.functions.matchFitness
FIELD_TEST id: functions.evolve.worked-example runner: jvm-main entrypoint: afield.examples.functions.EvolveWorkedExample tags: [smoke, examples, functions, evolve, cpu] timeoutSec: 30 expect: exitCode: 0 stdoutContains: - "evolve worked example passed"
object EvolveWorkedExample
The CPU-only reproducibility floor behind the evolve walkthrough in
public/docs/functions_examples.md.
There is no GPU, no Vulkan and no Field window here — and, deliberately, no file output.
It builds an adversarial target Fn, runs the deterministic (µ+λ) hill-climb evolve
from a plain seed under a FIXED rngSeed, and grows a behaviourally distinct set with
evolveVariety. runExample returns the raw run objects; main and the @Test assert
the run improves on the seed and reproduces bit-for-bit under the fixed seeds.
The figures in the doc are LIVE box captures of this same search, produced by the docs-capture pipeline (the document system) — not files this harness writes. Document assets do not come from code outside the document system, so this harness renders nothing and writes nowhere; it exists only to prove the numbers the doc shows are real and stable.
object EvolveWorkedExample {
const val RNG_SEED: Long = 3L
Fixed so the whole run is reproducible.
const val ITERATIONS: Int = 120
const val LAMBDA: Int = 8
const val SAMPLES: Int = 160
const val VARIETY_COUNT: Int = 6
Variety run. Mirrors the evolveVariety(...) snippet in the docs so the harness and
the documented call stay in lockstep.
const val VARIETY_ITERATIONS: Int = 48
const val VARIETY_RNG_SEED: Long = 7L
const val VARIETY_NEIGHBOURS: Int = 3
const val VARIETY_SAMPLES: Int = 120
data class Result
data class Result(
val run: FnEvolveRun,
val variety: FnVarietyRun
)
fun runExample(): Result
Run the worked example on the CPU and return the raw evolve / variety objects. Pure computation: no rendering, no file output. Given the fixed seeds it returns the same numbers and graphs every time — that reproducibility is the whole point.
fun runExample(): Result = with(Functions) {
// Adversarial target/seed: a half-amplitude sine reached from a full-amplitude
// COSINE — the wrong op AND the wrong amplitude, so a good run must both substitute
// the op (via substituteOp) and dial in the numeric leaves (via jitter/nudge).
val target = sin(frequency = 1.0, amplitude = 0.5)
val seed = cos(frequency = 1.0, amplitude = 1.0)
val run = evolve(
seed = seed,
iterations = ITERATIONS,
lambda = LAMBDA,
rngSeed = RNG_SEED,
fitness = matchFitness(target, samples = SAMPLES, complexityWeight = 0.01)
)
// Variety generation: grow a set of behaviourally distinct gestures from one seed.
// `normalize = true` measures novelty in *shape*, not amplitude, so the set spreads
// across genuinely different waveforms instead of ever-louder copies of one gesture.
val variety = evolveVariety(
seed = sin(2.0) * 0.5,
count = VARIETY_COUNT,
iterations = VARIETY_ITERATIONS,
rngSeed = VARIETY_RNG_SEED,
samples = VARIETY_SAMPLES,
neighbours = VARIETY_NEIGHBOURS,
normalize = true
)
Result(run, variety)
}
fun main(args: Array<String>)
@JvmStatic
fun main(args: Array<String>) {
val a = runExample()
val evolved = a.run
println("evolve worked example: seed=$RNG_SEED iterations=$ITERATIONS lambda=$LAMBDA")
println(" initialScore=${evolved.initialScore} bestScore=${evolved.bestScore} improved=${evolved.improved}")
println(" acceptedMutations=${evolved.steps.size} evaluations=${evolved.evaluations}")
val variety = a.variety
println("evolveVariety: count=$VARIETY_COUNT iterations=$VARIETY_ITERATIONS seed=$VARIETY_RNG_SEED")
println(" results=${variety.results.size} meanPairwiseDistance=${variety.meanPairwiseDistance}")
check(evolved.improved) { "expected evolution to improve on the seed" }
check(variety.results.size == VARIETY_COUNT) { "expected $VARIETY_COUNT variety results" }
check(variety.meanPairwiseDistance > 0.0) { "variety set should be behaviourally spread out" }
// Determinism: a second run under the same fixed seeds must reproduce the numbers and
// the evolved graph exactly — the property that makes an evolved gesture safe to keep
// in live art, and what the doc's committed numbers depend on.
val b = runExample()
check(a.run.bestScore == b.run.bestScore) { "bestScore not reproducible" }
check(a.run.initialScore == b.run.initialScore) { "initialScore not reproducible" }
check(a.run.best.graphSignature() == b.run.best.graphSignature()) { "evolved graph not reproducible" }
check(a.variety.meanPairwiseDistance == b.variety.meanPairwiseDistance) { "variety spread not reproducible" }
println("evolve worked example passed")
}
}