5: Analysis
5.3 Unbounded Universal Quantifiers
5.3.2 Omitting the Generator Axiom
analysis 15 When an ordering is required, a simple relation suffices if duplicates
are not permitted; otherwise, a sequence modeled as a signature with a field mapping indexes to elements
sig Seq {
element: Index -> lone Element }
is better than a recursive list because it introduces less extraneous structure.
Universal quantifiers over such sequences can suffer from the same problems as lists. The Alloy library includes a sequence module with a predicate that forces the existence of all sequences up to a given length (namely, the scope of Index). This predicate must obviously be used with care to avoid scope explosion. There’s no predicate for forcing all pos-sible sequences to exist, because that would introduce a contradiction.
only with a set on the left and a relation on the right. These restrictions ensure that universally quantified constraints can look only “inside”
structures—into the sublists of a list, for example, or the subtrees of a tree—and thus can’t tell whether or not the generator axiom has been applied.
Example. The analysis constraint in the problematic example from the previous subsection
sig Set {
elements: set Element }
assert Closed {
all s0, s1: Set | some s2: Set |
s2.elements = s0.elements + s1.elements }
after negation becomes some s0, s1: Set |
all s2: Set |
not s2.elements = s0.elements + s1.elements
which is not in bounded-universal form, because the quantifica-tion of s2 isn’t bounded. Checking this assertion therefore might (and actually does) result in spurious counterexamples that would not be present if a generator axiom for Set were added.
Example. In contrast, suppose we formulate an assertion in the same model saying that union of sets is commutative:
sig Set {
elements: set Element }
assert UnionCommutative { all s0, s1, s2: Set |
s0.elements + s1.elements = s2.elements
implies s1.elements + s0.elements = s2.elements }
The analysis constraint is some s0, s1, s2: Set |
s0.elements + s1.elements = s2.elements
and not s1.elements + s0.elements = s2.elements
analysis 11 which is in bounded-universal form, since it contains no univer-sal quantifiers. Checking this assertion will not produce spurious counterexamples; it has its intended meaning even in the absence of the generator axiom.
Perhaps the most serious consequence of this issue is that assertions about preconditions, in which the precondition is asserted to be at least as weak as some property, cannot generally be checked.
The declarative style of description is very powerful, but it has a down-side: inadvertent overconstraint. A specification of an operation that is intended to constrain only the values of the poststate may unintention-ally constrain the prestate and arguments too, so that the operation is not “total” and cannot be applied in some contexts. To mitigate this risk, you might think you could assert that, for every prestate, there is at least one poststate that the operation’s constraint admits. Unfortunately, assertions in this form are not in the bounded universal category, and thus may produce spurious counterexamples.
Example. Take the add operation of an address book sig Name, Addr {}
sig Book {
addr: Name -> Addr }
pred add (b, b’: Book, n: Name, a: Addr) { b’.addr = b.addr + n -> a
}
and consider checking the following assertion:
assert AddTotal {
all b: Book, n: Name, a: Addr | some b’: Book | add (b, b’, n, a) }
check AddTotal
You might think this is valid, since every map can be extended with a new pair. In contrast, if instead the field addr were declared as
addr: Name -> lone Addr
so that at most one address can be associated with a name, you would no longer expect the operation to be total, because, if pre-sented with a new address for an existing name, it would not be
possible to extend the address book without violating the multi-plicity constraint.
Indeed, in this second case, the assertion would be invalid. Sur-prisingly, it’s invalid in the first case too, and the analyzer will gen-erate a counterexample such as
b = {(B0)}
n = {(N0}
a = {(A0)}
addr = {(B0, N0, A1)}
in which there is no Book to bind to b’ that will satisfy the operation constraint.
The problem is that, for the assertion to have its intended meaning, we need to ensure that all possible Book structures exist. Adding an axiom to this effect is not practical, because for a scope of 3 for Name and Addr, it would require a scope of 512 for Book! Omitting the axiom is not acceptable either, because the assertion, which reduces to
all b’: Book | not b’.addr = b.addr + n -> a is not a bounded-universal formula.
Discussion
Does the bounded-universal rule allow an infinite model to be analyzed by considering only finite cases?
Yes, that’s exactly what it allows. It’s not that surprising, however, since the properties that fall in the bounded-universal category only “look downward” into a finite part of an infinite instance.
Does that mean that a check in a finite scope applies automatically to the infinite case?
No. It means that, if there is a counterexample to an assertion, then there is a finite one in some scope. You can still miss a counterexample because the scope is too small. For the example just discussed, it means that checking the assertions in all finite scopes covers the case of an imaginary analysis in which the generator axiom is included and the scope is infinite. So, in short, it means that omitting the axiom and not considering the infinite scope case doesn’t make it any more likely that counterexamples will be missed.
analysis 1 Can’t a universal quantifier be converted into an existential one by add-ing negations?
No. The bounded-universal rule assumes that the formula is in a normal form in which all quantifiers are outermost, and are not negated. A re-search paper presents the rule in more detail and proves its soundness in a general setting of algebraic datatypes and first-order logic [44].