简化

为了使本文档更易于阅读,我们将启用漂亮的打印功能。

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

simplify

现在让我们开始做一些有趣的数学。符号操作系统的最有用功能之一是能够简化数学表达式。SymPy 有数十个函数来执行各种类型的简化。还有一个名为 simplify() 的通用函数,它会尝试以智能的方式应用所有这些函数,以达到表达式的最简形式。以下是一些例子

>>> simplify(sin(x)**2 + cos(x)**2)
1
>>> simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1))
x - 1
>>> simplify(gamma(x)/gamma(x - 2))
(x - 2)⋅(x - 1)

这里,gamma(x)\(\Gamma(x)\),即 gamma 函数。我们看到 simplify() 能够处理一大类表达式。

simplify() 有一个陷阱。它只是应用了 SymPy 中的所有主要简化操作,并使用启发式方法来确定最简单的结果。但“最简单”并不是一个定义明确的术语。例如,假设我们想将 \(x^2 + 2x + 1\) “简化”为 \((x + 1)^2\)

>>> simplify(x**2 + 2*x + 1)
 2
x  + 2⋅x + 1

我们没有得到我们想要的结果。有一个函数可以执行这种简化,称为 factor(),将在下面讨论。

simplify() 的另一个陷阱是它可能不必要地慢,因为它在选择最佳简化之前尝试了许多种简化。如果你已经确切知道你需要的简化类型,最好应用那些执行这些简化的特定简化函数。

应用特定的简化函数而不是 simplify() 也有一个优势,即特定函数对其输出的形式有一定的保证。这些将在下面每个函数中讨论。例如,factor() 在多项式上调用时,保证将多项式分解为不可约因子。simplify() 没有任何保证。它完全是启发式的,而且,正如我们上面所看到的,它甚至可能错过 SymPy 能够执行的某种简化。

simplify() 最适合在交互式使用时,当你只想将一个表达式简化为更简单的形式时。然后,你可以选择在看到 simplify() 返回的结果后,应用特定的函数以获得更精确的结果。当你不知道一个表达式会呈现什么形式,并且你需要一个全能函数来简化它时,它也非常有用。

多项式/有理函数简化

扩展

expand() 是 SymPy 中最常见的简化函数之一。尽管它的应用范围很广,但目前我们将考虑它在展开多项式表达式中的功能。例如:

>>> expand((x + 1)**2)
 2
x  + 2⋅x + 1
>>> expand((x + 2)*(x - 3))
 2
x  - x - 6

给定一个多项式,expand() 会将其转换为单项式和的标准形式。

expand() 可能听起来不像是一个简化函数。毕竟,从它的名字来看,它使表达式变大,而不是变小。通常情况下是这样的,但通常在对其调用 expand() 后,表达式会因为抵消而变小。

>>> expand((x + 1)*(x - 2) - (x - 1)*x)
-2

因子

factor() 接受一个多项式,并将其分解为有理数上的不可约因子。例如:

>>> factor(x**3 - x**2 + x - 1)
        ⎛ 2    ⎞
(x - 1)⋅⎝x  + 1⎠
>>> factor(x**2*z + 4*x*y*z + 4*y**2*z)
           2
z⋅(x + 2⋅y)

对于多项式,factor()expand() 的反操作。factor() 使用了一个完整的多元有理数分解算法,这意味着 factor() 返回的每个因子都保证是不可约的。

如果你对因子本身感兴趣,factor_list 返回一个更有结构的输出。

>>> factor_list(x**2*z + 4*x*y*z + 4*y**2*z)
(1, [(z, 1), (x + 2⋅y, 2)])

注意,factorexpand 的输入不一定是严格意义上的多项式。它们会智能地分解或展开任何类型的表达式(尽管需要注意的是,如果输入不再是关于有理数的多项式,因子可能不是不可约的)。

>>> expand((cos(x) + sin(x))**2)
   2                           2
sin (x) + 2⋅sin(x)⋅cos(x) + cos (x)
>>> factor(cos(x)**2 + 2*cos(x)*sin(x) + sin(x)**2)
                 2
(sin(x) + cos(x))

收集

collect() 收集表达式中某一项的常见幂次。例如

>>> expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3
>>> expr
 3    2        2
x  - x ⋅z + 2⋅x  + x⋅y + x - 3
>>> collected_expr = collect(expr, x)
>>> collected_expr
 3    2
