def doprint(self, funcname, args, expr): """Returns the function definition code as a string.""" from sympy import Dummy funcbody = [] if not iterable(args): args = [args] argstrs, expr = self._preprocess(args, expr) # Generate argument unpacking and final argument list funcargs = [] unpackings = [] for argstr in argstrs: if iterable(argstr): funcargs.append(self._argrepr(Dummy())) unpackings.extend(self._print_unpacking(argstr, funcargs[-1])) else: funcargs.append(argstr) funcsig = 'def {}({}):'.format(funcname, ', '.join(funcargs)) # Wrap input arguments before unpacking funcbody.extend(self._print_funcargwrapping(funcargs)) funcbody.extend(unpackings) funcbody.append('return ({})'.format(self._exprrepr(expr))) funclines = [funcsig] funclines.extend(' ' + line for line in funcbody) return '\n'.join(funclines) + '\n'
def reduce_inequalities(inequalities, symbols=[]): """Reduce a system of inequalities with rational coefficients. Examples ======== >>> from sympy import sympify as S, Symbol >>> from sympy.abc import x, y >>> from sympy.solvers.inequalities import reduce_inequalities >>> reduce_inequalities(0 <= x + 3, []) And(-3 <= x, x < oo) >>> reduce_inequalities(0 <= x + y*2 - 1, [x]) x >= -2*y + 1 """ if not iterable(inequalities): inequalities = [inequalities] inequalities = [sympify(i) for i in inequalities] gens = set().union(*[i.free_symbols for i in inequalities]) if not iterable(symbols): symbols = [symbols] symbols = (set(symbols) or gens) & gens if any(i.is_real is False for i in symbols): raise TypeError( filldedent( """ inequalities cannot contain symbols that are not real.""" ) ) # make vanilla symbol real recast = dict([(i, Dummy(i.name, real=True)) for i in gens if i.is_real is None]) inequalities = [i.xreplace(recast) for i in inequalities] symbols = set([i.xreplace(recast) for i in symbols]) # prefilter keep = [] for i in inequalities: if isinstance(i, Relational): i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0) elif i not in (True, False): i = Eq(i, 0) if i == True: continue elif i == False: return S.false if i.lhs.is_number: raise NotImplementedError("could not determine truth value of %s" % i) keep.append(i) inequalities = keep del keep # solve system rv = _reduce_inequalities(inequalities, symbols) # restore original symbols and return return rv.xreplace(dict([(v, k) for k, v in recast.items()]))
def test_iterable_is_sequence(): ordered = [list(), tuple(), Tuple(), Matrix([[]])] unordered = [set()] not_sympy_iterable = [{}, '', u('')] assert all(is_sequence(i) for i in ordered) assert all(not is_sequence(i) for i in unordered) assert all(iterable(i) for i in ordered + unordered) assert all(not iterable(i) for i in not_sympy_iterable) assert all(iterable(i, exclude=None) for i in not_sympy_iterable)
def test_iterable_ordered_iter(): ordered = [list(), tuple(), Tuple(), Matrix([[]])] unordered = [set()] not_sympy_iterable = [{}, ''] assert all(ordered_iter(i) for i in ordered) assert all(not ordered_iter(i) for i in unordered) assert all(iterable(i) for i in ordered + unordered) assert all(not iterable(i) for i in not_sympy_iterable) assert all(iterable(i, exclude=None) for i in not_sympy_iterable)
def test_iterable(): assert iterable(0) is False assert iterable(1) is False assert iterable(None) is False class Test1(NotIterable): pass assert iterable(Test1()) is False class Test2(NotIterable): _iterable = True assert iterable(Test2()) is True class Test3(object): pass assert iterable(Test3()) is False class Test4(object): _iterable = True assert iterable(Test4()) is True class Test5(object): def __iter__(self): yield 1 assert iterable(Test5()) is True class Test6(Test5): _iterable = False assert iterable(Test6()) is False
def _find_opts(expr): if expr.is_Atom or expr.is_Order: return if iterable(expr): list(map(_find_opts, expr)) return if expr in seen_subexp: return expr seen_subexp.add(expr) list(map(_find_opts, expr.args)) if _coeff_isneg(expr): neg_expr = -expr if not neg_expr.is_Atom: opt_subs[expr] = Mul(S.NegativeOne, neg_expr, evaluate=False) seen_subexp.add(neg_expr) expr = neg_expr if expr.is_Mul: muls.add(expr) elif expr.is_Add: adds.add(expr) elif expr.is_Pow: if _coeff_isneg(expr.exp): opt_subs[expr] = Pow(Pow(expr.base, -expr.exp), S.NegativeOne, evaluate=False)
def _find_repeated(expr): if not isinstance(expr, Basic): return if expr.is_Atom or expr.is_Order: return if iterable(expr): args = expr else: if expr in seen_subexp: for ign in ignore: if ign in expr.free_symbols: break else: to_eliminate.add(expr) return seen_subexp.add(expr) if expr in opt_subs: expr = opt_subs[expr] args = expr.args list(map(_find_repeated, args))
def _find_opts(expr): if not isinstance(expr, Basic): return if expr.is_Atom or expr.is_Order: return if iterable(expr): list(map(_find_opts, expr)) return if expr in seen_subexp: return expr seen_subexp.add(expr) list(map(_find_opts, expr.args)) if _coeff_isneg(expr): neg_expr = -expr if not neg_expr.is_Atom: opt_subs[expr] = Mul(S.NegativeOne, neg_expr, evaluate=False) seen_subexp.add(neg_expr) expr = neg_expr if isinstance(expr, (Mul, MatMul)): muls.add(expr) elif isinstance(expr, (Add, MatAdd)): adds.add(expr) elif isinstance(expr, (Pow, MatPow)): if _coeff_isneg(expr.exp): opt_subs[expr] = Pow(Pow(expr.base, -expr.exp), S.NegativeOne, evaluate=False)
def fuzzy_and(*args): """Return True (all True), False (any False) or None. If `a` is an iterable it must have more than one element.""" if (len(args) == 1 and iterable(args[0]) or len(args) > 2): if len(args) == 1: args = args[0] rv = True i = 0 for ai in args: ai = fuzzy_bool(ai) if ai is False: return False if rv: # this will stop updating if a None is ever trapped rv = ai i += 1 if i < 2: raise ValueError('iterables must have 2 or more elements') return rv a, b = [fuzzy_bool(i) for i in args] if a is True and b is True: return True elif a is False or b is False: return False
def __new__(cls, *args, **kwargs): evaluate = kwargs.get('evaluate', global_evaluate[0]) if iterable(args[0]): if isinstance(args[0], Point) and not evaluate: return args[0] args = args[0] # unpack the arguments into a friendly Tuple # if we were already a Point, we're doing an excess # iteration, but we'll worry about efficiency later coords = Tuple(*args) if any(a.is_number and im(a) for a in coords): raise ValueError('Imaginary coordinates not permitted.') # Turn any Floats into rationals and simplify # any expressions before we instantiate if evaluate: coords = coords.xreplace(dict( [(f, simplify(nsimplify(f, rational=True))) for f in coords.atoms(Float)])) if len(coords) == 2: return Point2D(coords, **kwargs) if len(coords) == 3: return Point3D(coords, **kwargs) return GeometryEntity.__new__(cls, *coords)
def lambdastr(args, expr, printer=None): """ Returns a string that can be evaluated to a lambda function. >>> from sympy.abc import x, y, z >>> from sympy.utilities.lambdify import lambdastr >>> lambdastr(x, x**2) 'lambda x: (x**2)' >>> lambdastr((x,y,z), [z,y,x]) 'lambda x,y,z: ([z, y, x])' """ if printer is not None: if inspect.isfunction(printer): lambdarepr = printer else: if inspect.isclass(printer): lambdarepr = lambda expr: printer().doprint(expr) else: lambdarepr = lambda expr: printer.doprint(expr) else: #XXX: This has to be done here because of circular imports from sympy.printing.lambdarepr import lambdarepr # Transform everything to strings. from sympy.matrices import DeferredVector expr = lambdarepr(expr) if isinstance(args, str): pass elif iterable(args, exclude=DeferredVector): args = ",".join(str(a) for a in args) else: args = str(args) return "lambda %s: (%s)" % (args, expr)
def _walsh_hadamard_transform(seq, inverse=False): """Utility function for the Walsh Hadamard Transform""" if not iterable(seq): raise TypeError("Expected a sequence of coefficients " "for Walsh Hadamard Transform") a = [sympify(arg) for arg in seq] n = len(a) if n < 2: return a if n&(n - 1): n = 2**n.bit_length() a += [S.Zero]*(n - len(a)) h = 2 while h <= n: hf, ut = h // 2, n // h for i in range(0, n, h): for j in range(hf): u, v = a[i + j], a[i + j + hf] a[i + j], a[i + j + hf] = u + v, u - v h *= 2 if inverse: a = [x/n for x in a] return a
def _preprocess(self, args, expr): """Preprocess args, expr to replace arguments that do not map to valid Python identifiers. Returns string form of args, and updated expr. """ from sympy import Dummy, Function, flatten, Derivative, ordered, Basic from sympy.matrices import DeferredVector # Args of type Dummy can cause name collisions with args # of type Symbol. Force dummify of everything in this # situation. dummify = self._dummify or any( isinstance(arg, Dummy) for arg in flatten(args)) argstrs = [None]*len(args) for arg, i in reversed(list(ordered(zip(args, range(len(args)))))): if iterable(arg): s, expr = self._preprocess(arg, expr) elif isinstance(arg, DeferredVector): s = str(arg) elif isinstance(arg, Basic) and arg.is_symbol: s = self._argrepr(arg) if dummify or not self._is_safe_ident(s): dummy = Dummy() s = self._argrepr(dummy) expr = self._subexpr(expr, {arg: dummy}) elif dummify or isinstance(arg, (Function, Derivative)): dummy = Dummy() s = self._argrepr(dummy) expr = self._subexpr(expr, {arg: dummy}) else: s = str(arg) argstrs[i] = s return argstrs, expr
def convert(self, elem, M=None): """ Convert ``elem`` into the internal representation. This method is called implicitly whenever computations involve elements not in the internal representation. >>> from sympy.abc import x >>> from sympy import QQ >>> F = QQ.old_poly_ring(x).free_module(2) >>> F.convert([1, 0]) [1, 0] """ if isinstance(elem, FreeModuleElement): if elem.module is self: return elem if elem.module.rank != self.rank: raise CoercionFailed return FreeModuleElement(self, tuple(self.ring.convert(x, elem.module.ring) for x in elem.data)) elif iterable(elem): tpl = tuple(self.ring.convert(x) for x in elem) if len(tpl) != self.rank: raise CoercionFailed return FreeModuleElement(self, tpl) elif elem is 0: return FreeModuleElement(self, (self.ring.convert(0),)*self.rank) else: raise CoercionFailed
def is_sequence(i, include=None): """ Return a boolean indicating whether ``i`` is a sequence in the SymPy sense. If anything that fails the test below should be included as being a sequence for your application, set 'include' to that object's type; multiple types should be passed as a tuple of types. Note: although generators can generate a sequence, they often need special handling to make sure their elements are captured before the generator is exhausted, so these are not included by default in the definition of a sequence. See also: iterable Examples ======== >>> from sympy.utilities.iterables import is_sequence >>> from types import GeneratorType >>> is_sequence([]) True >>> is_sequence(set()) False >>> is_sequence('abc') False >>> is_sequence('abc', include=str) True >>> generator = (c for c in 'abc') >>> is_sequence(generator) False >>> is_sequence(generator, include=(str, GeneratorType)) True """ return hasattr(i, "__getitem__") and iterable(i) or bool(include) and isinstance(i, include)
def sub_args(args, dummies_dict): if isinstance(args, str): return args elif isinstance(args, DeferredVector): return str(args) elif iterable(args): flatten = lambda *n: (e for a in n for e in (flatten(*a) if iterable(a) else (a,))) dummies = flatten([sub_args(a, dummies_dict) for a in args]) return ",".join(str(a) for a in dummies) else: if isinstance(args, Function): dummies = Dummy() dummies_dict.update({args : dummies}) return str(dummies) else: return str(args)
def flatten(arg): if isinstance(arg, Set): if arg.is_ProductSet: return sum(map(flatten, arg.args), []) else: return [arg] elif iterable(arg): return sum(map(flatten, arg), []) raise TypeError("Input must be Sets or iterables of Sets")
def flatten(arg): if isinstance(arg, Set): if arg.is_Intersection: return sum(map(flatten, arg.args), []) else: return [arg] if iterable(arg): # and not isinstance(arg, Set) (implicit) return sum(map(flatten, arg), []) raise TypeError("Input must be Sets or iterables of Sets")
def group_to_finiteset(group): """ Converts ``group`` to a :class:``FiniteSet`` if it is an iterable. """ if iterable(group): return FiniteSet(group) else: return group
def _flatten(arg): if isinstance(arg, SeqBase): if isinstance(arg, SeqMul): return sum(map(_flatten, arg.args), []) else: return [arg] elif iterable(arg): return sum(map(_flatten, arg), []) raise TypeError("Input must be Sequences or " " iterables of Sequences")
def _rebuild(expr): if not isinstance(expr, Basic): return expr if not expr.args: return expr if iterable(expr): new_args = [_rebuild(arg) for arg in expr] return expr.func(*new_args) if expr in subs: return subs[expr] orig_expr = expr if expr in opt_subs: expr = opt_subs[expr] # If enabled, parse Muls and Adds arguments by order to ensure # replacement order independent from hashes if order != 'none': if isinstance(expr, (Mul, MatMul)): c, nc = expr.args_cnc() if c == [1]: args = nc else: args = list(ordered(c)) + nc elif isinstance(expr, (Add, MatAdd)): args = list(ordered(expr.args)) else: args = expr.args else: args = expr.args new_args = list(map(_rebuild, args)) if new_args != args: new_expr = expr.func(*new_args) else: new_expr = expr if orig_expr in to_eliminate: try: sym = next(symbols) except StopIteration: raise ValueError("Symbols iterator ran out of symbols.") if isinstance(orig_expr, MatrixExpr): sym = MatrixSymbol(sym.name, orig_expr.rows, orig_expr.cols) subs[orig_expr] = sym replacements.append((sym, new_expr)) return sym else: return new_expr
def _number_theoretic_transform(seq, prime, inverse=False): """Utility function for the Number Theoretic Transform""" if not iterable(seq): raise TypeError("Expected a sequence of integer coefficients " "for Number Theoretic Transform") p = as_int(prime) if isprime(p) == False: raise ValueError("Expected prime modulus for " "Number Theoretic Transform") a = [as_int(x) % p for x in seq] n = len(a) if n < 1: return a b = n.bit_length() - 1 if n&(n - 1): b += 1 n = 2**b if (p - 1) % n: raise ValueError("Expected prime modulus of the form (m*2**k + 1)") a += [0]*(n - len(a)) for i in range(1, n): j = int(ibin(i, b, str=True)[::-1], 2) if i < j: a[i], a[j] = a[j], a[i] pr = primitive_root(p) rt = pow(pr, (p - 1) // n, p) if inverse: rt = pow(rt, p - 2, p) w = [1]*(n // 2) for i in range(1, n // 2): w[i] = w[i - 1]*rt % p h = 2 while h <= n: hf, ut = h // 2, n // h for i in range(0, n, h): for j in range(hf): u, v = a[i + j], a[i + j + hf]*w[ut * j] a[i + j], a[i + j + hf] = (u + v) % p, (u - v) % p h *= 2 if inverse: rv = pow(n, p - 2, p) a = [x*rv % p for x in a] return a
def posify(eq): """Return eq (with generic symbols made positive) and a dictionary containing the mapping between the old and new symbols. Any symbol that has positive=None will be replaced with a positive dummy symbol having the same name. This replacement will allow more symbolic processing of expressions, especially those involving powers and logarithms. A dictionary that can be sent to subs to restore eq to its original symbols is also returned. >>> from sympy import posify, Symbol, log, solve >>> from sympy.abc import x >>> posify(x + Symbol('p', positive=True) + Symbol('n', negative=True)) (_x + n + p, {_x: x}) >>> eq = 1/x >>> log(eq).expand() log(1/x) >>> log(posify(eq)[0]).expand() -log(_x) >>> p, rep = posify(eq) >>> log(p).expand().subs(rep) -log(x) It is possible to apply the same transformations to an iterable of expressions: >>> eq = x**2 - 4 >>> solve(eq, x) [-2, 2] >>> eq_x, reps = posify([eq, x]); eq_x [_x**2 - 4, _x] >>> solve(*eq_x) [2] """ eq = sympify(eq) if iterable(eq): f = type(eq) eq = list(eq) syms = set() for e in eq: syms = syms.union(e.atoms(Symbol)) reps = {} for s in syms: reps.update(dict((v, k) for k, v in posify(s)[1].items())) for i, e in enumerate(eq): eq[i] = e.subs(reps) return f(eq), dict([(r, s) for s, r in reps.items()]) reps = dict([(s, Dummy(s.name, positive=True)) for s in eq.free_symbols if s.is_positive is None]) eq = eq.subs(reps) return eq, dict([(r, s) for s, r in reps.items()])
def __init__(self, monom, gens=None): if not iterable(monom): rep, gens = dict_from_expr(sympify(monom), gens=gens) if len(rep) == 1 and list(rep.values())[0] == 1: monom = list(rep.keys())[0] else: raise ValueError("Expected a monomial got %s" % monom) self.exponents = tuple(map(int, monom)) self.gens = gens
def __new__(cls, *args, **kwargs): if iterable(args[0]): coords = tuple([sympify(x) for x in args[0]]) else: coords = tuple([sympify(x) for x in args]) if len(coords) != 2: raise NotImplementedError("Only two dimensional points currently supported") return GeometryEntity.__new__(cls, *coords)
def rewrite(self, *args, **hints): """ Rewrite functions in terms of other functions. Rewrites expression containing applications of functions of one kind in terms of functions of different kind. For example you can rewrite trigonometric functions as complex exponentials or combinatorial functions as gamma function. As a pattern this function accepts a list of functions to to rewrite (instances of DefinedFunction class). As rule you can use string or a destination function instance (in this case rewrite() will use the str() function). There is also possibility to pass hints on how to rewrite the given expressions. For now there is only one such hint defined called 'deep'. When 'deep' is set to False it will forbid functions to rewrite their contents. >>> from sympy import sin, exp, I >>> from sympy.abc import x, y Unspecified pattern: >>> sin(x).rewrite(exp) -I*(exp(I*x) - exp(-I*x))/2 Pattern as a single function: >>> sin(x).rewrite(sin, exp) -I*(exp(I*x) - exp(-I*x))/2 Pattern as a list of functions: >>> sin(x).rewrite([sin, ], exp) -I*(exp(I*x) - exp(-I*x))/2 """ if self.is_Atom or not args: return self else: pattern = args[:-1] if isinstance(args[-1], basestring): rule = '_eval_rewrite_as_' + args[-1] else: rule = '_eval_rewrite_as_' + args[-1].__name__ if not pattern: return self._eval_rewrite(None, rule, **hints) else: if iterable(pattern[0]): pattern = pattern[0] pattern = [ p.__class__ for p in pattern if self.has(p) ] if pattern: return self._eval_rewrite(tuple(pattern), rule, **hints) else: return self
def flat_indexes(elems): n = 0 for el in elems: if iterable(el): for ndeep in flat_indexes(el): yield (n,) + ndeep else: yield (n,) n += 1
def __new__(cls, *args, **kwargs): if iterable(args[0]): coords = Tuple(*args[0]) elif isinstance(args[0], Point): coords = args[0].args else: coords = Tuple(*args) if len(coords) != 2: raise NotImplementedError("Only two dimensional points currently supported") return GeometryEntity.__new__(cls, *coords)
def __new__(cls, *args, **kw_args): """The constructor for the Prufer object. Examples ======== >>> from sympy.combinatorics.prufer import Prufer A Prufer object can be constructed from a list of edges: >>> a = Prufer([[0, 1], [0, 2], [0, 3]]) >>> a.prufer_repr [0, 0] If the number of nodes is given, no checking of the nodes will be performed; it will be assumed that nodes 0 through n - 1 are present: >>> Prufer([[0, 1], [0, 2], [0, 3]], 4) Prufer([[0, 1], [0, 2], [0, 3]], 4) A Prufer object can be constructed from a Prufer sequence: >>> b = Prufer([1, 3]) >>> b.tree_repr [[0, 1], [1, 3], [2, 3]] """ ret_obj = Basic.__new__(cls, *args, **kw_args) args = [list(args[0])] if args[0] and iterable(args[0][0]): if not args[0][0]: raise ValueError( 'Prufer expects at least one edge in the tree.') if len(args) > 1: nnodes = args[1] else: nodes = set(flatten(args[0])) nnodes = max(nodes) + 1 if nnodes != len(nodes): missing = set(range(nnodes)) - nodes if len(missing) == 1: msg = 'Node %s is missing.' % missing.pop() else: msg = 'Nodes %s are missing.' % list(sorted(missing)) raise ValueError(msg) ret_obj._tree_repr = [list(i) for i in args[0]] ret_obj._nodes = nnodes else: ret_obj._prufer_repr = args[0] ret_obj._nodes = len(ret_obj._prufer_repr) + 2 return ret_obj
def __new__(cls, *args): if len(args) == 1 and iterable(args[0]): args = args[0] args = map(sympify, args) if len(args) == 0: return EmptySet() args = frozenset(args) # remove duplicates obj = Basic.__new__(cls, *args) obj._elements = args return obj
def __new__(cls, *args, **kwargs): eval = kwargs.get('evaluate', global_evaluate[0]) check = True if isinstance(args[0], Point2D): if not eval: return args[0] args = args[0].args check = False else: if iterable(args[0]): args = args[0] if len(args) != 2: raise ValueError( "Only two dimensional points currently supported") coords = Tuple(*args) if check: if any(a.is_number and im(a) for a in coords): raise ValueError('Imaginary args not permitted.') if eval: coords = coords.xreplace( dict([(f, simplify(nsimplify(f, rational=True))) for f in coords.atoms(Float)])) return GeometryEntity.__new__(cls, *coords)
def _find_repeated(expr): if not isinstance(expr, Basic): return if expr.is_Atom or expr.is_Order: return if iterable(expr): args = expr else: if expr in seen_subexp: to_eliminate.add(expr) return seen_subexp.add(expr) if expr in opt_subs: expr = opt_subs[expr] args = expr.args list(map(_find_repeated, args))
def postorder_traversal(node): """ Do a postorder traversal of a tree. This generator recursively yields nodes that it has visited in a postorder fashion. That is, it descends through the tree depth-first to yield all of a node's children's postorder traversal before yielding the node itself. Parameters ---------- node : sympy expression The expression to traverse. Yields ------ subtree : sympy expression All of the subtrees in the tree. Examples -------- >>> from sympy import symbols >>> from sympy.utilities.iterables import postorder_traversal >>> from sympy.abc import x, y, z >>> set(postorder_traversal((x+y)*z)) == set([z, y, x, x + y, z*(x + y)]) True """ if isinstance(node, Basic): for arg in node.args: for subtree in postorder_traversal(arg): yield subtree elif iterable(node): for item in node: for subtree in postorder_traversal(item): yield subtree yield node
def _mobius_transform(seq, sgn, subset): r"""Utility function for performing Möbius Transform using Yate's Dynamic Programming (DP)""" if not iterable(seq): raise TypeError("Expected a sequence of coefficients") a = [sympify(arg) for arg in seq] n = len(a) if n < 2: return a if n & (n - 1): n = 2**n.bit_length() a += [S.Zero] * (n - len(a)) if subset: i = 1 while i < n: for j in range(n): if j & i: a[j] += sgn * a[j ^ i] i *= 2 else: i = 1 while i < n: for j in range(n): if j & i: continue a[j] += sgn * a[j ^ i] i *= 2 return a
def is_sequence(i, include=None): """ Return a boolean indicating whether ``i`` is a sequence in the SymPy sense. If anything that fails the test below should be included as being a sequence for your application, set 'include' to that object's type; multiple types should be passed as a tuple of types. Note: although generators can generate a sequence, they often need special handling to make sure their elements are captured before the generator is exhausted, so these are not included by default in the definition of a sequence. See also: iterable Examples ======== >>> from sympy.utilities.iterables import is_sequence >>> from types import GeneratorType >>> is_sequence([]) True >>> is_sequence(set()) False >>> is_sequence('abc') False >>> is_sequence('abc', include=str) True >>> generator = (c for c in 'abc') >>> is_sequence(generator) False >>> is_sequence(generator, include=(str, GeneratorType)) True """ return (hasattr(i, '__getitem__') and iterable(i) or bool(include) and isinstance(i, include))
def PolynomialRing(dom, *gens, **opts): r""" Create a generalized multivariate polynomial ring. A generalized polynomial ring is defined by a ground field `K`, a set of generators (typically `x_1, \dots, x_n`) and a monomial order `<`. The monomial order can be global, local or mixed. In any case it induces a total ordering on the monomials, and there exists for every (non-zero) polynomial `f \in K[x_1, \dots, x_n]` a well-defined "leading monomial" `LM(f) = LM(f, >)`. One can then define a multiplicative subset `S = S_> = \{f \in K[x_1, \dots, x_n] | LM(f) = 1\}`. The generalized polynomial ring corresponding to the monomial order is `R = S^{-1}K[x_1, \dots, x_n]`. If `>` is a so-called global order, that is `1` is the smallest monomial, then we just have `S = K` and `R = K[x_1, \dots, x_n]`. Examples ======== A few examples may make this clearer. >>> from sympy.abc import x, y >>> from sympy import QQ Our first ring uses global lexicographic order. >>> R1 = QQ.old_poly_ring(x, y, order=(("lex", x, y),)) The second ring uses local lexicographic order. Note that when using a single (non-product) order, you can just specify the name and omit the variables: >>> R2 = QQ.old_poly_ring(x, y, order="ilex") The third and fourth rings use a mixed orders: >>> o1 = (("ilex", x), ("lex", y)) >>> o2 = (("lex", x), ("ilex", y)) >>> R3 = QQ.old_poly_ring(x, y, order=o1) >>> R4 = QQ.old_poly_ring(x, y, order=o2) We will investigate what elements of `K(x, y)` are contained in the various rings. >>> L = [x, 1/x, y/(1 + x), 1/(1 + y), 1/(1 + x*y)] >>> test = lambda R: [f in R for f in L] The first ring is just `K[x, y]`: >>> test(R1) [True, False, False, False, False] The second ring is R1 localised at the maximal ideal (x, y): >>> test(R2) [True, False, True, True, True] The third ring is R1 localised at the prime ideal (x): >>> test(R3) [True, False, True, False, True] Finally the fourth ring is R1 localised at `S = K[x, y] \setminus yK[y]`: >>> test(R4) [True, False, False, True, False] """ order = opts.get("order", GeneralizedPolynomialRing.default_order) if iterable(order): order = build_product_order(order, gens) order = monomial_key(order) opts['order'] = order if order.is_global: return GlobalPolynomialRing(dom, *gens, **opts) else: return GeneralizedPolynomialRing(dom, *gens, **opts)
def isiter(l): return iterable(l, exclude=(str, DeferredVector))
def unpack_lhs(lvalues): return '[{}]'.format(', '.join( unpack_lhs(val) if iterable(val) else val for val in lvalues))
def __new__(cls, *args): """ Construct a new instance of Diagram. If no arguments are supplied, an empty diagram is created. If at least an argument is supplied, ``args[0]`` is interpreted as the premises of the diagram. If ``args[0]`` is a list, it is interpreted as a list of :class:`Morphism`'s, in which each :class:`Morphism` has an empty set of properties. If ``args[0]`` is a Python dictionary or a :class:`Dict`, it is interpreted as a dictionary associating to some :class:`Morphism`'s some properties. If at least two arguments are supplied ``args[1]`` is interpreted as the conclusions of the diagram. The type of ``args[1]`` is interpreted in exactly the same way as the type of ``args[0]``. If only one argument is supplied, the diagram has no conclusions. Examples ======== >>> from sympy.categories import Object, NamedMorphism >>> from sympy.categories import IdentityMorphism, Diagram >>> A = Object("A") >>> B = Object("B") >>> C = Object("C") >>> f = NamedMorphism(A, B, "f") >>> g = NamedMorphism(B, C, "g") >>> d = Diagram([f, g]) >>> IdentityMorphism(A) in d.premises.keys() True >>> g * f in d.premises.keys() True >>> d = Diagram([f, g], {g * f: "unique"}) >>> d.conclusions[g * f] {unique} """ premises = {} conclusions = {} # Here we will keep track of the objects which appear in the # premises. objects = EmptySet() if len(args) >= 1: # We've got some premises in the arguments. premises_arg = args[0] if isinstance(premises_arg, list): # The user has supplied a list of morphisms, none of # which have any attributes. empty = EmptySet() for morphism in premises_arg: objects |= FiniteSet(morphism.domain, morphism.codomain) Diagram._add_morphism_closure(premises, morphism, empty) elif isinstance(premises_arg, dict) or isinstance( premises_arg, Dict): # The user has supplied a dictionary of morphisms and # their properties. for morphism, props in premises_arg.items(): objects |= FiniteSet(morphism.domain, morphism.codomain) Diagram._add_morphism_closure( premises, morphism, FiniteSet( *props) if iterable(props) else FiniteSet(props)) if len(args) >= 2: # We also have some conclusions. conclusions_arg = args[1] if isinstance(conclusions_arg, list): # The user has supplied a list of morphisms, none of # which have any attributes. empty = EmptySet() for morphism in conclusions_arg: # Check that no new objects appear in conclusions. if (morphism.domain in objects) and \ (morphism.codomain in objects): # No need to add identities and recurse # composites this time. Diagram._add_morphism_closure(conclusions, morphism, empty, add_identities=False, recurse_composites=False) elif isinstance(conclusions_arg, dict) or \ isinstance(conclusions_arg, Dict): # The user has supplied a dictionary of morphisms and # their properties. for morphism, props in conclusions_arg.items(): # Check that no new objects appear in conclusions. if (morphism.domain in objects) and \ (morphism.codomain in objects): # No need to add identities and recurse # composites this time. Diagram._add_morphism_closure( conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props), add_identities=False, recurse_composites=False) return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects)
def lambdastr(args, expr, printer=None, dummify=False): """ Returns a string that can be evaluated to a lambda function. Examples ======== >>> from sympy.abc import x, y, z >>> from sympy.utilities.lambdify import lambdastr >>> lambdastr(x, x**2) 'lambda x: (x**2)' >>> lambdastr((x,y,z), [z,y,x]) 'lambda x,y,z: ([z, y, x])' Although tuples may not appear as arguments to lambda in Python 3, lambdastr will create a lambda function that will unpack the original arguments so that nested arguments can be handled: >>> lambdastr((x, (y, z)), x + y) 'lambda _0,_1: (lambda x,y,z: (x + y))(*list(__flatten_args__([_0,_1])))' """ # Transforming everything to strings. from sympy.matrices import DeferredVector from sympy import Dummy, sympify, Symbol, Function, flatten if printer is not None: if inspect.isfunction(printer): lambdarepr = printer else: if inspect.isclass(printer): lambdarepr = lambda expr: printer().doprint(expr) else: lambdarepr = lambda expr: printer.doprint(expr) else: #XXX: This has to be done here because of circular imports from sympy.printing.lambdarepr import lambdarepr def sub_args(args, dummies_dict): if isinstance(args, str): return args elif isinstance(args, DeferredVector): return str(args) elif iterable(args): dummies = flatten([sub_args(a, dummies_dict) for a in args]) return ",".join(str(a) for a in dummies) else: #Sub in dummy variables for functions or symbols if isinstance(args, (Function, Symbol)): dummies = Dummy() dummies_dict.update({args: dummies}) return str(dummies) else: return str(args) def sub_expr(expr, dummies_dict): try: expr = sympify(expr).xreplace(dummies_dict) except Exception: if isinstance(expr, DeferredVector): pass elif isinstance(expr, dict): k = [sub_expr(sympify(a), dummies_dict) for a in expr.keys()] v = [sub_expr(sympify(a), dummies_dict) for a in expr.values()] expr = dict(zip(k, v)) elif isinstance(expr, tuple): expr = tuple(sub_expr(sympify(a), dummies_dict) for a in expr) elif isinstance(expr, list): expr = [sub_expr(sympify(a), dummies_dict) for a in expr] return expr # Transform args def isiter(l): return iterable(l, exclude=(str, DeferredVector)) if isiter(args) and any(isiter(i) for i in args): from sympy.utilities.iterables import flatten import re dum_args = [str(Dummy(str(i))) for i in range(len(args))] iter_args = ','.join( [i if isiter(a) else i for i, a in zip(dum_args, args)]) lstr = lambdastr(flatten(args), expr, printer=printer, dummify=dummify) flat = '__flatten_args__' rv = 'lambda %s: (%s)(*list(%s([%s])))' % (','.join(dum_args), lstr, flat, iter_args) if len(re.findall(r'\b%s\b' % flat, rv)) > 1: raise ValueError('the name %s is reserved by lambdastr' % flat) return rv dummies_dict = {} if dummify: args = sub_args(args, dummies_dict) else: if isinstance(args, str): pass elif iterable(args, exclude=DeferredVector): args = ",".join(str(a) for a in args) # Transform expr if dummify: if isinstance(expr, str): pass else: expr = sub_expr(expr, dummies_dict) expr = lambdarepr(expr) return "lambda %s: (%s)" % (args, expr)
def inv(l): if iterable(l): return tuple(inv(x) for x in l) return -l
def autowrap(expr, language=None, backend='Cython', tempdir=None, args=None, flags=None, verbose=False, helpers=None): """Generates python callable binaries based on the math expression. Parameters ---------- expr The SymPy expression that should be wrapped as a binary routine. language : string, optional If supplied, (options: 'C' or 'F95'), specifies the language of the generated code. If ``None`` [default], the language is inferred based upon the specified backend. backend : string, optional Backend used to wrap the generated code. Either 'f2py', or 'cython' [default]. tempdir : string, optional Path to directory for temporary files. If this argument is supplied, the generated code and the wrapper input files are left intact in the specified path. args : iterable, optional An ordered iterable of symbols. Specifies the argument sequence for the function. flags : iterable, optional Additional option flags that will be passed to the backend. verbose : bool, optional If True, autowrap will not mute the command line backends. This can be helpful for debugging. helpers : iterable, optional Used to define auxillary expressions needed for the main expr. If the main expression needs to call a specialized function it should be put in the ``helpers`` iterable. Autowrap will then make sure that the compiled main expression can link to the helper routine. Items should be tuples with (<funtion_name>, <sympy_expression>, <arguments>). It is mandatory to supply an argument sequence to helper routines. >>> from sympy.abc import x, y, z >>> from sympy.utilities.autowrap import autowrap >>> expr = ((x - y + z)**(13)).expand() >>> binary_func = autowrap(expr) >>> binary_func(1, 4, 2) -1.0 """ if language: _validate_backend_language(backend, language) else: language = _infer_language(backend) helpers = [helpers] if helpers else () flags = flags if flags else () args = list(args) if iterable(args, exclude=set) else args code_generator = get_code_generator(language, "autowrap") CodeWrapperClass = _get_code_wrapper_class(backend) code_wrapper = CodeWrapperClass(code_generator, tempdir, flags, verbose) helps = [] for name_h, expr_h, args_h in helpers: helps.append(make_routine(name_h, expr_h, args_h)) for name_h, expr_h, args_h in helpers: if expr.has(expr_h): name_h = binary_function(name_h, expr_h, backend='dummy') expr = expr.subs(expr_h, name_h(*args_h)) try: routine = make_routine('autofunc', expr, args) except CodeGenArgumentListError as e: # if all missing arguments are for pure output, we simply attach them # at the end and try again, because the wrappers will silently convert # them to return values anyway. new_args = [] for missing in e.missing_args: if not isinstance(missing, OutputArgument): raise new_args.append(missing.name) routine = make_routine('autofunc', expr, args + new_args) return code_wrapper.wrap_code(routine, helpers=helps)
def convolution_subset(a, b): """ Performs Subset Convolution of given sequences. The indices of each argument, considered as bit strings, correspond to subsets of a finite set. The sequence is automatically padded to the right with zeros, as the definition of subset based on bitmasks (indices) requires the size of sequence to be a power of 2. Parameters ========== a, b : iterables The sequences for which convolution is performed. Examples ======== >>> from sympy import symbols, S, I >>> from sympy.discrete.convolutions import convolution_subset >>> u, v, x, y, z = symbols('u v x y z') >>> convolution_subset([u, v], [x, y]) [u*x, u*y + v*x] >>> convolution_subset([u, v, x], [y, z]) [u*y, u*z + v*y, x*y, x*z] >>> convolution_subset([1, S(2)/3], [3, 4]) [3, 6] >>> convolution_subset([1, 3, S(5)/7], [7]) [7, 21, 5, 0] References ========== .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf """ if not a or not b: return [] if not iterable(a) or not iterable(b): raise TypeError("Expected a sequence of coefficients for convolution") a = [sympify(arg) for arg in a] b = [sympify(arg) for arg in b] n = max(len(a), len(b)) if n & (n - 1): # not a power of 2 n = 2**n.bit_length() # padding with zeros a += [S.Zero] * (n - len(a)) b += [S.Zero] * (n - len(b)) c = [S.Zero] * n for mask in range(n): smask = mask while smask > 0: c[mask] += expand_mul(a[smask] * b[mask ^ smask]) smask = (smask - 1) & mask c[mask] += expand_mul(a[smask] * b[mask ^ smask]) return c
def default_sort_key(item, order=None): """Return a key that can be used for sorting. The key has the structure: (class_key, (len(args), args), exponent.sort_key(), coefficient) This key is supplied by the sort_key routine of Basic objects when ``item`` is a Basic object or an object (other than a string) that sympifies to a Basic object. Otherwise, this function produces the key. The ``order`` argument is passed along to the sort_key routine and is used to determine how the terms *within* an expression are ordered. (See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex', and reversed values of the same (e.g. 'rev-lex'). The default order value is None (which translates to 'lex'). Examples ======== >>> from sympy import S, I, default_sort_key, sin, cos, sqrt >>> from sympy.core.function import UndefinedFunction >>> from sympy.abc import x The following are equivalent ways of getting the key for an object: >>> x.sort_key() == default_sort_key(x) True Here are some examples of the key that is produced: >>> default_sort_key(UndefinedFunction('f')) ((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'), (0, ()), (), 1), 1) >>> default_sort_key('1') ((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1) >>> default_sort_key(S.One) ((1, 0, 'Number'), (0, ()), (), 1) >>> default_sort_key(2) ((1, 0, 'Number'), (0, ()), (), 2) While sort_key is a method only defined for SymPy objects, default_sort_key will accept anything as an argument so it is more robust as a sorting key. For the following, using key= lambda i: i.sort_key() would fail because 2 doesn't have a sort_key method; that's why default_sort_key is used. Note, that it also handles sympification of non-string items likes ints: >>> a = [2, I, -I] >>> sorted(a, key=default_sort_key) [2, -I, I] The returned key can be used anywhere that a key can be specified for a function, e.g. sort, min, max, etc...: >>> a.sort(key=default_sort_key); a[0] 2 >>> min(a, key=default_sort_key) 2 Note ---- The key returned is useful for getting items into a canonical order that will be the same across platforms. It is not directly useful for sorting lists of expressions: >>> a, b = x, 1/x Since ``a`` has only 1 term, its value of sort_key is unaffected by ``order``: >>> a.sort_key() == a.sort_key('rev-lex') True If ``a`` and ``b`` are combined then the key will differ because there are terms that can be ordered: >>> eq = a + b >>> eq.sort_key() == eq.sort_key('rev-lex') False >>> eq.as_ordered_terms() [x, 1/x] >>> eq.as_ordered_terms('rev-lex') [1/x, x] But since the keys for each of these terms are independent of ``order``'s value, they don't sort differently when they appear separately in a list: >>> sorted(eq.args, key=default_sort_key) [1/x, x] >>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex')) [1/x, x] The order of terms obtained when using these keys is the order that would be obtained if those terms were *factors* in a product. Although it is useful for quickly putting expressions in canonical order, it does not sort expressions based on their complexity defined by the number of operations, power of variables and others: >>> sorted([sin(x)*cos(x), sin(x)], key=default_sort_key) [sin(x)*cos(x), sin(x)] >>> sorted([x, x**2, sqrt(x), x**3], key=default_sort_key) [sqrt(x), x, x**2, x**3] See Also ======== ordered, sympy.core.expr.as_ordered_factors, sympy.core.expr.as_ordered_terms """ from sympy.core import S, Basic from sympy.core.sympify import sympify, SympifyError from sympy.core.compatibility import iterable if isinstance(item, Basic): return item.sort_key(order=order) if iterable(item, exclude=string_types): if isinstance(item, dict): args = item.items() unordered = True elif isinstance(item, set): args = item unordered = True else: # e.g. tuple, list args = list(item) unordered = False args = [default_sort_key(arg, order=order) for arg in args] if unordered: # e.g. dict, set args = sorted(args) cls_index, args = 10, (len(args), tuple(args)) else: if not isinstance(item, string_types): try: item = sympify(item) except SympifyError: # e.g. lambda x: x pass else: if isinstance(item, Basic): # e.g int -> Integer return default_sort_key(item) # e.g. UndefinedFunction # e.g. str cls_index, args = 0, (1, (str(item), )) return (cls_index, 0, item.__class__.__name__), args, S.One.sort_key(), S.One
def lambdify(args, expr, modules=None, printer=None, use_imps=True, dummify=False): """ Translates a SymPy expression into an equivalent numeric function For example, to convert the SymPy expression ``sin(x) + cos(x)`` to an equivalent NumPy function that numerically evaluates it: >>> from sympy import sin, cos, symbols, lambdify >>> import numpy as np >>> x = symbols('x') >>> expr = sin(x) + cos(x) >>> expr sin(x) + cos(x) >>> f = lambdify(x, expr, 'numpy') >>> a = np.array([1, 2]) >>> f(a) [1.38177329 0.49315059] The primary purpose of this function is to provide a bridge from SymPy expressions to numerical libraries such as NumPy, SciPy, NumExpr, mpmath, and tensorflow. In general, SymPy functions do not work with objects from other libraries, such as NumPy arrays, and functions from numeric libraries like NumPy or mpmath do not work on SymPy expressions. ``lambdify`` bridges the two by converting a SymPy expression to an equivalent numeric function. The basic workflow with ``lambdify`` is to first create a SymPy expression representing whatever mathematical function you wish to evaluate. This should be done using only SymPy functions and expressions. Then, use ``lambdify`` to convert this to an equivalent function for numerical evaluation. For instance, above we created ``expr`` using the SymPy symbol ``x`` and SymPy functions ``sin`` and ``cos``, then converted it to an equivalent NumPy function ``f``, and called it on a NumPy array ``a``. .. warning:: This function uses ``exec``, and thus shouldn't be used on unsanitized input. Arguments ========= The first argument of ``lambdify`` is a variable or list of variables in the expression. Variable lists may be nested. Variables can be Symbols, undefined functions, or matrix symbols. The order and nesting of the variables corresponds to the order and nesting of the parameters passed to the lambdified function. For instance, >>> from sympy.abc import x, y, z >>> f = lambdify([x, (y, z)], x + y + z) >>> f(1, (2, 3)) 6 The second argument of ``lambdify`` is the expression, list of expressions, or matrix to be evaluated. Lists may be nested. If the expression is a list, the output will also be a list. >>> f = lambdify(x, [x, [x + 1, x + 2]]) >>> f(1) [1, [2, 3]] If it is a matrix, an array will be returned (for the NumPy module). >>> from sympy import Matrix >>> f = lambdify(x, Matrix([x, x + 1])) >>> f(1) [[1] [2]] Note that the argument order here, variables then expression, is used to emulate the Python ``lambda`` keyword. ``lambdify(x, expr)`` works (roughly) like ``lambda x: expr`` (see :ref:`lambdify-how-it-works` below). The third argument, ``modules`` is optional. If not specified, ``modules`` defaults to ``["scipy", "numpy"]`` if SciPy is installed, ``["numpy"]`` if only NumPy is installed, and ``["math", "mpmath", "sympy"]`` if neither is installed. That is, SymPy functions are replaced as far as possible by either ``scipy`` or ``numpy`` functions if available, and Python's standard library ``math``, or ``mpmath`` functions otherwise. ``modules`` can be one of the following types - the strings ``"math"``, ``"mpmath"``, ``"numpy"``, ``"numexpr"``, ``"scipy"``, ``"sympy"``, or ``"tensorflow"``. This uses the corresponding printer and namespace mapping for that module. - a module (e.g., ``math``). This uses the global namespace of the module. If the module is one of the above known modules, it will also use the corresponding printer and namespace mapping (i.e., ``modules=numpy`` is equivalent to ``modules="numpy"``). - a dictionary that maps names of SymPy functions to arbitrary functions (e.g., ``{'sin': custom_sin}``). - a list that contains a mix of the arguments above, with higher priority given to entries appearing first (e.g., to use the NumPy module but override the ``sin`` function with a custom version, you can use ``[{'sin': custom_sin}, 'numpy']``). The ``dummify`` keyword argument controls whether or not the variables in the provided expression that are not valid Python identifiers are substituted with dummy symbols. This allows for undefined functions like ``Function('f')(t)`` to be supplied as arguments. By default, the variables are only dummified if they are not valid Python identifiers. Set ``dummify=True`` to replace all arguments with dummy symbols (if ``args`` is not a string) - for example, to ensure that the arguments do not redefine any built-in names. .. _lambdify-how-it-works: How it works ============ When using this function, it helps a great deal to have an idea of what it is doing. At its core, lambdify is nothing more than a namespace translation, on top of a special printer that makes some corner cases work properly. To understand lambdify, first we must properly understand how Python namespaces work. Say we had two files. One called ``sin_cos_sympy.py``, with .. code:: python # sin_cos_sympy.py from sympy import sin, cos def sin_cos(x): return sin(x) + cos(x) and one called ``sin_cos_numpy.py`` with .. code:: python # sin_cos_numpy.py from numpy import sin, cos def sin_cos(x): return sin(x) + cos(x) The two files define an identical function ``sin_cos``. However, in the first file, ``sin`` and ``cos`` are defined as the SymPy ``sin`` and ``cos``. In the second, they are defined as the NumPy versions. If we were to import the first file and use the ``sin_cos`` function, we would get something like >>> from sin_cos_sympy import sin_cos # doctest: +SKIP >>> sin_cos(1) # doctest: +SKIP cos(1) + sin(1) On the other hand, if we imported ``sin_cos`` from the second file, we would get >>> from sin_cos_numpy import sin_cos # doctest: +SKIP >>> sin_cos(1) # doctest: +SKIP 1.38177329068 In the first case we got a symbolic output, because it used the symbolic ``sin`` and ``cos`` functions from SymPy. In the second, we got a numeric result, because ``sin_cos`` used the numeric ``sin`` and ``cos`` functions from NumPy. But notice that the versions of ``sin`` and ``cos`` that were used was not inherent to the ``sin_cos`` function definition. Both ``sin_cos`` definitions are exactly the same. Rather, it was based on the names defined at the module where the ``sin_cos`` function was defined. The key point here is that when function in Python references a name that is not defined in the function, that name is looked up in the "global" namespace of the module where that function is defined. Now, in Python, we can emulate this behavior without actually writing a file to disk using the ``exec`` function. ``exec`` takes a string containing a block of Python code, and a dictionary that should contain the global variables of the module. It then executes the code "in" that dictionary, as if it were the module globals. The following is equivalent to the ``sin_cos`` defined in ``sin_cos_sympy.py``: >>> import sympy >>> module_dictionary = {'sin': sympy.sin, 'cos': sympy.cos} >>> exec(''' ... def sin_cos(x): ... return sin(x) + cos(x) ... ''', module_dictionary) >>> sin_cos = module_dictionary['sin_cos'] >>> sin_cos(1) cos(1) + sin(1) and similarly with ``sin_cos_numpy``: >>> import numpy >>> module_dictionary = {'sin': numpy.sin, 'cos': numpy.cos} >>> exec(''' ... def sin_cos(x): ... return sin(x) + cos(x) ... ''', module_dictionary) >>> sin_cos = module_dictionary['sin_cos'] >>> sin_cos(1) 1.38177329068 So now we can get an idea of how ``lambdify`` works. The name "lambdify" comes from the fact that we can think of something like ``lambdify(x, sin(x) + cos(x), 'numpy')`` as ``lambda x: sin(x) + cos(x)``, where ``sin`` and ``cos`` come from the ``numpy`` namespace. This is also why the symbols argument is first in ``lambdify``, as opposed to most SymPy functions where it comes after the expression: to better mimic the ``lambda`` keyword. ``lambdify`` takes the input expression (like ``sin(x) + cos(x)``) and 1. Converts it to a string 2. Creates a module globals dictionary based on the modules that are passed in (by default, it uses the NumPy module) 3. Creates the string ``"def func({vars}): return {expr}"``, where ``{vars}`` is the list of variables separated by commas, and ``{expr}`` is the string created in step 1., then ``exec``s that string with the module globals namespace and returns ``func``. In fact, functions returned by ``lambdify`` support inspection. So you can see exactly how they are defined by using ``inspect.getsource``, or ``??`` if you are using IPython or the Jupyter notebook. >>> f = lambdify(x, sin(x) + cos(x)) >>> import inspect >>> print(inspect.getsource(f)) def _lambdifygenerated(x): return (sin(x) + cos(x)) This shows us the source code of the function, but not the namespace it was defined in. We can inspect that by looking at the ``__globals__`` attribute of ``f``: >>> f.__globals__['sin'] <ufunc 'sin'> >>> f.__globals__['cos'] <ufunc 'cos'> >>> f.__globals__['sin'] is numpy.sin True This shows us that ``sin`` and ``cos`` in the namespace of ``f`` will be ``numpy.sin`` and ``numpy.cos``. Note that there are some convenience layers in each of these steps, but at the core, this is how ``lambdify`` works. Step 1 is done using the ``LambdaPrinter`` printers defined in the printing module (see :mod:`sympy.printing.lambdarepr`). This allows different SymPy expressions to define how they should be converted to a string for different modules. You can change which printer ``lambdify`` uses by passing a custom printer in to the ``printer`` argument. Step 2 is augmented by certain translations. There are default translations for each module, but you can provide your own by passing a list to the ``modules`` argument. For instance, >>> def mysin(x): ... print('taking the sin of', x) ... return numpy.sin(x) ... >>> f = lambdify(x, sin(x), [{'sin': mysin}, 'numpy']) >>> f(1) taking the sin of 1 0.8414709848078965 The globals dictionary is generated from the list by merging the dictionary ``{'sin': mysin}`` and the module dictionary for NumPy. The merging is done so that earlier items take precedence, which is why ``mysin`` is used above instead of ``numpy.sin``. If you want to modify the way ``lambdify`` works for a given function, it is usually easiest to do so by modifying the globals dictionary as such. In more complicated cases, it may be necessary to create and pass in a custom printer. Finally, step 3 is augmented with certain convenience operations, such as the addition of a docstring. Understanding how ``lambdify`` works can make it easier to avoid certain gotchas when using it. For instance, a common mistake is to create a lambdified function for one module (say, NumPy), and pass it objects from another (say, a SymPy expression). For instance, say we create >>> from sympy.abc import x >>> f = lambdify(x, x + 1, 'numpy') Now if we pass in a NumPy array, we get that array plus 1 >>> import numpy >>> a = numpy.array([1, 2]) >>> f(a) [2 3] But what happens if you make the mistake of passing in a SymPy expression instead of a NumPy array: >>> f(x + 1) x + 2 This worked, but it was only by accident. Now take a different lambdified function: >>> from sympy import sin >>> g = lambdify(x, x + sin(x), 'numpy') This works as expected on NumPy arrays: >>> g(a) [1.84147098 2.90929743] But if we try to pass in a SymPy expression, it fails >>> try: ... g(x + 1) ... # NumPy release after 1.17 raises TypeError instead of ... # AttributeError ... except (AttributeError, TypeError): ... raise AttributeError() # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... AttributeError: Now, let's look at what happened. The reason this fails is that ``g`` calls ``numpy.sin`` on the input expression, and ``numpy.sin`` does not know how to operate on a SymPy object. **As a general rule, NumPy functions do not know how to operate on SymPy expressions, and SymPy functions do not know how to operate on NumPy arrays. This is why lambdify exists: to provide a bridge between SymPy and NumPy.** However, why is it that ``f`` did work? That's because ``f`` doesn't call any functions, it only adds 1. So the resulting function that is created, ``def _lambdifygenerated(x): return x + 1`` does not depend on the globals namespace it is defined in. Thus it works, but only by accident. A future version of ``lambdify`` may remove this behavior. Be aware that certain implementation details described here may change in future versions of SymPy. The API of passing in custom modules and printers will not change, but the details of how a lambda function is created may change. However, the basic idea will remain the same, and understanding it will be helpful to understanding the behavior of lambdify. **In general: you should create lambdified functions for one module (say, NumPy), and only pass it input types that are compatible with that module (say, NumPy arrays).** Remember that by default, if the ``module`` argument is not provided, ``lambdify`` creates functions using the NumPy and SciPy namespaces. Examples ======== >>> from sympy.utilities.lambdify import implemented_function >>> from sympy import sqrt, sin, Matrix >>> from sympy import Function >>> from sympy.abc import w, x, y, z >>> f = lambdify(x, x**2) >>> f(2) 4 >>> f = lambdify((x, y, z), [z, y, x]) >>> f(1,2,3) [3, 2, 1] >>> f = lambdify(x, sqrt(x)) >>> f(4) 2.0 >>> f = lambdify((x, y), sin(x*y)**2) >>> f(0, 5) 0.0 >>> row = lambdify((x, y), Matrix((x, x + y)).T, modules='sympy') >>> row(1, 2) Matrix([[1, 3]]) ``lambdify`` can be used to translate SymPy expressions into mpmath functions. This may be preferable to using ``evalf`` (which uses mpmath on the backend) in some cases. >>> import mpmath >>> f = lambdify(x, sin(x), 'mpmath') >>> f(1) 0.8414709848078965 Tuple arguments are handled and the lambdified function should be called with the same type of arguments as were used to create the function: >>> f = lambdify((x, (y, z)), x + y) >>> f(1, (2, 4)) 3 The ``flatten`` function can be used to always work with flattened arguments: >>> from sympy.utilities.iterables import flatten >>> args = w, (x, (y, z)) >>> vals = 1, (2, (3, 4)) >>> f = lambdify(flatten(args), w + x + y + z) >>> f(*flatten(vals)) 10 Functions present in ``expr`` can also carry their own numerical implementations, in a callable attached to the ``_imp_`` attribute. This can be used with undefined functions using the ``implemented_function`` factory: >>> f = implemented_function(Function('f'), lambda x: x+1) >>> func = lambdify(x, f(x)) >>> func(4) 5 ``lambdify`` always prefers ``_imp_`` implementations to implementations in other namespaces, unless the ``use_imps`` input parameter is False. Usage with Tensorflow: >>> import tensorflow as tf >>> from sympy import Max, sin, lambdify >>> from sympy.abc import x >>> f = Max(x, sin(x)) >>> func = lambdify(x, f, 'tensorflow') After tensorflow v2, eager execution is enabled by default. If you want to get the compatible result across tensorflow v1 and v2 as same as this tutorial, run this line. >>> tf.compat.v1.enable_eager_execution() If you have eager execution enabled, you can get the result out immediately as you can use numpy. If you pass tensorflow objects, you may get an ``EagerTensor`` object instead of value. >>> result = func(tf.constant(1.0)) >>> print(result) tf.Tensor(1.0, shape=(), dtype=float32) >>> print(result.__class__) <class 'tensorflow.python.framework.ops.EagerTensor'> You can use ``.numpy()`` to get the numpy value of the tensor. >>> result.numpy() 1.0 >>> var = tf.Variable(2.0) >>> result = func(var) # also works for tf.Variable and tf.Placeholder >>> result.numpy() 2.0 And it works with any shape array. >>> tensor = tf.constant([[1.0, 2.0], [3.0, 4.0]]) >>> result = func(tensor) >>> result.numpy() [[1. 2.] [3. 4.]] Notes ===== - For functions involving large array calculations, numexpr can provide a significant speedup over numpy. Please note that the available functions for numexpr are more limited than numpy but can be expanded with ``implemented_function`` and user defined subclasses of Function. If specified, numexpr may be the only option in modules. The official list of numexpr functions can be found at: https://numexpr.readthedocs.io/en/latest/user_guide.html#supported-functions - In previous versions of SymPy, ``lambdify`` replaced ``Matrix`` with ``numpy.matrix`` by default. As of SymPy 1.0 ``numpy.array`` is the default. To get the old default behavior you must pass in ``[{'ImmutableDenseMatrix': numpy.matrix}, 'numpy']`` to the ``modules`` kwarg. >>> from sympy import lambdify, Matrix >>> from sympy.abc import x, y >>> import numpy >>> array2mat = [{'ImmutableDenseMatrix': numpy.matrix}, 'numpy'] >>> f = lambdify((x, y), Matrix([x, y]), modules=array2mat) >>> f(1, 2) [[1] [2]] - In the above examples, the generated functions can accept scalar values or numpy arrays as arguments. However, in some cases the generated function relies on the input being a numpy array: >>> from sympy import Piecewise >>> from sympy.utilities.pytest import ignore_warnings >>> f = lambdify(x, Piecewise((x, x <= 1), (1/x, x > 1)), "numpy") >>> with ignore_warnings(RuntimeWarning): ... f(numpy.array([-1, 0, 1, 2])) [-1. 0. 1. 0.5] >>> f(0) Traceback (most recent call last): ... ZeroDivisionError: division by zero In such cases, the input should be wrapped in a numpy array: >>> with ignore_warnings(RuntimeWarning): ... float(f(numpy.array([0]))) 0.0 Or if numpy functionality is not required another module can be used: >>> f = lambdify(x, Piecewise((x, x <= 1), (1/x, x > 1)), "math") >>> f(0) 0 """ from sympy.core.symbol import Symbol # If the user hasn't specified any modules, use what is available. if modules is None: try: _import("scipy") except ImportError: try: _import("numpy") except ImportError: # Use either numpy (if available) or python.math where possible. # XXX: This leads to different behaviour on different systems and # might be the reason for irreproducible errors. modules = ["math", "mpmath", "sympy"] else: modules = ["numpy"] else: modules = ["numpy", "scipy"] # Get the needed namespaces. namespaces = [] # First find any function implementations if use_imps: namespaces.append(_imp_namespace(expr)) # Check for dict before iterating if isinstance(modules, (dict, string_types)) or not hasattr(modules, '__iter__'): namespaces.append(modules) else: # consistency check if _module_present('numexpr', modules) and len(modules) > 1: raise TypeError("numexpr must be the only item in 'modules'") namespaces += list(modules) # fill namespace with first having highest priority namespace = {} for m in namespaces[::-1]: buf = _get_namespace(m) namespace.update(buf) if hasattr(expr, "atoms"): #Try if you can extract symbols from the expression. #Move on if expr.atoms in not implemented. syms = expr.atoms(Symbol) for term in syms: namespace.update({str(term): term}) if printer is None: if _module_present('mpmath', namespaces): from sympy.printing.pycode import MpmathPrinter as Printer elif _module_present('scipy', namespaces): from sympy.printing.pycode import SciPyPrinter as Printer elif _module_present('numpy', namespaces): from sympy.printing.pycode import NumPyPrinter as Printer elif _module_present('numexpr', namespaces): from sympy.printing.lambdarepr import NumExprPrinter as Printer elif _module_present('tensorflow', namespaces): from sympy.printing.tensorflow import TensorflowPrinter as Printer elif _module_present('sympy', namespaces): from sympy.printing.pycode import SymPyPrinter as Printer else: from sympy.printing.pycode import PythonCodePrinter as Printer user_functions = {} for m in namespaces[::-1]: if isinstance(m, dict): for k in m: user_functions[k] = k printer = Printer({ 'fully_qualified_modules': False, 'inline': True, 'allow_unknown_functions': True, 'user_functions': user_functions }) # Get the names of the args, for creating a docstring if not iterable(args): args = (args, ) names = [] # Grab the callers frame, for getting the names by inspection (if needed) callers_local_vars = inspect.currentframe().f_back.f_locals.items() for n, var in enumerate(args): if hasattr(var, 'name'): names.append(var.name) else: # It's an iterable. Try to get name by inspection of calling frame. name_list = [ var_name for var_name, var_val in callers_local_vars if var_val is var ] if len(name_list) == 1: names.append(name_list[0]) else: # Cannot infer name with certainty. arg_# will have to do. names.append('arg_' + str(n)) # Create the function definition code and execute it funcname = '_lambdifygenerated' if _module_present('tensorflow', namespaces): funcprinter = _TensorflowEvaluatorPrinter(printer, dummify) else: funcprinter = _EvaluatorPrinter(printer, dummify) funcstr = funcprinter.doprint(funcname, args, expr) # Collect the module imports from the code printers. imp_mod_lines = [] for mod, keys in (getattr(printer, 'module_imports', None) or {}).items(): for k in keys: if k not in namespace: ln = "from %s import %s" % (mod, k) try: exec_(ln, {}, namespace) except ImportError: # Tensorflow 2.0 has issues with importing a specific # function from its submodule. # https://github.com/tensorflow/tensorflow/issues/33022 ln = "%s = %s.%s" % (k, mod, k) exec_(ln, {}, namespace) imp_mod_lines.append(ln) # Provide lambda expression with builtins, and compatible implementation of range namespace.update({'builtins': builtins, 'range': range}) funclocals = {} global _lambdify_generated_counter filename = '<lambdifygenerated-%s>' % _lambdify_generated_counter _lambdify_generated_counter += 1 c = compile(funcstr, filename, 'exec') exec_(c, namespace, funclocals) # mtime has to be None or else linecache.checkcache will remove it linecache.cache[filename] = (len(funcstr), None, funcstr.splitlines(True), filename) func = funclocals[funcname] # Apply the docstring sig = "func({0})".format(", ".join(str(i) for i in names)) sig = textwrap.fill(sig, subsequent_indent=' ' * 8) expr_str = str(expr) if len(expr_str) > 78: expr_str = textwrap.wrap(expr_str, 75)[0] + '...' func.__doc__ = ("Created with lambdify. Signature:\n\n" "{sig}\n\n" "Expression:\n\n" "{expr}\n\n" "Source code:\n\n" "{src}\n\n" "Imported modules:\n\n" "{imp_mods}").format(sig=sig, expr=expr_str, src=funcstr, imp_mods='\n'.join(imp_mod_lines)) return func
def factor_terms(expr, radical=False, clear=False, fraction=False): """Remove common factors from terms in all arguments without changing the underlying structure of the expr. No expansion or simplification (and no processing of non-commutatives) is performed. If radical=True then a radical common to all terms will be factored out of any Add sub-expressions of the expr. If clear=False (default) then coefficients will not be separated from a single Add if they can be distributed to leave one or more terms with integer coefficients. If fraction=True (default is False) then a common denominator will be constructed for the expression. Examples ======== >>> from sympy import factor_terms, Symbol, Mul, primitive >>> from sympy.abc import x, y >>> factor_terms(x + x*(2 + 4*y)**3) x*(8*(2*y + 1)**3 + 1) >>> A = Symbol('A', commutative=False) >>> factor_terms(x*A + x*A + x*y*A) x*(y*A + 2*A) When ``clear`` is False, a rational will only be factored out of an Add expression if all terms of the Add have coefficients that are fractions: >>> factor_terms(x/2 + 1, clear=False) x/2 + 1 >>> factor_terms(x/2 + 1, clear=True) (x + 2)/2 This only applies when there is a single Add that the coefficient multiplies: >>> factor_terms(x*y/2 + y, clear=True) y*(x + 2)/2 >>> factor_terms(x*y/2 + y, clear=False) == _ True See Also ======== gcd_terms, sympy.polys.polytools.terms_gcd """ expr = sympify(expr) is_iterable = iterable(expr) if not isinstance(expr, Basic) or expr.is_Atom: if is_iterable: return type(expr)([factor_terms(i, radical=radical, clear=clear, fraction=fraction) for i in expr]) return expr if expr.is_Pow or expr.is_Function or is_iterable or not hasattr(expr, 'args_cnc'): args = expr.args newargs = tuple([factor_terms(i, radical=radical, clear=clear, fraction=fraction) for i in args]) if newargs == args: return expr return expr.func(*newargs) cont, p = expr.as_content_primitive(radical=radical) if p.is_Add: list_args = [gcd_terms(a, isprimitive=True, clear=clear, fraction=fraction) for a in Add.make_args(p)] p = Add._from_args(list_args) # gcd_terms will fix up ordering elif p.args: p = p.func( *[factor_terms(a, radical, clear, fraction) for a in p.args]) p = gcd_terms(p, isprimitive=True, clear=clear, fraction=fraction) return _keep_coeff(cont, p, clear=clear)
def lambdify(args, expr, modules=None, printer=None, use_imps=True, dummify=True): """ Returns a lambda function for fast calculation of numerical values. If not specified differently by the user, ``modules`` defaults to ``["numpy"]`` if NumPy is installed, and ``["math", "mpmath", "sympy"]`` if it isn't, that is, SymPy functions are replaced as far as possible by either ``numpy`` functions if available, and Python's standard library ``math``, or ``mpmath`` functions otherwise. To change this behavior, the "modules" argument can be used. It accepts: - the strings "math", "mpmath", "numpy", "numexpr", "sympy", "tensorflow" - any modules (e.g. math) - dictionaries that map names of sympy functions to arbitrary functions - lists that contain a mix of the arguments above, with higher priority given to entries appearing first. .. warning:: Note that this function uses ``eval``, and thus shouldn't be used on unsanitized input. The default behavior is to substitute all arguments in the provided expression with dummy symbols. This allows for applied functions (e.g. f(t)) to be supplied as arguments. Call the function with dummify=False if dummy substitution is unwanted (and `args` is not a string). If you want to view the lambdified function or provide "sympy" as the module, you should probably set dummify=False. For functions involving large array calculations, numexpr can provide a significant speedup over numpy. Please note that the available functions for numexpr are more limited than numpy but can be expanded with implemented_function and user defined subclasses of Function. If specified, numexpr may be the only option in modules. The official list of numexpr functions can be found at: https://github.com/pydata/numexpr#supported-functions In previous releases ``lambdify`` replaced ``Matrix`` with ``numpy.matrix`` by default. As of release 1.0 ``numpy.array`` is the default. To get the old default behavior you must pass in ``[{'ImmutableDenseMatrix': numpy.matrix}, 'numpy']`` to the ``modules`` kwarg. >>> from sympy import lambdify, Matrix >>> from sympy.abc import x, y >>> import numpy >>> array2mat = [{'ImmutableDenseMatrix': numpy.matrix}, 'numpy'] >>> f = lambdify((x, y), Matrix([x, y]), modules=array2mat) >>> f(1, 2) matrix([[1], [2]]) Usage ===== (1) Use one of the provided modules: >>> from sympy import sin, tan, gamma >>> from sympy.utilities.lambdify import lambdastr >>> from sympy.abc import x, y >>> f = lambdify(x, sin(x), "math") Attention: Functions that are not in the math module will throw a name error when the lambda function is evaluated! So this would be better: >>> f = lambdify(x, sin(x)*gamma(x), ("math", "mpmath", "sympy")) (2) Use some other module: >>> import numpy >>> f = lambdify((x,y), tan(x*y), numpy) Attention: There are naming differences between numpy and sympy. So if you simply take the numpy module, e.g. sympy.atan will not be translated to numpy.arctan. Use the modified module instead by passing the string "numpy": >>> f = lambdify((x,y), tan(x*y), "numpy") >>> f(1, 2) -2.18503986326 >>> from numpy import array >>> f(array([1, 2, 3]), array([2, 3, 5])) [-2.18503986 -0.29100619 -0.8559934 ] (3) Use a dictionary defining custom functions: >>> def my_cool_function(x): return 'sin(%s) is cool' % x >>> myfuncs = {"sin" : my_cool_function} >>> f = lambdify(x, sin(x), myfuncs); f(1) 'sin(1) is cool' Examples ======== >>> from sympy.utilities.lambdify import implemented_function >>> from sympy import sqrt, sin, Matrix >>> from sympy import Function >>> from sympy.abc import w, x, y, z >>> f = lambdify(x, x**2) >>> f(2) 4 >>> f = lambdify((x, y, z), [z, y, x]) >>> f(1,2,3) [3, 2, 1] >>> f = lambdify(x, sqrt(x)) >>> f(4) 2.0 >>> f = lambdify((x, y), sin(x*y)**2) >>> f(0, 5) 0.0 >>> row = lambdify((x, y), Matrix((x, x + y)).T, modules='sympy') >>> row(1, 2) Matrix([[1, 3]]) Tuple arguments are handled and the lambdified function should be called with the same type of arguments as were used to create the function.: >>> f = lambdify((x, (y, z)), x + y) >>> f(1, (2, 4)) 3 A more robust way of handling this is to always work with flattened arguments: >>> from sympy.utilities.iterables import flatten >>> args = w, (x, (y, z)) >>> vals = 1, (2, (3, 4)) >>> f = lambdify(flatten(args), w + x + y + z) >>> f(*flatten(vals)) 10 Functions present in `expr` can also carry their own numerical implementations, in a callable attached to the ``_imp_`` attribute. Usually you attach this using the ``implemented_function`` factory: >>> f = implemented_function(Function('f'), lambda x: x+1) >>> func = lambdify(x, f(x)) >>> func(4) 5 ``lambdify`` always prefers ``_imp_`` implementations to implementations in other namespaces, unless the ``use_imps`` input parameter is False. Usage with Tensorflow module: >>> import tensorflow as tf >>> f = Max(x, sin(x)) >>> func = lambdify(x, f, 'tensorflow') >>> result = func(tf.constant(1.0)) >>> result # a tf.Tensor representing the result of the calculation <tf.Tensor 'Maximum:0' shape=() dtype=float32> >>> sess = tf.Session() >>> sess.run(result) # compute result 1.0 >>> var = tf.Variable(1.0) >>> sess.run(tf.global_variables_initializer()) >>> sess.run(func(var)) # also works for tf.Variable and tf.Placeholder 1.0 >>> tensor = tf.constant([[1.0, 2.0], [3.0, 4.0]]) # works with any shape tensor >>> sess.run(func(tensor)) array([[ 1., 2.], [ 3., 4.]], dtype=float32) """ from sympy.core.symbol import Symbol from sympy.utilities.iterables import flatten # If the user hasn't specified any modules, use what is available. module_provided = True if modules is None: module_provided = False try: _import("numpy") except ImportError: # Use either numpy (if available) or python.math where possible. # XXX: This leads to different behaviour on different systems and # might be the reason for irreproducible errors. modules = ["math", "mpmath", "sympy"] else: modules = ["numpy"] # Get the needed namespaces. namespaces = [] # First find any function implementations if use_imps: namespaces.append(_imp_namespace(expr)) # Check for dict before iterating if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'): namespaces.append(modules) else: # consistency check if _module_present('numexpr', modules) and len(modules) > 1: raise TypeError("numexpr must be the only item in 'modules'") namespaces += list(modules) # fill namespace with first having highest priority namespace = {} for m in namespaces[::-1]: buf = _get_namespace(m) namespace.update(buf) if hasattr(expr, "atoms"): #Try if you can extract symbols from the expression. #Move on if expr.atoms in not implemented. syms = expr.atoms(Symbol) for term in syms: namespace.update({str(term): term}) if printer is None: if _module_present('mpmath', namespaces): from sympy.printing.pycode import MpmathPrinter as Printer elif _module_present('numpy', namespaces): from sympy.printing.pycode import NumPyPrinter as Printer elif _module_present('numexpr', namespaces): from sympy.printing.lambdarepr import NumExprPrinter as Printer elif _module_present('tensorflow', namespaces): from sympy.printing.lambdarepr import TensorflowPrinter as Printer elif _module_present('sympy', namespaces): from sympy.printing.pycode import SymPyPrinter as Printer else: from sympy.printing.pycode import PythonCodePrinter as Printer user_functions = {} for m in namespaces[::-1]: if isinstance(m, dict): for k in m: user_functions[k] = k printer = Printer({ 'fully_qualified_modules': False, 'inline': True, 'user_functions': user_functions }) # Get the names of the args, for creating a docstring if not iterable(args): args = (args, ) names = [] # Grab the callers frame, for getting the names by inspection (if needed) callers_local_vars = inspect.currentframe().f_back.f_locals.items() for n, var in enumerate(args): if hasattr(var, 'name'): names.append(var.name) else: # It's an iterable. Try to get name by inspection of calling frame. name_list = [ var_name for var_name, var_val in callers_local_vars if var_val is var ] if len(name_list) == 1: names.append(name_list[0]) else: # Cannot infer name with certainty. arg_# will have to do. names.append('arg_' + str(n)) # Create lambda function. lstr = lambdastr(args, expr, printer=printer, dummify=dummify) flat = '__flatten_args__' imp_mod_lines = [] for mod, keys in (getattr(printer, 'module_imports', None) or {}).items(): for k in keys: if k not in namespace: imp_mod_lines.append("from %s import %s" % (mod, k)) for ln in imp_mod_lines: exec_(ln, {}, namespace) if flat in lstr: namespace.update({flat: flatten}) # Provide lambda expression with builtins, and compatible implementation of range namespace.update({'builtins': builtins, 'range': range}) func = eval(lstr, namespace) # For numpy lambdify, wrap all input arguments in arrays. # This is a fix for gh-11306. if module_provided and _module_present('numpy', namespaces): def array_wrap(funcarg): @wraps(funcarg) def wrapper(*argsx, **kwargsx): asarray = namespace['asarray'] newargs = [ asarray(i) if isinstance(i, integer_types + (float, complex)) else i for i in argsx ] return funcarg(*newargs, **kwargsx) return wrapper func = array_wrap(func) # Apply the docstring sig = "func({0})".format(", ".join(str(i) for i in names)) sig = textwrap.fill(sig, subsequent_indent=' ' * 8) expr_str = str(expr) if len(expr_str) > 78: expr_str = textwrap.wrap(expr_str, 75)[0] + '...' func.__doc__ = ("Created with lambdify. Signature:\n\n" "{sig}\n\n" "Expression:\n\n" "{expr}\n\n" "Source code:\n\n" "{src}\n\n" "Imported modules:\n\n" "{imp_mods}").format(sig=sig, expr=expr_str, src=lstr, imp_mods='\n'.join(imp_mod_lines)) return func
def _as_finite_diff(derivative, points=1, x0=None, wrt=None): """ Returns an approximation of a derivative of a function in the form of a finite difference formula. The expression is a weighted sum of the function at a number of discrete values of (one of) the independent variable(s). Parameters ========== derivative: a Derivative instance points: sequence or coefficient, optional If sequence: discrete values (length >= order+1) of the independent variable used for generating the finite difference weights. If it is a coefficient, it will be used as the step-size for generating an equidistant sequence of length order+1 centered around ``x0``. default: 1 (step-size 1) x0: number or Symbol, optional the value of the independent variable (``wrt``) at which the derivative is to be approximated. Default: same as ``wrt``. wrt: Symbol, optional "with respect to" the variable for which the (partial) derivative is to be approximated for. If not provided it is required that the Derivative is ordinary. Default: ``None``. Examples ======== >>> from sympy import symbols, Function, exp, sqrt, Symbol, as_finite_diff >>> from sympy.utilities.exceptions import SymPyDeprecationWarning >>> import warnings >>> warnings.simplefilter("ignore", SymPyDeprecationWarning) >>> x, h = symbols('x h') >>> f = Function('f') >>> as_finite_diff(f(x).diff(x)) -f(x - 1/2) + f(x + 1/2) The default step size and number of points are 1 and ``order + 1`` respectively. We can change the step size by passing a symbol as a parameter: >>> as_finite_diff(f(x).diff(x), h) -f(-h/2 + x)/h + f(h/2 + x)/h We can also specify the discretized values to be used in a sequence: >>> as_finite_diff(f(x).diff(x), [x, x+h, x+2*h]) -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h) The algorithm is not restricted to use equidistant spacing, nor do we need to make the approximation around ``x0``, but we can get an expression estimating the derivative at an offset: >>> e, sq2 = exp(1), sqrt(2) >>> xl = [x-h, x+h, x+e*h] >>> as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2) 2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) + (-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) + (-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h) Partial derivatives are also supported: >>> y = Symbol('y') >>> d2fdxdy=f(x,y).diff(x,y) >>> as_finite_diff(d2fdxdy, wrt=x) -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y) See also ======== sympy.calculus.finite_diff.apply_finite_diff sympy.calculus.finite_diff.finite_diff_weights """ if derivative.is_Derivative: pass elif derivative.is_Atom: return derivative else: return derivative.fromiter( [_as_finite_diff(ar, points, x0, wrt) for ar in derivative.args], **derivative.assumptions0) if wrt is None: old = None for v in derivative.variables: if old is v: continue derivative = _as_finite_diff(derivative, points, x0, v) old = v return derivative order = derivative.variables.count(wrt) if x0 is None: x0 = wrt if not iterable(points): if getattr(points, 'is_Function', False) and wrt in points.args: points = points.subs(wrt, x0) # points is simply the step-size, let's make it a # equidistant sequence centered around x0 if order % 2 == 0: # even order => odd number of points, grid point included points = [x0 + points*i for i in range(-order//2, order//2 + 1)] else: # odd order => even number of points, half-way wrt grid point points = [x0 + points*S(i)/2 for i in range(-order, order + 1, 2)] others = [wrt, 0] for v in set(derivative.variables): if v == wrt: continue others += [v, derivative.variables.count(v)] if len(points) < order+1: raise ValueError("Too few points for order %d" % order) return apply_finite_diff(order, points, [ Derivative(derivative.expr.subs({wrt: x}), *others) for x in points], x0)
def solve(f, *symbols, **flags): """ Algebraically solves equations and systems of equations. Currently supported are: - univariate polynomial, - transcendental - piecewise combinations of the above - systems of linear and polynomial equations - sytems containing relational expressions. Input is formed as: f - a single Expr or Poly that must be zero, - an Equality - a Relational expression or boolean - iterable of one or more of the above symbols (Symbol, Function or Derivative) specified as - none given (all free symbols will be used) - single symbol - denested list of symbols e.g. solve(f, x, y) - ordered iterable of symbols e.g. solve(f, [x, y]) flags - ``simplified``, when False, will not simplify solutions (default=True except for polynomials of order 3 or greater) The output varies according to the input and can be seen by example: >>> from sympy import solve, Poly, Eq, Function, exp >>> from sympy.abc import x, y, z, a, b o boolean or univariate Relational >>> solve(x < 3) And(im(x) == 0, re(x) < 3) o single expression and single symbol that is in the expression >>> solve(x - y, x) [y] >>> solve(x - 3, x) [3] >>> solve(Eq(x, 3), x) [3] >>> solve(Poly(x - 3), x) [3] >>> solve(x**2 - y**2, x) [y, -y] >>> solve(x**4 - 1, x) [1, -1, -I, I] o single expression with no symbol that is in the expression >>> solve(3, x) [] >>> solve(x - 3, y) [] o when no symbol is given then all free symbols will be used and sorted with default_sort_key and the result will be the same as above as if those symbols had been supplied >>> solve(x - 3) [3] >>> solve(x**2 - y**2) [y, -y] o when a Function or Derivative is given as a symbol, it is isolated algebraically and an implicit solution may be obtained >>> f = Function('f') >>> solve(f(x) - x, f(x)) [x] >>> solve(f(x).diff(x) - f(x) - x, f(x).diff(x)) [x + f(x)] o single expression and more than 1 symbol when there is a linear solution >>> solve(x - y**2, x, y) {x: y**2} >>> solve(x**2 - y, x, y) {y: x**2} when undetermined coefficients are identified that are linear >>> solve((a + b)*x - b + 2, a, b) {a: -2, b: 2} that are nonlinear >>> solve((a + b)*x - b**2 + 2, a, b) [(-2**(1/2), 2**(1/2)), (2**(1/2), -2**(1/2))] if there is no linear solution then the first successful attempt for a nonlinear solution will be returned >>> solve(x**2 - y**2, x, y) [y, -y] >>> solve(x**2 - y**2/exp(x), x, y) [x*exp(x/2), -x*exp(x/2)] o iterable of one or more of the above involving relationals or bools >>> solve([x < 3, x - 2]) And(im(x) == 0, re(x) == 2) >>> solve([x > 3, x - 2]) False when the system is linear with a solution >>> solve([x - 3], x) {x: 3} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - 15), x, y, z) {x: -3, y: 1} >>> solve((x + 5*y - 2, -3*x + 6*y - z), z, x, y) {x: -5*y + 2, z: 21*y - 6} without a solution >>> solve([x + 3, x - 3]) when the system is not linear >>> solve([x**2 + y -2, y**2 - 4], x, y) [(-2, -2), (0, 2), (0, 2), (2, -2)] Warning: there is a possibility of obtaining ambiguous results if no symbols are given for a nonlinear system of equations or are given as a set since the symbols are not presently reported with the solution. A warning will be issued in this situation. >>> solve([x - 2, x**2 + y]) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, y] [(2, -4)] >>> solve([x - 2, x**2 + f(x)], set([f(x), x])) <BLANKLINE> For nonlinear systems of equations, symbols should be given as a list so as to avoid ambiguity in the results. solve sorted the symbols as [x, f(x)] [(2, -4)] See also: rsolve() for solving recurrence relationships dsolve() for solving differential equations """ # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. ########################################################################### def sympified_list(w): return map(sympify, iff(iterable(w), w, [w])) bare_f = not iterable(f) ordered_symbols = (symbols and symbols[0] and (isinstance(symbols[0], Symbol) or ordered_iter(symbols[0], include=GeneratorType))) f, symbols = (sympified_list(w) for w in [f, symbols]) # preprocess equation(s) ########################################################################### for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f[i] = piecewise_fold(f[i]) # preprocess symbol(s) ########################################################################### if not symbols: # get symbols from equations or supply dummy symbols so solve(3) behaves # like solve(3, x). symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy()]) ordered_symbols = False elif len(symbols) == 1 and iterable(symbols[0]): symbols = symbols[0] if not ordered_symbols: # we do this to make the results returned canonical in case f # contains a system of nonlinear equations; all other cases should # be unambiguous symbols = sorted(symbols, key=lambda i: i.sort_key()) # we can solve for Function and Derivative instances by replacing them # with Dummy symbols symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: msg = 'expected Symbol, Function or Derivative but got %s' raise TypeError(msg % type(s)) symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new # # try to get a solution ########################################################################### if bare_f: # pass f the way it was passed to solve; if it wasn't a list then # a list of solutions will be returned, otherwise a dictionary is # going to be returned f = f[0] solution = _solve(f, *symbols, **flags) # # postprocessing ########################################################################### # Restore original Functions and Derivatives if a dictionary is returned. # This is not necessary for # - the single equation, single unknown case # since the symbol will have been removed from the solution; # - the nonlinear poly_system since that only support zero-dimensional # systems and those results come back as a list if symbol_swapped and type(solution) is dict: solution = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in solution.iteritems()]) # warn if ambiguous results are being obtained # XXX agree on how to make this unambiguous # see issue 2405 for logic in how Polys chooses ordering and # for discussion of what to return see http://groups.google.com/group/sympy # Apr 18, 2011 posting 'using results from solve' elif (not ordered_symbols and len(symbols) > 1 and solution and ordered_iter(solution) and ordered_iter(solution[0]) and any(len(set(s)) > 1 for s in solution)): msg = ('\n\tFor nonlinear systems of equations, symbols should be' + '\n\tgiven as a list so as to avoid ambiguity in the results.' + '\n\tsolve sorted the symbols as %s') print msg % str( bool(symbol_swapped) and list(zip(*swap_dict)[0]) or symbols) # # done ########################################################################### return solution
def test_not_interable(): i, j = symbols('i j', integer=True) A = Indexed('A', i, i + j) assert not iterable(A)
def euler_equations(L, funcs=(), vars=()): r""" Find the Euler-Lagrange equations [1]_ for a given Lagrangian. Parameters ========== L : Expr The Lagrangian that should be a function of the functions listed in the second argument and their derivatives. For example, in the case of two functions `f(x,y)`, `g(x,y)` and two independent variables `x`, `y` the Lagrangian would have the form: .. math:: L\left(f(x,y),g(x,y),\frac{\partial f(x,y)}{\partial x}, \frac{\partial f(x,y)}{\partial y}, \frac{\partial g(x,y)}{\partial x}, \frac{\partial g(x,y)}{\partial y},x,y\right) In many cases it is not necessary to provide anything, except the Lagrangian, it will be auto-detected (and an error raised if this couldn't be done). funcs : Function or an iterable of Functions The functions that the Lagrangian depends on. The Euler equations are differential equations for each of these functions. vars : Symbol or an iterable of Symbols The Symbols that are the independent variables of the functions. Returns ======= eqns : list of Eq The list of differential equations, one for each function. Examples ======== >>> from sympy import Symbol, Function >>> from sympy.calculus.euler import euler_equations >>> x = Function('x') >>> t = Symbol('t') >>> L = (x(t).diff(t))**2/2 - x(t)**2/2 >>> euler_equations(L, x(t), t) [Eq(-x(t) - Derivative(x(t), t, t), 0)] >>> u = Function('u') >>> x = Symbol('x') >>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2 >>> euler_equations(L, u(t, x), [t, x]) [Eq(-Derivative(u(t, x), t, t) + Derivative(u(t, x), x, x), 0)] References ========== .. [1] http://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation """ funcs = tuple(funcs) if iterable(funcs) else (funcs, ) if not funcs: funcs = tuple(L.atoms(Function)) else: for f in funcs: if not isinstance(f, Function): raise TypeError('Function expected, got: %s' % f) vars = tuple(vars) if iterable(vars) else (vars, ) if not vars: vars = funcs[0].args else: vars = tuple(sympify(var) for var in vars) if not all(isinstance(v, Symbol) for v in vars): raise TypeError('Variables are not symbols, got %s' % vars) for f in funcs: if not vars == f.args: raise ValueError("Variables %s don't match args: %s" % (vars, f)) order = max( len(d.variables) for d in L.atoms(Derivative) if d.expr in funcs) eqns = [] for f in funcs: eq = diff(L, f) for i in range(1, order + 1): for p in combinations_with_replacement(vars, i): eq = eq + S.NegativeOne**i * diff(L, diff(f, *p), *p) eqns.append(Eq(eq)) return eqns
def sympify(a, locals=None, convert_xor=True, strict=False, rational=False): """ Converts an arbitrary expression to a type that can be used inside sympy. For example, it will convert python ints into instance of sympy.Rational, floats into instances of sympy.Float, etc. It is also able to coerce symbolic expressions which inherit from Basic. This can be useful in cooperation with SAGE. It currently accepts as arguments: - any object defined in sympy (except matrices [TODO]) - standard numeric python types: int, long, float, Decimal - strings (like "0.09" or "2e-19") - booleans, including ``None`` (will leave them unchanged) - lists, sets or tuples containing any of the above If the argument is already a type that sympy understands, it will do nothing but return that value. This can be used at the beginning of a function to ensure you are working with the correct type. >>> from sympy import sympify >>> sympify(2).is_integer True >>> sympify(2).is_real True >>> sympify(2.0).is_real True >>> sympify("2.0").is_real True >>> sympify("2e-45").is_real True If the expression could not be converted, a SympifyError is raised. >>> sympify("x***2") Traceback (most recent call last): ... SympifyError: SympifyError: "could not parse u'x***2'" If the option ``strict`` is set to ``True``, only the types for which an explicit conversion has been defined are converted. In the other cases, a SympifyError is raised. >>> sympify(True) True >>> sympify(True, strict=True) Traceback (most recent call last): ... SympifyError: SympifyError: True To extend `sympify` to convert custom objects (not derived from `Basic`), the static dictionary `convert` is provided. The custom converters are usually added at import time, and will apply to all objects of the given class or its derived classes. For example, all geometry objects derive from `GeometryEntity` class, and should not be altered by the converter, so we add the following after defining that class: >>> from sympy.core.sympify import converter >>> from sympy.geometry.entity import GeometryEntity >>> converter[GeometryEntity] = lambda x: x """ try: cls = a.__class__ except AttributeError: #a is probably an old-style class object cls = type(a) if cls in sympy_classes: return a if cls in (bool, type(None)): if strict: raise SympifyError(a) else: return a try: return converter[cls](a) except KeyError: for superclass in getmro(cls): try: return converter[superclass](a) except KeyError: continue try: return a._sympy_() except AttributeError: pass if not isinstance(a, basestring): for coerce in (float, int): try: return sympify(coerce(a)) except (TypeError, ValueError, AttributeError, SympifyError): continue if strict: raise SympifyError(a) if isinstance(a, tuple): from containers import Tuple return Tuple(*[sympify(x, locals=locals, convert_xor=convert_xor, rational=rational) for x in a]) if iterable(a): try: return type(a)([sympify(x, locals=locals, convert_xor=convert_xor, rational=rational) for x in a]) except TypeError: # Not all iterables are rebuildable with their type. pass if isinstance(a, dict): try: return type(a)([sympify(x, locals=locals, convert_xor=convert_xor, rational=rational) for x in a.iteritems()]) except TypeError: # Not all iterables are rebuildable with their type. pass # At this point we were given an arbitrary expression # which does not inherit from Basic and doesn't implement # _sympy_ (which is a canonical and robust way to convert # anything to SymPy expression). # # As a last chance, we try to take "a"'s normal form via unicode() # and try to parse it. If it fails, then we have no luck and # return an exception try: a = unicode(a) except Exception, exc: raise SympifyError(a, exc)
def sympified_list(w): return map(sympify, iff(iterable(w), w, [w]))
def linrec(coeffs, init, n): r""" Evaluation of univariate linear recurrences of homogeneous type having coefficients independent of the recurrence variable. Parameters ========== coeffs : iterable Coefficients of the recurrence init : iterable Initial values of the recurrence n : Integer Point of evaluation for the recurrence Notes ===== Let `y(n)` be the recurrence of given type, ``c`` be the sequence of coefficients, ``b`` be the sequence of initial/base values of the recurrence and ``k`` (equal to ``len(c)``) be the order of recurrence. Then, .. math :: y(n) = \begin{cases} b_n & 0 \le n < k \\ c_0 y(n-1) + c_1 y(n-2) + \cdots + c_{k-1} y(n-k) & n \ge k \end{cases} Let `x_0, x_1, \ldots, x_n` be a sequence and consider the transformation that maps each polynomial `f(x)` to `T(f(x))` where each power `x^i` is replaced by the corresponding value `x_i`. The sequence is then a solution of the recurrence if and only if `T(x^i p(x)) = 0` for each `i \ge 0` where `p(x) = x^k - c_0 x^(k-1) - \cdots - c_{k-1}` is the characteristic polynomial. Then `T(f(x)p(x)) = 0` for each polynomial `f(x)` (as it is a linear combination of powers `x^i`). Now, if `x^n` is congruent to `g(x) = a_0 x^0 + a_1 x^1 + \cdots + a_{k-1} x^{k-1}` modulo `p(x)`, then `T(x^n) = x_n` is equal to `T(g(x)) = a_0 x_0 + a_1 x_1 + \cdots + a_{k-1} x_{k-1}`. Computation of `x^n`, given `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}` is performed using exponentiation by squaring (refer to [1]_) with an additional reduction step performed to retain only first `k` powers of `x` in the representation of `x^n`. Examples ======== >>> from sympy.discrete.recurrences import linrec >>> from sympy.abc import x, y, z >>> linrec(coeffs=[1, 1], init=[0, 1], n=10) 55 >>> linrec(coeffs=[1, 1], init=[x, y], n=10) 34*x + 55*y >>> linrec(coeffs=[x, y], init=[0, 1], n=5) x**2*y + x*(x**3 + 2*x*y) + y**2 >>> linrec(coeffs=[1, 2, 3, 0, 0, 4], init=[x, y, z], n=16) 13576*x + 5676*y + 2356*z References ========== .. [1] https://en.wikipedia.org/wiki/Exponentiation_by_squaring .. [2] https://en.wikipedia.org/w/index.php?title=Modular_exponentiation§ion=6#Matrices See Also ======== sympy.polys.agca.extensions.ExtensionElement.__pow__ """ if not coeffs: return S.Zero if not iterable(coeffs): raise TypeError("Expected a sequence of coefficients for" " the recurrence") if not iterable(init): raise TypeError("Expected a sequence of values for the initialization" " of the recurrence") n = as_int(n) if n < 0: raise ValueError("Point of evaluation of recurrence must be a " "non-negative integer") c = [sympify(arg) for arg in coeffs] b = [sympify(arg) for arg in init] k = len(c) if len(b) > k: raise TypeError("Count of initial values should not exceed the " "order of the recurrence") else: b += [S.Zero]*(k - len(b)) # remaining initial values default to zero def _square_and_reduce(u, offset): # squares `(u_0 + u_1 x + u_2 x^2 + \cdots + u_{k-1} x^k)` (and # multiplies by `x` if offset is 1) and reduces the above result of # length upto `2k` to `k` using the characteristic equation of the # recurrence given by, `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}` w = [S.Zero]*(2*len(u) - 1 + offset) for i, p in enumerate(u): for j, q in enumerate(u): w[offset + i + j] += p*q for j in range(len(w) - 1, k - 1, -1): for i in range(k): w[j - i - 1] += w[j]*c[i] return w[:k] def _final_coeffs(n): # computes the final coefficient list - `cf` corresponding to the # point at which recurrence is to be evalauted - `n`, such that, # `y(n) = cf_0 y(k-1) + cf_1 y(k-2) + \cdots + cf_{k-1} y(0)` if n < k: return [S.Zero]*n + [S.One] + [S.Zero]*(k - n - 1) else: return _square_and_reduce(_final_coeffs(n//2), n%2) return b[n] if n < k else sum(u*v for u, v in zip(_final_coeffs(n), b))
def autowrap(expr, language=None, backend='f2py', tempdir=None, args=None, flags=None, verbose=False, helpers=None, code_gen=None, **kwargs): """Generates python callable binaries based on the math expression. Parameters ========== expr The SymPy expression that should be wrapped as a binary routine. language : string, optional If supplied, (options: 'C' or 'F95'), specifies the language of the generated code. If ``None`` [default], the language is inferred based upon the specified backend. backend : string, optional Backend used to wrap the generated code. Either 'f2py' [default], or 'cython'. tempdir : string, optional Path to directory for temporary files. If this argument is supplied, the generated code and the wrapper input files are left intact in the specified path. args : iterable, optional An ordered iterable of symbols. Specifies the argument sequence for the function. flags : iterable, optional Additional option flags that will be passed to the backend. verbose : bool, optional If True, autowrap will not mute the command line backends. This can be helpful for debugging. helpers : 3-tuple or iterable of 3-tuples, optional Used to define auxiliary expressions needed for the main expr. If the main expression needs to call a specialized function it should be passed in via ``helpers``. Autowrap will then make sure that the compiled main expression can link to the helper routine. Items should be 3-tuples with (<function_name>, <sympy_expression>, <argument_tuple>). It is mandatory to supply an argument sequence to helper routines. code_gen : CodeGen instance An instance of a CodeGen subclass. Overrides ``language``. include_dirs : [string] A list of directories to search for C/C++ header files (in Unix form for portability). library_dirs : [string] A list of directories to search for C/C++ libraries at link time. libraries : [string] A list of library names (not filenames or paths) to link against. extra_compile_args : [string] Any extra platform- and compiler-specific information to use when compiling the source files in 'sources'. For platforms and compilers where "command line" makes sense, this is typically a list of command-line arguments, but for other platforms it could be anything. extra_link_args : [string] Any extra platform- and compiler-specific information to use when linking object files together to create the extension (or to create a new static Python interpreter). Similar interpretation as for 'extra_compile_args'. Examples ======== >>> from sympy.abc import x, y, z >>> from sympy.utilities.autowrap import autowrap >>> expr = ((x - y + z)**(13)).expand() >>> binary_func = autowrap(expr) >>> binary_func(1, 4, 2) -1.0 """ if language: if not isinstance(language, type): _validate_backend_language(backend, language) else: language = _infer_language(backend) # two cases 1) helpers is an iterable of 3-tuples and 2) helpers is a # 3-tuple if iterable(helpers) and len(helpers) != 0 and iterable(helpers[0]): helpers = helpers if helpers else () else: helpers = [helpers] if helpers else () args = list(args) if iterable(args, exclude=set) else args if code_gen is None: code_gen = get_code_generator(language, "autowrap") CodeWrapperClass = { 'F2PY': F2PyCodeWrapper, 'CYTHON': CythonCodeWrapper, 'DUMMY': DummyWrapper }[backend.upper()] code_wrapper = CodeWrapperClass(code_gen, tempdir, flags if flags else (), verbose, **kwargs) helps = [] for name_h, expr_h, args_h in helpers: helps.append(code_gen.routine(name_h, expr_h, args_h)) for name_h, expr_h, args_h in helpers: if expr.has(expr_h): name_h = binary_function(name_h, expr_h, backend='dummy') expr = expr.subs(expr_h, name_h(*args_h)) try: routine = code_gen.routine('autofunc', expr, args) except CodeGenArgumentListError as e: # if all missing arguments are for pure output, we simply attach them # at the end and try again, because the wrappers will silently convert # them to return values anyway. new_args = [] for missing in e.missing_args: if not isinstance(missing, OutputArgument): raise new_args.append(missing.name) routine = code_gen.routine('autofunc', expr, args + new_args) return code_wrapper.wrap_code(routine, helpers=helps)
def reduce_inequalities(inequalities, symbols=[]): """Reduce a system of inequalities with rational coefficients. Examples ======== >>> from sympy import sympify as S, Symbol >>> from sympy.abc import x, y >>> from sympy.solvers.inequalities import reduce_inequalities >>> reduce_inequalities(0 <= x + 3, []) (-3 <= x) & (x < oo) >>> reduce_inequalities(0 <= x + y*2 - 1, [x]) (x < oo) & (x >= 1 - 2*y) """ if not iterable(inequalities): inequalities = [inequalities] inequalities = [sympify(i) for i in inequalities] gens = set().union(*[i.free_symbols for i in inequalities]) if not iterable(symbols): symbols = [symbols] symbols = (set(symbols) or gens) & gens if any(i.is_extended_real == False for i in symbols): raise TypeError( filldedent(''' inequalities cannot contain symbols that are not real. ''')) # make vanilla symbol real recast = { i: Dummy(i.name, extended_real=True) for i in gens if i.is_extended_real is None } inequalities = [i.xreplace(recast) for i in inequalities] symbols = {i.xreplace(recast) for i in symbols} # prefilter keep = [] for i in inequalities: if isinstance(i, Relational): i = i.func(i.lhs.as_expr() - i.rhs.as_expr(), 0) elif i not in (True, False): i = Eq(i, 0) if i == True: continue elif i == False: return S.false if i.lhs.is_number: raise NotImplementedError("could not determine truth value of %s" % i) keep.append(i) inequalities = keep del keep # solve system rv = _reduce_inequalities(inequalities, symbols) # restore original symbols and return return rv.xreplace({v: k for k, v in recast.items()})
def lambdify(args, expr, modules=None, printer=None, use_imps=True, dummify=True): """ Returns a lambda function for fast calculation of numerical values. If not specified differently by the user, SymPy functions are replaced as far as possible by either python-math, numpy (if available) or mpmath functions - exactly in this order. To change this behavior, the "modules" argument can be used. It accepts: - the strings "math", "mpmath", "numpy", "numexpr", "sympy" - any modules (e.g. math) - dictionaries that map names of sympy functions to arbitrary functions - lists that contain a mix of the arguments above, with higher priority given to entries appearing first. The default behavior is to substitute all arguments in the provided expression with dummy symbols. This allows for applied functions (e.g. f(t)) to be supplied as arguments. Call the function with dummify=False if dummy substitution is unwanted (and `args` is not a string). If you want to view the lambdified function or provide "sympy" as the module, you should probably set dummify=False. For functions involving large array calculations, numexpr can provide a significant speedup over numpy. Please note that the available functions for numexpr are more limited than numpy but can be expanded with implemented_function and user defined subclasses of Function. If specified, numexpr may be the only option in modules. The official list of numexpr functions can be found at: https://github.com/pydata/numexpr#supported-functions Deprecation Warnings ==================== In previous releases ``lambdify`` replaced ``Matrix`` with ``numpy.matrix`` by default. As of release 0.7.6 ``numpy.array`` is being transitioned to the default. In release 0.7.7 this transition will be complete. For now, to use the new default behavior you must pass in ``[{'ImmutableMatrix': numpy.array}, 'numpy']`` to the ``modules`` kwarg. >>> from sympy import lambdify, Matrix >>> from sympy.abc import x, y >>> import numpy >>> mat2array = [{'ImmutableMatrix': numpy.array}, 'numpy'] >>> f = lambdify((x, y), Matrix([x, y]), modules=mat2array) >>> f(1, 2) array([[1], [2]]) Usage ===== (1) Use one of the provided modules: >>> from sympy import sin, tan, gamma >>> from sympy.utilities.lambdify import lambdastr >>> from sympy.abc import x, y >>> f = lambdify(x, sin(x), "math") Attention: Functions that are not in the math module will throw a name error when the lambda function is evaluated! So this would be better: >>> f = lambdify(x, sin(x)*gamma(x), ("math", "mpmath", "sympy")) (2) Use some other module: >>> import numpy >>> f = lambdify((x,y), tan(x*y), numpy) Attention: There are naming differences between numpy and sympy. So if you simply take the numpy module, e.g. sympy.atan will not be translated to numpy.arctan. Use the modified module instead by passing the string "numpy": >>> f = lambdify((x,y), tan(x*y), "numpy") >>> f(1, 2) -2.18503986326 >>> from numpy import array >>> f(array([1, 2, 3]), array([2, 3, 5])) [-2.18503986 -0.29100619 -0.8559934 ] (3) Use a dictionary defining custom functions: >>> def my_cool_function(x): return 'sin(%s) is cool' % x >>> myfuncs = {"sin" : my_cool_function} >>> f = lambdify(x, sin(x), myfuncs); f(1) 'sin(1) is cool' Examples ======== >>> from sympy.utilities.lambdify import implemented_function >>> from sympy import sqrt, sin, Matrix >>> from sympy import Function >>> from sympy.abc import w, x, y, z >>> f = lambdify(x, x**2) >>> f(2) 4 >>> f = lambdify((x, y, z), [z, y, x]) >>> f(1,2,3) [3, 2, 1] >>> f = lambdify(x, sqrt(x)) >>> f(4) 2.0 >>> f = lambdify((x, y), sin(x*y)**2) >>> f(0, 5) 0.0 >>> row = lambdify((x, y), Matrix((x, x + y)).T, modules='sympy') >>> row(1, 2) Matrix([[1, 3]]) Tuple arguments are handled and the lambdified function should be called with the same type of arguments as were used to create the function.: >>> f = lambdify((x, (y, z)), x + y) >>> f(1, (2, 4)) 3 A more robust way of handling this is to always work with flattened arguments: >>> from sympy.utilities.iterables import flatten >>> args = w, (x, (y, z)) >>> vals = 1, (2, (3, 4)) >>> f = lambdify(flatten(args), w + x + y + z) >>> f(*flatten(vals)) 10 Functions present in `expr` can also carry their own numerical implementations, in a callable attached to the ``_imp_`` attribute. Usually you attach this using the ``implemented_function`` factory: >>> f = implemented_function(Function('f'), lambda x: x+1) >>> func = lambdify(x, f(x)) >>> func(4) 5 ``lambdify`` always prefers ``_imp_`` implementations to implementations in other namespaces, unless the ``use_imps`` input parameter is False. """ from sympy.core.symbol import Symbol from sympy.utilities.iterables import flatten # If the user hasn't specified any modules, use what is available. module_provided = True if modules is None: module_provided = False # Use either numpy (if available) or python.math where possible. # XXX: This leads to different behaviour on different systems and # might be the reason for irreproducible errors. modules = ["math", "mpmath", "sympy"] #Attempt to import numpy try: _import("numpy") except ImportError: pass else: modules.insert(1, "numpy") # Get the needed namespaces. namespaces = [] # First find any function implementations if use_imps: namespaces.append(_imp_namespace(expr)) # Check for dict before iterating if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'): namespaces.append(modules) else: # consistency check if 'numexpr' in modules and len(modules) > 1: raise TypeError("numexpr must be the only item in 'modules'") namespaces += list(modules) # fill namespace with first having highest priority namespace = {} for m in namespaces[::-1]: buf = _get_namespace(m) namespace.update(buf) _issue_7853_dep_check(namespaces, namespace, expr) if hasattr(expr, "atoms"): #Try if you can extract symbols from the expression. #Move on if expr.atoms in not implemented. syms = expr.atoms(Symbol) for term in syms: namespace.update({str(term): term}) if 'numexpr' in namespaces and printer is None: #XXX: This has to be done here because of circular imports from sympy.printing.lambdarepr import NumExprPrinter as printer # Get the names of the args, for creating a docstring if not iterable(args): args = (args, ) names = [] # Grab the callers frame, for getting the names by inspection (if needed) callers_local_vars = inspect.currentframe().f_back.f_locals.items() for n, var in enumerate(args): if hasattr(var, 'name'): names.append(var.name) else: # It's an iterable. Try to get name by inspection of calling frame. name_list = [ var_name for var_name, var_val in callers_local_vars if var_val is var ] if len(name_list) == 1: names.append(name_list[0]) else: # Cannot infer name with certainty. arg_# will have to do. names.append('arg_' + str(n)) # Create lambda function. lstr = lambdastr(args, expr, printer=printer, dummify=dummify) flat = '__flatten_args__' if flat in lstr: namespace.update({flat: flatten}) func = eval(lstr, namespace) # Apply the docstring sig = "func({0})".format(", ".join(str(i) for i in names)) sig = textwrap.fill(sig, subsequent_indent=' ' * 8) expr_str = str(expr) if len(expr_str) > 78: expr_str = textwrap.wrap(expr_str, 75)[0] + '...' func.__doc__ = ("Created with lambdify. Signature:\n\n{sig}\n\n" "Expression:\n\n{expr}").format(sig=sig, expr=expr_str) return func
def lambdastr(args, expr, printer=None, dummify=None): """ Returns a string that can be evaluated to a lambda function. Examples ======== >>> from sympy.abc import x, y, z >>> from sympy.utilities.lambdify import lambdastr >>> lambdastr(x, x**2) 'lambda x: (x**2)' >>> lambdastr((x,y,z), [z,y,x]) 'lambda x,y,z: ([z, y, x])' Although tuples may not appear as arguments to lambda in Python 3, lambdastr will create a lambda function that will unpack the original arguments so that nested arguments can be handled: >>> lambdastr((x, (y, z)), x + y) 'lambda _0,_1: (lambda x,y,z: (x + y))(_0,_1[0],_1[1])' """ # Transforming everything to strings. from sympy.matrices import DeferredVector from sympy import Dummy, sympify, Symbol, Function, flatten, Derivative, Basic if printer is not None: if inspect.isfunction(printer): lambdarepr = printer else: if inspect.isclass(printer): lambdarepr = lambda expr: printer().doprint(expr) else: lambdarepr = lambda expr: printer.doprint(expr) else: #XXX: This has to be done here because of circular imports from sympy.printing.lambdarepr import lambdarepr def sub_args(args, dummies_dict): if isinstance(args, string_types): return args elif isinstance(args, DeferredVector): return str(args) elif iterable(args): dummies = flatten([sub_args(a, dummies_dict) for a in args]) return ",".join(str(a) for a in dummies) else: # replace these with Dummy symbols if isinstance(args, (Function, Symbol, Derivative)): dummies = Dummy() dummies_dict.update({args: dummies}) return str(dummies) else: return str(args) def sub_expr(expr, dummies_dict): try: expr = sympify(expr).xreplace(dummies_dict) except Exception: if isinstance(expr, DeferredVector): pass elif isinstance(expr, dict): k = [sub_expr(sympify(a), dummies_dict) for a in expr.keys()] v = [sub_expr(sympify(a), dummies_dict) for a in expr.values()] expr = dict(zip(k, v)) elif isinstance(expr, tuple): expr = tuple(sub_expr(sympify(a), dummies_dict) for a in expr) elif isinstance(expr, list): expr = [sub_expr(sympify(a), dummies_dict) for a in expr] return expr # Transform args def isiter(l): return iterable(l, exclude=(str, DeferredVector, NotIterable)) def flat_indexes(iterable): n = 0 for el in iterable: if isiter(el): for ndeep in flat_indexes(el): yield (n, ) + ndeep else: yield (n, ) n += 1 if dummify is None: dummify = any( isinstance(a, Basic) and a.atoms(Function, Derivative) for a in (args if isiter(args) else [args])) if isiter(args) and any(isiter(i) for i in args): dum_args = [str(Dummy(str(i))) for i in range(len(args))] indexed_args = ','.join([ dum_args[ind[0]] + ''.join(["[%s]" % k for k in ind[1:]]) for ind in flat_indexes(args) ]) lstr = lambdastr(flatten(args), expr, printer=printer, dummify=dummify) return 'lambda %s: (%s)(%s)' % (','.join(dum_args), lstr, indexed_args) dummies_dict = {} if dummify: args = sub_args(args, dummies_dict) else: if isinstance(args, string_types): pass elif iterable(args, exclude=DeferredVector): args = ",".join(str(a) for a in args) # Transform expr if dummify: if isinstance(expr, string_types): pass else: expr = sub_expr(expr, dummies_dict) expr = lambdarepr(expr) return "lambda %s: (%s)" % (args, expr)
def _solve(f, *symbols, **flags): """ Return a checked solution for f in terms of one or more of the symbols.""" if not iterable(f): if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if not soln is None: return soln # find first successful solution failed = [] for s in symbols: n, d = solve_linear(f, x=[s]) if n.is_Symbol: soln = {n: cancel(d)} return soln failed.append(s) for s in failed: try: soln = _solve(f, s, **flags) return soln except NotImplementedError: pass else: msg = "No algorithms are implemented to solve equation %s" raise NotImplementedError(msg % f) symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [ s for s in soln if checksol(f, {symbol: s}) is not False ] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f * symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [ s for s in sols if checksol(f, {symbol: s}) is not False ] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] if result is False: raise NotImplementedError( msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: polys = [] for g in f: poly = g.as_poly(*symbols, **{'extension': True}) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None result = solve_linear_system(matrix, *symbols, **flags) return result else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] result = solve_poly_system(polys) return result