Some checks are pending
Lean Action CI / build (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
275 lines
9.8 KiB
Text
275 lines
9.8 KiB
Text
import OctiveLean.BigStep
|
||
|
||
namespace OctiveLean
|
||
|
||
/-!
|
||
# Phase C — Value Representation Equivalences
|
||
|
||
Three approaches for formalizing that multiple `Value` constructors are
|
||
semantically identical, enabling proof transport across representations.
|
||
-/
|
||
|
||
/-!
|
||
# Approach 1: Setoid / Quotient
|
||
-/
|
||
|
||
section Approach1
|
||
|
||
/-- Canonical form: collapses equivalent representations. -/
|
||
def Value.normalize : Value → Value
|
||
| Value.scalar f => Value.matrix 1 1 #[f]
|
||
| Value.fscalar f => Value.matrix 1 1 #[f]
|
||
| Value.boolean b => Value.matrix 1 1 #[if b then 1.0 else 0.0]
|
||
| Value.range s st e =>
|
||
let data := Value.rangeToArray s st e
|
||
if data.isEmpty then Value.empty else Value.matrix 1 data.size data
|
||
| v => v
|
||
|
||
/-- Semantic equivalence via normal forms. -/
|
||
def ValEq (a b : Value) : Prop := Value.normalize a = Value.normalize b
|
||
|
||
instance : Setoid Value where
|
||
r := ValEq
|
||
iseqv := ⟨fun _ => Eq.refl _,
|
||
fun h => Eq.symm h,
|
||
fun h k => Eq.trans h k⟩
|
||
|
||
/-- Octave value up to representation. -/
|
||
def OctaveValue := Quotient (inferInstance : Setoid Value)
|
||
|
||
def OctaveValue.mk (v : Value) : OctaveValue := Quotient.mk _ v
|
||
def OctaveValue.lift {α} (f : Value → α) (hf : ∀ a b, ValEq a b → f a = f b) :
|
||
OctaveValue → α := Quotient.lift f hf
|
||
|
||
/-! Simp lemmas for normalize -/
|
||
|
||
@[simp] theorem normalize_matrix (r c : Nat) (d : Array Float) :
|
||
Value.normalize (Value.matrix r c d) = Value.matrix r c d := rfl
|
||
@[simp] theorem normalize_empty : Value.normalize Value.empty = Value.empty := rfl
|
||
@[simp] theorem normalize_scalar (f : Float) :
|
||
Value.normalize (Value.scalar f) = Value.matrix 1 1 #[f] := rfl
|
||
@[simp] theorem normalize_fscalar (f : Float) :
|
||
Value.normalize (Value.fscalar f) = Value.matrix 1 1 #[f] := rfl
|
||
@[simp] theorem normalize_boolean (b : Bool) :
|
||
Value.normalize (Value.boolean b) =
|
||
Value.matrix 1 1 #[if b then 1.0 else 0.0] := rfl
|
||
@[simp] theorem normalize_string (s : String) :
|
||
Value.normalize (Value.string s) = Value.string s := rfl
|
||
@[simp] theorem normalize_struct (fs : Array (String × Value)) :
|
||
Value.normalize (Value.struct fs) = Value.struct fs := rfl
|
||
|
||
/-! Equivalence theorems -/
|
||
|
||
theorem scalar_eq_matrix11 (x : Float) :
|
||
ValEq (Value.scalar x) (Value.matrix 1 1 #[x]) := by
|
||
simp [ValEq]
|
||
|
||
theorem boolean_true_eq_scalar1 : ValEq (Value.boolean true) (Value.scalar 1.0) := by
|
||
simp [ValEq]
|
||
|
||
theorem boolean_false_eq_scalar0 : ValEq (Value.boolean false) (Value.scalar 0.0) := by
|
||
simp [ValEq]
|
||
|
||
theorem fscalar_eq_scalar (x : Float) : ValEq (Value.fscalar x) (Value.scalar x) := by
|
||
simp [ValEq]
|
||
|
||
theorem range_eq_matrix (s st e : Float)
|
||
(hne : 0 < (Value.rangeToArray s st e).size) :
|
||
ValEq (Value.range s st e)
|
||
(Value.matrix 1 (Value.rangeToArray s st e).size (Value.rangeToArray s st e)) := by
|
||
simp only [ValEq, Value.normalize]
|
||
have hne' : (Value.rangeToArray s st e).size ≠ 0 := Nat.pos_iff_ne_zero.mp hne
|
||
have hnonempty : (Value.rangeToArray s st e).isEmpty = false := by
|
||
simp [Array.isEmpty, hne']
|
||
simp [hnonempty]
|
||
|
||
theorem empty_range_eq_empty (s st e : Float)
|
||
(h : (Value.rangeToArray s st e).isEmpty) :
|
||
ValEq (Value.range s st e) Value.empty := by
|
||
simp [ValEq, Value.normalize, h]
|
||
|
||
/-! Transport and quotient induction -/
|
||
|
||
/-- HoTT-style transport: move a predicate across ValEq. -/
|
||
theorem ValEq.transport {P : Value → Prop}
|
||
(hresp : ∀ a b, ValEq a b → P a → P b)
|
||
{v w} (heq : ValEq v w) (hv : P v) : P w := hresp v w heq hv
|
||
|
||
theorem OctaveValue.ind {P : OctaveValue → Prop}
|
||
(h : ∀ v : Value, P (OctaveValue.mk v)) : ∀ x, P x := Quotient.ind h
|
||
|
||
/-- normalize is idempotent. -/
|
||
theorem normalize_idempotent (v : Value) :
|
||
Value.normalize (Value.normalize v) = Value.normalize v := by
|
||
cases v with
|
||
| scalar _ => simp [Value.normalize]
|
||
| fscalar _ => simp [Value.normalize]
|
||
| boolean b => cases b <;> simp [Value.normalize]
|
||
| range s st e =>
|
||
simp only [Value.normalize]
|
||
by_cases h : (Value.rangeToArray s st e).isEmpty
|
||
· simp [h]
|
||
· simp [h]
|
||
| _ => simp [Value.normalize]
|
||
|
||
/-- shape respects ValEq. -/
|
||
theorem shape_congr {a b : Value} (h : ValEq a b) :
|
||
(Value.normalize a).shape = (Value.normalize b).shape := by
|
||
simp [ValEq] at h; rw [h]
|
||
|
||
end Approach1
|
||
|
||
/-!
|
||
# Approach 2: Bijection between float-indexed reps
|
||
-/
|
||
|
||
section Approach2
|
||
|
||
/-- A bijection between two types (local stand-in for Equiv, no Mathlib needed). -/
|
||
structure Bijection (α β : Type) where
|
||
toFun : α → β
|
||
invFun : β → α
|
||
left_inv : ∀ x : α, invFun (toFun x) = x
|
||
right_inv : ∀ x : β, toFun (invFun x) = x
|
||
|
||
/-- Representation of a scalar value: wraps a float. -/
|
||
structure ScalarRep where f : Float
|
||
/-- Representation of a 1×1 matrix value: wraps a float. -/
|
||
structure Matrix11Rep where f : Float
|
||
|
||
def scalarToMatrix11 (s : ScalarRep) : Matrix11Rep := ⟨s.f⟩
|
||
def matrix11ToScalar (m : Matrix11Rep) : ScalarRep := ⟨m.f⟩
|
||
|
||
@[simp] theorem scalarToMatrix11_leftInv (v : ScalarRep) :
|
||
matrix11ToScalar (scalarToMatrix11 v) = v := by cases v; rfl
|
||
|
||
@[simp] theorem scalarToMatrix11_rightInv (v : Matrix11Rep) :
|
||
scalarToMatrix11 (matrix11ToScalar v) = v := by cases v; rfl
|
||
|
||
/-- Scalar ≃ 1×1 matrix: completely proved without sorry. -/
|
||
def scalarMatrix11Bij : Bijection ScalarRep Matrix11Rep where
|
||
toFun := scalarToMatrix11
|
||
invFun := matrix11ToScalar
|
||
left_inv := scalarToMatrix11_leftInv
|
||
right_inv := scalarToMatrix11_rightInv
|
||
|
||
/-- Embed scalar rep into Value. -/
|
||
def ScalarRep.toValue (s : ScalarRep) : Value := Value.scalar s.f
|
||
/-- Embed 1×1 matrix rep into Value. -/
|
||
def Matrix11Rep.toValue (m : Matrix11Rep) : Value := Value.matrix 1 1 #[m.f]
|
||
|
||
/-- The bijection preserves the float field. -/
|
||
theorem scalarBij_float (s : ScalarRep) : (scalarMatrix11Bij.toFun s).f = s.f := rfl
|
||
|
||
/-- The two representations are ValEq under their Value embeddings. -/
|
||
theorem scalarRep_valEq_matrix11Rep (s : ScalarRep) :
|
||
ValEq s.toValue (scalarMatrix11Bij.toFun s).toValue := by
|
||
simp [ValEq, ScalarRep.toValue, Matrix11Rep.toValue,
|
||
scalarMatrix11Bij, scalarToMatrix11, Value.normalize]
|
||
|
||
/-- Boolean embedding into floats. -/
|
||
def boolToFloat : Bool → Float := fun b => if b then 1.0 else 0.0
|
||
|
||
@[simp] theorem boolToFloat_true : boolToFloat true = 1.0 := rfl
|
||
@[simp] theorem boolToFloat_false : boolToFloat false = 0.0 := rfl
|
||
|
||
/-- Booleans are ValEq to their float scalar images. -/
|
||
theorem boolean_valEq_scalar (b : Bool) :
|
||
ValEq (Value.boolean b) (Value.scalar (boolToFloat b)) := by
|
||
cases b <;> simp [ValEq, boolToFloat, Value.normalize]
|
||
|
||
/-- P holds for scalar iff it holds for the equivalent matrix (for ValEq-respecting P). -/
|
||
theorem scalar_iff_matrix11 {P : Value → Prop}
|
||
(hresp : ∀ a b, ValEq a b → P a → P b) (f : Float) :
|
||
P (Value.scalar f) ↔ P (Value.matrix 1 1 #[f]) :=
|
||
⟨hresp _ _ (scalar_eq_matrix11 f),
|
||
hresp _ _ (Eq.symm (scalar_eq_matrix11 f))⟩
|
||
|
||
end Approach2
|
||
|
||
/-!
|
||
# Approach 3: normalize + congruence
|
||
-/
|
||
|
||
section Approach3
|
||
|
||
/-- toFloatP on normalize-equivalent values agrees. -/
|
||
theorem toFloatP_scalar_eq_matrix11 (f : Float) (env : Env) :
|
||
runPureM (toFloatP (Value.scalar f)) env =
|
||
runPureM (toFloatP (Value.matrix 1 1 #[f])) env := by
|
||
simp [toFloatP, Value.materialize]
|
||
|
||
theorem toFloatP_bool_true_eq_scalar1 (env : Env) :
|
||
runPureM (toFloatP (Value.boolean true)) env =
|
||
runPureM (toFloatP (Value.scalar 1.0)) env := by
|
||
simp [toFloatP, Value.materialize]
|
||
|
||
theorem toFloatP_bool_false_eq_scalar0 (env : Env) :
|
||
runPureM (toFloatP (Value.boolean false)) env =
|
||
runPureM (toFloatP (Value.scalar 0.0)) env := by
|
||
simp [toFloatP, Value.materialize]
|
||
|
||
/-- materialize is idempotent. -/
|
||
theorem materialize_idempotent (v : Value) :
|
||
Value.materialize (Value.materialize v) = Value.materialize v := by
|
||
cases v with
|
||
| range s st e =>
|
||
by_cases h : (Value.rangeToArray s st e).isEmpty
|
||
· simp [Value.materialize, h]
|
||
· simp [Value.materialize, h]
|
||
| _ => simp [Value.materialize]
|
||
|
||
/-- evalBinOpP on scalar vs 1×1 matrix (axiom: ewiseOpP is partial). -/
|
||
axiom evalBinOpP_scalar_matrix11 (op : BinOp) (x y : Float) (env : Env) :
|
||
(runPureM (evalBinOpP op (Value.scalar x) (Value.scalar y)) env).1 =
|
||
(runPureM (evalBinOpP op (Value.matrix 1 1 #[x]) (Value.matrix 1 1 #[y])) env).1
|
||
|
||
end Approach3
|
||
|
||
/-!
|
||
## Summary
|
||
|
||
### What compiled without sorry
|
||
|
||
**Approach 1:**
|
||
- `ValEq` setoid, `OctaveValue` quotient — ✓
|
||
- `scalar_eq_matrix11`, `boolean_*`, `fscalar_eq_scalar` — ✓
|
||
- `range_eq_matrix`, `empty_range_eq_empty` — ✓
|
||
- `normalize_idempotent` — ✓
|
||
- `ValEq.transport`, `OctaveValue.ind` — ✓
|
||
- `shape_congr` — ✓
|
||
|
||
**Approach 2:**
|
||
- `Bijection` structure (local, no Mathlib) — ✓
|
||
- `scalarMatrix11Bij` (full bijection, no sorry) — ✓
|
||
- `scalarRep_valEq_matrix11Rep`, `boolean_valEq_scalar` — ✓
|
||
- `scalar_iff_matrix11` transport theorem — ✓
|
||
|
||
**Approach 3:**
|
||
- `toFloatP` congruence lemmas — ✓
|
||
- `materialize_idempotent` — ✓
|
||
|
||
### What required axioms / sorry
|
||
|
||
- `evalBinOpP_scalar_matrix11`: blocked by `ewiseOpP` being `partial`
|
||
|
||
### Key findings
|
||
|
||
1. **`partial def` opacity** is the fundamental blocker for Approach 3.
|
||
Any function that transitively calls a `partial def` cannot be unfolded
|
||
by the kernel. This affects all `evalBinOpP` congruence lemmas.
|
||
|
||
2. **Approach 2** is the cleanest: zero sorry, fully constructive.
|
||
The `Bijection ScalarRep Matrix11Rep` captures the isomorphism.
|
||
No Mathlib needed — only local definitions.
|
||
|
||
3. **Approach 1** is best for semantic abstraction. The `OctaveValue`
|
||
quotient type lets you work with values modulo representation.
|
||
`ValEq.transport` provides HoTT-style proof transport.
|
||
|
||
4. **Float literal representation** (`(1 : Float)` vs `(1.0 : Float)`)
|
||
causes syntactic divergence in concrete BigStep examples; normalization
|
||
lemmas from Mathlib (or `native_decide`) are needed for those cases.
|
||
-/
|
||
|
||
end OctiveLean
|