I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,103 @@
"""Core module. Provides the basic operations needed in sympy.
"""
from .sympify import sympify, SympifyError
from .cache import cacheit
from .assumptions import assumptions, check_assumptions, failing_assumptions, common_assumptions
from .basic import Basic, Atom
from .singleton import S
from .expr import Expr, AtomicExpr, UnevaluatedExpr
from .symbol import Symbol, Wild, Dummy, symbols, var
from .numbers import Number, Float, Rational, Integer, NumberSymbol, \
RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo, \
AlgebraicNumber, comp, mod_inverse
from .power import Pow
from .intfunc import integer_nthroot, integer_log, num_digits, trailing
from .mul import Mul, prod
from .add import Add
from .mod import Mod
from .relational import ( Rel, Eq, Ne, Lt, Le, Gt, Ge,
Equality, GreaterThan, LessThan, Unequality, StrictGreaterThan,
StrictLessThan )
from .multidimensional import vectorize
from .function import Lambda, WildFunction, Derivative, diff, FunctionClass, \
Function, Subs, expand, PoleError, count_ops, \
expand_mul, expand_log, expand_func, \
expand_trig, expand_complex, expand_multinomial, nfloat, \
expand_power_base, expand_power_exp, arity
from .evalf import PrecisionExhausted, N
from .containers import Tuple, Dict
from .exprtools import gcd_terms, factor_terms, factor_nc
from .parameters import evaluate
from .kind import UndefinedKind, NumberKind, BooleanKind
from .traversal import preorder_traversal, bottom_up, use, postorder_traversal
from .sorting import default_sort_key, ordered
# expose singletons
Catalan = S.Catalan
EulerGamma = S.EulerGamma
GoldenRatio = S.GoldenRatio
TribonacciConstant = S.TribonacciConstant
__all__ = [
'sympify', 'SympifyError',
'cacheit',
'assumptions', 'check_assumptions', 'failing_assumptions',
'common_assumptions',
'Basic', 'Atom',
'S',
'Expr', 'AtomicExpr', 'UnevaluatedExpr',
'Symbol', 'Wild', 'Dummy', 'symbols', 'var',
'Number', 'Float', 'Rational', 'Integer', 'NumberSymbol', 'RealNumber',
'igcd', 'ilcm', 'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo',
'AlgebraicNumber', 'comp', 'mod_inverse',
'Pow',
'integer_nthroot', 'integer_log', 'num_digits', 'trailing',
'Mul', 'prod',
'Add',
'Mod',
'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality', 'GreaterThan',
'LessThan', 'Unequality', 'StrictGreaterThan', 'StrictLessThan',
'vectorize',
'Lambda', 'WildFunction', 'Derivative', 'diff', 'FunctionClass',
'Function', 'Subs', 'expand', 'PoleError', 'count_ops', 'expand_mul',
'expand_log', 'expand_func', 'expand_trig', 'expand_complex',
'expand_multinomial', 'nfloat', 'expand_power_base', 'expand_power_exp',
'arity',
'PrecisionExhausted', 'N',
'evalf', # The module?
'Tuple', 'Dict',
'gcd_terms', 'factor_terms', 'factor_nc',
'evaluate',
'Catalan',
'EulerGamma',
'GoldenRatio',
'TribonacciConstant',
'UndefinedKind', 'NumberKind', 'BooleanKind',
'preorder_traversal', 'bottom_up', 'use', 'postorder_traversal',
'default_sort_key', 'ordered',
]

View File

@ -0,0 +1,65 @@
"""
Base class to provide str and repr hooks that `init_printing` can overwrite.
This is exposed publicly in the `printing.defaults` module,
but cannot be defined there without causing circular imports.
"""
class Printable:
"""
The default implementation of printing for SymPy classes.
This implements a hack that allows us to print elements of built-in
Python containers in a readable way. Natively Python uses ``repr()``
even if ``str()`` was explicitly requested. Mix in this trait into
a class to get proper default printing.
This also adds support for LaTeX printing in jupyter notebooks.
"""
# Since this class is used as a mixin we set empty slots. That means that
# instances of any subclasses that use slots will not need to have a
# __dict__.
__slots__ = ()
# Note, we always use the default ordering (lex) in __str__ and __repr__,
# regardless of the global setting. See issue 5487.
def __str__(self):
from sympy.printing.str import sstr
return sstr(self, order=None)
__repr__ = __str__
def _repr_disabled(self):
"""
No-op repr function used to disable jupyter display hooks.
When :func:`sympy.init_printing` is used to disable certain display
formats, this function is copied into the appropriate ``_repr_*_``
attributes.
While we could just set the attributes to `None``, doing it this way
allows derived classes to call `super()`.
"""
return None
# We don't implement _repr_png_ here because it would add a large amount of
# data to any notebook containing SymPy expressions, without adding
# anything useful to the notebook. It can still enabled manually, e.g.,
# for the qtconsole, with init_printing().
_repr_png_ = _repr_disabled
_repr_svg_ = _repr_disabled
def _repr_latex_(self):
"""
IPython/Jupyter LaTeX printing
To change the behavior of this (e.g., pass in some settings to LaTeX),
use init_printing(). init_printing() will also enable LaTeX printing
for built in numeric types like ints and container types that contain
SymPy objects, like lists and dictionaries of expressions.
"""
from sympy.printing.latex import latex
s = latex(self, mode='plain')
return "$\\displaystyle %s$" % s

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
greeks = ('alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta',
'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu',
'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon',
'phi', 'chi', 'psi', 'omega')

View File

@ -0,0 +1,692 @@
"""
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() -> FactRules:
""" 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)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
import os
USE_SYMENGINE = os.getenv('USE_SYMENGINE', '0')
USE_SYMENGINE = USE_SYMENGINE.lower() in ('1', 't', 'true') # type: ignore
if USE_SYMENGINE:
from symengine import (Symbol, Integer, sympify as sympify_symengine, S,
SympifyError, exp, log, gamma, sqrt, I, E, pi, Matrix,
sin, cos, tan, cot, csc, sec, asin, acos, atan, acot, acsc, asec,
sinh, cosh, tanh, coth, asinh, acosh, atanh, acoth,
lambdify, symarray, diff, zeros, eye, diag, ones,
expand, Function, symbols, var, Add, Mul, Derivative,
ImmutableMatrix, MatrixBase, Rational, Basic)
from symengine.lib.symengine_wrapper import gcd as igcd
from symengine import AppliedUndef
def sympify(a, *, strict=False):
"""
Notes
=====
SymEngine's ``sympify`` does not accept keyword arguments and is
therefore not compatible with SymPy's ``sympify`` with ``strict=True``
(which ensures that only the types for which an explicit conversion has
been defined are converted). This wrapper adds an addiotional parameter
``strict`` (with default ``False``) that will raise a ``SympifyError``
if ``strict=True`` and the argument passed to the parameter ``a`` is a
string.
See Also
========
sympify: Converts an arbitrary expression to a type that can be used
inside SymPy.
"""
# The parameter ``a`` is used for this function to keep compatibility
# with the SymEngine docstring.
if strict and isinstance(a, str):
raise SympifyError(a)
return sympify_symengine(a)
# Keep the SymEngine docstring and append the additional "Notes" and "See
# Also" sections. Replacement of spaces is required to correctly format the
# indentation of the combined docstring.
sympify.__doc__ = (
sympify_symengine.__doc__
+ sympify.__doc__.replace(' ', ' ') # type: ignore
)
else:
from sympy.core.add import Add
from sympy.core.basic import Basic
from sympy.core.function import (diff, Function, AppliedUndef,
expand, Derivative)
from sympy.core.mul import Mul
from sympy.core.intfunc import igcd
from sympy.core.numbers import pi, I, Integer, Rational, E
from sympy.core.singleton import S
from sympy.core.symbol import Symbol, var, symbols
from sympy.core.sympify import SympifyError, sympify
from sympy.functions.elementary.exponential import log, exp
from sympy.functions.elementary.hyperbolic import (coth, sinh,
acosh, acoth, tanh, asinh, atanh, cosh)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (csc,
asec, cos, atan, sec, acot, asin, tan, sin, cot, acsc, acos)
from sympy.functions.special.gamma_functions import gamma
from sympy.matrices.dense import (eye, zeros, diag, Matrix,
ones, symarray)
from sympy.matrices.immutable import ImmutableMatrix
from sympy.matrices.matrixbase import MatrixBase
from sympy.utilities.lambdify import lambdify
#
# XXX: Handling of immutable and mutable matrices in SymEngine is inconsistent
# with SymPy's matrix classes in at least SymEngine version 0.7.0. Until that
# is fixed the function below is needed for consistent behaviour when
# attempting to simplify a matrix.
#
# Expected behaviour of a SymPy mutable/immutable matrix .simplify() method:
#
# Matrix.simplify() : works in place, returns None
# ImmutableMatrix.simplify() : returns a simplified copy
#
# In SymEngine both mutable and immutable matrices simplify in place and return
# None. This is inconsistent with the matrix being "immutable" and also the
# returned None leads to problems in the mechanics module.
#
# The simplify function should not be used because simplify(M) sympifies the
# matrix M and the SymEngine matrices all sympify to SymPy matrices. If we want
# to work with SymEngine matrices then we need to use their .simplify() method
# but that method does not work correctly with immutable matrices.
#
# The _simplify_matrix function can be removed when the SymEngine bug is fixed.
# Since this should be a temporary problem we do not make this function part of
# the public API.
#
# SymEngine issue: https://github.com/symengine/symengine.py/issues/363
#
def _simplify_matrix(M):
"""Return a simplified copy of the matrix M"""
if not isinstance(M, (Matrix, ImmutableMatrix)):
raise TypeError("The matrix M must be an instance of Matrix or ImmutableMatrix")
Mnew = M.as_mutable() # makes a copy if mutable
Mnew.simplify()
if isinstance(M, ImmutableMatrix):
Mnew = Mnew.as_immutable()
return Mnew
__all__ = [
'Symbol', 'Integer', 'sympify', 'S', 'SympifyError', 'exp', 'log',
'gamma', 'sqrt', 'I', 'E', 'pi', 'Matrix', 'sin', 'cos', 'tan', 'cot',
'csc', 'sec', 'asin', 'acos', 'atan', 'acot', 'acsc', 'asec', 'sinh',
'cosh', 'tanh', 'coth', 'asinh', 'acosh', 'atanh', 'acoth', 'lambdify',
'symarray', 'diff', 'zeros', 'eye', 'diag', 'ones', 'expand', 'Function',
'symbols', 'var', 'Add', 'Mul', 'Derivative', 'ImmutableMatrix',
'MatrixBase', 'Rational', 'Basic', 'igcd', 'AppliedUndef',
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
from sympy.core import Add, Mul, symbols
x, y, z = symbols('x,y,z')
def timeit_neg():
-x
def timeit_Add_x1():
x + 1
def timeit_Add_1x():
1 + x
def timeit_Add_x05():
x + 0.5
def timeit_Add_xy():
x + y
def timeit_Add_xyz():
Add(*[x, y, z])
def timeit_Mul_xy():
x*y
def timeit_Mul_xyz():
Mul(*[x, y, z])
def timeit_Div_xy():
x/y
def timeit_Div_2y():
2/y

View File

@ -0,0 +1,12 @@
from sympy.core import Symbol, Integer
x = Symbol('x')
i3 = Integer(3)
def timeit_x_is_integer():
x.is_integer
def timeit_Integer_is_irrational():
i3.is_irrational

View File

@ -0,0 +1,15 @@
from sympy.core import symbols, S
x, y = symbols('x,y')
def timeit_Symbol_meth_lookup():
x.diff # no call, just method lookup
def timeit_S_lookup():
S.Exp1
def timeit_Symbol_eq_xy():
x == y

View File

@ -0,0 +1,23 @@
from sympy.core import symbols, I
x, y, z = symbols('x,y,z')
p = 3*x**2*y*z**7 + 7*x*y*z**2 + 4*x + x*y**4
e = (x + y + z + 1)**32
def timeit_expand_nothing_todo():
p.expand()
def bench_expand_32():
"""(x+y+z+1)**32 -> expand"""
e.expand()
def timeit_expand_complex_number_1():
((2 + 3*I)**1000).expand(complex=True)
def timeit_expand_complex_number_2():
((2 + 3*I/4)**1000).expand(complex=True)

View File

@ -0,0 +1,92 @@
from sympy.core.numbers import Integer, Rational, pi, oo
from sympy.core.intfunc import integer_nthroot, igcd
from sympy.core.singleton import S
i3 = Integer(3)
i4 = Integer(4)
r34 = Rational(3, 4)
q45 = Rational(4, 5)
def timeit_Integer_create():
Integer(2)
def timeit_Integer_int():
int(i3)
def timeit_neg_one():
-S.One
def timeit_Integer_neg():
-i3
def timeit_Integer_abs():
abs(i3)
def timeit_Integer_sub():
i3 - i3
def timeit_abs_pi():
abs(pi)
def timeit_neg_oo():
-oo
def timeit_Integer_add_i1():
i3 + 1
def timeit_Integer_add_ij():
i3 + i4
def timeit_Integer_add_Rational():
i3 + r34
def timeit_Integer_mul_i4():
i3*4
def timeit_Integer_mul_ij():
i3*i4
def timeit_Integer_mul_Rational():
i3*r34
def timeit_Integer_eq_i3():
i3 == 3
def timeit_Integer_ed_Rational():
i3 == r34
def timeit_integer_nthroot():
integer_nthroot(100, 2)
def timeit_number_igcd_23_17():
igcd(23, 17)
def timeit_number_igcd_60_3600():
igcd(60, 3600)
def timeit_Rational_add_r1():
r34 + 1
def timeit_Rational_add_rq():
r34 + q45

View File

@ -0,0 +1,11 @@
from sympy.core import sympify, Symbol
x = Symbol('x')
def timeit_sympify_1():
sympify(1)
def timeit_sympify_x():
sympify(x)

View File

@ -0,0 +1,210 @@
""" Caching facility for SymPy """
from importlib import import_module
from typing import Callable
class _cache(list):
""" List of cached functions """
def print_cache(self):
"""print cache info"""
for item in self:
name = item.__name__
myfunc = item
while hasattr(myfunc, '__wrapped__'):
if hasattr(myfunc, 'cache_info'):
info = myfunc.cache_info()
break
else:
myfunc = myfunc.__wrapped__
else:
info = None
print(name, info)
def clear_cache(self):
"""clear cache content"""
for item in self:
myfunc = item
while hasattr(myfunc, '__wrapped__'):
if hasattr(myfunc, 'cache_clear'):
myfunc.cache_clear()
break
else:
myfunc = myfunc.__wrapped__
# global cache registry:
CACHE = _cache()
# make clear and print methods available
print_cache = CACHE.print_cache
clear_cache = CACHE.clear_cache
from functools import lru_cache, wraps
def __cacheit(maxsize):
"""caching decorator.
important: the result of cached function must be *immutable*
Examples
========
>>> from sympy import cacheit
>>> @cacheit
... def f(a, b):
... return a+b
>>> @cacheit
... def f(a, b): # noqa: F811
... return [a, b] # <-- WRONG, returns mutable object
to force cacheit to check returned results mutability and consistency,
set environment variable SYMPY_USE_CACHE to 'debug'
"""
def func_wrapper(func):
cfunc = lru_cache(maxsize, typed=True)(func)
@wraps(func)
def wrapper(*args, **kwargs):
try:
retval = cfunc(*args, **kwargs)
except TypeError as e:
if not e.args or not e.args[0].startswith('unhashable type:'):
raise
retval = func(*args, **kwargs)
return retval
wrapper.cache_info = cfunc.cache_info
wrapper.cache_clear = cfunc.cache_clear
CACHE.append(wrapper)
return wrapper
return func_wrapper
########################################
def __cacheit_nocache(func):
return func
def __cacheit_debug(maxsize):
"""cacheit + code to check cache consistency"""
def func_wrapper(func):
cfunc = __cacheit(maxsize)(func)
@wraps(func)
def wrapper(*args, **kw_args):
# always call function itself and compare it with cached version
r1 = func(*args, **kw_args)
r2 = cfunc(*args, **kw_args)
# try to see if the result is immutable
#
# this works because:
#
# hash([1,2,3]) -> raise TypeError
# hash({'a':1, 'b':2}) -> raise TypeError
# hash((1,[2,3])) -> raise TypeError
#
# hash((1,2,3)) -> just computes the hash
hash(r1), hash(r2)
# also see if returned values are the same
if r1 != r2:
raise RuntimeError("Returned values are not the same")
return r1
return wrapper
return func_wrapper
def _getenv(key, default=None):
from os import getenv
return getenv(key, default)
# SYMPY_USE_CACHE=yes/no/debug
USE_CACHE = _getenv('SYMPY_USE_CACHE', 'yes').lower()
# SYMPY_CACHE_SIZE=some_integer/None
# special cases :
# SYMPY_CACHE_SIZE=0 -> No caching
# SYMPY_CACHE_SIZE=None -> Unbounded caching
scs = _getenv('SYMPY_CACHE_SIZE', '1000')
if scs.lower() == 'none':
SYMPY_CACHE_SIZE = None
else:
try:
SYMPY_CACHE_SIZE = int(scs)
except ValueError:
raise RuntimeError(
'SYMPY_CACHE_SIZE must be a valid integer or None. ' + \
'Got: %s' % SYMPY_CACHE_SIZE)
if USE_CACHE == 'no':
cacheit = __cacheit_nocache
elif USE_CACHE == 'yes':
cacheit = __cacheit(SYMPY_CACHE_SIZE)
elif USE_CACHE == 'debug':
cacheit = __cacheit_debug(SYMPY_CACHE_SIZE) # a lot slower
else:
raise RuntimeError(
'unrecognized value for SYMPY_USE_CACHE: %s' % USE_CACHE)
def cached_property(func):
'''Decorator to cache property method'''
attrname = '__' + func.__name__
_cached_property_sentinel = object()
def propfunc(self):
val = getattr(self, attrname, _cached_property_sentinel)
if val is _cached_property_sentinel:
val = func(self)
setattr(self, attrname, val)
return val
return property(propfunc)
def lazy_function(module : str, name : str) -> Callable:
"""Create a lazy proxy for a function in a module.
The module containing the function is not imported until the function is used.
"""
func = None
def _get_function():
nonlocal func
if func is None:
func = getattr(import_module(module), name)
return func
# The metaclass is needed so that help() shows the docstring
class LazyFunctionMeta(type):
@property
def __doc__(self):
docstring = _get_function().__doc__
docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'"
return docstring
class LazyFunction(metaclass=LazyFunctionMeta):
def __call__(self, *args, **kwargs):
# inline get of function for performance gh-23832
nonlocal func
if func is None:
func = getattr(import_module(module), name)
return func(*args, **kwargs)
@property
def __doc__(self):
docstring = _get_function().__doc__
docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'"
return docstring
def __str__(self):
return _get_function().__str__()
def __repr__(self):
return f"<{__class__.__name__} object at 0x{id(self):x}>: wrapping '{module}.{name}'"
return LazyFunction()

View File

@ -0,0 +1,35 @@
"""
.. deprecated:: 1.10
``sympy.core.compatibility`` is deprecated. See
:ref:`sympy-core-compatibility`.
Reimplementations of constructs introduced in later versions of Python than
we support. Also some functions that are needed SymPy-wide and are located
here for easy import.
"""
from sympy.utilities.exceptions import sympy_deprecation_warning
sympy_deprecation_warning("""
The sympy.core.compatibility submodule is deprecated.
This module was only ever intended for internal use. Some of the functions
that were in this module are available from the top-level SymPy namespace,
i.e.,
from sympy import ordered, default_sort_key
The remaining were only intended for internal SymPy use and should not be used
by user code.
""",
deprecated_since_version="1.10",
active_deprecations_target="deprecated-sympy-core-compatibility",
)
from .sorting import ordered, _nodes, default_sort_key # noqa:F401
from sympy.utilities.misc import as_int as _as_int # noqa:F401
from sympy.utilities.iterables import iterable, is_sequence, NotIterable # noqa:F401

View File

@ -0,0 +1,410 @@
"""Module for SymPy containers
(SymPy objects that store other SymPy objects)
The containers implemented in this module are subclassed to Basic.
They are supposed to work seamlessly within the SymPy framework.
"""
from collections import OrderedDict
from collections.abc import MutableSet
from typing import Any, Callable
from .basic import Basic
from .sorting import default_sort_key, ordered
from .sympify import _sympify, sympify, _sympy_converter, SympifyError
from sympy.core.kind import Kind
from sympy.utilities.iterables import iterable
from sympy.utilities.misc import as_int
class Tuple(Basic):
"""
Wrapper around the builtin tuple object.
Explanation
===========
The Tuple is a subclass of Basic, so that it works well in the
SymPy framework. The wrapped tuple is available as self.args, but
you can also access elements or slices with [:] syntax.
Parameters
==========
sympify : bool
If ``False``, ``sympify`` is not called on ``args``. This
can be used for speedups for very large tuples where the
elements are known to already be SymPy objects.
Examples
========
>>> from sympy import Tuple, symbols
>>> a, b, c, d = symbols('a b c d')
>>> Tuple(a, b, c)[1:]
(b, c)
>>> Tuple(a, b, c).subs(a, d)
(d, b, c)
"""
def __new__(cls, *args, **kwargs):
if kwargs.get('sympify', True):
args = (sympify(arg) for arg in args)
obj = Basic.__new__(cls, *args)
return obj
def __getitem__(self, i):
if isinstance(i, slice):
indices = i.indices(len(self))
return Tuple(*(self.args[j] for j in range(*indices)))
return self.args[i]
def __len__(self):
return len(self.args)
def __contains__(self, item):
return item in self.args
def __iter__(self):
return iter(self.args)
def __add__(self, other):
if isinstance(other, Tuple):
return Tuple(*(self.args + other.args))
elif isinstance(other, tuple):
return Tuple(*(self.args + other))
else:
return NotImplemented
def __radd__(self, other):
if isinstance(other, Tuple):
return Tuple(*(other.args + self.args))
elif isinstance(other, tuple):
return Tuple(*(other + self.args))
else:
return NotImplemented
def __mul__(self, other):
try:
n = as_int(other)
except ValueError:
raise TypeError("Can't multiply sequence by non-integer of type '%s'" % type(other))
return self.func(*(self.args*n))
__rmul__ = __mul__
def __eq__(self, other):
if isinstance(other, Basic):
return super().__eq__(other)
return self.args == other
def __ne__(self, other):
if isinstance(other, Basic):
return super().__ne__(other)
return self.args != other
def __hash__(self):
return hash(self.args)
def _to_mpmath(self, prec):
return tuple(a._to_mpmath(prec) for a in self.args)
def __lt__(self, other):
return _sympify(self.args < other.args)
def __le__(self, other):
return _sympify(self.args <= other.args)
# XXX: Basic defines count() as something different, so we can't
# redefine it here. Originally this lead to cse() test failure.
def tuple_count(self, value) -> int:
"""Return number of occurrences of value."""
return self.args.count(value)
def index(self, value, start=None, stop=None):
"""Searches and returns the first index of the value."""
# XXX: One would expect:
#
# return self.args.index(value, start, stop)
#
# here. Any trouble with that? Yes:
#
# >>> (1,).index(1, None, None)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: slice indices must be integers or None or have an __index__ method
#
# See: http://bugs.python.org/issue13340
if start is None and stop is None:
return self.args.index(value)
elif stop is None:
return self.args.index(value, start)
else:
return self.args.index(value, start, stop)
@property
def kind(self):
"""
The kind of a Tuple instance.
The kind of a Tuple is always of :class:`TupleKind` but
parametrised by the number of elements and the kind of each element.
Examples
========
>>> from sympy import Tuple, Matrix
>>> Tuple(1, 2).kind
TupleKind(NumberKind, NumberKind)
>>> Tuple(Matrix([1, 2]), 1).kind
TupleKind(MatrixKind(NumberKind), NumberKind)
>>> Tuple(1, 2).kind.element_kind
(NumberKind, NumberKind)
See Also
========
sympy.matrices.kind.MatrixKind
sympy.core.kind.NumberKind
"""
return TupleKind(*(i.kind for i in self.args))
_sympy_converter[tuple] = lambda tup: Tuple(*tup)
def tuple_wrapper(method):
"""
Decorator that converts any tuple in the function arguments into a Tuple.
Explanation
===========
The motivation for this is to provide simple user interfaces. The user can
call a function with regular tuples in the argument, and the wrapper will
convert them to Tuples before handing them to the function.
Explanation
===========
>>> from sympy.core.containers import tuple_wrapper
>>> def f(*args):
... return args
>>> g = tuple_wrapper(f)
The decorated function g sees only the Tuple argument:
>>> g(0, (1, 2), 3)
(0, (1, 2), 3)
"""
def wrap_tuples(*args, **kw_args):
newargs = []
for arg in args:
if isinstance(arg, tuple):
newargs.append(Tuple(*arg))
else:
newargs.append(arg)
return method(*newargs, **kw_args)
return wrap_tuples
class Dict(Basic):
"""
Wrapper around the builtin dict object.
Explanation
===========
The Dict is a subclass of Basic, so that it works well in the
SymPy framework. Because it is immutable, it may be included
in sets, but its values must all be given at instantiation and
cannot be changed afterwards. Otherwise it behaves identically
to the Python dict.
Examples
========
>>> from sympy import Dict, Symbol
>>> D = Dict({1: 'one', 2: 'two'})
>>> for key in D:
... if key == 1:
... print('%s %s' % (key, D[key]))
1 one
The args are sympified so the 1 and 2 are Integers and the values
are Symbols. Queries automatically sympify args so the following work:
>>> 1 in D
True
>>> D.has(Symbol('one')) # searches keys and values
True
>>> 'one' in D # not in the keys
False
>>> D[1]
one
"""
def __new__(cls, *args):
if len(args) == 1 and isinstance(args[0], (dict, Dict)):
items = [Tuple(k, v) for k, v in args[0].items()]
elif iterable(args) and all(len(arg) == 2 for arg in args):
items = [Tuple(k, v) for k, v in args]
else:
raise TypeError('Pass Dict args as Dict((k1, v1), ...) or Dict({k1: v1, ...})')
elements = frozenset(items)
obj = Basic.__new__(cls, *ordered(items))
obj.elements = elements
obj._dict = dict(items) # In case Tuple decides it wants to sympify
return obj
def __getitem__(self, key):
"""x.__getitem__(y) <==> x[y]"""
try:
key = _sympify(key)
except SympifyError:
raise KeyError(key)
return self._dict[key]
def __setitem__(self, key, value):
raise NotImplementedError("SymPy Dicts are Immutable")
def items(self):
'''Returns a set-like object providing a view on dict's items.
'''
return self._dict.items()
def keys(self):
'''Returns the list of the dict's keys.'''
return self._dict.keys()
def values(self):
'''Returns the list of the dict's values.'''
return self._dict.values()
def __iter__(self):
'''x.__iter__() <==> iter(x)'''
return iter(self._dict)
def __len__(self):
'''x.__len__() <==> len(x)'''
return self._dict.__len__()
def get(self, key, default=None):
'''Returns the value for key if the key is in the dictionary.'''
try:
key = _sympify(key)
except SympifyError:
return default
return self._dict.get(key, default)
def __contains__(self, key):
'''D.__contains__(k) -> True if D has a key k, else False'''
try:
key = _sympify(key)
except SympifyError:
return False
return key in self._dict
def __lt__(self, other):
return _sympify(self.args < other.args)
@property
def _sorted_args(self):
return tuple(sorted(self.args, key=default_sort_key))
def __eq__(self, other):
if isinstance(other, dict):
return self == Dict(other)
return super().__eq__(other)
__hash__ : Callable[[Basic], Any] = Basic.__hash__
# this handles dict, defaultdict, OrderedDict
_sympy_converter[dict] = lambda d: Dict(*d.items())
class OrderedSet(MutableSet):
def __init__(self, iterable=None):
if iterable:
self.map = OrderedDict((item, None) for item in iterable)
else:
self.map = OrderedDict()
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
self.map[key] = None
def discard(self, key):
self.map.pop(key)
def pop(self, last=True):
return self.map.popitem(last=last)[0]
def __iter__(self):
yield from self.map.keys()
def __repr__(self):
if not self.map:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self.map.keys()))
def intersection(self, other):
return self.__class__([val for val in self if val in other])
def difference(self, other):
return self.__class__([val for val in self if val not in other])
def update(self, iterable):
for val in iterable:
self.add(val)
class TupleKind(Kind):
"""
TupleKind is a subclass of Kind, which is used to define Kind of ``Tuple``.
Parameters of TupleKind will be kinds of all the arguments in Tuples, for
example
Parameters
==========
args : tuple(element_kind)
element_kind is kind of element.
args is tuple of kinds of element
Examples
========
>>> from sympy import Tuple
>>> Tuple(1, 2).kind
TupleKind(NumberKind, NumberKind)
>>> Tuple(1, 2).kind.element_kind
(NumberKind, NumberKind)
See Also
========
sympy.core.kind.NumberKind
MatrixKind
sympy.sets.sets.SetKind
"""
def __new__(cls, *args):
obj = super().__new__(cls, *args)
obj.element_kind = args
return obj
def __repr__(self):
return "TupleKind{}".format(self.element_kind)

