Coverage for /usr/lib/python3/dist-packages/sympy/utilities/lambdify.py: 52%
427 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
2This module provides convenient functions to transform SymPy expressions to
3lambda functions which can be used to calculate numerical values very fast.
4"""
6from __future__ import annotations
7from typing import Any
9import builtins
10import inspect
11import keyword
12import textwrap
13import linecache
15# Required despite static analysis claiming it is not used
16from sympy.external import import_module # noqa:F401
17from sympy.utilities.exceptions import sympy_deprecation_warning
18from sympy.utilities.decorator import doctest_depends_on
19from sympy.utilities.iterables import (is_sequence, iterable,
20 NotIterable, flatten)
21from sympy.utilities.misc import filldedent
23__doctest_requires__ = {('lambdify',): ['numpy', 'tensorflow']}
25# Default namespaces, letting us define translations that can't be defined
26# by simple variable maps, like I => 1j
27MATH_DEFAULT: dict[str, Any] = {}
28MPMATH_DEFAULT: dict[str, Any] = {}
29NUMPY_DEFAULT: dict[str, Any] = {"I": 1j}
30SCIPY_DEFAULT: dict[str, Any] = {"I": 1j}
31CUPY_DEFAULT: dict[str, Any] = {"I": 1j}
32JAX_DEFAULT: dict[str, Any] = {"I": 1j}
33TENSORFLOW_DEFAULT: dict[str, Any] = {}
34SYMPY_DEFAULT: dict[str, Any] = {}
35NUMEXPR_DEFAULT: dict[str, Any] = {}
37# These are the namespaces the lambda functions will use.
38# These are separate from the names above because they are modified
39# throughout this file, whereas the defaults should remain unmodified.
41MATH = MATH_DEFAULT.copy()
42MPMATH = MPMATH_DEFAULT.copy()
43NUMPY = NUMPY_DEFAULT.copy()
44SCIPY = SCIPY_DEFAULT.copy()
45CUPY = CUPY_DEFAULT.copy()
46JAX = JAX_DEFAULT.copy()
47TENSORFLOW = TENSORFLOW_DEFAULT.copy()
48SYMPY = SYMPY_DEFAULT.copy()
49NUMEXPR = NUMEXPR_DEFAULT.copy()
52# Mappings between SymPy and other modules function names.
53MATH_TRANSLATIONS = {
54 "ceiling": "ceil",
55 "E": "e",
56 "ln": "log",
57}
59# NOTE: This dictionary is reused in Function._eval_evalf to allow subclasses
60# of Function to automatically evalf.
61MPMATH_TRANSLATIONS = {
62 "Abs": "fabs",
63 "elliptic_k": "ellipk",
64 "elliptic_f": "ellipf",
65 "elliptic_e": "ellipe",
66 "elliptic_pi": "ellippi",
67 "ceiling": "ceil",
68 "chebyshevt": "chebyt",
69 "chebyshevu": "chebyu",
70 "E": "e",
71 "I": "j",
72 "ln": "log",
73 #"lowergamma":"lower_gamma",
74 "oo": "inf",
75 #"uppergamma":"upper_gamma",
76 "LambertW": "lambertw",
77 "MutableDenseMatrix": "matrix",
78 "ImmutableDenseMatrix": "matrix",
79 "conjugate": "conj",
80 "dirichlet_eta": "altzeta",
81 "Ei": "ei",
82 "Shi": "shi",
83 "Chi": "chi",
84 "Si": "si",
85 "Ci": "ci",
86 "RisingFactorial": "rf",
87 "FallingFactorial": "ff",
88 "betainc_regularized": "betainc",
89}
91NUMPY_TRANSLATIONS: dict[str, str] = {
92 "Heaviside": "heaviside",
93}
94SCIPY_TRANSLATIONS: dict[str, str] = {}
95CUPY_TRANSLATIONS: dict[str, str] = {}
96JAX_TRANSLATIONS: dict[str, str] = {}
98TENSORFLOW_TRANSLATIONS: dict[str, str] = {}
100NUMEXPR_TRANSLATIONS: dict[str, str] = {}
102# Available modules:
103MODULES = {
104 "math": (MATH, MATH_DEFAULT, MATH_TRANSLATIONS, ("from math import *",)),
105 "mpmath": (MPMATH, MPMATH_DEFAULT, MPMATH_TRANSLATIONS, ("from mpmath import *",)),
106 "numpy": (NUMPY, NUMPY_DEFAULT, NUMPY_TRANSLATIONS, ("import numpy; from numpy import *; from numpy.linalg import *",)),
107 "scipy": (SCIPY, SCIPY_DEFAULT, SCIPY_TRANSLATIONS, ("import scipy; import numpy; from scipy.special import *",)),
108 "cupy": (CUPY, CUPY_DEFAULT, CUPY_TRANSLATIONS, ("import cupy",)),
109 "jax": (JAX, JAX_DEFAULT, JAX_TRANSLATIONS, ("import jax",)),
110 "tensorflow": (TENSORFLOW, TENSORFLOW_DEFAULT, TENSORFLOW_TRANSLATIONS, ("import tensorflow",)),
111 "sympy": (SYMPY, SYMPY_DEFAULT, {}, (
112 "from sympy.functions import *",
113 "from sympy.matrices import *",
114 "from sympy import Integral, pi, oo, nan, zoo, E, I",)),
115 "numexpr" : (NUMEXPR, NUMEXPR_DEFAULT, NUMEXPR_TRANSLATIONS,
116 ("import_module('numexpr')", )),
117}
120def _import(module, reload=False):
121 """
122 Creates a global translation dictionary for module.
124 The argument module has to be one of the following strings: "math",
125 "mpmath", "numpy", "sympy", "tensorflow", "jax".
126 These dictionaries map names of Python functions to their equivalent in
127 other modules.
128 """
129 try:
130 namespace, namespace_default, translations, import_commands = MODULES[
131 module]
132 except KeyError:
133 raise NameError(
134 "'%s' module cannot be used for lambdification" % module)
136 # Clear namespace or exit
137 if namespace != namespace_default:
138 # The namespace was already generated, don't do it again if not forced.
139 if reload:
140 namespace.clear()
141 namespace.update(namespace_default)
142 else:
143 return
145 for import_command in import_commands:
146 if import_command.startswith('import_module'):
147 module = eval(import_command)
149 if module is not None:
150 namespace.update(module.__dict__)
151 continue
152 else:
153 try:
154 exec(import_command, {}, namespace)
155 continue
156 except ImportError:
157 pass
159 raise ImportError(
160 "Cannot import '%s' with '%s' command" % (module, import_command))
162 # Add translated names to namespace
163 for sympyname, translation in translations.items():
164 namespace[sympyname] = namespace[translation]
166 # For computing the modulus of a SymPy expression we use the builtin abs
167 # function, instead of the previously used fabs function for all
168 # translation modules. This is because the fabs function in the math
169 # module does not accept complex valued arguments. (see issue 9474). The
170 # only exception, where we don't use the builtin abs function is the
171 # mpmath translation module, because mpmath.fabs returns mpf objects in
172 # contrast to abs().
173 if 'Abs' not in namespace:
174 namespace['Abs'] = abs
176# Used for dynamically generated filenames that are inserted into the
177# linecache.
178_lambdify_generated_counter = 1
181@doctest_depends_on(modules=('numpy', 'scipy', 'tensorflow',), python_version=(3,))
182def lambdify(args, expr, modules=None, printer=None, use_imps=True,
183 dummify=False, cse=False, docstring_limit=1000):
184 """Convert a SymPy expression into a function that allows for fast
185 numeric evaluation.
187 .. warning::
188 This function uses ``exec``, and thus should not be used on
189 unsanitized input.
191 .. deprecated:: 1.7
192 Passing a set for the *args* parameter is deprecated as sets are
193 unordered. Use an ordered iterable such as a list or tuple.
195 Explanation
196 ===========
198 For example, to convert the SymPy expression ``sin(x) + cos(x)`` to an
199 equivalent NumPy function that numerically evaluates it:
201 >>> from sympy import sin, cos, symbols, lambdify
202 >>> import numpy as np
203 >>> x = symbols('x')
204 >>> expr = sin(x) + cos(x)
205 >>> expr
206 sin(x) + cos(x)
207 >>> f = lambdify(x, expr, 'numpy')
208 >>> a = np.array([1, 2])
209 >>> f(a)
210 [1.38177329 0.49315059]
212 The primary purpose of this function is to provide a bridge from SymPy
213 expressions to numerical libraries such as NumPy, SciPy, NumExpr, mpmath,
214 and tensorflow. In general, SymPy functions do not work with objects from
215 other libraries, such as NumPy arrays, and functions from numeric
216 libraries like NumPy or mpmath do not work on SymPy expressions.
217 ``lambdify`` bridges the two by converting a SymPy expression to an
218 equivalent numeric function.
220 The basic workflow with ``lambdify`` is to first create a SymPy expression
221 representing whatever mathematical function you wish to evaluate. This
222 should be done using only SymPy functions and expressions. Then, use
223 ``lambdify`` to convert this to an equivalent function for numerical
224 evaluation. For instance, above we created ``expr`` using the SymPy symbol
225 ``x`` and SymPy functions ``sin`` and ``cos``, then converted it to an
226 equivalent NumPy function ``f``, and called it on a NumPy array ``a``.
228 Parameters
229 ==========
231 args : List[Symbol]
232 A variable or a list of variables whose nesting represents the
233 nesting of the arguments that will be passed to the function.
235 Variables can be symbols, undefined functions, or matrix symbols.
237 >>> from sympy import Eq
238 >>> from sympy.abc import x, y, z
240 The list of variables should match the structure of how the
241 arguments will be passed to the function. Simply enclose the
242 parameters as they will be passed in a list.
244 To call a function like ``f(x)`` then ``[x]``
245 should be the first argument to ``lambdify``; for this
246 case a single ``x`` can also be used:
248 >>> f = lambdify(x, x + 1)
249 >>> f(1)
250 2
251 >>> f = lambdify([x], x + 1)
252 >>> f(1)
253 2
255 To call a function like ``f(x, y)`` then ``[x, y]`` will
256 be the first argument of the ``lambdify``:
258 >>> f = lambdify([x, y], x + y)
259 >>> f(1, 1)
260 2
262 To call a function with a single 3-element tuple like
263 ``f((x, y, z))`` then ``[(x, y, z)]`` will be the first
264 argument of the ``lambdify``:
266 >>> f = lambdify([(x, y, z)], Eq(z**2, x**2 + y**2))
267 >>> f((3, 4, 5))
268 True
270 If two args will be passed and the first is a scalar but
271 the second is a tuple with two arguments then the items
272 in the list should match that structure:
274 >>> f = lambdify([x, (y, z)], x + y + z)
275 >>> f(1, (2, 3))
276 6
278 expr : Expr
279 An expression, list of expressions, or matrix to be evaluated.
281 Lists may be nested.
282 If the expression is a list, the output will also be a list.
284 >>> f = lambdify(x, [x, [x + 1, x + 2]])
285 >>> f(1)
286 [1, [2, 3]]
288 If it is a matrix, an array will be returned (for the NumPy module).
290 >>> from sympy import Matrix
291 >>> f = lambdify(x, Matrix([x, x + 1]))
292 >>> f(1)
293 [[1]
294 [2]]
296 Note that the argument order here (variables then expression) is used
297 to emulate the Python ``lambda`` keyword. ``lambdify(x, expr)`` works
298 (roughly) like ``lambda x: expr``
299 (see :ref:`lambdify-how-it-works` below).
301 modules : str, optional
302 Specifies the numeric library to use.
304 If not specified, *modules* defaults to:
306 - ``["scipy", "numpy"]`` if SciPy is installed
307 - ``["numpy"]`` if only NumPy is installed
308 - ``["math", "mpmath", "sympy"]`` if neither is installed.
310 That is, SymPy functions are replaced as far as possible by
311 either ``scipy`` or ``numpy`` functions if available, and Python's
312 standard library ``math``, or ``mpmath`` functions otherwise.
314 *modules* can be one of the following types:
316 - The strings ``"math"``, ``"mpmath"``, ``"numpy"``, ``"numexpr"``,
317 ``"scipy"``, ``"sympy"``, or ``"tensorflow"`` or ``"jax"``. This uses the
318 corresponding printer and namespace mapping for that module.
319 - A module (e.g., ``math``). This uses the global namespace of the
320 module. If the module is one of the above known modules, it will
321 also use the corresponding printer and namespace mapping
322 (i.e., ``modules=numpy`` is equivalent to ``modules="numpy"``).
323 - A dictionary that maps names of SymPy functions to arbitrary
324 functions
325 (e.g., ``{'sin': custom_sin}``).
326 - A list that contains a mix of the arguments above, with higher
327 priority given to entries appearing first
328 (e.g., to use the NumPy module but override the ``sin`` function
329 with a custom version, you can use
330 ``[{'sin': custom_sin}, 'numpy']``).
332 dummify : bool, optional
333 Whether or not the variables in the provided expression that are not
334 valid Python identifiers are substituted with dummy symbols.
336 This allows for undefined functions like ``Function('f')(t)`` to be
337 supplied as arguments. By default, the variables are only dummified
338 if they are not valid Python identifiers.
340 Set ``dummify=True`` to replace all arguments with dummy symbols
341 (if ``args`` is not a string) - for example, to ensure that the
342 arguments do not redefine any built-in names.
344 cse : bool, or callable, optional
345 Large expressions can be computed more efficiently when
346 common subexpressions are identified and precomputed before
347 being used multiple time. Finding the subexpressions will make
348 creation of the 'lambdify' function slower, however.
350 When ``True``, ``sympy.simplify.cse`` is used, otherwise (the default)
351 the user may pass a function matching the ``cse`` signature.
353 docstring_limit : int or None
354 When lambdifying large expressions, a significant proportion of the time
355 spent inside ``lambdify`` is spent producing a string representation of
356 the expression for use in the automatically generated docstring of the
357 returned function. For expressions containing hundreds or more nodes the
358 resulting docstring often becomes so long and dense that it is difficult
359 to read. To reduce the runtime of lambdify, the rendering of the full
360 expression inside the docstring can be disabled.
362 When ``None``, the full expression is rendered in the docstring. When
363 ``0`` or a negative ``int``, an ellipsis is rendering in the docstring
364 instead of the expression. When a strictly positive ``int``, if the
365 number of nodes in the expression exceeds ``docstring_limit`` an
366 ellipsis is rendered in the docstring, otherwise a string representation
367 of the expression is rendered as normal. The default is ``1000``.
369 Examples
370 ========
372 >>> from sympy.utilities.lambdify import implemented_function
373 >>> from sympy import sqrt, sin, Matrix
374 >>> from sympy import Function
375 >>> from sympy.abc import w, x, y, z
377 >>> f = lambdify(x, x**2)
378 >>> f(2)
379 4
380 >>> f = lambdify((x, y, z), [z, y, x])
381 >>> f(1,2,3)
382 [3, 2, 1]
383 >>> f = lambdify(x, sqrt(x))
384 >>> f(4)
385 2.0
386 >>> f = lambdify((x, y), sin(x*y)**2)
387 >>> f(0, 5)
388 0.0
389 >>> row = lambdify((x, y), Matrix((x, x + y)).T, modules='sympy')
390 >>> row(1, 2)
391 Matrix([[1, 3]])
393 ``lambdify`` can be used to translate SymPy expressions into mpmath
394 functions. This may be preferable to using ``evalf`` (which uses mpmath on
395 the backend) in some cases.
397 >>> f = lambdify(x, sin(x), 'mpmath')
398 >>> f(1)
399 0.8414709848078965
401 Tuple arguments are handled and the lambdified function should
402 be called with the same type of arguments as were used to create
403 the function:
405 >>> f = lambdify((x, (y, z)), x + y)
406 >>> f(1, (2, 4))
407 3
409 The ``flatten`` function can be used to always work with flattened
410 arguments:
412 >>> from sympy.utilities.iterables import flatten
413 >>> args = w, (x, (y, z))
414 >>> vals = 1, (2, (3, 4))
415 >>> f = lambdify(flatten(args), w + x + y + z)
416 >>> f(*flatten(vals))
417 10
419 Functions present in ``expr`` can also carry their own numerical
420 implementations, in a callable attached to the ``_imp_`` attribute. This
421 can be used with undefined functions using the ``implemented_function``
422 factory:
424 >>> f = implemented_function(Function('f'), lambda x: x+1)
425 >>> func = lambdify(x, f(x))
426 >>> func(4)
427 5
429 ``lambdify`` always prefers ``_imp_`` implementations to implementations
430 in other namespaces, unless the ``use_imps`` input parameter is False.
432 Usage with Tensorflow:
434 >>> import tensorflow as tf
435 >>> from sympy import Max, sin, lambdify
436 >>> from sympy.abc import x
438 >>> f = Max(x, sin(x))
439 >>> func = lambdify(x, f, 'tensorflow')
441 After tensorflow v2, eager execution is enabled by default.
442 If you want to get the compatible result across tensorflow v1 and v2
443 as same as this tutorial, run this line.
445 >>> tf.compat.v1.enable_eager_execution()
447 If you have eager execution enabled, you can get the result out
448 immediately as you can use numpy.
450 If you pass tensorflow objects, you may get an ``EagerTensor``
451 object instead of value.
453 >>> result = func(tf.constant(1.0))
454 >>> print(result)
455 tf.Tensor(1.0, shape=(), dtype=float32)
456 >>> print(result.__class__)
457 <class 'tensorflow.python.framework.ops.EagerTensor'>
459 You can use ``.numpy()`` to get the numpy value of the tensor.
461 >>> result.numpy()
462 1.0
464 >>> var = tf.Variable(2.0)
465 >>> result = func(var) # also works for tf.Variable and tf.Placeholder
466 >>> result.numpy()
467 2.0
469 And it works with any shape array.
471 >>> tensor = tf.constant([[1.0, 2.0], [3.0, 4.0]])
472 >>> result = func(tensor)
473 >>> result.numpy()
474 [[1. 2.]
475 [3. 4.]]
477 Notes
478 =====
480 - For functions involving large array calculations, numexpr can provide a
481 significant speedup over numpy. Please note that the available functions
482 for numexpr are more limited than numpy but can be expanded with
483 ``implemented_function`` and user defined subclasses of Function. If
484 specified, numexpr may be the only option in modules. The official list
485 of numexpr functions can be found at:
486 https://numexpr.readthedocs.io/projects/NumExpr3/en/latest/user_guide.html#supported-functions
488 - In the above examples, the generated functions can accept scalar
489 values or numpy arrays as arguments. However, in some cases
490 the generated function relies on the input being a numpy array:
492 >>> import numpy
493 >>> from sympy import Piecewise
494 >>> from sympy.testing.pytest import ignore_warnings
495 >>> f = lambdify(x, Piecewise((x, x <= 1), (1/x, x > 1)), "numpy")
497 >>> with ignore_warnings(RuntimeWarning):
498 ... f(numpy.array([-1, 0, 1, 2]))
499 [-1. 0. 1. 0.5]
501 >>> f(0)
502 Traceback (most recent call last):
503 ...
504 ZeroDivisionError: division by zero
506 In such cases, the input should be wrapped in a numpy array:
508 >>> with ignore_warnings(RuntimeWarning):
509 ... float(f(numpy.array([0])))
510 0.0
512 Or if numpy functionality is not required another module can be used:
514 >>> f = lambdify(x, Piecewise((x, x <= 1), (1/x, x > 1)), "math")
515 >>> f(0)
516 0
518 .. _lambdify-how-it-works:
520 How it works
521 ============
523 When using this function, it helps a great deal to have an idea of what it
524 is doing. At its core, lambdify is nothing more than a namespace
525 translation, on top of a special printer that makes some corner cases work
526 properly.
528 To understand lambdify, first we must properly understand how Python
529 namespaces work. Say we had two files. One called ``sin_cos_sympy.py``,
530 with
532 .. code:: python
534 # sin_cos_sympy.py
536 from sympy.functions.elementary.trigonometric import (cos, sin)
538 def sin_cos(x):
539 return sin(x) + cos(x)
542 and one called ``sin_cos_numpy.py`` with
544 .. code:: python
546 # sin_cos_numpy.py
548 from numpy import sin, cos
550 def sin_cos(x):
551 return sin(x) + cos(x)
553 The two files define an identical function ``sin_cos``. However, in the
554 first file, ``sin`` and ``cos`` are defined as the SymPy ``sin`` and
555 ``cos``. In the second, they are defined as the NumPy versions.
557 If we were to import the first file and use the ``sin_cos`` function, we
558 would get something like
560 >>> from sin_cos_sympy import sin_cos # doctest: +SKIP
561 >>> sin_cos(1) # doctest: +SKIP
562 cos(1) + sin(1)
564 On the other hand, if we imported ``sin_cos`` from the second file, we
565 would get
567 >>> from sin_cos_numpy import sin_cos # doctest: +SKIP
568 >>> sin_cos(1) # doctest: +SKIP
569 1.38177329068
571 In the first case we got a symbolic output, because it used the symbolic
572 ``sin`` and ``cos`` functions from SymPy. In the second, we got a numeric
573 result, because ``sin_cos`` used the numeric ``sin`` and ``cos`` functions
574 from NumPy. But notice that the versions of ``sin`` and ``cos`` that were
575 used was not inherent to the ``sin_cos`` function definition. Both
576 ``sin_cos`` definitions are exactly the same. Rather, it was based on the
577 names defined at the module where the ``sin_cos`` function was defined.
579 The key point here is that when function in Python references a name that
580 is not defined in the function, that name is looked up in the "global"
581 namespace of the module where that function is defined.
583 Now, in Python, we can emulate this behavior without actually writing a
584 file to disk using the ``exec`` function. ``exec`` takes a string
585 containing a block of Python code, and a dictionary that should contain
586 the global variables of the module. It then executes the code "in" that
587 dictionary, as if it were the module globals. The following is equivalent
588 to the ``sin_cos`` defined in ``sin_cos_sympy.py``:
590 >>> import sympy
591 >>> module_dictionary = {'sin': sympy.sin, 'cos': sympy.cos}
592 >>> exec('''
593 ... def sin_cos(x):
594 ... return sin(x) + cos(x)
595 ... ''', module_dictionary)
596 >>> sin_cos = module_dictionary['sin_cos']
597 >>> sin_cos(1)
598 cos(1) + sin(1)
600 and similarly with ``sin_cos_numpy``:
602 >>> import numpy
603 >>> module_dictionary = {'sin': numpy.sin, 'cos': numpy.cos}
604 >>> exec('''
605 ... def sin_cos(x):
606 ... return sin(x) + cos(x)
607 ... ''', module_dictionary)
608 >>> sin_cos = module_dictionary['sin_cos']
609 >>> sin_cos(1)
610 1.38177329068
612 So now we can get an idea of how ``lambdify`` works. The name "lambdify"
613 comes from the fact that we can think of something like ``lambdify(x,
614 sin(x) + cos(x), 'numpy')`` as ``lambda x: sin(x) + cos(x)``, where
615 ``sin`` and ``cos`` come from the ``numpy`` namespace. This is also why
616 the symbols argument is first in ``lambdify``, as opposed to most SymPy
617 functions where it comes after the expression: to better mimic the
618 ``lambda`` keyword.
620 ``lambdify`` takes the input expression (like ``sin(x) + cos(x)``) and
622 1. Converts it to a string
623 2. Creates a module globals dictionary based on the modules that are
624 passed in (by default, it uses the NumPy module)
625 3. Creates the string ``"def func({vars}): return {expr}"``, where ``{vars}`` is the
626 list of variables separated by commas, and ``{expr}`` is the string
627 created in step 1., then ``exec``s that string with the module globals
628 namespace and returns ``func``.
630 In fact, functions returned by ``lambdify`` support inspection. So you can
631 see exactly how they are defined by using ``inspect.getsource``, or ``??`` if you
632 are using IPython or the Jupyter notebook.
634 >>> f = lambdify(x, sin(x) + cos(x))
635 >>> import inspect
636 >>> print(inspect.getsource(f))
637 def _lambdifygenerated(x):
638 return sin(x) + cos(x)
640 This shows us the source code of the function, but not the namespace it
641 was defined in. We can inspect that by looking at the ``__globals__``
642 attribute of ``f``:
644 >>> f.__globals__['sin']
645 <ufunc 'sin'>
646 >>> f.__globals__['cos']
647 <ufunc 'cos'>
648 >>> f.__globals__['sin'] is numpy.sin
649 True
651 This shows us that ``sin`` and ``cos`` in the namespace of ``f`` will be
652 ``numpy.sin`` and ``numpy.cos``.
654 Note that there are some convenience layers in each of these steps, but at
655 the core, this is how ``lambdify`` works. Step 1 is done using the
656 ``LambdaPrinter`` printers defined in the printing module (see
657 :mod:`sympy.printing.lambdarepr`). This allows different SymPy expressions
658 to define how they should be converted to a string for different modules.
659 You can change which printer ``lambdify`` uses by passing a custom printer
660 in to the ``printer`` argument.
662 Step 2 is augmented by certain translations. There are default
663 translations for each module, but you can provide your own by passing a
664 list to the ``modules`` argument. For instance,
666 >>> def mysin(x):
667 ... print('taking the sin of', x)
668 ... return numpy.sin(x)
669 ...
670 >>> f = lambdify(x, sin(x), [{'sin': mysin}, 'numpy'])
671 >>> f(1)
672 taking the sin of 1
673 0.8414709848078965
675 The globals dictionary is generated from the list by merging the
676 dictionary ``{'sin': mysin}`` and the module dictionary for NumPy. The
677 merging is done so that earlier items take precedence, which is why
678 ``mysin`` is used above instead of ``numpy.sin``.
680 If you want to modify the way ``lambdify`` works for a given function, it
681 is usually easiest to do so by modifying the globals dictionary as such.
682 In more complicated cases, it may be necessary to create and pass in a
683 custom printer.
685 Finally, step 3 is augmented with certain convenience operations, such as
686 the addition of a docstring.
688 Understanding how ``lambdify`` works can make it easier to avoid certain
689 gotchas when using it. For instance, a common mistake is to create a
690 lambdified function for one module (say, NumPy), and pass it objects from
691 another (say, a SymPy expression).
693 For instance, say we create
695 >>> from sympy.abc import x
696 >>> f = lambdify(x, x + 1, 'numpy')
698 Now if we pass in a NumPy array, we get that array plus 1
700 >>> import numpy
701 >>> a = numpy.array([1, 2])
702 >>> f(a)
703 [2 3]
705 But what happens if you make the mistake of passing in a SymPy expression
706 instead of a NumPy array:
708 >>> f(x + 1)
709 x + 2
711 This worked, but it was only by accident. Now take a different lambdified
712 function:
714 >>> from sympy import sin
715 >>> g = lambdify(x, x + sin(x), 'numpy')
717 This works as expected on NumPy arrays:
719 >>> g(a)
720 [1.84147098 2.90929743]
722 But if we try to pass in a SymPy expression, it fails
724 >>> try:
725 ... g(x + 1)
726 ... # NumPy release after 1.17 raises TypeError instead of
727 ... # AttributeError
728 ... except (AttributeError, TypeError):
729 ... raise AttributeError() # doctest: +IGNORE_EXCEPTION_DETAIL
730 Traceback (most recent call last):
731 ...
732 AttributeError:
734 Now, let's look at what happened. The reason this fails is that ``g``
735 calls ``numpy.sin`` on the input expression, and ``numpy.sin`` does not
736 know how to operate on a SymPy object. **As a general rule, NumPy
737 functions do not know how to operate on SymPy expressions, and SymPy
738 functions do not know how to operate on NumPy arrays. This is why lambdify
739 exists: to provide a bridge between SymPy and NumPy.**
741 However, why is it that ``f`` did work? That's because ``f`` does not call
742 any functions, it only adds 1. So the resulting function that is created,
743 ``def _lambdifygenerated(x): return x + 1`` does not depend on the globals
744 namespace it is defined in. Thus it works, but only by accident. A future
745 version of ``lambdify`` may remove this behavior.
747 Be aware that certain implementation details described here may change in
748 future versions of SymPy. The API of passing in custom modules and
749 printers will not change, but the details of how a lambda function is
750 created may change. However, the basic idea will remain the same, and
751 understanding it will be helpful to understanding the behavior of
752 lambdify.
754 **In general: you should create lambdified functions for one module (say,
755 NumPy), and only pass it input types that are compatible with that module
756 (say, NumPy arrays).** Remember that by default, if the ``module``
757 argument is not provided, ``lambdify`` creates functions using the NumPy
758 and SciPy namespaces.
759 """
760 from sympy.core.symbol import Symbol
761 from sympy.core.expr import Expr
763 # If the user hasn't specified any modules, use what is available.
764 if modules is None:
765 try:
766 _import("scipy")
767 except ImportError:
768 try:
769 _import("numpy")
770 except ImportError:
771 # Use either numpy (if available) or python.math where possible.
772 # XXX: This leads to different behaviour on different systems and
773 # might be the reason for irreproducible errors.
774 modules = ["math", "mpmath", "sympy"]
775 else:
776 modules = ["numpy"]
777 else:
778 modules = ["numpy", "scipy"]
780 # Get the needed namespaces.
781 namespaces = []
782 # First find any function implementations
783 if use_imps:
784 namespaces.append(_imp_namespace(expr))
785 # Check for dict before iterating
786 if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'):
787 namespaces.append(modules)
788 else:
789 # consistency check
790 if _module_present('numexpr', modules) and len(modules) > 1:
791 raise TypeError("numexpr must be the only item in 'modules'")
792 namespaces += list(modules)
793 # fill namespace with first having highest priority
794 namespace = {}
795 for m in namespaces[::-1]:
796 buf = _get_namespace(m)
797 namespace.update(buf)
799 if hasattr(expr, "atoms"):
800 #Try if you can extract symbols from the expression.
801 #Move on if expr.atoms in not implemented.
802 syms = expr.atoms(Symbol)
803 for term in syms:
804 namespace.update({str(term): term})
806 if printer is None:
807 if _module_present('mpmath', namespaces):
808 from sympy.printing.pycode import MpmathPrinter as Printer # type: ignore
809 elif _module_present('scipy', namespaces):
810 from sympy.printing.numpy import SciPyPrinter as Printer # type: ignore
811 elif _module_present('numpy', namespaces):
812 from sympy.printing.numpy import NumPyPrinter as Printer # type: ignore
813 elif _module_present('cupy', namespaces):
814 from sympy.printing.numpy import CuPyPrinter as Printer # type: ignore
815 elif _module_present('jax', namespaces):
816 from sympy.printing.numpy import JaxPrinter as Printer # type: ignore
817 elif _module_present('numexpr', namespaces):
818 from sympy.printing.lambdarepr import NumExprPrinter as Printer # type: ignore
819 elif _module_present('tensorflow', namespaces):
820 from sympy.printing.tensorflow import TensorflowPrinter as Printer # type: ignore
821 elif _module_present('sympy', namespaces):
822 from sympy.printing.pycode import SymPyPrinter as Printer # type: ignore
823 else:
824 from sympy.printing.pycode import PythonCodePrinter as Printer # type: ignore
825 user_functions = {}
826 for m in namespaces[::-1]:
827 if isinstance(m, dict):
828 for k in m:
829 user_functions[k] = k
830 printer = Printer({'fully_qualified_modules': False, 'inline': True,
831 'allow_unknown_functions': True,
832 'user_functions': user_functions})
834 if isinstance(args, set):
835 sympy_deprecation_warning(
836 """
837Passing the function arguments to lambdify() as a set is deprecated. This
838leads to unpredictable results since sets are unordered. Instead, use a list
839or tuple for the function arguments.
840 """,
841 deprecated_since_version="1.6.3",
842 active_deprecations_target="deprecated-lambdify-arguments-set",
843 )
845 # Get the names of the args, for creating a docstring
846 iterable_args = (args,) if isinstance(args, Expr) else args
847 names = []
849 # Grab the callers frame, for getting the names by inspection (if needed)
850 callers_local_vars = inspect.currentframe().f_back.f_locals.items() # type: ignore
851 for n, var in enumerate(iterable_args):
852 if hasattr(var, 'name'):
853 names.append(var.name)
854 else:
855 # It's an iterable. Try to get name by inspection of calling frame.
856 name_list = [var_name for var_name, var_val in callers_local_vars
857 if var_val is var]
858 if len(name_list) == 1:
859 names.append(name_list[0])
860 else:
861 # Cannot infer name with certainty. arg_# will have to do.
862 names.append('arg_' + str(n))
864 # Create the function definition code and execute it
865 funcname = '_lambdifygenerated'
866 if _module_present('tensorflow', namespaces):
867 funcprinter = _TensorflowEvaluatorPrinter(printer, dummify)
868 else:
869 funcprinter = _EvaluatorPrinter(printer, dummify)
871 if cse == True:
872 from sympy.simplify.cse_main import cse as _cse
873 cses, _expr = _cse(expr, list=False)
874 elif callable(cse):
875 cses, _expr = cse(expr)
876 else:
877 cses, _expr = (), expr
878 funcstr = funcprinter.doprint(funcname, iterable_args, _expr, cses=cses)
880 # Collect the module imports from the code printers.
881 imp_mod_lines = []
882 for mod, keys in (getattr(printer, 'module_imports', None) or {}).items():
883 for k in keys:
884 if k not in namespace:
885 ln = "from %s import %s" % (mod, k)
886 try:
887 exec(ln, {}, namespace)
888 except ImportError:
889 # Tensorflow 2.0 has issues with importing a specific
890 # function from its submodule.
891 # https://github.com/tensorflow/tensorflow/issues/33022
892 ln = "%s = %s.%s" % (k, mod, k)
893 exec(ln, {}, namespace)
894 imp_mod_lines.append(ln)
896 # Provide lambda expression with builtins, and compatible implementation of range
897 namespace.update({'builtins':builtins, 'range':range})
899 funclocals = {}
900 global _lambdify_generated_counter
901 filename = '<lambdifygenerated-%s>' % _lambdify_generated_counter
902 _lambdify_generated_counter += 1
903 c = compile(funcstr, filename, 'exec')
904 exec(c, namespace, funclocals)
905 # mtime has to be None or else linecache.checkcache will remove it
906 linecache.cache[filename] = (len(funcstr), None, funcstr.splitlines(True), filename) # type: ignore
908 func = funclocals[funcname]
910 # Apply the docstring
911 sig = "func({})".format(", ".join(str(i) for i in names))
912 sig = textwrap.fill(sig, subsequent_indent=' '*8)
913 if _too_large_for_docstring(expr, docstring_limit):
914 expr_str = 'EXPRESSION REDACTED DUE TO LENGTH'
915 src_str = 'SOURCE CODE REDACTED DUE TO LENGTH'
916 else:
917 expr_str = str(expr)
918 if len(expr_str) > 78:
919 expr_str = textwrap.wrap(expr_str, 75)[0] + '...'
920 src_str = funcstr
921 func.__doc__ = (
922 "Created with lambdify. Signature:\n\n"
923 "{sig}\n\n"
924 "Expression:\n\n"
925 "{expr}\n\n"
926 "Source code:\n\n"
927 "{src}\n\n"
928 "Imported modules:\n\n"
929 "{imp_mods}"
930 ).format(sig=sig, expr=expr_str, src=src_str, imp_mods='\n'.join(imp_mod_lines))
931 return func
933def _module_present(modname, modlist):
934 if modname in modlist:
935 return True
936 for m in modlist:
937 if hasattr(m, '__name__') and m.__name__ == modname:
938 return True
939 return False
941def _get_namespace(m):
942 """
943 This is used by _lambdify to parse its arguments.
944 """
945 if isinstance(m, str):
946 _import(m)
947 return MODULES[m][0]
948 elif isinstance(m, dict):
949 return m
950 elif hasattr(m, "__dict__"):
951 return m.__dict__
952 else:
953 raise TypeError("Argument must be either a string, dict or module but it is: %s" % m)
956def _recursive_to_string(doprint, arg):
957 """Functions in lambdify accept both SymPy types and non-SymPy types such as python
958 lists and tuples. This method ensures that we only call the doprint method of the
959 printer with SymPy types (so that the printer safely can use SymPy-methods)."""
960 from sympy.matrices.common import MatrixOperations
961 from sympy.core.basic import Basic
963 if isinstance(arg, (Basic, MatrixOperations)):
964 return doprint(arg)
965 elif iterable(arg):
966 if isinstance(arg, list):
967 left, right = "[", "]"
968 elif isinstance(arg, tuple):
969 left, right = "(", ",)"
970 else:
971 raise NotImplementedError("unhandled type: %s, %s" % (type(arg), arg))
972 return left +', '.join(_recursive_to_string(doprint, e) for e in arg) + right
973 elif isinstance(arg, str):
974 return arg
975 else:
976 return doprint(arg)
979def lambdastr(args, expr, printer=None, dummify=None):
980 """
981 Returns a string that can be evaluated to a lambda function.
983 Examples
984 ========
986 >>> from sympy.abc import x, y, z
987 >>> from sympy.utilities.lambdify import lambdastr
988 >>> lambdastr(x, x**2)
989 'lambda x: (x**2)'
990 >>> lambdastr((x,y,z), [z,y,x])
991 'lambda x,y,z: ([z, y, x])'
993 Although tuples may not appear as arguments to lambda in Python 3,
994 lambdastr will create a lambda function that will unpack the original
995 arguments so that nested arguments can be handled:
997 >>> lambdastr((x, (y, z)), x + y)
998 'lambda _0,_1: (lambda x,y,z: (x + y))(_0,_1[0],_1[1])'
999 """
1000 # Transforming everything to strings.
1001 from sympy.matrices import DeferredVector
1002 from sympy.core.basic import Basic
1003 from sympy.core.function import (Derivative, Function)
1004 from sympy.core.symbol import (Dummy, Symbol)
1005 from sympy.core.sympify import sympify
1007 if printer is not None:
1008 if inspect.isfunction(printer):
1009 lambdarepr = printer
1010 else:
1011 if inspect.isclass(printer):
1012 lambdarepr = lambda expr: printer().doprint(expr)
1013 else:
1014 lambdarepr = lambda expr: printer.doprint(expr)
1015 else:
1016 #XXX: This has to be done here because of circular imports
1017 from sympy.printing.lambdarepr import lambdarepr
1019 def sub_args(args, dummies_dict):
1020 if isinstance(args, str):
1021 return args
1022 elif isinstance(args, DeferredVector):
1023 return str(args)
1024 elif iterable(args):
1025 dummies = flatten([sub_args(a, dummies_dict) for a in args])
1026 return ",".join(str(a) for a in dummies)
1027 else:
1028 # replace these with Dummy symbols
1029 if isinstance(args, (Function, Symbol, Derivative)):
1030 dummies = Dummy()
1031 dummies_dict.update({args : dummies})
1032 return str(dummies)
1033 else:
1034 return str(args)
1036 def sub_expr(expr, dummies_dict):
1037 expr = sympify(expr)
1038 # dict/tuple are sympified to Basic
1039 if isinstance(expr, Basic):
1040 expr = expr.xreplace(dummies_dict)
1041 # list is not sympified to Basic
1042 elif isinstance(expr, list):
1043 expr = [sub_expr(a, dummies_dict) for a in expr]
1044 return expr
1046 # Transform args
1047 def isiter(l):
1048 return iterable(l, exclude=(str, DeferredVector, NotIterable))
1050 def flat_indexes(iterable):
1051 n = 0
1053 for el in iterable:
1054 if isiter(el):
1055 for ndeep in flat_indexes(el):
1056 yield (n,) + ndeep
1057 else:
1058 yield (n,)
1060 n += 1
1062 if dummify is None:
1063 dummify = any(isinstance(a, Basic) and
1064 a.atoms(Function, Derivative) for a in (
1065 args if isiter(args) else [args]))
1067 if isiter(args) and any(isiter(i) for i in args):
1068 dum_args = [str(Dummy(str(i))) for i in range(len(args))]
1070 indexed_args = ','.join([
1071 dum_args[ind[0]] + ''.join(["[%s]" % k for k in ind[1:]])
1072 for ind in flat_indexes(args)])
1074 lstr = lambdastr(flatten(args), expr, printer=printer, dummify=dummify)
1076 return 'lambda %s: (%s)(%s)' % (','.join(dum_args), lstr, indexed_args)
1078 dummies_dict = {}
1079 if dummify:
1080 args = sub_args(args, dummies_dict)
1081 else:
1082 if isinstance(args, str):
1083 pass
1084 elif iterable(args, exclude=DeferredVector):
1085 args = ",".join(str(a) for a in args)
1087 # Transform expr
1088 if dummify:
1089 if isinstance(expr, str):
1090 pass
1091 else:
1092 expr = sub_expr(expr, dummies_dict)
1093 expr = _recursive_to_string(lambdarepr, expr)
1094 return "lambda %s: (%s)" % (args, expr)
1096class _EvaluatorPrinter:
1097 def __init__(self, printer=None, dummify=False):
1098 self._dummify = dummify
1100 #XXX: This has to be done here because of circular imports
1101 from sympy.printing.lambdarepr import LambdaPrinter
1103 if printer is None:
1104 printer = LambdaPrinter()
1106 if inspect.isfunction(printer):
1107 self._exprrepr = printer
1108 else:
1109 if inspect.isclass(printer):
1110 printer = printer()
1112 self._exprrepr = printer.doprint
1114 #if hasattr(printer, '_print_Symbol'):
1115 # symbolrepr = printer._print_Symbol
1117 #if hasattr(printer, '_print_Dummy'):
1118 # dummyrepr = printer._print_Dummy
1120 # Used to print the generated function arguments in a standard way
1121 self._argrepr = LambdaPrinter().doprint
1123 def doprint(self, funcname, args, expr, *, cses=()):
1124 """
1125 Returns the function definition code as a string.
1126 """
1127 from sympy.core.symbol import Dummy
1129 funcbody = []
1131 if not iterable(args):
1132 args = [args]
1134 if cses:
1135 subvars, subexprs = zip(*cses)
1136 exprs = [expr] + list(subexprs)
1137 argstrs, exprs = self._preprocess(args, exprs)
1138 expr, subexprs = exprs[0], exprs[1:]
1139 cses = zip(subvars, subexprs)
1140 else:
1141 argstrs, expr = self._preprocess(args, expr)
1143 # Generate argument unpacking and final argument list
1144 funcargs = []
1145 unpackings = []
1147 for argstr in argstrs:
1148 if iterable(argstr):
1149 funcargs.append(self._argrepr(Dummy()))
1150 unpackings.extend(self._print_unpacking(argstr, funcargs[-1]))
1151 else:
1152 funcargs.append(argstr)
1154 funcsig = 'def {}({}):'.format(funcname, ', '.join(funcargs))
1156 # Wrap input arguments before unpacking
1157 funcbody.extend(self._print_funcargwrapping(funcargs))
1159 funcbody.extend(unpackings)
1161 for s, e in cses:
1162 if e is None:
1163 funcbody.append('del {}'.format(s))
1164 else:
1165 funcbody.append('{} = {}'.format(s, self._exprrepr(e)))
1167 str_expr = _recursive_to_string(self._exprrepr, expr)
1169 if '\n' in str_expr:
1170 str_expr = '({})'.format(str_expr)
1171 funcbody.append('return {}'.format(str_expr))
1173 funclines = [funcsig]
1174 funclines.extend([' ' + line for line in funcbody])
1176 return '\n'.join(funclines) + '\n'
1178 @classmethod
1179 def _is_safe_ident(cls, ident):
1180 return isinstance(ident, str) and ident.isidentifier() \
1181 and not keyword.iskeyword(ident)
1183 def _preprocess(self, args, expr):
1184 """Preprocess args, expr to replace arguments that do not map
1185 to valid Python identifiers.
1187 Returns string form of args, and updated expr.
1188 """
1189 from sympy.core.basic import Basic
1190 from sympy.core.sorting import ordered
1191 from sympy.core.function import (Derivative, Function)
1192 from sympy.core.symbol import Dummy, uniquely_named_symbol
1193 from sympy.matrices import DeferredVector
1194 from sympy.core.expr import Expr
1196 # Args of type Dummy can cause name collisions with args
1197 # of type Symbol. Force dummify of everything in this
1198 # situation.
1199 dummify = self._dummify or any(
1200 isinstance(arg, Dummy) for arg in flatten(args))
1202 argstrs = [None]*len(args)
1203 for arg, i in reversed(list(ordered(zip(args, range(len(args)))))):
1204 if iterable(arg):
1205 s, expr = self._preprocess(arg, expr)
1206 elif isinstance(arg, DeferredVector):
1207 s = str(arg)
1208 elif isinstance(arg, Basic) and arg.is_symbol:
1209 s = self._argrepr(arg)
1210 if dummify or not self._is_safe_ident(s):
1211 dummy = Dummy()
1212 if isinstance(expr, Expr):
1213 dummy = uniquely_named_symbol(
1214 dummy.name, expr, modify=lambda s: '_' + s)
1215 s = self._argrepr(dummy)
1216 expr = self._subexpr(expr, {arg: dummy})
1217 elif dummify or isinstance(arg, (Function, Derivative)):
1218 dummy = Dummy()
1219 s = self._argrepr(dummy)
1220 expr = self._subexpr(expr, {arg: dummy})
1221 else:
1222 s = str(arg)
1223 argstrs[i] = s
1224 return argstrs, expr
1226 def _subexpr(self, expr, dummies_dict):
1227 from sympy.matrices import DeferredVector
1228 from sympy.core.sympify import sympify
1230 expr = sympify(expr)
1231 xreplace = getattr(expr, 'xreplace', None)
1232 if xreplace is not None:
1233 expr = xreplace(dummies_dict)
1234 else:
1235 if isinstance(expr, DeferredVector):
1236 pass
1237 elif isinstance(expr, dict):
1238 k = [self._subexpr(sympify(a), dummies_dict) for a in expr.keys()]
1239 v = [self._subexpr(sympify(a), dummies_dict) for a in expr.values()]
1240 expr = dict(zip(k, v))
1241 elif isinstance(expr, tuple):
1242 expr = tuple(self._subexpr(sympify(a), dummies_dict) for a in expr)
1243 elif isinstance(expr, list):
1244 expr = [self._subexpr(sympify(a), dummies_dict) for a in expr]
1245 return expr
1247 def _print_funcargwrapping(self, args):
1248 """Generate argument wrapping code.
1250 args is the argument list of the generated function (strings).
1252 Return value is a list of lines of code that will be inserted at
1253 the beginning of the function definition.
1254 """
1255 return []
1257 def _print_unpacking(self, unpackto, arg):
1258 """Generate argument unpacking code.
1260 arg is the function argument to be unpacked (a string), and
1261 unpackto is a list or nested lists of the variable names (strings) to
1262 unpack to.
1263 """
1264 def unpack_lhs(lvalues):
1265 return '[{}]'.format(', '.join(
1266 unpack_lhs(val) if iterable(val) else val for val in lvalues))
1268 return ['{} = {}'.format(unpack_lhs(unpackto), arg)]
1270class _TensorflowEvaluatorPrinter(_EvaluatorPrinter):
1271 def _print_unpacking(self, lvalues, rvalue):
1272 """Generate argument unpacking code.
1274 This method is used when the input value is not interable,
1275 but can be indexed (see issue #14655).
1276 """
1278 def flat_indexes(elems):
1279 n = 0
1281 for el in elems:
1282 if iterable(el):
1283 for ndeep in flat_indexes(el):
1284 yield (n,) + ndeep
1285 else:
1286 yield (n,)
1288 n += 1
1290 indexed = ', '.join('{}[{}]'.format(rvalue, ']['.join(map(str, ind)))
1291 for ind in flat_indexes(lvalues))
1293 return ['[{}] = [{}]'.format(', '.join(flatten(lvalues)), indexed)]
1295def _imp_namespace(expr, namespace=None):
1296 """ Return namespace dict with function implementations
1298 We need to search for functions in anything that can be thrown at
1299 us - that is - anything that could be passed as ``expr``. Examples
1300 include SymPy expressions, as well as tuples, lists and dicts that may
1301 contain SymPy expressions.
1303 Parameters
1304 ----------
1305 expr : object
1306 Something passed to lambdify, that will generate valid code from
1307 ``str(expr)``.
1308 namespace : None or mapping
1309 Namespace to fill. None results in new empty dict
1311 Returns
1312 -------
1313 namespace : dict
1314 dict with keys of implemented function names within ``expr`` and
1315 corresponding values being the numerical implementation of
1316 function
1318 Examples
1319 ========
1321 >>> from sympy.abc import x
1322 >>> from sympy.utilities.lambdify import implemented_function, _imp_namespace
1323 >>> from sympy import Function
1324 >>> f = implemented_function(Function('f'), lambda x: x+1)
1325 >>> g = implemented_function(Function('g'), lambda x: x*10)
1326 >>> namespace = _imp_namespace(f(g(x)))
1327 >>> sorted(namespace.keys())
1328 ['f', 'g']
1329 """
1330 # Delayed import to avoid circular imports
1331 from sympy.core.function import FunctionClass
1332 if namespace is None:
1333 namespace = {}
1334 # tuples, lists, dicts are valid expressions
1335 if is_sequence(expr):
1336 for arg in expr:
1337 _imp_namespace(arg, namespace)
1338 return namespace
1339 elif isinstance(expr, dict):
1340 for key, val in expr.items():
1341 # functions can be in dictionary keys
1342 _imp_namespace(key, namespace)
1343 _imp_namespace(val, namespace)
1344 return namespace
1345 # SymPy expressions may be Functions themselves
1346 func = getattr(expr, 'func', None)
1347 if isinstance(func, FunctionClass):
1348 imp = getattr(func, '_imp_', None)
1349 if imp is not None:
1350 name = expr.func.__name__
1351 if name in namespace and namespace[name] != imp:
1352 raise ValueError('We found more than one '
1353 'implementation with name '
1354 '"%s"' % name)
1355 namespace[name] = imp
1356 # and / or they may take Functions as arguments
1357 if hasattr(expr, 'args'):
1358 for arg in expr.args:
1359 _imp_namespace(arg, namespace)
1360 return namespace
1363def implemented_function(symfunc, implementation):
1364 """ Add numerical ``implementation`` to function ``symfunc``.
1366 ``symfunc`` can be an ``UndefinedFunction`` instance, or a name string.
1367 In the latter case we create an ``UndefinedFunction`` instance with that
1368 name.
1370 Be aware that this is a quick workaround, not a general method to create
1371 special symbolic functions. If you want to create a symbolic function to be
1372 used by all the machinery of SymPy you should subclass the ``Function``
1373 class.
1375 Parameters
1376 ----------
1377 symfunc : ``str`` or ``UndefinedFunction`` instance
1378 If ``str``, then create new ``UndefinedFunction`` with this as
1379 name. If ``symfunc`` is an Undefined function, create a new function
1380 with the same name and the implemented function attached.
1381 implementation : callable
1382 numerical implementation to be called by ``evalf()`` or ``lambdify``
1384 Returns
1385 -------
1386 afunc : sympy.FunctionClass instance
1387 function with attached implementation
1389 Examples
1390 ========
1392 >>> from sympy.abc import x
1393 >>> from sympy.utilities.lambdify import implemented_function
1394 >>> from sympy import lambdify
1395 >>> f = implemented_function('f', lambda x: x+1)
1396 >>> lam_f = lambdify(x, f(x))
1397 >>> lam_f(4)
1398 5
1399 """
1400 # Delayed import to avoid circular imports
1401 from sympy.core.function import UndefinedFunction
1402 # if name, create function to hold implementation
1403 kwargs = {}
1404 if isinstance(symfunc, UndefinedFunction):
1405 kwargs = symfunc._kwargs
1406 symfunc = symfunc.__name__
1407 if isinstance(symfunc, str):
1408 # Keyword arguments to UndefinedFunction are added as attributes to
1409 # the created class.
1410 symfunc = UndefinedFunction(
1411 symfunc, _imp_=staticmethod(implementation), **kwargs)
1412 elif not isinstance(symfunc, UndefinedFunction):
1413 raise ValueError(filldedent('''
1414 symfunc should be either a string or
1415 an UndefinedFunction instance.'''))
1416 return symfunc
1419def _too_large_for_docstring(expr, limit):
1420 """Decide whether an ``Expr`` is too large to be fully rendered in a
1421 ``lambdify`` docstring.
1423 This is a fast alternative to ``count_ops``, which can become prohibitively
1424 slow for large expressions, because in this instance we only care whether
1425 ``limit`` is exceeded rather than counting the exact number of nodes in the
1426 expression.
1428 Parameters
1429 ==========
1430 expr : ``Expr``, (nested) ``list`` of ``Expr``, or ``Matrix``
1431 The same objects that can be passed to the ``expr`` argument of
1432 ``lambdify``.
1433 limit : ``int`` or ``None``
1434 The threshold above which an expression contains too many nodes to be
1435 usefully rendered in the docstring. If ``None`` then there is no limit.
1437 Returns
1438 =======
1439 bool
1440 ``True`` if the number of nodes in the expression exceeds the limit,
1441 ``False`` otherwise.
1443 Examples
1444 ========
1446 >>> from sympy.abc import x, y, z
1447 >>> from sympy.utilities.lambdify import _too_large_for_docstring
1448 >>> expr = x
1449 >>> _too_large_for_docstring(expr, None)
1450 False
1451 >>> _too_large_for_docstring(expr, 100)
1452 False
1453 >>> _too_large_for_docstring(expr, 1)
1454 False
1455 >>> _too_large_for_docstring(expr, 0)
1456 True
1457 >>> _too_large_for_docstring(expr, -1)
1458 True
1460 Does this split it?
1462 >>> expr = [x, y, z]
1463 >>> _too_large_for_docstring(expr, None)
1464 False
1465 >>> _too_large_for_docstring(expr, 100)
1466 False
1467 >>> _too_large_for_docstring(expr, 1)
1468 True
1469 >>> _too_large_for_docstring(expr, 0)
1470 True
1471 >>> _too_large_for_docstring(expr, -1)
1472 True
1474 >>> expr = [x, [y], z, [[x+y], [x*y*z, [x+y+z]]]]
1475 >>> _too_large_for_docstring(expr, None)
1476 False
1477 >>> _too_large_for_docstring(expr, 100)
1478 False
1479 >>> _too_large_for_docstring(expr, 1)
1480 True
1481 >>> _too_large_for_docstring(expr, 0)
1482 True
1483 >>> _too_large_for_docstring(expr, -1)
1484 True
1486 >>> expr = ((x + y + z)**5).expand()
1487 >>> _too_large_for_docstring(expr, None)
1488 False
1489 >>> _too_large_for_docstring(expr, 100)
1490 True
1491 >>> _too_large_for_docstring(expr, 1)
1492 True
1493 >>> _too_large_for_docstring(expr, 0)
1494 True
1495 >>> _too_large_for_docstring(expr, -1)
1496 True
1498 >>> from sympy import Matrix
1499 >>> expr = Matrix([[(x + y + z), ((x + y + z)**2).expand(),
1500 ... ((x + y + z)**3).expand(), ((x + y + z)**4).expand()]])
1501 >>> _too_large_for_docstring(expr, None)
1502 False
1503 >>> _too_large_for_docstring(expr, 1000)
1504 False
1505 >>> _too_large_for_docstring(expr, 100)
1506 True
1507 >>> _too_large_for_docstring(expr, 1)
1508 True
1509 >>> _too_large_for_docstring(expr, 0)
1510 True
1511 >>> _too_large_for_docstring(expr, -1)
1512 True
1514 """
1515 # Must be imported here to avoid a circular import error
1516 from sympy.core.traversal import postorder_traversal
1518 if limit is None:
1519 return False
1521 i = 0
1522 for _ in postorder_traversal(expr):
1523 i += 1
1524 if i > limit:
1525 return True
1526 return False