{
 "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": [
    "# Scaling Variables\n",
    "\n",
    "As we saw in [Declaring Continuous Variables](continuous_variables.ipynb), we can specify scaling parameters for outputs and residuals. Scaling can be important for the efficiency of some linear solvers and can have an impact on some gradient free nonlinear solvers such as Broyden. Knowing when and how to use scaling can be tricky, but in general, it is a good idea to scale outputs and residuals so that both have values that are O(1) and so that they have roughly the same range of variation in your design space.\n",
    "\n",
    "For example, consider a variable that is expected to have a value around 2500. Then you might scale it by dividing by 1000. However, if the value was going to have an expected range between 2400 and 2500, then you might want to subtract out 2400 then divide by 100.\n",
    "\n",
    "OpenMDAO supports this kind of linear scaling of both output and residual values through a set of user defined reference values specified when the variables are defined. These are described below.\n",
    "\n",
    "```{note}\n",
    "When you apply scaling to your variables, it does not affect the inputs and outputs you work with in your components. These are still worked with in physical, dimensional quantities. The scaling is only applied internally when values are given to solvers.\n",
    "```\n",
    "\n",
    "## Basics\n",
    "\n",
    "For outputs, scaling can be specified using the `ref` argument to `add_output`. This argument is named as such because it represents the reference physical value that will be scaled to 1. The table below shows some example physical values and their scaled values for the a given `ref`.\n",
    "\n",
    "| `ref`  | Physical value | Normalized value |\n",
    "| ------ | -------------- | ---------------- | \n",
    "| 10000  |   0            |        0.0       | \n",
    "| –      |  10000         | 1.0              | \n",
    "| –      |  25000         | 2.5              | \n",
    "| 0.0001 |  0.0000        | 0.0              | \n",
    "| –      |  0.0001        |  1.0             | \n",
    "| –      |  0.0003        | 3.0              | \n",
    "\n",
    "For residuals, scaling works the same way, except that the argument to `add_output` is `res_ref`.\n",
    "\n",
    "## Scaling with an offset\n",
    "\n",
    "It can be desirable to scale with an offset when the variable values are very large but they only vary by a small amount. In these situations, we can specify a second argument, `ref0`, to `add_output`. This argument is named as such because `ref0` represents the physical value when the scaled value is 0.\n",
    "\n",
    "| `ref`  | `ref0`  | Physical value | Normalized value |\n",
    "| ------ | ------- | -------------- | ---------------- | \n",
    "| 10001  | 10000   | 9999.5         | -0.5             |\n",
    "| –      | –       | 10000.0        | 0.0              |\n",
    "| –      | –       | 10001.0        | 1.0              |\n",
    "| –      | –       | 10003.2        | 3.2              |\n",
    "\n",
    "Residual scaling works the same way with `res_ref`, though there is no offset for residuals. In explicit components, `res_ref` defaults to `ref`.\n",
    "\n",
    "## Using scaling with units\n",
    "\n",
    "Now, we address the situation in which we use scaling in conjunction with units. Let us say we specify to add_output the `units` argument along with `ref` and `ref0`. Then, the values pass in for `ref` and `ref0` are assumed to be in the `units` given by the units argument. For instance, if `ref=10001`. and `units='Pa'`, then a scaled value of 1 represents `10001. Pa`.\n",
    "\n",
    "| `units` | `ref`  | `ref0`  | Physical value | Normalized value |\n",
    "| ------- | ------ | ------- | -------------- | ---------------- | \n",
    "| kPa     | 100    | –       | 0 kPa          | 0.0              |\n",
    "| –       | –      | –       | 100 kPa        | 1.0              |\n",
    "| –       | –      | –       | 250 kPa        | 2.5              |\n",
    "| Pa      | 100100 | 100000  | 99900 Pa       | -0.1             |\n",
    "| –       | –      | –       | 100000 Pa      | 0.0              |\n",
    "| –       | –      | –       | 100100 Pa      | 0.1              |\n",
    "\n",
    "```{note}\n",
    "residual scaling is separate and independent of output scaling in implicit components. In explicit components, the requested output scaling is applied to the residuals as well unless `res_ref` is also specified.\n",
    "```\n",
    "\n",
    "## Specifying a scaler on an output\n",
    "\n",
    "This example shows how to specify a scaler on outputs ‘y1’ and ‘y2’. The scaling used here assures that the outputs (which are states in this implicit component) are in the same order of magnitude when the solver interacts with them. Note that whenever a user function is called (like *apply_nonlinear* here), all outputs and residuals are reverted to unscaled dimensional form."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class ScalingExample1(om.ImplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x1', val=100.0)\n",
    "        self.add_input('x2', val=5000.0)\n",
    "        self.add_output('y1', val=200., ref=1e2)\n",
    "        self.add_output('y2', val=6000., ref=1e3)\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        x1 = inputs['x1']\n",
    "        x2 = inputs['x2']\n",
    "        y1 = outputs['y1']\n",
    "        y2 = outputs['y2']\n",
    "\n",
    "        residuals['y1'] = 1e5 * (x1 - y1)/y1\n",
    "        residuals['y2'] = 1e-5 * (x2 - y2)/y2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p1', om.IndepVarComp('x1', 1.0))\n",
    "model.add_subsystem('p2', om.IndepVarComp('x2', 1.0))\n",
    "comp = model.add_subsystem('comp', ScalingExample1())\n",
    "model.connect('p1.x1', 'comp.x1')\n",
    "model.connect('p2.x2', 'comp.x2')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "model.run_apply_nonlinear()\n",
    "\n",
    "with model._scaled_context_all():\n",
    "    val = model.comp._outputs['y1']\n",
    "    assert_near_equal(val, 2.0)\n",
    "    val = model.comp._outputs['y2']\n",
    "    assert_near_equal(val, 6.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Specifying a scaler and offset on an output\n",
    "\n",
    "This example shows how to specify a scaler and an offset on outputs ‘y1’ and ‘y2’."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ScalingExample2(om.ImplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x1', val=100.0)\n",
    "        self.add_input('x2', val=5000.0)\n",
    "        self.add_output('y1', val=200., ref=300.0, ref0=100.0)\n",
    "        self.add_output('y2', val=6000., ref=11000.0, ref0=1000.0)\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        x1 = inputs['x1']\n",
    "        x2 = inputs['x2']\n",
    "        y1 = outputs['y1']\n",
    "        y2 = outputs['y2']\n",
    "\n",
    "        residuals['y1'] = 1e5 * (x1 - y1)/y1\n",
    "        residuals['y2'] = 1e-5 * (x2 - y2)/y2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p1', om.IndepVarComp('x1', 1.0))\n",
    "model.add_subsystem('p2', om.IndepVarComp('x2', 1.0))\n",
    "comp = model.add_subsystem('comp', ScalingExample2())\n",
    "model.connect('p1.x1', 'comp.x1')\n",
    "model.connect('p2.x2', 'comp.x2')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "model.run_apply_nonlinear()\n",
    "\n",
    "with model._scaled_context_all():\n",
    "    val = model.comp._outputs['y1']\n",
    "    assert_near_equal(val, 0.5)\n",
    "    val = model.comp._outputs['y2']\n",
    "    assert_near_equal(val, 0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Specifying a scaler on a residual\n",
    "\n",
    "This example shows how to specify a scaler on the residuals for variables ‘y1’ and ‘y2’. This choice of scaler values assures that the residuals are of the same order of magnitude when the solver interacts with them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ScalingExample3(om.ImplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x1', val=100.0)\n",
    "        self.add_input('x2', val=5000.0)\n",
    "        self.add_output('y1', val=200., ref=1e2, res_ref=1e5)\n",
    "        self.add_output('y2', val=6000., ref=1e3, res_ref=1e-5)\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        x1 = inputs['x1']\n",
    "        x2 = inputs['x2']\n",
    "        y1 = outputs['y1']\n",
    "        y2 = outputs['y2']\n",
    "\n",
    "        residuals['y1'] = 1e5 * (x1 - y1)/y1\n",
    "        residuals['y2'] = 1e-5 * (x2 - y2)/y2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p1', om.IndepVarComp('x1', 1.0))\n",
    "model.add_subsystem('p2', om.IndepVarComp('x2', 1.0))\n",
    "comp = model.add_subsystem('comp', ScalingExample3())\n",
    "model.connect('p1.x1', 'comp.x1')\n",
    "model.connect('p2.x2', 'comp.x2')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "model.run_apply_nonlinear()\n",
    "\n",
    "with model._scaled_context_all():\n",
    "    val = model.comp._residuals['y1']\n",
    "    assert_near_equal(val, -.995)\n",
    "    val = model.comp._residuals['y2']\n",
    "    assert_near_equal(val, (1-6000.)/6000.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Specifying a vector of scalers\n",
    "\n",
    "When you have a vector output, you can also specify a vector scaling factor with individually selected elements. For this, the `ref`, `ref0` or `res_ref` must have the same shape as the variable value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ScalingExampleVector(om.ImplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=np.array([100., 5000.]))\n",
    "        self.add_output('y', val=np.array([200., 6000.]),\n",
    "                        ref=np.array([1e2, 1e3]),\n",
    "                        res_ref=np.array([1e5, 1e-5]))\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        x = inputs['x']\n",
    "        y = outputs['y']\n",
    "\n",
    "        residuals['y'][0] = 1e5 * (x[0] - y[0])/y[0]\n",
    "        residuals['y'][1] = 1e-5 * (x[1] - y[1])/y[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p', om.IndepVarComp('x', np.ones((2))))\n",
    "comp = model.add_subsystem('comp', ScalingExampleVector())\n",
    "model.connect('p.x', 'comp.x')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "model.run_apply_nonlinear()\n",
    "\n",
    "with model._scaled_context_all():\n",
    "    val = model.comp._residuals['y']\n",
    "    assert_near_equal(val[0], (1-200.)/200.)\n",
    "    assert_near_equal(val[1], (1-6000.)/6000.)\n",
    "    val = model.comp._outputs['y']\n",
    "    assert_near_equal(val[0], 2.0)\n",
    "    assert_near_equal(val[1], 6.0)"
   ]
  }
 ],
 "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"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
