Python使用SymPy和Manim輕松搞定導(dǎo)數(shù)動畫
大家好,你有沒有試過在 Manim 里做導(dǎo)數(shù)定義的動畫?
就是那個經(jīng)典的場景:畫一條曲線,再畫一條割線,然后讓割線上的一個點(diǎn)無限逼近另一個點(diǎn),最后變成切線。
這個過程的核心是計算割線的斜率 (f(x+h) - f(x)) / h,并觀察當(dāng) h 趨近于 0 時,這個斜率是如何變化的。
聽起來很簡單,但實(shí)際操作起來,手動去推導(dǎo)極限、計算每一幀的坐標(biāo),不僅繁瑣,還特別容易出錯。
相信不少朋友都為此頭疼過。
想象一下,我們要為函數(shù)f(x)=x3−2x+1 做一個在x=1 處的切線動畫。
- 定義割線:我們需要兩個點(diǎn),P(1,f(1)) 和Q(1+h,f(1+h))。
- 計算斜率:slope=(f(1+h)−f(1))/h。
- 求極限:為了讓動畫平滑過渡到切線,我們需要知道當(dāng) h→0 時,slope 的精確值,也就是f′(1)。
- 動態(tài)更新:在動畫中,h是一個不斷變小的值(比如從 1 變到 0.01),我們需要為每一個 h 實(shí)時計算 Q 點(diǎn)的坐標(biāo)和割線的斜率。
如果手動來做,第2、3步就需要展開(1+h)3−2(1+h)+1,再減去f(1),化簡,最后求極限。
對于復(fù)雜的函數(shù),這簡直是災(zāi)難?。ū热绾瘮?shù)f(x)=sin(x2))
而且,在代碼里硬編碼這些公式,一旦函數(shù)變了,所有計算都得重來。
這就是我們的痛點(diǎn):動態(tài)、精準(zhǔn)、自動化地處理符號計算。
SymPy 解決方案:讓計算機(jī)做數(shù)學(xué)
SymPy 正是解決這個問題的完美工具,它可以把 x, h 當(dāng)作真正的數(shù)學(xué)符號來處理,而不是具體的數(shù)字。
針對我們的需求,只需要兩個核心函數(shù):
diff(f, x): 自動求導(dǎo)。直接告訴我們f(x)的導(dǎo)函數(shù)是什么。limit(expr, h, 0): 計算極限??梢则?yàn)證我們的割線斜率在h->0時的確等于導(dǎo)數(shù)值。
下面看一段核心的 SymPy 代碼,感受一下它的威力:
from sympy import symbols, diff, limit
# 定義符號變量
x = symbols('x')
# 定義我們的函數(shù) f(x)
f = x**3 - 2*x + 1
# --- 核心操作 ---
# 自動求導(dǎo),得到 f'(x)
f_prime = diff(f, x)
print(f"導(dǎo)函數(shù) f'(x) = {f_prime}")
# 輸出: 導(dǎo)函數(shù) f'(x) = 3*x**2 - 2
# 在 x=1 處的導(dǎo)數(shù)值
slope_at_1 = f_prime.subs(x, 1)
print(f"x=1 處的瞬時變化率 (斜率) = {slope_at_1}")
# 輸出: x=1 處的瞬時變化率 (斜率) = 1
# 用極限來驗(yàn)證割線斜率
# 割線斜率表達(dá)式
secant_slope_expr = (f.subs(x, 1+h) - f.subs(x, 1)) / h
# 計算 h->0 時的極限
limit_slope = limit(secant_slope_expr, h, 0)
print(f"通過極限計算得到的斜率 = {limit_slope}")
# 輸出: 通過極限計算得到的斜率 = 1
看!我們完全不用關(guān)心中間復(fù)雜的代數(shù)運(yùn)算,SymPy 幾行代碼就幫我們完成了求導(dǎo)和極限驗(yàn)證,并且結(jié)果精確無誤。
這為我們接下來的 Manim 動畫提供了堅實(shí)的數(shù)學(xué)基礎(chǔ)。
Manim 聯(lián)動實(shí)戰(zhàn):讓切線“動”起來
現(xiàn)在,我們將 SymPy 的計算能力嵌入到 Manim 動畫中。
我們將使用 ValueTracker 來控制 h 的值,讓它從一個較大的數(shù)(如1)逐漸減小到接近0。
在每一幀,Manim 都會調(diào)用 SymPy 重新計算 Q 點(diǎn)的位置和割線,從而實(shí)現(xiàn)動態(tài)效果。
下面是核心的代碼:
from manim import *
from sympy import symbols, lambdify, diff
class DerivativeAnimation(Scene):
def construct(self):
# ========== SymPy 符號計算部分 ==========
x_sym = symbols("x")
f_sym = x_sym**3 - 2*x_sym + 1 # 原函數(shù):f(x) = x3 - 2x + 1
f = lambdify(x_sym, f_sym, "numpy") # 轉(zhuǎn)為 NumPy 函數(shù)供繪圖
f_prime_sym = diff(f_sym, x_sym) # SymPy 自動求導(dǎo):f'(x) = 3x2 - 2
x_p = 1 # 切點(diǎn)橫坐標(biāo)
exact_k = float(f_prime_sym.subs(x_sym, x_p)) # 精確導(dǎo)數(shù) f'(1) = 1
# ========== Manim 坐標(biāo)系與曲線 ==========
ax = Axes(x_range=[-2, 3], y_range=[-3, 5])
graph = ax.plot(f, color=YELLOW) # 原函數(shù)曲線
p_point = Dot(ax.c2p(x_p, f(x_p)), color=RED) # 切點(diǎn) P
# ========== ValueTracker 驅(qū)動割線動態(tài)逼近 ==========
h_tracker = ValueTracker(1) # h 從 1 逐漸減小到 0.001
# 割線:隨 h 變化而重新繪制
def get_secant_line():
h_val = h_tracker.get_value()
x_q = x_p + h_val
k = (f(x_q) - f(x_p)) / h_val # 割線斜率 Δy/Δx
return ax.plot(
lambda x: k * (x - x_p) + f(x_p), # 點(diǎn)斜式
color=GREEN, x_range=[x_p - 1, x_q + 1]
)
secant_line = always_redraw(get_secant_line)
# 切線:使用 SymPy 算出的精確導(dǎo)數(shù)
tangent_line = ax.plot(
lambda x: exact_k * (x - x_p) + f(x_p),
color=PURPLE, x_range=[-0.5, 2.5]
)
# ========== 動畫流程 ==========
self.play(Create(ax), Create(graph), Create(p_point))
self.play(Create(secant_line))
# 核心:h → 0,割線動態(tài)逼近切線
self.play(
h_tracker.animate.set_value(0.001),
run_time=5,
rate_func=rate_functions.ease_in_out_quad,
)
# 對比展示精確切線
self.play(Create(tangent_line))
self.wait(1)
代碼關(guān)鍵點(diǎn)解析
lambdify: 連接SymPy和Manim的橋梁。它把SymPy的符號表達(dá)式f_sym轉(zhuǎn)換成一個普通的Python函數(shù)f,這個函數(shù)可以接受NumPy數(shù)組作為輸入,正好符合 Manimax.plot()的要求。ValueTracker:Manim中創(chuàng)建動態(tài)效果的核心。h_tracker存儲了h的當(dāng)前值。always_redraw: 這個裝飾器告訴Manim,被它修飾的對象(如q_point和secant_line)需要在每一幀都重新計算和繪制。它們內(nèi)部的函數(shù)get_q_point和get_secant_line會讀取h_tracker的最新值,并調(diào)用f函數(shù)來獲取最新的坐標(biāo)。- 動態(tài)割線: 在
get_secant_line中,我們雖然可以直接用兩點(diǎn)式畫線,但這里展示了如何利用SymPy的思想——通過計算斜率和截距來定義直線,邏輯更清晰。
效果展示說明
運(yùn)行這段代碼,你會看到以下動畫效果:
- 坐標(biāo)系與函數(shù)登場:黃色的三次函數(shù)f(x)=x3−2x+1被繪制出來。
- 固定點(diǎn) P:在x=1處,一個紅色的點(diǎn)
P被標(biāo)記出來。 - 動態(tài)點(diǎn) Q 與割線:一個藍(lán)色的點(diǎn)
Q出現(xiàn)在P的右側(cè)(因?yàn)槌跏?h=1),一條綠色的割線連接P和Q。 - 魔法時刻:動畫開始,
Q點(diǎn)開始平滑地向P點(diǎn)移動(h值從 1.5 逐漸減小到 0.01)。與此同時,綠色的割線也隨之旋轉(zhuǎn)。 - 切線顯現(xiàn):當(dāng)
Q無限接近P時,割線幾乎不再變化。此時,一條紫色的精確切線被繪制出來,你會發(fā)現(xiàn)它和最終的割線幾乎完全重合!
整個過程直觀地展示了導(dǎo)數(shù)作為瞬時變化率的幾何意義,而這一切的精準(zhǔn)性都由 SymPy 在幕后保證。

