效果
公式推导
这是泰勒展开式: $$
f(x)=f(0)+f'(0)x+\frac{f''(0)}{2!}x^2+\dots+\frac{f^{(n)}(0)}{n!}x^n+o(x^n)
$$
欲求对s i n x 的逼近,我们将s i n x 作为f (x ) 带入公式,尝试写出几项并观察其规律:
$$
sin(x)=
\boxed{sin(0)}+
cos(0)x+
\boxed{\frac{-sin(0)}{2!}x^2}+
\frac{-cos(0)}{3!}x^3+
\boxed{\frac{sin(0)}{4!}x^4}+
\frac{cos(0)}{5!}x^5+
\boxed{\frac{-sin(0)}{6!}x^6}
$$
可以发现所有s i n (0) 全为0,约去得:
$$
sin(x)=
cos(0)x+
\frac{-cos(0)}{3!}x^3+
\frac{cos(0)}{5!}x^5
$$ 可知,c o s (0) = 1 : $$
sin(x)=
\frac{x^1}{1!}+
\frac{-x^3}{3!}+
\frac{x^5}{5!}
+\dots
$$
以此类推得到: $$
sin(x)=\sum_{i=0}^{i}
\frac
{
(-1)^{i} \cdot x^{2i+1}}
{(2i+1)!}
$$
转化为 python 为:
1 2 3 4 def make_taylor_func (n_terms ): return lambda x: sum ( (-1 )**k * x**(2 *k + 1 ) / factorial(2 *k + 1 ) for k in range (n_terms) )
于是就可以写出这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 from manim import *import numpy as npfrom math import factorialclass SingleScene (Scene ): def introduction (self ): intro = Text("这是野生的泰勒展开式" ) tex = Tex(r"$$f(x)=f(0)+f'(0)x+\frac{f''(0)}{2!}x^2+\dots+\frac{f^{(n)}(0)}{n!}x^n+o(x^n)$$" ) intro_sin = Text("将正弦函数代入其中" ).shift(UP*3 ) tex_sin = Tex(r'''$$ sin(x)= \boxed{sin(0)}+ cos(0)x+ \boxed{\frac{-sin(0)}{2!}x^2}+ \frac{-cos(0)}{3!}x^3+ \boxed{\frac{sin(0)}{4!}x^4}+ \frac{cos(0)}{5!}x^5+ \boxed{\frac{-sin(0)}{6!}x^6} $$ ''' ).shift(DOWN).scale(0.6 ) tex_2 = Tex(r""" $$ sin(x)= cos(0)x+ \frac{-cos(0)}{3!}x^3+ \frac{cos(0)}{5!}x^5 $$ """ ) tex_3 = Tex(r""" $$ sin(x)= \frac{x^1}{1!}+ \frac{-x^3}{3!}+ \frac{x^5}{5!} $$ """ ) tex_4 = Tex(r""" $$ sin(x)=\sum_{i=0}^{i} \frac { (-1)^{i} \cdot x^{2i+1}} {(2i+1)!} $$ """ ) self .play(Write(intro)) self .play(intro.animate.shift(UP * 3 )) self .play(Write(tex)) self .play(tex.animate.shift(UP)) self .play(Unwrite(intro)) self .play(Write(intro_sin)) self .play(Write(tex_sin)) self .play(Uncreate(tex)) self .play(Unwrite(intro_sin)) self .play(tex_sin.animate.shift(UP)) self .play(Transform(tex_sin, tex_2), run_time=2 ) self .remove(tex_sin) self .play(Transform(tex_2, tex_3), run_time=2 ) self .remove(tex_2) self .play(Transform(tex_3, tex_4), run_time=2 ) self .play(Unwrite(tex_3)) def make_taylor_func (self, n_terms ): return lambda x: sum ( (-1 ) ** k * x ** (2 * k + 1 ) / factorial(2 * k + 1 ) for k in range (n_terms) ) def make_formula_tex (self, n_terms ): terms = [] for k in range (n_terms): sign = "-" if k % 2 == 1 else "" exponent = 2 * k + 1 term = rf"{sign} \frac{{x^{{{exponent} }}}} {{{exponent} !}}" terms.append(term) formula = "+" .join(terms).replace("+-" , "-" ) return MathTex("f(x) =" , formula).scale(0.8 ).to_edge(RIGHT) def write_image (self ): ax = Axes( x_range=[-12 , 12 , 2 ], y_range=[-2 , 2 ], axis_config={"color" : GREEN}, ) pl = NumberPlane() sin = ax.plot(lambda x: np.sin(x), color=BLUE) self .play(Create(ax), Create(pl)) self .play(Write(sin)) taylor_graphs = [] formula_labels = [] for i in range (1 , 15 ): f = self .make_taylor_func(i) graph = ax.plot(f, color=RED) formula = self .make_formula_tex(i).shift(DOWN*2 ) if (i >= 8 ): formula = formula.scale(0.5 ).shift(RIGHT*2 ) taylor_graphs.append(graph) formula_labels.append(formula) current_graph = taylor_graphs[0 ] current_formula = formula_labels[0 ] self .play(Write(current_graph), Write(current_formula)) for next_graph, next_formula in zip (taylor_graphs[1 :], formula_labels[1 :]): self .play(Transform(current_graph, next_graph), Transform(current_formula, next_formula)) self .remove(current_graph, current_formula) current_graph = next_graph current_formula = next_formula self .play(Write(current_graph), Write(current_formula)) self .wait(2 ) def construct (self ): self .introduction() self .write_image() with tempconfig({"quality" : "medium_quality" , "preview" : True }): scene = SingleScene() scene.render()
下面进行逐段解释。
introduction
该函数为画图前的公式推导铺垫动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 def introduction (self ): intro = Text("这是野生的泰勒展开式" ) tex = Tex(r"$$f(x)=f(0)+f'(0)x+\frac{f''(0)}{2!}x^2+\dots+\frac{f^{(n)}(0)}{n!}x^n+o(x^n)$$" ) intro_sin = Text("将正弦函数代入其中" ).shift(UP*3 ) tex_sin = Tex(r'''$$ sin(x)= \boxed{sin(0)}+ cos(0)x+ \boxed{\frac{-sin(0)}{2!}x^2}+ \frac{-cos(0)}{3!}x^3+ \boxed{\frac{sin(0)}{4!}x^4}+ \frac{cos(0)}{5!}x^5+ \boxed{\frac{-sin(0)}{6!}x^6} $$ ''' ).shift(DOWN).scale(0.6 ) tex_2 = Tex(r""" $$ sin(x)= cos(0)x+ \frac{-cos(0)}{3!}x^3+ \frac{cos(0)}{5!}x^5 $$ """ ) tex_3 = Tex(r""" $$ sin(x)= \frac{x^1}{1!}+ \frac{-x^3}{3!}+ \frac{x^5}{5!} $$ """ ) tex_4 = Tex(r""" $$ sin(x)=\sum_{i=0}^{i} \frac { (-1)^{i} \cdot x^{2i+1}} {(2i+1)!} $$ """ ) self .play(Write(intro)) self .play(intro.animate.shift(UP * 3 )) self .play(Write(tex)) self .play(tex.animate.shift(UP)) self .play(Unwrite(intro)) self .play(Write(intro_sin)) self .play(Write(tex_sin)) self .play(Uncreate(tex)) self .play(Unwrite(intro_sin)) self .play(tex_sin.animate.shift(UP)) self .play(Transform(tex_sin, tex_2), run_time=2 ) self .remove(tex_sin) self .play(Transform(tex_2, tex_3), run_time=2 ) self .remove(tex_2) self .play(Transform(tex_3, tex_4), run_time=2 ) self .play(Unwrite(tex_3))
较为简单,用到Animations
和Tex
。
write_image
此程序的难点所在。
先来看两个工具函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def make_taylor_func (self, n_terms ): return lambda x: sum ( (-1 ) ** k * x ** (2 * k + 1 ) / factorial(2 * k + 1 ) for k in range (n_terms) ) def make_formula_tex (self, n_terms ): terms = [] for k in range (n_terms): sign = "-" if k % 2 == 1 else "" exponent = 2 * k + 1 term = rf"{sign} \frac{{x^{{{exponent} }}}} {{{exponent} !}}" terms.append(term) formula = "+" .join(terms).replace("+-" , "-" ) return MathTex("f(x) =" , formula).scale(0.8 ).to_edge(RIGHT)
make_taylor_func
用于求每一个子项的大小,相当于: $$
\sum_{i=0}^{i}
\frac
{
(-1)^{i} \cdot x^{2i+1}}
{(2i+1)!}
$$
通过for
循环生成了k
项,也就是这里的i 。
make_formula_tex
用于产生每个展开式对应的Latex
对象。先用正则匹配生成k
条字符串,放入数组并拼接起来得到最终的公式。需要注意部分项会产生-
,拼接后就变成+-
,需统一替换成-
。
最终write_image(self)
如下,我们逐段研究。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 def write_image (self ): ax = Axes( x_range=[-12 , 12 , 2 ], y_range=[-2 , 2 ], axis_config={"color" : GREEN}, ) pl = NumberPlane() sin = ax.plot(lambda x: np.sin(x), color=BLUE) self .play(Create(ax), Create(pl)) self .play(Write(sin)) taylor_graphs = [] formula_labels = [] for i in range (1 , 15 ): f = self .make_taylor_func(i) graph = ax.plot(f, color=RED) formula = self .make_formula_tex(i).shift(DOWN*2 ) if (i >= 8 ): formula = formula.scale(0.5 ).shift(RIGHT*2 ) taylor_graphs.append(graph) formula_labels.append(formula) current_graph = taylor_graphs[0 ] current_formula = formula_labels[0 ] self .play(Write(current_graph), Write(current_formula)) for next_graph, next_formula in zip (taylor_graphs[1 :], formula_labels[1 :]): self .play(Transform(current_graph, next_graph), Transform(current_formula, next_formula)) self .remove(current_graph, current_formula) current_graph = next_graph current_formula = next_formula self .play(Write(current_graph), Write(current_formula)) self .wait(2 )
section 1
第一部分建立坐标系,并画图,难度不大。
1 2 3 4 5 6 7 8 9 10 11 ax = Axes( x_range=[-12 , 12 , 2 ], y_range=[-2 , 2 ], axis_config={"color" : GREEN}, ) pl = NumberPlane() sin = ax.plot(lambda x: np.sin(x), color=BLUE) self .play(Create(ax), Create(pl)) self .play(Write(sin))
section 2
第二部分先是建立两个数组用于存放未来要画的泰勒函数图像,公式标签在右下角展示使视频更直观。通过循环生成了
15 个表达式,f
为我们根据make_taylor_func
建立的函数对象。将函数对象 f
放入ax.plot()
中即可得到图像对象,公式formula
根据make_formula_tex()
处理得到。
由于公式较长时会溢出屏幕,需在 i ≥ 8 时缩小其尺寸。
1 2 3 4 5 6 7 8 9 10 11 taylor_graphs = [] formula_labels = [] for i in range (1 , 15 ): f = self .make_taylor_func(i) graph = ax.plot(f, color=RED) formula = self .make_formula_tex(i).shift(DOWN*2 ) if (i >= 8 ): formula = formula.scale(0.5 ).shift(RIGHT*2 ) taylor_graphs.append(graph) formula_labels.append(formula)
section 3
先画出第一个展开式的图像,后续展开式则用数组和循环批量处理。后续通过切片语法从第二个展开式开始迭代。每一次更新都播放衔接的过渡动画,移除当前处理的公式、函数图像。并将current_xxx
指针指向下一个带处理的对象。留下最后一组时,上一组对象已被循环移除,直接播放动画即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 current_graph = taylor_graphs[0 ] current_formula = formula_labels[0 ] self .play(Write(current_graph), Write(current_formula))for next_graph, next_formula in zip (taylor_graphs[1 :], formula_labels[1 :]): self .play(Transform(current_graph, next_graph), Transform(current_formula, next_formula)) self .remove(current_graph, current_formula) current_graph = next_graph current_formula = next_formula self .play(Write(current_graph), Write(current_formula))self .wait(2 )
这里的zip
函数用于将两个数组并排循环,本质仍在对数组进行迭代,例如:
1 2 3 4 5 6 7 8 9 10 a = [1 , 2 , 3 ] b = ['a' , 'b' , 'c' ] for x, y in zip (a, b): print (x, y)