{
 "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\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial will show you how to setup and run an optimization using a component you've already defined.\n",
    "The organization of this run script and its use of the `Problem` class is the basis for executing all models in OpenMDAO.\n",
    "\n",
    "# Optimization of Paraboloid\n",
    "\n",
    "To start out, we'll reuse the `Paraboloid` component that we defined in the [previous tutorial](first_analysis).\n",
    "We'll add that component to construct our model inside a [Problem](../../features/core_features/running_your_models/main).\n",
    "You've already used `Problem` in the run script from the previous tutorial on the paraboloid analysis,\n",
    "but we'll take a closer look now.\n",
    "\n",
    "All analyses and optimizations in OpenMDAO are executed with an instance of the `Problem` class.\n",
    "This class serves as a container for your model and the driver you've chosen,\n",
    "and provides methods for you to [run the model](../../features/core_features/running_your_models/run_model) and [run the driver](../../features/core_features/running_your_models/run_driver).\n",
    "It also provides a [interface for setting and getting variable values](../../features/core_features/running_your_models/set_get).\n",
    "Every problem has a single driver associated with it; similarly, every problem has a single model in it.\n",
    "\n",
    "![problem_diagram](images/problem_diagram.png)\n",
    "\n",
    "## The Run Script"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We'll use the component that was defined in the last tutorial\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "# build the model\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('parab', Paraboloid(), promotes_inputs=['x', 'y'])\n",
    "\n",
    "# define the component whose output will be constrained\n",
    "prob.model.add_subsystem('const', om.ExecComp('g = x + y'), promotes_inputs=['x', 'y'])\n",
    "\n",
    "# Design variables 'x' and 'y' span components, so we need to provide a common initial\n",
    "# value for them.\n",
    "prob.model.set_input_defaults('x', 3.0)\n",
    "prob.model.set_input_defaults('y', -4.0)\n",
    "\n",
    "# setup the optimization\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'COBYLA'\n",
    "\n",
    "prob.model.add_design_var('x', lower=-50, upper=50)\n",
    "prob.model.add_design_var('y', lower=-50, upper=50)\n",
    "prob.model.add_objective('parab.f_xy')\n",
    "\n",
    "# to add the constraint to the model\n",
    "prob.model.add_constraint('const.g', lower=0, upper=10.)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_driver();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# minimum value\n",
    "print(prob.get_val('parab.f_xy'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# location of the minimum\n",
    "print(prob.get_val('x'))\n",
    "print(prob.get_val('y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob.get_val('parab.f_xy'), -27., 1e-6)\n",
    "\n",
    "assert_near_equal(prob.get_val('x'), 7, 1e-4)\n",
    "assert_near_equal(prob.get_val('y'), -7, 1e-4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although we defined the `Paraboloid` component in a [previous tutorial](first_analysis), we wanted to add an additional equation to our model.\n",
    "Since it was a very simple equation, we used the [ExecComp](../../features/building_blocks/components/exec_comp) to quickly add the new output to our model, so that we can constrain it.\n",
    "Once you have defined the necessary output variable, you just have to add it to the problem formulation so the driver knows to actually respect it. For this toy problem it turns out that the constrained optimum occurs when `x = -y = 7.0`, so it's actually possible to get the same answer using an equality constraint set to 0. We included both options in the tutorial for your reference.\n",
    "\n",
    "```{Note}\n",
    "[ExecComp](../../features/building_blocks/components/exec_comp) is a useful utility component provided in OpenMDAO's [standard library](../../features/building_blocks/building_blocks) that lets you define new calculations just by typing in the expression. It supports basic math operations, and even some of numpy's more advanced methods. It also supports both scalar and array data as well.\n",
    "```\n",
    "\n",
    "## Setting a Driver\n",
    "\n",
    "Telling OpenMDAO to use a specific optimizer is done by setting the `driver` attribute of the problem.\n",
    "Here we'll use the [ScipyOptimizeDriver](../../features/building_blocks/drivers/scipy_optimize_driver), and tell it to use the *COBYLA* algorithm.\n",
    "\n",
    "```python\n",
    "    prob.driver = om.ScipyOptimizeDriver()\n",
    "    prob.driver.options['optimizer'] = 'COBYLA'\n",
    "```\n",
    "\n",
    "## Defining the Design Variables and Objective\n",
    "\n",
    "Next, we set up the problem formulation so that the optimizer knows what to vary and which objective to optimize.\n",
    "In these calls, we are going to be specifying a single variable. For [add_design_var](../../features/core_features/adding_desvars_cons_objs/adding_design_variables), the variable will be a component input. If the input is promoted, then it will be the promoted name. For [add_objective](../../features/core_features/adding_desvars_cons_objs/adding_objective) and [add_constraint](../../features/core_features/adding_desvars_cons_objs/adding_constraint) the variable can be the output of any component.\n",
    "\n",
    "```python\n",
    "    prob.model.add_design_var('x', lower=-50, upper=50)\n",
    "    prob.model.add_design_var('y', lower=-50, upper=50)\n",
    "    prob.model.add_objective('parab.f_xy')\n",
    "    prob.model.add_constraint('const.g', lower=0, upper=10.)\n",
    "```\n",
    "\n",
    "```{Note}\n",
    "Although these calls always point to a specific variable, that variable doesn't have to be a scalar value. See the feature docs for [adding design variables, objectives, and constraints](../../features/core_features/adding_desvars_cons_objs/main) for more details.\n",
    "```\n",
    "\n",
    "Finally, we call [setup()](../../features/core_features/running_your_models/setup), and then [run_driver()](../../features/core_features/running_your_models/run_driver) to actually execute the model, then we use some print statements to interrogate the final values."
   ]
  }
 ],
 "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.8.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
