{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "%pylab inline\n",
    "from ipyparallel import Client, error\n",
    "cluster=Client(profile=\"mpi\")\n",
    "view=cluster[:]\n",
    "view.block=True\n",
    "\n",
    "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": [
    "```{note}\n",
    "This feature requires MPI, and may not be able to be run on Colab.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parallel Groups\n",
    "\n",
    "When systems are added to a `ParallelGroup`, they will be executed in parallel, assuming that the `ParallelGroup` is given an MPI communicator of sufficient size.  Adding subsystems to a ParallelGroup is no different than adding them to a normal Group.  For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.set_input_defaults('x', 1.)\n",
    "\n",
    "parallel = model.add_subsystem('parallel', om.ParallelGroup(), \n",
    "                               promotes_inputs=[('c1.x', 'x'), ('c2.x', 'x'), \n",
    "                                                ('c3.x', 'x'), ('c4.x', 'x')])\n",
    "parallel.add_subsystem('c1', om.ExecComp(['y=-2.0*x']))\n",
    "parallel.add_subsystem('c2', om.ExecComp(['y=5.0*x']))\n",
    "parallel.add_subsystem('c3', om.ExecComp(['y=-3.0*x']))\n",
    "parallel.add_subsystem('c4', om.ExecComp(['y=4.0*x']))\n",
    "\n",
    "model.add_subsystem('c5', om.ExecComp(['y=3.0*x1 + 7.0*x2 - 2.0*x3 + x4']))\n",
    "\n",
    "model.connect(\"parallel.c1.y\", \"c5.x1\")\n",
    "model.connect(\"parallel.c2.y\", \"c5.x2\")\n",
    "model.connect(\"parallel.c3.y\", \"c5.x3\")\n",
    "model.connect(\"parallel.c4.y\", \"c5.x4\")\n",
    "\n",
    "prob.setup(check=False, mode='fwd')\n",
    "prob.run_model()\n",
    "\n",
    "print(prob['c5.y'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "%%px\n",
    "\n",
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob['c5.y'], 39.0, 1e-6)"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "In this example, components *c1* through *c4* will be executed in parallel, provided that the `ParallelGroup` is given four MPI processes.  If the name of the python file containing our example were `my_par_model.py`, we could run it under MPI and give it two processes using the following command:\n",
    "\n",
    "```\n",
    "    mpirun -n 4 python my_par_model.py\n",
    "```\n",
    "\n",
    "```{Note}\n",
    "This will only work if you've installed the mpi4py and petsc4py python packages, which are not installed by default in OpenMDAO.\n",
    "```\n",
    "\n",
    "In the previous example, all four components in the `ParallelGroup` required just a single MPI process, but\n",
    "what happens if we want to add subsystems to a `ParallelGroup` that has other processor requirements?\n",
    "In OpenMDAO, we control process allocation behavior by setting the `min_procs` and/or `max_procs` or\n",
    "`proc_weight` args when we call the `add_subsystem` function to add a particular subsystem to\n",
    "a `ParallelGroup`.\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.group.Group.add_subsystem\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "\n",
    "If you use both `min_procs/max_procs` and `proc_weight`, it can become less obvious what the\n",
    "resulting process allocation will be, so you may want to stick to just using one or the other.\n",
    "The algorithm used for the allocation starts, assuming that the number of processes is greater than or\n",
    "equal to the number of subsystems, by assigning the `min_procs` for each subsystem.  It then adds\n",
    "any remaining processes to subsystems based on their weights, being careful not to exceed their\n",
    "specified `max_procs`, if any.\n",
    "\n",
    "If the number of processes is less than the number of subsystems, then each subsystem, one at a\n",
    "time, starting with the one with the highest `proc_weight`, is allocated to the least-loaded process.\n",
    "An exception will be raised if any of the subsystems in this case have a `min_procs` value greater than one."
   ]
  }
 ],
 "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
}
