{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Problem\n", "\n", "## Define a problem using modOpt's `Problem` class\n", "\n", "This example does not intend to cover all the features of the `Problem` class.\n", "For more details on `Problem`, please see the **[API Reference](../api.md)**.\n", "In this example, we solve a constrained problem given by\n", "\n", "$$\n", "\\underset{x_1, x_2 \\in \\mathbb{R}}{\\text{minimize}} \\quad x_1^2 + x_2^2\n", "\n", "\\newline\n", "\\text{subject to} \\quad x_1 \\geq 0\n", "\\newline\n", "\\quad \\quad \\quad \\quad x_1 + x_2 = 1\n", "\\newline\n", "\\quad \\quad \\quad \\quad x_1 - x_2 \\geq 1\n", "$$\n", "\n", "We know the solution of this problem is $x_1=1$, and $x_2=0$.\n", "However, we start from an initial guess of $x_1=500.0$, and $x_2=5.0$ for the purposes of this tutorial.\n", "\n", "The problem model is written as a subclass of as `Problem` follows:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import modopt as mo\n", "\n", "# minimize x^2 + y^2 subject to x>=0, x+y=1, x-y>=1.\n", "\n", "class QuadraticProblem(mo.Problem):\n", " def initialize(self):\n", " self.problem_name = 'quadratic_problem'\n", "\n", " def setup(self):\n", " self.add_design_variables('x', # Design variable name\n", " shape=(2, ), # Design variable shape\n", " lower=np.array([0., -np.inf]), # Design variable lower bounds\n", " vals=np.array([500., 5.])) # Initial guess\n", " \n", " self.add_objective('f') # Objective name\n", "\n", " self.add_constraints('c', # Constraint name\n", " shape=(2, ), # Constraint shape\n", " lower=np.array([1., 1.]), # Constraint lower bounds\n", " upper=np.array([1., np.inf])) # Constraint upper bounds\n", "\n", " def setup_derivatives(self):\n", " self.declare_objective_gradient(wrt='x')\n", "\n", " constant_J = np.array([[1.,1.],[1.,-1]]) # Constant value for the constraint Jacobian\n", " self.declare_constraint_jacobian(of='c', \n", " wrt='x',\n", " vals=constant_J)\n", "\n", " def compute_objective(self, dvs, obj):\n", " x = dvs['x']\n", " obj['f'] = np.sum(x**2)\n", "\n", " def compute_objective_gradient(self, dvs, grad):\n", " grad['x'] = 2 * dvs['x']\n", "\n", " def compute_constraints(self, dvs, cons):\n", " x = dvs['x']\n", " con = cons['c']\n", " con[0] = x[0] + x[1]\n", " con[1] = x[0] - x[1]\n", "\n", " # NOTE: compute_constraint_jacobian must be defined even if the Jacobian is constant\n", " def compute_constraint_jacobian(self, dvs, jac):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that in the code above, we combined all the variables and constraints into single vectors.\n", "For examples with multiple variable and constraint vectors, see **[Examples](../examples/basic.md)**.\n", "This is one of the additional convenience features offered by `Problem` over `ProblemLite`,\n", "as `ProblemLite` requires users to combine all variables and constraints into single vectors.\n", "Although more verbose than `ProblemLite`, using the `Problem` class as shown above can be more \n", "beneficial for certain problems, e.g, sparse problems where some of the constraints \n", "are only affected by a subset of the variables.\n", "\n", "## Solve your problem using an optimizer\n", "\n", "Once your problem is completely defined within a subclass (here `QuadraticProblem`) of `Problem`,\n", "create a subclass object to pass to the optimizer.\n", "Lastly, import your preferred optimizer from modOpt \n", "and solve it, following the standard procedure.\n", "Here we will use the `SLSQP` optimizer from the SciPy library." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting objective name as \"f\".\n", "\n", "----------------------------------------------------------------------------\n", "Derivative type | Calc norm | FD norm | Abs error norm | Rel error norm \n", "----------------------------------------------------------------------------\n", "\n", "Gradient | 1.0000e+03 | 1.0000e+03 | 1.5473e-05 | 1.5472e-08 \n", "Jacobian | 2.0000e+00 | 2.0000e+00 | 5.0495e-09 | 2.5248e-09 \n", "----------------------------------------------------------------------------\n", "\n", "\n", "\tSolution from Scipy SLSQP:\n", "\t----------------------------------------------------------------------------------------------------\n", "\tProblem : quadratic_problem\n", "\tSolver : scipy-slsqp\n", "\tSuccess : True\n", "\tMessage : Optimization terminated successfully\n", "\tStatus : 0\n", "\tTotal time : 0.006365299224853516\n", "\tObjective : 1.0000000068019972\n", "\tGradient norm : 2.000000006801997\n", "\tTotal function evals : 2\n", "\tTotal gradient evals : 2\n", "\tMajor iterations : 2\n", "\tTotal callbacks : 17\n", "\tReused callbacks : 0\n", "\tobj callbacks : 5\n", "\tgrad callbacks : 3\n", "\thess callbacks : 0\n", "\tcon callbacks : 6\n", "\tjac callbacks : 3\n", "\t----------------------------------------------------------------------------------------------------\n" ] } ], "source": [ "# Create a Problem subclass object\n", "prob = QuadraticProblem()\n", "\n", "# Setup your preferred optimizer (SLSQP) with the Problem object \n", "# Pass in the options for your chosen optimizer\n", "optimizer = mo.SLSQP(prob, solver_options={'maxiter':20})\n", "\n", "# Check first derivatives at the initial guess, if needed\n", "optimizer.check_first_derivatives(prob.x0)\n", "\n", "# Solve your optimization problem\n", "optimizer.solve()\n", "\n", "# Print results of optimization\n", "optimizer.print_results()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scaling API\n", "\n", "Please refer to the code snippet below as a guide for scaling \n", "the design variables, objective, and constraints independent\n", "of their definitions.\n", "\n", "```{warning}\n", "The results provided by the optimizer will always be scaled,\n", "while the values from the models will remain unscaled.\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def setup(self):\n", " self.add_design_variables('x', # Design variable name\n", " shape=(2, ), # Design variable shape\n", " lower=np.array([0., -np.inf]), # Design variable lower bounds\n", " vals=np.array([500., 5.]), # Initial guess\n", " scaler=2.) # Design variable scaler - constant value\n", " # scaler=np.array([1., 2.]) # Design variable scaler - vector value\n", " \n", " self.add_objective('f', scaler=5.0) # Objective name and scaler\n", "\n", " self.add_constraints('c', # Constraint name\n", " shape=(2, ), # Constraint shape\n", " lower=np.array([1., 1.]), # Constraint lower bounds\n", " upper=np.array([1., np.inf]), # Constraint upper bounds\n", " scaler=np.array([10., 100.])) # Constraint scaler - vector value\n", " # scaler=5.0) # Constraint scaler - constant value" ] } ], "metadata": { "kernelspec": { "display_name": "venv", "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.9.13" } }, "nbformat": 4, "nbformat_minor": 2 }