Coverage for /usr/lib/python3/dist-packages/sympy/core/assumptions.py: 69%

156 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1""" 

2This module contains the machinery handling assumptions. 

3Do also consider the guide :ref:`assumptions-guide`. 

4 

5All symbolic objects have assumption attributes that can be accessed via 

6``.is_<assumption name>`` attribute. 

7 

8Assumptions determine certain properties of symbolic objects and can 

9have 3 possible values: ``True``, ``False``, ``None``. ``True`` is returned if the 

10object has the property and ``False`` is returned if it does not or cannot 

11(i.e. does not make sense): 

12 

13 >>> from sympy import I 

14 >>> I.is_algebraic 

15 True 

16 >>> I.is_real 

17 False 

18 >>> I.is_prime 

19 False 

20 

21When the property cannot be determined (or when a method is not 

22implemented) ``None`` will be returned. For example, a generic symbol, ``x``, 

23may or may not be positive so a value of ``None`` is returned for ``x.is_positive``. 

24 

25By default, all symbolic values are in the largest set in the given context 

26without specifying the property. For example, a symbol that has a property 

27being integer, is also real, complex, etc. 

28 

29Here follows a list of possible assumption names: 

30 

31.. glossary:: 

32 

33 commutative 

34 object commutes with any other object with 

35 respect to multiplication operation. See [12]_. 

36 

37 complex 

38 object can have only values from the set 

39 of complex numbers. See [13]_. 

40 

41 imaginary 

42 object value is a number that can be written as a real 

43 number multiplied by the imaginary unit ``I``. See 

44 [3]_. Please note that ``0`` is not considered to be an 

45 imaginary number, see 

46 `issue #7649 <https://github.com/sympy/sympy/issues/7649>`_. 

47 

48 real 

49 object can have only values from the set 

50 of real numbers. 

51 

52 extended_real 

53 object can have only values from the set 

54 of real numbers, ``oo`` and ``-oo``. 

55 

56 integer 

57 object can have only values from the set 

58 of integers. 

59 

60 odd 

61 even 

62 object can have only values from the set of 

63 odd (even) integers [2]_. 

64 

65 prime 

66 object is a natural number greater than 1 that has 

67 no positive divisors other than 1 and itself. See [6]_. 

68 

69 composite 

70 object is a positive integer that has at least one positive 

71 divisor other than 1 or the number itself. See [4]_. 

72 

73 zero 

74 object has the value of 0. 

75 

76 nonzero 

77 object is a real number that is not zero. 

78 

79 rational 

80 object can have only values from the set 

81 of rationals. 

82 

83 algebraic 

84 object can have only values from the set 

85 of algebraic numbers [11]_. 

86 

87 transcendental 

88 object can have only values from the set 

89 of transcendental numbers [10]_. 

90 

91 irrational 

92 object value cannot be represented exactly by :class:`~.Rational`, see [5]_. 

93 

94 finite 

95 infinite 

96 object absolute value is bounded (arbitrarily large). 

97 See [7]_, [8]_, [9]_. 

98 

99 negative 

100 nonnegative 

101 object can have only negative (nonnegative) 

102 values [1]_. 

103 

104 positive 

105 nonpositive 

106 object can have only positive (nonpositive) values. 

107 

108 extended_negative 

109 extended_nonnegative 

110 extended_positive 

111 extended_nonpositive 

112 extended_nonzero 

113 as without the extended part, but also including infinity with 

114 corresponding sign, e.g., extended_positive includes ``oo`` 

115 

116 hermitian 

117 antihermitian 

118 object belongs to the field of Hermitian 

119 (antihermitian) operators. 

120 

121Examples 

122======== 

123 

124 >>> from sympy import Symbol 

125 >>> x = Symbol('x', real=True); x 

126 x 

127 >>> x.is_real 

128 True 

129 >>> x.is_complex 

130 True 

131 

132See Also 

133======== 

134 

135.. seealso:: 

136 

137 :py:class:`sympy.core.numbers.ImaginaryUnit` 

138 :py:class:`sympy.core.numbers.Zero` 

139 :py:class:`sympy.core.numbers.One` 

140 :py:class:`sympy.core.numbers.Infinity` 

141 :py:class:`sympy.core.numbers.NegativeInfinity` 

142 :py:class:`sympy.core.numbers.ComplexInfinity` 

143 

144Notes 

145===== 

146 

147The fully-resolved assumptions for any SymPy expression 

148can be obtained as follows: 

149 

150 >>> from sympy.core.assumptions import assumptions 

151 >>> x = Symbol('x',positive=True) 

152 >>> assumptions(x + I) 

153 {'commutative': True, 'complex': True, 'composite': False, 'even': 

154 False, 'extended_negative': False, 'extended_nonnegative': False, 

155 'extended_nonpositive': False, 'extended_nonzero': False, 

156 'extended_positive': False, 'extended_real': False, 'finite': True, 

157 'imaginary': False, 'infinite': False, 'integer': False, 'irrational': 

158 False, 'negative': False, 'noninteger': False, 'nonnegative': False, 

159 'nonpositive': False, 'nonzero': False, 'odd': False, 'positive': 

160 False, 'prime': False, 'rational': False, 'real': False, 'zero': 

161 False} 

162 

163Developers Notes 

164================ 

165 

166The current (and possibly incomplete) values are stored 

167in the ``obj._assumptions dictionary``; queries to getter methods 

168(with property decorators) or attributes of objects/classes 

169will return values and update the dictionary. 

170 

171 >>> eq = x**2 + I 

172 >>> eq._assumptions 

173 {} 

174 >>> eq.is_finite 

175 True 

176 >>> eq._assumptions 

177 {'finite': True, 'infinite': False} 

178 

179For a :class:`~.Symbol`, there are two locations for assumptions that may 

180be of interest. The ``assumptions0`` attribute gives the full set of 

181assumptions derived from a given set of initial assumptions. The 

182latter assumptions are stored as ``Symbol._assumptions_orig`` 

183 

184 >>> Symbol('x', prime=True, even=True)._assumptions_orig 

185 {'even': True, 'prime': True} 

186 

187The ``_assumptions_orig`` are not necessarily canonical nor are they filtered 

188in any way: they records the assumptions used to instantiate a Symbol and (for 

189storage purposes) represent a more compact representation of the assumptions 

190needed to recreate the full set in ``Symbol.assumptions0``. 

191 

192 

193References 

194========== 

195 

196.. [1] https://en.wikipedia.org/wiki/Negative_number 

197.. [2] https://en.wikipedia.org/wiki/Parity_%28mathematics%29 

198.. [3] https://en.wikipedia.org/wiki/Imaginary_number 

199.. [4] https://en.wikipedia.org/wiki/Composite_number 

200.. [5] https://en.wikipedia.org/wiki/Irrational_number 

201.. [6] https://en.wikipedia.org/wiki/Prime_number 

202.. [7] https://en.wikipedia.org/wiki/Finite 

203.. [8] https://docs.python.org/3/library/math.html#math.isfinite 

204.. [9] https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html 

205.. [10] https://en.wikipedia.org/wiki/Transcendental_number 

206.. [11] https://en.wikipedia.org/wiki/Algebraic_number 

207.. [12] https://en.wikipedia.org/wiki/Commutative_property 

208.. [13] https://en.wikipedia.org/wiki/Complex_number 

209 

210""" 

