{
 "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": [
    "# Specifying Units for Variables\n",
    "\n",
    "As we saw in [Declaring Continuous Variables](continuous_variables.ipynb), we can specify units for inputs, outputs, and residuals. There is a `units` argument to `add_input` to specify input units, and there are `units` and `res_units` arguments on `add_output` to specify output and residual units, respectively. A complete listing of all available units is given [here](../../units).\n",
    "\n",
    "```{note}\n",
    "Residual units, if not specified, default to the same units as the output variable. `res_units` is very rarely specified.\n",
    "```\n",
    "Specifying units has the following result:\n",
    "\n",
    "1. Unit conversions occur during data passing. For instance, let’s say we have a `TimeComp` component that outputs `time1` in hours and a `SpeedComp` component takes `time2` as an input but in seconds. If we connect `TimeComp.time1` to `SpeedComp.time2` with hours/seconds specified during the corresponding `add_output`/`add_input` calls, then OpenMDAO automatically converts from hours to seconds.\n",
    "\n",
    "\n",
    "2. The user always gets/sets the variable in the specified units. Declaring an input, output, or residual to have certain units means that any value ‘set’ into the variable is assumed to be in the given units and any time the user asks to ‘get’ the variable, the value is return in the given units. This is the case not only in \\<Component\\> methods such as `compute`, `apply_nonlinear`, and `apply_linear`, but everywhere, including the user’s run script.\n",
    "\n",
    "\n",
    "3. In `add_input` and `add_output`, all arguments are assumed to be given in the specified units. In the case of `add_input`, if `units` is specified, then `val` is assumed to be given in those units. In the case of `add_output`, if `units` is specified, then `val`, `lower`, `upper`, `ref`, and `ref0` are all assumed to be given in those units. Also in `add_output`, if `res_units` is specified, then `res_ref` is assumed to be given in `res_units`.\n",
    "\n",
    "## Units syntax\n",
    "\n",
    "Units are specified as a string that adheres to the following syntax. The string is a composition of numbers and known units that are combined with multiplication (\\*), division (\\/), and exponentiation (\\*\\*) operators. The known units can be prefixed by kilo (*k*), Mega (*M*), and so on. The list of units and valid prefixes can be found in the [units library](../../units).\n",
    "\n",
    "For example, each of the following is a valid unit string representing the same quantity:\n",
    "* N\n",
    "* 0.224809 \\* lbf\n",
    "* kg \\* m \\/ s \\*\\* 2\n",
    "* kg \\* m \\* s \\*\\* -2\n",
    "* kkg \\* mm \\/ s \\*\\* 2\n",
    "\n",
    "```{note}\n",
    "If units are not specified, or are specified as `None` then the variable is assumed to be unitless. If such a variable is connected to a variable with units, the connection will be allowed, but a warning will be issued.\n",
    "```\n",
    "## Example\n",
    "\n",
    "This example illustrates how we can compute speed from distance and time given in `km` and `h` using a component that computes speed using `m` and `s`.\n",
    "\n",
    "We first define the component."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class SpeedComp(om.ExplicitComponent):\n",
    "    \"\"\"Simple speed computation from distance and time with unit conversations.\"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('distance', val=1.0, units='km')\n",
    "        self.add_input('time', val=1.0, units='h')\n",
    "        self.add_output('speed', val=1.0, units='km/h')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['speed'] = inputs['distance'] / inputs['time']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the overall problem, the first component, `c1`, defines distance and time in `m` and `s`. OpenMDAO handles the unit conversions when passing these two variables into `c2`, our ‘SpeedComp’. There is a further unit conversion from `c2` to `c3` since speed must be converted now to `m/s`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_units import SpeedComp\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('c1', SpeedComp())\n",
    "prob.model.add_subsystem('c2', om.ExecComp('f=speed',speed={'units': 'm/s'}))\n",
    "\n",
    "prob.model.set_input_defaults('c1.distance', val=1., units='m')\n",
    "prob.model.set_input_defaults('c1.time', val=1., units='s')\n",
    "\n",
    "prob.model.connect('c1.speed', 'c2.speed')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('c1.distance'))  # units: km"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('c1.time'))   # units: h"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('c1.speed'))       # units: km/h"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('c2.f'))           # units: m/s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(prob.get_val('c1.distance'), 1.e-3)  # units: km\n",
    "assert_near_equal(prob.get_val('c1.time'), 1./3600.)   # units: h\n",
    "assert_near_equal(prob.get_val('c1.speed'), 3.6)       # units: km/h\n",
    "assert_near_equal(prob.get_val('c2.f'), 1.0)           # units: m/s"
   ]
  }
 ],
 "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.5"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
