gam-sae 0.3.148

Sparse-autoencoder latent-manifold terms for the gam penalized-likelihood engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
//! Contract composition and loop holonomy — the "Atlas Machine" composition
//! layer (issue: end-to-end bounds over per-edge diagnostics).
//!
//! The crate already emits per-edge diagnostics for the atlas of charts and the
//! transports between them: [`layer_transport`](crate::inference::layer_transport)
//! reports isometry defects and composition-law defects, [`steering`](crate::inference::steering)
//! reports validity radii and amortization gaps, and
//! [`transport_class`](crate::inference::transport_class) classifies each circle
//! transport as an `O(2)` element `(winding, phase)`. What was missing is the
//! step that COMPOSES those local certificates into a statement about a whole
//! chain or a whole loop. This module is that step.
//!
//! Two instruments live here, both pure `f64` and both obeying the house
//! **measure-don't-latch** doctrine used across the crate (validity radii are
//! reported, never silently clipped — see [`steering`](crate::inference::steering);
//! the composition-defect floor is reported, never folded in — see
//! [`layer_transport`](crate::inference::layer_transport)):
//!
//! 1. **Contract composition** ([`compose_contracts`], [`compose_with_trace`]):
//!    the shadowing bound that turns a chain of per-component error/expansion
//!    certificates into a single end-to-end error bound, plus a domain-feasibility
//!    trace that reports where the accumulated error would leave a component's
//!    certified domain instead of quietly shrinking the bound to fit.
//! 2. **Loop holonomy** ([`loop_holonomy`], [`holonomy_from_transports`]): the
//!    net `O(2)` element obtained by composing the transports around a closed
//!    loop of charts, with a trivial/nontrivial verdict whose tolerance is
//!    DERIVED from the loop's own defects (never a magic constant).

use crate::inference::layer_transport::{ChartTopology, FittedTransport};
use crate::inference::transport_class::CircleTransportReport;
use ndarray::Array1;
use std::f64::consts::{PI, TAU};

/// One component map of a chart-coordinate pipeline, abstracted to exactly the
/// three numbers a composition bound needs.
///
/// The map is a certificate, not the map itself: it says that on a coordinate
/// ball of radius [`domain_radius`](Self::domain_radius) about the nominal
/// trajectory, the realized map stays within [`defect`](Self::defect) of the
/// true map it approximates (both measured in the same chart metric), and it
/// expands that metric by at most a factor [`lipschitz`](Self::lipschitz).
///
/// This is the common shape of every per-edge diagnostic the crate already
/// emits — an amortized decoder (defect = amortization gap, lipschitz from the
/// pullback), a fitted transport (defect = isometry/composition defect,
/// lipschitz from `|h′|`), a steering push (defect = endpoint-vs-path dose gap,
/// domain_radius = validity radius).
#[derive(Debug, Clone)]
pub struct Contract {
    /// Legible name of the component (for trace reporting).
    pub name: String,
    /// Radius of the coordinate ball on which this certificate is valid.
    pub domain_radius: f64,
    /// Sup-norm gap between the realized map and the true map on the domain
    /// ball, in the chart metric.
    pub defect: f64,
    /// Metric expansion (Lipschitz) bound of the map on the domain ball.
    pub lipschitz: f64,
}

impl Contract {
    /// Validate the three certificate numbers: all finite, none negative.
    fn validate(&self, stage: usize) -> Result<(), String> {
        for (label, v) in [
            ("domain_radius", self.domain_radius),
            ("defect", self.defect),
            ("lipschitz", self.lipschitz),
        ] {
            if !v.is_finite() {
                return Err(format!(
                    "contract stage {stage} ({}): {label} = {v} is not finite",
                    self.name
                ));
            }
            if v < 0.0 {
                return Err(format!(
                    "contract stage {stage} ({}): {label} = {v} is negative",
                    self.name
                ));
            }
        }
        Ok(())
    }

