946 lines
28 KiB
Python
946 lines
28 KiB
Python
|
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):
|
||
|
"""
|
||
|
Assumptions:
|
||
|
commutative = True
|
||
|
|
||
|
You can override the default assumptions in the constructor.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import symbols
|
||
|
>>> A,B = symbols('A,B', commutative = False)
|
||
|
>>> bool(A*B != B*A)
|
||
|
True
|
||
|
>>> bool(A*B*2 == 2*A*B) == True # multiplication by scalars is commutative
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
|
||
|
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)
|