{
 "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": [
    "# Visualizing The Structure of Your Model\n",
    "\n",
    "OpenMDAO models can have deep hierarchies of groups and components, with many connections between them.\n",
    "Often times, it is helpful to visualize the model structure. OpenMDAO provides a model visualization tool\n",
    "that is accessible via the [openmdao](om-command-n2) command line tool.\n",
    "\n",
    "For example, if you had built the Sellar problem with a missing connection, and you figured out that there\n",
    "was a missing connection by running [check setup](../command_line/check_setup),\n",
    "then you might want to take a closer look at the model to help figure out what's going on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.notebook_utils import get_code\n",
    "from myst_nb import glue\n",
    "glue(\"code_src3\", get_code(\"openmdao.test_suite.components.sellar.SellarDis1\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDis1` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src3`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.notebook_utils import get_code\n",
    "from myst_nb import glue\n",
    "glue(\"code_src4\", get_code(\"openmdao.test_suite.components.sellar.SellarDis2\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDis2` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src4`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2\n",
    "\n",
    "\n",
    "class SellarMDAConnect(om.Group):\n",
    "\n",
    "    def setup(self):\n",
    "        cycle = self.add_subsystem('cycle', om.Group(), promotes_inputs=['x', 'z'])\n",
    "        cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z'])\n",
    "        cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z'])\n",
    "        cycle.connect('d1.y1', 'd2.y1')\n",
    "\n",
    "        ######################################\n",
    "        # This is a \"forgotten\" connection!!\n",
    "        ######################################\n",
    "        #cycle.connect('d2.y2', 'd1.y2')\n",
    "\n",
    "        cycle.set_input_defaults('x', 1.0)\n",
    "        cycle.set_input_defaults('z', np.array([5.0, 2.0]))\n",
    "\n",
    "        # Nonlinear Block Gauss Seidel is a gradient free solver\n",
    "        cycle.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "        self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',\n",
    "                                                  z=np.array([0.0, 0.0]), x=0.0),\n",
    "                           promotes_inputs=['x', 'z'])\n",
    "\n",
    "        self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'))\n",
    "        self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'))\n",
    "\n",
    "        self.connect('cycle.d1.y1', ['obj_cmp.y1', 'con_cmp1.y1'])\n",
    "        self.connect('cycle.d2.y2', ['obj_cmp.y2', 'con_cmp2.y2'])\n",
    "\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model = SellarMDAConnect()\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "prob.driver.options['tol'] = 1e-8\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.model.add_design_var('x', lower=0, upper=10)\n",
    "prob.model.add_design_var('z', lower=0, upper=10)\n",
    "prob.model.add_objective('obj_cmp.obj')\n",
    "prob.model.add_constraint('con_cmp1.con1', upper=0)\n",
    "prob.model.add_constraint('con_cmp2.con2', upper=0)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 2.0)\n",
    "prob.set_val('z', [-1., -1.])\n",
    "\n",
    "prob.run_driver()\n",
    "print('minimum found at')\n",
    "print(prob.get_val('x')[0])\n",
    "print(prob.get_val('z'))\n",
    "print('minumum objective')\n",
    "print(prob.get_val('obj_cmp.obj')[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can generate a visualization of the model in $N^2$ form with the following command:\n",
    "```\n",
    "openmdao n2 sellar.py\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "om.n2(prob, show_browser=False, embeddable=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This diagram is a version of a design-structure matrix, with the model hierarchy displayed on the left side.\n",
    "On the diagonal is each input and output of each of the components. Off-diagonal blocks indicate data connections.\n",
    "Feed-forward connections are shown in the upper triangle, and feed-back connections (the kind that cause cycles) are shown in the lower triangle.\n",
    "If you hover over any of the blocks on the diagonal, the incoming and outgoing connections are highlighted with arrows.\n",
    "\n",
    "The unconnected input mentioned above, `cycle.d1.y2`, is highlighted in red to immediately draw attention to a possible problem.\n",
    "You can also see that it's a member of the cycle group (in this case, that was obvious by its name, but with variable promotion that will not always be the case).\n",
    "Furthermore, you can see that the variable you would want to connect `cycle.d1.y1` to, `cycle.d2.y2`, is in the same group.\n",
    "The lack of cycles in the model is made visually evident by the lack of any connections in the lower triangle of the diagram.\n",
    "\n",
    "Collapse groups down by right-clicking on them, and zoom in and out of different parts of the hierarchy by left-clicking.\n",
    "These controls help show that the $N^2$ diagram is a powerful tool for letting you inspect and understand large, complex models.\n",
    "\n",
    "The right side of the diagram shows the solver hierarchy. Initially, it shows the linear solvers but by clicking on\n",
    "the `Toggle Solver Names` button in the toolbar, the names of the nonlinear solvers can be displayed."
   ]
  }
 ],
 "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
