cubical-transport-hott-lean4/Topolei/Cubical/Trace.lean
Maximus Gorog c2e3ecb3e3
Some checks are pending
Lean Action CI / build (push) Waiting to run
Initial commit: topolei — cubical-transport HoTT in Lean 4 + Rust FFI
Implements the cells-spec vision: a computation space that preserves
auditability, correctness, interactivity. Phase 1 (Lean kernel +
naga-IR Rust backend) is closed; foundation hypothesis stack
(Selection H1+H2, Subobject H3, Trace H5, Obs.Ctx C2, Cubical.Trace)
landed.

Highlights:
- Cubical-HoTT syntax + value/eval/readback in Lean
- naga-IR pipeline (no GLSL string crosses FFI; 17/17 probes pass)
- Honesty audit: every non-transport (sealed cells, vertex shader,
  Y-flip, presentation conventions) is documented as such
- Polymorphic Trace α as free monoid; Cubical.Trace gives
  CTerm → Trace CTerm by structural fold (homomorphism = definition)
- Selection as Huet zipper; Subobject as Boolean algebra over WCell
- All theorems proven; the proof IS the implementation

See STATUS.md for the resume guide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:40:45 -06:00

207 lines
8.6 KiB
Text
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.

/-
Topolei.Cubical.Trace
=====================
The trace map at the cubical-syntax level.
## What this file does
Given a cubical term `t : CTerm`, `traceOf t : Trace CTerm` returns
the list of *all sub-terms encountered* in walking `t` (including
`t` itself). This is the **provenance fold**: every constructor
visited, every variable referenced, every face-conditional clause
walked.
Every rendered output produced from `t` traces back to `traceOf t`.
No instrumentation of the renderer is required — the trace is a
property of the *cubical structure*, computable in pure Lean before
the term ever leaves the host for the GPU. The Rust side renders
whatever single concrete shader Lean hands it; the trace was already
extracted upstream.
## Why this is the Euler-elegant move
Composition of cubical terms (via `comp`, `compN`, `glueIn`,
`unglue`) automatically composes their traces — this is forced by
`traceOf`'s structural recursion + `Trace`'s monoid structure. The
homomorphism theorems below say: for any constructor `C` with
sub-terms `s₁..sₙ`,
traceOf (C s₁ … sₙ) = single (C s₁ … sₙ) traceOf s₁ traceOf sₙ
Every one is `rfl`. The homomorphism IS the definition. No
external machinery, no enumerated cases, just structural recursion
realised as a fold + Trace's free-monoid algebra.
## What this gets us, semantically
Per-pixel traces (the user's "different pixels carrying projections
of different fibers") become straightforward once we add a face-
pruning version `traceOfAt : DimAssignment → CTerm → Trace CTerm`
that, before recursing into `compN`'s clauses, evaluates each face
formula at the given assignment and skips clauses whose face is
inactive. That's a sibling function we can land later.
Coherence between fibers (the differential / sheaf / bundle
question) is then a *predicate over `Trace CTerm`*: "the traces at
adjacent pixels share a long prefix," "the traces vary smoothly
along the rendered path," etc. All landed via simple `Prop`s, no
new types.
## Why no namespace wrap on `traceOf`
`CTerm` lives at the root namespace (Syntax.lean has no
`namespace` declaration), so `CTerm.traceOf` must too — that's
what makes the dot notation `t.traceOf` resolve for any `t : CTerm`.
Theorems are in a namespace below; the function is at root.
-/
import Topolei.Cubical.Syntax
import Topolei.Trace
open Topolei.Trace
-- ── traceOf : structural fold over CTerm ──────────────────────────────────
-- The trace of a cubical term: itself, plus the union of traces of
-- its immediate sub-terms (recursively).
--
-- Mutual with `traceOf.clauses` to handle `compN`'s list of face-
-- conditional sub-terms — same pattern as `CTerm.substDim` /
-- `CTerm.substDim.clauses` in `Syntax.lean`.
mutual
/-- The trace of a cubical term: itself, plus the union of traces of
its immediate sub-terms (recursively). -/
def CTerm.traceOf : CTerm → Trace CTerm
| t@(.var _) => Trace.single t
| t@(.lam _ body) =>
(Trace.single t).union body.traceOf
| t@(.app f a) =>
(Trace.single t).union (f.traceOf.union a.traceOf)
| t@(.plam _ body) =>
(Trace.single t).union body.traceOf
| t@(.papp body _) =>
(Trace.single t).union body.traceOf
| t@(.transp _ _ _ body) =>
(Trace.single t).union body.traceOf
| t@(.comp _ _ _ u v) =>
(Trace.single t).union (u.traceOf.union v.traceOf)
| t@(.compN _ _ clauses v) =>
(Trace.single t).union ((CTerm.traceOf.clauses clauses).union v.traceOf)
| t@(.glueIn _ a b) =>
(Trace.single t).union (a.traceOf.union b.traceOf)
| t@(.unglue _ f g) =>
(Trace.single t).union (f.traceOf.union g.traceOf)
| t@(.pair a b) =>
(Trace.single t).union (a.traceOf.union b.traceOf)
| t@(.fst a) =>
(Trace.single t).union a.traceOf
| t@(.snd a) =>
(Trace.single t).union a.traceOf
/-- Walk a `compN`'s face-conditional clauses, unioning each
sub-term's trace. The face formulas themselves contribute
*no* trace items — they're metadata about *when* the
sub-term participates, not source items. A future
`traceOfAt` will use the formulas to prune; the
unrestricted `traceOf` records all clauses unconditionally. -/
def CTerm.traceOf.clauses : List (FaceFormula × CTerm) → Trace CTerm
| [] => Trace.empty
| (_, c) :: rest => c.traceOf.union (CTerm.traceOf.clauses rest)
end
-- ── Theorems live in a sub-namespace ──────────────────────────────────────
namespace Topolei.Cubical.Trace
-- ── Homomorphism theorems (the construction-language equations) ──────────
--
-- For each cubical constructor C with sub-terms s₁..sₙ:
-- traceOf (C s₁ … sₙ) = single (C s₁ … sₙ) traceOf s₁ traceOf sₙ
--
-- Every one of these is `rfl` by the definition above. This is the
-- "Euler-elegant" core: the homomorphism IS the definition; we don't
-- need separate proofs. The theorems exist as named references for
-- downstream code, and as a stable contract that future refactors
-- of `traceOf` must preserve.
@[simp] theorem traceOf_var (x : String) :
(CTerm.var x).traceOf = Trace.single (CTerm.var x) := rfl
@[simp] theorem traceOf_lam (x : String) (body : CTerm) :
(CTerm.lam x body).traceOf =
(Trace.single (CTerm.lam x body)).union body.traceOf := rfl
@[simp] theorem traceOf_app (f a : CTerm) :
(CTerm.app f a).traceOf =
(Trace.single (CTerm.app f a)).union (f.traceOf.union a.traceOf) := rfl
@[simp] theorem traceOf_plam (i : DimVar) (body : CTerm) :
(CTerm.plam i body).traceOf =
(Trace.single (CTerm.plam i body)).union body.traceOf := rfl
@[simp] theorem traceOf_papp (body : CTerm) (r : DimExpr) :
(CTerm.papp body r).traceOf =
(Trace.single (CTerm.papp body r)).union body.traceOf := rfl
@[simp] theorem traceOf_transp (i : DimVar) (A : CType)
(φ : FaceFormula) (body : CTerm) :
(CTerm.transp i A φ body).traceOf =
(Trace.single (CTerm.transp i A φ body)).union body.traceOf := rfl
@[simp] theorem traceOf_comp (i : DimVar) (A : CType) (φ : FaceFormula)
(u v : CTerm) :
(CTerm.comp i A φ u v).traceOf =
(Trace.single (CTerm.comp i A φ u v)).union
(u.traceOf.union v.traceOf) := rfl
@[simp] theorem traceOf_glueIn (φ : FaceFormula) (a b : CTerm) :
(CTerm.glueIn φ a b).traceOf =
(Trace.single (CTerm.glueIn φ a b)).union (a.traceOf.union b.traceOf) :=
rfl
@[simp] theorem traceOf_unglue (φ : FaceFormula) (f g : CTerm) :
(CTerm.unglue φ f g).traceOf =
(Trace.single (CTerm.unglue φ f g)).union (f.traceOf.union g.traceOf) :=
rfl
@[simp] theorem traceOf_pair (a b : CTerm) :
(CTerm.pair a b).traceOf =
(Trace.single (CTerm.pair a b)).union (a.traceOf.union b.traceOf) := rfl
@[simp] theorem traceOf_fst (a : CTerm) :
(CTerm.fst a).traceOf =
(Trace.single (CTerm.fst a)).union a.traceOf := rfl
@[simp] theorem traceOf_snd (a : CTerm) :
(CTerm.snd a).traceOf =
(Trace.single (CTerm.snd a)).union a.traceOf := rfl
-- ── Length / non-emptiness ────────────────────────────────────────────────
--
-- A trivial but useful corollary: every term's trace is non-empty
-- (it always contains at least the term itself). The user's
-- introspection guarantee depends on this — "every rendered element
-- has *some* provenance" is a typed property, not a runtime hope.
theorem traceOf_nonempty (t : CTerm) : t.traceOf.items ≠ [] := by
cases t <;> simp [CTerm.traceOf, Trace.single, Trace.union]
end Topolei.Cubical.Trace
-- ── Operational sanity ────────────────────────────────────────────────────
/-- A demo: the trace of `λx. x` (an identity term) contains the lam
AND the var. -/
def demoIdentity : CTerm := .lam "x" (.var "x")
#eval demoIdentity.traceOf.items.length -- expected: 2 (lam + var)
/-- A demo: the trace of `(a, b)` contains pair, var "a", var "b" → 3. -/
def demoPair : CTerm := .pair (.var "a") (.var "b")
#eval demoPair.traceOf.items.length -- expected: 3
/-- A demo: the trace of an application contains app + f-trace + a-trace. -/
def demoApp : CTerm := .app (.var "f") (.var "a")
#eval demoApp.traceOf.items.length -- expected: 3