从抽象函数到可计算导数 ——SymPy 中占位、求导、代入的完整闭环
在 SymPy 里,fff 最初只是一个名字。
from sympy import symbols, Function, diff, exp, sin, lambdify
x = symbols('x')
f = Function('f')(x)
这行代码让解释器承认存在某个可微映射 x↦f(x)x\mapsto f(x)x↦f(x),却不必立即给出解析式。凭借这一占位符,我们可以先对含 fff 的任意表达式做纯粹符号推演,待逻辑闭合后再把真正的函数体嵌进去,从而把符号世界里的导数翻译成可执行的数值。
占位:表达式与导数
取一个简单却足够说明问题的例子
E(x)=x2f(x)+ef(x).
E(x)=x^{2}f(x)+\mathrm{e}^{f(x)}.
E(x)=x2f(x)+ef(x).
在 SymPy 中只需一行
expr = x**2 * f + exp(f)
接着对 EEE 求导:
d_expr_dx = diff(expr, x)
SymPy 立即给出
dEdx=2x f(x)+(x2+ef(x))dfdx,
\frac{\mathrm{d}E}{\mathrm{d}x}=2x\,f(x)+\left(x^{2}+\mathrm{e}^{f(x)}\right)\frac{\mathrm{d}f}{\mathrm{d}x},
dxdE=2xf(x)+(x2+ef(x))dxdf,
其中 dfdx\frac{\mathrm{d}f}{\mathrm{d}x}dxdf 保持为 Derivative(f(x), x)
,因为 fff 仍是抽象对象。
代入:把未知函数换成已知函数
真正的数值计算发生在“代入”这一步。替换的本质是告诉 SymPy:
从现在起,f(x)f(x)f(x) 不再是未知函数,而是某个已知的解析式 g(x)g(x)g(x)。
于是
- 所有出现的 f(x)f(x)f(x) 节点被 g(x)g(x)g(x) 取代;
- 同时出现的
Derivative(f(x), x)
被 dgdx\frac{\mathrm{d}g}{\mathrm{d}x}dxdg 取代。
这一替换通过一次 subs
完成,再调用 .doit()
触发求导:
g = sin(x) # 例 1:g(x)=sin(x)
d_expr_1 = d_expr_dx.subs(f, g).doit()
得到
dEdx=2xsinx+(x2+esinx)cosx.
\frac{\mathrm{d}E}{\mathrm{d}x}=2x\sin x+\left(x^{2}+\mathrm{e}^{\sin x}\right)\cos x.
dxdE=2xsinx+(x2+esinx)cosx.
此刻表达式已完全不含抽象符号,仅含初等函数,可直接代入数值:
val_1 = d_expr_1.subs(x, 3.14159).evalf()
再换一函数:从三次多项式到批量数值
把 ggg 换成 x3+2xx^{3}+2xx3+2x 同样只需一行:
g = x**3 + 2*x
d_expr_2 = d_expr_dx.subs(f, g).doit()
显式展开后
dEdx=2x(x3+2x)+(x2+ex3+2x)(3x2+2).
\frac{\mathrm{d}E}{\mathrm{d}x}=2x(x^{3}+2x)+\left(x^{2}+\mathrm{e}^{x^{3}+2x}\right)(3x^{2}+2).
dxdE=2x(x3+2x)+(x2+ex3+2x)(3x2+2).
若需对大量 xxx 求值,可再 lambdify
:
f_num = lambdify(x, d_expr_2, 'numpy')
xs = np.linspace(0, 2, 1000)
ys = f_num(xs)
小结
- 抽象函数 fff 通过
Function('f')(x)
占位,允许我们先写表达式并求导。 - 一次
.subs(f, g).doit()
把未知 fff 换成已知 ggg,同时自动求出 g′g'g′。 - 结果表达式完全初等化,可直接代入数值或
lambdify
为高效可执行函数。
至此,从抽象符号到可计算导数的闭环全部打通。