Ограничения OpenMDAO2 на групповые соединения и переменные ввода/вывода подрывают мое желание писать чистое модульное программное обеспечение.
from openmdao.api import Group, ExplicitComponent, IndepVarComp, Problem
class A(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Az', val=0.0)
def compute(self, inputs, outputs):
outputs['Az'] = inputs['x'] + inputs['y']
class B(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Bz', val=0.0)
def compute(self, inputs, outputs):
outputs['Bz'] = 2*inputs['x'] - inputs['y']
class AB(Group):
def setup(self):
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
class C(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Cz', val=0.0)
def compute(self, inputs, outputs):
outputs['Cz'] = 3*inputs['x'] - 2*inputs['y']
class D(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Dz', val=0.0)
def compute(self, inputs, outputs):
outputs['Dz'] = 4*inputs['x'] - 2.5*inputs['y']
class CD(Group):
def setup(self):
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
Иногда я хотел бы работать только с группой AB (запускать сценарии, оптимизировать и т.д.), а иногда я хотел бы работать только с группой CD. Я могу сделать это с помощью
prob = Problem()
prob.model = AB()
prob.setup()
prob['x'] = 10.0
prob['y'] = 20.0
prob.run_model()
print(prob['Az'],prob['Bz'])
Однако иногда хотелось бы поработать с группой ABCD:
class ABCD(Group):
def setup(self):
self.add_subsystem('AB', AB())
self.add_subsystem('CD', CD())
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.connect('xx', ['AB.x', 'CD.x'])
self.connect('yy', ['AB.y', 'CD.y'])
В этом случае не существует комбинации продвижения переменных, соединений или использования IndepVarComps, которая не выдавала бы мне ошибку «входы с несколькими соединениями».
В OpenMDAO 1.x я смог обойти это, удалив IndepVarComps из групп более низкого уровня (AB, CD) и используя их только в группе самого высокого уровня (см. ответ). Однако OpenMDAO 2.x выдает ошибку, что два входа подключены без выхода. Например:
class AB(Group):
def setup(self):
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
Теперь я застрял с поддержкой нескольких копий одного и того же кода, одной копии для AB и другой для ABCD, или просто вручную подключаю свои модули на чистом питоне и отхожу от OpenMDAO. Я что-то пропустил? Любая помощь или руководство будут приветствоваться.
В вашем простом примере я вижу два пути решения проблемы. Оба предполагают использование опций в группах. Поскольку я не уверен, какой способ будет работать лучше, я включил оба варианта в пример ниже.
Один из способов заключается в том, чтобы сделать его необязательным, если AB/CD владеют собственными независимыми файлами. Затем вы можете переключать необходимое поведение по своему усмотрению. Это работает, но я лично думаю, что это грязно.
Второй вариант — создать только одну группу, но использовать параметры для управления тем, что создается аргументом mode
. Я думаю, что этот способ чище, так как у вас есть только 4 компонента и одна группа.
from openmdao.api import Group, ExplicitComponent, IndepVarComp
class A(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Az', val=0.0)
def compute(self, inputs, outputs):
outputs['Az'] = inputs['x'] + inputs['y']
class B(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Bz', val=0.0)
def compute(self, inputs, outputs):
outputs['Bz'] = 2*inputs['x'] - inputs['y']
class AB(Group):
def initialize(self):
self.options.declare('owns_indeps', types=bool, default=True)
def setup(self):
if self.options['owns_indeps']:
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
class C(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Cz', val=0.0)
def compute(self, inputs, outputs):
outputs['Cz'] = 3*inputs['x'] - 2*inputs['y']
class D(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Dz', val=0.0)
def compute(self, inputs, outputs):
outputs['Dz'] = 4*inputs['x'] - 2.5*inputs['y']
class CD(Group):
def initialize(self):
self.options.declare('owns_indeps', types=bool, default=True)
def setup(self):
if self.options['owns_indeps']:
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
class ABCD(Group):
def setup(self):
self.add_subsystem('AB', AB(owns_indeps=False))
self.add_subsystem('CD', CD(owns_indeps=False))
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.connect('xx', ['AB.x', 'CD.x'])
self.connect('yy', ['AB.y', 'CD.y'])
class ABCD_ALT(Group):
"""Alternate approach that would not require more than one group class at all"""
def initialize(self):
self.options.declare('mode', values=['AB', 'CD', 'ABCD'], default='AB')
def setup(self):
mode = self.options['mode']
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
if 'AB' in mode:
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
if 'CD' in mode:
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
self.connect('xx', 'x')
self.connect('yy', 'y')
if __name__ == "__main__":
from openmdao.api import Problem
p = Problem()
# p.model = AB()
# p.model = CD()
p.model = ABCD()
# p.model = ABCD_ALT(mode='AB')
p.setup()
Спасибо, @Джастин Грей! Мне нравится ваш первый вариант, поскольку он кажется наиболее масштабируемым и модульным. Я так зациклился на ошибках, что не подумал о включении IndepVarComps в пользовательскую опцию.