{
 "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  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Reloading Case Data into a Model\n",
    "\n",
    "The OpenMDAO API provides a way to load into a model a case from a case recorder file. The method to do this is `Problem.load_case`. Its only argument is a case object from a case recorder file.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.load_case\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "The method pulls all input and output variables from a case into the model.\n",
    "\n",
    "Using case recorders, you can record many sources (driver, system, problem, solver). Therefore, cases can have many different types of values. `load_case` works with all of these recordings. \n",
    "\n",
    "It is important to keep in mind that just the variables in the case are updated in the model. The rest of the model variables are left unchanged."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why would you want to load a case?\n",
    "\n",
    "There are several use cases for using `load_case`.\n",
    "\n",
    "- Save points from a Design of Experiments (DOE) run and then go back and run further analysis on them\n",
    "\n",
    "- Give an optimization run an initial guess with values from a previous optimization run. For example, a user would use one optimization method to quickly get a solution that is close, save the case, then using a more accurate, but slower method to find the solution after loading in that case from the first run\n",
    "\n",
    "- Run a sweep of constraint values. This would allow a user to save time by doing runs using initial values from previous runs instead of starting from scratch each time"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simple load case example\n",
    "\n",
    "Here is a simple example of how to call the `load_case` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarProblem\n",
    "\n",
    "prob = SellarProblem()\n",
    "\n",
    "model = prob.model\n",
    "model.recording_options['record_inputs'] = True\n",
    "model.recording_options['record_outputs'] = True\n",
    "model.recording_options['record_residuals'] = True\n",
    "case_recorder_filename = \"cases.sql\"\n",
    "model.add_recorder(om.SqliteRecorder(case_recorder_filename))\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_recorder_filename)\n",
    "\n",
    "system_cases = cr.list_cases('root', out_stream=None)\n",
    "case = cr.get_case(system_cases[0])\n",
    "\n",
    "# Now load in the case we recorded\n",
    "prob.load_case(case)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "inputs = case.list_inputs(val=True,out_stream=None)\n",
    "for name, val in inputs:\n",
    "    assert_near_equal(val['val'], model._inputs[name])\n",
    "\n",
    "outputs = case.list_outputs(val=True, out_stream=None)\n",
    "for name, val in outputs:\n",
    "    assert_near_equal(val['val'], model._outputs[name])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As was mentioned before, just the variables values in the case get updated in the model. In this example, the case  contains values for the model variables `x`,`z`,`y1`,`y2`,`con1`,`con2`, and `obj`. \n",
    "\n",
    "But if the recorder had only been attached to a subsystem, like this\n",
    "\n",
    "    model.d2.add_recorder(om.SqliteRecorder(case_recorder_filename))\n",
    "\n",
    "only the variables `z`,`y1`, and `y2` would have been recorded and when `load_case` was called with a case from that recording, only those variables in the model would be updated."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a somewhat more realistic use case for `load_case`. In this example, an optimization is run and then the third case is used to load the model and start the optimization again. Notice how in the second optimization run, the optimization requires fewer iterations since it was given an initial guess."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarMDA\n",
    "\n",
    "prob = SellarProblem(SellarMDA)\n",
    "prob.setup()\n",
    "\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "prob.model.linear_solver = om.ScipyKrylov()\n",
    "prob.model.cycle.nonlinear_solver = om.NonlinearBlockGS()\n",
    "prob.model.cycle.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "case_recorder_filename = \"cases.sql\"\n",
    "prob.model.add_recorder(om.SqliteRecorder(case_recorder_filename))\n",
    "\n",
    "driver = prob.driver = om.ScipyOptimizeDriver()\n",
    "driver.options['optimizer'] = 'SLSQP'\n",
    "driver.options['tol'] = 1e-9\n",
    "driver.options['disp'] = False\n",
    "driver.recording_options['record_desvars'] = True\n",
    "driver.recording_options['record_objectives'] = True\n",
    "driver.recording_options['record_constraints'] = True\n",
    "\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "inputs_before = prob.model.list_inputs(val=True, units=True, out_stream=None)\n",
    "outputs_before = prob.model.list_outputs(val=True, units=True, out_stream=None)\n",
    "\n",
    "cr = om.CaseReader(prob.get_outputs_dir() / case_recorder_filename)\n",
    "\n",
    "# get third case\n",
    "system_cases = cr.list_cases('root')  #, out_stream=None)\n",
    "third_case = cr.get_case(system_cases[4])\n",
    "\n",
    "iter_count_before = driver.iter_count\n",
    "\n",
    "# run the model again with a fresh model\n",
    "prob = SellarProblem(SellarMDA)\n",
    "prob.setup()\n",
    "\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "prob.model.linear_solver = om.ScipyKrylov()\n",
    "prob.model.cycle.nonlinear_solver = om.NonlinearBlockGS()\n",
    "prob.model.cycle.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "driver = prob.driver = om.ScipyOptimizeDriver()\n",
    "driver.options['optimizer'] = 'SLSQP'\n",
    "driver.options['tol'] = 1e-9\n",
    "driver.options['disp'] = False\n",
    "\n",
    "prob.load_case(third_case)\n",
    "prob.run_driver()\n",
    "prob.cleanup()\n",
    "\n",
    "inputs_after = prob.model.list_inputs(val=True, units=True, out_stream=None)\n",
    "outputs_after = prob.model.list_outputs(val=True, units=True, out_stream=None)\n",
    "\n",
    "iter_count_after = driver.iter_count\n",
    "print(f\"iterations before: {iter_count_before} iterations after: {iter_count_after}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "import numpy as np\n",
    "\n",
    "for before, after in zip(inputs_before, inputs_after):\n",
    "    np.testing.assert_almost_equal(after[1]['val'], before[1]['val'])\n",
    "\n",
    "for before, after in zip(outputs_before, outputs_after):\n",
    "    np.testing.assert_almost_equal(after[1]['val'], before[1]['val'])\n",
    "\n",
    "# Should take one less iteration since we gave it an initial guess in the second run\n",
    "assert_near_equal(iter_count_before, iter_count_after + 3)"
   ]
  }
 ],
 "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.11.4"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
