{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "84b95869",
   "metadata": {},
   "source": [
    "# `np.einsum`: tensor notation and performance\n",
    "\n",
    "`np.einsum` implements Einstein summation notation directly in NumPy.\n",
    "\n",
    "It is useful for:\n",
    "\n",
    "- inner products,\n",
    "- matrix products,\n",
    "- traces,\n",
    "- quadratic forms,\n",
    "- batched contractions,\n",
    "- higher-rank tensor contractions.\n",
    "\n",
    "The key performance lesson is:\n",
    "\n",
    "> `einsum` is not automatically fastest, but it can remove Python loops, avoid large temporary arrays, and choose efficient contraction paths.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "be667391",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NumPy version: 2.5.2\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import time\n",
    "\n",
    "print(\"NumPy version:\", np.__version__)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f96507af",
   "metadata": {},
   "source": [
    "## 1. Einstein notation\n",
    "\n",
    "A repeated index is summed.\n",
    "\n",
    "For example,\n",
    "\n",
    "$$\n",
    "c_i = A_{ij} b_j\n",
    "$$\n",
    "\n",
    "means\n",
    "\n",
    "$$\n",
    "c_i = \\sum_j A_{ij}b_j.\n",
    "$$\n",
    "\n",
    "In NumPy`einsum`:\n",
    "\n",
    "```python\n",
    "np.einsum(\"ij,j->i\", A, b)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "45ecc0c8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A @ b = [0.99604556 0.86165781 1.16776557 1.01609896]\n",
      "einsum = [0.99604556 0.86165781 1.16776557 1.01609896]\n"
     ]
    }
   ],
   "source": [
    "rng = np.random.default_rng(12345)\n",
    "\n",
    "A = rng.random((4, 4))\n",
    "b = rng.random(4)\n",
    "\n",
    "c1 = A @ b\n",
    "c2 = np.einsum(\"ij,j->i\", A, b)\n",
    "\n",
    "print(\"A @ b =\", c1)\n",
    "print(\"einsum =\", c2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b0a7956",
   "metadata": {},
   "source": [
    "## 2. Common examples\n",
    "\n",
    "$$\n",
    "a_i b_i\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"i,i->\"}\n",
    "$$\n",
    "\n",
    "$$\n",
    "C_{ij}=a_i b_j\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"i,j->ij\"}\n",
    "$$\n",
    "\n",
    "$$\n",
    "C_{ij}=A_{ik}B_{kj}\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"ik,kj->ij\"}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\mathrm{Tr}(A)=A_{ii}\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"ii->\"}\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "85509208",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "inner: 0.34361522435087055\n",
      "outer:\n",
      " [[0.06656292 0.01577177 0.01056397 0.00747935 0.0488399 ]\n",
      " [0.13043906 0.03090692 0.02070154 0.01465679 0.09570839]\n",
      " [0.27744571 0.06573946 0.04403246 0.0311752  0.20357309]\n",
      " [0.3794936  0.08991923 0.06022813 0.04264181 0.27844974]\n",
      " [0.21733999 0.05149769 0.03449328 0.02442142 0.15947111]]\n",
      "trace: 2.0412295648601306\n"
     ]
    }
   ],
   "source": [
    "a = rng.random(5)\n",
    "b = rng.random(5)\n",
    "M = rng.random((5,5))\n",
    "\n",
    "print(\"inner:\", np.einsum(\"i,i->\", a, b))\n",
    "print(\"outer:\\n\", np.einsum(\"i,j->ij\", a, b))\n",
    "print(\"trace:\", np.einsum(\"ii->\", M))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d718f275",
   "metadata": {},
   "source": [
    "## 3. Batched quadratic forms\n",
    "\n",
    "Suppose\n",
    "\n",
    "$$\n",
    "E_n = x_{ni} A_{ij} x_{nj}.\n",
    "$$\n",
    "\n",
    "This computes one quadratic form for each row $x_n$.\n",
    "\n",
    "With `einsum`:\n",
    "\n",
    "```python\n",
    "E = np.einsum(\"ni,ij,nj->n\", x, A, x)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6223cefa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(100000,)\n",
      "[0.29633843 0.58931788 0.32327379 0.71371592 0.55547289]\n"
     ]
    }
   ],
   "source": [
    "M = 100_000\n",
    "D = 3\n",
    "\n",
    "x = rng.random((M, D))\n",
    "A = rng.random((D, D))\n",
    "A = 0.5*(A + A.T)\n",
    "\n",
    "E = np.einsum(\"ni,ij,nj->n\", x, A, x)\n",
    "\n",
    "print(E.shape)\n",
    "print(E[:5])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2acd9a4",
   "metadata": {},
   "source": [
    "## 4. Three implementations of the same calculation\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "03242f4c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python vs matmul: 4.440892098500626e-16\n",
      "python vs einsum: 8.881784197001252e-16\n"
     ]
    }
   ],
   "source": [
    "def quadratic_python(x, A):\n",
    "    out = np.empty(len(x))\n",
    "    for n in range(len(x)):\n",
    "        out[n] = x[n] @ A @ x[n]\n",
    "    return out\n",
    "\n",
    "def quadratic_matmul(x, A):\n",
    "    y = x @ A\n",
    "    return np.sum(y*x, axis=1)\n",
    "\n",
    "def quadratic_einsum(x, A):\n",
    "    return np.einsum(\"ni,ij,nj->n\", x, A, x)\n",
    "\n",
    "xt = x[:1000]\n",
    "\n",
    "q1 = quadratic_python(xt, A)\n",
    "q2 = quadratic_matmul(xt, A)\n",
    "q3 = quadratic_einsum(xt, A)\n",
    "\n",
    "print(\"python vs matmul:\", np.max(np.abs(q1-q2)))\n",
    "print(\"python vs einsum:\", np.max(np.abs(q1-q3)))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f0800d2",
   "metadata": {},
   "source": [
    "## 5. Timing helper\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "aa81f861",
   "metadata": {},
   "outputs": [],
   "source": [
    "def median_time(func, nrep=5):\n",
    "    ts = []\n",
    "    for _ in range(nrep):\n",
    "        t0 = time.perf_counter()\n",
    "        result = func()\n",
    "        ts.append(time.perf_counter() - t0)\n",
    "    return np.median(ts), result\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a27f29f0",
   "metadata": {},
   "source": [
    "## 6. Python loop vs vectorized implementations\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "c860e077",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAGzCAYAAADDgXghAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvctoD+AAAAAlwSFlzAAAPYQAAD2EBqD+naQAAgUlJREFUeJzt3Qd4FNXXBvA3vRNKCCH0DqH3jiAgIEVBRcWuWBAUBAUpiiBSRFA/RfSPBRGxgFJEQIr03qX3EhLSIJDeNvs95w4bkpCQBbbv+/PZh8zsZHeyGXdPzj33XBe9Xq8HERERkRNytfYJEBEREVkLAyEiIiJyWgyEiIiIyGkxECIiIiKnxUCIiIiInBYDISIiInJaDISIiIjIablb+wRsXXZ2NiIjIxEQEAAXFxdrnw4REREZQdokJiYmIjQ0FK6uhed9GAgVQYKgChUqGPOaExERkY0JDw9H+fLlC72fgVARJBNkeCGLFStmsl9MZmYmVq9ejQceeAAeHh4me1xybryuiNcW2ZNMM34WJiQkqESG4XO8MAyEimAYDpMgyNSBkK+vr3pMBkLE64psHd+zyF6vq6LKWlgsTURERE6LgRARERE5LQZCRERE5LRYI0RERGZrP5KRkcFXl25bI+Tu7o60tDTodDrcCakpcnNzw71iIERERCYnAdC5c+dUMER0u14/ISEhamb23fTqK168uPr+e+nzx0CIiIhM/uF2+fJl9de6TF++XTM7cm7Z2dlISkqCv7//HV0nco2lpKQgJiZGbZctW/auz4GBEBERmVRWVpb6kJKOvjI1mqio4VNvb+87Dph9fHzUvxIMBQcH3/UwGcN0IiIyKUOth6enJ19ZMitDoC21RneLgRAREZkF12cke7jGGAgREdFt6bL12HnuKvbGuah/ZZuMk56erm7WIENOMhuLbo81QkREVKhVhy9jwl9Hcfm6fKC6Yd6pPSgb6I3xvcPQvd7dF6jaIgkcDNP9pd7EUINiLAl4pIhX6l0MXn31VVUzNX/+fFja8OHDcenSJSxZssTiz21PmBEiIqJCg6BB8/fdCIJuirqepvbL/eYkmaftZ65g6YEI9a+5M1ETJ05Ua17JdOySJUuq29NPP43o6Gijvn/o0KHqeLIvzAgREdEtJOiQTFBBoYfsk8oMub9rWAjcXO+9TuP2mSiNJTJRVatWxenTp9XXR48eRe/evfHiiy+qrIphgdDcJNsjmSBp7idfS6G4TAcXBWWUZJbU7WZHSUaqoCJzw/Cal5eXUY9TlMKe527ORX5mUzQ2tBZmhIiI6Ba7zl29JROUPxiS++U4R8tEGYSFheH111/HmjVrsGHDBtW8z9C3JvfwU9euXTFt2jT8+OOP+Ouvv1RGSW6bN29Wx1y/fh3PP/88goKCVCDVr18/JCQk5DyGBBJjxoxRGSgJnipXrqweKzcZYnv00Udv+zhFMeZ5dEaeyyOPPIIBAwao42Qo8Mknn0RycjLsEQOhQsyaNUv9T9C8eXPL/kaIiGxATGKayY5Tze8ysoy6JaZlYvyyI4VmosQHy46q44x5PHnueyFZIMm8dOrUCZUqVcJPP/2UJ2OyYMECvPTSS3jvvffUvw8//LDKCMmtY8eO6rjly5ejRYsWiIyMxMmTJ3HgwAFMnz4953FmzpyJb7/9Vh0nxc0ffvihykJt2bIlz7kU9ThFMeZ5Zhp5Ln///TeqVauGiIgIHDlyBPv378fIkSNhjzg0VojBgwerm0TbgYGBlv2tEBFZkQQP5+NSjDo2OOBmYXBhUjN1CHv/HxOcmRYMRSWkof4Hq406/ujEbvD1dL+jn12CGMmMSKDxxRdfoE+fPmo9LAkIfvjhB4wYMUIdK8NlEiw8/vjjt33M1q1bq8ySqFixojp++/bteYKPd955B23atFHbzzzzDP7880/MmDED7dq1M/pximLM88w08lwkKJwwYYIKEmvWrIlJkybhqaeeUoGZvTXRZEaIiIhyRFxLxcvz9uLTtSdv+6q43KjZaVGlpEO9erI+mgxrSaAhQ0ASbMyZM0fd98ILL+DEiRPYuXOn2v7+++/x2GOPqeUhbqdKlSp5tuWP62vXruUMm0VFRaFZs2Z5jpHRiOPHjxv9OEUx5nmu38G5NGrUKE+NUtOmTVWG7OzZs7A3zAgRERGydNmYu+08Zq45iZQMHdxdXdAlLBj/HNZmTOUeYDKURkvhsjGF0j4ebiozYwypOXr+h91FHjf3heZGBWHy3HdbLJ2fBEg9e/ZUAVC5cuVyaofupemfIZjIv/J6QQXI99I80Jjncb2DcynoGCGZM3tjf2dMREQm9d+laxj95yEcidQKb5tVKoHJ/eqjZpmAAmdvhdzh7C35ADd2eKp9jdIq0ySF0QVV97jceH45zhyz1YoycOBANQQkBctSI9O+ffuc+2SGVf4AoSgBAQEq+yRDXF26dMnZv23bNtSrV89k523M8wTcwbns27dP1U/JbDkh3yNDYvmzVvaAgRARkZOSguMZq09i3vbzkBY9xbzdMfrBOni8mawYrwUZEuzIFPntp2OwevNOPNC+JVpXDzZbECKPK0GWzA5zucdMlDn06NFD9RqSWWJSF5ObBAErVqzAxYsXc2ZdGWPs2LGq0Lh+/fqqGPqXX35R2abdu4vOjN0JY55nrJHnIgXbb7zxhpphdv78efXvkCFDcqbU2xMGQkRETmjV4Sh8sOyIKjwWDzUKxbieYSgdcOsHmQQdLauUxJVjevWvuYMQCb5mP93knjNRd0o+xP38/G57jAwRPfvssyoQkn9zkxoimV3VqlUrNdFGZl7J1HLpL5SbZI5yFxS/8sorquhaggmp0ZHiY5mG37hx45xjjHmcgn6e3MGYMc/zihHHCJk+L48tmaPExET0799fzTCzRy76e51b6OAMs8akiEz+CjAVSSnKXw4PPvhgTmqRiNcVWaIYevzSI1h7TKv9qVjSF5MerocONUub7D1LPkil6FgyJLmXm7ibpo5SMyRT9GV2mtQEWSsTlJvMpJIiZQkQnNHzzz9vsmVDpDGkfM7K5+vdNIi83bVm7Oc3M0JERE5aDP3qfVXxxv014H2HBcWWIkFP62qlYCtSU1NVv5yFCxeqoJAcAwMhIiInKIYes/gQDkdoxdBNK5XAlBvF0GQ8mTEmzQNHjRqF+++/32lfOu8ChunsGQMhIiIHlZSehU/+OXHbYmgy3r///suXC8DXX3/tUK8DAyEiIgf0z5EoVQtkTDE0kTNjIERE5EAipRh62RGsOXpnxdBEzoqBEBGRgxRD/7j9AmasPpFTDP1Kh6p4s7PtFkMT2QIGQkREdu7QpesYvfi/PMXQk/vWR60QFkMTFYWBEBGRHRdDSwbox203i6Hf7VEHTzRnMTSRsRgIERHZaTG0dIY2dF7u0zAU43rVUY0Hich4DISIiOy4GLpCSR9Merg+7mMxNOXy6aefIjY2FpMnT+brUgQGQkREdlQMPXP1CSTnKoaWztA+ng5aDJ2tAy5sA5KiAf8yQKU2gKt9/qyffPKJWurBUutxnTlzBpcuXbLIc9k7BkJERDbOKYuhjy4DVo0CEiJv7isWCnSfBoT1gb05ffo04uLiYMuBmo+PDwYPHgxnw0CIiMhGOW0xtARBv8vK7vnWBE+4rO3vP88swZAMI8nSEWXLlsW6detUBuexxx5Ti4zOmTNHLbIqK76//PLL6NGjR873zZs3D//73//U17LIZ5MmTfD222+rr4V879KlS9XCte3atVP7PvvsM6xevfqunk+W+ChXrhzefPPNnH2zZs1Si49KQHO3gZq/v7/Rx8fExGDmzJk4ePAgSpUqhQEDBqgFeY09P8NrXaZMGfzzzz9ISUlRK9gX9bObAwMhIiIbtFo6QztKMbReD2SmGD8ctnLkrUGQ9kAAXLRMUdWOxg2TefgCLsYFjSdPnsSvv/6KPn36qA/ko0ePYuDAgSrIqVatGl5//XW16GqvXr1w6NAhhIWFqe/r1KkTqlatqr6+evUqZs+ejY4dO2Lv3r1qRfVu3brh77//zjM0VqNGDXz55Zd39Xyy3ll6enqec5cg4/Dhw7DkumshISF44403kJSUhG+++QYeHh7o2rWrUedneK179+6NJ554AhcuXDDqZzcHBkJERDZWDC2zwVY7UjG0BEGTQ030YHptuGxqBeMOHxMJePoZ/egVKlTAL7/8Ajc3N5XhkExOfHw85s+fDxcXF7Vv0aJFKmNh+HCW75GbgQQ+wcHB2LRpkwqIKlasqIIGd3f3nIzQvTyftaWlpWHPnj3Yt28fGjdurPZJNkeyOndCfvYFCxYgOTkZxYoVw7Jly6zysztNICTRp6T9QkNN9T8jEZHp6LL1aghshjMVQ9sgGdaSoMRAPjMk2yMfzLn3RUVF5QkMvvvuO2zYsEENGel0OjUMJgXLEgiZ+vlMQQKu6dOn52zLucp57NixI2ff+++/jwceeKDA1edbtGiBQYMGYejQoepnlOE9X1/fOzoHa/3sThcISTpO0m7Xrl1TUwn79u2rLlgiIlsqhh6z+BAORVx3zGJoGZ6SzIwxZJbYz48WfdxTi7RZZMY89x2Q4Z3c5EO5oH16Ge674aWXXsJ///2H4cOHq+yPl5cXHn/8caSmpprl+UyhZcuWmDp16m2LpWvVqlXo90tNkwxjzZ07V9XxNGvWDD/++CMqVapk9DlY62d3ukAoPDwcX331FZo2bapSbjL2OGHCBJQvX97ap0ZETk6KoWeuPom52845djG0/IVv7PBUtfu12WFSGF1gnZCLdr8cZwNT6bOzs/Hnn3+qYR2pjxESAF25ciXPcVIrZCqSeckfZEVHa0OpxpKhOrkZyHCUjJrkH7orjBwrgZ/cEhMT1c8+ceJElWgwxflZktUDIUkfSoru+PHjeOaZZwqMJuUFlItMXuy2bduqSNZA9klhVkFatWqFDh065GxLurJ48eKqSp2IyJocqhjalCS4kSnyataYS75g6EZw2H2qTQRBhgBHggIp6JVgQAIjmTWVv1hYaoak+NcU6tevr7IvUqRseO7FixcbHcTcq+joaCxcuBCvvvqqyuD4+fmpbJJhmMva52dXgZAUiMkFU7t2baxZs0a9SPkDISnI6tKliwp+JOX4wQcfqCr1jz76KGfoa9iwYQU+/tatW3N+MVLJL6lKmeaYP/VGRGQpDlkMbWoyNV6myBfYR2iqzfURki7Or7zyihomkkyQzAiT6eO5SYmGTCGXot+SJUuq6fN3a8iQIViyZAmqVKmiamgkoSBJAksJDAzEiRMnVFJBziEyMlKdh9QU2cL53SkXvbkH325DAhUpjJJMjVSPr1+//pbCMhnSkotKptmJ5cuXq6mG0rtAok5jyLS8p556SvU8kAKvO5GQkKB+6TLtUaraTUUujBUrVqiqeAZmxOvK8dl7MfSdvGdJ8bD8kSofhFJYay+dpU+dOqXqUWrWrJmzTz7w5Q/q6tWr5+yTae6SAZGfz0DqUKUXj/TUkf0yo0oKiOVmIMNF8hzyuSKfX1JYfbfPJ5kneSzJSEnJx8WLF9Xsq7p166r7z549q7JSderUMepnP3OjWLpy5cpGv14yS0zOt0SJErd8X1HnZ3it5eeU10M+X2WfMT+7sdeasZ/fVs0IGSLEwtqAyw8nF1Pugi7pXSAX1h9//GFUICTDZvI/rkSq8ouRinj5PknlFUQunNwpTXkhDW8CcjMVw2OZ8jGJeF3ZJukI/d6yozgcqb2fNKlYHB/2qYOaZaQYOhuZmdlwpGtLjpEPOXnPldvdcwEq5csk3NPj3Z58YGtPcfM55A/x/PtkFCP/PvmglVlQhv2NGjW65Rgpoq5Xr17OdkBAwF0/X+5jhYyY5D7GEJgY+/pXuRFo3MnvSwKPhg0bFvp9tzs/w2ttyMXIv3fysxvIfvleueZyz0C7k89Xq9cI3Y7UDYnc0bJUkEu0aLivKMeOHVOBk3SqNJD0peHFzW/KlCmqmDo/6QB6p1MDjSFDgkS8rhxTmg5YEe6KTZddoIcLfNz06FMpG62C43B672achv0x5j1L+uVIIa7UiGRkZFjkvMi+JSYm3tX3yfUlmTbp2SSdqnMztq+RTQdC8j+RMLQpN5CCZ8N9RXn00UfVzVijR49WVfC5M0IybCe9FEw9NCZvKFJcx6Ex4nXleNYei8GU5ccQlaBlmHvVD8GYHrVQOsAL9uhO3rNkuEJm7Eqh7D0NjZHD0+v1KgiS7Fju/kHGkmtNhs5kYlRBQ2N2HwgZhq/kh5Hgx0DG+8w180tSl3LLT/7HN0fAYq7HJefG68p6HL0Y2phrS+o+5UNN6kNMOW2cHE/2jSEvw/Vyp+R7DP2H8l+Xxn622nQgZGjmJAVohvFFQ1FX7mnxRETWZu/F0ETOyqYDISmmatCgAX744Qfcf//9ap+sUivF1dIhmojIFjh8Z2giB2bVQEhakkujRMM43k8//YQtW7aobI8h4/P111+r+pyHHnpIZYWk+6XU8BgWejMX6fcgN0nxEhEVJDk9CzNydYYOUJ2ha+PJ5hUdqzM0kQOzaiAkQYYUOnl6emLs2LFqn2znrvxu3bq1mvklLcyloEq6Uxa1iJ0pyHorcjP0ISAiyt8ZWmqBIm90hu7dMBTvsTM0kd2xaiAkWR1jMjuyLtibb75pkXMiIrqdy9dTMX5p3mLoDx+qh461gvnCEdkhm64RIiKypWLoedvP45N/bhZDv9yhKt5kMbRTkTpVGSl47LHHrH0qZCIMhIiIinA44jpG/3mzGFo6Q0/uVx+1Q0zXW4zsw19//aUm7DAQchwMhArBYmkikmLomWtO4oetLIa2Bl22Dvti9iE2JRalfUujSXATuFl51fnu3bsb3aiP7AMDoUKwWJrIua05Go3xSw+zGNpK1l5Yi6m7piI6RavFEmV8y+DdFu+iS6UuZnteWbLh77//Vv3qZFWBHj165FlVIH/jPskQSVNAWWfs33//VaseSPft3EtDiQ0bNqjFwmVR1m7duqF0aa3B5m+//aZWo5fvMVi3bp1akPXJJ5/M8xyyTqYsJSFNhXv16qVazEifvVWrVqlJRzK72lzNhh0ZW34SEeUrhn71pz14ed4eFQRJMfTcF5rjiycbIziAy0VYKggavmF4niBIxKTEqP1yvzlcvnxZTeD55JNPEBkZqdalDAsLw8mTJ3OOkaDk559/ztmWBcBHjBih2rzs3r1bBTGyEOn27dtzjnnppZfwzDPPqOBKgiWZ+SztYwxtYyTwym3lypX48ccf8zzHsGHD1HPIQuTLly9XQZEsJi4Lkcv5LViwQC30Gh8fb5bXxpExI0RExGJos68nlZqVavRw2JRdU6CH/tbHubFPMkUtQ1oaNUzm4+5j9BpWb7zxBpo3b64CIINBgwapQEcCoMJcvXoVJ06cyMnyPPLII/jiiy9U+5f09HQV1GzevFltCwlWJKtzJ+R4CXiCgoLUdp06dfDNN9+o55UlqCRjJCvIS0AkIxpkPAZCROQ0s752nbuKmMQ0ldlpUaUk3G40PZRiaOkM/d8lFkObgwRBLRe0NNnjSaaoza9tjDp254Cd8PXwLfI4CVgk2HniiScwdepUFbzJ7dq1a9ixY8dtv1caABuCICFZJUOWR4asJHiRITAZLpOhsRIlSqjbnZDnMARBom7dunB3d89Zh1PW3JLg6Pz583f0uMRAiIicwKrDlzHhr6O4fKP5oSgb6I1R3WurmWAshqa4uDhVHyTZI6nzMZA6nPz1Pvn5+/vn2ZYAJTMzU30tjycBljQNlpojeSwJtt56660CF/i+k+e43fOS8ZgRKgRnjRE5ThA0aP6+WwZaJCga9tuBnG12hjYfGZ6SzIwx9kbvxevrXi/yuK86f4WmZZoa9dzGkAyNm5sbOnXqhOeeew6mJMNtq1evVisnSI3Qq6++iuTkZHz44YcqeMm9moLIHYiR+bFYuhAyxnr06FFV/EZE9jscJpmgW6tNbnJzAb5/rhmLoc1IsiIyPGXMrU1oGzU7zAUF1/XI/hDfEHWcMY9nbH2Qr6+vmhr/2WefITX1Zj2T1N7s3bv3rn92mWp//Phx9bW3tzcefPBBtG/fXs32EpUrV84pnBby3BI0keUwI0REDktqgnIPhxVEpwd8PPlWaCukAFqmyMvsMAl6chdNG4KjUS1GmaWfkCzyLdPYGzRogN69e6sMjhQ5y1BW06ZFZ58KW1NTmi9KIbPM9JJmjEuWLFEzv8Qrr7yiMkYPP/wwatWqpabCS5aILIevNhE5LCmMNuVxZBnSJ2hmx5kF9hGSIMhcfYRkXUvp9SNBiowISHHy0KFDVYBSWEPFPn36qKxRbm3bts1ZrFuG3A4cOKCmxB86dEjNHJs8eTLKlSun7pfp+bJf6oik4PmXX37BxYsXVR+h2z1Hv379VCF2bhKwSU8iujMueimLp0IZVp+XqYu5m2rdKyloW7FihUqT5m7ORcTrynT+/u8yBi/YV+Rxv7zcCq2rleLFZ6L3LMmknDt3TmVBZDjIkTpLk2lJgCefs/L5KoHgnbrdtWbs5zczQkTkcDKystVMsM/X3myEVxAZaAkJ1KbSk+2RoKd5SHNrnwY5OAZCheCsMSL7tPlULMYvO4Kzsclqu0qQL87FpaigJ3f621BCO753WE4/ISJyPgyECsG1xojsy6X4FExafgyrjkSp7SB/T7zbow76NS6H1UejbukjJJkgCYK61ytrxbMmImtjIEREdi0tU4c5m85i1obTSMvMVtmd51pXxrCuNVDMW6tlkWCna1hIoZ2lich5MRAiIru17li0yvRcvJqitltWKYkJD9VF7ZBbCyMl6GFBNBHlx0CIiOzOhSvJmPjXUaw7rk0xLlPMC2N7hqF3g7JGN9Aj8+OkZDK3/G0F7gYDISKyG6kZOny14TS+2XRWzQzzcHPBi+2q4I37a8Dfi29ntkKm10tAGhsbqxYjZXBKtwtkZI03mQZ/J9PnJciW75NrTL4vf0+lO8F3DiKyefKm98+RKHy4/BgirmnLH7SvEYTxveuienDehSfJ+mTNLmlOKF2UuRo6FfX/tiwr4uPjc1cBsyyNUrFixbvqQWTAQIiIbNrpmCRM+OsINp+KU9vlivvgvV5h6Fa3DDMNNkxWRq9RowZXQ6ciG3Vu2rQJHTp0uOPmwhJwy3Ik95pxZCBUCPYRIrKupPQsfLHuFL7bcg5Z2Xp4urvitQ5VMahjdfh4sruwPZAPKrkRFUauj6ysLNUV2lqrLDAQKgT7CBFZL1W+7GAkJq84huiEdLWvc+1gvN87DJVK+fHXQkQmxUCIiGzG8agEvL/0iOr3IyqV8sX7vcLQuU4Za58aETkoBkJEZHXXUzPx2dqTmLf9AnTZenh7uGJwx+p4uUNVeHtwaIWIzIeBEBFZTXa2Hn/su4Rpq44jLilD7etRLwRje9ZB+RK+/M0QkdkxECIiqzgccR3vLz2MfRevqe2qpf0woU9dtK9Rmr8RIrIYBkJEZFHxyRn4ZPUJLNh1EXo94OvphqGda+CFtlXUzDAiIktiIEREFiG1P7/uvojp/5zAtZRMte+hRqEY3aOOWgmeiMgaGAgRkdntuxiP8UuP4FDEdbVdq0yAWhy1VdVSfPWJyKoYCBGR2cQlpWPayuNYuPeS2g7wcsfwB2rimVaV4O7GYTAisj4GQoVgZ2miu5ely8b8HRcwY81JJKZlqX2PNi2PUd1ro3SAF19aIrIZDIQKwc7SRHdn59krGL/sCI5HJarteuWKYUKfemhaqQRfUiKyOQyEiMgkohPSMGXFMSw5EKm2i/t64J1utfBE84pwc723RRGJiMyFgRAR3ZOMrGzM3XYOn689heQMHWQh6CdbVMQ7D9RCCT9PvrpEZNMYCBHRXdtyKg7jlx3Gmdhktd2oQnF8+FA91C8fyFeViOwCAyEiumMR11Lx0d9HseJQlNou5eeJUT1q49Em5eHKYTAisiMMhIjIaOlZOny7+Ry+/Pc0UjN1kJjn2daV8VbXmgj08eArSUR2h4EQERll/fEYTPjrCM5fSVHbLSqXVE0R65QtxleQiO6YLluHPdF7cDDjIIKjg9EitAXcXN1gaQyEiOi2Ll5JwcTlR7D2WIzaDg7wUqvD92kYChepjCYiukNrL6zF1F1TEZ0SrbYXrluIMr5l8G6Ld9GlUhdYEgMhIipQaoYOszeewdcbz6iZYe6uLnixXRW82bkG/L341kFEdx8EDd8wHHro8+yPSYlR+2d2nGnRYIjvZkSUh16vxz9HovHh8qOqKFq0rV4KE/rURfXgAL5aRHRPw2GSCcofBAnZ5wIXTNs1DZ0qdLLYMBkDISLKcSY2CR8sO4LNp+LUdmigN8b1CkOPeiEcBiOie7YvZl/OcFhBJBiKSolSxzUPaQ5LYCBUCK41Rs4kOT0LX/x7Gt9tOYtMnR6ebq54pUNVvN6pGnw9+TZBRKYRmxJr0uNMge9wheBaY+Qsw2DL/7uMj/4+hqiENLWvU63SGN+7LioH+Vn79IjIgYQnhmPB8QVGHVvatzQshYEQkZM6EZWoukLvOHtVbVco6YPxveqic51gDoMRkcmkZqXi20PfYu7hucjIzrjtsVIjJLPHmgQ3gaUwECJyMglpmWpdsLnbzkOXrYeXuysGd6quhsK8PSzfw4OIHDfjvPrCanyy5xNEJWtd6FuVbYWO5Tti2u5p2jG5iqYlCBKjWoyyaD8hBkJETiI7W4/F+yMwZeVxxCWlq33d6pbBuJ5hqFDS19qnR0QO5HT8aTU7bGfUTrUd6heKd5q/g84VO6uMcxm/Mnn6CAnJBEkQxD5CRHRXJLuz89xV7I1zQalzV9G6ejDcbqz7dTjiOsYvO4K9F+LVdtUgP4zvUxf31bTcODwROb6EjATMPjAbvxz/BTq9Dl5uXnix3ot4od4L8HH3yTlOgh2ZIr8rchfWbF+Drq27srM0Ed29VYcvY8JfR3H5uhQ8u2HeqT0oG+iNtx+oif3h17Bg50Vk6wFfTze8cX8NvNSuCjzdXfmSE5FJZOuzsfT0Uny27zNcTdPqDiX7I1mgcv7lCvweGf5qVqYZYjxj1L/WWF5DcGiMyAGCoEHz993SnkyCohEL/8vZ7t0wFGMerI2ygTf/KiMiuleHYg9hyq4pOBR3SG1XCayilspoE9oG9oCBEJGdD4dJJujWHq03ydIYP77QAm1rBFnwzIjI0V1JvYLP932OxacXq20/Dz8MajgIA2oPgIebB+wFAyEiO7br3NUbw2GFy8rWw/VGrRAR0b3KzM7Eb8d/w1cHvkJiZqLa16daHwxrMsyi/X9MhYEQkR3PAtt62rjuqzGJtw+WiIiMsevyLjUMdvraabUdVioMo1uMRqPgRrBXDISI7MzpmCQs3n8Ji/dFILKIbJBBcIC32c+LiBzX5aTLqh+Q9AUSxb2KY2iToehbva/VipxNhYEQkR2IT87AX/9F4o99ETgYfi1nv7+XG3R6IDVDV+D3yYBYSKA3WlQpacGzJSJHka5LVx2hpTN0mi4Nri6ueLzW4xjcaDACvQLv7cGzdXC5sAXlrm6Hy4ViQNUOgBWCKgZCRDYqPUuH9cdj8ee+S1h/IkYthiqkN1DHmqXRr0l5tRzGhhMxataYyF00bagKGt87LKefEBGRsV2h14evx8e7P0ZEUoTa17RMUzUMVqtkrXt/EY8uA1aNgntCJJrJ9oXZQLFQoPs0IKyPRX9JDISIbOzN50D4Nfy5L0JlgK6lZObcV69cMfRrXB59GoUiyN8rZ3/3emUx++kmufoIaSQTJEGQ3E9EZKxz189h2q5p2Bq5VW0H+wbj7WZvo3vl7qZZh1CCoN+fzfenm3RjvKzt7z/PosEQAyEiGxBxLRVL9kfgj32XcDY2OWd/mWJeeLhxORUA1QoJKPT7JdjpGhaC7adjsHrzTjzQvmWeztJEREVJzkzGNwe/wU/HfkJWdhY8XD3wXN3n8HL9l+HrYaJleLJ1KhN0SxCkyD4XYNW7QO2eFhsmYyBEZCVJ6VlYeeiyyv5sP3slZ7+Phxu61wtBvybl0KZakNHBjBzXskpJXDmmV/8yCCIiYzPRy88ux6d7P0VsqjYTtUP5DhjZfCQqFatk2hfxwjYgIfJ2ZwMkRGjHVWkPS2AgRGThBohbT8epup9VR6KQlpmdc1/rqqVU8NOjfln4e/F/TSIyv2NXjqnp8Ptj9qvtCgEVMKr5KNxX4T7zPGFStGmPMwG+2xZi1qxZ6qbTFTwbh+hOnIhKVMHPkgMRiE7QVn4XVUv74ZEm5fFQo1CUL8EV4InIMq6lXcMX+7/AolOL1DphsiCqDIE9W/dZtVCq2fiXMe1xJsBAqBCDBw9Wt4SEBAQG3uMUQXJKsYnpWHYwUgVARyITcvYX9/VAn4ahatZXw/KBpik+JCIygi5bh0UnF+GLA1/gevp1tU+KoEc0G4EQvxDzvoZpCcD++UUc5KLNHqtkuXXKGAgRmVBapg5rj0Wrup+NJ2PVUJjwcHPB/bWDVfDTqVYwV34nIovbF71PDYMdv3pcbdcoUUNNh28e0tz8Ty41P4tfBa5dvNHc40ZhdEFNP7pPtWg/IQZCRCYoNNx7IV7N+Fr+32UkpmXl3NeoQnE80qQcejUIRQk/T77WRGRxMSkxmLl3Jv4++7faDvAMUA0RpTGiu6uZw4CsDGDDZGDLZ1rQU7wi0Pd/QHKsNnssd+G06iM0lX2EiOzFxSsp+HP/JZX9uXg1JWd/ueI+6Nu4HPo2KYdqpf2teo5E5LwydZlqKrxMiU/JSoELXNCvRj+82eRNlPS2QLf5mOPAny8DUf9p242e0gId72Ladu2eyDq7CQc2/4NG7bvBnZ2liWzf9dRMrFBT3i9h9/n4nP1+nm5qtpfM+mpVpRRXeyciq9oSsUU1RTyfcF5tNyjdAGNajEHdoLrmf/LsbGDX/4C144GsNMCnJND781szPa5u0Fdqh4gjCWhYqZ1VltcQHBojKkKmLhubT8Wqdb7WHI1GRpY25V3a+7StHqRmfT1Qtwx8Pfm/ExFZV3hiuFoWY0P4BrVdyrsU3mr6FnpX663WCTO7hEhgyevA2fXadvUuwEOzgAAzF2LfA75zExVS9yMzvWTYa9nBCMQlZeTcV7OM/40p7+XUMhZERNaWmpWqFkaVBVIzsjPg7uKOAXUG4LWGr6maIIs4/Cew/C0g7Rrg7gM88CHQfCBg4zNjGQgR5RKdkKaWupAA6ER0Ys7+Un6eKvCRoa+6ocU45Z2IbOaPttUXVuOTPZ8gKjlK7WtVtpWaDVa1eFXLnETadWDFO8B/v2nbZRsB/eYApWvCHjAQIqeXmqHD6qNRWLT3kur6fGPGu5ri3jWsjJr11b5GaXi4WSCtTERkpNPxp9V0+F1Ru9R2qF8o3mn+DjpX7Gy5P9bOb9WmxV8PB2Torf0I4L5RgJsH7AUDIXJK2dl67Dx3VRU9S/FzcsbNDuLNKpXAI03L48H6ZRHoYz//MxORc0jISMDsA7Pxy/FfoNPrVCfoF+u9iBfqvaA6RFtEVjqw/iNg6/9p0+JLVNamxVdsCXvDQIicypnYJCzeF4HF+yPUiu8GFUr6qBXeZeirUik/q54jEVFBZCmMpaeX4rN9n+Fq2lW1T7I/kgUq51/Oci9a9FHgz1eA6EPaduOntWnxXhaqRTIxBkLk8OKTM7D8v0g16+tA+LWc/QHe7ujVQKa8l1dZIC51QUS26lDsITUMdihOCz6qBFbBuy3eRZtQyy1FAZkWv3M2sHYCoEsHfEsBvf8PqNML9oyBEDkkmeK+/kSMGvr693gMMnVa4Y+bqwvuq1laZX661CkDbw/r9K0gIjJGXGoc/m/f/2Hx6cVq28/DD4MaDsKA2gPgYck6nOsRwJJBwLmN2naNB4A+XwIBllsc1VwYCJFDzZ7479J1FfzIYqfxKZk598lML8n8yGKnpQPMuLIyEZEJZGZn4tfjv+KrA18hKTNJ7etTrQ+GNRmG0r6lLfsaH1oE/D1cmx0mNUjdPgKavWjz0+KNxUCI7J7U+mhT3i/hTGxyzv7gAK+cpS5qh9xo6U5EZON2Xt6Jqbum4vS102q7Tsk6GNNyDBoFN7LsiaReA1a8DRxaqG2HNtGmxQdVhyNhIEQ2R1Zs33XuKmIS0xAc4I0WVUqqIa3cktKzsOpwlAp+tp+9Av2NKe/eHq7oXjdEZX+k63P+7yMislWXky5j+p7pWHNhjdou7lVcrQvWr3o/uFl6+Ylzm4DFg4CES9q0+A7vaDc7mhZvLAZCZFNWHb6MCX8dxeXraTn7ygZ6Y3zvMHQNC8G2M3Gq2aEEQamZN6e8t6paUgU/PeqFIMDb8f5HJSLHla5Lxw+Hf8B3h75Dmi5NLYUhK8PLCvGBXoGWPZmsdGDdRGD7rBvT4qtoWaAKzeGoGAiRTQVBg+bvk//18pCg6LX5+xDo447rqVk5+6sG+ami54cbl0P5Er4WP18ionuta1wfvl6tDRaRFKH2NS3TVHWFrlWyluVf3OgjwB8vAzFHtO0mzwHdJgNe/nBkDITIZobDJBOUPwjKTYKgYt7uOUtdNKpQnFPeicgunbt+Tq0OvzVyq9oO9g3G283eRvfK3S3/vpadDeyYpWWCdBmAbxDQ5wug9oNwBgyEyCZITVDu4bDCfDmgMTrUDLbIORERmVpyZjK+OfgNfjr2E7Kys+Dh6oHn6j6Hl+u/DF8PK2S2r4Vr0+LPb9a2a3bXgiB/53mfZSBENuFgrkaHt5N7SjwRkT0Ngy0/uxyf7v0Usamxal+H8h0wsvlIVCpWyTon9d9C4O8RQPp1QIIwGQZr+rzDTIs3FgMhsqr9F+Mxa/0ZrD0WbdTxMouMiMieHLtyTHWF3h+zX21XCKiAUc1H4b4K91nnhFLjtQDo8B/adrlmQL//AaWqwRk5RSCUnp6O9evXw9XVFZ06dYKHB2cVWfsvo21nrmDW+tPqXwOZ+p6WmV3g98jfJyGB2lR6IiJbocvWYV/MPsSmxKpGh02Cm+RMdb+Wdg1f7P8Ci04tUuuEyYKoMgT2bN1n1UKpVnF2A7DkdSAhAnBxA+4bCbR/G3BzinCgQA7/k1+5cgUPPvggSpcujatXr2LkyJHYuXMnvLzYXdgaK76vORaNrzacyRkKc3d1UU0PX+tYDaeiE9WsMZG7aNqQpJUp9OwLRES2Yu2FtarxYXTKzYx2Gd8yargrPi0eXxz4Atdl2AlQRdAjmo1AiF+IdU42M00rhpaiaFGympYFKt8Mzs7hA6HMzEzMnz8fNWrUUNv16tXDsWPH0KiRhTt0OrEsXTb++i8SX60/g1MxSTnZnyeaV8TLHaqiXHEfta9aaX/MfrrJLX2EJBMkQVD3emWt9jMQEeUPgoZvGA59vrmuEhSN2DgiZ7tGiRpqOnzzECv24Yk6pE2Ljz2mbTd9QVsmw9PPeudkQ6weCF2+fBnfffcdjh8/jrFjx6JOnTq3HLN3714sWLAAiYmJaNu2LZ555hk1zCViYmKwYsWKAh/7ueeeQ0hICPz8/DB37lycPXsWpUqVKvA5yPTSMnVYtPcSvtl0BuFXU9W+AC93PNumEl5oWwVB/rdm5STYkcaJRXWWJiKy5nCYZILyB0G5ucAF7zR/B0/WfhLurlb6qM3WAdu+AP6dBGRnAn6ltYVSa3W3zvnYKKsGQp999hlmzpyJnj174ueff8bAgQNvCVKWL1+Ovn37YtCgQahVqxbee+89Ffj89ttv6v7r169jw4YNBT7+s88+q/oxpKamqmPCw8NRsmRJZGVlcWjMjGT5i593XMC3W84hNjFd7Svl54kX21XBM60roVgRnZ8l6GldrZQ5T5GI6K5JTVDu4bCCSJBUu2Rt6wVB1y4Ci18DLmh9ilDrQaD3/wH+Fl6w1Q5YNRDq1asXBg8ejOjoaHz99dcFHjN06FC8/vrr+Pzzz9V2hw4d0KJFCwwZMgTt27dXQ16S7SlMXFwcgoODc47p378/li5digEDBpjpp3Je8ckZ+GHbefy47Tyup2rT3EMDvfFKh6p4vHlF+HhaeK0cIiIzkMJoUx5nUrLw4n+/ASveAdITAA8/oMdUoPEzTjct3i4CoerVb7+C7dGjR9VwlgQvBs2bN0fVqlVVpkgCoaJs374d3377Lbp166aG0TZu3IiJEyfedoaZ3AwSEhJyao3kZiqGxzLlY1pLVEIafth6Ab/uuYSUDG39r6pBvnilfRX0blAWnu4yjJmNzEJmhJHpONJ1RbaF19ZNJTxLGH2cRf9fTI2H28q34XpsqdrMLtccuoe+0tYLy7q5PJGzXFeZRj6m1WuEbkeCIFGpUt5mU7JtuK8ovXv3VjVCixcvRmBgIP7991/Url270OOnTJmCCRMm3LJ/9erV8PU1fdfPNWu0VYbtUWwqsC7SFbtiXaDTa39plPfTo2u5bDQomQDXqINYG3XQ2qfplOz5uiLb5uzXVqY+E6tTVxd5XKBLIKL2RGGFS8E1rKZWOuEwGl+cA4/MeGTDFSfK9sWp0r2g3y4F0jeKpJ3sukpJSbH/QEhqe4S/f94F3wICAnLuM8b999+vbsYYPXo0hg8fnicjVKFCBTzwwAMoVqwYTBmpyi++a9eudtfX6HhUIr7ZdA4rDkch+0atYPPKJTCoQxW0q16K639ZkT1fV2TbeG0BZ66dwZhtY3Aq49Rti6TFuHbj0LlCZwv8YlLhuv5DuJ35n9rUl6qO7D5foXpoE9x+zMXxr6uEGyM6dh0ISQZHxMfHo3jx4jn7pR9QxYoVzfKc0l+ooB5D8gsyxweLuR7XHPZeiMdX609j3fGYnH2dapXG652qo3llNjq0JfZ0XZF9ccZrS5rA/nL8F8zcOxPpunSU9C6JiW0mIjM7s8A+QqNajEKXSl3Mf2KXDwJ/vgLEHte2mw+ES9cP4e5phTXLbPC6MvbxbDoQkp4/4vDhw6hSpYr6WqfTqT5AMtOMLPMGsOV0nOoCvePsVbVP6u0erF8Wr3eshrqhWrBKROSIrqRewXtb38PmCG1R0nbl2uHDth8iyCdIbXeq0KnQztJmnRa/9XNg/WRtWrx/GeChWUCNruZ9Xgdl04FQaGgoOnbsiC+++EJ1h3Zzc8O8efPUlPnHHnvMrM89a9YsdZPAy1m7QK8+Kl2gT+O/S1pnVA83F/RrXB6v3lcVVUvnHa4kInI0my9txrit43A17So8XT0xvNlwDKg9IM/wvwQ9Fm2WGH9BmxZ/cZu2XbuXNi3ejy1H7DIQ2rx5M7755pucgqaPPvpIzfDq16+fuok5c+agS5cuqF+/PsqVK4etW7fi//7v/1CtmnkXh5Np/XKTMUbDEJ0zyNRlY9mBSMzeeAanc3WBfrJFRbzcvipCb3SBJiJyVDL8NXPPTCw4vkBtVy9eHdM6TEPNEjWtd1IyLf7gL8CKkUBGIuDpD/SYBjR6itPi7TkQksCme3etw6Uh8Mk/rV6+lq7TmzZtUp2lv//+e1W8TKbvAr1wTzi+3ngWEddudIH2dsfzbSqrW6kCukATETmak/EnMWrTKJy+dlptP1XnKQxrMgze7t7WO6mUq8BfQ4Fjy7TtCq2Avl8DJbWSEbLjQEj6AcmtKN7e3mrWFpleYlom5u+4iO+2nENcktY/KcjfEy+1q4qnW1VEQBFdoImIHKUeUjJAkgnKyM5QBdGT2k5C+/JF96szq9NrgSWDgaQoQLpUdxwNtHsLMHcdkhOx6RohMp+r0gV66znVBTohTWu0JYufSv1P/2YV4O3B/8mIyDnEpcapWqCtEdpyFO3LtVcF0aV8rFh3k5ECrB0P7NKmxSOoprZafGhj652Tg2Ig5GQuX0/FnE3n8Muui0jN1ArBq5X2w+sdq6NPo1B4uGmL2RIROYNNlzapWWFSEO3l5oURzUbgiVpPWLcfWuR+bVp83Eltu8UrQJcJgB1Oi7cHDIScZNbYubhkfL3hDP7cfwmZOq0LYv1ygRjcqRoeCAuBK1d3JyInkpaVhhl7ZuDXE7+q7RolauDj9h+jegkrtiGUafFbZgIbpgLZWYB/CPDwLKC6BXoSOTEGQg4+a+xoZIKaAr/i0OWcLtCtqpZUGaD2NYLYBZqInM6JqydUQfSZ62fU9tN1nsawpsNURshqrp4DFr8KhO/Utuv0AXp/DviyWa3NBELjxo27oweeNGnS3ZwPmcie81fx1YYz+DdXF+jOtYPxeqdqaFqJ/2MRkfPJ1mdj/tH5+GzfZ6ordCnvUpjUbpJqkmjVafEHfgZWjgIykgDPAODB6UDDJzgt3tYCIenxc9999xl1rKzwzkDIOrMeNp3SukDvOqd1gZYRr54NQjHovmoICzXdWmlERPZEOj9LQfS2SK0R4X3l78PEthPV7DCrSb4C/PUmcHy5tl2xNdD3G6BE3oXGyYaGxjZs2GDUcVYtMnPSLtD/HInCrA2ncTgiIacL9KNNy+PVDtVQOcjP2qdIRGQ16y+ux/ht4xGfHq+Gv95p9g761+pv3c+qk6uBpYOB5BjA1QPoNAZoO5TT4m05EJIOz+Y41lbZQ7G0dIFesj9CdYE+G5us9vl4uGFAy4oY2L4KygayCzQROa/UrFR8svsT/H7yd7Vdq0Qt1SG6WnHzrkxQ5LT41eOAPd9p20G1gEfmAGUbWu+cnJzRgdDAgQNhjmNtlS0XS0sX6N92h+N/m252gS4mXaDbVlFdoEv6eVr7FImIrOr41eOqIPrs9bNq+7mw5/Bmkzfh6WbF98eIvdq0+Cta12q0HAR0GQ948I9Wu5s1Fh8fj8WLF+PFF19U27/88gtmzJih1v/66quvUKoUF38zh4S0TPy0/YJqhBiXlKH2Bfl74eX2VVQWiF2gicjZSUH0T0d/UgXRWdlZKO1TWhVEtwltY72T0mVp0+I3TtOmxQeUBR7+Cqh2v/XOie4tEBo1ahRat26tvo6NjVUZoNdffx179uzB22+/jR9++OFuHpYKIUtfSPAzb9sFJKZrXaDLl5Au0NXwWNPy7AJNRAQgJiUGY7eMxY7LO9Tr0alCJ0xoMwElvEtY7/W5ehb481Xg0i5tu25foOdMTou390Bo2bJlmDJlivp65cqVaNeuHaZPn47w8HA0b97c1OfotCKvparhr193X0RaZrbaVyPYX02B79WAXaCJiAzWXVyHD7Z9gGvp1+Dt5o2RLUbi0RqPmr8gWpogXtgGJEUD/mWASm20gmeZFr9vHrBqNJCZDHgVAx78BGjQn9PiHSEQSk1NVVO1xb///ouuXbuqr4sXL46UlBTTnqED0mXrsfPcVeyNc0Gpc1fRunow3HJ1dj4Tm6S6QC/eH4GsG10QG5YPxOudqqNrnTLsAk1EdENKZgqm75mORScXqe06JetgaoepqBpY9ILe9+zoMmDVKCAh8ua+YqFAx7HAib+BEyu0fZXaAX1nA8Ur8vfmKIFQy5YtMWLECHTu3BmLFi3Cu+++q/bL0BgzQre36vBlTPjrKC5fTwPghnmn9qBsoDfG9w5D+RK+mL3hDFYcvqz+mBBtqpVSXaDbVi/FtgRERLkcvXJUFUSfTzivtl+o+wKGNB5imYJoCYJ+f1Y6uOXdL0HRssHa1zItvvN7QOshnBbvaIHQl19+iZdeegnr16/H+++/j9q1a6v906ZNw9ixY+EIzDF9XoKgQfP35f/fRgVFr83fl2dflzpl1BBYk4pWHNsmIrLRgui5R+bii/1fqILoYJ9gfNT+I7Qq28pCJ6DTMkG3vJvn4uoODFwLhDayzDmRZQOhmjVrYvPmzbfsX7VqFRyFqafPy3CYZIJu87+N0qdhWTUEVjuEXaCJiPKLSo7CuC3jsDNKW5Orc8XO+KD1ByjuXdxyL5bUBOUeDiuIzA5LT7TUGdE9cDX2wPnz58McxzoLWfJCGw67vSdbVGIQRERUgLUX1uKRZY+oIMjH3UcFQJ92/NSyQZCQwmhTHkf2EQg988wzMMexziImMc2kxxEROVNBtCyR8daGt5CQkYCwUmH4rddveKTmI9apnfQPNvK4MuY+E7L00Fj37t1N8ZxOKTjA26THERE5gyNxRzBq8yhcSLgAF7jghXovYEijIfBw87DOCcWfBzZ/WsRBLtrsMZlKT44TCMksMWPVq1fvbs/HYbWoUlLNDou6nlZgnZD8TRMS6K2OIyJydrpsHX448gNm7Z+FLH0Wgn2DMaXdFLQo28JKJ5QJbP8S2DANyErViqGlDki9e+d+V7+Roeo+lTPFHC0Q+uSTT8x7Jg5O+gTJFHmZNVbI/zbq/tz9hIiInLUgesyWMdgdtVttd63UFeNbj0egl5XWfQzfDfw1FIg5om1Xbg/0+gyIOVpwHyEJgsL6WOdcyTKzxujudK9XFrOfbpKrj5BGMkESBMn9RETObPX51ZiwfYKqBZKC6NEtRuPh6g9bpxYo7TqwdgKw53vtz1efkkC3j4CGT2rdoYOqA7V7FtxZmuwGAyEL9hESEux0DQvB9tMxWL15Jx5o3/KWztJERM5YED1l1xQsOb1EbdcrVU91iK5UrJLlT0Y62h5dAqwcdXPmV8MBwAOTAL98i4pL0FOlveXPkUyGgZCF+gjlJkFPyyolceWYXv3LIIiInNmh2EN4d/O7uJh4URVED6w/EIMaDYKHdGa2tPgLwIp3gFP/aNslqwG9PwOqdLD8uZBFMBAiIiKrFUR/f/h7fHXgK1UQHeIXgsntJqN5iBUW79ZlATu+AjZMATJTtOUx2g8H2g0HPDib15HdUyAkw0aRkZGoUKGC6c6IiIgc3uWkyxi9ZTT2Ru9V290qd8N7rd6zTkH0pb1aMXT0IW27Ulug16dA6VqWPxey3YaKuckK86+99hr8/PxQsWLFPI0UDx26cSEREREVYNW5VapDtARBvu6+mNR2EqZ3mG75ICgtAVgxEvi2sxYESYfqPl8Czy1nEORE7ioQeu+993Dw4EGsXr06z/5HH30UH3zwganOjYiIHEhyZjLGbhmLdza9g8TMRNQPqo+FvRfioeoPWXZWmCqGXgbMagHs+kabEdbgcWDIHqDJM4DrXX00kjMNjS1cuBDr1q1DjRo18uxv27Ytl9cgIqJbHIw9iHc3vYtLSZfg6uKqCqJfa/ia5Quir4UDK0cCJ1Zo2yWqaMNg1Trxt+ak7ioQiomJQWhoqPo6dxSfmZlp8unmRERk3wXRcw7NwdcHv4ZOr0NZv7KY0n4KmpZpauETyQJ2/Q/4dxKQmax1hm47DOjwNuDhY9lzIfsPhGQJjbVr1+Khh/KmM7/++ms0bWrhi5uIiGxSRFIExmweg30x+9R2j8o9MK71OBTzLGbZE4ncrxVDXz6obVdopU2JD65j2fMgxwmEJkyYgKeeegpDhgxR29J4cNWqVVi5ciX++edG7wUiInJaK86uwIc7PkRSZhL8PPwwtuVY9Kray7K1QOmJwPrJwM6vAX024B0IdJ0INH6WdUB0b4FQz5498ccff2Dy5Mnw9/fHu+++iyZNmqggqHPnznAE5uosTUTkyJIykvDRzo+w/Oxytd2gdANMbT8VFQIs3Gbl+AqtMWLCJW273qNAt8lAQBnLngc5bh8hCXgcJeixdGdpIiJHdCDmgOoQLUNiUhD9SoNX8GqDV+Eu9TiWcj1CK4Y+rgViKF4J6DkTqNHFcudAdoWdpYmI6J5kZWdhzn9z8M1/36iC6FC/ULVOWOPgxpZ7ZbN1wO5vgXUfAhmJWjF0mzeADiMBT1/LnQc5RyCUkZGBH374AVu2bEF8fPwt9y9ffiMSJyIih3Yp8RJGbx6NA7EH1HbPqj1VPVCAZ4DlTuLyf1oxdKRWlI3yzYHenwNl6lruHMi5AqHXX38dS5cuVbVC1atXN/1ZERGRTU2B3xO9BwczDiI4OhgtQlvAzdVN1QF9tOMjVRDt7+GPsa20gmiLyUjWiqF3zAb0OsCrGNBlPND0RRZDk3kDoUWLFmHr1q2oW5fRNhGRI1t7YS2m7pqK6JRotb1w3UIE+wSjfED5nGnxjUo3Ur2BZJ/FnPwH+HsEcD1c267bF+g+FQgIsdw5kPMGQh4eHihXrpzpz4aIiGwqCBq+YTj0sgRFLjGpMermCle81ug1vFz/ZcsVRCdcBlaNAo4u1bYDKwI9ZwA1H7DM85PDuasFVaSH0LRp06CX9VqIiMghh8MkE5Q/CMqtuHdxvFL/FcsEQVIMvWuOtj6YBEEublox9OAdDILontzV1Tty5EjVXfrnn39GlSpVbmmQtWHDhns7KyIisioZ9jIMhxXmatpVdVzzkObmPZmow1oxdMQebTu0iVYMXbaBeZ+XnMJdBUIvv/wyfH190adPHxQvXtz0Z0VERFYVmxJr0uPuSkYKsHEasP1LIDsLkJlond8Hmr8EuLqZ73nJqdxVIPTvv//iwIEDqFWrlunPiIiI7EZp39LmeeBTa4G/3wKuXdS26/QGenwMFNMW/CayaiAUHByM0qXNdPETEZHVSO3nwpMLMX339Nse5wIXlPEtgybBTUx7AonRwKp3gSN/atvFygMPTgdqP2ja5yG6l2Lphx9+GBMnTnTodbhknbGwsDA0b27msW8iIhsRlRyF19a+phZLTdOloUqxKjlBT26G7VEtRql+QiaRnQ3s+R74srkWBLm4Aq0GA4N3Mggi28sISUfpffv24ZdffkHlypVvKZbesWMH7B3XGiMiZ8oCLT2zFNN2TVPNEb3cvPBm4zfxdNjT+Pfiv3n6CAnJBEkQ1KWSidbvij4KLB8GhO/Utss21IqhQy24RAc5rbsKhPr27atuRERk36TYecL2Cdh4aaPabhDUAJPaTUKVQC0bJMFOpwqdsCtyF9ZsX4OurbvmdJa+Z5mpwKbpwNbPtWJoDz/g/nFAi1cANy6FSZZxV1fauHHjTH8mRERk0SzQynMr8dHOj5CQkQAPVw+83uh1PF/3+Vv6AknQ06xMM8R4xqh/TRIEnfkXWP4WEH9e2671oFYLFGjB7tREXH2eiMj5XEm9ogKgNRfWqO06Jevgo3YfoUaJGuZ/8qRY4J8xwKHfte2AUC0AqmPBNcqI7iYjJA0UxeHDh3O+LowcQ0REtkeCn0k7JqlmiO4u7nil4SsYWH+gygiZlRRDH5gPrH4PSLumSq7R8lWg01jAu5h5n5vIFIHQwIEDC/yaiIhs3/X06yoLJMNhonrx6pjcbjLqlKpj/iePPQH8NQy4uE3bDqmvFUOXa2r+5yYyVSA0bNiwnK+9vb3x2muvFXjc119/bexDEhGRBWwM34gPtn+AuNQ4uLq44sV6L2JQw0HwdPM07xNnpgGbZwBbPgWyMwEPX6DTGKDlIBZDk333ERo0aNBd3UdERJaTmJGIcVvGYci/Q1QQVLlYZfzU4ycMbTLU/EHQ2Y3A7DbApo+1IKhGN60nkCyUyhlhZENMOj8xPDwcJUuWNOVDEhHRXdgWsQ3vb3tf9f+RBojPhj2LIY2HwNvd27yvZ3IcsHoccPAXbds/BOgxDQh7CMjXc47I7gKhRo0aFfi1yM7Oxvnz59G7d2/TnR0REd2R5MxkzNgzQy2TISoEVMCktpPQpIyJl8LIT68HDizQgqDUq1oxtCyOKoukegea97mJLBUIPf300+rfgwcP5nxt4OHhobpM9+zZ817Oh4iI7tLuqN14b+t7iEiKUNtP1n4Sw5oMg6/U5phT3CmtJ9D5zdp2cF2tGLoClygiBwuE3n77bfVvUFAQnn/+eXOdExER3YHUrFR8vu9z/HzsZ7Ud6heKiW0nomXZlqZ5HbN1cLmwBeWubofLhWJA1Q6ANFXMStcKoaUgWpcBuPsAHd8FWg8G3Mw8HZ/ImjVCDIKIiGzDgZgDGLd1HC4kXFDbj9R4BG83exv+nv6meYKjy4BVo+CeEIlmsn1hNlAsFGj6PPDfQuDKKe246l2AnjOAEpVN87xEFsLFXIiI7FC6Lh2z9s/Cj0d/RLY+G8G+wZjQZgLalWtnuieRIOj3Z6UAKO/+hEhg/WTta79goMdUoG4/FkOTXWIgRERkZ47EHcHYLWNx5voZtd2nWh+1GnwxTxN2aM7WqUzQLUFQbrJI6us7AL9SpnteIgtjIEREZCcydZn4+r+v8d2h76DT61DKuxTGtx6PThU7mf7JLmzTMj+3PaFkIOYoUKW96Z+fyEIYCBER2YETV0+oLNCJ+BNqu0flHhjTcgyKexc3zxMmRZv2OCIbxUCoELNmzVI3nU5n2d8IEVEumdmZKgP0zcFvkKXPQgmvEhjbaiy6Ve5m3tfJx8jmuP5lzHseRGbGQKgQgwcPVreEhAQEBrIZGBFZ3un40xi7dSyOXjmqtjtX7IxxrcYhyCfIvE8cdxpY834RB7los8cqtTHvuRCZGQMhIiIbo8vWqdlgX+7/UmWEAjwD1DBYzyo94WLuZSoO/gosH67V/8gU/IwkLejJUzR94xy6T9X6CRHZMQZCREQ25Pz186ov0MHYg2q7fbn2+KDNB2p6vFmlJwEr3r65Rljl9kC//wGX9mizx3IXTksmSIKgsD7mPSciC2AgRERkA6QX0IJjC1SH6DRdGvw8/DCq+Sg8XP1h82eBLh8EFr0IXDkNuLgCHUcD7Udo2R4Jdmr3RNbZTTiw+R80at8N7obO0kQOgIEQEZGVXUq8pNYI2xO9R223KtsKE9tMRFn/suZfKHXX/7SFUmWJjGLlgH5zgMpt8x7n6gZ9pXaIOJKAhpXaMQgih8JAiIjISvR6vVol/pM9n6j1wnzcfTCi6Qj0r9Xf/FmglKvA0sHAiRXadq0HgYdmAb5GzhYjchAMhIiIrCAqOQrvb30f2y9vV9tNyzTFh20/RIWACuZ/cmmW+MdAICECcPMEun4ItHyVS2SQU2IgRERk4SzQktNL8PHuj5GUmQQvNy8MazIMA+oMgKvU55iTLJshK8VvmALos4GS1YDHfgDKNjTv8xLZMAZCREQWEpsSiwnbJ2DjpY1qu0HpBvio7UeoHGiBFdsTLgN/vgyc36xtN3gC6PkJ4BVg/ucmsmEMhIiILJAFWnFuBSbvnIyEjAR4uHpgSOMheC7sObhZYvbVydXAkteAlCvaQqk9ZwCNnjT/8xLZAQZCRERmdCX1CibtmIS1F9eq7bBSYSoLVL1EdfO/7lkZwLoJwPYvte2Q+sCjc4EgCzw3kZ1gIEREZCZrLqzBh9s/RHx6PNxd3PFqw1fxUv2XVEbI7K6e1XoDRe7Xtlu8CnSdCHh4m/+5iewIAyEiIhO7lnYNk3dNxspzK9V2zRI18VG7j1C7ZG3LvNaHFgF/DQMyEgFZnf7hr1RTRCK6FQMhIiIT2hC+QRVEx6XGwc3FDS/WexGDGg6Ch5sFskAZycDKkcD++dp2xdbAI98CgeXN/9xEdoqBEBGRCUgR9LRd07DszDK1XTWwqsoC1QuqZ5nXN+owsOgFIO6ktijqfSOBDiMBN77NE90O/w8hIrpH2yK24f1t7yM6JRoucMFzdZ9Ts8KkR5DZyTIZe74DVo0BdOmAfwjwyBygSgfzPzeRA2AgRER0l5IzkzFjzwy1TIaoGFARk9pNQuPgxpZ5TVPjgWVvAse0LBRqPAA8PBvwC7LM8xM5AAZCRER3YXfUbrVQakRShNoeUHsAhjYZCl8PX8u8nuG7gEUvAdcvAjILrcsHQKvXAVczd6cmcjAMhIiI7oAsjvrZ3s+w4PgCtV3Ov5xaKb5F2RaWeR2zs4GtnwL/fgTodUCJysCj3wPlmlrm+YkcDAMhIiIjHYg5gLFbxuJi4kW1/WjNR/F2s7fhJ92aLSExGlj8CnB2g7Zd7xGg12eAdzHLPD+RA2IgRERUhHRdOr7c/yV+PPIj9NAj2DdYZYHalmtrudfu9Dpg8atAcizg7gM8OB1o/DRXjCe6RwyEiIhu43DcYZUFOnv9rNp+qNpDGNliJIp5WigLo8sE/p0EbP1M2w6uq60YX7qWZZ6fyME5TSCUnZ2Nt956C3Xr1sUrr7xi7dMhIhuXqcvE7IOz8f3h76HT6xDkE4TxrcejY4WOljuJ+PNaQXTEHm272UtAt48ADx/LnQORg3OaQGjKlCmIiIhAcnIyAyEiuq3jV4+rLNDJeGlOCPSo0gNjWoxBcVmuwlKOLNamxqcnAN6BQJ8vgLCHLPf8RE7CKQKhrVu3IiUlBb169cKWLVusfTpEZKMyszPx3aHv8M3Bb5Clz0IJrxJ4r/V76FqpqwVPIhVYNRrY+4O2Xb6FtkxGiUqWOwciJ2LVQCg9PR0LFy7E119/jePHj2Pp0qVo2zZv8aFer8fHH3+MefPmITExUd0/ffp0lC+vrZ1z4sQJjB8/vsDHX7BgAa5fv46vvvoKP/74I+bPv7H+DhFRPqfjT2Ps1rE4euWo2u5SsQvGtRqHUj6lLPdaxRwDFr4AxB7Tlslo9xbQaQxgiXXKiJyUVQOh9957Tw1XvfHGG3jiiSeQmZl5yzEffvghPvvsMxXEVKpUCSNGjEDXrl1x8OBBeHp6olSpUnj44YcLfHwXFxeMHTsWkZGRePrpp3Hu3DnExcVh1qxZGDx4sAV+QiKyFbpsHfbF7ENsSixK+5ZGk+AmcHN1U/t/PPqjmhUmGSEpgh7TcgwerPKgeg+xCFkmY988YOUoICsV8AsG+n0DVLvfMs9P5MSsGghNmzZNvdFcunSp0IzRJ598ggkTJuDBBx9U+ySzExoaij/++ANPPvkkgoKCVBBVmKeeegrh4eHq640bN6oMUtOmbDxG5EzWXliLqbumqrXADMr4llErw684twIHYw+qfR3Kd1AF0TI93mLSrgN/DQOO/KltS/DT9xvA34LnQOTErBoIFfXX1oEDB9RwWJcuXXL2lSlTBg0aNMDmzZtVIFSU3ENtaWlpKuvUqlWrQo+X4EtuBgkJCepf+b6CMlZ3y/BYpnxMIl5Xt1oXvg4jN49U/X9yk6Boyq4p6mt/D3+MaDICfar2Ue9Llvr/0iViH9yWvAyXaxegd3VHdscxyG41BHBxlTcHm7qgeW2RvV1Xxj6mTRdLy5CWIfjJTbYvX758x4933333qenzRc0ukwxUfqtXr4avr+nXEFqzZo3JH5OI15UmW5+NTxI+uSUIys0NbnjF+xV4nPDAyhMrLXPx6LNRLWYVwiIXwgU6JHsGYW/l1xEfXx1YucqmL2BeW2Qv15VMkrL7QEgKpYWbm1ue/e7u7qov0J2qUqWKut3O6NGjMXz48DwZoQoVKuCBBx5AsWLFTBqpyi9e6p08PFgISbyuzGFP9B4krNOyuoXRQYfaLWqjWZlmlrkMk2PhtmwIXCPXqc3sOg/B88GZaC1T5G0Y37PI3q4rw4iOXQdCpUuXVv/GxsaqomgD2a5fv75ZntPLy0vd8pNfkDkCFnM9Ljk3XlfAldQrWHdJCzaKEp8Rb5n/D2WNsD9fAZKiAXdvoPtUuDZ9Hq6WKso2AV5bZC/XlbGPZ9OBUKNGjVRQIr1/ateunRPhyYyxgQMHWvv0iMiGyOyvQ3GHsCVii7rJNPjbDYnlJrPIzHtyWcCGKcDmGZLrBkrXBh79ASgTZt7nJSL7DoQCAgLw/PPPq7qd+++/H2XLlsU777yDwMBAPP7442Z9bpliLzedTmfW5yGiuxeXGoetEVtV4LMtchsSMvKmwmuXqI3wpHAkZyYX+P0ucFGzx2QqvdlcCwf+GAiE79C2mzynMkHwNH3NIRHZWSD066+/YsiQITn1Pg899JBKZY0cOVLdxKeffopBgwahTp06qmYoLCwMK1asMGm9TkGkz5DcJAMlgRcRWV9Wdpaa6m4Ifo5dlcaDN0kPoDahbdSq8G1D26pMj0ydH75Bq/vLnSGSIEiMajFK9RMyi2N/AUsHa1PkvYoBvT8D6j1inuciIvsLhPr27ZtnarxB7tlZPj4+mDt3LubMmYOMjAz4+flZ+CyJyJqik6OxNVILfHZE7kBiZmKe++uWqqsCn/bl2qNeUD24u+Z9W+tSqQtmdpxZYB8hCYLkfpPLTANWjwN2z9G2yzUFHvkOKHn7yRpE5GSBUGGFyQVhgR6Rc5DuzgdiDuTU+hgWPjUo7lVcZX3alWun/jVmCQwJdjpV6FRgZ2mTiz0JLHoBiD6sbbd5E7j/PcDd0/TPRUSOXSNERM4hKjkKmyM2qyGvHZd35KnpkSGs+kH1VeAjmR/JAN1NACPf0zykOcxG2n0cWACseBvITAF8g7QO0TXMkHEiIpNhIFQIFksTmU+GLkNlZ7Zc2qKGvU5fO53n/pLeJVWNjwQ+kvUp4V3Ctn8d6YnA8uHAod+17SodgH5zgIAQa58ZERWBgVAhWCxNZFoRSREq8NkSuQU7L+9EqiwueoOriysaBDVQWR+51SlVR+2zC5H7gUUvAlfPAi5u2mrxsmq8uQqwicikGAgRkVmk69KxN2qvNuQVuRXnrp/Lc3+QT5DK+kjg0zq0NQK97Gx2pgyF7ZgNrHkfyM4EipUHHv0OqFj4WoZEZHsYCBGRyYQnhOcEPrujdufJ+ri5uKFh6YZoX769CoBqlaxlP1mf/JKvAEtfB07eWBesdi+gzxeAb0lrnxkR3SEGQkR019Ky0lTAY5jhdTHxYp77g32C0a68NtzVsmxL1efH7p3fojVITLwMuHkB3T4Cmg8E7GiZDCK6iYEQERlNmppeSLiQE/jIoqYyBJbzhuLijsZlGmszvELbomaJmnBxlAAhWwds/BjY9LFaPR6lagCP/QCEmGfdQyKyDAZCheCsMSJNSmaKyvoYprdfSrqU56UJ8QvJKXJuGdIS/p7+jvfSXY8A/nwZuLBV2270NPDgx4AnG7wS2TsGQoXgrDFy5qyPFDZL4CNZn73Re1WTQwMPVw80KdNEdXKW4KdqYFXHyfoU5MRKYMkgIDUekCCv16dAg/7WPisiMhEGQkSkGhjKlHYJfCTrE5kcmedVKedfLifr0yKkBXw9nGDB0Kx0YM14YOdsbbtsQ23F+FLVrH1mRGRCDISIHIQuW6dqdg5mHERwdDBahLYotAOzZH2kiaGh1keaG8qCpgaerp5oFtIsJ/ipXKyyY2d98rtyRlsm4/JBbbvVYKDLeMDduCWBiMh+MBAi2yxKvbANSIoG/MsAldqwOV0RZIX13IuKLly3UC0q+m6Ld3MWFU3KSFLLVxiCn9wLkIoKARVyAh9ZisLH3QdO6eBvwN/DgYwkwKck8PBsoFZ3a58VEZkJAyGyLUeXAatGAQm5hmaKhQLdpwFhfax5ZjYdBA3fMBx66PPsj0mJwVsb3kLPKj0RlRKFgzEHkaW/mfXxcvNSAY8EPlLvU7FYRTi19CRgxTvAwQXadqV2wCNztOuPiBwWAyGyrSDo92dl4Cbv/oTL2v7+8xgMFTAcJpmg/EGQMOz7+9zfOftkiMuQ9Wlapim83b3N8qu0O5f/05bJuHIKkCaP970LdHibmUgiJ8BAqBCcPm+F4TDJBBXwga7tcwFWvQvU7skPp1yktif/EFdBnq7zNAbUGaCGv5xWQUOuEvTsmgOsHgdIP6SAUOCRb4HKba19tkRkIQyECsHp8xYmH1C5h8NuoQcSIrTjqrS34InZttiUWKOOqx9U37mDoIKGXGVl+GLlgIi92nbNHsDDX3GZDCInw0CIbIP8lW6M+PMMhHIp7VvapMc51ZBrYpR2c3UHHvgIaPkql8kgckJ2uuIhORwZqjDGireBv4bdnNbsxDJ1mVh5buVtj3GBC0J8Q9AkuAmc0m2HXG+QmWEtXmYQROSkGAiRbQhtDLh63P4Y+cs9Kw3Y+wPwTQdgzv3Avp+AjGQ4m7jUOAxcPRALTy7ME/TkZtge1WJUof2EHF6RQ66yknyMdhwROSUGQmR98lf7kteAXMs45CUf6C7Ao98Dzy0H6j2iBU1S27FsCDCjjjbtOfoonMHhuMN4fPnjqlDa38MfX97/JT7t+CmCfYPzHCd9hGZ2nJnTR8jp6PXAxe2mHZolIofDGiGyvtXvAcf+Atw8gQ4jgb3fF9BHaOrNqfNSLJ0UCxz4Gdg7F4g/B+z6n3ar0BJo9iIQ9hDg4XgNAZeeXoqJ2yciIzsDVQKr4PNOn6t/RacKnbArchfWbF+Drq273raztENLuw789zuw90cg+pBph2aJyOEwECLr2vkNsGOW9rV08K3/KNB+eNGdpf1LA+2GAW3eBM5tBPZ8D5xYAYTv1G4rRwGNBgBNXwBK14S9k0VPZ+yZgZ+P/ay2O1boiCntpuRZ6V2CnmZlmiHGM0b961RBkGR/Lu3WAuPDfwJZqdp+V0/AzQ3IvLF9Cxct0JZrjIicEgOhQrCPkAUc/1sLWETn8VoQJOQD3Ngp8q6uQLVO2k1mAO3/Cdg7D7h+EdjxlXaTDsHNXgDq9LbLtaKupl3FOxvfwa6oXWp7UMNBeK3ha3CVHjjOTlaEV9mfuUBMrqHR0nWAps9rq8Sf33Jj1hjyFU3fqKmSbKMzBY1ElAcDoUKwj5CZSX3Pope0Dyb5wGr31r0/pvSF6fAO0G44cOZfLUt0chVwYYt28y0FNHpKez47WUH82JVjGLp+KC4nX4avuy8mt5+MzhU7w6mp2p8dWvBzdIlWQC9kbbR6/YAmzwEVWtycBSZDqtKVvMClW3INuRKRU2IgRJYnvYAWPK4NX1TvAjw4w7RTl+Wv+xpdtdv1iBtZoh+BxEhg2/9pt6odtWEz6VTtVsRsNSv5++zf+GDbB0jTpaFiQEVVD1S9RHU4rZSrwMFftQAo7sTN/cF1tYxf/ccAn+IFf68EO/K75mK+RJQPAyGy/FDGz48BybFASH3gsbmAmxkvw8ByQMd3gfZvA6dWa1mi02uBsxu0m9QgNX5ayyKUqARbkJWdhc/3fY65R+aqbVkXbGr7qQj0CoRTZn8ubNUC2aNLtWUwhIevlv2RYLZcU+MC6TsZciUip8FAiCwnKx349Wkg7qS2tMGAhYBXgGWeW4Kt2g9qt/gLwL55WqZICrI3zwA2z9SyU5JZqNHNvMHZbVxPv67qgbZf1qZ9D6w/EEMaDXGuwmeRfEVbBV4CIFkI1UCC56Y3sj/exax5hkTkIBgIkWVkZwNLB2u1Ol7FgKcWAsXKWufVl8xP5/e0TJHMNNvzA3B2PXB6jXaThTebPKvdJKNkISfjT2Lov0NxKekSfNx9MLHtRHSv3B1Olf05v1kb+pJ2CroMbb+Hn1ZIL7Vd0njTlMOoROT0GAiRZayfBBxaqHWHlsLVMnWt/8pLbZD0G5LblTPAvh+B/T9rtUQbpwKbPtayQ9KXqHpns84sWn1+NcZtHYfUrFSU8y+n6oFqlawFp2DoCSWv/9WzN/dL0CPBjzTQtFTmkIicDgMhMj8Z3pDhJ9H7c22qu62RWWRdJwKdxmrZCMlKSHbi5ErtFlhBqyNq8ow2O81EdNk6zDowC3MOzVHbLcu2xCcdPkFx70KKfh0pQyj9n+R1ljYKhq7ingFAg8e01zq0kbXPkoicAAMhMi8pTF5+Y2q8dI2WwmRbJn2GZBhGbrEntQ9qyVZcD9eyWpIpqtVDyxJV6aj1MbpLCRkJeHfTu9gcsVltPxf2HIY1HQZ3yZo5qsTom9kfmT1oIAXPkv2p2w/wutkkkojI3Bz4HffesKGiCUQdAn5/DtDrgAZPAJ3GwK5IR+ruk7V6IpmxJLVE4Tu0jJHcSlTWPrwbPa11ur4DZ6+dxZvr38SFhAvwcvPCB20+QK+qveCw2Z+z/2pB5YmVQHaWtl9qxRo8DjR9TiuCJiKyAgZChWBDxXsk/Xt+7g9kJAGV2wN9vrDfIldZs6zhE9pNFnbd+wNw8Dcto7H2A+Dfj7Su1TLjTH7WIn7Ofy/+izFbxiA5MxkhfiGqHiisVBgcTsJl4MB8bYbetYs395dvcSP78zDg6WfNMyQiYiBEZpCWACzorxUdl64NPD4fcPd0jJe6TBjw4HSgywfAkcVaXyLpkn3kT+1WqsaNLNEAwLdknm/N1mfj64NfY/bB2Wpb1gP75L5PUMqnlJV+GDPI1mldvQ3ZH8kGCu9ALSso2R9bKJQnIrqBGSEyLV0msPA5IPqw1qxQpskX1u3XnkkmQ+qd5Hb5Py1LJGteSc+b1WOBdRO12WhSS1SxFZIykzF6y2hsCN+gvn1A7QF4u/nb8HC1za7Wd0x18J6v9WaSeiqDiq21wFBeC8msERHZGAZCZNo+MFIYLRkB6fz75K9A8YqO/wqXbQD0+lSbdXZokZYlivoPOPS7up0PromhJXxwNiNeBT7vtXoPfWv0hd3TZWnF8JL9OfUPoM/W9suMN8mIycyv4NrWPksiottiIESms/kTLSMgq6I/+gNQrolzvbrS60bqhCQDErlPFVdvOr0M73qnIDEjDcFZOnwW2Aj1fSpoQaO91kxdC9d+z5IBSoi4ub9SO23oq04fwMPbmmdIRGQ0BkJkGjIs9O8k7eseHwO1nKgjcn4uLtCHNsF3Vffj/65tgh56NNK54tPISwgKXwYcXgaUqQ80ex6o398+loqQ7I9kfVT2Z42k/7T9PiVvZn9klh0RkZ1hIET37txmYMnr2tdt3gBavOzUr2pKZorqEr3mggQMwGM1H8Po5u/CI3K/VkskRdbRh4C/RwCr39d6FkkmSTop2xpZl82Q/Um8fHO/zI6TzJfMlpPeS0REdoqBEN2b2BPAb09pnYGlILbLRKd+RcMTwlV/oNPXTqvGiKNbjEb/Wv21Oyu21G7dJgMHf9WCIlmAVpoLyk0tKSELij5q3WnlUvAuM74k+yP1Xobsj28Q0PgpLfsjnbiJiBwAAyG6e0kxwM+PAmnXtd4wfb+5p07L9m5b5Da1crx0jA7yCcLMjjPROLiALI9Mq2/9OtBqEHBhm1ZcfWwZIBkjua0eBzTorwVFIfUs9wNcPaf1/JHsT3LMzf1VO2rZn1o9HacNAhHRDQyE6O5kJGu9gqRRXsmq2gwxJ50erdfr8eORH/Hpvk9Vr6D6QfXxacdPUcavzO2/UYqlK7fVbslx2tITkoWRhUd3f6vdyjfXpuDX7Wue1zcrAzjxt7Ye3Nn1N/f7Bd/I/jyr/X6JiBwUAyG6u6Z5i17SshdSLPvUIsDPgZoC3gFZLf6DbR9gxbkVavvh6g9jXKtxatmMO+IXBLQdCrR+Azi/ScsSyWKkl3Zrt1XvAg2f1LJEppiSfuWMNhy3/2cgJe7GTheg2v03sj89ADcH6XFERHQbDITozsi0b/lQlhXZ5cNeMkFOWi8SmRSJoeuH4vjV43B3ccfIFiPxRK0n4HIv0+JlaFGGouSmFiidr2WJJPO282vtVrGNliUK65O3UDlbB5cLW1Du6na4XCgGVO0AuLrdvD8rHTi+XHu8c5tu7vcP0RpDNnlGWz+NiMiJMBAqBBddLcSOr4Bd/9O+7vc/rfjXCe26vAtvb3wb8enxKOFVAjM6zkDzkOamfZKAMkD7EUDbG00qpbhaipgvbtNuK29MXZcsUcxRYNUouCdEopl874XZQLFQoPs0IDgM2DcXOLAASLly48FdgBpdtcLnmt2Y/SEip8VAqBBcdLUAsgL7P2O1r7t+qC2a6YT1QAuOL8D03dOh0+tQp2QdtWhqWf+y5ntSyRLV6KLdEiKBfT9pw1rSzHD7l9qtIHLs78/k3RcQqmV+JAPkDF2/iYiKwECIjBO+C/jzFW0qdfOBWr8gJ5OuS8fE7ROx7Mwytd2rai+Mbz0e3u4W7KIsWZ6Oo7RM0ek1wO7vgdOri/6+Gt20XkXVuwJu/N+eiMiA74hkXGHtL08AWWlAze7acIu9Lg9xl6KSo/DW+rdw+MphuLq4YkTTEXgm7Jl7qwe6FxLMSEGzp79xgZAErlXaW+LMiIjsCgMhur3kK8DPj2m1JWUbAY9+73QZhX3R+/DWhrdwNe0qAr0CMb3DdLQObQ2bkBRt2uOIiJyMc32i0Z3JTAN+HQBcPQMEVgQG/G7djsdWqAdaeHIhpuycgix9FmqWqKnqgcoHlIfN8C9j2uOIiJwMAyEqWHY2sOQ1IHwH4BUIPLVQm8XkJDJ0GZi8czL+OPWH2u5WuRsmtpkIXw9f2JRKbbS6oYTLN5fCyMNFu1+OIyKiWzjvegh0e+s+0BYHdfUAnphvmiZ+diI2JRYv/vOiCoJc4IJhTYap4TCbC4KE9AmSmi0lf73Sje3uU/P2EyIiohwMhOhWu78Dtn6uff3Ql0CVDk7zKh2MPYjHlz+u/g3wDMBXXb7CS/Vfsl5RtDGksWL/eUCxfFP4JRMk++V+IiIqEIfGKK+T/wAr3ta+7jQWaPiE07xCf576E5N2TEJmdiaqBVbD5/d/jkrFKsEuSLBTuyeyzm7Cgc3/oFH7bnDP31maiIhuwUCIboo8ACx8AdBnaw33OrzjFK9Opi4T03ZPw28nflPb91e4H5PbT4afh50Vhru6QV+pHSKOJKBhpXYMgoiIjMBAiDTXwrXV5DOTtXWuen3mFL2CrqRewfANw7EvZp/aHtxoMF5p8IrqFURERI6PgRABqde0XkHSaya4rlZX4gQrjx+5cgRD/x2K6JRolf2Z0m4KOlXsZO3TIiIiC2Ig5OyyMrT1qGKPAQFlgad+B7wD4ej+OvMXJmyfoJbNqFyssqoHqhpY1dqnRUREFsZAyJnp9cBfbwLnNmlLNUjDxEAbahZoBlnZWZixZwbmH5uvtjuU74Cp7aeqGWJEROR8GAg5sw1TgYO/AC5uwGM/AmUbwJHFp8XjnY3vYGfUTrUttUBSE8R6ICIi58VAyFkdWABsnKp93XMGUKMLHNnxq8dVPVBkciR83H3wUbuP0LVSV2ufFhERWRkDIWd0dgOw7A3t63ZvAc1egCNbeW4l3t/6PtJ0aagQUEGtF1ajRA1rnxYREdkABkKFmDVrlrrpdDo4lOijwG/PANlZQL1HgPvfh6PSZevw+f7P8cPhH9R229C2mNZhmlpBnoiISLBZSiEGDx6Mo0ePYvfu3Y5zpcjCnDJNPj0BqNgGeHg24OqYl8D19OsYvG5wThD0Yr0XMavzLAZBRESUBzNCziI9SWuYmHAJKFUDeOJnwN0LjuhU/CkMXT8U4Ynh8HbzxsS2E9GjSg9rnxYREdkgBkLOQJcFLHoBiPoP8A0CnloI+JaEI1p7YS3GbBmD1KxUhPqFqv5AtUvWtvZpERGRjWIg5Ay9gla+A5xaDbj7aL2CSlaBo8nWZ2PWgVn433//U9stQ1pi+n3TUcK7hLVPjYiIbBgDIUe39XNgz/cAXIBHvgXKN4WjScxIxOjNo7Hx0ka1/UzYMxjedDjcXXl5ExHR7fGTwpEd/gNYO177uvsUoE4vOJqz18+q/kDnE87D09UTH7T5AL2r9bb2aRERkZ1gIOSoLmwHFr+mfd1yENBqEBzNhvANeHfzu0jOTEYZ3zKqP1DdoLrWPi0iIrIjDIQcUdxp4NcnAV0GULsX0O0jOFo9kNQCSU2QaBLcBDM6zkCQT5C1T42IiOwMAyFHkxwH/PwIkBoPlGsK9JsDuLrBUUj2Z+yWsVh3cZ3afqLWExjZfCQ83DysfWpERGSHGAg5ksxU4JcngPjzQPFKwJO/AZ6+cBQXEy7izX/fxJnrZ+Dh6oFxrcahX41+1j4tIiKyYwyEHEW2DvjzZeDSbsC7OPD0H4B/adjr0hj7YvYhNiUWpX1Lq6Gv7Ze3Y+SmkWqGWGmf0vi006doWLqhtU+ViIjsHAMhR7H6PeDYX4CbJ/DkL0BQDbttiDh111REp0Tn7PP38EdSZpL6WoKfTzt+qgIkIiKie8VAyBHs/AbYoRUOq/XDKrWBvQZBwzcMhx76PPsNQVCrsq3UemGeEuwRERGZgGOuuOlMjq8AVr2rfd15PFD/UdjrcJhkgvIHQbmdv34ebi6OU/hNRETWx0DInkXsBRa9COizgSbPAe3egr2SbFDu4bCCRKVEqdohIiIiU+HQmL2SmWELHgeyUoHqXYCeMwEXF9iDDF0Gjl09hoMxB3EwVrsVFQQZSAE1ERGRqTAQskfSI+jnx4DkWCCkPvDYXMDNdn+V0cnROQGP3I5dOYaM7Iw8x7jCFdnILvKxWCRNRESmZLufnlSwrHTg16eBuJNAsXLAgIWAV4DNvFqZukwt25Mr8IlKjrrluBJeJdQMsIbBDdW/tUvURt9lfRGTElNgnZALXNQyGjKVnoiIyFQYCNkTvR5YOgS4sAXwKgY8tRAoVtaqpySBiwp4bgxzHb1y9NZsj4srapaoqQU+N24VAirAJd9Q3rst3lWzxiToyR0MybYY1WIU3ByoSzYREVkfAyF78u8k4NDvgKs70H8eUKauxbM9x68ez5PtuZx8+ZbjinsVzxP01AuqB1+Pojtcd6nUBTM7zrylj5BkgiQIkvuJiIhMiYGQvdj7I7D5E+3r3p8D1TqZ/SnjUuPyFDQfuXIE6br0W7I9NYrXyDPMVTGg4i3ZHmNJsNOpQqdbOkszE0RERObAQMgenF4LLL8xNb7DSKDx0yZ/iszsTJy8ehIHYg+ooOe/2P8QkRRxy3GBXoG3ZHv8PPxMei4S9DQPaW7SxyQiInLKQOj69evo3LlzznZYWBjmzZsHuxF1CPj9eUCvAxo8AXQaY7psj2GIK0ar7UnTpeU5RmpzqpeonifwqVys8l1ne4iIiGyNwwdCmZmZuHbtGn799Ve17edn2uyFWV2PAH7uD2QkApXbA32+uKteQSrbE38yzzBXQdmeYp7F0KB0g5ygp35Qffh7+pvohyEiIrI9NhEI6XQ6JCYmwt/fH+7uBZ9Sdna2Cmq8vLzu+PHj4uIwYsQIlC1bFqNGjYJdSEsAFvQHEiOB0rWBx+cD7satsXUl9UqeguYjcUcKzPZUK17tZrYnWMv2SM0PERGRs7BqIBQZGYn//e9/+PbbbxEREYH169ejY8eOeY5JS0vDG2+8gfnz5yMrKwuNGjXCnDlz1L/i4MGDeOmllwp8/J07d6J48eJYu3at+t6tW7eiW7duOHbsGEqVKgWbpcsEFj4HRB8G/IKBAb8DPsULPDQrO0vL9uQa5rqUdOmW4wI8A27J9sg+IiIiZ2bVQOjHH3+EXq/HokWL0Lp16wKPkUyOBDIS8JQrVw5Dhw5Fjx49cPLkSQQEBKBatWr4+uuvC/xeNzet50yzZs3Uv61atVKPtXfvXjzwwAOw5gKje6L34GDGQQRHB6NFaIubs6KkV5AURp/5F5Ap5wN+A0pUyvneq2lXb5nJlSrLbNwu2yO1PYHM9hAREdlUIDR69Gj176VLt2YwRFJSEr777jt88cUXqFmzpto3Y8YM/PTTT/jtt98wcOBANZxmCHSKEhMTg8OHDyM0NBTWXFw0d5+chesWqj450kxQ9cmRKfL7fwJcXJH1yLc45e2Dg8d/zQl8whPDb3nMAI982Z7SzPYQERHZTY1QYQ4cOID09HS0b98+Z19gYCAaNmyohr0kECrK0qVL8eGHH6o6pLNnz2Lw4MGoV69eocfL88nNICEhQf0r9Ulyuxfrwtdh5OaRtywhId2ZpaPy+PIPotSeuThYIhAHyobhyN4Jt2R7RNXAqmgQ1CDnVlBtz72eK9knw++dv3/itUXO/p6VaeRj2nQgJBkcERQUlGd/cHBwzn1Fadu2rRo6kyLsypUrq5qh25kyZQomTJhwy/7Vq1fD17fo7siFydZn45OETwpcR8uw74NLfwMhpbWdqdqsLi94oYJ7BVRwq4CK7hVR3q08fFx8gCtSFQ0cP3Ec8h9RbmvWrOELQmbBa4vs5bpKSUmx/0Ao94yx3KTwubDZZflJEJU/kCpquG748OF5MkIVKlRQNUXFihXD3ZKaoIR1WnbpdsrCAy2q9kCDoIYq21MlsApnctEd/QUkbyhdu3aFh4cHXzkyGV5bZG/XlWFEx64DISmOFtHR0SoLZCDZICl8NgeZnl/QFH35Bd3LLyk+I96o44a1eR8P1nj4rp+HyBTXK1FheG2RvVxXxj6eTTeNkVogKYZet25dniDov//+Q7t27WBPZM0so44L0II/IiIiMj+rBkIZGRmq67MhfSWzxGRbegcJb29vNUw1adIklTo7ceIEXnjhBTVl/pFHHjHruc2aNUstx9G8uWnWvGoS1BBldHq4yPT4Asj+EJ1eHUdEREROEAhJ/yApYG7Tpo2aDfb000+r7c8++yznmPHjx2PYsGFqtpc0W/Tx8VFB0d10mL4T8nxHjx7F7t27TfJ4buE78W5cnPo6fzBk2B4VF6eOIyIiIsuwao3QgAED1O12XF1dMW7cOHWza0nR6JKSipkxcZhaqgSicxV7l9HpMOpKvLpfjiMiIiLLsOliaYfiX0b9I8FOp5RU7PP2QqybG0rrdGiSlg63fMcRERGR+TEQspRKbYBioUDCZbhBj+ZpN5s2aly0++U4IiIisgibnjVmTaYuloasJdZ92o0Nl3x33tjuPlU7joiIiCyCgZCFiqWVsD5A/3lAsbJ590smSPbL/URERGQxHBqzNAl2avdE1tlNOLD5HzRq3w3uVTswE0RERGQFzAhZ5VV3g75SO0SUbK3+5XAYERGRdTAQIiIiIqfFQIiIiIicFgMhS80aIyIiIpvDQMiSs8aIiIjIpjAQIiIiIqfFQIiIiIicFgMhIiIicloMhIiIiMhpMRAiIiIip8UlNm4zfV5uWVlZajshIcGkL3xmZiZSUlLU43p4eJj0scl58boiXltkTzLN+Flo+NzW6/W3Pc5FX9QRTu7SpUuoUKGCtU+DiIiI7kJ4eDjKly9f6P0MhIqQnZ2NyMhIBAQEwMXFpcBjpOliYf2GCrtPIlUJsOQXVKxYMdiL2/2stvxcd/tYd/p9xh5vzHFFHVPQ/fZ6XVny2rKF6+pOv9fa15U9X1u8rmzj2mpuhetK8jyJiYkIDQ2Fq2vhlUAcGiuCvHi3iySFm5tbob/A290n5D57elMp6uex1ee628e60+8z9nhjjivqmNvdb2/XlSWvLVu4ru70e23lurLHa4vXlW1cW25Wuq4CAwOLPIbF0ibqQn0399kjS/48pnyuu32sO/0+Y4835riijuG1Zb/X1Z1+L6+ru2ep/0/s8bqy5HvWYBv+LOTQmJVIOlAi1evXr9vVX1dk23hdEa8tsicJNvBZyIyQlXh5eWH8+PHqXyJeV2Tr+J5FjnpdMSNERERETosZISIiInJaDISIiIjIaXH6PBERmcS1a9dU8WvFihX5itI9u3LlCuLj43O2y5YtCz8/P5gaM0I2aO/evWjTpg28vb3RqlUrHDlyxNqnRA4gIyMDI0eORIkSJVSDsa+++srap0QORJZJ6NWrFx544AFrnwo5iClTpqBdu3bo3r27um3evNksz8NAyAb9+uuvmDlzpvrr6v7771cfXkT3at++fahUqRIuXryINWvWYMyYMeovLiJTkPepoUOH8sUkk/rwww+xdetWnD59WgVD5sChMTNmdWJjY9GtW7cCl+ZIS0tTx7i7u6NJkyZ5FpubPn16ztdyn6x3RiSkDb1kCBs3bowyZcoU+KL8999/Kp1cv359lCxZMme/ZBflJosc6nQ61btDso5Ecl2tWrUK1atXR6dOnQp8QeTD6OTJk2rYq2PHjqpTsMHvv/+OBg0aqOuSyCA5ORnLli1T2ejnnnsOBTl79iy2bNmihry6dOmSpxN0UFAQPv74Y4wePVq93y1ZsgQ1atSAycmiq2Q68+bN0zdo0EAfEhIii9nqMzMzbzlm48aN+tKlS+tr166tr1Klir5ixYr6AwcO3HLc6dOn9a1bt9ZfvHiRvyInt2/fPv1DDz2kL1++vLquFi5ceMsxcXFx+hYtWqhrq0mTJnpfX1/9nDlz8hyzY8cOvaurq7p9/vnnFvwJyBZFRUXp+/Tpo69UqZK+TJky+ueee+6WY7KysvT9+vXTBwcH6/v376/er9q2batPTExU9589e1bft29f/alTp/Tr1q3TV61aVX/+/Hkr/DRkS0aOHKkvW7asvk6dOvpSpUoVeMzs2bP1Pj4++t69e+tbtmypDwoKUu91+WVnZ+snTJigrjNzYCBkYp988ol+//79+l9++aXAQCglJUUFSUOHDs35BT/22GPqYpGvDfbs2aPebPiGQmLJkiX6P//8U5+UlFRoIDRgwAB9w4YN1TFi7ty5ejc3N/3x48dvOfbcuXMqEJdrlZxXeHi4urYk2OncuXOBgZAE0/7+/irgMQTcoaGh+nHjxqntH374QV+tWjV1kyDJw8NDf99991n8ZyHbMnfuXH18fLz+008/LTAQunDhgt7T01P//fff5+x75JFH9I0aNSrw8Q4ePFjoffeKgZCZFBYILV68WO/i4qKPiIjI2bd37151rPy1Lv7++28VHV+6dEl9v7xJEYnU1NQCAyEJfuRNJXcGSALrcuXK5XxgyRvOX3/9pU9ISNAfOXJE/eW+a9cuvrCkFBYIyX75Yy23ESNG6KtXr37LsZIVqlWrFl9RylFYICT7AwMD9RkZGTn71q9fr97fDH+8SbAk15S8T/Xo0UP/5ptv6s2BxdIWduDAATXWKbN2DBo1aqRWuT948KDanjhxIvbs2aMKW6WGo2nTppY+TbIzx44dU+PwuWs0pDatYcOGOddVnz59MH/+fFSoUEF9PWTIEDRv3tyKZ032QGrSwsLC8uyT7TNnziA9PT3Pfk9PT/W+RWTMdSU1abnrYw3XmWGm9AsvvKAKpAcNGoR69eph8uTJMAcWS1uYzATLXcAqJAiSAjFDv4QdO3ZY+rTIAa4rkf/aKlWqFM6dO5fztcxIJLoT0heoePHiefZJCwYZUUhMTMyzRpQUUv/zzz98gemuryvDfWLdunWwBGaELEz+YkpNTb1lv+yT+4ju9royXEf5e7vwuqJ74ePjowKe3AwfVL6+vnxxye6vKwZCFla5cmVER0erKcwGcXFxajq93Ed0t9eVYRp0btJ6gdcV3QuZrmzIKhrItnT5ZSBE93JdXbhwQWUWDQzXmQyZWRIDIQuTrqvyV/vq1atz9v3xxx8qvSy9OYjuhgxJ1KlTR/XZMJCgaPfu3WZrQkbOQerJli9fnvPXutSiSd8g2U90t3r37q2SArmHvww1jFI3a0msETIxKfKSDyBDgaoEPFIDJEWpUqMhka4Ufr300kuYMGGCygSNGzcOY8eOzRkfJcpPmnNKA05DJlGK7v39/VVhqgRAQhqP9e3bV11Hsm/GjBlo2bIl+vXrxxeUCvX111+rfyMiIlQDPNkOCAjAU089pfa/8cYbWLBggepy/8gjj6j3NAmK3n//fb6qVKiVK1eqjM+2bdvU55zhOnv88cfVe5Q04JQJG08++SRef/11FRR99913WLRokfrMtCQXmTpm0Wd0cJ999pnq0Jrf1KlTc6Lc7OxsfP/99+o46Sz98MMP44knnrDC2ZK92LVrV4EfPPJXubyJGMhaPHJtSeG9BN/Dhg0zyyKF5Dhee+21W/bJH20fffRRzrYESHPnzsWJEydU9vH5559XXX+JCiNrGUqX+/zkfSz3rOmlS5di48aNapi1f//+KkCyNAZCRERE5LRYI0REREROi4EQEREROS0GQkREROS0GAgRERGR02IgRERERE6LgRARERE5LQZCRERE5LQYCBGR2UgjPlnxPikpyeZe5VOnTmHx4sVcLZ3IyTEQIiKzLg0iLfSjoqJs6lWW7tvSeVvWNlq/fr1VzkGWHZAg8fr16xZ/bllQQJ5bbrL2YX6yjIbcJ4v2Ejk6dpYmIrM5f/48qlSporIvll5R+nZat26tlicZPXq01c5BgkNZwf3QoUOoV6+eRZ87KysLHh4e6usff/wRzz77bM59MTExauFLWVx14cKFePTRRy16bkSWxowQkZMMTZ09exZ///13zoLABrIumRxjWNDV8EEp+65cuXLL45w+fRp//fVXnseRNYVkzSC5rzAnT55U3ycf/AWRhTz/+ecfdZMFGAv7OQ4fPow///xTLeh4uyBj2bJl6rESExNz9svPKI9z7tw5nDlzRn197NixW76/sJ9FhtIkqDPmnA3k+desWaOyLIZzkYyMPIeQNQflPHKvwl3Y+Rf1WsjPJ+vNye/ZmGxOhw4d1EKXuc2bN08FikROQxZdJSLHdO7cOVlUWd+7d299WFiYvmfPnnp/f3/9G2+8kXPM7t271THx8fE5+xITE9W+7du353mc++67T1+/fn19jx499B4eHvrhw4frn3zySX3Dhg313bt313t5eel//PHHW55fjq9evbr+gQce0Pv6+uqHDh2a5zwXLVqkL1GihL5Dhw7qcQIDA/VffvnlLY/Tq1cvfa1atfSPPfaYfvPmzQX+zF988YXex8dH37FjR33jxo31JUuW1K9fv17dl5ycrH/88cfVa9CsWTP19dKlS295jEcffVTdl9vRo0fVORw8eNCocxYLFy5U+xs1aqR+9ho1auh37Nih1+l0+oceekg9nnyvPNf48eOLPP/bvRbh4eH6qlWr6uvVq6fv06eP+nrChAkFvkaZmZnqMb755hu9t7e3/tSpUzn31a5dWz9v3jx1v5w/kaNjIETkwAwfmi+++KI+Oztb7duwYYPexcVFf+HChTsOhAYOHJjzOLNnz1b7hgwZkvN906dP11eoUOGW5+/SpYs+IyND7du5c6fe1dVVv2nTJrV99uxZvZ+fnzovAzkn+YA+duxYnsfp37+/CiIKIx/oEqD9+uuvOfsk6KtcubI+LS0tZ1+1atXU+Rdm8eLFKhhJSEjI2Td27FgVBBp7znIunp6e+s8++yznmKioqJzX9PLly+pnOnTo0B2df2GvxQcffKBv06ZNzrbct2TJktsGQhLoPPHEE/rRo0er/RJQlSpVSn/9+nUGQuQ0ODRG5AReffVVuLi4qK/btWsHV1dXNVR1p1555ZWcxzEMn8g+A9kXHh5+SwHusGHDcmpSWrRogY4dO+L3339X27/88gtKlSqlCqulJkX2yzBeyZIl1TBPboMHD1bnXpg//vhD1bc8/vjjOfvGjh2rapV27txp9M/54IMPwsfHRw2FGSxYsABPP/200ecsx4SEhODNN9/MeYwyZcqgVatWJjn//K+FnK8MZUZERKhtue+hhx4q8md96aWXVJ2QTqfDt99+i2eeeQaenp5GvlJE9s/d2idAROYnH9AGbm5ucHd3V7OW7lSJEiVyvvby8ip0X3p6uvpgNqhcuXKex5ECakNdi3zIS2HuokWL8hzTvn17BAUF5dknxcW3I49ZtWrVPPsk+PDz87ttTVF+EghIkfDPP/+sCom3bdumvn/AgAFGn/PFixdVgbghcDTGnZx//tdi0KBBOHDgAGrUqIGwsDB07doVQ4YMQbly5W77nJ07d1Y/rwRzEtTt2LHD6PMlcgQMhIicnCGrkJ2dnbPvboKk25GC7PzbhoChWLFiKpiSAuCiFBVUyGPmz5xIUJaSknJLUFUUyf506tRJFS5LQHTfffehfPnyRp9z8eLFc4rNjXUn55//tQgICFBZKymm3rp1K7744gs0a9ZMFX1LIFUYeZwXXnhBBVIye61+/fom//0T2TIOjRE5OUPGIPcsKVP31lmyZEnO19I3R2ZItW3bVm13795dzdzauHFjnu+R2VL5Z0wVRYb9ZDabzAgzkKyNt7c3mjRpcsePJYGP9BqSbIlhWMzYc37ggQfUbDrJ0uQmw2nC399f/Zs76LiX8zcMiUnQI8/9f//3fyqIMyYTJoGQ/EwyDEfkbJgRInJyMvQiH5xSKyL1LJcvX8ZPP/1k0ueQx5Mp+bVr18acOXNUHYyhd02XLl1UnVGvXr3wxhtvoFq1ajhx4oSqz5Ep55LpMJb8HFLfI8NCUpckQde0adPw3nvvqZ/zTkimRIbCJkyYoM49dz8dY85ZzkHqbWToaejQoQgODlYBofzc8rgSCNWqVUudX9++fXN+D3d7/rNnz8b27dvV90s2SrJYDRo0QM2aNYv8WeX3YUxGjsgRMSNE5MAkOyCFt/mDif79++epHZFeNJLxkPoQKWresGGD+j7DcExBjyPDQ7LP19c3Ty2S7DMU2xq+b9OmTQgNDcWuXbtUAa8UFBuKp8U333yjMh/SG0fqceRDXz7UpZbodj9HYQXHktmQTExkZCR+++23WxonSgBjTIPH559/Hj179lSBiPy8uRV1zmLu3LmqAFnOQ3r+jBgxIqfOSCxfvlzVBK1YsSKnyLqo8y/stZg0aZI6Twlk5fcowZU8ptSDFTQcKo8hAVBBpI7sdvcTORJ2liYiIiKnxYwQEREROS0GQkREROS0GAgRERGR02IgRERERE6LgRARERE5LQZCRERE5LQYCBEREZHTYiBERERETouBEBERETktBkJERETktBgIERERkdNiIERERERwVv8PjNx+AwZyLZoAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sizes = np.array([100, 300, 1000, 3000, 10_000, 30_000, 100_000])\n",
    "\n",
    "t_python = []\n",
    "t_matmul = []\n",
    "t_einsum = []\n",
    "\n",
    "for M in sizes:\n",
    "    xx = x[:M]\n",
    "\n",
    "    tp = np.nan\n",
    "    if M <= 10_000:\n",
    "        tp, _ = median_time(lambda xx=xx: quadratic_python(xx, A), 3)\n",
    "\n",
    "    tm, _ = median_time(lambda xx=xx: quadratic_matmul(xx, A), 5)\n",
    "    te, _ = median_time(lambda xx=xx: quadratic_einsum(xx, A), 5)\n",
    "\n",
    "    t_python.append(tp)\n",
    "    t_matmul.append(tm)\n",
    "    t_einsum.append(te)\n",
    "\n",
    "t_python = np.array(t_python)\n",
    "t_matmul = np.array(t_matmul)\n",
    "t_einsum = np.array(t_einsum)\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "\n",
    "mask = np.isfinite(t_python)\n",
    "ax.loglog(sizes[mask], t_python[mask], \"o-\", label=\"Python loop\")\n",
    "ax.loglog(sizes, t_matmul, \"o-\", label=\"matmul + sum\")\n",
    "ax.loglog(sizes, t_einsum, \"o-\", label=\"einsum\")\n",
    "\n",
    "ax.set_xlabel(\"number of vectors M\")\n",
    "ax.set_ylabel(\"time [s]\")\n",
    "ax.legend()\n",
    "ax.grid(True)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76e0d53c",
   "metadata": {},
   "source": [
    "The Python loop is usually much slower because every iteration incurs Python overhead.\n",
    "\n",
    "Both vectorized versions execute most work in compiled code.\n",
    "\n",
    "Which vectorized version wins depends on shapes, NumPy version, BLAS library, and hardware.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "330b27b3",
   "metadata": {},
   "source": [
    "## 7. Temporary arrays: broadcasting vs `einsum`\n",
    "\n",
    "For row-wise dot products,\n",
    "\n",
    "$$\n",
    "s_n = a_{ni}b_{ni},\n",
    "$$\n",
    "\n",
    "one common implementation is\n",
    "\n",
    "```python\n",
    "(a*b).sum(axis=1)\n",
    "```\n",
    "\n",
    "which explicitly creates the temporary array `a*b`.\n",
    "\n",
    "`einsum` can express the contraction directly:\n",
    "\n",
    "```python\n",
    "np.einsum(\"ni,ni->n\", a, b)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "c99732b0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max difference: 4.440892098500626e-16\n",
      "broadcast multiply + sum: 0.012266 s\n",
      "einsum                  : 0.003244 s\n",
      "ratio broadcast/einsum  : 3.78\n",
      "\n",
      "Size of one temporary (a*b): 11.444091796875 MiB\n"
     ]
    }
   ],
   "source": [
    "M = 500_000\n",
    "D = 3\n",
    "\n",
    "a = rng.random((M, D))\n",
    "b = rng.random((M, D))\n",
    "\n",
    "tb, r1 = median_time(lambda: (a*b).sum(axis=1), 5)\n",
    "te, r2 = median_time(lambda: np.einsum(\"ni,ni->n\", a, b), 5)\n",
    "\n",
    "print(\"max difference:\", np.max(np.abs(r1-r2)))\n",
    "print(f\"broadcast multiply + sum: {tb:.6f} s\")\n",
    "print(f\"einsum                  : {te:.6f} s\")\n",
    "print(f\"ratio broadcast/einsum  : {tb/te:.2f}\")\n",
    "\n",
    "print(\"\\nSize of one temporary (a*b):\",\n",
    "      a.nbytes/1024**2, \"MiB\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d52954de",
   "metadata": {},
   "source": [
    "This is a typical case where `einsum` may gain from reduced temporary-memory traffic.\n",
    "\n",
    "Both methods have the same asymptotic complexity,\n",
    "\n",
    "$$\n",
    "O(MD),\n",
    "$$\n",
    "\n",
    "so any speedup is primarily a constant-factor / memory-bandwidth effect.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24581c72",
   "metadata": {},
   "source": [
    "## 8. Matrix multiplication: `@` may be faster than `einsum`\n",
    "\n",
    "For\n",
    "\n",
    "$$\n",
    "C_{ij}=A_{ik}B_{kj},\n",
    "$$\n",
    "\n",
    "the most natural implementation is usually\n",
    "\n",
    "```python\n",
    "A @ B\n",
    "```\n",
    "\n",
    "because it maps directly to optimized BLAS matrix multiplication.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "123d2cff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max difference: 0.0\n",
      "A @ B                 : 0.002618 s\n",
      "einsum optimize=False : 0.061680 s\n",
      "einsum optimize=True  : 0.002489 s\n"
     ]
    }
   ],
   "source": [
    "N = 600\n",
    "\n",
    "A2 = rng.random((N, N))\n",
    "B2 = rng.random((N, N))\n",
    "\n",
    "t_mm, C1 = median_time(lambda: A2 @ B2, 3)\n",
    "t_e0, C2 = median_time(\n",
    "    lambda: np.einsum(\"ik,kj->ij\", A2, B2, optimize=False), 3\n",
    ")\n",
    "t_e1, C3 = median_time(\n",
    "    lambda: np.einsum(\"ik,kj->ij\", A2, B2, optimize=True), 3\n",
    ")\n",
    "\n",
    "print(\"max difference:\", np.max(np.abs(C1-C3)))\n",
    "print(f\"A @ B                 : {t_mm:.6f} s\")\n",
    "print(f\"einsum optimize=False : {t_e0:.6f} s\")\n",
    "print(f\"einsum optimize=True  : {t_e1:.6f} s\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7527d89",
   "metadata": {},
   "source": [
    "For ordinary matrix multiplication, A @ B is the clearest and most idiomatic expression. einsum(..., optimize=True) may achieve comparable or even slightly better performance, depending on NumPy, BLAS, matrix sizes, and hardware.\n",
    "\n",
    "If `@`, `dot`, or a linear-algebra routine expresses the mathematics directly, it is often preferable.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "8146caf1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  100: matmul=0.000078s, einsum=0.000084s, ratio=1.08, matmul faster\n",
      "  200: matmul=0.000158s, einsum=0.000247s, ratio=1.57, matmul faster\n",
      "  400: matmul=0.000747s, einsum=0.001028s, ratio=1.38, matmul faster\n",
      "  600: matmul=0.002940s, einsum=0.003498s, ratio=1.19, matmul faster\n",
      "  800: matmul=0.013455s, einsum=0.008300s, ratio=0.62, opt. einsum faster\n",
      " 1200: matmul=0.025819s, einsum=0.032908s, ratio=1.27, matmul faster\n"
     ]
    }
   ],
   "source": [
    "sizes = [100, 200, 400, 600, 800, 1200]\n",
    "\n",
    "for N in sizes:\n",
    "    A = rng.random((N, N))\n",
    "    B = rng.random((N, N))\n",
    "\n",
    "    t_mm, _ = median_time(lambda: A @ B, 5)\n",
    "    t_e, _ = median_time(\n",
    "        lambda: np.einsum(\"ik,kj->ij\", A, B, optimize=True),\n",
    "        5\n",
    "    )\n",
    "    verdict = \"opt. einsum faster\" if t_e < t_mm else \"matmul faster\"\n",
    "    print(f\"{N:5}: matmul={t_mm:.6f}s, einsum={t_e:.6f}s, ratio={t_e/t_mm:.2f}, {verdict}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ade9d6e",
   "metadata": {},
   "source": [
    "## 9. Why `optimize=True` can matter\n",
    "\n",
    "For several tensors, there may be many equivalent contraction orders.\n",
    "\n",
    "Consider\n",
    "\n",
    "$$\n",
    "D_{il}=A_{ij}B_{jk}C_{kl}.\n",
    "$$\n",
    "\n",
    "One can compute\n",
    "\n",
    "$$\n",
    "(AB)C\n",
    "$$\n",
    "\n",
    "or\n",
    "\n",
    "$$\n",
    "A(BC).\n",
    "$$\n",
    "\n",
    "The intermediate sizes and FLOP counts can differ.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "d8d6f85d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max difference: 1.2050804798491299e-11\n",
      "optimize=False: 0.063769 s\n",
      "optimize=True : 0.000073 s\n",
      "speedup       : 873.67\n"
     ]
    }
   ],
   "source": [
    "A3 = rng.random((80, 200))\n",
    "B3 = rng.random((200, 40))\n",
    "C3 = rng.random((40, 150))\n",
    "\n",
    "expr = \"ij,jk,kl->il\"\n",
    "\n",
    "t_no, D1 = median_time(\n",
    "    lambda: np.einsum(expr, A3, B3, C3, optimize=False), 5\n",
    ")\n",
    "t_opt, D2 = median_time(\n",
    "    lambda: np.einsum(expr, A3, B3, C3, optimize=True), 5\n",
    ")\n",
    "\n",
    "print(\"max difference:\", np.max(np.abs(D1-D2)))\n",
    "print(f\"optimize=False: {t_no:.6f} s\")\n",
    "print(f\"optimize=True : {t_opt:.6f} s\")\n",
    "print(f\"speedup       : {t_no/t_opt:.2f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65d154d5",
   "metadata": {},
   "source": [
    "## 10. Inspect the contraction path\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "3931f7f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  Complete contraction:  ij,jk,kl->il\n",
      "         Naive scaling:  4\n",
      "     Optimized scaling:  3\n",
      "      Naive FLOP count:  2.880e+08\n",
      "  Optimized FLOP count:  2.240e+06\n",
      "   Theoretical speedup:  128.571\n",
      "  Largest intermediate:  1.200e+04 elements\n",
      "--------------------------------------------------------------------------\n",
      "scaling                  current                                remaining\n",
      "--------------------------------------------------------------------------\n",
      "   3                   jk,ij->ki                                kl,ki->il\n",
      "   3                   ki,kl->il                                   il->il\n"
     ]
    }
   ],
   "source": [
    "path, info = np.einsum_path(\n",
    "    expr,\n",
    "    A3, B3, C3,\n",
    "    optimize=\"optimal\"\n",
    ")\n",
    "\n",
    "print(info)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22313ad2",
   "metadata": {},
   "source": [
    "The path report includes estimated:\n",
    "\n",
    "- naive scaling,\n",
    "- optimized scaling,\n",
    "- FLOP count,\n",
    "- largest intermediate,\n",
    "- contraction order.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7073217e",
   "metadata": {},
   "source": [
    "## 11. Pairwise dot products\n",
    "\n",
    "For two sets of vectors,\n",
    "\n",
    "$$\n",
    "C_{ij}=\\sum_k a_{ik}b_{jk},\n",
    "$$\n",
    "\n",
    "we can write\n",
    "\n",
    "```python\n",
    "np.einsum(\"ik,jk->ij\", a, b)\n",
    "```\n",
    "\n",
    "or recognize it as matrix multiplication:\n",
    "\n",
    "```python\n",
    "a @ b.T\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "29f51936",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max difference: 4.440892098500626e-16\n",
      "einsum : 0.005924 s\n",
      "a@b.T  : 0.001922 s\n"
     ]
    }
   ],
   "source": [
    "N = 1200\n",
    "M = 900\n",
    "D = 3\n",
    "\n",
    "a = rng.random((N, D))\n",
    "b = rng.random((M, D))\n",
    "\n",
    "te, C1 = median_time(\n",
    "    lambda: np.einsum(\"ik,jk->ij\", a, b), 5\n",
    ")\n",
    "tm, C2 = median_time(\n",
    "    lambda: a @ b.T, 5\n",
    ")\n",
    "\n",
    "print(\"max difference:\", np.max(np.abs(C1-C2)))\n",
    "print(f\"einsum : {te:.6f} s\")\n",
    "print(f\"a@b.T  : {tm:.6f} s\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce5e8a0a",
   "metadata": {},
   "source": [
    "Again, matrix multiplication may win because the contraction has exactly BLAS matrix-multiplication structure.\n",
    "\n",
    "But the `einsum` notation generalizes immediately to higher-rank tensors.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "457eeb06",
   "metadata": {},
   "source": [
    "## 12. Higher-rank tensor contraction\n",
    "\n",
    "Consider\n",
    "\n",
    "$$\n",
    "C_{abij}\n",
    "=\n",
    "\\sum_k X_{aik}Y_{bkj}.\n",
    "$$\n",
    "\n",
    "With `einsum`:\n",
    "\n",
    "```python\n",
    "C = np.einsum(\"aik,bkj->abij\", X, Y)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "c26478d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "X shape: (10, 20, 30)\n",
      "Y shape: (12, 30, 18)\n",
      "C shape: (10, 12, 20, 18)\n"
     ]
    }
   ],
   "source": [
    "X = rng.random((10, 20, 30))\n",
    "Y = rng.random((12, 30, 18))\n",
    "\n",
    "C = np.einsum(\"aik,bkj->abij\", X, Y)\n",
    "\n",
    "print(\"X shape:\", X.shape)\n",
    "print(\"Y shape:\", Y.shape)\n",
    "print(\"C shape:\", C.shape)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1c35e6a",
   "metadata": {},
   "source": [
    "## 13. Scaling of a contraction where `einsum` avoids a temporary\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "ca84d50c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sizes = np.array([1_000, 3_000, 10_000, 30_000, 100_000, 300_000, 500_000])\n",
    "\n",
    "D = 3\n",
    "tb = []\n",
    "te = []\n",
    "\n",
    "for M in sizes:\n",
    "    aa = rng.random((M, D))\n",
    "    bb = rng.random((M, D))\n",
    "\n",
    "    t1, _ = median_time(lambda aa=aa, bb=bb:\n",
    "                        (aa*bb).sum(axis=1), 5)\n",
    "    t2, _ = median_time(lambda aa=aa, bb=bb:\n",
    "                        np.einsum(\"ni,ni->n\", aa, bb), 5)\n",
    "\n",
    "    tb.append(t1)\n",
    "    te.append(t2)\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.loglog(sizes, tb, \"o-\", label=\"multiply + sum\")\n",
    "ax.loglog(sizes, te, \"o-\", label=\"einsum\")\n",
    "ax.set_xlabel(\"number of rows M\")\n",
    "ax.set_ylabel(\"time [s]\")\n",
    "ax.legend()\n",
    "ax.grid(True)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75ecac1d",
   "metadata": {},
   "source": [
    "## 14. Performance lessons\n",
    "\n",
    "### `einsum` can be excellent when it\n",
    "\n",
    "- replaces explicit Python loops,\n",
    "- avoids large broadcasted temporaries,\n",
    "- expresses a difficult tensor contraction clearly,\n",
    "- allows an optimized contraction path.\n",
    "\n",
    "### It is not automatically best\n",
    "\n",
    "Use specialized operations such as\n",
    "\n",
    "```python\n",
    "A @ B\n",
    "np.dot(...)\n",
    "np.linalg.solve(...)\n",
    "```\n",
    "\n",
    "when they naturally match the mathematics.\n",
    "\n",
    "### Always measure\n",
    "\n",
    "Performance depends on\n",
    "\n",
    "- tensor shapes,\n",
    "- memory layout,\n",
    "- BLAS implementation,\n",
    "- NumPy version,\n",
    "- CPU architecture,\n",
    "- cache,\n",
    "- contraction path.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09c9dc44",
   "metadata": {},
   "source": [
    "## 15. Summary\n",
    "\n",
    "Examples:\n",
    "\n",
    "$$\n",
    "a_i b_i\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"i,i->\"}\n",
    "$$\n",
    "\n",
    "$$\n",
    "A_{ik}B_{kj}\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"ik,kj->ij\"}\n",
    "$$\n",
    "\n",
    "$$\n",
    "x_{ni}A_{ij}x_{nj}\n",
    "\\quad\\rightarrow\\quad\n",
    "\\texttt{\"ni,ij,nj->n\"}\n",
    "$$\n",
    "\n",
    "The main message is\n",
    "\n",
    "$$\n",
    "\\boxed{\n",
    "\\text{compact tensor notation}\n",
    "\\neq\n",
    "\\text{automatically fastest implementation}\n",
    "}\n",
    "$$\n",
    "\n",
    "The real strengths of `einsum` are compact tensor notation, removal of Python loops, reduced temporary memory in some contractions, and optimized contraction ordering.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python_env",
   "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.13.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
