szczocik
szczocik

Reputation: 1333

Encode admissible sets in Z3Py

Based on the argumentation framework theory, I'm trying to encode the admissible sets using the Z3Py prover. However, I have run into some problems and would appreciate any pointers on how to improve it.

Based on the definition from Wikipedia, a set of arguments E is admissible if and only if it is conflict-free and all its arguments are acceptable with respect to E.

Based on the following directed graph with arguments: a, b, c, d, e and relations: (a, d), (a, b), (b, c), (c, b), (c, d), (e, a) the correct solution for admissible sets is: [], [e], [c], [c, e], [b, e], [b, d, e]

I started playing with Z3Py but have a problem encoding this.

So far I have this:

from z3 import *

a, b, c, d, e = Bools('a b c d e')
arguments = [a, b, c, d, e]
solver = Solver()
relations = [(a, d), (a, b), (b, c), (c, b), (c, d), (e, a)]

# ensure there are no conflicting arguments
for relation in relations:
    solver.append(Implies(relation[0], Not(relation[1])))

for argument in arguments:
    # if not attacked then removed any arguments attacked by this argument
    if len([item for item in relations if item[1] == argument]) == 0:
        solver.append([Not(attacked[1]) for attacked in relations if attacked[0] == argument])

    # if self attacking remove the argument
    if len([item for item in relations if item[1] == argument and item[0] == argument]) > 0:
        solver.append(Not(argument))

    # get all attackers
    attackers = [item[0] for item in relations if item[1] == argument]
    for attacker in attackers:
        # get defenders of the initial argument (arguments attacking the attacker)
        defenders = [item[0] for item in relations if item[1] == attacker]
        defending_z3 = []
        if len(defenders) > 0:
            for defender in defenders:
                if defender not in attackers:
                    defending_z3.append(defender)
            if len(defending_z3) > 0:
                solver.append(Implies(Or([defend for defend in defending_z3]), argument))
        else:
            solver.append(Not(argument))

# get solutions
solutions = []
while solver.check() == sat:
    model = solver.model()
    block = []
    solution = []
    for var in arguments:
        v = model.eval(var, model_completion=True)
        block.append(var != v)
        if is_true(v):
            solution.append(var)
    solver.add(Or(block))
    solutions.append(solution)

for solution in solutions:
    print(solution)

When executed it gives me following solutions: [], [c], [d], [b, d], [b, d, e], where only 3 of them are correct:

  1. [b, d] is incorrect since d can't defend itself (e is defender of d)
  2. [d] again incorrect since d can't defend itself
  3. [e] is missing
  4. [c, e] is missing
  5. [b, e] is missing

Any help would be appreciated.

Upvotes: 1

Views: 81

Answers (1)

alias
alias

Reputation: 30428

This is a really great problem for SMT solving, and z3 will be able to handle such problems with relative ease, even when you have really large sets.

Your encoding idea is correct, but you're conflating equality: You should be careful when you're testing if a variable is the same as another. To do that, use Python's eq method. (If you use ==, then you'll get a symbolic equality test, which isn't what you're looking for here.)

Given that, I'd be inclined to code your problem as follows:

from z3 import *

a, b, c, d, e = Bools('a b c d e')
arguments = [a, b, c, d, e]
solver = Solver()
relations = [(a, d), (a, b), (b, c), (c, b), (c, d), (e, a)]

# No conflicting arguments
for relation in relations:
    solver.add(Not(And(relation[0], relation[1])))

# For each element, if it is attacked, then another element that
# attacks the attacker must be present:
for argument in arguments:
    # Find the attackers for this argument:
    attackers = [relation[0] for relation in relations if argument.eq(relation[1])]

    # We must defend against each attacker:
    for attacker in attackers:
        defenders = [relation[0] for relation in relations if relation[1].eq(attacker)]

        # One of the defenders must be included:
        solver.add(Implies(argument, Or(defenders)))

# Collect the solutions:
while solver.check() == sat:
    model = solver.model()
    block = []
    solution = []

    for var in arguments:
        v = model.eval(var, model_completion=True)
        block.append(var != v)
        if is_true(v):
            solution.append(str(var))

    solver.add(Or(block))
    solution.sort()
    print(solution)

Hopefully, the code should be easy to follow. If not, feel free to ask specific questions.

When I run it, I get:

[]
['c']
['c', 'e']
['e']
['b', 'e']
['b', 'd', 'e']

which seems to match what you predicted the solution should be.

Upvotes: 1

Related Questions