{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "jupyter": {
     "source_hidden": true
    },
    "tags": [
     "remove-cell"
    ]
   },
   "outputs": [],
   "source": [
    "%config Completer.use_jedi = False\n",
    "%config InlineBackend.figure_formats = ['svg']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python `operator` library"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See Python's built-in [`operator`](https://docs.python.org/3/library/operator.html) library"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What we have now"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Build test model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "keep_output"
    ]
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Propagating quantum numbers: 100%|██████████| 24/24 [00:00<00:00, 102.35it/s]\n"
     ]
    }
   ],
   "source": [
    "import expertsystem as es\n",
    "\n",
    "result = es.generate_transitions(\n",
    "    initial_state=[(\"J/psi(1S)\", [-1, 1])],\n",
    "    final_state=[\"p\", \"p~\", \"eta\"],\n",
    "    allowed_intermediate_particles=[\"N(1440)\"],\n",
    "    allowed_interaction_types=\"strong\",\n",
    ")\n",
    "model = es.generate_amplitudes(result)\n",
    "for particle in result.get_intermediate_particles():\n",
    "    model.dynamics.set_breit_wigner(particle.name)\n",
    "es.io.write(model, \"recipe.yml\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Visualize the decay:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "keep_output"
    ]
   },
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 2.40.1 (20161225.0304)\n",
       " -->\n",
       "<!-- Title: %3 Pages: 1 -->\n",
       "<svg width=\"274pt\" height=\"314pt\"\n",
       " viewBox=\"0.00 0.00 274.00 314.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 310)\">\n",
       "<title>%3</title>\n",
       "<polygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-310 270,-310 270,4 -4,4\"/>\n",
       "<!-- g0_edge0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>g0_edge0</title>\n",
       "<text text-anchor=\"middle\" x=\"32.5\" y=\"-107.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">J/psi(1S)</text>\n",
       "</g>\n",
       "<!-- g0_node0 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>g0_node0</title>\n",
       "<ellipse fill=\"#000000\" stroke=\"#000000\" cx=\"102\" cy=\"-111\" rx=\"0\" ry=\"0\"/>\n",
       "</g>\n",
       "<!-- g0_edge0&#45;&gt;g0_node0 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>g0_edge0&#45;&gt;g0_node0</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M65.2149,-111C82.6777,-111 100.8283,-111 101.9456,-111\"/>\n",
       "</g>\n",
       "<!-- g0_edge2 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>g0_edge2</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">p</text>\n",
       "</g>\n",
       "<!-- g0_edge3 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>g0_edge3</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-122.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">p~</text>\n",
       "</g>\n",
       "<!-- g0_edge4 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>g0_edge4</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">eta</text>\n",
       "</g>\n",
       "<!-- g0_node0&#45;&gt;g0_edge3 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>g0_node0&#45;&gt;g0_edge3</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M102.1073,-111.0109C105.6698,-111.3719 197.022,-120.6306 234.3408,-124.4129\"/>\n",
       "</g>\n",
       "<!-- g0_node1 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>g0_node1</title>\n",
       "<ellipse fill=\"#000000\" stroke=\"#000000\" cx=\"196.5\" cy=\"-72\" rx=\"0\" ry=\"0\"/>\n",
       "</g>\n",
       "<!-- g0_node0&#45;&gt;g0_node1 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>g0_node0&#45;&gt;g0_node1</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M102.1038,-110.895C103.1309,-109.862 111.6096,-101.4586 120,-97 150.5996,-80.7394 193.7059,-72.5218 196.3702,-72.0241\"/>\n",
       "<text text-anchor=\"middle\" x=\"149\" y=\"-100.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">N(1440)+</text>\n",
       "</g>\n",
       "<!-- g0_node1&#45;&gt;g0_edge2 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>g0_node1&#45;&gt;g0_edge2</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M196.6535,-72C198.8109,-72 223.2164,-72 238.2592,-72\"/>\n",
       "</g>\n",
       "<!-- g0_node1&#45;&gt;g0_edge4 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>g0_node1&#45;&gt;g0_edge4</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M196.6535,-71.8451C198.6147,-69.8655 218.9629,-49.3272 233.9482,-34.2018\"/>\n",
       "</g>\n",
       "<!-- g1_edge0 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>g1_edge0</title>\n",
       "<text text-anchor=\"middle\" x=\"32.5\" y=\"-257.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">J/psi(1S)</text>\n",
       "</g>\n",
       "<!-- g1_node0 -->\n",
       "<g id=\"node11\" class=\"node\">\n",
       "<title>g1_node0</title>\n",
       "<ellipse fill=\"#000000\" stroke=\"#000000\" cx=\"102\" cy=\"-261\" rx=\"0\" ry=\"0\"/>\n",
       "</g>\n",
       "<!-- g1_edge0&#45;&gt;g1_node0 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>g1_edge0&#45;&gt;g1_node0</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M65.2149,-261C82.6777,-261 100.8283,-261 101.9456,-261\"/>\n",
       "</g>\n",
       "<!-- g1_edge2 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>g1_edge2</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-284.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">p</text>\n",
       "</g>\n",
       "<!-- g1_edge3 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>g1_edge3</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">p~</text>\n",
       "</g>\n",
       "<!-- g1_edge4 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>g1_edge4</title>\n",
       "<text text-anchor=\"middle\" x=\"250\" y=\"-176.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">eta</text>\n",
       "</g>\n",
       "<!-- g1_node0&#45;&gt;g1_edge2 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>g1_node0&#45;&gt;g1_edge2</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M102.1009,-261.1072C103.1,-262.1599 111.3625,-270.6857 120,-274 161.327,-289.8574 215.1229,-289.6801 238.4136,-288.6952\"/>\n",
       "</g>\n",
       "<!-- g1_node1 -->\n",
       "<g id=\"node12\" class=\"node\">\n",
       "<title>g1_node1</title>\n",
       "<ellipse fill=\"#000000\" stroke=\"#000000\" cx=\"196.5\" cy=\"-234\" rx=\"0\" ry=\"0\"/>\n",
       "</g>\n",
       "<!-- g1_node0&#45;&gt;g1_node1 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>g1_node0&#45;&gt;g1_node1</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M102.0685,-260.9804C104.8853,-260.1756 193.6601,-234.8114 196.4336,-234.019\"/>\n",
       "<text text-anchor=\"middle\" x=\"149\" y=\"-258.8\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">N(1440)~&#45;</text>\n",
       "</g>\n",
       "<!-- g1_node1&#45;&gt;g1_edge3 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>g1_node1&#45;&gt;g1_edge3</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M196.6535,-234C198.627,-234 219.218,-234 234.2286,-234\"/>\n",
       "</g>\n",
       "<!-- g1_node1&#45;&gt;g1_edge4 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>g1_node1&#45;&gt;g1_edge4</title>\n",
       "<path fill=\"none\" stroke=\"#000000\" d=\"M196.6535,-233.8451C198.6147,-231.8655 218.9629,-211.3272 233.9482,-196.2018\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.files.Source at 0x7fe9e17a0670>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import graphviz\n",
    "\n",
    "graphs = result.collapse_graphs()\n",
    "dot = es.io.asdot(graphs)\n",
    "graphviz.Source(dot)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "keep_output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FitParameters([\n",
       "    FitParameter(name='Magnitude_J/psi(1S)_to_N(1440)+_0.5+p~_-0.5;N(1440)+_to_eta_0+p_0.5;', value=1.0, fix=False),\n",
       "    FitParameter(name='Magnitude_J/psi(1S)_to_N(1440)+_0.5+p~_0.5;N(1440)+_to_eta_0+p_0.5;', value=1.0, fix=False),\n",
       "    FitParameter(name='Magnitude_J/psi(1S)_to_N(1440)~-_0.5+p_-0.5;N(1440)~-_to_eta_0+p~_0.5;', value=1.0, fix=False),\n",
       "    FitParameter(name='Magnitude_J/psi(1S)_to_N(1440)~-_0.5+p_0.5;N(1440)~-_to_eta_0+p~_0.5;', value=1.0, fix=False),\n",
       "    FitParameter(name='MesonRadius_J/psi(1S)', value=1.0, fix=True),\n",
       "    FitParameter(name='MesonRadius_N(1440)+', value=1.0, fix=True),\n",
       "    FitParameter(name='MesonRadius_N(1440)~-', value=1.0, fix=True),\n",
       "    FitParameter(name='Phase_J/psi(1S)_to_N(1440)+_0.5+p~_-0.5;N(1440)+_to_eta_0+p_0.5;', value=0.0, fix=False),\n",
       "    FitParameter(name='Phase_J/psi(1S)_to_N(1440)+_0.5+p~_0.5;N(1440)+_to_eta_0+p_0.5;', value=0.0, fix=False),\n",
       "    FitParameter(name='Phase_J/psi(1S)_to_N(1440)~-_0.5+p_-0.5;N(1440)~-_to_eta_0+p~_0.5;', value=0.0, fix=False),\n",
       "    FitParameter(name='Phase_J/psi(1S)_to_N(1440)~-_0.5+p_0.5;N(1440)~-_to_eta_0+p~_0.5;', value=0.0, fix=False),\n",
       "    FitParameter(name='Position_N(1440)+', value=1.44, fix=False),\n",
       "    FitParameter(name='Position_N(1440)~-', value=1.44, fix=False),\n",
       "    FitParameter(name='Width_N(1440)+', value=0.35, fix=False),\n",
       "    FitParameter(name='Width_N(1440)~-', value=0.35, fix=False),\n",
       "])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.parameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementation with `operators`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See [this answer](https://stackoverflow.com/a/7844038) on Stack Overflow:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "keep_output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import operator\n",
    "\n",
    "MAKE_BINARY = lambda opfn: lambda self, other: BinaryOp(  # noqa: E731\n",
    "    self, asMagicNumber(other), opfn\n",
    ")\n",
    "MAKE_RBINARY = lambda opfn: lambda self, other: BinaryOp(  # noqa: E731\n",
    "    asMagicNumber(other), self, opfn\n",
    ")\n",
    "\n",
    "\n",
    "class MagicNumber(object):\n",
    "    __add__ = MAKE_BINARY(operator.add)\n",
    "    __sub__ = MAKE_BINARY(operator.sub)\n",
    "    __mul__ = MAKE_BINARY(operator.mul)\n",
    "    __radd__ = MAKE_RBINARY(operator.add)\n",
    "    __rsub__ = MAKE_RBINARY(operator.sub)\n",
    "    __rmul__ = MAKE_RBINARY(operator.mul)\n",
    "    # __div__  = MAKE_BINARY(operator.div)\n",
    "    # __rdiv__ = MAKE_RBINARY(operator.div)\n",
    "    __truediv__ = MAKE_BINARY(operator.truediv)\n",
    "    __rtruediv__ = MAKE_RBINARY(operator.truediv)\n",
    "    __floordiv__ = MAKE_BINARY(operator.floordiv)\n",
    "    __rfloordiv__ = MAKE_RBINARY(operator.floordiv)\n",
    "\n",
    "    def __neg__(self, other):\n",
    "        return UnaryOp(self, lambda x: -x)\n",
    "\n",
    "    @property\n",
    "    def value(self):\n",
    "        return self.eval()\n",
    "\n",
    "\n",
    "class Constant(MagicNumber):\n",
    "    def __init__(self, value):\n",
    "        self.value_ = value\n",
    "\n",
    "    def eval(self):\n",
    "        return self.value_\n",
    "\n",
    "\n",
    "class Parameter(Constant):\n",
    "    def __init__(self):\n",
    "        super(Parameter, self).__init__(0.0)\n",
    "\n",
    "    def setValue(self, v):\n",
    "        self.value_ = v\n",
    "\n",
    "    value = property(fset=setValue, fget=lambda self: self.value_)\n",
    "\n",
    "\n",
    "class BinaryOp(MagicNumber):\n",
    "    def __init__(self, op1, op2, operation):\n",
    "        self.op1 = op1\n",
    "        self.op2 = op2\n",
    "        self.opn = operation\n",
    "\n",
    "    def eval(self):\n",
    "        return self.opn(self.op1.eval(), self.op2.eval())\n",
    "\n",
    "\n",
    "class UnaryOp(MagicNumber):\n",
    "    def __init__(self, op1, operation):\n",
    "        self.op1 = op1\n",
    "        self.operation = operation\n",
    "\n",
    "    def eval(self):\n",
    "        return self.opn(self.op1.eval())\n",
    "\n",
    "\n",
    "asMagicNumber = (\n",
    "    lambda x: x if isinstance(x, MagicNumber) else Constant(x)  # noqa: E731\n",
    ")\n",
    "asMagicNumber(2).eval()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Other ideas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "````{dropdown} Option 1: parameter _container_\n",
    "Remove `name` from the `FitParameter` class and give the `FitParameters` collection class the responsibility to keep track of 'names' of the `FitParameter`s as keys in a `dict`. In the `AmplitudeModel`, locations where a `FitParameter` should be inserted are indicated by an immutable (!) `str` that should exist as a key in the `FitParameters`.\n",
    "\n",
    "Such a setup best reflects the structure of the `AmplitudeModel` that we have now (best illustrated by [`expected_recipe`](https://github.com/ComPWA/expertsystem/blob/f4f1c553780e263eb5b2a478951223694386f22a/tests/unit/io/expected_recipe.yml), note in particular YAML anchors like [`&par1`](https://github.com/ComPWA/expertsystem/blob/f4f1c553780e263eb5b2a478951223694386f22a/tests/unit/io/expected_recipe.yml#L11)/[`*par1`](https://github.com/ComPWA/expertsystem/blob/f4f1c553780e263eb5b2a478951223694386f22a/tests/unit/io/expected_recipe.yml#L59)). It also allows one to couple `FitParameters`. See following snippet:\n",
    "\n",
    "```{literalinclude} ./parameter_container.py\n",
    "\n",
    "```\n",
    "````"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "````{dropdown} Option 2: read-only parameter _manager_\n",
    "Remove the `FitParameters` collection class altogether and use something like immutable `InitialParameter` instances in the dynamics and intensity section of the `AmplitudeModel`. The `AmplitudeModel` then starts to serve as a read-only' template. A fitter package like `tensorwaves` can then loop over the `AmplitudeModel` structure to extract the `InitialParameter` instances and convert them to something like an `FitParameter`.\n",
    "\n",
    "Here's a rough sketch with `tensorwaves` in mind.\n",
    "```python\n",
    "from typing import Dict, Generator, List\n",
    "\n",
    "import attr\n",
    "\n",
    "from expertsystem.amplitude.model import (\n",
    "    AmplitudeModel,\n",
    "    Dynamics,\n",
    "    Node,\n",
    "    ParticleDynamics,\n",
    ")\n",
    "from expertsystem.reaction.particle import Particle\n",
    "\n",
    "\n",
    "@attr.s\n",
    "class InitialParameter:\n",
    "    name: str = attr.ib()\n",
    "    value: float = attr.ib()\n",
    "    # fix: bool = attr.ib(default=False)\n",
    "\n",
    "\n",
    "@attr.s\n",
    "class FitParameter:\n",
    "    name: str = attr.ib(on_setattr=attr.setters.frozen)\n",
    "    value: float = attr.ib()\n",
    "    fix: bool = attr.ib(default=False)\n",
    "\n",
    "\n",
    "class FitParameterManager:\n",
    "    \"\"\"Manages all fit parameters of the model\"\"\"\n",
    "\n",
    "    def __init__(self, model: AmplitudeModel) -> None:\n",
    "        self.__model: AmplitudeModel\n",
    "        self.__parameter_couplings: Dict[str, str]\n",
    "\n",
    "    @property\n",
    "    def parameters(self) -> List[FitParameter]:\n",
    "        initial_parameters = list(__yield_parameter(self.__model))\n",
    "        self.__apply_couplings()\n",
    "        return self.__convert(initial_parameters)\n",
    "\n",
    "    def couple_parameters(self, parameter1: str, parameter2: str) -> None:\n",
    "        pass\n",
    "\n",
    "    def __convert(self, params: List[InitialParameter]) -> List[FitParameter]:\n",
    "        pass\n",
    "\n",
    "\n",
    "@attr.s\n",
    "class CustomDynamics(Dynamics):\n",
    "    parameter: InitialParameter = attr.ib(kw_only=True)\n",
    "\n",
    "    @staticmethod\n",
    "    def from_particle(particle: Particle):\n",
    "        pass\n",
    "\n",
    "\n",
    "def __yield_parameter(\n",
    "    instance: object,\n",
    ") -> Generator[InitialParameter, None, None]:\n",
    "    if isinstance(instance, InitialParameter):\n",
    "        yield instance\n",
    "    elif isinstance(instance, (ParticleDynamics, Node)):\n",
    "        for item in instance.values():\n",
    "            yield from __yield_parameter(item)\n",
    "    elif isinstance(instance, (list,)):\n",
    "        for item in instance:\n",
    "            yield from __yield_parameter(item)\n",
    "    elif attr.has(instance.__class__):\n",
    "        for field in attr.fields(instance.__class__):\n",
    "            field_value = getattr(instance, field.name)\n",
    "            yield from __yield_parameter(field_value)\n",
    "\n",
    "\n",
    "# usage in tensorwaves\n",
    "amp_model = AmplitudeModel()\n",
    "kinematics: HelicityKinematics = ...\n",
    "builder = IntensityBuilder(kinematics)\n",
    "\n",
    "intensity = builder.create(amp_model)  # this would call amp_model.parameters\n",
    "parameters: Dict[str, float] = intensity.parameters\n",
    "# PROBLEM?: fix status is lost at this point\n",
    "\n",
    "data_sample = generate_data(...)\n",
    "dataset = kinematics.convert(data_sample)\n",
    "\n",
    "parameters[\"Width_f(0)(980)\"] = 0.2  # name is immutable at this point\n",
    "\n",
    "# name of a parameter can be changed in the AmplitudeModel though\n",
    "# and then call builder again\n",
    "intensity(dataset, parameters)\n",
    "```\n",
    "````"
   ]
  }
 ],
 "metadata": {
  "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.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
