Coverage for /usr/lib/python3/dist-packages/sympy/assumptions/refine.py: 10%
165 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from __future__ import annotations
2from typing import Callable
4from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational
5from sympy.core.logic import fuzzy_not
6from sympy.logic.boolalg import Boolean
8from sympy.assumptions import ask, Q # type: ignore
11def refine(expr, assumptions=True):
12 """
13 Simplify an expression using assumptions.
15 Explanation
16 ===========
18 Unlike :func:`~.simplify()` which performs structural simplification
19 without any assumption, this function transforms the expression into
20 the form which is only valid under certain assumptions. Note that
21 ``simplify()`` is generally not done in refining process.
23 Refining boolean expression involves reducing it to ``S.true`` or
24 ``S.false``. Unlike :func:`~.ask()`, the expression will not be reduced
25 if the truth value cannot be determined.
27 Examples
28 ========
30 >>> from sympy import refine, sqrt, Q
31 >>> from sympy.abc import x
32 >>> refine(sqrt(x**2), Q.real(x))
33 Abs(x)
34 >>> refine(sqrt(x**2), Q.positive(x))
35 x
37 >>> refine(Q.real(x), Q.positive(x))
38 True
39 >>> refine(Q.positive(x), Q.real(x))
40 Q.positive(x)
42 See Also
43 ========
45 sympy.simplify.simplify.simplify : Structural simplification without assumptions.
46 sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
47 """
48 if not isinstance(expr, Basic):
49 return expr
51 if not expr.is_Atom:
52 args = [refine(arg, assumptions) for arg in expr.args]
53 # TODO: this will probably not work with Integral or Polynomial
54 expr = expr.func(*args)
55 if hasattr(expr, '_eval_refine'):
56 ref_expr = expr._eval_refine(assumptions)
57 if ref_expr is not None:
58 return ref_expr
59 name = expr.__class__.__name__
60 handler = handlers_dict.get(name, None)
61 if handler is None:
62 return expr
63 new_expr = handler(expr, assumptions)
64 if (new_expr is None) or (expr == new_expr):
65 return expr
66 if not isinstance(new_expr, Expr):
67 return new_expr
68 return refine(new_expr, assumptions)
71def refine_abs(expr, assumptions):
72 """
73 Handler for the absolute value.
75 Examples
76 ========
78 >>> from sympy import Q, Abs
79 >>> from sympy.assumptions.refine import refine_abs
80 >>> from sympy.abc import x
81 >>> refine_abs(Abs(x), Q.real(x))
82 >>> refine_abs(Abs(x), Q.positive(x))
83 x
84 >>> refine_abs(Abs(x), Q.negative(x))
85 -x
87 """
88 from sympy.functions.elementary.complexes import Abs
89 arg = expr.args[0]
90 if ask(Q.real(arg), assumptions) and \
91 fuzzy_not(ask(Q.negative(arg), assumptions)):
92 # if it's nonnegative
93 return arg
94 if ask(Q.negative(arg), assumptions):
95 return -arg
96 # arg is Mul
97 if isinstance(arg, Mul):
98 r = [refine(abs(a), assumptions) for a in arg.args]
99 non_abs = []
100 in_abs = []
101 for i in r:
102 if isinstance(i, Abs):
103 in_abs.append(i.args[0])
104 else:
105 non_abs.append(i)
106 return Mul(*non_abs) * Abs(Mul(*in_abs))
109def refine_Pow(expr, assumptions):
110 """
111 Handler for instances of Pow.
113 Examples
114 ========
116 >>> from sympy import Q
117 >>> from sympy.assumptions.refine import refine_Pow
118 >>> from sympy.abc import x,y,z
119 >>> refine_Pow((-1)**x, Q.real(x))
120 >>> refine_Pow((-1)**x, Q.even(x))
121 1
122 >>> refine_Pow((-1)**x, Q.odd(x))
123 -1
125 For powers of -1, even parts of the exponent can be simplified:
127 >>> refine_Pow((-1)**(x+y), Q.even(x))
128 (-1)**y
129 >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
130 (-1)**y
131 >>> refine_Pow((-1)**(x+y+2), Q.odd(x))
132 (-1)**(y + 1)
133 >>> refine_Pow((-1)**(x+3), True)
134 (-1)**(x + 1)
136 """
137 from sympy.functions.elementary.complexes import Abs
138 from sympy.functions import sign
139 if isinstance(expr.base, Abs):
140 if ask(Q.real(expr.base.args[0]), assumptions) and \
141 ask(Q.even(expr.exp), assumptions):
142 return expr.base.args[0] ** expr.exp
143 if ask(Q.real(expr.base), assumptions):
144 if expr.base.is_number:
145 if ask(Q.even(expr.exp), assumptions):
146 return abs(expr.base) ** expr.exp
147 if ask(Q.odd(expr.exp), assumptions):
148 return sign(expr.base) * abs(expr.base) ** expr.exp
149 if isinstance(expr.exp, Rational):
150 if isinstance(expr.base, Pow):
151 return abs(expr.base.base) ** (expr.base.exp * expr.exp)
153 if expr.base is S.NegativeOne:
154 if expr.exp.is_Add:
156 old = expr
158 # For powers of (-1) we can remove
159 # - even terms
160 # - pairs of odd terms
161 # - a single odd term + 1
162 # - A numerical constant N can be replaced with mod(N,2)
164 coeff, terms = expr.exp.as_coeff_add()
165 terms = set(terms)
166 even_terms = set()
167 odd_terms = set()
168 initial_number_of_terms = len(terms)
170 for t in terms:
171 if ask(Q.even(t), assumptions):
172 even_terms.add(t)
173 elif ask(Q.odd(t), assumptions):
174 odd_terms.add(t)
176 terms -= even_terms
177 if len(odd_terms) % 2:
178 terms -= odd_terms
179 new_coeff = (coeff + S.One) % 2
180 else:
181 terms -= odd_terms
182 new_coeff = coeff % 2
184 if new_coeff != coeff or len(terms) < initial_number_of_terms:
185 terms.add(new_coeff)
186 expr = expr.base**(Add(*terms))
188 # Handle (-1)**((-1)**n/2 + m/2)
189 e2 = 2*expr.exp
190 if ask(Q.even(e2), assumptions):
191 if e2.could_extract_minus_sign():
192 e2 *= expr.base
193 if e2.is_Add:
194 i, p = e2.as_two_terms()
195 if p.is_Pow and p.base is S.NegativeOne:
196 if ask(Q.integer(p.exp), assumptions):
197 i = (i + 1)/2
198 if ask(Q.even(i), assumptions):
199 return expr.base**p.exp
200 elif ask(Q.odd(i), assumptions):
201 return expr.base**(p.exp + 1)
202 else:
203 return expr.base**(p.exp + i)
205 if old != expr:
206 return expr
209def refine_atan2(expr, assumptions):
210 """
211 Handler for the atan2 function.
213 Examples
214 ========
216 >>> from sympy import Q, atan2
217 >>> from sympy.assumptions.refine import refine_atan2
218 >>> from sympy.abc import x, y
219 >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
220 atan(y/x)
221 >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
222 atan(y/x) - pi
223 >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
224 atan(y/x) + pi
225 >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
226 pi
227 >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
228 pi/2
229 >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
230 -pi/2
231 >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
232 nan
233 """
234 from sympy.functions.elementary.trigonometric import atan
235 y, x = expr.args
236 if ask(Q.real(y) & Q.positive(x), assumptions):
237 return atan(y / x)
238 elif ask(Q.negative(y) & Q.negative(x), assumptions):
239 return atan(y / x) - S.Pi
240 elif ask(Q.positive(y) & Q.negative(x), assumptions):
241 return atan(y / x) + S.Pi
242 elif ask(Q.zero(y) & Q.negative(x), assumptions):
243 return S.Pi
244 elif ask(Q.positive(y) & Q.zero(x), assumptions):
245 return S.Pi/2
246 elif ask(Q.negative(y) & Q.zero(x), assumptions):
247 return -S.Pi/2
248 elif ask(Q.zero(y) & Q.zero(x), assumptions):
249 return S.NaN
250 else:
251 return expr
254def refine_re(expr, assumptions):
255 """
256 Handler for real part.
258 Examples
259 ========
261 >>> from sympy.assumptions.refine import refine_re
262 >>> from sympy import Q, re
263 >>> from sympy.abc import x
264 >>> refine_re(re(x), Q.real(x))
265 x
266 >>> refine_re(re(x), Q.imaginary(x))
267 0
268 """
269 arg = expr.args[0]
270 if ask(Q.real(arg), assumptions):
271 return arg
272 if ask(Q.imaginary(arg), assumptions):
273 return S.Zero
274 return _refine_reim(expr, assumptions)
277def refine_im(expr, assumptions):
278 """
279 Handler for imaginary part.
281 Explanation
282 ===========
284 >>> from sympy.assumptions.refine import refine_im
285 >>> from sympy import Q, im
286 >>> from sympy.abc import x
287 >>> refine_im(im(x), Q.real(x))
288 0
289 >>> refine_im(im(x), Q.imaginary(x))
290 -I*x
291 """
292 arg = expr.args[0]
293 if ask(Q.real(arg), assumptions):
294 return S.Zero
295 if ask(Q.imaginary(arg), assumptions):
296 return - S.ImaginaryUnit * arg
297 return _refine_reim(expr, assumptions)
299def refine_arg(expr, assumptions):
300 """
301 Handler for complex argument
303 Explanation
304 ===========
306 >>> from sympy.assumptions.refine import refine_arg
307 >>> from sympy import Q, arg
308 >>> from sympy.abc import x
309 >>> refine_arg(arg(x), Q.positive(x))
310 0
311 >>> refine_arg(arg(x), Q.negative(x))
312 pi
313 """
314 rg = expr.args[0]
315 if ask(Q.positive(rg), assumptions):
316 return S.Zero
317 if ask(Q.negative(rg), assumptions):
318 return S.Pi
319 return None
322def _refine_reim(expr, assumptions):
323 # Helper function for refine_re & refine_im
324 expanded = expr.expand(complex = True)
325 if expanded != expr:
326 refined = refine(expanded, assumptions)
327 if refined != expanded:
328 return refined
329 # Best to leave the expression as is
330 return None
333def refine_sign(expr, assumptions):
334 """
335 Handler for sign.
337 Examples
338 ========
340 >>> from sympy.assumptions.refine import refine_sign
341 >>> from sympy import Symbol, Q, sign, im
342 >>> x = Symbol('x', real = True)
343 >>> expr = sign(x)
344 >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
345 1
346 >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
347 -1
348 >>> refine_sign(expr, Q.zero(x))
349 0
350 >>> y = Symbol('y', imaginary = True)
351 >>> expr = sign(y)
352 >>> refine_sign(expr, Q.positive(im(y)))
353 I
354 >>> refine_sign(expr, Q.negative(im(y)))
355 -I
356 """
357 arg = expr.args[0]
358 if ask(Q.zero(arg), assumptions):
359 return S.Zero
360 if ask(Q.real(arg)):
361 if ask(Q.positive(arg), assumptions):
362 return S.One
363 if ask(Q.negative(arg), assumptions):
364 return S.NegativeOne
365 if ask(Q.imaginary(arg)):
366 arg_re, arg_im = arg.as_real_imag()
367 if ask(Q.positive(arg_im), assumptions):
368 return S.ImaginaryUnit
369 if ask(Q.negative(arg_im), assumptions):
370 return -S.ImaginaryUnit
371 return expr
374def refine_matrixelement(expr, assumptions):
375 """
376 Handler for symmetric part.
378 Examples
379 ========
381 >>> from sympy.assumptions.refine import refine_matrixelement
382 >>> from sympy import MatrixSymbol, Q
383 >>> X = MatrixSymbol('X', 3, 3)
384 >>> refine_matrixelement(X[0, 1], Q.symmetric(X))
385 X[0, 1]
386 >>> refine_matrixelement(X[1, 0], Q.symmetric(X))
387 X[0, 1]
388 """
389 from sympy.matrices.expressions.matexpr import MatrixElement
390 matrix, i, j = expr.args
391 if ask(Q.symmetric(matrix), assumptions):
392 if (i - j).could_extract_minus_sign():
393 return expr
394 return MatrixElement(matrix, j, i)
396handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = {
397 'Abs': refine_abs,
398 'Pow': refine_Pow,
399 'atan2': refine_atan2,
400 're': refine_re,
401 'im': refine_im,
402 'arg': refine_arg,
403 'sign': refine_sign,
404 'MatrixElement': refine_matrixelement
405}