2.4 "doublecheck"
(include-book "doublecheck" :dir :teachpacks)
This teachpack defines automated random test generation as a complement to theorem proving. Each property declared via DoubleCheck is tested randomly by Dracula, and verified logically by ACL2. For a gentle introduction, see Doublecheck in Dracula: A Guide to ACL2 Theorem Proving in DrRacket.
2.4.1 Properties
syntax
(defproperty name property-option ... (bind ...) test theorem-option ...)
property-option = :repeat repeat-expr | :limit limit-expr bind = var bind-option ... bind-option = :where hypothesis-expr | :value random-expr | :limit limit-expr theorem-option = :rule-classes rule-classes | :instructions instructions | :hints hints | :otf-flg otf-flg | :doc doc-string
Each defproperty form defines a property called name which states that test must be true for all assignments to each var in the bindings. DoubleCheck attempts to run the test repeat times (default 50), but limits the attempts to generate satisfactory random data to limit times (default 2500).
Dracula generates values for each var by running random-expr, which defaults to (random-sexp). The var is generated until hypothesis-expr evaluates to true (non-nil), or limit attempts have been made (defaulting to the property’s limit, defaulting to 50 as noted above).
Here are some examples to illustrate the translation from theorems to DoubleCheck properties.
(include-book "doublecheck" :dir :teachpacks) ; This theorem has no hypotheses. (defthm acl2-count-cons-theorem (> (acl2-count (cons a b)) (+ (acl2-count a) (acl2-count b)))) ; The corresponding defproperty lists the free variables. (defproperty acl2-count-cons-property (a b) (> (acl2-count (cons a b)) (+ (acl2-count a) (acl2-count b))))
(include-book "doublecheck" :dir :teachpacks) ; This theorem needs some hypotheses. (defthm rationalp-/-theorem (implies (and (integerp x) (posp y)) (rationalp (/ x y)))) ; DoubleCheck expresses these as :where clauses. (defproperty rationalp-/-property (x :where (integerp x) y :where (posp y)) (rationalp (/ x y)))
(include-book "doublecheck" :dir :teachpacks) ; These hypotheses state a relationship between variables. (defthm member-equal-nth-theorem (implies (and (proper-consp lst) (natp idx) (< idx (len lst))) (member-equal (nth idx lst) lst)) :rule-classes (:rewrite :forward-chaining)) ; We can help DoubleCheck by picking random distributions ; that are likely to satisfy the hypotheses. (defproperty member-equal-nth-property (lst :where (proper-consp lst) :value (random-list-of (random-sexp)) idx :where (and (natp idx) (< idx (len lst))) :value (random-between 0 (1- (len lst)))) (member-equal (nth idx lst) lst) :rule-classes (:rewrite :forward-chaining))
2.4.2 Random Distributions
Randomness is an inherently imperative process. As such, it is not reflected in the logic of ACL2. The random distribution functions of DoubleCheck may only be used within :value clauses of defproperty, or in other random distributions.
procedure
(random-sexp) → t
procedure
(random-atom) → atom
procedure
procedure
(random-symbol) → symbolp
procedure
procedure
(random-string) → stringp
procedure
procedure
procedure
procedure
(random-natural) → natp
procedure
(random-between lo hi) → integerp
lo : integerp hi : integerp
procedure
(random-data-size) → natp
procedure
(random-element-of lst) → t
lst : proper-consp
syntax
(random-list-of expr maybe-size)
maybe-size =
| :size size
syntax
(random-sexp-of expr maybe-size)
maybe-size =
| :size size
syntax
(defrandom name (arg ...) body)
; Construct a distribution for random association lists: (defrandom random-alist (len) (random-list-of (cons (random-atom) (random-sexp)) :size len)) ; ...and now use it: (defproperty acons-preserves-alistp (alist :where (alistp alist) :value (random-alist (random-between 0 10)) key :where (atom key) :value (random-atom) datum) (alistp (acons key datum alist)))
syntax
(random-case clause ...)
clause = expr | expr :weight weight
Be careful of the branching factor; a distribution with a high probability of unbounded recursion is often unlikely to terminate. It is useful to give a depth parameter to limit recursion.
; Create a distribution for random expressions: (defrandom random-expression (max-depth) (random-case (random-symbol) ; variable (random-string) ; string literal (random-number) ; number literal ; one-argument function call ; (probability decreases with max-depth) (list (random-symbol) (random-expression (1- max-depth))) :weight (- 1 (/ 1 max-depth))))