基本操作

这里我们将讨论在 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 开始,并将 y 替换为 x**y。然后我们将得到 x**(x**y)。如果我们在这个新表达式中将 y 替换为 x**x,我们将得到 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 表达式是不可变的,因此任何函数都不会就地更改它们。所有函数都会返回新的表达式。

要一次执行多个替换,请将 (old, new) 对的列表传递给 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\),我们想用 \(y\) 替换所有偶数次方的 \(x\),得到 \(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,但更有效且数值上更稳定的是使用 subs 标志将替换传递给 evalf,该标志接受 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"

>>> 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