是否可以在 ExplicitComponent 中对 ExplicitComponents 进行分组?

Is it possible to group ExplicitComponents inside an ExplicitComponent?

我有一个 ExplicitComponent 'CSTSurface',它需要一些权重并生成一条曲线。 我想将其中两个组合在一起以生成双表面机翼 ('CSTAerofoil')。

目前,我使用一个带有别名提升变量的组。我宁愿使用一个仅采用一个输入权重向量的组件(如 ExplicitComponent),然后将它们分派给两个 CSTSurface 组件。

这可能吗?实现此目标的最佳方法是什么?

我的代码(暂时原谅懒惰的 'fd'!):

import openmdao.api as om
import numpy as np
import scipy.special as scsp
from types import FunctionType


class CSTSurface(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('nBP', types=int)
        self.options.declare('nPoints', types=int)
        self.options.declare('classFunc', default=lambda x: (x**0.5)*(1-x), types=FunctionType, recordable=False)  # A little experimental, allow class functions to be passed as an option?

    def setup(self):
        nPoints = self.options['nPoints']
        nBP = self.options['nBP']

        self.add_input('xArray', shape=nPoints)
        self.add_input('weights', shape=nBP)  # Must be a row vector for multiplication to work right.

        self.add_output('surfaceArray', shape=nPoints)

        self.declare_partials('surfaceArray', ['xArray', 'weights'], method='fd')

    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
        x = inputs['xArray']
        weights = np.append(inputs['weights'], [0.0, 0.0])
        C = self.options['classFunc']
        nBP = self.options['nBP']
        nPoints = self.options['nPoints']

        # Build bezier matrix
        B = np.zeros((nPoints, nBP + 2))
        for r in range(nBP):
            S = scsp.binom(nBP, r)*(x**r)*((1-x)**(nBP-r))  # Shape function
            B[:, r] = C(x)*S
        B[:, nBP] = x  # Trailing edge thickness terms
        B[:, nBP+1] = x*((1-x)**0.5)*((1-x)**nBP)  # Leading edge modification
        # Multiply by weights
        B = B * weights
        # Sum each row to get points
        surfaceArray = np.sum(B, axis=1)

        outputs['surfaceArray'] = surfaceArray


class CSTAerofoil(om.Group):

    def initialize(self):
        self.options.declare('nBPUpper', types=int)
        self.options.declare('nBPLower', types=int)
        self.options.declare('nPoints', types=int)
        self.options.declare('classFunc', default=lambda x: (x**0.5)*(1-x), types=FunctionType, recordable=False)  # A little experimental, allow class functions to be passed as an option?

    def setup(self):
        nPoints = self.options['nPoints']
        nBPUpper = self.options['nBPUpper']
        nBPLower = self.options['nBPLower']
        C = self.options['classFunc']

        self.add_subsystem('upperCSTSurface', CSTSurface(nBP=nBPUpper, nPoints=nPoints, classFunc=C),
                           promotes_inputs=[('weights', 'upperWeights'), 'xArray'],
                           promotes_outputs=[('surfaceArray', 'upperSurfaceArray')])
        self.add_subsystem('lowerCSTSurface', CSTSurface(nBP=nBPLower, nPoints=nPoints, classFunc=C),
                           promotes_inputs=[('weights', 'lowerWeights'), 'xArray'],
                           promotes_outputs=[('surfaceArray', 'lowerSurfaceArray')])

当您开始学习如何在 OpenMDAO 中构建东西时,有时很想尝试对所有内容使用 OpenMDAO 结构。例如,在 OpenMDAO 中,组件代表最小的计算工作单元,您可以根据需要剔除多个副本。然后组允许您根据需要组织这些副本。这就是您所做的,但正如您所指出的那样,这并不是您想要从组件中构造出您想要的结构,因此您想知道如何将组件嵌套在它们自身中。

在其他组件实例的计算中嵌套组件实例在技术上并非不可能,但我不推荐这样做。你必须做大量的工作来构建额外的字典并传递它们。这是不值得的。

相反,我们可以使用基本的 a python class 和方法(即 computecompute_partials)恢复到更基本的对象组合设计争论。 助手 class 可以处理 CST 计算,然后一个组件有两个实例,根据需要从权重数组中提取。

像这样:

import openmdao.api as om
import numpy as np
import scipy.special as scsp
from types import FunctionType


class CSTSurface():

    def __init__(self, n_Bp, n_points, class_func):
       self.n_Bp = n_Bp
       self.n_points = n_points 
       self.class_func = class_func

    def compute_surface(self, x_array, weights):
        x = x_array
        weights = np.append(weights, [0.0, 0.0])
        C = self.class_func
        n_BP = self.n_Bp
        n_points = self.n_points

        # Build bezier matrix
        B = np.zeros((n_points, n_BP + 2))
        for r in range(n_BP):
            S = scsp.binom(n_BP, r)*(x**r)*((1-x)**(n_BP-r))  # Shape function
            B[:, r] = C(x)*S
        B[:, n_BP] = x  # Trailing edge thickness terms
        B[:, n_BP+1] = x*((1-x)**0.5)*((1-x)**n_BP)  # Leading edge modification
        # Multiply by weights
        B = B * weights
        # Sum each row to get points
        surface_array = np.sum(B, axis=1)

        return surface_array


class CSTAerofoil(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('n_Bp_upper', types=int)
        self.options.declare('n_Bp_lower', types=int)
        self.options.declare('n_points', types=int)
        self.options.declare('class_func', default=lambda x: (x**0.5)*(1-x), types=FunctionType, recordable=False)  # A little experimental, allow class functions to be passed as an option?


    def setup(self):
        n_points = self.options['n_points']
        n_Bp_upper = self.options['n_Bp_upper']
        n_Bp_lower = self.options['n_Bp_lower']
        C = self.options['class_func']

        self.cst_upper = CSTSurface(n_Bp_upper, n_points, C)
        self.cst_lower = CSTSurface(n_Bp_lower, n_points, C)

        self.add_input('x_array', shape=n_points)

        self.add_input('weights', shape=n_Bp_upper+n_Bp_lower)

        self.add_output('surface', shape=2*n_points)

    def compute(self, inputs, outputs): 
        n_points = self.options['n_points']
        n_Bp_upper = self.options['n_Bp_upper']
        n_Bp_lower = self.options['n_Bp_lower']

        outputs['surface'][:n_points] = self.cst_upper.compute_surface(inputs['x_array'], inputs['weights'][:n_Bp_upper])
        outputs['surface'][n_points:] = self.cst_lower.compute_surface(inputs['x_array'], inputs['weights'][n_Bp_upper:])


if __name__ == "__main__": 

    p = om.Problem()

    p.model.add_subsystem('cst', CSTAerofoil(n_points=10, n_Bp_upper=3, n_Bp_lower=4))

    p.setup()

    p['cst.weights'] = [1,2,3,1,2,3,4]
    p['cst.x_array'] = np.linspace(0,1,10)

    p.run_model()


    p.model.list_outputs(print_arrays=True)