{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "%pylab inline\n",
    "from ipyparallel import Client, error\n",
    "cluster=Client(profile=\"mpi\")\n",
    "view=cluster[:]\n",
    "view.block=True\n",
    "\n",
    "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": [
    "# Listing Variables\n",
    "\n",
    "When working with a model, it may sometimes be helpful to examine the input and output variables. Several methods are provided for this purpose.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.system.System.list_inputs\n",
    "        :noindex:\n",
    "\n",
    "    .. automethod:: openmdao.core.system.System.list_outputs\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## Example\n",
    "\n",
    "In the following example, we create a model consisting of two instances of `ImplicitComponent`.\n",
    "\n",
    "The implicit components are both instances of `QuadraticComp`, defined as shown here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class QuadraticComp(om.ImplicitComponent):\n",
    "    \"\"\"\n",
    "    A Simple Implicit Component representing a Quadratic Equation.\n",
    "\n",
    "    R(a, b, c, x) = ax^2 + bx + c\n",
    "\n",
    "    Solution via Quadratic Formula:\n",
    "    x = (-b + sqrt(b^2 - 4ac)) / 2a\n",
    "    \"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', val=1., tags=['tag_a'])\n",
    "        self.add_input('b', val=1.)\n",
    "        self.add_input('c', val=1.)\n",
    "        self.add_output('x', val=0., tags=['tag_x'])\n",
    "\n",
    "    def setup_partials(self):\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        x = outputs['x']\n",
    "        residuals['x'] = a * x ** 2 + b * x + c\n",
    "\n",
    "    def solve_nonlinear(self, inputs, outputs):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        outputs['x'] = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These two components are placed in a `Group` with their common inputs promoted together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "group = om.Group()\n",
    "\n",
    "sub = group.add_subsystem('sub', om.Group(), promotes_inputs=['a', 'b', 'c'])\n",
    "\n",
    "sub.add_subsystem('comp1', QuadraticComp(), promotes_inputs=['a', 'b', 'c'])\n",
    "sub.add_subsystem('comp2', QuadraticComp(), promotes_inputs=['a', 'b', 'c'])\n",
    "\n",
    "global prob\n",
    "prob = om.Problem(model=group)\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('a', 1.)\n",
    "prob.set_val('b', -4.)\n",
    "prob.set_val('c', 3.)\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Usage\n",
    "\n",
    "(list-inputs)=\n",
    "### *List Inputs*\n",
    "\n",
    "The `list_inputs()` method on a System will display all the inputs in execution order with their values. By default, the variable name and variable value are displayed. Also by default, the variables are displayed as part of the System hierarchy. Finally, the default is to display this information to 'stdout'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_inputs()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Outputs*\n",
    "\n",
    "The `list_outputs()` method will display all the outputs in execution order. There are many options to this method, which we will explore below. For this example, we will only display the value in addition to the name of the output variable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Implicit or Explicit Outputs*\n",
    "\n",
    "Note that explicit and implicit outputs are listed separately. If you are only interested in seeing one or the other, you can exclude the ones you do not wish to see via the implicit and explicit arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(implicit=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(explicit=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Get List via Return Value*\n",
    "\n",
    "Both of these methods also return the information in the form of a list. You can disable the display of the information by setting the argument `out_stream` to `None` and then access the data instead via the return value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# list inputs\n",
    "inputs = prob.model.list_inputs(out_stream=None)\n",
    "print(sorted(inputs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `System.get_io_metadata` method, which is used internally by `list_inputs` and `list_outputs`, returns the specified variable information as a dict.\n",
    "\n",
    "### *List Names Only*\n",
    "\n",
    "If you just need the names of the variables, you can disable the display of the values by setting the optional argument, `val`, to *False*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# list inputs\n",
    "inputs = prob.model.list_inputs(val=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# list only explicit outputs\n",
    "outputs = prob.model.list_outputs(implicit=False, val=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Names and Promoted Name*\n",
    "\n",
    "If you want the names of the variables and their promoted name within the model, you can enable the display of promoted names by setting the optional argument, `prom_name`, to *True*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(prom_name=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Variables Filtered by Name*\n",
    "\n",
    "You can use the `includes` and `excludes` optional arguments to filter what variables are returned from `System.list_inputs` and `System.list_outputs`. Here are some short examples showing this feature."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# list inputs\n",
    "inputs = prob.model.list_inputs(val=False, includes=['*comp2*',])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "inputs = prob.model.list_inputs(val=False, excludes=['*comp2*',])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# list only explicit outputs\n",
    "outputs = prob.model.list_outputs(implicit=False, val=False, includes=['*b',])\n",
    "outputs = prob.model.list_outputs(implicit=False, val=False, excludes=['*b',])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Variables Filtered by Tags*\n",
    "\n",
    "When you add inputs and outputs to components, you can optionally set tags on the variables. These tags can then be used to filter what variables are printed and returned by the `System.list_inputs` and `System.list_outputs` methods. Each of those methods has an optional argument `tags` for that purpose.\n",
    "\n",
    "Here is a simple example to show you how this works. Imagine that a model-builder builds a model with some set of variables they expect other non-model-builder users to vary. They want to classify the inputs into two sets: “beginner” and “advanced”. The model-builder would like to write some functions that query the model for the set of *beginner* and *advanced* inputs and do some stuff with those lists (like make fancy formatted outputs or something)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "class ActuatorDiscWithTags(om.ExplicitComponent):\n",
    "    \"\"\"Simple wind turbine model based on actuator disc theory\"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "\n",
    "        # Inputs\n",
    "        self.add_input('a', 0.5, desc=\"Induced Velocity Factor\", tags=\"advanced\")\n",
    "        self.add_input('Area', 10.0, units=\"m**2\", desc=\"Rotor disc area\", tags=\"basic\")\n",
    "        self.add_input('rho', 1.225, units=\"kg/m**3\", desc=\"air density\", tags=\"advanced\")\n",
    "        self.add_input('Vu', 10.0, units=\"m/s\",\n",
    "                       desc=\"Freestream air velocity, upstream of rotor\", tags=\"basic\")\n",
    "\n",
    "        # Outputs\n",
    "        self.add_output('Vr', 0.0, units=\"m/s\",\n",
    "                        desc=\"Air velocity at rotor exit plane\")\n",
    "        self.add_output('Vd', 0.0, units=\"m/s\",\n",
    "                        desc=\"Slipstream air velocity, downstream of rotor\")\n",
    "        self.add_output('Ct', 0.0, desc=\"Thrust Coefficient\")\n",
    "        self.add_output('thrust', 0.0, units=\"N\",\n",
    "                        desc=\"Thrust produced by the rotor\")\n",
    "        self.add_output('Cp', 0.0, desc=\"Power Coefficient\")\n",
    "        self.add_output('power', 0.0, units=\"W\", desc=\"Power produced by the rotor\")\n",
    "\n",
    "    def setup_partials(self):\n",
    "        self.declare_partials('Vr', ['a', 'Vu'])\n",
    "        self.declare_partials('Vd', 'a')\n",
    "        self.declare_partials('Ct', 'a')\n",
    "        self.declare_partials('thrust', ['a', 'Area', 'rho', 'Vu'])\n",
    "        self.declare_partials('Cp', 'a')\n",
    "        self.declare_partials('power', ['a', 'Area', 'rho', 'Vu'])\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        \"\"\" Considering the entire rotor as a single disc that extracts\n",
    "        velocity uniformly from the incoming flow and converts it to\n",
    "        power.\"\"\"\n",
    "\n",
    "        a = inputs['a']\n",
    "        Vu = inputs['Vu']\n",
    "\n",
    "        qA = .5 * inputs['rho'] * inputs['Area'] * Vu ** 2\n",
    "\n",
    "        outputs['Vd'] = Vd = Vu * (1 - 2 * a)\n",
    "        outputs['Vr'] = .5 * (Vu + Vd)\n",
    "\n",
    "        outputs['Ct'] = Ct = 4 * a * (1 - a)\n",
    "        outputs['thrust'] = Ct * qA\n",
    "\n",
    "        outputs['Cp'] = Cp = Ct * (1 - a)\n",
    "        outputs['power'] = Cp * qA * Vu\n",
    "\n",
    "    def compute_partials(self, inputs, J):\n",
    "        \"\"\" Jacobian of partial derivatives.\"\"\"\n",
    "\n",
    "        a = inputs['a']\n",
    "        Vu = inputs['Vu']\n",
    "        Area = inputs['Area']\n",
    "        rho = inputs['rho']\n",
    "\n",
    "        # pre-compute commonly needed quantities\n",
    "        a_times_area = a * Area\n",
    "        one_minus_a = 1.0 - a\n",
    "        a_area_rho_vu = a_times_area * rho * Vu\n",
    "\n",
    "        J['Vr', 'a'] = -Vu\n",
    "        J['Vr', 'Vu'] = one_minus_a\n",
    "\n",
    "        J['Vd', 'a'] = -2.0 * Vu\n",
    "\n",
    "        J['Ct', 'a'] = 4.0 - 8.0 * a\n",
    "\n",
    "        J['thrust', 'a'] = .5 * rho * Vu**2 * Area * J['Ct', 'a']\n",
    "        J['thrust', 'Area'] = 2.0 * Vu**2 * a * rho * one_minus_a\n",
    "        J['thrust', 'rho'] = 2.0 * a_times_area * Vu ** 2 * (one_minus_a)\n",
    "        J['thrust', 'Vu'] = 4.0 * a_area_rho_vu * (one_minus_a)\n",
    "\n",
    "        J['Cp', 'a'] = 4.0 * a * (2.0 * a - 2.0) + 4.0 * (one_minus_a)**2\n",
    "\n",
    "        J['power', 'a'] = 2.0 * Area * Vu**3 * a * rho * (\n",
    "        2.0 * a - 2.0) + 2.0 * Area * Vu**3 * rho * one_minus_a ** 2\n",
    "        J['power', 'Area'] = 2.0 * Vu**3 * a * rho * one_minus_a ** 2\n",
    "        J['power', 'rho'] = 2.0 * a_times_area * Vu ** 3 * (one_minus_a)**2\n",
    "        J['power', 'Vu'] = 6.0 * Area * Vu**2 * a * rho * one_minus_a**2\n",
    "\n",
    "\n",
    "# build the model\n",
    "prob = om.Problem()\n",
    "indeps = prob.model.add_subsystem('indeps', om.IndepVarComp(), promotes=['*'])\n",
    "indeps.add_output('a', .5, tags=\"advanced\")\n",
    "indeps.add_output('Area', 10.0, units='m**2', tags=\"basic\")\n",
    "indeps.add_output('rho', 1.225, units='kg/m**3', tags=\"advanced\")\n",
    "indeps.add_output('Vu', 10.0, units='m/s', tags=\"basic\")\n",
    "\n",
    "prob.model.add_subsystem('a_disk', ActuatorDiscWithTags(),\n",
    "                        promotes_inputs=['a', 'Area', 'rho', 'Vu'])\n",
    "\n",
    "# setup the optimization\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "\n",
    "prob.model.add_design_var('a', lower=0., upper=1.)\n",
    "# negative one so we maximize the objective\n",
    "prob.model.add_objective('a_disk.Cp', scaler=-1)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_inputs(tags='basic', units=True, shape=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_inputs(tags=['basic','advanced'], units=True, shape=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(tags='basic', units=False, shape=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(tags=['basic','advanced'], units=False, shape=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that if you only have one tag, you can set the argument tags to a string. If you have more than one tag, you use a list of strings.\n",
    "\n",
    "This example showed how to add tags when using the `add_input` and `add_output` methods. You can also add tags to `IndepVarComp` and `ExecComp` Components using code like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "comp = om.IndepVarComp('indep_var', tags='tag1')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ec = om.ExecComp('y=x+z+1.',\n",
    "                  x={'val': 1.0, 'units': 'm', 'tags': 'tagx'},\n",
    "                  y={'units': 'm', 'tags': ['tagy','tagm']},\n",
    "                  z={'val': 2.0, 'tags': 'tagz'},\n",
    "                  )\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that outputs of `IndepVarComp` are always tagged with `indep_var_comp`.\n",
    "\n",
    "### *List Residuals Above a Tolerance*\n",
    "\n",
    "In some cases, it might be convenient to only list variables whose residuals are above a given tolerance. The `System.list_outputs` method provides an optional argument, `residuals_tol` for this purpose."
   ]
  },
  {
   "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_src68\", get_code(\"openmdao.test_suite.components.sellar.SellarImplicitDis1\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarImplicitDis1` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src68`\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_src69\", get_code(\"openmdao.test_suite.components.sellar.SellarImplicitDis2\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarImplicitDis2` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src69`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarImplicitDis1, SellarImplicitDis2\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p1', om.IndepVarComp('x', 1.0))\n",
    "model.add_subsystem('d1', SellarImplicitDis1())\n",
    "model.add_subsystem('d2', SellarImplicitDis2())\n",
    "model.connect('d1.y1', 'd2.y1')\n",
    "model.connect('d2.y2', 'd1.y2')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "model.nonlinear_solver.options['maxiter'] = 5\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.linear_solver.precon = om.LinearBlockGS()\n",
    "\n",
    "prob.setup()\n",
    "prob.set_solver_print(level=-1)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "outputs = model.list_outputs(residuals_tol=0.01, residuals=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(outputs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Additional Variable Metadata*\n",
    "\n",
    "The `list_outputs()` method has many options to also display units, shape, bounds (lower and upper), and scaling (res, res0, and res_ref) for the variables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p1', om.IndepVarComp('x', 12.0,\n",
    "                                          lower=1.0, upper=100.0,\n",
    "                                          ref=1.1, ref0=2.1,\n",
    "                                          units='inch',\n",
    "                                          ))\n",
    "model.add_subsystem('p2', om.IndepVarComp('y', 1.0,\n",
    "                                          lower=2.0, upper=200.0,\n",
    "                                          ref=1.2, res_ref=2.2,\n",
    "                                          units='ft',\n",
    "                                          ))\n",
    "model.add_subsystem('comp', om.ExecComp('z=x+y',\n",
    "                                        x={'val': 0.0, 'units': 'inch'},\n",
    "                                        y={'val': 0.0, 'units': 'inch'},\n",
    "                                        z={'val': 0.0, 'units': 'inch'}))\n",
    "model.connect('p1.x', 'comp.x')\n",
    "model.connect('p2.y', 'comp.y')\n",
    "\n",
    "prob.setup()\n",
    "prob.set_solver_print(level=0)\n",
    "prob.run_model()\n",
    "\n",
    "inputs = prob.model.list_inputs(units=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outputs = prob.model.list_outputs(implicit=False,\n",
    "                                  val=True,\n",
    "                                  units=True,\n",
    "                                  shape=True,\n",
    "                                  bounds=True,\n",
    "                                  residuals=True,\n",
    "                                  scaling=True,\n",
    "                                  hierarchical=False,\n",
    "                                  print_arrays=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(sorted(outputs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outputs = prob.model.list_outputs(implicit=False,\n",
    "                                  val=True,\n",
    "                                  units=True,\n",
    "                                  shape=True,\n",
    "                                  bounds=True,\n",
    "                                  residuals=True,\n",
    "                                  scaling=True,\n",
    "                                  hierarchical=True,\n",
    "                                  print_arrays=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Print Array Values*\n",
    "\n",
    "The `list_inputs()` and `list_outputs()` methods both have a `print_arrays` option. By default, this option is set to False and only the norm of the array will appear in the tabular display. The norm value is surrounded by vertical bars to indicate that it is a norm. When the option is set to True, the complete value of the array will also be a displayed below the row. The format is affected by the values set with `numpy.set_printoptions`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.utils.general_utils import printoptions\n",
    "\n",
    "class ArrayAdder(om.ExplicitComponent):\n",
    "    \"\"\"\n",
    "    Just a simple component that has array inputs and outputs\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, size):\n",
    "        super().__init__()\n",
    "        self.size = size\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=np.zeros(self.size), units='inch')\n",
    "        self.add_output('y', val=np.zeros(self.size), units='ft')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = inputs['x'] + 10.0\n",
    "\n",
    "size = 30\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('des_vars', om.IndepVarComp('x', np.ones(size), units='inch'),\n",
    "                         promotes=['x'])\n",
    "prob.model.add_subsystem('mult', ArrayAdder(size), promotes=['x', 'y'])\n",
    "\n",
    "prob.setup()\n",
    "prob['x'] = np.arange(size)\n",
    "prob.run_driver()\n",
    "\n",
    "prob.model.list_inputs(val=True,\n",
    "                       units=True,\n",
    "                       hierarchical=True,\n",
    "                       print_arrays=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with printoptions(edgeitems=3, infstr='inf',\n",
    "                  linewidth=75, nanstr='nan', precision=8,\n",
    "                  suppress=False, threshold=1000, formatter=None):\n",
    "\n",
    "    prob.model.list_outputs(val=True,\n",
    "                            implicit=False,\n",
    "                            units=True,\n",
    "                            shape=True,\n",
    "                            bounds=True,\n",
    "                            residuals=True,\n",
    "                            scaling=True,\n",
    "                            hierarchical=False,\n",
    "                            print_arrays=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(val=True,\n",
    "                        implicit=False,\n",
    "                        units=True,\n",
    "                        shape=True,\n",
    "                        bounds=True,\n",
    "                        residuals=True,\n",
    "                        scaling=True,\n",
    "                        hierarchical=True,\n",
    "                        print_arrays=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Print Minimum or Maximum Array Values*\n",
    "\n",
    "When working with large arrays, it can be difficult to determine how the array is interacting with the upper and lower bounds by looking through the output of the entire contents. To provide a quick visual reference, the `list_inputs()` and `list_outputs()` methods have `print_min` and `print_max` options that output columns with the minimum and maximum values of the array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_inputs(val=True,\n",
    "                       units=True,\n",
    "                       hierarchical=True,\n",
    "                       print_min=True,\n",
    "                       print_max=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(val=True,\n",
    "                        implicit=False,\n",
    "                        units=True,\n",
    "                        shape=True,\n",
    "                        bounds=True,\n",
    "                        residuals=True,\n",
    "                        scaling=True,\n",
    "                        hierarchical=True,\n",
    "                        print_min=True,\n",
    "                        print_max=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note}\n",
    "It is normally required to run the model before `list_inputs()` and `list_outputs()` can be used. This is because the final setup that occurs just before execution determines the hierarchy and builds the data structures and connections. In some cases however, it can be useful to call these functions on a system prior to execution to assist in configuring your model. At `configure` time, basic metadata about a system’s inputs and outputs is available. See the documentation for the [configure() method (../core_features/working_with_groups/configure_method.ipynb) for one such use case.\n",
    "```\n",
    "\n",
    "### *List Global Shape*\n",
    "\n",
    "When working with [Distributed Variables](../core_features/working_with_components/distributed_components.ipynb), it may also be useful to display the global shape of a variable as well as the shape on the current processor. Note that this information is not available until after the model has been completely set up and run."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note}\n",
    "This feature requires MPI, and may not be able to be run on Colab.\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_src70\", get_code(\"openmdao.test_suite.components.distributed_components.DistribComp\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `DistribComp` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src70`\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_src71\", get_code(\"openmdao.test_suite.components.distributed_components.Summer\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `Summer` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src71`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.distributed_components import DistribComp, Summer\n",
    "\n",
    "size = 15\n",
    "\n",
    "model = om.Group()\n",
    "indep = model.add_subsystem(\"indep\", om.IndepVarComp())\n",
    "indep.add_output('x', np.zeros(size), distributed=True)\n",
    "model.add_subsystem(\"C2\", DistribComp(size=size))\n",
    "model.add_subsystem(\"C3\", Summer(size=size))\n",
    "\n",
    "model.connect('indep.x', 'C2.invec')\n",
    "model.connect('C2.outvec', 'C3.invec', src_indices=om.slicer[:])\n",
    "\n",
    "prob = om.Problem(model)\n",
    "prob.setup()\n",
    "\n",
    "# prior to model execution, the global shape of a distributed variable is not available\n",
    "# and only the local portion of the value is available\n",
    "model.C2.list_inputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "model.C2.list_outputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True)\n",
    "\n",
    "prob['indep.x'] = np.ones(size)\n",
    "prob.run_model()\n",
    "\n",
    "# after model execution, the global shape of a distributed variable is available\n",
    "# and the complete global value is available\n",
    "model.C2.list_inputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "model.C2.list_outputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True)\n",
    "\n",
    "# note that the shape of the input variable for the non-distributed Summer component\n",
    "# is different on each processor, use the all_procs argument to display on all processors\n",
    "model.C3.list_inputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True, all_procs=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "print(prob['C3.sum'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(prob['C3.sum'], -25.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Listing Problem Variables*\n",
    "\n",
    "Problem has a method `list_problem_vars` which prints out the values and metadata for design, constraint, and objective variables.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.list_problem_vars\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "You can optionally print out a variety of metadata. In this example, all the metadata is printed. The `print_arrays` option is also set to true so that full array values are printed, and `min` and `max` are used so that the array's lowest and highest values are shown."
   ]
  },
  {
   "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_src72\", get_code(\"openmdao.test_suite.components.sellar.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src72`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "model = prob.model\n",
    "model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "prob.driver.options['tol'] = 1e-9\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.list_problem_vars(print_arrays=True,\n",
    "                       desvar_opts=['lower', 'upper', 'ref', 'ref0',\n",
    "                                    'indices', 'adder', 'scaler',\n",
    "                                    'parallel_deriv_color', 'min', 'max'],\n",
    "                       cons_opts=['lower', 'upper', 'equals', 'ref', 'ref0',\n",
    "                                  'indices', 'adder', 'scaler', 'linear', 'min', 'max'],\n",
    "                       objs_opts=['ref', 'ref0',\n",
    "                                  'indices', 'adder', 'scaler',\n",
    "                                  'parallel_deriv_color',\n",
    "                                  'cache_linear_solution'])"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "interpreter": {
   "hash": "e6c7471802ed76737b16357fb02af5587f3a4cbee5ea7658f3f9a6981469039b"
  },
  "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.10"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
