File: toy-csort.lisp

package info (click to toggle)
acl2 8.6%2Bdfsg-2
  • links: PTS
  • area: main
  • in suites: trixie
  • size: 1,111,420 kB
  • sloc: lisp: 17,818,294; java: 125,359; python: 28,122; javascript: 23,458; cpp: 18,851; ansic: 11,569; perl: 7,678; xml: 5,591; sh: 3,976; makefile: 3,833; ruby: 2,633; yacc: 1,126; ml: 763; awk: 295; csh: 233; lex: 197; php: 178; tcl: 49; asm: 23; haskell: 17
file content (209 lines) | stat: -rw-r--r-- 7,729 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
; Copyright (C) 2024, J Strother Moore
; License: A 3-clause BSD license.  See the LICENSE file distributed with ACL2.
; Author: J Strother Moore

; Toy Comparator Sort: An Interesting Exercise in Generalization
; J Strother Moore
; December, 2024

; Abstract:

; This problem is based on the problem described in

; ``A Mechanically Checked Proof of a Comparator Sort Algorithm,'' Bishop Brock
; and J Strother Moore, in C. A. R. Hoare, D. Harel, and M. Broy (Eds.)
; Engineering Theories of Software Intensive Systems, Springer NATO Science
; Series, vol 195, pp. 141--175, 2005.

; In that paper we described the verification of a piece of microcode on a
; Motorola digital signal processor that scanned a sequence of inputs and
; collected the five highest values, and output them in order when stimulated
; on another input line.  The ACL2 solution to that problem may be found in
; books/misc/csort.lisp.

; This is a toy version of a similar problem.  I define a function that scans a
; list of inputs (integers and nils) and collects the minimal and the maximal
; values over the latest sequence of integers and outputs them, in order, when
; the sequence is broken by two nil (non-integers) inputs.  The toy
; formalization has two main modules, one called ``c'' (for ``circuit'') which
; processes a single input value, possibly writing to the output and possibly
; updating an internal state, and the other called ``driver'', which runs the
; circuit over each value in an arbitrarily long list and collects the outputs.
; In the original csort.lisp problem, the circuit was a comparator module and
; the driver was microcode.  The main problem presented by the formalization is
; that the state of the circuit is not visible from outside the driver.  The
; solution, both in csort.lisp and this toy version, is to define a function
; that makes the state visible and then to state a generalized theorem about
; it.

; Re-certification

; (certify-book "toy-csort")

(in-package "ACL2")

; This function, thought of as the ``circuit,'' takes a single input and the
; current state.  If the input is nil, it outputs the minimal element collected
; so far (or the maximal element if the minimal has already been output).
; Otherwise the input is expected to be an integer and is compared to the
; minimal and maximal collected so far and, possibly, replaces the values in
; the state with the input.  In this case the circuit outputs nothing.

(defun c (in st)
; This function returns two values, the output stimulated by this input
; and the new state.
  (if in                          ; in non-nil means in is an integer
      (if st                      ; st non-nil means st is (cons lo hi).
          (let* ((lo (car st))
                 (hi (cdr st))
                 (new-lo (min in (if lo lo in))) ; lo might be nil
                 (new-hi (max in hi)))
            (mv nil              ; no output
                (cons new-lo new-hi)))
          (mv nil (cons in in)))
      (if st
          (let* ((lo (car st))
                 (hi (cdr st))
                 (out (if lo lo hi)))
            (mv out
                (if lo
                    (cons nil hi)
                    nil)))
          (mv nil nil))))

(defun driver (lst st)
  (if (endp lst)
      nil
      (mv-let (out new-st)
        (c (car lst) st)
        (if out
            (cons out (driver (cdr lst) new-st))
            (driver (cdr lst) new-st)))))

(defthm example
  (equal (driver '(1 5 -3 4 7 2 nil nil) nil) '(-3 7)))

; Our goal: When called on a non-empty list of integers, lst, followed by two
; nils (given the initial state nil), driver outputs the minimal element of lst
; and then the maximal element.

; (defthm main
;   (implies (and (consp lst)
;                 (integer-listp lst))
;            (equal (driver (append lst '(nil nil))
;                           nil)
;                   (list (min* lst)
;                         (max* lst)))))

; where

(defun min* (lst)
  (if lst
      (if (cdr lst)
          (min (car lst) (min* (cdr lst)))
          (car lst))
      nil))

(defun max* (lst)
  (if lst
      (if (cdr lst)
          (max (car lst) (max* (cdr lst)))
          (car lst))
      nil))

; By the way, we'll need:

(defthm integerp-min*
  (implies (and lst
                (integer-listp lst))
           (integerp (min* lst))))

(defthm integerp-max*
  (implies (and lst
                (integer-listp lst))
           (integerp (max* lst))))

; Note that driver never exposes the state!  We need a function that does, so
; we can specify what happens on (driver (append a b) st).

(defun fin-st-driver (lst st) ; ``final state of driver''
  (if (endp lst)
      st
      (mv-let (out new-st)
        (c (car lst) st)
        (declare (ignore out))
        (fin-st-driver (cdr lst) new-st))))

; The trickiest aspect of this problem is that (car st) = nil is used to signal
; we have output the minimal and a subsequent nil input will output the
; maximal.  So c behaves unexpectedly if the next input is not nil (i.e., is an
; integer): it puts it into both components of state, as though we're back in
; the initial (nil) state.  So we break the problem of (driver (append lst
; '(nil nil)), where lst contains only integers into two parts, running over a
; list of integers starting in a ``full state,'' and then dealing with two nils
; in a row.  But how do we get into a full state?  The answer is that (driver
; lst nil) recurs to (driver (cdr lst) (cons (car lst) (car lst))), and that
; state is full.  So the lemma below applies.

(defun full-statep (st)
  (and (consp st)
       (integerp (car st))
       (integerp (cdr st))))

(defthm key-lemma
  (implies (and (integer-listp lst)
                (full-statep st))
           (equal (fin-st-driver lst st)
                  (cons (if lst
                            (min (min* lst) (car st))
                            (car st))
                        (if lst
                            (max (max* lst) (cdr st))
                            (cdr st))))))

; Note that the key-lemma, above, is about a function, fin-st-driver, that is
; not mentioned in the main theorem, and involves a generalization (in which
; the final state involves both lst and the initial state.

; This lemma motivated the need for fin-st-driver: how does (driver (append a
; b) st) decompose into terms about driver over a and driver over b?

(defthm driver-append
  (equal (driver (append a b) st)
         (append (driver a st)
                 (driver b (fin-st-driver a st)))))

; Of course, if a is just a list of integers, (driver a st) is nil.
(defthm driver-integer-listp
  (implies (integer-listp a)
           (equal (driver a st) nil)))

; And here's our main theorem.
(defthm main
  (implies (and (consp lst)
                (integer-listp lst))
           (equal (driver (append lst '(nil nil))
                          nil)
                  (list (min* lst)
                        (max* lst)))))

; The proof above generates 1,715 subgoals, including 40 forced hypotheses, and
; takes 0.44 seconds.  We could speed it up by first proving:

; (defthm main-driver-lemma
;   (implies (and (integer-listp a)
;                 (full-statep st))
;            (equal (driver (append a '(nil nil)) st)
;                   (list (if (consp a)
;                             (min (min* a) (car st))
;                             (car st))
;                         (if (consp a)
;                             (max (max* a) (cdr st))
;                             (cdr st))))))

; which generates 104 subgoals and takes 0.03 seconds.  If we then prove main,
; above, the proof generates 128 subgoals including 10 forced hypotheses, in
; 0.05 seconds.