微积分

本节介绍如何在 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 来求解,这会添加常数(参见 教程-dsolve)。

要计算定积分,请传递参数 (积分变量, 下限, 上限)。例如,要计算

\[\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\) 处的朗道阶项(不要与计算机科学中使用的大 O 符号混淆,后者通常表示在 \(x\) 处的朗道阶项,其中 \(x \rightarrow \infty\))。这意味着所有幂次大于或等于 \(x^4\) 的 x 项都被省略。阶项可以在 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)

这里,一阶导数在 x 附近使用最少的点数(1 阶导数为 2 个点)进行近似,这些点等距分布,步长为 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