{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "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",
   "metadata": {},
   "source": [
    "# Using CSDL Components\n",
    "\n",
    "## CSDL Background\n",
    "\n",
    "CSDL is domain-specific language embedded in Python designed for solving Multidisciplinary Design Analysis and Optimization (MDAO) problems. CSDL enables complete separation between specifying a mathematical model and implementing a numerical simulation of a physical system. Separation between specification and implementation enables engineers to operate at a high level of abstraction, without the need to implement low level algorithms, including derivative computation. It is an open source project created by the [Large-Scale Design Optimization (LSDO) Lab](https://lsdo.eng.ucsd.edu/) in the Department of Mechanical and Aerospace Engineering at the University of California San Diego. To learn more, visit the [CSDL Website](https://csdl-alpha.readthedocs.io/en/latest/).\n",
    "\n",
    "## Using CSDL in OpenMDAO\n",
    "\n",
    "CSDL models can be used directly in OpenMDAO models using the [csdl_om_connect](https://github.com/lsdolab/csdl_om_connect) package, also created by the LSDO Lab. This document explains how to do that by way of an example. In this example, we optimize the thickness (height) distribution of a cantilever beam.\n",
    "\n",
    "The example is based on two existing scripts. The first is a [pure OpenMDAO implementation](https://openmdao.org/newdocs/versions/latest/examples/beam_optimization_example.html) of the beam optimization problem. The second is the same problem solved using [only CSDL](https://modopt.readthedocs.io/en/latest/src/_temp/examples/ex_15_3cantilever_beam_csdl.html).\n",
    "\n",
    "The example below is a hybrid where CSDL is used to define the component, but OpenMDAO defines the problem, the driver, and does the optimization.\n",
    "\n",
    "The first step is to install the `csdl_om_connect` package, which will also install the `CSDL` package. The use of CSDL and csdl_om_connect is optional for OpenMDAO so if not already installed, the user needs to install it by \n",
    "issuing the following command at your operating system command prompt:\n",
    "```\n",
    "pip install git+https://github.com/lsdolab/csdl_om_connect.git@main\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output",
     "active-ipynb"
    ]
   },
   "outputs": [],
   "source": [
    "!pip install git+https://github.com/lsdolab/csdl_om_connect.git@main"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using an CSDL Component in OpenMDAO is very simple. You only need to instantiate a `CSDLExplicitComponent` class passing it a CSDL Jax Simulator object and also lists of the input and output variables.\n",
    "\n",
    "The key to using a CSDL Component in OpenMDAO is to make sure the input and output variable names match both when creating the `CSDLExplicitComponent` object and also when setting the design variables, constraints, and objectives on the OpenMDAO driver. \n",
    "\n",
    "Here is the full script of the hybrid example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "import csdl_alpha as csdl\n",
    "import numpy as np\n",
    "from csdl_om_connect import CSDLExplicitComponent\n",
    "\n",
    "############\n",
    "# CSDL Model\n",
    "############\n",
    "E0, L0, b0, vol0, F0 = 1.0, 1.0, 0.1, 0.01, -1.0\n",
    "n_el = 50\n",
    "rec = csdl.Recorder()\n",
    "rec.start()\n",
    "\n",
    "### add design variables ###\n",
    "x = csdl.Variable(name=\"x\", shape=(n_el,), value=1.0)\n",
    "\n",
    "### add objective ###\n",
    "E, L, b, vol = E0, L0, b0, vol0\n",
    "L_el = L / n_el\n",
    "n_nodes = n_el + 1\n",
    "\n",
    "# Moment of inertia\n",
    "I = b * x**3 / 12\n",
    "\n",
    "# Force vector\n",
    "F = np.zeros((n_nodes * 2,))\n",
    "F[-2] = F0\n",
    "\n",
    "# Stiffness matrix\n",
    "c_el = (\n",
    "    E\n",
    "    / L_el**3\n",
    "    * np.array(\n",
    "        [\n",
    "            [12, 6 * L_el, -12, 6 * L_el],\n",
    "            [6 * L_el, 4 * L_el**2, -6 * L_el, 2 * L_el**2],\n",
    "            [-12, -6 * L_el, 12, -6 * L_el],\n",
    "            [6 * L_el, 2 * L_el**2, -6 * L_el, 4 * L_el**2],\n",
    "        ]\n",
    "    )\n",
    ")\n",
    "\n",
    "K = csdl.Variable(name=\"K\", value=np.zeros((n_nodes * 2, n_nodes * 2)))\n",
    "for i in range(n_el):\n",
    "    K = K.set(\n",
    "        csdl.slice[2 * i : 2 * i + 4, 2 * i : 2 * i + 4],\n",
    "        K[2 * i : 2 * i + 4, 2 * i : 2 * i + 4] + c_el * I[i],\n",
    "    )\n",
    "u = csdl.solve_linear(K[2:, 2:], F[2:])\n",
    "c = csdl.vdot(F[2:], u)\n",
    "c.add_name(\"compliance\")\n",
    "\n",
    "### add constraints ###\n",
    "v = L_el * b * csdl.sum(x)\n",
    "v.add_name(\"volume\")\n",
    "\n",
    "rec.stop()\n",
    "\n",
    "cantilever_beam_sim = csdl.experimental.JaxSimulator(\n",
    "    recorder=rec,\n",
    "    additional_inputs=[x,],\n",
    "    additional_outputs=[c, v],\n",
    ")\n",
    "\n",
    "################\n",
    "# OpenMDAO Model\n",
    "################\n",
    "\n",
    "# Generate a CSDLExplicitComponent object from a CSDL Jax Simulator\n",
    "# Define input and output names\n",
    "# Both are defined as a list of strings of CSDL variable names.\n",
    "# The names must be valid names of the CSDL variables\n",
    "# exposed as `additional_inputs` and `additional_outputs` in the JaxSimulator.\n",
    "in_names = [\"x\",]\n",
    "out_names = [\"compliance\", \"volume\"]\n",
    "cantilever_beam_comp = CSDLExplicitComponent(\n",
    "    cantilever_beam_sim, in_names=in_names, out_names=out_names\n",
    ")\n",
    "\n",
    "# Define OpenMDAO Problem\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem(\"cantilever_beam\", cantilever_beam_comp, promotes=[\"*\"])\n",
    "\n",
    "# Set the design variables, objective, and constraints\n",
    "# Since all the inputs and outputs were promoted, use promoted names\n",
    "prob.model.add_design_var(\"x\", lower=1e-2)\n",
    "prob.model.add_objective(\"compliance\")\n",
    "prob.model.add_constraint(\"volume\", equals=(vol0,))\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# setup and run the optimization\n",
    "prob.driver = om.ScipyOptimizeDriver(optimizer=\"SLSQP\", tol=1e-9, maxiter=200)\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "# Validate results\n",
    "opt_x = np.array([\n",
    "    0.14915759, 0.14764312, 0.14611338, 0.14456706, 0.14300427,\n",
    "    0.14142408, 0.13982619, 0.13820971, 0.13657403, 0.13491878,\n",
    "    0.13324254, 0.13154527, 0.12982591, 0.12808316, 0.12631659,\n",
    "    0.12452485, 0.122707, 0.12086176, 0.118988, 0.1170842,\n",
    "    0.11514902, 0.11318073, 0.11117753, 0.10913763, 0.10705892,\n",
    "    0.10493904, 0.10277539, 0.10056523, 0.09830546, 0.09599243,\n",
    "    0.09362241, 0.09119077, 0.08869255, 0.08612204, 0.08347229,\n",
    "    0.0807358, 0.07790325, 0.07496383, 0.07190449, 0.0687093,\n",
    "    0.06535832, 0.06182634, 0.05808047, 0.05407656, 0.04975294,\n",
    "    0.04501853, 0.03972914, 0.03363155, 0.02620191, 0.01610863,\n",
    "])\n",
    "opt_compliance = np.array([23762.15367702])\n",
    "assert np.allclose(prob.get_val(\"x\"), opt_x, atol=1e-6)\n",
    "assert np.allclose(prob.get_val(\"compliance\"), opt_compliance, atol=1e-6)\n"
   ]
  }
 ],
 "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.13.11"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
