cubical-transport-hott-lean4/CubicalTransport/Eval.lean
Maximus Gorog f27211f73f REL1 foundation: schema-based inductive + HIT CTypes (Lean side)
Adds schema-based inductive type encoding to the engine, supporting both
plain inductives (Nat, List, Bool) and HITs (S¹, propositional truncation,
suspensions) via a single CTypeSchema/CtorSpec/CTypeArg structure.

New CType constructor:
  - .ind (S : CTypeSchema) (params : List CType)

New CTerm constructors:
  - .dimExpr  (r : DimExpr)
  - .ctor     (S, ctorName, params, args)
  - .indElim  (S, params, motive, branches, target)

New CVal constructors:
  - .vctor    (S, ctorName, params, evaluatedArgs)
  - .vdimExpr (DimExpr)

New CNeu constructor:
  - .nIndElim (S, params, motive, branches, neuTarget)

Five-way mutual block in Syntax.lean (CType, CTerm, CTypeArg, CtorSpec,
CTypeSchema) with Repr derivation post-hoc.  Tag layout per
docs/INDUCTIVE_TYPES.md §6: old tags preserved (no shifting).  Existing
62/62 smoke + property tests pass unchanged through Rust.

Substitution / dim-absent / endpoint handling:
  Subst.lean      — substDim, substDimExpr, equation lemmas,
                    substDim_eq_substDimExpr (mutual with .params helpers).
  DimLine.lean    — CTerm.dimAbsent + CType.dimAbsent (mutual w/ helpers
                    for list / branches / params).  Plus all five
                    auxiliary mutual blocks: substDim_absent_aux,
                    substDimExpr_absent_aux, dimAbsent_after_substDim_aux,
                    substDim_comm_aux — for both CTerm and CType.