View File

@ -0,0 +1,21 @@
""" The core's core. """
from __future__ import annotations
class Registry:
"""
Base class for registry objects.
Registries map a name to an object using attribute notation. Registry
classes behave singletonically: all their instances share the same state,
which is stored in the class object.
All subclasses should set `__slots__ = ()`.
"""
__slots__ = ()
def __setattr__(self, name, obj):
setattr(self.__class__, name, obj)
def __delattr__(self, name):
delattr(self.__class__, name)

View File

@ -0,0 +1,9 @@
"""Definitions of common exceptions for :mod:`sympy.core` module. """
class BaseCoreError(Exception):
"""Base class for core related exceptions. """
class NonCommutativeExpression(BaseCoreError):
"""Raised when expression didn't have commutative property. """

View File

@ -0,0 +1,238 @@
"""
SymPy core decorators.
The purpose of this module is to expose decorators without any other
dependencies, so that they can be easily imported anywhere in sympy/core.
"""
from functools import wraps
from .sympify import SympifyError, sympify
def _sympifyit(arg, retval=None):
"""
decorator to smartly _sympify function arguments
Explanation
===========
@_sympifyit('other', NotImplemented)
def add(self, other):
...
In add, other can be thought of as already being a SymPy object.
If it is not, the code is likely to catch an exception, then other will
be explicitly _sympified, and the whole code restarted.
if _sympify(arg) fails, NotImplemented will be returned
See also
========
__sympifyit
"""
def deco(func):
return __sympifyit(func, arg, retval)
return deco
def __sympifyit(func, arg, retval=None):
"""Decorator to _sympify `arg` argument for function `func`.
Do not use directly -- use _sympifyit instead.
"""
# we support f(a,b) only
if not func.__code__.co_argcount:
raise LookupError("func not found")
# only b is _sympified
assert func.__code__.co_varnames[1] == arg
if retval is None:
@wraps(func)
def __sympifyit_wrapper(a, b):
return func(a, sympify(b, strict=True))
else:
@wraps(func)
def __sympifyit_wrapper(a, b):
try:
# If an external class has _op_priority, it knows how to deal
# with SymPy objects. Otherwise, it must be converted.
if not hasattr(b, '_op_priority'):
b = sympify(b, strict=True)
return func(a, b)
except SympifyError:
return retval
return __sympifyit_wrapper
def call_highest_priority(method_name):
"""A decorator for binary special methods to handle _op_priority.
Explanation
===========
Binary special methods in Expr and its subclasses use a special attribute
'_op_priority' to determine whose special method will be called to
handle the operation. In general, the object having the highest value of
'_op_priority' will handle the operation. Expr and subclasses that define
custom binary special methods (__mul__, etc.) should decorate those
methods with this decorator to add the priority logic.
The ``method_name`` argument is the name of the method of the other class
that will be called. Use this decorator in the following manner::
# Call other.__rmul__ if other._op_priority > self._op_priority
@call_highest_priority('__rmul__')
def __mul__(self, other):
...
# Call other.__mul__ if other._op_priority > self._op_priority
@call_highest_priority('__mul__')
def __rmul__(self, other):
...
"""
def priority_decorator(func):
@wraps(func)
def binary_op_wrapper(self, other):
if hasattr(other, '_op_priority'):
if other._op_priority > self._op_priority:
f = getattr(other, method_name, None)
if f is not None:
return f(self)
return func(self, other)
return binary_op_wrapper
return priority_decorator
def sympify_method_args(cls):
'''Decorator for a class with methods that sympify arguments.
Explanation
===========
The sympify_method_args decorator is to be used with the sympify_return
decorator for automatic sympification of method arguments. This is
intended for the common idiom of writing a class like :
Examples
========
>>> from sympy import Basic, SympifyError, S
>>> from sympy.core.sympify import _sympify
>>> class MyTuple(Basic):
... def __add__(self, other):
... try:
... other = _sympify(other)
... except SympifyError:
... return NotImplemented
... if not isinstance(other, MyTuple):
... return NotImplemented
... return MyTuple(*(self.args + other.args))
>>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4))
MyTuple(1, 2, 3, 4)
In the above it is important that we return NotImplemented when other is
not sympifiable and also when the sympified result is not of the expected
type. This allows the MyTuple class to be used cooperatively with other
classes that overload __add__ and want to do something else in combination
with instance of Tuple.
Using this decorator the above can be written as
>>> from sympy.core.decorators import sympify_method_args, sympify_return
>>> @sympify_method_args
... class MyTuple(Basic):
... @sympify_return([('other', 'MyTuple')], NotImplemented)
... def __add__(self, other):
... return MyTuple(*(self.args + other.args))
>>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4))
MyTuple(1, 2, 3, 4)
The idea here is that the decorators take care of the boiler-plate code
for making this happen in each method that potentially needs to accept
unsympified arguments. Then the body of e.g. the __add__ method can be
written without needing to worry about calling _sympify or checking the
type of the resulting object.
The parameters for sympify_return are a list of tuples of the form
(parameter_name, expected_type) and the value to return (e.g.
NotImplemented). The expected_type parameter can be a type e.g. Tuple or a
string 'Tuple'. Using a string is useful for specifying a Type within its
class body (as in the above example).
Notes: Currently sympify_return only works for methods that take a single
argument (not including self). Specifying an expected_type as a string
only works for the class in which the method is defined.
'''
# Extract the wrapped methods from each of the wrapper objects created by
# the sympify_return decorator. Doing this here allows us to provide the
# cls argument which is used for forward string referencing.
for attrname, obj in cls.__dict__.items():
if isinstance(obj, _SympifyWrapper):
setattr(cls, attrname, obj.make_wrapped(cls))
return cls
def sympify_return(*args):
'''Function/method decorator to sympify arguments automatically
See the docstring of sympify_method_args for explanation.
'''
# Store a wrapper object for the decorated method
def wrapper(func):
return _SympifyWrapper(func, args)
return wrapper
class _SympifyWrapper:
'''Internal class used by sympify_return and sympify_method_args'''
def __init__(self, func, args):
self.func = func
self.args = args
def make_wrapped(self, cls):
func = self.func
parameters, retval = self.args
# XXX: Handle more than one parameter?
[(parameter, expectedcls)] = parameters
# Handle forward references to the current class using strings
if expectedcls == cls.__name__:
expectedcls = cls
# Raise RuntimeError since this is a failure at import time and should
# not be recoverable.
nargs = func.__code__.co_argcount
# we support f(a, b) only
if nargs != 2:
raise RuntimeError('sympify_return can only be used with 2 argument functions')
# only b is _sympified
if func.__code__.co_varnames[1] != parameter:
raise RuntimeError('parameter name mismatch "%s" in %s' %
(parameter, func.__name__))
@wraps(func)
def _func(self, other):
# XXX: The check for _op_priority here should be removed. It is
# needed to stop mutable matrices from being sympified to
# immutable matrices which breaks things in quantum...
if not hasattr(other, '_op_priority'):
try:
other = sympify(other, strict=True)
except SympifyError:
return retval
if not isinstance(other, expectedcls):
return retval
return func(self, other)
return _func

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,634 @@
r"""This is rule-based deduction system for SymPy
The whole thing is split into two parts
- rules compilation and preparation of tables
- runtime inference
For rule-based inference engines, the classical work is RETE algorithm [1],
[2] Although we are not implementing it in full (or even significantly)
it's still worth a read to understand the underlying ideas.
In short, every rule in a system of rules is one of two forms:
- atom -> ... (alpha rule)
- And(atom1, atom2, ...) -> ... (beta rule)
The major complexity is in efficient beta-rules processing and usually for an
expert system a lot of effort goes into code that operates on beta-rules.
Here we take minimalistic approach to get something usable first.
- (preparation) of alpha- and beta- networks, everything except
- (runtime) FactRules.deduce_all_facts
_____________________________________
( Kirr: I've never thought that doing )
( logic stuff is that difficult... )
-------------------------------------
o ^__^
o (oo)\_______
(__)\ )\/\
||----w |
|| ||
Some references on the topic
----------------------------
[1] https://en.wikipedia.org/wiki/Rete_algorithm
[2] http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf
https://en.wikipedia.org/wiki/Propositional_formula
https://en.wikipedia.org/wiki/Inference_rule
https://en.wikipedia.org/wiki/List_of_rules_of_inference
"""
from collections import defaultdict
from typing import Iterator
from .logic import Logic, And, Or, Not
def _base_fact(atom):
"""Return the literal fact of an atom.
Effectively, this merely strips the Not around a fact.
"""
if isinstance(atom, Not):
return atom.arg
else:
return atom
def _as_pair(atom):
if isinstance(atom, Not):
return (atom.arg, False)
else:
return (atom, True)
# XXX this prepares forward-chaining rules for alpha-network
def transitive_closure(implications):
"""
Computes the transitive closure of a list of implications
Uses Warshall's algorithm, as described at
http://www.cs.hope.edu/~cusack/Notes/Notes/DiscreteMath/Warshall.pdf.
"""
full_implications = set(implications)
literals = set().union(*map(set, full_implications))
for k in literals:
for i in literals:
if (i, k) in full_implications:
for j in literals:
if (k, j) in full_implications:
full_implications.add((i, j))
return full_implications
def deduce_alpha_implications(implications):
"""deduce all implications
Description by example
----------------------
given set of logic rules:
a -> b
b -> c
we deduce all possible rules:
a -> b, c
b -> c
implications: [] of (a,b)
return: {} of a -> set([b, c, ...])
"""
implications = implications + [(Not(j), Not(i)) for (i, j) in implications]
res = defaultdict(set)
full_implications = transitive_closure(implications)
for a, b in full_implications:
if a == b:
continue # skip a->a cyclic input
res[a].add(b)
# Clean up tautologies and check consistency
for a, impl in res.items():
impl.discard(a)
na = Not(a)
if na in impl:
raise ValueError(
'implications are inconsistent: %s -> %s %s' % (a, na, impl))
return res
def apply_beta_to_alpha_route(alpha_implications, beta_rules):
"""apply additional beta-rules (And conditions) to already-built
alpha implication tables
TODO: write about
- static extension of alpha-chains
- attaching refs to beta-nodes to alpha chains
e.g.
alpha_implications:
a -> [b, !c, d]
b -> [d]
...
beta_rules:
&(b,d) -> e
then we'll extend a's rule to the following
a -> [b, !c, d, e]
"""
x_impl = {}
for x in alpha_implications.keys():
x_impl[x] = (set(alpha_implications[x]), [])
for bcond, bimpl in beta_rules:
for bk in bcond.args:
if bk in x_impl:
continue
x_impl[bk] = (set(), [])
# static extensions to alpha rules:
# A: x -> a,b B: &(a,b) -> c ==> A: x -> a,b,c
seen_static_extension = True
while seen_static_extension:
seen_static_extension = False
for bcond, bimpl in beta_rules:
if not isinstance(bcond, And):
raise TypeError("Cond is not And")
bargs = set(bcond.args)
for x, (ximpls, bb) in x_impl.items():
x_all = ximpls | {x}
# A: ... -> a B: &(...) -> a is non-informative
if bimpl not in x_all and bargs.issubset(x_all):
ximpls.add(bimpl)
# we introduced new implication - now we have to restore
# completeness of the whole set.
bimpl_impl = x_impl.get(bimpl)
if bimpl_impl is not None:
ximpls |= bimpl_impl[0]
seen_static_extension = True
# attach beta-nodes which can be possibly triggered by an alpha-chain
for bidx, (bcond, bimpl) in enumerate(beta_rules):
bargs = set(bcond.args)
for x, (ximpls, bb) in x_impl.items():
x_all = ximpls | {x}
# A: ... -> a B: &(...) -> a (non-informative)
if bimpl in x_all:
continue
# A: x -> a... B: &(!a,...) -> ... (will never trigger)
# A: x -> a... B: &(...) -> !a (will never trigger)
if any(Not(xi) in bargs or Not(xi) == bimpl for xi in x_all):
continue
if bargs & x_all:
bb.append(bidx)
return x_impl
def rules_2prereq(rules):
"""build prerequisites table from rules
Description by example
----------------------
given set of logic rules:
a -> b, c
b -> c
we build prerequisites (from what points something can be deduced):
b <- a
c <- a, b
rules: {} of a -> [b, c, ...]
return: {} of c <- [a, b, ...]
Note however, that this prerequisites may be *not* enough to prove a
fact. An example is 'a -> b' rule, where prereq(a) is b, and prereq(b)
is a. That's because a=T -> b=T, and b=F -> a=F, but a=F -> b=?
"""
prereq = defaultdict(set)
for (a, _), impl in rules.items():
if isinstance(a, Not):
a = a.args[0]
for (i, _) in impl:
if isinstance(i, Not):
i = i.args[0]
prereq[i].add(a)
return prereq
################
# RULES PROVER #
################
class TautologyDetected(Exception):
"""(internal) Prover uses it for reporting detected tautology"""
pass
class Prover:
"""ai - prover of logic rules
given a set of initial rules, Prover tries to prove all possible rules
which follow from given premises.
As a result proved_rules are always either in one of two forms: alpha or
beta:
Alpha rules
-----------
This are rules of the form::
a -> b & c & d & ...
Beta rules
----------
This are rules of the form::
&(a,b,...) -> c & d & ...
i.e. beta rules are join conditions that say that something follows when
*several* facts are true at the same time.
"""
def __init__(self):
self.proved_rules = []
self._rules_seen = set()
def split_alpha_beta(self):
"""split proved rules into alpha and beta chains"""
rules_alpha = [] # a -> b
rules_beta = [] # &(...) -> b
for a, b in self.proved_rules:
if isinstance(a, And):
rules_beta.append((a, b))
else:
rules_alpha.append((a, b))
return rules_alpha, rules_beta
@property
def rules_alpha(self):
return self.split_alpha_beta()[0]
@property
def rules_beta(self):
return self.split_alpha_beta()[1]
def process_rule(self, a, b):
"""process a -> b rule""" # TODO write more?
if (not a) or isinstance(b, bool):
return
if isinstance(a, bool):
return
if (a, b) in self._rules_seen:
return
else:
self._rules_seen.add((a, b))
# this is the core of processing
try:
self._process_rule(a, b)
except TautologyDetected:
pass
def _process_rule(self, a, b):
# right part first
# a -> b & c --> a -> b ; a -> c
# (?) FIXME this is only correct when b & c != null !
if isinstance(b, And):
sorted_bargs = sorted(b.args, key=str)
for barg in sorted_bargs:
self.process_rule(a, barg)
# a -> b | c --> !b & !c -> !a
# --> a & !b -> c
# --> a & !c -> b
elif isinstance(b, Or):
sorted_bargs = sorted(b.args, key=str)
# detect tautology first
if not isinstance(a, Logic): # Atom
# tautology: a -> a|c|...
if a in sorted_bargs:
raise TautologyDetected(a, b, 'a -> a|c|...')
self.process_rule(And(*[Not(barg) for barg in b.args]), Not(a))
for bidx in range(len(sorted_bargs)):
barg = sorted_bargs[bidx]
brest = sorted_bargs[:bidx] + sorted_bargs[bidx + 1:]
self.process_rule(And(a, Not(barg)), Or(*brest))
# left part
# a & b -> c --> IRREDUCIBLE CASE -- WE STORE IT AS IS
# (this will be the basis of beta-network)
elif isinstance(a, And):
sorted_aargs = sorted(a.args, key=str)
if b in sorted_aargs:
raise TautologyDetected(a, b, 'a & b -> a')
self.proved_rules.append((a, b))
# XXX NOTE at present we ignore !c -> !a | !b
elif isinstance(a, Or):
sorted_aargs = sorted(a.args, key=str)
if b in sorted_aargs:
raise TautologyDetected(a, b, 'a | b -> a')
for aarg in sorted_aargs:
self.process_rule(aarg, b)
else:
# both `a` and `b` are atoms
self.proved_rules.append((a, b)) # a -> b
self.proved_rules.append((Not(b), Not(a))) # !b -> !a
########################################
class FactRules:
"""Rules that describe how to deduce facts in logic space
When defined, these rules allow implications to quickly be determined
for a set of facts. For this precomputed deduction tables are used.
see `deduce_all_facts` (forward-chaining)
Also it is possible to gather prerequisites for a fact, which is tried
to be proven. (backward-chaining)
Definition Syntax
-----------------
a -> b -- a=T -> b=T (and automatically b=F -> a=F)
a -> !b -- a=T -> b=F
a == b -- a -> b & b -> a
a -> b & c -- a=T -> b=T & c=T
# TODO b | c
Internals
---------
.full_implications[k, v]: all the implications of fact k=v
.beta_triggers[k, v]: beta rules that might be triggered when k=v
.prereq -- {} k <- [] of k's prerequisites
.defined_facts -- set of defined fact names
"""
def __init__(self, rules):
"""Compile rules into internal lookup tables"""
if isinstance(rules, str):
rules = rules.splitlines()
# --- parse and process rules ---
P = Prover()
for rule in rules:
# XXX `a` is hardcoded to be always atom
a, op, b = rule.split(None, 2)
a = Logic.fromstring(a)
b = Logic.fromstring(b)
if op == '->':
P.process_rule(a, b)
elif op == '==':
P.process_rule(a, b)
P.process_rule(b, a)
else:
raise ValueError('unknown op %r' % op)
# --- build deduction networks ---
self.beta_rules = []
for bcond, bimpl in P.rules_beta:
self.beta_rules.append(
({_as_pair(a) for a in bcond.args}, _as_pair(bimpl)))
# deduce alpha implications
impl_a = deduce_alpha_implications(P.rules_alpha)
# now:
# - apply beta rules to alpha chains (static extension), and
# - further associate beta rules to alpha chain (for inference
# at runtime)
impl_ab = apply_beta_to_alpha_route(impl_a, P.rules_beta)
# extract defined fact names
self.defined_facts = {_base_fact(k) for k in impl_ab.keys()}
# build rels (forward chains)
full_implications = defaultdict(set)
beta_triggers = defaultdict(set)
for k, (impl, betaidxs) in impl_ab.items():
full_implications[_as_pair(k)] = {_as_pair(i) for i in impl}
beta_triggers[_as_pair(k)] = betaidxs
self.full_implications = full_implications
self.beta_triggers = beta_triggers
# build prereq (backward chains)
prereq = defaultdict(set)
rel_prereq = rules_2prereq(full_implications)
for k, pitems in rel_prereq.items():
prereq[k] |= pitems
self.prereq = prereq
def _to_python(self) -> str:
""" Generate a string with plain python representation of the instance """
return '\n'.join(self.print_rules())
@classmethod
def _from_python(cls, data : dict):
""" Generate an instance from the plain python representation """
self = cls('')
for key in ['full_implications', 'beta_triggers', 'prereq']:
d=defaultdict(set)
d.update(data[key])
setattr(self, key, d)
self.beta_rules = data['beta_rules']
self.defined_facts = set(data['defined_facts'])
return self
def _defined_facts_lines(self):
yield 'defined_facts = ['
for fact in sorted(self.defined_facts):
yield f' {fact!r},'
yield '] # defined_facts'
def _full_implications_lines(self):
yield 'full_implications = dict( ['
for fact in sorted(self.defined_facts):
for value in (True, False):
yield f' # Implications of {fact} = {value}:'
yield f' (({fact!r}, {value!r}), set( ('
implications = self.full_implications[(fact, value)]
for implied in sorted(implications):
yield f' {implied!r},'
yield ' ) ),'
yield ' ),'
yield ' ] ) # full_implications'
def _prereq_lines(self):
yield 'prereq = {'
yield ''
for fact in sorted(self.prereq):
yield f' # facts that could determine the value of {fact}'
yield f' {fact!r}: {{'
for pfact in sorted(self.prereq[fact]):
yield f' {pfact!r},'
yield ' },'
yield ''
yield '} # prereq'
def _beta_rules_lines(self):
reverse_implications = defaultdict(list)
for n, (pre, implied) in enumerate(self.beta_rules):
reverse_implications[implied].append((pre, n))
yield '# Note: the order of the beta rules is used in the beta_triggers'
yield 'beta_rules = ['
yield ''
m = 0
indices = {}
for implied in sorted(reverse_implications):
fact, value = implied
yield f' # Rules implying {fact} = {value}'
for pre, n in reverse_implications[implied]:
indices[n] = m
m += 1
setstr = ", ".join(map(str, sorted(pre)))
yield f' ({{{setstr}}},'
yield f' {implied!r}),'
yield ''
yield '] # beta_rules'
yield 'beta_triggers = {'
for query in sorted(self.beta_triggers):
fact, value = query
triggers = [indices[n] for n in self.beta_triggers[query]]
yield f' {query!r}: {triggers!r},'
yield '} # beta_triggers'
def print_rules(self) -> Iterator[str]:
""" Returns a generator with lines to represent the facts and rules """
yield from self._defined_facts_lines()
yield ''
yield ''
yield from self._full_implications_lines()
yield ''
yield ''
yield from self._prereq_lines()
yield ''
yield ''
yield from self._beta_rules_lines()
yield ''
yield ''
yield "generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications,"
yield " 'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers}"
class InconsistentAssumptions(ValueError):
def __str__(self):
kb, fact, value = self.args
return "%s, %s=%s" % (kb, fact, value)
class FactKB(dict):
"""
A simple propositional knowledge base relying on compiled inference rules.
"""
def __str__(self):
return '{\n%s}' % ',\n'.join(
["\t%s: %s" % i for i in sorted(self.items())])
def __init__(self, rules):
self.rules = rules
def _tell(self, k, v):
"""Add fact k=v to the knowledge base.
Returns True if the KB has actually been updated, False otherwise.
"""
if k in self and self[k] is not None:
if self[k] == v:
return False
else:
raise InconsistentAssumptions(self, k, v)
else:
self[k] = v
return True
# *********************************************
# * This is the workhorse, so keep it *fast*. *
# *********************************************
def deduce_all_facts(self, facts):
"""
Update the KB with all the implications of a list of facts.
Facts can be specified as a dictionary or as a list of (key, value)
pairs.
"""
# keep frequently used attributes locally, so we'll avoid extra
# attribute access overhead
full_implications = self.rules.full_implications
beta_triggers = self.rules.beta_triggers
beta_rules = self.rules.beta_rules
if isinstance(facts, dict):
facts = facts.items()
while facts:
beta_maytrigger = set()
# --- alpha chains ---
for k, v in facts:
if not self._tell(k, v) or v is None:
continue
# lookup routing tables
for key, value in full_implications[k, v]:
self._tell(key, value)
beta_maytrigger.update(beta_triggers[k, v])
# --- beta chains ---
facts = []
for bidx in beta_maytrigger:
bcond, bimpl = beta_rules[bidx]
if all(self.get(k) is v for k, v in bcond):
facts.append(bimpl)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,532 @@
"""
The routines here were removed from numbers.py, power.py,
digits.py and factor_.py so they could be imported into core
without raising circular import errors.
Although the name 'intfunc' was chosen to represent functions that
work with integers, it can also be thought of as containing
internal/core functions that are needed by the classes of the core.
"""
import math
import sys
from functools import lru_cache
from .sympify import sympify
from .singleton import S
from sympy.external.gmpy import (gcd as number_gcd, lcm as number_lcm, sqrt,
iroot, bit_scan1, gcdext)
from sympy.utilities.misc import as_int, filldedent
def num_digits(n, base=10):
"""Return the number of digits needed to express n in give base.
Examples
========
>>> from sympy.core.intfunc import num_digits
>>> num_digits(10)
2
>>> num_digits(10, 2) # 1010 -> 4 digits
4
>>> num_digits(-100, 16) # -64 -> 2 digits
2
Parameters
==========
n: integer
The number whose digits are counted.
b: integer
The base in which digits are computed.
See Also
========
sympy.ntheory.digits.digits, sympy.ntheory.digits.count_digits
"""
if base < 0:
raise ValueError('base must be int greater than 1')
if not n:
return 1
e, t = integer_log(abs(n), base)
return 1 + e
def integer_log(n, b):
r"""
Returns ``(e, bool)`` where e is the largest nonnegative integer
such that :math:`|n| \geq |b^e|` and ``bool`` is True if $n = b^e$.
Examples
========
>>> from sympy import integer_log
>>> integer_log(125, 5)
(3, True)
>>> integer_log(17, 9)
(1, False)
If the base is positive and the number negative the
return value will always be the same except for 2:
>>> integer_log(-4, 2)
(2, False)
>>> integer_log(-16, 4)
(0, False)
When the base is negative, the returned value
will only be True if the parity of the exponent is
correct for the sign of the base:
>>> integer_log(4, -2)
(2, True)
>>> integer_log(8, -2)
(3, False)
>>> integer_log(-8, -2)
(3, True)
>>> integer_log(-4, -2)
(2, False)
See Also
========
integer_nthroot
sympy.ntheory.primetest.is_square
sympy.ntheory.factor_.multiplicity
sympy.ntheory.factor_.perfect_power
"""
n = as_int(n)
b = as_int(b)
if b < 0:
e, t = integer_log(abs(n), -b)
# (-2)**3 == -8
# (-2)**2 = 4
t = t and e % 2 == (n < 0)
return e, t
if b <= 1:
raise ValueError('base must be 2 or more')
if n < 0:
if b != 2:
return 0, False
e, t = integer_log(-n, b)
return e, False
if n == 0:
raise ValueError('n cannot be 0')
if n < b:
return 0, n == 1
if b == 2:
e = n.bit_length() - 1
return e, trailing(n) == e
t = trailing(b)
if 2**t == b:
e = int(n.bit_length() - 1)//t
n_ = 1 << (t*e)
return e, n_ == n
d = math.floor(math.log10(n) / math.log10(b))
n_ = b ** d
while n_ <= n: # this will iterate 0, 1 or 2 times
d += 1
n_ *= b
return d - (n_ > n), (n_ == n or n_//b == n)
def trailing(n):
"""Count the number of trailing zero digits in the binary
representation of n, i.e. determine the largest power of 2
that divides n.
Examples
========
>>> from sympy import trailing
>>> trailing(128)
7
>>> trailing(63)
0
See Also
========
sympy.ntheory.factor_.multiplicity
"""
if not n:
return 0
return bit_scan1(int(n))
@lru_cache(1024)
def igcd(*args):
"""Computes nonnegative integer greatest common divisor.
Explanation
===========
The algorithm is based on the well known Euclid's algorithm [1]_. To
improve speed, ``igcd()`` has its own caching mechanism.
If you do not need the cache mechanism, using ``sympy.external.gmpy.gcd``.
Examples
========
>>> from sympy import igcd
>>> igcd(2, 4)
2
>>> igcd(5, 10, 15)
5
References
==========
.. [1] https://en.wikipedia.org/wiki/Euclidean_algorithm
"""
if len(args) < 2:
raise TypeError("igcd() takes at least 2 arguments (%s given)" % len(args))
return int(number_gcd(*map(as_int, args)))
igcd2 = math.gcd
def igcd_lehmer(a, b):
r"""Computes greatest common divisor of two integers.
Explanation
===========
Euclid's algorithm for the computation of the greatest
common divisor ``gcd(a, b)`` of two (positive) integers
$a$ and $b$ is based on the division identity
$$ a = q \times b + r$$,
where the quotient $q$ and the remainder $r$ are integers
and $0 \le r < b$. Then each common divisor of $a$ and $b$
divides $r$, and it follows that ``gcd(a, b) == gcd(b, r)``.
The algorithm works by constructing the sequence
r0, r1, r2, ..., where r0 = a, r1 = b, and each rn
is the remainder from the division of the two preceding
elements.
In Python, ``q = a // b`` and ``r = a % b`` are obtained by the
floor division and the remainder operations, respectively.
These are the most expensive arithmetic operations, especially
for large a and b.
Lehmer's algorithm [1]_ is based on the observation that the quotients
``qn = r(n-1) // rn`` are in general small integers even
when a and b are very large. Hence the quotients can be
usually determined from a relatively small number of most
significant bits.
The efficiency of the algorithm is further enhanced by not
computing each long remainder in Euclid's sequence. The remainders
are linear combinations of a and b with integer coefficients
derived from the quotients. The coefficients can be computed
as far as the quotients can be determined from the chosen
most significant parts of a and b. Only then a new pair of
consecutive remainders is computed and the algorithm starts
anew with this pair.
References
==========
.. [1] https://en.wikipedia.org/wiki/Lehmer%27s_GCD_algorithm
"""
a, b = abs(as_int(a)), abs(as_int(b))
if a < b:
a, b = b, a
# The algorithm works by using one or two digit division
# whenever possible. The outer loop will replace the
# pair (a, b) with a pair of shorter consecutive elements
# of the Euclidean gcd sequence until a and b
# fit into two Python (long) int digits.
nbits = 2 * sys.int_info.bits_per_digit
while a.bit_length() > nbits and b != 0:
# Quotients are mostly small integers that can
# be determined from most significant bits.
n = a.bit_length() - nbits
x, y = int(a >> n), int(b >> n) # most significant bits
# Elements of the Euclidean gcd sequence are linear
# combinations of a and b with integer coefficients.
# Compute the coefficients of consecutive pairs
# a' = A*a + B*b, b' = C*a + D*b
# using small integer arithmetic as far as possible.
A, B, C, D = 1, 0, 0, 1 # initial values
while True:
# The coefficients alternate in sign while looping.
# The inner loop combines two steps to keep track
# of the signs.
# At this point we have
# A > 0, B <= 0, C <= 0, D > 0,
# x' = x + B <= x < x" = x + A,
# y' = y + C <= y < y" = y + D,
# and
# x'*N <= a' < x"*N, y'*N <= b' < y"*N,
# where N = 2**n.
# Now, if y' > 0, and x"//y' and x'//y" agree,
# then their common value is equal to q = a'//b'.
# In addition,
# x'%y" = x' - q*y" < x" - q*y' = x"%y',
# and
# (x'%y")*N < a'%b' < (x"%y')*N.
# On the other hand, we also have x//y == q,
# and therefore
# x'%y" = x + B - q*(y + D) = x%y + B',
# x"%y' = x + A - q*(y + C) = x%y + A',
# where
# B' = B - q*D < 0, A' = A - q*C > 0.
if y + C <= 0:
break
q = (x + A) // (y + C)
# Now x'//y" <= q, and equality holds if
# x' - q*y" = (x - q*y) + (B - q*D) >= 0.
# This is a minor optimization to avoid division.
x_qy, B_qD = x - q * y, B - q * D
if x_qy + B_qD < 0:
break
# Next step in the Euclidean sequence.
x, y = y, x_qy
A, B, C, D = C, D, A - q * C, B_qD
# At this point the signs of the coefficients
# change and their roles are interchanged.
# A <= 0, B > 0, C > 0, D < 0,
# x' = x + A <= x < x" = x + B,
# y' = y + D < y < y" = y + C.
if y + D <= 0:
break
q = (x + B) // (y + D)
x_qy, A_qC = x - q * y, A - q * C
if x_qy + A_qC < 0:
break
x, y = y, x_qy
A, B, C, D = C, D, A_qC, B - q * D
# Now the conditions on top of the loop
# are again satisfied.
# A > 0, B < 0, C < 0, D > 0.
if B == 0:
# This can only happen when y == 0 in the beginning
# and the inner loop does nothing.
# Long division is forced.
a, b = b, a % b
continue
# Compute new long arguments using the coefficients.
a, b = A * a + B * b, C * a + D * b
# Small divisors. Finish with the standard algorithm.
while b:
a, b = b, a % b
return a
def ilcm(*args):
"""Computes integer least common multiple.
Examples
========
>>> from sympy import ilcm
>>> ilcm(5, 10)
10
>>> ilcm(7, 3)
21
>>> ilcm(5, 10, 15)
30
"""
if len(args) < 2:
raise TypeError("ilcm() takes at least 2 arguments (%s given)" % len(args))
return int(number_lcm(*map(as_int, args)))
def igcdex(a, b):
"""Returns x, y, g such that g = x*a + y*b = gcd(a, b).
Examples
========
>>> from sympy.core.intfunc import igcdex
>>> igcdex(2, 3)
(-1, 1, 1)
>>> igcdex(10, 12)
(-1, 1, 2)
>>> x, y, g = igcdex(100, 2004)
>>> x, y, g
(-20, 1, 4)
>>> x*100 + y*2004
4
"""
if (not a) and (not b):
return (0, 1, 0)
g, x, y = gcdext(int(a), int(b))
return x, y, g
def mod_inverse(a, m):
r"""
Return the number $c$ such that, $a \times c = 1 \pmod{m}$
where $c$ has the same sign as $m$. If no such value exists,
a ValueError is raised.
Examples
========
>>> from sympy import mod_inverse, S
Suppose we wish to find multiplicative inverse $x$ of
3 modulo 11. This is the same as finding $x$ such
that $3x = 1 \pmod{11}$. One value of x that satisfies
this congruence is 4. Because $3 \times 4 = 12$ and $12 = 1 \pmod{11}$.
This is the value returned by ``mod_inverse``:
>>> mod_inverse(3, 11)
4
>>> mod_inverse(-3, 11)
7
When there is a common factor between the numerators of
`a` and `m` the inverse does not exist:
>>> mod_inverse(2, 4)
Traceback (most recent call last):
...
ValueError: inverse of 2 mod 4 does not exist
>>> mod_inverse(S(2)/7, S(5)/2)
7/2
References
==========
.. [1] https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
.. [2] https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
"""
c = None
try:
a, m = as_int(a), as_int(m)
if m != 1 and m != -1:
x, _, g = igcdex(a, m)
if g == 1:
c = x % m
except ValueError:
a, m = sympify(a), sympify(m)
if not (a.is_number and m.is_number):
raise TypeError(
filldedent(
"""
Expected numbers for arguments; symbolic `mod_inverse`
is not implemented
but symbolic expressions can be handled with the
similar function,
sympy.polys.polytools.invert"""
)
)
big = m > 1
if big not in (S.true, S.false):
raise ValueError("m > 1 did not evaluate; try to simplify %s" % m)
elif big:
c = 1 / a
if c is None:
raise ValueError("inverse of %s (mod %s) does not exist" % (a, m))
return c
def isqrt(n):
r""" Return the largest integer less than or equal to `\sqrt{n}`.
Parameters
==========
n : non-negative integer
Returns
=======
int : `\left\lfloor\sqrt{n}\right\rfloor`
Raises
======
ValueError
If n is negative.
TypeError
If n is of a type that cannot be compared to ``int``.
Therefore, a TypeError is raised for ``str``, but not for ``float``.
Examples
========
>>> from sympy.core.intfunc import isqrt
>>> isqrt(0)
0
>>> isqrt(9)
3
>>> isqrt(10)
3
>>> isqrt("30")
Traceback (most recent call last):
...
TypeError: '<' not supported between instances of 'str' and 'int'
>>> from sympy.core.numbers import Rational
>>> isqrt(Rational(-1, 2))
Traceback (most recent call last):
...
ValueError: n must be nonnegative
"""
if n < 0:
raise ValueError("n must be nonnegative")
return int(sqrt(int(n)))
def integer_nthroot(y, n):
"""
Return a tuple containing x = floor(y**(1/n))
and a boolean indicating whether the result is exact (that is,
whether x**n == y).
Examples
========
>>> from sympy import integer_nthroot
>>> integer_nthroot(16, 2)
(4, True)
>>> integer_nthroot(26, 2)
(5, False)
To simply determine if a number is a perfect square, the is_square
function should be used:
>>> from sympy.ntheory.primetest import is_square
>>> is_square(26)
False
See Also
========
sympy.ntheory.primetest.is_square
integer_log
"""
x, b = iroot(as_int(y), as_int(n))
return int(x), b

View File

@ -0,0 +1,388 @@
"""
Module to efficiently partition SymPy objects.
This system is introduced because class of SymPy object does not always
represent the mathematical classification of the entity. For example,
``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance
of ``Integral`` class. However the former is number and the latter is
matrix.
One way to resolve this is defining subclass for each mathematical type,
such as ``MatAdd`` for the addition between matrices. Basic algebraic
operation such as addition or multiplication take this approach, but
defining every class for every mathematical object is not scalable.
Therefore, we define the "kind" of the object and let the expression
infer the kind of itself from its arguments. Function and class can
filter the arguments by their kind, and behave differently according to
the type of itself.
This module defines basic kinds for core objects. Other kinds such as
``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules.
.. notes::
This approach is experimental, and can be replaced or deleted in the future.
See https://github.com/sympy/sympy/pull/20549.
"""
from collections import defaultdict
from .cache import cacheit
from sympy.multipledispatch.dispatcher import (Dispatcher,
ambiguity_warn, ambiguity_register_error_ignore_dup,
str_signature, RaiseNotImplementedError)
class KindMeta(type):
"""
Metaclass for ``Kind``.
Assigns empty ``dict`` as class attribute ``_inst`` for every class,
in order to endow singleton-like behavior.
"""
def __new__(cls, clsname, bases, dct):
dct['_inst'] = {}
return super().__new__(cls, clsname, bases, dct)
class Kind(object, metaclass=KindMeta):
"""
Base class for kinds.
Kind of the object represents the mathematical classification that
the entity falls into. It is expected that functions and classes
recognize and filter the argument by its kind.
Kind of every object must be carefully selected so that it shows the
intention of design. Expressions may have different kind according
to the kind of its arguments. For example, arguments of ``Add``
must have common kind since addition is group operator, and the
resulting ``Add()`` has the same kind.
For the performance, each kind is as broad as possible and is not
based on set theory. For example, ``NumberKind`` includes not only
complex number but expression containing ``S.Infinity`` or ``S.NaN``
which are not strictly number.
Kind may have arguments as parameter. For example, ``MatrixKind()``
may be constructed with one element which represents the kind of its
elements.
``Kind`` behaves in singleton-like fashion. Same signature will
return the same object.
"""
def __new__(cls, *args):
if args in cls._inst:
inst = cls._inst[args]
else:
inst = super().__new__(cls)
cls._inst[args] = inst
return inst
class _UndefinedKind(Kind):
"""
Default kind for all SymPy object. If the kind is not defined for
the object, or if the object cannot infer the kind from its
arguments, this will be returned.
Examples
========
>>> from sympy import Expr
>>> Expr().kind
UndefinedKind
"""
def __new__(cls):
return super().__new__(cls)
def __repr__(self):
return "UndefinedKind"
UndefinedKind = _UndefinedKind()
class _NumberKind(Kind):
"""
Kind for all numeric object.
This kind represents every number, including complex numbers,
infinity and ``S.NaN``. Other objects such as quaternions do not
have this kind.
Most ``Expr`` are initially designed to represent the number, so
this will be the most common kind in SymPy core. For example
``Symbol()``, which represents a scalar, has this kind as long as it
is commutative.
Numbers form a field. Any operation between number-kind objects will
result this kind as well.
Examples
========
>>> from sympy import S, oo, Symbol
>>> S.One.kind
NumberKind
>>> (-oo).kind
NumberKind
>>> S.NaN.kind
NumberKind
Commutative symbol are treated as number.
>>> x = Symbol('x')
>>> x.kind
NumberKind
>>> Symbol('y', commutative=False).kind
UndefinedKind
Operation between numbers results number.
>>> (x+1).kind
NumberKind
See Also
========
sympy.core.expr.Expr.is_Number : check if the object is strictly
subclass of ``Number`` class.
sympy.core.expr.Expr.is_number : check if the object is number
without any free symbol.
"""
def __new__(cls):
return super().__new__(cls)
def __repr__(self):
return "NumberKind"
NumberKind = _NumberKind()
class _BooleanKind(Kind):
"""
Kind for boolean objects.
SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False``
have this kind. Boolean number ``1`` and ``0`` are not relevant.
Examples
========
>>> from sympy import S, Q
>>> S.true.kind
BooleanKind
>>> Q.even(3).kind
BooleanKind
"""
def __new__(cls):
return super().__new__(cls)
def __repr__(self):
return "BooleanKind"
BooleanKind = _BooleanKind()
class KindDispatcher:
"""
Dispatcher to select a kind from multiple kinds by binary dispatching.
.. notes::
This approach is experimental, and can be replaced or deleted in
the future.
Explanation
===========
SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the
algebraic structure where the object belongs to. Therefore, with
given operation, we can always find a dominating kind among the
different kinds. This class selects the kind by recursive binary
dispatching. If the result cannot be determined, ``UndefinedKind``
is returned.
Examples
========
Multiplication between numbers return number.
>>> from sympy import NumberKind, Mul
>>> Mul._kind_dispatcher(NumberKind, NumberKind)
NumberKind
Multiplication between number and unknown-kind object returns unknown kind.
>>> from sympy import UndefinedKind
>>> Mul._kind_dispatcher(NumberKind, UndefinedKind)
UndefinedKind
Any number and order of kinds is allowed.
>>> Mul._kind_dispatcher(UndefinedKind, NumberKind)
UndefinedKind
>>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind)
UndefinedKind
Since matrix forms a vector space over scalar field, multiplication
between matrix with numeric element and number returns matrix with
numeric element.
>>> from sympy.matrices import MatrixKind
>>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind)
MatrixKind(NumberKind)
If a matrix with number element and another matrix with unknown-kind
element are multiplied, we know that the result is matrix but the
kind of its elements is unknown.
>>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind))
MatrixKind(UndefinedKind)
Parameters
==========
name : str
commutative : bool, optional
If True, binary dispatch will be automatically registered in
reversed order as well.
doc : str, optional
"""
def __init__(self, name, commutative=False, doc=None):
self.name = name
self.doc = doc
self.commutative = commutative
self._dispatcher = Dispatcher(name)
def __repr__(self):
return "<dispatched %s>" % self.name
def register(self, *types, **kwargs):
"""
Register the binary dispatcher for two kind classes.
If *self.commutative* is ``True``, signature in reversed order is
automatically registered as well.
"""
on_ambiguity = kwargs.pop("on_ambiguity", None)
if not on_ambiguity:
if self.commutative:
on_ambiguity = ambiguity_register_error_ignore_dup
else:
on_ambiguity = ambiguity_warn
kwargs.update(on_ambiguity=on_ambiguity)
if not len(types) == 2:
raise RuntimeError(
"Only binary dispatch is supported, but got %s types: <%s>." % (
len(types), str_signature(types)
))
def _(func):
self._dispatcher.add(types, func, **kwargs)
if self.commutative:
self._dispatcher.add(tuple(reversed(types)), func, **kwargs)
return _
def __call__(self, *args, **kwargs):
if self.commutative:
kinds = frozenset(args)
else:
kinds = []
prev = None
for a in args:
if prev is not a:
kinds.append(a)
prev = a
return self.dispatch_kinds(kinds, **kwargs)
@cacheit
def dispatch_kinds(self, kinds, **kwargs):
# Quick exit for the case where all kinds are same
if len(kinds) == 1:
result, = kinds
if not isinstance(result, Kind):
raise RuntimeError("%s is not a kind." % result)
return result
for i,kind in enumerate(kinds):
if not isinstance(kind, Kind):
raise RuntimeError("%s is not a kind." % kind)
if i == 0:
result = kind
else:
prev_kind = result
t1, t2 = type(prev_kind), type(kind)
k1, k2 = prev_kind, kind
func = self._dispatcher.dispatch(t1, t2)
if func is None and self.commutative:
# try reversed order
func = self._dispatcher.dispatch(t2, t1)
k1, k2 = k2, k1
if func is None:
# unregistered kind relation
result = UndefinedKind
else:
result = func(k1, k2)
if not isinstance(result, Kind):
raise RuntimeError(
"Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format(
prev_kind, kind, result
))
return result
@property
def __doc__(self):
docs = [
"Kind dispatcher : %s" % self.name,
"Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details"
]
if self.doc:
docs.append(self.doc)
s = "Registered kind classes\n"
s += '=' * len(s)
docs.append(s)
amb_sigs = []
typ_sigs = defaultdict(list)
for sigs in self._dispatcher.ordering[::-1]:
key = self._dispatcher.funcs[sigs]
typ_sigs[key].append(sigs)
for func, sigs in typ_sigs.items():
sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)
if isinstance(func, RaiseNotImplementedError):
amb_sigs.append(sigs_str)
continue
s = 'Inputs: %s\n' % sigs_str
s += '-' * len(s) + '\n'
if func.__doc__:
s += func.__doc__.strip()
else:
s += func.__name__
docs.append(s)
if amb_sigs:
s = "Ambiguous kind classes\n"
s += '=' * len(s)
docs.append(s)
s = '\n'.join(amb_sigs)
docs.append(s)
return '\n\n'.join(docs)

