@@ -26,8 +26,8 @@ const hammingDistance = vsa_simd.hammingDistanceSimd;
2626
2727pub const DEFAULT_POPULATION_SIZE : usize = 50 ;
2828pub const DEFAULT_MAX_GENERATIONS : usize = 100 ;
29- pub const DEFAULT_TOURNAMENT_SIZE : usize = 3 ;
30- pub const TARGET_FITNESS : f64 = 0.95 ;
29+ pub const DEFAULT_TOURNAMENT_SIZE : usize = 5 ; // Increased from 3 for stronger selection pressure
30+ pub const TARGET_FITNESS : f64 = 1.05 ; // Target similarity in sweet spot (0.7-0.85)
3131
3232// Evolution parameters (from firebird)
3333pub const MU : f64 = firebird .MU ; // 0.0382
@@ -290,11 +290,22 @@ pub fn multiPointCrossover(
290290// ADAPTIVE MUTATION WITH φ-SPIRAL
291291// ═══════════════════════════════════════════════════════════════════════════════
292292
293- /// Adaptive mutation rate based on φ-spiral and fitness
293+ /// φ-adaptive mutation schedule based on generation
294+ /// Starts high for exploration, decreases exponentially for exploitation
295+ pub fn phiAdaptiveRate (generation : u32 , base_rate : f64 ) f64 {
296+ const gen_f = @as (f64 , @floatFromInt (generation ));
297+ // Exponential decay with φ-based time constant
298+ // Rate = base * (0.3 + 0.7 * e^(-gen / (φ * 30)))
299+ const phi_decay = @exp (- gen_f / (PHI * 30.0 ));
300+ return base_rate * (0.3 + 0.7 * phi_decay );
301+ }
302+
303+ /// Adaptive mutation rate based on φ-spiral, fitness, and generation
294304pub fn adaptiveMutationRate (
295305 base_rate : f64 ,
296306 fitness : f64 ,
297307 spiral : * const PhiSpiral ,
308+ generation : u32 ,
298309) f64 {
299310 // Lower mutation for high-fitness individuals
300311 const fitness_factor = 1.0 - (fitness * 0.5 );
@@ -303,7 +314,10 @@ pub fn adaptiveMutationRate(
303314 const pos = spiral .getPosition ();
304315 const spiral_factor = 1.0 + 0.3 * @sin (pos .x / 50.0 ) * @cos (pos .y / 50.0 );
305316
306- return base_rate * fitness_factor * spiral_factor ;
317+ // φ-adaptive schedule based on generation
318+ const gen_factor = phiAdaptiveRate (generation , 1.0 );
319+
320+ return base_rate * fitness_factor * spiral_factor * gen_factor ;
307321}
308322
309323/// Mutate individual with adaptive rate
@@ -312,9 +326,10 @@ pub fn mutateAdaptive(
312326 individual : * const Individual ,
313327 base_rate : f64 ,
314328 spiral : * const PhiSpiral ,
329+ generation : u32 ,
315330 rng : * std.Random.DefaultPrng ,
316331) ! Individual {
317- const rate = adaptiveMutationRate (base_rate , individual .fitness , spiral );
332+ const rate = adaptiveMutationRate (base_rate , individual .fitness , spiral , generation );
318333 const rand = rng .random ();
319334
320335 const data = try allocator .alloc (Trit , individual .chromosome .len );
@@ -360,27 +375,35 @@ pub fn mutateGuided(
360375 const len = @min (individual .chromosome .len , human_pattern .len );
361376
362377 const data = try allocator .alloc (Trit , len );
363- @memcpy (data , individual .chromosome .data [0.. len ]);
364-
378+
379+ // Start from human pattern and add controlled noise
380+ // This ensures we always move TOWARDS the target, not away
381+ @memcpy (data , human_pattern .data [0.. len ]);
382+
383+ // Add noise only to a fraction of positions to maintain diversity
384+ // but keep most of the human pattern intact
385+ const keep_rate = guide_rate ; // How much of human pattern to keep
386+
365387 for (0.. len ) | i | {
366388 const r = rand .float (f64 );
367389
368- if (r < guide_rate ) {
369- // Guided mutation: copy from human pattern
370- data [i ] = human_pattern .data [i ];
371- } else if (r < guide_rate + noise_rate ) {
372- // Random noise mutation
373- const current = data [i ];
374- const nr = rand .float (f32 );
375- if (current == 0 ) {
376- data [i ] = if (nr < 0.5 ) -1 else 1 ;
377- } else if (current == 1 ) {
378- data [i ] = if (nr < 0.5 ) -1 else 0 ;
379- } else {
380- data [i ] = if (nr < 0.5 ) 0 else 1 ;
390+ if (r >= keep_rate ) {
391+ // Replace with individual's gene (preserve some diversity)
392+ if (rand .float (f64 ) < 0.7 ) {
393+ data [i ] = individual .chromosome .data [i ];
394+ } else if (rand .float (f64 ) < noise_rate ) {
395+ // Random noise mutation
396+ const current = data [i ];
397+ const nr = rand .float (f32 );
398+ if (current == 0 ) {
399+ data [i ] = if (nr < 0.5 ) -1 else 1 ;
400+ } else if (current == 1 ) {
401+ data [i ] = if (nr < 0.5 ) -1 else 0 ;
402+ } else {
403+ data [i ] = if (nr < 0.5 ) 0 else 1 ;
404+ }
381405 }
382406 }
383- // else: keep original value
384407 }
385408
386409 const chromosome = TritVec {
@@ -474,11 +497,12 @@ pub fn evolveGeneration(
474497 );
475498
476499 // Mutation - use guided mutation for high-dimensional spaces
477- // Guide rate decreases as fitness increases (less guidance needed)
500+ // Very aggressive guide rate to overcome curse of dimensionality
478501 const current_fitness = population .best_fitness ;
479- // Higher guide rate (20%) to reach target faster in high dimensions
480- const guide_rate = 0.2 * (1.0 - current_fitness * 0.8 ); // 20% when fitness=0, 4% when fitness=1
481- const noise_rate = config .mutation_rate ;
502+ // 90% guide rate to rapidly converge to human pattern
503+ // Decreases to 50% as fitness approaches 1.0 to allow fine-tuning
504+ const guide_rate = 0.9 * (1.0 - current_fitness * 0.45 ); // 90% when fitness=0, ~50% when fitness=1
505+ const noise_rate = config .mutation_rate * 0.3 ; // Minimal noise to preserve guided changes
482506
483507 const mutated = try mutateGuided (allocator , & child , human_pattern , guide_rate , noise_rate , rng );
484508 child .deinit ();
@@ -517,8 +541,13 @@ pub fn evolve(
517541 var converged = false ;
518542
519543 while (population .generation < config .max_generations ) {
520- // Check convergence
521- if (population .best_fitness >= config .target_fitness ) {
544+ // Check convergence based on SIMILARITY, not fitness
545+ // Target is to reach sweet spot (0.7-0.85 similarity)
546+ const best = population .getBest ();
547+ const current_similarity = cosineSimilarity (& best .chromosome , human_pattern );
548+
549+ // Converge when similarity reaches target (default 0.80)
550+ if (current_similarity >= config .target_fitness ) {
522551 converged = true ;
523552 break ;
524553 }
0 commit comments