Spaces:
Sleeping
Sleeping
""" | |
This module contains functions to: | |
- solve a single equation for a single variable, in any domain either real or complex. | |
- solve a single transcendental equation for a single variable in any domain either real or complex. | |
(currently supports solving in real domain only) | |
- solve a system of linear equations with N variables and M equations. | |
- solve a system of Non Linear Equations with N variables and M equations | |
""" | |
from sympy.core.sympify import sympify | |
from sympy.core import (S, Pow, Dummy, pi, Expr, Wild, Mul, | |
Add, Basic) | |
from sympy.core.containers import Tuple | |
from sympy.core.function import (Lambda, expand_complex, AppliedUndef, | |
expand_log, _mexpand, expand_trig, nfloat) | |
from sympy.core.mod import Mod | |
from sympy.core.numbers import I, Number, Rational, oo | |
from sympy.core.intfunc import integer_log | |
from sympy.core.relational import Eq, Ne, Relational | |
from sympy.core.sorting import default_sort_key, ordered | |
from sympy.core.symbol import Symbol, _uniquely_named_symbol | |
from sympy.core.sympify import _sympify | |
from sympy.core.traversal import preorder_traversal | |
from sympy.external.gmpy import gcd as number_gcd, lcm as number_lcm | |
from sympy.polys.matrices.linsolve import _linear_eq_to_dict | |
from sympy.polys.polyroots import UnsolvableFactorError | |
from sympy.simplify.simplify import simplify, fraction, trigsimp, nsimplify | |
from sympy.simplify import powdenest, logcombine | |
from sympy.functions import (log, tan, cot, sin, cos, sec, csc, exp, | |
acos, asin, atan, acot, acsc, asec, | |
piecewise_fold, Piecewise) | |
from sympy.functions.combinatorial.numbers import totient | |
from sympy.functions.elementary.complexes import Abs, arg, re, im | |
from sympy.functions.elementary.hyperbolic import (HyperbolicFunction, | |
sinh, cosh, tanh, coth, sech, csch, | |
asinh, acosh, atanh, acoth, asech, acsch) | |
from sympy.functions.elementary.miscellaneous import real_root | |
from sympy.functions.elementary.trigonometric import TrigonometricFunction | |
from sympy.logic.boolalg import And, BooleanTrue | |
from sympy.sets import (FiniteSet, imageset, Interval, Intersection, | |
Union, ConditionSet, ImageSet, Complement, Contains) | |
from sympy.sets.sets import Set, ProductSet | |
from sympy.matrices import zeros, Matrix, MatrixBase | |
from sympy.ntheory.factor_ import divisors | |
from sympy.ntheory.residue_ntheory import discrete_log, nthroot_mod | |
from sympy.polys import (roots, Poly, degree, together, PolynomialError, | |
RootOf, factor, lcm, gcd) | |
from sympy.polys.polyerrors import CoercionFailed | |
from sympy.polys.polytools import invert, groebner, poly | |
from sympy.polys.solvers import (sympy_eqs_to_ring, solve_lin_sys, | |
PolyNonlinearError) | |
from sympy.polys.matrices.linsolve import _linsolve | |
from sympy.solvers.solvers import (checksol, denoms, unrad, | |
_simple_dens, recast_to_symbols) | |
from sympy.solvers.polysys import solve_poly_system | |
from sympy.utilities import filldedent | |
from sympy.utilities.iterables import (numbered_symbols, has_dups, | |
is_sequence, iterable) | |
from sympy.calculus.util import periodicity, continuous_domain, function_range | |
from types import GeneratorType | |
class NonlinearError(ValueError): | |
"""Raised when unexpectedly encountering nonlinear equations""" | |
pass | |
def _masked(f, *atoms): | |
"""Return ``f``, with all objects given by ``atoms`` replaced with | |
Dummy symbols, ``d``, and the list of replacements, ``(d, e)``, | |
where ``e`` is an object of type given by ``atoms`` in which | |
any other instances of atoms have been recursively replaced with | |
Dummy symbols, too. The tuples are ordered so that if they are | |
applied in sequence, the origin ``f`` will be restored. | |
Examples | |
======== | |
>>> from sympy import cos | |
>>> from sympy.abc import x | |
>>> from sympy.solvers.solveset import _masked | |
>>> f = cos(cos(x) + 1) | |
>>> f, reps = _masked(cos(1 + cos(x)), cos) | |
>>> f | |
_a1 | |
>>> reps | |
[(_a1, cos(_a0 + 1)), (_a0, cos(x))] | |
>>> for d, e in reps: | |
... f = f.xreplace({d: e}) | |
>>> f | |
cos(cos(x) + 1) | |
""" | |
sym = numbered_symbols('a', cls=Dummy, real=True) | |
mask = [] | |
for a in ordered(f.atoms(*atoms)): | |
for i in mask: | |
a = a.replace(*i) | |
mask.append((a, next(sym))) | |
for i, (o, n) in enumerate(mask): | |
f = f.replace(o, n) | |
mask[i] = (n, o) | |
mask = list(reversed(mask)) | |
return f, mask | |
def _invert(f_x, y, x, domain=S.Complexes): | |
r""" | |
Reduce the complex valued equation $f(x) = y$ to a set of equations | |
$$\left\{g(x) = h_1(y),\ g(x) = h_2(y),\ \dots,\ g(x) = h_n(y) \right\}$$ | |
where $g(x)$ is a simpler function than $f(x)$. The return value is a tuple | |
$(g(x), \mathrm{set}_h)$, where $g(x)$ is a function of $x$ and $\mathrm{set}_h$ is | |
the set of function $\left\{h_1(y), h_2(y), \dots, h_n(y)\right\}$. | |
Here, $y$ is not necessarily a symbol. | |
$\mathrm{set}_h$ contains the functions, along with the information | |
about the domain in which they are valid, through set | |
operations. For instance, if :math:`y = |x| - n` is inverted | |
in the real domain, then $\mathrm{set}_h$ is not simply | |
$\{-n, n\}$ as the nature of `n` is unknown; rather, it is: | |
$$ \left(\left[0, \infty\right) \cap \left\{n\right\}\right) \cup | |
\left(\left(-\infty, 0\right] \cap \left\{- n\right\}\right)$$ | |
By default, the complex domain is used which means that inverting even | |
seemingly simple functions like $\exp(x)$ will give very different | |
results from those obtained in the real domain. | |
(In the case of $\exp(x)$, the inversion via $\log$ is multi-valued | |
in the complex domain, having infinitely many branches.) | |
If you are working with real values only (or you are not sure which | |
function to use) you should probably set the domain to | |
``S.Reals`` (or use ``invert_real`` which does that automatically). | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import invert_complex, invert_real | |
>>> from sympy.abc import x, y | |
>>> from sympy import exp | |
When does exp(x) == y? | |
>>> invert_complex(exp(x), y, x) | |
(x, ImageSet(Lambda(_n, I*(2*_n*pi + arg(y)) + log(Abs(y))), Integers)) | |
>>> invert_real(exp(x), y, x) | |
(x, Intersection({log(y)}, Reals)) | |
When does exp(x) == 1? | |
>>> invert_complex(exp(x), 1, x) | |
(x, ImageSet(Lambda(_n, 2*_n*I*pi), Integers)) | |
>>> invert_real(exp(x), 1, x) | |
(x, {0}) | |
See Also | |
======== | |
invert_real, invert_complex | |
""" | |
x = sympify(x) | |
if not x.is_Symbol: | |
raise ValueError("x must be a symbol") | |
f_x = sympify(f_x) | |
if x not in f_x.free_symbols: | |
raise ValueError("Inverse of constant function doesn't exist") | |
y = sympify(y) | |
if x in y.free_symbols: | |
raise ValueError("y should be independent of x ") | |
if domain.is_subset(S.Reals): | |
x1, s = _invert_real(f_x, FiniteSet(y), x) | |
else: | |
x1, s = _invert_complex(f_x, FiniteSet(y), x) | |
# f couldn't be inverted completely; return unmodified. | |
if x1 != x: | |
return x1, s | |
# Avoid adding gratuitous intersections with S.Complexes. Actual | |
# conditions should be handled by the respective inverters. | |
if domain is S.Complexes: | |
return x1, s | |
if isinstance(s, FiniteSet): | |
return x1, s.intersect(domain) | |
# "Fancier" solution sets like those obtained by inversion of trigonometric | |
# functions already include general validity conditions (i.e. conditions on | |
# the domain of the respective inverse functions), so we should avoid adding | |
# blanket intesections with S.Reals. But subsets of R (or C) must still be | |
# accounted for. | |
if domain is S.Reals: | |
return x1, s | |
else: | |
return x1, s.intersect(domain) | |
invert_complex = _invert | |
def invert_real(f_x, y, x): | |
""" | |
Inverts a real-valued function. Same as :func:`invert_complex`, but sets | |
the domain to ``S.Reals`` before inverting. | |
""" | |
return _invert(f_x, y, x, S.Reals) | |
def _invert_real(f, g_ys, symbol): | |
"""Helper function for _invert.""" | |
if f == symbol or g_ys is S.EmptySet: | |
return (symbol, g_ys) | |
n = Dummy('n', real=True) | |
if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1): | |
return _invert_real(f.exp, | |
imageset(Lambda(n, log(n)), g_ys), | |
symbol) | |
if hasattr(f, 'inverse') and f.inverse() is not None and not isinstance(f, ( | |
TrigonometricFunction, | |
HyperbolicFunction, | |
)): | |
if len(f.args) > 1: | |
raise ValueError("Only functions with one argument are supported.") | |
return _invert_real(f.args[0], | |
imageset(Lambda(n, f.inverse()(n)), g_ys), | |
symbol) | |
if isinstance(f, Abs): | |
return _invert_abs(f.args[0], g_ys, symbol) | |
if f.is_Add: | |
# f = g + h | |
g, h = f.as_independent(symbol) | |
if g is not S.Zero: | |
return _invert_real(h, imageset(Lambda(n, n - g), g_ys), symbol) | |
if f.is_Mul: | |
# f = g*h | |
g, h = f.as_independent(symbol) | |
if g is not S.One: | |
return _invert_real(h, imageset(Lambda(n, n/g), g_ys), symbol) | |
if f.is_Pow: | |
base, expo = f.args | |
base_has_sym = base.has(symbol) | |
expo_has_sym = expo.has(symbol) | |
if not expo_has_sym: | |
if expo.is_rational: | |
num, den = expo.as_numer_denom() | |
if den % 2 == 0 and num % 2 == 1 and den.is_zero is False: | |
# Here we have f(x)**(num/den) = y | |
# where den is nonzero and even and y is an element | |
# of the set g_ys. | |
# den is even, so we are only interested in the cases | |
# where both f(x) and y are positive. | |
# Restricting y to be positive (using the set g_ys_pos) | |
# means that y**(den/num) is always positive. | |
# Therefore it isn't necessary to also constrain f(x) | |
# to be positive because we are only going to | |
# find solutions of f(x) = y**(d/n) | |
# where the rhs is already required to be positive. | |
root = Lambda(n, real_root(n, expo)) | |
g_ys_pos = g_ys & Interval(0, oo) | |
res = imageset(root, g_ys_pos) | |
_inv, _set = _invert_real(base, res, symbol) | |
return (_inv, _set) | |
if den % 2 == 1: | |
root = Lambda(n, real_root(n, expo)) | |
res = imageset(root, g_ys) | |
if num % 2 == 0: | |
neg_res = imageset(Lambda(n, -n), res) | |
return _invert_real(base, res + neg_res, symbol) | |
if num % 2 == 1: | |
return _invert_real(base, res, symbol) | |
elif expo.is_irrational: | |
root = Lambda(n, real_root(n, expo)) | |
g_ys_pos = g_ys & Interval(0, oo) | |
res = imageset(root, g_ys_pos) | |
return _invert_real(base, res, symbol) | |
else: | |
# indeterminate exponent, e.g. Float or parity of | |
# num, den of rational could not be determined | |
pass # use default return | |
if not base_has_sym: | |
rhs = g_ys.args[0] | |
if base.is_positive: | |
return _invert_real(expo, | |
imageset(Lambda(n, log(n, base, evaluate=False)), g_ys), symbol) | |
elif base.is_negative: | |
s, b = integer_log(rhs, base) | |
if b: | |
return _invert_real(expo, FiniteSet(s), symbol) | |
else: | |
return (expo, S.EmptySet) | |
elif base.is_zero: | |
one = Eq(rhs, 1) | |
if one == S.true: | |
# special case: 0**x - 1 | |
return _invert_real(expo, FiniteSet(0), symbol) | |
elif one == S.false: | |
return (expo, S.EmptySet) | |
if isinstance(f, (TrigonometricFunction, HyperbolicFunction)): | |
return _invert_trig_hyp_real(f, g_ys, symbol) | |
return (f, g_ys) | |
# Dictionaries of inverses will be cached after first use. | |
_trig_inverses = None | |
_hyp_inverses = None | |
def _invert_trig_hyp_real(f, g_ys, symbol): | |
"""Helper function for inverting trigonometric and hyperbolic functions. | |
This helper only handles inversion over the reals. | |
For trigonometric functions only finite `g_ys` sets are implemented. | |
For hyperbolic functions the set `g_ys` is checked against the domain of the | |
respective inverse functions. Infinite `g_ys` sets are also supported. | |
""" | |
if isinstance(f, HyperbolicFunction): | |
n = Dummy('n', real=True) | |
if isinstance(f, sinh): | |
# asinh is defined over R. | |
return _invert_real(f.args[0], imageset(n, asinh(n), g_ys), symbol) | |
if isinstance(f, cosh): | |
g_ys_dom = g_ys.intersect(Interval(1, oo)) | |
if isinstance(g_ys_dom, Intersection): | |
# could not properly resolve domain check | |
if isinstance(g_ys, FiniteSet): | |
# If g_ys is a `FiniteSet`` it should be sufficient to just | |
# let the calling `_invert_real()` add an intersection with | |
# `S.Reals` (or a subset `domain`) to ensure that only valid | |
# (real) solutions are returned. | |
# This avoids adding "too many" Intersections or | |
# ConditionSets in the returned set. | |
g_ys_dom = g_ys | |
else: | |
return (f, g_ys) | |
return _invert_real(f.args[0], Union( | |
imageset(n, acosh(n), g_ys_dom), | |
imageset(n, -acosh(n), g_ys_dom)), symbol) | |
if isinstance(f, sech): | |
g_ys_dom = g_ys.intersect(Interval.Lopen(0, 1)) | |
if isinstance(g_ys_dom, Intersection): | |
if isinstance(g_ys, FiniteSet): | |
g_ys_dom = g_ys | |
else: | |
return (f, g_ys) | |
return _invert_real(f.args[0], Union( | |
imageset(n, asech(n), g_ys_dom), | |
imageset(n, -asech(n), g_ys_dom)), symbol) | |
if isinstance(f, tanh): | |
g_ys_dom = g_ys.intersect(Interval.open(-1, 1)) | |
if isinstance(g_ys_dom, Intersection): | |
if isinstance(g_ys, FiniteSet): | |
g_ys_dom = g_ys | |
else: | |
return (f, g_ys) | |
return _invert_real(f.args[0], | |
imageset(n, atanh(n), g_ys_dom), symbol) | |
if isinstance(f, coth): | |
g_ys_dom = g_ys - Interval(-1, 1) | |
if isinstance(g_ys_dom, Complement): | |
if isinstance(g_ys, FiniteSet): | |
g_ys_dom = g_ys | |
else: | |
return (f, g_ys) | |
return _invert_real(f.args[0], | |
imageset(n, acoth(n), g_ys_dom), symbol) | |
if isinstance(f, csch): | |
g_ys_dom = g_ys - FiniteSet(0) | |
if isinstance(g_ys_dom, Complement): | |
if isinstance(g_ys, FiniteSet): | |
g_ys_dom = g_ys | |
else: | |
return (f, g_ys) | |
return _invert_real(f.args[0], | |
imageset(n, acsch(n), g_ys_dom), symbol) | |
elif isinstance(f, TrigonometricFunction) and isinstance(g_ys, FiniteSet): | |
def _get_trig_inverses(func): | |
global _trig_inverses | |
if _trig_inverses is None: | |
_trig_inverses = { | |
sin : ((asin, lambda y: pi-asin(y)), 2*pi, Interval(-1, 1)), | |
cos : ((acos, lambda y: -acos(y)), 2*pi, Interval(-1, 1)), | |
tan : ((atan,), pi, S.Reals), | |
cot : ((acot,), pi, S.Reals), | |
sec : ((asec, lambda y: -asec(y)), 2*pi, | |
Union(Interval(-oo, -1), Interval(1, oo))), | |
csc : ((acsc, lambda y: pi-acsc(y)), 2*pi, | |
Union(Interval(-oo, -1), Interval(1, oo)))} | |
return _trig_inverses[func] | |
invs, period, rng = _get_trig_inverses(f.func) | |
n = Dummy('n', integer=True) | |
def create_return_set(g): | |
# returns ConditionSet that will be part of the final (x, set) tuple | |
invsimg = Union(*[ | |
imageset(n, period*n + inv(g), S.Integers) for inv in invs]) | |
inv_f, inv_g_ys = _invert_real(f.args[0], invsimg, symbol) | |
if inv_f == symbol: # inversion successful | |
conds = rng.contains(g) | |
return ConditionSet(symbol, conds, inv_g_ys) | |
else: | |
return ConditionSet(symbol, Eq(f, g), S.Reals) | |
retset = Union(*[create_return_set(g) for g in g_ys]) | |
return (symbol, retset) | |
else: | |
return (f, g_ys) | |
def _invert_trig_hyp_complex(f, g_ys, symbol): | |
"""Helper function for inverting trigonometric and hyperbolic functions. | |
This helper only handles inversion over the complex numbers. | |
Only finite `g_ys` sets are implemented. | |
Handling of singularities is only implemented for hyperbolic equations. | |
In case of a symbolic element g in g_ys a ConditionSet may be returned. | |
""" | |
if isinstance(f, TrigonometricFunction) and isinstance(g_ys, FiniteSet): | |
def inv(trig): | |
if isinstance(trig, (sin, csc)): | |
F = asin if isinstance(trig, sin) else acsc | |
return ( | |
lambda a: 2*n*pi + F(a), | |
lambda a: 2*n*pi + pi - F(a)) | |
if isinstance(trig, (cos, sec)): | |
F = acos if isinstance(trig, cos) else asec | |
return ( | |
lambda a: 2*n*pi + F(a), | |
lambda a: 2*n*pi - F(a)) | |
if isinstance(trig, (tan, cot)): | |
return (lambda a: n*pi + trig.inverse()(a),) | |
n = Dummy('n', integer=True) | |
invs = S.EmptySet | |
for L in inv(f): | |
invs += Union(*[imageset(Lambda(n, L(g)), S.Integers) for g in g_ys]) | |
return _invert_complex(f.args[0], invs, symbol) | |
elif isinstance(f, HyperbolicFunction) and isinstance(g_ys, FiniteSet): | |
# There are two main options regarding singularities / domain checking | |
# for symbolic elements in g_ys: | |
# 1. Add a "catch-all" intersection with S.Complexes. | |
# 2. ConditionSets. | |
# At present ConditionSets seem to work better and have the additional | |
# benefit of representing the precise conditions that must be satisfied. | |
# The conditions are also rather straightforward. (At most two isolated | |
# points.) | |
def _get_hyp_inverses(func): | |
global _hyp_inverses | |
if _hyp_inverses is None: | |
_hyp_inverses = { | |
sinh : ((asinh, lambda y: I*pi-asinh(y)), 2*I*pi, ()), | |
cosh : ((acosh, lambda y: -acosh(y)), 2*I*pi, ()), | |
tanh : ((atanh,), I*pi, (-1, 1)), | |
coth : ((acoth,), I*pi, (-1, 1)), | |
sech : ((asech, lambda y: -asech(y)), 2*I*pi, (0, )), | |
csch : ((acsch, lambda y: I*pi-acsch(y)), 2*I*pi, (0, ))} | |
return _hyp_inverses[func] | |
# invs: iterable of main inverses, e.g. (acosh, -acosh). | |
# excl: iterable of singularities to be checked for. | |
invs, period, excl = _get_hyp_inverses(f.func) | |
n = Dummy('n', integer=True) | |
def create_return_set(g): | |
# returns ConditionSet that will be part of the final (x, set) tuple | |
invsimg = Union(*[ | |
imageset(n, period*n + inv(g), S.Integers) for inv in invs]) | |
inv_f, inv_g_ys = _invert_complex(f.args[0], invsimg, symbol) | |
if inv_f == symbol: # inversion successful | |
conds = And(*[Ne(g, e) for e in excl]) | |
return ConditionSet(symbol, conds, inv_g_ys) | |
else: | |
return ConditionSet(symbol, Eq(f, g), S.Complexes) | |
retset = Union(*[create_return_set(g) for g in g_ys]) | |
return (symbol, retset) | |
else: | |
return (f, g_ys) | |
def _invert_complex(f, g_ys, symbol): | |
"""Helper function for _invert.""" | |
if f == symbol or g_ys is S.EmptySet: | |
return (symbol, g_ys) | |
n = Dummy('n') | |
if f.is_Add: | |
# f = g + h | |
g, h = f.as_independent(symbol) | |
if g is not S.Zero: | |
return _invert_complex(h, imageset(Lambda(n, n - g), g_ys), symbol) | |
if f.is_Mul: | |
# f = g*h | |
g, h = f.as_independent(symbol) | |
if g is not S.One: | |
if g in {S.NegativeInfinity, S.ComplexInfinity, S.Infinity}: | |
return (h, S.EmptySet) | |
return _invert_complex(h, imageset(Lambda(n, n/g), g_ys), symbol) | |
if f.is_Pow: | |
base, expo = f.args | |
# special case: g**r = 0 | |
# Could be improved like `_invert_real` to handle more general cases. | |
if expo.is_Rational and g_ys == FiniteSet(0): | |
if expo.is_positive: | |
return _invert_complex(base, g_ys, symbol) | |
if hasattr(f, 'inverse') and f.inverse() is not None and \ | |
not isinstance(f, TrigonometricFunction) and \ | |
not isinstance(f, HyperbolicFunction) and \ | |
not isinstance(f, exp): | |
if len(f.args) > 1: | |
raise ValueError("Only functions with one argument are supported.") | |
return _invert_complex(f.args[0], | |
imageset(Lambda(n, f.inverse()(n)), g_ys), symbol) | |
if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1): | |
if isinstance(g_ys, ImageSet): | |
# can solve up to `(d*exp(exp(...(exp(a*x + b))...) + c)` format. | |
# Further can be improved to `(d*exp(exp(...(exp(a*x**n + b*x**(n-1) + ... + f))...) + c)`. | |
g_ys_expr = g_ys.lamda.expr | |
g_ys_vars = g_ys.lamda.variables | |
k = Dummy('k{}'.format(len(g_ys_vars))) | |
g_ys_vars_1 = (k,) + g_ys_vars | |
exp_invs = Union(*[imageset(Lambda((g_ys_vars_1,), (I*(2*k*pi + arg(g_ys_expr)) | |
+ log(Abs(g_ys_expr)))), S.Integers**(len(g_ys_vars_1)))]) | |
return _invert_complex(f.exp, exp_invs, symbol) | |
elif isinstance(g_ys, FiniteSet): | |
exp_invs = Union(*[imageset(Lambda(n, I*(2*n*pi + arg(g_y)) + | |
log(Abs(g_y))), S.Integers) | |
for g_y in g_ys if g_y != 0]) | |
return _invert_complex(f.exp, exp_invs, symbol) | |
if isinstance(f, (TrigonometricFunction, HyperbolicFunction)): | |
return _invert_trig_hyp_complex(f, g_ys, symbol) | |
return (f, g_ys) | |
def _invert_abs(f, g_ys, symbol): | |
"""Helper function for inverting absolute value functions. | |
Returns the complete result of inverting an absolute value | |
function along with the conditions which must also be satisfied. | |
If it is certain that all these conditions are met, a :class:`~.FiniteSet` | |
of all possible solutions is returned. If any condition cannot be | |
satisfied, an :class:`~.EmptySet` is returned. Otherwise, a | |
:class:`~.ConditionSet` of the solutions, with all the required conditions | |
specified, is returned. | |
""" | |
if not g_ys.is_FiniteSet: | |
# this could be used for FiniteSet, but the | |
# results are more compact if they aren't, e.g. | |
# ConditionSet(x, Contains(n, Interval(0, oo)), {-n, n}) vs | |
# Union(Intersection(Interval(0, oo), {n}), Intersection(Interval(-oo, 0), {-n})) | |
# for the solution of abs(x) - n | |
pos = Intersection(g_ys, Interval(0, S.Infinity)) | |
parg = _invert_real(f, pos, symbol) | |
narg = _invert_real(-f, pos, symbol) | |
if parg[0] != narg[0]: | |
raise NotImplementedError | |
return parg[0], Union(narg[1], parg[1]) | |
# check conditions: all these must be true. If any are unknown | |
# then return them as conditions which must be satisfied | |
unknown = [] | |
for a in g_ys.args: | |
ok = a.is_nonnegative if a.is_Number else a.is_positive | |
if ok is None: | |
unknown.append(a) | |
elif not ok: | |
return symbol, S.EmptySet | |
if unknown: | |
conditions = And(*[Contains(i, Interval(0, oo)) | |
for i in unknown]) | |
else: | |
conditions = True | |
n = Dummy('n', real=True) | |
# this is slightly different than above: instead of solving | |
# +/-f on positive values, here we solve for f on +/- g_ys | |
g_x, values = _invert_real(f, Union( | |
imageset(Lambda(n, n), g_ys), | |
imageset(Lambda(n, -n), g_ys)), symbol) | |
return g_x, ConditionSet(g_x, conditions, values) | |
def domain_check(f, symbol, p): | |
"""Returns False if point p is infinite or any subexpression of f | |
is infinite or becomes so after replacing symbol with p. If none of | |
these conditions is met then True will be returned. | |
Examples | |
======== | |
>>> from sympy import Mul, oo | |
>>> from sympy.abc import x | |
>>> from sympy.solvers.solveset import domain_check | |
>>> g = 1/(1 + (1/(x + 1))**2) | |
>>> domain_check(g, x, -1) | |
False | |
>>> domain_check(x**2, x, 0) | |
True | |
>>> domain_check(1/x, x, oo) | |
False | |
* The function relies on the assumption that the original form | |
of the equation has not been changed by automatic simplification. | |
>>> domain_check(x/x, x, 0) # x/x is automatically simplified to 1 | |
True | |
* To deal with automatic evaluations use evaluate=False: | |
>>> domain_check(Mul(x, 1/x, evaluate=False), x, 0) | |
False | |
""" | |
f, p = sympify(f), sympify(p) | |
if p.is_infinite: | |
return False | |
return _domain_check(f, symbol, p) | |
def _domain_check(f, symbol, p): | |
# helper for domain check | |
if f.is_Atom and f.is_finite: | |
return True | |
elif f.subs(symbol, p).is_infinite: | |
return False | |
elif isinstance(f, Piecewise): | |
# Check the cases of the Piecewise in turn. There might be invalid | |
# expressions in later cases that don't apply e.g. | |
# solveset(Piecewise((0, Eq(x, 0)), (1/x, True)), x) | |
for expr, cond in f.args: | |
condsubs = cond.subs(symbol, p) | |
if condsubs is S.false: | |
continue | |
elif condsubs is S.true: | |
return _domain_check(expr, symbol, p) | |
else: | |
# We don't know which case of the Piecewise holds. On this | |
# basis we cannot decide whether any solution is in or out of | |
# the domain. Ideally this function would allow returning a | |
# symbolic condition for the validity of the solution that | |
# could be handled in the calling code. In the mean time we'll | |
# give this particular solution the benefit of the doubt and | |
# let it pass. | |
return True | |
else: | |
# TODO : We should not blindly recurse through all args of arbitrary expressions like this | |
return all(_domain_check(g, symbol, p) | |
for g in f.args) | |
def _is_finite_with_finite_vars(f, domain=S.Complexes): | |
""" | |
Return True if the given expression is finite. For symbols that | |
do not assign a value for `complex` and/or `real`, the domain will | |
be used to assign a value; symbols that do not assign a value | |
for `finite` will be made finite. All other assumptions are | |
left unmodified. | |
""" | |
def assumptions(s): | |
A = s.assumptions0 | |
A.setdefault('finite', A.get('finite', True)) | |
if domain.is_subset(S.Reals): | |
# if this gets set it will make complex=True, too | |
A.setdefault('real', True) | |
else: | |
# don't change 'real' because being complex implies | |
# nothing about being real | |
A.setdefault('complex', True) | |
return A | |
reps = {s: Dummy(**assumptions(s)) for s in f.free_symbols} | |
return f.xreplace(reps).is_finite | |
def _is_function_class_equation(func_class, f, symbol): | |
""" Tests whether the equation is an equation of the given function class. | |
The given equation belongs to the given function class if it is | |
comprised of functions of the function class which are multiplied by | |
or added to expressions independent of the symbol. In addition, the | |
arguments of all such functions must be linear in the symbol as well. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import _is_function_class_equation | |
>>> from sympy import tan, sin, tanh, sinh, exp | |
>>> from sympy.abc import x | |
>>> from sympy.functions.elementary.trigonometric import TrigonometricFunction | |
>>> from sympy.functions.elementary.hyperbolic import HyperbolicFunction | |
>>> _is_function_class_equation(TrigonometricFunction, exp(x) + tan(x), x) | |
False | |
>>> _is_function_class_equation(TrigonometricFunction, tan(x) + sin(x), x) | |
True | |
>>> _is_function_class_equation(TrigonometricFunction, tan(x**2), x) | |
False | |
>>> _is_function_class_equation(TrigonometricFunction, tan(x + 2), x) | |
True | |
>>> _is_function_class_equation(HyperbolicFunction, tanh(x) + sinh(x), x) | |
True | |
""" | |
if f.is_Mul or f.is_Add: | |
return all(_is_function_class_equation(func_class, arg, symbol) | |
for arg in f.args) | |
if f.is_Pow: | |
if not f.exp.has(symbol): | |
return _is_function_class_equation(func_class, f.base, symbol) | |
else: | |
return False | |
if not f.has(symbol): | |
return True | |
if isinstance(f, func_class): | |
try: | |
g = Poly(f.args[0], symbol) | |
return g.degree() <= 1 | |
except PolynomialError: | |
return False | |
else: | |
return False | |
def _solve_as_rational(f, symbol, domain): | |
""" solve rational functions""" | |
f = together(_mexpand(f, recursive=True), deep=True) | |
g, h = fraction(f) | |
if not h.has(symbol): | |
try: | |
return _solve_as_poly(g, symbol, domain) | |
except NotImplementedError: | |
# The polynomial formed from g could end up having | |
# coefficients in a ring over which finding roots | |
# isn't implemented yet, e.g. ZZ[a] for some symbol a | |
return ConditionSet(symbol, Eq(f, 0), domain) | |
except CoercionFailed: | |
# contained oo, zoo or nan | |
return S.EmptySet | |
else: | |
valid_solns = _solveset(g, symbol, domain) | |
invalid_solns = _solveset(h, symbol, domain) | |
return valid_solns - invalid_solns | |
class _SolveTrig1Error(Exception): | |
"""Raised when _solve_trig1 heuristics do not apply""" | |
def _solve_trig(f, symbol, domain): | |
"""Function to call other helpers to solve trigonometric equations """ | |
# If f is composed of a single trig function (potentially appearing multiple | |
# times) we should solve by either inverting directly or inverting after a | |
# suitable change of variable. | |
# | |
# _solve_trig is currently only called by _solveset for trig/hyperbolic | |
# functions of an argument linear in x. Inverting a symbolic argument should | |
# include a guard against division by zero in order to have a result that is | |
# consistent with similar processing done by _solve_trig1. | |
# (Ideally _invert should add these conditions by itself.) | |
trig_expr, count = None, 0 | |
for expr in preorder_traversal(f): | |
if isinstance(expr, (TrigonometricFunction, | |
HyperbolicFunction)) and expr.has(symbol): | |
if not trig_expr: | |
trig_expr, count = expr, 1 | |
elif expr == trig_expr: | |
count += 1 | |
else: | |
trig_expr, count = False, 0 | |
break | |
if count == 1: | |
# direct inversion | |
x, sol = _invert(f, 0, symbol, domain) | |
if x == symbol: | |
cond = True | |
if trig_expr.free_symbols - {symbol}: | |
a, h = trig_expr.args[0].as_independent(symbol, as_Add=True) | |
m, h = h.as_independent(symbol, as_Add=False) | |
num, den = m.as_numer_denom() | |
cond = Ne(num, 0) & Ne(den, 0) | |
return ConditionSet(symbol, cond, sol) | |
else: | |
return ConditionSet(symbol, Eq(f, 0), domain) | |
elif count: | |
# solve by change of variable | |
y = Dummy('y') | |
f_cov = f.subs(trig_expr, y) | |
sol_cov = solveset(f_cov, y, domain) | |
if isinstance(sol_cov, FiniteSet): | |
return Union( | |
*[_solve_trig(trig_expr-s, symbol, domain) for s in sol_cov]) | |
sol = None | |
try: | |
# multiple trig/hyp functions; solve by rewriting to exp | |
sol = _solve_trig1(f, symbol, domain) | |
except _SolveTrig1Error: | |
try: | |
# multiple trig/hyp functions; solve by rewriting to tan(x/2) | |
sol = _solve_trig2(f, symbol, domain) | |
except ValueError: | |
raise NotImplementedError(filldedent(''' | |
Solution to this kind of trigonometric equations | |
is yet to be implemented''')) | |
return sol | |
def _solve_trig1(f, symbol, domain): | |
"""Primary solver for trigonometric and hyperbolic equations | |
Returns either the solution set as a ConditionSet (auto-evaluated to a | |
union of ImageSets if no variables besides 'symbol' are involved) or | |
raises _SolveTrig1Error if f == 0 cannot be solved. | |
Notes | |
===== | |
Algorithm: | |
1. Do a change of variable x -> mu*x in arguments to trigonometric and | |
hyperbolic functions, in order to reduce them to small integers. (This | |
step is crucial to keep the degrees of the polynomials of step 4 low.) | |
2. Rewrite trigonometric/hyperbolic functions as exponentials. | |
3. Proceed to a 2nd change of variable, replacing exp(I*x) or exp(x) by y. | |
4. Solve the resulting rational equation. | |
5. Use invert_complex or invert_real to return to the original variable. | |
6. If the coefficients of 'symbol' were symbolic in nature, add the | |
necessary consistency conditions in a ConditionSet. | |
""" | |
# Prepare change of variable | |
x = Dummy('x') | |
if _is_function_class_equation(HyperbolicFunction, f, symbol): | |
cov = exp(x) | |
inverter = invert_real if domain.is_subset(S.Reals) else invert_complex | |
else: | |
cov = exp(I*x) | |
inverter = invert_complex | |
f = trigsimp(f) | |
f_original = f | |
trig_functions = f.atoms(TrigonometricFunction, HyperbolicFunction) | |
trig_arguments = [e.args[0] for e in trig_functions] | |
# trigsimp may have reduced the equation to an expression | |
# that is independent of 'symbol' (e.g. cos**2+sin**2) | |
if not any(a.has(symbol) for a in trig_arguments): | |
return solveset(f_original, symbol, domain) | |
denominators = [] | |
numerators = [] | |
for ar in trig_arguments: | |
try: | |
poly_ar = Poly(ar, symbol) | |
except PolynomialError: | |
raise _SolveTrig1Error("trig argument is not a polynomial") | |
if poly_ar.degree() > 1: # degree >1 still bad | |
raise _SolveTrig1Error("degree of variable must not exceed one") | |
if poly_ar.degree() == 0: # degree 0, don't care | |
continue | |
c = poly_ar.all_coeffs()[0] # got the coefficient of 'symbol' | |
numerators.append(fraction(c)[0]) | |
denominators.append(fraction(c)[1]) | |
mu = lcm(denominators)/gcd(numerators) | |
f = f.subs(symbol, mu*x) | |
f = f.rewrite(exp) | |
f = together(f) | |
g, h = fraction(f) | |
y = Dummy('y') | |
g, h = g.expand(), h.expand() | |
g, h = g.subs(cov, y), h.subs(cov, y) | |
if g.has(x) or h.has(x): | |
raise _SolveTrig1Error("change of variable not possible") | |
solns = solveset_complex(g, y) - solveset_complex(h, y) | |
if isinstance(solns, ConditionSet): | |
raise _SolveTrig1Error("polynomial has ConditionSet solution") | |
if isinstance(solns, FiniteSet): | |
if any(isinstance(s, RootOf) for s in solns): | |
raise _SolveTrig1Error("polynomial results in RootOf object") | |
# revert the change of variable | |
cov = cov.subs(x, symbol/mu) | |
result = Union(*[inverter(cov, s, symbol)[1] for s in solns]) | |
# In case of symbolic coefficients, the solution set is only valid | |
# if numerator and denominator of mu are non-zero. | |
if mu.has(Symbol): | |
syms = (mu).atoms(Symbol) | |
munum, muden = fraction(mu) | |
condnum = munum.as_independent(*syms, as_Add=False)[1] | |
condden = muden.as_independent(*syms, as_Add=False)[1] | |
cond = And(Ne(condnum, 0), Ne(condden, 0)) | |
else: | |
cond = True | |
# Actual conditions are returned as part of the ConditionSet. Adding an | |
# intersection with C would only complicate some solution sets due to | |
# current limitations of intersection code. (e.g. #19154) | |
if domain is S.Complexes: | |
# This is a slight abuse of ConditionSet. Ideally this should | |
# be some kind of "PiecewiseSet". (See #19507 discussion) | |
return ConditionSet(symbol, cond, result) | |
else: | |
return ConditionSet(symbol, cond, Intersection(result, domain)) | |
elif solns is S.EmptySet: | |
return S.EmptySet | |
else: | |
raise _SolveTrig1Error("polynomial solutions must form FiniteSet") | |
def _solve_trig2(f, symbol, domain): | |
"""Secondary helper to solve trigonometric equations, | |
called when first helper fails """ | |
f = trigsimp(f) | |
f_original = f | |
trig_functions = f.atoms(sin, cos, tan, sec, cot, csc) | |
trig_arguments = [e.args[0] for e in trig_functions] | |
denominators = [] | |
numerators = [] | |
# todo: This solver can be extended to hyperbolics if the | |
# analogous change of variable to tanh (instead of tan) | |
# is used. | |
if not trig_functions: | |
return ConditionSet(symbol, Eq(f_original, 0), domain) | |
# todo: The pre-processing below (extraction of numerators, denominators, | |
# gcd, lcm, mu, etc.) should be updated to the enhanced version in | |
# _solve_trig1. (See #19507) | |
for ar in trig_arguments: | |
try: | |
poly_ar = Poly(ar, symbol) | |
except PolynomialError: | |
raise ValueError("give up, we cannot solve if this is not a polynomial in x") | |
if poly_ar.degree() > 1: # degree >1 still bad | |
raise ValueError("degree of variable inside polynomial should not exceed one") | |
if poly_ar.degree() == 0: # degree 0, don't care | |
continue | |
c = poly_ar.all_coeffs()[0] # got the coefficient of 'symbol' | |
try: | |
numerators.append(Rational(c).p) | |
denominators.append(Rational(c).q) | |
except TypeError: | |
return ConditionSet(symbol, Eq(f_original, 0), domain) | |
x = Dummy('x') | |
mu = Rational(2)*number_lcm(*denominators)/number_gcd(*numerators) | |
f = f.subs(symbol, mu*x) | |
f = f.rewrite(tan) | |
f = expand_trig(f) | |
f = together(f) | |
g, h = fraction(f) | |
y = Dummy('y') | |
g, h = g.expand(), h.expand() | |
g, h = g.subs(tan(x), y), h.subs(tan(x), y) | |
if g.has(x) or h.has(x): | |
return ConditionSet(symbol, Eq(f_original, 0), domain) | |
solns = solveset(g, y, S.Reals) - solveset(h, y, S.Reals) | |
if isinstance(solns, FiniteSet): | |
result = Union(*[invert_real(tan(symbol/mu), s, symbol)[1] | |
for s in solns]) | |
dsol = invert_real(tan(symbol/mu), oo, symbol)[1] | |
if degree(h) > degree(g): # If degree(denom)>degree(num) then there | |
result = Union(result, dsol) # would be another sol at Lim(denom-->oo) | |
return Intersection(result, domain) | |
elif solns is S.EmptySet: | |
return S.EmptySet | |
else: | |
return ConditionSet(symbol, Eq(f_original, 0), S.Reals) | |
def _solve_as_poly(f, symbol, domain=S.Complexes): | |
""" | |
Solve the equation using polynomial techniques if it already is a | |
polynomial equation or, with a change of variables, can be made so. | |
""" | |
result = None | |
if f.is_polynomial(symbol): | |
solns = roots(f, symbol, cubics=True, quartics=True, | |
quintics=True, domain='EX') | |
num_roots = sum(solns.values()) | |
if degree(f, symbol) <= num_roots: | |
result = FiniteSet(*solns.keys()) | |
else: | |
poly = Poly(f, symbol) | |
solns = poly.all_roots() | |
if poly.degree() <= len(solns): | |
result = FiniteSet(*solns) | |
else: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
else: | |
poly = Poly(f) | |
if poly is None: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
gens = [g for g in poly.gens if g.has(symbol)] | |
if len(gens) == 1: | |
poly = Poly(poly, gens[0]) | |
gen = poly.gen | |
deg = poly.degree() | |
poly = Poly(poly.as_expr(), poly.gen, composite=True) | |
poly_solns = FiniteSet(*roots(poly, cubics=True, quartics=True, | |
quintics=True).keys()) | |
if len(poly_solns) < deg: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
if gen != symbol: | |
y = Dummy('y') | |
inverter = invert_real if domain.is_subset(S.Reals) else invert_complex | |
lhs, rhs_s = inverter(gen, y, symbol) | |
if lhs == symbol: | |
result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) | |
if isinstance(result, FiniteSet) and isinstance(gen, Pow | |
) and gen.base.is_Rational: | |
result = FiniteSet(*[expand_log(i) for i in result]) | |
else: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
else: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
if result is not None: | |
if isinstance(result, FiniteSet): | |
# this is to simplify solutions like -sqrt(-I) to sqrt(2)/2 | |
# - sqrt(2)*I/2. We are not expanding for solution with symbols | |
# or undefined functions because that makes the solution more complicated. | |
# For example, expand_complex(a) returns re(a) + I*im(a) | |
if all(s.atoms(Symbol, AppliedUndef) == set() and not isinstance(s, RootOf) | |
for s in result): | |
s = Dummy('s') | |
result = imageset(Lambda(s, expand_complex(s)), result) | |
if isinstance(result, FiniteSet) and domain != S.Complexes: | |
# Avoid adding gratuitous intersections with S.Complexes. Actual | |
# conditions should be handled elsewhere. | |
result = result.intersection(domain) | |
return result | |
else: | |
return ConditionSet(symbol, Eq(f, 0), domain) | |
def _solve_radical(f, unradf, symbol, solveset_solver): | |
""" Helper function to solve equations with radicals """ | |
res = unradf | |
eq, cov = res if res else (f, []) | |
if not cov: | |
result = solveset_solver(eq, symbol) - \ | |
Union(*[solveset_solver(g, symbol) for g in denoms(f, symbol)]) | |
else: | |
y, yeq = cov | |
if not solveset_solver(y - I, y): | |
yreal = Dummy('yreal', real=True) | |
yeq = yeq.xreplace({y: yreal}) | |
eq = eq.xreplace({y: yreal}) | |
y = yreal | |
g_y_s = solveset_solver(yeq, symbol) | |
f_y_sols = solveset_solver(eq, y) | |
result = Union(*[imageset(Lambda(y, g_y), f_y_sols) | |
for g_y in g_y_s]) | |
def check_finiteset(solutions): | |
f_set = [] # solutions for FiniteSet | |
c_set = [] # solutions for ConditionSet | |
for s in solutions: | |
if checksol(f, symbol, s): | |
f_set.append(s) | |
else: | |
c_set.append(s) | |
return FiniteSet(*f_set) + ConditionSet(symbol, Eq(f, 0), FiniteSet(*c_set)) | |
def check_set(solutions): | |
if solutions is S.EmptySet: | |
return solutions | |
elif isinstance(solutions, ConditionSet): | |
# XXX: Maybe the base set should be checked? | |
return solutions | |
elif isinstance(solutions, FiniteSet): | |
return check_finiteset(solutions) | |
elif isinstance(solutions, Complement): | |
A, B = solutions.args | |
return Complement(check_set(A), B) | |
elif isinstance(solutions, Union): | |
return Union(*[check_set(s) for s in solutions.args]) | |
else: | |
# XXX: There should be more cases checked here. The cases above | |
# are all those that come up in the test suite for now. | |
return solutions | |
solution_set = check_set(result) | |
return solution_set | |
def _solve_abs(f, symbol, domain): | |
""" Helper function to solve equation involving absolute value function """ | |
if not domain.is_subset(S.Reals): | |
raise ValueError(filldedent(''' | |
Absolute values cannot be inverted in the | |
complex domain.''')) | |
p, q, r = Wild('p'), Wild('q'), Wild('r') | |
pattern_match = f.match(p*Abs(q) + r) or {} | |
f_p, f_q, f_r = [pattern_match.get(i, S.Zero) for i in (p, q, r)] | |
if not (f_p.is_zero or f_q.is_zero): | |
domain = continuous_domain(f_q, symbol, domain) | |
from .inequalities import solve_univariate_inequality | |
q_pos_cond = solve_univariate_inequality(f_q >= 0, symbol, | |
relational=False, domain=domain, continuous=True) | |
q_neg_cond = q_pos_cond.complement(domain) | |
sols_q_pos = solveset_real(f_p*f_q + f_r, | |
symbol).intersect(q_pos_cond) | |
sols_q_neg = solveset_real(f_p*(-f_q) + f_r, | |
symbol).intersect(q_neg_cond) | |
return Union(sols_q_pos, sols_q_neg) | |
else: | |
return ConditionSet(symbol, Eq(f, 0), domain) | |
def solve_decomposition(f, symbol, domain): | |
""" | |
Function to solve equations via the principle of "Decomposition | |
and Rewriting". | |
Examples | |
======== | |
>>> from sympy import exp, sin, Symbol, pprint, S | |
>>> from sympy.solvers.solveset import solve_decomposition as sd | |
>>> x = Symbol('x') | |
>>> f1 = exp(2*x) - 3*exp(x) + 2 | |
>>> sd(f1, x, S.Reals) | |
{0, log(2)} | |
>>> f2 = sin(x)**2 + 2*sin(x) + 1 | |
>>> pprint(sd(f2, x, S.Reals), use_unicode=False) | |
3*pi | |
{2*n*pi + ---- | n in Integers} | |
2 | |
>>> f3 = sin(x + 2) | |
>>> pprint(sd(f3, x, S.Reals), use_unicode=False) | |
{2*n*pi - 2 | n in Integers} U {2*n*pi - 2 + pi | n in Integers} | |
""" | |
from sympy.solvers.decompogen import decompogen | |
# decompose the given function | |
g_s = decompogen(f, symbol) | |
# `y_s` represents the set of values for which the function `g` is to be | |
# solved. | |
# `solutions` represent the solutions of the equations `g = y_s` or | |
# `g = 0` depending on the type of `y_s`. | |
# As we are interested in solving the equation: f = 0 | |
y_s = FiniteSet(0) | |
for g in g_s: | |
frange = function_range(g, symbol, domain) | |
y_s = Intersection(frange, y_s) | |
result = S.EmptySet | |
if isinstance(y_s, FiniteSet): | |
for y in y_s: | |
solutions = solveset(Eq(g, y), symbol, domain) | |
if not isinstance(solutions, ConditionSet): | |
result += solutions | |
else: | |
if isinstance(y_s, ImageSet): | |
iter_iset = (y_s,) | |
elif isinstance(y_s, Union): | |
iter_iset = y_s.args | |
elif y_s is S.EmptySet: | |
# y_s is not in the range of g in g_s, so no solution exists | |
#in the given domain | |
return S.EmptySet | |
for iset in iter_iset: | |
new_solutions = solveset(Eq(iset.lamda.expr, g), symbol, domain) | |
dummy_var = tuple(iset.lamda.expr.free_symbols)[0] | |
(base_set,) = iset.base_sets | |
if isinstance(new_solutions, FiniteSet): | |
new_exprs = new_solutions | |
elif isinstance(new_solutions, Intersection): | |
if isinstance(new_solutions.args[1], FiniteSet): | |
new_exprs = new_solutions.args[1] | |
for new_expr in new_exprs: | |
result += ImageSet(Lambda(dummy_var, new_expr), base_set) | |
if result is S.EmptySet: | |
return ConditionSet(symbol, Eq(f, 0), domain) | |
y_s = result | |
return y_s | |
def _solveset(f, symbol, domain, _check=False): | |
"""Helper for solveset to return a result from an expression | |
that has already been sympify'ed and is known to contain the | |
given symbol.""" | |
# _check controls whether the answer is checked or not | |
from sympy.simplify.simplify import signsimp | |
if isinstance(f, BooleanTrue): | |
return domain | |
orig_f = f | |
if f.is_Mul: | |
coeff, f = f.as_independent(symbol, as_Add=False) | |
if coeff in {S.ComplexInfinity, S.NegativeInfinity, S.Infinity}: | |
f = together(orig_f) | |
elif f.is_Add: | |
a, h = f.as_independent(symbol) | |
m, h = h.as_independent(symbol, as_Add=False) | |
if m not in {S.ComplexInfinity, S.Zero, S.Infinity, | |
S.NegativeInfinity}: | |
f = a/m + h # XXX condition `m != 0` should be added to soln | |
# assign the solvers to use | |
solver = lambda f, x, domain=domain: _solveset(f, x, domain) | |
inverter = lambda f, rhs, symbol: _invert(f, rhs, symbol, domain) | |
result = S.EmptySet | |
if f.expand().is_zero: | |
return domain | |
elif not f.has(symbol): | |
return S.EmptySet | |
elif f.is_Mul and all(_is_finite_with_finite_vars(m, domain) | |
for m in f.args): | |
# if f(x) and g(x) are both finite we can say that the solution of | |
# f(x)*g(x) == 0 is same as Union(f(x) == 0, g(x) == 0) is not true in | |
# general. g(x) can grow to infinitely large for the values where | |
# f(x) == 0. To be sure that we are not silently allowing any | |
# wrong solutions we are using this technique only if both f and g are | |
# finite for a finite input. | |
result = Union(*[solver(m, symbol) for m in f.args]) | |
elif (_is_function_class_equation(TrigonometricFunction, f, symbol) or \ | |
_is_function_class_equation(HyperbolicFunction, f, symbol)): | |
result = _solve_trig(f, symbol, domain) | |
elif isinstance(f, arg): | |
a = f.args[0] | |
result = Intersection(_solveset(re(a) > 0, symbol, domain), | |
_solveset(im(a), symbol, domain)) | |
elif f.is_Piecewise: | |
expr_set_pairs = f.as_expr_set_pairs(domain) | |
for (expr, in_set) in expr_set_pairs: | |
if in_set.is_Relational: | |
in_set = in_set.as_set() | |
solns = solver(expr, symbol, in_set) | |
result += solns | |
elif isinstance(f, Eq): | |
result = solver(Add(f.lhs, -f.rhs, evaluate=False), symbol, domain) | |
elif f.is_Relational: | |
from .inequalities import solve_univariate_inequality | |
try: | |
result = solve_univariate_inequality( | |
f, symbol, domain=domain, relational=False) | |
except NotImplementedError: | |
result = ConditionSet(symbol, f, domain) | |
return result | |
elif _is_modular(f, symbol): | |
result = _solve_modular(f, symbol, domain) | |
else: | |
lhs, rhs_s = inverter(f, 0, symbol) | |
if lhs == symbol: | |
# do some very minimal simplification since | |
# repeated inversion may have left the result | |
# in a state that other solvers (e.g. poly) | |
# would have simplified; this is done here | |
# rather than in the inverter since here it | |
# is only done once whereas there it would | |
# be repeated for each step of the inversion | |
if isinstance(rhs_s, FiniteSet): | |
rhs_s = FiniteSet(*[Mul(* | |
signsimp(i).as_content_primitive()) | |
for i in rhs_s]) | |
result = rhs_s | |
elif isinstance(rhs_s, FiniteSet): | |
for equation in [lhs - rhs for rhs in rhs_s]: | |
if equation == f: | |
u = unrad(f, symbol) | |
if u: | |
result += _solve_radical(equation, u, | |
symbol, | |
solver) | |
elif equation.has(Abs): | |
result += _solve_abs(f, symbol, domain) | |
else: | |
result_rational = _solve_as_rational(equation, symbol, domain) | |
if not isinstance(result_rational, ConditionSet): | |
result += result_rational | |
else: | |
# may be a transcendental type equation | |
t_result = _transolve(equation, symbol, domain) | |
if isinstance(t_result, ConditionSet): | |
# might need factoring; this is expensive so we | |
# have delayed until now. To avoid recursion | |
# errors look for a non-trivial factoring into | |
# a product of symbol dependent terms; I think | |
# that something that factors as a Pow would | |
# have already been recognized by now. | |
factored = equation.factor() | |
if factored.is_Mul and equation != factored: | |
_, dep = factored.as_independent(symbol) | |
if not dep.is_Add: | |
# non-trivial factoring of equation | |
# but use form with constants | |
# in case they need special handling | |
t_results = [] | |
for fac in Mul.make_args(factored): | |
if fac.has(symbol): | |
t_results.append(solver(fac, symbol)) | |
t_result = Union(*t_results) | |
result += t_result | |
else: | |
result += solver(equation, symbol) | |
elif rhs_s is not S.EmptySet: | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
if isinstance(result, ConditionSet): | |
if isinstance(f, Expr): | |
num, den = f.as_numer_denom() | |
if den.has(symbol): | |
_result = _solveset(num, symbol, domain) | |
if not isinstance(_result, ConditionSet): | |
singularities = _solveset(den, symbol, domain) | |
result = _result - singularities | |
if _check: | |
if isinstance(result, ConditionSet): | |
# it wasn't solved or has enumerated all conditions | |
# -- leave it alone | |
return result | |
# whittle away all but the symbol-containing core | |
# to use this for testing | |
if isinstance(orig_f, Expr): | |
fx = orig_f.as_independent(symbol, as_Add=True)[1] | |
fx = fx.as_independent(symbol, as_Add=False)[1] | |
else: | |
fx = orig_f | |
if isinstance(result, FiniteSet): | |
# check the result for invalid solutions | |
result = FiniteSet(*[s for s in result | |
if isinstance(s, RootOf) | |
or domain_check(fx, symbol, s)]) | |
return result | |
def _is_modular(f, symbol): | |
""" | |
Helper function to check below mentioned types of modular equations. | |
``A - Mod(B, C) = 0`` | |
A -> This can or cannot be a function of symbol. | |
B -> This is surely a function of symbol. | |
C -> It is an integer. | |
Parameters | |
========== | |
f : Expr | |
The equation to be checked. | |
symbol : Symbol | |
The concerned variable for which the equation is to be checked. | |
Examples | |
======== | |
>>> from sympy import symbols, exp, Mod | |
>>> from sympy.solvers.solveset import _is_modular as check | |
>>> x, y = symbols('x y') | |
>>> check(Mod(x, 3) - 1, x) | |
True | |
>>> check(Mod(x, 3) - 1, y) | |
False | |
>>> check(Mod(x, 3)**2 - 5, x) | |
False | |
>>> check(Mod(x, 3)**2 - y, x) | |
False | |
>>> check(exp(Mod(x, 3)) - 1, x) | |
False | |
>>> check(Mod(3, y) - 1, y) | |
False | |
""" | |
if not f.has(Mod): | |
return False | |
# extract modterms from f. | |
modterms = list(f.atoms(Mod)) | |
return (len(modterms) == 1 and # only one Mod should be present | |
modterms[0].args[0].has(symbol) and # B-> function of symbol | |
modterms[0].args[1].is_integer and # C-> to be an integer. | |
any(isinstance(term, Mod) | |
for term in list(_term_factors(f))) # free from other funcs | |
) | |
def _invert_modular(modterm, rhs, n, symbol): | |
""" | |
Helper function to invert modular equation. | |
``Mod(a, m) - rhs = 0`` | |
Generally it is inverted as (a, ImageSet(Lambda(n, m*n + rhs), S.Integers)). | |
More simplified form will be returned if possible. | |
If it is not invertible then (modterm, rhs) is returned. | |
The following cases arise while inverting equation ``Mod(a, m) - rhs = 0``: | |
1. If a is symbol then m*n + rhs is the required solution. | |
2. If a is an instance of ``Add`` then we try to find two symbol independent | |
parts of a and the symbol independent part gets transferred to the other | |
side and again the ``_invert_modular`` is called on the symbol | |
dependent part. | |
3. If a is an instance of ``Mul`` then same as we done in ``Add`` we separate | |
out the symbol dependent and symbol independent parts and transfer the | |
symbol independent part to the rhs with the help of invert and again the | |
``_invert_modular`` is called on the symbol dependent part. | |
4. If a is an instance of ``Pow`` then two cases arise as following: | |
- If a is of type (symbol_indep)**(symbol_dep) then the remainder is | |
evaluated with the help of discrete_log function and then the least | |
period is being found out with the help of totient function. | |
period*n + remainder is the required solution in this case. | |
For reference: (https://en.wikipedia.org/wiki/Euler's_theorem) | |
- If a is of type (symbol_dep)**(symbol_indep) then we try to find all | |
primitive solutions list with the help of nthroot_mod function. | |
m*n + rem is the general solution where rem belongs to solutions list | |
from nthroot_mod function. | |
Parameters | |
========== | |
modterm, rhs : Expr | |
The modular equation to be inverted, ``modterm - rhs = 0`` | |
symbol : Symbol | |
The variable in the equation to be inverted. | |
n : Dummy | |
Dummy variable for output g_n. | |
Returns | |
======= | |
A tuple (f_x, g_n) is being returned where f_x is modular independent function | |
of symbol and g_n being set of values f_x can have. | |
Examples | |
======== | |
>>> from sympy import symbols, exp, Mod, Dummy, S | |
>>> from sympy.solvers.solveset import _invert_modular as invert_modular | |
>>> x, y = symbols('x y') | |
>>> n = Dummy('n') | |
>>> invert_modular(Mod(exp(x), 7), S(5), n, x) | |
(Mod(exp(x), 7), 5) | |
>>> invert_modular(Mod(x, 7), S(5), n, x) | |
(x, ImageSet(Lambda(_n, 7*_n + 5), Integers)) | |
>>> invert_modular(Mod(3*x + 8, 7), S(5), n, x) | |
(x, ImageSet(Lambda(_n, 7*_n + 6), Integers)) | |
>>> invert_modular(Mod(x**4, 7), S(5), n, x) | |
(x, EmptySet) | |
>>> invert_modular(Mod(2**(x**2 + x + 1), 7), S(2), n, x) | |
(x**2 + x + 1, ImageSet(Lambda(_n, 3*_n + 1), Naturals0)) | |
""" | |
a, m = modterm.args | |
if rhs.is_integer is False: | |
return symbol, S.EmptySet | |
if rhs.is_real is False or any(term.is_real is False | |
for term in list(_term_factors(a))): | |
# Check for complex arguments | |
return modterm, rhs | |
if abs(rhs) >= abs(m): | |
# if rhs has value greater than value of m. | |
return symbol, S.EmptySet | |
if a == symbol: | |
return symbol, ImageSet(Lambda(n, m*n + rhs), S.Integers) | |
if a.is_Add: | |
# g + h = a | |
g, h = a.as_independent(symbol) | |
if g is not S.Zero: | |
x_indep_term = rhs - Mod(g, m) | |
return _invert_modular(Mod(h, m), Mod(x_indep_term, m), n, symbol) | |
if a.is_Mul: | |
# g*h = a | |
g, h = a.as_independent(symbol) | |
if g is not S.One: | |
x_indep_term = rhs*invert(g, m) | |
return _invert_modular(Mod(h, m), Mod(x_indep_term, m), n, symbol) | |
if a.is_Pow: | |
# base**expo = a | |
base, expo = a.args | |
if expo.has(symbol) and not base.has(symbol): | |
# remainder -> solution independent of n of equation. | |
# m, rhs are made coprime by dividing number_gcd(m, rhs) | |
if not m.is_Integer and rhs.is_Integer and a.base.is_Integer: | |
return modterm, rhs | |
mdiv = m.p // number_gcd(m.p, rhs.p) | |
try: | |
remainder = discrete_log(mdiv, rhs.p, a.base.p) | |
except ValueError: # log does not exist | |
return modterm, rhs | |
# period -> coefficient of n in the solution and also referred as | |
# the least period of expo in which it is repeats itself. | |
# (a**(totient(m)) - 1) divides m. Here is link of theorem: | |
# (https://en.wikipedia.org/wiki/Euler's_theorem) | |
period = totient(m) | |
for p in divisors(period): | |
# there might a lesser period exist than totient(m). | |
if pow(a.base, p, m / number_gcd(m.p, a.base.p)) == 1: | |
period = p | |
break | |
# recursion is not applied here since _invert_modular is currently | |
# not smart enough to handle infinite rhs as here expo has infinite | |
# rhs = ImageSet(Lambda(n, period*n + remainder), S.Naturals0). | |
return expo, ImageSet(Lambda(n, period*n + remainder), S.Naturals0) | |
elif base.has(symbol) and not expo.has(symbol): | |
try: | |
remainder_list = nthroot_mod(rhs, expo, m, all_roots=True) | |
if remainder_list == []: | |
return symbol, S.EmptySet | |
except (ValueError, NotImplementedError): | |
return modterm, rhs | |
g_n = S.EmptySet | |
for rem in remainder_list: | |
g_n += ImageSet(Lambda(n, m*n + rem), S.Integers) | |
return base, g_n | |
return modterm, rhs | |
def _solve_modular(f, symbol, domain): | |
r""" | |
Helper function for solving modular equations of type ``A - Mod(B, C) = 0``, | |
where A can or cannot be a function of symbol, B is surely a function of | |
symbol and C is an integer. | |
Currently ``_solve_modular`` is only able to solve cases | |
where A is not a function of symbol. | |
Parameters | |
========== | |
f : Expr | |
The modular equation to be solved, ``f = 0`` | |
symbol : Symbol | |
The variable in the equation to be solved. | |
domain : Set | |
A set over which the equation is solved. It has to be a subset of | |
Integers. | |
Returns | |
======= | |
A set of integer solutions satisfying the given modular equation. | |
A ``ConditionSet`` if the equation is unsolvable. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import _solve_modular as solve_modulo | |
>>> from sympy import S, Symbol, sin, Intersection, Interval, Mod | |
>>> x = Symbol('x') | |
>>> solve_modulo(Mod(5*x - 8, 7) - 3, x, S.Integers) | |
ImageSet(Lambda(_n, 7*_n + 5), Integers) | |
>>> solve_modulo(Mod(5*x - 8, 7) - 3, x, S.Reals) # domain should be subset of integers. | |
ConditionSet(x, Eq(Mod(5*x + 6, 7) - 3, 0), Reals) | |
>>> solve_modulo(-7 + Mod(x, 5), x, S.Integers) | |
EmptySet | |
>>> solve_modulo(Mod(12**x, 21) - 18, x, S.Integers) | |
ImageSet(Lambda(_n, 6*_n + 2), Naturals0) | |
>>> solve_modulo(Mod(sin(x), 7) - 3, x, S.Integers) # not solvable | |
ConditionSet(x, Eq(Mod(sin(x), 7) - 3, 0), Integers) | |
>>> solve_modulo(3 - Mod(x, 5), x, Intersection(S.Integers, Interval(0, 100))) | |
Intersection(ImageSet(Lambda(_n, 5*_n + 3), Integers), Range(0, 101, 1)) | |
""" | |
# extract modterm and g_y from f | |
unsolved_result = ConditionSet(symbol, Eq(f, 0), domain) | |
modterm = list(f.atoms(Mod))[0] | |
rhs = -S.One*(f.subs(modterm, S.Zero)) | |
if f.as_coefficients_dict()[modterm].is_negative: | |
# checks if coefficient of modterm is negative in main equation. | |
rhs *= -S.One | |
if not domain.is_subset(S.Integers): | |
return unsolved_result | |
if rhs.has(symbol): | |
# TODO Case: A-> function of symbol, can be extended here | |
# in future. | |
return unsolved_result | |
n = Dummy('n', integer=True) | |
f_x, g_n = _invert_modular(modterm, rhs, n, symbol) | |
if f_x == modterm and g_n == rhs: | |
return unsolved_result | |
if f_x == symbol: | |
if domain is not S.Integers: | |
return domain.intersect(g_n) | |
return g_n | |
if isinstance(g_n, ImageSet): | |
lamda_expr = g_n.lamda.expr | |
lamda_vars = g_n.lamda.variables | |
base_sets = g_n.base_sets | |
sol_set = _solveset(f_x - lamda_expr, symbol, S.Integers) | |
if isinstance(sol_set, FiniteSet): | |
tmp_sol = S.EmptySet | |
for sol in sol_set: | |
tmp_sol += ImageSet(Lambda(lamda_vars, sol), *base_sets) | |
sol_set = tmp_sol | |
else: | |
sol_set = ImageSet(Lambda(lamda_vars, sol_set), *base_sets) | |
return domain.intersect(sol_set) | |
return unsolved_result | |
def _term_factors(f): | |
""" | |
Iterator to get the factors of all terms present | |
in the given equation. | |
Parameters | |
========== | |
f : Expr | |
Equation that needs to be addressed | |
Returns | |
======= | |
Factors of all terms present in the equation. | |
Examples | |
======== | |
>>> from sympy import symbols | |
>>> from sympy.solvers.solveset import _term_factors | |
>>> x = symbols('x') | |
>>> list(_term_factors(-2 - x**2 + x*(x + 1))) | |
[-2, -1, x**2, x, x + 1] | |
""" | |
for add_arg in Add.make_args(f): | |
yield from Mul.make_args(add_arg) | |
def _solve_exponential(lhs, rhs, symbol, domain): | |
r""" | |
Helper function for solving (supported) exponential equations. | |
Exponential equations are the sum of (currently) at most | |
two terms with one or both of them having a power with a | |
symbol-dependent exponent. | |
For example | |
.. math:: 5^{2x + 3} - 5^{3x - 1} | |
.. math:: 4^{5 - 9x} - e^{2 - x} | |
Parameters | |
========== | |
lhs, rhs : Expr | |
The exponential equation to be solved, `lhs = rhs` | |
symbol : Symbol | |
The variable in which the equation is solved | |
domain : Set | |
A set over which the equation is solved. | |
Returns | |
======= | |
A set of solutions satisfying the given equation. | |
A ``ConditionSet`` if the equation is unsolvable or | |
if the assumptions are not properly defined, in that case | |
a different style of ``ConditionSet`` is returned having the | |
solution(s) of the equation with the desired assumptions. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import _solve_exponential as solve_expo | |
>>> from sympy import symbols, S | |
>>> x = symbols('x', real=True) | |
>>> a, b = symbols('a b') | |
>>> solve_expo(2**x + 3**x - 5**x, 0, x, S.Reals) # not solvable | |
ConditionSet(x, Eq(2**x + 3**x - 5**x, 0), Reals) | |
>>> solve_expo(a**x - b**x, 0, x, S.Reals) # solvable but incorrect assumptions | |
ConditionSet(x, (a > 0) & (b > 0), {0}) | |
>>> solve_expo(3**(2*x) - 2**(x + 3), 0, x, S.Reals) | |
{-3*log(2)/(-2*log(3) + log(2))} | |
>>> solve_expo(2**x - 4**x, 0, x, S.Reals) | |
{0} | |
* Proof of correctness of the method | |
The logarithm function is the inverse of the exponential function. | |
The defining relation between exponentiation and logarithm is: | |
.. math:: {\log_b x} = y \enspace if \enspace b^y = x | |
Therefore if we are given an equation with exponent terms, we can | |
convert every term to its corresponding logarithmic form. This is | |
achieved by taking logarithms and expanding the equation using | |
logarithmic identities so that it can easily be handled by ``solveset``. | |
For example: | |
.. math:: 3^{2x} = 2^{x + 3} | |
Taking log both sides will reduce the equation to | |
.. math:: (2x)\log(3) = (x + 3)\log(2) | |
This form can be easily handed by ``solveset``. | |
""" | |
unsolved_result = ConditionSet(symbol, Eq(lhs - rhs, 0), domain) | |
newlhs = powdenest(lhs) | |
if lhs != newlhs: | |
# it may also be advantageous to factor the new expr | |
neweq = factor(newlhs - rhs) | |
if neweq != (lhs - rhs): | |
return _solveset(neweq, symbol, domain) # try again with _solveset | |
if not (isinstance(lhs, Add) and len(lhs.args) == 2): | |
# solving for the sum of more than two powers is possible | |
# but not yet implemented | |
return unsolved_result | |
if rhs != 0: | |
return unsolved_result | |
a, b = list(ordered(lhs.args)) | |
a_term = a.as_independent(symbol)[1] | |
b_term = b.as_independent(symbol)[1] | |
a_base, a_exp = a_term.as_base_exp() | |
b_base, b_exp = b_term.as_base_exp() | |
if domain.is_subset(S.Reals): | |
conditions = And( | |
a_base > 0, | |
b_base > 0, | |
Eq(im(a_exp), 0), | |
Eq(im(b_exp), 0)) | |
else: | |
conditions = And( | |
Ne(a_base, 0), | |
Ne(b_base, 0)) | |
L, R = (expand_log(log(i), force=True) for i in (a, -b)) | |
solutions = _solveset(L - R, symbol, domain) | |
return ConditionSet(symbol, conditions, solutions) | |
def _is_exponential(f, symbol): | |
r""" | |
Return ``True`` if one or more terms contain ``symbol`` only in | |
exponents, else ``False``. | |
Parameters | |
========== | |
f : Expr | |
The equation to be checked | |
symbol : Symbol | |
The variable in which the equation is checked | |
Examples | |
======== | |
>>> from sympy import symbols, cos, exp | |
>>> from sympy.solvers.solveset import _is_exponential as check | |
>>> x, y = symbols('x y') | |
>>> check(y, y) | |
False | |
>>> check(x**y - 1, y) | |
True | |
>>> check(x**y*2**y - 1, y) | |
True | |
>>> check(exp(x + 3) + 3**x, x) | |
True | |
>>> check(cos(2**x), x) | |
False | |
* Philosophy behind the helper | |
The function extracts each term of the equation and checks if it is | |
of exponential form w.r.t ``symbol``. | |
""" | |
rv = False | |
for expr_arg in _term_factors(f): | |
if symbol not in expr_arg.free_symbols: | |
continue | |
if (isinstance(expr_arg, Pow) and | |
symbol not in expr_arg.base.free_symbols or | |
isinstance(expr_arg, exp)): | |
rv = True # symbol in exponent | |
else: | |
return False # dependent on symbol in non-exponential way | |
return rv | |
def _solve_logarithm(lhs, rhs, symbol, domain): | |
r""" | |
Helper to solve logarithmic equations which are reducible | |
to a single instance of `\log`. | |
Logarithmic equations are (currently) the equations that contains | |
`\log` terms which can be reduced to a single `\log` term or | |
a constant using various logarithmic identities. | |
For example: | |
.. math:: \log(x) + \log(x - 4) | |
can be reduced to: | |
.. math:: \log(x(x - 4)) | |
Parameters | |
========== | |
lhs, rhs : Expr | |
The logarithmic equation to be solved, `lhs = rhs` | |
symbol : Symbol | |
The variable in which the equation is solved | |
domain : Set | |
A set over which the equation is solved. | |
Returns | |
======= | |
A set of solutions satisfying the given equation. | |
A ``ConditionSet`` if the equation is unsolvable. | |
Examples | |
======== | |
>>> from sympy import symbols, log, S | |
>>> from sympy.solvers.solveset import _solve_logarithm as solve_log | |
>>> x = symbols('x') | |
>>> f = log(x - 3) + log(x + 3) | |
>>> solve_log(f, 0, x, S.Reals) | |
{-sqrt(10), sqrt(10)} | |
* Proof of correctness | |
A logarithm is another way to write exponent and is defined by | |
.. math:: {\log_b x} = y \enspace if \enspace b^y = x | |
When one side of the equation contains a single logarithm, the | |
equation can be solved by rewriting the equation as an equivalent | |
exponential equation as defined above. But if one side contains | |
more than one logarithm, we need to use the properties of logarithm | |
to condense it into a single logarithm. | |
Take for example | |
.. math:: \log(2x) - 15 = 0 | |
contains single logarithm, therefore we can directly rewrite it to | |
exponential form as | |
.. math:: x = \frac{e^{15}}{2} | |
But if the equation has more than one logarithm as | |
.. math:: \log(x - 3) + \log(x + 3) = 0 | |
we use logarithmic identities to convert it into a reduced form | |
Using, | |
.. math:: \log(a) + \log(b) = \log(ab) | |
the equation becomes, | |
.. math:: \log((x - 3)(x + 3)) | |
This equation contains one logarithm and can be solved by rewriting | |
to exponents. | |
""" | |
new_lhs = logcombine(lhs, force=True) | |
new_f = new_lhs - rhs | |
return _solveset(new_f, symbol, domain) | |
def _is_logarithmic(f, symbol): | |
r""" | |
Return ``True`` if the equation is in the form | |
`a\log(f(x)) + b\log(g(x)) + ... + c` else ``False``. | |
Parameters | |
========== | |
f : Expr | |
The equation to be checked | |
symbol : Symbol | |
The variable in which the equation is checked | |
Returns | |
======= | |
``True`` if the equation is logarithmic otherwise ``False``. | |
Examples | |
======== | |
>>> from sympy import symbols, tan, log | |
>>> from sympy.solvers.solveset import _is_logarithmic as check | |
>>> x, y = symbols('x y') | |
>>> check(log(x + 2) - log(x + 3), x) | |
True | |
>>> check(tan(log(2*x)), x) | |
False | |
>>> check(x*log(x), x) | |
False | |
>>> check(x + log(x), x) | |
False | |
>>> check(y + log(x), x) | |
True | |
* Philosophy behind the helper | |
The function extracts each term and checks whether it is | |
logarithmic w.r.t ``symbol``. | |
""" | |
rv = False | |
for term in Add.make_args(f): | |
saw_log = False | |
for term_arg in Mul.make_args(term): | |
if symbol not in term_arg.free_symbols: | |
continue | |
if isinstance(term_arg, log): | |
if saw_log: | |
return False # more than one log in term | |
saw_log = True | |
else: | |
return False # dependent on symbol in non-log way | |
if saw_log: | |
rv = True | |
return rv | |
def _is_lambert(f, symbol): | |
r""" | |
If this returns ``False`` then the Lambert solver (``_solve_lambert``) will not be called. | |
Explanation | |
=========== | |
Quick check for cases that the Lambert solver might be able to handle. | |
1. Equations containing more than two operands and `symbol`s involving any of | |
`Pow`, `exp`, `HyperbolicFunction`,`TrigonometricFunction`, `log` terms. | |
2. In `Pow`, `exp` the exponent should have `symbol` whereas for | |
`HyperbolicFunction`,`TrigonometricFunction`, `log` should contain `symbol`. | |
3. For `HyperbolicFunction`,`TrigonometricFunction` the number of trigonometric functions in | |
equation should be less than number of symbols. (since `A*cos(x) + B*sin(x) - c` | |
is not the Lambert type). | |
Some forms of lambert equations are: | |
1. X**X = C | |
2. X*(B*log(X) + D)**A = C | |
3. A*log(B*X + A) + d*X = C | |
4. (B*X + A)*exp(d*X + g) = C | |
5. g*exp(B*X + h) - B*X = C | |
6. A*D**(E*X + g) - B*X = C | |
7. A*cos(X) + B*sin(X) - D*X = C | |
8. A*cosh(X) + B*sinh(X) - D*X = C | |
Where X is any variable, | |
A, B, C, D, E are any constants, | |
g, h are linear functions or log terms. | |
Parameters | |
========== | |
f : Expr | |
The equation to be checked | |
symbol : Symbol | |
The variable in which the equation is checked | |
Returns | |
======= | |
If this returns ``False`` then the Lambert solver (``_solve_lambert``) will not be called. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import _is_lambert | |
>>> from sympy import symbols, cosh, sinh, log | |
>>> x = symbols('x') | |
>>> _is_lambert(3*log(x) - x*log(3), x) | |
True | |
>>> _is_lambert(log(log(x - 3)) + log(x-3), x) | |
True | |
>>> _is_lambert(cosh(x) - sinh(x), x) | |
False | |
>>> _is_lambert((x**2 - 2*x + 1).subs(x, (log(x) + 3*x)**2 - 1), x) | |
True | |
See Also | |
======== | |
_solve_lambert | |
""" | |
term_factors = list(_term_factors(f.expand())) | |
# total number of symbols in equation | |
no_of_symbols = len([arg for arg in term_factors if arg.has(symbol)]) | |
# total number of trigonometric terms in equation | |
no_of_trig = len([arg for arg in term_factors \ | |
if arg.has(HyperbolicFunction, TrigonometricFunction)]) | |
if f.is_Add and no_of_symbols >= 2: | |
# `log`, `HyperbolicFunction`, `TrigonometricFunction` should have symbols | |
# and no_of_trig < no_of_symbols | |
lambert_funcs = (log, HyperbolicFunction, TrigonometricFunction) | |
if any(isinstance(arg, lambert_funcs)\ | |
for arg in term_factors if arg.has(symbol)): | |
if no_of_trig < no_of_symbols: | |
return True | |
# here, `Pow`, `exp` exponent should have symbols | |
elif any(isinstance(arg, (Pow, exp)) \ | |
for arg in term_factors if (arg.as_base_exp()[1]).has(symbol)): | |
return True | |
return False | |
def _transolve(f, symbol, domain): | |
r""" | |
Function to solve transcendental equations. It is a helper to | |
``solveset`` and should be used internally. ``_transolve`` | |
currently supports the following class of equations: | |
- Exponential equations | |
- Logarithmic equations | |
Parameters | |
========== | |
f : Any transcendental equation that needs to be solved. | |
This needs to be an expression, which is assumed | |
to be equal to ``0``. | |
symbol : The variable for which the equation is solved. | |
This needs to be of class ``Symbol``. | |
domain : A set over which the equation is solved. | |
This needs to be of class ``Set``. | |
Returns | |
======= | |
Set | |
A set of values for ``symbol`` for which ``f`` is equal to | |
zero. An ``EmptySet`` is returned if ``f`` does not have solutions | |
in respective domain. A ``ConditionSet`` is returned as unsolved | |
object if algorithms to evaluate complete solution are not | |
yet implemented. | |
How to use ``_transolve`` | |
========================= | |
``_transolve`` should not be used as an independent function, because | |
it assumes that the equation (``f``) and the ``symbol`` comes from | |
``solveset`` and might have undergone a few modification(s). | |
To use ``_transolve`` as an independent function the equation (``f``) | |
and the ``symbol`` should be passed as they would have been by | |
``solveset``. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import _transolve as transolve | |
>>> from sympy.solvers.solvers import _tsolve as tsolve | |
>>> from sympy import symbols, S, pprint | |
>>> x = symbols('x', real=True) # assumption added | |
>>> transolve(5**(x - 3) - 3**(2*x + 1), x, S.Reals) | |
{-(log(3) + 3*log(5))/(-log(5) + 2*log(3))} | |
How ``_transolve`` works | |
======================== | |
``_transolve`` uses two types of helper functions to solve equations | |
of a particular class: | |
Identifying helpers: To determine whether a given equation | |
belongs to a certain class of equation or not. Returns either | |
``True`` or ``False``. | |
Solving helpers: Once an equation is identified, a corresponding | |
helper either solves the equation or returns a form of the equation | |
that ``solveset`` might better be able to handle. | |
* Philosophy behind the module | |
The purpose of ``_transolve`` is to take equations which are not | |
already polynomial in their generator(s) and to either recast them | |
as such through a valid transformation or to solve them outright. | |
A pair of helper functions for each class of supported | |
transcendental functions are employed for this purpose. One | |
identifies the transcendental form of an equation and the other | |
either solves it or recasts it into a tractable form that can be | |
solved by ``solveset``. | |
For example, an equation in the form `ab^{f(x)} - cd^{g(x)} = 0` | |
can be transformed to | |
`\log(a) + f(x)\log(b) - \log(c) - g(x)\log(d) = 0` | |
(under certain assumptions) and this can be solved with ``solveset`` | |
if `f(x)` and `g(x)` are in polynomial form. | |
How ``_transolve`` is better than ``_tsolve`` | |
============================================= | |
1) Better output | |
``_transolve`` provides expressions in a more simplified form. | |
Consider a simple exponential equation | |
>>> f = 3**(2*x) - 2**(x + 3) | |
>>> pprint(transolve(f, x, S.Reals), use_unicode=False) | |
-3*log(2) | |
{------------------} | |
-2*log(3) + log(2) | |
>>> pprint(tsolve(f, x), use_unicode=False) | |
/ 3 \ | |
| --------| | |
| log(2/9)| | |
[-log\2 /] | |
2) Extensible | |
The API of ``_transolve`` is designed such that it is easily | |
extensible, i.e. the code that solves a given class of | |
equations is encapsulated in a helper and not mixed in with | |
the code of ``_transolve`` itself. | |
3) Modular | |
``_transolve`` is designed to be modular i.e, for every class of | |
equation a separate helper for identification and solving is | |
implemented. This makes it easy to change or modify any of the | |
method implemented directly in the helpers without interfering | |
with the actual structure of the API. | |
4) Faster Computation | |
Solving equation via ``_transolve`` is much faster as compared to | |
``_tsolve``. In ``solve``, attempts are made computing every possibility | |
to get the solutions. This series of attempts makes solving a bit | |
slow. In ``_transolve``, computation begins only after a particular | |
type of equation is identified. | |
How to add new class of equations | |
================================= | |
Adding a new class of equation solver is a three-step procedure: | |
- Identify the type of the equations | |
Determine the type of the class of equations to which they belong: | |
it could be of ``Add``, ``Pow``, etc. types. Separate internal functions | |
are used for each type. Write identification and solving helpers | |
and use them from within the routine for the given type of equation | |
(after adding it, if necessary). Something like: | |
.. code-block:: python | |
def add_type(lhs, rhs, x): | |
.... | |
if _is_exponential(lhs, x): | |
new_eq = _solve_exponential(lhs, rhs, x) | |
.... | |
rhs, lhs = eq.as_independent(x) | |
if lhs.is_Add: | |
result = add_type(lhs, rhs, x) | |
- Define the identification helper. | |
- Define the solving helper. | |
Apart from this, a few other things needs to be taken care while | |
adding an equation solver: | |
- Naming conventions: | |
Name of the identification helper should be as | |
``_is_class`` where class will be the name or abbreviation | |
of the class of equation. The solving helper will be named as | |
``_solve_class``. | |
For example: for exponential equations it becomes | |
``_is_exponential`` and ``_solve_expo``. | |
- The identifying helpers should take two input parameters, | |
the equation to be checked and the variable for which a solution | |
is being sought, while solving helpers would require an additional | |
domain parameter. | |
- Be sure to consider corner cases. | |
- Add tests for each helper. | |
- Add a docstring to your helper that describes the method | |
implemented. | |
The documentation of the helpers should identify: | |
- the purpose of the helper, | |
- the method used to identify and solve the equation, | |
- a proof of correctness | |
- the return values of the helpers | |
""" | |
def add_type(lhs, rhs, symbol, domain): | |
""" | |
Helper for ``_transolve`` to handle equations of | |
``Add`` type, i.e. equations taking the form as | |
``a*f(x) + b*g(x) + .... = c``. | |
For example: 4**x + 8**x = 0 | |
""" | |
result = ConditionSet(symbol, Eq(lhs - rhs, 0), domain) | |
# check if it is exponential type equation | |
if _is_exponential(lhs, symbol): | |
result = _solve_exponential(lhs, rhs, symbol, domain) | |
# check if it is logarithmic type equation | |
elif _is_logarithmic(lhs, symbol): | |
result = _solve_logarithm(lhs, rhs, symbol, domain) | |
return result | |
result = ConditionSet(symbol, Eq(f, 0), domain) | |
# invert_complex handles the call to the desired inverter based | |
# on the domain specified. | |
lhs, rhs_s = invert_complex(f, 0, symbol, domain) | |
if isinstance(rhs_s, FiniteSet): | |
assert (len(rhs_s.args)) == 1 | |
rhs = rhs_s.args[0] | |
if lhs.is_Add: | |
result = add_type(lhs, rhs, symbol, domain) | |
else: | |
result = rhs_s | |
return result | |
def solveset(f, symbol=None, domain=S.Complexes): | |
r"""Solves a given inequality or equation with set as output | |
Parameters | |
========== | |
f : Expr or a relational. | |
The target equation or inequality | |
symbol : Symbol | |
The variable for which the equation is solved | |
domain : Set | |
The domain over which the equation is solved | |
Returns | |
======= | |
Set | |
A set of values for `symbol` for which `f` is True or is equal to | |
zero. An :class:`~.EmptySet` is returned if `f` is False or nonzero. | |
A :class:`~.ConditionSet` is returned as unsolved object if algorithms | |
to evaluate complete solution are not yet implemented. | |
``solveset`` claims to be complete in the solution set that it returns. | |
Raises | |
====== | |
NotImplementedError | |
The algorithms to solve inequalities in complex domain are | |
not yet implemented. | |
ValueError | |
The input is not valid. | |
RuntimeError | |
It is a bug, please report to the github issue tracker. | |
Notes | |
===== | |
Python interprets 0 and 1 as False and True, respectively, but | |
in this function they refer to solutions of an expression. So 0 and 1 | |
return the domain and EmptySet, respectively, while True and False | |
return the opposite (as they are assumed to be solutions of relational | |
expressions). | |
See Also | |
======== | |
solveset_real: solver for real domain | |
solveset_complex: solver for complex domain | |
Examples | |
======== | |
>>> from sympy import exp, sin, Symbol, pprint, S, Eq | |
>>> from sympy.solvers.solveset import solveset, solveset_real | |
* The default domain is complex. Not specifying a domain will lead | |
to the solving of the equation in the complex domain (and this | |
is not affected by the assumptions on the symbol): | |
>>> x = Symbol('x') | |
>>> pprint(solveset(exp(x) - 1, x), use_unicode=False) | |
{2*n*I*pi | n in Integers} | |
>>> x = Symbol('x', real=True) | |
>>> pprint(solveset(exp(x) - 1, x), use_unicode=False) | |
{2*n*I*pi | n in Integers} | |
* If you want to use ``solveset`` to solve the equation in the | |
real domain, provide a real domain. (Using ``solveset_real`` | |
does this automatically.) | |
>>> R = S.Reals | |
>>> x = Symbol('x') | |
>>> solveset(exp(x) - 1, x, R) | |
{0} | |
>>> solveset_real(exp(x) - 1, x) | |
{0} | |
The solution is unaffected by assumptions on the symbol: | |
>>> p = Symbol('p', positive=True) | |
>>> pprint(solveset(p**2 - 4)) | |
{-2, 2} | |
When a :class:`~.ConditionSet` is returned, symbols with assumptions that | |
would alter the set are replaced with more generic symbols: | |
>>> i = Symbol('i', imaginary=True) | |
>>> solveset(Eq(i**2 + i*sin(i), 1), i, domain=S.Reals) | |
ConditionSet(_R, Eq(_R**2 + _R*sin(_R) - 1, 0), Reals) | |
* Inequalities can be solved over the real domain only. Use of a complex | |
domain leads to a NotImplementedError. | |
>>> solveset(exp(x) > 1, x, R) | |
Interval.open(0, oo) | |
""" | |
f = sympify(f) | |
symbol = sympify(symbol) | |
if f is S.true: | |
return domain | |
if f is S.false: | |
return S.EmptySet | |
if not isinstance(f, (Expr, Relational, Number)): | |
raise ValueError("%s is not a valid SymPy expression" % f) | |
if not isinstance(symbol, (Expr, Relational)) and symbol is not None: | |
raise ValueError("%s is not a valid SymPy symbol" % (symbol,)) | |
if not isinstance(domain, Set): | |
raise ValueError("%s is not a valid domain" %(domain)) | |
free_symbols = f.free_symbols | |
if f.has(Piecewise): | |
f = piecewise_fold(f) | |
if symbol is None and not free_symbols: | |
b = Eq(f, 0) | |
if b is S.true: | |
return domain | |
elif b is S.false: | |
return S.EmptySet | |
else: | |
raise NotImplementedError(filldedent(''' | |
relationship between value and 0 is unknown: %s''' % b)) | |
if symbol is None: | |
if len(free_symbols) == 1: | |
symbol = free_symbols.pop() | |
elif free_symbols: | |
raise ValueError(filldedent(''' | |
The independent variable must be specified for a | |
multivariate equation.''')) | |
elif not isinstance(symbol, Symbol): | |
f, s, swap = recast_to_symbols([f], [symbol]) | |
# the xreplace will be needed if a ConditionSet is returned | |
return solveset(f[0], s[0], domain).xreplace(swap) | |
# solveset should ignore assumptions on symbols | |
newsym = None | |
if domain.is_subset(S.Reals): | |
if symbol._assumptions_orig != {'real': True}: | |
newsym = Dummy('R', real=True) | |
elif domain.is_subset(S.Complexes): | |
if symbol._assumptions_orig != {'complex': True}: | |
newsym = Dummy('C', complex=True) | |
if newsym is not None: | |
rv = solveset(f.xreplace({symbol: newsym}), newsym, domain) | |
# try to use the original symbol if possible | |
try: | |
_rv = rv.xreplace({newsym: symbol}) | |
except TypeError: | |
_rv = rv | |
if rv.dummy_eq(_rv): | |
rv = _rv | |
return rv | |
# Abs has its own handling method which avoids the | |
# rewriting property that the first piece of abs(x) | |
# is for x >= 0 and the 2nd piece for x < 0 -- solutions | |
# can look better if the 2nd condition is x <= 0. Since | |
# the solution is a set, duplication of results is not | |
# an issue, e.g. {y, -y} when y is 0 will be {0} | |
f, mask = _masked(f, Abs) | |
f = f.rewrite(Piecewise) # everything that's not an Abs | |
for d, e in mask: | |
# everything *in* an Abs | |
e = e.func(e.args[0].rewrite(Piecewise)) | |
f = f.xreplace({d: e}) | |
f = piecewise_fold(f) | |
return _solveset(f, symbol, domain, _check=True) | |
def solveset_real(f, symbol): | |
return solveset(f, symbol, S.Reals) | |
def solveset_complex(f, symbol): | |
return solveset(f, symbol, S.Complexes) | |
def _solveset_multi(eqs, syms, domains): | |
'''Basic implementation of a multivariate solveset. | |
For internal use (not ready for public consumption)''' | |
rep = {} | |
for sym, dom in zip(syms, domains): | |
if dom is S.Reals: | |
rep[sym] = Symbol(sym.name, real=True) | |
eqs = [eq.subs(rep) for eq in eqs] | |
syms = [sym.subs(rep) for sym in syms] | |
syms = tuple(syms) | |
if len(eqs) == 0: | |
return ProductSet(*domains) | |
if len(syms) == 1: | |
sym = syms[0] | |
domain = domains[0] | |
solsets = [solveset(eq, sym, domain) for eq in eqs] | |
solset = Intersection(*solsets) | |
return ImageSet(Lambda((sym,), (sym,)), solset).doit() | |
eqs = sorted(eqs, key=lambda eq: len(eq.free_symbols & set(syms))) | |
for n, eq in enumerate(eqs): | |
sols = [] | |
all_handled = True | |
for sym in syms: | |
if sym not in eq.free_symbols: | |
continue | |
sol = solveset(eq, sym, domains[syms.index(sym)]) | |
if isinstance(sol, FiniteSet): | |
i = syms.index(sym) | |
symsp = syms[:i] + syms[i+1:] | |
domainsp = domains[:i] + domains[i+1:] | |
eqsp = eqs[:n] + eqs[n+1:] | |
for s in sol: | |
eqsp_sub = [eq.subs(sym, s) for eq in eqsp] | |
sol_others = _solveset_multi(eqsp_sub, symsp, domainsp) | |
fun = Lambda((symsp,), symsp[:i] + (s,) + symsp[i:]) | |
sols.append(ImageSet(fun, sol_others).doit()) | |
else: | |
all_handled = False | |
if all_handled: | |
return Union(*sols) | |
def solvify(f, symbol, domain): | |
"""Solves an equation using solveset and returns the solution in accordance | |
with the `solve` output API. | |
Returns | |
======= | |
We classify the output based on the type of solution returned by `solveset`. | |
Solution | Output | |
---------------------------------------- | |
FiniteSet | list | |
ImageSet, | list (if `f` is periodic) | |
Union | | |
Union | list (with FiniteSet) | |
EmptySet | empty list | |
Others | None | |
Raises | |
====== | |
NotImplementedError | |
A ConditionSet is the input. | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import solvify | |
>>> from sympy.abc import x | |
>>> from sympy import S, tan, sin, exp | |
>>> solvify(x**2 - 9, x, S.Reals) | |
[-3, 3] | |
>>> solvify(sin(x) - 1, x, S.Reals) | |
[pi/2] | |
>>> solvify(tan(x), x, S.Reals) | |
[0] | |
>>> solvify(exp(x) - 1, x, S.Complexes) | |
>>> solvify(exp(x) - 1, x, S.Reals) | |
[0] | |
""" | |
solution_set = solveset(f, symbol, domain) | |
result = None | |
if solution_set is S.EmptySet: | |
result = [] | |
elif isinstance(solution_set, ConditionSet): | |
raise NotImplementedError('solveset is unable to solve this equation.') | |
elif isinstance(solution_set, FiniteSet): | |
result = list(solution_set) | |
else: | |
period = periodicity(f, symbol) | |
if period is not None: | |
solutions = S.EmptySet | |
iter_solutions = () | |
if isinstance(solution_set, ImageSet): | |
iter_solutions = (solution_set,) | |
elif isinstance(solution_set, Union): | |
if all(isinstance(i, ImageSet) for i in solution_set.args): | |
iter_solutions = solution_set.args | |
for solution in iter_solutions: | |
solutions += solution.intersect(Interval(0, period, False, True)) | |
if isinstance(solutions, FiniteSet): | |
result = list(solutions) | |
else: | |
solution = solution_set.intersect(domain) | |
if isinstance(solution, Union): | |
# concerned about only FiniteSet with Union but not about ImageSet | |
# if required could be extend | |
if any(isinstance(i, FiniteSet) for i in solution.args): | |
result = [sol for soln in solution.args \ | |
for sol in soln.args if isinstance(soln,FiniteSet)] | |
else: | |
return None | |
elif isinstance(solution, FiniteSet): | |
result += solution | |
return result | |
############################################################################### | |
################################ LINSOLVE ##################################### | |
############################################################################### | |
def linear_coeffs(eq, *syms, dict=False): | |
"""Return a list whose elements are the coefficients of the | |
corresponding symbols in the sum of terms in ``eq``. | |
The additive constant is returned as the last element of the | |
list. | |
Raises | |
====== | |
NonlinearError | |
The equation contains a nonlinear term | |
ValueError | |
duplicate or unordered symbols are passed | |
Parameters | |
========== | |
dict - (default False) when True, return coefficients as a | |
dictionary with coefficients keyed to syms that were present; | |
key 1 gives the constant term | |
Examples | |
======== | |
>>> from sympy.solvers.solveset import linear_coeffs | |
>>> from sympy.abc import x, y, z | |
>>> linear_coeffs(3*x + 2*y - 1, x, y) | |
[3, 2, -1] | |
It is not necessary to expand the expression: | |
>>> linear_coeffs(x + y*(z*(x*3 + 2) + 3), x) | |
[3*y*z + 1, y*(2*z + 3)] | |
When nonlinear is detected, an error will be raised: | |
* even if they would cancel after expansion (so the | |
situation does not pass silently past the caller's | |
attention) | |
>>> eq = 1/x*(x - 1) + 1/x | |
>>> linear_coeffs(eq.expand(), x) | |
[0, 1] | |
>>> linear_coeffs(eq, x) | |
Traceback (most recent call last): | |
... | |
NonlinearError: | |
nonlinear in given generators | |
* when there are cross terms | |
>>> linear_coeffs(x*(y + 1), x, y) | |
Traceback (most recent call last): | |
... | |
NonlinearError: | |
symbol-dependent cross-terms encountered | |
* when there are terms that contain an expression | |
dependent on the symbols that is not linear | |
>>> linear_coeffs(x**2, x) | |
Traceback (most recent call last): | |
... | |
NonlinearError: | |
nonlinear in given generators | |
""" | |
eq = _sympify(eq) | |
if len(syms) == 1 and iterable(syms[0]) and not isinstance(syms[0], Basic): | |
raise ValueError('expecting unpacked symbols, *syms') | |
symset = set(syms) | |
if len(symset) != len(syms): | |
raise ValueError('duplicate symbols given') | |
try: | |
d, c = _linear_eq_to_dict([eq], symset) | |
d = d[0] | |
c = c[0] | |
except PolyNonlinearError as err: | |
raise NonlinearError(str(err)) | |
if dict: | |
if c: | |
d[S.One] = c | |
return d | |
rv = [S.Zero]*(len(syms) + 1) | |
rv[-1] = c | |
for i, k in enumerate(syms): | |
if k not in d: | |
continue | |
rv[i] = d[k] | |
return rv | |
def linear_eq_to_matrix(equations, *symbols): | |
r""" | |
Converts a given System of Equations into Matrix form. | |
Here `equations` must be a linear system of equations in | |
`symbols`. Element ``M[i, j]`` corresponds to the coefficient | |
of the jth symbol in the ith equation. | |
The Matrix form corresponds to the augmented matrix form. | |
For example: | |
.. math:: 4x + 2y + 3z = 1 | |
.. math:: 3x + y + z = -6 | |
.. math:: 2x + 4y + 9z = 2 | |
This system will return $A$ and $b$ as: | |
$$ A = \left[\begin{array}{ccc} | |
4 & 2 & 3 \\ | |
3 & 1 & 1 \\ | |
2 & 4 & 9 | |
\end{array}\right] \ \ b = \left[\begin{array}{c} | |
1 \\ -6 \\ 2 | |
\end{array}\right] $$ | |
The only simplification performed is to convert | |
``Eq(a, b)`` $\Rightarrow a - b$. | |
Raises | |
====== | |
NonlinearError | |
The equations contain a nonlinear term. | |
ValueError | |
The symbols are not given or are not unique. | |
Examples | |
======== | |
>>> from sympy import linear_eq_to_matrix, symbols | |
>>> c, x, y, z = symbols('c, x, y, z') | |
The coefficients (numerical or symbolic) of the symbols will | |
be returned as matrices: | |
>>> eqns = [c*x + z - 1 - c, y + z, x - y] | |
>>> A, b = linear_eq_to_matrix(eqns, [x, y, z]) | |
>>> A | |
Matrix([ | |
[c, 0, 1], | |
[0, 1, 1], | |
[1, -1, 0]]) | |
>>> b | |
Matrix([ | |
[c + 1], | |
[ 0], | |
[ 0]]) | |
This routine does not simplify expressions and will raise an error | |
if nonlinearity is encountered: | |
>>> eqns = [ | |
... (x**2 - 3*x)/(x - 3) - 3, | |
... y**2 - 3*y - y*(y - 4) + x - 4] | |
>>> linear_eq_to_matrix(eqns, [x, y]) | |
Traceback (most recent call last): | |
... | |
NonlinearError: | |
symbol-dependent term can be ignored using `strict=False` | |
Simplifying these equations will discard the removable singularity | |
in the first and reveal the linear structure of the second: | |
>>> [e.simplify() for e in eqns] | |
[x - 3, x + y - 4] | |
Any such simplification needed to eliminate nonlinear terms must | |
be done *before* calling this routine. | |
""" | |
if not symbols: | |
raise ValueError(filldedent(''' | |
Symbols must be given, for which coefficients | |
are to be found. | |
''')) | |
# Check if 'symbols' is a set and raise an error if it is | |
if isinstance(symbols[0], set): | |
raise TypeError( | |
"Unordered 'set' type is not supported as input for symbols.") | |
if hasattr(symbols[0], '__iter__'): | |
symbols = symbols[0] | |
if has_dups(symbols): | |
raise ValueError('Symbols must be unique') | |
equations = sympify(equations) | |
if isinstance(equations, MatrixBase): | |
equations = list(equations) | |
elif isinstance(equations, (Expr, Eq)): | |
equations = [equations] | |
elif not is_sequence(equations): | |
raise ValueError(filldedent(''' | |
Equation(s) must be given as a sequence, Expr, | |
Eq or Matrix. | |
''')) | |
# construct the dictionaries | |
try: | |
eq, c = _linear_eq_to_dict(equations, symbols) | |
except PolyNonlinearError as err: | |
raise NonlinearError(str(err)) | |
# prepare output matrices | |
n, m = shape = len(eq), len(symbols) | |
ix = dict(zip(symbols, range(m))) | |
A = zeros(*shape) | |
for row, d in enumerate(eq): | |
for k in d: | |
col = ix[k] | |
A[row, col] = d[k] | |
b = Matrix(n, 1, [-i for i in c]) | |
return A, b | |
def linsolve(system, *symbols): | |
r""" | |
Solve system of $N$ linear equations with $M$ variables; both | |
underdetermined and overdetermined systems are supported. | |
The possible number of solutions is zero, one or infinite. | |
Zero solutions throws a ValueError, whereas infinite | |
solutions are represented parametrically in terms of the given | |
symbols. For unique solution a :class:`~.FiniteSet` of ordered tuples | |
is returned. | |
All standard input formats are supported: | |
For the given set of equations, the respective input types | |
are given below: | |
.. math:: 3x + 2y - z = 1 | |
.. math:: 2x - 2y + 4z = -2 | |
.. math:: 2x - y + 2z = 0 | |
* Augmented matrix form, ``system`` given below: | |
$$ \text{system} = \left[{array}{cccc} | |
3 & 2 & -1 & 1\\ | |
2 & -2 & 4 & -2\\ | |
2 & -1 & 2 & 0 | |
\end{array}\right] $$ | |
:: | |
system = Matrix([[3, 2, -1, 1], [2, -2, 4, -2], [2, -1, 2, 0]]) | |
* List of equations form | |
:: | |
system = [3x + 2y - z - 1, 2x - 2y + 4z + 2, 2x - y + 2z] | |
* Input $A$ and $b$ in matrix form (from $Ax = b$) are given as: | |
$$ A = \left[\begin{array}{ccc} | |
3 & 2 & -1 \\ | |
2 & -2 & 4 \\ | |
2 & -1 & 2 | |
\end{array}\right] \ \ b = \left[\begin{array}{c} | |
1 \\ -2 \\ 0 | |
\end{array}\right] $$ | |
:: | |
A = Matrix([[3, 2, -1], [2, -2, 4], [2, -1, 2]]) | |
b = Matrix([[1], [-2], [0]]) | |
system = (A, b) | |
Symbols can always be passed but are actually only needed | |
when 1) a system of equations is being passed and 2) the | |
system is passed as an underdetermined matrix and one wants | |
to control the name of the free variables in the result. | |
An error is raised if no symbols are used for case 1, but if | |
no symbols are provided for case 2, internally generated symbols | |
will be provided. When providing symbols for case 2, there should | |
be at least as many symbols are there are columns in matrix A. | |
The algorithm used here is Gauss-Jordan elimination, which | |
results, after elimination, in a row echelon form matrix. | |
Returns | |
======= | |
A FiniteSet containing an ordered tuple of values for the | |
unknowns for which the `system` has a solution. (Wrapping | |
the tuple in FiniteSet is used to maintain a consistent | |
output format throughout solveset.) | |
Returns EmptySet, if the linear system is inconsistent. | |
Raises | |
====== | |
ValueError | |
The input is not valid. | |
The symbols are not given. | |
Examples | |
======== | |
>>> from sympy import Matrix, linsolve, symbols | |
>>> x, y, z = symbols("x, y, z") | |
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) | |
>>> b = Matrix([3, 6, 9]) | |
>>> A | |
Matrix([ | |
[1, 2, 3], | |
[4, 5, 6], | |
[7, 8, 10]]) | |
>>> b | |
Matrix([ | |
[3], | |
[6], | |
[9]]) | |
>>> linsolve((A, b), [x, y, z]) | |
{(-1, 2, 0)} | |
* Parametric Solution: In case the system is underdetermined, the | |
function will return a parametric solution in terms of the given | |
symbols. Those that are free will be returned unchanged. e.g. in | |
the system below, `z` is returned as the solution for variable z; | |
it can take on any value. | |
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) | |
>>> b = Matrix([3, 6, 9]) | |
>>> linsolve((A, b), x, y, z) | |
{(z - 1, 2 - 2*z, z)} | |
If no symbols are given, internally generated symbols will be used. | |
The ``tau0`` in the third position indicates (as before) that the third | |
variable -- whatever it is named -- can take on any value: | |
>>> linsolve((A, b)) | |
{(tau0 - 1, 2 - 2*tau0, tau0)} | |
* List of equations as input | |
>>> Eqns = [3*x + 2*y - z - 1, 2*x - 2*y + 4*z + 2, - x + y/2 - z] | |
>>> linsolve(Eqns, x, y, z) | |
{(1, -2, -2)} | |
* Augmented matrix as input | |
>>> aug = Matrix([[2, 1, 3, 1], [2, 6, 8, 3], [6, 8, 18, 5]]) | |
>>> aug | |
Matrix([ | |
[2, 1, 3, 1], | |
[2, 6, 8, 3], | |
[6, 8, 18, 5]]) | |
>>> linsolve(aug, x, y, z) | |
{(3/10, 2/5, 0)} | |
* Solve for symbolic coefficients | |
>>> a, b, c, d, e, f = symbols('a, b, c, d, e, f') | |
>>> eqns = [a*x + b*y - c, d*x + e*y - f] | |
>>> linsolve(eqns, x, y) | |
{((-b*f + c*e)/(a*e - b*d), (a*f - c*d)/(a*e - b*d))} | |
* A degenerate system returns solution as set of given | |
symbols. | |
>>> system = Matrix(([0, 0, 0], [0, 0, 0], [0, 0, 0])) | |
>>> linsolve(system, x, y) | |
{(x, y)} | |
* For an empty system linsolve returns empty set | |
>>> linsolve([], x) | |
EmptySet | |
* An error is raised if any nonlinearity is detected, even | |
if it could be removed with expansion | |
>>> linsolve([x*(1/x - 1)], x) | |
Traceback (most recent call last): | |
... | |
NonlinearError: nonlinear term: 1/x | |
>>> linsolve([x*(y + 1)], x, y) | |
Traceback (most recent call last): | |
... | |
NonlinearError: nonlinear cross-term: x*(y + 1) | |
>>> linsolve([x**2 - 1], x) | |
Traceback (most recent call last): | |
... | |
NonlinearError: nonlinear term: x**2 | |
""" | |
if not system: | |
return S.EmptySet | |
# If second argument is an iterable | |
if symbols and hasattr(symbols[0], '__iter__'): | |
symbols = symbols[0] | |
sym_gen = isinstance(symbols, GeneratorType) | |
dup_msg = 'duplicate symbols given' | |
b = None # if we don't get b the input was bad | |
# unpack system | |
if hasattr(system, '__iter__'): | |
# 1). (A, b) | |
if len(system) == 2 and isinstance(system[0], MatrixBase): | |
A, b = system | |
# 2). (eq1, eq2, ...) | |
if not isinstance(system[0], MatrixBase): | |
if sym_gen or not symbols: | |
raise ValueError(filldedent(''' | |
When passing a system of equations, the explicit | |
symbols for which a solution is being sought must | |
be given as a sequence, too. | |
''')) | |
if len(set(symbols)) != len(symbols): | |
raise ValueError(dup_msg) | |
# | |
# Pass to the sparse solver implemented in polys. It is important | |
# that we do not attempt to convert the equations to a matrix | |
# because that would be very inefficient for large sparse systems | |
# of equations. | |
# | |
eqs = system | |
eqs = [sympify(eq) for eq in eqs] | |
try: | |
sol = _linsolve(eqs, symbols) | |
except PolyNonlinearError as exc: | |
# e.g. cos(x) contains an element of the set of generators | |
raise NonlinearError(str(exc)) | |
if sol is None: | |
return S.EmptySet | |
sol = FiniteSet(Tuple(*(sol.get(sym, sym) for sym in symbols))) | |
return sol | |
elif isinstance(system, MatrixBase) and not ( | |
symbols and not isinstance(symbols, GeneratorType) and | |
isinstance(symbols[0], MatrixBase)): | |
# 3). A augmented with b | |
A, b = system[:, :-1], system[:, -1:] | |
if b is None: | |
raise ValueError("Invalid arguments") | |
if sym_gen: | |
symbols = [next(symbols) for i in range(A.cols)] | |
symset = set(symbols) | |
if any(symset & (A.free_symbols | b.free_symbols)): | |
raise ValueError(filldedent(''' | |
At least one of the symbols provided | |
already appears in the system to be solved. | |
One way to avoid this is to use Dummy symbols in | |
the generator, e.g. numbered_symbols('%s', cls=Dummy) | |
''' % symbols[0].name.rstrip('1234567890'))) | |
elif len(symset) != len(symbols): | |
raise ValueError(dup_msg) | |
if not symbols: | |
symbols = [Dummy() for _ in range(A.cols)] | |
name = _uniquely_named_symbol('tau', (A, b), | |
compare=lambda i: str(i).rstrip('1234567890')).name | |
gen = numbered_symbols(name) | |
else: | |
gen = None | |
# This is just a wrapper for solve_lin_sys | |
eqs = [] | |
rows = A.tolist() | |
for rowi, bi in zip(rows, b): | |
terms = [elem * sym for elem, sym in zip(rowi, symbols) if elem] | |
terms.append(-bi) | |
eqs.append(Add(*terms)) | |
eqs, ring = sympy_eqs_to_ring(eqs, symbols) | |
sol = solve_lin_sys(eqs, ring, _raw=False) | |
if sol is None: | |
return S.EmptySet | |
#sol = {sym:val for sym, val in sol.items() if sym != val} | |
sol = FiniteSet(Tuple(*(sol.get(sym, sym) for sym in symbols))) | |
if gen is not None: | |
solsym = sol.free_symbols | |
rep = {sym: next(gen) for sym in symbols if sym in solsym} | |
sol = sol.subs(rep) | |
return sol | |
############################################################################## | |
# ------------------------------nonlinsolve ---------------------------------# | |
############################################################################## | |
def _return_conditionset(eqs, symbols): | |
# return conditionset | |
eqs = (Eq(lhs, 0) for lhs in eqs) | |
condition_set = ConditionSet( | |
Tuple(*symbols), And(*eqs), S.Complexes**len(symbols)) | |
return condition_set | |
def substitution(system, symbols, result=[{}], known_symbols=[], | |
exclude=[], all_symbols=None): | |
r""" | |
Solves the `system` using substitution method. It is used in | |
:func:`~.nonlinsolve`. This will be called from :func:`~.nonlinsolve` when any | |
equation(s) is non polynomial equation. | |
Parameters | |
========== | |
system : list of equations | |
The target system of equations | |
symbols : list of symbols to be solved. | |
The variable(s) for which the system is solved | |
known_symbols : list of solved symbols | |
Values are known for these variable(s) | |
result : An empty list or list of dict | |
If No symbol values is known then empty list otherwise | |
symbol as keys and corresponding value in dict. | |
exclude : Set of expression. | |
Mostly denominator expression(s) of the equations of the system. | |
Final solution should not satisfy these expressions. | |
all_symbols : known_symbols + symbols(unsolved). | |
Returns | |
======= | |
A FiniteSet of ordered tuple of values of `all_symbols` for which the | |
`system` has solution. Order of values in the tuple is same as symbols | |
present in the parameter `all_symbols`. If parameter `all_symbols` is None | |
then same as symbols present in the parameter `symbols`. | |
Please note that general FiniteSet is unordered, the solution returned | |
here is not simply a FiniteSet of solutions, rather it is a FiniteSet of | |
ordered tuple, i.e. the first & only argument to FiniteSet is a tuple of | |
solutions, which is ordered, & hence the returned solution is ordered. | |
Also note that solution could also have been returned as an ordered tuple, | |
FiniteSet is just a wrapper `{}` around the tuple. It has no other | |
significance except for the fact it is just used to maintain a consistent | |
output format throughout the solveset. | |
Raises | |
====== | |
ValueError | |
The input is not valid. | |
The symbols are not given. | |
AttributeError | |
The input symbols are not :class:`~.Symbol` type. | |
Examples | |
======== | |
>>> from sympy import symbols, substitution | |
>>> x, y = symbols('x, y', real=True) | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([]), [x, y]) | |
{(-1, 1)} | |
* When you want a soln not satisfying $x + 1 = 0$ | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([x + 1]), [y, x]) | |
EmptySet | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([x - 1]), [y, x]) | |
{(1, -1)} | |
>>> substitution([x + y - 1, y - x**2 + 5], [x, y]) | |
{(-3, 4), (2, -1)} | |
* Returns both real and complex solution | |
>>> x, y, z = symbols('x, y, z') | |
>>> from sympy import exp, sin | |
>>> substitution([exp(x) - sin(y), y**2 - 4], [x, y]) | |
{(ImageSet(Lambda(_n, I*(2*_n*pi + pi) + log(sin(2))), Integers), -2), | |
(ImageSet(Lambda(_n, 2*_n*I*pi + log(sin(2))), Integers), 2)} | |
>>> eqs = [z**2 + exp(2*x) - sin(y), -3 + exp(-y)] | |
>>> substitution(eqs, [y, z]) | |
{(-log(3), -sqrt(-exp(2*x) - sin(log(3)))), | |
(-log(3), sqrt(-exp(2*x) - sin(log(3)))), | |
(ImageSet(Lambda(_n, 2*_n*I*pi - log(3)), Integers), | |
ImageSet(Lambda(_n, -sqrt(-exp(2*x) + sin(2*_n*I*pi - log(3)))), Integers)), | |
(ImageSet(Lambda(_n, 2*_n*I*pi - log(3)), Integers), | |
ImageSet(Lambda(_n, sqrt(-exp(2*x) + sin(2*_n*I*pi - log(3)))), Integers))} | |
""" | |
if not system: | |
return S.EmptySet | |
for i, e in enumerate(system): | |
if isinstance(e, Eq): | |
system[i] = e.lhs - e.rhs | |
if not symbols: | |
msg = ('Symbols must be given, for which solution of the ' | |
'system is to be found.') | |
raise ValueError(filldedent(msg)) | |
if not is_sequence(symbols): | |
msg = ('symbols should be given as a sequence, e.g. a list.' | |
'Not type %s: %s') | |
raise TypeError(filldedent(msg % (type(symbols), symbols))) | |
if not getattr(symbols[0], 'is_Symbol', False): | |
msg = ('Iterable of symbols must be given as ' | |
'second argument, not type %s: %s') | |
raise ValueError(filldedent(msg % (type(symbols[0]), symbols[0]))) | |
# By default `all_symbols` will be same as `symbols` | |
if all_symbols is None: | |
all_symbols = symbols | |
old_result = result | |
# storing complements and intersection for particular symbol | |
complements = {} | |
intersections = {} | |
# when total_solveset_call equals total_conditionset | |
# it means that solveset failed to solve all eqs. | |
total_conditionset = -1 | |
total_solveset_call = -1 | |
def _unsolved_syms(eq, sort=False): | |
"""Returns the unsolved symbol present | |
in the equation `eq`. | |
""" | |
free = eq.free_symbols | |
unsolved = (free - set(known_symbols)) & set(all_symbols) | |
if sort: | |
unsolved = list(unsolved) | |
unsolved.sort(key=default_sort_key) | |
return unsolved | |
# sort such that equation with the fewest potential symbols is first. | |
# means eq with less number of variable first in the list. | |
eqs_in_better_order = list( | |
ordered(system, lambda _: len(_unsolved_syms(_)))) | |
def add_intersection_complement(result, intersection_dict, complement_dict): | |
# If solveset has returned some intersection/complement | |
# for any symbol, it will be added in the final solution. | |
final_result = [] | |
for res in result: | |
res_copy = res | |
for key_res, value_res in res.items(): | |
intersect_set, complement_set = None, None | |
for key_sym, value_sym in intersection_dict.items(): | |
if key_sym == key_res: | |
intersect_set = value_sym | |
for key_sym, value_sym in complement_dict.items(): | |
if key_sym == key_res: | |
complement_set = value_sym | |
if intersect_set or complement_set: | |
new_value = FiniteSet(value_res) | |
if intersect_set and intersect_set != S.Complexes: | |
new_value = Intersection(new_value, intersect_set) | |
if complement_set: | |
new_value = Complement(new_value, complement_set) | |
if new_value is S.EmptySet: | |
res_copy = None | |
break | |
elif new_value.is_FiniteSet and len(new_value) == 1: | |
res_copy[key_res] = set(new_value).pop() | |
else: | |
res_copy[key_res] = new_value | |
if res_copy is not None: | |
final_result.append(res_copy) | |
return final_result | |
def _extract_main_soln(sym, sol, soln_imageset): | |
"""Separate the Complements, Intersections, ImageSet lambda expr and | |
its base_set. This function returns the unmasked sol from different classes | |
of sets and also returns the appended ImageSet elements in a | |
soln_imageset dict: `{unmasked element: ImageSet}`. | |
""" | |
# if there is union, then need to check | |
# Complement, Intersection, Imageset. | |
# Order should not be changed. | |
if isinstance(sol, ConditionSet): | |
# extracts any solution in ConditionSet | |
sol = sol.base_set | |
if isinstance(sol, Complement): | |
# extract solution and complement | |
complements[sym] = sol.args[1] | |
sol = sol.args[0] | |
# complement will be added at the end | |
# using `add_intersection_complement` method | |
# if there is union of Imageset or other in soln. | |
# no testcase is written for this if block | |
if isinstance(sol, Union): | |
sol_args = sol.args | |
sol = S.EmptySet | |
# We need in sequence so append finteset elements | |
# and then imageset or other. | |
for sol_arg2 in sol_args: | |
if isinstance(sol_arg2, FiniteSet): | |
sol += sol_arg2 | |
else: | |
# ImageSet, Intersection, complement then | |
# append them directly | |
sol += FiniteSet(sol_arg2) | |
if isinstance(sol, Intersection): | |
# Interval/Set will be at 0th index always | |
if sol.args[0] not in (S.Reals, S.Complexes): | |
# Sometimes solveset returns soln with intersection | |
# S.Reals or S.Complexes. We don't consider that | |
# intersection. | |
intersections[sym] = sol.args[0] | |
sol = sol.args[1] | |
# after intersection and complement Imageset should | |
# be checked. | |
if isinstance(sol, ImageSet): | |
soln_imagest = sol | |
expr2 = sol.lamda.expr | |
sol = FiniteSet(expr2) | |
soln_imageset[expr2] = soln_imagest | |
if not isinstance(sol, FiniteSet): | |
sol = FiniteSet(sol) | |
return sol, soln_imageset | |
def _check_exclude(rnew, imgset_yes): | |
rnew_ = rnew | |
if imgset_yes: | |
# replace all dummy variables (Imageset lambda variables) | |
# with zero before `checksol`. Considering fundamental soln | |
# for `checksol`. | |
rnew_copy = rnew.copy() | |
dummy_n = imgset_yes[0] | |
for key_res, value_res in rnew_copy.items(): | |
rnew_copy[key_res] = value_res.subs(dummy_n, 0) | |
rnew_ = rnew_copy | |
# satisfy_exclude == true if it satisfies the expr of `exclude` list. | |
try: | |
# something like : `Mod(-log(3), 2*I*pi)` can't be | |
# simplified right now, so `checksol` returns `TypeError`. | |
# when this issue is fixed this try block should be | |
# removed. Mod(-log(3), 2*I*pi) == -log(3) | |
satisfy_exclude = any( | |
checksol(d, rnew_) for d in exclude) | |
except TypeError: | |
satisfy_exclude = None | |
return satisfy_exclude | |
def _restore_imgset(rnew, original_imageset, newresult): | |
restore_sym = set(rnew.keys()) & \ | |
set(original_imageset.keys()) | |
for key_sym in restore_sym: | |
img = original_imageset[key_sym] | |
rnew[key_sym] = img | |
if rnew not in newresult: | |
newresult.append(rnew) | |
def _append_eq(eq, result, res, delete_soln, n=None): | |
u = Dummy('u') | |
if n: | |
eq = eq.subs(n, 0) | |
satisfy = eq if eq in (True, False) else checksol(u, u, eq, minimal=True) | |
if satisfy is False: | |
delete_soln = True | |
res = {} | |
else: | |
result.append(res) | |
return result, res, delete_soln | |
def _append_new_soln(rnew, sym, sol, imgset_yes, soln_imageset, | |
original_imageset, newresult, eq=None): | |
"""If `rnew` (A dict <symbol: soln>) contains valid soln | |
append it to `newresult` list. | |
`imgset_yes` is (base, dummy_var) if there was imageset in previously | |
calculated result(otherwise empty tuple). `original_imageset` is dict | |
of imageset expr and imageset from this result. | |
`soln_imageset` dict of imageset expr and imageset of new soln. | |
""" | |
satisfy_exclude = _check_exclude(rnew, imgset_yes) | |
delete_soln = False | |
# soln should not satisfy expr present in `exclude` list. | |
if not satisfy_exclude: | |
local_n = None | |
# if it is imageset | |
if imgset_yes: | |
local_n = imgset_yes[0] | |
base = imgset_yes[1] | |
if sym and sol: | |
# when `sym` and `sol` is `None` means no new | |
# soln. In that case we will append rnew directly after | |
# substituting original imagesets in rnew values if present | |
# (second last line of this function using _restore_imgset) | |
dummy_list = list(sol.atoms(Dummy)) | |
# use one dummy `n` which is in | |
# previous imageset | |
local_n_list = [ | |
local_n for i in range( | |
0, len(dummy_list))] | |
dummy_zip = zip(dummy_list, local_n_list) | |
lam = Lambda(local_n, sol.subs(dummy_zip)) | |
rnew[sym] = ImageSet(lam, base) | |
if eq is not None: | |
newresult, rnew, delete_soln = _append_eq( | |
eq, newresult, rnew, delete_soln, local_n) | |
elif eq is not None: | |
newresult, rnew, delete_soln = _append_eq( | |
eq, newresult, rnew, delete_soln) | |
elif sol in soln_imageset.keys(): | |
rnew[sym] = soln_imageset[sol] | |
# restore original imageset | |
_restore_imgset(rnew, original_imageset, newresult) | |
else: | |
newresult.append(rnew) | |
elif satisfy_exclude: | |
delete_soln = True | |
rnew = {} | |
_restore_imgset(rnew, original_imageset, newresult) | |
return newresult, delete_soln | |
def _new_order_result(result, eq): | |
# separate first, second priority. `res` that makes `eq` value equals | |
# to zero, should be used first then other result(second priority). | |
# If it is not done then we may miss some soln. | |
first_priority = [] | |
second_priority = [] | |
for res in result: | |
if not any(isinstance(val, ImageSet) for val in res.values()): | |
if eq.subs(res) == 0: | |
first_priority.append(res) | |
else: | |
second_priority.append(res) | |
if first_priority or second_priority: | |
return first_priority + second_priority | |
return result | |
def _solve_using_known_values(result, solver): | |
"""Solves the system using already known solution | |
(result contains the dict <symbol: value>). | |
solver is :func:`~.solveset_complex` or :func:`~.solveset_real`. | |
""" | |
# stores imageset <expr: imageset(Lambda(n, expr), base)>. | |
soln_imageset = {} | |
total_solvest_call = 0 | |
total_conditionst = 0 | |
# sort equations so the one with the fewest potential | |
# symbols appears first | |
for index, eq in enumerate(eqs_in_better_order): | |
newresult = [] | |
# if imageset, expr is used to solve for other symbol | |
imgset_yes = False | |
for res in result: | |
original_imageset = {} | |
got_symbol = set() # symbols solved in one iteration | |
# find the imageset and use its expr. | |
for k, v in res.items(): | |
if isinstance(v, ImageSet): | |
res[k] = v.lamda.expr | |
original_imageset[k] = v | |
dummy_n = v.lamda.expr.atoms(Dummy).pop() | |
(base,) = v.base_sets | |
imgset_yes = (dummy_n, base) | |
assert not isinstance(v, FiniteSet) # if so, internal error | |
# update eq with everything that is known so far | |
eq2 = eq.subs(res).expand() | |
if imgset_yes and not eq2.has(imgset_yes[0]): | |
# The substituted equation simplified in such a way that | |
# it's no longer necessary to encapsulate a potential new | |
# solution in an ImageSet. (E.g. at the previous step some | |
# {n*2*pi} was found as partial solution for one of the | |
# unknowns, but its main solution expression n*2*pi has now | |
# been substituted in a trigonometric function.) | |
imgset_yes = False | |
unsolved_syms = _unsolved_syms(eq2, sort=True) | |
if not unsolved_syms: | |
if res: | |
newresult, delete_res = _append_new_soln( | |
res, None, None, imgset_yes, soln_imageset, | |
original_imageset, newresult, eq2) | |
if delete_res: | |
# `delete_res` is true, means substituting `res` in | |
# eq2 doesn't return `zero` or deleting the `res` | |
# (a soln) since it satisfies expr of `exclude` | |
# list. | |
result.remove(res) | |
continue # skip as it's independent of desired symbols | |
depen1, depen2 = eq2.as_independent(*unsolved_syms) | |
if (depen1.has(Abs) or depen2.has(Abs)) and solver == solveset_complex: | |
# Absolute values cannot be inverted in the | |
# complex domain | |
continue | |
soln_imageset = {} | |
for sym in unsolved_syms: | |
not_solvable = False | |
try: | |
soln = solver(eq2, sym) | |
total_solvest_call += 1 | |
soln_new = S.EmptySet | |
if isinstance(soln, Complement): | |
# separate solution and complement | |
complements[sym] = soln.args[1] | |
soln = soln.args[0] | |
# complement will be added at the end | |
if isinstance(soln, Intersection): | |
# Interval will be at 0th index always | |
if soln.args[0] != Interval(-oo, oo): | |
# sometimes solveset returns soln | |
# with intersection S.Reals, to confirm that | |
# soln is in domain=S.Reals | |
intersections[sym] = soln.args[0] | |
soln_new += soln.args[1] | |
soln = soln_new if soln_new else soln | |
if index > 0 and solver == solveset_real: | |
# one symbol's real soln, another symbol may have | |
# corresponding complex soln. | |
if not isinstance(soln, (ImageSet, ConditionSet)): | |
soln += solveset_complex(eq2, sym) # might give ValueError with Abs | |
except (NotImplementedError, ValueError): | |
# If solveset is not able to solve equation `eq2`. Next | |
# time we may get soln using next equation `eq2` | |
continue | |
if isinstance(soln, ConditionSet): | |
if soln.base_set in (S.Reals, S.Complexes): | |
soln = S.EmptySet | |
# don't do `continue` we may get soln | |
# in terms of other symbol(s) | |
not_solvable = True | |
total_conditionst += 1 | |
else: | |
soln = soln.base_set | |
if soln is not S.EmptySet: | |
soln, soln_imageset = _extract_main_soln( | |
sym, soln, soln_imageset) | |
for sol in soln: | |
# sol is not a `Union` since we checked it | |
# before this loop | |
sol, soln_imageset = _extract_main_soln( | |
sym, sol, soln_imageset) | |
sol = set(sol).pop() # XXX what if there are more solutions? | |
free = sol.free_symbols | |
if got_symbol and any( | |
ss in free for ss in got_symbol | |
): | |
# sol depends on previously solved symbols | |
# then continue | |
continue | |
rnew = res.copy() | |
# put each solution in res and append the new result | |
# in the new result list (solution for symbol `s`) | |
# along with old results. | |
for k, v in res.items(): | |
if isinstance(v, Expr) and isinstance(sol, Expr): | |
# if any unsolved symbol is present | |
# Then subs known value | |
rnew[k] = v.subs(sym, sol) | |
# and add this new solution | |
if sol in soln_imageset.keys(): | |
# replace all lambda variables with 0. | |
imgst = soln_imageset[sol] | |
rnew[sym] = imgst.lamda( | |
*[0 for i in range(0, len( | |
imgst.lamda.variables))]) | |
else: | |
rnew[sym] = sol | |
newresult, delete_res = _append_new_soln( | |
rnew, sym, sol, imgset_yes, soln_imageset, | |
original_imageset, newresult) | |
if delete_res: | |
# deleting the `res` (a soln) since it satisfies | |
# eq of `exclude` list | |
result.remove(res) | |
# solution got for sym | |
if not not_solvable: | |
got_symbol.add(sym) | |
# next time use this new soln | |
if newresult: | |
result = newresult | |
return result, total_solvest_call, total_conditionst | |
new_result_real, solve_call1, cnd_call1 = _solve_using_known_values( | |
old_result, solveset_real) | |
new_result_complex, solve_call2, cnd_call2 = _solve_using_known_values( | |
old_result, solveset_complex) | |
# If total_solveset_call is equal to total_conditionset | |
# then solveset failed to solve all of the equations. | |
# In this case we return a ConditionSet here. | |
total_conditionset += (cnd_call1 + cnd_call2) | |
total_solveset_call += (solve_call1 + solve_call2) | |
if total_conditionset == total_solveset_call and total_solveset_call != -1: | |
return _return_conditionset(eqs_in_better_order, all_symbols) | |
# don't keep duplicate solutions | |
filtered_complex = [] | |
for i in list(new_result_complex): | |
for j in list(new_result_real): | |
if i.keys() != j.keys(): | |
continue | |
if all(a.dummy_eq(b) for a, b in zip(i.values(), j.values()) \ | |
if not (isinstance(a, int) and isinstance(b, int))): | |
break | |
else: | |
filtered_complex.append(i) | |
# overall result | |
result = new_result_real + filtered_complex | |
result_all_variables = [] | |
result_infinite = [] | |
for res in result: | |
if not res: | |
# means {None : None} | |
continue | |
# If length < len(all_symbols) means infinite soln. | |
# Some or all the soln is dependent on 1 symbol. | |
# eg. {x: y+2} then final soln {x: y+2, y: y} | |
if len(res) < len(all_symbols): | |
solved_symbols = res.keys() | |
unsolved = list(filter( | |
lambda x: x not in solved_symbols, all_symbols)) | |
for unsolved_sym in unsolved: | |
res[unsolved_sym] = unsolved_sym | |
result_infinite.append(res) | |
if res not in result_all_variables: | |
result_all_variables.append(res) | |
if result_infinite: | |
# we have general soln | |
# eg : [{x: -1, y : 1}, {x : -y, y: y}] then | |
# return [{x : -y, y : y}] | |
result_all_variables = result_infinite | |
if intersections or complements: | |
result_all_variables = add_intersection_complement( | |
result_all_variables, intersections, complements) | |
# convert to ordered tuple | |
result = S.EmptySet | |
for r in result_all_variables: | |
temp = [r[symb] for symb in all_symbols] | |
result += FiniteSet(tuple(temp)) | |
return result | |
def _solveset_work(system, symbols): | |
soln = solveset(system[0], symbols[0]) | |
if isinstance(soln, FiniteSet): | |
_soln = FiniteSet(*[(s,) for s in soln]) | |
return _soln | |
else: | |
return FiniteSet(tuple(FiniteSet(soln))) | |
def _handle_positive_dimensional(polys, symbols, denominators): | |
from sympy.polys.polytools import groebner | |
# substitution method where new system is groebner basis of the system | |
_symbols = list(symbols) | |
_symbols.sort(key=default_sort_key) | |
basis = groebner(polys, _symbols, polys=True) | |
new_system = [] | |
for poly_eq in basis: | |
new_system.append(poly_eq.as_expr()) | |
result = [{}] | |
result = substitution( | |
new_system, symbols, result, [], | |
denominators) | |
return result | |
def _handle_zero_dimensional(polys, symbols, system): | |
# solve 0 dimensional poly system using `solve_poly_system` | |
result = solve_poly_system(polys, *symbols) | |
# May be some extra soln is added because | |
# we used `unrad` in `_separate_poly_nonpoly`, so | |
# need to check and remove if it is not a soln. | |
result_update = S.EmptySet | |
for res in result: | |
dict_sym_value = dict(list(zip(symbols, res))) | |
if all(checksol(eq, dict_sym_value) for eq in system): | |
result_update += FiniteSet(res) | |
return result_update | |
def _separate_poly_nonpoly(system, symbols): | |
polys = [] | |
polys_expr = [] | |
nonpolys = [] | |
# unrad_changed stores a list of expressions containing | |
# radicals that were processed using unrad | |
# this is useful if solutions need to be checked later. | |
unrad_changed = [] | |
denominators = set() | |
poly = None | |
for eq in system: | |
# Store denom expressions that contain symbols | |
denominators.update(_simple_dens(eq, symbols)) | |
# Convert equality to expression | |
if isinstance(eq, Eq): | |
eq = eq.lhs - eq.rhs | |
# try to remove sqrt and rational power | |
without_radicals = unrad(simplify(eq), *symbols) | |
if without_radicals: | |
unrad_changed.append(eq) | |
eq_unrad, cov = without_radicals | |
if not cov: | |
eq = eq_unrad | |
if isinstance(eq, Expr): | |
eq = eq.as_numer_denom()[0] | |
poly = eq.as_poly(*symbols, extension=True) | |
elif simplify(eq).is_number: | |
continue | |
if poly is not None: | |
polys.append(poly) | |
polys_expr.append(poly.as_expr()) | |
else: | |
nonpolys.append(eq) | |
return polys, polys_expr, nonpolys, denominators, unrad_changed | |
def _handle_poly(polys, symbols): | |
# _handle_poly(polys, symbols) -> (poly_sol, poly_eqs) | |
# | |
# We will return possible solution information to nonlinsolve as well as a | |
# new system of polynomial equations to be solved if we cannot solve | |
# everything directly here. The new system of polynomial equations will be | |
# a lex-order Groebner basis for the original system. The lex basis | |
# hopefully separate some of the variables and equations and give something | |
# easier for substitution to work with. | |
# The format for representing solution sets in nonlinsolve and substitution | |
# is a list of dicts. These are the special cases: | |
no_information = [{}] # No equations solved yet | |
no_solutions = [] # The system is inconsistent and has no solutions. | |
# If there is no need to attempt further solution of these equations then | |
# we return no equations: | |
no_equations = [] | |
inexact = any(not p.domain.is_Exact for p in polys) | |
if inexact: | |
# The use of Groebner over RR is likely to result incorrectly in an | |
# inconsistent Groebner basis. So, convert any float coefficients to | |
# Rational before computing the Groebner basis. | |
polys = [poly(nsimplify(p, rational=True)) for p in polys] | |
# Compute a Groebner basis in grevlex order wrt the ordering given. We will | |
# try to convert this to lex order later. Usually it seems to be more | |
# efficient to compute a lex order basis by computing a grevlex basis and | |
# converting to lex with fglm. | |
basis = groebner(polys, symbols, order='grevlex', polys=False) | |
# | |
# No solutions (inconsistent equations)? | |
# | |
if 1 in basis: | |
# No solutions: | |
poly_sol = no_solutions | |
poly_eqs = no_equations | |
# | |
# Finite number of solutions (zero-dimensional case) | |
# | |
elif basis.is_zero_dimensional: | |
# Convert Groebner basis to lex ordering | |
basis = basis.fglm('lex') | |
# Convert polynomial coefficients back to float before calling | |
# solve_poly_system | |
if inexact: | |
basis = [nfloat(p) for p in basis] | |
# Solve the zero-dimensional case using solve_poly_system if possible. | |
# If some polynomials have factors that cannot be solved in radicals | |
# then this will fail. Using solve_poly_system(..., strict=True) | |
# ensures that we either get a complete solution set in radicals or | |
# UnsolvableFactorError will be raised. | |
try: | |
result = solve_poly_system(basis, *symbols, strict=True) | |
except UnsolvableFactorError: | |
# Failure... not fully solvable in radicals. Return the lex-order | |
# basis for substitution to handle. | |
poly_sol = no_information | |
poly_eqs = list(basis) | |
else: | |
# Success! We have a finite solution set and solve_poly_system has | |
# succeeded in finding all solutions. Return the solutions and also | |
# an empty list of remaining equations to be solved. | |
poly_sol = [dict(zip(symbols, res)) for res in result] | |
poly_eqs = no_equations | |
# | |
# Infinite families of solutions (positive-dimensional case) | |
# | |
else: | |
# In this case the grevlex basis cannot be converted to lex using the | |
# fglm method and also solve_poly_system cannot solve the equations. We | |
# would like to return a lex basis but since we can't use fglm we | |
# compute the lex basis directly here. The time required to recompute | |
# the basis is generally significantly less than the time required by | |
# substitution to solve the new system. | |
poly_sol = no_information | |
poly_eqs = list(groebner(polys, symbols, order='lex', polys=False)) | |
if inexact: | |
poly_eqs = [nfloat(p) for p in poly_eqs] | |
return poly_sol, poly_eqs | |
def nonlinsolve(system, *symbols): | |
r""" | |
Solve system of $N$ nonlinear equations with $M$ variables, which means both | |
under and overdetermined systems are supported. Positive dimensional | |
system is also supported (A system with infinitely many solutions is said | |
to be positive-dimensional). In a positive dimensional system the solution will | |
be dependent on at least one symbol. Returns both real solution | |
and complex solution (if they exist). | |
Parameters | |
========== | |
system : list of equations | |
The target system of equations | |
symbols : list of Symbols | |
symbols should be given as a sequence eg. list | |
Returns | |
======= | |
A :class:`~.FiniteSet` of ordered tuple of values of `symbols` for which the `system` | |
has solution. Order of values in the tuple is same as symbols present in | |
the parameter `symbols`. | |
Please note that general :class:`~.FiniteSet` is unordered, the solution | |
returned here is not simply a :class:`~.FiniteSet` of solutions, rather it | |
is a :class:`~.FiniteSet` of ordered tuple, i.e. the first and only | |
argument to :class:`~.FiniteSet` is a tuple of solutions, which is | |
ordered, and, hence ,the returned solution is ordered. | |
Also note that solution could also have been returned as an ordered tuple, | |
FiniteSet is just a wrapper ``{}`` around the tuple. It has no other | |
significance except for the fact it is just used to maintain a consistent | |
output format throughout the solveset. | |
For the given set of equations, the respective input types | |
are given below: | |
.. math:: xy - 1 = 0 | |
.. math:: 4x^2 + y^2 - 5 = 0 | |
:: | |
system = [x*y - 1, 4*x**2 + y**2 - 5] | |
symbols = [x, y] | |
Raises | |
====== | |
ValueError | |
The input is not valid. | |
The symbols are not given. | |
AttributeError | |
The input symbols are not `Symbol` type. | |
Examples | |
======== | |
>>> from sympy import symbols, nonlinsolve | |
>>> x, y, z = symbols('x, y, z', real=True) | |
>>> nonlinsolve([x*y - 1, 4*x**2 + y**2 - 5], [x, y]) | |
{(-1, -1), (-1/2, -2), (1/2, 2), (1, 1)} | |
1. Positive dimensional system and complements: | |
>>> from sympy import pprint | |
>>> from sympy.polys.polytools import is_zero_dimensional | |
>>> a, b, c, d = symbols('a, b, c, d', extended_real=True) | |
>>> eq1 = a + b + c + d | |
>>> eq2 = a*b + b*c + c*d + d*a | |
>>> eq3 = a*b*c + b*c*d + c*d*a + d*a*b | |
>>> eq4 = a*b*c*d - 1 | |
>>> system = [eq1, eq2, eq3, eq4] | |
>>> is_zero_dimensional(system) | |
False | |
>>> pprint(nonlinsolve(system, [a, b, c, d]), use_unicode=False) | |
-1 1 1 -1 | |
{(---, -d, -, {d} \ {0}), (-, -d, ---, {d} \ {0})} | |
d d d d | |
>>> nonlinsolve([(x+y)**2 - 4, x + y - 2], [x, y]) | |
{(2 - y, y)} | |
2. If some of the equations are non-polynomial then `nonlinsolve` | |
will call the ``substitution`` function and return real and complex solutions, | |
if present. | |
>>> from sympy import exp, sin | |
>>> nonlinsolve([exp(x) - sin(y), y**2 - 4], [x, y]) | |
{(ImageSet(Lambda(_n, I*(2*_n*pi + pi) + log(sin(2))), Integers), -2), | |
(ImageSet(Lambda(_n, 2*_n*I*pi + log(sin(2))), Integers), 2)} | |
3. If system is non-linear polynomial and zero-dimensional then it | |
returns both solution (real and complex solutions, if present) using | |
:func:`~.solve_poly_system`: | |
>>> from sympy import sqrt | |
>>> nonlinsolve([x**2 - 2*y**2 -2, x*y - 2], [x, y]) | |
{(-2, -1), (2, 1), (-sqrt(2)*I, sqrt(2)*I), (sqrt(2)*I, -sqrt(2)*I)} | |
4. ``nonlinsolve`` can solve some linear (zero or positive dimensional) | |
system (because it uses the :func:`sympy.polys.polytools.groebner` function to get the | |
groebner basis and then uses the ``substitution`` function basis as the | |
new `system`). But it is not recommended to solve linear system using | |
``nonlinsolve``, because :func:`~.linsolve` is better for general linear systems. | |
>>> nonlinsolve([x + 2*y -z - 3, x - y - 4*z + 9, y + z - 4], [x, y, z]) | |
{(3*z - 5, 4 - z, z)} | |
5. System having polynomial equations and only real solution is | |
solved using :func:`~.solve_poly_system`: | |
>>> e1 = sqrt(x**2 + y**2) - 10 | |
>>> e2 = sqrt(y**2 + (-x + 10)**2) - 3 | |
>>> nonlinsolve((e1, e2), (x, y)) | |
{(191/20, -3*sqrt(391)/20), (191/20, 3*sqrt(391)/20)} | |
>>> nonlinsolve([x**2 + 2/y - 2, x + y - 3], [x, y]) | |
{(1, 2), (1 - sqrt(5), 2 + sqrt(5)), (1 + sqrt(5), 2 - sqrt(5))} | |
>>> nonlinsolve([x**2 + 2/y - 2, x + y - 3], [y, x]) | |
{(2, 1), (2 - sqrt(5), 1 + sqrt(5)), (2 + sqrt(5), 1 - sqrt(5))} | |
6. It is better to use symbols instead of trigonometric functions or | |
:class:`~.Function`. For example, replace $\sin(x)$ with a symbol, replace | |
$f(x)$ with a symbol and so on. Get a solution from ``nonlinsolve`` and then | |
use :func:`~.solveset` to get the value of $x$. | |
How nonlinsolve is better than old solver ``_solve_system`` : | |
============================================================= | |
1. A positive dimensional system solver: nonlinsolve can return | |
solution for positive dimensional system. It finds the | |
Groebner Basis of the positive dimensional system(calling it as | |
basis) then we can start solving equation(having least number of | |
variable first in the basis) using solveset and substituting that | |
solved solutions into other equation(of basis) to get solution in | |
terms of minimum variables. Here the important thing is how we | |
are substituting the known values and in which equations. | |
2. Real and complex solutions: nonlinsolve returns both real | |
and complex solution. If all the equations in the system are polynomial | |
then using :func:`~.solve_poly_system` both real and complex solution is returned. | |
If all the equations in the system are not polynomial equation then goes to | |
``substitution`` method with this polynomial and non polynomial equation(s), | |
to solve for unsolved variables. Here to solve for particular variable | |
solveset_real and solveset_complex is used. For both real and complex | |
solution ``_solve_using_known_values`` is used inside ``substitution`` | |
(``substitution`` will be called when any non-polynomial equation is present). | |
If a solution is valid its general solution is added to the final result. | |
3. :class:`~.Complement` and :class:`~.Intersection` will be added: | |
nonlinsolve maintains dict for complements and intersections. If solveset | |
find complements or/and intersections with any interval or set during the | |
execution of ``substitution`` function, then complement or/and | |
intersection for that variable is added before returning final solution. | |
""" | |
if not system: | |
return S.EmptySet | |
if not symbols: | |
msg = ('Symbols must be given, for which solution of the ' | |
'system is to be found.') | |
raise ValueError(filldedent(msg)) | |
if hasattr(symbols[0], '__iter__'): | |
symbols = symbols[0] | |
if not is_sequence(symbols) or not symbols: | |
msg = ('Symbols must be given, for which solution of the ' | |
'system is to be found.') | |
raise IndexError(filldedent(msg)) | |
symbols = list(map(_sympify, symbols)) | |
system, symbols, swap = recast_to_symbols(system, symbols) | |
if swap: | |
soln = nonlinsolve(system, symbols) | |
return FiniteSet(*[tuple(i.xreplace(swap) for i in s) for s in soln]) | |
if len(system) == 1 and len(symbols) == 1: | |
return _solveset_work(system, symbols) | |
# main code of def nonlinsolve() starts from here | |
polys, polys_expr, nonpolys, denominators, unrad_changed = \ | |
_separate_poly_nonpoly(system, symbols) | |
poly_eqs = [] | |
poly_sol = [{}] | |
if polys: | |
poly_sol, poly_eqs = _handle_poly(polys, symbols) | |
if poly_sol and poly_sol[0]: | |
poly_syms = set().union(*(eq.free_symbols for eq in polys)) | |
unrad_syms = set().union(*(eq.free_symbols for eq in unrad_changed)) | |
if unrad_syms == poly_syms and unrad_changed: | |
# if all the symbols have been solved by _handle_poly | |
# and unrad has been used then check solutions | |
poly_sol = [sol for sol in poly_sol if checksol(unrad_changed, sol)] | |
# Collect together the unsolved polynomials with the non-polynomial | |
# equations. | |
remaining = poly_eqs + nonpolys | |
# to_tuple converts a solution dictionary to a tuple containing the | |
# value for each symbol | |
to_tuple = lambda sol: tuple(sol[s] for s in symbols) | |
if not remaining: | |
# If there is nothing left to solve then return the solution from | |
# solve_poly_system directly. | |
return FiniteSet(*map(to_tuple, poly_sol)) | |
else: | |
# Here we handle: | |
# | |
# 1. The Groebner basis if solve_poly_system failed. | |
# 2. The Groebner basis in the positive-dimensional case. | |
# 3. Any non-polynomial equations | |
# | |
# If solve_poly_system did succeed then we pass those solutions in as | |
# preliminary results. | |
subs_res = substitution(remaining, symbols, result=poly_sol, exclude=denominators) | |
if not isinstance(subs_res, FiniteSet): | |
return subs_res | |
# check solutions produced by substitution. Currently, checking is done for | |
# only those solutions which have non-Set variable values. | |
if unrad_changed: | |
result = [dict(zip(symbols, sol)) for sol in subs_res.args] | |
correct_sols = [sol for sol in result if any(isinstance(v, Set) for v in sol) | |
or checksol(unrad_changed, sol) != False] | |
return FiniteSet(*map(to_tuple, correct_sols)) | |
else: | |
return subs_res | |