微积分

本节介绍如何在 SymPy 中执行基本微积分任务,例如导数、积分、极限和级数展开。如果您不熟悉本节中任何部分的数学知识,可以跳过。

>>> from sympy import *
>>> x, y, z = symbols('x y z')
>>> init_printing(use_unicode=True)

导数

要进行求导,请使用 diff 函数。

>>> diff(cos(x), x)
-sin(x)
>>> diff(exp(x**2), x)
     ⎛ 2⎞
     ⎝x ⎠
2⋅x⋅ℯ

diff 可以一次执行多个求导。要进行多次求导,将变量传递多次,次数与您想要求导的次数相同,或者在变量之后传递一个数字。例如,以下两个示例都将计算 \(x^4\) 的三阶导数。

>>> diff(x**4, x, x, x)
24⋅x
>>> diff(x**4, x, 3)
24⋅x

您也可以对多个变量进行求导。只需按顺序传递每个导数,使用与单变量导数相同的语法。例如,以下每个示例都将计算 \(\frac{\partial^7}{\partial x\partial y^2\partial z^4} e^{x y z}\)

>>> expr = exp(x*y*z)
>>> diff(expr, x, y, y, z, z, z, z)
 3  2 ⎛ 3  3  3       2  2  2                ⎞  x⋅y⋅z
x ⋅y ⋅⎝x ⋅y ⋅z  + 14⋅x ⋅y ⋅z  + 52⋅x⋅y⋅z + 48⎠⋅ℯ
>>> diff(expr, x, y, 2, z, 4)
 3  2 ⎛ 3  3  3       2  2  2                ⎞  x⋅y⋅z
x ⋅y ⋅⎝x ⋅y ⋅z  + 14⋅x ⋅y ⋅z  + 52⋅x⋅y⋅z + 48⎠⋅ℯ
>>> diff(expr, x, y, y, z, 4)
 3  2 ⎛ 3  3  3       2  2  2                ⎞  x⋅y⋅z
x ⋅y ⋅⎝x ⋅y ⋅z  + 14⋅x ⋅y ⋅z  + 52⋅x⋅y⋅z + 48⎠⋅ℯ

diff 也可作为方法调用。两种调用 diff 的方式完全相同,仅为方便起见提供。

>>> expr.diff(x, y, y, z, 4)
 3  2 ⎛ 3  3  3       2  2  2                ⎞  x⋅y⋅z
x ⋅y ⋅⎝x ⋅y ⋅z  + 14⋅x ⋅y ⋅z  + 52⋅x⋅y⋅z + 48⎠⋅ℯ

要创建未计算的导数,请使用 Derivative 类。它的语法与 diff 相同。

>>> deriv = Derivative(expr, x, y, y, z, 4)
>>> deriv
     7
    ∂     ⎛ x⋅y⋅z⎞
──────────⎝ℯ     ⎠
  4   2
∂z  ∂y  ∂x

要计算未计算的导数,请使用 doit 方法。

>>> deriv.doit()
 3  2 ⎛ 3  3  3       2  2  2                ⎞  x⋅y⋅z
x ⋅y ⋅⎝x ⋅y ⋅z  + 14⋅x ⋅y ⋅z  + 52⋅x⋅y⋅z + 48⎠⋅ℯ

这些未计算的对象对于延迟导数的计算或打印目的很有用。它们还用于 SymPy 不知道如何计算表达式的导数时(例如,如果它包含未定义的函数,这些函数在 求解微分方程 部分中描述)。

可以使用元组 (x, n) 创建未指定阶数的导数,其中 n 是相对于 x 的导数阶数。

>>> m, n, a, b = symbols('m n a b')
>>> expr = (a*x + b)**m
>>> expr.diff((x, n))
  n
 ∂ ⎛         m⎞
───⎝(a⋅x + b) ⎠
  n
∂x

积分

要计算积分,请使用 integrate 函数。积分有两种类型:定积分和不定积分。要计算不定积分,即反导数或原函数,只需在表达式后传递变量。

>>> integrate(cos(x), x)
sin(x)

请注意,SymPy 不包含积分常数。如果你需要它,可以自己添加一个,或者将你的问题重新表述为微分方程,并使用 dsolve 来求解它,这将添加常数(参见 求解微分方程)。

要计算定积分,请传递参数 (integration_variable, lower_limit, upper_limit)。例如,要计算

\[\int_0^\infty e^{-x}\,dx,\]

我们将执行以下操作

>>> integrate(exp(-x), (x, 0, oo))
1

与不定积分一样,你可以传递多个极限元组来执行多重积分。例如,要计算

