3b1b / 3b1b/manim

Value Error [Errno 27] raised when combining to movie file

Đang mở
#2,366 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
bug
Ngôn ngữ chính
Python
Star
93.8k
Fork
7.7k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

### Describe the bug
Value Error [Errno 27] raised when combining to movie file.
The bug seems to be resolution related. No problems when using the -pql parameter.
**Code**:
```
from manim import *
import math
import numpy as np

class QuinticUnsolvability(Scene):
def construct(self):
# self.next_section(skip_animations=True)
# 台词:这个视频中,我们将不用伽罗瓦理论来证明五次方程的不可解性。你需要知道复数相关的知识、代数基本定理。
text1 = Text(r"韦达定理、复数相关知识(欧拉公式、复数运算)").move_to(UP)
text2 = Text(r"代数基本定理:一元n次方程恰好有n个复数根").move_to(DOWN)
self.play(Write(text1), Write(text2))
self.wait(4)
self.play(FadeOut(text1), FadeOut(text2))


text = Text("Chapter 0: 复数开根", font_size=80)
self.play(FadeIn(text))
self.wait(2)
self.play(FadeOut(text))

# 台词:我们知道,对一个数x进行开n次方根,实际上就是在找一个数y,使得y的n次方等于x。比如说,我们对2开立方根。
text = MathTex(r"\sqrt[n]{x} = y \Leftrightarrow y^n = x")
self.play(Write(text))
self.wait(3)
self.play(Transform(text, MathTex(r"\sqrt[3]{2} = y \Leftrightarrow y^3 = 2")))
# 台词:在实数域内,这个方程只有一个根,2的立方根只有一个值。但是,根据代数基本定理,这个三次方程一定有3个根。既然实数域内只有一个根,那么剩下的两个根一定是复数根。它们在哪里呢?
self.wait(12)
self.play(FadeOut(text))

# 台词:我们假设2的立方根是一个复数z,它与x轴成夹角theta。我们可以用欧拉公式将其写成这样。
text = MathTex(r"z = re^{i\theta}").shift(UP * 2)
self.play(Write(text))
self.wait(3)
# 现在,z的立方就是这样,它等于2。
text2 = MathTex(r"z^3 = r^3 e^{i3\theta}").shift(UP * 0.5)
self.play(Write(text2))
self.wait(3)
text3 = MathTex(r"=2").next_to(text2, RIGHT)
self.play(Write(text3))
# 我们把2也看作一个复数,用欧拉公式表示。注意辐角可能等于2k派,其中k是任意整数。
text4 = MathTex(r"z^3=r^3 e^{i3\theta}=2\cdot e^{i\cdot 2k\pi},k\in\mathbb{Z}").shift(DOWN * 1)
self.play(Write(text4))
self.play(FadeOut(text), FadeOut(text2), FadeOut(text3), text4.animate.move_to(UP * 2))
self.wait(3)
# 分别让等式两边的模长、幅角相等。
text5 = MathTex(r"r^3=2, 3\theta=0+2k\pi, k\in\mathbb{Z}").shift(UP * 0.5)
self.play(Write(text5))
self.wait(2)

# 由此,我们可以得到r和theta的值。
text6 = MathTex(r"r=\sqrt[3]{2}, \theta=\frac{2k\pi}{3}, k\in\mathbb{Z}").shift(DOWN * 1)
self.play(Write(text6))
self.wait(3)

# 台词:现在,我们可以得到2的立方根的三个值了。它们分别对应这3个红点。
plane = ComplexPlane(
x_range=[-12, 12, 1],
y_range=[-6, 6, 1],
background_line_style={
"stroke_color": BLUE,
"stroke_width": 1,
"stroke_opacity": 0.3
}
).scale(0.8)
plane.add_coordinates()
self.play(Create(plane), FadeOut(text4), FadeOut(text5), text6.animate.move_to(UP * 2))
z = Dot(plane.c2p(2), color = YELLOW)
self.play(Create(z))
def get_roots(z, n):
roots = []
unit = math.cos(2 * math.pi / n) + math.sin(2 * math.pi / n) * 1j
for k in range(n):
root = z ** (1/n) * (unit ** k)
roots.append(root)
return roots
roots = VGroup(*[
Dot(color=RED).move_to(plane.n2p(pos)) for pos in get_roots(2, 3)
])
self.play(Create(roots))
self.wait(2)

# 台词:当我们移动黄点时,红点也会跟着移动。当黄点绕原点转一圈时,红点会转三分之一圈。这是因为黄点的辐角增加了2派,它的三次方根的辐角就会增加2派的三分之一。
def update_roots(mob):
new_roots = get_roots(plane.p2n(z.get_center()), 3)
for dot, pos in zip(mob, new_roots):
dot.move_to(plane.n2p(pos))
roots.add_updater(update_roots)
self.play(z.animate.move_to(plane.c2p(2, 2)), FadeOut(text6))
self.play(z.animate.move_to(plane.c2p(-2, 2)))
self.play(z.animate.move_to(plane.c2p(-2, -2)))
self.play(z.animate.move_to(plane.c2p(2, -2)))
self.play(z.animate.move_to(plane.c2p(2, 0)))
self.play(FadeOut(z), FadeOut(roots), FadeOut(plane))
for mob in self.mobjects:
mob.clear_updaters()

# self.next_section(skip_animations=True)
text = Text("Chapter 1: 二次方程", font_size=80)
self.play(FadeIn(text))
self.wait(2)
self.play(FadeOut(text))
# 台词:现在,我们来研究二次方程。我们知道,二次方程具有这样的形式。为了简化,我们把等式除以a,使得最高次项的系数为1。这样一来,我们就可以用求根公式求出它的根。
quad_eq = MathTex(r"ax^2+bx+c=0").move_to(UP * 2)
self.play(Write(quad_eq))
self.wait(3)
quad_eq2 = MathTex(r"x^2+c_1x+c_0=0").next_to(quad_eq, DOWN)
self.play(Write(quad_eq2))
self.wait(5)
solved_eq = MathTex(r"x=\frac{-c_1\pm\sqrt{c_1-4c_0}}{2}").next_to(quad_eq2, DOWN)
self.play(Write(solved_eq))
self.wait(3)
# 台词:二次方程的根可以是复数,系数a、b、c也可以为复数。让我们在复平面上画出一个二次方程的根。根据韦达定理,我们可以求出二次方程的系数。接下来我们要证明的是,不存在仅使用加减乘除的二次方程求根公式。
def create_point(pos, label, color=YELLOW, fill_opacity=1.0):
dot_inst = Dot(plane.n2p(pos), color=color, fill_opacity=fill_opacity)
label_inst = label.next_to(Dot(plane.n2p(pos), color=color), UR)
label_inst.add_updater(lambda m: m.next_to(dot_inst.get_center(), UR))
group = VGroup(dot_inst, label_inst)
group.dot = dot_inst
group.label = label_inst
return group
self.wait(4)
self.play(FadeOut(quad_eq), quad_eq2.animate.move_to(DOWN * 3 + LEFT * 5), FadeOut(solved_eq), FadeIn(plane))
root_vals = [0.7 + 0.9j, -1.3 + 1.9j]
root_dots = [create_point(pos, MathTex(f"x_{i+1}")) for i, pos in enumerate(root_vals)]
self.play(*[Create(root) for root in root_dots])
self.wait(3)
vieta = MathTex(r"c_1 = x_1 + x_2, c_0 = x_1 \cdot x_2").next_to(quad_eq2, UR)
self.play(FadeIn(vieta))
self.wait(1)
def decide_coeffs(roots):
coeffs = [1 + 0j]
for r in roots:
new_coeffs = [0] * (len(coeffs) + 1)
for i in range(len(coeffs)):
new_coeffs[i] += coeffs[i] * (-r)
new_coeffs[i + 1] += coeffs[i]
coeffs = new_coeffs
return coeffs[:len(coeffs) - 1]

coeffs = decide_coeffs(root_vals)
coeff_dots = [create_point(coeffs[0], MathTex(r"c_0"), color=RED),
create_point(coeffs[1], MathTex(r"c_1"), color=RED)]
self.play(*[Create(dot) for dot in coeff_dots])
self.wait(3)
self.play(FadeOut(vieta))
# 证明方法就是假设我们现在有不含根号的求根公式F1和F2,比如长这样的,它们仅使用四则运算分别计算出方程的两个根。
text2 = MathTex(r"x_{2(jiade)}'=F_2(c_0, c_1)=c_1-\frac{c_0}{2c_1}+5.4321").move_to(DOWN * 2 + LEFT * 4).scale(0.7)
text1 = MathTex(r"x_{1(jiade)}'=F_1(c_0, c_1)=c_1+\frac{c_0}{2c_1}+5.4321").move_to(DOWN + LEFT * 4).scale(0.7)
self.play(Write(text1), Write(text2))
c0 = plane.p2n(coeff_dots[0].dot.get_center())
c1 = plane.p2n(coeff_dots[1].dot.get_center())
def get_x1n(c0, c1):
return c1 + c0 / (2 * c1) + 5.4321
def get_x2n(c0, c1):
return c1 - c0 / (2 * c1) + 5.4321
x1n = create_point(get_x1n(c0, c1), MathTex("x_{1(jiade)}'").scale(0.7), color=WHITE, fill_opacity=0.5)
x2n = create_point(get_x2n(c0, c1), MathTex("x_{2(jiade)}'").scale(0.7), color=WHITE, fill_opacity=0.5)
self.play(Create(x1n))
self.play(Create(x2n))
self.wait(6)
# 注意现在我们是用韦达定理从两个根推出方程系数,再将系数代入假的求根公式中得出两个假根。接下来是关键的一步操作,我们交换二次方程的两个根x1和x2的位置。
def permute_anims(roots, permutations):
anims = []
new_roots = roots.copy()
for i in permutations:
if(len(i) <= 2):
# 只有两个根,一个沿圆形轨道,另一个沿直线
dot1 = roots[i[0]].dot
dot2 = roots[i[1]].dot
center = (dot1.get_center() + dot2.get_center()) / 2
radius = np.linalg.norm(dot1.get_center() - center)
delta = dot1.get_center() - dot2.get_center()
angle = np.arctan2(delta[1], delta[0])
semi_circle_x1 = Arc(
radius=radius,
start_angle=angle,
angle=PI if i[0] > i[1] else -PI,
arc_center=center,
color=BLUE
)
anims.append(MoveAlongPath(dot1, semi_circle_x1))
anims.append(dot2.animate.move_to(dot1.get_center()))
new_roots[i[0]] = roots[i[1]]
new_roots[i[1]] = roots[i[0]]

else:
# 多个根直线交换
for j in range(len(i) - 1):
dot1 = roots[i[j]].dot
dot2 = roots[i[j + 1]].dot
anims.append(dot1.animate.move_to(dot2.get_center()))
new_roots[i[j + 1]] = roots[i[j]]
anims.append(roots[i[-1]].dot.animate.move_to(roots[i[0]].dot.get_center()))
new_roots[i[0]] = roots[i[-1]]

for i in range(len(roots)):
roots[i] = new_roots[i]
return anims
def update_coeff(i):
return lambda mob: mob.move_to(plane.n2p(decide_coeffs([plane.p2n(root_dots[0].dot.get_center()), plane.p2n(root_dots[1].dot.get_center())])[i]))
def listp2n(lst):
return [plane.p2n(p) for p in lst]
def update_fake_root1(mob):
mob.move_to(plane.n2p(get_x1n(*listp2n([coeff_dot.get_center() for coeff_dot in coeff_dots]))))
def update_fake_root2(mob):
mob.move_to(plane.n2p(get_x2n(*listp2n([coeff_dot.get_center() for coeff_dot in coeff_dots]))))
coeff_dots[0].dot.add_updater(update_coeff(0))
coeff_dots[1].dot.add_updater(update_coeff(1))
x1n.dot.add_updater(update_fake_root1)
x2n.dot.add_updater(update_fake_root2)
self.play(permute_anims(root_dots, [[0, 1]]), run_time=3)
# 在两个根交换位置的过程中,方程的系数也会跟着变化,因此假根也会变化。现在,两个根已经互换了位置,但两个根的值其实并没有变,只是顺序变了,因此方程的系数也回到了原来的位置。方程的系数没有变,所以假根也不会变。但是,如果这个求根公式是真的,它求出来的根就必须紧紧跟着真正的根走,也就是说两个根必须交换。但是两个根只是在原地打转,所以,这个求根公式一定是假的!
for _ in range(6):
self.play(permute_anims(root_dots, [[0, 1]]), run_time=3)
self.play(FadeOut(x1n), FadeOut(x2n), FadeOut(text1), FadeOut(text2))
x1n.dot.clear_updaters()
x2n.dot.clear_updaters()
# 那么真正的求根公式是怎么避免这种情况的呢?答案就藏在公式里的根号中,它是判别式的平方根。在我们交换x1和x2的过程中,判别式绕着原点运动了一圈。
self.wait(6)
solved_eq.move_to(DOWN * 2 + LEFT * 4).scale(0.7)
self.play(Write(solved_eq))
delta_text = MathTex(r"\Delta = c_1^2 - 4c_0").next_to(solved_eq, UP)
def get_delta(coeff_dots):
return plane.p2n(coeff_dots[1].dot.get_center()) ** 2 - 4 * plane.p2n(coeff_dots[0].dot.get_center())
delta_dot = create_point(get_delta(coeff_dots), MathTex(r"\Delta"), color=GREEN)
self.play(Create(delta_dot), Write(delta_text))
self.wait(2)
def update_delta(mob):
mob.move_to(plane.n2p(get_delta(coeff_dots)))
delta_dot.dot.add_updater(update_delta)
self.play(permute_anims(root_dots, [[0, 1]]), run_time=3)
# 于是,判别式的平方根就会旋转半圈。也就是说,判别式的平方根并不会回到原来的位置,而是交换位置,这个求根公式就不能被我们搞掉。一个只含有加减乘除的求根公式只能求出单个值,但含有根号就可以求出多个值,求根公式自然就可以逃过一劫。
sqrtdelta_dots = VGroup(*[create_point(pos, MathTex(r"\sqrt{\Delta}"), color=BLUE) for pos in get_roots(get_delta(coeff_dots), 2)])
self.play(Create(sqrtdelta_dots))
def update_sqrtdelta(mob):
new_roots = get_roots(get_delta(coeff_dots), 2)
for vg, pos in zip(mob, new_roots):
vg.dot.move_to(plane.n2p(pos))
sqrtdelta_dots.add_updater(update_sqrtdelta)
for _ in range(2):
self.play(permute_anims(root_dots, [[0, 1]]), run_time=3)
self.wait(3)

text = Text("Chapter 2: 三次方程", font_size=80)
self.play(FadeOut(plane), FadeOut(root_dots[0]), FadeOut(root_dots[1]), FadeOut(coeff_dots[0]), FadeOut(coeff_dots[1]), FadeOut(delta_text), FadeOut(delta_dot), FadeOut(sqrtdelta_dots), FadeOut(quad_eq2), FadeOut(solved_eq), FadeIn(text))
for mob in self.mobjects:
mob.clear_updaters()
self.wait(2)
self.play(FadeOut(text))
# 台词:研究完了二次方程,我们来研究三次方程。通过一些操作,我们可以把三次方程转化成这样。它的求根公式长这样,这就是卡尔达诺公式。三次方程的解法可以看看这个视频。可以看到,它的求根公式嵌套了两层根号。嗯,头皮发麻,赶紧隐藏。
self.wait(2)
cubic_eq = MathTex(r"ax^3+bx^2+cx+d=0").move_to(UP * 2)
self.play(Write(cubic_eq))
self.wait(3)
cubic_eq2 = MathTex(r"x^3+px+q=0").next_to(cubic_eq, DOWN)
self.play(Write(cubic_eq2))
solved_eq = MathTex(r"x=\sqrt[3]{-\frac{q}{2}+\sqrt{\left(\frac{q}{2}\right)^2+\left(\frac{p}{3}\right)^3}}+\sqrt[3]{-\frac{q}{2}-\sqrt{\left(\frac{q}{2}\right)^2+\left(\frac{p}{3}\right)^3}}").next_to(cubic_eq2, DOWN)
self.play(Write(solved_eq))
self.wait(3)
self.play(FadeOut(cubic_eq), FadeOut(solved_eq), FadeOut(cubic_eq2))
cubic_eq2 = MathTex(r"x^3+c_2x^2+c_1x+c_0=0").move_to(DOWN * 3 + LEFT * 4)
self.play(Create(cubic_eq2))
plane.scale(2)
plane = ComplexPlane(
x_range=[-12, 12, 1],
y_range=[-6, 6, 1],
background_line_style={
"stroke_color": BLUE,
"stroke_width": 1,
"stroke_opacity": 0.3
}
).scale(2)
self.play(Create(plane))
# 根据代数基本定理,三次方程有三个根。三次方程也有韦达定理。事实上,韦达定理适用于任意次方程。先把方程的系数求出来。用类似二次方程的做法,我们可以证明,只用加减乘除是解不出来三次方程的,只需要随便选一对根交换就可以假掉它们。但是接下来我们要更进一步,我们要证明的是,即使加上单层的根号,也不能构造出三次方程的求根公式。换句话说,三次方程的求根公式必须包含嵌套的根号。
self.wait(6)
root_dots = [create_point(pos, MathTex(f"x_{i+1}")) for i, pos in enumerate([0.7 + 0.9j, -0.9 + 0.2j, 0.4 + 0.3j])]
self.play(*[Create(root) for root in root_dots])
coeff_dots = [create_point(pos, MathTex(f"c_{i}"), color=RED) for i, pos in enumerate(decide_coeffs(listp2n([vg.dot.get_center() for vg in root_dots])))]
def update_coeff(i):
return lambda mob: mob.move_to(plane.n2p(decide_coeffs(listp2n([vg.dot.get_center() for vg in root_dots]))[i]))
[coeff_dots[i].dot.add_updater(update_coeff(i)) for i in range(len(coeff_dots))]
self.play(*[Create(dot) for dot in coeff_dots])
self.play(permute_anims(root_dots, [[0, 1]]), run_time=1)
self.play(permute_anims(root_dots, [[0, 1]]), run_time=1)
# 回顾刚才二次方程的证明,我们在交换x1和x2后发现假根并没有变化,从而证伪了那个求根公式。现在,我们可以用相同的思路,假设我们现在有这么一个求根公式,它只使用加减乘除和单层根号,每个根号下都有一坨东西。注意这里的根号都可以是任意次的,F和G都表示不含根号的有理函数。我们只需要找到一种操作,它交换了真正的根,但这个假公式的值不会变化。实际上,我们只需要让每一项的值都不变化就可以了。F0和G0是最好处理的,与刚才二次方程的情况相同,交换x1和x2,F0和G0都不会变化,因为它是只含四则运算的有理函数,直接假掉。
fakesolvedeq_text = MathTex(r"x=\frac{F_0+\sqrt{F_1}+\sqrt{F_2}+...+\sqrt{F_n}}{G_0+\sqrt{G_1}+\sqrt{G_2}+...+\sqrt{G_m}}").next_to(cubic_eq2, UP).scale(0.7)
self.play(Write(fakesolvedeq_text))
f0_dot = create_point(0.5 * get_x1n(plane.p2n(coeff_dots[0].dot.get_center()), plane.p2n(coeff_dots[1].dot.get_center())), MathTex("F_0"), color=WHITE, fill_opacity=0.5)
g0_dot = create_point(0.5 * get_x2n(plane.p2n(coeff_dots[0].dot.get_center()), plane.p2n(coeff_dots[1].dot.get_center())), MathTex("G_0"), color=WHITE, fill_opacity=0.5)
self.play(Create(f0_dot), Create(g0_dot))
def update_f0(mob):
mob.move_to(plane.n2p(0.5 * get_x1n(*listp2n([vg.dot.get_center() for vg in coeff_dots[0:2]]))))
def update_g0(mob):
mob.move_to(plane.n2p(0.5 * get_x2n(*listp2n([vg.dot.get_center() for vg in coeff_dots[0:2]]))))
f0_dot.dot.add_updater(update_f0)
g0_dot.dot.add_updater(update_g0)
self.play(permute_anims(root_dots, [[0, 1]]), run_time=1)
self.play(permute_anims(root_dots, [[0, 1]]), run_time=1)
self.play(FadeOut(f0_dot), FadeOut(g0_dot))
f0_dot.dot.clear_updaters()
g0_dot.dot.clear_updaters()
# 接下来我们重点关注根号F1这一项。我们能不能用类似的方法交换方程的根,同时保持根号F1这一项不变呢?假设这个是F1,这些是根下F1的值。现在,我们交换x1和x2。然而,虽然F1在旋转了一圈后回到了原来的位置,但根下F1的值却发生了变化,它们只旋转了三分之一圈。所以,交换方程的根并不能证明这样的求根公式是假的……吗?
note_text = Text("*由于接下来的动画有点眼花缭乱, 我们暂时隐藏方程的系数c0、c1和c2。").move_to(RIGHT * 4 + DOWN * 3).scale(0.3)
self.play(Write(note_text), *[FadeOut(coeff_dot) for coeff_dot in coeff_dots])
f1_dot = create_point(0.8 - 0.7j, MathTex("F_1"), color=GREEN)
self.play(Create(f1_dot))
self.wait(1)
sqrtf1_dots = VGroup(create_point(pos, MathTex(r"\sqrt{F_1}"), color=BLUE) for pos in get_roots(plane.p2n(f1_dot.dot.get_center()), 3))
self.play(Create(sqrtf1_dots), FadeOut(note_text))
def update_sqrtf1(mob):
new_roots = get_roots(plane.p2n(f1_dot.dot.get_center()), 3)
for vg, pos in zip(mob, new_roots):
vg.dot.move_to(plane.n2p(pos))
sqrtf1_dots.add_updater(update_sqrtf1)
self.wait(2)
def rotate_dot_anim(dot, laps, center = 0.3 - 0.3j):
center = plane.n2p(center)
radius = np.linalg.norm(dot.get_center() - center)
delta = dot.get_center() - center
angle = np.arctan2(delta[1], delta[0])
circle = Arc(
radius=radius,
start_angle=angle,
angle= 2 * PI * laps,
arc_center=center
)
return MoveAlongPath(dot, circle)
for _ in range(2):
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f1_dot.dot, 1), run_time=3)
# 刚才我们的操作全部都是在交换x1和x2,但x3被遗忘在了角落里。那如果我们交换一下x1和x3呢?看起来这一次F1旋转了4圈。但是这样也不能达成我们的目的,因为根下F1的值还是变化了。也许我们可以考虑把两种操作结合起来?我们把刚才两种操作分别记作操作1和操作2。我们先进行操作1,然后进行操作2。F1会先绕原点旋转一圈,然后再绕原点旋转4圈。看起来根下F1的值还是变化了。但是接下来的操作才是神来之笔,我们分别进行操作1的逆操作和操作2的逆操作。这会使F1反着转1圈,然后再反着转4圈。这样一来,F1就回到了原来的位置,更关键的是,正正反反四次操作对F1的辐角的影响全部抵消,所以根号下F1的值也回到了原来的位置。而比较前后根的顺序,发现三个根的顺序确实变化了,做了一个轮换。我们成功了!我们找到了一个操作,它交换了真正的根,但根号下F1的值不会变化。对于假求根公式中的各项也是如此,它们的值都不会变化。于是,整个公式的值都不会变化。所以,这个公式一定是假的!三次方程的求根公式一定含有嵌套的根号!
self.wait(7)
self.play(permute_anims(root_dots, [[0, 2]]), rotate_dot_anim(f1_dot.dot, 4), run_time=4)
self.wait(1)
self.play(permute_anims(root_dots, [[2, 0]]), rotate_dot_anim(f1_dot.dot, -4))
self.wait(7)
op1_text = Text("操作1: 1 ↔ 2").move_to(UP * 2 + LEFT * 4).scale(0.7)
self.play(Write(op1_text))
op2_text = Text("操作2: 1 ↔ 3").next_to(op1_text, DOWN).scale(0.7)
self.play(Write(op2_text))
self.wait(1)
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f1_dot.dot, 1), run_time=3)
note_text = Text("*注意: 这里在进行完操作1后, 由于x1和x2已交换, 操作2实际上交换的是x2和x3。\n当多个操作连续进行时, 前面的操作不影响后面的操作, 我们以最开始时各位置上根的\n编号为基准。").move_to(RIGHT * 4 + DOWN * 3).scale(0.3)
self.play(Write(note_text))
self.play(permute_anims(root_dots, [[0, 2]]), rotate_dot_anim(f1_dot.dot, 4), run_time=3)
self.wait(3)
self.play(permute_anims(root_dots, [[1, 0]]), rotate_dot_anim(f1_dot.dot, -1), run_time=3)
self.play(permute_anims(root_dots, [[2, 0]]), rotate_dot_anim(f1_dot.dot, -4), run_time=3)
self.play(FadeOut(note_text))
self.wait(12)
self.play(permute_anims(root_dots, [[0, 1, 2]]))
self.wait(1)
self.play(permute_anims(root_dots, [[0, 2, 1]]))


text = Text("Chapter 3: 四次方程", font_size=80)
self.play(FadeOut(plane), *[FadeOut(root_dot) for root_dot in root_dots], FadeOut(f1_dot), *[FadeOut(sqrtf1_dot) for sqrtf1_dot in sqrtf1_dots], FadeOut(op1_text), FadeOut(op2_text), FadeOut(cubic_eq2), FadeOut(fakesolvedeq_text), FadeIn(text))
for mob in self.mobjects:
mob.clear_updaters()
self.wait(2)
# 接下来就是令人闻风丧胆的四次方程了。先上求根公式看看。很明显,这个公式嵌套了三层根号。也许你已经猜到我们要证什么了。没错,我们要证明四次方程的求根公式必须嵌套至少三层根号,只嵌套两层根号的解不存在。
self.play(FadeOut(text))
quar_eq = MathTex(r"ax^4+bx^3+cx^2+dx+e=0").move_to(UP)
quar_eq2 = MathTex(r"x^4+c_3x^3+c_2x^2+c_1x+c_0=0").next_to(quar_eq, DOWN).scale(0.7)
self.play(Write(quar_eq), Write(quar_eq2))
self.wait(3)
self.play(FadeOut(quar_eq), quar_eq2.animate.move_to(DOWN * 3 + LEFT * 4))
quar_eq2.move_to(DOWN * 3 + LEFT * 4)
# 用四次方程的韦达定理,可以解出方程的系数。让我们直奔主题,假设我们存在这样一个求根公式,它只使用加减乘除和最多两层根号。这一次,F和G是含有加减乘除和至多一层根号的函数。我们要证明它不存在。
self.play(Create(plane))
root_dots = [create_point(pos, MathTex(f"x_{i+1}")) for i, pos in enumerate([-0.12 + 1.03j, -0.53 + 0.56j, -0.28 - 0.47j, 0.19 + 0.1j])]
self.play(*[Create(root) for root in root_dots])
coeff_dots = [create_point(pos, MathTex(f"c_{i}"), color=RED) for i, pos in enumerate(decide_coeffs(listp2n([vg.dot.get_center() for vg in root_dots])))]
self.play(*[Create(dot) for dot in coeff_dots])
fakesolvedeq_text = MathTex(r"x=\frac{F_0+\sqrt{F_1}+\sqrt{F_2}+...+\sqrt{F_n}}{G_0+\sqrt{G_1}+\sqrt{G_2}+...+\sqrt{G_m}}").next_to(quar_eq2, UP).scale(0.7)
self.play(Write(fakesolvedeq_text))
fn_text = MathTex(r"F_n=F_0'+\sqrt{F_1'}+\sqrt{F_2'}+...+\sqrt{F_m'}").next_to(fakesolvedeq_text, UP).scale(0.7)
fn_text.color = ManimColor(WHITE, 0.5)
self.play(Write(fn_text))
self.wait(3)
self.play(FadeOut(fn_text))
# 先证明F0和G0可以被假掉,方法和三次方程一模一样,因为F0和G0都只有单层根号。
note_text = Text("*接下来的动画更离谱, 暂时隐藏系数c0、c1、c2和c3。").move_to(RIGHT * 4 + DOWN * 3).scale(0.3)
self.play(Write(note_text), *[FadeOut(coeff_dot) for coeff_dot in coeff_dots])
f0_dot = create_point(0.7 - 0.6j, MathTex("F_0"), color=GREEN)
sqrtf0_dots = VGroup(create_point(pos, MathTex(r"\sqrt{F_0}"), color=BLUE) for pos in get_roots(plane.p2n(f0_dot.dot.get_center()), 3))
self.play(Create(f0_dot), Create(sqrtf0_dots))
def update_sqrtf0(mob):
new_roots = get_roots(plane.p2n(f0_dot.dot.get_center()), 3)
for vg, pos in zip(mob, new_roots):
vg.dot.move_to(plane.n2p(pos))
sqrtf0_dots.add_updater(update_sqrtf0)
text1 = Text("操作1: 1 ↔ 2").move_to(UP * 2 + LEFT * 4).scale(0.7)
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f0_dot.dot, 1), Write(text1))
text2 = Text("操作2: 1 ↔ 3").next_to(text1, DOWN).scale(0.7)
self.play(permute_anims(root_dots, [[0, 2]]), rotate_dot_anim(f0_dot.dot, 2), Write(text2))
text3 = Text("操作3: 2 ↔ 1").next_to(text2, DOWN).scale(0.7)
self.play(permute_anims(root_dots, [[1, 0]]), rotate_dot_anim(f0_dot.dot, -1), Write(text3))
text4 = Text("操作4: 3 ↔ 1").next_to(text3, DOWN).scale(0.7)
self.play(permute_anims(root_dots, [[2, 0]]), rotate_dot_anim(f0_dot.dot, -2), Write(text4))
self.wait(1)
self.play(FadeOut(text1), FadeOut(text2), FadeOut(text3), FadeOut(text4), FadeOut(note_text), FadeOut(sqrtf0_dots), FadeOut(f0_dot))
# 现在F0和G0都被处理掉了。然后就是重头戏,我们来处理根号F1这一项。和之前一样,我们要找到一个操作,它交换了真正的根,但根号F1的值不会变化。我们展开根号F1这一项。目前场上的形势是,F撇是不含根号的有理函数,F1是单层根号公式,整个公式是双层嵌套根式。现在,用刚才正正反反的方法,我们能证明的是构成F1的各项的值不会变化,即F1本身不会变化,但根下F1的值会变化。问题出在我们虽然保证了F1的值不变,但由于F1是由几个根式组合而成的,所以它的运动路径会比较复杂,可能绕原点运动了几圈才会回去,而这会影响根下F1的值。举个例子,假设这些是F1的值。我们关注其中一个F1的辐角变化。现在,交换x1和x2,F1可能会这么运动
self.wait(10)
expanded_f1_text = MathTex(r"F_1=F_0'+\sqrt{F_1'}+\sqrt{F_2'}+...+\sqrt{F_n'}").next_to(quar_eq2, UP).scale(0.7)
self.play(FadeOut(quar_eq2), fakesolvedeq_text.animate.move_to(DOWN * 3 + LEFT * 4), Write(expanded_f1_text))
self.wait(18)
f1_dots = [create_point(pos, MathTex("F_1"), color=GREEN) for pos in [1.5 + 0.1j, 2.5 + 0.2j]]
for vg in f1_dots:
vg.dot.arg = np.arctan2(vg.dot.get_center()[1], vg.dot.get_center()[0])
self.play(*[Create(dot) for dot in f1_dots])
note_text = Text("*由于F1中含有根号, 所以会有多个值。").move_to(RIGHT * 4 + DOWN * 3).scale(0.3)
self.play(Write(note_text))

self.wait(1)
self.play(f1_dots[1].animate.set_color(WHITE), f1_dots[1].animate.set_opacity(0.2))
f1_dots[1].set_color(WHITE)
f1_dots[1].set_opacity(0.2)
arg_displayers = [Sector(radius=2, angle=f1_dots[0].dot.arg, color=GREEN, fill_opacity=0.5), *[Sector() for _ in range(5)]]
arg_displayers[0].set_fill(GREEN, 0.5)
for i in range(len(arg_displayers)):
arg_displayers[i].index = i
[displayer.set_opacity(0) for displayer in arg_displayers[1:]]
def update_arg(mob):
targetangle = (np.arctan2(mob.get_center()[1], mob.get_center()[0]) + 2 * PI) % (2 * PI)
candidates = [targetangle + 2 * k * PI for k in range(-10, 10)]
newarg = candidates[np.argmin([abs(angle - mob.arg) for angle in candidates])]
mob.arg = newarg
def update_arg_displayer0(mob):
arg = f1_dots[0].dot.arg
if(arg // (2 * PI) > mob.index + 1):
mob.set_opacity(0)
elif arg // (2 * PI) == mob.index + 1:
mob.become(Circle(radius=2, color=GREEN, fill_opacity=0.5*(1 - arg % (2 * PI) / (2 * PI))))
elif arg // (2 * PI) == mob.index:
mob.become(Sector(radius=2, angle=(arg + 2000 * PI) % (2 * PI), color=GREEN, fill_opacity=0.5))
else:
mob.set_opacity(0)
def update_arg_displayer1(mob):
arg = f1_dots[1].dot.arg
if(arg // (2 * PI) > mob.index + 1):
mob.set_opacity(0)
elif arg // (2 * PI) == mob.index + 1:
mob.become(Circle(radius=2, color=GREEN, fill_opacity=0.5*(1 - arg % (2 * PI) / (2 * PI))))
mob.stroke_opacity = 0
elif arg // (2 * PI) == mob.index:
mob.become(Sector(radius=2, angle=(arg + 2000 * PI) % (2 * PI), color=GREEN, fill_opacity=0.5))
else:
mob.set_opacity(0)
f1_dots[0].dot.add_updater(update_arg)
f1_dots[1].dot.add_updater(update_arg)
[displayer.add_updater(update_arg_displayer0) for displayer in arg_displayers]
self.play(*[Create(arg_displayer) for arg_displayer in arg_displayers])
text1 = Text("操作1: 1 ↔ 2").move_to(UP * 2 + LEFT * 4).scale(0.7)
self.play(Write(text1))
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f1_dots[0], 1, 0.7), rotate_dot_anim(f1_dots[1], -1, 1.3), run_time=3)
text2 = Text("操作2: 1 ↔ 3").next_to(text1, DOWN).scale(0.7)
self.play(Write(text2))
[displayer.clear_updaters() for displayer in arg_displayers]
[displayer.add_updater(update_arg_displayer1) for displayer in arg_displayers]
self.play(permute_anims(root_dots, [[0, 2]]), permute_anims(f1_dots, [[0, 1]]), run_time = 1)
text3 = Text("操作3: 2 ↔ 1").next_to(text2, DOWN).scale(0.7)
self.play(Write(text3))
self.play(permute_anims(root_dots, [[1, 0]]), rotate_dot_anim(f1_dots[0], -1, 0.7), rotate_dot_anim(f1_dots[1], 1, 1.3), run_time=3)
text4 = Text("操作4: 3 ↔ 1").next_to(text3, DOWN).scale(0.7)
self.play(Write(text4))
[displayer.clear_updaters() for displayer in arg_displayers]
[displayer.add_updater(update_arg_displayer0) for displayer in arg_displayers]
self.play(permute_anims(root_dots, [[2, 0]]), permute_anims(f1_dots, [[1, 0]]), run_time = 1)
self.play(FadeOut(text1), FadeOut(text2), FadeOut(text3), FadeOut(text4), FadeOut(note_text), *[FadeOut(displayer) for displayer in arg_displayers], *[FadeOut(dot) for dot in f1_dots])
[displayer.clear_updaters() for displayer in arg_displayers]
# 可以看到,F1绕原点运行了2圈,辐角增加了4派,这会使根下F1的值变化。
# 那么有什么办法可以让F1保持辐角不变呢?我们再回头看看刚才处理三次方程的方法。在证伪单层根号的三次方程求根公式时,交换两个根会使F1绕原点旋转,但我们通过正正反反的方法抵消了它的旋转。而现在,由于F1含有了根号,正正反反的操作只会使F1绕原点旋转,不会抵消。我们对比一下,三次方程时,交换操作会使F1绕原点旋转,而正正反反的操作会使F1的旋转被抵消。四次方程的情况中,正正反反的操作会使F1绕原点旋转,那什么操作可以抵消这个旋转呢?没错,我们只需要把正正反反的操作正正反反地做就可以了!
hint = Text("视频后期处理: \n回放三次方程的操作", font_size=80)
self.play(Write(hint))
self.play(FadeOut(hint))
self.play(*[FadeOut(mob) for mob in self.mobjects])
title_text1 = Text("三次方程").move_to(UP * 3 + LEFT * 4)
title_text2 = Text("四次方程").move_to(UP * 3 + RIGHT * 4)
self.play(Write(title_text1), Write(title_text2))
text1 = Text("1 ↔ 2").move_to(LEFT * 5.5).scale(0.7)
self.play(Write(text1))
text2 = Text(" → F1绕原点旋转").next_to(text1, RIGHT).scale(0.7)
self.play(Write(text2))
text3 = Text("正正反反").move_to(DOWN * 3 + LEFT * 6).scale(0.7)
self.play(Write(text3))
text4 = Text(" → F1的旋转被抵消").next_to(text3, RIGHT).scale(0.7)
self.play(Write(text4))
text5 = Text("正正反反").move_to(RIGHT * 2).scale(0.7)
self.play(Write(text5))
text6 = Text(" → F1绕原点旋转").next_to(text5, RIGHT).scale(0.7)
self.play(Write(text6))
text7 = Text("?").move_to(DOWN * 3 + RIGHT * 2).scale(0.7)
text7.set_color(RED)
self.play(Write(text7))
text8 = Text(" → F1的旋转被抵消").next_to(text7, RIGHT).scale(0.7)
self.play(Write(text8))
self.wait(3)
self.play(FadeOut(title_text1), FadeOut(title_text2), FadeOut(text1), FadeOut(text2), FadeOut(text3), FadeOut(text4), FadeOut(text5), FadeOut(text6), FadeOut(text7), FadeOut(text8))
# 这么说比较抽象,举个例子。比如说,我们正正反反的操作是这个。我们再找一个正正反反的操作,比如这个。咱也甭管操作具体是什么,我们只需要知道,正正反反的操作分别会使F1绕原点旋转l1和l2圈。
self.play(FadeIn(plane), *[FadeIn(root_dot) for root_dot in root_dots])
text1 = Text("正正反反(1): 1 ↔ 2, 1 ↔ 3, 2 ↔ 1, 3 ↔ 1").move_to(UP * 2 + LEFT * 4).scale(0.5)
self.play(Write(text1))
self.wait(2)
text2 = Text("正正反反(2): 1 ↔ 4, 1 ↔ 2, 4 ↔ 1, 2 ↔ 1").next_to(text1, DOWN).scale(0.5)
self.play(Write(text2))
self.wait(6)

self.play(Transform(text1, Text("正正反反(1): 🔄l1圈").move_to(UP * 2 + LEFT * 4).scale(0.7)))
self.play(Transform(text2, Text("正正反反(2): 🔄l2圈").next_to(text1, DOWN).scale(0.7)))
# 接下来,我们先做正正反反1,这会使F1绕原点旋转l1圈。然后,我们再做正正反反2,这会使F1绕原点旋转l2圈。现在F1一共旋转了l1+l2圈。接下来,我们做正正反反1的逆操作,让F1往回转l1圈。最后,我们再做正正反反2的逆操作,让F1往回转l2圈。这样一来,F1的旋转成功被抵消了,说明根下F1也不会变化!而且我们发现,四个根的顺序发生了变化。这符合我们的要求。
f1_dots[0].dot.arg = np.arctan2(f1_dots[0].dot.get_center()[1], f1_dots[0].dot.get_center()[0])
[displayer.add_updater(update_arg_displayer0) for displayer in arg_displayers]
self.play(FadeIn(f1_dots[0]), *[FadeIn(displayer) for displayer in arg_displayers])

text3 = Text("操作1: 正正反反(1)").move_to(DOWN * 3 + LEFT * 4).scale(0.7)
self.play(*[root_dots[i].dot.animate.set_color(RED) for i in [0, 1, 2]], FadeIn(text3))
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[0, 2]]))
self.play(permute_anims(root_dots, [[1, 0]]))
self.play(permute_anims(root_dots, [[2, 0]]))
self.play(rotate_dot_anim(f1_dots[0], 2))
self.play(*[root_dots[i].dot.animate.set_color(YELLOW) for i in [0, 1, 2]])
self.wait(1)

text4 = Text("操作2: 正正反反(2)").next_to(text3, UP).scale(0.7)
self.play(*[root_dots[i].dot.animate.set_color(RED) for i in [0, 1, 3]], FadeIn(text4))
self.play(permute_anims(root_dots, [[0, 3]]))
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[3, 1]]))
self.play(permute_anims(root_dots, [[1, 0]]))
self.play(rotate_dot_anim(f1_dots[0], 1))
self.play(*[root_dots[i].dot.animate.set_color(YELLOW) for i in [0, 1, 3]])
self.wait(1)

text5 = Text("操作3: 正正反反(1)逆").next_to(text4, UP).scale(0.7)
self.play(*[root_dots[i].dot.animate.set_color(RED) for i in [0, 1, 2]], FadeIn(text5))
self.play(permute_anims(root_dots, [[0, 2]]))
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[2, 0]]))
self.play(permute_anims(root_dots, [[1, 0]]))
self.play(rotate_dot_anim(f1_dots[0], -2))
self.play(*[root_dots[i].dot.animate.set_color(YELLOW) for i in [0, 1, 2]])
self.wait(1)

text6 = Text("操作3: 正正反反(2)逆").next_to(text5, UP).scale(0.7)
self.play(*[root_dots[i].dot.animate.set_color(RED) for i in [0, 1, 3]], FadeIn(text6))
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[1, 3]]))
self.play(permute_anims(root_dots, [[1, 0]]))
self.play(permute_anims(root_dots, [[3, 0]]))
self.play(rotate_dot_anim(f1_dots[0], -1))
self.play(*[root_dots[i].dot.animate.set_color(YELLOW) for i in [0, 1, 3]])
self.wait(1)

self.play(permute_anims(root_dots, [[2, 3, 0, 1]]))
self.play(permute_anims(root_dots, [[1, 0, 3, 2]]))

# 回到最开始我们假设的求根公式,和之前一样,其中的每一项在这么操作后都会回到原位,于是整个公式的值都不会变化。但根的顺序变了,说明这不可能是求根公式。我们成功证明了四次方程的求根公式必须嵌套至少三层根号。
self.play(FadeOut(text1), FadeOut(text2), FadeOut(text3), FadeOut(text4), FadeOut(text5), FadeOut(text6), FadeIn(fakesolvedeq_text), FadeIn(expanded_f1_text))
self.wait(13)
for mob in self.mobjects:
mob.clear_updaters()
self.play(*[FadeOut(mob) for mob in self.mobjects])

text = Text("Chapter 4: 五次方程", font_size=80)
self.play(FadeIn(text))
self.wait(2)
self.play(FadeOut(text))
# 现在,我们来到了五次方程。一切变得清晰起来了。我们的手段,就是通过找到一个操作,它交换了真正的根,但假设的求根公式的值不会变化。通过重复嵌套正正反反的组合操作,我们可以证明嵌套根号的层数存在下限。我们已经证明了,二次方程的求根公式必须嵌套至少一层根号,三次方程的求根公式必须嵌套至少两层根号,四次方程的求根公式必须嵌套至少三层根号。那么五次方程呢?也许你会觉得这样只能五次方程的求根公式必须嵌套至少四层根号。
self.wait(17)
text1 = Text("二次方程 → 至少1层根号").move_to(UP * 3)
self.play(Write(text1))
text2 = Text("三次方程 → 至少2层根号").move_to(UP * 1)
self.play(Write(text2))
text3 = Text("四次方程 → 至少3层根号").move_to(DOWN * 1)
self.play(Write(text3))
self.wait(3)
text4 = Text("五次方程 → 至少4层根号?").move_to(DOWN * 3)
self.play(Write(text4))
self.play(*[FadeOut(mob) for mob in [text1, text2, text3, text4]])
# 事实并非如此,五次方程没有求根公式。我们来考察一下五次方程的正正反反操作。这是一个嵌套了两层的正正反反操作,它的效果是这样的。
root_dots = [create_point(pos, MathTex(f"x_{i+1}")) for i, pos in enumerate([-0.12 + 1.03j, -0.53 + 0.56j, -0.28 - 0.47j, 0.19 + 0.1j, 0.5 + 0.5j])]
self.play(FadeIn(plane), *[Create(root) for root in root_dots])
text1 = Text("(1): 1 ↔ 2, 1 ↔ 3, 2 ↔ 1, 3 ↔ 1").move_to(UP * 2 + LEFT * 4).scale(0.5)
text2 = Text("(2): 3 ↔ 4, 3 ↔ 5, 4 ↔ 3, 5 ↔ 3").next_to(text1, DOWN).scale(0.5)
self.play(Write(text1), Write(text2))
text3 = Text("操作: (1) → (2) → (1)逆 → (2)逆").next_to(text2, DOWN).scale(0.5)
self.play(Write(text3))
self.wait(5)
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[0, 2]]))
self.play(permute_anims(root_dots, [[1, 0]]))
self.play(permute_anims(root_dots, [[2, 0]]))

self.play(permute_anims(root_dots, [[2, 3]]))
self.play(permute_anims(root_dots, [[2, 4]]))
self.play(permute_anims(root_dots, [[3, 2]]))
self.play(permute_anims(root_dots, [[4, 2]]))

self.play(permute_anims(root_dots, [[0, 2]]))
self.play(permute_anims(root_dots, [[0, 1]]))
self.play(permute_anims(root_dots, [[2, 0]]))
self.play(permute_anims(root_dots, [[1, 0]]))

self.play(permute_anims(root_dots, [[2, 4]]))
self.play(permute_anims(root_dots, [[2, 3]]))
self.play(permute_anims(root_dots, [[4, 2]]))
self.play(permute_anims(root_dots, [[3, 2]]))
# 如果你的注意力足够集中,你会发现,这个操作实际上等效于这个操作。我们把正正反反这种操作更简洁的记号表示出来。注意到这个操作实际上可以被表示为双层正正反反嵌套。现在,这两个操作是等价的,我们得到了一个等式。我们可以把等式中的1全部替换成a,2全部替换成b,等等。这时候如果我们从右往左观察这个式子,就能看出端倪:它允许我们把一个正正反反的操作展开成双层嵌套正正反反操作。更重要的是,这个过程可以无限进行下去,从而把一层的正正反反变成任意层数的正正反反操作。
self.wait(4)
text4 = Text("等效: 1 ↔ 4, 1 ↔ 3, 4 ↔ 1, 3 ↔ 1").next_to(text3, DOWN).scale(0.5)
self.play(Write(text4))
self.wait(1)
self.play(*[FadeOut(root_dot) for root_dot in root_dots], FadeOut(plane))
text5 = Text("[a, b] = a → b → a逆 → b逆").move_to(UP * 3)
self.play(Write(text5), FadeOut(text1), FadeOut(text2), text3.animate.move_to(ORIGIN), text4.animate.move_to(DOWN * 1.5))
self.play(Transform(text3, Text("操作: [1 ↔ 2, 1 ↔ 3] → [3 ↔ 4, 3 ↔ 5] → [1 ↔ 2, 1 ↔ 3]逆 → [3 ↔ 4, 3 ↔ 5]逆").scale(0.7)), Transform(text4, Text("等效: [1 ↔ 4, 1 ↔ 3]").move_to(text4.get_center()).scale(0.5)))
self.wait(2)
self.play(Transform(text3, Text("操作: [[1 ↔ 2, 1 ↔ 3], [3 ↔ 4, 3 ↔ 5]]")))
self.wait(1)
text1 = Text("[[1 ↔ 2, 1 ↔ 3], [3 ↔ 4, 3 ↔ 5]] = [1 ↔ 4, 1 ↔ 3]")
self.play(FadeOut(text3), FadeOut(text4), FadeIn(text1))
self.wait(5)
self.play(Transform(text1, Text("[[a ↔ b, a ↔ c], [c ↔ d, c ↔ e]] = [a ↔ d, a ↔ c]")))
self.wait(10)
self.play(Transform(text1, Text("[[[a ↔ d, a ↔ c], [c ↔ b, c ↔ e]], [[c ↔ b, c ↔ a], [e ↔ d, e ↔ a]]] = [a ↔ d, a ↔ c]").scale(0.5)))
self.wait(1)
self.play(Transform(text1, Text("[[[[a ↔ b, a ↔ c], [c ↔ d, c ↔ e]], [[a ↔ b, a ↔ c], [c ↔ d, c ↔ e]]], [[[a ↔ b, a ↔ c], [c ↔ d, c ↔ e]], [[a ↔ b, a ↔ c], [c ↔ d, c ↔ e]]]] = [a ↔ d, a ↔ c]").scale(0.3)))
# 现在我们回到五次方程。假设我们现在找到了一个嵌套10层根号的求根公式。于是,F九个撇就是有理函数。我们从里往外破解它,第一层是有理函数,交换1和2就能破解。第二层是单层根号的函数,一个正正反反就能破解。接下来,我们用刚才的等式把操作展开成双层嵌套的正正反反操作,然后来处理第三层,也就是双层根号函数。不管你上一层的辐角怎么变,你最终都会被双层正正反反嵌套抵消掉,所以这一层的值也不会变。然后再展开,再处理第四层,依次类推。直到第十层,所有的根号都被抵消掉了。
self.play(FadeOut(text1), FadeOut(text5))
root_dots = [create_point(pos, MathTex(f"x_{i+1}")) for i, pos in enumerate([-0.12 + 1.03j, -0.53 + 0.56j, -0.28 - 0.47j, 0.19 + 0.1j, 0.5 + 0.5j])]
self.play(FadeIn(plane), *[FadeIn(root_dot) for root_dot in root_dots])
fakesolvedeq_text = MathTex(r"x=\frac{F_0+\sqrt{F_1}+\sqrt{F_2}+...+\sqrt{F_n}}{G_0+\sqrt{G_1}+\sqrt{G_2}+...+\sqrt{G_m}}").move_to(DOWN * 3 + LEFT * 4).scale(0.7)
fakesolvedeq_text1 = MathTex(r"F=F_0'+\sqrt{F_1'}+\sqrt{F_2'}+...+\sqrt{F_n'}").next_to(fakesolvedeq_text, UP).scale(0.7)
fakesolvedeq_text2 = MathTex(r"F'=F_0''+\sqrt{F_1''}+\sqrt{F_2''}+...+\sqrt{F_n''}").next_to(fakesolvedeq_text1, UP).scale(0.7)
fakesolvedeq_text3 = Text("...").next_to(fakesolvedeq_text2, UP).scale(0.7)
self.play(Write(fakesolvedeq_text), Write(fakesolvedeq_text1), Write(fakesolvedeq_text2), Write(fakesolvedeq_text3))
f10_dot = create_point(0.7 - 0.6j, MathTex("F^{(9)}"), color=GREEN)
self.play(Create(f10_dot))
self.wait(3)
text = Text("操作1: 1 ↔ 2").move_to(UP * 3 + LEFT * 4).scale(0.7)
self.play(Write(text))
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f10_dot.dot, 1, 0.7 - 0.9j), run_time=3)
self.wait(1)
self.play(Transform(text, Text("操作2: [1 ↔ 2, 1 ↔ 3]").move_to(text.get_center()).scale(0.7)), FadeOut(f10_dot))
f9_dot = create_point(0.7 + 0.6j, MathTex("F^{(8)}"), color=GREEN)
self.play(Create(f9_dot))
self.play(permute_anims(root_dots, [[0, 1]]), rotate_dot_anim(f9_dot.dot, 1))
self.play(permute_anims(root_dots, [[0, 2]]), rotate_dot_anim(f9_dot.dot, 2, 1 + 1j))
self.play(permute_anims(root_dots, [[1, 0]]), rotate_dot_anim(f9_dot.dot, -1))
self.play(permute_anims(root_dots, [[2, 0]]), rotate_dot_anim(f9_dot.dot, -2, 1 + 1j))
self.play(FadeOut(f9_dot))
self.wait(2)
text1 = Text("=[[1 ↔ 4, 1 ↔ 3], [3 ↔ 2, 3 ↔ 5]]").move_to(UP * 3 + RIGHT).scale(0.7)
self.play(Write(text1))
f8_dot = create_point(1.1 + 0.6j, MathTex("F^{(7)}"), color=GREEN)
self.play(Create(f8_dot))
self.wait(9)
self.play(FadeOut(f8_dot))
text2 = Text("=...=...").next_to(text1, RIGHT).scale(0.7)
self.play(Write(text2))
self.wait(5)
# 无论我们把上面的10层换成100层还是10000层,它最终都会被我们的无限嵌套正正反反操作抵消掉。于是,F的值不会变化。但是,这些正正反反最终都等价于这个操作2,根的顺序发生了变化。我们期待已久的最终矛盾终于显现。
self.wait(15)
# 终于,我们证明了,五次方程是没有根式解的。
for mob in self.mobjects:
mob.clear_updaters()
self.play(*[FadeOut(mob) for mob in self.mobjects])
theorem_title = Text("Abel-Ruffini定理", font_size=80).move_to(UP * 0.5)
theorem_text = Text("五次及更高次的一般多项式方程没有根式解", font_size=50).next_to(theorem_title, DOWN)
self.play(Write(theorem_title))
self.play(Write(theorem_text))
self.wait(3)
self.play(FadeOut(theorem_title), FadeOut(theorem_text))

# self.next_section(skip_animations=True)
# text = Text("Chapter 5: 最后", font_size=80)
# self.play(FadeIn(text))
# self.wait(2)
# self.play(FadeOut(text))
# # 为什么正正好好是五次方程呢?我们回顾整个证明,其实都是在做一件事,那就是找一个操作,它交换了真正的根,但假设的求根公式的值不会变化。通过正正反反的操作,我们可以抵消辐角的变化,从而将求根公式的根号一层层抽离开。而如果我们想要证明方程无根式解,就要保证无论求根公式有多少层根号,我们都能找到对应层数的正正反反嵌套操作,同时保证这个操作能交换真正的根。也就是说,正正反反嵌套必须达到无穷层。然而,这样的操作只有当我们能交换的元素个数达到5个或以上时才存在。
# self.play(Write(hint))
# self.wait(2)
# self.play(FadeOut(hint))
# self.wait(29)
# # 比如四次方程。我们把4个元素所有可能的交换操作都列出来,共有6种。这6种操作中,任意一种都可以证伪仅含四则运算的求根公式。然后我们任选其中两种,组成一个正正反反的操作。这样的操作共有种。熟悉群论的朋友可能已经发现,正正反反的操作实际上就是交换子。
# ops1 = VGroup(*[Text("1 ↔ 2"), Text("1 ↔ 3"), Text("1 ↔ 4"), Text("2 ↔ 3"), Text("2 ↔ 4"), Text("3 ↔ 4")])
# for i, op in enumerate(ops1):
# op.move_to(UP * (3 - i * 0.5) + LEFT * 4).scale(0.7)
# self.play(Create(ops1))
# ops2 = Text("[1 ↔ 3, 1 ↔ 4]")
```
command line:
```
manim file.py --fps=30
```
**Wrong display or Error traceback**:
```
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\cli\render\commands.py:125 │
│ in render │
│ │
│ 122 │ │ │ try: │
│ 123 │ │ │ │ with tempconfig({}): │
│ 124 │ │ │ │ │ scene = SceneClass() │
│ ❱ 125 │ │ │ │ │ scene.render() │
│ 126 │ │ │ except Exception: │
│ 127 │ │ │ │ error_console.print_exception() │
│ 128 │ │ │ │ sys.exit(1) │
│ │
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\scene\scene.py:247 in render │
│ │
│ 244 │ │ │ return True │
│ 245 │ │ self.tear_down() │
│ 246 │ │ # We have to reset these settings in case of multiple renders. │
│ ❱ 247 │ │ self.renderer.scene_finished(self) │
│ 248 │ │ │
│ 249 │ │ # Show info only if animations are rendered or to get image │
│ 250 │ │ if ( │
│ │
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\renderer\cairo_renderer.py:2 │
│ 69 in scene_finished │
│ │
│ 266 │ def scene_finished(self, scene): │
│ 267 │ │ # If no animations in scene, render an image instead │
│ 268 │ │ if self.num_plays: │
│ ❱ 269 │ │ │ self.file_writer.finish() │
│ 270 │ │ elif config.write_to_movie: │
│ 271 │ │ │ config.save_last_frame = True │
│ 272 │ │ │ config.write_to_movie = False │
│ │
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\scene\scene_file_writer.py:5 │
│ 14 in finish │
│ │
│ 511 │ │ frame in the default image directory. │
│ 512 │ │ """ │
│ 513 │ │ if write_to_movie(): │
│ ❱ 514 │ │ │ self.combine_to_movie() │
│ 515 │ │ │ if config.save_sections: │
│ 516 │ │ │ │ self.combine_to_section_videos() │
│ 517 │ │ │ if config["flush_cache"]: │
│ │
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\scene\scene_file_writer.py:7 │
│ 40 in combine_to_movie │
│ │
│ 737 │ │ │ return │
│ 738 │ │ │
│ 739 │ │ logger.info("Combining to Movie file.") │
│ ❱ 740 │ │ self.combine_files( │
│ 741 │ │ │ partial_movie_files, │
│ 742 │ │ │ movie_file_path, │
│ 743 │ │ │ is_gif_format(), │
│ │
│ C:\Users\Username\AppData\Roaming\Python\Python310\site-packages\manim\scene\scene_file_writer.py:7 │
│ 14 in combine_files │
│ │
│ 711 │ │ │ │ │
│ 712 │ │ │ │ # We need to assign the packet to the new stream. │
│ 713 │ │ │ │ packet.stream = output_stream │
│ ❱ 714 │ │ │ │ output_container.mux(packet) │
│ 715 │ │ │
│ 716 │ │ partial_movies_input.close() │
│ 717 │ │ output_container.close() │
│ │
│ in av.container.output.OutputContainer.mux:257 │
│ │
│ in av.container.output.OutputContainer.mux_one:278 │
│ │
│ in av.container.core.Container.err_check:286 │
│ │
│ in av.error.err_check:326 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ValueError: [Errno 22] Invalid argument: 'C:\\Users\\Username\\Documents\\Tiger\\media\\videos\\quintic unsolvability\\1080p30\\QuinticUnsolvability.mp4'
```

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.