小結(jié)
我們已經(jīng)成功地將 SymPy 的符號計算能力與 Manim 的動畫渲染能力結(jié)合起來,解決了制作導(dǎo)數(shù)定義動畫時的手動計算痛點(diǎn)。
通過 diff 和 limit,我們獲得了精確的數(shù)學(xué)結(jié)果;
通過 ValueTracker 和 always_redraw,我們讓這些結(jié)果在屏幕上“活”了起來。
這種 “SymPy 負(fù)責(zé)思考,Manim 負(fù)責(zé)表現(xiàn)” 的模式非常強(qiáng)大,可以應(yīng)用到各種復(fù)雜的數(shù)學(xué)可視化場景中。
到此這篇關(guān)于Python使用SymPy和Manim輕松搞定導(dǎo)數(shù)動畫的文章就介紹到這了,更多相關(guān)Python SymPy Manim實(shí)現(xiàn)導(dǎo)數(shù)動畫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python中if?__name__?==?“__main__“的作用總結(jié)
這篇文章主要介紹了Python中if?__name__?==?“__main__“作用的相關(guān)資料,if?__name__?==?'__main__'是Python中控制腳本執(zhí)行方式的關(guān)鍵語句,通過判斷`__name__`的值區(qū)分直接運(yùn)行和模塊導(dǎo)入,需要的朋友可以參考下2025-11-11
python實(shí)現(xiàn)網(wǎng)站的模擬登錄
這篇文章主要介紹了python實(shí)現(xiàn)網(wǎng)站的模擬登錄的相關(guān)資料,通過自己構(gòu)造post數(shù)據(jù)來用Python實(shí)現(xiàn)登錄過程,需要的朋友可以參考下2016-01-01
python生成器和yield關(guān)鍵字(完整代碼)
這篇文章主要介紹了python生成器和yield關(guān)鍵字,文章主要附上完整的代碼及些許的解釋說明,需要的小伙伴可以參考一下2022-01-01

