Filtering...

bitsets

books/std/bitsets/bitsets
other
(in-package "BITSETS")
other
(include-book "bits-between")
other
(include-book "bignum-extract")
other
(local (include-book "centaur/bitops/equal-by-logbitp" :dir :system))
other
(local (include-book "centaur/misc/witness-cp" :dir :system))
other
(local (include-book "centaur/bitops/ihs-extensions" :dir :system))
other
(local (include-book "ihs/quotient-remainder-lemmas" :dir :system))
other
(local (include-book "arithmetic-3/bind-free/top" :dir :system))
other
(local (in-theory (enable* definitions
      expensive-rules
      ash-1-removal)))
other
(defxdoc bitsets
  :parents (std/bitsets)
  :short "Bitsets library: uses bitmasks to represent finite sets of (small)
natural numbers."
  :long "<h3>Introduction</h3>

<p>In this library, sets of natural numbers are represented as natural numbers
by saying @('a') is a member of the set @('X') when @('(logbitp a X)').  For
instance, the set {1, 2, 4} would be represented as the number 22.  In binary,
this number is 10110, and you can see that bits 1, 2, and 4 are each
"true".</p>

<p>This representation enjoys certain efficiencies.  In particular, operations
like union, intersection, difference, and subset testing can be carried out in
a "word at a time" fashion with bit operations.</p>

<p>But bitsets are only appropriate for sets whose elements are reasonably
small numbers since the space needed to represent a bitset is determined by its
maximal element.  If your sets contain extremely small numbers---less than
28-60, depending on the Lisp---then they can be represented as fixnums and the
bitset operations will be remarkably efficient.</p>

<p>Beyond this, bignums are necessary.  Even though bignum operations generally
involve memory allocation and are much slower than fixnum operations, using
bignums to represent sets can still be very efficient.  For instance, on 64-bit
CCL, bignums are represented as a header/data pair where the data is an array
of 32-bit words; operations like @(see logand) can smash together the two data
arrays a word at a time.</p>

<p>Let's define the <i>density</i> of a set as its cardinality divided by its
maximal element plus one (to account for zero-indexing).  Under this
definition, the set {1, 2, 4} has a density of 60%, whereas {96, 97, 98} has a
density of 3%.</p>

<p>Without destructive updates, you probably can't do much better than bitsets
for dense sets.  But bitsets are not good at representing sparse sets.  For
instance, you would need 8 KB of memory just to represent the singleton set
{65536}.</p>

<p>The <see topic="@(url sbitsets)">sparse bitsets</see> library is an
alternative to bitsets that uses lists of @('(offset . data)') pairs instead of
ordinary natural numbers as the set representation.  This representation is
perhaps somewhat less efficient for dense sets, but is far more efficient for
sparse sets (e.g., {65536} can be represented with a single cons of fixnums
instead of an 8 KB bignum.) and still provides word-at-a-time operations in
many cases.</p>


<h3>Bitset Operations</h3>

<p>Convention:</p>

<ul>
<li>@('a, b, ...') denote set elements</li>
<li>@('X, Y, ...') denote sets.</li>
</ul>

<p>There is no such explicit @('bitsetp') recognizer; instead we just use
@('natp').  Similarly there is no separate bitset-fixing function because we
just use @('nfix').</p>

<p>See @(see reasoning) for some notes about how to prove theorems about bitset
functions.</p>

<h5>Bitset Constructors</h5>

<dl>

<dt>@('(bitset-singleton a)')</dt>
<dd>Constructs the set @('{a}')</dd>
<dd>Execution: @('(ash 1 a)')</dd>

<dt>@('(bitset-insert a X)')</dt>
<dd>Constructs the set @('X U {a}')</dd>
<dd>Execution: @('(logior (ash 1 a) x)')</dd>

<dt>@('(bitset-list a b ...)')</dt>
<dd>Constructs the set @('{a, b, ...}')</dd>
<dd>Execution: repeated @('bitset-insert')s</dd>

<dt>@('(bitset-list* a b ... X)')</dt>
<dd>Constructs the set @('X U {a, b, ...}')</dd>
<dd>Execution: repeated @('bitset-insert')s</dd>

<dt>@('(bitset-delete a X)')</dt>
<dd>Constructs the set @('X - {a}')</dd>
<dd>Execution: @('(logandc1 (ash 1 a) x)')</dd>

<dt>@('(bitset-union X Y ...)')</dt>
<dd>Constructs the set @('X U Y U ...')</dd>
<dd>Execution: @('(logior x (logior y ...))')</dd>

<dt>@('(bitset-intersect X Y ...)')</dt>
<dd>Constructs the set @('X \intersect Y \intersect ...')</dd>
<dd>Execution: @('(logand x (logand y ...))')</dd>

<dt>@('(bitset-difference X Y)')</dt>
<dd>Constructs the set @('X - Y')</dd>
<dd>Execution: @('(logandc1 y x)')</dd>
</dl>

<h5>Inspecting Bitsets</h5>

<dl>
<dt>@('(bitset-memberp a X)')</dt>
<dd>Determine whether @('a') is a member of the set @('X')</dd>
<dd>Execution: @('(logbitp a x)')</dd>

<dt>@('(bitset-intersectp X Y)')</dt>
<dd>Determines whether @('X') and @('Y') have any common members</dd>
<dd>Execution: @('(logtest x y)')</dd>

<dt>@('(bitset-subsetp X Y)')</dt>
<dd>Determines whether @('X') is a subset of @('Y')</dd>
<dd>Execution: @('(= 0 (logandc2 y x))')</dd>

<dt>@('(bitset-cardinality X)')</dt>
<dd>Determines the cardinality of @('X')</dd>
<dd>Execution: @('(logcount x)')</dd>

</dl>

<h5>Enumerating Bitset Members</h5>

<dl>
<dt>@('(bitset-members X)')</dt>
<dd>Constructs an ordinary ordered set with the elements of X.</dd>
<dd>Expensive: must cons together all of the set elements.</dd>
<dd>Important in @(see reasoning) about bitsets.</dd>
</dl>")
other
(local (set-default-parents bitsets))
other
(define bitset-members
  ((x natp))
  :short "@(call bitset-members) converts a bitset into an ordinary, ordered
set."
  :long "<p>Reasoning note: @('bitset-members') is fundamental to reasoning
about bitsets; see <i>Reasoning with Bitsets</i> in @(see bitsets).  Its
definition and the theorem @('in-of-bitset-members') should generally be left
disabled since they expose the underlying representation of bitsets.</p>

<p>Performance note: @('bitset-members') is <b>inherently expensive</b>: no
matter how it is implemented, it must create at least @('|X|') conses.  You may
sometimes be able to avoid this cost using functions like @(see
bitset-memberp), @(see bitset-intersectp), or @(see bitset-subsetp)
instead.</p>

<p>It is simple enough to convert a bitset into an ordered set: since the @(see
integer-length) of @('x') gives us an upper bound on its elements, we just need
to walk up to this bound and collect all @('i') such that @('(logbitp i
x)').</p>

<p>The definition below uses @(see bits-between) to do just this.  However,
note that when the @('bignum-extract-opt') book is loaded (which requires a
ttag), a more efficient implementation is used; see @(see ttag-bitset-members)
and @(see bignum-extract) for details.</p>"
  (mbe :logic (let ((x (nfix x)))
      (bits-between 0
        (integer-length x)
        x))
    :exec (bits-between 0
      (integer-length x)
      x))
  ///
  (defthm bitset-members-default
    (implies (not (natp a))
      (equal (bitset-members a) nil)))
  (defthm bitset-members-of-nfix
    (equal (bitset-members (nfix b))
      (bitset-members b)))
  (defthm true-listp-of-bitset-members
    (true-listp (bitset-members x))
    :rule-classes :type-prescription)
  (defthm nat-listp-of-bitset-members
    (nat-listp (bitset-members x)))
  (defthm no-duplicatesp-equal-of-bitset-members
    (no-duplicatesp-equal (bitset-members x)))
  (defthm setp-of-bitset-members
    (setp (bitset-members x)))
  (defthmd in-of-bitset-members
    (equal (in a (bitset-members x))
      (and (natp a)
        (logbitp a (nfix x)))))
  (encapsulate nil
    (local (in-theory (disable bitset-members)))
    (local (defthm l0
        (implies (posp x)
          (logbitp-mismatch x 0))))
    (local (defthm l1
        (implies (posp x)
          (logbitp (logbitp-mismatch x 0)
            x))
        :hints (("Goal" :in-theory (disable logbitp-mismatch-correct)
           :use ((:instance logbitp-mismatch-correct
              (a x)
              (b 0)))))))
    (local (defthm l2
        (implies (posp x)
          (bitset-members x))
        :hints (("Goal" :in-theory (enable natp)
           :use ((:instance in-of-bitset-members
              (a (logbitp-mismatch x 0))))))))
    (local (defthm l3
        (implies (not (posp x))
          (not (bitset-members x)))
        :hints (("Goal" :cases ((equal x 0))))))
    (defthm bitset-members-under-iff
      (iff (bitset-members x)
        (posp x)))))
other
(defsection bits-between-of-bignum-extract
  (local (in-theory (enable bignum-extract)))
  (local (defthm l0
      (implies (and (natp slice) (integerp x))
        (equal (bits-between 0
            32
            (ash x (* -32 slice)))
          (add-to-each (* -32 slice)
            (bits-between (* 32 slice)
              (* 32 (+ 1 slice))
              x))))
      :hints (("Goal" :use ((:instance bits-between-of-right-shift
            (n 0)
            (m 32)
            (k (* -32 slice))))
         :in-theory (disable bits-between-of-right-shift)))))
  (defthm bits-between-of-bignum-extract
    (implies (and (posp slice) (integerp x))
      (equal (bits-between 0
          32
          (bignum-extract x slice))
        (add-to-each (* -32 slice)
          (bits-between (* 32 slice)
            (* 32 (+ 1 slice))
            x))))
    :hints (("Goal" :in-theory (e/d (loghead)
         (bits-between-of-increment-right-index right-shift-to-logtail))))))
other
(define ttag-bitset-members
  ((x natp))
  :parents (bitset-members)
  :short "Notes about the optimization of @(see bitset-members)."
  :long "<p>The basic version of @('bitset-members') calls @(see bits-between),
which repeatedly indexes into the bitset using @(see logbitp).</p>

<p>We found this wasn't very efficient in 64-bit CCL, and have developed an
alternate implementation that uses:</p>

<ol>

<li>@(see bits-0-31), a partially unrolled version of @(see bits-between) that
is specialized for 32-bit numbers, and</li>

<li>@(see bignum-extract), a function for extracting a 32-bit chunk from a
bignum in a particularly efficient way on 64-bit CCL.</li>

</ol>

<p>Basic performance testing suggests that @('ttag-bitset-members') is almost
5x faster than a regular version.  Here's the testing code I used:</p>

@({
 (include-book "bitsets")
 (include-book "centaur/aig/random-sim" :dir :system)
 (include-book "centaur/misc/memory-mgmt" :dir :system)
 (include-book "std/util/defconsts" :dir :system)

  (acl2::set-max-mem ;; newline to fool dependency scanner
    (* 30 (expt 2 30)))

  (acl2::defconsts (*random-data* state)
    (acl2::random-list 100000 (ash 1 5000) state))

 (defund bitset-members-list (x)
   (if (atom x)
       nil
     (cons (bitset-members (car x))
           (bitset-members-list (cdr x)))))

 (defund ttag-bitset-members-list (x)
   (if (atom x)
       nil
     (cons (ttag-bitset-members (car x))
           (ttag-bitset-members-list (cdr x)))))

 (progn$
  (gc$)
  (time$ (bitset-members-list *random-data*)
         :msg "Unoptimized Original: ~st sec, ~sa bytes~%")
  (gc$)
  (time$ (ttag-bitset-members-list *random-data*)
         :msg "Unoptimized TTAG: ~st sec, ~sa bytes~%")
  nil)

 (include-book "bitsets-opt")

 (progn$
  (gc$)
  (time$ (bitset-members-list *random-data*)
         :msg "Optimized Original: ~st sec, ~sa bytes~%")
  (gc$)
  (time$ (ttag-bitset-members-list *random-data*)
         :msg "Optimized TTAG: ~st sec, ~sa bytes~%")
  nil)
})

<p>And the results (on compute-1-3):</p>

@({
     Unoptimized Original: 12.80 sec, 4,001,843,488 bytes
     Unoptimized TTAG: 8.27 sec, 9,118,511,648 bytes
     Optimized Original: 4.05 sec, 4,001,843,488 bytes
     Optimized TTAG: 4.01 sec, 4,001,843,488 bytes
})"
  :prepwork ((local (in-theory (disable associativity-of-append))) (local (defthm rassoc-append
        (equal (append x (append y z))
          (append (append x y) z))))
    (local (defthm ash-5-to-times
        (implies (natp x)
          (equal (ash x 5) (* x 32)))
        :hints (("Goal" :in-theory (enable ash floor)))))
    (local (defthm ash-minus-5-to-floor
        (implies (natp x)
          (equal (ash x -5) (floor x 32)))
        :hints (("Goal" :in-theory (enable ash floor)))))
    (defund ttag-bitset-members-aux
      (slice x acc)
      (declare (xargs :guard (and (natp slice) (integerp x))))
      (if (zp slice)
        (bits-0-31 (ash slice 5)
          (bignum-extract x slice)
          acc)
        (ttag-bitset-members-aux (the (integer 0 *) (- (lnfix slice) 1))
          x
          (bits-0-31 (the (integer 0 *) (ash slice 5))
            (bignum-extract x slice)
            acc))))
    (local (in-theory (enable loghead)))
    (defthm ttag-bitset-members-aux-redef
      (implies (and (natp slice) (integerp x))
        (equal (ttag-bitset-members-aux slice
            x
            acc)
          (append (bits-between 0
              (* (+ 1 slice) 32)
              x)
            acc)))
      :hints (("Goal" :in-theory (e/d (ttag-bitset-members-aux bignum-extract)
           (right-shift-to-logtail))))))
  :guard-hints (("Goal" :in-theory (e/d (bitset-members))))
  (mbe :logic (bitset-members x)
    :exec (ttag-bitset-members-aux (the (integer 0 *) (ash (integer-length x) -5))
      x
      nil)))
other
(define bitset-memberp
  ((a natp :type unsigned-byte) (x natp :type unsigned-byte))
  :returns (bool booleanp
    :rule-classes :type-prescription)
  :short "@(call bitset-memberp) tests whether @('a') is a member of the set
@('X')."
  :long "<p>This is reasonably efficient: it executes as @(see logbitp) and
does not need to use @(see bitset-members).</p>

<p>We prefer to reason about @('(set::in a (bitset-members X))').  We could
have used this as the @(':logic') definition, but the @(see logbitp)-based
definition should be better for symbolic simulation with @(see gl::gl).</p>"
  :inline t
  :split-types t
  (logbitp a
    (the unsigned-byte (lnfix x)))
  ///
  (defthm bitset-memberp-removal
    (equal (bitset-memberp a x)
      (in (nfix a)
        (bitset-members x)))
    :hints (("Goal" :in-theory (enable bitset-memberp
         in-of-bitset-members)))))
other
(define bitset-singleton
  ((a natp :type unsigned-byte))
  :returns (bitset natp
    :rule-classes :type-prescription)
  :short "@(call bitset-singleton) constructs the singleton set @('{a}')."
  :long "<p>This is perhaps slightly more efficient than the equivalent,
 @('(bitset-insert A 0)').</p>"
  :inline t
  :split-types t
  (the unsigned-byte
    (ash 1 (the unsigned-byte (lnfix a))))
  ///
  (defthm bitset-singleton-when-not-natp
    (implies (not (natp a))
      (equal (bitset-singleton a)
        (bitset-singleton 0))))
  (defthm bitset-singleton-of-nfix
    (equal (bitset-singleton (nfix a))
      (bitset-singleton a)))
  (encapsulate nil
    (local (defthm l0
        (equal (in a
            (bitset-members (bitset-singleton b)))
          (equal a (nfix b)))
        :hints (("Goal" :in-theory (e/d (in-of-bitset-members)
             ((expt) |(expt x 0)|))) (and stable-under-simplificationp
            '(:in-theory (enable expt))))))
    (defthm bitset-members-of-bitset-singleton
      (equal (bitset-members (bitset-singleton a))
        (insert (nfix a) nil))
      :hints (("Goal" :in-theory (disable bitset-singleton))))))
other
(define bitset-insert
  ((a natp :type unsigned-byte) (x natp :type unsigned-byte))
  :returns (bitset natp
    :rule-classes :type-prescription)
  :parents (bitsets)
  :short "@(call bitset-insert) constructs the set @('X U {a}')."
  :long "<p>This looks pretty efficient, but keep in mind that it still has to
construct a new set and hence is linear in the size of the set.  You should
probably avoid calling this in a loop if possible; instead consider functions
like @(see bitset-union).</p>"
  :inline t
  :split-types t
  (the unsigned-byte
    (logior (the unsigned-byte (ash 1 (lnfix a)))
      (the unsigned-byte (lnfix x))))
  ///
  (defthm bitset-insert-when-not-natp-left
    (implies (not (natp a))
      (equal (bitset-insert a x)
        (bitset-insert 0 x))))
  (defthm bitset-insert-when-not-natp-right
    (implies (not (natp x))
      (equal (bitset-insert a x)
        (bitset-singleton a)))
    :hints (("Goal" :in-theory (enable bitset-singleton))))
  (defthm bitset-insert-of-nfix-left
    (equal (bitset-insert (nfix a)
        x)
      (bitset-insert a x)))
  (defthm bitset-insert-of-nfix-right
    (equal (bitset-insert a
        (nfix x))
      (bitset-insert a x)))
  (encapsulate nil
    (local (defthm l0
        (equal (in a
            (bitset-members (bitset-insert b x)))
          (or (equal a (nfix b))
            (in a (bitset-members x))))
        :hints (("Goal" :in-theory (e/d (in-of-bitset-members)
             (|(expt x 0)| |(expt x (if a b c))|))))))
    (defthmd bitset-members-of-bitset-insert
      (equal (bitset-members (bitset-insert a x))
        (insert (nfix a)
          (bitset-members x)))
      :hints (("Goal" :in-theory (disable bitset-insert))))))
other
(define bitset-delete
  ((a natp :type unsigned-byte) (x natp :type unsigned-byte))
  :returns (bitset natp
    :rule-classes :type-prescription)
  :parents (bitsets)
  :short "@(call bitset-delete) constructs the set @('X - {a}')."
  :long "<p>This looks pretty efficient, but keep in mind that it still has to
construct a new set and hence is linear in the size of the set.  You should
probably avoid calling this in a loop if possible; instead, consider functions
like @(see bitset-intersect) and @(see bitset-difference).</p>"
  :inline t
  :split-types t
  (the unsigned-byte
    (logandc1 (the unsigned-byte
        (ash 1 (the unsigned-byte (lnfix a))))
      (the unsigned-byte (lnfix x))))
  ///
  (defthm bitset-delete-when-not-natp-left
    (implies (not (natp a))
      (equal (bitset-delete a x)
        (bitset-delete 0 x))))
  (defthm bitset-delete-when-not-natp-right
    (implies (not (natp x))
      (equal (bitset-delete a x) 0)))
  (defthm bitset-delete-of-nfix-left
    (equal (bitset-delete (nfix a)
        x)
      (bitset-delete a x)))
  (defthm bitset-delete-of-nfix-right
    (equal (bitset-delete a
        (nfix x))
      (bitset-delete a x)))
  (encapsulate nil
    (local (defthm l0
        (equal (in a
            (bitset-members (bitset-delete b x)))
          (and (in a (bitset-members x))
            (not (equal a (nfix b)))))
        :hints (("Goal" :in-theory (e/d (in-of-bitset-members)
             (|(expt x 0)| |(expt x (if a b c))|))))))
    (defthm bitset-members-of-bitset-delete
      (equal (bitset-members (bitset-delete a x))
        (delete (nfix a)
          (bitset-members x)))
      :hints (("Goal" :in-theory (disable bitset-delete))))))
other
(defsection bitset-union
  :parents (bitsets)
  :short "@('(bitset-union X Y ...)') constructs the set @('X U Y U ...')."
  (define bitset-binary-union
    ((x natp :type unsigned-byte) (y natp :type unsigned-byte))
    :returns (bitset natp
      :rule-classes :type-prescription)
    :parents (bitset-union)
    :inline t
    :split-types t
    (the unsigned-byte
      (logior (the unsigned-byte (lnfix x))
        (the unsigned-byte (lnfix y)))))
  (defmacro bitset-union
    (&rest args)
    (cond ((atom args) 0)
      ((atom (cdr args)) (car args))
      (t (xxxjoin 'bitset-binary-union
          args))))
  (add-macro-alias bitset-union
    bitset-binary-union$inline)
  (add-macro-fn bitset-union
    bitset-binary-union$inline
    t)
  (local (in-theory (enable bitset-union)))
  (defthm bitset-union-when-not-natp-left
    (implies (not (natp x))
      (equal (bitset-union x y)
        (nfix y))))
  (defthm bitset-union-when-not-natp-right
    (implies (not (natp y))
      (equal (bitset-union x y)
        (nfix x))))
  (defthm bitset-union-of-nfix-left
    (equal (bitset-union (nfix x)
        y)
      (bitset-union x y)))
  (defthm bitset-union-of-nfix-right
    (equal (bitset-union x
        (nfix y))
      (bitset-union x y)))
  (encapsulate nil
    (local (defthm l0
        (equal (in a
            (bitset-members (bitset-union x y)))
          (or (in a (bitset-members x))
            (in a (bitset-members y))))
        :hints (("Goal" :in-theory (enable in-of-bitset-members)))))
    (defthm bitset-members-of-bitset-union
      (equal (bitset-members (bitset-union x y))
        (union (bitset-members x)
          (bitset-members y)))
      :hints (("Goal" :in-theory (disable bitset-union))))))
other
(defsection bitset-intersect
  :parents (bitsets)
  :short "@('(bitset-intersect X Y ...)') constructs the set @('X \intersect Y
  \intersect ...')."
  :long "<p>Note: if you only want to know whether or not two sets intersect,
@(see bitset-intersectp) is probably more efficient.</p>"
  (define bitset-binary-intersect
    ((x natp :type unsigned-byte) (y natp :type unsigned-byte))
    :returns (bitset natp
      :rule-classes :type-prescription)
    :parents (bitset-intersect)
    :inline t
    :split-types t
    (the unsigned-byte
      (logand (the unsigned-byte (lnfix x))
        (the unsigned-byte (lnfix y)))))
  (defmacro bitset-intersect
    (&rest args)
    (cond ((atom args) 0)
      ((atom (cdr args)) (car args))
      (t (xxxjoin 'bitset-binary-intersect
          args))))
  (add-macro-alias bitset-intersect
    bitset-binary-intersect$inline)
  (add-macro-fn bitset-intersect
    bitset-binary-intersect$inline
    t)
  (local (in-theory (enable bitset-intersect)))
  (defthm bitset-intersect-when-not-natp-left
    (implies (not (natp x))
      (equal (bitset-intersect x y) 0)))
  (defthm bitset-intersect-when-not-natp-right
    (implies (not (natp y))
      (equal (bitset-intersect x y) 0)))
  (defthm bitset-intersect-of-nfix-left
    (equal (bitset-intersect (nfix x)
        y)
      (bitset-intersect x y)))
  (defthm bitset-intersect-of-nfix-right
    (equal (bitset-intersect x
        (nfix y))
      (bitset-intersect x y)))
  (encapsulate nil
    (local (defthm l0
        (equal (in a
            (bitset-members (bitset-intersect x y)))
          (and (in a (bitset-members x))
            (in a (bitset-members y))))
        :hints (("Goal" :in-theory (enable in-of-bitset-members)))))
    (defthm bitset-members-of-bitset-intersect
      (equal (bitset-members (bitset-intersect x y))
        (intersect (bitset-members x)
          (bitset-members y)))
      :hints (("Goal" :in-theory (disable bitset-intersect))))))
other
(define bitset-difference
  ((x natp :type unsigned-byte) (y natp :type unsigned-byte))
  :returns (bitset natp
    :rule-classes :type-prescription)
  :parents (bitsets)
  :short "@(call bitset-difference) constructs the set @('X - Y')."
  :inline t
  :split-types t
  (the unsigned-byte
    (logandc1 (the unsigned-byte (lnfix y))
      (the unsigned-byte (lnfix x))))
  ///
  (defthm bitset-difference-when-not-natp-left
    (implies (not (natp x))
      (equal (bitset-difference x y) 0)))
  (defthm bitset-difference-when-not-natp-right
    (implies (not (natp y))
      (equal (bitset-difference x y)
        (nfix x))))
  (defthm bitset-difference-of-nfix-left
    (equal (bitset-difference (nfix x)
        y)
      (bitset-difference x y)))
  (defthm bitset-difference-of-nfix-right
    (equal (bitset-difference x
        (nfix y))
      (bitset-difference x y)))
  (encapsulate nil
    (local (defthm l0
        (iff (in a
            (bitset-members (bitset-difference x y)))
          (and (in a (bitset-members x))
            (not (in a (bitset-members y)))))
        :hints (("Goal" :in-theory (enable in-of-bitset-members)))))
    (defthm bitset-members-of-bitset-difference
      (equal (bitset-members (bitset-difference x y))
        (difference (bitset-members x)
          (bitset-members y)))
      :hints (("Goal" :in-theory (disable bitset-difference))))))
other
(encapsulate nil
  (local (defthm l0
      (implies (and (equal (bitset-members x)
            (bitset-members y))
          (natp x)
          (natp y)
          (natp a))
        (equal (equal (logbitp a x)
            (logbitp a y))
          t))
      :hints (("Goal" :in-theory (disable in-of-bitset-members)
         :use ((:instance in-of-bitset-members
            (x x)) (:instance in-of-bitset-members
             (x y)))))))
  (defthmd equal-bitsets-by-membership
    (implies (and (natp x) (natp y))
      (equal (equal x y)
        (equal (bitset-members x)
          (bitset-members y))))
    :hints (("Goal" :in-theory (disable double-containment)
       :use ((:functional-instance equal-by-logbitp
          (logbitp-hyp (lambda nil
              (and (natp x)
                (natp y)
                (equal (bitset-members x)
                  (bitset-members y)))))
          (logbitp-lhs (lambda nil x))
          (logbitp-rhs (lambda nil y))))))))
other
(local (progn (table bitset-fns
      nil
      '((bitset-singleton$inline . t) (bitset-insert$inline . t)
        (bitset-delete$inline . t)
        (bitset-binary-intersect$inline . t)
        (bitset-binary-union$inline . t)
        (bitset-difference$inline . t))
      :clear)
    (defthm bitset-equal-witnessing-lemma
      (implies (not (equal a b))
        (let ((k (logbitp-mismatch (nfix a)
               (nfix b))))
          (implies (and (natp a) (natp b))
            (not (iff (in k (bitset-members a))
                (in k (bitset-members b)))))))
      :hints (("goal" :use ((:instance equal-bitsets-by-membership
            (x (nfix a))
            (y (nfix b))) (:instance logbitp-mismatch-correct
             (a (nfix a))
             (b (nfix b))))
         :in-theory (e/d (in-of-bitset-members natp)
           (double-containment logbitp-mismatch-correct))))
      :rule-classes nil)
    (defwitness bitset-equal-witnessing
      :predicate (not (equal a b))
      :expr (let ((k (logbitp-mismatch (nfix a)
             (nfix b))))
        (implies (and (natp a) (natp b))
          (not (iff (in k (bitset-members a))
              (in k (bitset-members b))))))
      :restriction (let ((bitset-fns (table-alist 'bitset-fns world)))
        (or (and (consp a)
            (assoc (car a) bitset-fns))
          (and (consp b)
            (assoc (car b) bitset-fns))))
      :generalize (((logbitp-mismatch (nfix a)
          (nfix b)) . badbit))
      :hints ('(:use bitset-equal-witnessing-lemma :in-theory nil)))
    (definstantiate bitset-equal-instancing
      :predicate (equal a b)
      :expr (iff (in k (bitset-members a))
        (in k (bitset-members b)))
      :vars (k)
      :restriction (let ((bitset-fns (table-alist 'bitset-fns world)))
        (or (and (consp a)
            (assoc (car a) bitset-fns))
          (and (consp b)
            (assoc (car b) bitset-fns))))
      :hints ('(:in-theory nil)))
    (defexample bitset-equal-example
      :pattern (in k (bitset-members x))
      :templates (k)
      :instance-rulename bitset-equal-instancing)
    (defmacro bitset-witnessing
      nil
      `(witness :ruleset (bitset-equal-witnessing bitset-equal-instancing
          bitset-equal-example)))))
other
(local (encapsulate nil
    (local (defthm test1
        (implies (equal (bitset-union (bitset-union a d)
              b)
            (bitset-intersect b c))
          (equal (bitset-union b
              (bitset-union a d))
            (nfix b)))
        :hints ((bitset-witnessing))))
    (local (defthm test2
        (equal (bitset-union a
            (bitset-intersect b c))
          (bitset-intersect (bitset-union a b)
            (bitset-union a c)))
        :hints ((bitset-witnessing))))))
other
(define bitset-subsetp
  ((x natp :type unsigned-byte) (y natp :type unsigned-byte))
  :parents (bitsets)
  :returns (subsetp booleanp :rule-classes :type-prescription)
  :short "@(call bitset-subsetp) efficiently determines if @('X') is a subset
of @('Y')."
  :inline t
  :split-types t
  (eql 0
    (the unsigned-byte
      (bitset-difference x y)))
  ///
  (defthm bitset-subsetp-when-not-natp-left
    (implies (not (natp x))
      (equal (bitset-subsetp x y) t)))
  (defthm bitset-subsetp-when-not-natp-right
    (implies (not (natp y))
      (equal (bitset-subsetp x y)
        (zp x))))
  (defthm bitset-subsetp-of-nfix-left
    (equal (bitset-subsetp (nfix x)
        y)
      (bitset-subsetp x y)))
  (defthm bitset-subsetp-of-nfix-right
    (equal (bitset-subsetp x
        (nfix y))
      (bitset-subsetp x y)))
  (encapsulate nil
    (local (defthm l0
        (implies (and (bitset-subsetp x y)
            (in a (bitset-members x)))
          (in a (bitset-members y)))
        :hints ((bitset-witnessing))))
    (local (defthm l1
        (implies (bitset-subsetp x y)
          (subset (bitset-members x)
            (bitset-members y)))
        :hints (("Goal" :in-theory (disable bitset-subsetp)))))
    (local (defthm l2
        (implies (subset (bitset-members x)
            (bitset-members y))
          (bitset-subsetp x y))
        :hints ((bitset-witnessing))))
    (defthm bitset-subsetp-removal
      (equal (bitset-subsetp x y)
        (subset (bitset-members x)
          (bitset-members y)))
      :hints (("Goal" :in-theory (disable bitset-subsetp)
         :cases ((bitset-subsetp x y)))))))
other
(define bitset-intersectp
  ((x natp :type unsigned-byte) (y natp :type unsigned-byte))
  :returns (intersectp booleanp
    :rule-classes :type-prescription)
  :parents (bitsets)
  :short "@(call bitset-intersectp) efficiently determines if @('X') and @('Y')
have any common members."
  :long "<p>This is efficient because, unlike @(see bitset-intersect), we don't
actually construct the intersection.</p>"
  :inline t
  :split-types t
  (logtest (the unsigned-byte (lnfix x))
    (the unsigned-byte (lnfix y)))
  ///
  (defthm bitset-intersectp-when-not-natp-left
    (implies (not (natp x))
      (equal (bitset-intersectp x y)
        nil)))
  (defthm bitset-intersectp-when-not-natp-right
    (implies (not (natp y))
      (equal (bitset-intersectp x y)
        nil)))
  (defthm bitset-intersectp-of-nfix-left
    (equal (bitset-intersectp (nfix x)
        y)
      (bitset-intersectp x y)))
  (defthm bitset-intersectp-of-nfix-right
    (equal (bitset-intersectp x
        (nfix y))
      (bitset-intersectp x y)))
  (encapsulate nil
    (local (defthm l0
        (implies (bitset-intersectp x y)
          (not (equal 0 (bitset-intersect x y))))
        :hints (("Goal" :in-theory (enable bitset-intersectp
             bitset-intersect)))))
    (defthm bitset-intersectp-removal
      (implies (bitset-intersectp x y)
        (intersect (bitset-members x)
          (bitset-members y)))
      :hints (("Goal" :in-theory (disable bitset-intersectp
           bitset-members-of-bitset-intersect
           double-containment)
         :use ((:instance bitset-members-of-bitset-intersect) (:instance l0)))))))
other
(define bitset-cardinality
  ((x natp :type unsigned-byte))
  :parents (bitsets)
  :short "@(call bitset-cardinality) determines the cardinality of the set
@('X')."
  :long "<p>We prefer to reason about @('(set::cardinality (bitset-members
X))').  We could have used this as the @(':logic') definition, but the @(see
logcount)-based definition should be better for symbolic simulation with @(see
gl::gl).</p>"
  :inline t
  :split-types t
  (the unsigned-byte
    (logcount (the unsigned-byte (lnfix x))))
  ///
  (local (defund lsb-bits
      (n x)
      (if (zp x)
        nil
        (if (eql (logcar x) 1)
          (cons (nfix n)
            (lsb-bits (+ 1 (nfix n))
              (logcdr x)))
          (lsb-bits (+ 1 (nfix n))
            (logcdr x))))))
  (local (defthmd logcount-is-len-lsb-bits
      (implies (natp x)
        (equal (logcount x)
          (len (lsb-bits n x))))
      :hints (("Goal" :in-theory (enable lsb-bits logcount**)))))
  (local (defthm member-equal-lsb-bits
      (iff (member-equal a
          (lsb-bits n x))
        (and (natp a)
          (<= (nfix n) a)
          (logbitp (- a (nfix n))
            (nfix x))))
      :hints (("Goal" :do-not '(generalize fertilize)
         :in-theory (enable lsb-bits logbitp**)
         :induct (lsb-bits n x)))))
  (local (defthm l0
      (implies (and (force (natp a))
          (force (natp b)))
        (equal (<< a b)
          (< a b)))
      :hints (("Goal" :in-theory (enable <<
           lexorder
           alphorder)))))
  (local (defthm l1
      (equal (natp (car (lsb-bits n x)))
        (if (lsb-bits n x)
          t
          nil))
      :hints (("Goal" :in-theory (enable lsb-bits)))))
  (local (defthm l2
      (implies (lsb-bits n x)
        (<= (nfix n)
          (car (lsb-bits n x))))
      :rule-classes ((:rewrite) (:linear))
      :hints (("Goal" :in-theory (enable lsb-bits)))))
  (local (defthm setp-of-lsb-bits
      (setp (lsb-bits n x))
      :hints (("Goal" :in-theory (enable* lsb-bits
           (:ruleset primitive-rules))))))
  (local (in-theory (disable l0 l1 l2)))
  (local (defthmd sets-membership-hack
      (implies (and (setp x)
          (syntaxp (not (quotep x))))
        (iff (member-equal a x)
          (in a x)))
      :hints (("Goal" :in-theory (enable* (:ruleset primitive-rules))))))
  (local (defthm in-lsb-bits
      (equal (in a
          (lsb-bits n x))
        (and (natp a)
          (<= (nfix n) a)
          (logbitp (- a (nfix n))
            (nfix x))))
      :hints (("Goal" :use ((:instance sets-membership-hack
            (a a)
            (x (lsb-bits n x))))))))
  (local (defthm lsb-bits-is-bitset-members
      (equal (lsb-bits 0 x)
        (bitset-members x))
      :hints (("goal" :in-theory (e/d (in-of-bitset-members)
           (lsb-bits))))))
  (local (defthm logcount-is-len-of-bitset-members
      (implies (natp x)
        (equal (logcount x)
          (len (bitset-members x))))
      :hints (("Goal" :in-theory (enable bitset-members)
         :use ((:instance logcount-is-len-lsb-bits (n 0)))))))
  (local (defthm cardinality-is-len
      (implies (setp x)
        (equal (cardinality x)
          (len x)))
      :hints (("Goal" :in-theory (enable* (:ruleset primitive-rules))))))
  (local (defthm logcount-is-cardinality-of-bitset-members
      (implies (natp x)
        (equal (logcount x)
          (cardinality (bitset-members x))))))
  (defthm bitset-cardinality-removal
    (equal (bitset-cardinality x)
      (cardinality (bitset-members x)))
    :hints (("Goal" :in-theory (enable bitset-cardinality)))))
other
(defsection bitset-list*
  :parents (bitsets)
  :short "Macro to repeatedly insert into a bitset."
  :long "<p>Example: @('(bitset-list* a b c X)') expands to:</p>

@({
 (bitset-insert a
  (bitset-insert b
   (bitset-insert c X)))
})

<p>This is much like the @(see list*) macro for constructing lists.  See also
@(see bitset-list) for a @(see list)-like version.</p>

<p><b>NOTE:</b> This is not very efficient.  Inserting N elements into a set
will require N calls of @(see bitset-insert).</p>"
  (defun bitset-list*-macro
    (lst)
    (declare (xargs :guard (and (true-listp lst)
          (consp lst))))
    (if (atom (cdr lst))
      (car lst)
      (list 'bitset-insert
        (car lst)
        (bitset-list*-macro (cdr lst)))))
  (defmacro bitset-list*
    (&rest args)
    (bitset-list*-macro args)))
other
(defsection bitset-list
  :parents (bitsets)
  :short "Macro to construct a bitset from particular members."
  :long "<p>Example: @('(bitset-list 1 2 4)') constructs the set @('{1, 2,
  4}').</p>

<p>In general, @('(bitset-list x1 x2 ... xn)') expands to</p>

@({
 (bitset-insert x1
  (bitset-insert x2
   (bitset-insert ...
     (bitset-insert xn 0))))
})

<p>This is much like the @(see list) macro for constructing lists.  See also
@(see bitset-list*) for a @(see list*)-like version.</p>

<p><b>NOTE:</b> This is not very efficient.  Constructing a set with N elements
will require N calls of @(see bitset-insert).</p>"
  (defun bitset-list-macro
    (lst)
    (declare (xargs :guard t))
    (if (atom lst)
      0
      (list 'bitset-insert
        (car lst)
        (bitset-list-macro (cdr lst)))))
  (defmacro bitset-list
    (&rest args)
    (bitset-list-macro args)))