View File

@ -0,0 +1,427 @@
"""Logic expressions handling
NOTE
----
at present this is mainly needed for facts.py, feel free however to improve
this stuff for general purpose.
"""
from __future__ import annotations
from typing import Optional
# Type of a fuzzy bool
FuzzyBool = Optional[bool]
def _torf(args):
"""Return True if all args are True, False if they
are all False, else None.
>>> from sympy.core.logic import _torf
>>> _torf((True, True))
True
>>> _torf((False, False))
False
>>> _torf((True, False))
"""
sawT = sawF = False
for a in args:
if a is True:
if sawF:
return
sawT = True
elif a is False:
if sawT:
return
sawF = True
else:
return
return sawT
def _fuzzy_group(args, quick_exit=False):
"""Return True if all args are True, None if there is any None else False
unless ``quick_exit`` is True (then return None as soon as a second False
is seen.
``_fuzzy_group`` is like ``fuzzy_and`` except that it is more
conservative in returning a False, waiting to make sure that all
arguments are True or False and returning None if any arguments are
None. It also has the capability of permiting only a single False and
returning None if more than one is seen. For example, the presence of a
single transcendental amongst rationals would indicate that the group is
no longer rational; but a second transcendental in the group would make the
determination impossible.
Examples
========
>>> from sympy.core.logic import _fuzzy_group
By default, multiple Falses mean the group is broken:
>>> _fuzzy_group([False, False, True])
False
If multiple Falses mean the group status is unknown then set
`quick_exit` to True so None can be returned when the 2nd False is seen:
>>> _fuzzy_group([False, False, True], quick_exit=True)
But if only a single False is seen then the group is known to
be broken:
>>> _fuzzy_group([False, True, True], quick_exit=True)
False
"""
saw_other = False
for a in args:
if a is True:
continue
if a is None:
return
if quick_exit and saw_other:
return
saw_other = True
return not saw_other
def fuzzy_bool(x):
"""Return True, False or None according to x.
Whereas bool(x) returns True or False, fuzzy_bool allows
for the None value and non-false values (which become None), too.
Examples
========
>>> from sympy.core.logic import fuzzy_bool
>>> from sympy.abc import x
>>> fuzzy_bool(x), fuzzy_bool(None)
(None, None)
>>> bool(x), bool(None)
(True, False)
"""
if x is None:
return None
if x in (True, False):
return bool(x)
def fuzzy_and(args):
"""Return True (all True), False (any False) or None.
Examples
========
>>> from sympy.core.logic import fuzzy_and
>>> from sympy import Dummy
If you had a list of objects to test the commutivity of
and you want the fuzzy_and logic applied, passing an
iterator will allow the commutativity to only be computed
as many times as necessary. With this list, False can be
returned after analyzing the first symbol:
>>> syms = [Dummy(commutative=False), Dummy()]
>>> fuzzy_and(s.is_commutative for s in syms)
False
That False would require less work than if a list of pre-computed
items was sent:
>>> fuzzy_and([s.is_commutative for s in syms])
False
"""
rv = True
for ai in args:
ai = fuzzy_bool(ai)
if ai is False:
return False
if rv: # this will stop updating if a None is ever trapped
rv = ai
return rv
def fuzzy_not(v):
"""
Not in fuzzy logic
Return None if `v` is None else `not v`.
Examples
========
>>> from sympy.core.logic import fuzzy_not
>>> fuzzy_not(True)
False
>>> fuzzy_not(None)
>>> fuzzy_not(False)
True
"""
if v is None:
return v
else:
return not v
def fuzzy_or(args):
"""
Or in fuzzy logic. Returns True (any True), False (all False), or None
See the docstrings of fuzzy_and and fuzzy_not for more info. fuzzy_or is
related to the two by the standard De Morgan's law.
>>> from sympy.core.logic import fuzzy_or
>>> fuzzy_or([True, False])
True
>>> fuzzy_or([True, None])
True
>>> fuzzy_or([False, False])
False
>>> print(fuzzy_or([False, None]))
None
"""
rv = False
for ai in args:
ai = fuzzy_bool(ai)
if ai is True:
return True
if rv is False: # this will stop updating if a None is ever trapped
rv = ai
return rv
def fuzzy_xor(args):
"""Return None if any element of args is not True or False, else
True (if there are an odd number of True elements), else False."""
t = f = 0
for a in args:
ai = fuzzy_bool(a)
if ai:
t += 1
elif ai is False:
f += 1
else:
return
return t % 2 == 1
def fuzzy_nand(args):
"""Return False if all args are True, True if they are all False,
else None."""
return fuzzy_not(fuzzy_and(args))
class Logic:
"""Logical expression"""
# {} 'op' -> LogicClass
op_2class: dict[str, type[Logic]] = {}
def __new__(cls, *args):
obj = object.__new__(cls)
obj.args = args
return obj
def __getnewargs__(self):
return self.args
def __hash__(self):
return hash((type(self).__name__,) + tuple(self.args))
def __eq__(a, b):
if not isinstance(b, type(a)):
return False
else:
return a.args == b.args
def __ne__(a, b):
if not isinstance(b, type(a)):
return True
else:
return a.args != b.args
def __lt__(self, other):
if self.__cmp__(other) == -1:
return True
return False
def __cmp__(self, other):
if type(self) is not type(other):
a = str(type(self))
b = str(type(other))
else:
a = self.args
b = other.args
return (a > b) - (a < b)
def __str__(self):
return '%s(%s)' % (self.__class__.__name__,
', '.join(str(a) for a in self.args))
__repr__ = __str__
@staticmethod
def fromstring(text):
"""Logic from string with space around & and | but none after !.
e.g.
!a & b | c
"""
lexpr = None # current logical expression
schedop = None # scheduled operation
for term in text.split():
# operation symbol
if term in '&|':
if schedop is not None:
raise ValueError(
'double op forbidden: "%s %s"' % (term, schedop))
if lexpr is None:
raise ValueError(
'%s cannot be in the beginning of expression' % term)
schedop = term
continue
if '&' in term or '|' in term:
raise ValueError('& and | must have space around them')
if term[0] == '!':
if len(term) == 1:
raise ValueError('do not include space after "!"')
term = Not(term[1:])
# already scheduled operation, e.g. '&'
if schedop:
lexpr = Logic.op_2class[schedop](lexpr, term)
schedop = None
continue
# this should be atom
if lexpr is not None:
raise ValueError(
'missing op between "%s" and "%s"' % (lexpr, term))
lexpr = term
# let's check that we ended up in correct state
if schedop is not None:
raise ValueError('premature end-of-expression in "%s"' % text)
if lexpr is None:
raise ValueError('"%s" is empty' % text)
# everything looks good now
return lexpr
class AndOr_Base(Logic):
def __new__(cls, *args):
bargs = []
for a in args:
if a == cls.op_x_notx:
return a
elif a == (not cls.op_x_notx):
continue # skip this argument
bargs.append(a)
args = sorted(set(cls.flatten(bargs)), key=hash)
for a in args:
if Not(a) in args:
return cls.op_x_notx
if len(args) == 1:
return args.pop()
elif len(args) == 0:
return not cls.op_x_notx
return Logic.__new__(cls, *args)
@classmethod
def flatten(cls, args):
# quick-n-dirty flattening for And and Or
args_queue = list(args)
res = []
while True:
try:
arg = args_queue.pop(0)
except IndexError:
break
if isinstance(arg, Logic):
if isinstance(arg, cls):
args_queue.extend(arg.args)
continue
res.append(arg)
args = tuple(res)
return args
class And(AndOr_Base):
op_x_notx = False
def _eval_propagate_not(self):
# !(a&b&c ...) == !a | !b | !c ...
return Or(*[Not(a) for a in self.args])
# (a|b|...) & c == (a&c) | (b&c) | ...
def expand(self):
# first locate Or
for i, arg in enumerate(self.args):
if isinstance(arg, Or):
arest = self.args[:i] + self.args[i + 1:]
orterms = [And(*(arest + (a,))) for a in arg.args]
for j in range(len(orterms)):
if isinstance(orterms[j], Logic):
orterms[j] = orterms[j].expand()
res = Or(*orterms)
return res
return self
class Or(AndOr_Base):
op_x_notx = True
def _eval_propagate_not(self):
# !(a|b|c ...) == !a & !b & !c ...
return And(*[Not(a) for a in self.args])
class Not(Logic):
def __new__(cls, arg):
if isinstance(arg, str):
return Logic.__new__(cls, arg)
elif isinstance(arg, bool):
return not arg
elif isinstance(arg, Not):
return arg.args[0]
elif isinstance(arg, Logic):
# XXX this is a hack to expand right from the beginning
arg = arg._eval_propagate_not()
return arg
else:
raise ValueError('Not: unknown argument %r' % (arg,))
@property
def arg(self):
return self.args[0]
Logic.op_2class['&'] = And
Logic.op_2class['|'] = Or
Logic.op_2class['!'] = Not

