SilverSight/rust/src/bin/ingest.rs
allaun b6a4cf9e4d feat(ingest): Landauer + Shannon entropy density check via rgflow
Density now measured by information-theoretic bounds, not just counts:

Shannon entropy H = -Σ p(x) log₂ p(x) over equation symbol distribution
  - Higher H = more informational diversity = denser content
  - RG flow bound: H < ln(2⁸) ≈ 2.08 for 8-strand representation

Landauer cost E = N·kT·ln(2) where N = distinct math tokens
  - Each distinct symbol requires erasing energy
  - kT = 0.025 eV at 290K → ~0.017 eV per bit

Density levels:
  THERMODYNAMIC_SATURATION: both Shannon + Landauer exceed bounds
  SHANNON_SATURATED: entropy > rgflow fixed-point
  LANDAUER_EXPENSIVE: energy cost too high
  COUNT_EXCEEDED: simple count threshold
2026-06-30 20:51:17 -05:00

366 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Equation Ingestion Pipeline — Rust CLI
// Markdown → Parse → Classify → Compute → Emit
// Usage: cargo run --bin ingest -- equations.md
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use regex::Regex;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ParsedEquation {
text: String,
line: usize,
is_block: bool,
classification: String,
#[serde(skip_serializing_if = "Option::is_none")]
spectral_fingerprint: Option<SpectralFingerprint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SpectralFingerprint {
sigma: f64,
tau: f64,
delta: f64,
lambda_min: i32,
lambda_max: i32,
denominator: i32,
regime: String,
gap_numerator: i32,
}
#[derive(Serialize, Deserialize)]
struct DensityReport {
total_equations: usize,
classified: usize,
unknown: usize,
max_allowed: usize,
should_pause: bool,
reason: String,
shannon_entropy: f64,
landauer_bits: f64,
landauer_energy_ev: f64,
rgflow_bound: f64,
density_level: String,
}
#[derive(Serialize, Deserialize)]
struct IngestionReceipt {
schema: String,
source_file: String,
total_parsed: usize,
total_processed: usize,
density: DensityReport,
results: Vec<ParsedEquation>,
}
// ── Chiral weight table (integer rational pairs, zero floats) ─────
const CHIRAL_WEIGHTS: [(i32, i32); 4] = [(1,1), (1,2), (3,2), (3,2)]; // A,S,L,R
const CHIRAL_NAMES: [&str; 4] = ["A", "S", "L", "R"];
fn parse_markdown(text: &str) -> Vec<ParsedEquation> {
let mut equations = Vec::new();
let block_re = Regex::new(r"\$\$(.+?)\$\$").unwrap();
let inline_re = Regex::new(r"\$(.+?)\$").unwrap();
for (i, line) in text.lines().enumerate() {
for cap in block_re.captures_iter(line) {
let eq = cap[1].trim().to_string();
if !eq.is_empty() {
equations.push(ParsedEquation {
text: eq, line: i + 1, is_block: true,
classification: String::new(), spectral_fingerprint: None,
});
}
}
for cap in inline_re.captures_iter(line) {
let eq = cap[1].trim().to_string();
if !eq.is_empty() && !line.contains(&format!("$${}$$", eq)) {
equations.push(ParsedEquation {
text: eq, line: i + 1, is_block: false,
classification: String::new(), spectral_fingerprint: None,
});
}
}
}
equations
}
fn classify_equation(eq: &mut ParsedEquation) {
let text = eq.text.to_lowercase();
let spectral_kw = ["sigma", "tau", "delta", "lambda", "spectral", "eigenvalue",
"gap", "radius", "threshold", "1792", "256", "273", "28",
"cartan", "sidon", "chiral", "rossby", "kelvin", "braid"];
let braid_kw = ["braid", "strand", "cross", "sidon", "eigensolid", "yang-baxter"];
let cartan_kw = ["cartan", "weight", "matrix", "diagonal", "adjacent", "block"];
let mut scores = HashMap::new();
scores.insert("spectral", 0usize);
scores.insert("braid", 0usize);
scores.insert("cartan", 0usize);
for kw in &spectral_kw {
if text.contains(kw) { *scores.get_mut("spectral").unwrap() += 1; }
}
for kw in &braid_kw {
if text.contains(kw) { *scores.get_mut("braid").unwrap() += 1; }
}
for kw in &cartan_kw {
if text.contains(kw) { *scores.get_mut("cartan").unwrap() += 1; }
}
let best = scores.iter().max_by_key(|&(_, v)| v).unwrap();
eq.classification = if *best.1 > 0 { best.0.to_string() } else { "unknown".to_string() };
}
// ── Spectral computation (integer only) ──────────────────────────
fn compute_spectral(chiral: &str) -> SpectralFingerprint {
// chiral is 8 chars of A/S/L/R
let names: Vec<char> = chiral.chars().collect();
let pairs = [(0,1), (2,3), (4,5), (6,7)];
let mut all_lo = Vec::new();
let mut all_hi = Vec::new();
for (a, b) in pairs {
let ai = CHIRAL_NAMES.iter().position(|&c| c == names[a].to_string()).unwrap_or(0);
let bi = CHIRAL_NAMES.iter().position(|&c| c == names[b].to_string()).unwrap_or(0);
let (an, ad) = CHIRAL_WEIGHTS[ai];
let (bn, bd) = CHIRAL_WEIGHTS[bi];
// w = 128 × (num_a*den_b + num_b*den_a) / (den_a * den_b)
let num = 128 * (an * bd + bn * ad);
let den = ad * bd;
let w = num / den;
all_lo.push(273 + w);
all_hi.push(273 - w);
}
let lam_max = *all_lo.iter().max().unwrap();
let lam_min = *all_hi.iter().min().unwrap();
let d = 1792;
let sigma = 273.0 / d as f64;
let w_avg: i32 = all_lo.iter().sum::<i32>() / all_lo.len() as i32 - 273;
let tau = w_avg as f64 / d as f64;
let delta = sigma - tau;
let regime = if lam_min < 0 { "ROSSBY" } else if lam_min == 17 { "CANONICAL" } else { "SCARRED" };
SpectralFingerprint {
sigma, tau, delta, lambda_min: lam_min, lambda_max: lam_max,
denominator: d, regime: regime.to_string(), gap_numerator: lam_min,
}
}
fn check_density(eqs: &[ParsedEquation], max_eqns: usize, max_unknown: usize) -> DensityReport {
let total = eqs.len();
let unknown = eqs.iter().filter(|e| e.classification == "unknown").count();
// ── Shannon Entropy: H = -Σ p(x) log₂ p(x) ──────────────────
// Entropy over the equation text symbol distribution.
// Higher H = more informational diversity = denser content.
let full_text: String = eqs.iter().map(|e| &e.text).fold(String::new(), |a, b| a + b);
let mut freq = [0usize; 256];
for b in full_text.bytes() { freq[b as usize] += 1; }
let total_bytes = full_text.len().max(1) as f64;
let shannon: f64 = freq.iter()
.filter(|&&c| c > 0)
.map(|&c| { let p = c as f64 / total_bytes; -p * p.log2() })
.sum();
// ── Landauer Cost: E = kT·ln(2) × unique_terms ──────────────
// Landauer (1961): erasing N bits costs N·kT·ln(2) energy.
// Each distinct mathematical symbol/operator is a "bit" of
// computational state that must be erased.
// kT ≈ 0.025 eV at room temp → Landauer energy per bit.
let kT_eV = 0.025; // eV at 290K
let ln2 = 0.693147;
let landauer_per_bit = kT_eV * ln2; // ≈ 0.017 eV/bit
// Count distinct mathematical symbols/tokens
let math_tokens = Regex::new(r"[\\a-z]+|\d+|[+\-*/=<>\[\]{}()∫∑∏∂∇√∞π]").unwrap();
let mut terms = std::collections::HashSet::new();
for eq in eqs {
for m in math_tokens.find_iter(&eq.text) {
terms.insert(m.as_str().to_string());
}
}
let landauer_bits = terms.len() as f64;
let landauer_energy = landauer_bits * landauer_per_bit;
// ── RGFlow Entropy Bound ──────────────────────────────────────
// Renormalization group flow: entropy decreases monotonically
// under coarse-graining. The RG fixed-point occurs at H = ln(2⁸) ≈ 5.545
// for the 8-strand system (8 bits of address space).
// Equation sets exceeding this entropy need RG coarse-graining
// (information compression) before they can be reliably classified.
let rgflow_bound = (8.0_f64).ln(); // ln(2⁸) ≈ 5.545
// ── Decision ──────────────────────────────────────────────────
let exceeds_shannon = shannon > rgflow_bound;
let exceeds_landauer = landauer_bits > (max_eqns as f64 * 3.0);
let exceeds_count = total > max_eqns || unknown > max_unknown;
let should_pause = exceeds_shannon || exceeds_landauer || exceeds_count;
let density_level = if exceeds_shannon && exceeds_landauer {
"THERMODYNAMIC_SATURATION"
} else if exceeds_shannon {
"SHANNON_SATURATED"
} else if exceeds_landauer {
"LANDAUER_EXPENSIVE"
} else if exceeds_count {
"COUNT_EXCEEDED"
} else {
"PROCESSABLE"
};
DensityReport {
total_equations: total,
classified: total - unknown,
unknown,
max_allowed: max_eqns,
should_pause,
reason: if exceeds_shannon {
format!("Shannon entropy {:.2} > rgflow bound {:.2} — information-saturated", shannon, rgflow_bound)
} else if exceeds_landauer {
format!("Landauer cost {:.0} bits > threshold — too expensive to auto-process", landauer_bits)
} else if exceeds_count {
if total > max_eqns { format!("too many equations ({} > {})", total, max_eqns) }
else { format!("too many unknowns ({} > {})", unknown, max_unknown) }
} else {
"proceed".to_string()
},
shannon_entropy: (shannon * 100.0).round() / 100.0,
landauer_bits,
landauer_energy_ev: (landauer_energy * 1000.0).round() / 1000.0,
rgflow_bound: (rgflow_bound * 100.0).round() / 100.0,
density_level: density_level.to_string(),
}
}
fn write_decision_doc(input: &PathBuf, eqs: &[ParsedEquation], density: &DensityReport) -> PathBuf {
let out = input.with_extension("decision.md");
let mut lines = vec![
format!("# Equation Pipeline — Decision Required\n"),
format!("**File:** `{}`", input.display()),
format!("**Date:** auto-generated\n"),
format!("## Density Report\n"),
format!("| Metric | Value |"),
format!("|--------|-------|"),
format!("| Total equations | {} |", density.total_equations),
format!("| Classified | {} |", density.classified),
format!("| Unknown | {} |", density.unknown),
format!("| Max auto-process | {} |\n", density.max_allowed),
format!("**Reason for pause:** {}\n", density.reason),
format!("## Detected Equations\n"),
format!("| Line | Type | Classification | Equation |"),
format!("|------|------|---------------|----------|"),
];
for eq in eqs {
let preview: String = eq.text.chars().take(60).collect();
let more = if eq.text.len() > 60 { "..." } else { "" };
lines.push(format!("| {} | {} | {} | `{}{}` |",
eq.line,
if eq.is_block { "block" } else { "inline" },
eq.classification,
preview, more));
}
lines.push(format!("\nRun with `--force` to process anyway."));
fs::write(&out, lines.join("\n")).unwrap();
out
}
// ── Main ──────────────────────────────────────────────────────────
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <equations.md> [--force] [--max-eqns N]", args[0]);
std::process::exit(1);
}
let input = PathBuf::from(&args[1]);
let force = args.contains(&"--force".to_string());
let classified_only = args.contains(&"--classified-only".to_string());
let max_eqns = args.iter().position(|a| a == "--max-eqns")
.and_then(|i| args.get(i+1))
.and_then(|s| s.parse().ok())
.unwrap_or(20);
let text = fs::read_to_string(&input).unwrap_or_else(|e| {
eprintln!("Error reading {}: {}", input.display(), e);
std::process::exit(1);
});
let mut equations = parse_markdown(&text);
println!("Parsed {} equations from {}", equations.len(), input.display());
for eq in &mut equations { classify_equation(eq); }
let spectral = equations.iter().filter(|e| e.classification == "spectral").count();
let braid = equations.iter().filter(|e| e.classification == "braid").count();
let cartan = equations.iter().filter(|e| e.classification == "cartan").count();
let unknown = equations.iter().filter(|e| e.classification == "unknown").count();
println!(" Spectral: {}, Braid: {}, Cartan: {}, Unknown: {}", spectral, braid, cartan, unknown);
let density = check_density(&equations, max_eqns, 5);
if density.should_pause && !force {
let doc = write_decision_doc(&input, &equations, &density);
println!("\n⚠️ Too dense — wrote decision document:");
println!(" {}", doc.display());
println!(" Review and re-run with --force to proceed.");
return;
}
if classified_only {
equations.retain(|e| e.classification != "unknown");
println!("Processing {} classified equations", equations.len());
}
// Compute spectral fingerprints
for eq in &mut equations {
if eq.classification == "unknown" { continue; }
let chiral = if eq.text.to_lowercase().contains("rossby") ||
eq.text.to_lowercase().contains("chiral") {
"LLLLLLLL"
} else if eq.text.to_lowercase().contains("scarred") {
"SSSSSSSS"
} else {
"AAAAAAAA"
};
eq.spectral_fingerprint = Some(compute_spectral(chiral));
}
let processed = equations.iter().filter(|e| e.spectral_fingerprint.is_some()).count();
let total = equations.len();
let receipt = IngestionReceipt {
schema: "equation_ingestion_v1".to_string(),
source_file: input.display().to_string(),
total_parsed: total,
total_processed: processed,
density,
results: equations,
};
let out = input.with_extension("receipt.json");
fs::write(&out, serde_json::to_string_pretty(&receipt).unwrap()).unwrap();
println!("\n✅ Pipeline complete");
println!(" Processed: {}/{} equations", processed, total);
println!(" Receipt: {}", out.display());
let computed: Vec<_> = receipt.results.iter().filter(|e| e.spectral_fingerprint.is_some()).collect();
if !computed.is_empty() {
for eq in computed {
let fp = eq.spectral_fingerprint.as_ref().unwrap();
println!(" [{}] λ=[{}, {}] σ={:.6} τ={:.6} ∆={:.6} {}",
eq.classification, fp.lambda_min, fp.lambda_max,
fp.sigma, fp.tau, fp.delta, fp.regime);
}
}
}