SilverSight/python/phi/ast_parse.py
allaun 1794299a6c chore(quality): native_decide migration, docs, and phi pipeline cleanup
Systematic native_decide → dec_trivial/rfl migration across all Lean modules
to comply with AGENTS.md rule 5 (no native_decide unless only option):
- CoreFormalism: BraidEigensolid, BraidField, ChentsovFinite, HachimojiBase,
  HachimojiBridging, HachimojiCodec, HachimojiLUT, HachimojiManifoldAxiom,
  Q16_16Numerics
- BindingSite: BindingSiteCodec, BindingSiteEntropy, BindingSiteHachimoji
- SilverSight: ProductSchema, ProductWireFormat, PolyFactorIdentity, Schema, WireFormat
- PVGS_DQ_Bridge: all three files (native_decide->dec_trivial)
- UniversalEncoding/ChiralitySpace

Additional changes:
- gemma4_mcp.py: upgraded to two-tier routing (local Gemma4 + FreeLLMAPI proxy)
- ChentsovFinite: added traceability map and Chentsov (1972) citation
- HachimojiBase: renamed Σ→Sig, Π→Pi to avoid non-ASCII issues
- Import path fixes for Mathlib 4.30.0-rc2 compatibility
- Doc updates: PURE_FORMULAS, SOS_CERTIFICATE, fundamental math derivations
- Build log: 2026-06-26 session findings
- BRKGLASS_NR_BRACKET_PROPOSAL: updated to REAL-DATA VALIDATED status
- New docs: FOUNDATIONAL_GUIDANCE, PURE_EQUATION_MAP, CHENTSOV_FINITE_MATH,
  BREAKGLASS_FUSION_REVIEW_SPEC, COLD_REVIEWER_FORMULA
- New python: phi pipeline (equation_dna_encoder, ast_parse, charclass,
  consistency, embed, output), nr_bracket_validation with receipt

Build: lake build SilverSightRRC — passes on all committed modules.
  Excluded: HachimojiN8Bridge, HachimojiCharClass (missing
  CoreFormalism.HachimojiManifoldAxiom olean — WIP)
2026-06-27 01:56:54 -05:00

182 lines
5.7 KiB
Python
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.