View File

@ -0,0 +1,260 @@
from .add import Add
from .exprtools import gcd_terms
from .function import Function
from .kind import NumberKind
from .logic import fuzzy_and, fuzzy_not
from .mul import Mul
from .numbers import equal_valued
from .singleton import S
class Mod(Function):
"""Represents a modulo operation on symbolic expressions.
Parameters
==========
p : Expr
Dividend.
q : Expr
Divisor.
Notes
=====
The convention used is the same as Python's: the remainder always has the
same sign as the divisor.
Many objects can be evaluated modulo ``n`` much faster than they can be
evaluated directly (or at all). For this, ``evaluate=False`` is
necessary to prevent eager evaluation:
>>> from sympy import binomial, factorial, Mod, Pow
>>> Mod(Pow(2, 10**16, evaluate=False), 97)
61
>>> Mod(factorial(10**9, evaluate=False), 10**9 + 9)
712524808
>>> Mod(binomial(10**18, 10**12, evaluate=False), (10**5 + 3)**2)
3744312326
Examples
========
>>> from sympy.abc import x, y
>>> x**2 % y
Mod(x**2, y)
>>> _.subs({x: 5, y: 6})
1
"""
kind = NumberKind
@classmethod
def eval(cls, p, q):
def number_eval(p, q):
"""Try to return p % q if both are numbers or +/-p is known
to be less than or equal q.
"""
if q.is_zero:
raise ZeroDivisionError("Modulo by zero")
if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False:
return S.NaN
if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1):
return S.Zero
if q.is_Number:
if p.is_Number:
return p%q
if q == 2:
if p.is_even:
return S.Zero
elif p.is_odd:
return S.One
if hasattr(p, '_eval_Mod'):
rv = getattr(p, '_eval_Mod')(q)
if rv is not None:
return rv
# by ratio
r = p/q
if r.is_integer:
return S.Zero
try:
d = int(r)
except TypeError:
pass
else:
if isinstance(d, int):
rv = p - d*q
if (rv*q < 0) == True:
rv += q
return rv
# by difference
# -2|q| < p < 2|q|
d = abs(p)
for _ in range(2):
d -= abs(q)
if d.is_negative:
if q.is_positive:
if p.is_positive:
return d + q
elif p.is_negative:
return -d
elif q.is_negative:
if p.is_positive:
return d
elif p.is_negative:
return -d + q
break
rv = number_eval(p, q)
if rv is not None:
return rv
# denest
if isinstance(p, cls):
qinner = p.args[1]
if qinner % q == 0:
return cls(p.args[0], q)
elif (qinner*(q - qinner)).is_nonnegative:
# |qinner| < |q| and have same sign
return p
elif isinstance(-p, cls):
qinner = (-p).args[1]
if qinner % q == 0:
return cls(-(-p).args[0], q)
elif (qinner*(q + qinner)).is_nonpositive:
# |qinner| < |q| and have different sign
return p
elif isinstance(p, Add):
# separating into modulus and non modulus
both_l = non_mod_l, mod_l = [], []
for arg in p.args:
both_l[isinstance(arg, cls)].append(arg)
# if q same for all
if mod_l and all(inner.args[1] == q for inner in mod_l):
net = Add(*non_mod_l) + Add(*[i.args[0] for i in mod_l])
return cls(net, q)
elif isinstance(p, Mul):
# separating into modulus and non modulus
both_l = non_mod_l, mod_l = [], []
for arg in p.args:
both_l[isinstance(arg, cls)].append(arg)
if mod_l and all(inner.args[1] == q for inner in mod_l) and all(t.is_integer for t in p.args) and q.is_integer:
# finding distributive term
non_mod_l = [cls(x, q) for x in non_mod_l]
mod = []
non_mod = []
for j in non_mod_l:
if isinstance(j, cls):
mod.append(j.args[0])
else:
non_mod.append(j)
prod_mod = Mul(*mod)
prod_non_mod = Mul(*non_mod)
prod_mod1 = Mul(*[i.args[0] for i in mod_l])
net = prod_mod1*prod_mod
return prod_non_mod*cls(net, q)
if q.is_Integer and q is not S.One:
if all(t.is_integer for t in p.args):
non_mod_l = [i % q if i.is_Integer else i for i in p.args]
if any(iq is S.Zero for iq in non_mod_l):
return S.Zero
p = Mul(*(non_mod_l + mod_l))
# XXX other possibilities?
from sympy.polys.polyerrors import PolynomialError
from sympy.polys.polytools import gcd
# extract gcd; any further simplification should be done by the user
try:
G = gcd(p, q)
if not equal_valued(G, 1):
p, q = [gcd_terms(i/G, clear=False, fraction=False)
for i in (p, q)]
except PolynomialError: # issue 21373
G = S.One
pwas, qwas = p, q
# simplify terms
# (x + y + 2) % x -> Mod(y + 2, x)
if p.is_Add:
args = []
for i in p.args:
a = cls(i, q)
if a.count(cls) > i.count(cls):
args.append(i)
else:
args.append(a)
if args != list(p.args):
p = Add(*args)
else:
# handle coefficients if they are not Rational
# since those are not handled by factor_terms
# e.g. Mod(.6*x, .3*y) -> 0.3*Mod(2*x, y)
cp, p = p.as_coeff_Mul()
cq, q = q.as_coeff_Mul()
ok = False
if not cp.is_Rational or not cq.is_Rational:
r = cp % cq
if equal_valued(r, 0):
G *= cq
p *= int(cp/cq)
ok = True
if not ok:
p = cp*p
q = cq*q
# simple -1 extraction
if p.could_extract_minus_sign() and q.could_extract_minus_sign():
G, p, q = [-i for i in (G, p, q)]
# check again to see if p and q can now be handled as numbers
rv = number_eval(p, q)
if rv is not None:
return rv*G
# put 1.0 from G on inside
if G.is_Float and equal_valued(G, 1):
p *= G
return cls(p, q, evaluate=False)
elif G.is_Mul and G.args[0].is_Float and equal_valued(G.args[0], 1):
p = G.args[0]*p
G = Mul._from_args(G.args[1:])
return G*cls(p, q, evaluate=(p, q) != (pwas, qwas))
def _eval_is_integer(self):
p, q = self.args
if fuzzy_and([p.is_integer, q.is_integer, fuzzy_not(q.is_zero)]):
return True
def _eval_is_nonnegative(self):
if self.args[1].is_positive:
return True
def _eval_is_nonpositive(self):
if self.args[1].is_negative:
return True
def _eval_rewrite_as_floor(self, a, b, **kwargs):
from sympy.functions.elementary.integers import floor
return a - b*floor(a/b)
def _eval_as_leading_term(self, x, logx=None, cdir=0):
from sympy.functions.elementary.integers import floor
return self.rewrite(floor)._eval_as_leading_term(x, logx=logx, cdir=cdir)
def _eval_nseries(self, x, n, logx, cdir=0):
from sympy.functions.elementary.integers import floor
return self.rewrite(floor)._eval_nseries(x, n, logx=logx, cdir=cdir)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,131 @@
"""
Provides functionality for multidimensional usage of scalar-functions.
Read the vectorize docstring for more details.
"""
from functools import wraps
def apply_on_element(f, args, kwargs, n):
"""
Returns a structure with the same dimension as the specified argument,
where each basic element is replaced by the function f applied on it. All
other arguments stay the same.
"""
# Get the specified argument.
if isinstance(n, int):
structure = args[n]
is_arg = True
elif isinstance(n, str):
structure = kwargs[n]
is_arg = False
# Define reduced function that is only dependent on the specified argument.
def f_reduced(x):
if hasattr(x, "__iter__"):
return list(map(f_reduced, x))
else:
if is_arg:
args[n] = x
else:
kwargs[n] = x
return f(*args, **kwargs)
# f_reduced will call itself recursively so that in the end f is applied to
# all basic elements.
return list(map(f_reduced, structure))
def iter_copy(structure):
"""
Returns a copy of an iterable object (also copying all embedded iterables).
"""
return [iter_copy(i) if hasattr(i, "__iter__") else i for i in structure]
def structure_copy(structure):
"""
Returns a copy of the given structure (numpy-array, list, iterable, ..).
"""
if hasattr(structure, "copy"):
return structure.copy()
return iter_copy(structure)
class vectorize:
"""
Generalizes a function taking scalars to accept multidimensional arguments.
Examples
========
>>> from sympy import vectorize, diff, sin, symbols, Function
>>> x, y, z = symbols('x y z')
>>> f, g, h = list(map(Function, 'fgh'))
>>> @vectorize(0)
... def vsin(x):
... return sin(x)
>>> vsin([1, x, y])
[sin(1), sin(x), sin(y)]
>>> @vectorize(0, 1)
... def vdiff(f, y):
... return diff(f, y)
>>> vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z])
[[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]]
"""
def __init__(self, *mdargs):
"""
The given numbers and strings characterize the arguments that will be
treated as data structures, where the decorated function will be applied
to every single element.
If no argument is given, everything is treated multidimensional.
"""
for a in mdargs:
if not isinstance(a, (int, str)):
raise TypeError("a is of invalid type")
self.mdargs = mdargs
def __call__(self, f):
"""
Returns a wrapper for the one-dimensional function that can handle
multidimensional arguments.
"""
@wraps(f)
def wrapper(*args, **kwargs):
# Get arguments that should be treated multidimensional
if self.mdargs:
mdargs = self.mdargs
else:
mdargs = range(len(args)) + kwargs.keys()
arglength = len(args)
for n in mdargs:
if isinstance(n, int):
if n >= arglength:
continue
entry = args[n]
is_arg = True
elif isinstance(n, str):
try:
entry = kwargs[n]
except KeyError:
continue
is_arg = False
if hasattr(entry, "__iter__"):
# Create now a copy of the given array and manipulate then
# the entries directly.
if is_arg:
args = list(args)
args[n] = structure_copy(entry)
else:
kwargs[n] = structure_copy(entry)
result = apply_on_element(wrapper, args, kwargs, n)
return result
return f(*args, **kwargs)
return wrapper

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,718 @@
from __future__ import annotations
from operator import attrgetter
from collections import defaultdict
from sympy.utilities.exceptions import sympy_deprecation_warning
from .sympify import _sympify as _sympify_, sympify
from .basic import Basic
from .cache import cacheit
from .sorting import ordered
from .logic import fuzzy_and
from .parameters import global_parameters
from sympy.utilities.iterables import sift
from sympy.multipledispatch.dispatcher import (Dispatcher,
ambiguity_register_error_ignore_dup,
str_signature, RaiseNotImplementedError)
class AssocOp(Basic):
""" Associative operations, can separate noncommutative and
commutative parts.
(a op b) op c == a op (b op c) == a op b op c.
Base class for Add and Mul.
This is an abstract base class, concrete derived classes must define
the attribute `identity`.
.. deprecated:: 1.7
Using arguments that aren't subclasses of :class:`~.Expr` in core
operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
deprecated. See :ref:`non-expr-args-deprecated` for details.
Parameters
==========
*args :
Arguments which are operated
evaluate : bool, optional
Evaluate the operation. If not passed, refer to ``global_parameters.evaluate``.
"""
# for performance reason, we don't let is_commutative go to assumptions,
# and keep it right here
__slots__: tuple[str, ...] = ('is_commutative',)
_args_type: type[Basic] | None = None
@cacheit
def __new__(cls, *args, evaluate=None, _sympify=True):
# Allow faster processing by passing ``_sympify=False``, if all arguments
# are already sympified.
if _sympify:
args = list(map(_sympify_, args))
# Disallow non-Expr args in Add/Mul
typ = cls._args_type
if typ is not None:
from .relational import Relational
if any(isinstance(arg, Relational) for arg in args):
raise TypeError("Relational cannot be used in %s" % cls.__name__)
# This should raise TypeError once deprecation period is over:
for arg in args:
if not isinstance(arg, typ):
sympy_deprecation_warning(
f"""
Using non-Expr arguments in {cls.__name__} is deprecated (in this case, one of
the arguments has type {type(arg).__name__!r}).
If you really did intend to use a multiplication or addition operation with
this object, use the * or + operator instead.
""",
deprecated_since_version="1.7",
active_deprecations_target="non-expr-args-deprecated",
stacklevel=4,
)
if evaluate is None:
evaluate = global_parameters.evaluate
if not evaluate:
obj = cls._from_args(args)
obj = cls._exec_constructor_postprocessors(obj)
return obj
args = [a for a in args if a is not cls.identity]
if len(args) == 0:
return cls.identity
if len(args) == 1:
return args[0]
c_part, nc_part, order_symbols = cls.flatten(args)
is_commutative = not nc_part
obj = cls._from_args(c_part + nc_part, is_commutative)
obj = cls._exec_constructor_postprocessors(obj)
if order_symbols is not None:
from sympy.series.order import Order
return Order(obj, *order_symbols)
return obj
@classmethod
def _from_args(cls, args, is_commutative=None):
"""Create new instance with already-processed args.
If the args are not in canonical order, then a non-canonical
result will be returned, so use with caution. The order of
args may change if the sign of the args is changed."""
if len(args) == 0:
return cls.identity
elif len(args) == 1:
return args[0]
obj = super().__new__(cls, *args)
if is_commutative is None:
is_commutative = fuzzy_and(a.is_commutative for a in args)
obj.is_commutative = is_commutative
return obj
def _new_rawargs(self, *args, reeval=True, **kwargs):
"""Create new instance of own class with args exactly as provided by
caller but returning the self class identity if args is empty.
Examples
========
This is handy when we want to optimize things, e.g.
>>> from sympy import Mul, S
>>> from sympy.abc import x, y
>>> e = Mul(3, x, y)
>>> e.args
(3, x, y)
>>> Mul(*e.args[1:])
x*y
>>> e._new_rawargs(*e.args[1:]) # the same as above, but faster
x*y
Note: use this with caution. There is no checking of arguments at
all. This is best used when you are rebuilding an Add or Mul after
simply removing one or more args. If, for example, modifications,
result in extra 1s being inserted they will show up in the result:
>>> m = (x*y)._new_rawargs(S.One, x); m
1*x
>>> m == x
False
>>> m.is_Mul
True
Another issue to be aware of is that the commutativity of the result
is based on the commutativity of self. If you are rebuilding the
terms that came from a commutative object then there will be no
problem, but if self was non-commutative then what you are
rebuilding may now be commutative.
Although this routine tries to do as little as possible with the
input, getting the commutativity right is important, so this level
of safety is enforced: commutativity will always be recomputed if
self is non-commutative and kwarg `reeval=False` has not been
passed.
"""
if reeval and self.is_commutative is False:
is_commutative = None
else:
is_commutative = self.is_commutative
return self._from_args(args, is_commutative)
@classmethod
def flatten(cls, seq):
"""Return seq so that none of the elements are of type `cls`. This is
the vanilla routine that will be used if a class derived from AssocOp
does not define its own flatten routine."""
# apply associativity, no commutativity property is used
new_seq = []
while seq:
o = seq.pop()
if o.__class__ is cls: # classes must match exactly
seq.extend(o.args)
else:
new_seq.append(o)
new_seq.reverse()
# c_part, nc_part, order_symbols
return [], new_seq, None
def _matches_commutative(self, expr, repl_dict=None, old=False):
"""
Matches Add/Mul "pattern" to an expression "expr".
repl_dict ... a dictionary of (wild: expression) pairs, that get
returned with the results
This function is the main workhorse for Add/Mul.
Examples
========
>>> from sympy import symbols, Wild, sin
>>> a = Wild("a")
>>> b = Wild("b")
>>> c = Wild("c")
>>> x, y, z = symbols("x y z")
>>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z)
{a_: x, b_: y, c_: z}
In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is
the expression.
The repl_dict contains parts that were already matched. For example
here:
>>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x})
{a_: x, b_: y, c_: z}
the only function of the repl_dict is to return it in the
result, e.g. if you omit it:
>>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z)
{b_: y, c_: z}
the "a: x" is not returned in the result, but otherwise it is
equivalent.
"""
from .function import _coeff_isneg
# make sure expr is Expr if pattern is Expr
from .expr import Expr
if isinstance(self, Expr) and not isinstance(expr, Expr):
return None
if repl_dict is None:
repl_dict = {}
# handle simple patterns
if self == expr:
return repl_dict
d = self._matches_simple(expr, repl_dict)
if d is not None:
return d
# eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2)
from .function import WildFunction
from .symbol import Wild
wild_part, exact_part = sift(self.args, lambda p:
p.has(Wild, WildFunction) and not expr.has(p),
binary=True)
if not exact_part:
wild_part = list(ordered(wild_part))
if self.is_Add:
# in addition to normal ordered keys, impose
# sorting on Muls with leading Number to put
# them in order
wild_part = sorted(wild_part, key=lambda x:
x.args[0] if x.is_Mul and x.args[0].is_Number else
0)
else:
exact = self._new_rawargs(*exact_part)
free = expr.free_symbols
if free and (exact.free_symbols - free):
# there are symbols in the exact part that are not
# in the expr; but if there are no free symbols, let
# the matching continue
return None
newexpr = self._combine_inverse(expr, exact)
if not old and (expr.is_Add or expr.is_Mul):
check = newexpr
if _coeff_isneg(check):
check = -check
if check.count_ops() > expr.count_ops():
return None
newpattern = self._new_rawargs(*wild_part)
return newpattern.matches(newexpr, repl_dict)
# now to real work ;)
i = 0
saw = set()
while expr not in saw:
saw.add(expr)
args = tuple(ordered(self.make_args(expr)))
if self.is_Add and expr.is_Add:
# in addition to normal ordered keys, impose
# sorting on Muls with leading Number to put
# them in order
args = tuple(sorted(args, key=lambda x:
x.args[0] if x.is_Mul and x.args[0].is_Number else
0))
expr_list = (self.identity,) + args
for last_op in reversed(expr_list):
for w in reversed(wild_part):
d1 = w.matches(last_op, repl_dict)
if d1 is not None:
d2 = self.xreplace(d1).matches(expr, d1)
if d2 is not None:
return d2
if i == 0:
if self.is_Mul:
# make e**i look like Mul
if expr.is_Pow and expr.exp.is_Integer:
from .mul import Mul
if expr.exp > 0:
expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False)
else:
expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False)
i += 1
continue
elif self.is_Add:
# make i*e look like Add
c, e = expr.as_coeff_Mul()
if abs(c) > 1:
from .add import Add
if c > 0:
expr = Add(*[e, (c - 1)*e], evaluate=False)
else:
expr = Add(*[-e, (c + 1)*e], evaluate=False)
i += 1
continue
# try collection on non-Wild symbols
from sympy.simplify.radsimp import collect
was = expr
did = set()
for w in reversed(wild_part):
c, w = w.as_coeff_mul(Wild)
free = c.free_symbols - did
if free:
did.update(free)
expr = collect(expr, free)
if expr != was:
i += 0
continue
break # if we didn't continue, there is nothing more to do
return
def _has_matcher(self):
"""Helper for .has() that checks for containment of
subexpressions within an expr by using sets of args
of similar nodes, e.g. x + 1 in x + y + 1 checks
to see that {x, 1} & {x, y, 1} == {x, 1}
"""
def _ncsplit(expr):
# this is not the same as args_cnc because here
# we don't assume expr is a Mul -- hence deal with args --
# and always return a set.
cpart, ncpart = sift(expr.args,
lambda arg: arg.is_commutative is True, binary=True)
return set(cpart), ncpart
c, nc = _ncsplit(self)
cls = self.__class__
def is_in(expr):
if isinstance(expr, cls):
if expr == self:
return True
_c, _nc = _ncsplit(expr)
if (c & _c) == c:
if not nc:
return True
elif len(nc) <= len(_nc):
for i in range(len(_nc) - len(nc) + 1):
if _nc[i:i + len(nc)] == nc:
return True
return False
return is_in
def _eval_evalf(self, prec):
"""
Evaluate the parts of self that are numbers; if the whole thing
was a number with no functions it would have been evaluated, but
it wasn't so we must judiciously extract the numbers and reconstruct
the object. This is *not* simply replacing numbers with evaluated
numbers. Numbers should be handled in the largest pure-number
expression as possible. So the code below separates ``self`` into
number and non-number parts and evaluates the number parts and
walks the args of the non-number part recursively (doing the same
thing).
"""
from .add import Add
from .mul import Mul
from .symbol import Symbol
from .function import AppliedUndef
if isinstance(self, (Mul, Add)):
x, tail = self.as_independent(Symbol, AppliedUndef)
# if x is an AssocOp Function then the _evalf below will
# call _eval_evalf (here) so we must break the recursion
if not (tail is self.identity or
isinstance(x, AssocOp) and x.is_Function or
x is self.identity and isinstance(tail, AssocOp)):
# here, we have a number so we just call to _evalf with prec;
# prec is not the same as n, it is the binary precision so
# that's why we don't call to evalf.
x = x._evalf(prec) if x is not self.identity else self.identity
args = []
tail_args = tuple(self.func.make_args(tail))
for a in tail_args:
# here we call to _eval_evalf since we don't know what we
# are dealing with and all other _eval_evalf routines should
# be doing the same thing (i.e. taking binary prec and
# finding the evalf-able args)
newa = a._eval_evalf(prec)
if newa is None:
args.append(a)
else:
args.append(newa)
return self.func(x, *args)
# this is the same as above, but there were no pure-number args to
# deal with
args = []
for a in self.args:
newa = a._eval_evalf(prec)
if newa is None:
args.append(a)
else:
args.append(newa)
return self.func(*args)
@classmethod
def make_args(cls, expr):
"""
Return a sequence of elements `args` such that cls(*args) == expr
Examples
========
>>> from sympy import Symbol, Mul, Add
>>> x, y = map(Symbol, 'xy')
>>> Mul.make_args(x*y)
(x, y)
>>> Add.make_args(x*y)
(x*y,)
>>> set(Add.make_args(x*y + y)) == set([y, x*y])
True
"""
if isinstance(expr, cls):
return expr.args
else:
return (sympify(expr),)
def doit(self, **hints):
if hints.get('deep', True):
terms = [term.doit(**hints) for term in self.args]
else:
terms = self.args
return self.func(*terms, evaluate=True)
class ShortCircuit(Exception):
pass
class LatticeOp(AssocOp):
"""
Join/meet operations of an algebraic lattice[1].
Explanation
===========
These binary operations are associative (op(op(a, b), c) = op(a, op(b, c))),
commutative (op(a, b) = op(b, a)) and idempotent (op(a, a) = op(a) = a).
Common examples are AND, OR, Union, Intersection, max or min. They have an
identity element (op(identity, a) = a) and an absorbing element
conventionally called zero (op(zero, a) = zero).
This is an abstract base class, concrete derived classes must declare
attributes zero and identity. All defining properties are then respected.
Examples
========
>>> from sympy import Integer
>>> from sympy.core.operations import LatticeOp
>>> class my_join(LatticeOp):
... zero = Integer(0)
... identity = Integer(1)
>>> my_join(2, 3) == my_join(3, 2)
True
>>> my_join(2, my_join(3, 4)) == my_join(2, 3, 4)
True
>>> my_join(0, 1, 4, 2, 3, 4)
0
>>> my_join(1, 2)
2
References
==========
.. [1] https://en.wikipedia.org/wiki/Lattice_%28order%29
"""
is_commutative = True
def __new__(cls, *args, **options):
args = (_sympify_(arg) for arg in args)
try:
# /!\ args is a generator and _new_args_filter
# must be careful to handle as such; this
# is done so short-circuiting can be done
# without having to sympify all values
_args = frozenset(cls._new_args_filter(args))
except ShortCircuit:
return sympify(cls.zero)
if not _args:
return sympify(cls.identity)
elif len(_args) == 1:
return set(_args).pop()
else:
# XXX in almost every other case for __new__, *_args is
# passed along, but the expectation here is for _args
obj = super(AssocOp, cls).__new__(cls, *ordered(_args))
obj._argset = _args
return obj
@classmethod
def _new_args_filter(cls, arg_sequence, call_cls=None):
"""Generator filtering args"""
ncls = call_cls or cls
for arg in arg_sequence:
if arg == ncls.zero:
raise ShortCircuit(arg)
elif arg == ncls.identity:
continue
elif arg.func == ncls:
yield from arg.args
else:
yield arg
@classmethod
def make_args(cls, expr):
"""
Return a set of args such that cls(*arg_set) == expr.
"""
if isinstance(expr, cls):
return expr._argset
else:
return frozenset([sympify(expr)])
class AssocOpDispatcher:
"""
Handler dispatcher for associative operators
.. notes::
This approach is experimental, and can be replaced or deleted in the future.
See https://github.com/sympy/sympy/pull/19463.
Explanation
===========
If arguments of different types are passed, the classes which handle the operation for each type
are collected. Then, a class which performs the operation is selected by recursive binary dispatching.
Dispatching relation can be registered by ``register_handlerclass`` method.
Priority registration is unordered. You cannot make ``A*B`` and ``B*A`` refer to
different handler classes. All logic dealing with the order of arguments must be implemented
in the handler class.
Examples
========
>>> from sympy import Add, Expr, Symbol
>>> from sympy.core.add import add
>>> class NewExpr(Expr):
... @property
... def _add_handler(self):
... return NewAdd
>>> class NewAdd(NewExpr, Add):
... pass
>>> add.register_handlerclass((Add, NewAdd), NewAdd)
>>> a, b = Symbol('a'), NewExpr()
>>> add(a, b) == NewAdd(a, b)
True
"""
def __init__(self, name, doc=None):
self.name = name
self.doc = doc
self.handlerattr = "_%s_handler" % name
self._handlergetter = attrgetter(self.handlerattr)
self._dispatcher = Dispatcher(name)
def __repr__(self):
return "<dispatched %s>" % self.name
def register_handlerclass(self, classes, typ, on_ambiguity=ambiguity_register_error_ignore_dup):
"""
Register the handler class for two classes, in both straight and reversed order.
Paramteters
===========
classes : tuple of two types
Classes who are compared with each other.
typ:
Class which is registered to represent *cls1* and *cls2*.
Handler method of *self* must be implemented in this class.
"""
if not len(classes) == 2:
raise RuntimeError(
"Only binary dispatch is supported, but got %s types: <%s>." % (
len(classes), str_signature(classes)
))
if len(set(classes)) == 1:
raise RuntimeError(
"Duplicate types <%s> cannot be dispatched." % str_signature(classes)
)
self._dispatcher.add(tuple(classes), typ, on_ambiguity=on_ambiguity)
self._dispatcher.add(tuple(reversed(classes)), typ, on_ambiguity=on_ambiguity)
@cacheit
def __call__(self, *args, _sympify=True, **kwargs):
"""
Parameters
==========
*args :
Arguments which are operated
"""
if _sympify:
args = tuple(map(_sympify_, args))
handlers = frozenset(map(self._handlergetter, args))
# no need to sympify again
return self.dispatch(handlers)(*args, _sympify=False, **kwargs)
@cacheit
def dispatch(self, handlers):
"""
Select the handler class, and return its handler method.
"""
# Quick exit for the case where all handlers are same
if len(handlers) == 1:
h, = handlers
if not isinstance(h, type):
raise RuntimeError("Handler {!r} is not a type.".format(h))
return h
# Recursively select with registered binary priority
for i, typ in enumerate(handlers):
if not isinstance(typ, type):
raise RuntimeError("Handler {!r} is not a type.".format(typ))
if i == 0:
handler = typ
else:
prev_handler = handler
handler = self._dispatcher.dispatch(prev_handler, typ)
if not isinstance(handler, type):
raise RuntimeError(
"Dispatcher for {!r} and {!r} must return a type, but got {!r}".format(
prev_handler, typ, handler
))
# return handler class
return handler
@property
def __doc__(self):
docs = [
"Multiply dispatched associative operator: %s" % self.name,
"Note that support for this is experimental, see the docs for :class:`AssocOpDispatcher` for details"
]
if self.doc:
docs.append(self.doc)
s = "Registered handler classes\n"
s += '=' * len(s)
docs.append(s)
amb_sigs = []
typ_sigs = defaultdict(list)
for sigs in self._dispatcher.ordering[::-1]:
key = self._dispatcher.funcs[sigs]
typ_sigs[key].append(sigs)
for typ, sigs in typ_sigs.items():
sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)
if isinstance(typ, RaiseNotImplementedError):
amb_sigs.append(sigs_str)
continue
s = 'Inputs: %s\n' % sigs_str
s += '-' * len(s) + '\n'
s += typ.__name__
docs.append(s)
if amb_sigs:
s = "Ambiguous handler classes\n"
s += '=' * len(s)
docs.append(s)
s = '\n'.join(amb_sigs)
docs.append(s)
return '\n\n'.join(docs)

