def test_distribute(): assert distribute_and_over_or(Or(And(A, B), C)) == And(Or(A, C), Or(B, C)) assert distribute_or_over_and(And(A, Or(B, C))) == Or(And(A, B), And(A, C))
def _intervals(self, sym): """Return a list of unique tuples, (a, b, e, i), where a and b are the lower and upper bounds in which the expression e of argument i in self is defined and a < b (when involving numbers) or a <= b when involving symbols. If there are any relationals not involving sym, or any relational cannot be solved for sym, NotImplementedError is raised. The calling routine should have removed such relationals before calling this routine. The evaluated conditions will be returned as ranges. Discontinuous ranges will be returned separately with identical expressions. The first condition that evaluates to True will be returned as the last tuple with a, b = -oo, oo. """ from sympy.solvers.inequalities import _solve_inequality from sympy.logic.boolalg import to_cnf, distribute_or_over_and assert isinstance(self, Piecewise) def _solve_relational(r): if sym not in r.free_symbols: nonsymfail(r) rv = _solve_inequality(r, sym) if isinstance(rv, Relational): free = rv.args[1].free_symbols if rv.args[0] != sym or sym in free: raise NotImplementedError( filldedent(''' Unable to solve relational %s for %s.''' % (r, sym))) if rv.rel_op == '==': # this equality has been affirmed to have the form # Eq(sym, rhs) where rhs is sym-free; it represents # a zero-width interval which will be ignored # whether it is an isolated condition or contained # within an And or an Or rv = S.false elif rv.rel_op == '!=': try: rv = Or(sym < rv.rhs, sym > rv.rhs) except TypeError: # e.g. x != I ==> all real x satisfy rv = S.true elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): rv = S.true return rv def nonsymfail(cond): raise NotImplementedError( filldedent(''' A condition not involving %s appeared: %s''' % (sym, cond))) # make self canonical wrt Relationals reps = dict([(r, _solve_relational(r)) for r in self.atoms(Relational)]) # process args individually so if any evaluate, their position # in the original Piecewise will be known args = [i.xreplace(reps) for i in self.args] # precondition args expr_cond = [] default = idefault = None for i, (expr, cond) in enumerate(args): if cond is S.false: continue elif cond is S.true: default = expr idefault = i break cond = to_cnf(cond) if isinstance(cond, And): cond = distribute_or_over_and(cond) if isinstance(cond, Or): expr_cond.extend([(i, expr, o) for o in cond.args if not isinstance(o, Equality)]) elif cond is not S.false: expr_cond.append((i, expr, cond)) # determine intervals represented by conditions int_expr = [] for iarg, expr, cond in expr_cond: if isinstance(cond, And): lower = S.NegativeInfinity upper = S.Infinity for cond2 in cond.args: if isinstance(cond2, Equality): lower = upper # ignore break elif cond2.lts == sym: upper = Min(cond2.gts, upper) elif cond2.gts == sym: lower = Max(cond2.lts, lower) else: nonsymfail(cond2) # should never get here elif isinstance(cond, Relational): lower, upper = cond.lts, cond.gts # part 1: initialize with givens if cond.lts == sym: # part 1a: expand the side ... lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 elif cond.gts == sym: # part 1a: ... that can be expanded upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 else: nonsymfail(cond) else: raise NotImplementedError('unrecognized condition: %s' % cond) lower, upper = lower, Max(lower, upper) if (lower >= upper) is not S.true: int_expr.append((lower, upper, expr, iarg)) if default is not None: int_expr.append( (S.NegativeInfinity, S.Infinity, default, idefault)) return list(uniq(int_expr))
def _intervals(self, sym): """Return a list of unique tuples, (a, b, e, i), where a and b are the lower and upper bounds in which the expression e of argument i in self is defined and a < b (when involving numbers) or a <= b when involving symbols. If there are any relationals not involving sym, or any relational cannot be solved for sym, NotImplementedError is raised. The calling routine should have removed such relationals before calling this routine. The evaluated conditions will be returned as ranges. Discontinuous ranges will be returned separately with identical expressions. The first condition that evaluates to True will be returned as the last tuple with a, b = -oo, oo. """ from sympy.solvers.inequalities import _solve_inequality from sympy.logic.boolalg import to_cnf, distribute_or_over_and assert isinstance(self, Piecewise) def _solve_relational(r): if sym not in r.free_symbols: nonsymfail(r) rv = _solve_inequality(r, sym) if isinstance(rv, Relational): free = rv.args[1].free_symbols if rv.args[0] != sym or sym in free: raise NotImplementedError(filldedent(''' Unable to solve relational %s for %s.''' % (r, sym))) if rv.rel_op == '==': # this equality has been affirmed to have the form # Eq(sym, rhs) where rhs is sym-free; it represents # a zero-width interval which will be ignored # whether it is an isolated condition or contained # within an And or an Or rv = S.false elif rv.rel_op == '!=': try: rv = Or(sym < rv.rhs, sym > rv.rhs) except TypeError: # e.g. x != I ==> all real x satisfy rv = S.true elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): rv = S.true return rv def nonsymfail(cond): raise NotImplementedError(filldedent(''' A condition not involving %s appeared: %s''' % (sym, cond))) # make self canonical wrt Relationals reps = dict([ (r, _solve_relational(r)) for r in self.atoms(Relational)]) # process args individually so if any evaluate, their position # in the original Piecewise will be known args = [i.xreplace(reps) for i in self.args] # precondition args expr_cond = [] default = idefault = None for i, (expr, cond) in enumerate(args): if cond is S.false: continue elif cond is S.true: default = expr idefault = i break cond = to_cnf(cond) if isinstance(cond, And): cond = distribute_or_over_and(cond) if isinstance(cond, Or): expr_cond.extend( [(i, expr, o) for o in cond.args if not isinstance(o, Equality)]) elif cond is not S.false: expr_cond.append((i, expr, cond)) # determine intervals represented by conditions int_expr = [] for iarg, expr, cond in expr_cond: if isinstance(cond, And): lower = S.NegativeInfinity upper = S.Infinity for cond2 in cond.args: if isinstance(cond2, Equality): lower = upper # ignore break elif cond2.lts == sym: upper = Min(cond2.gts, upper) elif cond2.gts == sym: lower = Max(cond2.lts, lower) else: nonsymfail(cond2) # should never get here elif isinstance(cond, Relational): lower, upper = cond.lts, cond.gts # part 1: initialize with givens if cond.lts == sym: # part 1a: expand the side ... lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 elif cond.gts == sym: # part 1a: ... that can be expanded upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 else: nonsymfail(cond) else: raise NotImplementedError( 'unrecognized condition: %s' % cond) lower, upper = lower, Max(lower, upper) if (lower >= upper) is not S.true: int_expr.append((lower, upper, expr, iarg)) if default is not None: int_expr.append( (S.NegativeInfinity, S.Infinity, default, idefault)) return list(uniq(int_expr))
def _intervals(self, sym): """Return a list of tuples, (a, b, e, i), where a and b are the lower and upper bounds in which the expression e of argument i in self is defined. If there are any relationals not involving sym, or any relational cannot be solved for sym, NotImplementedError is raised. The evaluated conditions will be returned as ranges. Discontinuous ranges will be returned separately with identical expressions and the first condition that evaluates to True will be returned as the last tuple with a, b = -oo, oo. """ from sympy.solvers.inequalities import _solve_inequality from sympy.logic.boolalg import to_cnf, distribute_or_over_and assert isinstance(self, Piecewise) def _solve_relational(r): rv = _solve_inequality(r, sym) if isinstance(rv, Relational) and \ sym in rv.free_symbols: if rv.args[0] != sym: raise NotImplementedError( filldedent(''' Unable to solve relational %s for %s.''' % (r, sym))) if rv.rel_op == '!=': try: rv = Or(sym < rv.rhs, sym > rv.rhs) except TypeError: # e.g. x != I ==> all real x satisfy rv = S.true if rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): rv = S.true return rv def nonsymfail(cond): raise NotImplementedError( filldedent(''' A condition not involving %s appeared: %s''' % (sym, cond))) # make self canonical wrt Relationals reps = dict([(r, _solve_relational(r)) for r in self.atoms(Relational)]) # process args individually so if any evaluate, their position # in the original Piecewise will be known args = [i.xreplace(reps) for i in self.args] # precondition args expr_cond = [] default = idefault = None for i, (expr, cond) in enumerate(args): if cond == False: continue elif cond == True: default = expr idefault = i break elif sym not in cond.free_symbols: nonsymfail(cond) cond = to_cnf(cond) if isinstance(cond, And): cond = distribute_or_over_and(cond) if isinstance(cond, Or): expr_cond.extend([(i, expr, o) for o in cond.args]) elif cond != False: expr_cond.append((i, expr, cond)) # determine intervals represented by conditions int_expr = [] for iarg, expr, cond in expr_cond: if isinstance(cond, Equality): if cond.lhs == sym: v = cond.rhs int_expr.append((v, v, expr.subs(sym, v), iarg)) continue else: nonsymfail(cond) if isinstance(cond, And): lower = S.NegativeInfinity upper = S.Infinity nonsym = False rhs = None for cond2 in cond.args: if isinstance(cond2, Equality): # all relationals were solved and # only sym-dependent ones made it to here assert cond2.lhs == sym assert sym not in cond2.rhs.free_symbols if rhs is None: rhs = cond2.rhs else: same = Eq(cond2.rhs, rhs) if same == False: cond = S.false break elif same != True: raise Undecidable('%s did not evaluate' % same) elif cond2.lts == sym: upper = Min(cond2.gts, upper) elif cond2.gts == sym: lower = Max(cond2.lts, lower) else: # mark but don't fail until later nonsym = cond2 if rhs is not None and cond not in (S.true, S.false): lower = upper = rhs cond = cond.subs(sym, lower) # cond might have evaluated b/c of an Eq if cond is S.true: default = expr continue elif cond is S.false: continue elif nonsym is not False: nonsymfail(nonsym) else: pass # for coverage elif isinstance(cond, Relational): lower, upper = cond.lts, cond.gts # part 1: initialize with givens if cond.lts == sym: # part 1a: expand the side ... lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 elif cond.gts == sym: # part 1a: ... that can be expanded upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 else: nonsymfail(cond) else: raise NotImplementedError('unrecognized condition: %s' % cond) lower, upper = lower, Max(lower, upper) if (lower > upper) is not S.true: int_expr.append((lower, upper, expr, iarg)) if default is not None: int_expr.append( (S.NegativeInfinity, S.Infinity, default, idefault)) return int_expr
def _intervals(self, sym): """Return a list of tuples, (a, b, e, i), where a and b are the lower and upper bounds in which the expression e of argument i in self is defined. If there are any relationals not involving sym, or any relational cannot be solved for sym, NotImplementedError is raised. The evaluated conditions will be returned as ranges. Discontinuous ranges will be returned separately with identical expressions and the first condition that evaluates to True will be returned as the last tuple with a, b = -oo, oo. """ from sympy.solvers.inequalities import _solve_inequality from sympy.logic.boolalg import to_cnf, distribute_or_over_and assert isinstance(self, Piecewise) def _solve_relational(r): rv = _solve_inequality(r, sym) if isinstance(rv, Relational) and \ sym in rv.free_symbols: if rv.args[0] != sym: raise NotImplementedError(filldedent(''' Unable to solve relational %s for %s.''' % (r, sym))) if rv.rel_op == '!=': try: rv = Or(sym < rv.rhs, sym > rv.rhs) except TypeError: # e.g. x != I ==> all real x satisfy rv = S.true if rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): rv = S.true return rv def nonsymfail(cond): raise NotImplementedError(filldedent(''' A condition not involving %s appeared: %s''' % (sym, cond))) # make self canonical wrt Relationals reps = dict( [(r,_solve_relational(r)) for r in self.atoms(Relational)]) # process args individually so if any evaluate, their position # in the original Piecewise will be known args = [i.xreplace(reps) for i in self.args] # precondition args expr_cond = [] default = idefault = None for i, (expr, cond) in enumerate(args): if cond == False: continue elif cond == True: default = expr idefault = i break elif sym not in cond.free_symbols: nonsymfail(cond) cond = to_cnf(cond) if isinstance(cond, And): cond = distribute_or_over_and(cond) if isinstance(cond, Or): expr_cond.extend( [(i, expr, o) for o in cond.args]) elif cond != False: expr_cond.append((i, expr, cond)) # determine intervals represented by conditions int_expr = [] for iarg, expr, cond in expr_cond: if isinstance(cond, Equality): if cond.lhs == sym: v = cond.rhs int_expr.append((v, v, expr.subs(sym, v), iarg)) continue else: nonsymfail(cond) if isinstance(cond, And): lower = S.NegativeInfinity upper = S.Infinity nonsym = False rhs = None for cond2 in cond.args: if isinstance(cond2, Equality): # all relationals were solved and # only sym-dependent ones made it to here assert cond2.lhs == sym assert sym not in cond2.rhs.free_symbols if rhs is None: rhs = cond2.rhs else: same = Eq(cond2.rhs, rhs) if same == False: cond = S.false break elif same != True: raise Undecidable('%s did not evaluate' % same) elif cond2.lts == sym: upper = Min(cond2.gts, upper) elif cond2.gts == sym: lower = Max(cond2.lts, lower) else: # mark but don't fail until later nonsym = cond2 if rhs is not None and cond not in (S.true, S.false): lower = upper = rhs cond = cond.subs(sym, lower) # cond might have evaluated b/c of an Eq if cond is S.true: default = expr continue elif cond is S.false: continue elif nonsym is not False: nonsymfail(nonsym) else: pass # for coverage elif isinstance(cond, Relational): lower, upper = cond.lts, cond.gts # part 1: initialize with givens if cond.lts == sym: # part 1a: expand the side ... lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 elif cond.gts == sym: # part 1a: ... that can be expanded upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 else: nonsymfail(cond) else: raise NotImplementedError( 'unrecognized condition: %s' % cond) lower, upper = lower, Max(lower, upper) if (lower > upper) is not S.true: int_expr.append((lower, upper, expr, iarg)) if default is not None: int_expr.append( (S.NegativeInfinity, S.Infinity, default, idefault)) return int_expr