{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Adding Subsystems to a Group and Promoting Variables\n",
    "\n",
    "To add a Component or another Group to a Group, use the `add_subsystem` method.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.group.Group.add_subsystem\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## Usage\n",
    "\n",
    "### Add a Component to a Group\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "p = om.Problem()\n",
    "p.model.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))\n",
    "\n",
    "p.setup();\n",
    "print(p.get_val('comp1.a'))\n",
    "print(p.get_val('comp1.b'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert(p.get_val('comp1.a') == 3.0)\n",
    "assert(p.get_val('comp1.b') == 6.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note}\n",
    "Group names must be Pythonic, so they can only contain alphanumeric characters plus the underscore. In addition, the first character in the group name must be a letter of the alphabet. Also, the system name should not duplicate any method or attribute of the `System` API.\n",
    "```\n",
    "\n",
    "### Promote the input and output of a Component\n",
    "Because the promoted names of `indep.a` and `comp.a` are the same, `indep.a` is automatically connected to `comp1.a`.\n",
    "\n",
    "```{note}\n",
    "Inputs are always accessed using unpromoted names even when they are\n",
    "promoted, because promoted input names may not be unique.  The unpromoted name\n",
    "is the full system path to the variable from the point of view of the calling\n",
    "system.  Accessing the variables through the Problem as in this example means\n",
    "that the unpromoted name and the full or absolute pathname are the same.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = om.Problem()\n",
    "p.model.add_subsystem('indep', om.IndepVarComp('a', 3.0),\n",
    "                      promotes_outputs=['a'])\n",
    "p.model.add_subsystem('comp1', om.ExecComp('b=2.0*a'),\n",
    "                      promotes_inputs=['a'])\n",
    "\n",
    "p.setup()\n",
    "p.run_model() \n",
    "\n",
    "print(p.get_val('a'))\n",
    "print(p.get_val('comp1.b'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(p.get_val('a') == 3.0)\n",
    "assert(p.get_val('comp1.b') == 6.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add two Components to a Group nested within another Group\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = om.Problem()\n",
    "p.model.add_subsystem('G1', om.Group())\n",
    "p.model.G1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))\n",
    "p.model.G1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0))\n",
    "\n",
    "p.setup()\n",
    "print(p.get_val('G1.comp1.a'))\n",
    "print(p.get_val('G1.comp1.b'))\n",
    "print(p.get_val('G1.comp2.a'))\n",
    "print(p.get_val('G1.comp2.b'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(p.get_val('G1.comp1.a') == 3.0)\n",
    "assert(p.get_val('G1.comp1.b') == 6.0)\n",
    "assert(p.get_val('G1.comp2.a') == 4.0)\n",
    "assert(p.get_val('G1.comp2.b') == 12.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Promote the input and output of Components to subgroup level\n",
    "\n",
    "In this example, there are two inputs promoted to the same name, so\n",
    "the promoted name *G1.a* is not unique."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# promotes from bottom level up 1\n",
    "p = om.Problem()\n",
    "g1 = p.model.add_subsystem('G1', om.Group())\n",
    "g1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0),\n",
    "                 promotes_inputs=['a'], promotes_outputs=['b'])\n",
    "g1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0),\n",
    "                 promotes_inputs=['a'])\n",
    "g1.set_input_defaults('a', val=3.5)\n",
    "p.setup()\n",
    "\n",
    "# output G1.comp1.b is promoted\n",
    "print(p.get_val('G1.b'))\n",
    "# output G1.comp2.b is not promoted\n",
    "print(p.get_val('G1.comp2.b'))\n",
    "\n",
    "# use unpromoted names for the following 2 promoted inputs\n",
    "print(p.get_val('G1.comp1.a'))\n",
    "print(p.get_val('G1.comp2.a'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(p.get_val('G1.b') == 6.0)\n",
    "assert(p.get_val('G1.comp2.b') == 12.0)\n",
    "\n",
    "assert(p.get_val('G1.comp1.a') == 3.5)\n",
    "assert(p.get_val('G1.comp2.a') == 3.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Promote the input and output of Components from subgroup level up to top level\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# promotes up from G1 level\n",
    "p = om.Problem()\n",
    "g1 = om.Group()\n",
    "g1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))\n",
    "g1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0))\n",
    "\n",
    "# use glob pattern 'comp?.a' to promote both comp1.a and comp2.a\n",
    "# use glob pattern 'comp?.b' to promote both comp1.b and comp2.b\n",
    "p.model.add_subsystem('G1', g1,\n",
    "                      promotes_inputs=['comp?.a'],\n",
    "                      promotes_outputs=['comp?.b'])\n",
    "p.setup()\n",
    "\n",
    "# output G1.comp1.b is promoted\n",
    "print(p.get_val('comp1.b'), 6.0)\n",
    "# output G1.comp2.b is promoted\n",
    "print(p.get_val('comp2.b'), 12.0)\n",
    "\n",
    "# access both promoted inputs using unpromoted names.\n",
    "print(p.get_val('G1.comp1.a'), 3.0)\n",
    "print(p.get_val('G1.comp2.a'), 4.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(p.get_val('comp1.b') == 6.0)\n",
    "assert(p.get_val('comp2.b') == 12.0)\n",
    "\n",
    "assert(p.get_val('G1.comp1.a') == 3.0)\n",
    "assert(p.get_val('G1.comp2.a') == 4.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Promote with an alias to connect an input to a source\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = om.Problem()\n",
    "p.model.add_subsystem('indep', om.IndepVarComp('aa', 3.0),\n",
    "                      promotes=['aa'])\n",
    "p.model.add_subsystem('comp1', om.ExecComp('b=2.0*aa'),\n",
    "                      promotes_inputs=['aa'])\n",
    "\n",
    "# here we alias 'a' to 'aa' so that it will be automatically\n",
    "# connected to the independent variable 'aa'.\n",
    "p.model.add_subsystem('comp2', om.ExecComp('b=3.0*a'),\n",
    "                      promotes_inputs=[('a', 'aa')])\n",
    "\n",
    "p.setup()\n",
    "p.run_model()\n",
    "\n",
    "print(p.get_val('comp1.b'))\n",
    "print(p.get_val('comp2.b'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(p.get_val('comp1.b') == 6.0)\n",
    "assert(p.get_val('comp2.b') == 9.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(group-promotion)=\n",
    "# Promote Inputs and Outputs After Adding Subsystems\n",
    "\n",
    "It is also possible to promote inputs and outputs after a subsystem has been added\n",
    "to a Group using the `promotes` method.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.group.Group.promotes\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## Usage\n",
    "\n",
    "### Promote any subsystem inputs and outputs from the configure function\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SimpleGroup(om.Group):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_subsystem('comp1', om.IndepVarComp('x', 5.0))\n",
    "        self.add_subsystem('comp2', om.ExecComp('b=2*a'))\n",
    "\n",
    "    def configure(self):\n",
    "        self.promotes('comp1', any=['*'])\n",
    "\n",
    "top = om.Problem(model=SimpleGroup())\n",
    "top.setup()\n",
    "\n",
    "print(top.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(top.get_val('x') == 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Promote specific inputs and outputs from the configure function\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SimpleGroup(om.Group):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_subsystem('comp1', om.IndepVarComp('x', 5.0))\n",
    "        self.add_subsystem('comp2', om.ExecComp('b=2*a'))\n",
    "\n",
    "    def configure(self):\n",
    "        self.promotes('comp2', inputs=['a'], outputs=['b'])\n",
    "\n",
    "top = om.Problem(model=SimpleGroup())\n",
    "top.setup()\n",
    "\n",
    "print(top.get_val('a'))\n",
    "print(top.get_val('b'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(top.get_val('a') == 1)\n",
    "assert(top.get_val('b') == 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Specifying source shape and source indices for promoted inputs of a group\n",
    "\n",
    "The arg `src_shape` can be passed to `promotes` or `set_input_defaults` calls in order to\n",
    "specify the shape of the source that the input is expecting.  This allows an output having\n",
    "a different shape to be connected to an input by specifying `src_indices` in the `connect`\n",
    "or `promotes` call, even if there are other `src_indices` specified at lower levels in the\n",
    "system tree for the same input(s).  This basically allows you to specify the 'connection interface'\n",
    "for a given Group, making it easier to use that Group in other models without having to modify\n",
    "its internal `src_indices` based on the shape of whatever sources are connected to its inputs\n",
    "in a given model.\n",
    "\n",
    "Note that if multiple inputs are promoted to the same name then their `src_shape` must match,\n",
    "but their `src_indices` may be different.\n",
    "\n",
    "Below is an example of applying multiple `src_indices` to the same promoted input at different\n",
    "system tree levels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np \n",
    "\n",
    "p = om.Problem()\n",
    "G = p.model.add_subsystem('G', om.Group())\n",
    "\n",
    "# At the top level, we assume that the source has a shape of (3,3), and after we\n",
    "# slice it with [:,:-1], lower levels will see their source having a shape of (3,2)\n",
    "p.model.promotes('G', inputs=['x'], src_indices=om.slicer[:,:-1], src_shape=(3, 3))\n",
    "\n",
    "# This specifies that G.x assumes a source shape of (3,2)\n",
    "G.set_input_defaults('x', src_shape=(3, 2))\n",
    "\n",
    "g1 = G.add_subsystem('g1', om.Group(), promotes_inputs=['x'])\n",
    "g1.add_subsystem('C1', om.ExecComp('y = 3*x', shape=3))\n",
    "\n",
    "# C1.x has a shape of 3, so we apply a slice of [:, 1] to our source which has a shape\n",
    "# of (3,2) to give us our final shape of 3.\n",
    "g1.promotes('C1', inputs=['x'], src_indices=om.slicer[:, 1], src_shape=(3, 2))\n",
    "\n",
    "g2 = G.add_subsystem('g2', om.Group(), promotes_inputs=['x'])\n",
    "g2.add_subsystem('C2', om.ExecComp('y = 2*x', shape=2))\n",
    "\n",
    "# C2.x has a shape of 2, so we apply flat source indices of [1,5] to our source which has\n",
    "# a shape of (3,2) to give us our final shape of 2.\n",
    "g2.promotes('C2', inputs=['x'], src_indices=[1, 5], src_shape=(3, 2), flat_src_indices=True)\n",
    "\n",
    "p.setup()\n",
    "\n",
    "inp = np.arange(9).reshape((3,3)) + 1.\n",
    "\n",
    "p.set_val('x', inp[:, :-1])\n",
    "p.run_model()\n",
    "\n",
    "print(p['x'])\n",
    "print(p['G.g1.C1.y'])\n",
    "print(p['G.g2.C2.y'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(p['x'], inp[:, :-1])\n",
    "assert_near_equal(p['G.g1.C1.y'], inp[:, :-1][:, 1]*3.)\n",
    "assert_near_equal(p['G.g2.C2.y'], inp[:, :-1].flatten()[[1,5]]*2.)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
