{
 "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": [
    "In the previous tutorial, we discussed the three basic kinds of Components in the OpenMDAO framework.\n",
    "This tutorial focuses on using one of those, [ExplicitComponent](../../features/core_features/working_with_components/explicit_component), to build a simple analysis of a paraboloid function. We'll explain the basic structure of a run file, show you how to set inputs, run the model, and check the output files.\n",
    "\n",
    "# Paraboloid - A Single-Discipline Model\n",
    "\n",
    "Consider a paraboloid, defined by the explicit function\n",
    "\n",
    "$$\n",
    "  f(x,y) = (x-3.0)^2 + x \\times y + (y+4.0)^2 - 3.0 ,\n",
    "$$\n",
    "\n",
    "where `x` and `y` are the inputs to the function.\n",
    "The minimum of this function is located at\n",
    "\n",
    "$$\n",
    "  x = \\frac{20}{3} \\quad , \\quad y = -\\frac{22}{3} .\n",
    "$$\n",
    "\n",
    "Here is a complete script that defines this equation as a component and then executes it with different input values, printing the results to the console when it's done. Take a look at the full run script first, then we'll break it down part by part to explain what each one does."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class Paraboloid(om.ExplicitComponent):\n",
    "    \"\"\"\n",
    "    Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.\n",
    "    \"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=0.0)\n",
    "        self.add_input('y', val=0.0)\n",
    "\n",
    "        self.add_output('f_xy', val=0.0)\n",
    "\n",
    "    def setup_partials(self):\n",
    "        # Finite difference all partials.\n",
    "        self.declare_partials('*', '*', method='fd')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        \"\"\"\n",
    "        f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3\n",
    "\n",
    "        Minimum at: x = 6.6667; y = -7.3333\n",
    "        \"\"\"\n",
    "        x = inputs['x']\n",
    "        y = inputs['y']\n",
    "\n",
    "        outputs['f_xy'] = (x - 3.0)**2 + x * y + (y + 4.0)**2 - 3.0\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    model = om.Group()\n",
    "    model.add_subsystem('parab_comp', Paraboloid())\n",
    "\n",
    "    prob = om.Problem(model)\n",
    "    prob.setup()\n",
    "\n",
    "    prob.set_val('parab_comp.x', 3.0)\n",
    "    prob.set_val('parab_comp.y', -4.0)\n",
    "\n",
    "    prob.run_model()\n",
    "    print(prob['parab_comp.f_xy'])\n",
    "\n",
    "    prob.set_val('parab_comp.x', 5.0)\n",
    "    prob.set_val('parab_comp.y', -2.0)\n",
    "\n",
    "    prob.run_model()\n",
    "    print(prob.get_val('parab_comp.f_xy'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, let's break this script down and understand each section:\n",
    "\n",
    "## Preamble\n",
    "\n",
    "```python\n",
    "import openmdao.api as om\n",
    "```\n",
    "\n",
    "At the top of any script you'll see this line (or lines very similar) which makes available the most common OpenMDAO classes and functions that are needed to build and run a model. \n",
    "\n",
    "The `openmdao.api` module contains the class `ExplicitComponent` that we need to define the Paraboloid model. This\n",
    "can be accessed via \"om.ExplicitComponent\" when we need it. As you progress to more complex models, you will learn about more classes available in `openmdao.api`, but for now we only need this one to define our paraboloid component.\n",
    "\n",
    "## Defining a Component\n",
    "\n",
    "The component is the basic building block of a model. You will always define components as a subclass of either [ExplicitComponent](../../../_srcdocs/packages/core/explicitcomponent) or [ImplicitComponent](../../../_srcdocs/packages/core/implicitcomponent). Since our simple paraboloid function is explicit, we'll use the [ExplicitComponent](../../../_srcdocs/packages/core/explicitcomponent). You see three methods defined:\n",
    "\n",
    "- `setup`: define all your inputs and outputs here\n",
    "- `setup_partials`: declare partial derivatives\n",
    "- `compute`: calculation of all output values for the given inputs\n",
    "\n",
    "In the `setup` method you define the inputs and outputs of the component.\n",
    "In the `setup_partials` method in this case you also ask OpenMDAO to approximate all the partial\n",
    "derivatives (derivatives of outputs with respect to inputs)\n",
    "using the `finite difference method <https://en.wikipedia.org/wiki/Finite_difference_method>`_.\n",
    "\n",
    "```{Note}\n",
    "    One of OpenMDAO's most unique features is its support for analytic derivatives.\n",
    "    Providing analytic partial derivatives from your components can result in much more efficient optimizations.\n",
    "    We'll get to using analytic derivatives in later tutorials.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Paraboloid(om.ExplicitComponent):\n",
    "    \"\"\"\n",
    "    Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.\n",
    "    \"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=0.0)\n",
    "        self.add_input('y', val=0.0)\n",
    "\n",
    "        self.add_output('f_xy', val=0.0)\n",
    "\n",
    "    def setup_partials(self):\n",
    "        # Finite difference all partials.\n",
    "        self.declare_partials('*', '*', method='fd')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        \"\"\"\n",
    "        f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3\n",
    "\n",
    "        Minimum at: x = 6.6667; y = -7.3333\n",
    "        \"\"\"\n",
    "        x = inputs['x']\n",
    "        y = inputs['y']\n",
    "\n",
    "        outputs['f_xy'] = (x - 3.0)**2 + x * y + (y + 4.0)**2 - 3.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Run Script\n",
    "\n",
    "In this example we've set up the run script at the bottom of the file.\n",
    "The start of the run script is denoted by the following statement:\n",
    "```\n",
    "if __name__ == '__main__':\n",
    "```\n",
    "\n",
    "All OpenMDAO models are built up from a hierarchy of [Group](../../../_srcdocs/packages/core/group) instances that organize the components.\n",
    "In this example, the hierarchy is very simple, consisting of a single root group that holds a single component that is an\n",
    "instance of the `Paraboloid` class that we just defined.\n",
    "\n",
    "Once the model hierarchy is defined,\n",
    "we pass it to the constructor of the [Problem](../../../_srcdocs/packages/core/problem) class.\n",
    "Then we call the `setup()` method on that problem, which tells the framework to do some initial work to get the data structures in place for execution.\n",
    "In this case, we call `run_model()` to actually perform the computation. Later, we'll see how to explicitly set drivers and will be calling `run_driver()` instead.\n",
    "\n",
    "Here we called run_model twice.\n",
    "The first time `x` and `y` have the initial values of 3.0 and -4.0 respectively.\n",
    "The second time we changed those values to 5.0 and -2.0 and then re-ran.\n",
    "There are a few details to note here.\n",
    "First, notice the way we printed the outputs via `prob.get_val('parab_comp.f_xy')` and similarly how we set the new values for `x` and `y`.\n",
    "You can get and set values using the \"get_val\" and \"set_val\" methods on problem. By default, these methods will set and get values defined in\n",
    "the dimensional units of the specified input or output.\n",
    "In this case, there are no units on the source (i.e. `des_vars.x`).\n",
    "\n",
    "```{Note}\n",
    "Detailed information on [units](../../features/core_features/working_with_components/units) and [scaling](../../features/core_features/working_with_components/scaling) can be found in the feature documentation.\n",
    "```\n",
    "\n",
    "```python\n",
    "    if __name__ == \"__main__\":\n",
    "\n",
    "        model = om.Group()\n",
    "        model.add_subsystem('parab_comp', Paraboloid())\n",
    "\n",
    "        prob = om.Problem(model)\n",
    "        prob.setup()\n",
    "\n",
    "        prob.set_val('parab_comp.x', 3.0)\n",
    "        prob.set_val('parab_comp.y', -4.0)\n",
    "\n",
    "        prob.run_model()\n",
    "        print(prob['parab_comp.f_xy'])\n",
    "\n",
    "        prob.set_val('parab_comp.x', 5.0)\n",
    "        prob.set_val('parab_comp.y', -2.0)\n",
    "\n",
    "        prob.run_model()\n",
    "        print(prob['parab_comp.f_xy'])\n",
    "```"
   ]
  }
 ],
 "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
}
