From 187a3286abfdf7eed4499ec810606ad8e7ddafcc Mon Sep 17 00:00:00 2001
From: Anthony Mahanna <43019056+aMahanna@users.noreply.github.com>
Date: Fri, 31 Dec 2021 14:02:59 -0500
Subject: [PATCH] new: blog post preparation
---
examples/ArangoDB_DGL_Adapter.ipynb | 285 ++--
.../outputs/ArangoDB_DGL_Adapter_output.ipynb | 1449 +++++++++++++++++
2 files changed, 1612 insertions(+), 122 deletions(-)
create mode 100644 examples/outputs/ArangoDB_DGL_Adapter_output.ipynb
diff --git a/examples/ArangoDB_DGL_Adapter.ipynb b/examples/ArangoDB_DGL_Adapter.ipynb
index 742b3e7..53eb7c5 100644
--- a/examples/ArangoDB_DGL_Adapter.ipynb
+++ b/examples/ArangoDB_DGL_Adapter.ipynb
@@ -34,7 +34,7 @@
"id": "bpvZS-1aeG89"
},
"source": [
- "Version: 1.0.0\n",
+ "Version: 1.0.2\n",
"\n",
"Objective: Export Graphs from [ArangoDB](https://www.arangodb.com/), a multi-model Graph Database, to [Deep Graph Library](https://www.dgl.ai/) (DGL), a python package for graph neural networks, and vice-versa."
]
@@ -58,10 +58,9 @@
"source": [
"%%capture\n",
"!git clone -b oasis_connector --single-branch https://github.com/arangodb/interactive_tutorials.git\n",
- "!git clone -b 1.0.1 --single-branch https://github.com/arangoml/dgl-adapter.git\n",
- "!rsync -av dgl-adapter/examples/ ./ --exclude=.git\n",
+ "!git clone -b 1.0.2 --single-branch https://github.com/arangoml/dgl-adapter.git\n",
"!rsync -av interactive_tutorials/ ./ --exclude=.git\n",
- "!pip3 install adbdgl_adapter==1.0.1\n",
+ "!pip3 install adbdgl_adapter==1.0.2\n",
"!pip3 install matplotlib\n",
"!pip3 install pyArango\n",
"!pip3 install networkx ## For drawing purposes "
@@ -75,7 +74,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "RpqvL4COeG8-",
- "outputId": "2df55e4e-03fa-47ed-c2c9-baf9f597e1d8"
+ "outputId": "4e453af8-33d9-4834-fb4e-250032695e01"
},
"outputs": [],
"source": [
@@ -129,7 +128,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "vf0350qvj8up",
- "outputId": "a65f00d2-cd6e-4583-94d8-2c9884e2e2e2"
+ "outputId": "eadc8e26-4edd-4859-e074-88db180dab84"
},
"outputs": [],
"source": [
@@ -162,7 +161,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "oOS3AVAnkQEV",
- "outputId": "4609cdef-25ce-4f00-94b5-482c76274f88"
+ "outputId": "96640c60-adfd-4785-98bb-26a38e40507b"
},
"outputs": [],
"source": [
@@ -198,7 +197,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "meLon-KgkU4h",
- "outputId": "976680a4-eadd-43f2-da17-e6a574fad8a7"
+ "outputId": "c9bcbd68-9c41-4f65-eab9-83d2c8069d0a"
},
"outputs": [],
"source": [
@@ -236,7 +235,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "zTebQ0LOlsGA",
- "outputId": "9c84cb84-f7ce-42b3-9174-01f38295c5dd"
+ "outputId": "3be2ec4d-bd08-4f11-b626-1312e931480b"
},
"outputs": [],
"source": [
@@ -279,7 +278,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "KsxNujb0mSqZ",
- "outputId": "3f3fd2b1-e1d3-4b03-c6c4-43566672cbb5"
+ "outputId": "716e7226-dca0-42ae-e469-df5c975f852f"
},
"outputs": [],
"source": [
@@ -322,7 +321,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "2ekGwnJDeG8-",
- "outputId": "92e9d288-0259-45cc-e73d-a8e9f629063a"
+ "outputId": "5cfdfdd4-ec46-4ec5-c69d-5864bb393077"
},
"outputs": [],
"source": [
@@ -330,16 +329,14 @@
"con = oasis.getTempCredentials()\n",
"\n",
"# Connect to the db via the python-arango driver\n",
- "python_arango_db_driver = oasis.connect_python_arango(con)\n",
+ "db = oasis.connect_python_arango(con)\n",
"\n",
- "# (Alternative) Connect to the db via the pyArango driver\n",
- "# pyarango_db_driver = oasis.connect(con)[con['dbName']]\n",
- "\n",
- "print()\n",
+ "print('\\n--------------------')\n",
"print(\"https://{}:{}\".format(con[\"hostname\"], con[\"port\"]))\n",
"print(\"Username: \" + con[\"username\"])\n",
"print(\"Password: \" + con[\"password\"])\n",
- "print(\"Database: \" + con[\"dbName\"])"
+ "print(\"Database: \" + con[\"dbName\"])\n",
+ "print('--------------------\\n')"
]
},
{
@@ -366,16 +363,7 @@
"id": "BM0iRYPDeG8_"
},
"source": [
- "We will use an Fraud Detection example graph, explained in more detail in this [interactive notebook](https://colab.research.google.com/github/joerg84/Graph_Powered_ML_Workshop/blob/master/Fraud_Detection.ipynb)."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "1jWclaDdeG8_"
- },
- "source": [
- "*Note the included arangorestore will only work on Linux system, if you want to run this notebook on a different OS please consider using the appropriate arangorestore from the [Download area](https://www.arangodb.com/download-major/).*"
+ "For demo purposes, we will be using the [ArangoDB Fraud Detection example graph](https://colab.research.google.com/github/joerg84/Graph_Powered_ML_Workshop/blob/master/Fraud_Detection.ipynb)."
]
},
{
@@ -388,76 +376,7 @@
"source": [
"%%capture\n",
"!chmod -R 755 ./tools\n",
- "!./tools/arangorestore -c none --server.endpoint http+ssl://{con[\"hostname\"]}:{con[\"port\"]} --server.username {con[\"username\"]} --server.database {con[\"dbName\"]} --server.password {con[\"password\"]} --replication-factor 3 --input-directory \"data/fraud_dump\""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "227hLXnPeG8_"
- },
- "source": [
- "# Create Graph"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "howeguvmeG8_"
- },
- "source": [
- "The graph we will be using in the following looks as follows:"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "WqRlqnJCeG8_"
- },
- "source": [
- "![networkX](https://github.com/arangoml/networkx-adapter/blob/master/examples/assets/fraud_graph.jpeg?raw=1) "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/"
- },
- "id": "PybHP7jpeG8_",
- "outputId": "ba3bfc7c-ef56-47e7-8e98-3763d4f34afe"
- },
- "outputs": [],
- "source": [
- "edge_definitions = [\n",
- " {\n",
- " \"edge_collection\": \"accountHolder\",\n",
- " \"from_vertex_collections\": [\"customer\"],\n",
- " \"to_vertex_collections\": [\"account\"],\n",
- " },\n",
- " {\n",
- " \"edge_collection\": \"transaction\",\n",
- " \"from_vertex_collections\": [\"account\"],\n",
- " \"to_vertex_collections\": [\"account\"],\n",
- " },\n",
- "]\n",
- "\n",
- "name = \"fraud-detection\"\n",
- "python_arango_db_driver.delete_graph(name, ignore_missing=True)\n",
- "fraud_graph = python_arango_db_driver.create_graph(name, edge_definitions=edge_definitions)\n",
- "\n",
- "print(\"Graph Setup done.\")\n",
- "print(fraud_graph)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "ANrsn9GreG9A"
- },
- "source": [
- "Feel free to visit the ArangoDB UI using the above link and login data and check the Graph!"
+ "!./tools/arangorestore -c none --server.endpoint http+ssl://{con[\"hostname\"]}:{con[\"port\"]} --server.username {con[\"username\"]} --server.database {con[\"dbName\"]} --server.password {con[\"password\"]} --replication-factor 3 --input-directory \"dgl-adapter/examples/data/fraud_dump\" --include-system-collections true"
]
},
{
@@ -466,7 +385,7 @@
"id": "QfE_tKxneG9A"
},
"source": [
- "# Create Adapter"
+ "# Instantiate the Adapter"
]
},
{
@@ -475,7 +394,7 @@
"id": "kGfhzPT9eG9A"
},
"source": [
- "Connect the ArangoDB_DGL_Adapter to our temp ArangoDB cluster:"
+ "Connect the ArangoDB-DGL Adapter to our temporary ArangoDB cluster:"
]
},
{
@@ -486,7 +405,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "oG496kBeeG9A",
- "outputId": "50ecbdf5-c82f-4540-d345-14eb4a488f2c"
+ "outputId": "235419bb-52cc-429c-f497-b79b361689cc"
},
"outputs": [],
"source": [
@@ -512,6 +431,22 @@
"## Via ArangoDB Graph"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "H8nlvWCryPW0"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Graph\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_graph_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L182-L197)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case must point to an existing ArangoDB graph in your ArangoDB instance. "
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -520,7 +455,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "zZ-Hu3lLVHgd",
- "outputId": "39f32c51-0753-45a8-a361-dcf46d4e6148"
+ "outputId": "02940624-1b41-488f-99fa-d081a065e1b2"
},
"outputs": [],
"source": [
@@ -535,6 +470,7 @@
"# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
"\n",
"# Show graph data\n",
+ "print('\\n--------------------')\n",
"print(dgl_g)\n",
"print(dgl_g.ntypes)\n",
"print(dgl_g.etypes)"
@@ -549,6 +485,23 @@
"## Via ArangoDB Collections"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bRcCmqWGy1Kf"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_collections_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L153-L180)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `vertex_collections` & `edge_collections` parameters must point to existing ArangoDB collections within your ArangoDB instance."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -557,7 +510,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "i4XOpdRLUNlJ",
- "outputId": "b58e75d1-e935-4abd-9bdb-bc8935d9cdc8"
+ "outputId": "1cdd6aed-a084-4da8-b342-ee650efe0ccd"
},
"outputs": [],
"source": [
@@ -573,6 +526,7 @@
"# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
"\n",
"# Show graph data\n",
+ "print('\\n--------------------')\n",
"print(dgl_g)\n",
"print(dgl_g.ntypes)\n",
"print(dgl_g.etypes)"
@@ -587,6 +541,23 @@
"## Via ArangoDB Metagraph"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PipFzJ0HzTMA"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L58-L151)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `metagraph` parameter should contain collections & associated document attributes names that exist within your ArangoDB instance."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -595,7 +566,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "7Kz8lXXq23Yk",
- "outputId": "1458aef6-14e5-48c0-98bf-77f21431bc73"
+ "outputId": "2ce0cff8-a4a5-4373-8297-17e313a16aae"
},
"outputs": [],
"source": [
@@ -638,6 +609,25 @@
"## Via ArangoDB Metagraph with a custom controller"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PGkGh_KjzlYM"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L58-L151)\n",
+ "* [`adbdgl_adapter.controller._adb_attribute_to_dgl_feature()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L21-L47)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `metagraph` parameter should contain collections & associated document attributes names that exist within your ArangoDB instance.\n",
+ "* We are creating a custom `ADBDGL_Controller` to specify *how* to convert our ArangoDB vertex/edge attributes into DGL node/edge features. View the default `ADBDGL_Controller` [here](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L11)."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -646,7 +636,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "U4_vSdU_4AS4",
- "outputId": "b719b0d4-c0a4-43a0-915f-8ee765e1ec86"
+ "outputId": "9d958954-ea94-4fa0-9778-255b2b02712e"
},
"outputs": [],
"source": [
@@ -764,16 +754,32 @@
"## Example 1: DGL Karate Graph"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "tx-tjPfx0U_h"
+ },
+ "source": [
+ "Data source\n",
+ "* [DGL Karate Graph](https://docs.dgl.ai/en/0.6.x/api/python/dgl.data.html#karate-club-dataset)\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.dgl_to_arangodb()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L199-L297)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your ArangoDB graph."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
- "height": 577
+ "height": 0
},
"id": "eRVbiBy4ZdE4",
- "outputId": "d44eb9d9-e046-443b-8ded-79654f004e02"
+ "outputId": "da33e345-87d7-442f-fbb7-5484b79889a5"
},
"outputs": [],
"source": [
@@ -783,7 +789,7 @@
"\n",
"# Create the ArangoDB graph\n",
"name = \"Karate\"\n",
- "python_arango_db_driver.delete_graph(name, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(name, drop_collections=True, ignore_missing=True)\n",
"adb_karate_graph = adbdgl_adapter.dgl_to_arangodb(name, dgl_karate_graph)\n",
"\n",
"print('\\n--------------------')\n",
@@ -792,8 +798,8 @@
"print(\"Password: \" + con[\"password\"])\n",
"print(\"Database: \" + con[\"dbName\"])\n",
"print('--------------------\\n')\n",
- "print(f\"\\nInspect the graph here: https://tutorials.arangodb.cloud:8529/_db/{con['dbName']}/_admin/aardvark/index.html#graph/{name}\\n\")\n",
- "print(f\"\\nView the original graph below:\")"
+ "print(f\"Inspect the graph here: https://tutorials.arangodb.cloud:8529/_db/{con['dbName']}/_admin/aardvark/index.html#graph/{name}\\n\")\n",
+ "print(f\"View the original graph below:\")"
]
},
{
@@ -806,16 +812,32 @@
"## Example 2: DGL MiniGCDataset Graphs"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "KaExiE2x0-M6"
+ },
+ "source": [
+ "Data source\n",
+ "* [DGL Mini Graph Classification Dataset](https://docs.dgl.ai/en/0.6.x/api/python/dgl.data.html#mini-graph-classification-dataset)\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.dgl_to_arangodb()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L199-L297)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameters in this case are simply for naming your ArangoDB graph."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
- "height": 1000
+ "height": 0
},
"id": "dADiexlAioGH",
- "outputId": "273988c8-1749-4fe0-85fe-51b0e1ab2058"
+ "outputId": "f7926863-61bb-4202-ba7e-e55413d20ca5"
},
"outputs": [],
"source": [
@@ -837,9 +859,9 @@
"hypercube = \"Hypercube\"\n",
"clique = \"Clique\"\n",
"\n",
- "python_arango_db_driver.delete_graph(lollipop, drop_collections=True, ignore_missing=True)\n",
- "python_arango_db_driver.delete_graph(hypercube, drop_collections=True, ignore_missing=True)\n",
- "python_arango_db_driver.delete_graph(clique, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(lollipop, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(hypercube, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(clique, drop_collections=True, ignore_missing=True)\n",
"\n",
"adb_lollipop_graph = adbdgl_adapter.dgl_to_arangodb(lollipop, dgl_lollipop_graph)\n",
"adb_hypercube_graph = adbdgl_adapter.dgl_to_arangodb(hypercube, dgl_hypercube_graph)\n",
@@ -868,6 +890,24 @@
"## Example 3: DGL MiniGCDataset Graphs with a custom controller"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CZ1UX9YX1Zzo"
+ },
+ "source": [
+ "Data source\n",
+ "* [DGL Mini Graph Classification Dataset](https://docs.dgl.ai/en/0.6.x/api/python/dgl.data.html#mini-graph-classification-dataset)\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.dgl_to_arangodb()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L199-L297)\n",
+ "* [`adbdgl_adapter.controller._dgl_feature_to_adb_attribute()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L49-L70)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameters in this case are simply for naming your ArangoDB graph.\n",
+ "* We are creating a custom `ADBDGL_Controller` to specify *how* to convert our DGL node/edge features into ArangoDB vertex/edge attributes. View the default `ADBDGL_Controller` [here](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L11)."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -876,7 +916,7 @@
"base_uri": "https://localhost:8080/"
},
"id": "jbJsvMMaoJoT",
- "outputId": "2ddca41f-9c8b-4db4-c0aa-c1b2cc124fa5"
+ "outputId": "7cc01a20-201b-42ea-85e7-c60d0d8d2fbb"
},
"outputs": [],
"source": [
@@ -961,9 +1001,9 @@
"hypercube = \"Hypercube_With_Attributes\"\n",
"clique = \"Clique_With_Attributes\"\n",
"\n",
- "python_arango_db_driver.delete_graph(lollipop, drop_collections=True, ignore_missing=True)\n",
- "python_arango_db_driver.delete_graph(hypercube, drop_collections=True, ignore_missing=True)\n",
- "python_arango_db_driver.delete_graph(clique, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(lollipop, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(hypercube, drop_collections=True, ignore_missing=True)\n",
+ "db.delete_graph(clique, drop_collections=True, ignore_missing=True)\n",
"\n",
"adb_lollipop_graph = adbdgl_adapter.dgl_to_arangodb(lollipop, dgl_lollipop_graph)\n",
"adb_hypercube_graph = adbdgl_adapter.dgl_to_arangodb(hypercube, dgl_hypercube_graph)\n",
@@ -986,15 +1026,16 @@
"colab": {
"collapsed_sections": [
"ot1oJqn7m78n",
- "7y81WHO8eG8_",
- "227hLXnPeG8_",
"QfE_tKxneG9A",
"ZrEDmtqCVD0W",
+ "RQ4CknYfUEuz",
"qEH6OdSB23Ya",
+ "DqIKT1lO4ASw",
"UafSB_3JZNwK",
- "gshTlSX_ZZsS"
+ "gshTlSX_ZZsS",
+ "CNj1xKhwoJoL"
],
- "name": "ArangoDB_DGL_Adapter_v1.0.0.ipynb",
+ "name": "ArangoDB_DGL_Adapter_v1.ipynb",
"provenance": []
},
"kernelspec": {
diff --git a/examples/outputs/ArangoDB_DGL_Adapter_output.ipynb b/examples/outputs/ArangoDB_DGL_Adapter_output.ipynb
new file mode 100644
index 0000000..b6ea068
--- /dev/null
+++ b/examples/outputs/ArangoDB_DGL_Adapter_output.ipynb
@@ -0,0 +1,1449 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "q8KesL7xeG89"
+ },
+ "source": [
+ "# ArangoDB DGL Adapter Getting Started Guide "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "U1d45V4OeG89"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Nx9aa3LAeG89"
+ },
+ "source": [
+ "![arangodb](https://raw.githubusercontent.com/arangoml/dgl-adapter/master/examples/assets/adb_logo.png)\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bpvZS-1aeG89"
+ },
+ "source": [
+ "Version: 1.0.2\n",
+ "\n",
+ "Objective: Export Graphs from [ArangoDB](https://www.arangodb.com/), a multi-model Graph Database, to [Deep Graph Library](https://www.dgl.ai/) (DGL), a python package for graph neural networks, and vice-versa."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "KS9c-vE5eG89"
+ },
+ "source": [
+ "# Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "id": "fUnFAFAheG89"
+ },
+ "outputs": [],
+ "source": [
+ "%%capture\n",
+ "!git clone -b oasis_connector --single-branch https://github.com/arangodb/interactive_tutorials.git\n",
+ "!git clone -b 1.0.2 --single-branch https://github.com/arangoml/dgl-adapter.git\n",
+ "!rsync -av interactive_tutorials/ ./ --exclude=.git\n",
+ "!pip3 install adbdgl_adapter==1.0.2\n",
+ "!pip3 install matplotlib\n",
+ "!pip3 install pyArango\n",
+ "!pip3 install networkx ## For drawing purposes "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "RpqvL4COeG8-",
+ "outputId": "4e453af8-33d9-4834-fb4e-250032695e01"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "DGL backend not selected or invalid. Assuming PyTorch for now.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Setting the default backend to \"pytorch\". You can change it in the ~/.dgl/config.json file or export the DGLBACKEND environment variable. Valid options are: pytorch, mxnet, tensorflow (all lowercase)\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Using backend: pytorch\n"
+ ]
+ }
+ ],
+ "source": [
+ "import json\n",
+ "import oasis\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "import dgl\n",
+ "import torch\n",
+ "import networkx as nx\n",
+ "\n",
+ "from dgl import remove_self_loop\n",
+ "from dgl.data import KarateClubDataset\n",
+ "from dgl.data import MiniGCDataset\n",
+ "\n",
+ "from adbdgl_adapter.adapter import ADBDGL_Adapter\n",
+ "from adbdgl_adapter.controller import ADBDGL_Controller\n",
+ "from adbdgl_adapter.typings import Json, ArangoMetagraph, DGLCanonicalEType, DGLDataDict"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ot1oJqn7m78n"
+ },
+ "source": [
+ "# Understanding DGL"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "N6Bx3hTjnGd7"
+ },
+ "source": [
+ "(referenced from [docs.dgl.ai](https://docs.dgl.ai/en/0.6.x/))\n",
+ "\n",
+ "\n",
+ "Deep Graph Library (DGL) is a Python package built for easy implementation of graph neural network model family, on top of existing DL frameworks (currently supporting **PyTorch**, **MXNet** and **TensorFlow**).\n",
+ "\n",
+ "DGL represents a directed graph as a `DGLGraph` object. You can construct a graph by specifying the number of nodes in the graph as well as the list of source and destination nodes. **Nodes in the graph have consecutive IDs starting from 0.**\n",
+ "\n",
+ "The following code constructs a directed \"star\" homogeneous graph with 6 nodes and 5 edges. \n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "vf0350qvj8up",
+ "outputId": "eadc8e26-4edd-4859-e074-88db180dab84"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Graph(num_nodes=6, num_edges=5,\n",
+ " ndata_schemes={}\n",
+ " edata_schemes={})\n",
+ "\n",
+ "Canonical Edge Types: [('_N', '_E', '_N')]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# A homogeneous graph with 6 nodes, and 5 edges\n",
+ "g = dgl.graph(([0, 0, 0, 0, 0], [1, 2, 3, 4, 5]))\n",
+ "print(g)\n",
+ "\n",
+ "# Print the graph's canonical edge types\n",
+ "print(\"\\nCanonical Edge Types: \", g.canonical_etypes)\n",
+ "# [('_N', '_E', '_N')]\n",
+ "# '_N' being the only Node type\n",
+ "# '_E' being the only Edge type"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wvJ0506mkMjz"
+ },
+ "source": [
+ "\n",
+ "In DGL, a heterogeneous graph (heterograph for short) is specified with a series of graphs as below, one per relation. Each relation is a string triplet `(source node type, edge type, destination node type)`. Since relations disambiguate the edge types, DGL calls them canonical edge types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "oOS3AVAnkQEV",
+ "outputId": "96640c60-adfd-4785-98bb-26a38e40507b"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Graph(num_nodes={'game': 4, 'user': 4},\n",
+ " num_edges={('user', 'follows', 'game'): 3, ('user', 'follows', 'user'): 2, ('user', 'plays', 'game'): 2},\n",
+ " metagraph=[('user', 'game', 'follows'), ('user', 'game', 'plays'), ('user', 'user', 'follows')])\n",
+ "\n",
+ "Canonical Edge Types: [('user', 'follows', 'game'), ('user', 'follows', 'user'), ('user', 'plays', 'game')]\n",
+ "\n",
+ "Node Types: ['game', 'user']\n",
+ "\n",
+ "Edge Types: ['follows', 'follows', 'plays']\n"
+ ]
+ }
+ ],
+ "source": [
+ "# A heterogeneous graph with 8 nodes, and 7 edges\n",
+ "g = dgl.heterograph({\n",
+ " ('user', 'follows', 'user'): (torch.tensor([0, 1]), torch.tensor([1, 2])),\n",
+ " ('user', 'follows', 'game'): (torch.tensor([0, 1, 2]), torch.tensor([1, 2, 3])),\n",
+ " ('user', 'plays', 'game'): (torch.tensor([1, 3]), torch.tensor([2, 3]))\n",
+ "})\n",
+ "\n",
+ "print(g)\n",
+ "print(\"\\nCanonical Edge Types: \", g.canonical_etypes)\n",
+ "print(\"\\nNode Types: \", g.ntypes)\n",
+ "print(\"\\nEdge Types: \", g.etypes)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "5C-R0tkkkS5N"
+ },
+ "source": [
+ "Many graph data contain attributes on nodes and edges. Although the types of node and edge attributes can be arbitrary in real world, **DGLGraph only accepts attributes stored in tensors** (with numerical contents). Consequently, an attribute of all the nodes or edges must have the same shape. In the context of deep learning, those attributes are often called features.\n",
+ "\n",
+ "You can assign and retrieve node and edge features via ndata and edata interface."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "meLon-KgkU4h",
+ "outputId": "c9bcbd68-9c41-4f65-eab9-83d2c8069d0a"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Graph(num_nodes=6, num_edges=5,\n",
+ " ndata_schemes={'x': Scheme(shape=(), dtype=torch.int64)}\n",
+ " edata_schemes={'a': Scheme(shape=(4,), dtype=torch.float32)})\n",
+ "\n",
+ "Node Data X attribute: tensor([151, 124, 41, 89, 76, 55])\n",
+ "\n",
+ "Edge Data A attribute: tensor([[-0.9712, 0.3131, -1.7787, -0.4953],\n",
+ " [ 1.5366, -0.8591, -1.4719, 0.5857],\n",
+ " [-0.5803, 0.6757, 0.9276, -0.9756],\n",
+ " [ 0.4396, 1.0612, 0.0943, 0.6856],\n",
+ " [-0.8685, -1.3693, -0.1184, -1.0903]])\n"
+ ]
+ }
+ ],
+ "source": [
+ "# A homogeneous graph with 6 nodes, and 5 edges\n",
+ "g = dgl.graph(([0, 0, 0, 0, 0], [1, 2, 3, 4, 5]))\n",
+ "\n",
+ "# Assign an integer value for each node.\n",
+ "g.ndata['x'] = torch.tensor([151, 124, 41, 89, 76, 55])\n",
+ "# Assign a 4-dimensional edge feature vector for each edge.\n",
+ "g.edata['a'] = torch.randn(5, 4)\n",
+ "\n",
+ "print(g)\n",
+ "print(\"\\nNode Data X attribute: \", g.ndata['x'])\n",
+ "print(\"\\nEdge Data A attribute: \", g.edata['a'])\n",
+ "\n",
+ "\n",
+ "# NOTE: The following line ndata insertion will fail, since not all nodes have been assigned an attribute value\n",
+ "# g.ndata['bad_attribute'] = torch.tensor([0,10,20,30,40])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ETRCZzF1lSLE"
+ },
+ "source": [
+ "When multiple node/edge types are introduced, users need to specify the particular node/edge type when invoking a DGLGraph API for type-specific information. In addition, nodes/edges of different types have separate IDs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "zTebQ0LOlsGA",
+ "outputId": "3be2ec4d-bd08-4f11-b626-1312e931480b"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "All nodes: 8\n",
+ "User nodes: 4\n",
+ "tensor([0, 1, 2, 3])\n"
+ ]
+ }
+ ],
+ "source": [
+ "g = dgl.heterograph({\n",
+ " ('user', 'follows', 'user'): (torch.tensor([0, 1]), torch.tensor([1, 2])),\n",
+ " ('user', 'follows', 'game'): (torch.tensor([0, 1, 2]), torch.tensor([1, 2, 3])),\n",
+ " ('user', 'plays', 'game'): (torch.tensor([1, 3]), torch.tensor([2, 3]))\n",
+ "})\n",
+ "\n",
+ "# Get the number of all nodes in the graph\n",
+ "print(\"All nodes: \", g.num_nodes())\n",
+ "\n",
+ "# Get the number of user nodes\n",
+ "print(\"User nodes: \", g.num_nodes('user'))\n",
+ "\n",
+ "# Nodes of different types have separate IDs,\n",
+ "# hence not well-defined without a type specified\n",
+ "# print(g.nodes())\n",
+ "#DGLError: Node type name must be specified if there are more than one node types.\n",
+ "\n",
+ "print(g.nodes('user'))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "H9dwJuKvmK0w"
+ },
+ "source": [
+ "To set/get features for a specific node/edge type, DGL provides two new types of syntax – g.nodes[‘node_type’].data[‘feat_name’] and g.edges[‘edge_type’].data[‘feat_name’].\n",
+ "\n",
+ "**Note:** If the graph only has one node/edge type, there is no need to specify the node/edge type."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "KsxNujb0mSqZ",
+ "outputId": "716e7226-dca0-42ae-e469-df5c975f852f"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "defaultdict(, {'age': {'user': tensor([21, 16, 38, 64])}})\n"
+ ]
+ }
+ ],
+ "source": [
+ "g = dgl.heterograph({\n",
+ " ('user', 'follows', 'user'): (torch.tensor([0, 1]), torch.tensor([1, 2])),\n",
+ " ('user', 'follows', 'game'): (torch.tensor([0, 1, 2]), torch.tensor([1, 2, 3])),\n",
+ " ('user', 'plays', 'game'): (torch.tensor([1, 3]), torch.tensor([2, 3]))\n",
+ "})\n",
+ "\n",
+ "g.nodes['user'].data['age'] = torch.tensor([21, 16, 38, 64])\n",
+ "# An alternative (yet equivalent) syntax:\n",
+ "# g.ndata['age'] = {'user': torch.tensor([21, 16, 38, 64])}\n",
+ "\n",
+ "print(g.ndata)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "1M_isKWLnCfr"
+ },
+ "source": [
+ "For more info, visit https://docs.dgl.ai/en/0.6.x/. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Oc__NAd1eG8-"
+ },
+ "source": [
+ "# Create a Temporary ArangoDB Instance"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "2ekGwnJDeG8-",
+ "outputId": "5cfdfdd4-ec46-4ec5-c69d-5864bb393077"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Requesting new temp credentials.\n",
+ "Temp database ready to use.\n",
+ "\n",
+ "--------------------\n",
+ "https://tutorials.arangodb.cloud:8529\n",
+ "Username: TUT487i8kal98gb73c2iklds\n",
+ "Password: TUTn5t85w8t50kcupmo2mmyb\n",
+ "Database: TUTn187e39v9qho3768ilyk4\n",
+ "--------------------\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Request temporary instance from the managed ArangoDB Cloud Oasis.\n",
+ "con = oasis.getTempCredentials()\n",
+ "\n",
+ "# Connect to the db via the python-arango driver\n",
+ "db = oasis.connect_python_arango(con)\n",
+ "\n",
+ "print('\\n--------------------')\n",
+ "print(\"https://{}:{}\".format(con[\"hostname\"], con[\"port\"]))\n",
+ "print(\"Username: \" + con[\"username\"])\n",
+ "print(\"Password: \" + con[\"password\"])\n",
+ "print(\"Database: \" + con[\"dbName\"])\n",
+ "print('--------------------\\n')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "e4QfL37neG8_"
+ },
+ "source": [
+ "Feel free to use to above URL to checkout the UI!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "7y81WHO8eG8_"
+ },
+ "source": [
+ "# Data Import"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "BM0iRYPDeG8_"
+ },
+ "source": [
+ "For demo purposes, we will be using the [ArangoDB Fraud Detection example graph](https://colab.research.google.com/github/joerg84/Graph_Powered_ML_Workshop/blob/master/Fraud_Detection.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "id": "7bgGJ3QkeG8_"
+ },
+ "outputs": [],
+ "source": [
+ "%%capture\n",
+ "!chmod -R 755 ./tools\n",
+ "!./tools/arangorestore -c none --server.endpoint http+ssl://{con[\"hostname\"]}:{con[\"port\"]} --server.username {con[\"username\"]} --server.database {con[\"dbName\"]} --server.password {con[\"password\"]} --replication-factor 3 --input-directory \"dgl-adapter/examples/data/fraud_dump\" --include-system-collections true"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QfE_tKxneG9A"
+ },
+ "source": [
+ "# Instantiate the Adapter"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "kGfhzPT9eG9A"
+ },
+ "source": [
+ "Connect the ArangoDB-DGL Adapter to our temporary ArangoDB cluster:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "oG496kBeeG9A",
+ "outputId": "235419bb-52cc-429c-f497-b79b361689cc"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Connecting to https://tutorials.arangodb.cloud:8529\n"
+ ]
+ }
+ ],
+ "source": [
+ "adbdgl_adapter = ADBDGL_Adapter(con)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "uByvwf9feG9A"
+ },
+ "source": [
+ "# ArangoDB to DGL\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ZrEDmtqCVD0W"
+ },
+ "source": [
+ "## Via ArangoDB Graph"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "H8nlvWCryPW0"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Graph\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_graph_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L182-L197)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case must point to an existing ArangoDB graph in your ArangoDB instance. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "zZ-Hu3lLVHgd",
+ "outputId": "02940624-1b41-488f-99fa-d081a065e1b2"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "DGL: fraud-detection created\n",
+ "\n",
+ "--------------------\n",
+ "Graph(num_nodes={'account': 54, 'customer': 17},\n",
+ " num_edges={('account', 'accountHolder', 'customer'): 54, ('account', 'transaction', 'account'): 62},\n",
+ " metagraph=[('account', 'customer', 'accountHolder'), ('account', 'account', 'transaction')])\n",
+ "['account', 'customer']\n",
+ "['accountHolder', 'transaction']\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Define graph name\n",
+ "graph_name = \"fraud-detection\"\n",
+ "\n",
+ "# Create DGL graph from ArangoDB graph\n",
+ "dgl_g = adbdgl_adapter.arangodb_graph_to_dgl(graph_name)\n",
+ "\n",
+ "# You can also provide valid Python-Arango AQL query options to the command above, like such:\n",
+ "# dgl_g = aadbdgl_adapter.arangodb_graph_to_dgl(graph_name, ttl=1000, stream=True)\n",
+ "# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
+ "\n",
+ "# Show graph data\n",
+ "print('\\n--------------------')\n",
+ "print(dgl_g)\n",
+ "print(dgl_g.ntypes)\n",
+ "print(dgl_g.etypes)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "RQ4CknYfUEuz"
+ },
+ "source": [
+ "## Via ArangoDB Collections"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bRcCmqWGy1Kf"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_collections_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L153-L180)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `vertex_collections` & `edge_collections` parameters must point to existing ArangoDB collections within your ArangoDB instance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "i4XOpdRLUNlJ",
+ "outputId": "1cdd6aed-a084-4da8-b342-ee650efe0ccd"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "DGL: fraud-detection created\n",
+ "\n",
+ "--------------------\n",
+ "Graph(num_nodes={'Class': 4, 'account': 54, 'customer': 17},\n",
+ " num_edges={('Class', 'Relationship', 'Class'): 4, ('account', 'accountHolder', 'customer'): 54, ('account', 'transaction', 'account'): 62},\n",
+ " metagraph=[('Class', 'Class', 'Relationship'), ('account', 'customer', 'accountHolder'), ('account', 'account', 'transaction')])\n",
+ "['Class', 'account', 'customer']\n",
+ "['Relationship', 'accountHolder', 'transaction']\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Define collection\n",
+ "vertex_collections = {\"account\", \"Class\", \"customer\"}\n",
+ "edge_collections = {\"accountHolder\", \"Relationship\", \"transaction\"}\n",
+ "\n",
+ "# Create DGL from ArangoDB collections\n",
+ "dgl_g = adbdgl_adapter.arangodb_collections_to_dgl(\"fraud-detection\", vertex_collections, edge_collections)\n",
+ "\n",
+ "# You can also provide valid Python-Arango AQL query options to the command above, like such:\n",
+ "# dgl_g = adbdgl_adapter.arangodb_collections_to_dgl(\"fraud-detection\", vertex_collections, edge_collections, ttl=1000, stream=True)\n",
+ "# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
+ "\n",
+ "# Show graph data\n",
+ "print('\\n--------------------')\n",
+ "print(dgl_g)\n",
+ "print(dgl_g.ntypes)\n",
+ "print(dgl_g.etypes)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "qEH6OdSB23Ya"
+ },
+ "source": [
+ "## Via ArangoDB Metagraph"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PipFzJ0HzTMA"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L58-L151)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `metagraph` parameter should contain collections & associated document attributes names that exist within your ArangoDB instance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "7Kz8lXXq23Yk",
+ "outputId": "2ce0cff8-a4a5-4373-8297-17e313a16aae"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "DGL: FraudDetection created\n",
+ "\n",
+ "--------------\n",
+ "Graph(num_nodes={'Class': 4, 'account': 54, 'customer': 17},\n",
+ " num_edges={('Class', 'Relationship', 'Class'): 4, ('account', 'accountHolder', 'customer'): 54, ('account', 'transaction', 'account'): 62},\n",
+ " metagraph=[('Class', 'Class', 'Relationship'), ('account', 'customer', 'accountHolder'), ('account', 'account', 'transaction')])\n",
+ "\n",
+ "--------------\n",
+ "defaultdict(, {'concrete': {'Class': tensor([True, True, True, True])}, 'Balance': {'account': tensor([5331, 7630, 1433, 2201, 4837, 5817, 1689, 1042, 4104, 10, 2338, 10,\n",
+ " 3779, 0, 529, 0, 1992, 2912, 6367, 1819, 0, 221, 5062, 2372,\n",
+ " 841, 5393, 1138, 8414, 4064, 5686, 6294, 6540, 7358, 3452, 0, 3993,\n",
+ " 10, 0, 471, 8148, 5832, 1758, 1747, 1679, 6789, 1599, 8320, 0,\n",
+ " 10, 8626, 7199, 8644, 3879, 10])}, 'customer_id': {'account': tensor([10000009, 10000004, 10000004, 10000010, 10000002, 10000011, 10000015,\n",
+ " 10000006, 10000010, 10810, 10000002, 10000014, 10000008, 0,\n",
+ " 10000002, 0, 10000008, 10000006, 10000012, 10000015, 10000001,\n",
+ " 10000010, 10000015, 10000005, 10000009, 10000008, 10000011, 10000014,\n",
+ " 10000010, 10000006, 10000002, 10000007, 10000006, 10000005, 0,\n",
+ " 10000010, 10810, 0, 10000009, 10000006, 10000002, 10000005,\n",
+ " 10000009, 10000012, 10000007, 10000002, 10000014, 0, 10810,\n",
+ " 10000016, 10000006, 10000016, 10000013, 10810])}, 'rank': {'account': tensor([0.0021, 0.0031, 0.0052, 0.0021, 0.0046, 0.0037, 0.0032, 0.0042, 0.0021,\n",
+ " 0.0021, 0.0030, 0.0037, 0.0040, 0.0037, 0.0021, 0.0046, 0.0040, 0.0030,\n",
+ " 0.0026, 0.0032, 0.0021, 0.0034, 0.0032, 0.0021, 0.0021, 0.0035, 0.0026,\n",
+ " 0.0026, 0.0046, 0.0021, 0.0021, 0.0035, 0.0036, 0.0036, 0.0038, 0.0055,\n",
+ " 0.0021, 0.0041, 0.0044, 0.0021, 0.0030, 0.0035, 0.0033, 0.0026, 0.0071,\n",
+ " 0.0036, 0.0032, 0.0059, 0.0021, 0.0090, 0.0057, 0.0032, 0.0026, 0.0021]), 'customer': tensor([0.0135, 0.0050, 0.0062, 0.0066, 0.0096, 0.0088, 0.0089, 0.0047, 0.0066,\n",
+ " 0.0045, 0.0062, 0.0103, 0.0081, 0.0039, 0.0054, 0.0044, 0.0093])}})\n",
+ "--------------\n",
+ "\n",
+ "defaultdict(, {'sender_bank_id': {('account', 'transaction', 'account'): tensor([10000000003, 10000000002, 10000000001, 10000000001, 10000000002,\n",
+ " 10000000003, 10000000003, 10000000002, 10000000002, 10000000003,\n",
+ " 10000000001, 10000000001, 0, 10000000003, 10000000003,\n",
+ " 0, 10000000002, 0, 10000000001, 10000000003,\n",
+ " 10000000001, 10000000003, 10000000002, 0, 10000000003,\n",
+ " 10000000003, 10000000003, 10000000003, 10000000001, 10000000001,\n",
+ " 10000000002, 10000000001, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000001, 0, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000002, 10000000003, 10000000003, 10000000003, 10000000002,\n",
+ " 10000000003, 10000000002, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000001, 0, 10000000003, 10000000003, 0,\n",
+ " 10000000003, 10000000003, 10000000001, 10000000001, 10000000003,\n",
+ " 10000000003, 10000000002])}, 'receiver_bank_id': {('account', 'transaction', 'account'): tensor([10000000003, 10000000003, 10000000001, 10000000002, 10000000002,\n",
+ " 10000000003, 10000000001, 10000000003, 10000000001, 10000000003,\n",
+ " 10000000002, 10000000003, 0, 10000000003, 10000000003,\n",
+ " 0, 10000000001, 0, 10000000002, 10000000003,\n",
+ " 10000000003, 10000000003, 10000000001, 0, 10000000003,\n",
+ " 10000000002, 10000000003, 10000000003, 10000000001, 10000000001,\n",
+ " 10000000003, 10000000003, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000002, 0, 10000000001, 10000000001, 10000000002,\n",
+ " 10000000001, 10000000003, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000003, 10000000002, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000003, 0, 10000000003, 10000000003, 0,\n",
+ " 10000000003, 10000000002, 10000000002, 10000000001, 10000000003,\n",
+ " 10000000003, 10000000003])}, 'transaction_amt': {('account', 'transaction', 'account'): tensor([9000, 299, 498, 954, 756, 627, 142, 946, 920, 9000, 421, 343,\n",
+ " 9000, 457, 9000, 9000, 53, 9000, 284, 120, 441, 9000, 364, 901,\n",
+ " 9000, 279, 9000, 9000, 273, 127, 952, 354, 795, 9000, 835, 761,\n",
+ " 9000, 478, 172, 804, 665, 995, 9000, 9000, 670, 9000, 340, 9000,\n",
+ " 747, 347, 52, 911, 762, 9000, 0, 790, 619, 491, 954, 9000,\n",
+ " 9000, 843])}})\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Define Metagraph\n",
+ "fraud_detection_metagraph = {\n",
+ " \"vertexCollections\": {\n",
+ " \"account\": {\"rank\", \"Balance\", \"customer_id\"},\n",
+ " \"Class\": {\"concrete\"},\n",
+ " \"customer\": {\"rank\"},\n",
+ " },\n",
+ " \"edgeCollections\": {\n",
+ " \"accountHolder\": {},\n",
+ " \"Relationship\": {},\n",
+ " \"transaction\": {\"receiver_bank_id\", \"sender_bank_id\", \"transaction_amt\"},\n",
+ " },\n",
+ "}\n",
+ "\n",
+ "# Create DGL Graph from attributes\n",
+ "dgl_g = adbdgl_adapter.arangodb_to_dgl('FraudDetection', fraud_detection_metagraph)\n",
+ "\n",
+ "# You can also provide valid Python-Arango AQL query options to the command above, like such:\n",
+ "# dgl_g = adbdgl_adapter.arangodb_to_dgl(graph_name = 'FraudDetection', fraud_detection_metagraph, ttl=1000, stream=True)\n",
+ "# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
+ "\n",
+ "# Show graph data\n",
+ "print('\\n--------------')\n",
+ "print(dgl_g)\n",
+ "print('\\n--------------')\n",
+ "print(dgl_g.ndata)\n",
+ "print('--------------\\n')\n",
+ "print(dgl_g.edata)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "DqIKT1lO4ASw"
+ },
+ "source": [
+ "## Via ArangoDB Metagraph with a custom controller"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PGkGh_KjzlYM"
+ },
+ "source": [
+ "Data source\n",
+ "* ArangoDB Fraud-Detection Collections\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.arangodb_to_dgl()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L58-L151)\n",
+ "* [`adbdgl_adapter.controller._adb_attribute_to_dgl_feature()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L21-L47)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your DGL graph.\n",
+ "* The `metagraph` parameter should contain collections & associated document attributes names that exist within your ArangoDB instance.\n",
+ "* We are creating a custom `ADBDGL_Controller` to specify *how* to convert our ArangoDB vertex/edge attributes into DGL node/edge features. View the default `ADBDGL_Controller` [here](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/controller.py#L11)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "U4_vSdU_4AS4",
+ "outputId": "9d958954-ea94-4fa0-9778-255b2b02712e"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Connecting to https://tutorials.arangodb.cloud:8529\n",
+ "DGL: FraudDetection created\n",
+ "\n",
+ "--------------\n",
+ "Graph(num_nodes={'Class': 4, 'account': 54, 'customer': 17},\n",
+ " num_edges={('Class', 'Relationship', 'Class'): 4, ('account', 'accountHolder', 'customer'): 54, ('account', 'transaction', 'account'): 62},\n",
+ " metagraph=[('Class', 'Class', 'Relationship'), ('account', 'customer', 'accountHolder'), ('account', 'account', 'transaction')])\n",
+ "\n",
+ "--------------\n",
+ "defaultdict(, {'concrete': {'Class': tensor([True, True, True, True])}, 'name': {'Class': tensor([0, 1, 2, 3])}, 'rank': {'account': tensor([0.0021, 0.0031, 0.0052, 0.0021, 0.0046, 0.0037, 0.0032, 0.0042, 0.0021,\n",
+ " 0.0021, 0.0030, 0.0037, 0.0040, 0.0037, 0.0021, 0.0046, 0.0040, 0.0030,\n",
+ " 0.0026, 0.0032, 0.0021, 0.0034, 0.0032, 0.0021, 0.0021, 0.0035, 0.0026,\n",
+ " 0.0026, 0.0046, 0.0021, 0.0021, 0.0035, 0.0036, 0.0036, 0.0038, 0.0055,\n",
+ " 0.0021, 0.0041, 0.0044, 0.0021, 0.0030, 0.0035, 0.0033, 0.0026, 0.0071,\n",
+ " 0.0036, 0.0032, 0.0059, 0.0021, 0.0090, 0.0057, 0.0032, 0.0026, 0.0021]), 'customer': tensor([0.0135, 0.0050, 0.0062, 0.0066, 0.0096, 0.0088, 0.0089, 0.0047, 0.0066,\n",
+ " 0.0045, 0.0062, 0.0103, 0.0081, 0.0039, 0.0054, 0.0044, 0.0093])}, 'Ssn': {'customer': tensor([123456786, 123456780, 123456780, 123456787, 123456780, 123456789,\n",
+ " 123456780, 123456785, 123456783, 123456784, 123456780, 123456788,\n",
+ " 123456782, 123456781, 123456780, 123456780, 111223333])}, 'Sex': {'customer': tensor([1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1])}})\n",
+ "--------------\n",
+ "\n",
+ "defaultdict(, {'sender_bank_id': {('account', 'transaction', 'account'): tensor([10000000003, 10000000002, 10000000001, 10000000001, 10000000002,\n",
+ " 10000000003, 10000000003, 10000000002, 10000000002, 10000000003,\n",
+ " 10000000001, 10000000001, 0, 10000000003, 10000000003,\n",
+ " 0, 10000000002, 0, 10000000001, 10000000003,\n",
+ " 10000000001, 10000000003, 10000000002, 0, 10000000003,\n",
+ " 10000000003, 10000000003, 10000000003, 10000000001, 10000000001,\n",
+ " 10000000002, 10000000001, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000001, 0, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000002, 10000000003, 10000000003, 10000000003, 10000000002,\n",
+ " 10000000003, 10000000002, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000001, 0, 10000000003, 10000000003, 0,\n",
+ " 10000000003, 10000000003, 10000000001, 10000000001, 10000000003,\n",
+ " 10000000003, 10000000002])}, 'receiver_bank_id': {('account', 'transaction', 'account'): tensor([10000000003, 10000000003, 10000000001, 10000000002, 10000000002,\n",
+ " 10000000003, 10000000001, 10000000003, 10000000001, 10000000003,\n",
+ " 10000000002, 10000000003, 0, 10000000003, 10000000003,\n",
+ " 0, 10000000001, 0, 10000000002, 10000000003,\n",
+ " 10000000003, 10000000003, 10000000001, 0, 10000000003,\n",
+ " 10000000002, 10000000003, 10000000003, 10000000001, 10000000001,\n",
+ " 10000000003, 10000000003, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000002, 0, 10000000001, 10000000001, 10000000002,\n",
+ " 10000000001, 10000000003, 10000000003, 10000000003, 10000000001,\n",
+ " 10000000003, 10000000002, 10000000003, 10000000002, 10000000001,\n",
+ " 10000000003, 0, 10000000003, 10000000003, 0,\n",
+ " 10000000003, 10000000002, 10000000002, 10000000001, 10000000003,\n",
+ " 10000000003, 10000000003])}, 'transaction_date': {('account', 'transaction', 'account'): tensor([ 201966, 201721, 2017528, 2018924, 2017516, 2018128, 2019213,\n",
+ " 201847, 2017914, 201966, 2017810, 20181020, 0, 2017724,\n",
+ " 201966, 0, 2019311, 0, 2018211, 2018125, 201932,\n",
+ " 201966, 201795, 0, 201966, 2017111, 201966, 201966,\n",
+ " 2019822, 2017317, 2019124, 2017121, 2017110, 201966, 2017717,\n",
+ " 20181012, 0, 20181023, 2019724, 2019611, 2019928, 2019117,\n",
+ " 201966, 201966, 2017328, 201966, 2019316, 201966, 2017914,\n",
+ " 2017521, 201713, 0, 2018124, 201966, 0, 201784,\n",
+ " 201713, 20171212, 2019413, 201966, 201966, 201887])}, 'trans_time': {('account', 'transaction', 'account'): tensor([1136, 1516, 1340, 1030, 1552, 1116, 1450, 924, 1046, 1426, 1247, 1459,\n",
+ " 0, 1459, 1258, 0, 1758, 0, 1230, 1210, 1252, 1039, 1741, 0,\n",
+ " 1420, 1713, 1710, 1028, 1636, 1054, 1658, 1332, 1316, 955, 1629, 1642,\n",
+ " 0, 1710, 932, 1652, 1018, 1527, 1555, 1640, 1158, 1035, 1015, 1133,\n",
+ " 1320, 1514, 1213, 0, 1133, 1340, 0, 1026, 1312, 1027, 1745, 1342,\n",
+ " 1520, 1141])}, 'transaction_amt': {('account', 'transaction', 'account'): tensor([9000, 299, 498, 954, 756, 627, 142, 946, 920, 9000, 421, 343,\n",
+ " 9000, 457, 9000, 9000, 53, 9000, 284, 120, 441, 9000, 364, 901,\n",
+ " 9000, 279, 9000, 9000, 273, 127, 952, 354, 795, 9000, 835, 761,\n",
+ " 9000, 478, 172, 804, 665, 995, 9000, 9000, 670, 9000, 340, 9000,\n",
+ " 747, 347, 52, 911, 762, 9000, 0, 790, 619, 491, 954, 9000,\n",
+ " 9000, 843])}})\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Define Metagraph\n",
+ "fraud_detection_metagraph = {\n",
+ " \"vertexCollections\": {\n",
+ " \"account\": {\"rank\"},\n",
+ " \"Class\": {\"concrete\", \"name\"},\n",
+ " \"customer\": {\"Sex\", \"Ssn\", \"rank\"},\n",
+ " },\n",
+ " \"edgeCollections\": {\n",
+ " \"accountHolder\": {},\n",
+ " \"Relationship\": {},\n",
+ " \"transaction\": {\"receiver_bank_id\", \"sender_bank_id\", \"transaction_amt\", \"transaction_date\", \"trans_time\"},\n",
+ " },\n",
+ "}\n",
+ "\n",
+ "# When converting to DGL via an ArangoDB Metagraph that contains non-numerical values, a user-defined \n",
+ "# Controller class is required to specify how ArangoDB attributes should be converted to DGL features.\n",
+ "class FraudDetection_ADBDGL_Controller(ADBDGL_Controller):\n",
+ " \"\"\"ArangoDB-DGL controller.\n",
+ "\n",
+ " Responsible for controlling how ArangoDB attributes\n",
+ " are converted into DGL features, and vice-versa.\n",
+ "\n",
+ " You can derive your own custom ADBDGL_Controller if you want to maintain\n",
+ " consistency between your ArangoDB attributes & your DGL features.\n",
+ " \"\"\"\n",
+ "\n",
+ " def _adb_attribute_to_dgl_feature(self, key: str, col: str, val):\n",
+ " \"\"\"\n",
+ " Given an ArangoDB attribute key, its assigned value (for an arbitrary document),\n",
+ " and the collection it belongs to, convert it to a valid\n",
+ " DGL feature: https://docs.dgl.ai/en/0.6.x/guide/graph-feature.html.\n",
+ "\n",
+ " NOTE: You must override this function if you want to transfer non-numerical\n",
+ " ArangoDB attributes to DGL (DGL only accepts 'attributes' (a.k.a features)\n",
+ " of numerical types). Read more about DGL features here:\n",
+ " https://docs.dgl.ai/en/0.6.x/new-tutorial/2_dglgraph.html#assigning-node-and-edge-features-to-graph.\n",
+ "\n",
+ " :param key: The ArangoDB attribute key name\n",
+ " :type key: str\n",
+ " :param col: The ArangoDB collection of the ArangoDB document.\n",
+ " :type col: str\n",
+ " :param val: The assigned attribute value of the ArangoDB document.\n",
+ " :type val: Any\n",
+ " :return: The attribute's representation as a DGL Feature\n",
+ " :rtype: Any\n",
+ " \"\"\"\n",
+ " try:\n",
+ " if col == \"transaction\":\n",
+ " if key == \"transaction_date\":\n",
+ " return int(str(val).replace(\"-\", \"\"))\n",
+ " \n",
+ " if key == \"trans_time\":\n",
+ " return int(str(val).replace(\":\", \"\"))\n",
+ " \n",
+ " if col == \"customer\":\n",
+ " if key == \"Sex\":\n",
+ " return 0 if val == \"M\" else 1\n",
+ "\n",
+ " if key == \"Ssn\":\n",
+ " return int(str(val).replace(\"-\", \"\"))\n",
+ "\n",
+ " if col == \"Class\":\n",
+ " if key == \"name\":\n",
+ " if val == \"Bank\":\n",
+ " return 0\n",
+ " elif val == \"Branch\":\n",
+ " return 1\n",
+ " elif val == \"Account\":\n",
+ " return 2\n",
+ " elif val == \"Customer\":\n",
+ " return 3\n",
+ " else:\n",
+ " return -1\n",
+ " except (ValueError, TypeError, SyntaxError):\n",
+ " return 0\n",
+ "\n",
+ " return super()._adb_attribute_to_dgl_feature(key, col, val)\n",
+ "\n",
+ "fraud_adbdgl_adapter = ADBDGL_Adapter(con, FraudDetection_ADBDGL_Controller())\n",
+ "\n",
+ "# Create DGL Graph from attributes\n",
+ "dgl_g = fraud_adbdgl_adapter.arangodb_to_dgl('FraudDetection', fraud_detection_metagraph)\n",
+ "\n",
+ "# You can also provide valid Python-Arango AQL query options to the command above, like such:\n",
+ "# dgl_g = fraud_adbdgl_adapter.arangodb_to_dgl(graph_name = 'FraudDetection', fraud_detection_metagraph, ttl=1000, stream=True)\n",
+ "# See more here: https://docs.python-arango.com/en/main/specs.html#arango.aql.AQL.execute\n",
+ "\n",
+ "# Show graph data\n",
+ "print('\\n--------------')\n",
+ "print(dgl_g)\n",
+ "print('\\n--------------')\n",
+ "print(dgl_g.ndata)\n",
+ "print('--------------\\n')\n",
+ "print(dgl_g.edata)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bvzJXSHHTi3v"
+ },
+ "source": [
+ "# DGL to ArangoDB"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "UafSB_3JZNwK"
+ },
+ "source": [
+ "## Example 1: DGL Karate Graph"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "tx-tjPfx0U_h"
+ },
+ "source": [
+ "Data source\n",
+ "* [DGL Karate Graph](https://docs.dgl.ai/en/0.6.x/api/python/dgl.data.html#karate-club-dataset)\n",
+ "\n",
+ "Package methods used\n",
+ "* [`adbdgl_adapter.adapter.dgl_to_arangodb()`](https://github.com/arangoml/dgl-adapter/blob/1.0.1/adbdgl_adapter/adapter.py#L199-L297)\n",
+ "\n",
+ "Important notes\n",
+ "* The `name` parameter in this case is simply for naming your ArangoDB graph."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 0
+ },
+ "id": "eRVbiBy4ZdE4",
+ "outputId": "da33e345-87d7-442f-fbb7-5484b79889a5"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "ArangoDB: Karate created\n",
+ "\n",
+ "--------------------\n",
+ "https://tutorials.arangodb.cloud:8529\n",
+ "Username: TUT487i8kal98gb73c2iklds\n",
+ "Password: TUTn5t85w8t50kcupmo2mmyb\n",
+ "Database: TUTn187e39v9qho3768ilyk4\n",
+ "--------------------\n",
+ "\n",
+ "Inspect the graph here: https://tutorials.arangodb.cloud:8529/_db/TUTn187e39v9qho3768ilyk4/_admin/aardvark/index.html#graph/Karate\n",
+ "\n",
+ "View the original graph below:\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAb4AAAEuCAYAAADx63eqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOzdd3RUxdvA8e/dnl5IA5LQuxSB0KRJ1SAoEECliKiAFBWkCCpFQUUF/NFtIFXaC4KACAZDBymhCQktIQkhvZfdbHv/iFlYkpAqIJnPOZzj3nv3zmyQfTJzZ55HMpvNZgRBEAShgpA96g4IgiAIwsMkAp8gCIJQoYjAJwiCIFQoIvAJgiAIFYoIfIIgCEKFIgKfIAiCUKGIwCcIgiBUKCLwCYIgCBWKCHyCIAhChSICnyAIglChiMAnCIIgVCgi8AmCIAgVigh8giAIQoUiAp8gCIJQoYjAJwiCIFQoIvAJgiAIFYoIfIIgCEKFIgKfIAiCUKGIwCcIgiBUKCLwCYIgCBWKCHyCIAhChSICnyAIglChKB51BwRBeLQSMnRsPRNFSEwaaVoDjhoF9b0cGdDCm0r26ie2baHiksxms/lRd0IQhIfvfGQKS4Ouc/BqPAA6g8lyTqOQYQY613NnTKfaNPVxfmLaFgQR+AShAlp3Ipy5e0LQGow86BtAkkCjkPOhf32GtKn+n29bEEAEPkGocHIDzxWy9aaiL/6HjVLGh/4NyhyAHmXbgpBHPOMThArkfGQKc/eEkJWtI3HfMrTh5zBpM1A4e+HS6TVsarW0uj7lyM+kHlmPx8tzmLtHoom3M028Szf1WJy2DSmx3F7xBpJSY3mfY5v+zGVImdoWhHuJwCcIFcjSoOu5U4wmIwoHN7xe/QK5kzvZN04Tv2MeVUYsQeHsCYA++Q5ZoUeQ27sCoDUYWRZ0nRVDWj6oiTK1ncdnwiYkmdzyuqxtC8K9xHYGQaggEjJ0HLwaj9kMMpUG5w6DUTh7IkkybGu3QuHkiS7muuX6pH3Lcek8HGS5vx+bzfBnaDyJGTrLNWazmaCgIA4fPmx5vXnzZlJTU8vU9v0KalsQSksEPkGoILaeiSr0nDEzGX3SbVTuvgBkhhxBkiuxqeVndZ0EbD0bhdlsZs+ePTRt2pRu3bqxYMECALKyshg0aBDe3t7MmTOHzMzMErcNcHvZ60QtfY2E3d9gzEq1alsQykoEPkGoIEJi0qy2DeQxGw0k7Pwa+8ZdUVbywaTLIuXgaly7jcx3rdZgYsXGX7GxsaF3795cvHgRo9HIkSNH6NKlC71790aSJDIyMpg5cybOzs507tyZ309eKlbbMltHvF5bSNUxq6g8/BvMOVkk/Pq1pe2QO+nl/4MRKhwR+AShgkjTGvIdM5tNJOyaD3IFrt1HA5ByZAN2jbpYnvXdz8bJFQcHB6tjkiSh1+tJT78bmEwmEwaDgcuXL3MnIblYbctUNqgr10GSyZHbueDa/W20YcGYdFn/fAZ96T68INxDBD5BqCAcNdZr2cxmM4l7FmHMTMG973Qkee557a3zpJ/5lcjFQ4hcPARjegIJv3xB6omtALRu1oT4+HiOHj1K69atkclk9OzZk8OHD7N7924ANBoNr7/+OmFhYcTFxdGmRdNitZ2PZHnDP59BWU4/DaEiE6s6BaGCqO/liFoRY5lyTPp9KfrESDxfnoNMeTc9mOcrc8FotLy+s3oCLl3fxKZmCzQKGfUr54722rRpw4kTJwgKCrKMACtVqsTChQsZMGAAVapUKXHbuuhQZGo7FK5VMGkzSNr/HWrfxsg0dlZtC0JZiA3sglBBJGToeGbeAXQGE4bUOG4vHwFypdW2AdfnxmLf6Fmr90UtG0El/3ewqd4MtULGsaldSpxHs7htS5KM5INrMGWlIFPZoqneDJdnRyC3dyl124JwPxH4BKECGbn2NPuvxD4wVVhhJAl6NvQs9V66R9m2INxLPOMThApkbOfaaBTyoi8sgEYhZ0zn2v/JtgXhXiLwCUIF0tTHmQ/962OjLNk//dx8mfXLlDKstG0rJXOZ2xaEe4nAJwgVzJA21fnQvwE2SjmSVMTFZhNyjOWWJNqq7SKulSRQKyTifl/O7KHdOXjwIOLJjFAeROAThApoSJvqbBrZhp4NPVErZGgU1l8FGoUMtUJGLXUmUT+9z5/ff0p2dna5tl0pOwo5pkLb7tnQky2j2vG0QybXr1+nR48e+Pj48NVXX6HVasulL0LFJBa3CEIFl5ihY+vZKC5Hp/Lz1l9o37oFPVs/RUBzb6LDrtK0ae4evKpVq7Jx40aeeeaZMrf57bffMnr0aHb8foA7Gl9C7qSTptXjqFFSv7IDAc3vVmDfsWMHgwYNQqfLzdMpl8sJDg6mcePGZe6HUDGJwCcIAgBr1qzhtddeo0mTJpw/fx6AyMhI6tWrZzXa27lzJ7179y51O/Pnz2fq1KkYjUaOHj1Ku3btHnh9amoqHh4e5OTkALBq1SqGDx9e6vYFQUx1CoKA2Wxm5syZAISEhHDkyBEAnJ2d0evvpgkbNWoUnTp1KnUb7733HjNmzMBoNKJSqYiNjS3yfU5OTtSpUwc3Nzf8/PwYO3YsMTExpeqDIIAIfIIgAHv27CEhIQGAnJwcxo4di9lsxt7eHplMRps2bVAqlbi7u+Po6FiqNhITE/npp58w/pMVRiaTFSvwAWzatImLFy9y4sQJvL29adKkiXjOJ5SaCHyCILBq1SrLdKZMJuPSpUuEh4cjSRLh4eEcO3aMGTNmMG/ePNLS0krVhpubG7GxsVSuXBl7e3u0Wi2JiYnFem+jRo3w8vJCJpMRHBwMQLNmzTCZ8ld8EISiiGd8giCQmZlJeno6DRo0YOLEiXz44YfIZPl/L/b09KRFixbs2bOnVO2kpKTg6urKgQMHqFatGg4ODri5uZX4PjExMdSqVYvWrVtz4MCBUvVFqLjEiE8QBOzs7PDy8kKlUpGTk1Ng0ANYt24de/fu5dy5c6Vq5+OPP8bFxYXOnTtTo0aNUgU9AC8vL06cOMHhw4cZMWJEqe4hVFwi8AmCYKFQKMjKyir0fPfu3WnZsiUBAQGluv+6desYNmxYabtnpXHjxuzcuZPVq1czd+7ccrmnUDGIwCcIgoVSqXxg4APYtm0bYWFhrFq1qkT3DgwMJDU1ldmzZ5eli1aef/55li1bxscff8z69evL7b7Ck00EPkEQLFQqVZEZWry9vXn99dcZP348BkP+qu6F+fDDD2nevHmpV4UWZtSoUUyePJlhw4ZZtmEIwoOIwCcIgoVKpSpyxAewYsUKAMaOHVus+2ZlZXHq1Ck++eSTMvWvMPPmzaNfv3507dqV69ev/yttCE8OEfgEQbBQqVSW1GAPolAo+Oabb/jhhx+Ijo4u8vpZs2Zhb2+Pv79/eXSzQFu2bKFZs2Y0b96cpKSkf60d4b9PBD5BECzUanWxk1G/+eabVK9enf79+xd57apVq3jllVfK2r0iHT16FFdXVxo3bmxJcSYI9xOBTxAEC7VaXawRX54tW7Zw8uRJAgMDC73m6NGjJCYm8tlnn5VHFx9IoVBw4cIFsrKyaNWqldjgLhRIBD5BECw0Gk2JUoE1b96c7t27M2TIkEKvmT59Ok899RSurq7l0cUiOTo6cu7cOUJCQujTp89DaVP4bxGBTxAEC41GU+Ipwk2bNpGQkMDnn3+e71xOTg5Hjx5lxowZ5dXFYqlWrRqHDh1i7969vPPOOw+1beHxJwKfIAgWNjY2JZrqhNwKDpMnT2bWrFn5VoR+9tlnaDSaUm94L4tWrVqxadMmlixZwjfffPPQ2xceXyLwCYJgYWNjY1WGqLjmzJmDvb19vinPb7/9tliLX/4t/fv356uvvmLixIls3779kfVDeLyIwCcIgoWNjU2pVkPKZDJWrVrFL7/8wpUrVwA4e/YssbGxzJs3r7y7WSLvv/8+b7/9NgMGDODUqVOPtC/C40EEPkEQLOzs7Eo14gPo06cPTZo0sYzwpk6dSr169fDy8irPLpbK0qVL6dGjBx07diQiIuJRd0d4xETgEwTBwtbWtkRpyO63fft2QkJCWL9+PUFBQUybNq0ce1c2u3btok6dOjRr1oyMjAxOnTrF7t27H3W3hEdA8ag7IAjC48POzq5Mga9GjRq88sorvPnmmygUCgwGA0lJSQ9tK8ODyGQyTp8+TfXq1alZsyapqal4e3vTq1evR9014SETIz5BECzKGvggN0uLVqvFYDDw1ltvPVaJo1UqFe+++y7x8fHk5OQQFRVFfHz8o+6W8JCJwCcIgoW9vT1Go7HU78/OzqZjx44AGAwGNBoNKSkp5dW9Mvvrr7/44IMPLK9NJpOo4F4BialOQRAs7OzsyhT4JEni2rVrSJKE2WwmOzv7sQp8fn5+BAYGsmDBAvbt24der+eTTz5h0KBBlmsSMnRsPRNFSEwaaVoDjhoF9b0cGdDCm0r26kfYe6G8iMAnCIKFo6NjmfJbqlQqMjIyGDJkCFu2bEGr1XLhwoVy7GHZSJJEly5d6NKlC7GxsYwdO5YdO3Zw+vRplJ61WRp0nYNXc6c+dYa7PweNIoaFf1ylcz13xnSqTVMf50f1EYRyIJnNZvOj7oQgCI+H48eP06FDh1I/51u8eDGTJk0iOzub2NhYGjdujK2tLREREY/tSOrQoUMERRn5OUSH1mDkQd+IkgQahZwP/eszpE31h9ZHoXyJEZ8gCBZlHfEtXLiQ559/HplMRuXKlYmIiKDbwDd4c/VfHL6eCDx+I6kbeLLgq3fIuBmMSZuBwtkLl06vYVOrJTkJESTuWoAh+Q4AKq/auHQfxdw9udFRBL//JjHiEwTBIiIigmrVqlGar4WwsDBq1qxJSEgI9erVA2DdiXDm7gl5bEdS5yNTGLg0iNgjm7Fv3A25kzvZN06TsPMrqoxYgkxjh0mbidzJA8wm0s/uJuP8Pqq8sQQbpZxNI9vQxFtMe/7XiBGfIAgWzs6l/xKfPHkyvr6+9wW9K2Trix5Bms2QrTcyd09uurOHFfyWBl0nR6bEucNgyzHb2q1QOHmii7mOXf1nkGnsLX2UJJll9Kc1GFkWdJ0VQ1qWW38e1+ngJ40IfIIgWNjb537JGwwGFIrifz2YTCZ2797NnDlzgNyR1Kc7L3B79xK04efyTSFm/P0nSXuX3r2B2YzZoMNr+DfM3SPRxNu53EZS2dnZyGQy1GrrwJGQoePg1fh8I1FjZjL6pNuo3H0txyIWDsKckw1mM07/BEmzGf4MjScxQ1fmoHQ+MkUsrHmIxD4+QRAsZLLcr4SMjIwSvW/VqlUYDAbeffddIHckpc3Ro3Bww+vVL/CZsAnnjkOJ3zEPQ0os9o2exff9rZY/rj3eRuHshcqzlmUkdS+TycShQ4dKNQU7adIk3N3d+eCDD4iOjrYc33omKt+1ZqOBhJ1fY9+4K8pKPpbjvhM24TNhM649RqPyrGU5LgFbz+a/T0msOxHOy9+fYP+VWHQGk1XQA9D+c2zf5Vhe/v4E606El6k9QQQ+QRAKkJqaWqLrv/zyS7p27YpCobCMpCSlBucOg1E4eyJJMqspxPtlXArE7qku/+z/uzuSAjhy5AgNGzakU6dOhIWFFbtPGRkZhIaGEhsbS3p6OvPnz8fX15fq1aszadIkjl0OtwoyZrOJhF3zQa7AtfvofPeTqTTYP/08ibsWYMzM3ZuoNZgIuZNeop/Vve5OBz/4GWhu/+5OB4vgVzZiqlMQBCuSJJGeXvwv8+joaK5du8aWLVuAgkdSUPAUIoAhNQ5d5N9U8n/X6vic9X9w8NuPuXTpEnq9HqVSyZgxY5AkidTUVDIyMsjMzCQrKwutVotOp8NgMGAwGCwjQ0mS7rbzzxaNiIgITp48iWvlrpZzZrOZxD2LMGam4DFgFpK8kK/Gf6ZkjemJyO1ypxzTtKWrZnE+MoW5e0KIPbGDzIuB5MSHY9egE24vTMh3bcqRn0k9sh6Pl+dA9WbM3RNSrtPBFY0IfIIgWJHJZCUa8U2ZMgUvLy+aNGkCQEhMWr7pusKmECF3tKf2bojS+W75Ip3BxPo9QcQHB1uOGQwGQkJC8PDwwMnJiSpVquDi4oKrqyvu7u64u7vj5eVF1apVqVKlCs7OzshkMhYvXsyECRNQKpW88cYbzJkzB2dnZ97bFMz5hNypz6Tfl6JPjMTz5TnIlHef12WHBSO3cUTpUR2zXkfKobXINPYo3e5+BkeNstg/q3stDbqO1mBEYV8Jp3aDyA47i1mfvxaiPvkOWaFHkNvfTfT9byysqUhE4BMEwYpMJivRiG/79u1MnTrV8jpNa735vagpxMxLB3BqOzDf8Rf6DeT1SX347LPPCAoKwmg0snjxYnr37l2CTwNNmzalV69efPXVV9StW9dyvL6XI2pFDJmJMWSc2wtyJVGLh1rOuz43FkmuJGn/txjTE5AUKtRV6uIxcDaSQgWARiGjfmWHEvUHrBfW2NZrB4Au5jpGfUK+a5P2Lcel83ASf19uOVaeC2sqIhH4BEGwIpPJir24ZdOmTeh0OqvEz46au18rRU0haqMuY8xIwrbeM/nu7aRR0qGDH7/99hsRERF8//33NGzYsMSfp2PHjpbE2fcKaOHNwj+uonDyoNoHuwp9v1399oWeMwMBzb1L3KfCpoPvlxlyBEmuxKaWH7Dc6lzewppRHWsV+F6hcGJxiyAIVuRyebED35w5c+jQoQMqlcpyLHcklfvVkjeF6BEww2oKMU/mxUBs67ZDpra1On7/SMrX15dPP/2UWrXK70vezV5Np7ru3PMYsEQkCZ6t516qEVdB08H3M+mySDm4GtduIws8X9aFNRWZGPEJQgX0oI3SxQ18CQkJ/P333xw/ftzqeN5IypAaV+gUon2jZzEbcsgMOYJ73/xV2ks7kiqpsZ1rc/haAtn6UlSkMOrpU1uD2Wy2WkRTHPdPBxck5cgG7Bp1QeHs+YD7lG5hTUUnAp8gVCDF2Sjt4D+RsNSis61MmzYNNzc3WrdubXU8byS1/4rpgVOIkkKF74RN+Y+XYSRVUk19nPnQv36xM8zkUWAidt939P76d1xdXenWrRtDhw7F39+/WO+/dzq4MNpb5zGmJ5IevBsAU1YaCb98gWObAJzaBPxzn9ItrKnoROAThAqiqLyZ2n+CoKJaC37JMPP0ifAHpg7btGkTY8eOLfBcWUZSGoWcMZ1rl/h9pZX3GUuSU3T6842YuvocGSYTCQkJbN68mYiIiGIHvryFNTqDCbPJCHl/zCbMhhyQyfF8ZS7cUxvxzuoJuHR9E5uaLYDSL6wRQD5r1qxZj7oTgiD8u0qSN1OSJMzIOH4zEWcbZYF7xXbu3MnatWvZu3cvSmX+UYeXkwZnGwXHbyZiMBU/24qNUsaH/g3o3tCr6IvLURNvZzrWcSM5M4eIpCwMeh2S7O64QKOQIZdJdGvgwZf9m9CjkRcajYY///wTg8GAJEkEBgbi6ur6gFbuqu5mx8qjYRhNZlKP/Ezcpo/RRV1GHx9O6rFNgIRtLT9kKhvLn7TTO7Fr0AHVP9tB5DKJ+QOaYqsS45eSEtUZBOEJdz4yhYHLDxWaNxMg88phUo6sx5ieiMLBDedOw7Ct27bQCgQtWrRArVZz7NixB7a9bP8l5u27ikyh5kFfNGaTCbVCYkbvpx55qZ/2XZ/jYqYdTTo+T+jNCAb27U1jH1cCmlsnik5LS8PT0xOVSoWdnR0ZGRmcPn3aasvEg4xce5r9V2KLzNhSEEmCng09xT6+UhKrOgXhCVdU3kxDegIJv87Htcub+EzYjPOzI0jY+TXGzJQC82ampaURHBxsSUhdmODgYCa80IKcPfPo2cgTtUKGRmH9laNRyFArZNgmXePWygnEH9+O0ViKhSbl5NChQ5w4+Afpf23n5vqZxP/fp2iCNzGqY618zxwdHR353//+R2BgIBERETRs2JCnnnqKoKCgYrU1tnNtNAp5qfr5sKeDnzQi8AnCE6w4eTON6YnINHbY1GqJJEnY1vZDUqoxpNzJlzcT4KOPPsLZ2ZkuXboU2KZOp2Pq1Km0adOGnJwc/Ns8xYohLTk2tQsTutelb7OqdK3vQd9mVZnQvS7Hpnbhg/aVyIm5zpQpU2jatCkXLlx4WD8ii5SUFAICAjAajZjNZmJjYwFYtmwZ4eHhBb5n5MiRtGzZEoVCwYkTJ+jXrx9du3blp59+KrK9vIU19/8yUJTc6eD6Il1ZGYjJYUF4ghUnb6bCpQrKSj5kXTuJTa2WZF//C0mhROleA8i/UXrdunUMHTq0wPtmZ2fTuHFjbt++TU5ObvqtOnXqAFDJXl3oZmsvLy9sbGzIzs7m77//5umnnyY4ONiSBu1hePfdd0lMTLS8zht56nQ6xo0bx65dha9QzbNx40Zq1arFiBEjuHHjBp9++ukDr3dNukzKnytx7Pw6eiMPnA5+VMV6n0Qi8AnCE6y4eTPtnupCws6vMBtykORK3F76AJlKA1hvlD5w4AApKSmFfqFrNBoGDRrEl19+CYBarcbJyanIfnp4eCCX5077SZLEggULaNSoUek+dCmNGDGCypUrM2/ePORyuSXw1a5d22qDflHmzp1L7dq1efPNN7l+/To///xzvmuCg4OZPn06e/fupWbNmtgd1+PUdiB3JFck7q6whdzpYDO5WzzGdK4tRnrlQCxuEYQn2IjVpzgQEmd5bTabSNj5FSZdFh79P0aSK8gOP0fCL/PwGPQJKq9a5MRcJ37rp3gMnI3KsyYAXet78ONrfrRt2xa9Xs/p06cLbTMrKwtnZ2dq1KjB1atX2bBhA6+88soD+3nnzh2qVKnCc889x++//853333Hm2++WT4/hBKIi4vD09MTnU6HSqVCkiTCw8OpVq1aie/1559/0rNnT5o3b86RI0dQKBSEh4czePBgzp07R1ZWFgBVq1bl9u3bnDt3Du9a9dl6NoqQO+mkafU4apTUr+yQb2GNUDZixCcIT7Di5M3Mib2J2qcR6sq5U5LqynVRValHdvg5S+Bz1CjJysrir7/+YseOHQ9sc/DgwTg6OnLlyhUuXrxYrDRjlStX5vbt21SpUoUxY8bwzjvvMGzYsBKNtMpDYGAgarXa0q5cLic6OrpUge/ZZ5/l0qVLtGzZklq1anHx4kXLVG52drblutu3byOTyfDy8nrgdLBQfsTiFkF4ghUnb6a6ch10UZfJib0JQE7MDXSRf6PyqA7c3Sg9e/Zs7O3teeGFFwptLyQkhB07drBy5UpkMhlNmzbF3t6+WH2tUqUKAEuWLEGtVjN48ODSfOQyOXbsGG5ubpbXcrncssilNOrWrUt4eDhGoxFfX180Gg0zZ87MV0leoVD8q0E+IUPHioM3eG9TMCNWn+K9TcGsOHjDatFSRSKmOgXhCZaQoeOZeQfITIzh9vIRIFciye4uoc/Lm5l25lfST+3EmJWC3MYRh+a9cGzdL/cikwGPY/8j5PxpBg4cyA8//FBoe40aNUKpVHLu3Lky9Xvnzp289NJLnDt37qEucOncuTMGg4EjR44AYG9vz4IFCxg5suBE0cVlMBho3bo1Fy9exGg08vbbbxMYGEhISAiSJCGXy0lJScHOzq48PobFg1PU5T477FzPnTGdatPUp+I8OxRTnYLwBCtu3kzHFr1xbJG/zp0EKOOvcurIn0DuqkVfX19mzJiR79qtW7dy5coVrl27VuZ+9+nTh1atWvHiiy8SFhZW5vsVV1hYmFXaMbVaTUJC/hp5JaVQKDh69CiOjo6YTCbatm2Lt7c3H3/8Mf379+fXX39FrS7fZ3jFTVG373Ish64mVKjVomKqUxCecGXZKG3Sa4kLWmt5rdfrSUpKyn+dycRbb71FQEBAuZUO2rlzJ5GRkXzzzTflcr/iiI+Pp02bNpbXGo3GaotDWbRv3x5HR0fee+89hgwZwowZMxgzZgyrVq3ixIkTKBTlNw65m6LuwblHIbeobbbeyNw9V1h3Irzc+vA4E4FPEJ5weRulbZQl++euVkh0dU0lNeyi5ZjJZGLfvn35rp0+fTrZ2dmsWbOmzP3N4+Hhwfvvv8/UqVOLXR+wLAwGA9nZ2XTt2tVyzNbWluTk5DLf+9133+X8+fMcP36chQsX0q1bN/R6PYmJifTp04f27duj05XP87bzkSl8uvMCkTsWErXsdSIWDCB65Xiyb9xdiZsdfo7b340m4uv+xGyYhiE1jmy9ibl7QrgQlVIu/XicicAnCBXAkDbV+dC/ATZKedGFV81mzHodH/dqyMrpI6hUqZLV6ZkzZ1q9TktLY/78+cyePRuNRlOu/f78889xcnJi4MCB5Xrfgpw6dQqZTIa39906gLa2tqSklC0QbN++ncWLF7Nu3Trq1KmDVqvl0KFDDBs2jA0bNnDgwAGMRiPbtm0r60cAik5RZ8xKJX77Zzh3HILPez+j9qpD/I55AAWmqHsSicAnCBXEkDbV2TSyDT0b5ubNVEj3bWzX65BJ4GanwE2ezelbyaw4eIOaDZsil8uRy+WYTCbeeecdq8UrgwYNws3NjalTp5Z7n2UyGZs2bWLv3r35Ct6Wt6CgIBwdHa2OOTg4kJqaWup73rp1i0GDBjFq1CgGDRoEwNtvv42NjQ3169dHpVJhMpnIzMzkiy++yPf+kq7GLE6Kuqyrx1G5+WJXvz2SQoVT+1fRx4WhT4wsMEXdk0gEPkGoQJp4O7NiSEuW9nAh/dhG+jaril81Fyo7aZAUytysLllGEmXO/HIumm/+uEpS+4m0nPAdzjWbcvPmTerVq0fz5s0ZO3Ys586d4/fffy/XKc77Pfvss3Tu3Jn+/fv/a20AnDlzxrKlIo+joyPp6emlup/BYKBVq1Y0aNCA5cuXA7n5QNeuXcu8efOIi4tDpVJha2sLwIULF/jzz9xFROcjUxi59jTPzDvAwj+u8su5aA6ExFn+TtrNO8Codac5H2k9Gi1Oijp9/C2UHjUs52QqDQpnL3LiI4C7KeqeZCLwCUIFYzabGfPGMOIObqCZtwOXotOISdOCJMMss15goTWYMJghVulJpUGfcviOmUOHDrF69Wp+/PFHWrZsSTEIzJUAACAASURBVIMGDejevfu/2udt27aRkJDA7Nmz/7U2rl69Sr169ayOOTk5lfr5Yvfu3dFqtVYj1eHDh+Pm5saoUaNYuHAhKSkpHD9+nHnz5mFvb8+rr77Ktweu8PL3J9h/JRadwZQv5Zz2n2P7Lsfy8vcnrBakFCdFnUmvRaa23jYhU9thzsm23D8vRd2TSgQ+QahgfvzxR27cuIHD0/7M3RNS7JV/OoPZsvJv6NChLFq0CKPRyJUrV5g4ceK/2mdnZ2dmzpzJnDlzClxVWh6ioqJo3ry51TEXFxdLarGSmDFjBocOHeLw4cOWEV10dDQ7d+5k6dKllutkMhlNmjRhypQppKWlMWD6Ihb+GV7q1ZhpWsN915hI2DUf5Apcu4/ObVOpwaSz/kymnCwklY3ldZpWX+LP/F8i9vEJQgUSEhLCu+++i8KjFs5dRpBjgoRfv0Ybfh6TXovczgXHNv1xaNoTs1FPws6v0N25jjEtDs9XPoNqTZi7J4SnqjgxadIkhg0bRocOHRg7dixbt25l//79+UZN5eXDDz9k+fLl9O3bl4MHD5b7/dPS0ujcubPVMRcXF6v0YsWxf/9+5syZw4oVK6w23w8ePJhq1aoVOmV7ISqV/fEOZGu1JO5bVmjR4PTzv5N2fCvGzGTU3g2p5P8uc/dINPF2LlaKOqV7NTIvBlquM+VoMSTHoHL3tRxz1ChL9Jn/a8SITxAqkHHjxmEwGHBsOwBJnpsiy7HNAKq+vRLfiVvwCPiYlENr0cXkruxTezfCrff7yO1cLPfQGoy8vXg7BoOB77//njfffJM7d+7g4eFBw4YNmTZt2r/W/+3bt3P48GH2799frveNiYnBaDRa7eEDcHd3L9E2g5iYGHr37s2gQYOssr2EhIRw8OBBfvzxx0LfuzToeu5mc5Ox0BWZ2lsXSDm4Bvf+H+Hz3s8onD1J2PmVZTVmcVLU2dZtS07CLTJDjmI25JB69GeUHtUtlTryUtQ9yUTgE4QKZPXq1SxfuRbbWi2RZLn//FXu1ZAUeb/hS0hIGJLvIMmVOPq9iManEcjuflWYzRCNCzM/+8qSX9LV1ZXTp0+zaNEi5s+fT82aNf+VjCt+fn74+/vzyiuvYDKZin5DMQUGBqLRaPJtInd3d7fUFSyKyWSiVatW+Pj4sH79eqtzgwcPplGjRoUW781bjWk25y42KWxFZvaNU9jWb5/7dyZX4tTuZXSRl8hJusPvF6P5ftZ4jCYThtQ4Ms7tJSf2JlGLhxIxP4CI+QFk/P0nclsn3PtOJ+XQWiK/eRlddCjufaZY+mIGApp7F9jPJ4WY6hSECqRq1aqE6l0wm9O5dztf4u/LyLwYiNmgQ+VZyzKt9iDOzZ/Ld2zs2LEMGDCA7t27U7t2bWbMmJFv319Zbd68GVdXVyZPnsz8+fPL5Z73J6fO4+HhgV5fvOddL730EomJiURGRiK75xeFkydPEhwczNmzZwt9b2GrMcF6RWZOdAjWD/9y/1sffwu5vSsptj4o40JRutd7YIo6m+rNqDpyRb7jkpRb9+9JL4EkRnyCUMH8dTXaauoLoFLPMfhM3Izn4HnY1G2LJH/wMx5JoSp05Z+Hhwfnz5/nyy+/ZM6cOdStW5eIiIhy67+trS1fffUV//vf/4iOji6Xe166dIkaNWrkO165cmVLQdoHWbBgAbt372bfvn24urpanRs2bBitW7emWbNmhb6/oNWYkH9FpqZmC7JCjpATF4ZJryP16EZAwmzQIVOqSZXs+bh/q1KnqNMo5IzpXLtY1/6XKz6IwCcIFUx8SsEBS5LJ0fg0wpieQHrwniLvU9TKv/fff59bt26hUCioWbNmgRu0S2v8+PFUq1aNF198sVzuFxYWxlNPPZXvuJeXV74SQvc7efIkkydP5vPPP+eZZ56xOvfbb79x7do1NmzY8MB73L8aEwpekWlTvRnO7V8lfvtn3F7+BgonDyS1DXKH3Ow6DZo1Z/Bz7Xm/a01UJfx2t1HK+NC/fpEV3ku7x/BxIgKfIFQwGclFVBswmTAk3ynyPsVZ+VelShUuX77Mp59+ykcffUTDhg3LbZS2Y8cOzpw5w/bt28t8r/j4eNq1a5fveN7orbAtDSkpKXTp0oXnnnuOKVOm5Ds/cuRIevToUeBo8l73rsYE6xWZ7n2nW1ZkAji0eIGqo77H55112NZ7BkxGlO7VAYi6eQ17e3tGd21I8oEfMOm1YH7ws1BJAhulnA/9GxRZnWHdifBS7zF8nIjAJwgVTEZUiCVdmTEzhczLBzHlZGM2Gcm+eYbMKwfRVM+dljMb9JgNuYs7zCYDZkMOZrMZtVyintfdlX8mk4mkpKRCpwWnTZvGjRs3MBgM1GjQhFc/XVXmKbKnnnqKgQMHMnz48DItdDEYDGi12gIXnshkMiRJ4s6d/L8ImEwmWrdujaurK7/++mu+8+vWrSM6Opp169YV2Yd7V2NC4SsyzYYccuLDMZvNGFLjSPxtMQ4t+yDX2KOQTNSqpCErKwuj0UjCiV+IXf8B3L6AzGxEdd/sp0YhQ62Q0bOhJ5tGtmFIm+oP/Dk+SRUfRCFaQahADAYDaic3ak3YQI7R/E/C4s/JiQsDswmFkwcOLXrj0Cx34UrUshEY0+Ks7lF19I/I7V2IWjocdBlIkmQJeDNnzmTWrFkFtp1XFPWPy3cw6PVWX+ilLYqak5ODi4sLgwcP5rvvvivxzwPg6NGjdOzYsdCgrVAoCAoKon379lbHhwwZwtatWwkLC6Ny5cr53ufm5ka3bt3YuHFjkX2IT9fS9otADCYwpMYVWjTYtpYfMes/wJByB0llg33jbjh3HIokk6NWyDg2tQtxkTfp1q0b8fHxmEwmOnTowJWbkWRXborKozq2Tm5UcrSlZa3KzBjaA2/3uz/rZs2a0bp1a5YuXWq1wvV8ZAoDlx/i9u4lhe4vNOm1JB9YSVbIEcwmAyr3GngNmYeNUs6mkW2KnEJ9mMSqTkGoQI4fPw7adDrX82D/lVjktk54DS782Zv3mJX5jkkStK6iYbM2Pd/zr3tL+tzLqigqsnyLa0pbFFWlUrFs2TJGjBjBtGnTipxSLEhQUBBOTk6FnlcoFMTGxlod++GHH9iwYQO//fZbgUFv4cKFpKamFlitXqvVMm7cOCIiIkhOTiYqKoq4uDh8XvkEyacZCiePB67IrPLGknzH7l2NWalBAy5dusQLL7zArVu3LPk/TSYTgYGBbN68mSMHjrDmxzC+f1+Hk5MTDRs2pEOHDvz9999cvXqVixcvsmvXLstU7/0VH+RO7mTfOE38jnlUGbEEhbMnSXuXYDYZqfLWcmQa+9xfprhb8WHFkKJXCj8sYqpTECqQwMBAXFxcylScVqOQM/2lFoSHh6NUWj/n69q1KyNGjLCqWv5vT5G99tpr1K9fnz59+pT0owBw9uxZqlatWuh5pVJJfHy85fWlS5cYPXo006ZNo2fPnvmuN5lMzJgxg7feegt7e/t85+VyOb/99hv79+/n9OnTxMTEYGNjQyd3HUa9tlSf4f7VmC4uLhw8eJBTp05ZjslkMrp3787333/PlStX0Gq13Lp1i6lTp6JSqVi+fLmlJuHx48epWrUqq1evJi4tu8iKD/rESLKunaTSc+OR2zrljkC9cvvzOFZ8EIFPECqQU6dO4evrS1MfZ6b0qI3MVLKcjPeu/PP19SUwMNCyZ+3pp5/GaDSyatUq3N3dadq0Kcs27f4nH6j1syN9QiQxG6YTsXAgt1e8RVboMavzJS2KumvXLi5fvlyqKhGhoaHUrVu30PNqtdoSyLOysmjfvj3t2rVj7ty5BV7/0UcfYTAYWLRoUYHndTqdVRUISZLIzMxkzTefoj22Id+zuKIUthpToVAUOBq9l6+vL9OmTSMoKIgJEyYgSRIKhQKFQoFOp2Ps2LEMnZF/hAnW+wt10VdROHmQcng9kf97legfx5IZcvTuZ+TxqvggAp8gVCB5X/Jff/01b3WuT/axDWiUsiKL0xa28q9Dhw4sXbqU6tWrc+rUKXQ6HTNnzsTBwYELFy4wc9MxsnTWwdVsMhL3f59iW9sPn3d/xvW5cSTsmo8+6bbVdSUpilqjRg1GjBjB22+/XexMK3mio6Np2bLwaTiNRkNiYiIA7du3R6VSERgYWOC1OTk5LFiwgEmTJuXLApM3EnR1dSU8PBy1One6N2+6WKVScfvgRma80AiNUlauqzGLQ6VS0alTJ7744gtOnDiBXq8nIyODBm27FVnxwZieiD7+FjK1Ld7jVuPafTSJuxeiT4gEHr+KDyLwCUIFERkZSXh4OP/3f//H1KlTMRqNzBzchc0j21qK02oU1l8JJr0OlVyyWvl3v9GjRxMWFoZcLkehUDBr1izS0tL4Ye0m7Gr7WVKj5dEnRmLMSMLB7yUkmRyb6k1RV21I5qUDVteVdIrs22+/RaFQMHz48BL9XFJTU+nYsWOh521tbUlKSmLs2LFcvHiRkydP5pvizfPOO++gUqnylU/atWsXHh4ezJs3j4kTJ9K6dWurHKBKpZKJEyeiVCppZp9B6v/NQnv9LxSSOd/fSUGrMcvD0KFD+emnn3j//fdp0aIFcnnu0LM4FR8khQpkCpyeeRlJrkTj2xiNb2Oyw+5mq3mcKj6IxS2CUEGcOnXKauWiUqnkpZdewssrtzhtYoaOrWejCLmTTkqWjj07ttKiVmXWfjC2VCms9D4tUIdeLTAjSX5mcuJv5Tuao9Ox/vhN3uneoMg7yGQyVq5cyYABA/jwww9p1KhRke+Jjo62bEsojL29PZcvX+bMmTNs3ry50AU0GRkZ/PjjjyxYsMAy/RsWFkbfvn25cOEC/v7+KBQKvvrqKzw9PfH09LRUdzebzYwZM4apU6fy9ddf06ZNG37/aSo6lJa/kzStHkeNkvqVHQho7l3uacU++OADNm7ciJeXF88//zxdunTBw8MDk/ZukC+04oNH9fw3vG8a4XGq+CACnyBUEB4eHlavPT098fLysryuZK9mVMdaQG6aLe2fO9i1Ldkq72RJFJaGS+nqjdzWibST/4ej30toIy6gjbiEplrjfNea5UrmLl2FV3JLBg4cWGSb/fv3p3nz5rz44otcv174NKnZbCY+Pp79+/cXmJz6XnK5nDNnzjB27FgCAgIKve7111/H2dmZ8ePHo9VqGT58OJs3b6ZevXq88MIL7Nmzh0qVKrFw4UJmzpyJRqMhMjKSU6dOsXXrVp599llu3brF0qVLGT06dyRlD5a/k39TVlYWcrncsmdx5cqVrFy5EgcHBwI+/ha1whmdwWTZX+j58hzr7Sg+T6FwdCf1+Gac2g5EFx2KNuIiLs++nnv+Mav4IKY6BaGCWLEiNynx2LFjcXZ2LnBFIkBERATr169n6dKlpQ56UHAaLgBJrsC9/0dk3zhN1OKhpP21HbsG7ZE75E8SDeBdsy4vv/wy3bp1K1ZR2J07dxIeHs6SJQUvygA4cOAAnp6evPHGGxgMBvr27UtQUFC+63Jycjh//jw2NjYsXry40PvFxcWxbds2Fi1axNdff42zszN79+6lS5cuXL9+naNHj/Ltt9+ybds2pkyZQrVq1bh16xZubm6EhoayevVqVCoVkZGRlqD3b8nJyWH79u28+eabNGnSBHt7e+zs7Ni0aZPleaNSqeTdd98lOTmZL9/uB/DAig/3/p1GLhxI0m+Lces1wVLq6HGr+CA2sAtCBRASEkLDhg1xdnYmKSmJhIQE5HI5Li4u+a718/MjNTWVq1evlqnN9zYF88u54qUni1k7CbunuuLw9PP5zvVtVpWXq+vo1asX2dnZbNiwocgcnRMnTmTZsmVMnTqVxYsXc/PmTZyd7656zMjIwM3NzfKcTSaTsWnTJgICAkhOTsZoNOLm5kaHDh04efIkNWrUIDQ0tND2evTowblz51AoFMTHx9OkSRMuXLiAra0tc+bMYfz48axfv55hw4bx4osvsm3bNlJSUujWrRvBwcF8/PHHhW78LwuDwcD+/fv59ddfOXHiBNevXyc9PR2lUkmVKlVo1qwZ3bp1IyAgAAcHB5ycnLCxsWHDhg307t3bcp+Ra0+z/0pskdtRCiJJ0LOhp9jHJwjCw5OSkkKrVq1QqVSWLzM3N7cCg97+/fs5c+YMW7duLXO796fhuldOXBhmQw4mvZbUk9swZCRj37hb/gsNOaRFXsHPz4+4uDj69etH37598ff3f+DqzRdeeAG9Xs+cOXPQarX58oPa29tbEkrLZDL69+9vmcacPn06tWrVYvDgwRw7doxnnnmGpKQkVq9ezZkzZ/K1dezYMfbv3098fDxqtRqZTEZoaChz5swhOTmZ8ePHM2vWLIYOHcqkSZPYtm0bP//8M15eXsTExHD58uVyCXomk8myLaFVq1a4uLigVCrp06cPv/76K15eXsycOZPw8HBycnIIDw/nl19+Ydy4cXh5eWFnZ8dnn31GcHCwVdADyrzvs7gVHx4WMeIThCeYwWCgZs2aACQmJrJgwQJGjRpV6PVeXl40b96cPXuKrs5QlIQMHc/MO1Dgc77kAyvJOP87ZpMRtU8jXLuPQulSJd91ZkMOt5e9jtKko0ePHkyYMIEbN24wadIkDAYDmzdv5vnnrUeJZ8+epUWLFpbXdnZ27N69m06dOlldt2bNGl577TUcHR2JjIzE0dERgIYNG3LlyhXLdSqVCr1ej1wuZ9y4cSxcuBDITc925swZdu/ejUwmQ6lUIpPJmDx5MjNnzrRME7/yyits3ryZb7/9liFDhtC7d28CAwMZNWpUqaeTTSYTp06dYtu2bRw5coTQ0FCSkpKQyWR4eHjQqFEjOnfuzIABAx64R7Ek7iYiKH5e1Nw9huWz3aI8icUtgvAE69ixIykpKdy8eRN3d3eeey5/8dg8X3zxBYmJicXKLVkcbvZqmnmq+Ou2lvt/u3bpMgKXLiMefAOziewbpzFmpWIk99ndzp07Afj66685efIkvXr1ok+fPmzZsgWlUsnKlSvx9/dn7dq1jBs3jrS0NLKysqwyr+Tx9/cHYO3atZagp9fruXbtmtV1eSNLhULB+PHjgdytE5988onlGplMxjvvvMNnn31mWShjMBh45plnCA4OZv/+/ZjNZtzd3VEqlRw/fvyBK0nvd+HCBbZu3crBgwe5cuWKZUO9u7s7DRo0YOzYsfTr14+mTZsW+54llRe8LKnnHjBkkqTckV5xU889bCLwCcITatiwYZw+fZqLFy8SEhKCTCajWrVqBV6r1WqZNWsWkyZNsgSBktq/fz+HDh0iLCyMa9euce7cOZRetaky5EtySlE8wUalZOeiyYyJO8jRo3ezgCgUCiZPnoy9vT29evXiwIEDuLm5MX36dKZNm8bAgQPZuHEj/v7+jBw5kv/7v//j559/JiAggIQMHVvPRBESk0Z8Sia+gz4m2qkRiRk6KtmruXDhAgaDAUmS0Gg09O7dmy1btmA2m3nmmWdISEigS5cu+QrrGgwGfHx8LKO3lJQUmjRpQkpKChcvXmT27Nls3LiRvn37smnTpgeuIg0NDWXr1q0EBQVx6dIl4uLiMJvNuLq6Uq9ePUaMGEG/fv1o2bJlmRYflcaQNtVp4u3MsqDr/Bkaj8TdPKtwN9n4s/XcGdO59mOVmPpeYqpTEJ5An3/+OR999BF79uyhZ8+ezJ49m0WLFlkykNxv4MCB/PHHHyQkJJT6y3Tw4MFs3LjRqrTN5s2b0fn4lWmKzGw207t3b/bt24den7sJWpIkatWqRWJiIsnJydjZ2ZGZmQnkZlo5evQozZs3B2DKlClsDjxJj/fmc/Bq7shPV8CXded67lzavIBjv/7M+++/zwcffEBYWBitWrUCoF69eoSGhqJQKDCZTJbPqdFoqF+/PvXr1ycqKooVK1bQrl07nJycWLNmDQMGDCArK4vNmzfTq1cvq89569Yttm7dSmBgIBcuXCA2NhaDwYCzszN169albdu29O3blw4dOjz0IFeUe/d9/tt7DMubCHyC8ITZtm0bAQEBLFq0iHHjxgHQq1cv7ty5w9mzZ/Ndf+PGDerUqcOWLVvo379/qdsNCgqiS5cumM1m5HI5r776qiV3plV1hlJMkWVmZtK0aVMyMjI4cOAAvXr1Ijw8HIDq1atb/jtPnTp1CA0NRZIk1p0I55Ndf6M3UWTbZn0O73TwZmIfP3Q6HdWrVycmJuaeayRefvllvv32WxwdHZHJZEyfPp0JEybg7e1NTk4ORqMRPz8/2rRpw5IlS+jQoQO//fYbaWlpbN26lf3793P+/Hmio6PR6/U4OjpSq1Yt2rZtS+/evenWrdsDR4RC2YnAJwhPkODgYPz8/CwLJ/LUqVOHtm3bFpjEuWnTphgMBv7+++9StWkwGBg+fDgbNmygRo0aREVFoVarCQsLo1KlSpbrLkSllGmK7M6dO8TGxtKsWW6R3MzMTJ577jmOHDlSYL/c3d15Z9EW1l/OKtVoc+3MUfxx5GRuPkr36sjUdjzTqjk9WzemoSaVZ9v58csvv9C7d2+WLl3KxIkTLc8D1Wo1BoOBp59+mqSkJG7fvo1Op8POzo6aNWvSqlUrXnjhBfz9/VGpVMXum1A+ROAThCdETEwMNWvWpF27dvzxxx9W5+zt7Zk/f36+FZ07d+7kpZde4vLly9SvX7/Ebe7atYvBgwdjMpn46aef6NevHz169GDo0KEMGzaswPeU9xRZp06dOHTokNWxOnXqEKNX49x/Fkl7l6ANP49Jr0Vu54Jjm/44NO1JTkIEibsWYEjOra6u8qqNS/dRqNx8kZmNZN66hMa7AWazOV/RXIPRSF0HPZ8PeZaaznI8PDzQaq1LCqnVamrUqIGfnx/+/v706dMHW1vbEn8+ofyJwCcIT4CcnBx8fX1xcHAgNDTU6nmQ0WhEoVAQHh5utbjFZDLh4eFBhw4d2L59e4naS0lJ4cUXX+Tw4cMMHDiQNWvWPLKRi6urK8nJyUiSZFUY9+l3lpNs64Mu/hZKlypICiX6xEhiNkzDY8AslM5emLSZyJ08wGwi/exuMs7vyy30mnefB5WtMJkwG/UkBf5AxrnfrE5JkkS7du0KHY0Kj5aYSBaEJ4Cfnx85OTkEBwdbgl5e0mStVoskSfj4+Fi9Z/bs2aSnp7N+/foStfXNN98wZcoU3Nzc+Ouvvx5Y0udhSEpKynfs4tUw+q+5gtloRuV+70pWCQkJQ/Id1F61kWlyC8WazSBJMsvor8g6TQAyGZJMjVuPkWg0apJP/UqLFi3w8/OjUaNGJdquIDxcIvAJwn9cv379CA0NJTQ01Krid0ZGBu+99x4qlQqz2YxGo+Gtt96iY8eOPP/885aVn8Wdfrtx4wb+/v7cuHGDyZMn8/nnn/9bH6nMjt4xIUkS/LODMPH3ZWReDMRs0KHyrIVNrbvBOmLhIMw52WA249RhMAm/fl3g1Kjudggph9eRE3MdJBka38a4dB+Fwt4V5y5vELhl1WO7fF+wJqY6BeE/bPr06cybN4+DBw/Svn37fOfr169vyTFpY2PD6NGj+d///oetrS1yuZyUlKIrnJtMJsaPH8+KFSto1KgRe/bswdv78Uk4XJCC8oSaTUZ0t0PQRlzEqU2ApaQOgClHS+alQOSOHiicPAqcGjVlpmDSa7Gp0RxkMpL2rcCYkYTnoE8ey3yUQuEer40hgiAU25o1a/jiiy9YuXJlgUEP4NVXXwVyS+u89957ltJEGRkZaLVaAgICrGr03e/QoUN4eHiwcuVKvvvuOy5cuPDYBz0ouDKEJJOj8WmEMT2B9GDrlGwylQb7p58ncdcC5LZOSIq82nF3p0ZtarXErn57ZGpbZEoNDi1eQHc7N7VZSYvmCo+WCHyC8B909OhRXn/9daZOncprr71W6HX9+uWWlKlSpQqffPIJiYmJlo3XefXXpAKeZ2m1Wnr16kXnzp3x8/MjMTGRN95449/5MP8CR80DnuKYTHef5d3LbMZs0GFMTyTx92VEfN2f6O9HI7d3tZoazaOL/Bulm6/ltQRsPRtVDr0X/m0i8AnCf0xERARdu3ald+/eRT5na9CgAZIksWXLFhQKBadOnQLA0dGRDRs2sGXLFgA+++wzMjIyAFi9ejWurq6cOHGCP/74g99+++0/tww/rzKEMTOFzMsHMeVkYzYZyb55hswrB9FUb0Z2WDA5MTcwm4yYdFkkB/6ATGOP0s2HSj3H4DNxM56D52FTty2S3Lp6eE5cGKlHf7YUWoXcfYkhd9KtrsvbpC48XsQzPkH4D8nKysLHx4eqVaty7ty5AtNY3ZuPMiVTx/lTJxj1cm8Cmlelto8Xzs7OnD17FldXVwC+++47Ro8ezbBhwzh//jwXLlxg1KhRLFmy5LFLk1WQHTt2EBUVhb29Pfb29iQnJ3P276vss+mILjON+O2fkxMXBmYTCicPHFr0xqHZc2SGHCHl0DqM6QlIChXqKnVx7vQaKo8aVvdP3LsEpZsvji37AKBPjiZ2/Qc4dx6O/VNdrK51z4mhn1sccXFx/PLLL4SHh7N3795Ci/4+TPf+f5GmNeCoUVDfy5EBLR7/FGPlTQQ+QfiPMJlM1K9fn9TUVG7duoVGo7E6fz4yhaVB1wvNR2kym1ElXmfN1ME0r56bUSUxMZEaNWqQnp47UvH29ubAgQPUqVPnIX2qsuvcuTNHjx5FJpOh1+sxm804OTlR9eXZZDrVhDIG78Q9i5CUaly7j8KQGkfM+g9wahuAw9P++a7NuHiAxN0LrI75+fnRp08f/P39adas2UP/ZaKo/y/y8pSO6VSbpj4VY1WqCHyC8B/Ro0cPjhw5ws2bN/Hy8rI6V9pcmL169bKqvVenTh0uX7782OWKTEhIIDg4mIsXL3L16lXCw8OJjo4mLi6OhIQEqwU6kiTh4eGB2cUXmxemgaL4G+uNE51lDgAAIABJREFUmSlob53HpnYrJIUKbfg54rd/hlufKai8ahG7/gPsn/bHqXW/fO/VKGR08chm3YfDLb9IFESlUuHh4UHbtm0tATFv9F3eypoj9UklAp8g/AeMHz+e5cuX89dff1mqDuQpbYHQhjmhbJs3wbLAxcnJCV9fX/744w/c3NzK+yMUyGQycevWLYKDg7l8+TLXrl3j1q1bxMTEkJiYSEZGBjqdDrPZjEKhwM7ODmdnZ0u2lsjISCC3VJFOp8PGxgY7OzsSEhJQKpVMXL6drddNGIq5nMGYlVro1GjKkQ2kHtmApLQeafu+n1utXq2QcWxqF1TmHIYPH86uXbuoWrUq169f59SpU/z+++/88ccf/P3336SkpFhVsZAkCRsbG6pXr06PHj0ICAigbdu2RY4Or169ipeXV4GlpJ6kwrHlTQQ+QXjMLVu2jHHjxrF582YCAgKszp2PTOHl70+Qrb874jEb9CTuW4Y2/BwmbQYKZy9cOr2Wb2WizKRnZK0sRrzUDTc3t3KfgjMYDFy+fJnz589z5coVrl+/TlRUFLGxsSQlJZGZmWlZ+KFSqbC3t6dSpUp4enri6+tLnTp1aNCgAc2aNaNOnTrI/p+9846v6f7/+POu3JEtEjES1Iq9apXau0rt2SqpFq0WraJWW5TaVFF7K6qqFLVVY6VFzIRIJJFE9pCb3HnO7498c7lyZRDKz30+Hnk8OOdzP+dzTk4+7/v5vMdLKiUkJIRPPvmEY8eO4e7uzujRo5k4cSL+/v5s2LDBcm2JRELjxo05e/YsY5b9yv4YVb6rHkQR8X+fLSwSRNpX9+anh/L4NmzYQEpKCqNHj37s8zl9+jT79+/n4MGDhIWFkZGRYVV2TSqV4urqStWqVenTpw99+/a1Wu07Ozuj1+vZt28f7du3txzPeS/izv6G9spRDAl3cKzaguJdxljaaG+cIvXvLZjvJyF3Lo5bi/fQVG6CWiFj+4eN/18n49sNnx07LzCHDx+mY8eOfPvtt0yaNCnX+Q83/cPhG3FWE7pg0JF+bhdONdsic/Uk6/Y/JP4+l1JDlyJ3K2Fp92jStU6nY8qUKWzYsIG4uLg8DUBGRgZBQUFcvXqV4OBgwsLCuHv3LgkJCaSmppKZmYnZbLYIujo7O1O8eHFKlixJuXLlqFy5MtWqVaNOnToFygvcv38/X3zxBcHBwVSsWJFZs2ZZSSgJgoCvry/R0dFWn9NoNGi1Wi7fTeXL9Ue5qXVAKpFgEh/cm2DUI5VKaVyxBOfD4hEkhd/mFY164rZNpKqXhoYNG1KjRg2aNm2aa3VeEAwGAydPnmTHjh2cOHGC6OhosrKyrNooFAo8PDysJJOGDx/OsmXLkEgklvdCG3waJBKywi8gGg0Ww2e6n0j08g/w6jkZ1Wv1s9+R32ZTesQa5E5u/++T8V+sjXw7duxYCAkJ4a233qJ///42jV5ihp6TNxNyrWKkDirc3hxo+b+mYkPkriXQ3wu1MnwPJ11fu3COAQMGkJycjNlsZseOHdy9e5dbt24RHh5ObGwsCQkJpKeno9PpEAQBmUyGWq3G1dUVT09PSpcuzRtvvEGVKlWoVasWderUeWI1d8g2ZgsXLmTOnDkkJCTw5ptvsmPHDmrUqGHVLiMjg6ZNmxIXF5erjyFDstMN3MX7HJ7Wh1HjvqJ00x7MW70NUa5C0GlxyErg7NZF+JYoxvzfz7P4ryikj2xn5oVaIWVgHQ+mzL/JpRi4dOkScrmcli1bcvjw4ULft4ODA+3ataNdu3ZWxzMzMzl48CCbNm0iMDAw1/2uWLGClStX8skXEznp0BRRBE2VNwDQ3wvFbEy0tDXfT0KqcrTsAmgqNkCiUGJKjUXm6GZ5L/6/RnvaDZ+dJ8IeGv1sSU1NpUGDBtStW5fNmzfbbPPLvwVLljZrUzAmR+Pg6Zv7nNlE3R7DiTq83ur4wIEDcXJywt3dHS8vLypXrkynTp3w8/Ojdu3a1KhR45mpMaSnp/P555+zefNmBEGgX79+LFy40GYAyIULF2jZsiUajYbQ0FCuXLnC22+/DWRvA3bu3Jnw8HDq16+PIAgEnjrGD3O/w8vLi5SUFGQyGTv37cO3RHbfY7q8zrfffItrqyFIZA5I8tj+FQUBzAZaeRqY3LcTdw77s27dOgRBwGw2M2fOnCJ9LhqNhh49eliKEsyaNYuvvvrKcl4qlaLRaDh+JxMq592Xg3dFFB4+ZN46h7rC62SFnkciV6DwzE7lyEnG/6h5hSK9hxcFu+GzUyjyDo2+x8IjN1+50OiixmQyUbt2bVxdXQkICHhsu+B76VbP3xai2UTi7/OyxVQ9fHKdN4lSvKrUQ3rzONHR0ZZqLjt37qRLly5PfS+FISQkhI8//pjjx4/j7u7OpEmT+Oqrrx7re1yyZAljxoyhdevWHDhwAKlUyttvv42rqys+Pj7cuHGDkiVL0qBBA1JSUgAIDAxk+fLlfPjhh/Tu3RsPDw9atWoFZMs3zZ8/n/QLf6CLCcG1SR8cKzVE6eBgUzQ37cZZtIG7+enuDY6t/Ia1a9eybds2zGYzGo2GJk2asG7dOvr37/9Mnpevry8+Pj707NmTzz77jHLlygG265Q+ikQqw7FGaxJ/n4toMiCRKSj+zgSkDtkrXVvJ+P+fsBs+OwUmv9DonMnh0PU4/rqZ+MqERhc1LVq0IDk5mYiIiDzTCmzVo3wYURRI3DcfZHKKtRv+2Ha1X2/Mmh/ucO/ePTZt2sTq1aufazrDo/67HTt2WPnvHkUQBLp3786+ffusfJ+dO3fm1q1bhISE4OnpyenTp+nevTtJSUmWz3bs2JEPP/wQgB07dliOBwUFMWDAAEtB744NqzH2s/a0fesd+kxYiFeNOrlEczu3+ZrzkddQKpVotVqaNWtGrVq1KFu2LL/++iujRo1i4MCBrF+/nr179xb5CnngwIEMHDgw1/H83guArDuXSD2+jhIDZuHgXQHDvVASfpmOvM83OJR47X/9/P+tOGM3fHYKRGFCo0URsoxmZu7PLuBrN34FZ8iQIZw/f57Lly9btvZiYmKYO3cuNWvWpFatWlSrVg2NRoMmj79eURRJ2r8EszYVr95fWykRPIqLKrscl7e3N+PGjWPcuHFFek+2EASBBQsWMGfOHBITEx/rv3uU2NhYGjZsSHJyspUixccff8yhQ4c4c+YMvr6+xMfH8+233xIZGUnx4sVJTExEJpMRERFh6SsneOf48eO0bdvWkl6gVquZOXMmNWrUYObUiUyc6M/Zs2dp0KCB1Vhq1arF+fPn0ev1JCUlUbVqVcLCwrh16xabNm1i2bJlvPvuu3Tu3BlPT0/27t1L8+bNi/Ix2iTPOqX/wxAXhtKnOsqS2YUKlCUr41CqCll3LlkMX8578f+RF78ekZ3/nKCoVGbuDybu7B5i148mYu47JO5baDmvjw4m7ufJRC3qR9TiASTsnoUpI5kso8DM/cFcvpu/9I0dmDNnDhs2bOD333+natWqluNarZbFixfz6aef8uabb+Lo6IhUKmXXmiUo5bYjL5P//BFjUhRevaYiVTze56qSS/Er6Vzk9/I40tPTGTZsGI6OjkyaNIlOnTqRmJjIyZMn8zV6+/fvp1y5cmg0GqKjoy1Gb/78+Sxfvpxdu3ZRt25dRowYQalSpbhz5w4BAQGUL1+eJk2acOHCBRYuXJir34YNGzJixAjL/0VRxNPTE4Avv/ySdu3a0bp1a9LT060+l9MGsrene/fuTWpqKoMGDcLf35+qVatSrFgxEhISaN68OS1btsTf398qf+9ZkFOnFLKlmESTAQQziAKiyYAomFGWrIT+7nUMcWEAGO7dRh91DQevcsDzfy+eN3bDZydffjwRis5kRu7kgesbfXGqZR1tJugycKrTkdIj1lJ65FokDmqS/lgEgM5kZtmJ0P9i2C8Vu3fvZsKECSxcuJBOnTpZjkdERFhqZmq1WnQ6HVKplC5dunDp1xVkhyFYY0qLJ+PSQQxxYdz94V0i5/cicn4vMq4dz9VWBHrVe/YyQyEhIbRt2xZ3d3d2797NpEmTyMrKshTEzo9x48bRpUsX+vXrR0hICG5u2f7jX375hXHjxrFgwQISEhJwc3Nj48aNLF26lIiICBo0aMCFCxf48ssvqVWrFq1bt87Vt6OjIyVLlkQmk1G9enUMBgMeHh6W8/v378fZ2Zk33njD6nM54y5ZsiSOjo5MnToVqVTKTz/9RFhYGEqlkqpVq/L++++za9cudu7cydatWylTpoxlS/VZ0Kv+g99nWsDPRM7rQfrZX9BeO07kvB6kBfyMyrcmrs36k7B7FpELepOw+ztcm/TO1hrk+b0X/xX2PD47eZKYoafp98esgihS/tqEOT3RKhn2YfT3QonbOhHfsdmV/3MqWtijPW0TFBRE/fr1GTZsGD/++CP79u3jp59+IiAggLS0NDw9PXF1deX27duoVCoWLFjA8OHZPrthGwM5ciOeJ/kjfh7iqfnl3+WHTqejefPmXLhwgbVr1/Lee+9Zzp07d46mTZvSo0cPAgMDiYyMxN/fn2XLlll8lIsXL2bChAm58uAeJjU1FS8vLyZNmsSUKVO4efMmfn5+Vm0iIyOpWLEigwcPZtWqVQDcu3ePqKgoqlevjru7O1988QUzZ860+tz27dsZNmwYZrOZZcuW8c4779C2bVsuXLjA1KlTmTZtWoGfRWGwld9ZUF4FUV37is9OnhQ0ZP5h7DplBSc+Pp4mTZrg4+NDQEAASqWSHj16EBERwejRo0lISCA+Pp6VK1cilUpZu3YtH330EWFhYYwfP56fpw7J3sp6ApRyKSNbViziO8r2382dOxcvLy+6dOmCp6cnly9f5ubNm4UyeteuXaNkyZKEhYVx7do1K6MXERFB8+bNcXNz45dffqFMmTJER0ezcuVKq8CcFStWWKI2H0ePHj0oXrw406ZNQyqV5jJ6kB1BuXPnTtasWcO2bduAbJ9ogwYN0Gg0TJ06lTlz5uTaDu3bty/JyckMGDCAoUOH0rhxY7Zu3cr8+fOZPn06NWrUICEhocDPpKB83LIiKrnsiT6rksueyXvxImE3fHbypCAh8w9TUJ2yR0szvWoEBgbSv39/SpYsSVZWFklJSZQqVYrt27djMBi4evUqX3/9taVmZsuWLVm3bh1fffUVCoWCChUqMGfOHDIirvFl+4qoFYX8UzYbiNm3lAWTR3Ps2DEMhiczng/zsP9u8uTJdOzYscD+u0dZvXo1tWvXpnr16sTExFClShXLudTUVCpXrozBYECtVnPq1ClOnTqVq3B3eno6ISEhTJky5bHXOX78OCdOnOCXX37Jd0zdunVj1KhRvPfee9y+fdvq3KRJk/Dw8LCZuiCXy1m1ahVhYWE4ODhQpUoV/v33X0JCQtBqtZQuXZr169fne/3CUNvHjUmd/Qr9XmTX6vT7f12uDOyGz04+FCQ0OgdjSgzxO6bh3vZDVD7WE12azsjly5eZMWMGNWvWxMXFhVOnThX1cAtFYoaeFSdvM3r7RYZuCGT09ousOHmbpAx9kV/LYDCwcuVKmjZtilqtplGjRuzatQu5XM7FixdJT0/n4MGD9OjRw2bemkQiQafTER4ebqVE8NlnnzGyXU0mda6KWiGz4fF7tB9QK2R82a4S9y/uZ8OGDXTt2hVXV1eGDh36RPf2qP9u8uTJZGVlsXHjxkKrDuQkrH/44YdMmDCBv//+2yoNYOXKlRQrVgyDwcDixYuJioqiadOmNvuaPXs2Li4uNGnS5LHX6tu3Lx06dMjlv3scixcvplatWjRp0gSTyfpvY9OmTRw4cIDLly/b/GzZsmUJCgpi27Zt7Nmzh9q1a/P1118zcuRIhg4dSuvWrdHpdAUaR0EY1Ljcg/cinxcj5714FQpUg93HZycfbCXD2vLx5atTdvUYSfsWIJFIEEURhULBtWvX/hPdt+elTxYeHs7ChQvZt28fd+7cQalUUrduXQYNGsSRI0f4448/CAkJsSQe50daWhpeXl6W1ZlUKiUtLQ0nJ6f/3VcKXb78AZlPLVRKpc2k61ZVPBnZsiK1yrjRuXNnDhw4AIBSqWTs2LF89913Bb6/R/13s2fPtlQVeRISExNp1KgRMTEx7N27l7Zt21rOBQYG0rdvX8LDw1EoFMTExOSrIOHj40Pz5s3ZsmWLzfMTJ05kwYIFJCUlWZ5hQcjMzKRUqVLUqVOHEydOWJ1r0KABaWlp3Lx5M88+TCYTw4cPZ926dfj5+TF9+nSGDRuGXq9n9+7ducqVPQ2X76ay7EQox0MSkEC+78WrgH3FZydPChIabbqfSNy2r3Cu38Wm0VPJpXRqXAul8kFwi9FopHLlymg0GipUqECXLl2YNWsWly5deqbh3pvP3qHfqrMcvhGH3iTk2sbV/e/Yoetx9Ft1ls1n79js55dffuHSpUtWxwRBYM+ePXTu3Bk3Nzdee+01tm/fTqNGjQgICCArK4vTp09z79499uzZw5EjRwps9AIDAylevDgGg8EySXfv3h0nJyeSkpI4ceIEk0a8y92fpyHbN40x7SrTvU5p2vh50b1Oaca0q8zp8a1ZMeh1y+Q2atQonJyckMlk6PV6S3pAXjzqv/Py8rL4757G6B07dgwfHx8EQSAyMtJi9BITE2ndujWNGjUiLS0NhULBjRs38jV6d+/e5e7du3zzzTc2z8fHxzN37lxmzZpVKKMH2aXD/vrrL06dOsXXX39tdW7Xrl3cvn37sWXmcpDL5axevZrQ0FBkMhm9evWiU6dOtGvXjg4dOjBo0KAi+zuoVcaNFYNe5/T41gV6L14JRDt28iDhvk6sPHm/WHbCPtG1aX+R7Ehny49r0/6ia7MBIiBKFCqrn7IT9ollJ+wTK0/eLybe14n37t0T33jjDVGhUIg1a9YU4+LixPXr14uDBw8W69SpI7q5uYkSiUSUSCSim5ubWKdOHXHw4MHi+vXrxYSEhAKNV6/Xi1qt1ua5TWfCRb8p+y3jKsiP35T94qYz4Vb9zJ07V5RIJGKPHj3ElJQU8ZtvvhFr1qwpyuVyUSaTidWrVxenTZsmJiYm5hrD5s2bRYlEIq5Zs6bAv4N58+ZlP1+JRNyzZ4+YnJwsvvHGG2JkZGT2fW3aZPU7qVSpUoH6NRqNYrFixcTu3buLQ4cOFaVSqbh582abbdPS0kR/f39RpVKJSqVSfO+998SkpKQC30NeTJkyRZRIJGLv3r1Fs9ksiqIoms1m8ZNPPhFlMpno4+Mj9uvXT5RKpeKpU6cK1OcHH3wglihR4rHnGzduLJYtW/apxr1ixQpRIpGIR48etTr+3nvvic7OzpZ7KQhbt24VnZycREdHR3HMmDGiWq0Wvby8xCtXrjzVGO3Yxm747OTLsI2BYrmJBTcWD/+Um7hP/GhToKUvs9ksTp8+XVy4cKHNa5nNZvHixYvijBkzxLfeekt87bXXRJVKJQKiQqEQS5UqJbZs2VL88ssvxSNHjohGo9Hq87Nnzxbd3d3FkydPWh2/FJki+k05kGt8jtVbijJHd1HioBbl7qXEYp1G2TB+B8SgqBRREARx/PjxlvHk/Li4uIgdO3YUf/311zwnuzNnzohSqVT88ssvC/TczWaz2KlTJxEQlUqlePPmTZtt6tWrl+sLydmzZwt0jcjISNFgMIiiKIoTJkwQJRKJuGjRIsv54OBgsU2bNqJUKhU9PDzEGTNmFGpCzwu9Xi+++eabokwmE1esWGE5vnbtWtHJyUnUaDTi0qVLxR9//FGUSCTitm3bCty3h4eH+Omnn9o8t3fvXlEikYgXL1586nvo1auXqFKprL6Y6fV6Ua1Wi6NGjSpUX0ajURwyZIgolUpFPz8/sW7duqJUKhW/+uqrpx6nHWvsPj47+WJL7LSgFJWoZWZmJkeOHOHIkSP8888/3L59m6SkJMxmM46OjpQpU4YaNWpw48YNrl+/jlqtZty4cUydOhWZTPbYvCZDQgQK91JI5AqMSVHc2zoRr95fo/R+EM4tkUCj0irOzx9GeHi45bhCoWDr1q25xGFtERUVRaVKlWjfvj2///57vu0TExOpVasWsbGxlC1bluvXr6PRaGw+l/LlyxMfH2913Gw2P5Gw7Pz58xk3bhy9e/fmypUr3Lhxg7Jly7Jw4UK6d+9e6P4ex61btywBIqdOnaJmzZr8888/9O3blzt37vD+++/z008/cfDgQbp27crMmTOZOHFigfq+fPkytWvXJiEhIdeWqCAIlsLUv/7661PfhyAIVKyY/a6EhoZanvnSpUsZPXo09+7dK7SafXh4OF27duX69es0aNCAf/75hwoVKnDixAlKliwJZFeXeRLBXDvZ2H18dvLlSUOjRaOeFi5JVPMunA/FFhqNhq5du7JkyRJOnz5NXFwcJpOJO3fuMGfOHOrWrcuNGze4cSO7PmhWVhbffvstbm5uLPlpnU3dOgAHz7JI5Dk1CSVIkGBKibW+DxHOR2nx8q1A/fr1qVChAgqFArPZXKAKHJmZmdStW5eKFSvy22+/5dv+r7/+olSpUsTGxtKtWzfu3Llj0+jlPBdb9Sfv3y98ZX1BEBAEAScnJ3bs2EFMTHZQU/Xq1Z/a6OUUgTaZTGzevJmqVatSvnx57t27R+nSpWnTpg0NGzbE29ubqKgo1qxZw9WrV+nevTv+/v4FNnoAX3/9Na+99ppNg/PZZ5+h0+nYunXrU91PDlKplPPnzxMbG0u/fv0sxz/55BNKlSpF7969C91n+fLluXLlChs3buTatewi2Glpafj6+rJy5UoiIyOpUKECx4/nrsRjp2DYV3x2Ckx+6gw5SCTZSbDJR1cTf3oXzs7ODBw4kCFDhtCgQYNn9k1Vr9ejVqtRKBRIJBLkcjnFixfHt4M/9zzrYzDbHnTSn8vQXjmKaNLjUKICJQbORuqgtmqjkksZ066yRZ/MbDYTGhqKp6dnniH7giBQrVo1kpOTiYyMRKXKW+D0YY21WbNmMWHChHzv22QyoVAokMvlGAwG0tPTcXV1zfdzOaSnpzN27Fi2bNmCKIr07dsXLy8v5s2bB2RHfMbGxuLu7l7gPh+lffv2HDt2DF9fX+7cucOYMWOYO3cuo0ePZtmyZZQsWZItW7ZYijjHxMRQsWJFmjZtWmgxVycnJyZOnJhLvDcyMpLy5ctbZImKkmPHjtG2bVuWL1/ORx99BMDp06dp1qwZAQEBj02pyA+TycSwYcPYuHEj7u7uJCUlIZfLMZlMVKpUieDgYJsre7teZt7YDZ+dQlGY0Oh/D+/mgw8+QBAESxrDsWPH8q2k8aRotVp69epFq1at6Nq1K1WqVEEikRRIn0wUzOijg9FFXsG1cS+bagbd65RmYd862e1FkZs3byKRSKhc+fGqn506deLkyZPcvn3bsk1lC0EQ6NixI4cPH0YqlXLgwAHat2+fq52tCc1FyGDuyJ50bNmU3bt32+xfp9MhiiJq9QOD/qj+3ZgxY5g4cSK3b9+mWrVqljw1qVTK4sWL+eSTT/Idi63J9fr169SvX9+So9apUyf69u3LqFGjMJlMfP/994waNcrSPjMzE19fX7y8vLh69WqhtmyPHDlC+/btyczMzPUlo27dumRmZj6zOplff/01M2bM4MKFC9SqVQvIlpi6c+eO1ar8Sbh9+zbvvPMOV69etRyTyWRs3LiRAQMGWI49r1Sdlx274bPzRCRl6Pnlwl2CY+/n0inLmfSSkpIoWbIkRmO2rtcHH3zAypUrn7tvYuiGQI4Fx+ffEEg6uBRFcV9cXu+a61yDUiqGVNCzd+9edu/eTUJCAr169WL79u02+xo9ejRLly7l7NmzvP764+sexsbGUq9ePe7du4eLiwuXL1+mbNmyVm3ymtCUcilZOh2NyjgyuUfDXBOaXq+nYcOG+Pn5sX37dvbt28e4ceMICQmhUqVKzJ4922orMzMzk2XLlvHzzz8TFBSEyWRCqVSSmZmJVCot9OTaqlWrXPlugMWP93CCuiAIVK5cmYyMDO7cuZPvCvlRWrVqRXJyMkFBQVbHt2/fTv/+/blx44ZVFZiipkWLFgQFBRETE4NGoyE+Pp5SpUqxZMkSRo4c+VR921rJKxQK0tLSUKvVhd6ReZX1Mu2Gz84zpX79+gQFBeHi4oIoity+fbvQ1TyeloKs+HJI2r8EiUJJsXYf5TqXdeMk8XvmWv4vkUjo1KkTCxYsyDWZrlixgpEjR/Lzzz/Tp0+fx17v0KFDdOnSBaPRSI0aNQgMDMw12T/NhCaKIv369eP333/HZDLh4uJCSkoKzZs3Z+nSpfmWEktPT2fr1q1MnjyZwYMHU7f3qEKNpb2XliWjcgf/DBs2jJUrV+Y6/uabb3Lx4kXCwsLw8vLKc2yPIggCKpWK5cuX4+/vbzluMplwd3ene/fubNy4sVB9FhaTyUSpUqXw9fXln3/+AbK1AtevX09aWtpTCfyuW7cOf39/lEoler3eUvKvRo0ajPlxF3OP3C6QXmYO2eXJXo1KLY9iN3x2nil79uwhLCyMjz/+mMqVK6PVarl9+zYuLi7PbQwrTt5m4ZGbuZLVzdpUdBFBqCs2RCJ3QHfnEgm7v6N41y/RVGpk1VYll/JZm0pc37WYn376CaPRiFQqRaVSWVZC7u7ulC9fnhIlSrB//36++uorZsyY8dhxTZ06lenTpwPQv39/mwEXhREAzuHhCe2bb75hxowZlm3L6tWrc+rUqUL76zIzM1l6MIh1l1ILNRaJ2UixiONc2LEEiUSCRqNBFEXMZjMZGRlWhmDgwIHs3LmTixcvUr169UKND2Djxo34+/uj1+uttkeHDBnCzp07SU1NfS7K8rdv38bPz4+RI0eyePFiBEHAzc2NXr16sXbt2ifu12g0EhISQmJiIomJidy4cYPt27ejcyyBvP0XxJ/bg/YeQ1uIAAAgAElEQVTKUQwJd3Cs2sJSWcmUGkf0Cn8kigdfqFwa98Staf8ii7p+2bAbPjvPjczMTKvQ78dFKhY1tqSVAMyZaSTsnoUhPhxEAbmrF87138a5TsdcfTwsrXTs2DF69uxJeno6UVFReHt7c+7cOQ4cOMCxY8cICAhAKpUiCAJKpRJvb2/8/Pxo3LgxnTp1om7durRr146//voLgHbt2nH16lX27dtHvXr1LNfMSSOJO/ubzQnNkBhJ0r4FlihUB++KuLf7CIfivqgUUipH/sHeDT8C2VtiAA4ODiQlJVlV0SkIj0tpMaXGkXRoGYboYJArcKzSFPe2HyKRPqQMYDLwetop3uvSEhcXF1xcXPDy8qJMmQd6b1OnTmXmzJkcOnSINm3aFGpsOdSrVw8nJyfLc4XstIkqVaqwadMmBg4c+ET9Pgnbtm1j4MCB7N69m27durFhwwaGDh1KRESE1X0XBTmpOtrg0yCRkBV+AdFoyGX4fL/cY/174dWQILKF3fDZea5kZGTw2muvoVaruXXrlpV/51lS1PpkcXFxrFu3jvHjx1t8lunp6fj4+FClShXOnz9PamoqBw4c4Pjx41y4cIHw8HBSUlKsVCnatm3LnTt3CA0NRa1WM2vWLD799FMkEkm+E5qgy0DQaZG5eoEocP/CH2QEHaKU/1IkQHF9NF3d4/Dz80Or1VoUMUaMGFHolc/jnl/cjmnING54dPwYQaclbvtknGp3sPKR5je55mzhrV69+okLZecoNezdu5fOnR+UzatatSoKheKxhaOfJcOGDWPDhg2Ehobi6+tLpUqVKFasGOfOnSuyaxRELzMvwwevpl7ms1/327HzEE5OTpaixtWrV+fGjRvPZfvp45YVOXUr8YmS8G3pk5UoUcIq1UAQBGrVqoWzszOnT58GwM3Njf79+1ukavbs2UOvXr0wm82o1WqaNWtGWFgYoaHZCvVZWVmMHj2ab7/9lgXLVnHyphpRBE2VbOUA/b1QzMZEyzWlKiekquwcSVEEiURqWf2JQLqjDyPHDH7qCS0xQ//YPEhTWhwu9bsgkTsgc3JAXb4+xsRIqzaiCMdDEkjK0Ocay9GjR/nggw+YNGnSExs9gEWLFqFUKq2M3po1a7h58yZhYWFP3O/TsGrVKs6cOUPjxo2Jiopi586d1KtXj6NHjz7xqvZRCqOXGb1sCEgkqMrVxb3VEGSa7ECZHL3MnFSdVwF7Arud506xYsUIDg4mPj6eOnXqPNOi1Dk8aRK+DHOB9MlatGhBUlISly9ftmnIP//8c7p3747ZbOb1118nJSWFP//8k8DAQCB7C9LBwQG1Wo1Go2H10asUdHkaubAvkXO7k3z4J1yaPEiYLioB4LwmV5fXu6G9/heCUYfpfiJZYf+gLl8vVztbY7l+/TqdOnWif//+Fl/nk7J69WorRQODwcCoUaP48MMPc0XIPk9Onz5Neno6b731FnXq1LEUoC4qCqKXKdW44D14IaVHrqPk+4sQDZkk7p1nOW9LL/P/O3bDZ+c/wcvLi2vXrnHnzh0aNmz4XIxfYfTJREFAMOqIP7iCzMuHcmmvPYy/vz9nz57l7NmzlojVcePG8eeff2IwGGjcuDGLFi1CFEX8/f05f/68ZYvXaDTSpk0bvv32Wy5duoRWqyUqKor6bd5G/5iE+0fxHbMdnzE7KNZ+OA4lHnxrL6oJLa/JVeVTA2NiJFEL+hD94/s4eFdCXTl3svajY4mPj6dhw4Y0bNgwXyWD/EhNTSU0NJSpU6dajg0cOBAHBwd+/PHHp+r7aXFxceH48eMcPnyYOXPm8PPPP5OYmMj3339fJP0XRC9T6qBGWbISEqkMmaM7xdqNQBd+EUGf+VA/xiIZz8uC3fDZ+c8oU6YMQUFBXLt2jRYtWjyXaw5qXI7tHzamQ7USOMgkiCZr0VmVXIpcIqIPO0/clglkXDrA8OHD8fHxYc2aNZacxBzmzZvHunXr+O233yyRiLdu3eKHH36gR48eeHl5ceHCBQRBYMWKFaxatcrq856enhw5coTx48dTtWrVB/7CQggAA0gdVDjV7UTSvgWYtamW40UxoT1uLKIoELdjKpoqb+D7+S7KfLYVQZdB6ol1j+kneyw6nY6aNWvi7e1tFYjypEyfPh03Nzfq168PZNfq3LVrFxs3bnyieqVFTYMGDZg7dy4TJ07k+vXrfPHFF0ybNs1SVOBpwixcVE/gJsj50vfQdV1UCttt/59i9/HZ+U+pUKECFy5coE6dOrRv355Dhw4982vm6JPVf6MF8YInQ8ZMzpWEn5FUk3LlslMRRFEkPj2Lsav2s/GWhApVa+KikiMk32XZtBksWLCAt956y9L/9OnTMRqN6PXZRlWhUPD3338/VincFk80oYkiokmP+X4SMke3//VT8AntwIED/P3339SsWZPq1atTuXJlJBIJCtG24ROy7mNOT8C5XhckcgUyuQKnWm1J/WsT7q1y++tcVAoEQaBu3bqYzWYuX75cJIZp27ZtdOvWzfL/d955hwYNGtC1a+4iBP8VY8eOtZQ1i4qKYsWKFbRt25akpCTefPNNmzmNBSFbL/MeepOAKJiztTIf0stEKsNwLxSp0hF5sVIIugySD69E6VsTqcoRyP6y51fSuShv94XHbvjs/OdUrVqVc+fO0bBhQ7p37/7YsltFyebNm7l49hQA495cQqlSpazOF3P0RS6XIy1eHpcmvdFUeB0JECFxIOJ/VWAEoxHfURu5UbwUQVGp1PZxIzY2lq1bt1pt3fbq1atQRg8KNqHpIi4jU7ug8CqHaNST+tcmpConFMV9gMJPaFeuXOH7779HrVaj1+stuYqtRsxE6V4713anTOOK3LUE9y/ux6VRD0RDFhlXjqLwKp+r75yxdOjQgYiIiCJLZwkPDyc2NtYiCLt48WIiIyMtAUYvEr///ju+vr7Ur18fQRAICAgAeKr0hl71y7DwSLbae1rAz6QFbLOc0147jmvT/ig8ypByciNCZipSBw2qcnXw7PqlpZ0I9KpXtCkWLzp2w2fnhaBOnTqcPHmS5s2bM2DAgCKrnm+LS5cu8dFHHyGKIgqFguPHj+fK8ZJIJHg06oZD4/5IZA5IbKxMpAolAnDoehx/3UxkQodKTB/cAbM5O3LU2dmZkSNH5itblJGRwfnz5zGbzQiCQFZWFucPHcfg3Bqk8sdPaJ5lST78E+b7iUjkDihLVcarzzdI5Nn+w8JMaIGBgZw8edKSWA7ZKuFbtmyhdeduNP3+mM3PefaYRPKRlaSf/QWkMlRla1GszQe52onA6c0LOHHiBIGBgbm+aBSWs2fPotVqWb9+PaVKlaJs2bJkZmYyfvx4xowZg7e391P1/yyQSqVs3LgxV0RnSkrKE/dZ3ElJi8qeHL4Rh9ubA3F703auomM1264EiSS7tu6rlMoA9jw+Oy8Yx44do3379gwZMiSXP6yoKFu2LJGRD0Lu+/bty88//2zVZvPZO0z+9SLICp5nqJCKGM9vJ+LIZj7++GOWLl1aoM/t2rWL3r174+zsjE6nw2AwIJFIeGfeH1xMFIos9/BhBEFg9+7dLFu2jLNnz5KVlUW5cuVISEggIyMDd3d3/v77b6pVqwY8XR4kooCX4R7/LPqI33//nS5dujxBJ9YMGDCAnTt3YjKZKFmyJD/88ANr167l/PnzxMXFvRC+vUe5desWNWrUwGAwWB0vWbKkRQLqSXgR9DJfNuyGz84Lx/79+3n77bf55JNPWLx4cZH3f/z4cVauXMnPP/+MRqPB19fXouMHtieSyPnWqzbRZMC5bmeKtR9udVww6vioQiaTRrxb4PHo9Xo8PDzQarVA9krr1KlTqEv7FemEptPpWLFiBRs3buTKlSuIokjNmjV59913GT58OBqNhi+//JJ169Zx5swZS5UdeLrJVTTquLdlAsa42zg6OuLj48MXX3zxVHl733zzDd98802uwJC9e/cWiWF9FhiNRubPn8/333+PVqvFaDQikUiQSCSYTCaStIYnlhJ62tJ2rxp2w2fnhWTXrl306dOH8ePH89133xV5/x9//DF79uwhKioKnU5nJdeT3+pGMGRx94d38er9NSpf6yLPEqBD9YKVgBIEge+//56ZM2ei1+uzA0kUCiZPnmwRXn3aCS0uLo758+eza9cuwsPDUalUNG7cmOHDh9OrV69cK6OsrCz0ej1ubrlXAE8yFgcp3DuwjIxLByxGSi6Xs3PnTt55550C9/MoGzduZOjQoZZtZcBSL/XQoUNWpd9eNHQ6HevWrePTTz9FJpPhXrEOjYdO4/r/djyfVErIrs5QcF68/QA7doCePXuyYcMGZs+enWeh5yfl9OnT1K5dG4lEYmX08qpSkkNmyGlkGleUPrkLKYs8qFKSF4sXL8bd3Z2vv/4af39/MjIyKFmyJPXr12f8+PGWdoXJPZRIsld679d25dCyqZQoUQJvb2/WrFlDvXr1CAgIIDMzk2PHjtGnTx+b24Fqtdqm0XuSsSjlEuIPraBdeRXDhg2z1AsVBMHKYKWmplr9vyCUK1cOs9mMg4ODpWCAUqnE2dkZR0fHQvX1vFGpVIwYMYL09HSqdf0Qp66TuZQgoDcJuQKIdP87duh6HP1WnWXz2TuP7ffhVB2lXIpKbv37VcmlKOVSOlQrwfYPG7+yRg/swS12XmAGDRqEVqtlxIgRaDQaxo4dW2R9h4aGMnjw4FzHC1ICKuPKURxrtH6srmBeJaBWrlzJhAkTuH//PkOGDGHJkiUWGaIc1YRHDdKgxuWoVcaNH47e5NDVaERRRKp4sPWlkksxmQXUKaFEHVrLhOmX8fHxoU+fPnzxxRdFWrkkZyz5iRE3e82NX2eMoFIxBbt2BZKUlMSWLVtwds6O7Ozduzft27dn8+bN1K5dmwEDBjB37tzHXvdRcvIp58yZw+jRo5HL5UyYMIEJEyY8t/qvT8uuoDgy/TphLMAKWhQhy2hm5v7sLfnHGa2cVJ2C6GW+yti3Ou288CxatIixY8eyfPlyPvoot05eYcnIyMDZ2ZmYmJhcquj5afeZ0uKJXvEBpT5aicLt8ZGDD6u1Q3b6xNixY0lOTmbAgAGsWLGiUOH8giDQsGFDLl6/RcV2g+jY359rN8O4G3aTuJsXybh8hCrlStO/f39GjRr1XGSfHje5dqtZgtdr+iGTybh9+7ZlRbZ79268vLxo2rQpAQEBvP3229y/n13NRaFQEBwcjK+vb67r2FJ610bfwjXpOrF3bvHbb78RFBRk5ZN80cnxmWZm6Ug6tAzdnUsIugzkbt64txiMusLr6KODST21GcO9UJBIUfnWxL3dRzi7e76SASlFiX3FZ+eFZ/To0VYrv3ffLXjgiC1+++03lEplLqMH+VdMybh6DGWZankavex+slcku3btYtSoUcTFxdGzZ09Wr15daKOUo6hw4cIFRFEk4dQ2lu5bgUKh4PXXX2fiR8MYOHDbcyn2/TAeTspcq1pBEKhXrx5arZaIiAirMT2s8t60aVO2bdtG586dLVufn376Kb/99pulTd5K786I0kZ4lC7LryenvFRGD+DHE6HZvjjBjNy5ON4DZiNz9STr9j8k7PmeUkOXIugycKrTMbv2qVRK8qEVJP2xCEW/b1l2IvSVkxIqSuyGz85LwaRJk8jMzOT9999HrVbnmxuXF3/88YfNlQXkXzFFe/UYro3zv/b95Hh8fX2Jjo6mS5curFu37omU52/dukWvXr2sZHXKlSvHvHnzaN26daH7e9Z069aN4OBgQkJC8jXw48ePt2xL6nQ69uzZw6pVqxg2bFi+gRo526uxci9G/36HSSbVS+OzetiPLHVQWeXeaSo2RO5aAv29UBz9rIseONfvQtzWiXmqXdgpGHbDZ+elYebMmWi1Wvr27ZtLd60gTJ8+nWPHjnH58mVq165NSkpKLiXyhyumPIru7g3MGUlo/JrlfSGTgT+3b6ZJtWqcP3++0MnUJ06cYNGiRZw4cYK0tDQkEolF2BayNfxeRKP36aefcuDAAQICAgrkVzxy5Ag3btwgLCyM4OBg1q9fz5gxY4hxrMDPwfoCRY8W1Pf1IpGXH9msTcGYHI2DZ+4vZvqoayiKZx9/FaWEihJ7VKedl4pFixbh7+9P165dOXbMdjWRx2Eymfjrr79ITk7mzJkzFC9ePFficK/6ZXIlGOegvXoUTeU3kCrz8c1JpZzaMJeDBw8WyOgJgsCGDRto1qwZKpWK1q1bc+PGDUaNGkVSUhJGo5FWrVoB2Rp/CQkJBbvh58iiRYtYunQp27dvp1GjRgX6TPHixXnzzTcZPHgws2bNIjY2lh1Hz7Hthp6oX+dw94d3iVzQm+ifPuR+0J+Wz2XduUT0yuFEzuvJva0TMaXFk2UUmLk/mMt3U/O44ovB49QuRLOJxN/n4VSzDQoPH6tzhvhw0gK24d5qCPBqSgkVJXbDZ+elY+XKlfTt25cOHTpw5swZAGJiYvKUDgJo3769JaBEIpHg7+9PqVKlyMzMZNy4caSmpjJr2ldkhgZiK17To+MnFH/78zyvIUGkY83S1KryWp7tMjIymDlzJjVq1EChUDBs2DAMBgOLFi1Cp9MREhLC9OnTKVasGDKZjH///ReA6Oho1q2zrX7wX7F7927Gjh3L3Llz6dmz51P19dvNLPRmAZfGvSk9Yi2+Y3fi1WsKqX9tyhbizUwjYfd3uDUfhM/obSi9K5GwJ1viR2cys+xEaFHc0jPFlh9ZFAUS980HmZxi7ayLIhhTYojfMQ33th+i8nmQN/qqSQkVJXbDZ+elZMuWLXTp0oUWLVqwZs0aKlSowJo1a/L8TIMGDdDpdEB2YewcrbZTp06xcOFCSpcuzYIFCxjVtgoSoXCyQDkIRj2DG+QOmgGIiIhg1KhR+Pr64uzszOzZs/Hx8WHv3r0YDAbOnz/P8OHDc4Xjx8fHk5qaSrly5YqksHNREhgYSO/evRkxYgSff573l4L8eNj35eBZFok8R1lCggQJppRYMm+ewaG4L45+zZDIHXBtNgBjfDjGpCgr39eLzKN+ZFEUSdq/BLM2Fc/uXyGRPThvSosnbttkXJv2w6lG60f6ebWkhIoSu+Gz89Kye/duatWqxQcffIBOp2Pjxo15tndwcMDJyQmZTMb+/fstCdWHDh1CEAQyM7OFOaeMfI/EwysRjYWbQKWCEVPgDob3eeB7PHfuHL1798bDw4Ny5cqxY8cOWrZsSVBQEPfv3+fAgQP5+ioXLVoEYDPv8L8kMjKS5s2b0759+yIRfH3U95X05zIi5/UkZtVwZE7FUFd4HWNChJX6g9RBhdzNG0NCdu3VolKdf5Zk+5EfTL3Jf/6IMSkKr15TrfIzTfcTidv2Fc71u+Bc1/odeRWlhIoSe3CLnZeWEydOWEU7BgYGkp6enmc0YYUKFWjTpg0lSpSwHFu1alWumo+amH+Z8vYC5h8NL1AJKKVMSvQfK9FdOUyMyUTp0qVJSUlBp9NRvnx5hg4dyueff/5EqgE5ShVFmcD/tNy/f5/atWtTqVIl9u3bVyR9Pur78ugwkmLtPkIfHYwu8goSmQLBqEOmcbX6nFTpiGjIAl4O39fDUkKmtHgyLh0EmYK7PzxI0ynW8WNMKbGYUu+R9vdW0v5+oFbi+/kvr6SUUFFiN3x2XlqkUilVqlQhNDTUoh+3efNmRo4caWnzaPJzqV5f4V2nsiUUPDY21pJE/TDx8fEMalSWCm5yRi3fx31n32y/n/zBNmROlZLmFT0odi+QZbdOWfyMMTExVKhQgfPnzz9RGkMOJpOJiIgISpQo8VyS0vMiJCSEzp07s2PHDnr27Ilareaff/4ptBKCIAiEhoZSvnx5y6obbPu+JFIZKp/qaK8d5/7F/UgVKgR9pnV/hkwkDg/Kzr3ovq+HpYTkrl6UnfD4Lw5uzQbkOvaqSgkVJXbDZ+elpXnz5ly5coXw8HA2bdrEjBkzmDhxIoMHDyY02fiY5GclNwOiWBEQRcsqnsQcXo9arSYrK8uqb1EUqV+/PsHBwZQpU4a5S5aT4l7FUqVEgYmYa+e5sncNa0KuolarKVu2LLdu3bIYv7CwsFz9Fpac1d6QIUOeqp+iYN++fURGRtKwYUMcHByIjo5+ovJgERERVKlSBQcHBypWrMjrr7+OIAhElWkDeNr+kCBgSolF4VkW7ZWjDw4bdJhS7lmF/78Mvq+PW1bk1K3EJ1K7UMlljGz5ciXsv2jYfXx2XnrKly/P1KlT0el0+Pv7M3f3WfqtOsvhG3H5Fv4NLt0Bh2rZwqCvvfaaVf3N69evs3HjRiIiIujTrTNN3LQk7J3H3nFv8dP7bxCw5lsa1PTjzJkzaLVa/vjjD+RyuaVaiYeHBx07dnyqe1uyZAkA48aNe6p+ioLdu3djMpks+YQHDx58on68vb1RqVQYDAbLM96yZQsuwn2UcilmbSra6ycRDFmIgpmssH/R3jiJqlwdNJWbYEiMQBscgGgykBawDYVXOUv4/8vi+6rt48akzn6oFYWbgrOVN/zs5cqeEnutzv8AW7UHC6q7ZSdvbEnnmLPuk7R/Mbo7F5GqXXBvMRjH6i0t5+UIjGlVjuVj+3Pt2jWr/rp164ZOpyMgIICMjAx8fHx45513+OKLL3JVfxFFkbJly/L2228TFhbGiRMnMBqNfPzxx0+sKyiXy3FyciI19dnmp+X3ThoMBjQaDWazGaUy+x399NNPmTNnTr59C4LAvn372LBhAwEBARahWEEQkEqllC5dmoCAANTuXjT9/hiZ6Skk7J6FIT4cRAG5qxfO9d/GuU72l4isO5dIPrQCc3o8DiUrU/ytMcjdsn22SrmU0+NbvzR/R3Ypof8Gu+F7juRde7Dgult2bPM4sdSEPXNAFPHo/CmGuDDif/kG70FzcfB8UF1EIhjJ/H0mbkI6t27dsgp2qVGjBgMGDGDUqFE4OTnlOQaDwYCDgwOCIFC1alXu3btHeno6hw4dol27dgW6j7i4OD777DOqVavGtGnT+OSTT/jhhx8K8SQKTkHfSdXtv1jy9ee4u7vz1VdfMWTIEDw8PB7bb2BgICtXruTo0aNEREQglUqpUKECHTp0YPjw4Zw7dw5/f3/q16/PoUOHLFJIT6P0np/q/IvK5bup+apdtKriyciWFe0rvSLCbvieE/Zvds8eW5OmYNARtagfpT74EUWx0gAk7p2PzNkD95bvW9pJAOf0cK4sG2XVp4uLCykpKYUO4ADIzMykTJkyiKKITqcjJiYmV4k0W5w/f96q+kmFChVYsGABXbt2LfQY8qIw76Rg1NNAHsXO7z61KccUERHBihUr+OOPPwgJCcFoNFK6dGlatGjBBx98QMuWLa3aJyUlsXjxYiZNmmRZQcLTKb3bUp1/mbBLCT0/7IbvOfC0Ktp2HnD37l2uXr1K+/btrYxRYoaept8fy+XPM9y7zb3NX+L7xS7LsbRzv6KPvIJX72lWbWUIfFY+AY1U4OLFi/z7779ERUXxzz//2FRyKAjh4eH4+WVL9JQtW5ZGjRpx/PhxQkNDrSIaH0ar1eZaWTZs2JBz58490Rhs8bTvZGpqKmvXrmXXrl0EBQWh1Wrx8PCgUaNGDBo0iN69ez+xWoT978XOs8Ye1fmMCYpKZeb+YJt/xNrrJ0kN2IY5PQGZozseb422lCTKqT1Yq4zbS/sN9lnwxx9/MGLECEqXLs2UKVN47733UKlUjy38KxizkCjVVsekSg2CIXe0pUIuR+nXgqHNyrNv3z7OnTtHTEwMt27demLDV758eQ4cOECbNm0IDg4mODgYuVxOcHAwNWvWtPkZR0dHlEolen12An2ZMmU4evSozbZPQlBUKl98M4eUS4cxJNzBsWoLincZYzl/P+hP0s/8glmbgrJMNTw6f4bc2YMso8CU3ZcYM7gXiTf/xcnJidq1azNjxgyGDh1aZOkWOcbLvkNi51lhN3zPmBzdrUfJCr9Iyon1eHYbj0OpypgzknO1yak9+KjPIj09HbPZXKBtsxcdk8lEamoqKSkppKamkpqaSlpaGmlpady/f5/09HQyMjLIyMhAq9VaVj13795lxIgRjBgxglq1alGm9yT0JnWu/qUKNaL+kVQFfSZSh9xtdSaBJRt3MbrDNEwmEyaTCaVSyYEDB0hISKBEiRJ4enpSsmRJnJycCrz9WbZsWWQyGWZz9nsgCAJBQUGPNXwAKpUKvV5P8eLFuX79er6+xcLw44lQBI07rm/0JSv8AqLxQVFuXcRlUk9upET/71AUK0XykZUk/j4X74GzARCRUaXHKP4Z3rJIld0fpaBK73bfl50nwW74niEP1x58lLS/t+DatD/K0n4AyJ2L52rzqO6WXq9n6dKlTJs2jX79+rF69eoiH3NmZibJyckWI5RjiNLT07l//z7379+3MkSZmZlkZmaSlZVFVlYWer3e8mMwGDAajRiNRkwmE2azGbPZjCAIiKJoFUAikUgs8jsymcySFiCXy3FwcLD8JCYmWj6XExWYlZVFaqYByG3M5MVKIwpmjMnRFh+fIT4chaftSTvLBHq93nINvV7PokWLEEXREsqfc+7h8SoUChwcHFAqlahUKjQaDRqNBicnJ4KDgy1GL2fcq1evxsPDA09PT0qUKEGJEiWscuJy/Gi3b9/G2bnw4fmrV69mzpw5LFu2jLZt21qO57yTmspvZN/fvVDMxsQH9387EI1fM0vgj+sb/Yj+cTDGlFgU7iVBKiVB7oWTR+Er0BSWWmXcWDHodbvvy06RYzd8z5DHbb+Jghl9bCjqio2IXjEM0WxAU6kxbq2GWtXqg+ygi/V/haC7uJd58+ZZjMv58+eZMWOGxRBptVqLIcoxQjqdjixBjr50HczO3ohyFWadFmNCOJnXjmPKSLEyRFbXfWhSz/lRKBSWH6VSiYODAyqVCpVKhYJ8cuMAACAASURBVFqtxsXFBY1Gg6OjI46Ojjg5OeHs7IyzszMuLi64uLjg5uaGq6sr7u7uuLu74+rqikwmy/M5xsbG8ueff3Lq1CkOHDhgdU4QBFJTU/GVimAjHkLqoEJTpQmpp7bg0elTDPFhZIaew3vQXJvX0t9PsWwz5jyTAwcO5ArOyMjIIDY2lvj4eBISEkhMTCQ5OZmkpCTS0tJITU21fFnw8vJCEAQSExMtOXAnT57k9OnT2QZR5ZwtReNZDqnKEfRZCFXaoAr5iwoVKqBUKlGr1ZZnm/NMc55lsWLFKFasGMWLF8fT0xNPT0+uXbvGrVu36NatG40aNWLZsmX4+fnlqQVnwepdyP63MSEi2/Dx/LXgbCm927HzNNgN3zPkcbpbZm0qCCYyQwIoMeh7JFIZCbtmkHZ6O+4t3rNqqzMJzF25hbg91vlS169fZ9GiRVYrjZwftVqNokRFMks3Qu/siwQRqST7Vy0DNJWb4NpsINXcoWdVJ+qXK46bmxvFihX7T6v/x8TEcPDgQf7++2+uXLlCREQEycnJmM1m1Go1JUqUwNvbm9jYWKvPxcXFEfrvSZT13kFvyr28LtZ+JEn7F3P3h4FI1S54tB9plcqQg4MMzPfvWRQccti8eTMajYaGDRtajjk5OVGpUiUqVapU4PsTRZEjR47wzjvvYDAYWLz5Ny7qvWymErjUaIHYajClpWm8lnULkiNIT08nPT2dtLQ0YmNj0Wq16HQ6dDqdZXWds6rOMdqZmZkcP36cqlWrUrp0aXz6TEav9LE5PgDVa/VJ3DMH57qdkLuXIi3gZ0CCaHpQsPtlqIdpx05e2A3fM8RW7UEAyf9Wdc7130bulF3H0bnBOzYNH8Bb3XsxacFHzJ49m82bN2MwGGjSpAmnTp2y2f+jYeqPmgITUhDhSgrcOq9jUnEnBtV4fgVvo6OjOXjwIAEBAVy+fJmIiAhSUlIsBs7b25vKlSvTvn172rRpQ7NmzXBwcMBgMLB8+XIuXrxo6UsqlfLee+8xZ8kSms05Tu67BZnaGa+ek/Mdl0Qi5eKu5SyeU4YFCxaQmZmJt7c3Bw4cYO3atchkMsqVK0eLFi14//33adYsHyX2XP1LaNeuHUlJSQyYtpzFVyTozbZz1kyiBJAQJbqT4NSESX2G2AzeSEtLQyaT5fIBNmvWjICAAORyORKJBJlMRnR0NKboOFSvPd7wqcvVwa3ZABJ2f4egz8KlQVckSjUyZ+ucvRe9HqYdO3lhN3zPkEd1t3KQqZyQPeLTs5UblUP83QiOHr2Es7MzVapU4dq1awQFBbF8+XJGjBhh1bYwoeCiCFlGMzP33wAo8qi4qKgo/vzzTwICAiwrOFsGrlOnTrRq1cpi4B4mMzOThQsXsmHDBkJCQlAqlZQpU4bY2FgcHByYNGkS7du3Z9Lnn6LLLIvUp44N05c/OYV/izurmD59On5+frz//vtMmTKFkSNHIggCe/fuZdu2bRw6dIh169b9X3v3HR5VmT1w/DuTSTIppBCSUEJHCEIAIbhIaFKX4iosEopZEZeygAiKAiqC+1Mg0nGRJqJUKaJIUYoQAgLK0hNKiARIiEAqIW36749sBobUSYYkmPN5Hp8H5t773jtMnJP73vecg0KhoE6dOnTq1ImQkBCef/5584KXkydPMnDgQHbu3EmLFi0szrXt7G0uOfqTXcrP6OLFi3Ts2JEOHTqwY8cOIKcN0ooVK8yLgPR6PUqlEnd3d7p06YLjMy04e6/wc1Zp048qbfoBoEu+xb1jm7H3rmexz5NQD1OIgkgen42kpqZy/fp1PDw88PT0pEqVKizaG8myozfJ7/stNXw9WddO5eSS2alI2PZv1HUC8OgUYrGfSmHi/rFN3D38oC2JUqlEoVDg6OjIhAkTmDVrFgqFosjkX13yLeJXj8fFP4hqL0y22JZf8q9Go2HBggW89tprhbbTuXnzJj/99BPHjh0zB7jU1FQMBgPOzs7mABcYGEi3bt0ICgoqMIcNcp6fzZ8/nw0bNhAdHY2zszOdO3dm8uTJPP/880RGRtK8eXMcHBwwmUzm55RjPwjloKKFzZKfo6Oj8fPzQ61W59nfaDSyd+9eNm7cyJEjR4iNjQWgdu3aBAUFYTQa2bx5M87OzmzZssXcc6+gzyjt1E4yLvycb3pBftcYHh5O7969zT0EmzVrRlRUFHq9Hjc3N0wmE2lpaRbHt2/fnufHzWLrpUyytTowGkg9uhHD/SS8er8BSjswGtClxGNfrS6GtAQSdy3A0a8pnp0f9AJUq5RM6tFYnruJJ5YEPhuZNWsWM2bMwNHRkezsbAwGA3YuHtQZ/zVGRd7FGyaDnuQDK8m4eBiFyh4X/454Pv8aCpXlHY+jSsmRd7rwxsjhbN++3Vz5P5ednR1du3Zlz549jN10ttByT3e+mY5Jr0Hl7pMn8D1a7ikqKooXXniB6OhovvrqK0JCQrhx44bFHdzNmzdJSUnBaDSaA1yTJk0IDAyke/futG/fvthJzMnJycybN4/NmzcTExODq6sr3bp149133+W5557Ls3+vXr34+eefzaslO3fuTFhYWLklPxuNRsLCwli3bh3h4eFcu3bNvE2pVDJ8+HBWrVrFmA2n8/2MMq8cA4XCnF6QX+BTKKB7E2+M4Sv48ssvLbbZ29uj1+sxmUx4enpSq1Ytrly5gsFgwM3NjV27dhEUFGRO9L9zaB33ftlkMYZ70BDc2r7I7Q1T0af+gcLBCdeA7nh0CkGhfPAz/KTVwxTiURL4bOTixYu0aNHC/EWsVCpZuXIlvzq0tEntQZPJxEcffcScOXMwGo3odLr/7aPAZDLh5lML79eX/+/ZUF4ZFw+TGXUce6/a6FP/yBP4IOcL7Zd3n+fLZUuYPn06Wm1OflduJf3cAFejRg0aN27Ms88+S9euXa0KcA+7e/cuoaGhbN26ldjYWDw8POjRowdTp06ldevWBR6XmZlJz549+eWXXwBwcnLi/PnzNGqU06pl/Ynr/N+ui2j0BlAUnGv3uJKftVotrq6u6HQ67OzszKtmq9Wqh+fwpWgNBf8wpISvw5CWmG/gA8CgI/Y/r2LMsrybe+aZZ5g7dy5dunTBzs7OfA0tWrRg165dFnfslbEephAPk2d8pXT27FkmTZpEeHi4+fmOs7MzixYt4vXXXycwNtUmfbcUCgUzZ86kTp06zJ07F6VSSVBQEEFBQTzzzDNsvpDM1kuZkM+XqlGTSeqRDfgOmUX6ub0Fns9oNNLshdctplUBfH19Wb9+Pe3atStxGapccXFxhIaGsn37duLj46latSq9e/dm2rRpNGvWrMjjd+/ezaBBg3B2duazzz7jjTfeICQkxBz0ALrWcWDMuneo3et1jL5N8yQ/O9gp0Gg0tKnlxIyBtq/tmJqaSqNGjQgICKBHjx60b98ef39/Fu2NZMWxOPJbgFNcKpWKfhM+Rh1zlFOnTnHjxg0MBgOtWrWiW7du5v0cHBw4ceIEAQEBeaaVpRecqOwk8JWA0Whk1apVzJo1i9jY2Jz8qG3bqFKlCj169GD06NGMHDkSeNB3q2TTb3n7bo0YMYIRI0bk2f/LS2fQGjLyHSs1fB2uLXuicsubJP8wnRFe/MdYJi37gJ07d7JhwwYiIyMxGAxWr2B8WExMDLNnz+aHH37gzp07eHt7069fP6ZNm1bsdAC9Xs+gQYP4/vvvGTJkCOvWrUOpVOLt7U3Pnj3N+/3222/06NGDjLQ0Xqx6l3emjGPb6TjW7zzElZibBPd/gQZV1Uz+ewfidZlMeOYA+HUu8XvLj4+PDxcvXszz+s00fb7pLdbQmxQ0eKYjC+fkFNNOSUnh0KFD+f47FnTXbOufSSGeNBL4rJCcnMzbb7/N5s2b0ev19O3bl/DwcHPpJpPJxLfffstLL71kcVxZ1B4sKHVCe+ca2TfOUeO14vWDM9g50LRpU5o2bcq7775LQkICN2/eLPZ15Lpy5QqzZ89m9+7dJCYmUr16dfr378/UqVOtLnV1/Phx+vTpg8FgYP/+/RZ3NsHBweY/r127ljFjxpi7njs4OODl6siojg34v6GdSbh1C3d/I38dNoyphmy0ej29e/fmq6++YtCgQVa/R2sV9BlZP86DVAJPT08GDBhg9RhSD1NUZhL4iuHo0aO8/fbbnDx5kmrVqvHee+8xderUPNN+CoWiwC+hx117sKDUieybF9Dfu0Pc568BYNJmg8nIH4lv5hsMH12mnlsJpDguXLjArFmz2Lt3LykpKfj5+TF06FCmTJlCzZo1rXo/kHNnPXr0aFavXk2vXr3YsWNHnnSHXLnPQB9+ZJ3bvPXo0aMkJSUBsGDBAlq3bm1+bpmVlcXgwYNp0qQJLVu2tPoarVHQZ2T9OLZJJZB6mKKyksBXAKPRyLx581i4cCF37tyhdevWee42rJVf7cETZ84TffEc704cTfCzdUu8Us6/uhuOqtt5ptJcW/XCpWkn89/TftuO/t4dqvYal2cMtUpJk+rW1YU8efIkoaGhHDhwgHv37lG3bl1GjhzJO++8Q7VqhU+tFiYyMpIePXqQkpLCN998U+QdmUKh4MqVKwQFBXH69GmMRqN5Of/MmTPN1Vh0Oh1LliwhMzMThUKBs7Mza9euLbRgtK0U9BlBThk7cv8zGTHptaC0s1hNCTmfkX8N62t3FkTqYYrKSALfI+Lj45k4cSI7duxAoVAwcOBAFixYgI+Pj83O8XDtwWfmvEbi2bMktfXAq+v0Eo85sI0fCw9E5Xldaa8G+wd5aAp7NQqVA3bO7nn2NQEDW9cq8lxHjx4lNDSUsLAwMjIyaNCgARMmTOCtt94yd9Iujffee4/Q0FDatm3L5cuXi93uxmg0cvr0aZYuXcrTTz+Ni4sLJpOJa9eu4enpSUpKCnXr1kWtVjN9+nSeeuophg0bRvfu3UvUaNZaBX1GAPd++cYivSAj8hDuQUPw6DjMYr+cz8j2VXakHqaoTCSd4X9+/PFHpkyZQkREBDVr1mTy5MlMmDDhsX4hDho0iG3btpmn5xo0aMDvv/9e4vEe5zL1AwcOMG/ePI4cOUJWVhZPPfUUISEhTJw40WYtc27evEm3bt24fv06ixcvZuzYsVYdP3nyZFasWMH9+3nrSGZmZuLi4oLBYLD4TKtWrUpISAiLFxfvGWhpSSqBEOXv8f+aW4FptVref/99vLy86NevH25ubvz666/ExcUxceLEx34XcOLECYtnUhkZGXm6JFhjXJdGqFWFdzooSH7L1Hfu3Em3bt1wcnKiV69exMXF8e9//5vMzEyuXLnCBx98YLOgt2DBAho0aIC9vT2xsbFWBz2AFStWMGbMmHy35Rbfzq10kuuVV15h3bp11l9wCY3r0ghHVcl+riSVQAjbeGICX2K6huWHf2fi5jOM+PokEzefYfnh30lK1xR63KOVTgCuXr1K3759cXFxYfHixQwaNIiUlBSOHj1K27ZtH9dbyOPRTgju7u7mxPSSyF2m7mRv3ceau0y9eU03tm7dSqdOnXB0dOSll14iOTmZuXPnkpWVRUREBG+//Xa+JbxKKjk5mdatW/POO+/w4YcfcvHixULLoxVk5cqVZGdn88knnxR5vod9/PHHpKamcvDgQavPWRLZ8Ve4f/hrlCbrVnhKKoEQtlPhpzrPxaayNCw639YtuavOujTxZmznRrSsbfmlcPbsWTp37szRo0cJCAhg06ZNzJgxg+joaOrXr8/06dMZPnx4Gb4bS8nJyXz88ccsXLgQX19fbt++bZNxH+3OUJCcZepKunnd59Q3Czl16hQmk4lWrVoxevRohg8fXuqE9cKsXbuWkSNH4uvry88//2xVi59H+fn50a5dO7Zt21bgPnZ2dpw6dYpWrVpZvN6mTRscHR05duxYic9fmOTkZDZu3MiiRYvMU9mrw6OYu/93SSUQohxU6MBn3Re45ZfDrVu3aNmyJUlJSTRp0oT4+HgyMzPp2rUrixcvpmnTpmXzJoqQnZ2Ni4sL/fr1M1fYt4XzcamFLlPXGQzY3b5E7N7VGBKuERgYyNixYxk6dOhjn+LNzMykb9++HD58mPHjx7NkyZJSjXf48GGef/554uPjC71btLe3Z+/evXTt2tXi9V27dvHiiy9y//59m/cj1Ol0VK1aFa1Way4B17NnT/bu3cv5uFTm7j5PeHQSarU631/qJJVACNursIGvNMWGX2pejWbNmlkkXr/xxht8+umnNp2ms4XEdA1/n/wpbnWexrtWHdzUKvyru/FyG9ssI89dpn7xVirnLkcTceo3tAnX0UUdoW2APxMmTGDAgAFlsqoRYM+ePQwaNAi1Ws1PP/1EYGDpF2q0atUKtVrNiRMnCt1PrVazadMm+vfvn2ebm5sb48aNY/bs2aW+nkdt2bKFIUOGmGudLl++nJCQELKzs6lfvz4JaVks/fGUpBIIUUYqZDrDudhUJn/0KSln9+fbpsWoyybl4JdkXj6KyajHwbs+1V8JJUtn5P92RfJmyAckPxT07OzsqF27doUKeg9P4ZqqteHGfRNcvguAWnWbhQeiCpzCLa7s7Gy+XPYZX331FZcvX8bOzg6dTkenTp04nHLXlm+nSHq9nuDgYL777jsGDRrExo0bbRJsY2JiOH/+PL/99luR+6pUKnNS+6OCg4NZvXr1Ywl8+/btw2QymZPme/XqhU6no0+fPty5cweFQsHLzT2pKukEQpSJChn4loZFY3T2xL19sLlNy8OSf/oPJqOBmiOXoVS7or0bY96mMZho0O9fvF87HXt7e1JSUkhISCjV8yNbK2oKN3dact/FO4RHJeb7fCcjI4PU1FRq1bLMu0tPT2fhwoWsX7+eq1ev4uTkRKdOnVi8eDHLli1j+/btHD9+nNOnTxfaAcGWjh8/Tt++fdHpdKUuAvCocePGUa9evWLdOapUKu7dy78L6+zZs1m9ejW//vorf/nLX2x2fdOnT2fNmjXs2rULX19fNmzYgJeXF/379zev6nV2diYyMpKOHTva7LxCiIJVuMCXmK7hcFQCzo3bA6C5HY1Bl2jerkuKJfPqr/iN+xqlY87zGMfqDy/xVpDmWptXR1XMfmG26JCemppKhw4dsLOz49y5c6SmpjJ//nw2bdrEtWvXcHFxoWvXrqxevdpcXPr+/fu88MILQM5zp8GDBxMZGVloQ9jSMhqNjBkzhi+++IKePXuyY8cOHB1t95mkp6ebm8EWh729fZ7mrLmqVcuZHp86dSqHDh2yyfV9/vnnfPLJJ6xevdrciLZNmzbcvHmT8PBwjMacnwGdTkdERIQEPiHKSIULfNtOxRW6XRMfhcrdh9QjG8iIPISdqyfuQUNx8Q8y76MAtp2OY3SnhsTGxrJ06VJ++OEHzpw5Y9MvXmudi03lkz2X8wS92xumoom/Yi5PZVfFi1qjVpi3Z+mMfLLnMi38PKjtYiIoKIjo6GiMRiN+fn7cunULd3d3unfvzqZNm/JNydi2bRt6vR6FQoGdnR1Xr15l9+7deQpq28qlS5fo1q0bycnJbNq0yaKYtK1MnjwZd3f3Yo/t4OCQb3J7rg8++IBhw4ah1WoLrAlaXNu2bWP8+PHMmjWL1157zWJbnTp1SEpKolmzZqSnpwMUel1CCNuqcIHv8u20Qlu3GO4noUu4gXPj9viN/xrNrcvc3foRDtXqYF+tNpAzVbh1/zG+eHsw58+fx2QymZuClqelYdFk6/PvgVa15xiqtOxV4LHZegOhO8+y573+Fs+pfH192bNnDy1atCj03AEBAcycOZMNGzagUCj49ttvady4ccneSBFyS44FBgZaVXLMGkajkbVr1zJlypRiH1NU4AsODmbEiBGEhoYyfXrJy8eFh4cTHBzMm2++ydSpU/PdR6FQ8Pvvv7N27VqGDBlS4nMJIaxX4QJfUa1bFCoHUKpwDxqMQmmHuk4A6joBZMWcNgc+gPOXrpLw3/9aHOvi4oKDgwNOTk64urri5uaGp6cnXl5e+Pj4UL16dWrVqkWdOnWoV68edevWtfo3/9WrVzN9+nRWrFhhnlqEB1O4JV1DazLB0WsppGmMKJVKnJ2d0Wg0mEymIoMeQGBgIIGBgfz+++8cPnwYf3//kl1IIeLi4ujatSsxMTF89tlnJaq+UlwLFy7EYDDw/vvvF/sYtVpd5J1V//79Wb58eYkDX0REBN27d+fll19m4cKFBe63Z88eTCbTY7kTFkIUrsIFvqJat9j71Mv7okKR56XgAX/DpZGWJUuWkJ2djYeHB2vXruXWrVvcunWL27dvk5CQQFJSElFRUfz3v/8lIyODrKwstFoter3eXD5MpVLh4OCAWq3GxcUFNzc3PDw88PLywsvLi+rVq1OzZk1q167Nr7/+yh9//MGQIUMIDAxk1apVPPXUU0VO4aaGfU1q2NfYV62FR6cQ1HXzBjO12pGlP57mpSYuREVFERUVZdUzuuTkZFxcXLh79y4rV66kVq1a9O3bt9jHF2bRokVMnjyZp556ihs3bpSoDZE1QkNDCQ4OtirBXq1Wk5GRf7PeXHPmzKFOnTpERETQvHlzq64pLi6OZ599lvbt2/PNN98Uuu/SpUtp2rRpmaWRCCEeqHB5fMsP/87CA1Fka3VgNJB6dCOG+0l49X4DlHZgMhG/6l+4BHTF/blBaOKvcHfLDGq8ugB7r5w7PrVKyaQejRndqSHXrl1j1KhRpKam8t9H7gCLotfriY2NJSYmhtjYWOLi4swBMzExkZSUFO7du0d6ejqZmZloNBpzkvLDGjVqxDNjF/FbARkEmvgr2HvVRmFnT8alcJL3L6fGa0uw96yRZ9/+rWqxMLhVPqMULjk5GW9vb+zt7dFoNKhUKvr06VPqpPmUlBS6d+/O2bNn+fDDD5kxY0apxiuO3ITzpKQkq7pBtG/fHldXV/bt21fofo0bN6Zhw4b8+OOPxR47NTWV+vXr4+fnx7lz54oMaG5ubkybNo1p06YV+xxCCNuocIEvMV1DUOhB7hxaZ9GmBTC3adEm3CDpxyXoEq6jcvPBo1MIzk3am/dzVCk5NsVyVafRaCyT367/9re/sXPnTlxcXHB2dqZ37978/e9/Z8vtqhyNyX8p/aPubP4Qp4ZtcQt8Ic+2bv4+rH61ZPVE//GPf7B582a0Wi1VqlRhy5Yt/PWvfy3RWADr16/n9ddfx8fHh4MHD5ZZyoi/vz81atSwevVlt27d0Gg0HD16tND9vvjiC8aOHUt2dnaxfma0Wi3169fHzs6O6OjoIqfHIyIiCAgI4N69e4/l+acQonAVbqqzmqsjnRt7s98wLE8vslwO3nWp8Y/5+W5TKHLKPD2aylBWU0q1atWiR48eTJkyha5du6L43zTswc1ngOIFvpyp2/x/HylN9+0lS5awc+dOtFotCoWCHj16lGiczMxM+vXrR1hYGOPGjWPx4sVl9u8bGRlJVFQU27dvt/pYZ2fnAhPYHzZixAjGjx/PokWLeOuttwrd12g00rJlS7Kzs4mJiSnWM+H58+fj5+cnQU+IclIhHzDYur1OWVq2bBn79u2jW7du5qAHud238/5zG7PTybp2CpNei8loID3yEJrYCJwatMmzb2m7b3t4ePDll18C0KVLl2KtcjWZTBatkn788Ud8fHzM1VI+++yzMn1ONW7cOJo0acLTTz9t9bGurq7mTuyFUSqV9O3bt1g1RDt16kRsbCwXLlwodiD76aef6NevX7H2FULYXoW744MH7XVKVquzYrZuKaj7tsloIDV8PbrkOFAosffyw3vAB9hXzdsJXW8w0qJKlnkhTnp6Oi1btsTPr/gdufv374+npyfdu3cv1v6jR49Gq9XyxRdfmEuOBQcHs2HDhjJfmJGcnEx4eDi7du0q0fEuLi7FCnwAc+fOpWHDhly9erXAKdwBAwZw8uRJzp49W+zFPMnJydy+fZvJkycX+7qFELZV4Z7xPaw03RkqotJ03wYTmVeOk/DdLHNKRkZGBnPmzOHtt9/GZDJZ3GEWJDFdw4SFG7Hzqo29i3uhRbEvXrxIYGAgRqMRlUqFQqFg+/btJZ4iLa2QkBD27dvHnTt3SnT8pEmT2LZtG7GxscXav379+rRq1Yrvvvsuz7axY8eycuVKjhw5wnPPPVfsa/jwww9ZsmRJsaZchRCPR4W848v1Srt6tPDzKLS9zpPUumVcl0YcuZpIli7/JPbCONmrCP3XC4TsmmtucaNUKunVKyfp/d133+X69ets2bIl3wBo2dewOppYDVB4UeyxY8eSlZUFgMFg4Nq1a9SuXTvP2GVBr9ezZcuWUhWRrlKlSr6rbgsyceJEpkyZkmdh1L///W9WrFjBd999Z1XQA9i6dSudO3e26hghhG1V6Du+h+W213nSW7eUpt3SK+3q8f333zN06FA0Gg1qtZqsrCxat25NZGQkCoWCKVOm5EkpKMmds/5ymEWpLZVKxSuvvMKaNWusfs+2MHPmTD799FPS09NLPMU6b948PvnkE1JSUoq1v9FoRK1W89lnnzF69Gggp9P7mDFjWL58OaNGjbLq/Hq9HkdHR3766adyu2sWQjxBge/PpLRTuDNnzuSjjz7i8uXL3LlzhyFDhhAfHw/kFGLesmWLuQZnSQKtgx3c3bucrPP76NixI8888wz+/v507NjxsZU5K4qnpyeDBw9m2bJlJR5j5cqVvPXWW+b6mMXRt29frl69SlRUFDt27GDAgAHMmDGDDz/80Orzr1mzhjFjxqDRaKw+VghhOxL4yklRHdILm8I1mUycOXOG1q1bo9frqVGjBomJiRb7hIaG0mPwPxn6xW/cOfE9GRd+zre3Ydb1syTvW44hLQGHmo2p1ncSKncfHOxg25igCjF9vGnTJkJCQkhLSytVh/QtW7bw6quvmqdviyMiIoIWLVrw7bff8vLLLzNq1Cg+//zzEp0/qZYyhQAADSlJREFUKCgIg8FQZMNcIcTjJYGvnJV2Clej0TBq1CiMRiMeHh44Oztz/PhxsrKyqP+P2Zz8Q0PG5WOgUJh7G+YGPkPmPW6tGIlX7wk4N3qW1PD1ZMdFUuMf81EooNfTvix/pfQd0kurQYMG+Pv7s2fPnlKNs2/fPvr162fVcz7IKQSekJBA//79+fbbb0t8frVazZIlS6yeIhVC2FaFXtxSGXi5OjK6FJ23HR0d+frrr/O8nlsBx2TCXNXm0d6GmVHHcahWBxf/nJ597h2Gcn/JUHRJsdh71ebQlQSS0jXl+gz15MmTXL9+nYMHD5Z4DIPBwJtvvklkZCQ6nY4OHTrQtGlTVq1aVeSx8fHx5meCpQl6hw8fRqvVMnz48BKPIYSwjQqZwC5Kr6ii2AC6hBvY+9Q3/13poEblUR1twk3gQV/D8jR+/HhatmxJvXr1SjyGQqFg165dhIWFAXDs2LECG9I+LC0tjebNm9OwYUMUCgUbNmwo8TUsXryYRo0albrPnxCi9CTw/UkV1dcQwKjLRunoYvGa0tEFkzbnGVi23sjlP8qvQWp8fDwnT55k8eLFpRpHqVSydOlS8/NBtVpdYNuhkydPkpycjFarpVmzZjg5OXHu3Dm6dOnCrFmzSnwNYWFhDBw4sMTHCyFsRwLfn1RRfQ0BlPZqjJpMi9eM2kwUDk4PjaOz+bUV1/jx46lZsyadOnUq9Vh9+vQxlzlr3bp1vi2HTCYTffr0oVWrVgQEBJCenk5kZCQODg7MmTOHS5cucfduAS02ChETE0NKSgoTJ04s9fsQQpSeBL4/qaL6GgLYe9dFdzfG/HejNht9ym0cvOs8NE7Ji2KXhlarZefOnTZrc6RQKMypEJMmTcp3n4sXL5KZmUlsbCxRUVF899135rZHbdu2xdvbu8CO6oVZsGABPj4++Pj4lPwNCCFsRgLfn9TDRbFNRgMmvRaMBjAZzQWxnRs/hzbxBhmXf8Gk13Lvl03Y+9Sz6GtYmqLYpTFt2jScnJwYOXKkzcYMDAzEzc2Njh075rt99+7dFrU8//a3v1mkifzzn/9k69atVp/3hx9+MFfYEUKUP0ln+JPKXdWp0RtJPbKhwN6GD/L47uJQ4395fB6+QP59DctKlSpVGDNmDHPnzrXJeInpGradiuPg6Su4enrj5pS3Rqmvry93797FwcGBBg0aMGHCBEaPHm2uFJOdnY2Liwvbt2/nxRdfLNZ509PTcXNz4/Tp07RqZX0DYSGE7Ung+xMrTVHs8szjW7FiBePHjycjI6PUqyAta5RiseAnt1BAlybeBDonM3rgX+nZsydz584lICAg3/E6dOhARkYGZ86cKdb5586dy0cffWRVtRghxOMlge9P7FxsKoNXnShhUWw7No9qVy6VW/z8/GjXrh3btm0r1TjWlIazw0hIcxdmDO1a6Jjh4eF06dKF5ORk8/O/wrRq1Qpvb2/2799v7eULIR4Tecb3J5bb19DJ3rqPuTz7GoaFhREfH89//vOfUo3zoEZp4UEPwGQCvUnJN5c1rD9xvdB9O3XqhKenJ++//36R12A0GomMjGTMmDFWXLkQ4nGTO75K4Enqa9iyZUtz2bWSOhebyqBl4dza/R+yr5/FmJ2OyqM6np1fxalhICaDjsQf5qL5IxpD2l18h8xCXbcFULw73bfeeos1a9aQkpKSbx/EtLQ0EhMTOXPmDIMHD0aj0ZR5014hRMHk/8ZK4JV29dg8qh29nvbFUaVErbL82NUqJY4qJb2e9mXzqHblFvRiYmK4cOFCqe/2loZFk63VoapSjepD51B70mY8OoWQsCMUfWpOE1tHv2ZUe+Ft7Fw8LY7N1hv4PCy60PE/+ugj7t27R48ePahSpQoXL1602L5x40YaNmzIsGHD8PDw4MSJE8jvl0JUHFKrs5Jo4efB8lcCK3Rfw3/961/Ur1+fNm3alHiMxHQNh6MSUNir8eg4zPy6c6NnUbn7orkdjYtHEG5t/7cq85E7MZOJQmuUnjx5ksGDB6NQKDhw4ABqtTrPApxGjRrh5uZGWloaWq2WoKAgTpw4wV/+8pcSvy8hhO1I4KtkSlsU+3FJT09n//79bNy4sVTjFFSj1JCRgi75lkVyfkFya5Tm9+9UpUoVkpKSzHdwer0eX19fi32aNWtmzgdUq9VMmDBBgp4QFYhMdYoKYfLkybi7uxMcHFyqcfKrUWoy6En8YR6uAd3MyfmFKaxGqb+/P2fOnKFmzZpAzgIWV1dXi32qV6+OwZCzknb48OHMnj27JG9FCPGYSOAT5c5oNPL111/bpJblozVKTSYjibvmg52Kqj2Kv7qysBql9evX58yZM3h4eOS7uEWhUKBUKmnSpAlLly7Ns10IUb5kqlOUu/nz52M0GnnvvfdKPdbDNUpNJhNJe5ZgyEjF5+WZKOyK/+NeVI1Sb29vIiIiaNKkCRFXr3M03sDl22mkZetxU6vwbD+I3evnS9ATogKSwCfK3dy5cxk8eDAqVel/HHNqlN5GozeSvHcpuqRYfAd/jNLecqGKSa8Dcp7TmYz6nFqmdvYoFIpi1yhNNLrQ6+NvGfD1RRQKhcUUq0eHofRZcZouTbwZ27kRLWuXfU6kECJ/kscnytXOnTt56aWXSEpKKlYllKLk1ijNSLrNrWUjcoKZ0s68vepfx+Ha7HniPh+BIc2yxVCtMatRefgWq0bpk5QbKYSwJIFPlCt/f39q1KjBoUOHbDbm465R+qAqTOGNfh+WUw2nqQQ/ISoAmeoU5SYiIsLc986WxnVpxJGriSWqUapW2TG2S6MCt5+LTeWTPZe5c2IHGRd+RptwHZemnanWL6fHX3rkIZJ/WvrgAJMJk15D9eGL+GSPghZ+HuVSCk4I8YDc8Yly07lzZ+7evculS5dsPvbjuivLvZvMuHwMFAqyYk5j0mnNge9R6ecPcO/YN9QcvQqlUlFuHS+EEA/IHZ8oF8nJyRw5coRdu3Y9lvFzg5ctn8PlVoUxmcC5SXsANLejMegSCzwmPeJnXJp3RaFQFFkVRghRNiSPT5SLCRMm4O3tTZ8+fR7bOWxdo7SgqjAF0d+7iyY2EpfmD1od5VaFEUKUH7njE2XGZDJhNBoxmUxs3bqVOXPmPPZz2rJGaX5VYQqTHvEzjn5PY+9R3fxaYVVhhBBlQwKfKDObN29m1KhRBAQEoFQqefPNN8vs3LaoUfpoVZiiZEQcxP25QfmMU3BVGCHE4ydTnaLMGAwGdDodx44dQ6fTERISQnp6enlfVrE9XBWmKNlxFzGkJ+PcJCifcQqvCiOEeLwk8Iky4+zsbP6zQqEgPDwcrVZbjldknZyqMDn/y5iMhpxqL0YDmIyY9FpMxgfpExkXfsa5cXuUjs4WYxS3KowQ4vGRwCfKjIuLi7kb+XPPPceFCxeoWrVqeV9WsQ1s42f+871fvuHmvAGkndhGRuQhbs4bwL1fvgHApNeScfkoLgHd8oxhAga29svzuhCi7MgzPlFm/vjjD0wmE6+++ipffPEFdnZ2RR9UgVRzdaRzY2/2X7qDR8dhFo1uH6ZQOVBn0ua8ryvg+SbeksogRDmTwCdsLjFdw7ZTcRbdCvyru3Hp4lW6d+/OmjVryvsSS+xxVoURQpQNqdwibOZcbCpLw6I5HJUAYLH0X61SYjAa6dCwKhN7NH2iuxVIrU4hnmwS+IRNVLZuBZXt/QrxZyKBT5RaZb0DOh+Xyudh0Ry6koCCnOT0XGqVEhM5z/TGdmkkhamFqEAk8IlSORebyuBVJ7hz4vt8uxWYDDoSf5iL5o9oDGl38R0yC3XdFgA42duxeVS7Jz4o2KIqjBCi7MjiFlEqS8OiydYbULl64d4+2Nyt4GGOfs2oEvgiid9blijL1hv4PCz6ie9WYIuqMEKIsiOBT5RYcboVKOzscWv7Ys5flJZpo9KtQAhRHiSBXZSYtd0K8iPdCoQQZU0Cnygxa7sV5Ee6FQghypoEPlFi1nYrKHgc6VYghCg7EvhEiVnTraDwcaRbgRCi7EjgEyVW3G4FJr0uZxtgMupztv0vi0a6FQghyprk8YkSS0zXEBR6EI3eSOqRDdz7ZZPFdvegIXh0HEbc5yMwpN212FZrzGpUHr44qpQcm9JVVnUKIcqMBD5RKqPW/Zf9l+4UWrarIAoF9Hra94nP4xNCPFlkqlOUyrgujVCrStZeSLoVCCHKgwQ+USota3vwfh9/nOyt+1HKqdXp/8SXKxNCPHmkcosotdxC09KtQAjxJJBnfMJmpFuBEOJJIIFP2Jx0KxBCVGQS+IQQQlQqsrhFCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSlIoFPCCFEpSKBTwghRKUigU8IIUSl8v+ngP8jHagB1QAAAABJRU5ErkJggg==",
+ "text/plain": [
+ "