|
"""Tools for solving inequalities and systems of inequalities. """ |
|
import itertools |
|
|
|
from sympy.calculus.util import (continuous_domain, periodicity, |
|
function_range) |
|
from sympy.core import sympify |
|
from sympy.core.exprtools import factor_terms |
|
from sympy.core.relational import Relational, Lt, Ge, Eq |
|
from sympy.core.symbol import Symbol, Dummy |
|
from sympy.sets.sets import Interval, FiniteSet, Union, Intersection |
|
from sympy.core.singleton import S |
|
from sympy.core.function import expand_mul |
|
from sympy.functions.elementary.complexes import Abs |
|
from sympy.logic import And |
|
from sympy.polys import Poly, PolynomialError, parallel_poly_from_expr |
|
from sympy.polys.polyutils import _nsort |
|
from sympy.solvers.solveset import solvify, solveset |
|
from sympy.utilities.iterables import sift, iterable |
|
from sympy.utilities.misc import filldedent |
|
|
|
|
|
def solve_poly_inequality(poly, rel): |
|
"""Solve a polynomial inequality with rational coefficients. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import solve_poly_inequality, Poly |
|
>>> from sympy.abc import x |
|
|
|
>>> solve_poly_inequality(Poly(x, x, domain='ZZ'), '==') |
|
[{0}] |
|
|
|
>>> solve_poly_inequality(Poly(x**2 - 1, x, domain='ZZ'), '!=') |
|
[Interval.open(-oo, -1), Interval.open(-1, 1), Interval.open(1, oo)] |
|
|
|
>>> solve_poly_inequality(Poly(x**2 - 1, x, domain='ZZ'), '==') |
|
[{-1}, {1}] |
|
|
|
See Also |
|
======== |
|
solve_poly_inequalities |
|
""" |
|
if not isinstance(poly, Poly): |
|
raise ValueError( |
|
'For efficiency reasons, `poly` should be a Poly instance') |
|
if poly.as_expr().is_number: |
|
t = Relational(poly.as_expr(), 0, rel) |
|
if t is S.true: |
|
return [S.Reals] |
|
elif t is S.false: |
|
return [S.EmptySet] |
|
else: |
|
raise NotImplementedError( |
|
"could not determine truth value of %s" % t) |
|
|
|
reals, intervals = poly.real_roots(multiple=False), [] |
|
|
|
if rel == '==': |
|
for root, _ in reals: |
|
interval = Interval(root, root) |
|
intervals.append(interval) |
|
elif rel == '!=': |
|
left = S.NegativeInfinity |
|
|
|
for right, _ in reals + [(S.Infinity, 1)]: |
|
interval = Interval(left, right, True, True) |
|
intervals.append(interval) |
|
left = right |
|
else: |
|
if poly.LC() > 0: |
|
sign = +1 |
|
else: |
|
sign = -1 |
|
|
|
eq_sign, equal = None, False |
|
|
|
if rel == '>': |
|
eq_sign = +1 |
|
elif rel == '<': |
|
eq_sign = -1 |
|
elif rel == '>=': |
|
eq_sign, equal = +1, True |
|
elif rel == '<=': |
|
eq_sign, equal = -1, True |
|
else: |
|
raise ValueError("'%s' is not a valid relation" % rel) |
|
|
|
right, right_open = S.Infinity, True |
|
|
|
for left, multiplicity in reversed(reals): |
|
if multiplicity % 2: |
|
if sign == eq_sign: |
|
intervals.insert( |
|
0, Interval(left, right, not equal, right_open)) |
|
|
|
sign, right, right_open = -sign, left, not equal |
|
else: |
|
if sign == eq_sign and not equal: |
|
intervals.insert( |
|
0, Interval(left, right, True, right_open)) |
|
right, right_open = left, True |
|
elif sign != eq_sign and equal: |
|
intervals.insert(0, Interval(left, left)) |
|
|
|
if sign == eq_sign: |
|
intervals.insert( |
|
0, Interval(S.NegativeInfinity, right, True, right_open)) |
|
|
|
return intervals |
|
|
|
|
|
def solve_poly_inequalities(polys): |
|
"""Solve polynomial inequalities with rational coefficients. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Poly |
|
>>> from sympy.solvers.inequalities import solve_poly_inequalities |
|
>>> from sympy.abc import x |
|
>>> solve_poly_inequalities((( |
|
... Poly(x**2 - 3), ">"), ( |
|
... Poly(-x**2 + 1), ">"))) |
|
Union(Interval.open(-oo, -sqrt(3)), Interval.open(-1, 1), Interval.open(sqrt(3), oo)) |
|
""" |
|
return Union(*[s for p in polys for s in solve_poly_inequality(*p)]) |
|
|
|
|
|
def solve_rational_inequalities(eqs): |
|
"""Solve a system of rational inequalities with rational coefficients. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x |
|
>>> from sympy import solve_rational_inequalities, Poly |
|
|
|
>>> solve_rational_inequalities([[ |
|
... ((Poly(-x + 1), Poly(1, x)), '>='), |
|
... ((Poly(-x + 1), Poly(1, x)), '<=')]]) |
|
{1} |
|
|
|
>>> solve_rational_inequalities([[ |
|
... ((Poly(x), Poly(1, x)), '!='), |
|
... ((Poly(-x + 1), Poly(1, x)), '>=')]]) |
|
Union(Interval.open(-oo, 0), Interval.Lopen(0, 1)) |
|
|
|
See Also |
|
======== |
|
solve_poly_inequality |
|
""" |
|
result = S.EmptySet |
|
|
|
for _eqs in eqs: |
|
if not _eqs: |
|
continue |
|
|
|
global_intervals = [Interval(S.NegativeInfinity, S.Infinity)] |
|
|
|
for (numer, denom), rel in _eqs: |
|
numer_intervals = solve_poly_inequality(numer*denom, rel) |
|
denom_intervals = solve_poly_inequality(denom, '==') |
|
|
|
intervals = [] |
|
|
|
for numer_interval, global_interval in itertools.product( |
|
numer_intervals, global_intervals): |
|
interval = numer_interval.intersect(global_interval) |
|
|
|
if interval is not S.EmptySet: |
|
intervals.append(interval) |
|
|
|
global_intervals = intervals |
|
|
|
intervals = [] |
|
|
|
for global_interval in global_intervals: |
|
for denom_interval in denom_intervals: |
|
global_interval -= denom_interval |
|
|
|
if global_interval is not S.EmptySet: |
|
intervals.append(global_interval) |
|
|
|
global_intervals = intervals |
|
|
|
if not global_intervals: |
|
break |
|
|
|
for interval in global_intervals: |
|
result = result.union(interval) |
|
|
|
return result |
|
|
|
|
|
def reduce_rational_inequalities(exprs, gen, relational=True): |
|
"""Reduce a system of rational inequalities with rational coefficients. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Symbol |
|
>>> from sympy.solvers.inequalities import reduce_rational_inequalities |
|
|
|
>>> x = Symbol('x', real=True) |
|
|
|
>>> reduce_rational_inequalities([[x**2 <= 0]], x) |
|
Eq(x, 0) |
|
|
|
>>> reduce_rational_inequalities([[x + 2 > 0]], x) |
|
-2 < x |
|
>>> reduce_rational_inequalities([[(x + 2, ">")]], x) |
|
-2 < x |
|
>>> reduce_rational_inequalities([[x + 2]], x) |
|
Eq(x, -2) |
|
|
|
This function find the non-infinite solution set so if the unknown symbol |
|
is declared as extended real rather than real then the result may include |
|
finiteness conditions: |
|
|
|
>>> y = Symbol('y', extended_real=True) |
|
>>> reduce_rational_inequalities([[y + 2 > 0]], y) |
|
(-2 < y) & (y < oo) |
|
""" |
|
exact = True |
|
eqs = [] |
|
solution = S.EmptySet |
|
for _exprs in exprs: |
|
if not _exprs: |
|
continue |
|
_eqs = [] |
|
_sol = S.Reals |
|
for expr in _exprs: |
|
if isinstance(expr, tuple): |
|
expr, rel = expr |
|
else: |
|
if expr.is_Relational: |
|
expr, rel = expr.lhs - expr.rhs, expr.rel_op |
|
else: |
|
expr, rel = expr, '==' |
|
|
|
if expr is S.true: |
|
numer, denom, rel = S.Zero, S.One, '==' |
|
elif expr is S.false: |
|
numer, denom, rel = S.One, S.One, '==' |
|
else: |
|
numer, denom = expr.together().as_numer_denom() |
|
|
|
try: |
|
(numer, denom), opt = parallel_poly_from_expr( |
|
(numer, denom), gen) |
|
except PolynomialError: |
|
raise PolynomialError(filldedent(''' |
|
only polynomials and rational functions are |
|
supported in this context. |
|
''')) |
|
|
|
if not opt.domain.is_Exact: |
|
numer, denom, exact = numer.to_exact(), denom.to_exact(), False |
|
|
|
domain = opt.domain.get_exact() |
|
|
|
if not (domain.is_ZZ or domain.is_QQ): |
|
expr = numer/denom |
|
expr = Relational(expr, 0, rel) |
|
_sol &= solve_univariate_inequality(expr, gen, relational=False) |
|
else: |
|
_eqs.append(((numer, denom), rel)) |
|
|
|
if _eqs: |
|
_sol &= solve_rational_inequalities([_eqs]) |
|
exclude = solve_rational_inequalities([[((d, d.one), '==') |
|
for i in eqs for ((n, d), _) in i if d.has(gen)]]) |
|
_sol -= exclude |
|
|
|
solution |= _sol |
|
|
|
if not exact and solution: |
|
solution = solution.evalf() |
|
|
|
if relational: |
|
solution = solution.as_relational(gen) |
|
|
|
return solution |
|
|
|
|
|
def reduce_abs_inequality(expr, rel, gen): |
|
"""Reduce an inequality with nested absolute values. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import reduce_abs_inequality, Abs, Symbol |
|
>>> x = Symbol('x', real=True) |
|
|
|
>>> reduce_abs_inequality(Abs(x - 5) - 3, '<', x) |
|
(2 < x) & (x < 8) |
|
|
|
>>> reduce_abs_inequality(Abs(x + 2)*3 - 13, '<', x) |
|
(-19/3 < x) & (x < 7/3) |
|
|
|
See Also |
|
======== |
|
|
|
reduce_abs_inequalities |
|
""" |
|
if gen.is_extended_real is False: |
|
raise TypeError(filldedent(''' |
|
Cannot solve inequalities with absolute values containing |
|
non-real variables. |
|
''')) |
|
|
|
def _bottom_up_scan(expr): |
|
exprs = [] |
|
|
|
if expr.is_Add or expr.is_Mul: |
|
op = expr.func |
|
|
|
for arg in expr.args: |
|
_exprs = _bottom_up_scan(arg) |
|
|
|
if not exprs: |
|
exprs = _exprs |
|
else: |
|
exprs = [(op(expr, _expr), conds + _conds) for (expr, conds), (_expr, _conds) in |
|
itertools.product(exprs, _exprs)] |
|
elif expr.is_Pow: |
|
n = expr.exp |
|
if not n.is_Integer: |
|
raise ValueError("Only Integer Powers are allowed on Abs.") |
|
|
|
exprs.extend((expr**n, conds) for expr, conds in _bottom_up_scan(expr.base)) |
|
elif isinstance(expr, Abs): |
|
_exprs = _bottom_up_scan(expr.args[0]) |
|
|
|
for expr, conds in _exprs: |
|
exprs.append(( expr, conds + [Ge(expr, 0)])) |
|
exprs.append((-expr, conds + [Lt(expr, 0)])) |
|
else: |
|
exprs = [(expr, [])] |
|
|
|
return exprs |
|
|
|
mapping = {'<': '>', '<=': '>='} |
|
inequalities = [] |
|
|
|
for expr, conds in _bottom_up_scan(expr): |
|
if rel not in mapping.keys(): |
|
expr = Relational( expr, 0, rel) |
|
else: |
|
expr = Relational(-expr, 0, mapping[rel]) |
|
|
|
inequalities.append([expr] + conds) |
|
|
|
return reduce_rational_inequalities(inequalities, gen) |
|
|
|
|
|
def reduce_abs_inequalities(exprs, gen): |
|
"""Reduce a system of inequalities with nested absolute values. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import reduce_abs_inequalities, Abs, Symbol |
|
>>> x = Symbol('x', extended_real=True) |
|
|
|
>>> reduce_abs_inequalities([(Abs(3*x - 5) - 7, '<'), |
|
... (Abs(x + 25) - 13, '>')], x) |
|
(-2/3 < x) & (x < 4) & (((-oo < x) & (x < -38)) | ((-12 < x) & (x < oo))) |
|
|
|
>>> reduce_abs_inequalities([(Abs(x - 4) + Abs(3*x - 5) - 7, '<')], x) |
|
(1/2 < x) & (x < 4) |
|
|
|
See Also |
|
======== |
|
|
|
reduce_abs_inequality |
|
""" |
|
return And(*[ reduce_abs_inequality(expr, rel, gen) |
|
for expr, rel in exprs ]) |
|
|
|
|
|
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() does not need to be called on it) |
|
|
|
Raises |
|
====== |
|
|
|
NotImplementedError |
|
The solution of the inequality cannot be determined due to limitation |
|
in :func:`sympy.solvers.solveset.solvify`. |
|
|
|
Notes |
|
===== |
|
|
|
Currently, we cannot solve all the inequalities due to limitations in |
|
:func:`sympy.solvers.solveset.solvify`. Also, the solution returned for trigonometric inequalities |
|
are restricted in its periodic interval. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.solvers.solveset.solvify: solver returning solveset solutions with solve's output API |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import solve_univariate_inequality, Symbol, sin, Interval, S |
|
>>> x = Symbol('x') |
|
|
|
>>> solve_univariate_inequality(x**2 >= 4, x) |
|
((2 <= x) & (x < oo)) | ((-oo < x) & (x <= -2)) |
|
|
|
>>> 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.solvers.solvers import denoms |
|
|
|
if domain.is_subset(S.Reals) is False: |
|
raise NotImplementedError(filldedent(''' |
|
Inequalities in the complex domain are |
|
not supported. Try the real domain by |
|
setting domain=S.Reals''')) |
|
elif domain is not S.Reals: |
|
rv = solve_univariate_inequality( |
|
expr, gen, relational=False, continuous=continuous).intersection(domain) |
|
if relational: |
|
rv = rv.as_relational(gen) |
|
return rv |
|
else: |
|
pass |
|
|
|
|
|
|
|
|
|
_gen = gen |
|
_domain = domain |
|
if gen.is_extended_real is False: |
|
rv = S.EmptySet |
|
return rv if not relational else rv.as_relational(_gen) |
|
elif gen.is_extended_real is None: |
|
gen = Dummy('gen', extended_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 == 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 in ('<', '<='): |
|
if expr.func(frange.sup, 0): |
|
rv = domain |
|
elif not expr.func(frange.inf, 0): |
|
rv = S.EmptySet |
|
|
|
elif rel in ('>', '>='): |
|
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).intersect(_domain) |
|
_domain = domain |
|
|
|
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 |
|
|
|
|
|
solns = solvify(e, gen, domain) |
|
if solns is None: |
|
|
|
raise ValueError |
|
except (ValueError, NotImplementedError): |
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_extended_real is False: |
|
return S.false |
|
else: |
|
v = v.n(2) |
|
if v.is_comparable: |
|
return expr.func(v, 0) |
|
|
|
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)) |
|
|
|
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: |
|
sifted = sift(critical_points, lambda x: x.is_extended_real) |
|
if sifted[None]: |
|
|
|
|
|
raise NotImplementedError |
|
try: |
|
reals = sifted[True] |
|
if len(reals) > 1: |
|
reals = sorted(reals) |
|
except TypeError: |
|
raise NotImplementedError |
|
except NotImplementedError: |
|
raise NotImplementedError('sorting of these roots is not supported') |
|
|
|
|
|
|
|
make_real = S.Reals |
|
if (coeffI := expanded_e.coeff(S.ImaginaryUnit)) != S.Zero: |
|
check = True |
|
im_sol = FiniteSet() |
|
try: |
|
a = solveset(coeffI, gen, domain) |
|
if not isinstance(a, Interval): |
|
for z in a: |
|
if z not in singularities and valid(z) and z.is_extended_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_extended_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 im_sol is S.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) |
|
|
|
sol_sets = [S.EmptySet] |
|
|
|
start = domain.inf |
|
if start in domain and 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: |
|
_valid = include_x |
|
if _valid: |
|
sol_sets.append(FiniteSet(x)) |
|
|
|
start = end |
|
|
|
end = domain.sup |
|
if end in domain and valid(end) and end.is_finite: |
|
sol_sets.append(FiniteSet(end)) |
|
|
|
if valid(_pt(start, end)): |
|
sol_sets.append(Interval.open(start, end)) |
|
|
|
if coeffI != 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 _pt(start, end): |
|
"""Return a point between start and end""" |
|
if not start.is_infinite and not end.is_infinite: |
|
pt = (start + end)/2 |
|
elif start.is_infinite and end.is_infinite: |
|
pt = S.Zero |
|
else: |
|
if (start.is_infinite and start.is_extended_positive is None or |
|
end.is_infinite and end.is_extended_positive is None): |
|
raise ValueError('cannot proceed with unsigned infinite values') |
|
if (end.is_infinite and end.is_extended_negative or |
|
start.is_infinite and start.is_extended_positive): |
|
start, end = end, start |
|
|
|
|
|
|
|
if end.is_infinite: |
|
if start.is_extended_positive: |
|
pt = start*2 |
|
elif start.is_extended_negative: |
|
pt = start*S.Half |
|
else: |
|
pt = start + 1 |
|
elif start.is_infinite: |
|
if end.is_extended_positive: |
|
pt = end*S.Half |
|
elif end.is_extended_negative: |
|
pt = end*2 |
|
else: |
|
pt = end - 1 |
|
return pt |
|
|
|
|
|
def _solve_inequality(ie, s, linear=False): |
|
"""Return the inequality with s isolated on the left, if possible. |
|
If the relationship is non-linear, a solution involving And or Or |
|
may be returned. False or True are returned if the relationship |
|
is never True or always True, respectively. |
|
|
|
If `linear` is True (default is False) an `s`-dependent expression |
|
will be isolated on the left, if possible |
|
but it will not be solved for `s` unless the expression is linear |
|
in `s`. Furthermore, only "safe" operations which do not change the |
|
sense of the relationship are applied: no division by an unsigned |
|
value is attempted unless the relationship involves Eq or Ne and |
|
no division by a value not known to be nonzero is ever attempted. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Eq, Symbol |
|
>>> from sympy.solvers.inequalities import _solve_inequality as f |
|
>>> from sympy.abc import x, y |
|
|
|
For linear expressions, the symbol can be isolated: |
|
|
|
>>> f(x - 2 < 0, x) |
|
x < 2 |
|
>>> f(-x - 6 < x, x) |
|
x > -3 |
|
|
|
Sometimes nonlinear relationships will be False |
|
|
|
>>> f(x**2 + 4 < 0, x) |
|
False |
|
|
|
Or they may involve more than one region of values: |
|
|
|
>>> f(x**2 - 4 < 0, x) |
|
(-2 < x) & (x < 2) |
|
|
|
To restrict the solution to a relational, set linear=True |
|
and only the x-dependent portion will be isolated on the left: |
|
|
|
>>> f(x**2 - 4 < 0, x, linear=True) |
|
x**2 < 4 |
|
|
|
Division of only nonzero quantities is allowed, so x cannot |
|
be isolated by dividing by y: |
|
|
|
>>> y.is_nonzero is None # it is unknown whether it is 0 or not |
|
True |
|
>>> f(x*y < 1, x) |
|
x*y < 1 |
|
|
|
And while an equality (or inequality) still holds after dividing by a |
|
non-zero quantity |
|
|
|
>>> nz = Symbol('nz', nonzero=True) |
|
>>> f(Eq(x*nz, 1), x) |
|
Eq(x, 1/nz) |
|
|
|
the sign must be known for other inequalities involving > or <: |
|
|
|
>>> f(x*nz <= 1, x) |
|
nz*x <= 1 |
|
>>> p = Symbol('p', positive=True) |
|
>>> f(x*p <= 1, x) |
|
x <= 1/p |
|
|
|
When there are denominators in the original expression that |
|
are removed by expansion, conditions for them will be returned |
|
as part of the result: |
|
|
|
>>> f(x < x*(2/x - 1), x) |
|
(x < 1) & Ne(x, 0) |
|
""" |
|
from sympy.solvers.solvers import denoms |
|
if s not in ie.free_symbols: |
|
return ie |
|
if ie.rhs == s: |
|
ie = ie.reversed |
|
if ie.lhs == s and s not in ie.rhs.free_symbols: |
|
return ie |
|
|
|
def classify(ie, s, i): |
|
|
|
|
|
|
|
try: |
|
v = ie.subs(s, i) |
|
if v is S.NaN: |
|
return v |
|
elif v not in (True, False): |
|
return |
|
return v |
|
except TypeError: |
|
return S.NaN |
|
|
|
rv = None |
|
oo = S.Infinity |
|
expr = ie.lhs - ie.rhs |
|
try: |
|
p = Poly(expr, s) |
|
if p.degree() == 0: |
|
rv = ie.func(p.as_expr(), 0) |
|
elif not linear and p.degree() > 1: |
|
|
|
raise NotImplementedError |
|
except (PolynomialError, NotImplementedError): |
|
if not linear: |
|
try: |
|
rv = reduce_rational_inequalities([[ie]], s) |
|
except PolynomialError: |
|
rv = solve_univariate_inequality(ie, s) |
|
|
|
|
|
okoo = classify(ie, s, oo) |
|
if okoo is S.true and classify(rv, s, oo) is S.false: |
|
rv = rv.subs(s < oo, True) |
|
oknoo = classify(ie, s, -oo) |
|
if (oknoo is S.true and |
|
classify(rv, s, -oo) is S.false): |
|
rv = rv.subs(-oo < s, True) |
|
rv = rv.subs(s > -oo, True) |
|
if rv is S.true: |
|
rv = (s <= oo) if okoo is S.true else (s < oo) |
|
if oknoo is not S.true: |
|
rv = And(-oo < s, rv) |
|
else: |
|
p = Poly(expr) |
|
|
|
conds = [] |
|
if rv is None: |
|
e = p.as_expr() |
|
|
|
|
|
|
|
|
|
rhs = 0 |
|
b, ax = e.as_independent(s, as_Add=True) |
|
e -= b |
|
rhs -= b |
|
ef = factor_terms(e) |
|
a, e = ef.as_independent(s, as_Add=False) |
|
if (a.is_zero != False or |
|
a.is_negative == |
|
a.is_positive is None and |
|
ie.rel_op not in ('!=', '==')): |
|
e = ef |
|
a = S.One |
|
rhs /= a |
|
if a.is_positive: |
|
rv = ie.func(e, rhs) |
|
else: |
|
rv = ie.reversed.func(e, rhs) |
|
|
|
|
|
|
|
beginning_denoms = denoms(ie.lhs) | denoms(ie.rhs) |
|
current_denoms = denoms(rv) |
|
for d in beginning_denoms - current_denoms: |
|
c = _solve_inequality(Eq(d, 0), s, linear=linear) |
|
if isinstance(c, Eq) and c.lhs == s: |
|
if classify(rv, s, c.rhs) is S.true: |
|
|
|
conds.append(~c) |
|
for i in (-oo, oo): |
|
if (classify(rv, s, i) is S.true and |
|
classify(ie, s, i) is not S.true): |
|
conds.append(s < i if i is oo else i < s) |
|
|
|
conds.append(rv) |
|
return And(*conds) |
|
|
|
|
|
def _reduce_inequalities(inequalities, symbols): |
|
|
|
|
|
poly_part, abs_part = {}, {} |
|
other = [] |
|
|
|
for inequality in inequalities: |
|
|
|
expr, rel = inequality.lhs, inequality.rel_op |
|
|
|
|
|
|
|
|
|
gens = expr.atoms(Symbol) |
|
|
|
if len(gens) == 1: |
|
gen = gens.pop() |
|
else: |
|
common = expr.free_symbols & symbols |
|
if len(common) == 1: |
|
gen = common.pop() |
|
other.append(_solve_inequality(Relational(expr, 0, rel), gen)) |
|
continue |
|
else: |
|
raise NotImplementedError(filldedent(''' |
|
inequality has more than one symbol of interest. |
|
''')) |
|
|
|
if expr.is_polynomial(gen): |
|
poly_part.setdefault(gen, []).append((expr, rel)) |
|
else: |
|
components = expr.find(lambda u: |
|
u.has(gen) and ( |
|
u.is_Function or u.is_Pow and not u.exp.is_Integer)) |
|
if components and all(isinstance(i, Abs) for i in components): |
|
abs_part.setdefault(gen, []).append((expr, rel)) |
|
else: |
|
other.append(_solve_inequality(Relational(expr, 0, rel), gen)) |
|
|
|
poly_reduced = [reduce_rational_inequalities([exprs], gen) for gen, exprs in poly_part.items()] |
|
abs_reduced = [reduce_abs_inequalities(exprs, gen) for gen, exprs in abs_part.items()] |
|
|
|
return And(*(poly_reduced + abs_reduced + other)) |
|
|
|
|
|
def reduce_inequalities(inequalities, symbols=[]): |
|
"""Reduce a system of inequalities with rational coefficients. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.abc import x, y |
|
>>> from sympy import reduce_inequalities |
|
|
|
>>> reduce_inequalities(0 <= x + 3, []) |
|
(-3 <= x) & (x < oo) |
|
|
|
>>> reduce_inequalities(0 <= x + y*2 - 1, [x]) |
|
(x < oo) & (x >= 1 - 2*y) |
|
""" |
|
if not iterable(inequalities): |
|
inequalities = [inequalities] |
|
inequalities = [sympify(i) for i in inequalities] |
|
|
|
gens = set().union(*[i.free_symbols for i in inequalities]) |
|
|
|
if not iterable(symbols): |
|
symbols = [symbols] |
|
symbols = (set(symbols) or gens) & gens |
|
if any(i.is_extended_real is False for i in symbols): |
|
raise TypeError(filldedent(''' |
|
inequalities cannot contain symbols that are not real. |
|
''')) |
|
|
|
|
|
recast = {i: Dummy(i.name, extended_real=True) |
|
for i in gens if i.is_extended_real is None} |
|
inequalities = [i.xreplace(recast) for i in inequalities] |
|
symbols = {i.xreplace(recast) for i in symbols} |
|
|
|
|
|
keep = [] |
|
for i in inequalities: |
|
if isinstance(i, Relational): |
|
i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0) |
|
elif i not in (True, False): |
|
i = Eq(i, 0) |
|
if i == True: |
|
continue |
|
elif i == False: |
|
return S.false |
|
if i.lhs.is_number: |
|
raise NotImplementedError( |
|
"could not determine truth value of %s" % i) |
|
keep.append(i) |
|
inequalities = keep |
|
del keep |
|
|
|
|
|
rv = _reduce_inequalities(inequalities, symbols) |
|
|
|
|
|
return rv.xreplace({v: k for k, v in recast.items()}) |
|
|