def quantity_simplify(expr): if expr.is_Atom: return expr if not expr.is_Mul: return expr.func(*map(quantity_simplify, expr.args)) if expr.has(Prefix): coeff, args = expr.as_coeff_mul(Prefix) args = list(args) for arg in args: if isinstance(arg, Pow): coeff = coeff * (arg.base.scale_factor ** arg.exp) else: coeff = coeff * arg.scale_factor expr = coeff coeff, args = expr.as_coeff_mul(Quantity) args_pow = [arg.as_base_exp() for arg in args] quantity_pow, other_pow = sift(args_pow, lambda x: isinstance(x[0], Quantity), binary=True) quantity_pow_by_dim = sift(quantity_pow, lambda x: x[0].dimension) # Just pick the first quantity: ref_quantities = [i[0][0] for i in quantity_pow_by_dim.values()] new_quantities = [ Mul.fromiter( (quantity*i.scale_factor/quantity.scale_factor)**p for i, p in v) if len(v) > 1 else v[0][0]**v[0][1] for quantity, (k, v) in zip(ref_quantities, quantity_pow_by_dim.items())] return coeff*Mul.fromiter(other_pow)*Mul.fromiter(new_quantities)
def quantity_simplify(expr): if expr.is_Atom: return expr if not expr.is_Mul: return expr.func(*map(quantity_simplify, expr.args)) if expr.has(Prefix): coeff, args = expr.as_coeff_mul(Prefix) args = list(args) for arg in args: if isinstance(arg, Pow): coeff = coeff * (arg.base.scale_factor**arg.exp) else: coeff = coeff * arg.scale_factor expr = coeff coeff, args = expr.as_coeff_mul(Quantity) args_pow = [arg.as_base_exp() for arg in args] quantity_pow, other_pow = sift(args_pow, lambda x: isinstance(x[0], Quantity), binary=True) quantity_pow_by_dim = sift(quantity_pow, lambda x: x[0].dimension) # Just pick the first quantity: ref_quantities = [i[0][0] for i in quantity_pow_by_dim.values()] new_quantities = [ Mul.fromiter((quantity * i.scale_factor / quantity.scale_factor)**p for i, p in v) if len(v) > 1 else v[0][0]**v[0][1] for quantity, (k, v) in zip(ref_quantities, quantity_pow_by_dim.items()) ] return coeff * Mul.fromiter(other_pow) * Mul.fromiter(new_quantities)
def _group_Add_terms(self, add): numbers, non_num = sift(add.args, lambda arg: arg.is_number, binary=True) numsum = sum(numbers) terms_with_func, other = sift(non_num, lambda arg: arg.has(self.func), binary=True) return numsum, terms_with_func, other
def _separate_imaginary_from_complex(cls, complexes): from sympy.utilities.iterables import sift def is_imag(c): ''' return True if all roots are imaginary (ax**2 + b) return False if no roots are imaginary return None if 2 roots are imaginary (ax**N''' u, f, k = c deg = f.degree() if f.length() == 2: if deg == 2: return True # both imag elif _ispow2(deg): if f.LC() * f.TC() < 0: return None # 2 are imag return False # none are imag # separate according to the function sifted = sift(complexes, lambda c: c[1]) del complexes imag = [] complexes = [] for f in sifted: isift = sift(sifted[f], lambda c: is_imag(c)) imag.extend(isift.pop(True, [])) complexes.extend(isift.pop(False, [])) mixed = isift.pop(None, []) assert not isift if not mixed: continue while True: # the non-imaginary ones will be on one side or the other # of the y-axis i = 0 while i < len(mixed): u, f, k = mixed[i] if u.ax * u.bx > 0: complexes.append(mixed.pop(i)) else: i += 1 if len(mixed) == 2: imag.extend(mixed) break # refine for i, (u, f, k) in enumerate(mixed): u = u._inner_refine() mixed[i] = u, f, k return imag, complexes
def _separate_imaginary_from_complex(cls, complexes): from sympy.utilities.iterables import sift def is_imag(c): ''' return True if all roots are imaginary (ax**2 + b) return False if no roots are imaginary return None if 2 roots are imaginary (ax**N''' u, f, k = c deg = f.degree() if f.length() == 2: if deg == 2: return True # both imag elif _ispow2(deg): if f.LC()*f.TC() < 0: return None # 2 are imag return False # none are imag # separate according to the function sifted = sift(complexes, lambda c: c[1]) del complexes imag = [] complexes = [] for f in sifted: isift = sift(sifted[f], lambda c: is_imag(c)) imag.extend(isift.pop(True, [])) complexes.extend(isift.pop(False, [])) mixed = isift.pop(None, []) assert not isift if not mixed: continue while True: # the non-imaginary ones will be on one side or the other # of the y-axis i = 0 while i < len(mixed): u, f, k = mixed[i] if u.ax*u.bx > 0: complexes.append(mixed.pop(i)) else: i += 1 if len(mixed) == 2: imag.extend(mixed) break # refine for i, (u, f, k) in enumerate(mixed): u = u._inner_refine() mixed[i] = u, f, k return imag, complexes
def _eval_expand_power_base(self, deep=True, **hints): """(a*b)**n -> a**n * b**n""" force = hints.get('force', False) b, ewas = self.args if deep: e = self.exp.expand(deep=deep, **hints) else: e = self.exp if b.is_Mul: bargs = b.args if force or e.is_integer: nonneg = bargs other = [] elif ewas.is_Rational or len( bargs) == 2 and bargs[0] is S.NegativeOne: # the Rational exponent was already expanded automatically # if there is a negative Number * foo, foo must be unknown # or else it, too, would have automatically expanded; # sqrt(-Number*foo) -> sqrt(Number)*sqrt(-foo); then # sqrt(-foo) -> unchanged if foo is not positive else # -> I*sqrt(foo) # So...if we have a 2 arg Mul and the first is a Number # that number is -1 and there is nothing more than can # be done without the force=True hint nonneg = [] else: # this is just like what is happening automatically, except # that now we are doing it for an arbitrary exponent for which # no automatic expansion is done def pred(x): if x.is_polar is None: return x.is_nonnegative return x.is_polar sifted = sift(b.args, pred) nonneg = sifted.get(True, []) other = sifted.get(None, []) neg = sifted.get(False, []) # make sure the Number gets pulled out if neg and neg[0].is_Number and neg[0] is not S.NegativeOne: nonneg.append(-neg[0]) neg[0] = S.NegativeOne # leave behind a negative sign oddneg = len(neg) % 2 if oddneg: other.append(S.NegativeOne) # negate all negatives and append to nonneg nonneg += [-n for n in neg] if nonneg: # then there's a new expression to return other = [Pow(Mul(*other), e)] if deep: return Mul(*([Pow(b.expand(deep=deep, **hints), e)\ for b in nonneg] + other)) else: return Mul(*([Pow(b, e) for b in nonneg] + other)) return Pow(b, e)
def _ncsplit(expr): # this is not the same as args_cnc because here # we don't assume expr is a Mul -- hence deal with args -- # and always return a set. cpart, ncpart = sift(expr.args, lambda arg: arg.is_commutative is True, binary=True) return set(cpart), ncpart
def merge_explicit(vecadd): """ Merge Vector arguments Example ======== >>> from sympy.vector import CoordSys3D >>> v1 = VectorSymbol("v1") >>> v2 = VectorSymbol("v2") >>> C = CoordSys3D("C") >>> vn1 = 2 * C.i + 3 * C.j + 4 * C.k >>> vn2 = x * C.i + y * C.j + x * y * C.k >>> expr = v1 + v2 + vn1 + vn2 >>> pprint(expr) (2*C.i + 3*C.j + 4*C.k) + (x*C.i + y*C.j + x*y*C.k) + v1 + v2 >>> pprint(merge_explicit(expr)) ((2 + x)*C.i + (3 + y)*C.j + (4 + x*y)*C.k) + v1 + v2 """ # there has to be some bug deep inside the core, I absolutely need this # function to correctly perform addition def recreate_args(args): return [a.func(*a.args) for a in args] # Adapted from sympy.matrices.expressions.matadd.py groups = sift(vecadd.args, lambda arg: isinstance(arg, (Vector))) if len(groups[True]) > 1: return VecAdd(*(recreate_args(groups[False]) + [reduce(add, recreate_args(groups[True]))])) # return VecAdd(*(groups[False] + [reduce(add, groups[True])])) else: return vecadd
def factor_minmax(args): is_other = lambda arg: isinstance(arg, other) other_args, remaining_args = sift(args, is_other, binary=True) if not other_args: return args # Min(Max(x, y, z), Max(x, y, u, v)) -> {x,y}, ({z}, {u,v}) arg_sets = [set(arg.args) for arg in other_args] common = set.intersection(*arg_sets) if not common: return args new_other_args = list(common) arg_sets_diff = [arg_set - common for arg_set in arg_sets] # If any set is empty after removing common then all can be # discarded e.g. Min(Max(a, b, c), Max(a, b)) -> Max(a, b) if all(arg_sets_diff): other_args_diff = [ other(*s, evaluate=False) for s in arg_sets_diff ] new_other_args.append(cls(*other_args_diff, evaluate=False)) other_args_factored = other(*new_other_args, evaluate=False) return remaining_args + [other_args_factored]
def _eval_simplify(self, ratio, measure, rational, inverse): args = [ a._eval_simplify(ratio, measure, rational, inverse) for a in self.args ] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) return self.func(*args)
def quantity_simplify(expr): """Return an equivalent expression in which prefixes are replaced with numerical values and all units of a given dimension are the unified in a canonical manner. Examples ======== >>> from sympy.physics.units.util import quantity_simplify >>> from sympy.physics.units.prefixes import kilo >>> from sympy.physics.units import foot, inch >>> quantity_simplify(kilo*foot*inch) 250*foot**2/3 >>> quantity_simplify(foot - 6*inch) foot/2 """ if expr.is_Atom or not expr.has(Prefix, Quantity): return expr # replace all prefixes with numerical values p = expr.atoms(Prefix) expr = expr.xreplace({p: p.scale_factor for p in p}) # replace all quantities of given dimension with a canonical # quantity, chosen from those in the expression d = sift(expr.atoms(Quantity), lambda i: i.dimension) for k in d: if len(d[k]) == 1: continue v = list(ordered(d[k])) ref = v[0]/v[0].scale_factor expr = expr.xreplace({vi: ref*vi.scale_factor for vi in v[1:]}) return expr
def _eval_simplify(self, ratio, measure, rational, inverse): args = [a._eval_simplify(ratio, measure, rational, inverse) for a in self.args] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) return self.func(*args)
def MatMul_elements(matrix_predicate, scalar_predicate, expr, assumptions): d = sift(expr.args, lambda x: isinstance(x, MatrixExpr)) factors, matrices = d[False], d[True] return fuzzy_and([ test_closed_group(Basic(*factors), assumptions, scalar_predicate), test_closed_group(Basic(*matrices), assumptions, matrix_predicate) ])
def quantity_simplify(expr): """Return an equivalent expression in which prefixes are replaced with numerical values and all units of a given dimension are the unified in a canonical manner. Examples ======== >>> from sympy.physics.units.util import quantity_simplify >>> from sympy.physics.units.prefixes import kilo >>> from sympy.physics.units import foot, inch >>> quantity_simplify(kilo*foot*inch) 250*foot**2/3 >>> quantity_simplify(foot - 6*inch) foot/2 """ if expr.is_Atom or not expr.has(Prefix, Quantity): return expr # replace all prefixes with numerical values p = expr.atoms(Prefix) expr = expr.xreplace({p: p.scale_factor for p in p}) # replace all quantities of given dimension with a canonical # quantity, chosen from those in the expression d = sift(expr.atoms(Quantity), lambda i: i.dimension) for k in d: if len(d[k]) == 1: continue v = list(ordered(d[k])) ref = v[0] / v[0].scale_factor expr = expr.xreplace({vi: ref * vi.scale_factor for vi in v[1:]}) return expr
def replace_in_Add(self, e): """ passed as second argument to Basic.replace(...) """ numsum, terms_with_func, other_non_num_terms = self._group_Add_terms(e) if numsum == 0: return e substituted, untouched = [], [] for with_func in terms_with_func: if with_func.is_Mul: func, coeff = sift(with_func.args, lambda arg: arg.func == self.func, binary=True) if len(func) == 1 and len(coeff) == 1: func, coeff = func[0], coeff[0] else: coeff = None elif with_func.func == self.func: func, coeff = with_func, S.One else: coeff = None if coeff is not None and coeff.is_number and sign( coeff) == -sign(numsum): if self.opportunistic: do_substitute = abs(coeff + numsum) < abs(numsum) else: do_substitute = coeff + numsum == 0 if do_substitute: # advantageous substitution numsum += coeff substituted.append(coeff * self.func_m_1(*func.args)) continue untouched.append(with_func) return e.func(numsum, *substituted, *untouched, *other_non_num_terms)
def _eval_expand_power_base(self, deep=True, **hints): """(a*b)**n -> a**n * b**n""" force = hints.get("force", False) b, ewas = self.args if deep: e = self.exp.expand(deep=deep, **hints) else: e = self.exp if b.is_Mul: bargs = b.args if force or e.is_integer: nonneg = bargs other = [] elif ewas.is_Rational or len(bargs) == 2 and bargs[0] is S.NegativeOne: # the Rational exponent was already expanded automatically # if there is a negative Number * foo, foo must be unknown # or else it, too, would have automatically expanded; # sqrt(-Number*foo) -> sqrt(Number)*sqrt(-foo); then # sqrt(-foo) -> unchanged if foo is not positive else # -> I*sqrt(foo) # So...if we have a 2 arg Mul and the first is a Number # that number is -1 and there is nothing more than can # be done without the force=True hint nonneg = [] else: # this is just like what is happening automatically, except # that now we are doing it for an arbitrary exponent for which # no automatic expansion is done def pred(x): if x.is_polar is None: return x.is_nonnegative return x.is_polar sifted = sift(b.args, pred) nonneg = sifted.get(True, []) other = sifted.get(None, []) neg = sifted.get(False, []) # make sure the Number gets pulled out if neg and neg[0].is_Number and neg[0] is not S.NegativeOne: nonneg.append(-neg[0]) neg[0] = S.NegativeOne # leave behind a negative sign oddneg = len(neg) % 2 if oddneg: other.append(S.NegativeOne) # negate all negatives and append to nonneg nonneg += [-n for n in neg] if nonneg: # then there's a new expression to return other = [Pow(Mul(*other), e)] if deep: return Mul(*([Pow(b.expand(deep=deep, **hints), e) for b in nonneg] + other)) else: return Mul(*([Pow(b, e) for b in nonneg] + other)) return Pow(b, e)
def __new__(cls, sym, condition, base_set): # nonlinsolve uses ConditionSet to return an unsolved system # of equations (see _return_conditionset in solveset) so until # that is changed we do minimal checking of the args unsolved = isinstance(sym, (Tuple, tuple)) if unsolved: sym = Tuple(*sym) condition = FiniteSet(*condition) else: condition = as_Boolean(condition) if isinstance(base_set, set): base_set = FiniteSet(*base_set) elif not isinstance(base_set, Set): raise TypeError('expecting set for base_set') if condition == S.false: return S.EmptySet if condition == S.true: return base_set if isinstance(base_set, EmptySet): return base_set if not unsolved: if isinstance(base_set, FiniteSet): sifted = sift(base_set, lambda _: fuzzy_bool(condition.subs(sym, _))) if sifted[None]: return Union( FiniteSet(*sifted[True]), Basic.__new__(cls, sym, condition, FiniteSet(*sifted[None]))) else: return FiniteSet(*sifted[True]) if isinstance(base_set, cls): s, c, base_set = base_set.args if sym == s: condition = And(condition, c) elif sym not in c.free_symbols: condition = And(condition, c.xreplace({s: sym})) elif s not in condition.free_symbols: condition = And(condition.xreplace({sym: s}), c) sym = s else: # user will have to use cls.sym to get symbol dum = Symbol('lambda') if dum in condition.free_symbols or \ dum in c.free_symbols: dum = Dummy(str(dum)) condition = And(condition.xreplace({sym: dum}), c.xreplace({s: dum})) sym = dum if sym in base_set.free_symbols or \ not isinstance(sym, Symbol): s = Symbol('lambda') if s in base_set.free_symbols: s = Dummy('lambda') condition = condition.xreplace({sym: s}) sym = s return Basic.__new__(cls, sym, condition, base_set)
def conglomerate(expr): """ Conglomerate together identical args x + x -> 2x """ groups = sift(expr.args, key) counts = dict((k, sum(map(count, args))) for k, args in groups.items()) newargs = [combine(cnt, mat) for mat, cnt in counts.items()] if set(newargs) != set(expr.args): return new(type(expr), *newargs) else: return expr
def __new__(cls, variable, condition, base_set=S.UniversalSet): # nonlinsolve uses ConditionSet to return an unsolved system # of equations (see _return_conditionset in solveset) so until # that is changed we do minimal checking of the args if isinstance(variable, (Tuple, tuple)): # unsolved eqns syntax variable = Tuple(*variable) condition = FiniteSet(*condition) return Basic.__new__(cls, variable, condition, base_set) condition = as_Boolean(condition) if isinstance(base_set, set): base_set = FiniteSet(*base_set) elif not base_set.is_set: raise TypeError('expecting set for base_set') if condition is S.false: return S.EmptySet if condition is S.true: return base_set if isinstance(base_set, EmptySet): return base_set know = None if isinstance(base_set, FiniteSet): sifted = sift( base_set, lambda _: fuzzy_bool( condition.subs(variable, _))) if sifted[None]: know = FiniteSet(*sifted[True]) base_set = FiniteSet(*sifted[None]) else: return FiniteSet(*sifted[True]) if isinstance(base_set, cls): s, c, base_set = base_set.args if variable == s: condition = And(condition, c) elif variable not in c.free_symbols: condition = And(condition, c.xreplace({s: variable})) elif s not in condition.free_symbols: condition = And(condition.xreplace({variable: s}), c) variable = s else: # user will have to use cls.variable to get symbol dum = Symbol('lambda') if dum in condition.free_symbols or \ dum in c.free_symbols: dum = Dummy(str(dum)) condition = And( condition.xreplace({variable: dum}), c.xreplace({s: dum})) variable = dum from sympy.tensor.indexed import Slice, IndexedBase assert isinstance(variable, (Symbol, Slice, IndexedBase)) # s = Dummy('lambda') # if s not in condition.xreplace({variable: s}).free_symbols: # raise ValueError('non-symbol dummy not recognized in condition') if condition.is_BooleanFalse: return S.EmptySet rv = Basic.__new__(cls, variable, condition, base_set) return rv if know is None else Union(know, rv)
def quantity_simplify(expr): """Return an equivalent expression in which prefixes are replaced with numerical values and products of related quantities are unified in a canonical manner. Examples ======== >>> from sympy.physics.units.util import quantity_simplify >>> from sympy.physics.units.prefixes import kilo >>> from sympy.physics.units import foot, inch >>> quantity_simplify(kilo*foot*inch) 250*foot**2/3 """ if expr.is_Atom: return expr if isinstance(expr, Prefix): return expr.scale_factor expr = expr.func(*map(quantity_simplify, expr.args)) if not expr.is_Mul or not expr.has(Quantity): return expr args_pow = [arg.as_base_exp() for arg in expr.args] quantity_pow, other_pow = sift(args_pow, lambda x: isinstance(x[0], Quantity), binary=True) coeff = Mul.fromiter([Pow(b, e, evaluate=False) for b, e in other_pow]) quantity_pow_by_dim = sift(quantity_pow, lambda x: x[0].dimension) new_quantities = [] for _, bp in quantity_pow_by_dim.items(): if len(bp) == 1: new_quantities.append(bp[0][0]**bp[0][1]) else: # just let reference quantity be the first quantity, # picked from an ordered list bp = list(ordered(bp)) ref = bp[0][0] / bp[0][0].scale_factor new_quantities.append( Mul.fromiter(((ref * b.scale_factor)**p for b, p in bp))) return coeff * Mul.fromiter(new_quantities)
def _filter_assumptions(kwargs): """Split the given dict into assumptions and non-assumptions. Keys are taken as assumptions if they correspond to an entry in ``_assume_defined``. """ assumptions, nonassumptions = map( dict, sift(kwargs.items(), lambda i: i[0] in _assume_defined, binary=True)) Symbol._sanitize(assumptions) return assumptions, nonassumptions
def _handle_finite_sets(op, x, y, commutative): # Handle finite sets: fs_args, other = sift([x, y], lambda x: isinstance(x, FiniteSet), binary=True) if len(fs_args) == 2: return FiniteSet(*[op(i, j) for i in fs_args[0] for j in fs_args[1]]) elif len(fs_args) == 1: sets = [_apply_operation(op, other[0], i, commutative) for i in fs_args[0]] return Union(*sets) else: return None
def __new__(cls, sym, condition, base_set=S.UniversalSet): # nonlinsolve uses ConditionSet to return an unsolved system # of equations (see _return_conditionset in solveset) so until # that is changed we do minimal checking of the args if isinstance(sym, (Tuple, tuple)): # unsolved eqns syntax sym = Tuple(*sym) condition = FiniteSet(*condition) return Basic.__new__(cls, sym, condition, base_set) condition = as_Boolean(condition) if isinstance(base_set, set): base_set = FiniteSet(*base_set) elif not isinstance(base_set, Set): raise TypeError('expecting set for base_set') if condition is S.false: return S.EmptySet if condition is S.true: return base_set if isinstance(base_set, EmptySet): return base_set know = None if isinstance(base_set, FiniteSet): sifted = sift( base_set, lambda _: fuzzy_bool( condition.subs(sym, _))) if sifted[None]: know = FiniteSet(*sifted[True]) base_set = FiniteSet(*sifted[None]) else: return FiniteSet(*sifted[True]) if isinstance(base_set, cls): s, c, base_set = base_set.args if sym == s: condition = And(condition, c) elif sym not in c.free_symbols: condition = And(condition, c.xreplace({s: sym})) elif s not in condition.free_symbols: condition = And(condition.xreplace({sym: s}), c) sym = s else: # user will have to use cls.sym to get symbol dum = Symbol('lambda') if dum in condition.free_symbols or \ dum in c.free_symbols: dum = Dummy(str(dum)) condition = And( condition.xreplace({sym: dum}), c.xreplace({s: dum})) sym = dum if not isinstance(sym, Symbol): s = Dummy('lambda') if s not in condition.xreplace({sym: s}).free_symbols: raise ValueError( 'non-symbol dummy not recognized in condition') rv = Basic.__new__(cls, sym, condition, base_set) return rv if know is None else Union(know, rv)
def _expm1_value(e): numbers, non_num = sift(e.args, lambda arg: arg.is_number, binary=True) non_num_exp, non_num_other = sift(non_num, lambda arg: arg.has(exp), binary=True) numsum = sum(numbers) new_exp_terms, done = [], False for exp_term in non_num_exp: if done: new_exp_terms.append(exp_term) else: looking_at = exp_term + numsum attempt = _try_expm1(looking_at) if looking_at == attempt: new_exp_terms.append(exp_term) else: done = True new_exp_terms.append(attempt) if not done: new_exp_terms.append(numsum) return e.func(*chain(new_exp_terms, non_num_other))
def __call__(self, e): numbers, non_num = sift(e.args, lambda arg: arg.is_number, binary=True) non_num_func, non_num_other = sift(non_num, lambda arg: arg.has(self.func), binary=True) numsum = sum(numbers) new_func_terms, done = [], False for func_term in non_num_func: if done: new_func_terms.append(func_term) else: looking_at = func_term + numsum attempt = self._try_func_m_1(looking_at) if looking_at == attempt: new_func_terms.append(func_term) else: done = True new_func_terms.append(attempt) if not done: new_func_terms.append(numsum) return e.func(*chain(new_func_terms, non_num_other))
def test_sift(): assert sift(list(range(5)), lambda _: _ % 2) == {1: [1, 3], 0: [0, 2, 4]} assert sift([x, y], lambda _: _.has(x)) == {False: [y], True: [x]} assert sift([S.One], lambda _: _.has(x)) == {False: [1]} assert sift([0, 1, 2, 3], lambda x: x % 2, binary=True) == ([1, 3], [0, 2]) assert sift([0, 1, 2, 3], lambda x: x % 3 == 1, binary=True) == ([1], [0, 2, 3]) raises(ValueError, lambda: sift([0, 1, 2, 3], lambda x: x % 3, binary=True))
def bc_matadd(expr): args = sift(expr.args, lambda M: isinstance(M, BlockMatrix)) blocks = args[True] if not blocks: return expr nonblocks = args[False] block = blocks[0] for b in blocks[1:]: block = block._blockadd(b) if nonblocks: return MatAdd(*nonblocks) + block else: return block
def _handle_finite_sets(op, x, y, commutative): # Handle finite sets: fs_args, other = sift([x, y], lambda x: isinstance(x, FiniteSet), binary=True) if len(fs_args) == 2: return FiniteSet(*[op(i, j) for i in fs_args[0] for j in fs_args[1]]) elif len(fs_args) == 1: sets = [ _apply_operation(op, other[0], i, commutative) for i in fs_args[0] ] return Union(*sets) else: return None
def _eval_factor(self, **hints): if 1 == len(self.limits): summand = self.function.factor(**hints) if summand.is_Mul: out = sift(summand.args, lambda w: w.is_commutative \ and not w.has(*self.variables)) return Mul(*out[True])*self.func(Mul(*out[False]), \ *self.limits) else: summand = self.func(self.function, self.limits[0:-1]).factor() if not summand.has(self.variables[-1]): return self.func(1, [self.limits[-1]]).doit() * summand elif isinstance(summand, Mul): return self.func(summand, self.limits[-1]).factor() return self
def __new__(cls, sym, condition, base_set): if condition == S.false: return S.EmptySet if condition == S.true: return base_set if isinstance(base_set, EmptySet): return base_set if isinstance(base_set, FiniteSet): sifted = sift(base_set, lambda _: fuzzy_bool(condition.subs(sym, _))) if sifted[None]: return Union(FiniteSet(*sifted[True]), Basic.__new__(cls, sym, condition, FiniteSet(*sifted[None]))) else: return FiniteSet(*sifted[True]) return Basic.__new__(cls, sym, condition, base_set)
def _eval_factor(self, **hints): if 1 == len(self.limits): summand = self.function.factor(**hints) if summand.is_Mul: out = sift(summand.args, lambda w: w.is_commutative \ and not w.has(*self.variables)) return C.Mul(*out[True])*self.func(C.Mul(*out[False]), \ *self.limits) else: summand = self.func(self.function, self.limits[0:-1]).factor() if not summand.has(self.variables[-1]): return self.func(1, [self.limits[-1]]).doit()*summand elif isinstance(summand, C.Mul): return self.func(summand, self.limits[-1]).factor() return self
def test_sift(): assert sift(list(range(5)), lambda _: _ % 2) == {1: [1, 3], 0: [0, 2, 4]} assert sift([x, y], lambda _: _.has(x)) == {False: [y], True: [x]} assert sift([S.One], lambda _: _.has(x)) == {False: [1]} assert sift([0, 1, 2, 3], lambda x: x % 2, binary=True) == ( [1, 3], [0, 2]) assert sift([0, 1, 2, 3], lambda x: x % 3 == 1, binary=True) == ( [1], [0, 2, 3]) raises(ValueError, lambda: sift([0, 1, 2, 3], lambda x: x % 3, binary=True))
def cse_separate(r, e): """Move expressions that are in the form (symbol, expr) out of the expressions and sort them into the replacements using the reps_toposort. Examples ======== >>> from sympy.simplify.cse_main import cse_separate >>> from sympy.abc import x, y, z >>> from sympy import cos, exp, cse, Eq >>> eq = (x + 1 + exp((x + 1)/(y + 1)) + cos(y + 1)) >>> cse([eq, Eq(x, z + 1), z - 2], postprocess=cse_separate) [[(x0, y + 1), (x, z + 1), (x1, x + 1)], [x1 + exp(x1/x0) + cos(x0), z - 2]] """ syms = set([k for k, v in r]) d = sift(e, lambda w: w.is_Equality and not bool(w.free_symbols & set(syms))) r, e = [r + [w.args for w in d[True]], d[False]] return [reps_toposort(r), e]
def __new__(cls, sym, condition, base_set): if condition == S.false: return S.EmptySet if condition == S.true: return base_set if isinstance(base_set, EmptySet): return base_set if isinstance(base_set, FiniteSet): sifted = sift(base_set, lambda _: fuzzy_bool(condition.subs(sym, _))) if sifted[None]: return Union( FiniteSet(*sifted[True]), Basic.__new__(cls, sym, condition, FiniteSet(*sifted[None]))) else: return FiniteSet(*sifted[True]) return Basic.__new__(cls, sym, condition, base_set)
def as_coeff_add(self, *deps): """ Returns a tuple (coeff, args) where self is treated as an Add and coeff is the Number term and args is a tuple of all other terms. Examples ======== >>> from sympy.abc import x >>> (7 + 3*x).as_coeff_add() (7, (3*x,)) >>> (7*x).as_coeff_add() (0, (7*x,)) """ if deps: l1, l2 = sift(self.args, lambda x: x.has_free(*deps), binary=True) return self._new_rawargs(*l2), tuple(l1) coeff, notrat = self.args[0].as_coeff_add() if coeff is not S.Zero: return coeff, notrat + self.args[1:] return S.Zero, self.args
def merge_explicit_mul(vecmul): """ Merge explicit Vector and Expr (Numbers, Symbols, ...) arguments Example ======== >>> from sympy.vector import CoordSys3D >>> v1 = VectorSymbol("v1") >>> v2 = VectorSymbol("v2") >>> C = CoordSys3D("C") >>> vn1 = R.x * R.i + R.y * R.j + R.k * R.z >>> expr = VecMul(2, v1.mag, vn1, x, evaluate=False) >>> pprint(expr) 2*x*(R.x*R.i + R.y*R.j + R.z*R.k)*Magnitude(VectorSymbol(v1)) >>> pprint(merge_explicit(expr)) (2*R.x*x*R.i + 2*R.y*x*R.j + 2*R.z*x*R.k)*Magnitude(VectorSymbol(v1)) """ groups = sift(vecmul.args, lambda arg: not isinstance(arg, VectorExpr)) print("\t", groups) if len(groups[True]) > 1: return VecMul(*(groups[False] + [reduce(mul, groups[True])])) else: return vecmul
def cse_separate(r, e): """Move expressions that are in the form (symbol, expr) out of the expressions and sort them into the replacements using the reps_toposort. Examples ======== >>> from sympy.simplify.cse_main import cse_separate >>> from sympy.abc import x, y, z >>> from sympy import cos, exp, cse, Eq, symbols >>> x0, x1 = symbols('x:2') >>> eq = (x + 1 + exp((x + 1)/(y + 1)) + cos(y + 1)) >>> cse([eq, Eq(x, z + 1), z - 2], postprocess=cse_separate) in [ ... [[(x0, y + 1), (x, z + 1), (x1, x + 1)], ... [x1 + exp(x1/x0) + cos(x0), z - 2]], ... [[(x1, y + 1), (x, z + 1), (x0, x + 1)], ... [x0 + exp(x0/x1) + cos(x1), z - 2]]] ... True """ d = sift(e, lambda w: w.is_Equality and w.lhs.is_Symbol) r = r + [w.args for w in d[True]] e = d[False] return [reps_toposort(r), e]
def solve_univariate_inequality(expr, gen, relational=True, domain=S.Reals, continuous=False): """Solves a real univariate inequality. Parameters ========== expr : Relational The target inequality gen : Symbol The variable for which the inequality is solved relational : bool A Relational type output is expected or not domain : Set The domain over which the equation is solved continuous: bool True if expr is known to be continuous over the given domain (and so continuous_domain() doesn't need to be called on it) Raises ====== NotImplementedError The solution of the inequality cannot be determined due to limitation in `solvify`. Notes ===== Currently, we cannot solve all the inequalities due to limitations in `solvify`. Also, the solution returned for trigonometric inequalities are restricted in its periodic interval. See Also ======== solvify: solver returning solveset solutions with solve's output API Examples ======== >>> from sympy.solvers.inequalities import solve_univariate_inequality >>> from sympy import Symbol, sin, Interval, S >>> x = Symbol('x') >>> solve_univariate_inequality(x**2 >= 4, x) ((2 <= x) & (x < oo)) | ((x <= -2) & (-oo < x)) >>> solve_univariate_inequality(x**2 >= 4, x, relational=False) Union(Interval(-oo, -2), Interval(2, oo)) >>> domain = Interval(0, S.Infinity) >>> solve_univariate_inequality(x**2 >= 4, x, False, domain) Interval(2, oo) >>> solve_univariate_inequality(sin(x) > 0, x, relational=False) Interval.open(0, pi) """ from sympy import im from sympy.calculus.util import (continuous_domain, periodicity, function_range) from sympy.solvers.solvers import denoms from sympy.solvers.solveset import solveset_real, solvify, solveset from sympy.solvers.solvers import solve # This keeps the function independent of the assumptions about `gen`. # `solveset` makes sure this function is called only when the domain is # real. _gen = gen _domain = domain if gen.is_real is False: rv = S.EmptySet return rv if not relational else rv.as_relational(_gen) elif gen.is_real is None: gen = Dummy('gen', real=True) try: expr = expr.xreplace({_gen: gen}) except TypeError: raise TypeError( filldedent(''' When gen is real, the relational has a complex part which leads to an invalid comparison like I < 0. ''')) rv = None if expr is S.true: rv = domain elif expr is S.false: rv = S.EmptySet else: e = expr.lhs - expr.rhs period = periodicity(e, gen) if period is S.Zero: e = expand_mul(e) const = expr.func(e, 0) if const is S.true: rv = domain elif const is S.false: rv = S.EmptySet elif period is not None: frange = function_range(e, gen, domain) rel = expr.rel_op if rel == '<' or rel == '<=': if expr.func(frange.sup, 0): rv = domain elif not expr.func(frange.inf, 0): rv = S.EmptySet elif rel == '>' or rel == '>=': if expr.func(frange.inf, 0): rv = domain elif not expr.func(frange.sup, 0): rv = S.EmptySet inf, sup = domain.inf, domain.sup if sup - inf is S.Infinity: domain = Interval(0, period, False, True) if rv is None: n, d = e.as_numer_denom() try: if gen not in n.free_symbols and len(e.free_symbols) > 1: raise ValueError # this might raise ValueError on its own # or it might give None... solns = solvify(e, gen, domain) if solns is None: # in which case we raise ValueError raise ValueError except (ValueError, NotImplementedError): # replace gen with generic x since it's # univariate anyway raise NotImplementedError( filldedent(''' The inequality, %s, cannot be solved using solve_univariate_inequality. ''' % expr.subs(gen, Symbol('x')))) expanded_e = expand_mul(e) def valid(x): # this is used to see if gen=x satisfies the # relational by substituting it into the # expanded form and testing against 0, e.g. # if expr = x*(x + 1) < 2 then e = x*(x + 1) - 2 # and expanded_e = x**2 + x - 2; the test is # whether a given value of x satisfies # x**2 + x - 2 < 0 # # expanded_e, expr and gen used from enclosing scope v = expanded_e.subs(gen, expand_mul(x)) try: r = expr.func(v, 0) except TypeError: r = S.false if r in (S.true, S.false): return r if v.is_real is False: return S.false else: v = v.n(2) if v.is_comparable: return expr.func(v, 0) # not comparable or couldn't be evaluated raise NotImplementedError( 'relationship did not evaluate: %s' % r) singularities = [] for d in denoms(expr, gen): singularities.extend(solvify(d, gen, domain)) if not continuous: domain = continuous_domain(expanded_e, gen, domain) include_x = '=' in expr.rel_op and expr.rel_op != '!=' try: discontinuities = set(domain.boundary - FiniteSet(domain.inf, domain.sup)) # remove points that are not between inf and sup of domain critical_points = FiniteSet( *(solns + singularities + list(discontinuities))).intersection( Interval(domain.inf, domain.sup, domain.inf not in domain, domain.sup not in domain)) if all(r.is_number for r in critical_points): reals = _nsort(critical_points, separated=True)[0] else: from sympy.utilities.iterables import sift sifted = sift(critical_points, lambda x: x.is_real) if sifted[None]: # there were some roots that weren't known # to be real raise NotImplementedError try: reals = sifted[True] if len(reals) > 1: reals = list(sorted(reals)) except TypeError: raise NotImplementedError except NotImplementedError: raise NotImplementedError( 'sorting of these roots is not supported') # If expr contains imaginary coefficients, only take real # values of x for which the imaginary part is 0 make_real = S.Reals if im(expanded_e) != S.Zero: check = True im_sol = FiniteSet() try: a = solveset(im(expanded_e), gen, domain) if not isinstance(a, Interval): for z in a: if z not in singularities and valid( z) and z.is_real: im_sol += FiniteSet(z) else: start, end = a.inf, a.sup for z in _nsort(critical_points + FiniteSet(end)): valid_start = valid(start) if start != end: valid_z = valid(z) pt = _pt(start, z) if pt not in singularities and pt.is_real and valid( pt): if valid_start and valid_z: im_sol += Interval(start, z) elif valid_start: im_sol += Interval.Ropen(start, z) elif valid_z: im_sol += Interval.Lopen(start, z) else: im_sol += Interval.open(start, z) start = z for s in singularities: im_sol -= FiniteSet(s) except (TypeError): im_sol = S.Reals check = False if isinstance(im_sol, EmptySet): raise ValueError( filldedent(''' %s contains imaginary parts which cannot be made 0 for any value of %s satisfying the inequality, leading to relations like I < 0. ''' % (expr.subs(gen, _gen), _gen))) make_real = make_real.intersect(im_sol) empty = sol_sets = [S.EmptySet] start = domain.inf if valid(start) and start.is_finite: sol_sets.append(FiniteSet(start)) for x in reals: end = x if valid(_pt(start, end)): sol_sets.append(Interval(start, end, True, True)) if x in singularities: singularities.remove(x) else: if x in discontinuities: discontinuities.remove(x) _valid = valid(x) else: # it's a solution _valid = include_x if _valid: sol_sets.append(FiniteSet(x)) start = end end = domain.sup if valid(end) and end.is_finite: sol_sets.append(FiniteSet(end)) if valid(_pt(start, end)): sol_sets.append(Interval.open(start, end)) if im(expanded_e) != S.Zero and check: rv = (make_real).intersect(_domain) else: rv = Intersection((Union(*sol_sets)), make_real, _domain).subs(gen, _gen) return rv if not relational else rv.as_relational(_gen)
def split_real_imag(expr): real_imag = lambda t: ( 'real' if is_extended_real(t, assumptions) else 'imag' if is_extended_real(I * t, assumptions) else None) return sift(Add.make_args(expr), real_imag)
def _eval_simplify(self, ratio, measure, rational, inverse): args = [a._eval_simplify(ratio, measure, rational, inverse) for a in self.args] for i, (expr, cond) in enumerate(args): # try to simplify conditions and the expression for # equalities that are part of the condition, e.g. # Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) # -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): expr = expr.subs(*e.args) eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] other = [ei.subs(*e.args) for ei in other] cond = And(*(eqs + other)) args[i] = args[i].func(expr, cond) # See if expressions valid for a single point happens to evaluate # to the same function as in the next piecewise segment, see: # https://github.com/sympy/sympy/issues/8458 prevexpr = None for i, (expr, cond) in reversed(list(enumerate(args))): if prevexpr is not None: _prevexpr = prevexpr _expr = expr if isinstance(cond, And): eqs, other = sift(cond.args, lambda i: isinstance(i, Equality), binary=True) elif isinstance(cond, Equality): eqs, other = [cond], [] else: eqs = other = [] if eqs: eqs = list(ordered(eqs)) for j, e in enumerate(eqs): # these blessed lhs objects behave like Symbols # and the rhs are simple replacements for the "symbols" if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \ isinstance(e.rhs, (Rational, NumberSymbol, Symbol, UndefinedFunction)): _prevexpr = _prevexpr.subs(*e.args) _expr = _expr.subs(*e.args) if _prevexpr == _expr: args[i] = args[i].func(args[i+1][0], cond) else: prevexpr = expr else: prevexpr = expr return self.func(*args)
def _eval_expand_power_base(self, **hints): """(a*b)**n -> a**n * b**n""" force = hints.get('force', False) b = self.base e = self.exp if not b.is_Mul: return self cargs, nc = b.args_cnc(split_1=False) # expand each term - this is top-level-only # expansion but we have to watch out for things # that don't have an _eval_expand method if nc: nc = [i._eval_expand_power_base(**hints) if hasattr(i, '_eval_expand_power_base') else i for i in nc] if e.is_Integer: if e.is_positive: rv = Mul(*nc*e) else: rv = 1/Mul(*nc*-e) if cargs: rv *= Mul(*cargs)**e return rv if not cargs: return self.func(Mul(*nc), e, evaluate=False) nc = [Mul(*nc)] # sift the commutative bases def pred(x): if x is S.ImaginaryUnit: return S.ImaginaryUnit polar = x.is_polar if polar: return True if polar is None: return fuzzy_bool(x.is_nonnegative) sifted = sift(cargs, pred) nonneg = sifted[True] other = sifted[None] neg = sifted[False] imag = sifted[S.ImaginaryUnit] if imag: I = S.ImaginaryUnit i = len(imag) % 4 if i == 0: pass elif i == 1: other.append(I) elif i == 2: if neg: nonn = -neg.pop() if nonn is not S.One: nonneg.append(nonn) else: neg.append(S.NegativeOne) else: if neg: nonn = -neg.pop() if nonn is not S.One: nonneg.append(nonn) else: neg.append(S.NegativeOne) other.append(I) del imag # bring out the bases that can be separated from the base if force or e.is_integer: # treat all commutatives the same and put nc in other cargs = nonneg + neg + other other = nc else: # this is just like what is happening automatically, except # that now we are doing it for an arbitrary exponent for which # no automatic expansion is done assert not e.is_Integer # handle negatives by making them all positive and putting # the residual -1 in other if len(neg) > 1: o = S.One if not other and neg[0].is_Number: o *= neg.pop(0) if len(neg) % 2: o = -o for n in neg: nonneg.append(-n) if o is not S.One: other.append(o) elif neg and other: if neg[0].is_Number and neg[0] is not S.NegativeOne: other.append(S.NegativeOne) nonneg.append(-neg[0]) else: other.extend(neg) else: other.extend(neg) del neg cargs = nonneg other += nc rv = S.One if cargs: rv *= Mul(*[self.func(b, e, evaluate=False) for b in cargs]) if other: rv *= self.func(Mul(*other), e, evaluate=False) return rv
def piecewise_fold(expr): """ Takes an expression containing a piecewise function and returns the expression in piecewise form. In addition, any ITE conditions are rewritten in negation normal form and simplified. Examples ======== >>> from sympy import Piecewise, piecewise_fold, sympify as S >>> from sympy.abc import x >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) >>> piecewise_fold(x*p) Piecewise((x**2, x < 1), (x, True)) See Also ======== Piecewise """ if not isinstance(expr, Basic) or not expr.has(Piecewise): return expr new_args = [] if isinstance(expr, (ExprCondPair, Piecewise)): for e, c in expr.args: if not isinstance(e, Piecewise): e = piecewise_fold(e) # we don't keep Piecewise in condition because # it has to be checked to see that it's complete # and we convert it to ITE at that time assert not c.has(Piecewise) # pragma: no cover if isinstance(c, ITE): c = c.to_nnf() c = simplify_logic(c, form='cnf') if isinstance(e, Piecewise): new_args.extend([(piecewise_fold(ei), And(ci, c)) for ei, ci in e.args]) else: new_args.append((e, c)) else: from sympy.utilities.iterables import cartes, sift, common_prefix # Given # P1 = Piecewise((e11, c1), (e12, c2), A) # P2 = Piecewise((e21, c1), (e22, c2), B) # ... # the folding of f(P1, P2) is trivially # Piecewise( # (f(e11, e21), c1), # (f(e12, e22), c2), # (f(Piecewise(A), Piecewise(B)), True)) # Certain objects end up rewriting themselves as thus, so # we do that grouping before the more generic folding. # The following applies this idea when f = Add or f = Mul # (and the expression is commutative). if expr.is_Add or expr.is_Mul and expr.is_commutative: p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) pc = sift(p, lambda x: x.args[0].cond) for c in pc: if len(pc[c]) > 1: pargs = [list(i.args) for i in pc[c]] # the first one is the same; there may be more com = common_prefix(*[ [i.cond for i in j] for j in pargs]) n = len(com) collected = [] for i in range(n): collected.append(( expr.func(*[ai[i].expr for ai in pargs]), com[i])) remains = [] for a in pargs: if n == len(a): # no more args continue if a[n].cond == True: # no longer Piecewise remains.append(a[n].expr) else: # restore the remaining Piecewise remains.append( Piecewise(*a[n:], evaluate=False)) if remains: collected.append((expr.func(*remains), True)) args.append(Piecewise(*collected, evaluate=False)) continue args.extend(pc[c]) else: args = expr.args # fold folded = list(map(piecewise_fold, args)) for ec in cartes(*[ (i.args if isinstance(i, Piecewise) else [(i, true)]) for i in folded]): e, c = zip(*ec) new_args.append((expr.func(*e), And(*c))) return Piecewise(*new_args)
def rule_gamma(expr, level=0): """ Simplify products of gamma functions further. """ if expr.is_Atom: return expr def gamma_rat(x): # helper to simplify ratios of gammas was = x.count(gamma) xx = x.replace(gamma, lambda n: _rf(1, (n - 1).expand() ).replace(_rf, lambda a, b: gamma(a + b)/gamma(a))) if xx.count(gamma) < was: x = xx return x def gamma_factor(x): # return True if there is a gamma factor in shallow args if isinstance(x, gamma): return True if x.is_Add or x.is_Mul: return any(gamma_factor(xi) for xi in x.args) if x.is_Pow and (x.exp.is_integer or x.base.is_positive): return gamma_factor(x.base) return False # recursion step if level == 0: expr = expr.func(*[rule_gamma(x, level + 1) for x in expr.args]) level += 1 if not expr.is_Mul: return expr # non-commutative step if level == 1: args, nc = expr.args_cnc() if not args: return expr if nc: return rule_gamma(Mul._from_args(args), level + 1)*Mul._from_args(nc) level += 1 # pure gamma handling, not factor absorption if level == 2: T, F = sift(expr.args, gamma_factor, binary=True) gamma_ind = Mul(*F) d = Mul(*T) nd, dd = d.as_numer_denom() for ipass in range(2): args = list(ordered(Mul.make_args(nd))) for i, ni in enumerate(args): if ni.is_Add: ni, dd = Add(*[ rule_gamma(gamma_rat(a/dd), level + 1) for a in ni.args] ).as_numer_denom() args[i] = ni if not dd.has(gamma): break nd = Mul(*args) if ipass == 0 and not gamma_factor(nd): break nd, dd = dd, nd # now process in reversed order expr = gamma_ind*nd/dd if not (expr.is_Mul and (gamma_factor(dd) or gamma_factor(nd))): return expr level += 1 # iteration until constant if level == 3: while True: was = expr expr = rule_gamma(expr, 4) if expr == was: return expr numer_gammas = [] denom_gammas = [] numer_others = [] denom_others = [] def explicate(p): if p is S.One: return None, [] b, e = p.as_base_exp() if e.is_Integer: if isinstance(b, gamma): return True, [b.args[0]]*e else: return False, [b]*e else: return False, [p] newargs = list(ordered(expr.args)) while newargs: n, d = newargs.pop().as_numer_denom() isg, l = explicate(n) if isg: numer_gammas.extend(l) elif isg is False: numer_others.extend(l) isg, l = explicate(d) if isg: denom_gammas.extend(l) elif isg is False: denom_others.extend(l) # =========== level 2 work: pure gamma manipulation ========= if not as_comb: # Try to reduce the number of gamma factors by applying the # reflection formula gamma(x)*gamma(1-x) = pi/sin(pi*x) for gammas, numer, denom in [( numer_gammas, numer_others, denom_others), (denom_gammas, denom_others, numer_others)]: new = [] while gammas: g1 = gammas.pop() if g1.is_integer: new.append(g1) continue for i, g2 in enumerate(gammas): n = g1 + g2 - 1 if not n.is_Integer: continue numer.append(S.Pi) denom.append(sin(S.Pi*g1)) gammas.pop(i) if n > 0: for k in range(n): numer.append(1 - g1 + k) elif n < 0: for k in range(-n): denom.append(-g1 - k) break else: new.append(g1) # /!\ updating IN PLACE gammas[:] = new # Try to reduce the number of gammas by using the duplication # theorem to cancel an upper and lower: gamma(2*s)/gamma(s) = # 2**(2*s + 1)/(4*sqrt(pi))*gamma(s + 1/2). Although this could # be done with higher argument ratios like gamma(3*x)/gamma(x), # this would not reduce the number of gammas as in this case. for ng, dg, no, do in [(numer_gammas, denom_gammas, numer_others, denom_others), (denom_gammas, numer_gammas, denom_others, numer_others)]: while True: for x in ng: for y in dg: n = x - 2*y if n.is_Integer: break else: continue break else: break ng.remove(x) dg.remove(y) if n > 0: for k in range(n): no.append(2*y + k) elif n < 0: for k in range(-n): do.append(2*y - 1 - k) ng.append(y + S(1)/2) no.append(2**(2*y - 1)) do.append(sqrt(S.Pi)) # Try to reduce the number of gamma factors by applying the # multiplication theorem (used when n gammas with args differing # by 1/n mod 1 are encountered). # # run of 2 with args differing by 1/2 # # >>> gammasimp(gamma(x)*gamma(x+S.Half)) # 2*sqrt(2)*2**(-2*x - 1/2)*sqrt(pi)*gamma(2*x) # # run of 3 args differing by 1/3 (mod 1) # # >>> gammasimp(gamma(x)*gamma(x+S(1)/3)*gamma(x+S(2)/3)) # 6*3**(-3*x - 1/2)*pi*gamma(3*x) # >>> gammasimp(gamma(x)*gamma(x+S(1)/3)*gamma(x+S(5)/3)) # 2*3**(-3*x - 1/2)*pi*(3*x + 2)*gamma(3*x) # def _run(coeffs): # find runs in coeffs such that the difference in terms (mod 1) # of t1, t2, ..., tn is 1/n u = list(uniq(coeffs)) for i in range(len(u)): dj = ([((u[j] - u[i]) % 1, j) for j in range(i + 1, len(u))]) for one, j in dj: if one.p == 1 and one.q != 1: n = one.q got = [i] get = list(range(1, n)) for d, j in dj: m = n*d if m.is_Integer and m in get: get.remove(m) got.append(j) if not get: break else: continue for i, j in enumerate(got): c = u[j] coeffs.remove(c) got[i] = c return one.q, got[0], got[1:] def _mult_thm(gammas, numer, denom): # pull off and analyze the leading coefficient from each gamma arg # looking for runs in those Rationals # expr -> coeff + resid -> rats[resid] = coeff rats = {} for g in gammas: c, resid = g.as_coeff_Add() rats.setdefault(resid, []).append(c) # look for runs in Rationals for each resid keys = sorted(rats, key=default_sort_key) for resid in keys: coeffs = list(sorted(rats[resid])) new = [] while True: run = _run(coeffs) if run is None: break # process the sequence that was found: # 1) convert all the gamma functions to have the right # argument (could be off by an integer) # 2) append the factors corresponding to the theorem # 3) append the new gamma function n, ui, other = run # (1) for u in other: con = resid + u - 1 for k in range(int(u - ui)): numer.append(con - k) con = n*(resid + ui) # for (2) and (3) # (2) numer.append((2*S.Pi)**(S(n - 1)/2)* n**(S(1)/2 - con)) # (3) new.append(con) # restore resid to coeffs rats[resid] = [resid + c for c in coeffs] + new # rebuild the gamma arguments g = [] for resid in keys: g += rats[resid] # /!\ updating IN PLACE gammas[:] = g for l, numer, denom in [(numer_gammas, numer_others, denom_others), (denom_gammas, denom_others, numer_others)]: _mult_thm(l, numer, denom) # =========== level >= 2 work: factor absorption ========= if level >= 2: # Try to absorb factors into the gammas: x*gamma(x) -> gamma(x + 1) # and gamma(x)/(x - 1) -> gamma(x - 1) # This code (in particular repeated calls to find_fuzzy) can be very # slow. def find_fuzzy(l, x): if not l: return S1, T1 = compute_ST(x) for y in l: S2, T2 = inv[y] if T1 != T2 or (not S1.intersection(S2) and (S1 != set() or S2 != set())): continue # XXX we want some simplification (e.g. cancel or # simplify) but no matter what it's slow. a = len(cancel(x/y).free_symbols) b = len(x.free_symbols) c = len(y.free_symbols) # TODO is there a better heuristic? if a == 0 and (b > 0 or c > 0): return y # We thus try to avoid expensive calls by building the following # "invariants": For every factor or gamma function argument # - the set of free symbols S # - the set of functional components T # We will only try to absorb if T1==T2 and (S1 intersect S2 != emptyset # or S1 == S2 == emptyset) inv = {} def compute_ST(expr): if expr in inv: return inv[expr] return (expr.free_symbols, expr.atoms(Function).union( set(e.exp for e in expr.atoms(Pow)))) def update_ST(expr): inv[expr] = compute_ST(expr) for expr in numer_gammas + denom_gammas + numer_others + denom_others: update_ST(expr) for gammas, numer, denom in [( numer_gammas, numer_others, denom_others), (denom_gammas, denom_others, numer_others)]: new = [] while gammas: g = gammas.pop() cont = True while cont: cont = False y = find_fuzzy(numer, g) if y is not None: numer.remove(y) if y != g: numer.append(y/g) update_ST(y/g) g += 1 cont = True y = find_fuzzy(denom, g - 1) if y is not None: denom.remove(y) if y != g - 1: numer.append((g - 1)/y) update_ST((g - 1)/y) g -= 1 cont = True new.append(g) # /!\ updating IN PLACE gammas[:] = new # =========== rebuild expr ================================== return Mul(*[gamma(g) for g in numer_gammas]) \ / Mul(*[gamma(g) for g in denom_gammas]) \ * Mul(*numer_others) / Mul(*denom_others)
def solve_univariate_inequality(expr, gen, relational=True, domain=S.Reals, continuous=False): """Solves a real univariate inequality. Parameters ========== expr : Relational The target inequality gen : Symbol The variable for which the inequality is solved relational : bool A Relational type output is expected or not domain : Set The domain over which the equation is solved continuous: bool True if expr is known to be continuous over the given domain (and so continuous_domain() doesn't need to be called on it) Raises ====== NotImplementedError The solution of the inequality cannot be determined due to limitation in `solvify`. Notes ===== Currently, we cannot solve all the inequalities due to limitations in `solvify`. Also, the solution returned for trigonometric inequalities are restricted in its periodic interval. See Also ======== solvify: solver returning solveset solutions with solve's output API Examples ======== >>> from sympy.solvers.inequalities import solve_univariate_inequality >>> from sympy import Symbol, sin, Interval, S >>> x = Symbol('x') >>> solve_univariate_inequality(x**2 >= 4, x) ((2 <= x) & (x < oo)) | ((x <= -2) & (-oo < x)) >>> solve_univariate_inequality(x**2 >= 4, x, relational=False) Union(Interval(-oo, -2), Interval(2, oo)) >>> domain = Interval(0, S.Infinity) >>> solve_univariate_inequality(x**2 >= 4, x, False, domain) Interval(2, oo) >>> solve_univariate_inequality(sin(x) > 0, x, relational=False) Interval.open(0, pi) """ from sympy import im from sympy.calculus.util import (continuous_domain, periodicity, function_range) from sympy.solvers.solvers import denoms from sympy.solvers.solveset import solveset_real, solvify, solveset from sympy.solvers.solvers import solve # This keeps the function independent of the assumptions about `gen`. # `solveset` makes sure this function is called only when the domain is # real. _gen = gen _domain = domain if gen.is_real is False: rv = S.EmptySet return rv if not relational else rv.as_relational(_gen) elif gen.is_real is None: gen = Dummy('gen', real=True) try: expr = expr.xreplace({_gen: gen}) except TypeError: raise TypeError(filldedent(''' When gen is real, the relational has a complex part which leads to an invalid comparison like I < 0. ''')) rv = None if expr is S.true: rv = domain elif expr is S.false: rv = S.EmptySet else: e = expr.lhs - expr.rhs period = periodicity(e, gen) if period is not None: frange = function_range(e, gen, domain) rel = expr.rel_op if rel == '<' or rel == '<=': if expr.func(frange.sup, 0): rv = domain elif not expr.func(frange.inf, 0): rv = S.EmptySet elif rel == '>' or rel == '>=': if expr.func(frange.inf, 0): rv = domain elif not expr.func(frange.sup, 0): rv = S.EmptySet inf, sup = domain.inf, domain.sup if sup - inf is S.Infinity: domain = Interval(0, period, False, True) if rv is None: n, d = e.as_numer_denom() try: if gen not in n.free_symbols and len(e.free_symbols) > 1: raise ValueError # this might raise ValueError on its own # or it might give None... solns = solvify(e, gen, domain) if solns is None: # in which case we raise ValueError raise ValueError except (ValueError, NotImplementedError): raise NotImplementedError(filldedent(''' The inequality cannot be solved using solve_univariate_inequality. ''')) expanded_e = expand_mul(e) def valid(x): # this is used to see if gen=x satisfies the # relational by substituting it into the # expanded form and testing against 0, e.g. # if expr = x*(x + 1) < 2 then e = x*(x + 1) - 2 # and expanded_e = x**2 + x - 2; the test is # whether a given value of x satisfies # x**2 + x - 2 < 0 # # expanded_e, expr and gen used from enclosing scope v = expanded_e.subs(gen, x) try: r = expr.func(v, 0) except TypeError: r = S.false if r in (S.true, S.false): return r if v.is_real is False: return S.false else: v = v.n(2) if v.is_comparable: return expr.func(v, 0) # not comparable or couldn't be evaluated raise NotImplementedError( 'relationship did not evaluate: %s' % r) singularities = [] for d in denoms(expr, gen): singularities.extend(solvify(d, gen, domain)) if not continuous: domain = continuous_domain(e, gen, domain) include_x = '=' in expr.rel_op and expr.rel_op != '!=' try: discontinuities = set(domain.boundary - FiniteSet(domain.inf, domain.sup)) # remove points that are not between inf and sup of domain critical_points = FiniteSet(*(solns + singularities + list( discontinuities))).intersection( Interval(domain.inf, domain.sup, domain.inf not in domain, domain.sup not in domain)) if all(r.is_number for r in critical_points): reals = _nsort(critical_points, separated=True)[0] else: from sympy.utilities.iterables import sift sifted = sift(critical_points, lambda x: x.is_real) if sifted[None]: # there were some roots that weren't known # to be real raise NotImplementedError try: reals = sifted[True] if len(reals) > 1: reals = list(sorted(reals)) except TypeError: raise NotImplementedError except NotImplementedError: raise NotImplementedError('sorting of these roots is not supported') #If expr contains imaginary coefficients #Only real values of x for which the imaginary part is 0 are taken make_real = S.Reals if im(expanded_e) != S.Zero: check = True im_sol = FiniteSet() try: a = solveset(im(expanded_e), gen, domain) if not isinstance(a, Interval): for z in a: if z not in singularities and valid(z) and z.is_real: im_sol += FiniteSet(z) else: start, end = a.inf, a.sup for z in _nsort(critical_points + FiniteSet(end)): valid_start = valid(start) if start != end: valid_z = valid(z) pt = _pt(start, z) if pt not in singularities and pt.is_real and valid(pt): if valid_start and valid_z: im_sol += Interval(start, z) elif valid_start: im_sol += Interval.Ropen(start, z) elif valid_z: im_sol += Interval.Lopen(start, z) else: im_sol += Interval.open(start, z) start = z for s in singularities: im_sol -= FiniteSet(s) except (TypeError): im_sol = S.Reals check = False if isinstance(im_sol, EmptySet): raise ValueError(filldedent(''' %s contains imaginary parts which cannot be made 0 for any value of %s satisfying the inequality, leading to relations like I < 0. ''' % (expr.subs(gen, _gen), _gen))) make_real = make_real.intersect(im_sol) empty = sol_sets = [S.EmptySet] start = domain.inf if valid(start) and start.is_finite: sol_sets.append(FiniteSet(start)) for x in reals: end = x if valid(_pt(start, end)): sol_sets.append(Interval(start, end, True, True)) if x in singularities: singularities.remove(x) else: if x in discontinuities: discontinuities.remove(x) _valid = valid(x) else: # it's a solution _valid = include_x if _valid: sol_sets.append(FiniteSet(x)) start = end end = domain.sup if valid(end) and end.is_finite: sol_sets.append(FiniteSet(end)) if valid(_pt(start, end)): sol_sets.append(Interval.open(start, end)) if im(expanded_e) != S.Zero and check: rv = (make_real).intersect(_domain) else: rv = Intersection( (Union(*sol_sets)), make_real, _domain).subs(gen, _gen) return rv if not relational else rv.as_relational(_gen)
def _sort_args(self, args): lists, atoms = sift(args, lambda a: isinstance(a, (tuple, list)), binary=True) return atoms, lists
def test_sift(): assert sift(range(5), lambda _: _ % 2) == {1: [1, 3], 0: [0, 2, 4]} assert sift([x, y], lambda _: _.has(x)) == {False: [y], True: [x]} assert sift([S.One], lambda _: _.has(x)) == {False: [1]}
def MatMul_elements(matrix_predicate, scalar_predicate, expr, assumptions): d = sift(expr.args, lambda x: isinstance(x, MatrixExpr)) factors, matrices = d[False], d[True] return fuzzy_and([ test_closed_group(Basic(*factors), assumptions, scalar_predicate), test_closed_group(Basic(*matrices), assumptions, matrix_predicate)])
def _separate_sq(p): """ helper function for ``_minimal_polynomial_sq`` It selects a rational ``g`` such that the polynomial ``p`` consists of a sum of terms whose surds squared have gcd equal to ``g`` and a sum of terms with surds squared prime with ``g``; then it takes the field norm to eliminate ``sqrt(g)`` See simplify.simplify.split_surds and polytools.sqf_norm. Examples ======== >>> from sympy import sqrt >>> from sympy.abc import x >>> from sympy.polys.numberfields import _separate_sq >>> p= -x + sqrt(2) + sqrt(3) + sqrt(7) >>> p = _separate_sq(p); p -x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8 >>> p = _separate_sq(p); p -x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20 >>> p = _separate_sq(p); p -x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400 """ from sympy.simplify.simplify import _split_gcd, _mexpand from sympy.utilities.iterables import sift def is_sqrt(expr): return expr.is_Pow and expr.exp is S.Half # p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)] a = [] for y in p.args: if not y.is_Mul: if is_sqrt(y): a.append((S.One, y**2)) elif y.is_Atom: a.append((y, S.One)) elif y.is_Pow and y.exp.is_integer: a.append((y, S.One)) else: raise NotImplementedError continue sifted = sift(y.args, is_sqrt) a.append((Mul(*sifted[False]), Mul(*sifted[True])**2)) a.sort(key=lambda z: z[1]) if a[-1][1] is S.One: # there are no surds return p surds = [z for y, z in a] for i in range(len(surds)): if surds[i] != 1: break g, b1, b2 = _split_gcd(*surds[i:]) a1 = [] a2 = [] for y, z in a: if z in b1: a1.append(y*z**S.Half) else: a2.append(y*z**S.Half) p1 = Add(*a1) p2 = Add(*a2) p = _mexpand(p1**2) - _mexpand(p2**2) return p
# short entries will be ignored. The ORDER MATTERS # so don't re-order the lines for a given address. # Other tidying up could be done but we won't do that here. def short_entry(line): if line.count('<') == 2: if line.split('>', 1)[1].split('<')[0].strip(): return False return True if len(who[k]) == 1: line = who[k][0] if not line.strip(): continue # ignore blank lines out.append(line) else: uniq = list(OrderedDict.fromkeys(who[k])) short, long = sift(uniq, short_entry, binary=True) out.extend(long) out.extend(short) if out != lines or not blankline: # write lines with codecs.open(os.path.realpath(os.path.join( __file__, os.path.pardir, os.path.pardir, ".mailmap")), "w", "utf-8") as fd: fd.write('\n'.join(out)) fd.write('\n') print() if out != lines: print(yellow('.mailmap lines were re-ordered.')) else: print(yellow('blank line added to end of .mailmap'))
def _matches_commutative(self, expr, repl_dict={}, old=False): """ Matches Add/Mul "pattern" to an expression "expr". repl_dict ... a dictionary of (wild: expression) pairs, that get returned with the results This function is the main workhorse for Add/Mul. For instance: >>> from sympy import symbols, Wild, sin >>> a = Wild("a") >>> b = Wild("b") >>> c = Wild("c") >>> x, y, z = symbols("x y z") >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z) {a_: x, b_: y, c_: z} In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is the expression. The repl_dict contains parts that were already matched. For example here: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x}) {a_: x, b_: y, c_: z} the only function of the repl_dict is to return it in the result, e.g. if you omit it: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z) {b_: y, c_: z} the "a: x" is not returned in the result, but otherwise it is equivalent. """ # make sure expr is Expr if pattern is Expr from .expr import Add, Expr from sympy import Mul if isinstance(self, Expr) and not isinstance(expr, Expr): return None # handle simple patterns if self == expr: return repl_dict d = self._matches_simple(expr, repl_dict) if d is not None: return d # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2) from .function import WildFunction from .symbol import Wild wild_part, exact_part = sift(self.args, lambda p: p.has(Wild, WildFunction) and not expr.has(p), binary=True) if not exact_part: wild_part = list(ordered(wild_part)) else: exact = self._new_rawargs(*exact_part) free = expr.free_symbols if free and (exact.free_symbols - free): # there are symbols in the exact part that are not # in the expr; but if there are no free symbols, let # the matching continue return None newexpr = self._combine_inverse(expr, exact) if not old and (expr.is_Add or expr.is_Mul): if newexpr.count_ops() > expr.count_ops(): return None newpattern = self._new_rawargs(*wild_part) return newpattern.matches(newexpr, repl_dict) # now to real work ;) i = 0 saw = set() while expr not in saw: saw.add(expr) expr_list = (self.identity,) + tuple(ordered(self.make_args(expr))) for last_op in reversed(expr_list): for w in reversed(wild_part): d1 = w.matches(last_op, repl_dict) if d1 is not None: d2 = self.xreplace(d1).matches(expr, d1) if d2 is not None: return d2 if i == 0: if self.is_Mul: # make e**i look like Mul if expr.is_Pow and expr.exp.is_Integer: if expr.exp > 0: expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False) else: expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False) i += 1 continue elif self.is_Add: # make i*e look like Add c, e = expr.as_coeff_Mul() if abs(c) > 1: if c > 0: expr = Add(*[e, (c - 1)*e], evaluate=False) else: expr = Add(*[-e, (c + 1)*e], evaluate=False) i += 1 continue # try collection on non-Wild symbols from sympy.simplify.radsimp import collect was = expr did = set() for w in reversed(wild_part): c, w = w.as_coeff_mul(Wild) free = c.free_symbols - did if free: did.update(free) expr = collect(expr, free) if expr != was: i += 0 continue break # if we didn't continue, there is nothing more to do return