Examples using the Pyidp3 API¶
All of these examples were made to be used in my CI testing pipeline. For each of the examples, I list up what elements are in it in case you’re looking for an example of something specific.
simple_inference¶
As the title says, this is a simple inference test. Contains:
- nbmodels and xdb options
- Defining constants without value or type
- Constraint
- Satchecking
- Model expanding
#!/usr/bin/python3
"""
This is a very simple and small test.
If this test fails, there's something extremely wrong.
It defines 3 variables, that each are either True or False.
"""
from pathlib import Path
from pyidp3.typedIDP import IDP
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.nbmodels = 2
idp.xsb = "true"
idp.Constant("P")
idp.Constant("Q")
idp.Constant("R")
idp.Constraint("(P => Q) <=> (P <= R).", True)
idp.Constraint("satisfiable().", True)
idp.check_sat()
idp.model_expand()
harder_inference¶
This is a bit tougher than the previous example. Contains:
- Type with a range
- Constant with type, no value
- Constraint
- Predicate
- Satchecking
- Model expanding
#!/usr/bin/python3
"""
This is a fairly simple test, it just adds more constants and constraints.
The generated IDP file will have 6 letters (each an int),
which have some constraints (A, E and I need to be even numbers,
while B, C and D need to be uneven).
A, B and C also can't be zero.
No two letters can be the same number.
The following puzzle needs to be solved:
AI
BA
+-----
CDE
Or in other terms: AI + BA = CDE
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
# Define the range of numbers
idp.Type("Decimal", (0, 9))
# Define the letters
letters = ['A', 'B', 'C', 'I', 'D', 'E']
even_letters = ['A', 'I', 'E']
for letter in letters:
idp.Constant(letter+": Decimal")
for letter2 in letters:
if letter == letter2:
continue
idp.Constraint(letter + " ~= " + letter2, True)
if letter in even_letters:
idp.Constraint("Even(" + letter + ")", True)
else:
idp.Constraint("~Even(" + letter + ")", True)
# Define the Even numbers
idp.Predicate("Even(Decimal)", [0, 2, 4, 6, 8])
# No two letters can have the same value
idp.Constraint("A ~= B & A ~= C & A ~= I & A ~= D & A ~= E & B ~= C & B ~= I"
"& B ~= D & B ~= E & C ~= I & C ~= D & C ~= E & I ~= D &"
" I ~= E & D ~= E", True)
idp.Constraint("I + A + 10*A + 10*B = E + 10 * D + 100 * C", True)
idp.check_sat()
idp.model_expand()
definition_test¶
This is a graphsolver with a Define in it. Other than that, nothing special going on. Contains:
- Type
- Predicate, with and without value
- Define
- Satchecking
- Modelexpansion
#!/usr/bin/python3
"""
This testfile is a graph solver.
Given a set of edges, it finds out what nodes are connected.
To do this is uses a definition consisting of two rules.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
# Define Node, Edge and Connected
nodelist = ['A', 'B', 'C', 'D', 'E']
idp.Type("Node", nodelist)
idp.Predicate("Edge(Node, Node)", ["A,B", "A,C", "B,E", "C,D", "C,E", "D,E",
"E,B"])
idp.Predicate("Connected(Node, Node)")
idp.Define("!x[Node] y[Node]: Connected(x,y) <- Edge(y,x). \n"
"!x[Node] y[Node] z[Node]: Connected(x,y) "
"<- Connected(x,z) & Connected(z,y)", True)
idp.check_sat()
idp.model_expand()
constructed_from¶
This file was mainly made to test the constructed from keyword. It contains:
- Type, with constructed from
- Predicate, with value
- Constraint
- Satchecking
- Modelexpansion
#!/usr/bin/python3
"""
Tests for "constructed_from".
This .idp file can only work when "constructed_from" works.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
days_of_the_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
"Saturday", "Sunday"]
idp.Type("Day", days_of_the_week, constructed_from=True)
idp.Predicate("Weekend(Day)", ["Saturday", "Sunday"])
idp.Constant("Easter: Day")
idp.Constraint("Weekend(Easter)", True)
idp.Constraint("Easter ~= Saturday", True)
idp.check_sat()
idp.model_expand()
initial_group_assign¶
This file is part of my master’s thesis. It’s a simplified version of my initial group assign, where the IDP-system tries to assign students to groups as best as it can. It tries to minimize a term “Totaal”. This currently isn’t in English, for which I’m sorry.
It contains:
- mxtimeout option
- Types, with lists as values
- Predicate
- Function
- Constant
- Define
- Constraint
- Satchecking
- Modelexpansion
- Minimization, using the Totaal term.
#!/usr/bin/python3
"""
Testfile to test if the initial solution to our problem still works as it
should be.
It minimizes, and then it modelexpands.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.mxtimeout = 10
WoonDict = {1: 87, 2: 98, 3: 80, 4: 80, 5: 83, 6: 88, 7: 90, 8: 86, 9: 80, 10:
53, 11: 98, 12: 57, 13: 86, 14: 82, 15: 98, 16: 83, 17: 15, 18: 88,
19: 22, 20: 78, 21: 80, 22: 86, 23: 27, 24: 57, 25: 84}
ZoneDict = {1: 2, 2: 1, 3: 2, 4: 2, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 10: 2, 11: 1,
12: 2, 13: 2, 14: 2, 15: 1, 16: 2, 17: 2, 18: 1, 19: 2, 20: 1, 21:
2, 22: 2, 23: 2, 24: 2, 25: 2}
idp.Type("Student", list(range(1, 26)))
idp.Type("Getal", list(range(int(1000000))))
idp.Type("Postcode", list(range(0, 100)))
idp.Type("Zone", list(range(0, 10)))
idp.Predicate("Samen(Student,Student)")
idp.Predicate("VolSamen(Student, Student)")
idp.Function("Woont(Student): Postcode", WoonDict)
idp.Function("WoontZone(Student): Zone", ZoneDict)
idp.Predicate("Wortel(Student)")
idp.Predicate("Blad(Student)")
idp.Function("Aant(Student): Getal")
idp.Constant("SamenSchool: Getal")
idp.Constant("Afstand: Getal")
idp.Constant("UitZone: Getal")
idp.Constant("Totaal: Getal")
idp.Define("Wortel(x) <- x < min{y[Student]: Samen(y,x):y}.", True)
idp.Define("Blad(x) <- ~Wortel(x).", True)
idp.Constraint("#{x[Student]: Wortel(x)} = 5", True)
idp.Constraint("!x[Student]: Blad(x) <=> ?y[Student]: Wortel(y) & Samen(y,x)",
True)
idp.Constraint("!x[Student]: Aant(x) = #{y[Student]: Samen(x,y) | Samen(y,x)}",
True)
idp.Constraint("!x[Student]: Wortel(x) <=> 4 =< Aant(x) =< 6", True)
idp.Constraint("!x[Student]: Blad(x) <=> Aant(x) = 1", True)
idp.Constraint("!x[Student] y[Student]: Samen(x,y) => Wortel(x) & Blad(y)",
True)
idp.Define("!x[Student] y[Student] z[Student]: VolSamen(y,z) <- Wortel(x) & y"
" < z & Samen(x,y) & Samen(x,z).\n"
"!x[Student] y[Student]: VolSamen(x,y) <- Wortel(x) & Samen(x,y).",
True)
idp.Constraint("Afstand = sum{x[Student] y[Student]: x < y & VolSamen(x,y) &"
" WoontZone(x) = WoontZone(y): abs(Woont(x) - Woont(y))}", True)
idp.Constraint("UitZone = #{x[Student] y[Student]: x < y & VolSamen(x,y) &"
" WoontZone(x) ~= WoontZone(y) }", True)
idp.Constraint("Totaal = Afstand + UitZone * 100", True)
idp.check_sat()
idp.model_expand()
sols = idp.minimize("Totaal")
for sol in sols:
print(sol)
if sol['satisfiable']:
for x in sol['Samen']:
print(x)
initial_group_assign_plus¶
This file is an expanded version of the previous, where schools are taken into account.
It contains:
- mxtimeout and nbmodels option
- Types, with lists as values
- Predicate
- Function
- Constant
- Define
- Constraint
- Satchecking
- Modelexpansion
- Minimization, using the Totaal term.
#!/usr/bin/python3
"""
Testfile that checks whether ISSUE-1 (the {x..y} in structs) is still valid.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.mxtimeout = 10
idp.nbmodels = 5
WoonDict = {1: 87, 2: 98, 3: 80, 4: 80, 5: 83, 6: 88, 7: 90, 8: 86, 9: 80, 10:
53, 11: 98, 12: 57, 13: 86, 14: 82, 15: 98, 16: 83, 17: 15, 18: 88,
19: 22, 20: 78, 21: 80, 22: 86, 23: 27, 24: 57, 25: 84}
ZoneDict = {1: 2, 2: 1, 3: 2, 4: 2, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 10: 2, 11: 1,
12: 2, 13: 2, 14: 2, 15: 1, 16: 2, 17: 2, 18: 1, 19: 2, 20: 1, 21:
2, 22: 2, 23: 2, 24: 2, 25: 2}
SchoolDict = {1: "GO! atheneum Anderlecht",
2: "KONINKLIJK ATHENEUM KOEKELBERG",
3: "TSM-Bovenbouw"', 4: '"TSM-Bovenbouw",
5: "PTS, Provinciale Scholen voor Tuinbouw en Techniek",
6: "Onze-Lieve-Vrouw-Presentatie", 7: "Sint-Ludgardisschool",
8: "Mater Salvatorisinstituut", 9: "Scheppersinstituut",
10: "Sint-Gabriëlcollege", 11: "Scheppersinstituut",
12: "Sint-Norbertusinstituut 2", 13: "Ursulinen Mechelen 1",
14: "College Hagelstein 2", 15: "Scheppersinstituut",
16: "TSM-Bovenbouw", 17: "TOEKOMSTONDERWIJS HOBOKEN",
18: "Gemeentelijk Technisch Instituut",
19: "Heilig Hart - Bovenbouw 1",
20: "Gemeentelijke Technische en Beroepsschool",
21: "GO! Busleyden Atheneum-campus Pitzemburg",
22: "College Hagelstein 2",
23: "Kardinaal van Roey-Instituut ASO",
24: "Sint-Gummaruscollege", 25: "GO! atheneum Boom"}
idp.Type("Student", (1, 25))
idp.Type("Getal", (0, 10000000))
idp.Type("Postcode", (0, 100))
idp.Type("Zone", (0, 10))
idp.Predicate("Samen(Student,Student)")
idp.Predicate("VolSamen(Student, Student)")
idp.Function("Woont(Student): Postcode", WoonDict)
idp.Function("WoontZone(Student): Zone", ZoneDict)
idp.Function("School(Student): string", SchoolDict)
idp.Predicate("Wortel(Student)")
idp.Predicate("Blad(Student)")
idp.Function("Aant(Student): Getal")
idp.Constant("SamenSchool: Getal")
idp.Constant("Afstand: Getal")
idp.Constant("UitZone: Getal")
idp.Constant("Totaal: Getal")
idp.Define("Wortel(x) <- x < min{y[Student]: Samen(y,x):y}.", True)
idp.Define("Blad(x) <- ~Wortel(x).", True)
idp.Constraint("#{x[Student]: Wortel(x)} = 5", True)
idp.Constraint(" !x[Student]: Blad(x) <=> ?y[Student]: Wortel(y) & Samen(y,x)",
True)
idp.Constraint("!x[Student]: Aant(x) = #{y[Student]: Samen(x,y) | Samen(y,x)}",
True)
idp.Constraint("!x[Student]: Wortel(x) <=> 4 =< Aant(x) =< 6", True)
idp.Constraint("!x[Student]: Blad(x) <=> Aant(x) = 1", True)
idp.Constraint("!x[Student] y[Student]: Samen(x,y) => Wortel(x) & Blad(y)",
True)
idp.Define("!x[Student] y[Student] z[Student]: VolSamen(y,z) <- Wortel(x) & y"
" < z & Samen(x,y) & Samen(x,z).\n"
"!x[Student] y[Student]: VolSamen(x,y) <- Wortel(x) & Samen(x,y).",
True)
idp.Constraint("SamenSchool = #{x[Student] y[Student]: x < y & VolSamen(x,y)"
"& School(x) ~= School(y)}", True)
idp.Constraint("Afstand = sum{x[Student] y[Student]: x < y & VolSamen(x,y) &"
" WoontZone(x) = WoontZone(y): abs(Woont(x) - Woont(y))}", True)
idp.Constraint("UitZone = #{x[Student] y[Student]: x < y & VolSamen(x,y) &"
" WoontZone(x) ~= WoontZone(y) }", True)
idp.Constraint("Totaal = Afstand + UitZone * 100 + SamenSchool*100", True)
solutions = idp.model_expand()
solutions = idp.minimize("Totaal")
idp.check_sat()
print("{:d} solutions!".format(len(solutions)))
for index, sol in enumerate(solutions):
if sol['satisfiable']:
print("Sol {:d}:".format(index))
for x in sol['Samen']:
print('\t', x)
else:
continue
further_group_assign¶
This is the other part of my master’s thesis. Here we try to reassign students based on their old groups, and a preference.
It contains:
- nbmodels option
- comparing solutions using the compare method
- Type, also one with the isa keyword
- Constant, with value
- Predicate
- Function
- Constraint
- Define
- minimization, using the Total term
#!/usr/bin/python3
"""
Testfile which attempts to assign students to a group, based on a previous
group and a preference.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.nbmodels = 5
# All the inputvars
idp.Type("Student", (1, 152))
idp.Type("Number", (0, 10000000))
idp.Type("Group", (2, 11))
idp.Type("Priority", "Number", isa=True)
idp.Constant("MinSize: Number", 14)
idp.Constant("MaxSize: Number", 16)
together = [(142, 139, 2), (139, 143, 1), (139, 142, 1)]
not_together = [(142, 143, 2), (142, 141, 2), (142, 140, 2)]
in_group = []
group_dict = {1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10,
10: 11, 11: 3, 12: 11, 13: 8, 14: 6, 15: 4, 16: 2, 17: 10,
18: 8, 19: 7, 20: 5, 21: 4, 22: 3, 23: 2, 24: 11, 25: 9,
26: 8, 27: 6, 28: 6, 29: 5, 30: 4, 31: 3, 32: 2, 33: 10,
34: 11, 35: 9, 36: 5, 37: 7, 38: 7, 39: 6, 40: 5, 41: 5,
42: 4, 43: 3, 44: 3, 45: 2, 46: 10, 47: 10, 48: 11, 49: 8,
50: 9, 51: 7, 52: 8, 53: 10, 54: 6, 55: 6, 56: 5, 57: 5,
58: 5, 59: 4, 60: 4, 61: 3, 62: 3, 63: 2, 64: 2, 65: 10,
66: 7, 67: 10, 68: 11, 69: 11, 70: 9, 71: 9, 72: 8, 73: 8,
74: 7, 75: 7, 76: 6, 77: 6, 78: 6, 79: 9, 80: 5, 81: 5,
82: 4, 83: 4, 84: 4, 85: 3, 86: 3, 87: 3, 88: 2, 89: 2,
90: 2, 91: 2, 92: 10, 93: 11, 94: 10, 95: 8, 96: 11,
97: 11, 98: 9, 99: 9, 100: 9, 101: 9, 102: 8, 103: 8,
104: 8, 105: 7, 106: 7, 107: 7, 108: 6, 109: 6, 110: 6,
111: 6, 112: 5, 113: 5, 114: 5, 115: 5, 116: 4, 117: 4,
118: 4, 119: 4, 120: 4, 121: 3, 122: 3, 123: 3, 124: 3,
125: 2, 126: 2, 127: 2, 128: 2, 129: 11, 130: 10, 131: 10,
132: 10, 133: 10, 134: 10, 135: 11, 136: 11, 137: 11,
138: 11, 139: 9, 140: 9, 141: 9, 142: 9, 143: 9, 144: 8,
145: 8, 146: 8, 147: 7, 148: 7, 149: 7, 150: 7, 151:
7, 152: 6}
idp.Function("InGroup(Student): Group", group_dict)
idp.Predicate("WantsTogether(Student, Student, Priority)", together)
idp.Predicate("NotWantsTogether(Student, Student, Priority)", not_together)
idp.Predicate("WantsInGroup(Student, Group, Priority)", in_group)
# All the inner workings + output
idp.Function("GroupSize(Group): Number")
idp.Constant("Total: Number")
idp.Constant("TotUnsatTogether: Number")
idp.Constant("TotUnsatNotTogether: Number")
idp.Constant("TotUnsatInGroup: Number")
idp.Constant("TotUnsatNewGroup: Number")
idp.Predicate("UnsatTogether(Student, Student)")
idp.Predicate("UnsatNotTogether(Student, Student)")
idp.Predicate("UnsatInGroup(Student, Group)")
idp.Predicate("UnsatNewGroup(Student)")
idp.Function("NewInGroup(Student): Group")
# All the necessary constraints
idp.Constraint("!g[Group]: GroupSize(g) = #{x[Student]: NewInGroup(x) = g}",
True)
idp.Constraint("!g[Group]: MinSize =< GroupSize(g) =< MaxSize", True)
idp.Constraint("TotUnsatNewGroup = #{x[Student]: InGroup(x) ~= NewInGroup(x)}",
True)
idp.Constraint("TotUnsatTogether = sum{x[Student] y[Student]:"
" WantsTogether(x,y,p) & NewInGroup(x) ~= NewInGroup(y): p}",
True)
idp.Constraint("TotUnsatNotTogether = sum{x[Student] y[Student]:"
" NotWantsTogether(x,y,p) & NewInGroup(x) = NewInGroup(y): p}",
True)
idp.Constraint("TotUnsatInGroup = sum{x[Student] g[Group]: WantsInGroup(x,g,p)"
" & NewInGroup(x) ~= g: p}", True)
idp.Constraint("Total = TotUnsatTogether + TotUnsatNotTogether +"
" TotUnsatInGroup + TotUnsatNewGroup", True)
idp.Define("!x[Student] y[Student]: UnsatTogether(x,y) <- WantsTogether(x,y,p)"
" & NewInGroup(x) ~= NewInGroup(y).", True)
idp.Define("!x[Student] y[Student]: UnsatNotTogether(x,y) <- "
" NotWantsTogether(x,y,p) & NewInGroup(x) = NewInGroup(y).",
True)
idp.Define("!x[Student]: UnsatNewGroup(x) <- InGroup(x) ~= NewInGroup(x).",
True)
idp.Define("!x[Student] g[Group]: UnsatInGroup(x,g) <- WantsInGroup(x,g,p) &"
" NewInGroup(x) ~= g.", True)
sols = idp.minimize("Total")
newgroups = []
for sol in sols:
newgroups.append(sol['NewInGroup'])
print(newgroups)
print("Verschil tussen sols:")
idp.compare(newgroups)
# idp.compare(sols)
sudoku¶
Sudokusolver.
Contains:
- nbmodels option
- Type
- Constant, with value
- Predicate
- Function
- Constraint
- Define
- Modelexpansion
#!/usr/bin/python3
"""
This testfile solves a sudoku.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.nbmodels = 10
idp.Type("Row", (1, 9))
idp.Type("Column", (1, 9))
idp.Type("Number", (1, 9))
start_numbers = [(1, 1, 8), (1, 1, 8),
(2, 3, 3), (2, 4, 6),
(3, 2, 7), (3, 5, 9), (3, 7, 2),
(4, 2, 5), (4, 6, 7),
(5, 5, 4), (5, 6, 5), (5, 7, 7),
(6, 4, 1), (6, 8, 3),
(7, 3, 1), (7, 8, 6),
(7, 9, 8),
(8, 3, 8),
(8, 4, 5),
(8, 8, 1),
(9, 2, 9),
(9, 7, 4)]
idp.Predicate("Square(Row, Column, Row, Column)")
idp.Predicate("Start(Row,Column,Number)", start_numbers)
idp.Predicate("Group(Row, Column, Row, Column)")
idp.Function("Solution(Row, Column): Number")
idp.Constraint("!r[Row] c[Column] number: Start(r, c, number) =>"
"Solution(r, c) = number", True)
idp.Define("!r1[Row] c1[Column] r2[Row] c2[Column]:"
"Square(r1,c1,r2,c2) <- r1- (r1-1)%3 = "
" r2 - (r2-1)%3 & c1-(c1-1)%3 = c2-(c2-1)%3",
True)
idp.Define("!r1[Row] c1[Column] r2[Row] c2[Column]: Group(r1, c1, r2, c2) "
"<- Square(r1, c1, r2, c2).\n"
"!r1[Row] r2[Row] c[Column]: Group(r1, c, r2, c) <- true.\n"
"!r[Row] c1[Row] c2[Column]: Group(r, c1, r, c2).", True)
idp.Constraint("!r1[Row] c1[Column] r2[Row] c2[Column]: Group(r1, c1, r2, c2)"
"& (r1 ~= r2 | c1 ~= c2) => Solution(r1,c1) ~="
"Solution(r2,c2)", True)
idp.check_sat()
sols = idp.model_expand()
for index, sol in enumerate(sols):
if sol['satisfiable']:
print("Sol{:d}: ".format(index), sol['Solution'])
masyu¶
Contains:
- Type, constructed_from
- Constant, with value
- Predicate
- Function
- Constraint
- Define
- Satchecking
- Modelexpansion
#!/usr/bin/python3
"""
This is by far the testfile with the most in it (yet it's not the hardest!).
It solves a masyu puzzle.
"""
from pathlib import Path
from pyidp3.typedIDP import *
home = str(Path.home())
idp = IDP(home+"/idp/usr/local/bin/idp")
idp.Type("Row", (0, 4))
idp.Type("Column", (0, 4))
idp.Type("Pearl", ["Hollow", "Filled"], constructed_from=True)
idp.Type("Wire", ["NS", "EW", "ES", "WS", "NE", "NW", "Empty"],
constructed_from=True)
pearl_position = [(0, 2, "Filled"),
(1, 4, "Filled"),
(2, 2, "Filled"),
(3, 0, "Filled"), (3, 1, "Filled"),
(4, 4, "Hollow")]
idp.Predicate("PearlPosition(Row, Column, Pearl)", pearl_position)
idp.Predicate("WireStraight(Wire)", ["NS", "EW"])
idp.Predicate("WireCurve(Wire)", ["ES", "WS", "NE", "NW"])
idp.Predicate("WireNorth(Wire)", ["NS", "NE", "NW"])
idp.Predicate("WireEast(Wire)", ["ES", "NE", "EW"])
idp.Predicate("WireSouth(Wire)", ["NS", "WS", "ES"])
idp.Predicate("WireWest(Wire)", ["EW", "WS", "NW"])
idp.Predicate("Link(Row, Column, Row, Column)")
idp.Predicate("Connected(Row, Column, Row, Column)")
idp.Function("Solution(Row, Column): Wire")
idp.Constraint("!r c: WireNorth(Solution(r,c)) => WireSouth(Solution(r-1,c))",
True)
idp.Constraint("!r c: WireSouth(Solution(r,c)) => WireNorth(Solution(r+1,c))",
True)
idp.Constraint("!r c: WireEast(Solution(r,c)) => WireWest(Solution(r,c+1))",
True)
idp.Constraint("!r c: WireWest(Solution(r,c)) => WireEast(Solution(r,c-1))",
True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Hollow => "
"WireCurve(Solution(r,c))", True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Filled => "
"WireStraight(Solution(r,c))", True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Hollow & "
"WireNorth(Solution(r,c)) => WireStraight(Solution(r-1,c))",
True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Hollow & "
"WireSouth(Solution(r,c)) => WireStraight(Solution(r+1,c))",
True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Hollow & "
"WireEast(Solution(r,c)) => WireStraight(Solution(r,c+1))",
True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Hollow & "
"WireWest(Solution(r,c)) => WireStraight(Solution(r,c-1))",
True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Filled & "
"WireNorth(Solution(r,c)) => (WireCurve(Solution(r-1,c)) | "
"WireCurve(Solution(r+1,c)))", True)
idp.Constraint("!r c parel: PearlPosition(r,c,parel) & parel = Filled & "
"WireEast(Solution(r,c)) => (WireCurve(Solution(r,c-1)) | "
"WireCurve(Solution(r,c+1)))", True)
idp.Constraint("!r1 c1 r2 c2: (Solution(r1,c1) ~= Empty & Solution(r2,c2) ~= "
"Empty) => Connected(r1,c1,r2,c2)", True)
idp.Define("!r c: Link(r,c,r-1,c) <- WireNorth(Solution(r,c)) & "
"WireSouth(Solution(r-1,c)). "
"!r c: Link(r,c,r+1,c) <- WireSouth(Solution(r,c)) & "
"WireNorth(Solution(r+1,c)). "
"!r c: Link(r,c,r,c+1) <- WireEast(Solution(r,c)) & "
"WireWest(Solution(r,c+1)). "
"!r c: Link(r,c,r,c-1) <- WireWest(Solution(r,c)) &"
"WireEast(Solution(r,c-1)).", True)
idp.Define("!r1 c1 r2 c2: Connected(r1,c1,r2,c2) <- Link(r1,c1,r2,c2). "
"!r1 c1 r2 c2: Connected(r1,c1,r2,c2) <- ?r3 k3: "
"Connected(r3,k3,r2,c2) & Connected(r1,c1,r3,k3). ", True)
idp.check_sat()
idp.model_expand()