x  + x ⋅(2 - z) + x⋅(y + 1) - 3

collect() 在与 .coeff() 方法结合使用时特别有用。expr.coeff(x, n) 给出 exprx**n 的系数:

>>> collected_expr.coeff(x, 2)
2 - z

取消

cancel() 将接受任何有理函数并将其转换为标准规范形式 \(\frac{p}{q}\),其中 \(p\)\(q\) 是展开的多项式,没有公因子,且 \(p\)\(q\) 的首项系数没有分母(即,是整数)。

>>> cancel((x**2 + 2*x + 1)/(x**2 + x))
x + 1
─────
  x
>>> expr = 1/x + (3*x/2 - 2)/(x - 4)
>>> expr
3⋅x
─── - 2
 2        1
─────── + ─
 x - 4    x
>>> cancel(expr)
   2
3⋅x  - 2⋅x - 8
──────────────
     2
  2⋅x  - 8⋅x
>>> expr = (x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1)
>>> expr
   2                2    2            2
x⋅y  - 2⋅x⋅y⋅z + x⋅z  + y  - 2⋅y⋅z + z
───────────────────────────────────────
                  2
                 x  - 1
>>> cancel(expr)
 2            2
y  - 2⋅y⋅z + z
───────────────
     x - 1

请注意,由于 factor() 将完全分解表达式的分子和分母,因此它也可以用于同样的事情:

>>> factor(expr)
       2
(y - z)
────────
 x - 1

然而,如果你只关心确保表达式处于取消形式,cancel()factor() 更高效。

分开

apart() 对一个有理函数执行 部分分式分解

>>> expr = (4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x)
>>> expr
   3       2
4⋅x  + 21⋅x  + 10⋅x + 12
────────────────────────
  4      3      2
 x  + 5⋅x  + 5⋅x  + 4⋅x
>>> apart(expr)
 2⋅x - 1       1     3
────────── - ───── + ─
 2           x + 4   x
x  + x + 1

三角函数简化

备注

SymPy 遵循 Python 的命名约定来命名反三角函数,即在函数名前加上一个 a。例如,反余弦,或弧余弦,被称为 acos()

>>> acos(x)
acos(x)
>>> cos(acos(x))
x
>>> asin(1)
π

2

trigsimp

要使用三角恒等式简化表达式,请使用 trigsimp()

>>> trigsimp(sin(x)**2 + cos(x)**2)
1
>>> trigsimp(sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4)
cos(4⋅x)   1
──────── + ─
   2       2
>>> trigsimp(sin(x)*tan(x)/sec(x))
   2
sin (x)

trigsimp() 也适用于双曲三角函数。

>>> trigsimp(cosh(x)**2 + sinh(x)**2)
cosh(2⋅x)
>>> trigsimp(sinh(x)/tanh(x))
cosh(x)

simplify() 类似,trigsimp() 对输入表达式应用各种三角恒等式,然后使用启发式方法返回“最佳”结果。

expand_trig

要展开三角函数,即应用和或倍角恒等式,请使用 expand_trig()

>>> expand_trig(sin(x + y))
sin(x)⋅cos(y) + sin(y)⋅cos(x)
>>> expand_trig(tan(2*x))
  2⋅tan(x)
───────────
       2
1 - tan (x)

因为 expand_trig() 倾向于使三角表达式变大,而 trigsimp() 倾向于使它们变小,所以可以使用 trigsimp() 反向应用这些恒等式。

>>> trigsimp(sin(x)*cos(y) + sin(y)*cos(x))
sin(x + y)

力量

在我们介绍幂简化函数之前,有必要对幂所持有的恒等式进行数学讨论。幂满足三种恒等式

  1. \(x^ax^b = x^{a + b}\)

  2. \(x^ay^a = (xy)^a\)

  3. \((x^a)^b = x^{ab}\)

身份 1 总是为真。

恒等式 2 并不总是成立。例如,若 \(x = y = -1\)\(a = \frac{1}{2}\),则 \(x^ay^a = \sqrt{-1}\sqrt{-1} = i\cdot i = -1\),而 \((xy)^a = \sqrt{-1\cdot-1} = \sqrt{1} = 1\)。然而,恒等式 2 至少在 \(x\)\(y\) 为非负且 \(a\) 为实数时成立(在其他条件下也可能成立)。恒等式 2 失效的一个常见结果是 \(\sqrt{x}\sqrt{y} \neq \sqrt{xy}\)

