【manim】3b1b的"Almost" Fourier Transform复刻

Posted on 2022-07-12 20:53  罗芭Remoo  阅读(340)  评论(0编辑  收藏  举报

最近在做Fourier Transform的内容,记录一下今天下午的成果。

本文代码全部自行编写,需要math and music项目完整工程可以在gayhub上获取。(现在还没弄完,就先不发了。)

概要

第一部分:

    图像代码部分原理很直接,即极坐标参数方程的转化

第二部分:

  关于图像质点中心的问题,数学上需要使用复数与微积分的知识求出。

    3b1b的原代码也是将质点的x-coordinate of center of mass图像直接用公式绘制。

    但是本文使用的是暴力的方法,直接用Mobject的get_center通过帧updater描点的方法绘制离散的点集。如果需要,可以拟合为曲线。

  

原视频:https://www.youtube.com/watch?v=spUNpyF58BY&t=188s

bilibili:

https://www.bilibili.com/video/BV1pW411J7s8?share_source=copy_web

数学公式在geogebra中构建如下:

即将cos(mx)+1转化为参数方程[(1+cos(mt))·cos(nt),(1+cos(mt))·sin(nt)]

关于sin和cos的选择:

正弦和余弦差别在于初始相位。这里出于教学考虑用余弦而不是正弦,主要是因为复指数函数的实部是余弦。如果用正弦的话,会发现质心的纵坐标而不是横坐标在频率重合的时候达到峰值(而且是负的)。

 

manim代码:

创建的Mobject有:axes axes_label polarPlane D_delta D_rou D_move

  以及三个图像:axes_graph polar_graph para_graph

然后使用add_update()添加DecimalNumber作为变化数值到动画更新函数即可。

第一部分代码:

class FourierTrans_First(Scene):
    def construct(self):
        axes = Axes(
            x_range=[-0, 2*PI, PI/4],
            y_range=[-0.1, 2.5, 1],
            x_length=14,
            y_length=2
            ).to_edge(UP)
        axes_label = axes.get_axis_labels('Time','Intensity')
        polarPlane = PolarPlane(
            radius_max=2.0,
            azimuth_units="TAU radians",
            size=4,
            azimuth_label_font_size=24,
            radius_config={"font_size": 24},
            ).add_coordinates().to_edge(DOWN+LEFT)
        #显示坐标与坐标标签
        self.add(axes,axes_label,polarPlane)

        #一些全局参数-修改这三个参数就好
        delta = 0#偏移量
        rou = 20#theta的系数
        move = 10#极坐标变换的变化程度系数
        dn_kwargs = {'num_decimal_places':3,}
        D_delta = DecimalNumber(delta,**dn_kwargs)
        D_rou = DecimalNumber(rou,**dn_kwargs)
        D_move = DecimalNumber(move,**dn_kwargs).shift(DOWN)

        #解析式-公式修改这里就好
        r = lambda t: np.sin(D_rou.get_value()*t)+1
        par = lambda t: np.array([
            (r(t))*np.cos(D_move.get_value()*t),
            (r(t))*np.sin(D_move.get_value()*t),
            0])
        #更新函数
        def Func_axes():
            return axes.plot(r,color=PINK)
    
        def Func_polar():
            return polarPlane.plot_polar_graph(r, [0, 2 * PI], color=ORANGE)

        def Func_para():
            para = ParametricFunction(
                par, 
                t_range = np.array([0, 3.14*2]),
                fill_opacity=0)\
                .set_color(color=[RED,YELLOW,BLUE,RED])\
                .shift(DOWN*2+RIGHT*2)
            self.add(Dot(para.get_center()))
            return para

        axes_graph = Func_axes()
        polar_graph = Func_polar()
        para_graph = Func_para()

        axes_graph.add_updater(lambda mob: mob.become(Func_axes()))
        polar_graph.add_updater(lambda mob: mob.become(Func_polar()))
        para_graph.add_updater(lambda mob:mob.become(Func_para()))

        self.add(D_rou,D_move)
        self.play(Write(axes_graph),Write(polar_graph),Write(para_graph))
        #self.play(ChangeDecimalToValue(D_rou,20),run_time=5,rate_func=linear)
        self.play(ChangeDecimalToValue(D_move,20),run_time=5,rate_func=linear)
        self.wait(1)

 

第二部分(含一)代码:

from manim import *

class FourierTrans_First(Scene):
    def construct(self):
        #########全局参数-修改这5个参数就好#########
        delta = 0#偏移量
        rou = 0#theta的系数
        move = 1#极坐标变换的变化程度系数
        target = 1#追踪质心y坐标
        track_end = 18#扭扭的结尾
        ############################################
        dn_kwargs = {'num_decimal_places':3,}
        D_delta = DecimalNumber(delta,**dn_kwargs)
        D_rou = DecimalNumber(rou,**dn_kwargs).shift(LEFT)
        D_move = DecimalNumber(move,**dn_kwargs).next_to(D_rou,DOWN)
        D_target = DecimalNumber(target,**dn_kwargs).next_to(D_move,DOWN)


        #######解析式-修改这里 《公式r》 就好#######
        r = lambda t: np.sin(D_rou.get_value()*t)+1
        ############################################
        par = lambda t: np.array([
            (r(t))*np.cos(D_move.get_value()*t),
            (r(t))*np.sin(D_move.get_value()*t),
            0])


        #coordinate system & labels
        axes = Axes(
            x_range=[-0, 2*PI, PI/4],
            y_range=[-0.1, 2.5, 1],
            x_length=14,
            y_length=2
            ).to_edge(UP)
        axes_label = axes.get_axis_labels('Time','Intensity')

        #polar plane
        polarPlane = PolarPlane(
            radius_max=2.0,
            azimuth_units="TAU radians",
            size=4,
            azimuth_label_font_size=24,
            radius_config={"font_size": 24},
            ).add_coordinates().to_edge(DOWN+LEFT)

        #point track plane
        pointAxes = Axes(
            x_range=[0, track_end+track_end*0.3,1],
            y_range=[-0.5, 1],
            x_length=6,
            y_length=4
            ).to_edge(UP).to_edge(DOWN+RIGHT)


        #更新函数
        def Func_axes():
            return axes.plot(r,color=PINK)
    
        def Func_polar():
            return polarPlane.plot_polar_graph(r, [0, 2 * PI], color=ORANGE)

        def Func_para():
            para = ParametricFunction(
                par, 
                t_range = np.array([0, 3.14*2]),
                fill_opacity=0)\
                    .set_color(color=[RED,YELLOW,BLUE,RED])
            return para

        def Func_para_with_messPoint():
            para = ParametricFunction(
                par, 
                t_range = np.array([0, 3.14*2]),
                fill_opacity=0)\
                    .set_color(color=[RED,YELLOW,BLUE,RED])

            messPoint = Dot(pointAxes.coords_to_point(D_move.get_value(),para.get_y()),color=GREEN_B,radius=0.02)
            self.add(messPoint)
            return para

        axes_graph = Func_axes()
        polar_graph = Func_polar()
        para_graph = Func_para()

        #define messPoint
        messPoint = Dot(para_graph.get_center())

        #定义updater_func表达式
        axU = lambda mob: mob.become(Func_axes())
        poU = lambda mob: mob.become(Func_polar())
        paU = lambda mob:mob.become(Func_para().shift(DOWN+RIGHT*3))
        paU_with_messPoint = lambda mob:mob.become(Func_para_with_messPoint().move_to(para_graph))


        #添加updater
        axes_graph.add_updater(axU)
        polar_graph.add_updater(poU)
        para_graph.add_updater(paU)

        ################################################
        ####################开始动画####################
        ################################################

        #显示坐标与坐标标签
        group_axes = VGroup(axes,axes_label,axes_graph)
        group_polar = VGroup(polarPlane,polar_graph)
        self.add(D_rou,D_move)
        self.play(Write(group_axes),Write(group_polar),Write(para_graph))
        self.play(ChangeDecimalToValue(D_rou,track_end),run_time=14,rate_func=linear)

        #左下角的图像消失,右下角的图像挪来左边
        polar_graph.remove_updater(poU)
        self.play(FadeOut(polar_graph))

        para_graph.remove_updater(paU)
        self.play(para_graph.animate.move_to(group_polar))

        self.play(GrowFromCenter(pointAxes))

        #开始扭扭~并且加点追踪
        para_graph.add_updater(paU_with_messPoint)
        D_move.set_value(0)
        self.play(ChangeDecimalToValue(D_move,track_end),run_time=30,rate_func=linear)
        self.wait(1)