今天专门解决一个非常具体的痛点——因式分解公式的可视化演示。

问题说穿了很简单:
你想用 Manim 把 通过“十字相乘”的面积模型展示出来。第一步你就得琢磨——这个大矩形怎么拆成四块?哪块是 、、、6?它们的边长和位置又该怎么算?全得手动来。
好,这个问题搞定了。那么换个公式呢?改成 ,布局又得从头算一遍。
这就引出最核心的问题了:有没有办法只告诉程序“我要做这个多项式的拼图证明”,它就能自动把尺寸和坐标全部算好?
答案是:有,用 SymPy。
它能根据代数式的结构自动推导出几何布局,让你从“一个公式写一套代码”进化到“一类公式共用一套生成器”。
1. 痛点场景还原:公式一换,布局全乱
先来看一个典型的“手工布局”案例。
我们要把 的面积模型画出来,大矩形分成四块。代码大概长这样:
from manim import *class ManualX2Plus5xPlus6(Scene):
def construct(self):
x = 2 # 给 x 取个值来画图
# 手动定义四块的宽高
sq_x2 = Rectangle(width=x, height=x, color=BLUE, fill_opacity=0.4)
rec_3x = Rectangle(width=3, height=x, color=RED, fill_opacity=0.4)
rec_2x = Rectangle(width=x, height=2, color=RED, fill_opacity=0.4)
rec_6 = Rectangle(width=3, height=2, color=GREEN, fill_opacity=0.4)
# 手动拼装:左下角是 x^2,右边是 3x,上边是 2x,右上是 6
sq_x2.shift(LEFT * (3 / 2) + DOWN * (2 / 2))
rec_3x.next_to(sq_x2, RIGHT, buff=0)
rec_2x.next_to(sq_x2, UP, buff=0)
rec_6.next_to(rec_3x, UP, buff=0)
self.play(Create(sq_x2), Create(rec_3x), Create(rec_2x), Create(rec_6))
self.wait(1)
这段代码跑是能跑,但代价是每一处数字都是针对 硬编码的。如果要改成 ,那么:
- 常数项从 2、3 变成 3、4;
rec_3x的宽度要改成 4;rec_2x的高度要改成 3;- 小矩形
rec_6的宽高要改成 4 和 3; shift里的偏移量也得跟着重新算。
每一次换公式都是一次手工劳动,而且还很容易出错。要是公式结构复杂一点,比如二次项系数不是 1,或者涉及到三项展开,这种手动模式基本就撑不住了。
2. SymPy 解决方案:因式分解 → 几何布局
SymPy 的真正厉害之处就在这里:它可以对多项式进行因式分解或展开,然后自动把每一项的系数和次数全都提取出来。
而我们要做的,就是根据这些信息,自动生成对应的矩形拼图布局。
整个流程是这样的:
- 输入一个多项式字符串,比如
"x**2 + 5*x + 6"; - 让 SymPy 对它做因式分解,得到
(x + 2)*(x + 3); - 从因式结果中读出两个一次式的常数项(2 和 3),从展开式中读出各项系数(1, 5, 6);
- 自动生成四个矩形块:(边长 )、(宽 2 高 )、(宽 高 3)、
6(宽 2 高 3),然后根据这些边长自动算出它们左下角的坐标; - 最后把布局信息打包成一个统一的数据结构,交给
Manim去渲染。
这样做最大的好处是什么?不管输入是 、,甚至是带参数的 ,布局生成的逻辑完全一样——你只需要改一行输入字符串。
下面是一段纯 SymPy 的布局生成器代码,不依赖 Manim,可以直接在 Jupyter 里跑:
from sympy import symbols, factor, expand, Poly
from sympy.abc import xdef generate_layout(poly_expr):
"""
输入:一个关于 x 的多项式(可因式分解为 (x+a)(x+b))
输出:四个矩形的布局信息列表,每个矩形包括 (标签, 宽, 高, x坐标, y坐标)
"""
# 因式分解
factored = factor(poly_expr) # 例如 (x + 2)*(x + 3)
# 确保是两个一次因式
if not factored.is_Mul:
raise ValueError("多项式不能分解为两个一次因式")
factors = factored.args
if len(factors) != 2:
raise ValueError("暂时只支持两个一次因式")
# 提取常数项 a, b
a = None
b = None
for fac in factors:
if fac.is_Add:
p = Poly(fac, x)
if p.degree() == 1:
const = p.coeff_monomial(1) # 系数 1
const_term = p.coeff_monomial(0) # 常数项
if const == 1:
val = const_term
else:
val = const_term / const
if a is None:
a = val
else:
b = val
# 展开式获取各项
expanded = expand(poly_expr)
poly = Poly(expanded, x)
coeff_x2 = poly.coeff_monomial(2) # x^2 系数
coeff_x = poly.coeff_monomial(1) # x 系数
const = poly.coeff_monomial(0) # 常数项
# 返回布局,宽和高用数值表达式(含 x),留给 Manim 代入 x_val
layout = [
{"label": "x^2", "w": "x", "h": "x", "x0": 0, "y0": 0},
{"label": f"{b}x", "w": b, "h": "x", "x0": "x", "y0": 0},
{"label": f"{a}x", "w": "x", "h": a, "x0": 0, "y0": "x"},
{"label": f"{a*b}", "w": b, "h": a, "x0": "x", "y0": "x"},
]
return layout, a, b# 示例:x^2 + 5x + 6
layout, a, b = generate_layout(x**2 + 5*x + 6)
print("a =", a, " b =", b)
for item in layout:
print(item)
输出结果如下:
a = 2 b = 3
{'label': 'x^2', 'w': 'x', 'h': 'x', 'x0': 0, 'y0': 0}
{'label': '3x', 'w': 3, 'h': 'x', 'x0': 'x', 'y0': 0}
{'label': '2x', 'w': 'x', 'h': 2, 'x0': 0, 'y0': 'x'}
{'label': '6', 'w': 3, 'h': 2, 'x0': 'x', 'y0': 'x'}
可以看到,矩形的宽高和坐标全部由 a 和 b 决定,而 a,b 是从 SymPy 的因式分解结果中自动提取的。换个多项式,只需要改一行输入,布局自动就能生成。
3. Manim 联动实战:通用因式分解拼图动画
现在我们把这个自动布局引擎接入 Manim,做一个通用场景:只需要在开头指定一个多项式,动画就会自动展示对应的面积拼图,并标注出公式。
完整代码如下:
from manim import *
from sympy import factor, Poly
from sympy.abc import xclass AutoFactorPuzzle(Scene):
def construct(self):
# ========== 1. 在这里输入你要讲的多项式 ==========
poly_expr = x**2 + 5 * x + 6 # 试着改成 x**2 + 7*x + 12 看看!
x_val = 1.5 # 动画中 x 的具体取值(可调)
# ========== 2. SymPy 自动分析多项式 ==========
factored = factor(poly_expr)
if not factored.is_Mul or len(factored.args) != 2:
raise ValueError("多项式需能分解为两个一次因式 (x+a)(x+b)")
# 提取 a, b
roots = []
for fac in factored.args:
p = Poly(fac, x)
if p.degree() != 1:
raise ValueError("因式必须是一次式")
coeffs = p.all_coeffs()
root = -coeffs[1] / coeffs[0]
roots.append(root)
a, b = float(roots[0]), float(roots[1]) # 常数项 a, b
# ========== 3. 根据 a, b 生成矩形布局 ==========
blocks = [
{"label": "x^2", "w": x_val, "h": x_val, "x0": 0, "y0": 0, "color": BLUE},
{
"label": f"{abs(b)} \times x",
"w": abs(b),
"h": x_val,
"x0": x_val,
"y0": 0,
"color": RED,
},
{
"label": f"{abs(a)} \times x",
"w": x_val,
"h": abs(a),
"x0": 0,
"y0": x_val,
"color": YELLOW,
},
{
"label": f"{abs(b)} \times {abs(a)}",
"w": abs(b),
"h": abs(a),
"x0": x_val,
"y0": x_val,
"color": GREEN,
},
]
# ========== 4. Manim 绘制 ==========
for blk in blocks:
rect = Rectangle(
width=blk["w"],
height=blk["h"],
color=blk["color"],
fill_opacity=0.4,
stroke_width=1,
)
rect.move_to([blk["x0"] + blk["w"] / 2, blk["y0"] + blk["h"] / 2, 0])
rect.shift(LEFT * 2 + DOWN)
label = MathTex(f"{blk['label']}", font_size=16)
label.move_to(rect.get_center())
self.play(Create(rect), Write(label))
self.wait(0.5)
# 显示等式
eq = MathTex(
f"{{x}}^2 + {poly_expr.coeff(x,1)}x + {poly_expr.coeff(x,0)}",
"=",
f"(x{a if a<0 else abs(a)})(x{b if b<0 else abs(b)})",
font_size=20,
).shift(DOWN * 1.5)
self.play(Write(eq))
self.wait(2)
关键点需要说明一下:
- 多项式的输入只改
poly_expr = ...这一行,一整段代码就能自动绘制出对应的十字相乘面积图; - SymPy 的
factor自动拆出一次因式,Poly帮我们提取出系数,a和b直接成为矩形的边长参数; - 整个布局的坐标逻辑只写了一次,通过
a、b、x_val计算出blocks,完全没有手动偏移的操作。
试试把 poly_expr 改成 x**2 + 7*x + 12,重新运行一次。动画会立刻切换成 (蓝)、(红)、(黄)、(绿)的四块拼图,等式也会同步更新为 。
4. 效果展示说明
运行上面这个 AutoFactorPuzzle,你会看到:
- 四块矩形依次浮现:蓝色 在左下角,右侧是一块红色的 ,上方是一块黄色的 ,右上角是绿色的常数块 。所有块之间严丝合缝,因为它们的坐标和边长都来自同一个因式分解结果。
- 标签 、、、 精准地位于每块的中心,清晰对应着代数项。
- 画面下方会显示因式分解的等式——左边是展开式,右边是因式乘积。学生可以立刻把图形和公式对应起来。
更关键的是:换一个多项式只需要改一行代码,动画逻辑完全复用。
如果想一次性展示多个公式的对比,只需要建几个不同的场景实例即可。
这意味着,制作一套完整的“因式分解几何证明”微课,可以从一天的工作量缩短到一小时。
5. 小结
SymPy 在这个场景里扮演的角色,本质上是一个代数结构的自动解析器。
传统方法当然也能拼出图,但你需要用大脑去“解析”公式,再手动翻译成矩形的宽高和位置;
而 SymPy 帮你自动完成了“因式分解 → 提取系数 → 生成几何参数”的全过程,让你可以专注于教学设计本身和动画的视觉效果美化。
这个模式可以轻松扩展到:
- 二次三项式的十字相乘面积模型(就是本文的例子);
- 完全平方和平方差公式的拼图证明;
- 多项式乘法的面积表示,比如 的体积模型;
- 甚至更复杂的恒等式,只要你能把代数结构映射为空间的分割。
