Spaces:
Running
Running
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 | |
def kind(self): | |
if self.is_commutative: | |
return NumberKind | |
return UndefinedKind | |
def _diff_wrt(self): | |
"""Allow derivatives wrt Symbols. | |
Examples | |
======== | |
>>> from sympy import Symbol | |
>>> x = Symbol('x') | |
>>> x._diff_wrt | |
True | |
""" | |
return True | |
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) | |
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 | |
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 | |
def assumptions0(self): | |
return self._assumptions0.copy() | |
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 | |
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) | |
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) | |
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) | |