Compare commits

...

109 Commits

Author SHA1 Message Date
Kim Morrison
76a8cc68e8 cleanup 2025-05-18 18:29:53 +10:00
Kim Morrison
251806c18f . 2025-05-16 10:46:12 +10:00
Kim Morrison
5d9309a8e9 . 2025-05-16 10:40:33 +10:00
Kim Morrison
ee7d0d8774 cleanup 2025-05-16 10:11:43 +10:00
Kim Morrison
5e6e62b6a3 cleanup 2025-05-16 10:09:55 +10:00
Kim Morrison
69228571dd Merge remote-tracking branch 'origin/master' into qsort_grind 2025-05-16 10:04:59 +10:00
Kim Morrison
392f954416 Merge branch 'qsort_grind' of github.com:leanprover/lean4 into qsort_grind 2025-05-16 10:04:41 +10:00
Kim Morrison
00af6200c0 nicer bounds 2025-05-16 00:12:51 +10:00
Kim Morrison
dd8f47a147 . 2025-05-15 23:23:16 +10:00
Kim Morrison
21aa0b1003 cleanup 2025-05-15 23:20:58 +10:00
Kim Morrison
0ede320642 cleanup 2025-05-15 23:16:17 +10:00
Kim Morrison
7b94313938 drop lots of arguments 2025-05-15 23:08:03 +10:00
Kim Morrison
7f5fe53c8d surreptitious renaming of variables 2025-05-15 23:02:49 +10:00
Kim Morrison
31a29292d5 renaming variables in preparation for fixing algorithm 2025-05-15 22:42:00 +10:00
Kim Morrison
ea57fc628d . 2025-05-15 22:35:38 +10:00
Kim Morrison
5a474b6998 . 2025-05-15 22:32:15 +10:00
Kim Morrison
ab0420e6b5 revert 2025-05-15 22:11:09 +10:00
Kim Morrison
b8394cb038 . 2025-05-15 21:59:46 +10:00
Kim Morrison
d90ed0bdf7 Merge remote-tracking branch 'origin/master' into HEAD 2025-05-15 21:57:56 +10:00
Kim Morrison
923a746398 . 2025-05-14 10:23:44 +10:00
Kim Morrison
cb5a47eb31 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-05-14 10:05:16 +10:00
Joachim Breitner
0a247f7e09 Adjust to fun_induction now unfolding the function application 2025-05-13 13:58:00 +02:00
Joachim Breitner
db7ec4ea58 Merge branch 'master' of https://github.com/leanprover/lean4 into qsort_grind 2025-05-13 13:50:50 +02:00
Kim Morrison
c942ef64e7 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-05-13 09:14:20 +10:00
Kim Morrison
c4c6402694 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-05-12 14:54:18 +10:00
Kim Morrison
89c2d97f41 grinding away at qsort 2025-05-03 21:04:32 +02:00
Kim Morrison
4b705a4ec6 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-05-03 19:44:30 +02:00
Kim Morrison
0309912b3a continue cleanup 2025-05-03 15:57:49 +02:00
Kim Morrison
cc49f768e7 Merge branch 'master' into qsort_grind 2025-05-03 14:10:59 +02:00
Kim Morrison
8d33e5af4f fixes 2025-04-30 14:55:45 +02:00
Kim Morrison
aeb8745c01 add 'module' 2025-04-30 14:41:56 +02:00
Kim Morrison
96ec0fb473 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-30 14:40:19 +02:00
Kim Morrison
1c5d411191 merge master 2025-04-28 19:00:34 +02:00
Kim Morrison
7c21a569cf fix copyright header 2025-04-17 12:28:28 +10:00
Kim Morrison
9307eb4755 . 2025-04-17 12:24:49 +10:00
Kim Morrison
df783b696e . 2025-04-17 12:22:26 +10:00
Kim Morrison
997394a924 . 2025-04-17 12:19:47 +10:00
Kim Morrison
97e8ba0951 Merge branch 'qsort_grind' of github.com:leanprover/lean4 into qsort_grind 2025-04-17 11:48:40 +10:00
Kim Morrison
9249be477b Merge remote-tracking branch 'origin/vector_perm' into qsort_grind 2025-04-17 11:48:08 +10:00
Kim Morrison
fb74c806b1 chore: reproduce Array.Perm API for Vector.Perm 2025-04-17 11:46:35 +10:00
Kim Morrison
29aaad2f5d chore: reproduce Array.Perm API for Vector.Perm 2025-04-17 11:46:31 +10:00
Kim Morrison
d6cc916fbd . 2025-04-17 11:36:53 +10:00
Kim Morrison
619a5ba183 comment the long proof 2025-04-16 19:54:34 +10:00
Kim Morrison
bd9b467b89 after talking to joachim 2025-04-16 19:24:28 +10:00
Kim Morrison
b789981952 drop hypothesis 2025-04-14 04:27:39 +02:00
Kim Morrison
ce2a9b8924 . 2025-04-14 04:25:03 +02:00
Kim Morrison
78303008a3 . 2025-04-14 04:00:04 +02:00
Kim Morrison
225acf5814 . 2025-04-14 03:43:56 +02:00
Kim Morrison
136691d6f4 cleanup without lift_lets 2025-04-14 03:42:27 +02:00
Kim Morrison
0ef442d6ba . 2025-04-14 03:37:16 +02:00
Kim Morrison
5fc5fe86a9 cleanup 2025-04-14 03:28:48 +02:00
Kim Morrison
60fe66177a Merge branch 'qsort_grind' of github.com:leanprover/lean4 into qsort_grind 2025-04-14 02:56:17 +02:00
Kim Morrison
635f17a1e2 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-14 02:55:43 +02:00
Kim Morrison
0cd1ab0184 . 2025-04-13 15:44:45 +10:00
Kim Morrison
72556527ea . 2025-04-13 15:42:30 +10:00
Kim Morrison
1270d0fe14 cleanup 2025-04-13 15:41:15 +10:00
Kim Morrison
58f978e289 all proved 2025-04-13 15:14:18 +10:00
Kim Morrison
0bf50a9d53 progress 2025-04-13 14:47:29 +10:00
Kim Morrison
51142d0be6 progress 2025-04-13 14:14:36 +10:00
Kim Morrison
d5fb690053 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-13 14:06:53 +10:00
Kim Morrison
8101b9916b Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-13 13:02:13 +10:00
Kim Morrison
5cb381e599 Merge branch 'qsort_grind' of github.com:leanprover/lean4 into qsort_grind 2025-04-13 12:39:55 +10:00
Kim Morrison
7af8d4d33b . 2025-04-13 12:39:48 +10:00
Kim Morrison
cf2cde1368 . 2025-04-13 12:08:47 +10:00
Kim Morrison
d0302369fa Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-13 11:55:23 +10:00
Kim Morrison
d84c7a2980 . 2025-04-11 17:02:35 +10:00
Kim Morrison
f8e67154c7 . 2025-04-11 07:44:47 +02:00
Kim Morrison
eff7237a06 merge extract_lets 2025-04-11 05:16:31 +02:00
Kim Morrison
4a82c38a9b . 2025-04-11 05:13:57 +02:00
Kim Morrison
ae713b4916 . 2025-04-11 05:00:37 +02:00
Kim Morrison
0d5e7142b2 Merge remote-tracking branch 'origin/master' into qsort_grind 2025-04-11 04:41:29 +02:00
Kim Morrison
0bdba0534b . 2025-04-03 11:17:28 +11:00
Kim Morrison
5b67df3160 using grind 2025-03-31 12:14:33 +11:00
Kim Morrison
09023b055f cleanup 2025-03-31 10:56:09 +11:00
Kim Morrison
2fa2d6c55e merge master 2025-03-31 10:41:15 +11:00
Kyle Miller
dd9ad37716 linter 2024-12-31 14:28:41 -05:00
Kyle Miller
649b2a9d23 improve mkLetFun, add instantiateForallWithParamInfos 2024-12-31 09:24:36 -05:00
Kyle Miller
180553e6be remove ellipsis 2024-12-30 15:49:07 -05:00
Kyle Miller
3967b8b786 eliminate extractNoDescend 2024-12-30 15:49:07 -05:00
Kyle Miller
0f0c0982cf comments, some reorg, some fixes 2024-12-30 15:49:07 -05:00
Kyle Miller
b502bd00a0 finish lift_lets tests 2024-12-30 15:49:07 -05:00
Kyle Miller
839bde6bd1 lift_lets 2024-12-30 15:49:07 -05:00
Kyle Miller
136f0e4443 fix transitive lifting bug 2024-12-30 15:49:07 -05:00
Kyle Miller
eefaff7e97 +lift mode 2024-12-30 15:49:07 -05:00
Kyle Miller
8d4b93fcc6 lots-of-lets test 2024-12-30 15:49:07 -05:00
Kyle Miller
2580411dae remove todo 2024-12-30 15:49:07 -05:00
Kyle Miller
9999a56563 wrote tests, made conv tactic 2024-12-30 15:49:05 -05:00
Kyle Miller
3277a0b237 finished first pass at full implementation, need to test 2024-12-30 15:48:43 -05:00
Kyle Miller
8bc4645d77 feat: extract_lets tactic
(first implementation of core algorithm, completely untested)
2024-12-30 15:48:42 -05:00
Kim Morrison
0b673c2ca1 rabbit hole 2024-12-05 22:29:47 +11:00
Kim Morrison
38f73da06c merge master 2024-12-05 20:57:48 +11:00
Kim Morrison
ba8a8c58c0 . 2024-12-02 16:43:02 +11:00
Kim Morrison
f32141bdce . 2024-12-02 09:34:44 +11:00
Kim Morrison
e453eeee40 . 2024-12-02 00:36:54 +11:00
Kim Morrison
d17a0c7259 . 2024-12-01 21:50:09 +11:00
Kim Morrison
1bc110bacc might be doable 2024-12-01 21:40:39 +11:00
Kim Morrison
6ef0286cf1 cleanup 2024-12-01 19:27:05 +11:00
Kim Morrison
09d6991016 merge 2024-12-01 19:14:32 +11:00
Kim Morrison
06c955902c import all 2024-12-01 19:12:25 +11:00
Kim Morrison
c94934e2ae Merge remote-tracking branch 'origin/master' into array_perm 2024-12-01 19:10:53 +11:00
Kim Morrison
77ad0769fb feat: Array.swap_perm 2024-12-01 19:10:26 +11:00
Kim Morrison
c32dfcd4b7 . 2024-12-01 19:09:46 +11:00
Kim Morrison
d120dfa545 Merge branch 'upstream_vector_lemmas' into qsort_spec 2024-12-01 17:27:57 +11:00
Kim Morrison
26e1de45df chore: upstream Vector lemmas 2024-12-01 17:26:04 +11:00
Kim Morrison
058f3a7374 wip 2024-12-01 17:25:45 +11:00
Kim Morrison
c646d39008 cleanup 2024-12-01 17:01:15 +11:00
Kim Morrison
af0693c164 benchmark 2024-11-28 11:34:43 +11:00
Kim Morrison
b437b7d510 benchmark 2024-11-28 11:34:17 +11:00
Kim Morrison
9c1ba76074 feat: remove runtime bounds checks and partial from qsort 2024-11-28 11:21:58 +11:00
4 changed files with 399 additions and 44 deletions

