from z3 import *
Color the following graph with red, yellow, and blue such that no two adjacent nodes have the same color
from IPython.display import Image
Image(filename='/e/graph.jpg')
nodes = ['A', 'B', 'C', 'D', 'E']
edges = [('A', 'C'), ('A', 'E'), ('B', 'C'), ('B', 'E'), ('C', 'D'), ('D', 'E')]
colors = ['r', 'b', 'y']
Propositional variables are created by calling Bool
with the name of the variable as an argument.
Z3 treats variable objects with the same name identically, so future objects returned by calling Bool
with an identical argument will refer to the same variable.
Here we define one variable for each node-color pairing.
vs = []
for node in nodes:
for color in colors:
vs.append(Bool('{}{}'.format(node, color)))
vs
[Ar, Ab, Ay, Br, Bb, By, Cr, Cb, Cy, Dr, Db, Dy, Er, Eb, Ey]
Define constraints which prohibit adjacent vertices being assigned the same color.
adjacent_not_same = []
for (n1, n2) in edges:
for color in colors:
v1 = z3.Bool('{}{}'.format(n1, color))
v2 = z3.Bool('{}{}'.format(n2, color))
adjacent_not_same.append(Or(Not(v1), Not(v2)))
adjacent_not_same, len(adjacent_not_same)
([Or(Not(Ar), Not(Cr)), Or(Not(Ab), Not(Cb)), Or(Not(Ay), Not(Cy)), Or(Not(Ar), Not(Er)), Or(Not(Ab), Not(Eb)), Or(Not(Ay), Not(Ey)), Or(Not(Br), Not(Cr)), Or(Not(Bb), Not(Cb)), Or(Not(By), Not(Cy)), Or(Not(Br), Not(Er)), Or(Not(Bb), Not(Eb)), Or(Not(By), Not(Ey)), Or(Not(Cr), Not(Dr)), Or(Not(Cb), Not(Db)), Or(Not(Cy), Not(Dy)), Or(Not(Dr), Not(Er)), Or(Not(Db), Not(Eb)), Or(Not(Dy), Not(Ey))], 18)
Now we define constraints which state that each vertex must be assigned a color.
at_least_one_color = []
for node in nodes:
disj = []
for color in colors:
disj.append(z3.Bool('{}{}'.format(node, color)))
at_least_one_color.append(Or(disj))
at_least_one_color
[Or(Ar, Ab, Ay), Or(Br, Bb, By), Or(Cr, Cb, Cy), Or(Dr, Db, Dy), Or(Er, Eb, Ey)]
And finally, constraints which limit each vertex to at most one color.
at_most_one_color = []
for node in nodes:
for i in range(len(colors)):
for j in range(i+1, len(colors)):
at_most_one_color.append(Or([Not(Bool('{}{}'.format(node, colors[i]))),
Not(Bool('{}{}'.format(node, colors[j])))]))
at_most_one_color
[Or(Not(Ar), Not(Ab)), Or(Not(Ar), Not(Ay)), Or(Not(Ab), Not(Ay)), Or(Not(Br), Not(Bb)), Or(Not(Br), Not(By)), Or(Not(Bb), Not(By)), Or(Not(Cr), Not(Cb)), Or(Not(Cr), Not(Cy)), Or(Not(Cb), Not(Cy)), Or(Not(Dr), Not(Db)), Or(Not(Dr), Not(Dy)), Or(Not(Db), Not(Dy)), Or(Not(Er), Not(Eb)), Or(Not(Er), Not(Ey)), Or(Not(Eb), Not(Ey))]
s = Solver()
s.add(adjacent_not_same)
s.add(at_least_one_color)
s.add(at_most_one_color)
The string representation of a solver object is the set of constraints that have been asserted on it
s
s.check()
If the solver returns sat
, then a satisfying assignment can be obtained with its model
method
s.model()
It's a bit unnatural to read the assignment this way, so we'll pretty-print it by adjacency
for v1, v2 in edges:
v1_color = None
v2_color = None
for color in colors:
if is_true(s.model().eval(z3.Bool('{}{}'.format(v1, color)))):
v1_color = color
if is_true(s.model().eval(z3.Bool('{}{}'.format(v2, color)))):
v2_color = color
print('{}{} -- {}{}'.format(v1, v1_color, v2, v2_color))
Ar -- Cb Ar -- Eb Br -- Cb Br -- Eb Cb -- Dr Dr -- Eb
Although the solution Z3 obtained isn't the same one shown in the drawing, it is a valid coloring of the graph nonetheless
from IPython.display import Image
Image(filename='/e/graph.jpg')