{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f8d8af9",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output",
     "active-ipynb"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7bc3f2f0",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.api import clean_outputs\n",
    "\n",
    "clean_outputs(prompt=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "118db89f",
   "metadata": {},
   "source": [
    "# Case Recording"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "155a6661",
   "metadata": {},
   "source": [
    "## Case Recording Options\n",
    "\n",
    "The available recording options for different OpenMDAO classes are summarized in this table:\n",
    "\n",
    "|Recording Options        |Driver|System|Solver|Problem|\n",
    "|:------------------------|:----:|:----:|:----:|:-----:|\n",
    "|record_constraints       |  X   |      |      |  X    |\n",
    "|record_desvars           |  X   |      |      |  X    |\n",
    "|record_objectives        |  X   |      |      |  X    |\n",
    "|record_derivatives       |  X   |      |      |  X    |\n",
    "|record_responses         |  X   |      |      |  X    |\n",
    "|record_inputs            |  X   |  X   |  X   |  X    |\n",
    "|record_outputs           |  X   |  X   |  X   |  X    |\n",
    "|record_residuals         |  X   |  X   |      |  X    |\n",
    "|record_abs_error         |      |      |  X   |       |\n",
    "|record_rel_error         |      |      |  X   |       |\n",
    "|record_solver_residuals  |      |      |  X   |       |\n",
    "|includes                 |  X   |  X   |  X   |  X    |\n",
    "|excludes                 |  X   |  X   |  X   |  X    |\n",
    "|options_excludes         |      |  X   |      |       |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "665ae681",
   "metadata": {},
   "source": [
    "## Case Recorder Files\n",
    "\n",
    "When you instantiate a recorder, you must specify the filename to which the SQLite database of data is stored.\n",
    "By default, this file will be stored under the output directory of the problem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd6f486e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import openmdao.api as om\n",
    "\n",
    "p = om.Problem(reports=None)\n",
    "p.model.add_subsystem('exec', om.ExecComp('z = x + y'),\n",
    "                       promotes_inputs=['x', 'y'], promotes_outputs=['z'])\n",
    "\n",
    "p.add_recorder(om.SqliteRecorder('prob_recorder.sql'))\n",
    "\n",
    "p.setup()\n",
    "p.set_val('x', 3)\n",
    "p.set_val('y', 5)\n",
    "p.run_model()\n",
    "p.record('first_case')\n",
    "\n",
    "p.set_val('x', 4)\n",
    "p.set_val('y', 5)\n",
    "p.run_model()\n",
    "p.record('second_case')\n",
    "\n",
    "p.cleanup()\n",
    "\n",
    "# Now prob_recorder.sql should exist in the problem's outputs directory.\n",
    "os.listdir(p.get_outputs_dir())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70d27e86",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert((p.get_outputs_dir() / 'prob_recorder.sql').exists())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "489c88c2",
   "metadata": {},
   "source": [
    "If the filename contains path separators (`/`), then OpenMDAO will assume that the recorder file path is not to be prepended with the problem's output directory.\n",
    "\n",
    "For instance, to place the file in the current working directory rather than the problem outputs directory, one could prepend it with `./`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "152c7302",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pathlib\n",
    "\n",
    "p = om.Problem(reports=None)\n",
    "p.model.add_subsystem('exec', om.ExecComp('z = x + y'),\n",
    "                       promotes_inputs=['x', 'y'], promotes_outputs=['z'])\n",
    "\n",
    "p.add_recorder(om.SqliteRecorder('./prob_recorder.sql'))\n",
    "\n",
    "p.setup()\n",
    "p.set_val('x', 3)\n",
    "p.set_val('y', 5)\n",
    "p.run_model()\n",
    "p.record('first_case')\n",
    "\n",
    "p.set_val('x', 4)\n",
    "p.set_val('y', 5)\n",
    "p.run_model()\n",
    "p.record('second_case')\n",
    "\n",
    "p.cleanup()\n",
    "\n",
    "# Now prob_recorder.sql should exist in the current working directory.\n",
    "print([file.parts[-1] for file in pathlib.Path.cwd().glob('*.sql')])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49fb03dd",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert((pathlib.Path(os.getcwd()) / 'prob_recorder.sql').exists())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "016e11d6",
   "metadata": {},
   "source": [
    "## Specifying a Case Prefix\n",
    "It is possible to record data from multiple executions by specifying a prefix that will be used to differentiate the cases. This prefix can be specified when calling run_model or run_driver and will be prepended to the case ID in the recorded case data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "979d8372",
   "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_src87\", get_code(\"openmdao.test_suite.components.sellar_feature.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3240f508",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src87`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b14a9419",
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "prob.setup()\n",
    "\n",
    "recorder = om.SqliteRecorder(\"cases.sql\", record_viewer_data=False)\n",
    "prob.model.add_recorder(recorder)\n",
    "prob.driver.add_recorder(recorder)\n",
    "\n",
    "prob.set_solver_print(0)\n",
    "\n",
    "prob.run_model(case_prefix='Model_Run1')\n",
    "prob.run_driver(case_prefix='Driver_Run1')\n",
    "\n",
    "prob.run_model('Model_Run2')\n",
    "prob.run_driver('Driver_Run2')\n",
    "\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / \"cases.sql\")\n",
    "\n",
    "# all cases recorded by the root system\n",
    "model_cases = cr.list_cases('root', recurse=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c5eaeda",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert '\\n'.join(model_cases) == '\\n'.join([\n",
    "    'Model_Run1_rank0:root._solve_nonlinear|0',\n",
    "    'Driver_Run1_rank0:Driver|0|root._solve_nonlinear|0',\n",
    "    'Model_Run2_rank0:root._solve_nonlinear|0',\n",
    "    'Driver_Run2_rank0:Driver|0|root._solve_nonlinear|0'\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d31369f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# all cases recorded by the driver\n",
    "driver_cases = cr.list_cases('driver', recurse=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "864ac779",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert '\\n'.join(driver_cases) == '\\n'.join([\n",
    "    'Driver_Run1_rank0:Driver|0',\n",
    "    'Driver_Run2_rank0:Driver|0'\n",
    "])\n",
    "\n",
    "clean_outputs(prompt=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b75dec6e",
   "metadata": {},
   "source": [
    "```{note}\n",
    "A recorder can be attached to more than one object. Also, more than one recorder can be attached to an object.\n",
    "```\n",
    "```{note}\n",
    "In this example, we have disabled the saving of data needed by the standalone N2 visualizer and debugging tool by setting `record_viewer_data` to `False`.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fddc228b",
   "metadata": {},
   "source": [
    "# Recording Options Include and Exclude Matching\n",
    "\n",
    "The `includes` and `excludes` recording options provide support for Unix shell-style wildcards, which are not \n",
    "the same as regular expressions. The documentation for the `fnmatchcase` function from the Python standard library\n",
    "documents the wildcards."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a50f0b8",
   "metadata": {},
   "source": [
    "# Recording Options Precedence\n",
    "\n",
    "The precedence of recording options that determines what gets recorded can sometime be a little confusing. \n",
    "Here is an example that might help. The code shows how the record_desvars and includes options interact."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "915fe237",
   "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_src88\", get_code(\"openmdao.test_suite.components.paraboloid.Paraboloid\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2358e829",
   "metadata": {},
   "source": [
    ":::{Admonition} `Paraboloid` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src88`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b98a4d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.driver = om.ScipyOptimizeDriver(optimizer='SLSQP', tol=1e-9, disp=False)\n",
    "\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', Paraboloid(), promotes=['*'])\n",
    "model.add_subsystem('con', om.ExecComp('c = x - y'), promotes=['*'])\n",
    "\n",
    "model.set_input_defaults('x', val=50.0)\n",
    "model.set_input_defaults('y', val=50.0)\n",
    "\n",
    "model.add_design_var('x', lower=-50.0, upper=50.0)\n",
    "model.add_design_var('y', lower=-50.0, upper=50.0)\n",
    "model.add_objective('f_xy')\n",
    "model.add_constraint('c', lower=15.0)\n",
    "\n",
    "case_file = 'cases.sql'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d771b84e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# First case with record_desvars = True and includes = []\n",
    "recorder = om.SqliteRecorder(case_file)\n",
    "prob.driver.add_recorder(recorder)\n",
    "prob.driver.recording_options['record_desvars'] = True\n",
    "prob.driver.recording_options['record_outputs'] = True\n",
    "prob.driver.recording_options['includes'] = []\n",
    "prob.driver.recording_options['excludes'] = []\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_file)\n",
    "case = cr.get_case(0)\n",
    "\n",
    "case.outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e282013",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert sorted(case.outputs.keys()) == ['c', 'f_xy', 'x', 'y']\n",
    "\n",
    "clean_outputs(prompt=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "feb60d54",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Second case with record_desvars = False and includes = []\n",
    "recorder = om.SqliteRecorder(case_file)\n",
    "prob.driver.add_recorder(recorder)\n",
    "prob.driver.recording_options['record_desvars'] = False\n",
    "prob.driver.recording_options['record_outputs'] = True\n",
    "prob.driver.recording_options['includes'] = []\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_file)\n",
    "case = cr.get_case(0)\n",
    "\n",
    "case.outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "607dbbf4",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert sorted(case.outputs.keys()) == ['c', 'f_xy']\n",
    "\n",
    "clean_outputs(prompt=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1641d99",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Third case with record_desvars = True and includes = ['*']\n",
    "recorder = om.SqliteRecorder(case_file)\n",
    "prob.driver.add_recorder(recorder)\n",
    "prob.driver.recording_options['record_desvars'] = True\n",
    "prob.driver.recording_options['record_outputs'] = True\n",
    "prob.driver.recording_options['includes'] = ['*']\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_file)\n",
    "case = cr.get_case(0)\n",
    "\n",
    "case.outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fbfc3805",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert sorted(case.outputs.keys()) == ['c', 'f_xy', 'x', 'y']\n",
    "\n",
    "clean_outputs(prompt=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39490cfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fourth case with record_desvars = False, record_outputs = True, and includes = ['*']\n",
    "recorder = om.SqliteRecorder(case_file)\n",
    "prob.driver.add_recorder(recorder)\n",
    "prob.driver.recording_options['record_desvars'] = False\n",
    "prob.driver.recording_options['record_outputs'] = True\n",
    "prob.driver.recording_options['includes'] = ['*']\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_file)\n",
    "case = cr.get_case(0)\n",
    "\n",
    "case.outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "242da023",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert sorted(case.outputs.keys()) == ['c', 'f_xy', 'x', 'y']"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "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.11.4"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
