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]) """ 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)