    /// Build a component contract from a fitted inter-layer transport `h`, the
    /// bridge from the crate's real transport artifacts ([`FittedTransport`],
    /// produced by [`fit_transport_map`](crate::inference::layer_transport::fit_transport_map))
    /// into the composer. The three certificate numbers are read directly off
    /// the fit:
    ///
    /// - `domain_radius` = the source chart's coordinate span (a full turn `2π`
    ///   on a circle, `hi − lo` on an interval): the ball on which `h`'s smooth
    ///   is defined.
    /// - `defect` = the target-space approximation error of the fitted map,
    ///   estimated by its [`residual_rms`](FittedTransport::residual_rms) — how
    ///   far `h` sits from the observed continuation coordinates. This is the
    ///   sup-norm-style gap the shadowing bound propagates.
    /// - `lipschitz` = the metric expansion bound `sup_t |h′(t)|` measured over
    ///   a dense grid of the source domain — the largest local stretch the map
    ///   applies. For a near-isometric TRANSPORT layer this is `≈ 1`; a COMPUTE
    ///   layer that reshapes the chart metric reads `> 1` and amplifies earlier
    ///   stages' error more (see [`compose_contracts`]).
    pub fn from_transport(
        t: &FittedTransport,
        name: impl Into<String>,
        grid: usize,
    ) -> Result<Contract, String> {
        let (lo, hi) = match t.topology_from {
            ChartTopology::Circle => (0.0, TAU),
            ChartTopology::Interval { lo, hi } => (lo, hi),
        };
        let g = grid.max(2);
        let pts: Vec<f64> = (0..g)
            .map(|k| lo + (hi - lo) * (k as f64) / ((g - 1) as f64))
            .collect();
        let deriv = t.derivative(Array1::from_vec(pts).view())?;
        let lipschitz = deriv.iter().fold(0.0_f64, |m, &v| m.max(v.abs()));
        Ok(Contract {
            name: name.into(),
            domain_radius: hi - lo,
            defect: t.residual_rms,
            lipschitz,
        })
    }
}

/// The end-to-end certificate produced by composing a [`Contract`] chain.
#[derive(Debug, Clone)]
pub struct ComposedContract {
    /// Shadowing bound on the sup-norm gap between the composed realized map
    /// and the composed true map over the whole chain.
    pub total_defect: f64,
    /// `per_stage_contribution[j]` is stage `j`'s additive share of
    /// [`total_defect`](Self::total_defect): `defect_j · Π_{i>j} lipschitz_i`.
    /// Sums to `total_defect`. Earlier stages carry more when the later
    /// expansion factors exceed 1 (their error is amplified by more stages).
    pub per_stage_contribution: Vec<f64>,
    /// Whether the accumulated error stays inside every stage's certified
    /// domain along the chain. For [`compose_contracts`] this is the drift-only
    /// check (accumulated error alone must fit each `domain_radius`); for
    /// [`compose_with_trace`] the feasible path always returns `true` and an
    /// infeasible one returns `Err` naming the offending stages.
    pub domain_ok: bool,
}

/// Compose a chain of component contracts into one end-to-end shadowing bound.
///
/// The chain is applied in order: `F = f_{n} ∘ … ∘ f_1`, with `f_j` the realized
/// map of `chain[j-1]` approximating the true map `g_j` (`|f_j − g_j| ≤
/// defect_j` on its domain) and `g_j` metric-Lipschitz with constant
/// `lipschitz_j`.
///
/// # Shadowing bound (two-line induction)
///
/// Let `F_k = f_k ∘ … ∘ f_1`, `G_k = g_k ∘ … ∘ g_1`, and `E_k = sup_x |F_k(x) −
/// G_k(x)|` (`E_0 = 0`). Splitting the `k`-th step through the intermediate
/// point `F_{k-1}(x)`:
///
/// ```text
/// |F_k(x) − G_k(x)|
///   = |f_k(F_{k-1}(x)) − g_k(G_{k-1}(x))|
///   ≤ |f_k(F_{k-1}(x)) − g_k(F_{k-1}(x))|   (approximation error of f_k)
///   + |g_k(F_{k-1}(x)) − g_k(G_{k-1}(x))|   (g_k is lipschitz_k-Lipschitz)
///   ≤ defect_k + lipschitz_k · E_{k-1}.
/// ```
///
/// Taking the sup gives the recurrence `E_k ≤ defect_k + lipschitz_k · E_{k-1}`,
/// and unrolling it from `E_0 = 0` gives the closed form
///
/// ```text
/// E_n ≤ Σ_{j=1}^{n} defect_j · Π_{i=j+1}^{n} lipschitz_i,
/// ```
///
/// which is [`total_defect`](ComposedContract::total_defect); the `j`-th summand
/// is [`per_stage_contribution`](ComposedContract::per_stage_contribution)`[j-1]`.
///
/// [`domain_ok`](ComposedContract::domain_ok) is the drift-only feasibility
/// check available without entry radii: the error accumulated *before* stage `j`
/// (namely `E_{j-1}`) must not already exceed stage `j`'s `domain_radius`, else
/// the realized trajectory has drifted outside where `f_j`'s certificate holds.
/// Use [`compose_with_trace`] to fold in the nominal input spread as well.
///
/// An empty chain is the identity: zero defect, no contributions, feasible.
pub fn compose_contracts(chain: &[Contract]) -> ComposedContract {
    let n = chain.len();
    // Suffix products Π_{i>j} lipschitz_i, computed right-to-left.
    let mut suffix = vec![1.0_f64; n + 1];
    for j in (0..n).rev() {
        suffix[j] = suffix[j + 1] * chain[j].lipschitz;
    }
    let mut per_stage_contribution = vec![0.0_f64; n];
    let mut total_defect = 0.0_f64;
    for j in 0..n {
        let c = chain[j].defect * suffix[j + 1];
        per_stage_contribution[j] = c;
        total_defect += c;
    }

    // Drift-only domain check: accumulated error entering stage j is E_{j-1};
    // require it to fit stage j's domain_radius. Propagate E the same way the
    // bound does.
    let mut domain_ok = true;
    let mut accumulated = 0.0_f64;
    for stage in chain.iter() {
        if accumulated > stage.domain_radius {
            domain_ok = false;
        }
        accumulated = stage.defect + stage.lipschitz * accumulated;
    }

    ComposedContract {
        total_defect,
        per_stage_contribution,
        domain_ok,
    }
}

