+ self._inequalities.append(constraint)
+ self._inequalities = tuple(self._inequalities)
+ self._constraints = self._equalities + self._inequalities
+ self._symbols = set()
+ for constraint in self._constraints:
+ self.symbols.update(constraint.symbols)
+ self._symbols = tuple(sorted(self._symbols))
+ return self
+
+ @classmethod
+ def fromstring(cls, string):
+ string = string.strip()
+ string = re.sub(r'^\{\s*|\s*\}$', '', string)
+ string = re.sub(r'([^<=>])=([^<=>])', r'\1==\2', string)
+ string = re.sub(r'(\d+|\))\s*([^\W\d_]\w*|\()', r'\1*\2', string)
+ equalities = []
+ inequalities = []
+ for cstr in re.split(r',|;|and|&&|/\\|∧', string, flags=re.I):
+ tree = ast.parse(cstr.strip(), 'eval')
+ if not isinstance(tree, ast.Module) or len(tree.body) != 1:
+ raise SyntaxError('invalid syntax')
+ node = tree.body[0]
+ if not isinstance(node, ast.Expr):
+ raise SyntaxError('invalid syntax')
+ node = node.value
+ if not isinstance(node, ast.Compare):
+ raise SyntaxError('invalid syntax')
+ left = Expression._fromast(node.left)
+ for i in range(len(node.ops)):
+ op = node.ops[i]
+ right = Expression._fromast(node.comparators[i])
+ if isinstance(op, ast.Lt):
+ inequalities.append(right - left - 1)
+ elif isinstance(op, ast.LtE):
+ inequalities.append(right - left)
+ elif isinstance(op, ast.Eq):
+ equalities.append(left - right)
+ elif isinstance(op, ast.GtE):
+ inequalities.append(left - right)
+ elif isinstance(op, ast.Gt):
+ inequalities.append(left - right - 1)
+ else:
+ raise SyntaxError('invalid syntax')
+ left = right
+ return cls(equalities, inequalities)
+