Skip to content

Commit 254a976

Browse files
authored
Merge branch 'main' into dev-hantao-autonumpy
2 parents d4b7f9c + a5ebaef commit 254a976

File tree

4 files changed

+529
-3
lines changed

4 files changed

+529
-3
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ name: CI
22

33
on:
44
push:
5-
branches: [ main, "*" ]
5+
branches: [ "main", "*" ]
66
pull_request:
7-
branches: [ main, "*" ]
7+
branches: [ "main", "*" ]
8+
89
workflow_dispatch:
910

1011
jobs:
@@ -118,4 +119,4 @@ jobs:
118119
path: |
119120
.lake/build/trace
120121
logs/
121-
*.log
122+
*.log

DafnySpecs/NpMax.lean

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import Std.Do
2+
3+
/-!
4+
# Array Maximum Specification
5+
6+
Port of `np_max.dfy` to idiomatic Lean 4.
7+
8+
This module demonstrates several approaches to finding the maximum element in an array:
9+
1. Runtime constraints via hypotheses (non-empty requirement)
10+
2. Compile-time constraints via dependent types
11+
3. Refinement types for non-empty arrays
12+
4. MPL-style specifications for future verification
13+
14+
The Dafny specification requires:
15+
- Precondition: array length > 0
16+
- Postcondition 1: result equals some element in the array
17+
- Postcondition 2: result is greater than or equal to all elements
18+
-/
19+
20+
namespace DafnySpecs.NpMax
21+
22+
/-- Find maximum element in a non-empty array.
23+
24+
The hypothesis `h` ensures the array is non-empty at the call site.
25+
This mirrors the Dafny `requires a.Length > 0` clause.
26+
-/
27+
def max (a : Array Int) (h : a.size > 0) : Int :=
28+
a.foldl (init := a[0]'(h)) max
29+
30+
/-- Specification theorem: the maximum is an element of the array -/
31+
theorem max_exists (a : Array Int) (h : a.size > 0) :
32+
∃ i : Fin a.size, max a h = a[i] := by
33+
sorry -- Proof would use properties of foldl and max
34+
35+
/-- Specification theorem: the maximum is greater than or equal to all elements -/
36+
theorem max_universal (a : Array Int) (h : a.size > 0) :
37+
∀ i : Fin a.size, a[i] ≤ max a h := by
38+
sorry -- Proof would use induction on foldl
39+
40+
/-- Combined specification capturing both Dafny postconditions -/
41+
theorem max_spec (a : Array Int) (h : a.size > 0) :
42+
(∃ i : Fin a.size, max a h = a[i]) ∧
43+
(∀ i : Fin a.size, a[i] ≤ max a h) := by
44+
constructor
45+
· exact max_exists a h
46+
· exact max_universal a h
47+
48+
/-- MPL-style specification comment for future verification:
49+
50+
⦃ a.size > 0 ⦄
51+
max a
52+
⦃ λ res, (∃ i : Fin a.size, res = a[i]) ∧ (∀ i : Fin a.size, a[i] ≤ res) ⦄
53+
54+
When MPL tactics are available, this can be proved using `mspec` or `mvcgen`.
55+
-/
56+
57+
/- Alternative implementations with different trade-offs -/
58+
59+
section AlternativeImplementations
60+
61+
/-- Maximum using explicit recursion for clarity -/
62+
def maxRec (a : Array Int) (h : a.size > 0) : Int :=
63+
go 1 a[0]'(h)
64+
where
65+
go (i : Nat) (currMax : Int) : Int :=
66+
if hi : i < a.size then
67+
go (i + 1) (max currMax a[i])
68+
else
69+
currMax
70+
71+
/-- Maximum that returns both the value and its index -/
72+
def maxWithIndex (a : Array Int) (h : a.size > 0) : Int × Fin a.size :=
73+
a.foldlIdx (init := (a[0]'(h), ⟨0, h⟩)) fun i (currMax, idx) val =>
74+
if val > currMax then (val, i) else (currMax, idx)
75+
76+
/-- Theorem: maxWithIndex returns a valid maximum and witness index -/
77+
theorem maxWithIndex_correct (a : Array Int) (h : a.size > 0) :
78+
let (m, idx) := maxWithIndex a h
79+
m = a[idx] ∧ ∀ i : Fin a.size, a[i] ≤ m := by
80+
sorry
81+
82+
end AlternativeImplementations
83+
84+
/- Vector approach using compile-time size checking -/
85+
86+
section VectorApproach
87+
88+
/-- Maximum for vectors with compile-time non-empty guarantee -/
89+
def maxVec {n : Nat} (a : Vector Int (n + 1)) : Int :=
90+
a.toArray.foldl (init := a.get ⟨0, Nat.zero_lt_succ n⟩) max
91+
92+
/-- Vector maximum satisfies the existence property -/
93+
theorem maxVec_exists {n : Nat} (a : Vector Int (n + 1)) :
94+
∃ i : Fin (n + 1), maxVec a = a.get i := by
95+
sorry
96+
97+
/-- Vector maximum satisfies the universal property -/
98+
theorem maxVec_universal {n : Nat} (a : Vector Int (n + 1)) :
99+
∀ i : Fin (n + 1), a.get i ≤ maxVec a := by
100+
sorry
101+
102+
end VectorApproach
103+
104+
/- Refinement type approach -/
105+
106+
section RefinementTypes
107+
108+
/-- Non-empty array as a refinement type -/
109+
abbrev NonEmptyArray (α : Type) := {arr : Array α // arr.size > 0}
110+
111+
/-- Maximum for non-empty arrays using refinement types -/
112+
def maxNonEmpty (a : NonEmptyArray Int) : Int :=
113+
a.val.foldl (init := a.val[0]'(a.property)) max
114+
115+
/-- Get an element from a non-empty array -/
116+
def NonEmptyArray.get (a : NonEmptyArray α) (i : Fin a.val.size) : α :=
117+
a.val[i]
118+
119+
/-- Refinement type version preserves specifications -/
120+
theorem maxNonEmpty_spec (a : NonEmptyArray Int) :
121+
(∃ i : Fin a.val.size, maxNonEmpty a = a.get i) ∧
122+
(∀ i : Fin a.val.size, a.get i ≤ maxNonEmpty a) := by
123+
sorry
124+
125+
end RefinementTypes
126+
127+
/- Polymorphic versions -/
128+
129+
section Polymorphic
130+
131+
/-- Polymorphic maximum for any linearly ordered type -/
132+
def maxPoly {α : Type} [LinearOrder α] (a : Array α) (h : a.size > 0) : α :=
133+
a.foldl (init := a[0]'(h)) max
134+
135+
/-- Maximum with custom comparison function -/
136+
def maxBy {α : Type} (a : Array α) (h : a.size > 0) (cmp : α → α → Bool) : α :=
137+
a.foldl (init := a[0]'(h)) fun x y => if cmp y x then y else x
138+
139+
/-- Maximum by key extraction -/
140+
def maxByKey {α β : Type} [LinearOrder β] (a : Array α) (h : a.size > 0) (key : α → β) : α :=
141+
a.foldl (init := a[0]'(h)) fun x y => if key y > key x then y else x
142+
143+
end Polymorphic
144+
145+
/- Properties and theorems -/
146+
147+
section Properties
148+
149+
/-- Maximum is idempotent -/
150+
theorem max_singleton (x : Int) :
151+
max #[x] (by simp) = x := by
152+
unfold max
153+
simp
154+
155+
/-- Maximum of two elements -/
156+
theorem max_pair (x y : Int) :
157+
max #[x, y] (by simp) = Int.max x y := by
158+
unfold max
159+
simp [Int.max]
160+
161+
/-- Maximum is preserved under array concatenation -/
162+
theorem max_append (a b : Array Int) (ha : a.size > 0) (hb : b.size > 0) :
163+
max (a ++ b) (by simp [ha, hb]) = Int.max (max a ha) (max b hb) := by
164+
sorry
165+
166+
/-- Maximum respects monotone transformations -/
167+
theorem max_map_mono {f : Int → Int} (hf : Monotone f) (a : Array Int) (h : a.size > 0) :
168+
f (max a h) = max (a.map f) (by simp [h]) := by
169+
sorry
170+
171+
end Properties
172+
173+
/- Option-based API for more idiomatic Lean -/
174+
175+
section OptionAPI
176+
177+
/-- Safe maximum that returns None for empty arrays -/
178+
def maxOption (a : Array Int) : Option Int :=
179+
if h : a.size > 0 then
180+
some (max a h)
181+
else
182+
none
183+
184+
/-- Option version specification -/
185+
theorem maxOption_spec (a : Array Int) :
186+
match maxOption a with
187+
| none => a.size = 0
188+
| some m => a.size > 0 ∧ (∃ i : Fin a.size, m = a[i]) ∧ (∀ i : Fin a.size, a[i] ≤ m) := by
189+
sorry
190+
191+
/-- Maximum with default value for empty arrays -/
192+
def maxWithDefault (a : Array Int) (default : Int) : Int :=
193+
maxOption a |>.getD default
194+
195+
end OptionAPI
196+
197+
section Examples
198+
199+
/- Example usage:
200+
#eval max #[3, 1, 4, 1, 5, 9] (by simp)
201+
-- Output: 9
202+
203+
#eval maxWithIndex #[3, 1, 4, 1, 5, 9] (by simp)
204+
-- Output: (9, 5)
205+
206+
#eval maxOption #[]
207+
-- Output: none
208+
209+
#eval maxOption #[42]
210+
-- Output: some 42
211+
212+
#check maxVec ⟨#[1, 2, 3], rfl⟩
213+
-- Type: Int
214+
-/
215+
216+
end Examples
217+
218+
end DafnySpecs.NpMax

DafnySpecs/NpMultiply.lean

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/-!
2+
# Array Multiplication Specification
3+
4+
Port of `np_multiply.dfy` to idiomatic Lean 4.
5+
6+
This module demonstrates several approaches to specifying element-wise array multiplication:
7+
1. Runtime constraints via hypotheses
8+
2. Compile-time constraints via dependent types
9+
3. MPL-style specifications for future verification
10+
-/
11+
12+
namespace DafnySpecs.NpMultiply
13+
14+
/-- Element-wise multiplication of arrays with runtime size checking.
15+
16+
The hypothesis `h` ensures arrays have equal length at the call site.
17+
This mirrors the Dafny `requires` clause.
18+
-/
19+
def multiply (a b : Array Int) (_ : a.size = b.size) : Array Int :=
20+
Array.zipWith (· * ·) a b
21+
22+
/-- Specification theorem: result has same length as inputs -/
23+
theorem multiply_length (a b : Array Int) (h : a.size = b.size) :
24+
(multiply a b h).size = a.size := by
25+
simp only [multiply, Array.size_zipWith, h, Nat.min_self]
26+
27+
/-- Specification theorem: element-wise correctness -/
28+
theorem multiply_elem (a b : Array Int) (h : a.size = b.size) (i : Nat) (hi : i < a.size) :
29+
(multiply a b h)[i]'(by simp [multiply_length, hi]) = a[i] * b[i] := by
30+
sorry
31+
32+
section VectorApproach
33+
34+
/-- Multiplication using vectors with compile-time size checking.
35+
36+
This approach eliminates the need for runtime hypotheses by encoding
37+
the size constraint in the type system.
38+
-/
39+
def multiplyVec {n : Nat} (a b : Vector Int n) : Vector Int n :=
40+
⟨Array.zipWith (· * ·) a.toArray b.toArray, by simp [Array.size_zipWith]⟩
41+
42+
/-- Vector multiplication preserves all elements correctly -/
43+
theorem multiplyVec_elem {n : Nat} (a b : Vector Int n) (i : Fin n) :
44+
(multiplyVec a b).get i = a.get i * b.get i := by
45+
simp [multiplyVec, Vector.get]
46+
47+
end VectorApproach
48+
49+
section GeneralizedApproach
50+
51+
/-- Polymorphic multiplication for any type with a Mul instance -/
52+
def multiplyPoly {α : Type} [Mul α] (a b : Array α) (_ : a.size = b.size) : Array α :=
53+
Array.zipWith (· * ·) a b
54+
55+
/-- Multiplication for non-empty arrays (refinement type approach) -/
56+
def multiplyNonEmpty (a b : {arr : Array Int // arr.size > 0})
57+
(h : a.val.size = b.val.size) : {arr : Array Int // arr.size > 0} :=
58+
⟨Array.zipWith (· * ·) a.val b.val, by
59+
simp only [Array.size_zipWith, h, Nat.min_self]
60+
exact b.property⟩
61+
62+
end GeneralizedApproach
63+
64+
section Properties
65+
66+
/-- Multiplication is commutative when the element type is commutative -/
67+
theorem multiply_comm (a b : Array Int) (h : a.size = b.size) :
68+
multiply a b h = multiply b a h.symm := by
69+
sorry
70+
71+
/-- Multiplication is associative (with appropriate size constraints) -/
72+
theorem multiply_assoc (a b c : Array Int)
73+
(hab : a.size = b.size) (hbc : b.size = c.size) :
74+
multiply (multiply a b hab) c (sorry : (multiply a b hab).size = c.size) =
75+
multiply a (multiply b c hbc) (sorry : a.size = (multiply b c hbc).size) := by
76+
sorry
77+
78+
/-- One array is the identity element -/
79+
def ones (n : Nat) : Array Int := Array.replicate n 1
80+
81+
theorem multiply_one (a : Array Int) :
82+
multiply a (ones a.size) (by simp only [ones, Array.size_replicate]) = a := by
83+
sorry
84+
85+
/-- Zero array is the zero element (multiplication by zero) -/
86+
def zeros (n : Nat) : Array Int := Array.replicate n 0
87+
88+
theorem multiply_zero (a : Array Int) :
89+
multiply a (zeros a.size) (by simp only [zeros, Array.size_replicate]) = zeros a.size := by
90+
sorry
91+
92+
/-- Multiplication distributes over addition -/
93+
theorem multiply_add_distrib (a b c : Array Int)
94+
(hab : a.size = b.size) (hac : a.size = c.size) :
95+
multiply a (Array.zipWith (· + ·) b c) (sorry : a.size = (Array.zipWith (· + ·) b c).size) =
96+
Array.zipWith (· + ·) (multiply a b hab) (multiply a c hac) := by
97+
sorry
98+
99+
end Properties
100+
101+
section Examples
102+
103+
#check multiply #[1, 2, 3] #[4, 5, 6] rfl
104+
105+
end Examples
106+
107+
end DafnySpecs.NpMultiply

0 commit comments

Comments
 (0)