From ce1604e3fd46e12785c50388aa69636879e917ea Mon Sep 17 00:00:00 2001 From: ffrancesco94 Date: Tue, 16 Dec 2025 15:06:42 +0100 Subject: [PATCH 1/4] Added dask --- content/dask.md | 742 +++++++++++++++++++++++++++++++--- content/dask_opt.md | 165 ++++++++ content/img/dag.png | Bin 0 -> 16170 bytes content/img/dask-overview.svg | 419 +++++++++++++++++++ 4 files changed, 1278 insertions(+), 48 deletions(-) create mode 100644 content/dask_opt.md create mode 100644 content/img/dag.png create mode 100644 content/img/dask-overview.svg diff --git a/content/dask.md b/content/dask.md index 222131e..b126e9c 100644 --- a/content/dask.md +++ b/content/dask.md @@ -1,93 +1,739 @@ -# Dask +(dask)= -:::{questions} -- What syntax is used to make a lesson? -- How do you structure a lesson effectively for teaching? -- `questions` are at the top of a lesson and provide a starting - point for what you might learn. It is usually a bulleted list. -::: +# Dask for Scalable Analytics + +```{eval-rst} +.. objectives:: + + - Understand how Dask achieves parallelism + - Learn a few common workflows with Dask + - Understand lazy execution +``` -:::{objectives} -- Show a complete lesson page with all of the most common - structures. -- ... +```{eval-rst} +.. instructor-note:: + + - 40 min teaching/type-along + - 40 min exercises + +``` -This is also a holdover from the carpentries-style. It could -usually be left off. +## Overview + +An increasingly common problem faced by researchers and data scientists +today is that datasets are becoming larger and larger and modern data analysis +is thus becoming more and more computationally demanding. The first +difficulty to deal with is when the volume of data exceeds one's computer's RAM. +Modern laptops/desktops have about 10 GB of RAM. Beyond this threshold, +some special care is required to carry out data analysis. +The next threshold of difficulty is when the data can not even +fit on the hard drive, which is about a couple of TB on a modern laptop. +In this situation, it is better to use an HPC system or a cloud-based solution, +and Dask is a tool that helps us easily extend our familiar data analysis +tools to work with big data. In addition, Dask can also speeds up +our analysis by using multiple CPU cores which makes our work run +faster on laptop, HPC and cloud platforms. + +## What is Dask? + +Dask is composed of two parts: + +- Dynamic task scheduling optimized for computation. Similar to other workflow + management systems, but optimized for interactive computational workloads. +- "Big Data" collections like parallel arrays, dataframes, and lists that extend + common interfaces like NumPy, Pandas, or Python iterators to larger-than-memory + or distributed environments. These parallel collections run on top of dynamic + task schedulers. + +:::{figure} img/dask-overview.svg +High level collections are used to generate task graphs which can be executed +by schedulers on a single machine or a cluster. From the +[Dask documentation](https://docs.dask.org/en/stable/). ::: +## Dask clusters +Dask needs computing resources in order to perform parallel computations. +"Dask Clusters" have different names corresponding to different computing environments, +for example: +> - `LocalCluster` on laptop/desktop/cluster +> - `PBSCluster` or `SLURMCluster` on HPC +> - `Kubernetes` cluster in the cloud -The introduction should be a high level overview of what is on the -page and why it is interesting. +Each cluster will be allocated with a given number of "workers" associated with +CPU and RAM and the Dask scheduling system automatically maps jobs to each worker. +Dask provides four different schedulers: -The lines below (only in the source) will set the default highlighting -language for the entire page. +```{eval-rst} +.. csv-table:: + :widths: auto + :delim: ; -:::{highlight} python -::: + Type ; Multi-node ; Description + ``threads`` ; No ; A single-machine scheduler backed by a thread pool + ``processes`` ; No ; A single-machine scheduler backed by a process pool + ``synchronous`` ; No ; A single-threaded scheduler, used for debugging + ``distributed`` ; yes ; A distributed scheduler for executing on multiple nodes/machines + +``` + +Here we will focus on using a `LocalCluster`, and it is recommended to use +a distributed scheduler `dask.distributed`. It is more sophisticated, offers more features, +but requires minimum effort to set up. It can run locally on a laptop and scale up to a cluster. + +```{eval-rst} +.. callout:: Alternative 1: Initializing a Dask ``LocalCluster`` via JupyterLab + :class: dropdown + + This makes use of the ``dask-labextension`` which is pre-installed in our conda environment. + + #. Start New Dask Cluster from the sidebar and by clicking on ``+ NEW`` button. + #. Click on the ``< >`` button to inject the client code into a notebook cell. Execute it. + + + |dask-1| |dask-2| + + 3. You can scale the cluster for more resources or launch the dashboard. + + |dask-3| + + .. |dask-1| image:: ./img/jlab-dask-1.png + :width: 49% + + .. |dask-2| image:: ./img/jlab-dask-2.png + :width: 49% + + .. |dask-3| image:: ./img/jlab-dask-3.png + :width: 100% +``` + +**Alternative 2**: We can also start a `LocalCluster` scheduler manually, which makes use of: + +```{eval-rst} +.. tabs:: + + .. tab:: all resources + + all the cores and RAM we have on the machine by: + .. code-block:: python + from dask.distributed import Client, LocalCluster + # create a local cluster + cluster = LocalCluster() + # connect to the cluster we just created + client = Client(cluster) + client -## Section -A section. + Or you can simply lauch a Client() call which is shorthand for what is described above. -:::{discussion} -Discuss the following. + .. code-block:: python -- A discussion section -- Another discussion topic + from dask.distributed import Client + client = Client() # same as Client(processes=True) + client + + .. tab:: specified resources + + which limits the compute resources available as follows: + + .. code-block:: python + + from dask.distributed import Client, LocalCluster + + cluster = LocalCluster( + n_workers=4, + threads_per_worker=1, + memory_limit='4GiB' # memory limit per worker + ) + client = Client(cluster) + client + +``` + +:::{note} +When setting up the cluster, one should consider the balance between the number of workers +and threads per worker with different workloads by setting the parameter `processes`. +By default `processes=True` and this is a good choice for workloads that have the GIL, +thus it is better to have more workers and fewer threads per worker. Otherwise, when `processes=False`, +in this case all workers run as threads within the same process as the client, +and they share memory resources. This works well for large datasets. ::: +Cluster managers also provide useful utilities: for example if a cluster manager supports scaling, +you can modify the number of workers manually or automatically based on workload: +```python +cluster.scale(10) # Sets the number of workers to 10 +cluster.adapt(minimum=1, maximum=10) # Allows the cluster to auto scale to 10 when tasks are computed +``` -## Section +Dask distributed scheduler also provides live feedback via its interactive dashboard. +A link that redirects to the dashboard will prompt in the terminal +where the scheduler is created, and it is also shown when you create a Client and connect the scheduler. +By default, when starting a scheduler on your local machine the dashboard will be served at + and can be always queried from commond line by: +```python +cluster.dashboard_link +http://127.0.0.1:8787/status +# or +client.dashboard_link ``` -print("hello world") -# This uses the default highlighting language + +When everything finishes, you can shut down the connected scheduler and workers +by calling the {meth}`shutdown` method: + +```python +client.shutdown() +``` + +## Dask collections + +Dask provides dynamic parallel task scheduling and +three main high-level collections: + +> - `dask.array`: Parallel NumPy arrays +> - `dask.dataframe`: Parallel Pandas DataFrames +> - `dask.bag`: Parallel Python Lists + +### Dask arrays + +A Dask array looks and feels a lot like a NumPy array. +However, a Dask array uses the so-called "lazy" execution mode, +which allows one to build up complex, large calculations symbolically +before turning them over the scheduler for execution. + +```{eval-rst} +.. callout:: Lazy evaluation + + Contrary to normal computation, lazy execution mode is when all the computations + needed to generate results are symbolically represented, forming a queue of + tasks mapped over data blocks. Nothing is actually computed until the actual + numerical values are needed, e.g. plotting, to print results to the screen or write to disk. + At that point, data is loaded into memory and computation proceeds in a streaming + fashion, block-by-block. The actual computation is controlled by a multi-processing + or thread pool, which allows Dask to take full advantage of multiple processors + available on the computers. + ``` ```python -print("hello world) +import numpy as np +shape = (1000, 4000) +ones_np = np.ones(shape) +ones_np +ones_np.nbytes / 1e6 ``` +Now let's create the same array using Dask's array interface. + +```python +import dask.array as da +shape = (1000, 4000) +ones = da.ones(shape) +ones +``` +Although this works, it is not optimized for parallel computation. In order to use all +available computing resources, we also specify the `chunks` argument with Dask, +which describes how the array is split up into sub-arrays: -## Exercises: description +```python +import dask.array as da +shape = (1000, 4000) +chunk_shape = (1000, 1000) +ones = da.ones(shape, chunks=chunk_shape) +ones +``` -:::{exercise} Exercise Topic-1: imperative description of exercise -Exercise text here. +:::{note} +In this course, we will use a chunk shape, but other ways to specify `chunks` size can be found here + ::: -:::{solution} -Solution text here -::: +Let us further calculate the sum of the dask array: +```python +sum_da = ones.sum() +``` +So far, only a task graph of the computation is prepared. +We can visualize the task graph by calling {meth}`visualize`: -## Summary +```python +dask.visualize(sum_da) +# or +sum_da.visualize() +``` + +One way to trigger the computation is to call {meth}`compute`: -A Summary of what you learned and why it might be useful. Maybe a -hint of what comes next. +```python +dask.compute(sum_da) +# or +sum_da.compute() +``` +You can find additional details and examples here +. +### Dask dataframe -## See also +Dask dataframes split a dataframe into partitions along an index and can be used +in situations where one would normally use Pandas, but this fails due to data size or +insufficient computational efficiency. Specifically, you can use Dask dataframes to: -- Other relevant links -- Other link +- manipulate large datasets, even when these don't fit in memory +- accelerate long computations by using many cores +- perform distributed computing on large datasets with standard Pandas operations + like groupby, join, and time series computations. +Let us revisit the dataset containing the Titanic passenger list, and now transform it to +a Dask dataframe: +```python +import pandas as pd +import dask.dataframe as dd -:::{keypoints} -- What the learner should take away -- point 2 -- ... +url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/titanic.csv" -This is another holdover from the carpentries style. This perhaps -is better done in a "summary" section. -::: +df = pd.read_csv(url, index_col="Name") +# read a Dask Dataframe from a Pandas Dataframe +ddf = dd.from_pandas(df, npartitions=10) +``` + +Alternatively you can directly read into a Dask dataframe, whilst also modifying +how the dataframe is partitioned in terms of `blocksize`: + +``` +# blocksize=None which means a single chunk is used +df = dd.read_csv(url,blocksize=None).set_index('Name') +ddf= df.repartition(npartitions=10) + +# blocksize="4MB" or blocksize=4e6 +ddf = dd.read_csv(url,blocksize="4MB").set_index('Name') +ddf.npartitions + +# blocksize="default" means the chunk is computed based on +# available memory and cores with a maximum of 64MB +ddf = dd.read_csv(url,blocksize="default").set_index('Name') +ddf.npartitions +``` + +Dask dataframes do not support the entire interface of Pandas dataframes, but +the most [commonly used methods are available](https://docs.dask.org/en/stable/dataframe.html#scope). +For a full listing refer to the +[dask dataframe API](https://docs.dask.org/en/stable/dataframe-api.html). + +We can for example perform the group-by operation we did earlier, but this time in parallel: + +```python +# add a column +ddf["Child"] = ddf["Age"] < 12 +ddf.groupby(["Sex", "Child"])["Survived"].mean().compute() +``` + +However, for a small dataframe like this the overhead of parallelisation will far +outweigh the benefit. + +You can find additional details and examples here +. + +### Dask bag + +A Dask bag enables processing data that can be represented as a sequence of arbitrary +inputs ("messy data"), like in a Python list. Dask Bags are often used to for +preprocessing log files, JSON records, or other user defined Python objects. + +We will content ourselves with implementing a dask version of the word-count problem, +specifically the step where we count words in a text. + +(word-count-problem)= + +```{eval-rst} +.. demo:: Demo: Dask version of word-count + + If you have not already cloned or downloaded ``word-count-hpda`` repository, + `get it from here `__. + Then, navigate to the ``word-count-hpda`` directory. The serial version (wrapped in + multiple functions in the ``source/wordcount.py`` code) looks like this: + + .. code-block:: python + + filename = './data/pg10.txt' + DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() + + with open(filename, "r") as input_fd: + lines = input_fd.read().splitlines() + + counts = {} + for line in lines: + for purge in DELIMITERS: + line = line.replace(purge, " ") + words = line.split() + for word in words: + word = word.lower().strip() + if word in counts: + counts[word] += 1 + else: + counts[word] = 1 + + sorted_counts = sorted( + list(counts.items()), + key=lambda key_value: key_value[1], + reverse=True + ) + + sorted_counts[:10] + + A very compact ``dask.bag`` version of this code is as follows: + + .. code-block:: python + + import dask.bag as db + filename = './data/pg10.txt' + DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() + + text = db.read_text(filename, blocksize='1MiB') + sorted_counts = ( + text + .filter(lambda word: word not in DELIMITERS) + .str.lower() + .str.strip() + .str.split() + .flatten() + .frequencies().topk(10,key=1) + .compute() + ) + + sorted_counts + + The last two steps of the pipeline could also have been done with a dataframe: + + .. code-block:: python + :emphasize-lines: 9-10 + + filtered = ( + text + .filter(lambda word: word not in DELIMITERS) + .str.lower() + .str.strip() + .str.split() + .flatten() + ) + ddf = filtered.to_dataframe(columns=['words']) + ddf['words'].value_counts().compute()[:10] +``` + +```{eval-rst} +.. callout:: When to use Dask + + There is no benefit from using Dask on small datasets. But imagine we were + analysing a very large text file (all tweets in a year? a genome?). Dask provides + both parallelisation and the ability to utilize RAM on multiple machines. +``` + +## Exercise set 1 + +Choose an exercise with the data structure that you are most interested in: +{ref}`ex-dask-array`, {ref}`ex-dask-df` or {ref}`ex-dask-bag`. + +(ex-dask-array)= + +### 1.1. using dask.array + +```{eval-rst} +.. challenge:: Chunk size + + The following example calculate the mean value of a random generated array. + Run the example and see the performance improvement by using dask. + + .. tabs:: + + .. tab:: NumPy + + .. literalinclude:: example/chunk_np.py + :language: python + + .. tab:: Dask + + .. literalinclude:: example/chunk_dask.py + :language: python + + + But what happens if we use different chunk sizes? + Try out with different chunk sizes: + + - What happens if the dask chunks=(20000,20000) + + - What happens if the dask chunks=(250,250) + + + .. solution:: Choice of chunk size + + The choice is problem dependent, but here are a few things to consider: + + Each chunk of data should be small enough so that it fits comforably in each worker's available memory. + Chunk sizes between 10MB-1GB are common, depending on the availability of RAM. Dask will likely + manipulate as many chunks in parallel on one machine as you have cores on that machine. + So if you have a machine with 10 cores and you choose chunks in the 1GB range, Dask is likely to use at least + 10 GB of memory. Additionally, there should be enough chunks available so that each worker always has something to work on. + + On the otherhand, you also want to avoid chunk sizes that are too small as we see in the exercise. + Every task comes with some overhead which is somewhere between 200us and 1ms. Very large graphs + with millions of tasks will lead to overhead being in the range from minutes to hours which is not recommended. + +``` + +(ex-dask-df)= + +### 1.2. using dask.dataframe + +```{eval-rst} +.. exercise:: Benchmarking DataFrame.apply() + + Recall the + :ref:`word count ` + project that we encountered earlier and the :func:`scipy.optimize.curve_fit` function. + The :download:`results.csv ` file contains word counts of the 10 + most frequent words in different texts, and we want to fit a power law to the + individual distributions in each row. + + Here are our fitting functions: + + .. code-block:: python + + from scipy.optimize import curve_fit + + def powerlaw(x, A, s): + return A * np.power(x, s) + + def fit_powerlaw(row): + X = np.arange(row.shape[0]) + 1.0 + params, cov = curve_fit(f=powerlaw, xdata=X, ydata=row, p0=[100, -1], bounds=(-np.inf, np.inf)) + return params[1] + + Compare the performance of + :meth:`dask.dataframe.DataFrame.apply` with + :meth:`pandas.DataFrame.apply` + for the this example. You will probably see a slowdown due to the parallelisation + overhead. But what if you add a ``time.sleep(0.01)`` inside :meth:`fit_powerlaw` to + emulate a time-consuming calculation? + + .. callout:: Hints + :class: dropdown + + - You will need to call :meth:`apply` on the dataframe starting from column 1: ``dataframe.iloc[:,1:].apply()`` + - Remember that both Pandas and Dask have the :meth:`read_csv` function. + - Try repartitioning the dataframe into 4 partitions with ``ddf4=ddf.repartition(npartitions=4)``. + - You will probably get a warning in your Dask version that `You did not provide metadata`. + To remove the warning, add the ``meta=(None, "float64")`` flag to :meth:`apply`. For the + current data, this does not affect the performance. + + .. callout:: More hints with Pandas code + :class: dropdown + + You need to reimplement the highlighted part which creates the + dataframe and applies the :func:`fit_powerlaw` function. + + .. literalinclude:: exercise/apply_pd.py + :language: ipython + :emphasize-lines: 16-17 + + + .. solution:: + + .. literalinclude:: exercise/apply_dask.py + :language: ipython + +``` + +(ex-dask-bag)= + +### 1.3. using dask.bag + +```{eval-rst} +.. exercise:: Break down the dask.bag computational pipeline + + Revisit the + :ref:`word count problem ` + and the implementation with a ``dask.bag`` that we saw above. + + - To get a feeling for the computational pipeline, break down the computation into + separate steps and investigate intermediate results using :meth:`.compute`. + - Benchmark the serial and ``dask.bag`` versions. Do you see any speedup? + What if you have a larger textfile? You can for example concatenate all texts into + a single file: ``cat data/*.txt > data/all.txt``. + +``` + +## Low level interface: delayed + +Sometimes problems don't fit into one of the collections like +`dask.array` or `dask.dataframe`, they are not as simple as just a big array or dataframe. +In these cases, `dask.delayed` may be the right choice. If the problem is paralellisable, +we can use `dask.delayed` which allows users to make function calls lazy +and thus can be put into a task graph with dependencies. + +Consider the following example. The functions are very simple, and they *sleep* +for a prescribed time to simulate real work: + +```{literalinclude} example/delay.py +``` + +Let us run the example first, one after the other in sequence: + +```ipython +%%timeit +x = inc(1) +y = dec(2) +z = add(x, y) +# 902 ms ± 367 µs per loop (mean ± std. dev. of 7 runs, 1 loop each) +``` + +Note that the first two functions `inc` and `dec` don't depend on each other, +we could have called them in parallel. We can call `dask.delayed` on these functions +to make them lazy and tasks into a graph which we will run later on parallel hardware. + +```ipython +import dask +inc_delay = dask.delayed(inc) +dec_delay = dask.delayed(dec) +add_delay = dask.delayed(add) +``` + +```ipython +%%timeit +x = inc_delay(1) +y = dec_delay(2) +z = add_delay(x, y) +# 59.6 µs ± 356 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each) +``` + +```ipython +%%timeit +x = inc_delay(1) +y = dec_delay(2) +z = add_delay(x, y) +z.compute() +# 603 ms ± 181 µs per loop (mean ± std. dev. of 7 runs, 1 loop each) +``` + +```{eval-rst} +.. callout:: Default scheduler for dask collections + + ``dask.array`` and ``dask.dataframe`` use the ``threads`` scheduler + + ``dask.bag`` uses the ``processes`` scheduler + + In case to change the default scheduler, using `dask.config.set` is recommended: + + .. code-block:: ipython + + # To set globally + dask.config.set(scheduler='processes') + x.compute() + + # To set it as a context manager + with dask.config.set(scheduler='threads'): + x.compute() + +``` + +## Comparison to Spark + +Dask has much in common with the [Apache Spark](https://spark.apache.org/). +Here are [some differences](https://docs.dask.org/en/stable/spark.html) +between the two frameworks: + +- Dask is smaller and more lightweight but is used together with other packages in + the Python ecosystem. Spark is an all-in-one project with its own ecosystem. +- Spark is written in Scala, with some support for Python and R, while Dask is in Python. +- Spark is more focused on business intelligence (SQL, lightweight machine learning) while + Dask is more general and is used more in scientific applications. +- Both Dask and Spark can scale from one to thousands of nodes. +- Dask supports the NumPy model for multidimensional arrays which Spark doesn't. +- Spark generally expects users to compose computations out of high-level primitives + (map, reduce, groupby, join, etc.), while Dask allows to specify arbitrary task + graphs for more complex and custom systems. + +## Exercise set 2 + +```{eval-rst} +.. challenge:: Dask delay + + We extend the previous example a little bit more by applying the function + on a data array using for loop and adding an *if* condition: + + .. literalinclude:: example/delay_more.py + + + Please add ``dask.delayed`` to parallelize the program as much as possible + and check graph visualizations. + + .. solution:: + + .. literalinclude:: example/delay_more_solution.py + +``` + +```{eval-rst} +.. challenge:: Climate simulation data using Xarray and Dask + + This exercise is working with NetCDF files using Xarray. The files contain + monthly global 2m air temperature for 10 years. + Xarray is chosen due to its ability to seamlessly integrate with Dask + to support parallel computations on datasets. + + + We will first read data with Dask and Xarray. See + https://xarray.pydata.org/en/stable/dask.html#reading-and-writing-data for more details. + + Note that the NetCDF files are here https://github.com/ENCCS/hpda-python/tree/main/content/data , + you need to ``git clone`` the repository or download the files to your laptop first. + Then depending on where you put the files, + you may need to adapt the path to the data folder in the Python code. + + .. code-block:: ipython + + import dask + import xarray as xr + import matplotlib.pyplot as plt + %matplotlib inline + ds=xr.open_mfdataset('./data/tas*.nc', parallel=True,use_cftime=True) + + + :func:`xarray.open_mfdataset` is for reading multiple files and will chunk each file into a single Dask array by default. + One could supply the chunks keyword argument to control the size of the resulting Dask arrays. + Passing the keyword argument ``parallel=True`` to :func:`xarray.open_mfdataset` will speed up the reading of + large multi-file datasets by executing those read tasks in parallel using ``dask.delayed``. + + Explore the following operations line-by-line: + + .. code-block:: ipython + + ds + ds.tas + #dsnew = ds.chunk({"time": 1,"lat": 80,"lon":80}) # you can further rechunk the data + #dask.visualize(ds.tas) # do not visualize, the graph is too big + ds['tas'] = ds['tas'] - 273.15 # convert from Kelvin to degree Celsius + mean_tas=ds.tas.mean("time") # lazy compuation + mean_tas.plot(cmap=plt.cm.RdBu_r,vmin=-50,vmax=50) # plotting triggers computation + tas_ann=ds.tas.groupby('time.year').mean() # lazy compuation + tas_sto=tas_ann.sel(lon=18.07, lat=59.33,method='nearest') # slicing is lazy as well + plt.plot(tas_sto.year,tas_sto) # plotting trigers computation + +``` + +```{eval-rst} +.. keypoints:: + + - Dask uses lazy execution + - Dask can parallelize and perform out-of-memory computation. + That is, handle data that would not fit in the memory if loaded at once. + - Only use Dask for processing very large amount of data +``` diff --git a/content/dask_opt.md b/content/dask_opt.md new file mode 100644 index 0000000..c5ccf71 --- /dev/null +++ b/content/dask_opt.md @@ -0,0 +1,165 @@ +# Dask (II) + +```{eval-rst} +.. challenge:: Testing different schedulers + + We will test different schedulers and compare the performance on a simple task calculating + the mean of a random generated array. + + Here is the code using NumPy: + + .. literalinclude:: example/dask_gil.py + :language: ipython + :lines: 1-7 + + Here we run the same code using different schedulers from Dask: + + .. tabs:: + + .. tab:: ``serial`` + + .. literalinclude:: example/dask_gil.py + :language: ipython + :lines: 9-12 + + .. tab:: ``threads`` + + .. literalinclude:: example/dask_gil_threads.py + :language: ipython + :lines: 1-10 + + .. literalinclude:: example/dask_gil_threads.py + :language: ipython + :lines: 12-15 + + .. literalinclude:: example/dask_gil_threads.py + :language: ipython + :lines: 17-20 + + .. literalinclude:: example/dask_gil_threads.py + :language: ipython + :lines: 22-25 + + .. tab:: ``processes`` + + .. literalinclude:: example/dask_gil_processes.py + :language: ipython + :lines: 1-10 + + .. literalinclude:: example/dask_gil_processes.py + :language: ipython + :lines: 12-15 + + .. literalinclude:: example/dask_gil_processes.py + :language: ipython + :lines: 17-20 + + .. literalinclude:: example/dask_gil_processes.py + :language: ipython + :lines: 22-25 + + .. tab:: ``distributed`` + + .. literalinclude:: example/dask_gil_distributed.py + :language: ipython + :lines: 1-14 + + .. literalinclude:: example/dask_gil_distributed.py + :language: ipython + :lines: 16-17 + + .. literalinclude:: example/dask_gil_distributed.py + :language: ipython + :lines: 19-21 + + .. literalinclude:: example/dask_gil_distributed.py + :language: ipython + :lines: 23-25 + + .. literalinclude:: example/dask_gil_distributed.py + :language: ipython + :lines: 27 + + + + .. solution:: Testing different schedulers + + Comparing profiling from mt_1, mt_2 and mt_4: Using ``threads`` scheduler is limited by the GIL on pure Python code. + In our case, although it is not a pure Python function, it is still limited by GIL, therefore no multi-core speedup + + Comparing profiling from mt_1, mp_1 and dis_1: Except for ``threads``, the other two schedulers copy data between processes + and this can introduce performance penalties, particularly when the data being transferred between processes is large. + + Comparing profiling from serial, mt_1, mp_1 and dis_1: Creating and destroying threads and processes have overheads, + ``processes`` have even more overhead than ``threads`` + + Comparing profiling from mp_1, mp_2 and mp_4: Running multiple processes is only effective when there is enough computational + work to do i.e. CPU-bound tasks. In this very example, most of the time is actually spent on transferring the data + rather than computing the mean + + Comparing profiling from ``processes`` and ``distributed``: Using ``distributed`` scheduler has advantages over ``processes``, + this is related to better handling of data copying, i.e. ``processes`` scheduler copies data for every task, while + ``distributed`` scheduler copies data for each worker. + + +``` + +```{eval-rst} +.. challenge:: SVD with large skinny matrix using ``distributed`` scheduler + + We can use dask to compute SVD of a large matrix which does not fit into the memory of a + normal laptop/desktop. While it is computing, you should switch to the Dask dashboard and + watch column "Workers" and "Graph", so you must run this using ``distributed`` scheduler + + .. code-block:: python + + import dask + import dask.array as da + X = da.random.random((2000000, 100), chunks=(10000, 100)) + X + u, s, v = da.linalg.svd(X) + dask.visualize(u, s, v) + s.compute() + + + SVD is only supported for arrays with chunking in one dimension, which requires that the matrix + is either *tall-and-skinny* or *short-and-fat*. + If chunking in both dimensions is needed, one should use approximate algorithm. + + .. code-block:: python + + import dask + import dask.array as da + X = da.random.random((10000, 10000), chunks=(2000, 2000)) + u, s, v = da.linalg.svd_compressed(X, k=5) + dask.visualize(u, s, v) + s.compute() + +``` + +```{eval-rst} +.. callout:: Memory management + + You may observe that there are different memory categories showing on the dashboard: + + - process: Overall memory used by the worker process, as measured by the OS + - managed: Size of data that Dask holds in RAM, but most probably inaccurate, excluding spilled data. + - unmanaged: Memory that Dask is not directly aware of, this can be e.g. Python modules, + temporary arrays, memory leasks, memory not yet free()'d by the Python memory manager to the OS + - unmanaged recent: Unmanaged memory that has appeared within the last 30 seconds whch is not included + in the "unmanaged" memory measure + - spilled: Memory spilled to disk + + The sum of managed + unmanaged + unmanaged recent is equal by definition to the process memory. + + When the managed memory exceeds 60% of the memory limit (target threshold), + the worker will begin to dump the least recently used data to disk. + Above 70% of the target memory usage based on process memory measurment (spill threshold), + the worker will start dumping unused data to disk. + + At 80% process memory load, currently executing tasks continue to run, but no additional tasks + in the worker's queue will be started. + + At 95% process memory load (terminate threshold), all workers will be terminated. Tasks will be cancelled + as well and data on the worker will be lost and need to be recomputed. +``` diff --git a/content/img/dag.png b/content/img/dag.png new file mode 100644 index 0000000000000000000000000000000000000000..ae13ef6b887f593d6bcdb534fa867903554128f4 GIT binary patch literal 16170 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 literal 0 HcmV?d00001 diff --git a/content/img/dask-overview.svg b/content/img/dask-overview.svg new file mode 100644 index 0000000..c88c73e --- /dev/null +++ b/content/img/dask-overview.svg @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From abcd80c338270241fec65582a042bd306ada5b90 Mon Sep 17 00:00:00 2001 From: ffrancesco94 Date: Tue, 16 Dec 2025 15:07:30 +0100 Subject: [PATCH 2/4] Added dask optional material --- content/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/content/index.md b/content/index.md index 7e6dd4e..31f7320 100644 --- a/content/index.md +++ b/content/index.md @@ -28,6 +28,13 @@ dask ``` +```{toctree} +:caption: Optional material +:maxdepth: 1 + +dask_opt +``` + ```{toctree} :caption: Reference :maxdepth: 1 From 703a18f0e68961c9ba1f6ad144a96c241fe829dd Mon Sep 17 00:00:00 2001 From: ffrancesco94 Date: Tue, 16 Dec 2025 17:20:13 +0100 Subject: [PATCH 3/4] Mystified Dask episode --- content/dask.md | 565 ++++++++++++------------- content/example/chunk_dask.py | 7 + content/example/chunk_np.py | 5 + content/example/delay_more.py | 28 ++ content/example/delay_more_solution.py | 28 ++ content/exercise/apply_dask.py | 26 ++ content/exercise/apply_pd.py | 17 + content/img/jlab-dask-1.png | Bin 0 -> 29428 bytes content/img/jlab-dask-2.png | Bin 0 -> 43496 bytes content/img/jlab-dask-3.png | Bin 0 -> 82224 bytes 10 files changed, 390 insertions(+), 286 deletions(-) create mode 100644 content/example/chunk_dask.py create mode 100644 content/example/chunk_np.py create mode 100644 content/example/delay_more.py create mode 100644 content/example/delay_more_solution.py create mode 100644 content/exercise/apply_dask.py create mode 100644 content/exercise/apply_pd.py create mode 100644 content/img/jlab-dask-1.png create mode 100644 content/img/jlab-dask-2.png create mode 100644 content/img/jlab-dask-3.png diff --git a/content/dask.md b/content/dask.md index b126e9c..da70763 100644 --- a/content/dask.md +++ b/content/dask.md @@ -2,21 +2,18 @@ # Dask for Scalable Analytics -```{eval-rst} -.. objectives:: +:::{objectives} - - Understand how Dask achieves parallelism - - Learn a few common workflows with Dask - - Understand lazy execution -``` +- Understand how Dask achieves parallelism +- Learn a few common workflows with Dask +- Understand lazy execution -```{eval-rst} -.. instructor-note:: +:::{instructor-note} - - 40 min teaching/type-along - - 40 min exercises +- 40 min teaching/type-along +- 40 min exercises -``` +::: ## Overview @@ -66,10 +63,9 @@ CPU and RAM and the Dask scheduling system automatically maps jobs to each worke Dask provides four different schedulers: -```{eval-rst} -.. csv-table:: - :widths: auto - :delim: ; +```{csv-table} +:widths: auto +:delim: ; Type ; Multi-node ; Description ``threads`` ; No ; A single-machine scheduler backed by a thread pool @@ -83,77 +79,71 @@ Here we will focus on using a `LocalCluster`, and it is recommended to use a distributed scheduler `dask.distributed`. It is more sophisticated, offers more features, but requires minimum effort to set up. It can run locally on a laptop and scale up to a cluster. -```{eval-rst} -.. callout:: Alternative 1: Initializing a Dask ``LocalCluster`` via JupyterLab - :class: dropdown - - This makes use of the ``dask-labextension`` which is pre-installed in our conda environment. - - #. Start New Dask Cluster from the sidebar and by clicking on ``+ NEW`` button. - #. Click on the ``< >`` button to inject the client code into a notebook cell. Execute it. +:::{callout} Alternative 1: Initializing a Dask ``LocalCluster`` via JupyterLab +:class: dropdown +This makes use of the ``dask-labextension`` which is pre-installed in our conda environment. - |dask-1| |dask-2| +1. Start New Dask Cluster from the sidebar and by clicking on ``+ NEW`` button. +2. Click on the ``< >`` button to inject the client code into a notebook cell. Execute it. - 3. You can scale the cluster for more resources or launch the dashboard. - - |dask-3| - - .. |dask-1| image:: ./img/jlab-dask-1.png - :width: 49% - - .. |dask-2| image:: ./img/jlab-dask-2.png - :width: 49% - - .. |dask-3| image:: ./img/jlab-dask-3.png - :width: 100% -``` + ![](img/jlab-dask-1.png) ![](img/jlab-dask-2.png) -**Alternative 2**: We can also start a `LocalCluster` scheduler manually, which makes use of: - -```{eval-rst} -.. tabs:: - - .. tab:: all resources +3. You can scale the cluster for more resources or launch the dashboard. +![](img/jlab-dask-3.png) +::: - all the cores and RAM we have on the machine by: +:::::{callout} Alternative 2: Manual `LocalCluster` - .. code-block:: python +We can also start a `LocalCluster` scheduler manually, which can use all +available resources or just a subset. - from dask.distributed import Client, LocalCluster - # create a local cluster - cluster = LocalCluster() - # connect to the cluster we just created - client = Client(cluster) - client +::::{tabs} +:::{group-tab} All resources - Or you can simply lauch a Client() call which is shorthand for what is described above. +We can use all the cores and RAM we have on the machine by: - .. code-block:: python +```python +from dask.distributed import Client, LocalCluster +# create a local cluster +cluster = LocalCluster() +# connect to the cluster we just created +client = Client(cluster) +client +``` - from dask.distributed import Client - client = Client() # same as Client(processes=True) - client +Or you can simply launch a `Client()` call which is shorthand for what is +described above. - .. tab:: specified resources +```python +from dask.distributed import Client +client = Client() # same as Client(processes=True) +client +``` - which limits the compute resources available as follows: +::: - .. code-block:: python +:::{group-tab} Specified resources - from dask.distributed import Client, LocalCluster +This option limits the compute resources available as follows: - cluster = LocalCluster( - n_workers=4, - threads_per_worker=1, - memory_limit='4GiB' # memory limit per worker - ) - client = Client(cluster) - client +```python +from dask.distributed import Client, LocalCluster +cluster = LocalCluster( + n_workers=4, + threads_per_worker=1, + memory_limit='4GiB' # memory limit per worker +) +client = Client(cluster) +client ``` +::: +:::: +::::: + :::{note} When setting up the cluster, one should consider the balance between the number of workers and threads per worker with different workloads by setting the parameter `processes`. @@ -207,8 +197,7 @@ However, a Dask array uses the so-called "lazy" execution mode, which allows one to build up complex, large calculations symbolically before turning them over the scheduler for execution. -```{eval-rst} -.. callout:: Lazy evaluation +:::{callout} Lazy evaluation Contrary to normal computation, lazy execution mode is when all the computations needed to generate results are symbolically represented, forming a queue of @@ -219,7 +208,7 @@ before turning them over the scheduler for execution. or thread pool, which allows Dask to take full advantage of multiple processors available on the computers. -``` +::: ```python import numpy as np @@ -251,8 +240,9 @@ ones ``` :::{note} -In this course, we will use a chunk shape, but other ways to specify `chunks` size can be found here - +In this course, we will use a chunk shape, but other ways to specify `chunks` +size can be found +[here](). ::: Let us further calculate the sum of the dask array: @@ -278,8 +268,7 @@ dask.compute(sum_da) sum_da.compute() ``` -You can find additional details and examples here -. +You can find additional details and examples [here](https://examples.dask.org/array.html). ### Dask dataframe @@ -309,7 +298,7 @@ ddf = dd.from_pandas(df, npartitions=10) Alternatively you can directly read into a Dask dataframe, whilst also modifying how the dataframe is partitioned in terms of `blocksize`: -``` +```python # blocksize=None which means a single chunk is used df = dd.read_csv(url,blocksize=None).set_index('Name') ddf= df.repartition(npartitions=10) @@ -354,88 +343,92 @@ specifically the step where we count words in a text. (word-count-problem)= -```{eval-rst} -.. demo:: Demo: Dask version of word-count +:::{demo} Demo: Dask version of word-count - If you have not already cloned or downloaded ``word-count-hpda`` repository, - `get it from here `__. - Then, navigate to the ``word-count-hpda`` directory. The serial version (wrapped in - multiple functions in the ``source/wordcount.py`` code) looks like this: +If you have not already cloned or downloaded ``word-count-hpda`` repository, +[get it from here](https://github.com/ENCCS/word-count-hpda). +Then, navigate to the ``word-count-hpda`` directory. The serial version (wrapped in +multiple functions in the ``source/wordcount.py`` code) looks like this: + +```python - .. code-block:: python +filename = './data/pg10.txt' +DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() - filename = './data/pg10.txt' - DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() +with open(filename, "r") as input_fd: + lines = input_fd.read().splitlines() - with open(filename, "r") as input_fd: - lines = input_fd.read().splitlines() +counts = {} +for line in lines: + for purge in DELIMITERS: + line = line.replace(purge, " ") + words = line.split() + for word in words: + word = word.lower().strip() + if word in counts: + counts[word] += 1 + else: + counts[word] = 1 - counts = {} - for line in lines: - for purge in DELIMITERS: - line = line.replace(purge, " ") - words = line.split() - for word in words: - word = word.lower().strip() - if word in counts: - counts[word] += 1 - else: - counts[word] = 1 +sorted_counts = sorted( + list(counts.items()), + key=lambda key_value: key_value[1], + reverse=True +) - sorted_counts = sorted( - list(counts.items()), - key=lambda key_value: key_value[1], - reverse=True - ) +sorted_counts[:10] - sorted_counts[:10] + ``` A very compact ``dask.bag`` version of this code is as follows: - .. code-block:: python +```python - import dask.bag as db - filename = './data/pg10.txt' - DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() +import dask.bag as db +filename = './data/pg10.txt' +DELIMITERS = ". , ; : ? $ @ ^ < > # % ` ! * - = ( ) [ ] { } / \" '".split() - text = db.read_text(filename, blocksize='1MiB') - sorted_counts = ( - text - .filter(lambda word: word not in DELIMITERS) - .str.lower() - .str.strip() - .str.split() - .flatten() - .frequencies().topk(10,key=1) - .compute() - ) +text = db.read_text(filename, blocksize='1MiB') +sorted_counts = ( + text + .filter(lambda word: word not in DELIMITERS) + .str.lower() + .str.strip() + .str.split() + .flatten() + .frequencies().topk(10,key=1) + .compute() +) - sorted_counts +sorted_counts +``` - The last two steps of the pipeline could also have been done with a dataframe: +The last two steps of the pipeline could also have been done with a dataframe: - .. code-block:: python - :emphasize-lines: 9-10 +```python + :emphasize-lines: 9-10 - filtered = ( - text - .filter(lambda word: word not in DELIMITERS) - .str.lower() - .str.strip() - .str.split() - .flatten() - ) - ddf = filtered.to_dataframe(columns=['words']) - ddf['words'].value_counts().compute()[:10] + filtered = ( + text + .filter(lambda word: word not in DELIMITERS) + .str.lower() + .str.strip() + .str.split() + .flatten() + ) + ddf = filtered.to_dataframe(columns=['words']) + ddf['words'].value_counts().compute()[:10] ``` -```{eval-rst} -.. callout:: When to use Dask +::: + +:::{callout} When to use Dask There is no benefit from using Dask on small datasets. But imagine we were analysing a very large text file (all tweets in a year? a genome?). Dask provides both parallelisation and the ability to utilize RAM on multiple machines. -``` + +::: ## Exercise set 1 @@ -444,132 +437,136 @@ Choose an exercise with the data structure that you are most interested in: (ex-dask-array)= -### 1.1. using dask.array - -```{eval-rst} -.. challenge:: Chunk size +### 1.1. Using dask.array - The following example calculate the mean value of a random generated array. - Run the example and see the performance improvement by using dask. +:::::{challenge} Chunk size - .. tabs:: +The following example calculate the mean value of a random generated array. +Run the example and see the performance improvement by using dask. - .. tab:: NumPy +::::{tabs} - .. literalinclude:: example/chunk_np.py - :language: python +:::{group-tab} NumPy - .. tab:: Dask +```{literalinclude} example/chunk_np.py +:language: python +``` - .. literalinclude:: example/chunk_dask.py - :language: python +::: +:::{group-tab} Dask +```{literalinclude} example/chunk_dask.py +:language: python +``` - But what happens if we use different chunk sizes? - Try out with different chunk sizes: +::: +:::: - - What happens if the dask chunks=(20000,20000) +But what happens if we use different chunk sizes? +Try out with different chunk sizes: - - What happens if the dask chunks=(250,250) +- What happens if the dask chunks=(20000,20000) +- What happens if the dask chunks=(250,250) - .. solution:: Choice of chunk size +:::{solution} Choice of chunk size - The choice is problem dependent, but here are a few things to consider: +The choice is problem dependent, but here are a few things to consider: - Each chunk of data should be small enough so that it fits comforably in each worker's available memory. - Chunk sizes between 10MB-1GB are common, depending on the availability of RAM. Dask will likely - manipulate as many chunks in parallel on one machine as you have cores on that machine. - So if you have a machine with 10 cores and you choose chunks in the 1GB range, Dask is likely to use at least - 10 GB of memory. Additionally, there should be enough chunks available so that each worker always has something to work on. +Each chunk of data should be small enough so that it fits comforably in each worker's available memory. +Chunk sizes between 10MB-1GB are common, depending on the availability of RAM. Dask will likely +manipulate as many chunks in parallel on one machine as you have cores on that machine. +So if you have a machine with 10 cores and you choose chunks in the 1GB range, Dask is likely to use at least +10 GB of memory. Additionally, there should be enough chunks available so that each worker always has something to work on. - On the otherhand, you also want to avoid chunk sizes that are too small as we see in the exercise. - Every task comes with some overhead which is somewhere between 200us and 1ms. Very large graphs - with millions of tasks will lead to overhead being in the range from minutes to hours which is not recommended. +On the otherhand, you also want to avoid chunk sizes that are too small as we see in the exercise. +Every task comes with some overhead which is somewhere between 200us and 1ms. Very large graphs +with millions of tasks will lead to overhead being in the range from minutes to hours which is not recommended. +::: -``` +::::: (ex-dask-df)= -### 1.2. using dask.dataframe - -```{eval-rst} -.. exercise:: Benchmarking DataFrame.apply() +### 1.2. Using dask.dataframe - Recall the - :ref:`word count ` - project that we encountered earlier and the :func:`scipy.optimize.curve_fit` function. - The :download:`results.csv ` file contains word counts of the 10 - most frequent words in different texts, and we want to fit a power law to the - individual distributions in each row. +::::{exercise} Benchmarking DataFrame.apply() - Here are our fitting functions: +Recall the :ref:`word count ` project that we encountered +earlier and the :func:`scipy.optimize.curve_fit` function. The +:download:`results.csv ` file contains word counts of the 10 +most frequent words in different texts, and we want to fit a power law to the +individual distributions in each row. - .. code-block:: python +Here are our fitting functions: - from scipy.optimize import curve_fit - - def powerlaw(x, A, s): - return A * np.power(x, s) +```python +from scipy.optimize import curve_fit - def fit_powerlaw(row): - X = np.arange(row.shape[0]) + 1.0 - params, cov = curve_fit(f=powerlaw, xdata=X, ydata=row, p0=[100, -1], bounds=(-np.inf, np.inf)) - return params[1] +def powerlaw(x, A, s): + return A * np.power(x, s) - Compare the performance of - :meth:`dask.dataframe.DataFrame.apply` with - :meth:`pandas.DataFrame.apply` - for the this example. You will probably see a slowdown due to the parallelisation - overhead. But what if you add a ``time.sleep(0.01)`` inside :meth:`fit_powerlaw` to - emulate a time-consuming calculation? +def fit_powerlaw(row): + X = np.arange(row.shape[0]) + 1.0 + params, cov = curve_fit(f=powerlaw, xdata=X, ydata=row, p0=[100, -1], bounds=(-np.inf, np.inf)) + return params[1] +``` - .. callout:: Hints - :class: dropdown +Compare the performance of :meth:`dask.dataframe.DataFrame.apply` with +:meth:`pandas.DataFrame.apply` for the this example. You will probably see a +slowdown due to the parallelisation overhead. But what if you add a +``time.sleep(0.01)`` inside :meth:`fit_powerlaw` to emulate a time-consuming +calculation? - - You will need to call :meth:`apply` on the dataframe starting from column 1: ``dataframe.iloc[:,1:].apply()`` - - Remember that both Pandas and Dask have the :meth:`read_csv` function. - - Try repartitioning the dataframe into 4 partitions with ``ddf4=ddf.repartition(npartitions=4)``. - - You will probably get a warning in your Dask version that `You did not provide metadata`. - To remove the warning, add the ``meta=(None, "float64")`` flag to :meth:`apply`. For the - current data, this does not affect the performance. +:::{callout} Hints +:class: dropdown - .. callout:: More hints with Pandas code - :class: dropdown +- You will need to call :meth:`apply` on the dataframe starting from column 1: ``dataframe.iloc[:,1:].apply()`` +- Remember that both Pandas and Dask have the :meth:`read_csv` function. +- Try repartitioning the dataframe into 4 partitions with ``ddf4=ddf.repartition(npartitions=4)``. +- You will probably get a warning in your Dask version that `You did not provide metadata`. + To remove the warning, add the ``meta=(None, "float64")`` flag to :meth:`apply`. For the + current data, this does not affect the performance. +::: - You need to reimplement the highlighted part which creates the - dataframe and applies the :func:`fit_powerlaw` function. +:::{callout} More hints with Pandas code +:class: dropdown - .. literalinclude:: exercise/apply_pd.py - :language: ipython - :emphasize-lines: 16-17 +You need to reimplement the highlighted part which creates the +dataframe and applies the :func:`fit_powerlaw` function. +```{literalinclude} exercise/apply_pd.py +:language: ipython +:emphasize-lines: 16-17 +``` - .. solution:: +::: - .. literalinclude:: exercise/apply_dask.py - :language: ipython +:::{solution} +```{literalinclude} exercise/apply_dask.py +:language: ipython ``` -(ex-dask-bag)= +::: +:::: -### 1.3. using dask.bag +(ex-dask-bag)= -```{eval-rst} -.. exercise:: Break down the dask.bag computational pipeline +### 1.3. Using dask.bag - Revisit the - :ref:`word count problem ` - and the implementation with a ``dask.bag`` that we saw above. +:::{exercise} Break down the dask.bag computational pipeline - - To get a feeling for the computational pipeline, break down the computation into - separate steps and investigate intermediate results using :meth:`.compute`. - - Benchmark the serial and ``dask.bag`` versions. Do you see any speedup? - What if you have a larger textfile? You can for example concatenate all texts into - a single file: ``cat data/*.txt > data/all.txt``. +Revisit the +:ref:`word count problem ` +and the implementation with a ``dask.bag`` that we saw above. -``` +- To get a feeling for the computational pipeline, break down the computation into + separate steps and investigate intermediate results using :meth:`.compute`. +- Benchmark the serial and ``dask.bag`` versions. Do you see any speedup? + What if you have a larger textfile? You can for example concatenate all texts into + a single file: ``cat data/*.txt > data/all.txt``. +::: ## Low level interface: delayed @@ -623,27 +620,26 @@ z.compute() # 603 ms ± 181 µs per loop (mean ± std. dev. of 7 runs, 1 loop each) ``` -```{eval-rst} -.. callout:: Default scheduler for dask collections +:::{callout} Default scheduler for dask collections - ``dask.array`` and ``dask.dataframe`` use the ``threads`` scheduler +``dask.array`` and ``dask.dataframe`` use the ``threads`` scheduler - ``dask.bag`` uses the ``processes`` scheduler +``dask.bag`` uses the ``processes`` scheduler - In case to change the default scheduler, using `dask.config.set` is recommended: +In case to change the default scheduler, using `dask.config.set` is recommended: - .. code-block:: ipython - - # To set globally - dask.config.set(scheduler='processes') - x.compute() - - # To set it as a context manager - with dask.config.set(scheduler='threads'): - x.compute() +```ipython +# To set globally +dask.config.set(scheduler='processes') +x.compute() +# To set it as a context manager +with dask.config.set(scheduler='threads'): + x.compute() ``` +::: + ## Comparison to Spark Dask has much in common with the [Apache Spark](https://spark.apache.org/). @@ -663,77 +659,74 @@ between the two frameworks: ## Exercise set 2 -```{eval-rst} -.. challenge:: Dask delay - - We extend the previous example a little bit more by applying the function - on a data array using for loop and adding an *if* condition: - - .. literalinclude:: example/delay_more.py - - - Please add ``dask.delayed`` to parallelize the program as much as possible - and check graph visualizations. +::::{challenge} Dask delay - .. solution:: - - .. literalinclude:: example/delay_more_solution.py +We extend the previous example a little bit more by applying the function +on a data array using for loop and adding an *if* condition: +```{literalinclude} example/delay_more.py ``` -```{eval-rst} -.. challenge:: Climate simulation data using Xarray and Dask - - This exercise is working with NetCDF files using Xarray. The files contain - monthly global 2m air temperature for 10 years. - Xarray is chosen due to its ability to seamlessly integrate with Dask - to support parallel computations on datasets. +Please add ``dask.delayed`` to parallelize the program as much as possible +and check graph visualizations. +:::{solution} - We will first read data with Dask and Xarray. See - https://xarray.pydata.org/en/stable/dask.html#reading-and-writing-data for more details. +```{literalinclude} example/delay_more_solution.py +``` - Note that the NetCDF files are here https://github.com/ENCCS/hpda-python/tree/main/content/data , - you need to ``git clone`` the repository or download the files to your laptop first. - Then depending on where you put the files, - you may need to adapt the path to the data folder in the Python code. +::: +:::: - .. code-block:: ipython +::::{challenge} Climate simulation data using Xarray and Dask - import dask - import xarray as xr - import matplotlib.pyplot as plt - %matplotlib inline - ds=xr.open_mfdataset('./data/tas*.nc', parallel=True,use_cftime=True) +This exercise is working with NetCDF files using Xarray. The files contain +monthly global 2m air temperature for 10 years. +Xarray is chosen due to its ability to seamlessly integrate with Dask +to support parallel computations on datasets. +We will first read data with Dask and Xarray. See + for more details. - :func:`xarray.open_mfdataset` is for reading multiple files and will chunk each file into a single Dask array by default. - One could supply the chunks keyword argument to control the size of the resulting Dask arrays. - Passing the keyword argument ``parallel=True`` to :func:`xarray.open_mfdataset` will speed up the reading of - large multi-file datasets by executing those read tasks in parallel using ``dask.delayed``. +Note that the NetCDF files are here , +you need to ``git clone`` the repository or download the files to your laptop first. +Then depending on where you put the files, +you may need to adapt the path to the data folder in the Python code. - Explore the following operations line-by-line: +```ipython +import dask +import xarray as xr +import matplotlib.pyplot as plt +%matplotlib inline +ds=xr.open_mfdataset('./data/tas*.nc', parallel=True,use_cftime=True) +``` - .. code-block:: ipython +:func:`xarray.open_mfdataset` is for reading multiple files and will chunk each +file into a single Dask array by default. One could supply the chunks keyword +argument to control the size of the resulting Dask arrays. Passing the keyword +argument ``parallel=True`` to :func:`xarray.open_mfdataset` will speed up the +reading of large multi-file datasets by executing those read tasks in parallel +using ``dask.delayed``. - ds - ds.tas - #dsnew = ds.chunk({"time": 1,"lat": 80,"lon":80}) # you can further rechunk the data - #dask.visualize(ds.tas) # do not visualize, the graph is too big - ds['tas'] = ds['tas'] - 273.15 # convert from Kelvin to degree Celsius - mean_tas=ds.tas.mean("time") # lazy compuation - mean_tas.plot(cmap=plt.cm.RdBu_r,vmin=-50,vmax=50) # plotting triggers computation - tas_ann=ds.tas.groupby('time.year').mean() # lazy compuation - tas_sto=tas_ann.sel(lon=18.07, lat=59.33,method='nearest') # slicing is lazy as well - plt.plot(tas_sto.year,tas_sto) # plotting trigers computation +Explore the following operations line-by-line: +```ipython +ds +ds.tas +#dsnew = ds.chunk({"time": 1,"lat": 80,"lon":80}) # you can further rechunk the data +#dask.visualize(ds.tas) # do not visualize, the graph is too big +ds['tas'] = ds['tas'] - 273.15 # convert from Kelvin to degree Celsius +mean_tas=ds.tas.mean("time") # lazy compuation +mean_tas.plot(cmap=plt.cm.RdBu_r,vmin=-50,vmax=50) # plotting triggers computation +tas_ann=ds.tas.groupby('time.year').mean() # lazy compuation +tas_sto=tas_ann.sel(lon=18.07, lat=59.33,method='nearest') # slicing is lazy as well +plt.plot(tas_sto.year,tas_sto) # plotting trigers computation ``` -```{eval-rst} -.. keypoints:: +:::: +```{keypoints} - Dask uses lazy execution - Dask can parallelize and perform out-of-memory computation. That is, handle data that would not fit in the memory if loaded at once. - Only use Dask for processing very large amount of data -``` diff --git a/content/example/chunk_dask.py b/content/example/chunk_dask.py new file mode 100644 index 0000000..3420f97 --- /dev/null +++ b/content/example/chunk_dask.py @@ -0,0 +1,7 @@ +import dask +import dask.array as da + +%%time +x = da.random.random((20000, 20000), chunks=(1000, 1000)) +y = x.mean(axis=0) +y.compute() diff --git a/content/example/chunk_np.py b/content/example/chunk_np.py new file mode 100644 index 0000000..57eaabb --- /dev/null +++ b/content/example/chunk_np.py @@ -0,0 +1,5 @@ +import numpy as np + +%%time +x = np.random.random((20000, 20000)) +y = x.mean(axis=0) diff --git a/content/example/delay_more.py b/content/example/delay_more.py new file mode 100644 index 0000000..2b59c0e --- /dev/null +++ b/content/example/delay_more.py @@ -0,0 +1,28 @@ +import time +import dask + +def inc(x): + time.sleep(0.5) + return x + 1 + +def dec(x): + time.sleep(0.3) + return x - 1 + +def add(x, y): + time.sleep(0.1) + return x + y + + +data = [1, 2, 3, 4, 5] +output = [] +for x in data: + if x % 2: + a = inc(x) + b = dec(x) + c = add(a, b) + else: + c = 10 + output.append(c) + +total = sum(output) diff --git a/content/example/delay_more_solution.py b/content/example/delay_more_solution.py new file mode 100644 index 0000000..6cd1007 --- /dev/null +++ b/content/example/delay_more_solution.py @@ -0,0 +1,28 @@ +import time +import dask + +def inc(x): + time.sleep(0.5) + return x + 1 + +def dec(x): + time.sleep(0.3) + return x - 1 + +def add(x, y): + time.sleep(0.1) + return x + y + + +data = [1, 2, 3, 4, 5] +output = [] +for x in data: + if x % 2: + a = dask.delayed(inc)(x) + b = dask.delayed(dec)(x) + c = dask.delayed(add)(a, b) + else: + c = dask.delayed(10) + output.append(c) + +total = dask.delayed(sum)(output) diff --git a/content/exercise/apply_dask.py b/content/exercise/apply_dask.py new file mode 100644 index 0000000..972e9ca --- /dev/null +++ b/content/exercise/apply_dask.py @@ -0,0 +1,26 @@ +import numpy as np +import dask.dataframe as dd +from scipy.optimize import curve_fit +import time + +def powerlaw(x, A, s): + return A * np.power(x, s) + +def fit_powerlaw(row): + X = np.arange(row.shape[0]) + 1.0 + params, cov = curve_fit(f=powerlaw, xdata=X, ydata=row, p0=[100, -1], bounds=(-np.inf, np.inf)) + time.sleep(0.01) + return params[1] + +ddf = dd.read_csv("https://raw.githubusercontent.com/ENCCS/hpda-python/main/content/data/results.csv") +ddf4=ddf.repartition(npartitions=4) + +# Note the optional argument ``meta`` which is recommended for dask dataframes. +# It should contain an empty ``pandas.DataFrame`` or ``pandas.Series`` +# that matches the dtypes and column names of the output, +# or a dict of ``{name: dtype}`` or iterable of ``(name, dtype)``. + +results = ddf4.iloc[:,1:].apply(fit_powerlaw, axis=1, meta=(None, "float64")) +%timeit results.compute() +results.visualize() + diff --git a/content/exercise/apply_pd.py b/content/exercise/apply_pd.py new file mode 100644 index 0000000..ae68109 --- /dev/null +++ b/content/exercise/apply_pd.py @@ -0,0 +1,17 @@ +import numpy as np +import pandas as pd +from scipy.optimize import curve_fit +import time + +def powerlaw(x, A, s): + return A * np.power(x, s) + +def fit_powerlaw(row): + X = np.arange(row.shape[0]) + 1.0 + params, cov = curve_fit(f=powerlaw, xdata=X, ydata=row, p0=[100, -1], bounds=(-np.inf, np.inf)) + time.sleep(0.01) + return params[1] + + +df = pd.read_csv("https://raw.githubusercontent.com/ENCCS/hpda-python/main/content/data/results.csv") +%timeit results = df.iloc[:,1:].apply(fit_powerlaw, axis=1) diff --git a/content/img/jlab-dask-1.png b/content/img/jlab-dask-1.png new file mode 100644 index 0000000000000000000000000000000000000000..38335948ce24e9ad93b928e0848028c576c8e641 GIT binary patch literal 29428 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 literal 0 HcmV?d00001 diff --git a/content/img/jlab-dask-2.png b/content/img/jlab-dask-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c30d2ba3ddb553991f6ec93a094489016b986cd3 GIT binary patch literal 43496 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 literal 0 HcmV?d00001 diff --git a/content/img/jlab-dask-3.png b/content/img/jlab-dask-3.png new file mode 100644 index 0000000000000000000000000000000000000000..60af478b194a47bf3ebd1b3972e24d742263130d GIT binary patch literal 82224 zcmZ_01yCK))~yR6K!D)xE**We!9-QC^YB^!5lcY96Fx%d6=)xA_v zP`!J1uia~{Imh_sgkV`|QFvG!STHa!c(ETs@?c;eF2TUS%RWH?N9;?Yu7Dpf)<0D3 zz`ziC-@n1*Xb^DU&$bsEAc#mvd~-x2KTMXYGGFeFnX8NZ=Q7cJ^6=XokB2lGnwkgGd2nF6ejPeo?Z_id zNR^2eJNB!6Q<#X|WsZo=PG{1Jihn*{0K)+8&qG-Rb@_0)v$fjhrV~Qpdhm600K|;} zE&$K%hvNu!75!Jy^~l_wPYEydzvG<_z7(nfnNZ8wwdiqnKx>Y7@53nz?kORWtJ9Tx4~ZwW7gfx1p9iEZb0y>1bpO{U2Hcr+ImHUr^zTNU?m=SVhm1yd9W8C~ zaQx@eHHZuD3^&j$B>I&aiBco-XH|S4#y#Pg#+GZZKZn<(x)u9EKON^X-~3hTKhuiF znif=X7{L(xJL}3LZr_Ncn%&t;O2yGT!F=|t!;8&Gb4=;8O6Z)MW1lMFuo`U(i>cr{ zgLezPv<%sjXAn6Sd3?aEU#rh{NV30%Y2UmSHf40iDtjqK6rELfQ*C;&ee~p1qyO5V zkL2Om6ySk}-bYBtPULLysmjHCs?CdgxcODW;T~)EMIVOxFtox-7Z&p-NAhp~v(+f< zIFBvu14HP_j}E3TcTW<}GgBeF*BJKV`rS|n#KCjo^oLIwpf88k2>os1twY(k3>TD& zXVq^@;H`2sc%FSBTiRI&R@dsirZxIqxiRUnIEazudOc140XJ{AtoubO9Cr?O&u(S? zuV_qm*6wuF@M}XIOQl5qNSk7Lt92A~n)=h#7S*5s5Hv1TML_3Q(VzKn8q6E=j@88udb{Tb(T~PR&Sl-|jN|g(($F9ivX+w(Y_Fi|-(H_Tb1FDMxuX7A1W!;m-n-R$oF62z5 z$@0Ug{MQW}GGO1d5*0NJb{L=u2WnL`wAqVOYksQzAx($>?MV&&)8|q_Kl<4D>xrx2 zTlBUr&!x?2!Zg+KbGC0E|9lbsE5iG5DEqPh-n$=!PL(`D13lxg=viKdBaoJv%<^X8 z#kG>KKd8_FQ<1jNx$A|Z7J7E3HeuP|%0hh8*U2Y_4QTC_pz*N5khnabm>&2kwt39&|AiC22FZYZ`7}S@F>|OGS#xK)Jpv8 zXnXfCKvu8H+QyMaoZM1#wp$fchn9x%J1g79$yB)*;XWdd&>>Sg4@?~LND0=oIWni{sG)(>M5F#U(&u%a$A)5Q9UYfqR+oedr?q-Maz;Wu_crO#l`9&; zHaT>!H|b~VzcG*X9i3aa0+?RzxYtxXd&*y*$NFcB)uL^|l}tcZ#FQxP4{EPQ ze<#^y%kh2)^yxHvHHD^7d-u2XT+Og;MS0vCtBrVo)@O|O6 zVKeGy_QE!syC!`F|3hv8J>$xnWu`-fZdR)|>6^ndNh>xnB$l$k(hj#(zpDE*a03Hd zJQA2fu%PWX8i=J`K^L3g(bpnXf~u0wg6`YGtZ&tLQY3lZgdS#sD%8Z}qi8-IYD$wo zS*7Lf)G|^^_=W*r{865sD5|*2irK@rt2O&{gOg>&Z=mNQq&&GP054UK5AJtOai#b*tUIYam3hwyR6sIwQI zm(sKE8X}?5oDA>O*gqYM=tz;pN}b=|@6>!lVL9$&UiCWGc;TE#vO?0#OKnc2a98o{W4WsBoV=rGuC@Y>MKBGIBmw50_# zN33u&#CA-2IG!~{z$VI^?V03v*bUmDsBj@Y33P#8}8L_cm%Bf*AZnMt+R zTzls^3(2Ibwabl!BMyhT4Uqv`pR*?fJ@eO%(nY7BC}At)r!hClW#qW)R6Ml|C_?GH zk_@DbQxR0Ie3o`= zU-Aqh^%~7PSV?NV8*)saP^Iuk`oN??WVJg{L|3FyMS@QAGgmK;ZIt7oF=z*wn1(H; z$dGUmeJm{7F2^G9CmMHMF#ZAW;ka#j(irL^A|^d^(3*ai?X8n%hiaUY>(Acrh^}|G z_8v7{N!s?95Pg2hQq;B{TTtU_0&R8SH-R1|vz0%l%fIC7mulX5CtEpnuSrF2$|wG$ zgJ`VsfX)h_k?T6AgqBH}Iz7{UGUexLSBfZUakNvfvp!fy&i+7srN(tvJbg;Ll_pP4 zCD%%Zt~U1V;09ubd)sXjVt#!o)hQXBq~=3E&rOkb|LD#L`-~Iq2XEK39l~(^tFb_# z0h8c*nVsEubbqw{!KW@h4jNb6k97sn1D0$j;a2r*8*?r!nl#%K=0=#Qn-S4}8eSj? z)?@^Re#V%gU&RU|a@~o~Qt5Al!nx~huz!<`?Q2l7OMBPI=r`97cCFGq1u0g`Rc105 z%)O27qB>PJI&Ue{Do<}Q!CwcgZY~)TDr_u%SS=$I3iX^>z{7#}-dVOP~fon^oaVQQUTefX)M-X}41c#d_XEFwBopnYuFCU!rucz0BIRE|+d; zHAf_|{0iE+>DwE(jIUT6+4+IL0QV@j&UxL&eu5=Dc5iOl7ou3I5=RhPv2Z)5Fc(@y z`>R=zA`slZ{IzwZ4g4@->^aFo7f@mc#A50eC>S#}cMdWm)k93{Yg=wc^;h*!KSF$H zPhCGbP+`cl4TPu2X{=uG7t@9!7SZ-%!6Kn0jYqoQ2=BMK;uGF41rp}FIk~LIr)UJ@ z%rfnaiq&7gX-NkN)@SxBzPRJyO52kz72V)5ECwM%#=s%Cu1S^#pB#ZFGs%Co_@% zzVJYYfMciHA=DWqL|MAkV#fjNGi>rBp06f3b6jABPVLfM?Gb-ak|D-LyBCGP)@}Gw zFF}U%(QwTcta)P2&7bhFF!Q=`5g$PUvB&8GAm-w)4IMA;4RoJC@L&F2cEE!V{Z^pt zY<+;76JseF^e_wKT3WpWhmYC3_m;W@yJL@Gjx(fGQ^gED&@2TiTx&15C7+G@ za-qm9eh2Uu)ax27(<@}uYyCNPHjrs_VBH-JaNEW{P4d2e#sPG{?v;>_`*Hc1P4V9z z#=WZ9O6Ed#20f~kK}H&>&#KEq(WHTz5*8t4#a!&IJepJ&rxT^l>`C!^m;wna_zgj_ zmVG)Uzh+*Zgv`<%cQ(It+NG?LNuVdsruOEJsbBDTM97CzMsYV${PN>YUubiAnTc~p zT6YdISuT6V*=jh}J$lebE93kqqYUw_!ecz*q(ycdGe_KIj;zb1c}Oje?<^sE0drv?B!}X#gY8=gB>5c zyzfdip)%?zwD| zxEmUCzF8i5EhDsqkFTIA_( z!4LYZdxy<@!)v#GfHB&6qEMKT3mwXtWH;TMffxDJ?aPV8q~TrXi7r(GswV0vB5Ba< zrWc-+C|k$w=hW5IJF8<|k+nN#D-mK4;+Hf7!h+yf8ZA!SsPpKtMq|)-4JY>%{Uh=u zaEe8I%hjL688_K_ur2**r38%Oisk{ZFnWj9C)sVa2OF9ia@X}CjrjfHT z5lJ2kzF~=j97a96s!?W}`JBAcyC*2S86nh0@yFBdQ}!64Bx(|?RA}oHVE04+X4NZy zy4oYyO^2ktlc_YBk|v4q@CrC4SP0^93h|Q6EE>WP_mli%r2*}*BpI*uDN_#r36}&{ z9~+0=)@UXb60aQ{&Lr_vdv`marI5;iK|4v%>!o{BKcEnMJ2=~*!1g8^PrEe{^?_QU zi+Q0g9~FO)K5LJI0Fc zyGYsA?_yHr7my;~6@JE>WBMn!Og@t1idYSoT~ikAno-QTN}V3`Y@fuD4o0V&U&XT(LO)OVFeB189K&FnOSj$1KBXrI&!e49X2cKI z`K2se&7q$PC07cqZ1xTtJ|k=5y$QNr_oYl1%YLfK|Ftztie7nsp0SmRP!n~`S!CBk z%I%-dHAcD)xh~EA`z_l#u=Rf+L#)>RVl|C*zv0ATHkIg#*`#>ON)G{&tOl1 zV$F6hlK)e53q?rJIn;Cg`m;9G_$Lj22nL;*N1RFLb`FL$wdmTx_b;_q9%cz}WQ8yG zBq_BylGWOluCTSoQrw=oQhArN*flf{?S-c6iuuSlsol;hRgA0(7AK=Z3#-~pqi-KG z+Eg>l{uV2A^jLfH=;1dOUX=?#-pSJMlzx*%z|UM)t)q6S&>&IJ5q6)hTlk9oTd$Qh zmmGObX1hQFqZ)KDFj#Diy;93Jwq&0<(Oo)QakkSq8(P31@HD}fwKx@t-MUX=mJX6k z{|oa=&dCh=ev42xWqfPSmT^}Vu^-dfy+AOb0P@APvF6e%++_98p;@v`Y-~3Vh4*qs zu2P?2_;g>!`k`9Uf;V;$G=sIqL>Yq7mt^?eRF2cz7M)t5VS@19gC%tr<|TOg2kGUq zmHT21IqQdKD&+a7yM&_yjw|NXbC6Fv)!AsG;7jF!JuJ0fhZhlivy9;rEAvo#YzX&_ znEMACBcvCvNCyx`TGl+dD|2BK8G0a`};fs13G_Gy?z+8`cR9{sll3%$d z17*_fv(RWV45J zqp3MKCVQAq*;llZ-|fy)PXPf)f@COtYk@M}R;sW@?Afc_D5_ zT`tbxTC+pw!At=JD1NPB)fG?wKDxyk7~2RjoYLdlVE=2x0d_*WHN#JQCrNa{PZzDca)b~3 zulIg%nDmpirV2c~u^DV~&u&bY@bTBtnD3OvnN0?5Ff<|ML4>~T;xYC8bw>)l|Bp;6B(%fC zWQu7M05u5aRw&n)OO>;M1cW|aT%+~5A^ojFXN@$Ip2PCS6jnO^Ik(|!2x z0WWsuFEFO}Lkxo@3nUERT9GECXG-^?-fyF2R$&y{3IMd+u?}kl%@oC9MzK%PB9~9_ zf~|{MV;Ddv`FEPl|9e+4A|Q7FZxwC=UH$g9iO#kQk}p&|L|Jqm>< zOzwbuBu^MaKQq5Znd5U^;T>)OQX> zOWP*Z%>o82l4^Pe1C01wX@cVxIDU(Xg(@0#Wi@*&l+s%A){MSWV7w{GrgD8BI)i2L z^}Sds?G0$bo}`ZwYkLQMi5VN4RT6k#8xV|h9N1!i?ykmoTJ7lmS@@D{w6)qXBi+X?%>C^ft&#+V#ZnDORS1oRU}H2yu4;SK z(j$YA4VU98vYYZv;`J)HwnpPMcC-y6t8vGEv^s~kaVW=X)mGB;R3ri~I5HBE*X`DR z^n*^PH#i!tN|fizBd1ZG+1YB_XFQIml@=#%qbk=MGtZYB<=wHAy~Mck>FO5}2Vm{# z1Lv?<%uh1HdBfwf$<~=EEiW(Q)l5sNs3hvgAZ?OU_&iG#S7!{@YNI$#lyAAFhFL#W z3kE;qP<}^;buBS)sM6(iA zSF2`y=RD2&HMzS{Sfm{vDb#%V5uCz-8F||Z{u#dvpU|%cnT=rfYU2(H6jlcXTC-w_ z;zYGb)p>b4{6&7D=h(WuUO@wkEH}S;2iznn>~;r?YnJV*3m24vCz=Oa^xP~ukqLr% zC~-WxxT?yKXDo97HI)Y>nP0mvFGwnucHgCZer8#A%^^=`7zyxDp+9w!gc3MYN7jrG zq{?+Kg@lH_T6H^{06xqro5&4vUFSLVAqhNc>d36zn?m@wA)rtdFz11Mx?(w)Ebn+o|!*R8?B( z9^6kC7PMiWySx0BJyVjZn^`*M7{`-3g0TvK9iJWYGv6GPD8GY}uwTAU*F z2O_|&_a})7_}uqp1#{VRz@Onv@Y)KuCOIMBe5qL_N{%^g=WJoiS6V4NQ%>_RgJcDP!+mP%*9xq*p zMY5o*H>yY%4{px4FV#0X(+0(#@-t9$&|QpD*B0t+%&mYNQbsG^!qUwu$V(Ykr_)@Q z>fJA6Jz_m}C2>6YaGoYqjt=K|xsrdf$r3(OATv{AEJLr^EF>$7gwJIUR;nqJ zFO?Fn*Pin|5Me4?7;ZS8P9jGne7?pwlJ4xR<1HU}M1Q_0{C>PSvOAnD5|UU{uQmui z;eEfxSTd_>tqHPHu_E!;uTfoo(5TqhL$@bOMJiP!7!uu8%^pvV)QW}P3l>H|EEr8@ z4Q*KSOqtG;P-t^?h^N;yqk*%2d-dRQJpLgd0M^yjWwF-2La&`G1Tm>_YO*snn=3z2;TdDITpk=vV*XVTipgMmu_@eAd+&KA7?dD$DHRzkBrMEm zvne!c#0&|CLB_)Jr&P6u$@SXAzPZ*zE@Yr&yTEomEW!kJmdY^e2SNDK9a=XbHQtl@ zz#kGA@Y7hfm~d5-PMxnv$VFehtUo8EqFk0QEAB51k0PeP*CJG}r~ly=t=8mQI1X!}as>e}V|v~GFcu5deKw5Zf2c0z-MyC@ ztY+%Wl_{i>g?fI&FVvc%0&&@3AOhcDBtD$ai0A;mG(9i_`nT4FyXkF=fpi1&7z&+Knf(mVCTm5HRG23!FHZe@ckRlm}(DC3ge} zcN~3*lIZJOP%Br_3b#{6*Tdgjj7_jd8!;x-h&Z({I%&kRHJJJ2WkNWFF;XzaIxo1C zY$P%+b3-sOUL9Ta%tP|q+zE{(5KOhg@AnQJEjD%!h}6|`#m)WWN_O?|;fgB|r%Y8@ zA8-R_XU`i}KjQNhkTwL0+|u6OL}@t~u?Q3i;^e_nc$g7sA*F$*)^OzWJ)|w_Z;vEI z*8f_{o;w)q+tZ+P6v1N&O5wN=pY>;4pJy~TpyQvI)ABdjKq z-b{wWV6rtvQq=}S-FXrT$CsfPbg%+tYIRgvtsKBvH9Mi#x-1mT9Q&MraLIQ%Tdhy1 zs9I(te0Bws^B+ldQ;Avx&&d zeyR$6%LnI2U=}Bb%`}&IJSN~em*m{F>1*FdN13#yEwX!;kmGz$rcS~6g;mHT*=P=W zxhihKghh|7U;^A4*b_iz_}&+S{;Q(J=?n!Kxoa$ieJGv|H;LJ}K5ha?3E6oHdpkRZ zPry=40X9gEDGrcD$$<$;VYjNb*%Apsr!j&XB|doK@HCQp=NPObiT>)m9g9bLA8~ zPTNc>*!1-D*)sJ6R&x*%3Q9MwRjY2p@)jR+6q!kf=L;9B*=(f*>clDI!T;(7n4sqi zvRZCrq*JQ}>$G62vEC5uTs#3v)|`R_F+ei1=mYJm`Cy4o(w=I*gY;(mJk%igLEe&OWKN}U2b;upK`(569sQCoQRD2-x3<(q4OKv|ub7~qrj zD#!|O!l5ACrj%wlDXI2y?XDknUu*ZARx`fHzmsmV?7h;Zn)z7ZdbZHeY&;wyG1pYs z@LK=+mMG9JmBRL!kPpwx%L_=4oqo`WiA;vD4_A8wYiny&&ecF;0t_77*6B)%!9+T* z7+9*BClEOjd0dz}o-g5mB`qq4CY`|-4(#RtSoCb`jc&;dzO-+CP#5c65E!&7I_f`X zSO6_9R%0|KpW3&5RMBB{y+764-+y^Cr#;hXqpz+7Xmbwc&^eun3BaZGl8W!0YPYr`Off15r7y^VJ+g3kC$NUdjyj7-t)x?&j2- z+8!bHGsKs^fML-ug~tfT1&BPIKrhApam~ahczAM!e94cHkc?ih&t%|E;_ph&+WIRX z2S~Ns-AlFF9*ZvRwgt#|pAYIm&utOumJvzwdU z<+fO{a)o6I3^BQA1YVYO8dt=SX-$o{ehAX0>E9n%>xl&QmMe#e276-V5L5h!A}vlU zjGU@mXW1MLcqfU^!!7m)D1gl0-RujEP-Vji4am$SYQNuvD^@JpOr+By29h~B5m6^2 zrzSq9Z7<-EFMN6Uh9iK7jJ-q)1!RrQf(*}1;KP<`G)-rbjb`w#-8ST#UJfdKine5` z%Q#LXOYaK9W(M+C6cJ$QnHnkGey0wWsW))g??-7S1Gewmw{K!b%=5LTu`7s?fa$-# zvsiA72G+vG(^&_t@6$HT+Z=zNiV zGN(m{0r$Qcfi&-Rv0@$~Ro@Q0C+LkMNl7F~c}@ZPQcFt<(B6yi&b-d&I@u^NzJ7k% z?(fW6@YDKGz5}|TfrbqJAOy>=MKnS}LSWIw0r2F%DCYWZ)?*tSFu$K*VM*L>j~zQ` z_qVr&isbVKqe-Fb%>Ihs3}pTz7ytsN7>llNY2iL=f1(E)A0J0(dW)&?+vsPSlz*90 zBlgd^?1p|@6T~pv0M`miSY&o6b2$)0Vh~y! z2PiIP@_4idCR}Hv z>-G+!20Qr zz?T5B&dIQ_sOS~I_3Qw5Q2et@9N4ADml^#VJ%K~1oXj3iw+^G#Hcn1XlHxUlUeD1$ z(D-0|vdF65oNp>5Dr&boiha2~q)$WR^IQ`e6tsT$w*>6|{yvIa0z*B0$N-RNi?5i1 ztT$oS zb}qm)&$7?7$M@BbyWn1rzK;F4rNzOK?MNMts;5BfOV$Pm&r^Fy*TScU(=X3waFi5x zL46aV`m0d9%yU>aBNl1Ay-VNqFRYL1Z&G)Aha_O>MZV%>8Y?gZ$$d?EM(0)y!u;+a zI1EaLJ$%7K%GpqY3r_6W)zO{6k57)W1khy>H32Q0&pz=lt8ke+o zfdfcDEx}Rm{Y!xCF6bAW^IQ^6o?`wD z8+qv`lYeLm_3+i^$#5xyE5Cy2avhliTUU`1RY_c}fi>?jbmD3l3U+-1mXte*O6dnV zSG)mm*g;sNCow=I;DoJGfg#!Wg z+1avdsG{Q)Rsaylc(&8@fZA(yxw@PZB?Lde7$8JNLHQ@D4N;&k`1ttj_b0pJ?@Cis zQ>o;#KLX0cqpYkv zP^v5fD5iJo3b?LBNzjpRY%FRxE_*)UqbydM(Gd|5fuz4)nF|)Q0t(DtNqab`_@d~> zmw-x~9{lmrW*Flqr=7597))w-{X{I6*2=@k;7S)C{zF-pW~(S8Y@0C_HfYs9 zLq@Q3C9B?HVO#RpjAC%DJ5C+8Y=BelgcVN9(t5o#A{_d<6w9#fTcfMG7+X-}-bs1Y z-k}!%FSQLckoAw1Zu~ z!Ge|xUs6*ySDU6r6A3YDKtX?L8w;%2{Z#18$cP$4rtpfp*%l0)vR+qV*7Fuz9~Hs=@_w=ZU$k&3+a-X0W+`MvA5%AU-(zVQ-zTIr0< zt1xkrca%jvH@B~u5ZO`8b9A}t+uS%V<8RSWTdlVKp_1!nTo?y{{KH{M1r~>OrvA_7 zAgcv;0yN=oa%ickrR;gGZ3H!(8yr9IdNBcB32=<9O%$i|d_QbvBOSIB1q+`Jw>x^{ ziFCukDB=j!2_ZQ&psQ1%mDA47j+BI?8_216eC~;WaGI>r7X={OabntOv*XEowr)9V zx04i)b-O!d)b0KVFo`y|yZ*Vk1b|7{0y!VhTmyiyb~#@M2VQX0xK6_3b^QZ)7x@;) z6H0L}K1Iv3m-wo!y-MnBhH}xv!T7nz%)54@sZ4PLM_T$`JH1u@_{FsNt*Z@cjMoWF z4|kE{v(Lp$p(q`+S#IF!W+6aF=(tteEb}CbCHr5fXYgdWQfVDHQSr5Y)osSpl@v%)7w`a2b$7IIY*c z_2rvg?$3$W+^^t)FwlB6&JC#cm8u`VZ?+=&QE+ib5*hUeegm>QgcvgmgP#b!MKH&+ZDhB*nTYv0f9|+vjru{IC~^L*p6AGs_^#1Ds`gkWcr`~ z-O|bR@v=yi>Ww(=Me#PHzJ55PBY*4W5aShL1{uBNedCLJg1{J$1Bm9m`%slKbTN1-D)&PTkoqV z24MW9EnU=?;zlke)kYWtRl^@B8^36?GwX+^PbA=C%`aLU<^TCQ462Rz`Z%E4F56R%&iS%CnaZ9Z2%)UE{yME%dWY+*bum+KoF-vIg01Q^l`&&MLV#Jj7jEkJAh zNFdTuNdpuQGO0+V-QGB5yQ+HYVF$=yOkpaa0J1A<;k$IV_Ckua>Ti>-kS zD&CdE$cw*GE?BGD{nLmka>LxrA_W%rf6RYaFC2rScn(=Yp+71xMM4v#yHG9YfbqZF z4AzGXZmrFr=h!lTDkLw8yX`EA4Q{>cpQ;rjJB_blA zQ&P&6Y3HsavC7(IN2ghOX`)UEq7YRvb~oc>;Im4%Kgmp{kQd znes4jEi#83&Ofsh(*Bt7`=H!~+>H=IITFm-Y$Qi8xT-zzQN{zoe$5mgdg72udz7!@ z7raCaJ>CUSrqcO}6%?eH$j|Xx5>#+T1uJ=Ax;Y3LaK?u9;F*(t2GtkC8bI->4dD<< zW!_mUB^HrPIWkykIt`-KV+~iicJdUJ)I+_q7>SpzB&jJzu<~%<^suxs za@ZV_#qu{zeZi*#K?SN9QBoi%HkDZ)#t24rK&J5>#Bt`mOao;YE<;5O%c&V>f7G>W zb~#$@?)m~nFR)+z+%^n?U6cj=qj&OWbLq>?t;;$)`zmaNAm&|Y%3-wnbDO=~r7!6# zfSNF4$JcwtT+S7XwJ;?Y|MR1P?+V~fIVx3pV1RpMa=A1(U2f`GZn8U=%7I>}Htf-f z8gX_F4GGbEeSUaXD*&1WtkI4NSr1@#Av-4~Cio!sXNyn)b!;msUDI3EfQ*bRnZ^~j zSZBU8*8GQ~4N$EIToJ-;)p28c;?wO4U_)!0e0qC9@z?yX{Ge17i9QQtyktMdjW3RpzF##j??D7l1WYziUjK~sm1qde?c%x?3j!QsV5(F~ zn@{VfpViXSd|k1h$bJ6Q^*vW|`&GkCeIcsTt@Pf?-D+Q@4y1>=lwS7%zsuQ_N_|Ah zZ{$u}pC%7zF<^b=d9aRa&r}`x3&MMugZ%{Z!>IySg!U}5?0j-OIqJ5pGKWWB``;^} zLSHLObj@rvkGAHdzxsNn$}`;$C1UDgZY*rfMJd6$S7AazoV)oJ_QuTS1AS4XDI)hh zxkvC+r58#WDy_UaPgMi_{ibqb-4$-vr3>bns(IUf_p=5Q*>W5W{i_}J$9#D?Mqi%5 zICzgrHIU=v17e`&53+;}&Ar~@h(VO7GBgzl{gt;y&8$pUTW66KcfIl9VLN-sj2bOD zndC2nGLrvao-ENb%!I|x&c3|N1WMSSkwt^lTn@IqIaJD*P7_z0RnTDR^^$*Pydrs7 zl3|(!ny*T>$(+j3_}q>OJhd=}j(Q#yrh33!oj$A)esdd{pq)n!c-Y4AY|%;p|85r5 z0|r$=eSM~xL2Hwsa5Y3u%cB+NNqQK)^l3a`aSG`Et)scq!_WFt1%SIy;_tuDfmA^u zojM52XOrC?0#37Tdw+`|cOuxCnRN^NLKMy>gVZ)cACUVJV}V{_d$cZn*8 zyk-yWuIz8Sk}?hO*h(YubV*ze)CvXCd1A2?5eoJeS?cr#Mn zr#ocYr$TdT9$N0LIgKt`#PQq>pC-~HTVa^(HeH6aUA0+MNf&W^2%CSw}YI9dvA%CuUc+RjVwG$z=bE6srkuv7sR+ik* zL_1Ckl=>u#$y;#jiektR{NhLJ5sQ;GUNfKQfbaUT*C(DGc>xiqsrSD!((~g=k5pR6 zxWdWiW$Y4F1d!vryQynHSO;^%y%(We6t5O5Wg?`trH=-FCMn84ITO1rSTArq6JZ9v zAflFN{xsZnC+~|tzAmpZM#cvg42$i-@9t4nS6B8LFDnN2^RY6yJcG-nk3(k(=YgM94m04QE*e<1&kbGD0=EG%dNjFAlh$OF&M4^K~&+=Z-p*Vox2 zghEjhqO{tsF+PNG6yL)^X@xdOQanC z2Y{Se`~a|8Kb!>3qJj8 zvAq|lkn{r2>0~$h?(VJts33c@f<0}9GH^JZ;=M!E{mCp-gx>cA`SNskadVUB_4djF zAmTn%b5Mo$1VD~X`k{GBfPHm&+H#i975Lx-0iDQd{4K%gGTddhuK6A4^E39$gld`m(@SbR9MMPT<&6Js!=uf&AZ}A0E4wg1jsgB#7 z0sfBsavQ|&!)&@vPj#gPssR>=2p5J=zd!TOCH*qamzCli+E%+!ku#(Z<+l{dqQyZJ zj6^b{k~nam=A%ANx{J(P#ZopZClCEy>+B6sel7~| zF9iks_%7VEI&Ql%*qlY|tRfmDJfI(J8BDF5H_F2gyV;imCHJn^PxdstNnQllL)i1b zCp^=HR!YH?ux_r%9&gP=ql+R3V>)nF9vyP@md|2!oWp%2F!cyne7PXGthq&G>IIdY zy~Y_(216+`*VPP>^l?wNPcpXLB&iHaZjjdy5_=O@9TMG_{9~PnD!jO2S6@K`f{<#r z`70os+9DR{IU|L6mV79nWs!vLQZNXuwU_P^0NyT}4;w(lmjLJueyp~ua=C^su$qBb zo>@!{L}>lv`6@9>0+hF}ia@>ny)>@*T7&ad2MnEB@kdpFEAX~wpp5cP9V1j_%49P5 z8e3WlaDIpi3B7kSv;%+iIY4{i|DF_p%FbX6Ib4(NPW1{EEHM+sJ8KB?Vhd#&fdBGL-+@zkApTKa zvXQyDIe`5W_eyO4O%>(i-Ti{L*&#Vj{I7fO&J60ZY4HaUNpFoj+R;A-3}E3BpjCO^ z;7H=XU3|0e{dfP+b1#kbdz&u6vx%9A{#_n8Wzbt|jPKGIx+mQ5JmlHE5s5+ZKRtb9 zb%bFk?|8dSnK=q@Q$fZBrXah0Na#!8u>$42oS`Nf;95M|!sIIIKF2q8=b7qF3@ZZO4I>=t|esg~$?5O`!YvKQB^7GTwCeeN|6Zo5{Ex`;X z;dGc(HQYJ=g%J$!ox>h!3v8UprSwSH@IWzp!A3Grx*&=TOSa56t znnsuoi@-#_5#T?;tVt9H<_Y0x@v-4L+h@2a=oqVYeta&c2N_1bmGvx`qzUKhy2NFj zNP_(V_^L9SEEu9P*20ibks#}2;Gy>Kh(L?2MUy6r#!OO4&anIB5Bps-xre$7x__% z6>^3Q=mf)k##`5>B~x%fcMnEoK2MQoPz_aim%mn42Ux&b_Wj|8;blCv(GP-)2X6i!5vHPO%{p;uya*Yvq zC6`~2FgH&jn-;3UIlQEFmq|$15Q6$xEnmQ5o0C3W%J5-n za`QnMlUnc;?)99(mtvc@5#M{}_>3m$Y<)a>Q?{ZB#;n;oEv|G<@##lrfpw)^_U2gp zO1y}nIIzUF;OaNCV@K)A&b+kJ+4@spXI~gUPP{PnMKmxLxt?%l;ll%jv4efNahooe ztK7gKq=?ZS>xQ;$pTTcNejIGhwZZLE7BNs#TU#54&B6p|7sxNI7)~mIWu*AJk1p+h=v-4C zd@^wJXV7Z5Q#D}N6ZB%e6W?7G3d_98tRJ=%n&+X!HCz4uX(D4_zIxc2Y~Og}Zoq0; z!uY(NJi$U3;Xbsw(Khj>dm@~fRj6XUY6|`-GXsGxoL$-o8l=Nt%SHiF+==*IPfkAK zAwzrzg&OD?gLp_AZ~%HPb_dqr6Gt=Wx_}$*gsL`}Po23oL#13Wn3SpKfQ#6(DClQ0 zI}s$F+czFe9b_JwQ;#_ApD=rKn#1=dXIU*Ppug|*puL?ckL~Z@zpJ*pa{{7gt2+SB zu>vSa*XZ^@4G0V2V8O3z3v2<;I&l2UJ*Hxjs+!>dF@!9 zdheTq{?{3hEr8Ms<-~KsDu#V(xL%vHWe0JYxYha39?ZF(;TF9d0l@jpsYI_K^QdJ6 zSIVCWDLx{d9DEwaLlouFYvR3FwKV35jv^rpVpa7TFpU zPU3)@EyRe?n@xvI_L@tn(`W*P#@wlyt4|TPrgK-y02nfn=d%0 z9MU>oA45x&D;610rsZ ziPb+}lK2h3@|7pVNuh3C3}vC>#4+j7C21FEE5y;?l!a;jIc=nmi_;%_W}-+3#%bRo zpc0*>${L5RzqMeau_34O7;Rg{!=-sx;$A;`Po}i5iiJj{tfGJMb4QFzzAc0r7z19L zg+!B>B1uMn67Cn7fJEnDbwtCE9Wk{E1HSOJ{sHEqiU0{GvSY8HT6KuQ+Dh07R z-fu`BzajbW(XE(#pHnDMGXUH`sYi5F1Z==YKLm`D-!eh{ZH4wX{hiZXZ#1Hg2I)LK zE(K+*Dhf&ZjAn|f5$S+Aocwh7p9WZSBi)S|VMV@P-d%J`-8p1GPsbPk#1Ie?Cp3KPB(T{=QjlBTv1 z%W*Emg%f~N!$F{ruT-~HEWpObcYC)<{+awC?x5R7+R2a=qrod|1OVWD(4Xv9yrfnBOfZz%4uEE{iJxFkOcY;F*?he6&y9al7cXxN!4gc^w?{~iUoT~p+ z4VArj#%HZHy}J9pnqrPrwXs2L*8qFnRw6xzc$mTZPk{O{0+qvr_$c#~T`mfOI2WU{ z=+Qj*jitrzjB_%?zEy>0^Sl7C6Bhj4o>XR88P8QIHq&Md&J82~%U0TtK?R6Asa`uT zx%irq`G|k;iWJ01IE1oI7IRrrns!;Q+z+Xy!oH%<{$A+sS`Zb(j&#Z&G^40)Jn)>O zIMuqBch`6{iSUgcm~k%Cmdg?Be0D7p#laN#}NdOg3${kQ=Ep?A!p=DmhyHOYcU`{o|yPUTU+^2l12qO3S0?vm)^D zA6z8ZaUR`r=ic}G<^y<{??m5QJe=IWKmGHW61bfs18~6S_pj|rO};Jvd<4F|$q`3m z0Bz{K8DV^D^7RCg+fKu(N4mSa+YZi?k~0rcKN4AQ;Ud4U;M3}qsiD+hrw?S)4iqUB zOd$JgQp6dl=L4oakcB_tJ-I$!&&;C+L)@?XxmqsPYCrm)0Po|ynl8kgwh z=7HJ6LY*}ZFh?Ze;#%^)TqpI{eqP$TPep=^ zx%mtIGF6Ywiw=3o^MkdlDewEM{6zOW(?f#Jixc-FesS7k6B?`dTL`E6;ah~e-(y8< z*D_9xL#ZaG{U1KjHrl~d)$LP^wlo@##}-)>Z0MP6hh5)NspET|%wpaEjD$}+kb<9I z$$@z}i}}=h&^Mow-+t>OkodO`B%N#nkC!SDJf24Vir_6zlh++j7ys4IvJXs*J-s{4 z9gpg38&L*ao-0luf{jE5O?|;oW3qM8Ej)Prj$+8XSX*R!|8@I$Z?z8(LaOX;!>sq9 zPg+w(8*|1FhEfz~ecCvvx-4&2{^A)kk^cHUM`9?3xy|ld&Bd!}y`cgZNTIuImGu}W zs&k|EoyA_Oy;apFrt_sYedVtl@%k1K?@z#U7sw3#010b%G(+_)`o?ZC;)QR{CAJ>f zqFn{Ic&hq2BPF(i9PfHD=~Y-pv}irA(CbjTLtxgO;!}mG?yG^4p9+&fhs;*9*O>ft z$u&n2Ge1Pk84j-HG|j!YcgU0#>;(JuTBeFQ3dGi=wiRLkP`71OJFD#hGSdLEP= z(_9{RG(ckZOoi^cx5AtmG$wBa)p{VsNTv2!*!kZ!O8oPx)^*b2VJ;-Oy8My3*7%|a zaBm`MHLr8N6lv(TbMC0fF>EATukK)N$Hk|EFR_+xDaw!G<`pv}Np%@*>$iJWYx!nK zcR3h`!hXSdG~U1c5M|k!3!K*A>EVtQOds90T=&izB729U5O2|(JW&#hwRAu*aXwrf zz0F_L(mg5X9}xI(7ky*P!MI`y5}E9$4DQx~h1i?o+V{ZtGN%Hz5KUM{+2@(>Mi)iy z(+2;}OKn-S|4P3(hFV(v4o|=lb*&K9cs}Q6e7u&URee8;Pq6WPsS89Dv<=889MHWI z?%QysYgUjuam1!w<|GZe&QXl< z!QXNA)2o(e@0yJSn>e~s*dmh7{Eae&Axyu{8aJ~k^A4!Gr>P4~Y0uicvHf}CWbP6c zK*p3pRPW&M=!uAN(!WIHOqB~}(_6Hf*8mDNue5t8IrFS$jURxHDq^yyU#}ZVG>Eu) zdhbiJe296n(#cBV706EVUA6;BGG~e(;%+~#-#$i1bTWSI7qxTJlUBj%_}0YF?>U?7 ziIqOkwZ6A&<^H7yu4{VQd6|>+rKGpF_X`%5xo(=X156Xt%RrR#a*5mRaXcj)cw)ld2F9 zO0~jDu!5lpBL8@i|45t66FK z44g6=!poo|E8prbeTkGA!5|m6nE1zrD;d2bM@h37@8Dgy{%32-u=;(9? z3p!4$|L&eo{@S~r&)B`oORNPuD9+R;i+@`^_k9; z?|V1Z`R6uU&ZMedhiyF`s3qC6RE?<_5M64dHHB*L57h9bzF6vcf|SdHit} z?ENHG5ScHpkm55VxZ@$XX-JATDf7w0FMGn5>=FZuDUA8Oo_qBvy&Wrv+(cxzh!k>s zJ@ppfuQ*q#!ZC~ZtjK&*_a-Zxj@*{QpCAQ1oC2YD`3=^tZeQ5)xJ*q)HpeMDHu^QC zeJJckDJ{5@a?e*}fn2@nOPnt~Z|$vVqx8RA1zp*;aG&N&G-|&B$&uCc-mq)RbZZH8 zy+bj=A9~Bk(}#ZKNMB01=Bde(%Rin!G^U-AVL=I0FJGUDP>7OVsS!(Z|d~B2;$*rz(QU8mBj!I05LsOl73XT~;s;bJaV`A!kA z4@+LxqM6z~%Md1?*Inq8meBg*9e}nqM@xbnN@IxF+MDpFzelosr+h>HFs(=o3ya}f zSwEr0^TG%AeV%~9Q>%I@?C=EcYjJrnp0e;4l;)r9fTi zcSC^!5IE4~P?KpYR{-lj1epH`^Sin@X62rs_H zE;tMpmSiG0mbzjznLtgn{y<>>3i9T!DET`74zWHG)p8@>x*qnC{lgRZ*G02$% z@j^6OHD>`jfh2hfl-F+h@Ke4JZVkQ{ZY0x{=p|SFkdjiZRmZ?zXe*D@cF^;{U2!$Z zzJxzc{1V^*(WV!^D~^tI+!2H%i&{3HI!SQfHO>pCpLQa-wgSAAlL_@Nu876AG&w!C z4zUF#8lWQg8<{0ZZuA*0^?ex2L7lm@>V|fs1j4!bBtZdwE_5x(&jNRy^U_;_TTSCq zu`zl6Gpv$glh!_(wYem6_M;KjCmV8SR&)~Xm_$``@R6pmtPwfao9f2>yxbz9Vlk7L z?8i(;7gR_5wK9A z6)_#ddqui~S|B>kp%&w~mB5^#8DrA%>7d6y$dCvL%>BTG52p(1UVg7S-&|13IAM6* zWwSO;Qu7DZQoKt(+b$jr47RnxB8Ljm7%FuzY6m=U%8dmR!`O)-Zr)GnPebXUYNh6FgNh!(UQr@C$ggbPY;ax zTy@@|a73i!(2$NxY$o5}573wOhCd$ISM@9gmBsph`~jle-rNq5j#=_-HuyxQ7y;wd z#p^&U>W`A}AjJEtG8mN_bUHI2tyLwUzO79cAetljcGHziQY(cjQ=*IV!HTsz#?Arj7LL^Du0X!}))gu!_X0`knBfaxr@xY?{D|~0|)*D>|q$vxHjs;$q{38O3OuVkWsKW%!&5TCl98eWFNO7LBlKr{#C7*XRX~>yjUNjuf!A12nER+Bn{qgRX74(TPZ!ja zleKYjL8=SZ+9+pIk$179wW zPnVNldBJ#XXbDQaQWa8x?3G9?GI3#SR}S9HZS^Z67d9+f1Gsep?SZKp>IIybomsQToEfE;#!oZzQu>!jm^*86Pmkn`G-_ihmmr^KYX(H}h;$itrsWUUy*bZp&QPhp!6u2<w+{5iVJDEy2fCUm&l*b{sq(I6?K8y>Gb3b0T-0-yt`c%a{x#i#3tJ#eqtDjI z@@!#(ThRxSGUfJ6g0U6{!mss-F@mbh82x*C?TTHL5-DNP*0e^FPFulY=2eRc$Ik^@>wht#S`0MnBIZ@2{Pg$e z_`J5r^$TgqnN;^xNSRI*pT7CfG*3i4@GsB6Upj=}h7QEKNy#+$TIjv4_ylA4ef7GL zIn$-wBAdTlVut==Dk6d{*3)kDHFqw^H%&~=rdu_JDzyCE=93hKGXP}}$-u~^MDn?N zQi{jeQ8)iWM~f>9D2UHZuXFdN~7NN>OEm}&CN~N&$+}D7A`GeohXFEns zFuZflm&Drr5`F$M^bGRZNh?Oar9`}ruVcv|(k}%OmGnVxqzJYfg;v3?2F>W*%p}fa zZuv_m3(`E4Q~y8`L4Flfl#tNiN4OgY1SAjFZ<}$e0VmQQ1qX`V*-xc21_9aRNtmMR zSti7rDG~wS&O((TW`x+g2p3RZ$~OWEEG8gzy~~PCsomR4OPg8dMq(cDw_aXeR?(yT zzv1%QP~n*(83f(CBHm(4QuQ}7{<`RTkRKl)Xrp8{Eb}$av7V-3Cy-*nI_;SoA4R>Y z)n++*ONd#k05C9xS;v)nVO^o=P&8jTlK;pXQmXk4rggvrv~b9l@oC$@Z? z`h6O6cD8Fr7vAbwy3;Bk*z9od4#P*!WaI;h@UmKwLf@PX$zF=U)Z-Pxa=;$SJSm^1 ziP3C--F>UKyulT{7~N`OH0ithZp0G(irhmca9xk0#qsAy*QU+|eDtrVRn|VCT(@Xmt%!tP+b9LFssOu%F!v%8aR-<#X~D;a)c=h8@*^{mY9puSI9}$w54#wuz;-~H+8plZ9`si$ zeiOY%@k`fcVY`N&jbt%+Sf}Dt)DahJJ!26H>ME0vHC@cbA2Rupv+;{YjF-r^-lj%Q z>~O}gq>w8WtbbeQV*b{7?8J^FlE9YmS0Q{p`$z$~7`fsGZ+TDkw<7(=kJTA)^rJ-i z#+Mu$6}}^r-HKtR^-{)4BjqW*KVn!B-5ACe@skrQ99|GC-^Hc{5;qS-A>XFz*n&d? z<_nTB*A}HxvFq*u?P1n<5;>cvMIY7T_ifqC#XIw1g@7n@?1c;SBaul2Xw*n8<%dhq z%&>;Cs=31I9lkL%$Myc-2h?GTo4SivqVnQTwf-UC8(ZB>>RX0K{8{LeB7zH^JuuXmYe7h|@#XpSh>J2bk|+!Nv1J-@ z3p~(SIk{~~Kw{?_?(9X{61UN;WIP#%Nz^OLwJo=f*FiGh1WU8E4);5yrdoluEE-^L zeO#}Kl){%^aGSa$Qg2QhO5d0nM%0a0|0}z>oV7>s{8l=mVx}RxeNQ5Zqtz^*|440) z-)ZBlUDvwJ?SdWQ_NT>)-+jH@$R;JHLtv!*E z+hyA@G(=tH!c)D8-NJSmD9pJ*se+KP5hUf{5P6XGq)FQ0;IR$gzzZMumYL=%L@6#0 zJ@XAW2}OCHx6eQRRvk}safi1rtWuVqt$jar83T&RzN2Ej05}MKYR8*ck*J?Doproa zo4VPo9tbCVw2rssBR7JQZmS`H^BK2^5Lid1)_;RdA<5mMSUEz(+*&nuqeKurQa3^E zvzgH*dH`his5w-96CESqs-MgJz2KoDCgnn=e%+871g@72qVN6SwH7u^HxT z5)RwKIW1CS@ioBlxZ!u~8p&k_xW`@Zf5&eW_gr;bDJJvUh5%y89s+Yg<0#EES?K<3 z2y6GLUWg+**-@fuOu|gflKHJ_h1gf{1eQcARWruEo|Su=_>s(+`D2P)86qjVR<9pD zlXWet`llSs%aK$v4}&6k24A;5^2!U|N2O2nbp1-cZ$xNeM_P^4HT&TrtygsdCElVD zpnW4b-MAvH5tYPIEKgoix{rC`=;JHVlqurEa&xhGM{4T)oig)JUsca?Bk7>)Dw&*r z225T@i1aPd%W8@(muboDFK3xJcyx^^9gb&nW9bXgQ*Wc=NV=xoGM6Z57~Y#FU#L;W zOH))NMSl0Q?nkqTmxKK!y))10$a|aRvKJ^4;z#-5&v5t0DIUpY&ds6{=w@^2s%r z;UNJm0X7r@{^Xde0M_hnYjesFY-L6Dr=VZDT?q)4+N)|TT_ZcX;hv3CbCkPbW;FQb zk_p7Ig}O@A)_t7z8)+{)jFBOp4oBhV#`+Ih5G8zUhC@3Fc&33c(h)13gp3}3U8!YC zcRBUksG~VbHKp6Hko_PuY%)fp{Fnm{tQ^7aio&snHL7V1fK3$+A|W{?Ti+k*tz|hr zGSjrY$5v|;V=X0eVwH+qX1v!OzuVX~O@6V=C?) zpsw1|MG1*N$xHuN{3iM3FbzXy(7ev)eKlaYh;`>QeUE(na|AMq|LYMVU-Duu^vDjH zK*)lhnPOdQN*cyR-nV+4)fvNb`(JkP61~4x&A~=Hn*|qLD5IXpOj+3;D z`*?6*MyJyv0l;P6XH2f{?uwOqfcIT+qm;mPQb1!fiU2tN&ykYa=<=fuMpT5@G!u(GzK&Km2)l-r$s?y2)hZ&ra z^TlKo-XB-iC02LRx_s`Ksr;T16>p(rTM8_<1?SsgV&u=YU<~I^(Sf>8OfIV)@s2Y; zd}NB*(UxZ^ z)je&c)1;Tvzm?lGSj$1^wIy4swQRD@5j*q4UnMxr8FyXkAjh_9KOBnhOI@`%^3quBj^wqAw zhzXtYd0+bRn`bjc0W|HdQ~G?Pb2IxPs`sBtXzIJF|C0aKtIgi-(sLct{vzhLl@qi>AMmM1;WZ<*X-bM)qrK-YK)NIKhR>HxIc z$=ohBP2er90FK3EJYy8dn5F_qEg%D+m2Mi7G%DmvVmwb_q&|MQUc()M9nX@{W(BGU zD6DX_q+1Sdb&Y(xn$0!jd!F(M+#QkfZu}lLiQquINbgD1gIs0Bd3h@yyFB?*vb75& zDn9V~6l5eV^D%nWzHAvH3cvo7`}7%%miwo@hS(i^cFg-N9kYF(y`zEq#&}28-KO3C z33sU1(O3jbA$G^AZ6U3c#9`&pL%%>J6pXAVVuKvlJSEl z)Y0<@!4N|CUDvVpd)BdIexd?tQP?tWAwgP3bmfqza-4lzmTr=QGJ~Ze^|UuV=tVJ^ ze)>NQ4i5+hhAYT(QDiU8n=1`7!Az+bF}zm1NzjoyQW>oAhZ|u=Ls8;y_B&tWF>*>G zcY8k2Md0?Gc9l@6%?Y-18~ssUR&jOK?VHPDHQ!Ir_iII+Lf9c8AyEgy*onU$M2d^6 z%_fP(qVOZ)rq>ySuj%a`vPS^pCNZLJG+@P(a?FoVIExV*bQt-|yQ>wd&((RgcgndoNJca85vw_mV(v^U1hT76!(y2g%OnlM&9ib& zndHVmVRjP62j%S1=WVRHTre@^u~Dn>;{wa+@oVH~y7fIdt1s@JuVf%lHHo_Xg?1*= zA||e|oz_Qj=sKeWaQM9E>u%Nb!K6<3dTcuV5MV?gWq3NE#PS>$KUMFMS1o3Gxz|y? zj{RvBk9Z(x{){&LXH~oR!uOhk;P&^k4X>I>l17ery5a6$dbLUre5Z;_=!{1?Bf)*& zxOTO6Kp{Ky{MxI?i*qQBVAOz$3#qQpRGMd~+(qEzO}caJ?~7*<_5SJnMdgl&K9oe} z6@JF-?~M(H6hz*_MO(2qZCKSA8!DNMJnpy=WOT6?zGV%zU`0+oLvrW-HSnM1SMJUm zFT0_-Y{h&%z#VRIr9=3XU5tj}nr*>c_!b1roP<5Amn&??gO5upHbWBz zNMIGQeLXc!>2`}G#&|+3IrXnbLvS6^7wjU^=$K+B<7dPfpWRR~L;A%d7^J`${Xx}G zwYMyx&NnrA9fBWM$cjdH$%M#0!5Ng!!t#BDar0= zK$o_GZLjcRIDY^#)uW|iw+EdMet536v`bL`%rN;Xi6a13>gn%a#xT_o5%~xxpR)lK z&Rv)etamR99n@ftX2;;CX8B>%i^)Z2seBhhgztXS>_){2eBFtK$)|HIhJJF8>uimlhlih;WXbsE`p#J* zSXVgjTzjhkQzIC6(n>aRs zh5SOlKDOS<)F$V+o7TyH+;j`(yg`HL$It294@*{T2a}7zShVNBm+fa`l23{`>Q+I1 zun~%ES)F>AUT1ciL3|;n*%()aGvIsVwQaoy4-2K+jo7Ml9x<)ht~EfEhC9$oS@pwb zW}9;NX7@z!ZmGR-SqohpX}-l!(m!*6LLEIh!R2}A3E1X7kp`zo7;4@gOttaU+dT@2 z6Tvu_N$|(&FO9wav2`4gj_j~=if)0l$YS!TW?N@J^_E|8@|G^O55Ztz|52U2@yi+5 z5yDtcxbb5)qY=__7xZaC)x|+c_$~X^s{2h=b##u}O zkkTp-Mcq(`CkbwY!pkQ}M4%=t7C*+rDm)aceDXLi4?EYXm=H&?= zH5gW?(hlaZBPxX0PcbI>^A?>^jzG!W+&O;1Q0x2^e?GJ#%Zd6tH$sghxY-SbilbklP%@t-L6LTTMrbxG>~KdF6lVJ|MpbyC9vYG-D-N2h*ks)}JwIIPj5j?_(; z);G+Vw#r4BGNDMyh^F-i`}vMki9HWult@I3z0ECEM&pV22-jSCJF=CT*r5iD-;Y~1 z1&nRGA|Kz93NM*~QMK^i<2aWYN{6K3NPN$Tg@Kg@w8>ztOICfI^Z}*pPgV^=X|Qrk zf1t1J)YxrsLpreWgS4tC@+?W6OLx>$Jq@L{Ef|F68F#MlBvvz|h@?=zFFKuJTS&Lx z_^l*~q&#DypmYOD;?uAq?PfMKYI2-W@^?nj-T8WEwiN)SYI!;;8K?&NUTiUA{n65o zh?_SmPMZ3U@2s6b;b0YWY(17L7OtAi^A{bcyQ8$8M*f57zrHi9_MT|sX!#w3sUk(K z?J+~FSmRLhrk-cVa3{+@iUnFjVEoLm4+lHf{?J*-YiD&>CUJ~ZlwYBM4 z7{*WBJhPPr@74nS&4r~}z*QQ$DI2x{+n{pTZTUUvVtOp~hFa$nnBndo3%%ZR3#DQs zkIiwpy|kZctL~`dH=YWoN_t3CynA?h)MJrAaUnMLm5eI zr>HIqU#GXb-ZCzg!U}2XtAnx;Z%n;;7Y$b;3VX zD4e|Xx8)IxIiFW-`G)(YCeXqA0bXk^vXhq$9nBPVT9}xvd{6(LQeEDJY};v3!hWK%h+ zO)7kOO;Pa!g+e^J9M@!*TY$cy$)?hf>wu}T!qY{6MVODg-UHwf6 zAxEw_@(U$C!5dGJsZ*vpPZYlY7Y_q2ZB6gzT;&MozB09&xOU@tE1r-NW2`}oT5SYZ zu|O^CMch1+0li8jImH+|$8ki}?BJ=6Yfjlf@+kt|jjUU?k~Pcjy`CmBlWZBZkjUvO z=RoRm`D`>8(dxXiBcm-Ln4j@@^(UY4u%12ruz8WCE3x8y*8XPxCTcYFbZ}Pcitfhi z60YKUD0h@0`!Q?KC3V|X@ym}y$wT1c*E*w?`dJ zgOG}iZ=dc)42e=10+1u5wNZuD*eOzc3O>WU5}XjNjT@9sB|%3ihpZ8fcCU|fK>+1G zA#WDTTrF*Kd{HVxslx{>4~U*4y?Z$g^{)1z)J#u#&MW+y=<6YaoTLJ6Tn$FQHV*v_;ti z8@d|CZ|cc8Nu=%{a}n!KC1Ku>%N8y50552Xi*Y`ZRJv)#y!8GAB(Mr#4aqc?O7pUg zvc$qj!(+lj5$BMhM}+rtZu$)`n(|68OM8QUH`BnlnV6_tE5j`{Fdbe;^VzIx&b1;t~02&U6|>Pja<`NLOp zn#J-7gYb~Xb9KsOe)G5YbZjF~KsPPwn6>5|i}jX75Lev{wsz1t`&ZAFfsPx&Ixi=t zt`2mSHZ60behl^02;F~V1bc3GA%wZo4&BTBA>#Gt`uPpkVr$@%iGnrxuAw4*X%~aL zzVcy`r8r9IGs(7CK71b*Y>=mFUi`X|DEOo;B76sPdbbQDO<8i#vZ;#K2m?4 zt#C33?*Z%1a2drySqg@o7!*YmBu)6m9i*0hRhJeYM5+c$&fSWz79-%P{M9BJJtg3=mH9z5vr<`W25h-Wl0UZ0}rDW|a zOHqA;r*@WzKn%^za@c6+p*{*;I*aC2sgSVnX_yZ7|I2+}(eQ-EdY2i;=_Q-*z^JLt zW_ocO-S9d7%8tiTINJQZdCH7Uil+)HA-T=AmGqFC-Z@&W=(W-LP?iH5QV5-&dpZ_W3N{9#&4 z!!@mXIw6U!+>QYo-B`epTKY}m@u_3b{)Lk^gTJ7cA7+a=Rwp%AH1>}(LWD4eQU>35 z=>yc8*$mC4@#`ZLK?$IE*iwCm+j9NL3YJJXl*J6%j~DyELZhH+r7Yr|Mvt@{7bV?E zsnnPuYt1<8W2?QBcIi??ixoq<^Tm#}R7AOpznDB14jvtO)G_MV60V5B1xN7EZ*6}! zlJzYRj4GCc&LEL4SN)A~JwE$!lK&p|@ZP+?9z3Gk&ypYtxDJX!i3u1QY>P10y~YkJ zsMr?zjphWWP%mF;yiC=_4EjR6xw#v%y98;I+XjOAMu|27HjeVlkr;3uN&%mNxr+bU zagk59GawC5ndNq|HW%FU)ecoIAhBHiQWKdyEE3O zX*Tz*3xkG?N0r(0D>*@*22jf~#8A7PoTv9i<_C|)Y|v>~g9{Nh?-9>E#;JhC0&LVy z4#O>O{d3*c!oz7%P**V|?OJ&L;X(0{-uhH=v($|(zn#yy+m=wyHv*lFNp^nZ?1b(z z^wmF24?|TVrwK0qfTe|j(ueYb5Isb>3+EGY5_kr-Xd_c ziP8nu^z1w@_z+}=y9$=!#A2L>(3KY*l(Xc72z3iuhB7IPVfUu&CGU(UkH5nKA{m&F zzj!*Go5+Ar=U|$2w&+ub2OhvJ%CS4uIqJSlh{Y~@`A$wF%iEuTFB)jyN)!G(7|gi| zu`@>!(nAM}nDour&Ux&HUGl42>mN;sgmIrbJYf|yHhh-KBSXz-(EeZ7HXIB6QQ+mz zBLG;p3DRaSEzIU_%B5vCU`>ei1d=-=uQ$#zV+yH8M*a2y*^Qaw&)ion&my0v;>!B$ z_B=KGG?_h6zO`~WFMD>Rq^j%~5%5YS>v&EKjzrv zu03+x$(u`W_eGd9dP6h*tXbFeWY7Al+IA)?aBDGRH4}l9luk@+K8?k!lJy*ot(CbH z`-A{tcC`mv=AiZSvf(@}K&$a3kHDFBIAJ5U1&vIYM3}FX^25=W5I#@+m~v@3 zYst_rvs5T^*VaHx8M`BNME?M&-S;344(l>r5spzlPXrc0D)+#CUMIHEo-a z+=3ayLSL=bETy{!F0U*%woYV*5W9-w~C|hO+8?x>FVe z?0qgb>bV=7%@XeHW5rhUTJWg&YSE6ORyj}B!-vJC6?aOqene%=C=ft@aoMlZhE4X* znhc6RNlg#(!**cUki~az5iC-8UuC;$4UU*^;k7zKYG&jPw^SpYnIAF>?_bRd=@){A*l+bF59Dh*PyYT@z5Ve= zxeoO{V9=Iq`%|yKP*YyD+ zk-rR^wXCx3hA{CnZ1>9tib={V3eKIXs?}=h4^lG6#$R2=6_ZbTRfp(Al>NOLe^3bH z#VMMhwe_d~%$dvEpDxRqzpLg3j(Fd5jS~D^mNa259C;eRraV7LUp1+hPMD;jWZqPM z#-q4Awh3rF&(mrjBvtye56?E8a7zGh>Y=c&J3-?RP6jIA%kWm)}jao|olnTR<; zq<;_-0oz;Dx$nM?Z!k{&b#0K>I&~qHpzxyG1R}%cj|Ndx(pb-YnLIu-tleYeNa153 zYQJFi&CWR0H zT|v5EoH?M$P$t1ai@jHCYwxMt`F7cne`R@~U3*dJEsCWST2EY|gDfr3GgR?0C$P0tj0t0~V z({F#sjFZEI6dd?ykNB>d>uT4@KNoWT@gpAq1ip*T091+n{h79qkWe%!3_U$PEIN$@ zP!Qbyhumce^cz82`evupGCefRuV~g-BkrJ&;NRrOw{ByO8wk*Z>@oyPUyTWSad{QK zwRXN_q;pOhQmofizg1zr2dK`Y4+Z!X?oJkqbXuFdF59L569evIR`?2UY`*a=o4rjI z)O#l#7t>le>jIFy$)7oo_sL9{&*yYy0?>xBzRgTh3ITg+ryBlOc9={2Z_G>aegC%q z#HRhc(Kghp#Ue-%YQKkLc)uFl$oM112gUsEi8x$MbncvOdxKVt>HU zJTH^)?2SdMRF8%AU$T$JtbaY91g;2L9hpIhRpyGuaz0PsVdbPCi-yM&=a#jU?kQHB zhpbye@84B1LD+g1+huv}C=Dj4wdvARE@$K)ARrbWB0g;aL0*B6SHOdPv#-KctBG0Y zJHL?Y{?IV=XNOXByAN&fbp(mN+Ro}!<7^&#Zzi>$&F}^2pXBfgt^c#L*$j`(17qjv{?f;Nb(xPA$@Vm|XI>e-=9lIdrmqJdIOi zox`d(n3R7_)ULVQL04B7tJYw4V-9toEJ%lAREKW1{N%bZ=Q}g$o!L|5*ZWLwWzAf? zcR{cG4szc^+);yo(QKjlyt1(T#&>%i94b&J50?1TFFkuR4VM4O1%SZ0!}egem=RgM z=^@2Y3RfSsU?ER2%4r^at){Y(p_w$Zs_G8}x@_pLPuZw>_goC!7te-W0az)JD1#Z} z*AM=j`zzdz^ezJ)<8I@PC|Ff;fi3F>b@Vu)>h+MGZ7b*wa5Mq?zlk3jS>nXvZB0!nN9&0sg^v}4JYS8Ntg|a+ z$m6_$CkG5n4>`QZzMjU$WEQp95n`p{gghzm@bC;wOhR-?hI)ih)&H)w4XpJ7Q05s~ zt*vei1YKF;lae6x3=D{WJw6kv6#SzZPP~;6At4UN(11`;$jci;CCe|Ft7<5k`8Rt&zXz}r zp}_z|_sHmI9#?`?fEHbn&VO{$QHWm#-lB>N3-#V!LAX4wpA)1Ye^ym70!2&SQ9iib zKj68!x!vzhit3Gr7g{~o!B8mVW=h@#7{GESRw7?k1Z)iC-QJw7rULI1fF<7p)L+Qs z0!HXHw|%_UGD3cS0^o~EsTxs&^?Dl~@EO3I(Ff9<`1A3V{$%H2C`i~bpSxP zi>s^R`-;-i($1$O9e2s4{S;P9;I}T|-I2lTQ4Q3tfQ5t0d%|J2VKD522Wr|KEZ5Tm z5Tdvv4Ix26aG(mn?o5$V%gvN@sb+noZ96CtC|+du@?gK_eoPCH|3hWSr`P|!-xG0c z7&93oBT|4b|8n2{He1m(J{|=qnjvaWk6z*6;d6_NLxEy4b^yZMyt%rX=>-IO?B{&}OW?msBW1%MgZe**!cux+rJmYP}D{M`UDgL_Q#(>Sra7z{rDd6rlzI#c*y94=!Y7N7+KEa)LXYrfcE zpW|JN2;fsd#8B{<9YBR7npQsd8%h8-zqxGNPDVxs3DCg}3=9fOOG!C68paa;9U-FX zRDgV`o*poX)-qb_i9do3O-)yK%eIa;2MXx)I^jVz-0I{w)XEZf$vpDT|5mKgb3g0 z*`I9G)YSCM%p~OGB9&F*q?BA>#56TE6KOU30Bj&79o-&~JEW-X2y})6z>)?kUUiO+ z%C@Hf&o~Xxcw{RURaRC`6ydO0VLsoV7uc@10hBSnqa%y-r{`G!ApGYCOAe8Og2Kx# zy-!v{gIfDhn=DZ23&2oHzP;B+1Io2H58ZfdA0GjG2L>~M-AN_@!a)Xl7f_vu3iJ#Z zvbjyKpl1|({4oGM4B!bd|Hwu(0s0Yjb#>r2g#J75`l}RtFPM9HeEfon+5?>ZH%e4j zH@BGR=nfQl9>E|)yxbo}MMc2lz?hH|K!AY}q)VD^_whl*CQgZSSESS;k`K6^Y?z*a~^X{ArfM@%?wA6bEvDp$;X~81^k%`IY z$pJiAU~IqvRzlYl?310ecuT#xhj&&Jt9JWSs{0tj{TKx`Bg6hZSk9tacplLydO zf3&Ig@{)A6fU`3N3f{qEvwZUK@Mr_wX#(RM%%@LglQY;8dtDVkMMhx9jJ9h$Ev>Bz zMasb4tF~R|gN237MbpM7W2It)L#Hvb^Up3WmfCk7-I)gl;47f;j-G`@F>|KD!^P&y z^ZJ_!@ULPr;otb1oBu=ETLom*c3qkwIPuKG~7Po7T2%&r-hI-&~_5hszpjOz7M@2_lz?rtI z2T;BWi;L&McPFA9Fc*E+~$vvD|Z8&>f1`y=#H@<-k|9 z6fPbT9)7h!F2O8Bwcg@)3GZZfZ+I;wI+oV#Fb^uc*Z)FFPB5tO#M(SB7YCk_ z>KECp>WuI-Fk{OIH68BH&dF)(?0m(@7#bJ%6nsF_W)Oj{{)aB~-H{o${`J!QO>ego zO8HHW+pMf35ojfQslUX%EZd~yfAR*n*HJ2dptPPqUI^@;$!&Z7m5>7ku>Kxs)HpnX zgHHPxkqyTnW};RjmkuxVf9LnQ*om{HF3QU=e|J9h+`-B8Q~0q_7`1jTYhv%ES{{#d zbXD+j#_Sj8Qu<+aFwlw(nxj{TzCqQ zs~I6u`Jpt2O+SXq$ramk=c-$j!RM5XUz0q@%H=36xt@3NMTd{zsD zXNZf-lQ&%Ydlwhy=c#&~tDdj%uGOz=t1gZlT#n(+_H7OpHG9Yym}s$?I%Sf3D3E zFT#(|$Ntm%(WjrFDV(>b5_u$u$J`knfOW{;sd*XdOs9?=;~e zF$y{P%VQf{cc*7ImoyztPUJIByw+yZ#`&R7O;$n9o?TqL7L=GMrLO)I%a4!$qX5{l+}6fXuh#FfI^+dFf}Fo2L-G($4p+#<^v^$@%X z*GqoF>Z^NL*h8pm_?u~Uk!^XdH=$1fOU|cW{Pj@!{_X7anR!G<<9+({>9mPMGJ&fN zEV)3z`2uoSZx&)p1)g{}aA`H)X36*0IyEh8IrKQYpWYB!#)j1jZ z-KWoKX|cgW#T%Qbn=-%J!^`Ui3h((pG_X8)lO_wUmv?P9Mz857GStE`rv{10lT10` ztIhLal%=?pE2`}t{(Nw8Wd$)i-S^%89vCR)>ArSV1YnS?s%krQxJ2!h?9gw~*6crk z_gXxb1q%lU2P~&l%8c>)`ud!9+sHUJ=l}lw4F3ZvfFB_@z8gq`YcEfY5DOzPazxqO zwgkU9@E=3`s6KT@UE_mZFVz{Q-O2o_TMIMP)2AQOulGSN ze*7$s*Okx>5YUw&t_o<)kx2ud)95c7O3TWpZX;T*@wl3w-Qld(Xb{3fg??oc8Xf%& zY~BD;izFS+9x`kWB!M%-_rswadvi#)#H_55FmnXO#^%Ve zf)6B|SN#XX@7AdHZs63Zsb}*PejH5Y+Q8ydL{#*&A#^|_w>6TazI(W;OBXT?F% zdZlP+Xi41^ELcHpu`}F!%X$IQ8X7l-@GmG}0&T?%YWrLN`<4#2*-#*Fv4&Z>6AJ11 zcBaaW5Sc4Kg_&&_4Ek=I!*OZqOi zE-x>m73lZ<5IWjiT1wv=|NR>UdboH7K$vFK_S~zJD-Vg>{yap9&ZoU8N91EQvBoqQ z!mj$^mo7l!(jwUqsLHZJXm8j*RHpAqaL+@Tc(734hR z{nH`r)K3~G&O@Z0^rz6I~HFKNXi!tSiWLhGEFA9wm;*s2Umq<$w zkW1=C1-$k1oEPqEYU;(TjD(k@q(XJK&0Q;31Gx{M*xK4|Oq2%}6wqB5_V@QoQ-q&3 z+2xYnAmicD%!fn0=IWUeRR^vQ*pX@PZqQ+JI<6=oUzFe1;b2Iq4JJMqRuNwb7?wG# zSKN}5UsP6Bs@J*1gTr01PTPYEiCBpAX5PykY|YPeHxOeRKp$3nlhJP6xH0+5#uqFD z?>&C(U0hsznQqqvZ6D0V$W)o&xWx{wrMFN@))5~CJPOAHKKJU*v6}*V{{xl0(YFv1 z5^{q4>i3K@0u1LYPj+?h5^{Z2&21hYj(|JHdNw%O{J-YndyqJAiz8`Hp31;Fwhr@}o%gZ|vc5FYNA2;1%d&=2I!KR=&)`(KZO6x!VXce^L~(S!ec znp+9~|GNOBm^pNP*NnH%E7gQ?-`CYnWa87mHKt8D6r(4tI-SoJ8~RF9M8Q@aX8KHB!XtMj2#7hO84`8}DxW2C4a*#UsUxdX49%+!PJRW}Q zXoWSMk=CXFzu|qes~Ps`(>kj-Khc(vDg5x$Iqne0%O_fjb-i+R6-^~}g$pa=^g6h& zqp#-3A3NFJ)Z=8d&BYJ!%2KbZe10qNb+kC?#m_#j>N*jdBaI_&S8T{2;9i(;e(uC~ z#xx8K-Abw9h3)3JAk4-t7CAUzyb9TR-&f7=^5CB^#2PZ8gG)&QJUtkN_G8bbPPC5W zukI7BxJ#o}v$Z7_zS!z?xMDl&P8D4wSTC_xZuY9MnwJcAvGA=7*A(AgJ_=G^=FTI* zHQ$*K*~hY8_;dKj!lEe;bnVzP>_9la9>H%;y{& zF{!Dq+S}WMIu_a3*m|q%%)})mY{pepRQi$y(Bq5H9}yA;KoW(zaH@dS-qWMEJzWDp z|GlSAslt`y_D0ru9LHK+9_mks5Jr>7W7akVg5qgtWo4C9zofG_Fq;}(K{=P9Ptc9 zN9XNj9-7oM+=|+YRLv!&qVii<&_RY3ttn$?xjgksetUa%IL7)-O5gLy88vieIEDgR zh0`Xt!%6}$S&ieg3xKJ8ZO~?OJRFJ?w2&eI1AyHST<(G;I}HsDpk4%wjEwOnhEVju zk@6itI6r=%5)cr8OQFF;+3K>}!$*$_!M^+ABMKzp+qZA`(bm#pJ3LEGO7oc9A60&`cR5h+w>lq?RvDMN|55X7S*5#KXURU24@GQ*&l%jF$f|sc)0z3z4c(5f38qNUVJJK@X*NUsJ^m+3q37_u z@mEeEZ-%>xbqxesal~~MsBX)o$MngG=(J9jG5IrFQc_ne)kTMxYA(4KRLpBPcTmcU zOPBknRg>jtZv9E=02BpdVW7YNotW5wQQz)=#f$K(Y?cm0QtP56xIDQ-yCa$w0y9r% zXJ@Z#Su%7uFjEeZJKn?h_vWY05pe?CIPky8V78L5j;|1MtlAG^(;76M8(9wjGuXBu_bDi zm{qC$OA9^&fYqOUNoAF%OAjAD1ZK?}{+r8?hQUbQGn4U>K5|Mbss%72f;R+3T2nKi zu8tQ*1lib_*zxb*F`()o4&hLUpyc`KnEq*XLFD;gj0K^a z8z<)1C2iB4N>&+GD|M}R%7~eZulHVB>{}PTAHt3lX@C9c@ax^5j?7;&kJV|&mR4(s zt<8&nm=IV^pDlT~DEJ=lcDX#k!>4mqc%4mwrBS_^`rhf(QFyG<@cMF|?%o`BWM<`K zOdST>pHI9!OFbfa?+B+27pzqh8msYywM?@;FWoa|(wpyp^F>f^_h5QGF0kWrQzQ2A zq|8~%yeQhTz`ZZRp^=feFoPU8Y!OC4x~ZsIP@PE@S`>uF6ij#>j6my zYq8`${ZSY!pv_01bvKy6S;ugm;NyQUFK30U#<4h(uZ5s5fHtj%jf4Ys1Z^8RHFeYU zbOJbZLP(}LKm;cX__VgR2AL`w3K8`W48T9o{oD8rUIb$k6S%q;EG&D(wV*(n z!l<>$7x$a5uRyLMGIH;68gDT&+liyt+uMWBLuhQQFT7z83?YXzQ6(izfTVqexPe&e z%{MKpEi@9Pi7a-#aw#e<7Lkx3fg*u?CqKGDC|TeN7Ze=)Mo{nuMC|_N1THWNFa^PF z>NC)?k~H*KgAN`b@q*IQAp`Uz>DPdG6i7zHJpz1yL)XS;ZSWg(ha&l&aMH8CKM5I` zutQI0r}#@^V)*uetHD9Cd|)@Q?(7Y)%Jaw$8xLdXo&keJU~-rTz|?!fCBzV0Oa@_il+KOyO6!@w@MiL8D1r^BtUv zWAk}kp#wuPRwBonHNOHO+)?5a_V*_l(rLIUYHFQ`163MoR;IEfJl(5(YYu6+SIY_9 zBbH{my=iTmE&s}Lpk(ChI}W03Ercb@8oj1gvLMS4ryv@e@YlRFu?{FKChII;;1s>CIg;W!dmlArgh%*=}h|@oG-(95D}125O>z1q9WB) zvP_z2ac={TyM$9OpX(Rv|SbgAt(#4MiXwLI5G@5dLLI z2%K}`*9a!s`j<#x!?l5P;osTW-$`#gk-mnDOWklHDn=$HrjNq8YlziG!k>sY0j_Lr zb~Y(gF|B8G^a)I&0FVX%KZYFH4uB^|)>CF^g`nyWip)c(t5<&x-dpZTk$#P&2f%Hy zT%>k)cPpTy<4%jq8ZsO-EM=mQk(Y0TclzJn9x*U5?d_uRgzLYMCP&3R|dS3trXJKPwa>$Ws0gXJ|Hy=O`;4AkZ zwTzBN?e1Ed&eV1RG)+fGmjp)X&`h9FQd05}w6ZG9Odi486%3~Di0v?B19BP~m;n?| z^FDw648DAl2n$hOj=<~w(@c}O;5b70@WE_rp|64LJ#1<9^q2dVH!rI6eQZvzvxP*c zk>052lF4G$S)?p1?7cvf6F8h$N#{4sRO{n+;c^IVvijBEtXd!G`1f76{-t-aRuYcN ziDLRf=7oz4&wplm4ZoERS7B`G#Q;pb^mEXKBivjlb|3Zh zGPG=z1`V0v+$I1OR7Gu|0)K=xn%Ci?SxVDWQ_tYd=9{2YAHZ4t`}gH$4m%zm9&E(1 z#xSa>tBV7u2{}s#XbNb*%)ws}jJ&?)=2F8=0~%6vTV;S)l{Gg5@_By`oHC6tZ_o`x z09prRc=IRo=CFYGRM0CG7yJIe=|0j#$HVE(?gw&q~K384SN@-ic+TC&8m zy29BU1xZeLg<7hfFj6&`*9eGo$tY19w4NS9fURtFWOV1(YP~jZv8laK=`5aD{e3 zbUEVc|7Zaa*DWYSX+^(S+u+?EotOwsObpD)G4fn4-LjER0h*$Jd2=WS7vSK(V7dyw zDHw*rzjJf$5P;B@0X~c{p}^+CkkB>EdUR1^yNM4SOuxm*^;P(600Zw65%m;&XaL&e zt)QRb`%FkWedY$X)oS(Hd9Y4L~;10qiJt<~n`es-6{S~p) zSuIv!F++Wy;+xA$GMjcREFEZ_#qvAC48M15x!km=Mv!y(=-fbWi=!>yhH zgwVgs0DK-yEWDrdnvycq1MMP#UR!(Kft=F@1~z1-;B&<3EjQCb4myXgw01x@0^tgf zk}!r-O?CBGUteM%Bmp3S4zjJIV;;;6`dJK1V~UE#gO!YorbygOOiV;1Oq>!FyG}ye zbbu8?_(yVux5?78o0}3alhi!t0aOW^IeGw;;@cV_OvGcDZikBs+S;-p+c*FT0>SN>M0Kkpbx$-`G^r-*j2{2<6 z;oo5v0tWCc%wrIrB#_O&e!YbP(-Cm7!G|}-ALfIp!U6 zr=V)JQ2E&DQf(bHR9M~EvJ|C-%h0?QmXusi0xOZmrl#Q7*fTy~v~vU0 z69}ifEC+RLCdP0%Ms?L*B;jmK3?c)6!7#6P3cuA*Fm-XsJ z>DS307^M4q`sP3MweinfL*f-Kc+WW{Nekz79(ev@FWwru$5O)@^}#haF-IvSDK%v9 zr!1jsK8s+ckQ^($L^RK!Z+Opj#`O!Fx6O+yN`O>x$wM>o1P@O}TDn)K$4uMs5}g+` za1J>WgU-(RH6MZW4DKiO^YcT{Nf2-VkE~`Ww>J$1To=5);3tQ~1nHQW!#&1<_(Ud6 zxR;Uy+VkT9QQm{st1-OzG4KuL<>i5AeO+S)z0oRoUI5ncgn$6&*)t8Zm)e?|gn;uS z`U_bp2Q0uPZipQo9_lmU13Zf$*K6^iza|FLT=Qa2#qF%bPxp^_flgkv>yr+0&fhwW z$<9f(+5?K}qpoff*z&?_429PUg6~)CHKfCU)-o2;@S1@&kP4#iPF?mTrKL?k9YJc9 zk(Kp;=FZgAw7<-Te8pZ0st6rv$GK3-!xJ%t?z2c+!=^Gqmn!EHJlGj&?o zW?LNk8|hV90VvRvz(q8b6NJ?KYjW;#cZdd_@fn>Ui7>E7bP}1|skqx@!$11O`WX5y*s!ni?JfumvJ#gNaUR<&kA1 zlReC7uBO1i0*W~lZLKk2rLLr)@Cft>5=-MFtsNZ=zket7%~mZP$!lm#&FZN3ruRTc zBtvGB=?&8d9-c%C2Y7@fgJmWFI_pY9vFGtHSU}AH(geb1eq-b3-ZKjFtS%J&EsjhX zyFtE`K52MI^hJ|J(|$n7o__ZR>jHV%LFWSBX3&ur8XCwokOw|HI--ro-b<4mTiISS z#k1L-)~MUCgG804HTYV)^4k3A+O+=?u9D@cC~4x=(}uW~>Jx(|>C2iueD9kKnu&z( z>;)fPjagy2)y!q7i_yIAD0=a8?5TvU`vYEV0!gi|SL`X*Z4{Wb1-QOSkvEq5vlDR< zhzsgoyvX~dXja>ql-GTJUT^NgX6?yHKQ5v}81Pu$fa&K&*4)@!OG>}J!%x~4n$+M3 zwdPh$lV~Qsa`dH~@>8pQetg!ghaYrlmRAdFY@dDK zXwt3K@u|m$_}F+t!89FLTknH-B8u>^@i$V+tF7&Ge$=NYTT|{&>!JeOG1IYj!`8lc z3_azrt15%T5B~@Z7VAlYwzUD7=t1+LsHg~?R7hf?7EnZq>NG82q7Eq=q;Cb#@csPh z2kj0i+9ziqfnSl3h`4*Y+#8gB|0=RW=Ji9_Y+*h=6tFd{Z)>{^Djq=Z-iU~N1KV8a zP$c8n-2MHrfZ@-Sr+_ep7A@FeQBGDC$zsq)q^6|>hlKorSN)oaW+4Ow)HQ2B)}VlV z)Ye`d$xl2)OP06;y%?g~ICVAevTto`n*;FMdTsDF3<8#xhQc2}A5{US(lFM+%Ycc6 zg$%$P9KY%xsTd-C2z1}`3kzSMrvn#9QbtBOJ(JyZ9yzO)1prw97z8mEz&XjXSrz7U zb3aLyQb_X1LHGjmXlr+O1gQ{9Nzov9#AW8?MWFu1hkPX4Yy9bxkc$fsoLxln{5c9t zmhj!$dU}8>#Pi~R19cCEfd&9b`(}5b2Zw357jz!b=f&gc{ zk48qmp`kdSVCX-Z0o!!AKiVHYxP!!jf#HF$aEpV%`=jhR`YhovudKMPZLB_WU%!qI zD_TdP;rTj^e>{IWl%dbi;Jdkt3#}|-Y923?JgipC%zje7LYI@UUSD+bCW^wZ!lLRc zaj@OH9_slMsXxaPp;O9i#6g3d4-`$RsnQmi(o594q6B?D+ZdwA-E?3u`oapYTnMA+ zaZkdyPs)ng{vtb%eHN=D55D`i$)Mg2yiScr?LCx6WDg&OXC0rNZ?TQX$V#iX!Sq3u zTRgakKBM(#sx6M_k_D;Fzbd&#q7hg(c8mMdH?=*YcMgp^C=U6qm2+lpoAG-zc4YNwvfPrF9D(Q z;Mp^3=dE8^%)r+7uhE_o%UD*%LCc76+0X_;1wuxK_A3}1UyzV^WoEtx)E&bM)z0pS z)bS9$r%cXJ9GeAyMl}qZenh;4fPy3b?I0+ufHG%UY3rdc@f=xL0ea8I@@Rbx4836( z*qE%usZ_5z*MT_-#pGUGcz6?BJv|dsDijJ1E6SLvyL^tFaKo0NrS9JiMNkM+4}ZUs zoI6A3wJ^azM)3)|JD~1tH3RoR3$6;8$Vy5cT3J~cNSr1mA#xHRW>d1WF+eOus^s`f ztq35$=1?CObdZXPs}iH~zd0_RIIta%x@r84S0C6XkAHQkwiZ(V>0A*x3D*CI$oBsq zU-j-ktvT}RdoeDu&`!*zw|f4vYaIA)Prx51OPiOcO<22@rKu~ka;HsoITu8MI$N)a zEpF>UtF4$m3A9X=Qth?{=6aW#IwLCGZP2rO@}}|C;=0l*9aB(pR^F%137>b}uak=-seO2#NyzgP zF3%Uma!0sQt@!(-+?dBDnzL0TXHYSjNDt_$A5SeyMp)Ea)IStbhuM^4-y7?+284=) zDJcd=YjtlG=nN{1Yl=fEB;uY4B~VKbt<+;d=fxwr@#Jy3CE)2o?T68gF1+CxLbjR3 zdQq(7K9n;OFLckuZS(Qps0mwTZ51Y%#+@0yv~zLsm?8@J-TX&6dwq4pdxGRW`;@%T`Y6v`HAQeJk%rGioer5tDBVaAi18hr_ZjW#k~%@=hQ9)JZ8oBDp(dOceWkoR*~zlS?-b&zA-?f6E7gpyVLCA6b% zl&pIq-Bk4hO{n^(%3K;$(W7mvYA=cvXPW)bVk4KgwcUra z?;kceLq$zxZZtUR57-FR*ncG`_f-CLBEz--_H5)O!z` zuRiaSDw!<;U44K4)Zw3%{(~fmN_&GM-ucJRNO?+wq8q9|!^5?WtEh6wyuyp&;Dz~{ zu`m&xJ8?=vr1JY+Q$?Leuj!dt7H008^=+rsKp&sv?~lspb;wW^t=XBiBA(6k%ixt- z;8dz_`7@aD&JPmx(srpkyvrc;%p+M`EjJT-f^gWGS~+6c*E!L)~J&Yc#tPVR-d@|UXAK)kM7@>Lpc zXtYwER#Qp+CV8&3)x!C+3koXnMR++ZsI&Med?ngJM(|6Ll3L4-QkGHdHFbW?V-%}EKbUS}HcDbqtJI8oa4>LO9HgW1S` z^1#4dGv}g+mraW~%o}%_QX#G$vwjtPJjf5TuhWhdRSjn;U}p6&wS21y@5z&ElUEXu zccO8W%y@TJgUe%*duvU<2eP_MTGo=H1ASebh%SGftt`B^-b%v>NR;okXLG0-;Su1` zIeuYXcNMEY-r_^uYdID@z&S0~9teI5Y6R)Dz9llUw2`G=Rwe8MUIDq~Wl2fAvIx>n z`=4m=pe8Hi-pUw|gd1X!N0V@|)?-86D!LOUyS)v3i~o}8`rckaI%LUhC;J4C5K#7ylhHal~B+{y)q;x*N_(c+vG#kOh-WQ=}FBV0iR6eb*s{@)N1h!^kY_ zc6d8SXN13t3M^khTGQExX(EuaUdnY9hH95AH`7~KF&cgU!R*_&#w;33+d5n5WGB$k zLXNc{^swr zgU~bv?Mipa?aQu`zYg5#HvZeo?m(M1BnF_CRCYs&{9L6H{5j*69;aOQ`EZr`yshMB z;s1RI4x%rJXSiBprL5=T$25-T{$VLP;R>dyHuTnQ>CNr)NjlR96s(9(pX@r-%m`S; zZHTwg=B1{pvqt3xYa|WYJCkK#>T#N6`_^U%+04#x8lgEF-O+CL{#>v>p<$!S+|xAe zS3~4K>$y$4x-dTS1oy9dT2|aMGP6~sYe8D-A~wv*G3{vywv>$QYuSbr>weEs79K0U zm(jb9T0;CFt=(JLXiH%ti}xifLsC^BH!`aDd)+2*6=U@94s233{hhUfdFX9qrk_uO zTd*xo;rWZxiiKr8FHqvYrXixw5cLF~xuy+XbflPkP-1AK%>JG0&pr}jS8Z!B3!hjFrx9vtN+z8T+X2ONOZK0DAfI{!pi54AthGPJNz2!^!84a3Q4td+krp zuTI$&W2%y+(uRY=Wc=zfu<+{A7LOgrA4JMt{i?CZ_zKbwdRFET)OpDS7Im|v%?sHq z=D4beu3E+{%+;+QIIXPKRNoAmKzBk@b93`N?0b8*BL~ZW{j#c1vGz3JmY~NOzEk2N zRl7BeC`f9;Z5Sa{3pBmp=nHF0lfGcso{}C#XL!-V@Zv4B>?F`J7rFV z1EZ8^)sy}yFX+XnH|z3X66Gi@u5Xq0b{B6hT|oUScLx#dK<VVQAI|H?V# zv~ewpXYdX!uGro9fhEg2P~t2%+g+c{3g$VtK7afud)zvWOn6#NRRW!r@-WxMk2yAF z%!&igN{+Zw!rxj}$oqO_?{rfL7CO$F@okf5T_sb;|cty*@cE)WIf) ztejjllfeT}IT?=?3D$|EfUM_DzGe*wj-%6}4M0-u66s#m)5BjI$lG1)h++4B< zqU97^2g^;LEI3*FT{qQSsf}J`{FJ$jQdURZmf! z=yqvGML#LrJf?QxQk}4>-jg3Vz%p1nXB&Ne+!K3YQ6eLqoc&i^{Bh!i^&>PiJj%q| z=_x+Uk0`Lp`+d`H|J({#g2LeEE?#y0qeg7tD~9wyPS*17_v?MJ)d#N2QI#8PiM5ly z1hE+@S$RtxQx-o*@7?$vkpf%e#&lC0qgL>3$0#L5xi>UwHZ$doK%hfs6@@Ezd6=`$ z1JBvQuIT;M*f`f6fr~j5yAx9{N8MvxxqlB^I!|+EHVFwfer_vg5PvCSKm; zAdUUlV4;1L3aTA1>dRY4Af@v_HoidV4{Eq?fNKFjqOgu~U&w_>_q1i_^}n4g=apnYmVu#LXd_G1IX~W!Jpufnu*8_lXDr)lV`p%4; zwKY>Hsl>;o?~gi>#lT-f`WtLcPENq!kZ`C@MDHZQxp|!+6~kjQe?H@U6z^ig4eT?r zJqTz~Ff3pIjYH(Te;-+I%y+%WaoKxZ3O?~5@lvj|OoM%dk|=noV9V2hfst`%cbA_u zbf;g8c9j*`)BVERNLzQ#4|)!`7dilgL`546jE0G(J!}Ny^?z`4?&0G4@F%~ATU33r z6bpMuVEBpbB-z9M;Wt0OS1(_p!V2eOD&^m>^okP2sCO5b0c7{l>0~hyaC;nJt6@{c z+_ImC7nn{j;EqAKig3Ze2rf?9%`~-(a*9^DTsW|pPQHU(NRTje#&br(y()NLkBSmO zqtOo3}Y!JoAA}6qM{J23P=@u`faGMBnnIy2dRoouD1fz*eJ+r=zzW54Vq#AKreu_rTmQ{9sMI(x z3ki)&e*kH+)78ZhEFR4*E;jw~x`PLstbo<(@3WzRuvMkp^cgJ42xiGMm`<{Rn1a-w z1@e{GcI{cf;GjHUFXAV}5wflYu%Bs03@))jcowz5M4PUwn6s0fe6n? zbivR^s{|VO9zK3MEXK8hmIyJnhC4@t0zxevSQ-b_2J8(A09l4T{_Oq*vDZ9@Po34KE9HrUyym8f)2M89A3+&Mcz|Iy`A-C|I5CuT^(r(pg^m88ggjA7b=W5Y4(;X4`PX&VewplEq>3 z7h(x*7Bp2r&&YsO2bS1TfXk!_HC3&1aYn>^us1;|o?8ivxY&eTN$`lMREn;OvH0ZUFmVgH*-a zn<|2H6Pr*R=m3>E7jAg;^R4%XJ|oQz@>j^A358cK7pHwd9bjULDrveBLcFD>WerF5 zMI%Rf%ooLpi9|r>oS!TwA_pqKSNaF}Q)H8yTP(x)@t3*@ zMzj;94+S|0pc9s%$~DavqTNL_}oc z)nMEa!(~H*a}-cT*L}asudBZz%^qGsa)r(MumZEoOLB4%jG}DA zKu1X*j&nO@#+dwEa1sruTZoBJ=Y(Z^l-1)w1(vVqLlzUGnV)zlrP|Ki*OdSK`NQ-# z8rB~`k%(vr($Zv7yba#~8h>@z$yd7|M&mFK`$}JwB;f9|o6n-a%H+q{-|j2xMl`94 z{c=P&DXOjFG?8NUDX{|sGI~IP!Q%r81te0U^(k-+ zK~(UP!GL)7@?7NCfPkCw%hEr7%;&tbUsms(y33|b8S{Y zNFj#+fPRI5AeLcS*T@Kwmjd|>daVe@d1+9vZdJ-Jz&0-EzXq}uC^sp-Idp29nMuKc zaAVc>#j3uroS+CYEThgKw3nt2E?v3yLTPVSVE_h@pAGD6(WkEe-VSL9vd4& z1ZW@t{JXu~1N&ykavgjNM2{O2(@p@(`~B%pjX^RFiwLj~mjM4{er-)uR`vmgmj|4K zIsn8kko~Z;hzf!9ikuvwY)PdOsBWx+Xodt{PDq3OrAA8r7v&}sk3rdrFkFz@dj|&Q zfh`*RsS*l_-KgZE3X_1v4>53oV=7LMwgc5^mO;!cOVPI%Cu|S9i|}ErQEV13r+(SY zLsfP&>_kHpxs^8Sk8w!FgPLzitIyBf^&1a;edk;H+Aei(7J@+n@onnwg8Z?7CR0ug$VSj%gB!p=24O2h~i-P%t z&JB(x2#Jbnh1=EEMO`)RQCrIcM-(JE9&G_7<^j(TxzEG-TC`Ye7gaal*ekv6r?94u zG!MWZf?yh7PHyOrcaIg}BGLtFxn3|kI5-%Om)w5#>=_&o(FSpK4cXm?9ZXVC{=gp$ zaQ+E7H@duMOjlPIJuEi2mmENc#9_0BEad_d*KTs` z&E3m#J$4+~7^BQBm4hoH25jt%zSb$OuRZ;Fr@}QDBSM+@jahxfx5B6!12F z*}1TCFj>FN`ts-F4Vl2AQJ0q?X`H5V%IYj++jW1M{{*%EMyd8OC8Ot-qOdDqSh|dO zGx)S3yk*QwoN%pU|Gqy;YZomk!wcU>j{GWi$2=&ZKBxT^5m*9xIfO>FR1!uLAxhGn z@tK%e#(e0k9yBfCDp_Sk5t8~69~}s-M$pA{M_e{r8i{DlDcG((i}q_XV|m}MhP7kw zNQNyZ&;6DG&u?X}s|9UMW?(w(O9|IwlbW{E=R+HqGVa%u)xtF1ewy{0e_HvoJ(v1} zPEyBTi`KZd;lvARt`pltB*z z^y>xDvZt=U;|@k-C=#7%Xp2m49~fx{r2OwOcnO=4)}TYgZStpk=3Q` zY9|)$L}UE{par5B3kTM^RL2E3T&KmIlKG}br30@$Q#Uy4bIJNy8pwV6izBW!*sav- zMV-VZpQsSe_|4F-ZnETchxUp?PDy~5>Dkyv!BZgE@Cs4`Ma6X1wMjg-JpqZk^`ho8_QJ~{Qxk2S$ zaq>J~Dnj%5`3F*zfZ^uOt0eYKe6}C?WZGAi%IQC=#TD}?T=VoO_0sPeeGSbexAgKz zw4Iu3iMK;J*0tme(EOHR#7K5pX0_NL==#8V^~H?8$(z84?uQTCPHg3Fop@QUDr?G{ zy3qU3He1jx(H~6Zwqr?)t*oz_UyQgol^>8AVP`pPmnv9qK6V<7BczTunE35JmJ+ku zv_|Q-sf6oDNi2DJ%z@z*LW6U+KP8QE)9G1jH)V5O3IY2`(|HlCpWuWk>OYt5ai&+bI+D~>36-CqgI1v#v z*DZG>8TxLI4ccZ2y?d(~9sB3Dr_=Ffuk-3v#xM8y$>V=#R;hYfJi@+cW_us%I2La% zha1Fdz0&*BzxVoKZ*|_18`Z)3i%|D(!Gtg7vMWnM8F#;lv^V$im4?g@bPGx;9Y&T@ zC%be}o(5c)MbUDra#$Uf{Ev0poNji%r$%W19=kKKyQF^(nzZyQy$K?0{nKf}YVYeI zyKIR)sB36+Wv<*9z7GfS%FqJc_(=0BUculZQ01`O97bG$IoY1A~e#LQ)<%k@eDA zkt1p&ja_{k(UqG`mN)eHAAEC=$EP#OSs9nOJYleVIS^g4@sg*(u4q}D=^`WN)6*5E zEfo0wqN{w@G`QFZLfj^jOpc{^n_DgqRwlY1xuFLZvEhVd-dDc6z18JSmPMDern^1S z?d2~_jM7T^dN|V+ADvQr@@ZGa165l>LtJ#m(-NBxC#FOGksOTL&e;=D=1SBiE0XdY zrH0u{W{OJDWhfu$11ZH7Wa1|NPy_m8N(dWFm@&0AQ>OVB>PpD5Dt%huW~ z8Q9j$h(#2OaK?Iy$&7m0qh9oRIQtMSNz9p4Xg;Te8`V zI+u!TIx}i8V>r1-rVTp z&KYZgl$Jvo!&P||xF7qa9ytqs{V1gHF`aQQpXU05QWkD{NUT=O_OsA1&xAZoBP#*R z-S2plO`hfFT!nOf!pSH`cLcIniDq5mCD0MU_MJ6bGmA$>xd@5U8=k<-J z$x=7^+Gx0e8lK{kH`Eu-0oTg*1jfE&2i0O*RrKs={DM^xDfTc|sGHPbHA_9G0sqmQ z|11@F{yKOKmawMDrK~-z`=PpOvY2Yys?%#FdoP~s=|YA^%<8>2k$+cqPCj_|a5s&l z#Pp}v8Zz^^Sw2n~nAO*a!O%ARDaGi{yV25 z39+hT$o-py|7HKE6-$i$HtRs%saS&TW{DYJiB_b!ET4h-tJ}Ey|uWVuNt|M&8ZxoY~D|z&Y@~dd;k36;2 zxMrKFpSS$UB@)SHqRYbjrbpcOfASy3$#BQt`u0xSVwT#wI=%9t(|;kSXpN-ba69M! zj&u7=ufPygUe53X(&eGcfPXP7&X}uasc~sd`2CUXv=FV=EsgJ>We`JWKXp(QlUw!T z?Od>se9`RPbH(>jJaK$f1wT1Iqhas^R^_AUZqH+?ttOr&LuHKO8556ScbX;AQ>PP2 z?J`1FKAA#PM@sGfVvJO`TqvT(?a}>GQ`5XDC)Va@RN`24gCn~A(nEhpAB|Zv#hqx! z^!;wYAV$-0n*4eE$=ZsA&_9#T?bLA7<~(L3Nt<|RcQGmET2m{Z(rMqwz#CFel6keh z>GBc{-PBJ@^XqAbAJTw^0 z7gpZT<}!*JsEAm@kHuf_tMb3Ynt=J<>wC|lU>Wsx4DTjqL`>F6qu_tt5AM-<@CN1k z>6K5N&UJWRf|h?i5*9nSKFK-s7Oh|N)5*xi+k6$RfxrP#z4 z7xTcTl`^RK;27<)B7LpR^>L+o8!LgpE!9CZM=@fLhJh#aM`27Piv{_TyHmo{6?OLa z*l#pFVPL}^P!IxQy9%XpM&A6?6i^-Ha3 zNM#}OBSz<8n4udFj&9`iGQJ+$99+7@jQ z3a~a_XmzYH5?jPIC7U}M`+&l;p%fX5-Ov1q233Kh(?}v-xkAv!1lO$2-`5kbd>AW9 zIq&`1w|A=|y}l*R_y_CvAMZuo7NadVA7D|A!TTZqn%#+9Qrq<6d-r0#sahYi>$sh9 zUzwWtT2_AFe}`~>!QJZ&*SIpx|I?oLhu7#Ww%JWPi{*nh;gXy$uJ;Rza{ehTE=2@1 z{6CDnbyQYc+cyd#q9TGID6MoO-C!Uc(hbrjAl;z?N;gQObPGs#cSv_ggEUAA=ephd zd7tk)<2_@XG5BL|gd14*T613Wmk2msLaB3@U}0`mDPdL-@qS-lP@?*uUfHboIf}tG zyF1^vcil&cnLI~S|MIEVzqoxB{ZHIy&ZGY-5pn+;_nGznze+?UNX|%pY&Xs)==URhRT=C`P8^f8=wj6@0 zhneb|o*}SgE0(jgo%e37ZA1!>ZI+|wq5!QJ#q~G9dgC6R!1}x7);1i!O zj8HOaMzE609rvHSENuvo%23DJSVTU`GPRO;u1*qAHf!MiYJDUBuO8{O~*tjJ-=Gw}a7!o&>rs3y0~at5CLzEe(;Hk234=(>7-52j!-;W#7xf3D;+XeTnS zn{fKz#YFPmd_F~sQhb){4s3Ai_6NKU!l*aI3SV$x7?`NY!jVI=DT#~cTSJJ8W%QfG zk=TmF7ZoM5$;iahX}^(zYAq^?tC)XGp^RVUlfufKR<*%N^(KDAXKf34dWJic#eGxE zp4`Rhpt(ZXjXj0Ln(yN-{HMIutrzZEMzvGkQ=5PMrg*$-52_co@+Pl-g-JtiFtwG# z!P0LR&nkBN!ST_)!B6YWga->>g(hHmSqgN6Wt7{4BdO((9|<;lHNJe51&tdxwE|v- zI8(1JzcO!;+YLR1)!?B0r*_)5C%UNlVrG&;gnAi~&UD15{149GES}H{S)o6D!ED}C zekY@@I%>E~So<9)F39nZwqjXjaf@vNK7TvB#?>?wN*}3N6Cq{yoU>Fb$Mic+4#}VQ z*8|zCQgn7V`0=s5yVQa20Ji1=vMeW+umMy77AQmO;;H~}5_Yp;v1(N;Z_gq++zx8v zB{th$4v)C;8~P|(TN|sK#VU+d?k*3Hy`CQ0+jAEk+I!5}J}|z@@0q~r8N8Jrn%x@j zK3O$j|MM~bwQH3`aXnScyk8H}8Z+<{KeD`hu4$yPzmPB%TsucB1!t zR80Clw!-Xwx&;avZ;+6e>(fV>A8^J6%ISU2s(LhSgDj}YCa~@Y_wOcNSaWFWPr>mz zB1)s<-9Zt-^2Vs=kFcdfw~df-@ehs;`U<_FlsM|<-__XCQFJa{aATfnh)eamZt(Py z4h34Ylg`7jIC-I3wQBJ$l4&ezLT&6*b)$D@1;{X}8#UWzbaAWVFRqxhMGf5FqWj{{ z(&6qEOoskp8f{YP{q7(UkIi*3s{#*bGLV7{nRv!PzyT&NU7Zao-+GxSIK$f;Ubs98 z98V+42r_k+#*{q4~k&^r?b#tPfy!msBa(FJ&-J6Xuzm?`232PS9jn(jPkMx>qj|IopYmwK#k};BbTr#Ses>7!u z6(m`b6TMT{JQ44=w1m5;w79-{|ME$M2l2GYlEn+{XDsEZgUFwxyA~3iGyP=qPn?oo z>d1anikRGcG0#XwYG|s0t`sEo?#WB3AH-=S7t7 zwVVdRr#j__gIQy%xc4Js6}(eRS!BwnkggZ5%br9xdRyU3C6_UM&PMIT{8yvieIWPtn0o6-v7+l*}ptAGY)r* z6Nf5VS0`LKeM=SicHt@fzF!nm=6>(Yg*PvLG;Dl{XvM1Pixwmr?kd8g1IcNKXv`wB z5jxxryeD*ne#ZHD`us1 z{t^yYyy`JGx-;5b6xlc)J!Ztz(!zh)dV8a`_CwRpeMfbVn}uIr(TVqUv`@@NR{y~F zqN^y>HI~qnH+`!UX3K?B#L1OrUP3@rz5l8@4%R}L$)0eGl2gm#_EhtJeyl@iN z8!Z6FD~dq8Z6qa2xobx!q2_DJBDI4>{?=!n&{UjVC83 z2r$^=>W?D!r1qscBV^9)HHK*KTbQI|@_mh+57dg99T!CK#}hSXmVlDZomS>3MZ2KLMP}O$-dw zh8E!wwTRGAuXpc|!D8Jyu3CbV1$HmcC_JdtlmV|XB6ftCSvGa1*svSn!D?zkj153N)Im8q|eB^tGz91q}jNpSj z%aS(}tnj8?Zvf2!G6JSYp!*QH0fy$gtr1Cdz+xiA3rX5he;{16$u_HaZa4gXd;tBK zNu+1p1-4M2d246^iNPvf$)X}fP>luqq$0cvOYuREuQP7Et4+TL;BDY%SBbcAo3&p! z2w2mR>jbnAU-cCo-()r7kXG{0*+wQtCuz4`L8)zhD zCJv?aZNEvHh}Eln4`)&lW`0Q-ZI*(&Mm3|uem)w{zdIf){+X;EED9qhCgj=I-kFp? z^_dbsaaW$4qP+S375?5{PA76+em;apA|fI|ND`KiASD-O#;Q}PIifZIYm(skFE)?U ztE06w@*|L^Kz`5t+o(Xr zJ|xJICy?BT2v;QL1U_6J*sFi+;`He#raSc%Kvvcv^Yil~jr4x{OCY9~2uB_EK~~z1 zlHs*eL4{zm2lo;~gY@fr2@GSlsfHYW2?iBM)#tisA#L25`l5_7XH}iP$6Nm@_+`k^ zpn38FAM(wr`_xg{l2ZMPGgdO@4^Y1So%HJpwer4XsW!&cYcF@-e!tltBjDcWmW~aZ zYWMz(V`(nZ$X>0$YNSJv@VQIFQ2+W+GE+2P$Q6(t{2#BIJ&5_@VS&s^8ua==uJ@1z z_|gZ6LY&VXnJVwnfV#t^B>1XF=TJmIk5V}ZsEZo*R-<2bIJ z8l$#?K@C<8ePd%$NJL6;p?j^b|Dm9O3F3pmHjl{e20;@NhuJV>9Y-OEJ3YN7h=UIa z2tbFd#pOnn-if5|Xj7BpS-e?#`bs6yxOjLHjqbN9HMNcSAQ(_9N9v!DKU_%Br2^*} z6&;;FuwY+OQ=4Jg1m_n8bOs=SpbMOq@Zb!NBO+821lUh?<%5A8IN-#I;CWoI{l z>~Hor;|Q`4d3VyhP}j_?3#hC40+C$soj@_KZEixEML&c* zr9HheMQo11AtLgufiH*Tuke@{>Y2BQh>e|{5Qu|sYtZ=n8&BtPAWkhQjJ&*YaF;=9 zvmb1vAY(}bQ$CX2&MdKzkPvwDn_;ns72+!*BclwM{OKC!2t-~VVl%FBwnao(XJ0x) zY#k!V0ZM>Fzy}K{EWiq*c>yKI3VwIEoa3eD44cf+Vjn z(Q(`X#{HSP(;pV6mQwZr7YGr&5Oe8|zIT`KI!?Fd1?pz*ljV!?<;7E9xeh&uHxrrm zmookt@}L4IUA;>)Ug}13wJTfA#|Ne6$}eMPcOvmCku~Gd`RS4GQS2~=I4`xRL3j#GKbN<}~%j7ODwqV+*DD($l zi6&CQMfxro>|-}bl!?@aBQ}qRDv08wj`8{ekB=9EXhlEC^sJnRs6)XGx=5c;YAwCT z<#$HEb!Vr$6TwlH$RF_>sM*Rt8CGu()C3dHc?=|QWsp#wc+im^6}E3zYMJiOlGyA^ z#9U`C+COmko@3npeMF_gi8{9C8qX{TP)RKA`jN%e00+*TBC;w}rF-VuAK*9&5Z*$0HnA#4cA}yR8F_Um&*pnS znO8^Zb8ewBc6ywaBfUrurza9IM7M3Zct1>E=&j^oX2|lXp2%jTCLSO2h$hDrshb^GNOP6$*nlJa5Z=0gI}_~Zlm3T zf-LVVZ#dG5^7oLD2}aCXm`ND!93C3YES#^cM<@9MfXY|5(RBQ-Jd5WoJ!Pt`^{qo7>$&2=tGLWkn9{6NOQ0O21tPUG(T>H zeNg9TxNS!_b6c+*?pjQ!0MG8lwMxa`^Ky7_qC*CTwu7D_ALUbEe^ax#QelA#y>YOn z$H{YA7Kdn&>vnsuuaa5d#0s}gHDh}!yLKi&)BIYLD1o8pOE)u;B&+Sm)b)qrE8A>E zhw&crF78510l_%IyX(dD^7v1s^zM?ndEVP8_sIM`RnPV_-j*V58Ts4FYwlnahse`) zmQ&0i_egI11hJ2M&nhq{q|5t5?qSRA;cRq$N9A^-O`5d2>gyZAy}D$g31_n(v7dA! z^4{tA13xM}J}jqRsh{^RiTsGrF`EZbSZau3{-D@a?p*}K=8_wtPWLL+h_M~{LxJ_#tj82!Cvkj+&Nv@^VgAp z!f9+8M&gqsNAj&%=`4?b0rkb1uu~sTS?ZdkNV=A4U;dY&I(=y~nnRokNO56#L8ouZ zTcqgiir#+LZHXzf0DyYD1+4Zb25v~Hd&qUYO zDEwFu90*`ik4yP2pO?3hWM;$=v?hN-4?Hi8!1^W<@P!xRPNR%#;7iKhL?Uofy@&ho z(I3T@reUuo_hzw)E4+IeX!^Q(4HwdnCwl>9yi|77Cpj3}eic+a*WXqb`P5^|k4D={ z3q>JykYS{^oecjhjxXi*laFrhzHz@Zx^s9JK|;!&D1_%*4N+UKiAYYn8Mx|t07xv# zKK#SoYpX+QmnACpN5AvQkoIo zYoolvAEVR4$(p6JDcIzz$zKX`DY?e(zHUAYMgZ!~_eXdDVM^L5o27W`@Sxz&ASS6J zJ=4bwtYmoy{KM}(*NF0h(#O8AgqZRn`d%87qXNkycGWYH!qaxOg6;dL-#UEnp0#Op< zXQJ#csw>0|i^O>}F=ZEnMmdmx(A4iABL+T_vXu7Tz(kaztCOCokrym7wdx~}7?h7Z zSYtO5Bs@S_!R>H#FV!Xc5-*Dn9@{FW5vSB3wi_N;50--88K2{`E(JLbi@(l`xBn>V1AfG(c+9Om`{AUA z+vh6s--C9C&pZUMw6|B0<~x=}Po#NeK7^f{9L^#u|Ek4|ysAWgi!t7tPc4g%R4^-< zzWu3oaC3iF1MAW<%0CAbXP_ZX1>tIl;Mq+$4nDp`WKui zbYmry@&lOmuGS~R*VK-@Tm^k?z4?X(-p_xH-1DKw8>M^``r zb|=>&8yk40`PlRjQeyyy*&e6Y=c!zhbal-s)nbg|`A(}2CEJ#Vr=4MEz9-!S5Ls2@ zVR*C};tl+W;B5DkS3oKwv|ZCYTuvZgok2Oa9aVb|(eszvH!Y?2idx0!ax}i>(CtD{ zNTgll?xD#-OF|e%Z0YYCo|nTf%sdaC@m8MN;JFP5TQKiVeYyGDeF&9JU!YFiG2-}v z_Wby<796*~1cfjb1g8sZdd^CSh~9wHcv>-R2x~)a_z~0shcj_ef~X$R=&){RPpMZw z^dfci!5(PEpg$(*A|)h3t4(oqH&Fx zX?*qr_L5QGfsSr}(y}?jzARsLjVzO7ggzl|j2}3EY9XzaljE5zt)cgk?WjvD4;FvE zR^MK2$gjfh7BYROf?*4+>-uTE(ArpEtV_X2m&Fx3Zf*Iaiy68R^1|HQ0GQfiV`CAL z!+#_4qZ!nmD=XuH?9kI3OGgj(@xXNjG%iuLU@hk?tzHm->>hnH0z%;M$ZlXL>c_wk zfqt{ZOu5*6mCxnXy9KVW}wLW89o5`eP8OC|KsHB~mX}{e1xk z#6KpQ-8WDKhz(dx)=Z@Vchs!OB~Yw7HQ}~$JpLLi$pNya6Fn#LU~OkH7iDCSj{#tE zmzr>d{FN5*2(v0M>mrA{MxT?Z1IlwV{I4r9qj}|~;u9L%>2>ejBuS6n3Y)W~iqi;N zzYr`Wgb_~9Ebk+Trew+tNpV$LhE+qXFONEUXqU9#OvK+soD~7SX-knQSnDuQEa%K{ zw$NxI&2cHGUz{OHQjI5kos!uc$>m-_c<{MHbfUxHn}Me7Bo~bqz5cS z{E1z$ylDhdH%-+aXmA8mX_ntj_DE>@nVGx;vq6v4A&(nNa8JUZP9#X)x@Sh46qBt) zxZ0~TP^)-Ze=69h+3u3$9%sAs_0LG??fstmbv}xzKrX;EE%USa<*MRT+}eS<0yUnc z-13+DiQWkK-qaMaMqmrhWCU`;Ha!gOg<#GNjaF;$O*y})I0#nElqq<{Iv=3#Ciop$ zvaP&nV)lNerntNd37^bMQ?8iEr4E$)c(fJw!rHvn+dKP)diEZGxKM5+pQHU2{@UiN zLyDKhtRU#gK##-U)HTYJ)pHR{QpZ6CCC)uNo+RdvT%lbub7D>7Ph*TqEr!(vrz)p9 z-S!u8IX!;hX_Ou_7rFHdxO{(0$JI~LFgYE1KfhGC_`v@O3mu>HGs8*cw*LCLMVJjW zwSSmE_^;NwF{r4G% zfG3~$;u#zvE)wk|G9J>4IxA7KxtdCfF%r{90f_*^U$B|}7l~i|I4^Rm%D8h-P2)pe zDpzq`8oVV-zH*DQSD1P=SIcfuEUd2lI6g$Q*wK<2UOA?O#~9OGkoay;rva?*e;=o= zoBa2Bhjov#jdHqdllg}~=6ecTlk+DkA1T&u;v%lS#wtjON4jl=je#0A ze#9kw(WgY36%kEKQDftLg=|Nfxlo?VIHy%D;FXkg$47ltSI?zR-J%s9EYHkUiF_Wn zlP3uO4Mg!XN1zwTK=v~@=R7V?jqj2&34OX`j%6Q?8E!uve8UaLhNdfK&>&$C9*Flc zgt};0Fx)UZOZkPUps_DmJCzj&VC`M6FaTJ4K49 zOS>pPZZDvGlC^k%*7A|5?OugzY0P1;PyWFcZ z7d=m23cq%qmri^!zYkUHm^|&L_6wXnz9i7=X2TIb5W_Be)ks|X-9~5N@6|^z0MOgM zFcVOlTY^y}%`i%kXUr0Fe%+vNLL;aDsczBxBT*8ut>oWu?>f2^keq_wZJ^9TLpfia zG9A$zx3Io`>nZs1M1!+#L_>NktIMHDPp{4|IW48p%zShqkHNS#?_V}5T8K`HOHcOw zvaiPI)RP`W!qTod!5(8#jfq;fs)$1R`CReGJEJO736KX0Eu_z+g~az4-3Tf)iZ^UW zT*yGQCuBuA^feg6@hd4#OyrB>6`4U-kg!Rg-}pVQ7A+l=-4RniGp{Sb6h-O1(QuV|t^KV0AIUK)jEIx*ydPE(05&t^thRGarq zU{9#>LV?tjv7FWUQI<%Ls_h3(Qq(W6Xso|+u9VYSd~;?knHc&jp_t`Rp^0=Wy`(zU zV`CW}YwZ0YMKF-}hiCjs+W2>}r&xE<$1F<=Dfsp5+-zPbXrHIf*&EKf2A#}xnZFUb z7ZmG9@@H~1a%I7D(iOdFP8_1@l&v3BNB0Y!OPw8qQ*8H038M$B61o8C%FlodKFw1I z5!b(7{zwKvO4IeRr?M|Iv=$B*LzPP5RqClSCi@RRr~dfpDu1qELNAIy!msQb`a2C;-DJ@Sk5^IqV8 zL;?>71yyvP-1hpqU#k8DkLkZi+A}AfB>GTbHu+Lw)gh=W#74m!tPTL*Mg0Y8nsT?# zm4R&K)7h&IDVw@|NlMENM#eFHvUd@kk^BMz>4ZrCDjcx13swn| zAV41OE$_DB2!L%Nln(3Oy)R+Psfu)dpFTZ$yfDYIthbi&?)&kd)j@B#u_rDJjlW() z;jYdrcR0EV7?4IW-BPIf`cItD``vz5xPQZ+8?lp|1Y8l};1&@vd@$f7 z=ep|H+}-&ePye@B-rY0MY?$$=(3d5qW$(#r|dks^Sy}CGy{`@4C=q{ z?yinv_EQqY+HC|Y2TtCvI`tmVO^2w{YzxHy;NxTF^S!>Ci_i*dp!I_ThQtGlf#@2m zhjmG#gPSY%zdqf*6RQ2ulAvRtAW2hgCI(;to7eA1K}9pco682ekhjLQLMXfy;#oq+lCeD@PFe59^TXdr+m=KfpX=nSeLf_2&(2JdpyT+BT7=H;kKlW_ zf4q@{T1nudp`TUQ>_bhD&6#f@Iq;RF<=4ZeM2t%*B2682|1;;{a%W<`Z3`0NMRiV~ zA+BONw{EWeY~1MF5}V|^a}{jKo^9B@*DByQ&iff{>PE@_K^YSJVKJ3k_v?*n4e=>+ z!XM3qFWiY?w$0K4o(JB~-DHLJr*+=IOCj)UFz9k~D-x;JO93>8&}$@9l{Jn(RjEF! z4Em)MV3|zPCh!Q$U5i+T!96kgc)E4Mlirr|dWqe8Gmd)f|G$b6T8x78@Ai%Dz2P@@ zei==6b;~>Z(OR7jmc2tsY$I(DcoaI+cjtRmtiutY4?< z{<6B$R;^Yj@Q^{N!Rf+`|2Ad9ZRO?I9Rb2-|NcomV^d-IDmtIV7?k6V4yNm`8&~4t z?Iy*fwuKXl{FJYtLI`NM|EnD7@-q)XqKT#lVJg4P%hPopkd7Ek|2&`&5`S>&F~!(+ znEwJ!&P1*v{Rf~6{R!;jz`(nf?{5A}x8ZEYZTC3X(RJi!rj{e(T76{BR_@yx>54tK zj{U`rPis*R^VRJBfnIN;>(kcu%{NwEDyea-AXMp_Rm8CT?NH{la+-Wg{Pof06H}oD zOt$s0#vsWrwAbfkmmMO_YF@sPzYAyzW2r(W$WxiG%-b`&nZ(D$0kypgR%) z4zB}+=C5poyS#?0T1NCs)}yCPa@wmFdg|*ob1!XBJMozjrc?A2U$@$MM5s+zJ z^bivMdmn_;J?KDbp)g`QFW+Ri`i`Ly996)H{z@}XSQJE}piV7S$MvC8_OuKhO|O0S z|Ea>@q+m+_-&7dkMh#kFLInbPg9A5!z*noFyzUkF>p5UsPx|IQ!tDu=21;mT(np;G zf{pmajn@)+ldej3HABkUje|X2_Cve_M!y6>Q6MhX?fU!F9OHiU|0d92zp-ydxT+HZ z3-kqF6$$`uAEgkC#Gu~cj(Yw-`{*U%rA_dK&{hnJE-07SHjX9x+uQ zSD8v4v9Ms!H-33E{+2@1Od(7(yE2c*DI~x#LhaO<&#T_<5)cy>)`6GV-sZa5YwH^+ zx%ADTZvh$9f`P*CdoDBOjsrG5+LOUEqEkiZ<4zO_iJ4!W+-*V~6#ji<1L+7mV$#OP z)yy(d+&}{Gq1x%KM=K$b>OkTPGgA^zpZ}r%pDmR&!n~;4GOaKe83|i0B=v<>6JCJe zFp`mtPoXy9^>;E`uIpeM!+xNAxG=l&41`MUo@aZvT`Nij`k0oK4R~XAGE5H5NV!P^ zN`1bxeUA|0`#Sd@Er9s?>0z=*Am;hVV~15Nhu-xraDNykneq3YP_AY#ss{nc;MJ9U z1u_7H81h`>#@^KTyMfwH(Frwr3{r>A`Pn!bZHA=Slwo@%{?<>7J5t}F94{*m6h)>FZYh0+ znn{FK7|c_!O>}{+Pli@vfmPM@ROBh)WVAP}-`x(}<-0&#U=+N{3wVd2ry*^%JLK z*QpXJQ+-1F1g%d2rMfgxPwJBSc21P0ZLhut-f4RK{W3W2E>52hVKT`n8!{HK1-j z*3dZJbh?1&b1sMAvL|j`b8drD<)uj!^R4Wm7*h zupP0)>J|2~3`ahbx!Hd$V<^YiHX@YQz2u_2_NN^Zk(6Fwm(}RWXNxGK-MXP$&E?Ti zESS2cvFVz5f(dA)XxOhTDCVM1a77~?@)$kKUWOka8SwtWQt_=;PHcG163M*C?v2S= zvNRbT55{8wq%ESp&Sd>h-6TAB=iN%>G;;)}v!t-Rxu(D{{h6EPT;IkLg8ykX$HPFW zM4P*xu3b9`iZ2Z{hV$~|gO{D_wwzYm?a(P1+4}yu%;qbn@>(1}xB8V<{Et@5Q?B|)1G@DFrEg;Ntp(`x_q1bT=e6RH^o#5Tk+jn z1h!{9qJp`gIEmQ!Q6 z$>XwFJ^GMpFV&-}3p!X0#i(5SKBB0^@_fTi<=w=dR62hj_~8;&A>tJUZLBv1CRq{& z($nzoCZ*v-i?8NA#Y~*`^O%cn9Suryh=q#;BY!L6MtQ0z#YeO?s%POGowmd(!s2|y zP)3<;+Sm0nCMTg)KTlEo;-1on5G&6+UIZAX%zFg;+Tm>aJ4Yz5-A{S$a|A54hejv( zXPXe+6gFpwJxqCk)R)oEt!a3z_BOGhmN(a`e)qK}8EbfNhHt(3WYY4fn)KRhy7Z#D zi#G>T4Vjt|`5y2^;CTuZ6jvPb$Qt(UFmo-8UJToL>T(A-C-pF{+nf-FWgz+1Dec*?!t5>rR zw`^&2AkWzNwWD4mHiqqgFTzN?}5 zGb`ZdKR8nv5FBhYkb=3lnNBTm4c)m)&});`+O zBJ6U!Ek039nXOTd2ep^1Idet&e06oRkxW~@nZ{ZkSMK?mG11X&y9>#$Qhm{)=%O;6 zZH1tMi)L&rS0%oo0Ugr({eHG;-MYyw3*}p>qMkAk_WjneumKjPCv0pnF>fB5rent#>&-Jo<;4!nd=dkRUEHUN+^;WbF;*~*~JecN(iMT>F zm88&ib0Y(%W#E#MwoN0k>Npe5YQTW4j)1N&IHE=ALYSRV1@c zoN|6#_j2e_Rj0V+Cc%8Nr{%)trZoJiL4wu0mfgImE|RKowG|;(WOI_KMN|y?IP!x; zMWuqze>{(%HHrA)D4p+*`iQZY2OA8 z!nXwGAL&X-kwuL#zf{cOsI~Iw`;hkh4ns|{f>Jwcmwhhl9k0IN%!l)CUdVlDYx619 zID0uh8TYT-xvw4llD3;uQ7tZzkqzd6v*TqRE0sJ!sl{bub%UXNshP4jC*?DJk<@D{ zS#OSz2<@*?-_PGeTFJRz=Qcp-WhIf}rJ4OMQiy#}Q~^A2TvW30?7br%R4K6cS8@>bAM{ zp-g5j%W0#Vy0!(7U_B`%Vt|6;aGLpjc~b%5nZ= zPXTNb5tF5xPn`FKPiv8V@0@K3PPzYhu}rwfqG_R6MjL`Wgihy zleY=z#{T&ZVTJV~Cj_!Y3&G9jYCW-O{>^UHU3m+=Ntltv@vUK;qNBX8L9Wu)^B!VN ze4Nsj9QS8{j_Kg<7gD50E?BqRC;NJq`a@OMxI}@ZBj?8k30r}(C?Qk0Ur-cNEaP^l zaCVah3&)SruT!dmb4tHccWI|F)tdf;G9z4r%M_yUWIA z3-vc$L4;MK+pxgdY)`9Y0B$WRN)6&*S}k7kcfXI0nukgrhf~O9%NxQAnGMSq9M3np z!%}kkGr$j;W)xDMB8vo`^w6{o4q^+{Q$eMJo~fxHbPyfy%%P+R`(a=FIYxEZnSBY_ zbV|7{ug&6EZe+_QdsI}e8`*A79=ZR7Ci}y+FwLFSF>g>^J{HF=H5-BOoU%7l8Rl=) z>MtFk%G*2B_yu$ifG&3F1E%17`J8fhH#BYCff@v)I4-ML(?8I{5|78IE)TyC6pnyI zoG{sg_YQ>eNJ;meEJf(5s;&3=Im>-Z5qR;@!#_5b2U=6ut!DYVkIrf!GE4Hu*H*ZF z4Ld@=fVk7Qqg#j zy(~+qA2xES=kmDA8Y#*G%}u{W{o_9_zQ!h}9xu(aNAdbFgjOb4 z7`~!gvy;UqMjE&u+AS-h@-<>H`Os0Y!}x~#($NF*vnAqr11ZYESBTqEBJN|+vN#G- zuyFOFzN4S=Z{+oE1Cq6zW2INJ7r&TT%d&*zOsy^*8E#tT5c8EmHV^@XP{P%NWW1Cg z54o;C9HK*~QD;v@PXPVEbR01O!F%8wA$rNC_Azv4rjAt^Dg<^RA?ngOI5=ELRa6!& z9Um}jLuw^yh36GXIK|$H^^>F|Pz(PW$LFhlk62M};k2Z z^sU*es?xLuRCg9iOG{fjFulPFV*?vOYTbNBh7kp|v38;Jml31OI$~lrg6ovxO|9uS zb#=c$;3FhOF^?Af!I16ueKYfWIbohv(;it~F#y?lgf zQ5F^{ANQS|kDPpJ(D<44+M@Gh%7HVa3-U8auXk%oI zB5V6^Eakc?n+opZ+8{KpEzOYT*dW z;dnK9dOmFcel~{9|E-J8MeiU=^Rgm}I zzA-qn&K<@0p(1Kcf@8Bfvec>PPs{VXo91-wZE0#YECm!tgBr6(S|*2nuI^KW(=s-l zdtVpj!pVjBExX4LpOr1pI&@(@!?L-D^)^XX=+8sXtF7U@?D+hgmjQv+OyDO;OUI{v z4%t`Z;|9~ZD4}V2R+@agykT*15~`{X`}$(3?{!%6P{QH?iKR~a6whVIlKDFy?nH%$ zORe_pKup#3Xr@2V5ZF4L9dQezS8U+oB5oemFSwrva#b)B5^n#fb;W#ij2!yqOWSG} z^qXsH`Xf~dKcVSxBKriTDgF<}f@ooSr@4DK7-lJ4sM-R-di!UVWWrW@v~6BKzJrrJ z{nJwA+~xJ58porat)0%`?Ce|W`FK!DQ~z<_3hm~tPMRUJ((9-KDQ|UWUMkPk{n_0! zZ2=~(!fDTHH1CwS!{u8+iLGvxeQwj3&SnOGpTEt_5(V?-MT1{qIkpZ}DXY1i7hOQ(vkrX74c+#(TX16}!stzb!=J~E} zOO{%Vll++l2D0bmWJyQlJR`W02j;38NurdLVc|LE0?<`D;}IY`AYBa$C0*UaQx+jd z(r13}DZeS(6^5l5yvYyly2|UZM|pywrjZb}+1s;Cm#gC+U`<(>FfYOwKV@fg7NHu1 zy10GZDpXv@$}USav(xJs!W)*wk($`7)}`wGjiZ=^xWH5Vjrz(CKbLY21)NixhE{kz zTca1?0QJzbzEG}brlsk74J+zZ$63W^fpqvr1|{(ef)&wC^9Z&-FxN)OeW842PSx*b z%TdRDj~4aSmt|GFAsISGk7-|5@z@BIXIQL~P`eBvewQ)f;m_d)fpkN~?61{}r(-(E znzeD(nhYuh{4jMXRA z=5I7Q&h?V<@c2Oqs>g}$J55s3t; zVREG)7jz3Q{hDAiDOJCAtq%%GWhTo#F$e7bjFV|^Nj$`lfMevv;n1L`fR2YYOb87vEs;3X!6;ZbIL%X(Unon6jPzE{?H#Hb^>hdlX@;`SPaz@A zQ+%n7vQAvJIE?!)=;gC7bA86Whu@IiBqvUZGwmP7 zR-Ufts?<4;k7P*&2uD3##K$~z5FTx$96TwZFqO{ov@VkaXQtZu<+(E?-SVFvx$fA* zrj*WZ`Af3arPSJ<(f{K|G%#_7>4O=0{YNTwzD9Y&Y~3TD+dM!`%VJ}*23J;|9*xbN z{0y)j&XU8;SC%X>Vmw*Tyug45&(E;iLl>H%6tm@T!L|nXk{zrQXkJi=1-W<2A23~h z{`wUNI~j~H?dg(Lv89Nv<7B~FaVaT86V*zu21Lh_KD_iNaz7eT#AEo}AIp|mRPOkC zW7yQmh4Xp?jCZ!H9{p=Sl5vPGZmNuZl}h9dg7GVzna_v0o~XUbz8=gFHrtvj z$hE$0Ay?skIQ1>v-8fMadJSHfdk5VW3j1WH>;%JRTCVAkCq3s`?*$tiG#~mv<|pDD zQ9j3dEd4!He(&`)>jVXL)Hzfn_ zXFA+&;6Us4zNPaPItP1tdSC|Q-5mYHJQnT5#kFXuMlF-jXRjrREfsf&E8yy|y3qVc zB8skcW5j$p0Apc>@}-E#iih(Fwia~D-<=as#H*KklVd3Gd0A;7g94ksyzIwT3;0A? zY%k@j80*{fcULzFPipk8`kc(NA1$@;(k;?fTBFvE^Ok*nW?=+6#xAmFNOta}$UQy# zf;93cR#5Mo`s@R5o_eXTNRXWZ(rN}^g@{WCGXrg|0mK{Yj^@R}v%op#eIZ?H*nNfc zkX4w!a7k-drnKWqWldv+t0u$XFE~6a47Gj$W&0rTg2T0_RSIq9%mU`HFj;5;Our=cajFj;A7LkJ{B zRQW0Ao`QKYOZ{kBR-eYeVkAci;ya5yI+^WstaT_3{>Aj~oyshM+=9NJJ{}+p@KL=a zyNb)#sn5T^z_>UY=XA6JQ`gFsR5;SXdZ?S~yEc z<%bU_jOyPTW@l#~Nt|%mJQTJPPVOnxxjE~DIk5}O| z-Z0q~?d~aLH1D6AP*wPhwRBKmdkb|SBP0BRTNRMczn8Lx%F4*-%vapc?YYDfj^Q5t z7iq~GX7m9;X&D*&>@qKEktJ{4mKA0#f)6C|)_^zvVJ>Eg=nUWOuKeK2(NI%Eq$`6C z%10tym?lN6;IMkB`&jde?+1+7XqblTa6**sbzJx9K|c)qV1lT#-|~6Ivd?`0wj&MQ7qUdjaUS!smykbxai2 za)nOE5uS}fcko^;gHy~5odO44M%yoKCvuD8b+=Evt{ri`&$uv))xX!u^JOsOJ`9VX zibQmWgS0tnWTF`QJb^$G3y&tK4BHuKlktnRVs3sa4R3 zfNwI>T{xf`aeLIfh6j3cIQvDV%`%df{2bgQQhqgu35?BSg>z3+asSkmC{!uZGUPK@ z(<|SB^O@A_0Cn$es|*MkCqYF^RnCRz_=WA-b2X|1f_&f2F{_JBVeNeR)E{QA#DBh5 zK+tO1zC``sUi#mc!_$R|&cmvw@2-gyBKtpJE@0@203YqA2oKH#bRJz(L-7wTN04BS zBVH3~1=ZYtBH`Wc?pw%nr>b{&`?di>yB}S%=`RhlN z)6?I|%9NFFJ+FhJ73-@;6f?!bf;<;f;V}Zl3p*9TdgQ~^JYqhUrBt$F>!^W!{RIp@ z=R(Xo_dlkgfwF{~hI{BR28v5aK=w3f(r!YOe&pl7x6*~^bggTQmlqPGes{yS zzIAY*Vq^@0=-^jHdM`Idaz%p(IB1mfKHJDG5K|lFcV>ECo__rNIgnbpj?QRhWjI?4 zXt108Thpfp6)FXJh~*S(7He}f9}!LrCf{qXaujI+9}%u~IT|o4MYyATpq~`!;#@y@cTTrr=BD#81g=9K zbRol~3LN~#Yn<6o(ecu(=IRio0)Xrer949c6w0g=YSx7SY-)+o@eqf<6NXr0fK8!9 znPzTV2wj-|rrvT1ze@g9o*fdhN*&*|3!{cWp+v@gvTiI)I$q}&@)`UA7e!X}#V0z> z7bh$_QvXj+*BwY@|GrgkDUnblR7NSvNEwkr;j}2C2ua6iDSJktx00C|k(8O4oJf?i z&QZvUV`gvJ+wXdMzy12F^mxv5p3nWc$93PI>k9KU6Wrb}ZLYq2&*MFLG(B6l3r2M+ zOkxEd72FGb4R3`(>aX>nO!~&pPiE1sYmTPqz3Zh@(gc1rzo;_rt&V5?mv#ArcN1h! zD|4*P2y%2pP{+B6#Qnz{{$<&w5C%fzEr%bTwS;{jx%I%ZiXKdq~_>jFJadNZd>91u4x4~yAr3d1Xp zdOofCnHDN;JwJ7G_8pchSa=L1BwgbbN8<5F1hq7nw|}JQ%Ro|f#cg~0cz~IYUmF&b zma5P8;7orqbvkYw9DAK)YMdJl1wo^4b(m-#PHR0aV89(NOr(E_CH$RP=h71XaG$i@iydPffyCZ?C@ zVclqY9{_q5$U)O|ja-9=*6^XtM+__f{v89)^G2U?hFd^t@SIkb`B{%=Z@PANlhZRY zV!k*J?YWS?L*`b?KLp>x>h)ah)vvg*bEK?lwgCKHY2#Lp?bMYk*6eh9ev0p?y}sq= z!s9asZz}mTPMWu7NWo8oSYOikm+pa^Elg>}`FX7$xqt;cJ>o$)DD&pw!~HT7acU!q z=%W~L2MW6l(ltuNMzrGZsx)Q|dS*$Q*QDrv}5*#z4&8J+ahp}A9{EFY=-h`vTUfhQOvbT zJykLzD_k2%xm6@mIrv?{23_eUtST3#HuwCxN+mM?xR=;$Q zes8Vr@Zb3C!1XfdU|UQZ3inHoQYVhaZy2IirByJ>9y)XdI^7Y{DaZuGpa-vd=K-^s zeq)9SBaEgPQdHQ2R)aq!b8Z!Su3H$Ri8J#Z+61i<^!$rpw_Q#uP5i~m3ljiI#T%o7C=^{kK;NV|+sP57;3y!@wPlNXn&HRM zsi`=y@;783a&mBJ10Bm}5Ai914K{8COY=PYud4f+I9lm37DlAO4?`J<-ur6?IT{oV z&|%)}p>A-}aCvFbqUpWlKK*P$T19o<^%niK9o-I65H}mMjB)Z&_4f<;3PKkLO#VOM z!k}mXR--S~)ml+`{0a(@cRGp*(*_6-wx9es^8HHmuqbK$lnbqxp!@^ud&mdCtn*xHwbAR)CFZFnu~W5k#AWw(Sky4U&o(I z+^wIz7rab`b#*$}%uvs%dVP|Mc!`fW_dS07dM9M(wUe~hxp#J61oVi6!0vl-Z zyFmt0Y#GP`GA5lj7w&!d@FBB5xO9kj9IRJ&U=Z*C$8j~zw4_)4aj+mtd8O>d1O;nF z4N*sYwFBmr?ikVEZ}CG27Z=5-{0mhWL72+U0|#n)kB;?L2@=d7FtDGwwxZ}M^+1D0 z!l8s2i~rK$_b*-r2mj5YI-~H~)^igUJwv`9-Y|X>gwv3$3s$3Je`YmvSqld zmmO(RqLDu9HX!FT!w@JW*)?Fn@Wy}=1=gAk3mN}DE!iM-lX~?;P-XMY3z-wb8@3%P zuQ;-=y*#m_JYta#uwO#$HN7nJ?>ft;2`7E6Rw47WZ$*@RMHY9f#5_HG!SUDb6#aKs zd(+u2W`FPQbUDgkbUKR4)lqEKU316uuGDkKajw#o#`qm&&F}wBf1x4s z&f$39?Z!o!(Fy01ciq&Zyu2QIW@hey>2iyKi`3%v9X%uKb|?tk4{!Zu`rW?O>UQfk z?W9QiR`_ZKg`{qW`*KbbyBQ#9Zo0HE3yLuhWF}Im1&jT*TiS?PqSnVQ1{`=s;?A#Q zh$l+gLBq;|@AFRjAnu+KlvciLt&P!2Q)cpne=GLvu9*wb7AnD;{70=You6eps(%TG(8XFu^R6_Z8zLhmT)$f!N5KS-aQ=>3V^pXPMly8 z)lL1!M)T32tadq`mP)Y6aUJg!AmjGYOH$txocoVmxi!2zs zG#`{%>0>uTxq$_`8?m?a0L#bWH&nSO2=HWNUp51EK4|<9G*JtSix3cz@}IYRTAiJu zlMDe2yO6i9zyZdI<|6iM(D_UV{Is>TeS!#5LyomDp=utj*u2{mlyXR49~1mZ0EXdb zdTW!pN^s5?p8)c?Z@Z1lHS#cg4^FeI$ct_?=LUl;X#=n9j)HpJ3t^TCqXL%^(s^ZX zA4Sy;#xb}lGNjme5813m(uX8nRDAv=O5t;_)Y%Am`p|vQ?||23-?;G;x~1my?0rbD zLkH3><=l^m(9h@SaubZVeO(iy!nk+u-k>5mCRWxS_+XpfN~McC=lkNLXF%dJ0|hJ4 zrb$4crt7JOVwqNI`_Ry0qkf0=p^_(j@*$`&2^0Zs+vkb%j^D#SoqVB_Oek<|7SV2t z^;0AcX~+#TKy)n@Zb?1x7)aSx^deuxX(3RmX<>Y3n}WmTtaA4itI$;?q7?YRwhl$2 zYv0^~6o3d2@AOV*TqzUbt32AJQN~r z+NT#Z)?Sj_o=Y5^c~&Bw$CO=GMo3$NSn=7*)Kposfb#yXiNh<1 ztYL0EKs!NGvEE~1l1Jvk5=|t|<>1+iffaOfX-$pw z>BcI+^AYs|2UrMP6Xnhigci(o>=gcb!hX0Z1~@DAir~ee3X$N)&sapNR`!>8h)>94TPk_)P{H>#>C!TrLSoQ`_nF!U`7@WEq zGspln+tN%2D@1h6f_~ztUJy9Net847b52(^WUD>XDUFrfwX z)s}$}`5|D!X2Wp~OUMZgHRg_!?yX!sIy%}`75ij7EIC;e(9F>kosM6>l+kp_u~xIE z%(Z>?$g*sWLedF?w}ARx2-g8|W%$Bb?3zx+k*KJsg%P*q4&Y+N6%~HO%gsOvh$)r4 zv5>CFx?1lSmYd2fA{yZ*2vtEap+Rr25;9z)$hk2hy)bIDp1348Im<+ga zu<@NYo1kjCd6mGm+Wo_E3|qd%h>_Suf|(c@8FR{OY7SJt*zjczB?kvLcX*$)hJ%Cb z3!Y;yL!Ui+92y$B(CCljZ~!qGV97K}UM2@0U&>ym#0Shv4>c6G72fmsH*9XwhKc(P z#lKQBKSC;WcD;@LuB@#r*sWiSzgEWCiZX55{fTO%D`j1GW6q`0d&T0M0EykWz5TuE z?-!4RHKInVD`+`3vrL#zdOsuk=*{k64x>A_1sDba{qk9>WV&XFL##OSlj+WzVN)kACj9v)4u z6(9v>9@1c@NKt+j&AJX<3rV{`@~#^)Z+WvP_Q>*Hx{yNr2q{gwtf|BgjdL&KXrHTf z<$XaFpmSK#v4V&rT5YPfK$Il=6<-ckn_sySn8@R8h-8XT;sSn1AQAbl^O;VQ{ijII zFrw@M74Z*?Iaq{+h4Bg~KcS}KMwxvwd_Ol=1e&uu_U-$YFexBD?+TCnHqSd|d&#QY z4&VIf5BcxZqX*r^GR|!aod7WNHpSVHjg2jQ8xxEvu**xwunuVK^{SVvi86_3^<$e} zt4$(1bt5NHkLxVCUGV+phq96E6f#^(7xKl}Ff_HfgC=TdvPj<@bK zFALmBIC7N10P!&;_5wnNsu@se1BCVf(8ZSEf*W!v$y`d&mSChKJLXrKAHXZ%d5%+z z!7lj@&xJ_TAh-Ksx|-eG4bM%i#`V}84=)A}E-lWlTf4S&e}G$5T@}LbY#WrE1i_*v zl_H_0riRiDxm!_9O-+KBC=wH7)Gg)vdU=OPSQK(GY!d1G z`f8ogubu%D-pJGdU6rkl2Rozb6z;jcwY+@1y`zTCNTb)1q9f`mLpcfx;e0mJ?tZ0G zAIFyXO4;glpKW09ii}K{F?1~U;ZAu&4K4isUDb8f3SxNs*MopmH*xHpzF*7Z-OqDvuRRS6ENnS4Rh2{@W>n_T;>gn5x7A0rO4bw_$fk zjV!Y+VVth_$LsY3i6&|%tL2HNsrkXB?}NU_F|Fj_;Lz!qO9Kwh%b0eURZXqrlz~a+ zK;zR;+Z}ncI529_n6(GIrX35j;}^eAb)wEeFUE(8hw1VZrVQ;Wpl2Hb!9nH4jV=8O zxfS&qRt{%a`Qb>Aj{c?9 zZHno)7&nSFf(L+U@P=fRUik~&c>#ls=W?|6v^Y4vN7gpydi(g0-?|kuRAL4NX$5Pq-aIX6k05JaHignt zcVyW=GIB4g-nTwysyDHz>=wAnpUtwIPuAWE2|K{A3h$l}0TJg%335a{E(!*Kz*nxF4A7uGdVqIxXmcv z#gRDV4ck|h zK>(stEZ@4h)m2C2KY`(IBb0Zc52@fXvk~#1d=LO&Q-NDF)QvGlhb-yJP~$r*uB@i) zh{)`lDLOQ;V9L2Ejyniqya!#@&#y>Ne?mY{BT9i^Ow1SgHya<{Y2@ZW{LI=5y@Yn| zWaHv0!8v#H>NuaOEuo>rn?c2xq?_g|M8d~P&P}_@x}W5vI3MYhQtb4%&V8KGH4Gkh^!7D`<{=&QV6FEynm%UL)y(EpG*! zQH(N$81jYRCBC8ibc?eeI#4u@q&Jsx*uk@u6z{j$#ekrzL<{Hwj?$yj5Jawt_hpSe z5}U!21LUs<@7H};ps148YO)FFOPGx1HYj}}!Vx8s;_`xZe^>0JgfO@*gG6v?H~1Vo*liAbuuzKX?mX%0-_~Nx+yc3bk!B~l@o(iJ_X{2 z+Ad6d6>BEL6)aXtnYpTIR;rYLD^D0S437@kOPykRYx;anHO>A8UAg(tyYT&v>{JhgP~zC!oWisIr2Y4K-F8=qLFXQh8F zE`G-OEuw63DmLo)c#$W;5pWp@S`pLcumkNU8CtH_`vUZ+`kBT(IZ#VpYHET4WHo_m=Mgaqm6;_({#C9H~xGl&m%Asl|rozDqt4hz#U9p&R zQ%3G49j)F-d3!Os>U+$|LSvJPMkt{Y9w-ED*kaH<@78?5fb6AqyQs+K?Hb+4UG=46|Z_C)HS6LLO#7v@=!l@>J`wmEQ@MY?x{lj@_sN25Kj(- zkKkHCv7j7V1x8v8czt4AYpTIawK>=jM?o+2*H_5OB-i%47%X%a;SRgC6E*@R(bZ}1 z=_w(iEM$%e{afVTvhwm$2#*Ln1W-XJQv*jh9$Te}!W8M&MB~UIKxbjtPLI$8A(Vsx zdqeHjtnbI`@Z5J@S3w$#!X+3nlv;gofCQMOLB=PajDD4QYyM$$Jv4PbO3eMUbY4nI zCjPtpp@QsDr$~9XZPQf859$sV!ADG7x3shr9$1<;SOJt4F8Uc(kmk|Ya&>OSUBng1 zt?JizqeF-n{T3SMWDsmD8^fc*SK{QzF2_Ty=s=Av&5!gX+fJXx5H=4`#wd%0d;V{A zTLhM~MS&A4DJg}|DHoiA-5e`;;LWIn^JkHfL_r31bv~@qd7YkX4VmPhbb7&NQl3sS znOONS34Xc|8lC*|fkIW#Z+#|xlM0$(f1!1jmLs}9`1;7}Sy@X83zda_SF{f;PZ@cD z0UksjNB_SS+VitVzCehCPB}BY9s#drXLnO;^QMD2Jt-uposKov5(2Rpk*a@>Sp*m} zd6I_|D1_AOHPNLk<$}<9Y`AC3YA>eEfzg&bIxFOB-*p?hlhBF zLV>R0b)P?j66l#ON2zzVvIhoDs;vH}-Eqrz*FWUFg>+J6St~o57HQ;c-uC8A96a8b zUB=Nj^g}%<54BUG;yF_|Q$8PAyk7Y&>qYINJLkSW@ltbM>4l5addme*ez{}|^++ug ze5#0-{Pt|F>gC0^^@`$2qjo1rB;(e5s}?dw82|%3?G`BEWw~a?jW+`!2jS z*Eli$m|%t!PJ;{`T@fW!<>)2TF?#GAT-TT@W262MkQ7`yRl7yy@rCsc!K4=L{(Su6fu4o^8mHbS2Wj5AH6Q6 z)+Vu@jy2&qiUMva{j*F<73UJQ9h?t-rPI6x#JpM`<(PQs3Zr#W!q3kufA_wLum;(w z{OzB2XUUal>Y=K4(}~?rr#0zTWTlzV=+!yy2@8kre+;?*WB7%w^y;q9*YT_Tm=qMiHDJO1mX7P;^`)$7w0z-e=_c^8e z#`^l4<}{8vdAP*cT>7!77X8ztMm^2%dt^H8Q9-8WpX*=-E>cxp!`#-N2Xbq+2u-Xh z`^dTH<3~dc-Owe@$5v0+Z9BSbo`n9rFMk@dUgtY2)07*sGNxgj1JyHcIBZRdX?-V| zyQVh$Y2IU*dc%67>XMHCIKj=*r?>BNXQ*%YR-5&!()K)N-p%nJX$2lO z({$yI*TVg|I*y(BgSjy&fswW+bA5l$apf*6)m1p}-&<|8_beG;NOe=89#{Vw@VIRi?!S%^RW7< z{!sM*3%RvkalFX={};jRzLKcIV;zUsSBf7`iEHt7#ws07_Qs*&|L!tI75b>8_*^lu zg8!bzh$(@FK ztbau9X|Jd8iu}Ec*s1;J>{T9+D0T0R6}`X8Tc4FCIqQtE{k`D6Z|Zd)>DcFo>t9`3 zNeV)@J^F1m%{i)oO|coR1E7 z3g=WPDSr9OSX6pBvQ%^rhjXcPPCe~V*`M(|+^+rjT+MyHNd^Xn)udxe+D9J#yrA7U T{M{NqU?3eob1YT)^8Nn<$b;lK literal 0 HcmV?d00001 From 94153cbed263d0a98076d5aa33fbe77868d1fd1c Mon Sep 17 00:00:00 2001 From: ffrancesco94 Date: Tue, 16 Dec 2025 17:32:58 +0100 Subject: [PATCH 4/4] Mystified optional Dask episode --- content/dask_opt.md | 249 +++++++++++++----------- content/example/dask_gil.py | 12 ++ content/example/dask_gil_distributed.py | 27 +++ content/example/dask_gil_processes.py | 25 +++ content/example/dask_gil_threads.py | 25 +++ 5 files changed, 224 insertions(+), 114 deletions(-) create mode 100644 content/example/dask_gil.py create mode 100644 content/example/dask_gil_distributed.py create mode 100644 content/example/dask_gil_processes.py create mode 100644 content/example/dask_gil_threads.py diff --git a/content/dask_opt.md b/content/dask_opt.md index c5ccf71..dbb897f 100644 --- a/content/dask_opt.md +++ b/content/dask_opt.md @@ -1,165 +1,186 @@ # Dask (II) -```{eval-rst} -.. challenge:: Testing different schedulers +:::::{challenge} Testing different schedulers - We will test different schedulers and compare the performance on a simple task calculating - the mean of a random generated array. +We will test different schedulers and compare the performance on a simple task calculating +the mean of a random generated array. - Here is the code using NumPy: +Here is the code using NumPy: - .. literalinclude:: example/dask_gil.py - :language: ipython - :lines: 1-7 - - Here we run the same code using different schedulers from Dask: +```{literalinclude} example/dask_gil.py +:language: ipython +:lines: 1-7 +``` - .. tabs:: +Here we run the same code using different schedulers from Dask: - .. tab:: ``serial`` +::::{tabs} - .. literalinclude:: example/dask_gil.py - :language: ipython - :lines: 9-12 +:::{group-tab} Serial - .. tab:: ``threads`` +```{literalinclude} example/dask_gil.py +:language: ipython +:lines: 9-12 +``` - .. literalinclude:: example/dask_gil_threads.py - :language: ipython - :lines: 1-10 +::: - .. literalinclude:: example/dask_gil_threads.py - :language: ipython - :lines: 12-15 +:::{group-tab} Threads - .. literalinclude:: example/dask_gil_threads.py - :language: ipython - :lines: 17-20 +```{literalinclude} example/dask_gil_threads.py +:language: ipython +:lines: 1-10 +``` - .. literalinclude:: example/dask_gil_threads.py - :language: ipython - :lines: 22-25 +```{literalinclude} example/dask_gil_threads.py +:language: ipython +:lines: 12-15 +``` - .. tab:: ``processes`` +```{literalinclude} example/dask_gil_threads.py +:language: ipython +:lines: 17-20 +``` - .. literalinclude:: example/dask_gil_processes.py - :language: ipython - :lines: 1-10 +```{literalinclude} example/dask_gil_threads.py +:language: ipython +:lines: 22-25 +``` - .. literalinclude:: example/dask_gil_processes.py - :language: ipython - :lines: 12-15 +::: - .. literalinclude:: example/dask_gil_processes.py - :language: ipython - :lines: 17-20 +:::{group-tab} Processes - .. literalinclude:: example/dask_gil_processes.py - :language: ipython - :lines: 22-25 +```{literalinclude} example/dask_gil_processes.py +:language: ipython +:lines: 1-10 +``` - .. tab:: ``distributed`` +```{literalinclude} example/dask_gil_processes.py +:language: ipython +:lines: 12-15 +``` - .. literalinclude:: example/dask_gil_distributed.py - :language: ipython - :lines: 1-14 +```{literalinclude} example/dask_gil_processes.py +:language: ipython +:lines: 17-20 +``` - .. literalinclude:: example/dask_gil_distributed.py - :language: ipython - :lines: 16-17 +```{literalinclude} example/dask_gil_processes.py +:language: ipython +:lines: 22-25 +``` - .. literalinclude:: example/dask_gil_distributed.py - :language: ipython - :lines: 19-21 +::: - .. literalinclude:: example/dask_gil_distributed.py - :language: ipython - :lines: 23-25 +:::{group-tab} Distributed - .. literalinclude:: example/dask_gil_distributed.py - :language: ipython - :lines: 27 +```{literalinclude} example/dask_gil_distributed.py +:language: ipython +:lines: 1-14 +``` +```literalinclude} example/dask_gil_distributed.py +:language: ipython +:lines: 16-17 +``` +```{literalinclude} example/dask_gil_distributed.py +:language: ipython +:lines: 19-21 +``` - .. solution:: Testing different schedulers +```{literalinclude} example/dask_gil_distributed.py +:language: ipython +:lines: 23-25 +``` - Comparing profiling from mt_1, mt_2 and mt_4: Using ``threads`` scheduler is limited by the GIL on pure Python code. - In our case, although it is not a pure Python function, it is still limited by GIL, therefore no multi-core speedup +```{literalinclude} example/dask_gil_distributed.py +:language: ipython +:lines: 27 +``` - Comparing profiling from mt_1, mp_1 and dis_1: Except for ``threads``, the other two schedulers copy data between processes - and this can introduce performance penalties, particularly when the data being transferred between processes is large. +::: - Comparing profiling from serial, mt_1, mp_1 and dis_1: Creating and destroying threads and processes have overheads, - ``processes`` have even more overhead than ``threads`` +:::: - Comparing profiling from mp_1, mp_2 and mp_4: Running multiple processes is only effective when there is enough computational - work to do i.e. CPU-bound tasks. In this very example, most of the time is actually spent on transferring the data - rather than computing the mean +:::{solution} Testing different schedulers - Comparing profiling from ``processes`` and ``distributed``: Using ``distributed`` scheduler has advantages over ``processes``, - this is related to better handling of data copying, i.e. ``processes`` scheduler copies data for every task, while - ``distributed`` scheduler copies data for each worker. +Comparing profiling from mt_1, mt_2 and mt_4: Using ``threads`` scheduler is limited by the GIL on pure Python code. +In our case, although it is not a pure Python function, it is still limited by GIL, therefore no multi-core speedup +Comparing profiling from mt_1, mp_1 and dis_1: Except for ``threads``, the other two schedulers copy data between processes +and this can introduce performance penalties, particularly when the data being transferred between processes is large. -``` +Comparing profiling from serial, mt_1, mp_1 and dis_1: Creating and destroying threads and processes have overheads, +``processes`` have even more overhead than ``threads`` -```{eval-rst} -.. challenge:: SVD with large skinny matrix using ``distributed`` scheduler +Comparing profiling from mp_1, mp_2 and mp_4: Running multiple processes is only effective when there is enough computational +work to do i.e. CPU-bound tasks. In this very example, most of the time is actually spent on transferring the data +rather than computing the mean - We can use dask to compute SVD of a large matrix which does not fit into the memory of a - normal laptop/desktop. While it is computing, you should switch to the Dask dashboard and - watch column "Workers" and "Graph", so you must run this using ``distributed`` scheduler +Comparing profiling from ``processes`` and ``distributed``: Using ``distributed`` scheduler has advantages over ``processes``, +this is related to better handling of data copying, i.e. ``processes`` scheduler copies data for every task, while +``distributed`` scheduler copies data for each worker. +::: +::::: - .. code-block:: python +:::{challenge} SVD with large skinny matrix using ``distributed`` scheduler - import dask - import dask.array as da - X = da.random.random((2000000, 100), chunks=(10000, 100)) - X - u, s, v = da.linalg.svd(X) - dask.visualize(u, s, v) - s.compute() +We can use dask to compute SVD of a large matrix which does not fit into the +memory of a normal laptop/desktop. While it is computing, you should switch to +the Dask dashboard and watch column "Workers" and "Graph", so you must run this +using ``distributed`` scheduler +```python +import dask +import dask.array as da +X = da.random.random((2000000, 100), chunks=(10000, 100)) +X +u, s, v = da.linalg.svd(X) +dask.visualize(u, s, v) +s.compute() - SVD is only supported for arrays with chunking in one dimension, which requires that the matrix - is either *tall-and-skinny* or *short-and-fat*. - If chunking in both dimensions is needed, one should use approximate algorithm. +``` - .. code-block:: python +SVD is only supported for arrays with chunking in one dimension, which requires that the matrix +is either *tall-and-skinny* or *short-and-fat*. +If chunking in both dimensions is needed, one should use approximate algorithm. - import dask - import dask.array as da - X = da.random.random((10000, 10000), chunks=(2000, 2000)) - u, s, v = da.linalg.svd_compressed(X, k=5) - dask.visualize(u, s, v) - s.compute() +```python +import dask +import dask.array as da +X = da.random.random((10000, 10000), chunks=(2000, 2000)) +u, s, v = da.linalg.svd_compressed(X, k=5) +dask.visualize(u, s, v) +s.compute() ``` -```{eval-rst} -.. callout:: Memory management +::: - You may observe that there are different memory categories showing on the dashboard: +:::{callout} Memory management - - process: Overall memory used by the worker process, as measured by the OS - - managed: Size of data that Dask holds in RAM, but most probably inaccurate, excluding spilled data. - - unmanaged: Memory that Dask is not directly aware of, this can be e.g. Python modules, - temporary arrays, memory leasks, memory not yet free()'d by the Python memory manager to the OS - - unmanaged recent: Unmanaged memory that has appeared within the last 30 seconds whch is not included - in the "unmanaged" memory measure - - spilled: Memory spilled to disk +You may observe that there are different memory categories showing on the dashboard: - The sum of managed + unmanaged + unmanaged recent is equal by definition to the process memory. +- process: Overall memory used by the worker process, as measured by the OS +- managed: Size of data that Dask holds in RAM, but most probably inaccurate, excluding spilled data. +- unmanaged: Memory that Dask is not directly aware of, this can be e.g. Python modules, + temporary arrays, memory leasks, memory not yet free()'d by the Python memory manager to the OS +- unmanaged recent: Unmanaged memory that has appeared within the last 30 seconds whch is not included + in the "unmanaged" memory measure +- spilled: Memory spilled to disk - When the managed memory exceeds 60% of the memory limit (target threshold), - the worker will begin to dump the least recently used data to disk. - Above 70% of the target memory usage based on process memory measurment (spill threshold), - the worker will start dumping unused data to disk. +The sum of managed + unmanaged + unmanaged recent is equal by definition to the process memory. - At 80% process memory load, currently executing tasks continue to run, but no additional tasks - in the worker's queue will be started. +When the managed memory exceeds 60% of the memory limit (target threshold), +the worker will begin to dump the least recently used data to disk. +Above 70% of the target memory usage based on process memory measurment (spill threshold), +the worker will start dumping unused data to disk. - At 95% process memory load (terminate threshold), all workers will be terminated. Tasks will be cancelled - as well and data on the worker will be lost and need to be recomputed. -``` +At 80% process memory load, currently executing tasks continue to run, but no additional tasks +in the worker's queue will be started. + +At 95% process memory load (terminate threshold), all workers will be terminated. Tasks will be cancelled +as well and data on the worker will be lost and need to be recomputed. +::: diff --git a/content/example/dask_gil.py b/content/example/dask_gil.py new file mode 100644 index 0000000..f4ce647 --- /dev/null +++ b/content/example/dask_gil.py @@ -0,0 +1,12 @@ +import dask +import time +import numpy as np + +def calc_mean(i, n): + data = np.mean(np.random.normal(size = n)) + return(data) + +n = 100000 +%%timeit +rs=[calc_mean(i, n) for i in range(100)] +#352 ms ± 925 µs per loop (mean ± std. dev. of 7 runs, 1 loop each) diff --git a/content/example/dask_gil_distributed.py b/content/example/dask_gil_distributed.py new file mode 100644 index 0000000..79b1279 --- /dev/null +++ b/content/example/dask_gil_distributed.py @@ -0,0 +1,27 @@ +import dask +import time +import numpy as np +from dask.distributed import Client, LocalCluster + +def calc_mean(i, n): + data = np.mean(np.random.normal(size = n)) + return(data) + +n = 100000 +output = [dask.delayed(calc_mean)(i, n) for i in range(100)] + +cluster = LocalCluster(n_workers = 1,threads_per_worker=1) +c = Client(cluster) + +%timeit dis_1 = dask.compute(output,n_workers = 1) +#619 ms ± 253 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +cluster.scale(2) +%timeit dis_2 = dask.compute(output,n_workers = 2) +#357 ms ± 131 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +cluster.scale(4) +%timeit dis_4 = dask.compute(output,n_workers = 4) +#265 ms ± 53.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +c.shutdown() diff --git a/content/example/dask_gil_processes.py b/content/example/dask_gil_processes.py new file mode 100644 index 0000000..a71a219 --- /dev/null +++ b/content/example/dask_gil_processes.py @@ -0,0 +1,25 @@ +import dask +import time +import numpy as np + +def calc_mean(i, n): + data = np.mean(np.random.normal(size = n)) + return(data) + +n = 100000 +output = [dask.delayed(calc_mean)(i, n) for i in range(100)] + +%%timeit +with dask.config.set(scheduler='processes',num_workers=1): + mp_1 = dask.compute(output) +#990 ms ± 39.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +%%timeit +with dask.config.set(scheduler='processes',num_workers=2): + mp_2 = dask.compute(output) +#881 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +%%timeit +with dask.config.set(scheduler='processes',num_workers=4): + mp_4 = dask.compute(output) +#836 ms ± 10.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) diff --git a/content/example/dask_gil_threads.py b/content/example/dask_gil_threads.py new file mode 100644 index 0000000..3736c89 --- /dev/null +++ b/content/example/dask_gil_threads.py @@ -0,0 +1,25 @@ +import dask +import time +import numpy as np + +def calc_mean(i, n): + data = np.mean(np.random.normal(size = n)) + return(data) + +n = 100000 +output = [dask.delayed(calc_mean)(i, n) for i in range(100)] + +%%timeit +with dask.config.set(scheduler='threads',num_workers=1): + mt_1 = dask.compute(output) +#395 ms ± 18.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +%%timeit +with dask.config.set(scheduler='threads',num_workers=2): + mt_2 = dask.compute(output) +#1.28 s ± 1.46 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) + +%%timeit +with dask.config.set(scheduler='threads',num_workers=4): + mt_4 = dask.compute(output) +#1.28 s ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)