123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- """
- This module contains the machinery handling assumptions.
- Do also consider the guide :ref:`assumptions-guide`.
- All symbolic objects have assumption attributes that can be accessed via
- ``.is_<assumption name>`` attribute.
- Assumptions determine certain properties of symbolic objects and can
- have 3 possible values: ``True``, ``False``, ``None``. ``True`` is returned if the
- object has the property and ``False`` is returned if it does not or cannot
- (i.e. does not make sense):
- >>> from sympy import I
- >>> I.is_algebraic
- True
- >>> I.is_real
- False
- >>> I.is_prime
- False
- When the property cannot be determined (or when a method is not
- implemented) ``None`` will be returned. For example, a generic symbol, ``x``,
- may or may not be positive so a value of ``None`` is returned for ``x.is_positive``.
- By default, all symbolic values are in the largest set in the given context
- without specifying the property. For example, a symbol that has a property
- being integer, is also real, complex, etc.
- Here follows a list of possible assumption names:
- .. glossary::
- commutative
- object commutes with any other object with
- respect to multiplication operation. See [12]_.
- complex
- object can have only values from the set
- of complex numbers. See [13]_.
- imaginary
- object value is a number that can be written as a real
- number multiplied by the imaginary unit ``I``. See
- [3]_. Please note that ``0`` is not considered to be an
- imaginary number, see
- `issue #7649 <https://github.com/sympy/sympy/issues/7649>`_.
- real
- object can have only values from the set
- of real numbers.
- extended_real
- object can have only values from the set
- of real numbers, ``oo`` and ``-oo``.
- integer
- object can have only values from the set
- of integers.
- odd
- even
- object can have only values from the set of
- odd (even) integers [2]_.
- prime
- object is a natural number greater than 1 that has
- no positive divisors other than 1 and itself. See [6]_.
- composite
- object is a positive integer that has at least one positive
- divisor other than 1 or the number itself. See [4]_.
- zero
- object has the value of 0.
- nonzero
- object is a real number that is not zero.
- rational
- object can have only values from the set
- of rationals.
- algebraic
- object can have only values from the set
- of algebraic numbers [11]_.
- transcendental
- object can have only values from the set
- of transcendental numbers [10]_.
- irrational
- object value cannot be represented exactly by :class:`~.Rational`, see [5]_.
- finite
- infinite
- object absolute value is bounded (arbitrarily large).
- See [7]_, [8]_, [9]_.
- negative
- nonnegative
- object can have only negative (nonnegative)
- values [1]_.
- positive
- nonpositive
- object can have only positive (nonpositive) values.
- extended_negative
- extended_nonnegative
- extended_positive
- extended_nonpositive
- extended_nonzero
- as without the extended part, but also including infinity with
- corresponding sign, e.g., extended_positive includes ``oo``
- hermitian
- antihermitian
- object belongs to the field of Hermitian
- (antihermitian) operators.
- Examples
- ========
- >>> from sympy import Symbol
- >>> x = Symbol('x', real=True); x
- x
- >>> x.is_real
- True
- >>> x.is_complex
- True
- See Also
- ========
- .. seealso::
- :py:class:`sympy.core.numbers.ImaginaryUnit`
- :py:class:`sympy.core.numbers.Zero`
- :py:class:`sympy.core.numbers.One`
- :py:class:`sympy.core.numbers.Infinity`
- :py:class:`sympy.core.numbers.NegativeInfinity`
- :py:class:`sympy.core.numbers.ComplexInfinity`
- Notes
- =====
- The fully-resolved assumptions for any SymPy expression
- can be obtained as follows:
- >>> from sympy.core.assumptions import assumptions
- >>> x = Symbol('x',positive=True)
- >>> assumptions(x + I)
- {'commutative': True, 'complex': True, 'composite': False, 'even':
- False, 'extended_negative': False, 'extended_nonnegative': False,
- 'extended_nonpositive': False, 'extended_nonzero': False,
- 'extended_positive': False, 'extended_real': False, 'finite': True,
- 'imaginary': False, 'infinite': False, 'integer': False, 'irrational':
- False, 'negative': False, 'noninteger': False, 'nonnegative': False,
- 'nonpositive': False, 'nonzero': False, 'odd': False, 'positive':
- False, 'prime': False, 'rational': False, 'real': False, 'zero':
- False}
- Developers Notes
- ================
- The current (and possibly incomplete) values are stored
- in the ``obj._assumptions dictionary``; queries to getter methods
- (with property decorators) or attributes of objects/classes
- will return values and update the dictionary.
- >>> eq = x**2 + I
- >>> eq._assumptions
- {}
- >>> eq.is_finite
- True
- >>> eq._assumptions
- {'finite': True, 'infinite': False}
- For a :class:`~.Symbol`, there are two locations for assumptions that may
- be of interest. The ``assumptions0`` attribute gives the full set of
- assumptions derived from a given set of initial assumptions. The
- latter assumptions are stored as ``Symbol._assumptions_orig``
- >>> Symbol('x', prime=True, even=True)._assumptions_orig
- {'even': True, 'prime': True}
- The ``_assumptions_orig`` are not necessarily canonical nor are they filtered
- in any way: they records the assumptions used to instantiate a Symbol and (for
- storage purposes) represent a more compact representation of the assumptions
- needed to recreate the full set in ``Symbol.assumptions0``.
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Negative_number
- .. [2] https://en.wikipedia.org/wiki/Parity_%28mathematics%29
- .. [3] https://en.wikipedia.org/wiki/Imaginary_number
- .. [4] https://en.wikipedia.org/wiki/Composite_number
- .. [5] https://en.wikipedia.org/wiki/Irrational_number
- .. [6] https://en.wikipedia.org/wiki/Prime_number
- .. [7] https://en.wikipedia.org/wiki/Finite
- .. [8] https://docs.python.org/3/library/math.html#math.isfinite
- .. [9] https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html
- .. [10] https://en.wikipedia.org/wiki/Transcendental_number
- .. [11] https://en.wikipedia.org/wiki/Algebraic_number
- .. [12] https://en.wikipedia.org/wiki/Commutative_property
- .. [13] https://en.wikipedia.org/wiki/Complex_number
- """
- from sympy.utilities.exceptions import sympy_deprecation_warning
- from .facts import FactRules, FactKB
- from .sympify import sympify
- from sympy.core.random import _assumptions_shuffle as shuffle
- from sympy.core.assumptions_generated import generated_assumptions as _assumptions
- def _load_pre_generated_assumption_rules():
- """ Load the assumption rules from pre-generated data
- To update the pre-generated data, see :method::`_generate_assumption_rules`
- """
- _assume_rules=FactRules._from_python(_assumptions)
- return _assume_rules
- def _generate_assumption_rules():
- """ Generate the default assumption rules
- This method should only be called to update the pre-generated
- assumption rules.
- To update the pre-generated assumptions run: bin/ask_update.py
- """
- _assume_rules = FactRules([
- 'integer -> rational',
- 'rational -> real',
- 'rational -> algebraic',
- 'algebraic -> complex',
- 'transcendental == complex & !algebraic',
- 'real -> hermitian',
- 'imaginary -> complex',
- 'imaginary -> antihermitian',
- 'extended_real -> commutative',
- 'complex -> commutative',
- 'complex -> finite',
- 'odd == integer & !even',
- 'even == integer & !odd',
- 'real -> complex',
- 'extended_real -> real | infinite',
- 'real == extended_real & finite',
- 'extended_real == extended_negative | zero | extended_positive',
- 'extended_negative == extended_nonpositive & extended_nonzero',
- 'extended_positive == extended_nonnegative & extended_nonzero',
- 'extended_nonpositive == extended_real & !extended_positive',
- 'extended_nonnegative == extended_real & !extended_negative',
- 'real == negative | zero | positive',
- 'negative == nonpositive & nonzero',
- 'positive == nonnegative & nonzero',
- 'nonpositive == real & !positive',
- 'nonnegative == real & !negative',
- 'positive == extended_positive & finite',
- 'negative == extended_negative & finite',
- 'nonpositive == extended_nonpositive & finite',
- 'nonnegative == extended_nonnegative & finite',
- 'nonzero == extended_nonzero & finite',
- 'zero -> even & finite',
- 'zero == extended_nonnegative & extended_nonpositive',
- 'zero == nonnegative & nonpositive',
- 'nonzero -> real',
- 'prime -> integer & positive',
- 'composite -> integer & positive & !prime',
- '!composite -> !positive | !even | prime',
- 'irrational == real & !rational',
- 'imaginary -> !extended_real',
- 'infinite == !finite',
- 'noninteger == extended_real & !integer',
- 'extended_nonzero == extended_real & !zero',
- ])
- return _assume_rules
- _assume_rules = _load_pre_generated_assumption_rules()
- _assume_defined = _assume_rules.defined_facts.copy()
- _assume_defined.add('polar')
- _assume_defined = frozenset(_assume_defined)
- def assumptions(expr, _check=None):
- """return the T/F assumptions of ``expr``"""
- n = sympify(expr)
- if n.is_Symbol:
- rv = n.assumptions0 # are any important ones missing?
- if _check is not None:
- rv = {k: rv[k] for k in set(rv) & set(_check)}
- return rv
- rv = {}
- for k in _assume_defined if _check is None else _check:
- v = getattr(n, 'is_{}'.format(k))
- if v is not None:
- rv[k] = v
- return rv
- def common_assumptions(exprs, check=None):
- """return those assumptions which have the same True or False
- value for all the given expressions.
- Examples
- ========
- >>> from sympy.core import common_assumptions
- >>> from sympy import oo, pi, sqrt
- >>> common_assumptions([-4, 0, sqrt(2), 2, pi, oo])
- {'commutative': True, 'composite': False,
- 'extended_real': True, 'imaginary': False, 'odd': False}
- By default, all assumptions are tested; pass an iterable of the
- assumptions to limit those that are reported:
- >>> common_assumptions([0, 1, 2], ['positive', 'integer'])
- {'integer': True}
- """
- check = _assume_defined if check is None else set(check)
- if not check or not exprs:
- return {}
- # get all assumptions for each
- assume = [assumptions(i, _check=check) for i in sympify(exprs)]
- # focus on those of interest that are True
- for i, e in enumerate(assume):
- assume[i] = {k: e[k] for k in set(e) & check}
- # what assumptions are in common?
- common = set.intersection(*[set(i) for i in assume])
- # which ones hold the same value
- a = assume[0]
- return {k: a[k] for k in common if all(a[k] == b[k]
- for b in assume)}
- def failing_assumptions(expr, **assumptions):
- """
- Return a dictionary containing assumptions with values not
- matching those of the passed assumptions.
- Examples
- ========
- >>> from sympy import failing_assumptions, Symbol
- >>> x = Symbol('x', positive=True)
- >>> y = Symbol('y')
- >>> failing_assumptions(6*x + y, positive=True)
- {'positive': None}
- >>> failing_assumptions(x**2 - 1, positive=True)
- {'positive': None}
- If *expr* satisfies all of the assumptions, an empty dictionary is returned.
- >>> failing_assumptions(x**2, positive=True)
- {}
- """
- expr = sympify(expr)
- failed = {}
- for k in assumptions:
- test = getattr(expr, 'is_%s' % k, None)
- if test is not assumptions[k]:
- failed[k] = test
- return failed # {} or {assumption: value != desired}
- def check_assumptions(expr, against=None, **assume):
- """
- Checks whether assumptions of ``expr`` match the T/F assumptions
- given (or possessed by ``against``). True is returned if all
- assumptions match; False is returned if there is a mismatch and
- the assumption in ``expr`` is not None; else None is returned.
- Explanation
- ===========
- *assume* is a dict of assumptions with True or False values
- Examples
- ========
- >>> from sympy import Symbol, pi, I, exp, check_assumptions
- >>> check_assumptions(-5, integer=True)
- True
- >>> check_assumptions(pi, real=True, integer=False)
- True
- >>> check_assumptions(pi, negative=True)
- False
- >>> check_assumptions(exp(I*pi/7), real=False)
- True
- >>> x = Symbol('x', positive=True)
- >>> check_assumptions(2*x + 1, positive=True)
- True
- >>> check_assumptions(-2*x - 5, positive=True)
- False
- To check assumptions of *expr* against another variable or expression,
- pass the expression or variable as ``against``.
- >>> check_assumptions(2*x + 1, x)
- True
- To see if a number matches the assumptions of an expression, pass
- the number as the first argument, else its specific assumptions
- may not have a non-None value in the expression:
- >>> check_assumptions(x, 3)
- >>> check_assumptions(3, x)
- True
- ``None`` is returned if ``check_assumptions()`` could not conclude.
- >>> check_assumptions(2*x - 1, x)
- >>> z = Symbol('z')
- >>> check_assumptions(z, real=True)
- See Also
- ========
- failing_assumptions
- """
- expr = sympify(expr)
- if against is not None:
- if assume:
- raise ValueError(
- 'Expecting `against` or `assume`, not both.')
- assume = assumptions(against)
- known = True
- for k, v in assume.items():
- if v is None:
- continue
- e = getattr(expr, 'is_' + k, None)
- if e is None:
- known = None
- elif v != e:
- return False
- return known
- class StdFactKB(FactKB):
- """A FactKB specialized for the built-in rules
- This is the only kind of FactKB that Basic objects should use.
- """
- def __init__(self, facts=None):
- super().__init__(_assume_rules)
- # save a copy of the facts dict
- if not facts:
- self._generator = {}
- elif not isinstance(facts, FactKB):
- self._generator = facts.copy()
- else:
- self._generator = facts.generator
- if facts:
- self.deduce_all_facts(facts)
- def copy(self):
- return self.__class__(self)
- @property
- def generator(self):
- return self._generator.copy()
- def as_property(fact):
- """Convert a fact name to the name of the corresponding property"""
- return 'is_%s' % fact
- def make_property(fact):
- """Create the automagic property corresponding to a fact."""
- def getit(self):
- try:
- return self._assumptions[fact]
- except KeyError:
- if self._assumptions is self.default_assumptions:
- self._assumptions = self.default_assumptions.copy()
- return _ask(fact, self)
- getit.func_name = as_property(fact)
- return property(getit)
- def _ask(fact, obj):
- """
- Find the truth value for a property of an object.
- This function is called when a request is made to see what a fact
- value is.
- For this we use several techniques:
- First, the fact-evaluation function is tried, if it exists (for
- example _eval_is_integer). Then we try related facts. For example
- rational --> integer
- another example is joined rule:
- integer & !odd --> even
- so in the latter case if we are looking at what 'even' value is,
- 'integer' and 'odd' facts will be asked.
- In all cases, when we settle on some fact value, its implications are
- deduced, and the result is cached in ._assumptions.
- """
- # FactKB which is dict-like and maps facts to their known values:
- assumptions = obj._assumptions
- # A dict that maps facts to their handlers:
- handler_map = obj._prop_handler
- # This is our queue of facts to check:
- facts_to_check = [fact]
- facts_queued = {fact}
- # Loop over the queue as it extends
- for fact_i in facts_to_check:
- # If fact_i has already been determined then we don't need to rerun the
- # handler. There is a potential race condition for multithreaded code
- # though because it's possible that fact_i was checked in another
- # thread. The main logic of the loop below would potentially skip
- # checking assumptions[fact] in this case so we check it once after the
- # loop to be sure.
- if fact_i in assumptions:
- continue
- # Now we call the associated handler for fact_i if it exists.
- fact_i_value = None
- handler_i = handler_map.get(fact_i)
- if handler_i is not None:
- fact_i_value = handler_i(obj)
- # If we get a new value for fact_i then we should update our knowledge
- # of fact_i as well as any related facts that can be inferred using the
- # inference rules connecting the fact_i and any other fact values that
- # are already known.
- if fact_i_value is not None:
- assumptions.deduce_all_facts(((fact_i, fact_i_value),))
- # Usually if assumptions[fact] is now not None then that is because of
- # the call to deduce_all_facts above. The handler for fact_i returned
- # True or False and knowing fact_i (which is equal to fact in the first
- # iteration) implies knowing a value for fact. It is also possible
- # though that independent code e.g. called indirectly by the handler or
- # called in another thread in a multithreaded context might have
- # resulted in assumptions[fact] being set. Either way we return it.
- fact_value = assumptions.get(fact)
- if fact_value is not None:
- return fact_value
- # Extend the queue with other facts that might determine fact_i. Here
- # we randomise the order of the facts that are checked. This should not
- # lead to any non-determinism if all handlers are logically consistent
- # with the inference rules for the facts. Non-deterministic assumptions
- # queries can result from bugs in the handlers that are exposed by this
- # call to shuffle. These are pushed to the back of the queue meaning
- # that the inference graph is traversed in breadth-first order.
- new_facts_to_check = list(_assume_rules.prereq[fact_i] - facts_queued)
- shuffle(new_facts_to_check)
- facts_to_check.extend(new_facts_to_check)
- facts_queued.update(new_facts_to_check)
- # The above loop should be able to handle everything fine in a
- # single-threaded context but in multithreaded code it is possible that
- # this thread skipped computing a particular fact that was computed in
- # another thread (due to the continue). In that case it is possible that
- # fact was inferred and is now stored in the assumptions dict but it wasn't
- # checked for in the body of the loop. This is an obscure case but to make
- # sure we catch it we check once here at the end of the loop.
- if fact in assumptions:
- return assumptions[fact]
- # This query can not be answered. It's possible that e.g. another thread
- # has already stored None for fact but assumptions._tell does not mind if
- # we call _tell twice setting the same value. If this raises
- # InconsistentAssumptions then it probably means that another thread
- # attempted to compute this and got a value of True or False rather than
- # None. In that case there must be a bug in at least one of the handlers.
- # If the handlers are all deterministic and are consistent with the
- # inference rules then the same value should be computed for fact in all
- # threads.
- assumptions._tell(fact, None)
- return None
- def _prepare_class_assumptions(cls):
- """Precompute class level assumptions and generate handlers.
- This is called by Basic.__init_subclass__ each time a Basic subclass is
- defined.
- """
- local_defs = {}
- for k in _assume_defined:
- attrname = as_property(k)
- v = cls.__dict__.get(attrname, '')
- if isinstance(v, (bool, int, type(None))):
- if v is not None:
- v = bool(v)
- local_defs[k] = v
- defs = {}
- for base in reversed(cls.__bases__):
- assumptions = getattr(base, '_explicit_class_assumptions', None)
- if assumptions is not None:
- defs.update(assumptions)
- defs.update(local_defs)
- cls._explicit_class_assumptions = defs
- cls.default_assumptions = StdFactKB(defs)
- cls._prop_handler = {}
- for k in _assume_defined:
- eval_is_meth = getattr(cls, '_eval_is_%s' % k, None)
- if eval_is_meth is not None:
- cls._prop_handler[k] = eval_is_meth
- # Put definite results directly into the class dict, for speed
- for k, v in cls.default_assumptions.items():
- setattr(cls, as_property(k), v)
- # protection e.g. for Integer.is_even=F <- (Rational.is_integer=F)
- derived_from_bases = set()
- for base in cls.__bases__:
- default_assumptions = getattr(base, 'default_assumptions', None)
- # is an assumption-aware class
- if default_assumptions is not None:
- derived_from_bases.update(default_assumptions)
- for fact in derived_from_bases - set(cls.default_assumptions):
- pname = as_property(fact)
- if pname not in cls.__dict__:
- setattr(cls, pname, make_property(fact))
- # Finally, add any missing automagic property (e.g. for Basic)
- for fact in _assume_defined:
- pname = as_property(fact)
- if not hasattr(cls, pname):
- setattr(cls, pname, make_property(fact))
- # XXX: ManagedProperties used to be the metaclass for Basic but now Basic does
- # not use a metaclass. We leave this here for backwards compatibility for now
- # in case someone has been using the ManagedProperties class in downstream
- # code. The reason that it might have been used is that when subclassing a
- # class and wanting to use a metaclass the metaclass must be a subclass of the
- # metaclass for the class that is being subclassed. Anyone wanting to subclass
- # Basic and use a metaclass in their subclass would have needed to subclass
- # ManagedProperties. Here ManagedProperties is not the metaclass for Basic any
- # more but it should still be usable as a metaclass for Basic subclasses since
- # it is a subclass of type which is now the metaclass for Basic.
- class ManagedProperties(type):
- def __init__(cls, *args, **kwargs):
- msg = ("The ManagedProperties metaclass. "
- "Basic does not use metaclasses any more")
- sympy_deprecation_warning(msg,
- deprecated_since_version="1.12",
- active_deprecations_target='managedproperties')
- # Here we still call this function in case someone is using
- # ManagedProperties for something that is not a Basic subclass. For
- # Basic subclasses this function is now called by __init_subclass__ and
- # so this metaclass is not needed any more.
- _prepare_class_assumptions(cls)
|