211 

212from sympy.utilities.exceptions import sympy_deprecation_warning 

213 

214from .facts import FactRules, FactKB 

215from .sympify import sympify 

216 

217from sympy.core.random import _assumptions_shuffle as shuffle 

218from sympy.core.assumptions_generated import generated_assumptions as _assumptions 

219 

220def _load_pre_generated_assumption_rules(): 

221 """ Load the assumption rules from pre-generated data 

222 

223 To update the pre-generated data, see :method::`_generate_assumption_rules` 

224 """ 

225 _assume_rules=FactRules._from_python(_assumptions) 

226 return _assume_rules 

227 

228def _generate_assumption_rules(): 

229 """ Generate the default assumption rules 

230 

231 This method should only be called to update the pre-generated 

232 assumption rules. 

233 

234 To update the pre-generated assumptions run: bin/ask_update.py 

235 

236 """ 

237 _assume_rules = FactRules([ 

238 

239 'integer -> rational', 

240 'rational -> real', 

241 'rational -> algebraic', 

242 'algebraic -> complex', 

243 'transcendental == complex & !algebraic', 

244 'real -> hermitian', 

245 'imaginary -> complex', 

246 'imaginary -> antihermitian', 

247 'extended_real -> commutative', 

248 'complex -> commutative', 

249 'complex -> finite', 

250 

251 'odd == integer & !even', 

252 'even == integer & !odd', 

253 

254 'real -> complex', 

255 'extended_real -> real | infinite', 

256 'real == extended_real & finite', 

257 

258 'extended_real == extended_negative | zero | extended_positive', 

259 'extended_negative == extended_nonpositive & extended_nonzero', 

260 'extended_positive == extended_nonnegative & extended_nonzero', 

261 

262 'extended_nonpositive == extended_real & !extended_positive', 

263 'extended_nonnegative == extended_real & !extended_negative', 

264 

265 'real == negative | zero | positive', 

266 'negative == nonpositive & nonzero', 

267 'positive == nonnegative & nonzero', 

268 

269 'nonpositive == real & !positive', 

270 'nonnegative == real & !negative', 

271 

272 'positive == extended_positive & finite', 

273 'negative == extended_negative & finite', 

274 'nonpositive == extended_nonpositive & finite', 

275 'nonnegative == extended_nonnegative & finite', 

276 'nonzero == extended_nonzero & finite', 

277 

278 'zero -> even & finite', 

279 'zero == extended_nonnegative & extended_nonpositive', 

280 'zero == nonnegative & nonpositive', 

281 'nonzero -> real', 

282 

283 'prime -> integer & positive', 

284 'composite -> integer & positive & !prime', 

285 '!composite -> !positive | !even | prime', 

286 

287 'irrational == real & !rational', 

288 

289 'imaginary -> !extended_real', 

290 

291 'infinite == !finite', 

292 'noninteger == extended_real & !integer', 

293 'extended_nonzero == extended_real & !zero', 

294 ]) 

