z3z3pytheorem-provingsatsatisfiability

How to use soft constraints in Z3-Python to express 'abstract' biases in SAT search: such as 'I prefer half of the literals to be true and half false'


I previously asked in How to bias Z3's (Python) SAT solving towards a criteria, such as 'preferring' to have more negated literals, whether there was a way in Z3 (Python) to 'bias' the SAT search towards a 'criteria'?

In that post, we learnt 'simple biases' such as I would like Z3 to obtain a model, but not any model: if possible, give me a model that has a great amount of negated literals.

This was performed using a new solver (Optimize instead of Solver) and soft constraints (add_soft instead of add). Concretely, for each literal lit_n and an optimized solver o_solver, this was added: o_solver.add_soft(Not(lit_n)); or, equivalently: o_solver.add_soft(Or(Not(lit1), Not(lit2), ...).

However, I would like to express a 'little more complicated biases'. Concretely: if possible: I prefer models with the half of literals to True and the half to False.

Is there any way I can express this and similar 'biases' using the Optimize tool?


Solution

  • Here’s a simple idea that might help. Count the number of positive literals and subtract from the count of negative ones. Take the absolute value of the difference and minimize it using the optimizer.

    That should find the solution with as close a count of positive and negative literals, satisfying your “about half” soft constraint.

    Here's a simple example. Let's say you have six literals and you want to satisfy their disjunction. The idiomatic solution would be:

    from z3 import *
    
    a, b, c, d, e, f = Bools("a b c d e f")
    
    s = Solver()
    s.add(Or(a, b, c, d, e, f))
    
    print(s.check())
    print(s.model())
    

    If you run this, you'll get:

    sat
    [f = False,
     b = False,
     a = True,
     c = False,
     d = False,
     e = False]
    

    So, z3 simply made a True and all others False. But you wanted to have roughly the same count of positive and negative literals. So, let's encode that:

    from z3 import *
    
    a, b, c, d, e, f = Bools("a b c d e f")
    
    s = Optimize()
    s.add(Or(a, b, c, d, e, f))
    
    def count(ref, xs):
        s = 0
        for x in xs:
            s += If(x == ref, 1, 0)
        return s
    
    def sabs(x):
        return If(x > 0, x, -x)
    
    lits = [a, b, c, d, e, f]
    posCount = count(True,  lits)
    negCount = count(False, lits)
    s.minimize(sabs(posCount - negCount))
    
    
    print(s.check())
    print(s.model())
    

    Note how we "symbolically" count the negative and positive literals and ask z3 to minimize the absolute value of the difference. If you run this, you'll get:

    sat
    [a = True,
     b = False,
     c = False,
     d = True,
     e = False,
     f = True]
    

    With 3 positive and 3 negative literals. If you had 7 literals to start with, it'd have found a 4-3 split. If you prefer more positive literals than negatives, you can additionally add a soft constraint of the form:

    s.add_soft(posCount >= negCount)
    

    to bias the solver that way. Hope this gets you started..