{
 "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  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modifying Children of a Group with Configure Method\n",
    "\n",
    "Most of the time, the `setup` method is the only one you need to define on a group.\n",
    "The main exception is the case where you want to modify a solver that was set in one of\n",
    "your children groups. When you call `add_subsystem`, the system you add is instantiated\n",
    "but its `setup` method is not called until after the parent group's `setup` method\n",
    "is finished with its execution. That means that anything you do with that subsystem\n",
    "(e.g., changing the nonlinear solver) will potentially be overwritten by the child system's\n",
    "`setup` if it is assigned there as well.\n",
    "\n",
    "To get around this timing problem, there is a second setup method called `configure`\n",
    "that runs after the `setup` on all subsystems has completed. While `setup` recurses\n",
    "from the top down, `configure` recurses from the bottom up, so that the highest\n",
    "system in the hierarchy takes precedence over all lower ones for any modifications.\n",
    "\n",
    "## Configuring Solvers\n",
    "\n",
    "Here is a simple example where a lower system sets a solver, but we want to change it to a\n",
    "different one in the top-most system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class ImplSimple(om.ImplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', val=1.)\n",
    "        self.add_output('x', val=0.)\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        residuals['x'] = np.exp(outputs['x']) - \\\n",
    "            inputs['a']**2 * outputs['x']**2\n",
    "\n",
    "    def linearize(self, inputs, outputs, jacobian):\n",
    "        jacobian['x', 'x'] = np.exp(outputs['x']) - \\\n",
    "            2 * inputs['a']**2 * outputs['x']\n",
    "        jacobian['x', 'a'] = -2 * inputs['a'] * outputs['x']**2\n",
    "\n",
    "class Sub(om.Group):\n",
    "    def setup(self):\n",
    "        self.add_subsystem('comp', ImplSimple())\n",
    "\n",
    "    def configure(self):\n",
    "        # This solver won't solve the system. We want\n",
    "        # to override it in the parent.\n",
    "        self.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "class Super(om.Group):\n",
    "    def setup(self):\n",
    "        self.add_subsystem('sub', Sub())\n",
    "\n",
    "    def configure(self):\n",
    "        # This will solve it.\n",
    "        self.sub.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "        self.sub.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "top = om.Problem(model=Super())\n",
    "\n",
    "top.setup()\n",
    "\n",
    "print(isinstance(top.model.sub.nonlinear_solver, om.NewtonSolver))\n",
    "print(isinstance(top.model.sub.linear_solver, om.ScipyKrylov))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(isinstance(top.model.sub.nonlinear_solver, om.NewtonSolver))\n",
    "assert(isinstance(top.model.sub.linear_solver, om.ScipyKrylov))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configuring Setup-Dependent I/O\n",
    "\n",
    "Another situation in which the `configure` method might be useful is if the inputs\n",
    "and outputs of a component or subsystem are dependent on the `setup` of another system.\n",
    "\n",
    "Collecting variable metadata information during configure can be done via the\n",
    "`get_io_metadata` method.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.system.System.get_io_metadata\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "The following example is a variation on the model used to illustrate use of an\n",
    "[AddSubtractComp](../../building_blocks/components/add_subtract_comp).  Here we assume the component that\n",
    "provides the vectorized data must be `setup` before the shape of that data is known.\n",
    "The shape information is collected using `get_io_metadata`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class FlightDataComp(om.ExplicitComponent):\n",
    "    \"\"\"\n",
    "    Simulate data generated by an external source/code\n",
    "    \"\"\"\n",
    "    def setup(self):\n",
    "        # number of points may not be known a priori\n",
    "        n = 3\n",
    "\n",
    "        # The vector represents forces at n time points (rows) in 2 dimensional plane (cols)\n",
    "        self.add_output(name='thrust', shape=(n, 2), units='kN')\n",
    "        self.add_output(name='drag', shape=(n, 2), units='kN')\n",
    "        self.add_output(name='lift', shape=(n, 2), units='kN')\n",
    "        self.add_output(name='weight', shape=(n, 2), units='kN')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['thrust'][:, 0] = [500, 600, 700]\n",
    "        outputs['drag'][:, 0]  = [400, 400, 400]\n",
    "        outputs['weight'][:, 1] = [1000, 1001, 1002]\n",
    "        outputs['lift'][:, 1]  = [1000, 1000, 1000]\n",
    "\n",
    "\n",
    "class ForceModel(om.Group):\n",
    "    def setup(self):\n",
    "        self.add_subsystem('flightdatacomp', FlightDataComp(),\n",
    "                           promotes_outputs=['thrust', 'drag', 'lift', 'weight'])\n",
    "\n",
    "        self.add_subsystem('totalforcecomp', om.AddSubtractComp())\n",
    "\n",
    "    def configure(self):\n",
    "        # Some models that require self-interrogation need to be able to add\n",
    "        # I/O in components from the configure method of their containing groups.\n",
    "        # In this case, we can only determine the 'vec_size' for totalforcecomp\n",
    "        # after flightdatacomp has been setup.\n",
    "\n",
    "        meta = self.flightdatacomp.get_io_metadata('output', includes='thrust')\n",
    "        data_shape = meta['thrust']['shape']\n",
    "\n",
    "        self.totalforcecomp.add_equation('total_force',\n",
    "                                         input_names=['thrust', 'drag', 'lift', 'weight'],\n",
    "                                         vec_size=data_shape[0], length=data_shape[1],\n",
    "                                         scaling_factors=[1, -1, 1, -1], units='kN')\n",
    "\n",
    "        self.connect('thrust', 'totalforcecomp.thrust')\n",
    "        self.connect('drag', 'totalforcecomp.drag')\n",
    "        self.connect('lift', 'totalforcecomp.lift')\n",
    "        self.connect('weight', 'totalforcecomp.weight')\n",
    "\n",
    "\n",
    "p = om.Problem(model=ForceModel())\n",
    "p.setup()\n",
    "p.run_model()\n",
    "\n",
    "print(p.get_val('totalforcecomp.total_force', units='kN'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "import numpy as np\n",
    "\n",
    "assert_near_equal(p.get_val('totalforcecomp.total_force', units='kN'),\n",
    "                 np.array([[100, 200, 300], [0, -1, -2]]).T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Variable information may also be collected using list_inputs and list_outputs which provide a somewhat simpler interface with a little less flexibility and a little more overhead. Also, list_inputs and list_outputs return their data as a list of (name, metadata) tuples rather than as a dictionary.\n",
    "\n",
    "## Uses of setup vs. configure\n",
    "\n",
    "To understand when to use setup and when to use configure, see the [Theory Manual entry on how the setup stack works](../../../theory_manual/setup_stack).\n",
    "\n"
   ]
  }
 ],
 "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
}