\[\int_{-\infty}^{\infty}\int_{-\infty}^{\infty} e^{- x^{2} - y^{2}}\, dx\, dy,\]

执行以下操作

>>> integrate(exp(-x**2 - y**2), (x, -oo, oo), (y, -oo, oo))
π

如果 integrate 无法计算积分,它将返回一个未计算的 Integral 对象。

>>> expr = integrate(x**x, x)
>>> print(expr)
Integral(x**x, x)
>>> expr

⎮  x
⎮ x  dx

Derivative 一样,你可以使用 Integral 创建未计算的积分。要稍后计算此积分,请调用 doit

>>> expr = Integral(log(x)**2, x)
>>> expr

⎮    2
⎮ log (x) dx

>>> expr.doit()
         2
x⋅log (x) - 2⋅x⋅log(x) + 2⋅x

integrate 使用功能强大的算法来计算定积分和不定积分,这些算法一直在改进,包括启发式模式匹配类型算法、Risch 算法 的部分实现,以及使用 Meijer G 函数 的算法,该算法可用于计算以特殊函数表示的积分,尤其是定积分。以下是一些 integrate 功能强大的示例。

>>> integ = Integral((x**4 + x**2*exp(x) - x**2 - 2*x*exp(x) - 2*x -
...     exp(x))*exp(x)/((x - 1)**2*(x + 1)**2*(exp(x) + 1)), x)
>>> integ

⎮ ⎛ 4    2  x    2        x          x⎞  x
⎮ ⎝x  + x ⋅ℯ  - x  - 2⋅x⋅ℯ  - 2⋅x - ℯ ⎠⋅ℯ
⎮ ──────────────────────────────────────── dx
⎮               2        2 ⎛ x    ⎞
⎮        (x - 1) ⋅(x + 1) ⋅⎝ℯ  + 1⎠

>>> integ.doit()
                 x
   ⎛ x    ⎞     ℯ
log⎝ℯ  + 1⎠ + ──────
               2
              x  - 1
>>> integ = Integral(sin(x**2), x)
>>> integ

⎮    ⎛ 2⎞
⎮ sin⎝x ⎠ dx

>>> integ.doit()
         ⎛√2⋅x⎞
3⋅√2⋅√π⋅S⎜────⎟⋅Γ(3/4)
         ⎝ √π ⎠
──────────────────────
       8⋅Γ(7/4)
>>> integ = Integral(x**y*exp(-x), (x, 0, oo))
>>> integ


⎮  y  -x
⎮ x ⋅ℯ   dx

0
>>> integ.doit()
⎧ Γ(y + 1)    for re(y) > -1

⎪∞
⎪⌠
⎨⎮  y  -x
⎪⎮ x ⋅ℯ   dx    otherwise
⎪⌡
⎪0

最后一个示例返回了一个 Piecewise 表达式,因为积分只有在 \(\Re(y) > -1.\) 时才会收敛。

数值积分

数值积分是数学分析中的一种方法,用于估计函数在简化范围内的定积分。SymPy 不仅支持符号积分,还支持数值积分。它利用 mpmath 库的精度功能来提高数值积分计算的精度。

>>> from sympy import Integral, Symbol, sqrt
>>> x = Symbol('x')
>>> integral = Integral(sqrt(2)*x, (x, 0, 1))
>>> integral
1

⎮ √2⋅x dx

0
>>> integral.evalf()
0.707106781186548

要以指定精度计算积分

>>> integral.evalf(50)
0.70710678118654752440084436210484903928483593768847

当符号积分不切实际或不可能时,数值积分成为一种可行的方法。这种方法允许通过数值技术计算积分,即使在处理无限区间或被积函数时也是如此。

>>> Integral(exp(-(x ** 2)), (x, -oo, oo)).evalf()
1.77245385090552
>>> Integral(1 / sqrt(x), (x, 0, 1)).evalf()
2.00000000000000

极限

SymPy 可以使用 limit 函数计算符号极限。计算

\[\lim_{x\to x_0} f(x)\]

的语法是 limit(f(x), x, x0)

>>> limit(sin(x)/x, x, 0)
1

只要评估点是奇点,就应该使用 limit 而不是 subs。即使 SymPy 有对象来表示 \(\infty\),使用它们进行评估也不可靠,因为它们不跟踪诸如增长率之类的东西。此外,诸如 \(\infty - \infty\)\(\frac{\infty}{\infty}\) 之类的东西返回 \(\mathrm{nan}\)(非数字)。例如

>>> expr = x**2/exp(x)
>>> expr.subs(x, oo)
nan
>>> limit(expr, x, oo)
0

