计数函数评估

Counting function evaluations

我刚刚完成了有关 reader 和录音机的教程。我想知道是否有一种方法可以计算每个学科被调用的次数。下面我在 Sellar 问题中使用全局变量来计算这个。什么是更好的方法来做到这一点?另外,如果问题中没有求解器,建议的方法会改变吗?

import numpy as np
import time
import openmdao.api as om

tic = time.perf_counter()
dc1 = 0
dc2 = 0

class SellarDis1(om.ExplicitComponent):
    def setup(self):
        self.add_input('z', val=np.zeros(2))
        self.add_input('x', val=0.)
        self.add_input('y2', val=1.0)
        self.add_output('y1', val=1.0)
        self.declare_partials('*', '*', method='fd')

    def compute(self, inputs, outputs):
        global dc1
        dc1+=1
        z1 = inputs['z'][0]
        z2 = inputs['z'][1]
        x1 = inputs['x']
        y2 = inputs['y2']
        outputs['y1'] = z1**2 + z2 + x1 - 0.2*y2

class SellarDis2(om.ExplicitComponent):

    def setup(self):
        self.add_input('z', val=np.zeros(2))
        self.add_input('y1', val=1.0)
        self.add_output('y2', val=1.0)
        self.declare_partials('*', '*',method='fd')

    def compute(self, inputs, outputs):
        global dc2
        dc2 +=1
        z1 = inputs['z'][0]
        z2 = inputs['z'][1]
        y1 = inputs['y1']
        if y1.real < 0.0:
            y1 *= -1
        outputs['y2'] = y1**.5 + z1 + z2

class SellarMDF(om.Group):
    def setup(self):
        indeps = self.add_subsystem('indeps', om.IndepVarComp(), promotes=['*'])
        indeps.add_output('x', 1.0)
        indeps.add_output('z', np.array([5.0, 2.0]))

        cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])
        cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z', 'y2'],
                            promotes_outputs=['y1'])
        cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z', 'y1'],
                            promotes_outputs=['y2'])

        cycle.linear_solver = om.ScipyKrylov()

        cycle.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)
        

        self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',
                                                  z=np.array([0.0, 0.0]), x=0.0, y1=0.0, y2=0.0),
                           promotes=['x', 'z', 'y1', 'y2', 'obj'])

        self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])
        self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])

prob = om.Problem()
prob.model = SellarMDF()

driver = prob.driver = om.ScipyOptimizer()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('x', lower=0, upper=10)
prob.model.add_design_var('z', lower=0, upper=10)

prob.model.add_objective('obj')
prob.model.add_constraint('con1', upper=0)
prob.model.add_constraint('con2', upper=0)
prob.setup()
prob.set_solver_print(level=0)

prob.run_driver()

print('minimum found at')
print(prob['x'][0])
print(prob['z'])

print('Coupling Variables')
print(prob['y1'][0])
print(prob['y2'][0])

print('minumum objective')
print(prob['obj'][0])

toc = time.perf_counter()
print(f"You waited {toc - tic:0.4f} seconds")
print("Function Calls")
print('SellarDis1 : ', dc1)
print('SellarDis2 : ', dc2)

编辑:更好的意思是“openmdao”方式,比如录音机之类的。

Coverage or PyTest-Cov 这样的覆盖工具可以在 python 解释器中使用钩子来收集有关代码在程序执行期间 运行 的数据,这通常包括“命中”的数量" 每个函数甚至单独的行。如果您只想要程序外的数字,这对您来说可能已经足够了。

如果您需要在脚本本身中使用数字,您可以自己使用一些底层 API。使用 sys.settrace you can register a function to be called on every function call in your program. The first argument to that function will be a frame object,其中包含您可以用来检查这是否是您感兴趣的函数的属性。

这是一个简短的例子:

import sys

class A:
    def my_func(self, x):
        return x + 1

my_func_calls = 0

def tracefunc(frame, event, arg):
    global my_func_calls
    if event == 'call' and frame.f_code.co_name == 'my_func':
        my_func_calls += 1

sys.settrace(tracefunc)

if __name__ == '__main__':
    a = A()
    x = a.my_func(0)
    x = a.my_func(x)
    x = a.my_func(x)
    print("my_func_calls:", my_func_calls)

因为我看不到任何方法来获取与之关联的实际函数对象(这会给你 class 使用 __self__.__class__),你可能需要使用 frame.co_firstlineno区分同名方法。

这个跟踪功能的细节有点棘手,您可以参考 coverage.py's implementation 以获取有关您的选择和可能出现的问题的灵感。

所有 OpenMDAO 系统(组件和组的总称)都具有 iter_count 属性。这是 documentation page for System,所有组件和组都继承自它。 iter_count 是对每个 compute() 方法的调用总数,而 iter_count_without_approx 是对 compute() 方法的调用次数,不包括由于梯度近似引起的调用。此方法适用于任何求解器层次结构或驱动程序设置。

示例脚本的最后两行将是:

print('SellarDis1 : ', prob.model.cycle.d1.iter_count)
print('SellarDis2 : ', prob.model.cycle.d2.iter_count)

这并没有像您建议的那样使用录音机。如果您更喜欢使用记录器的解决方案,我们可以找到可行的方法。那里有很大的灵活性,要开始使用,您可以查看 this specific doc page on case reading,这可以帮助对案例进行后处理以获得呼叫总数。

如有不明之处请告诉我!