I've recently observed several behaviours in Z3 with respect to triggering, which I don't understand. Unfortunately, the examples come from large Boogie files, so I thought I'd describe them abstractly for now, just to see if there are obvious answers. However, if the concrete files would be better, I can attach them.
There are basically three issues, although the third may well be a special case of the second. As far as my understanding goes, none of the behaviours are expected, but maybe I'm missing something. Any help would be greatly appreciated!
Firstly: Trivial equalities in my program seem to get ignored as far as triggering is concerned. For example, if t1
is a term which should match the pattern for a quantified axiom, if I add a line in my Boogie program of the form
assert t1 == t1;
then t1
does not seem to get used as a trigger for quantified axioms. I added the line explicitly in order to provide t1
as a trigger to the prover, which I often do/did in Boogie programs.
If instead I introduce an extra function, say
function f(x : same_type_as_t1) : bool
{ true }
and now instead add a line
assert f(t1);
to my program, then t1
does appear to be used as a trigger by Z3. I have checked the Z3 translation of the former program, and the (trivial) equality on t1
did survive the Boogie translation (i.e., it isn't Boogie trying to do something clever).
Secondly: Secondary patterns don't seem to be working properly for me. For example, I have a program in which one axiom has the form
axiom (forall ... :: {t1,t2} {t3,t4,t5} ..... );
and a situation in which t3, t4
and t5
have all occurred. The program fails to verify, apparently because the axiom is not instantiated. However, if I rewrite the axiom as
axiom (forall ... :: {t3,t4,t5} {t1,t2} ..... );
then the program verifies. In both cases, the time to run Boogie is approximately 3 seconds, and the patterns survive to the Z3 output.
Thirdly: This could well be a symptom of the second problem, but I was surprised by the following behaviour:
I wrote axioms of the form
axiom (forall .. :: {t1,t2} .... );
axiom (forall ... :: {t2,t4} {t2,t3} ...some expression involving t1... );
and in a situation where t2
and t3
had occurred, the first axiom didn't get instantiated (I expected it to be, since after the instantiation of the second axiom, t1
occurs). However, if I rewrote as
axiom (forall .. :: {t2,t3} {t1,t2} .... );
axiom (forall ... :: {t2,t4} {t2,t3} ...some expression involving t1... );
then the first axiom was instantiated. However, if for some reason secondary patterns are not getting used in general, then that would also explain this behaviour.
If explicit examples would be useful, I can certainly attach long ones, and can try to cut them down a bit (but of course triggering problems are a bit delicate, so I might well lose the behaviour if I make the example too small).
Thanks a lot in advance for any advice!
Alex Summers
Edit: here is an example which partially illustrates the second and third behaviours described above. I've attached Boogie code to make it easier to read here, but I can also copy in or email the Z3 input if it'd be more useful. I've cut out almost all of the original Boogie code, but it seems hard to make it any simpler without losing the behaviour entirely.
Already the code below behaves subtly differently from my original example, but I think it's close enough. Basically the axiom labelled (1) below doesn't manage to have its second pattern set matched. If I comment out axiom (1), and instead replace it with the (currently-commented) axioms (2) and (3), which are just copies of the original with each of the two pattern sets, then the program verifies. Actually, it's enough to have axiom (2) in this particular case. In my original code (before I cut it down) it was also sufficient to flip the order of the two pattern sets in axiom (1), but that doesn't seem to be the case in my smaller repro any more.
type ref;
type HeapType;
function vals1(HeapType, ref) returns (ref);
function vals2(HeapType, ref) returns (ref);
function vals3(HeapType, ref) returns (ref);
function heap_trigger(HeapType) returns (bool);
function trigger1(ref) returns (bool);
function trigger2(ref) returns (bool);
axiom (forall Heap: HeapType, this: ref :: {vals1(Heap, this)} (vals1(Heap, this) == vals3(Heap,this)));
axiom (forall Heap: HeapType, this: ref :: {vals2(Heap, this)} trigger2(this));
// (1)
axiom (forall Heap: HeapType, this: ref :: {vals1(Heap, this)} {trigger1(this), heap_trigger(Heap), trigger2(this)} (vals1(Heap, this) == vals2(Heap, this)));
// (2)
// axiom (forall Heap: HeapType, this: ref :: {trigger1(this), heap_trigger(Heap), trigger2(this)} (vals1(Heap, this) == vals2(Heap, this)));
// (3)
// axiom (forall Heap: HeapType, this: ref :: {vals1(Heap, this)} (vals1(Heap, this) == vals2(Heap, this)));
procedure test(Heap:HeapType, this:ref)
{
assume trigger1(this); assume heap_trigger(Heap);
assert (vals2(Heap, this) == vals3(Heap,this));
}