title: Is it possible to group ExplicitComponents inside an ExplicitComponent?
body: <p>I have an ExplicitComponent, 'CSTSurface', which takes some weights and generates a curve.
  I'd like to group two of these together to generate a two-surface aerofoil ('CSTAerofoil').</p>
  
  <p>Currently, I use a group with aliased promoted variables. I'd rather use a component that just took one vector of input weights (like an ExplicitComponent) and then dispatched them to two CSTSurface components.</p>
  
  <p>Is this possible? What would be the best way to achieve this?</p>
  
  <p>My code (excuse the lazy 'fd' for now!):</p>
  
  <pre class="lang-py prettyprint-override"><code>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')])
  </code></pre>