/// Compose a contract chain and check domain feasibility against the nominal
/// input spread at each stage entry.
///
/// `entry_radii[j]` is the radius of the coordinate ball the pipeline actually
/// feeds to stage `j` (the nominal spread of inputs, before approximation
/// error). The realized inputs to stage `j` sit within `entry_radii[j] + E_{j-1}`
/// of the certified center, where `E_{j-1}` is the error accumulated before
/// stage `j` (propagated exactly as in [`compose_contracts`]). Feasibility
/// requires that displacement to fit the stage's certified domain:
///
/// ```text
/// entry_radii[j] + E_{j-1} ≤ domain_radius_j    for every stage j.
/// ```
///
/// Per the measure-don't-latch doctrine, a violation is **reported, never folded
/// into the bound**: [`total_defect`](ComposedContract::total_defect) is always
/// the honest shadowing bound of the chain (it is never shrunk to make a stage
/// fit), and any stage whose certified domain is overrun is surfaced as an
/// `Err` naming the stage and the exact overflow. A feasible chain returns
/// `Ok` with [`domain_ok`](ComposedContract::domain_ok)` = true`.
///
/// `Err` is also returned for structural problems (length mismatch, non-finite
/// or negative certificate numbers or entry radii).
pub fn compose_with_trace(
    chain: &[Contract],
    entry_radii: &[f64],
) -> Result<ComposedContract, String> {
    if chain.len() != entry_radii.len() {
        return Err(format!(
            "compose_with_trace: {} contracts but {} entry radii",
            chain.len(),
            entry_radii.len()
        ));
    }
    for (j, c) in chain.iter().enumerate() {
        c.validate(j)?;
    }
    for (j, &r) in entry_radii.iter().enumerate() {
        if !r.is_finite() || r < 0.0 {
            return Err(format!(
                "compose_with_trace: entry_radii[{j}] = {r} invalid"
            ));
        }
    }

    let composed = compose_contracts(chain);

    // Feasibility trace: displacement entering stage j is entry_radii[j] + E_{j-1}.
    // Collect every violation rather than bailing on the first — a full report
    // is more actionable than the first offender.
    let mut violations: Vec<String> = Vec::new();
    let mut accumulated = 0.0_f64;
    for (j, stage) in chain.iter().enumerate() {
        let required = entry_radii[j] + accumulated;
        if required > stage.domain_radius {
            let overflow = required - stage.domain_radius;
            violations.push(format!(
                "stage {j} ({}): entry radius {} + accumulated error {} = {} exceeds \
                 domain_radius {} by {}",
                stage.name, entry_radii[j], accumulated, required, stage.domain_radius, overflow
            ));
        }
        accumulated = stage.defect + stage.lipschitz * accumulated;
    }

    if !violations.is_empty() {
        return Err(format!(
            "compose_with_trace: {} domain violation(s) (bound total_defect = {} is unchanged): {}",
            violations.len(),
            composed.total_defect,
            violations.join("; ")
        ));
    }

    Ok(ComposedContract {
        domain_ok: true,
        ..composed
    })
}

