{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "from ipyparallel import Client, error  # noqa: F401\n",
    "cluster=Client(profile=\"mpi\")\n",
    "view=cluster[:]\n",
    "view.block=True\n",
    "\n",
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "attachments": {},
   "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",
    "    .. automethod:: openmdao.core.system.System.list_vars\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)"
   ]
  },
  {
   "attachments": {},
   "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()"
   ]
  },
  {
   "attachments": {},
   "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_inputs();"
   ]
  },
  {
   "attachments": {},
   "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();"
   ]
  },
  {
   "attachments": {},
   "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);"
   ]
  },
  {
   "attachments": {},
   "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",
    "\n",
    "from pprint import pprint\n",
    "pprint(sorted(inputs))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Names Only*\n",
    "\n",
    "If you just want to see 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": [
    "prob.model.list_inputs(val=False);"
   ]
  },
  {
   "attachments": {},
   "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);"
   ]
  },
  {
   "attachments": {},
   "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": [
    "prob.model.list_inputs(val=False, includes=['*comp2*',]);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.list_outputs(val=False, excludes=['*comp2*',]);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Independent Variables and Design Variables*\n",
    "\n",
    "The `System.list_inputs` method also provides a way to determine which inputs you are ultimately responsible for setting. The [inputs report](../reports/reports_system.ipynb) achieves this in a graphical format, but this method allows it to be done programmatically.\n",
    "\n",
    "Consider the following simple example using the Sellar model, where we intentionally have not added the variable `x` as a design variable:"
   ]
  },
  {
   "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_feature import SellarMDA\n",
    "\n",
    "\n",
    "model = SellarMDA()\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 = om.Problem(model)\n",
    "\n",
    "prob.setup()\n",
    "prob.final_setup();"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `is_indep_var` argument provides inputs that the user can ultimately change, though some of them maybe be overridden by the Driver as design variables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "indeps = model.list_inputs(is_indep_var=True, prom_name=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also get a list of design variables using the `is_design_var` argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "desvars = model.list_inputs(is_design_var=True, prom_name=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Combining these two arguments will show those variables that should be set by the user and whose values will not be overridden by the Driver:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "nonDV_indeps = model.list_inputs(is_indep_var=True, is_design_var=False, prom_name=True)"
   ]
  },
  {
   "attachments": {},
   "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 *basic* 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",
    "prob.model.add_objective('a_disk.Cp', scaler=-1)  # negated to maximize the objective\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);"
   ]
  },
  {
   "attachments": {},
   "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` variables using code like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "comp = om.IndepVarComp('indep_var', tags='tag1')"
   ]
  },
  {
   "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'})"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note}\n",
    "Note that outputs of `IndepVarComp` are always tagged with `openmdao:indep_var`.\n",
    "```"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *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 `list_outputs` method provides the 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)"
   ]
  },
  {
   "attachments": {},
   "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)"
   ]
  },
  {
   "attachments": {},
   "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)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *List Additional Variable Metadata*\n",
    "\n",
    "The `list_inputs()` and `list_outputs()` methods have 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": [
    "from pprint import pprint\n",
    "pprint(sorted(outputs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "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);"
   ]
  },
  {
   "attachments": {},
   "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\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": [
    "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);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can control the format of the array values via `numpy.set_printoptions`. OpenMDAO provides the `printoptions` context manager to assist with this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.utils.general_utils import printoptions\n",
    "\n",
    "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)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Print Minimum, Maximum or Mean 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.\n",
    "Additionally, seeing the mean value of the array can be useful.\n",
    "To provide a quick visual reference, the `list_inputs()` and `list_outputs()` methods have `print_min`, `print_max`, `print_mean` 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,\n",
    "                       print_mean=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,\n",
    "                        print_mean=True);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that 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. \n",
    "See the documentation for the [configure](../core_features/working_with_groups/configure_method.ipynb) method for one such use case.\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."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note}\n",
    "This feature requires MPI, and may not be able to be run on Colab or Binder.\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)"
   ]
  },
  {
   "attachments": {},
   "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)"
   ]
  },
  {
   "attachments": {},
   "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",
    "from openmdao.utils.array_utils import get_evenly_distributed_size\n",
    "\n",
    "size = 15\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "indep = model.add_subsystem(\"indep\", om.IndepVarComp())\n",
    "indep.add_output('x', np.ones(get_evenly_distributed_size(prob.comm, 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.setup()\n",
    "prob.final_setup()\n",
    "\n",
    "model.C2.list_inputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True);\n",
    "model.C2.list_outputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the shape of the `invec` and `outvec` variables for the distributed C2 component can be different on each processor. Use the `all_procs` argument to display on all processors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "model.C2.list_outputs(hierarchical=False, shape=True, global_shape=True, print_arrays=True, all_procs=True);"
   ]
  },
  {
   "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.)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Listing Problem Variables*\n",
    "\n",
    "The `Problem` class has a method `list_driver_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_driver_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_feature.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "attachments": {},
   "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_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "model = prob.model\n",
    "model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "model.linear_solver = om.ScipyKrylov()\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_driver_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 (ipykernel)",
   "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.12.3"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
