7.4 函数求导
函数y= f(x)的导数表示因变量y关于自变量x的变化率,记为f(x)或dy/dx。我们可以通过创建Derivative类的对象来对函数进行求导。以之前的汽车行驶例子中的函数为例:
>>> from sympy import Symbol, Derivative # ①
>>> t = Symbol('t')
>>> St = 5*t**2 + 2*t +8
>>> Derivative(St, t)
Derivative(5*t**2 + 2*t + 8, t)
我们在①处导入Derivative类,在②处创建一个Derivative类的对象,创建对象时传递的两个参数分别是函数S(t)(符号St)和变量t(符号t)。和Limit类一样,首先返回一个Derivative类的对象,此时并没有真正计算导数。我们调用doit()函数对未计算的Derivative类的对象求导:
>>> d = Derivative(St, t)
>>> d.doit()
10*t + 2
导数的表达式为10*t+2。现在,我们希望计算一个特定值t处的导数值,比如说 或t=1,可以使用subs()函数:
>>> d.doit().subs({t:t_1})
10*t_1 + 2
>>> d.doit().subs({t:1})
12
>>>
下面试着考虑一个仅以x为变量的复杂的函数
>>> from sympy import Derivative, Symbol
>>> x = Symbol('x')
>>> y = (x**3 + x**2 + x)* (x**2 + x)
>>> Derivative(y, x)
Derivative((x**2 + x)*(x**3 + x**2 + x), x)
>>> Derivative(y, x).doit()
(2*x + 1)*(x**3 + x**2 + x) + (x**2 + x)*(3*x**2 + 2*x + 1)
可以看到这个函数是两个独立的函数的乘积形式,这意味着,我们需要使用微分链式法则来计算这个导数。但不用担心,只需创建Derivative类的对象来处理就可以了。
你还可以尝试其他的复杂表达式,比如说涉及三角函数的表达式。
7.4.1 求导计算器
现在我们来编写一个求导计算器程序,它将以以一个函数作为输入,然后输出关于指定变量的导数:
'''
Derivative calculator
'''
from sympy import Symbol, Derivative, sympify, pprint
from sympy.core.sympify import SympifyError
def derivative(f, var):
var = Symbol('var')
d = Derivative(f, var).doit()
pprint(d)
if __name__ == '__main__':
f = input('Enter a function: ') # ①
var = input('Enter the variable to differentiate with respect to : ')
try:
f = sympify(f) # ②
except SympifyError:
print('Invalid input')
else:
derivative(f, var) # ③
在①处,我我们提示用户输入一个要求导的函数,然后询问用户要这个函数的哪个变量求导。在②处,我们使用sympify()函数将输入函数转换为SymPy对象,我们在try...except模块中调用sympify()函数,从而可以在用户无效输入的情况下显示错误信息。如果输入函数有效,我们在③处调用求导函数derivative(),此时将转换后的输入函数以及要求导的变量作为输入参数。
在derivative()函数中,首先创建一个对应于求 导变量的Symbol对象,并使用标签var来指代这个变量。接下来,创建一个Derivative 对象,并将输入函数f和Symbol对象var 作为输入参数。调用doit()函数计算导数,然后使用pprint()函数输出结果,使其看起来接近于它相应的数学公式。程序的运行示例如下:
Enter a function: 2*x**2 + 3*x +1
Enter the variable to differentiate with respect to : x
4⋅x + 3
以下是一个二元函数的运行实例:
Enter a function: x**2 + y**2
Enter the variable to differentiate with respect to : x
2⋅x
7.4.2 求偏导数
在上一个程序里,我们看到可以使用Derivative类对多变量函数中的其中任意一个变量求导,这种计算通常被称为计算偏导数,“偏”意味着我们假设仅有一个变量变化,而其他变量固定。
考虑函数 。f(x,y)关于x的偏导数是 。先前的程序可以用来计算偏导数,只需要指定正确的变量即可:
Enter a function: 2*x*y + x*y**2
Enter the variable to differentiate with respect to : x
2
y + 2⋅y
注:这一章要做的一个关键假设就是,我们要计算导数的所有函数在它们的定义域中都是可导的。
7.5 高阶导数和最大最小值点
默认情况下,使用Derivative类创建一个导数对象是求一阶导数。为求高阶导数,只需在创建Derivative对象时将求导阶数作为第三个参数即可。这一节里我们将演示如何使用函数的一阶和二阶导数在区间上找到最大和最小值点。
考虑函数 ,定义域为[-5,5],注意我使用了方括号表示定义域是闭区间,即自变量可以是-5到5之间的任何数,包括端点。

