-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathconstraints.py
166 lines (136 loc) · 5.56 KB
/
constraints.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# -*- coding: utf-8 -*-
from __future__ import print_function
import itertools
class NegativeClause(object):
# disjunction of inequalities
def __init__(self, parts):
self.parts = parts
assert len(parts)
def __str__(self):
disj = " or ".join(["(%s != %s)" % (v1, v2)
for (v1, v2) in self.parts])
return "(%s)" % disj
def is_satisfiable(self):
for part in self.parts:
if part[0] != part[1]:
return True
return False
def apply_mapping(self, m):
new_parts = [(m.get(v1, v1), m.get(v2, v2)) for (v1, v2) in self.parts]
return NegativeClause(new_parts)
class Assignment(object):
def __init__(self, equalities):
self.equalities = tuple(equalities)
# represents a conjunction of expressions ?x = ?y or ?x = d
# with ?x, ?y being variables and d being a domain value
self.consistent = None
self.mapping = None
self.eq_classes = None
def __str__(self):
conj = " and ".join(["(%s = %s)" % (v1, v2)
for (v1, v2) in self.equalities])
return "(%s)" % conj
def _compute_equivalence_classes(self):
eq_classes = {}
for (v1, v2) in self.equalities:
c1 = eq_classes.setdefault(v1, set([v1]))
c2 = eq_classes.setdefault(v2, set([v2]))
if c1 is not c2:
if len(c2) > len(c1):
v1, c1, v2, c2 = v2, c2, v1, c1
c1.update(c2)
for elem in c2:
eq_classes[elem] = c1
self.eq_classes = eq_classes
def _compute_mapping(self):
if not self.eq_classes:
self._compute_equivalence_classes()
# create mapping: each key is mapped to the smallest
# element in its equivalence class (with objects being
# smaller than variables)
mapping = {}
for eq_class in self.eq_classes.values():
variables = [item for item in eq_class if item.startswith("?")]
constants = [item for item in eq_class if not item.startswith("?")]
if len(constants) >= 2:
self.consistent = False
self.mapping = None
return
if constants:
set_val = constants[0]
else:
set_val = min(variables)
for entry in eq_class:
mapping[entry] = set_val
self.consistent = True
self.mapping = mapping
def is_consistent(self):
if self.consistent is None:
self._compute_mapping()
return self.consistent
def get_mapping(self):
if self.consistent is None:
self._compute_mapping()
return self.mapping
class ConstraintSystem(object):
def __init__(self):
self.combinatorial_assignments = []
self.neg_clauses = []
def __str__(self):
combinatorial_assignments = []
for comb_assignment in self.combinatorial_assignments:
disj = " or ".join([str(assig) for assig in comb_assignment])
disj = "(%s)" % disj
combinatorial_assignments.append(disj)
assigs = " and\n".join(combinatorial_assignments)
neg_clauses = [str(clause) for clause in self.neg_clauses]
neg_clauses = " and ".join(neg_clauses)
return assigs + "(" + neg_clauses + ")"
def _all_clauses_satisfiable(self, assignment):
mapping = assignment.get_mapping()
for neg_clause in self.neg_clauses:
clause = neg_clause.apply_mapping(mapping)
if not clause.is_satisfiable():
return False
return True
def _combine_assignments(self, assignments):
new_equalities = []
for a in assignments:
new_equalities.extend(a.equalities)
return Assignment(new_equalities)
def add_assignment(self, assignment):
self.add_assignment_disjunction([assignment])
def add_assignment_disjunction(self, assignments):
self.combinatorial_assignments.append(assignments)
def add_negative_clause(self, negative_clause):
self.neg_clauses.append(negative_clause)
def combine(self, other):
"""Combines two constraint systems to a new system"""
combined = ConstraintSystem()
combined.combinatorial_assignments = (self.combinatorial_assignments +
other.combinatorial_assignments)
combined.neg_clauses = self.neg_clauses + other.neg_clauses
return combined
def copy(self):
other = ConstraintSystem()
other.combinatorial_assignments = list(self.combinatorial_assignments)
other.neg_clauses = list(self.neg_clauses)
return other
def dump(self):
print("AssignmentSystem:")
for comb_assignment in self.combinatorial_assignments:
disj = " or ".join([str(assig) for assig in comb_assignment])
print(" ASS: ", disj)
for neg_clause in self.neg_clauses:
print(" NEG: ", str(neg_clause))
def is_solvable(self):
"""Check whether the combinatorial assignments include at least
one consistent assignment under which the negative clauses
are satisfiable"""
for assignments in itertools.product(*self.combinatorial_assignments):
combined = self._combine_assignments(assignments)
if not combined.is_consistent():
continue
if self._all_clauses_satisfiable(combined):
return True
return False