|
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*. |
|
""" |
|
|
|
|
|
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) |
|
|
|
|
|
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): |
|
if not isinstance(name, str): |
|
raise TypeError("name should be a string, not %s" % repr(type(name))) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assumptions_orig = assumptions.copy() |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return obj |
|
|
|
@staticmethod |
|
@cacheit |
|
def __xnew_cached_(cls, name, **assumptions): |
|
return Symbol.__xnew__(cls, name, **assumptions) |
|
|
|
def __getnewargs_ex__(self): |
|
return ((self.name,), self._assumptions_orig) |
|
|
|
|
|
|
|
|
|
|
|
|
|
def __setstate__(self, state): |
|
for name, value in state.items(): |
|
setattr(self, name, value) |
|
|
|
def _hashable_content(self): |
|
|
|
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): |
|
|
|
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 |
|
|
|
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 |
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_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) |
|
|
|
|
|
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') |
|
|
|
|
|
names = [n.strip() for n in names.split(',')] |
|
if not all(n for n in names): |
|
raise ValueError('missing symbol between commas') |
|
|
|
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]] = [] |
|
|
|
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)]) |
|
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 |
|
|
|
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: |
|
|
|
|
|
mapk0 = Symbol("%s" % (k), **mapping[k][0].assumptions0) |
|
if mapping[k][0] != mapk0: |
|
reps[mapping[k][0]] = mapk0 |
|
|
|
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) |
|
|