#!/usr/bin/env python3 """ Equation Ingestion Pipeline — Markdown → Parse → Classify → Compute → Emit Accepts a .md file containing mathematical equations. Runs each through the spectral pipeline. If the file is too dense, pauses and writes an intermediate decision document. Usage: python3 scripts/ingest.py equations.md python3 scripts/ingest.py equations.md --max-eqns 20 """ import json, re, sys, argparse from pathlib import Path from dataclasses import dataclass, field from typing import List, Dict, Optional, Tuple # ── Stage 0: Equation Parser ──────────────────────────────────────── @dataclass class ParsedEquation: text: str line: int is_block: bool classification: str = "unknown" # braid, spectral, cartan, unknown metadata: Dict = field(default_factory=dict) def parse_markdown(text: str) -> List[ParsedEquation]: """Extract all LaTeX equations from markdown. Returns list with line numbers and block/inline type.""" equations = [] lines = text.split('\n') # Block equations: $$...$$ for i, line in enumerate(lines): blocks = re.findall(r'\$\$(.+?)\$\$', line) for eq in blocks: equations.append(ParsedEquation(text=eq.strip(), line=i+1, is_block=True)) # Inline equations: $...$ for i, line in enumerate(lines): inlines = re.findall(r'\$(.+?)\$', line) for eq in inlines: # Skip if it was already captured as block if f"$${eq}$$" in line: continue equations.append(ParsedEquation(text=eq.strip(), line=i+1, is_block=False)) return equations # ── Stage 1: Equation Classifier ──────────────────────────────────── SPECTRAL_KEYWORDS = [ r'\sigma', r'\tau', r'\Delta', r'\lambda', r'\gamma', r'\chi', r'spectral', r'eigenvalue', r'gap', r'radius', r'threshold', r'\d\{1792\}', r'\d\{256\}', r'\d\{273\}', r'\d\{17\}', r'\d\{28\}', r'Cartan', r'Sidon', r'chiral', r'rossby', r'kelvin', r'braid', ] BRAID_KEYWORDS = [ r'braid', r'strand', r'cross', r'Sidon', r'eigensolid', r'bracket', r'Yang-Baxter', r'crossStep', r'residue', r'phase', ] CARTAN_KEYWORDS = [ r'Cartan', r'weight', r'matrix', r'diagonal', r'adjacent', r'block', r'2\\times2', r'8\\times8', r'Gram', ] def classify_equation(eq: ParsedEquation) -> ParsedEquation: """Classify equation as braid/spectral/cartan based on keyword detection.""" text = eq.text.lower() scores = {"spectral": 0, "braid": 0, "cartan": 0} for kw in SPECTRAL_KEYWORDS: if re.search(kw, text, re.IGNORECASE): scores["spectral"] += 1 for kw in BRAID_KEYWORDS: if re.search(kw, text, re.IGNORECASE): scores["braid"] += 1 for kw in CARTAN_KEYWORDS: if re.search(kw, text, re.IGNORECASE): scores["cartan"] += 1 best = max(scores, key=scores.get) if scores[best] > 0: eq.classification = best else: eq.classification = "unknown" eq.metadata["keyword_scores"] = scores return eq # ── Stage 2: Density Check ────────────────────────────────────────── def check_density(equations: List[ParsedEquation], max_eqns: int = 20, max_unknown: int = 5, max_lines: int = 500) -> Dict: """Check if the input is too dense/complex to auto-process. Returns decision dict with whether to proceed or pause.""" total = len(equations) unknown = sum(1 for e in equations if e.classification == "unknown") should_pause = (total > max_eqns) or (unknown > max_unknown) return { "total_equations": total, "classified": total - unknown, "unknown": unknown, "max_allowed": max_eqns, "should_pause": should_pause, "reason": f"too many equations ({total} > {max_eqns})" if total > max_eqns else f"too many unknowns ({unknown} > {max_unknown})" if unknown > max_unknown else "proceed" } def write_decision_doc(equations: List[ParsedEquation], density: Dict, input_path: Path): """Write an intermediate decision document for excessive density.""" out = input_path.with_suffix(".decision.md") lines = [ f"# Equation Pipeline — Decision Required", f"", f"**File:** `{input_path}`", f"**Date:** auto-generated", f"", f"## Density Report", f"", f"| Metric | Value |", f"|--------|-------|", f"| Total equations | {density['total_equations']} |", f"| Classified | {density['classified']} |", f"| Unknown | {density['unknown']} |", f"| Max auto-process | {density['max_allowed']} |", f"", f"**Reason for pause:** {density['reason']}", f"", f"## Detected Equations", f"", f"| Line | Type | Classification | Equation |", f"|------|------|---------------|----------|", ] for eq in equations: cls = eq.classification lines.append(f"| {eq.line} | {'block' if eq.is_block else 'inline'} | {cls} | `{eq.text[:60]}{'...' if len(eq.text)>60 else ''}` |") lines += [ "", "## Decision Required", "", f"To proceed, run with `--force` or reduce equation count:", f"```", f"python3 scripts/ingest.py {input_path} --force", f"```", f"Or process only classified equations:", f"```", f"python3 scripts/ingest.py {input_path} --classified-only", f"```", ] out.write_text("\n".join(lines)) return out # ── Stage 3: Compute (delegates to fundamental force pipeline) ─────── def run_pipeline(equations: List[ParsedEquation]) -> List[Dict]: """Run each classified equation through the spectral pipeline.""" from fundamental_force import pipeline as fp_pipeline results = [] for eq in equations: if eq.classification == "unknown": results.append({"equation": eq.text, "line": eq.line, "result": "skipped", "reason": "unknown classification"}) continue # Build input from equation classification inp = { "strand_count": 8, "crossing_pairs": [(0,1),(2,3),(4,5),(6,7)], "chiral_labels": ["A"]*8, "strand_phases": [0]*8, "source_equation": eq.text, "source_line": eq.line, } # Spectral equations: vary chiral labels based on detected keywords if "rossby" in eq.text.lower() or "chiral" in eq.text.lower(): inp["chiral_labels"] = ["L"]*8 if "scarred" in eq.text.lower(): inp["chiral_labels"] = ["S"]*8 result = fp_pipeline(inp) results.append({ "equation": eq.text, "line": eq.line, "classification": eq.classification, "fingerprint": result["derived"] }) return results # ── Main ───────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser(description="Equation ingestion pipeline") parser.add_argument("input", type=str, help="Markdown file with equations") parser.add_argument("--max-eqns", type=int, default=20, help="Max equations before pausing") parser.add_argument("--max-unknown", type=int, default=5, help="Max unknown equations before pausing") parser.add_argument("--force", action="store_true", help="Process even if too dense") parser.add_argument("--classified-only", action="store_true", help="Skip unknown equations") args = parser.parse_args() input_path = Path(args.input) if not input_path.exists(): print(f"Error: {input_path} not found") sys.exit(1) text = input_path.read_text() # Parse equations = parse_markdown(text) print(f"Parsed {len(equations)} equations from {input_path}") # Classify equations = [classify_equation(e) for e in equations] spectral = sum(1 for e in equations if e.classification == "spectral") braid = sum(1 for e in equations if e.classification == "braid") cartan = sum(1 for e in equations if e.classification == "cartan") unknown = sum(1 for e in equations if e.classification == "unknown") print(f" Spectral: {spectral}, Braid: {braid}, Cartan: {cartan}, Unknown: {unknown}") # Density check density = check_density(equations, args.max_eqns, args.max_unknown) if density["should_pause"] and not args.force: doc = write_decision_doc(equations, density, input_path) print(f"\n⚠️ Too dense — wrote decision document:") print(f" {doc}") print(f" Review and re-run with --force to proceed.") return # Filter if args.classified_only: equations = [e for e in equations if e.classification != "unknown"] print(f"Processing {len(equations)} classified equations") # Compute results = run_pipeline(equations) # Emit receipt = { "schema": "equation_ingestion_v1", "source_file": str(input_path), "total_parsed": len(equations), "total_processed": len(results), "density": density, "results": results } out_path = input_path.with_suffix(".receipt.json") out_path.write_text(json.dumps(receipt, indent=2)) print(f"\n✅ Pipeline complete") print(f" Processed: {len(results)} equations") print(f" Receipt: {out_path}") # Summary computed = [r for r in results if "fingerprint" in r] if computed: gaps = [r["fingerprint"]["delta"][0] for r in computed] regimes = {r["fingerprint"]["delta"][0]: "CANONICAL" if r["fingerprint"]["delta"][0] > 0 else "ROSSBY" for r in computed} print(f" Gaps: {[f'{g:.6f}' for g in gaps]}") print(f" Regimes: {list(regimes.values())}") if __name__ == "__main__": main()