{
 "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": [
    "# DotProductComp\n",
    "\n",
    "`DotProductComp` performs a dot product between two compatible inputs.  It may be vectorized to provide the result at one or more points simultaneously.\n",
    "\n",
    "$$\n",
    "    c_i = \\bar{a}_i \\cdot \\bar{b}_i\n",
    "$$\n",
    "\n",
    "## DotProductComp Options\n",
    "\n",
    "The default `vec_size` is 1, providing the dot product of $a$ and $b$ at a single\n",
    "point.  The lengths of $a$ and $b$ are provided by option `length`.\n",
    "\n",
    "Other options for DotProductComp allow the user to rename the input variables $a$ and $b$ and the output $c$, as well as specifying their units."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "om.show_options_table(\"openmdao.components.dot_product_comp.DotProductComp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DotProductComp Constructor\n",
    "\n",
    "The call signature for the `DotProductComp` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.dot_product_comp.DotProductComp.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## DotProductComp Usage\n",
    "\n",
    "There are often situations when numerous products need to be computed, essentially in parallel.\n",
    "You can reduce the number of components required by having one `DotProductComp` perform multiple operations.\n",
    "This is also convenient when the different operations have common inputs.\n",
    "\n",
    "The ``add_product`` method is used to create additional products after instantiation.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.dot_product_comp.DotProductComp.add_product\n",
    "       :noindex:\n",
    "```\n",
    "\n",
    "## DotProductComp Example\n",
    "\n",
    "In the following example DotProductComp is used to compute instantaneous power as the\n",
    "dot product of force and velocity at 100 points simultaneously.  Note the use of\n",
    "`a_name`, `b_name`, and `c_name` to assign names to the inputs and outputs.\n",
    "Units are assigned using `a_units`, `b_units`, and `c_units`.\n",
    "Note that no internal checks are performed to ensure that `c_units` are consistent\n",
    "with `a_units` and `b_units`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "allow-assert"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "n = 24\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "dp_comp = om.DotProductComp(vec_size=n, length=3, a_name='F', b_name='v', c_name='P',\n",
    "                            a_units='N', b_units='m/s', c_units='W')\n",
    "\n",
    "p.model.add_subsystem(name='dot_prod_comp', subsys=dp_comp,\n",
    "                     promotes_inputs=[('F', 'force'), ('v', 'vel')])\n",
    "\n",
    "p.setup()\n",
    "\n",
    "p.set_val('force', np.random.rand(n, 3))\n",
    "p.set_val('vel', np.random.rand(n, 3))\n",
    "\n",
    "p.run_model()\n",
    "\n",
    "# Verify the results against numpy.dot in a for loop.\n",
    "expected = []\n",
    "for i in range(n):\n",
    "    a_i = p.get_val('force')[i, :]\n",
    "    b_i = p.get_val('vel')[i, :]\n",
    "    expected.append(np.dot(a_i, b_i))\n",
    "\n",
    "    actual_i = p.get_val('dot_prod_comp.P')[i]\n",
    "    rel_error = np.abs(expected[i] - actual_i)/actual_i\n",
    "    assert rel_error < 1e-9, f\"Relative error: {rel_error}\"\n",
    "\n",
    "print(p.get_val('dot_prod_comp.P', units='kW'))"
   ]
  },
  {
   "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(p.get_val('dot_prod_comp.P', units='kW'), np.array(expected)/1000.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DotProductComp Example with Multiple Products\n",
    "\n",
    "When defining multiple products:\n",
    "\n",
    "- An input name in one call to `add_product` may not be an output name in another call, and vice-versa.\n",
    "- The units and shape of variables used across multiple products must be the same in each one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "allow-assert"
    ]
   },
   "outputs": [],
   "source": [
    "n = 24\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "dp_comp = om.DotProductComp(vec_size=n, length=3,\n",
    "                            a_name='F', b_name='d', c_name='W',\n",
    "                            a_units='N', b_units='m', c_units='J')\n",
    "\n",
    "dp_comp.add_product(vec_size=n, length=3,\n",
    "                    a_name='F', b_name='v', c_name='P',\n",
    "                    a_units='N', b_units='m/s', c_units='W')\n",
    "\n",
    "p.model.add_subsystem(name='dot_prod_comp', subsys=dp_comp,\n",
    "                      promotes_inputs=[('F', 'force'), ('d', 'disp'), ('v', 'vel')])\n",
    "\n",
    "p.setup()\n",
    "\n",
    "p.set_val('force', np.random.rand(n, 3))\n",
    "p.set_val('disp', np.random.rand(n, 3))\n",
    "p.set_val('vel', np.random.rand(n, 3))\n",
    "\n",
    "p.run_model()\n",
    "\n",
    "# Verify the results against numpy.dot in a for loop.\n",
    "expected_P = []\n",
    "expected_W = []\n",
    "for i in range(n):\n",
    "    a_i = p.get_val('force')[i, :]\n",
    "\n",
    "    b_i = p.get_val('disp')[i, :]\n",
    "    expected_W.append(np.dot(a_i, b_i))\n",
    "\n",
    "    actual_i = p.get_val('dot_prod_comp.W')[i]\n",
    "    rel_error = np.abs(actual_i - expected_W[i])/actual_i\n",
    "    assert rel_error < 1e-9, f\"Relative error: {rel_error}\"\n",
    "\n",
    "    b_i = p.get_val('vel')[i, :]\n",
    "    expected_P.append(np.dot(a_i, b_i))\n",
    "\n",
    "    actual_i = p.get_val('dot_prod_comp.P')[i]\n",
    "    rel_error = np.abs(expected_P[i] - actual_i)/actual_i\n",
    "    assert rel_error < 1e-9, f\"Relative error: {rel_error}\"\n",
    "\n",
    "print(p.get_val('dot_prod_comp.W', units='kJ'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(p.get_val('dot_prod_comp.P', units='kW'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(p.get_val('dot_prod_comp.W', units='kJ'), np.array(expected_W)/1000.)\n",
    "assert_near_equal(p.get_val('dot_prod_comp.P', units='kW'), np.array(expected_P)/1000.)"
   ]
  }
 ],
 "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
}
