简化

为了使这份文档更易阅读,我们将启用漂亮打印。

>>> 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)\),即 伽马函数。我们可以看到 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 中包含的特殊函数及其文档的完整列表位于 函数模块 页面。

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

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

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

阶乘 函数是 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\)\(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   │  ⎠

rewrite

处理特殊函数的一种常见方法是将它们改写成彼此的形式。这适用于 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 函数(有关更多信息,请参见 its documentation)。

>>> 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),以便即使我们只传入 Python int,结果也将始终是 SymPy 对象。

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

每个有限连分数都是有理数,但我们在这里关注符号,因此让我们创建一个符号连分数。我们一直在使用的symbols()函数有一个快捷方式来创建编号符号。symbols('a0:5')将创建符号a0a1,…,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}\)中被约简时会发生什么)。