|
from __future__ import annotations |
|
from typing import Callable |
|
|
|
from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational |
|
from sympy.core.logic import fuzzy_not |
|
from sympy.logic.boolalg import Boolean |
|
|
|
from sympy.assumptions import ask, Q |
|
|
|
|
|
def refine(expr, assumptions=True): |
|
""" |
|
Simplify an expression using assumptions. |
|
|
|
Explanation |
|
=========== |
|
|
|
Unlike :func:`~.simplify` which performs structural simplification |
|
without any assumption, this function transforms the expression into |
|
the form which is only valid under certain assumptions. Note that |
|
``simplify()`` is generally not done in refining process. |
|
|
|
Refining boolean expression involves reducing it to ``S.true`` or |
|
``S.false``. Unlike :func:`~.ask`, the expression will not be reduced |
|
if the truth value cannot be determined. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import refine, sqrt, Q |
|
>>> from sympy.abc import x |
|
>>> refine(sqrt(x**2), Q.real(x)) |
|
Abs(x) |
|
>>> refine(sqrt(x**2), Q.positive(x)) |
|
x |
|
|
|
>>> refine(Q.real(x), Q.positive(x)) |
|
True |
|
>>> refine(Q.positive(x), Q.real(x)) |
|
Q.positive(x) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.simplify.simplify.simplify : Structural simplification without assumptions. |
|
sympy.assumptions.ask.ask : Query for boolean expressions using assumptions. |
|
""" |
|
if not isinstance(expr, Basic): |
|
return expr |
|
|
|
if not expr.is_Atom: |
|
args = [refine(arg, assumptions) for arg in expr.args] |
|
|
|
expr = expr.func(*args) |
|
if hasattr(expr, '_eval_refine'): |
|
ref_expr = expr._eval_refine(assumptions) |
|
if ref_expr is not None: |
|
return ref_expr |
|
name = expr.__class__.__name__ |
|
handler = handlers_dict.get(name, None) |
|
if handler is None: |
|
return expr |
|
new_expr = handler(expr, assumptions) |
|
if (new_expr is None) or (expr == new_expr): |
|
return expr |
|
if not isinstance(new_expr, Expr): |
|
return new_expr |
|
return refine(new_expr, assumptions) |
|
|
|
|
|
def refine_abs(expr, assumptions): |
|
""" |
|
Handler for the absolute value. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Q, Abs |
|
>>> from sympy.assumptions.refine import refine_abs |
|
>>> from sympy.abc import x |
|
>>> refine_abs(Abs(x), Q.real(x)) |
|
>>> refine_abs(Abs(x), Q.positive(x)) |
|
x |
|
>>> refine_abs(Abs(x), Q.negative(x)) |
|
-x |
|
|
|
""" |
|
from sympy.functions.elementary.complexes import Abs |
|
arg = expr.args[0] |
|
if ask(Q.real(arg), assumptions) and \ |
|
fuzzy_not(ask(Q.negative(arg), assumptions)): |
|
|
|
return arg |
|
if ask(Q.negative(arg), assumptions): |
|
return -arg |
|
|
|
if isinstance(arg, Mul): |
|
r = [refine(abs(a), assumptions) for a in arg.args] |
|
non_abs = [] |
|
in_abs = [] |
|
for i in r: |
|
if isinstance(i, Abs): |
|
in_abs.append(i.args[0]) |
|
else: |
|
non_abs.append(i) |
|
return Mul(*non_abs) * Abs(Mul(*in_abs)) |
|
|
|
|
|
def refine_Pow(expr, assumptions): |
|
""" |
|
Handler for instances of Pow. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Q |
|
>>> from sympy.assumptions.refine import refine_Pow |
|
>>> from sympy.abc import x,y,z |
|
>>> refine_Pow((-1)**x, Q.real(x)) |
|
>>> refine_Pow((-1)**x, Q.even(x)) |
|
1 |
|
>>> refine_Pow((-1)**x, Q.odd(x)) |
|
-1 |
|
|
|
For powers of -1, even parts of the exponent can be simplified: |
|
|
|
>>> refine_Pow((-1)**(x+y), Q.even(x)) |
|
(-1)**y |
|
>>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z)) |
|
(-1)**y |
|
>>> refine_Pow((-1)**(x+y+2), Q.odd(x)) |
|
(-1)**(y + 1) |
|
>>> refine_Pow((-1)**(x+3), True) |
|
(-1)**(x + 1) |
|
|
|
""" |
|
from sympy.functions.elementary.complexes import Abs |
|
from sympy.functions import sign |
|
if isinstance(expr.base, Abs): |
|
if ask(Q.real(expr.base.args[0]), assumptions) and \ |
|
ask(Q.even(expr.exp), assumptions): |
|
return expr.base.args[0] ** expr.exp |
|
if ask(Q.real(expr.base), assumptions): |
|
if expr.base.is_number: |
|
if ask(Q.even(expr.exp), assumptions): |
|
return abs(expr.base) ** expr.exp |
|
if ask(Q.odd(expr.exp), assumptions): |
|
return sign(expr.base) * abs(expr.base) ** expr.exp |
|
if isinstance(expr.exp, Rational): |
|
if isinstance(expr.base, Pow): |
|
return abs(expr.base.base) ** (expr.base.exp * expr.exp) |
|
|
|
if expr.base is S.NegativeOne: |
|
if expr.exp.is_Add: |
|
|
|
old = expr |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
coeff, terms = expr.exp.as_coeff_add() |
|
terms = set(terms) |
|
even_terms = set() |
|
odd_terms = set() |
|
initial_number_of_terms = len(terms) |
|
|
|
for t in terms: |
|
if ask(Q.even(t), assumptions): |
|
even_terms.add(t) |
|
elif ask(Q.odd(t), assumptions): |
|
odd_terms.add(t) |
|
|
|
terms -= even_terms |
|
if len(odd_terms) % 2: |
|
terms -= odd_terms |
|
new_coeff = (coeff + S.One) % 2 |
|
else: |
|
terms -= odd_terms |
|
new_coeff = coeff % 2 |
|
|
|
if new_coeff != coeff or len(terms) < initial_number_of_terms: |
|
terms.add(new_coeff) |
|
expr = expr.base**(Add(*terms)) |
|
|
|
|
|
e2 = 2*expr.exp |
|
if ask(Q.even(e2), assumptions): |
|
if e2.could_extract_minus_sign(): |
|
e2 *= expr.base |
|
if e2.is_Add: |
|
i, p = e2.as_two_terms() |
|
if p.is_Pow and p.base is S.NegativeOne: |
|
if ask(Q.integer(p.exp), assumptions): |
|
i = (i + 1)/2 |
|
if ask(Q.even(i), assumptions): |
|
return expr.base**p.exp |
|
elif ask(Q.odd(i), assumptions): |
|
return expr.base**(p.exp + 1) |
|
else: |
|
return expr.base**(p.exp + i) |
|
|
|
if old != expr: |
|
return expr |
|
|
|
|
|
def refine_atan2(expr, assumptions): |
|
""" |
|
Handler for the atan2 function. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Q, atan2 |
|
>>> from sympy.assumptions.refine import refine_atan2 |
|
>>> from sympy.abc import x, y |
|
>>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x)) |
|
atan(y/x) |
|
>>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x)) |
|
atan(y/x) - pi |
|
>>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x)) |
|
atan(y/x) + pi |
|
>>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x)) |
|
pi |
|
>>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x)) |
|
pi/2 |
|
>>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x)) |
|
-pi/2 |
|
>>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x)) |
|
nan |
|
""" |
|
from sympy.functions.elementary.trigonometric import atan |
|
y, x = expr.args |
|
if ask(Q.real(y) & Q.positive(x), assumptions): |
|
return atan(y / x) |
|
elif ask(Q.negative(y) & Q.negative(x), assumptions): |
|
return atan(y / x) - S.Pi |
|
elif ask(Q.positive(y) & Q.negative(x), assumptions): |
|
return atan(y / x) + S.Pi |
|
elif ask(Q.zero(y) & Q.negative(x), assumptions): |
|
return S.Pi |
|
elif ask(Q.positive(y) & Q.zero(x), assumptions): |
|
return S.Pi/2 |
|
elif ask(Q.negative(y) & Q.zero(x), assumptions): |
|
return -S.Pi/2 |
|
elif ask(Q.zero(y) & Q.zero(x), assumptions): |
|
return S.NaN |
|
else: |
|
return expr |
|
|
|
|
|
def refine_re(expr, assumptions): |
|
""" |
|
Handler for real part. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.assumptions.refine import refine_re |
|
>>> from sympy import Q, re |
|
>>> from sympy.abc import x |
|
>>> refine_re(re(x), Q.real(x)) |
|
x |
|
>>> refine_re(re(x), Q.imaginary(x)) |
|
0 |
|
""" |
|
arg = expr.args[0] |
|
if ask(Q.real(arg), assumptions): |
|
return arg |
|
if ask(Q.imaginary(arg), assumptions): |
|
return S.Zero |
|
return _refine_reim(expr, assumptions) |
|
|
|
|
|
def refine_im(expr, assumptions): |
|
""" |
|
Handler for imaginary part. |
|
|
|
Explanation |
|
=========== |
|
|
|
>>> from sympy.assumptions.refine import refine_im |
|
>>> from sympy import Q, im |
|
>>> from sympy.abc import x |
|
>>> refine_im(im(x), Q.real(x)) |
|
0 |
|
>>> refine_im(im(x), Q.imaginary(x)) |
|
-I*x |
|
""" |
|
arg = expr.args[0] |
|
if ask(Q.real(arg), assumptions): |
|
return S.Zero |
|
if ask(Q.imaginary(arg), assumptions): |
|
return - S.ImaginaryUnit * arg |
|
return _refine_reim(expr, assumptions) |
|
|
|
def refine_arg(expr, assumptions): |
|
""" |
|
Handler for complex argument |
|
|
|
Explanation |
|
=========== |
|
|
|
>>> from sympy.assumptions.refine import refine_arg |
|
>>> from sympy import Q, arg |
|
>>> from sympy.abc import x |
|
>>> refine_arg(arg(x), Q.positive(x)) |
|
0 |
|
>>> refine_arg(arg(x), Q.negative(x)) |
|
pi |
|
""" |
|
rg = expr.args[0] |
|
if ask(Q.positive(rg), assumptions): |
|
return S.Zero |
|
if ask(Q.negative(rg), assumptions): |
|
return S.Pi |
|
return None |
|
|
|
|
|
def _refine_reim(expr, assumptions): |
|
|
|
expanded = expr.expand(complex = True) |
|
if expanded != expr: |
|
refined = refine(expanded, assumptions) |
|
if refined != expanded: |
|
return refined |
|
|
|
return None |
|
|
|
|
|
def refine_sign(expr, assumptions): |
|
""" |
|
Handler for sign. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.assumptions.refine import refine_sign |
|
>>> from sympy import Symbol, Q, sign, im |
|
>>> x = Symbol('x', real = True) |
|
>>> expr = sign(x) |
|
>>> refine_sign(expr, Q.positive(x) & Q.nonzero(x)) |
|
1 |
|
>>> refine_sign(expr, Q.negative(x) & Q.nonzero(x)) |
|
-1 |
|
>>> refine_sign(expr, Q.zero(x)) |
|
0 |
|
>>> y = Symbol('y', imaginary = True) |
|
>>> expr = sign(y) |
|
>>> refine_sign(expr, Q.positive(im(y))) |
|
I |
|
>>> refine_sign(expr, Q.negative(im(y))) |
|
-I |
|
""" |
|
arg = expr.args[0] |
|
if ask(Q.zero(arg), assumptions): |
|
return S.Zero |
|
if ask(Q.real(arg)): |
|
if ask(Q.positive(arg), assumptions): |
|
return S.One |
|
if ask(Q.negative(arg), assumptions): |
|
return S.NegativeOne |
|
if ask(Q.imaginary(arg)): |
|
arg_re, arg_im = arg.as_real_imag() |
|
if ask(Q.positive(arg_im), assumptions): |
|
return S.ImaginaryUnit |
|
if ask(Q.negative(arg_im), assumptions): |
|
return -S.ImaginaryUnit |
|
return expr |
|
|
|
|
|
def refine_matrixelement(expr, assumptions): |
|
""" |
|
Handler for symmetric part. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.assumptions.refine import refine_matrixelement |
|
>>> from sympy import MatrixSymbol, Q |
|
>>> X = MatrixSymbol('X', 3, 3) |
|
>>> refine_matrixelement(X[0, 1], Q.symmetric(X)) |
|
X[0, 1] |
|
>>> refine_matrixelement(X[1, 0], Q.symmetric(X)) |
|
X[0, 1] |
|
""" |
|
from sympy.matrices.expressions.matexpr import MatrixElement |
|
matrix, i, j = expr.args |
|
if ask(Q.symmetric(matrix), assumptions): |
|
if (i - j).could_extract_minus_sign(): |
|
return expr |
|
return MatrixElement(matrix, j, i) |
|
|
|
handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = { |
|
'Abs': refine_abs, |
|
'Pow': refine_Pow, |
|
'atan2': refine_atan2, |
|
're': refine_re, |
|
'im': refine_im, |
|
'arg': refine_arg, |
|
'sign': refine_sign, |
|
'MatrixElement': refine_matrixelement |
|
} |
|
|