x^5-30x^3+50x的函数图像
由图中可以看到函数在区间[-2, 0]上在点B取到最小值。类似地,在区间[0,2]上在点C取到最大值。另一方面,在x的整个定义域上,函数分别在A点和D点处取得最大和最小值。因此,当考虑整个区间[-5, 5]上的函数时,点B和点C分别称为局部最小值和局部最大值,而点A和点D分别称为全局最大值和全局最小值。 极值点是指函数取局部或全局最大或最小值的点。如果x是函数f(x)的极值点,那么f(x)在x点处的一阶导数(记为f'(x))必须为0。这个性质表明寻找可能的极值点的一个好方法就是求解方程f(x)=0, 该方程的解称为函数的极值点。 试着做一 下:
>>> from sympy import Symbol, solve, Derivative
>>> x = Symbol('x')
>>> f = x**5 - 30*x**3 + 50*x
>>> d1 = Derivative(f, x).doit()
现在我们计算了一阶导数f'(x),接下来解f'(x)=0以得到极值点:
>>> crtical_points = solve(d1)
>>> crtical_points
[-sqrt(9 - sqrt(71)), sqrt(9 - sqrt(71)), -sqrt(sqrt(71) + 9), sqrt(sqrt(71) + 9)]
这里显示的crtical_points列表中的数字分别对应于图中的B、C、A和D。我们将创建这标签来分别指代这些点,然后在命令行中使用这些标签:
>>> A = crtical_points[2]
>>> B = crtical_points[0]
>>> C = crtical_points[1]
>>> D = crtical_points[3]
因为这个函数的所有极值点都在所考虑的区间内,它们都和我们要搜寻的f(x)的全局最大和最小值相关。现在应用所谓的二阶导数检验来缩小可能的全局最大和最小值点的范围。
首先,我们计算函数f(x)的二阶导数。注意,为计算二阶导数,我们输入2作为第三个参数:
>>> d2 = Derivative(f, x, 2).doit()
现在我们通过将每一个极值点逐一代入x来求该点处的二阶导数值。如果结果小于0,则该值为局部最大值;如果结果大于0,则该值为局部最小值;如果结果等于0,则不能得出结论,即不能判断极值点x是否为局部最大值、最小值或二者都不是。
>>> d2.subs({x:B}).evalf()
127.661060789073
>>> d2.subs({x:C}).evalf()
-127.661060789073
>>> d2.subs({x:A}).evalf()
-703.493179468151
>>> d2.subs({x:D}).evalf()
703.493179468151
对极值点的二阶导数检验进行计算,结果可知A和C是局部最大值点,B和D是局部最小值点。 区间[-5,5]上f(x)的全局最大值和最小值是在极值点x处或者在定义域的某一端点(x=-5和x=5)处获得的。我们已经找到了所有极值点,即点A、B、C和D。函数不可能在点A或C达到全局最小值,因为它们是局部最大值点。同理,函数不可能在点B或D达到全局最大值。 因此,为找到全局最大值,我们必须计算f(x)在点A、C、-5和5处的值。在这些点中,f(x)取最大值的地方一定是全局最大值。我们将创建两个标签,x_ min 和x max,来分别指代定义域边界,然后在点A、C、x_ min和x max处分别计算函数值:
>>> x_min = -5
>>> x_max = 5
>>> f.subs({x:A}).evalf()
705.959460380365
>>> f.subs({x:C}).evalf()
25.0846626340294
>>> f.subs({x:x_min}).evalf()
375.000000000000
>>> f.subs({x:x_max}).evalf()
-375.000000000000
通过上述计算,以及所有极值点这定义域边界处函数值的比较,我们发现A点为是全局最大值点。
类似地,为了确定全局最小值点,我们需要在计算函数在B、D点和端点处的函数值。
>>> f.subs({x:B}).evalf()
-25.0846626340294
>>> f.subs({x:D}).evalf()
-705.959460380365
>>> f.subs({x:x_min}).evalf()
375.000000000000
>>> f.subs({x:x_max}).evalf()
-375.000000000000
f(x)值最小的点一定是函数的全局最小值点,比较后可知结果是D点。
只要函数二阶可导,上述求函数极值的方法始终有效,即通过考虑所有极值点(通过二阶导数检验去除一些极值点后)和边界点处的函数值。二阶可导意味着函数在定义域内的一阶导数和二阶导数都存在。 类似 形式的函数在定义域中可能不存在极值点,但这种情形下该方法仍然有效,它告诉我们:极值点在定义域边界上。