DerivativeIntegral 一样,limit 有一个未计算的对应项,即 Limit。要计算它,请使用 doit

>>> expr = Limit((cos(x) - 1)/x, x, 0)
>>> expr
     ⎛cos(x) - 1⎞
 lim ⎜──────────⎟
x─→0⁺⎝    x     ⎠
>>> expr.doit()
0

要仅在一侧评估极限,请将 '+''-' 作为第四个参数传递给 limit。例如,要计算

\[\lim_{x\to 0^+}\frac{1}{x},\]

执行以下操作

>>> limit(1/x, x, 0, '+')

与以下不同

>>> limit(1/x, x, 0, '-')
-∞

级数展开

SymPy 可以计算函数在某一点周围的渐近级数展开式。要计算 \(f(x)\) 在点 \(x = x_0\) 附近以 \(x^n\) 阶展开的表达式,请使用 f(x).series(x, x0, n)。可以省略 x0n,在这种情况下,将使用默认值 x0=0n=6

>>> expr = exp(sin(x))
>>> expr.series(x, 0, 4)
         2
        x     ⎛ 4⎞
1 + x + ── + O⎝x ⎠
        2

末尾的 \(O\left(x^4\right)\) 项表示 \(x=0\) 处的 Landau 阶项(不要与计算机科学中使用的大 O 符号混淆,后者通常表示 \(x\) 处的 Landau 阶项,其中 \(x \rightarrow \infty\))。这意味着所有 x 项的幂大于或等于 \(x^4\) 都被省略了。可以在 series 之外创建和操作阶项。它们会自动吸收高阶项。

>>> x + x**3 + x**6 + O(x**4)
     3    ⎛ 4⎞
x + x  + O⎝x ⎠
>>> x*O(1)
O(x)

如果你不想要阶项,请使用 removeO 方法。

>>> expr.series(x, 0, 4).removeO()
 2
x
── + x + 1
2

O 符号支持任意极限点(除 0 之外)

>>> exp(x - 6).series(x, x0=6)
            2          3          4          5
     (x - 6)    (x - 6)    (x - 6)    (x - 6)         ⎛       6       ⎞
-5 + ──────── + ──────── + ──────── + ──────── + x + O⎝(x - 6) ; x → 6⎠
        2          6          24        120

有限差分

到目前为止,我们已经研究了具有解析导数和原函数的表达式。但是,如果我们想要一个表达式来估计曲线的导数,而我们没有它的闭合形式表示,或者我们还没有知道它的函数值,该怎么办?一种方法是使用有限差分方法。

使用有限差分进行微分最简单的方法是使用 differentiate_finite 函数

>>> f, g = symbols('f g', cls=Function)
>>> differentiate_finite(f(x)*g(x))
-f(x - 1/2)⋅g(x - 1/2) + f(x + 1/2)⋅g(x + 1/2)

如果你已经有一个 Derivative 实例,你可以使用 as_finite_difference 方法生成任意阶导数的近似值

>>> f = Function('f')
>>> dfdx = f(x).diff(x)
>>> dfdx.as_finite_difference()
-f(x - 1/2) + f(x + 1/2)

这里,使用最小点数(1 阶导数为 2 个点)在 x 周围用步长为 1 的等距方式进行评估,对一阶导数进行近似。我们可以使用任意步长(可能包含符号表达式)

>>> f = Function('f')
>>> d2fdx2 = f(x).diff(x, 2)
>>> h = Symbol('h')
>>> d2fdx2.as_finite_difference([-3*h,-h,2*h])
f(-3⋅h)   f(-h)   2⋅f(2⋅h)
─────── - ───── + ────────
     2        2        2
  5⋅h      3⋅h     15⋅h

如果你只对评估权重感兴趣,可以手动执行此操作

>>> finite_diff_weights(2, [-3, -1, 2], 0)[-1][-1]
[1/5, -1/3, 2/15]

请注意,我们只需要 finite_diff_weights 返回的最后一个子列表中的最后一个元素。原因是该函数还会生成用于较低阶导数和使用较少点的权重(有关详细信息,请参见 finite_diff_weights 的文档)。

如果直接使用 finite_diff_weights 看起来很复杂,而 Derivative 实例的 as_finite_difference 方法不够灵活,则可以使用 apply_finite_diff,它以 orderx_listy_listx0 作为参数

>>> x_list = [-3, 1, 2]
>>> y_list = symbols('a b c')
>>> apply_finite_diff(1, x_list, y_list, 0)
  3⋅a   b   2⋅c
- ─── - ─ + ───
   20   4    5