用SymPy自动计算抛物线求根、判别式与顶点
痛点场景还原假设我要做一个演示固定a1, c2让b从 -3 滑到 3观察抛物线与 x 轴交点个数的变化。如果纯手算我可能会这样写Manim代码from manim import * import math class PainfulDemo(Scene): def construct(self): a, c 1, 2 b_tracker ValueTracker(-3) axes Axes(x_range[-5,5], y_range[-1,6]) # 抛物线 graph always_redraw(lambda: axes.plot( lambda x: a*x**2 b_tracker.get_value()*x c )) # 计算交点 —— 这里就是噩梦开始的地方 def get_roots(): b b_tracker.get_value() disc b**2 - 4*a*c if disc 0: root1 (-b math.sqrt(disc)) / (2*a) # 负数平方根直接报错 root2 (-b - math.sqrt(disc)) / (2*a) return [root1, root2] else: return [] # 如果忘了判断上面一行就炸了 dots always_redraw(lambda: VGroup(*[ Dot(axes.coords_to_point(r, 0)) for r in get_roots() ])) self.add(axes, graph, dots) self.play(b_tracker.animate.set_value(3), run_time5) self.wait(1)我必须手动写出求根公式反复检查符号。判别式0时要手动跳过否则math.sqrt抛异常动画中断。得到的只是浮点近似值不能显示精确的根式表达如 √22。如果再加「自动标注顶点」还得再手算一次导数或配方法。这些体力活完全可以交给符号计算库SymPy让动画代码只关心“展示什么”而不是“怎么算”。2. SymPy 解决方案介绍SymPy 可以帮我们把求根、判别式计算、顶点坐标求解全部自动化而且返回精确的符号表达式。import sympy as sp x sp.Symbol(x, realTrue) a_val, c_val 1, 2 b_sym sp.Symbol(b) # 定义二次函数 expr a_val * x**2 b_sym * x c_val # 1. 判别式 delta b_sym**2 - 4 * a_val * c_val # b² - 8 # 2. 求根 —— 一行搞定自动给出含根号的精确解 roots sp.solve(expr, x) # 输出[-b/2 - sqrt(b**2 - 8)/2, -b/2 sqrt(b**2 - 8)/2] # 3. 求顶点导数求极值 vertex_x sp.solve(sp.diff(expr, x), x)[0] # -b/2 vertex_y expr.subs(x, vertex_x) # 代入得到顶点纵坐标solve返回的根自带根号当判别式0时它会变成复数形式如-b/2 - I*sqrt(8 - b**2)/2我们只需判断虚部是否为 0 就能筛出实根。顶点坐标也不用背公式diff求导 solve一步到位。在Manim中我们只需要把数值b传入SymPy表达式调用evalf()就可以快速得到高精度结果彻底告别手写公式。3. Manim 联动实战下面是一个完整的动画场景b变化时抛物线、交点、顶点、判别式与交点个数文本全部自动更新。from manim import * import sympy as sp class QuadraticRootDance(Scene): def construct(self): # SymPy 符号准备 x_sym sp.Symbol(x, realTrue) a_val, c_val 1, 2 # 固定 a, c只让 b 变化 b_sym sp.Symbol(b) expr a_val * x_sym**2 b_sym * x_sym c_val # 判别式表达式 delta_expr b_sym**2 - 4 * a_val * c_val # b² - 8 # 顶点 x 坐标求导 vertex_x_expr sp.solve(sp.diff(expr, x_sym), x_sym)[0] # -b/2 # 顶点 y 坐标 vertex_y_expr expr.subs(x_sym, vertex_x_expr) # Manim 场景搭建 axes Axes( x_range[-5, 5, 1], y_range[-1, 7, 1], axis_config{include_numbers: True, font_size: 18}, tipsFalse, ).add_coordinates() self.play(Create(axes)) b_tracker ValueTracker(-3) # b 初始值 -3 # 抛物线always_redraw 保证系数一更新图像就重绘 graph always_redraw( lambda: axes.plot( lambda x: a_val * x**2 b_tracker.get_value() * x c_val, colorBLUE ) ) self.add(graph) # 交点集合实心圆点 roots_dots always_redraw( lambda: self.get_roots_dots(axes, b_tracker, x_sym, expr) ) self.add(roots_dots) # 顶点标记 vertex_dot always_redraw( lambda: self.get_vertex_dot(axes, b_tracker, vertex_x_expr, vertex_y_expr) ) self.add(vertex_dot) # 动态文本判别式 交点个数 info_text always_redraw( lambda: self.get_info_text(b_tracker, delta_expr, x_sym, expr) ) info_text.to_corner(UR) self.add(info_text) # 动画b 从 -3 滑到 3 self.play(b_tracker.animate.set_value(3), run_time5, rate_funclinear) self.wait() # ---------- 辅助方法内部封装 SymPy 计算---------- def get_roots_dots(self, axes, tracker, x_sym, expr): 返回当前参数下所有实根对应的 Dot b_val tracker.get_value() # 用 SymPy 解方程并数值化 roots sp.solve(expr.subs(b, b_val), x_sym) real_roots [] for r in roots: r_num complex(r.evalf()) # 转为 Python 复数判断虚实 if abs(r_num.imag) 1e-8: # 虚部为 0 - 实根 real_roots.append(r_num.real) # 为每个实根创建红点 dot_group VGroup() for rx in real_roots: dot_group.add(Dot(axes.coords_to_point(rx, 0), colorRED)) return dot_group def get_vertex_dot(self, axes, tracker, vx_expr, vy_expr): 返回顶点位置的 Dot b_val tracker.get_value() vx float(vx_expr.subs(b, b_val).evalf()) vy float(vy_expr.subs(b, b_val).evalf()) return Dot(axes.coords_to_point(vx, vy), colorYELLOW) def get_info_text(self, tracker, delta_expr, x_sym, expr): 生成判别式与交点个数的信息文本 b_val tracker.get_value() delta_val float(delta_expr.subs(b, b_val).evalf()) # 判断实根个数用 solve 求全部根再筛实根 roots sp.solve(expr.subs(b, b_val), x_sym) real_count sum(1 for r in roots if abs(complex(r.evalf()).imag) 1e-8) text1 MathTex( f\\Delta {delta_val:.2f}, tex_to_color_map{f\\Delta {delta_val:.2f}: GREEN}, font_size24, ) text2 MathTex( f\\text{{交点个数}}{real_count}, tex_templateTexTemplateLibrary.ctex, font_size24, ) text VGroup(text1, text2).arrange(RIGHT, buff1).shift(UP) return text关键点解释用SymPy提前准备好符号表达式always_redraw里只做数值代入 求值保证运行流畅。用complex(r.evalf()).imag判断虚部是否为 0优雅地区分实根与复根完全不用手动写条件分支。顶点坐标直接由diff推导动画中总有一个黄色圆点稳稳跟随抛物线顶点。左上角文本实时显示判别式的值和交点个数看一眼就能对应上「$ \Delta 0 两个交点两个交点 \Delta0 一个交点一个交点 \Delta0 $无交点」。4. 效果展示说明运行这个场景你会看到一根蓝色抛物线开口向上a1与 y 轴交于 2。随着b从-3匀速滑到3开始b-3时判别式 Δ10Δ10抛物线与 x 轴有两个红色交点。当 b 经过 $ -\sqrt{8} \approx -2.828 时两交点靠拢∗∗重合为一个点∗∗时两交点靠拢∗∗重合为一个点∗∗ \Delta0 $此时左上角显示「交点个数1」。紧接着 ΔΔ变成负数所有红点消失抛物线悬浮在x轴上方与x轴无交点。当 b 跨越 √88时两点再次出现并逐渐远离。整个过程黄色顶点一直精准地落在抛物线最低点随b移动而滑动。左上角的 ΔΔ 数值和交点个数同步刷新完全不需要手动干预。5. 小结SymPy在Manim动画里的角色很纯粹把数学计算还给计算机把视觉表达留给你。手算求根公式、判断判别式、求导数零点……这些重复且易错的体力活SymPy一句solve、一句diff就能完美代劳。动画代码的逻辑因此变得清晰——你只负责告诉Manim“什么东西应该画在什么位置”而“位置怎么算”就让SymPy这个符号大脑去完成。合集: manim动画分类: databook标签: databook, manim免责声明本内容来自平台创作者博客园系信息发布平台仅提供信息存储空间服务。好文要顶 关注我 收藏该文 微信分享wang_yb粉丝 - 717 关注 - 6加关注00升级成为会员« 上一篇 一次函数图像工厂用 SymPy 自动生成 ykxb 对比动画» 下一篇 用SymPy自动求解三角形构造与全等条件验证posted 2026-06-05 16:31 wang_yb 阅读(113) 评论(0) 收藏 举报刷新页面返回顶部