View File

@@ -27,23 +27,27 @@ Internal implementation of `Array.qsort`.
It does so by first swapping the elements at indices `lo`, `mid := (lo + hi) / 2`, and `hi`
if necessary so that the middle (pivot) element is at index `hi`.
We then iterate from `j = lo` to `j = hi`, with a pointer `i` starting at `lo`, and
We then iterate from `k = lo` to `k = hi`, with a pointer `i` starting at `lo`, and
swapping each element which is less than the pivot to position `i`, and then incrementing `i`.
-/
def qpartition {n} (as : Vector α n) (lt : α α Bool) (lo hi : Nat)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) : {m : Nat // lo m m < n} × Vector α n :=
def qpartition {n} (as : Vector α n) (lt : α α Bool) (lo hi : Nat) (w : lo hi := by omega)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) : {m : Nat // lo m m hi} × Vector α n :=
let mid := (lo + hi) / 2
let as := if lt as[mid] as[lo] then as.swap lo mid else as
let as := if lt as[hi] as[lo] then as.swap lo hi else as
let as := if lt as[mid] as[hi] then as.swap mid hi else as
let pivot := as[hi]
let rec loop (as : Vector α n) (i j : Nat)
(ilo : lo i := by omega) (jh : j < n := by omega) (w : i j := by omega) :=
if h : j < hi then
if lt as[j] pivot then
loop (as.swap i j) (i+1) (j+1)
-- During this loop, elements below in `[lo, i)` are less than `pivot`,
-- elements in `[i, k)` are greater than or equal to `pivot`,
-- elements in `[k, hi)` are unexamined,
-- while `as[hi]` is (by definition) the pivot.
let rec loop (as : Vector α n) (i k : Nat)
(ilo : lo i := by omega) (ik : i k := by omega) (w : k hi := by omega) :=
if h : k < hi then
if lt as[k] pivot then
loop (as.swap i k) (i+1) (k+1)
else
loop as i (j+1)
loop as i (k+1)
else
(i, ilo, by omega, as.swap i hi)
loop as lo lo
@@ -51,25 +55,28 @@ def qpartition {n} (as : Vector α n) (lt : αα → Bool) (lo hi : Nat)
/--
In-place quicksort.
`qsort as lt low high` sorts the subarray `as[low:high+1]` in-place using `lt` to compare elements.
`qsort as lt lo hi` sorts the subarray `as[lo:hi+1]` in-place using `lt` to compare elements.
-/
@[inline] def qsort (as : Array α) (lt : α α Bool := by exact (· < ·))
(low := 0) (high := as.size - 1) : Array α :=
let rec @[specialize] sort {n} (as : Vector α n) (lo hi : Nat)
(lo := 0) (hi := as.size - 1) : Array α :=
let rec @[specialize] sort {n} (as : Vector α n) (lo hi : Nat) (w : lo hi := by omega)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) :=
if h₁ : lo < hi then
let mid, hmid, as := qpartition as lt lo hi
if h₂ : mid hi then
-- This only occurs when `hi ≤ lo`,
-- and thus `as[lo:hi+1]` is trivially already sorted.
as
else
-- Otherwise, we recursively sort the two subarrays.
sort (sort as lo mid) (mid+1) hi
else as
if h : as.size = 0 then
as
else
let low := min low (as.size - 1)
let high := min high (as.size - 1)
sort as.toVector low high |>.toArray
let lo := min lo (as.size - 1)
let hi := max lo (min hi (as.size - 1))
sort as.toVector lo hi |>.toArray
set_option linter.unusedVariables.funArgs false in
/--