295 return _assume_rules 

296 

297 

298_assume_rules = _load_pre_generated_assumption_rules() 

299_assume_defined = _assume_rules.defined_facts.copy() 

300_assume_defined.add('polar') 

301_assume_defined = frozenset(_assume_defined) 

302 

303 

304def assumptions(expr, _check=None): 

305 """return the T/F assumptions of ``expr``""" 

306 n = sympify(expr) 

307 if n.is_Symbol: 

308 rv = n.assumptions0 # are any important ones missing? 

309 if _check is not None: 

310 rv = {k: rv[k] for k in set(rv) & set(_check)} 

311 return rv 

312 rv = {} 

313 for k in _assume_defined if _check is None else _check: 

314 v = getattr(n, 'is_{}'.format(k)) 

315 if v is not None: 

316 rv[k] = v 

317 return rv 

318 

319 

320def common_assumptions(exprs, check=None): 

321 """return those assumptions which have the same True or False 

322 value for all the given expressions. 

323 

324 Examples 

325 ======== 

326 

327 >>> from sympy.core import common_assumptions 

328 >>> from sympy import oo, pi, sqrt 

329 >>> common_assumptions([-4, 0, sqrt(2), 2, pi, oo]) 

330 {'commutative': True, 'composite': False, 

331 'extended_real': True, 'imaginary': False, 'odd': False} 

332 

333 By default, all assumptions are tested; pass an iterable of the 

334 assumptions to limit those that are reported: 

335 

336 >>> common_assumptions([0, 1, 2], ['positive', 'integer']) 

337 {'integer': True} 

338 """ 

339 check = _assume_defined if check is None else set(check) 

340 if not check or not exprs: 

341 return {} 

342 

343 # get all assumptions for each 

344 assume = [assumptions(i, _check=check) for i in sympify(exprs)] 

345 # focus on those of interest that are True 

346 for i, e in enumerate(assume): 

347 assume[i] = {k: e[k] for k in set(e) & check} 

348 # what assumptions are in common? 

349 common = set.intersection(*[set(i) for i in assume]) 

350 # which ones hold the same value 

351 a = assume[0] 

352 return {k: a[k] for k in common if all(a[k] == b[k] 

353 for b in assume)} 

