feat(semisym): modular fast path + scar-map ingest skeleton

- modp.rs: order-3 jet AD over 𝔽_p (multi-prime, robust exact-escalation,
  named edge receipts, Miller–Rabin modulus guard); 272× at Δ₇ centroid,
  gate-preserving (≢0 mod p is a proof; PROPER via division-free certificate)
- main.rs: --mod flag, validate-modp cross-check, and collision-fixed survey
  filenames (numer-denom; reduced numerators silently folded ~6435 pts onto 330)
- ingest_semisym_scar.rs: receipt → ene.rrc_classifications dry-run mapper,
  float-free exact JSONB metrics (no Q16/IEEE), --apply gated behind explicit --dsn
- CITATION.cff: credit Kritchevsky "Everything Is Logarithms" for the exact /
  log-in-prime-basis scar-map encoding

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
allaun 2026-07-02 20:38:22 -05:00
parent 6c58feaf53
commit e128aa50aa
5 changed files with 1462 additions and 21 deletions

View file

@ -17,7 +17,7 @@ authors:
given-names: "Brandon"
alias: "allaunthefox"
affiliation: "Independent Researcher"
orcid: "https://orcid.org/0000-0000-0000-0000"
orcid: "https://orcid.org/0009-0000-1594-0095"
repository: "https://github.com/allaunthefox/SilverSight"
repository-code: "https://github.com/allaunthefox/SilverSight"
url: "https://github.com/allaunthefox/SilverSight"
@ -178,7 +178,7 @@ references:
given-names: "Alex"
date-published: "2026-05-25"
url: "https://alexkritchevsky.com/2026/05/25/everything-is-logarithms.html"
notes: "Binds to SilverSight formal/CoreFormalism/HachimojiLUT.lean and formal/CoreFormalism/ChentsovFinite.lean. Core claim: logarithms are coordinate-free objects."
notes: "Binds to SilverSight formal/CoreFormalism/HachimojiLUT.lean and formal/CoreFormalism/ChentsovFinite.lean. Core claim: logarithms are coordinate-free objects. Also informed rust/src/bin/ingest_semisym_scar.rs (semisym scar-map ingest): the prime-factorization-as-logarithm identity (log n = Sum_p nu_p(n) log p) motivated the exact, float-free encoding of scar-map metrics (rationals kept exact, no Q16.16/IEEE float) and the planned prime-exponent nu_p log-vector field over the precomputed prime-LUT basis."
- type: article
title: "Stabilizing Recurrent Dynamics for Test-Time Scalable Latent Reasoning in Looped Language Models"

207
rust/semisym/Cargo.lock generated Normal file
View file

