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
0 commit comments