1 # Copyright 2014 MINES ParisTech
3 # This file is part of LinPy.
5 # LinPy is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # LinPy is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with LinPy. If not, see <http://www.gnu.org/licenses/>.
23 from fractions
import Fraction
25 from . import islhelper
26 from .islhelper
import mainctx
, libisl
27 from .linexprs
import LinExpr
, Symbol
28 from .geometry
import GeometricObject
, Point
, Vector
37 @functools.total_ordering
38 class Domain(GeometricObject
):
40 A domain is a union of polyhedra. Unlike polyhedra, domains allow exact
41 computation of union, subtraction and complementary operations.
43 A domain with a unique polyhedron is automatically subclassed as a
53 def __new__(cls
, *polyhedra
):
55 Return a domain from a sequence of polyhedra.
57 >>> square1 = Polyhedron('0 <= x <= 2, 0 <= y <= 2')
58 >>> square2 = Polyhedron('1 <= x <= 3, 1 <= y <= 3')
59 >>> dom = Domain(square1, square2)
61 Or(And(x <= 2, 0 <= x, y <= 2, 0 <= y),
62 And(x <= 3, 1 <= x, y <= 3, 1 <= y))
64 It is also possible to build domains from polyhedra using arithmetic
65 operators Domain.__or__(), Domain.__invert__() or functions Or() and
66 Not(), using one of the following instructions:
68 >>> dom = square1 | square2
69 >>> dom = Or(square1, square2)
71 Alternatively, a domain can be built from a string:
73 >>> dom = Domain('0 <= x <= 2, 0 <= y <= 2; 1 <= x <= 3, 1 <= y <= 3')
75 Finally, a domain can be built from a GeometricObject instance, calling
76 the GeometricObject.asdomain() method.
78 from .polyhedra
import Polyhedron
79 if len(polyhedra
) == 1:
80 argument
= polyhedra
[0]
81 if isinstance(argument
, str):
82 return cls
.fromstring(argument
)
83 elif isinstance(argument
, GeometricObject
):
84 return argument
.aspolyhedron()
86 raise TypeError('argument must be a string '
87 'or a GeometricObject instance')
89 for polyhedron
in polyhedra
:
90 if not isinstance(polyhedron
, Polyhedron
):
91 raise TypeError('arguments must be Polyhedron instances')
92 symbols
= cls
._xsymbols
(polyhedra
)
93 islset
= cls
._toislset
(polyhedra
, symbols
)
94 return cls
._fromislset
(islset
, symbols
)
97 def _xsymbols(cls
, iterator
):
99 Return the ordered tuple of symbols present in iterator.
102 for item
in iterator
:
103 symbols
.update(item
.symbols
)
104 return tuple(sorted(symbols
, key
=Symbol
.sortkey
))
109 The tuple of polyhedra present in the domain.
111 return self
._polyhedra
116 The tuple of symbols present in the domain equations, sorted according
124 The dimension of the domain, i.e. the number of symbols present in it.
126 return self
._dimension
130 Return True if the domain is empty, that is, equal to Empty.
132 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
133 empty
= bool(libisl
.isl_set_is_empty(islset
))
134 libisl
.isl_set_free(islset
)
139 Return True if the domain is non-empty.
141 return not self
.isempty()
143 def isuniverse(self
):
145 Return True if the domain is universal, that is, equal to Universe.
147 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
148 universe
= bool(libisl
.isl_set_plain_is_universe(islset
))
149 libisl
.isl_set_free(islset
)
154 Return True if the domain is bounded.
156 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
157 bounded
= bool(libisl
.isl_set_is_bounded(islset
))
158 libisl
.isl_set_free(islset
)
161 def __eq__(self
, other
):
163 Return True if two domains are equal.
165 if isinstance(other
, Domain
):
166 symbols
= self
._xsymbols
([self
, other
])
167 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
168 islset2
= other
._toislset
(other
.polyhedra
, symbols
)
169 equal
= bool(libisl
.isl_set_is_equal(islset1
, islset2
))
170 libisl
.isl_set_free(islset1
)
171 libisl
.isl_set_free(islset2
)
173 return NotImplemented
175 def isdisjoint(self
, other
):
177 Return True if two domains have a null intersection.
179 if not isinstance(other
, Domain
):
180 raise TypeError('other must be a Domain instance')
181 symbols
= self
._xsymbols
([self
, other
])
182 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
183 islset2
= self
._toislset
(other
.polyhedra
, symbols
)
184 equal
= bool(libisl
.isl_set_is_disjoint(islset1
, islset2
))
185 libisl
.isl_set_free(islset1
)
186 libisl
.isl_set_free(islset2
)
189 def issubset(self
, other
):
191 Report whether another domain contains the domain.
195 def __le__(self
, other
):
196 if isinstance(other
, Domain
):
197 symbols
= self
._xsymbols
([self
, other
])
198 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
199 islset2
= self
._toislset
(other
.polyhedra
, symbols
)
200 equal
= bool(libisl
.isl_set_is_subset(islset1
, islset2
))
201 libisl
.isl_set_free(islset1
)
202 libisl
.isl_set_free(islset2
)
204 return NotImplemented
205 __le__
.__doc
__ = issubset
.__doc
__
207 def __lt__(self
, other
):
209 Report whether another domain is contained within the domain.
211 if isinstance(other
, Domain
):
212 symbols
= self
._xsymbols
([self
, other
])
213 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
214 islset2
= self
._toislset
(other
.polyhedra
, symbols
)
215 equal
= bool(libisl
.isl_set_is_strict_subset(islset1
, islset2
))
216 libisl
.isl_set_free(islset1
)
217 libisl
.isl_set_free(islset2
)
219 return NotImplemented
221 def complement(self
):
223 Return the complementary domain of the domain.
225 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
226 islset
= libisl
.isl_set_complement(islset
)
227 return self
._fromislset
(islset
, self
.symbols
)
229 def __invert__(self
):
230 return self
.complement()
231 __invert__
.__doc
__ = complement
.__doc
__
233 def make_disjoint(self
):
235 Return an equivalent domain, whose polyhedra are disjoint.
237 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
238 islset
= libisl
.isl_set_make_disjoint(mainctx
, islset
)
239 return self
._fromislset
(islset
, self
.symbols
)
243 Simplify the representation of the domain by trying to combine pairs of
244 polyhedra into a single polyhedron, and return the resulting domain.
246 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
247 islset
= libisl
.isl_set_coalesce(islset
)
248 return self
._fromislset
(islset
, self
.symbols
)
250 def detect_equalities(self
):
252 Simplify the representation of the domain by detecting implicit
253 equalities, and return the resulting domain.
255 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
256 islset
= libisl
.isl_set_detect_equalities(islset
)
257 return self
._fromislset
(islset
, self
.symbols
)
259 def remove_redundancies(self
):
261 Remove redundant constraints in the domain, and return the resulting
264 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
265 islset
= libisl
.isl_set_remove_redundancies(islset
)
266 return self
._fromislset
(islset
, self
.symbols
)
268 def aspolyhedron(self
):
269 from .polyhedra
import Polyhedron
270 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
271 islbset
= libisl
.isl_set_polyhedral_hull(islset
)
272 return Polyhedron
._fromislbasicset
(islbset
, self
.symbols
)
277 def project(self
, symbols
):
279 Project out the sequence of symbols given in arguments, and return the
282 symbols
= list(symbols
)
283 for symbol
in symbols
:
284 if not isinstance(symbol
, Symbol
):
285 raise TypeError('symbols must be Symbol instances')
286 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
288 for index
, symbol
in reversed(list(enumerate(self
.symbols
))):
289 if symbol
in symbols
:
292 islset
= libisl
.isl_set_project_out(islset
,
293 libisl
.isl_dim_set
, index
+ 1, n
)
296 islset
= libisl
.isl_set_project_out(islset
, libisl
.isl_dim_set
, 0, n
)
297 symbols
= [symbol
for symbol
in self
.symbols
if symbol
not in symbols
]
298 return Domain
._fromislset
(islset
, symbols
)
302 Return a sample of the domain, as an integer instance of Point. If the
303 domain is empty, a ValueError exception is raised.
305 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
306 islpoint
= libisl
.isl_set_sample_point(islset
)
307 if bool(libisl
.isl_point_is_void(islpoint
)):
308 libisl
.isl_point_free(islpoint
)
309 raise ValueError('domain must be non-empty')
311 for index
, symbol
in enumerate(self
.symbols
):
312 coordinate
= libisl
.isl_point_get_coordinate_val(islpoint
,
313 libisl
.isl_dim_set
, index
)
314 coordinate
= islhelper
.isl_val_to_int(coordinate
)
315 point
[symbol
] = coordinate
316 libisl
.isl_point_free(islpoint
)
319 def intersection(self
, *others
):
321 Return the intersection of two or more domains as a new domain. As an
322 alternative, function And() can be used.
329 def __and__(self
, other
):
330 if isinstance(other
, Domain
):
331 symbols
= self
._xsymbols
([self
, other
])
332 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
333 islset2
= other
._toislset
(other
.polyhedra
, symbols
)
334 islset
= libisl
.isl_set_intersect(islset1
, islset2
)
335 return self
._fromislset
(islset
, symbols
)
336 return NotImplemented
337 __and__
.__doc
__ = intersection
.__doc
__
339 def union(self
, *others
):
341 Return the union of two or more domains as a new domain. As an
342 alternative, function Or() can be used.
349 def __or__(self
, other
):
350 if isinstance(other
, Domain
):
351 symbols
= self
._xsymbols
([self
, other
])
352 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
353 islset2
= other
._toislset
(other
.polyhedra
, symbols
)
354 islset
= libisl
.isl_set_union(islset1
, islset2
)
355 return self
._fromislset
(islset
, symbols
)
356 return NotImplemented
357 __or__
.__doc
__ = union
.__doc
__
359 def __add__(self
, other
):
361 __add__
.__doc
__ = union
.__doc
__
363 def difference(self
, other
):
365 Return the difference of two domains as a new domain.
369 def __sub__(self
, other
):
370 if isinstance(other
, Domain
):
371 symbols
= self
._xsymbols
([self
, other
])
372 islset1
= self
._toislset
(self
.polyhedra
, symbols
)
373 islset2
= other
._toislset
(other
.polyhedra
, symbols
)
374 islset
= libisl
.isl_set_subtract(islset1
, islset2
)
375 return self
._fromislset
(islset
, symbols
)
376 return NotImplemented
377 __sub__
.__doc
__ = difference
.__doc
__
381 Return the lexicographic minimum of the elements in the domain.
383 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
384 islset
= libisl
.isl_set_lexmin(islset
)
385 return self
._fromislset
(islset
, self
.symbols
)
389 Return the lexicographic maximum of the elements in the domain.
391 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
392 islset
= libisl
.isl_set_lexmax(islset
)
393 return self
._fromislset
(islset
, self
.symbols
)
395 if islhelper
.isl_version
>= '0.13':
396 _RE_COORDINATE
= re
.compile(r
'\((?P<num>\-?\d+)\)(/(?P<den>\d+))?')
398 _RE_COORDINATE
= None
402 Return the vertices of the domain, as a list of rational instances of
405 from .polyhedra
import Polyhedron
406 if not self
.isbounded():
407 raise ValueError('domain must be bounded')
408 islbset
= self
._toislbasicset
(self
.equalities
, self
.inequalities
,
410 vertices
= libisl
.isl_basic_set_compute_vertices(islbset
);
411 vertices
= islhelper
.isl_vertices_vertices(vertices
)
413 for vertex
in vertices
:
414 expr
= libisl
.isl_vertex_get_expr(vertex
)
416 if self
._RE
_COORDINATE
is None:
417 constraints
= islhelper
.isl_basic_set_constraints(expr
)
418 for constraint
in constraints
:
419 constant
= libisl
.isl_constraint_get_constant_val(constraint
)
420 constant
= islhelper
.isl_val_to_int(constant
)
421 for index
, symbol
in enumerate(self
.symbols
):
422 coefficient
= libisl
.isl_constraint_get_coefficient_val(constraint
,
423 libisl
.isl_dim_set
, index
)
424 coefficient
= islhelper
.isl_val_to_int(coefficient
)
426 coordinate
= -Fraction(constant
, coefficient
)
427 coordinates
.append((symbol
, coordinate
))
429 string
= islhelper
.isl_multi_aff_to_str(expr
)
430 matches
= self
._RE
_COORDINATE
.finditer(string
)
431 for symbol
, match
in zip(self
.symbols
, matches
):
432 numerator
= int(match
.group('num'))
433 denominator
= match
.group('den')
434 denominator
= 1 if denominator
is None else int(denominator
)
435 coordinate
= Fraction(numerator
, denominator
)
436 coordinates
.append((symbol
, coordinate
))
437 points
.append(Point(coordinates
))
442 Return the integer points of a bounded domain, as a list of integer
443 instances of Point. If the domain is not bounded, a ValueError exception
446 if not self
.isbounded():
447 raise ValueError('domain must be bounded')
448 from .polyhedra
import Universe
, Eq
449 islset
= self
._toislset
(self
.polyhedra
, self
.symbols
)
450 islpoints
= islhelper
.isl_set_points(islset
)
452 for islpoint
in islpoints
:
454 for index
, symbol
in enumerate(self
.symbols
):
455 coordinate
= libisl
.isl_point_get_coordinate_val(islpoint
,
456 libisl
.isl_dim_set
, index
)
457 coordinate
= islhelper
.isl_val_to_int(coordinate
)
458 coordinates
[symbol
] = coordinate
459 points
.append(Point(coordinates
))
462 def __contains__(self
, point
):
464 Return True if the point if contained within the domain.
466 for polyhedron
in self
.polyhedra
:
467 if point
in polyhedron
:
472 def _polygon_inner_point(cls
, points
):
473 symbols
= points
[0].symbols
474 coordinates
= {symbol
: 0 for symbol
in symbols
}
476 for symbol
, coordinate
in point
.coordinates():
477 coordinates
[symbol
] += coordinate
478 for symbol
in symbols
:
479 coordinates
[symbol
] /= len(points
)
480 return Point(coordinates
)
483 def _sort_polygon_2d(cls
, points
):
486 o
= cls
._polygon
_inner
_point
(points
)
490 dx
, dy
= (coordinate
for symbol
, coordinate
in om
.coordinates())
491 angle
= math
.atan2(dy
, dx
)
493 return sorted(points
, key
=angles
.get
)
496 def _sort_polygon_3d(cls
, points
):
499 o
= cls
._polygon
_inner
_point
(points
)
510 raise ValueError('degenerate polygon')
514 normprod
= norm_oa
* om
.norm()
515 cosinus
= max(oa
.dot(om
) / normprod
, -1.)
516 sinus
= u
.dot(oa
.cross(om
)) / normprod
517 angle
= math
.acos(cosinus
)
518 angle
= math
.copysign(angle
, sinus
)
520 return sorted(points
, key
=angles
.get
)
524 Return the list of faces of a bounded domain. Each face is represented
525 by a list of vertices, in the form of rational instances of Point. If
526 the domain is not bounded, a ValueError exception is raised.
529 for polyhedron
in self
.polyhedra
:
530 vertices
= polyhedron
.vertices()
531 for constraint
in polyhedron
.constraints
:
533 for vertex
in vertices
:
534 if constraint
.subs(vertex
.coordinates()) == 0:
540 def _plot_2d(self
, plot
=None, **kwargs
):
541 import matplotlib
.pyplot
as plt
542 from matplotlib
.patches
import Polygon
545 plot
= fig
.add_subplot(1, 1, 1)
546 xmin
, xmax
= plot
.get_xlim()
547 ymin
, ymax
= plot
.get_ylim()
548 for polyhedron
in self
.polyhedra
:
549 vertices
= polyhedron
._sort
_polygon
_2d
(polyhedron
.vertices())
550 xys
= [tuple(vertex
.values()) for vertex
in vertices
]
552 xmin
, xmax
= min(xmin
, float(min(xs
))), max(xmax
, float(max(xs
)))
553 ymin
, ymax
= min(ymin
, float(min(ys
))), max(ymax
, float(max(ys
)))
554 plot
.add_patch(Polygon(xys
, closed
=True, **kwargs
))
555 plot
.set_xlim(xmin
, xmax
)
556 plot
.set_ylim(ymin
, ymax
)
559 def _plot_3d(self
, plot
=None, **kwargs
):
560 import matplotlib
.pyplot
as plt
561 from mpl_toolkits
.mplot3d
import Axes3D
562 from mpl_toolkits
.mplot3d
.art3d
import Poly3DCollection
568 xmin
, xmax
= axes
.get_xlim()
569 ymin
, ymax
= axes
.get_ylim()
570 zmin
, zmax
= axes
.get_zlim()
572 for vertices
in self
.faces():
573 vertices
= self
._sort
_polygon
_3d
(vertices
)
574 vertices
.append(vertices
[0])
575 face_xyzs
= [tuple(vertex
.values()) for vertex
in vertices
]
576 xs
, ys
, zs
= zip(*face_xyzs
)
577 xmin
, xmax
= min(xmin
, float(min(xs
))), max(xmax
, float(max(xs
)))
578 ymin
, ymax
= min(ymin
, float(min(ys
))), max(ymax
, float(max(ys
)))
579 zmin
, zmax
= min(zmin
, float(min(zs
))), max(zmax
, float(max(zs
)))
580 poly_xyzs
.append(face_xyzs
)
581 collection
= Poly3DCollection(poly_xyzs
, **kwargs
)
582 axes
.add_collection3d(collection
)
583 axes
.set_xlim(xmin
, xmax
)
584 axes
.set_ylim(ymin
, ymax
)
585 axes
.set_zlim(zmin
, zmax
)
588 def plot(self
, plot
=None, **kwargs
):
590 Plot a 2D or 3D domain using matplotlib. Draw it to the current plot
591 object if present, otherwise create a new one. options are keyword
592 arguments passed to the matplotlib drawing functions, they can be used
593 to set the drawing color for example. Raise ValueError is the domain is
596 if not self
.isbounded():
597 raise ValueError('domain must be bounded')
598 elif self
.dimension
== 2:
599 return self
._plot
_2d
(plot
=plot
, **kwargs
)
600 elif self
.dimension
== 3:
601 return self
._plot
_3d
(plot
=plot
, **kwargs
)
603 raise ValueError('domain must be 2 or 3-dimensional')
605 def subs(self
, symbol
, expression
=None):
607 Substitute the given symbol by an expression in the domain constraints.
608 To perform multiple substitutions at once, pass a sequence or a
609 dictionary of (old, new) pairs to subs. The syntax of this function is
610 similar to LinExpr.subs().
612 polyhedra
= [polyhedron
.subs(symbol
, expression
)
613 for polyhedron
in self
.polyhedra
]
614 return Domain(*polyhedra
)
617 def _fromislset(cls
, islset
, symbols
):
618 from .polyhedra
import Polyhedron
619 islset
= libisl
.isl_set_remove_divs(islset
)
620 islbsets
= islhelper
.isl_set_basic_sets(islset
)
621 libisl
.isl_set_free(islset
)
623 for islbset
in islbsets
:
624 polyhedron
= Polyhedron
._fromislbasicset
(islbset
, symbols
)
625 polyhedra
.append(polyhedron
)
626 if len(polyhedra
) == 0:
627 from .polyhedra
import Empty
629 elif len(polyhedra
) == 1:
632 self
= object().__new
__(Domain
)
633 self
._polyhedra
= tuple(polyhedra
)
634 self
._symbols
= cls
._xsymbols
(polyhedra
)
635 self
._dimension
= len(self
._symbols
)
639 def _toislset(cls
, polyhedra
, symbols
):
640 polyhedron
= polyhedra
[0]
641 islbset
= polyhedron
._toislbasicset
(polyhedron
.equalities
,
642 polyhedron
.inequalities
, symbols
)
643 islset1
= libisl
.isl_set_from_basic_set(islbset
)
644 for polyhedron
in polyhedra
[1:]:
645 islbset
= polyhedron
._toislbasicset
(polyhedron
.equalities
,
646 polyhedron
.inequalities
, symbols
)
647 islset2
= libisl
.isl_set_from_basic_set(islbset
)
648 islset1
= libisl
.isl_set_union(islset1
, islset2
)
652 def _fromast(cls
, node
):
653 from .polyhedra
import Polyhedron
654 if isinstance(node
, ast
.Module
) and len(node
.body
) == 1:
655 return cls
._fromast
(node
.body
[0])
656 elif isinstance(node
, ast
.Expr
):
657 return cls
._fromast
(node
.value
)
658 elif isinstance(node
, ast
.UnaryOp
):
659 domain
= cls
._fromast
(node
.operand
)
660 if isinstance(node
.operand
, ast
.invert
):
662 elif isinstance(node
, ast
.BinOp
):
663 domain1
= cls
._fromast
(node
.left
)
664 domain2
= cls
._fromast
(node
.right
)
665 if isinstance(node
.op
, ast
.BitAnd
):
666 return And(domain1
, domain2
)
667 elif isinstance(node
.op
, ast
.BitOr
):
668 return Or(domain1
, domain2
)
669 elif isinstance(node
, ast
.Compare
):
672 left
= LinExpr
._fromast
(node
.left
)
673 for i
in range(len(node
.ops
)):
675 right
= LinExpr
._fromast
(node
.comparators
[i
])
676 if isinstance(op
, ast
.Lt
):
677 inequalities
.append(right
- left
- 1)
678 elif isinstance(op
, ast
.LtE
):
679 inequalities
.append(right
- left
)
680 elif isinstance(op
, ast
.Eq
):
681 equalities
.append(left
- right
)
682 elif isinstance(op
, ast
.GtE
):
683 inequalities
.append(left
- right
)
684 elif isinstance(op
, ast
.Gt
):
685 inequalities
.append(left
- right
- 1)
690 return Polyhedron(equalities
, inequalities
)
691 raise SyntaxError('invalid syntax')
693 _RE_BRACES
= re
.compile(r
'^\{\s*|\s*\}$')
694 _RE_EQ
= re
.compile(r
'([^<=>])=([^<=>])')
695 _RE_AND
= re
.compile(r
'\band\b|,|&&|/\\|∧|∩')
696 _RE_OR
= re
.compile(r
'\bor\b|;|\|\||\\/|∨|∪')
697 _RE_NOT
= re
.compile(r
'\bnot\b|!|¬')
698 _RE_NUM_VAR
= LinExpr
._RE
_NUM
_VAR
699 _RE_OPERATORS
= re
.compile(r
'(&|\||~)')
702 def fromstring(cls
, string
):
704 Create a domain from a string. Raise SyntaxError if the string is not
707 # Remove curly brackets.
708 string
= cls
._RE
_BRACES
.sub(r
'', string
)
709 # Replace '=' by '=='.
710 string
= cls
._RE
_EQ
.sub(r
'\1==\2', string
)
711 # Replace 'and', 'or', 'not'.
712 string
= cls
._RE
_AND
.sub(r
' & ', string
)
713 string
= cls
._RE
_OR
.sub(r
' | ', string
)
714 string
= cls
._RE
_NOT
.sub(r
' ~', string
)
715 # Add implicit multiplication operators, e.g. '5x' -> '5*x'.
716 string
= cls
._RE
_NUM
_VAR
.sub(r
'\1*\2', string
)
717 # Add parentheses to force precedence.
718 tokens
= cls
._RE
_OPERATORS
.split(string
)
719 for i
, token
in enumerate(tokens
):
721 token
= '({})'.format(token
)
723 string
= ''.join(tokens
)
724 tree
= ast
.parse(string
, 'eval')
725 return cls
._fromast
(tree
)
728 assert len(self
.polyhedra
) >= 2
729 strings
= [repr(polyhedron
) for polyhedron
in self
.polyhedra
]
730 return 'Or({})'.format(', '.join(strings
))
732 def _repr_latex_(self
):
734 for polyhedron
in self
.polyhedra
:
735 strings
.append('({})'.format(polyhedron
._repr
_latex
_().strip('$')))
736 return '${}$'.format(' \\vee '.join(strings
))
739 def fromsympy(cls
, expr
):
741 Create a domain from a SymPy expression.
744 from .polyhedra
import Lt
, Le
, Eq
, Ne
, Ge
, Gt
746 sympy
.And
: And
, sympy
.Or
: Or
, sympy
.Not
: Not
,
747 sympy
.Lt
: Lt
, sympy
.Le
: Le
,
748 sympy
.Eq
: Eq
, sympy
.Ne
: Ne
,
749 sympy
.Ge
: Ge
, sympy
.Gt
: Gt
,
751 if expr
.func
in funcmap
:
752 args
= [Domain
.fromsympy(arg
) for arg
in expr
.args
]
753 return funcmap
[expr
.func
](*args
)
754 elif isinstance(expr
, sympy
.Expr
):
755 return LinExpr
.fromsympy(expr
)
756 raise ValueError('non-domain expression: {!r}'.format(expr
))
760 Convert the domain to a SymPy expression.
763 polyhedra
= [polyhedron
.tosympy() for polyhedron
in polyhedra
]
764 return sympy
.Or(*polyhedra
)
769 Create the intersection domain of the domains given in arguments.
771 if len(domains
) == 0:
772 from .polyhedra
import Universe
775 return domains
[0].intersection(*domains
[1:])
779 Create the union domain of the domains given in arguments.
781 if len(domains
) == 0:
782 from .polyhedra
import Empty
785 return domains
[0].union(*domains
[1:])
789 Create the complementary domain of the domain given in argument.