简化¶
为了使本文档更易于阅读,我们将启用漂亮的打印功能。
>>> 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)])
注意,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 包含的特殊函数及其文档的详细列表位于 Functions 模块 页面。
在本教程中,让我们介绍SymPy中的一些特殊函数。
让我们将 x
、y
和 z
定义为常规的、复杂的符号,移除我们在上一节中对它们的任何假设。我们还将定义 k
、m
和 n
。
>>> x, y, z = symbols('x y z')
>>> k, m, n = symbols('k m n')
factorial 函数是 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\) 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)
广义超几何函数 是 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 │ ⎠
重写¶
处理特殊函数的一种常见方法是根据彼此重写它们。这适用于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, \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₄
当然,这个练习似乎毫无意义,因为我们已经知道我们的 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}\) 中被消去时会发生什么)。