"""
phi.ast_parse — Layer 3: Parse tree analysis → τ and δ distributions
Converts equation strings to Python ASTs (after preprocessing math
notation), then computes:
τ(E) — node-type frequency histogram over Δ_{k-1}
δ(E) — child-ordering frequency histogram over Δ_{2k²-1}
Dependencies: Python ast, re (stdlib). Independent of phi.charclass.
"""
from __future__ import annotations
import ast
import re
from typing import Dict, List, Optional, Tuple
# ── Node types recognized by the parser ──────────────────────────────────
NODE_TYPES = [
"bin_add", "bin_sub", "bin_mul", "bin_div",
"bin_pow", "bin_eq", "un_neg", "un_not",
"var", "const", "call", "subscript", "tuple",
"list", "dict", "set", "lambda", "if_exp",
]
# ── Math notation preprocessor ───────────────────────────────────────────
def preprocess_math(text: str) -> str:
"""Preprocess math notation into Python-parseable form.
Handles:
- Single = → == (but preserves <=, >=, !=, ==)
- |x| → abs(x)
- Parenthesized subscripts: p_(i+j) → p[i+j]
- Implicit multiplication: (x)(y) → (x)*(y)
- TeX markers: removed
"""
s = text.strip()
if s.endswith(":"):
return ""
s = re.sub(r'\$|\$\$|\\\[|\\\]|\\\(|\\\)', '', s)
s = re.sub(r'(?<![<>=!])=(?!=)', '==', s)
s = re.sub(r'\|([^|=]+)\|', r'abs(\1)', s)
s = re.sub(r'([a-zA-Z])_\(([^)]+)\)', r'\1[\2]', s)
s = re.sub(r'\)\(', r')*(', s)
return s
def parse_to_ast(equation: str) -> Optional[ast.AST]:
"""Parse an equation string into a Python AST.
Tries direct parse first, then wraps in parens for expressions
where math notation drops outer parentheses.
"""
text = preprocess_math(equation)
if not text:
return None
try:
return ast.parse(text, mode="eval")
except SyntaxError:
pass
try:
return ast.parse(f"({text})", mode="eval")
except SyntaxError:
return None
# ── AST node classification ──────────────────────────────────────────────
def _classify_node(node: ast.AST) -> str:
"""Map an AST node to one of the NODE_TYPES."""
if isinstance(node, ast.BinOp):
if isinstance(node.op, ast.Add):
return "bin_add"
if isinstance(node.op, ast.Sub):
return "bin_sub"
if isinstance(node.op, ast.Mult):
return "bin_mul"
if isinstance(node.op, ast.Div):
return "bin_div"
if isinstance(node.op, ast.Pow):
return "bin_pow"
return "bin_add"
if isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.USub):
return "un_neg"
if isinstance(node.op, ast.Not):
return "un_not"
return "un_neg"
if isinstance(node, ast.Name):
return "var"
if isinstance(node, ast.Constant):
return "const"
if isinstance(node, ast.Call):
return "call"
if isinstance(node, ast.Subscript):
return "subscript"
if isinstance(node, ast.Tuple):
return "tuple"
return "const"
# ── τ(E): node-type frequencies → Δ_{k-1} ───────────────────────────────
def compute_tau(equation: str) -> Optional[List[float]]:
"""Compute τ(E) — normalized node-type histogram.
Returns 18 floats (one per NODE_TYPE) summing to 1.0,
or None if the equation cannot be parsed.
"""
tree = parse_to_ast(equation)
if tree is None:
return None
counts = {t: 0 for t in NODE_TYPES}
for node in ast.walk(tree):
t = _classify_node(node)
counts[t] = counts.get(t, 0) + 1
total = sum(counts.values())
if total == 0:
return [1.0 / len(NODE_TYPES)] * len(NODE_TYPES)
return [counts[t] / total for t in NODE_TYPES]
# ── δ(E): child-ordering frequencies → Δ_{2k²-1} ────────────────────────
# Which (parent, child) type combinations are tracked in δ
DELTA_PARENT_TYPES = [
"bin_add", "bin_sub", "bin_mul", "bin_div", "bin_pow",
"un_neg", "un_not", "call", "subscript",
]
DELTA_CHILD_TYPES = [
"bin_add", "bin_sub", "bin_mul", "bin_div", "bin_pow",
"un_neg", "var", "const", "call",
]
MAX_CHILDREN = 3
def compute_delta(equation: str) -> Optional[List[float]]:
"""Compute δ(E) — child-ordering frequency histogram.
For each (parent_type, child_type, child_index) triple, returns
the fraction of all parent-child edges. Captures syntactic
topology beyond raw node counts.
Returns a flat list of length len(DELTA_PARENT_TYPES) ×
len(DELTA_CHILD_TYPES) × MAX_CHILDREN, or None if parse fails.
"""
tree = parse_to_ast(equation)
if tree is None:
return None
edges: Dict[Tuple[str, str, int], int] = {}
total_edges = 0
def walk(node: ast.AST, _child_idx: int = 0):
nonlocal total_edges
parent_t = _classify_node(node)
for i, child in enumerate(ast.iter_child_nodes(node)):
child_t = _classify_node(child)
key = (parent_t, child_t, i)
edges[key] = edges.get(key, 0) + 1
total_edges += 1
walk(child, i)
walk(tree)
if total_edges == 0:
return None
result = []
for pt in DELTA_PARENT_TYPES:
for ct in DELTA_CHILD_TYPES:
for i in range(MAX_CHILDREN):
result.append(edges.get((pt, ct, i), 0) / total_edges)
return result