{ "cells": [ { "cell_type": "markdown", "id": "blocked-chemical", "metadata": {}, "source": [ "In this demo, we first show how to use the Arrow `Dataset` API `SkyhookFileFormat` API to scan parquet files by pushing down scan opertations into Ceph and then we show how to use the `Dataset` API to process parquet files containing NanoEvents stored in Ceph in parallel through Coffea using Dask." ] }, { "cell_type": "markdown", "id": "medieval-profile", "metadata": {}, "source": [ "## Exploring SkyhookFileFormat with PyArrow\n", "\n", "We import the Dataset API and the Parquet API from PyArrow." ] }, { "cell_type": "code", "execution_count": 19, "id": "fifty-syndrome", "metadata": {}, "outputs": [], "source": [ "import pyarrow\n", "import pyarrow.dataset as ds\n", "import pyarrow.parquet as pq" ] }, { "cell_type": "markdown", "id": "inappropriate-watts", "metadata": {}, "source": [ "Now, we will instantiate the `SkyhookFileFormat`. Upon instantiation, the connection to the Ceph cluster is made under the hood. The connection is closed automatically upon object destruction. The `SkyhookFileFormat` API currently takes the Ceph configuration file as input. It inherits from the `FileFormat` API and uses the `DirectObjectAccess` API under the hood to interact with the underlying objects that make up a file in CephFS. Since, we mount CephFS, we use the `FileSystemDataset` that comes out of the box with Apache Arrow for instantiating our dataset, as by mounting CephFS we have just another directory of Parquet files. Having the suitability of using the `FileSystemDataset`, we just can start pushing down scan operations to our Parquet files by just plugging in `SkyhookFileFormat` in the format paramter. " ] }, { "cell_type": "code", "execution_count": 20, "id": "aerial-helping", "metadata": {}, "outputs": [], "source": [ "dataset = ds.dataset(\"file:///mnt/cephfs/nyc\", format=ds.SkyhookFileFormat(\"parquet\", \"/etc/ceph/ceph.conf\"))" ] }, { "cell_type": "markdown", "id": "received-costa", "metadata": {}, "source": [ "Now we apply some projections and filters on the dataset." ] }, { "cell_type": "code", "execution_count": 21, "id": "romance-prague", "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", " \n", "
total_amountfare_amount
075.8452.00
169.9952.00
259.8453.00
368.5053.50
470.0152.00
.........
37678.8867.00
37764.8458.50
3780.310.01
37958.8057.50
380229.80228.50
\n", "

381 rows × 2 columns

\n", "
" ], "text/plain": [ " total_amount fare_amount\n", "0 75.84 52.00\n", "1 69.99 52.00\n", "2 59.84 53.00\n", "3 68.50 53.50\n", "4 70.01 52.00\n", ".. ... ...\n", "376 78.88 67.00\n", "377 64.84 58.50\n", "378 0.31 0.01\n", "379 58.80 57.50\n", "380 229.80 228.50\n", "\n", "[381 rows x 2 columns]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataset.to_table(columns=[\"total_amount\", \"fare_amount\"], filter=(ds.field(\"trip_distance\") > 20.0)).to_pandas()" ] }, { "cell_type": "markdown", "id": "occupational-formula", "metadata": {}, "source": [ "## Install Dask\n", "\n", "We will be using Dask workers for parallel execution. So, let's install it." ] }, { "cell_type": "code", "execution_count": 22, "id": "homeless-overall", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: dask[distributed] in /usr/local/lib/python3.6/site-packages (2021.3.0)\n", "Requirement already satisfied: pyyaml in /usr/lib64/python3.6/site-packages (from dask[distributed]) (3.12)\n", "Requirement already satisfied: distributed>=2021.03.0 in /usr/local/lib/python3.6/site-packages (from dask[distributed]) (2021.3.0)\n", "Requirement already satisfied: sortedcontainers!=2.0.0,!=2.0.1 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (2.4.0)\n", "Requirement already satisfied: click>=6.6 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (8.0.3)\n", "Requirement already satisfied: toolz>=0.8.2 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (0.11.2)\n", "Requirement already satisfied: tblib>=1.6.0 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (1.7.0)\n", "Requirement already satisfied: zict>=0.1.3 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (2.0.0)\n", "Requirement already satisfied: psutil>=5.0 in /usr/local/lib64/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (5.8.0)\n", "Requirement already satisfied: cloudpickle>=1.5.0 in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (2.0.0)\n", "Requirement already satisfied: setuptools in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (57.0.0)\n", "Requirement already satisfied: tornado>=5 in /usr/local/lib64/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (6.1)\n", "Requirement already satisfied: contextvars in /usr/local/lib/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (2.4)\n", "Requirement already satisfied: msgpack>=0.6.0 in /usr/local/lib64/python3.6/site-packages (from distributed>=2021.03.0->dask[distributed]) (1.0.3)\n", "Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.6/site-packages (from click>=6.6->distributed>=2021.03.0->dask[distributed]) (4.8.3)\n", "Requirement already satisfied: heapdict in /usr/local/lib/python3.6/site-packages (from zict>=0.1.3->distributed>=2021.03.0->dask[distributed]) (1.0.1)\n", "Requirement already satisfied: immutables>=0.9 in /usr/local/lib64/python3.6/site-packages (from contextvars->distributed>=2021.03.0->dask[distributed]) (0.16)\n", "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.6/site-packages (from immutables>=0.9->contextvars->distributed>=2021.03.0->dask[distributed]) (4.0.1)\n", "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.6/site-packages (from importlib-metadata->click>=6.6->distributed>=2021.03.0->dask[distributed]) (3.6.0)\n", "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\n", "Requirement already satisfied: fsspec>=0.3.3 in /usr/local/lib/python3.6/site-packages (2021.11.1)\n", "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\n" ] } ], "source": [ "!pip3 install dask[distributed]\n", "!pip3 install 'fsspec>=0.3.3'" ] }, { "cell_type": "markdown", "id": "concerned-therapy", "metadata": {}, "source": [ "## Import the required modules\n", "\n", "Import `uproot`, `awkward`, `coffea`." ] }, { "cell_type": "code", "execution_count": 23, "id": "hollow-genre", "metadata": {}, "outputs": [], "source": [ "import uproot\n", "import awkward as ak\n", "from coffea.nanoevents import NanoEventsFactory, NanoAODSchema\n", "from coffea import processor, hist" ] }, { "cell_type": "markdown", "id": "manual-disposition", "metadata": {}, "source": [ "## Define a Processor instance\n", "\n", "The processor implementation given below has been taken from [here](https://github.com/CoffeaTeam/coffea/blob/master/binder/nanoevents.ipynb)." ] }, { "cell_type": "code", "execution_count": 24, "id": "domestic-interim", "metadata": {}, "outputs": [], "source": [ "class MyZPeak(processor.ProcessorABC):\n", " def __init__(self):\n", " self._histo = hist.Hist(\n", " \"Events\",\n", " hist.Cat(\"dataset\", \"Dataset\"),\n", " hist.Bin(\"mass\", \"Z mass\", 60, 60, 120),\n", " )\n", " \n", " @property\n", " def accumulator(self):\n", " return self._histo\n", " \n", " # we will receive a NanoEvents instead of a coffea DataFrame\n", " def process(self, events):\n", " out = self.accumulator.identity()\n", " mmevents = events[\n", " (ak.num(events.Muon) == 2)\n", " & (ak.sum(events.Muon.charge, axis=1) == 0)\n", " ]\n", " zmm = mmevents.Muon[:, 0] + mmevents.Muon[:, 1]\n", " out.fill(\n", " dataset=events.metadata[\"dataset\"],\n", " mass=zmm.mass,\n", " )\n", " return out\n", " \n", " def postprocess(self, accumulator):\n", " return accumulator" ] }, { "cell_type": "markdown", "id": "utility-compact", "metadata": {}, "source": [ "## Convert Root files containing NanoEvents to a Parquet file" ] }, { "cell_type": "code", "execution_count": 25, "id": "sweet-reply", "metadata": {}, "outputs": [], "source": [ "ak.to_parquet(\n", " uproot.lazy(\"../tests/samples/nano_dy.root:Events\"),\n", " \"nano_dy.parquet\",\n", " list_to32=True,\n", " use_dictionary=False,\n", " compression=\"GZIP\",\n", " compression_level=1,\n", ")\n", "\n", "ak.to_parquet(\n", " uproot.lazy(\"../tests/samples/nano_dimuon.root:Events\"),\n", " \"nano_dimuon.parquet\",\n", " list_to32=True,\n", " use_dictionary=False,\n", " compression=\"GZIP\",\n", " compression_level=1,\n", ")" ] }, { "cell_type": "markdown", "id": "together-assist", "metadata": {}, "source": [ "## Write some NanoEvents Parquet files to CephFS\n", "\n", "Here we populate the CephFS mounted directory with the parquet files created in the previous step. In this version, we need to make sure that the individual file sizes is under 4MB which is the default object size of Ceph to ensure one-to-one mapping of files to objects, which is a requirement in the multiple-file design that we have now." ] }, { "cell_type": "code", "execution_count": 26, "id": "muslim-retrieval", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "os.makedirs(\"/mnt/cephfs/nanoevents/ZJets\", exist_ok=True)\n", "os.makedirs(\"/mnt/cephfs/nanoevents/Data\", exist_ok=True)\n", "for i in range(6):\n", " os.system(f\"cp nano_dy.parquet /mnt/cephfs/nanoevents/ZJets/nano_dy.{i}.parquet\")\n", " os.system(f\"cp nano_dimuon.parquet /mnt/cephfs/nanoevents/Data/nano_dimuon.{i}.parquet\")" ] }, { "cell_type": "code", "execution_count": 27, "id": "ready-channels", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "nano_dimuon.0.parquet nano_dimuon.2.parquet nano_dimuon.4.parquet\r\n", "nano_dimuon.1.parquet nano_dimuon.3.parquet nano_dimuon.5.parquet\r\n" ] } ], "source": [ "!ls /mnt/cephfs/nanoevents/Data" ] }, { "cell_type": "code", "execution_count": 28, "id": "christian-upset", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "nano_dy.0.parquet nano_dy.2.parquet nano_dy.4.parquet\r\n", "nano_dy.1.parquet nano_dy.3.parquet nano_dy.5.parquet\r\n" ] } ], "source": [ "!ls /mnt/cephfs/nanoevents/ZJets" ] }, { "cell_type": "markdown", "id": "parental-yorkshire", "metadata": {}, "source": [ "## Reading Nanoevents using SkyhookFileFormat" ] }, { "cell_type": "code", "execution_count": 29, "id": "republican-given", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/workspace/binder/coffea/nanoevents/schemas/nanoaod.py:195: RuntimeWarning: Missing cross-reference index for FatJet_genJetAK8Idx => GenJetAK8\n", " RuntimeWarning,\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "events_skyhook = NanoEventsFactory.from_parquet(\"/mnt/cephfs/nanoevents/ZJets/nano_dy.0.parquet\", skyhook_options = {\"ceph_config_path\": \"/etc/ceph/ceph.conf\", \"ceph_data_pool\": \"cephfs_data\"}).events()\n", "events_skyhook.Muon" ] }, { "cell_type": "markdown", "id": "manufactured-america", "metadata": {}, "source": [ "## Running a job in parallel using Dask\n", "\n", "The `LocalCluster()` used below creates a process pool with worker count equal to the number of cores available to the Notebook where each worker is single-threaded. The `LocalCluster` can be replaced by other cluster resource managers provided by Dask Distributed like `KuberneresCluster`, `YarnCluster`, etc. Here, we create a `LocalCluster` and get a client handle to it." ] }, { "cell_type": "code", "execution_count": 30, "id": "adapted-amplifier", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/lib/python3.6/site-packages/distributed/node.py:155: UserWarning: Port 8787 is already in use.\n", "Perhaps you already have a cluster running?\n", "Hosting the HTTP server on port 44011 instead\n", " http_address[\"port\"], self.http_server.port\n" ] } ], "source": [ "from dask.distributed import Client, LocalCluster\n", "cluster = LocalCluster(processes=True, threads_per_worker=1)\n", "client = Client(cluster)" ] }, { "cell_type": "markdown", "id": "toxic-empty", "metadata": {}, "source": [ "We have added a new function called `run_parquet_job` to the executor API in coffea to run jobs on Parquet files using the Arrow Dataset API under the hood. \n", "This API takes an optional `ceph_config_path` parameter, which is basically the path to the configuration file of the Ceph cluster and instructs this function to read from RADOS using the `SkyhookFileFormat` (which allows pushdown) instead of the out of the box `ParquetFormat` API . This API also allows just passing a single directory path and the Datasets API does the dataset discovery task by itself. The calls to the Dataset API are launced in parallel and there will one Dataset API call per file." ] }, { "cell_type": "code", "execution_count": 31, "id": "altered-transition", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 2.66 s, sys: 518 ms, total: 3.18 sCompleted | 7.1s\n", "Wall time: 7.21 s\n" ] } ], "source": [ "%%time\n", "result = processor.run_parquet_job({\n", " \"ZJets\": \"/mnt/cephfs/nanoevents/ZJets\",\n", " \"Data\": \"/mnt/cephfs/nanoevents/Data\"\n", " },\n", " \"Events\",\n", " processor_instance=MyZPeak(),\n", " executor=processor.dask_executor,\n", " executor_args={\"schema\": processor.NanoAODSchema, \"client\": client, \"use_skyhook\": True}\n", ")" ] }, { "cell_type": "markdown", "id": "statutory-worst", "metadata": {}, "source": [ "## Running iteratively using the `iterative_executor`\n", "\n", "Run the same job again, but now iteratively. The calls to the Dataset API will now be sequential." ] }, { "cell_type": "code", "execution_count": 32, "id": "aboriginal-immune", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f8970577209f4bf7b919998126b3cab2", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Processing: 0%| | 0/12 [00:00 GenJetAK8\n", " RuntimeWarning,\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 8.12 s, sys: 854 ms, total: 8.98 s\n", "Wall time: 8.94 s\n" ] } ], "source": [ "%%time\n", "result = processor.run_parquet_job({\n", " \"ZJets\": \"/mnt/cephfs/nanoevents/ZJets\",\n", " \"Data\": \"/mnt/cephfs/nanoevents/Data\"\n", " },\n", " \"Events\",\n", " processor_instance=MyZPeak(),\n", " executor=processor.iterative_executor,\n", " executor_args={\"schema\": processor.NanoAODSchema, \"use_skyhook\": True}\n", ")" ] }, { "cell_type": "markdown", "id": "consolidated-panel", "metadata": {}, "source": [ "As expected, much slower than running using Dask." ] }, { "cell_type": "markdown", "id": "surrounded-petroleum", "metadata": {}, "source": [ "## Plotting the results\n", "\n" ] }, { "cell_type": "code", "execution_count": 33, "id": "collectible-applicant", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEGCAYAAAB/+QKOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAYFElEQVR4nO3dfbiVdZ3v8fcXRFFC8gGQCQtxOJ1EYUv4wDG80szHGh/GVOzqKKVUjmWnc5qjNmk4xdikQ5OdgzJjYJ50LMqwmEqPiVqaI3oQBA2MsDCQHZUPoLCB7/lj3cAG7g2LzV5r7b15v65rXWvdv/W71/39sfbFZ90P67ciM5EkaVs9Gl2AJKlzMiAkSaUMCElSKQNCklTKgJAkldqr0QVU4+CDD84hQ4Y0ugxJ6lKeeuqpP2Rm//au3yUCYsiQIcyZM6fRZUhSlxIRL+7O+h5ikiSVMiAkSaUMCElSqS5xDkKSdkVLSwvLli3jzTffbHQpddG7d28GDx5Mr169OvR1DQhJ3c6yZcvo27cvQ4YMISIaXU5NZSarVq1i2bJlHHbYYR362h5iktTtvPnmmxx00EHdPhwAIoKDDjqoJntLBoSkbmlPCIdNajVWA0KSVMqAkLTH69mzJ01NTQwfPpyRI0dy8803s3Hjxh2us3TpUu66664Or+VrX/saa9as6fDXbQ8DQuoAF972OBfe9nijy1A77bvvvsydO5cFCxbwwAMP8OMf/5iJEyfucB0DQpL2MAMGDGDq1Kl84xvfIDNZunQpY8eOZdSoUYwaNYrHHnsMgKuvvppHH32UpqYmJk+e3Ga/5cuXc+KJJ9LU1MSRRx7Jo48+CsD999/PmDFjGDVqFB/60Id4/fXX+frXv87vf/97TjrpJE466aSG/Rtslpmd/vbud787pc7sglsfywtufazRZaiwcOHCXerfp0+f7dr69euXK1asyNWrV+cbb7yRmZmLFi3KTf8fPfTQQ3nWWWdt7t9Wv5tuuim/9KUvZWbm+vXr89VXX83m5uYcO3Zsvv7665mZeeONN+bEiRMzM/Md73hHNjc371L9meVjBubkbvzf6/cgJGkHWlpauPLKK5k7dy49e/Zk0aJFu9TvmGOO4aMf/SgtLS2cc845NDU18fDDD7Nw4UJOOOEEANatW8eYMWPqNqZqGRCStI0lS5bQs2dPBgwYwMSJExk4cCDPPPMMGzdupHfv3qXrTJ48ubTfiSeeyCOPPMKsWbO49NJL+exnP8sBBxzA+9//fu6+++56DmuXeQ5Cklppbm7mE5/4BFdeeSURwSuvvMKgQYPo0aMHd955Jxs2bACgb9++vPbaa5vXa6vfiy++yMCBA7n88su57LLLePrppzn++OP5xS9+wQsvvADA6tWrN+9xbPu6jWRASNrjvfHGG5svcz3llFM49dRTuf766wG44ooruOOOOxg5ciTPP/88ffr0AWDEiBH07NmTkSNHMnny5Db7zZ49m5EjR3L00Udzzz33cNVVV9G/f3+mT5/OuHHjGDFiBGPGjOH5558HYMKECZx++umd4iR1VM5jdG6jR49OfzBIndmmS1zv+XjnO468J3ruued417ve1egy6qpszBHxVGaObu9rugchSSplQEiSShkQkqRSBoQkqZQBIUkqZUBIEk64WMaAkKQa6ExTiLeXASFJNdCZphBvLwNCkmqso6cQrxcn65PUrU384QIW/v7V7doXLt+6bc3a9QAc9cWfbtV+xKD9t1v3iL/Yn+s/OHyX6hg6dCgbNmxg5cqVDBgwgAceeIDevXuzePFixo0bx5w5c7jxxhu56aab+NGPflSpac2a0n71YkBIUp3t7hTi9WJASOrWqv2kX+v5tDpyCvF68RyEJNVYR08hXi8GhCTVQC2nEK8XDzFJUg3s6NP+sGHDmDdv3ublr3zlKwD06tWLn/3sZ1v1LetXLwaEJOFveZTxEJMkqZQBIUkqVbOAiIhDI+KhiFgYEQsi4qqi/cCIeCAiFhf3B9SqBklS+9VyD2I98N8z8wjgeOBvIuII4GrgwcwcBjxYLEuSOpmaBURmLs/Mp4vHrwHPAW8DzgbuKLrdAZxTqxokqWrTzqrctFldzkFExBDgaOAJYGBmLi+eWgEMbGOdCRExJyLmNDc316NMSeoQ9957L01NTVvdevTowZQpUzjyyCN3uG5nmtG15gEREW8Bvgd8JjO3mh0rMxPIsvUyc2pmjs7M0f379691mZLUYc4991zmzp27+XbFFVcwduxYTjvttJ2uu8cERET0ohIO387M7xfNL0fEoOL5QcDKWtYgSY20aNEibrjhBu6880569NjyX+6GDRv43Oc+xzHHHMOIESO47bbbgO2n/F6wYAHHHnssTU1NjBgxgsWLF9et9pp9US4iArgdeC4z/6nVU/cBlwA3Fvcza1WDJPHjq2HF/O3bV8zbennd6sr9Pxy6dfshI7Zf95Cj4Iwbd7rplpYWLr74Ym6++Wbe/va3s3Tp0s3P3X777fTr148nn3yStWvXcsIJJ3DqqaduN+X3pz71Ka666io+/OEPs27durrOx1TLb1KfAHwEmB8Rc4u2a6kEw3ci4mPAi8AFNaxBkhrmC1/4AsOHD+fCCy/c7rn777+fefPmMWPGDKAyMd/ixYvZe++9t+o3ZswYvvzlL7Ns2TLOO+88hg0bVpfaoYYBkZk/B6KNp99Xq+1K0laq+KQPbLmCafysDtns7Nmz+d73vsfTTz9d+nxmcsstt2x3XmL27NlbLV988cUcd9xxzJo1izPPPJPbbruNk08+uUNq3Bm/SS1JHexPf/oT48eP51vf+hZ9+/Yt7XPaaacxZcoUWlpagMq5itWrV2835feSJUsYOnQon/70pzn77LO3mryv1pysT5I62K233srKlSv55Cc/uVX7uHHj2GeffQC47LLLWLp0KaNGjSIz6d+/Pz/4wQ+2mvL70ksvZe3atdx555306tWLQw45hGuvvbZu4zAgJKmDXXPNNVxzzTXbtc+cOZPDDz8cgB49ejBp0iQmTZq0Xb9tp/y++urGTDhhQEgSdNi5h7Zcd911zJw5k+nTp9d0Ox3JcxCSVAc33HADzzzzDEcffXSjS6maASGpW6pM1LBnqNVYDQhJ3U7v3r1ZtWrVHhESmcmqVavo3bt3h7+25yAkdTuDBw9m2bJl7CkTffbu3ZvBgwd3+OsaEJK6nV69enHYYYc1uowuz0NMkqRSBoQkqZSHmKR2uuuJ3zJz7ksALFxe+amTC297HICzm97Gxce9vWG1SR3BPQipnWbOfWlzMLS2cPmrm4ND6soMCGk3HDFof+75+BiOGLT/Vo+l7sCAkCSVMiAkSaUMCElSKQNCklTKgJAklTIgJEmlDAhJUikDQpJUyoCQJJUyICRJpQwISVIpA0KadlblJmkrBoQkqZQBIUkqZUBIkkoZEJKkUgaEJKmUASFJKmVASJJKGRCSpFIGhCSpVM0CIiK+GRErI+LZVm1fjIiXImJucTuzVtuXJO2eWu5BTAdOL2mfnJlNxe3fa7h9SdJuqFlAZOYjwB9r9fqSpNpqxDmIKyNiXnEI6oAGbF+SVIV6B8QU4HCgCVgO3NxWx4iYEBFzImJOc3NzncqTJG1S14DIzJczc0NmbgT+BTh2B32nZubozBzdv3//+hUpSQLqHBARMajV4rnAs231lSQ11l61euGIuBt4L3BwRCwDrgfeGxFNQAJLgY/XavuSpN1Ts4DIzHElzbfXanuSpI7lN6klSaUMCElSKQNCklTKgJAklTIgJEmldvkqpmJ6jEMzc14N6pG6pHs+PqbRJUgdrqo9iIiYHRH7R8SBwNPAv0TEP9W2NElSI1V7iKlfZr4KnAd8KzOPA06pXVmSpEarNiD2KqbJuAD4UQ3rkSR1EtUGxETgp8ALmflkRAwFFteuLElSo1V7knp5Zo7YtJCZSzwHIUndW7V7ELdU2SZJ6iZ2uAcREWOA/wL0j4jPtnpqf6BnLQuTJDXWzg4x7Q28pejXt1X7q8D5tSpKktR4OwyIzHwYeDgipmfmi3WqSZLUCVR7knqfiJgKDGm9TmaeXIuiJEmNV21AfBe4FfhXYEPtypEkdRbVBsT6zJxS00okSZ1KtZe5/jAiroiIQRFx4KZbTSuTJDVUtXsQlxT3n2vVlsDQji1HktRZVBUQmXlYrQuR6m7ONJg/A1YUM9dPO6tyf9T5MHp84+qSOolqp/veLyL+rriSiYgYFhEfqG1pUo3NnwEr5m/dtmJ+pV1S1ecgpgHrqHyrGuAl4Es1qUiqp0OOgkNGVG7jZ1WWJQHVB8ThmfmPQAtAZq4BomZVSZIartqAWBcR+1I5MU1EHA6srVlVkqSGq/Yqpi8CPwEOjYhvAycAl9aoJklSJ1DtVUz3R8RTwPFUDi1dlZl/qGllkqSGqiogIuKHwF3AfZm5urYlSZI6g2rPQdwEjAUWRsSMiDg/InrXsC5JUoNVe4hp07TfPYGTgcuBb1L54SBJUjdU7UlqiquYPghcCIwC7qhVUZKkxqv2HMR3gGOpXMn0DeDhzNxYy8IkSY1V7R7E7cC4zPS3ICRpD7HDk9QR8bcAmflT4LxtnptUw7okSQ22s6uYLmr1+Jptnju9g2uRJHUiOwuIaONx2bIkqRvZWUBkG4/LlrcSEd+MiJUR8WyrtgMj4oGIWFzcH7CL9UqS6mRnATEyIl6NiNeAEcXjTcs7mxd5OtsfhroaeDAzhwEPFsuSpE5oh1cxZWbP9r5wZj4SEUO2aT4beG/x+A5gNvA/27sNSVLtVDvVRkcZmJnLi8crgIFtdYyICRExJyLmNDc316c6SdJm9Q6IzTIz2cF5jMycmpmjM3N0//7961iZJAnqHxAvR8QggOJ+ZZ23L0mqUr0D4j7gkuLxJcDMOm9fklSlqifr21URcTeVE9IHR8Qy4HrgRuA7EfEx4EXgglptX6qVBZPeU3lw0Fe3NE47q3I/flbHbqxWrytVoWYBkZnj2njqfbXapiSp4zTsJLUkqXMzICRJpQwISVIpA0KSVMqAkCSVMiAkSaUMCElSqZp9D0LqMvwSmlTKPQhJUikDQpJUyoCQJJUyICRJpQwISVIpA0KSVMqAkCSVMiAkSaUMCElSKQNCklTKgJAklTIgJEmlDAhJUikDQpJUyoCQJJUyICRJpQwISVIpA0KSVMqAkCSVMiAkSaUMCElSqb0aXYDUFTzx3Zt5y+J7Adj45usA/I/ln2W/vXvCtH6wYl6l47SzuG7VK/xi35OAMe3f4JxpMH/GVq8LwFHnw+jx7X9daRcYEFIV3rL4Xg5d92t+t/fhm9v227snB79ln+36DmlZsvsbnD8DVszfum3TsgGhOjEgpCr9bu/DGX7tz1kw6T0ADL/251ue3PQJf/wslhbP77ZDjtryePysLduQ6sRzEJKkUgaEJKmUASFJKtWQcxARsRR4DdgArM/M0Y2oQ5LUtkaepD4pM//QwO1LknbAQ0ySpFKNCogE7o+IpyJiQlmHiJgQEXMiYk5zc3Ody5MkNSog3pOZo4AzgL+JiBO37ZCZUzNzdGaO7t+/f/0rlKQ9XEMCIjNfKu5XAvcCxzaiDklS2+oeEBHRJyL6bnoMnAo8W+86JEk71oirmAYC90bEpu3flZk/aUAdkqQdqHtAZOYSYGS9tytJ2jVe5ipJKmVASJJKGRCSpFIGhCSplAEhSSplQEiSShkQkqRS/ia1tIu2+i3qTcbPqs3GavW6UhXcg5AklTIgJEmlDAhJUikDQpJUyoCQJJUyICRJpQwISVIpA0KSVMqAkCSVMiAkSaUMCElSKQNCklTKgJAklTIgJEmlDAhJUikDQpJUyoCQJJUyICRJpQwISVIpA0KSVMqAkCSVMiAkSaUMCElSKQNCklTKgJAklTIgJEmlDAhJUikDQpJUqiEBERGnR8SvIuKFiLi6ETVIknas7gERET2B/wWcARwBjIuII+pdhyRpx/ZqwDaPBV7IzCUAEfFvwNnAwrZWeGP58yyY9J46lac9xZCWJSztNZQbbnt8c9t1q16ptG/z93boul/zu70Pr/q1D1336936m92V2qRaicys7wYjzgdOz8zLiuWPAMdl5pXb9JsATCgWjwSerWuh9XUw8IdGF1FD3Xl83Xls4Pi6undmZt/2rtyIPYiqZOZUYCpARMzJzNENLqlmHF/X1Z3HBo6vq4uIObuzfiNOUr8EHNpqeXDRJknqRBoREE8CwyLisIjYG7gIuK8BdUiSdqDuh5gyc31EXAn8FOgJfDMzF+xktam1r6yhHF/X1Z3HBo6vq9ut8dX9JLUkqWvwm9SSpFIGhCSpVKcLiIh4a0TMiIjnI+K5iBgTEQdGxAMRsbi4P6DRdbZHRLwzIua2ur0aEZ/pLuMDiIj/FhELIuLZiLg7InoXFyQ8UUytck9xcUKXFBFXFWNbEBGfKdq67PsXEd+MiJUR8WyrttLxRMXXi/dxXkSMalzl1WljfB8q3r+NETF6m/7XFOP7VUScVv+Kq9fG2L5a/N85LyLujYi3tnpul8fW6QIC+GfgJ5n5n4GRwHPA1cCDmTkMeLBY7nIy81eZ2ZSZTcC7gTXAvXST8UXE24BPA6Mz80gqFyFcBHwFmJyZfwn8CfhY46psv4g4EricymwAI4EPRMRf0rXfv+nA6du0tTWeM4BhxW0CMKVONe6O6Ww/vmeB84BHWjcWU/5cBAwv1vnfxdRAndV0th/bA8CRmTkCWARcA+0fW6cKiIjoB5wI3A6Qmesy889UpuK4o+h2B3BOI+rrYO8Dfp2ZL9K9xrcXsG9E7AXsBywHTgZmFM935fG9C3giM9dk5nrgYSr/0XTZ9y8zHwH+uE1zW+M5G/hWVvwSeGtEDKpLoe1UNr7MfC4zf1XS/Wzg3zJzbWb+BniByoeBTqmNsd1f/G0C/JLK98ygnWPrVAEBHAY0A9Mi4v9FxL9GRB9gYGYuL/qsAAY2rMKOcxFwd/G4W4wvM18CbgJ+SyUYXgGeAv7c6o92GfC2xlS4254FxkbEQRGxH3AmlS99dov3r5W2xvM24Het+nXl97JMdxvfR4EfF4/bNbbOFhB7AaOAKZl5NLCabXbXs3Jdbpe+Nrc4Bv9XwHe3fa4rj684Vn02laD/C6AP2+8Cd1mZ+RyVw2X3Az8B5gIbtunTZd+/Mt1tPHuKiPg8sB749u68TmcLiGXAssx8olieQSUwXt60K1vcr2xQfR3lDODpzHy5WO4u4zsF+E1mNmdmC/B94AQqhyI2fSmzS0+tkpm3Z+a7M/NEKudTFtF93r9N2hpPd58mp1uMLyIuBT4AfDi3fNGtXWPrVAGRmSuA30XEO4um91GZBvw+4JKi7RJgZgPK60jj2HJ4CbrP+H4LHB8R+0VEsOX9ewg4v+jTlcdHRAwo7t9O5fzDXXSf92+TtsZzH/Bfi6uZjgdeaXUoqju4D7goIvaJiMOonIz/jwbXtEsi4nTgb4G/ysw1rZ5q39gys1PdgCZgDjAP+AFwAHAQlaspFgP/Fziw0XXuxvj6AKuAfq3autP4JgLPUzlefyewDzC0+GN8gcphtX0aXedujO9RKqH3DPC+rv7+UfmgshxoobIH/7G2xgMElR/7+jUwn8rVag0fQzvGd27xeC3wMvDTVv0/X4zvV8AZja6/HWN7gcq5hrnF7dbdGZtTbUiSSnWqQ0ySpM7DgJAklTIgJEmlDAhJUikDQpJUyoDQHikizt1mZt25xeyeZzS6Nqmz8DJXCYiICcCHgZMyc2Oj65E6A/cgtMeLiP8EXAd8ZNtwiIghxfz60yNiUUR8OyJOiYhfFL+XcGzR79iIeLyYZPKxTbMBRMTwiPiPYg9lXkQMi4g+ETErIp4pflviwvqPWto59yC0R4uIXsDjwFcz856S54dQ+Xbq0cAC4Ekq36L+GJUJF8dn5jkRsT+wJjPXR8QpwCcz868j4hbgl5n57WKSxp5UZoE9PTMvL7bRLzNfqflgpV201867SN3a3wMLysKhld9k5nyAiFhA5cd0MiLmA0OKPv2AOyJiGJXZT3sV7Y8Dn4+IwcD3M3Nxsd7NEfEV4EeZ+WjHD0vafR5i0h4rIt4L/DVw5U66rm31eGOr5Y1s+ZD198BDWfklvQ8CvQEy8y4qexpvAP8eESdn5iIqsxTPB74UEdft9mCkGnAPQnuk4rcrpgEXZ+ZrHfCS/dgyffKlrbYzFFiSmV8vZoAdERHPA3/MzP8TEX8GLuuA7UsdzoDQnuoTwABgSmVm8s3+YSeHm9ryj1QOMf0dMKtV+wXARyKihcqvs00CjgG+GhEbqczE+cl2bE+qOU9SS5JKeQ5CklTKgJAklTIgJEmlDAhJUikDQpJUyoCQJJUyICRJpf4/y2fyPrKJTUkAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "\n", "hist.plot1d(result)" ] } ], "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.8" } }, "nbformat": 4, "nbformat_minor": 5 }