{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "20b922d4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:04.621345Z",
     "iopub.status.busy": "2026-10-02T14:41:04.621173Z",
     "iopub.status.idle": "2026-10-02T14:41:04.625982Z",
     "shell.execute_reply": "2026-10-02T14:41:04.624790Z"
    },
    "hide_input": true,
    "papermill": {
     "duration": 0.00708,
     "end_time": "2026-10-02T14:41:04.626544+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:04.619464+00:00",
     "status": "completed"
    },
    "tags": [
     "active-ipynb",
     "remove-input",
     "remove-output"
    ]
   },
   "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",
   "id": "d6bfc619",
   "metadata": {
    "papermill": {
     "duration": 0.000635,
     "end_time": "2026-10-02T14:41:04.628321+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:04.627686+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# Simple Optimization using Simultaneous Derivatives\n",
    "\n",
    "Consider a set of points in 2-d space that are to be arranged along a circle such that the radius\n",
    "of the circle is maximized, subject to constraints.  We start out with the points randomly\n",
    "distributed within a unit circle centered about the origin.  The locations of our points are\n",
    "determined by the values of the *x* and *y* arrays defined in our problem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "eb766d71",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:04.630913Z",
     "iopub.status.busy": "2026-10-02T14:41:04.630784Z",
     "iopub.status.idle": "2026-10-02T14:41:07.146589Z",
     "shell.execute_reply": "2026-10-02T14:41:07.145890Z"
    },
    "papermill": {
     "duration": 2.517872,
     "end_time": "2026-10-02T14:41:07.147273+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:04.629401+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952067.022950] [runnervm8df0l:6270 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x561ba3bdf100 failed to create UD QP TX wr:256 sge:6 inl:64 resp:0 RX wr:4096 sge:1 resp:0 failed: Operation not supported\n",
      "[1790952067.023202] [runnervm8df0l:6270 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Jacobian shape: (22, 21)  (13.42% nonzero)\n",
      "FWD solves: 5   REV solves: 0\n",
      "Total colors vs. total size: 5 vs 21  (76.19% improvement)\n",
      "\n",
      "Sparsity computed using tolerance: 1e-25.\n",
      "Dense total jacobian for Problem 'problem' was computed 3 times.\n",
      "Time to compute sparsity:   0.0085 sec\n",
      "Time to compute coloring:   0.0617 sec\n",
      "Memory to compute coloring:   0.6641 MB\n",
      "Coloring created on: 2026-10-02 14:41:07\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3.14159265]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:06270] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "SIZE = 10\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('arctan_yox', om.ExecComp('g=arctan(y/x)', has_diag_partials=True,\n",
    "                                                g=np.ones(SIZE), x=np.ones(SIZE), y=np.ones(SIZE)),\n",
    "                      promotes_inputs=['x', 'y'])\n",
    "\n",
    "p.model.add_subsystem('circle', om.ExecComp('area=pi*r**2'), promotes_inputs=['r'])\n",
    "\n",
    "p.model.add_subsystem('r_con', om.ExecComp('g=x**2 + y**2 - r', has_diag_partials=True,\n",
    "                                           g=np.ones(SIZE), x=np.ones(SIZE), y=np.ones(SIZE)),\n",
    "                      promotes_inputs=['r', 'x', 'y'])\n",
    "\n",
    "thetas = np.linspace(0, np.pi/4, SIZE)\n",
    "p.model.add_subsystem('theta_con', om.ExecComp('g = x - theta', has_diag_partials=True,\n",
    "                                               g=np.ones(SIZE), x=np.ones(SIZE),\n",
    "                                               theta=thetas))\n",
    "p.model.add_subsystem('delta_theta_con', om.ExecComp('g = even - odd', has_diag_partials=True,\n",
    "                                                     g=np.ones(SIZE//2), even=np.ones(SIZE//2),\n",
    "                                                     odd=np.ones(SIZE//2)))\n",
    "\n",
    "p.model.add_subsystem('l_conx', om.ExecComp('g=x-1', has_diag_partials=True, g=np.ones(SIZE), x=np.ones(SIZE)),\n",
    "                      promotes_inputs=['x'])\n",
    "\n",
    "IND = np.arange(SIZE, dtype=int)\n",
    "ODD_IND = IND[1::2]  # all odd indices\n",
    "EVEN_IND = IND[0::2]  # all even indices\n",
    "\n",
    "p.model.connect('arctan_yox.g', 'theta_con.x')\n",
    "p.model.connect('arctan_yox.g', 'delta_theta_con.even', src_indices=EVEN_IND)\n",
    "p.model.connect('arctan_yox.g', 'delta_theta_con.odd', src_indices=ODD_IND)\n",
    "\n",
    "p.driver = om.ScipyOptimizeDriver()\n",
    "p.driver.options['optimizer'] = 'SLSQP'\n",
    "p.driver.options['disp'] = False\n",
    "\n",
    "# set up dynamic total coloring here\n",
    "p.driver.declare_coloring()\n",
    "\n",
    "p.model.add_design_var('x')\n",
    "p.model.add_design_var('y')\n",
    "p.model.add_design_var('r', lower=.5, upper=10)\n",
    "\n",
    "# nonlinear constraints\n",
    "p.model.add_constraint('r_con.g', equals=0)\n",
    "\n",
    "p.model.add_constraint('theta_con.g', lower=-1e-5, upper=1e-5, indices=EVEN_IND)\n",
    "p.model.add_constraint('delta_theta_con.g', lower=-1e-5, upper=1e-5)\n",
    "\n",
    "# this constrains x[0] to be 1 (see definition of l_conx)\n",
    "p.model.add_constraint('l_conx.g', equals=0, linear=False, indices=[0,])\n",
    "\n",
    "# linear constraint\n",
    "p.model.add_constraint('y', equals=0, indices=[0,], linear=True)\n",
    "\n",
    "p.model.add_objective('circle.area', ref=-1)\n",
    "\n",
    "p.setup(mode='fwd')\n",
    "\n",
    "# the following were randomly generated using np.random.random(10)*2-1 to randomly\n",
    "# disperse them within a unit circle centered at the origin.\n",
    "p.set_val('x', np.array([ 0.55994437, -0.95923447,  0.21798656, -0.02158783,  0.62183717,\n",
    "                          0.04007379,  0.46044942, -0.10129622,  0.27720413, -0.37107886]))\n",
    "p.set_val('y', np.array([ 0.52577864,  0.30894559,  0.8420792 ,  0.35039912, -0.67290778,\n",
    "                         -0.86236787, -0.97500023,  0.47739414,  0.51174103,  0.10052582]))\n",
    "p.set_val('r', .7)\n",
    "\n",
    "p.run_driver()\n",
    "\n",
    "print(p['circle.area'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "fda3209b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:07.150308Z",
     "iopub.status.busy": "2026-10-02T14:41:07.150059Z",
     "iopub.status.idle": "2026-10-02T14:41:07.152564Z",
     "shell.execute_reply": "2026-10-02T14:41:07.151985Z"
    },
    "papermill": {
     "duration": 0.004667,
     "end_time": "2026-10-02T14:41:07.153054+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:07.148387+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from numpy.testing import assert_almost_equal\n",
    "assert_almost_equal(p['circle.area'], np.pi, decimal=7)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59fd5ed1",
   "metadata": {
    "papermill": {
     "duration": 0.000871,
     "end_time": "2026-10-02T14:41:07.154851+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:07.153980+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Total derivatives with respect to *x* and *y* will be solved for simultaneously based on the\n",
    "color of the points shown below.  Note that we have two colors and our *x* and *y* design\n",
    "variables are of size 10. We have a third design variable, *r*, that is size 1.\n",
    "This means that if we don't solve for derivatives simultaneously, we must\n",
    "perform 21 linear solves (10 + 10 + 1) to solve for total derivatives with respect to\n",
    "all of our design variables.  But, since all of our design variables have only\n",
    "5 colors, we can solve for all of our total derivatives using only 5 linear solves.\n",
    "This means that using simultaneous derivatives saves us 16 linear solves each time we compute our\n",
    "total derivatives.\n",
    "\n",
    "Here's our problem at the start of the optimization:\n",
    "\n",
    "![pre optimization](images/circle_example1.png)\n",
    "\n",
    "After the optimization, all of our points lie along the unit circle.  The final radius is 1.0\n",
    "(which corresponds to an area of PI) because we constrained our *x[0]* value to be 1.0.\n",
    "\n",
    "![pre optimization](images/circle_example2.png)"
   ]
  }
 ],
 "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.13.14"
  },
  "papermill": {
   "default_parameters": {},
   "duration": 3.708871,
   "end_time": "2026-10-02T14:41:07.670993+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/examples/simul_deriv_example.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/examples/simul_deriv_example.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:41:03.962122+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}