juddddd commited on
Commit
7e32cd4
·
verified ·
1 Parent(s): a3e5250

Upload routing/identity_routing_experiment.py with huggingface_hub

Browse files
routing/identity_routing_experiment.py ADDED
@@ -0,0 +1,552 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Identity Routing Experiment: Testing τ-Weighted Identity Encoding
3
+
4
+ BACKGROUND:
5
+ - Anchored-tail experiment showed: distribution helps but doesn't fully solve
6
+ - Anchored-tail (25% at τ≥2048) → basin width 1024 (only 25% of L)
7
+ - Hypothesis: identity is being written uniformly, leaking into fast channels
8
+
9
+ THIS EXPERIMENT:
10
+ Tests whether preferentially routing identity to long-τ oscillators
11
+ improves basin width beyond distributional improvements alone.
12
+
13
+ CONDITIONS:
14
+ A) Collapsed + Uniform encoding (baseline)
15
+ B) Anchored-tail + Uniform encoding (distributional fix only)
16
+ C) Anchored-tail + τ-Weighted encoding (distributional + routing fix)
17
+ D) Anchored-tail + τ-Gated encoding (hard routing to slow modes only)
18
+
19
+ DECISION RULE:
20
+ - If C or D >> B: routing was the bottleneck, routing fix works
21
+ - If C ≈ D ≈ B: routing doesn't help, bottleneck is elsewhere
22
+
23
+ Authors: Routing Experiment
24
+ Date: 2026-01-22
25
+ """
26
+
27
+ import numpy as np
28
+ import json
29
+ import hashlib
30
+ from typing import Dict, List, Tuple, Optional, Any
31
+ from dataclasses import dataclass
32
+ from pathlib import Path
33
+ from datetime import datetime
34
+ import sys
35
+
36
+ sys.path.insert(0, str(Path(__file__).parent.parent))
37
+
38
+ from training.fdra_oscillators import FDRAOscillatorBank, OscillatorConfig
39
+
40
+
41
+ def compute_checkpoint_hash(lambdas: np.ndarray) -> str:
42
+ return hashlib.sha256(lambdas.tobytes()).hexdigest()[:16]
43
+
44
+
45
+ @dataclass
46
+ class ParameterSnapshot:
47
+ lambdas: np.ndarray
48
+ checkpoint_hash: str
49
+ half_life_stats: Dict[str, Any]
50
+ per_oscillator_taus: List[float]
51
+ condition_name: str
52
+
53
+ @classmethod
54
+ def from_lambdas(cls, lambdas: np.ndarray, condition_name: str) -> 'ParameterSnapshot':
55
+ safe_lambdas = np.clip(lambdas, 1e-10, 1 - 1e-10)
56
+ taus = np.log(0.5) / np.log(safe_lambdas)
57
+
58
+ stats = {
59
+ "tau_min": float(np.min(taus)),
60
+ "tau_max": float(np.max(taus)),
61
+ "tau_mean": float(np.mean(taus)),
62
+ "tau_median": float(np.median(taus)),
63
+ "frac_tau_ge_2048": float(np.mean(taus >= 2048)),
64
+ "frac_tau_ge_4096": float(np.mean(taus >= 4096)),
65
+ "n_long_range": int(np.sum(taus >= 2048)),
66
+ }
67
+
68
+ return cls(
69
+ lambdas=lambdas.copy(),
70
+ checkpoint_hash=compute_checkpoint_hash(lambdas),
71
+ half_life_stats=stats,
72
+ per_oscillator_taus=taus.tolist(),
73
+ condition_name=condition_name
74
+ )
75
+
76
+ def to_dict(self) -> Dict[str, Any]:
77
+ return {
78
+ "condition_name": self.condition_name,
79
+ "checkpoint_hash": self.checkpoint_hash,
80
+ "half_life_stats": self.half_life_stats,
81
+ "per_oscillator_taus": self.per_oscillator_taus,
82
+ }
83
+
84
+
85
+ def sample_tau_collapsed(n: int, seed: int = 42) -> np.ndarray:
86
+ rng = np.random.default_rng(seed)
87
+ taus = rng.uniform(2, 10, n)
88
+ return np.power(0.5, 1.0 / taus)
89
+
90
+
91
+ def sample_tau_anchored_tail(
92
+ n: int,
93
+ L: int = 4096,
94
+ p_tail: float = 0.25,
95
+ seed: int = 42
96
+ ) -> np.ndarray:
97
+ rng = np.random.default_rng(seed)
98
+
99
+ n_tail = int(n * p_tail)
100
+ n_non_tail = n - n_tail
101
+
102
+ # Tail: τ ∈ [0.75*L, 1.25*L]
103
+ tail_min, tail_max = 0.75 * L, 1.25 * L
104
+ log_taus_tail = rng.uniform(np.log(tail_min), np.log(tail_max), n_tail)
105
+ taus_tail = np.exp(log_taus_tail)
106
+
107
+ # Non-tail: τ ∈ [1, 512]
108
+ log_taus_non_tail = rng.uniform(np.log(1), np.log(512), n_non_tail)
109
+ taus_non_tail = np.exp(log_taus_non_tail)
110
+
111
+ taus = np.concatenate([taus_tail, taus_non_tail])
112
+ return np.power(0.5, 1.0 / taus)
113
+
114
+
115
+ class IdentityEncoderWithRouting:
116
+ """
117
+ Identity encoder with configurable routing strategies.
118
+
119
+ Routing modes:
120
+ - "uniform": Equal weight to all oscillators (baseline)
121
+ - "tau_weighted": Weight ∝ τ (soft routing to slow modes)
122
+ - "tau_gated": Only write to oscillators with τ > threshold (hard routing)
123
+ """
124
+
125
+ def __init__(self, dim: int = 16, routing_mode: str = "uniform"):
126
+ self.dim = dim
127
+ self.routing_mode = routing_mode
128
+ self.patterns = {
129
+ "decision_rule": self._make_pattern(0),
130
+ "normative_constraint": self._make_pattern(1),
131
+ "self_continuity": self._make_pattern(2),
132
+ }
133
+
134
+ def _make_pattern(self, idx: int) -> np.ndarray:
135
+ pattern = np.zeros(self.dim)
136
+ start = (idx * self.dim // 3) % self.dim
137
+ for i in range(self.dim // 3):
138
+ pattern[(start + i) % self.dim] = 1.0 / np.sqrt(self.dim // 3)
139
+ return pattern
140
+
141
+ def _compute_routing_weights(self, taus: np.ndarray, L: int = 4096) -> np.ndarray:
142
+ """Compute routing weights based on routing mode."""
143
+
144
+ if self.routing_mode == "uniform":
145
+ # Equal weight to all oscillators
146
+ return np.ones(len(taus)) / len(taus)
147
+
148
+ elif self.routing_mode == "tau_weighted":
149
+ # Weight ∝ τ (soft routing: prefer slow modes)
150
+ # Normalize so weights sum to 1
151
+ weights = taus / np.sum(taus)
152
+ return weights
153
+
154
+ elif self.routing_mode == "tau_gated":
155
+ # Only write to oscillators with τ > L/4 (hard routing)
156
+ threshold = L / 4
157
+ mask = (taus > threshold).astype(float)
158
+ if np.sum(mask) == 0:
159
+ # Fallback to uniform if no oscillators above threshold
160
+ return np.ones(len(taus)) / len(taus)
161
+ return mask / np.sum(mask)
162
+
163
+ elif self.routing_mode == "tau_softmax":
164
+ # Softmax over log(τ) with temperature
165
+ temperature = 1.0
166
+ log_taus = np.log(taus + 1)
167
+ exp_weights = np.exp(log_taus / temperature)
168
+ return exp_weights / np.sum(exp_weights)
169
+
170
+ else:
171
+ raise ValueError(f"Unknown routing mode: {self.routing_mode}")
172
+
173
+ def encode(self, bank: FDRAOscillatorBank, strength: float = 1.0):
174
+ """Inject identity pattern with routing."""
175
+ taus = bank.get_half_lives()
176
+ weights = self._compute_routing_weights(taus, bank.L)
177
+
178
+ for name, pattern in self.patterns.items():
179
+ # Route identity preferentially to weighted oscillators
180
+ u = np.outer(weights, pattern) * strength * len(taus) # Scale to maintain magnitude
181
+ for _ in range(10):
182
+ bank.forward(u)
183
+
184
+ def measure_identity(self, bank: FDRAOscillatorBank) -> Dict[str, float]:
185
+ """Measure alignment with identity patterns (τ-weighted readout)."""
186
+ taus = bank.get_half_lives()
187
+ weights = taus / np.sum(taus)
188
+ weighted_h = bank.h * weights[:, np.newaxis]
189
+ slow = np.sum(weighted_h, axis=0)
190
+ slow_norm = np.linalg.norm(slow)
191
+
192
+ if slow_norm < 1e-10:
193
+ return {name: 0.0 for name in self.patterns}
194
+
195
+ alignments = {}
196
+ for name, pattern in self.patterns.items():
197
+ alignment = np.dot(slow, pattern) / slow_norm
198
+ alignments[name] = max(0, float(alignment))
199
+
200
+ return alignments
201
+
202
+
203
+ class RoutingExperiment:
204
+ """
205
+ Four-condition routing experiment.
206
+
207
+ Tests whether τ-weighted identity encoding improves basin width.
208
+ """
209
+
210
+ def __init__(
211
+ self,
212
+ num_oscillators: int = 32,
213
+ state_dim: int = 16,
214
+ sequence_length: int = 4096
215
+ ):
216
+ self.n = num_oscillators
217
+ self.d = state_dim
218
+ self.L = sequence_length
219
+
220
+ self.osc_config = OscillatorConfig(
221
+ num_oscillators=num_oscillators,
222
+ state_dim=state_dim,
223
+ sequence_length=sequence_length
224
+ )
225
+
226
+ self.k_values = [0, 64, 128, 256, 512, 1024, 2048, 4096]
227
+ self.output_dir = Path("outputs/identity_routing")
228
+ self.output_dir.mkdir(parents=True, exist_ok=True)
229
+
230
+ def run_identity_trial(
231
+ self,
232
+ snapshot: ParameterSnapshot,
233
+ encoder: IdentityEncoderWithRouting,
234
+ k: int,
235
+ seed: int
236
+ ) -> Dict[str, Any]:
237
+ rng = np.random.default_rng(seed)
238
+
239
+ bank = FDRAOscillatorBank(self.osc_config)
240
+ bank.lambdas = snapshot.lambdas.copy()
241
+ bank.reset()
242
+
243
+ # Encode with routing
244
+ encoder.encode(bank, strength=1.0)
245
+
246
+ pre_identity = encoder.measure_identity(bank)
247
+ pre_score = np.mean(list(pre_identity.values()))
248
+
249
+ if pre_score < 0.3:
250
+ return {
251
+ "k": k, "seed": seed,
252
+ "pre_score": float(pre_score),
253
+ "post_score": 0.0,
254
+ "retention": 0.0,
255
+ "identity_preserved": False,
256
+ "encoding_failed": True
257
+ }
258
+
259
+ # Interference
260
+ for _ in range(k):
261
+ noise = rng.standard_normal((bank.n, bank.d)) * 0.5
262
+ bank.forward(noise)
263
+
264
+ post_identity = encoder.measure_identity(bank)
265
+ post_score = np.mean(list(post_identity.values()))
266
+ retention = post_score / pre_score if pre_score > 0 else 0.0
267
+
268
+ return {
269
+ "k": k, "seed": seed,
270
+ "pre_score": float(pre_score),
271
+ "post_score": float(post_score),
272
+ "retention": float(retention),
273
+ "identity_preserved": retention >= 0.5,
274
+ "encoding_failed": False
275
+ }
276
+
277
+ def run_sweep(
278
+ self,
279
+ snapshot: ParameterSnapshot,
280
+ encoder: IdentityEncoderWithRouting,
281
+ condition_name: str,
282
+ seeds: List[int],
283
+ n_trials: int = 8
284
+ ) -> Dict[str, Any]:
285
+
286
+ print(f"\nCondition: {condition_name}")
287
+ print(f" Distribution: {snapshot.condition_name}")
288
+ print(f" Routing: {encoder.routing_mode}")
289
+ print(f" τ >= 2048: {snapshot.half_life_stats['frac_tau_ge_2048']:.0%}")
290
+ print("-" * 60)
291
+
292
+ all_trials = []
293
+ preservation_curve = []
294
+
295
+ for k in self.k_values:
296
+ k_trials = []
297
+ for seed in seeds:
298
+ for t in range(n_trials):
299
+ trial = self.run_identity_trial(snapshot, encoder, k, seed * 1000 + t)
300
+ k_trials.append(trial)
301
+ all_trials.append(trial)
302
+
303
+ preserved_rate = np.mean([t["identity_preserved"] for t in k_trials])
304
+ mean_retention = np.mean([t["retention"] for t in k_trials])
305
+
306
+ preservation_curve.append({
307
+ "k": k,
308
+ "preserved_rate": float(preserved_rate),
309
+ "mean_retention": float(mean_retention)
310
+ })
311
+
312
+ print(f" K={k:5d}: Preserved={preserved_rate:.0%}, Retention={mean_retention:.1%}")
313
+
314
+ # Basin widths
315
+ bw80 = max([p["k"] for p in preservation_curve if p["preserved_rate"] >= 0.8], default=0)
316
+ bw50 = max([p["k"] for p in preservation_curve if p["preserved_rate"] >= 0.5], default=0)
317
+
318
+ return {
319
+ "condition_name": condition_name,
320
+ "distribution": snapshot.condition_name,
321
+ "routing": encoder.routing_mode,
322
+ "snapshot": snapshot.to_dict(),
323
+ "trials": all_trials,
324
+ "analysis": {
325
+ "preservation_curve": preservation_curve,
326
+ "basin_width_80": bw80,
327
+ "basin_width_50": bw50,
328
+ "basin_width_ratio_80": bw80 / self.L,
329
+ "basin_width_ratio_50": bw50 / self.L
330
+ }
331
+ }
332
+
333
+ def run_full_experiment(
334
+ self,
335
+ seeds: List[int] = [42, 137, 256, 314, 999],
336
+ n_trials: int = 8
337
+ ) -> Dict[str, Any]:
338
+
339
+ print("=" * 70)
340
+ print("ROUTING EXPERIMENT: Does τ-weighted encoding improve basin width?")
341
+ print("=" * 70)
342
+ print()
343
+ print("Conditions:")
344
+ print(" A) Collapsed + Uniform (baseline)")
345
+ print(" B) Anchored + Uniform (distribution fix only)")
346
+ print(" C) Anchored + τ-Weighted (distribution + soft routing)")
347
+ print(" D) Anchored + τ-Gated (distribution + hard routing)")
348
+ print()
349
+ print(f"Trials: {len(seeds)} seeds × {n_trials} trials = {len(seeds) * n_trials} per K")
350
+ print("=" * 70)
351
+
352
+ # Create snapshots
353
+ collapsed = ParameterSnapshot.from_lambdas(sample_tau_collapsed(self.n), "collapsed")
354
+ anchored = ParameterSnapshot.from_lambdas(sample_tau_anchored_tail(self.n, self.L), "anchored_tail")
355
+
356
+ # Create encoders
357
+ uniform_enc = IdentityEncoderWithRouting(self.d, "uniform")
358
+ weighted_enc = IdentityEncoderWithRouting(self.d, "tau_weighted")
359
+ gated_enc = IdentityEncoderWithRouting(self.d, "tau_gated")
360
+
361
+ # Run conditions
362
+ results = {}
363
+
364
+ results["A_collapsed_uniform"] = self.run_sweep(
365
+ collapsed, uniform_enc, "A) Collapsed + Uniform", seeds, n_trials)
366
+
367
+ results["B_anchored_uniform"] = self.run_sweep(
368
+ anchored, uniform_enc, "B) Anchored + Uniform", seeds, n_trials)
369
+
370
+ results["C_anchored_weighted"] = self.run_sweep(
371
+ anchored, weighted_enc, "C) Anchored + τ-Weighted", seeds, n_trials)
372
+
373
+ results["D_anchored_gated"] = self.run_sweep(
374
+ anchored, gated_enc, "D) Anchored + τ-Gated", seeds, n_trials)
375
+
376
+ # Summary
377
+ print("\n" + "=" * 70)
378
+ print("COMPARISON SUMMARY")
379
+ print("=" * 70)
380
+
381
+ print("\n Basin Width (80% threshold):")
382
+ for key in ["A_collapsed_uniform", "B_anchored_uniform", "C_anchored_weighted", "D_anchored_gated"]:
383
+ bw = results[key]["analysis"]["basin_width_80"]
384
+ ratio = results[key]["analysis"]["basin_width_ratio_80"]
385
+ name = results[key]["condition_name"]
386
+ print(f" {name:30s}: {bw:5d} tokens ({ratio:.1%} of L)")
387
+
388
+ print("\n Basin Width (50% threshold):")
389
+ for key in ["A_collapsed_uniform", "B_anchored_uniform", "C_anchored_weighted", "D_anchored_gated"]:
390
+ bw = results[key]["analysis"]["basin_width_50"]
391
+ ratio = results[key]["analysis"]["basin_width_ratio_50"]
392
+ name = results[key]["condition_name"]
393
+ print(f" {name:30s}: {bw:5d} tokens ({ratio:.1%} of L)")
394
+
395
+ # Decision
396
+ bw_B = results["B_anchored_uniform"]["analysis"]["basin_width_50"]
397
+ bw_C = results["C_anchored_weighted"]["analysis"]["basin_width_50"]
398
+ bw_D = results["D_anchored_gated"]["analysis"]["basin_width_50"]
399
+
400
+ print("\n" + "=" * 70)
401
+ print("DECISION")
402
+ print("=" * 70)
403
+
404
+ improvement_C = (bw_C - bw_B) / bw_B if bw_B > 0 else float('inf')
405
+ improvement_D = (bw_D - bw_B) / bw_B if bw_B > 0 else float('inf')
406
+
407
+ if bw_C >= 2048 or bw_D >= 2048:
408
+ conclusion = "ROUTING_SOLVES"
409
+ explanation = (
410
+ f"τ-weighted or τ-gated encoding achieves basin width >= 2048.\n"
411
+ f"Routing was the bottleneck. Identity must be written to slow modes.\n"
412
+ f"Next step: Implement routing during training."
413
+ )
414
+ elif improvement_C >= 0.5 or improvement_D >= 0.5:
415
+ conclusion = "ROUTING_HELPS"
416
+ explanation = (
417
+ f"Routing improves basin width by ≥50%:\n"
418
+ f" Anchored + Uniform: {bw_B}\n"
419
+ f" Anchored + τ-Weighted: {bw_C} ({improvement_C:+.0%})\n"
420
+ f" Anchored + τ-Gated: {bw_D} ({improvement_D:+.0%})\n"
421
+ f"Routing helps but doesn't fully solve. Need combined approach."
422
+ )
423
+ else:
424
+ conclusion = "ROUTING_INEFFECTIVE"
425
+ explanation = (
426
+ f"Routing does NOT significantly improve basin width:\n"
427
+ f" Anchored + Uniform: {bw_B}\n"
428
+ f" Anchored + τ-Weighted: {bw_C} ({improvement_C:+.0%})\n"
429
+ f" Anchored + τ-Gated: {bw_D} ({improvement_D:+.0%})\n"
430
+ f"The bottleneck is elsewhere (perhaps readout or architecture)."
431
+ )
432
+
433
+ print(f"\n Conclusion: {conclusion}")
434
+ print(f"\n {explanation}")
435
+ print("=" * 70)
436
+
437
+ # Assemble results
438
+ full_results = {
439
+ "timestamp": datetime.now().isoformat(),
440
+ "experiment": "routing_experiment",
441
+ "parameters": {
442
+ "num_oscillators": self.n,
443
+ "state_dim": self.d,
444
+ "sequence_length": self.L,
445
+ "k_values": self.k_values,
446
+ "seeds": seeds,
447
+ "n_trials": n_trials
448
+ },
449
+ "conditions": results,
450
+ "comparison": {
451
+ "basin_width_50": {
452
+ "collapsed_uniform": results["A_collapsed_uniform"]["analysis"]["basin_width_50"],
453
+ "anchored_uniform": bw_B,
454
+ "anchored_weighted": bw_C,
455
+ "anchored_gated": bw_D
456
+ },
457
+ "basin_width_80": {
458
+ "collapsed_uniform": results["A_collapsed_uniform"]["analysis"]["basin_width_80"],
459
+ "anchored_uniform": results["B_anchored_uniform"]["analysis"]["basin_width_80"],
460
+ "anchored_weighted": results["C_anchored_weighted"]["analysis"]["basin_width_80"],
461
+ "anchored_gated": results["D_anchored_gated"]["analysis"]["basin_width_80"]
462
+ }
463
+ },
464
+ "conclusion": {
465
+ "verdict": conclusion,
466
+ "explanation": explanation
467
+ }
468
+ }
469
+
470
+ # Save
471
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
472
+ with open(self.output_dir / f"routing_{ts}.json", "w") as f:
473
+ json.dump(full_results, f, indent=2, default=str)
474
+
475
+ report = self._generate_report(full_results)
476
+ with open(self.output_dir / f"ROUTING_REPORT_{ts}.md", "w") as f:
477
+ f.write(report)
478
+
479
+ print(f"\nResults saved to: {self.output_dir}/")
480
+
481
+ return full_results
482
+
483
+ def _generate_report(self, results: Dict[str, Any]) -> str:
484
+ comp = results["comparison"]
485
+ concl = results["conclusion"]
486
+
487
+ report = f"""# Routing Experiment: τ-Weighted Identity Encoding
488
+
489
+ **Date:** {results['timestamp']}
490
+
491
+ ## Question
492
+
493
+ Does preferentially routing identity to long-τ oscillators improve basin width?
494
+
495
+ ## Conditions
496
+
497
+ | Condition | Distribution | Routing | Description |
498
+ |-----------|-------------|---------|-------------|
499
+ | A | Collapsed | Uniform | Baseline |
500
+ | B | Anchored-tail | Uniform | Distribution fix only |
501
+ | C | Anchored-tail | τ-Weighted | Distribution + soft routing |
502
+ | D | Anchored-tail | τ-Gated | Distribution + hard routing |
503
+
504
+ ## Results
505
+
506
+ ### Basin Width (50% threshold)
507
+
508
+ | Condition | Basin Width | Ratio |
509
+ |-----------|-------------|-------|
510
+ | A) Collapsed + Uniform | {comp['basin_width_50']['collapsed_uniform']} | {comp['basin_width_50']['collapsed_uniform']/4096:.1%} |
511
+ | B) Anchored + Uniform | {comp['basin_width_50']['anchored_uniform']} | {comp['basin_width_50']['anchored_uniform']/4096:.1%} |
512
+ | C) Anchored + τ-Weighted | {comp['basin_width_50']['anchored_weighted']} | {comp['basin_width_50']['anchored_weighted']/4096:.1%} |
513
+ | D) Anchored + τ-Gated | {comp['basin_width_50']['anchored_gated']} | {comp['basin_width_50']['anchored_gated']/4096:.1%} |
514
+
515
+ ### Preservation Curves
516
+
517
+ """
518
+ for cond_key in ["A_collapsed_uniform", "B_anchored_uniform", "C_anchored_weighted", "D_anchored_gated"]:
519
+ cond = results["conditions"][cond_key]
520
+ report += f"#### {cond['condition_name']}\n"
521
+ report += "| K | Preserved Rate | Mean Retention |\n|---|---|---|\n"
522
+ for p in cond["analysis"]["preservation_curve"]:
523
+ report += f"| {p['k']} | {p['preserved_rate']:.0%} | {p['mean_retention']:.1%} |\n"
524
+ report += "\n"
525
+
526
+ report += f"""## Conclusion
527
+
528
+ **Verdict: {concl['verdict']}**
529
+
530
+ {concl['explanation']}
531
+
532
+ ---
533
+
534
+ *Report generated by identity_routing_experiment.py*
535
+ """
536
+ return report
537
+
538
+
539
+ def run_experiment():
540
+ experiment = RoutingExperiment(
541
+ num_oscillators=32,
542
+ state_dim=16,
543
+ sequence_length=4096
544
+ )
545
+ return experiment.run_full_experiment(
546
+ seeds=[42, 137, 256, 314, 999],
547
+ n_trials=8
548
+ )
549
+
550
+
551
+ if __name__ == "__main__":
552
+ run_experiment()