恒等式 3 并不总是成立。例如,如果 \(x = -1\)\(a = 2\),且 \(b = \frac{1}{2}\),那么 \((x^a)^b = {\left((-1)^2\right)}^{1/2} = \sqrt{1} = 1\)\(x^{ab} = (-1)^{2\cdot1/2} = (-1)^1 = -1\)。然而,当 \(b\) 是整数时,恒等式 3 是成立的(当然,在其他情况下也可能成立)。恒等式 3 失效的两个常见结果是 \(\sqrt{x^2}\neq x\)\(\sqrt{\frac{1}{x}} \neq \frac{1}{\sqrt{x}}\)

总结一下

身份

保持的充分条件

不满足条件时的反例

重要后果

  1. \(x^ax^b = x^{a + b}\)

总是正确

  1. \(x^ay^a = (xy)^a\)

\(x, y \geq 0\)\(a \in \mathbb{R}\)

\((-1)^{1/2}(-1)^{1/2} \neq (-1\cdot-1)^{1/2}\)

\(\sqrt{x}\sqrt{y} \neq \sqrt{xy}\) 一般来说

  1. \((x^a)^b = x^{ab}\)

\(b \in \mathbb{Z}\)

\({\left((-1)^2\right)}^{1/2} \neq (-1)^{2\cdot1/2}\)

\(\sqrt{x^2}\neq x\)\(\sqrt{\frac{1}{x}}\neq\frac{1}{\sqrt{x}}\) 一般来说

这一点很重要,因为默认情况下,如果简化不总是成立,SymPy 不会执行简化操作。

为了使 SymPy 在涉及仅在某些假设下为真的恒等式的简化操作中表现良好,我们需要对我们的符号设置假设。我们将在后面全面讨论假设系统,但目前,我们只需要知道以下内容。

  • 默认情况下,SymPy 符号被假定为复数(\(\mathbb{C}\) 的元素)。也就是说,除非某个表达式对所有复数都成立,否则不会对该表达式进行简化。

  • 可以通过将假设传递给 symbols() 来赋予符号不同的假设。在本节的其余部分,我们将假设 xy 为正,ab 为实数。我们将把 ztc 作为任意复数符号,以展示在这种情况下会发生什么。

    >>> x, y = symbols('x y', positive=True)
    >>> a, b = symbols('a b', real=True)
    >>> z, t, c = symbols('z t c')
    

备注

在 SymPy 中,sqrt(x) 只是 x**Rational(1, 2) 的快捷方式。它们是完全相同的对象。

>>> sqrt(x) == x**Rational(1, 2)
True

powsimp

powsimp() 从左到右应用上述的恒等式1和2。

>>> powsimp(x**a*x**b)
  a + b
 x
>>> powsimp(x**a*y**a)
     a
(x⋅y)

注意 powsimp() 如果简化无效,则会拒绝执行简化操作。

>>> powsimp(t**c*z**c)
 c  c
t ⋅z

如果你知道你想应用这种简化,但不想处理假设,你可以传递 force=True 标志。这将强制简化发生,无论假设如何。

>>> powsimp(t**c*z**c, force=True)
     c
(t⋅z)

请注意,在某些情况下,特别是当指数为整数或有理数,并且恒等式2成立时,它将自动应用。

>>> (z*t)**2
  2  2
 t ⋅z
>>> sqrt(x*y)
 √x⋅√y

这意味着将无法使用 powsimp() 撤销这个身份,因为即使 powsimp() 试图将基数放在一起,它们也会被自动再次分开。

>>> powsimp(z**2*t**2)
  2  2
 t ⋅z
>>> powsimp(sqrt(x)*sqrt(y))
 √x⋅√y

expand_power_exp / expand_power_base

expand_power_exp()expand_power_base() 分别从右到左应用身份1和身份2。

>>> expand_power_exp(x**(a + b))
 a  b
x ⋅x
>>> expand_power_base((x*y)**a)
 a  a
x ⋅y

powsimp() 一样,如果恒等式 2 无效,则不会应用它。

>>> expand_power_base((z*t)**c)
     c
(t⋅z)

powsimp() 一样,你可以使用 force=True 强制展开,而无需调整假设。

>>> expand_power_base((z*t)**c, force=True)
  c  c
 t ⋅z

