{
 "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": [
    "# CrossProductComp\n",
    "\n",
    "`CrossProductComp` performs a cross product between two 3-vector inputs.  It may be vectorized to provide the result at one or more points simultaneously.\n",
    "\n",
    "$$\n",
    "    c_i = \\bar{a}_i \\times \\bar{b}_i\n",
    "$$\n",
    "\n",
    "The first dimension of the inputs holds the vectorized dimension.\n",
    "The default `vec_size` is 1, providing the cross product of $a$ and $b$ at a single\n",
    "point.  The lengths of $a$ and $b$ at each point must be 3.\n",
    "\n",
    "The shape of $a$ and $b$ will always be `(vec_size, 3)`, but the connection rules\n",
    "of OpenMDAO allow the incoming connection to have shape `(3,)` when `vec_size` is 1, since\n",
    "the storage order of the underlying data is the same.  The output vector `c` of\n",
    "CrossProductComp will always have shape `(vec_size, 3)`.\n",
    "\n",
    "## CrossProductComp Options\n",
    "\n",
    "Options for CrossProductComp 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.cross_product_comp.CrossProductComp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CrossProductComp Constructor\n",
    "\n",
    "The call signature for the `CrossProductComp` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.cross_product_comp.CrossProductComp.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## CrossProductComp 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 `CrossProductComp` 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.cross_product_comp.CrossProductComp.add_product\n",
    "       :noindex:\n",
    "```\n",
    "\n",
    "## CrossProductComp Example\n",
    "\n",
    "In the following example CrossProductComp is used to compute torque as the\n",
    "cross product of force ($F$) and radius ($r$) at 100 points simultaneously.\n",
    "Note the use of `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",
    "p.model.add_subsystem(name='cross_prod_comp',\n",
    "                      subsys=om.CrossProductComp(vec_size=n,\n",
    "                                                 a_name='r', b_name='F', c_name='torque',\n",
    "                                                 a_units='m', b_units='N', c_units='N*m'),\n",
    "                      promotes_inputs=['r', 'F'])\n",
    "\n",
    "p.setup()\n",
    "\n",
    "p.set_val('r', np.random.rand(n, 3))\n",
    "p.set_val('F', np.random.rand(n, 3))\n",
    "\n",
    "p.run_model()\n",
    "\n",
    "# Check the output in units of ft*lbf to ensure that our units work as expected.\n",
    "expected = []\n",
    "for i in range(n):\n",
    "    a_i = p.get_val('r')[i, :]\n",
    "    b_i = p.get_val('F')[i, :]\n",
    "    expected.append(np.cross(a_i, b_i) * 0.73756215)\n",
    "\n",
    "    actual_i = p.get_val('cross_prod_comp.torque', units='ft*lbf')[i]\n",
    "    rel_error = np.abs(expected[i] - actual_i)/actual_i\n",
    "    assert np.all(rel_error < 1e-8), f\"Relative error: {rel_error}\"\n",
    "\n",
    "print(p.get_val('cross_prod_comp.torque', units='ft*lbf'))"
   ]
  },
  {
   "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('cross_prod_comp.torque', units='ft*lbf'), np.array(expected), tolerance=1e-8)"
   ]
  },
  {
   "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",
    "cpc = om.CrossProductComp(vec_size=n,\n",
    "                          a_name='r', b_name='F', c_name='torque',\n",
    "                          a_units='m', b_units='N', c_units='N*m')\n",
    "\n",
    "cpc.add_product(vec_size=n,\n",
    "                a_name='r', b_name='p', c_name='L',\n",
    "                a_units='m', b_units='kg*m/s', c_units='kg*m**2/s')\n",
    "\n",
    "p.model.add_subsystem(name='cross_prod_comp', subsys=cpc,\n",
    "                      promotes_inputs=['r', 'F', 'p'])\n",
    "\n",
    "p.setup()\n",
    "\n",
    "p.set_val('r', np.random.rand(n, 3))\n",
    "p.set_val('F', np.random.rand(n, 3))\n",
    "p.set_val('p', np.random.rand(n, 3))\n",
    "\n",
    "p.run_model()\n",
    "\n",
    "# Check the output.\n",
    "expected_T = []\n",
    "expected_L = []\n",
    "for i in range(n):\n",
    "    a_i = p.get_val('r')[i, :]\n",
    "    b_i = p.get_val('F')[i, :]\n",
    "    expected_T.append(np.cross(a_i, b_i))\n",
    "\n",
    "    actual_i = p.get_val('cross_prod_comp.torque')[i]\n",
    "    rel_error = np.abs(expected_T[i] - actual_i)/actual_i\n",
    "    assert np.all(rel_error < 1e-8), f\"Relative error: {rel_error}\"\n",
    "\n",
    "    b_i = p.get_val('p')[i, :]\n",
    "    expected_L.append(np.cross(a_i, b_i))\n",
    "\n",
    "    actual_i = p.get_val('cross_prod_comp.L')[i]\n",
    "    rel_error = np.abs(expected_L[i] - actual_i)/actual_i\n",
    "    assert np.all(rel_error < 1e-8), f\"Relative error: {rel_error}\"\n",
    "\n",
    "print(p.get_val('cross_prod_comp.torque'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(p.get_val('cross_prod_comp.L'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(p.get_val('cross_prod_comp.torque'), np.array(expected_T), tolerance=1e-8)\n",
    "assert_near_equal(p.get_val('cross_prod_comp.L'), np.array(expected_L), tolerance=1e-8)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.3"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
