File: without-subsumption.lisp

package info (click to toggle)
acl2 8.3dfsg-2
  • links: PTS
  • area: main
  • in suites: bullseye
  • size: 309,408 kB
  • sloc: lisp: 3,311,842; javascript: 22,569; cpp: 9,029; ansic: 7,872; perl: 6,501; xml: 3,838; java: 3,738; makefile: 3,383; ruby: 2,633; sh: 2,489; ml: 763; python: 741; yacc: 721; awk: 260; csh: 186; php: 171; lex: 154; tcl: 49; asm: 23; haskell: 17
file content (396 lines) | stat: -rw-r--r-- 13,411 bytes parent folder | download | duplicates (4)
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
;;
;; Copyright (C) 2020, David Greve
;; License: A 3-clause BSD license.  See the LICENSE file distributed with ACL2.
;;
(in-package "ACL2")
(include-book "xdoc/top" :dir :system)

;; The issue that we address with this macro, and that we illustrate
;; below, is that in the course of a proof, ACL2 appears to lose
;; information that *would* (in this case) have allowed it to complete
;; the proof.

;; J Moore made the following observations about this behavior:

;; Here we illustrate the source of the "problem" using purely
;; propositional simplification.
;; 
;; ACL2 !>(clausify '(IF A
;;                     B
;;                     (IF C
;;                         B
;;                         'T))
;;                 nil nil 500)
;; (((NOT A) B)   ; A --> B
;;  ((NOT C) B))  ; C --> B
;; 
;; The issue is this: Do you really want to focus on (~A & C) --> B
;; when you already have to prove A --> B?
;; 
;; This process is called ``subsumption-replacement'' which comes from
;; resolution.  It says that if you're adding a new clause, (~A B), to
;; a set of clauses to prove, e.g., {(A ~C B)}, and a resolvent of the
;; new clause and one of the old ones subsumes one of the parents,
;; replace the subsumed clause by the resolvent.  So the resolvent of
;; (~A B) and (A ~C B) is (~C B), which subsumes the second parent so
;; we replace it with the resolvent.
;; 
;; This example is really basic (being purely propositional).  Doing
;; away with this process would add a lot of ``junk'' to clauses.  I
;; can imagine for some A it might be useful, e.g., as in Dave's
;; example.  But I can imagine for other A, e.g., humongous terms,
;; you'd just as soon drop it.

;; Fortunately, using only existing mechanisms in ACL2, we were able
;; to inhibit this process on the challenge problem.  Curiously, it
;; requires the combination of two features, a quick-and-dirty-srs
;; attachment and a :case-split-limitation hint, because subsumption
;; takes place in multiple places during simplification.

(defmacro without-subsumption(form &key (cases 'nil))
  `(encapsulate
       ()

     (local (defun without-subsumption-disable-quick-and-dirty (x y)
              (declare (ignore x y) (xargs :guard t)) nil))
     (local (defattach-system quick-and-dirty-srs
              without-subsumption-disable-quick-and-dirty))
     (set-case-split-limitations '(0 ,cases))
     
     ,form

     ))

;; Below we illustrate the use of the without-subsumption macro to
;; prove the lemma that served as the original motivating example in
;; its development.  Along the way we document failing proofs that
;; demonstrate the symptoms we originally observed .. symptoms that
;; suggest situations in which this macro may prove handy.

(local
 (encapsulate
     ()

   ;; This example was derived from the k-induction book originally written
   ;; by Matt Kaufmann.
   ;;
   ;; Copyright (C) 2020, Matt Kaufmann
   ;; Written by Matt Kaufmann
   ;; License: A 3-clause BSD license.  See the LICENSE file distributed with ACL2.

(encapsulate
  ((pk (n params) t)
   (pk-k () t)
   (pk-badguy (n params) t))

  (local (defun pk (n params)
           (declare (ignore n params))
           t))

  (local (defun pk-k ()
           1))

  (local (defun pk-badguy (n params)
           (declare (ignore n params))
           0))

  (defthm posp-pk-k
    (posp (pk-k))
    :rule-classes :type-prescription)

  (defthm natp-pk-badguy
    (natp (pk-badguy n params))
    :rule-classes :type-prescription)

  (defthm pk-badguy-range
    (implies (and (natp n)
                  (not (pk n params)))
             (and (< (pk-badguy n params)
                     n)
                  (>= (pk-badguy n params)
                      (- n (pk-k)))))
    :rule-classes (:linear :rewrite))

  (defthm pk-badguy-is-counterexample
    (implies (and (natp n)
                  (not (pk n params)))
             (not (pk (pk-badguy n params) params)))))

(defun pk-induction (n params)
  (if (or (zp n) (pk n params))
      t
    (pk-induction (pk-badguy n params) params)))

(defthm pk-0
  (pk 0 params)
  :hints (("Goal" :use ((:instance pk-badguy-range (n 0))))))

(defthm pk-holds
  (implies (natp n)
           (pk n params))
  :hints (("Goal" :induct (pk-induction n params))))

(encapsulate
  ((q (n x y) t))
  (local (defun q (n x y)
           (declare (ignore n x y))
           t))
  (defthm q-3-properties
    (and (q 0 x y)
         (q 1 x y)
         (q 2 x y)
         (implies (and (natp n)
                       (<= 3 n)
                       (q (- n 3) x y)
                       (q (- n 2) x y)
                       (q (- n 1) x y))
                  (q n x y)))))

(defun q-params (n params)
  (q n (nth 0 params) (nth 1 params)))

(defun k-params-badguy (n k params)
  (if (zp k) n
    (if (zp n) 0
      (let ((n (1- n)))
        (if (not (q-params n params)) n
          (k-params-badguy n (- k 1) params))))))

(defthm open-k-params
  (implies
   (syntaxp (quotep k))
   (equal (k-params-badguy n k params)
          (if (zp k) n
            (if (zp n) 0
              (let ((n (1- n)))
                (if (not (q-params n params)) n
                  (k-params-badguy n (- k 1) params))))))))


(local (include-book "std/testing/eval" :dir :system))

;; As formulated, the following proof fails.  I'm turning off (zp)
;; related rules to help show what is happening.

(local
 (must-fail
  (defthmd q-params-holds-1
    (implies (natp n)
             (q-params n params))
    :otf-flg t
    :hints (("Goal" :use ((:functional-instance pk-holds
                                                (pk q-params)
                                                (pk-k (lambda () 3))
                                                (pk-badguy (lambda (n params) (k-params-badguy n 3 params)))))
             :in-theory (disable open-k-params ZP-COMPOUND-RECOGNIZER zp zp-open))
            ;; Open up K-PARAMS-BADGUY (but disable zp-related rules)
            (and stable-under-simplificationp
                 '(:in-theory (e/d (open-k-params) (ZP-COMPOUND-RECOGNIZER zp zp-open))))
            ;; Re-enable zp rules
            (and stable-under-simplificationp
                 '(:in-theory (enable zp-open zp)))
            ))
  :with-output-off nil))

;; In Subgoal 4.2.4 (zp n) is true.  Here we see (zp n) in the
;; hypothesis.  This case proves without problems.

;; Subgoal 4.2.4
;; (IMPLIES (AND (INTEGERP N)
;;               (<= 0 N)
;;               (CONSP PARAMS)
;;               (NOT (Q N (CAR PARAMS) (NTH 1 PARAMS)))
;;               (ZP N))  ;; <<- This is the case we are considering
;;          (NOT (Q 0 (CAR PARAMS) (NTH 1 PARAMS)))).

;; Subgoal 4.2.2 is the case where (zp (1- n)) is true; but in
;; order to open up K-PARAMS-BADGUY to this point, (zp n)
;; must have been false.  However, (not (zp n)) is conspicuously
;; absent from the hypothesis .. and THIS IS WHY THE PROOF FAILS.

;; Subgoal 4.2.2
;; (IMPLIES (AND (INTEGERP N)
;;               (<= 0 N)
;;               (CONSP PARAMS)
;;               (NOT (Q N (CAR PARAMS) (NTH 1 PARAMS)))
;;               (Q (+ -1 N) (CAR PARAMS) (NTH 1 PARAMS))
;;               (ZP (+ -1 N)))  ;; <<- This is the case we are considering
;;                               ;; ?? Where is (not (zp n)) ??
;;          (NOT (Q 0 (CAR PARAMS) (NTH 1 PARAMS)))).


;; Beyond this point, lacking additional case splitting, the proof
;; just fails.

;; This time we try something different.  We run the proof in slow
;; motion .. note the :restrict hint applied to open-k-params .. this
;; ensures that we open up K-PARAMS-BADGUY one step at a time.

(local
 (encapsulate ()
   (local
    (defthm q-params-holds-2
      (implies (natp n)
               (q-params n params))
      :otf-flg t
      :hints (("Goal" :use ((:functional-instance pk-holds
                                                  (pk q-params)
                                                  (pk-k (lambda () 3))
                                                  (pk-badguy (lambda (n params) (k-params-badguy n 3 params)))))
               :in-theory (disable open-k-params))
              ;; Open K-PARAMS-BADGUY one step at a time (with zp-related rules disabled)
              (and stable-under-simplificationp
                   '(:in-theory (e/d (open-k-params) (ZP-COMPOUND-RECOGNIZER zp zp-open))
                                :restrict ((open-k-params ((k 3))))))
              (and stable-under-simplificationp
                   '(:in-theory (e/d (open-k-params) (ZP-COMPOUND-RECOGNIZER zp zp-open))
                                :restrict ((open-k-params ((k 2))))))
              (and stable-under-simplificationp
                   '(:in-theory (e/d (open-k-params) (ZP-COMPOUND-RECOGNIZER zp zp-open))
                                :restrict ((open-k-params ((k 1))))))
              ;; Re-enable zp rules
              (and stable-under-simplificationp
                   '(:in-theory (enable zp-open zp)))
              ))
    )))

;; Now Subgoal 4.2.2 is the case where (zp n) is true (Subgoal
;; 4.2.4 above) and this case proves without problems.

;; Subgoal 4.2.2
;; (IMPLIES (AND (INTEGERP N)
;;               (<= 0 N)
;;               (CONSP PARAMS)
;;               (NOT (Q N (CAR PARAMS) (NTH 1 PARAMS)))
;;               (ZP N)) ;; <<- This is the case we are considering
;;          (NOT (Q 0 (CAR PARAMS) (NTH 1 PARAMS)))).

;; This time we get a Subgoal 4.2.1 in which we explicitly know (not
;; (zp n)) ..

;; Subgoal 4.2.1
;; (IMPLIES (AND (INTEGERP N)
;;               (<= 0 N)
;;               (CONSP PARAMS)
;;               (NOT (Q N (CAR PARAMS) (NTH 1 PARAMS)))
;;               (NOT (ZP N)) ;; <<- Yay!
;;               (Q (+ -1 N)
;;                  (CAR PARAMS)
;;                  (NTH 1 PARAMS)))
;;          (NOT (Q (K-PARAMS-BADGUY (+ -1 N) 2 PARAMS)
;;                  (CAR PARAMS)
;;                  (NTH 1 PARAMS)))).

;; We continue expanding K-PARAMS-BADGUY and end up with Subgoal
;; 4.2.1.2 in which (ZP (+ -1 N)) is true (just like in Subgoal 4.2.2
;; from the failed proof above) but this time we also know (not
;; (zp n)).

;; Subgoal 4.2.1.2
;; (IMPLIES (AND (INTEGERP N)
;;               (<= 0 N)
;;               (CONSP PARAMS)
;;               (NOT (Q N (CAR PARAMS) (NTH 1 PARAMS)))
;;               (NOT (ZP N)) ;; <<- This was missing in the previous proof
;;               (Q (+ -1 N) (CAR PARAMS) (NTH 1 PARAMS))
;;               (ZP (+ -1 N))) ;; <<- This is the case we are considering
;;          (NOT (Q 0 (CAR PARAMS) (NTH 1 PARAMS)))).

;; With that information in hand we re-enable ZP and this case
;; proves without issue.

;; And, in fact, using the slow opening strategy, ACL2 is able to
;; complete the entire proof because it isn't losing information along
;; the way.

;; The lack of an essential hypothesis in a failing subgoal, a
;; hypothesis that "should have been generated" by the opening of
;; various definitions, is a possible indication that
;; without-subsumption might be useful.

;; Here the same proof that ultimately succeeds is shown failing
;; for lack of the without-subsumption macro.

(local
 (must-fail
 (defthm q-params-holds-3
   (implies (natp n)
            (q-params n params))
   :otf-flg t
   :hints (("Goal" :use
            ((:functional-instance
              pk-holds
              (pk q-params)
              (pk-k (lambda () 3))
              (pk-badguy (lambda (n params) (k-params-badguy n 3 params)))))
            )))
 :with-output-off nil)
 )

;; Finally, the successful proof using the without-subsumption macro.

(without-subsumption
 (defthmd q-params-holds-4
   (implies (natp n)
            (q-params n params))
   :otf-flg t
   :hints (("Goal" :use
            ((:functional-instance
              pk-holds
              (pk q-params)
              (pk-k (lambda () 3))
              (pk-badguy (lambda (n params) (k-params-badguy n 3 params)))))
            )))
 )
 
))

(defxdoc without-subsumption
  :short "Perform proofs without subsumption/replacement to preserve hypotheses that might otherwise be dropped."
  :parents (proof-automation)
  :long "
<p>
@('without-subsumption') is a simple macro that allows you to perform
proofs without subsumption/replacement to preserve hypotheses that
might otherwise be dropped during clausification.
</p>
<p>
In the course of a proof, ACL2 will sometimes drop hypotheses during
subsumption/replacement that would otherwise have allowed it to
complete the proof.
</p>
<p>
@('without-subsumption') uses both
@(tsee quick-and-dirty-subsumption-replacement-step) and
@(tsee case-split-limitations) to stop subsumption/replacement in various
stages of the ACL2 simplifier.  These hints can help preserve hypothesis
in a proof that the ACL2 simplifier might otherwise drop.  The
macro is defined as follows:
</p>
@({
 (defmacro without-subsumption(form &key (cases 'nil))
  `(encapsulate
       ()

     (local (defun without-subsumption-disable-quick-and-dirty (x y)
              (declare (ignore x y) (xargs :guard t)) nil))
     (local (defattach-system quick-and-dirty-srs
              without-subsumption-disable-quick-and-dirty))
     (set-case-split-limitations '(0 ,cases))
     
     ,form

     ))
})
<p>Usage:</p>
@({
 (include-book \"tools/without-subsumption\" :dir :system)
 (without-subsumption
   (defthm natp-implies-integerp
     (implies 
       (natp n)
       (integerp n)))
  )
 })

")