@ -0,0 +1,207 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "semisym"
version = "0.1.0"
dependencies = [
"num-bigint",
"num-rational",
"num-traits",
"rayon",
"serde",
"serde_json",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View file

@ -36,7 +36,9 @@ use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
type Q = BigRational;
mod modp;
pub(crate) type Q = BigRational;
fn q(n: i64, d: i64) -> Q {
Q::new(BigInt::from(n), BigInt::from(d))
@ -56,12 +58,12 @@ fn parse_q(s: &str) -> Q {
// ── symmetric index helpers (n vars) ─────────────────────────────────
#[inline]
fn i2(n: usize, i: usize, j: usize) -> usize {
pub(crate) fn i2(n: usize, i: usize, j: usize) -> usize {
let (i, j) = if i <= j { (i, j) } else { (j, i) };
i * n - i * (i + 1) / 2 + j
}
fn i3_table(n: usize) -> (Vec<[usize; 3]>, Vec<Vec<Vec<usize>>>) {
pub(crate) fn i3_table(n: usize) -> (Vec<[usize; 3]>, Vec<Vec<Vec<usize>>>) {
let mut list = Vec::new();
let mut idx = vec![vec![vec![0usize; n]; n]; n];
for i in 0..n {
@ -226,7 +228,7 @@ impl JetCtx {
// ── receipts ─────────────────────────────────────────────────────────
#[derive(Serialize)]
struct Receipt {
pub(crate) struct Receipt {
schema: &'static str,
m: usize,
geometrization: String,
@ -250,7 +252,7 @@ fn qs(x: &Q) -> String {
// ── geometry pipeline at a rational point ────────────────────────────
fn drift_beta(m: usize) -> Vec<Q> {
pub(crate) fn drift_beta(m: usize) -> Vec<Q> {
// BraidStateN.lean rossbyDriftFromChirality weights, cycled
let base = [q(1, 1), q(-1, 1), q(1, 2), q(0, 1)];
let w: Vec<Q> = (0..m).map(|i| base[i % 4].clone()).collect();
@ -258,7 +260,7 @@ fn drift_beta(m: usize) -> Vec<Q> {
w.iter().map(|wi| wi - &mean).collect()
}
fn probe(m: usize, point: &[Q]) -> Receipt {
pub(crate) fn probe(m: usize, point: &[Q]) -> Receipt {
let n = m - 1;
let cx = JetCtx::new(n);
let ps: Vec<Jet> = (0..n).map(|i| cx.coord(i, point[i].clone())).collect();
@ -572,10 +574,19 @@ fn mat_inv(cx: &JetCtx, a: &[Vec<Jet>], n: usize) -> Vec<Vec<Jet>> {
inv
}
fn signature(gm: &[Vec<Q>]) -> (usize, usize) {
// Exact signature by symmetric (congruence) elimination — robust to
// vanishing leading minors, which Sylvester's criterion is not.
// Congruences A ↦ EᵀAE preserve signature (Sylvester's law of inertia).
pub(crate) fn signature(gm: &[Vec<Q>]) -> (usize, usize) {
// Panicking wrapper for the exact/oracle path, where a degenerate g'
// block is not expected. The robust modular path uses signature_opt and
// records an edge receipt instead of crashing.
signature_opt(gm).expect("degenerate metric block (rank deficiency)")
}
/// Exact signature by symmetric (congruence) elimination — robust to vanishing
/// leading minors, which Sylvester's criterion is not. Congruences A ↦ EᵀAE
/// preserve signature (Sylvester's law of inertia). Returns None (rather than
/// panicking) when the block is rank-deficient / degenerate — a genuine
/// geometric edge (e.g. a null drift direction) that callers record explicitly.
pub(crate) fn signature_opt(gm: &[Vec<Q>]) -> Option<(usize, usize)> {
let n = gm.len();
let mut a: Vec<Vec<Q>> = gm.to_vec();
let (mut pos, mut neg) = (0usize, 0usize);
@ -598,7 +609,7 @@ fn signature(gm: &[Vec<Q>]) -> (usize, usize) {
a[t][k] += v;
}
} else {
panic!("degenerate metric block (rank deficiency)");
return None; // rank-deficient block (e.g. null drift) — edge case
}
}
if a[k][k] > Q::zero() {
@ -621,9 +632,10 @@ fn signature(gm: &[Vec<Q>]) -> (usize, usize) {
a[k][i] = Q::zero();
}
}
(pos, neg)
Some((pos, neg))
}
#[allow(dead_code)]
fn det(a: &[Vec<Q>]) -> Q {
let n = a.len();
let mut m = a.to_vec();
@ -694,6 +706,7 @@ fn main() {
let mut grid = 2u64;
let mut ckpt = PathBuf::from("semisym_ckpt");
let mut max_points = 200usize;
let mut primes: Option<Vec<u64>> = None; // Some ⇒ modular fast path
let mut i = 2;
while i < args.len() {
match args[i].as_str() {
@ -705,18 +718,49 @@ fn main() {
"--grid" => { grid = args[i + 1].parse().unwrap(); i += 2; }
"--checkpoint" => { ckpt = PathBuf::from(&args[i + 1]); i += 2; }
"--max-points" => { max_points = args[i + 1].parse().unwrap(); i += 2; }
"--mod" => {
// bare `--mod` uses the default multi-prime set; `--mod p1,p2,..`
// overrides (each must be prime — guarded below).
let list = args.get(i + 1).and_then(|s| {
s.split(',').map(|x| x.parse::<u64>()).collect::<Result<Vec<u64>, _>>().ok()
});
match list {
Some(l) if !l.is_empty() => { primes = Some(l); i += 2; }
_ => { primes = Some(modp::DEFAULT_PRIMES.to_vec()); i += 1; }
}
}
other => panic!("unknown arg {other}"),
}
}
// primality guard: a composite modulus makes the Fermat inverse — and every
// modular "proof" — silently unsound, so reject it up front (edge accounted
// for, not assumed away).
if let Some(ref ps) = primes {
for &p in ps {
assert!(
modp::is_prime(p),
"--mod modulus {p} is not prime; modular soundness requires a prime field"
);
}
}
match cmd {
"probe" => {
let n = m - 1;
let pt = point.unwrap_or_else(|| {
(0..n).map(|_| Q::new(BigInt::one(), BigInt::from(m as i64))).collect()
});
let r = probe(m, &pt);
println!("{}", serde_json::to_string_pretty(&r).unwrap());
match &primes {
Some(ps) => {
let (_verdict, json) = modp::probe_survey(m, &pt, ps);
println!("{json}");
}
None => {
let r = probe(m, &pt);
println!("{}", serde_json::to_string_pretty(&r).unwrap());
}
}
}
"survey" => {
fs::create_dir_all(&ckpt).unwrap();
@ -727,22 +771,56 @@ fn main() {
let stride = pts.len() / max_points;
pts = pts.into_iter().step_by(stride.max(1)).take(max_points).collect();
}
eprintln!("survey: {} lattice points, checkpoint {}", pts.len(), ckpt.display());
let tag = match &primes {
Some(ps) => format!("modular (primes={ps:?})"),
None => "exact ".into(),
};
eprintln!(
"survey [{tag}]: {} lattice points, checkpoint {}",
pts.len(),
ckpt.display()
);
let done: Vec<(usize, String)> = pts
.par_iter()
.enumerate()
.map(|(idx, pt)| {
let mut name = format!("m{m}_g{grid}_");
for x in pt {
let _ = write!(name, "{}_", x.numer());
// encode numer AND denom: reduced numerators alone
// collide (1/16,1/8,1/4,1/2 all → "1"), silently folding
// distinct lattice points onto one checkpoint file
let _ = write!(name, "{}-{}_", x.numer(), x.denom());
}
let file = ckpt.join(format!("{name}.json"));
if file.exists() {
return (idx, "skip".to_string());
}
let r = probe(m, pt);
let v = r.verdict.clone();
fs::write(&file, serde_json::to_string_pretty(&r).unwrap()).unwrap();
// catch_unwind backstop: an UNFORESEEN panic on one point
// must never abort the whole survey (which would discard all
// in-flight rayon work). Assume it happens; contain it.
let compute = std::panic::AssertUnwindSafe(|| match &primes {
Some(ps) => modp::probe_survey(m, pt, ps),
None => {
let r = probe(m, pt);
(r.verdict.clone(), serde_json::to_string_pretty(&r).unwrap())
}
});
let (v, json) = match std::panic::catch_unwind(compute) {
Ok(x) => x,
Err(_) => {
let pt_s: Vec<String> = pt.iter().map(|x| x.to_string()).collect();
let e = serde_json::json!({
"schema": "semisym_receipt_edge_v1",
"m": m,
"point": pt_s,
"verdict": "PANIC_CAUGHT",
"reason": "unforeseen_panic",
"note": "contained by survey backstop; needs manual review",
});
("PANIC_CAUGHT".to_string(), serde_json::to_string_pretty(&e).unwrap())
}
};
fs::write(&file, json).unwrap();
(idx, v)
})
.collect();
@ -752,6 +830,84 @@ fn main() {
}
println!("{counts:?}");
}
// Cross-check the modular path against the exact- receipt oracle:
// for every exact receipt in --checkpoint, rerun modp at that point and
// assert method agreement (different arithmetic, so this is a genuine
// independent check, not the same-algorithm trap that hid the old
// signature bug). Prints any mismatch; exits nonzero if any found.
"validate-modp" => {
let p = primes
.as_ref()
.and_then(|v| v.first().copied())
.unwrap_or(modp::DEFAULT_PRIMES[0]);
let entries: Vec<PathBuf> = fs::read_dir(&ckpt)
.unwrap_or_else(|_| panic!("no checkpoint dir {}", ckpt.display()))
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "json"))
.collect();
let mut checked = 0usize;
let mut mismatches = 0usize;
let mut poles = 0usize;
for path in &entries {
let txt = fs::read_to_string(path).unwrap();
let v: serde_json::Value = serde_json::from_str(&txt).unwrap();
// only compare against EXACT receipts
if v.get("schema").and_then(|s| s.as_str()) != Some("semisym_receipt_v1") {
continue;
}
let mm = v["m"].as_u64().unwrap() as usize;
let pt: Vec<Q> = v["point"].as_array().unwrap().iter()
.map(|s| parse_q(s.as_str().unwrap())).collect();
let exact_verdict = v["verdict"].as_str().unwrap();
let exact_nabla = v["nabla_r_nonzero"].as_u64().unwrap() as usize;
let exact_sig = (
v["gprime_signature"][0].as_u64().unwrap() as usize,
v["gprime_signature"][1].as_u64().unwrap() as usize,
);
let r = match modp::probe_modp(mm, &pt, p) {
Ok(r) => r,
Err(reason) => {
// exact receipt exists ⇒ point is interior/non-degenerate,
// so a geometric edge here is a genuine disagreement
mismatches += 1;
eprintln!("MISMATCH {}: modp edge '{reason}' but exact receipt present", path.display());
continue;
}
};
checked += 1;
if r.modular_pole {
// expected edge for a single prime, not a correctness failure
poles += 1;
continue;
}
let mut problems = Vec::new();
if r.gprime_signature != exact_sig {
problems.push(format!(
"signature exact {exact_sig:?} vs modp {:?}",
r.gprime_signature
));
}
// modp certified-nonzero must be a subset of exact nonzero
if r.nabla_r_certified_nonzero > exact_nabla {
problems.push(format!(
"∇R certified {} > exact nonzero {}",
r.nabla_r_certified_nonzero, exact_nabla
));
}
// if exact says PROPER, modp (sound) must not disagree
if exact_verdict == "PROPER" && r.sound && r.verdict != "PROPER" {
problems.push(format!("exact PROPER vs modp {}", r.verdict));
}
if !problems.is_empty() {
mismatches += 1;
eprintln!("MISMATCH {}: {}", path.display(), problems.join("; "));
}
}
println!("validate-modp: checked {checked} exact receipts, {mismatches} mismatches, {poles} single-prime poles (p={p})");
if mismatches > 0 {
std::process::exit(1);
}
}
other => panic!("unknown command {other}"),
}
}

832
rust/semisym/src/modp.rs Normal file
View file

@ -0,0 +1,832 @@
//! modp — modular fast path for the §0 covariant semi-symmetry discriminator.
//!
//! Same order-3 jet AD pipeline as the exact `main.rs`, but the scalar is a
//! finite field 𝔽_p (u64 mod a large prime) instead of BigRational. This kills
//! the denominator blowup that makes off-centre lattice points take 30+ minutes
//! in exact : 𝔽_p elements are single machine words, arithmetic is O(1), and
//! memory is flat. Typical speedup is 1001000× per point.
//!
//! SOUNDNESS (the OTOM gate is preserved for the verdicts we actually get):
//! * A curvature/∇R component ≢ 0 mod p is a *proof* it is ≠ 0 over
//! (p ∤ numerator). So "∇R ≠ 0" (not locally symmetric) is a hard proof.
//! * PROPER (not pseudosymmetric) is certified WITHOUT division: pick a
//! reference pair (rr₀,qq₀) with qq₀ ≠ 0; if any pair has
//! rr·qq₀ rr₀·qq ≢ 0 mod p, then R·R ≠ L·Q(g,R) for the only possible
//! L = rr₀/qq₀ — a hard proof over . If all Q-side entries vanish but some
//! R·R entry is ≢ 0, that is also PROPER (no finite L works).
//! * The ONLY verdicts needing an exact recheck are the all-zero ones
//! (candidate locally-symmetric / semisymmetric / pseudosymmetric): there a
//! modular zero could be a false zero (p | numerator). We have never hit one
//! in the exact survey, and they escalate to the path automatically.
//!
//! Signature and g'(β,β) stay in exact (they need order/sign, meaningless in
//! 𝔽_p) — but they are an n×n value-only computation, not the bottleneck.
//!
//! A modular pivot may vanish (p | a nonzero-over- pivot) → probe returns a
//! Pole; the caller retries with another prime or falls back to exact.
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::Zero;
use crate::{drift_beta, i2, i3_table, signature_opt, Q};
// ── 𝔽_p, p = 2³¹ 1 (Mersenne prime; products fit in u64) ────────────
pub const DEFAULT_PRIME: u64 = 2_147_483_647; // 2^31 - 1
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Fp {
v: u64,
p: u64,
}
impl Fp {
#[inline]
fn new(v: u64, p: u64) -> Fp {
Fp { v: v % p, p }
}
#[inline]
fn zero(p: u64) -> Fp {
Fp { v: 0, p }
}
#[inline]
fn one(p: u64) -> Fp {
Fp { v: 1, p }
}
#[inline]
fn is_zero(self) -> bool {
self.v == 0
}
#[inline]
fn add(self, o: Fp) -> Fp {
let s = self.v + o.v;
Fp { v: if s >= self.p { s - self.p } else { s }, p: self.p }
}
#[inline]
fn sub(self, o: Fp) -> Fp {
Fp {
v: if self.v >= o.v { self.v - o.v } else { self.v + self.p - o.v },
p: self.p,
}
}
#[inline]
fn mul(self, o: Fp) -> Fp {
// p < 2^31 ⇒ product < 2^62 < 2^64, no overflow
Fp { v: (self.v * o.v) % self.p, p: self.p }
}
#[inline]
fn neg(self) -> Fp {
Fp { v: if self.v == 0 { 0 } else { self.p - self.v }, p: self.p }
}
fn pow(self, mut e: u64) -> Fp {
let mut r = Fp::one(self.p);
let mut b = self;
while e > 0 {
if e & 1 == 1 {
r = r.mul(b);
}
b = b.mul(b);
e >>= 1;
}
r
}
/// Fermat inverse; returns None at a modular pole (self ≡ 0).
fn inv(self) -> Option<Fp> {
if self.is_zero() {
None
} else {
Some(self.pow(self.p - 2))
}
}
fn from_i64(x: i64, p: u64) -> Fp {
let m = x.rem_euclid(p as i64);
Fp { v: m as u64, p }
}
/// Reduce a BigRational mod p; None if p | denominator.
fn from_q(qr: &BigRational, p: u64) -> Option<Fp> {
let n = bigint_mod(qr.numer(), p);
let d = bigint_mod(qr.denom(), p);
Some(Fp::new(n, p).mul(Fp::new(d, p).inv()?))
}
}
fn bigint_mod(b: &BigInt, p: u64) -> u64 {
let r = (b % BigInt::from(p)).to_string();
// r is a decimal in (-p, p); parse with sign
let x: i64 = r.parse().unwrap_or(0);
x.rem_euclid(p as i64) as u64
}
// ── order-3 jets over 𝔽_p ─────────────────────────────────────────────
#[derive(Clone)]
struct JetF {
v: Fp,
d1: Vec<Fp>,
d2: Vec<Fp>,
d3: Vec<Fp>,
}
struct CtxF {
n: usize,
n2: usize,
n3: usize,
p: u64,
t3: Vec<[usize; 3]>,
t3idx: Vec<Vec<Vec<usize>>>,
}
impl CtxF {
fn new(n: usize, p: u64) -> Self {
let (t3, t3idx) = i3_table(n);
CtxF { n, n2: n * (n + 1) / 2, n3: t3.len(), p, t3, t3idx }
}
fn zero(&self) -> JetF {
JetF {
v: Fp::zero(self.p),
d1: vec![Fp::zero(self.p); self.n],
d2: vec![Fp::zero(self.p); self.n2],
d3: vec![Fp::zero(self.p); self.n3],
}
}
fn constant(&self, c: Fp) -> JetF {
let mut j = self.zero();
j.v = c;
j
}
fn coord(&self, i: usize, val: Fp) -> JetF {
let mut j = self.zero();
j.v = val;
j.d1[i] = Fp::one(self.p);
j
}
fn add(&self, a: &JetF, b: &JetF) -> JetF {
let mut c = a.clone();
c.v = c.v.add(b.v);
for i in 0..self.n {
c.d1[i] = c.d1[i].add(b.d1[i]);
}
for i in 0..self.n2 {
c.d2[i] = c.d2[i].add(b.d2[i]);
}
for i in 0..self.n3 {
c.d3[i] = c.d3[i].add(b.d3[i]);
}
c
}
fn sub(&self, a: &JetF, b: &JetF) -> JetF {
let mut c = a.clone();
c.v = c.v.sub(b.v);
for i in 0..self.n {
c.d1[i] = c.d1[i].sub(b.d1[i]);
}
for i in 0..self.n2 {
c.d2[i] = c.d2[i].sub(b.d2[i]);
}
for i in 0..self.n3 {
c.d3[i] = c.d3[i].sub(b.d3[i]);
}
c
}
fn neg(&self, a: &JetF) -> JetF {
self.sub(&self.zero(), a)
}
fn mul(&self, a: &JetF, b: &JetF) -> JetF {
let n = self.n;
let mut c = self.zero();
c.v = a.v.mul(b.v);
for i in 0..n {
c.d1[i] = a.d1[i].mul(b.v).add(a.v.mul(b.d1[i]));
}
for i in 0..n {
for j in i..n {
let ij = i2(n, i, j);
c.d2[ij] = a.d2[ij]
.mul(b.v)
.add(a.d1[i].mul(b.d1[j]))
.add(a.d1[j].mul(b.d1[i]))
.add(a.v.mul(b.d2[ij]));
}
}
for (id, t) in self.t3.iter().enumerate() {
let [i, j, k] = *t;
c.d3[id] = a.d3[id]
.mul(b.v)
.add(a.d2[i2(n, i, j)].mul(b.d1[k]))
.add(a.d2[i2(n, i, k)].mul(b.d1[j]))
.add(a.d2[i2(n, j, k)].mul(b.d1[i]))
.add(a.d1[i].mul(b.d2[i2(n, j, k)]))
.add(a.d1[j].mul(b.d2[i2(n, i, k)]))
.add(a.d1[k].mul(b.d2[i2(n, i, j)]))
.add(a.v.mul(b.d3[id]));
}
c
}
/// reciprocal 1/b; None at a modular pole (b.v ≡ 0).
fn inv(&self, b: &JetF) -> Option<JetF> {
let n = self.n;
let bv_inv = b.v.inv()?;
let mut r = self.zero();
r.v = bv_inv;
for i in 0..n {
r.d1[i] = b.d1[i].mul(r.v).neg().mul(bv_inv);
}
for i in 0..n {
for j in i..n {
let ij = i2(n, i, j);
let s = b.d2[ij]
.mul(r.v)
.add(b.d1[i].mul(r.d1[j]))
.add(b.d1[j].mul(r.d1[i]));
r.d2[ij] = s.neg().mul(bv_inv);
}
}
for (id, t) in self.t3.iter().enumerate() {
let [i, j, k] = *t;
let s = b.d3[id]
.mul(r.v)
.add(b.d2[i2(n, i, j)].mul(r.d1[k]))
.add(b.d2[i2(n, i, k)].mul(r.d1[j]))
.add(b.d2[i2(n, j, k)].mul(r.d1[i]))
.add(b.d1[i].mul(r.d2[i2(n, j, k)]))
.add(b.d1[j].mul(r.d2[i2(n, i, k)]))
.add(b.d1[k].mul(r.d2[i2(n, i, j)]));
r.d3[id] = s.neg().mul(bv_inv);
}
Some(r)
}
fn div(&self, a: &JetF, b: &JetF) -> Option<JetF> {
Some(self.mul(a, &self.inv(b)?))
}
fn shift(&self, a: &JetF, dir: usize) -> JetF {
let n = self.n;
let mut s = self.zero();
s.v = a.d1[dir];
for i in 0..n {
s.d1[i] = a.d2[i2(n, dir, i)];
}
for i in 0..n {
for j in i..n {
s.d2[i2(n, i, j)] = a.d3[self.t3idx[dir][i][j]];
}
}
s
}
}
// ── modular receipt ───────────────────────────────────────────────────
#[derive(serde::Serialize)]
pub struct ReceiptModp {
pub schema: &'static str,
pub m: usize,
pub prime: u64,
pub geometrization: String,
pub point: Vec<String>,
pub baseline_constant_curvature_quarter: bool,
pub gprime_signature: (usize, usize),
pub gprime_beta_norm: String,
/// components of ∇R that are ≢ 0 mod p — each a PROOF of ≠ 0 over
pub nabla_r_certified_nonzero: usize,
pub nabla_r_total: usize,
pub rr_nonzero_modp: usize,
/// index tuple + nonzero residue certifying PROPER, if found
pub proper_certificate: Option<(Vec<usize>, String)>,
pub verdict: String,
/// true ⇒ the verdict is a hard proof; false ⇒ needs exact recheck
pub sound: bool,
pub modular_pole: bool,
}
/// Exact value-only metric block at the point → g', signature input & β-norm.
/// Returns a named edge instead of a bare None so the caller can record WHY a
/// point has no valid drift-flip geometry (these edges are assumed to occur on
/// every large run, not treated as unreachable):
/// "outside_simplex" — Σp_i ≥ 1 (p_m ≤ 0), point not interior
/// "null_drift" — g(β,β) = 0, the reflection g 2β♭⊗β♭/g(β,β) is
/// undefined (drift direction is g-null); itself a
/// meaningful scar-map feature, not a failure.
fn exact_gprime(m: usize, point: &[Q]) -> Result<(Vec<Vec<Q>>, Q), &'static str> {
use num_traits::One;
let n = m - 1;
if point.len() < n {
return Err("outside_simplex");
}
let mut pm = Q::one();
for pv in point.iter().take(n) {
if pv <= &Q::zero() {
return Err("outside_simplex");
}
pm -= pv;
}
if pm <= Q::zero() {
return Err("outside_simplex");
}
let inv_pm = Q::one() / &pm;
let mut g = vec![vec![Q::zero(); n]; n];
for i in 0..n {
for j in 0..n {
g[i][j] = if i == j {
Q::one() / &point[i] + &inv_pm
} else {
inv_pm.clone()
};
}
}
let beta_full = drift_beta(m);
let beta: Vec<Q> = beta_full[..n].to_vec();
let mut beta_flat = vec![Q::zero(); n];
for i in 0..n {
for j in 0..n {
beta_flat[i] += &g[i][j] * &beta[j];
}
}
let mut bnorm = Q::zero();
for i in 0..n {
bnorm += &beta[i] * &beta_flat[i];
}
if bnorm.is_zero() {
return Err("null_drift"); // g-null drift: reflection undefined (edge)
}
let mut gp = vec![vec![Q::zero(); n]; n];
for i in 0..n {
for j in 0..n {
gp[i][j] = &g[i][j] - &(Q::new(BigInt::from(2), BigInt::from(1)) * &beta_flat[i] * &beta_flat[j] / &bnorm);
}
}
let mut bb = Q::zero();
for i in 0..n {
for j in 0..n {
bb += &gp[i][j] * &beta[i] * &beta[j];
}
}
Ok((gp, bb))
}
fn curvature_f(
cx: &CtxF,
g: &[Vec<JetF>],
n: usize,
) -> Option<(Vec<Vec<Vec<Vec<JetF>>>>, Vec<Vec<Vec<Vec<JetF>>>>, Vec<Vec<Vec<JetF>>>)> {
let ginv = mat_inv_f(cx, g, n)?;
let half = cx.constant(Fp::new(1, cx.p).mul(Fp::new(2, cx.p).inv().unwrap()));
let mut gamma = vec![vec![vec![cx.zero(); n]; n]; n];
for l in 0..n {
for i in 0..n {
for j in i..n {
let mut s = cx.zero();
for k in 0..n {
let t = cx.sub(
&cx.add(&cx.shift(&g[k][j], i), &cx.shift(&g[k][i], j)),
&cx.shift(&g[i][j], k),
);
s = cx.add(&s, &cx.mul(&ginv[l][k], &t));
}
let v = cx.mul(&half, &s);
gamma[l][i][j] = v.clone();
gamma[l][j][i] = v;
}
}
}
let mut rup = vec![vec![vec![vec![cx.zero(); n]; n]; n]; n];
for rho in 0..n {
for sg in 0..n {
for mu in 0..n {
for nu in (mu + 1)..n {
let mut t = cx.sub(
&cx.shift(&gamma[rho][nu][sg], mu),
&cx.shift(&gamma[rho][mu][sg], nu),
);
for lam in 0..n {
t = cx.add(&t, &cx.mul(&gamma[rho][mu][lam], &gamma[lam][nu][sg]));
t = cx.sub(&t, &cx.mul(&gamma[rho][nu][lam], &gamma[lam][mu][sg]));
}
rup[rho][sg][mu][nu] = t.clone();
rup[rho][sg][nu][mu] = cx.neg(&t);
}
}
}
}
let mut rdn = vec![vec![vec![vec![cx.zero(); n]; n]; n]; n];
for rho in 0..n {
for sg in 0..n {
for mu in 0..n {
for nu in (mu + 1)..n {
let mut s = cx.zero();
for lam in 0..n {
s = cx.add(&s, &cx.mul(&g[rho][lam], &rup[lam][sg][mu][nu]));
}
rdn[rho][sg][mu][nu] = s.clone();
rdn[rho][sg][nu][mu] = cx.neg(&s);
}
}
}
}
Some((rup, rdn, gamma))
}
fn mat_inv_f(cx: &CtxF, a: &[Vec<JetF>], n: usize) -> Option<Vec<Vec<JetF>>> {
let mut m: Vec<Vec<JetF>> = a.to_vec();
let mut inv: Vec<Vec<JetF>> = (0..n)
.map(|i| {
(0..n)
.map(|j| if i == j { cx.constant(Fp::one(cx.p)) } else { cx.zero() })
.collect()
})
.collect();
for col in 0..n {
let piv = (col..n).find(|&r| !m[r][col].v.is_zero())?; // None ⇒ modular pole
m.swap(col, piv);
inv.swap(col, piv);
let pinv = cx.inv(&m[col][col])?;
for j in 0..n {
m[col][j] = cx.mul(&m[col][j], &pinv);
inv[col][j] = cx.mul(&inv[col][j], &pinv);
}
for r in 0..n {
let entry_zero = m[r][col].v.is_zero()
&& m[r][col].d1.iter().all(|x| x.is_zero())
&& m[r][col].d2.iter().all(|x| x.is_zero())
&& m[r][col].d3.iter().all(|x| x.is_zero());
if r != col && !entry_zero {
let f = m[r][col].clone();
for j in 0..n {
let t = cx.mul(&f, &m[col][j]);
m[r][j] = cx.sub(&m[r][j], &t);
let t = cx.mul(&f, &inv[col][j]);
inv[r][j] = cx.sub(&inv[r][j], &t);
}
}
}
}
Some(inv)
}
fn check_constant_curvature_f(cx: &CtxF, g: &[Vec<JetF>], n: usize) -> bool {
let (_, rdn, _) = match curvature_f(cx, g, n) {
Some(x) => x,
None => return false,
};
let quarter = Fp::new(1, cx.p).mul(Fp::new(4, cx.p).inv().unwrap());
for i in 0..n {
for j in 0..n {
for k in 0..n {
for l in 0..n {
let expect = quarter.mul(g[i][k].v.mul(g[j][l].v).sub(g[i][l].v.mul(g[j][k].v)));
if rdn[i][j][k][l].v != expect {
return false;
}
}
}
}
}
true
}
/// Single-prime modular probe.
/// Err(reason) — a prime-independent geometric edge (outside_simplex
/// / null_drift / degenerate_signature_block); the
/// caller records it once, does not retry other primes.
/// Ok(r), r.modular_pole — this prime divides a nonzero-over- quantity; the
/// caller retries another prime.
/// Ok(r), r.sound — PROPER is a hard proof (see module header).
/// Ok(r), !r.sound — a candidate all-zero verdict; caller escalates .
pub fn probe_modp(m: usize, point: &[Q], p: u64) -> Result<ReceiptModp, &'static str> {
let n = m - 1;
// exact side: signature + β-norm (needs sign/order). Named edges bubble up.
let (gm_q, bb) = exact_gprime(m, point)?;
let sig = signature_opt(&gm_q).ok_or("degenerate_signature_block")?;
let cx = CtxF::new(n, p);
let pole_receipt = |base_ok: bool| ReceiptModp {
schema: "semisym_receipt_modp_v1",
m,
prime: p,
geometrization: "drift_flip".into(),
point: point.iter().map(|x| x.to_string()).collect(),
baseline_constant_curvature_quarter: base_ok,
gprime_signature: sig,
gprime_beta_norm: bb.to_string(),
nabla_r_certified_nonzero: 0,
nabla_r_total: n.pow(5),
rr_nonzero_modp: 0,
proper_certificate: None,
verdict: "MODULAR_POLE".into(),
sound: false,
modular_pole: true,
};
// build 𝔽_p coord jets (p | a coord denominator ⇒ modular pole, retry prime)
let ps: Vec<JetF> = match (0..n)
.map(|i| Fp::from_q(&point[i], p).map(|c| cx.coord(i, c)))
.collect::<Option<Vec<_>>>()
{
Some(v) => v,
None => return Ok(pole_receipt(false)),
};
let mut pm = cx.constant(Fp::one(p));
for pj in &ps {
pm = cx.sub(&pm, pj);
}
let inv_pm = match cx.inv(&pm) {
Some(x) => x,
None => return Ok(pole_receipt(false)),
};
let mut g = vec![vec![cx.zero(); n]; n];
for i in 0..n {
let inv_pi = match cx.inv(&ps[i]) {
Some(x) => x,
None => return Ok(pole_receipt(false)),
};
for j in 0..n {
g[i][j] = if i == j { cx.add(&inv_pi, &inv_pm) } else { inv_pm.clone() };
}
}
let base_ok = check_constant_curvature_f(&cx, &g, n);
// drift-flip g'
let beta_full = drift_beta(m);
let bjets: Vec<JetF> = match (0..n)
.map(|i| Fp::from_q(&beta_full[i], p).map(|b| cx.constant(b)))
.collect::<Option<Vec<_>>>()
{
Some(v) => v,
None => return Ok(pole_receipt(base_ok)),
};
let mut beta_flat = vec![cx.zero(); n];
for i in 0..n {
for j in 0..n {
beta_flat[i] = cx.add(&beta_flat[i], &cx.mul(&g[i][j], &bjets[j]));
}
}
let mut bnorm = cx.zero();
for i in 0..n {
bnorm = cx.add(&bnorm, &cx.mul(&bjets[i], &beta_flat[i]));
}
let two = cx.constant(Fp::new(2, p));
let mut gp = vec![vec![cx.zero(); n]; n];
for i in 0..n {
for j in 0..n {
let corr = match cx.div(&cx.mul(&two, &cx.mul(&beta_flat[i], &beta_flat[j])), &bnorm) {
Some(x) => x,
None => return Ok(pole_receipt(base_ok)),
};
gp[i][j] = cx.sub(&g[i][j], &corr);
}
}
let (rup, rdn, gamma) = match curvature_f(&cx, &gp, n) {
Some(x) => x,
None => return Ok(pole_receipt(base_ok)),
};
// ∇R — count certified-nonzero components
let mut nabla_nz = 0usize;
for a in 0..n {
for i in 0..n {
for jj in 0..n {
for k in 0..n {
for l in 0..n {
let mut v = rdn[i][jj][k][l].d1[a];
for mm in 0..n {
v = v.sub(gamma[mm][a][i].v.mul(rdn[mm][jj][k][l].v));
v = v.sub(gamma[mm][a][jj].v.mul(rdn[i][mm][k][l].v));
v = v.sub(gamma[mm][a][k].v.mul(rdn[i][jj][mm][l].v));
v = v.sub(gamma[mm][a][l].v.mul(rdn[i][jj][k][mm].v));
}
if !v.is_zero() {
nabla_nz += 1;
}
}
}
}
}
}
// R·R and Q(g,R): collect pairs, certify PROPER by cross-multiplication
let mut rr_nz = 0usize;
let mut pairs: Vec<(Fp, Fp, Vec<usize>)> = Vec::new();
for a in 0..n {
for b in (a + 1)..n {
for i in 0..n {
for jj in 0..n {
for k in 0..n {
for l in 0..n {
let mut rr = Fp::zero(p);
let mut qq = Fp::zero(p);
for mm in 0..n {
rr = rr.sub(rup[mm][i][a][b].v.mul(rdn[mm][jj][k][l].v));
rr = rr.sub(rup[mm][jj][a][b].v.mul(rdn[i][mm][k][l].v));
rr = rr.sub(rup[mm][k][a][b].v.mul(rdn[i][jj][mm][l].v));
rr = rr.sub(rup[mm][l][a][b].v.mul(rdn[i][jj][k][mm].v));
}
let gv = |r: usize, c: usize| -> Fp { gp[r][c].v };
let wedge = |mm: usize, idx: usize| -> Fp {
let mut w = Fp::zero(p);
if mm == a {
w = w.add(gv(b, idx));
}
if mm == b {
w = w.sub(gv(a, idx));
}
w
};
for mm in 0..n {
qq = qq.sub(wedge(mm, i).mul(rdn[mm][jj][k][l].v));
qq = qq.sub(wedge(mm, jj).mul(rdn[i][mm][k][l].v));
qq = qq.sub(wedge(mm, k).mul(rdn[i][jj][mm][l].v));
qq = qq.sub(wedge(mm, l).mul(rdn[i][jj][k][mm].v));
}
if !rr.is_zero() {
rr_nz += 1;
}
if !(rr.is_zero() && qq.is_zero()) {
pairs.push((rr, qq, vec![a, b, i, jj, k, l]));
}
}
}
}
}
}
}
// PROPER certificate: reference pair with qq ≠ 0, then any pair breaking
// rr·qq₀ rr₀·qq ≡ 0. If all qq ≡ 0 but some rr ≠ 0 ⇒ also PROPER.
let mut proper_cert: Option<(Vec<usize>, String)> = None;
if let Some((rr0, qq0, _)) = pairs.iter().find(|(_, q, _)| !q.is_zero()).cloned() {
for (rr, qq, idx) in &pairs {
let cross = rr.mul(qq0).sub(rr0.mul(*qq));
if !cross.is_zero() {
proper_cert = Some((idx.clone(), cross.v.to_string()));
break;
}
}
} else if let Some((_, _, idx)) = pairs.iter().find(|(r, _, _)| !r.is_zero()) {
// no Q-side entry nonzero but R·R nonzero ⇒ no finite L works
proper_cert = Some((idx.clone(), "rr_nonzero_q_all_zero".into()));
}
let (verdict, sound) = if nabla_nz == 0 {
("CANDIDATE_LOCALLY_SYMMETRIC(needs exact)".to_string(), false)
} else if rr_nz == 0 {
("CANDIDATE_SEMISYMMETRIC(needs exact)".to_string(), false)
} else if proper_cert.is_some() {
("PROPER".to_string(), true) // hard proof: ∇R≠0 (certified) & not pseudosym
} else {
("CANDIDATE_PSEUDOSYMMETRIC(needs exact)".to_string(), false)
};
Ok(ReceiptModp {
schema: "semisym_receipt_modp_v1",
m,
prime: p,
geometrization: "drift_flip".into(),
point: point.iter().map(|x| x.to_string()).collect(),
baseline_constant_curvature_quarter: base_ok,
gprime_signature: sig,
gprime_beta_norm: bb.to_string(),
nabla_r_certified_nonzero: nabla_nz,
nabla_r_total: n.pow(5),
rr_nonzero_modp: rr_nz,
proper_certificate: proper_cert,
verdict,
sound,
modular_pole: false,
})
}
// ── robust orchestration: assume edges happen on every run ────────────
//
// GUARANTEE: probe_survey resolves EVERY point to exactly one of
// (a) a proven-PROPER modular receipt (fast, hard proof over ),
// (b) a sound exact- receipt (auto-escalation), or
// (c) a recorded edge receipt (geometric edge, no crash).
// It never panics on a known edge and never emits an unsound verdict.
/// A geometric / arithmetic edge recorded in-band (never a panic).
#[derive(serde::Serialize)]
pub struct EdgeReceipt {
pub schema: &'static str,
pub m: usize,
pub point: Vec<String>,
pub verdict: String,
pub reason: String,
pub note: String,
}
/// Default multi-prime set: the three largest primes below 2³¹. Multi-prime
/// makes a modular pole (one prime dividing a nonzero-over- quantity) a
/// non-event — another prime almost surely resolves it — and guards a
/// single-prime false-zero from hiding a PROPER certificate.
pub const DEFAULT_PRIMES: [u64; 3] = [2_147_483_647, 2_147_483_629, 2_147_483_587];
fn mod_mul(a: u64, b: u64, m: u64) -> u64 {
((a as u128 * b as u128) % m as u128) as u64
}
fn mod_pow(mut a: u64, mut e: u64, m: u64) -> u64 {
let mut r = 1u64 % m;
while e > 0 {
if e & 1 == 1 {
r = mod_mul(r, a, m);
}
a = mod_mul(a, a, m);
e >>= 1;
}
r
}
/// Deterministic MillerRabin; bases {2,3,5,7} are a proven witness set for all
/// n < 3.2·10⁹ ⊃ our p < 2³¹. Guards against a composite modulus, which would
/// make the Fermat inverse (and every "proof") silently unsound.
pub fn is_prime(n: u64) -> bool {
if n < 2 {
return false;
}
for &sp in &[2u64, 3, 5, 7, 11, 13, 17, 19, 23] {
if n % sp == 0 {
return n == sp;
}
}
let mut d = n - 1;
let mut r = 0u32;
while d % 2 == 0 {
d /= 2;
r += 1;
}
'witness: for &a in &[2u64, 3, 5, 7] {
let mut x = mod_pow(a % n, d, n);
if x == 1 || x == n - 1 {
continue;
}
for _ in 0..r.saturating_sub(1) {
x = mod_mul(x, x, n);
if x == n - 1 {
continue 'witness;
}
}
return false;
}
true
}
fn edge(m: usize, point: &[Q], reason: &str, note: &str) -> (String, String) {
let e = EdgeReceipt {
schema: "semisym_receipt_edge_v1",
m,
point: point.iter().map(|x| x.to_string()).collect(),
verdict: format!("EDGE_{}", reason.to_uppercase()),
reason: reason.into(),
note: note.into(),
};
let j = serde_json::to_string_pretty(&e).unwrap();
(e.verdict, j)
}
/// Robust per-point resolution used by surveys. Returns (verdict_for_tally,
/// pretty_json). See the module GUARANTEE above.
pub fn probe_survey(m: usize, point: &[Q], primes: &[u64]) -> (String, String) {
let mut saw_candidate = false;
let mut n_poles = 0usize;
for &p in primes {
match probe_modp(m, point, p) {
// prime-independent geometric edge — record once, no retry
Err(reason) => {
return edge(m, point, reason, "no valid drift-flip geometry at this point");
}
Ok(r) if r.modular_pole => {
n_poles += 1;
continue; // this prime divides something; try the next
}
Ok(r) if r.sound => {
// PROPER proven over by this prime — fast path, done
let j = serde_json::to_string_pretty(&r).unwrap();
return (r.verdict, j);
}
Ok(_) => {
// candidate all-zero on this prime; another prime may still
// expose a PROPER certificate hidden by a false-zero
saw_candidate = true;
}
}
}
// No prime proved PROPER ⇒ escalate to exact for a sound verdict. Covers
// genuine candidate verdicts, single-prime false-zeros, and all-poled.
// (Reached only for interior, non-null-drift points, so crate::probe's
// interior/non-degenerate preconditions hold; the survey also wraps this
// in catch_unwind as a final backstop.)
let ex = crate::probe(m, point);
let why = if saw_candidate {
"no_modular_PROPER_proof(candidate all-zero)"
} else {
"all_primes_poled"
};
let mut v: serde_json::Value = serde_json::to_value(&ex).unwrap();
v["_escalated_from_modular"] = serde_json::json!({
"reason": why,
"primes_tried": primes,
"poles": n_poles,
});
(ex.verdict.clone(), serde_json::to_string_pretty(&v).unwrap())
}

View file

@ -0,0 +1,246 @@
//! ingest_semisym_scar — map §0 covariant semi-symmetry receipts → ENE rows.
//!
//! SKELETON (dry-run first). Reads the per-point receipt JSONs from
//! `semisym survey` (schemas `semisym_receipt_modp_v1`, `semisym_receipt_v1`,
//! `semisym_receipt_edge_v1`) and maps each lattice point to an
//! `ene.rrc_classifications` row for later data mining of the scar map.
//!
//! NO FLOATS (OTOM doctrine — the whole stack is integer / exact / Q16_16):
//! every quantity is stored EXACTLY. Rationals (β-norm, obstruction) are kept
//! as exact strings ("628/9"); counts/signature are exact integers; the whole
//! bundle lives in a JSONB `metrics` column. Ordering, when needed, is exact
//! integer cross-multiplication (a/b < c/d ⇔ a·d < c·b), never IEEE float.
//! FUTURE revision (per "everything is logarithms", the prime-basis log): add a
//! prime-exponent vector νₚ per rational — exact integer log-coordinates,
//! multiplicatively additive, using the precomputed prime LUT as the basis.
//!
//! SAFETY (repo dry-run-by-default rule):
//! * default — parse + map, PRINT a summary + WRITE SQL to a file.
//! NO database connection.
//! * --apply --dsn <..> — execute the SQL via `psql`. Requires an EXPLICIT
//! dsn; a bare --apply refuses (no baked endpoint).
//! RDS / rds_connect.py are never used; ENE is the
//! neon `research_stack`/`ene` schema.
//!
//! Ids are sha256(equation_id) so re-ingest is idempotent (ON CONFLICT UPDATE).
//! The legacy FLOAT columns (spectral_radius, score) are deliberately NOT
//! populated — they predate the float ban and should be migrated out.
//!
//! Usage:
//! ingest_semisym_scar <ckpt_dir> [--out scar.sql] [--limit N] # dry-run
//! ingest_semisym_scar <ckpt_dir> --apply --dsn "postgresql://…/research_stack"
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use serde::Deserialize;
use serde_json::json;
use sha2::{Digest, Sha256};
/// Union of the three receipt schemas; every field optional so one struct
/// deserializes modp / exact-escalated / edge receipts alike.
#[derive(Deserialize, Default)]
struct Receipt {
schema: Option<String>,
m: Option<u32>,
prime: Option<u64>,
geometrization: Option<String>,
point: Option<Vec<String>>,
gprime_signature: Option<(u32, u32)>, // JSON [pos, neg]
gprime_beta_norm: Option<String>, // exact rational as string
verdict: Option<String>,
obstruction_along_drift: Option<String>, // exact rational (v1 only)
nabla_r_certified_nonzero: Option<u64>,
nabla_r_nonzero: Option<u64>,
nabla_r_total: Option<u64>,
rr_nonzero_modp: Option<u64>,
rr_nonzero: Option<u64>,
baseline_constant_curvature_quarter: Option<bool>,
reason: Option<String>, // edge receipts
sound: Option<bool>,
modular_pole: Option<bool>,
}
/// One `ene.rrc_classifications` row. Typed columns are exact-integer/text only;
/// all numeric detail (exact) is carried in `metrics` (JSONB).
struct RrcRow {
id: String,
equation_id: String,
shape: String, // verdict
pist_label: String, // geometrization / edge reason
weak_axes: Option<u32>, // negative signature count (exact integer)
metrics: String, // exact JSONB bundle (no floats)
}
/// Deterministic UUID-shaped id from a key (sha256 → 8-4-4-4-12 hex).
fn stable_uuid(key: &str) -> String {
let d = Sha256::digest(key.as_bytes());
let h: String = d[..16].iter().map(|b| format!("{b:02x}")).collect();
format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32])
}
fn sql_str(s: &str) -> String {
format!("'{}'", s.replace('\'', "''"))
}
fn sql_u32(x: Option<u32>) -> String {
x.map(|v| v.to_string()).unwrap_or_else(|| "NULL".into())
}
fn map_receipt(r: &Receipt) -> RrcRow {
let point = r.point.clone().unwrap_or_default().join("_");
let equation_id = format!("semisym:m{}:{}", r.m.unwrap_or(0), point);
let id = stable_uuid(&equation_id);
let shape = r.verdict.clone().unwrap_or_else(|| "UNKNOWN".into());
let pist_label = r
.geometrization
.clone()
.or_else(|| r.reason.clone())
.unwrap_or_default();
let weak_axes = r.gprime_signature.map(|(_, neg)| neg);
// exact JSONB bundle — rationals as strings, counts/signature as integers,
// NO floats anywhere. This is the "combine both" richer field.
let signature = r.gprime_signature.map(|(p, n)| json!([p, n]));
let metrics = json!({
"schema": r.schema,
"verdict": r.verdict,
"geometrization": r.geometrization,
"prime": r.prime,
"signature": signature,
"beta_norm": r.gprime_beta_norm, // EXACT rational string
"obstruction_exact": r.obstruction_along_drift, // exact string or null (modp: null)
"nabla_certified": r.nabla_r_certified_nonzero.or(r.nabla_r_nonzero),
"nabla_total": r.nabla_r_total,
"rr_nonzero": r.rr_nonzero_modp.or(r.rr_nonzero),
"baseline_constant_curvature_quarter": r.baseline_constant_curvature_quarter,
"sound": r.sound,
"point": r.point,
"reason": r.reason,
});
RrcRow {
id,
equation_id,
shape,
pist_label,
weak_axes,
metrics: serde_json::to_string(&metrics).unwrap(),
}
}
fn row_sql(row: &RrcRow) -> String {
format!(
"INSERT INTO ene.rrc_classifications \
(id, equation_id, shape, pist_label, weak_axes, metrics) VALUES \
({}, {}, {}, {}, {}, {}::jsonb) \
ON CONFLICT (id) DO UPDATE SET shape=EXCLUDED.shape, pist_label=EXCLUDED.pist_label, \
weak_axes=EXCLUDED.weak_axes, metrics=EXCLUDED.metrics;",
sql_str(&row.id),
sql_str(&row.equation_id),
sql_str(&row.shape),
sql_str(&row.pist_label),
sql_u32(row.weak_axes),
sql_str(&row.metrics),
)
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("usage: ingest_semisym_scar <ckpt_dir> [--out f.sql] [--limit N] [--apply --dsn <conn>]");
std::process::exit(2);
}
let dir = PathBuf::from(&args[1]);
let mut out = PathBuf::from("semisym_scar.sql");
let mut limit: Option<usize> = None;
let mut apply = false;
let mut dsn: Option<String> = None;
let mut i = 2;
while i < args.len() {
match args[i].as_str() {
"--out" => { out = PathBuf::from(&args[i + 1]); i += 2; }
"--limit" => { limit = args[i + 1].parse().ok(); i += 2; }
"--apply" => { apply = true; i += 1; }
"--dsn" => { dsn = Some(args[i + 1].clone()); i += 2; }
other => { eprintln!("unknown arg {other}"); std::process::exit(2); }
}
}
let mut files: Vec<PathBuf> = fs::read_dir(&dir)
.unwrap_or_else(|_| panic!("cannot read checkpoint dir {}", dir.display()))
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "json"))
.collect();
files.sort();
if let Some(n) = limit {
files.truncate(n);
}
let mut rows: Vec<RrcRow> = Vec::with_capacity(files.len());
let mut by_verdict: BTreeMap<String, usize> = BTreeMap::new();
let mut parse_errors = 0usize;
let mut edges = 0usize;
for f in &files {
let txt = match fs::read_to_string(f) { Ok(t) => t, Err(_) => { parse_errors += 1; continue; } };
let r: Receipt = match serde_json::from_str(&txt) { Ok(r) => r, Err(_) => { parse_errors += 1; continue; } };
if r.schema.as_deref() == Some("semisym_receipt_edge_v1") || r.modular_pole == Some(true) {
edges += 1;
}
let row = map_receipt(&r);
*by_verdict.entry(row.shape.clone()).or_insert(0) += 1;
rows.push(row);
}
// generation only — no DB touch. Migration adds the JSONB metrics column.
let mut sql = String::new();
sql.push_str("BEGIN;\n");
sql.push_str("ALTER TABLE ene.rrc_classifications ADD COLUMN IF NOT EXISTS metrics JSONB DEFAULT '{}'::jsonb;\n");
for row in &rows {
sql.push_str(&row_sql(row));
sql.push('\n');
}
let audit_id = stable_uuid(&format!("semisym_scar_ingest:{}", dir.display()));
sql.push_str(&format!(
"INSERT INTO ene.ingest_events (id, session_id, event_type, payload) VALUES \
({}, NULL, 'semisym_scar_ingest', {}) ON CONFLICT (id) DO NOTHING;\n",
sql_str(&audit_id),
sql_str(&format!("{{\"rows\":{},\"source\":\"{}\"}}", rows.len(), dir.display())),
));
sql.push_str("COMMIT;\n");
fs::write(&out, &sql).expect("write sql");
eprintln!("── semisym scar ingest (DRY-RUN, float-free) ──");
eprintln!("checkpoint : {}", dir.display());
eprintln!("receipts : {} ({} parse errors, {} edge/pole)", files.len(), parse_errors, edges);
eprintln!("rows : {} → ene.rrc_classifications (+ metrics JSONB, +1 ingest_events)", rows.len());
eprintln!("verdicts : {by_verdict:?}");
eprintln!("sql written : {}", out.display());
eprintln!("preview (first 2):");
for row in rows.iter().take(2) {
eprintln!(" {} shape={} weak_axes={:?}", row.equation_id, row.shape, row.weak_axes);
eprintln!(" metrics={}", row.metrics);
}
if !apply {
eprintln!("\n[dry-run] no database touched. To apply: --apply --dsn <postgres-conn>");
return;
}
let Some(dsn) = dsn else {
eprintln!("\nERROR: --apply requires --dsn <postgres-conn> (no default endpoint; confirm live ENE first)");
std::process::exit(2);
};
eprintln!("\n[apply] executing {} via psql …", out.display());
let status = Command::new("psql")
.arg(&dsn)
.arg("-v").arg("ON_ERROR_STOP=1")
.arg("-f").arg(&out)
.status()
.expect("failed to spawn psql");
if status.success() {
eprintln!("[apply] OK — {} rows upserted into ene.rrc_classifications", rows.len());
} else {
eprintln!("[apply] psql failed ({status})");
std::process::exit(1);
}
}