354 

355 

356def failing_assumptions(expr, **assumptions): 

357 """ 

358 Return a dictionary containing assumptions with values not 

359 matching those of the passed assumptions. 

360 

361 Examples 

362 ======== 

363 

364 >>> from sympy import failing_assumptions, Symbol 

365 

366 >>> x = Symbol('x', positive=True) 

367 >>> y = Symbol('y') 

368 >>> failing_assumptions(6*x + y, positive=True) 

369 {'positive': None} 

370 

371 >>> failing_assumptions(x**2 - 1, positive=True) 

372 {'positive': None} 

373 

374 If *expr* satisfies all of the assumptions, an empty dictionary is returned. 

375 

376 >>> failing_assumptions(x**2, positive=True) 

377 {} 

378 

379 """ 

380 expr = sympify(expr) 

381 failed = {} 

382 for k in assumptions: 

383 test = getattr(expr, 'is_%s' % k, None) 

384 if test is not assumptions[k]: 

385 failed[k] = test 

386 return failed # {} or {assumption: value != desired} 

387 

388 

389def check_assumptions(expr, against=None, **assume): 

390 """ 

391 Checks whether assumptions of ``expr`` match the T/F assumptions 

392 given (or possessed by ``against``). True is returned if all 

393 assumptions match; False is returned if there is a mismatch and 

394 the assumption in ``expr`` is not None; else None is returned. 

395 

396 Explanation 

397 =========== 

398 

399 *assume* is a dict of assumptions with True or False values 

400 

401 Examples 

402 ======== 

403 

404 >>> from sympy import Symbol, pi, I, exp, check_assumptions 

405 >>> check_assumptions(-5, integer=True) 

406 True 

407 >>> check_assumptions(pi, real=True, integer=False) 

408 True 

409 >>> check_assumptions(pi, negative=True) 

410 False 

411 >>> check_assumptions(exp(I*pi/7), real=False) 

412 True 

413 >>> x = Symbol('x', positive=True) 

414 >>> check_assumptions(2*x + 1, positive=True) 

415 True 

416 >>> check_assumptions(-2*x - 5, positive=True) 

417 False 

418 

419 To check assumptions of *expr* against another variable or expression, 

420 pass the expression or variable as ``against``. 

421 

422 >>> check_assumptions(2*x + 1, x) 

423 True 

424 

425 To see if a number matches the assumptions of an expression, pass 

426 the number as the first argument, else its specific assumptions 

427 may not have a non-None value in the expression: 

428 

429 >>> check_assumptions(x, 3) 

430 >>> check_assumptions(3, x) 

431 True 

432 

433 ``None`` is returned if ``check_assumptions()`` could not conclude. 

434 

435 >>> check_assumptions(2*x - 1, x) 

436 

437 >>> z = Symbol('z') 

438 >>> check_assumptions(z, real=True) 

439 

440 See Also 

441 ======== 

442 

443 failing_assumptions 

444 

445 """ 

446 expr = sympify(expr) 

447 if against is not None: 

448 if assume: 

449 raise ValueError( 

450 'Expecting `against` or `assume`, not both.') 

451 assume = assumptions(against) 

452 known = True 

453 for k, v in assume.items(): 

454 if v is None: 

455 continue 

456 e = getattr(expr, 'is_' + k, None) 

457 if e is None: 

458 known = None 

459 elif v != e: 

460 return False 

461 return known 

462 

463 

464class StdFactKB(FactKB): 

465 """A FactKB specialized for the built-in rules 

466 

467 This is the only kind of FactKB that Basic objects should use. 

468 """ 

469 def __init__(self, facts=None): 

470 super().__init__(_assume_rules) 

471 # save a copy of the facts dict 

472 if not facts: 

473 self._generator = {} 

474 elif not isinstance(facts, FactKB): 

475 self._generator = facts.copy() 

476 else: 

477 self._generator = facts.generator 

478 if facts: 

479 self.deduce_all_facts(facts) 

480 

481 def copy(self): 

482 return self.__class__(self) 

483 

484 @property 

485 def generator(self): 

486 return self._generator.copy() 

487 

488 

489def as_property(fact): 

490 """Convert a fact name to the name of the corresponding property""" 

