From 49563124c7a1a9bf99966502b9653e324224fc33 Mon Sep 17 00:00:00 2001 From: labtob <70279295+alibillalhammoud@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:55:49 -0400 Subject: [PATCH 1/9] RL code refactor --- openfasoc/MLoptimization/README.md | 4 + openfasoc/MLoptimization/eval.py | 16 +- openfasoc/MLoptimization/gen_spec.py | 4 +- openfasoc/MLoptimization/glayout_import.py | 5 + openfasoc/MLoptimization/model.py | 18 +- .../MLoptimization/recreate_results.bash | 101 +++++++++ openfasoc/MLoptimization/run_training.py | 38 ++-- openfasoc/MLoptimization/test1.py | 126 +++++++++++ openfasoc/MLoptimization/train.yaml | 200 ++++++++++++++++++ .../tapeout_and_RL/sky130_nist_tapeout.py | 97 +++++++-- 10 files changed, 544 insertions(+), 65 deletions(-) mode change 100755 => 100644 openfasoc/MLoptimization/gen_spec.py create mode 100644 openfasoc/MLoptimization/glayout_import.py create mode 100644 openfasoc/MLoptimization/recreate_results.bash create mode 100644 openfasoc/MLoptimization/test1.py create mode 100644 openfasoc/MLoptimization/train.yaml diff --git a/openfasoc/MLoptimization/README.md b/openfasoc/MLoptimization/README.md index aa9d5cf66..868385648 100644 --- a/openfasoc/MLoptimization/README.md +++ b/openfasoc/MLoptimization/README.md @@ -44,3 +44,7 @@ Please note that results vary greatly based on random seed and spec generation (
+ +## version +ray 2.34.0 and gym 0.26.2 will not work. instead you should use +ray 2.7.1 and gym 0.10.5 \ No newline at end of file diff --git a/openfasoc/MLoptimization/eval.py b/openfasoc/MLoptimization/eval.py index 1a2164837..0c5fe7a18 100644 --- a/openfasoc/MLoptimization/eval.py +++ b/openfasoc/MLoptimization/eval.py @@ -1,8 +1,4 @@ -# Add glayout to path -import sys -sys.path.append('../generators/gdsfactory-gen') -sys.path.append('../generators/gdsfactory-gen/tapeout_and_RL') - +import glayout_import #training import import numpy as np from ray.rllib.algorithms.ppo import PPO @@ -17,7 +13,7 @@ def unlookup(norm_spec, goal_spec): return spec def evaluate_model(checkpoint_dir: str = "./last_checkpoint"): - specs = yaml.safe_load(Path('newnew_eval_3.yaml').read_text()) + specs = yaml.safe_load(Path('eval.yaml').read_text()) #training set up env_config = { @@ -42,9 +38,9 @@ def evaluate_model(checkpoint_dir: str = "./last_checkpoint"): }, } - parser = argparse.ArgumentParser() - parser.add_argument('--checkpoint_dir', '-cpd', type=str) - args = parser.parse_args() + #parser = argparse.ArgumentParser() + #parser.add_argument('--checkpoint_dir', '-cpd', type=str) + #args = parser.parse_args() env = Envir(env_config=env_config) agent = PPO.from_checkpoint(checkpoint_dir) @@ -60,7 +56,7 @@ def evaluate_model(checkpoint_dir: str = "./last_checkpoint"): action_arr_comp = [] rollout_steps = 0 reached_spec = 0 - f = open("newnewnew_eval__3.txt", "a") + f = open("eval_1.txt", "a") while rollout_steps < 100: rollout_num = [] diff --git a/openfasoc/MLoptimization/gen_spec.py b/openfasoc/MLoptimization/gen_spec.py old mode 100755 new mode 100644 index 613368afa..13134a015 --- a/openfasoc/MLoptimization/gen_spec.py +++ b/openfasoc/MLoptimization/gen_spec.py @@ -6,8 +6,8 @@ def generate_random_specs(env, num_specs): specs_range = { - "gain_min" : [float(14003380.0), float(50003380.0)], - "FOM" : [float(4e11), float(4e11)] + "gain_min" : [float(10003380.0), float(35003380.0)], + "FOM" : [float(5e11), float(5e11)] } specs_range_vals = list(specs_range.values()) specs_valid = [] diff --git a/openfasoc/MLoptimization/glayout_import.py b/openfasoc/MLoptimization/glayout_import.py new file mode 100644 index 000000000..03913362d --- /dev/null +++ b/openfasoc/MLoptimization/glayout_import.py @@ -0,0 +1,5 @@ +import sys +sys.path.append('../generators/glayout/tapeout/tapeout_and_RL/') +#sys.path.append('../tapeout_and_RL') +#sys.path.append('../generators/gdsfactory-gen/') + diff --git a/openfasoc/MLoptimization/model.py b/openfasoc/MLoptimization/model.py index 93789d0d1..8d560d12f 100644 --- a/openfasoc/MLoptimization/model.py +++ b/openfasoc/MLoptimization/model.py @@ -1,8 +1,4 @@ -# Add glayout to path -import sys -sys.path.append('../generators/gdsfactory-gen') -sys.path.append('../generators/gdsfactory-gen/tapeout_and_RL') - +import glayout_import #training import import ray import ray.tune as tune @@ -12,14 +8,14 @@ import argparse def train_model(save_checkpoint_dir: str = "./last_checkpoint"): - ray.init(num_cpus=33, num_gpus=0,include_dashboard=True, ignore_reinit_error=True) + ray.init(num_cpus=31, num_gpus=0,include_dashboard=True, ignore_reinit_error=True) #configures training of the agent with associated hyperparameters config_train = { "env": Envir, "train_batch_size": 1000, "model":{"fcnet_hiddens": [64, 64]}, - "num_workers": 32, + "num_workers": 30, "env_config":{"generalize":True, "run_valid":False, "horizon":20}, } @@ -27,7 +23,7 @@ def train_model(save_checkpoint_dir: str = "./last_checkpoint"): #If checkpoint fails for any reason, training can be restored trials = tune.run( "PPO", #You can replace this string with ppo.PPOTrainer if you want / have customized it - name="new_train_with_new_params_3", # The name can be different. + name="new_train_1", # The name can be different. stop={"episode_reward_mean": 12, "training_iteration": 12}, checkpoint_freq=1, config=config_train, @@ -35,8 +31,8 @@ def train_model(save_checkpoint_dir: str = "./last_checkpoint"): trials.get_last_checkpoint().to_directory(save_checkpoint_dir) if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--checkpoint_dir', '-cpd', type=str) - args = parser.parse_args() + #parser = argparse.ArgumentParser() + #parser.add_argument('--checkpoint_dir', '-cpd', type=str) + #args = parser.parse_args() train_model() diff --git a/openfasoc/MLoptimization/recreate_results.bash b/openfasoc/MLoptimization/recreate_results.bash new file mode 100644 index 000000000..bf36db6fe --- /dev/null +++ b/openfasoc/MLoptimization/recreate_results.bash @@ -0,0 +1,101 @@ +#!/bin/bash + +# this script will recreate the ICCAD paper RL results (using the default seed) + + + + +# ===================================================================== +# +# find most recent version of python +# +# Find all installed Python 3 versions and sort them in descending order +PYTHON_VERSIONS=$(compgen -c | grep -E '^python3\.[0-9]+$' | sort -V | tail -n 1) +# Extract the most recent version +MOST_RECENT_PYTHON=$(echo "$PYTHON_VERSIONS" | tail -n 1) +# Check if a Python version was found +if [ -z "$MOST_RECENT_PYTHON" ]; then + echo "No Python 3 versions found." + exit 1 +fi +# Print the most recent Python version +echo +echo "Currently using Python version: $MOST_RECENT_PYTHON" +echo +# Check if the most recent version is at least 3.10 +MINIMUM_VERSION="3.10" +if [[ "$(echo $MOST_RECENT_PYTHON | cut -d '.' -f2)" -lt "$(echo $MINIMUM_VERSION | cut -d '.' -f2)" ]]; then + echo "The most recent Python version ($MOST_RECENT_PYTHON) is less than $MINIMUM_VERSION. Please update your Python installation." + echo + exit 1 +fi +# Save the command to run the most recent Python version into a variable +PY_RUN=$MOST_RECENT_PYTHON + + + + + +# ===================================================================== +# +# ensure all python depedencies are installed +# + +# File containing the list of python dependencies +#requirements_file="requirements.txt" +requirements_file="donotdothischeck.txt" + +# Function to check if a Python package is installed +is_installed() { + $PY_RUN -m pip show "$1" &> /dev/null +} + +# Read the dependencies from requirements.txt and process each line +while IFS= read -r package || [ -n "$package" ]; do + # Remove leading/trailing whitespace + package=$(echo "$package" | xargs) + # Skip empty lines and comments + if [[ -z "$package" || "$package" == \#* ]]; then + continue + fi + # Extract the package name without extras and version specifiers for checking + package_name=$(echo "$package" | sed 's/\[.*\]//;s/[<>=].*//') + echo "Checking if $package is installed..." + if is_installed "$package_name"; then + echo "$package is already installed." + else + echo "$package is not installed. Installing..." + $PY_RUN -m pip install "$package" + # Check if the installation was successful + if is_installed "$package_name"; then + echo "$package installed successfully." + else + echo "Failed to install $package." + fi + fi + echo +done < "$requirements_file" +echo "Dependency check and package installations complete." + + + +# ===================================================================== +# +# setup and run the RL code +# + +# clean old files +#rm -rf *checkpoint +#rm record*.txt +#rm train.yaml +#rm eval.yaml +#rm eval*.txt + +# open gen_spec.py line 39, change the name of yaml file to train.yaml +$PY_RUN gen_spec.py --first_run +$PY_RUN model.py +# NOTE: this is done automatically when you do not specify "first_run" flag +# open gen_spec.py line 39, change the name of yaml file, and put the same name into eval.py line 20, +$PY_RUN gen_spec.py +$PY_RUN eval.py +# eval.py creates eval*.txt which shows how many specifications are reached diff --git a/openfasoc/MLoptimization/run_training.py b/openfasoc/MLoptimization/run_training.py index 781b5d177..0b1c28ed1 100644 --- a/openfasoc/MLoptimization/run_training.py +++ b/openfasoc/MLoptimization/run_training.py @@ -1,14 +1,11 @@ -# Add glayout to path -import sys -sys.path.append('../generators/gdsfactory-gen/tapeout_and_RL') - +import glayout_import #env import import gymnasium as gym from gymnasium import spaces from gymnasium.spaces import Discrete from gymnasium.wrappers import EnvCompatibility from ray.rllib.env.wrappers.multi_agent_env_compatibility import MultiAgentEnvCompatibility -from sky130_nist_tapeout import single_build_and_simulation +from sky130_nist_tapeout import safe_single_build_and_simulation import numpy as np import random import psutil @@ -48,7 +45,6 @@ def __init__(self, env_config): #data = np.load('./training_params.npy') #result = np.load('./training_results.npy') #self.result = result - self.epi_steps = 0 # design specs if self.generalize == True: @@ -66,20 +62,20 @@ def __init__(self, env_config): # param array params = { - "diffpair_params0" : [1, 8, 1], - "diffpair_params1" : [0.5, 2.1, 0.1], - "diffpair_params2" : [1, 9, 1], + "diffpair_params0" : [1, 8, 1], + "diffpair_params1" : [0.5, 2.1, 0.1], + "diffpair_params2" : [1, 13, 1], "Diffpair_bias0" : [1, 8, 1], "Diffpair_bias1" : [1, 4.5, 0.5], "Diffpair_bias2" : [3, 13, 1], - "pamp_hparams0" : [1, 8, 1], - "pamp_hparams1" : [0.5, 2.1, 0.1], - "pamp_hparams2" : [2, 11, 1], - "bias0" : [1, 8, 1], - "bias1" : [0.5, 2.1, 0.1], - "bias2" : [3, 13, 1], + "pamp_hparams0" : [1, 9, 1], + "pamp_hparams1" : [0.5, 2.1, 0.1], + "pamp_hparams2" : [2, 13, 1], + "bias0" : [1, 8, 1], + "bias1" : [0.5, 2.1, 0.1], + "bias2" : [3, 18, 1], "bias3" : [2, 4, 1], - "half_pload1": [3, 7, 1], + "half_pload1": [3, 10, 1], "half_pload3": [4, 9, 1], "mim_cap_rows" : [1, 4, 1], } @@ -107,7 +103,7 @@ def __init__(self, env_config): for spec in list(self.specs.values()): self.global_g.append(float(spec[self.fixed_goal_idx])) self.g_star = np.array(self.global_g) - self.global_g = np.array([60003380.0, 3e12]) + self.global_g = np.array([30003380.0, 1e12]) #objective number (used for validation)g self.obj_idx = 0 @@ -141,7 +137,7 @@ def reset(self, *, seed=None, options=None): self.specs_ideal_norm = self.lookup(self.specs_ideal, self.global_g) #initialize current parameters - self.cur_params_idx = np.array([3, 5, 7, 5, 4, 0, 3, 0, 2, 5, 15, 1, 0, 3, 4, 2]) + self.cur_params_idx = np.array([6, 2, 7, 6, 0, 5, 7, 0, 10, 6, 5, 13, 0, 6, 4, 2]) # param array self.cur_specs = self.update(self.cur_params_idx) cur_spec_norm = self.lookup(self.cur_specs, self.global_g) @@ -169,7 +165,7 @@ def step(self, action): reward = self.reward(self.cur_specs, self.specs_ideal) terminated = False #f = open("newnew_5.txt", "a") - f = open("record2.txt", "a") + f = open("record_2.txt", "a") #incentivize reaching goal state if(prevreward >= 2.0 and reward < 2.0): terminated = True @@ -247,7 +243,7 @@ def update(self, params_idx): params = np.array([self.params[i][params_idx[i]] for i in range(len(self.params_id))]) #run param vals and simulate - inputparam = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 5.0, 1.0, 16.0, 6.0, 2.0, 4.0, 0.0, 1.0, 0.0, 12.0, 12.0, 0.0, 2.0]) + inputparam = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 5.0, 1.0, 16.0, 6.0, 2.0, 4.0, 0.0, 0.5, 0.0, 12.0, 12.0, 0.0, 2.0]) inputparam[0:3] = params[0:3] inputparam[3:6] = params[3:6] @@ -256,7 +252,7 @@ def update(self, params_idx): inputparam[20] = params[13] inputparam[22] = params[14] inputparam[25] = params[15] - result = single_build_and_simulation(inputparam,-269) + result = safe_single_build_and_simulation(inputparam) specs = np.array([0.0 , 0.0]) specs[0] = result["ugb"] specs[1] = result["ugb"]/(result["Ibias_diffpair"]+result["Ibias_commonsource"]) diff --git a/openfasoc/MLoptimization/test1.py b/openfasoc/MLoptimization/test1.py new file mode 100644 index 000000000..ccb583fb1 --- /dev/null +++ b/openfasoc/MLoptimization/test1.py @@ -0,0 +1,126 @@ +import numpy as np +import glayout_import + +from sky130_nist_tapeout import safe_single_build_and_simulation +from sky130_nist_tapeout import opamp_parameters_serializer +#from tapeout_and_RL.sky130_nist_tapeout import single_build_and_simulation +#from tapeout_and_RL.sky130_nist_tapeout import opamp_parameters_serializer +import yaml +from pathlib import Path +import numpy as np + +params = { + "diffpair_params0" : [1, 8, 1], + "diffpair_params1" : [0.5, 2.1, 0.1], + "diffpair_params2" : [1, 13, 1], + "Diffpair_bias0" : [1, 8, 1], + "Diffpair_bias1" : [1, 4.5, 0.5], + "Diffpair_bias2" : [3, 13, 1], + "pamp_hparams0" : [1, 9, 1], + "pamp_hparams1" : [0.5, 2.1, 0.1], + "pamp_hparams2" : [2, 14, 1], + "bias0" : [1, 8, 1], + "bias1" : [0.5, 2.1, 0.1], + "bias2" : [3, 18, 1], + "bias3" : [2, 4, 1], + "half_pload1": [3, 10, 1], + "half_pload3": [4, 9, 1], + "mim_cap_rows" : [1, 4, 1], + } +paramss = [] +params_id = list(params.keys()) + +#params_idx = np.array([1, 5, 3, 5, 2, 1, 6, 0, 6, 5, 5, 1, 1, 3, 2, 0]) + +#[ 6. 1. 4. 6. 2. 4. 7.2 1. 10. 3. 8. 2. 12. 3. +# 5. 1. 16. 6. 2. 4. 6. 1. 6. 12. 12. 3. 2. ] + +#[4. 1. 8. 6. 3. 3. 4. 0.5 4. 6. 2. 4. 2. 6. 8. 3. ] + +#[ 4. 1. 8. 6. 3. 3. 4. 0.5 4. 3. 6. 2. 4. 2. +# 5. 1. 16. 6. 2. 4. 6. 1. 8. 12. 12. 3. 2. ] + +#[4. 1. 8. 6. 3. 3. 4. 0.5 4. 6. 2. 4. 2. 6. 8. 3. ] + +#[ 7. , 0.7, 12. , 7. , 1. , 10. , 8. , 0.5, 12. , 3. , 7. , +# 1. , 12. , 2. , 5. , 1. , 16. , 6. , 2. , 4. , 9. , 0.5, +# 6. , 12. , 12. , 3. , 2. ] + +# [ 7. , 0.7, 8. , 7. , 1. , 8. , 8. , 0.5, 12. , 3. , 7. , +# 1. , 16. , 2. , 5. , 1. , 16. , 6. , 2. , 4. , 9. , 0.5, +# 8. , 12. , 12. , 3. , 2. ]) + +#params_idx = np.array([6, 2, 11, 6, 0, 7, 7, 0, 10, 6, 5, 9, 0, 6, 2, 2]) + +params_idx = np.array([6, 2, 7, 6, 0, 5, 7, 0, 10, 6, 5, 13, 0, 6, 4, 2]) + +# params_idx = np.array([5, 5, 3, 5, 2, 1, 6, 1, 8, 7, 15, 9, 1, 3, 2, 2]) + +for value in params.values(): + param_vec = np.arange(value[0], value[1], value[2]) + paramss.append(param_vec) + +paramsss = np.array([paramss[i][params_idx[i]] for i in range(len(params_id))]) +#param_val = np.array[OrderedDict(list(zip(self.params_id,params)))] + +#run param vals and simulate +#cur_specs = OrderedDict(sorted(self.sim_env.create_design_and_simulate(param_val[0])[1].items(), key=lambda k:k[0])) +#2.69966400e+07 + +#inputparam = np.array([ 9. , 2., 6. , 6., 2. , 4. , 6., 1. , 2. , 3. , 7. , 1 ,6. , 3. ,12. ,12. , 3. , 2. ]) +#[ 4. 1. 8. 6. 3. 3. 4. 0.5 4. 3. 6. 2. 4. 2. +# 5. 1. 16. 6. 2. 4. 6. 1. 8. 12. 12. 3. 2. ] + +#[4. 1. 8. 6. 3. 3. 4. 0.5 4. 6. 2. 4. 2. 6. 8. 3. ] + +inputparam = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 5.0, 1.0, 16.0, 6.0, 2.0, 4.0, 0.0, 0.5, 0.0, 12.0, 12.0, 0.0, 2.0]) +inputparam[0:3] = paramsss[0:3] +inputparam[3:6] = paramsss[3:6] +inputparam[6:9] = paramsss[6:9] +inputparam[10:14] = paramsss[9:13] +inputparam[20] = paramsss[13] +inputparam[22] = paramsss[14] +inputparam[25] = paramsss[15] + +# params = { +# "half_diffpair_params": (6, 1, 4), +# "diffpair_bias": (6, 2, 4), +# "half_common_source_params": (7.2, 1, 10, 3), +# "half_common_source_bias": (8, 2, 12, 3), +# "output_stage_params": (5, 1, 16), +# "output_stage_bias": (6, 2, 4), +# "mim_cap_size": (12, 12), +# "mim_cap_rows": 3, +# "rmult": 2 +# } +# numpy_params = opamp_parameters_serializer(**params) +#numpy_params[0:3] = paramsss[0:3] +#numpy_params[3:6] = paramsss[3:6] +#numpy_params[6:9] = paramsss[6:9] +#numpy_params[10:14] = paramsss[9:13] +#numpy_params[20] = paramsss[13] +# numpy_params[22] = paramsss[14] +# numpy_params[25] = paramsss[15] + +# print(numpy_params) +print(inputparam) + +#{'ugb': 2233790.0, 'dcGain': 65.19988, 'phaseMargin': 104.0, 'Ibias_diffpair': 1e-06, 'Ibias_commonsource': 2.0736e-06, 'Ibias_output': 9.35e-05, 'area': 47939.75594998075, 'power': 0.000353842085, 'noise': 5.22616086, 'bw_3db': 831.1834, 'power_twostage': 1.72420852e-05} +#726766657990.63 + +# supposed to be 10MHz +#numpy_params = np.array([ 7. , 0.7, 12. , 7. , 1. , 10. , 8. , 0.5, 12. , 3. , 7. , +# 1. , 12. , 2. , 5. , 1. , 16. , 6. , 2. , 4. , 9. , 0.5, +# 6. , 12. , 12. , 3. , 2. ]) +#{'ugb': 21067680.0, 'dcGain': 52.27519, 'phaseMargin': 96.0, 'Ibias_diffpair': 7.43008371e-06, 'Ibias_commonsource': 2.21861111e-05, 'Ibias_output': 9.35e-05, 'area': 42865.81769998964, 'power': 0.00049207212, 'noise': 4.18722441, 'bw_3db': 27197.53, 'power_twostage': 0.00015547212} +#711356747048.626 + +# supposed to be 25459060 +numpy_params = np.array([ 7. , 0.7, 8. , 7. , 1. , 8. , 8. , 0.5, 12. , 3. , 7. , + 1. , 16. , 2. , 5. , 1. , 16. , 6. , 2. , 4. , 9. , 0.5, + 8. , 12. , 12. , 3. , 2. ]) +result = safe_single_build_and_simulation(inputparam, hardfail=True) +# {'ugb': 13847390.0, 'dcGain': 50.5001, 'phaseMargin': 101.0, 'Ibias_diffpair': 6.19173642e-06, 'Ibias_commonsource': 1.54070216e-05, 'Ibias_output': 9.35e-05, 'area': 43918.335699987125, 'power': 0.000449201424, 'noise': 3.90373916, 'bw_3db': 25231.79, 'power_twostage': 0.000112601424} +# 641119734161.4553 +print(result) +print(result["ugb"]/(result["Ibias_diffpair"]+result["Ibias_commonsource"])) \ No newline at end of file diff --git a/openfasoc/MLoptimization/train.yaml b/openfasoc/MLoptimization/train.yaml new file mode 100644 index 000000000..cb3b7d386 --- /dev/null +++ b/openfasoc/MLoptimization/train.yaml @@ -0,0 +1,200 @@ +{'gain_min': [17090194.99554372, + 10984305.211848216, + 20053504.423596017, + 12976189.157424614, + 24326470.29417651, + 16614758.394137407, + 33885991.90088244, + 24294552.22901272, + 28738035.56465131, + 11591339.368499167, + 31958488.46862089, + 15544024.80657728, + 15149513.215502445, + 27673885.800500393, + 11672764.583840156, + 33688402.14780255, + 11900587.877020324, + 29088955.227047775, + 16042240.156555323, + 25007299.829085357, + 11767743.741556473, + 26889072.117443666, + 21748637.263493054, + 25419452.390491217, + 13495849.113006288, + 12326608.274902595, + 34251301.280750245, + 11019406.231670735, + 18445667.912573654, + 12219465.357591916, + 34416600.18777928, + 21049504.871381804, + 10458351.194655038, + 34035829.88434639, + 32468074.14915852, + 15099920.586221404, + 33814614.38533154, + 33244481.40777875, + 22760226.36875734, + 26359270.520485826, + 29720396.749431696, + 16816778.99788832, + 32151724.84805751, + 22628051.710884996, + 30499285.1270553, + 15331235.774977118, + 18651884.112132788, + 21219532.025170457, + 31954788.070722405, + 28645349.07311203, + 22745498.624895595, + 14810371.165483847, + 24165763.531959772, + 20446463.677116342, + 12759637.451458758, + 12288730.679502226, + 15389271.247741468, + 33835826.03447209, + 22452980.052944463, + 12724991.202638619, + 20193964.10649318, + 28496042.89249976, + 16634502.535836034, + 20198266.08878302, + 29114584.114452228, + 25424355.35771987, + 31707805.090082336, + 17317077.1745918, + 32903494.929625683, + 22092604.260551408, + 26777515.956465393, + 19401036.555276237, + 34717286.83561894, + 13680125.737745302, + 21630678.164861888, + 33150704.03529205, + 18204307.62331588, + 34245288.27348, + 21138909.53099878, + 13185873.658424318, + 14418194.723765042, + 23246632.98301909, + 17887636.136720534, + 29654059.506156836, + 21025461.959095426, + 14244096.673632663, + 16830633.025223523, + 29737919.980511952, + 26853920.94271256, + 19657571.234978635, + 28781225.1670665, + 21099575.171396002, + 24299835.513481088, + 21555701.88809367, + 22217625.330910273, + 29419937.982774194, + 12353441.5186, + 16065924.94925332, + 14169462.631807983, + 30946980.400972273], + 'FOM': [500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0, + 500000000000.0]} \ No newline at end of file diff --git a/openfasoc/generators/glayout/tapeout/tapeout_and_RL/sky130_nist_tapeout.py b/openfasoc/generators/glayout/tapeout/tapeout_and_RL/sky130_nist_tapeout.py index a3d68084e..7ea31755f 100644 --- a/openfasoc/generators/glayout/tapeout/tapeout_and_RL/sky130_nist_tapeout.py +++ b/openfasoc/generators/glayout/tapeout/tapeout_and_RL/sky130_nist_tapeout.py @@ -1,5 +1,6 @@ import sys from os import path, rename + # path to glayout sys.path.append(path.join(path.dirname(__file__), '../../')) @@ -37,7 +38,11 @@ from glayout.flow.pdk.util.snap_to_grid import component_snap_to_grid from glayout.flow.pdk.util.component_array_create import write_component_matrix import re +import pickle +import tempfile +import subprocess +global _TAPEOUT_AND_RL_DIR_PATH_ global _GET_PARAM_SET_LENGTH_ global _TAKE_OUTPUT_AT_SECOND_STAGE_ global PDK_ROOT @@ -46,9 +51,10 @@ __SMALL_PAD_ = True __NO_LVT_GLOBAL_ = False _GET_PARAM_SET_LENGTH_ = False -_TAKE_OUTPUT_AT_SECOND_STAGE_ = False +_TAKE_OUTPUT_AT_SECOND_STAGE_ = True PDK_ROOT = "/usr/bin/miniconda3/share/pdk/" - +_TAPEOUT_AND_RL_DIR_PATH_ = Path(__file__).resolve().parent +#print(_TAPEOUT_AND_RL_DIR_PATH_) # ====Build Opamp==== @@ -451,7 +457,7 @@ def get_small_parameter_list(test_mode = False, clarge=False) -> np.array: # if test_mode create a failed attempt (to test error handling) if test_mode: short_list[index] = opamp_parameters_serializer(mim_cap_rows=-1) - short_list[index+1] = opamp_parameters_serializer(mim_cap_rows=0) + short_list[index+1] = opamp_parameters_serializer(mim_cap_rows=2) global _GET_PARAM_SET_LENGTH_ if _GET_PARAM_SET_LENGTH_: print("created parameter set of length: "+str(len(short_list))) @@ -571,6 +577,7 @@ def __run_single_brtfrc(index, parameters_ele, save_gds_dir, temperature_info: t # pass pdk as global var to avoid pickling issues global pdk global PDK_ROOT + global _TAPEOUT_AND_RL_DIR_PATH_ # generate layout destination_gds_copy = save_gds_dir / (str(index)+".gds") sky130pdk = pdk @@ -587,14 +594,14 @@ def __run_single_brtfrc(index, parameters_ele, save_gds_dir, temperature_info: t destination_gds_copy.write_bytes(tmp_gds_path.read_bytes()) extractbash_template=str() #import pdb; pdb.set_trace() - with open("extract.bash.template","r") as extraction_script: + with open(str(_TAPEOUT_AND_RL_DIR_PATH_)+"/extract.bash.template","r") as extraction_script: extractbash_template = extraction_script.read() extractbash_template = extractbash_template.replace("@@PDK_ROOT",PDK_ROOT).replace("@@@PAROPT","noparasitics" if noparasitics else "na") with open(str(tmpdirname)+"/extract.bash","w") as extraction_script: extraction_script.write(extractbash_template) #copyfile("extract.bash",str(tmpdirname)+"/extract.bash") - copyfile("opamp_perf_eval.sp",str(tmpdirname)+"/opamp_perf_eval.sp") - copytree("sky130A",str(tmpdirname)+"/sky130A") + copyfile(str(_TAPEOUT_AND_RL_DIR_PATH_)+"/opamp_perf_eval.sp",str(tmpdirname)+"/opamp_perf_eval.sp") + copytree(str(_TAPEOUT_AND_RL_DIR_PATH_)+"/sky130A",str(tmpdirname)+"/sky130A") # extract layout Popen(["bash","extract.bash", tmp_gds_path, opamp_v.name],cwd=tmpdirname).wait() print("Running simulation at temperature: " + str(temperature_info[0]) + "C") @@ -652,9 +659,9 @@ def brute_force_full_layout_and_PEXsim(sky130pdk: MappedPDK, parameter_list: np. pdk = sky130pdk with Pool(128) as cores: if saverawsims: - results = np.array(cores.starmap(__run_single_brtfrc, zip(count(0), parameter_list, repeat(save_gds_dir), repeat(temperature_info), repeat(cload), repeat(noparasitics), count(0))),np.float64) + results = np.array(cores.starmap(safe_single_build_and_simulation, zip(parameter_list, repeat(temperature_info[0]), count(0), repeat(cload), repeat(noparasitics),repeat(False), count(0), repeat(save_gds_dir), repeat(False))),np.float64) else: - results = np.array(cores.starmap(__run_single_brtfrc, zip(count(0), parameter_list, repeat(save_gds_dir), repeat(temperature_info), repeat(cload), repeat(noparasitics))),np.float64) + results = np.array(cores.starmap(safe_single_build_and_simulation, zip(parameter_list, repeat(temperature_info[0]), repeat(None), repeat(cload), repeat(noparasitics),repeat(False), count(0), repeat(save_gds_dir), repeat(False))),np.float64) # undo pdk modification sky130pdk.default_decorator = add_npc_decorator return results @@ -674,7 +681,7 @@ def get_training_data(test_mode: bool=True, temperature_info: tuple[int,str]=(25 #util function for pure simulation. sky130 is imported automatically -def single_build_and_simulation(parameters: np.array, temp: int=25, output_dir: Optional[Union[str,Path]] = None, cload: float=0.0, noparasitics: bool=False,hardfail=False) -> dict: +def single_build_and_simulation(parameters: np.array, temp: int=25, output_dir: Optional[Union[str,Path]] = None, cload: float=0.0, noparasitics: bool=False,hardfail=False, index: int = 12345678987654321, save_gds_dir="./", return_dict: bool=True) -> dict: """Builds, extract, and simulates a single opamp saves opamp gds in current directory with name 12345678987654321.gds returns -987.654321 for all values IF phase margin < 45 @@ -690,16 +697,16 @@ def single_build_and_simulation(parameters: np.array, temp: int=25, output_dir: temperature_info[1] = "cryo model" temperature_info = tuple(temperature_info) # run single build - save_gds_dir = Path('./').resolve() - index = 12345678987654321 + save_gds_dir = Path(save_gds_dir).resolve() # pass pdk as global var to avoid pickling issues global pdk pdk = sky130_mapped_pdk results = __run_single_brtfrc(index, parameters, temperature_info=temperature_info, save_gds_dir=save_gds_dir, output_dir=output_dir, cload=cload, noparasitics=noparasitics, hardfail=hardfail) - results = opamp_results_de_serializer(results) - if results["phaseMargin"] < 45: - for key in results: - results[key] = -987.654321 + if return_dict: # default behavoir will return a dictionary and filter phase margin below 45 + results = opamp_results_de_serializer(results) + if results["phaseMargin"] < 45: + for key in results: + results[key] = -987.654321 return results @@ -1122,6 +1129,43 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ +# ================ safe single build and sim ================== + + +class safe_single_build_and_simulation_helperclass: + def __init__(self, *args, **kwargs): + self.passed_args = args + self.passed_kwargs = kwargs + # create and run using a temp dir to pass information + with tempfile.TemporaryDirectory() as temp_dir: + # Define the path for the pickle file + pickle_file_path = Path(temp_dir).resolve() / 'class_instance.pkl' + # Serialize the instance to the pickle file + with open(pickle_file_path, 'wb') as f: + pickle.dump(self, f) + # Define and run the subprocess + python_executable = sys.executable + command = [python_executable, "sky130_nist_tapeout.py", "safe_single_build_and_sim", "--class_pickle_file", pickle_file_path] + #process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + subprocess.Popen(command,cwd=str(_TAPEOUT_AND_RL_DIR_PATH_)).wait() + # load the result back from the same pickle file which was passed + with open(pickle_file_path, 'rb') as pckfile: + restored_run = pickle.load(pckfile) + # if restored_run does not have a results attribute, that means execute did not run properly in the other session + # single build and sim probably failed + try: + self.results = restored_run.results + except AttributeError: + raise RuntimeError("\nAn error silently occurred somewhere before this point\n") + + + def execute(self): + self.results = single_build_and_simulation(*self.passed_args,**self.passed_kwargs) + +# same as calling single_build_and_simulation, but runs in a subprocess +def safe_single_build_and_simulation(*args, **kwargs) -> dict: + return safe_single_build_and_simulation_helperclass(*args,**kwargs).results + if __name__ == "__main__": @@ -1181,6 +1225,10 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ create_opamp_matrix_parser.add_argument("--indices", type=int, nargs="+", help="list of int indices to pick from the opamp param.npy and add to the matrix (default: the entire params list)") create_opamp_matrix_parser.add_argument("--output_dir", type=Path, default="./opampmatrix", help="Directory for output files (default: ./opampmatrix)") + # Hidden subparser used for safe_single_build_and_simulation + safe_single_build_and_sim = subparsers.add_parser("safe_single_build_and_sim") + safe_single_build_and_sim.add_argument("--class_pickle_file",type=Path,help="see safe_single_build_and_simulation") + for prsr in [get_training_data_parser,gen_opamp_parser,test,create_opamp_matrix_parser]: prsr.add_argument("--no_lvt",action="store_true",help="do not place any low threshold voltage transistors.") prsr.add_argument("--PDK_ROOT",type=Path,default="/usr/bin/miniconda3/share/pdk/",help="path to the sky130 PDK library") @@ -1217,8 +1265,8 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ elif args.mode=="get_training_data": if args.get_tset_len: _GET_PARAM_SET_LENGTH_ = True - if args.output_second_stage: - _TAKE_OUTPUT_AT_SECOND_STAGE_ = True + if not args.output_second_stage: + _TAKE_OUTPUT_AT_SECOND_STAGE_ = False # Call the get_training_data function with test_mode flag parameter_array = None if args.nparray is not None: @@ -1251,8 +1299,8 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ opamp_comp_final.write_gds(args.output_gds) elif args.mode == "test": - if args.output_second_stage: - _TAKE_OUTPUT_AT_SECOND_STAGE_ = True + if not args.output_second_stage:# defaults to True, so we only need to change in the False condition + _TAKE_OUTPUT_AT_SECOND_STAGE_ = False params = { "half_diffpair_params": (6, 1, 4), "diffpair_bias": (6, 2, 4), @@ -1264,7 +1312,7 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ "mim_cap_rows": 3, "rmult": 2 } - results = single_build_and_simulation(opamp_parameters_serializer(**params), temperature_info[0], args.output_dir, cload=args.cload, noparasitics=args.noparasitics, hardfail=True) + results = safe_single_build_and_simulation(opamp_parameters_serializer(**params), temperature_info[0], args.output_dir, cload=args.cload, noparasitics=args.noparasitics, hardfail=True) print(results) elif args.mode =="create_opamp_matrix": @@ -1277,7 +1325,13 @@ def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[ else: indices = None create_opamp_matrix(args.output_dir,params,results,indices) - + + elif args.mode=="safe_single_build_and_sim": + with open(args.class_pickle_file, 'rb') as pckfile: + restored_run = pickle.load(pckfile) + restored_run.execute() + with open(args.class_pickle_file, 'wb') as pckfile: + pickle.dump(restored_run, pckfile) elif args.mode == "gen_opamps": global usepdk @@ -1299,3 +1353,4 @@ def create_func(argnparray, indx: int): end_watch = time.time() print("\ntotal runtime was "+str((end_watch-start_watch)/3600) + " hours\n") + From 7e3a9ea61fa6fd041a4c7822c562599437b64144 Mon Sep 17 00:00:00 2001 From: labtob <70279295+alibillalhammoud@users.noreply.github.com> Date: Sun, 11 Aug 2024 17:14:42 -0400 Subject: [PATCH 2/9] ML readme update --- openfasoc/MLoptimization/README.md | 7 +- openfasoc/MLoptimization/gen_spec.py | 8 +- .../mean_reward_versus_step.png | Bin 50214 -> 0 bytes ...{recreate_results.bash => quickstart.bash} | 11 +- openfasoc/MLoptimization/requirements.txt | 344 ++++++++++++++++++ openfasoc/MLoptimization/sample_spec.yaml | 100 ----- openfasoc/MLoptimization/train.yaml | 200 ---------- .../flow/blocks/opamp/opamp_twostage.py | 3 +- 8 files changed, 361 insertions(+), 312 deletions(-) delete mode 100644 openfasoc/MLoptimization/mean_reward_versus_step.png rename openfasoc/MLoptimization/{recreate_results.bash => quickstart.bash} (87%) create mode 100644 openfasoc/MLoptimization/requirements.txt delete mode 100644 openfasoc/MLoptimization/sample_spec.yaml delete mode 100644 openfasoc/MLoptimization/train.yaml diff --git a/openfasoc/MLoptimization/README.md b/openfasoc/MLoptimization/README.md index 868385648..74d3cf91c 100644 --- a/openfasoc/MLoptimization/README.md +++ b/openfasoc/MLoptimization/README.md @@ -1,6 +1,9 @@ # Machine Learning Optimization Code for reinforcement learning loop with openfasoc generators for optimizing metrics +## Quick Start +run `bash quickstart.bash` to get an example RL run optimizing opamps at room temperature. + ## Code Setup The code is setup as follows: @@ -45,6 +48,4 @@ Please note that results vary greatly based on random seed and spec generation ( -## version -ray 2.34.0 and gym 0.26.2 will not work. instead you should use -ray 2.7.1 and gym 0.10.5 \ No newline at end of file + diff --git a/openfasoc/MLoptimization/gen_spec.py b/openfasoc/MLoptimization/gen_spec.py index 13134a015..1ca350646 100644 --- a/openfasoc/MLoptimization/gen_spec.py +++ b/openfasoc/MLoptimization/gen_spec.py @@ -28,10 +28,12 @@ def generate_random_specs(env, num_specs): def main(): parser = argparse.ArgumentParser() - parser.add_argument('--num_specs', type=str) + #parser.add_argument('--num_specs', type=str) + parser.add_argument('--first_run',action='store_true', help='Indicate whether this is the first run of the script.') + # first_run change the name of yaml file to train.yaml args = parser.parse_args() - - generate_random_specs("train.yaml", int(100)) + yaml_file_name = "train.yaml" if args.first_run else "eval.yaml" + generate_random_specs(yaml_file_name, int(100)) if __name__=="__main__": main() diff --git a/openfasoc/MLoptimization/mean_reward_versus_step.png b/openfasoc/MLoptimization/mean_reward_versus_step.png deleted file mode 100644 index 24dca66ae25c73078cdf964e676a756cf8b5fc8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50214 zcmd?Q^IzTn|398j=CV#UmuuPfs-@*+FYCl*o6F{E*{)^V=CZzL+w1vx{}JDQIb9x? z2ktlHdb@=wD@vgt5+FiAK%mG-i>pFFK=DIBK(@offv>oT^;Lj>V4Or{)ZxK@UhpQN z5D-8J8F3MHH~kYmfFEJstPhRe*B-;|C=|Si$VjBwD7KU}Pb6YYps*hVq-YfaIv*Js z0_C$XJk<
oQ?pX?e!$l|t5d@Ta3eUQ-0+4c?PLx%((|6qWvB)ddzYH@})&JNO6nPx8 z^D#EQ+6*C8jEW=^3r^uhF^2FyJq;+t!~;ALEfr_KJ?~2%vH6hIr3 i|F8KYD)Dz zr@r%~#2j1%MgjsB<*TI6%CZ!(i@Kxr=h3$3s}arY=x_zD#ondcyN nZmwWyGmuC xqeHljQ*w*}#FK`iu93g^?+jcT^4@QN#u%Zx zK_LUaZ%^y_TnEV=&jlE$`DlXoKwdgfsGsr%VIU;h4LHuslaRPI<-ESVocji2JY!$r zAYFj~{KJ<3I+6t}YWbM;x7WMc0TNF<_dp@Uo!)h~g;k%Y(<^W^Sq2fJS$1%_sOKrx z?=~M-)^QQ}+;36?meAsY0P_cv-%srZy(dHfi((I8>tMAOW_MjQ#eKNwK|AdVGzNa* zi3-6rf$4%~3lM@Mkp)_{dEBtq!Ag^mG+AI(_6 5Q(jCiG%}qqoAvD zl6sfFIkL|Ay)4^x*q|v9IaT0Z^n-{8l!Zuw57Z1p06guVqeKlhAd&I{E?u_5Xmnnr zI}x@Cz5N3(gV;#Ds*P8=BEmzCW2H#Gct`y*czgUvae4rVP_y8*M;lG&{#8#HGu >zv ~-F>9p#ELs*7hA zgElV!Hg;E31on+xB;%X%y75{;Xdj?3Yiw?G?7%)%ODNqF`UGKO7<@ee6ko}w)knXG zLlDC)IO6sv`1Sto!krFdEX4XEPuf%i_6f9wc5ZB$%0_&b3!INJQHL-DYDP1{KAAkm ze2{(40YA2BX*dseaHkR23JECq3zk4BacL=vIW?*8&8h1cQokLh8_AP$uw^b7GqbSY z+ud+}qf_~o38sAx)%$+nw*qgWogbUGa&HpTC$rs`r}ej}!$6|x5ZPL<=Lvv{JZT^` zbR1vLk_(@IfBVx}ONwGYn|Ds%k2Mb~{wlPm6wJUBL1%etZ@lG8!vO12nb49#N_VXn zV2)FC?x{2_sZ7|n%>uL`6Mb)>j=uNB0vna(kW=w=)oh5HJZb2VQ)%C~Lowz;7^=cv z@ `14qBCY9=)Li2IdO(6`ZJ?l-58f Tk{KtIjghC0AXAck zEZ1KFNz3|84X5dFz1e<%DPEk!7@2v5zh6#ULo96b3l=4Xp~mB2baM*C2iPgOg^P9* z($`a6@Bd1cB608a**--U6y&2zCm^<)o> Ma z?-u6BvbfrT$X87ASy90)yf^(`@9U~5HC?Mhj|yfQiUmCo3v*{+nQ09@H*6ANxn7dh z#{2bQ->lmYQC}u<*FU4G?KZ7VqDLLX?KT5l0Xe=IiO(+5;?J#Xk$*9VteqPvtjC%y zY_ 1Ca1XY2t z)X;; j7J=<^#l4Jr3LdZA$_SH%I z#V#3pSD3+h*`_g76UI~|9u~h%=|ferzmLQd8;QQ#CTtm&dt1k6&Fc;aCV7Pz1n zjE4n&X}YKRARXOj76x$=p%!!5Q6|>Vcv1pc2eG!r^YadbA}i zN<%1Kf82TnX$yuk86Fg|9)P u>d+NPRMzh=kMkW|L?9OrPY0%*z9v6jw3m1CCG& g240sE@2m1Uh(I%ZKxk>UYGD$}2)5@)S9gOgLiQY8w7T2d z9`S=+ oN|xyX>S2-8 zPWXLc=MUK<5!WK}&rNsSM+0^%O80)%{t4ankx#_=h&N(_l)BMZ_9#)CREU(sg>)=f z>oJ5Z^!Jg0b}L9taCgX )^G_I+GuqjxS@a*2pcajd|I>vy@P~?Fz64XJ&8)AbD=G zla-%xo9+dnBG@%{!n<^x0|FuN{8&AnbbYcfE*nQ8n^(gK=6QqN47YSG9lh*7JZt8l z_^qSnjc<7R10t~4>Jd!ErlI%M(KS0rIxaWFyPCq1RpCW(F_3AflP|1#8|$a z?!-;wrgvP0TPVKh=!~4RsYw_+xTv**A@3MRMtZn+5ce)?P{y#9PK`+&x45Ons|Aur zqTQDn &Uu}jj=ZSBq6ew>)p+L`=xG)Q9G!&S*Tlal~Llg2{*cc;;N1Zuva|)6r zusd->3jPA210?`&sN=5*VfULP3 ;S;3vjJ6!6(r z3@Xh3J&n8q&^!j4w3Y0UkZ%}5ySoQBmXcLUcpADEdOCz(dAm`mHf3f~lcylSD@R`7 z#PH~Xd(!!8+rto{BFM_*!oHi+4PI ZVaD8$;}S}OTdj=T@B+A z%8b9h>j{3HC(4d;0DTws_+s5HzCQ)xOF$BQ8cMlo;IrFC_u|YDqDip80pQ%=TuPu< z*sW6833HTm5O5Xd_$+ej(k$Nz? ((t!C!kW&h-uIq|t z%(`j+iytrC!v!H}3c7=xsAr%T9)DzmgDKv%?jAb0(?~Q|{)GzcUhT2@YBwWE$p%Bm zFA%Wek)xiL$w_?N+&aCW>M%%jfL!p|LG*-NUI}3#yxl`0e8OB8F+Cu-c*7Xvdk`Zn zHFf_+V0tA&46$P_ -@eF_WNC4P)SPm5a!)C~_>5VpU72y{o-< Y{{Vs+3=xQ`G^JmU2m6$A+GWa)PyLIOZs zd8LaX;pJ*Kih8p_B=U$K5XP;k+dRX;bkO2hXx{Tgn>^21ekh|%K+}oen_a;G;$azY zEfMP9A50+6Uj11BUhRfZmw>7}-=hWpjHvfJU0QI55;JJ_a4PZnUnb wcr zM8<=NpSd&^eQ$~ewWK01o->O+Av1V1$1N6as~n>6^oeF5U!HGr!H$X}34p;U-Grk; z(j1d5F?aytDnw%m7o7swvL7K#vV@vU4Xu!y&G5c=?Zf`4r5(B=D%u;NsLwVFvA8Zv zd-4e(d3QRb?&b6f&bQ@+ujUhp_qKwhW78-t06~SF4`D!^5MvsUJp}DOi)@NPEE^XW z%U}Glfzp-u;v$q>cN;@422e|%TYU=&EiQcsnjjyU`E5E+QIah!zaGHUje; VR-FBwu^ zCA!AJU0@s{;kBiHZQgU1W;RtB^c^}T>&G|~o2vuqj& &aB_C zHVwX~SWi0X%qXi3kaW%C^SD2Bd2)24qqu;`O_Y;8PT!~Er}hn=HOX8Vk$MDq7`IP7 z=~U{g#naxF&nV2a{LfAsMNreoJGTPul1NcvyC@GiLBgpM$Qw4e`-Vw=d?fSaXwQxp zIAPrCgW*>ft_b*-dq_5LSMEL*t`mZ-KSf&t8ov1@DjpS5iFT(mEP)z*W?&zP4HB*= zrASj*5bDubD}>d8`2;)|ifyFZaCGVq38>(t6u~cxS!Wad;Pw>k3a8aG59tj7aGrsD z$h{Hq^bcx8T0i)j5X~0Q_(c~WM3!&(lzRhd8$(L1Y!Q92j2kPqoa17D4d5^ Od$!Lxo3?vgQ@g{?nU|LC}MNcH3Fry8xWs3Hw9LtTQj@YqNWZolDEL3<80 zTykFYL+U4&PmZG7>2+b9{hX1r%1ZH&1n* !Uxfhan%cM770^Pz1ofO;gj64fLn zJfN` WBbKMAcvVcD7=A0-MbxjEJgt aPpG}R5TYkWoTvm?3=LmIp0uGk< -bkS# zwk<}x;M1|Ti<0HUOXi7AST*pgsE70clx_G{QTifMt}6(R;G19Rqq?s3S$1TTpLH^j zZfla)7Z$qQcciY`FRgSH&y86K*Gp;(kM9iEAKV8w?~sQPwtkk@#Cj-gJ t> p% z6B6NLPE1T2>xpx{;yN4oi#(1&`}U(WDKpnR?X`i9UI9pZNOK|Af|nH0^Q&5!T>>Sw zPYqc#4Otrx{X2>k+ 9y?zFl`MUm7)xC8_inCKk7eegW}08pW^Q3eehZY$2kZc04WN`!uZjqd zOXjc7Pej2@d+OVeb@V34=dW1dJG=kGQmNoOL*RF)6BA6s+}z0|FCG^09;_|JWWai1 zxaC2^M=3A{LmN)^2YNMugsSZOGMbSJOTJeRU-muMDhe*{gae~5$E+w!;?nUyaMcFK zKWAOt2_&4INli{Lb6^ errvkJ?} z{Ing_(-eo83uC|^i1^Qg;}m|Uiq$|GRk^1Y@G*PrMQApmZBSj=`;UsUbiNgEffoMn zW=RZtD|n)g&D+gAhPccK*PbN}oZr+T1mnrdQeP XR2F{n-B7TCqoFGwlDM>l}tOtp7u){or@r zgKP7Ll@@El|HHD!PzV0+ftYo8<+}Lwmyx-DFNeUexCU!y{R5dW#DCEC4mT9?hhOKf zrpNzXDaU})LMjrj3dKI?cx(}wGKLKZa(1#J=CyR4vw&xP|G$s ^Ui3$OZ< p#ZQ3t4e zDMUm^hR{Tj|7HP{>cnc}4`kJr>D2lJi5Iqq-}-OSw5FEcY|ljcul_q?Jsi}lZciOc zAwh uyVW#MadRE?YlZ9qc9_j7MfWGsqZ z7ahl$NP28AmLl?CW5Ev++9dkJZOk9A=yvt%dZ3!7t~VeKX5W jVbtKLynk zh4{ru`BnIbvLmN _`}Wr38N90c`Xr>Z&&mJs9S&VOj-Y};(QYe|dQ8bJcU}xu*12@OMZvQDzH#PP zHhGP7!MhtmhJx?o@p&W^`8n3B_e*?_@*wkQ2~#RBubj!t1S@LB!PUJXRK78O)J()F z%oF7du)5NvG}^7VJA%8EGT?!LOz{70C)GWus%mQStyx0<843ptl%n7i_z1Mp;&JZ6 z`bpz)rD$gfOTW%yA4`K&TQ5m4&ji(F8wDh~Ph0wA;`kVFi^E1RD wA-BgIQ)Q_dSx)XqMq`jQ1W}((d@&5qxf-tPZyf3!`lar6(T_%SzCLy zaidM)HydA;`Mn*!T1x&fREeVQkNZFOv*Q1NZ60@LHah8-+XEv7$@-R=B+iPrkp6az zqFU4g5s9!QEVjhO-SGfc=c$3eS&S#2s=^5%Uk=~|-c+e=VraGYGR zbXbHA^~`x;PHS19rwzsK^0N-&tVxhbtK?hF@wI$>{L-CG)=3U1x1tN8JB=?|=(Q=s z_w~VXCsu07gFlncCGL9^AH9r;w1sL2`Wy8>UEh=J%;t2!%(L3`OYqw`c#rfp`;jT8 zxH1f@Ouw8U!*tJ?s6v~zfj}EJUL#ggEvN-`st>|9q5F&?-)&F@lm5`q=yAkDAc9Eh zYQ!Y5k_Bw^B1NDRVSaGn=^n3Yy^`fUF0V7`4aLC8ovkq*z^>;Kxgqg+q9Gm>ko@25 zlA#Nj_SnEe*zi|uAc1Tu0tc%K6>rTqch$>he9VVE35}%#;}uOIO(xXax+yOS!ZSu< zRPSEt$*aHvdM2a+%ssj?q&*h?1R?4&XP!p)+U-*Qz}WBGHI`T3qXm~B7|a)Dj`9gb z|NG!rkY9Wh&!Gx+o;bBmi~Nz9FWIET`-F{%Y3wM{$$gE?Ku_evytVK7Ir;IG{;HLM zMe2yM{L(7xHH8S@Bq?N#Fh#fvv1RXf4u1Z)4Oj!KIb1OoiI)?_#9p(hHtIi9E=Pc> zoUO4EmUgb{Z+2J9KY9_88I$@dTds=x<8(&kBj;;sS}|Ah*4S;b(d<^f94$k>r(VTH zT-H88hO^cN-jE^`zWMYa7nDSj*T+jvE@8Kfe|oAz0!c&Vw#nRSHG@&3@E#OKZ| zY}8He8{mX#^M)%i0JZJ+-7BVkV+VdoUh}C~MhDEOGOHC;J&%`b-&2%H(e41N6 {i?Kmjwnei=%{Qu6vP%b z?l>spNd6K$Y$+*9fiW@4@Ze<|>v&%+Tm zyjo&Ks`F#+N)(mU0*tvXTyeJ{0*pb;z#N->^p&d8u4djZh`YIx;E}s8?+CK~<7KrO zJd9Y|DL1nc)|t(I!odv7qauA4zaB-Uex3dGRE@cuN+VPGRB>Ex%$~@a8{0$M#d;c9 zo7qnp*EwXCy?+)7p7s;(3=73bgZcSI*o~ve%He2F;YZAs{eEPD&nsn#0jov>FB<+N z!q;iyL8&Z?zVtI(8txw#23{D#Y2|5x8`7?6QDk!e&g9P(+KH%>5eP@4XghP*)V>rd zcRKSXGrPgqc GfoS=el7xQ~KmfB3UEYEOhcF 2*(4c|~P@gje$$w~%O6v=)BumTNGG z1=C{pXx+BCn>Vd0&8|4+go8ARG3-$x(MYF!Gz^dkU^n>W*9-L}}n< zdhaLgssr~iN%o%!@CFD&pDF-nmPcg};xW@OXVEVo`_^U3J!0cBs4!C>^Jc~-R|k{F z+0D1}P<*KS2v3=F#|dRrIx=UhXM5x|^N^s7tN_zXJk&?Ux-m0*y+iL5Rnn)~LBS?P zruL>K|133T7oKOxArdh{)+}x6L+K220ZtKpN%~D};L_K4tyA{dwP%f^c2BV*pGQrE z2upjuq@%NK5M}FlziNI 5a}t8oc1jj^ybKMgYS= zAI0L(C2Ictzt2@a!slB$Sbr9>Fc6lmK8v)pMO<=etiUf_y9cTJIyMHjSW{0@2}!?X z(5WssA<>a~wtx95n)xI*+24|)q`28kn2vt=Qig6ClsPc_Wl<(7>-(J9%~Q!wCva)P zoyYjUs9THU--JU`gPUkOsSLcNrMED$q!FUiIA5<^O2xp`w|ifb`n$N7-b~J9NFL#C zMfEGG(5G(z(n_+e< If}E!4Tyas4xI z3s{&q0%A^;)L@A)#no0i)$3i*k#C(Ar7D?=P;1#zI!pd*$k%+~yP|Kin#G{Yu4c0R zP1OZczqB$A0iyB#2^hM`u#n7SJ134{CB+r>pgooFX~fLdIC>dZO(;~Q|EaMpg&_fs z!Up%57*>TI{VeHv#pY(TWy<})k((}-!MdIIP21uLu-KHc*)2G&3Qqb#ah`tYFdYWc zwAU~TYEvZ++BYMI1yx4TH3~N_^W+3qR^p$ADTo7QMA2_xBBL n#y)L91-~i7 zga#T|hxm^zgUC=LoV*U~MupTVsWYG57DsSpcFN2tn=P6pRZx;@Cvh#`%_<%l5*ci; z=$WXm+`*0aE2H$`zUw`Ac7H!DI# iuJ(y=RH0@`Rsm)sqc-+?N8B { zF=MwVIUjgSrCqP;3>NACFh@9D7%1(WGgR|Z22R2~wtVv+XXO;?u+9YKhx>I)8Auo= zWhBi@u-JW$Lttp$LJ)@(U+>sHN&k`8AU{mTPVdVX=Q>5b8J2vj{TY@FO9s+;c{i>3 z@sfl%m3`TVsQHC&gOj}k{34_otjh0Rk z2aKsya@m41M;*R$h)+>98ZmN>d>`7^6K(s$svR&ht&e9%2f<}1eug&EKPGZOLeB5> z_H-O?r(01EgG%W0%1A%@KKRWj_-cRSe*2kl?%4LuqaK@sPc}FvZkIy@M?cLIEY2PM zY!#z_ED!^>kD_>4Z-qn8mHedF S6c+RxF=^-^Z2hBz&_>hu)o;ej zC%ZRkyuP@!Ih$HWW&Lyp;Q^EGs3SR$-;Pq%fClyl0eb_l_VOhDy9g5+kYU|l;edMr zME<+6s_=l1j^BN_7V3v@vt_+LDtW2X54 yFNBaqu1r{#}$)v5PT;AEGI2T{w{gF$~7kG}Dy-wW%mP~N;`NSbss^6iSC+G7{ z#SyDQA6R2rN2U3ZDyZ#8e4Ia^EVXoW5FJzW7g9B$yHTw0_@csbvR6y iLSE6!2bV^cva4GnZ-PskV9-iOYY7ckQHe;Q?2j1-D`nX`>0EiuLDF8 zYGnT@Z7ApbaXfhuobZOCOwE+26?i}0I)dlGM?8)zXf!l5rpi8gH7LYnY#6EYt{+?f zZR+G;V}xQQ+*7BsYSZ &)3dS9 zu=cs|Yo?vr^@+o1iUVC{%2Oz$qVLCcbdohVDe}2LB{S=pTx@jR691k>SWD55DwDoM z8AFe=^3l}5{e|@>Q(2;i8re6a{hTMOFKU^mL$5(fJM^$aN+8wVE3X#x;u;6`Y^6Bn z&X}c?iT;v$(MID1KT9j3@;R-(uEk{n@wFGnly!70Lq*E2U!TrLz*Ri#Kz|f5!=KTQ zFYdal-@GO+HueJd_Mgr>l8=ro`Gb!?LpjCOo2_=P{E43aKm3KC3s@DMt6vrBT~ir7 zukgaaNyJ4LoyEG|u#^EgRXV9 2P zr$UT4Lq{l>xY{A=^ucldb4KtGc>HEh$OyP~!cdgq_$@Y-CfkesbuV^7tuO2scqnV9 zep)eJCZ2lH-3y0V*Frq(e+iJLc;~)ScJ_B{r-H4A_f+gR5_Bg&BNME>OM|1)V!Lku zsdIqOyS8apR%! h&k|s< z71fPKlg7Gw99v01-E8 o1_{Oy=1uhAZzI_*` zv8~j}{K-GhmiwG41+*Jdj7i|Q4c>r(4K({R0d7=hy~w_3@jcR-t*he468-1*d!Hot zLa434{V?D>hP{HD{6~rYN1oVJQklNGBAL}_Qn|~$5Sf-$yF~jCl1ulb&pSWYR<)UY z!PYY?vMPfvuME#QK$EiKTf|md&*#F5_Sf|>k#7*B(f)8y4%xP_DFC YhBp*Apt0)mfr+*SLcnpKNnaq$Q3;hpT|| zL|(l39g`>4LNBmBm>@xk sFM^A4-R9;6=F_62Vg&U#K>;nf_1#Oh&gj z2**NOV11v&{0{HoOrgZru1 zwv6jl5z8Xx-(XTlu|WxtB|6cD#ZreCuI |5{X)Ut`3ly9emqaqFrgLYt!C!Hef6_B{megMI5 zVuEK5|C@5`kVB0$@P;OR>(_W+ni!_RoX{L(Ab0xlm-H#kTSB+~@`CJO5MjjR`lm9P zGQlj;UdcO3j <4~9v9Zv0Fj>(h|%d!4CrsHXMvNcg)MM3wky0q-8wITFaY%!YxekAF+(D=5hK zD((S?G9ME{%=$ATIcHhY$P%|%;s*2I4iE?VWj~+Cs+pK%#Y#=j6Z z58NR!31g@KWM|HIDsF#IG>tB8X_# ~0`gLZ@+4v*J#+SSz2N5TSTs1 ^C-K~XRYu$$-2 zc;BUo@B315#*@t3U!7CV{H#l`_5PM%?NW-7S (2P2Jg@Tt@3H*nk|GWHQ0ZeGZGitoPVvh07J|F zz#@nTM-{M>W|tK|jQdN)X!@hkh) 4HVt_H7eceme%T3uDTHpR~Gz-kXyLTeFSqe zODCP?eJvj+a^c$P42xREzl7odtD(A7u&~JsHq?nUY!s!(R3?{9F;iblD>L7JJi5_x zTZNpmEwZv%&48j3?f-H@dNW+pVWTZvnHUfIB8M?Y1l9@8fryV_jTeTgMfA`4(dSFk zp`jXsoj({&IuaLhn;ZxEw~{_`Mf)Wq(n136#(jC@l@K+}j9jN2>5)M2C9-s^Eghj7 zB>*3-F4&Ka|1X^l81_s^qw CIick z=SIt~NwSN@Ncmn*>ztIl2m$dkd-bj$Z6OQ`a d zGrxTL-XiB1)jTyS#LJvLLsHLWO^)13GGA+@cLfl>9#E;S=o`uU$a3*(Wyb!Bs`4wh zI>A#x#3Yu~HZ8T%RzjixbFv$v?w<}j0XM`4 4wsq3a*JhZANoIo} zgc3dU7y#ig!7g><_S(2~2Q>u=Wz+yq9V#~!T?Uxb7=F_2#yFz1inQ O@6jrvN?N=TZPAQG!W}XHtk|e$tE(s-V8U&XEIw}gq81mJOy6Sx{ z%(MDjeH?C^)N@6SJbaf~ )}KM9O$UQfDpwdNl)TQdC^W`G#Hmjch;KoCaxz6 z% n2KPd&@gdvJ=N)23xv6q(G2 zf*(EpAlf0M9zV;d2UFdafXplZErf_FNY=KSiZ~A5DAD*6Jo=NS2*@}sBiM{SE}ctY zW65G##&yYJpl *zS2-_- z=oy_bb^2p^j0Yg+_Ci*=c!-V0TU91wVY|^#PR)|imP0({n6VJEQLpb?w>Kx{XHs auW2pq@!_gOT8(i #xGNT=UbKyI2@5f{E;)oE7{= r2~OD0w0n}5~~H}>trda=h8z6ggehw|4X N?Q!}?M%(eUh%mYbC*73_ukE^4CGeB*W4!YoPB`W*Z2 zhM2*$nauq2yg#OnIR%}tICfrjJyC+$lkO% r8Gm^Y0=Ce5+!&O-X*aV&_b_9r%ldjPL7`ZV5YDnFw+5wx}Zx(sY6tgLqK* zH2jrxmxPU_rLyE0dLPMS^Nzees;ZKM7pBHUIlKZ&%gYnMTg=inHWdlU$#UT3)A(Kf z+&|eqsqeGOk&Dc&?P@c4p|@FEKj1=GK#>a{l8DOqzgYmn 6IduN?qbRXJpl{N zZ(j})I?5!Dos~)-Emxh@C`t<3^7+3B(Yma6RHoaw&cK%{%y2ZMzq9iR{9@mV%q_Oq z8gP}#v*_QO#uK)#4G9wjgyM|?^C@b5W+aIz9=x}tr0@IULP5r(ucbwMy*CywhW0@n zyc$YGA=1}($w@PZW1c{W7__rwRh0-{j~)V7iR2&vS`v5(ygL-_2qsLs$v*cFG +(UfJolnsXhBUN(n0lhAIlM2>8K>a5T)N-+BbGSW4q7 z;8d>O(tn2Kd2X(| *TdvJ!(T~&q8^^a&uaJ=n5lIVl32aIQIt3I=vUV-g z0iK+94 aO%uf z`F9l^jMJOcOi=o-=sFHjUG1gY4TrO|(}G(MiQc!1Qa(3j21DSs$Fy+N24$etrARst zFK0uM+XoB_Zx~? ;PGQuXRxO%?%K(N`up%0c3JCR&j6q211OTbm z1P4D5JCZ6U&v}T3(hFpfGuj`Q>@cGurJV!@V0pf9ImIt#@H*{)m%us4#wT*cDq5Fq zTNL^u@G0J#k0-3PdD4WVDF$!aYh6HUi$d{Ub9eXeY&!hO3y}Obyo?BlvXvroiaR0; zLxY2)Zq&H9Bk%Xp>xvG(^%F{83cIkb8AeoC>lU|!hYZg*JpV9-)|5cSPSqTcj>4iF z=>kU*VQLKNU(`CvP`*|KT-FOZ^ILWj_qGw}ut)&+lCtSADK%vz&=4TIs{L_z)bxYL z@9OJ8ev1hp1>2vcOhNBQN*Ux~s4joIUlh>-Sq5rVc@pOfP$Lmd0XR(+jit$6*L%9G#2$ERjAT$ z`!m)cDIm q&x5I?5rkiC}Jh_ zqCbdZShv~vi{;HN_j4F{dlc{hZta}f#ZaZyO^Nn_#DP}m_5sF)bcw@1td)h*q1K~i zUu?nop?Z=Ul}mGro5@wYtfOGeL~~n#$-yOoLPKI&*^flg6ZW(D9+5?^RdoXr3r- zCR81TxOh%)C6Ohrzpe(~{b4tG7u+jJ48x)!_z8&sIq4^J2;SsRW~glHmL3=${Qr3R z3WluOrdvS}5b2WcPHAo$L1~aqk?syfN=iz)q!B^7LrS_kr9-+AkgjuWp7%R{VAnN! zV$GU0=ybRis+p!TJd}T6nAn(v8J VQJA!!1A>d*IaMI|xwWJ5 zar5(e-+U;Hguc|Cc#DBm>Zh+IIZbSTUi&99dbK9q22RL(>roj*#%_&iA9da(XfTu$ zQkWXE*Q9l#df4ZB^Y0fKa=g)Y=WxdB@@HryOYKf=e>h2sD94J&*nTp9;#3u*V-;b& z-u@;Rl5iD|f=si! PWXPDUErk_g%t>yL$N(B@o=cR?Wclrx8e-J~qn$)7V z52b4>n?6(@GD>-Q(@EW)N443#qz%BhIa0gP`UN9-sr%hSw^PCqEdt-l`3Z`m*>z$y zmM@PuWF$P)#@!M6IjP%Ku-YE_cnTDhyE8xkSd1#6i(1QPrv4^>8a&PqI6wZg;3^D< zCgEK$uSBdS7_P&e8a+Kd>S}+_tt$2v+Te0QA$0{A;ZQX!Z_H$lb?g1@e6Py&AU`TW zslnfHD_0TK?iIE%yUoIEixzK{Oc8!W+Fj||+bg%UBN~3 !6S1Hx$br$59e8G-I&PSmvsH*9t!a9NKyhbtJb_OoSu?J?HDp@}Eq6Df(a} z9RD`Z)ziTXpl5@Ln_wRLXAuCGoK7b-`|W+Mb!UJMNbwLF-~5Qgsy@!T>qp_%tG{!i z3I|yDV&F%N#r*5Ra}eq8TC*Y;rnw#3RZYZ3Hh!Czh1wLaH7S9Tf2c5{*qj)na0W{9 z!4;hh$^D~2z89ML8b~y+H_@DVyMoLL=uWZN>EM%Ifp3en_86xV{DSnaNgM+#)thdP z M5)KBfc++Sb?4k((|Izi zUUX7V|6l1(Tnu5w_AU%{zsh_oM_9f#GZ~c~<*E$TclH+{>ooH-wXV8-v!I66(na7@ zbQ7b=6sX%ND74908&?w5ubD9;c0cZ=Hw6L=Zu3*smgs(eOj}3-m5fnv%o^PGZPrbc zL_&CjNWv%kS3%lZL<)~~pfq0J!PCVd_$NJFgFPlRacEq>%YefcU0Si>*ZmJ-hltUw zd?Oysc)Iph1=MVx%X(M|sh>&X{Zxt12Ry7EI)vmV$s?kgC$Y#PBP-C5dQA;l73)`J zv5($ol^NRn0>=n1d#B2hP24gte+k8HlP7j^A)Q>fMltg&&GxlHd`1fxytnAeBDcO` z>cTXM(KlpSy`986ntn22J*y_qksF)7R|Yhskv{se0N~>hAa%7R32%`fkg8qmlbzpX z@I%PmzU{yc7UQLiavY3MqT#@5)kL~hYc+pjIOnlnohY9Mn rMed2MLazE>HMQ0-i@vNQDVh1wS1xq^WT3sjFi%qO>Gmc3*! QfwN@Bkv!QSLqrQmq!^*MjQmXnq7+Wh zg#Myh5Q$T;f=RfPakUW31CD1SL6J9Dz7!%{_+@K?C0d7;U+&HxN&Bi)KgX>PM`fg> zFz%U%1dQY+>T?Y9sBh^ziP+Wsr!eA^K)RHVDHWK$8?Px=lSvJ_Hy|aQzCKxJ{HDKc zsDt9qb-DCBk2>+O5LH!;clWqOpRq&(pJ^lY4$(HEB#|1Kddk7U!l_`XS)B^!4qyrO znv-ToJMf>3vp=->VjpDpziNp^lI$i8|JGy($gr7%;FDIYZZPsQQe#_z5ROk6XiFMZ zmY2zXL7=Jnvrfd&A6O$YMHIIsb(<`Xr^60`LLw0wZRy8Dd`4wfr9WLBGQDNSIVK~N zN6fb3X~BVCVz(Ulp@I@z7vah7&b;TzAgA3tKQ%v~PKC1Q)sFmbo3Z`5U{Y9IQ-SGV z^`2ikL~Kr?g {XEHZk^pHI>oNO_r|r%iIb|VY-o@! z#l~oNpJMb&bw$al09BMI@ ` zr7CgvE;yVXJA?$BSbmuhCPzjFmKWa&=|z}axfY`=I&E&f)w9CP<+g*3b6ipk qslMv@_~egWEtwe{->x;FRuKwfA2cX3MN?b%#S4Ch&1n{JYQ z#Z2;}a)?)yOb$0SOP3b;Z4iaPDoQ %<##RJ*_;Ryb!Ny;}}& zL9)g8_HpOtahS=h)$s9qTMfQcS=~h#g-B8;VOro-Jibqb1Tvg}N9%9#eY_5}p2Yp1 z%uxAvlQ@4Naho=*L19RXJu!(ZCzPc4B&PiMacsP8Rla2IL9SBHE54ng`#aK*0Z+}g zU3*H>h A#wy+w}KeB-vV2dYa<;-6N6~UyCL%rEYrDNe|YzE3f8{aEG z92w0q!Dm!da5`&kq|ENPP@aJN<5dd}JDaKJs1X` _MN6WTi&uU;b33JQ5I( zk55JeFAym9cn}pi)Y5o`F8AxJ;3;`;m4d~3#hp#Xv5_O!__b7KDSz}>hfOq|k9wpL z;S=wWocH`quLYD-VpXX>wPE}2K}jV2MZ)>Cab&L?`!AE--DQ5{p_|K-@JEJxF8pl9 zN8)tgl$ e109ddjO`t_A(HRVo~TdF9w#hvm5Z9bP4NmDr7?;B~YeI%X*3f74g+ zL*+U2gb2Q0Vo5JnK25w6Zv|o(uhE76w!He5rIGu_;)5Tu2`PL@tIR8{)|-I%FNPlr zbQt1G4XAm@Z`G%Iw`mYN_j4TD({f0w1kyfk_eWf9B_;L6@jLEESUczS_SQH35Ccsf z%pU%k){dZIz*J^g35DKm{&h>WfJF!vfuxpYhgwmM1ZWd$F}-b&1?jwg+rnq*nHqB$ z!>Ly6gBL ) poF&)1T#eeH}2GN=u3CgZ9iDIce-#1 _Q0IWcz-+dy9#&~SoB(pJ(cgc3 zH;{B?I3Ne>h5WqtO?ga{fY$x1L5=d{9F^0%jO>klVp@D{D^xgq`}*d%XQJXyL;Gx0 zm8J`8H5AWI8EL8DFGHcCRT|yXQ4zLH<1F6<{)RzHLY!P{w~wbgrXR-5=?#KN=i+yf zxjA(6$=|XNl}|>NOH%|Zo?0gs_F8?7dnT+HGSq~v@!BLTuZ_yDe&>Zs6w5k{EG`2| z;2$zA91@Y~g_{}kjCj!B iXiLY@FTfZUFlL1*e)_tff;*sfMQ{o_u4d`uee zII0Z;Ru2|JjAXYPoo2TY%MC$3pjRKp)t6?(&sws})$WQhF5TAfkHYo7Ay4;nXdi^d zD)tDB*@c4P12H=gIVx7aZEDH?UGzAN-%R&V{B7XlbutBq)mDQApo!8{_l+dKw9&Az z)mRg|cKPiPu> Pl1SM |ODN(%IZLtYd0=e6>*G;$UvkqB};B=TqL)h*V zncmh(^6h)-?htrdH^d|!IwaXHYAdUiNFwP9<-K~1g07%(31gaOqlyxQe7Km0zssb$ zrnW_0P?n&{*XIkGK@I7fLt f@FRL3^5iL?C_(M3^} UE#Q@*OGNuq){X{iGX;c?Whd{ z|DNQ#YOEgQ85C@i8%6c#Tv@qrMJB476yz!)c<&|JLnT^%=~Dn?c3z-n+g(`~)S}33 zyKPEeMkHF-x?8sKA APD z@x`kuMsZdvvTBm#j%#$fIi%6~H7(I6_nCw_M>n0tFUom*BOu{%$aPBDLkA3`YhR*B zmsYI~=Ibg `2|ZyD#Ij&UHVN3{ z519r<9gzQH15+4mpc$VADKb{ )IGLq%{Lo#ABcg*#lxe+Wp2?X?+hsxLKO+ z`c9!W?O~m$@Akqk5aAN&c&T3AoD7?8kLAdq4NsRFn*xeVLJ&?vYfBswG6GNXkDO5i zTXcy&>)c;d)v;u;^@OE>Z}no#ZmC)B&&S6!I-1|zL)FHUMuK&s@Iw^Io+0u2B3Gdx za7wrd4UnwCk!Ks+?r0vTEZw{sg6$U3jF@M5)sUtLxmSQ$8j(U5lecMPZ5`;vLqH|X z91<8@2i$wk3N9cw$d&O&sAmY|ny=mewjv=043IwoF6mn|>5W1~CCq|LsfId=r2F~! zDb~p^IMe*gD}nrvr%oaA_hN0ryw#+@n8?@-sbP~liF!tBpU=f>&N Yo(td{r6=B6a(k>eR=0 zJn|Bw_heYqJ`r(AzQ3<;4M?9PG~e#qGvg8tY}z_XtV#AXYu0{63KqksSYDkxK$_ZM z7!3hsVL{|!%+7neXstmiMq&?NT>FYd+_PEU@~}EK8tJT9L}6(xsE&IIJ)R IrEM%fu#6GU93D_5+Zc{R29` z7ejiY@$vCvxk}_G`vNP$)DH;|$b?IM3H#~2N{LU~W{6Ns1>AL#gdh;eSInWvsI)+f zJIi`CPTFPLCjaBfxr*TB3s;Hs9OCz63_0J`zK;tk9(=c2^uxg5J$hypz)8uq3)~rK zDj~oV(h9bQIJf#CI_+0aC4WCI(ysi%U{f{vssiNV$!AE}9U;Y=d-JArhfC9cQLhMy9dJ8-pTq{42YaBGGJ{b}${qzlo zl%6M_@P@w+j&Q~#RzZW}6$J$cyRX=vpQ4ZclHOd@DW<`+d7>}^5PwvGWPeFwRq*{m zDsF49N+g-^TB`Gq%;$bEPW2XDU=4Fz@dXxyno2t9Q=GB1H)`=RwmWpD=96GE6`+%?$K0|VNqc%?-NiFTEVr `~#dRo^d>G z_1OTFJ?@p*h`o#KR9Oc<($&qsXnwB!tUtfb43sXIrP#_B24H^pnknW`QoqAQt)+E= zxqC~Y^UMj696lcdA@y8I1npT3tp`@wi@cRim%Z&}`KLtWDYc0bAaf;EIqj-07}PFk z5}?k$P4|g$_{qxcGmqAN5ZN((`ht|^WM=N8?8AVSmNSCO_mvi#x{ayQBRr9EBU)p6 zu2va*6XXqwuOTWSokW@mwV90TMLRmFLEBFy(Y%EKpk72#(Qml^*u>T&d@AQ91wYw7 zAE)m>3;6xk(yKniii@Ga{m2#8i1>==P1)Lo<6j$o#?I=if@#nG8?pk39LMH|s#BgR zJ>#(3JD_99BZZXyo%M8JAkPeG`NPXh;UnBs2TlCIYTo6KGZ$wvB3g==)64Fn5r@m| zhPEclJd&9Lm|7wUZ-^3 zc&X!f7;h2_F6yPVI0d&9U|NXH@<;;en2~U5&%_b7zK}jB333#_7g^;B75m)N{5eRG z*~ahIj>G#Wx}AC_P@)+KgIK3g>_7r>#vQmO8-3!hg(BB`T)KariwJSp17SJW&-O3{ z`KCw1ZlGwCqKMym*V>#{@=5RZk3r7PDRcZh%WqavZj~~950qk2wa}|WwoW3Qw^c`M zj^T=-OEWva`ys{}rmrW2c_Y=p8cmH}v;gj!Y)R9VRoJJth)oCV&&Cz@__Nbb`!3-{ z=EM;+0$ZL+zUG0cRLU1D{(4ieboD*JB`tOAPlHUAQBA-ETwQkafpzsA1vCBzv(2p- z+E9V=hCBFd$D~lTIraP9q>0T?9Z1o}mnmpS43~+mF8G2+RF#A$Ex0wB-anmtjqpkA zWtKmhKp2e%@ZU{tIch#PnzXRT*$X9nGDoijQ-0)T=CK?!s73~|PTCY+`mbZEgBt{k z=}J)?{Q27Y`&yTdcP F!hb#B3+RH?ku`}8X=sIhe+Y16P^OS+lF9C)nfO7x%c @WPl-y?HLJ?BYi3eGnW4ESTv|YHC{6C s~;#3YPDNIG8OMz^XF~QS)+Z zp73@`oET1H$a$$0S3$~_sGPf4F#2XZVWTz7*T7tb;<(Jc!;tae{2Tw*7z3?C?t2oY z>5=i+&2Lx2>|M`^%af6_vD12rB6E({mjFdGBY1)>b}Zv|2p^e+VSj>uRC}=gYxsk+ zPk<`JBmH3v5aFF=7ePCM_3CMg6*?hjM;U#urQFLX+wj=bi`t-Jv?`wJ@wd^T1m4u6 zm#TnOf2LA#YN^uL`*tTqRU)gM^5y?16PyQ`&&F}--rE#9oG-lZ2Qb1~2z@$7+jyyg zsK8dHzX{+FCj~gsKL4R6 CTlO@S$=r}EH!dPv|2r}i#nOQvT$ftYu!zS~S^7C;e)6!;cQh@DFS zP*nkvDx#S`I)X{W&x#l;k!c1s4wRq0UbO%}!Hqu{US?SrZBUl|lkpwFqRui=FwVRt zQ45kHxcmg66^CA@psejpv2S7o&CfCiXGzm=LatX(Z=K7wH7?Lt9t`SYSxCE-4ae8x zPHh+Tkl*U>@EjLd_?K^df1Ks-Vea{K>i@U^Pe2d(x6{~5f;CSP0vl+=n-%JSM;u~$ zcVNAqKEtF>bBVdR9QH{72yYEsU2FD%;STG#3z#d@LGt|!_{gCIvR !SYi_oFke^i_(ls5RW5f-RZcFOv&+KlmIbbi7#EcZP}SNZC9`5R zUw#S){EJLH2z~T-Gb%*(j1sxpqK5L;H4u`^NJo@}Pt|~Mz#SD2508pY0dqSimamW) z#iR=&j{0qd0KO|87~aBlBd6RyZ|=rN{X)friu!a1Sal*2W*N$ZMQc2C@v3>I|KT*G z`G8g_r|m2^H7~MgH!n7LAB15kL|s$)vN3##mJqpTSn9GSS(9NoD?XcYCM(n`wMXfI zXjiyeNX1v9^$xYS)9Amr4ACXff6ugL`z*Ll^=8Y&W&<^2&JTaz5ehhE7pH$wcA<%) zvLlE-Y%krf8dgw|VO0QwjK946C%lh7+1`)X4U5VcZhF`MQkOihHnMP^BP6t81m~CZ z@c((amGNoZ-`x(1(({aJ(u2N9T0b&>Yk3~iXt?ksUhdVnN6ytRU7C5@JTPRV@3*z} zIlbPOUO}^Nel9KzxN#dviq){ES1`eb{hE}^RXx!8t3j~ s()-Bf57cdTGCJPDV^1Lu;VN VwB;FASCg90LS&W{y=;d1y%dWxfVET$CG^;WpBon53B(1s;2PAtJ8y zc8$5*(W{1?zTWCAbgPN2B9Wqj2ek<;Y6z5ALCwbN0i8z*AaK<%?>eQw@$<1! ;M{;5Mgqm@$;d>mq=$o|h9U6^C zSvUq9m^2vxWqb(J1W>&38SgCUg@mR}{n2 z@lv=8Dx^{E|`*IiVkqv zJN@(V&5FHz9GV)7 B%FU6DIz zP5JVp>;{$}da =!P4#KC{qc(T@p${q(Q1!<6W3f=x2 z-zfBVeP04b-}~DY#42rTmDANJ!4$v?mS=f}YwD#2w5Ot8RDqz?z22~pO`Jl|dETHh zj{il&d$6GIAq0b)XbB>4%DQpndvN~-`u`STPu{vts-JoFG3NGw SuHIqH{+iINlK3DK75$)djI+5Qbk3&vWNr)%Zqa AU1m3{S2 j-Nbj@}ICA%WxLe)j$>sWADlRdNwz&hew79Cs{^;)vvurxV-!8X(7c+b}<%`RAZL zd*Q85g5OxgkJF>0g~sYiZYbjb-xJ_hJjR{m$d0GCZGouk5H|`4ql@z{c{+nZDqbJ5 zua&PN$ylM%@V3j5jw)&pnMOfnWH#@_@pSVX5XV=u%g$@Y?mk_+Fb9XnAsh1i{SZJ| zoiH<>5(mPnucsu%cN2PKimb8-!2|xVj%1K% k&pw4j z56-~m=H;s Ixx6ZWTr_@ZiOzM|vdPFG$=tz60(kJ5 z#dfKp-XN{NQx!KOy5 Nu=40L+hLA2*pJkros4x!TtRZfRxF#)LPJ|bKKdiOP0Y&|YO z6uk$LK#yY1rZW5s5s$2*=&cL2=v|Z(q5l$_$KQs!xt8M!3^?+3x};a{)WM9Rnvi## z mO8Q~g#RQ+3&)e{0Q@~JJgq39T_T zu*fVNGk<|yAZb{{Q3*hMdZ{FZ$2#HP-VB#aqXT+3J zfnr@ef)pGd99l%547L2tg>(O8hGh1m$?Lk0*uK$_f$x>k4J85^P%SBWOu0S!{q^xU z0Ko)B?B;7tfbKF6pl3m -?cn)2U6mu04_KN z??KGC1HOeye{4~lXa$k4TGpzLgecjKE>dv#!d2jS_R6J!+`62(+=sOA_4_$Nj7N0^ zJNFs;4f!;bl>ZhbVi$9e8KgIZT)bN_a1pa_#3Yro2^jq-{A8owN{S_`+ %8#x+Qf zk$XhJ&%F_hOLwGk$jaXPxw8FUiGzLXG|R%M@D-|9GIP^(k|0_ZGL$S;0V~wU1@W zS&14eOq{6ZrhKBLzEXjtM IijFV@ZI76I>ogir9gL*`t;!$)D@5G*C4z1dIH*<1CNRt7q18FI zgs6SJr$S`i_bca~?&jo&O{KpoI?l$Zm(K_FRCoNWJooYSi# XJ`Zr{;Dv>>e5Ob9k)KHjV)q!Iy%AALU=z^f&)Y z0F|% {=-|R9NGkngJK}}g5MvD|6(yZN06JqVYIjUJ-w{6hfXH8|M
%t(F4;#=$p0^qKzMBB8v@)VAZd#Sa!nn1Zz-d{fFEM!8YT3@*gC zwO~2Q+$r*>SJISb43AMG4uG$bh~g?2t! -R&r-RA;jtT!v@t;#Y4}gP!Ia?HR3fUvXM1ECfF> z%;z5vTx2r#GO};g_}GRq?O|ko-eGBt-;Z-B$EK9*txI1TH$)Oqi(P@NDhl*g*d XSx4Csr^`>` zY_oYHN1ujXL39kr+QdFB>0N2GX>!;>yM>%U$kv``dC}-)O3EH;+PxqHnB(PWCqH7; z5;E5e+dt1l+0QF{v1+?&V_*t+to#&_?0t1_cHpzAKBcvNJ0=n5XY261ze#_xRMWd0 zpM)K*i? ebVKvXY`H z$0N~=f4-M?I@jl~6~_q~nDQb*D3u!~R&7LZeBDtOG3#?eN8ivCZp)Yuau`_v61+c_ zs(HHm4@Ti3-RZTbFIe@SuCI5mvm@+upT&={s3lDQK1Zm{p!*>7_rn>k<=yaoZ0NU_ zE?C3SDm*T42rCvYRet@$hgmM?j9vALW_$Zn=a>bSZfDT$G>vgNyIZ`C28V+TFP>Qi z84iV+G|T9xupr7+`M&OfUa-0$TmCCYYsU~oF^&|u%Co7S)Nu1uDuU&N3(HPrAPfX{ z%B|X(3A@%*dH(&P q-cjipZd;u88gv;#IS@E|A{I2W&m+vo~@yn!9bwm#xIrG4Npb|`=wIA z+ik$`di8n#N;jXRsn%<_$aWodpy2OlQZ$w*SsD&o5!`}WmJ{UX#o(nJjdSY$jAgag zLA(}_qQ!^v&i0sb(wPN`nQhDo9MrB36sTHYjlg}t$ddJvr(kt?F4IZ#sgg`!^mE)) zNqs}tx@V`JZqf%512U=vl!t>=`6q3QUC1SD9Nz-NigCM-GR-&ig<)X??QyZo$y~ zGuWLq(4l;Ds#nF!vCM8lom-W_=0ddOu9m#$qqjMztrwJ(VM-Om`+9CI4R_zZg)c1c z9XMS7V6o9obenObA7CY)(fch6p(ugRr|4Pu)7|Q>M@F_KDpUN{$&3m<&5AMG d?M-g@U+;}&?QL+^4@%3 z1A(d1wM^vZ?@L`07ZAk=YRp$yR!R@Bt(i?#Q@^0f(PMOYowho2Zq9^dJ9Wg2mCf|3 z@OU;3{xs&IGEekjNC}k^_kVD8@4g}93Y&G`$J~K28`G7Jkea7gATTq@vDIbY949u% zfe!lbjd_{we=KT)E7{^QV2g;ht&3mFZ!=5x^WibrrC0vkUk{ba0emKx3BRwd2JQpX znFx6C=upUcMg=7}Y(|OzgLB_2@0OovJnm!W?H+<;JztZ1RY6Du-3JF-si*Mzw967i ze0WB=uCqvEH$yK~esvHrxzto$q#?^5Fb(pvK@oz(0U1|+uQn_+elStnj4*{K32!7x z4K(b@;{e{HX;;5Zha>pnpXV}nmC;0pes&Tfxr_KoZ>h|QhfjR6>CfM%rN7I+Z4`VM z@WNv&GMbOBef8jbxuMH`sJ>)Q hwN2yI*ML++%|4}@?Dy!nmyGkY zftjrZ T|{uOL?li$4lbXj@cYFK)16Xkam zRPK}{X_2BSM)0SJ&j@9WLB cjk;OtSiKM>D`A(DEuwT*M@Ccmu{FX$>_K}#WFpERbIPSN?mVn#o! z%ANc0Z)MUb 4Ynr8?v2LLt3;j$IOn*yqxxw*Ng|2o6i%;eKS$A-kQ1IuYt zieLsCJC(rlFBXSk)@1soiwfoUSh!gWrrTk&aYH9Dycf1bj+4I`WO9Z&ZbwhXZlWVv zbdHEs8s>lVmPQ1qoB!p>%yWdyghDEl!KgdYORVJ?DQqHO5Uet#sx%v{I7B9b@fm$E z8%}3k?F#P?$*9%!k8JV8e~y6ems3CVN;|sZTNj zBUP5; %W>h=BG5ENH7OJws-k_ yO$+I%NzAZj5 zeiLlKihhhCG=hM|2rFP)=!SEQTCz@XFY_ps$TjT~%{f}PR`2noVvW6H>Mc@I@8Fi( z?e>ZNokT7-H{oOYD#jVT?T1_TL`l_m?T=+UY8s!_&BeGQ@+2RAJ|rmdX~bz)@z48n z8zaXS-Ot{vr^x>YK^wq{w%0%ymr?X;G}2_PC%OXA66P8O%a7eL4(tfic2<6e7EuLF zTx=}V2XA(cUA5ll2NN=7!T-oI3+_CwlK-gK8C73n6(l1;q9pgdif4FYCZ?IG+WH_d z{Ls)n*ufzF4Oej16DXow1&F=rP^@1Q;e-}CMuQ6RH9Fu5%pVA!O}DRn@IG%?TJJzV zC=RaMiuEv1NpgzhvVHk_?e_K+9_+o$+#26x%t^?QUt^xyM0i~2l@z!4Ykgg&4{QDJ zdE(3?)YyOJOxXYPvQ#frnyjsMAuU4VHZx~wQo)fjBxK2-BMC%-9Oge%->fjwdpY9W z@b3NPu`JcL(Dt%_pzqj!N2PRMO`fLFTosw6&%L>0lg{j%E}%54HX!Z#OYL`Lj4U3< z xZDB_9Xb(Oy!I}+K_lz@T0(kT*l_`;HP$gLb*RQgt@494Bgb(=cgw&_ zGTa(S{ZeQJZOc7}5Laj!7qMGJ?H{c}1do*ni^KV|n#6yk#8H9$$hz|>;rHIkkE5EE zzl~GGqkT8o8fxaXMr-e8o-|$0x7vT~6edDphY1teME_o+#}Fh<7KKK~ga g1hxf!>L~!!; zw$j}ilpQ7hd)H?SP$)u1gVDgpIT~Qa`nSA;7Z))ew6w9QGR73#Ep2XsGx%z_#a2Y8 zkW2$r8A3-LILClwGxux|3RNT@x4yY807 siU83~wL44vx9V_)dWyaASxaab`aA=UMKauxQL`|!b$}3!FN?hGQK_wszEM}m4 zTcx5+__cUZXje<8wsw-mIU@aA0+9xz(uQ|Q*n2xGiEeE70 xl-lHKcCSt-rgR`sJIf+h9F=2IqEx+gqRQ zz<5(zKTf~;4K!2%vZ@}0h)o^<-9Zcl`RB!DZBNZCW#9ixJ)AllO15R|C= peLtL#6${C9#<4p02Ka5b!W5IgJ}V@>(D;g z)AAwVbG@+fqw4Z8QvDLuN%UG~d~%q^&qfEKm7TQri_s7*Yf76vmkW0P2g4`oCAvUI zLh-a+!~Vc24;a(K;n*I^*rgsw_d3(|Fek^MGD0I_IDsGR*43A%NidWC2NE&oe2eF4 zc$6nACKX6YBbF&wHE39+MCEIq8i@nP=sQ0qyzsS(By!#RbUCk&RCzeu>nFU{mG!5r zy1$IPDT$+&RJ6RckhFYwrS%6>p~q|dHzv0Z7Q@CtJ?4!`@x3o(!m}()ke>06CHWKt z6%+jr4(Rwq^J*@^mW59>@DYZ<5?kB$nQ_t$2wVmY`YL|XFoIEMh*3DVH>h_H%smlW z)2e4ZfVk$KQ~Cm|&Bd_ 9dmSWAOj!5^Gn$yx!&unkUE8xSE2m6{ zaB^k#b&zClcRY5g_?)>kNQmLozkSbg;oSr9(!YkYa(Bni2cF?-kvc QS-iseB28}TO%!p;Me+U!YDBv9VpA<0{ z#q)SwxugguNp61rv^~HRk1tr8MdrSSRrxo2)eMt-tr~n7T`^9u9RgZ23b?92wG(PZ zy34%4YU4^yOl}1sEUF;NztBQ&?L<8m_soCbE3&*_%-`kA8N-%iG5nq8q%=_--h2I= z^fV(EfKc7igPgkCJ$d`tM~73pAJxvK=l1ed_zc-6IZjUs3OBh6KUjWT|5$w|A1{eG zkosCFrq9uOhvWFgN4>y-OP@`d$!WZ7r+>0Fi<5hz YfQP$lps^DLta=(gI%N1LK+CC>+o%KC**vFMiez)jOwf)uVsqhjJX%1+ z%+3H+a=n$k@az5m;{sgFZ?cV}+$|>l7+xB!O>U4%HkVuHa5db%wH!Uo$F5$f+Clgf zoHRRm7FRcHKpAe8pHEUAw=v4VdMLzVTe|ce=Mh`hCM#n+H9@S&8SdaYb~#U348Dw+ zeDv(;oi0;Q*{m1c0b_uoIV W!kGTzZLIuHjsQq}?Q(=XaScsM*sM zKJ=IbJ(Jt~cyQoUh`s6Yi0c~3@49a$$yRDX(KejR)zneHM10kSscazcL-bX2fzrkf zCwfNj-u?T8Dn)OFnC<&f?JwPOCmxY=6=?C)3CcJ#F8TV`@9-pWa@=~d#yU)VD@9`Z z{>WLs{hqz<-#oqU5TsrZR^*mrpXX{`8`6&n^QK=BhKd$UgpdAWhCHLh0zYGeQ1Rx) zo}JtQT>*A5V_%F&>$9u76eD~-KaLMCrB`J>BZG>5_#9O~LI_h6YAHZ0U^)|^B*%cN zywS*7Gu>7s0YuxsLBopp*Dq7iOY-71bYzA^u5AQG??3AG@BYcSOkdQ<6I!nbA5}w6 z-5BQS(P9;lsSG7~W5$C 67hK^+Ef4>|7^vl(LXfg6DTNcca5mD*iEZoj-`Fnz* zvN8F#ExB^^74Zyn+Su=$vj9yG_k>-y22rz_d=3KK_EzhJMfsiIzk3PHy_xWc!c?^d z)tKJCRWob8&QoKnso}Qym^Gm|tt _#1Tb o`HkdXys&LfPE(KGpU(9z7P4#JKh-Y(McF^X=YF z>8PGp=zQ@Ozven@)uT{oeMBr()YT5O9qMz vq#xw^j=JM*ys`jm? zwThjYBBUo*>82ZsjZH}^;kvCl^T*=*xvtAdw!P&}Hnu5)(x1-T{5ZmgGB&8{c@3dq zrF&^Q--e1uY69!b(=igV&Q^SS5?ANz)wW>2)YB#06K8oP(ZLq-QGr`({ZiS)!{?Qu zA+f0(>TvBNN6|sIQcMNhpL7G!Urs78nC4F&lg$}8zYH9FXQXF!F4Ttu z-Shbt!j;0_GncY;9nnf7^-1a7qFy3BA@v1CW1cOoVpuQ%o=`Bhk9tIsdA|7k01i*m zY%Z%2;R`N%#6nEI^;wCmd8i8YJ*LJq%Dp+z&WNAxK=FGOl=jp6s6>&3Uur?cy1?fR zy?O^{EFMhbQxRg1o|2aHKZB+Qb_9itg@PXb|9;zg@yr&du?2oXbW&vm&J~)~5-Zj6 z*n#w-O43qOE)ses7wLn(Hb&E>O9QxXu?wX354VMCi8L`Sx2s$HI7K-59xa>jA@aOR zs-J_&OzN=Nz7?nWm{>gFOO2G9!yMH@2ATgQsBZ9vGlYAr6uLeE5tgLa>C(GZ7_QUq zVNd8wL(ltIV0M!zCjWPXz7VnCG^cu9FD;GA0X2?hblwxjEX&9*G6r4`b$r^y(kT9 z?Ay2ksl?$TX}ce!E3sUTg1=PTduadr9)4nu$N{Fg{HYCI@n3Qfqg%Ha@C0BIeQEN) zSYbWvy+g6%H=|*k*Q4appzJG{*49sS`K$iNt0dJ t&AbEQWPn$6E<`{il_lua*is5x6=s;D53EUOY#!@QKw0Uu_Y z@p12=SVHjim8R%W3|X9{X?3L?JKiqj+>ORD-O#_59Y-qSjF|4-+m#s|R7h}@`@>OK zL~8WC1Y3Wd1XX1&k-WgvoJW7axdT2H@ocT$(eA%9K=3uf3I3X9gmbuZYJ)9`znXee z-!*)rZgOCQM}Z@iuqedUYCv#edC1|cKl7!h;C+gjRqD{KnXpyTMO=wwQ^17nU;TW8 za|hO!W`P%rZfVt;CDUY(O@o)u{D~4Uy=|NID?TIqvAK&{wYFY%jr-dhd3J=S=AUCK zpXWX;$hYq;3W}tLX|oD)xke8cXM6Aa=Q#Cz-AmR?%AxpJXcevW|DO~anNQ<|<@-$? zWTGN={8T+2dr6J=$6%9-e4`>Adamt8F3fk?95&I|YV^LKYX=+a<#VTWM_1D#u_VF| zupUmg2Ot$Z8-olyfVYloEWTCzCf1MC56UQ<#wz0Ym59kvTNcI9I}kZ(m3;*Bw6l!q zd&6A7BNOgz%Zf`w|MBDDPPFR(J *V@SsPL%}-0I0OY0gM3Xz7|I1W}|1asKn0D{9Z`82UeB^@7foylwx@Z#E7! z2emv?`t8okoB2RfM?u!`FV|1&S;4TQFE^%cGm zJ*{ALu7Cf8?93LUoSk(n#g17(OO`y}=6P}H8(s;ypIr_o*!Bc(8vncN7R(n@P~Uu# z{q>3!Cu*2wtosIO24-19;PJcFFI)Ll?k+Jiu{l}Z*`aAKHfG1P-?Pj)7`P_dk&yp) zzB$k{GcLxfCE0Ab2Yw{x>Q^;2;~I*H*@;*_G9GmL?%Pe|RzzJ!*FUdm|9f&UcLwuZ zo}%h)s#FEl|DD8h3h-;hU4+XcQ<>?rRPJrvtL2%9xD8Ko+t-*>Z& P!wDyXqwzDzx?gS)2KZlO~xKW`6hDV7(l9 z!!uuC?DyJS&Mx_1quK;o>~HqPgg&gWuq|vXLSx0?)Cm5rrvE+&gA!yGXHBMoLE*tF zZ^k*)i0-|AasTql;I?tAP-Cwz7_v)E^2n{6COZ3>Bvuer$F9#B&VH}a8ByX+TaSj9 zV{J=|{A~nAJ4clD?!Ut+`wL!>MW{hUgsV{Kd3d1uYfTFK%VomqF8TOmy!UYhFACRQ z$-C0I4OIB!Ne8Nq@BWmVdq!#{vTMxRtjLmANw>)&`65G+d}+K# 3P&u{?xJZF#`h~((R`7q=$&IJgSkJ< z)ck+50m!oCC#T-U{-Q1Aqv*VbK9}%NEciHPms_TJMSVQFqRfcX+{^C-F8hUbDlcpr z&2mbZ`~P2cU)fgG_Qk6-f~0_Shjd6INQ-oLNVjx{G)OlH8>FPWkr3(bkVd*CrMYvT zqvwC0`wgzIlxMq`bFIC`9Ao@qeAVolu*O!9S{od+JH-&7o~Qr2(NS*@&36Q=DIl@V zM&v3O)Pt2q+2WFB7BQ8Y9+wM#tbGU7r|0Ka^YoURZx`o6-kfUxe!G`^v@?#OEq$(( zRxp~HRaTkELD9Q87xwH=1TiqcK&n*U{XJ&-vO6&mlb_yIWq&Z{2~1QmAJ?EeBRa1N zR&L4ZE>($TS^LoW-V&{&{%s {%?5(dSPj*;0#X_i#_{nmtMG?6(*jQXDh~ z(Wt*gaxTItn3d7!=nf_fHb%1OQZiv@PER;1wXYIX#}}D-X5Qqq+7JDW2raS(-@=LU zUM9} {qf6el6@(XhJ*IXwN5|uC1ub#H9R?XSk&v zH+ZKMQZe&(WAr#x8B_7P51yFt6yCsWCiL#VRYRbw2I@xdbJeTuoM>Y(aEcxDQ8XQg zI87?Ma0lQHE%T$dQvnbS#V@8;%Rxtu-RJfeH|D+}6~8$ABkmwaVkjiSq{mG0utG+& z_!$RPwSxz|zx-)oLD>ir(W@U_vZ3i{Qr&+-9GwwNfW-doI;~JF=wFX^${HEvFjfz; zL%Y>|^u;9 ?9yZ$0ab_mhyI&bT=PugcaDq(o z{m+RmC1oPDT o-u{B|#xEhd0x7WSP7(V+aU 0f)wBQ?n0E9vS& zS{xxu^(h$TIqki;SrPMvN^?%z?zVxr ep7@}8wThvsaHEEw33qr`}^>eFfdwPq# zRF41DYzWMLRnPc$c(CC@!lR;Wv9q^ !>uDemvxGc&(#JE76xA^k)DQw z+iL4~L(%jRfX_Kl1bc^m0`tL_7Yx8mvzmdQ*Pqx;K2FZexT-`P9*OoEEV6MYJt1V| zha=d6N#K~y@W(gPRqb2Y*}eY6%vpR;=D2HZepS0utT5`*?qz87_lRYuQAjfEVZw(a zq~w8W<>43Dk+2vAJ9n)cH z^7Ab%o|b4ZYAnFnC2{q=?D<=>vV`w%w}iITD2`gVguP_D6E|&3xRPAVQ`5eRkq<;^ zrc`xj7?){PoVnp*oA_ji;o&)Rs|klI9N`E${5^6 X_Q}j?WKs+aDVKu+&D%e5 zxa@1pN2fr2VfKUG@ByCV26(f$@Nm^gqTccM#DY0{? IM!>Z z!Lf5t3@~wLwCT^oKRXJgFL_NexS*-oRI7hlZm!HAi}RR=-uhB*VR%16l|IKFH)4iu z08J!pCY@!fDko=z5V!W|4`~F{z%(!>OfF4x_HBT>V%q)#n8FPyoh~?z^Z|E6MrgGk zmH=vXz<)7Iw+}7>-OtTo!?S5X@}X-e#^7N#9>>LWbw7?u* q31s^JxFc)8OGJ zUJK? 0@qrV2|JYR2f^sc)q&N*?i z(swyaMEFtAy9$oyWgETMkqrG>E^`Ce;quwo$XG}5FGpfOYB3qf1`yjiUc@No{*BQA zA6{#Igoya(w0L$^GL$!0EdVm*rme_=LC~?HvMo;gPe)*9&<7ndr@6XIapluy>N&G> zejq3rCHts=jXwci(d4JhqK2c;NJ+FfDjEe6ikw=52}*|MOBEx}xG$PYY{n7H#BA?Z zN3&=0XC8|O{|(?x5}z5n J3S zySqGbgV*5@cVgcg;YQoW)w*1v{P)UQXU`5UuBiMszxQ`8&o*fgd{tH~6x~qWpB2e$ zdRiS_8c`M`I;-hE_0edW%v5`NgMvQ9+|FURs(R()k$ADX&OgLDD7zyT8P&XUg4k7E za^a?HV-2 0a-KCAa>xk-J_ z)W?v*w42y-?&Bt9v|FE!4xhwg32!y&Lu`%DS@vY c~&9YtW;Jp7LrL{Dc`l;>q^<1l)dakCKTUk+J9iX&K6>K=F zLFz2xo#w?Xd`_9QAJrI!%9UxT4p9yM?**cVVY_dDd;j~^UJQ=Fid-{Zx|E}H$^3Ox z4W|!tX=Y0qzf(T* hsD)DpV0B@=8SIE WB7Ah2@arj3|-~h^V&8RJ}>I%Ehfy} zXz5XHV7=A(E&o7==aBw#C~2C7M(V(l-@@P|Zui7P-ZE)UXJlsWWt$Ij1GDh(-}mP^ z5j6K-@vh5S)7%S3CJj!?QF)naV%EzO**3--FzG>=cwLrL_UYj?p{AD064kM#so{&& zqf?ziJr{<;hyE U} |mNBj3xQxJqM za<`@bBeC`qk$72>g1!<#(H@Hk2OCoo;@Wl8#p5SwuK8DYJwqq)gV!=fPDQHQ5j8xS zxq0>(;o+yRj+9D06Bz!@-2Gp(U?EjbgtK~bGN4lox8^TuBzAOLp`sUmw)$43WoxOv zC>xT=y^V#X|7LGc-nn}nKb36rQx;7e<20>y0|sA 0kAzwaj @qk@h8Z>8eQ;`wvwWo?y zC7JkB6NZX=Og^IiTk4Y`C6Iz@GS6eA>W~42rcP1i$C!pxxC&PUSQ#Tfz02hH0IMR? zDP3ndh~_x^u@nG=l2l$sWYD@SfG)E0|Fs-Z=yEV@qpOC)2%joQY#GBQ*bEcHDI{un zOwIvzz;dxfDH$h&Ev*fQVBGYJ_XV?x;qsB)$9C3mRKK^?I{W@SxTSv{+}*&FXK61- zBlr$K_D6hqJNYqwd%suYGrQ-W 7oucGr zIVQm&J_4)2;V(LAIMTK_^i%y3jeqa(88NuS-aU08ynE{(K8a>?(52D#gTReIs9w;O z@38=fqSWnS6dnln#W~=m8uGb#PkL*)5*cagsp&N2&EKN~;f0@_QQ~%_daB_G!hjsj zez)waRG4agypD0$l$We)heea @Eth ztHkS0M;qjziKw#YrLnTE+lYYg4N?eG Sp$fp1IIWJ_;*;6^v?S9B*E*u;^T|n*6(^{fa|9gsHQG;fx*!u{af#Aeq zlYFIsXvfF>c{(d}+J8SW4sExd9ZD665%?K#(Cz(l&}XJFjFi5P5?}rIp?y9>3Y>k> z;+aj|PycB@_X8Y}dV@(m#iX3nQWc%bzt>4%1do;4-Kq*3X^!B#o{{_ck|82{R6s8933t6w!0MAf-&2T@J2zllSQp!77iEeTbb=%exHX z;0%sJGTV|8W|QFd|DL=5>ttk9x5_oqa!lr{!rEF+TDK<$@}&9; =|c>ZHwg8z0p8S!Kz*p7^J@Xn+fD{K zMtURgIZ&S`r32p%wP`J@(uLpI$QcpuB;%4KqzKw3;RS(}!RAxqrQTWmKNS!e(lZ)X zb3+#>nwk1<%`gC{3Cy7EXaIg{u)OrpvS$HMC)u60725liwYAf#uN!WPS-%U;eu6OZ zT4Ye$a0n%S46dYilQ3p^W?LdiMxyx-!NvdEBO2BfCVtyUUZ4jw26Zh5!wA&&`5P>H z0x)pdIvINJGhzE)R5ZZr$a6U!sOX*j<{@?4Zu@9QRCppS^r5}loU-Er+R#RrL$7&k z(`u?imq7{qp}aNts{PyC#q_JH+0P9^u%+`4Kh aNV`#ribOAk+*c75 blgS6em9{@*(Si*m5yYGxF|8LRzS{wXyM5%fe5b@|Tq(q^XH-wI%-rZBdPY z=?f)pHFoJm4N;Z-0>XiQEW`Z#l(pMGFQGA<$CBu8SXMlEC;^P@CrG46#AH3Tc|;Hg zhHGc^tjg#Ezw3D(P(z6&&A{pfI#9X~kO^Kutwo^>E5KQ_w&x?N2R;@^?)rA^@KwN- zX5Qt*l=KLxgnP+NwDlBnG|NA;$q6uce0Jkr5o~f_Y~JglNq6VCq{eYBZAqg-!s6^E zu4*}4#}|^b!y+6}*#81r2EQ=rtSUt-j)A`2T%?PMXGc5K$^w9+y_#mANF!^m_{mt+ zOW^=C ^u_Cm >c`eprgj3sLQyE&vRV$)8#cX|HulF}52up#)JPHMkkr8-< zern-+dsuWL1;?9YLo(Ch!pNmzXD3O{7y9E}DBsOOcE{Indbsg%X_i;ZCcNA>y1NB3 zK^G>BYE6<|a;I$%RMn)sibdU#$-Cl7Bi(xmc{7y?sz(2XpKR!sjgn%};=X=O(FgkP zLpFvH&@vf{bOws;0bxX9D(Gx7sGjn$?ZGE}>s=@Aa(UamN8{zs@S)wct6Y|ina#yg zp`fU}&u%FD4&1m5qAF1uZ|QqV=xd7)XaH->in-)t|89Dag K|d+-2!HIf6MPB1|a56+<5u=Dm$FHYL@84Fn4+|l|y#HAhq4laj&$#9{}|!l?8Ns zBgMrt%x}KNNA6guL_)|Y(R@@?qn{9o5lc%L8+qI|$W?_e{*6BVAF$}8E7u_2>il |t&u%dJuKIpgFN(`$Syd0|U%5MN-FS(-g_jdoH4O_7yj3>YmdmiW z{=4XQIcUYC{{B-qqATa|;e2S<@nxDHEOFgmF2I@32*0nddXTF6fZBFL%?8a^z9eE? zk$jBO*}uTxXDc{_npr9Bch424^nNxBU(~zWzJJeR%=?p|3q!j_H@TPPZ=&}CBXspu zo*iB`>VI2#orwvOKQnX6wSQkJ=wbB?dOiew#W49BP5_h^0ouEQqJ;Q=_yS-9d9R_J z_Jtt2|2=|HGjJ%L;m`d=?m&c~gNJ5>tIT5ff5QckK2WlL-ukh_VM%PfkBxUi^5XCM zFj6GH-Mp!LR-K7jIe}8j`mlL!ym6{WCujU$!9#`Y=KEuAj!*yUTI&)3IYFB%dxn*_ zofP4W6pWmh|GSh%%!l8xpv%%%iZ(h^3>P@7`Ag8r6NMYuYWQqtVgu^)gp5t|26X&3 zs#&>Xnh6L9a>0N{fQ*MP5a=y3&yw-^wLNkBfRUC7#vhOsxsMb*sR#N!?Rgsu3(bOy zxoWJ0 HPv6f&;Y9t7lBOOh9Fi#(2=UlF1>0B)a7v=CHY>hsjbZ*8iH24T;unB zkcppCGAaQ$$_!r1jekE&T(lq}cWDwW{#LobgU1iGT)y3r_l<+9K)=Q6&Wrf~dhH1y z=l4VBGNjqmr2^Hz>$jI@Ws2MPinkbp>AcEdBEr)jb{$U-9`Ejj #)%2W$NY zz&sH;jzEJ(E}e($=CMyTrXw^wPo |BuJd~z2XcOjf_#wOB#|1i4 qU*-b1 zSPIK!?@b9omGMvy^V^I1wu5V_fZI120}o)qNi%w$Hs9j6i(4n#L31@kwpSdD`(xX+ z8&iqTx`2>tf5MB48FZEfl#>3XdH bAi&Y;^&rhn&R;r>T}V{~= >}yD{ibR{Kk8 zTRq(8rIv!Ag*!_;IJec6amMjRXLhFbf|LK<=ijj2ciO~k{KWNNvkzst9ZL9`ae?3b z_6NR36lRx86wlqs!m3%raQJFw)-?
!nYh1dz za-7vRmbb;Yd7tt=EAsX1sUUWIpLJ*9135TiL3q_@OYXCz_GGHQ#bug!X=9Ft|EKUB z|Jr5=f0O?3mr=Hj&LK_bT0!DUn`rH8?$Xh#jqk;y9J5nfg)LkFSFgVLcK(Fz%mtFE zpLM~DQ4hXcVuzMZk8v#{AO|e5mS>u5O-c`E=(5*66DU&?ciw7^dc6%fO*oeFI#nEB zUM()8Ozp-q@)fH$wfy33V{&%!QNQqPJy=W=EObNXI4CG-Egxlr9DmjzhAb!Ey(-aC zlxOLrYm4$a3U^7yRk^yt!x0hEVeFI>dj0+=P5^GH_t=KxZ7_n=FY`GqHU+}M2P9Un zOgC!M4H2EdXa#BT&oDAd0VEmIK$%aZJ!~KS6iGYPSSdMQc%xEUJ|EwyPPo2zSW8=0 z`~_+12;?D>pG@T2nn}K!Ouc;60Im#n9e(rJaqj19By%~(HO LB z6%oUvmD{?0=iX&A%;55BrjWX%2c4_Iu}MF(=!=9h`KHzi#%Cx=5BpjOg_BFw+Plro zvi9Yx>p8=2xGlPF#JBE&=m*{LTkVZ@+xzY^zcJTiZD%^AVxQCwJUeVZR%{pBHG{b} zpt{ed I h$w%GjPp%Wb23*omOF5{t|3=O`!|O!K z=&h&LyC9r6kaKnI?hfkv-kpm{K2Q?~e1c*_VRDbmN>*e&0Mrrp-g{3w41t|inz2&s zB!KjmOz~a9K-)r!drKS0HP^vtjLJH(MGF+Z*!-#RGmzZV0u8@z3Db!{20(Pw-oKYl zAE_?NG2QPn!{NF}kE^M!a^y)CZ~>xFF|*EE@1^Ot5aJCG$=6-N 0ngd{ z1Pn_Ua#Jvy`zJ`w3A*j>H#A<4XWahCiyXssR?!y8pxUwn!Y*2d(wewh&Wtfoxy)WT zGWR5~v$P%Y;Tu$eGl&Yz#&OH0;B`M6lXP@m>42vHP^*)Ro6h$OKy5Cx{C))dHpn&| z<9yqCoHUIY#5t+*e9L}3SE>)>tC^l8K!3R{_7KxoIc5?Z2kI*>d?BCaT4_iM>(#Fg z^itG~TS7A&jo!c~5h^fnw>`-r@b=X}CP>3KZw_Tq(g2Bp*9jouE&Is0T~$&m?CUZ4 zKZFvyugCTQ56~VlS~VFxpDPn=hFiM^g{#FQA2SHzkrS6dFY9<4Zm9t4M4K^CDl~Tb zJc &}Y(s*A-Zb{o#0=JJiT=yMXgZ;$(c-YN;|U2_5JKEye*esR>aS4Kl`5$_Am zE^K0vbm*{vM$#Dv%lL%Ey4)njJ=Sd%U`xhSyjNV_`U&Xk_%HAk*UqOgt@?mY)$rgD z&?1MYZa|GE11&+tl0_=yv_sU>R> )n~EN{;;ygi405!R$zBBQ2S0RM7|uS z?CGs^GLccJ#qIRPrFRoxxJT}c_})7X1l-vzgQ>3(10Z=YyzvDU_q>k}iqA=WriS)L z^f%x8)68k+Mv|>2Hzr{(Z>?O=6Z%+hm3pJk(CiCzVPSI1nYVzxJOa4|pyiy8g5piC zlJCk_wY}l!6SVZ< Lr?Mo$UP^z>K$l#Cx<=6}IQ z*80`Ne{b~h*bhfRhyibI%_zv0F@&<6UP3)O?p9) g!ZZL(`+tJx?n>$w1 z*P{4`?@x?{AkEAjY|H2ufOl +8wOx%s%Yk{>04r`%^%W#q`S;Q{aL+!%qQ#75K zz3K~b&l4w5vuX6mguMMO;sRdzjSFRYMxLhoPgB^%qu|}OnICZsY1l-^#)H+Mthk72 zV1~ScIR+5b(5tk(VFvXpYvQS(#9~sD2ha 1ByWip%xEKVC-GBCOg&iGnz5x~`9%nA?#dy1z?f}QkfvWWp5n(A}4lb-M3 zJAe1HW0p9TLF{d>knf5YZtI{#-7BNJ%keVR-_l#o6NqC!otrb}kbh8xUqX(Bi^-mO zr$nOnU~zx;y+cNMdIAwwX*)?Q*C6T4vLivAQZ ^XcGR;Lz z&!=AoWjnL0JGDnIW_!Y2q?WEj!dRz-?D2OAs=X_|KtPIoN3xk*nb7Kq-}qqJ7YCXI z^0q)bp*Ry>Fx8%N>~;LLEBs =2b>F|kCB{w>V+KP8U#@ S^V~ycixuo0D04bP!_9 zc#L}k@=4B^XHT4<4>_~?c)kKR`nu>!VWalTdx=RgEA$tJA&41KX-0P;fuySbBs&Uj zv>&*Lk@}BGN_xH`9$ll;MIaAT=lQ0;#m5!6B+t#Tyr@Upk`%g*g*R3f@ ONhB3DGz;&IK9yZnx^d4Sqv);RRx=eAWbu85B1wtFs&x!Zm zOo=n@s#OXNUv@-rg*N0wl~isDtn`GN#chRO$Dxn#^o;sggfo9{VY+_*=<3+I8nSLv zqoSxN#F9S~d-UsceT354jK610VjQ)A |sd1E0&+Pjbto#Fhh zgY=$kH!cnsJ1-}_Bs-@3hRzUSu*T>XdMBCS+WOTw)N9AdOAPl&CqUp<5~uqr=k41_ zc^`Kj)QLWGg?e+Zz;qy>QsXYcvTWi)xP|Z252A9bd@5+S4$rbH?=zfmH{9WCe)s%j zg=7CV<+dvE#Pukq0C51X!h@IiCCgk|6MyNY6In&>LCh`3U;)2B`(3(&OjD^lExh#b zx%YZ9$w89O{ecK|5*;ZqO_E?Fu)%g+P}FEo&ppH$a}$BO>kVoNi%@xb-{A))YNP$= z9+)q$=PYZlMpnLvpOx^r-@iUR{5^_BpSAuw&Q)(`nNCpIc2|V(vv*7ZgYzbDZmV7P z@ctH$#&vaHqk!Xj{DXi4k I*Pe6u{whGC NBUWA!H74RX|xK9=QhTB+=0yfl&1FJmmi`ntbP%kBj0Um9SL)5=7BBS?(JdH zFy^G6hm!B^HiU&*X4!Q?+3C5{3A3GDhaKux`_tA2bc2C#qn*#@R*dcK?hUjC-Qgqm zjGp<*yEa1uQuEo6m6IH!jl7%>5tz30_ZaQ|ujh}KnWK))qU%J=BD5znWi`I3tV!(? zMXJB#=D1lEp&dFRoqrJmCV^SVo+eXyvs~>j-*GsvxJno$?&IeTPbLb zX5VH#ot2 74jtG>SRW)uk~Ty zOsM+ND;RsJt#rpI_&BUzk`yay@J7sb39-bekEtIeb{63Kzd?RRbqDMu>rSWQjF-<5 z8`c9PUeGqDr{V}Cg4rbeYa)hc&vG~g>=Lfo*O#t}q`QJE?$7dtYS-{<>(bFe&X3^M zpt>@yvve+GGl_Z#NMITt4S;-TK_AO69quFhcx 8=u#t!} zqi2LRv9SpPUn@3#zXc9j5$*6)2#CM-v5cja>Wks0Jy@A@kZVsz;Lq5*;CgP}NTnn> zoNUFq9+uw68jKJ;p7klmZ|phkvU9*vCN2n-vTZq6yBU5BgHQ;y_P&5DGf3p_kiNVi zPJ(NlNa9HX5nTEM2lC+pqJJO0|1cTM+zxQzvbLEUFhxYq4%CX89D!?|I6>^%5_s1@ zmScX&Hr1A1l5qR0W~Tkzk<-J=-3PURP{^fd-zEVO)r}1`KC5JZ>+zS4ck75Z+cI8f z1UFa0#%BmZakognj0St*_$-gMq0=dUM6+&Pqa$y*<9q0&QkbqB^bE+o+O|tM3cL_$ z!9S7K_D2-T1$r9EQQr@kDHVapBEfP`hKIuy#gIre*x19f$^{o@%k?|ye{U-#%<$U< zpR@M|T{%C`l0+1dl>ONNBF6As7Qzc{&v~H7vJtv5=%AyLs2l4xsIEopFrCcZ8mSDW zPKiUL42R0-29}TT+!Gz%))xaWtFhgz$JJb2j}Lafq}f)PMe$JvS`b!zZc)^qLqpaJ z75Fm!f% 3RF6O{mfHmf+`~pH56)8U;1qMaL*@Zj4}m$B?*1IM(ha#hwh>iZLpi zaBl%YFVF}hQm_hBEuzLBeT1syUORtg1zjJ{GgMrR;~>gwo3dHgL6C9_)JwEqY1-^- zq)Ee6quT6Sj=efj`@IcWv>Rm3gXh9h@WUz%5>UTLO1L#hxBq5Yz