View File

@ -0,0 +1,161 @@
"""Thread-safe global parameters"""
from .cache import clear_cache
from contextlib import contextmanager
from threading import local
class _global_parameters(local):
"""
Thread-local global parameters.
Explanation
===========
This class generates thread-local container for SymPy's global parameters.
Every global parameters must be passed as keyword argument when generating
its instance.
A variable, `global_parameters` is provided as default instance for this class.
WARNING! Although the global parameters are thread-local, SymPy's cache is not
by now.
This may lead to undesired result in multi-threading operations.
Examples
========
>>> from sympy.abc import x
>>> from sympy.core.cache import clear_cache
>>> from sympy.core.parameters import global_parameters as gp
>>> gp.evaluate
True
>>> x+x
2*x
>>> log = []
>>> def f():
... clear_cache()
... gp.evaluate = False
... log.append(x+x)
... clear_cache()
>>> import threading
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> print(log)
[x + x]
>>> gp.evaluate
True
>>> x+x
2*x
References
==========
.. [1] https://docs.python.org/3/library/threading.html
"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __setattr__(self, name, value):
if getattr(self, name) != value:
clear_cache()
return super().__setattr__(name, value)
global_parameters = _global_parameters(evaluate=True, distribute=True, exp_is_pow=False)
class evaluate:
""" Control automatic evaluation
Explanation
===========
This context manager controls whether or not all SymPy functions evaluate
by default.
Note that much of SymPy expects evaluated expressions. This functionality
is experimental and is unlikely to function as intended on large
expressions.
Examples
========
>>> from sympy import evaluate
>>> from sympy.abc import x
>>> print(x + x)
2*x
>>> with evaluate(False):
... print(x + x)
x + x
"""
def __init__(self, x):
self.x = x
self.old = []
def __enter__(self):
self.old.append(global_parameters.evaluate)
global_parameters.evaluate = self.x
def __exit__(self, exc_type, exc_val, exc_tb):
global_parameters.evaluate = self.old.pop()
@contextmanager
def distribute(x):
""" Control automatic distribution of Number over Add
Explanation
===========
This context manager controls whether or not Mul distribute Number over
Add. Plan is to avoid distributing Number over Add in all of sympy. Once
that is done, this contextmanager will be removed.
Examples
========
>>> from sympy.abc import x
>>> from sympy.core.parameters import distribute
>>> print(2*(x + 1))
2*x + 2
>>> with distribute(False):
... print(2*(x + 1))
2*(x + 1)
"""
old = global_parameters.distribute
try:
global_parameters.distribute = x
yield
finally:
global_parameters.distribute = old
@contextmanager
def _exp_is_pow(x):
"""
Control whether `e^x` should be represented as ``exp(x)`` or a ``Pow(E, x)``.
Examples
========
>>> from sympy import exp
>>> from sympy.abc import x
>>> from sympy.core.parameters import _exp_is_pow
>>> with _exp_is_pow(True): print(type(exp(x)))
<class 'sympy.core.power.Pow'>
>>> with _exp_is_pow(False): print(type(exp(x)))
exp
"""
old = global_parameters.exp_is_pow
clear_cache()
try:
global_parameters.exp_is_pow = x
yield
finally:
clear_cache()
global_parameters.exp_is_pow = old

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
"""
When you need to use random numbers in SymPy library code, import from here
so there is only one generator working for SymPy. Imports from here should
behave the same as if they were being imported from Python's random module.
But only the routines currently used in SymPy are included here. To use others
import ``rng`` and access the method directly. For example, to capture the
current state of the generator use ``rng.getstate()``.
There is intentionally no Random to import from here. If you want
to control the state of the generator, import ``seed`` and call it
with or without an argument to set the state.
Examples
========
>>> from sympy.core.random import random, seed
>>> assert random() < 1
>>> seed(1); a = random()
>>> b = random()
>>> seed(1); c = random()
>>> assert a == c
>>> assert a != b # remote possibility this will fail
"""
from sympy.utilities.iterables import is_sequence
from sympy.utilities.misc import as_int
import random as _random
rng = _random.Random()
choice = rng.choice
random = rng.random
randint = rng.randint
randrange = rng.randrange
sample = rng.sample
# seed = rng.seed
shuffle = rng.shuffle
uniform = rng.uniform
_assumptions_rng = _random.Random()
_assumptions_shuffle = _assumptions_rng.shuffle
def seed(a=None, version=2):
rng.seed(a=a, version=version)
_assumptions_rng.seed(a=a, version=version)
def random_complex_number(a=2, b=-1, c=3, d=1, rational=False, tolerance=None):
"""
Return a random complex number.
To reduce chance of hitting branch cuts or anything, we guarantee
b <= Im z <= d, a <= Re z <= c
When rational is True, a rational approximation to a random number
is obtained within specified tolerance, if any.
"""
from sympy.core.numbers import I
from sympy.simplify.simplify import nsimplify
A, B = uniform(a, c), uniform(b, d)
if not rational:
return A + I*B
return (nsimplify(A, rational=True, tolerance=tolerance) +
I*nsimplify(B, rational=True, tolerance=tolerance))
def verify_numerically(f, g, z=None, tol=1.0e-6, a=2, b=-1, c=3, d=1):
"""
Test numerically that f and g agree when evaluated in the argument z.
If z is None, all symbols will be tested. This routine does not test
whether there are Floats present with precision higher than 15 digits
so if there are, your results may not be what you expect due to round-
off errors.
Examples
========
>>> from sympy import sin, cos
>>> from sympy.abc import x
>>> from sympy.core.random import verify_numerically as tn
>>> tn(sin(x)**2 + cos(x)**2, 1, x)
True
"""
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.core.numbers import comp
f, g = (sympify(i) for i in (f, g))
if z is None:
z = f.free_symbols | g.free_symbols
elif isinstance(z, Symbol):
z = [z]
reps = list(zip(z, [random_complex_number(a, b, c, d) for _ in z]))
z1 = f.subs(reps).n()
z2 = g.subs(reps).n()
return comp(z1, z2, tol)
def test_derivative_numerically(f, z, tol=1.0e-6, a=2, b=-1, c=3, d=1):
"""
Test numerically that the symbolically computed derivative of f
with respect to z is correct.
This routine does not test whether there are Floats present with
precision higher than 15 digits so if there are, your results may
not be what you expect due to round-off errors.
Examples
========
>>> from sympy import sin
>>> from sympy.abc import x
>>> from sympy.core.random import test_derivative_numerically as td
>>> td(sin(x), x)
True
"""
from sympy.core.numbers import comp
from sympy.core.function import Derivative
z0 = random_complex_number(a, b, c, d)
f1 = f.diff(z).subs(z, z0)
f2 = Derivative(f, z).doit_numerically(z0)
return comp(f1.n(), f2.n(), tol)
def _randrange(seed=None):
"""Return a randrange generator.
``seed`` can be
* None - return randomly seeded generator
* int - return a generator seeded with the int
* list - the values to be returned will be taken from the list
in the order given; the provided list is not modified.
Examples
========
>>> from sympy.core.random import _randrange
>>> rr = _randrange()
>>> rr(1000) # doctest: +SKIP
999
>>> rr = _randrange(3)
>>> rr(1000) # doctest: +SKIP
238
>>> rr = _randrange([0, 5, 1, 3, 4])
>>> rr(3), rr(3)
(0, 1)
"""
if seed is None:
return randrange
elif isinstance(seed, int):
rng.seed(seed)
return randrange
elif is_sequence(seed):
seed = list(seed) # make a copy
seed.reverse()
def give(a, b=None, seq=seed):
if b is None:
a, b = 0, a
a, b = as_int(a), as_int(b)
w = b - a
if w < 1:
raise ValueError('_randrange got empty range')
try:
x = seq.pop()
except IndexError:
raise ValueError('_randrange sequence was too short')
if a <= x < b:
return x
else:
return give(a, b, seq)
return give
else:
raise ValueError('_randrange got an unexpected seed')
def _randint(seed=None):
"""Return a randint generator.
``seed`` can be
* None - return randomly seeded generator
* int - return a generator seeded with the int
* list - the values to be returned will be taken from the list
in the order given; the provided list is not modified.
Examples
========
>>> from sympy.core.random import _randint
>>> ri = _randint()
>>> ri(1, 1000) # doctest: +SKIP
999
>>> ri = _randint(3)
>>> ri(1, 1000) # doctest: +SKIP
238
>>> ri = _randint([0, 5, 1, 2, 4])
>>> ri(1, 3), ri(1, 3)
(1, 2)
"""
if seed is None:
return randint
elif isinstance(seed, int):
rng.seed(seed)
return randint
elif is_sequence(seed):
seed = list(seed) # make a copy
seed.reverse()
def give(a, b, seq=seed):
a, b = as_int(a), as_int(b)
w = b - a
if w < 0:
raise ValueError('_randint got empty range')
try:
x = seq.pop()
except IndexError:
raise ValueError('_randint sequence was too short')
if a <= x <= b:
return x
else:
return give(a, b, seq)
return give
else:
raise ValueError('_randint got an unexpected seed')

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
"""
Replacement rules.
"""
class Transform:
"""
Immutable mapping that can be used as a generic transformation rule.
Parameters
==========
transform : callable
Computes the value corresponding to any key.
filter : callable, optional
If supplied, specifies which objects are in the mapping.
Examples
========
>>> from sympy.core.rules import Transform
>>> from sympy.abc import x
This Transform will return, as a value, one more than the key:
>>> add1 = Transform(lambda x: x + 1)
>>> add1[1]
2
>>> add1[x]
x + 1
By default, all values are considered to be in the dictionary. If a filter
is supplied, only the objects for which it returns True are considered as
being in the dictionary:
>>> add1_odd = Transform(lambda x: x + 1, lambda x: x%2 == 1)
>>> 2 in add1_odd
False
>>> add1_odd.get(2, 0)
0
>>> 3 in add1_odd
True
>>> add1_odd[3]
4
>>> add1_odd.get(3, 0)
4
"""
def __init__(self, transform, filter=lambda x: True):
self._transform = transform
self._filter = filter
def __contains__(self, item):
return self._filter(item)
def __getitem__(self, key):
if self._filter(key):
return self._transform(key)
else:
raise KeyError(key)
def get(self, item, default=None):
if item in self:
return self[item]
else:
return default

View File

@ -0,0 +1,174 @@
"""Singleton mechanism"""
from .core import Registry
from .sympify import sympify
class SingletonRegistry(Registry):
"""
The registry for the singleton classes (accessible as ``S``).
Explanation
===========
This class serves as two separate things.
The first thing it is is the ``SingletonRegistry``. Several classes in
SymPy appear so often that they are singletonized, that is, using some
metaprogramming they are made so that they can only be instantiated once
(see the :class:`sympy.core.singleton.Singleton` class for details). For
instance, every time you create ``Integer(0)``, this will return the same
instance, :class:`sympy.core.numbers.Zero`. All singleton instances are
attributes of the ``S`` object, so ``Integer(0)`` can also be accessed as
``S.Zero``.
Singletonization offers two advantages: it saves memory, and it allows
fast comparison. It saves memory because no matter how many times the
singletonized objects appear in expressions in memory, they all point to
the same single instance in memory. The fast comparison comes from the
fact that you can use ``is`` to compare exact instances in Python
(usually, you need to use ``==`` to compare things). ``is`` compares
objects by memory address, and is very fast.
Examples
========
>>> from sympy import S, Integer
>>> a = Integer(0)
>>> a is S.Zero
True
For the most part, the fact that certain objects are singletonized is an
implementation detail that users should not need to worry about. In SymPy
library code, ``is`` comparison is often used for performance purposes
The primary advantage of ``S`` for end users is the convenient access to
certain instances that are otherwise difficult to type, like ``S.Half``
(instead of ``Rational(1, 2)``).
When using ``is`` comparison, make sure the argument is sympified. For
instance,
>>> x = 0
>>> x is S.Zero
False
This problem is not an issue when using ``==``, which is recommended for
most use-cases:
>>> 0 == S.Zero
True
The second thing ``S`` is is a shortcut for
:func:`sympy.core.sympify.sympify`. :func:`sympy.core.sympify.sympify` is
the function that converts Python objects such as ``int(1)`` into SymPy
objects such as ``Integer(1)``. It also converts the string form of an
expression into a SymPy expression, like ``sympify("x**2")`` ->
``Symbol("x")**2``. ``S(1)`` is the same thing as ``sympify(1)``
(basically, ``S.__call__`` has been defined to call ``sympify``).
This is for convenience, since ``S`` is a single letter. It's mostly
useful for defining rational numbers. Consider an expression like ``x +
1/2``. If you enter this directly in Python, it will evaluate the ``1/2``
and give ``0.5``, because both arguments are ints (see also
:ref:`tutorial-gotchas-final-notes`). However, in SymPy, you usually want
the quotient of two integers to give an exact rational number. The way
Python's evaluation works, at least one side of an operator needs to be a
SymPy object for the SymPy evaluation to take over. You could write this
as ``x + Rational(1, 2)``, but this is a lot more typing. A shorter
version is ``x + S(1)/2``. Since ``S(1)`` returns ``Integer(1)``, the
division will return a ``Rational`` type, since it will call
``Integer.__truediv__``, which knows how to return a ``Rational``.
"""
__slots__ = ()
# Also allow things like S(5)
__call__ = staticmethod(sympify)
def __init__(self):
self._classes_to_install = {}
# Dict of classes that have been registered, but that have not have been
# installed as an attribute of this SingletonRegistry.
# Installation automatically happens at the first attempt to access the
# attribute.
# The purpose of this is to allow registration during class
# initialization during import, but not trigger object creation until
# actual use (which should not happen until after all imports are
# finished).
def register(self, cls):
# Make sure a duplicate class overwrites the old one
if hasattr(self, cls.__name__):
delattr(self, cls.__name__)
self._classes_to_install[cls.__name__] = cls
def __getattr__(self, name):
"""Python calls __getattr__ if no attribute of that name was installed
yet.
Explanation
===========
This __getattr__ checks whether a class with the requested name was
already registered but not installed; if no, raises an AttributeError.
Otherwise, retrieves the class, calculates its singleton value, installs
it as an attribute of the given name, and unregisters the class."""
if name not in self._classes_to_install:
raise AttributeError(
"Attribute '%s' was not installed on SymPy registry %s" % (
name, self))
class_to_install = self._classes_to_install[name]
value_to_install = class_to_install()
self.__setattr__(name, value_to_install)
del self._classes_to_install[name]
return value_to_install
def __repr__(self):
return "S"
S = SingletonRegistry()
class Singleton(type):
"""
Metaclass for singleton classes.
Explanation
===========
A singleton class has only one instance which is returned every time the
class is instantiated. Additionally, this instance can be accessed through
the global registry object ``S`` as ``S.<class_name>``.
Examples
========
>>> from sympy import S, Basic
>>> from sympy.core.singleton import Singleton
>>> class MySingleton(Basic, metaclass=Singleton):
... pass
>>> Basic() is Basic()
False
>>> MySingleton() is MySingleton()
True
>>> S.MySingleton is MySingleton()
True
Notes
=====
Instance creation is delayed until the first time the value is accessed.
(SymPy versions before 1.0 would create the instance during class
creation time, which would be prone to import cycles.)
"""
def __init__(cls, *args, **kwargs):
cls._instance = obj = Basic.__new__(cls)
cls.__new__ = lambda cls: obj
cls.__getnewargs__ = lambda obj: ()
cls.__getstate__ = lambda obj: None
S.register(cls)
# Delayed to avoid cyclic import
from .basic import Basic

View File

@ -0,0 +1,312 @@
from collections import defaultdict
from .sympify import sympify, SympifyError
from sympy.utilities.iterables import iterable, uniq
__all__ = ['default_sort_key', 'ordered']
def default_sort_key(item, order=None):
"""Return a key that can be used for sorting.
The key has the structure:
(class_key, (len(args), args), exponent.sort_key(), coefficient)
This key is supplied by the sort_key routine of Basic objects when
``item`` is a Basic object or an object (other than a string) that
sympifies to a Basic object. Otherwise, this function produces the
key.
The ``order`` argument is passed along to the sort_key routine and is
used to determine how the terms *within* an expression are ordered.
(See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex',
and reversed values of the same (e.g. 'rev-lex'). The default order
value is None (which translates to 'lex').
Examples
========
>>> from sympy import S, I, default_sort_key, sin, cos, sqrt
>>> from sympy.core.function import UndefinedFunction
>>> from sympy.abc import x
The following are equivalent ways of getting the key for an object:
>>> x.sort_key() == default_sort_key(x)
True
Here are some examples of the key that is produced:
>>> default_sort_key(UndefinedFunction('f'))
((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'),
(0, ()), (), 1), 1)
>>> default_sort_key('1')
((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1)
>>> default_sort_key(S.One)
((1, 0, 'Number'), (0, ()), (), 1)
>>> default_sort_key(2)
((1, 0, 'Number'), (0, ()), (), 2)
While sort_key is a method only defined for SymPy objects,
default_sort_key will accept anything as an argument so it is
more robust as a sorting key. For the following, using key=
lambda i: i.sort_key() would fail because 2 does not have a sort_key
method; that's why default_sort_key is used. Note, that it also
handles sympification of non-string items likes ints:
>>> a = [2, I, -I]
>>> sorted(a, key=default_sort_key)
[2, -I, I]
The returned key can be used anywhere that a key can be specified for
a function, e.g. sort, min, max, etc...:
>>> a.sort(key=default_sort_key); a[0]
2
>>> min(a, key=default_sort_key)
2
Notes
=====
The key returned is useful for getting items into a canonical order
that will be the same across platforms. It is not directly useful for
sorting lists of expressions:
>>> a, b = x, 1/x
Since ``a`` has only 1 term, its value of sort_key is unaffected by
``order``:
>>> a.sort_key() == a.sort_key('rev-lex')
True
If ``a`` and ``b`` are combined then the key will differ because there
are terms that can be ordered:
>>> eq = a + b
>>> eq.sort_key() == eq.sort_key('rev-lex')
False
>>> eq.as_ordered_terms()
[x, 1/x]
>>> eq.as_ordered_terms('rev-lex')
[1/x, x]
But since the keys for each of these terms are independent of ``order``'s
value, they do not sort differently when they appear separately in a list:
>>> sorted(eq.args, key=default_sort_key)
[1/x, x]
>>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex'))
[1/x, x]
The order of terms obtained when using these keys is the order that would
be obtained if those terms were *factors* in a product.
Although it is useful for quickly putting expressions in canonical order,
it does not sort expressions based on their complexity defined by the
number of operations, power of variables and others:
>>> sorted([sin(x)*cos(x), sin(x)], key=default_sort_key)
[sin(x)*cos(x), sin(x)]
>>> sorted([x, x**2, sqrt(x), x**3], key=default_sort_key)
[sqrt(x), x, x**2, x**3]
See Also
========
ordered, sympy.core.expr.Expr.as_ordered_factors, sympy.core.expr.Expr.as_ordered_terms
"""
from .basic import Basic
from .singleton import S
if isinstance(item, Basic):
return item.sort_key(order=order)
if iterable(item, exclude=str):
if isinstance(item, dict):
args = item.items()
unordered = True
elif isinstance(item, set):
args = item
unordered = True
else:
# e.g. tuple, list
args = list(item)
unordered = False
args = [default_sort_key(arg, order=order) for arg in args]
if unordered:
# e.g. dict, set
args = sorted(args)
cls_index, args = 10, (len(args), tuple(args))
else:
if not isinstance(item, str):
try:
item = sympify(item, strict=True)
except SympifyError:
# e.g. lambda x: x
pass
else:
if isinstance(item, Basic):
# e.g int -> Integer
return default_sort_key(item)
# e.g. UndefinedFunction
# e.g. str
cls_index, args = 0, (1, (str(item),))
return (cls_index, 0, item.__class__.__name__
), args, S.One.sort_key(), S.One
def _node_count(e):
# this not only counts nodes, it affirms that the
# args are Basic (i.e. have an args property). If
# some object has a non-Basic arg, it needs to be
# fixed since it is intended that all Basic args
# are of Basic type (though this is not easy to enforce).
if e.is_Float:
return 0.5
return 1 + sum(map(_node_count, e.args))
def _nodes(e):
"""
A helper for ordered() which returns the node count of ``e`` which
for Basic objects is the number of Basic nodes in the expression tree
but for other objects is 1 (unless the object is an iterable or dict
for which the sum of nodes is returned).
"""
from .basic import Basic
from .function import Derivative
if isinstance(e, Basic):
if isinstance(e, Derivative):
return _nodes(e.expr) + sum(i[1] if i[1].is_Number else
_nodes(i[1]) for i in e.variable_count)
return _node_count(e)
elif iterable(e):
return 1 + sum(_nodes(ei) for ei in e)
elif isinstance(e, dict):
return 1 + sum(_nodes(k) + _nodes(v) for k, v in e.items())
else:
return 1
def ordered(seq, keys=None, default=True, warn=False):
"""Return an iterator of the seq where keys are used to break ties
in a conservative fashion: if, after applying a key, there are no
ties then no other keys will be computed.
Two default keys will be applied if 1) keys are not provided or
2) the given keys do not resolve all ties (but only if ``default``
is True). The two keys are ``_nodes`` (which places smaller
expressions before large) and ``default_sort_key`` which (if the
``sort_key`` for an object is defined properly) should resolve
any ties. This strategy is similar to sorting done by
``Basic.compare``, but differs in that ``ordered`` never makes a
decision based on an objects name.
If ``warn`` is True then an error will be raised if there were no
keys remaining to break ties. This can be used if it was expected that
there should be no ties between items that are not identical.
Examples
========
>>> from sympy import ordered, count_ops
>>> from sympy.abc import x, y
The count_ops is not sufficient to break ties in this list and the first
two items appear in their original order (i.e. the sorting is stable):
>>> list(ordered([y + 2, x + 2, x**2 + y + 3],
... count_ops, default=False, warn=False))
...
[y + 2, x + 2, x**2 + y + 3]
The default_sort_key allows the tie to be broken:
>>> list(ordered([y + 2, x + 2, x**2 + y + 3]))
...
[x + 2, y + 2, x**2 + y + 3]
Here, sequences are sorted by length, then sum:
>>> seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], [
... lambda x: len(x),
... lambda x: sum(x)]]
...
>>> list(ordered(seq, keys, default=False, warn=False))
[[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]]
If ``warn`` is True, an error will be raised if there were not
enough keys to break ties:
>>> list(ordered(seq, keys, default=False, warn=True))
Traceback (most recent call last):
...
ValueError: not enough keys to break ties
Notes
=====
The decorated sort is one of the fastest ways to sort a sequence for
which special item comparison is desired: the sequence is decorated,
sorted on the basis of the decoration (e.g. making all letters lower
case) and then undecorated. If one wants to break ties for items that
have the same decorated value, a second key can be used. But if the
second key is expensive to compute then it is inefficient to decorate
all items with both keys: only those items having identical first key
values need to be decorated. This function applies keys successively
only when needed to break ties. By yielding an iterator, use of the
tie-breaker is delayed as long as possible.
This function is best used in cases when use of the first key is
expected to be a good hashing function; if there are no unique hashes
from application of a key, then that key should not have been used. The
exception, however, is that even if there are many collisions, if the
first group is small and one does not need to process all items in the
list then time will not be wasted sorting what one was not interested
in. For example, if one were looking for the minimum in a list and
there were several criteria used to define the sort order, then this
function would be good at returning that quickly if the first group
of candidates is small relative to the number of items being processed.
"""
d = defaultdict(list)
if keys:
if isinstance(keys, (list, tuple)):
keys = list(keys)
f = keys.pop(0)
else:
f = keys
keys = []
for a in seq:
d[f(a)].append(a)
else:
if not default:
raise ValueError('if default=False then keys must be provided')
d[None].extend(seq)
for k, value in sorted(d.items()):
if len(value) > 1:
if keys:
value = ordered(value, keys, default, warn)
elif default:
value = ordered(value, (_nodes, default_sort_key,),
default=False, warn=warn)
elif warn:
u = list(uniq(value))
if len(u) > 1:
raise ValueError(
'not enough keys to break ties: %s' % u)
yield from value

View File

@ -0,0 +1,979 @@
from __future__ import annotations
from .assumptions import StdFactKB, _assume_defined
from .basic import Basic, Atom
from .cache import cacheit
from .containers import Tuple
from .expr import Expr, AtomicExpr
from .function import AppliedUndef, FunctionClass
from .kind import NumberKind, UndefinedKind
from .logic import fuzzy_bool
from .singleton import S
from .sorting import ordered
from .sympify import sympify
from sympy.logic.boolalg import Boolean
from sympy.utilities.iterables import sift, is_sequence
from sympy.utilities.misc import filldedent
import string
import re as _re
import random
from itertools import product
from typing import Any
class Str(Atom):
"""
Represents string in SymPy.
Explanation
===========
Previously, ``Symbol`` was used where string is needed in ``args`` of SymPy
objects, e.g. denoting the name of the instance. However, since ``Symbol``
represents mathematical scalar, this class should be used instead.
"""
__slots__ = ('name',)
def __new__(cls, name, **kwargs):
if not isinstance(name, str):
raise TypeError("name should be a string, not %s" % repr(type(name)))
obj = Expr.__new__(cls, **kwargs)
obj.name = name
return obj
def __getnewargs__(self):
return (self.name,)
def _hashable_content(self):
return (self.name,)
def _filter_assumptions(kwargs):
"""Split the given dict into assumptions and non-assumptions.
Keys are taken as assumptions if they correspond to an
entry in ``_assume_defined``.
"""
assumptions, nonassumptions = map(dict, sift(kwargs.items(),
lambda i: i[0] in _assume_defined,
binary=True))
Symbol._sanitize(assumptions)
return assumptions, nonassumptions
def _symbol(s, matching_symbol=None, **assumptions):
"""Return s if s is a Symbol, else if s is a string, return either
the matching_symbol if the names are the same or else a new symbol
with the same assumptions as the matching symbol (or the
assumptions as provided).
Examples
========
>>> from sympy import Symbol
>>> from sympy.core.symbol import _symbol
>>> _symbol('y')
y
>>> _.is_real is None
True
>>> _symbol('y', real=True).is_real
True
>>> x = Symbol('x')
>>> _symbol(x, real=True)
x
>>> _.is_real is None # ignore attribute if s is a Symbol
True
Below, the variable sym has the name 'foo':
>>> sym = Symbol('foo', real=True)
Since 'x' is not the same as sym's name, a new symbol is created:
>>> _symbol('x', sym).name
'x'
It will acquire any assumptions give:
>>> _symbol('x', sym, real=False).is_real
False
Since 'foo' is the same as sym's name, sym is returned
>>> _symbol('foo', sym)
foo
Any assumptions given are ignored:
>>> _symbol('foo', sym, real=False).is_real
True
NB: the symbol here may not be the same as a symbol with the same
name defined elsewhere as a result of different assumptions.
See Also
========
sympy.core.symbol.Symbol
"""
if isinstance(s, str):
if matching_symbol and matching_symbol.name == s:
return matching_symbol
return Symbol(s, **assumptions)
elif isinstance(s, Symbol):
return s
else:
raise ValueError('symbol must be string for symbol name or Symbol')
def uniquely_named_symbol(xname, exprs=(), compare=str, modify=None, **assumptions):
"""
Return a symbol whose name is derivated from *xname* but is unique
from any other symbols in *exprs*.
*xname* and symbol names in *exprs* are passed to *compare* to be
converted to comparable forms. If ``compare(xname)`` is not unique,
it is recursively passed to *modify* until unique name is acquired.
Parameters
==========
xname : str or Symbol
Base name for the new symbol.
exprs : Expr or iterable of Expr
Expressions whose symbols are compared to *xname*.
compare : function
Unary function which transforms *xname* and symbol names from
*exprs* to comparable form.
modify : function
Unary function which modifies the string. Default is appending
the number, or increasing the number if exists.
Examples
========
By default, a number is appended to *xname* to generate unique name.
If the number already exists, it is recursively increased.
>>> from sympy.core.symbol import uniquely_named_symbol, Symbol
>>> uniquely_named_symbol('x', Symbol('x'))
x0
>>> uniquely_named_symbol('x', (Symbol('x'), Symbol('x0')))
x1
>>> uniquely_named_symbol('x0', (Symbol('x1'), Symbol('x0')))
x2
Name generation can be controlled by passing *modify* parameter.
>>> from sympy.abc import x
>>> uniquely_named_symbol('x', x, modify=lambda s: 2*s)
xx
"""
def numbered_string_incr(s, start=0):
if not s:
return str(start)
i = len(s) - 1
while i != -1:
if not s[i].isdigit():
break
i -= 1
n = str(int(s[i + 1:] or start - 1) + 1)
return s[:i + 1] + n
default = None
if is_sequence(xname):
xname, default = xname
x = compare(xname)
if not exprs:
return _symbol(x, default, **assumptions)
if not is_sequence(exprs):
exprs = [exprs]
names = set().union(
[i.name for e in exprs for i in e.atoms(Symbol)] +
[i.func.name for e in exprs for i in e.atoms(AppliedUndef)])
if modify is None:
modify = numbered_string_incr
while any(x == compare(s) for s in names):
x = modify(x)
return _symbol(x, default, **assumptions)
_uniquely_named_symbol = uniquely_named_symbol
class Symbol(AtomicExpr, Boolean):
"""
Symbol class is used to create symbolic variables.
Explanation
===========
Symbolic variables are placeholders for mathematical symbols that can represent numbers, constants, or any other mathematical entities and can be used in mathematical expressions and to perform symbolic computations.
Assumptions:
commutative = True
positive = True
real = True
imaginary = True
complex = True
complete list of more assumptions- :ref:`predicates`
You can override the default assumptions in the constructor.
Examples
========
>>> from sympy import Symbol
>>> x = Symbol("x", positive=True)
>>> x.is_positive
True
>>> x.is_negative
False
passing in greek letters:
>>> from sympy import Symbol
>>> alpha = Symbol('alpha')
>>> alpha #doctest: +SKIP
α
Trailing digits are automatically treated like subscripts of what precedes them in the name.
General format to add subscript to a symbol :
``<var_name> = Symbol('<symbol_name>_<subscript>')``
>>> from sympy import Symbol
>>> alpha_i = Symbol('alpha_i')
>>> alpha_i #doctest: +SKIP
αᵢ
Parameters
==========
AtomicExpr: variable name
Boolean: Assumption with a boolean value(True or False)
"""
is_comparable = False
__slots__ = ('name', '_assumptions_orig', '_assumptions0')
name: str
is_Symbol = True
is_symbol = True
@property
def kind(self):
if self.is_commutative:
return NumberKind
return UndefinedKind
@property
def _diff_wrt(self):
"""Allow derivatives wrt Symbols.
Examples
========
>>> from sympy import Symbol
>>> x = Symbol('x')
>>> x._diff_wrt
True
"""
return True
@staticmethod
def _sanitize(assumptions, obj=None):
"""Remove None, convert values to bool, check commutativity *in place*.
"""
# be strict about commutativity: cannot be None
is_commutative = fuzzy_bool(assumptions.get('commutative', True))
if is_commutative is None:
whose = '%s ' % obj.__name__ if obj else ''
raise ValueError(
'%scommutativity must be True or False.' % whose)
# sanitize other assumptions so 1 -> True and 0 -> False
for key in list(assumptions.keys()):
v = assumptions[key]
if v is None:
assumptions.pop(key)
continue
assumptions[key] = bool(v)
def _merge(self, assumptions):
base = self.assumptions0
for k in set(assumptions) & set(base):
if assumptions[k] != base[k]:
raise ValueError(filldedent('''
non-matching assumptions for %s: existing value
is %s and new value is %s''' % (
k, base[k], assumptions[k])))
base.update(assumptions)
return base
def __new__(cls, name, **assumptions):
"""Symbols are identified by name and assumptions::
>>> from sympy import Symbol
>>> Symbol("x") == Symbol("x")
True
>>> Symbol("x", real=True) == Symbol("x", real=False)
False
"""
cls._sanitize(assumptions, cls)
return Symbol.__xnew_cached_(cls, name, **assumptions)
@staticmethod
def __xnew__(cls, name, **assumptions): # never cached (e.g. dummy)
if not isinstance(name, str):
raise TypeError("name should be a string, not %s" % repr(type(name)))
# This is retained purely so that srepr can include commutative=True if
# that was explicitly specified but not if it was not. Ideally srepr
# should not distinguish these cases because the symbols otherwise
# compare equal and are considered equivalent.
#
# See https://github.com/sympy/sympy/issues/8873
#
assumptions_orig = assumptions.copy()
# The only assumption that is assumed by default is comutative=True:
assumptions.setdefault('commutative', True)
assumptions_kb = StdFactKB(assumptions)
assumptions0 = dict(assumptions_kb)
obj = Expr.__new__(cls)
obj.name = name
obj._assumptions = assumptions_kb
obj._assumptions_orig = assumptions_orig
obj._assumptions0 = assumptions0
# The three assumptions dicts are all a little different:
#
# >>> from sympy import Symbol
# >>> x = Symbol('x', finite=True)
# >>> x.is_positive # query an assumption
# >>> x._assumptions
# {'finite': True, 'infinite': False, 'commutative': True, 'positive': None}
# >>> x._assumptions0
# {'finite': True, 'infinite': False, 'commutative': True}
# >>> x._assumptions_orig
# {'finite': True}
#
# Two symbols with the same name are equal if their _assumptions0 are
# the same. Arguably it should be _assumptions_orig that is being
# compared because that is more transparent to the user (it is
# what was passed to the constructor modulo changes made by _sanitize).
return obj
@staticmethod
@cacheit
def __xnew_cached_(cls, name, **assumptions): # symbols are always cached
return Symbol.__xnew__(cls, name, **assumptions)
def __getnewargs_ex__(self):
return ((self.name,), self._assumptions_orig)
# NOTE: __setstate__ is not needed for pickles created by __getnewargs_ex__
# but was used before Symbol was changed to use __getnewargs_ex__ in v1.9.
# Pickles created in previous SymPy versions will still need __setstate__
# so that they can be unpickled in SymPy > v1.9.
def __setstate__(self, state):
for name, value in state.items():
setattr(self, name, value)
def _hashable_content(self):
# Note: user-specified assumptions not hashed, just derived ones
return (self.name,) + tuple(sorted(self.assumptions0.items()))
def _eval_subs(self, old, new):
if old.is_Pow:
from sympy.core.power import Pow
return Pow(self, S.One, evaluate=False)._eval_subs(old, new)
def _eval_refine(self, assumptions):
return self
@property
def assumptions0(self):
return self._assumptions0.copy()
@cacheit
def sort_key(self, order=None):
return self.class_key(), (1, (self.name,)), S.One.sort_key(), S.One
def as_dummy(self):
# only put commutativity in explicitly if it is False
return Dummy(self.name) if self.is_commutative is not False \
else Dummy(self.name, commutative=self.is_commutative)
def as_real_imag(self, deep=True, **hints):
if hints.get('ignore') == self:
return None
else:
from sympy.functions.elementary.complexes import im, re
return (re(self), im(self))
def is_constant(self, *wrt, **flags):
if not wrt:
return False
return self not in wrt
@property
def free_symbols(self):
return {self}
binary_symbols = free_symbols # in this case, not always
def as_set(self):
return S.UniversalSet
class Dummy(Symbol):
"""Dummy symbols are each unique, even if they have the same name:
Examples
========
>>> from sympy import Dummy
>>> Dummy("x") == Dummy("x")
False
If a name is not supplied then a string value of an internal count will be
used. This is useful when a temporary variable is needed and the name
of the variable used in the expression is not important.
>>> Dummy() #doctest: +SKIP
_Dummy_10
"""
# In the rare event that a Dummy object needs to be recreated, both the
# `name` and `dummy_index` should be passed. This is used by `srepr` for
# example:
# >>> d1 = Dummy()
# >>> d2 = eval(srepr(d1))
# >>> d2 == d1
# True
#
# If a new session is started between `srepr` and `eval`, there is a very
# small chance that `d2` will be equal to a previously-created Dummy.
_count = 0
_prng = random.Random()
_base_dummy_index = _prng.randint(10**6, 9*10**6)
__slots__ = ('dummy_index',)
is_Dummy = True
def __new__(cls, name=None, dummy_index=None, **assumptions):
if dummy_index is not None:
assert name is not None, "If you specify a dummy_index, you must also provide a name"
if name is None:
name = "Dummy_" + str(Dummy._count)
if dummy_index is None:
dummy_index = Dummy._base_dummy_index + Dummy._count
Dummy._count += 1
cls._sanitize(assumptions, cls)
obj = Symbol.__xnew__(cls, name, **assumptions)
obj.dummy_index = dummy_index
return obj
def __getnewargs_ex__(self):
return ((self.name, self.dummy_index), self._assumptions_orig)
@cacheit
def sort_key(self, order=None):
return self.class_key(), (
2, (self.name, self.dummy_index)), S.One.sort_key(), S.One
def _hashable_content(self):
return Symbol._hashable_content(self) + (self.dummy_index,)
class Wild(Symbol):
"""
A Wild symbol matches anything, or anything
without whatever is explicitly excluded.
Parameters
==========
name : str
Name of the Wild instance.
exclude : iterable, optional
Instances in ``exclude`` will not be matched.
properties : iterable of functions, optional
Functions, each taking an expressions as input
and returns a ``bool``. All functions in ``properties``
need to return ``True`` in order for the Wild instance
to match the expression.
Examples
========
>>> from sympy import Wild, WildFunction, cos, pi
>>> from sympy.abc import x, y, z
>>> a = Wild('a')
>>> x.match(a)
{a_: x}
>>> pi.match(a)
{a_: pi}
>>> (3*x**2).match(a*x)
{a_: 3*x}
>>> cos(x).match(a)
{a_: cos(x)}
>>> b = Wild('b', exclude=[x])
>>> (3*x**2).match(b*x)
>>> b.match(a)
{a_: b_}
>>> A = WildFunction('A')
>>> A.match(a)
{a_: A_}
Tips
====
When using Wild, be sure to use the exclude
keyword to make the pattern more precise.
Without the exclude pattern, you may get matches
that are technically correct, but not what you
wanted. For example, using the above without
exclude:
>>> from sympy import symbols
>>> a, b = symbols('a b', cls=Wild)
>>> (2 + 3*y).match(a*x + b*y)
{a_: 2/x, b_: 3}
This is technically correct, because
(2/x)*x + 3*y == 2 + 3*y, but you probably
wanted it to not match at all. The issue is that
you really did not want a and b to include x and y,
and the exclude parameter lets you specify exactly
this. With the exclude parameter, the pattern will
not match.
>>> a = Wild('a', exclude=[x, y])
>>> b = Wild('b', exclude=[x, y])
>>> (2 + 3*y).match(a*x + b*y)
Exclude also helps remove ambiguity from matches.
>>> E = 2*x**3*y*z
>>> a, b = symbols('a b', cls=Wild)
>>> E.match(a*b)
{a_: 2*y*z, b_: x**3}
>>> a = Wild('a', exclude=[x, y])
>>> E.match(a*b)
{a_: z, b_: 2*x**3*y}
>>> a = Wild('a', exclude=[x, y, z])
>>> E.match(a*b)
{a_: 2, b_: x**3*y*z}
Wild also accepts a ``properties`` parameter:
>>> a = Wild('a', properties=[lambda k: k.is_Integer])
>>> E.match(a*b)
{a_: 2, b_: x**3*y*z}
"""
is_Wild = True
__slots__ = ('exclude', 'properties')
def __new__(cls, name, exclude=(), properties=(), **assumptions):
exclude = tuple([sympify(x) for x in exclude])
properties = tuple(properties)
cls._sanitize(assumptions, cls)
return Wild.__xnew__(cls, name, exclude, properties, **assumptions)
def __getnewargs__(self):
return (self.name, self.exclude, self.properties)
@staticmethod
@cacheit
def __xnew__(cls, name, exclude, properties, **assumptions):
obj = Symbol.__xnew__(cls, name, **assumptions)
obj.exclude = exclude
obj.properties = properties
return obj
def _hashable_content(self):
return super()._hashable_content() + (self.exclude, self.properties)
# TODO add check against another Wild
def matches(self, expr, repl_dict=None, old=False):
if any(expr.has(x) for x in self.exclude):
return None
if not all(f(expr) for f in self.properties):
return None
if repl_dict is None:
repl_dict = {}
else:
repl_dict = repl_dict.copy()
repl_dict[self] = expr
return repl_dict
_range = _re.compile('([0-9]*:[0-9]+|[a-zA-Z]?:[a-zA-Z])')
def symbols(names, *, cls=Symbol, **args) -> Any:
r"""
Transform strings into instances of :class:`Symbol` class.
:func:`symbols` function returns a sequence of symbols with names taken
from ``names`` argument, which can be a comma or whitespace delimited
string, or a sequence of strings::
>>> from sympy import symbols, Function
>>> x, y, z = symbols('x,y,z')
>>> a, b, c = symbols('a b c')
The type of output is dependent on the properties of input arguments::
>>> symbols('x')
x
>>> symbols('x,')
(x,)
>>> symbols('x,y')
(x, y)
>>> symbols(('a', 'b', 'c'))
(a, b, c)
>>> symbols(['a', 'b', 'c'])
[a, b, c]
>>> symbols({'a', 'b', 'c'})
{a, b, c}
If an iterable container is needed for a single symbol, set the ``seq``
argument to ``True`` or terminate the symbol name with a comma::
>>> symbols('x', seq=True)
(x,)
To reduce typing, range syntax is supported to create indexed symbols.
Ranges are indicated by a colon and the type of range is determined by
the character to the right of the colon. If the character is a digit
then all contiguous digits to the left are taken as the nonnegative
starting value (or 0 if there is no digit left of the colon) and all
contiguous digits to the right are taken as 1 greater than the ending
value::
>>> symbols('x:10')
(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9)
>>> symbols('x5:10')
(x5, x6, x7, x8, x9)
>>> symbols('x5(:2)')
(x50, x51)
>>> symbols('x5:10,y:5')
(x5, x6, x7, x8, x9, y0, y1, y2, y3, y4)
>>> symbols(('x5:10', 'y:5'))
((x5, x6, x7, x8, x9), (y0, y1, y2, y3, y4))
If the character to the right of the colon is a letter, then the single
letter to the left (or 'a' if there is none) is taken as the start
and all characters in the lexicographic range *through* the letter to
the right are used as the range::
>>> symbols('x:z')
(x, y, z)
>>> symbols('x:c') # null range
()
>>> symbols('x(:c)')
(xa, xb, xc)
>>> symbols(':c')
(a, b, c)
>>> symbols('a:d, x:z')
(a, b, c, d, x, y, z)
>>> symbols(('a:d', 'x:z'))
((a, b, c, d), (x, y, z))
Multiple ranges are supported; contiguous numerical ranges should be
separated by parentheses to disambiguate the ending number of one
range from the starting number of the next::
>>> symbols('x:2(1:3)')
(x01, x02, x11, x12)
>>> symbols(':3:2') # parsing is from left to right
(00, 01, 10, 11, 20, 21)
Only one pair of parentheses surrounding ranges are removed, so to
include parentheses around ranges, double them. And to include spaces,
commas, or colons, escape them with a backslash::
>>> symbols('x((a:b))')
(x(a), x(b))
>>> symbols(r'x(:1\,:2)') # or r'x((:1)\,(:2))'
(x(0,0), x(0,1))
All newly created symbols have assumptions set according to ``args``::
>>> a = symbols('a', integer=True)
>>> a.is_integer
True
>>> x, y, z = symbols('x,y,z', real=True)
>>> x.is_real and y.is_real and z.is_real
True
Despite its name, :func:`symbols` can create symbol-like objects like
instances of Function or Wild classes. To achieve this, set ``cls``
keyword argument to the desired type::
>>> symbols('f,g,h', cls=Function)
(f, g, h)
>>> type(_[0])
<class 'sympy.core.function.UndefinedFunction'>
"""
result = []
if isinstance(names, str):
marker = 0
splitters = r'\,', r'\:', r'\ '
literals: list[tuple[str, str]] = []
for splitter in splitters:
if splitter in names:
while chr(marker) in names:
marker += 1
lit_char = chr(marker)
marker += 1
names = names.replace(splitter, lit_char)
literals.append((lit_char, splitter[1:]))
def literal(s):
if literals:
for c, l in literals:
s = s.replace(c, l)
return s
names = names.strip()
as_seq = names.endswith(',')
if as_seq:
names = names[:-1].rstrip()
if not names:
raise ValueError('no symbols given')
# split on commas
names = [n.strip() for n in names.split(',')]
if not all(n for n in names):
raise ValueError('missing symbol between commas')
# split on spaces
for i in range(len(names) - 1, -1, -1):
names[i: i + 1] = names[i].split()
seq = args.pop('seq', as_seq)
for name in names:
if not name:
raise ValueError('missing symbol')
if ':' not in name:
symbol = cls(literal(name), **args)
result.append(symbol)
continue
split: list[str] = _range.split(name)
split_list: list[list[str]] = []
# remove 1 layer of bounding parentheses around ranges
for i in range(len(split) - 1):
if i and ':' in split[i] and split[i] != ':' and \
split[i - 1].endswith('(') and \
split[i + 1].startswith(')'):
split[i - 1] = split[i - 1][:-1]
split[i + 1] = split[i + 1][1:]
for s in split:
if ':' in s:
if s.endswith(':'):
raise ValueError('missing end range')
a, b = s.split(':')
if b[-1] in string.digits:
a_i = 0 if not a else int(a)
b_i = int(b)
split_list.append([str(c) for c in range(a_i, b_i)])
else:
a = a or 'a'
split_list.append([string.ascii_letters[c] for c in range(
string.ascii_letters.index(a),
string.ascii_letters.index(b) + 1)]) # inclusive
if not split_list[-1]:
break
else:
split_list.append([s])
else:
seq = True
if len(split_list) == 1:
names = split_list[0]
else:
names = [''.join(s) for s in product(*split_list)]
if literals:
result.extend([cls(literal(s), **args) for s in names])
else:
result.extend([cls(s, **args) for s in names])
if not seq and len(result) <= 1:
if not result:
return ()
return result[0]
return tuple(result)
else:
for name in names:
result.append(symbols(name, cls=cls, **args))
return type(names)(result)
def var(names, **args):
"""
Create symbols and inject them into the global namespace.
Explanation
===========
This calls :func:`symbols` with the same arguments and puts the results
into the *global* namespace. It's recommended not to use :func:`var` in
library code, where :func:`symbols` has to be used::
Examples
========
>>> from sympy import var
>>> var('x')
x
>>> x # noqa: F821
x
>>> var('a,ab,abc')
(a, ab, abc)
>>> abc # noqa: F821
abc
>>> var('x,y', real=True)
(x, y)
>>> x.is_real and y.is_real # noqa: F821
True
See :func:`symbols` documentation for more details on what kinds of
arguments can be passed to :func:`var`.
"""
def traverse(symbols, frame):
"""Recursively inject symbols to the global namespace. """
for symbol in symbols:
if isinstance(symbol, Basic):
frame.f_globals[symbol.name] = symbol
elif isinstance(symbol, FunctionClass):
frame.f_globals[symbol.__name__] = symbol
else:
traverse(symbol, frame)
from inspect import currentframe
frame = currentframe().f_back
try:
syms = symbols(names, **args)
if syms is not None:
if isinstance(syms, Basic):
frame.f_globals[syms.name] = syms
elif isinstance(syms, FunctionClass):
frame.f_globals[syms.__name__] = syms
else:
traverse(syms, frame)
finally:
del frame # break cyclic dependencies as stated in inspect docs
return syms
def disambiguate(*iter):
"""
Return a Tuple containing the passed expressions with symbols
that appear the same when printed replaced with numerically
subscripted symbols, and all Dummy symbols replaced with Symbols.
Parameters
==========
iter: list of symbols or expressions.
Examples
========
>>> from sympy.core.symbol import disambiguate
>>> from sympy import Dummy, Symbol, Tuple
>>> from sympy.abc import y
>>> tup = Symbol('_x'), Dummy('x'), Dummy('x')
>>> disambiguate(*tup)
(x_2, x, x_1)
>>> eqs = Tuple(Symbol('x')/y, Dummy('x')/y)
>>> disambiguate(*eqs)
(x_1/y, x/y)
>>> ix = Symbol('x', integer=True)
>>> vx = Symbol('x')
>>> disambiguate(vx + ix)
(x + x_1,)
To make your own mapping of symbols to use, pass only the free symbols
of the expressions and create a dictionary:
>>> free = eqs.free_symbols
>>> mapping = dict(zip(free, disambiguate(*free)))
>>> eqs.xreplace(mapping)
(x_1/y, x/y)
"""
new_iter = Tuple(*iter)
key = lambda x:tuple(sorted(x.assumptions0.items()))
syms = ordered(new_iter.free_symbols, keys=key)
mapping = {}
for s in syms:
mapping.setdefault(str(s).lstrip('_'), []).append(s)
reps = {}
for k in mapping:
# the first or only symbol doesn't get subscripted but make
# sure that it's a Symbol, not a Dummy
mapk0 = Symbol("%s" % (k), **mapping[k][0].assumptions0)
if mapping[k][0] != mapk0:
reps[mapping[k][0]] = mapk0
# the others get subscripts (and are made into Symbols)
skip = 0
for i in range(1, len(mapping[k])):
while True:
name = "%s_%i" % (k, i + skip)
if name not in mapping:
break
skip += 1
ki = mapping[k][i]
reps[ki] = Symbol(name, **ki.assumptions0)
return new_iter.xreplace(reps)

View File

@ -0,0 +1,620 @@
"""sympify -- convert objects SymPy internal format"""
from __future__ import annotations
from typing import Any, Callable
import mpmath.libmp as mlib
from inspect import getmro
import string
from sympy.core.random import choice
from .parameters import global_parameters
from sympy.utilities.iterables import iterable
class SympifyError(ValueError):
def __init__(self, expr, base_exc=None):
self.expr = expr
self.base_exc = base_exc
def __str__(self):
if self.base_exc is None:
return "SympifyError: %r" % (self.expr,)
return ("Sympify of expression '%s' failed, because of exception being "
"raised:\n%s: %s" % (self.expr, self.base_exc.__class__.__name__,
str(self.base_exc)))
converter: dict[type[Any], Callable[[Any], Basic]] = {}
#holds the conversions defined in SymPy itself, i.e. non-user defined conversions
_sympy_converter: dict[type[Any], Callable[[Any], Basic]] = {}
#alias for clearer use in the library
_external_converter = converter
class CantSympify:
"""
Mix in this trait to a class to disallow sympification of its instances.
Examples
========
>>> from sympy import sympify
>>> from sympy.core.sympify import CantSympify
>>> class Something(dict):
... pass
...
>>> sympify(Something())
{}
>>> class Something(dict, CantSympify):
... pass
...
>>> sympify(Something())
Traceback (most recent call last):
...
SympifyError: SympifyError: {}
"""
__slots__ = ()
def _is_numpy_instance(a):
"""
Checks if an object is an instance of a type from the numpy module.
"""
# This check avoids unnecessarily importing NumPy. We check the whole
# __mro__ in case any base type is a numpy type.
return any(type_.__module__ == 'numpy'
for type_ in type(a).__mro__)
def _convert_numpy_types(a, **sympify_args):
"""
Converts a numpy datatype input to an appropriate SymPy type.
"""
import numpy as np
if not isinstance(a, np.floating):
if np.iscomplex(a):
return _sympy_converter[complex](a.item())
else:
return sympify(a.item(), **sympify_args)
else:
from .numbers import Float
prec = np.finfo(a).nmant + 1
# E.g. double precision means prec=53 but nmant=52
# Leading bit of mantissa is always 1, so is not stored
p, q = a.as_integer_ratio()
a = mlib.from_rational(p, q, prec)
return Float(a, precision=prec)
def sympify(a, locals=None, convert_xor=True, strict=False, rational=False,
evaluate=None):
"""
Converts an arbitrary expression to a type that can be used inside SymPy.
Explanation
===========
It will convert Python ints into instances of :class:`~.Integer`, floats
into instances of :class:`~.Float`, etc. It is also able to coerce
symbolic expressions which inherit from :class:`~.Basic`. This can be
useful in cooperation with SAGE.
.. warning::
Note that this function uses ``eval``, and thus shouldn't be used on
unsanitized input.
If the argument is already a type that SymPy understands, it will do
nothing but return that value. This can be used at the beginning of a
function to ensure you are working with the correct type.
Examples
========
>>> from sympy import sympify
>>> sympify(2).is_integer
True
>>> sympify(2).is_real
True
>>> sympify(2.0).is_real
True
>>> sympify("2.0").is_real
True
>>> sympify("2e-45").is_real
True
If the expression could not be converted, a SympifyError is raised.
>>> sympify("x***2")
Traceback (most recent call last):
...
SympifyError: SympifyError: "could not parse 'x***2'"
When attempting to parse non-Python syntax using ``sympify``, it raises a
``SympifyError``:
>>> sympify("2x+1")
Traceback (most recent call last):
...
SympifyError: Sympify of expression 'could not parse '2x+1'' failed
To parse non-Python syntax, use ``parse_expr`` from ``sympy.parsing.sympy_parser``.
>>> from sympy.parsing.sympy_parser import parse_expr
>>> parse_expr("2x+1", transformations="all")
2*x + 1
For more details about ``transformations``: see :func:`~sympy.parsing.sympy_parser.parse_expr`
Locals
------
The sympification happens with access to everything that is loaded
by ``from sympy import *``; anything used in a string that is not
defined by that import will be converted to a symbol. In the following,
the ``bitcount`` function is treated as a symbol and the ``O`` is
interpreted as the :class:`~.Order` object (used with series) and it raises
an error when used improperly:
>>> s = 'bitcount(42)'
>>> sympify(s)
bitcount(42)
>>> sympify("O(x)")
O(x)
>>> sympify("O + 1")
Traceback (most recent call last):
...
TypeError: unbound method...
In order to have ``bitcount`` be recognized it can be imported into a
namespace dictionary and passed as locals:
>>> ns = {}
>>> exec('from sympy.core.evalf import bitcount', ns)
>>> sympify(s, locals=ns)
6
In order to have the ``O`` interpreted as a Symbol, identify it as such
in the namespace dictionary. This can be done in a variety of ways; all
three of the following are possibilities:
>>> from sympy import Symbol
>>> ns["O"] = Symbol("O") # method 1
>>> exec('from sympy.abc import O', ns) # method 2
>>> ns.update(dict(O=Symbol("O"))) # method 3
>>> sympify("O + 1", locals=ns)
O + 1
If you want *all* single-letter and Greek-letter variables to be symbols
then you can use the clashing-symbols dictionaries that have been defined
there as private variables: ``_clash1`` (single-letter variables),
``_clash2`` (the multi-letter Greek names) or ``_clash`` (both single and
multi-letter names that are defined in ``abc``).
>>> from sympy.abc import _clash1
>>> set(_clash1) # if this fails, see issue #23903
{'E', 'I', 'N', 'O', 'Q', 'S'}
>>> sympify('I & Q', _clash1)
I & Q
Strict
------
If the option ``strict`` is set to ``True``, only the types for which an
explicit conversion has been defined are converted. In the other
cases, a SympifyError is raised.
>>> print(sympify(None))
None
>>> sympify(None, strict=True)
Traceback (most recent call last):
...
SympifyError: SympifyError: None
.. deprecated:: 1.6
``sympify(obj)`` automatically falls back to ``str(obj)`` when all
other conversion methods fail, but this is deprecated. ``strict=True``
will disable this deprecated behavior. See
:ref:`deprecated-sympify-string-fallback`.
Evaluation
----------
If the option ``evaluate`` is set to ``False``, then arithmetic and
operators will be converted into their SymPy equivalents and the
``evaluate=False`` option will be added. Nested ``Add`` or ``Mul`` will
be denested first. This is done via an AST transformation that replaces
operators with their SymPy equivalents, so if an operand redefines any
of those operations, the redefined operators will not be used. If
argument a is not a string, the mathematical expression is evaluated
before being passed to sympify, so adding ``evaluate=False`` will still
return the evaluated result of expression.
>>> sympify('2**2 / 3 + 5')
19/3
>>> sympify('2**2 / 3 + 5', evaluate=False)
2**2/3 + 5
>>> sympify('4/2+7', evaluate=True)
9
>>> sympify('4/2+7', evaluate=False)
4/2 + 7
>>> sympify(4/2+7, evaluate=False)
9.00000000000000
Extending
---------
To extend ``sympify`` to convert custom objects (not derived from ``Basic``),
just define a ``_sympy_`` method to your class. You can do that even to
classes that you do not own by subclassing or adding the method at runtime.
>>> from sympy import Matrix
>>> class MyList1(object):
... def __iter__(self):
... yield 1
... yield 2
... return
... def __getitem__(self, i): return list(self)[i]
... def _sympy_(self): return Matrix(self)
>>> sympify(MyList1())
Matrix([
[1],
[2]])
If you do not have control over the class definition you could also use the
``converter`` global dictionary. The key is the class and the value is a
function that takes a single argument and returns the desired SymPy
object, e.g. ``converter[MyList] = lambda x: Matrix(x)``.
>>> class MyList2(object): # XXX Do not do this if you control the class!
... def __iter__(self): # Use _sympy_!
... yield 1
... yield 2
... return
... def __getitem__(self, i): return list(self)[i]
>>> from sympy.core.sympify import converter
>>> converter[MyList2] = lambda x: Matrix(x)
>>> sympify(MyList2())
Matrix([
[1],
[2]])
Notes
=====
The keywords ``rational`` and ``convert_xor`` are only used
when the input is a string.
convert_xor
-----------
>>> sympify('x^y',convert_xor=True)
x**y
>>> sympify('x^y',convert_xor=False)
x ^ y
rational
--------
>>> sympify('0.1',rational=False)
0.1
>>> sympify('0.1',rational=True)
1/10
Sometimes autosimplification during sympification results in expressions
that are very different in structure than what was entered. Until such
autosimplification is no longer done, the ``kernS`` function might be of
some use. In the example below you can see how an expression reduces to
$-1$ by autosimplification, but does not do so when ``kernS`` is used.
>>> from sympy.core.sympify import kernS
>>> from sympy.abc import x
>>> -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1
-1
>>> s = '-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1'
>>> sympify(s)
-1
>>> kernS(s)
-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1
Parameters
==========
a :
- any object defined in SymPy
- standard numeric Python types: ``int``, ``long``, ``float``, ``Decimal``
- strings (like ``"0.09"``, ``"2e-19"`` or ``'sin(x)'``)
- booleans, including ``None`` (will leave ``None`` unchanged)
- dicts, lists, sets or tuples containing any of the above
convert_xor : bool, optional
If true, treats ``^`` as exponentiation.
If False, treats ``^`` as XOR itself.
Used only when input is a string.
locals : any object defined in SymPy, optional
In order to have strings be recognized it can be imported
into a namespace dictionary and passed as locals.
strict : bool, optional
If the option strict is set to ``True``, only the types for which
an explicit conversion has been defined are converted. In the
other cases, a SympifyError is raised.
rational : bool, optional
If ``True``, converts floats into :class:`~.Rational`.
If ``False``, it lets floats remain as it is.
Used only when input is a string.
evaluate : bool, optional
If False, then arithmetic and operators will be converted into
their SymPy equivalents. If True the expression will be evaluated
and the result will be returned.
"""
# XXX: If a is a Basic subclass rather than instance (e.g. sin rather than
# sin(x)) then a.__sympy__ will be the property. Only on the instance will
# a.__sympy__ give the *value* of the property (True). Since sympify(sin)
# was used for a long time we allow it to pass. However if strict=True as
# is the case in internal calls to _sympify then we only allow
# is_sympy=True.
#
# https://github.com/sympy/sympy/issues/20124
is_sympy = getattr(a, '__sympy__', None)
if is_sympy is True:
return a
elif is_sympy is not None:
if not strict:
return a
else:
raise SympifyError(a)
if isinstance(a, CantSympify):
raise SympifyError(a)
cls = getattr(a, "__class__", None)
#Check if there exists a converter for any of the types in the mro
for superclass in getmro(cls):
#First check for user defined converters
conv = _external_converter.get(superclass)
if conv is None:
#if none exists, check for SymPy defined converters
conv = _sympy_converter.get(superclass)
if conv is not None:
return conv(a)
if cls is type(None):
if strict:
raise SympifyError(a)
else:
return a
if evaluate is None:
evaluate = global_parameters.evaluate
# Support for basic numpy datatypes
if _is_numpy_instance(a):
import numpy as np
if np.isscalar(a):
return _convert_numpy_types(a, locals=locals,
convert_xor=convert_xor, strict=strict, rational=rational,
evaluate=evaluate)
_sympy_ = getattr(a, "_sympy_", None)
if _sympy_ is not None:
return a._sympy_()
if not strict:
# Put numpy array conversion _before_ float/int, see
# <https://github.com/sympy/sympy/issues/13924>.
flat = getattr(a, "flat", None)
if flat is not None:
shape = getattr(a, "shape", None)
if shape is not None:
from sympy.tensor.array import Array
return Array(a.flat, a.shape) # works with e.g. NumPy arrays
if not isinstance(a, str):
if _is_numpy_instance(a):
import numpy as np
assert not isinstance(a, np.number)
if isinstance(a, np.ndarray):
# Scalar arrays (those with zero dimensions) have sympify
# called on the scalar element.
if a.ndim == 0:
try:
return sympify(a.item(),
locals=locals,
convert_xor=convert_xor,
strict=strict,
rational=rational,
evaluate=evaluate)
except SympifyError:
pass
elif hasattr(a, '__float__'):
# float and int can coerce size-one numpy arrays to their lone
# element. See issue https://github.com/numpy/numpy/issues/10404.
return sympify(float(a))
elif hasattr(a, '__int__'):
return sympify(int(a))
if strict:
raise SympifyError(a)
if iterable(a):
try:
return type(a)([sympify(x, locals=locals, convert_xor=convert_xor,
rational=rational, evaluate=evaluate) for x in a])
except TypeError:
# Not all iterables are rebuildable with their type.
pass
if not isinstance(a, str):
raise SympifyError('cannot sympify object of type %r' % type(a))
from sympy.parsing.sympy_parser import (parse_expr, TokenError,
standard_transformations)
from sympy.parsing.sympy_parser import convert_xor as t_convert_xor
from sympy.parsing.sympy_parser import rationalize as t_rationalize
transformations = standard_transformations
if rational:
transformations += (t_rationalize,)
if convert_xor:
transformations += (t_convert_xor,)
try:
a = a.replace('\n', '')
expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate)
except (TokenError, SyntaxError) as exc:
raise SympifyError('could not parse %r' % a, exc)
return expr
def _sympify(a):
"""
Short version of :func:`~.sympify` for internal usage for ``__add__`` and
``__eq__`` methods where it is ok to allow some things (like Python
integers and floats) in the expression. This excludes things (like strings)
that are unwise to allow into such an expression.
>>> from sympy import Integer
>>> Integer(1) == 1
True
>>> Integer(1) == '1'
False
>>> from sympy.abc import x
>>> x + 1
x + 1
>>> x + '1'
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Symbol' and 'str'
see: sympify
"""
return sympify(a, strict=True)
def kernS(s):
"""Use a hack to try keep autosimplification from distributing a
a number into an Add; this modification does not
prevent the 2-arg Mul from becoming an Add, however.
Examples
========
>>> from sympy.core.sympify import kernS
>>> from sympy.abc import x, y
The 2-arg Mul distributes a number (or minus sign) across the terms
of an expression, but kernS will prevent that:
>>> 2*(x + y), -(x + 1)
(2*x + 2*y, -x - 1)
>>> kernS('2*(x + y)')
2*(x + y)
>>> kernS('-(x + 1)')
-(x + 1)
If use of the hack fails, the un-hacked string will be passed to sympify...
and you get what you get.
XXX This hack should not be necessary once issue 4596 has been resolved.
"""
hit = False
quoted = '"' in s or "'" in s
if '(' in s and not quoted:
if s.count('(') != s.count(")"):
raise SympifyError('unmatched left parenthesis')
# strip all space from s
s = ''.join(s.split())
olds = s
# now use space to represent a symbol that
# will
# step 1. turn potential 2-arg Muls into 3-arg versions
# 1a. *( -> * *(
s = s.replace('*(', '* *(')
# 1b. close up exponentials
s = s.replace('** *', '**')
# 2. handle the implied multiplication of a negated
# parenthesized expression in two steps
# 2a: -(...) --> -( *(...)
target = '-( *('
s = s.replace('-(', target)
# 2b: double the matching closing parenthesis
# -( *(...) --> -( *(...))
i = nest = 0
assert target.endswith('(') # assumption below
while True:
j = s.find(target, i)
if j == -1:
break
j += len(target) - 1
for j in range(j, len(s)):
if s[j] == "(":
nest += 1
elif s[j] == ")":
nest -= 1
if nest == 0:
break
s = s[:j] + ")" + s[j:]
i = j + 2 # the first char after 2nd )
if ' ' in s:
# get a unique kern
kern = '_'
while kern in s:
kern += choice(string.ascii_letters + string.digits)
s = s.replace(' ', kern)
hit = kern in s
else:
hit = False
for i in range(2):
try:
expr = sympify(s)
break
except TypeError: # the kern might cause unknown errors...
if hit:
s = olds # maybe it didn't like the kern; use un-kerned s
hit = False
continue
expr = sympify(s) # let original error raise
if not hit:
return expr
from .symbol import Symbol
rep = {Symbol(kern): 1}
def _clear(expr):
if isinstance(expr, (list, tuple, set)):
return type(expr)([_clear(e) for e in expr])
if hasattr(expr, 'subs'):
return expr.subs(rep, hack2=True)
return expr
expr = _clear(expr)
# hope that kern is not there anymore
return expr
# Avoid circular import
from .basic import Basic

Some files were not shown because too many files have changed in this diff Show More