cubical-transport-hott-lean4/native/cubical/src/composition.rs
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

283 lines
11 KiB
Rust
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.

//! # composition
//!
//! Rust implementation of `vHCompValue`, `vCompAtTerm`, `vCompNAtTerm`
//! — CCHM composition at the value level (Cubical/Eval.lean).
//!
//! `vHCompValue A φ tube base` — homogeneous composition:
//! - φ = .top → `vPApp tube .one` (tube's 1-endpoint)
//! - A = .pi → `.vHCompFun {_c} codA φ tube base` (Π β later)
//! - else → `.vneu (.nhcomp {} A φ tube base)`
//!
//! `vCompAtTerm env i A φ u t` — heterogeneous composition (CTerm-input):
//! - φ = .top → `eval env (u.substDim i .one)` (C1)
//! - φ = .bot → `eval env (.transp i {} A .bot t)` (C2)
//! - A dim-absent from i → hcomp via `.plam i u` as tube (C3-like)
//! - A = .pi → `.vCompFun {_d _c} env i domA codA φ u t`
//! - else → `.vneu (.ncomp {} i A φ (eval u) (eval t))`
//!
//! `vCompNAtTerm env i A clauses t` — multi-clause comp.
//!
//! ## ABI v4 — ULevel
//!
//! Each entry takes the implicit `{ : ULevel}` of the Lean `vCompAt*`
//! function as `l : LeanObj` first. When constructing CVal/CNeu carrying
//! their own implicit ULevel(s), we forward `l` (or read sub-level slots
//! from a CType.pi's [_d, _c, ...] layout).
use crate::lean_runtime::*;
use crate::tags::*;
use crate::value::*;
const LIST_NIL: u32 = 0;
const LIST_CONS: u32 = 1;
const PROD_MK: u32 = 0;
// ── vHCompValue ─────────────────────────────────────────────────────────────
/// Homogeneous composition at the value level.
/// Takes borrowed `l`, `a`, `phi`; owned `tube` and `base`. Returns owned.
pub fn vhcomp_value(
l: LeanObj,
a: LeanObj, phi: LeanObj, tube: LeanObjMut, base: LeanObjMut,
) -> LeanObjMut {
// (1) φ = .top → vPApp tube .one
if ctor_tag(phi) == FACE_TOP {
release(base as LeanObj);
// Construct DimExpr.one.
let one = lean_box_mut(DIM_ONE as usize);
// vPApp consumes tube and r.
return crate::eval::vpapp(tube, one as LeanObj);
}
// (2) A = .pi → .vHCompFun {_c} closure.
// ABI v4: pi layout [_d, _c, var, A, B] — codomain B at field 4.
if ctor_tag(a) == TY_PI {
let lc = ctor_field(a, 1);
let cod_a = ctor_field(a, 4);
retain(lc); retain(cod_a); retain(phi);
return mk_vhcompfun(lc, cod_a, phi, tube as LeanObj, base as LeanObj);
}
// (3) Stuck — .vneu (.nhcomp {} A φ tube base)
retain(l); retain(a); retain(phi);
let nhcomp = mk_nhcomp(l, a, phi, tube as LeanObj, base as LeanObj);
mk_vneu(nhcomp as LeanObj)
}
// ── vCompAtTerm ─────────────────────────────────────────────────────────────
/// Heterogeneous composition taking CTerm inputs (so C1 can substitute at
/// the term level).
/// All arguments borrowed; returns owned CVal.
pub fn vcomp_at_term(
l: LeanObj,
env: LeanObj, i: LeanObj, a: LeanObj, phi: LeanObj,
u: LeanObj, t: LeanObj,
) -> LeanObjMut {
// (1) φ = .top → C1: eval env (u.substDim i .one)
if ctor_tag(phi) == FACE_TOP {
let one = lean_box_mut(DIM_ONE as usize);
let u_at_1 = crate::subst::cterm_subst_dim(i, one as LeanObj, u);
let result = crate::eval::eval(env, u_at_1 as LeanObj);
release(u_at_1 as LeanObj);
return result;
}
// (2) φ = .bot → C2: eval env (.transp i {} A .bot t)
if ctor_tag(phi) == FACE_BOT {
// ABI v4: TERM_TRANSP layout [i, , A, φ, t] (5 fields).
retain(l); retain(i); retain(a);
let bot = lean_box_mut(FACE_BOT as usize);
let transp_term = alloc_ctor(TERM_TRANSP, 5);
ctor_set_field(transp_term, 0, i);
ctor_set_field(transp_term, 1, l);
ctor_set_field(transp_term, 2, a);
ctor_set_field(transp_term, 3, bot as LeanObj);
retain(t);
ctor_set_field(transp_term, 4, t);
let result = crate::eval::eval(env, transp_term as LeanObj);
release(transp_term as LeanObj);
return result;
}
// (3) A dim-absent from i → hcomp on A with `.plam i u` as tube
if crate::dim_absent::ctype_absent(i, a) {
// Build `.plam i u`.
retain(i); retain(u);
let plam_u = alloc_ctor(TERM_PLAM, 2);
ctor_set_field(plam_u, 0, i);
ctor_set_field(plam_u, 1, u);
let tube_val = crate::eval::eval(env, plam_u as LeanObj);
release(plam_u as LeanObj);
let base_val = crate::eval::eval(env, t);
return vhcomp_value(l, a, phi, tube_val, base_val);
}
// (4) A = .pi → .vCompFun {_d _c} env i domA codA φ u t closure.
// ABI v4: pi layout [_d, _c, var, A, B].
if ctor_tag(a) == TY_PI {
let ld = ctor_field(a, 0);
let lc = ctor_field(a, 1);
let dom_a = ctor_field(a, 3);
let cod_a = ctor_field(a, 4);
retain(ld); retain(lc);
retain(env); retain(i);
retain(dom_a); retain(cod_a); retain(phi);
retain(u); retain(t);
return mk_vcompfun(ld, lc, env, i, dom_a, cod_a, phi, u, t);
}
// (5) Stuck — .vneu (.ncomp {} i A φ (eval u) (eval t))
let u_val = crate::eval::eval(env, u);
let t_val = crate::eval::eval(env, t);
retain(l); retain(i); retain(a); retain(phi);
let ncomp = mk_ncomp(l, i, a, phi, u_val as LeanObj, t_val as LeanObj);
mk_vneu(ncomp as LeanObj)
}
// ── vCompNAtTerm ────────────────────────────────────────────────────────────
/// Find the first clause whose face is `.top`. Returns the matching body
/// CTerm (borrowed) or null pointer if none found.
fn find_top_clause(clauses: LeanObj) -> LeanObj {
let mut cur = clauses;
loop {
match ctor_tag(cur) {
LIST_NIL => return core::ptr::null(),
LIST_CONS => {
let head = ctor_field(cur, 0);
let face = ctor_field(head, 0);
if ctor_tag(face) == FACE_TOP {
return ctor_field(head, 1); // body
}
cur = ctor_field(cur, 1);
}
_ => return core::ptr::null(),
}
}
}
/// Build a new clause list containing only the non-`.bot` clauses of the
/// input. Fields retained. Returns the length as well.
fn filter_live_clauses(clauses: LeanObj) -> (LeanObjMut, u32) {
// Collect non-bot clauses via a simple recursive traversal.
build_live(clauses)
}
/// Recursive helper; returns `(list, length)`.
fn build_live(cur: LeanObj) -> (LeanObjMut, u32) {
match ctor_tag(cur) {
LIST_NIL => (lean_box_mut(LIST_NIL as usize), 0),
LIST_CONS => {
let head = ctor_field(cur, 0);
let face = ctor_field(head, 0);
let tail = ctor_field(cur, 1);
let (tail_list, tail_len) = build_live(tail);
if ctor_tag(face) == FACE_BOT {
// Drop this clause.
(tail_list, tail_len)
} else {
// Retain head pair fields and rebuild.
let body = ctor_field(head, 1);
retain(face); retain(body);
let new_pair = alloc_ctor(PROD_MK, 2);
ctor_set_field(new_pair, 0, face);
ctor_set_field(new_pair, 1, body);
let new_cons = alloc_ctor(LIST_CONS, 2);
ctor_set_field(new_cons, 0, new_pair as LeanObj);
ctor_set_field(new_cons, 1, tail_list as LeanObj);
(new_cons, tail_len + 1)
}
}
_ => (lean_box_mut(LIST_NIL as usize), 0),
}
}
/// Evaluate every clause's body CTerm under `env`, producing a new
/// `List (FaceFormula × CVal)`.
fn eval_clause_bodies(env: LeanObj, clauses: LeanObjMut) -> LeanObjMut {
eval_clauses_rec(env, clauses as LeanObj)
}
fn eval_clauses_rec(env: LeanObj, cur: LeanObj) -> LeanObjMut {
match ctor_tag(cur) {
LIST_NIL => lean_box_mut(LIST_NIL as usize),
LIST_CONS => {
let head = ctor_field(cur, 0);
let face = ctor_field(head, 0);
let body = ctor_field(head, 1);
let tail = ctor_field(cur, 1);
retain(face);
let body_val = crate::eval::eval(env, body);
let new_pair = alloc_ctor(PROD_MK, 2);
ctor_set_field(new_pair, 0, face);
ctor_set_field(new_pair, 1, body_val as LeanObj);
let new_tail = eval_clauses_rec(env, tail);
let new_cons = alloc_ctor(LIST_CONS, 2);
ctor_set_field(new_cons, 0, new_pair as LeanObj);
ctor_set_field(new_cons, 1, new_tail as LeanObj);
new_cons
}
_ => lean_box_mut(LIST_NIL as usize),
}
}
/// Multi-clause heterogeneous composition.
pub fn vcompn_at_term(
l: LeanObj,
env: LeanObj, i: LeanObj, a: LeanObj,
clauses: LeanObj, t: LeanObj,
) -> LeanObjMut {
// (1) Any `.top` clause → fire it at i := 1.
let top_body = find_top_clause(clauses);
if !top_body.is_null() {
let one = lean_box_mut(DIM_ONE as usize);
let u_at_1 = crate::subst::cterm_subst_dim(i, one as LeanObj, top_body);
let result = crate::eval::eval(env, u_at_1 as LeanObj);
release(u_at_1 as LeanObj);
return result;
}
// (2) Strip `.bot` clauses.
let (live, len) = filter_live_clauses(clauses);
match len {
0 => {
// No clauses — plain transport of base via .transp i {} A .bot t.
release(live as LeanObj);
retain(l); retain(i); retain(a);
let bot = lean_box_mut(FACE_BOT as usize);
// ABI v4: TERM_TRANSP has 5 fields [i, , A, φ, t].
let transp_term = alloc_ctor(TERM_TRANSP, 5);
ctor_set_field(transp_term, 0, i);
ctor_set_field(transp_term, 1, l);
ctor_set_field(transp_term, 2, a);
ctor_set_field(transp_term, 3, bot as LeanObj);
retain(t);
ctor_set_field(transp_term, 4, t);
let result = crate::eval::eval(env, transp_term as LeanObj);
release(transp_term as LeanObj);
result
}
1 => {
// Single clause — delegate to vCompAtTerm.
let head = ctor_field(live as LeanObj, 0);
let face = ctor_field(head, 0);
let body = ctor_field(head, 1);
let result = vcomp_at_term(l, env, i, a, face, body, t);
release(live as LeanObj);
result
}
_ => {
// Stuck — produce ncompN with evaluated clause bodies.
let evaled_clauses = eval_clause_bodies(env, live);
let t_val = crate::eval::eval(env, t);
retain(l); retain(env); retain(i); retain(a);
let ncompn = mk_ncompn(
l, env, i, a, evaled_clauses as LeanObj, t_val as LeanObj);
mk_vneu(ncompn as LeanObj)
}
}
}