下面是单个组件问题的代码段。
setting self.flag --> 1 uses complex step
setting self.flag --> 0 uses analytical gradients
近似/计算偏导数。
计算时间 使用选项1计算总导数所需的时间约为20秒,而选项0约为60秒。
我原本期望相反,因为有成千上万个带有复杂步骤的“计算”函数调用。
我检查了函数调用,它们似乎正确。我用'cs'检查了分析部分,它们似乎也是正确的。
有人能启发我为什么通过分析偏导数计算总导数需要更长的时间吗?
import time
import numpy as np
dim1,dim2,dim3=10,40,30
ran1=np.random.random([dim1,dim2,dim3])*5
ran2=np.random.random([dim1,dim2,dim3])*10
from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent
class FDPartialComp(ExplicitComponent):
def setup(self):
dim1,dim2,dim3=10,40,30
self.add_input('var1', val=np.ones([dim1,dim2,dim3]))
self.add_input('var2', val=np.ones([dim1,dim2,dim3]))
self.add_output('f', shape=(dim1,))
self.flag=0
self.cou=0
self.partcou=0
if self.flag:
self.declare_partials('*', '*', method='cs')
else:
self.declare_partials('f', 'var1',cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
self.declare_partials('f', 'var2' ,cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
def compute(self, inputs, outputs):
self.cou+=1
print(self.cou)
var1 = inputs['var1']
var2 = inputs['var2']
m=3
outputs['f'] = np.sum((var2*var1**m),axis=(1,2))
def compute_partials(self, inputs, partials):
if self.flag:
pass
else:
m=3
var1 = inputs['var1']
var2 = inputs['var2']
partials['f','var1'] =(var1**m*m*var2/var1).flatten()
partials["f","var2" ]= (var1**m).flatten()
self.partcou+=1
print(self.partcou)
model = Group()
comp = IndepVarComp()
comp.add_output('var1', ran1)
comp.add_output('var2', ran2)
#comp.add_output('var1', np.ones([dim1,dim2,dim3])*5)
#comp.add_output('var2', np.ones([dim1,dim2,dim3])*10)
model.add_subsystem('input', comp,promotes=['*'])
model.add_subsystem('example', FDPartialComp(),promotes=['*'])
problem = Problem(model=model)
problem.setup(check=True)
#problem.run_model()
st=time.time()
totals = problem.compute_totals(['f'], ['var1','var2'])
#problem.setup(force_alloc_complex=True)
#problem.check_partials(compact_print=True,method='cs')
print(time.time()-st)
在回答之后,我为该代码的各个部分添加了计算时间花絮
答案 0 :(得分:3)
我看了看OpenMDAO代码,以弄清楚为什么没有直接求解器的CS和没有直接求解器的解析导数一样快地运行。在执行此操作的过程中,我发现在几个地方相互使用numpy.add.at
调用,这些调用非常慢。我用更快的numpy.bincount
呼叫代替了这些呼叫。此处显示的数字使用的是这些代码改进,这些改进自提交7f13fda起已合并到OpenMDAO master分支中。这些改进将在V2.9中发布。
在最近对OpenMDAO进行更改之后,我得到了以下计时信息:
analytical derivs w/o direct solver (fwd mode): 13.55 s
analytical derivs with direct solver (fwd mode): 27.02 s
CS w/o direct solver (fwd mode): 15.76 s
请注意,现在,没有DirectSolver的分析导数实际上比CS快,但是如果我们借助探查器来更深入地了解,我们会发现一些有趣的东西。
solve_linear time (analytical): 12.65000 s
linearize time (analytical): + 0.00195 s (1 call to compute_partials)
----------
12.65195 s
solve_linear time (CS): 9.63 s
linearize time (CS): + 4.81 s (24,000 compute calls)
-------
14.44 s
因此在CS下,solve_linear仍然更快。这样做的原因是,对于CS,将部分声明为密集(这是目前唯一的方法,因为在使用FD或CS时我们尚不支持声明稀疏部分)。当将部分声明为密集时,使用快速numpy.dot
调用完成solve_linear内部的矩阵向量乘积,但是当将部分声明为稀疏时(如您在使用解析导数的示例中的情况),则我们使用功能较慢。在您运行计时时,我们正在使用numpy.add.at
,如上所述,它确实很慢。我们现在正在使用numpy.bincount
,它速度更快,但仍然不如numpy.dot
快,因此就是如此。
顺便说一句,由于在这种情况下,您的整体jacobian的形状为(10 x 24000),因此我强烈建议您使用rev
模式而不是fwd
模式,因此您将10个线性解而不是24000个。当我这样做时,我得到了这些计时:
analytical derivs w/o direct solver (rev mode): 0.01 s
analytical derivs with direct solver (rev mode): 0.04 s
CS w/o direct solver (rev mode): 4.86 s
分析性派生案例现在显然是赢家。
请注意,现在CS情况的时机几乎完全是由于线性化所花费的时间,与fwd
模式下花费的时间相同,因为CS非线性求解的次数始终由部分jacobian中的列数。
答案 1 :(得分:1)
您看到的性能差异与OpenMDAO中的内部数据结构有关。当给定解析导数时,您的模型是使用稀疏格式指定的(这很好,因为它非常稀疏!)。但是要真正利用这一点,您需要使用assembled matrix格式存储偏导数据,并使用direct solver计算稀疏LU分解。将这些功能添加到模型后,分析的性能将比CS更好。
之所以出现差异,是因为当您使用纯CS时,会将导数存储为密集格式,其表现为组合矩阵。但是,当您指定分析导数时,默认情况下并没有获得该好处。因此,框架在处理每个案件的方式上存在一些根本差异。
这是一个更新的脚本,显示了正确的性能(我将输入的尺寸减小了,因此运行速度更快)
import time
import numpy as np
# dim1,dim2,dim3=10,40,30
dim1,dim2,dim3=10,40,5
ran1=np.random.random([dim1,dim2,dim3])*5
ran2=np.random.random([dim1,dim2,dim3])*10
from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent, DirectSolver
class FDPartialComp(ExplicitComponent):
def setup(self):
self.add_input('var1', val=np.ones([dim1,dim2,dim3]))
self.add_input('var2', val=np.ones([dim1,dim2,dim3]))
self.add_output('f', shape=(dim1,))
self.flag=0
self.cou=0
self.partcou=0
if self.flag:
self.declare_partials('*', '*', method='cs')
else:
self.declare_partials('f', 'var1',cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
self.declare_partials('f', 'var2' ,cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
def compute(self, inputs, outputs):
self.cou+=1
# print(self.cou)
var1 = inputs['var1']
var2 = inputs['var2']
m=3
outputs['f'] = np.sum((var2*var1**m),axis=(1,2))
def compute_partials(self, inputs, partials):
if self.flag:
pass
else:
m=3
var1 = inputs['var1']
var2 = inputs['var2']
partials['f','var1'] = (var1**m*m*var2/var1).flatten()
partials['f','var2' ]= (var1**m).flatten()
self.partcou+=1
# print(self.partcou)
model = Group()
comp = IndepVarComp()
comp.add_output('var1', ran1)
comp.add_output('var2', ran2)
#comp.add_output('var1', np.ones([dim1,dim2,dim3])*5)
#comp.add_output('var2', np.ones([dim1,dim2,dim3])*10)
model.add_subsystem('input', comp,promotes=['*'])
model.add_subsystem('example', FDPartialComp(),promotes=['*'])
model.linear_solver = DirectSolver(assemble_jac=True)
problem = Problem(model=model)
problem.setup(check=True, mode='fwd')
problem.final_setup()
# exit()
#problem.run_model()
st=time.time()
totals = problem.compute_totals(['f'], ['var1','var2'])
#problem.check_partials(compact_print=True,method='cs')
print(time.time()-st)
print(problem._mode)