View File

@@ -98,7 +98,7 @@ structure Config where
This approach is cheaper but incomplete. -/
qlia : Bool := false
/--
If `mbtc` is `true`, `grind` will use model-based theory commbination for creating new case splits.
If `mbtc` is `true`, `grind` will use model-based theory combination for creating new case splits.
See paper "Model-based Theory Combination" for details.
-/
mbtc : Bool := true

View File

@@ -1,28 +0,0 @@
open Array
set_option grind.warning false
reset_grind_attrs%
attribute [grind] Vector.getElem_swap_of_ne
theorem qpartition_loop_spec₁ {n} (lt : α α Bool) (lo hi : Nat)
(hlo : lo < n := by omega) (hhi : hi < n := by omega)
{ilo : lo i} {jh : j < n} {w : i j} (jhi : j hi := by omega)
(as : Vector α n) (hpivot : pivot = as[hi])
(q : k, (hk₁ : lo k) (hk₂ : k < i) lt as[k] as[hi]) (mid as')
(w_mid : mid = (qpartition.loop lt lo hi hhi pivot as i j ilo jh w).fst.1)
(hmid : mid < n)
(w_as : as' = (qpartition.loop lt lo hi hhi pivot as i j ilo jh w).2) :
k, (h₁ : lo k) (h₂ : k < mid) lt as'[k] as'[mid] := by
sorry
grind_pattern qpartition_loop_spec₁ =>
qpartition.loop lt lo hi hhi pivot as i j ilo jh w, as'[k], as'[mid]
example {n} (lt : α α Bool) (lo hi : Nat)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) (w : lo hi := by omega)
(as : Vector α n) (mid as')
(w_mid : mid = (qpartition as lt lo hi hlo hhi).fst.1)
(hmid : mid < n)
(w_as : as' = (qpartition as lt lo hi hlo hhi).2) :
i, (h₁ : lo i) (h₂ : i < mid) lt as'[i] as'[mid] := by
grind [qpartition]

View File

@@ -0,0 +1,376 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
-- TODO: when `grind` is ready for production use, move this file to `src/Init/Data/Array/QSort/Lemmas.lean`.
set_option grind.warning false
/-!
# Verification of `Array.qsort`
This file contains a verification of the `Array.qsort` function,
using the `grind` tactic.
The theorems are:
* `size_qsort : (qsort as lt lo hi).size = as.size`
* `qsort_perm : qsort as lt lo hi ~ as`
And when `lt` is antisymmetric and `¬ lt a b` is transitive, we have:
* `qsort_sorted' : lo ≤ i < j ≤ hi → ¬ lt (as.qsort lt lo hi)[j] (as.qsort lt lo hi)[i]`
* `qsort_sorted : i < j → ¬ lt (as.qsort lt)[j] (as.qsort lt)[i]`
(There is not currently a public theorem that `(qsort as lt lo hi)[i] = as[i]` when `i < lo` or `hi < i`.)
-/
namespace Array
open List Vector
-- These attributes still need to be moved to the standard library.
-- attribute [grind =] Vector.getElem_swap_of_ne -- Setting `(splits := 9)` means we don't need this
-- set_option trace.grind.ematch.pattern true in
-- attribute [grind] Vector.getElem?_eq_getElem -- This one requires some consideration! -- Probably not need, see Vector.Perm.extract' below.
-- Hmm, we don't seem to have the Array analogues of these!
attribute [grind] Vector.toArray_perm_iff
attribute [grind] Vector.perm_toArray_iff
attribute [grind] Vector.swap_perm
attribute [grind] List.Perm.refl
attribute [grind] Array.Perm.refl
attribute [grind] Vector.Perm.refl
-- attribute [grind] Array.Perm.extract
-- attribute [grind] Vector.Perm.extract
-- These are just the patterns resultin from `grind`, but the behaviour should be explained!
grind_pattern List.Perm.trans => l₁ ~ l₂, l₁ ~ l₃
grind_pattern Array.Perm.trans => xs ~ ys, xs ~ zs
grind_pattern Vector.Perm.trans => xs ~ ys, xs ~ zs
/-- Variant of `List.Perm.take` specifying the the permutation is constant after `i` elementwise. -/
theorem _root_.List.Perm.take_of_getElem {l₁ l₂ : List α} (h : l₁ ~ l₂) {i : Nat}
(w : j, i j (_ : j < l₁.length) l₁[j] = l₂[j]'(by have := h.length_eq; omega)) :
l₁.take i ~ l₂.take i := by
apply h.take_of_getElem?
sorry
/-- Variant of `List.Perm.drop` specifying the the permutation is constant before `i` elementwise. -/
theorem _root_.List.Perm.drop_of_getElem {l₁ l₂ : List α} (h : l₁ ~ l₂) {i : Nat}
(w : j, j < i (_ : j < l₁.length) l₁[j] = l₂[j]'(by have := h.length_eq; omega)) :
l₁.drop i ~ l₂.drop i := by
apply h.drop_of_getElem?
sorry
theorem _root_.Array.Perm.extract' {xs ys : Array α} (h : xs ~ ys) {lo hi : Nat}
(wlo : i, i < lo (_ : i < xs.size) xs[i] = ys[i]'(by have := h.size_eq; omega))
(whi : i, hi i (_ : i < xs.size) xs[i] = ys[i]'(by have := h.size_eq; omega)) :
xs.extract lo hi ~ ys.extract lo hi := by
rcases xs with xs
rcases ys with ys
simp_all only [perm_iff_toList_perm, List.getElem?_toArray, List.extract_toArray,
List.extract_eq_drop_take]
apply List.Perm.take_of_getElem (w := fun i h₁ h₂ => by simpa using whi (lo + i) (by omega) sorry)
apply List.Perm.drop_of_getElem (w := wlo)
simpa using List.perm_iff_toArray_perm.mpr h
theorem _root_.Vector.Perm.extract' {xs ys : Vector α n} (h : xs ~ ys) {lo hi : Nat}
(wlo : i, i < lo (_ : i < n) xs[i] = ys[i]) (whi : i, hi i (_ : i < n) xs[i] = ys[i]) :
xs.extract lo hi ~ ys.extract lo hi := by
rcases xs with xs, rfl
rcases ys with ys, h
exact Array.Perm.extract' h.toArray (by simpa using wlo) (by simpa using whi)
attribute [grind] Array.Perm.extract'
attribute [grind] Vector.Perm.extract'
variable (lt : α α Bool) (lo hi : Nat)
@[simp, grind] theorem size_qsort (as : Array α) :
(qsort as lt lo hi).size = as.size := by
grind [qsort]
private theorem qpartition_loop_perm (as : Vector α n)
(hhi : hi < n) (ilo : lo i) (ik : i k) (w : k hi) :
(qpartition.loop lt lo hi hhi pivot as i k).2 ~ as := by
fun_induction qpartition.loop with grind
@[local grind]
private theorem qpartition_perm
(as : Vector α n) (w : lo hi) (hlo : lo < n) (hhi : hi < n) :
(qpartition as lt lo hi).2 ~ as := by
unfold qpartition
refine Vector.Perm.trans (qpartition_loop_perm ..) ?_
repeat' first
| split
| grind
| refine Vector.Perm.trans (Vector.swap_perm ..) ?_
private theorem qsort_sort_perm
(as : Vector α n) (w : lo hi) (hlo : lo < n) (hhi : hi < n) :
qsort.sort lt as lo hi w hlo hhi ~ as := by
fun_induction qsort.sort with grind
grind_pattern qsort_sort_perm => qsort.sort lt as lo hi w hlo hhi
theorem qsort_perm (as : Array α) : qsort as lt lo hi ~ as := by
grind [qsort]
private theorem getElem_qpartition_loop_snd_of_lt_lo
(hhi : hi < n) (as : Vector α n) (i k : Nat) (ilo : lo i) (ik : i k) (w : k hi) (w' : lo hi)
(l : Nat) (h : l < lo) : (qpartition.loop lt lo hi hhi pivot as i k).2[l] = as[l] := by
fun_induction qpartition.loop <;> grind
private theorem getElem_qpartition_snd_of_lt_lo (as : Vector α n)
(hhi : hi < n) (w : lo hi)
(k : Nat) (h : k < lo) : (qpartition as lt lo hi).2[k] = as[k] := by
grind (splits := 9) [qpartition, getElem_qpartition_loop_snd_of_lt_lo]
@[local grind] private theorem getElem_qsort_sort_of_lt_lo
(as : Vector α n)
(hlo : lo < n) (hhi : hi < n) (w : lo hi)
(i : Nat) (h : i < lo) : (qsort.sort lt as lo hi)[i] = as[i] := by
fun_induction qsort.sort with grind [getElem_qpartition_snd_of_lt_lo]
private theorem getElem_qpartition_loop_snd_of_hi_lt
(hhi : hi < n) (as : Vector α n) (i k)
(ilo : lo i) (ik : i k) (w : k hi) (w' : lo hi) (z : i hi)
(l : Nat) (h : hi < l) (h' : l < n) : (qpartition.loop lt lo hi hhi pivot as i k).2[l] = as[l] := by
fun_induction qpartition.loop <;> grind
private theorem getElem_qpartition_snd_of_hi_lt (as : Vector α n)
(hhi : hi < n) (w : lo hi)
(k : Nat) (h : hi < k) (h' : k < n) : (qpartition as lt lo hi).2[k] = as[k] := by
grind (splits := 9) [qpartition, getElem_qpartition_loop_snd_of_hi_lt]
@[local grind] private theorem getElem_qsort_sort_of_hi_lt
(as : Vector α n) (w : lo hi)
(hlo : lo < n) (hhi : hi < n) (w : lo hi)
(i : Nat) (h : hi < i) (h' : i < n) : (qsort.sort lt as lo hi)[i] = as[i] := by
fun_induction qsort.sort with grind [getElem_qpartition_snd_of_hi_lt]
private theorem extract_qsort_sort_perm (as : Vector α n) (lt : α α Bool)
(hlo := by grind) (hhi := by grind) (w : lo hi := by grind) :
((qsort.sort lt as lo hi).extract lo (hi + 1)) ~ (as.extract lo (hi + 1)) := by
grind [qsort_sort_perm]
private theorem getElem_qsort_sort_mem
(as : Vector α n) (hhi : hi < n) (i : Nat) (h : i < n) (_ : lo i) (_ : i hi) :
(qsort.sort lt as lo hi)[i] as.extract lo (hi + 1) := by
rw [ (extract_qsort_sort_perm lo hi as lt).mem_iff, Vector.mem_extract_iff_getElem]
exact i - lo, by grind
private theorem qpartition_loop_spec₁
(hhi : hi < n) (ilo : lo i) (ik : i k) (w : k < n) (khi : k hi)
(as : Vector α n) (hpivot : pivot = as[hi])
(q : l, (hk₁ : lo l) (hk₂ : l < i) lt as[l] as[hi]) (mid as')
(w_mid : mid = (qpartition.loop lt lo hi hhi pivot as i k).fst.1) (hmid : mid < n)
(w_as : as' = (qpartition.loop lt lo hi hhi pivot as i k).2) :
l, (h₁ : lo l) (h₂ : l < mid) lt as'[l] as'[mid] := by
fun_induction qpartition.loop with unfold qpartition.loop at w_mid w_as
| case1
| case2 => apply_assumption <;> grind
| case3 => grind
private theorem qpartition_loop_spec₂
(hhi : hi < n) (ilo : lo i) (ik : i k) (w : k < n) (khi : k hi)
(as : Vector α n) (hpivot : pivot = as[hi])
(q : l, (hk₁ : i l) (hk₂ : l < k) !lt as[l] as[hi]) (mid as')
(w_mid : mid = (qpartition.loop lt lo hi hhi pivot as i k).fst.1) (hmid : mid < n)
(w_as : as' = (qpartition.loop lt lo hi hhi pivot as i k).2) :
l, (h₁ : mid < l) (h₂ : l hi) lt as'[l] as'[mid] = false := by
fun_induction qpartition.loop with grind
/--
All elements in the active range before the pivot, are less than the pivot.
-/
private theorem qpartition_spec₁
(hhi : hi < n) (w : lo hi)
(as : Vector α n) (mid as')
(w_mid : mid = (qpartition as lt lo hi).fst.1) (hmid : mid < n)
(w_as : as' = (qpartition as lt lo hi).2) :
i, (h₁ : lo i) (h₂ : i < mid) lt as'[i] as'[mid] := by
grind [qpartition, qpartition_loop_spec₁]
/--
All elements in the active range after the pivot, are greater than or equal to the pivot.
-/
private theorem qpartition_spec₂
(hhi : hi < n) (w : lo hi)
(as : Vector α n) (mid as')
(w_mid : mid = (qpartition as lt lo hi).fst.1) (hmid : mid < n)
(w_as : as' = (qpartition as lt lo hi).2) :
i, (h₁ : mid < i) (h₂ : i hi) lt as'[i] as'[mid] = false := by
grind [qpartition, qpartition_loop_spec₂]
/-!
We now need to deal with a corner case:
we need to show that `qpartition` only returns a value `≥ hi` when `hi ≤ lo`
(and hence the slice of the array between `lo` and `hi` (inclusive) is trivially already sorted).
We prove two preliminary lemmas about `qpartition.loop`.
-/
/-- If we already have `i < k`, then we're sure to return something less than `hi`. -/
private theorem qpartition_loop_lt_hi₁
(h : lo < hi) (ilo : lo i) (ik : i < k) (w : k hi) (z : k hi) (ik' : i k) :
(qpartition.loop lt lo hi hhi pivot as i k).1.val < hi := by
fun_induction qpartition.loop with grind
/--
Otherwise, if there is some position `k' ≥ k` which is greater than or equal to the pivot,
then when we reach that we'll be sure `i < k`, and hence the previous lemma will apply,
and so we're sure to return something less than `hi`.
-/
private theorem qpartition_loop_lt_hi₂
{as : Vector α n} (h : lo < hi) (ilo : lo i) (ik : i k) (w : k < n) (z : k hi)
(q : (k' : Nat) (hj' : k' < n), k' k k' < hi ¬ lt as[k'] pivot) :
(qpartition.loop lt lo hi hhi pivot as i k).1.val < hi := by
fun_induction qpartition.loop with
| case1 =>
-- It would be nice if a more aggressive mode in `grind` would do this.
apply_assumption <;> grind
| case2 => grind [qpartition_loop_lt_hi₁]
| case3 => grind
/-- The only way `qpartition` returns a pivot position `≥ hi` is if `hi ≤ lo`. -/
private theorem qpartition_fst_lt_hi (lt_asymm : {a b}, lt a b ¬ lt b a)
(as : Vector α n) (hhi : hi < n) (w : lo < hi) : (qpartition as lt lo hi).fst.1 < hi := by
apply qpartition_loop_lt_hi₂ lt lo hi w
· grind
· exact (lo + hi)/2, by grind
private theorem qsort_sort_spec
(lt_asymm : {a b}, lt a b ¬ lt b a)
(le_trans : {a b c}, ¬ lt b a ¬ lt c b ¬ lt c a)
(as : Vector α n) (lo hi : Nat) (hhi : hi < n) (w : lo hi)
(as' : Vector α n) (w_as : as' = qsort.sort lt as lo hi) :
i, (h₁ : lo i) (h₂ : i < hi) ¬ lt (as')[i + 1] as'[i] := by
unfold qsort.sort at w_as
split at w_as <;> rename_i w₁
· -- The interesting case, where `lo < hi`.
intro i h₁ h₂
-- Decompose `qpartition as lt lo hi` into `mid` (the pivot) and `as'` (the partitioned array).
split at w_as <;> rename_i mid hmid as' w₂
split at w_as <;> rename_i w₃
· -- If the pivot was at least `hi`, then we get a contradiction from `lo < hi`.
simp only [Prod.ext_iff, Subtype.ext_iff] at w₂
obtain rfl, rfl := w₂
have := qpartition_fst_lt_hi lt lo hi lt_asymm as hhi w₁
grind
· -- Now we know `lo ≤ mid < hi`.
subst w_as
if p₁ : i < mid then
-- If `i < mid`, then the second stage of sorting is only
-- moving elements above where we're looking.
rw [getElem_qsort_sort_of_lt_lo (i := i)]
rw [getElem_qsort_sort_of_lt_lo (i := i + 1)]
-- And so we can appply the theorem recursively replacing `hi` with `mid`.
apply qsort_sort_spec lt_asymm le_trans as' lo mid
-- The remaining arithmetic side conditions are easily resolved.
all_goals grind
else
replace p₁ : mid i := by grind
-- If `mid ≤ i`, we need to consider two cases.
if p₃ : mid = i then
-- The tricky case, where `mid = i`.
subst i
-- On the right hand side, the index is below the range where the second stage of sorting is happening,
-- so we can drop that sort.
rw [getElem_qsort_sort_of_lt_lo (i := mid)]
-- The `mid` element of `qsort.sort lt as' lo mid ⋯`
-- is *some* element `lo + k` of `as'` in the range `lo ≤ lo + k ≤ mid`.
have z := getElem_qsort_sort_mem lt lo mid as' ?_ mid ?_ ?_ ?_
rw [Vector.mem_extract_iff_getElem] at z
obtain k, hk, z := z
rw [ z]
clear z
-- Similarly, the `mid + 1` element on the left hand side
-- is some element `mid + 1 + k'` of `qsort.sort lt as' lo mid ⋯`
-- in the range `mid + 1 ≤ mid + 1 + k' ≤ hi`
have z := getElem_qsort_sort_mem lt (mid + 1) hi
(qsort.sort lt as' lo mid ?_ ?_) ?_ (mid + 1) ?_ ?_ ?_
rw [Vector.mem_extract_iff_getElem] at z
obtain k', hk', z := z
rw [ z]
clear z
-- And then the first stage sort on the left hand side can't have any effect,
-- as it only moves elements between `lo` and `mid` inclusive.
rw [getElem_qsort_sort_of_hi_lt]
· by_cases p : lo + k = mid
· -- Now if `lo + k = mid`,
-- the element `as'[mid + 1 + k']` is in the top part of the partitioned array,
-- and `as[lo + k]` is the pivot, so we get the inequality from the specification of `qpartition`.
grind [qpartition_spec₂]
· -- Otherwise, we use transitivity:
-- `as[lo + k']` is in the bottom part, so is strictly less than the pivot,
-- while `as'[mid + 1 + k']` is in the top, so greater than or equal to the pivot.
apply le_trans (b := as'[mid])
· grind [qpartition_spec₁]
· grind [qpartition_spec₂]
-- Various arithmetic side conditions remain from the rewriting,
-- but are now all easy to resolve.
all_goals grind
else
-- If `i < mid`, we can apply the theorem recursively replacing
-- `as` with `qsort.sort lt as' lo mid ⋯` and `lo` with `mid + 1`.
apply qsort_sort_spec lt_asymm le_trans _ _ _ (w_as := rfl) <;> grind
· -- Just an arithmetical contradiction.
grind
/--
The slice of `as.qsort lt lo hi` from `lo` to `hi` (inclusive) is sorted.
This variant states that adjacent elements are non-decreasing.
See `qsort_sorted'` for a variant about arbitrary pairs of indices.
-/
theorem qsort_sorted₁' (lt : α α Bool) (lt_asymm : {a b}, lt a b ¬ lt b a)
(le_trans : {a b c}, ¬ lt b a ¬ lt c b ¬ lt c a)
(as : Array α) (lo hi : Nat) (i) (h₁ : lo i) (h₂ : i < hi) (h₃ : i + 1 < as.size) :
¬ lt ((as.qsort lt lo hi)[i + 1]'(by grind)) ((as.qsort lt lo hi)[i]'(by grind)) := by
unfold qsort
split <;> rename_i w
· grind
· apply qsort_sort_spec lt lt_asymm le_trans (w_as := rfl) <;> grind
/--
`Array.qsort` returns a sorted array, i.e. adjacent elements are non-decreasing.
See `qsort_sorted` for a variant about arbitrary pairs of indices.
-/
theorem qsort_sorted₁ (lt : α α Bool) (lt_asymm : {a b}, lt a b ¬ lt b a)
(le_trans : {a b c}, ¬ lt b a ¬ lt c b ¬ lt c a) (as : Array α)
(i) (h : i + 1 < (qsort as lt).size) :
¬ lt (as.qsort lt)[i + 1] (as.qsort lt)[i] := by
have := qsort_sorted₁' lt lt_asymm le_trans
grind
/-- The slice of `as.qsort lt lo hi` from `lo` to `hi` (inclusive) is sorted. -/
theorem qsort_sorted' (lt : α α Bool) (lt_asymm : {a b}, lt a b ¬ lt b a)
(le_trans : {a b c}, ¬ lt b a ¬ lt c b ¬ lt c a)
(as : Array α) (lo hi : Nat) (i j) (h₁ : lo i) (h₂ : i < j) (h₃ : j hi) (h₄ : j < as.size) :
¬ lt ((as.qsort lt lo hi)[j]'(by grind)) ((as.qsort lt lo hi)[i]'(by grind)) := by
induction j with
| zero => grind
| succ j ih =>
if p : i = j then
subst p
apply qsort_sorted₁' <;> grind
else
apply le_trans (b := (as.qsort lt lo hi)[j]'(by grind))
· grind
· apply qsort_sorted₁' <;> grind
theorem qsort_sorted (lt : α α Bool) (lt_asymm : {a b}, lt a b ¬ lt b a)
(le_trans : {a b c}, ¬ lt b a ¬ lt c b ¬ lt c a) (as : Array α) :
i j, (h₁ : i < j) (h₂ : j < (qsort as lt).size)
¬ lt (as.qsort lt)[j] (as.qsort lt)[i] := by
have := qsort_sorted' lt lt_asymm le_trans
grind
end Array