在制作物理追及问题的动画时,常需要根据题意手动列方程求相遇时间,再逐一计算每时刻坐标。参数(速度、初始距离、出发时间差)一旦改变,所有计算就得重来,过程繁琐且易错。本文基于 Python 的 SymPy 符号计算库,将列方程和求解自动化,并联动 Manim 动画引擎,实现任意参数下追及问题的自动求解与可视化。
一、痛点场景
假设经典追及问题:甲从原点出发,速度 v1=2;乙从 x=10 处同向出发,速度 v2=5,问多久追上?若手工编写 Manim 动画,通常需要手动解方程:然后手动算出相遇位置,再硬编码轨迹函数。每次修改速度或初始距离,都需要重复上述过程,且容易在单位换算、正方向等细节上出错。
二、SymPy 自动求解
SymPy 可以将物理情景“翻译”成符号方程,自动求解。核心思路:定义时间符号 t,用符号表达式表示甲、乙的位置,令二者相等,调用 sp.solve() 直接得到相遇时间。
示例:同时出发,甲初始在乙前面 10 米,速度 v1=2,v2=5。- t = sp.symbols('t', positive=True)
- v1, v2 = 2, 5
- s0 = 10 # 初始距离
- pos1 = s0 + v1*t # 甲位置
- pos2 = v2*t # 乙位置
- eq = sp.Eq(pos1, pos2)
- solution = sp.solve(eq, t)
- print(solution) # 输出 [10/3]
复制代码
若甲先出发 2 秒(即乙出发时甲已运动 2 秒),只需调整表达式:- t_delay = 2
- pos1 = s0 + v1*(t + t_delay)
- eq = sp.Eq(pos1, pos2)
- solution = sp.solve(eq, t)
- print(solution) # 输出 [14/3]
复制代码
无论初始距离、速度差值还是出发延迟如何变化,只需修改符号表达式构建逻辑,求解交给 solve,相遇坐标代入即可获得精确值。
三、Manim 联动实战
将 SymPy 求解嵌入 Manim 动画场景,实现全自动追及动画。以下是一个完整示例,参数集中定义,动画完全自适应。
- from manim import *
- import sympy as sp
- class CatchUpLab(Scene):
- def construct(self):
- # ====== 可修改参数 ======
- v1 = 1.5 # 甲速度
- v2 = 2.5 # 乙速度
- init_gap = 8 # 初始距离(甲在乙前面)
- delay = 1 # 甲早出发时间
- # ====== SymPy 自动求解 ======
- t = sp.symbols("t", positive=True)
- pos1_expr = init_gap + v1 * (t + delay)
- pos2_expr = v2 * t
- eq = sp.Eq(pos1_expr, pos2_expr)
- t_meet = sp.solve(eq, t)[0] # 精确相遇时间
- meet_x = float(pos2_expr.subs(t, t_meet)) # 相遇位置
- def pos1_func(time):
- return init_gap + v1 * (time + delay)
- def pos2_func(time):
- return v2 * time
- # ====== 场景搭建 ======
- axes = NumberLine(
- x_range=[0, 30, 5],
- length=8,
- include_numbers=True,
- label_direction=DOWN,
- )
- self.play(Create(axes))
- dot1 = Dot(color=RED, radius=0.2)
- dot2 = Dot(color=BLUE, radius=0.2)
- dot1.move_to(axes.number_to_point(pos1_func(0)))
- dot2.move_to(axes.number_to_point(pos2_func(0)))
- self.add(dot1, dot2)
- label1 = Text("甲", color=RED).next_to(dot1, UP*2)
- label2 = Text("乙", color=BLUE).next_to(dot2, UP*2)
- self.add(label1, label2)
- trace1 = TracedPath(dot1.get_center, stroke_color=RED, stroke_width=2)
- trace2 = TracedPath(dot2.get_center, stroke_color=BLUE, stroke_width=2)
- self.add(trace1, trace2)
- meet_dot = Dot(point=axes.number_to_point(meet_x), color=YELLOW)
- meet_label = Text(f"相遇点: {meet_x:.2f}", font_size=24, color=YELLOW)
- meet_label.next_to(meet_dot, UP*1.5)
- time_text = MathTex("t=0.0").shift(UL*2)
- self.add(time_text)
- total_time = float(t_meet) + 2
- def update_dots(mob, alpha):
- t_now = alpha * total_time
- dot1.move_to(axes.number_to_point(pos1_func(t_now)))
- dot2.move_to(axes.number_to_point(pos2_func(t_now)))
- label1.next_to(dot1, UP*2)
- label2.next_to(dot2, UP*2)
- time_text.become(MathTex(f"t={t_now:.1f}").shift(UL*2))
- if t_now >= float(t_meet):
- self.add(meet_dot, meet_label)
- self.play(
- UpdateFromAlphaFunc(
- VGroup(dot1, dot2, label1, label2, time_text),
- update_dots,
- run_time=total_time,
- rate_func=linear,
- )
- )
- self.wait(1)
复制代码
关键设计:
- 所有参数集中在脚本开头,修改 v1、v2、init_gap、delay 即可改变题目,SymPy 自动重新求解相遇时间和位置,动画完全自适应。
- 使用 UpdateFromAlphaFunc 驱动动画,每帧根据当前时间计算两个点的坐标,坐标函数直接由 SymPy 表达式生成,精确无误差。
- 相遇点预先通过 sp.solve 得到精确值,当动画时间超过相遇时刻时,黄色标记出现,直观展示“乙追上甲”的瞬间。
- 未采用 always_redraw 而使用 UpdateFromAlphaFunc,更好地控制动画进度和时间显示,避免复杂依赖更新。
四、效果说明
运行上述场景,你将看到:一条水平数轴,红点(甲)在蓝点(乙)前方。动画开始后,两点同时向右移动,蓝点速度更快逐渐逼近红点。到达精确相遇时刻,黄色圆点出现在相遇位置并标注坐标,甲和乙重合。
若修改参数——例如甲速度从 1.5 改为 1.2,初始距离改为 12,甲提前出发 3 秒——只需修改脚本顶部几个数字,无需任何手动计算,动画仍准确呈现追及过程,并在正确位置标记相遇点。
注意:若参数设置导致无法追上(如乙速度小于甲),sp.solve 将返回空或负解,可在代码中增加判断逻辑,提示无法相遇,避免误标。
五、拓展思路
追及问题的自动化求解与动画联动,核心价值在于将“根据题意建立方程”和“求解”这两步程序化。对于其他行程问题(相遇、环形跑道、顺流逆流等),只要能将物理情景转化为代数方程,SymPy 就能自动解出关键节点,Manim 负责将节点转化为流畅的视觉呈现。两个工具分工明确,既精准又灵活。
(参考来源:脚本之家《Python使用SymPy自动求解追及问题的方程》) |