与身份2一样,如果幂是一个数字,身份1会自动应用,因此不能通过 expand_power_exp() 撤销。

>>> x**2*x**3
  5
 x
>>> expand_power_exp(x**5)
  5
 x

powdenest

powdenest() 从左到右应用恒等式 3。

>>> powdenest((x**a)**b)
 a⋅b
x

如前所述,如果给定的假设下不成立,则不会应用该身份。

>>> powdenest((z**a)**b)
    b
⎛ a⎞
⎝z ⎠

和之前一样,这可以通过 force=True 手动覆盖。

>>> powdenest((z**a)**b, force=True)
 a⋅b
z

指数和对数

备注

在 SymPy 中,如同在 Python 和大多数编程语言中,log 是自然对数,也称为 ln。如果你忘记这一点,SymPy 会自动提供一个别名 ln = log

>>> ln(x)
log(x)

对数与幂有类似的问题。主要有两个恒等式

  1. \(\log{(xy)} = \log{(x)} + \log{(y)}\)

  2. \(\log{(x^n)} = n\log{(x)}\)

对于任意的复数 \(x\)\(y\),这两个等式都不成立,因为复数对数在复平面上的分支切割。然而,如果 \(x\)\(y\) 是正数且 \(n\) 是实数,则这些等式成立的充分条件是满足的。

>>> x, y = symbols('x y', positive=True)
>>> n = symbols('n', real=True)

和之前一样,zt 将是没有任何额外假设的符号。

注意,恒等式 \(\log{\left(\frac{x}{y}\right)} = \log(x) - \log(y)\) 是恒等式1和2的特例,通过 \(\log{\left(\frac{x}{y}\right)} =\) \(\log{\left(x\cdot\frac{1}{y}\right)} =\) \(\log(x) + \log{\left( y^{-1}\right)} =\) \(\log(x) - \log(y)\),因此如果 \(x\)\(y\) 为正数,该恒等式也成立,但在一般情况下可能不成立。

我们还看到 \(\log{\left( e^x \right)} = x\) 来自 \(\log{\left( e^x \right)} = x\log(e) = x\),因此当 \(x\) 是实数时成立(并且可以验证,对于任意复数 \(x\) 一般不成立,例如,\(\log{\left(e^{x + 2\pi i}\right)} = \log{\left(e^x\right)} = x \neq x + 2\pi i\))。

expand_log

要从左到右应用恒等式1和2,请使用 expand_log()。 一如既往,除非恒等式有效,否则不会应用它们。

>>> expand_log(log(x*y))
log(x) + log(y)
>>> expand_log(log(x/y))
log(x) - log(y)
>>> expand_log(log(x**2))
2⋅log(x)
>>> expand_log(log(x**n))
n⋅log(x)
>>> expand_log(log(z*t))
log(t⋅z)

powsimp()powdenest() 一样,expand_log() 也有一个 force 选项,可以用来忽略假设。

>>> expand_log(log(z**2))
   ⎛ 2⎞
log⎝z ⎠
>>> expand_log(log(z**2), force=True)
2⋅log(z)

logcombine

要应用从右到左的恒等式1和2,请使用 logcombine()

>>> logcombine(log(x) + log(y))
log(x⋅y)
>>> logcombine(n*log(x))
   ⎛ n⎞
log⎝x ⎠
>>> logcombine(n*log(z))
n⋅log(z)

logcombine() 还有一个 force 选项,可以用来忽略假设。

>>> logcombine(n*log(z), force=True)
   ⎛ n⎞
log⎝z ⎠

特殊函数

SymPy 实现了数十种特殊函数,涵盖了从组合学到数学物理的函数。

SymPy 包含的特殊函数及其文档的详细列表位于 Functions 模块 页面。

在本教程中,让我们介绍SymPy中的一些特殊函数。

让我们将 xyz 定义为常规的、复杂的符号,移除我们在上一节中对它们的任何假设。我们还将定义 kmn

>>> x, y, z = symbols('x y z')
>>> k, m, n = symbols('k m n')

factorial 函数是 factorialfactorial(n) 表示 \(n!= 1\cdot2\cdots(n - 1)\cdot n\)\(n!\) 表示 \(n\) 个不同项的排列数。

>>> factorial(n)
n!