491 return 'is_%s' % fact 

492 

493 

494def make_property(fact): 

495 """Create the automagic property corresponding to a fact.""" 

496 

497 def getit(self): 

498 try: 

499 return self._assumptions[fact] 

500 except KeyError: 

501 if self._assumptions is self.default_assumptions: 

502 self._assumptions = self.default_assumptions.copy() 

503 return _ask(fact, self) 

504 

505 getit.func_name = as_property(fact) 

506 return property(getit) 

507 

508 

509def _ask(fact, obj): 

510 """ 

511 Find the truth value for a property of an object. 

512 

513 This function is called when a request is made to see what a fact 

514 value is. 

515 

516 For this we use several techniques: 

517 

518 First, the fact-evaluation function is tried, if it exists (for 

519 example _eval_is_integer). Then we try related facts. For example 

520 

521 rational --> integer 

522 

523 another example is joined rule: 

524 

525 integer & !odd --> even 

526 

527 so in the latter case if we are looking at what 'even' value is, 

528 'integer' and 'odd' facts will be asked. 

529 

530 In all cases, when we settle on some fact value, its implications are 

531 deduced, and the result is cached in ._assumptions. 

532 """ 

533 # FactKB which is dict-like and maps facts to their known values: 

534 assumptions = obj._assumptions 

535 

536 # A dict that maps facts to their handlers: 

537 handler_map = obj._prop_handler 

538 

539 # This is our queue of facts to check: 

540 facts_to_check = [fact] 

541 facts_queued = {fact} 

542 

543 # Loop over the queue as it extends 

544 for fact_i in facts_to_check: 

545 

546 # If fact_i has already been determined then we don't need to rerun the 

547 # handler. There is a potential race condition for multithreaded code 

548 # though because it's possible that fact_i was checked in another 

549 # thread. The main logic of the loop below would potentially skip 

550 # checking assumptions[fact] in this case so we check it once after the 

551 # loop to be sure. 

552 if fact_i in assumptions: 

553 continue 

554 

555 # Now we call the associated handler for fact_i if it exists. 

556 fact_i_value = None 

557 handler_i = handler_map.get(fact_i) 

558 if handler_i is not None: 

559 fact_i_value = handler_i(obj) 

560 

561 # If we get a new value for fact_i then we should update our knowledge 

562 # of fact_i as well as any related facts that can be inferred using the 

563 # inference rules connecting the fact_i and any other fact values that 

564 # are already known. 

565 if fact_i_value is not None: 

566 assumptions.deduce_all_facts(((fact_i, fact_i_value),)) 

567 

568 # Usually if assumptions[fact] is now not None then that is because of 

569 # the call to deduce_all_facts above. The handler for fact_i returned 

570 # True or False and knowing fact_i (which is equal to fact in the first 

571 # iteration) implies knowing a value for fact. It is also possible 

572 # though that independent code e.g. called indirectly by the handler or 

573 # called in another thread in a multithreaded context might have 

574 # resulted in assumptions[fact] being set. Either way we return it. 

575 fact_value = assumptions.get(fact) 

576 if fact_value is not None: 

577 return fact_value 

578 

579 # Extend the queue with other facts that might determine fact_i. Here 

580 # we randomise the order of the facts that are checked. This should not 

581 # lead to any non-determinism if all handlers are logically consistent 

582 # with the inference rules for the facts. Non-deterministic assumptions 

583 # queries can result from bugs in the handlers that are exposed by this 

584 # call to shuffle. These are pushed to the back of the queue meaning 

585 # that the inference graph is traversed in breadth-first order. 

586 new_facts_to_check = list(_assume_rules.prereq[fact_i] - facts_queued) 

587 shuffle(new_facts_to_check) 

588 facts_to_check.extend(new_facts_to_check) 

589 facts_queued.update(new_facts_to_check) 

590 

591 # The above loop should be able to handle everything fine in a 

592 # single-threaded context but in multithreaded code it is possible that 

593 # this thread skipped computing a particular fact that was computed in 

594 # another thread (due to the continue). In that case it is possible that 

595 # fact was inferred and is now stored in the assumptions dict but it wasn't 

596 # checked for in the body of the loop. This is an obscure case but to make 

