在Manim中绘制布朗运动时路径为何不连续?
问题:Manim中布朗运动路径动画出现异常间断
我尝试在Manim中制作布朗运动路径的动画,但绘制出的路径出现了不应有的间断。
以下是效果截图(白色路径为布朗运动,绿色路径为二次变差):
使用环境:Python 3.9.13、Manim CE 0.19.0
相关代码:
from manim import * import numpy as np class BrownianPath(): def __init__(self, drift = 0.0, sigma = 1.0): self.drift = drift self.sigma = sigma self.T = [0] self.path = [0] self.qv = [0] self.rng = np.random.default_rng() def increment(self, dT): self.path.append(self.path[-1] + self.drift*dT + self.rng.normal()*self.sigma*np.sqrt(dT)) self.qv.append(self.qv[-1] + (self.path[-1] - self.path[-2])**2) self.T.append(self.T[-1] + dT) def run_for_time(self, T = 1.0, n = 100): for t in np.arange(0, T, T/n): self.increment(T/n) self.T.append(self.T[-1] + T/n) def get_last_path(self): return self.path[-1] def get_last_qv(self): return self.qv[-1] def get_last_T(self): return self.T[-1] class Brownian(Scene): def construct(self): axes = Axes([0, 5], [-1, 6]) self.add(axes) # Create the brownian path bm = BrownianPath(drift=0.1) # Animate its path and its quadratic variation self.path = VGroup() self.path.add(Line(axes.c2p(0, 0, 0), axes.c2p(0, 0, 0))) self.qv = VGroup() self.qv.add(Line(axes.c2p(0, 0, 0), axes.c2p(0, 0, 0))) def brownian_updater(mob, dt): bm.increment(dt) mob.move_to(axes.c2p(bm.get_last_T(), bm.get_last_path(), 0)) def qv_updater(mob, dt): # assumes brownian updater called first (?) mob.move_to(axes.c2p(bm.get_last_T(), bm.get_last_qv(), 0)) def add_path_element(): last_line = self.path[-1] self.path.add(Line(last_line, axes.c2p(bm.get_last_T(), bm.get_last_path(), 0))) return self.path def add_qv_element(): last_line = self.qv[-1] self.qv.add(Line(last_line, axes.c2p(bm.get_last_T(), bm.get_last_qv(), 0), color=GREEN)) return self.qv dot = Dot(radius=0.07, color=BLUE) dot.move_to(axes.c2p(0, 0, 0)) dot_qv = Dot(radius=0.08, color=GREEN) dot_qv.move_to(axes.c2p(0, 0, 0)) dot.add_updater(brownian_updater) dot_qv.add_updater(qv_updater) curve_to_path = always_redraw(add_path_element) curve_to_qv = always_redraw(add_qv_element) self.add(dot, dot_qv) self.add(curve_to_path, curve_to_qv) self.wait(5)
我猜测这可能与场景调用更新器的顺序不一致有关?
解决方案
问题根源在于更新器执行顺序不确定,加上always_redraw的回调逻辑和路径添加逻辑冲突:
- Manim不保证多个对象更新器的执行顺序,你原代码中依赖
brownian_updater先执行,但实际可能出现顺序颠倒,导致路径点和点的位置不同步; always_redraw每帧都会调用添加线段的函数,导致路径中不断累积冗余线段,同时因为更新顺序问题,出现跳变间断。
方法一:统一更新逻辑(推荐)
用一个全局更新函数处理所有逻辑,避免依赖更新器顺序,同时用VMobject的set_points_as_corners动态更新路径,无需手动添加线段:
from manim import * import numpy as np class BrownianPath(): def __init__(self, drift = 0.0, sigma = 1.0): self.drift = drift self.sigma = sigma self.T = [0] self.path = [0] self.qv = [0] self.rng = np.random.default_rng() def increment(self, dT): self.path.append(self.path[-1] + self.drift*dT + self.rng.normal()*self.sigma*np.sqrt(dT)) self.qv.append(self.qv[-1] + (self.path[-1] - self.path[-2])**2) self.T.append(self.T[-1] + dT) def get_points(self, axes): return [axes.c2p(t, val, 0) for t, val in zip(self.T, self.path)] def get_qv_points(self, axes): return [axes.c2p(t, val, 0) for t, val in zip(self.T, self.qv)] class Brownian(Scene): def construct(self): axes = Axes([0, 5], [-1, 6]) self.add(axes) bm = BrownianPath(drift=0.1) # 初始化路径和二次变差曲线 path_curve = VMobject(color=WHITE) qv_curve = VMobject(color=GREEN) path_curve.set_points_as_corners([axes.c2p(0,0,0)]) qv_curve.set_points_as_corners([axes.c2p(0,0,0)]) dot = Dot(radius=0.07, color=BLUE).move_to(axes.c2p(0,0,0)) dot_qv = Dot(radius=0.08, color=GREEN).move_to(axes.c2p(0,0,0)) def update_all(mob, dt): bm.increment(dt) # 更新整条路径的点 path_curve.set_points_as_corners(bm.get_points(axes)) qv_curve.set_points_as_corners(bm.get_qv_points(axes)) # 更新点的位置 dot.move_to(path_curve.get_last_point()) dot_qv.move_to(qv_curve.get_last_point()) # 给路径添加全局更新器,确保所有逻辑按顺序执行 path_curve.add_updater(update_all) self.add(dot, dot_qv, path_curve, qv_curve) self.wait(5)
方法二:修复原逻辑的更新顺序问题
如果要保留原结构,需要统一增量逻辑,避免重复添加线段:
- 替换原有的
add_path_element和add_qv_element,每次清空路径后重新生成所有线段:
def add_path_element(): self.path.clear() prev_point = axes.c2p(bm.T[0], bm.path[0], 0) for t, val in zip(bm.T[1:], bm.path[1:]): curr_point = axes.c2p(t, val, 0) self.path.add(Line(prev_point, curr_point)) prev_point = curr_point return self.path def add_qv_element(): self.qv.clear() prev_point = axes.c2p(bm.T[0], bm.qv[0], 0) for t, val in zip(bm.T[1:], bm.qv[1:]): curr_point = axes.c2p(t, val, 0) self.qv.add(Line(prev_point, curr_point, color=GREEN)) prev_point = curr_point return self.qv
- 移除两个点的独立更新器,改用全局更新器确保只执行一次增量:
def global_updater(mob, dt): bm.increment(dt) dot.move_to(axes.c2p(bm.get_last_T(), bm.get_last_path(), 0)) dot_qv.move_to(axes.c2p(bm.get_last_T(), bm.get_last_qv(), 0)) dot.add_updater(global_updater)
内容的提问来源于stack exchange,提问作者tompdavis
相关产品推荐
相关产品推荐

