6c10de3bfd46f3c22f73cb3ef24a5eec4602e6b0
[linpy.git] / pypol / domains.py
1 import ast
2 import functools
3 import re
4
5 from . import islhelper
6
7 from .islhelper import mainctx, libisl, isl_set_basic_sets
8 from .linexprs import Expression
9
10
11 __all__ = [
12 'Domain',
13 'And', 'Or', 'Not',
14 ]
15
16
17 @functools.total_ordering
18 class Domain:
19
20 __slots__ = (
21 '_polyhedra',
22 '_symbols',
23 '_dimension',
24 )
25
26 def __new__(cls, *polyhedra):
27 from .polyhedra import Polyhedron
28 if len(polyhedra) == 1:
29 polyhedron = polyhedra[0]
30 if isinstance(polyhedron, str):
31 return cls.fromstring(polyhedron)
32 elif isinstance(polyhedron, Polyhedron):
33 return polyhedron
34 else:
35 raise TypeError('argument must be a string '
36 'or a Polyhedron instance')
37 else:
38 for polyhedron in polyhedra:
39 if not isinstance(polyhedron, Polyhedron):
40 raise TypeError('arguments must be Polyhedron instances')
41 symbols = cls._xsymbols(polyhedra)
42 islset = cls._toislset(polyhedra, symbols)
43 return cls._fromislset(islset, symbols)
44
45 @classmethod
46 def _xsymbols(cls, iterator):
47 """
48 Return the ordered tuple of symbols present in iterator.
49 """
50 symbols = set()
51 for item in iterator:
52 symbols.update(item.symbols)
53 return tuple(sorted(symbols))
54
55 @property
56 def polyhedra(self):
57 return self._polyhedra
58
59 @property
60 def symbols(self):
61 return self._symbols
62
63 @property
64 def dimension(self):
65 return self._dimension
66
67 def disjoint(self):
68 islset = self._toislset(self.polyhedra, self.symbols)
69 islset = libisl.isl_set_make_disjoint(mainctx, islset)
70 return self._fromislset(islset, self.symbols)
71
72 def isempty(self):
73 islset = self._toislset(self.polyhedra, self.symbols)
74 empty = bool(libisl.isl_set_is_empty(islset))
75 libisl.isl_set_free(islset)
76 return empty
77
78 def __bool__(self):
79 return not self.isempty()
80
81 def isuniverse(self):
82 islset = self._toislset(self.polyhedra, self.symbols)
83 universe = bool(libisl.isl_set_plain_is_universe(islset))
84 libisl.isl_set_free(islset)
85 return universe
86
87 def isbounded(self):
88 islset = self._toislset(self.polyhedra, self.symbols)
89 bounded = bool(libisl.isl_set_is_bounded(islset))
90 libisl.isl_set_free(islset)
91 return bounded
92
93 def __eq__(self, other):
94 symbols = self._xsymbols([self, other])
95 islset1 = self._toislset(self.polyhedra, symbols)
96 islset2 = other._toislset(other.polyhedra, symbols)
97 equal = bool(libisl.isl_set_is_equal(islset1, islset2))
98 libisl.isl_set_free(islset1)
99 libisl.isl_set_free(islset2)
100 return equal
101
102 def isdisjoint(self, other):
103 symbols = self._xsymbols([self, other])
104 islset1 = self._toislset(self.polyhedra, symbols)
105 islset2 = self._toislset(other.polyhedra, symbols)
106 equal = bool(libisl.isl_set_is_disjoint(islset1, islset2))
107 libisl.isl_set_free(islset1)
108 libisl.isl_set_free(islset2)
109 return equal
110
111 def issubset(self, other):
112 symbols = self._xsymbols([self, other])
113 islset1 = self._toislset(self.polyhedra, symbols)
114 islset2 = self._toislset(other.polyhedra, symbols)
115 equal = bool(libisl.isl_set_is_subset(islset1, islset2))
116 libisl.isl_set_free(islset1)
117 libisl.isl_set_free(islset2)
118 return equal
119
120 def __le__(self, other):
121 return self.issubset(other)
122
123 def __lt__(self, other):
124 symbols = self._xsymbols([self, other])
125 islset1 = self._toislset(self.polyhedra, symbols)
126 islset2 = self._toislset(other.polyhedra, symbols)
127 equal = bool(libisl.isl_set_is_strict_subset(islset1, islset2))
128 libisl.isl_set_free(islset1)
129 libisl.isl_set_free(islset2)
130 return equal
131
132 def complement(self):
133 islset = self._toislset(self.polyhedra, self.symbols)
134 islset = libisl.isl_set_complement(islset)
135 return self._fromislset(islset, self.symbols)
136
137 def __invert__(self):
138 return self.complement()
139
140 def simplify(self):
141 #does not change anything in any of the examples
142 #isl seems to do this naturally
143 islset = self._toislset(self.polyhedra, self.symbols)
144 islset = libisl.isl_set_remove_redundancies(islset)
145 return self._fromislset(islset, self.symbols)
146
147 def polyhedral_hull(self):
148 # several types of hull are available
149 # polyhedral seems to be the more appropriate, to be checked
150 from .polyhedra import Polyhedron
151 islset = self._toislset(self.polyhedra, self.symbols)
152 islbset = libisl.isl_set_polyhedral_hull(islset)
153 return Polyhedron._fromislbasicset(islbset, self.symbols)
154
155 def drop_dims(self, dims):
156 # use to remove certain variables use isl_set_drop_constraints_involving_dims instead?
157 from .polyhedra import Polyhedron
158 dims = list(dims)
159 symbols = list(self.symbols)
160 print(symbols)
161 islset = self._toislset(self.polyhedra, self.symbols)
162 for dim in dims:
163 if dim in symbols:
164 first = symbols.index(dim)
165 islbset = libisl.isl_set_project_out(islset, libisl.isl_dim_set, first, 1)
166 symbols.__delitem__(first)
167 else:
168 islbset = libisl.isl_set_project_out(islset, libisl.isl_dim_set, 0, 0)
169 return Polyhedron._fromislset(islbset, symbols)
170
171 def sample(self):
172 from .polyhedra import Polyhedron
173 islset = self._toislset(self.polyhedra, self.symbols)
174 islbset = libisl.isl_set_sample(islset)
175 return Polyhedron._fromislbasicset(islbset, self.symbols)
176
177 def intersection(self, *others):
178 if len(others) == 0:
179 return self
180 symbols = self._xsymbols((self,) + others)
181 islset1 = self._toislset(self.polyhedra, symbols)
182 for other in others:
183 islset2 = other._toislset(other.polyhedra, symbols)
184 islset1 = libisl.isl_set_intersect(islset1, islset2)
185 return self._fromislset(islset1, symbols)
186
187 def __and__(self, other):
188 return self.intersection(other)
189
190 def union(self, *others):
191 if len(others) == 0:
192 return self
193 symbols = self._xsymbols((self,) + others)
194 islset1 = self._toislset(self.polyhedra, symbols)
195 for other in others:
196 islset2 = other._toislset(other.polyhedra, symbols)
197 islset1 = libisl.isl_set_union(islset1, islset2)
198 return self._fromislset(islset1, symbols)
199
200 def __or__(self, other):
201 return self.union(other)
202
203 def __add__(self, other):
204 return self.union(other)
205
206 def difference(self, other):
207 symbols = self._xsymbols([self, other])
208 islset1 = self._toislset(self.polyhedra, symbols)
209 islset2 = other._toislset(other.polyhedra, symbols)
210 islset = libisl.isl_set_subtract(islset1, islset2)
211 return self._fromislset(islset, symbols)
212
213 def __sub__(self, other):
214 return self.difference(other)
215
216 def lexmin(self):
217 islset = self._toislset(self.polyhedra, self.symbols)
218 islset = libisl.isl_set_lexmin(islset)
219 return self._fromislset(islset, self.symbols)
220
221 def lexmax(self):
222 islset = self._toislset(self.polyhedra, self.symbols)
223 islset = libisl.isl_set_lexmax(islset)
224 return self._fromislset(islset, self.symbols)
225
226 @classmethod
227 def _fromislset(cls, islset, symbols):
228 from .polyhedra import Polyhedron
229 islset = libisl.isl_set_remove_divs(islset)
230 islbsets = isl_set_basic_sets(islset)
231 libisl.isl_set_free(islset)
232 polyhedra = []
233 for islbset in islbsets:
234 polyhedron = Polyhedron._fromislbasicset(islbset, symbols)
235 polyhedra.append(polyhedron)
236 if len(polyhedra) == 0:
237 from .polyhedra import Empty
238 return Empty
239 elif len(polyhedra) == 1:
240 return polyhedra[0]
241 else:
242 self = object().__new__(Domain)
243 self._polyhedra = tuple(polyhedra)
244 self._symbols = cls._xsymbols(polyhedra)
245 self._dimension = len(self._symbols)
246 return self
247
248 def _toislset(cls, polyhedra, symbols):
249 polyhedron = polyhedra[0]
250 islbset = polyhedron._toislbasicset(polyhedron.equalities,
251 polyhedron.inequalities, symbols)
252 islset1 = libisl.isl_set_from_basic_set(islbset)
253 for polyhedron in polyhedra[1:]:
254 islbset = polyhedron._toislbasicset(polyhedron.equalities,
255 polyhedron.inequalities, symbols)
256 islset2 = libisl.isl_set_from_basic_set(islbset)
257 islset1 = libisl.isl_set_union(islset1, islset2)
258 return islset1
259
260 @classmethod
261 def _fromast(cls, node):
262 from .polyhedra import Polyhedron
263 if isinstance(node, ast.Module) and len(node.body) == 1:
264 return cls._fromast(node.body[0])
265 elif isinstance(node, ast.Expr):
266 return cls._fromast(node.value)
267 elif isinstance(node, ast.UnaryOp):
268 domain = cls._fromast(node.operand)
269 if isinstance(node.operand, ast.invert):
270 return Not(domain)
271 elif isinstance(node, ast.BinOp):
272 domain1 = cls._fromast(node.left)
273 domain2 = cls._fromast(node.right)
274 if isinstance(node.op, ast.BitAnd):
275 return And(domain1, domain2)
276 elif isinstance(node.op, ast.BitOr):
277 return Or(domain1, domain2)
278 elif isinstance(node, ast.Compare):
279 equalities = []
280 inequalities = []
281 left = Expression._fromast(node.left)
282 for i in range(len(node.ops)):
283 op = node.ops[i]
284 right = Expression._fromast(node.comparators[i])
285 if isinstance(op, ast.Lt):
286 inequalities.append(right - left - 1)
287 elif isinstance(op, ast.LtE):
288 inequalities.append(right - left)
289 elif isinstance(op, ast.Eq):
290 equalities.append(left - right)
291 elif isinstance(op, ast.GtE):
292 inequalities.append(left - right)
293 elif isinstance(op, ast.Gt):
294 inequalities.append(left - right - 1)
295 else:
296 break
297 left = right
298 else:
299 return Polyhedron(equalities, inequalities)
300 raise SyntaxError('invalid syntax')
301
302 _RE_BRACES = re.compile(r'^\{\s*|\s*\}$')
303 _RE_EQ = re.compile(r'([^<=>])=([^<=>])')
304 _RE_AND = re.compile(r'\band\b|,|&&|/\\|∧|∩')
305 _RE_OR = re.compile(r'\bor\b|;|\|\||\\/|∨|∪')
306 _RE_NOT = re.compile(r'\bnot\b|!|¬')
307 _RE_NUM_VAR = Expression._RE_NUM_VAR
308 _RE_OPERATORS = re.compile(r'(&|\||~)')
309
310 @classmethod
311 def fromstring(cls, string):
312 # remove curly brackets
313 string = cls._RE_BRACES.sub(r'', string)
314 # replace '=' by '=='
315 string = cls._RE_EQ.sub(r'\1==\2', string)
316 # replace 'and', 'or', 'not'
317 string = cls._RE_AND.sub(r' & ', string)
318 string = cls._RE_OR.sub(r' | ', string)
319 string = cls._RE_NOT.sub(r' ~', string)
320 # add implicit multiplication operators, e.g. '5x' -> '5*x'
321 string = cls._RE_NUM_VAR.sub(r'\1*\2', string)
322 # add parentheses to force precedence
323 tokens = cls._RE_OPERATORS.split(string)
324 for i, token in enumerate(tokens):
325 if i % 2 == 0:
326 token = '({})'.format(token)
327 tokens[i] = token
328 string = ''.join(tokens)
329 tree = ast.parse(string, 'eval')
330 return cls._fromast(tree)
331
332 def __repr__(self):
333 assert len(self.polyhedra) >= 2
334 strings = [repr(polyhedron) for polyhedron in self.polyhedra]
335 return 'Or({})'.format(', '.join(strings))
336
337 @classmethod
338 def fromsympy(cls, expr):
339 raise NotImplementedError
340
341 def tosympy(self):
342 raise NotImplementedError
343
344 def And(*domains):
345 if len(domains) == 0:
346 from .polyhedra import Universe
347 return Universe
348 else:
349 return domains[0].intersection(*domains[1:])
350
351 def Or(*domains):
352 if len(domains) == 0:
353 from .polyhedra import Empty
354 return Empty
355 else:
356 return domains[0].union(*domains[1:])
357
358 def Not(domain):
359 return ~domain