General method
You can use the z3's optimize engine to solve this. Here's an example.
Let's say we have 4 variables a, b, c, d
, and the predicates are a -> b&c
and c & !d
. We first create them and add the constraints to the engine:
from z3 import *
a, b, c, d = Bools('a b c d')
bools = [a, b, c, d]
s = Optimize()
s.add(Implies(a, And(b, c)))
s.add(And(c, Not(d)))
Now we keep a counter, and add 1 to it for each true variable:
goal = 0
for v in bools:
goal = goal + If(v, 1, 0)
Finally, we tell z3 to maximize this goal:
s.maximize(goal)
We can now query for the optimal model:
print(s.check())
print(s.model())
This prints:
sat
[b = True, a = True, d = False, c = True]
from which we can conclude that a maximally satisfying subset is {a, b, c}
for our constraints; which you can easily see is also the only one.
Using Soft Constraints
Another way to do this would be to add soft-constraints for each boolean instead of creating the goal
variable, something like this:
from z3 import *
a, b, c, d = Bools('a b c d')
s = Optimize()
s.add(Implies(a, And(b, c)))
s.add(And(c, Not(d)))
s.add_soft(a)
s.add_soft(b)
s.add_soft(c)
s.add_soft(d)
print(s.check())
print(s.model())
This'll produce the exact same output; and is equivalent when all you want to do is maximize the number of true
assignments. (The first form is more general if you want to prioritize certain variables over others, since you can assign different "integers" instead of 1
to all to insure they are preferable over others.)