{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output",
     "active-ipynb"
    ]
   },
   "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": [
    "# Warning Control\n",
    "\n",
    "OpenMDAO has several classes of warnings that may be raised during operation.\n",
    "In general, these warnings are useful and the user should pay attention to them.\n",
    "Sometimes these warnings can be unnecessarily noisy.\n",
    "Filtering out noisy \"low-priority\" warnings can make other more important ones more obvious.\n",
    "\n",
    "(om_specific_warning_categories)=\n",
    "## OpenMDAO-Specific Warning Categories\n",
    "\n",
    "Class **OpenMDAOWarning** serves as the base-class for all OpenMDAO-specific warnings.\n",
    "All OpenMDAO-specific warnings default to a filter of 'always'. The following table shows all OpenMDAOWarning-derived classes.\n",
    "\n",
    "| Warning Class         | Description                                                                         |\n",
    "|-----------------------|-------------------------------------------------------------------------------------|\n",
    "| CacheWarning          | Issued when cache is invalid and must be discarded.                                 |\n",
    "| CaseRecorderWarning   | Issued when a problem is encountered by a case recorder or case reader.             |\n",
    "| DerivativesWarning    | Issued when the approximated partials or coloring cannot be evaluated as expected.  |\n",
    "| DriverWarning         | Issued when a problem is encountered during driver execution.                       |\n",
    "| OMDeprecationWarning  | Issued when a deprecated OpenMDAO feature is used.                                  |\n",
    "| SetupWarning          | Issued when a problem is encountered during setup.                                  |\n",
    "| SolverWarning         | Issued when a problem is encountered during solver execution.                       |\n",
    "| UnusedOptionWarning   | Issued when a given option or argument has no effect.                               |\n",
    "\n",
    "Note that the OpenMDAO-Specific **OMDeprecationWarning** behaves a bit differently than the default Python DeprecationWarning. **OMDeprecationWarning** is is always displayed by default, but can be silenced by the user.\n",
    "\n",
    "For finer control over which warnings are displayed during setup, the following warning classes derive from **SetupWarning**. Using a filter to silence SetupWarning will silence **all** of the following.\n",
    "\n",
    "| Warning Class               | Description                                                          |\n",
    "|-----------------------------|----------------------------------------------------------------------|\n",
    "| DistributedComponentWarning | Issued when problems arise with a distributed component.             |\n",
    "| MPIWarning                  | Issued when MPI is not available or cannot be used.                  |\n",
    "| PromotionWarning            | Issued when there is ambiguity due to variable promotion.            |\n",
    "| UnitsWarning                | Issued when unitless variable is connected to a variable with units. |\n",
    "\n",
    "(filtering_warnings)=\n",
    "## Filtering Warnings\n",
    "\n",
    "Python's built-in warning filtering system can be used to control which warnings are displayed when using OpenMDAO.\n",
    "The following script generates an OpenMDAO model which will generate UnitsWarning due to connecting unitless outputs to inputs with units.\n",
    "\n",
    "In the following code, the UnitsWarning will be displayed as expected:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "allow-assert"
    ]
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "Test nominal UnitsWarning.\n",
    "\"\"\"\n",
    "import warnings\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class AComp(om.ExplicitComponent):\n",
    "\n",
    "    def initialize(self):\n",
    "        pass\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', shape=(10,), units='m')\n",
    "        self.add_input('x', shape=(10,), units='1/s')\n",
    "        self.add_input('b', shape=(10,), units='m/s')\n",
    "\n",
    "        self.add_output('y', shape=(10,), units='m')\n",
    "        self.add_output('z', shape=(10,), units='m/s')\n",
    "\n",
    "        self.declare_coloring(wrt='*', form='cs')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = inputs['a'] * inputs['x'] + inputs['b']\n",
    "        outputs['z'] = inputs['b'] * inputs['x']\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('a_comp', AComp())\n",
    "p.model.add_subsystem('exec_comp',\n",
    "                      om.ExecComp('foo = y + z',\n",
    "                                  y={'shape': (10,)},\n",
    "                                  z={'shape': (10,)},\n",
    "                                  foo={'shape': (10,)}))\n",
    "\n",
    "p.model.connect('a_comp.y', 'exec_comp.y')\n",
    "p.model.connect('a_comp.z', 'exec_comp.z')\n",
    "p.driver.declare_coloring()\n",
    "\n",
    "p.setup()\n",
    "\n",
    "with warnings.catch_warnings(record=True) as w:\n",
    "    p.setup()\n",
    "    unit_warnings = [wm for wm in w if wm.category is om.UnitsWarning]\n",
    "    assert(len(unit_warnings) == 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The warnings can be completely turned off by filtering them using Python’s filterwarnings function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "allow-assert"
    ]
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "Test the ability to ignore UnitsWarning\n",
    "\"\"\"\n",
    "import warnings\n",
    "import openmdao.api as om\n",
    "\n",
    "class AComp(om.ExplicitComponent):\n",
    "\n",
    "    def initialize(self):\n",
    "        pass\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', shape=(10,), units='m')\n",
    "        self.add_input('x', shape=(10,), units='1/s')\n",
    "        self.add_input('b', shape=(10,), units='m/s')\n",
    "\n",
    "        self.add_output('y', shape=(10,), units='m')\n",
    "        self.add_output('z', shape=(10,), units='m/s')\n",
    "\n",
    "        self.declare_coloring(wrt='*', form='cs')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = inputs['a'] * inputs['x'] + inputs['b']\n",
    "        outputs['z'] = inputs['b'] * inputs['x']\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('a_comp', AComp())\n",
    "p.model.add_subsystem('exec_comp',\n",
    "                      om.ExecComp('foo = y + z',\n",
    "                                  y={'shape': (10,)},\n",
    "                                  z={'shape': (10,)},\n",
    "                                  foo={'shape': (10,)}))\n",
    "\n",
    "p.model.connect('a_comp.y', 'exec_comp.y')\n",
    "p.model.connect('a_comp.z', 'exec_comp.z')\n",
    "p.driver.declare_coloring()\n",
    "\n",
    "warnings.filterwarnings('ignore', category=om.UnitsWarning)\n",
    "\n",
    "with warnings.catch_warnings(record=True) as w:\n",
    "    p.setup()\n",
    "    unit_warnings = [wm for wm in w if wm.category is om.UnitsWarning]\n",
    "    assert (len(unit_warnings) == 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to clean your code and remove warnings, it can be useful to promote them to errors so that they cannot be ignored. The following code filters **all** OpenMDAO associated warnings to Errors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "Test the ability to raise a UnitWarning to an error.\n",
    "\"\"\"\n",
    "import warnings\n",
    "import openmdao.api as om\n",
    "\n",
    "class AComp(om.ExplicitComponent):\n",
    "\n",
    "    def initialize(self):\n",
    "        pass\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', shape=(10,), units='m')\n",
    "        self.add_input('x', shape=(10,), units='1/s')\n",
    "        self.add_input('b', shape=(10,), units='m/s')\n",
    "\n",
    "        self.add_output('y', shape=(10,), units='m')\n",
    "        self.add_output('z', shape=(10,), units='m/s')\n",
    "\n",
    "        self.declare_coloring(wrt='*', form='cs')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = inputs['a'] * inputs['x'] + inputs['b']\n",
    "        outputs['z'] = inputs['b'] * inputs['x']\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('a_comp', AComp())\n",
    "p.model.add_subsystem('exec_comp',\n",
    "                      om.ExecComp('foo = y + z',\n",
    "                                  y={'shape': (10,)},\n",
    "                                  z={'shape': (10,)},\n",
    "                                  foo={'shape': (10,)}))\n",
    "\n",
    "p.model.connect('a_comp.y', 'exec_comp.y')\n",
    "p.model.connect('a_comp.z', 'exec_comp.z')\n",
    "p.driver.declare_coloring()\n",
    "\n",
    "warnings.filterwarnings('error', category=om.OpenMDAOWarning)\n",
    "\n",
    "expected = \"<model> <class Group>: Output 'a_comp.y' with units of 'm' is connected to \" \\\n",
    "           \"input 'exec_comp.y' which has no units.\"\n",
    "\n",
    "try:\n",
    "    p.setup()\n",
    "except om.UnitsWarning as e:\n",
    "    if str(e) != expected:\n",
    "        raise RuntimeError(f\"{str(e.exception)} != {expected}\")\n",
    "    else:\n",
    "        print(str(e))\n",
    "else:\n",
    "    raise RuntimeError(f\"Exception '{expected}' not raised\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(notes_for_developers)=\n",
    "## Notes for Developers\n",
    "\n",
    "Python's treatment of warnings inside UnitTest tests can be somewhat confusing.\n",
    "If you wish to test that certain warnings are filtered during testing, we recommend using the `om.reset_warnings()` method in the `setUp` method that is run before each test in a `TestCase`.\n",
    "\n",
    "```python\n",
    "    import unittest\n",
    "    import openmdao.api as om\n",
    "\n",
    "    class MyTestCase(unittest.TestCase):\n",
    "\n",
    "        def setUp(self):\n",
    "            \"\"\"\n",
    "            Ensure that OpenMDAO warnings are using their default filter action.\n",
    "            \"\"\"\n",
    "            om.reset_warnings()\n",
    "\n",
    "        def test_a(self):\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.1"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
