You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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的回调逻辑和路径添加逻辑冲突:

  1. Manim不保证多个对象更新器的执行顺序,你原代码中依赖brownian_updater先执行,但实际可能出现顺序颠倒,导致路径点和点的位置不同步;
  2. 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)

方法二:修复原逻辑的更新顺序问题

如果要保留原结构,需要统一增量逻辑,避免重复添加线段:

  1. 替换原有的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
  1. 移除两个点的独立更新器,改用全局更新器确保只执行一次增量:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:17:04