aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Kummerlaender2019-06-27 21:10:09 +0200
committerAdrian Kummerlaender2019-06-27 21:10:09 +0200
commit05933a2f22c29afc8c9680c7725b8d5696b95ef9 (patch)
treee37a34d4f36fb30d36611dd4d1f1b13f47cbdbbd
parent9f5fa79347ae91012e8b820a1ab7064d70733431 (diff)
downloadsymlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar.gz
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar.bz2
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar.lz
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar.xz
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.tar.zst
symlbm_playground-05933a2f22c29afc8c9680c7725b8d5696b95ef9.zip
Add some benchmark plots
-rw-r--r--notebook/ldc_2d_initial_benchmark.ipynb287
-rw-r--r--notebook/ldc_benchmark.ipynb729
-rw-r--r--notebook/ldc_interactive.ipynb20
3 files changed, 861 insertions, 175 deletions
diff --git a/notebook/ldc_2d_initial_benchmark.ipynb b/notebook/ldc_2d_initial_benchmark.ipynb
new file mode 100644
index 0000000..59f6ffb
--- /dev/null
+++ b/notebook/ldc_2d_initial_benchmark.ipynb
@@ -0,0 +1,287 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from lbm import Lattice, Geometry"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import symbolic.D2Q9 as D2Q9"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def cavity(geometry, x, y):\n",
+ " if x == 1 or y == 1 or x == geometry.size_x-2:\n",
+ " return 2\n",
+ " elif y == geometry.size_y-2:\n",
+ " return 3\n",
+ " else:\n",
+ " return 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "boundary = \"\"\"\n",
+ " if ( m == 2 ) {\n",
+ " u_0 = 0.0;\n",
+ " u_1 = 0.0;\n",
+ " }\n",
+ " if ( m == 3 ) {\n",
+ " u_0 = 0.1;\n",
+ " u_1 = 0.0;\n",
+ " }\n",
+ "\"\"\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import time\n",
+ "\n",
+ "def MLUPS(cells, steps, time):\n",
+ " return cells * steps / time * 1e-6"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def test(nX, nY, nSteps):\n",
+ " lattice = Lattice(\n",
+ " descriptor = D2Q9,\n",
+ " geometry = Geometry(nX, nY),\n",
+ " moments = D2Q9.moments(optimize = False),\n",
+ " collide = D2Q9.bgk(tau = 0.56),\n",
+ " boundary_src = boundary)\n",
+ " lattice.setup_geometry(cavity)\n",
+ " \n",
+ " start = time.time()\n",
+ " \n",
+ " for i in range(0,nSteps):\n",
+ " lattice.evolve()\n",
+ " lattice.sync()\n",
+ " \n",
+ " end = time.time()\n",
+ " \n",
+ " return MLUPS(lattice.geometry.volume, nSteps, end - start)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " 32 : 15 MLUPS\n",
+ " 64 : 62 MLUPS\n",
+ " 96 : 135 MLUPS\n",
+ " 128 : 233 MLUPS\n",
+ " 160 : 376 MLUPS\n",
+ " 192 : 539 MLUPS\n",
+ " 224 : 744 MLUPS\n",
+ " 256 : 761 MLUPS\n",
+ " 288 : 763 MLUPS\n",
+ " 320 : 776 MLUPS\n",
+ " 352 : 726 MLUPS\n",
+ " 384 : 717 MLUPS\n",
+ " 416 : 750 MLUPS\n",
+ " 448 : 798 MLUPS\n",
+ " 480 : 797 MLUPS\n",
+ " 512 : 818 MLUPS\n",
+ " 544 : 802 MLUPS\n",
+ " 576 : 814 MLUPS\n",
+ " 608 : 814 MLUPS\n",
+ " 640 : 812 MLUPS\n",
+ " 672 : 815 MLUPS\n",
+ " 704 : 815 MLUPS\n",
+ " 736 : 804 MLUPS\n",
+ " 768 : 824 MLUPS\n",
+ " 800 : 728 MLUPS\n",
+ " 832 : 722 MLUPS\n",
+ " 864 : 819 MLUPS\n",
+ " 896 : 826 MLUPS\n",
+ " 928 : 828 MLUPS\n",
+ " 960 : 822 MLUPS\n",
+ " 992 : 824 MLUPS\n",
+ "1024 : 823 MLUPS\n",
+ "1056 : 822 MLUPS\n",
+ "1088 : 822 MLUPS\n",
+ "1120 : 825 MLUPS\n",
+ "1152 : 828 MLUPS\n",
+ "1184 : 821 MLUPS\n",
+ "1216 : 818 MLUPS\n",
+ "1248 : 813 MLUPS\n",
+ "1280 : 828 MLUPS\n",
+ "1312 : 824 MLUPS\n",
+ "1344 : 827 MLUPS\n",
+ "1376 : 826 MLUPS\n",
+ "1408 : 823 MLUPS\n",
+ "1440 : 826 MLUPS\n",
+ "1472 : 824 MLUPS\n",
+ "1504 : 826 MLUPS\n",
+ "1536 : 828 MLUPS\n",
+ "1568 : 823 MLUPS\n",
+ "1600 : 824 MLUPS\n",
+ "1632 : 825 MLUPS\n",
+ "1664 : 828 MLUPS\n",
+ "1696 : 827 MLUPS\n",
+ "1728 : 830 MLUPS\n",
+ "1760 : 831 MLUPS\n",
+ "1792 : 826 MLUPS\n",
+ "1824 : 828 MLUPS\n",
+ "1856 : 826 MLUPS\n",
+ "1888 : 825 MLUPS\n",
+ "1920 : 826 MLUPS\n",
+ "1952 : 826 MLUPS\n",
+ "1984 : 826 MLUPS\n",
+ "2016 : 827 MLUPS\n",
+ "2048 : 840 MLUPS\n",
+ "2080 : 829 MLUPS\n",
+ "2112 : 828 MLUPS\n",
+ "2144 : 828 MLUPS\n",
+ "2176 : 831 MLUPS\n",
+ "2208 : 826 MLUPS\n",
+ "2240 : 828 MLUPS\n",
+ "2272 : 829 MLUPS\n",
+ "2304 : 831 MLUPS\n",
+ "2336 : 828 MLUPS\n",
+ "2368 : 831 MLUPS\n",
+ "2400 : 829 MLUPS\n",
+ "2432 : 829 MLUPS\n",
+ "2464 : 829 MLUPS\n",
+ "2496 : 830 MLUPS\n",
+ "2528 : 830 MLUPS\n",
+ "2560 : 814 MLUPS\n",
+ "2592 : 827 MLUPS\n",
+ "2624 : 828 MLUPS\n",
+ "2656 : 828 MLUPS\n",
+ "2688 : 829 MLUPS\n",
+ "2720 : 828 MLUPS\n",
+ "2752 : 830 MLUPS\n",
+ "2784 : 827 MLUPS\n",
+ "2816 : 829 MLUPS\n",
+ "2848 : 826 MLUPS\n",
+ "2880 : 830 MLUPS\n",
+ "2912 : 829 MLUPS\n",
+ "2944 : 826 MLUPS\n",
+ "2976 : 828 MLUPS\n",
+ "3008 : 828 MLUPS\n",
+ "3040 : 829 MLUPS\n",
+ "3072 : 832 MLUPS\n",
+ "3104 : 826 MLUPS\n",
+ "3136 : 829 MLUPS\n",
+ "3168 : 827 MLUPS\n"
+ ]
+ }
+ ],
+ "source": [
+ "results = []\n",
+ "for size in [ 32*i for i in range(1,100) ]:\n",
+ " perf = test(nX = size, nY = size, nSteps = 100)\n",
+ " results.append((size, perf))\n",
+ " print(\"%4d : %3.0f MLUPS\" % (size, perf))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import matplotlib\n",
+ "import matplotlib.pyplot as plt\n",
+ "matplotlib.use('AGG')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Text(0, 0.5, 'MLUPS')"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7YAAAHwCAYAAACSZPPAAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzt3XucZGddJ/7P18mArVyGS7jMEAkuOAqiDM4qGi9sWBwBhRFxjbgYL7t4gVVXdzTx91JRUaLxyoqwKGpEICKGIQo6sgbcFZfLxAFDjANBLslMlAk4XFtJhuf3xzmddDrdXTVdXV19et7v16tfXXXOqapvnfN0dX3qec5T1VoLAAAADNVnzLoAAAAAmIRgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AJtQVX1fVf1zVX28qu4z63o2QlV9Y1Xd0D/nPbOuZ7Gq+py+rm2zrmU1fY2fu8K676iqv17Dff5ZVV04eXXTU1U/XlW/Pes6JlVVL6qqn1hlfauqh46zLcCZRrAFtqSqel9V/cdllj+2qj7dB4CPV9WNVfXKqvr3S7arqvqBqnpnVX2i3+6PquqRKzzeG6vqX/v7vLmqrqiqB66x9u1JfiXJ17bW7tZa+9Ba7meAfinJs/vnfGTWxSzWWvtAX9epWdeymr7Gf1zn+3xCa+2y9bzP9dZa+/nW2n+ZdR1V9Xt9+PzSRcseWlVtnNu31r63tfaz673tMnXe4fWxqi6oqn+pqq+pqs+rqtdU1Ymq+nBVHaqq3Yu2vbCqrq6qj/avi79YVWctWn/vqnp1/7r5/qp6+pLHfnq//BNVdbCq7r2W5wCwlGALnImOt9buluTuSR6T5B+S/N+qetyibX49yQ8m+YEk907yeUkOJnnSKvf77P5+Py/JjiS/erqF9W8Q75/kM5Ncu4bbV1UN9bX9wVnDc06SafakLn7TvlkNocYzyIeTPHfWRYyr741/QZIntdb+Kt1r15VJdqd7LXprktcsuslnJfmhJPdN8mVJHpfkfyxa/4Ikn+pv+21JXlhVj+gf6xFJ/leSZ/TrP5nkN6f13IAzy1Df/ABMrHVubK39ZJLfTvILSVJVD0vyrCTf2lq7qrX2b621T7bWXtZau2SM+/1wkj9O8oX9/d21qn6pqj7QDy9+UVXN9ese2/d6/FhV/VOSlyY52t/Vyaq6qt/uK6rqbVX1kf73Vyw8Xt9b/HNV9aZ0bxQ/t1/23Kr6m74X+U+q6j5V9bK+p+VtVXXuovv49X4Y8Ef73pivWrTuOX2v9u9X1ceq6tqq2rto/Tl9D/WJqvpQVf3GonXfVVXX9b1Bh6rqwUv3V79/Pp5kW5J3VNV7+uVf0D+Pk/1jPnnRbX6vql5YVa+rqk8k+Q9L7vOCqjq8ZNl/r6or+8tPqqoj/fO9oaqes2i7c/tet++uqg8kuWrRsrP6bXZW1ZV9j9b1VfVfl9T23EXXH1tVNy66/mNVdazfl0eXfKCyuN779Mdt4Xg9txYNJe7reVZVvTvJuxcte+ii21/Z3/6tSf7dco/Tb/uZVfUH/fE72T/e/ft1b6yq/9JffkfdPtrh4/3jPbZf95i+vZ3st3vsovv/jqr6x/45v7eqvm2FOta07/o2+gdLjt+F1f3N3VxV/9+i+5irqsv6NnldVf3o4sdYpqYV/zZWcFmSL6qqr1nmvka1y6XP/0BV3VRVx6vqu1baV1V1r6r60+r+Bv+lv/ygEXWmqp6Z5JeT7Gut/U2StNbe2lp7SWvtw621W9J9QLe7+lMiWmsvbK3939bap1prx5K8LMl5/f19dpJvSvITrbWPt9b+Ol1Ifkb/kN+W5E9aa/+ntfbxJD+R5KlVdfdRtQKMItgCdK5I8uj+jdnjktzYWnvrWu6oqu6b7s3dwnDaX0jXi/uoJA9NsivJTy66yQPS9Qo/OMl3JXlEv3xHa+386obqvTbJ85PcJ90w5dfWHc+9fUaSZ6brhX5/v+yCfvmudKHm/yX53f6xrkvyU4tu/7a+vnsneXmSP6qqz1y0/slJLs/tvTm/0T/XbUn+tH/Mc/vHurxftz/Jjyd5apKzk/zfJK9Yur/6Dw7u1l/94tbav6tuOPafJPmLJPdL8t+SvKwWDYlM8vQkP9c/56Xnjl6Z7s34w5Zs//L+8ieSfHv/fJ6U5Pv6ehf7miRfkGTf0pr753Fjkp1Jnpbk51cKqIv19T87yb9vrd29v+/3rbD5C/o6H5Dkwv5nqf3pes0evsLt/zXJA9O1q+9aZpsFFya5Z5Jz0rWx700yv3Sj1toX98Od75bkh9N9CPO3VbUrXRt9bro29D+S/HFVnd3/TT0/yRP65/wVSd6+Si3LOs19lyRfma7X8XFJfrKqvqBf/lPp2urnJnl8kv884qFH/W0s9ckkP5+ubS41ql3epqq+Lt1+fHyShyW506kVi3xGur/tByf5nHTH7jdW2T5Jvi/JzyZ5XGvt8CrbfXWSf1rllIivzu0jLT4vyanW2rsWrX9Hbn9Ne0R/PUnSWntPut7dzxtRK8BIgi1A53iSShd07pPkpjXcx/Or6mS6N243Jfnhqqok/zXJf+97QD6W7k3vBYtu9+kkP9UHvDuFiXTB692ttZe21m5trb0i3fDpb1i0ze+11q7t19/SL/vd1tp7WmsfSfJnSd7TWvvfrbVbk/xRktsmaGqt/UFr7UP97X85yV3ThYIFf91ae11/julLk3xxv/xL04W7A621T7TW/rXvpUmS70nyvNbadf1j/nySR9UyvbbLeEySuyW5pO8ZuipdgP7WRdu8prX2ptbap1tr/7r4xq21T6YbPvmtyW298J+fLliktfbG1to1/W3/Ll1QXdrD9pz+Od3hmFTVOelC04/1z/ft6Xr8n5HRTqXbtw+vqu2ttff1b+7voP/A4JvStYtPttb+Pl1P4FLP69vV0hoXbv+T/XN45wq3X3BLunb/0Nbaqdba1a21j660cVV9ZboQ++R+u/+c5HV9G/l0a+31SQ4neWJ/k08n+cKqmmut3dRaW8uQ87H23SI/3Vqbb629I93f5EKb/U9Jfr619i+ttRvThe4VjfG3sZz/leRzquoJS+5r1Xa5xH9K9zf8ztbaJ5I8Z5UaP9Ra++O+rXwsXai+U4/xEo9P8uYk16y0Qd/r+4J0H2Ist/47k+xNd3580v3NfmTJZh9J9+HTOOsB1kywBejsStKSnEzyoXS9XKfrB1prO1pru1pr39ZaO5Gup/KzklzdD9E8meTP++ULTiwNZkvszO29sAve39e84IZlbvfPiy7PL3N9oZc0VfUj/bDMj/Q13jPdOXQL/mnR5U8m+czqhuWek+T9fXBd6sFJfn3R8/5wug8Pdi2z7VI7k9zQWvv0omXjPOfFXp7bg/DTkxzsg0Wq6suq6g390M2PpOuhvO+S2690/zuTLHxIsVJty2qtXZ/u/MTnJPlgVV1eVTuX2fTsJGctqWG5elaqcbnbL21Di700yaEkl/fDXn+x7zW/kz7YvzLJhYt65h6c5JsXjnV/vL8yyQP7UPYt6fbxTVX12qr6/FVqWdZp7LsFS9vsQnvfmdH79TZj/G0sV+u/pesN/dl0bX6xFdvlEkvrXPH4VdVnVdX/qm5Spo8m+T9JdtTq555/b7qe0t/uP4Bbep9npxsx8Zv9h2lL1+9Pckm6nvib+8UfT3KPJZveI8nHxlwPsGaCLUDnG5P8bf8m/C+TPKgWnUc6gZvThchH9KF3R2vtnouG3iZdoF7N8XTBYbHPSXLsNO5jRf05gz+WrofoXq21Hel6Ue70ZncZN6TrmVpu8qIbknzPoue9o7U21/pz+UY4nuScuuNEWKf7nP8iyX2r6lHpgsTi4Z4vT9dLdk5r7Z5JXpQ7P9+V7v94knsvOS9wcW2fSPdhxoIH3OFOW3t5a+0r0x3Tlv7c7iVOJLk1yeLzJM9ZZruValy4/eLbfM4K26a1dktr7adbaw9PN1T469MN1b6D6s4NP5jk11prf7Zo1Q1JXrrkWH92689Jb60daq09Pt0HRv+Q5LdWKGU99t0oN2X0fk0y8d/G76YLwd+4ZPlq7XJpnWMdvyQ/kq4X+ctaa/dINzw4I+r8YLph2l+VJRM4VdW9+jqvbK3daUh1P0z6t5J8Q2ttcY/vu5KctWSo9Rfn9qHK1+b2nvNU99VUd+1vBzARwRbYyrZXNynOws8dwld1dlXVTyX5L+nOB01r7d3p3ui9orrJa+7S3/6CqrrodAroexx/K8mvVtX9+sfdVVXLnbe5ktcl+bzqvibjrKr6lnTnVP7p6dSyirunC0En0r0p/cncuVdlJW9N9wb8kqr67H4/ndeve1GSi+v2GVHvWVXfPOb9viVdyPnRqtpe3URE35D+/N1x9L3Ir0pyabrzI1+/aPXd0/W6/mt1X83y9GXuYqX7vSHJ3yR5Xv98vyjJd6ebRCfpzh99YnVfe/KAdL2MSbrzRKvq/Kq6a7rzX+fTDbFd+hin0p33/Zy+N+7zs0zQXKXGpbd/eJY/R3ehrv9QVY/se/g+mm5o8nJfbfQ7Sf6htfaLS5b/QZJvqKp9VbWt3y+PraoHVdX9q+rJ/bm2/5au126lr02aeN+N4ZXp2uW9qjs3+NmrbLvmv42+/T0nXTBeunyldrm0zu+oqodX1WfljufEL1fnfLoJ5+49YtvFtRxPcn6Sr6uqX02SqrpHut77N7XW7vR6V1Xnp2vr39SWzEPQfzB4RZKf6V8PzkvylHQjAtLf7huq6qv69vAzSa5YMvoBYE0EW2Are126N3sLP8/pl++sbhbej6ebGOaRSR7bWvuLRbf9gXSTr7wg3fDk96TrefmTNdTxY0muT/Lmfpjg/87oc/Ru07pJW74+Xa/Mh5L8aJKvXzT8b1KH0p2D+650wx3/NaOH+S7Udipd4Hxokg+km1DpW/p1r07Xo3Z5/7zfmeQJK9zV0vv9VLoJq56Qrtf7N5N8e2vtH8Z+Vp2Xp5t054+WDJf+/nRvvj+WbiKvV57m/X5rugmIjid5dbpzYRcCykvTndP5vnS9Xn+46HZ3TTd88+Z0Q2Xvl/4DlWU8O12P38Js2a9IFwzH9ex0w2//KcnvpetBXMkD0oWtj6abWOyv0oXVpS5I8o11x5mRv6oP+0/pn8uJdO3nQLr3GZ+Rru0eTzcc/WvS7f/lrNe+W83PpGun7033t/iqrLxf1/y30XtFlj9ff6V2eZu+R/zXklyV7vXjqlUe59eSzKXbN29Od7rDWPpjd36Sp1XV89K9zv37JN+55Dgv9Bj/RLp2+bpF6xb33n9/X8sH0z3/71s4p7r//b3pAu4H0wXyldoCwGmp1tY8eg0A2CBV9QtJHtBaW7HnldNXVd+X5ILW2qjJlgDYxPTYAsAmVFWfX1Vf1A+Z/9J0w51fPeu6hq6qHlhV51XVZ1T3FUI/EvsVYPCWm+wDAJi9u6cbyrkz3bDNX073VTFM5i7pvo7nIelOM7g8SyZPAmB4DEUGAABg0AxFBgAAYNAEWwAAAAZt0OfY3ve+923nnnvurMsAAABgCq6++uqbW2tnj9pu0MH23HPPzeHDh2ddBgAAAFNQVe8fZztDkQEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYtLNmXQAAwJnk4JFjufTQ0Rw/OZ+dO+ZyYN/u7N+za9ZlAQyaYAsAsEEOHjmWi6+4JvO3nEqSHDs5n4uvuCZJhFuACRiKDACwQS49dPS2ULtg/pZTufTQ0RlVBLA1CLYAABvk+Mn501oOwHgMRQYAxjLq3FDnjo62c8dcji0TYnfumNvwWjbD8Zq0hq3wHFhfW+F4bIXnMAuCLQBT45/z5rEeAWK1c0PX49zRM6G9HNi3+w77KUnmtm/LgX2777DdtD9E2Ihzfcd5DpPUMO7tJ92Xq61frxo2wkZ8MDXrDyrGOR6b/Vis19/mZnieG61aa7OuYc327t3bDh8+POsygE1knBfyM+XFftbPc+k/56R7A/+8pz5yXYPOrN9IjXMfQzgWo2o875Krlu1p3LVjLm+66PyR60c9xrjtZSsc79MNfEv3xUYcz0n3wzg1Ttpmxr39JPty1Pr1qGEcQ2hTk97HRrTr9XiMSY/HerSpjdiXm0lVXd1a2ztqOz22wLqZ9YvkuJ/UboUZSafdE7IeVpskZy1vcqZxPNfj0/3N0pO52jajjsU4NY46N3TU+lGPMW57meR4rUfv2noc7/17dq167Efti404npPuh3GO56RtZpznMOm+HLV+PWoYZT3a3Ea0qUnvYyPa9Xo8xqTHYz3a1EbsyyEyeRSwLhZeJI+dnE/L7S+SB48c27AaxpltdJxtDh45lvMuuSoPuei1Oe+Sq+70HCZdP+42KxlnX2+GmVfHfdO60vPYiOM56vbrsa8nrXGcGkZtM8mbvQUrnQO6sHzU+lGPMWlASNanTU16H+vxtzfphwjrcTwn3Q/jHM9J28yo249Tx6Tr16OGUdajzW1Em5r0PjaiXa/HY0x6PNajTW3EvhwiwRZYF5vhRXKcNw+Thq1J15/ONmsNY+Pui0nC9Tg2IuhMejw34s3cZgj4k77ZS7pzQ+e2b7vD+sXnho5avxEBYSPC86THexyTfoiwHsdzI96cT9pmRt1+nDomXb8eNYyyHm1uI9rUpPexEe16PR5j2h+GjNOmNmJfDpFgC+tg2gFhCDbqRXK1fT3Om4dJw9Z6fHI+aa/TerzBmDRcj2Mjgs60e3w24g3jRgT8Sd/sJd3wtOc99ZHZtWMule58r8Xna41avxEBYSPC86THexyTfoiwHsdzI96cT9pmRt1+nDomXb8eNYyyHm1uI9rUpPexEe16PR5j2h+GjNOmNmJfDpFgCxPaDENwN4ONeJEcta/HefMwadhaj0/OJ+11Wo83GOsxBHeUjQg60+7x2Yg3jBsR8Cd9s7dg/55dedNF5+e9lzwpb7ro/Dudi7Xa+o0ICBsRnic93uOY9EOE9TieG/HmfFQN44bjUW1ykn05zvOYtIZR1qPNbUSbmvQ+NqJdr8djbMSHIaPa1Ebty6ExKzJMaNozS57ONrN8jPWY9XGUSWfQXFzrSttMOvPrODWO2uYhF702y70yV5L3XvKksff1as9z1GOsR7seZSNmp9wsM/VOUuO4+2kjZqCe1Ho8xrRnAl2v+5j2vhxlPf5nbIZZVTfDvtwIk7apoRyLjXgvM6n1eL+0Weqc5u030rizIgu2MKFRAWGU9XrTuh5v9jb7G+NJ9/U4Ru2HSdeP8xjrFeBXM2m4Xo8a1us+Rt3/Zm/XGxHwzyRDaJdDYT9sjI34YBiGTLCFDTJpz9Z69PCtR1DaiB66SW1UjRvxyfl69BJO+hwnaTNDeiM2hDfnQ6gRmI4h/P+FWfI9trBBDuzbvewb/HHPU5j2OZn794z3nWhDmCFv0n09rv17Vv+OyUnXj9pmYfk0g86oxxi1r0e1uc1knOMxa0OoEZiOIfz/hSEQbGFCk4aQnTvmlv2kdunEMKttM87kM5M+xmawEYFvs9iIoDNJuPZGDGB9DOH/LwyBYAvrYJIQMk4v5KhtRv1TXI/HSDbHcEk9WxtntX3tjRjA+tio0Uiw1U31636q6r9X1bVV9c6qekVVfWZVPaSq3lJV766qP6yqu/Tb3rW/fn2//txp1gYbabXvAx132vdpTqE/zja+1ojFtupXBQBstHH+RwOjTW3yqKraleSvkzy8tTZfVa9M8rokT0xyRWvt8qp6UZJ3tNZeWFXfn+SLWmvfW1UXJPnG1tq3rPYYJo9iCDZqkp1p96aa3IKlNkMPPgCwtW2WyaPOSjJXVbck+awkNyU5P8nT+/WXJXlOkhcmeUp/OUleleQ3qqrakKdtZhCm/TUaGzXJzrSH6DqnkqUMCwcANoupDUVurR1L8ktJPpAu0H4kydVJTrbWbu03uzHJwruiXUlu6G97a7/9faZVHyTjDa+ddAjuVgmEK5076ZxKAABmbWrBtqrula4X9iFJdib57CRPWGbThR7ZWmXd4vt9ZlUdrqrDJ06cWK9yOUOt1pt6OtusZqsEQudUAgCwWU1z8qj/mOS9rbUTrbVbklyR5CuS7KiqhSHQD0pyvL98Y5JzkqRff88kH156p621F7fW9rbW9p599tlTLJ8zwUZ8v+tWCYQmtwAAYLOa5jm2H0jymKr6rCTzSR6X5HCSNyR5WpLLk1yY5DX99lf21/9fv/4q59cybRvx/a5b6btXnVMJAMBmNLVg21p7S1W9KsnfJrk1yZEkL07y2iSXV9Vz+2Uv6W/ykiQvrarr0/XUXjCt2mDBen2/6ygCIQAATM/Uvu5nI/i6H9bDtGdFBgAA1mbcr/sRbAEAANiUxg2205w8CgAAAKZOsAUAAGDQBFsAAAAGbZpf9wMjmZQJAACYlGDLzBw8cuwOX6Nz7OR8Lr7imiQRbgEAgLEZiszMXHro6B2+GzZJ5m85lUsPHZ1RRQAAwBAJtszM8ZPzp7UcAABgOYItM7Nzx9xpLQcAAFiOYMvMHNi3O3Pbt91h2dz2bTmwb/eMKgIAAIbI5FFM1WqzHi/8NisyAAAwCcGWiawWXMeZ9Xj/nl2CLAAAMBFDkVmzheB67OR8Wm4PrgePHEti1mMAAGBjCLas2ajgatZjAABgIwi2rNmo4GrWYwAAYCMItqzZqOBq1mMAAGAjCLas2ajgun/PrjzvqY/Mrh1zqSS7dszleU995IZPFnXwyLGcd8lVechFr815l1x12znAAADA1mBWZNZsnK/rmfWsx+PMzAwAAAybYMtEZh1cR1ltgqvNXDcAADA+wZYVrfYdtUNhZmYAANj6nGPLskZ9R+1QmJkZAAC2PsGWZY36jtqhMDMzAABsfYYis6ytMoR3nAmuAACAYRNsWdbOHXM5tkyIHeIQ3s0+wRUAADAZQ5FZliG8AADAUOixZVmG8AIAAEMh2LIiQ3gBAIAhEGzZ1LbCd+lyO8cTAIBpEGzZtBa+S3fha4cWvks3iTA0QI4nAADTYvIoNq2t8l26dMY9ngePHMt5l1yVh1z02px3yVU5eOTYRpYJAMAA6bFl09oq36VLZ5zjqVcXAIC10GPLprXSd+YO8bt0Ge946qUHAGAtBFs2rXG/S9fQ1WEY53jqpQcAYC0MRWbTGue7dA1dHY5xjufOHXM5tkyI1UsPAMBqqrU26xrWbO/eve3w4cOzLoMZOu+Sq5YNQrt2zOVNF50/g4qYxNIPKpKuV/d5T32kDyoAAM5AVXV1a23vqO302DJohq5uLeP06gIAwFKCLYNm6OrWs3/PLkEWAIDTYvIoBm3cCaYAAICtS48tg2boKgAAINgyeIauAgDAmc1QZAAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABm2qwbaqdlTVq6rqH6rquqr68qq6d1W9vqre3f++V79tVdXzq+r6qvq7qnr0NGsDAABga5h2j+2vJ/nz1trnJ/niJNcluSjJX7bWHpbkL/vrSfKEJA/rf56Z5IVTrg0AAIAtYGrBtqrukeSrk7wkSVprn2qtnUzylCSX9ZtdlmR/f/kpSX6/dd6cZEdVPXBa9QEAALA1TLPH9nOTnEjyu1V1pKp+u6o+O8n9W2s3JUn/+3799ruS3LDo9jf2ywAAAGBF0wy2ZyV5dJIXttb2JPlEbh92vJxaZlm700ZVz6yqw1V1+MSJE+tTKQAAAIM1zWB7Y5IbW2tv6a+/Kl3Q/eeFIcb97w8u2v6cRbd/UJLjS++0tfbi1tre1tres88+e2rFAwAAMAxTC7attX9KckNV7e4XPS7J3ye5MsmF/bILk7ymv3xlkm/vZ0d+TJKPLAxZBgAAgJWcNeX7/29JXlZVd0nyj0m+M12YfmVVfXeSDyT55n7b1yV5YpLrk3yy3xYAAABWNdVg21p7e5K9y6x63DLbtiTPmmY9AAAAbD3T/h5bAAAAmCrBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABi0s2ZdALNz8MixXHroaI6fnM/OHXM5sG939u/ZNeuyAAAATotge4Y6eORYLr7imszfcipJcuzkfC6+4pokEW4BAIBBMRT5DHXpoaO3hdoF87ecyqWHjs6oIgAAgLURbM9Qx0/On9ZyAACAzUqwPUPt3DF3WssBAAA2K8H2DHVg3+7Mbd92h2Vz27flwL7dM6oIAABgbUwedYZamCDKrMgAAMDQCbZnsP17dgmyAADA4BmKDAAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAz7BPlMAAAU/ElEQVSaYAsAAMCgCbYAAAAM2mkF26raXlV7qup+0yoIAAAATseqwbaqXlRVj+gv3zPJO5L8fpIjVfWtG1AfAAAArGpUj+1Xtdau7S9/Z5J3tdYemeRLkvzoVCsDAACAMYwKtp9adPnxSQ4mSWvtn6ZWEQAAAJyGUcH2ZFV9fVU9Osl5Sf48SarqrCRz0y4OAAAARjlrxPrvSfL8JA9I8kOLemofl+S10ywMAAAAxrFqsG2tvSvJ11XVfVtrNy9afijJoWkXBwAAAKOMmhX5G6rqRJK/q6obq+orNqguAAAAGMuoc2x/Lt3MyDuTfFOS502/JAAAABjfqGB7a2vtH5KktfaWJHeffkkAAAAwvlGTR92vqn54peuttV+ZTlkAAAAwnlHB9rdyx17apdcBAABgpkbNivzTG1UIAAAArMWqwbaqnr9kUUtyc5I3tNb+empVAQAAwJhGDUW+epll905yaVX9YWvt16ZQEwAAAIxt1FDky5ZbXlUvSvI3SQRbAAAAZmrU1/0sq7U2v96FAAAAwFqMGop8J1V1VpJnJLlx/csBAACA0zNq8qiPpZswarH5JH+V5HumVRQAAACMa9Q5tit+Z21V7Vz/cgAAAOD0rOkc296b160KAAAAWKNJgm2tWxUAAACwRpME26Xn3gIAAMCGGzV51P/M8gG2kuyYSkUAAABwGkZ93c/hNa67TVVt67c91lr7+qp6SJLLk9w7yd8meUZr7VNVddckv5/kS5J8KMm3tNbeN85jAAAAcOYaNSvyZevwGD+Y5Lok9+iv/0KSX22tXV5VL0ry3Ule2P/+l9baQ6vqgn67b1mHxwcAAGALGzUU+crV1rfWnjzi9g9K8qQkP5fkh6uqkpyf5On9JpcleU66YPuU/nKSvCrJb1RVtdacywsAAMCKRg1F/vIkNyR5RZK35PRnQv61JD+aZOH7cO+T5GRr7db++o1JdvWXd/WPldbarVX1kX77m0/zMQEAADiDjJoV+QFJfjzJFyb59SSPT3Jza+2vWmt/tdoNq+rrk3ywtXb14sXLbNrGWLf4fp9ZVYer6vCJEydGlA8AAMBWt2qwba2daq39eWvtwiSPSXJ9kjdW1X8b477PS/Lkqnpfusmizk/Xg7ujqhZ6ih+U5Hh/+cYk5yRJv/6eST68TE0vbq3tba3tPfvss8coAwAAgK1s5PfYVtVdq+qpSf4gybOSPD/JFaNu11q7uLX2oNbauUkuSHJVa+3bkrwhydP6zS5M8pr+8pX99fTrr3J+LQAAAKOMmjzqsnTDkP8syU+31t65Do/5Y0kur6rnJjmS5CX98pckeWlVXZ+up/aCdXgsAAAAtrharVO0qj6d5BP91cUbVpLWWrvHnW+1cfbu3dsOHx7r63QBAAAYmKq6urW2d9R2o77HduRQZQAAAJglwRUAAIBBE2wBAAAYtFWHIjNsB48cy6WHjub4yfns3DGXA/t2Z/+eXbMuCwAAYF0JtlvUwSPHcvEV12T+llNJkmMn53PxFdckiXALAABsKYYib1GXHjp6W6hdMH/LqVx66OiMKgIAAJgOwXaLOn5y/rSWAwAADJVgu0Xt3DF3WssBAACGSrDdog7s25257dvusGxu+7Yc2Ld7RhUBAABMh8mjtqiFCaLMigwAAGx1gu0Wtn/PLkEWAADY8gxFBgAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABk2wBQAAYNAEWwAAAAZNsAUAAGDQBFsAAAAGTbAFAABg0ARbAAAABu2sWRfA2h08ciyXHjqa4yfns3PHXA7s2539e3bNuiwAAIANJdgO1MEjx3LxFddk/pZTSZJjJ+dz8RXXJIlwCwAAnFEMRR6oSw8dvS3ULpi/5VQuPXR0RhUBAADMhmA7UMdPzp/WcgAAgK1KsB2onTvmTms5AADAViXYDtSBfbszt33bHZbNbd+WA/t2z6giAACA2TB51EAtTBBlVmQAAOBMJ9gO2P49uwRZAADgjGcoMgAAAIMm2AIAADBogi0AAACDJtgCAAAwaIItAAAAgybYAgAAMGiCLQAAAIMm2AIAADBoUwu2VXVOVb2hqq6rqmur6gf75feuqtdX1bv73/fql1dVPb+qrq+qv6uqR0+rNgAAALaOafbY3prkR1prX5DkMUmeVVUPT3JRkr9srT0syV/215PkCUke1v88M8kLp1gbAAAAW8TUgm1r7abW2t/2lz+W5Loku5I8Jcll/WaXJdnfX35Kkt9vnTcn2VFVD5xWfQAAAGwNG3KObVWdm2RPkrckuX9r7aakC79J7tdvtivJDYtudmO/bOl9PbOqDlfV4RMnTkyzbAAAAAZg6sG2qu6W5I+T/FBr7aOrbbrMsnanBa29uLW2t7W29+yzz16vMgEAABioqQbbqtqeLtS+rLV2Rb/4nxeGGPe/P9gvvzHJOYtu/qAkx6dZHwAAAMM3zVmRK8lLklzXWvuVRauuTHJhf/nCJK9ZtPzb+9mRH5PkIwtDlgEAAGAlZ03xvs9L8owk11TV2/tlP57kkiSvrKrvTvKBJN/cr3tdkicmuT7JJ5N85xRrAwAAYIuYWrBtrf11lj9vNkket8z2LcmzplUPAAAAW9OGzIoMAAAA0yLYAgAAMGiCLQAAAIMm2AIAADBogi0AAACDJtgCAAAwaIItAAAAgybYAgAAMGiCLQAAAIMm2AIAADBogi0AAACDJtgCAAAwaIItAAAAgybYAgAAMGiCLQAAAIMm2AIAADBogi0AAACDJtgCAAAwaIItAAAAgybYAgAAMGiCLQAAAIMm2AIAADBogi0AAACDdtasC2B5B48cy6WHjub4yfns3DGXA/t2Z/+eXbMuCwAAYNMRbDehg0eO5eIrrsn8LaeSJMdOzufiK65JEuEWAABgCUORN6FLDx29LdQumL/lVC49dHRGFQEAAGxegu0mdPzk/GktBwAAOJMJtpvQzh1zp7UcAADgTCbYbkIH9u3O3PZtd1g2t31bDuzbPaOKAAAANi+TR21CCxNEmRUZAABgNMF2k9q/Z5cgCwAAMAZDkQEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYNMEWAACAQRNsAQAAGDTBFgAAgEETbAEAABg0wRYAAIBBE2wBAAAYtLNmXcCZ6uCRY7n00NEcPzmfnTvmcmDf7uzfs2vWZQEAAAyOYDsDB48cy8VXXJP5W04lSY6dnM/FV1yTJMItAADAaTIUeQYuPXT0tlC7YP6WU7n00NEZVQQAADBcgu0MHD85f1rLAQAAWJlgOwM7d8yd1nIAAABWJtjOwIF9uzO3fdsdls1t35YD+3bPqCIAAIDhMnnUDCxMEGVWZAAAgMkJtjOyf88uQRYAAGAdGIoMAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAMmmALAADAoAm2AAAADNpZsy5gqzp45FguPXQ0x0/OZ+eOuRzYtzv79+yadVkAAABbjmA7BQePHMvFV1yT+VtOJUmOnZzPxVdckyTCLQAAwDozFHkKLj109LZQu2D+llO59NDRGVUEAACwdQm2U3D85PxpLQcAAGDtBNsp2Llj7rSWAwAAsHaC7RQc2Lc7c9u33WHZ3PZtObBv94wqAgAA2Lo2VbCtqq+rqqNVdX1VXTTretZq/55ded5TH5ldO+ZSSXbtmMvznvpIE0cBAABMwaaZFbmqtiV5QZLHJ7kxyduq6srW2t/PtrK12b9nlyALAACwATZTj+2XJrm+tfaPrbVPJbk8yVNmXBMAAACb3GYKtruS3LDo+o39MgAAAFjRZgq2tcyydqeNqp5ZVYer6vCJEyc2oCwAAAA2s80UbG9Mcs6i6w9KcnzpRq21F7fW9rbW9p599tkbVhwAAACb02YKtm9L8rCqekhV3SXJBUmunHFNAAAAbHKbZlbk1tqtVfXsJIeSbEvyO621a2dcFgAAAJvcpgm2SdJae12S1826DgAAAIZjMw1FBgAAgNMm2AIAADBogi0AAACDJtgCAAAwaIItAAAAgybYAgAAMGiCLQAAAIMm2AIAADBo1VqbdQ1rVlUnkrx/hiXcN8nNM3x8NgftAG2ARDtAG0AboKMdrK8Ht9bOHrXRoIPtrFXV4dba3lnXwWxpB2gDJNoB2gDaAB3tYDYMRQYAAGDQBFsAAAAGTbCdzItnXQCbgnaANkCiHaANoA3Q0Q5mwDm2AAAADJoeWwAAAAZNsF2jqvq6qjpaVddX1UWzrofpqar3VdU1VfX2qjrcL7t3Vb2+qt7d/75Xv7yq6vl9u/i7qnr0bKtnrarqd6rqg1X1zkXLTvu4V9WF/fbvrqoLZ/FcWJsV2sBzqupY/3rw9qp64qJ1F/dt4GhV7Vu03P+Lgaqqc6rqDVV1XVVdW1U/2C/3WnAGWaUdeD04Q1TVZ1bVW6vqHX0b+Ol++UOq6i393/UfVtVd+uV37a9f368/d9F9Lds2WAetNT+n+ZNkW5L3JPncJHdJ8o4kD591XX6mdrzfl+S+S5b9YpKL+ssXJfmF/vITk/xZkkrymCRvmXX9ftZ83L86yaOTvHOtxz3JvZP8Y//7Xv3le836ufmZqA08J8n/WGbbh/f/C+6a5CH9/4ht/l8M+yfJA5M8ur989yTv6o+114Iz6GeVduD14Az56f+m79Zf3p7kLf3f+CuTXNAvf1GS7+svf3+SF/WXL0jyh6u1jVk/v63yo8d2bb40yfWttX9srX0qyeVJnjLjmthYT0lyWX/5siT7Fy3//dZ5c5IdVfXAWRTIZFpr/yfJh5csPt3jvi/J61trH26t/UuS1yf5uulXz3pYoQ2s5ClJLm+t/Vtr7b1Jrk/3v8L/iwFrrd3UWvvb/vLHklyXZFe8FpxRVmkHK/F6sMX0f9Mf769u739akvOTvKpfvvS1YOE14lVJHldVlZXbButAsF2bXUluWHT9xqz+AsewtSR/UVVXV9Uz+2X3b63dlHT/8JLcr1+ubWxtp3vctYet6dn9MNPfWRiCGm1gy+uHEu5J11PjteAMtaQdJF4PzhhVta2q3p7kg+k+nHpPkpOttVv7TRYfz9uOdb/+I0nuE21gqgTbtalllpleeus6r7X26CRPSPKsqvrqVbbVNs5MKx137WHreWGSf5fkUUluSvLL/XJtYAurqrsl+eMkP9Ra++hqmy6zTDvYIpZpB14PziCttVOttUcleVC6XtYvWG6z/rc2MAOC7drcmOScRdcflOT4jGphylprx/vfH0zy6nQvZv+8MMS4//3BfnNtY2s73eOuPWwxrbV/7t/cfDrJb+X2IWTawBZVVdvThZmXtdau6Bd7LTjDLNcOvB6cmVprJ5O8Md05tjuq6qx+1eLjedux7tffM92pLdrAFAm2a/O2JA/rZ0K7S7qTwq+ccU1MQVV9dlXdfeFykq9N8s50x3thVssLk7ymv3xlkm/vZ8Z8TJKPLAxXY0s43eN+KMnXVtW9+iFqX9svY6CWnDP/jeleD5KuDVzQz4T5kCQPS/LW+H8xaP05cS9Jcl1r7VcWrfJacAZZqR14PThzVNXZVbWjvzyX5D+mO9f6DUme1m+29LVg4TXiaUmuaq21rNw2WAdnjd6EpVprt1bVs9P9U9qW5Hdaa9fOuCym4/5JXt39T8tZSV7eWvvzqnpbkldW1Xcn+UCSb+63f126WTGvT/LJJN+58SWzHqrqFUkem+S+VXVjkp9KcklO47i31j5cVT+b7s1MkvxMa23cyYiYsRXawGOr6lHpho69L8n3JElr7dqqemWSv09ya5JntdZO9ffj/8VwnZfkGUmu6c+tS5Ifj9eCM81K7eBbvR6cMR6Y5LKq2pauY/CVrbU/raq/T3J5VT03yZF0H4Ck//3Sqro+XU/tBcnqbYPJVffhAQAAAAyTocgAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAm0RV/X9VdW1V/V1Vvb2qvqyqfruqHj7r2gBgM/N1PwCwCVTVlyf5lSSPba39W1XdN8ldWmvHZ1waAGx6emwBYHN4YJKbW2v/liSttZtba8er6o1Vtbeqntz34r69qo5W1XuTpKq+pKr+qqqurqpDVfXAmT4LAJgBwRYANoe/SHJOVb2rqn6zqr5m8crW2pWttUe11h6V5B1Jfqmqtif5n0me1lr7kiS/k+TnNrxyAJixs2ZdAACQtNY+XlVfkuSrkvyHJH9YVRct3a6qfjTJfGvtBVX1hUm+MMnrqypJtiW5aQPLBoBNQbAFgE2itXYqyRuTvLGqrkly4eL1VfW4JN+c5KsXFiW5trX25RtZJwBsNoYiA8AmUFW7q+phixY9Ksn7F61/cJLfTPKfWmvz/eKjSc7uJ55KVW2vqkdsVM0AsFnosQWAzeFuSf5nVe1IcmuS65M8M8mr+vXfkeQ+SV7dDzs+3lp7YlU9Lcnzq+qe6f6v/1qSaze4dgCYKV/3AwAAwKAZigwAAMCgCbYAAAAMmmALAADAoAm2AAAADJpgCwAAwKAJtgAAAAyaYAsAAMCgCbYAAAAM2v8PpeWrDTazP/kAAAAASUVORK5CYII=\n",
+ "text/plain": [
+ "<Figure size 1152x576 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.figure(figsize=(16,8))\n",
+ "plt.scatter(*zip(*results))\n",
+ "plt.title('LDC Performance for various grid sizes using a Nvidia K2200')\n",
+ "plt.xlabel('Size')\n",
+ "plt.ylabel('MLUPS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "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.7.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebook/ldc_benchmark.ipynb b/notebook/ldc_benchmark.ipynb
index 59f6ffb..2530f6f 100644
--- a/notebook/ldc_benchmark.ipynb
+++ b/notebook/ldc_benchmark.ipynb
@@ -6,7 +6,8 @@
"metadata": {},
"outputs": [],
"source": [
- "from lbm import Lattice, Geometry"
+ "import matplotlib\n",
+ "import matplotlib.pyplot as plt"
]
},
{
@@ -15,7 +16,7 @@
"metadata": {},
"outputs": [],
"source": [
- "import symbolic.D2Q9 as D2Q9"
+ "import numpy"
]
},
{
@@ -24,43 +25,51 @@
"metadata": {},
"outputs": [],
"source": [
- "def cavity(geometry, x, y):\n",
- " if x == 1 or y == 1 or x == geometry.size_x-2:\n",
- " return 2\n",
- " elif y == geometry.size_y-2:\n",
- " return 3\n",
- " else:\n",
- " return 1"
+ "from result.ldc_2d_benchmark_P100 import ldc_2d_p100\n",
+ "from result.ldc_2d_benchmark_K2200 import ldc_2d_k2200\n",
+ "from result.ldc_3d_benchmark_P100 import ldc_3d_p100\n",
+ "from result.ldc_3d_benchmark_K2200 import ldc_3d_k2200"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "((32, (16, 1), 'single', True), [15, 16, 16, 16, 16, 16, 16, 16, 16, 16])"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "boundary = \"\"\"\n",
- " if ( m == 2 ) {\n",
- " u_0 = 0.0;\n",
- " u_1 = 0.0;\n",
- " }\n",
- " if ( m == 3 ) {\n",
- " u_0 = 0.1;\n",
- " u_1 = 0.0;\n",
- " }\n",
- "\"\"\""
+ "ldc_2d_k2200[0]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "((16, (16, 1, 1), 'symbolic.D3Q19', 'single', True),\n",
+ " [58, 60, 60, 60, 60, 60, 60, 60, 59, 60])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "import time\n",
- "\n",
- "def MLUPS(cells, steps, time):\n",
- " return cells * steps / time * 1e-6"
+ "ldc_3d_k2200[0]"
]
},
{
@@ -69,176 +78,562 @@
"metadata": {},
"outputs": [],
"source": [
- "def test(nX, nY, nSteps):\n",
- " lattice = Lattice(\n",
- " descriptor = D2Q9,\n",
- " geometry = Geometry(nX, nY),\n",
- " moments = D2Q9.moments(optimize = False),\n",
- " collide = D2Q9.bgk(tau = 0.56),\n",
- " boundary_src = boundary)\n",
- " lattice.setup_geometry(cavity)\n",
- " \n",
- " start = time.time()\n",
- " \n",
- " for i in range(0,nSteps):\n",
- " lattice.evolve()\n",
- " lattice.sync()\n",
- " \n",
- " end = time.time()\n",
- " \n",
- " return MLUPS(lattice.geometry.volume, nSteps, end - start)"
+ "def descriptor_subset(data, descriptor):\n",
+ " return list(\n",
+ " map(lambda m: (m[0][0:2] + m[0][3:], m[1]),\n",
+ " filter(lambda m: m[0][2] == descriptor, ldc_3d_p100)))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
+ "outputs": [],
+ "source": [
+ "ldc_3d_D3Q19_p100 = descriptor_subset(ldc_3d_p100, 'symbolic.D3Q19')\n",
+ "ldc_3d_D3Q27_p100 = descriptor_subset(ldc_3d_p100, 'symbolic.D3Q27')\n",
+ "ldc_3d_D3Q19_k2200 = descriptor_subset(ldc_3d_k2200, 'symbolic.D3Q19')\n",
+ "ldc_3d_D3Q27_k2200 = descriptor_subset(ldc_3d_k2200, 'symbolic.D3Q27')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def has(value, options):\n",
+ " if options == None:\n",
+ " return True\n",
+ " else:\n",
+ " return value in options\n",
+ "\n",
+ "def subset(data, size=None, layout=None, precision=None, optimization=None):\n",
+ " return list(\n",
+ " filter(lambda m: has(m[0][0], size) and has(m[0][1][0], layout) and has(m[0][2], precision) and has(m[0][3], optimization),\n",
+ " data))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def mlups_for_size(measurement):\n",
+ " return (measurement[0][0]**2, numpy.average(measurement[1]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def scatter(data, **kwargs):\n",
+ " plt.scatter(*zip(*list(data)), **kwargs)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
"outputs": [
{
- "name": "stdout",
- "output_type": "stream",
- "text": [
- " 32 : 15 MLUPS\n",
- " 64 : 62 MLUPS\n",
- " 96 : 135 MLUPS\n",
- " 128 : 233 MLUPS\n",
- " 160 : 376 MLUPS\n",
- " 192 : 539 MLUPS\n",
- " 224 : 744 MLUPS\n",
- " 256 : 761 MLUPS\n",
- " 288 : 763 MLUPS\n",
- " 320 : 776 MLUPS\n",
- " 352 : 726 MLUPS\n",
- " 384 : 717 MLUPS\n",
- " 416 : 750 MLUPS\n",
- " 448 : 798 MLUPS\n",
- " 480 : 797 MLUPS\n",
- " 512 : 818 MLUPS\n",
- " 544 : 802 MLUPS\n",
- " 576 : 814 MLUPS\n",
- " 608 : 814 MLUPS\n",
- " 640 : 812 MLUPS\n",
- " 672 : 815 MLUPS\n",
- " 704 : 815 MLUPS\n",
- " 736 : 804 MLUPS\n",
- " 768 : 824 MLUPS\n",
- " 800 : 728 MLUPS\n",
- " 832 : 722 MLUPS\n",
- " 864 : 819 MLUPS\n",
- " 896 : 826 MLUPS\n",
- " 928 : 828 MLUPS\n",
- " 960 : 822 MLUPS\n",
- " 992 : 824 MLUPS\n",
- "1024 : 823 MLUPS\n",
- "1056 : 822 MLUPS\n",
- "1088 : 822 MLUPS\n",
- "1120 : 825 MLUPS\n",
- "1152 : 828 MLUPS\n",
- "1184 : 821 MLUPS\n",
- "1216 : 818 MLUPS\n",
- "1248 : 813 MLUPS\n",
- "1280 : 828 MLUPS\n",
- "1312 : 824 MLUPS\n",
- "1344 : 827 MLUPS\n",
- "1376 : 826 MLUPS\n",
- "1408 : 823 MLUPS\n",
- "1440 : 826 MLUPS\n",
- "1472 : 824 MLUPS\n",
- "1504 : 826 MLUPS\n",
- "1536 : 828 MLUPS\n",
- "1568 : 823 MLUPS\n",
- "1600 : 824 MLUPS\n",
- "1632 : 825 MLUPS\n",
- "1664 : 828 MLUPS\n",
- "1696 : 827 MLUPS\n",
- "1728 : 830 MLUPS\n",
- "1760 : 831 MLUPS\n",
- "1792 : 826 MLUPS\n",
- "1824 : 828 MLUPS\n",
- "1856 : 826 MLUPS\n",
- "1888 : 825 MLUPS\n",
- "1920 : 826 MLUPS\n",
- "1952 : 826 MLUPS\n",
- "1984 : 826 MLUPS\n",
- "2016 : 827 MLUPS\n",
- "2048 : 840 MLUPS\n",
- "2080 : 829 MLUPS\n",
- "2112 : 828 MLUPS\n",
- "2144 : 828 MLUPS\n",
- "2176 : 831 MLUPS\n",
- "2208 : 826 MLUPS\n",
- "2240 : 828 MLUPS\n",
- "2272 : 829 MLUPS\n",
- "2304 : 831 MLUPS\n",
- "2336 : 828 MLUPS\n",
- "2368 : 831 MLUPS\n",
- "2400 : 829 MLUPS\n",
- "2432 : 829 MLUPS\n",
- "2464 : 829 MLUPS\n",
- "2496 : 830 MLUPS\n",
- "2528 : 830 MLUPS\n",
- "2560 : 814 MLUPS\n",
- "2592 : 827 MLUPS\n",
- "2624 : 828 MLUPS\n",
- "2656 : 828 MLUPS\n",
- "2688 : 829 MLUPS\n",
- "2720 : 828 MLUPS\n",
- "2752 : 830 MLUPS\n",
- "2784 : 827 MLUPS\n",
- "2816 : 829 MLUPS\n",
- "2848 : 826 MLUPS\n",
- "2880 : 830 MLUPS\n",
- "2912 : 829 MLUPS\n",
- "2944 : 826 MLUPS\n",
- "2976 : 828 MLUPS\n",
- "3008 : 828 MLUPS\n",
- "3040 : 829 MLUPS\n",
- "3072 : 832 MLUPS\n",
- "3104 : 826 MLUPS\n",
- "3136 : 829 MLUPS\n",
- "3168 : 827 MLUPS\n"
- ]
+ "data": {
+ "text/plain": [
+ "<matplotlib.legend.Legend at 0x7f36603a0080>"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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ZX19ROHikIuzx/svFJ7XTcfwT4EpFJ7b1yhcGfzT+tQpFZtwul6Stk9E3EghUJywrZPj8yS72OJBCv9zfhNQQWE09BwKX7cyMAtxkpqJ4UySdkA2/TYvpfRkc9dSX9m06aUURqcOBT4C/gB8L6X0WjOWJgnDmSRJUq54zpkkSVKOGM4kSZJypKi/hLZr166pR48eTbKv1atX06FDfb9KSGoaHpfKK49N5VFzH5fTp09fllLaq671ijqc9ejRg7/85S9Nsq/y8nLKysqaZF9SfXlcKq88NpVHzX1cRsR/172W05qSJEm5YjiTJEnKEcOZJElSjhT1OWe1WbduHQsXLmTt2rU7td/OnTsze/bsndqntq1t27bsv//+tG7durlLkSSpyexy4WzhwoV06tSJHj16EBE7rd9Vq1bRqVOnndafti2lxPLly1m4cCE9e/Zs7nIkSWoyu9y05tq1a+nSpctODWZqehFBly5ddvoIqCRJebfLhTPAYLaL8HWUJJWiXTKc5dHll1/OG2+8sV3bzp8/n759+273visrK7nyyivp1asXffr04YQTTuCll14C4Oabb6ZPnz7079+fAQMGVLWXlZXRu3dvBgwYwIABAzj77LO3e/+SJKn+drlzzvLqZz/7WbPt+/LLL6dnz57MnTuXFi1a8M477zB79mxefPFFHn/8cWbMmEGbNm1YtmwZH330UdV2EydOZNCgQc1WtyRJpajkR84mv7KI437we3p+8wmO+8HvmfzKoh3qb/Xq1QwfPpzDDz+cvn378utf/xoojERt+jWDjh078u1vf5vDDz+cwYMH8/777wPw9ttvM3jwYI466iiuv/56OnbsuEX/GzZsYMyYMRx11FH079+fn/70p9us5+233+all17ipptuokWLwst90EEHMXz4cJYsWULXrl1p06YNAF27dqV79+479PglSdKOKelwNvmVRVz78KssWrGGBCxasYZrH351hwLak08+Sffu3Zk5cyavvfYaJ5988hbrrF69msGDBzNz5kxOOOEE7rnnHgBGjx7N6NGjmTZt2lZD0r333kvnzp2ZNm0a06ZN45577uHdd9/daj2vv/46AwYMoGXLllssO+mkk1iwYAGHHHIIX/7yl3nuuedqLL/wwgurpjXHjBnTkKdBkiRtp5IOZ+OemsOadRtqtK1Zt4FxT83Z7j779evHM888wze+8Q2ef/55OnfuvMU6u+22G6eeeioARx55JPPnzwfgxRdf5JxzzgHgggsuqLX/qVOn8otf/IIBAwZwzDHHsHz5cubOnbtdtXbs2JHp06dz9913s9dee3Heeedx3333VS2fOHEiFRUVVFRUMG7cuO3ahyRJapiSPuds8Yo1DWqvj0MOOYTp06fz29/+lmuvvZaTTjqJ66+/vsY6rVu3rroSsWXLlqxfv77e/aeUuPPOOxk2bFi91u/Tpw8zZ85k48aNVdOa1bVs2ZKysjLKysro168fEyZM4NJLL613PZIkaecq6ZGz7nu0a1B7fSxevJj27dvzxS9+kWuuuYYZM2bUe9vBgwfzm9/8BoBJkybVus6wYcP4yU9+wrp16wB46623WL16NQCf+tSntli/V69eDBo0iBtuuIGUEgBz587l0UcfZc6cOTVG3SoqKjjwwAPrXa8kSdr5SnrkbMyw3lz78Ks1pjbbtW7JmGG9t7vPV199lTFjxtCiRQtat27NT37yk3pve9ttt/HFL36RH//4xwwfPrzWKdHLL7+c+fPnM3DgQFJK7LXXXkyePJlly5ZVha/N/exnP+PrX/86Bx98MO3bt6dLly6MGzeOyspKvvKVr7BixQpatWrFwQcfzN1331213YUXXki7doWg2rVrV5555pkGPhuSJKmhYmsf6MVg0KBBadMVkJvMnj2bQw89tN59TH5lEeOemsPiFWvovkc7xgzrzRlH7LfFek3x800ffvgh7dq1IyKYNGkSv/rVr3j00Ufrte3jjz/OO++8w6hRoxq1xqbW0Nez1JSXl1NWVtbcZUhb8NhUHm06Luv72b+zRcT0lFKd31FV0iNnAGccsV+TvCD1MX36dEaOHElKiT322IPx48fXe9tNFxhIkqSt2/RNDZtmzTZ9UwOQmzxQ8uEsT44//nhmzpzZ3GVIkrTL2tY3NRjOJEmSmtjiFWtotfsrtNnrKaL1CtK6PfifD4axeMURzV1aFcOZJEkqGV33eZ01nR8mWhS+9SB2W0HbfR+mXfvdgOHNW1ympL9KQ5IklZY2ez9VFcw2iRbraLP3U81U0ZYcOZMkqRE115WBqt0/1n3QoPbm4MhZIxs7diw/+tGPtmvb++67j5EjR9a6rLYfRa+vdevW8c1vfpNPfvKT9O3bl6OPPprf/e53AIwfP55+/frRv39/+vbtW/VVHpdeeik9e/as+q3NY489drv3L0mlojF+w1k7Zp8O+zSovTk4claCrrvuOpYsWcJrr71GmzZteP/993nuuedYuHAhN998MzNmzKBz585UVlbywQcf/5/EuHHjOPvss5uxckkqLuOemsO6dn+hwz/VPPl83FO7OXrWTEYPHM3YP49l7Ya1VW1tW7Zl9MDRzVhVTY6czXoAbu0LY/co/DvrgR3u8uabb6Z37958/vOfZ86cj39EvaKigsGDB9O/f3/OPPNM/v73vwNQVlbGpi/TXbZsGT169KjaZsGCBZx88sn07t2b7373u7Xub9y4cRx11FH079+fG264YZu1ffjhh9xzzz3ceeedtGnTBoBu3bpx7rnnsnTpUjp16lQ1KtexY0d69uy53c+DJJW6pRv/TNt9H6bFbiuIgBbZyedLN/65uUsrWcMPGs7YY8eyb4d9CYJ9O+zL2GPHMvygfFwMAKUezmY9AFNGwcoFQCr8O2XUDgW06dOnM2nSJF555RUefvhhpk2bVrXs4osv5oc//CGzZs2iX79+Ww1b1b388stMnDiRiooKHnzwQTb/RYSpU6cyd+5cXn75ZSoqKpg+fTp//OMft9rfvHnz+Kd/+id23333LZYdfvjhdOvWjZ49e3LZZZcxZcqUGsvHjBlTNa154YUX1lm7JJW6dt2m1nryebtuU5upIkEhoE09eyqzLpnF1LOn5iqYQamHs2dvhHVraratW1No307PP/88Z555Ju3bt2f33XfntNNOA2DlypWsWLGCz372swBccskl2wxRm5x44ol06dKFdu3acdZZZ/HCCy/UWD516lSmTp3KEUccwcCBA3nzzTdr/Jh5Q7Rs2ZInn3yShx56iEMOOYSrr76asWPHVi0fN24cFRUVVFRUMHHixO3ahySVktRqRYPaJSj1c85WLmxYez1FRIPWb9WqFRs3bgRg7dq1NZZt3tfm91NKXHvttVx55ZX12tfBBx/MX//6163+VmhEcPTRR3P00Udz4oknctlll9UIaJKk+tu3wz4sWb2k1nZpa0p75Kzz/g1rr4cTTjiBRx55hDVr1rBq1aqqqcHOnTuz55578vzzzwNw//33V42i9ejRg+nTpwPw0EMP1ejv6aef5m9/+xtr1qxh8uTJHHfccTWWDxs2jPHjx1NZWQnAokWLWLp0KQBDhw5l0aKa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