与显式组件相比,是否可以将 "explicit output" 添加到隐式组件而无需额外的计算工作?

Is it possible to add an "explicit output" to an implicit component without extra computational effort compared to an explicit component?

在尝试弄清楚是否可以简化代码以避免某些重复时,我想知道是否可以将显式输出添加到隐式组件而不增加与显式组件相比的额外计算工作量。不过,显式输出在这里可能不是一个完全正确的术语,因为它取决于另一个隐式确定的输出。从文档中获取节点隐式组件示例:

class Node(om.ImplicitComponent):
    """Computes voltage residual across a node based on incoming and outgoing current."""

    def initialize(self):
        self.options.declare('n_in', default=1, types=int, desc='number of connections with + assumed in')
        self.options.declare('n_out', default=1, types=int, desc='number of current connections + assumed out')

    def setup(self):
        self.add_output('V', val=5., units='V')

        for i in range(self.options['n_in']):
            i_name = 'I_in:{}'.format(i)
            self.add_input(i_name, units='A')

        for i in range(self.options['n_out']):
            i_name = 'I_out:{}'.format(i)
            self.add_input(i_name, units='A')

    def setup_partials(self):
        #note: we don't declare any partials wrt `V` here,
        #      because the residual doesn't directly depend on it
        self.declare_partials('V', 'I*', method='fd')

    def apply_nonlinear(self, inputs, outputs, residuals):
        residuals['V'] = 0.
        for i_conn in range(self.options['n_in']):
            residuals['V'] += inputs['I_in:{}'.format(i_conn)]
        for i_conn in range(self.options['n_out']):
            residuals['V'] -= inputs['I_out:{}'.format(i_conn)]

当我们想要计算通过节点的功率时,一种选择是创建一个显式组件,将节点电压和每个节点电流输入和输出作为输入来计算功率,然后分组它与隐式组件。但是,由于所有参数都已经在隐式组件中可用,并且这种方法在组件之间复制了一些当前的 in/out 循环,我想知道这是否可以直接在隐式组件中完成。由于文档示例提到“solve_nonlinear 方法提供了一种在隐式组件 中显式定义输出的方法”:

def solve_nonlinear(self, inputs, outputs):
    total_abs_current = 0
    for i_conn in range(self.options['n_in']):
        total_abs_current += np.abs(inputs['I_in:{}'.format(i_conn)])
    for i_conn in range(self.options['n_out']):
        total_abs_current += np.abs(inputs['I_out:{}'.format(i_conn)])
    outputs['P_total'] = total_abs_current * outputs['V'] / 2

进一步阅读,文档说在 apply_nonlinear() 方法下还需要添加一个幂残差。因此,类似于:

def apply_nonlinear(self, inputs, outputs, residuals):
    residuals['V'] = 0
    total_abs_current = 0
    for i_conn in range(self.options['n_in']):
        residuals['V'] += inputs['I_in:{}'.format(i_conn)]
        total_abs_current += np.abs(inputs['I_in:{}'.format(i_conn)])
    for i_conn in range(self.options['n_out']):
        residuals['V'] -= inputs['I_out:{}'.format(i_conn)]
        total_abs_current += np.abs(inputs['I_out:{}'.format(i_conn)])
    residuals['P_total'] = outputs['P_total'] - total_abs_current * outputs['V'] / 2

但是即使 solve_linear() specifies/calculates 功率已经明确显示,组件是否会实际使用此函数来“求解”功率?与显式组件方法相比,此实现是否因此需要更多的计算资源?当通过 linearize() 方法指定偏音时,它们应该遵循 apply_nonlinear() 还是 solve_nonlinear()计算?

我通常将这种情况称为伪隐式输出。你有一个解析表达式,所以你真的不需要它是隐式的,但你想把计算和一堆其他隐式的东西放在一起。你有基本的布局权利。您编写一个 solve_nonlinear 方法来为您进行计算,然后在 apply_nonlinear.

中添加残差形式

But will the component actually use this function to "solve" for the power, even when solve_linear() specifies/calculates the power already explicitly?

是 .. 而不是 :) 简单的答案是(在大多数情况下)solve_nonlinear 方法最终会为伪隐式输出提供值,作为整个全局非线性求解的一部分。对于该特定变量,残差形式将有效地始终 return 0 。如果您使用块高斯赛德尔求解器或打开 solve_subsystems 的牛顿求解器,这适用。

如果使用纯牛顿法(没有solve_subsystem),会出现更微妙的情况。在那种情况下,残差形式实际上驱动了整个计算,并且永远不会调用任何隐式组件的 solve_nonlinear 方法。这不是 运行 牛顿求解器的超级常见模式,但它确实经常出现。

我会说伪隐式输出使您可以灵活地以任何一种方式工作,而不会真正损失性能。正如我将在下面讨论的那样,这与将它分解成一个显式组件之间没有任何实际区别。

Will this implementation then therefore require more computational resources compared to the explicit component approach?

简短的回答是否定的,至少没有任何有意义的数量。长答案需要深入研究牛顿求解器的数学并理解 OpenMDAO 是如何真正执行 ExplicitComponents 的。有关所有详细信息,您应该查看 section 5.1 of the OpenMDAO paper 以及 OpenMDAO 在内部为所有 ExplicitComponents 执行的隐式转换。

总而言之,OpenMDAO 中的显式组件与您在 apply_linear 中所做的完全相同,这就是 OpenMDAO 在需要计算残差时在内部所做的事情。因此,您的实现实际上并没有比 OpenMDAO 已经在后台添加更多或更少的内容。

    residuals['P_total'] = outputs['P_total'] - total_abs_current * outputs['V'] / 2

这里有一个警告。我夸大了情况会清楚。假设您在一个组件中有一个单一的缩放器隐式关系,然后您也向它添加 1e6 伪隐式输出。在那种情况下,您最好将它们分开,因为您会使牛顿系统变得更大且更昂贵。但一般来说,添加一些额外的伪显式输出根本不会有太大的不同。

When specifying the partials through the linearize() method, should they follow the apply_nonlinear() or solve_nonlinear() calculation?

区分 apply_nonlinear。在隐式组件的导数上下文中,根本不用担心您在 solve_nonlinear 中所做的事情!