OpenMDAO2 ограничивает модульную архитектуру программного обеспечения

Ограничения 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. Я что-то пропустил? Любая помощь или руководство будут приветствоваться.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Один из способов заключается в том, чтобы сделать его необязательным, если 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 в пользовательскую опцию.

gbarter 11.05.2019 05:47

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