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

小結(jié)
我們已經(jīng)成功地將 SymPy 的符號(hào)計(jì)算能力與 Manim 的動(dòng)畫渲染能力結(jié)合起來,解決了制作導(dǎo)數(shù)定義動(dòng)畫時(shí)的手動(dòng)計(jì)算痛點(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ù)動(dòng)畫的文章就介紹到這了,更多相關(guān)Python SymPy Manim實(shí)現(xiàn)導(dǎo)數(shù)動(dòng)畫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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
10個(gè)常見的Python初學(xué)者錯(cuò)誤及避免方法
Python以其簡潔易讀的語法而聞名,是初學(xué)者的理想編程語言,本文將探討10個(gè)最常見的Python初學(xué)者錯(cuò)誤,希望對(duì)大家有一定的幫助2025-10-10

