From ba7462d7da5185456298f240f2e6f6dac240cc7e Mon Sep 17 00:00:00 2001 From: Son Ho Date: Fri, 12 Apr 2024 08:33:21 +0200 Subject: Update the core.num.checked_* definitions --- backends/lean/Base/Primitives/Scalar.lean | 264 ++++++++++++++++++++++++++---- 1 file changed, 233 insertions(+), 31 deletions(-) (limited to 'backends/lean/Base/Primitives/Scalar.lean') diff --git a/backends/lean/Base/Primitives/Scalar.lean b/backends/lean/Base/Primitives/Scalar.lean index 53bc7854..d1f7bee7 100644 --- a/backends/lean/Base/Primitives/Scalar.lean +++ b/backends/lean/Base/Primitives/Scalar.lean @@ -325,33 +325,65 @@ instance [Decide (Scalar.cMin ty ≤ v ∧ v ≤ Scalar.cMax ty)] : InBounds ty @[reducible, match_pattern] def Scalar.ofInt {ty : ScalarTy} (x : Int) [InBounds ty x] : Scalar ty := Scalar.ofIntCore x (Scalar.bound_suffices ty x InBounds.hInBounds) -@[simp] def Scalar.check_bounds (ty : ScalarTy) (x : Int) : Bool := +@[simp] abbrev Scalar.in_bounds (ty : ScalarTy) (x : Int) : Prop := + Scalar.min ty ≤ x ∧ x ≤ Scalar.max ty + +@[simp] abbrev Scalar.check_bounds (ty : ScalarTy) (x : Int) : Bool := (Scalar.cMin ty ≤ x || Scalar.min ty ≤ x) ∧ (x ≤ Scalar.cMax ty || x ≤ Scalar.max ty) -theorem Scalar.check_bounds_prop {ty : ScalarTy} {x : Int} (h: Scalar.check_bounds ty x) : - Scalar.min ty ≤ x ∧ x ≤ Scalar.max ty := by +theorem Scalar.check_bounds_imp_in_bounds {ty : ScalarTy} {x : Int} + (h: Scalar.check_bounds ty x) : + Scalar.in_bounds ty x := by simp at * have ⟨ hmin, hmax ⟩ := h have hbmin := Scalar.cMin_bound ty have hbmax := Scalar.cMax_bound ty cases hmin <;> cases hmax <;> apply And.intro <;> linarith +theorem Scalar.check_bounds_eq_in_bounds (ty : ScalarTy) (x : Int) : + Scalar.check_bounds ty x ↔ Scalar.in_bounds ty x := by + constructor <;> intro h + . apply (check_bounds_imp_in_bounds h) + . simp_all + -- Further thoughts: look at what has been done here: -- https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Data/Fin/Basic.lean -- and -- https://github.com/leanprover-community/mathlib4/blob/master/Mathlib/Data/UInt.lean -- which both contain a fair amount of reasoning already! -def Scalar.tryMk (ty : ScalarTy) (x : Int) : Result (Scalar ty) := +def Scalar.tryMkOpt (ty : ScalarTy) (x : Int) : Option (Scalar ty) := if h:Scalar.check_bounds ty x then -- If we do: -- ``` - -- let ⟨ hmin, hmax ⟩ := (Scalar.check_bounds_prop h) + -- let ⟨ hmin, hmax ⟩ := (Scalar.check_bounds_imp_in_bounds h) -- Scalar.ofIntCore x hmin hmax -- ``` -- then normalization blocks (for instance, some proofs which use reflexivity fail). -- However, the version below doesn't block reduction (TODO: investigate): - ok (Scalar.ofIntCore x (Scalar.check_bounds_prop h)) - else fail integerOverflow + some (Scalar.ofIntCore x (Scalar.check_bounds_imp_in_bounds h)) + else none + +def Result.ofOption {a : Type u} (x : Option a) (e : Error) : Result a := + match x with + | some x => ok x + | none => fail e + +def Scalar.tryMk (ty : ScalarTy) (x : Int) : Result (Scalar ty) := + Result.ofOption (tryMkOpt ty x) integerOverflow + +theorem Scalar.tryMk_eq (ty : ScalarTy) (x : Int) : + match tryMk ty x with + | ok y => y.val = x ∧ in_bounds ty x + | fail _ => ¬ (in_bounds ty x) + | _ => False := by + simp [tryMk, ofOption, tryMkOpt, ofIntCore] + have h := check_bounds_eq_in_bounds ty x + split_ifs <;> simp_all + +@[simp] theorem Scalar.tryMk_eq_div (ty : ScalarTy) (x : Int) : + tryMk ty x = div ↔ False := by + simp [tryMk, ofOption, tryMkOpt] + split_ifs <;> simp def Scalar.neg {ty : ScalarTy} (x : Scalar ty) : Result (Scalar ty) := Scalar.tryMk ty (- x.val) @@ -583,42 +615,117 @@ instance {ty} : HAnd (Scalar ty) (Scalar ty) (Scalar ty) where /- A helper function that converts failure to none and success to some TODO: move up to Base module? -/ -@[simp] abbrev Option.ofResult : Result (Scalar ty) → Result (Option (Scalar ty)) - | Result.ret z => Result.ret (some z) - | Result.fail _ => Result.ret none - | Result.div => Result.div +def Option.ofResult {a : Type u} (x : Result a) : + Option a := + match x with + | ok x => some x + | _ => none /- [core::num::{T}::checked_add] -/ -def core.num.checked_add (x y : Scalar ty) : Result (Option (Scalar ty)) := +def core.num.checked_add (x y : Scalar ty) : Option (Scalar ty) := Option.ofResult (x + y) +def U8.checked_add (x y : U8) : Option U8 := core.num.checked_add x y +def U16.checked_add (x y : U16) : Option U16 := core.num.checked_add x y +def U32.checked_add (x y : U32) : Option U32 := core.num.checked_add x y +def U64.checked_add (x y : U64) : Option U64 := core.num.checked_add x y +def U128.checked_add (x y : U128) : Option U128 := core.num.checked_add x y +def Usize.checked_add (x y : Usize) : Option Usize := core.num.checked_add x y +def I8.checked_add (x y : I8) : Option I8 := core.num.checked_add x y +def I16.checked_add (x y : I16) : Option I16 := core.num.checked_add x y +def I32.checked_add (x y : I32) : Option I32 := core.num.checked_add x y +def I64.checked_add (x y : I64) : Option I64 := core.num.checked_add x y +def I128.checked_add (x y : I128) : Option I128 := core.num.checked_add x y +def Isize.checked_add (x y : Isize) : Option Isize := core.num.checked_add x y + /- [core::num::{T}::checked_sub] -/ -def core.num.checked_sub (x y : Scalar ty) : Result (Option (Scalar ty)) := +def core.num.checked_sub (x y : Scalar ty) : Option (Scalar ty) := Option.ofResult (x - y) +def U8.checked_sub (x y : U8) : Option U8 := core.num.checked_sub x y +def U16.checked_sub (x y : U16) : Option U16 := core.num.checked_sub x y +def U32.checked_sub (x y : U32) : Option U32 := core.num.checked_sub x y +def U64.checked_sub (x y : U64) : Option U64 := core.num.checked_sub x y +def U128.checked_sub (x y : U128) : Option U128 := core.num.checked_sub x y +def Usize.checked_sub (x y : Usize) : Option Usize := core.num.checked_sub x y +def I8.checked_sub (x y : I8) : Option I8 := core.num.checked_sub x y +def I16.checked_sub (x y : I16) : Option I16 := core.num.checked_sub x y +def I32.checked_sub (x y : I32) : Option I32 := core.num.checked_sub x y +def I64.checked_sub (x y : I64) : Option I64 := core.num.checked_sub x y +def I128.checked_sub (x y : I128) : Option I128 := core.num.checked_sub x y +def Isize.checked_sub (x y : Isize) : Option Isize := core.num.checked_sub x y + /- [core::num::{T}::checked_mul] -/ -def core.num.checked_mul (x y : Scalar ty) : Result (Option (Scalar ty)) := +def core.num.checked_mul (x y : Scalar ty) : Option (Scalar ty) := Option.ofResult (x * y) +def U8.checked_mul (x y : U8) : Option U8 := core.num.checked_mul x y +def U16.checked_mul (x y : U16) : Option U16 := core.num.checked_mul x y +def U32.checked_mul (x y : U32) : Option U32 := core.num.checked_mul x y +def U64.checked_mul (x y : U64) : Option U64 := core.num.checked_mul x y +def U128.checked_mul (x y : U128) : Option U128 := core.num.checked_mul x y +def Usize.checked_mul (x y : Usize) : Option Usize := core.num.checked_mul x y +def I8.checked_mul (x y : I8) : Option I8 := core.num.checked_mul x y +def I16.checked_mul (x y : I16) : Option I16 := core.num.checked_mul x y +def I32.checked_mul (x y : I32) : Option I32 := core.num.checked_mul x y +def I64.checked_mul (x y : I64) : Option I64 := core.num.checked_mul x y +def I128.checked_mul (x y : I128) : Option I128 := core.num.checked_mul x y +def Isize.checked_mul (x y : Isize) : Option Isize := core.num.checked_mul x y + /- [core::num::{T}::checked_div] -/ -def core.num.checked_div (x y : Scalar ty) : Result (Option (Scalar ty)) := +def core.num.checked_div (x y : Scalar ty) : Option (Scalar ty) := Option.ofResult (x / y) -/- [core::num::{T}::checked_mod] -/ -def core.num.checked_mod (x y : Scalar ty) : Result (Option (Scalar ty)) := +def U8.checked_div (x y : U8) : Option U8 := core.num.checked_div x y +def U16.checked_div (x y : U16) : Option U16 := core.num.checked_div x y +def U32.checked_div (x y : U32) : Option U32 := core.num.checked_div x y +def U64.checked_div (x y : U64) : Option U64 := core.num.checked_div x y +def U128.checked_div (x y : U128) : Option U128 := core.num.checked_div x y +def Usize.checked_div (x y : Usize) : Option Usize := core.num.checked_div x y +def I8.checked_div (x y : I8) : Option I8 := core.num.checked_div x y +def I16.checked_div (x y : I16) : Option I16 := core.num.checked_div x y +def I32.checked_div (x y : I32) : Option I32 := core.num.checked_div x y +def I64.checked_div (x y : I64) : Option I64 := core.num.checked_div x y +def I128.checked_div (x y : I128) : Option I128 := core.num.checked_div x y +def Isize.checked_div (x y : Isize) : Option Isize := core.num.checked_div x y + +/- [core::num::{T}::checked_rem] -/ +def core.num.checked_rem (x y : Scalar ty) : Option (Scalar ty) := Option.ofResult (x % y) +def U8.checked_rem (x y : U8) : Option U8 := core.num.checked_rem x y +def U16.checked_rem (x y : U16) : Option U16 := core.num.checked_rem x y +def U32.checked_rem (x y : U32) : Option U32 := core.num.checked_rem x y +def U64.checked_rem (x y : U64) : Option U64 := core.num.checked_rem x y +def U128.checked_rem (x y : U128) : Option U128 := core.num.checked_rem x y +def Usize.checked_rem (x y : Usize) : Option Usize := core.num.checked_rem x y +def I8.checked_rem (x y : I8) : Option I8 := core.num.checked_rem x y +def I16.checked_rem (x y : I16) : Option I16 := core.num.checked_rem x y +def I32.checked_rem (x y : I32) : Option I32 := core.num.checked_rem x y +def I64.checked_rem (x y : I64) : Option I64 := core.num.checked_rem x y +def I128.checked_rem (x y : I128) : Option I128 := core.num.checked_rem x y +def Isize.checked_rem (x y : Isize) : Option Isize := core.num.checked_rem x y + +theorem Scalar.add_equiv {ty} {x y : Scalar ty} : + match x + y with + | ok z => Scalar.in_bounds ty (↑x + ↑y) ∧ (↑z : Int) = ↑x + ↑y + | fail _ => ¬ (Scalar.in_bounds ty (↑x + ↑y)) + | _ => ⊥ := by + -- Applying the unfoldings only inside the match + conv in _ + _ => unfold HAdd.hAdd instHAddScalarResult; simp [add] + have h := tryMk_eq ty (↑x + ↑y) + simp [in_bounds] at h + split at h <;> simp_all [check_bounds_eq_in_bounds] + -- Generic theorem - shouldn't be used much @[pspec] theorem Scalar.add_spec {ty} {x y : Scalar ty} (hmin : Scalar.min ty ≤ ↑x + y.val) (hmax : ↑x + ↑y ≤ Scalar.max ty) : (∃ z, x + y = ok z ∧ (↑z : Int) = ↑x + ↑y) := by - -- Applying the unfoldings only on the left - conv => congr; ext; lhs; unfold HAdd.hAdd instHAddScalarResult; simp [add, tryMk] - split - . simp [pure]; rfl - . tauto + have h := @add_equiv ty x y + split at h <;> simp_all + apply h theorem Scalar.add_unsigned_spec {ty} (s: ¬ ty.isSigned) {x y : Scalar ty} (hmax : ↑x + ↑y ≤ Scalar.max ty) : @@ -684,17 +791,36 @@ theorem Scalar.add_unsigned_spec {ty} (s: ¬ ty.isSigned) {x y : Scalar ty} ∃ z, x + y = ok z ∧ (↑z : Int) = ↑x + ↑y := Scalar.add_spec hmin hmax +theorem core.num.checked_add_spec {ty} {x y : Scalar ty} : + match core.num.checked_add x y with + | some z => Scalar.in_bounds ty (↑x + ↑y) ∧ ↑z = (↑x + ↑y : Int) + | none => ¬ (Scalar.in_bounds ty (↑x + ↑y)) := by + have h := Scalar.tryMk_eq ty (↑x + ↑y) + simp only [checked_add, Option.ofResult] + cases heq: x + y <;> simp_all <;> simp [HAdd.hAdd, Scalar.add] at heq + <;> simp [Add.add] at heq + <;> simp_all + +theorem Scalar.sub_equiv {ty} {x y : Scalar ty} : + match x - y with + | ok z => Scalar.in_bounds ty (↑x - ↑y) ∧ (↑z : Int) = ↑x - ↑y + | fail _ => ¬ (Scalar.in_bounds ty (↑x - ↑y)) + | _ => ⊥ := by + -- Applying the unfoldings only inside the match + conv in _ - _ => unfold HSub.hSub instHSubScalarResult; simp [sub] + have h := tryMk_eq ty (↑x - ↑y) + simp [in_bounds] at h + split at h <;> simp_all [check_bounds_eq_in_bounds] + -- Generic theorem - shouldn't be used much @[pspec] theorem Scalar.sub_spec {ty} {x y : Scalar ty} (hmin : Scalar.min ty ≤ ↑x - ↑y) (hmax : ↑x - ↑y ≤ Scalar.max ty) : ∃ z, x - y = ok z ∧ (↑z : Int) = ↑x - ↑y := by - conv => congr; ext; lhs; simp [HSub.hSub, sub, tryMk, Sub.sub] - split - . simp [pure] - rfl - . tauto + have h := @sub_equiv ty x y + split at h <;> simp_all + apply h theorem Scalar.sub_unsigned_spec {ty : ScalarTy} (s : ¬ ty.isSigned) {x y : Scalar ty} (hmin : Scalar.min ty ≤ ↑x - ↑y) : @@ -768,12 +894,33 @@ theorem Scalar.mul_spec {ty} {x y : Scalar ty} (hmax : ↑x * ↑y ≤ Scalar.max ty) : ∃ z, x * y = ok z ∧ (↑z : Int) = ↑x * ↑y := by conv => congr; ext; lhs; simp [HMul.hMul] - simp [mul, tryMk] - split + simp [mul, tryMk, tryMkOpt, ofOption] + split_ifs . simp [pure] rfl . tauto +theorem core.num.checked_sub_spec {ty} {x y : Scalar ty} : + match core.num.checked_sub x y with + | some z => Scalar.in_bounds ty (↑x - ↑y) ∧ ↑z = (↑x - ↑y : Int) + | none => ¬ (Scalar.in_bounds ty (↑x - ↑y)) := by + have h := Scalar.tryMk_eq ty (↑x - ↑y) + simp only [checked_sub, Option.ofResult] + have add_neg_eq : x.val + (-y.val) = x.val - y.val := by omega -- TODO: why do we need this?? + cases heq: x - y <;> simp_all <;> simp only [HSub.hSub, Scalar.sub, Sub.sub, Int.sub] at heq + <;> simp_all + +theorem Scalar.mul_equiv {ty} {x y : Scalar ty} : + match x * y with + | ok z => Scalar.in_bounds ty (↑x * ↑y) ∧ (↑z : Int) = ↑x * ↑y + | fail _ => ¬ (Scalar.in_bounds ty (↑x * ↑y)) + | _ => ⊥ := by + -- Applying the unfoldings only inside the match + conv in _ * _ => unfold HMul.hMul instHMulScalarResult; simp [mul] + have h := tryMk_eq ty (↑x * ↑y) + simp [in_bounds] at h + split at h <;> simp_all [check_bounds_eq_in_bounds] + theorem Scalar.mul_unsigned_spec {ty} (s: ¬ ty.isSigned) {x y : Scalar ty} (hmax : ↑x * ↑y ≤ Scalar.max ty) : ∃ z, x * y = ok z ∧ (↑z : Int) = ↑x * ↑y := by @@ -838,6 +985,29 @@ theorem Scalar.mul_unsigned_spec {ty} (s: ¬ ty.isSigned) {x y : Scalar ty} ∃ z, x * y = ok z ∧ (↑z : Int) = ↑x * ↑y := Scalar.mul_spec hmin hmax +theorem core.num.checked_mul_spec {ty} {x y : Scalar ty} : + match core.num.checked_mul x y with + | some z => Scalar.in_bounds ty (↑x * ↑y) ∧ ↑z = (↑x * ↑y : Int) + | none => ¬ (Scalar.in_bounds ty (↑x * ↑y)) := by + have h := Scalar.tryMk_eq ty (↑x * ↑y) + simp only [checked_mul, Option.ofResult] + have : Int.mul ↑x ↑y = ↑x * ↑y := by simp -- TODO: why do we need this?? + cases heq: x * y <;> simp_all <;> simp only [HMul.hMul, Scalar.mul, Mul.mul] at heq + <;> simp [add_neg_eq] at heq + <;> simp_all + +theorem Scalar.div_equiv {ty} {x y : Scalar ty} : + match x / y with + | ok z => y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_div ↑x ↑y) ∧ (↑z : Int) = scalar_div ↑x ↑y + | fail _ => ¬ (y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_div ↑x ↑y)) + | _ => ⊥ := by + -- Applying the unfoldings only inside the match + conv in _ / _ => unfold HDiv.hDiv instHDivScalarResult; simp [div] + have h := tryMk_eq ty (scalar_div ↑x ↑y) + simp [in_bounds] at h + split_ifs <;> simp <;> + split at h <;> simp_all [check_bounds_eq_in_bounds] + -- Generic theorem - shouldn't be used much @[pspec] theorem Scalar.div_spec {ty} {x y : Scalar ty} @@ -846,7 +1016,7 @@ theorem Scalar.div_spec {ty} {x y : Scalar ty} (hmax : scalar_div ↑x ↑y ≤ Scalar.max ty) : ∃ z, x / y = ok z ∧ (↑z : Int) = scalar_div ↑x ↑y := by simp [HDiv.hDiv, div, Div.div] - simp [tryMk, *] + simp [tryMk, tryMkOpt, ofOption, *] rfl theorem Scalar.div_unsigned_spec {ty} (s: ¬ ty.isSigned) (x : Scalar ty) {y : Scalar ty} @@ -932,6 +1102,28 @@ theorem Scalar.div_unsigned_spec {ty} (s: ¬ ty.isSigned) (x : Scalar ty) {y : S ∃ z, x / y = ok z ∧ (↑z : Int) = scalar_div ↑x ↑y := Scalar.div_spec hnz hmin hmax +theorem core.num.checked_div_spec {ty} {x y : Scalar ty} : + match core.num.checked_div x y with + | some z => y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_div ↑x ↑y) ∧ ↑z = (scalar_div ↑x ↑y : Int) + | none => ¬ (y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_div ↑x ↑y)) := by + have h := Scalar.tryMk_eq ty (scalar_div ↑x ↑y) + simp only [checked_div, Option.ofResult] + cases heq0: (y.val = 0 : Bool) <;> + cases heq1: x / y <;> simp_all <;> simp only [HDiv.hDiv, Scalar.div, Div.div] at heq1 + <;> simp_all + +theorem Scalar.rem_equiv {ty} {x y : Scalar ty} : + match x % y with + | ok z => y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_rem ↑x ↑y) ∧ (↑z : Int) = scalar_rem ↑x ↑y + | fail _ => ¬ (y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_rem ↑x ↑y)) + | _ => ⊥ := by + -- Applying the unfoldings only inside the match + conv in _ % _ => unfold HMod.hMod instHModScalarResult; simp [rem] + have h := tryMk_eq ty (scalar_rem ↑x ↑y) + simp [in_bounds] at h + split_ifs <;> simp <;> + split at h <;> simp_all [check_bounds_eq_in_bounds] + -- Generic theorem - shouldn't be used much @[pspec] theorem Scalar.rem_spec {ty} {x y : Scalar ty} @@ -940,7 +1132,7 @@ theorem Scalar.rem_spec {ty} {x y : Scalar ty} (hmax : scalar_rem ↑x ↑y ≤ Scalar.max ty) : ∃ z, x % y = ok z ∧ (↑z : Int) = scalar_rem ↑x ↑y := by simp [HMod.hMod, rem] - simp [tryMk, *] + simp [tryMk, tryMkOpt, ofOption, *] rfl theorem Scalar.rem_unsigned_spec {ty} (s: ¬ ty.isSigned) (x : Scalar ty) {y : Scalar ty} @@ -1019,6 +1211,16 @@ theorem Scalar.rem_unsigned_spec {ty} (s: ¬ ty.isSigned) (x : Scalar ty) {y : S ∃ z, x % y = ok z ∧ (↑z : Int) = scalar_rem ↑x ↑y := Scalar.rem_spec hnz hmin hmax +theorem core.num.checked_rem_spec {ty} {x y : Scalar ty} : + match core.num.checked_rem x y with + | some z => y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_rem ↑x ↑y) ∧ ↑z = (scalar_rem ↑x ↑y : Int) + | none => ¬ (y.val ≠ 0 ∧ Scalar.in_bounds ty (scalar_rem ↑x ↑y)) := by + have h := Scalar.tryMk_eq ty (scalar_rem ↑x ↑y) + simp only [checked_rem, Option.ofResult] + cases heq0: (y.val = 0 : Bool) <;> + cases heq1: x % y <;> simp_all <;> simp only [HMod.hMod, Scalar.rem, Mod.mod] at heq1 + <;> simp_all + -- ofIntCore -- TODO: typeclass? def Isize.ofIntCore := @Scalar.ofIntCore .Isize -- cgit v1.2.3