cubical-transport-hott-lean4/CubicalTransport/Face.lean
Maximus Gorog 19928d040a
Some checks failed
Lean Action CI / build (push) Has been cancelled
REL2 universe stratification + topolei naming cleanup + Rust ABI v4
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>
2026-05-04 00:21:14 -06:00

543 lines
22 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.

/-
CubicalTransport.Face
====================
The face lattice F: distributive lattice generated by (i=0) and (i=1)
with the fundamental relation (i=0) ∧ (i=1) = 0_F.
Reference: CCHM §4.1
Grammar: ϕ, ψ ::= 0_F | 1_F | (i=0) | (i=1) | ϕ∧ψ | ϕ∨ψ
Face formulas describe which sub-face of a cube we are on.
The key invariant: (i=0) and (i=1) are mutually exclusive and jointly exhaustive.
-/
import CubicalTransport.Interval
-- ── Face formulas ─────────────────────────────────────────────────────────────
inductive FaceFormula where
| bot : FaceFormula -- 0_F
| top : FaceFormula -- 1_F
| eq0 (i : DimVar) : FaceFormula -- (i = 0)
| eq1 (i : DimVar) : FaceFormula -- (i = 1)
| meet (ϕ ψ : FaceFormula) : FaceFormula -- ϕ ∧ ψ
| join (ϕ ψ : FaceFormula) : FaceFormula -- ϕ ψ
deriving Repr, Inhabited, DecidableEq
-- ── Semantic evaluation ───────────────────────────────────────────────────────
def FaceFormula.eval (env : DimVar → Bool) : FaceFormula → Bool
| .bot => false
| .top => true
| .eq0 i => !(env i)
| .eq1 i => env i
| .meet ϕ ψ => ϕ.eval env && ψ.eval env
| .join ϕ ψ => ϕ.eval env || ψ.eval env
-- ── Fundamental laws ──────────────────────────────────────────────────────────
-- Strategy: simp only [eval] to unfold, then Bool case analysis.
-- Note: `show` is avoided here because `&&`/`||` bind looser than `=` in Lean 4,
-- causing parse surprises. `simp only [eval]` is cleaner and consistent.
namespace FaceFormula
-- (i=0) and (i=1) are mutually exclusive: their meet is always false
theorem eq0_meet_eq1 (i : DimVar) (env : DimVar → Bool) :
(meet (eq0 i) (eq1 i)).eval env = false := by
simp only [eval]
cases env i <;> rfl
-- They are jointly exhaustive: their join is always true
theorem eq0_join_eq1 (i : DimVar) (env : DimVar → Bool) :
(join (eq0 i) (eq1 i)).eval env = true := by
simp only [eval]
cases env i <;> rfl
-- Commutativity
theorem meet_comm (ϕ ψ : FaceFormula) (env : DimVar → Bool) :
(meet ϕ ψ).eval env = (meet ψ ϕ).eval env := by
simp only [eval]
cases ϕ.eval env <;> cases ψ.eval env <;> rfl
theorem join_comm (ϕ ψ : FaceFormula) (env : DimVar → Bool) :
(join ϕ ψ).eval env = (join ψ ϕ).eval env := by
simp only [eval]
cases ϕ.eval env <;> cases ψ.eval env <;> rfl
-- Associativity
theorem meet_assoc (ϕ ψ χ : FaceFormula) (env : DimVar → Bool) :
(meet ϕ (meet ψ χ)).eval env = (meet (meet ϕ ψ) χ).eval env := by
simp only [eval]
cases ϕ.eval env <;> cases ψ.eval env <;> cases χ.eval env <;> rfl
theorem join_assoc (ϕ ψ χ : FaceFormula) (env : DimVar → Bool) :
(join ϕ (join ψ χ)).eval env = (join (join ϕ ψ) χ).eval env := by
simp only [eval]
cases ϕ.eval env <;> cases ψ.eval env <;> cases χ.eval env <;> rfl
-- Identity laws
theorem meet_top (ϕ : FaceFormula) (env : DimVar → Bool) :
(meet ϕ top).eval env = ϕ.eval env := by
simp only [eval]
cases ϕ.eval env <;> rfl
theorem join_bot (ϕ : FaceFormula) (env : DimVar → Bool) :
(join ϕ bot).eval env = ϕ.eval env := by
simp only [eval]
cases ϕ.eval env <;> rfl
theorem meet_bot (ϕ : FaceFormula) (env : DimVar → Bool) :
(meet ϕ bot).eval env = false := by
simp only [eval]
cases ϕ.eval env <;> rfl
theorem join_top (ϕ : FaceFormula) (env : DimVar → Bool) :
(join ϕ top).eval env = true := by
simp only [eval]
cases ϕ.eval env <;> rfl
-- Distributivity
theorem distrib (ϕ ψ χ : FaceFormula) (env : DimVar → Bool) :
(meet ϕ (join ψ χ)).eval env = (join (meet ϕ ψ) (meet ϕ χ)).eval env := by
simp only [eval]
cases ϕ.eval env <;> cases ψ.eval env <;> cases χ.eval env <;> rfl
-- ── Face restriction ──────────────────────────────────────────────────────────
/-- Restrict a face formula by fixing dimension i to value b.
This is the substitution ϕ(i/b) from CCHM. -/
def restrict (i : DimVar) (b : Bool) : FaceFormula → FaceFormula
| .bot => .bot
| .top => .top
| .eq0 j => if j = i then (if b then .bot else .top) else .eq0 j
| .eq1 j => if j = i then (if b then .top else .bot) else .eq1 j
| .meet ϕ ψ => .meet (restrict i b ϕ) (restrict i b ψ)
| .join ϕ ψ => .join (restrict i b ϕ) (restrict i b ψ)
theorem eval_restrict (i : DimVar) (b : Bool) (ϕ : FaceFormula) (env : DimVar → Bool) :
(restrict i b ϕ).eval env =
ϕ.eval (fun j => if j = i then b else env j) := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 j =>
simp only [restrict, eval]
by_cases h : j = i
· subst h
cases b <;> simp [eval]
· simp only [if_neg h, eval]
| eq1 j =>
simp only [restrict, eval]
by_cases h : j = i
· subst h
cases b <;> simp [eval]
· simp only [if_neg h, eval]
| meet ϕ ψ ih_ϕ ih_ψ => simp only [restrict, eval, ih_ϕ, ih_ψ]
| join ϕ ψ ih_ϕ ih_ψ => simp only [restrict, eval, ih_ϕ, ih_ψ]
-- Endpoint restriction lemmas (used in composition algorithm)
theorem restrict_eq0_false (i : DimVar) (env : DimVar → Bool) :
(restrict i false (eq0 i)).eval env = true := by
simp [restrict, eval]
theorem restrict_eq0_true (i : DimVar) (env : DimVar → Bool) :
(restrict i true (eq0 i)).eval env = false := by
simp [restrict, eval]
theorem restrict_eq1_true (i : DimVar) (env : DimVar → Bool) :
(restrict i true (eq1 i)).eval env = true := by
simp [restrict, eval]
theorem restrict_eq1_false (i : DimVar) (env : DimVar → Bool) :
(restrict i false (eq1 i)).eval env = false := by
simp [restrict, eval]
-- ── Entailment ────────────────────────────────────────────────────────────────
/-- ϕ entails ψ: whenever ϕ holds, ψ holds. -/
def Entails (ϕ ψ : FaceFormula) : Prop :=
∀ env : DimVar → Bool, ϕ.eval env = true → ψ.eval env = true
theorem entails_refl (ϕ : FaceFormula) : Entails ϕ ϕ :=
fun _ h => h
theorem entails_trans (ϕ ψ χ : FaceFormula) :
Entails ϕ ψ → Entails ψ χ → Entails ϕ χ :=
fun h1 h2 env hϕ => h2 env (h1 env hϕ)
theorem entails_meet_left (ϕ ψ : FaceFormula) : Entails (meet ϕ ψ) ϕ :=
fun env h => by
simp only [eval] at h
cases hϕ : ϕ.eval env
· simp [hϕ] at h
· rfl
theorem entails_join_left (ϕ ψ : FaceFormula) : Entails ϕ (join ϕ ψ) :=
fun env h => by simp only [eval, h, Bool.true_or]
theorem entails_join_right (ϕ ψ : FaceFormula) : Entails ψ (join ϕ ψ) :=
fun env h => by simp only [eval, h, Bool.or_true]
-- ── Dimension absence ─────────────────────────────────────────────────────────
/-- Syntactic check: `i` does not appear in the face formula. -/
def dimAbsent (i : DimVar) : FaceFormula → Bool
| .bot => true
| .top => true
| .eq0 j => j != i
| .eq1 j => j != i
| .meet ϕ ψ => ϕ.dimAbsent i && ψ.dimAbsent i
| .join ϕ ψ => ϕ.dimAbsent i && ψ.dimAbsent i
-- ── DimExpr → FaceFormula: "the DimExpr equals 0 / 1" ────────────────────────
-- These translate a DimExpr r into a FaceFormula saying "r = 0" (or "r = 1").
-- Used by FaceFormula.substDim below: when we substitute a dim variable j
-- that appears inside `eq0 j` with a DimExpr r, the result should encode
-- "r = 0" as a face, which — for composite r — is itself a composite face.
--
-- De Morgan at the DimExpr ↔ FaceFormula boundary:
-- (meet r s = 0) ↔ (r = 0 s = 0) [meet zero: either factor is 0]
-- (join r s = 0) ↔ (r = 0 ∧ s = 0) [join zero: both factors are 0]
-- (inv r = 0) ↔ (r = 1)
-- Dually for "= 1".
mutual
/-- Build a FaceFormula expressing `r = 0` for an arbitrary DimExpr `r`. -/
def dimExprEq0 : DimExpr → FaceFormula
| .zero => .top
| .one => .bot
| .var k => .eq0 k
| .inv r => dimExprEq1 r
| .meet r s => .join (dimExprEq0 r) (dimExprEq0 s)
| .join r s => .meet (dimExprEq0 r) (dimExprEq0 s)
/-- Build a FaceFormula expressing `r = 1` for an arbitrary DimExpr `r`. -/
def dimExprEq1 : DimExpr → FaceFormula
| .zero => .bot
| .one => .top
| .var k => .eq1 k
| .inv r => dimExprEq0 r
| .meet r s => .meet (dimExprEq1 r) (dimExprEq1 s)
| .join r s => .join (dimExprEq1 r) (dimExprEq1 s)
end
-- ── Face formula substitution by DimExpr ─────────────────────────────────────
/-- Substitute a dimension variable `i` with an arbitrary `DimExpr r`
throughout a face formula. Uses `dimExprEq0`/`dimExprEq1` to encode
the composite faces arising from substitution into `eq0 j`/`eq1 j`. -/
def substDim (i : DimVar) (r : DimExpr) : FaceFormula → FaceFormula
| .bot => .bot
| .top => .top
| .eq0 j => if j = i then dimExprEq0 r else .eq0 j
| .eq1 j => if j = i then dimExprEq1 r else .eq1 j
| .meet ϕ ψ => .meet (ϕ.substDim i r) (ψ.substDim i r)
| .join ϕ ψ => .join (ϕ.substDim i r) (ψ.substDim i r)
-- ── Correctness: dimExprEq0/1 compute the right predicate ───────────────────
-- The semantic content: `(dimExprEq0 r).eval env = !(r.eval env)`, i.e. the
-- face holds iff r evaluates to 0 (= false in Bool).
mutual
theorem dimExprEq0_eval (r : DimExpr) (env : DimVar → Bool) :
(dimExprEq0 r).eval env = !(r.eval env) := by
cases r with
| zero => rfl
| one => rfl
| var k => rfl
| inv s =>
show (dimExprEq1 s).eval env = _
rw [dimExprEq1_eval s env]
simp [DimExpr.eval]
| meet s t =>
show ((dimExprEq0 s).join (dimExprEq0 t)).eval env = _
simp only [eval]
rw [dimExprEq0_eval s env, dimExprEq0_eval t env]
simp [DimExpr.eval, Bool.not_and]
| join s t =>
show ((dimExprEq0 s).meet (dimExprEq0 t)).eval env = _
simp only [eval]
rw [dimExprEq0_eval s env, dimExprEq0_eval t env]
simp [DimExpr.eval, Bool.not_or]
theorem dimExprEq1_eval (r : DimExpr) (env : DimVar → Bool) :
(dimExprEq1 r).eval env = r.eval env := by
cases r with
| zero => rfl
| one => rfl
| var k => rfl
| inv s =>
show (dimExprEq0 s).eval env = _
rw [dimExprEq0_eval s env]
simp [DimExpr.eval]
| meet s t =>
show ((dimExprEq1 s).meet (dimExprEq1 t)).eval env = _
simp only [eval]
rw [dimExprEq1_eval s env, dimExprEq1_eval t env]
simp [DimExpr.eval]
| join s t =>
show ((dimExprEq1 s).join (dimExprEq1 t)).eval env = _
simp only [eval]
rw [dimExprEq1_eval s env, dimExprEq1_eval t env]
simp [DimExpr.eval]
end
-- ── Endpoint specialisations: substDim at .zero / .one agrees with restrict ──
theorem substDim_zero (i : DimVar) (ϕ : FaceFormula) :
ϕ.substDim i .zero = ϕ.restrict i false := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 j => by_cases h : j = i <;> simp [substDim, restrict, h, dimExprEq0]
| eq1 j => by_cases h : j = i <;> simp [substDim, restrict, h, dimExprEq1]
| meet ϕ ψ ihϕ ihψ => simp [substDim, restrict, ihϕ, ihψ]
| join ϕ ψ ihϕ ihψ => simp [substDim, restrict, ihϕ, ihψ]
theorem substDim_one (i : DimVar) (ϕ : FaceFormula) :
ϕ.substDim i .one = ϕ.restrict i true := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 j => by_cases h : j = i <;> simp [substDim, restrict, h, dimExprEq0]
| eq1 j => by_cases h : j = i <;> simp [substDim, restrict, h, dimExprEq1]
| meet ϕ ψ ihϕ ihψ => simp [substDim, restrict, ihϕ, ihψ]
| join ϕ ψ ihϕ ihψ => simp [substDim, restrict, ihϕ, ihψ]
-- ── substDim is identity when dim is absent ──────────────────────────────────
theorem substDim_of_absent (i : DimVar) (r : DimExpr)
(ϕ : FaceFormula) (h : ϕ.dimAbsent i = true) :
ϕ.substDim i r = ϕ := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 j =>
simp only [dimAbsent, bne_iff_ne] at h
simp [substDim, if_neg h]
| eq1 j =>
simp only [dimAbsent, bne_iff_ne] at h
simp [substDim, if_neg h]
| meet ϕ ψ ihϕ ihψ =>
simp only [dimAbsent, Bool.and_eq_true] at h
simp [substDim, ihϕ h.1, ihψ h.2]
| join ϕ ψ ihϕ ihψ =>
simp only [dimAbsent, Bool.and_eq_true] at h
simp [substDim, ihϕ h.1, ihψ h.2]
-- ── dimExprEq{0,1} dim-absence preservation ────────────────────────────────
-- Mutual recursion because dimExprEq0 and dimExprEq1 refer to each other
-- on `.inv`.
mutual
/-- When `r` doesn't mention `j`, neither does `dimExprEq0 r`. -/
theorem dimExprEq0_dimAbsent (j : DimVar) : (r : DimExpr) →
r.dimAbsent j = true → (dimExprEq0 r).dimAbsent j = true
| .zero, _ => rfl
| .one, _ => rfl
| .var k, h => by
simp only [DimExpr.dimAbsent, bne_iff_ne] at h
simp [dimExprEq0, dimAbsent, bne_iff_ne, h]
| .inv r, h => by
simp only [DimExpr.dimAbsent] at h
show (dimExprEq1 r).dimAbsent j = true
exact dimExprEq1_dimAbsent j r h
| .meet r s, h => by
simp only [DimExpr.dimAbsent, Bool.and_eq_true] at h
show ((dimExprEq0 r).join (dimExprEq0 s)).dimAbsent j = true
simp only [dimAbsent, Bool.and_eq_true]
exact ⟨dimExprEq0_dimAbsent j r h.1, dimExprEq0_dimAbsent j s h.2⟩
| .join r s, h => by
simp only [DimExpr.dimAbsent, Bool.and_eq_true] at h
show ((dimExprEq0 r).meet (dimExprEq0 s)).dimAbsent j = true
simp only [dimAbsent, Bool.and_eq_true]
exact ⟨dimExprEq0_dimAbsent j r h.1, dimExprEq0_dimAbsent j s h.2⟩
/-- When `r` doesn't mention `j`, neither does `dimExprEq1 r`. -/
theorem dimExprEq1_dimAbsent (j : DimVar) : (r : DimExpr) →
r.dimAbsent j = true → (dimExprEq1 r).dimAbsent j = true
| .zero, _ => rfl
| .one, _ => rfl
| .var k, h => by
simp only [DimExpr.dimAbsent, bne_iff_ne] at h
simp [dimExprEq1, dimAbsent, bne_iff_ne, h]
| .inv r, h => by
simp only [DimExpr.dimAbsent] at h
show (dimExprEq0 r).dimAbsent j = true
exact dimExprEq0_dimAbsent j r h
| .meet r s, h => by
simp only [DimExpr.dimAbsent, Bool.and_eq_true] at h
show ((dimExprEq1 r).meet (dimExprEq1 s)).dimAbsent j = true
simp only [dimAbsent, Bool.and_eq_true]
exact ⟨dimExprEq1_dimAbsent j r h.1, dimExprEq1_dimAbsent j s h.2⟩
| .join r s, h => by
simp only [DimExpr.dimAbsent, Bool.and_eq_true] at h
show ((dimExprEq1 r).join (dimExprEq1 s)).dimAbsent j = true
simp only [dimAbsent, Bool.and_eq_true]
exact ⟨dimExprEq1_dimAbsent j r h.1, dimExprEq1_dimAbsent j s h.2⟩
end
/-- `substDim j s` on `dimExprEq0 r` is identity when `r` doesn't mention `j`. -/
theorem dimExprEq0_substDim_of_absent (j : DimVar) (s : DimExpr)
(r : DimExpr) (h : r.dimAbsent j = true) :
(dimExprEq0 r).substDim j s = dimExprEq0 r :=
substDim_of_absent j s _ (dimExprEq0_dimAbsent j r h)
/-- `substDim j s` on `dimExprEq1 r` is identity when `r` doesn't mention `j`. -/
theorem dimExprEq1_substDim_of_absent (j : DimVar) (s : DimExpr)
(r : DimExpr) (h : r.dimAbsent j = true) :
(dimExprEq1 r).substDim j s = dimExprEq1 r :=
substDim_of_absent j s _ (dimExprEq1_dimAbsent j r h)
-- ── dimAbsent after substDim (i becomes absent after substituting i → r) ──
/-- After `substDim i r ϕ`, `i` is absent from the result — provided `r`
itself doesn't mention `i`. Needed downstream in `CTerm.dimAbsent_after_substDim`. -/
theorem dimAbsent_after_substDim (i : DimVar) (r : DimExpr)
(hr : r.dimAbsent i = true) (ϕ : FaceFormula) :
(ϕ.substDim i r).dimAbsent i = true := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 j =>
simp only [substDim]
by_cases hji : j = i
· rw [hji, if_pos rfl]; exact dimExprEq0_dimAbsent i r hr
· simp only [if_neg hji, dimAbsent, bne_iff_ne]; exact hji
| eq1 j =>
simp only [substDim]
by_cases hji : j = i
· rw [hji, if_pos rfl]; exact dimExprEq1_dimAbsent i r hr
· simp only [if_neg hji, dimAbsent, bne_iff_ne]; exact hji
| meet ϕ ψ ihϕ ihψ =>
simp only [substDim, dimAbsent, Bool.and_eq_true]
exact ⟨ihϕ, ihψ⟩
| join ϕ ψ ihϕ ihψ =>
simp only [substDim, dimAbsent, Bool.and_eq_true]
exact ⟨ihϕ, ihψ⟩
-- ── substDim commutes with itself on disjoint dim variables ─────────────────
/-- Substituting disjoint dims `i` and `j` (with the RHS DimExprs not
mentioning the opposite side's binder) commutes. Required for the
`CTerm.substDim_comm_aux` mutual induction through the new `transp`/
`comp` arms that now substitute in the face formula. -/
theorem substDim_comm (i j : DimVar) (r s : DimExpr) (hij : i ≠ j)
(hrj : r.dimAbsent j = true) (hsi : s.dimAbsent i = true)
(ϕ : FaceFormula) :
(ϕ.substDim i r).substDim j s = (ϕ.substDim j s).substDim i r := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 k =>
by_cases hki : k = i
· subst hki
simp only [substDim, ite_true, if_neg hij]
exact dimExprEq0_substDim_of_absent j s r hrj
· by_cases hkj : k = j
· subst hkj
simp only [substDim, if_neg hki, ite_true]
exact (dimExprEq0_substDim_of_absent i r s hsi).symm
· simp [substDim, if_neg hki, if_neg hkj]
| eq1 k =>
by_cases hki : k = i
· subst hki
simp only [substDim, ite_true, if_neg hij]
exact dimExprEq1_substDim_of_absent j s r hrj
· by_cases hkj : k = j
· subst hkj
simp only [substDim, if_neg hki, ite_true]
exact (dimExprEq1_substDim_of_absent i r s hsi).symm
· simp [substDim, if_neg hki, if_neg hkj]
| meet ϕ ψ ihϕ ihψ => simp [substDim, ihϕ, ihψ]
| join ϕ ψ ihϕ ihψ => simp [substDim, ihϕ, ihψ]
-- ── Normalisation (Stage 4.3, 2026-04-23) ────────────────────────────────────
--
-- After `substDim`, face formulas can contain terms like `.meet .top φ`
-- or `.join .bot φ` (e.g. `.substDim i .zero` on `.eq0 i` produces
-- `.top` as a sub-term which may need propagation). Rust's cubical
-- evaluator needs canonicalised faces to make dispatch deterministic:
-- the existing Glue-transport axioms hinge on `φ.substDim i .one = .bot`
-- or `= .top` literal equalities, which only hold after normalisation.
--
-- Reductions implemented (mirrors the semantic laws already in this
-- file):
-- · `.meet .top φ → φ`, `.meet φ .top → φ`
-- · `.meet .bot _ → .bot`, `.meet _ .bot → .bot`
-- · `.join .bot φ → φ`, `.join φ .bot → φ`
-- · `.join .top _ → .top`, `.join _ .top → .top`
--
-- Mutually-exclusive literal reductions (`.meet (.eq0 i) (.eq1 i) → .bot`,
-- etc.) are deliberately skipped — they require decidable equality on
-- `DimVar` in the reduction step and don't affect any current axiom.
@[extern "cubical_transport_face_normalize"]
private opaque normalizeRust : FaceFormula → FaceFormula
/-- Canonical form under literal-identity / absorption reductions. -/
@[implemented_by normalizeRust]
def normalize : FaceFormula → FaceFormula
| .bot => .bot
| .top => .top
| .eq0 i => .eq0 i
| .eq1 i => .eq1 i
| .meet ϕ ψ =>
match normalize ϕ, normalize ψ with
| .bot, _ => .bot
| _, .bot => .bot
| .top, nψ => nψ
| nϕ, .top => nϕ
| nϕ, nψ => .meet nϕ nψ
| .join ϕ ψ =>
match normalize ϕ, normalize ψ with
| .top, _ => .top
| _, .top => .top
| .bot, nψ => nψ
| nϕ, .bot => nϕ
| nϕ, nψ => .join nϕ nψ
-- ── Preservation of evaluation ──────────────────────────────────────────────
private theorem normalize_meet_aux (ϕ ψ : FaceFormula) (env : DimVar → Bool) :
((normalize (FaceFormula.meet ϕ ψ)).eval env) =
(((normalize ϕ).eval env) && ((normalize ψ).eval env)) := by
show (match normalize ϕ, normalize ψ with
| FaceFormula.bot, _ => FaceFormula.bot
| _, FaceFormula.bot => FaceFormula.bot
| FaceFormula.top, nψ => nψ
| nϕ, FaceFormula.top => nϕ
| nϕ, nψ => FaceFormula.meet nϕ nψ).eval env =
(((normalize ϕ).eval env) && ((normalize ψ).eval env))
cases hϕ : normalize ϕ <;> cases hψ : normalize ψ <;> simp [eval]
private theorem normalize_join_aux (ϕ ψ : FaceFormula) (env : DimVar → Bool) :
((normalize (FaceFormula.join ϕ ψ)).eval env) =
(((normalize ϕ).eval env) || ((normalize ψ).eval env)) := by
show (match normalize ϕ, normalize ψ with
| FaceFormula.top, _ => FaceFormula.top
| _, FaceFormula.top => FaceFormula.top
| FaceFormula.bot, nψ => nψ
| nϕ, FaceFormula.bot => nϕ
| nϕ, nψ => FaceFormula.join nϕ nψ).eval env =
(((normalize ϕ).eval env) || ((normalize ψ).eval env))
cases hϕ : normalize ϕ <;> cases hψ : normalize ψ <;> simp [eval]
/-- Normalisation is semantically transparent. -/
theorem normalize_preserves_eval (ϕ : FaceFormula) (env : DimVar → Bool) :
(normalize ϕ).eval env = ϕ.eval env := by
induction ϕ with
| bot => rfl
| top => rfl
| eq0 i => rfl
| eq1 i => rfl
| meet ϕ ψ ihϕ ihψ =>
have h := normalize_meet_aux ϕ ψ env
rw [h, ihϕ, ihψ]
rfl
| join ϕ ψ ihϕ ihψ =>
have h := normalize_join_aux ϕ ψ env
rw [h, ihϕ, ihψ]
rfl
end FaceFormula