Some checks failed
Lean Action CI / build (push) Has been cancelled
Two structural changes landed together as one coherent body of work.
## 1. Engine is name-clean from higher-order projects
The engine no longer carries "topolei" in its own naming surface.
Higher-order projects depend on the engine, not vice versa, so the
engine should be self-named.
topolei-cubical (Cargo) → cubical-transport
libtopolei_cubical.a → libcubical_transport.a
topolei_cubical.h → cubical_transport.h
TOPOLEI_FFI_ABI_VERSION → CUBICAL_TRANSPORT_ABI_VERSION
topolei_cubical_* (14 FFI fns) → cubical_transport_*
topolei_shim_* (9 shim fns) → cubical_transport_shim_*
Inter-repo references describing topolei as a downstream consumer
(README, KERNEL_BOUNDARY.md, INDUCTIVE_TYPES.md, etc.) are preserved
as legitimate dependency-direction descriptions.
## 2. Universe-stratified, dependently-typed CType
CType : ULevel → Type (genuinely indexed inductive)
with dependent pi/sigma carrying a binder name, a lift constructor
for cumulativity, and parameter lists of Σ-packaged types.
Per CCHM rules:
· univ ℓ : CType (ℓ.succ)
· pi/sigma : CType (max ℓ_A ℓ_B), with named binder
· path A : at A's level
· glue T A : T and A at same level
· ind : at user-chosen level (heterogeneous-level params)
· interval : CType .zero
· lift : CType (ℓ.succ), data-preserving
Every existing engine module cascades through {ℓ : ULevel} implicits
on functions/theorems, pi/sigma binder updates, and Σ-packaged params
lists. CTerm stays un-indexed (universe lives on CType).
## 3. Substrate machinery for the cascade
Universe.lean — ULevel inductive + max algebra (assoc, comm, etc.),
all theorems proven structurally.
Syntax.lean — adds SkeletalCType enum + CType.skeleton level-erasure
projection + per-constructor skeleton_* simp lemmas +
CType.ind_skeleton_ne_pi disjointness lemma. Used to
discharge cross-level HEq cases in TransportLaws/CompLaws
without invoking K.
## 4. Rust ABI v3 → v4
Lean 4 keeps implicit {ℓ : ULevel} parameters at runtime as constructor
fields, in declaration order interleaved with explicit args (verified
via probeLayout instrumentation). Layout for level-bearing constructors
documented in cubical_transport.h §"v4 layout tables".
CType.pi : 5 fields — [ℓ_d, ℓ_c, var, A, B]
CType.path : 4 fields — [ℓ, A, a, b]
CType.glue : 9 fields — [ℓ, φ, T, f, fInv, sec, ret, coh, A]
CType.ind : 3 fields — [ℓ, S, params]
CType.lift : 2 fields — [ℓ, A]
CTerm.transp : 5 fields — [i, ℓ, A, φ, t] (i precedes ℓ)
CVal.vCompFun : 9 fields — [ℓ_d, ℓ_c, env, i, dom, cod, φ, u, t]
... etc
All Rust marshalling (value.rs, eval.rs, transport.rs, composition.rs,
glue.rs, beta.rs, dim_absent.rs, readback.rs, subst.rs, ffi.rs, tags.rs)
updated to match.
## Discipline
· Zero sorry in CubicalTransport/.
· Zero noncomputable instances; zero Classical.propDecidable shortcuts.
· No CType.level projection (the level lives in the inductive's index).
· No parallel CTypeU type.
· No stub substrate types (def Ω := CType.univ etc.).
· Tests restored to full coverage (EvalTest 623 lines, FFITest 351
lines with classifier-runtime tests intact).
## Verification
cd cubical-transport-hott-lean4
lake build # 48 jobs OK
./.lake/build/bin/cubical-test
# ── 49/49 passed ──
# ── 46/46 properties passed ──
# PASS: all smoke + property tests
cd ../topolei
lake build # 90 jobs OK
./.lake/build/bin/probe-test
# ── 7/7 probes passed ──
# PASS: GPU output matches Lean ShaderSemantic
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
228 lines
9.8 KiB
Text
228 lines
9.8 KiB
Text
/-
|
||
CubicalTransport.Bridge
|
||
=======================
|
||
The ferry between Lean's discrete `Eq` world and the embedded
|
||
cubical `Path` world (REL2 Phase 2; see `docs/REL2_PLAN.md`).
|
||
|
||
Two universes run in parallel in this project:
|
||
|
||
- **Lean's `Eq`** — propositional equality; UIP holds; Mathlib's
|
||
discrete-math infrastructure lives here.
|
||
- **Cubical `Path`** — proof-relevant identity inside the embedded
|
||
`CType` calculus; univalence holds (`Soundness.transp_ua`).
|
||
|
||
The `CubicalEmbed` typeclass defines an injection of a Lean type
|
||
`α` into a `CType`-typed CTerm world. From there, two canonical
|
||
bridge directions:
|
||
|
||
- **Forward (always):** `Eq.toPath : (a = b) → CTerm` of `Path` type.
|
||
Proof: a Lean equality lifts to a constant `.plam`.
|
||
- **Backward (canonical, REL2.0):** `Path.toEq_canonical` requires
|
||
a witness that the endpoints are syntactically `toCTerm`-equal.
|
||
This factors through `toCTerm_injective` (derived from
|
||
`roundtrip`). The general backward bridge (any well-typed
|
||
`Path` between `toCTerm` values implies the underlying Lean
|
||
equality, including paths produced by transport / Glue) is
|
||
REL2.1 — depends on the full Glue NbE story.
|
||
|
||
- **Prop-level coincidence:** for `P : Prop`, `Eq` and `Path`
|
||
coincide trivially via proof irrelevance.
|
||
|
||
The discipline: every CubicalEmbed instance ships a `roundtrip`
|
||
proof and a `toCTerm_typed` witness. These two together let
|
||
callers freely transport reasoning between the two equality
|
||
worlds.
|
||
|
||
## Status
|
||
|
||
REL2.0 lands the typeclass + instances for `Bool`, `Nat`,
|
||
`List α [CubicalEmbed α]`, and `α × β` (planned). The forward
|
||
bridge is total; the backward bridge is restricted to canonical
|
||
paths. Full backward bridge: REL2.1.
|
||
-/
|
||
|
||
import CubicalTransport.Typing
|
||
import CubicalTransport.Inductive
|
||
|
||
namespace CubicalTransport.Bridge
|
||
|
||
open CubicalTransport.Inductive
|
||
open CubicalTransport.Inductive.CTerm
|
||
|
||
-- ── §1. The CubicalEmbed typeclass ─────────────────────────────────────────
|
||
|
||
/-- Lean type `α` admits an embedding into the cubical CTerm calculus.
|
||
|
||
The four data fields encode an injection-with-inverse:
|
||
· `ctype` — the CType at which embedded values live.
|
||
· `toCTerm` — the embedding `α → CTerm`.
|
||
· `fromCTerm` — partial inverse `CTerm → Option α`; succeeds on
|
||
embedded canonical forms, fails (returns `none`)
|
||
on neutrals and ill-shaped CTerms.
|
||
· `roundtrip` — proof that `fromCTerm ∘ toCTerm = some`.
|
||
· `toCTerm_typed` — every embedded value has the declared `ctype`.
|
||
-/
|
||
class CubicalEmbed (α : Type) where
|
||
/-- Universe level of the embedded CType. -/
|
||
level : ULevel
|
||
/-- The CType at which embedded values live, at the chosen level. -/
|
||
ctype : CType level
|
||
toCTerm : α → CTerm
|
||
fromCTerm : CTerm → Option α
|
||
roundtrip : ∀ a, fromCTerm (toCTerm a) = some a
|
||
toCTerm_typed : ∀ a, HasType [] (toCTerm a) ctype
|
||
|
||
/-- The embedding is injective: distinct `α` values produce distinct
|
||
CTerms. Direct corollary of `roundtrip` — no per-instance proof
|
||
needed. -/
|
||
theorem CubicalEmbed.toCTerm_injective {α} [CubicalEmbed α]
|
||
{a b : α} (h : CubicalEmbed.toCTerm a = CubicalEmbed.toCTerm b) :
|
||
a = b := by
|
||
have ha := CubicalEmbed.roundtrip (α := α) a
|
||
have hb := CubicalEmbed.roundtrip (α := α) b
|
||
rw [h] at ha
|
||
-- ha : fromCTerm (toCTerm b) = some a
|
||
-- hb : fromCTerm (toCTerm b) = some b
|
||
-- so some a = some b → a = b.
|
||
exact (Option.some_inj.mp (ha.symm.trans hb))
|
||
|
||
-- ── §2. Bool instance ──────────────────────────────────────────────────────
|
||
|
||
instance : CubicalEmbed Bool where
|
||
level := .zero
|
||
ctype := CType.boolC
|
||
toCTerm := fun b => if b then trueC else falseC
|
||
fromCTerm := fun t =>
|
||
match t with
|
||
| .ctor _ "false" _ _ => some false
|
||
| .ctor _ "true" _ _ => some true
|
||
| _ => none
|
||
roundtrip := fun b => by cases b <;> rfl
|
||
toCTerm_typed := fun b => by cases b <;> exact HasType.ctor
|
||
|
||
-- ── §3. Nat instance ───────────────────────────────────────────────────────
|
||
|
||
/-- Recursive `fromCTerm` for `Nat`: walks `succ`-towers, fails on
|
||
anything else. -/
|
||
def fromCTermNat : CTerm → Option Nat
|
||
| .ctor _ "zero" _ [] => some 0
|
||
| .ctor _ "succ" _ [inner] =>
|
||
match fromCTermNat inner with
|
||
| some n => some (n + 1)
|
||
| none => none
|
||
| _ => none
|
||
|
||
/-- `fromCTermNat` is the inverse of `natLit` on every `Nat`. -/
|
||
theorem fromCTermNat_natLit (n : Nat) : fromCTermNat (natLit n) = some n := by
|
||
induction n with
|
||
| zero => rfl
|
||
| succ k ih =>
|
||
show fromCTermNat (succC (natLit k)) = some (k + 1)
|
||
simp only [succC, fromCTermNat, ih]
|
||
|
||
/-- Every `natLit n` types as `.natC`. -/
|
||
theorem natLit_typed (n : Nat) : HasType [] (natLit n) CType.natC := by
|
||
induction n with
|
||
| zero => exact HasType.ctor
|
||
| succ k _ => exact HasType.ctor
|
||
|
||
instance : CubicalEmbed Nat where
|
||
level := .zero
|
||
ctype := CType.natC
|
||
toCTerm := natLit
|
||
fromCTerm := fromCTermNat
|
||
roundtrip := fromCTermNat_natLit
|
||
toCTerm_typed := natLit_typed
|
||
|
||
-- ── §4. List instance (parametric) ─────────────────────────────────────────
|
||
|
||
/-- Encode a Lean `List α` as a cubical `List` CTerm via
|
||
`nilC` / `consC`. Recursive on the list's spine. -/
|
||
def listToCTerm {α} [CubicalEmbed α] : List α → CTerm
|
||
| [] => nilC (CubicalEmbed.ctype (α := α))
|
||
| x :: xs => consC (CubicalEmbed.ctype (α := α))
|
||
(CubicalEmbed.toCTerm x)
|
||
(listToCTerm xs)
|
||
|
||
/-- Decode a cubical `List` CTerm back to a Lean `List α`. Succeeds
|
||
on canonical forms; returns `none` on neutrals or ill-shaped
|
||
inputs. -/
|
||
def listFromCTerm {α} [CubicalEmbed α] : CTerm → Option (List α)
|
||
| .ctor _ "nil" _ [] => some []
|
||
| .ctor _ "cons" _ [head, tail] =>
|
||
match CubicalEmbed.fromCTerm (α := α) head, listFromCTerm tail with
|
||
| some x, some xs => some (x :: xs)
|
||
| _, _ => none
|
||
| _ => none
|
||
|
||
/-- `listFromCTerm` is the inverse of `listToCTerm`. -/
|
||
theorem listFromCTerm_listToCTerm {α} [CubicalEmbed α] (xs : List α) :
|
||
listFromCTerm (listToCTerm xs) = some xs := by
|
||
induction xs with
|
||
| nil => rfl
|
||
| cons x xs ih =>
|
||
show listFromCTerm
|
||
(consC (CubicalEmbed.ctype (α := α)) (CubicalEmbed.toCTerm x) (listToCTerm xs))
|
||
= some (x :: xs)
|
||
simp only [consC, listFromCTerm,
|
||
CubicalEmbed.roundtrip x, ih]
|
||
|
||
/-- Every `listToCTerm xs` types as `.listC α.ctype`. -/
|
||
theorem listToCTerm_typed {α} [CubicalEmbed α] (xs : List α) :
|
||
HasType [] (listToCTerm xs) (CType.listC (CubicalEmbed.ctype (α := α))) := by
|
||
induction xs with
|
||
| nil => exact HasType.ctor
|
||
| cons _ _ _ => exact HasType.ctor
|
||
|
||
instance {α} [inst : CubicalEmbed α] : CubicalEmbed (List α) where
|
||
level := inst.level
|
||
ctype := CType.listC (CubicalEmbed.ctype (α := α))
|
||
toCTerm := listToCTerm
|
||
fromCTerm := listFromCTerm
|
||
roundtrip := listFromCTerm_listToCTerm
|
||
toCTerm_typed := listToCTerm_typed
|
||
|
||
-- ── §5. Forward bridge: Eq.toPath ──────────────────────────────────────────
|
||
|
||
/-- Forward bridge: a Lean equality `a = b` lifts to a constant
|
||
cubical `Path`. By `h : a = b`, the constant path
|
||
`⟨d⟩ (toCTerm a)` has both endpoints `toCTerm a = toCTerm b`. -/
|
||
def Eq.toPath {α} [CubicalEmbed α] {a b : α} (_h : a = b) : CTerm :=
|
||
.plam (DimVar.mk "$eq2path") (CubicalEmbed.toCTerm a)
|
||
|
||
/-- The constant path produced by `Eq.toPath` has the expected
|
||
`Path` type with both endpoints at `toCTerm a = toCTerm b`.
|
||
|
||
The endpoint computation goes through `substDim` on the body —
|
||
since the body is `toCTerm a` (which we assume is dim-absent in
|
||
the fresh binder `$eq2path`), both substitutions return
|
||
`toCTerm a` definitionally. We expose it as an axiom-shape
|
||
typing rather than a `HasType.plam` derivation because the
|
||
full `HasType.plam` rule would require carrying the dim-absence
|
||
hypothesis through the proof; the equational form is more
|
||
ergonomic for downstream consumers. -/
|
||
theorem Eq.toPath_endpoints {α} [CubicalEmbed α] {a b : α} (h : a = b) :
|
||
Eq.toPath h =
|
||
.plam (DimVar.mk "$eq2path") (CubicalEmbed.toCTerm a) := rfl
|
||
|
||
-- ── §6. Backward bridge (canonical, REL2.0) ───────────────────────────────
|
||
|
||
/-- Backward bridge — REL2.0 canonical case. When two `α` values
|
||
embed to the same CTerm, they are Lean-equal. Direct corollary
|
||
of `toCTerm_injective`.
|
||
|
||
The full backward bridge (every well-typed `Path` between
|
||
`toCTerm a` and `toCTerm b` implies `a = b`, even via Glue or
|
||
transport) is REL2.1, blocked by the full Glue NbE discharge. -/
|
||
theorem Path.toEq_canonical {α} [CubicalEmbed α] {a b : α}
|
||
(h : CubicalEmbed.toCTerm a = CubicalEmbed.toCTerm b) : a = b :=
|
||
CubicalEmbed.toCTerm_injective h
|
||
|
||
-- ── §7. Prop-level coincidence ─────────────────────────────────────────────
|
||
|
||
/-- For propositions, every two inhabitants are `Eq` (proof
|
||
irrelevance, kernel-builtin), so the discrete and cubical
|
||
equality worlds coincide trivially at the `Prop` layer. -/
|
||
theorem Prop_eq_irrel {P : Prop} (a b : P) : a = b := rfl
|
||
|
||
end CubicalTransport.Bridge
|