From 02fd9b685c6a6d1031ed9b8bf092121c462732a5 Mon Sep 17 00:00:00 2001 From: Roope Astala Date: Mon, 11 Feb 2019 15:47:37 -0500 Subject: [PATCH] few missing files --- ...nes-use-databricks-as-compute-target.ipynb | 714 ++++++++++++++++++ .../azure-databricks/testdata.txt | 1 + .../azure-databricks/train-db-dbfs.py | 5 + .../azure-databricks/train-db-local.py | 5 + .../aml-pipelines-concept.png | Bin 0 -> 24462 bytes 5 files changed, 725 insertions(+) create mode 100644 how-to-use-azureml/azure-databricks/aml-pipelines-use-databricks-as-compute-target.ipynb create mode 100644 how-to-use-azureml/azure-databricks/testdata.txt create mode 100644 how-to-use-azureml/azure-databricks/train-db-dbfs.py create mode 100644 how-to-use-azureml/azure-databricks/train-db-local.py create mode 100644 how-to-use-azureml/machine-learning-pipelines/aml-pipelines-concept.png diff --git a/how-to-use-azureml/azure-databricks/aml-pipelines-use-databricks-as-compute-target.ipynb b/how-to-use-azureml/azure-databricks/aml-pipelines-use-databricks-as-compute-target.ipynb new file mode 100644 index 00000000..649c197a --- /dev/null +++ b/how-to-use-azureml/azure-databricks/aml-pipelines-use-databricks-as-compute-target.ipynb @@ -0,0 +1,714 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved. \n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Databricks as a Compute Target from Azure Machine Learning Pipeline\n", + "To use Databricks as a compute target from [Azure Machine Learning Pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-ml-pipelines), a [DatabricksStep](https://docs.microsoft.com/en-us/python/api/azureml-pipeline-steps/azureml.pipeline.steps.databricks_step.databricksstep?view=azure-ml-py) is used. This notebook demonstrates the use of DatabricksStep in Azure Machine Learning Pipeline.\n", + "\n", + "The notebook will show:\n", + "1. Running an arbitrary Databricks notebook that the customer has in Databricks workspace\n", + "2. Running an arbitrary Python script that the customer has in DBFS\n", + "3. Running an arbitrary Python script that is available on local computer (will upload to DBFS, and then run in Databricks) \n", + "4. Running a JAR job that the customer has in DBFS.\n", + "\n", + "## Before you begin:\n", + "\n", + "1. **Create an Azure Databricks workspace** in the same subscription where you have your Azure Machine Learning workspace. You will need details of this workspace later on to define DatabricksStep. [Click here](https://ms.portal.azure.com/#blade/HubsExtension/Resources/resourceType/Microsoft.Databricks%2Fworkspaces) for more information.\n", + "2. **Create PAT (access token)**: Manually create a Databricks access token at the Azure Databricks portal. See [this](https://docs.databricks.com/api/latest/authentication.html#generate-a-token) for more information.\n", + "3. **Add demo notebook to ADB**: This notebook has a sample you can use as is. Launch Azure Databricks attached to your Azure Machine Learning workspace and add a new notebook. \n", + "4. **Create/attach a Blob storage** for use from ADB" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Add demo notebook to ADB Workspace\n", + "Copy and paste the below code to create a new notebook in your ADB workspace." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# direct access\n", + "dbutils.widgets.get(\"myparam\")\n", + "p = getArgument(\"myparam\")\n", + "print (\"Param -\\'myparam':\")\n", + "print (p)\n", + "\n", + "dbutils.widgets.get(\"input\")\n", + "i = getArgument(\"input\")\n", + "print (\"Param -\\'input':\")\n", + "print (i)\n", + "\n", + "dbutils.widgets.get(\"output\")\n", + "o = getArgument(\"output\")\n", + "print (\"Param -\\'output':\")\n", + "print (o)\n", + "\n", + "n = i + \"/testdata.txt\"\n", + "df = spark.read.csv(n)\n", + "\n", + "display (df)\n", + "\n", + "data = [('value1', 'value2')]\n", + "df2 = spark.createDataFrame(data)\n", + "\n", + "z = o + \"/output.txt\"\n", + "df2.write.csv(z)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Azure Machine Learning and Pipeline SDK-specific imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import azureml.core\n", + "from azureml.core.runconfig import JarLibrary\n", + "from azureml.core.compute import ComputeTarget, DatabricksCompute\n", + "from azureml.exceptions import ComputeTargetException\n", + "from azureml.core import Workspace, Experiment\n", + "from azureml.pipeline.core import Pipeline, PipelineData\n", + "from azureml.pipeline.steps import DatabricksStep\n", + "from azureml.core.datastore import Datastore\n", + "from azureml.data.data_reference import DataReference\n", + "\n", + "# Check core SDK version number\n", + "print(\"SDK version:\", azureml.core.VERSION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize Workspace\n", + "\n", + "Initialize a workspace object from persisted configuration. Make sure the config file is present at .\\config.json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attach Databricks compute target\n", + "Next, you need to add your Databricks workspace to Azure Machine Learning as a compute target and give it a name. You will use this name to refer to your Databricks workspace compute target inside Azure Machine Learning.\n", + "\n", + "- **Resource Group** - The resource group name of your Azure Machine Learning workspace\n", + "- **Databricks Workspace Name** - The workspace name of your Azure Databricks workspace\n", + "- **Databricks Access Token** - The access token you created in ADB\n", + "\n", + "**The Databricks workspace need to be present in the same subscription as your AML workspace**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Replace with your account info before running.\n", + " \n", + "db_compute_name=os.getenv(\"DATABRICKS_COMPUTE_NAME\", \"\") # Databricks compute name\n", + "db_resource_group=os.getenv(\"DATABRICKS_RESOURCE_GROUP\", \"\") # Databricks resource group\n", + "db_workspace_name=os.getenv(\"DATABRICKS_WORKSPACE_NAME\", \"\") # Databricks workspace name\n", + "db_access_token=os.getenv(\"DATABRICKS_ACCESS_TOKEN\", \"\") # Databricks access token\n", + " \n", + "try:\n", + " databricks_compute = DatabricksCompute(workspace=ws, name=db_compute_name)\n", + " print('Compute target {} already exists'.format(db_compute_name))\n", + "except ComputeTargetException:\n", + " print('Compute not found, will use below parameters to attach new one')\n", + " print('db_compute_name {}'.format(db_compute_name))\n", + " print('db_resource_group {}'.format(db_resource_group))\n", + " print('db_workspace_name {}'.format(db_workspace_name))\n", + " print('db_access_token {}'.format(db_access_token))\n", + " \n", + " config = DatabricksCompute.attach_configuration(\n", + " resource_group = db_resource_group,\n", + " workspace_name = db_workspace_name,\n", + " access_token= db_access_token)\n", + " databricks_compute=ComputeTarget.attach(ws, db_compute_name, config)\n", + " databricks_compute.wait_for_completion(True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data Connections with Inputs and Outputs\n", + "The DatabricksStep supports Azure Bloband ADLS for inputs and outputs. You also will need to define a [Secrets](https://docs.azuredatabricks.net/user-guide/secrets/index.html) scope to enable authentication to external data sources such as Blob and ADLS from Databricks.\n", + "\n", + "- Databricks documentation on [Azure Blob](https://docs.azuredatabricks.net/spark/latest/data-sources/azure/azure-storage.html)\n", + "- Databricks documentation on [ADLS](https://docs.databricks.com/spark/latest/data-sources/azure/azure-datalake.html)\n", + "\n", + "### Type of Data Access\n", + "Databricks allows to interact with Azure Blob and ADLS in two ways.\n", + "- **Direct Access**: Databricks allows you to interact with Azure Blob or ADLS URIs directly. The input or output URIs will be mapped to a Databricks widget param in the Databricks notebook.\n", + "- **Mounting**: You will be supplied with additional parameters and secrets that will enable you to mount your ADLS or Azure Blob input or output location in your Databricks notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Direct Access: Python sample code\n", + "If you have a data reference named \"input\" it will represent the URI of the input and you can access it directly in the Databricks python notebook like so:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "dbutils.widgets.get(\"input\")\n", + "y = getArgument(\"input\")\n", + "df = spark.read.csv(y)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Mounting: Python sample code for Azure Blob\n", + "Given an Azure Blob data reference named \"input\" the following widget params will be made available in the Databricks notebook:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# This contains the input URI\n", + "dbutils.widgets.get(\"input\")\n", + "myinput_uri = getArgument(\"input\")\n", + "\n", + "# How to get the input datastore name inside ADB notebook\n", + "# This contains the name of a Databricks secret (in the predefined \"amlscope\" secret scope) \n", + "# that contians an access key or sas for the Azure Blob input (this name is obtained by appending \n", + "# the name of the input with \"_blob_secretname\". \n", + "dbutils.widgets.get(\"input_blob_secretname\") \n", + "myinput_blob_secretname = getArgument(\"input_blob_secretname\")\n", + "\n", + "# This contains the required configuration for mounting\n", + "dbutils.widgets.get(\"input_blob_config\")\n", + "myinput_blob_config = getArgument(\"input_blob_config\")\n", + "\n", + "# Usage\n", + "dbutils.fs.mount(\n", + " source = myinput_uri,\n", + " mount_point = \"/mnt/input\",\n", + " extra_configs = {myinput_blob_config:dbutils.secrets.get(scope = \"amlscope\", key = myinput_blob_secretname)})\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Mounting: Python sample code for ADLS\n", + "Given an ADLS data reference named \"input\" the following widget params will be made available in the Databricks notebook:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "# This contains the input URI\n", + "dbutils.widgets.get(\"input\") \n", + "myinput_uri = getArgument(\"input\")\n", + "\n", + "# This contains the client id for the service principal \n", + "# that has access to the adls input\n", + "dbutils.widgets.get(\"input_adls_clientid\") \n", + "myinput_adls_clientid = getArgument(\"input_adls_clientid\")\n", + "\n", + "# This contains the name of a Databricks secret (in the predefined \"amlscope\" secret scope) \n", + "# that contains the secret for the above mentioned service principal\n", + "dbutils.widgets.get(\"input_adls_secretname\") \n", + "myinput_adls_secretname = getArgument(\"input_adls_secretname\")\n", + "\n", + "# This contains the refresh url for the mounting configs\n", + "dbutils.widgets.get(\"input_adls_refresh_url\") \n", + "myinput_adls_refresh_url = getArgument(\"input_adls_refresh_url\")\n", + "\n", + "# Usage \n", + "configs = {\"dfs.adls.oauth2.access.token.provider.type\": \"ClientCredential\",\n", + " \"dfs.adls.oauth2.client.id\": myinput_adls_clientid,\n", + " \"dfs.adls.oauth2.credential\": dbutils.secrets.get(scope = \"amlscope\", key =myinput_adls_secretname),\n", + " \"dfs.adls.oauth2.refresh.url\": myinput_adls_refresh_url}\n", + "\n", + "dbutils.fs.mount(\n", + " source = myinput_uri,\n", + " mount_point = \"/mnt/output\",\n", + " extra_configs = configs)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use Databricks from Azure Machine Learning Pipeline\n", + "To use Databricks as a compute target from Azure Machine Learning Pipeline, a DatabricksStep is used. Let's define a datasource (via DataReference) and intermediate data (via PipelineData) to be used in DatabricksStep." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use the default blob storage\n", + "def_blob_store = Datastore(ws, \"workspaceblobstore\")\n", + "print('Datastore {} will be used'.format(def_blob_store.name))\n", + "\n", + "# We are uploading a sample file in the local directory to be used as a datasource\n", + "def_blob_store.upload_files(files=[\"./testdata.txt\"], target_path=\"dbtest\", overwrite=False)\n", + "\n", + "step_1_input = DataReference(datastore=def_blob_store, path_on_datastore=\"dbtest\",\n", + " data_reference_name=\"input\")\n", + "\n", + "step_1_output = PipelineData(\"output\", datastore=def_blob_store)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a DatabricksStep\n", + "Adds a Databricks notebook as a step in a Pipeline.\n", + "- ***name:** Name of the Module\n", + "- **inputs:** List of input connections for data consumed by this step. Fetch this inside the notebook using dbutils.widgets.get(\"input\")\n", + "- **outputs:** List of output port definitions for outputs produced by this step. Fetch this inside the notebook using dbutils.widgets.get(\"output\")\n", + "- **existing_cluster_id:** Cluster ID of an existing Interactive cluster on the Databricks workspace. If you are providing this, do not provide any of the parameters below that are used to create a new cluster such as spark_version, node_type, etc.\n", + "- **spark_version:** Version of spark for the databricks run cluster. default value: 4.0.x-scala2.11\n", + "- **node_type:** Azure vm node types for the databricks run cluster. default value: Standard_D3_v2\n", + "- **num_workers:** Specifies a static number of workers for the databricks run cluster\n", + "- **min_workers:** Specifies a min number of workers to use for auto-scaling the databricks run cluster\n", + "- **max_workers:** Specifies a max number of workers to use for auto-scaling the databricks run cluster\n", + "- **spark_env_variables:** Spark environment variables for the databricks run cluster (dictionary of {str:str}). default value: {'PYSPARK_PYTHON': '/databricks/python3/bin/python3'}\n", + "- **notebook_path:** Path to the notebook in the databricks instance. If you are providing this, do not provide python script related paramaters or JAR related parameters.\n", + "- **notebook_params:** Parameters for the databricks notebook (dictionary of {str:str}). Fetch this inside the notebook using dbutils.widgets.get(\"myparam\")\n", + "- **python_script_path:** The path to the python script in the DBFS or S3. If you are providing this, do not provide python_script_name which is used for uploading script from local machine.\n", + "- **python_script_params:** Parameters for the python script (list of str)\n", + "- **main_class_name:** The name of the entry point in a JAR module. If you are providing this, do not provide any python script or notebook related parameters.\n", + "- **jar_params:** Parameters for the JAR module (list of str)\n", + "- **python_script_name:** name of a python script on your local machine (relative to source_directory). If you are providing this do not provide python_script_path which is used to execute a remote python script; or any of the JAR or notebook related parameters.\n", + "- **source_directory:** folder that contains the script and other files\n", + "- **hash_paths:** list of paths to hash to detect a change in source_directory (script file is always hashed)\n", + "- **run_name:** Name in databricks for this run\n", + "- **timeout_seconds:** Timeout for the databricks run\n", + "- **runconfig:** Runconfig to use. Either pass runconfig or each library type as a separate parameter but do not mix the two\n", + "- **maven_libraries:** maven libraries for the databricks run\n", + "- **pypi_libraries:** pypi libraries for the databricks run\n", + "- **egg_libraries:** egg libraries for the databricks run\n", + "- **jar_libraries:** jar libraries for the databricks run\n", + "- **rcran_libraries:** rcran libraries for the databricks run\n", + "- **compute_target:** Azure Databricks compute\n", + "- **allow_reuse:** Whether the step should reuse previous results when run with the same settings/inputs\n", + "- **version:** Optional version tag to denote a change in functionality for the step\n", + "\n", + "\\* *denotes required fields* \n", + "*You must provide exactly one of num_workers or min_workers and max_workers paramaters* \n", + "*You must provide exactly one of databricks_compute or databricks_compute_name parameters*\n", + "\n", + "## Use runconfig to specify library dependencies\n", + "You can use a runconfig to specify the library dependencies for your cluster in Databricks. The runconfig will contain a databricks section as follows:\n", + "\n", + "```yaml\n", + "environment:\n", + "# Databricks details\n", + " databricks:\n", + "# List of maven libraries.\n", + " mavenLibraries:\n", + " - coordinates: org.jsoup:jsoup:1.7.1\n", + " repo: ''\n", + " exclusions:\n", + " - slf4j:slf4j\n", + " - '*:hadoop-client'\n", + "# List of PyPi libraries\n", + " pypiLibraries:\n", + " - package: beautifulsoup4\n", + " repo: ''\n", + "# List of RCran libraries\n", + " rcranLibraries:\n", + " -\n", + "# Coordinates.\n", + " package: ada\n", + "# Repo\n", + " repo: http://cran.us.r-project.org\n", + "# List of JAR libraries\n", + " jarLibraries:\n", + " -\n", + "# Coordinates.\n", + " library: dbfs:/mnt/libraries/library.jar\n", + "# List of Egg libraries\n", + " eggLibraries:\n", + " -\n", + "# Coordinates.\n", + " library: dbfs:/mnt/libraries/library.egg\n", + "```\n", + "\n", + "You can then create a RunConfiguration object using this file and pass it as the runconfig parameter to DatabricksStep.\n", + "```python\n", + "from azureml.core.runconfig import RunConfiguration\n", + "\n", + "runconfig = RunConfiguration()\n", + "runconfig.load(path='', name='')\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Running the demo notebook already added to the Databricks workspace\n", + "Create a notebook in the Azure Databricks workspace, and provide the path to that notebook as the value associated with the environment variable \"DATABRICKS_NOTEBOOK_PATH\". This will then set the variable notebook_path when you run the code cell below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "notebook_path=os.getenv(\"DATABRICKS_NOTEBOOK_PATH\", \"\") # Databricks notebook path\n", + "\n", + "dbNbStep = DatabricksStep(\n", + " name=\"DBNotebookInWS\",\n", + " inputs=[step_1_input],\n", + " outputs=[step_1_output],\n", + " num_workers=1,\n", + " notebook_path=notebook_path,\n", + " notebook_params={'myparam': 'testparam'},\n", + " run_name='DB_Notebook_demo',\n", + " compute_target=databricks_compute,\n", + " allow_reuse=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Build and submit the Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#steps = [dbNbStep]\n", + "#pipeline = Pipeline(workspace=ws, steps=steps)\n", + "#pipeline_run = Experiment(ws, 'DB_Notebook_demo').submit(pipeline)\n", + "#pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### View Run Details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#from azureml.widgets import RunDetails\n", + "#RunDetails(pipeline_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Running a Python script from DBFS\n", + "This shows how to run a Python script in DBFS. \n", + "\n", + "To complete this, you will need to first upload the Python script in your local machine to DBFS using the [CLI](https://docs.azuredatabricks.net/user-guide/dbfs-databricks-file-system.html). The CLI command is given below:\n", + "\n", + "```\n", + "dbfs cp ./train-db-dbfs.py dbfs:/train-db-dbfs.py\n", + "```\n", + "\n", + "The code in the below cell assumes that you have completed the previous step of uploading the script `train-db-dbfs.py` to the root folder in DBFS." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "python_script_path = os.getenv(\"DATABRICKS_PYTHON_SCRIPT_PATH\", \"\") # Databricks python script path\n", + "\n", + "dbPythonInDbfsStep = DatabricksStep(\n", + " name=\"DBPythonInDBFS\",\n", + " inputs=[step_1_input],\n", + " num_workers=1,\n", + " python_script_path=python_script_path,\n", + " python_script_params={'--input_data'},\n", + " run_name='DB_Python_demo',\n", + " compute_target=databricks_compute,\n", + " allow_reuse=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Build and submit the Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#steps = [dbPythonInDbfsStep]\n", + "#pipeline = Pipeline(workspace=ws, steps=steps)\n", + "#pipeline_run = Experiment(ws, 'DB_Python_demo').submit(pipeline)\n", + "#pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### View Run Details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#from azureml.widgets import RunDetails\n", + "#RunDetails(pipeline_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Running a Python script in Databricks that currenlty is in local computer\n", + "To run a Python script that is currently in your local computer, follow the instructions below. \n", + "\n", + "The commented out code below code assumes that you have `train-db-local.py` in the `scripts` subdirectory under the current working directory.\n", + "\n", + "In this case, the Python script will be uploaded first to DBFS, and then the script will be run in Databricks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "python_script_name = \"train-db-local.py\"\n", + "source_directory = \".\"\n", + "\n", + "dbPythonInLocalMachineStep = DatabricksStep(\n", + " name=\"DBPythonInLocalMachine\",\n", + " inputs=[step_1_input],\n", + " num_workers=1,\n", + " python_script_name=python_script_name,\n", + " source_directory=source_directory,\n", + " run_name='DB_Python_Local_demo',\n", + " compute_target=databricks_compute,\n", + " allow_reuse=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Build and submit the Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "steps = [dbPythonInLocalMachineStep]\n", + "pipeline = Pipeline(workspace=ws, steps=steps)\n", + "pipeline_run = Experiment(ws, 'DB_Python_Local_demo').submit(pipeline)\n", + "pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### View Run Details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(pipeline_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Running a JAR job that is alreay added in DBFS\n", + "To run a JAR job that is already uploaded to DBFS, follow the instructions below. You will first upload the JAR file to DBFS using the [CLI](https://docs.azuredatabricks.net/user-guide/dbfs-databricks-file-system.html).\n", + "\n", + "The commented out code in the below cell assumes that you have uploaded `train-db-dbfs.jar` to the root folder in DBFS. You can upload `train-db-dbfs.jar` to the root folder in DBFS using this commandline so you can use `jar_library_dbfs_path = \"dbfs:/train-db-dbfs.jar\"`:\n", + "\n", + "```\n", + "dbfs cp ./train-db-dbfs.jar dbfs:/train-db-dbfs.jar\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_jar_class_name = \"com.microsoft.aeva.Main\"\n", + "jar_library_dbfs_path = os.getenv(\"DATABRICKS_JAR_LIB_PATH\", \"\") # Databricks jar library path\n", + "\n", + "dbJarInDbfsStep = DatabricksStep(\n", + " name=\"DBJarInDBFS\",\n", + " inputs=[step_1_input],\n", + " num_workers=1,\n", + " main_class_name=main_jar_class_name,\n", + " jar_params={'arg1', 'arg2'},\n", + " run_name='DB_JAR_demo',\n", + " jar_libraries=[JarLibrary(jar_library_dbfs_path)],\n", + " compute_target=databricks_compute,\n", + " allow_reuse=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Build and submit the Experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#steps = [dbJarInDbfsStep]\n", + "#pipeline = Pipeline(workspace=ws, steps=steps)\n", + "#pipeline_run = Experiment(ws, 'DB_JAR_demo').submit(pipeline)\n", + "#pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### View Run Details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#PUBLISHONLY\n", + "#from azureml.widgets import RunDetails\n", + "#RunDetails(pipeline_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Next: ADLA as a Compute Target\n", + "To use ADLA as a compute target from Azure Machine Learning Pipeline, a AdlaStep is used. This [notebook](./aml-pipelines-use-adla-as-compute-target.ipynb) demonstrates the use of AdlaStep in Azure Machine Learning Pipeline." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "diray" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/azure-databricks/testdata.txt b/how-to-use-azureml/azure-databricks/testdata.txt new file mode 100644 index 00000000..2069d6e5 --- /dev/null +++ b/how-to-use-azureml/azure-databricks/testdata.txt @@ -0,0 +1 @@ +Test1 diff --git a/how-to-use-azureml/azure-databricks/train-db-dbfs.py b/how-to-use-azureml/azure-databricks/train-db-dbfs.py new file mode 100644 index 00000000..99b511af --- /dev/null +++ b/how-to-use-azureml/azure-databricks/train-db-dbfs.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +print("In train.py") +print("As a data scientist, this is where I use my training code.") diff --git a/how-to-use-azureml/azure-databricks/train-db-local.py b/how-to-use-azureml/azure-databricks/train-db-local.py new file mode 100644 index 00000000..99b511af --- /dev/null +++ b/how-to-use-azureml/azure-databricks/train-db-local.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. + +print("In train.py") +print("As a data scientist, this is where I use my training code.") diff --git a/how-to-use-azureml/machine-learning-pipelines/aml-pipelines-concept.png b/how-to-use-azureml/machine-learning-pipelines/aml-pipelines-concept.png new file mode 100644 index 0000000000000000000000000000000000000000..b01526dab9c152842441b7822d36a7316d7fad06 GIT binary patch literal 24462 zcmZsCWmKF&lQj?s?oP15o#5{7I=BUg;2zxF-CYNFC&4X1(BSUwF5l$c-E(%&`F{00 z&&>2`sj9yBR&|83q7*U$0RjXB1hR~@xGDt1CsGIqNG3Qa@SCTwEpu>#?;@e)qGoUI z;{MIa3_`@z-q?&(#`c?qnX1`0(;tqbX8aHkhF@jGMbtf(Pje8xfrH%_T+ky)!4xT$ z28*P$e*$4YNzYYc1y+Z&TsNd>%&liC&v|l}i_d+p4iu5?W*h@pT~yD2u%x}Qjw>R< z4N@$M1CjBFrNjb1QvjGTzQ(_-oa7-TH9r_EE&VtfzIa*6GZiqO$ZTHV_hq}u%kZuB z)4OX;wEhAJMiYfQqWbJ@;C|kJ?b!n-3I_;|KQ9zAg#6D}FgX98rr;OZzrX$827smY z@8!P+nt0>G9LW$0pikjyN#pB{8VnVMHZx|zMWt$jJ|poN6=5tZiS>sEmLbf)K9o&d z1B1&&yl`T2()kxtg8uq)Uf;uuJr%BgTS(XH`d#zWxt5HK`;bcr4~JW3w6!O8m|;R% zkBx5-S4_|!UqQ)aY%mgKzUtjCPjfE&CgqacTuDCmPaM{1EgfC;5>R6}Zl#+5(eLBF z79=bU6D~R)HMZ7{7BZZHg$rY)CBwb(Ol0WQjh)kKcl&MD~iZrRjz%F2oWLWVva$knwh?78J6R ze)-q(_o=KW5LuD%dJWIrW4Scs{EEt0TE0>BKx51OZ)Aj&-$DmXdJ1z2>BVrlhVS~a}$rV`liJwDv4$b>5C=!@HA|Z zvPyie(PYY_wcsUlve5Qn4r^ijt)hsDl2}l&yz@|uf=t9uGt$?~6&mVcp*jO(C0qZ^ zG99jhGqdz%BqKNar`mfXv|nD9E03FsdAqi}cqo$awkUppTz=CtNdMOt1R{hg1DN`= z^FSrT?wc=c!vk7#x!tem4P1Vq4G1#lMrK8PEO?ZV+nz?gSNKImY}A8KmV2FH!reGO z>I+=Q)*M&$W7yH?CBk%W^j;+dAm?v$eYNztV zGSaw+>1^zK+Zu{W+_)njG{(1~wLz||1&dm{`nh$=WYX3aIBFG`{>{yIW}nBBpMsks zZOZfRq}{HbY$@V*B<~wR;i3ziT6BRj;>V;zz8Y~~WTGYRI?hj@H1EA;x&ZereB;N1 z(ue?e>>tc^ouiB`j$2cs=G@J#f@{k>jq|wRWEG&`-<}np%%6+6MT-?Ig z(n{JMNyj~K-=+(6-gGpVV&T`=Cw#~QdK6?iYi%%w4*K2InUz0y!?DLB$L=1T!N!`V z7^Ar{+1GWne!`@7XRx-Hcc8DL@{+Qi3%%j+$7-xEB=o(pRS00z8ut?#CfK4HJ{McK zquO-3fIxr!2ws-%%Y&YKg{&uxWTYE|3r!sqjHzj8Do9-lO(@sr0`X{TXh~@5OF&13 zIH}34_;;CEokDp9;ozi3jQRZe;=kmq+;ADCkL21s$7pm{q&?exA+}c-GKhYI7LHY~ zj-7!{k{q8X#9`<+(02ZrH0yWq@1`V=0N10>unSuheSWKGd7d(dweOCCg&?Lp?pnL}09rF;4r4Q;fpTtc+m zW?8MeW|y)FzDh|mGO%aTx(F-&@e_L7L+voWH0+Zb_ulPEIaUF?+GO;%F*MnZr_J`r zoAQ#Kw#sOCZ55cBkTB5x#u6<1Z`jIP=@WBE3Ko1bGB<}RjvLBMC zk!LX`1+$uUQFWRk@(o`$h^)Dwm6k%JTeMW1HOwZ$X~MbMWZkP8Oa>^r^X%@t@+wUs zP#w4CbzpGsTwMYD;tBb5OAgW5?)&MMae0Vwja}7aA&2jMxy&kRA};R7i@e$5EG90u zwLM9@Tya7CSnb5V;q~FI`b~l=j#|Mz>;&zOqol8B5*JL5>M#%@GfC-u`Id9KxxCmP z68fXS+SA&^1xq=7Ewj0O*z&Tvr3qt@{5)sZ{GL6qJUIk8IqFz+gtpj`LLlYy3brqi z86Rj3olG7oF6^!#)PniKEv=UPLXE6U<|n8z)R*_T$xEdK4%lXD|Bkd5LStnGo5@2Z zX%EZMY<%2jwy=n2wHGKq%_n2*K zfJ40$I4>s=g$K`j7Cqa{8BnnTqua8#ythd}`${%4?Eq*~#PIy2jV z+fi$v#^lbkO2DBIeOjk>qfKQ9$zfzWY>Zrb0txuKOm*;ACphVlRP#E^LIqp)D8}<{ z(th=R1`ubKYC<^F5@N3^7rIYFClDh$>D^v^j( zV?2Iuwp^g_7(eqqw%toE6%-r?W$GA5uWwzVs0FwE&d6oJ=d(*-AybtlDPz?)JC!Dy z=?eCsq)d}-PnBViya0LKcQfB=9h*b$vQu3!R4TbFaG&AXcO!!j1V)|gLiexatrEGG znf}9MisQ{AV^_44K;f!WcF!CIw#IpD30G4GvfjFM87>^b0%=u{UQ(<96wg4v% z3?doHyf6cic4hzt!87r$6KiT#=iz&mGFnk=w2?)CWAc1ebh+#ElQ{v08@?#bk%Se) zcM%iYqyuxvxe6W1?P@Nhw1QvP`0>QW7fbpnn7DM>1=CbmRZ2gp66S$=7aaj0m-_Lu z+ZnlI)0&!0E3Q6Qu55Kp-&gj5s0G1c+u4m5D<> zt8pK>fK)AI&BmdjQ@TobogF7{H>+$lm@m2NKb8HS&Mo*@$NjZTcg|eb;+eiCC3aVs zi_bL<)get|D%ye=8hoUNLyuX9A*~)0o`2Oe@OCy4poc7?_%tpLws!LJKmiR?$dZRd zLu;uEnXsKxVd72X-BJLg>nQi<8M0a}KXA)s_+rQ0)YUzWm2JnR%jtmOwB7VdDC(R9 zMEJSm-w%1PbU@-P^AuHO!Y)GpW`!i!J%C@~b~61dhn8LCv%I{u#X+#@22hd#SzYrx zePsAh-yYmhBN4LDsf|L%N*P3R96rCg^DwlR)YeUB4m&{D6@Wxl>1>tVPs=aJDPJ&y zOv3lujnGcB^|@R2XvJ9d+CTef6r00BVtRXL`$Q`a?+#?ljwGih_bGLrWsc`}>2RUn7r&EDdpF9Q>{$PD`GZ>z|6#GqQz+jmpmfXY1O{-8Na zq;uErsyM6LQ#UnjKChI|+wju;Y{TNM8M6lF4J#7Tk^~)pEOXAqxhra3F$OTj_2<+| z{Flt+TiaZ$y9P*1aN=SSy((lXxfvS!J0s80!27$U6tQDQh z^QCiR)Sph_)kG^N^$+%Ig*9R53Ybj&D#}eQT!hHT#66LPDC}hGSS(n+xUq(1$jFe|MG=ZDI}&J` zJp!!J$KFv@rkk{oSF!LuX)2HtXQzr_i>B(5htOK*G2<1ndIb)gKv3{4npFh4s%C|dm+w4Z z-iwqf>qO;}Yra)90qbx;158xBf535Me9b4I)8+J9BbAI)OWUgce*1ywgzG>PHoT*xHNb&hoj5 z)-)VFNW0fRw*FpFcabhsTUfZdOeuKV?QXHh;1|d|5!Xu{3O$Uf(sz5e$L&ibi9H+* zr3t7#RDRxR_4=z=mUbdEpf#|9Le6;(H$3uZ&Y5Ro;dYaVUpes1O0D+dlr0fLJTAjC zSbSGym`_)b?7cH-jA5G%CiO4Nx9xd8w-3ypW=hra($WLyrr%hj<^AUg2s0#l-PkdNB_1s zrs5WY%v@!ojEqbc+a)!n@p~q6If*w^SMBM!@?9BMW%U+rv0jE1MSTqW&wJAMGg2jd zcpF$LU#e&N4o4t&I2?vl$4>v#%I5@U}^)Qq+$tT z)^@P5iH6QzHM!^??4X9*pm7_9vt8)wPiL%2?Yn_AEb+`?P~$Q<~B^Y+tECvwQMM%(@a zQ%=BKNRq|qZD6!i^03)!&F6Jgr0U4veWK6P&|I!;oz(G3lL;TbD^I$*G-Ns5Tm03o z^Gy-hX6zoQ(gU`!7Mt8DioJDN#>!beRFz2@vBYTRpGclsBU8Y<`=dW zxo#GPIGaobm#cNBit)Wi&Ajjcg%}wz8Fr#S1tB39(9+e*vTcCLBy$R}s-uG!cur=K zAbVVu!B)&Thkf2jVIX0vd-eHS3_F8Q59s&;beY5;R(t-pLq#5gWE9d`LP@ThQ05D* zj`cBJtzqlAlB`3KBBjjx)KIUc;gadxsnia)uO~GT0w9^p2?2MTuJgm*wT3EGw47dl z2B+6~UOXT_vRKTrgkg^4#2bgl_0lK;l%QxOgk)%%6nSr)QeVSSze}zP zcqhf#O2!)mW%|2JoRqE8c_v+~hQXw!{|*0|Hsoz1r-*g2tT*SAHn0+Z{dpAc$k6jy zIkg;P5F1tKmb5Uk*jvpt<`Ghj%`SPyIIW;j0p{~eCyo8+E&31Boy~wxoSgz=iWLK2 zhPD$tZ{tPbc%pCr$b3rYW7Hb^ELUizDY>WxJw-d?Qnn6poCo*w{n4#4GNB010tzSM z3z`_n@6X(T!{sI<_Fl9ln^a-9QS-JaU)+ zE_jT^-Ksj_aZ&LLHDysk^!zgARYz##vW~ailh)0_Mg=RBz6TGTRSE}QbsxUuN7!kyDjZ?yD9v=N3>S^?4M_=Zri>l-9rqlz@wOdvy=4FwXlgNC(_dq z5`<@uzF(V`W-tyupjqORM-S4E(-{Pg9fux1$|I&W*ejR9kLT$`T+oqct0Wh8z@yu$ z?iIETLQjFm!&&UEq^(exj}b-x6nm}UI$b5$Dvo{lb(G<v^ zRH-(I8h`)d!8iCV(@q9==1ci;jkE@xS@)erfx7nJ?n}RU;FTmxq?~&8!JM=pZJmqq zpZ%X5+Gfe1JU9+TwP6xPS{b{uL=VPChb1`@{WrauQ>Ly1L7B^N2*ee4^nV^^Fct}& z23NF#jP~ZIH5|+u#cAqv`%v9jc`Z$T|Bf+F*5O!GRUMwFbOAC7l|&;u;p z*g=h3>6ZaaqNd@A)Ezs&ntV~Nxx0^M`Y5l6=Ps4XNWXjduF2^je3VtHarUn(emr}( zzH8FD&+h^5=*8hVDtHrLZEr4;3nj>c*cC)nBG|B_G=w@eCQPb=bu642kES_6&PqbB ze}dxOhej57P&-*5Dg>J8k1FUxZ}v47r3Gty>nKw~@FgsH=!T})ZLAM%_=Ri&0Q6ILNj`tU#T)&T`Z$U+moYI>4cn_E;Sbmp(sy7T? z7P~Eutsgnz06{BCQ(g}O4?p;sSpRrD*q`|Eyn$X<%bw_@HH{Ecf9@fAt-rr02#sdu z+>vg41_b<`V6$+x2^-F?sGXS7f0{fwIoSBveYE$BV-EHZCTTJ{`egeIjj!Q6{QAQ; zn>qg(reyvl5BMg3((?mgt&D^_MHbAxH$D~j=5}?aw0hT{G#qzj8$XGG#;B#N{>Qnr z{uzFqMWKIRf!Y4`L{9&9w{a#eXCJX>re^fVe*1tu(#hLF;t%(0!?ja^qEtG%u~r;x z!H3fe`2yL^U?NBe<01k82l%9SZ&kS80<~=3dVtT|H5*Kf7wXkM_Nw{dsseiY80gK% z9(O~-kI4;#b5)<~)ez8-IU=l57kBx|7QgaqFL?c)&%1u-ZL|*Hd|4a6!bt(X!I90+ zRm^s9Q|TCcw8nXmQbF~79%sM9FWiq^`#^$r4rX)ZaBc3kP7J|{a5&u^OJ{WB1kKp8Kgzoe6VKt-wgd-Cn|Z zC|g&`{IxtPm*4R(s=|}HmiX@LOPc;i5Q!m6F)BOzI^%kx4w9m^T>0BLq03{Sq7Vtn zWY3S#iS*BIC%X2aC$D83G{)DP4j~zQ_$l0V=e*z3WIhRx)}3x=@tlo8uqfOeb5iPC zOmG12)RhWr!fl9b8Bpp7`Ea!f?Uk?fiM3hta-b%s3F3x+PeZZeaBYRT@l9Ld#@4A1 zh@;PQp;dTuTBvLPs#ySR6J3=|EJXOJ&l4->hMby50KIfgJ(npn#)mU6j!N|r0>7RQNV z{Clp?r;Zq0wNm}pexdp$iS?<$&8pzek$F!w(qJ#HQcKnwDnpTI!%kCIv2cn_Pxs`U z|HxXyWvNbS?MFUzy=Vq8NGqU?g2{Fn>0$;z^En@5)-?6UJUOlNa6jhN20L>LFAXx@ z;-3RA3IRxK5TbAMbH&|CeoYsEUwIdN>d-zN>)K<3e#>sB^dx6T>d-9|7;KF(;lvJ- z0=e>1xT_ZMgIdSLu)7m5$9`X^T8=rYb%h*kOA4LSfp%)GF-79Ny9*}ip>eJ=5B_$m zKQ44)*qff&X(Pg4!4F@}zBpd?Iz(g#)}&Qo)}MLk7Q_Z|hsc&y*)waJG5gHbH_#YF zAVW`%>F|cLdyabE{9fXwV#4u@2!)tG-+FOz0aoTvO^0vW9H@}Fl@jI@u?`(&%fx^O z#{n$MZ%UM~SJ#`V3=EHv(`QE`ZDF=eGCZGSEctxK_;C0RXmWNg`biOKHFnBiRVQev^=@%_z9kc zOeGjAZ3l4+L#6UAfI(w6V=;#=QIp$|?6w@lu5RVL%I#or&XDRhCaG-rVVZT~EsUH= znS$=0KMH8T{3+4_Gh%3!o3V8J)|0*LwOWwKF#U$IvGxRBhYRCgv^VQ$` z1j96=$q4y9>SpzRFZkB*OW3ZA8OOSP<-R_q&$`UH#71XCFj%t5!Hm!hVq}!>7GloI zxyw&$GSbzm6mRL4)XY;dLl`sDUm<9nnGJ)En+u-`=$n~4DRat3(dT7mJvrumT~Pez z{S>l`NG7r-AfIE4uG%*9`V536j1YS{OnR}J>f8ezZf^Apu5v{zNB(oJ z=o2kC*pqpFWleL(IT#=gIb(9Wg5x}@sX>V48)eQiH>RzRU57oVu2DwDsZw-nYgBRR!-oiDjB>T-0xo>a5es`9lbJo=Tj;{!uHQX$JzAXe9#2r&%mCkk zc(frW#B>;R1Z{|Qgz_g7g&liS&B3R~`wWXPSGiHVmC(@DL+vgg(9&xvaRrl)Zy}hr zt^+yQ*1Z+TG@prjC21FK<9OHj9f+h{Qp8kq*7%jbQzP~li6)1T2aIDi!Rr=M?8Uiv zNoreM)pw^y>PY~gcN{IinA#F5Azg`airpxwyT$>UV}@s^UYEv4rq(S=iJ z9Lx{KF4yQySZsai-Q;|X*HD!n%HMdC;frG$z7Ho~5cV0>cmi107 zJb-c~(fu}Y|0d2S?fL4ig1yTlM1g}3Y_VtR(1)7{%>pWdJt!336}m~??6ZgnoX)hK zG2vG{4znPx;$)vPCa_}V;b!Z5KKs;8Z}@y!JnVk4_ek;&%vRZhkqSIhwT6E7C{Cuq zZH8vG*MjDgt;N-PGaOi8jc=ktn*# z3^8a;ruF853c5uJw`r2Q=L(R31BhcgH9=B#rnLTf%+%b{g0ORU&>m%a3H5v zz$C1h#UsXZy!{}I)Msb1Pol2@CLD%a!w zz1ID};QH^|KDw{we+h@Ks88)mI~QqrBuU8sHM%OMtMii+R4hNz75lDquhN=O!?b_W zqfcO7G+A)A&O=rh^ZCNxnwJT3L}J4URQYf&HLv^E;S`G};;z*4`1cJ$g1!e~=}m+x zE`za<@|n12grZHKK9qHA`0s}5>|30KM~oI#(+5+eW-PkP5R6D?op`@l z_Nbw*^QqoU#Z~RDOLrl~7&ky%c@~2lT=}v?${&G(jG-USC(G7Jj0f2ox=Y7Yi(3nt zV&5tCbS8r0^jfc7L6JlZ}2H)rRyw(PEBH=*g9yWjlzo^2y=zIqUfXMs`FZB z`~AC;YO{i4&{*-DTz$12tk9lhY_kH8KGcM#rfZ%Vcd8_ke%|ThQBrWD!;*lG`vN3a z^U)h;VIeTG`D6b~-3 z!7WUK@A{G(PQ>iNU&>$e5pdb@L%eJ;{$b@zJw~j@##(Wy`#IO>l4)@51X`L&C zWQqO>risYF=Rs4}z}2opWn?Lec;R_1i-d7NnZz9`w$3S8fV7E_FK%V+kt29Z?JsQ8 zvjR7!K~yPebojwGZm(=F@f(GG(fS_Y(I}Jgv!LwRMZt9q%GjU}2MB>=Hgf!P-O6F@ zqImTZb9xR{!r|&IBo2!?>6Jw1*QRF5wj8}!B>W0Izk2$0RU>rcr27Jas3FqcL!2~a zA!+r(jNt8SPyJa^=05_xOPqJk>x;IQYXY-XI-q#Ft!hw!t0f<0%)wIDIMZ=8{^;Y$ z;`%<;E;mirJX0`MUsbEoQ9yEL#(UQ}LY2^Ki_}j=UnvxGizV*G1=RcrCt<(0TkhSk zS)>T^`-}3J@@&jI;AkePar{d`O(i{^sS9_wT4KRgvJ3Yn^J_ioETs)zl z)T}09GC|J~aW0Isv6y8Eid!-vC^ESNU7d6;>lPz3boUmJ<(8()EZ9-K-8HzRq{wc| zRMFs2=ivl?`k|@c2X{*1P$l`x*G2tuAh2()v9vI_Mn5u+qnJ{%sa9s${cfySn(WaE z*P1P{1RZf1?!BCMcks9*q4$`>6y0r`Q5@Jw+H57bXJEZd%TnG3Uhr)sil4t_#9a9( zyiBHw(I}`(?BmztTRK%mhV_dRs>Gt4)DH3|zG_MdAbHBgiidq}2M|sj^CV1Tk*w09 z%Xq?iFOka)A7R#s#1>_uFfz>+1GsS2Fp?3pzr>X{eCvNzx0+NRPSKPr9oUKDvqcO1 zB>rgt8UNN_RqDuNfY+?Cu#enUZZReI)~cOSF-JyuD-IgIp3B~=g!N^X+Cy#rQz!cF zJ}aSA`~ZHWR(pt=MN5k!5Sln*B=6Z63e#I$TqnXSwh?0)AAPTUbk*Jx_d5awCCj~P zH{?0_TTtm70XIpx&<@QJP6H>=ywp8Fgv=3#$+Edw+#s%ze*NBih@p5UdJd2%$YWTvLOr&887H?g3NjXK=!JjHeF!{mc``!MNqgb@W9J;H{pWV~9Q?j}ZFj1-%5e{2(yO#b14(Lh}Z%9bu%EVGEAl3}Ml@nCVVE|!@ zIG)rwGOVlJikSMz`pjLPD!$3*fDB~60R2&GOMj8AePaFYgWy^`W%d0GzdPDsG@Bg@ zcv)k5NMjYxfmft5RNgQP>*Y<4Y41V-EkaipDb=b|fN!5<&>e;ic9}E*kZ< z=$=Y&QGswF)uqKE$%VNTo;#U=Gc&o*?5`<9pb`~T>*C0=hd9-#Wg{O?A(Hs6M`ep0 z8%=%q+01EhdeCAxVu=kV+c(OAI~-*n^)Px1xB?IQ;rU-E;+l`lX4s~ffz-oWcxk55 z;Z9VL`n*X2w9#^c$SyqB^j>Ub2`O7anRHDcbIV4qSQO0^1gRW~X(VFnJ0-8(gf7Jn zn60eULN=&M5h_{rbvMhu)7HNUc7zo4PRS^U$7qM21$M&)a}g^LVIp>#>C}`bP&+hf zkZvos=7sTARdh&`*<96zNqKC&zCKkkiPsc_sr_8QQr8^T=OJH<-JubsB+w&>BK)ol z6Es2q{OfK;T3C}pEVd_n=YIH^zM8|eUlw39E|Q% zMa2SWdv5jzO`hM?iDTW%M=!-$=ca{1zyHfDdJ}WSNXYxp)F6o`ld@I#AquHH;po!8 zh^A(p#xzq!ne3G{v$;Ds_bO;kn&vZIG0NYGm8Gm-mAtI})TqS)X&97^#5;6PT-EoARl)*0W1BC$p9 z#qrYd5C#7svQQMUbgV_8?sA*O2N03r;OcANevL0c#sLhFIG8a&p%&(I z!qdeQe-DQup6vh!|FnVNGbNXQB`JkE%9gJn9p?K^I`b}>l5yl%xH5MNxCAh=h}yK6!qb-h z?Kt|4-A2J{9;P@u`N>Vm&IXfT}J46;( z1PGrH_=K)WDB!v2J0)lS|Y4W|ysBgo=|-yejn-T4F*O|XvcJvoF3qy z=D?$#+4G757(l%o#i=Sl{{#zjmo&IlUmY8UiePPJ)jOnlpnKcQS{Fl(Y{5L2$LCl0 zPVeiQgE}^ONqGy{h(wGq>bkZ4<8Th;#7k3+ z>1uey%kVYhhE?B!C$&>>akc%rGB~6ag zZqNQNs`BjrD-wn1@@{c+pnUUEL_rCgpvZ-xmauUFRIO=?FF=Q^<_OOQ)nNkG&)6gHZ;UUmihrX239#=NwcYb&1&Y?9R*4(V-8)d6#o<#3C4g zIIh-oLG>EK+I#{v)s2zE5$5dfQ)6$%caQxTP zTba802KMg4qA^EkLr*8lP;5G2ilp-o1~K2`GRxJ2B$8|s{)CPuWpG?O`)U7mXc17) zEjQjD2ROD%SP0t*J{OuZ>y-$W8Cw@0r)e!2JiNv$RBgbVAr{3f3kTpE;vPdr=&Zds zhpr;4Kc7d>{JgvH(~ln|k!D1rdJgzEK=acJSKrD5>f7bT$5?sQ3gs~D=etf%EoiLu z{CwYN!eD4B`W6lGFl0;Zlpa%As5#*MenV5S!>L_*-f~Qo%tk{EmR}qL5p14gTHy>q zW-!LKl7~vp5oqAjUJnE0CdA*e9hG^fEV@=+nH|NbdnYvRi;ltn9!l@DH}cmcu%PLE zV_IhjRTL_I`#LmT%B^y(izwys=vt0b9+3#7??dH2+x*OQ&ibC=bBvX|Fcf$EbPZqd z>)5;x7Ut*}v>Wx|+~6jrBuAf9+UP}kR3ocH))}D^q-rd>s98>~M#W2<29MqOU_e_B z__AMCO1aFa{%n0b2lq{#2sC24Ou%MD!TpnV%n zhR)zn*!!hldy(PPZs3d4Oa=v`QLC!D^jIy&JgLa?mFC*JR+k+5E^f|UiNm3%O|V}} zeb$cclS1DI{{5y(SKrugS%*gar+ zHWWw!s5vhe4d0AlTUCFioL{q*Ofx&r#=^Y>NIF`vC$IAB?44I9+ckF0{vX{^*cKj{Hj=$FvPb{=kuy|<@LP*pl!L8h7t1m zi9RK5Sg~8uVf3^=yoF6@c9~wMW8QJa%m;p zL8*xc&CS*9xJ2geR69@jlyHDGM5pxa7Jk@v<^AFUva({)avReGzAb7h!Vz`n1C+F} zV!J7X;RRRGVb9tGXWpv`b;F7E>x}f+bnBlOUq|(0t_aKGQRPyA7tiXDNjr;YD!R$S z=vqeCnTfWDQYQrTJ71Xp=FxBXdEeD%`lV#A&ggT21P}`}9)9#kJdV&y`JVYj$Ncn? zK9pXAoSW|pEV{FpVw35FSB+Br0t$p-3=Dd{ZvD4E$z9uL2sFeQ+A5x(C>?p5zxp$% z?Hi7u#QQrkCMT^<3r6~m56=5W?^Aj~fD7Wt1jZ2@6E(Psm#g<3aih?9)8dXCM@I60 z6^~=26%j`uDGD^~$U2|1S%@0P`DXrz&gqspJ7~$AvX#-iQiwm77)T=c98iU{MBBh& zbE?gbvxI(4d3tB zyYHjWTEH3S_?4aHIZa3A7uDPf6%YAo-AjCfdYH-;?zKgvn7hggiuDH}CN)^{&y=Nx zJ!EU<->n7Ttma#okKdoHn&0fz8p9IruMfX45xv?!>Jtn6kUrdtMOo)n4t@0|h)D3V3gb$81#BPmq2CN=9}V5+1A5;l$%8(=Co^ zYWHKsvRge3sHY7XK17bm4wbNw#Br>q;d%bTC-$fOOioG1mGSqDTnUppU3E_aY@%5j zyfRXrrK|+1K^cYe#(q(GkKCN#`>IG5BcrG|^%X{*!Aa z-@sK|;SVs}X>bIKQvG$d+_`*QFm%v&Vn@|U2K@C}X9gWw7k5?nCw@C^FJ87Ig4Mh) z7qz~gPe~-PFK0c&8w`F2(p*!OJ(X4OOAkG5PfD=pB5BwuYWl8-s%8nY*u~Km8*Av6 zM9m0anqr%DTIC9d;syE(*%Vg~Derl==! ziO@g`WV3jEvr~y&{wS+zn1?stZhe~s_%7RSw;La_MGt^)K20c$O+8eASkm^ox)Vp^ zLd;wE>w9A%q|nlPYw+cGQP9=H#&&;bFiIOg)uPo)YD7olK|$WGy~M=QCu#!PGEaFU z4uHtMyizEF2y4h_xbqhnWp67BcfH4>?PYj1Zs>MW0PeS?)Edy}PqJ1T5rUB|E$@l`QWsQrD z{^`bU?hd?|$Vm!Z#p|i~7XeJ$5(%5|ORo|WJny61&jHE^CzFbR+(HAE}Pbg zyqZzH@Xthddt2F|fA)=FL56lZoRjmFU(YZ^rEygx&p~z(@}GesDr}|uucs4ce`u`T zKDC9(qm$?@nY3$PxE^O6<@<;PHd7L>DB@#YL65Uz(vX8S(pY5FDjRUZC}TEeVkKH| zVDS>NK07W8O*A~tgyp5Za`c|J>bhNxnjOedP1FejW`a7O8`F<>pY6bV#&N0qjKjdN z*PZtsJ@BvI_lo%RCXGN%?B!NVl=B|kNf;rY#{I?5F5Qj6jU~39wXv($MY?{sCX@HF z;kt#gB0UU%36@OY!t@xq-8a90Wg#kxfR(3x23bTuA0tX0y2m5RB zm&fZHlb`zDFPQFcmyJq-DCMwW!GZBk{JH7rE}HvLl1QRZIBw{ld5>hidBgiitA`r` zo&LM7=N(+J!lR8EiX4j&AJ_I6KWYXh5vFsthQq@)X679-@<8^kp+ju8JBj_0nC&#qI;zI}iBVx2vw zp8n6{bUF=~uE70^F8Ajf3p;<$)^jUr-)`)Q!Pseo`oqVWv`cb?Hk{6E{r!YGmCYJm z6r7TQ*Q@z>kGndFME$pTd71*oym&b1KW3F0pvqw6#OIqTxE7-;@Oz%BS)z6RQ&k6dJp_PlhA7xh zt+o);_vUc|6UUJCb&{`jZqdfK0q2oxC(uuTBNjdDUlvGu0}ThA2V9`=_aooAJBC96 zw}pgn$IzZ^JN%~r`oCIlmt52_ExcRG|Kf7jcV+20cfS;F8lWr;KL6*gFVVkzP*3oG z=YwAE2YA3qmX$LNy7^u?|d~GoB{w-0uNhK#74=ofzPOh zJ*`UT>kV{k&u^PBr31n82ju~PmTCPKJdSL&p-4)lq0lx0qVm6Oec@|Wq`+&Ht z%?LOX;*eIT`f`di$MjBLr-PCQ*2m8Km_$Xx?IDPg5(t`&PaKyVP7>4O4dAuD{si+` zXx%z#RMv;FMdTYbdu`2*t0J}MD-bpHru4#4QEXZ{6>M@o7<+9|*-Mp-#$>A^e?I@y zKu7e>-A#T~aHzhv*k$}3hVxR*@MC1gQ6kKGZdhWKTW21cD0Jueq|$&*yzaM5HJb&9 z2B4tqki=s}(-N0!Em}MwABUL+R`_QZQa2a4Q`iLXTZcqW`;hzc`3HQiS~s@O8VZ){ z!O5dvz=ktCE6PjoJpikSt;LkfUrMhjMw{n!R@|Y4n|-V8{d-Po#v-z#;0R`9@+B?~ z)|7#)_^%Nn&XI_6YCa(=2WP%FnA{pA>M7PrXz!KRtU^nmM*^D>Ys+h1g^l)XTZduA ziB))TgmX5F$Z=%EF|fj@H6$Tz-hGSZKP!wFLUMrx-*MWu;CLqj&Bj)5D&PIz(xG0#B7u;OrLT?z zPS6k{gQ3B)_Vr?QfV*qA?0C6LxG^p;Pr|vf&95OG7d>PobpLrZ%2wFF$a|tG%(SWL zQa*&XQ3hb(ozsdH+)TvQjg!klGbU{*;Mq*!_Qwfe=toV!fdzOD;IGQPptTgJqj^c} z_Qtp#<*wLH+C3K1xE#jRyBlmcQp!}E+me3_EfWav|>9r3jsr%hAeb%EF5eDND8^ZE#yjOW+g9vr|h zYT9_n7%b%l+WeiHUV9`O*a==ClbL4ZXa=K?Nz}U;xjVo4()sqMoy|wVCM^cuqIY5@ z9mWKT}G=1Gn#P1gMQy~2SdY9 zXNz<=il?WMDsXh@`TPGu_ZFWP>xLjJDCZUbDsEKYd~I&&F8y!AEWjY4&?PyVcn@55unsmxcYWgq3^m_UZplSdoYWruRBR+R{sQzHcKQGs?SdrC|9sjmfCR z5C$`-A@f{9lRO_*G}^h*kQ39XIa;wdOD-3xpY@qAh{jr^bwmzW2&hX=nM2=@fCFUs zhN?^XX9USmpV6;gJm^(M;WJ-4l$)gEa<+N54L_aa=&W<)0fhYD{nB(^o?pg+1c_5QRp16#`Rj)_%{D> z_y0OM@1Q2QE{!8adJ9zvhAJpkdXW~2ktV%Y1ynjnuP+dgZs@%uy(7{Q0qICnI!YCY zpnxD9_QvVu+!5s(m}OgqLQo@|d1yww=ze6bK%Y zw;r5p?)N8^LRT^ZDoKi+X}QwfEDO&ne92<+&x%@u1~sSDeJo5CHS?-VSOt~ENW(&9 z#yosl6K~y}3@OPK(4w3mc22)O!O)-T;Xk8q=M*)LKW$l`&>qqCg(sdVpD4-ob#kJ# zHkq3hcisS3qHZX$lF9=JZ|#v$hv|DK1wcSxM;+6bEi1bpSUE_fLl_q7?)s1B-}9DK zcVnuFCPe0TFG+irAMCn2CQjx34)Yn;XbivXB&1-+xapc;$$tXJa?njX{L7f8I<@2O(=^3o;5-K3Vng1{%r01q-eK>=?o@qKLI#xFEA-F0U03lIPBWlMT0^nJfvcpwfld$JaY@Jjl*?Th`7w_LWg z+@K7ceUl50zus1hISTodvYNPLQV>;u&NU#?j@)9P5~*>*7LnlP7WC)*Ov4;78g^33 z5}~ju%SMwTx|AX)Wyk|_H0qzQapUGqi)ZS_VU7p$C%8uRfe@qL=VA%m1bdyF+6;pm zjD9wcnpVV^171zfy1XPs%lO)FtFab*6*oHT4MOuVr28%xy_dUq8OURW4)34Y4T`2h z;$ox39y?m+I9RHr(I)}XRtTIEiK=88{Z4DQ8LUl?I1PX*yq3|dX!_)0C`uL36$Jf+ z4;hSHZGQ1mUrPM_)D%id&;wk@J`ZgcgQY`i`WGb$iMit-Pd+h+8(cQ8d8|$89S}Eq(lJXpYK|!A#NTSHHWiBE1 znc|+yljwTi8^3vehOe;;oCF(Yqfl9FtYh?IsAU4(YXb#C0`4CT8f4V6Fp#b2fZb%su0J)k)97^uE=SOV zT_PzEWPI0)BJu=&YOrlR z#K>S)L1rewtpNMc%tq9_*eC>$*6f^KLBi_9W+?yKFrv3|ncOb{ZyWP9sibCt4}bMd zueBLPf2{~5*N2Mlx=oSqPM<=Wyo;Akc)v7Sj0hyOv9^=5AuOj#(1IglvfU{^W5^h= zYOVWIEHWd#twdwx7nYbj@{Fv~M9$rd8_Cc_D+6ng*8PwlwKvLl;J040D^Vw9_sT8Y zlMo^p9I#0ZA24bYb2Rc&4~`jACdjKwgi;Gku7>f=6T@mqw|dFdz;Ax1eOwc>g2$C+ z=aY0fX8RUDzSHuS-{oovXm#8}y3v0Ai_HN}!GgLoNy6-UpflO~N?op0wqvQHuC7b& z86ALr_P?nw61VgvQA#-mF|6;DAUDE>ruwmJLsqpWTPu2JRth0oRd4k`hqixIZ|A3JDEoLu0F2bNgNK?+SkN`7e7Y@5~X3YBTY?dW=V{zeDyCB zac{Vhh$u{Pela-m-OTR0+!uL!bv)CR+Jw8wwguDK@2^6W!6*WkmZP}p6-fpROSm#2 ztNg`zUF>5|S%#d?KNP2ww02+PrK=5f5DSqhqPQ-gacd%}C;@1BMo$6j=S*a)6kP=^ z1qHxzy%3)`c23@W&x}eeNb;_mJ><( z^dXy7;4XTPBbl$|=dI$W4+4J~>*ynKx%~wrcR9!_P`$UpZ;czPXGPqtyRoCh@==wn ztfM5nS?F3Yp^TWv!8JgOz9Xo6cph|`cK^FaIP>$^y4m^9j`~GM>hcZ0MzHNw3g|=} z)31i2*v;P)HrmD92LVmS@-&#kCd3K$Wc=v z`nelh^id8KcQcf~V#bJ>R)0KC?iQ{j&%Jz1-WQ?5l*d$w$vm&4C$lvVpRx%en$00qms6cN&V~ z)wHc>ZEZcndRl3cr|OHUZ7x$G&D9(vFuWuj8L2VR77N##WxZh%QA6zZXbw`-9tu%4 zfpiR!$x}Y@+L)phlZ`)L?gvUYD3LXOL=7?Qd+5MbB4%lkWQDqxQ_r>|pSKCdM_segD`ZHZ~a*cE6BlZZTkDMS&Pm35Ygdy_(|@qAP7 z0%2vvmQhVW=U1^6n@j5kC3YMsb zVxXV>^71Q>m}Ze~w<()9@X~}!j#d*Oh?RHuYt76d1(_mKG9r>M4 z7E+9x@c>JKlK0-PGk&M%AU{|VQN0cCGi;?4N?6?{DI$%k*}8lcXp>XG!E$(@O(#4H zhK@igFL()^X=Q5G-hG+LuUPy!c&-QpMRQ{2AXTR~7VX-*=di13zQpIv1S#EN%@Fmi zeZ>@R4StmMg!u8wpnb9QP>q5-`Rr${;n##=93kZ$oOJm5Cf*JAZtfkeZ0|mh{NOyO zInhk?`SaJb&~hvO0+TANjeC4-PR0%%@O*%c{h`%O7cnoac zjv|)}n6wjPsNDuSj-qm7?758bP%XGbszo}-C#!b1M?2(;IFeQ;x}WJOF2|vS-@Y}3 z^u+t`S4cYB2{Y}1wWU7a5OA@1ac?n1vBVjg{>Z*E_k`(V4YMtYnefy!OXoxqVM=Bh z@Z<6F(beD0@AApK1)^n~rlub=5?AcvpOFghO?w2kYu~l)4Dx@SZ+eWl>e&sTC0o&l zu07q62L#0S*GVB&bpoQyYQms~?p6c50 zwi~?Opwl<-&94~u@(=ORP9dnOUvjf);(RG5<2a_)8E#23x<-J1+20sXjRq-bHl^%wb`GAU4Mm$tvBh#3*RW! zY$6KeSpaCZOM``XBi1P31o~d75a8*V6IZ-FQ6yvzdT8U5`Fve@d&R;D|A|&6_vt4? zKz+FBit;pC0ml2h0(s2x<#C9vuAyd)O`4E&sFH$uwtH(j*_)OAYnfU@B3I{o!p`Tt zEnj~IzC%E-)`FnWlpD&xZOi=DX2i`ffwwgn2o03@jt?TC=hrhu*C<0`Shm+HxAA-- zi7P4upKvdrj3I-$7zmW*)sK;jU&tT2rM@(@Q?}=S#|lEwqS6!p%8J*lR9+q0yKVOz zd{$EJ1aqDA_%2IH*6Sq&g|t7@t;Za?uN;HOY-W;nu{1aRDH5rnQ-oK@b54gpPh{4q zOW6Hj*){X#3$?IiFk9k>=^-7`5*%ZCeS$rYAon6~5Mz>6Bc}0^{XKnOf00RrFvLr8 zC>llN4gk}jind}nlR8C^)o`v;)lRJoe=D$u9k8_AdinS3+@Ov@F#u*zd@Wp8_6<{0 z97YZ?07Ut{dB%8mxa6D>UEjj1+`%e0$=}9prXoG~UQxv=D$ytuE0(Q;ns!ZXlu8pq zoY_B0-;MGI$RNtXCq$l{$ehPSsG#&XGeZ|@8ycQOtkzDeh6L1^ z;f{3$TJBfO&nM3|Gj_;nF7pM{_O684jy#8lr{EA`yA-s{)C2*XgCh~A1l3icIc|yb z_|_~b9*#TSEZYQrY@XK=PArg*MS;LnRh1i%YqSOttFIa9Y5-Wj0`ndBP>Ut8SXrRW z(8pMGK3ed^h@4{t9{jioO(_iGr_m~GUc0cn%3Znm3>{nVZzFMAIwcMj@pKY9P%xpG zXGMPpnvuPDPoiu-gL>|>Z$fA2ASH&Rr)$5Q{ew%VYajSuwAN1pG>51kzKsN8n8g3R)R; z5ihIaMVLX>v^aeaRE88<0Ifyv*ERZGcgNGEX0$a|K&00>YkS_z%*`)cS504-fUts4 z6?V%Wj>sjh_=x?yp>he&{(kD2&*;cLKS;?|PGB%&&LMFscv}vjDJXLuN_{z# zaj(=Vg>NrWR)nYl7Ot)9I@j+Bq^Xl0sBg=JFF{U;(tC~MQo2Ek*dZDAY&XoU_{c>D z+l;)LGI3e|eB3U$;8-6z??MQN`^j4Dlnkd?{ydB#BMl~^WSn#d8?K?I6^#O(8oEq9 z+}ol9nqLY!l82+j^+BMuWt7%&8|YTjJEm9q0%aXJsNN#97X9Ly_&47 zMivx*RE|oNh)<^1^c$|S$1oRm2@+pC9%{Qksqb4oiyZn+`n4-7d*q9j$Yt8aTi$4o z9x8EC5#~aty~r~1H$$T^a~zt(<45m!PM=S8y|iXyx_sA&Y z)2X$2WPo0i-n_};05J5j;34q!=Z&YOXQM655L!X{(}*n#I1w`Sj-o$uf-jEg-!(n!x``f1-Qi#Q9t$1IZ3`UHz7SdD;CBj`2x zNJw}bWpJ#l;6RTK(X#@O0l%Ho6|{-S88lwJKa_MuVcbGJ>1i6;w}DG5OiL)lWL+pZ zI?hD+fT_}TgRI)eHMmLV`nNDIqOc#7_t$*`Gewd_6V!3cy@gr}hQa;GiI98GovAJgUF_;G)LoxC4!1&$K8eZ}{Ju%uw1tXv;Pe4N)Ds6`?V#GwHo26gb19pDw~+gC2>82lr+LBK`mXk;QtE+)V1%w603nSReyt+8sFv&VSF(rr(CEmOp}(g-g>%RvcpSePB>iz<`HpS={)82Als1K~ z?NMNLK zurKe2{9gmBR(^utyP-n)D^R>wOPlq3S9C0=Hb0(5lmnNo#u)`X3&$4veV~;Ln66PS z6&EmwZEnW^_1BSz^*Zwx!Uy2D_ltPbbt0e-8`T-*_23s}R!QV>W$ms1PGfk4*A z#{}&4q#ST9hx+2;6f0aU8$s_iypbcIwTa0FZEJnY1}$WJ+)a9($%6i=NZh{VS0kHG z6YD{ZQQw#dMASSHDBl3!z!u;)+rhoIt|W#5D44=E;ipgxChQlHKQZaUs^_+eWxwIz z#F3PnAfTiW6KG=&TG{e!maw#xpme-7^UviNmJ`1)S;-^`6Wt~4d&&2efl&l2yNRSb z?`CuL_G28mx#RItfG0ueM)RDOa6C-Cu|I^s5@FD%4iaUeLqJ_wy5|BlQ+#G*Nkdym?-CX7c55$9jYTO}ExWAgcCr^TKcW0HOL`&}-TMorjWHan`tiaU zOdVt^DrS64=8(CF8qzyBl{kIJ<>}6a)`$}v8kTQr8iKoHRVpU^tjC0+&bheBocEzG z$ydbvI@eJqRd{;$-QwTm-w0|eNvp|fe?9DF(CVn7Lev<*;U$55&@eIZ!dm_2-0$A> z^~6m<#G_soxX(4rec%@j(h;UR0vsRYvSgKH7dHLPfLAM05ciNV?xVsp#`Ep02#46i z^1{6Vp;+?xAzk`h-`oKv6Q+8QmL_(|cP=HNv)tBkc;D|FILZ<9MIDhcqCjTm#8!Vj zT`SP+{`9!^Q(a=cz1k>GaU(G1ND-qBBE{s4QVXAu31It_x3)0?Oz@?yw>HG5b7s=?xi=kgS(afsZ~wtoB8PF=qy8J81Gcz=Lx^}@}rM#$@HeG zp3~1^_80lME(HtMmn!G`q2?bCtm3%GxWXDHT{w#|ODf&t!#MrzBV$!nFf=Fx^!fAYieOvX8r7Y55dF~ORL+LM3@6A9bwsW%PAdSuF2Z;&Zn^SU#270Ovy{)g7H9` z?9*+fR9`Lw04uO|EbIV#BOioE!#=1Q3v41H7o1TSg`$KSU~s-LR8|dlV_oqGlQPUi z;$BO*0XbR}Y!GF&FqBdG~*K_~Qv- zYAs+5{nx`nFjWi$ei{whVKIaZx!DjPKTaTjuIyu@6Dgq{pIa&*Drn9qBL4M#|Nbd| zVgW|n{_~Q59{y}aP`hDvB%p<0ooNe%%u)z%d1CWm*yPNxr!3}Ah0|anA2t~G&W2@4 z$BcR9zrXK4pZ$qK|I65TOp*A{5cz+fJ=oKDe1%PdMXA5wDm)e_2?p$AJycXzsDfJr F{|77diwpn& literal 0 HcmV?d00001