This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Set up environment (first time)
uv venv
uv add sympy
uv add pytest --dev
# Run all tests
uv run pytest cmtz/tests/
# Run a single test file
uv run pytest cmtz/tests/test_compiler.py -v
# Run a single test
uv run pytest cmtz/tests/test_compiler.py::TestEndToEnd::test_simple_embed_measure -v
# Compile a .cmtz script and print full report
uv run python compile_script.py
# Run a specific example
uv run python examples/fibonacci_runner.pyAlways use uv for package management — never pip directly.
The compiler is a classic pipeline: DSL source → IR DAG → analysis → optimization → backend. The entry point is compile_program() in cmtz/compiler.py, which orchestrates all phases and returns a CompilationResult.
-
Lexer (
lexer.py) — tokenizes source. Token ordering matters:MODEand keyword tokens must appear beforeIDinTOKEN_SPECor identifiers likereal,fieldparse as generic IDs. -
Parser (
parser.py) — recursive descent producing typed AST nodes fromast_nodes.py. All statement parameters are literals; no expressions or variables. -
Elaborator (
elaborator.py) — resolvesfield(p, q), validates bothpandqare prime withq ≥ p, infers field fromroots(p)if no explicit declaration. Produces anIRFieldfor the lowering phase. -
Lowering (
lowering.py) — AST → IR DAG. Key naming conventions produced here:embed(i, ψ)→ register namedembed_irotate(src, dst, j)→ register namedrot_{src}_{dst}compose(a, b, ...)→ fresh namescompose_1,compose_2, ...catalytic { } restoring ()→ fresh namecatalytic_Nmatpow(M, d, ε) as name→ register namedname;Mmust already exist in the IR by name
-
Static analysis (
analysis/) — four independent passes that all run even if one fails:cycle_check.py— topological sort; must pass before anything else is meaningfulfield_consistency.py— every node'sir_fieldmust match its parents'catalytic_verify.py— symbolic delta tracking throughIRCatalyticRegion; currently initializes all deltas to 0 (all regions pass)cost_propagation.py— bottom-up Lemma 2.1 cost propagation; checksIRMatPownodes against Theorem 1.2 bounds with 10% slack
-
Optimization (
optimization/) — three passes:rotate_fusion.py— merges adjacentIRRotatenodes into one when the parent has no fan-out; updatesj = (j1 + j2) % mcse.py— deduplicatesIRPolynodes with identical(coeffs, p, q)signaturesconstant_fold.py— evaluatesIRPolyat compile time when input is constant
-
Backends (
backends/) — pluggable, selected by thebackendparameter:python_ref.py—PythonBackend: exact Z_p arithmetic, walks IR viatopo_order().IRMatPowevaluates to 0 unlesseval_matpow_matrix(node, matrix)is called directly with a concrete matrix.torch_backend.py—int64tensors, explicit% p; availability-gatedanalog_descriptor.py— emits a JSONAnalogDescriptorwith phase offsets, GEMM pass count, register depth for hardware targetingglsl_backend.py— emits a fully unrolled GLSL 4.50 compute shader (branch-free, integer mod arithmetic); SSBO for root table;spirv_compile_command()returns the glslangValidator invocation
IRField(p, q)— frozen dataclass;m = p - 1is the root-of-unity order. Bothpandqmust be prime,q ≥ p. The elaborator enforces this;IRField.__post_init__also validates.IRProgram—OrderedDict[str, IRNode]maintaining insertion order;topo_order()does the topological sort. RaisesCycleErrorif a cycle exists. Duplicate names raiseValueError.IRNode— base dataclass withname,parents: list[IRNode],ir_field, and Lemma 2.1 cost fields (recursive_calls,basic_instructions,num_registers). Mutable defaultparents=[]was a v1 bug — v2 usesfield(default_factory=list).IRMatPow— has adeltaproperty (= 2^(3/ε)) andcost_bound()returning Theorem 1.2 bounds. Thematrix_namestring is a reference to an existing IR node name.
The DSL compiles structure and verifies cost bounds. Actual matrix computation requires calling PythonBackend.eval_matpow_matrix(node, matrix) directly in Python — the DSL backend returns 0 for IRMatPow nodes. The matrix (list[list[int]]) is not expressible in the DSL; it's a runtime parameter. See examples/fibonacci_runner.py for the pattern.
q must be a prime ≥ p. The spec example uses field(17, 289) but 289 = 17² is not prime — this is illustrative only. Use field(17, 293) (the next prime above 17²) in real programs. compute_working_field(p, degree, n) in field.py computes the correct q per Corollary 3.4.2.
matpow(M, d, ε) as name requires M to already be a named node in the IR. The idiomatic workaround is roots(p) as M which creates an IRPrimitiveRoots node with that name, satisfying the lookup.
Cook_Mertz_Spec_v2.md is the authoritative reference. Section 4 has the complete BNF grammar. The spec documents six v1 bugs fixed in v2; the most important are: cyclo(n) replaced by roots(p) (primitive roots, not cyclotomic polynomial), IRRotate.theta: float replaced by IRRotate.j: int, and IRPoly now carries an IRField.