Почему выходы первого компонента не передаются на входы второго непосредственно в связанной группе с помощью решателя NonlinearBlockGS? - PullRequest
0 голосов
/ 02 ноября 2018

У меня проблема с Group, которая включает обратную связь между его подсистемами в OpenMDAO. Я использую решатель NonlinearBlockBS. Я ожидаю, что решатель Гаусса-Зейделя будет запускать подсистемы последовательно, передавая выходы из более ранних блоков на входы других. Однако, похоже, этого не происходит, когда я реализую это в OpenMDAO.

Я сделал пример сценария, который демонстрирует эту проблему:

class A(ExplicitComponent):

    def setup(self):
        self.add_input('x', shape=1)
        self.add_input('b', shape=1)
        self.add_output('a', shape=1)

    def compute(self, inputs, outputs):
        outputs['a'] = inputs['x'] + 2 * inputs['b']
        print('A: x = {:1.0f}, b = {:1.0f}, a = {:1.0f}'.format(inputs['x'][0], inputs['b'][0], outputs['a'][0]))


class B(ExplicitComponent):

    def setup(self):
        self.add_input('x', shape=1)
        self.add_input('a', shape=1)
        self.add_output('b', shape=1)

    def compute(self, inputs, outputs):
        outputs['b'] = inputs['x'] - 0.5 * inputs['a']
        print('B: x = {:1.0f}, a = {:1.0}, b = {:1.0f}'.format(inputs['x'][0], inputs['a'][0], outputs['b'][0]))


if __name__ == '__main__':
    ivc = IndepVarComp()
    ivc.add_output('x', val=3.)

    coupled_group = Group()
    coupled_group.add_subsystem('A', A(), promotes=['*'])
    coupled_group.add_subsystem('B', B(), promotes=['*'])
    coupled_group.nonlinear_solver = NonlinearBlockGS()

    prob = Problem()
    model = prob.model = Group()
    model.add_subsystem('I', ivc, promotes=['*'])
    model.add_subsystem('C', coupled_group, promotes=['*'])

    prob.setup()
    prob.run_model()

Два компонента A и B связаны своими выходами a и b. Они также имеют общий параметр x, который изначально устанавливается IndepVarComp. При запуске код выдает следующий вывод:

 =
 C
 =
 A: x = 3, b = 1, a = 7
 B: x = 3, a = 1, b = 4
 A: x = 3, b = 1, a = 7
 B: x = 3, a = 7, b = 1
 A: x = 3, b = 1, a = 7
 B: x = 3, a = 7, b = 1
 NL: NLBGS Converged in 1 iterations

Параметр b, взятый в качестве ввода A, еще не был определен, когда A запускается первым. Таким образом, он принимает начальное значение 1. Это, как и ожидалось. Затем запускается B, который должен принимать выходные данные из A, a = 7, но вместо этого a также устанавливается в начальное предположение 1. Это не то, что я ожидаю, когда использую метод Гаусса-Зейделя.

Тот факт, что B не получает обновленное значение a после запуска A, не влияет на тот факт, что эта система сходится к правильному решению в этом случае. Однако в моем случае a = 1 не является допустимым значением для B. Поэтому система не может сходиться.

Я что-то здесь не так делаю? Что я могу сделать, чтобы убедиться, что B получает обновленное значение a при первом запуске?

1 Ответ

0 голосов
/ 02 ноября 2018

В OpenMDAO переменные определяются как неявные функции всех других переменных. Это серьезное отличие по сравнению с архитектурой на основе потоков, где компоненты определяются как явные функции. См. [Архитектура на основе потоков и архитектура MAUD] [1]. В OpenMDAO вместо этого решается нелинейная система уравнений для невязок. Таким образом, это нормально, что на первом шаге значение по умолчанию используется в компоненте B для ввода a (вход B.a равен A.a, поэтому B.a будет равен в первом вычислении по умолчанию A.a)

Если a не является допустимым значением в вашей системе B, инициализируйте его подходящим значением по умолчанию (теперь вы указываете только фигуру в настройке, а не значение.). Сигнатура метода добавления ввода - add_input(self, name, val=1.0, shape=None, src_indices=None, flat_src_indices=None, units=None, desc=''), поэтому значение по умолчанию будет равно 1,0, если вы его не укажете. Я переписал ваши классы, чтобы начать с начальных значений, определенных в настройках для ваших переменных. Для компонента B a передается из установки A, а не из первых вычислений A. То же самое относится к b в компоненте A. После этого решатель попытается минимизировать остатки, чтобы привести B.a-A.a и B.b-A.b к нулю.

class A(ExplicitComponent):

    def setup(self):
        self.add_input('x', shape=1)
        self.add_input('b', val=2)
        self.add_output('a', val=1)

    def compute(self, inputs, outputs):
        outputs['a'] = inputs['x'] + 2 * inputs['b']
        print('A: x = {:1.0f}, b = {:1.0f}, a = {:1.0f}'.format(inputs['x'][0], inputs['b'][0], outputs['a'][0]))


class B(ExplicitComponent):

    def setup(self):
        self.add_input('x', shape=1)
        self.add_input('a', val=3)
        self.add_output('b', val=4)

    def compute(self, inputs, outputs):
        outputs['b'] = inputs['x'] - 0.5 * inputs['a']
        print('B: x = {:1.0f}, a = {:1.0}, b = {:1.0f}'.format(inputs['x'][0], inputs['a'][0], outputs['b'][0]))

Первый шаг решателя:

=
C
=
A: x = 3, b = 4, a = 11
B: x = 3, a = 1e+00, b = 2

[1]: https://i.stack.imgur.com/ygBdn.png Хван, Дж. Т. и Мартинс, JRRA: «Вычислительная архитектура для связи неоднородных численных моделей и вычисления связанных производных»

...