/// The net `O(2)` element obtained by composing the transports around a closed
/// loop of charts, with a derived trivial/nontrivial verdict.
#[derive(Debug, Clone)]
pub struct HolonomyReport {
    /// Number of edges (transports) composed around the loop.
    pub loop_len: usize,
    /// Sign of the net element: `+1` net rotation, `−1` net reflection
    /// (product of the per-edge signs).
    pub net_sign: i8,
    /// Net rotation angle in `(−π, π]` (for a net reflection, the phase of the
    /// reflected element).
    pub net_angle: f64,
    /// Whether the net element is within tolerance of the identity: a rotation
    /// (`net_sign = +1`) whose angle is below [`angle_tolerance`](Self::angle_tolerance).
    /// A net reflection is never trivial (the identity is a rotation).
    pub is_trivial: bool,
    /// Tolerance the trivial verdict uses, DERIVED as the loop's composed defect
    /// bound (see [`loop_holonomy`]).
    pub angle_tolerance: f64,
}

/// Wrap an angle into `(−π, π]`.
fn wrap_pi(x: f64) -> f64 {
    let w = (x + PI).rem_euclid(TAU) - PI;
    if w <= -PI { w + TAU } else { w }
}

/// The inverse of the `O(2)` edge `(sign, angle)`.
///
/// The element acts as `x ↦ sign·x + angle`; its inverse is `y ↦ sign·(y −
/// angle) = sign·y − sign·angle` (using `sign² = 1`), i.e. `(sign, −sign·angle)`.
/// This is what closes a composition triangle into a loop: given the two
/// forward hops `h_ab, h_bc` and the direct map `h_ac`, the loop
/// `h_ab, h_bc, h_ac⁻¹` returns to the start, so its [`loop_holonomy`] measures
/// exactly the failure of the composition law `h_ac = h_bc ∘ h_ab` as an `O(2)`
/// element.
pub fn invert_o2_edge(edge: (i8, f64)) -> (i8, f64) {
    let s = if edge.0 >= 0 { 1i8 } else { -1i8 };
    (s, -(s as f64) * edge.1)
}

/// Compose a closed loop of circle isometries and report the net `O(2)` element.
///
/// Each edge is an `O(2)` element `(sign, angle)` in the vocabulary of
/// [`transport_class`](crate::inference::transport_class): `sign = +1` is the
/// rotation `x ↦ x + angle`, `sign = −1` is the reflection `x ↦ −x + angle`
/// (matching [`CircleTransportReport::winding`] and `phase`). The elements are
/// composed exactly in `O(2)`: for `g = (s_g, φ_g)` applied after `f = (s_f,
/// φ_f)`,
///
/// ```text
/// (g ∘ f)(x) = s_g·(s_f·x + φ_f) + φ_g = (s_g·s_f)·x + (s_g·φ_f + φ_g),
/// ```
///
/// so signs multiply and a reflection flips the orientation of every angle it
/// composes over. Folding this over the loop yields the net element; rotation
/// angles add (mod 2π) and the net sign is the product of the edge signs — both
/// tracked exactly, not sampled.
///
/// # Trivial verdict and its derived tolerance
///
/// Nontrivial holonomy is the obstruction to the loop being a single global
/// feature: if transporting a concept all the way around the loop does not
/// return it to itself, the charts cannot be glued into one coordinate for that
/// feature across the loop. The identity is a rotation by `0`, so the net
/// element is trivial only when `net_sign = +1` and `|net_angle|` is within the
/// tolerance.
///
/// The tolerance is not a magic constant: it is the loop's own composed defect
/// bound. Each transport is an isometry, so its Lipschitz constant is exactly
/// `1`; feeding `lipschitz_i ≡ 1` into the shadowing bound of
/// [`compose_contracts`] collapses `Σ_j defect_j · Π_{i>j} lipschitz_i` to
/// `Σ_j defect_j`. So the angle by which the composed transport is uncertain is
/// bounded by the sum of the per-edge defects, and any net angle smaller than
/// that cannot be distinguished from the identity — the honest measure-don't-latch
/// verdict (a cleaner loop, with smaller defects, gets a tighter tolerance and
/// so is easier to certify nontrivial).
pub fn loop_holonomy(edges: &[(i8, f64)], defects: &[f64]) -> HolonomyReport {
    // Fold the O(2) elements in loop order: acc ← edge ∘ acc.
    let mut acc_sign = 1i8;
    let mut acc_angle = 0.0_f64;
    for &(sign, angle) in edges.iter() {
        let s = if sign >= 0 { 1i8 } else { -1i8 };
        acc_angle = (s as f64) * acc_angle + angle;
        acc_sign *= s;
    }
    let net_angle = wrap_pi(acc_angle);

    // Derived tolerance: composed defect bound of the loop = Σ defects, since
    // isometries have Lipschitz 1 (see the doc comment). Defensive against a
    // caller passing a mismatched-length or non-finite defect slice.
    let angle_tolerance = defects
        .iter()
        .copied()
        .filter(|v| v.is_finite() && *v >= 0.0)
        .sum::<f64>();

    let is_trivial = acc_sign == 1 && net_angle.abs() <= angle_tolerance;

    HolonomyReport {
        loop_len: edges.len(),
        net_sign: acc_sign,
        net_angle,
        is_trivial,
        angle_tolerance,
    }
}

