Verifying Merge Sort in Imandra¶
Merge sort is a widely used efficient general purpose sorting algorithm invented by John von Neumann in 1945. It is a prototypical divide and conquer algorithm and forms the basis of standard library sorting functions in languages like OCaml, Java and Python.
Let's verify it in Imandra!
The main idea¶
The main idea of merge sort is as follows:
- Divide the input list into sublists
- Sort the sublists
- Merge the sorted sublists to obtain a sorted version of the original list
Merging two lists¶
We'll start by defining a function to merge
two lists in a "sorted" way. Note that our termination proof involves both arguments l
and m
, using a lexicographic product (via the [@@adm l,m]
admission annotation).
let rec merge (l : int list) (m : int list) =
match l, m with
| [], _ -> m
| _, [] -> l
| a :: ls, b :: ms ->
if a < b then
a :: (merge ls m)
else
b :: (merge l ms)
[@@adm l, m]
val merge : Z.t list -> Z.t list -> Z.t list = <fun>
Termination proof
original: | merge l m | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | merge (List.tl l) m | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.of_list [Ordinal.Int (_cnt l); Ordinal.Int (_cnt m)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | Ordinal.of_list [Ordinal.Int (_cnt (List.tl l)); Ordinal.Int (_cnt m)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [List.hd l < List.hd m && m <> [] && l <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
original: | merge l m | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | merge l (List.tl m) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.of_list [Ordinal.Int (_cnt l); Ordinal.Int (_cnt m)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | Ordinal.of_list [Ordinal.Int (_cnt l); Ordinal.Int (_cnt (List.tl m))] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [not (List.hd l < List.hd m) && m <> [] && l <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
Let's experiment a bit with merge
.
Observe that merge
will merge sorted lists in a manner that respects their sortedness. But, if the lists given to merge
aren't sorted, the output of merge
need not be sorted.
Dividing into sublists¶
In merge sort, we need to divide a list into sublists. We'll do this in a simple way using the function odds
which computes "every other" element of a list. We'll then be able to split a list x
into sublists by taking odds x
and odds (List.tl x)
.
let rec odds l =
match l with
| [] -> []
| [x] -> [x]
| x :: y :: rst -> x :: odds rst
val odds : 'a list -> 'a list = <fun>
Termination proof
original: | odds l | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | odds (List.tl (List.tl l)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.Int (_cnt l) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | Ordinal.Int (_cnt (List.tl (List.tl l))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [(List.tl l) <> [] && l <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
Let's compute a bit with odds
to better understand how it works.
Defining Merge Sort¶
We can now put the pieces together and define our main merge_sort
function, as follows:
let rec merge_sort l =
match l with
| [] -> []
| [_] -> l
| _ :: ls -> merge (merge_sort (odds l)) (merge_sort (odds ls))
val merge_sort : Z.t list -> Z.t list = <fun>
Error:
unsupported: rejected definition for function merge_sort,
Imandra could not prove termination.
hint: problematic sub-call from `merge_sort l`
to `merge_sort (odds (List.tl l))` under path
not Is_a([], List.tl l) and l <> []
could not be proved to decrease (measured subset: (l))
See https://docs.imandra.ai/imandra-docs/notebooks/proving-program-termination/
At jupyter cell 8:1,0--129
1 | let rec merge_sort l =
2 | match l with
3 | | [] -> []
4 | | [_] -> l
5 | | _ :: ls -> merge (merge_sort (odds l)) (merge_sort (odds ls))
To admit merge_sort
into Imandra, we must prove it terminating. We can do this with a custom measure function which we'll call merge_sort_measure
. We'll prove a lemma about odds
(by functional induction) to make it clear why the measure is decreasing in every recursive call. We'll install the lemma as a forward_chaining
rule, which is a good strategy for making it effective during termination analysis.
let merge_sort_measure x =
Ordinal.of_int (List.length x)
val merge_sort_measure : 'a list -> Ordinal.t = <fun>
theorem odds_len_1 x =
x <> [] && List.tl x <> []
==>
(List.length (odds x) [@trigger]) < List.length x
[@@induct functional odds] [@@forward_chaining]
val odds_len_1 : 'a list -> bool = <fun> Goal: (x <> []) && (List.tl x <> []) ==> List.length (odds x) < List.length x. 1 nontautological subgoal. We shall induct according to a scheme derived from odds. Induction scheme: (not ((List.tl x) <> [] && x <> []) ==> φ x) && (x <> [] && ((List.tl x) <> [] && φ (List.tl (List.tl x))) ==> φ x). 2 nontautological subgoals. Subgoal 2: H0. List.length x <= List.length (odds x) H1. not (x = []) H2. not (List.tl x = []) |--------------------------------------------------------------------------- (List.tl x) <> [] && x <> [] But simplification reduces this to true, using the forward-chaining rule List.len_nonnegative. Subgoal 1: H0. List.length x <= List.length (odds x) H1. not (x = []) H2. not (List.tl x = []) H3. x <> [] H4. (List.tl x) <> [] H5. not (List.length (List.tl (List.tl x)) <= List.length (odds (List.tl (List.tl x)))) || not (not (List.tl (List.tl x) = []) && not (List.tl (List.tl (List.tl x)) = [])) |--------------------------------------------------------------------------- false This simplifies, using the definitions of List.length and odds to the following 3 subgoals: Subgoal 1.3: H0. x <> [] H1. (List.tl x) <> [] H2. List.length (List.tl (List.tl x)) <= List.length (odds (List.tl (List.tl x))) H3. List.tl (List.tl x) = [] H4. List.length (List.tl x) <= List.length (odds (List.tl (List.tl x))) |--------------------------------------------------------------------------- false But simplification reduces this to true, using the definitions of List.length and odds. Subgoal 1.2: H0. x <> [] H1. (List.tl x) <> [] H2. List.length (List.tl (List.tl x)) <= List.length (odds (List.tl (List.tl x))) H3. (List.tl (List.tl x)) <> [] H4. List.tl (List.tl (List.tl x)) = [] H5. List.length (List.tl x) <= List.length (odds (List.tl (List.tl x))) |--------------------------------------------------------------------------- false This simplifies, using the definitions of List.length and odds to: Subgoal 1.2': H0. List.tl (List.tl (List.tl x)) = [] H1. (List.tl x) <> [] H2. x <> [] H3. (List.tl (List.tl x)) <> [] H4. List.length (List.tl (List.tl x)) <= 0 H5. List.length (List.tl (List.tl (List.tl x))) <= 0 |--------------------------------------------------------------------------- false But simplification reduces this to true, using the definition of List.length. Subgoal 1.1: H0. x <> [] H1. (List.tl x) <> [] H2. List.length (List.tl x) <= List.length (odds (List.tl (List.tl x))) |--------------------------------------------------------------------------- List.length (List.tl (List.tl x)) <= List.length (odds (List.tl (List.tl x))) But simplification reduces this to true, using the definition of List.length. ⓘ Rules: (:def List.length) (:def odds) (:fc List.len_nonnegative) (:induct odds)
ground_instances: | 0 |
definitions: | 23 |
inductions: | 1 |
search_time: | 0.363s |
start[0.363s, "Goal"] not (( :var_0: ) = []) && not (List.tl ( :var_0: ) = []) ==> List.length (odds ( :var_0: )) < List.length ( :var_0: )
subproof
not (List.length x <= List.length (odds x)) || (x = []) || (List.tl x = [])start[0.363s, "1"] not (List.length x <= List.length (odds x)) || (x = []) || (List.tl x = [])
induction on (functional odds) :scheme (not ((List.tl x) <> [] && x <> []) ==> φ x) && (x <> [] && ((List.tl x) <> [] && φ (List.tl (List.tl x))) ==> φ x)
Split (let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : bool) = _x_0 <> [] in let (_x_2 : bool) = x <> [] in let (_x_3 : bool) = not (List.length x <= List.length (odds x)) in let (_x_4 : bool) = not (not (x = []) && not (_x_0 = [])) in let (_x_5 : sko_ty_0 list) = List.tl _x_0 in ((_x_1 && _x_2) || _x_3 || _x_4) && (_x_3 || _x_4 || not (_x_2 && _x_1 && (not (List.length _x_5 <= List.length (odds _x_5)) || not (not (_x_5 = []) && not (List.tl _x_5 = []))))) :cases [let (_x_0 : sko_ty_0 list) = List.tl x in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || (_x_0 <> [] && x <> []); let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : sko_ty_0 list) = List.tl _x_0 in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || not (x <> []) || not (_x_0 <> []) || not (not (List.length _x_1 <= List.length (odds _x_1)) || not (not (_x_1 = []) && not (List.tl _x_1 = [])))])
subproof
let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : sko_ty_0 list) = List.tl _x_0 in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || not (x <> []) || not (_x_0 <> []) || not (not (List.length _x_1 <= List.length (odds _x_1)) || not (not (_x_1 = []) && not (List.tl _x_1 = [])))start[0.362s, "1"] let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : sko_ty_0 list) = List.tl _x_0 in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || not (x <> []) || not (_x_0 <> []) || not (not (List.length _x_1 <= List.length (odds _x_1)) || not (not (_x_1 = []) && not (List.tl _x_1 = [])))
simplify
into: let (_x_0 : sko_ty_0 list) = List.tl x in let (_x_1 : bool) = (x = []) || (_x_0 = []) in let (_x_2 : sko_ty_0 list) = List.tl _x_0 in let (_x_3 : int) = List.length (odds _x_2) in let (_x_4 : bool) = List.length _x_2 <= _x_3 in let (_x_5 : bool) = _x_1 || not _x_4 in let (_x_6 : bool) = _x_2 = [] in let (_x_7 : bool) = not (List.length _x_0 <= _x_3) in (_x_5 || not _x_6 || _x_7) && (_x_5 || _x_6 || not (List.tl _x_2 = []) || _x_7) && (_x_1 || _x_4 || _x_7)
expansions: [List.length, odds, List.length, List.length, odds, List.length, List.length, odds, List.length]
rewrite_steps: forward_chaining: List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
List.len_nonnegative
Subproof
Subproof
Subproof
subproof
let (_x_0 : sko_ty_0 list) = List.tl x in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || (_x_0 <> [] && x <> [])start[0.362s, "2"] let (_x_0 : sko_ty_0 list) = List.tl x in not (List.length x <= List.length (odds x)) || (x = []) || (_x_0 = []) || (_x_0 <> [] && x <> [])
simplify
into: true
expansions: []
rewrite_steps: forward_chaining: List.len_nonnegative
List.len_nonnegative
Now we're ready to define merge_sort
. Imandra is able prove it terminating using our custom measure (and lemma!).
let rec merge_sort l =
match l with
| [] -> []
| [_] -> l
| _ :: ls -> merge (merge_sort (odds l)) (merge_sort (odds ls))
[@@measure merge_sort_measure l]
val merge_sort : Z.t list -> Z.t list = <fun>
Termination proof
original: | merge_sort l | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | merge_sort (odds l) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | merge_sort_measure l | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | merge_sort_measure (odds l) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [not Is_a([], List.tl l) && l <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
original: | merge_sort l | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | merge_sort (odds (List.tl l)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | merge_sort_measure l | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | merge_sort_measure (odds (List.tl l)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [not Is_a([], List.tl l) && l <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
Let's experiment a bit with merge_sort
to gain confidence we've defined it correctly.
merge_sort [9;100;6;2;34;19;3;4;7;6]
- : Z.t list = [2;3;4;6;6;7;9;19;34;100]
This looks pretty good! Let's now use Imandra to prove it correct.
Proving Merge Sort correct¶
What does it mean for a sorting algorithm to be correct? There are two main components:
- The result is sorted
- The result has the same elements as the original
Let's start by defining what it means for a list to be sorted.
let rec is_sorted (x : int list) =
match x with
| [] -> true
| [_] -> true
| x :: x' :: xs -> x <= x' && is_sorted (x' :: xs)
val is_sorted : Z.t list -> bool = <fun>
Termination proof
original: | is_sorted x | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | let (_x_0 : int list) = List.tl x in is_sorted ((List.hd _x_0) :: (List.tl _x_0)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.Int (_cnt x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | let (_x_0 : int list) = List.tl x in Ordinal.Int (_cnt ((List.hd _x_0) :: (List.tl _x_0))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [List.hd x <= List.hd (List.tl x) && (List.tl x) <> [] && x <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
As usual, let's experiment a bit with this definition.
instance (fun x -> List.length x >= 5 && is_sorted x)
- : Z.t list -> bool = <fun> module CX : sig val x : Z.t list end
Instance (after 11 steps, 0.016s): let x : int list = [0; 1; 1237; 3033; 3482]
ground_instances: | 11 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
definitions: | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
inductions: | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
search_time: | 0.017s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
details: | Expand
|
start[0.017s] (List.length ( :var_0: ) >= 5) && is_sorted ( :var_0: )
- unroll
expr: (is_sorted_1294/client x_1301/client)
expansions: - unroll
expr: (|List.length_1101/server| x_1301/client)
expansions: - unroll
expr: (|List.length_1101/server| (|get.::.1_1100/server| x_1301/client))
expansions: - unroll
expr: (is_sorted_1294/client (|::| (|get.::.0_1099/server| (|get.::.1_1100/server| x_1301/client)) …
expansions: - unroll
expr: (|List.length_1101/server| (|get.::.1_1100/server| (|get.::.1_1100/server| x_1301/client)))
expansions: - unroll
expr: (let ((a!1 (|::| (|get.::.0_1099/server| (|get.::.1_1100/server| …
expansions: - unroll
expr: (|List.length_1101/server| (|get.::.1_1100/server| (|get.::.1_1100/server| (|get.::.1_1100/ser…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1100/server| (|get.::.1_1100/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.0_1099/server| (|get.::.1_1100/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1100/server| (|get.::.1_1100/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1100/server| (|get.::.1_1100/server| (|get.::.1_11…
expansions: - Sat (Some let x : int list = [(Z.of_nativeint (0n)); (Z.of_nativeint (1n)); (Z.of_nativeint (1237n)); (Z.of_nativeint (3033n)); (Z.of_nativeint (3482n))] )
instance (fun x -> List.length x >= 5 && is_sorted x && is_sorted (List.rev x))
- : Z.t list -> bool = <fun> module CX : sig val x : Z.t list end
Instance (after 43 steps, 0.065s): let x : int list = [9294; 9294; 9294; 9294; 9294]
ground_instances: | 43 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
definitions: | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
inductions: | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
search_time: | 0.065s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
details: | Expand
|
start[0.065s] (List.length ( :var_0: ) >= 5) && (is_sorted ( :var_0: ) && is_sorted (List.rev ( :var_0: )))
simplify
into: (List.length ( :var_0: ) >= 5) && is_sorted ( :var_0: ) && is_sorted (List.rev ( :var_0: ))
expansions: []
rewrite_steps: forward_chaining: - unroll
expr: (is_sorted_1294/client (|List.rev_1144/server| x_1303/client))
expansions: - unroll
expr: (|List.rev_1144/server| x_1303/client)
expansions: - unroll
expr: (is_sorted_1294/client x_1303/client)
expansions: - unroll
expr: (|List.length_1140/server| x_1303/client)
expansions: - unroll
expr: (let ((a!1 (|::| (|get.::.0_1138/server| (|get.::.1_1139/server| …
expansions: - unroll
expr: (|List.append_1148/server| (|List.rev_1144/server| (|get.::.1_1139/server| x_1303/client)) (|::|…
expansions: - unroll
expr: (|List.rev_1144/server| (|get.::.1_1139/server| x_1303/client))
expansions: - unroll
expr: (|List.length_1140/server| (|get.::.1_1139/server| x_1303/client))
expansions: - unroll
expr: (is_sorted_1294/client (|::| (|get.::.0_1138/server| (|get.::.1_1139/server| x_1303/client)) …
expansions: - unroll
expr: (let ((a!1 (|get.::.0_1138/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (|List.append_1148/server| (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_1139/ser…
expansions: - unroll
expr: (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_1139/server| x_1303/client)))
expansions: - unroll
expr: (|List.length_1140/server| (|get.::.1_1139/server| (|get.::.1_1139/server| x_1303/client)))
expansions: - unroll
expr: (|List.append_1148/server| (|get.::.1_1139/server| (|List.rev_1144/server| (|get.::.1_1139/ser…
expansions: - unroll
expr: (let ((a!1 (|::| (|get.::.0_1138/server| (|get.::.1_1139/server| …
expansions: - unroll
expr: (let ((a!1 (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_1139/server…
expansions: - unroll
expr: (|List.length_1140/server| (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_1139/ser…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|List.rev_1144/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|List.rev_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.0_1138/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|List.rev_1144/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|List.rev_11…
expansions: - unroll
expr: (let ((a!1 (|List.rev_1144/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|List.rev_1144/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|get.::.1_11…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_1139/server| (|get.::.1_1139/server| (|List.rev_11…
expansions: - Sat (Some let x : int list = [(Z.of_nativeint (9294n)); (Z.of_nativeint (9294n)); (Z.of_nativeint (9294n)); (Z.of_nativeint (9294n)); (Z.of_nativeint (9294n))] )
Proving Merge Sort sorts¶
Now that we've defined our is_sorted
predicate, we're ready to state one of our main verification goals: that merge_sort
sorts!
We can write this this way:
theorem merge_sort_sorts x =
is_sorted (merge_sort x)
Before we try to prove this for all possible cases by induction, let's ask Imandra to verify that this property holds up to our current recursion unrolling bound (100
).
verify (fun x -> is_sorted (merge_sort x))
- : Z.t list -> bool = <fun>
expanded: |
|
blocked: |
|
ground_instances: | 100 |
definitions: | 0 |
inductions: | 0 |
search_time: | 1.719s |
start[1.719s] is_sorted (merge_sort ( :var_0: ))
- unroll
expr: (is_sorted_1294/client (merge_sort_1288/client x_424/server))
expansions: - unroll
expr: (merge_sort_1288/client x_424/server)
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server x_424/server))
expansions: - unroll
expr: (merge_1256/client (merge_sort_1288/client (odds_425/server x_424/server)) (merge_sort_1288/clie…
expansions: - unroll
expr: (let ((a!1 (|::| (|get.::.0_422/server| (|get.::.1_423/server| (merge_sort_1288/c…
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server (|get.::.1_423/server| x_424/server)))
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| x_424/server))
expansions: - unroll
expr: (odds_425/server x_424/server)
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (|get.::.1_423/server| x_424/server)))
expansions: - unroll
expr: (let ((a!1 (merge_sort_1288/client (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.0_422/server| (merge_sort_1288/client (odds_425/serv…
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server (|get.::.1_423/server| (odds_425/server x_424/server))))
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (odds_425/server x_424/server)))
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server (odds_425/server x_424/server)))
expansions: - unroll
expr: (odds_425/server (odds_425/server x_424/server))
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/ser…
expansions: - unroll
expr: (let ((a!1 (merge_sort_1288/client (odds_425/server (odds_425/server (|get.::.1_423/ser…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| x…
expansions: - unroll
expr: (let ((a!1 (|get.::.0_422/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server (odds_425/server (|get.::.1_423/server| x_424/server))))
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/server x…
expansions: - unroll
expr: (odds_425/server (odds_425/server (|get.::.1_423/server| x_424/server)))
expansions: - unroll
expr: (let ((a!1 (merge_sort_1288/client (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (|get.::.0_422/server| (merge_sort_1288/client (odds_425/serv…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (merge_sort_1288/client (odds_425/server (odds_425/server (odds_425/server x…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (odds_425/server (|get.::.1_423/server| (odds_425/server (odds_425/server x_424/s…
expansions: - unroll
expr: (merge_sort_1288/client (odds_425/server (odds_425/server (odds_425/server x_424/server))))
expansions: - unroll
expr: (odds_425/server (odds_425/server (odds_425/server x_424/server)))
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (odds_425/server (odds_425/server (|get.::.1_423/server| (odds_4…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (|get.::.1_423/serv…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (|get.::.1_423/serv…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (merge_sort_1288/client (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.0_422/server| (merge_sort_1288/client (odds_425/serv…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (odds_425/server x_424/server))))) …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (o…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (odds_425/server x_…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (odds_425/server x_…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (odds_425/server x_424/server)))))) …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (odds_425/server (odds_425/server (odds_425/server (odds_425/server x_424/server))))
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (|get.::.1_423/…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (|get.::.1_423/serv…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (|get.::.1_423/serv…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (odds_425/server (odds_425/server (|get.::.1_423/serv…
expansions: - unroll
expr: (let ((a!1 (|get.::.1_423/server| (|get.::.1_423/server| (odds_425/serve…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (odds_425/server (odds_425/server (|get.::.1_423/server| …
expansions: - unroll
expr: (let ((a!1 (odds_425/server (|get.::.1_423/server| (odds_425/server (|…
expansions:
Excellent. This gives us quite some confidence that this result is true. Let's now try to prove it by induction. We'll want to use an induction principle suggested by the definition of merge_sort
. Let's use our usual strategy of #max_induct 1
, so we can analyse Imandra's output to help us find needed lemmata.
Note, we could prove this theorem directly without any lemmas by using #max_induct 3
, but it's good practice to use #max_induct 1
and have Imandra help us develop useful collections of lemmas.
theorem merge_sort_sorts x =
is_sorted (merge_sort x)
[@@induct functional merge_sort]
val merge_sort_sorts : Z.t list -> bool = <fun> Goal: is_sorted (merge_sort x). 1 nontautological subgoal. We shall induct according to a scheme derived from merge_sort. Induction scheme: (not (not Is_a([], List.tl x) && x <> []) ==> φ x) && (x <> [] && (not Is_a([], List.tl x) && (φ (odds (List.tl x)) && φ (odds x))) ==> φ x). 2 nontautological subgoals. Subgoal 2: |--------------------------------------------------------------------------- C0. not Is_a([], List.tl x) && x <> [] C1. is_sorted (merge_sort x) But simplification reduces this to true, using the definitions of is_sorted and merge_sort. Subgoal 1: H0. x <> [] H1. not Is_a([], List.tl x) H2. is_sorted (merge_sort (odds (List.tl x))) H3. is_sorted (merge_sort (odds x)) |--------------------------------------------------------------------------- is_sorted (merge_sort x) This simplifies, using the definition of merge_sort to: Subgoal 1': H0. is_sorted (merge_sort (odds x)) H1. x <> [] H2. is_sorted (merge_sort (odds (List.tl x))) |--------------------------------------------------------------------------- C0. Is_a([], List.tl x) C1. is_sorted (merge (merge_sort (odds x)) (merge_sort (odds (List.tl x)))) We can eliminate destructors by the following substitution: x -> x1 :: x2 This produces the modified subgoal: Subgoal 1'': H0. is_sorted (merge_sort (odds x2)) H1. is_sorted (merge_sort (odds (x1 :: x2))) |--------------------------------------------------------------------------- C0. is_sorted (merge (merge_sort (odds (x1 :: x2))) (merge_sort (odds x2))) C1. Is_a([], x2) Candidates for generalization: odds (x1 :: x2) odds x2 This produces the modified subgoal: Subgoal 1''': H0. is_sorted (merge_sort gen_2) H1. is_sorted (merge_sort gen_1) |--------------------------------------------------------------------------- C0. is_sorted (merge (merge_sort gen_1) (merge_sort gen_2)) C1. Is_a([], x2) Candidates for generalization: merge_sort gen_1 merge_sort gen_2 This produces the modified subgoal: Subgoal 1'''': H0. is_sorted gen_4 H1. is_sorted gen_3 |--------------------------------------------------------------------------- C0. is_sorted (merge gen_3 gen_4) C1. Is_a([], x2) Must try induction. ⚠ Aborting proof attempt for merge_sort_sorts. ⓘ Rules: (:def is_sorted) (:def merge_sort) (:induct merge_sort) Checkpoints: H0. is_sorted gen_4 H1. is_sorted gen_3 |--------------------------------------------------------------------------- C0. is_sorted (merge gen_3 gen_4) C1. Is_a([], x2) Error: Maximum induction depth reached (1). You can set this with #max_induct. At <none>:1
Analysing the output of this proof attempt, we notice the following components of our Checkpoint
:
H1. is_sorted gen_1
H2. is_sorted gen_2
|---------------------------------------------------------------------------
is_sorted (merge gen_2 gen_1)
Ah, of course! We need to prove a lemma that merge
respects the sortedness of its inputs.
Let's do this by functional induction following the definition of merge
and install it as a rewrite
rule.
We'll allow nested induction (#max_induct 2
), but it's a good exercise to do this proof without it (deriving an additional lemma!).
#max_induct 2;;
theorem merge_sorts x y =
is_sorted x && is_sorted y
==>
is_sorted (merge x y)
[@@induct functional merge]
[@@rewrite]
val merge_sorts : Z.t list -> Z.t list -> bool = <fun> Goal: is_sorted x && is_sorted y ==> is_sorted (merge x y). 1 nontautological subgoal. We shall induct according to a scheme derived from merge. Induction scheme: (not ((List.hd x < List.hd y) && (y <> [] && x <> [])) && not (not (List.hd x < List.hd y) && (y <> [] && x <> [])) ==> φ x y) && ((x <> [] && (y <> [] && (not (List.hd x < List.hd y) && φ x (List.tl y))) ==> φ x y) && (x <> [] && (y <> [] && ((List.hd x < List.hd y) && φ (List.tl x) y)) ==> φ x y)). 3 nontautological subgoals. Subgoal 3: H0. is_sorted x H1. is_sorted y H2. not (not (List.hd y <= List.hd x) && y <> [] && x <> []) H3. not ((List.hd y <= List.hd x) && y <> [] && x <> []) |--------------------------------------------------------------------------- is_sorted (merge x y) But simplification reduces this to true, using the definitions of is_sorted and merge. Subgoal 2: H0. is_sorted x H1. is_sorted y H2. x <> [] H3. y <> [] H4. List.hd y <= List.hd x H5. is_sorted x && is_sorted (List.tl y) ==> is_sorted (merge x (List.tl y)) |--------------------------------------------------------------------------- is_sorted (merge x y) This simplifies, using the definition of merge to the following 2 subgoals: Subgoal 2.2: H0. List.hd y <= List.hd x H1. is_sorted x H2. is_sorted y H3. x <> [] H4. y <> [] |--------------------------------------------------------------------------- C0. is_sorted (merge x (List.tl y)) C1. is_sorted (List.tl y) C2. is_sorted ((List.hd y) :: (merge x (List.tl y))) But we verify Subgoal 2.2 by recursive unrolling. Subgoal 2.1: H0. List.hd y <= List.hd x H1. is_sorted x H2. is_sorted y H3. x <> [] H4. y <> [] H5. is_sorted (merge x (List.tl y)) |--------------------------------------------------------------------------- is_sorted ((List.hd y) :: (merge x (List.tl y))) But we verify Subgoal 2.1 by recursive unrolling. Subgoal 1: H0. is_sorted x H1. is_sorted y H2. x <> [] H3. y <> [] H4. not (List.hd y <= List.hd x) H5. is_sorted (List.tl x) && is_sorted y ==> is_sorted (merge (List.tl x) y) |--------------------------------------------------------------------------- is_sorted (merge x y) This simplifies, using the definition of merge to the following 2 subgoals: Subgoal 1.2: H0. is_sorted x H1. is_sorted y H2. x <> [] H3. y <> [] |--------------------------------------------------------------------------- C0. is_sorted (merge (List.tl x) y) C1. is_sorted (List.tl x) C2. List.hd y <= List.hd x C3. is_sorted ((List.hd x) :: (merge (List.tl x) y)) But we verify Subgoal 1.2 by recursive unrolling. Subgoal 1.1: H0. is_sorted x H1. is_sorted y H2. x <> [] H3. y <> [] H4. is_sorted (merge (List.tl x) y) |--------------------------------------------------------------------------- C0. List.hd y <= List.hd x C1. is_sorted ((List.hd x) :: (merge (List.tl x) y)) But we verify Subgoal 1.1 by recursive unrolling. ⓘ Rules: (:def is_sorted) (:def merge) (:induct merge)
ground_instances: | 19 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
definitions: | 12 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
inductions: | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
search_time: | 1.430s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
details: | Expand
|
start[1.430s, "Goal"] is_sorted ( :var_0: ) && is_sorted ( :var_1: ) ==> is_sorted (merge ( :var_0: ) ( :var_1: ))
subproof
not (is_sorted x) || not (is_sorted y) || is_sorted (merge x y)start[1.430s, "1"] not (is_sorted x) || not (is_sorted y) || is_sorted (merge x y)
induction on (functional merge) :scheme (not ((List.hd x < List.hd y) && (y <> [] && x <> [])) && not (not (List.hd x < List.hd y) && (y <> [] && x <> [])) ==> φ x y) && ((x <> [] && (y <> [] && (not (List.hd x < List.hd y) && φ x (List.tl y))) ==> φ x y) && (x <> [] && (y <> [] && ((List.hd x < List.hd y) && φ (List.tl x) y)) ==> φ x y))
Split (let (_x_0 : bool) = is_sorted x in let (_x_1 : bool) = is_sorted y in let (_x_2 : bool) = is_sorted (merge x y) || not (_x_0 && _x_1) in let (_x_3 : bool) = List.hd y <= List.hd x in let (_x_4 : bool) = not _x_3 in let (_x_5 : bool) = y <> [] in let (_x_6 : bool) = x <> [] in let (_x_7 : bool) = _x_6 && _x_5 in let (_x_8 : int list) = List.tl y in let (_x_9 : int list) = List.tl x in (_x_2 || not (not (_x_4 && _x_5 && _x_6) && not (_x_3 && _x_5 && _x_6))) && (_x_2 || not (_x_7 && _x_3 && (is_sorted (merge x _x_8) || not (_x_0 && is_sorted _x_8)))) && (_x_2 || not (_x_7 && _x_4 && (is_sorted (merge _x_9 y) || not (is_sorted _x_9 && _x_1)))) :cases [let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in not (is_sorted x) || not (is_sorted y) || (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || is_sorted (merge x y); let (_x_0 : bool) = is_sorted x in let (_x_1 : int list) = List.tl y in not _x_0 || not (is_sorted y) || not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (_x_0 && is_sorted _x_1 ==> is_sorted (merge x _x_1)) || is_sorted (merge x y); let (_x_0 : bool) = is_sorted y in let (_x_1 : int list) = List.tl x in not (is_sorted x) || not _x_0 || not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (is_sorted _x_1 && _x_0 ==> is_sorted (merge _x_1 y)) || is_sorted (merge x y)])
subproof
let (_x_0 : bool) = is_sorted y in let (_x_1 : int list) = List.tl x in not (is_sorted x) || not _x_0 || not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (is_sorted _x_1 && _x_0 ==> is_sorted (merge _x_1 y)) || is_sorted (merge x y)start[1.429s, "1"] let (_x_0 : bool) = is_sorted y in let (_x_1 : int list) = List.tl x in not (is_sorted x) || not _x_0 || not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (is_sorted _x_1 && _x_0 ==> is_sorted (merge _x_1 y)) || is_sorted (merge x y)
simplify
into: let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = merge _x_0 y in let (_x_2 : bool) = is_sorted _x_1 in let (_x_3 : int) = List.hd x in let (_x_4 : bool) = List.hd y <= _x_3 in let (_x_5 : bool) = not (is_sorted x) in let (_x_6 : bool) = not (is_sorted y) in let (_x_7 : bool) = not (x <> []) in let (_x_8 : bool) = not (y <> []) in let (_x_9 : bool) = is_sorted (_x_3 :: _x_1) in (_x_2 || is_sorted _x_0 || _x_4 || _x_5 || _x_6 || _x_7 || _x_8 || _x_9) && (_x_4 || _x_5 || _x_6 || _x_7 || _x_8 || not _x_2 || _x_9)
expansions: [merge, merge]
rewrite_steps: forward_chaining: Subproof
Subproof
subproof
let (_x_0 : bool) = is_sorted x in let (_x_1 : int list) = List.tl y in not _x_0 || not (is_sorted y) || not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (_x_0 && is_sorted _x_1 ==> is_sorted (merge x _x_1)) || is_sorted (merge x y)start[1.429s, "2"] let (_x_0 : bool) = is_sorted x in let (_x_1 : int list) = List.tl y in not _x_0 || not (is_sorted y) || not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (_x_0 && is_sorted _x_1 ==> is_sorted (merge x _x_1)) || is_sorted (merge x y)
simplify
into: let (_x_0 : int list) = List.tl y in let (_x_1 : int list) = merge x _x_0 in let (_x_2 : bool) = is_sorted _x_1 in let (_x_3 : int) = List.hd y in let (_x_4 : bool) = not (_x_3 <= List.hd x) in let (_x_5 : bool) = not (is_sorted x) in let (_x_6 : bool) = not (is_sorted y) in let (_x_7 : bool) = not (x <> []) in let (_x_8 : bool) = not (y <> []) in let (_x_9 : bool) = is_sorted (_x_3 :: _x_1) in (_x_2 || is_sorted _x_0 || _x_4 || _x_5 || _x_6 || _x_7 || _x_8 || _x_9) && (_x_4 || _x_5 || _x_6 || _x_7 || _x_8 || _x_9 || not _x_2)
expansions: [merge, merge]
rewrite_steps: forward_chaining: Subproof
Subproof
subproof
let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in not (is_sorted x) || not (is_sorted y) || (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || is_sorted (merge x y)start[1.429s, "3"] let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in not (is_sorted x) || not (is_sorted y) || (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || is_sorted (merge x y)
simplify
into: true
expansions: [merge, is_sorted, merge, is_sorted, merge, is_sorted, merge, is_sorted]
rewrite_steps: forward_chaining:
Excellent! Now that we have that key lemma proved and available as a rewrite rule, let's return to our original goal:
theorem merge_sort_sorts x =
is_sorted (merge_sort x)
[@@induct functional merge_sort]
val merge_sort_sorts : Z.t list -> bool = <fun> Goal: is_sorted (merge_sort x). 1 nontautological subgoal. We shall induct according to a scheme derived from merge_sort. Induction scheme: (not (not Is_a([], List.tl x) && x <> []) ==> φ x) && (x <> [] && (not Is_a([], List.tl x) && (φ (odds (List.tl x)) && φ (odds x))) ==> φ x). 2 nontautological subgoals. Subgoal 2: |--------------------------------------------------------------------------- C0. not Is_a([], List.tl x) && x <> [] C1. is_sorted (merge_sort x) But simplification reduces this to true, using the definitions of is_sorted and merge_sort. Subgoal 1: H0. x <> [] H1. not Is_a([], List.tl x) H2. is_sorted (merge_sort (odds (List.tl x))) H3. is_sorted (merge_sort (odds x)) |--------------------------------------------------------------------------- is_sorted (merge_sort x) This simplifies, using the definition of merge_sort to: Subgoal 1': H0. is_sorted (merge_sort (odds x)) H1. x <> [] H2. is_sorted (merge_sort (odds (List.tl x))) |--------------------------------------------------------------------------- C0. Is_a([], List.tl x) C1. is_sorted (merge (merge_sort (odds x)) (merge_sort (odds (List.tl x)))) But simplification reduces this to true, using the rewrite rule merge_sorts. ⓘ Rules: (:def is_sorted) (:def merge_sort) (:rw merge_sorts) (:induct merge_sort)
ground_instances: | 0 |
definitions: | 5 |
inductions: | 1 |
search_time: | 0.266s |
start[0.266s, "Goal"] is_sorted (merge_sort ( :var_0: ))
subproof
is_sorted (merge_sort x)start[0.266s, "1"] is_sorted (merge_sort x)
induction on (functional merge_sort) :scheme (not (not Is_a([], List.tl x) && x <> []) ==> φ x) && (x <> [] && (not Is_a([], List.tl x) && (φ (odds (List.tl x)) && φ (odds x))) ==> φ x)
Split (let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = not Is_a([], _x_0) in let (_x_2 : bool) = x <> [] in let (_x_3 : bool) = is_sorted (merge_sort x) in ((_x_1 && _x_2) || _x_3) && (_x_3 || not (_x_2 && _x_1 && is_sorted (merge_sort (odds _x_0)) && is_sorted (merge_sort (odds x)))) :cases [(not Is_a([], List.tl x) && x <> []) || is_sorted (merge_sort x); let (_x_0 : int list) = List.tl x in not (x <> []) || Is_a([], _x_0) || not (is_sorted (merge_sort (odds _x_0))) || not (is_sorted (merge_sort (odds x))) || is_sorted (merge_sort x)])
subproof
let (_x_0 : int list) = List.tl x in not (x <> []) || Is_a([], _x_0) || not (is_sorted (merge_sort (odds _x_0))) || not (is_sorted (merge_sort (odds x))) || is_sorted (merge_sort x)start[0.265s, "1"] let (_x_0 : int list) = List.tl x in not (x <> []) || Is_a([], _x_0) || not (is_sorted (merge_sort (odds _x_0))) || not (is_sorted (merge_sort (odds x))) || is_sorted (merge_sort x)
simplify
into: let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = merge_sort (odds x) in let (_x_2 : int list) = merge_sort (odds _x_0) in Is_a([], _x_0) || not (is_sorted _x_1) || not (x <> []) || not (is_sorted _x_2) || is_sorted (merge _x_1 _x_2)
expansions: merge_sort
rewrite_steps: forward_chaining: simplify
into: true
expansions: []
rewrite_steps: merge_sorts
forward_chaining:
subproof
(not Is_a([], List.tl x) && x <> []) || is_sorted (merge_sort x)start[0.265s, "2"] (not Is_a([], List.tl x) && x <> []) || is_sorted (merge_sort x)
simplify
into: true
expansions: [is_sorted, merge_sort, is_sorted, merge_sort]
rewrite_steps: forward_chaining:
Beautiful! So now we've proved half of our specification: merge_sort
sorts!
Merge Sort contains the right elements¶
Let's now turn to the second half of our correctness criterion: That merge_sort x
contains "the same" elements as x
.
What does this mean, and why does it matter?
An incorrect sorting function¶
Consider the sorting function bad_sort
defined as bad_sort x = []
.
Clearly, we could prove theorem bad_sort_sorts x = is_sorted (bad_sort x)
, even though we'd never want to use bad_sort
as a sorting function in practice.
That is, computing a sorted list is only one piece of the puzzle. We must also verify that merge_sort x
contains exactly the same elements as x
, including their multiplicity).
Multiset equivalence¶
How can we express this concept? We'll use the notion of a multiset and count the number of occurrences of each element of x
, and make sure these are respected by merge_sort
.
We'll begin by defining a function num_occurs x y
which counts the number of times an element x
appears in the list y
.
let rec num_occurs x y =
match y with
| [] -> 0
| hd :: tl when hd = x ->
1 + num_occurs x tl
| _ :: tl ->
num_occurs x tl
val num_occurs : 'a -> 'a list -> Z.t = <fun>
Termination proof
original: | num_occurs x y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | num_occurs x (List.tl y) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.Int (_cnt y) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | Ordinal.Int (_cnt (List.tl y)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [List.hd y = x && y <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
original: | num_occurs x y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub: | num_occurs x (List.tl y) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
original ordinal: | Ordinal.Int (_cnt y) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sub ordinal: | Ordinal.Int (_cnt (List.tl y)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path: | [not (List.hd y = x) && y <> []] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
proof: | detailed proof
Expand
|
Anticipating the need to reason about the interplay of num_occurs
and merge
, let's prove the following nice lemmas characterising their relationship (applying the max_induct 1
strategy to find these lemmas as above is a great exercise).
theorem num_occur_merge a x y =
num_occurs a (merge x y) = num_occurs a x + num_occurs a y
[@@induct functional merge]
[@@rewrite]
val num_occur_merge : Z.t -> Z.t list -> Z.t list -> bool = <fun> Goal: num_occurs a (merge x y) = num_occurs a x + num_occurs a y. 1 nontautological subgoal. We shall induct according to a scheme derived from merge. Induction scheme: (not ((List.hd x < List.hd y) && (y <> [] && x <> [])) && not (not (List.hd x < List.hd y) && (y <> [] && x <> [])) ==> φ a x y) && ((x <> [] && (y <> [] && (not (List.hd x < List.hd y) && φ a x (List.tl y))) ==> φ a x y) && (x <> [] && (y <> [] && ((List.hd x < List.hd y) && φ a (List.tl x) y)) ==> φ a x y)). 3 nontautological subgoals. Subgoal 3: H0. not (not (List.hd y <= List.hd x) && y <> [] && x <> []) H1. not ((List.hd y <= List.hd x) && y <> [] && x <> []) |--------------------------------------------------------------------------- num_occurs a (merge x y) = num_occurs a x + num_occurs a y But simplification reduces this to true, using the definitions of merge and num_occurs. Subgoal 2: H0. x <> [] H1. y <> [] H2. List.hd y <= List.hd x H3. num_occurs a (merge x (List.tl y)) = num_occurs a x + num_occurs a (List.tl y) |--------------------------------------------------------------------------- num_occurs a (merge x y) = num_occurs a x + num_occurs a y But simplification reduces this to true, using the definitions of merge and num_occurs. Subgoal 1: H0. x <> [] H1. y <> [] H2. not (List.hd y <= List.hd x) H3. num_occurs a (merge (List.tl x) y) = num_occurs a (List.tl x) + num_occurs a y |--------------------------------------------------------------------------- num_occurs a (merge x y) = num_occurs a x + num_occurs a y But simplification reduces this to true, using the definitions of merge and num_occurs. ⓘ Rules: (:def merge) (:def num_occurs) (:induct merge)
ground_instances: | 0 |
definitions: | 14 |
inductions: | 1 |
search_time: | 0.207s |
start[0.207s, "Goal"] num_occurs ( :var_0: ) (merge ( :var_1: ) ( :var_2: )) = num_occurs ( :var_0: ) ( :var_1: ) + num_occurs ( :var_0: ) ( :var_2: )
subproof
num_occurs a (merge x y) = num_occurs a x + num_occurs a ystart[0.207s, "1"] num_occurs a (merge x y) = num_occurs a x + num_occurs a y
induction on (functional merge) :scheme (not ((List.hd x < List.hd y) && (y <> [] && x <> [])) && not (not (List.hd x < List.hd y) && (y <> [] && x <> [])) ==> φ a x y) && ((x <> [] && (y <> [] && (not (List.hd x < List.hd y) && φ a x (List.tl y))) ==> φ a x y) && (x <> [] && (y <> [] && ((List.hd x < List.hd y) && φ a (List.tl x) y)) ==> φ a x y))
Split (let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = not _x_0 in let (_x_2 : bool) = y <> [] in let (_x_3 : bool) = x <> [] in let (_x_4 : int) = num_occurs a x in let (_x_5 : int) = num_occurs a y in let (_x_6 : bool) = num_occurs a (merge x y) = _x_4 + _x_5 in let (_x_7 : bool) = _x_3 && _x_2 in let (_x_8 : int list) = List.tl y in let (_x_9 : int list) = List.tl x in (not (not (_x_1 && _x_2 && _x_3) && not (_x_0 && _x_2 && _x_3)) || _x_6) && (not (_x_7 && _x_0 && (num_occurs a (merge x _x_8) = _x_4 + num_occurs a _x_8)) || _x_6) && (not (_x_7 && _x_1 && (num_occurs a (merge _x_9 y) = num_occurs a _x_9 + _x_5)) || _x_6) :cases [let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || (num_occurs a (merge x y) = num_occurs a x + num_occurs a y); let (_x_0 : int list) = List.tl y in let (_x_1 : int) = num_occurs a x in not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (num_occurs a (merge x _x_0) = _x_1 + num_occurs a _x_0) || (num_occurs a (merge x y) = _x_1 + num_occurs a y); let (_x_0 : int list) = List.tl x in let (_x_1 : int) = num_occurs a y in not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (num_occurs a (merge _x_0 y) = num_occurs a _x_0 + _x_1) || (num_occurs a (merge x y) = num_occurs a x + _x_1)])
subproof
let (_x_0 : int list) = List.tl x in let (_x_1 : int) = num_occurs a y in not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (num_occurs a (merge _x_0 y) = num_occurs a _x_0 + _x_1) || (num_occurs a (merge x y) = num_occurs a x + _x_1)start[0.206s, "1"] let (_x_0 : int list) = List.tl x in let (_x_1 : int) = num_occurs a y in not (x <> []) || not (y <> []) || (List.hd y <= List.hd x) || not (num_occurs a (merge _x_0 y) = num_occurs a _x_0 + _x_1) || (num_occurs a (merge x y) = num_occurs a x + _x_1)
simplify
into: true
expansions: [num_occurs, num_occurs, merge]
rewrite_steps: forward_chaining:
subproof
let (_x_0 : int list) = List.tl y in let (_x_1 : int) = num_occurs a x in not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (num_occurs a (merge x _x_0) = _x_1 + num_occurs a _x_0) || (num_occurs a (merge x y) = _x_1 + num_occurs a y)start[0.206s, "2"] let (_x_0 : int list) = List.tl y in let (_x_1 : int) = num_occurs a x in not (x <> []) || not (y <> []) || not (List.hd y <= List.hd x) || not (num_occurs a (merge x _x_0) = _x_1 + num_occurs a _x_0) || (num_occurs a (merge x y) = _x_1 + num_occurs a y)
simplify
into: true
expansions: [num_occurs, num_occurs, merge]
rewrite_steps: forward_chaining:
subproof
let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || (num_occurs a (merge x y) = num_occurs a x + num_occurs a y)start[0.206s, "3"] let (_x_0 : bool) = List.hd y <= List.hd x in let (_x_1 : bool) = y <> [] in let (_x_2 : bool) = x <> [] in (not _x_0 && _x_1 && _x_2) || (_x_0 && _x_1 && _x_2) || (num_occurs a (merge x y) = num_occurs a x + num_occurs a y)
simplify
into: true
expansions: [num_occurs, merge, num_occurs, merge, num_occurs, merge, num_occurs, merge]
rewrite_steps: forward_chaining:
theorem num_occurs_arith (x : int list) (a : int) =
x <> [] && List.tl x <> []
==>
num_occurs a (odds (List.tl x))
=
num_occurs a x - num_occurs a (odds x)
[@@induct functional num_occurs]
[@@rewrite]
val num_occurs_arith : Z.t list -> Z.t -> bool = <fun> Goal: (x <> []) && (List.tl x <> []) ==> num_occurs a (odds (List.tl x)) = num_occurs a x - num_occurs a (odds x). 1 nontautological subgoal. We shall induct according to a scheme derived from num_occurs. Induction scheme: (not (not (List.hd x = a) && x <> []) && not ((List.hd x = a) && x <> []) ==> φ a x) && ((x <> [] && ((List.hd x = a) && φ a (List.tl x)) ==> φ a x) && (x <> [] && (not (List.hd x = a) && φ a (List.tl x)) ==> φ a x)). 3 nontautological subgoals. Subgoal 3: H0. not (x = []) H1. not (List.tl x = []) H2. not (not (List.hd x = a) && x <> []) H3. not ((List.hd x = a) && x <> []) |--------------------------------------------------------------------------- num_occurs a (odds (List.tl x)) = num_occurs a x + (-1) * num_occurs a (odds x) But simplification reduces this to true. Subgoal 2: H0. not (x = []) H1. not (List.tl x = []) H2. x <> [] H3. List.hd x = a H4. not (List.tl x = []) && not (List.tl (List.tl x) = []) ==> num_occurs a (odds (List.tl (List.tl x))) = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl x)) |--------------------------------------------------------------------------- num_occurs a (odds (List.tl x)) = num_occurs a x + (-1) * num_occurs a (odds x) This simplifies, using the definitions of num_occurs and odds to the following 4 subgoals: Subgoal 2.4: H0. x <> [] H1. (List.tl x) <> [] H2. (List.tl (List.tl x)) <> [] H3. num_occurs (List.hd x) (odds (List.tl (List.tl x))) = num_occurs (List.hd x) (List.tl (List.tl x)) + (-1) * num_occurs (List.hd x) (odds (List.tl x)) |--------------------------------------------------------------------------- C0. List.hd (List.tl x) = List.hd x C1. num_occurs (List.hd x) (odds (List.tl x)) = num_occurs (List.hd x) (List.tl x) + (-1) * num_occurs (List.hd x) (odds (List.tl (List.tl x))) But simplification reduces this to true, using the definition of num_occurs. Subgoal 2.3: H0. x <> [] H1. (List.tl x) <> [] H2. (List.tl (List.tl x)) <> [] H3. List.hd (List.tl x) = List.hd x H4. num_occurs (List.hd x) (odds (List.tl (List.tl x))) = 1 + num_occurs (List.hd x) (List.tl (List.tl x)) + (-1) * num_occurs (List.hd x) (odds (List.tl x)) |--------------------------------------------------------------------------- num_occurs (List.hd x) (odds (List.tl x)) = num_occurs (List.hd x) (List.tl x) + (-1) * num_occurs (List.hd x) (odds (List.tl (List.tl x))) But simplification reduces this to true, using the definition of num_occurs. Subgoal 2.2: H0. x <> [] H1. (List.tl x) <> [] H2. List.tl (List.tl x) = [] |--------------------------------------------------------------------------- C0. List.hd (List.tl x) = List.hd x C1. 0 = num_occurs (List.hd x) (List.tl x) + (-1) * num_occurs (List.hd x) (odds (List.tl (List.tl x))) This simplifies, using the definitions of num_occurs and odds to: Subgoal 2.2': H0. x <> [] H1. (List.tl x) <> [] H2. List.tl (List.tl x) = [] |--------------------------------------------------------------------------- C0. List.hd (List.tl x) = List.hd x C1. 0 = num_occurs (List.hd x) (List.tl (List.tl x)) But simplification reduces this to true, using the definition of num_occurs. Subgoal 2.1: H0. x <> [] H1. (List.tl x) <> [] H2. List.tl (List.tl x) = [] H3. List.hd (List.tl x) = List.hd x |--------------------------------------------------------------------------- 1 = num_occurs (List.hd x) (List.tl x) + (-1) * num_occurs (List.hd x) (odds (List.tl (List.tl x))) This simplifies, using the definitions of num_occurs and odds to: Subgoal 2.1': H0. List.tl (List.tl x) = [] H1. (List.tl x) <> [] H2. x <> [] H3. List.hd (List.tl x) = List.hd x |--------------------------------------------------------------------------- 0 = num_occurs (List.hd x) (List.tl (List.tl x)) But simplification reduces this to true, using the definition of num_occurs. Subgoal 1: H0. not (x = []) H1. not (List.tl x = []) H2. x <> [] H3. not (List.hd x = a) H4. not (List.tl x = []) && not (List.tl (List.tl x) = []) ==> num_occurs a (odds (List.tl (List.tl x))) = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl x)) |--------------------------------------------------------------------------- num_occurs a (odds (List.tl x)) = num_occurs a x + (-1) * num_occurs a (odds x) This simplifies, using the definitions of num_occurs and odds to the following 4 subgoals: Subgoal 1.4: H0. x <> [] H1. (List.tl x) <> [] H2. (List.tl (List.tl x)) <> [] H3. num_occurs a (odds (List.tl (List.tl x))) = num_occurs a (List.tl (List.tl x)) + (-1) * num_occurs a (odds (List.tl x)) |--------------------------------------------------------------------------- C0. List.hd x = a C1. num_occurs a (odds (List.tl x)) = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl (List.tl x))) C2. List.hd (List.tl x) = a But simplification reduces this to true, using the definition of num_occurs. Subgoal 1.3: H0. List.hd (List.tl x) = a H1. x <> [] H2. (List.tl x) <> [] H3. (List.tl (List.tl x)) <> [] H4. num_occurs a (odds (List.tl (List.tl x))) = 1 + num_occurs a (List.tl (List.tl x)) + (-1) * num_occurs a (odds (List.tl x)) |--------------------------------------------------------------------------- C0. List.hd x = a C1. num_occurs a (odds (List.tl x)) = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl (List.tl x))) But simplification reduces this to true, using the definition of num_occurs. Subgoal 1.2: H0. x <> [] H1. (List.tl x) <> [] H2. List.tl (List.tl x) = [] |--------------------------------------------------------------------------- C0. List.hd x = a C1. 0 = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl (List.tl x))) C2. List.hd (List.tl x) = a This simplifies, using the definitions of num_occurs and odds to: Subgoal 1.2': H0. List.tl (List.tl x) = [] H1. (List.tl x) <> [] H2. x <> [] |--------------------------------------------------------------------------- C0. List.hd x = a C1. 0 = num_occurs a (List.tl (List.tl x)) C2. List.hd (List.tl x) = a But simplification reduces this to true, using the definition of num_occurs. Subgoal 1.1: H0. List.hd (List.tl x) = a H1. x <> [] H2. (List.tl x) <> [] H3. List.tl (List.tl x) = [] |--------------------------------------------------------------------------- C0. List.hd x = a C1. 1 = num_occurs a (List.tl x) + (-1) * num_occurs a (odds (List.tl (List.tl x))) This simplifies, using the definitions of num_occurs and odds to: Subgoal 1.1': H0. List.tl (List.tl x) = [] H1. (List.tl x) <> [] H2. x <> [] |--------------------------------------------------------------------------- C0. List.hd x = List.hd (List.tl x) C1. 0 = num_occurs (List.hd (List.tl x)) (List.tl (List.tl x)) But simplification reduces this to true, using the definition of num_occurs. ⓘ Rules: (:def num_occurs) (:def odds) (:induct num_occurs)
ground_instances: | 0 |
definitions: | 48 |
inductions: | 1 |
search_time: | 1.160s |
start[1.160s, "Goal"] let (_x_0 : int list) = List.tl ( :var_0: ) in not (( :var_0: ) = []) && not (_x_0 = []) ==> num_occurs ( :var_1: ) (odds _x_0) = num_occurs ( :var_1: ) ( :var_0: ) - num_occurs ( :var_1: ) (odds ( :var_0: ))
subproof
let (_x_0 : int list) = List.tl x in (x = []) || (_x_0 = []) || (num_occurs a (odds _x_0) = num_occurs a x + (-1) * num_occurs a (odds x))start[1.160s, "1"] let (_x_0 : int list) = List.tl x in (x = []) || (_x_0 = []) || (num_occurs a (odds _x_0) = num_occurs a x + (-1) * num_occurs a (odds x))
induction on (functional num_occurs) :scheme (not (not (List.hd x = a) && x <> []) && not ((List.hd x = a) && x <> []) ==> φ a x) && ((x <> [] && ((List.hd x = a) && φ a (List.tl x)) ==> φ a x) && (x <> [] && (not (List.hd x = a) && φ a (List.tl x)) ==> φ a x))
Split (let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = not (_x_0 = []) in let (_x_2 : int) = num_occurs a (odds _x_0) in let (_x_3 : bool) = not (not (x = []) && _x_1) || (_x_2 = num_occurs a x + (-1) * num_occurs a (odds x)) in let (_x_4 : bool) = List.hd x = a in let (_x_5 : bool) = not _x_4 in let (_x_6 : bool) = x <> [] in let (_x_7 : int list) = List.tl _x_0 in let (_x_8 : bool) = not (_x_1 && not (_x_7 = [])) || (num_occurs a (odds _x_7) = num_occurs a _x_0 + (-1) * _x_2) in (_x_3 || not (not (_x_5 && _x_6) && not (_x_4 && _x_6))) && (_x_3 || not (_x_6 && _x_4 && _x_8)) && (_x_3 || not (_x_6 && _x_5 && _x_8)) :cases [let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = List.hd x = a in let (_x_2 : bool) = x <> [] in (x = []) || (_x_0 = []) || (not _x_1 && _x_2) || (_x_1 && _x_2) || (num_occurs a (odds _x_0) = num_occurs a x + (-1) * num_occurs a (odds x)); let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || not (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x)); let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x))])
subproof
let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x))start[1.159s, "1"] let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x))
simplify
into: let (_x_0 : bool) = List.hd x = a in let (_x_1 : bool) = x = [] in let (_x_2 : int list) = List.tl x in let (_x_3 : bool) = _x_2 = [] in let (_x_4 : bool) = _x_0 || _x_1 || _x_3 in let (_x_5 : int list) = List.tl _x_2 in let (_x_6 : bool) = _x_5 = [] in let (_x_7 : int) = num_occurs a (odds _x_5) in let (_x_8 : int) = num_occurs a _x_5 in let (_x_9 : int) = num_occurs a (odds _x_2) in let (_x_10 : int) = (-1) * _x_9 in let (_x_11 : int) = num_occurs a _x_2 + (-1) * _x_7 in let (_x_12 : bool) = _x_9 = _x_11 in let (_x_13 : bool) = List.hd _x_2 = a in let (_x_14 : bool) = not _x_13 || _x_0 || _x_1 || _x_3 in let (_x_15 : bool) = not _x_6 in (_x_4 || _x_6 || not (_x_7 = _x_8 + _x_10) || _x_12 || _x_13) && (_x_14 || _x_6 || _x_12 || not (_x_7 = 1 + _x_8 + _x_10)) && (_x_4 || _x_15 || (0 = _x_11) || _x_13) && (_x_14 || _x_15 || (1 = _x_11))
expansions: [num_occurs, odds, num_occurs, num_occurs, num_occurs, num_occurs, odds, num_occurs, odds, num_occurs, num_occurs, odds, num_occurs, num_occurs]
rewrite_steps: forward_chaining: Subproof
Subproof
Subproof
Subproof
subproof
let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || not (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x))start[1.159s, "2"] let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = _x_0 = [] in let (_x_2 : int list) = List.tl _x_0 in let (_x_3 : int) = num_occurs a (odds _x_0) in (x = []) || _x_1 || not (x <> []) || not (List.hd x = a) || not (not _x_1 && not (_x_2 = []) ==> num_occurs a (odds _x_2) = num_occurs a _x_0 + (-1) * _x_3) || (_x_3 = num_occurs a x + (-1) * num_occurs a (odds x))
simplify
into: let (_x_0 : bool) = x = [] in let (_x_1 : int list) = List.tl x in let (_x_2 : bool) = _x_1 = [] in let (_x_3 : bool) = _x_0 || _x_2 in let (_x_4 : int list) = List.tl _x_1 in let (_x_5 : bool) = _x_4 = [] in let (_x_6 : bool) = _x_3 || _x_5 in let (_x_7 : int) = List.hd x in let (_x_8 : bool) = List.hd _x_1 = _x_7 in let (_x_9 : int) = num_occurs _x_7 (odds _x_1) in let (_x_10 : int) = num_occurs _x_7 (odds _x_4) in let (_x_11 : int) = num_occurs _x_7 _x_1 + (-1) * _x_10 in let (_x_12 : bool) = _x_9 = _x_11 in let (_x_13 : int) = num_occurs _x_7 _x_4 in let (_x_14 : int) = (-1) * _x_9 in let (_x_15 : bool) = not _x_8 in let (_x_16 : bool) = not _x_5 in (_x_6 || _x_8 || _x_12 || not (_x_10 = _x_13 + _x_14)) && (_x_6 || _x_12 || _x_15 || not (_x_10 = 1 + _x_13 + _x_14)) && (_x_3 || _x_16 || _x_8 || (0 = _x_11)) && ((1 = _x_11) || _x_0 || _x_2 || _x_16 || _x_15)
expansions: [num_occurs, odds, num_occurs, num_occurs, num_occurs, num_occurs, odds, num_occurs, odds, num_occurs, num_occurs, odds, num_occurs, num_occurs]
rewrite_steps: forward_chaining: Subproof
Subproof
Subproof
Subproof
subproof
let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = List.hd x = a in let (_x_2 : bool) = x <> [] in (x = []) || (_x_0 = []) || (not _x_1 && _x_2) || (_x_1 && _x_2) || (num_occurs a (odds _x_0) = num_occurs a x + (-1) * num_occurs a (odds x))start[1.159s, "3"] let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = List.hd x = a in let (_x_2 : bool) = x <> [] in (x = []) || (_x_0 = []) || (not _x_1 && _x_2) || (_x_1 && _x_2) || (num_occurs a (odds _x_0) = num_occurs a x + (-1) * num_occurs a (odds x))
simplify
into: true
expansions: []
rewrite_steps: forward_chaining:
Finally, let's put the pieces together and prove our main remaining theorem.
And now, our second main theorem at last:
theorem merge_sort_elements a (x : int list) =
num_occurs a x = num_occurs a (merge_sort x)
[@@induct functional merge_sort]
val merge_sort_elements : Z.t -> Z.t list -> bool = <fun> Goal: num_occurs a x = num_occurs a (merge_sort x). 1 nontautological subgoal. We shall induct according to a scheme derived from merge_sort. Induction scheme: (not (not Is_a([], List.tl x) && x <> []) ==> φ a x) && (x <> [] && (not Is_a([], List.tl x) && (φ a (odds (List.tl x)) && φ a (odds x))) ==> φ a x). 2 nontautological subgoals. Subgoal 2: |--------------------------------------------------------------------------- C0. not Is_a([], List.tl x) && x <> [] C1. num_occurs a x = num_occurs a (merge_sort x) This simplifies, using the definitions of merge_sort and num_occurs to the following 3 subgoals: Subgoal 2.3: H0. Is_a([], List.tl x) |--------------------------------------------------------------------------- C0. x <> [] C1. num_occurs a x = 0 But simplification reduces this to true, using the definition of num_occurs. Subgoal 2.2: H0. Is_a([], List.tl x) H1. x <> [] |--------------------------------------------------------------------------- C0. List.hd x = a C1. num_occurs a x = 0 This simplifies, using the definition of num_occurs to: Subgoal 2.2': H0. Is_a([], List.tl x) H1. x <> [] |--------------------------------------------------------------------------- C0. num_occurs a (List.tl x) = 0 C1. List.hd x = a But simplification reduces this to true, using the definition of num_occurs. Subgoal 2.1: H0. Is_a([], List.tl x) H1. x <> [] H2. List.hd x = a |--------------------------------------------------------------------------- num_occurs a x = 1 This simplifies, using the definition of num_occurs to: Subgoal 2.1': H0. Is_a([], List.tl x) H1. x <> [] |--------------------------------------------------------------------------- num_occurs (List.hd x) (List.tl x) = 0 But simplification reduces this to true, using the definition of num_occurs. Subgoal 1: H0. x <> [] H1. not Is_a([], List.tl x) H2. num_occurs a (odds (List.tl x)) = num_occurs a (merge_sort (odds (List.tl x))) H3. num_occurs a (odds x) = num_occurs a (merge_sort (odds x)) |--------------------------------------------------------------------------- num_occurs a x = num_occurs a (merge_sort x) This simplifies, using the definitions of merge_sort and num_occurs, and the rewrite rule num_occurs_arith to the following 2 subgoals: Subgoal 1.2: H0. num_occurs a (odds x) = num_occurs a (merge_sort (odds x)) H1. x <> [] H2. num_occurs a (List.tl x) + (-1) * num_occurs a (odds x) = num_occurs a (merge_sort (odds (List.tl x))) |--------------------------------------------------------------------------- C0. Is_a([], List.tl x) C1. List.hd x = a C2. num_occurs a (List.tl x) = num_occurs a (merge (merge_sort (odds x)) (merge_sort (odds (List.tl x)))) But simplification reduces this to true, using the rewrite rule num_occur_merge. Subgoal 1.1: H0. num_occurs a (odds x) = num_occurs a (merge_sort (odds x)) H1. x <> [] H2. List.hd x = a H3. num_occurs a (List.tl x) + (-1) * num_occurs a (odds x) = (-1) + num_occurs a (merge_sort (odds (List.tl x))) |--------------------------------------------------------------------------- C0. Is_a([], List.tl x) C1. num_occurs a (List.tl x) = (-1) + num_occurs a (merge (merge_sort (odds x)) (merge_sort (odds (List.tl x)))) But simplification reduces this to true, using the rewrite rule num_occur_merge. ⓘ Rules: (:def merge_sort) (:def num_occurs) (:rw num_occur_merge) (:rw num_occurs_arith) (:induct merge_sort)
ground_instances: | 0 |
definitions: | 17 |
inductions: | 1 |
search_time: | 1.749s |
start[1.749s, "Goal"] num_occurs ( :var_0: ) ( :var_1: ) = num_occurs ( :var_0: ) (merge_sort ( :var_1: ))
subproof
num_occurs a x = num_occurs a (merge_sort x)start[1.749s, "1"] num_occurs a x = num_occurs a (merge_sort x)
induction on (functional merge_sort) :scheme (not (not Is_a([], List.tl x) && x <> []) ==> φ a x) && (x <> [] && (not Is_a([], List.tl x) && (φ a (odds (List.tl x)) && φ a (odds x))) ==> φ a x)
Split (let (_x_0 : int list) = List.tl x in let (_x_1 : bool) = not Is_a([], _x_0) in let (_x_2 : bool) = x <> [] in let (_x_3 : bool) = num_occurs a x = num_occurs a (merge_sort x) in let (_x_4 : int list) = odds _x_0 in let (_x_5 : int list) = odds x in ((_x_1 && _x_2) || _x_3) && (not (_x_2 && _x_1 && (num_occurs a _x_4 = num_occurs a (merge_sort _x_4)) && (num_occurs a _x_5 = num_occurs a (merge_sort _x_5))) || _x_3) :cases [(not Is_a([], List.tl x) && x <> []) || (num_occurs a x = num_occurs a (merge_sort x)); let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = odds _x_0 in let (_x_2 : int list) = odds x in not (x <> []) || Is_a([], _x_0) || not (num_occurs a _x_1 = num_occurs a (merge_sort _x_1)) || not (num_occurs a _x_2 = num_occurs a (merge_sort _x_2)) || (num_occurs a x = num_occurs a (merge_sort x))])
subproof
let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = odds _x_0 in let (_x_2 : int list) = odds x in not (x <> []) || Is_a([], _x_0) || not (num_occurs a _x_1 = num_occurs a (merge_sort _x_1)) || not (num_occurs a _x_2 = num_occurs a (merge_sort _x_2)) || (num_occurs a x = num_occurs a (merge_sort x))start[1.749s, "1"] let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = odds _x_0 in let (_x_2 : int list) = odds x in not (x <> []) || Is_a([], _x_0) || not (num_occurs a _x_1 = num_occurs a (merge_sort _x_1)) || not (num_occurs a _x_2 = num_occurs a (merge_sort _x_2)) || (num_occurs a x = num_occurs a (merge_sort x))
simplify
into: let (_x_0 : int list) = List.tl x in let (_x_1 : int list) = odds x in let (_x_2 : int) = num_occurs a _x_1 in let (_x_3 : int list) = merge_sort _x_1 in let (_x_4 : bool) = Is_a([], _x_0) || not (_x_2 = num_occurs a _x_3) || not (x <> []) in let (_x_5 : bool) = List.hd x = a in let (_x_6 : int) = num_occurs a _x_0 in let (_x_7 : int) = _x_6 + (-1) * _x_2 in let (_x_8 : int list) = merge_sort (odds _x_0) in let (_x_9 : int) = num_occurs a _x_8 in let (_x_10 : int) = num_occurs a (merge _x_3 _x_8) in (_x_4 || _x_5 || not (_x_7 = _x_9) || (_x_6 = _x_10)) && (_x_4 || not _x_5 || (_x_6 = (-1) + _x_10) || not (_x_7 = (-1) + _x_9))
expansions: [merge_sort, num_occurs, merge_sort, num_occurs, num_occurs]
rewrite_steps: num_occurs_arith
forward_chaining: Subproof
Subproof
subproof
(not Is_a([], List.tl x) && x <> []) || (num_occurs a x = num_occurs a (merge_sort x))start[1.749s, "2"] (not Is_a([], List.tl x) && x <> []) || (num_occurs a x = num_occurs a (merge_sort x))
simplify
into: let (_x_0 : bool) = not Is_a([], List.tl x) in let (_x_1 : bool) = x <> [] in let (_x_2 : int) = num_occurs a x in let (_x_3 : bool) = _x_2 = 0 in let (_x_4 : bool) = _x_0 || not _x_1 in let (_x_5 : bool) = List.hd x = a in (_x_0 || _x_1 || _x_3) && (_x_4 || _x_5 || _x_3) && (_x_4 || not _x_5 || (_x_2 = 1))
expansions: [num_occurs, merge_sort, num_occurs, num_occurs, num_occurs, num_occurs, merge_sort]
rewrite_steps: forward_chaining: Subproof
Subproof
Subproof
So now we're sure merge_sort
really is a proper sorting function.
Merge Sort is Correct!¶
To recap, we've proved:
theorem merge_sort_sorts x =
is_sorted (merge_sort x)
and
theorem merge_sort_elements a (x : int list) =
num_occurs a x = num_occurs a (merge_sort x)
Beautiful! Happy proving (and sorting)!