二项式系数 函数是 binomialbinomial(n, k) 表示 \(\binom{n}{k}\),即从 \(n\) 个不同的项目中选择 \(k\) 个项目的方式数。它也常写作 \(nCk\),并读作“\(n\) choose \(k\)”。

>>> binomial(n, k)
⎛n⎞
⎜ ⎟
⎝k⎠

阶乘函数与 伽玛函数 gamma 密切相关。gamma(z) 表示 \(\Gamma(z) = \int_0^\infty t^{z - 1}e^{-t}\,dt\),对于正整数 \(z\) 来说,它等同于 \((z - 1)!\)

>>> gamma(z)
Γ(z)

广义超几何函数hyperhyper([a_1, ..., a_p], [b_1, ..., b_q], z) 表示 \({}_pF_q\left(\begin{matrix} a_1, \cdots, a_p \\ b_1, \cdots, b_q \end{matrix} \middle| z \right)\)。 最常见的情况是 \({}_2F_1\),通常被称为 普通超几何函数

>>> hyper([1, 2], [3], z)
 ┌─  ⎛1, 2 │  ⎞
 ├─  ⎜     │ z⎟
2╵ 1 ⎝ 3   │  ⎠

重写

处理特殊函数的一种常见方法是根据彼此重写它们。这适用于SymPy中的任何函数,而不仅仅是特殊函数。要将表达式重写为某个函数的形式,请使用 expr.rewrite(function)。例如,

>>> tan(x).rewrite(cos)
   ⎛    π⎞
cos⎜x - ─⎟
   ⎝    2⎠
──────────
  cos(x)
>>> factorial(x).rewrite(gamma)
Γ(x + 1)

关于应用更精准重写的技巧,请参阅 高级表达式操作 部分。

expand_func

要根据某些恒等式扩展特殊函数,请使用 expand_func()。例如

>>> expand_func(gamma(x + 3))
x⋅(x + 1)⋅(x + 2)⋅Γ(x)

hyperexpand

要使用更标准的函数重写 hyper,请使用 hyperexpand()

>>> hyperexpand(hyper([1, 1], [2], z))
-log(1 - z)
────────────
     z

hyperexpand() 也可以用于更一般的 Meijer G-函数(更多信息请参见 其文档)。

>>> expr = meijerg([[1],[1]], [[1],[]], -z)
>>> expr
╭─╮1, 1 ⎛1  1 │   ⎞
│╶┐     ⎜     │ -z⎟
╰─╯2, 1 ⎝1    │   ⎠
>>> hyperexpand(expr)
 1

 z

combsimp

要简化组合表达式,请使用 combsimp()

>>> n, k = symbols('n k', integer = True)
>>> combsimp(factorial(n)/factorial(n - 3))
n⋅(n - 2)⋅(n - 1)
>>> combsimp(binomial(n+1, k+1)/binomial(n, k))
n + 1
─────
k + 1

gammasimp

要简化带有非整数参数的伽马函数或组合函数的表达式,请使用 gammasimp()

>>> gammasimp(gamma(x)*gamma(1 - x))
   π
────────
sin(π⋅x)

示例:连分数

让我们使用 SymPy 来探索连分数。连分数 是一种形式的表达式

\[a_0 + \cfrac{1}{a_1 + \cfrac{1}{a_2 + \cfrac{1}{ \ddots + \cfrac{1}{a_n} }}}\]

其中 \(a_0, \ldots, a_n\) 是整数,且 \(a_1, \ldots, a_n\) 是正数。连分数也可以是无限的,但无限对象在计算机中更难表示,因此我们这里只研究有限的情况。

上述形式的连分数通常表示为一个列表 \([a_0; a_1, \ldots, a_n]\)。 让我们编写一个简单的函数,将这样的列表转换为其连分数形式。 从列表构造连分数的最简单方法是反向工作。 请注意,尽管定义看似对称,但第一个元素 \(a_0\) 通常必须与其他元素区别对待。

>>> def list_to_frac(l):
...     expr = Integer(0)
...     for i in reversed(l[1:]):
...         expr += i
...         expr = 1/expr
...     return l[0] + expr
>>> list_to_frac([x, y, z])
      1
x + ─────
        1
    y + ─
        z

我们在 list_to_frac 中使用 Integer(0),以便结果始终是一个 SymPy 对象,即使我们只传入 Python 的 int 类型。

>>> list_to_frac([1, 2, 3, 4])
43
──
30