/// Adapter: compute loop holonomy directly from a loop of fitted circle
/// transport classifications.
///
/// [`CircleTransportReport`] already carries the `O(2)` element as
/// [`winding`](CircleTransportReport::winding) (the sign) and
/// [`phase`](CircleTransportReport::phase) (the angle), plus its
/// [`defect`](CircleTransportReport::defect) (the `O(2)` departure that bounds
/// the angle uncertainty). This lifts them straight into [`loop_holonomy`]
/// without the caller restating the `(sign, angle)` interface. The reports must
/// be given in loop order.
pub fn holonomy_from_transports(loop_edges: &[CircleTransportReport]) -> HolonomyReport {
    let edges: Vec<(i8, f64)> = loop_edges.iter().map(|r| (r.winding, r.phase)).collect();
    let defects: Vec<f64> = loop_edges.iter().map(|r| r.defect).collect();
    loop_holonomy(&edges, &defects)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Deterministic LCG uniform in `[0, 1)` for the transport-fit tests.
    fn lcg(seed: &mut u64) -> f64 {
        *seed = seed
            .wrapping_mul(6364136223846793005)
            .wrapping_add(1442695040888963407);
        ((*seed >> 11) as f64) / ((1u64 << 53) as f64)
    }

    fn c(name: &str, domain_radius: f64, defect: f64, lipschitz: f64) -> Contract {
        Contract {
            name: name.to_string(),
            domain_radius,
            defect,
            lipschitz,
        }
    }

    #[test]
    fn composition_matches_closed_form_sum() {
        // Hand-built 3-stage chain with distinct numbers.
        let chain = [
            c("a", 10.0, 0.1, 2.0),
            c("b", 10.0, 0.2, 3.0),
            c("c", 10.0, 0.4, 5.0),
        ];
        let out = compose_contracts(&chain);
        // total = 0.1·(3·5) + 0.2·(5) + 0.4·(1) = 1.5 + 1.0 + 0.4 = 2.9.
        assert!((out.total_defect - 2.9).abs() < 1e-12);
        assert!((out.per_stage_contribution[0] - 1.5).abs() < 1e-12);
        assert!((out.per_stage_contribution[1] - 1.0).abs() < 1e-12);
        assert!((out.per_stage_contribution[2] - 0.4).abs() < 1e-12);
        // Contributions sum to the total.
        let s: f64 = out.per_stage_contribution.iter().sum();
        assert!((s - out.total_defect).abs() < 1e-12);
        assert!(out.domain_ok);
    }

    #[test]
    fn lipschitz_gt_one_amplifies_early_defects_more() {
        // Equal defects, equal expansion > 1: earlier stages carry more.
        let chain = [
            c("a", 100.0, 0.3, 2.0),
            c("b", 100.0, 0.3, 2.0),
            c("c", 100.0, 0.3, 2.0),
            c("d", 100.0, 0.3, 2.0),
        ];
        let out = compose_contracts(&chain);
        for w in out.per_stage_contribution.windows(2) {
            assert!(w[0] > w[1], "contribution not strictly decreasing: {w:?}");
        }
        // Closed form: 0.3·(8 + 4 + 2 + 1) = 0.3·15 = 4.5.
        assert!((out.total_defect - 4.5).abs() < 1e-12);
    }

    #[test]
    fn empty_chain_is_identity() {
        let out = compose_contracts(&[]);
        assert_eq!(out.total_defect, 0.0);
        assert!(out.per_stage_contribution.is_empty());
        assert!(out.domain_ok);
    }

    #[test]
    fn trace_reports_domain_violation_at_right_stage() {
        // Stage 0 amplifies heavily so the error entering stage 2 blows its
        // tight domain; stages 0 and 1 have generous domains.
        let chain = [
            c("a", 100.0, 1.0, 10.0),
            c("b", 100.0, 1.0, 10.0),
            c("c", 5.0, 1.0, 1.0),
        ];
        // entry spreads are small; the accumulated error is what overruns.
        // E_0 = 0, E_1 = 1 + 10·0 = 1, E_2 = 1 + 10·1 = 11.
        // Stage 2 entry displacement = 0 + 11 = 11 > domain_radius 5.
        let entry = [0.0, 0.0, 0.0];
        let res = compose_with_trace(&chain, &entry);
        let err = res.expect_err("expected a domain violation");
        assert!(err.contains("stage 2"), "wrong stage reported: {err}");
        assert!(err.contains("by 6"), "wrong overflow reported: {err}");
        // The bound itself is still reported, unmodified.
        assert!(err.contains("total_defect"));
    }

    #[test]
    fn trace_feasible_chain_is_ok() {
        let chain = [
            c("a", 100.0, 0.1, 1.0),
            c("b", 100.0, 0.1, 1.0),
            c("c", 100.0, 0.1, 1.0),
        ];
        let entry = [1.0, 1.0, 1.0];
        let out = compose_with_trace(&chain, &entry).expect("feasible");
        assert!(out.domain_ok);
        assert!((out.total_defect - 0.3).abs() < 1e-12);
    }

    #[test]
    fn trace_length_mismatch_errors() {
        let chain = [c("a", 1.0, 0.1, 1.0)];
        assert!(compose_with_trace(&chain, &[1.0, 2.0]).is_err());
    }

    #[test]
    fn rotations_summing_to_zero_are_trivial() {
        // Pure rotations that sum to 2π ≡ 0, tiny defects.
        let edges = [(1i8, 2.0), (1, 2.0), (1, TAU - 4.0)];
        let defects = [1e-6, 1e-6, 1e-6];
        let r = loop_holonomy(&edges, &defects);
        assert_eq!(r.net_sign, 1);
        assert!(r.net_angle.abs() < 1e-9, "net_angle = {}", r.net_angle);
        assert!(r.is_trivial);
    }

    #[test]
    fn small_net_rotation_with_tiny_defects_is_nontrivial() {
        // Net rotation π/7, defects far below it → cannot be excluded as noise.
        let edges = [(1i8, PI / 7.0)];
        let defects = [1e-4];
        let r = loop_holonomy(&edges, &defects);
        assert_eq!(r.net_sign, 1);
        assert!((r.net_angle - PI / 7.0).abs() < 1e-12);
        assert!(!r.is_trivial);
    }

    #[test]
    fn two_reflections_compose_to_a_rotation() {
        let edges = [(-1i8, 0.3), (-1, 0.9)];
        let defects = [1e-6, 1e-6];
        let r = loop_holonomy(&edges, &defects);
        // (-1)·(-1) = +1: net rotation.
        assert_eq!(r.net_sign, 1);
        // acc: start (1,0); edge0 → (-1, 0.3); edge1 = (-1,0.9)∘(-1,0.3):
        // sign +1, angle = -1·0.3 + 0.9 = 0.6.
        assert!((r.net_angle - 0.6).abs() < 1e-12);
    }

    #[test]
    fn single_reflection_stays_a_reflection() {
        let edges = [(1i8, 0.2), (-1, 0.4)];
        let defects = [1e-6, 1e-6];
        let r = loop_holonomy(&edges, &defects);
        assert_eq!(r.net_sign, -1);
        // A reflection is never the identity.
        assert!(!r.is_trivial);
    }

    #[test]
    fn tolerance_above_net_angle_cannot_exclude_identity() {
        // Same π/7 net rotation, but defects summing above π/7: the bound can't
        // distinguish it from the identity, so measure-don't-latch calls it
        // trivial (we do not latch a nontrivial verdict the data can't support).
        let edges = [(1i8, PI / 7.0)];
        let defects = [PI / 7.0 + 0.01];
        let r = loop_holonomy(&edges, &defects);
        assert!(r.angle_tolerance > (PI / 7.0));
        assert!(r.is_trivial);
    }

    #[test]
    fn empty_loop_is_trivial_identity() {
        let r = loop_holonomy(&[], &[]);
        assert_eq!(r.loop_len, 0);
        assert_eq!(r.net_sign, 1);
        assert_eq!(r.net_angle, 0.0);
        assert!(r.is_trivial);
    }

    #[test]
    fn invert_o2_edge_round_trips_to_identity() {
        use crate::inference::contracts::invert_o2_edge;
        // A rotation and its inverse compose to the identity.
        let e = (1i8, 0.7);
        let inv = invert_o2_edge(e);
        let r = loop_holonomy(&[e, inv], &[0.0, 0.0]);
        assert_eq!(r.net_sign, 1);
        assert!(r.net_angle.abs() < 1e-12);
        assert!(r.is_trivial);
        // A reflection is its own inverse's sign; edge·inv = identity too.
        let f = (-1i8, 1.1);
        let finv = invert_o2_edge(f);
        assert_eq!(finv.0, -1);
        let r2 = loop_holonomy(&[f, finv], &[0.0, 0.0]);
        assert_eq!(r2.net_sign, 1);
        assert!(r2.net_angle.abs() < 1e-12);
    }

    #[test]
    fn contract_from_near_isometric_transport() {
        use crate::inference::layer_transport::{ChartTopology, fit_transport_map};
        // Plant a pure rotation θ_to = θ_from + 0.6 (mod 2π): a near-isometry,
        // so lipschitz ≈ 1, domain_radius = 2π, defect ≈ 0.
        let mut s = 11u64;
        let n = 256usize;
        let (mut from, mut to) = (Vec::with_capacity(n), Vec::with_capacity(n));
        for _ in 0..n {
            let th = lcg(&mut s) * TAU;
            from.push(th);
            to.push((th + 0.6).rem_euclid(TAU));
        }
        let fit = fit_transport_map(
            Array1::from_vec(from).view(),
            Array1::from_vec(to).view(),
            ChartTopology::Circle,
            ChartTopology::Circle,
        )
        .expect("transport fit");
        let c = Contract::from_transport(&fit, "L_a->L_b", 128).expect("contract");
        assert!((c.domain_radius - TAU).abs() < 1e-9);
        assert!(
            (c.lipschitz - 1.0).abs() < 0.05,
            "lipschitz = {}",
            c.lipschitz
        );
        assert!(c.defect < 0.05, "defect = {}", c.defect);
        // It composes with itself as a well-formed chain.
        let composed = compose_contracts(&[c.clone(), c]);
        assert!(composed.total_defect.is_finite());
    }

    #[test]
    fn adapter_matches_plain_interface() {
        let reports = [
            CircleTransportReport {
                layer_from: 0,
                layer_to: 1,
                n_samples: 128,
                winding: -1,
                phase: 0.3,
                defect: 1e-6,
                resultant_shift: 0.0,
                resultant_reflect: 1.0,
                class: crate::inference::transport_class::CircleTransportClass::Reflect,
            },
            CircleTransportReport {
                layer_from: 1,
                layer_to: 2,
                n_samples: 128,
                winding: -1,
                phase: 0.9,
                defect: 1e-6,
                resultant_shift: 0.0,
                resultant_reflect: 1.0,
                class: crate::inference::transport_class::CircleTransportClass::Reflect,
            },
        ];
        let via_adapter = holonomy_from_transports(&reports);
        let via_plain = loop_holonomy(&[(-1, 0.3), (-1, 0.9)], &[1e-6, 1e-6]);
        assert_eq!(via_adapter.net_sign, via_plain.net_sign);
        assert!((via_adapter.net_angle - via_plain.net_angle).abs() < 1e-15);
        assert_eq!(via_adapter.is_trivial, via_plain.is_trivial);
    }
}