forge_engine/lab/
emitters.rs1use rand::Rng;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct AlgebraSpec {
7 pub k: f64,
9 pub operator_weights: OperatorWeights,
11 pub delta_amp_default: f64,
13 pub delta_amp_stabilize1: f64,
14 pub delta_amp_stabilize2: f64,
15 pub delta_amp_clamp: f64,
16 pub evidence_budget: usize,
18 pub orthogonality_target: f64,
20 pub token_budget: usize,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct OperatorWeights {
26 pub correctness: f64,
27 pub novelty: f64,
28 pub stability: f64,
29}
30
31impl Default for AlgebraSpec {
32 fn default() -> Self {
33 Self {
34 k: 5.0,
35 operator_weights: OperatorWeights {
36 correctness: 0.7,
37 novelty: 0.2,
38 stability: 0.1,
39 },
40 delta_amp_default: 0.7,
41 delta_amp_stabilize1: 0.2,
42 delta_amp_stabilize2: 0.1,
43 delta_amp_clamp: 0.0,
44 evidence_budget: 8,
45 orthogonality_target: 0.10,
46 token_budget: 1800,
47 }
48 }
49}
50
51pub fn mutate_params<R: Rng>(
54 parent: &AlgebraSpec,
55 perturbation_scale: f64,
56 rng: &mut R,
57) -> AlgebraSpec {
58 let mut child = parent.clone();
59
60 child.k = clamp_positive(child.k * (1.0 + perturbation_scale * signed(rng)));
61 child.delta_amp_default =
62 (child.delta_amp_default + perturbation_scale * signed(rng)).clamp(0.0, 1.0);
63 child.delta_amp_stabilize1 =
64 (child.delta_amp_stabilize1 + perturbation_scale * signed(rng)).clamp(0.0, 1.0);
65 child.delta_amp_stabilize2 =
66 (child.delta_amp_stabilize2 + perturbation_scale * signed(rng)).clamp(0.0, 1.0);
67 child.orthogonality_target =
68 (child.orthogonality_target + perturbation_scale * 0.1 * signed(rng)).clamp(0.0, 1.0);
69 child.evidence_budget = ((child.evidence_budget as f64
70 + perturbation_scale * 2.0 * signed(rng))
71 .round()
72 .max(1.0)) as usize;
73
74 child.operator_weights.correctness =
76 (child.operator_weights.correctness + perturbation_scale * 0.1 * signed(rng)).max(0.01);
77 child.operator_weights.novelty =
78 (child.operator_weights.novelty + perturbation_scale * 0.1 * signed(rng)).max(0.01);
79 child.operator_weights.stability =
80 (child.operator_weights.stability + perturbation_scale * 0.1 * signed(rng)).max(0.01);
81 normalize_weights(&mut child.operator_weights);
82
83 child
84}
85
86pub fn crossover(parent_a: &AlgebraSpec, parent_b: &AlgebraSpec) -> AlgebraSpec {
88 let mut child = AlgebraSpec {
89 k: parent_a.k,
91 token_budget: parent_a.token_budget,
92 evidence_budget: parent_a.evidence_budget,
93
94 delta_amp_default: parent_b.delta_amp_default,
96 delta_amp_stabilize1: parent_b.delta_amp_stabilize1,
97 delta_amp_stabilize2: parent_b.delta_amp_stabilize2,
98 delta_amp_clamp: parent_b.delta_amp_clamp,
99
100 orthogonality_target: (parent_a.orthogonality_target + parent_b.orthogonality_target) / 2.0,
102 operator_weights: OperatorWeights {
103 correctness: (parent_a.operator_weights.correctness
104 + parent_b.operator_weights.correctness)
105 / 2.0,
106 novelty: (parent_a.operator_weights.novelty + parent_b.operator_weights.novelty) / 2.0,
107 stability: (parent_a.operator_weights.stability + parent_b.operator_weights.stability)
108 / 2.0,
109 },
110 };
111 normalize_weights(&mut child.operator_weights);
112 child
113}
114
115fn normalize_weights(w: &mut OperatorWeights) {
117 let sum = w.correctness + w.novelty + w.stability;
118 if sum > 0.0 {
119 w.correctness /= sum;
120 w.novelty /= sum;
121 w.stability /= sum;
122 }
123}
124
125fn clamp_positive(v: f64) -> f64 {
126 if v < 0.1 {
127 0.1
128 } else {
129 v
130 }
131}
132
133fn signed<R: Rng>(rng: &mut R) -> f64 {
135 if rng.random::<bool>() {
136 1.0
137 } else {
138 -1.0
139 }
140}