993 lines
29 KiB
Python
993 lines
29 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
|
||
|
||
|
||
# XXX: We need type: ignore below because Expr and Boolean are incompatible as
|
||
# superclasses. Really Symbol should not be a subclass of Boolean.
|
||
|
||
|
||
class Symbol(AtomicExpr, Boolean): # type: ignore
|
||
"""
|
||
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
|
||
@cacheit
|
||
def _canonical_assumptions(**assumptions):
|
||
# 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 commutative=True:
|
||
assumptions.setdefault('commutative', True)
|
||
|
||
assumptions_kb = StdFactKB(assumptions)
|
||
assumptions0 = dict(assumptions_kb)
|
||
|
||
return assumptions_kb, assumptions_orig, assumptions0
|
||
|
||
@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)))
|
||
|
||
|
||
obj = Expr.__new__(cls)
|
||
obj.name = name
|
||
|
||
assumptions_kb, assumptions_orig, assumptions0 = Symbol._canonical_assumptions(**assumptions)
|
||
|
||
obj._assumptions = assumptions_kb
|
||
obj._assumptions_orig = assumptions_orig
|
||
obj._assumptions0 = tuple(sorted(assumptions0.items()))
|
||
|
||
# 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):
|
||
return (self.name,) + self._assumptions0
|
||
|
||
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 dict(self._assumptions0)
|
||
|
||
@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)
|