{ "cells": [ { "cell_type": "markdown", "id": "8f189092", "metadata": {}, "source": [ "# CTR Acceleration Backend Benchmark\n", "\n", "Benchmark the CTR structure-factor backends for a representative test crystal. The notebook compares the original NumPy path with the optional C++ and Numba acceleration backends when they are available.\n", "\n", "The first accelerated call may include setup or compilation overhead, so the benchmark does an explicit warmup before recording timings. The reported timings are runtime-only measurements after warmup.\n", "\n", "The accelerated CTR kernels are intentionally single-threaded at this level. This benchmark measures the benefit from fused loops, low temporary-array allocation, and compiler optimization without adding thread parallelism inside the structure-factor kernels.\n" ] }, { "cell_type": "markdown", "id": "f2f13a4c", "metadata": {}, "source": [ "## Setup\n", "\n", "Start Jupyter from the `benchmarks/` directory and run this notebook there. The benchmark imports the installed `orgui` package from the active Python environment. The benchmark uses the copied CTR regression fixture in `benchmarks/fixtures/0V12_calculated.xpr`.\n" ] }, { "cell_type": "markdown", "id": "ctr-backend-selection", "metadata": {}, "source": [ "## Backend Selection\n", "\n", "CTR structure-factor calculations default to C++ when the extension is available and run the explicit NumPy structure-factor path otherwise. Numba is not imported automatically unless selected. To select the active process-global backend before importing `CTRuc`, set the shared backend variable:\n", "\n", "```bash\n", "ORGUI_ACCEL_BACKEND=cpp python my_ctr_script.py\n", "ORGUI_ACCEL_BACKEND=numba python my_ctr_script.py\n", "ORGUI_ACCEL_BACKEND=numpy python my_ctr_script.py\n", "```\n", "\n", "The same selection can be changed inside a Python process:\n", "\n", "```python\n", "from orgui.datautils.xrayutils import CTRuc\n", "\n", "CTRuc.set_accel_backend(\"cpp\")\n", "CTRuc.set_accel_backend(\"numba\")\n", "CTRuc.set_accel_backend(\"numpy\") # disable acceleration\n", "```\n", "\n", "`CTRuc.set_accel_backend(\"numba\")` raises `ValueError` if the optional Numba backend cannot be imported. See **Acceleration Backends** in the main documentation for the ROI and CTR selector summary.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "a1541d65", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Benchmark directory: C:\\Users\\timof\\Documents\\repos\\orGUI\\benchmarks\n", "Fixture: C:\\Users\\timof\\Documents\\repos\\orGUI\\benchmarks\\fixtures\\0V12_calculated.xpr\n" ] } ], "source": [ "from pathlib import Path\n", "import statistics\n", "import time\n", "\n", "import numpy as np\n", "\n", "\n", "BENCHMARK_DIR = Path.cwd().resolve()\n", "FIXTURE_DIR = BENCHMARK_DIR / \"fixtures\"\n", "XPR_PATH = FIXTURE_DIR / \"0V12_calculated.xpr\"\n", "\n", "if (BENCHMARK_DIR / \"orgui\").is_dir():\n", " raise RuntimeError(\n", " \"Notebook is running from the repository root. Start Jupyter from \"\n", " \"benchmarks/ to benchmark the installed package.\"\n", " )\n", "if not XPR_PATH.exists():\n", " raise FileNotFoundError(\n", " f\"Could not find {XPR_PATH}. Start Jupyter from the benchmarks/ \"\n", " \"directory so benchmark fixtures are available.\"\n", " )\n", "\n", "print(f\"Benchmark directory: {BENCHMARK_DIR}\")\n", "print(f\"Fixture: {XPR_PATH}\")\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "60c6be1f-5d75-4412-ab23-3dc5e2265f5c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CTRsurfacePC\n" ] } ], "source": [ "!hostname\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "8734efe3-c5a6-4500-b5b3-e15de1c62ddc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "orGUI version 1.5.1.dev39+g72e3872c2.d20260704\n", "{'native_optimization': True, 'target_cpu': 'x86_64', 'allowed_instruction_set': ['AVX512'], 'native_arguments': '/arch:AVX512', 'cpp_compiler_id': 'msvc', 'cpp_compiler_version': '19.43.34810', 'host_system': 'windows', 'host_cpu_family': 'x86_64', 'host_cpu': 'x86_64', 'available': True}\n" ] } ], "source": [ "from orgui import __version__, get_build_config\n", "print(\"orGUI version\", __version__)\n", "print(get_build_config())\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "06bb510e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Default CTR acceleration backend: cpp\n", "Available CTR acceleration backends: ['numpy', 'cpp', 'numba']\n" ] } ], "source": [ "from orgui.datautils import util\n", "from orgui.datautils.xrayutils import CTRcalc, CTRuc\n", "\n", "\n", "AVAILABLE_BACKENDS = [\"numpy\"]\n", "if CTRuc.HAS_CPP_ACCEL:\n", " AVAILABLE_BACKENDS.append(\"cpp\")\n", "if CTRuc.ctr_numba_accel_available():\n", " AVAILABLE_BACKENDS.append(\"numba\")\n", "\n", "print(f\"Default CTR acceleration backend: {CTRuc.CTR_ACCEL_BACKEND}\")\n", "print(f\"Available CTR acceleration backends: {AVAILABLE_BACKENDS}\")\n" ] }, { "cell_type": "markdown", "id": "24ca4200", "metadata": {}, "source": [ "## Benchmark Helpers\n", "\n", "`CTRuc.CTR_ACCEL_BACKEND` selects the active implementation. Use `CTRuc.set_accel_backend(\"cpp\" | \"numba\" | \"numpy\")` to compare the direct C++ extension, Numba kernels, and original NumPy implementation.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "75eae4f7", "metadata": {}, "outputs": [], "source": [ "def set_backend(backend):\n", " CTRuc.set_accel_backend(backend)\n", "\n", "\n", "def load_crystal():\n", " xtal = CTRcalc.SXRDCrystal.fromFile(XPR_PATH)\n", " pt100 = CTRcalc.UnitCell(\n", " [3.9242, 3.9242, 3.9242],\n", " [90.0, 90.0, 90.0],\n", " )\n", " xtal.setGlobalReferenceUnitCell(\n", " pt100,\n", " util.z_rotation(np.deg2rad(45.0)),\n", " )\n", " return xtal\n", "\n", "\n", "def make_rod(n_points):\n", " l_values = np.ascontiguousarray(\n", " np.linspace(0.05, 7.0, n_points),\n", " dtype=np.float64,\n", " )\n", " zeros = np.zeros_like(l_values)\n", " return zeros, zeros, l_values\n", "\n", "\n", "def time_call(func, repeats=7, warmups=1):\n", " for _ in range(warmups):\n", " func()\n", "\n", " timings = []\n", " for _ in range(repeats):\n", " start = time.perf_counter()\n", " func()\n", " timings.append(time.perf_counter() - start)\n", "\n", " return {\n", " \"min_s\": min(timings),\n", " \"median_s\": statistics.median(timings),\n", " \"mean_s\": statistics.mean(timings),\n", " \"repeats\": repeats,\n", " }\n", "\n", "\n", "def format_seconds(value):\n", " if value < 1e-3:\n", " return f\"{value * 1e6:8.1f} us\"\n", " if value < 1:\n", " return f\"{value * 1e3:8.2f} ms\"\n", " return f\"{value:8.3f} s\"\n" ] }, { "cell_type": "markdown", "id": "fc12eceb", "metadata": {}, "source": [ "## Warmup and Correctness Check\n", "\n", "The accelerated and non-accelerated paths should produce the same complex amplitudes within normal floating-point tolerance before any timing results are trusted.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "34d21491", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Available accelerated backends match NumPy for UnitCell.F_uc and SXRDCrystal.F\n" ] } ], "source": [ "xtal = load_crystal()\n", "h, k, l = make_rod(2_000)\n", "\n", "set_backend(\"numpy\")\n", "numpy_uc = xtal.uc_bulk.F_uc(h, k, l)\n", "numpy_xtal = xtal.F(h, k, l)\n", "\n", "for backend in AVAILABLE_BACKENDS:\n", " if backend == \"numpy\":\n", " continue\n", " set_backend(backend)\n", " backend_uc = xtal.uc_bulk.F_uc(h, k, l)\n", " backend_xtal = xtal.F(h, k, l)\n", " np.testing.assert_allclose(backend_uc, numpy_uc, rtol=1e-12, atol=1e-12)\n", " np.testing.assert_allclose(backend_xtal, numpy_xtal, rtol=1e-10, atol=1e-10)\n", "\n", "print(\"Available accelerated backends match NumPy for UnitCell.F_uc and SXRDCrystal.F\")\n" ] }, { "cell_type": "markdown", "id": "b7762abd", "metadata": {}, "source": [ "## Benchmark Structure-Factor Evaluation\n", "\n", "This measures direct repeated evaluations for increasing CTR rod lengths. Use the median time for stable comparisons; the minimum time is also shown as a lower bound on a quiet machine.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "1a6acbcf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "benchmark points numpy median cpp median numba median cpp speedup numba speedup\n", "UnitCell.F_uc 200 130.9 us 29.3 us 27.0 us 4.47x 4.85x\n", "SXRDCrystal.F 200 370.5 us 99.5 us 97.9 us 3.72x 3.78x\n", "UnitCell.F_uc 2000 551.5 us 207.7 us 216.1 us 2.66x 2.55x\n", "SXRDCrystal.F 2000 1.10 ms 767.0 us 981.5 us 1.44x 1.12x\n", "UnitCell.F_uc 20000 3.02 ms 2.04 ms 2.20 ms 1.48x 1.37x\n", "SXRDCrystal.F 20000 11.02 ms 7.37 ms 9.38 ms 1.50x 1.18x\n" ] } ], "source": [ "def benchmark_structure_factor(n_points, repeats=7):\n", " xtal = load_crystal()\n", " h, k, l = make_rod(n_points)\n", "\n", " def run_unitcell(backend):\n", " set_backend(backend)\n", " return lambda: xtal.uc_bulk.F_uc(h, k, l)\n", "\n", " def run_crystal(backend):\n", " set_backend(backend)\n", " return lambda: xtal.F(h, k, l)\n", "\n", " rows = []\n", " for label, factory in (\n", " (\"UnitCell.F_uc\", run_unitcell),\n", " (\"SXRDCrystal.F\", run_crystal),\n", " ):\n", " stats = {\n", " backend: time_call(factory(backend), repeats=repeats)\n", " for backend in AVAILABLE_BACKENDS\n", " }\n", " row = {\n", " \"benchmark\": label,\n", " \"points\": n_points,\n", " }\n", " for backend, backend_stats in stats.items():\n", " row[f\"{backend}_median_s\"] = backend_stats[\"median_s\"]\n", " row[f\"{backend}_min_s\"] = backend_stats[\"min_s\"]\n", " for backend in AVAILABLE_BACKENDS:\n", " if backend == \"numpy\":\n", " continue\n", " row[f\"{backend}_speedup\"] = (\n", " row[\"numpy_median_s\"] / row[f\"{backend}_median_s\"]\n", " )\n", " rows.append(row)\n", " return rows\n", "\n", "\n", "results = []\n", "for n_points in (200, 2_000, 20_000):\n", " results.extend(benchmark_structure_factor(n_points))\n", "\n", "header = f\"{'benchmark':<18} {'points':>8}\"\n", "for backend in AVAILABLE_BACKENDS:\n", " header += f\" {backend + ' median':>15}\"\n", "for backend in AVAILABLE_BACKENDS:\n", " if backend != \"numpy\":\n", " header += f\" {backend + ' speedup':>14}\"\n", "print(header)\n", "for row in results:\n", " line = f\"{row['benchmark']:<18} {row['points']:>8}\"\n", " for backend in AVAILABLE_BACKENDS:\n", " line += f\" {format_seconds(row[f'{backend}_median_s']):>15}\"\n", " for backend in AVAILABLE_BACKENDS:\n", " if backend != \"numpy\":\n", " line += f\" {row[f'{backend}_speedup']:>13.2f}x\"\n", " print(line)\n" ] }, { "cell_type": "markdown", "id": "3d349f09", "metadata": {}, "source": [ "## Benchmark Results\n", "\n", "Plot the median runtime comparison for the direct structure-factor benchmarks.\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "97d96a40", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABEEAAAG4CAYAAACqxPmiAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAdBVJREFUeJzt3Xd4FOX6//HPbkIJQkICSJEWBFQEBMECKMWCFHvDggqCHjRiQcSComJXVCwRVA6IehS7oh5FLFhBpX1BRREBwQZCAglCQjZ7//7glz2EJDAJs5md7Pt1XV6S3cnMvZvPPsneO/M8ATMzAQAAAAAAVHFBrwsAAAAAAACoDDRBAAAAAABAXKAJAgAAAAAA4gJNEAAAAAAAEBdoggAAAAAAgLhAEwQAAAAAAMQFmiAAAAAAACAu0AQBAAAAAABxIdHrAmJdOBzWH3/8oTp16igQCHhdDgAAAAAA2IWZKTc3V02aNFEwWPb5HjRB9uCPP/5Qs2bNvC4DAAAAAADswdq1a9W0adMy76cJsgd16tSRtOOJTE5O9rgaAAAAAACwq5ycHDVr1izyHr4sNEH2oOgSmOTkZJogAAAAAADEsD1NY8HEqAAAAAAAIC7QBAEAAAAAAHGBJggAAAAAAIgLzAniksLCQhUUFHhdRtypVq2aEhISvC4DAAAAAOADNEH2kpnpr7/+0qZNm7wuJW7VrVtXjRo12uMEOAAAAACA+EYTZC8VNUD23Xdf1apVizfilcjMtHXrVq1fv16S1LhxY48rAgAAAADEMpoge6GwsDDSAKlXr57X5cSlpKQkSdL69eu17777cmkMAAAAAKBMTIy6F4rmAKlVq5bHlcS3ouefOVkAAAAAALtDE8QFXALjLZ5/AAAAAIATNEEAAAAAAEBcoAkSp1q2bKmJEydW+nEDgYDefPPNSj8uAAAAAABVvgmydu1a9e7dW+3atVPHjh31yiuveF3SHg0ZMkSBQCDyX7169dSvXz8tWbLE69IAAAAAAPCtKr86TGJioiZOnKhOnTpp/fr1OvTQQzVgwADts88+Xpe2W/369dO0adMk7ViG9+abb9aJJ56oNWvWeFwZAAAAgHjR8oZ3vS7BkdX3DvS6BPhElT8TpHHjxurUqZMkad9991VaWpqysrK8LcqBGjVqqFGjRmrUqJE6deqk66+/XmvXrtXff/8tSbr++uvVtm1b1apVS61atdItt9xSYnWUmTNnqmvXrqpZs6bq16+v008/vczjTZs2TSkpKZo9e7Yk6YcfftCAAQNUu3ZtNWzYUBdccIE2bNgQ2b5379668sorNWbMGKWlpalRo0a67bbbiu3z559/Vs+ePVWzZk21a9cusm8AAAAAALzgeRPks88+00knnaQmTZqUOV/EE088ofT0dNWsWVNdunTR559/XqFjzZ8/X+FwWM2aNdvLqivXli1b9J///EetW7dWvXr1JEl16tTRM888ox9++EGPPPKInn76aT388MOR73n33Xd1+umna+DAgVq0aJE++ugjde3atdT9T5gwQaNHj9asWbN0/PHH688//1SvXr3UqVMnzZ8/X++//77WrVuns88+u9j3TZ8+Xfvss4++/vpr3X///Ro/fnyk0REOh3X66acrISFB8+bN0+TJk3X99ddH6RkCAAAAAGDPPL8c5p9//tEhhxyioUOH6owzzihx/0svvaSrr75aTzzxhHr06KEnn3xS/fv31w8//KDmzZtLkrp06aL8/PwS3/vBBx+oSZMmkqSNGzfqwgsv1JQpU6L7gFzyzjvvqHbt2pJ2PEeNGzfWO++8o2BwR9/q5ptvjmzbsmVLXXvttXrppZc0ZswYSdJdd92lc845R7fffntku0MOOaTEcW688UZNnz5dc+bMUYcOHSRJkyZN0qGHHqq77747st3UqVPVrFkzLV++XG3btpUkdezYUbfeeqskqU2bNnr88cf10Ucf6fjjj9eHH36oZcuWafXq1WratKkk6e6771b//v1de44AAAAAACgPz5sg/fv33+0b44ceekjDhg3T8OHDJUkTJ07UrFmzNGnSJN1zzz2SpAULFuz2GPn5+TrttNN04403qnv37nvcdueGSk5OjqQdZzaEw+Fi24bDYZlZ5D839enTR0888YQkKSsrS5MmTVL//v319ddfq0WLFnr11Vf1yCOPaMWKFdqyZYtCoZCSk5MjdSxevFjDhw/fbV0PPvig/vnnH3377bdq1apVZNsFCxbok08+iTRhdrZixQq1adNGktShQ4di+2/cuLHWrVsnM4s0qfbbb7/INkceeaQkuf58Fe2vtJ8RAAAAgIoLyt33OdHC+wA4zYDnTZDd2b59uxYsWKAbbrih2O19+/bVV1995WgfZqYhQ4bomGOO0QUXXLDH7e+5555iZ08Uyc7OVigUKnZbQUGBwuGwQqFQifv2RjgcVlJSklq2bClpx5kekydPVv369fXkk09q4MCBOvfcczVu3DhNmDBBycnJevnllzVx4sRIHUlJSZHaytKjRw+99957mjFjRuQMEkkqLCzUwIEDi50JUqRx48YKhUIyMyUmJhbbv5mpsLBQoVBIhYWFklTs/qLbirZxSygUUjgc1ubNm7V161bX9gsAAADEu1bJ/miC+GHeR0RXbm6uo+1iugmyYcMGFRYWqmHDhsVub9iwof766y9H+/jyyy/10ksvqWPHjpH5Rp577rnIpR+7uvHGGzVq1KjI1zk5OWrWrJlSU1OVnJxcbNu8vDxlZ2crMTFRiYnuPZXBYFDBYLDYPotuy8/P17x589SiRQvdcsstkfvXrl0rSZHv6dixo+bMmaNhw4aVeZwjjjhCV155pfr166dq1arpuuuukyQdeuihev3119W6desyH1fR8r2l1ZiYmKj27dtrzZo1Wr9+feSSpG+//VaSlJCQ4OrzlZiYqGAwqJSUFNWsWdO1/QIAAADxbmVOwOsSHElLS/O6BHjM6XvMmG6CFAkEir/wzKzEbWU56qijynVqVI0aNVSjRo0Stxe9wd/1tqJmgNN6nMrPz9e6desk7TgL5fHHH9eWLVt08skna/PmzVqzZo1eeuklHXbYYXr33XcjDZ6iOm699VYde+yx2n///XXOOecoFArpvffeK3bGRyAQUPfu3fXee+9FGiHXXHONrrjiCk2ZMkXnnXeerrvuOtWvX18rVqzQjBkz9PTTTyshISHy/aU97kAgoOOPP14HHHCALrroIj344IPKycmJzGPi9vNVtL/SfkYAAAAAKi4sfzRBeB8ApxmI6SZI/fr1lZCQUOKsj/Xr15c4O6Sqef/999W4cWNJO1aCOfDAA/XKK6+od+/ekhRpVuTn52vgwIG65ZZbii1R27t3b73yyiu64447dO+99yo5OVk9e/Ys9Vg9evTQu+++qwEDBighIUFXXnmlvvzyS11//fU64YQTlJ+frxYtWqhfv36OgxUMBvXGG29o2LBhOvzww9WyZUs9+uij6tev3149LwAAAADgVx2ml35FQqxZetFSr0uImoC5PaPnXggEAnrjjTd06qmnRm474ogj1KVLl8gkoZLUrl07nXLKKZGJUaMpJydHKSkpys7OLvVymNWrV0eW74U38vLytGrVKrVs2ZKfAwAAAOCi1jf91+sSHFlx9wCvS3Ck83OdvS7BkUUXLPK6hHLLyclRamqqNm/eXOK9+848PxNky5YtWrFiReTrVatWafHixUpLS1Pz5s01atQoXXDBBeratau6deump556SmvWrNGIESOiWldmZqYyMzMjk3lW5sSoKB8mRgUAAACig4lR3dUisYXXJTjil+dzZ76ZGHX+/Pnq06dP5OuiSUkvuugiPfPMMxo0aJA2btyo8ePH688//1T79u313//+Vy1aRDc8GRkZysjIiJwJUpkTo6J8mBgVAAAAiA4mRnXXr6FfvS7BEb88nzvzzcSovXv31p6uyLn88st1+eWXV1JFpavsiVHhHBOjAgAAANHBxKjuCsv5oh1e8svzuTPH81dGuQ4AAAAAAICYQBMEAAAAAADEBc8vh/GLcDiscDhc4jYzi/wHbxQ9/6X9jAAAAABUXFD+eJ/jl/cBQZ+ch+CX53NnTmumCVIGVofxD1aHAQAAAKKD1WHcxeow0eOb1WFiFavD+AerwwAAAADRweow7mJ1mOjxzeowfsHqMLGL1WEAAACA6GB1GHexOkz0sDoMAAAAAADATjgTJApa3vBupR5v9b0DK/V4AAAAAAD4EU2QONS7d2917NhRNWvW1JQpU1S9enWNGDFCt912m1avXq309HQtWrRInTp1kiRt2rRJqamp+uSTT9S7d2/NmTNHffr00fvvv68bbrhBP/74o7p166YZM2ZowYIFGjVqlH7//XcNHDhQ//73v1WrVq3Icdu3by9Jev7555WQkKDLLrtMd9xxhwKBgMaPH69XXnlFS5cuLVZvly5dNHDgQI0fP75SnycAAAAAPnFbitcVOJPe3OsK4h5NEIdieYncihx7+vTpuuaaazRv3jzNnTtXQ4cOVffu3dWmTZvIPov2u/P/d779tttu02OPPaZatWpp0KBBOvvss1WjRg395z//0ZYtW3T66afr0Ucf1fXXX1/suBdffLHmzZun+fPn61//+peaN2+uSy65REOHDtXtt9+ub775RocddpgkacmSJVq0aJFefvnlMh8nS+QCAAAA0eGbJXJ9MtMDS+RGD0vk7iU/LZFb3mObmTp06KCxY8dKktLT0/X4449r9uzZSk9Pj+yzaL9F/y8sLFQoFIo8J7fddpuOOOIISdKQIUN0880368cff1SrVq0kSaeffro++eQTXXvttZHjNm3aVA888IACgYD2339//d///Z8efvhhDR06VI0aNVLfvn01depUde7cWZI0depU9ezZU82bNy/zcbJELgAAABAdvlkit3pbr0twpEViQ69LcIQlcuOQn5bILe+xA4GAOnbsWOz7mjRpog0bNkRu2/kxFf0/ISFBiYmJSkhIkCR17tw5cl/jxo1Vq1YttW37v8GnUaNGmj9/fmSbQCCgI488UtWqVYts06NHD02cOFGBQEAJCQm65JJLNGzYMD388MNKSEjQiy++qAkTJuz2MbJELgAAABAdvlkit+Zyr0tw5NdQntclOMISuYjpJXIrcuzq1asX+75AICAzizQ4dt5v0RkYuz7WnfcRDAZVrVq1YvsMBoMKh8MljrPr1zvffvLJJ6tGjRp68803VaNGDeXn5+vMM8/c7WNkiVwAAAAgOnyzRK5Plp5lidzocVozTRAU06BBA0nSn3/+GbkkZfHixa7tf968eSW+btOmTaT5kpiYqIsuukjTpk1TjRo1dM4550QmVgUAAAAAYG/QBEExSUlJOvLII3XvvfeqZcuW2rBhg26++WbX9r927VqNGjVK//rXv7Rw4UI99thjevDBB4ttM3z4cB100EGSpC+//NK1YwMAAAAA4htNEJQwdepUXXzxxeratasOOOAA3X///erbt68r+77wwgu1bds2HX744UpISNDIkSN16aWXFtumTZs26t69uzZu3BiZeBUAAAAAgL0VMC/XdvWBoolRN2/eXOrEqKtWrVJ6ejoTcjrQu3dvderUSRMnTtztdmamAw88UP/61780atSoPe6XnwMAAAAQHS1veNfrEhxZXfM8r0twpEN6c69LcGTpRUu9LqHcdvfefWecCeJQOBwuse5wOByWmUX+w57t6blav369nnvuOf3+++8aMmSIo+e1aJ+l/YwAAAAAVFxQ/nifE5Y/JvIM+qROP76vclozTZAyZGZmKjMzU4WFhZKk7OzsyCopRQoKChQOhxUKhUrch5KKmhW7e64aNWqk+vXr64knnlCdOnUcPa+hUEjhcFibN2/W1q1b3SwZAAAAiGutkv3RBMmq3tbrEhxpkdjQ6xIcycrK8rqEcsvNzXW0HU2QMmRkZCgjIyNySk1qamqpl8NkZ2crMTHR8ZrE8WzOnDl7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idvMLPKfVypy7I4dO2rcuHGSpNatW+vxxx/Xhx9+qNatW0f2WbTfnf+/8+133HGHunfvLkm6+OKLddNNN2nFihVq1aqVJOmMM87QJ598ojFjxkSO26xZMz300EMKBAJq27atlixZoocffljDhw+XJA0dOjSybXp6uh555BEdccQRys3NVe3atct8/GZW6s8IAAAAQMUF5d37nPII++Tz/aBP6vTj+yqnNdMEKUNmZqYyMzNVWFgoScrOzlYoFCq2TUFBgcLhsEKhUIn7KlN5j21mat++fbHva9iwodatWxe5befHVPT/wsJChUKhyHPSrl27yH0NGjRQrVq11Lx582K3ffPNN5GvzUyHH3545Psl6fDDD9dDDz2k/Px8JSQkaNGiRbrjjju0ZMkSZWVlRYK8cuVKtWvXrszHHw6HtXnz5hKX3wAAAFQV5zw11+sSHJlxaTevS4CLWiX7owmSVb2t1yU40iKxodclOJKVleV1CeWWm5vraDuaIGXIyMhQRkaGcnJylJKSotTUVCUnJxfbJi8vT9nZ2UpMTFRiondPZXmPHQgEVL169WLfl5CQIEmqXr165Oui+4vO/Ci6rWjbpKSkyDYJCQmqVq1aiX2aWeS2QCCgQCBQ6nETExOVl5engQMHqm/fvnruuefUoEEDrVmzRv369VM4HC7zcSYmJioYDColJUU1a9Ys13MBAADgFytzAl6X4EhaWprXJcBFvsldzeVel+DIr6E8r0twxI+vY6fvi2mCOBQMBhUMBkvcVvTGPhDwbnCoyLHLqnnfffeVJP3111+R+//v//6v2PcU3b7rv3etpbTbvv766xJft2nTRomJifrpp5+0YcMG3XvvvWrWrJkkacGCBbutd+f7SvsZAQAAVBVh+ePNKH+PVS2+yZ38cflG2Cd1+vF17LRm/z0yRFVSUpKOPPJI3Xvvvfrhhx/02Wef6eabb3Zt/2vXrtWoUaP0008/6cUXX9Rjjz2mq666SpLUvHlzVa9eXY899phWrlypmTNn6o477nDt2AAAAACA+EYTBCVMnTpVBQUF6tq1q6666irdeeedru37wgsv1LZt23T44YcrIyNDI0eO1KWXXippxxwizzzzjF555RW1a9dO9957ryZMmODasQEAAAAA8S1gXi5r4gNFc4Js3ry51DlBVq1apfT0dOai8BA/BwAAEA9a3vCu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IcuXL9f333+vgQMHqnr16pHbi7J3xBFHqFevXnr99dfVunVr5efn65prrlFGRoZq1qzpVdmoghjvEG2Md4gFjHWINsY6uIUzQeCJuXPnqkePHlqzZo0kqUaNGtpnn31Uu3ZtSf9bzopfkqiop556SmeffbaWL19e4r6iT6sGDx6sJk2aKBwO64MPPtC1115b7Jcq4AbGO0Qb4x1iAWMdoo2xDm6hCYKoK2197rZt22ru3Ll6/vnnI7c1bdpUr7/+uqQda3ubmbZv366FCxcqJyen0uqFvxVNejVhwgTtu+++evLJJ/XPP/8U26boE4OWLVvq9NNPV8OGDTV79uxi9wEVwXiHysR4B68w1qEyMdbBbSQCUZeQkKC///5by5Ytk7SjU1uvXj1NmDBB999/v3788UcdddRROvTQQ/Xuu+9GBqxAIKBPPvlE48aN0x9//OHlQ4CPBAIBhUIhSdL999+vp556St9++22J7Yp+oZ5xxhk64IADNHPmzMgnC7te1ww4xXiHysR4B68w1qEyMdbBbTRBUCmGDx+uU089VVu3bo10Y0eNGqUGDRro7rvvliSNHj1azZo104knnqiTTz5ZAwcO1Omnn65OnTrpwAMP9LJ8+MDOn0oVLcl3/vnnq0OHDnrggQdKnZHezJSSkqKzzjpLhYWFevLJJyXxiQH2DuMdoo3xDrGAsQ7RxliHaCENqLDyrLV9zz336Pfff9dLL71U7HsnTJigF154QR999JEOOugg/ec//9Hjjz+uTp06qWXLlvruu+905513RqV+VA1FvyCLTrP9888/lZ+fH7n/ySef1HvvvadZs2YV+xRg52uSBw4cqDZt2ujbb7+NXMsM7IzxDrGA8Q7RxliHWMBYh2gLWHlGO+D/KywsjMzyvW3bNiUlJRW7rTTXXnutXn75ZX377bdq1KiRJGn79u064IAD1LVrVz322GOR23c9VjAYZCIt7NakSZM0YcIE1atXTwkJCZo+fbpatWqlxMREnXPOOfruu+/09ttvKz09XZL01Vdf6aOPPtI555yjNm3aaNWqVapXr56Sk5M9fiSINYx3iDWMd4gGxjrEGsY6RAtngqBcirqtCQkJys7O1oknnqgLLrggctvu3HzzzQqFQpo4cWLktp9++klJSUl67bXX9OGHH5Z6vISEBH5JIsLMip0emZWVpfPPP18PPfSQbr75Zk2cOFENGjTQxRdfrAULFkiSJk+erJ9//lkvvPBC5JOEn3/+Wbfddpu+++47SVJ6erqSk5O5ZhQRjHfwGuMdKgNjHbzGWIdKZ0AFjB071hITE23gwIG2cuVKx9/37LPPWkJCgt199922ZMkSy8jIsGnTptnMmTMtPz9/j98fDoctHA7vTenwqezsbLv77rsjX2/YsMHy8/Ptt99+sxtuuMGWLFliZmb//POPnXrqqZaQkGBjxoyxdevWmZnZ7bffbo0aNbJvv/02so8PP/zQ0bHJXXzzarxD/PJyvEP88nKsKywsrGjZ8DGvxzpyF79ogqBcvvzyS2vWrJm1atXK5syZU6F9ZGRkWIcOHSwlJcWOPPJIW716deS+3b3RLCgoiPx7+/btFTo2/GvhwoUWCARsxowZdvXVV1vdunXtww8/tC1bttjPP/9sZmYTJ060evXq2fnnn2+jRo2y+vXr28yZMyP7qFatmg0dOtS2bt1abN/kDqXxcrwrLCzkj7M45tV4R+7iE2MdvMJYB6/QBEG5TJ061RITE+3VV18tdvuaNWv2+KlB0WBTUFBgf/31ly1atKjcxw+Hw3bTTTfZ2LFjy/UpBfypsLCw2FkYRx55pNWqVcvat29vX375ZbFtP/vsM+vUqZPNmDHDzHbkbJ999rGLL77Yli1bZmZm8+bNs7Vr15a7DnIXn7wa70KhUOTfa9euJXNxwuvxjtzFL6/Gup3fhC5cuNCuuuoq+/HHH50XDl/yeqwjdzAzY04QlMvQoUPVs2dPvfDCC8rNzZUkDRkyRF27dtWKFSt2+71FS1MlJCSoYcOG6tSpk6Tiy1/trOh2+/9z97766qtq1KiRPvjgA6Wnp6ugoMCNh4QYZP//2tCiSdPy8/O1bds2rVmzRoWFhTrnnHPUvXt3STuuLTYzvf3226pdu7aOOeYYSdLbb7+t1NRUvfTSS1q2bJkk6fDDD1fTpk13e20ouUORyhzvdpaQkKC8vDxdcsklOuyww/T6669r/fr1e/dgELO8HO92Ru7iV2WPdUW/X4PBoP755x8NHjxYffr00R9//KHff/+d37NVlNdjHblDMV51XxC73nnnnVJvL/qUaM6cObbffvvZGWecYfXq1bPjjz++ROe2rO/f9dS00k5VW716ta1fv77YbX/99ZcdccQRds899zh9GKgCsrOzbcSIEXb11Vfb33//bWZmTz75pNWpUyfStS/K0G233WatWrWyGTNm2MKFC23gwIH29ttv2wcffODoWOQuPnk93pmVvCb5u+++swMPPNCOPfZY++STT+yXX37htN04UJnjnRm5izexMNbl5OSUuO3WW2+1I444InLpA/NvVX2VPdaRO5SGJgiKmT9/fuTaPLOyJwwaMWKEJSYm2l133bXHfYbD4WKn2b7//vuR/e/qo48+sgYNGtisWbPsr7/+sl69etnixYvtyy+/tLZt29rnn39u27dvt48//thmzZplb7zxhv31118VeKSINUVZK/pFNGnSJEtOTrbjjz/eXnnllcgvxry8PDv44INt8ODBxbIVDoetV69e1qZNG6tVq5ZdcMEFxebz2N0vOHIXn7we77766iu7//77I1//3//9n5mZTZs2zfr06RO5vrlofzvvF/7m5XhH7uKP12NdXl6edevWzcaMGWNmZq+++qqNGzfOzMw6dOhg48ePNzOzX375xZYtW2Zz5syxjRs3On+AiFlejnXkDrtDEwT222+/RTqqeXl5NmLECNt///1L3bZoMPvtt9+sRYsWdtttt9nmzZvNrPSBaOeBatWqVda/f39LSUmxxx57rMx6evbsaQceeKAlJSXZcccdZ5s3b7a//vrLOnToYIceeqi1bt3aBgwYYM2bN7c2bdrYSSedVOHHjti0atUq69Spk02bNq3U+2fOnGkJCQn26aefRm4r+jRh8eLFtmrVqsjtTrv75C4+xNJ4N2XKlMgbk549e1pqaqrl5ubajTfeaOnp6fbf//7X7r//fhs9erQdeeSRNmrUKFu8ePFePX7Ensoe78hdfIilsc7M7D//+Y/VqVPH2rdvb3Xq1LEpU6aYmdmQIUOsVatW1qtXL+vXr58dccQRFggE7LTTTrO5c+dW7MEjJnnxtx25Q1logsDGjBljCQkJ9scff5iZ2bJly6xevXp25513mlnJTwyKvr7tttvsoIMOslmzZu12/+Fw2K666iqrXbu2DR48uNiM4UWTI5nt+CW9adMma9u2rSUmJtqoUaOK7WfJkiU2ceJEe/PNN+3TTz+1rKwse/zxx+2AAw5gQqMqIDs7284//3z7+++/7bPPPrO2bdvap59+ajk5Ofbuu+/aCy+8YNOnT4/8QjzxxBOtefPmdt9991mHDh3stNNOK7Z6y+5m/iZ38cvL8a409evXt4SEBBs0aFAk21u2bLETTjjB0tLSbODAgXbllVfayJEj7eijj7aLL764Qo8bsaUyx7vSkLuqz+uxbtf9jxkzxgKBgB1wwAGWlZUVuT0/P9/Gjx9vo0ePttdff92++eYb+/rrr61x48b2n//8p2IPHjGjssc6cgenaILANm3aZC1atLBrr73WzHb8YrvvvvssKSkpsg530RvGcDgcGWBCoZB17tzZLrnkkjJnZX722WctJSXFjjrqqBLLru18GuU///wT+ffixYvt/PPPt169etk333yz29qvv/56O/PMM8v5iOG10k6v/vzzzy09Pd0WLFhgP//8sx133HHWsmVLa9WqlZ144omWnp5urVu3tr59+5qZWW5url144YXWvXt3u+WWWyp0bHIXf7wa73ZuvBV57733rGPHjhYIBOztt9+ObGdmlpWVZevWrbOCggLLy8szM7MBAwbY1VdfXaxGxD4vxztyF79iZawr2u/ChQtt0qRJFggEbPbs2cXu29XatWutffv29sUXX1T04cMDsTTWkTvsCU2QOFc0YEybNs1q1KgRuTb4zz//tPbt29v5559fbLsiRadCPvPMM1azZk17//33S+x7/fr1dtNNN9mTTz5ZbMDZeZDMycmxIUOG2IABA+zWW2+NnHL7888/W/PmzW3cuHGRUzILCgqssLDQ3nrrLXvnnXfsuOOOswYNGtjrr7/u1tOBKNtd9/6vv/6yevXqRU6DXL58uT399NP26aef2rfffms5OTn29NNPW9OmTe2XX34xM7OtW7dafn5+ZB+7u3ad3MGL8W7X/X3//ff29ddf27Zt2yK3nXnmmXbIIYcUO9W3yObNmy0cDtsLL7xgBx54oD3//PMVeOTwgpfjnRm5i2dejHU7L3lqtqPZNnjwYLvtttvs559/juT11FNPtS5dupSYrHLt2rX20Ucf2eTJk61x48Y2aNAgy87O3stnApXBy7GO3KGiaILEoZ2v5SwSDoft8MMPt5NOOikymL366qsWCARs3rx5ZrZjkNu2bZs99NBDkdmUzcw+/PDDMo+1u4Fr8eLF1q9fPzv22GNtzJgx1rZtWzvooIMib0jHjh1r7dq1s48++qjY991xxx12yCGH2CWXXGK5ubnOHzg8tfMvqR9//DHyCdLOeezVq1fkU6vSjBgxwoYMGVLi9tI+7SwLuYsvXo53O2cyJyfHzj77bEtKSrJmzZrZscceay+//LKZ7XhjkpiYaPfee2+xP/xWrVplgwcPtqOPPtpSU1Nt8uTJFXgG4AUvxztyF59i5W+77du325133mn169e3c845x1q1amXt2rWzhx9+2Mx2fNhQo0YNmzRpUrHv+/LLL61bt27Wrl27MueMQOyJlb/tyB3KiyZIHNl1IPn8889txYoVkWvtPv30UwsGg/bWW2+Z2Y5T0gYOHGiHH3545Hs2bdpkgUDArrjiimLX6JW2/7Js2LDBzj33XOvXr5+df/75kU+mli1bZieccIJ16dLFzHYMaJ06dbJzzjnHFi5caJmZmXbllVea2Y4/3oqU9osf3tk1Bzv/sfTbb79Z79697d///rf17t3bunfvbjfffLOZ7ZibY/DgwXbVVVdFzr7Iy8uzN954w5599lnr0qWLtWzZ0j7++OMK1UXu4kusjHdmZrNnz7Z77rnHBg0aZN999519/PHHdtZZZ1mbNm3ss88+MzOzG2+80Ro3bmwLFy40sx3zM+Tk5Ni0adPsnnvuKZY3li2NHbE63pmRu3gRS2Pdo48+aieddJKdf/75kUsK8vLy7LLLLrNDDz00cunM9ddfbw0bNrSFCxfan3/+aWPGjLElS5bY999/X2x/rEwUO2J5rCN3qAiaIFXQrqeG7erFF1+0/fbbz7p06WINGza0kSNH2vLly83M7Oyzz7b27dtHThebO3eu7bPPPvbcc89Fvn/WrFn266+/7lWNl1xyiVWrVs0yMjKK1f3555/bPvvsY6+++qqZ7fjEokePHrbffvtZkyZN7N///ndk+/JOBIfo2t3PIhwO2w8//GC33367nXTSSbZ582bLzs62adOmWVJSko0dO9a2bt1qd911V7E/zMx2nJnRs2dPGzt27F7XSO6qnlgf77744gsLBALWqFEje/HFFyO3L1u2zE4//XTr06dP5LYWLVrYcccdZyNHjrTq1avbPffcU2xfNN5iR6yPd+Su6on1sc7sf7lr1qxZsWXkFy5caCeffLINHTo0cttBBx1k7du3t+rVq1u/fv2KXYLAm9DYEetjnRm5Q8XQBKlidh6s/v77b8vKyir2S/PTTz+1gw8+2CZOnGiFhYX2ySef2BFHHGG9evUyM7PVq1fbPvvsYw8++KCZ7fjj57LLLrNAIFBicKjIG8GifaxcudI6depk/fr1KzZb88aNG61v37520003RW777bff9qpDjOjbOWMzZsywjIwMu/POOyPLjF133XUWCASsd+/etmbNmmLfO2PGDDv22GPt6KOPtmeffdbatGlTbDK2DRs2FLuGsyJ/kJO7qimWxrvdvTkZOXKkBYNBe+mll4rt79lnn7WDDjoo8qn8l19+aSNHjrQ+ffrYzJkzHe8flSuWxjtyFx9iaawrsms2ir5v6NChVq9evRLLKl9zzTXWr1+/SL7/+OMPe+utt+zrr792dDxUvlga60qryYzcoeJoglRBOTk5dumll1qfPn3sxBNPtNdeey1yeuM111wTWdViy5YtdvHFF1tycrJdd911kZngb7/9dmvUqFHkE4GVK1faM8884+jYjz/+uJ199tl29913l7nCRtEAdt9991nXrl2Lfcq+bds2a9GiReQavrIm7ULs+fbbb619+/bWtm1bGzdunN1888327bffmtmOZWY7dOhgPXr0KDYbfZHVq1dbz549rU6dOtasWbNi1yUX2d21oeQufnk53v3zzz+2dOnSMvNR9Obi119/tWbNmtm1115rGzdujNz/xRdf2L777lvsj7ad52bYecUGxBYvxztyF5+8HOv++usvu+mmm2zChAn2+eefl7pNUWY2bNhg1apVszvuuKPYCmw333yztWzZssy5S/gUPjZ5OdaRO0QTTZAqZtq0aZaammonnniiffzxx/bCCy/Ypk2bLBwOW15enp199tn29NNP24MPPmh169a1vn372qJFi4rtIz8/3/bZZx8bNmyYo2OGw2H7+++/rV+/ftayZUu77LLLrGvXrlanTh17/fXXIwNP0SBXNGBt3rzZjjvuOGvdurW99NJLtmLFCnviiSesWbNmkWWs4A/fffedde7c2a644orIqio7KygosAcffNBq1Khhq1evNrOSjYasrCwbO3asJSYmRiYl3dNpmOQuvnkx3hW5++67rX79+ta2bVvr2rWrTZ06tdTtinJ36623Wps2bWz69OmR+/773/9aixYtIqes74w/zGKXF+NdEXIXn7wc66ZPn2777LOPnXDCCdahQwdr0qRJsbMmd1aUn1tvvdVq1apljz32mP3xxx/266+/Wvfu3e3aa6+lweYjXo515A7RRhOkClm6dKl169bNHnjggRKDUNHXw4cPt0AgYO3bt7fXXnstcv+WLVvsqaeeikwoNGfOHPvxxx8dH3vOnDmWnp5uK1asiNx2zjnnWI8ePeyNN94oVoPZ/wbA119/3VJTU61FixZ22mmnWcuWLYt9Qg9/uPrqq+3QQw+1tWvXlvpzNtsxqWiHDh1KXZqv6N9//PGH9evXr8Q16WUhd/HLy/Fu6tSp1qpVK/vvf/9rs2fPtuuuu86CwaA9//zzkU9dd/1UrKCgwDp16mQpKSk2YsQIu+OOO6x27do2fPjwYsuWIvZ5Nd6Ru/jk5VgXDofthBNOsBtvvNHMdiyP+8ILL1ggELCpU6fudhLVVq1aWSAQsHPPPddat25tPXv2tD/++KN8Dx6e8mqsI3eoDDRBfKis6+GuuOIKa9Wqlf3+++8lti/aZsWKFVajRg277777im3z7LPPWr9+/eyTTz7Z7bHKcscdd1inTp0sOzs78j1r1661Pn362LnnnhtZVaO0/Z133nl2xhlnRCalLO+xEX07T8i26y+43Nxc69Spk1111VV73M8rr7xigUAgci1maZ35jh072hNPPFHiWKUhd1VfLI13RaftnnPOOZFTz4sMGzbMOnfuXOrZREWfUr3xxhtWrVo1Gzp0qI0cObLYmxXEjlgb78hdfIilsa7o/mXLlllSUlJkDogil112mbVq1arE2SZm/7t89O2337ZAIGCvvfZase34HRs7Ym2sI3eoTEHBdwKBQLGvg8EdP8Zvv/1Wxx9/vJo0aaJQKFRs+2AwKDPT/vvvr5tuukmPPPKIjjnmGD3yyCMaOHCgRo4cqf79+6t37967PZYkffrpp1q+fLk2b94cua1p06b65ZdflJSUpEAgoFAopKZNm+rcc8/VTz/9pE8++aTE/goLCyVJI0aM0OrVq7V48eLIfeFwuNRjo/IVFhYqEAgoEAgoJycncpu04+eZkJCgX375RXXr1i12XxEzi/z7xBNP1EknnaThw4dL+l92i3z++ef69ddflZKSEtl/EXIXn7we73Y9diAQ0IoVK5Seni5JysvLkyTdf//9Kigo0FtvvaWsrKxi35eQkCBJOvXUU3X00Udr27ZtGjlypE4//XSFw+ESrxl4J1bGu52Ru/jg9Vi3detWfffddwqFQpH7GzdurDp16mjp0qWSpO3bt0uSHnnkEWVnZ+v1118vlnlJSkxMlLQj/127dtWTTz6pVq1aSVKxfcNbsTLWkTt4xrv+CypqxowZ9tBDD0W+Luq89u3b17p167bb7y06hWzmzJk2ePBgO/vss23o0KH2999/R7Ypq1u6ZMkSa9++vTVq1Mhat25t7dq1s6+++srMdkxelJaWFqmraJK17du32yGHHGJjxozZ7b5Hjx5tXbp0iVzCAO/t3MnPycmxa665xk455RQ77LDDrEePHvb666/bhg0bzMzs+OOPt0MPPbTMfW3atMmmTZtmZjtmsa9fv7799NNPxbbZsmWLXXjhhSVOlyR38c2r8c6s7Al3b775ZmvRokWJ40ycONGaNWtm33//fYl9FX0qP3fuXGvevLk98sgjxSZvg7diZbwzI3fxysuxbte5ZoouD928ebNdcMEFdsIJJ0QuuSr6PXv33Xdbo0aNSt1fUe6WLFligUDAJk2axHwzMSKWxjpyBy/RBPGBr776ysaNG2e5ublmtuPUyIMPPtgmTJhgDRs2tDZt2piZ2YgRI6xZs2b29ttvm1nJFS1++uknu+2222z9+vWR23a+JjgUCu12NvqTTjrJhg8fbr/99pstXbrUevXqZd27d48sqXfttddaampq5A+sol/Ko0aNsq5du5a6351P5ezXr599+eWX5XtyEHW33nqrJSUlWf/+/W3y5Mk2duxYGzhwoNWuXdtGjx5tZmZPPfVU5FrNXRUWFtoDDzxgw4cPt4KCAisoKCjzes5df1mRu/jj9Xi3pwl3w+Gwffzxx9aqVSubOHGimRVfWaNOnTr23HPPmVnJU4KLjnf++edbmzZtSn3TCm95Nd6Ru/jj9VhXpLS5ZgKBQCRP06ZNs86dO9ukSZOKHX/p0qVWv379MldkK8rhaaedZsOGDWOVtRjj5d92ZuQO3qMJ4gP33HOPPfnkk5HBZPny5ZacnGwJCQk2duzYyHbffvutNWrUyE455RTbsmVLif3cddddkcFqV3uaNfmXX36xevXqFZs/4eeff7bTTjvN+vbta1lZWfbLL79YmzZtbNCgQcW+98wzz7RLL73UzEr/JKK06xHhvZ9++sk6depk9erVi0yqtrMLL7zQGjRoYK+//rr9888/duKJJ1q9evXszTffNLMdv7C2bdtmmZmZdvjhh9srr7xS7PuddOfJXfyJhfGurAl3jzzySHv//fctPz/fLr/8cmvbtm3kk9bCwkLbvHmzdejQwe66665S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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from matplotlib import pyplot as plt\n", "\n", "labels = [f\"{row['benchmark']}\\n{row['points']} pts\" for row in results]\n", "x = np.arange(len(labels))\n", "width = min(0.8 / len(AVAILABLE_BACKENDS), 0.28)\n", "offsets = (np.arange(len(AVAILABLE_BACKENDS)) - (len(AVAILABLE_BACKENDS) - 1) / 2) * width\n", "\n", "fig, ax = plt.subplots(figsize=(11, 4.5))\n", "for offset, backend in zip(offsets, AVAILABLE_BACKENDS):\n", " ax.bar(\n", " x + offset,\n", " [row[f\"{backend}_median_s\"] for row in results],\n", " width,\n", " label=backend,\n", " )\n", "\n", "ax.set_ylabel(\"Median runtime [s]\")\n", "ax.set_yscale(\"log\")\n", "ax.set_xticks(x)\n", "ax.set_xticklabels(labels, rotation=30, ha=\"right\")\n", "ax.legend(title=\"Backend\")\n", "ax.grid(axis=\"y\", which=\"both\", alpha=0.25)\n", "fig.tight_layout()\n" ] }, { "cell_type": "code", "execution_count": null, "id": "44121b2e-221f-48b5-b520-051ab38a1e8c", "metadata": {}, "outputs": [], "source": [] } ], "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.12.3" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 5 }