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 # type: ignore 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] # TODO: this will probably not work with Integral or Polynomial 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)): # if it's nonnegative return arg if ask(Q.negative(arg), assumptions): return -arg # arg is Mul 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 # For powers of (-1) we can remove # - even terms # - pairs of odd terms # - a single odd term + 1 # - A numerical constant N can be replaced with mod(N,2) 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)) # Handle (-1)**((-1)**n/2 + m/2) 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): # Helper function for refine_re & refine_im expanded = expr.expand(complex = True) if expanded != expr: refined = refine(expanded, assumptions) if refined != expanded: return refined # Best to leave the expression as is 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 }