基本操作

在这里,我们讨论SymPy中表达式操作所需的一些最基本的操作。一些更高级的操作将在 高级表达式操作 部分中讨论。

>>> from sympy import *
>>> x, y, z = symbols("x y z")

替换

你可能最常想对数学表达式做的事情之一就是替换。替换将表达式中所有某物实例替换为其他东西。它通过使用 subs 方法来完成。例如

>>> expr = cos(x) + 1
>>> expr.subs(x, y)
cos(y) + 1

替换通常出于以下两个原因之一:

  1. 在某个点上评估一个表达式。例如,如果我们的表达式是 cos(x) + 1 ,而我们想在点 x = 0 处评估它,所以我们得到 cos(0) + 1 ,即 2。

    >>> expr.subs(x, 0)
    2
    
  2. 用另一个子表达式替换子表达式。我们可能想要这样做有两个原因。第一个原因是如果我们试图构建一个具有某种对称性的表达式,例如 \(x^{x^{x^x}}\)。为了构建这个,我们可能会从 x**y 开始,并用 x**y 替换 y。然后我们会得到 x**(x**y)。如果在这个新表达式中用 x**x 替换 y,我们会得到 x**(x**(x**x)),即所需的表达式。

    >>> expr = x**y
    >>> expr
    x**y
    >>> expr = expr.subs(y, x**y)
    >>> expr
    x**(x**y)
    >>> expr = expr.subs(y, x**x)
    >>> expr
    x**(x**(x**x))
    

    第二种情况是如果我们想要进行一种非常受控的简化,或者可能是SymPy无法做到的简化。例如,假设我们有 \(\sin(2x) + \cos(2x)\),我们想要用 \(2\sin(x)\cos(x)\) 替换 \(\sin(2x)\)。正如我们稍后将学到的,函数 expand_trig 可以做到这一点。然而,这个函数也会展开 \(\cos(2x)\),这可能不是我们想要的。虽然有办法进行这种精确的简化,我们将在 高级表达式操作 部分学习其中的一些方法,但一个简单的方法就是直接用 \(2\sin(x)\cos(x)\) 替换 \(\sin(2x)\)

    >>> expr = sin(2*x) + cos(2*x)
    >>> expand_trig(expr)
    2*sin(x)*cos(x) + 2*cos(x)**2 - 1
    >>> expr.subs(sin(2*x), 2*sin(x)*cos(x))
    2*sin(x)*cos(x) + cos(2*x)
    

关于 subs 有两点需要注意。首先,它返回一个新的表达式。SymPy 对象是不可变的。这意味着 subs 不会就地修改它。例如

>>> expr = cos(x)
>>> expr.subs(x, 0)
1
>>> expr
cos(x)
>>> x
x

在这里,我们看到执行 expr.subs(x, 0)expr 保持不变。事实上,由于 SymPy 表达式是不可变的,没有任何函数会就地改变它们。所有函数都将返回新的表达式。

要一次性执行多个替换,请将 (旧, 新) 对的列表传递给 subs

>>> expr = x**3 + 4*x*y - z
>>> expr.subs([(x, 2), (y, 4), (z, 0)])
40

通常将此与列表推导结合使用非常有用,以便一次性完成大量类似的替换。例如,假设我们有 \(x^4 - 4x^3 + 4x^2 - 2x + 3\),我们希望将所有偶数次幂的 \(x\) 替换为 \(y\),以得到 \(y^4 - 4x^3 + 4y^2 - 2x + 3\)

>>> expr = x**4 - 4*x**3 + 4*x**2 - 2*x + 3
>>> replacements = [(x**i, y**i) for i in range(5) if i % 2 == 0]
>>> expr.subs(replacements)
-4*x**3 - 2*x + y**4 + 4*y**2 + 3

将字符串转换为 SymPy 表达式

sympify 函数(即 sympify,不要与 simplify 混淆)可以用来将字符串转换为 SymPy 表达式。

例如

>>> str_expr = "x**2 + 3*x - 1/2"
>>> expr = sympify(str_expr)
>>> expr
x**2 + 3*x - 1/2
>>> expr.subs(x, 2)
19/2

警告

sympify 使用了 eval。不要在未净化的输入上使用它。

evalf

要将数值表达式计算为浮点数,请使用 evalf

>>> expr = sqrt(8)
>>> expr.evalf()
2.82842712474619

SymPy 可以以任意精度计算浮点表达式。默认情况下,使用 15 位精度,但你可以将任意数字作为参数传递给 evalf。让我们计算 \(\pi\) 的前 100 位。

>>> pi.evalf(100)
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068

要在某点对包含符号的表达式进行数值评估,我们可能会先使用 subs 再使用 evalf,但更高效且数值稳定的方法是将替换传递给 evalf 使用 subs 标志,该标志接受一个 Symbol: point 对的字典。

>>> expr = cos(2*x)
>>> expr.evalf(subs={x: 2.4})
0.0874989834394464

有时在表达式求值后,会留下小于所需精度的舍入误差。用户可以通过将 chop 标志设置为 True 来选择删除这些数字。

>>> one = cos(1)**2 + sin(1)**2
>>> (one - 1).evalf()
-0.e-124
>>> (one - 1).evalf(chop=True)
0

lambdify

subsevalf 适合进行简单的评估,但如果你打算在多个点上评估一个表达式,有更高效的方法。例如,如果你想在一个千点上评估一个表达式,使用 SymPy 会比实际需要的慢得多,特别是如果你只关心机器精度。相反,你应该使用像 NumPySciPy 这样的库。

将 SymPy 表达式转换为可进行数值计算的表达式的最简单方法是使用 lambdify 函数。lambdify 的作用类似于 lambda 函数,只不过它将 SymPy 名称转换为给定数值库的名称,通常是 NumPy。例如

>>> import numpy 
>>> a = numpy.arange(10) 
>>> expr = sin(x)
>>> f = lambdify(x, expr, "numpy") 
>>> f(a) 
[ 0.          0.84147098  0.90929743  0.14112001 -0.7568025  -0.95892427
 -0.2794155   0.6569866   0.98935825  0.41211849]

警告

lambdify 使用 eval。不要在未经净化的输入上使用它。

你可以使用其他库而不是 NumPy。例如,要使用标准库的 math 模块,请使用 "math"

>>> f = lambdify(x, expr, "math")
>>> f(0.1)
0.0998334166468

要在 lambdify 中使用它不了解的数值库,请传递一个 sympy_name:numerical_function 对的字典。例如

>>> def mysin(x):
...     """
...     My sine. Note that this is only accurate for small x.
...     """
...     return x
>>> f = lambdify(x, expr, {"sin":mysin})
>>> f(0.1)
0.1