{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6a108533",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:53.407059Z",
     "iopub.status.busy": "2026-10-02T14:40:53.406782Z",
     "iopub.status.idle": "2026-10-02T14:40:53.412732Z",
     "shell.execute_reply": "2026-10-02T14:40:53.411440Z"
    },
    "hide_input": true,
    "papermill": {
     "duration": 0.011256,
     "end_time": "2026-10-02T14:40:53.413197+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.401941+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": "b8ab2a45",
   "metadata": {
    "papermill": {
     "duration": 0.066091,
     "end_time": "2026-10-02T14:40:53.480706+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.414615+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# Wind Turbine Actuator Disc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0352aab1",
   "metadata": {
    "papermill": {
     "duration": 0.001004,
     "end_time": "2026-10-02T14:40:53.482887+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.481883+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Optimizing an Actuator Disc Model to Find Betz Limit for Wind Turbines\n",
    "\n",
    "The Betz limit is the theoretical maximum amount of kinetic energy that a wind turbine can extract from the flow.\n",
    "This limit was derived analytically by Albert Betz in 1919, but it can also be found numerically using an optimizer and a simple actuator disc model for a wind-turbine."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adabcc1d",
   "metadata": {
    "papermill": {
     "duration": 0.001149,
     "end_time": "2026-10-02T14:40:53.485179+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.484030+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "![The actuator disc model of a wind turbine.](images/actuator_disk.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39dc4c82",
   "metadata": {
    "papermill": {
     "duration": 0.001877,
     "end_time": "2026-10-02T14:40:53.488368+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.486491+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## The Actuator Disc Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7d9b4a39",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:53.492669Z",
     "iopub.status.busy": "2026-10-02T14:40:53.491905Z",
     "iopub.status.idle": "2026-10-02T14:40:55.256221Z",
     "shell.execute_reply": "2026-10-02T14:40:55.255379Z"
    },
    "papermill": {
     "duration": 1.767275,
     "end_time": "2026-10-02T14:40:55.257061+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:53.489786+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class ActuatorDisc(om.ExplicitComponent):\n",
    "    \"\"\"Simple wind turbine model based on actuator disc theory\"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "\n",
    "        # Inputs\n",
    "        self.add_input('a', 0.5, desc=\"Induced Velocity Factor\")\n",
    "        self.add_input('Area', 10.0, units=\"m**2\", desc=\"Rotor disc area\")\n",
    "        self.add_input('rho', 1.225, units=\"kg/m**3\", desc=\"air density\")\n",
    "        self.add_input('Vu', 10.0, units=\"m/s\", desc=\"Freestream air velocity, upstream of rotor\")\n",
    "\n",
    "        # Outputs\n",
    "        self.add_output('Vr', 0.0, units=\"m/s\",\n",
    "                        desc=\"Air velocity at rotor exit plane\")\n",
    "        self.add_output('Vd', 0.0, units=\"m/s\",\n",
    "                        desc=\"Slipstream air velocity, downstream of rotor\")\n",
    "        self.add_output('Ct', 0.0, desc=\"Thrust Coefficient\")\n",
    "        self.add_output('thrust', 0.0, units=\"N\",\n",
    "                        desc=\"Thrust produced by the rotor\")\n",
    "        self.add_output('Cp', 0.0, desc=\"Power Coefficient\")\n",
    "        self.add_output('power', 0.0, units=\"W\", desc=\"Power produced by the rotor\")\n",
    "\n",
    "        # Every output depends on `a`\n",
    "        self.declare_partials(of='*', wrt='a', method='cs')\n",
    "\n",
    "        # Other dependencies\n",
    "        self.declare_partials(of='Vr', wrt=['Vu'], method='cs')\n",
    "        self.declare_partials(of=['thrust', 'power'], wrt=['Area', 'rho', 'Vu'], method='cs')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        \"\"\" Considering the entire rotor as a single disc that extracts\n",
    "        velocity uniformly from the incoming flow and converts it to\n",
    "        power.\"\"\"\n",
    "\n",
    "        a = inputs['a']\n",
    "        Vu = inputs['Vu']\n",
    "\n",
    "        qA = .5 * inputs['rho'] * inputs['Area'] * Vu ** 2\n",
    "\n",
    "        outputs['Vd'] = Vd = Vu * (1 - 2 * a)\n",
    "        outputs['Vr'] = .5 * (Vu + Vd)\n",
    "\n",
    "        outputs['Ct'] = Ct = 4 * a * (1 - a)\n",
    "        outputs['thrust'] = Ct * qA\n",
    "\n",
    "        outputs['Cp'] = Cp = Ct * (1 - a)\n",
    "        outputs['power'] = Cp * qA * Vu"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a12b99bd",
   "metadata": {
    "papermill": {
     "duration": 0.038469,
     "end_time": "2026-10-02T14:40:55.320985+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:55.282516+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Build the problem and add the actuator disc model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "503b121f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:55.350868Z",
     "iopub.status.busy": "2026-10-02T14:40:55.350356Z",
     "iopub.status.idle": "2026-10-02T14:40:56.589185Z",
     "shell.execute_reply": "2026-10-02T14:40:56.588474Z"
    },
    "papermill": {
     "duration": 1.26234,
     "end_time": "2026-10-02T14:40:56.589641+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:55.327301+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952056.582906] [runnervm8df0l:6140 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x5564226fc140 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",
      "[1790952056.583188] [runnervm8df0l:6140 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:06140] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    }
   ],
   "source": [
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('a_disk', ActuatorDisc(),\n",
    "                         promotes_inputs=['a', 'Area', 'rho', 'Vu']);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b8693f5",
   "metadata": {
    "papermill": {
     "duration": 0.012121,
     "end_time": "2026-10-02T14:40:56.603143+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.591022+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Define the driver and optimization problem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a1033c83",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.606571Z",
     "iopub.status.busy": "2026-10-02T14:40:56.606416Z",
     "iopub.status.idle": "2026-10-02T14:40:56.609422Z",
     "shell.execute_reply": "2026-10-02T14:40:56.608818Z"
    },
    "papermill": {
     "duration": 0.005417,
     "end_time": "2026-10-02T14:40:56.609834+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.604417+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "\n",
    "prob.model.add_design_var('a', lower=0., upper=1.)\n",
    "\n",
    "# negative one so we maximize the objective\n",
    "prob.model.add_objective('a_disk.Cp', scaler=-1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a267e83",
   "metadata": {
    "papermill": {
     "duration": 0.000987,
     "end_time": "2026-10-02T14:40:56.611933+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.610946+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Call setup and provide the initial guess"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d79e3acd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.614628Z",
     "iopub.status.busy": "2026-10-02T14:40:56.614517Z",
     "iopub.status.idle": "2026-10-02T14:40:56.619848Z",
     "shell.execute_reply": "2026-10-02T14:40:56.619323Z"
    },
    "papermill": {
     "duration": 0.007355,
     "end_time": "2026-10-02T14:40:56.620312+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.612957+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "prob.setup()\n",
    "\n",
    "prob.set_val('a', .5)\n",
    "prob.set_val('Area', 10.0, units='m**2')\n",
    "prob.set_val('rho', 1.225, units='kg/m**3')\n",
    "prob.set_val('Vu', 10.0, units='m/s')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b53f862",
   "metadata": {
    "papermill": {
     "duration": 0.001,
     "end_time": "2026-10-02T14:40:56.622446+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.621446+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Run the optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "4d65c2e0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.649213Z",
     "iopub.status.busy": "2026-10-02T14:40:56.649080Z",
     "iopub.status.idle": "2026-10-02T14:40:56.681121Z",
     "shell.execute_reply": "2026-10-02T14:40:56.680617Z"
    },
    "papermill": {
     "duration": 0.034224,
     "end_time": "2026-10-02T14:40:56.681690+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.647466+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully    (Exit mode 0)\n",
      "            Current function value: -0.592592590665925\n",
      "            Iterations: 5\n",
      "            Function evaluations: 6\n",
      "            Gradient evaluations: 5\n",
      "Optimization Complete\n",
      "-----------------------------------\n"
     ]
    }
   ],
   "source": [
    "result = prob.run_driver()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b3e5c48",
   "metadata": {
    "papermill": {
     "duration": 0.001013,
     "end_time": "2026-10-02T14:40:56.683956+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.682943+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## View the inputs and outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7e319283",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.686913Z",
     "iopub.status.busy": "2026-10-02T14:40:56.686680Z",
     "iopub.status.idle": "2026-10-02T14:40:56.691707Z",
     "shell.execute_reply": "2026-10-02T14:40:56.690953Z"
    },
    "papermill": {
     "duration": 0.00704,
     "end_time": "2026-10-02T14:40:56.692111+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.685071+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4 Input(s) in 'model'\n",
      "\n",
      "varname  val           units    prom_name\n",
      "-------  ------------  -------  ---------\n",
      "a_disk\n",
      "  a      [0.33335528]  None     a        \n",
      "  Area   [10.]         m**2     Area     \n",
      "  rho    [1.225]       kg/m**3  rho      \n",
      "  Vu     [10.]         m/s      Vu       \n",
      "\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[('a_disk.a', {'units': None, 'prom_name': 'a', 'val': array([0.33335528])}),\n",
       " ('a_disk.Area', {'units': 'm**2', 'prom_name': 'Area', 'val': array([10.])}),\n",
       " ('a_disk.rho',\n",
       "  {'units': 'kg/m**3', 'prom_name': 'rho', 'val': array([1.225])}),\n",
       " ('a_disk.Vu', {'units': 'm/s', 'prom_name': 'Vu', 'val': array([10.])})]"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prob.model.list_inputs(val=True, units=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "dea56637",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.695179Z",
     "iopub.status.busy": "2026-10-02T14:40:56.695053Z",
     "iopub.status.idle": "2026-10-02T14:40:56.699096Z",
     "shell.execute_reply": "2026-10-02T14:40:56.698504Z"
    },
    "papermill": {
     "duration": 0.006112,
     "end_time": "2026-10-02T14:40:56.699482+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.693370+00:00",
     "status": "completed"
    },
    "scrolled": false,
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6 Explicit Output(s) in 'model'\n",
      "\n",
      "varname   val              units  prom_name    \n",
      "--------  ---------------  -----  -------------\n",
      "a_disk\n",
      "  Vr      [6.6664472]      m/s    a_disk.Vr    \n",
      "  Vd      [3.33289439]     m/s    a_disk.Vd    \n",
      "  Ct      [0.88891815]     None   a_disk.Ct    \n",
      "  thrust  [544.46236677]   N      a_disk.thrust\n",
      "  Cp      [0.59259259]     None   a_disk.Cp    \n",
      "  power   [3629.62961783]  W      a_disk.power \n",
      "\n",
      "\n",
      "0 Implicit Output(s) in 'model'\n",
      "\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[('a_disk.Vr',\n",
       "  {'units': 'm/s', 'val': array([6.6664472]), 'prom_name': 'a_disk.Vr'}),\n",
       " ('a_disk.Vd',\n",
       "  {'units': 'm/s', 'val': array([3.33289439]), 'prom_name': 'a_disk.Vd'}),\n",
       " ('a_disk.Ct',\n",
       "  {'units': None, 'val': array([0.88891815]), 'prom_name': 'a_disk.Ct'}),\n",
       " ('a_disk.thrust',\n",
       "  {'units': 'N', 'val': array([544.46236677]), 'prom_name': 'a_disk.thrust'}),\n",
       " ('a_disk.Cp',\n",
       "  {'units': None, 'val': array([0.59259259]), 'prom_name': 'a_disk.Cp'}),\n",
       " ('a_disk.power',\n",
       "  {'units': 'W', 'val': array([3629.62961783]), 'prom_name': 'a_disk.power'})]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prob.model.list_outputs(val=True, units=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "62978c38",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.702690Z",
     "iopub.status.busy": "2026-10-02T14:40:56.702551Z",
     "iopub.status.idle": "2026-10-02T14:40:56.705163Z",
     "shell.execute_reply": "2026-10-02T14:40:56.704590Z"
    },
    "hide_input": true,
    "papermill": {
     "duration": 0.004724,
     "end_time": "2026-10-02T14:40:56.705505+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.700781+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.59259259]\n",
      "[0.33335528]\n"
     ]
    }
   ],
   "source": [
    "# Verify the correct outputs\n",
    "\n",
    "# minimum value\n",
    "print(prob.get_val('a_disk.Cp'))\n",
    "print(prob.get_val('a'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7ea99d6c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:40:56.708533Z",
     "iopub.status.busy": "2026-10-02T14:40:56.708428Z",
     "iopub.status.idle": "2026-10-02T14:40:56.711858Z",
     "shell.execute_reply": "2026-10-02T14:40:56.711324Z"
    },
    "papermill": {
     "duration": 0.005628,
     "end_time": "2026-10-02T14:40:56.712389+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:40:56.706761+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(7.584221220143485e-05)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob['a_disk.Cp'], 16./27., 1e-4)\n",
    "assert_near_equal(prob['a'], 0.33333, 1e-4)"
   ]
  }
 ],
 "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": 4.774541,
   "end_time": "2026-10-02T14:40:57.228730+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/examples/betz_limit.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/examples/betz_limit.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:40:52.454189+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}