Eval.lean: ctor → vctor (eval'd args); indElim β-reduces on canonical
vctor target via vApp chain over branch body, otherwise stuck via
.nIndElim; dimExpr → vdimExpr.  Path-ctor boundary firing and
recursive-arg IH wiring marked TODO REL1.1 (β-reduction works for
non-recursive ctors today).

Readback.lean: vctor → .ctor, vdimExpr → .dimExpr, nIndElim → .indElim.

FFITest.lean: cvalSummary / ctermSummary extended with new constructor
arms.

Topolei (sibling repo) has not yet been migrated — see
docs/INDUCTIVE_TYPES.md §9.1 for the per-file impact map.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:09:39 -06:00

821 lines
38 KiB
Text
Raw Permalink 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.Eval
====================
Environment-based evaluator for the cubical calculus (cells-spec §5.4,
Phase 1 Week 2).
`eval env t` reduces `t` to weak-head normal form in environment `env`.
Three mutually-recursive pieces:
· `eval` : `CEnv → CTerm → CVal` — main evaluator
· `vApp` : `CVal → CVal → CVal` — function application at the value level
· `vPApp` : `CVal → DimExpr → CVal` — path (dimension) application at value level
Coverage: the *λ-calculus fragment* — `var`/`lam`/`app`/`plam`/`papp`.
`transp`/`comp` produce stuck neutrals (`ntransp`/`ncomp`); their real
reduction rules come with `Transport.lean` and `Comp.lean` (Weeks 34).
These are `partial def`s. Termination isn't statically checked because the
`vPApp` β-step substitutes inside the closure body and re-evaluates; the
result is the same `CTerm` size, but Lean's structural recursion can't see
through that. A future total version will measure on a subject-reduction
metric. For now, `partial def` is the honest choice.
-/
import CubicalTransport.Value
import CubicalTransport.Transport
-- ── Rust FFI declarations (Phase C.2) ──────────────────────────────────────
-- `@[extern "topolei_cubical_*"] opaque *Rust ...` declares the Rust
-- entry point. `@[implemented_by]` on each partial def routes runtime
-- calls to Rust (kernel-level proof reasoning still uses the axioms).
@[extern "topolei_cubical_eval"]
opaque evalRust (env : CEnv) : CTerm → CVal
@[extern "topolei_cubical_vapp"]
opaque vAppRust : CVal → CVal → CVal
@[extern "topolei_cubical_vpapp"]
opaque vPAppRust : CVal → DimExpr → CVal
@[extern "topolei_cubical_vhcomp"]
opaque vHCompValueRust (A : CType) (φ : FaceFormula) (tube base : CVal) : CVal
@[extern "topolei_cubical_vcomp_term"]
opaque vCompAtTermRust (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (u t : CTerm) : CVal
@[extern "topolei_cubical_vcompn_term"]
opaque vCompNAtTermRust (env : CEnv) (i : DimVar) (A : CType)
(clauses : List (FaceFormula × CTerm)) (t : CTerm) : CVal
@[extern "topolei_cubical_vfst"]
opaque vFstRust : CVal → CVal
@[extern "topolei_cubical_vsnd"]
opaque vSndRust : CVal → CVal
mutual
/-- Evaluate a term in an environment. λ-fragment + dimension abstraction
are reduced immediately; transport dispatches through `vTransp`;
heterogeneous composition dispatches through `vCompAtTerm` (which has
access to the raw CTerms so it can substitute at the term level for
`comp_full`).
This is a `partial def` because the β-step in `vPApp` substitutes inside
a closure body (via `CTerm.substDim`) and re-evaluates — the resulting
term is the same CTerm size, but Lean's structural-recursion checker
can't see through that, and indeed an untyped Ω-like term genuinely
diverges. A well-typed total evaluator arrives in a later phase. -/
@[implemented_by evalRust]
partial def eval (env : CEnv) : CTerm → CVal
| .var x => (env.lookup x).getD (.vneu (.nvar x))
| .lam x body => .vlam env x body
| .app f a => vApp (eval env f) (eval env a)
| .plam i body => .vplam env i body
| .papp t r => vPApp (eval env t) r
| .transp i A φ t =>
-- Priority order: (1) full face, (2) constant line, (3) path
-- (needs env), (4) glue (dispatched via dedicated axioms in
-- `Glue.lean`; the partial def produces a stuck neutral as a
-- runtime placeholder), (5) other via value-level `vTransp`.
match φ with
| .top => eval env t -- (1) T1
| _ =>
if CType.dimAbsent i A then
eval env t -- (2) T2
else
match A with
| .path A₀ a b => .vPathTransp env i A₀ a b φ t -- (3)
| .glue _ _ _ _ _ _ _ _ => -- (4) Glue
-- Real reduction rules live in `Glue.lean` as dedicated
-- axioms keyed on specific sub-cases (e.g. constant
-- equivalence components). The partial def produces
-- `ntransp` as a placeholder — the same shape vTransp
-- would produce on stuck glue — so that kernel-level
-- proofs go exclusively through the Glue axioms.
.vneu (.ntransp i A φ (eval env t))
| _ => vTransp i A φ (eval env t) -- (5)
| .comp i A φ u t => vCompAtTerm env i A φ u t
| .compN i A clauses t => vCompNAtTerm env i A clauses t
| .glueIn φ t a =>
-- Face-priority dispatch: φ = .top → t-side, φ = .bot → a-side,
-- else stuck `.nglueIn` preserving both face-on / face-off values.
match φ with
| .top => eval env t
| .bot => eval env a
| _ => .vneu (.nglueIn φ (eval env t) (eval env a))
| .unglue φ f g =>
-- Face-priority dispatch:
-- φ = .top: apply the forward map; the argument is a T-value.
-- φ = .bot: identity — on the empty face, the glued term is the
-- A-value `g` itself (consistent with `glueIn .bot t a → a`).
-- Else: stuck `.nunglue` preserving `f` and `g`.
match φ with
| .top => vApp (eval env f) (eval env g)
| .bot => eval env g
| _ => .vneu (.nunglue φ (eval env f) (eval env g))
| .pair a b => .vpair (eval env a) (eval env b)
| .fst t => vFst (eval env t)
| .snd t => vSnd (eval env t)
-- REL1 inductive-type constructors.
| .dimExpr r => .vdimExpr r
| .ctor S c params args =>
-- Produce a canonical constructor value with all args evaluated.
-- (Boundary firing for path ctors lands in a follow-up — REL1
-- design doc §4: when a `.dim`-typed arg evaluates to .zero/.one
-- and matches a clause in `S.ctors[c].boundary`, we should fire
-- that clause body instead. TODO REL1.1.)
.vctor S c params (args.map (eval env))
| .indElim S params motive branches target =>
-- β-reduce on a canonical `vctor`; otherwise build a stuck
-- `.nIndElim` that preserves env-evaluated motive and branches
-- so it can unstick when target later resolves.
match eval env target with
| .vctor _ c _ argsV =>
match List.lookup c branches with
| some body =>
-- Apply branch body to constructor's args via repeated
-- `vApp` — the body is curried: λ a₁ … aₙ. result.
-- Recursive-arg hypotheses (the IH passed alongside each
-- `.self` arg in the design doc §5) are not yet wired
-- here; an `indElim` of a non-recursive ctor is fully
-- handled, but recursive ctors land their branch body
-- with only the constructor args (no IH) — TODO REL1.1.
argsV.foldl (fun f a => vApp f a) (eval env body)
| none =>
.vneu (.nvar s!"<indElim: no branch for {c}>")
| .vneu n =>
.vneu (.nIndElim S params (eval env motive)
(branches.map (fun (nm, b) => (nm, eval env b))) n)
| _ =>
.vneu (.nvar "<indElim: target is not canonical>")
/-- First projection at the value level. β-reduces `vpair`; pushes a
stuck neutral into `nfst`. Projecting any other value shape is a
type error (marker neutral). -/
@[implemented_by vFstRust]
partial def vFst : CVal → CVal
| .vpair a _ => a
| .vneu n => .vneu (.nfst n)
| _ => .vneu (.nvar "<vFst: non-pair projection>")
/-- Second projection at the value level. Mirror of `vFst`. -/
@[implemented_by vSndRust]
partial def vSnd : CVal → CVal
| .vpair _ b => b
| .vneu n => .vneu (.nsnd n)
| _ => .vneu (.nvar "<vSnd: non-pair projection>")
/-- Apply one value to another. β-reduces `vlam` closures; pushes stuck
neutrals into `napp`. `vTranspFun` unfolds per the *full* CCHM Π rule.
`vHCompFun` unfolds per the CCHM Π hcomp rule: apply pointwise
through the tube, then recursively hcomp on the codomain. Applying
a `vplam` or `vTubeApp` as a function is a type error. -/
@[implemented_by vAppRust]
partial def vApp : CVal → CVal → CVal
| .vlam env x body, arg => eval (env.extend x arg) body
| .vneu n, arg => .vneu (.napp n arg)
| .vTranspFun i domA codA φ f, arg =>
vTransp i codA φ (vApp f (vTranspInv i domA φ arg))
| .vHCompFun codA φ tube base, arg =>
vHCompValue codA φ (.vTubeApp tube arg) (vApp base arg)
-- Full CCHM Π hetero comp β-rule. See file-level comment below for
-- the fill construction.
| .vCompFun env i domA codA φ u t, arg =>
let yName := "$y"
let fj : DimVar := ⟨"$fj"⟩
let newEnv := env.extend yName arg
-- `filled_y_at_i`: a line in the *outer* binder `i` giving y_at_i:A(i).
-- Build as `transp^{fj} (domA[i := (inv fj) i]) φ ($y)`:
-- at fj=0: line slice is A(1) (source: y : A(1))
-- at fj=1: line slice is A(i) (target we want)
let filled_y_at_i : CTerm :=
.transp fj
(domA.substDimExpr i (.join (.inv (.var fj)) (.var i)))
φ (.var yName)
-- `filled_y_at_0`: substitute i := 0 in the above — the reversed
-- line is `A[i := inv fj]`, taking A(1) → A(0).
let filled_y_at_0 : CTerm :=
.transp fj
(domA.substDimExpr i (.inv (.var fj)))
φ (.var yName)
eval newEnv (.comp i codA φ
(.app u filled_y_at_i)
(.app t filled_y_at_0))
| .vplam _ _ _, _ => .vneu (.nvar "<vApp: vplam applied as function>")
| .vTubeApp _ _, _ => .vneu (.nvar "<vApp: vTubeApp applied as function>")
| .vPathTransp _ _ _ _ _ _ _, _ => .vneu (.nvar "<vApp: vPathTransp applied as function>")
| .vpair _ _, _ => .vneu (.nvar "<vApp: vpair applied as function>")
| .vctor _ _ _ _, _ => .vneu (.nvar "<vApp: vctor applied as function>")
| .vdimExpr _, _ => .vneu (.nvar "<vApp: vdimExpr applied as function>")
/-- Apply a value to a dimension expression. β-reduces `vplam` closures
by substituting the dim in the body and re-evaluating; pushes stuck
neutrals into `npapp`. `vTubeApp tube arg` applied to dim `r` reduces
to `vApp (vPApp tube r) arg` — the semantic meaning of
`λj. (tube @ j) arg`. Marker neutrals for ill-typed applications. -/
@[implemented_by vPAppRust]
partial def vPApp : CVal → DimExpr → CVal
| .vplam env i body, r => eval env (body.substDim i r)
| .vneu n, r => .vneu (.npapp n r)
| .vTubeApp tube arg, r => vApp (vPApp tube r) arg
-- Path transport endpoint reductions: at `.zero` / `.one` the CCHM
-- multi-clause system degenerates (the (j=0) or (j=1) clause fires)
-- and the result is the specified endpoint at i=1. At generic `r`
-- we fall through to `vCompNAtTerm` on the CCHM multi-clause system
-- `[φ ↦ p@r, (r=0) ↦ a, (r=1) ↦ b]` with base `p@r` — which reduces
-- further if the face substitutions happen to simplify.
| .vPathTransp env i _ a _ _ _, .zero => eval env (a.substDim i .one)
| .vPathTransp env i _ _ b _ _, .one => eval env (b.substDim i .one)
| .vPathTransp env i A a b φ p, r =>
vCompNAtTerm env i A
[ (φ, .papp p r)
, (FaceFormula.dimExprEq0 r, a)
, (FaceFormula.dimExprEq1 r, b) ]
(.papp p r)
| .vlam _ _ _, _ => .vneu (.nvar "<vPApp: vlam applied as path>")
| .vTranspFun _ _ _ _ _, _ => .vneu (.nvar "<vPApp: vTranspFun applied as path>")
| .vHCompFun _ _ _ _, _ => .vneu (.nvar "<vPApp: vHCompFun applied as path>")
| .vCompFun _ _ _ _ _ _ _, _ => .vneu (.nvar "<vPApp: vCompFun applied as path>")
| .vpair _ _, _ => .vneu (.nvar "<vPApp: vpair applied as path>")
| .vctor _ _ _ _, _ => .vneu (.nvar "<vPApp: vctor applied as path>")
| .vdimExpr _, _ => .vneu (.nvar "<vPApp: vdimExpr applied as path>")
/-- Homogeneous composition at the value level. The type `A` is
*homogeneous* (doesn't vary along `i`); the tube and base are
already-evaluated values.
Reducing cases (in match order):
1. `φ = .top` — tube covers everything, result is `tube @ 1`.
2. `A = .pi` — CCHM Π hcomp: construct a `vHCompFun` closure
that applies pointwise when the function is
applied to an argument.
3. Otherwise — stuck `.vneu (.nhcomp ...)`.
Note the crucial difference from `vTransp`: no constant-line check,
because hcomp is *already* homogeneous — constancy is built in. -/
@[implemented_by vHCompValueRust]
partial def vHCompValue (A : CType) (φ : FaceFormula) (tube base : CVal) :
CVal :=
match φ with
| .top => vPApp tube .one
| _ =>
match A with
| .pi _domA codA => .vHCompFun codA φ tube base
| _ => .vneu (.nhcomp A φ tube base)
/-- Heterogeneous composition at the term level. Takes `u` and `t` as
`CTerm`s (not `CVal`s) so that the `comp_full` reduction can perform
substitution *at the term level* — `eval env (u.substDim i .one)` —
which is in general different from `vPApp (eval env u) .one`.
Reducing cases (in match order):
1. `φ = .top` — C1: `eval env (u[i := 1])`.
2. `φ = .bot` — C2: `eval env (transp i A .bot t)`.
3. `CType.dimAbsent i A = true` — constant line: heterogeneous
comp reduces to hcomp on the
fixed type `A`, with `.plam i u`
as the tube.
4. Otherwise — stuck `.vneu (.ncomp ...)`.
Note cases (1) and (2) are decidable at the `.top`/`.bot` head of
`φ`; case (3) discriminates on `A` only after `.top`/`.bot` are
ruled out. All four cases are mutually exclusive. -/
@[implemented_by vCompAtTermRust]
partial def vCompAtTerm (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (u t : CTerm) : CVal :=
match φ with
| .top => eval env (u.substDim i .one)
| .bot => eval env (.transp i A .bot t)
| _ =>
if CType.dimAbsent i A then
-- Constant line: hetero comp reduces to hcomp. `.plam i u` is the
-- tube as a dim-closure.
vHCompValue A φ (eval env (.plam i u)) (eval env t)
else
match A with
| .pi domA codA =>
-- Hetero Π comp: package into a `vCompFun` closure. The CCHM
-- β-rule runs at `vApp`-time with a full fill-based tube.
.vCompFun env i domA codA φ u t
| _ =>
.vneu (.ncomp i A φ (eval env u) (eval env t))
/-- Multi-clause heterogeneous composition at the term level. Dispatches
by scanning the clause list:
· If any clause has face `.top`, that clause's body substituted at
`i := .one` is the result (CCHM multi-clause full-system rule).
· If all clause faces are `.bot` or the list is empty, no clause
fires — reduces to plain transport of the base.
· If exactly one clause has a non-trivial face, delegate to
`vCompAtTerm` (single-clause specialisation).
· Otherwise produce a stuck `ncompN` neutral preserving env, line
binder, type, evaluated clauses, and evaluated base. -/
@[implemented_by vCompNAtTermRust]
partial def vCompNAtTerm (env : CEnv) (i : DimVar) (A : CType)
(clauses : List (FaceFormula × CTerm)) (t : CTerm) : CVal :=
-- Scan for a `.top` clause first.
match clauses.find? (fun ⟨φ, _⟩ => match φ with | .top => true | _ => false) with
| some ⟨_, u⟩ => eval env (u.substDim i .one)
| none =>
-- Strip `.bot` clauses (they never fire).
let live := clauses.filter
(fun ⟨φ, _⟩ => match φ with | .bot => false | _ => true)
match live with
| [] => eval env (.transp i A .bot t)
| [⟨φ, u⟩] => vCompAtTerm env i A φ u t
| _ =>
.vneu (.ncompN env i A
(live.map (fun ⟨φ, u⟩ => (φ, eval env u)))
(eval env t))
end
/-!
## Reduction lemmas (axioms)
`partial def` is opaque at the kernel level, so the defining cases of
`eval`, `vApp`, and `vPApp` are not reducible by `rfl`. We state them as
axioms — the same pattern used for `CTerm.step` and `step_papp_plam` in
`Syntax.lean`. They exactly mirror the `partial def` match arms above,
so they are consistent with the runtime implementation while also being
usable in kernel-level proofs.
-/
-- Reduction lemmas for `eval`.
axiom eval_var (env : CEnv) (x : String) :
eval env (.var x) = (env.lookup x).getD (.vneu (.nvar x))
axiom eval_lam (env : CEnv) (x : String) (body : CTerm) :
eval env (.lam x body) = .vlam env x body
axiom eval_app (env : CEnv) (f a : CTerm) :
eval env (.app f a) = vApp (eval env f) (eval env a)
axiom eval_plam (env : CEnv) (i : DimVar) (body : CTerm) :
eval env (.plam i body) = .vplam env i body
axiom eval_papp (env : CEnv) (t : CTerm) (r : DimExpr) :
eval env (.papp t r) = vPApp (eval env t) r
/-!
### `eval` on `.transp` — four disjoint cases
Replaces the earlier coarse `eval_transp` axiom. Match-arm priority is:
1. `.top` face → identity (T1 at eval level).
2. Constant line → identity (T2 at eval level).
3. `.path A₀ a b` line, non-constant → `vPathTransp` closure.
4. Non-path, non-constant line → delegate to value-level `vTransp`.
The four cases are mutually exclusive by precondition, so the axiom set
is consistent. -/
/-- (1) T1 at eval level: transport under a full face is identity. -/
axiom eval_transp_top (env : CEnv) (i : DimVar) (A : CType) (t : CTerm) :
eval env (.transp i A .top t) = eval env t
/-- (2) T2 at eval level: transport along a constant line is identity.
Covers `.univ`, constant-`pi`, and constant-`path` lines uniformly. -/
axiom eval_transp_const (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (t : CTerm)
(hφ : φ ≠ .top)
(hA : CType.dimAbsent i A = true) :
eval env (.transp i A φ t) = eval env t
/-- (3) Path transport: when the line's body is `.path A₀ a b` with the
whole path-line genuinely varying, produce a `vPathTransp` closure
that captures the endpoint CTerms `a`/`b` along with the env and the
path term `t` (kept as a CTerm so later `.papp t r` constructions
work for the multi-clause reduction at generic dim). Reduces
further under `vPApp` at endpoints. -/
axiom eval_transp_path (env : CEnv) (i : DimVar) (A₀ : CType)
(a b : CTerm) (φ : FaceFormula) (t : CTerm)
(hφ : φ ≠ .top)
(hA : CType.dimAbsent i (.path A₀ a b) = false) :
eval env (.transp i (.path A₀ a b) φ t) =
.vPathTransp env i A₀ a b φ t
/-- (4) Non-path non-glue non-constant transport: delegate to the value-level
`vTransp`, which is env-agnostic and handles `.pi` via `vTranspFun`.
`.glue` is excluded because its CCHM transport formula lives in dedicated
Glue-specific axioms (see `Glue.lean`); routing it through `vTransp`
here would claim it reduces to a stuck neutral, which would contradict
those axioms in their specific sub-cases. -/
axiom eval_transp_nonpath (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (t : CTerm)
(hφ : φ ≠ .top)
(hA : CType.dimAbsent i A = false)
(h_not_path : ∀ A₀ a b, A ≠ .path A₀ a b)
(h_not_glue : ∀ φG T f fInv sec ret coh Ai,
A ≠ .glue φG T f fInv sec ret coh Ai) :
eval env (.transp i A φ t) = vTransp i A φ (eval env t)
/-- Π-case theorem (full CCHM): transport along any `pi domA codA` line
produces a `vTranspFun` closure. Derived via `eval_transp_nonpath`
(`pi ≠ path` and `pi ≠ glue` by constructor disjointness) plus
`vTransp_pi`. -/
theorem eval_transp_pi (env : CEnv) (i : DimVar)
(domA codA : CType) (φ : FaceFormula) (t : CTerm)
(hφ : φ ≠ .top)
(hA : CType.dimAbsent i (.pi domA codA) = false) :
eval env (.transp i (.pi domA codA) φ t) =
.vTranspFun i domA codA φ (eval env t) := by
rw [eval_transp_nonpath env i _ φ t hφ hA
(by intro _ _ _ h; nomatch h)
(by intro _ _ _ _ _ _ _ _ h; nomatch h)]
exact vTransp_pi _ _ _ _ _ hφ hA
/-- Stuck fallback theorem. In our current `CType` universe
`{univ, pi, path, glue}`, this never actually fires in practice: `univ`
always has `dimAbsent = true`, so the const case wins; `pi` is handled
by `eval_transp_pi`; `path` is handled by `eval_transp_path`; `glue` is
handled by the dedicated Glue-transport axioms in `Glue.lean`. Kept
here for parity with `vTransp_stuck` and future CType extensions. -/
theorem eval_transp_stuck (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (t : CTerm)
(hφ : φ ≠ .top)
(hA : CType.dimAbsent i A = false)
(h_not_pi : ∀ domA codA, A ≠ .pi domA codA)
(h_not_path : ∀ A₀ a b, A ≠ .path A₀ a b)
(h_not_glue : ∀ φG T f fInv sec ret coh Ai,
A ≠ .glue φG T f fInv sec ret coh Ai) :
eval env (.transp i A φ t) =
.vneu (.ntransp i A φ (eval env t)) := by
rw [eval_transp_nonpath env i A φ t hφ hA h_not_path h_not_glue]
exact vTransp_stuck _ _ _ _ hφ hA h_not_pi
/-- **T5 at eval level**: face-formula congruence for transport. When
two face formulas are semantically equal — i.e. `φ.eval env =
ψ.eval env` for every env — transport under them computes the same
result.
This is a claim about Rust's evaluator: it inspects face formulas
only through their truth-value semantics, not their syntactic form.
Two faces that classify the same set of environments produce the
same transport reduction, regardless of their concrete tree
structure (e.g. `(i=0) ∧ ` vs `(i=0)`).
Stated at the eval level (rather than augmenting the partial def
with a face-normalisation prepass) so the existing eval-axiom set is
not re-audited. The Rust backend's discharge: a face-normalisation
routine ensures syntactically distinct but semantically equal faces
take the same dispatch branch. -/
axiom eval_transp_face_congr (env : CEnv) (i : DimVar) (A : CType)
(φ ψ : FaceFormula) (t : CTerm)
(h : ∀ ε, φ.eval ε = ψ.eval ε) :
eval env (.transp i A φ t) = eval env (.transp i A ψ t)
/-!
### `eval` on `.comp` — four disjoint cases
The old coarse `eval_comp` (which asserted the stuck form unconditionally)
is replaced by four case-axioms mirroring the arms of `vCompAtTerm`. The
cases are disjoint by precondition, so the axiom set is consistent.
-/
/-- **C1 at eval level**: with a full face, heterogeneous composition
reduces to the system body substituted at `i := 1`. Crucially this
is *term-level* substitution, not `vPApp` on the evaluated body —
`u` may be e.g. a free variable whose value doesn't look like a
function. -/
axiom eval_comp_top (env : CEnv) (i : DimVar) (A : CType) (u t : CTerm) :
eval env (.comp i A .top u t) = eval env (u.substDim i .one)
/-- **C2 at eval level**: with an empty face, the system contributes
nothing and composition reduces to plain transport. -/
axiom eval_comp_bot (env : CEnv) (i : DimVar) (A : CType) (u t : CTerm) :
eval env (.comp i A .bot u t) = eval env (.transp i A .bot t)
/-- **Constant-line comp = hcomp**: when the type `A` doesn't vary along
`i`, heterogeneous composition reduces to homogeneous composition on
the (fixed) type `A`. The tube is `.plam i u` — the system body `u`
packaged as a dim-closure over the line binder. -/
axiom eval_comp_const (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (u t : CTerm)
(hφ₁ : φ ≠ .top) (hφ₂ : φ ≠ .bot)
(hA : CType.dimAbsent i A = true) :
eval env (.comp i A φ u t) =
vHCompValue A φ (eval env (.plam i u)) (eval env t)
/-- **Heterogeneous Π comp**: when A is a pi type with a genuinely-varying
line, `vCompAtTerm` packages the comp into a `vCompFun` closure that
will run the CCHM β-rule when the function is applied. -/
axiom eval_comp_pi (env : CEnv) (i : DimVar) (domA codA : CType)
(φ : FaceFormula) (u t : CTerm)
(hφ₁ : φ ≠ .top) (hφ₂ : φ ≠ .bot)
(hA : CType.dimAbsent i (.pi domA codA) = false) :
eval env (.comp i (.pi domA codA) φ u t) =
.vCompFun env i domA codA φ u t
/-- Stuck fallback: `.comp` whose face is neither `.top` nor `.bot`, whose
line genuinely varies, and whose type is neither `.pi` nor a constant
produces a neutral. -/
axiom eval_comp_stuck (env : CEnv) (i : DimVar) (A : CType)
(φ : FaceFormula) (u t : CTerm)
(hφ₁ : φ ≠ .top) (hφ₂ : φ ≠ .bot)
(hA : CType.dimAbsent i A = false)
(h_not_pi : ∀ domA codA, A ≠ .pi domA codA) :
eval env (.comp i A φ u t) =
.vneu (.ncomp i A φ (eval env u) (eval env t))
/-- `eval` on `.compN` delegates to `vCompNAtTerm`. -/
axiom eval_compN (env : CEnv) (i : DimVar) (A : CType)
(clauses : List (FaceFormula × CTerm)) (t : CTerm) :
eval env (.compN i A clauses t) = vCompNAtTerm env i A clauses t
/-!
### `vHCompValue` — three disjoint cases
-/
/-- Homogeneous composition under a full face: the tube covers everything,
and the result is the tube evaluated at `1`. -/
axiom vHCompValue_top (A : CType) (tube base : CVal) :
vHCompValue A .top tube base = vPApp tube .one
/-- **CCHM Π hcomp rule**: homogeneous composition on a Π type produces
a `vHCompFun` closure that applies pointwise when its function is
applied to an argument. `domA` is stored in the type but unused in
the resulting closure because hcomp on the domain is trivial (A is
fixed, not varying). -/
axiom vHCompValue_pi (domA codA : CType) (φ : FaceFormula) (tube base : CVal)
(hφ : φ ≠ .top) :
vHCompValue (.pi domA codA) φ tube base = .vHCompFun codA φ tube base
/-- Stuck fallback: hcomp on a non-pi type under a non-top face. Uses
`nhcomp` (separate from `ncomp` because hcomp's type is fixed). -/
axiom vHCompValue_stuck (A : CType) (φ : FaceFormula) (tube base : CVal)
(hφ : φ ≠ .top)
(h_not_pi : ∀ domA codA, A ≠ .pi domA codA) :
vHCompValue A φ tube base = .vneu (.nhcomp A φ tube base)
-- Reduction lemmas for `vApp`.
axiom vApp_vlam (env : CEnv) (x : String) (body : CTerm) (arg : CVal) :
vApp (.vlam env x body) arg = eval (env.extend x arg) body
axiom vApp_vneu (n : CNeu) (arg : CVal) :
vApp (.vneu n) arg = .vneu (.napp n arg)
/-- Full CCHM Π β-rule at the value level: applying a transported-function
closure to an argument `arg` inversely transports `arg` through the
domain, applies the underlying function `f`, and forward-transports the
result through the codomain.
When `CType.dimAbsent i domA = true`, `vTranspInv` reduces to identity
(by `vTranspInv_const`) and this specialises to the simpler
const-domain rule `vTransp i codA φ (vApp f arg)`. -/
axiom vApp_vTranspFun (i : DimVar) (domA codA : CType) (φ : FaceFormula)
(f : CVal) (arg : CVal) :
vApp (.vTranspFun i domA codA φ f) arg =
vTransp i codA φ (vApp f (vTranspInv i domA φ arg))
/-- **CCHM Π hcomp β-rule** at the value level: applying a homogeneously
composed function closure to `arg` yields hcomp on the codomain with:
· tube = `λj. (tube @ j) arg` (represented by `vTubeApp`),
· base = `base arg`.
No inverse transport — hcomp's type is fixed, so the argument passes
through unchanged. -/
axiom vApp_vHCompFun (codA : CType) (φ : FaceFormula) (tube base arg : CVal) :
vApp (.vHCompFun codA φ tube base) arg =
vHCompValue codA φ (.vTubeApp tube arg) (vApp base arg)
/-- **Full CCHM Π hetero comp β-rule**: applying `comp^i (pi A B) φ u u₀` to
`y : A(1)` unfolds via the *fill* construction. For a fresh dim `$fj`
and argument name `$y`:
· `y_at_i = transp^{$fj} (A[i := inv $fj i]) φ y` — fill from A(1) down to A(i).
· `y_at_0 = transp^{$fj} (A[i := inv $fj]) φ y` — reversed-line transport A(1) → A(0).
The result is `comp^i B(i) φ (u (y_at_i)) (u₀ (y_at_0))` evaluated in
an env extending `$y` with the argument.
When `CType.dimAbsent i domA = true`, both fills degenerate to `y`
(by T2 on the constant line), recovering the simpler const-domain
formula `comp^i B(i) φ (u y) (u₀ y)`.
Hygiene assumption: `$y` is not a user variable in `env`, and `$fj`
is not a user DimVar in `domA`, `codA`, `φ`, `u`, `t`. These reserved
names are chosen to minimise collision probability. -/
axiom vApp_vCompFun (env : CEnv) (i : DimVar) (domA codA : CType)
(φ : FaceFormula) (u t : CTerm) (arg : CVal) :
vApp (.vCompFun env i domA codA φ u t) arg =
eval (env.extend "$y" arg) (.comp i codA φ
(.app u (.transp ⟨"$fj"⟩
(domA.substDimExpr i (.join (.inv (.var ⟨"$fj"⟩)) (.var i)))
φ (.var "$y")))
(.app t (.transp ⟨"$fj"⟩
(domA.substDimExpr i (.inv (.var ⟨"$fj"⟩)))
φ (.var "$y"))))
-- Reduction lemmas for `vPApp`.
axiom vPApp_vplam (env : CEnv) (i : DimVar) (body : CTerm) (r : DimExpr) :
vPApp (.vplam env i body) r = eval env (body.substDim i r)
axiom vPApp_vneu (n : CNeu) (r : DimExpr) :
vPApp (.vneu n) r = .vneu (.npapp n r)
/-- `vTubeApp tube arg` under dim application reduces to `(tube @ r) arg`.
Encodes the semantic meaning of `λj. (tube @ j) arg`. -/
axiom vPApp_vTubeApp (tube arg : CVal) (r : DimExpr) :
vPApp (.vTubeApp tube arg) r = vApp (vPApp tube r) arg
/-!
### `vCompNAtTerm` — compound equation mirroring the partial-def arms
Single axiom exposing the full case analysis so that derived theorems can
pattern-match on the clause list's structure. -/
axiom vCompNAtTerm_def (env : CEnv) (i : DimVar) (A : CType)
(clauses : List (FaceFormula × CTerm)) (t : CTerm) :
vCompNAtTerm env i A clauses t =
match clauses.find?
(fun ⟨φ, _⟩ => match φ with | .top => true | _ => false) with
| some ⟨_, u⟩ => eval env (u.substDim i .one)
| none =>
let live := clauses.filter
(fun ⟨φ, _⟩ => match φ with | .bot => false | _ => true)
match live with
| [] => eval env (.transp i A .bot t)
| [⟨φ, u⟩] => vCompAtTerm env i A φ u t
| _ => .vneu (.ncompN env i A
(live.map (fun ⟨φ, u⟩ => (φ, eval env u)))
(eval env t))
/-!
### Path transport endpoint reductions
Applied at `.zero`, the CCHM `(j=0)` clause fires, giving `a(1)`.
Applied at `.one`, the `(j=1)` clause fires, giving `b(1)`.
Applied at any other DimExpr, the multi-clause comp can't be computed
without machinery we haven't built yet — stalls at `npathTranspApp`.
The three axioms are disjoint by the shape of the DimExpr argument.
-/
/-- Path transport at left endpoint: result is `a(1)`, i.e. `a` evaluated
with `i` substituted by `.one`. This is *not* a transport of `a(0)` —
it's the line's specified endpoint at `i=1`, made forced by CCHM's
multi-clause `(j=0)` constraint. -/
axiom vPApp_vPathTransp_zero
(env : CEnv) (i : DimVar) (A : CType) (a b : CTerm) (φ : FaceFormula)
(p : CTerm) :
vPApp (.vPathTransp env i A a b φ p) .zero =
eval env (a.substDim i .one)
/-- Path transport at right endpoint: result is `b(1)`. -/
axiom vPApp_vPathTransp_one
(env : CEnv) (i : DimVar) (A : CType) (a b : CTerm) (φ : FaceFormula)
(p : CTerm) :
vPApp (.vPathTransp env i A a b φ p) .one =
eval env (b.substDim i .one)
/-- **Full CCHM path transport at a generic dim**: apply the path
transport at `r` by evaluating the CCHM multi-clause comp
`comp^i A [φ ↦ p@r, (r=0) ↦ a, (r=1) ↦ b] (p@r)` via `vCompNAtTerm`.
This genuinely unsticks when `r`'s face substitution simplifies:
· `r = .zero` → `(r=0)` becomes `.top`, firing the left clause → `a(1)`.
· `r = .one` → `(r=1)` becomes `.top`, firing the right clause → `b(1)`.
· `r = .var k` generic → both clauses are non-trivial; stalls at a
structured `ncompN` neutral that can still unstick if `k` later
becomes an endpoint. -/
axiom vPApp_vPathTransp_general
(env : CEnv) (i : DimVar) (A : CType) (a b : CTerm) (φ : FaceFormula)
(p : CTerm) (r : DimExpr)
(h_zero : r ≠ .zero) (h_one : r ≠ .one) :
vPApp (.vPathTransp env i A a b φ p) r =
vCompNAtTerm env i A
[ (φ, .papp p r)
, (FaceFormula.dimExprEq0 r, a)
, (FaceFormula.dimExprEq1 r, b) ]
(.papp p r)
/-!
### `eval` on `.glueIn` — three disjoint cases
`glueIn [φ ↦ t] a` evaluates differently on/off/between the face `φ`:
· `φ = .top`: the face is full, the result is `t` (only the T-side
matters).
· `φ = .bot`: the face is empty, the result is `a` (only the A-side
matters).
· Otherwise: a structurally stuck `nglueIn` neutral preserving both
face-on and face-off values — later dim substitution into `φ` may
collapse it.
The three cases are mutually exclusive by precondition. -/
/-- (1) Full-face glueIn reduces to the T-side. -/
axiom eval_glueIn_top (env : CEnv) (t a : CTerm) :
eval env (.glueIn .top t a) = eval env t
/-- (2) Empty-face glueIn reduces to the A-side. -/
axiom eval_glueIn_bot (env : CEnv) (t a : CTerm) :
eval env (.glueIn .bot t a) = eval env a
/-- (3) Neutral-face glueIn produces an `nglueIn` stuck neutral preserving
both evaluated sides. The face formula is kept syntactic so that
later dim substitution can resolve it to `.top` or `.bot`.
The `h_not_unglue` precondition rules out the η-redex shape
`.glueIn φ t (.unglue φ f g)`, which reduces via
`eval_glueIn_of_unglue` to `eval env g` instead of a stuck form.
Without this restriction, the stuck rule and the η-rule would
disagree on a common instance. -/
axiom eval_glueIn_stuck (env : CEnv) (φ : FaceFormula) (t a : CTerm)
(hφ₁ : φ ≠ .top) (hφ₂ : φ ≠ .bot)
(h_not_unglue : ∀ f g, a ≠ .unglue φ f g) :
eval env (.glueIn φ t a) = .vneu (.nglueIn φ (eval env t) (eval env a))
/-!
### `eval` on `.unglue` — three disjoint cases
`unglue [φ ↦ f] g` — extracting an A-value from a glued term:
· `φ = .top`: apply the forward map: `vApp (eval f) (eval g)`.
· `φ = .bot`: identity. `glueIn .bot t a` reduces to `a`, so the
argument is already an A-value; unglue is the identity there.
· Otherwise: stuck `nunglue` preserving `f` and `g`.
All three cases are mutually exclusive. -/
/-- (1) Full-face unglue: apply the forward map pointwise. -/
axiom eval_unglue_top (env : CEnv) (f g : CTerm) :
eval env (.unglue .top f g) = vApp (eval env f) (eval env g)
/-- (2) Empty-face unglue: identity on `g`. This is the definitional
content of `Glue [bot ↦ (T, e)] A = A`: values are already A-values. -/
axiom eval_unglue_bot (env : CEnv) (f g : CTerm) :
eval env (.unglue .bot f g) = eval env g
/-- (3) Neutral-face unglue: produce a stuck `nunglue` neutral preserving
`f` and `g`. Later dim substitution into `φ` may resolve it to
`.top` (→ forward-map application) or `.bot` (→ identity).
The `h_not_glueIn` precondition rules out the β-redex shape
`.unglue φ f (.glueIn φ t a)`, which reduces via
`eval_unglue_of_glueIn` to `eval env a` under the overlap
condition. Without this restriction, the stuck rule and the
β-rule would disagree on a common instance. -/
axiom eval_unglue_stuck (env : CEnv) (φ : FaceFormula) (f g : CTerm)
(hφ₁ : φ ≠ .top) (hφ₂ : φ ≠ .bot)
(h_not_glueIn : ∀ t a, g ≠ .glueIn φ t a) :
eval env (.unglue φ f g) = .vneu (.nunglue φ (eval env f) (eval env g))
/-!
### Glue β- and η-rules (eval level)
The β-rule (`unglue · glueIn`) and η-rule (`glueIn · unglue`) on
matching face formulas fire *regardless* of whether `φ` is `.top`,
`.bot`, or stuck. On `.top` / `.bot` the existing face-dispatched
axioms reduce to the same result under the overlap condition
(consistency check below); on stuck faces, these rules are the only
way the redex reduces — `eval_unglue_stuck` and `eval_glueIn_stuck`
carry `h_not_glueIn` / `h_not_unglue` preconditions to avoid conflict.
The `h_overlap` condition encodes the CCHM typing-side constraint
`e.f t = a` (or `t = e.f g`) that accompanies a well-typed glueIn /
unglue — the evaluator assumes it and short-circuits.
-/
/-- β-rule: `unglue φ f (glueIn φ t a)` reduces to `a` under the
overlap condition. Rust-discharge: the evaluator recognises the
nested pattern and short-circuits when the overlap invariant holds
(typing guarantees it). -/
axiom eval_unglue_of_glueIn (env : CEnv) (φ : FaceFormula) (f t a : CTerm)
(h_overlap : eval env (.app f t) = eval env a) :
eval env (.unglue φ f (.glueIn φ t a)) = eval env a
/-- η-rule: `glueIn φ t (unglue φ f g)` reduces to `g` under the
overlap condition. Rust-discharge: dual to `eval_unglue_of_glueIn`. -/
axiom eval_glueIn_of_unglue (env : CEnv) (φ : FaceFormula) (f t g : CTerm)
(h_overlap : eval env t = eval env (.app f g)) :
eval env (.glueIn φ t (.unglue φ f g)) = eval env g
/-!
### `eval` on Σ constructors — three arms
`.pair a b` evaluates component-wise to `.vpair`; `.fst t` / `.snd t`
project through `vFst` / `vSnd`, which themselves β-reduce on `vpair`
and produce stuck `.nfst` / `.nsnd` on neutrals.
-/
/-- Pair construction evaluates component-wise. -/
axiom eval_pair (env : CEnv) (a b : CTerm) :
eval env (.pair a b) = .vpair (eval env a) (eval env b)
/-- First projection delegates to `vFst`. -/
axiom eval_fst (env : CEnv) (t : CTerm) :
eval env (.fst t) = vFst (eval env t)
/-- Second projection delegates to `vSnd`. -/
axiom eval_snd (env : CEnv) (t : CTerm) :
eval env (.snd t) = vSnd (eval env t)
/-- β-rule for `vFst` on a pair. -/
axiom vFst_vpair (a b : CVal) : vFst (.vpair a b) = a
/-- β-rule for `vSnd` on a pair. -/
axiom vSnd_vpair (a b : CVal) : vSnd (.vpair a b) = b
/-- `vFst` on a neutral produces a stuck `nfst` neutral. -/
axiom vFst_vneu (n : CNeu) : vFst (.vneu n) = .vneu (.nfst n)
/-- `vSnd` on a neutral produces a stuck `nsnd` neutral. -/
axiom vSnd_vneu (n : CNeu) : vSnd (.vneu n) = .vneu (.nsnd n)