{
 "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": [
    "# Finding Feasible Solutions\n",
    "\n",
    "Constrained, gradient-based optimization tools like SLSQP, IPOPT, and SNOPT can benefit from starting from a feasible design point,\n",
    "but finding a feasible design with dozens or hundreds of design variables can be a challenge in itself.\n",
    "\n",
    "Domain-specific methods exist for finding a feasible starting point, such as using a suboptimal explicit simulation to jump-start an implicit trajectory optimization, but in a multidisciplinary environment a more general approach is needed.\n",
    "\n",
    "OpenMDAO's `Problem.find_feasible` method aims to find a feasible point if one exists, or otherwise minimize the violation of infeasible constraints in the design. \n",
    "\n",
    "This approach poses the constraint violations as residuals to be minimized via a least-squares approach.\n",
    "Under the hood, it utilizes [scipy.optimize.least_squares](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html) to perform this minimization. \n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.find_feasible\n",
    "        :noindex:\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Consider the following optimization problem. It contains two constraints which are in conflict with each other.\n",
    "\n",
    "\\begin{align}\n",
    "    \\mathrm{Minimize}: \\\\\n",
    "    y = x_1^2 - x_2^2 \\\\\n",
    "    \\mathrm{Subject\\,to:} \\\\\n",
    "    g_1 = 2 x_1 - 5 \\\\\n",
    "    g_2 = -2 x_1 - 5 \\\\\n",
    "    g_3 = x_2 - 5\n",
    "\\end{align}\n",
    "\n",
    "\n",
    "The OpenMDAO problem can be formulated as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "c1 = om.ExecComp()\n",
    "c1.add_expr('y = x1**2 - x2**2', y={'copy_shape': 'x1'}, x1={'shape_by_conn': True}, x2={'shape_by_conn': True})\n",
    "c1.add_expr('g1 = 2 * x1 - 5', g1={'copy_shape': 'x1'})\n",
    "c1.add_expr('g2 = -2 * x1 - 5', g2={'copy_shape': 'x1'})\n",
    "c1.add_expr('g3 = x2 - 5', g3={'copy_shape': 'x2'})\n",
    "\n",
    "c1.add_design_var('x1', lower=-10, upper=10)\n",
    "c1.add_design_var('x2', lower=-10, upper=10)\n",
    "c1.add_objective('y')\n",
    "c1.add_constraint('g1', lower=0)\n",
    "c1.add_constraint('g2', lower=0)\n",
    "c1.add_constraint('g3', lower=0)\n",
    "\n",
    "prob.model.add_subsystem('c1', c1, promotes=['*'])\n",
    "prob.driver = om.ScipyOptimizeDriver()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The feasible regions due to each constraint are shaded in the figure below.  Note that there is no solution which satisfies all three constraints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "x1s = np.linspace(-10, 10, 100)\n",
    "x2s = np.linspace(-10, 10, 100)\n",
    "xx1, xx2 = np.meshgrid(x1s, x2s)\n",
    "\n",
    "prob.setup()\n",
    "prob.set_val('x1', xx1)\n",
    "prob.set_val('x2', xx2)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "ys = prob.get_val('y')\n",
    "\n",
    "fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n",
    "ax.contour(x1s, x2s, ys, levels=20)\n",
    "ax.fill_between(np.linspace(-10, 10, 10), 5 * np.ones(10), 10 * np.ones(10), color='r', alpha=0.3)\n",
    "ax.fill_between(np.linspace(2.5, 10, 10), -10 * np.ones(10), 10 * np.ones(10), color='g', alpha=0.3)\n",
    "ax.fill_between(np.linspace(-10, -2.5, 10), -10 * np.ones(10), 10 * np.ones(10), color='b', alpha=0.3)\n",
    "ax.set_xlabel('$x_1$')\n",
    "ax.set_ylabel('$x_2$')\n",
    "ax.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we perform an optimization with the driver, it will fail due to the incompatible constraints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Call setup again so we can use a new size for x1 and x2\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x1', 1.0)\n",
    "prob.set_val('x2', 1.0)\n",
    "\n",
    "prob.run_driver()\n",
    "prob.list_driver_vars(cons_opts=['lower', 'upper'], objs_opts=['val']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we use the `find_feasible` method on `Problem`, we can find the solution which minimizes the L2 norm of the constraint violations.\n",
    "This method will attempt to find the solution that minimizes the constraint violations. If it fails to converge to a solution, it will return as having failed.\n",
    "If it converges but the solution is still infeasible (it found the solution which minimizes the constraint violation), it will also return as having failed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x1_opt = prob.get_val('x1').copy()\n",
    "x2_opt = prob.get_val('x2').copy()\n",
    "\n",
    "prob.set_val('x1', 1.0)\n",
    "prob.set_val('x2', 1.0)\n",
    "\n",
    "prob.find_feasible()\n",
    "prob.list_driver_vars(desvar_opts=['lower', 'upper'], cons_opts=['lower', 'upper'], objs_opts=['val']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following plot highlights the difference between the solution at the end of the failed optimization run, versus the solution found which minimizes feasibilities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "x1_min_infeas = prob.get_val('x1').copy()\n",
    "x2_min_infeas = prob.get_val('x2').copy()\n",
    "\n",
    "fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n",
    "ax.contour(x1s, x2s, ys, levels=20)\n",
    "ax.fill_between(np.linspace(-10, 10, 10), 5 * np.ones(10), 10 * np.ones(10), color='r', alpha=0.3)\n",
    "ax.fill_between(np.linspace(2.5, 10, 10), -10 * np.ones(10), 10 * np.ones(10), color='g', alpha=0.3)\n",
    "ax.fill_between(np.linspace(-10, -2.5, 10), -10 * np.ones(10), 10 * np.ones(10), color='b', alpha=0.3)\n",
    "ax.plot(x1_opt, x2_opt, 'bo', ms=10, label='Optimizer solution')\n",
    "ax.plot(x1_min_infeas, x2_min_infeas, 'k*', ms=10, label='find_feasible result')\n",
    "ax.set_xlabel('$x_1$')\n",
    "ax.set_ylabel('$x_2$')\n",
    "ax.grid()\n",
    "ax.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Omitting one or more design variables from the feasibility search\n",
    "\n",
    "Sometimes the user may be interested in finding a feasible (or least infeasible) solution at a given value of one or more design variables.\n",
    "The `find_feasible` method supports an `exclude_desvars` keyword argument that allows the user to exclude one or more design variables from the feasibility search. This argument may be a glob pattern which can match one or more design variable names.\n",
    "\n",
    "For instance, let's find the minimimum-infeasibility-solution such that $x_1 = 5$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.set_val('x1', 5.0)\n",
    "\n",
    "prob.find_feasible(exclude_desvars=['x1'])\n",
    "prob.list_driver_vars(desvar_opts=['lower', 'upper'], cons_opts=['lower', 'upper'], objs_opts=['val']);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "x1_min_infeas_x1_fixed = prob.get_val('x1').copy()\n",
    "x2_min_infeas_x1_fixed = prob.get_val('x2').copy()\n",
    "\n",
    "fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n",
    "ax.contour(x1s, x2s, ys, levels=20)\n",
    "ax.fill_between(np.linspace(-10, 10, 10), 5 * np.ones(10), 10 * np.ones(10), color='r', alpha=0.3)\n",
    "ax.fill_between(np.linspace(2.5, 10, 10), -10 * np.ones(10), 10 * np.ones(10), color='g', alpha=0.3)\n",
    "ax.fill_between(np.linspace(-10, -2.5, 10), -10 * np.ones(10), 10 * np.ones(10), color='b', alpha=0.3)\n",
    "ax.plot(x1_opt, x2_opt, 'bo', ms=10, label='Optimizer solution')\n",
    "ax.plot(x1_min_infeas, x2_min_infeas, 'k*', ms=10, label='find_feasible result')\n",
    "ax.plot(x1_min_infeas_x1_fixed, x2_min_infeas_x1_fixed, 'r*', ms=10, label='find_feasible result $x_1=5$')\n",
    "ax.set_xlabel('$x_1$')\n",
    "ax.set_ylabel('$x_2$')\n",
    "ax.grid()\n",
    "ax.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Starting an Optimization from a Feasible Point\n",
    "\n",
    "The `find_feasible` method iterates on the OpenMDAO model.\n",
    "\n",
    "If it completes successfully, the model will be in a feasible state and optimization can begin immediately.\n",
    "\n",
    "The following problem can be challenging to optimize from the default initial condition of `[1, 1, 10, 1]`.\n",
    "\n",
    "This problem is the cost minimization of a pressure vessel with the design variables being the thicknesses of the end-cap and cylindrical sections (`d1` and `d2`), the vessel radius (`r`), and the length of the cylindrical section (`L`).\n",
    "\n",
    "This example comes from _Solving Engineering Optimization Problems with the Simple Constrained Particle Swarm Optimizer_, by Cagnina et al., 2008.\n",
    "Note that the original problem assumes integer values for `d1` and `d2` but they are continuous for the purpose of this demonstration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('vessel',\n",
    "                      om.ExecComp(['f = 0.6224 * d1 * r * L + 1.7781 * d2 * r**2 + 3.1661 * d1**2 * L + 19.84 * d1**2 * r',\n",
    "                                   'g1 = -d1 + 0.0193 * r',\n",
    "                                   'g2 = -d2 + 0.00954 * r',\n",
    "                                   'g3 = -pi * r**2 * L - (4/3) * pi * r**3 + 1296000.'], has_diag_partials=True),\n",
    "                      promotes=['*'])\n",
    "\n",
    "p.model.add_design_var('d1', lower=0.0625)\n",
    "p.model.add_design_var('d2', lower=0.0625, upper = 99 * 0.0625)\n",
    "p.model.add_design_var('r', lower=10)\n",
    "p.model.add_design_var('L', lower=1., upper=200.)\n",
    "\n",
    "p.model.add_constraint('g1', upper=0)\n",
    "p.model.add_constraint('g2', upper=0)\n",
    "p.model.add_constraint('g3', upper=0, ref=1_000_000.)\n",
    "\n",
    "p.model.add_objective('f', ref=1_000.)\n",
    "\n",
    "p.driver = om.ScipyOptimizeDriver()\n",
    "\n",
    "p.setup();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instead, we'll reset the design point to the initial naive value and use find_feasible to first try to find a solution that satisfies all constraints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.set_val('r', 10.) # Note we start r within its bounds for find_feasible.\n",
    "\n",
    "p.run_model()\n",
    "find_feas_fail = p.find_feasible()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert not find_feas_fail"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The feasible starting point is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p.list_driver_vars(desvar_opts=['lower', 'upper'], cons_opts=['lower', 'upper'], objs_opts=[]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the problem is in a state such that the problem is feasible, run the driver."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "opt_fail = p.run_driver()\n",
    "\n",
    "p.list_driver_vars(desvar_opts=['lower', 'upper'], cons_opts=['lower', 'upper'], objs_opts=[]);"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "default",
   "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.7"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
