#| This is in an ACL2 "book" defining a batcher-merge sort for powerlists. It uses the infrastructure defined in merge-sort.lisp. This book, as well as others working with powerlists, is described in the paper "Defthms About Zip and Tie", UTCS tech report TR97-02. The informal correctness algorithm is as follows. Consider merging two sorted lists X = (x1 x2 ... xn) and Y = (y1 y2 ... yn). We restrict ourselves to lists of length n=2^k for some k. Now, the merging step is given by (merge '(x1 x2 ... xn) '(y1 y2 ... yn)) = (zip (min (merge '(x1 x3 ... x{n-1}) '(y2 y4 ... yn)) (merge '(x2 x4 ... xn) '(y1 y3 ... y{n-1}))) (max (merge '(x1 x3 ... x{n-1}) '(y2 y4 ... yn)) (merge '(x2 x4 ... xn) '(y1 y3 ... y{n-1})))) Ok, now let's break this up into two pieces. Consider the step (zip (min A B) (max A B)) This is sorted when A and B have this remarkable property: A and B are said to be interleaved if for any valid indices i= (p-min-elem (p-max-tie x y)) (p-min-elem x)) (>= (p-min-elem (p-max-tie x y)) (p-min-elem y)))) :rule-classes (:rewrite :linear)) (defthm min-elem-p-max (implies (p-similar-p x y) (and (>= (p-min-elem (p-max x y)) (p-min-elem x)) (>= (p-min-elem (p-max x y)) (p-min-elem y)))) :hints (("Goal" :use ((:instance max-tie-same-as-max-zip) (:instance min-elem-p-max-tie)))) :rule-classes (:rewrite :linear)) (defthm max-elem-p-max-tie (implies (p-similar-p x y) (equal (p-max-elem (p-max-tie x y)) (if (>= (p-max-elem x) (p-max-elem y)) (p-max-elem x) (p-max-elem y))))) (defthm max-elem-p-max (implies (p-similar-p x y) (equal (p-max-elem (p-max x y)) (if (>= (p-max-elem x) (p-max-elem y)) (p-max-elem x) (p-max-elem y)))) :hints (("Goal" :use ((:instance max-tie-same-as-max-zip) (:instance max-elem-p-max-tie))))) ;;; Now, we derive two easy consequences of the interleaved definition. ;;; Namely, the two lists must themselves be sorted. (defthm interleaved-implies-sorted-1 (implies (p-interleaved-p x y) (p-sorted-p x)) :rule-classes (:type-prescription :rewrite)) (defthm interleaved-implies-sorted-2 (implies (p-interleaved-p x y) (p-sorted-p y)) :rule-classes (:type-prescription :rewrite)) ;;; Even better, we can conclude that two interleaved powerlists are ;;; similar. This turns out to be a dangerous rewrite rule, so we disable it. (defthm interleaved-implies-similar (implies (p-interleaved-p x y) (equal (p-similar-p x y) t)) :rule-classes nil) ;;; It is tempting to believe that interleaved is an equivalence ;;; relation. Certainly, the theorems below indicate that. However, it's ;;; simply not true, since the missing theorem (reflexivity) is clearly false. ;;; We don't know if ACL2 can make as much use of the following as it does out ;;; of equivalence reasoning. (defthm p-interleaved-p-is-boolean (booleanp (p-interleaved-p x y))) (defthm p-interleaved-p-is-symmetric (implies (p-interleaved-p x y) (p-interleaved-p y x))) (defthm p-interleaved-p-is-transitive (implies (and (p-interleaved-p x y) (p-interleaved-p y z)) (p-interleaved-p x x))) ;;; Now, we can prove that (zip (min A B) (max A B)), the last step of ;;; batcher-merge properly sorts if A and B are interleaved. It turns out to ;;; be much easier to prove this is we allow ourselves to use the untie ;;; versions of the min/max functions. So we do so first, then prove the main ;;; result by invoking their equality. (defthm zip-min-max-sorted-if-interleaved-tie (implies (and (p-interleaved-p x y) (p-similar-p x y) (p-number-list x) (p-number-list y)) (p-sorted-p (p-zip (p-min-tie x y) (p-max-tie x y))))) (defthm zip-min-max-sorted-if-interleaved (implies (and (p-interleaved-p x y) (p-similar-p x y) (p-number-list x) (p-number-list y)) (p-sorted-p (p-zip (p-min x y) (p-max x y)))) :hints (("Goal" :use ((:instance min-tie-same-as-min-zip) (:instance max-tie-same-as-max-zip) (:instance zip-min-max-sorted-if-interleaved-tie)) :in-theory (disable zip-min-max-sorted-if-interleaved-tie)))) ;;; We are now trying to prove that the recursive calls to batcher merge return ;;; interleaved results. The proof (outlined at the top of the file, and ;;; detailed in the tech report UTCS TR97-02) is based on reasoning about ;;; indices. In particular, it can be proved by considering which X's and Y's ;;; can appear in corresponding prefixes of the two merged lists. We replace ;;; the notion of indices with the (equivalent) notion of the number of ;;; elements smaller than a given X. We say "equivalent", since the sublists ;;; are expected to be sorted. (Of course, there's duplicates to worry about, ;;; but it turns out that's not a problem.) ;;; First, we define the concepts of p-member-count-<= and then we prove lemmas ;;; that allow us to compute this across the function symbols we already know. (defun p-member-count-<= (x m) "Return the number of times a scalar <= m occurs in powerlist x" (declare (xargs :guard (and (p-number-list x) (numericp m)))) (if (powerlist-p x) (+ (p-member-count-<= (p-untie-l x) m) (p-member-count-<= (p-untie-r x) m)) (if (<= (realfix x) m) 1 0))) (defun p-elem-count (x) "Return the number of scalars in powerlist x" (declare (xargs :guard (p-number-list x))) (if (powerlist-p x) (+ (p-elem-count (p-untie-l x)) (p-elem-count (p-untie-r x))) 1)) (defthm similar-elem-count (implies (and (p-similar-p x y) ;; Added for mod to ACL2 v2-8 that does better matching for ;; calls of equivalence relations against the current context ;; (avoids rewrite stack overflow in ;; unzip-of-sorted-is-interleaved): (syntaxp (not (acl2::term-order x y)))) (equal (p-elem-count x) (p-elem-count y)))) (defthm max-elem-member-count-<= (implies (<= (p-max-elem x) m) (equal (p-member-count-<= x m) (p-elem-count x)))) (defthm min-elem-member-count-<= (implies (< m (p-min-elem x)) (equal (p-member-count-<= x m) 0))) (defthm min-elem-member-count-<=-2 (implies (>= m (p-min-elem x)) (<= 1 (p-member-count-<= x m))) :rule-classes (:rewrite :linear)) (defthm max-elem-member-count-<=-2 (implies (< m (p-max-elem x)) (< (p-member-count-<= x m) (p-elem-count x))) :rule-classes (:rewrite :linear)) (defthm sorted-member-count-<=-p-min-untie-r (implies (and (powerlist-p x) (p-sorted-p x)) (<= (p-elem-count (p-untie-l x)) (p-member-count-<= x (p-min-elem (p-untie-r x))))) :rule-classes (:rewrite :linear)) (defthm sorted-member-count-<=-p-max-untie-l (implies (and (powerlist-p x) (p-sorted-p x)) (<= (p-elem-count (p-untie-l x)) (p-member-count-<= x (p-max-elem (p-untie-l x))))) :rule-classes (:rewrite :linear)) ;;; The next set of theorems are key. They tell us how the elements of a ;;; sorted list are distributed when we take their unzip. This is the theorem ;;; that will let us argue about how many X's and Y's can go at the front of ;;; the merged sublists, and so will eventually prove the two merged sublists ;;; are interleaved. (defthm member-count-<=-of-sorted-unzips-1 (implies (and (powerlist-p x) (p-regular-p x) (p-sorted-p x)) (<= (p-member-count-<= (p-unzip-r x) m) (p-member-count-<= (p-unzip-l x) m))) :rule-classes (:rewrite :linear)) (defthm member-count-<=-of-interleaved-crossing-rule (implies (and (p-interleaved-p x y) (< (p-member-count-<= x m) (p-member-count-<= y m))) (< m (p-max-elem x))) :rule-classes (:rewrite :linear)) (defthm member-count-<=-of-interleaved-crosses-once (implies (and (powerlist-p x) (p-interleaved-p x y) (< (p-member-count-<= (p-untie-l x) m) (p-member-count-<= (p-untie-l y) m))) (equal (equal (p-member-count-<= (p-untie-r x) m) (p-member-count-<= (p-untie-r y) m)) t)) :hints (("Goal" :use (:instance member-count-<=-of-interleaved-crossing-rule (x (p-untie-l x)) (y (p-untie-l y))) :in-theory (disable member-count-<=-of-interleaved-crossing-rule)))) (defthm member-count-<=-of-interleaved-1 (implies (and (powerlist-p x) (p-interleaved-p x y) (equal (1+ (p-member-count-<= (p-untie-l x) m)) (p-member-count-<= (p-untie-l y) m))) (equal (equal (p-member-count-<= (p-untie-r x) m) (p-member-count-<= (p-untie-r y) m)) t)) :hints (("Goal" :use (:instance member-count-<=-of-interleaved-crosses-once) :in-theory (disable member-count-<=-of-interleaved-crosses-once)))) (defthm member-count-<=-of-interleaved-2 (implies (and (powerlist-p x) (p-interleaved-p x y) (equal (1+ (p-member-count-<= (p-untie-l x) m)) (p-member-count-<= (p-untie-l y) m))) (and (equal (equal (1+ (p-member-count-<= (p-untie-r x) m)) (p-member-count-<= (p-untie-r y) m)) nil) (equal (equal (1+ (p-member-count-<= (p-untie-r y) m)) (p-member-count-<= (p-untie-r x) m)) nil))) :hints (("Goal" :use (:instance member-count-<=-of-interleaved-1) :in-theory (disable member-count-<=-of-interleaved-1 member-count-<=-of-interleaved-crosses-once)))) (defthm member-count-<=-of-interleaved (implies (p-interleaved-p x y) (or (equal (p-member-count-<= x m) (p-member-count-<= y m)) (equal (1+ (p-member-count-<= y m)) (p-member-count-<= x m)) (equal (1+ (p-member-count-<= x m)) (p-member-count-<= y m)))) :rule-classes nil) (defthm unzip-of-sorted-is-interleaved (implies (and (p-sorted-p x) (p-regular-p x)) (p-interleaved-p (p-unzip-l x) (p-unzip-r x)))) (defthm member-count-<=-of-sorted-unzips-2 (implies (and (powerlist-p x) (p-regular-p x) (p-sorted-p x)) (<= (p-member-count-<= (p-unzip-l x) m) (1+ (p-member-count-<= (p-unzip-r x) m)))) :hints (("Goal" :use ((:instance unzip-of-sorted-is-interleaved) (:instance member-count-<=-of-interleaved (x (p-unzip-l x)) (y (p-unzip-r x))) (:instance member-count-<=-of-sorted-unzips-1)) :in-theory (disable unzip-of-sorted-is-interleaved member-count-<=-of-sorted-unzips-1))) :rule-classes (:rewrite :linear)) (defthm member-count-<=-of-merge (implies (and (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y)) (equal (p-member-count-<= (p-batcher-merge x y) m) (+ (p-member-count-<= x m) (p-member-count-<= y m)))) :hints (("Goal" :by (:functional-instance b-tie-plus-fn1-of-merge (b-tie-plus-fn1 (lambda (x) (p-member-count-<= x m))) (fn1-num (lambda (x) (if (<= (realfix x) m) 1 0))))))) ;;; The key theorem. We now know that the member-counts (i.e., indices) of any ;;; given number in the two sublists regurned by batcher merge must be within ;;; 1. What is left is to show that this fact implies the two sublists are ;;; interleaved (defthm member-count-<=-of-merge-unzips (implies (and (powerlist-p x) (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y)) (let ((M1 (p-member-count-<= (p-batcher-merge (p-unzip-l x) (p-unzip-r y)) m)) (M2 (p-member-count-<= (p-batcher-merge (p-unzip-r x) (p-unzip-l y)) m))) (or (equal M1 M2) (equal (1+ M1) M2) (equal (1+ M2) M1)))) :hints (("Goal" :use ((:instance member-count-<=-of-sorted-unzips-1) (:instance member-count-<=-of-sorted-unzips-1 (x y)) (:instance member-count-<=-of-sorted-unzips-2) (:instance member-count-<=-of-sorted-unzips-2 (x y))) :in-theory (disable member-count-<=-of-sorted-unzips-1 member-count-<=-of-sorted-unzips-2))) :rule-classes nil) ;;; Now, we prove, by contradiction, that the previous theorem implies the two ;;; sublists are interleaved. We will do this by assuming that the sublists ;;; are not interleaved. Then, we will find a specific number m, so that it ;;; violates the theorem above. The function which picks this number is ;;; defined below. (defun interleaved-p-cutoff (x y) "Counter-example to member-count-<=-of-merge-unzips" (declare (xargs :guard (and (p-number-list x) (p-number-list y)))) (if (and (powerlist-p x) (powerlist-p y)) (cond ((< (p-min-elem (p-untie-r x)) (p-max-elem (p-untie-l x))) (p-min-elem (p-untie-r x))) ((< (p-min-elem (p-untie-r x)) (p-max-elem (p-untie-l y))) (p-min-elem (p-untie-r x))) ((interleaved-p-cutoff (p-untie-l x) (p-untie-l y)) (interleaved-p-cutoff (p-untie-l x) (p-untie-l y))) ((interleaved-p-cutoff (p-untie-r x) (p-untie-r y)) (interleaved-p-cutoff (p-untie-r x) (p-untie-r y)))) nil)) (defthm interleaved-p-if-nil-cutoff (implies (and (p-similar-p x y) (p-number-list x) (p-number-list y) (not (numericp (interleaved-p-cutoff x y))) (not (numericp (interleaved-p-cutoff y x)))) (p-interleaved-p x y))) (encapsulate () (local (defthm foo-1 (implies (interleaved-p-cutoff x y) (and (<= (p-min-elem x) (interleaved-p-cutoff x y)) (<= (interleaved-p-cutoff x y) (p-max-elem x)))) :hints (("Goal" :induct (interleaved-p-cutoff x y))) :rule-classes (:linear :rewrite))) (local (defthm foo-2 (implies (and (p-sorted-p x) (interleaved-p-cutoff x y)) (< (interleaved-p-cutoff x y) (p-max-elem y))) :hints (("Goal" :induct (interleaved-p-cutoff x y))) :rule-classes (:linear :rewrite))) (local (defthm foo-3 (implies (and (powerlist-p x) (powerlist-p y) (p-sorted-p x) (p-sorted-p y) (interleaved-p-cutoff (p-untie-l x) (p-untie-l y))) (and (<= (interleaved-p-cutoff (p-untie-l x) (p-untie-l y)) (p-min-elem (p-untie-r x))) (< (interleaved-p-cutoff (p-untie-l x) (p-untie-l y)) (p-min-elem (p-untie-r y))))) :rule-classes (:linear :rewrite))) (defthm member-count-diff-2-if-interleaved-cutoff-sorted (implies (and (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y) (interleaved-p-cutoff x y)) (< (1+ (p-member-count-<= y (interleaved-p-cutoff x y))) (p-member-count-<= x (interleaved-p-cutoff x y)))) :hints (("Goal" :induct (interleaved-p-cutoff x y)) ("Subgoal *1/2'''" :expand ((p-member-count-<= y (p-min-elem (powerlist-cdr x)))) :do-not '(eliminate-destructors))) :rule-classes nil)) ;;; With the two theorems above, it is time to show that the inner calls to ;;; batcher merge are indeed interleaved. (encapsulate () (local (defthm inner-batcher-merge-call-is-interleaved-p-1 (implies (and (powerlist-p x) (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y) (p-sorted-p (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (p-sorted-p (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) (not (interleaved-p-cutoff (p-batcher-merge (p-unzip-l x) (p-unzip-r y)) (p-batcher-merge (p-unzip-r x) (p-unzip-l y))))) :hints (("Goal" :use ((:instance member-count-diff-2-if-interleaved-cutoff-sorted (x (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (y (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) (:instance member-count-<=-of-merge-unzips (m (interleaved-p-cutoff (p-batcher-merge (p-unzip-l x) (p-unzip-r y)) (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))))))))) (local (defthm inner-batcher-merge-call-is-interleaved-p-2 (implies (and (powerlist-p x) (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y) (p-sorted-p (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (p-sorted-p (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) (not (interleaved-p-cutoff (p-batcher-merge (p-unzip-r x) (p-unzip-l y)) (p-batcher-merge (p-unzip-l x) (p-unzip-r y))))) :hints (("Goal" :use ((:instance member-count-diff-2-if-interleaved-cutoff-sorted (x (p-batcher-merge (p-unzip-r x) (p-unzip-l y))) (y (p-batcher-merge (p-unzip-l x) (p-unzip-r y)))) (:instance member-count-<=-of-merge-unzips (m (interleaved-p-cutoff (p-batcher-merge (p-unzip-r x) (p-unzip-l y)) (p-batcher-merge (p-unzip-l x) (p-unzip-r y)))))))))) (defthm inner-batcher-merge-call-is-interleaved-p (implies (and (powerlist-p x) (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y) (p-sorted-p (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (p-sorted-p (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) (p-interleaved-p (p-batcher-merge (p-unzip-l x) (p-unzip-r y)) (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) :hints (("Goal" :use ((:instance interleaved-p-if-nil-cutoff (x (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (y (p-batcher-merge (p-unzip-r x) (p-unzip-l y))))) :in-theory (disable interleaved-p-if-nil-cutoff))))) ;;; Now, it is a simple matter to show that batcher merge returns a sorted ;;; list, as long as we assume the inner calls are doing their job. I.e., we ;;; can now prove the inductive case of the proof (defthm recursive-batcher-merge-is-sorted (implies (and (powerlist-p x) (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y) (p-sorted-p (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (p-sorted-p (p-batcher-merge (p-unzip-r x) (p-unzip-l y)))) (p-sorted-p (p-batcher-merge x y))) :hints (("Goal" :use ((:instance inner-batcher-merge-call-is-interleaved-p) (:instance zip-min-max-sorted-if-interleaved (x (p-batcher-merge (p-unzip-l x) (p-unzip-r y))) (y (p-batcher-merge (p-unzip-r x) (p-unzip-l y))))) :in-theory (disable inner-batcher-merge-call-is-interleaved-p zip-min-max-sorted-if-interleaved)))) ;;; This trivializes the following correctness result for batcher merge. (defthm sorted-merge (implies (and (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y) (p-sorted-p x) (p-sorted-p y)) (p-sorted-p (p-batcher-merge x y))) :hints (("Goal" :induct (unzip-swap-on-x-and-y x y)) ("Subgoal *1/1" :hands-off (p-batcher-merge)))) ;;; We need only show the remaining proof obligations to invoke the generic ;;; merge sorting theorems (defthm batcher-sort-similar (implies (p-regular-p x) (p-similar-p (p-batcher-sort x) x)) :hints (("Subgoal *1/2'" :use (:instance merge-similar-zip (x (p-batcher-sort (p-untie-l x))) (y (p-batcher-sort (p-untie-r x))))))) (defthm batcher-sort-regular (implies (p-regular-p x) (p-regular-p (p-batcher-sort x))) :hints (("Goal" :use (:instance batcher-sort-similar) :in-theory (disable batcher-sort-similar)))) (defthm batcher-sort-number-list (implies (and (p-regular-p x) (p-number-list x)) (p-number-list (p-batcher-sort x)))) (defthm batcher-sort-is-permutation (implies (and (p-regular-p x) (p-number-list x)) (equal (p-member-count (p-batcher-sort x) m) (p-member-count x m))) :hints (("Goal" :by (:functional-instance merge-sort-is-permutation (p-sortable-p (lambda (x) (and (p-regular-p x) (p-number-list x)))) (p-mergeable-p (lambda (x y) (and (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y)))) (p-split-1 (lambda (x) (p-untie-l x))) (p-split-2 (lambda (x) (p-untie-r x))) (p-merge p-batcher-merge) (p-merge-sort p-batcher-sort))))) ;;; And finally, we can invoke the result from the generic merge sort routine (defthm batcher-sort-sorts-inputs (implies (and (p-regular-p x) (p-number-list x)) (p-sorted-p (p-batcher-sort x))) :hints (("Goal" :by (:functional-instance merge-sort-sorts-input (p-sortable-p (lambda (x) (and (p-regular-p x) (p-number-list x)))) (p-mergeable-p (lambda (x y) (and (p-regular-p x) (p-similar-p x y) (p-number-list x) (p-number-list y)))) (p-split-1 (lambda (x) (p-untie-l x))) (p-split-2 (lambda (x) (p-untie-r x))) (p-merge p-batcher-merge) (p-merge-sort p-batcher-sort))))) (verify-guards p-batcher-merge) (verify-guards p-batcher-sort)