{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "60736944-f728-4729-9275-0c55fa2b29ea",
   "metadata": {},
   "source": [
    "# Multiprocessing on GPU on scisoft16\n",
    "\n",
    "In this tutorial, the idea is to parallelized the reading of several HDF5 files in different processes and to perform the azimuthal integration on the GPU together with the Bitshuffle-LZ decompression.\n",
    "\n",
    "The size of the problem is 1000 files containing 1000 frames each of 4Mpix. That's one million frames. Reduced data are 1000x1000x1000 in single precision float.\n",
    "\n",
    "This is an extreme case where we will try how to use all resources of the computer, so the first thing to do is to determine the topology of the computer:\n",
    "* Levovo computer with AMD Epyc processors (Generation Genoa).\n",
    "* 1 processors, 8x4 cores. 4 cores share one L3-cache on 32MB\n",
    "* 1 Nvidia Tesla L40s GPUs (Generation Dad-lovelace).\n",
    "\n",
    "**Disclaimer:**\n",
    "* We use `multiprocess` instead of the `multiprocessing` library to be able to demonstrate the precedure with the jupuyter notebook.\n",
    "* We use `SharedArray` to provide a natural way of sharing the result array in memory between processes.\n",
    "\n",
    "Those python packages must be installed to be able to run the notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "64ae7c41-c094-4b79-96ec-020bff748f40",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Failed to open /tmp/topo.png for writing (File exists)\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABawAAAFSCAIAAACR6w8QAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd1xT1/sH8JNAkKGAA0VlyRTBRYMggkRAQBEE3BUXdYB121otiqg4q9a9cNaBq4ogiBMUBFSkihYVkFqGiqgMC4SQ8fvj2nzzS5AqEgLJ5/3qq6/k3Oeee557SeQ+3HsuzdnZmcViEQAAAAAAAAAA+ZWYmKjMYrHCwsJkPRIAAAAAAAAAACkKCwujy3oMAAAAAAAAAABNAUUQAAAAAAAAAFAIKIIAAAAAAAAAgEJAEQQAAAAAAAAAFAKKIAAAAAAAAACgEFAEAQAAAAAAAACFgCIIAAAAAAAAACiEuosgXC6XRqNNmTKFeltdXd2mTZuxY8d+ZqevX7/u06ePaEtZWVn37t0bML7KykpnZ2c+n8/n8/v162dkZGRoaPjDDz8IBIJnz57p/UtNTW3Dhg2SLYSQd+/eTZ48uUuXLt27d+/fv//58+epBOl0uqmpqampaa9evZKSkkQ3WlVVtWvXrk8Nqf6lwkwrKytZLBafz29A1gAAAAAAAADQ6D55JYimpmZGRgaHwyGEXLx40djY+Gs206ZNmxMnTjRgxf37948aNYpOp9Pp9Ojo6BcvXjx79iwlJeXy5csWFhaFhYWFhYUFBQUdO3b08/OTbCGEjB07tkuXLn///ffTp08jIyNzcnKonlVUVHJzc3Nzc1etWrV48WLRjX5NEURIQ0PD3t4+KiqqAVkDAAAAAAAAQKP7ZBGERqO5u7tfvnyZEHLq1KkxY8YIF7m7u9vY2FhbWx8+fJhqOXPmTO/evXv37j1y5EiqhcfjTZ06tXv37kOGDGGz2R8+fPj2228JIa9fv+7evfv8+fP9/Py8vLzYbDYh5OzZs/369bOxsZkwYUJNTY3oMI4fP+7r60u91tXVJYRQV4WIxiQnJ3fs2NHMzEyyJT09/cWLF+Hh4QwGgxBiZGS0aNEisUyrqqratm0r2rJy5cq//vqLxWItXbqUEBIREdGzZ09ra+sVK1ZILpXcG0J+fn7Hjh371B4GAAAAAAAAgKakXM+ysWPHbt68mcViFRQUBAQEZGZmUu0nT55s165ddXX1gAED/P393759+9NPP6WkpOjq6r5//56KefLkyeHDh/fv3z9hwoSoqChPT09ht3/99dfcuXONjIzmzJkTFRVlb2+/Y8eOpKSkVq1aLV++PCIiYtasWVQkm80uLCzU09MTrmtlZZWXlxcQEODh4SFsPHHiBFVhkWzJysrq27cvnV5HrYfD4XTv3p3NZpeWll6/fl10UWho6I0bNxITEwkhz549++WXX+7du6eqqspisQYMGCC6VHJviPbTt2/flJSUevYwAAAAAAAAADSZ+oog33zzTVZWVmRkpI+Pj2j7zp074+PjlZWVCwoK8vLy/vjjD29vb+oyjXbt2lExpqam33zzDSHE3t7+xYsXoqubmpoaGRkRQrp37/7ixQs2m/3ixQuqqFFdXe3i4iKMfPPmjbBDyp9//llWVubv73/nzh17e3tCCJfLPXfu3IMHD4QxYi0CgYB6sXDhwtjYWGoyEUKIiorK06dPCSHJycnjx4/PyspSUlKS3AnJycnDhg3T0tIihIwdO/bWrVti052I7Q0qNYqKikptbW1tbS11HQoAAAAAAAAAyFB9RRBCyNChQ5csWXLnzp3Hjx9TLVeuXElJSUlISFBRUfH09GSz2QKBgEajia2oqqpKvVBSUuJyuaKLhBUBOp3O5XIFAoGnp+eePXskt66mpkbdLyNKW1vbw8Pj4sWLVBHk8uXL1tbWnTt3FgaItvTo0WPVqlV8Pp9Op2/atGnFihWiRQqKo6Pj27dvi4qKDAwM6t8bkiT3hlgAj8dDBQQAAAAAAACgOfiPR+ROmzZt1apVpqamwpbS0tJu3bqpqKgUFxdT93qwWKzo6OhXr14RQkpKSr50BNTqeXl5hJCysrLnz58LF+no6LDZbGpy1jdv3vz9999UzIULFywtLamYeu6FIYQwmUwDA4OQkBCqk7KyMskB3L9/n8vlUleyUFq3bv3hwwfqtaOjY2xsbHl5eU1NzcmTJ52dnUWXSu4NUUVFRZI1FwAAAAAAAACQif+4EsTIyGjmzJmiLd7e3keOHBk9enTr1q1tbGwIIcbGxuvWrRs8eDAhxNLS8syZM180gm7duu3evdvPz4+6bWTr1q0mJibCpa6urikpKSwWq6ysbOTIkW/fvmUwGOPHj6fKHFVVVfHx8Tt37hTGS7acOnVqwYIF+vr6WlpaOjo6wktOampqqAqFmpra0aNHVVRUhKuoqqq6u7tbW1sPHjz4119/XbhwYf/+/QUCwdixY11dXQkhwqWrV68W2xuiEhISvLy8vmhvAAAAAAAAAICU0JYvXx4WFibrYXzSvXv3du3adejQIVkPpCG8vLx27tyJi0EAAAAAAAAAZC4sLOw/boeROVtbW0dHR7Fn4rYIVVVV48ePRwUEAAAAAAAAoJn4j9thmoPvvvtO1kNoCHV1dbHJSgAAAAAAAABAhpr7lSAAAAAAAAAAAI0CRRAAAAAAAAAAUAgoggAAAAAAAACAQqhjTpDExMTExMQmHwkAAAAAADRTLBaLxWKJtnzqrEEysrE0ny3KGRxckBXpHd961F0Eib1y2tbBsomHAgAAAAAAzdC9lCeEkDrPkyUbJSMbi0y2GBV7oo+tmTQ6byYe3MshOLggC3X+7DWBup8OY+tgGfyDfxMPBQAAAAAAmqON5+psZrFYYWFhoi1ibxtd02+xj63ZpGAvqW5C1mLrbMXBBemr+2dP2lrAI3IBAAAAAKClSExMlNK5q+SVAk2wRaMeGtLouYXCwQU5gIlRAQAAAAAAoGXAXB7wlXAlCAAAAAAANBrJmxoay6e6leoWX7y5J42eW6jmcHCpt18/DBxchYUrQQAAAAAAAABAIeBKEAAAAAAAaAjJ+Ro+NbNDy92iwsLBBXmFIggAAAAAAHyxOs9OWSyW9M5am36LCgsHF+QYiiAAAAAAAPDFGnyCGhAQcPny5U6dOj1+/JhqKSkp+e677+7evauurv7777/37du3mWxRYeHgghz7gjlBeFxe7y4TvPovHGI3f87kzRXllZ+z1ts35aPcfm7o8OoeBtNwSp2Ltq45dS/lyY34+we2x1Atd29neQ/4wbPf/G3rTjfiGAAAAAAAoGGmTZsWFxcn2hIUFGRvb//q1auHDx8aGho2/y3yeHzX3rMCvJaPHxK6dM7eDxVVhJAP5ZXrQn4b5frz+CGhi4N3/v38FY/H92DOFVv3wd3sid4rvvUMPbAtmmpZs+Sw38CfAv3CJTckGSzZUk9w01Pwg0sIEfAF34//Zc6kTdRbeTq4cuPLJkZlMJRjUzfFpW3W0FA7uveSlMbUYI8ynveyMUlPeWJjb0EIEQgEYQv3b94/NzZlY9rNxw/u5ch6gAAAAAAAis7Z2blt27bCt69fv05OTl60aBGNRmvTpk27du1axBaVGUrHYlcci1uhrtHq7NEbhJDwnw6176h18kr48UsrJ8wY8rLwneRaAoHgl7DjYZunHY0NS097+ueDPEKI14gB63bN/JzgOlevp+emp8gHlxJzNrmzXgfhW3k6uHKjIU+HodFodk5W+S+KCSEzxq4bPXipH2vxhVO3qKVXYu6MdP15pOvPC6ZuFV2r4EXx6MFLnzx6Uedah3fHDndaNGPsup9n7T4WEU8IuXrx7rdDQkcPXvrzrN0cTm39Q9q8MnKEy5LHD/IChoWdO5EY/tOhPZvPP/vzby1tDTNLfSVlpWEjHa/H4QFIAAAAAADNS25uroGBwaRJk3r06BEYGFhZ+VnXmzeTLdJoNBs7i6L8kpwnBUUFJVNn+ygp0QkhVn2M+ztb17HpZ4WaWurGZl2UlOjuw/olXX9ACOnNNGutqS4aVlXJ5vP5ksF1rl5PzzKnUAeXEFL2/kNC/H3fsc7CFjk+uC1XQ4ogtbXcm1cyTMy7EkI27Jl1+mr4yfiVx/df+edDdeHfb34NP7kn8qez19cs3/idcJW/cl8unLZ99bYZlj2NJNfKy3kZfTrp1JXwbYcXZD8pIIS8LCiJPHj18IVlp6+G6xl2/P1YYv1DWhA6bsXmacPHOEVeWmXeQ//3G2uDFvgVvyrt2Plj5U+3a/vi16UNSBYAAAAAAKSHy+VmZGQEBwc/fvxYIBBs2LChBW2RW8tNufnIyKTzX7kvzSwNaHRa/fFvi8s7dNSmXuvotn1bXFZn2KyAjTlPCiWD61n9M3tuYgp1cAkhuzeeC5ztTVeqL1JuDm7L9WUTo3I4tZ6282h0mk0/i4BpnoSQk4eu3U54qKSk9Prlu8L8N08f/c1yt+nQUYsQoqXdmlrrXUn53Mm/bj08v5tpF6pFbK2sB385ufZRVVMhhAzysCGE3Et58rKgJGjsBkIIm82xc+zxn2N7+viFRQ/Dv3JfGpt1/dgkEAiX8vn8L8oUAAAAAACagJ6enq6urqOjIyHE399///79LWKLtRzuOM9ldBrN2sZkRMCgpOsPRM8+Pu1/MQL+J+N/WjVB36jj+7flEsH1rP5ZPTcxhTq4D+5m02i0nn1NsjL/qidMbg5uy/VlRRAVFUb8vS3Ctyk3Hz24l33g9xAGQzlo3AYOu1YgEBCJslcbTY2Ondv+cSebKoLUsRYRP5ACARkwqNeyDYGfM6rsrPwVPx4oyi9pr6NVvb1GIBCM91p+8PeQTl3aFb98T8UUv3zfSbdt/f0AAAAAAEATMzU11dHRyczM7NWr19WrV62t677RoLltkaGiHBm/SvjWyKTzkd1xAr6g/usFOnTSLvn3z/glxWUdOmnXGWZhbVhncD2rf2bPTUyhDu7jh3n3056O81xWy+F+qKgKmb1n9fYgyTC5ObgtV0NuhxGqKKvsatCRwVB+V1L+MD2bEGLrYJl4OYM6QqXvKqgwlVbKWw/Njzp1Mz4qtc61+tiaJ11/wK7m1LA5CZczPvZzJaPw7zeEkA8VVQUviusZhnkPg+OxK/SNOp25tqZvP/Odx348HruilaqKeQ/D8rJ/srPyubW82N9vuwz55muSBQAAAACAr+fv7+/k5PTs2TM9Pb0DBw4QQvbs2RMQEGBubl5UVPTTTz+1xC2a9zDoot9h/7Zobi2XEPLng7zUm48kw0zN9T6UV+ZlF3G5vKuxdwe49K6zt/upTz9UVEkG17l6SmJmLYf7mT1LmyIf3IBpnmeur4mMX7Vyy3QLK4M6KyCkJR9cufFlV4KIYbnbRJ9O+mH6dnWNVpY9uxFC9Aw7zgsZO33MOkKIsVmXTRFzqEh1DdUdR3+YMWadqlorybVMzLt6j3Qc5fazbtf23a0NNVqrdTXQWbpuyrzALdxarjJD+adVE/SNOgm3y+HUuvWdTb0OmOY5eaZX6bsKTS0NOp324vkrY7OPN93Q6bSwjVMXTN1aw671GjGgbz+Lr0kWAAAAAAC+3rlz58Ra7O3tMzMzW/oWl60P3Lnh7OjBS1VaMbqZdp754wjJGBqdtjBs/PIFEZyaWjevfj37mhBCQufvy3r4V3npP6PdQibP9Brq77Bzw9mfwidaWBlIBku2rP35tyPRoe06aEouanqKfHDrJE8HV27Qli9fHhYWJtoUFhb25p9HwT/4N+U42NUcVTUVDqd22qh1P6+ZZGFl0JRbBwAAAACAT9m98VzH1j3FzhoUQVhY2Is39yYFe8l6IFJ0ZHesUUfbFnRwqaF+/YAV4eA2czL52QsLC/uqK0Ea0bplvz199HdNTa2XnwMqIAAAAAAAAADQ6JpLESRs41RZDwEAAAAAAAAA5NlXTYwKAAAAAAAAANBSoAgCAAAAAAAAAAoBRRAAAAAAAAAAUAifOyfIvZQn6SlPpDoUAAAAAACQOaaDpa2DpaxH0Xw9uJfzMD1b1qNooN5M8z62ZrIeRfPVog+ulMjfz8znFkHSU55kJhc52ttLdTQAAAAAACBDyWlphBAUQerxMD37acY7+xZ4ZpSWlkZItpyd0DaulntwpUQuf2a+4Okwjvb2i+fNld5QAAAAAABAttZtIRWkQNajaO7s7e3nzm2RZ0ZlnBxZD6G5a7kHV0rk72cGc4IAAAAAAAAAgEJAEQQAAAAAAAAAFAKKIAAAAAAAAACgEFAEAQAAAAAAAACFgCIIAAAAAAAAACgEFEEAAAAAAAAAQCGgCAIAAAAAAAAACuFriyBcHq9tN+Olq1dTb7dHRKzbsjW/sNCGxRLGLFuzZntEBBX5/Y+LqEY2m61nZf3d7DnCsIuXL7ftZpzz/Hk9Pdc/mO0REbsPHqx/tLoW3etctGLDhuS0tNgrV7bs3kO1JKWm2rq49nYauGrjRrFO2hmb2LBYfQY6fzttell5eQM6IYRUVVV5jRnL5/P5fL7rcN9ejk49BzguW7NGIBDk5OX16O9A/afb3XLr3r2SLYSQ96WlM3/4sbudfT9Xt8H+Iy5eviw6PBsWa4DnkNR796htDRs7js/n178DAQAAAACgYXg8nomJCYvFYrFYQ4YMuXfvHiGkoqLCzc3tM3soKSnx8vL60u1GREQcPHiwYevCZ+LxeMbGxqv/PTmNiIjYurWOk9Pq6upjx441yhbnz5//zTffeHh4UG/5fL6vr6+Tk5Ojo+OaNWsEAgEhZO/eve7u7qGhoYSQ1NRUV1fXgQMHbpQ48axHw9Zq6RrhShBVVdULcZfelZb+Z2Sb1q0fPn7Mqa0lhMTfuGFkYCC69PfoGHsm8/eYiw3o+evdf/CQ2afP7Tt3+9vaEkIEAsGcxUuO7N51PzEhMSn57v0M0WAVBiMjMfGPm4mtW2vsOnCwYZ38duqUr9dQOp1Op9Mj90dkJiel37h+537GjVu3zIyNs1JTslJT/ky5rdO+/TAPD8kWQsh3s+fodur46Hby3evXDmzb+vzFC9HhZSQmhixcELZ+PSFEXV3dtm/f2CtXpbsTAQAAAAAUGIPBSExMTExMXLBgwfr162U9HGhMqqqqly5dKq335JTNZh89erRRNjd27NhDhw4J39Lp9IiIiKSkpOvXr2dkZNy6dYsQcuzYsfPnz69cuVIgECxZsmTXrl0JCQnJyckZGRmf7vh/GraWHGiEIogKgzFx7JhdBw78ZySNRnNxcrpx6xYh5PzFi/7DhgkXVVZVpd2/v33D+nMxMfX3fOTkSQcPz/4eHuv/rb3tOnCwn9tg34CAx1lPqJYLly65Dvd1HuY9Y/6CGg6n/lGFrlk7wHNIxsOHg/1H/Hby5IKlSzds2/YoK6utllYPCwtlJaUx/n4xl+PrTMfZYUDe3y8a1smZqAte7u7U6446OoQQPp8v+P8Xa6Slp+u0b29iZCTZ8sejR/mFhUsXLmQoKxNCDPT05s6YITbC6mq2tpYW9drLw/10VFT9uwIAAAAAAL4em83W+vf3cKGTJ096enp6eHgILyKIi4sbOnTo0KFDZ86cKRqZn5/v7e39+PFjYUtFRcXEiRN9fHyGDh165coVQsjBgwcHDx4cEBDw5MnHkyAul7tw4UIfH58ZM2ZUVVVJMT2FxGAwxowZc+D/n5xeunTJ19fX29t7wYIFHA5n27ZthYWF48aN27RpUz1hJSUlbm5u4eHhQUFBgYGBNTU1ksfXzs5O7EdI599zRuoC/7CwsOLi4qlTp0ZHR2dlZWlpaVlYWCgpKfn5+cXHx0tuoqSkxMXFZdmyZbNmzQoODq6qqpJcS+o7sXlonDlBpk+adOrc+YoPH/4z0t/b+/eYmH8qK4tevrIwMxW2x1654uY80LRbN21t7Ycin3axnnPy8rbv3Rd/9kxiTMy1m7cSb9/Oef788IkTCdEXTuzb98ejR4SQ/MLCiCO/xZ05ffNijJGBwW8nT9Y/pJU/L9m+Yf23I0feiL5gZdn9dvylRXPmvCou7qyrSwV07dz55etiyRU5tbXx1693NzNrQCc1NTVFr193+XcpIcTe3aNbX5seFhYuAwcKG89ciB45fLjoRoUtz3JyellZ0el1HEFObW0/V7dejk7zQ0IWz5tHNfaysrp7/379uwIAAAAAABqstrbWzc3NyckpJCRk3r+/h1Py8vL27dt35syZmJiYW7du3b59Oz8/f/369UeOHImLi1uzZo0w8vnz599///3GjRutra2FjVevXrWwsIiOjo6LixswYMDz589PnDhx4cKFffv2PXr0iIrJyckJCAiIjo42MjLav39/06SsUCZNmnT+/PkP/56cFhYW/vbbb6dPn46JiTEwMDh58uScOXP09PQiIyMXLlwoXEsyjBBSUFAwefLkPXv2GBoaXrlyRez4fmoAHh4eNjY2FhYWAwcODAsL09bWjoyM9PHxKS4u1v331LJz587FxcWSmyCE5OfnBwYG7tixo3fv3vv3769zLUWg3Ci9aGlqjvbz3Xv4sKqqKiGERqOJBdDIx5Y+Pa2f5eSejY4e8v9vjTsbHR0cGEgIGeE97PfomN7/fuDFek67l+7h6qLZpg0VmXLnrl7XLh6uLhrq6oQQb08PQkhyWlp+YeGIiZMIIWw228mh/3+OP/PPP617WOY8f25h+rEuQ91kReHzBWLxNRxOL0cnOp1uz2RSw/7STkrevWur/f8Ke2lXLpdXVEwICr7/4AGzb19CCJfHuxgfnxQXK4wRaxH2v3T16is3EgQCwb0b1wkhKgzG3evXCCFp6enT585Lu3pFSUlJhcGo5XJruVzqyhEAAAAAAGhcDAbj2rVrhJD09PR58+ZRZ56U9PR0FxeXNm3aEEKGDRt29+7dV69eubq6Un/e19bWpsLevn07Y8aMvXv3mpiYiPZsbW29bds2BoPh5uZmY2ND9aaurk4IEU4boa+v37dvX0KIr6+vaFUFGoumpqavr+9h4clpWlphYeGkSR9PPPv3r/vEs84wIyMjPT09QoiJiUlRUdGgQYNEj++nBnD58uWKiorg4OAHDx5Qx5oieuIpfC22CUKIvr5+t27dCCEuLi6rVq2ysrKqswe512jnw99Pncoa5h0wehQhpF3btqVl/5su9H1pWQ8LC+HbwYNYKzf8ci3q/JNnz/4NKL2VkvrkWTaNRuPxeDQabcWSxXX2XHcO/57VM5QZhBCBgLg5O29eHf45w/7z6dN5S37+u6BAp0OHX6urBQLBYD//mMgTXXR1X75+RcW8fP2qi24n0bVaqahkJid9TSeqqqo1NTVig9HS1HQZ6BR/4wZVBLlx85alhXmnjh2FAaItFmZmv2zbzufz6XR6eEjIkvnzezk6iXVoz2S+Ky19VVys16ULIYTH46ECAgAAAAAgbUwms7S0tLi4uHXr1p+KEQgEkn881tTU7NSpU3p6ulgRxMLCIioqKiEhYe3atW5ubtra2v87CWIwqBeSvUGjmzp1qre396hRH09OnZ2dw8P/d+L5qRlDxMJKSkqEh49Go3G5XLHjO0NiogMhTU1NJyenGzduiBZBdHV1X736eOL56tWrTp06EZHTZGoThBDq/4SQ2traT62lCBrtEbnt27b1GeJ59NRpQoiGurqRgX7slSuEkOI3bxKTk+2YTGHkpLFjQxYsMDY0FLZcuHRpnL//o9vJmclJf6amGOrrp6Wn19mzvS3zyo2Eig8fajic32MuDrCz689kJqWk8ng8Pp9/IymJEOJobxd37dqL/HxCSHlFxV9/59czbKvu3a+eP2dkaJgUF2vPZJ4+eODq+XOqqqrWlpalZeV/Pn1ay+WePh8lnLyjsTrp0K4du4ZDzRFb8u5dQVERNdq4q9csTD5eSHIm+sJIn/9/L4xIS9+ePfW6dlm1cSPVSXlFheTAHjx6zOVyqQlHXr1+baCnV08WAAAAAADQKB4/fszlcqmrPChMJjMhIeHDhw8cDufixYt2dnZ2dnbXrl178+YNIeT9+/dUmIqKivCuGdEOX79+raGh4e/vP23atKysLCaTmZr68SQoKenjX2fz8/OpuS3Pnz/fr1+/JkpVwbRt29bT0/P06dOEEOoI5ufnE0IqKiry8/PV1dUrKyvFVpEMk+xW7PhKBrx79466mqOiouLatWumpqaiSy0tLcvLy58+fcrlcqOiotw/cfZaVFSUnp5O/v0J+cy15E9jXhcwZ/r0A0c/PhBoz6ZN80OWLluzhsFQCV30o7GhIZfHoxYZ6Ol9NyFAdMXfo2PmBwcJ3/oM8TxzIdpW5CogYc9mxsbfT5s62H+EQCAY4T3MeYADIcTf23vEpEk6HTrod+lCCDHU198cvipgRhB168e65aHdDP/3GJoaDqdHfwfqddCUyXOmT3/7/r22liadTs/NyzP/94eJTqdvXbt2YlAwu6ZmtK+vvUgRp04N6MTZweHu/fuO9vblFRWTgme+Ky1lKCuP8h0+crgPIaS6uvr6zVsbV64Uxku2HNyxI2RVuHV/B802bdq3bye8+IW6W4cQoqqquvfXzSoMBiEkKS3N3WVQ/VkAAAAAAECDcTgcJ6ePv4dv3ryZwWBUV1dTi4yNjadOnTpixAiBQDBs2DAHBwdCyKJFiyZMmEAIMTU13blzJxWprq5+4MCBiRMnqqmpCR+vm5mZuXnzZiUlpVatWq1evdrExMTb23vSpEkdOnTo0qULFWNubn7mzJlly5Z17dp1y5YtTZy74pg+fTr1HFx9ff1Vq1YFBQVxuVxlZeXQ0FADAwMnJycPDw8nJ6elS5dS8ZJhYpf5EInjSwgJDg7OyMh4//69g4PDvHnzbG1tZ86cWVpaqqysPHz4cB8fH9HV6XT62rVrg4ODa2pqfH19mUxmSUmJ5MjNzc1///330NBQPT29LVu2SK4llf3V/NCWL18eFhYm2hQWFvbmn0fBP/iLNu7eeE6Tq7943twmHZ1cy8jMPHD02M5fNjTN5kYHBm5cuRIXgwAAAABAPdZt2VqhXCB5LtCxdU+xswZFEBYW9uLNvUnBXqKNR3bHaquYzZ3b8s6Mtqd+2sMAACAASURBVG7dWsbJkUzHqKNtCzq41FC/fsBydnD/U0lJyeTJk2NjY/87VESdPzONRSY/e2FhYZghQmZsevX6k8mkJvWQ9raqq6tHD/dFBQQAAAAAAAAUGYogsjRhzOim2ZCamhp1lw0AAAAAAAAoGh0dnS+9DEReSf0aBAAAAAAAAACA5gBFEAAAAAAAAABQCCiCAAAAAAAAAIBC+II5QZLT0tbhQUsAAAAAAPIrOS2tl2NXWY+ipTp+/Pjhw4cJIcbGxps3b9bQ0Dh06NCJEyd4PN6YMWNmzJjB5/P9/f3fvXsnEAiGDh26ZMkSGo0mXL20tHTNmjVJSUmtW7fW0tKaPn26h4cHj8czNzfX19cnhKipqa1cudLW1lZG+SmEtLQ0WQ+hGUlLS+tu017Wo2hkn1sEYTpYEkIqSIE0BwMAAAAAALLUy7Er9Zs/fKny8vLNmzffuHFDS0trwYIFp06dcnR0PHLkSGxsLIPBGDdu3KBBg8zNzSMiInR0dGpqasaPH3/r1i1nZ2dhD3PmzOnVq1dycrKysnJhYaFwGksGg5GYmEgIuXr16vr168+ePSuTBBVBb6Y5IdllnBxZD6S56G7TvjfTXNajaGSfWwSxdbC0xbchAAAAAABAXQQCASGkpqaGx+PV1NR06tQpNze3V69eGhoahJB+/frFx8ebm5vr6OgQQvh8Pp/PF1390aNHhYWFR44codPphBA9Pb0ZM2aIbYLNZmtpaTVRPgqpj61ZH1szWY8CpAuPyAUAAAAAAPha2traixYtcnFxUVNTs7Gx8fLyys3NXbt27fv371VVVZOTk62tralIDw+P/Px8X1/fgQMHClfPycmxsrKiKiBiamtr3dzcampqysvLjx8/3kT5AMgpTIwKAAAAAADwtaqqqs6cOXPt2rW0tDQGg3H8+HFTU9PZs2dPmjRpypQpVlZWysof/wJ9+fLlO3fu5OfnP3jwQLQH6loSQsjq1avd3NxcXV2ptwwG49q1a0lJSQcPHpw3bx6Px2vKvADkDIogAAAAAAAAX+vu3buampq6urpKSkru7u4ZGRmEkNGjR8fExJw6dUpVVdXIyEgYrKmp6eTkdOPGDWGLmZlZVlYWdY9MSEhIdHR0WVmZ2CaYTGZpaWlxcXFT5AMgp1AEAQAAAAAA+Fpdu3bNysoqLS0VCARJSUnGxsaEkIKCAkJIVlZWfHy8n5/fu3fvioqKCCEVFRXXrl0zNTUVrt6zZ88uXbps3LixtraWCpDcxOPHj7lcLjWrCAA0zOfOCXIv5Ul6yhOpDgUApIopMb0xPtcALZ3k57o5wHcLQEvXPL9bmj8zM7PJkyf7+fnRaLQePXpMnjyZELJ48eLc3NzWrVuvX7++Xbt2f/3118yZM0tLS5WVlYcPH+7j4yPaw44dO8LDwx0cHNq0adOuXbvw8HCqncPhODk5EUJUVVU3b97MYDCaPDlF8eBezsP0bFmPQj71Zpo3k0lnP7cIkp7yJCv9HYvFkuZgAEBaEhMTCXki9gsNPtcALVqdn+vmAN8tAC1as/1uaRGCgoKCgoJEW8TmMe3WrdulS5c+tXrbtm03bdok1qikpJSXl9eIg4R6PEzPfprxzt7eXtYDkTdpaWmEZLewIgghhMVihYWFSW0kACBFYWFhb/55JNmOzzVAy/Wpz3VzgO8WgJarOX+3ADQBe3v7uXPnynoUcqiMkyPrIXyEOUEAAAAAAAAAQCGgCAIAAAAAAAAACgFFEAAAAAAAAABQCCiCAAAAAAAAAIBC+IKJUQEAAKCZ+9TjaT//IdlfEwkAoCDS0tJkPYSGSEtL627TXtajAJAxFEEAAADkR52Pp/38h2R/ZSQAgCLozTQnJLv5POri83W3ad+baS7rUQDIGIogAAAAckXy8bSf/5Dsr48EAJB7fWzN+tiayXoUANBAXzsnCJfLVVVVFW3h8/n9+vUzMjIyNDT84YcfBAKB6NLKykpnZ2c+ny8ZVlBQ4ObmpqenZ2JismPHDqpzGo02ZcoUat3q6uo2bdqMHTuWWkSn001NTU1NTXv16pWUlER1zmKx+Hz+VyYFoODwuQYAacB3CwAAtFzv37+fPXu2g4ODg4NDSEhIdXV1g7vi8XjGxsYO/8rLy5OMKSkp8fLy+lQPFRUVbm5uDR7Al3b4zTffUEN1dXWtMyA1NdXV1XXgwIEbN25sxFFJSeNPjEqn06Ojo1+8ePHs2bOUlJTLly+LLt2/f/+oUaPodHqdYaGhoYWFhampqevWrcvKyiKEaGpqZmRkcDgcQsjFixeNjY2FXamoqOTm5ubm5q5atWrx4sWEEA0NDXt7+6ioqEZPCkDB4XMNANKA7xYAAGgp5syZY2Fhcfv27aSkJIFAsHLlyq/pTUVFJeVfov9gfQ6BQKCurr5ly5avGcAXUVJSooZ6/fr1OsezZMmSXbt2JSQkJCcnZ2RkNNnAGkYqT4fR1dUlhFB/uhFbdPz4cV9f3zrD9PX1Bw4cSAjp2LGjhYXFy5cvCSE0Gs3d3Z36defUqVNjxoyR3FxVVVXbtm2p135+fseOHZNGUgAKDp9rAJAGfLcAAEDz9/jx48LCwpkzZ9JoNCUlpaVLl166dOndu3clJSUuLi7Lli2bNWtWcHBwVVUVIeTSpUu+vr7e3t4LFizgcDglJSVubm7h4eFBQUGBgYE1NTV1bmLixIne3t4eHh5nz56lWrhc7sKFC318fGbMmFFVVVVSUjJ48ODQ0NApU6b8/fff8+bNo8Li4uKGDh06dOjQmTNnEkIqKiomTpzo4+MzdOjQK1euCPsvKSnx8PBYtGiRu7v77Nmz79y54+/v7+zsnJqaKjqMzMzMIUOG1NTUVFVVDR48ODs7+z93TlZWlpaWloWFhZKSkp+fX3x8fIP2cdOR1iNyrays2rdv37NnTw8PD2Ejm80uLCzU09OrPyw7Ozs7O9vOzo56O3bs2JMnT3748KGgoKBHjx7CMA6H0717dyMjo6CgIOGNyn379k1JSZFSUgAKDp9rAJAGfLcAAEAzl52dbWVlRad/PH1WV1c3NDR8/vw5ISQ/Pz8wMHDHjh29e/fev39/YWHhb7/9dvr06ZiYGAMDg5MnTxJCCgoKJk+evGfPHkNDQ6owweFwnJycnJycxo0bR/W5bdu2mJiYCxcuHDly5J9//iGE5OTkBAQEREdHGxkZ7d+/nxDy4sWLyZMnHz58WEdHh1orPz9//fr1R44ciYuLW7NmDSHk6tWrFhYW0dHRcXFxAwYMEM0iLy9vypQply9ffv/+/fHjx8+ePbt169Zt27aJxvTq1cvNzW3Tpk1r16719/c3NzcnhPB4vEGDBnl4eJw4cUJy5xQXF1N/qyCEdO7cubi4uJH2urRIa2LUP//8s6yszN/f/86dO/b29lTjmzdv2rVrV39YWVnZqFGj9u3b16ZNGy6XSwj55ptvsrKyIiMjfXx8RNdVUVF5+vQpISQ5OXn8+PFZWVlKSkoqKiq1tbW1tbUMBkNKqQEoLHyuAVqExMREsUlMExMTezDreCaiNCIbAN8tANCyPLiX8zD9v/883jz1ZppjVtcGEAgENBqtzhZ9ff1u3boRQlxcXFatWtWlS5fCwsJJkyYRQthsdv/+/QkhRkZGVFnfxMSkqKiIEKKiokLNUSV09OjRmzdvKisrv3z5Mj8/X0dHR19fv2/fvoQQX1/fNWvWjBs3zsDAQOzembt377q6ulI1EW1tbUKItbX1tm3bGAyGm5ubjY2NaLCRkZGlpSUhxNLS0tzcnE6nW1lZFRYWiiU7Z86c4cOHt2rVSvhP/4ULF/T09AoKCiZNmmRubs5kMsV2RZ2vmy0pPh1GW1vbw8Pj4sWLwl9o1NTU2Gx2PWFsNnv48OFz584dMmSIaMzQoUOXLFly586dx48fS27I0dHx7du3RUVFBgYGhBAej4ffZgCkBJ9rgGaO6WBJyBOx57b0YLZnSjzLVhqRDYbvFgBoQR6mZz/NeCf8vmpB0tLSCMlGEaQBzM3Nt2/fzufzqYtBKisr8/PzjY2N+Xw+VYUnhNTW1lIvnJ2dw8PDheuWlJQoK38876bRaMJ4UUlJSRkZGZGRkQwGY/LkydQtM2JlF0KI2PzipK7qjIWFRVRUVEJCwtq1a93c3GbMmCFcpKKiQr2g0+nUazqdzuPxxPosLS2tqqricrk1NTXq6uqEEKqCo6+v7+bmlpmZKVYE0dXVffXqFfX61atXnTp1kkywWWn8IsibN2+qq6sNDQ3LysouXLjw/fffCxfp6Oiw2WwOh6OioiIZxuPxxowZ4+npGRgYKNbntGnTunbtampqWucvNPfv3+dyudQVOEVFRUZGRo2eFICCw+caoKWwdbC0/bzahDQivxS+WwCghbK3t587d66sR9EQZZwcWQ+hRerZs2fXrl23b98+e/ZsPp+/atUqDw+P9u3bl5SUFBUVpaenM5nM8+fP9+vXz87ObuPGjdOnTzcwMKioqCgrK1NTU/vP/svLy/X09BgMxtu3b+/fv0815ufnZ2Rk2NjYUD3XuaKdnd2OHTtmzJjRsWPH9+/ft2vX7vXr1+3atfP392/dunVsbGwDkv35558XLFhQUFCwfv36FStWlJeXc7nc9u3bv3v37ubNm8uXLxeLt7S0LC8vf/r0qampaVRU1NKlSxuw0abUCEWQmpoa4S27c+fOHT58+MiRI9++fctgMMaPH//tt9+KBru6uqakpLBYrLKyMrGwhISE6Ojo+/fv79y5kxCyfft2b29vai0jIyNqlhex7VK/u6ipqR09epQqZSUkJNTzJCEA+Ez4XAOANOC7BQAAWqitW7cuX77cwcGBEDJo0KDQ0FCq3dzc/Pfffw8NDdXT09uyZYu6uvqqVauCgoK4XK6ysnJoaKiJicl/du7q6nru3LlZs2apq6tbW1sLez5z5syyZcu6du26ZcuWyspKyRUNDAwWLVo0YcIEQoipqenOnTszMzM3b96spKTUqlWr1atXf2maZ8+eZTAYPj4+PB5v5MiRqampHTp0CAoKqqqqYjAY3377LbUHRNHp9LVr1wYHB9fU1Pj6+opdJ9IMfW0RRFlZWfK2n8zMzE/Fz5o1a9euXSwWy9zcXCzMxcVFsquysjLRt76+vtRE8XVulxASGRlJ/T4EAA2GzzUASAO+WwAAoOXq0KFDnf9qKCkprV27VrRl8ODBgwcPFm0RXpExfvx46gU1U5WQmprawYMHxXoWe8yKurq6sB9NTc1r165Rr728vERr+u7u7u7u7pLj1NHREa7+888/CwdPzREu7HDkyJEjR46kFp0/f54Kq/PJuKIcHBwSEhLqj2k+pPV0mE+xtbV1dHSUfAxeo6iqqho/fjwubQVoYvhcA4A04LsFAAAAGp0UJ0b9lO+++05KPaurq4tdSQsATQOfawCQBny3AABAcyZ6eYWCKCsrCw4OFm3ZvXs39WCalkIGRRAAAACQqtO/XT8ecZkQYmTaec32YI3W4pPJC7GrORdO3Roz2a0BSz9UVI0fujw6+ZdGGTMAAAA0f9ra2pGRkbIexVdp6tthAAAAQKoqyit3rD979OLyC0kbNDRUz0cm1hPMrq45efhaw5YCAAAAtDgoggAAAMgVgUBABIIadi2fx6+pqdXp1Pbxg7wRLktqamqrq2qGOy3KfVooDN6z+XxR/ptA/9Xb150hhPx+PMF/0GI/1uLdm85JLp0xdt3owUv9WIsvnLolq+wAAAAAvsYX3A6TmJgYFhYmtZEAgBQlJib2YLavsx2fa4AW6lOfay3t1vNCxg5zWKim3qo308zDx44QwnK32bHuTHU1x3uUo2l3PWFw0AK/O8lZB8+FEEJePH91aFfsyUsrVVQZgf6r+9qaiy4lhGzYM0tLu3UNmzPBe6XrUNv6x4bvFoAW6lPfLfCfeDyeubm5vr4+IURNTW3lypW2trYVFRX+/v7CB3nUr6SkZPLkyV86x0RERISSklJgYGBDBg0S0tLSZD0EOZSWltbdprl8sXxuEYTpYEnIkzf/PJLqaABASnow2zMdLMUa8bkGaNHq/FwTQqoq2VGnbkYn/9JBR+unmbtO/3Z99ETXoIV+4zxDVVoxfl498VMdZtx55uzWt7WmOiFkyPD+99Oedrc2FA04eeja7YSHSkpKr1++K8x/01Vfp85+8N0C0KJ96rsFPgeDwUhMTCSEXL16df369WfPnpX1iODL9GaaE5JdxsmR9UDkTXeb9r2Z5rIexUefWwSxdbC0xbchgHzB5xpALmXcedZGU6NT53aEEBfPb24nZI6e6Fr2/p+qSja3lltTU6um3qoB3abcfPTgXvaB30MYDOWgcRs47NpPReK7BQCAzWZraWmJNZ48efLw4cMCgWDo0KFz584lhMTFxe3YsYMQYmRktGvXLmFkfn7+999/v3btWmtra6qloqJi1qxZZWVlXC533rx57u7uBw8ejIyM7NSpU8eOHamwiRMnlpaWcjicadOmjRw5solSlS99bM362JrJehQgXXg6DAAAgFzp3LX908cvyt5/0GrbOuXmIyPTzoSQlT8emLVoZFFBya/hJ39eM0kYrKahWvVPNfXaxs7i0M6LwQv9VFQZly6kzv5plOjSirLKrgYdGQzldyXlD9Ozmz4vAIDmr7a21s3Nraampry8/Pjx46KL8vLy9u3bd+HChVatWo0bN47JZOrr61NXi+jo6JSVlQkjnz9/Pm/evI0bN1pYWAgbr169amFhERISQgiprKx8/vz5iRMnoqOjCSHDhw+niiDbtm3T1tZms9mjRo3y9PRs3bp1E6UN0KKgCAIAACBXTCz0xk/1GO8VRqMRCyvDb7/ziDp5S1lZaai/A5/HD/BecTc5q59jDyq4VStGf1ZPP9bi/gOtF60MmBQ8NGBYmEBAPH3t7ZysCCHCpXOWjI4+nfTD9O3qGq0se3aTaYoAAM0Ug8Ggpv9IT0+fN2/elStXhIvS09NdXFzatGlDCBk2bNjdu3dfvXrl6uqqo6NDCNHW1qbC3r59O2PGjL1795qYmIj2bG1tvW3bNgaD4ebmZmNjQ/Wmrq5OCPHw8KBijh49evPmTWVl5ZcvX+bn5/fo0aNJkgZoYVAEAQAAkDeBs7wDZ3kL3/qOHeg7diAhhK5EPxG3Qiw4bONU4euRAS4jA1w+tXTX8R/F1o1O/qWxxgwAIE+YTGZpaWlxcXE9l2MIBAIajSbWqKmp2alTp/T0dLEiiIWFRVRUVEJCwtq1a93c3LS1tZWVP57KMRgMQkhSUlJGRkZkZCSDwZg8eXJNTU1j5wQgJ/CIXAAAAAAAgMb0+PFjLpdLXeVBYTKZCQkJHz584HA4Fy9etLOzs7Ozu3bt2ps3bwgh79+/p8JUVFT27dt35syZmJgY0Q5fv36toaHh7+8/bdq0rKwsJpOZmprK4/H4fH5SUhIhpLy8XE9Pj8FgvH379v79+02YK0AL87lXgtxLeZKe8kSqQ5E5psREbshaXiFrCrKWV8iaophZNweKueeRtbxC1vBFOByOk5MTIURVVXXz5s0MBqO6+uPMSsbGxlOnTh0xYoRAIBg2bJiDgwMhZNGiRRMmTCCEmJqa7ty5k4pUV1c/cODAxIkT1dTU3NzcqMbMzMzNmzcrKSm1atVq9erVJiYm3t7ekyZN6tChQ5cuXQghrq6u586dmzVrlrq6unA6Vajfg3s5hHzZA4mhET24l2PkZdv02/3cIkh6ypOs9HcsFkuag5GlxMREQp6Ifd0ja7mErIWQtVxC1kKKmXVzoJh7HlnLJWQNX0RJSSkvL0+sUVNTk5olhBAybty4cePGiS718vLy8vISvtXR0YmNjSWEaGlpXbhwQTTS3d3d3d1dtCUwMDAwMFC05eDBg1+dhAKR409xS2HkZSuTo/AFc4KwWKywsDCpjUTGwsLC3vzzSLIdWcsfZC0KWcsfZC1KMbNuDhRzzyNr+YOsAeQYi8VCHUQxYU4QAAAAAAAAAFAIKIIAAAAAAAAAgEJAEQQAAAAAAAAAFAKKIAAAAAAAAACgEL5gYlQAAAAAAAD4lPnz59+6datDhw6XL18WNvL5/JEjRyopKZ05c4YQcujQoRMnTvB4vDFjxsyYMYMQkpqaunTp0traWh8fnx9++EG0w9LS0jVr1iQlJbVu3VpLS2v69OkeHh48Hs/c3FxfX58QoqamtnLlSltbGTxntKVLTExMTEyU9SgUnUymp0URBAAAAAAAoBGMHTt2ypQpP/74o2hjZGSkvr7+y5cvCSHZ2dlHjhyJjY1lMBjjxo0bNGiQmZnZkiVL9u7da2pqOmLECBcXFxsbG+G6c+bM6dWrV3JysrKycmFhIfUAXUIIg8GgTuCvXr26fv36s2fPNl2S8iIxMTEq9kQfWzNZD0RxPbiXQ2TxrGIUQQAAAAAAABqBnZ3d33//Ldry7t27ixcv/vDDD+vWrSOE5Obm9urVS0NDgxDSr1+/+Pj42tpaLS0tCwsLQoifn198fLywCPLo0aPCwsIjR47Q6XRCiJ6eHnXliCg2m62lpdUEqcmlPrZmk4K9ZD0KRRYrk61+7ZwgXC5XVVVVtKWgoMDNzU1PT8/ExGTHjh1i8ZWVlc7Oznw+nxASEBCgo6NjbW0tXJqQkGBhYdGtW7eQkJB6uuLz+fb29o6OjmIDuHLliomJSV5eHpfLpdPppqampqamvXr1SkpKojbNYrGoTSNrZI2skTWyRtYKlXVzoJh7Hlkja/nOGv7T6tWrFy5cSFUxCCHm5uZ//PHH+/fvq6qqkpOTX716VVxcrKurSy3t3LlzcXGxcN2cnBwrKyvhuqJqa2vd3NycnJxCQkLmzZvXBIk0K4mJiWFfDffCKCypTIwaGhpaWFiYmpq6bt26rKws0UX79+8fNWoU9UmeNm1aXFyccJFAIJg6derZs2dzcnKuXr2akpLyqa727t1rbGwsttGEhISgoKC4uDhqkYqKSm5ubm5u7qpVqxYvXkwI0dDQsLe3j4qKkkbKyBpZI2tkjayRdYvLujlQzD2PrJG1fGcNQqmpqXQ6nclkCltMTU1nz549adKkKVOmWFlZKSsrCwQC4VLR12Itq1evdnNzc3V1pd4yGIxr164lJSUdPHhw3rx5PB5Pyqk0I2FhYU1/AwXIk8Yvgujr6w8cOJAQ0rFjRwsLC+rmN6Hjx4/7+vpSr52dndu2bStc9ODBg3bt2vXs2VNZWXnChAnnzp2rs6s3b96cOnXq+++/F+329u3bgYGBMTEx1IVkoqqqqoRb8fPzO3bsWCMnTAhB1sj6X8gaWRNkjaxbSNbNgWLueWSNrCnymjWIysjIuH37tpOTU1BQ0KNHj6ZNm0YIGT16dExMzKlTp1RVVY2MjHR1dV+9ekXFv3r1qlOnTsLVzczMsrKyqGt2QkJCoqOjy8rKxDbBZDJLS0tFrx9RBF9/GQgqKYpMinOCZGdnZ2dn29nZCVvYbHZhYaGenl6d8UVFRV27dqVe6+vrUzVvya4WLlwYHh6upKQkXFpbW+vt7X3t2jUrKythI4fD6d69O5vNLi0tvX79OtXYt29f0W6lAVlTkLUkZI2sCbJG1v9qVlk3B4q555E1BVlLko+sgRDy/fffU1WqP/74Y82aNREREYSQgoICfX39rKys+Pj4uLg4bW3t8vLyp0+fmpqaRkVFLV26VLh6z549u3TpsnHjxvnz5zMYjIqKCslNPH78mMvl6ujoNFlSAC2dVG6HIYSUlZWNGjVq3759bdq0ETa+efOmXbt2n1pF9Oov0XsURbtKSEig0+nUfY9CysrKTk5OBw4cEG1UUVF5+vTpixcvYmNjx48fT10hpqKiUltbW1tb+/UJ1glZCxuRtSRkjayRNbIWaj5ZNweKueeRtbARWUuSg6wVVnBw8OjRo/Py8hwcHE6fPl1nzOLFi+3s7GbPnr1+/fp27drR6fS1a9cGBwcPHDjQwcFB9N4ZQsiOHTuKi4sdHBxcXFxmzZoVHh5OtXM4HCcnJycnp/nz52/evJnBYEg9NwB5IZUiCJvNHj58+Ny5c4cMGSLarqamxmazP7WWnp5eYWEh9bqwsJCqf4t1lZKScu3aNSMjIz8/v/T0dB8fH0IIjUY7efJkZmbmypUrJbt1dHR8+/ZtUVER9ZbH40npOwJZi7Yja0nIGlkja2Td3LJuDhRzzyNr0XZkLamlZ63Idu/efefOnZycnJSUlNGjRwvb+/bte+bMGer18ePH79y5c/36dWdnZ6rFwcEhISEhJSVl0aJFYh22bdt206ZN9+7du3HjxtmzZ6mjr6SklJeXl5SUlJSUdPXqVTc3tyZJDkBONH4RhMfjjRkzxtPTMzAwUGyRjo4Om83mcDh1rti7d+/3799nZmbW1tYeO3bM19dXsquQkJCioqIXL16cP3+eyWRGR0dT7WpqatHR0WfOnNm7d69Yt/fv3+dyudSUy0VFRUZGRo2YrBCyFluErCUha2SNrJF1s8q6OVDMPY+sxRYha0ktOmuAJib5cKWSkhIfHx9dXV1jY+M//vhDhmOD5qkRiiA1NTV6//rll19u3rwZHR29c+dOquX8+fOiwa6ursL7D/39/Z2cnJ49e6anp3fgwAE6nR4RETFixAhjY2MXFxdHR8f6uxLTtm3b+Pj49evXnzt3jhqVkZGRkZFRQEDA0aNHVVRUCCEJCQleXo3zIGhkjayRNbJG1si6ZWXdHCjmnkfWyFq+swaQLbGHKxFCgoKC7O3tX7169fDhQ0NDwy/tkMfju/aeFeC1fPyQ0KVz9n6oqCKEfCivXBfy2yjXn8cPCV0cvPPv5694PL4Hc67k6gK+4Pvxv8yZtIl6u2bJYb+BPwX6hUtGPribPdF7xbeeoQe2RX+qRaiefuBLfe3EqGJPdaJItgjNmjVr165d1Ey81PeyKFdX15ycHOFbFxeXT3VlmD/jbQAAIABJREFUb2+fnJxMDUB4MWHXrl3z8vLqGUNkZOTOnTvrTeizIGshZC0GWSNrCrImyFqCbLNuDhRzzyNrIWQtRj6yBpA5Z2fn3Nxc4dvXr18nJyefOnWKRqOJTsTzRZQZSsdiVwgEgrU/Hzl79MaU74eF/3TI1FL/5JVwJSX6nw/yXha+0zPqVOe6MWeTO+t1ePP6PfXWa8SAEeMHrV92VCxMIBD8EnZ81dYZhsa6syZstB9o3aN3N7EWqz7/ewD2p/qBBpDWxKifYmtr6+joKDrDU5OpqqoaP368TC78Q9ZNDFk3MWTdxJB1E1PMrJsDxdzzyLqJIWsAOZObm2tgYDBp0qQePXoEBgZWVlY2uCsajWZjZ1GUX5LzpKCooGTqbB8lJTohxKqPcX9n6zpXKXv/ISH+vu9YZ2FLb6ZZa0110ZiqSjafz899VqippW5s1kVJie4+rF/S9QeSLaJrSfYDDdbURRBCyHfffUeny2C76urq3377bdNvl4KsmxKybnrIuikh66anmFk3B4q555F1U0LWAHKGy+VmZGQEBwc/fvxYIBBs2LCh4V3VclNuPjIy6fxX7kszSwManfafq+zeeC5wtjddqb7IWQEbc54Uvi0u79BRm2rR0W37trhMsqXBI4f6fe3tMAAAAAAAAAolLS1N1kNoiLS0tO427WU9CunS09PT1dWlnh7t7++/f//+BnRSy+GO81xGp9GsbUxGBAxKuv6AfPrWNqEHd7NpNFrPviZZmX/VE/bTqgn6Rh3fvy0Xtgj4VOcCiRaQChRBAAAAAAAAPldvpjkh2WWcnP8ObWa627TvzTSX9Siky9TUVEdHJzMzs1evXlevXhV9asznY6goR8avEr41Mul8ZHecgC+o/2KQxw/z7qc9Hee5rJbD/VBRFTJ7z+rtQZJhFtaGhJAOnbRL/r3Wo6S4rEMnbcmWBowcPgeKIAAAAAAAAJ+rj61ZH1szWY8CPvL3909NTX379q2ent6KFSu+++67PXv2BAQEsNnsnj17Hjp06Os3Yd7DoIt+h/3boqd876XMUP7zQV5FeWU/RyuxsIBpngHTPAkhWZl/7dl0rs4KCCHkfupTcysDU3O9D+WVedlFBsa6V2PvzvxxhGQLISQlMdPWoQdDBaftjekL9mZiYmJYWJjURiJjiYmJPZh1XBuGrOUPshZrR9ZyBlmLtStg1s2BYu55ZC1/kDVA8yf5cCV7e/vMzMzG3cqy9YE7N5wdPXipSitGN9POVIXic4TO35f18K/y0n9Gu4VMnuk11N9h54azP4VPtLAyWBg2fvmCCE5NrZtXv559TQghki1rf/7tSHRouw6akv00boIK5XOLIEwHS0KevPnnkVRHI0M9mO2ZDpZijchaLiFrIWQtl5C1kGJm3Rwo5p5H1nIJWQMoICUl+uX0rWKNmtoaS9ZMEmuUDKP06NVt25GF1OuVv04XW3rw/FLqhY2dxdGLYaKLJFtiUjZ+qh9osM8tgtg6WNoq3rchslYcyFpxIGvFoZhZNweKueeRteJQzKwBAOSGDB7KBQAAAAAAAADQ9DDDCgAAAAAAQN0e3MshJFbWo5CiB/dyjLxsZT0KgKaDIggAAAAAAEAdWCyWrIcgdUZetoqQJoAQiiAAAAAAAAB1YLFYKBAAyJnPLYLcS3mSnvJEqkNpesz/mtcKWcsNZF0nZC03kHWdFDPr5kAx9zyylhvIGkCRPbiX8zA9W9ajaF56M8372JrJehSN6XOLIOkpT7LS38lTHTQxMZGQJ/V/3SNr+YCsPwVZywdk/SmKmXVzoJh7HlnLB2QNoOAepmc/zXhnb28v64E0F2lpaYRkK2gRhBDCYrHCwsKkNpKmFhYW9jkPeEfWcgBZ1wNZywFkXQ/FzLo5UMw9j6zlALIGAHt7+7lz58p6FM1IGSdH1kNoZHhELgAAAAAAAAAoBBRBAAAAAAAAAEAhoAgCAAAAAAAAAAoBRRAAAAAAAAAAUAgoggAAAAAAAACAQkARBAAAAAAAAAAUAoogAAAAAAAAAKAQvrYIwuVyVVVVxRoDAgJ0dHSsra0l4zdu3LhlyxbRlsrKSmdnZz6fz+fz+/XrZ2RkZGho+MMPPwgEgoKCAjc3Nz09PRMTkx07doht7sqVKyYmJnl5eVwul06nm5qampqa9urVKykpieqWxWLx+fyvTBBZI2tkjayRNbKWg6ybA8Xc88gaWct31gAKgsfjGRsbr169mnobERGxdetWybDq6upjx441yhbnz5//zTffeHh4UG/5fL6vr6+Tk5Ojo+OaNWsEAgEhZO/eve7u7qGhoZLxDduKgpDKlSDTpk2Li4v7zOD9+/ePGjWKTqfT6fTo6OgXL148e/YsJSXl8uXLhJDQ0NDCwsLU1NR169ZlZWUJ10pISAgKCoqLizM2NiaEqKio5Obm5ubmrlq1avHixYQQDQ0Ne3v7qKgoKeRXN2T9n5A1skbWyJqCrEnzyLo5UMw9j6z/E7Ju0VkDyCVVVdVLly6VlpbWE8Nms48ePdoomxs7duyhQ4eEb+l0ekRERFJS0vXr1zMyMm7dukUIOXbs2Pnz51euXCkZ37CtKAipFEGcnZ3btm0r2vLrr79aWlq6ubk9fPhQLPj48eO+vr7Ua11dXUIIVf8mhOjr6w8cOJAQ0rFjRwsLi5cvX1Jht2/fDgwMjImJsbCwEOutqqpKuGk/P7/GqsN9DmRNQdZiwcgaWRNkjayba9bNgWLueWRNQdZiwXKTNYBcYjAYY8aMOXDggGjjpUuXfH19vb29FyxYwOFwtm3bVlhYOG7cuE2bNtUTVlJS4ubmFh4eHhQUFBgYWFNTU1FRMXHiRB8fn6FDh/4fe3ca18S1sAH8JGyKooiCiAEDssgm6kVACkoBRaEi4oqgWNxwqUvdbmtVqta9rdVScW2rbbHWFXfEC75YQEFEREVARAhiQSUiKEuW98O0ubkEkCX7PP8P/sKZMzPnmYyZycnMmfj4eEKIq6tr9+7dxRdiaGhIxD4WoqKi/vrrr9mzZ8fFxUnWl1xFRUWFt7f32rVrFy1aNH/+/Ldv3za5FjrQlMM6cnNz9+3bl5GRwWAwnJ2d//Wvf4km1dbWcjgcFoslKrG3ty8sLAwLCxO/JicvLy8vL8/V1ZUQ0tDQMHbs2ISEBHt7e1GF+vr6AQMG1NbWVlZWXrt2jSocPHhwSkqKzOM1A6mRmiD1P5BaVIjUSC2qoISplQE9tzxSIzVR69QAaiM8PHzMmDHz5s2j/uRwOEeOHDl+/Li2tvauXbuOHTu2ePHilJSU2NhY8bkkq40ZM6akpGTmzJksFuvLL7+Mj4+vr6+3sbFZs2YNIaSmpqa5Bvj5+RUXFwcFBQ0fPnzEiBEXL15stC5xjVbh5uZWXFx86NAhc3PzmJiYgwcPLl68WEobRsXIY2DUGzdufPTRR127du3SpUtwcLD4pPLycgMDA/GS+/fvl5WVPX78+ObNm1QJl8udNGnS/v379fT0CCGampqenp6NeuC0tbVzc3OLioouXLgQGhrK5/OpwoaGhoaGBtnGawZSIzVB6n8gNVWC1ASpxShhamVAzy2P1EhN1Do1gNro1q1bUFDQTz/9RP2ZlpbG4XDCw8NDQkKuX7/+/PnzJudqshqbzaY6Pfv3719aWurg4BAfH799+/bMzMwuXbo014ArV67cvHmzuLg4Kyvrva1ttApCiKmpqbm5OSHE29s7PT29rfHVhpyeDqOlpdXoBaVz5861tbWNKuvr6/v5+Z0/f54QUltbO27cuCVLlowZM4aaymAwjh07lp2dTd371IiHh8eLFy+o95gQwufzG61RnpBavBypRZAaqampSC25IqQmik6tDOi55ZFavBypRdQmNYDamD179u+//07dS0IIGTFiRGxsbGxs7OnTp1etWtXcXJLVNDX/vieDwWDweDwbG5szZ85YWlpu2bJl3759LTSgW7dunp6e//nPf97b1EarIIRQ/xJCaN4lKo9OEA8Pj//85z98Pl8gEFA3OIkYGhrW1tbW19cTQsrLy58+fUoI4XK5Z8+etbW15fP5U6ZMGT16dEREhPhcnTt3jouL++OPPyT3j9u3b/N4POouytLSUjabLdNoLUBqpCZIjdRIjdT/UInUyoCeWx6pkZqodWoAddKjR4/Ro0cfP36cEOLq6pqQkFBcXEwIqaqqKi4u1tXVlbyZRbKa5GKfP39OXSA2Z84c8ZGPRV6+fEn1aVZVVSUkJFhaWraj8aWlpRkZGYSQ06dPu7i4tGMJ6kEKnSB1dXWsf+zYsYMQEhwc7Onp+ejRIxaLdejQoQEDBkydOtXPz2/69OlmZmaNZvfx8aHuUeRyuWPHjjUxMXFycvLy8po2bdr169fj4uKio6OphZ8+fVo0V48ePS5fvrxt27ZTp05RbWCz2Ww2Oyws7OjRo9ra2oSQxMTEgICAjgdEaqRGaqRGaqRWg9TKgJ5bHqmRWr1TA9DN3LlzX758SQgxNTXduHFjZGTkqFGjpk6d+uzZMx0dHU9PTz8/v02bNonqS1aTXGZ2dnZgYGBAQEBMTExkZCQhZP78+ZMnTy4sLHR3dz9+/HhVVdXs2bPd3Nz8/f1dXV0DAwMbLaFR/SZbbm1tffLkSX9//+Li4lmzZrVyLvXT0YFRNTU1qWcUi6M+gsUtW7Zs2bJlTS5h0aJFP/zwg5eXl7W1dXZ2tvgkb29vyYWLLhTs27dvYWEh9VqyGiEkNjY2Ojq6dTnaBqlFkJqC1I0gNVITpBajDKmVAT23PFKLIDVFzVID0ISGhoboiU5GRka5ubnU65EjR44cOVK85pYtWyRnl6x24cIF6kVoaCj1YtSoUeIV9u7d22ghly5dalRy69atFuo3WkVFRYWGhkaj5knORQdyGhOkBUOHDvXw8KAe8yNFb9++DQ0NVdoL/5BaipBaCSG1FCG1EqJnamVAzy2P1FKE1AAAII9H5L4XdSmOdOnq6k6bNk3qi5UipJYWpFZOSC0tSK2c6JlaGdBzyyO1tCA1ANCWoaGh6NoQmlP8lSAAAAAAAAAAAHKAThAAAAAAAAAAoAV0ggAAAAAAAAAALbRhTJCkpKSoqCiZtUTekpKS7Jx7tqYaUqs6pG65GlKrOqRuuRoNUysDem55pFYDSA0AaWlpim6CEklLSxswRN0+H1rbCeLsbkvIw/LqezJtjTzZOfd0drdtuQ5Sqwekbg5Sqwekbg49UysDem55pFYPSA1Ac07O1oTkcevzFd0QZTFgSE8nZ2tFt0LKWtsJMtTddij9PhyRmj6Qmj6Qmj7omVoZ0HPLIzV90DM1AE0MGmo1aKiVolsBsoUxQQAAAAAAAACAFtAJAgAAAAAAAAC0gE4QAAAAAAAAAKAFdIIAAAAAAAAAAC20dmDU9JSHGSkPZdoU+XN+37hWSK02kLpJSK02kLpJ9EytDOi55ZFabSA1AJ1lpeffzchTdCvUk5OztZIMOtvaTpCMlIcPMl56eXnJsjFylZSURMjDlj/ukVo9IHVzkFo9IHVz6JlaGdBzyyO1ekBqAJq7m5GXm/nSzc1N0Q1RN2lpaYTkqVgnCCHEy8srKipKZi2Rt6ioqNY84B2p1QBStwCp1QBSt4CeqZUBPbc8UqsBpAYANze3JUuWKLoVaohbn6/oJvwNY4IAAAAAAAAAAC2gEwQAAAAAAAAAaAGdIAAAAAAAAABAC+gEAQAAAAAAAABaQCcIAAAAAAAAANACOkEAAAAAAAAAgBbQCQIAAAAAAAAAtNDRThAej9epU6dGhWFhYYaGhg4ODtSfXC53wIABjerU1NSMGDFCIBAIBAIXFxc2m92vX78VK1YIhcKSkhJfX18Wi9W/f//vv/++0Vri4+P79+9fWFjI4/GYTKalpaWlpeXAgQOTk5OpxXp5eQkEgg7mQmqkRmqkRmqkVqfUyoCeWx6pkVq9UwPQx6tXrz755BN3d3d3d/c1a9a8e/eu3Yvi8/kWFhbu/ygsLJSsU1FRERAQ0NwSqqqqfH19292Ati7wX//6F9VUHx+fJissW7bsX//6l5+fnxSbJDsyuRJkzpw5Fy9eFP2pp6f322+/Napz8ODBSZMmMZlMJpMZFxdXVFT06NGjlJSUK1euEELWrVvH4XBSU1O3bt364MED0VyJiYmRkZEXL160sLAghGhraxcUFBQUFGzcuPHf//43IaRLly5ubm5nzpyRRa6WITVBajFIjdRIjdQUZU6tDOi55ZGaILUY9UsNoJYWL15sY2Pz559/JicnC4XCDRs2dGRp2traKf+g/he3nlAo1NXV3bVrV0ca0CYaGhpUU69du9ZkhalTp/74449ya08HyaQTZMSIET169BD9+ebNm2nTpjWq8+uvvwYFBVGvjY2NCSFU/zchxNTUdPjw4YQQIyMjGxubZ8+eUdX+/PPPiIiIc+fO2djYNFra27dvRWscP378L7/8Iv1U74PUBKnFIDVSE6RGaqVPrQzoueWRmiC1GPVLDaB+cnJyOBzOggULGAyGhobGF198cenSpZcvX1ZUVHh7e69du3bRokXz589/+/YtIeTSpUtBQUFjx4799NNP6+vrKyoqfH19N23aFBkZGRERUVdX1+QqZsyYMXbsWD8/vxMnTlAlPB5v+fLlgYGB8+bNe/v2bUVFxciRI9etW/fxxx8/ffp06dKlVLWLFy/6+/v7+/svWLCAEFJVVTVjxozAwEB/f//4+HjR8isqKvz8/FatWjVq1KhPPvnk5s2bwcHBI0aMSE1NFW9Gdnb2mDFj6urq3r59O3LkyLy8vNZsH1dX1+7du7d9uyqGpkLWWltby+FwWCyWqMTe3r6wsDAsLEz8Epq8vLy8vDxXV1dCSENDw9ixYxMSEuzt7UUV6uvrBwwYUFtbW1lZKeqUGjx4cEpKiryitAFSU5BaVIjUSC2qgNRIrZyplQE9tzxSU5BaVKhmqQFUTl5enr29PZP59zUEurq6/fr1e/z4sbm5eXFx8aFDh8zNzWNiYg4ePBgcHHzkyJHjx49ra2vv2rXr2LFjY8aMKSkpmTlzJovF+vLLL+Pj4/39/evr6z09PQkhLBYrNjaWELJ79259ff3a2tpJkyaNHj2aEJKfn79169bBgwdv2bLl4MGDISEhRUVF+/bts7CwqKqqolpSXFy8bdu2EydOGBoacrlcQsjVq1dtbGzWrFlDCKmpqRFPUVhYuGvXrgEDBoSFhf36668nTpzIzs7etm3bsGHDRHUGDhzo6+v79ddfv3v3Ljg42NramhDC5/M//PBDbW3t8PBwyZ5claOYgVHLy8sNDAzES+7fv19WVvb48eObN29SJVwud9KkSfv379fT0yOEaGpqenp6Hjp0SHwubW3t3NzcoqKiCxcuhIaG8vl8qrChoaGhoUFeaVoLqSlITZUgNUFqMUiN1MqZWhnQc8sjNQWpqRL1Sw2gcoRCIYPBaLLE1NTU3NycEOLt7Z2enp6WlsbhcMLDw0NCQq5fv/78+XNCCJvNpvo6+/fvX1paSgjR1tZOTk5OTk6mekAIIUePHp04ceLMmTOfPXtWXFxMLXnw4MGEkKCgoPT0dEKImZlZo3tnbt265ePjY2hoSAjR19cnhDg4OMTHx2/fvj0zM7NLly7ildlstq2tLYPBsLW19fDwYDKZ9vb2HA6nUdjFixffuHEjJydn7ty5VMnZs2cTExMPHjx48ODBjIwMqWxSBVJMJ0jnzp1ra2sbFerr6/v5+Z0/f54QUltbO27cuCVLlowZM4aaymAwjh07lp2d3eTNVx4eHi9evKD2J0IIn8/X0tKSZYL2QGoRpEZqaipSSy4QqQlSw/+i55ZHahGkVsvUACrH2to6JydHNNJwTU1NcXEx1R/B4/GoQlGH44gRI2JjY2NjY0+fPr1q1SpCiKbm33dgMBgMUX1xycnJmZmZsbGxx44dc3R0pG6ZadTtQgiRHH1ZsnfGxsbmzJkzlpaWW7Zs2bdvn/gkbW1t6gWTyaReM5lMqs9UXGVl5du3b2tqakR37lA9OKampr6+vtnZ2c1vJ9WgmE4QQ0PD2tra+vp6Qkh5efnTp08JIVwu9+zZs7a2tnw+f8qUKaNHj46IiBCfq3PnznFxcX/88Uej95IQcvv2bR6PR91FWVpaymaz5ZSkLZAaqZEaqSlI3WiBSK20qZUBPbc8UiO1eqcGUDmOjo59+/bds2ePQCDg8XgbN2708/Pr2bMnIaS0tJS6OOL06dMuLi6urq4JCQnUpRxVVVXUi/d6/fo1i8XS0tJ68eLF7du3qcLi4uLMzEzRkpuckVpdeXk5IeTVq1eEkOfPn3fp0iU4OHjOnDnioym33ueff/7pp5+OGzdu27ZtVNtevnxJCHn58uX169clH3GlcqTQCVJXV8f6x44dOwghwcHBnp6ejx49YrFYjS7VE/Hx8aHuUeRyuWPHjjUxMXFycvLy8po2bdr169fj4uKio6OpZZ4+fVo0V48ePS5fvrxt27ZTp05Rq2az2Ww2Oyws7OjRo1RvVmJiYgsPE5IWpEZqpEZqpEZq1UqtDOi55ZEaqdU7NQBNfPfdd3l5ee7u7h4eHgwGY/369VS5tbX1yZMn/f39i4uLZ82aZWpqunHjxsjIyFGjRk2dOlU0nnHLfHx8SktLFy1atH37dtFDta2trf/444+AgICioqJZs2Y1OaOZmdmqVaumT5/u5+e3du1aQkh2dnZgYGBAQEBMTExkZGRbY544cUJLSyswMDAyMjI7Ozs1NbW8vHzy5MnDhg0bP378+PHj3d3dJeeaP3/+5MmTCwsL3d3djx8/3taVyllHB0bV1NQUCoWNCqnPYnG5ubmNShYtWvTDDz94eXlZW1s3uqLG29tbcpmiCwX79u0repCyZDVCSGxsbHR0dFtCtBlSiyA1BakpSI3UBKnFKFVqZUDPLY/UIkhNUbPUAPTRq1evJv9DaWhobNmyRbxk5MiRI0eOFC+5cOEC9SI0NJR60eijoHPnzocPH2605MuXL4v/qaurK1pOt27dEhISqNcBAQHi3Z2jRo0aNWqUZDsNDQ1Fs3/++eeixlOdsKIFTpw4ceLEidQkUcdrc0/GFdm7d2/LFZSKYm6HIYQMHTrUw8NDdFeVtLx9+zY0NFRpL/xDailCaiWE1FKE1EqInqmVAT23PFJLEVIDAICIYh6RS2nukp6O0NXVVfJn9iC1tCC1ckJqaUFq5UTP1MqAnlseqaUFqQFARsQvr6AJLpc7f/588ZK9e/dSD6ZRFYrsBAEAAAAAAAAAVaGvry96rK+KUtjtMAAAAAAAAAAA8oROEAAAAAAAAACghTbcDpOUlBQVFSWzlshbUlKSnXPP1lRDalWH1C1XQ2pVh9QtV6NhamVAzy2P1GoAqQEgLS1N0U1QQ2lpaQOGKMvnTGs7QZzdbQl5WF59T6atkSc7557O7rYt10Fq9YDUzUFq9YDUzaFnamVAzy2P1OoBqQFozsnZmpA8bn2+ohuibgYM6enkbK3oVvyttZ0gQ91th9LvwxGp6QOp6QOp6YOeqZUBPbc8UtMHPVMD0MSgoVaDhlopuhUgWxgTBAAAAAAAAABoAZ0gAAAAAAAAAEAL6AQBAAAAAAAAAFpAJwgAAAAAAAAA0EJrB0ZNT3mYkfJQpk2RP+f3jWuF1GoDqZuE1GoDqZtEz9TKgJ5bHqnVBlID0EdWej4hFxTdCvrKSs9nBwyV/3pb2wmSkfIwJeHxoEGDZNoaecrKyiKEtPxxj9TqAambg9TqAambQ8/UyoCeWx6p1QNSA9CHl5eXoptAd+yAoQp5F1rbCUIIGTRoUHh4uOyaoghv3lsDqdUFUjcNqdUFUjeNnqmVAT23PFKrC6QGoAUvLy/0g9ATxgQBAAAAAAAAAFpAJwgAAAAAAAAA0AI6QQAAAAAAAACAFtAJAgAAAAAAAAC00IaBUQEAAAAAAChJSUlJSUmS5bIbb1J51gjSJfkOKs97TYc1qq72bSt0ggAAAAAAQJtRX6gkv7sSmT18VCFrPHPht0FDrWSxcKBkpecTiXeQJnsX9ueOaHLPaQ10ggAAAAAAQHt4eXlFRUWJlzT6Uw3WOGioVfj8AJmugvYuNFlKh70L+3PHNL3nvJcUOkHevHkTHR19+/ZtbW1tU1PT+fPn9+vXr32L4vP5/v7+V65ckZx04MCBoUOHVldXl5SUhISEEEKysrK++eYbHo/n4+Mza9asDmVoO0Wl3rx5c3p6eo8ePQ4fPtyhAO2ikNTl5eXbt28vLi7W0tKaNGlSUFBQx0K0mUJSC4XCBQsWcLlcoVDo5eU1b948BoPRsRxto6g9nBAiFAoXLVqkoaGxe/fu9gdoF0WlHj9+vJaWFiGkU6dOR44c6UiEdlBUai6Xu2PHjtzc3E6dOkVFRVlZyfVHCYWkLikpWb58OTWpqqpq5syZU6dObX8G1aQ8x5Fz586dPHmSEGJqavrZZ5/p6up2JFfLlCf1yZMnz507JxAI/P39Zb37Kc/RU55njMqTWrFnjElJSTL6Iif5s7kc1si26yKLJUP7qNnehf1ZIaQwMOqmTZt69ux57NixX3/9dfr06c+ePWuhslAoFAqF7VjLw4cPbW1t79696+joSC1nx44dUVFRR48ezcjIuH//fjtb314KSU0ICQgI2Lp1a3taLA2KSj1jxozjx49HR0f/9ttvT58+bU/TO0AhqRkMxldffRUbG3vkyJGcnJz09PR2tr69FPVeE0LOnTvXp0+fdiyt4xSVmslkHj9+/Pjx4/LvASGKS/3tt9/a2dmdOHHiwIEDxsbG7Wl6BygktampKfVG//777/r6+h4eHu1svSpTkuPImzdvDh8+vGfPnp9++klXV/fSpUvtCdNqSpL6yZMnp0+f/uGHHw4dOnTjxo0nT560J0yrKcnRU85njEqSmij6jBFAntRvnAuQtY61DDFZAAAgAElEQVReCZKfn19aWrp161bqZ2p7e3uq/MKFC6dOnRIKhSNGjAgPD3/16tWnn346ePDgZ8+erV69+t69e8eOHRMIBGw2e8WKFdRPoM2JiYnJyMgoKytbtGjRs2fP7ty5M3z48GHDhnXr1s3CwoIQMmrUqOTkZNGq5UBRqWfMmOHk5FRaWiqPkBIUmNrIyIgQoq+vb2pq+uLFi3b/qNIOCkxtYGBAOnCG1BEKTM3lchMTE2fNmrV//355RBWjwNTyiNcMRaX+6KOP7t27t27dOgaDIdNf4Juk8Pc6JyenR48eLBZL1kmVjfIcRwwMDIRCYX19vUAgqK+v79mzJx1SV1dX29jYUP/jBg4cmJycbG5urn6pGx09CwoK5HbGqDypCSEKPGMkTV3hLy3NLVamaywql/dvUdCC5u4f6fgOoJC9S/5rxP5MOt4J8uTJEysrq0YX6peUlPz+++979+7V1tZetmyZg4ODubk5h8PZuHGjqanp8+fPz5w5s3v3bi0trZ9++unChQst3+AQGRn56NGjK1euLFiwYNmyZXv27CGEpKam9urVi6pgaGgo5ytBFJVasRSemsPhcDgcW1tbmcRrhmJTf/zxx2VlZSNHjhw6dKisEjZFgan37t0bERHBZCrg6d0KTC0QCKZPn66pqRkcHDx27FgZhpSgqNT37t3r3bv31q1bCwoKbG1tFy9e3KlTJ9lGFaPwT7OEhAQfHx+ZZFNuCt/youOIrq7unDlzpk+f3qlTJ3t7exmNP0dRntQVFRUxMTFcLldHR+f27dvW1tYyCUwIUXRq8aNnWlqa3M4YlSe1rBICAKgFmQyMeu/ePTc3ty5duhBCPvzww+zsbHNzcxMTE1NTU0JIVlbW8+fPV61aRQipq6sbPHjwexeYn5/fv3//4uLiJi8BkP/v5E2Sc2olIbfU1dXVUVFRy5cvl/+PxpLklvrHH3+srq5et27dw4cP7ezsZJOmteSQOisri8FgODo6PnjwQJZR2kA+7/XevXuNjY3LyspWr17NZrPFbw5SCDmk5vP5eXl5CxYscHBw2LFjx7Fjx2bOnCnDSK0gt//XfD4/OTn5wIEDssmhehRyHHn37t3ly5d//vlnAwODTZs2nTt3Ts79jwpJ3a9fv+nTp69evbpTp05WVlby725WyNFTvFwhZ4xqf84gOXhBc8McqO4aQVHosHdhf1aIjnaCsNnsn3/+WSgUvnfURh0dHdFrFxeXZcuWtWb5hYWFX3/9dVlZmYGBwW+//UYIWbhw4TfffNOrV6+KigqqTkVFhaiPXz4UlVp8afKnwNT19fVffPHFhAkTXFxcOhKhHRT+Xnft2pX6IUueJzSKSp2Tk3P79u2QkJCGhoY3b96sWbPmq6++6kiQNlHge02NiNGnTx93d/e8vDx5doIoKrWhoaGBgQGV1NPT88KFdg7u3T6K/X+dnp5ubm4u0/svlJbyHEfu3bvXtWtXQ0NDQoiHh8etW7dk1wmiPKkJIf7+/v7+/oSQ3bt3y3T0JeU5enp6esrtjFF5UsvtnKHJr2peXl6y+won/zWCotBh78L+rCgd/RHA2traxMTk4MGDPB6PEHL//v3U1FRHR8e0tLSampqGhobExEQnJyfxWZycnFJSUsrKyggh1dXVLQ8ZZWFhER0dbWJicuDAAUdHx82bN0dHR+vo6FhaWr5586awsJDH4129evWDDz7oYJA2UVRqmYZ6L0WlFggEGzZscHFxGTNmjEwDNklRqblc7l9//UUt4c8//zQzM5NlysYUlTosLOyPP/6IjY3dsGGDjY2NPHtAiOJSv3nzhsvlEkK4XO7Nmzdld4t+kxSVum/fvvr6+oWFhYSQjIwMmqSmpl67do2e98IQZTqOGBkZFRQUvH79WigUZmRkUL/Jy4jypCaEUMssKChITk4eOXKkTAITQpTp6CnPM0blSS27jI1QIxdIavkrnEAgcHFxYbPZ/fr1W7FihVAoLCkp8fX1ZbFY/fv3//7775VqjaAo7XuvCSGJiYk2Njbm5uZr1qxpNKmioiIwMNDY2NjCwuLOnTuqu0boICncDrN27dro6OjJkydra2ubm5svWLDA1NR08uTJixYtEgqFH3744ZAhQ169eiWq36dPn6VLl65du5bP52toaCxatMjExEQ0taGhYfLkydTrCRMmTJkyhcvl6unpMRiMkpIS0YV/DAZj+fLl69evr6+v9/X1lf/V4wpJTQhZt27dgwcPXr9+PXny5JkzZ1K/58iNQlLfvXs3JSUlLy/vzJkzhJDFixfL+ZEKCklNXcD8+vVrDQ0NX19f+X9lUtQerlgKSf3q1au1a9e+e/dOS0tr7NixQ4YMkWdkorj3etmyZZs3b66vrzc3N1+9erXc8lIUlbquru7WrVtLliyRW1JlozzHkeDg4IULFzIYDEtLy+DgYJqk3rlz59OnT3V1dVeuXKmvr69+qSWPnnI+Y1SS1ETRZ4wtYzKZcXFxxsbGtbW13t7eV65csbe3X7du3fDhw8vLy4cMGeLt7S3di1lksUY+XzBqyOI+rJ5CgdDcqu/qTdP1uum+eV0Tvf3k7bRcbW1NU3bv+SuCWeze/q7LrmR8Jz7vuePJJ39NJISYsnt/tjlct0unzZ/9lP7nwx499Q6f/qLRirJu5X2zMZbXwPfxd561OLDJkhYq041QKJw9e/aZM2dsbW3d3d0DAgLc3d1FUyMjI93c3M6ePVtdXd3Q0KCia5SFjuzPTRIKhIum79TQZO7+ebl4uZLsz4z169dHSYyvW159b/6K/zkh2Lvz1NsXeuHh4VJct2L9/PPPur3eNIrZCFKrB6RuDlKrB6RuDj1TKwN6bnmkVg9I3Zy9O08ZdXWU7hMr3r596+3tHRUVNXr0aFGhj4/PZ5995uvrK8UVdWSN1NM0wucHNCrn8wXUt0GhULjl85/7sHp9vPCj1ZHfW9qaRiwaq6HBvJ9VWPX6rYuHXaMvjW+q3s746Msj59frddPd8vnP1nZmE8I+vJuR36mT9ra1Rxt1ggiFwrCAqI3fzetnYbxo+s5FqybaOZk3KrEfZNFcZdEkJffz3gtso6Ht3ruixJ4Oc+fOnblz51IPit6zZ8/Tp0937txJVXv+/Dn11CRNTWkOiyn/NXaE1Pfn5sQdT86+XVD+/JV4J4jU9+f27TlRUVEKeAQDAAAAAADQmb29fc+ePR0dHf38/ESFeXl5eXl5rq6uKrRGBoMxxNWmtLgi/2FJaUnF7E8CNTSYhBD7QRbDRjg0MYNQKCTC+roGgUBQX9fQ07A7IcTJ2aprt/8Z+/9tTa1AICh4xOnWXdfCykRDgznqI5fka1mSJaJZWphEH6WlpX379qVem5qaij8ouqCgwMzMLDw83M7OLiIioqamRkXXKFNt3p8JuR6fOXvi5tkTN0d9+veg79xXbxIv3w6aOkJUR9n2Z3SCAAAAAACAXN2/f7+srOzx48c3b96kSrhc7qRJk/bv36+np6dCa+Q18FKu32P37/Ok4JmVrRmD+Z5hcfW6d5mzNGj6R1ETvT/n8fhefk3fBrsobGf+Q86Lv173Mvr7zjVD4x4v/uJKlohmaWESfYg/BEogEIhP4vF4mZmZ8+fPz8nJEQqF27dvV9E1ylRb9+cyzov9357ZHrPo4InPl6+fRhXu3Xkq4pOxTI3/zqts+zM6QQAAAAAAQN709fX9/PzOnz9PCKmtrR03btySJUtkOhC+dNfYUM8LGb02PHBDZ12dCWEfEkJIKx7D/O5t3eUzqT/Hrf/j2mZNLc1zx5ObrLZ643RTthEh/12gUEC9liz5b0Hzk+iCxWJxOBzqNYfDEV2jQU0yNjb28PBgMpnBwcFZWdK5skD+a5SR9u3PdzPyh3k5GvTqRgjR696FEJJ1K4/BYDgO7i9eTdn2Z3SCAAAAAACAnJSXlz99+pQQwuVyz549a2try+fzp0yZMnr06IiICBVao5a2Zuzljb9e2vDZV+GddXXY/fvkPSx577e1e5kFXfU6G/bW19BgengPzLlb2GQ1G4d+ul069eqtX/HPD+AVf3F79daXLBHN0sIk+nBycnr16lV2dnZDQ8Mvv/wSFBRECImLi6urq7O0tDQ0NMzOziaEXL161cGh6Zs7lH+NMtK+/VkoJAzyP1eL5NwtvJ2WGzJ67bql+x/dL17zSQxRvv0ZnSAAAAAAACAnXC537NixJiYmTk5OXl5e06ZNu379elxcXHR0NIvFYrFYp0+fVsU1WtuZmZj2Org7jtfAI4TczypMvX5PsppRH4OCXM5rbrVQKMxIyTVl925yabdTc99UvbW0Zr15XVOYV8rj8a9euPWBt5NkCSEkJSm7oZ7X5CS6YTKZBw4cmDBhgoWFhbe3N/VMyRkzZlRWVhJCYmJiwsLCrK2tS0tLpfVkOvmvUT5auT8PGmqVkpT9suI1IYRbWU0ICZsz+o9rm2Mvb9ywa66NvdlXeyKJ8u3PHRqoNisr6+7du9Jqikw5OTkNGjRIKotCaiWH1B2E1EoOqTuInqmVAT23PFIrOaRWCGtra+q3cRFvb29hKy68V/41rt0WEb39xOSRX2jraJlb9lmwcoJkHXb/PsGhXgtDdzAYDEsbVvA0L0LIumX7H9x98rqyerLvmpkLAvyD3aO3n1i9aYaNvdnyqND1nx6or2vwDXChbjGQLNny+ZGf49YZ9OomOYmGfHx88vPzxUu43L8vKHBzc2u0J6joGuWjNftzH1avOUuDVs7dQwjpZ2G8/uvZTS5K2fbnDnWC3L17t6ioyMvLS0qNkZWkpCRCiLQ+7pFamSF1xyG1MkPqjqNnamVAzy2P1MoMqaEjNDSYkg8K7abf5bPNjZ+dLFktJGJUSMQo8ZIN385tVEf0rNwhrjZHz0eJT5IsOZeys7lJAK3Rkf3Zy29Ik+P72g00Fz0fV9n2544+stjLy0u6DwyXhaioqKKiIikuEKmVFlJLBVIrLaSWCnqmVgb03PJIrbSQGgCAnjAmCAAAAAAAAADQAjpBAAAAAAAAAIAW0AkCAAAAAAAAALSAThAAAAAAAAAAoIWODowKAAAAAABAH1np+Xcz8hTdClXl5Gw9aKiVoltBX1np+YRcUHQrpCMrPZ8dMLQdM6ITBAAAAAAAoLXuZuTlZr50c3NTdENUT1paGiF56ARRFOV/jHebsAOGti8ROkEAAAAAAADawM3NbcmSJYpuhUri1ucrugn05eXlpWb9IO0j2zFBeDwek8m0tLS0sLAYN25cZWVlUVGRpaWlqMKKFSt27twpXr9Tp05NLuqzzz5LSko6c+bM1q1bqZLExEQbGxtzc/M1a9bINEVbyTR1WFiYoaGhg4ODTCO0g+xSl5SU+Pr6slis/v37f//997IO0iaySy0QCFxcXNhsdr9+/VasWCEUCmWdpfVkuocTQgQCgZubm4eHh+witINMUxsaGrJYLBaLZWNjI9MUbSXT1BUVFYGBgcbGxhYWFnfu3JFpkDaRXepHjx6x/tG5c+ft27fLOotKkPPhIyYmxtbW1tbWNigo6M2bN7JM1hI5p/7uu+9sbW2tra0Vu9fJ+aCpJCeKck6ttCeKAADKQ+YDo2praxcUFDx+/FhPT+/bb79t93Ju3rzp6up6/fp1T09PQohQKJw9e/aJEyfy8/OvXr2akpIivSZLgYxSE0LmzJlz8eJFKTVTymSXet26dRwOJzU1devWrQ8ePJBSe6VDRqmZTGZcXFxRUdGjR49SUlKuXLkivSZLgezea0LIvn37LCwspNFMKZNdag0NDQ6Hw+FwHj16JKXGSo3sUkdGRrq5uZWVld29e7dfv35Saq90yCi1jY0N9UaXlJQYGRmNHz9eek1WbXI7fFRWVq5duzYlJeXhw4d6enqHDh2SUoL2kFvqnJycPXv23Lp16969e6dPn87JyZFSgvaQ20FTqU4U5XmqoMwnigAASkJOT4dhMBg+Pj4FBQXtmHflypUDBw5MT08fNmzYwYMH58+fv2HDhqysLAMDA0dHR01NzenTp586dUrqbe44qacmhIwYMaJHjx7Sbqk0ST21qanp8OHDCSFGRkY2NjbPnj2TdpOlQBbvtbGxMSFEIBAIBAIpN1dKZJG6vLz8999/X7hwobQbKzWySK38pJ76+fPnN27cWLVqFYPB0NPTMzAwkHqbO0527/WNGzeMjIysrHBH9P+Qw+FDKBQKhcLa2lo+n19bW2tiYiLtEG0mh9QPHz50dnbW09PT0dEZPny4MpwyyeGgqYQnivI5VVD+E0UAAIWTUydIfX39uXPn7Ozs2jHvjh07Dh06NHPmzPT09IEDB2ZnZ69bt660tLRv375UBVNT09LSUqm2VzqknlrqLZQF2aXOy8vLy8tzdXWVXmOlRkap7e3te/bs6ejo6OfnJ9X2SocsUi9fvnzTpk0aGhrSbqzUyCI1n8+3srKyt7fft2+ftNsrHVJPXVBQYGZmFh4ebmdnFxERUVNTI/U2d5zsPs1+++23adOmSa+lakIOhw8DA4OtW7daWVmZmJg0NDRMnjxZes1vJzmkdnBwSEtLq6ioqK6uvnr1aklJifSa305yOGgq4YkiPU8VAACUkMwHRq2rq2Oz2Uwm08PDY+nSpS9fvmxUgcFgvHchd+7ccXJyys3NtbW1pUrEh0hQwt/JZZRayck0NZfLnTRp0v79+/X09KTZ6A6Taer79+9zudzg4OCbN28q1QjkMkqdmJhILTMtLU36je4w2b3X6enpbDb7yZMnfn5+9vb2SjUeioxS83i8zMzMb7/91t3dfdasWdu3b//yyy+l3/r2kun/ax6Pd+rUqaysLGm2WMXJ7fBRXV39448/5ubmGhsbT5s2LSYmJjIyUppJ2kJuqW1tbdeuXevn59e1a9chQ4ZoaipyUHy5HTSV6kSRnqcKAConKSkpKSlJslx2g4k2t0bouJbfNZkfCHV0dIqKisRLXr16JXr98uVLR0fHFmbPzs6eO3fukydPevfuXVNTIxQK3dzcEhMTWSwWh8Oh6nA4HFFnv5KQUerOnTvLqMFSIbvUtbW148aNW7JkyZgxY2TU+HaT9Xutr6/v5+d3/vx5pTqzkVHqlJSUhIQENptdV1dXWVkZGBgYFxcnowjtILv3ms1mE0LMzc0DAwMzMjKUqhNEdp/hxsbGVNLg4OCDBw/KpvntJNP/11euXHFwcOjTp49s2q6S5Hb4SE5O1tfXZ7FYhJCgoKDLly8rsBNEngfNWbNmzZo1ixDyySefmJubSz9Mq8ntoDlhwgTlOVGk56kCgMqhuiQafXOmOilk1wly5sJveGCw1GWl55MW3zV5/xrQtWtXCwuLM2fOBAUFlZWVJSQktDxk98CBA9PS0tzd3W/cuPHxxx//+9//pvq/nZycXr16lZ2dbWtr+8svv3zzzTfyStAe0kqtWqSVms/nT5kyZfTo0REREfJqe/tJK3V5efm7d+/69evH5XLPnj2rzGNkEOmlXrNmDTVjWlraihUrlKoHRJK0UldWVjY0NBgZGZWXl1+6dGn37t3yStAe0kptaWlpaGiYnZ09cODAq1evKvmDDKT7GY57Yd5LdocPMzOzO3fuvHjxomfPnvHx8Ur1PCaZHjSfPHlibm6elZV18uTJu3fvyjxMq8nuoKnMJ4r0PFUAUAleXl5RUVHiJY3+lLpBQ63C5wfIdBW0dKHlyQq4JPLIkSORkZErVqzQ1tbevHmz+EPCCCF1dXXUTzSEkCVLlqxcubKioqJHjx5MJvPRo0ei80gmk3ngwIEJEybU1taGhoYq1Q+nTZJKakJIcHBwamrqixcvWCzWl19+Sf2wo7Skkvr69etxcXG3b9+Ojo4mhOzZs0fJH6kgldRcLnfixIkvXrzQ0tIKDQ1V/q9M0trDVYtUUpeVlY0fP766ulpbW3vevHk+Pj7yjtFG0nqvY2JiwsLCamtrHR0df/zxR7lmaDtppX779u3ly5epTzNogewOH0uWLHFzc2MwGIMGDVq8eLGcc7VMdqlnz5794MGDbt26HT582NDQUM65WiajgyaDwVDmE0XZnSqo1omiWuJyuZs2bbpx44aOjo6FhcXnn38uOQw2n8+3trY2NTUVCoU2NjY7duzo3r27QlrbgqqqquDg4ISEBEJIamrqF1980dDQEBgYuGLFCvFqBw4c0NDQiIiIaKGOHCQlJXW8t0LyMhApLry5NbLtushiySB615q8L0a2nSCampq1tbWNCu3s7P7v//6vufri93BSDA0NL1y4QAhpNEyAj49Pfn6+9BorNTJNrQzDmzdJdqm9vb0layoJ2aW2trbOzs6WamOlRqZ7OMXNze3GjRvSaKzUyC61nZ2dEj4ZlyLT99rNzU05d3KZptbV1ZUcDoDm5Hz4WL169erVqzva6A6Tc+pr1651tMXSIOeDppKcKMo5tdKeKNLH0qVL7ezs/vzzTw0NjczMzJKSkiafBaalpZWUlCQUCj/99NPDhw8vW7aMKqceYsVkyunhFa0hFAo/++yzffv2WVpaTpgwwdvbe8iQIe2oIztRUVGyvl4DVA635llReXpz98UocnAsAAAAAAAA9ZCTk1NUVHT48GGqF0PUEXDs2LGffvpJKBT6+/svWbJEVJ/BYHzwwQfJyckVFRXTpk0bNmxYcXHxjh07rl271qj+xYsXv//+e0IIm83+4YcfxFfaaOEVFRVTpkz54IMPKisr+Xz+119/raure+nSpX379lFPo9u6devr169DQkK8vLw4HE59ff3evXt1dHSaW8WDBw+6d+9O3T84fvz4y5cvDxky5PDhw7Gxsb179zYyMnJwcGiyjqy3tjipdII0txDJe2SkJSoqqqg8XRZLhn/uM2r6vhh0ggAAAAAAAHRUXl6eg4NDo+s4CgsL9+/ff/bsWR0dnZCQEGdnZ9EQtg0NDdeuXbO3tyeEFBUV7du3z8LCQrK+qanptm3bTpw4YWhoyOVyW164tbV1cXHxoUOHzM3NY2JiDh48GBwcfOTIkePHj2tra+/atevYsWNjxowpKSmZOXMmddtUfHy8k5NTc6v466+/jI2Nqdd9+vTJzMx8/Pjxb7/9Ro3dNm7cOAcHB8k6stnAANKBThAAAAAAAAApkLybKSMjw9vbW09PjxDy0Ucf3bp1y83Nrb6+3tPTk8lkOjs7R0RE1NTUmJmZWVhYNFm/rKzMx8eHGtBHX1+/5YVTo41Qz4Hy9vbeuHGjiYkJh8MJDw8nhNTW1g4bNowQwmazqaFn+vfvX1paWldX19wqxBNRr6mV6urqEkL8/PyarKOiJIf/aG6gEFBp6AQBAAAAAADoKGtr6927dwsEgvcO6qGtrZ2cnCz6s6amplOnTs1VFgqFDAaj9c3g8XjUi4aGBurFiBEjNm3aJKpQUVGhqfn310AGg8Hj8VpYhbGxcVlZGfW6rKysd+/ehBDR7FpaWs3VUTlNdnY0OawmqDolGnQHAAAAAABARTk4OJiZme3cuZPqfcjMzLx27Zqzs3NiYuKbN2/q6+vPnz/v6ura8kIk67u6uiYkJJSXlxNCXr161XJlQkhpaWlGRgYh5PTp0y4uLtTsxcXFhJCqqirqRSMtrMLW1vb169e5ubk8Hu/MmTOjRo1ydnZOTU3l8/kCgYDqypGs04Gt2FEVFRWBgYHGxsYWFhZ37twRCAQuLi5sNrtfv34rVqxo4UIVauwPSe/tBGn3GkFRFNMJwuPxmEympaWlhYXFuHHjKisrRZM+++yzpKSkM2fObN26lSpJTEy0sbExNzdv+SHqyq9NqcPCwgwNDR0cHBTUWKlpfeqSkhJfX18Wi9W/f39qWCbV1frU6vQp2aY9nBAiEAjc3NyU7bGFbdWm1IaGhiwWi8ViUSOHqa42pW50ZqCgJktB61M/evSI9Y/OnTtv375dca1WYR08fMTExNja2tra2gYFBb1580YxGdqug6m/++47W1tba2tr1drrOnjQVNETxQ6mVpsTRfXz3XffPX/+3N3d3cPD4/vvvzc3N7ewsJg9e/aECRMCAgKGDx/u7u7e8hIk65uZma1atWr69Ol+fn5r165tuTIhxNra+uTJk/7+/sXFxbNmzTI1Nd24cWNkZOSoUaOmTp367NkzyZW2sAomk7lly5b58+dTy3d2du7fv//YsWPDw8M//fRTExOTJut0dDt2QGRkpJubW1lZ2d27d/v168dkMuPi4oqKih49epSSknLlyhXlX2P588oVc3ZP9l0T6r/+TOz1Ns1bX9fw6axdS2Z+y62sFhXy+QIfp0VhAetDx6z7YvG+N1VvCSFvXtdsXXNkks/noWPW/Xt+9NPHZXy+wM95SfPL/hu3snrNJzETPvx36Jh1+bkl4pOybuXNGPvltNHrDu2Oa66khcriNn/20/jhqyPGb5KcFL3txJxJm7NvF7y3qc1R2JUg2traBQUFjx8/1tPT+/bbb0XlN2/edHV1vX79uqenJyFEKBTOnj37xIkT+fn5V69eTUlJUVSDpaKVqQkhc+bMuXjxooKaKWWtT71u3ToOh5Oamrp169YHDx4oqL3S0crUcvhclqfWv9eEEGr0L0U0U8pan1pDQ4PD4XA4HKV9Jm7rtT51ozMDBbVXOlqZ2sbGhnqjS0pKjIyMxo8fr7gmq7Z2Hz4qKyvXrl2bkpLy8OFDPT29Q4cOKShBe7Q7dU5Ozp49e27dunXv3r3Tp0/n5OQoKEF7tPugqdInih05VVCnE0U106NHj2+++SY9Pf3GjRuHDx+mTnVCQkLi4+OvXr1KPepFQ0MjNzdXfC7RU5ApjeoTQgICAq5cuXLlypXo6OhGa5SsrKGhsWXLlosXL+7fv58auWPkyJEXL16Mj4+/ePGim5ub+OpCQ0MXLVokuYpu3bolJCRQddzd3RMTE1NSUlatWkWVRERE/PLLL7t27dq9e3dERESTdRTi+RVqLBwAACAASURBVPPnN27cWLVqFYPB0NPTMzAwIIRQg7YKBAKBQKAqa5wR6X884avoX1b8dij+6eOy1s9Y9LhMS0vzu5+W6ffoKl6uqaXxy4Uvf7n4pW4XnRNH/0MI2bT6x55G3Y/Fb/r10obp88Y847xs5Sq+3Rhr52R+4j9bDpz43LiPgahcKBTuiPo16ps5Ry9EZaTl3s8qlCxpoXKjtQRM+GDrDwuabMDC1RMnh/smX8tqZYMlKfh2GAaD4ePjU1BQQAhZuXLlwIED09PThw0bdvDgwfnz52/YsCErK8vAwMDR0VFTU3P69Onq8fDz96YmhIwYMaJHjx6Kbqk0vTe1qanp8OHDCSFGRkY2NjZN9lKrnNa81zL9XFaI1qQuLy///fffFy5cqOjGSk1rUquf96Zu8sxA1bX+vb5x44aRkZGVlZXiGqsO2nH4EAqFQqGwtraWz+fX1tZSP1SqlnakfvjwobOzs56eno6OzvDhw1XxlKkdB001OFFs36mC+p0oAkhFQUGBmZlZeHi4nZ0dNeIsVW5vb9+zZ09HR0dqJFclX6ORcY+B/7IkhOgb6Jmye7+oeC2a9Cjn6ewJX9XXNdS+q585bsOTgsbfmOrqGnS7NDu+DIPBGOJqU1pckf+wpLSkYvYngRoaTEKI/SCLYSOavrLsenzm7ImbZ0/cHPXpAULIqxdV9zIfT/14JIPB0O3SSa97F0LI25pagUBQ8IjTrbuuhZWJhgZz1EcuydeyJEv+u92an0Rxcrbq2k23uSCddXXq6xqam/peCu4Eqa+vP3funJ2dHSFkx44dhw4dmjlzZnp6+sCBA7Ozs9etW1daWtq3b1+qsqmpaWlpqULbKx3vTa3oBspE61Pn5eXl5eW994ZJldDK1LL7XFaI1qRevnz5pk2bNDQ0FN1YqWlNaj6fb2VlZW9vv2/fPkW3Vzrem7q5MwOV1vpPs99++23atGmKa6maaMfhw8DAYOvWrVZWViYmJg0NDZMnT1Zc89upHakdHBzS0tIqKiqqq6uvXr1aUlLS/OKVVDsOmmpwokjPUwWQnUYXldANj8fLzMycP39+Tk6OUCgU3Rt4//79srKyx48f37x5U4XWyHlaznn6l60jW1Ri49DP3Wvg4e/PxXx9atRYV3PLxr38z0oqWug74DXwUq7fY/fv86TgmZWtGYP5ngF3yzgv9n97ZnvMooMnPl++fhohpLSkonefHlvXHPk4aOP2db/UvqsnhCwK25n/kPPir9e9jP5+tJChcY8Xf3ElS0RLbmFSa+h1031e2tpLVyQprBOkrq6OzWYPGDCga9euS5cupQrv3Lnj5OSUm5tra2tLlYgPkaAGv5O3MrWaaVNqLpc7adKk/fv3U8/6Ul1tSi27z2U5a2XqxMREJpOp6qOBiLT+vU5PT8/Pzz9//vzXX39948YNBbVXOlqZurkzAxXVpv/XPB7v1KlTU6dOVURL1US7Dx/V1dU//vhjbm5uaWmptrZ2TEyMIprfTu1ObWtru3btWj8/P39//yFDhoie3aAS2n3QVOkTRXqeKgDIFIvFMjY29vDwYDKZwcHBWVn/vb5AX1/fz8/v/PnzqrLG6jfvopYfXL5+WqMrO2ZEjslIzc17UDI1YmSjWb7ddOz7rSfGTRkuubSGel7I6LXhgRs66+pMCPuQEEJaMRbh3Yz8YV6OBr26EUKoiz74PEHew5LAKZ6HT31BhMJjP14lhKzeON2UbUSI2JOSBdRryZL/FjQ/6f0GDrGsqa4ND9wgPvRJ6ynsAKmjo1NUVCT6Mzs7e+7cuU+ePOndu3dNTY1QKHRzc0tMTGSxWBwOh6rD4XBEnf0qqpWpO3furLg2Sl/rU9fW1o4bN27JkiVjxoxRXHulo63vtehT0s3NTTEtloZWpk5JSUlISGCz2XV1dZWVlYGBgXFxTYyHpCpa/16z2WxCiLm5eWBgYEZGhkp3A7X+M5w6MyCEBAcHHzx4UGEtloY2/b++cuWKg4NDnz59FNZc1dfuw0dycrK+vj6LxSKEBAUFXb58OTIyUlEp2qojB81Zs2bNmjWLEPLJJ5+Ym5srpP3t0+6D5oQJE1T3RJGepwoAMmVpaWloaJidnT1w4MCrV686ODiUl5e/e/euX79+XC737NmzUr8XW0ZrrK9r+GJxzITQD1087BtNes2tefe2js/j19fxOnXWFp+07Iup5pZ9rpxNsxwwsdFcWtqasZc3iv5k9+/z896LQoGw5YtBhELCIP9TwbC3vkHPbo6D+xNCPH0HXTiZQgixcehHCOnVW7/inws6Kv7i9uqtL1kiWo7kpBNH/3Pm2HVCyK4fl4kuEmlO3sMSDU2Nn86sfe/FLE1SlkfkDhw4MC0trX///llZWR4eHhcuXEhLS+vcubOTk9OrV6+ys7MbGhp++eWXoKAgRbdUmppLreh2yVZzqfl8/pQpU0aPHk2NrqRmmktdXl7+9OlTQgj1KalmVwM1l3rNmjWlpaVFRUWnT592dnZW6R4QSc2lrqyspJ48V15efunSJUdHR0W3VJqaSy06MyCEUGcGim6pNLX8GY57YaSu9YcPMzOzO3fuvHjxQigUxsfHq/TzmNp00Hzy5AkhJCsr6+TJk9OnT1dcqzuq9QdNdTpRpOepAoDUxcTEhIWFWVtbl5aWrl69msvljh071sTExMnJycvLSxaHZqmvUSAQbFh52OUDuzHjh0lO/WbDbxGLPvIJGLr/2zOSU/tb9xUfQ6Q51nZmJqa9Du6O4zXwCCH3swpTr9+TrDZoqFVKUvbLiteEEOqai75mhvoGXQvzSgkhGam55pZ9CCG3U3PfVL21tGa9eV1TmFfK4/GvXrj1gbeTZAkhJCUpu6GeJzlp4nTvXy58+cuFL9/bA0IIeVnB7Wdh3L4eEKLAK0EkVVRU9OjRg8lkPnr0SPT5zmQyDxw4MGHChNra2tDQUJX+4bRJTaYmhAQHB6empr548YLFYn355ZfUDztqo8nU169fj4uLu337NjUk9Z49e9TskQpNpuZyuRMnTnzx4oWWllZoaKj6fWVqbg9Xb02mLisrGz9+fHV1tba29rx583x8fBTbSKlr7r2mzgxqa2sdHR1//PFHBbZQFppL/fbt28uXL0uO4Q8d1PrDx5IlS9zc3BgMxqBBgxYvXqzQVndU61PPnj37wYMH3bp1O3z4sKGhoUJb3VGtPGgyGAx1OlFs/amCep8oqrRly5b93//9X69evcQf+ScQCCZOnKihofHHH3+UlZWtXLmysLBQW1s7IiJixowZcm7hu3fvTp48GRYW1vFFVVVVBQcHJyQkSHGZHefm5kb9+kLp1q2b+J8qsca7GfkpSdl5D4rPHPs/QsjizyZ7+DhRky6fSdXQ1PDxHyoQCBaFfX3n1qPBLv/T0c9gMIWtuzdw7baI6O0nJo/8QltHy9yyz4KVEyTr9GH1mrM0aOXcPYSQfhbG67+eTQhZtjZk82c/1dfxzK1NVm+YTgiJ3n5i9aYZNvZmy6NC1396oL6uwTfAhbpaRLJky+dHfo5bZ9Crm+QkceuW7X9w98nryurJvmtmLgjwD/6fZ0sL3ncNS8sU0wmiqalZW1vbqFA0ik9aWpp4uY+PT35+vvwaJzNtSq2Kw5s3qfWpvb29ha24M00ltD61tbW1rD+X5aZNezjFzc1N1YfGaH1qOzs7NXgyLqVN73WjMwPV1abUurq6L1+2f7wuIB0+fKxevXr16tWybqTUdTD1tWvXZN1CWejgQVNFTxQ7mFptThTVz9SpUz/++OOVK1eKF8bGxpqamooefbh48WIXF5eXL1+OHTt22LBhcn6IWG1t7dGjR6XSYdGlS5ddu3ZJd5lACBnsYvOf7KZ/RxkdNGx00DBCCJPJ/OG3lZIVuul3kbwSREODeSXjO8man20Ob1QoWc3Lb4iX3xDxEruB5gdPrhEvOXz6C+rFEFebo+ejxCdJlpxL2dncJHEbvp3b3CRCyMuK1931u7RQoWXKcjsMAAAAAACASnN1de3evbt4ycuXL8+fPy+64qNPnz4uLi6EkJ49e5qbm1O3yopcvHjR39/f399/wYIFVMmxY8dGjx7t5+f33XffEUIqKir8/PxWrVo1atSoTz755ObNm8HBwSNGjEhNTaWment7r127dtGiRfPnz3/79u3jx49DQkKoRcXExBw+fHj37t0cDickJOTrr78mhFy6dCkoKGjs2LGffvppfX29qCWSM1ZUVPj6+m7atCkyMjIiIqKurq6mpoYa01d8mVVVVTNmzAgMDPT394+Pj5f+JoYWmZn3trRhLf342/aNGKoSorefuPGfu36B7R8dSYluhwEAAAAAAFAnX3311fLly5nMxr89P3ny5MmTJ4MGDRKVFBcXb9u27cSJE4aGhlwulxBSWFi4f//+s2fP6ujohISEODs7W1tbFxYW7tq1a8CAAWFhYb/++uuJEyeys7O3bds2bNgwaiGHDh0yNzePiYk5ePBgQEBAo/UuXrw4JSUlNjaWEMLhcI4cOXL8+HFtbe1du3YdO3as5dtzSkpKZs6cSd2EFR8fP2LECMllnjx50sbGZs2aNYSQmpqaDm49aIelX6j5k+kWrmo87Gtb4UoQAAAAAAAA6UtNTWUymc7Ozo3Kq6qqFi5cuHnz5i5d/ntJ/61bt3x8fKgBffT19QkhGRkZ3t7eenp62traH3300a1btwghbDbb1taWwWDY2tpST2a1t7cXPSbJ1NSUejiUt7d3enp6y81LS0vjcDjh4eEhISHXr19//vx5y/XZbDb14K3+/fuXlpY2WcfBwSE+Pn779u2ZmZni6QCUB64EAQAAAAAAkL7MzMw///zT09Ozvr7+9evXc+bMOXDgQF1d3dy5cz/++GMvLy/xykKhkMF4/1iP2tp/PxWVyWRSr5lMJp/Ppwp5PB71oqGhgRCiqakp+GeYzPr6etG8IiNGjNi0aZPkWpqcUVPz7y+PDAZDtKJGbGxszpw5k5iYuGXLFl9f33nz5r03EYCc4UoQAAAAAAAA6Vu4cGFqampycnJMTIyjo+OBAwf4fP4nn3wyYsSISZMmNars6uqakJBAjRLy6tUrQoizs3NiYuKbN2/q6+vPnz/v6ur63jWWlpZmZGQQQk6fPu3i4mJoaPj8+XM+ny8QCKgBd3V1dUV3qVBrLC4uJoRUVVVRLyiSM7ZAfJnPnz/v0qVLcHDwnDlzHjx40LrtBCBXHb0SJCkpKSoqShotkaGkpCQ2my3dBSK1ckJqaS0QqZUTUktrgTRMrQzoueWRWmkhNcjC/PnzMzMzX7165e7uvnTp0smTJzeqcOvWrYSEhJycnKNHjxJC1q9f7+fnR00yMzNbtWrV9OnTCSGWlpbR0dEWFhazZ8+eMGGCUCj86KOP3N3dKyoqWm6AtbX1yZMn161bx2Kxdu3apaurGxoaGhIS0qdPHyMjI0KIjo6Op6enn5+fp6fnF198sXHjxsjISB6Pp6mpuW7dOjMzM2o5kjO2QHyZLi4u33zzjYaGho6OzldffdX2TUgvWen5hFxQdCvUTVZ6/qChLT10qUOdIE5OToSQoqKijixEDthsNtVUqUBqZYbUHYfUygypO46eqZUBPbc8UiszpAZZ2Lt3b5PlgwcP/uOPPwghw4YNKywsbG72gICARkOZhoSEiJ7SQsSeo0wI+fzzz6kXGhoaKSkpotdbtmwRX8LcuXPnzv2fp42KVxg5cuTIkSObbIzkjKJVh4aGUi8SEhIklzlq1KjmAoI40f1QSUlJ3JpnLX9vh9YbNNTKydm6hQod6gQZNGiQ+IDGNIHU9IHU9IHU9EHP1MqAnlseqemDnqkBoCO8vLyofpCoqKii8vTw+Y0f5QMygoFRAQAAAAAAVJ74dSKgWnBfjCxkpeezA4ZKlqMTBAAAAAAAAEAxGj0nCKSFHTC0yW2LThAAAAAAAAAAxRDdFwPygUfkAgAAAAAAAAAttOFKkKysLNm1Q/6ysrLcffu3ppocGiM3SN1yNTk0Rm6QuuVqcmiM3CB1y9Xk0Bi5aWVqZUDPLY/UagCpoeMEAsHEiRM1NDSox8E0WUKprKzcvHlzcnJy165du3fvPnfuXD8/Pz6fb21tbWpqSgjp3Lnzhg0bhg5tYlADAGi31naCOLvbEkIIeSO7psiZu2//f0I1C6nVA1I3B6nVA1I3h56plQE9tzxSqwekho6LjY01NTV99uxZCyWUxYsXDxw48MaNG5qamhwORzSmqZaWVlJSEiHk6tWr27ZtO3HihLzaDkALre0EGepuO5R+H45ITR9ITR9ITR/0TK0M6LnlkZo+6JkaWunly5fnz59fsWLF1q1bmyuh3Lt3j8Ph/Pzzz0wmkxDCYrHmzZvXaGm1tbXdu3eXT8sB6AMDowIAAAAAAEjBV199tXz5cqpfo7kSSn5+vr29vWQ5IaShocHX17euru7169e//vqrbFsMQD8YGBUAAAAAAKCjUlNTmUyms7NzCyXihEIh9eKrr77y9fX18fGh/tTS0kpISEhOTj58+PDSpUv5fL6sWw5AK+gEAQAAAAAA6KjMzMw///zT09MzMjLy3r17c+bMkSwRVbaysnrw4IFAICCErFmzJi4ujsvlNlqgs7NzZWXlX3/9JdcYAOoOnSAAAAAAAAAdtXDhwtTU1OTk5JiYGEdHxwMHDkiWiCo7OjqamJjs3LmzoaGBEFJVVSW5wJycHB6PZ2hoKL8MADTQ9Jgg6SkPyc5Tcm4KAAAAAAAoofSUhwGjHBXdCnXz/fffb9q0yd3dXU9Pz8DAYNOmTVR5fX29p6cnIaRTp07ffPONlpaWQpsJoG6a6ATx8vKSezMAAAAAAEBJBYxyxHeE1hs8ePAff/zRcgkhpEePHl9//XWjQg0NjcLCQtm2D4Demu4EwWccAAAAAAAAAKgZjAkCAAAAAAAAALSAThAAAAAAAAAAoAV0ggAAAAAAAAAALaATBAAAAAAAAABooelH5AIAAAAAAECT0tLSFN0ElZSWljZgSE9FtwLoDp0gAAAAAAAAreXkbE1IHrc+X9ENUT0DhvR0crZWdCuA7tAJAgAAAAAA0FqDhloNGmql6FYAQDthTBAAAAAAAAAAoAV0ggAAAAAAAAAALaATBAAAAAAAAABoAZ0gAAAAAAAAAEALGBgVAAAAAACgaVnp+YRcUHQr1FlWej47YKiiWwE0gk4QAAAAAACAJnh5eSm6CeqPHTAU2xnkCZ0gAAAAAAAATfDy8sL3cwA1gzFBAAAAAAAAAIAW0AkCAAAAAAAAALSAThAAAAAAAAAAoAV0ggAAAAAAAAAALTD69evHZrMV3QwAAAAAAAAAABkqKirSZLPZGPEYAAAAAAAAANRbUlKSppeXV1RUlKJbAgAAAAAAAAAgQ1FRURgTBAAAAAAAAABoAZ0gAAAAAAAAAEAL6AQBAAAAAAAAAFpAJwgAAMD/t3fvMU1e/x/AT6uTLtJNMSwIRdpCyq2lSbmPTroLBZ3gQB2dAc3G3KaEGIZR8TIu04wwF9kQdBLBLAvgcIK4m2MR2KATnVwEHRqBMkBQJjBqlArt8/vjiU3zPOWi/nTzy/v1V5/D53w+n3PCP5w8nAIAAADArIBDEAAAAAAAAACYFXAIAgAAAAAAAACzAg5BAAAAAAAAAGBWsH4IMjExwePxzI/79u3bvn37TNLduXMnPz//obsZGRnx8PCYYXB2draXl1diYiKjaFxcnL29vVQqNY8MDg5GRUU5ODiIxeKmpqaHbg8AAAAAAAAAnl7/z2+CPOIhCJ/PLy4unmFwfn5+Q0NDXl4eo+iGDRt++OEHy8gPPvggKCiov7+/paXFxcXlodsDAAAAAAAAgKfXAx+CFBQUyGQyqVSakZFBCBkZGVGr1b6+vnK5vKKiIjMzs6urS6VS7dq1ixCi1+uNRqN5LiOYEFJWViaXy+Vy+erVq+n4tWvXWo1k1E1KSrp+/XpkZGRxcTGjaGho6MKFC81FBwYG6urqtm7dyuFw+Hy+nZ3do+0YAAAAAAAAADyV5k72A4PBIBQK6c+jo6PvvfceIeTKlSuffvrp+fPneTyeSqUKCQnp6+uTyWSfffYZIUSv1yuVyjNnztTU1NATg4ODjx496ufnRz+ePHnSMrizs3Pbtm1ardbBwWFoaMiyOiOSXTc3N7esrIwupFarLYsyXLt2bcmSJevXr29qagoKCsrNzZ0/f/7DbxgAAAAAAAAAPJ0mfRPExsZGd9+OHTvowbq6uhUrVjz//PM2NjYajebXX39VKBTl5eWpqalarZbP5zOSFBUVubu7mx8ZwbW1tZGRkQ4ODoQQxgsajEh23ZmvcGJiorGxcePGjW1tbRRFZWdnz3wuAAAAAAAAAPzPeNQ7QWQy2blz5zw9Pbdu3co+X/D397c8GWEEUxTF4XAeIu0DEQgEDg4OSqWSy+XGxMQ0Nzc/SjYAAAAAAAAAeEo92CGIUqn8/vvv//nnH4PBUFpaGhoa2tvby+fz161bt2XLlqamJltbW71eb46vqqoaHh42PzKCVSpVZWVlf38/IWRwcNCyECOSXdcymFGUwc3Nzd7e/uLFi3Q/lt8aAwAAAAAAAACzx6R3gljl7u6ekpISHBxMUZRGo3n11VcrKip27949Z84cHo/35Zdf8ng8tVotlUrDwsL279+fnJxseSfIH3/8YRksFouzsrLCwsIIIZ6enmVlZeZCjEh2XcuuGEVjYmJ+//33v//+WyAQZGRkJCQkHDp0KC4ubmxsTCaTFRUVPfKmAQAAAAAAAMDTh5OWlpaenv5vtwEAAAAAAAAA8Bilp6c/6p0gAAAAAAAAAABPBRyCAAAAAAAAAMCsgEMQAAAAAAAAAJgVcAgCAAAAAAAAALMCDkEAAAAAAAAAYFawfggyMTHB5XLd3NxcXFxkMllOTo7JZLIaeefOnfz8/AcquW/fPolEIpFIfHx86urqHmhuT0/Pa6+9JhAIXF1dDxw4QA9+/vnnnp6eEokkOzv74VbxoBWrq6vd3d1FItHOnTsniyGEmEymoKAgpVLJyBkXF2dvby+VSs0jjIQM7DyTZWbkMZlMAQEBQqHQxcVly5YtFEUx4svLyzkcTnt7O/04MTHB4XDefvtt+vHu3bt8Pl+j0TBmDQ0NrV+/3tHRUSwWL1u27PLly/REwX0ffvgh/WHBggV8Pp/+rNPpJt9mAAAAAAAAgMcvLS2NYhkfH7exsaE/d3V1BQQEZGZmssMoihocHPT29rb6I6suXLggkUhGR0cpirpx40Z3d/fM51IU9ddff9XW1tJznZycLl261Nra6urqOjo6OjY2FhQU1Nra+hCreKCKJpNJLBZfvHhxfHzc39+/vr6eHUPPzc/Pf+utt0JCQhg5a2pqzp07Z943dkJGPDuP1cxW8/T391MUdffu3eDg4B9//JGRec2aNUql0vw7MD4+/txzz/n4+BgMBoqivvnmGx8fn9jYWMas8PDw7du3j4+PUxRVX19/6tQpy622tH///pSUlMl3FwAAAAAAAOAJSUtLm/7fYYRC4cGDB3Nzc+lHtVqtUCikUunRo0cJIZmZmV1dXSqVateuXYSQ48ePBwQEKBSK+Ph4g8FACNHr9Uaj0ZxtYGDA3t7e1taWEPLCCy8sWbKkvb1dpVLRP83KysrJyRkYGPDw8EhOTo6Ojn799dfHxsbM052dnZcuXUrPdXd3v379+p9//unn58fn821sbJYuXXrixImZrILdZ1lZmVwul8vlq1evtpzIrtjc3GxnZyeTyebOnRsfH3/ixAl2DCHk5s2bx44dS0xMZDcTGhq6cOFC8yM7oeW+sfOwR+hgq3kcHBwIISaTif0WzO3bt+vr648cOVJaWmoe5HA4arX69OnThJBjx47FxsYyZjU2Nl67dm3v3r1z584lhLz44osrVqywuucAAAAAAAAA/ykzuhNELpcPDw/funWLEFJaWtrY2Hj+/PkvvvhidHT0o48+EolENTU1e/bs0el0Bw4c+O233xobG8VicUFBASEkODi4qanJnOrll18eHx/38PBITEysrq6erGJXV9fmzZvLy8tdXV0rKirYAVevXr169WpgYKBUKj179uzg4ODt27erqqp6enqmXQW7z87Ozm3btp0+fbqlpeXw4cNWp5sr9vX1OTk50YPOzs59fX3sGEJISkrKnj175syZM/XeEkKsJjTvGzsPe4QOnqwxb2/vRYsWyWSy8PBwy7oVFRURERESicTOzq6xsdE8rtFoSktL9Xp9T0+Pl5cXo9u2tjaFQsHlMn9zDAaD8L7CwsJpVw0AAAAAAADwhM3oEIR+b4T+nJeXFxISEhER0dPT09nZaRlWU1Oj0+nCw8NVKtVPP/1E/xFeVFTk7u5ujnn22WfPnj371VdfCQSC+Pj4ye4TcXNzEwqFhBAPDw/2XRIjIyNr1qw5fPgwn8/39PTcvXt3eHj48uXLFQoF/XrC1Ktg91lbWxsZGUm/NGFnZ8eea1mRsrhZw/L1CsuY6upqLpfLvrNjssbYCel9Y+exmpkOnqyxS5cu9ff3d3R0NDQ0WM4qKSmh7/vQaDQlJSXmcV9f38uXL5eUlERFRU3bsJmNjY3uvnfeeWcG6wYAAAAAAAB4oiY9MrDU0tJiZ2e3aNGin3/+WavVVldXz5s3LyIiwvIfVQghFEVFREQcOnTIctDf35+RjcPhBAYGBgYGenl55eTkqNVq81/s9+7d4/F4hJBnnnmGHuFyuRMTE5bTx8bGVq5cuXnz5mXLltEjCQkJCQkJhJCkpCSRSDTtKth9FhYWcjicySYyKgoEgt7eXvpHvb299MsXjBitVvvLL78IhUKDwTA8PBwVFVVZWTlZfqsJ6X1j5wkMDGRnpoOt5qEtWLAgPDz8u+++CwoKokdu3bp15syZtrY2DodjNBo5HI7ltbLLly9PTU1taGhopNRHmgAAArdJREFUa2tjdOvt7Z2RkWEymdgvgwAAAAAAAAD8l03/d2xHR8emTZuSkpIIIcPDwyKRaN68eTdu3NBqtYQQW1tbvV5PR6pUqsrKSvr1kJGRkY6ODkJIVVXV8PCwOVt7e/uFCxcIIRRFNTQ0uLi4LF68uLe312g0mkymKf5BhmY0GmNjYyMiIizfNejq6iKENDc3f/vtt/Hx8dOugt0nPdLf308IGRwcnLqiXC4fGhqi7x/9+uuv33jjDXbMzp07+/r6dDpdeXm5n5/fFCcgVhOa942dx2pmOpid5+bNm93d3fQyT5486enpaS56/PjxdevWdXd363S6np4ekUhk+U09GzZs+Pjjj93c3Njd+vr6urq67tix4969e4QQrVZ76tSpKVYHAAAAAAAA8B8x6SEIfcWDk5PTypUrNRoN/ZWrkZGROp3uzTffTE1NVSgUhBAej6dWq6VSaXJyskgkOnjwYHR0tJeXV2hoKH09R3JyMn0aQjMajSkpKUKh0M3NrbW19ZNPPpk/f/7GjRtVKlVcXJyjo+PU7dbW1lZWVubl5dHfulpeXk4IeffddxcvXhwbG1tYWGhvbz/tKth9isXirKyssLAwqVS6adOmqStyudyCgoJVq1aJxeJXXnlFqVRa7WoKMTExL7300pUrVwQCwZEjR9gJ2fs2NTqYnWdkZCQyMtLR0VEul6tUqrVr15qnlJSUREdHmx9XrVpVXFxsfhQKhYx9sFRSUtLX1+fs7Ozi4rJ3716JREJvtfkrct9///0Zdg4AAAAAAADwxHDS0tLS09P/7TYAAAAAAAAAAB6j9PR0XOsAAAAAAAAAALMCDkEAAAAAAAAAYFbAIQgAAAAAAAAAzAo4BAEAAAAAAACAWQGHIAAAAAAAAAAwK3BCQ0NVKtW/3QYAAAAAAAAAwGNUU1Pzf7qcojzP5EhLAAAAAElFTkSuQmCC",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "# use `widget` for better user experience; `inline` is for documentation generation\n",
    "\n",
    "#Topology of the computer:\n",
    "!lstopo /tmp/topo.png\n",
    "from IPython.display import Image\n",
    "Image(filename='/tmp/topo.png') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8dd653b1-3040-4f08-9bfe-1b1178cfcde4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pyFAI version:  2025.1.0-dev0\n",
      "OpenCL devices:\n",
      "[0] NVIDIA CUDA: (0,0) NVIDIA L40S\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import glob\n",
    "import concurrent\n",
    "import time\n",
    "import json\n",
    "from matplotlib.pyplot import subplots\n",
    "import multiprocess\n",
    "from multiprocess import Process, Value, Array\n",
    "if \"mpctx\" in globals():\n",
    "    mpctx = multiprocess.get_context('spawn')\n",
    "else:\n",
    "    multiprocess.set_start_method('spawn')\n",
    "    mpctx = multiprocess.get_context('spawn')\n",
    "import numpy\n",
    "import hdf5plugin\n",
    "import h5py\n",
    "import pyFAI\n",
    "print(\"pyFAI version: \", pyFAI.version)\n",
    "import SharedArray\n",
    "from silx.opencl import ocl\n",
    "from silx.opencl.codec.bitshuffle_lz4 import BitshuffleLz4\n",
    "import inspect\n",
    "import collections\n",
    "Item = collections.namedtuple(\"Item\", \"index filename\")\n",
    "MAIN_PROCESS = multiprocess.parent_process() is None\n",
    "print(ocl)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7c6aaba7-377d-4478-87b4-45b1b85c7ce8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#This cell contains the parameters for all the processing\n",
    "params = {\n",
    "    \"DEVICES\": [[0,0],],\n",
    "    \"NWORKERS\": 32,\n",
    "    \"FRAME_PER_FILE\": 1000,\n",
    "    \"NFILES\" : 1000,\n",
    "    \"NBINS\" : 1000,\n",
    "    \"DETECTOR\":\"Eiger_4M\",\n",
    "    \"pathname\" : \"/scratch/kieffer/big_%04d.h5\",\n",
    "    \"pathmask\" : \"/scratch/kieffer/big_????.h5\",\n",
    "    \"dtype\" : \"float32\",\n",
    "    \"SHARED_NAME\" : \"shm://multigpu\",\n",
    "    \"array_shape\" : [1000, 1000, 1000],\n",
    "    }\n",
    "with open(\"param.json\", \"w\") as w: w.write(json.dumps(params, indent=2))\n",
    "for k,v in params.items():\n",
    "    globals()[k] = v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0f58cd73-78b5-4b6e-a6cd-2d553d35f3fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_integrator(detector=DETECTOR):\n",
    "    \"Build an azimuthal integrator with a dummy geometry\"\n",
    "    geo = {\"detector\": detector, \n",
    "           \"wavelength\": 1e-10, \n",
    "           \"rot3\":0} #work around a bug https://github.com/silx-kit/pyFAI/pull/1749\n",
    "    ai = pyFAI.load(geo)\n",
    "    return ai"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1d4a5604-4c2d-4bdb-aea8-63114f9e43c4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['/scratch/kieffer/big_0000.h5',\n",
       " '/scratch/kieffer/big_0001.h5',\n",
       " '/scratch/kieffer/big_0002.h5',\n",
       " '/scratch/kieffer/big_0003.h5',\n",
       " '/scratch/kieffer/big_0004.h5']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Generate a set of files\n",
    "def generate_one_frame(ai, unit=\"q_nm^-1\", dtype=\"uint32\"):\n",
    "    \"\"\"Prepare a frame with little count so that it compresses well\"\"\"\n",
    "    qmax = ai.array_from_unit(unit=unit).max()\n",
    "    q = numpy.linspace(0, qmax, 100)\n",
    "    img = ai.calcfrom1d(q, 100/(1+q*q))\n",
    "    frame = numpy.random.poisson(img).astype(dtype)\n",
    "    return frame\n",
    "\n",
    "def generate_files(img):\n",
    "    cmp = hdf5plugin.Bitshuffle()\n",
    "    filename = pathname%0\n",
    "    shape = img.shape\n",
    "    with h5py.File(filename, \"w\") as h:\n",
    "        ds = h.create_dataset(\"data\", shape=(FRAME_PER_FILE,)+shape, chunks=(1,)+shape, dtype=img.dtype, **cmp) \n",
    "        for i in range(FRAME_PER_FILE):\n",
    "            ds[i] = img + i%500 #Each frame has a different value to prevent caching effects\n",
    "    res = [filename]\n",
    "    for i in range(1, NFILES):\n",
    "        new_file = pathname%i\n",
    "        os.link(filename,new_file)\n",
    "        res.append(new_file)\n",
    "    return res\n",
    "\n",
    "# Create a set of files with dummy data in them:\n",
    "if len(glob.glob(pathmask)) == NFILES: \n",
    "    input_files = glob.glob(pathmask)\n",
    "    input_files.sort()\n",
    "else:\n",
    "    for f in glob.glob(pathmask):\n",
    "        os.remove(f)\n",
    "    input_files = generate_files(generate_one_frame(build_integrator(DETECTOR)))\n",
    "input_files[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "89a5845f-1ce6-41e9-9b1a-4ce532058d4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#This is allows to create and destroy shared numpy arrays\n",
    "\n",
    "def create_shared_array(shape, dtype=\"float32\", name=SHARED_NAME, create=False):\n",
    "    if create:\n",
    "        ary = SharedArray.create(name, shape, dtype=dtype)\n",
    "    else:\n",
    "        ary = SharedArray.attach(name)\n",
    "    return ary\n",
    "\n",
    "def release_shared(name=SHARED_NAME):\n",
    "    if MAIN_PROCESS:\n",
    "        SharedArray.delete(name)\n",
    "\n",
    "result_array = create_shared_array(array_shape, dtype, SHARED_NAME, create=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "dc73341d-85d8-4a60-9085-3a556312a8a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def worker(rank, queue, shm_name, counter):\n",
    "    \"\"\"Function representing one worker, used in a pool of worker.\n",
    "    \n",
    "    :param rank: integer, index of the worker.\n",
    "    :param queue: input queue, expects Item with index and name of the file to process\n",
    "    :param shm_name: name of the output shared memory to put integrated intensities\n",
    "    :param counter: decremented when quits\n",
    "    :return: nothing, used in a process.\n",
    "    \"\"\"\n",
    "    def new_engine(engine, wg):\n",
    "        \"Change workgroup size of an engine\"\n",
    "        return engine.__class__((engine._data, engine._indices, engine._indptr), \n",
    "                              engine.size, empty=engine.empty, unit=engine.unit, \n",
    "                              bin_centers=engine.bin_centers, azim_centers = engine.azim_centers,  \n",
    "                              ctx=engine.ctx, block_size=wg)\n",
    "    #imports:\n",
    "    import pyFAI\n",
    "    import numpy\n",
    "    import SharedArray\n",
    "    from silx.opencl.codec.bitshuffle_lz4 import BitshuffleLz4\n",
    "    import h5py\n",
    "    import json\n",
    "    import sys\n",
    "    #load parameters:\n",
    "    for k,v in json.load(open(\"param.json\")).items():\n",
    "        globals()[k] = v\n",
    "    #Start up the integrator:\n",
    "    ai = pyFAI.load({\"detector\": DETECTOR, \n",
    "                     \"wavelength\": 1e-10, \n",
    "                     \"rot3\":0})\n",
    "    blank = numpy.zeros(ai.detector.shape, dtype=\"uint32\")\n",
    "    method = (\"full\", \"csr\", \"opencl\", DEVICES[rank%len(DEVICES)])\n",
    "    res = ai.integrate1d(blank, NBINS, method=method)\n",
    "    omega = ai.solidAngleArray()\n",
    "    engine = ai.engines[res.method].engine\n",
    "    \n",
    "    omega_crc = engine.on_device[\"solidangle\"]\n",
    "    engine = new_engine(engine, 512)\n",
    "   \n",
    "    gpu_decompressor = BitshuffleLz4(2000000, blank.size, dtype=blank.dtype, ctx=engine.ctx)\n",
    "    gpu_decompressor.block_size = 128\n",
    "    result_array = SharedArray.attach(SHARED_NAME)\n",
    "    with counter.get_lock():\n",
    "        counter.value += 1\n",
    "    #Worker is ready !\n",
    "    while True:\n",
    "        item = queue.get()\n",
    "        index = item.index\n",
    "        if index<0: \n",
    "            with counter.get_lock():\n",
    "                counter.value -= 1\n",
    "            return\n",
    "        with h5py.File(item.filename, \"r\") as h5:\n",
    "            ds = h5[\"data\"]\n",
    "            for i in range(ds.id.get_num_chunks()):\n",
    "                filter_mask, chunk = ds.id.read_direct_chunk(ds.id.get_chunk_info(i).chunk_offset)\n",
    "                if filter_mask == 0:\n",
    "                    # print(f\"{rank}: process frame #{i}\")\n",
    "                    dec = gpu_decompressor(chunk)\n",
    "                    intensity = engine.integrate_ng(dec, solidangle=omega, solidangle_checksum=omega_crc).intensity\n",
    "                    result_array[index, i,:] = intensity.astype(dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c80957b0-fbe3-4692-8536-a56e321b5ba4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "/nobackup/scratch/kieffer/py310/lib/python3.10/site-packages/pyopencl/__init__.py:519: CompilerWarning: Non-empty compiler output encountered. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more.\n",
      "  lambda: self._prg.build(options_bytes, devices),\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n",
      "Please upgrade to silx v2.2+\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[<Process name='worker_00' pid=3992353 parent=3992198 started>,\n",
       " <Process name='worker_01' pid=3992354 parent=3992198 started>,\n",
       " <Process name='worker_02' pid=3992355 parent=3992198 started>,\n",
       " <Process name='worker_03' pid=3992356 parent=3992198 started>,\n",
       " <Process name='worker_04' pid=3992357 parent=3992198 started>,\n",
       " <Process name='worker_05' pid=3992358 parent=3992198 started>,\n",
       " <Process name='worker_06' pid=3992359 parent=3992198 started>,\n",
       " <Process name='worker_07' pid=3992360 parent=3992198 started>,\n",
       " <Process name='worker_08' pid=3992361 parent=3992198 started>,\n",
       " <Process name='worker_09' pid=3992362 parent=3992198 started>,\n",
       " <Process name='worker_10' pid=3992363 parent=3992198 started>,\n",
       " <Process name='worker_11' pid=3992364 parent=3992198 started>,\n",
       " <Process name='worker_12' pid=3992365 parent=3992198 started>,\n",
       " <Process name='worker_13' pid=3992366 parent=3992198 started>,\n",
       " <Process name='worker_14' pid=3992367 parent=3992198 started>,\n",
       " <Process name='worker_15' pid=3992368 parent=3992198 started>,\n",
       " <Process name='worker_16' pid=3992369 parent=3992198 started>,\n",
       " <Process name='worker_17' pid=3992370 parent=3992198 started>,\n",
       " <Process name='worker_18' pid=3992371 parent=3992198 started>,\n",
       " <Process name='worker_19' pid=3992372 parent=3992198 started>,\n",
       " <Process name='worker_20' pid=3992373 parent=3992198 started>,\n",
       " <Process name='worker_21' pid=3992374 parent=3992198 started>,\n",
       " <Process name='worker_22' pid=3992375 parent=3992198 started>,\n",
       " <Process name='worker_23' pid=3992376 parent=3992198 started>,\n",
       " <Process name='worker_24' pid=3992377 parent=3992198 started>,\n",
       " <Process name='worker_25' pid=3992471 parent=3992198 started>,\n",
       " <Process name='worker_26' pid=3992596 parent=3992198 started>,\n",
       " <Process name='worker_27' pid=3992721 parent=3992198 started>,\n",
       " <Process name='worker_28' pid=3992753 parent=3992198 started>,\n",
       " <Process name='worker_29' pid=3993034 parent=3992198 started>,\n",
       " <Process name='worker_30' pid=3993068 parent=3992198 started>,\n",
       " <Process name='worker_31' pid=3993100 parent=3992198 started>]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def build_pool(nbprocess, queue, shm_name, counter):\n",
    "    \"\"\"Build a pool of processes with workers, and starts them\"\"\"\n",
    "    pool = []\n",
    "    for i in range(nbprocess):\n",
    "        process = Process(target=worker, name=f\"worker_{i:02d}\", args=(i, queue, shm_name, counter))\n",
    "        process.start()\n",
    "        pool.append(process)\n",
    "    while counter.value<nbprocess:\n",
    "        time.sleep(1)\n",
    "    return pool\n",
    "\n",
    "def end_pool(pool, queue):\n",
    "    \"\"\"Ends all processes from a pool by sending them a \"kill-pill\"\"\"\n",
    "    for process in pool:\n",
    "        queue.put(Item(-1, None))\n",
    "\n",
    "# Build the pool of workers\n",
    "queue = mpctx.Queue()\n",
    "counter = mpctx.Value(\"i\", 0)\n",
    "pool = build_pool(NWORKERS, queue, SHARED_NAME, counter)\n",
    "pool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "9b9ef84b-650b-4b8e-87df-104d6aea6953",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "32"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "start_time = time.perf_counter()\n",
    "for idx, fn in enumerate(input_files):        \n",
    "    queue.put(Item(idx, fn))\n",
    "end_pool(pool, queue)\n",
    "counter.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a1491ab-d03c-4685-8adf-0b0de570a033",
   "metadata": {},
   "outputs": [],
   "source": [
    "while counter.value:\n",
    "    time.sleep(1)\n",
    "run_time = time.perf_counter()-start_time\n",
    "print(f\"run-time: {run_time:.1f}s\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58c44cc3-6218-428b-8d42-02e12506fe55",
   "metadata": {},
   "outputs": [],
   "source": [
    "# result_array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "978b72f0-1ff1-4f19-ad5b-dcdd97e41851",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = subplots()\n",
    "ax.imshow(result_array[:,:,5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7db29db0-f29c-41c1-89da-b53f93ef09db",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"Performances: {NFILES*FRAME_PER_FILE/run_time:.3f} fps\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c584fc0b-6109-4b4e-a87b-b29ca3d87289",
   "metadata": {},
   "outputs": [],
   "source": [
    "release_shared(SHARED_NAME)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e104df72-0507-4e71-935d-d4d2389dea49",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "It is possible to reach 845 frames per second on a single GPU computer. In this configuration, a million of Eiger4M frames are reduced in something close to 19mn.\n",
    "\n",
    "Energy considerations for the reduction of one million frames:\n",
    "* the GPU was consuming 150 W (over the 350W TDP of one GPU) over 1183s which represents 178 kJ (49.2 Wh) for the data reduction.\n",
    "* The equivalent processing using only a 6-core workstation takes half a day (40682 s) and consumes 1561 kJ (433 Wh).\n",
    "* Processing on GPUs is thus close to 9x more efficient than processing on CPU only.\n",
    "\n",
    "Other considerations:\n",
    "* Here reading from disk is fast enough, no need to put more than 2 processes per GPU. If reading is much slower, more can be beneficial.\n",
    "* Since the output array is in shared memory, one needs to have enough RAM for hosting it."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
