Some checks are pending
Lean Action CI / build (push) Waiting to run
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>
243 lines
11 KiB
Text
243 lines
11 KiB
Text
/-
|
|
Topolei.EML.Path
|
|
================
|
|
Connection between EML expressions and the cubical interval.
|
|
|
|
An EML expression with a free "dimension variable" (a named string)
|
|
that is evaluated at 0.0 or 1.0 is a path in the Float domain.
|
|
This is the bridge between:
|
|
|
|
· the cubical interval I (Bool endpoints: false = 0, true = 1)
|
|
· EML evaluation (Float-valued)
|
|
|
|
Key definitions:
|
|
· EMLExpr.evalWithEnv — generalised evaluator with custom var resolver
|
|
· EMLExpr.varAbsent — syntactic check that a variable does not appear
|
|
· EMLPath — an EML expression with a distinguished dim var
|
|
· EMLPath.atBool/at0/at1 — evaluation at Bool endpoints
|
|
· EMLPath.const_endpoints — absent dim var → at0 = at1
|
|
|
|
The GPU-layer connection (linking baseEnv to shaderVar) lives in
|
|
GPU/Spec.lean rather than here, to avoid a circular import.
|
|
-/
|
|
|
|
import Topolei.EML
|
|
import Topolei.Cubical.DimLine
|
|
|
|
-- ── Bool → Float endpoint map ─────────────────────────────────────────────────
|
|
|
|
/-- Map Bool to its Float interval endpoint: false ↦ 0.0, true ↦ 1.0 -/
|
|
def boolToFloat : Bool → Float
|
|
| false => 0.0
|
|
| true => 1.0
|
|
|
|
@[simp] theorem boolToFloat_false : boolToFloat false = 0.0 := rfl
|
|
@[simp] theorem boolToFloat_true : boolToFloat true = 1.0 := rfl
|
|
|
|
-- ── Generalised EML evaluator ─────────────────────────────────────────────────
|
|
|
|
/-- Evaluate an EML expression using a custom variable resolver.
|
|
This generalises EMLExpr.evalAt (which uses shaderVar) so we can
|
|
override the dimension variable without touching shader-layer types. -/
|
|
def EMLExpr.evalWithEnv (env : String → Float) : EMLExpr → Float
|
|
| .one => 1.0
|
|
| .var name => env name
|
|
| .eml l r =>
|
|
let lv := l.evalWithEnv env
|
|
let rv := r.evalWithEnv env
|
|
Float.exp lv - Float.log (max rv 1e-9)
|
|
|
|
-- ── Variable absence predicate ────────────────────────────────────────────────
|
|
|
|
/-- Syntactic check: named variable does not appear in the expression. -/
|
|
def EMLExpr.varAbsent (name : String) : EMLExpr → Bool
|
|
| .one => true
|
|
| .var n => n != name
|
|
| .eml l r => l.varAbsent name && r.varAbsent name
|
|
|
|
/-- Two environments that agree on all names except `name` give the same
|
|
evaluation on expressions that don't mention `name`. -/
|
|
theorem EMLExpr.evalWithEnv_congr
|
|
(e : EMLExpr) (name : String)
|
|
(habs : e.varAbsent name = true)
|
|
(env1 env2 : String → Float)
|
|
(henv : ∀ n, n ≠ name → env1 n = env2 n) :
|
|
e.evalWithEnv env1 = e.evalWithEnv env2 := by
|
|
induction e with
|
|
| one => rfl
|
|
| var n =>
|
|
simp only [varAbsent, bne_iff_ne] at habs
|
|
simp [evalWithEnv, henv n habs]
|
|
| eml l r ihl ihr =>
|
|
simp only [varAbsent, Bool.and_eq_true] at habs
|
|
simp [evalWithEnv, ihl habs.1, ihr habs.2]
|
|
|
|
-- ── EMLPath ───────────────────────────────────────────────────────────────────
|
|
|
|
/-- An EML path: an expression parametric in a named dimension variable.
|
|
The `dimName` variable ranges over {0.0, 1.0} ≅ Bool. -/
|
|
structure EMLPath where
|
|
dimName : String -- dimension variable (e.g. "t")
|
|
body : EMLExpr -- parametric expression
|
|
|
|
/-- Evaluate an EMLPath at a Bool endpoint, given a base variable resolver for
|
|
all variables other than dimName. -/
|
|
def EMLPath.atBool (path : EMLPath) (b : Bool) (baseEnv : String → Float) : Float :=
|
|
path.body.evalWithEnv (fun name =>
|
|
if name = path.dimName then boolToFloat b else baseEnv name)
|
|
|
|
def EMLPath.at0 (path : EMLPath) (baseEnv : String → Float) : Float :=
|
|
path.atBool false baseEnv
|
|
|
|
def EMLPath.at1 (path : EMLPath) (baseEnv : String → Float) : Float :=
|
|
path.atBool true baseEnv
|
|
|
|
-- ── Endpoint reduction lemmas ─────────────────────────────────────────────────
|
|
|
|
@[simp] theorem EMLPath.at0_def (path : EMLPath) (baseEnv : String → Float) :
|
|
path.at0 baseEnv =
|
|
path.body.evalWithEnv (fun name =>
|
|
if name = path.dimName then 0.0 else baseEnv name) := by
|
|
simp [at0, atBool, boolToFloat]
|
|
|
|
@[simp] theorem EMLPath.at1_def (path : EMLPath) (baseEnv : String → Float) :
|
|
path.at1 baseEnv =
|
|
path.body.evalWithEnv (fun name =>
|
|
if name = path.dimName then 1.0 else baseEnv name) := by
|
|
simp [at1, atBool, boolToFloat]
|
|
|
|
-- ── Constant path ─────────────────────────────────────────────────────────────
|
|
|
|
/-- When the body does not mention dimName, at0 = at1 for any baseEnv. -/
|
|
theorem EMLPath.const_endpoints (path : EMLPath)
|
|
(habs : path.body.varAbsent path.dimName = true)
|
|
(baseEnv : String → Float) :
|
|
path.at0 baseEnv = path.at1 baseEnv := by
|
|
simp only [at0_def, at1_def]
|
|
apply EMLExpr.evalWithEnv_congr _ _ habs
|
|
intro n hn
|
|
simp [if_neg hn]
|
|
|
|
-- ── Example: exp path ────────────────────────────────────────────────────────
|
|
|
|
/-- The exponential path: f(t) = exp(t) for t ∈ {0, 1}. -/
|
|
def expPath : EMLPath :=
|
|
{ dimName := "t"
|
|
body := EMLExpr.expOf (.var "t") }
|
|
|
|
/-- At t = 0, expPath evaluates to exp(0) - log(max 1.0 1e-9). -/
|
|
theorem expPath_at0 (baseEnv : String → Float) :
|
|
expPath.at0 baseEnv =
|
|
Float.exp 0.0 - Float.log (max 1.0 1e-9) := by
|
|
simp [expPath, EMLExpr.expOf, EMLExpr.evalWithEnv]
|
|
|
|
/-- At t = 1, expPath evaluates to exp(1) - log(max 1.0 1e-9). -/
|
|
theorem expPath_at1 (baseEnv : String → Float) :
|
|
expPath.at1 baseEnv =
|
|
Float.exp 1.0 - Float.log (max 1.0 1e-9) := by
|
|
simp [expPath, EMLExpr.expOf, EMLExpr.evalWithEnv]
|
|
|
|
-- ── Connection to DimLine ─────────────────────────────────────────────────────
|
|
/-
|
|
Structural parallel:
|
|
|
|
DimLine (cubical, CType-valued) ↔ EMLPath (rendering, Float-valued)
|
|
───────────────────────────────────────────────────────────────────────
|
|
DimLine.binder : DimVar ↔ EMLPath.dimName : String
|
|
DimLine.at0 : CType ↔ EMLPath.at0 : Float
|
|
DimLine.at1 : CType ↔ EMLPath.at1 : Float
|
|
transp_const_id (T2) ↔ EMLPath.const_endpoints
|
|
|
|
Transport along a constant DimLine is the identity (T2).
|
|
EML analogue: evaluation of a constant EMLPath (dimName absent) gives
|
|
the same Float value at both endpoints.
|
|
|
|
The rendering correctness claim:
|
|
"If the GPU evaluates shader at the 1-end of a DimLine,
|
|
the Float result equals EMLPath.at1 baseEnv."
|
|
This is the bridge axiom in GPU/Spec.lean (to be added there).
|
|
-/
|
|
|
|
theorem EMLPath_const_mirrors_T2
|
|
(path : EMLPath)
|
|
(habs : path.body.varAbsent path.dimName = true)
|
|
(baseEnv : String → Float) :
|
|
path.at0 baseEnv = path.at1 baseEnv :=
|
|
EMLPath.const_endpoints path habs baseEnv
|
|
|
|
-- ── PlotConfig → EMLPath ──────────────────────────────────────────────────────
|
|
/-
|
|
A `PlotConfig` carries an `EMLExpr` and a distinguished `dimName` which is
|
|
the path dimension. Viewing a plot as an `EMLPath` is a direct projection:
|
|
drop the display metadata, keep the expression and the dimension.
|
|
|
|
When the plot's expression does not mention `dimName`, the resulting path
|
|
is constant (T2 analogue); when it does, the plot is genuinely time-varying
|
|
and `at0`, `at1` differ.
|
|
-/
|
|
|
|
/-- Project a `PlotConfig` to its `EMLPath` view. -/
|
|
def PlotConfig.toEMLPath (cfg : PlotConfig) : EMLPath :=
|
|
{ dimName := cfg.dimName
|
|
body := cfg.expr }
|
|
|
|
@[simp] theorem PlotConfig.toEMLPath_dimName (cfg : PlotConfig) :
|
|
cfg.toEMLPath.dimName = cfg.dimName := rfl
|
|
|
|
@[simp] theorem PlotConfig.toEMLPath_body (cfg : PlotConfig) :
|
|
cfg.toEMLPath.body = cfg.expr := rfl
|
|
|
|
/-- A plot is a *constant path* exactly when its expression does not mention
|
|
the plot's declared dimension variable. -/
|
|
theorem PlotConfig.const_path_of_varAbsent
|
|
(cfg : PlotConfig)
|
|
(habs : cfg.expr.varAbsent cfg.dimName = true)
|
|
(baseEnv : String → Float) :
|
|
cfg.toEMLPath.at0 baseEnv = cfg.toEMLPath.at1 baseEnv :=
|
|
EMLPath.const_endpoints cfg.toEMLPath habs baseEnv
|
|
|
|
-- ── Demo expressions as paths ─────────────────────────────────────────────────
|
|
/-
|
|
`plotExp` and `plotLn` (in `EML.lean`) use `px` as their free variable,
|
|
not the dimension variable `t`. They are therefore *constant paths* — the
|
|
same value at both endpoints — under the default `dimName := "t"`.
|
|
-/
|
|
|
|
theorem plotExp_body_varAbsent : plotExp.expr.varAbsent "t" = true := by decide
|
|
|
|
theorem plotExp_is_const_path (baseEnv : String → Float) :
|
|
plotExp.toEMLPath.at0 baseEnv = plotExp.toEMLPath.at1 baseEnv :=
|
|
plotExp.const_path_of_varAbsent plotExp_body_varAbsent baseEnv
|
|
|
|
theorem plotLn_body_varAbsent : plotLn.expr.varAbsent "t" = true := by decide
|
|
|
|
theorem plotLn_is_const_path (baseEnv : String → Float) :
|
|
plotLn.toEMLPath.at0 baseEnv = plotLn.toEMLPath.at1 baseEnv :=
|
|
plotLn.const_path_of_varAbsent plotLn_body_varAbsent baseEnv
|
|
|
|
-- ── Parametric example (a genuinely non-constant path) ───────────────────────
|
|
|
|
/-- A parametric plot that actually uses the dimension variable `t`:
|
|
`eml(t, 1) = exp(t)` — a path from `exp(0)` at `t=0` to `exp(1)` at `t=1`. -/
|
|
def plotExpT : PlotConfig :=
|
|
{ expr := EMLExpr.expOf (.var "t")
|
|
dimName := "t" }
|
|
|
|
theorem plotExpT_at0 (baseEnv : String → Float) :
|
|
plotExpT.toEMLPath.at0 baseEnv =
|
|
Float.exp 0.0 - Float.log (max 1.0 1e-9) := by
|
|
simp [plotExpT, PlotConfig.toEMLPath, EMLPath.at0, EMLPath.atBool,
|
|
EMLExpr.expOf, EMLExpr.evalWithEnv, boolToFloat]
|
|
|
|
theorem plotExpT_at1 (baseEnv : String → Float) :
|
|
plotExpT.toEMLPath.at1 baseEnv =
|
|
Float.exp 1.0 - Float.log (max 1.0 1e-9) := by
|
|
simp [plotExpT, PlotConfig.toEMLPath, EMLPath.at1, EMLPath.atBool,
|
|
EMLExpr.expOf, EMLExpr.evalWithEnv, boolToFloat]
|
|
|
|
-- The shader whose semantic IS `EMLPath.toColor` is now built directly
|
|
-- as a `naga::Module` (no GLSL string intermediary) on the Rust side
|
|
-- by `native/canvas-rs/src/emit_naga.rs::build_probe_module`. See
|
|
-- `NAGA_IR_PLAN.md` for the construction; `Topolei.GPU.Spec`'s
|
|
-- `compileEMLPath_correct` axiom states the contract that builder
|
|
-- must satisfy.
|