Hands on the Cooperation Protocol
Your first session writing FOL semantics directly: type the evaluator, predict mixed-theory outcomes, then construct a cooperation gap from scratch.
Where We Left Off
Theory covered FOL semantics, the theory-solver interface, the three Nelson-Oppen restrictions, purification, convexity, the convex algorithm, the worked example, the non-convex extension on Practice Act 2's formula, and two teasers for L06.
Four exercises: three core, one stretch. Each one is predict, then check: write down your predicted answer first, then run the code or compute by hand. You will also write a bit of code for the first exercise.
Format
A single Python file with sections per exercise.
01-fol-eval.py is the skeleton; 01-fol-eval-soln.py is the
solution. Both files are in the course code repo next to the Practice demos. Clone and run them locally with python3 01-fol-eval.py (requires z3-solver; see Setup).
The skeleton has TODO holes. The runner catches
NotImplementedError so you see all test cases at once even
before you've implemented anything.
Rhythm
About 20 minutes in, we pause for a class-wide check-in. By then most of you should be through Ex 1 and starting Ex 2. Bring whatever question is on top of your stack.
Exercise 1: FOL evaluator on
Turn the FOL semantics from Theory §1 into code. The structure is a Python dictionary. The evaluator is recursive.
The skeleton fixes the structure:
structure = {
'universe': {'star', 'circle'},
'interp': {
'x': 'star',
'y': 'circle',
'f': {('star',): 'circle', ('circle',): 'star'},
'p': {('star', 'star'), ('star', 'circle')},
},
}
This is exactly Theory §1's worked example, typed out. Constants
map to elements, the function f maps to a dict of
arg-tuple-to-result, and the predicate p maps to a set of
tuples (those for which the relation holds).
eval_term is provided. eval_formula has TODO holes for the
disjunction case and the predicate-application case. Fill them
in based on the FOL semantics rules:
Predict, then check
The skeleton has six test formulas, each with expected = 'TODO'.
Replace each 'TODO' with your predicted True or False
before implementing the cases.
Work the first one by hand:
Compute terms. , so . , so , and . Is in ? The structure says yes. So the formula is true.
Predict the other five. Implement the TODOs. Run. Check.
Stretch: extend the universe
Once your evaluator works, add a third element:
structure['universe'].add('triangle')
Extend f and p to handle the new element. There is no single
right answer. Pick something. Then predict how the existing six
tests change (or don't), and verify.
Exercise 2: Mixed-theory predict-then-check
Switch back to Z3. Three formulas, each spanning two or more theories. Predict SAT or UNSAT before running.
from z3 import (Ints, IntSort, Array, Store, Select,
Function, Solver, Not, And)
x, y = Ints('x y')
a = Array('a', IntSort(), IntSort())
f = Function('f', IntSort(), IntSort())
# Formula 1: array + integer
F1 = And(Select(Store(a, x, 5), x) == 5,
x == y,
Select(Store(a, y, 5), y) != 5)
# Formula 2: equality + integer
F2 = And(x + y == 0, f(x) != f(-y))
# Formula 3: from Practice Act 2
F3 = And(1 <= x, x <= 2, f(x) != f(1), f(x) != f(2))
for label, F in [('F1', F1), ('F2', F2), ('F3', F3)]:
s = Solver()
s.add(F)
print(label, s.check())
Predict each one on paper, then run. Compare your predictions to the reasoning below.
Reasoning (peek after you've predicted on paper)
- F1 mixes arrays and integers. The array axiom says reading right after writing returns what you wrote, so
Select(Store(a, x, 5), x) == 5is forced. Withx == y, function congruence on arrays should give the same result on the secondSelect(Store(...), y). So it equals 5, not the thing the formula says. - F2 mixes equality and integers.
x + y == 0meansy == -x, so-y == x, sof(x)andf(-y)refer to the same input. Function congruence forces them equal, contradicting!=. - F3 is the cooperation-gap formula from Practice Act 2. You traced both branches in Theory §9.
If a formula returned SAT when you predicted UNSAT (or vice versa), back up and find where your reasoning broke. The integer reasoner, the equality reasoner, and the array reasoner all have to agree.
Exercise 3: Build your own cooperation-gap formula
Construct a formula where:
- is a conjunction of -literals only.
- is a conjunction of -literals only.
- is satisfiable on its own.
- is satisfiable on its own.
- is unsatisfiable.
Verify each piece SAT in Z3 and the conjunction UNSAT.
Find a fact that one theory implies about its shared constants but the other theory cannot agree with. Practice Act 2 did this with a non-convex disjunction. Can you do it with a single equality across convex theories?
Hint:
Pick two shared constantsx and y. Use
F_2 ∈ T_R to force x = y without writing the equality
outright. Then use F_1 ∈ T_= to assert something about
f(x) and f(y) that the equality forbids.
Stretch: Exercise 4
A preview of L06.
from z3 import Function, IntSort, Ints, ForAll, Solver, Not
f = Function('f', IntSort(), IntSort())
x, y = Ints('x y')
s = Solver()
s.add(ForAll([x], f(x) >= 0))
s.add(Not(f(y) >= 0))
print(s.check())
Predict. Run. Then change the axiom (try , or ) and predict the same negated-instance check.
Next week opens the mechanism that makes this work: E-matching and MBQI.
One last beat
Theories pass notes through equality.
Next week: the boolean envelope, and ForAll.