分析梯度与复杂步骤相比,compute_totals花费的时间更长

时间:2018-12-27 15:13:28

标签: openmdao

下面是单个组件问题的代码段。

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)

在回答之后,我为该代码的各个部分添加了计算时间花絮

enter image description here

2 个答案:

答案 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)