简化¶
为了使这份文档更易阅读,我们将启用漂亮打印。
>>> 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)])
请注意,factor
和 expand
的输入不一定是严格意义上的多项式。它们将智能地分解或扩展任何类型的表达式(但请注意,如果输入不再是有理数上的多项式,则因子可能不会是不可约的)。
>>> 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)
给出了 expr
中 x**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)
幂¶
在我们介绍幂简化函数之前,需要对幂所满足的恒等式进行数学上的讨论。幂满足三种类型的恒等式
\(x^ax^b = x^{a + b}\)
\(x^ay^a = (xy)^a\)
\((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}}\)。
总结一下
恒等式 |
成立的充分条件 |
条件不满足时的反例 |
重要的后果 |
---|---|---|---|
|
始终为真 |
无 |
无 |
|
\(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}\) 通常情况 |
|
\(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()
来为符号指定不同的假设。在本节的其余部分,我们将假设x
和y
为正数,并且a
和b
为实数。我们将保留z
、t
和c
作为任意复数符号,以演示在这种情况下会发生什么。>>> 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)
对数与幂具有类似的问题。有两个主要标识
\(\log{(xy)} = \log{(x)} + \log{(y)}\)
\(\log{(x^n)} = n\log{(x)}\)
由于复数平面中复数对数的分支割,这两个标识对于任意复数 \(x\) 和 \(y\) 都不成立。但是,标识成立的充分条件是 \(x\) 和 \(y\) 为正,而 \(n\) 为实数。
>>> x, y = symbols('x y', positive=True)
>>> n = symbols('n', real=True)
如前所述,z
和 t
将是没有任何其他假设的符号。
请注意,标识 \(\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 中的一些特殊函数。
让我们将 x
、y
和 z
定义为常规的复数符号,删除我们在上一节中对它们的任何假设。我们还将定义 k
、m
和 n
。
>>> x, y, z = symbols('x y z')
>>> k, m, n = symbols('k m n')
阶乘 函数是 factorial
。 factorial(n)
表示 \(n!= 1\cdot2\cdots(n - 1)\cdot n\)。 \(n!\) 表示 \(n\) 个不同项目的排列数。
>>> factorial(n)
n!
二项式系数 函数是 binomial
。 binomial(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)
广义超几何函数 是 hyper
。 hyper([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, \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')
将创建符号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₄
当然,这项练习似乎毫无意义,因为我们已经知道我们的frac
是list_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}\)中被约简时会发生什么)。