597 # sure we catch it we check once here at the end of the loop. 

598 if fact in assumptions: 

599 return assumptions[fact] 

600 

601 # This query can not be answered. It's possible that e.g. another thread 

602 # has already stored None for fact but assumptions._tell does not mind if 

603 # we call _tell twice setting the same value. If this raises 

604 # InconsistentAssumptions then it probably means that another thread 

605 # attempted to compute this and got a value of True or False rather than 

606 # None. In that case there must be a bug in at least one of the handlers. 

607 # If the handlers are all deterministic and are consistent with the 

608 # inference rules then the same value should be computed for fact in all 

609 # threads. 

610 assumptions._tell(fact, None) 

611 return None 

612 

613 

614def _prepare_class_assumptions(cls): 

615 """Precompute class level assumptions and generate handlers. 

616 

617 This is called by Basic.__init_subclass__ each time a Basic subclass is 

618 defined. 

619 """ 

620 

621 local_defs = {} 

622 for k in _assume_defined: 

623 attrname = as_property(k) 

624 v = cls.__dict__.get(attrname, '') 

625 if isinstance(v, (bool, int, type(None))): 

626 if v is not None: 

627 v = bool(v) 

628 local_defs[k] = v 

629 

630 defs = {} 

631 for base in reversed(cls.__bases__): 

632 assumptions = getattr(base, '_explicit_class_assumptions', None) 

633 if assumptions is not None: 

634 defs.update(assumptions) 

635 defs.update(local_defs) 

636 

637 cls._explicit_class_assumptions = defs 

638 cls.default_assumptions = StdFactKB(defs) 

639 

640 cls._prop_handler = {} 

641 for k in _assume_defined: 

642 eval_is_meth = getattr(cls, '_eval_is_%s' % k, None) 

643 if eval_is_meth is not None: 

644 cls._prop_handler[k] = eval_is_meth 

645 

646 # Put definite results directly into the class dict, for speed 

647 for k, v in cls.default_assumptions.items(): 

648 setattr(cls, as_property(k), v) 

649 

650 # protection e.g. for Integer.is_even=F <- (Rational.is_integer=F) 

651 derived_from_bases = set() 

652 for base in cls.__bases__: 

653 default_assumptions = getattr(base, 'default_assumptions', None) 

654 # is an assumption-aware class 

655 if default_assumptions is not None: 

656 derived_from_bases.update(default_assumptions) 

657 

658 for fact in derived_from_bases - set(cls.default_assumptions): 

659 pname = as_property(fact) 

660 if pname not in cls.__dict__: 

661 setattr(cls, pname, make_property(fact)) 

662 

663 # Finally, add any missing automagic property (e.g. for Basic) 

664 for fact in _assume_defined: 

665 pname = as_property(fact) 

666 if not hasattr(cls, pname): 

667 setattr(cls, pname, make_property(fact)) 

668 

669 

670# XXX: ManagedProperties used to be the metaclass for Basic but now Basic does 

671# not use a metaclass. We leave this here for backwards compatibility for now 

672# in case someone has been using the ManagedProperties class in downstream 

673# code. The reason that it might have been used is that when subclassing a 

674# class and wanting to use a metaclass the metaclass must be a subclass of the 

675# metaclass for the class that is being subclassed. Anyone wanting to subclass 

676# Basic and use a metaclass in their subclass would have needed to subclass 

677# ManagedProperties. Here ManagedProperties is not the metaclass for Basic any 

678# more but it should still be usable as a metaclass for Basic subclasses since 

679# it is a subclass of type which is now the metaclass for Basic. 

680class ManagedProperties(type): 

681 def __init__(cls, *args, **kwargs): 

682 msg = ("The ManagedProperties metaclass. " 

683 "Basic does not use metaclasses any more") 

684 sympy_deprecation_warning(msg, 

685 deprecated_since_version="1.12", 

686 active_deprecations_target='managedproperties') 

687 

688 # Here we still call this function in case someone is using 

689 # ManagedProperties for something that is not a Basic subclass. For 

690 # Basic subclasses this function is now called by __init_subclass__ and 

691 # so this metaclass is not needed any more. 

692 _prepare_class_assumptions(cls)