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

1from __future__ import annotations 

2from typing import Callable 

3 

4from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational 

5from sympy.core.logic import fuzzy_not 

6from sympy.logic.boolalg import Boolean 

7 

8from sympy.assumptions import ask, Q # type: ignore 

9 

10 

11def refine(expr, assumptions=True): 

12 """ 

13 Simplify an expression using assumptions. 

14 

15 Explanation 

16 =========== 

17 

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. 

22 

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. 

26 

27 Examples 

28 ======== 

29 

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 

36 

37 >>> refine(Q.real(x), Q.positive(x)) 

38 True 

39 >>> refine(Q.positive(x), Q.real(x)) 

40 Q.positive(x) 

41 

42 See Also 

43 ======== 

44 

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 

50 

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) 

69 

70 

71def refine_abs(expr, assumptions): 

72 """ 

73 Handler for the absolute value. 

74 

75 Examples 

76 ======== 

77 

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 

86 

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)) 

107 

108 

109def refine_Pow(expr, assumptions): 

110 """ 

111 Handler for instances of Pow. 

112 

113 Examples 

114 ======== 

115 

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 

124 

125 For powers of -1, even parts of the exponent can be simplified: 

126 

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) 

135 

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) 

152 

153 if expr.base is S.NegativeOne: 

154 if expr.exp.is_Add: 

155 

156 old = expr 

157 

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) 

163 

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) 

169 

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) 

175 

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 

183 

184 if new_coeff != coeff or len(terms) < initial_number_of_terms: 

185 terms.add(new_coeff) 

186 expr = expr.base**(Add(*terms)) 

187 

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) 

204 

205 if old != expr: 

206 return expr 

207 

208 

209def refine_atan2(expr, assumptions): 

210 """ 

211 Handler for the atan2 function. 

212 

213 Examples 

214 ======== 

215 

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 

252 

253 

254def refine_re(expr, assumptions): 

255 """ 

256 Handler for real part. 

257 

258 Examples 

259 ======== 

260 

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) 

275 

276 

277def refine_im(expr, assumptions): 

278 """ 

279 Handler for imaginary part. 

280 

281 Explanation 

282 =========== 

283 

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) 

298 

299def refine_arg(expr, assumptions): 

300 """ 

301 Handler for complex argument 

302 

303 Explanation 

304 =========== 

305 

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 

320 

321 

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 

331 

332 

333def refine_sign(expr, assumptions): 

334 """ 

335 Handler for sign. 

336 

337 Examples 

338 ======== 

339 

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 

372 

373 

374def refine_matrixelement(expr, assumptions): 

375 """ 

376 Handler for symmetric part. 

377 

378 Examples 

379 ======== 

380 

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) 

395 

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}