def test_issue_5526(): assert reduce_inequalities(S(0) <= x + Integral(y**2, (y, 1, 3)) - 1, True, [x]) == \ (-Integral(y**2, (y, 1, 3)) + 1 <= x) f = Function('f') e = Sum(f(x), (x, 1, 3)) assert reduce_inequalities(S(0) <= x + e + y**2, True, [x]) == \ (-y**2 - Sum(f(x), (x, 1, 3)) <= x)
def test_reduce_inequalities_errors(): x = Symbol('x') y = Symbol('y') raises(NotImplementedError, lambda: reduce_inequalities(Ge(sin(x) + x, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(x**2*y + y, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(sqrt(2)*x, 1)))
def test_issue_5526(): assert reduce_inequalities(0 <= x + Integral(y**2, (y, 1, 3)) - 1, [x]) == \ (x >= -Integral(y**2, (y, 1, 3)) + 1) f = Function('f') e = Sum(f(x), (x, 1, 3)) assert reduce_inequalities(0 <= x + e + y**2, [x]) == \ (x >= -y**2 - Sum(f(x), (x, 1, 3)))
def test_reduce_inequalities_errors(): x = Symbol('x') y = Symbol('y') raises(NotImplementedError, lambda: reduce_inequalities(Ge(sin(x) + x, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(x**2 * y + y, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(sqrt(2) * x, 1)))
def test_reduce_abs_inequalities(): real = Q.real(x) assert reduce_inequalities(abs(x - 5) < 3, assume=real) == And(Gt(x, 2), Lt(x, 8)) assert reduce_inequalities(abs(2*x + 3) >= 8, assume=real) == Or(Le(x, -S(11)/2), Ge(x, S(5)/2)) assert reduce_inequalities(abs(x - 4) + abs(3*x - 5) < 7, assume=real) == And(Gt(x, S(1)/2), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7, assume=real) == Or(And(-2 < x, x < -1), And(S(1)/2 < x, x < 4)) raises(NotImplementedError, "reduce_inequalities(abs(x - 5) < 3)")
def test_issue_10198(): assert reduce_inequalities(-1 + 1 / abs(1 / x - 1) < 0) == Or( And(-oo < x, x < 0), And(S.Zero < x, x < S.Half)) assert reduce_inequalities(abs(1 / sqrt(x)) - 1, x) == Eq(x, 1) assert reduce_abs_inequality(-3 + 1/abs(1 - 1/x), '<', x) == \ Or(And(-oo < x, x < 0), And(S.Zero < x, x < Rational(3, 4)), And(Rational(3, 2) < x, x < oo)) raises( ValueError, lambda: reduce_abs_inequality(-3 + 1 / abs(1 - 1 / sqrt(x)), '<', x))
def test_issue_10198(): assert reduce_inequalities(-1 + 1 / abs(1 / x - 1) < 0) == Or( And(-oo < x, x < 0), And(S(0) < x, x < S(1) / 2)) assert reduce_inequalities(abs(1 / sqrt(x)) - 1, x) == Eq(x, 1) assert reduce_abs_inequality(-3 + 1/abs(1 - 1/x), '<', x) == \ Or(And(-oo < x, x < 0), And(S(0) < x, x < S(3)/4), And(S(3)/2 < x, x < oo)) raises( ValueError, lambda: reduce_abs_inequality(-3 + 1 / abs(1 - 1 / sqrt(x)), '<', x))
def test_issue_10198(): assert reduce_inequalities( -1 + 1/abs(1/x - 1) < 0) == Or( And(-oo < x, x < 0), And(S(0) < x, x < S(1)/2) ) assert reduce_inequalities(abs(1/sqrt(x)) - 1, x) == Eq(x, 1) assert reduce_abs_inequality(-3 + 1/abs(1 - 1/x), '<', x) == \ Or(And(-oo < x, x < 0), And(S(0) < x, x < S(3)/4), And(S(3)/2 < x, x < oo)) raises(ValueError,lambda: reduce_abs_inequality(-3 + 1/abs( 1 - 1/sqrt(x)), '<', x))
def test_reduce_abs_inequalities(): real = Q.real(x) assert reduce_inequalities( abs(x - 5) < 3, assume=real) == And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities( abs(2*x + 3) >= 8, assume=real) == Or(And(Le(S(5)/2, x), Lt(x, oo)), And(Le(x, -S(11)/2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs( 3*x - 5) < 7, assume=real) == And(Lt(S(1)/2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7, assume=real) == Or(And(S(-2) < x, x < -1), And(S(1)/2 < x, x < 4)) raises(NotImplementedError, lambda: reduce_inequalities(abs(x - 5) < 3))
def test_reduce_abs_inequalities(): x = Symbol('x', real=True) assert reduce_inequalities(abs(x - 5) < 3) == And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities( abs(2*x + 3) >= 8) == Or(And(Le(S(5)/2, x), Lt(x, oo)), And(Le(x, -S(11)/2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs( 3*x - 5) < 7) == And(Lt(S(1)/2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7) == \ Or(And(S(-2) < x, x < -1), And(S(1)/2 < x, x < 4)) x = Symbol('x') raises(NotImplementedError, lambda: reduce_inequalities(abs(x - 5) < 3))
def test_reduce_abs_inequalities(): x = Symbol('x', real=True) assert reduce_inequalities(abs(x - 5) < 3) == And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities(abs(2 * x + 3) >= 8) == Or( And(Le(S(5) / 2, x), Lt(x, oo)), And(Le(x, -S(11) / 2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs(3 * x - 5) < 7) == And( Lt(S(1) / 2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7) == \ Or(And(S(-2) < x, x < -1), And(S(1)/2 < x, x < 4)) x = Symbol('x') raises(NotImplementedError, lambda: reduce_inequalities(abs(x - 5) < 3))
def test_reduce_abs_inequalities(): real = Q.real(x) assert reduce_inequalities(abs(x - 5) < 3, assume=real) == And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities(abs(2 * x + 3) >= 8, assume=real) == Or( And(Le(S(5) / 2, x), Lt(x, oo)), And(Le(x, -S(11) / 2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs(3 * x - 5) < 7, assume=real) == And(Lt(S(1) / 2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3 * abs(x) - 5) < 7, assume=real) == Or(And(S(-2) < x, x < -1), And(S(1) / 2 < x, x < 4)) raises(NotImplementedError, lambda: reduce_inequalities(abs(x - 5) < 3))
def test_issue_8545(): from sympy import refine eq = 1 - x - abs(1 - x) ans = And(Lt(1, x), Lt(x, oo)) assert reduce_abs_inequality(eq, '<', x) == ans eq = 1 - x - sqrt((1 - x)**2) assert reduce_inequalities(refine(eq) < 0) == ans
def test_issue_8235(): x = Symbol('x', real=True) assert reduce_inequalities(x**2 - 1 < 0) == \ And(S(-1) < x, x < S(1)) assert reduce_inequalities(x**2 - 1 <= 0) == \ And(S(-1) <= x, x <= 1) assert reduce_inequalities(x**2 - 1 > 0) == \ Or(And(-oo < x, x < -1), And(x < oo, S(1) < x)) assert reduce_inequalities(x**2 - 1 >= 0) == \ Or(And(-oo < x, x <= S(-1)), And(S(1) <= x, x < oo)) eq = x**8 + x**2 - 9 sol = solve(eq >= 0) known_sol = Or(And(-oo < x, RootOf(x**8 + x**2 - 9, 1) <= x, x < oo), \ And(-oo < x, x < oo, x <= RootOf(x**8 + x**2 - 9, 0))) assert sol == known_sol
def test_reduce_inequalities_multivariate(): x = Symbol('x') y = Symbol('y') assert reduce_inequalities([Ge(x**2, 1), Ge(y**2, 1)]) == \ And(Eq(im(x), 0), Eq(im(y), 0), Or(And(Le(1, re(x)), Lt(re(x), oo)), And(Le(re(x), -1), Lt(-oo, re(x)))), Or(And(Le(1, re(y)), Lt(re(y), oo)), And(Le(re(y), -1), Lt(-oo, re(y)))))
def test_issue_8235(): assert reduce_inequalities(x**2 - 1 < 0) == \ And(S(-1) < x, x < S(1)) assert reduce_inequalities(x**2 - 1 <= 0) == \ And(S(-1) <= x, x <= 1) assert reduce_inequalities(x**2 - 1 > 0) == \ Or(And(-oo < x, x < -1), And(x < oo, S(1) < x)) assert reduce_inequalities(x**2 - 1 >= 0) == \ Or(And(-oo < x, x <= S(-1)), And(S(1) <= x, x < oo)) eq = x**8 + x - 9 # we want CRootOf solns here sol = solve(eq >= 0) tru = Or(And(rootof(eq, 1) <= x, x < oo), And(-oo < x, x <= rootof(eq, 0))) assert sol == tru # recast vanilla as real assert solve(sqrt((-x + 1)**2) < 1) == And(S(0) < x, x < 2)
def test_reduce_abs_inequalities(): e = abs(x - 5) < 3 ans = And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities(e) == ans assert reduce_inequalities(e, x) == ans assert reduce_inequalities(abs(x - 5)) == Eq(x, 5) assert reduce_inequalities(abs(2 * x + 3) >= 8) == Or( And(Le(S(5) / 2, x), Lt(x, oo)), And(Le(x, -S(11) / 2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs(3 * x - 5) < 7) == And( Lt(S(1) / 2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7) == \ Or(And(S(-2) < x, x < -1), And(S(1)/2 < x, x < 4)) nr = Symbol('nr', extended_real=False) raises(TypeError, lambda: reduce_inequalities(abs(nr - 5) < 3)) assert reduce_inequalities(x < 3, symbols=[x, nr]) == And(-oo < x, x < 3)
def test_reduce_abs_inequalities(): e = abs(x - 5) < 3 ans = And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities(e) == ans assert reduce_inequalities(e, x) == ans assert reduce_inequalities(abs(x - 5)) == Eq(x, 5) assert reduce_inequalities( abs(2*x + 3) >= 8) == Or(And(Le(S(5)/2, x), Lt(x, oo)), And(Le(x, -S(11)/2), Lt(-oo, x))) assert reduce_inequalities(abs(x - 4) + abs( 3*x - 5) < 7) == And(Lt(S(1)/2, x), Lt(x, 4)) assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7) == \ Or(And(S(-2) < x, x < -1), And(S(1)/2 < x, x < 4)) nr = Symbol('nr', real=False) raises(TypeError, lambda: reduce_inequalities(abs(nr - 5) < 3)) assert reduce_inequalities(x < 3, symbols=[x, nr]) == And(-oo < x, x < 3)
def test_reduce_abs_inequalities(): e = abs(x - 5) < 3 ans = And(Lt(2, x), Lt(x, 8)) assert reduce_inequalities(e) == ans assert reduce_inequalities(e, x) == ans assert reduce_inequalities(abs(x - 5)) == Eq(x, 5) assert reduce_inequalities(abs(2 * x + 3) >= 8) == Or( And(Le(Rational(5, 2), x), Lt(x, oo)), And(Le(x, Rational(-11, 2)), Lt(-oo, x)) ) assert reduce_inequalities(abs(x - 4) + abs(3 * x - 5) < 7) == And( Lt(S.Half, x), Lt(x, 4) ) assert reduce_inequalities(abs(x - 4) + abs(3 * abs(x) - 5) < 7) == Or( And(S(-2) < x, x < -1), And(S.Half < x, x < 4) ) nr = Symbol("nr", extended_real=False) raises(TypeError, lambda: reduce_inequalities(abs(nr - 5) < 3)) assert reduce_inequalities(x < 3, symbols=[x, nr]) == And(-oo < x, x < 3)
def test_reduce_inequalities_boolean(): assert reduce_inequalities( [Eq(x**2, 0), True]) == Eq(x, 0) assert reduce_inequalities([Eq(x**2, 0), False]) == False assert reduce_inequalities(x**2 >= 0) is S.true # issue 10196
def test_reduce_inequalities_general(): assert reduce_inequalities(Ge(sqrt(2)*x, 1)) == And(sqrt(2)/2 <= x, x < oo) assert reduce_inequalities(PurePoly(x + 1, x) > 0) == And(S(-1) < x, x < oo)
def test_N2(): with assuming(Q.real(x)): assert reduce_inequalities(x**4 - x + 1 > 0) is True assert reduce_inequalities(x**4 - x + 1 > 1) is Or(Lt(1, x), Lt(x, 0))
def _solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympified_list(w): return map(sympify, iff(isinstance(w,(list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not iterable(f) f, symbols = (sympified_list(w) for w in [f, symbols]) for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) if not symbols: #get symbols from equations or supply dummy symbols since #solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy('x')]) symbols = list(symbols) symbols.sort(key=Basic.sort_key) if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if bare_f: f = f[0] # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if soln is None: n, d = solve_linear(f, x=symbols) if n.is_Symbol: soln = {n: cancel(d)} if soln: if symbol_swapped and isinstance(soln, dict): return dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) # reject any result that makes Q affirmatively 0; # if in doubt, keep it try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [s for s in soln if checksol(f, {symbol: s}) is not False] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f*symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [s for s in sols if checksol(f, {symbol: s}) is not False] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: # reject any result that makes any dens affirmatively 0, # if in doubt, keep it soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] if result is False: raise NotImplementedError(msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols, extension=True) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None soln = solve_linear_system(matrix, *symbols, **flags) # Use swap_dict to ensure we return the same type as what was # passed; this is not necessary in the poly-system case which # only supports zero-dimensional systems if symbol_swapped and soln: soln = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] return solve_poly_system(polys)
def test_hacky_inequalities(): assert reduce_inequalities(x + y < 1, symbols=[x]) == (x < 1 - y) assert reduce_inequalities(x + y >= 1, symbols=[x]) == (x >= 1 - y) assert reduce_inequalities(Eq(0, x - y), symbols=[x]) == Eq(x, y) assert reduce_inequalities(Ne(0, x - y), symbols=[x]) == Ne(x, y)
def test_reduce_inequalities_multivariate(): assert reduce_inequalities([Ge(x**2, 1), Ge(y**2, 1)]) == \ And(And(Or(Le(re(x), -1), Le(1, re(x))), Eq(im(x), 0)), And(Or(Le(re(y), -1), Le(1, re(y))), Eq(im(y), 0)))
def test_reduce_inequalities_assume(): assert reduce_inequalities( [Le(x**2, 1), Q.real(x)]) == And(Le(-1, x), Le(x, 1)) assert reduce_inequalities( [Le(x**2, 1)], Q.real(x)) == And(Le(-1, x), Le(x, 1))
def test_reduce_inequalities_general(): assert reduce_inequalities(Ge(sqrt(2) * x, 1)) == And(sqrt(2) / 2 <= x, x < oo) assert reduce_inequalities(PurePoly(x + 1, x) > 0) == And( S(-1) < x, x < oo)
def solve(f, *symbols, **flags): """ Algebraically solves equations and systems of equations. Currently supported are: - univariate polynomial, - transcendental - piecewise combinations of the above - systems of linear and polynomial equations - sytems containing relational expressions. Input is formed as: f - a single Expr or Poly that must be zero, - an Equality - a Relational expression or boolean - iterable of one or more of the above symbols (Symbol, Function or Derivative) specified as - none given (all free symbols will be used) - single symbol - denested list of symbols e.g. solve(f, x, y) - ordered iterable of symbols e.g. solve(f, [x, y]) flags - ``simplified``, when False, will not simplify solutions (default=True except for polynomials of order 3 or greater) - ``warning``, when True, will warn every time a solution can not be checked, or assumptions about a variable can't be verified for a solution. The output varies according to the input and can be seen by example: >>> from sympy import solve, Poly, Eq, Function, exp >>> from sympy.abc import x, y, z, a, b o boolean or univariate Relational >>> solve(x < 3) And(im(x) == 0, re(x) < 3) o single expression and single symbol that is in the expression >>> solve(x - y, x) [y] >>> solve(x - 3, x) [3] >>> solve(Eq(x, 3), x) [3] >>> solve(Poly(x - 3), x) [3] >>> solve(x**2 - y**2, x) [y, -y] >>> solve(x**4 - 1, x) [1, -1, -I, I] o single expression with no symbol that is in the expression >>> solve(3, x) [] >>> solve(x - 3, y) [] o when no symbol is given then all free symbols will be used and sorted with default_sort_key and the result will be the same as above as if those symbols had been supplied >>> solve(x - 3) [3] >>> solve(x**2 - y**2) [y, -y] o when a Function or Derivative is given as a symbol, it is isolated algebraically and an implicit solution may be obtained >>> f = Function('f') >>> solve(f(x) - x, f(x)) [x] >>> solve(f(x).diff(x) - f(x) - x, f(x).diff(x)) [x + f(x)] o single expression and more than 1 symbol when there is a linear solution >>> solve(x - y**2, x, y) {x: y**2} >>> solve(x**2 - y, x, y) {y: x**2} when undetermined coefficients are identified that are linear >>> solve((a + b)*x - b + 2, a, b) {a: -2, b: 2} that are nonlinear >>> solve((a + b)*x - b**2 + 2, a, b) [(-2**(1/2), 2**(1/2)), (2**(1/2), -2**(1/2))] if there is no linear solution then the first successful attempt for a nonlinear solution will be returned >>> solve(x**2 - y**2, x, y) [y, -y] >>> solve(x**2 - y**2/exp(x), x, y) [x*exp(x/2), -x*exp(x/2)] o iterable of one or more of the above involving relationals or bools >>> solve([x < 3, x - 2]) And(im(x) == 0, re(x) == 2) >>> solve([x > 3, x - 2]) False when the system is linear with a solution >>> solve([x - 3], x) {x: 3} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y, z) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - z), z, x, y) {x: -5*y + 2, z: 21*y - 6} without a solution >>> solve([x + 3, x - 3]) when the system is not linear >>> solve([x**2 + y -2, y**2 - 4], x, y) [(-2, -2), (0, 2), (0, 2), (2, -2)] Warning: there is a possibility of obtaining ambiguous results if no symbols are given for a nonlinear system of equations or are given as a set since the symbols are not presently reported with the solution. A warning will be issued in this situation. >>> solve([x - 2, x**2 + y]) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, y] [(2, -4)] >>> solve([x - 2, x**2 + f(x)], set([f(x), x])) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, f(x)] [(2, -4)] If two variables (or more) don't appear in the result, the assumptions can't be checked. >>> solve(z**2*x**2 - z**2*y**2/exp(x), x, y, z, warning=True) <BLANKLINE> Warning: assumptions can't be checked (can't find for which variable equation was solved). [x*exp(x/2), -x*exp(x/2)] Presently, assumptions aren't checked either when `solve()` input involves relationals or bools. See also: rsolve() for solving recurrence relationships dsolve() for solving differential equations """ # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. ########################################################################### def sympified_list(w): return map(sympify, w if iterable(w) else [w]) bare_f = not iterable(f) ordered_symbols = (symbols and symbols[0] and (isinstance(symbols[0], Symbol) or is_sequence(symbols[0], include=GeneratorType) ) ) f, symbols = (sympified_list(w) for w in [f, symbols]) # preprocess equation(s) ########################################################################### for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f[i] = piecewise_fold(f[i]) # preprocess symbol(s) ########################################################################### if not symbols: # get symbols from equations or supply dummy symbols so solve(3) behaves # like solve(3, x). symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy()]) ordered_symbols = False elif len(symbols) == 1 and iterable(symbols[0]): symbols = symbols[0] if not ordered_symbols: # we do this to make the results returned canonical in case f # contains a system of nonlinear equations; all other cases should # be unambiguous symbols = sorted(symbols, key=lambda i: i.sort_key()) # we can solve for Function and Derivative instances by replacing them # with Dummy symbols symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: msg = 'expected Symbol, Function or Derivative but got %s' raise TypeError(msg % type(s)) symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new # # try to get a solution ########################################################################### if bare_f: # pass f the way it was passed to solve; if it wasn't a list then # a list of solutions will be returned, otherwise a dictionary is # going to be returned f = f[0] solution = _solve(f, *symbols, **flags) # # postprocessing ########################################################################### # Restore original Functions and Derivatives if a dictionary is returned. # This is not necessary for # - the single equation, single unknown case # since the symbol will have been removed from the solution; # - the nonlinear poly_system since that only support zero-dimensional # systems and those results come back as a list if symbol_swapped and type(solution) is dict: solution = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in solution.iteritems()]) # warn if ambiguous results are being obtained # XXX agree on how to make this unambiguous # see issue 2405 for logic in how Polys chooses ordering and # for discussion of what to return see http://groups.google.com/group/sympy # Apr 18, 2011 posting 'using results from solve' elif (not ordered_symbols and len(symbols) > 1 and solution and is_sequence(solution) and is_sequence(solution[0]) and any(len(set(s)) > 1 for s in solution) ): msg = ('\n\tFor nonlinear systems of equations, symbols should be' + '\n\tgiven as a list so as to avoid ambiguity in the results.' + '\n\tsolve sorted the symbols as %s') if symbol_swapped: from itertools import izip tmp = izip(*swap_dict) # separate for the benefit of 2to3 print msg % list(tmp.next()) else: print msg % symbols # Get assumptions about symbols, to filter solutions. # Note that if assumptions about a solution can't be verified, it is still returned. # XXX: Currently, there are some cases which are not handled, # see issue 2098 comment 13: http://code.google.com/p/sympy/issues/detail?id=2098#c13. warn = flags.get('warning', False) if type(solution) is list: if solution: unchecked = [] filtered = [] if type(solution[0]) is tuple: for sol in solution: full_check = True for symb, val in zip(symbols, sol): test = check_assumptions(val, **symb.assumptions0) if test is None: full_check = False if test is False: # not None nor True break if test is not False: filtered.append(sol) if not full_check: unchecked.append(sol) solution = filtered else: if len(symbols) != 1: # find which one was solved for symbols = list(f.free_symbols - set.union(*(s.free_symbols for s in solution))) if len(symbols) == 1: for sol in solution: test = check_assumptions(sol, **symbols[0].assumptions0) if test is None: unchecked.append(sol) if test is not False: # None or True filtered.append(sol) solution = filtered else: if warn: print("\n\tWarning: assumptions can't be checked" "\n\t(can't find for which variable equation was solved).") if warn and unchecked: print("\n\tWarning: assumptions concerning following solution(s) can't be checked:" + '\n\t' + ', '.join(str(s) for s in unchecked)) elif type(solution) is dict: full_check = True for symb, val in solution.iteritems(): test = check_assumptions(val, **symb.assumptions0) if test is None: full_check = False if test is False: # not None nor True solution = None break if warn and not full_check: print("\n\tWarning: assumptions concerning solution can't be checked.") elif isinstance(solution, (Relational, And, Or)): assert len(symbols) == 1 if warn and symbols[0].assumptions0: print("\n\tWarning: assumptions about variable '%s' are not handled currently." %symbols[0]) # TODO: check also variable assumptions for inequalities elif solution is not None: raise TypeError('Unrecognized solution') # improve the checker to handle this # # done ########################################################################### return solution
def test_N3(): with assuming(Q.real(x)): assert reduce_inequalities(abs(x) < 1) is And(Lt(-1, x), Lt(x, 1))
def test_hacky_inequalities(): assert reduce_inequalities(x + y < 1, symbols=[x]) == (x < 1 - y) assert reduce_inequalities(x + y >= 1, symbols=[x]) == (x >= 1 - y)
def test_reduce_inequalities_boolean(): assert reduce_inequalities([Eq(x**2, 0), True]) == Eq(x, 0) assert reduce_inequalities([Eq(x**2, 0), False]) == False
def _solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympified_list(w): return map(sympify, iff(isinstance(w, (list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not iterable(f) f, symbols = (sympified_list(w) for w in [f, symbols]) for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) if not symbols: #get symbols from equations or supply dummy symbols since #solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy('x')]) symbols = list(symbols) symbols.sort(key=Basic.sort_key) if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if bare_f: f = f[0] # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if soln is None: n, d = solve_linear(f, x=symbols) if n.is_Symbol: soln = {n: cancel(d)} if soln: if symbol_swapped and isinstance(soln, dict): return dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) # reject any result that makes Q affirmatively 0; # if in doubt, keep it try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [ s for s in soln if checksol(f, {symbol: s}) is not False ] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f * symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [ s for s in sols if checksol(f, {symbol: s}) is not False ] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: # reject any result that makes any dens affirmatively 0, # if in doubt, keep it soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] if result is False: raise NotImplementedError( msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols, extension=True) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None soln = solve_linear_system(matrix, *symbols, **flags) # Use swap_dict to ensure we return the same type as what was # passed; this is not necessary in the poly-system case which # only supports zero-dimensional systems if symbol_swapped and soln: soln = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] return solve_poly_system(polys)
def test_reduce_inequalities_multivariate(): assert reduce_inequalities([Ge(x**2, 1), Ge(y**2, 1)]) == And( Or(And(Le(1, x), Lt(x, oo)), And(Le(x, -1), Lt(-oo, x))), Or(And(Le(1, y), Lt(y, oo)), And(Le(y, -1), Lt(-oo, y))))
def test_reduce_inequalities_errors(): raises(NotImplementedError, lambda: reduce_inequalities(Ge(sin(x) + x, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(x**2*y + y, 1)))
def test__solve_inequalities(): assert reduce_inequalities(x + y < 1, symbols=[x]) == (x < 1 - y) assert reduce_inequalities(x + y >= 1, symbols=[x]) == (x < oo) & (x >= -y + 1) assert reduce_inequalities(Eq(0, x - y), symbols=[x]) == Eq(x, y) assert reduce_inequalities(Ne(0, x - y), symbols=[x]) == Ne(x, y)
def test_issue_6343(): eq = -3*x**2/2 - 45*x/4 + S(33)/2 > 0 assert reduce_inequalities(eq) == \ And(x < -S(15)/4 + sqrt(401)/4, -sqrt(401)/4 - S(15)/4 < x)
def test_reduce_inequalities_boolean(): x = Symbol('x') assert reduce_inequalities([Eq(x**2, 0), True]) == And(Eq(re(x), 0), Eq(im(x), 0)) assert reduce_inequalities([Eq(x**2, 0), False]) is False
def test_issue_8545(): eq = 1 - x - abs(1 - x) ans = And(Lt(1, x), Lt(x, oo)) assert reduce_abs_inequality(eq, '<', x) == ans eq = 1 - x - sqrt((1 - x)**2) assert reduce_inequalities(eq < 0) == ans
def test_reduce_inequalities_boolean(): assert reduce_inequalities( [Eq(x**2, 0), True]) == Eq(x, 0) assert reduce_inequalities([Eq(x**2, 0), False]) == False
def test_reduce_inequalities_multivariate(): assert reduce_inequalities([Ge(x**2, 1), Ge(y**2, 1)]) == \ And(And(Or(Le(re(x), -1), Ge(re(x), 1)), Eq(im(x), 0)), And(Or(Le(re(y), -1), Ge(re(y), 1)), Eq(im(y), 0)))
def test_hacky_inequalities(): x = Symbol('x') y = Symbol('y') assert reduce_inequalities(x + y < 1, symbols=[x]) == (x < 1 - y) assert reduce_inequalities(x + y >= 1, symbols=[x]) == (x >= 1 - y)
def test_reduce_inequalities_boolean(): assert reduce_inequalities([Eq(x**2, 0), True]) == Eq(x, 0) assert reduce_inequalities([Eq(x**2, 0), False]) == False assert reduce_inequalities(x**2 >= 0) is S.true # issue 10196
def test_reduce_inequalities_errors(): raises(NotImplementedError, lambda: reduce_inequalities(Ge(sin(x) + x, 1))) raises(NotImplementedError, lambda: reduce_inequalities(Ge(x**2 * y + y, 1)))
def _handle_irel(self, x, handler): """Return either None (if the conditions of self depend only on x) else a Piecewise expression whose expressions (handled by the handler that was passed) are paired with the governing x-independent relationals, e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> Piecewise( (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) """ # identify governing relationals rel = self.atoms(Relational) irel = list(ordered([r for r in rel if x not in r.free_symbols and r not in (S.true, S.false)])) if irel: args = {} exprinorder = [] for truth in product((1, 0), repeat=len(irel)): reps = dict(zip(irel, truth)) # only store the true conditions since the false are implied # when they appear lower in the Piecewise args if 1 not in truth: cond = None # flag this one so it doesn't get combined else: andargs = Tuple(*[i for i in reps if reps[i]]) free = list(andargs.free_symbols) if len(free) == 1: from sympy.solvers.inequalities import ( reduce_inequalities, _solve_inequality) try: t = reduce_inequalities(andargs, free[0]) # ValueError when there are potentially # nonvanishing imaginary parts except (ValueError, NotImplementedError): # at least isolate free symbol on left t = And(*[_solve_inequality( a, free[0], linear=True) for a in andargs]) else: t = And(*andargs) if t is S.false: continue # an impossible combination cond = t expr = handler(self.xreplace(reps)) if isinstance(expr, self.func) and len(expr.args) == 1: expr, econd = expr.args[0] cond = And(econd, True if cond is None else cond) # the ec pairs are being collected since all possibilities # are being enumerated, but don't put the last one in since # its expr might match a previous expression and it # must appear last in the args if cond is not None: args.setdefault(expr, []).append(cond) # but since we only store the true conditions we must maintain # the order so that the expression with the most true values # comes first exprinorder.append(expr) # convert collected conditions as args of Or for k in args: args[k] = Or(*args[k]) # take them in the order obtained args = [(e, args[e]) for e in uniq(exprinorder)] # add in the last arg args.append((expr, True)) # if any condition reduced to True, it needs to go last # and there should only be one of them or else the exprs # should agree trues = [i for i in range(len(args)) if args[i][1] is S.true] if not trues: # make the last one True since all cases were enumerated e, c = args[-1] args[-1] = (e, S.true) else: assert len(set([e for e, c in [args[i] for i in trues]])) == 1 args.append(args.pop(trues.pop())) while trues: args.pop(trues.pop()) return Piecewise(*args)
def test_issue_6343(): eq = -3 * x**2 / 2 - 45 * x / 4 + S(33) / 2 > 0 assert reduce_inequalities(eq) == \ And(x < -S(15)/4 + sqrt(401)/4, -sqrt(401)/4 - S(15)/4 < x)
def solve(f, *symbols, **flags): """ Algebraically solves equations and systems of equations. Currently supported are: - univariate polynomial, - transcendental - piecewise combinations of the above - systems of linear and polynomial equations - sytems containing relational expressions. Input is formed as: f - a single Expr or Poly that must be zero, - an Equality - a Relational expression or boolean - iterable of one or more of the above symbols (Symbol, Function or Derivative) specified as - none given (all free symbols will be used) - single symbol - denested list of symbols e.g. solve(f, x, y) - ordered iterable of symbols e.g. solve(f, [x, y]) flags - ``simplified``, when False, will not simplify solutions (default=True except for polynomials of order 3 or greater) The output varies according to the input and can be seen by example: >>> from sympy import solve, Poly, Eq, Function, exp >>> from sympy.abc import x, y, z, a, b o boolean or univariate Relational >>> solve(x < 3) And(im(x) == 0, re(x) < 3) o single expression and single symbol that is in the expression >>> solve(x - y, x) [y] >>> solve(x - 3, x) [3] >>> solve(Eq(x, 3), x) [3] >>> solve(Poly(x - 3), x) [3] >>> solve(x**2 - y**2, x) [y, -y] >>> solve(x**4 - 1, x) [1, -1, -I, I] o single expression with no symbol that is in the expression >>> solve(3, x) [] >>> solve(x - 3, y) [] o when no symbol is given then all free symbols will be used and sorted with default_sort_key and the result will be the same as above as if those symbols had been supplied >>> solve(x - 3) [3] >>> solve(x**2 - y**2) [y, -y] o when a Function or Derivative is given as a symbol, it is isolated algebraically and an implicit solution may be obtained >>> f = Function('f') >>> solve(f(x) - x, f(x)) [x] >>> solve(f(x).diff(x) - f(x) - x, f(x).diff(x)) [x + f(x)] o single expression and more than 1 symbol when there is a linear solution >>> solve(x - y**2, x, y) {x: y**2} >>> solve(x**2 - y, x, y) {y: x**2} when undetermined coefficients are identified that are linear >>> solve((a + b)*x - b + 2, a, b) {a: -2, b: 2} that are nonlinear >>> solve((a + b)*x - b**2 + 2, a, b) [(-2**(1/2), 2**(1/2)), (2**(1/2), -2**(1/2))] if there is no linear solution then the first successful attempt for a nonlinear solution will be returned >>> solve(x**2 - y**2, x, y) [y, -y] >>> solve(x**2 - y**2/exp(x), x, y) [x*exp(x/2), -x*exp(x/2)] o iterable of one or more of the above involving relationals or bools >>> solve([x < 3, x - 2]) And(im(x) == 0, re(x) == 2) >>> solve([x > 3, x - 2]) False when the system is linear with a solution >>> solve([x - 3], x) {x: 3} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y, z) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - z), z, x, y) {x: -5*y + 2, z: 21*y - 6} without a solution >>> solve([x + 3, x - 3]) when the system is not linear >>> solve([x**2 + y -2, y**2 - 4], x, y) [(-2, -2), (0, 2), (0, 2), (2, -2)] Warning: there is a possibility of obtaining ambiguous results if no symbols are given for a nonlinear system of equations or are given as a set since the symbols are not presently reported with the solution. A warning will be issued in this situation. >>> solve([x - 2, x**2 + y]) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, y] [(2, -4)] >>> solve([x - 2, x**2 + f(x)], set([f(x), x])) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, f(x)] [(2, -4)] See also: rsolve() for solving recurrence relationships dsolve() for solving differential equations """ # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. ########################################################################### def sympified_list(w): return map(sympify, iff(iterable(w), w, [w])) bare_f = not iterable(f) ordered_symbols = (symbols and symbols[0] and (isinstance(symbols[0], Symbol) or ordered_iter(symbols[0], include=GeneratorType))) f, symbols = (sympified_list(w) for w in [f, symbols]) # preprocess equation(s) ########################################################################### for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f[i] = piecewise_fold(f[i]) # preprocess symbol(s) ########################################################################### if not symbols: # get symbols from equations or supply dummy symbols so solve(3) behaves # like solve(3, x). symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy()]) ordered_symbols = False elif len(symbols) == 1 and iterable(symbols[0]): symbols = symbols[0] if not ordered_symbols: # we do this to make the results returned canonical in case f # contains a system of nonlinear equations; all other cases should # be unambiguous symbols = sorted(symbols, key=lambda i: i.sort_key()) # we can solve for Function and Derivative instances by replacing them # with Dummy symbols symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: msg = 'expected Symbol, Function or Derivative but got %s' raise TypeError(msg % type(s)) symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new # # try to get a solution ########################################################################### if bare_f: # pass f the way it was passed to solve; if it wasn't a list then # a list of solutions will be returned, otherwise a dictionary is # going to be returned f = f[0] solution = _solve(f, *symbols, **flags) # # postprocessing ########################################################################### # Restore original Functions and Derivatives if a dictionary is returned. # This is not necessary for # - the single equation, single unknown case # since the symbol will have been removed from the solution; # - the nonlinear poly_system since that only support zero-dimensional # systems and those results come back as a list if symbol_swapped and type(solution) is dict: solution = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in solution.iteritems()]) # warn if ambiguous results are being obtained # XXX agree on how to make this unambiguous # see issue 2405 for logic in how Polys chooses ordering and # for discussion of what to return see http://groups.google.com/group/sympy # Apr 18, 2011 posting 'using results from solve' elif (not ordered_symbols and len(symbols) > 1 and solution and ordered_iter(solution) and ordered_iter(solution[0]) and any(len(set(s)) > 1 for s in solution)): msg = ('\n\tFor nonlinear systems of equations, symbols should be' + '\n\tgiven as a list so as to avoid ambiguity in the results.' + '\n\tsolve sorted the symbols as %s') print msg % str( bool(symbol_swapped) and list(zip(*swap_dict)[0]) or symbols) # # done ########################################################################### return solution
def _handle_irel(self, x, handler): """Return either None (if the conditions of self depend only on x) else a Piecewise expression whose expressions (handled by the handler that was passed) are paired with the governing x-independent relationals, e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> Piecewise( (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) """ # identify governing relationals rel = self.atoms(Relational) irel = list( ordered([ r for r in rel if x not in r.free_symbols and r not in (S.true, S.false) ])) if irel: args = {} exprinorder = [] for truth in product((1, 0), repeat=len(irel)): reps = dict(zip(irel, truth)) # only store the true conditions since the false are implied # when they appear lower in the Piecewise args if 1 not in truth: cond = None # flag this one so it doesn't get combined else: andargs = Tuple(*[i for i in reps if reps[i]]) free = list(andargs.free_symbols) if len(free) == 1: from sympy.solvers.inequalities import ( reduce_inequalities, _solve_inequality) try: t = reduce_inequalities(andargs, free[0]) # ValueError when there are potentially # nonvanishing imaginary parts except (ValueError, NotImplementedError): # at least isolate free symbol on left t = And(*[ _solve_inequality(a, free[0], linear=True) for a in andargs ]) else: t = And(*andargs) if t is S.false: continue # an impossible combination cond = t expr = handler(self.xreplace(reps)) if isinstance(expr, self.func) and len(expr.args) == 1: expr, econd = expr.args[0] cond = And(econd, True if cond is None else cond) # the ec pairs are being collected since all possibilities # are being enumerated, but don't put the last one in since # its expr might match a previous expression and it # must appear last in the args if cond is not None: args.setdefault(expr, []).append(cond) # but since we only store the true conditions we must maintain # the order so that the expression with the most true values # comes first exprinorder.append(expr) # convert collected conditions as args of Or for k in args: args[k] = Or(*args[k]) # take them in the order obtained args = [(e, args[e]) for e in uniq(exprinorder)] # add in the last arg args.append((expr, True)) # if any condition reduced to True, it needs to go last # and there should only be one of them or else the exprs # should agree trues = [i for i in range(len(args)) if args[i][1] is S.true] if not trues: # make the last one True since all cases were enumerated e, c = args[-1] args[-1] = (e, S.true) else: assert len(set([e for e, c in [args[i] for i in trues]])) == 1 args.append(args.pop(trues.pop())) while trues: args.pop(trues.pop()) return Piecewise(*args)
def solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympit(w): return map(sympify, iff(isinstance(w, (list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not isinstance(f, (list, tuple, set)) f, symbols = (sympit(w) for w in [f, symbols]) if any(isinstance(fi, bool) or (fi.is_Relational and not fi.is_Equality) for fi in f): return reduce_inequalities(f, assume=flags.get("assume")) for i, fi in enumerate(f): if fi.is_Equality: f[i] = fi.lhs - fi.rhs if not symbols: # get symbols from equations or supply dummy symbols since # solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.atoms(Symbol) or set([Dummy("x")]) symbols = list(symbols) if bare_f: f = f[0] if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy("F%d" % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy("D%d" % i) else: raise TypeError("not a Symbol or a Function") symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if not isinstance(f, (tuple, list, set)): # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: result = {} for s in symbols: result[s] = solve(f, s, **flags) if flags.get("simplified", True): for s, r in result.items(): result[s] = map(simplify, r) return result symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: raise NotImplementedError("Cannot solve equation " + str(f) + " for " + str(symbol)) # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree > 2: flags["simplified"] = flags.get("simplified", False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() # TODO: check for Q != 0 result = solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy("t", positive=True) f_ = f.subs(symbol, t ** m) if guess_solve_strategy(f_, t) != GS_POLY: raise NotImplementedError("Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) for sol in cv_sols: result.append(sol ** m) elif isinstance(f, Mul): for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f * symbol ** (-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == GS_TRANSCENDENTAL: # a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise ValueError("Could not parse expression %s" % f) else: raise NotImplementedError("No algorithms are implemented to solve equation %s" % f) # This symbol swap should not be necessary for the single symbol case: if you've # solved for the symbol the it will not appear in the solution. Right now, however # ode's are getting solutions for solve (even though they shouldn't be -- see the # swap_back test in test_solvers). if symbol_swapped: result = [ri.subs(swap_back_dict) for ri in result] if flags.get("simplified", True) and strategy != GS_RATIONAL: return map(simplify, result) else: return result else: if not f: return {} else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff soln = solve_linear_system(matrix, *symbols, **flags) else: soln = solve_poly_system(polys) # Use swap_dict to ensure we return the same type as what was # passed if symbol_swapped: if isinstance(soln, dict): res = {} for k in soln.keys(): res.update({swap_back_dict[k]: soln[k]}) return res else: return soln else: return soln
def test_reduce_inequalities_boolean(): assert reduce_inequalities( [Eq(x**2, 0), True]) == And(Eq(re(x), 0), Eq(im(x), 0)) assert reduce_inequalities([Eq(x**2, 0), False]) is False