在 OpenMDAO 中使用中间离散变量执行基于梯度的优化

Performing gradient-based optimization with intermediate discrete variables in OpenMDAO

我们有一位用户想要使用基于梯度的方法解决具有中间离散变量的优化问题。他们 运行 变成了 this error。我知道我们可以重组问题以不使用离散变量,但如果离散变量没有改变,我是否也可以将此错误视为警告?或者是否存在派生无法正确传播的根本原因。明确地说,我们在模型级别使用 approx_totals。

这是一个展示这个的小测试用例:

import openmdao.api as om
import numpy as np


class DiscreteComp(om.ExplicitComponent):
    def setup(self):
        self.add_input('a', 2.)
        self.add_discrete_output('b', val=0)

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
        discrete_outputs['b'] = 2 * inputs['a']
        
class DummyComp(om.ExplicitComponent):
    def setup(self):
        self.add_input('a', 2.)
        self.add_discrete_input('b', 1)
        self.add_output('c')

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
        b = discrete_inputs['b']
        outputs['c'] = inputs['a']**2 * b

prob = om.Problem()

prob.model.add_subsystem('discrete_comp', DiscreteComp(), promotes=['*'])
prob.model.add_subsystem('dummy_comp', DummyComp(), promotes=['*'])

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

prob.model.add_design_var('a', lower=-10, upper=10)
prob.model.add_objective('c')

prob.model.approx_totals()

prob.setup()

# run the optimization
prob.run_driver()

给出了这个输出:

Traceback (most recent call last):
  File "tmp.py", line 39, in <module>
    prob.run_driver()
  File "/home/john/anaconda3/envs/weis/lib/python3.8/site-packages/openmdao/core/problem.py", line 685, in run_driver
    return self.driver.run()
  File "/home/john/anaconda3/envs/weis/lib/python3.8/site-packages/openmdao/drivers/pyoptsparse_driver.py", line 480, in run
    raise self._exc_info
  File "/home/john/anaconda3/envs/weis/lib/python3.8/site-packages/openmdao/drivers/pyoptsparse_driver.py", line 643, in _gradfunc
    sens_dict = self._compute_totals(of=self._quantities,
  File "/home/john/anaconda3/envs/weis/lib/python3.8/site-packages/openmdao/core/driver.py", line 892, in _compute_totals
    total_jac = _TotalJacInfo(problem, of, wrt, use_abs_names,
  File "/home/john/anaconda3/envs/weis/lib/python3.8/site-packages/openmdao/core/total_jac.py", line 209, in __init__
    raise RuntimeError("Total derivative %s '%s' depends upon "
RuntimeError: Total derivative with respect to '_auto_ivc.v0' depends upon discrete output variables ['discrete_comp.b'].

鉴于您的示例,OpenMDAO 引发此错误是有原因的。离散输出不通过与连续输出相同的数据系统。只有连续输出才能访问导数系统。虽然这里可以有这个中间离散变量,并对整个事物进行有限差分,但你只能逃避它,因为你真的在那里有一个连续的计算。

如果输出确实是离散的,那么在整个计算中取导数或进行有限差分近似在数学上是无效的。这就是 OpenMDAO 引发错误的原因。

诚然,在某些极端情况下,您可以争辩说 OpenMDAO 应该让您这样做。例如,如果您的输出 c 计算为: outputs['c'] = floor(inputs['a']) 并且您知道您会将 a 的值限制在 1 到 2 之间。您知道 c 将始终恰好为 1。因此您可以说它有效地区分它,因为离散变量值永远不会改变,因此函数在这些范围内是可微的。

如果你想使用 OpenMDAO 进行有限差分,你有两个选择:

  1. 即使它是一个离散值,无论如何也要将它列为一个连续值。这适用于 OpenMDAO 的有效性检查,但完全取决于您,以确保这些值真的不会随着您所采用的导数范围而改变。如果您决定实施解析导数,那么就不要为关于任何事物的离散输出声明一个导数。
  2. 将离散计算移至 setup 阶段并将信息作为选项传递。这迫使您绝对尊重“永不更改”范例,因为设置只发生一次。它确实需要重新设计。
  3. 如果您不喜欢 OpenMDAO 的保姆式操作,您可以随时注释掉错误 :) 您显然这样做需要您自担风险……但这就是开源软件的价值之一!

我个人的建议是选项 #2。这确实是最好的整体设计,并迫使您确保离散计算在选择过程中永远不会改变。