crosslang/octive-lean/OctiveLean/ValueEquiv.lean
Maximus Gorog 6592cd058d Add 'octive-lean/' from commit '4b6fcec565a170d7029d4ccba21be2ecd0512d13'
git-subtree-dir: octive-lean
git-subtree-mainline: fd3d42ae33
git-subtree-split: 4b6fcec565
2026-05-12 02:59:14 -06:00

275 lines
9.8 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.

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