OpenMDAO: как обрабатывать не сходящиеся точки в ExplicitComponent.compute?

Я пытался обрабатывать не сходящиеся точки в compute метод моего ExplicitComponentподняв AnalysisErrorКак указано в разделе Как лучше всего сообщить драйверу или решателю openMDAO, что в какой-то момент невозможно оценить модель? (Первоначально я хотел оставить комментарий в этой теме, но мне не разрешили из-за моей низкой оценки репутации переполнения стека). Однако, похоже, это не решает мою проблему. Я ожидал, что ошибка будет обнаружена, точка проектирования будет пропущена, и оптимизатор продолжит оценивать другие точки, чтобы найти решение. Правильно, что ошибка обнаружена, но по какой-то причине ошибка затем пересматривается в ScipyOptimizeDriver.run, Какова цель этого?

Это пример сценария для воспроизведения поведения:

import numpy as np
from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent, ScipyOptimizeDriver, ScipyKrylov, AnalysisError


class Test1Comp(ExplicitComponent):

    def setup(self):
        self.add_input('design_x', 1.0)
        self.add_input('design_y', 1.0)
        self.add_input('design_z', 0.5)
        self.add_output('y', val=0.1)
        self.add_output('z', val=0.1)
        self.add_output('obj', val=0.0)

        self.declare_partials(of='*', wrt='*', method='fd', form='central', step=1.0e-4)

    def compute(self, inputs, outputs):
        design_z = inputs['design_z']
        design_x = inputs['design_x']
        design_y = inputs['design_y']

        # Let's assume we have a model that has problems converging around design_x = 5.0
        if 0.49999 < design_x < 0.500001:
            raise AnalysisError()

        z = 4/(design_z + 1)
        y = - design_z - 2*z
        obj = (y/5.833333 - design_x)**2 + z/2.666667*100*(design_y - design_x**2)**2

        outputs["z"] = z
        outputs["y"] = y
        outputs['obj'] = obj


if __name__ == "__main__":

    prob = Problem()
    model = prob.model = Group()

    model.add_subsystem('d1', IndepVarComp('design_x', 1.0))
    model.add_subsystem('d2', IndepVarComp('design_y', 1.0))
    model.add_subsystem('d3', IndepVarComp('design_z', 0.5))
    model.add_subsystem('comp', Test1Comp())

    model.connect('d1.design_x', 'comp.design_x')
    model.connect('d2.design_y', 'comp.design_y')
    model.connect('d3.design_z', 'comp.design_z')

    prob.driver = ScipyOptimizeDriver()
    prob.driver.options["optimizer"] = 'SLSQP'
    prob.driver.options['tol'] = 1e-8
    model.add_design_var("d1.design_x", lower=0.5, upper=1.5)
    model.add_design_var("d2.design_y", lower=0.5, upper=1.5)
    model.add_design_var("d3.design_z", lower=0.0, upper=1.0)
    model.add_objective('comp.obj')

    model.linear_solver = ScipyKrylov()
    model.linear_solver.options['maxiter'] = int(1200)
    model.linear_solver.options['restart'] = int(20)

    # prob.model.approx_totals()
    prob.setup()
    prob.run_driver()
    print(prob['comp.y'])
    print(prob['comp.z'])

Кроме того, глядя на ExplicitComponent._solve_nonlinear, который вызывает метод ExplicitComponent.computeмне кажется, что естественным способом сообщения OpenMDAO о том, что точка не сходится, было бы иметь ExplicitComponent.compute вернуть True, Посмотрите исходный код для метода:

def _solve_nonlinear(self):
    """
    Compute outputs. The model is assumed to be in a scaled state.

    Returns
    -------
    boolean
        Failure flag; True if failed to converge, False is successful.
    float
        absolute error.
    float
        relative error.
    """
    super(ExplicitComponent, self)._solve_nonlinear()

    with Recording(self.pathname + '._solve_nonlinear', self.iter_count, self):
        with self._unscaled_context(
                outputs=[self._outputs], residuals=[self._residuals]):
            self._residuals.set_const(0.0)
            failed = self.compute(self._inputs, self._outputs)
    return bool(failed), 0., 0. 

Таким образом, кто-то может уточнить, каков рекомендуемый способ обработки не сходящихся вычислений в ExplicitComponent.compute?

1 ответ

Решение

Я посмотрел на ваш код, и вы все правильно указали, как сказать оптимизатору, что компонент не может оценить проектную точку. Проблема в том, что scipy.minimize (который является оптимизатором подScipyOptimizeDriver) не знает, что делать, когда он достигает точки сбоя (кроме создания исключения), и не имеет возможности сообщить об ошибке обратно (по крайней мере, насколько мне известно).

Тем не мение, pyOptSparseDriver может сделать что-то, когда точка не удалась: попытка продвинуться снова в направлении градиента, но с меньшим размером шага. Я взял твой код и обменял ScipyOptimizeDriver с pyOptSparseDriverЯ использовал оптимизатор "SLSQP" в этом пакете, и он прекрасно справился с этой проблемной точкой, достигнув, как мне кажется, хорошего оптимума:

[0.69651727]
[-5.]
[2.]

Мой код опции драйвера выглядит так:

prob.driver = pyOptSparseDriver()
prob.driver.options["optimizer"] = 'SLSQP'
prob.driver.opt_settings['ACC'] = 1e-8

Если у вас еще нет pyoptsparse, который является отдельным пакетом, который не поддерживается нами, вы можете получить его по https://github.com/mdolab/pyoptsparse - и вы можете собрать и установить его с помощью:

python setup.py build
python setup.py install

По вашему другому вопросу, я просмотрел наш код и обнаружил, что флаг сбоя возвращается _solve_nonlinear никогда не используется ни для чего. Таким образом, единственный способ сообщить о не сходящемся статусе драйверу или решателю более высокого уровня - это вызвать AnalysisError. Решатели могут создать АЕ, когда они не сходятся, если для параметра "err_on_maxiter" в его параметрах установлено значение "Истина" (по умолчанию установлено значение "Ложь").

В заключение, я думаю, что пока используются наши механизмы обработки ошибок, мы не думаем обо всем и всегда открыты для предложений по улучшению.

Другие вопросы по тегам