Skip to main content
  (Week 5)

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:

01-fol-eval.py
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:

Iϕ1ϕ2Iϕ1 or Iϕ2 Ip(t1,,tn)(I[t1],,I[tn])I[p]

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:

p(f(y),f(f(x)))

Compute terms. I[y]=, so I[f(y)]=. I[x]=, so I[f(x)]=, and I[f(f(x))]=. Is (,) in I[p]? 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)

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 F=F1F2 where:

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 constants x 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 f(x)>x, or f(x)=f(x+1)) 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.