每个有限连分数都是一个有理数,但我们在这里关注的是符号,所以让我们创建一个符号连分数。我们一直在使用的 symbols() 函数有一个快捷方式来创建编号的符号。 symbols('a0:5') 将创建符号 a0, a1, …, a4

>>> syms = symbols('a0:5')
>>> syms
(a₀, a₁, a₂, a₃, a₄)
>>> a0, a1, a2, a3, a4 = syms
>>> frac = list_to_frac(syms)
>>> frac
             1
a₀ + ─────────────────
               1
     a₁ + ────────────
                  1
          a₂ + ───────
                    1
               a₃ + ──
                    a₄

这个形式对于理解连分数很有用,但让我们使用 cancel() 将其转换为标准的分数函数形式。

>>> frac = cancel(frac)
>>> frac
a₀⋅a₁⋅a₂⋅a₃⋅a₄ + a₀⋅a₁⋅a₂ + a₀⋅a₁⋅a₄ + a₀⋅a₃⋅a₄ + a₀ + a₂⋅a₃⋅a₄ + a₂ + a₄
─────────────────────────────────────────────────────────────────────────
                 a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1

现在假设我们得到了上述约简形式的 frac。实际上,我们可能会以任何形式得到分数,但我们可以始终使用 cancel() 将其转换为上述规范形式。假设我们知道它可以被重写为连分数。我们如何用 SymPy 做到这一点?连分数是递归的 \(c + \frac{1}{f}\),其中 \(c\) 是一个整数,\(f\) 是一个(更小的)连分数。如果我们能以这种形式写出表达式,我们可以递归地提取每个 \(c\) 并将其添加到列表中。然后我们可以使用我们的 list_to_frac() 函数得到一个连分数。

这里的关键观察是我们可以通过对 \(c\) 进行部分分式分解,将一个表达式转换为 \(c + \frac{1}{f}\) 的形式。这是因为 \(f\) 不包含 \(c\)。这意味着我们需要使用 apart() 函数。我们使用 apart() 来提取项,然后从表达式中减去它,并取倒数以得到 \(f\) 部分。

>>> l = []
>>> frac = apart(frac, a0)
>>> frac
                a₂⋅a₃⋅a₄ + a₂ + a₄
a₀ + ───────────────────────────────────────
     a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1
>>> l.append(a0)
>>> frac = 1/(frac - a0)
>>> frac
a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1
───────────────────────────────────────
           a₂⋅a₃⋅a₄ + a₂ + a₄

现在我们重复这个过程

>>> frac = apart(frac, a1)
>>> frac
         a₃⋅a₄ + 1
a₁ + ──────────────────
     a₂⋅a₃⋅a₄ + a₂ + a₄
>>> l.append(a1)
>>> frac = 1/(frac - a1)
>>> frac = apart(frac, a2)
>>> frac
         a₄
a₂ + ─────────
     a₃⋅a₄ + 1
>>> l.append(a2)
>>> frac = 1/(frac - a2)
>>> frac = apart(frac, a3)
>>> frac
     1
a₃ + ──
     a₄
>>> l.append(a3)
>>> frac = 1/(frac - a3)
>>> frac = apart(frac, a4)
>>> frac
a₄
>>> l.append(a4)
>>> list_to_frac(l)
             1
a₀ + ─────────────────
               1
     a₁ + ────────────
                  1
          a₂ + ───────
                    1
               a₃ + ──
                    a₄

当然,这个练习似乎毫无意义,因为我们已经知道我们的 fraclist_to_frac([a0, a1, a2, a3, a4])。所以尝试以下练习。取一个符号列表并随机化它们,创建被取消的连分数,看看你是否能重现原始列表。例如

>>> import random
>>> l = list(symbols('a0:5'))
>>> random.shuffle(l)
>>> orig_frac = frac = cancel(list_to_frac(l))
>>> del l

在 SymPy 中,以上述示例为基础,尝试从 frac 中重新生成 l。我已经删除了末尾的 l,以避免偷看(你可以在最后通过调用 cancel(list_to_frac(l)) 来检查你的答案,并将其与你生成的列表进行比较,与 orig_frac 进行对比。)

看看你是否能想出一个方法,在每个阶段确定要传递给 apart() 的符号(提示:思考当 \(a_0\) 在公式 \(a_0 + \frac{1}{a_1 + \cdots}\) 中被消去时会发生什么)。