多项式逼近正弦¶
每一个新的泰勒项使多项式 跟随更多正弦波的形状。增长的公式与其图像一同变化。
examples/taylor_series.py
"""变为正弦的多项式。
在 0 附近,sin x 看起来像 x。减去 x³/3!,三次项便随正弦一同弯回;加上 x⁵/5!,它便跟随下一
个转折;每一项都修正前一项留下的误差。次数为 2n + 1 的泰勒多项式在 0 处与 sin x 及其前
2n + 1 阶导数相匹配,其误差至多为 |x|^(2n+3)/(2n + 3)!:阶乘的增长快于任何幂,因此每一个
新项都在更宽的区间上贴合波形,而整个无穷级数处处等于正弦。
"""
from math import factorial
import numpy as np
import manimgx as m
WALL = 9.4 # 多项式将正弦追逐到边缘
DEGREES = list(range(1, 41, 2)) # 1, 3, …, 39
# 各项逐一写出,至 x¹⁹/19!;其余各项在最后涌入
WRITTEN = 10
X_MAX = 11.0 # 坐标轴的范围为 |x| ≤ X_MAX
Y_MAX = 3.0 # 且 |y| ≤ Y_MAX;多项式在超出范围处被截断
COLORS = m.color_gradient([m.YELLOW, m.RED, m.PURPLE_A], WRITTEN)
XS = np.linspace(-X_MAX, X_MAX, 2201)
def term(degree: int, x: np.ndarray) -> np.ndarray:
"""该(奇)次数对应的级数项:±x^degree / degree!。"""
sign = -1 if degree % 4 == 3 else 1
return sign * x**degree / factorial(degree)
def term_tex(degree: int) -> str:
if degree == 1:
return "x"
sign = "-" if degree % 4 == 3 else "+"
return sign + rf" \frac{{x^{{{degree}}}}}{{{degree}!}}"
def color(k: int) -> m.ManimColor:
"""具有 k + 1 项的多项式的颜色,及其最后一项的颜色。"""
return COLORS[min(k, WRITTEN - 1)]
def series_tex(count: int) -> m.MathTex:
"""sin x ≈ 前 `count` 项;超过七项时,中间各项归入 ⋯。"""
shown = list(range(count))
if count > 7:
shown = [0, 1, 2, 3, -1, count - 1]
parts = [r"\sin x", r"\approx"]
parts += [r"+ \cdots" if k < 0 else term_tex(DEGREES[k]) for k in shown]
tex = m.MathTex(*parts, font_size=50)
tex[0].set_color(m.BLUE)
for part, k in zip(tex[2:], shown, strict=True):
if k >= 0:
part.set_color(color(k))
return tex.move_to(3.25 * m.UP)
class TaylorSeries(m.Scene):
def construct(self) -> None:
axes = m.Axes(
x_range=[-X_MAX, X_MAX, np.pi],
y_range=[-Y_MAX, Y_MAX, 1],
x_length=13.4,
y_length=5.4,
tips=False,
axis_config={"stroke_width": 3, "color": m.GREY_B},
).move_to(0.7 * m.DOWN)
sine = axes.plot(
np.sin, x_range=[-X_MAX, X_MAX, 0.02], color=m.BLUE, stroke_width=6
)
# 多项式:`reach` 以下的所有项,以及下一项按其分数缩放
reach = m.ValueTracker(1.0)
def polynomial() -> m.VMobject:
done = int(reach.get_value())
s = reach.get_value() - done
ys = np.sum([term(d, XS) for d in DEGREES[:done]], axis=0)
if s > 0 and done < len(DEGREES):
ys = ys + s * term(DEGREES[done], XS)
# 将 0 附近的部分保持在坐标轴内,在超出处截断
inside = np.abs(ys) <= Y_MAX
middle = len(XS) // 2
left_out = np.nonzero(~inside[:middle])[0]
right_out = np.nonzero(~inside[middle:])[0]
lo = left_out[-1] + 1 if len(left_out) else 0
hi = middle + right_out[0] if len(right_out) else len(XS)
xs, ys_in = list(XS[lo:hi]), list(ys[lo:hi])
if lo > 0: # 它离开的确切位置,在每一侧
a, b = lo, lo - 1
bound = np.sign(ys[b]) * Y_MAX
t = (bound - ys[a]) / (ys[b] - ys[a])
xs.insert(0, XS[a] + t * (XS[b] - XS[a]))
ys_in.insert(0, bound)
if hi < len(XS):
a, b = hi - 1, hi
bound = np.sign(ys[b]) * Y_MAX
t = (bound - ys[a]) / (ys[b] - ys[a])
xs.append(XS[a] + t * (XS[b] - XS[a]))
ys_in.append(bound)
shade = color(done - 1)
if s > 0 and done < len(DEGREES):
shade = m.interpolate_color(color(done - 1), color(done), s)
# 超过已写出的项后变细,以便正弦在其周围显现
width = 7 - 3 * min(1.0, max(0.0, (reach.get_value() - WRITTEN) / 4))
curve = m.VMobject(stroke_color=shade, stroke_width=width)
curve.set_points_as_corners(axes.c2p(np.array(xs), np.array(ys_in)).T)
return curve
poly = m.always_redraw(polynomial)
series = series_tex(1)
self.add(axes)
self.play(m.Create(sine), run_time=1.6)
self.play(m.Create(poly), m.Write(series), run_time=1.2)
self.remove(poly)
poly = m.always_redraw(polynomial)
self.add(poly)
# 每一个新项使多项式沿波形多弯过一个转折
for count in range(2, WRITTEN + 1):
new_series = series_tex(count)
self.play(
reach.animate.set_value(float(count)),
m.TransformMatchingTex(series, new_series),
run_time=1.6,
)
series = new_series
self.wait(0.8)
# 所有项:级数即正弦
whole = m.MathTex(
r"\sin x",
"=",
r"\sum_{n=0}^{\infty} \frac{(-1)^n\, x^{2n+1}}{(2n+1)!}",
font_size=56,
)
whole[0].set_color(m.BLUE)
whole.move_to(3.2 * m.UP)
# 所有项同时出现:多项式在整个区间上与正弦重合
self.play(reach.animate.set_value(float(len(DEGREES))), run_time=2.5)
name, approx, terms = series[0], series[1], m.VGroup(*series[2:])
self.remove(series)
self.add(name, approx, terms)
self.play(m.FadeOut(terms, shift=0.4 * m.UP), run_time=0.8)
self.play(
m.ReplacementTransform(approx, whole[1]),
name.animate.move_to(whole[0]),
m.FadeIn(whole[2], shift=0.4 * m.UP),
run_time=1.4,
)
self.wait(2)
if __name__ == "__main__":
TaylorSeries().render("taylor_series.mp4")