{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# 2A.i - Jupyter et calcul distribu\u00e9\n", "\n", "[Jupyter](http://jupyter.org/) a \u00e9t\u00e9 d\u00e9coup\u00e9 en plusieurs extensions comme [ipyparallel](http://ipyparallel.readthedocs.io/en/latest/) qui permet de distribuer un calcul sur plusieurs processus. Ce notebook montre comment faire sur une seule machine."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Avec de commencer, quelques mots de vocabulaire :\n", "\n", "* [thread](http://fr.wikipedia.org/wiki/Thread_%28informatique%29) : il est possible de parall\u00e9liser un traitement au sein d'un m\u00eame programme ou processus, c'est un *thread*. Les threads ont acc\u00e8s aux m\u00eames variables.\n", "* [processus](http://fr.wikipedia.org/wiki/Processus_%28informatique%29) : c'est un programme, chaque processus a sa propre m\u00e9moire non partag\u00e9e avec d'autres processus."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Une fontion utile pour r\u00e9cup\u00e9rer les processus qui tournent\n", "\n", "Voir [psutil](https://pythonhosted.org/psutil/)."]}, {"cell_type": "code", "execution_count": 2, "metadata": {"collapsed": true}, "outputs": [], "source": ["import psutil\n", "\n", "def find_process(name):\n", " for proc in psutil.process_iter():\n", " try: pinfo = proc.as_dict(attrs=['pid', 'name'])\n", " except psutil.NoSuchProcess: pass\n", " else:\n", " if name in pinfo[\"name\"]:\n", " return pinfo, proc\n", " return None, None"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"data": {"text/plain": ["(None, None)"]}, "execution_count": 4, "metadata": {}, "output_type": "execute_result"}], "source": ["i, p = find_process('ipcluster')\n", "i, p"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Calcul distribu\u00e9\n", "\n", "Quelques liens :\n", "\n", "* [Overview and getting started with ipyrallel](https://ipyparallel.readthedocs.io/en/latest/)\n", "La distribution d'un programme implique l'ex\u00e9cution en parall\u00e8le de morceaux de programme, sur la m\u00eame machine ou sur des machines diff\u00e9rentes. Dans le cas de cette s\u00e9ance, ce sera sur la m\u00eame machine mais le principe reste le m\u00eame : une machine centrale (serveur) envoie des donn\u00e9es et le traitement associ\u00e9 sur d'autres machines (clients). Pour que les clients comprennent qu'ils doivent ex\u00e9cuter un programme, ils doivent continuellement attendre qu'on leur envoie des instructions : un client est lanc\u00e9 puis attend des instructions. Pour lancer des clients localement sur cette machine, on doit aller dans le r\u00e9pertoire :"]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"data": {"text/plain": ["'c:\\\\python35_x64\\\\Scripts'"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["import os,sys\n", "if hasattr(sys, 'real_prefix'):\n", " exe = sys.real_prefix\n", "else:\n", " exe = sys.base_exec_prefix\n", "f = os.path.join(exe, \"Scripts\")\n", "f"]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"data": {"text/plain": ["['ipcluster.exe']"]}, "execution_count": 6, "metadata": {}, "output_type": "execute_result"}], "source": ["[ _ for _ in os.listdir(f) if \"cluster\" in _ ]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Il faut ex\u00e9cuter l'instruction ``ipcluster start -n 2`` depuis une ligne de commande. Celle-ci vient avec le module [ipyparallel](https://ipyparallel.readthedocs.org/en/latest/) ``2`` signifie deux clients puis on v\u00e9rifie que ceux-ci sont bien en train de s'ex\u00e9cuter. Sous Windows, il faut ouvrir une fen\u00eatre de commande avec la commande [cmd](http://windows.microsoft.com/fr-fr/windows/command-prompt-faq#1TC=windows-8)."]}, {"cell_type": "code", "execution_count": 6, "metadata": {"collapsed": true}, "outputs": [], "source": ["# ipcluster start -n 2"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On obtient quelque chose similaire \u00e0 ceci :"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAAA2EAAAE8CAIAAADG4S5oAAAAAXNSR0IArs4c6QAAAARnQU1BAACx\njwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAEz+SURBVHhe7d17sK5pWR745tCHfere3S0w0CbU\nVESZSBQ5yBkUgZghDh5GBauMCIxCFIwSAcWaiBBQUbo5jDZQAzMaMw7RiZZTUUYsMA5KWvos6gwz\nmZikpuJ/879/OPd6rvu91/Xdz+F7vsP61tp7X7/atXLd13O/7/eu7t2sN9hbr7t06dLTn/70W2+9\n9UUvetETn/jE5zznOfbVSnPu3LmbF7fccsv/NeHChQvXFY985CMRHvWoR91+++3f8R3f8da3vvVp\nT3vaK17xiqc+9anf+73fe+eddz7+8Y9/5zvf+X3f930XL1581ate9ZKXvOSZz3ymff3Kr/zKb/u2\nb3vlK19522234SYvf/nLf+qnfurtb3/7xz72sQ984ANvfvObX/va1z7/+c/H6Zd/+Zfb/b/hG77h\nBS94wRvf+Eb7+qxnPctue8cdd9iajU95ylO++7u/2x7Dbv7617/+SU960nd+53favj3Ja17zGvtO\nX/rSl9qF9v3a3W688Ubc1r4Fy/bVvgUb7VtDPn/+PBZuuummRzziEddffz0usWCncbmNdkNcC49+\n9KNttP5lL3uZ/XW2a2208oYbbrBTyzi1jK9gGQu2GQ9Ts7sZPDMa27TRLrevOAVreje5cvnvv6Ev\n/t+f/u3f/mev/PbvvOOOL7/xxtv//iu+5brrr/ubT37i05/7rEuXn3Dh4uMf9ciLj77+xksXLj/i\nuuvt1+Me84THPeaORz/ihlsu3Xr50u3nbrxkH2T58Y/7Uvvdce6Gi5cu3HLx3MVvfOk3PvWrvtqO\nzt94/uK5SxfOXfrSJ/yNy5cu33zxlsfc/phnPu2ZN1x/w3Of+9zbLt/2iOse+dKXvOxL77DLr7vx\nhpsuX771lkuXL99y69O+5unPeNozrn/0DbZwy4Vbv+TWx9nH3Xrz7edvuPnypVvt66Vzl2+//Jjz\nN1y6/fJjb7l02xMe/6VPfdozbv2Sxz3lq5/6JY99/Ne9+CUvfsnLHn/H33zU9Teeu3jLzZdvtwd5\nwh1/83kv+LqnfNXX2Gddvu0x11t18+Wbzt9s48233m7hpvOXrr/pwqVbbr3l1i+x8tE33mjPcuP5\nc0//2mc+6Su+4tzFi4949KNvunDh3MVLlm+6cP6Gc+fP2T8ql295/B13nL906XFPePxzn2+f8CIL\n9vvLysu33273ue2xj3nSV3z5DedvfNwTHnfD+esfdcMjL9xy/r/4O09+4dc///Jtl2+8cMNX/O0n\nvejFL/zP7njc3/jP7/i7L3/Z1zzzqbfcdvNXPfUpT/vapz7lq/+2LTzpyV/2977pG2977G03nrd/\nhB59/uZzN99qn3vRgt1q+XXORitvvvUivt5y2yX8su/swrJ58fLRL1uOxn6V+9jlFuyeF+3v4cXL\n9nfykn2N5vzNF5b+6G/peftVeqwdLXhzlLFpazdduunc8a9zN1260T7lgv265cLFy7Zz9Ki33HLh\n8sVzt104f9vF87ddumC/br/54lHAeNF/3XrpvG3a/sXLx1/t17mLN9108Sb7ave3b8R+2d8f++Sb\nz99084Vzt5Rf9pfq6DsoX/2X9Uff07nL52+6fP7crRfO46t90OVzubHNCzfdcNF+2e/48zfZL7vQ\nvqJBGV8v2j8GpYxf/g/b0F0f/KU/++L/+2/ufejeh+5/8Av33/8n99774Ofvf/CBBx/8woP3/+nn\n//j+e+75/H333fvww/ebhx566L777vvsZz/7cJnvvffez3/+85/85Cff/e532w+R3//9z9xzz+d+\n4zfv/uTv/fT/+tv/9DO//9G73v+WX/of7vyjP/jkO37yxz589wfe+c6fuPP9P/erv/qrn/jVX//H\nP/Jjn/3s5z7xiU+8//3v/9SnPvWe97znrrvu+ou/+Itf+ZVfsdJu/vGPf/y3fuu37GfNhz/84bvv\nvvvXfu3XfvZnf9autc963/ve94d/+If2A+jHf/zH7cg23/GOd9jRu971rl/4hV/44Ac/+MUvfhGP\n9+CDD9rXBxYPPvDA/Uf/z4MP3n/fQ/f98UMP3Hf/Qw/f+/Af/9GD//qzf/zpP/zc/37v5x+y83sf\nuOdzD933id/+o//6dR+9+Ulv+obX/cuP/cF/euOdv/fkl7/vha/5+I/e/fDbP3bPm+781Js/+LnX\nvuNTP/Mr/+Hu3/rCV/3dD73vf/qP/+Btn/qB9/zJ237h33772z79Pe/4g7d85P95y0f+/Xe87d88\n4bm/+GUv+eirf/LB//Zj/+69//OfvenOT//gXZ99y4e/+Mp/8gdf9l/+i8vP/ugbPvDAP3jn7/+d\nb3nvT/zyw+/8xL996nf90vmnv++bfuzPv//uf//St/zOa95/3xP//s++9Zf/9K3/40Pf/hO/9b7f\nfujFb/j4j3zkwR+6+09e8bZPvunu+1/1jt973bs+/aMf+rOf+42/fN3PP/y3vumXH/v1d73nf/lP\n3/Xjv/e3XvgjH/wXf/6N3/2ed3/0X7/l53/nvR+/520/9+kfeffvfeRffuHJz3/jc7/5nf/drz30\nD9/56z/zzz73U//9Pf/VP/yFD/3GF172uvc/65U/+b5/9ZdP/ObfufzCX3vsi3/9ad/1qde+9/98\n1T/5317+pl/5wZ//zEf/1V9+z1s++7xX/PPnfcs//4G3f+bOH3vv3a/5e7/59lf95e/c/Zef+83P\n/eQP/vSLn/Hh73nTG77++1/w1d/zB/f8x3v+/MGH/uxPH7z3zx6472H7W1D+JtjfgwM5+ij7O//g\nA/c/ZF/tt/Z9DzxovzHsd8WDDz70hT++9+EH/vQ//NyHftn/eRh6zpvuevYP3fWsH7rza//R0dfn\nvOl9z/6h99nX5/7Qzz/nTe991ht/+lk/+J5n/cC7n/GGdz37De953g/83HPf8N5nf/9Pf+1/855n\nvO6fPv217/qa733n017zTsvWPPv1P/Oc1//sc23n6PL3Pe8f3fm8H77rhW/+wAv/8Qef/8N3HTU/\nfNcL3vyBF/3oh77uLR/6+rf84tf9yN1f98Mfue4zIiIiIiKrrvsrERERETklf/TN33wWfvnTEL0j\nioiIiJya9K52Wr/8aYjeEUVEREROTXpXO61f/jRE74giIiIipya9q53WL38aondEERERkVOT3tVO\n65c/DdE7ooiIiMipSe9qp/XLn4boHVFERERkD5485EuV9K52Wr/8aYjeEUVERET2wF8GO3ypkt7V\n5n/9dTFu5n/50xC9I4qIiIjsweBF8CTeEe0XvxRy3uKXPw1Z/4545wd+caNffpmIiIjIteTw74j2\nC6+GkI42+uVPQ6beER/+P/5d79frf+z9/FXviCJr/e5bn/zkt/6uD5s4unC7K1u++JFv3d/NzrqT\n/mZ3+Vtj137rR77ogyTlr6y7Zn67ypXLfp96qgyO0rvaFr92f0G0X/40ZOod8Z7P3zv5S++IImtt\n/Y64Yuc3C70jnhFb/p086VfL7e6/56f64kfeGnezW5/sdyyyu1N5R8QLIqSjjX7505Cpd8Trpukd\nUWSto591ekc8LL0jbmy7+5/kUx39TTzRb1muSfbq1uTHGxpcODhK72ob/eJXQ85b/PKnIXpHFDk0\n+zG6vK/g1eXoa+E/AGnhSPxojJ+/Rwth2Yy7DP7bluMLv/Ujv1s+2w/mLqed40u55BuWpz36kOOT\n49Xjvcm1lY85fsCjtv4LCBt/s3xB569AT/ku/Brk449YvRl/H3giupZvs3xzyKvPdjyZZaX1TeEW\nWKfvv7b2/vzcx8+4cv+3tp6qD9fGfekb77HPmdgS2Qh+/9X8eEN+cYcvVdK72vyv+qWwbuZ/+dOQ\nDd4R8cHIkEajd0SRtY5+ovpPUPyA9J97Rz3iys9C/Cg9Siv16s/Lo6UY6QPY8f19ON6aubw86/EF\nHylppVydyif4TcvB6rThGv0PHbnHFX4Bf4Oc8Rl0q9Y3y+3vfmQ5nmW3SZ+2jOm7WBnw15Cu5dtg\nu/9s9W6MR0+ATyn1tx7/z2vbpu7f/eu/cv/Vq4bKfZbto0ded+HxtyVydUnvaqf1y5+GzL4jxusg\nAn9lekcUWYt+1PEPZ0w4oJ+0x+Xqz9/+sHLNsbRzNDdv3L98/T3T09IF1bRcN7m24rg/+rzjleNP\nz1fSfdPRck3zm57GN6XPKo6fMZ8AXbv6bPFIzWdb2V29cPVKPmhbf/9Vx0fV/QdXZavXNp+BHJ0P\nF0SuXOld7bR++dOQjd8RmZWp1zuiyFr2Y3T5Ybf6g5F+aMaPWt5Y+fnLw9Eds/zjtPoZfFzMXH60\nk7tWefxYx+lId5pcM0cPHLxf/a6OpnKwzTfrt0/fz4Iva6zwk64+tVmKo3uMr1299PihW8/Gu0d3\nzspy9Reibd39C18CP6ruX1214EfEFavXHk3tK025eOL7ELkypXe10/rlT0N2+u8ROYDeEUXWOvqB\n5z/v+j8m/WftysLKz18e6I5d1c7xrbe6/EijtMof6zgd6U5za5bofxZ53Hf+Ah6trzzY7Dd7tLfN\n68jxE63mYik6H037q5eufnP52Xi3c+fqFkOD+5cPWPk4z9X9V64aW732aGpeWR5r9nsQuSKld7XT\n+uVPQ3b69xENmjjSO6LIWvQDffBjsvywXf2jFis/f3no/nwl1c7xY2x1+ZG6PGr8gVeetj9NrdFt\nDX1s54A2wO40/c2u36jxcx9/FsTcuTFdy7epb2ToFrzbfeSjg3SLocH9j29DS9X9V7+Doe5NWeOv\ngchVJ72rndYvfxqywTviDL0jiqxFP/ZGPyaPftoa+rG58vN39Yfn0XQ88v9WuWNHO3F9uSCumL08\ndujPW6xcSN+AHdHDd6epNb7xUV73P2sulx7f6WhY983+7luPv7f03jOBn7v+tNWj48+p/sxK+d78\nnG7TfLaj86VOd45vavJ7WXv/o3rlGZepuv/qUw2tXssfEZqlyFUnvaud1i9/GqJ3RJFDox+jwx+T\nR3srPyCtoPloO94izNH6ovNzdbnE4H/pCP00n7icd45X6KYrF64+bXeaXDv+FGuO+9Vv4miKy+m5\nZr5ZWo9bzOPnLvn4f5sPf66hj/Yr+Fo6p4duP9vSLvenO8da+r47Ju5/vGIbx09c33/lqqHVa48m\n+nA4/tRj628sInsy9Y640S+/TETkmrT6zicicqVa/44oIiLz9I4oIlcHvSOKiOyT3hFF5Oqgd0QR\nERERyfSOKCIiIiKZ3hFFREREJNM7ooiIiIhkekcUERERkUzviCIiIiKS6R1RRERERLLuO+KfioiI\niMi1wd//yOgd0f9vMIuIiIjIFeKv//qv/78NbfyO+JnCP/C669Jo6uYU4WGMz5tLl6fR1M0Y9o3P\nB+EfWXh1KCf6obi58XnIVwuvhny18Grh7Wrv1cLbfm+8KrwSERE5AYd7RzT2eZ5aP/Z8OFXxJBFC\nGgfStWk0dbPWFpfs6PCfGE7uoze6My9z7uEdziaNkMoYI0AaoVmKiIjs0UHfEZl//hnTe7Ze35SW\n07idvdxkI4f/xJO2y3c0cy3v9DLjHtkMetYsRURE9uiU3xF9KNCAV4VXqz8vIwBG8KrwqvCq8Krw\nqqgbg5L5QUfa4REZ0BifF94WXi28Hd7Hh9URGdCsVS9zw9lgBK+ojIzeYASvCq8KrwqvCq9WS+Nt\n3+Ra08y1vNPLLHoEGPSsWYqIiOzRQf99xPiKEFLDI2cTI4Lhci85oIS6GYjl+IoQUsPjvrKJMULK\nY/UmNzPZxIhguEw5pJLHmdyDneDtOr49t++ri0FpYoyvdeCRNUsREZE9Oot/ZiWNDEcmMoeUTRqB\nS84mjaHXN8VyfEUIqeFxLzm+IqQ8qb6Emzoz9Mbn0kRIuZZOeZzJPbzDecZG+2mZxzoDHyEA96xZ\nioiI7FHvHfHVr361p8rpvyP6sOCSc0glj5xNGkOv74n9CCyVPO4lx1eEUDcD9TI3vZwMjkzvNPU8\nzuQe3uE8Y6P9tMxjnQ2Pg541SxERkT1qviPaCyL4vOqg74icTRqBy12ySWOIPsJYrEVgqeRxl2xi\njDDIY81NlODV8P5pBC45h1TyOJN7eIczoDE+b74PdclNL5sYI0AaoVmKiIjsUf2O6K+HC2/JPt8R\nkVndozE+F14tvC28KrwqvCq8Krwa3t/4QeuoFpsRACOLkheQDUYTOfUGDUQTATCCV+v4duFVUTcG\nJXg1/FBvC68Kr0jdozE8ch7AGni18Ha196rwauFtZ9/nhbeFV4VXC2/7vfGq8EpEROQEpHdEfzEs\nr4aeqtfEjd8R/aNERERE5ArRfEf0oRpB74giIiIiV7n6HdHTQu+IInLKTut/tn5anysichY0/8zK\n2E7viJP/mTu5FrAPXhVeLbxdNJvgVV9aS6Opm1OEhzE+b8vvUnh1pTnww+PjmB8cin/qnv7W+3BA\n19rnioicEQd9R9zoP3Pnl3mzl814NNxw7sGOSTnUzWmJJ4kQ0jjGl3M+gwbPduAnx2fFh+LrgcWn\n76J5k91vO9b80AM4rc8VETk7DveOuMV/5k5ewmvjbDgbnNbGp4CdxM/OmN6z9fqmtJzGM+UMPtvp\nPtIJffoJ3VZERM6CA70j9n6WoAevVg2Omng/MgLgyKQxGZ8CdpL6CA14VXhFZWT0BiN4VXhVeFV4\nVXhV1I1ByfygY7CDI/CKysjcRzDojc+FV/37GIzg1WoJftDZN14VXlX38XZbzZugBK9WNzkbjODV\nkK8uUhkZvcEIXhVeFV5VNzd+0L9Pk++VzQiAEbwqvCq8KrwqvOrfv5cNRvCq8KrwSkTk6nWa74hc\ncma9viktxxhfESCNAb3xuS/W4itCSA2PnE2MCIbLveSAEupmoLfMPWcTI4JJPecIKZsYEQz6kMo0\nJumUx5m8nfoO3OySe3iHs4kRwXCZcqjLujFcch6INQTD5Y7ZxIhguNwxi4hcrewd8d5NnNQ7Ys/M\nDtSbaCBGHJk0JuNTSDtpNKlJI8ORicwhZZNG4JKzSWPo9U29Ze45G4wmMvcpR0jZYDSRUw8oTRqT\ndMrjTN5OfQduZrJJ4xgvczYYTWQOKYe6rBvo9T3YN5G5DGX3SBoBJfOD4f3LeSMz9CaNIiJXtwO9\nI5r6P17rJlm7EJqbKA2PyCaNyfgU0k4aTWrSyJpHXHIOqeSRs0lj6PVNaTnGCCmbNAbuI0dI2aQR\nuORs0pikUx5n8nbqO3Azk0OzrPEaZ5NG4JJzqMu6YeNT1txsliwtpJE1j7js5aa1CyIiV4fDvSOa\n9J+tPHKGuunhzV4249Fww7kn7aTRpIZHziaNwOUu2aQxRB9hjNciR0jZpDFwHzlCyiaNwCVnE2ME\nlkoeZzKgMT6vUy9zs0vu4R3OJo3AJedQl9FEGOSB5hqXu2STxoAevJq7J2dAY3wWEbnyHfQd0aT/\nGMUIXhV1M4Bl5gfVUbM06I3PhVd9aY1HZFb3aIzPhVcLbwuvCq8KrwqvCq+G9zd+0Dpq8u3Cq80/\n19uFtzvcJzJ6g9H4XHhF6h6N4ZEzoDE+D/lq4VXhVeFV4VUpI3Bv0Iz56up9EKBsHfO28KrwivjB\n6kcErwqv+nyv8GrhbeFV4VXhVeFV4dW6h2n2KMGrwqvCq4W3rY8QEblCHfodUcTop6mIiMgZp3dE\nOTS8IIJXIiIicsboHVFEREREMr0jioiIiEimd0QRERERyfSOKCIiIiKZ3hFFREREJNM7ooiIiIhk\nekcUERERkUzviCIiIiKS6R1RRERERDK9I4qIiIhIpndEEREREcnsHdFe+zaid0QRERGRq5zeEUVE\nREQkO9w74mcImk3xtZyvOHh44/PJ2+7jcJXxeUN+MfGDDfnF+/jL5TcqvFo1OBIREbmmHOgdkX/0\ncm7qnfKFnE/aSXzQIZ8ftvvEmasGC3w554HmzuS1Y3wTzgGl8VlEROQadoh3xPRztzkanzs/vw33\nnE/aSXzWSdxzrS0+dOaSwQ4fce7p7fT6rdU3RANeiYiIXMNO4R0xwanxuagbwyVngxG8KrwqvKIy\nMnqDEbwq6sakkkdkQANeLbgcZATDvYmM3mAEr1alI4zgVeHVwtu+3hr3azMCw4KJMQJgBK+q+3i7\nKh3FGEFEROQad/rviD31Vdzskk2MCIbLlKFuTJQREu43zYbHZkYwXKacxFGEyTzW3EQZvN3849Cb\nyBwmM6AxPq/bFxERuTad3XdEky7kkbNJI6BkfkBHkblHrjVPURqfC68WXM5nw2OdDfcIrOyu4J4z\n457zWvVy3QD3vcy4jxxhMjPukRMciYiIXLPO4r+PyPiol0Mq08gGR2ZwWh+hMT6v7uySDY+9HJol\nSwtpDNxznpH20xi472XGfeQIk5lt2ouIiFxrDvGOaB/DP3o5G4zG50qcRtgxmzQCl5wTPoocYY/Z\n8NjLgUvOMG5m8iS+hHOCI/CK9iMAj5Ej7JhZrxcREbnWHOgd0eCnL6CZF5fgckADXhVeFV4VXg2f\nxNvCqxacpjUekc2+eqhLNMHbwquibgA9eFV4VcoI8/hCQF+rT9EYn6umzoDG8MjZYASviB8UXomI\niFyrDveOKFu4ut9Xru7vTkRE5Iqmd8SzC69Q4NVVwb+lwisRERE5Y/SOKCIiIiKZ3hFFREREJNM7\nooiIiIhkekcUERERkUzviCIiIiKS6R1RRERERDK9I4qIiIhIpndEEREREcn0jigiIiIimd4RRURE\nRCTTO6KIiIiIZHpHFBEREZFM74giIiIikh3uHfEzBM2m/GLiBxvyi7e9nPmNCq9WpaM0TsJVxuc+\n3yu82sG+7iMiIiJXnAO9I/LbBuemwSlfy3mguTN57RjfhHNAaXwu6mbGzFW8w3lrG91k948TERGR\ns+MQ74jpVaM5Gp+HryZ8xLmnt9Prt1bfEA14tWiWYzOX8A7nAzjwx4mIiMhJO4V3xASnxueiboD7\ntRmBYcHEGAEwglfVfbxdlY5ijJCkHiN4VXi18LZjsIMj8IrKyOgNRvCKdhAM9wy98bnwSkRERK4E\np/+O2NO8CmXwdnW5lxl6E5nDZAY0xud1+yGOIkzmAawZnwtuOJsYEQx6GDe9HLjkLCIiImff2X1H\nNPWFdQPc9zLjPnKEycy4R05wFLjkzLjnvFZaTiPDkYmMHsZNL7NeLyIiImfcWfz3EVk6SmPgvpcZ\n95EjTGa2UZ/KNAbuOffwTi8ngyNTn3LTy7XxqYiIiJxBh3hHtI/htwTOBqPxucKnnBMcgVe0HwF4\njBxhx8zqftzM5B7emckmjUl9yk0zRxhkEREROfsO9I5o8JYAaObhElwL6Gv1KRrjc9XUGdAYHjkb\njOAV8YOCG2SGHrwqvCplhAHsgFeFV4VX/WXjLWn2KMGric8VERGRs+9w74gHoHeRk6a/wiIiIteI\nq+EdES8u4JWcAP9LXHglIiIiV6mr6r9HFBEREZG90DuiiIiIiGR6RxQRERGRTO+IIiIiIpLpHVFE\nREREMr0jioiIiEimd0QRERERyfSOKCIiIiKZ3hFFREREJNM7ooiIiIhkekcUERERkUzviCIiIiKS\n6R1RRERERLLDvSN+hqDZlF9M/GBDfvG2lzO/UeHVqsERwxp4tYN93ac2vvP4dAbuAF7JAflf+sIr\nERG5Vh3oHZF/6nBuGpzytZwHmjuT147xTTgHlCZGhCTtRN7aRjfZ9OMGNx8cbWQv99nLk2xhX597\n+Oc/+ou+fChnERG5Nh3iHTH9vGmOxufhzyc+4tzT2+n1W6tviAa4QWbccz6A7T5uu6vm7X7/3e+w\nnX197r7usxH+UM4iInJtOoV3xASnxueiboD7tRmBYcHEGAEwglfVfbxdlY5ijABphGYJOAKvqIyM\n3mAEr2gHwXDP0BufC69WpSOM4BXtIBj0xufCK5J6HjkbjODVagl+sG4/MvoBrIFX+/tcZIZ+U37x\nwts5W1wiIiJXmdN/R+xpXoUyeLu63MsMvYnMYTIDGuPz3L4PC5TG54IbziZGBIMexk0vBy45J/XR\nuIkcIeWQSh5nskkjcMnZxIhg0DfxAmeTxlpaiBHBpH4GlpPoeQd5rY2WRUTkanV23xFNfWHdAPe9\nzLiPHGEyM+6RExxB3UDq08hwZCKjh3HTy6zXh+ZCXXITOUJPWuCRs0ljaPYomR/QUWT0Pb2dcR+8\n7X8u563xTTiPzW+KiMjV7Sz++4gsHaUxcN/LjPvIESYz26WfycngyNSn3PRyrXc633MTOUJPWuCR\nc6jLujHNEgZHA/VVdWO45GzSGHr9RvgmnAcm10RE5FpwiHdE+xj+2cPZYDQ+V/iUc4Ij8Ir2IwCP\nkSPsmFmvN3EUYTKbNCb1KTfNHGGQoW5CfcRN5Agph1TyOJNNjBEG2aRxjJc5mxgjDLJJY4g+whb4\nWs49vMNZRESuTQd6RzT4qQNo5uESXAvoa/UpGuNz1dQZ0BgeORuM4BXxg8KrVdFjB9CAV4VXw9t6\nS5o9SvBq4nNN3QB61uxRGp8LrwqvSOojp96gCd727+/V8CY9vl14tfC287mRuTRla4UftI4m8eWc\ne7DD/EBERK5Jh3tHPAD9YOs5rb8yp/W5IiIisqOr4R0RLyLglRD/S1N4dRD+kYVXIiIicoW4qv57\nRBERERHZi4P+mRXAB4NXC28XdROa+zWsBW/7z9Pjq4VX1c3Bzzp8aeFtvx+rN3E5eDWhXsYdwKsh\nXy28KrwqvCq8Wng75KuFVxMl+EHhVeFVpzxF/iiFV4fin1p41cE7nNfaaPkA9vU8+7rPicJDGp9J\n3aMBr3bgN6pu1Sz3pXdz9ODVnvA9OV818E0Zn680J/TwuK3x+crk3wN9F4d4R+SP7GUzHhmOjM8d\naSfGCCn38E4vmzTW0kKMESCNPfUaN5zH6k1uOPfwzqbZpLGnt9bsuZzJUDdb2Msd4iacIY1bG9+n\nfOyaD+IdzmtttHwAGz3PYHOj+5yKeMIIrFmaXr+F5q2a5b40b84l56be6aCPI84nrfdBJ/EA5du6\nUr+v8uy7PnzzDid3Z7P7ndcqj3/0KRHMWXlHRDacDU6ZHxRedfAOsmn2yD2808smjTVeQDaDHnxu\nNcarghvOgMb4vO19aryzXTYYB3przZ7LyBEgjaZuNrX7HQzfhLNJ49bW3mftguEdzlexK/3b3O75\nt7uqafdb4Q7G53Xq5dQ0R+Nz6w4w03M+ab3P6vW7OIl79vQ+q9cfQO+je/1Gejfp9XvU/IhD//uI\n/BCREQBHJo0QZYSB2EEA7lOeMdgfHEEsIMCgB5+rO9clN5wBjfF50SzD+BR4hzPjPjIC4Gigt9bs\nuYwcoSct8MjZYASvVkvwg3X7kblHZuiZH2x1f4ae9XrGO5wNRvCq8KrwarU03hZelTLCAHbAq+pC\nHpEBDXhVeLVagh/M7XtbeFXKCD2xgGC4N5HRG4zgVVE3gB68InWPBrwa8tWFt537+FyaCAGN8Xmo\nudksA06Nz0XdQLPnkrPBCF4VXhVeURkZvcEIXhV1A80+lTwiAxrwasHlICMY7k1k9AYjeFXUDfT6\nHuyDV3QTBIPe8BgZgWHBxBgBMIJX1X28LeoGmn2UCIZ7Exm9wQheFXVjDvqOmJ4gxviKAGk03NQ5\n4Z00Rg7NEnAEdRMGRyF24msdeBxrrqEM3g4NNgdHCTaDt4tUxhhfEcZ6a80eZeASuSkt8DiTTRqB\nS84mRgTDvcEYmiVLCzEimNT3rF0wvDOTQyp5nMk9vMPZxBiBpZJHziaNSTrlcSYP8FozIxgumzmg\nZJM9j5x7eIdzqMtoEAz6vdjuhr2r6p6bXbKJEcFwmTLUDTT7KCMk3G+aDY/NjGC4TBnqBnr9QH0J\nN5tmht5E5jCZoW6g2XPZzAiGy2YOKM3h3hHTBxs0ECOOTBoNmsTPWnyjiBFHUDcDg+XBEcMaxDjo\nxwZrg6Nab7nXDzQvqUs0ECOOBnprzX6+ZGmBR84mjaHZo2R+QEeR0cNMA+iDt/37c25au2B4h7NJ\nY5JOeZzJPdhhflB41bpJ3dcN9HpIpzzO5AFeq7PhPkLKJo1J7zT1GJkfdPAO51CXaExk9Hux9Q17\nF6aeR84mjYCS+QEdReYeudY7bfYojc+FVwsu57Phsc6GewRWdo81S9Pre+p9bjbNjPvIESZzaJam\n7rmps+E+QsomjXCgd8TmZ6M0PCKbNCbjU8CO4REZ6qZnsDk4SrBpeBz0gNH4vGiW0DxCaXxezJdr\n1VfVjUFpeEQe6K01+5kyjSY1PHIOdVk3pllC84hLziaNwCVnk8bQ68PaBcM7nEOzNKnncSb3jHdw\nanwm833dsHTK40we4LVeDlxyNmlMeqepT+NavM851GXdBBwZnzeXLm+OxudVvSPuezmkMo1scGQG\np72jukdjfF7d2SUbHns5NMukt9Prm+plbjbNjPvIESYzm+x57OXAJWeTRjgrf2bFjMdkfAppZzwO\n8CZnqJuetBljBGiOxudFszTj3vi8qEtuOAMa4/OiLrnpZZPGnt5as2+WhnvOkBoeZ7KJMcIgmzQC\nl5xNjBEG2aQxRB8h6fWMd2ZySCWPnA3G4G0H73A2MUZgqeSRs4kxAkslj5wNxuBtH6/1cuCSs0lj\n0jtNPY+ce3iHc6jLugk4Mj5vhe/A2WA0Pld6p9FH2DGbNAKXnJPeEfeRI+wxGx57OXDJOekdpR6j\n8ZnUPTecDUbwinYiAI+RI0zmpHfE/UwOXHI2aYTDvSMy//DlmUKzNOiDt4VXHb608JZ6n4d8lfhB\nUTcDWA7e9vsmXyJ+QEc+D/kqGffg1SZl8IPqyNshXy282qQMflB4VT2PSX3k1Bs0wdvV3qvCq7mb\nGK8W3nbuH5lLU7ZW+EH//sarPt8rvCq8KrwqvCJR8gIy6/UJ1sCr6loekVndownervZekSh5AZn1\n+gRroS7RBG8Lr6b3Yb5HM+arZTkC9yGVWDsJ/gGFV9N6l0Rf7urQgFeFV4VXhVf9ZeNt4VVL7xR9\nuTp/HGezrx7qEk3wtvCqpXfKfbnHEZ8Lr0izR8nqHo3xuWrqDGgMj5xrvaPoy9XH6hJN8Lbwqr9/\n0D+zIiIyif/TivOVjr8XzgOTayJnypXy+3b8nOPTs+BEn1DviCJyRuE/+8Crq4J/S4VXfb5XeCVy\n5vlv2cKrM8kfsfCq8Krw6kzyRyy82iu9I4qIiIhIdqB3RH/LLfDB4NXC20XdhOZ+DWvB2/7z9Phq\n4VV1c/CzDl9aeNvvx+pNXA5eTaiXcQfwashXC68KrwqvCq8Kr9bx7cKriRL8oPCq8Gqr5zlR/iiF\nV4fin1p41eFLxA8O5VQ+1PQ+F73xechX+/fxoUADXu2b373wakN8LecrDh7e+HxWndBD4rbG57PN\nn7X1tHWPBrzagd9o7la+utvn+i0Kr05Auv/h/swKPq+XzXhkODI+d6SdGCOk3MM7vWzSWEsLMUaA\nNPbUa9xwHqs3ueHcwzu75LHeZrPnciZD3WxhL3eIm3CGNG5tfJ/yses/iNc4H8bhPxEGnzs4YrEW\ngTVL0+t3x3fm3NQ75Qs5n7ST+KAr4vn38pDNO5zcnfcrnjMCa5am129h8laxFmEXzZvMNJPK7Y+v\nPSvviMiGs8Ep84PCqw7eQTbNHrmHd3rZpLHGC8hm0IPPrcZ4VXDDGdAYn7e9T413dsljvc1mz2Xk\nCJBGUzeb2v0Ohm/C2aRxa2vvs3YBeI3zNWvyL8LkWrLRVVg2PvelteZofO4/BvecT9pJfNZJ3LPn\nkJ+V9D66129kLzcZ2+4jtruqafJWk2uT6rvVjWmWWzj0v4/Izx0ZAXBk0ghRRhiIHQTgPuUZg/3B\nEcQCAgx68Lm6c11ywxnQGJ8XzTKMT4F3ODPue3mst9nsuYwcoSct8MjZYASvVkvwg3X7kblHZuiZ\nH2x1f4ae9fqE1+oMaIzPxA+G+3U2GMGr6v7eFl6VMkJPLCAY9MbnwqvCq4W3fb019OAVqXs04NXC\n2x0eBnBqfC7qxnDJ2WAErwqvCq+ojIzeYASviroxqeQRGdCAVwsuBxnBcG8iozcYwauibsawD17R\nTRAMesNjZASGBRNjBMAIXlX38baoGxMlguHeREZvMIJXRd0AevCK1D0a8GrIVxfe9u9TN4AevFpd\n5hxSiZH5wbrLEQx643Ph1YHfEdNnxxhfESCNhps6J7yTxsihWQKOoG7C4CjETnytA49jzTWUwduh\nwebgKMFm8HZRl2jAq3V6y80eZeASuSkt8DiTTRqBS84mRgTDvcEYmiVLCzEimNT3rF0ArAUuBzm+\npjCZQyp5nMkDvMYZUsMj5x7sBG/JZM8j501td219FTe7ZBMjguEyZagbE2WEhPtNs+GxmREMlylD\n3axVX8LNppmhN5E5TGaoG8NlMyMYLps5oGSTPY+ce3hnPgeUCR/1cqjLugnNIy45Q2oO946YPtig\ngRhxZNJo0CR+1uIbRYw4groZGCwPjhjWIMZBPzZYGxzVesu9fqB5SV1yw3mst9ns50uWFnjkbNIY\nmj1K5gd0FBk9zDSAPnjbvz/nprULEGsRIjPuEdKIMJlDKnmcyQO8xhlSwyPngfFa7zT1GJkfbGjr\na9OFPHI2aQSUzA/oKDL3yLXmKUrjc+HVgsv5bHiss+EegZXdY81yoN7nZtPMuI8cYTKHuuSmzob7\nCCmbNCa909RjZH7QwTt1ZuhNGgP6wGWdQ13WDatPueEMqTnQO2L6VEBpeEQ2aUzGp4AdwyMy1E3P\nYHNwlGDT8DjoAaPxedEsoXmE0vi8mC/Xqq+qG8Ml57HeZrOfKdNoUsMj51CXdWOaJTSPuORs0ghc\ncjZpDL0+rF0AXoscIYk+vqYwmUMqeZzJA7zGGVLDI+eB8VrvNPVpTHBqfO5La83R+LyKj3o5pDKN\nbHBkBqf1ERrj8+rOLtnw2MuhWSYzO6Fe5mbTzLiPHGEys9Tz2MuBS84mjUnvNPVpXIv3ezlpHnE5\nk0Nd1k2SFnjkDKk5K39mxYzHZHwKaWc8DvAmZ6ibnrQZYwRojsbnRbM04974vKhLbjgDGuPzoi65\nmcljvc1m3ywN95whNTzOZBNjhEE2aQQuOZsYIwyySWOIPkLS6xNeixyhl+NrCikbjOAVST2PnA3G\n4G0fr3GG1PDIeWC81jtNPY+cAY3xeYg3ORuMxudKnEbYMZs0ApecEz6KHGGP2fDYy4FLzkk6wmh8\nJnXPDWeDEbyinQjAY+QIkznho5kcuORs0pj0TlPPI+ce3pnJJo3A5UwOdRlNhBof9TKk5nDviMw/\nfHma0CwN+uBt4VWHLy28pd7nIV8lflDUzQCWg7f9vsmXiB/Qkc9DvkrGPXi1SRn8YPXIq3V8u/Bq\nkzL4QeHVagmpj5x6gyZ4u9p7VXg1dxPj1cLbzv0jc2nK1go/6N/feNXhS8tanQGNGYzIgCYMShYl\nLyCzXp9gLXjb+VzuI6NvwgJ4tfCWzPdotuZ3KbyaFpfgckADXhVeFV4VXg2fxNvCqxacpjUekc2+\neqhLNMHbwqsWPsWy8bnwijR7lKzu0Rifq6bOgMbwyLmW1kJdogneFl5N78N8j2bMV8tyBO4NGuNz\n4dXC2/59en1Ab3ymphan2Awojc/EyoP+mRURkQOL/7BLeWByTa5xZ+33yfh5xqeHdHaeZAAPWfPj\nK9lG34jeEUXkKof/TASv+nyv8Eqk4r9FCq9OlT9K4VXhVeHVqfJHKbySA/K/9IVXQ3pHFBEREZHs\nQO+I/tZa4IPBq4W3i7oJzf0a1oK3/efp8dXCq+rm4GcdvrTwtt+P1Zu4HLyaUC/jDuDVkK8WXhVe\nFV4VXhVerePbhVcTJfhB4VXh1VbPc6L8UQqvDsU/tfCqz/cKrw7opD939/vjDuDVFWjy+bFmfJ7j\n1xReLbw9q3/p/OFW+VmHLxVebcvvUnglcgIO92dW8Hm9bMYjw5HxuSPtxBgh5R7e6WWTxlpaiDEC\npLGnXuOG81i9yQ3nHt7ZJY/1Nps9lzMZ6mYLe7lD3IQzpHFr4/uUj13zQbzDGdK4tcF9ymfu51Oa\n9nX/vdxnL0+ynebzN5+nuTk2vmR8eur48TiPzW+utcdbiTSdlXdEZMPZ4JT5QeFVB+8gm2aP3MM7\nvWzSWOMFZDPowedWY7wquOEMaIzP296nxju75LHeZrPnMnIESKOpm03tfgfDN+Fs0ri1tfdZu2B4\nh7NJ49b2dZ/Ttft3sfsd9qv3PL1+YHzJ+PTUpcebfNR01S72eCuRpkP/+4j8ezoyAuDIpBGijDAQ\nOwjAfcozBvuDI4gFBBj04HN157rkhjOgMT4vmmUYnwLvcGbc9/JYb7PZcxk5Qk9a4JGzwQherZbg\nB+v2I3OPzNAzP9jq/gw96/Wst4Oe+cHqkVdURk59QG98Lryq9r0tvKo+ogc74BVdiGDQG58Lr0jq\neeRsMIJXqyX4wbr9yOh70g6PyIDG+Ez8gK6NsFZzE2XwdrX3qvCq8Kq6ifGDuX1vC68I95ERDHrw\nitQ9GvCq8KrwatXgSGQvDvqOmH5DxxhfESCNhps6J7yTxsihWQKOoG7C4CjETnytA49jzTWUwduh\nwebgKMFm8HZRl2iCt0O9zWaPMnCJ3JQWeJzJJo3AJWcTI4Lh3mAMzZKlhRgRTOp71i4A1ozPi2bJ\n0kKMCCb1TemUx5m8Vr3MTeQIKYdU8jiTTRqBS84mRgSDviktpNHMNIDeREY/UK9xw5lxvzbH1xQm\n8wDWgrek2aeSx5nMer3IvhzuHbH+3YwGYsSRSaNBk/hZi28UMeII6mZgsDw4YliDGAf92GBtcFTr\nLff6geYlzRIGR7XecrOfL1la4JGzSWNo9iiZH9BRZPQw0wD64G3//pyb1i6werluAH3wlvrI3CPX\n0imPM3mtepmbyBF60gKPnE0aQ7NHyfyAjiKjb4qF+IoQZhrgnvNAvcYNZ4MxNEvDPUIaESazwci4\nj4xg0AdvF3VZNwYl8wPS60X25UDviM3fyigNj8gmjcn4FLBjeESGuukZbA6OEmwaHgc9YDQ+L5ol\nNI9QGp8X8+Va9VV1w8anSW+52c+UaTSp4ZFzqMu6Mc0SmkdccjZpBC45mzSGXh/WLhje4WzSCFxy\nNmkMvR7SKY8zea16mZvIEXrSAo+cQ13WjWmWMDiqxXJ8RQgzDXDPeaBe42bTzKKPrylM5oHmGpec\nQ7M0qU9j08yOyC7Oyp9ZMeMxGZ9C2hmPA7zJGeqmJ23GGAGao/F50SzNuDc+L+qSG86Axvi8qEtu\nOEPdjPX2m32zNNxzhtTwOJNNjBEG2aQRuORsYowwyCaNIfoISa9nvMPZxBhhkE0aQ/QRWCp55Gww\nBm/XqZe5iRwh5ZBKHmeyiTHCIJs0rhX7EVhdRhMBeOQ8UK9xs5ccX1OYzAPNNS45h1TyOJNZrxfZ\nl8O9IzL/8OW3eGiWBn3wtvCqw5cW3lLv85CvEj8o6mYAy8Hbft/kS8QP6MjnIV8l4x682qQMftDZ\nH/MLCq82KYMfFF6tlpD6yKk3aIK3q71XhVdzNzFeLbzt3D8yl6ZsrfCD/v2NV32+V3i18Ha196qU\nKUDZWuEHnfuEKHkBmfX6BGus2aM0PhdeFV6R1EdOvUETvO3f36vhTXpiOQJgZH5ARz5XDecBrIFX\nq7eKMNMbNGYwIgMawyPnHuyAVwtvSx+B+1D3aMCrwquFt4VXIifgoH9mRUTkwPjnKOct7Hi5iMiV\nRe+IInKVw7sdeLU5v77wSkTkqqZ3RBERERHJDvSO6P+/7wIfDF4tvF3UTWju17AWvO0/T4+vFl5V\nNwc/6/Clhbf9fqzexOXg1YR6GXcAr4Z8tfCq8KrwqvCq8God3y68mijBDwqvCq+2ep4T5Y9SeHUo\n/qmFV0O+Wnh1KKfyoab3ueiNz0O+2r+PDwUa8Grf/O6FVxviazlfcfDwxuez6oQeErc1Pp9t/qyt\np617NODVDvxGc7fy1d0+129ReHUC0v0P92dW8Hm9bMYjw5HxuSPtxBgh5R7e6WWTxlpaiDECpLGn\nXuOG81i9yQ3nHt7ZJY/1Nps9lzMZ6mYLe7lD3IQzpHFr4/uUj13/QbzGeY8G9zyhT1xr8LmDIxZr\nEVizNL1+d3xnzk29U76Q80k7iQ+6Ip5/Lw/ZvMPJ3Xm/4jkjsGZpev0WJm8VaxF20bzJTDOp3P74\n2rPyjohsOBucMj8ovOrgHWTT7JF7eKeXTRprvIBsBj343GqMVwU3nAGN8Xnb+9R4hzPjvpfHepvN\nnsvIESCNpm42tfsdDN+Es0nj1tbeZ+2CSTtp3IuTuOeJmnzgybVko6uwbHzuS2vN0fjcfwzuOZ+0\nk/isk7hnzyE/K+l9dK/fyF5uMrbdR2x3VdPkrSbXJtV3qxvTLLdw6H8fkZ87MgLgyKQRoowwEDsI\nwH3KMwb7gyOIBQQY9OBzdee65IYzoDE+L5plGJ8C73Bm3PfyWG+z2XMZOUJPWuCRs8EIXq2W4Afr\n9iNzj8zQMz/Y6v4MPev1rLeD3kRGbzCCV4VXhVerJfjB3L63hVeljNATCwgGvfG58KrwauFtX28N\nPXhF6h4NeLXwdoeHAZwan4u6MVxyNhjBq8KrwisqI6M3GMGrom5MKnlEBjTg1YLLQUYw3JvI6A1G\n8KqomzHsg1d0EwSD3vAYGYFhwcQYATCCV9V9vC3qxkSJYLg3kdEbjOBVUTeAHrwidY8GvBry1YW3\n/fvUDaAHr1aXOYdUYmR+sO5yBIPe+Fx4deB3xPTZMcZXBEij4abOCe+kMXJoloAjqJswOAqxE1/r\nwONYcw1l8HZosDk4SrAZvC286pTGq3V6y80eZeASuSkt8DiTTRqBS84mRgTDvcEYmiVLCzEimNT3\nrF0wg504QjBcpsxSn8YknfI4kwd4jTOkhkfOPdgJ3pLJnkfOm9ru2voqbnbJJkYEw2XKUDcmyggJ\n95tmw2MzIxguU4a6Wau+hJtNM0NvInOYzFA3hstmRjBcNnNAySZ7Hjn38M58DigTPurlUJd1E5pH\nXHKG1BzuHTF9sEEDMeLIpNGgSfysxTeKGHEEdTMwWB4cMaxBjIN+bLA2OKr1lnv9wOASPurlsd5m\ns58vWVrgkbNJY2j2KJkf0FFk9DDTAPrgbf/+nJvWLpjBDo5MZC5D2T3i88Lb4UeYdMrjTB7gNc6Q\nGh45D4zXeqepx8j8YENbX5su5JGzSSOgZH5AR5G5R641T1EanwuvFlzOZ8NjnQ33CKzsHmuWA/U+\nN5tmxn3kCJM51CU3dTbcR0jZpDHpnaYeI/ODDt6pM0Nv0hjQBy7rHOqyblh9yg1nSM2B3hHTpwJK\nwyOySWMyPgXsGB6RoW56BpuDowSbhsdBDxiNz4tmCc0jlMbnxXy51uAqPurlsd5ms58p02hSwyPn\nUJd1Y5olNI+45GzSCFxyNmkMvT6sXTBph0fOoVka7jmbNCbplMeZPMBrnCE1PHIeGK/1TlOfxgSn\nxue+tNYcjc+r+KiXQyrTyAZHZnBaH6ExPq/u7JINj70cmmUysxPqZW42zYz7yBEmM0s9j70cuORs\n0pj0TlOfxrV4v5eT5hGXMznUZd0kaYFHzpCas/JnVsx4TMankHbG4wBvcoa66UmbMUaA5mh8XjRL\nM+6Nz4u65IYzoDE+L+qSm5k81tts9s3ScM8ZUsPjTDYxRhhkk0bgkrOJMcIgmzSG6CMkvT7htV4O\nXM5kE2MElkoeORuMwds+XuMMqeGR88B4rXeaeh45Axrj8xBvcjYYjc+VOI2wYzZpBC45J3wUOcIe\ns+GxlwOXnJN0hNH4TOqeG84GI3hFOxGAx8gRJnPCRzM5cMnZpDHpnaaeR849vDOTTRqBy5kc6jKa\nCDU+6mVIzeHeEZl/+PI0oVka9MHbwqsOX1p4S73PQ75K/KComwEsB2/7fZMvET+gI5+HfJWMe/Bq\nojTeFl4VXhVerePbhVeblMEPCq9WS0h95NQbNMHb1d6rwqu5mxivFt527h+ZS1O2VvhB//7GqyFf\nLXpN8LbwqvCqlBEAo/G58IpEyQvIrNcnWAvedj6X+8jom7AAXi28JfM9mq35XQqvpsUluBzQgFeF\nV4VXhVfDJ/G28KoFp2mNR2Szrx7qEk3wtvCqhU+xbHwuvCLNHiWrezTG56qpM6AxPHKupbVQl2iC\nt4VX0/sw36MZ89WyHIF7g8b4XHi18LZ/n14f0BufqanFKTYDSuMzsfKgf2ZFROTA4j/sUh6YXJNr\n3Fn7fTJ+nvHpIZ2dJxnAQ9b8+Eq20Teid0QRucrhPxPBqz7fK7wSqfhvkcKrU+WPUnhVeFV4dar8\nUQqv5ID8L33h1ZDeEUVEREQkO9A7or+1Fvhg8Grh7aJuQnO/hrXgbf95eny18Kq6OfhZhy8tvO33\nY/UmLgevJtTLuAN4NeSrhVermke95SbcAbyaKMEPCq8KrzrlKfJHKbw6FP/Uwqs+3yu8OqAT/Vzc\nHLw6Pf4chVeFV4VXBzT5uVgzPq/j24VX++Z3L7xaeFt4dXr8OQqvCq8Krzbn1xdeHYR/5Bn4yysz\nDvdnVvB5vWzGI8OR8bkj7cQYIeUe3ullk8ZaWogxAqSxp17jhvNYvckN5x7e4RxQGp+Luhnr7Td7\nLmcy1M0W9nKHuAlnSOPWxvcpH7vmg3iHM6Rxa4P7lM/cz6ckfGfOW9jl2qT3JL3+pDU/t/kkzc2B\nTfe3U38KN5y3sMu1Se9Jev283e+wndP6XNnCWXlHRDacDU6ZHxRedfAOsmn2yD2808smjTVeQDaD\nHnxuNcarghvOgMb4vO19arzDGdBAr5nR22/2XEaOAGk0dbOp3e9g+CacTRq3tvY+axcM73A2adza\nvu6zkfShadzILtfWenfr9YfXe5Je37Pp/nbSp4zHjexyba13t14/b/c7bOe0Ple2cOh/H5F/c0RG\nAByZNEKUEQZiBwG4T3nGYH9wBLGAAIMefK7uXJfccAY0xudFswzjU+AdzibGCKFuxnr7zZ7LyBF6\n0gKPnA1G8Gq1BD9Ytx+Ze2SGnvnBVvdn6FmvZ70d9MwPVo+8ojJy6gN643PhVbXvbeFV9RFNgwUc\ngVf9z/WZpD4yeoMRvCLzPRrwisrI6HvSDo/IgMb4TPyAro0w1ltDD14VXhVeFV4VXpHUp5HhCLxa\nLY23VW9SHxm9wQhekfkeDXhFZWT0Jo0BPXi1ulzngBK8KrwqookAGMErKiOjl8M46Dti+hscY3xF\ngDQabuqc8E4aI4dmCTiCugmDoxA78bUOPI4111AGb4cGm4OjBJuByzpD3Yz19ps9ysAlclNa4HEm\nmzQCl5xNjAiGe4MxNEuWFmJEMKnvWbsAWDM+L5olSwsxIpjUN6VTHmdyT2+H+5ls0hiiRzBcphya\npUk9j5xNjAgGfVNaSKOZaQC9iYy+p7nD5UxmzT6VaQzcz2STxhA9guEy5dAsTep55GxiRDCp7+GF\nfWWD0UTmkLKJEcGglwM43Dti/bcWDcSII5NGgybxsxbfKGLEEdTNwGB5cMSwBjEO+rHB2uCo1lvu\n9QPpEoyJn21+/95+s58vWVrgkbNJY2j2KJkf0FFk9DDTAPrgbf/+nJvWLrB6uW4AffCW+sjcI9fS\nKY8zuae3w/1MNmkM6E1kDimHZmlSj5H5AR1FRt8UC/EVIcw0wD3nnrSDzGUvJzgK3i5SmcbA/Uw2\naQzoTWQOKYdmaVKPkfkBHUXmHtmkPnC5XU74KDICw4LxmdbQywEc6B2x+fcVpeER2aQxGZ8CdgyP\nyFA3PYPNwVGCTcPjoAeMxudFs4TmEUrj82K+XGtwVX1UN2O9/WY/U6bRpIZHzqEu68Y0S2geccnZ\npBG45GzSGHp9WLtgeIezSSNwydmkMfR6SKc8zuSetBNjhMls0hiaPZecQ7M0qU8jGxzVYjm+IoSZ\nBrjn3MM7kSMMMuOec0hlb4wwmU0aQ7PnknNolib1aWS9I+4jR9hjTvgocoTa4EhO2ln5MytmPCbj\nU0g743GANzlD3fSkzRgjQHM0Pi+apRn3xudFXXLDGdAYnxfNMtSndTPW22/2zdJwzxlSw+NMNjFG\nGGSTRuCSs4kxwiCbNIboIyS9nvEOZxNjhEE2aQzRR2Cp5JGzwRi87eO1yBEms4kxAqQRuOQcmqVJ\nPY+cTRrXiv0IrC6jiQA8cu7hncgRdsyhLrmJHGEymxgjQBqBS86hWZrU88jZpDFwHznCCWXDY+QI\nKZs0yiEd7h2R+Ycvf+9DszTog7eFVx2+tPCWep+HfJX4QVE3A1gO3vb7Jl8ifkBHPg/5Khn34NVE\nGfysSCNgbcxXC682KYMfFF71H8mHMkbg3qAJ3q72XhVezd3EeLXwtnP/yFyasrXCD/r3N171+V7h\n1cLb1d6rUqYAZWuFH3TuE6LkBWTW62vYBK+GZZ0BjfG5cxPwtvCq8IrM92iMz4VX68RyBMDI/ICO\nfK4azk1YSOojNOBV4VXhVSkjcB/QG58Lr4ZlnQGN8blzE/C28Krwisz3aIzPhVeFV6vSUeRBHyFl\ngxG8Wr1JjJwBjfG58EoO6KB/ZkVE5MD4pwtnEREZ0zuiiFzl8GoIXomIyDp6RxQRERGR7EDviP7/\nhS/wweDVwttF3YTmfg1rwdv+8/T4auFVdXPwsw5fWnjb78fqTVwOXk2ol3EH8GrIVwuvCq8Kr1bL\n4Gd9vld4NX1/Pyi8KrzqlKfIH6Xw6lD8Uwuv+nyv8OqATu5zcWfwqs/3VvnZVna/w35NPg/WjM/r\n+Hbh1b753QuvVsvgZ1vZ1x3Aq8359YVXB+EfedgPlYM53J9Zwef1shmPDEfG5460E2OElHt4p5dN\nGmtpIcYIkMaeeo0bzmP1Jjece3hn02zS2NNba/ZczmSomy3s5Q5xE86Qxq2N71M+ds0H8Q5nSOPW\nBvcpn7mfT2F8W84DaW3mkoF0t1PXfJ7mEzY3Bzbd3079KalJp5tKd9sUX855rLk2f/l+ndbnygGc\nlXdEZMPZ4JT5QeFVB+8gm2aP3MM7vWzSWOMFZDPowedWY7wquOEMaIzP/fuw8SnwDmfGPWeTxp7e\nWrPnMnIESKOpm03tfgfDN+Fs0ri1tfdZu2B4h7NJ49b2dZ+N8IdyHphcu2r0vt9e37Pp/nbqT6mb\nU8QPw3mgt9brT9ppfa4cwKH/fUT+zRQZAXBk0ghRRhiIHQTgPuUZg/3BEcQCAgx68Lm6c11ywxnQ\nGJ8XzTKMT4F3OLNebwZHrLfW7LmMHKEnLfDI2WAEr1ZL8IN1+5G5R2bomR9sdX+GnvV61ttBz/xg\n9cgrKiOnPqA3PhdeVfveFl5VH7HW5DKvcWAowatSRuDeoDE+L7wtvCq8KrwqvOpLazwiAxrjM/ED\nujbCWG8NPXhVeFV4VXhVeEXqnhsODCV4VcoI3Bs0xueFt4VXhVerN0xwBF5V9zd+0L8VevBqdbnO\nASV4VXhVRBMBMIJXVEZGL2fTQd8R02+IGOMrAqTRcFPnhHfSGDk0S8AR1E0YHIXYia914HGsuYYy\neDvU20RvfB7y1YW3hVed+wyOkt5ms0cZuERuSgs8zmSTRuCSs4kRwXBvMIZmydJCjAgm9T1rFwBr\nxudFs2RpIUYEk/qmdMrjTF5rfhmbgcv5HFLJ4y65J+2k0cw0gN5ERt/T3OFyJrNmX5doApfzOaSS\nx5lsMBqfC244mzSGXh94YV/ZYDSROaRsYkQw6OUMOtw7Yv1bAQ3EiCOTRoMm8bMW3yhixBHUzcBg\neXDEsAYxDvqxwdrgqDZeHp8mg+XmUbPs6S03+/mSpQUeOZs0hmaPkvkBHUVGDzMNoA/e9u/PuWnt\nAquX6wbQB2+pj8w9ci2d8jiTx+Y3DS9z2CiHVPJYZ4bepHEsluMrQphpgHvOPWkHmcteTnAUvF3U\nJTccNsohlTzWmaEPqcTI/KDaDKmPjD5wuV1O+CgyAsOC8ZnW0MsZdKB3xObvA5SGR2STxmR8Ctgx\nPCJD3fQMNgdHCTYNj4MeMBqfF80Smkcojc+LZhnGp8lguT6qm7HefrOfKdNoUsMj51CXdWOaJTSP\nuORs0ghccjZpDL0+rF0wvMPZpBG45GzSGHo9pFMeZ/LA5Fpo7nM5k0MqeezlprULEGvxFSHMNMA9\n5x7eiRxhkBn3nENd1o3hciaHVPLYy4z7Xk56R9xHjrDHnPBR5Ai1wZGcNWflz6yY8ZiMTyHtjMcB\n3uQMddOTNmOMAM3R+LxolmbcG58XdckNZ0BjfF7UJTecoW7GevvNvlka7jlDanicySbGCINs0ghc\ncjYxRhhkk8YQfYSk1zPe4WxijDDIJo0h+ggslTxyNhiDtx28w3kgrSFzydlgDN4uUsnjLnkg1iKw\nuowmAvDIuYd3IkfYMYe6TA0yl5wNxuDtIpU8nkQ2MUYAHiNHOKFseIwcIWWTRjnLDveOyPzDl98r\noVka9MHbwqsOX1p4S73PQ75K/KComwEsB2/7fZMvET+gI5+HfJX4weqRVwtvV3uv+svGq0WzHMA+\neLVJGfyg8Gq1hNRHTr1BE7xd7b0qvJq7ifFq4W3n/pG5NGVrhR/072+86vO9wquFt6u9V6VMAcrW\nCj/o3CdEyQvIrNcz7DA/6PClVdHzDjJLPUYWJS8gG4zgVeFV4dVQbEYAjMwP6MjnquHchIWkPkID\nXhVeFV6VMgL3oVma6Mt1K5mlHiOLkheQDUbwqvCq8KrwqvBq4W3n5iEdRR70EVI2GMGr1ZvEyBnQ\nGJ8Lr+QMO+ifWREROTD+acT5tPAzcJazif8ecRa5FugdUUSucvjRDl6dKn+Uwis5w/xvVeGVyLVB\n74giIiIikukdUUREREQyvSOKiIiISKZ3RBERERHJ9I4oIiIiIpneEUVEREQk0zuiiIiIiGR6RxQR\nERGRTO+IIiIiIpId4h3R//fTV/wRREREROSM0TuiiIiIiGR6RxQRERGRTP8+ooiIiIhkekcUERER\nkUzviCIiIiKS6R1RRERERDK9I4qIiIhIpndEEREREcn0jigiIiIimd4RRURERCTTO6KIiIiIZHpH\nFBEREZFM74giIiIikukdUUREREQyvSOKiIiISKZ3RBERERHJ9I4oIiIiIpneEUVEREQk0zuiiIiI\niGR6RxQRERGRTO+IIiIiIpLpHVFEREREMr0jioiIiEimd0QRERERyfSOKCIiIiKZ3hFFREREJNM7\nooiIiIhkekcUERERkUzviCIiIiKS6R1RRERERDK9I4qIiIhIpndEEREREcn0jigiIiIimd4RRURE\nRCTTO6KIiIiIZHpHFBEREZFM74giIiIikukdUUREREQyvSOKiIiISKZ3RBERERHJ9I4oIiIiIpne\nEUVEREQk0zuiiIiIiGR6RxQRERGRTO+IIiIiIpLpHVFEREREMr0jioiIiEimd0QRERERyfSOKCIi\nIiKZ3hFFREREJNM7ooiIiIhkekcUERERkUzviCIiIiKS6R1RRERERDK9I4qIiIhIpndEEREREcn0\njigiIiIimd4RRURERCTTO6KIiIiIZHpHFBEREZFM74giIiIikukdUUREREQyvSOKiIiISKZ3RBER\nERHJ9I4oIiIiIpneEUVEREQk0zuiiIiIiGR6RxQRERGRTO+IIiIiIpLpHVFEREREMr0jioiIiEim\nd0QRERERyfSOKCIiIiKZ3hFFREREJNM7ooiIiIhkekcUERERkSy9I7761a/2tKgbvSOKiIiIXOXq\nd0R+KUwj6B1RRERE5CrXfEc0KTO9I4qIiIhc5ep/H9FfDBfeEr0jioiIiFzlmn9mxV8PWy+IRu+I\nIiIiIle55jui6b0gGr0jioiIiFzleu+IA3pHFBEREbnK6R1RRERERDK9I4qIiIhIpndEEREREckO\n8Y5onyEiIiIiVxB/79vExu+Ift0m7hWRbfk/RSIiIoeld0SRM83/KRIRETksvSOKnGn+T5GIiMhh\n6R1R5Ezzf4pEREQOa2/viPi/92d8Jv6zTkQ25/8UiYiI9PlLWP//sN4W9vOO6M/VeTL/WScim/N/\nikRERIb8VWx/r4l7eEf0J+o/k/+sE5HN+T9FIiIi6/gL2baviZ8ufNj9HdGfZfg0/rNORDbn/xSJ\niIhM8NeyrV4T9/yOaPxZ9N8jipwA/6dIRERkHX8h2/a/R0z28I5o/Ik6z+Q/60Rkc/5PkYiIyJC/\nim3+ghj/9WEE2M87ovHnaj2Z/6wTkc35P0UiIiJ9/hK21X+DGK+GEWBv74gD/rNORDbn/xSJiIgc\nlt4RRc40/6dIRERkr+K/NawD6B1R5Ezzf4pERET2Kt4I6wB6RxQ50/yfIhERkcM6xDuiiIiIiFxZ\nNn5HFBEREZFrgb//hb/6q/8f1eUw/nrvGFgAAAAASUVORK5CYII=\n", "text/plain": [""]}, "execution_count": 8, "metadata": {}, "output_type": "execute_result"}], "source": ["from pyquickhelper.helpgen import NbImage\n", "NbImage(\"cmdipc.png\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On utilise une astuce pour le lancer depuis un noteboook :"]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["start ipcluster\n"]}], "source": ["if find_process(\"ipcluster\")[0] is None:\n", " print(\"start ipcluster\")\n", " from pyquickhelper.loghelper import run_cmd\n", " if sys.platform.startswith(\"win\"):\n", " cmd = os.path.join(f, \"ipcluster\")\n", " else:\n", " cmd = \"ipcluster\"\n", " cmd += \" start -n 2\"\n", " run_cmd(cmd, wait=False)\n", "else:\n", " print(\"d\u00e9j\u00e0 d\u00e9marr\u00e9\", find_process(\"ipcluster\"))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On attend que le processus d\u00e9marre."]}, {"cell_type": "code", "execution_count": 9, "metadata": {"collapsed": true}, "outputs": [], "source": ["import time\n", "time.sleep(5)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On v\u00e9rifie que les deux clients sont accessibles depuis de notebook :"]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[0, 1]\n"]}], "source": ["from ipyparallel import Client\n", "clients = Client()\n", "clients.block = True # use synchronous computations\n", "print(clients.ids) # on s'attend \u00e0 deux clients"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Tout va bien. L'ensemble fonctionne comme le dessin ci-dessous :"]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": [""]}, "execution_count": 12, "metadata": {"image/png": {"width": 300}}, "output_type": "execute_result"}], "source": ["from IPython.core.display import Image\n", "Image(\"http://ipython.org/ipython-doc/stable/_images/wideView.png\", width=300)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Exemple simple"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On teste l'exemple du [tutoriel](https://ipyparallel.readthedocs.io/en/latest/intro.html#getting-started) :"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"data": {"text/plain": ["['Hello, World', 'Hello, World']"]}, "execution_count": 13, "metadata": {}, "output_type": "execute_result"}], "source": ["clients[:].apply_sync(lambda : \"Hello, World\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Et un autre pour parall\u00e9liser l'ex\u00e9cution d'une fonction : [map_sync](https://ipyparallel.readthedocs.io/en/latest/multiengine.html#parallel-map)."]}, {"cell_type": "code", "execution_count": 13, "metadata": {"collapsed": true}, "outputs": [], "source": ["def addition(m):\n", " return m + 1"]}, {"cell_type": "code", "execution_count": 14, "metadata": {"collapsed": true}, "outputs": [], "source": ["parallel_result = clients[:].map_sync(addition, range(32))"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"data": {"text/plain": ["[1, 2, 3, 4, 5]"]}, "execution_count": 16, "metadata": {}, "output_type": "execute_result"}], "source": ["parallel_result[:5]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Exemple plus subtile"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On cr\u00e9e une autre fonction plus complexe qu'on souhaite parall\u00e9liser :"]}, {"cell_type": "code", "execution_count": 16, "metadata": {"collapsed": true}, "outputs": [], "source": ["def inverse_matrice(m) :\n", " return numpy.linalg.inv ( m )"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ensuite, on cr\u00e9er 100 matrices \u00e0 inverser :"]}, {"cell_type": "code", "execution_count": 17, "metadata": {"collapsed": true}, "outputs": [], "source": ["import numpy\n", "ms = [ numpy.random.random ( (5,5) ) for i in range(0,10) ]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On fait de m\u00eame pour parall\u00e9liser la fonction. Selon les diff\u00e9rentes installation, cela provoaque une erreur ou cela n'aboutit pas."]}, {"cell_type": "code", "execution_count": 18, "metadata": {"collapsed": true}, "outputs": [], "source": ["# mat = clients[:].map_sync(inverse_matrice, ms)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Car le module ``numpy`` a \u00e9t\u00e9 import\u00e9 dans ce notebook mais il ne l'a pas \u00e9t\u00e9 sur les processus distribu\u00e9s :"]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["importing numpy on engine(s)\n"]}], "source": ["with clients[:].sync_imports():\n", " import numpy"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On r\u00e9essaye :"]}, {"cell_type": "code", "execution_count": 20, "metadata": {"collapsed": true}, "outputs": [], "source": ["mat = clients[:].map_sync(inverse_matrice, ms)"]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [{"data": {"text/plain": ["[array([[ 0.44355592, -1.51704114, 0.78806645, 1.46110877,\n", " -0.91563883],\n", " [ 2.75547106, -7.29685701, 6.23665899, 7.79916234,\n", " -15.77432008],\n", " [ -2.86686696, 4.24283253, -2.76788208, -4.2635678 ,\n", " 10.16315185],\n", " [ 3.66888355, -4.75685452, 4.39946704, 4.01960793,\n", " -12.05135127],\n", " [ -7.28746322, 16.05911264, -13.53209716, -14.15627803,\n", " 33.21478412]]),\n", " array([[ 6.25664148, 15.66280127, 12.06488389, -13.3586936 ,\n", " -16.76660498],\n", " [ 0.35637738, -4.10233268, -4.27594773, 2.7287904 ,\n", " 4.91983962],\n", " [-15.0833757 , -30.39031239, -18.9426754 , 28.14733333,\n", " 30.08489302],\n", " [ 0.2343882 , -0.76741284, -3.21725047, 1.67819449,\n", " 2.19797219],\n", " [ 6.31315111, 13.48306181, 9.00514844, -13.46040096,\n", " -12.49950715]])]"]}, "execution_count": 22, "metadata": {}, "output_type": "execute_result"}], "source": ["mat[:2]"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Autre \u00e9criture et mesure de temps"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Puis on cr\u00e9e une [view](https://ipyparallel.readthedocs.io/en/latest/api/ipyparallel.html?ipyparallel.LoadBalancedView) *load balanced* :"]}, {"cell_type": "code", "execution_count": 22, "metadata": {"collapsed": true}, "outputs": [], "source": ["view = clients.load_balanced_view()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Qu'on utilise pour dispatcher les r\u00e9sultats sur plusieurs processus :"]}, {"cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [{"data": {"text/plain": ["10"]}, "execution_count": 24, "metadata": {}, "output_type": "execute_result"}], "source": ["results = view.map(inverse_matrice, ms)\n", "len(results)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On recommence \u00e0 ex\u00e9cuter le code suivant :"]}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [{"data": {"text/plain": ["10"]}, "execution_count": 25, "metadata": {}, "output_type": "execute_result"}], "source": ["results = view.map(inverse_matrice, ms)\n", "len(results)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On compare avec un traitement non distribu\u00e9 :"]}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["The slowest run took 33.95 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "1000 loops, best of 3: 296 \u00b5s per loop\n"]}], "source": ["%timeit list(map(inverse_matrice, ms))"]}, {"cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1 loop, best of 3: 294 ms per loop\n"]}], "source": ["%timeit view.map(inverse_matrice, ms)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le processus distribu\u00e9 est plus long : cela prend du temps de communiquer les matrices depuis ce notebook vers les *engines*. Ce temps est manifestement plus long. C'est pourquoi on distribue g\u00e9n\u00e9ralement des processus dont le calcul est significativement plus lent que le temps de communication. Selon ce sch\u00e9ma, on pr\u00e9f\u00e8re envoyer des noms de fichiers au processus pour ce recevoir des r\u00e9sultats courts. Pour distribuer, il faut faire attention au ratio communication/calcul. La fonction suivante ne re\u00e7oit rien, fait beaucoup de calcul et retourne une matrice 10x10."]}, {"cell_type": "code", "execution_count": 27, "metadata": {"collapsed": true}, "outputs": [], "source": ["def average_random_matrix(i):\n", " mean = None\n", " for n in range(0,100000):\n", " m = numpy.random.random ( (10,10) )\n", " if mean is None : mean = m\n", " else : mean += m\n", " return mean / n"]}, {"cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1 loop, best of 3: 5.16 s per loop\n"]}], "source": ["%timeit list(map(average_random_matrix, range(0,10)))"]}, {"cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1 loop, best of 3: 3.23 s per loop\n"]}], "source": ["%timeit view.map(average_random_matrix, range(0,10))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La distribution commence \u00e0 montrer un gain de temps. Elle ne devient g\u00e9n\u00e9ralement int\u00e9ressante que si un traitement num\u00e9rique dure au moins quelques secondes."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Autres options pour distribuer les calculs"]}, {"cell_type": "markdown", "metadata": {}, "source": ["IPython n'est pas le seul module \u00e0 proposer un m\u00e9chanisme de parall\u00e9lisation. Un probl\u00e8me r\u00e9current et souvent tr\u00e8s aga\u00e7ant est l'**erreur qui oblige \u00e0 tout recommencer depuis le depuis**. On parall\u00e9lise pour gagner du temps et une erreur se produit au bout de trois heures \u00e0 cause d'une division par z\u00e9ro par exemple. Il existe des modules qui sauvent les r\u00e9sultats interm\u00e9diaires et ne recommencent pas les calculs d\u00e9j\u00e0 effectu\u00e9s :\n", "\n", "* [joblib](http://pythonhosted.org//joblib/index.html) : tr\u00e8s simple d'utilisation, le module parall\u00e9lise des t\u00e2ches sur plusieurs processus sans que vous ayez \u00e0 vous soucier des \u00e9changes entre processus.\n", "* [luigi](https://github.com/spotify/luigi) : c'est un syst\u00e8me pour les long [workflow](http://en.wikipedia.org/wiki/Workflow) (plusieurs heures ou jours), il permet de voir visuellement l'avancement.\n", "\n", "Il existe d'autres options comme [pyina](https://pypi.python.org/pypi/pyina). Une des premi\u00e8res solutions capable de faire cela \u00e9tait [RPyC](http://rpyc.readthedocs.org/en/latest/). Lorsqu'on veut distribuer sur plusieurs machines, il faut faire attention \u00e0 :\n", "\n", "* Les machines sont-elles sur un r\u00e9seau local ou distantes (Azure, Amazon, ...) ?\n", "* Les communications doivent-elles encrypt\u00e9es ?\n", "\n", "Quelques pointeurs :\n", "\n", "* [An introduction to 0mq](http://nichol.as/zeromq-an-introduction)\n", "* [Using IPython for parallel computing](http://ipython.org/ipython-doc/stable/parallel/index.html)\n", "* [mpi4py](http://pythonhosted.org//mpi4py/)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Un peu plus sur les threads\n", "\n", "Le module [threading](https://docs.python.org/3.4/library/threading.html) propose une interface pour cr\u00e9er des threads. C'est utile pour ex\u00e9cuter des fonctions en parall\u00e8le. Toutefois, comme le langage Python n'est pas un langage multithread\u00e9 (voir [Global Interpreter Lock](http://en.wikipedia.org/wiki/Global_Interpreter_Lock), il ne sera pas plus rapide du point de vue vitesse de calcul. La fonction suivante montre comment \u00e0 partir de la librairie standard [threading](https://docs.python.org/3.4/library/threading.html) on peut parall\u00e9liser un traitement (voir [ParallelThread](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/_modules/td_2a/parallel_thread.html))."]}, {"cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1 loop, best of 3: 3.12 s per loop\n"]}], "source": ["import numpy\n", "ms = [ numpy.random.random ( (5,5) ) for i in range(0,100000) ]\n", "\n", "def inverse_matrice(m) :\n", " return numpy.linalg.inv ( m )\n", "\n", "from ensae_teaching_cs.td_2a import ParallelThread\n", "%timeit res = ParallelThread.parallel( inverse_matrice, [ (m,) for m in ms ], 4, delay_sec = 0.1 )"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ce code sera plus lent que d'appeler la fonction directement car il faudra en plus passer du temps \u00e0 attendre la fin des autres threads ou fils d'ex\u00e9cution."]}, {"cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1 loop, best of 3: 2.95 s per loop\n"]}], "source": ["%timeit list(map(inverse_matrice, ms))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Exercice 1 : Distribuer un calcul"]}, {"cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [{"data": {"text/plain": ["['stations.txt', 'td8_velib.txt']"]}, "execution_count": 33, "metadata": {}, "output_type": "execute_result"}], "source": ["import pyensae.datasource\n", "pyensae.datasource.download_data(\"td8_velib.zip\")"]}, {"cell_type": "code", "execution_count": 33, "metadata": {"collapsed": true}, "outputs": [], "source": ["import pandas\n", "df = pandas.read_csv(\"td8_velib.txt\", sep=\"\\t\")"]}, {"cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [{"data": {"text/plain": ["(1103787, 7)"]}, "execution_count": 35, "metadata": {}, "output_type": "execute_result"}], "source": ["df.shape"]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"data": {"text/plain": ["Index(['collect_date', 'last_update', 'available_bike_stands',\n", " 'available_bikes', 'number', 'heure', 'minute'],\n", " dtype='object')"]}, "execution_count": 36, "metadata": {}, "output_type": "execute_result"}], "source": ["df.columns"]}, {"cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
collect_datelast_updateavailable_bike_standsavailable_bikesnumberheureminute
407292013-09-10 11:27:43.3900542013-09-10 11:25:19715220051125
404672013-09-10 11:27:43.3900542013-09-10 11:25:19190130371125
405252013-09-10 11:27:43.3900542013-09-10 11:25:1920290211125
403512013-09-10 11:27:43.3900542013-09-10 11:25:19162190381125
400702013-09-10 11:27:43.3900542013-09-10 11:20:19190130371120
\n", "
"], "text/plain": [" collect_date last_update available_bike_stands \\\n", "40729 2013-09-10 11:27:43.390054 2013-09-10 11:25:19 7 \n", "40467 2013-09-10 11:27:43.390054 2013-09-10 11:25:19 19 \n", "40525 2013-09-10 11:27:43.390054 2013-09-10 11:25:19 20 \n", "40351 2013-09-10 11:27:43.390054 2013-09-10 11:25:19 16 \n", "40070 2013-09-10 11:27:43.390054 2013-09-10 11:20:19 19 \n", "\n", " available_bikes number heure minute \n", "40729 15 22005 11 25 \n", "40467 0 13037 11 25 \n", "40525 2 9021 11 25 \n", "40351 2 19038 11 25 \n", "40070 0 13037 11 20 "]}, "execution_count": 37, "metadata": {}, "output_type": "execute_result"}], "source": ["df = df.sort_values(\"collect_date\")\n", "df.head()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La colonne ``number`` indique le num\u00e9ro d'une station de v\u00e9lo. On peut calculer le pourcentage de places disponibles sur l'ensemble des places de chaque station, on moyenne ce pourcentage pour chaque heure de la journ\u00e9e. Pour chaque station, on a un vecteur qui correspond \u00e0 un pourcentage pour chaque heure de la journ\u00e9e. On veut constuire la matrice de corr\u00e9lation de ces vecteurs, si possible en distribuant."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Fin : on arr\u00eate le cluster"]}, {"cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [{"data": {"text/plain": ["'c:\\\\python35_x64\\\\Scripts'"]}, "execution_count": 38, "metadata": {}, "output_type": "execute_result"}], "source": ["import os,sys\n", "if hasattr(sys, 'real_prefix'):\n", " exe = sys.real_prefix\n", "else:\n", " exe = sys.base_exec_prefix\n", "f = os.path.join(exe, \"Scripts\")\n", "f"]}, {"cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["stop ipcluster\n", "\n"]}], "source": ["if find_process(\"ipcluster\")[0] is not None:\n", " print(\"stop ipcluster\")\n", " from pyquickhelper.loghelper import run_cmd\n", " if sys.platform.startswith(\"win\"):\n", " cmd = os.path.join(f, \"ipcluster\")\n", " else:\n", " cmd = \"ipcluster\"\n", " cmd += \" stop\"\n", " out, err = run_cmd(cmd, wait=True)\n", " print(out.replace(os.environ[\"USERNAME\"], \"USERNAME\"))\n", "else:\n", " print(\"aucun processus ipcluster trouv\u00e9\")"]}, {"cell_type": "code", "execution_count": 39, "metadata": {"collapsed": true}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1"}}, "nbformat": 4, "nbformat_minor": 2}