From 1389c5296899a7cca0f165f0cf74430789a921b2 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Tue, 13 Aug 2024 21:15:39 +0800 Subject: [PATCH 01/11] feat(primitive): update --- config.ini | 16 +++++++-- huixiangdou/primitive/__init__.py | 1 + huixiangdou/primitive/embedder.py | 42 +++++++++++++++++------- huixiangdou/primitive/llm_reranker.py | 10 +++--- huixiangdou/primitive/rpm.py | 36 ++++++++++++++++++++ huixiangdou/service/llm_server_hybrid.py | 35 +------------------- 6 files changed, 88 insertions(+), 52 deletions(-) create mode 100644 huixiangdou/primitive/rpm.py diff --git a/config.ini b/config.ini index 44ed5ee5..be8e21cd 100644 --- a/config.ini +++ b/config.ini @@ -1,10 +1,22 @@ [feature_store] # `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` reject_throttle = -1.0 -# text2vec model path, support local relative path and huggingface model format. -# also support local path, model_path = "/path/to/your/text2vec-model" +# text2vec model, support local relative path, huggingface repo and URL. +# for example: +# "maidalun1020/bce-embedding-base_v1" +# "BAAI/bge-m3" +# "https://api.siliconflow.cn/v1/embeddings" embedding_model_path = "maidalun1020/bce-embedding-base_v1" + +# reranker model, support list: +# "maidalun1020/bce-reranker-base_v1" +# "BAAI/bge-reranker-v2-minicpm-layerwise" +# "https://api.siliconflow.cn/v1/rerank" reranker_model_path = "maidalun1020/bce-reranker-base_v1" + +# if `embedding_model_path` or `reranker_model_path` using `siliconcloud` API, give the token +api_token = "" +api_rpm = 1000 work_dir = "workdir" [web_search] diff --git a/huixiangdou/primitive/__init__.py b/huixiangdou/primitive/__init__.py index 7e5ae84b..773e3a92 100644 --- a/huixiangdou/primitive/__init__.py +++ b/huixiangdou/primitive/__init__.py @@ -13,3 +13,4 @@ MarkdownTextRefSplitter, RecursiveCharacterTextSplitter, nested_split_markdown) +from .rpm import RPM diff --git a/huixiangdou/primitive/embedder.py b/huixiangdou/primitive/embedder.py index 146222e4..00027da2 100644 --- a/huixiangdou/primitive/embedder.py +++ b/huixiangdou/primitive/embedder.py @@ -5,51 +5,64 @@ from typing import Any, List import numpy as np -import torch from loguru import logger -from .query import DistanceStrategy +from .query import distance_strategy +from .rpm import RPM class Embedder: """Wrap text2vec (multimodal) model.""" client: Any + _type: str - def __init__(self, model_path: str): + def __init__(self, model_config: dict): self.support_image = False # bce also use euclidean distance. self.distance_strategy = DistanceStrategy.EUCLIDEAN_DISTANCE - if self.use_multimodal(model_path=model_path): + model_path = model_config['embedding_model_path'] + self._type = self.model_type(model_path=model_path) + if 'bce' in self._type: + from sentence_transformers import SentenceTransformer + self.client = SentenceTransformer(model_name_or_path=model_path).half() + elif 'bge' in self._type: from FlagEmbedding.visual.modeling import Visualized_BGE self.support_image = True vision_weight_path = os.path.join(model_path, 'Visualized_m3.pth') self.client = Visualized_BGE( model_name_bge=model_path, model_weight=vision_weight_path).eval() + elif 'siliconcloud' in self._type: + self.client = { + 'api_token': model_config['api_token'], + 'api_rpm': model_config['api_rpm'] + } else: - from sentence_transformers import SentenceTransformer - self.client = SentenceTransformer(model_name_or_path=model_path).half() + raise ValueError('Unknown type {}'.format(self._type)) @classmethod - def use_multimodal(self, model_path): + def model_type(self, model_path): """Check text2vec model using multimodal or not.""" + if model_path.startswith('https'): + return 'siliconcloud' if 'bge-m3' not in model_path.lower(): - return False + return 'bce' vision_weight = os.path.join(model_path, 'Visualized_m3.pth') if not os.path.exists(vision_weight): logger.warning( '`Visualized_m3.pth` (vision model weight) not exist') - return False - return True + return 'bce' + return 'bge' def embed_query(self, text: str = None, path: str = None) -> np.ndarray: """Embed input text or image as feature, output np.ndarray with np.float32""" - if 'BGE' in str(type(self.client)): + if 'bge' in self._type: + import torch with torch.no_grad(): feature = self.client.encode(text=text, image=path) return feature.cpu().numpy().astype(np.float32) - else: + elif 'bce' in self._type: if text is None: raise ValueError('This model only support text') emb = self.client.encode([text], show_progress_bar=False, normalize_embeddings=True) @@ -57,3 +70,8 @@ def embed_query(self, text: str = None, path: str = None) -> np.ndarray: # for norm in np.linalg.norm(emb, axis=1): # assert abs(norm - 1) < 0.001 return emb + else: + # siliconcloud bce API + if text is None: + raise ValueError('This model only support text') + # TODO \ No newline at end of file diff --git a/huixiangdou/primitive/llm_reranker.py b/huixiangdou/primitive/llm_reranker.py index 6f8a02e4..6af5f6e1 100644 --- a/huixiangdou/primitive/llm_reranker.py +++ b/huixiangdou/primitive/llm_reranker.py @@ -5,13 +5,10 @@ from typing import List, Tuple import numpy as np -import torch -from BCEmbedding import RerankerModel -from transformers import AutoModelForCausalLM, AutoTokenizer from .chunk import Chunk from .embedder import Embedder - +from .rpm import RPM class LLMReranker: @@ -24,6 +21,9 @@ def __init__( self.topn = topn if self.llm_reranker: + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer + self.tokenizer = AutoTokenizer.from_pretrained( model_name_or_path, trust_remote_code=True) self.model = AutoModelForCausalLM.from_pretrained( @@ -31,6 +31,7 @@ def __init__( trust_remote_code=True, torch_dtype=torch.bfloat16).eval().to('cuda') else: + from BCEmbedding import RerankerModel self.bce_client = RerankerModel( model_name_or_path=model_name_or_path, use_fp16=True) @@ -102,6 +103,7 @@ def _sort(self, texts: List[str], query: str): pairs.append([query, text]) if self.llm_reranker: + import torch with torch.no_grad(): inputs = self._get_inputs(pairs).to(self.model.device) all_scores = self.model(**inputs, diff --git a/huixiangdou/primitive/rpm.py b/huixiangdou/primitive/rpm.py new file mode 100644 index 00000000..21e01cdc --- /dev/null +++ b/huixiangdou/primitive/rpm.py @@ -0,0 +1,36 @@ +import time +import datetime +from loguru import logger + +class RPM: + + def __init__(self, rpm: int = 30): + self.rpm = rpm + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + + def get_minute_slot(self): + current_time = time.time() + dt_object = datetime.fromtimestamp(current_time) + total_minutes_since_midnight = dt_object.hour * 60 + dt_object.minute + return total_minutes_since_midnight + + def wait(self): + current = time.time() + dt_object = datetime.fromtimestamp(current) + minute_slot = self.get_minute_slot() + + if self.record['slot'] == minute_slot: + # check RPM exceed + if self.record['counter'] >= self.rpm: + # wait until next minute + next_minute = dt_object.replace( + second=0, microsecond=0) + timedelta(minutes=1) + _next = next_minute.timestamp() + sleep_time = abs(_next - current) + time.sleep(sleep_time) + + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + else: + self.record = {'slot': self.get_minute_slot(), 'counter': 0} + self.record['counter'] += 1 + logger.debug(self.record) diff --git a/huixiangdou/service/llm_server_hybrid.py b/huixiangdou/service/llm_server_hybrid.py index 35588030..c2880325 100644 --- a/huixiangdou/service/llm_server_hybrid.py +++ b/huixiangdou/service/llm_server_hybrid.py @@ -13,6 +13,7 @@ import requests from loguru import logger from openai import OpenAI +from huixiangdou.primitive import RPM from transformers import AutoModelForCausalLM, AutoTokenizer import asyncio from fastapi import FastAPI, APIRouter @@ -51,40 +52,6 @@ def build_messages(prompt, history, system: str = None): return messages -class RPM: - - def __init__(self, rpm: int = 30): - self.rpm = rpm - self.record = {'slot': self.get_minute_slot(), 'counter': 0} - - def get_minute_slot(self): - current_time = time.time() - dt_object = datetime.fromtimestamp(current_time) - total_minutes_since_midnight = dt_object.hour * 60 + dt_object.minute - return total_minutes_since_midnight - - def wait(self): - current = time.time() - dt_object = datetime.fromtimestamp(current) - minute_slot = self.get_minute_slot() - - if self.record['slot'] == minute_slot: - # check RPM exceed - if self.record['counter'] >= self.rpm: - # wait until next minute - next_minute = dt_object.replace( - second=0, microsecond=0) + timedelta(minutes=1) - _next = next_minute.timestamp() - sleep_time = abs(_next - current) - time.sleep(sleep_time) - - self.record = {'slot': self.get_minute_slot(), 'counter': 0} - else: - self.record = {'slot': self.get_minute_slot(), 'counter': 0} - self.record['counter'] += 1 - logger.debug(self.record) - - class InferenceWrapper: """A class to wrapper kinds of inference framework.""" From 5027dbf7d4eab1f17d8fc78f59fbcbfa965045d4 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 11:43:38 +0800 Subject: [PATCH 02/11] feat(primitive/llm_reranker.py): support siliconcloud api --- config-cpu.ini | 212 +++++++++++++++++++++++ config.ini | 2 +- huixiangdou/primitive/embedder.py | 36 +++- huixiangdou/primitive/faiss.py | 15 +- huixiangdou/primitive/llm_reranker.py | 67 +++++-- huixiangdou/service/llm_server_hybrid.py | 2 +- huixiangdou/service/retriever.py | 8 +- requirements-cpu.txt | 32 ++++ unittest/primitive/test_embedder.py | 4 +- unittest/primitive/test_faiss.py | 5 +- unittest/primitive/test_reranker.py | 17 ++ 11 files changed, 367 insertions(+), 33 deletions(-) create mode 100644 config-cpu.ini create mode 100644 requirements-cpu.txt create mode 100644 unittest/primitive/test_reranker.py diff --git a/config-cpu.ini b/config-cpu.ini new file mode 100644 index 00000000..38ba462c --- /dev/null +++ b/config-cpu.ini @@ -0,0 +1,212 @@ +[feature_store] +# `feature_store.py` use this throttle to distinct `good_questions` and `bad_questions` +reject_throttle = -1.0 +# text2vec model, support local relative path, huggingface repo and URL. +# for example: +# "maidalun1020/bce-embedding-base_v1" +# "BAAI/bge-m3" +# "https://api.siliconflow.cn/v1/embeddings" +embedding_model_path = "https://api.siliconflow.cn/v1/embeddings" + +# reranker model, support list: +# "maidalun1020/bce-reranker-base_v1" +# "BAAI/bge-reranker-v2-minicpm-layerwise" +# "https://api.siliconflow.cn/v1/rerank" +reranker_model_path = "https://api.siliconflow.cn/v1/rerank" + +# if using `siliconcloud` API as `embedding_model_path` or `reranker_model_path`, give the token +api_token = "" +api_rpm = 1000 +work_dir = "workdir" + +[web_search] +engine = "serper" +# web search engine support ddgs and serper +# For ddgs, see https://pypi.org/project/duckduckgo-search +# For serper, check https://serper.dev/api-key to get a free API key +serper_x_api_key = "YOUR-API-KEY-HERE" +domain_partial_order = ["openai.com", "pytorch.org", "readthedocs.io", "nvidia.com", "stackoverflow.com", "juejin.cn", "zhuanlan.zhihu.com", "www.cnblogs.com"] +save_dir = "logs/web_search_result" + +[llm] +enable_local = 0 +enable_remote = 1 +# hybrid llm service address +client_url = "http://127.0.0.1:8888/inference" + +[llm.server] +# local LLM configuration +# support "internlm/internlm2-chat-7b", "internlm2_5-7b-chat" and "qwen/qwen-7b-chat-int8" +# support local path, for example +# local_llm_path = "/path/to/your/internlm2_5" + +local_llm_path = "internlm/internlm2_5-7b-chat" +local_llm_max_text_length = 3000 +# llm server listen port +local_llm_bind_port = 8888 + +# remote LLM service configuration +# support "gpt", "kimi", "deepseek", "zhipuai", "step", "internlm", "xi-api" and "alles-apin" +# support "siliconcloud", see https://siliconflow.cn/zh-cn/siliconcloud +# xi-api and alles-apin is chinese gpt proxy +# for internlm, see https://internlm.intern-ai.org.cn/api/document + +remote_type = "kimi" +remote_api_key = "Y2tpMG41dDB0YzExbjRqYW5nN2c6bXNrLTFzVlB2NGJRaDExeWdnNTlZY3dYMm5mcVRpWng=" +# max text length for remote LLM. +# use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 +remote_llm_max_text_length = 40000 +# openai API model type, support model list: +# "auto" for kimi. To save money, we auto select model name by prompt length. +# "auto" for step to save money, see https://platform.stepfun.com/ +# "gpt-4-0613" for gpt/xi-api, +# "deepseek-chat" for deepseek, +# "glm-4" for zhipuai, +# "gpt-4-1106-preview" for alles-apin or OpenAOE +# "internlm2-latest" for internlm +# for example "alibaba/Qwen1.5-110B-Chat", see https://siliconflow.readme.io/reference/chat-completions-1 +remote_llm_model = "auto" +# request per minute +rpm = 500 + +[coreference_resolution] +base_url = 'http://127.0.0.1:9999/v1' +api_key = 'token-abc123' + +[worker] +# enable web search or not +enable_web_search = 1 +# enable search enhancement or not +enable_sg_search = 0 +# enable coreference resolution in `PreprocNode` +enable_cr = 0 +save_path = "logs/work.txt" + +[worker.time] +enable = 0 +start = "00:00:00" +end = "23:59:59" +has_weekday = 1 + +[sg_search] +# download `src` from https://github.com/sourcegraph/src-cli#installation +binary_src_path = "/usr/local/bin/src" +src_access_token = "YOUR-SRC-ACCESS-TOKEN" + +# add your repo here, we just take opencompass and lmdeploy as example +[sg_search.opencompass] +github_repo_id = "open-compass/opencompass" +introduction = "用于评测大型语言模型(LLM). 它提供了完整的开源可复现的评测框架,支持大语言模型、多模态模型的一站式评测,基于分布式技术,对大参数量模型亦能实现高效评测。评测方向汇总为知识、语言、理解、推理、考试五大能力维度,整合集纳了超过70个评测数据集,合计提供了超过40万个模型评测问题,并提供长文本、安全、代码3类大模型特色技术能力评测。" +# introduction = "For evaluating Large Language Models (LLMs). It provides a fully open-source, reproducible evaluation framework, supporting one-stop evaluation for large language models and multimodal models. Based on distributed technology, it can efficiently evaluate models with a large number of parameters. The evaluation directions are summarized in five capability dimensions: knowledge, language, understanding, reasoning, and examination. It integrates and collects more than 70 evaluation datasets, providing in total over 400,000 model evaluation questions. Additionally, it offers evaluations for three types of capabilities specific to large models: long text, security, and coding." + +[sg_search.lmdeploy] +github_repo_id = "internlm/lmdeploy" +introduction = "lmdeploy 是一个用于压缩、部署和服务 LLM(Large Language Model)的工具包。是一个服务端场景下,transformer 结构 LLM 部署工具,支持 GPU 服务端部署,速度有保障,支持 Tensor Parallel,多并发优化,功能全面,包括模型转换、缓存历史会话的 cache feature 等. 它还提供了 WebUI、命令行和 gRPC 客户端接入。" +# introduction = "lmdeploy is a toolkit for compressing, deploying, and servicing Large Language Models (LLMs). It is a deployment tool for transformer-structured LLMs in server-side scenarios, supporting GPU server-side deployment, ensuring speed, and supporting Tensor Parallel along with optimizations for multiple concurrent processes. It offers comprehensive features including model conversion, cache features for caching historical sessions and more. Additionally, it provides access via WebUI, command line, and gRPC clients." +# add your repo here, we just take opencompass and lmdeploy as example + +[sg_search.mmpose] +github_repo_id = "open-mmlab/mmpose" +introduction = "MMPose is an open-source toolbox for pose estimation based on PyTorch" + +[sg_search.mmdetection] +github_repo_id = "open-mmlab/mmdetection" +introduction = "MMDetection is an open source object detection toolbox based on PyTorch." + +[sg_search.huixiangdou] +github_repo_id = "internlm/huixiangdou" +introduction = "茴香豆是一个基于 LLM 的群聊知识助手。设计拒答、响应两阶段 pipeline 应对群聊场景,解答问题同时不会消息泛滥。" + +[sg_search.xtuner] +github_repo_id = "internlm/xtuner" +introduction = "XTuner is an efficient, flexible and full-featured toolkit for fine-tuning large models." + +[sg_search.mmyolo] +github_repo_id = "open-mmlab/mmyolo" +introduction = "OpenMMLab YOLO series toolbox and benchmark. Implemented RTMDet, RTMDet-Rotated,YOLOv5, YOLOv6, YOLOv7, YOLOv8,YOLOX, PPYOLOE, etc." + +[sg_search.Amphion] +github_repo_id = "open-mmlab/Amphion" +introduction = "Amphion is a toolkit for Audio, Music, and Speech Generation. Its purpose is to support reproducible research and help junior researchers and engineers get started in the field of audio, music, and speech generation research and development." + +[sg_search.mmcv] +github_repo_id = "open-mmlab/mmcv" +introduction = "MMCV is a foundational library for computer vision research and it provides image/video processing, image and annotation visualization, image transformation, various CNN architectures and high-quality implementation of common CPU and CUDA ops" + +[frontend] +# chat group assistant type, support "lark_group", "wechat_personal", "wechat_wkteam" and "none" +# for "lark_group", open https://open.feishu.cn/document/home/introduction-to-custom-app-development/self-built-application-development-process to create one +# for "wechat_personal", read ./docs/add_wechat_group_zh.md to setup gateway +# for "wkteam", see https://wkteam.cn/ +type = "none" + +# for "lark", it is chat group webhook url, send reply to group, for example "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" +# for "lark_group", it is the url to fetch chat group message, for example "http://101.133.161.20:6666/fetch", `101.133.161.20` is your own public IPv4 addr +# for "wechat_personal", it is useless +webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxx" + +# when a new group chat message is received, should it be processed immediately or wait for 18 seconds in case the user hasn't finished speaking? +# support "immediate" +message_process_policy = "immediate" + +[frontend.lark_group] +# "lark_group" configuration examples, use your own app_id and secret !!! +app_id = "cli_a53a34dcb778500e" +app_secret = "2ajhg1ixSvlNm1bJkH4tJhPfTCsGGHT1" +encrypt_key = "abc" +verification_token = "def" + +[frontend.wechat_personal] +# "wechat_personal" listen port +bind_port = 9527 + +[frontend.wechat_wkteam] +# wechat message callback server ip +callback_ip = "101.133.161.11" +callback_port = 9528 + +# public redis config +redis_host = "101.133.161.11" +redis_port = "6380" +redis_passwd = "hxd123" + +# wkteam +account = "" +password = "" +# !!! `proxy` is very import parameter, it's your account location +# 1:北京 2:天津 3:上海 4:重庆 5:河北 +# 6:山西 7:江苏 8:浙江 9:安徽 10:福建 +# 11:江西 12:山东 13:河南 14:湖北 15:湖南 +# 16:广东 17:海南 18:四川 20:陕西 +# bad proxy would cause account deactivation !!! +proxy = -1 + +# save dir +dir = "wkteam" + +# 群号和介绍 +# 茴香豆相关 +[frontend.wechat_wkteam.43925126702] +name = "茴香豆群(大暑)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.44546611710] +name = "茴香豆群(立夏)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.38720590618] +name = "茴香豆群(惊蛰)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.48437885473] +name = "茴香豆群(谷雨)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +[frontend.wechat_wkteam.34744063953] +name = "茴香豆群(雨水)" +introduction = "github https://github.com/InternLM/HuixiangDou 用户体验群" + +# github.com/tencent/ncnn contributors +[frontend.wechat_wkteam.18356748488] +name = "卷卷群" +introduction = "ncnn contributors group" diff --git a/config.ini b/config.ini index be8e21cd..f40da1c4 100644 --- a/config.ini +++ b/config.ini @@ -14,7 +14,7 @@ embedding_model_path = "maidalun1020/bce-embedding-base_v1" # "https://api.siliconflow.cn/v1/rerank" reranker_model_path = "maidalun1020/bce-reranker-base_v1" -# if `embedding_model_path` or `reranker_model_path` using `siliconcloud` API, give the token +# if using `siliconcloud` API as `embedding_model_path` or `reranker_model_path`, give the token api_token = "" api_rpm = 1000 work_dir = "workdir" diff --git a/huixiangdou/primitive/embedder.py b/huixiangdou/primitive/embedder.py index 00027da2..903ca2f3 100644 --- a/huixiangdou/primitive/embedder.py +++ b/huixiangdou/primitive/embedder.py @@ -2,6 +2,9 @@ # import os import pdb +import requests +import json + from typing import Any, List import numpy as np @@ -32,10 +35,15 @@ def __init__(self, model_config: dict): model_name_bge=model_path, model_weight=vision_weight_path).eval() elif 'siliconcloud' in self._type: + api_token = model_config['api_token'].strip() + if len(api_token) < 1: + raise ValueError('siliconclud remote embedder api token is None') + api_rpm = max(1, int(model_config['api_rpm'])) self.client = { - 'api_token': model_config['api_token'], - 'api_rpm': model_config['api_rpm'] + 'api_token': api_token, + 'api_rpm': RPM(api_rpm) } + else: raise ValueError('Unknown type {}'.format(self._type)) @@ -71,7 +79,27 @@ def embed_query(self, text: str = None, path: str = None) -> np.ndarray: # assert abs(norm - 1) < 0.001 return emb else: + self.client['api_rpm'].wait() + # siliconcloud bce API if text is None: - raise ValueError('This model only support text') - # TODO \ No newline at end of file + raise ValueError('This api only support text') + + url = "https://api.siliconflow.cn/v1/embeddings" + + payload = { + "model": "netease-youdao/bce-embedding-base_v1", + "input": text, + "encoding_format": "float" + } + headers = { + "accept": "application/json", + "content-type": "application/json", + "authorization": self.client['api_token'] + } + + response = requests.post(url, json=payload, headers=headers) + json_obj = json.loads(response.text) + emb_list = json_obj['data']['embedding'] + emb = np.array(emb_list).astype(np.float32) + return emb diff --git a/huixiangdou/primitive/faiss.py b/huixiangdou/primitive/faiss.py index 67d8d4ad..74a4b33c 100644 --- a/huixiangdou/primitive/faiss.py +++ b/huixiangdou/primitive/faiss.py @@ -127,12 +127,15 @@ def save_local(self, folder_path: str, chunks: List[Chunk], index = None for chunk in tqdm(chunks): - if chunk.modal == 'text': - np_feature = embedder.embed_query(text=chunk.content_or_path) - elif chunk.modal == 'image': - np_feature = embedder.embed_query(path=chunk.content_or_path) - else: - raise ValueError(f'Unimplement chunk type: {chunk.modal}') + try: + if chunk.modal == 'text': + np_feature = embedder.embed_query(text=chunk.content_or_path) + elif chunk.modal == 'image': + np_feature = embedder.embed_query(path=chunk.content_or_path) + else: + raise ValueError(f'Unimplement chunk type: {chunk.modal}') + except Exception as e: + logger.error('{}, skip and continue'.format(e)) if index is None: dimension = np_feature.shape[-1] diff --git a/huixiangdou/primitive/llm_reranker.py b/huixiangdou/primitive/llm_reranker.py index 6af5f6e1..78ab14ce 100644 --- a/huixiangdou/primitive/llm_reranker.py +++ b/huixiangdou/primitive/llm_reranker.py @@ -2,6 +2,7 @@ import json import os import pdb +import requests from typing import List, Tuple import numpy as np @@ -11,16 +12,19 @@ from .rpm import RPM class LLMReranker: + _type: str + topn: int def __init__( self, - model_name_or_path: str = 'BAAI/bge-reranker-v2-minicpm-layerwise', + model_config: dict, topn: int = 10): - self.llm_reranker = self.use_llm_reranker( - model_path=model_name_or_path) + + model_name_or_path = model_config['reranker_model_path'] + self._type = self.model_type(model_path=model_name_or_path) self.topn = topn - if self.llm_reranker: + if 'bge' in self._type: import torch from transformers import AutoModelForCausalLM, AutoTokenizer @@ -30,28 +34,44 @@ def __init__( model_name_or_path, trust_remote_code=True, torch_dtype=torch.bfloat16).eval().to('cuda') - else: + elif 'bce' in self._type: from BCEmbedding import RerankerModel self.bce_client = RerankerModel( model_name_or_path=model_name_or_path, use_fp16=True) + elif 'siliconcloud' in self._type: + api_token = model_config['api_token'].strip() + if len(api_token) < 1: + raise ValueError('siliconclud remote embedder api token is None') + + api_rpm = max(1, int(model_config['api_rpm'])) + self.client = { + 'api_token': api_token, + 'api_rpm': RPM(api_rpm) + } + + else: + raise ValueError('Unknown type {}'.format(self._type)) + @classmethod - def use_llm_reranker(self, model_path): + def model_type(self, model_path): """Check reranker model is LLM reranker or not.""" + if model_path.startswith('https'): + return 'siliconcloud' config_path = os.path.join(model_path, 'config.json') if not os.path.exists(config_path): if 'bge-reranker-v2-minicpm-layerwise' in config_path.lower(): - return True - return False + return 'bge' + return 'bce' try: with open(config_path) as f: if 'bge-reranker-v2-minicpm-layerwise' in json.loads( f.read())['_name_or_path']: - return True + return 'bge' except Exception as e: logger.warning(e) - return False + return 'bce' def _get_inputs(self, pairs, prompt=None, max_length=1024): """Build input tokens with query and chunks.""" @@ -102,7 +122,7 @@ def _sort(self, texts: List[str], query: str): for text in texts: pairs.append([query, text]) - if self.llm_reranker: + if 'bge' in self._type: import torch with torch.no_grad(): inputs = self._get_inputs(pairs).to(self.model.device) @@ -114,9 +134,32 @@ def _sort(self, texts: List[str], query: str): for scores in all_scores[0] ] scores = scores[0].cpu().numpy() - else: + elif 'bce' in self._type scores_list = self.bce_client.compute_score(pairs) scores = np.array(scores_list) + else: + self.client['api_rpm'].wait() + + url = "https://api.siliconflow.cn/v1/rerank" + payload = { + "model": "netease-youdao/bce-reranker-base_v1", + "query": query, + "documents": texts, + "return_documents": False, + "max_chunks_per_doc": 832, + "overlap_tokens": 32 + } + headers = { + "accept": "application/json", + "content-type": "application/json", + "authorization": "Bearer sk-ducerqngypudxuevovkmvsbatstjyikvbjdpylfsvkfqcgox" + } + response = requests.post(url, json=payload, headers=headers) + json_obj = json.loads(response.text) + results = json_obj['results'] + scores_list = [item['index'] for index in results] + scores = np.array(scores_list).astype(np.float32) + return scores[0:self.topn] # get descending order return scores.argsort()[::-1][0:self.topn] diff --git a/huixiangdou/service/llm_server_hybrid.py b/huixiangdou/service/llm_server_hybrid.py index c2880325..6451da89 100644 --- a/huixiangdou/service/llm_server_hybrid.py +++ b/huixiangdou/service/llm_server_hybrid.py @@ -14,7 +14,6 @@ from loguru import logger from openai import OpenAI from huixiangdou.primitive import RPM -from transformers import AutoModelForCausalLM, AutoTokenizer import asyncio from fastapi import FastAPI, APIRouter from fastapi.middleware.cors import CORSMiddleware @@ -57,6 +56,7 @@ class InferenceWrapper: def __init__(self, model_path: str): """Init model handler.""" + from transformers import AutoModelForCausalLM, AutoTokenizer self.model_path = model_path self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) diff --git a/huixiangdou/service/retriever.py b/huixiangdou/service/retriever.py index 34d60daf..9909c124 100644 --- a/huixiangdou/service/retriever.py +++ b/huixiangdou/service/retriever.py @@ -231,14 +231,12 @@ def __init__(self, self.cache = dict() self.cache_size = cache_size with open(config_path, encoding='utf8') as f: - config = pytoml.load(f)['feature_store'] - embedding_model_path = config['embedding_model_path'] - reranker_model_path = config['reranker_model_path'] + fs_config = pytoml.load(f)['feature_store'] # load text2vec and rerank model logger.info('loading test2vec and rerank models') - self.embedder = Embedder(model_path=embedding_model_path) - self.reranker = LLMReranker(model_name_or_path=reranker_model_path, + self.embedder = Embedder(model_config=fs_config) + self.reranker = LLMReranker(model_config=fs_config, topn=rerank_topn) def get(self, diff --git a/requirements-cpu.txt b/requirements-cpu.txt new file mode 100644 index 00000000..1c84733a --- /dev/null +++ b/requirements-cpu.txt @@ -0,0 +1,32 @@ +aiohttp +beautifulsoup4 +duckduckgo_search +einops +faiss +loguru +lxml_html_clean +nest_asyncio +networkx>=3.0 +numpy<2.0.0 +openai>=1.0.0 +openpyxl +pandas +pickle +pydantic>=1.10.13 +pymupdf +python-docx +pytoml +readability-lxml +redis +requests +scikit-learn +# See https://github.com/deanmalmgren/textract/issues/461 +# textract @ git+https://github.com/tpoisonooo/textract@master +textract +texttable +tiktoken +torch --index-url https://download.pytorch.org/whl/cpu +unstructured +sse_starlette +fastapi +uvicorn diff --git a/unittest/primitive/test_embedder.py b/unittest/primitive/test_embedder.py index 869cbbea..05186700 100644 --- a/unittest/primitive/test_embedder.py +++ b/unittest/primitive/test_embedder.py @@ -4,9 +4,7 @@ def test_embedder(): - assert Embedder.use_multimodal(model_path='/data2/khj/bge-m3') is True - - emb = Embedder(model_path='/data2/khj/bge-m3') + emb = Embedder({'embedding_model_path':'/data2/khj/bge-m3'}) sentence = 'hello world ' sentence_16k = sentence * (16384 // len(sentence)) image_path = 'resource/figures/wechat.jpg' diff --git a/unittest/primitive/test_faiss.py b/unittest/primitive/test_faiss.py index 45704e34..5a7cbdd1 100644 --- a/unittest/primitive/test_faiss.py +++ b/unittest/primitive/test_faiss.py @@ -13,7 +13,10 @@ def test_faiss(): save_path = '/tmp/faiss' - embedder = Embedder('/data2/khj/bge-m3') + model_config = { + 'embedding_model_path': '/data2/khj/bge-m3' + } + embedder = Embedder(model_config) Faiss.save_local(folder_path=save_path, chunks=chunks, embedder=embedder) assert os.path.exists(os.path.join(save_path, 'embedding.faiss')) diff --git a/unittest/primitive/test_reranker.py b/unittest/primitive/test_reranker.py new file mode 100644 index 00000000..9e2e6340 --- /dev/null +++ b/unittest/primitive/test_reranker.py @@ -0,0 +1,17 @@ +import pdb + +from huixiangdou.primitive import LLMReranker + + +def test_reranker(): + model = LLMReranker({'reranker_model_path':'/data2/khj/bce-reranker-base_v1'}) + + query = 'apple' + texts = [ 'roast banana', 'ice juice', 'red orange', 'apple pie'] + scores = model._sort(texts=texts, query=query) + + assert scores[0] == len(texts) - 1 + + +if __name__ == '__main__': + test_reranker() From 6f588901c538c7d5614857549a02e40b2dde2e2d Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 11:54:05 +0800 Subject: [PATCH 03/11] fix(primitive/embedder.py): import error --- huixiangdou/primitive/embedder.py | 2 +- huixiangdou/primitive/llm_reranker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/huixiangdou/primitive/embedder.py b/huixiangdou/primitive/embedder.py index 903ca2f3..41407794 100644 --- a/huixiangdou/primitive/embedder.py +++ b/huixiangdou/primitive/embedder.py @@ -9,7 +9,7 @@ import numpy as np from loguru import logger -from .query import distance_strategy +from .query import DistanceStrategy from .rpm import RPM class Embedder: diff --git a/huixiangdou/primitive/llm_reranker.py b/huixiangdou/primitive/llm_reranker.py index 78ab14ce..09617f70 100644 --- a/huixiangdou/primitive/llm_reranker.py +++ b/huixiangdou/primitive/llm_reranker.py @@ -134,7 +134,7 @@ def _sort(self, texts: List[str], query: str): for scores in all_scores[0] ] scores = scores[0].cpu().numpy() - elif 'bce' in self._type + elif 'bce' in self._type: scores_list = self.bce_client.compute_score(pairs) scores = np.array(scores_list) else: From 49573dc25f8a7f4522c9c72486b010c4f31650d2 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 12:50:15 +0800 Subject: [PATCH 04/11] feat(dockerfile): add pure cpu mode --- Dockerfile-cpu | 21 +++++++++++++++++++++ entrypoint.sh | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Dockerfile-cpu create mode 100755 entrypoint.sh diff --git a/Dockerfile-cpu b/Dockerfile-cpu new file mode 100644 index 00000000..5be174b7 --- /dev/null +++ b/Dockerfile-cpu @@ -0,0 +1,21 @@ +FROM continuumio/miniconda3 + +# 设置环境变量 +ENV PATH=/opt/conda/bin:$PATH + +# 内部挂载点 +VOLUME /huixiangdou +WORKDIR /huixiangdou + +# 创建并激活 py38 环境,并安装依赖包 +RUN conda create --name py38 python=3.8 -y && \ + conda run -n py38 pip install -r requirements-cpu.txt && \ + conda clean --all -f -y + +# 暴露 gradio 和 http 服务端口 +EXPOSE 7860 +EXPOSE 23333 + +# 选择启动提特征还是运行服务 +ENTRYPOINT ["./entrypoint.sh"] +CMD ["setup_feature", "start_service"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..81790da4 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# entrypoint.sh +setup_feature() { + echo "Build feature store.." + source /usr/local/bin/activate py38 && python3 -m huixiangdou.service.feature_store +} + +start_service() { + echo "Starting service.." + source /usr/local/bin/activate py38 && python3 -m huixiangdou.gradio +} + +case "$1" in + setup_feature) + setup_feature + ;; + start_service) + start_service + ;; + *) + echo "Invalid command: $1" + exit 1 + ;; +esac \ No newline at end of file From 2c670645f77363cdb153663df90ac57f2e77e71b Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 16:28:12 +0800 Subject: [PATCH 05/11] upidiiddiid --- Dockerfile-cpu | 21 --------------------- README.md | 3 ++- README_zh.md | 6 +++++- entrypoint.sh | 25 ------------------------- requirements-cpu.txt | 10 +++++----- requirements.txt | 3 +-- 6 files changed, 13 insertions(+), 55 deletions(-) delete mode 100644 Dockerfile-cpu delete mode 100755 entrypoint.sh diff --git a/Dockerfile-cpu b/Dockerfile-cpu deleted file mode 100644 index 5be174b7..00000000 --- a/Dockerfile-cpu +++ /dev/null @@ -1,21 +0,0 @@ -FROM continuumio/miniconda3 - -# 设置环境变量 -ENV PATH=/opt/conda/bin:$PATH - -# 内部挂载点 -VOLUME /huixiangdou -WORKDIR /huixiangdou - -# 创建并激活 py38 环境,并安装依赖包 -RUN conda create --name py38 python=3.8 -y && \ - conda run -n py38 pip install -r requirements-cpu.txt && \ - conda clean --all -f -y - -# 暴露 gradio 和 http 服务端口 -EXPOSE 7860 -EXPOSE 23333 - -# 选择启动提特征还是运行服务 -ENTRYPOINT ["./entrypoint.sh"] -CMD ["setup_feature", "start_service"] diff --git a/README.md b/README.md index bf7f233b..eeeeb2c8 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,9 @@ Our Web version has been released to [OpenXLab](https://openxlab.org.cn/apps/det The following are the GPU memory requirements for different features, the difference lies only in whether the **options are turned on**. -| Configuration Example | GPU mem Requirements | Description | Verified Devices on Linux System | +| Configuration Example | GPU mem Requirements | Description | Verified on Linux | | :----------------------------------------------: | :------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | +| [config-cpu.ini](./config-cpu.ini) | - | Use siliconcloud API
for text only | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | | [config-2G.ini](./config-2G.ini) | 2GB | Use openai API (such as [kimi](https://kimi.moonshot.cn), [deepseek](https://platform.deepseek.com/usage), [stepfun](https://platform.stepfun.com/) and [siliconcloud](https://siliconflow.cn/)) to search for text only | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | | [config-multimodal.ini](./config-multimodal.ini) | 10GB | Use openai API for LLM, image and text retrieval | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | \[Standard Edition\] [config.ini](./config.ini) | 19GB | Local deployment of LLM, single modality | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | diff --git a/README_zh.md b/README_zh.md index 500dd508..b3b5c970 100644 --- a/README_zh.md +++ b/README_zh.md @@ -139,10 +139,11 @@ Web 版视频教程见 [BiliBili](https://www.bilibili.com/video/BV1S2421N7mn) # 📦 硬件要求 -以下是不同特性所需显存,区别仅在**配置选项是否开启**。 +以下是不同特性所需显存,区别仅在**配置选项是否开启**。 `Python<=3.11` | 配置示例 | 显存需求 | 描述 | Linux 系统已验证设备 | | :----------------------------------------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | +| [config-cpu.ini](./config-cpu.ini) | - | 用 siliconcloud API
仅检索文本 | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | | [config-2G.ini](./config-2G.ini) | 2GB | 用 openai API(如 [kimi](https://kimi.moonshot.cn)、[deepseek](https://platform.deepseek.com/usage)、[stepfun](https://platform.stepfun.com/) 和 [siliconcloud](https://siliconflow.cn/))
仅检索文本 | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | | [config-multimodal.ini](./config-multimodal.ini) | 10GB | 用 openai API 做 LLM,图文检索 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | 【标准版】[config.ini](./config.ini) | 19GB | 本地部署 LLM,单模态 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | @@ -249,6 +250,9 @@ curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: app # 🍴 其他配置 +## CPU 版 + + ## 2G 实惠版 如果你的显存超过 1.8G,或追求性价比。此配置扔掉了本地 LLM,使用 remote LLM 代替,其他和标准版相同。 diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 81790da4..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# entrypoint.sh -setup_feature() { - echo "Build feature store.." - source /usr/local/bin/activate py38 && python3 -m huixiangdou.service.feature_store -} - -start_service() { - echo "Starting service.." - source /usr/local/bin/activate py38 && python3 -m huixiangdou.gradio -} - -case "$1" in - setup_feature) - setup_feature - ;; - start_service) - start_service - ;; - *) - echo "Invalid command: $1" - exit 1 - ;; -esac \ No newline at end of file diff --git a/requirements-cpu.txt b/requirements-cpu.txt index 1c84733a..934a57f9 100644 --- a/requirements-cpu.txt +++ b/requirements-cpu.txt @@ -1,8 +1,9 @@ +--extra-index-url https://download.pytorch.org/whl/cpu aiohttp beautifulsoup4 duckduckgo_search einops -faiss +faiss-cpu loguru lxml_html_clean nest_asyncio @@ -11,7 +12,6 @@ numpy<2.0.0 openai>=1.0.0 openpyxl pandas -pickle pydantic>=1.10.13 pymupdf python-docx @@ -21,11 +21,11 @@ redis requests scikit-learn # See https://github.com/deanmalmgren/textract/issues/461 -# textract @ git+https://github.com/tpoisonooo/textract@master -textract +textract @ git+https://github.com/tpoisonooo/textract@master +# textract texttable tiktoken -torch --index-url https://download.pytorch.org/whl/cpu +torch unstructured sse_starlette fastapi diff --git a/requirements.txt b/requirements.txt index 17f308f4..cd505c38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,6 @@ numpy<2.0.0 openai>=1.0.0 openpyxl pandas -pickle pydantic>=1.10.13 pymupdf python-docx @@ -22,7 +21,7 @@ requests scikit-learn # See https://github.com/deanmalmgren/textract/issues/461 # textract @ git+https://github.com/tpoisonooo/textract@master -textract +# textract texttable tiktoken torch>=2.0.0 From 81cd41c5377525d74179be8487c417e1e84204a4 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 18:56:47 +0800 Subject: [PATCH 06/11] docs(project): support cpu-only RAG --- README.md | 21 +++++++++++++++++ README_zh.md | 30 +++++++++++++++++++----- config-cpu.ini | 8 +++---- huixiangdou/primitive/embedder.py | 18 ++++++++++---- huixiangdou/primitive/faiss.py | 13 +++++++++- huixiangdou/primitive/llm_reranker.py | 13 +++++----- huixiangdou/primitive/rpm.py | 8 ++++--- huixiangdou/service/feature_store.py | 6 +---- huixiangdou/service/llm_server_hybrid.py | 3 --- huixiangdou/service/web_search.py | 14 +++++------ requirements-cpu.txt | 3 +++ requirements-multimodal.txt | 1 - requirements.txt | 4 +++- 13 files changed, 101 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index eeeeb2c8..ccffc150 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,27 @@ Same as [OpenXlab APP](https://openxlab.org.cn/apps/detail/tpoisonooo/huixiangdo # 🍴 Other Configurations +## **CPU-only Edition** + +If there is no GPU available, model inference can be completed using the [siliconcloud](https://siliconflow.cn/) API. + +Taking docker miniconda+Python3.11 as an example, install CPU dependencies and run: + +```bash +# Start container +docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash +# Install dependencies +python3 -m pip install -r requirements-cpu.txt +# Establish knowledge base +python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini +# Q&A test +python3 -m huixiangdou.main --standalone --config_path config-cpu.ini +# gradio UI +python3 -m huixiangdou.gradio --config_path config-cpu.ini +``` + +If you find the installation too slow, a pre-installed image is provided in [Docker Hub](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags). Simply replace it when starting the docker. + ## **2G Cost-effective Edition** If your GPU mem exceeds 1.8G, or you pursue cost-effectiveness. This configuration discards the local LLM and uses remote LLM instead, which is the same as the standard edition. diff --git a/README_zh.md b/README_zh.md index b3b5c970..1337fe84 100644 --- a/README_zh.md +++ b/README_zh.md @@ -139,7 +139,7 @@ Web 版视频教程见 [BiliBili](https://www.bilibili.com/video/BV1S2421N7mn) # 📦 硬件要求 -以下是不同特性所需显存,区别仅在**配置选项是否开启**。 `Python<=3.11` +以下是不同特性所需显存,区别仅在**配置选项是否开启**。 | 配置示例 | 显存需求 | 描述 | Linux 系统已验证设备 | | :----------------------------------------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | @@ -250,10 +250,28 @@ curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: app # 🍴 其他配置 -## CPU 版 +## **纯 CPU 版** +若没有 GPU,可以使用 [siliconcloud](https://siliconflow.cn/) API 完成模型推理。 -## 2G 实惠版 +以 docker ubuntu 20.04 + Python3.11 为例,安装 cpu 依赖,运行: + +```bash +# 启动容器 +docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash +# 安装依赖 +python3 -m pip install -r requirements-cpu.txt +# 建立知识库 +python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini +# 问答测试 +python3 -m huixiangdou.main --standalone --config_path config-cpu.ini +# gradio UI +python3 -m huixiangdou.gradio --config_path config-cpu.ini +``` + +如果觉得装依赖太慢,[dockerhub 里](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags)提供了安装好依赖的镜像,docker 启动时替换即可。 + +## **2G 实惠版** 如果你的显存超过 1.8G,或追求性价比。此配置扔掉了本地 LLM,使用 remote LLM 代替,其他和标准版相同。 @@ -281,7 +299,7 @@ remote_llm_model = "alibaba/Qwen1.5-110B-Chat" python3 -m huixiangdou.main --standalone --config-path config-2G.ini # 一次启动所有服务 ``` -## 10G 多模态版 +## **10G 多模态版** 如果你有 10G 显存,那么可以进一步支持图文检索。仅需修改 config.ini 使用的模型。 @@ -304,7 +322,7 @@ reranker_model_path = "BAAI/bge-reranker-v2-minicpm-layerwise" python3 tests/test_query_gradio.py ``` -## 80G 完整版 +## **80G 完整版** 微信体验群里的 “茴香豆” 开启了全部功能: @@ -322,7 +340,7 @@ python3 tests/test_query_gradio.py - [使用 wkteam 微信接入,整合图片、公众号解析和指代消歧](./docs/add_wechat_commercial_zh.md) - [使用 rag.py 标注 SFT 训练数据](./docs/rag_annotate_sft_data_zh.md) -## 移动端 +## **移动端** 贡献者提供了[android工具](./android) 完成微信接入。方案基于系统层 API,原理上可以控制任何 UI(不限于通讯软件)。 diff --git a/config-cpu.ini b/config-cpu.ini index 38ba462c..ad5b4b92 100644 --- a/config-cpu.ini +++ b/config-cpu.ini @@ -16,7 +16,7 @@ reranker_model_path = "https://api.siliconflow.cn/v1/rerank" # if using `siliconcloud` API as `embedding_model_path` or `reranker_model_path`, give the token api_token = "" -api_rpm = 1000 +api_rpm = 800 work_dir = "workdir" [web_search] @@ -51,8 +51,8 @@ local_llm_bind_port = 8888 # xi-api and alles-apin is chinese gpt proxy # for internlm, see https://internlm.intern-ai.org.cn/api/document -remote_type = "kimi" -remote_api_key = "Y2tpMG41dDB0YzExbjRqYW5nN2c6bXNrLTFzVlB2NGJRaDExeWdnNTlZY3dYMm5mcVRpWng=" +remote_type = "siliconcloud" +remote_api_key = "=" # max text length for remote LLM. # use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 remote_llm_max_text_length = 40000 @@ -65,7 +65,7 @@ remote_llm_max_text_length = 40000 # "gpt-4-1106-preview" for alles-apin or OpenAOE # "internlm2-latest" for internlm # for example "alibaba/Qwen1.5-110B-Chat", see https://siliconflow.readme.io/reference/chat-completions-1 -remote_llm_model = "auto" +remote_llm_model = "alibaba/Qwen1.5-110B-Chat" # request per minute rpm = 500 diff --git a/huixiangdou/primitive/embedder.py b/huixiangdou/primitive/embedder.py index 41407794..9c3fe6ab 100644 --- a/huixiangdou/primitive/embedder.py +++ b/huixiangdou/primitive/embedder.py @@ -38,6 +38,9 @@ def __init__(self, model_config: dict): api_token = model_config['api_token'].strip() if len(api_token) < 1: raise ValueError('siliconclud remote embedder api token is None') + + if 'Bearer' not in api_token: + api_token = 'Bearer ' + api_token api_rpm = max(1, int(model_config['api_rpm'])) self.client = { 'api_token': api_token, @@ -63,6 +66,12 @@ def model_type(self, model_path): return 'bce' return 'bge' + def token_length(self, text: str) -> int: + if 'bge' in self._type or 'bce' in self._type: + return len(self.embedder.client.tokenizer(text, padding=False, truncation=False)['input_ids']) + else: + return len(text) // 2 + def embed_query(self, text: str = None, path: str = None) -> np.ndarray: """Embed input text or image as feature, output np.ndarray with np.float32""" if 'bge' in self._type: @@ -79,7 +88,7 @@ def embed_query(self, text: str = None, path: str = None) -> np.ndarray: # assert abs(norm - 1) < 0.001 return emb else: - self.client['api_rpm'].wait() + self.client['api_rpm'].wait(silent=True) # siliconcloud bce API if text is None: @@ -89,7 +98,8 @@ def embed_query(self, text: str = None, path: str = None) -> np.ndarray: payload = { "model": "netease-youdao/bce-embedding-base_v1", - "input": text, + # Since siliconcloud API return 50400 for long input, we have to truncate it. + "input": text[0:512], "encoding_format": "float" } headers = { @@ -100,6 +110,6 @@ def embed_query(self, text: str = None, path: str = None) -> np.ndarray: response = requests.post(url, json=payload, headers=headers) json_obj = json.loads(response.text) - emb_list = json_obj['data']['embedding'] - emb = np.array(emb_list).astype(np.float32) + emb_list = json_obj['data'][0]['embedding'] + emb = np.array(emb_list).astype(np.float32).reshape(1, -1) return emb diff --git a/huixiangdou/primitive/faiss.py b/huixiangdou/primitive/faiss.py index 74a4b33c..b65ac152 100644 --- a/huixiangdou/primitive/faiss.py +++ b/huixiangdou/primitive/faiss.py @@ -106,10 +106,16 @@ def similarity_search_with_query(self, pairs = self.similarity_search(embedding=np_feature) # ret = list(filter(lambda x: x[1] >= threshold, pairs)) + highest_score = -1.0 ret = [] for pair in pairs: if pair[1] >= threshold: ret.append(pair) + if highest_score < pair[1]: + highest_score = pair[1] + + if len(ret) < 1: + logger.info('highest score {}, threshold {}'.format(highest_score, threshold)) return ret @classmethod @@ -127,6 +133,7 @@ def save_local(self, folder_path: str, chunks: List[Chunk], index = None for chunk in tqdm(chunks): + np_feature = None try: if chunk.modal == 'text': np_feature = embedder.embed_query(text=chunk.content_or_path) @@ -135,7 +142,11 @@ def save_local(self, folder_path: str, chunks: List[Chunk], else: raise ValueError(f'Unimplement chunk type: {chunk.modal}') except Exception as e: - logger.error('{}, skip and continue'.format(e)) + logger.error('{}'.format(e)) + + if np_feature is None: + logger.error('np_feature is None') + continue if index is None: dimension = np_feature.shape[-1] diff --git a/huixiangdou/primitive/llm_reranker.py b/huixiangdou/primitive/llm_reranker.py index 09617f70..fde93240 100644 --- a/huixiangdou/primitive/llm_reranker.py +++ b/huixiangdou/primitive/llm_reranker.py @@ -42,7 +42,8 @@ def __init__( api_token = model_config['api_token'].strip() if len(api_token) < 1: raise ValueError('siliconclud remote embedder api token is None') - + if 'Bearer' not in api_token: + api_token = 'Bearer ' + api_token api_rpm = max(1, int(model_config['api_rpm'])) self.client = { 'api_token': api_token, @@ -138,7 +139,7 @@ def _sort(self, texts: List[str], query: str): scores_list = self.bce_client.compute_score(pairs) scores = np.array(scores_list) else: - self.client['api_rpm'].wait() + self.client['api_rpm'].wait(silent=True) url = "https://api.siliconflow.cn/v1/rerank" payload = { @@ -152,14 +153,14 @@ def _sort(self, texts: List[str], query: str): headers = { "accept": "application/json", "content-type": "application/json", - "authorization": "Bearer sk-ducerqngypudxuevovkmvsbatstjyikvbjdpylfsvkfqcgox" + "authorization": self.client['api_token'] } response = requests.post(url, json=payload, headers=headers) json_obj = json.loads(response.text) results = json_obj['results'] - scores_list = [item['index'] for index in results] - scores = np.array(scores_list).astype(np.float32) - return scores[0:self.topn] + indexes_list = [round(item['index']) for item in results] + indexes = np.array(indexes_list).astype(np.int32) + return indexes[0:self.topn] # get descending order return scores.argsort()[::-1][0:self.topn] diff --git a/huixiangdou/primitive/rpm.py b/huixiangdou/primitive/rpm.py index 21e01cdc..fc312bb5 100644 --- a/huixiangdou/primitive/rpm.py +++ b/huixiangdou/primitive/rpm.py @@ -1,5 +1,5 @@ import time -import datetime +from datetime import datetime, timedelta from loguru import logger class RPM: @@ -14,7 +14,7 @@ def get_minute_slot(self): total_minutes_since_midnight = dt_object.hour * 60 + dt_object.minute return total_minutes_since_midnight - def wait(self): + def wait(self, silent=False): current = time.time() dt_object = datetime.fromtimestamp(current) minute_slot = self.get_minute_slot() @@ -33,4 +33,6 @@ def wait(self): else: self.record = {'slot': self.get_minute_slot(), 'counter': 0} self.record['counter'] += 1 - logger.debug(self.record) + + if not silent: + logger.debug(self.record) diff --git a/huixiangdou/service/feature_store.py b/huixiangdou/service/feature_store.py index f485bbbe..7c4d99fe 100644 --- a/huixiangdou/service/feature_store.py +++ b/huixiangdou/service/feature_store.py @@ -165,11 +165,7 @@ def analyze(self, chunks: List[Chunk]): for chunk in chunks: content = chunk.content_or_path text_lens.append(len(content)) - token_lens.append( - len( - self.embedder.client.tokenizer( - content, padding=False, - truncation=False)['input_ids'])) + token_lens.append(self.embedder.token_length(content)) logger.info('text histogram, {}'.format(histogram(text_lens))) logger.info('token histogram, {}'.format( diff --git a/huixiangdou/service/llm_server_hybrid.py b/huixiangdou/service/llm_server_hybrid.py index 6451da89..766b74c8 100644 --- a/huixiangdou/service/llm_server_hybrid.py +++ b/huixiangdou/service/llm_server_hybrid.py @@ -5,7 +5,6 @@ import os import random import time -from datetime import datetime, timedelta from multiprocessing import Process, Value, set_start_method import torch import pdb @@ -322,8 +321,6 @@ async def call_openai(self, client = OpenAI(api_key=self.server_config['remote_api_key'], base_url=base_url) else: - import pdb - pdb.set_trace() client = OpenAI(api_key=self.server_config['remote_api_key']) messages = build_messages(prompt=prompt, diff --git a/huixiangdou/service/web_search.py b/huixiangdou/service/web_search.py index a0b79680..e4b535a8 100644 --- a/huixiangdou/service/web_search.py +++ b/huixiangdou/service/web_search.py @@ -7,7 +7,6 @@ import time import types -import nest_asyncio import pytoml import requests from bs4 import BeautifulSoup as BS @@ -138,12 +137,13 @@ def fetch_url(self, query: str, target_link: str, brief: str = ''): content = content.replace(' ', ' ') if not check_str_useful(content=content): - logger.info('retry with chromium {}'.format(target_link)) - nest_asyncio.apply() - content = asyncio.get_event_loop().run_until_complete( - fetch_chroumium_content(url=target_link)) - if not check_str_useful(content=content): - return None + return None + # logger.info('retry with chromium {}'.format(target_link)) + # nest_asyncio.apply() + # content = asyncio.get_event_loop().run_until_complete( + # fetch_chroumium_content(url=target_link)) + # if not check_str_useful(content=content): + # return None return Article(content=content, source=target_link, brief=brief) except Exception as e: diff --git a/requirements-cpu.txt b/requirements-cpu.txt index 934a57f9..513cf6f9 100644 --- a/requirements-cpu.txt +++ b/requirements-cpu.txt @@ -30,3 +30,6 @@ unstructured sse_starlette fastapi uvicorn +termcolor +opencv-python-headless +gradio diff --git a/requirements-multimodal.txt b/requirements-multimodal.txt index c305ef14..0ac0554e 100644 --- a/requirements-multimodal.txt +++ b/requirements-multimodal.txt @@ -1,6 +1,5 @@ einops ftfy -opencv-python-headless timm torchvision FlagEmbedding diff --git a/requirements.txt b/requirements.txt index cd505c38..deb876eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ einops faiss-gpu loguru lxml_html_clean -nest_asyncio networkx>=3.0 numpy<2.0.0 openai>=1.0.0 @@ -32,3 +31,6 @@ sentence_transformers sse_starlette fastapi uvicorn +termcolor +opencv-python-headless +gradio From 11f5fb69f96bb800da15666b3bac1fb5c8910847 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 19:10:15 +0800 Subject: [PATCH 07/11] docs(project): add config-cpu.ini --- README.md | 4 ++-- README_zh.md | 4 ++-- config-cpu.ini | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ccffc150..c64b8097 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,8 @@ The following are the GPU memory requirements for different features, the differ | Configuration Example | GPU mem Requirements | Description | Verified on Linux | | :----------------------------------------------: | :------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | -| [config-cpu.ini](./config-cpu.ini) | - | Use siliconcloud API
for text only | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | -| [config-2G.ini](./config-2G.ini) | 2GB | Use openai API (such as [kimi](https://kimi.moonshot.cn), [deepseek](https://platform.deepseek.com/usage), [stepfun](https://platform.stepfun.com/) and [siliconcloud](https://siliconflow.cn/)) to search for text only | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | +| [config-cpu.ini](./config-cpu.ini) | - | Use [siliconcloud](https://siliconflow.cn/) API
for text only | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | +| [config-2G.ini](./config-2G.ini) | 2GB | Use openai API (such as [kimi](https://kimi.moonshot.cn), [deepseek](https://platform.deepseek.com/usage) and [stepfun](https://platform.stepfun.com/) to search for text only | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | | [config-multimodal.ini](./config-multimodal.ini) | 10GB | Use openai API for LLM, image and text retrieval | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | \[Standard Edition\] [config.ini](./config.ini) | 19GB | Local deployment of LLM, single modality | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | [config-advanced.ini](./config-advanced.ini) | 80GB | local LLM, anaphora resolution, single modality, practical for WeChat group | ![](https://img.shields.io/badge/A100%2080G-passed-blue?style=for-the-badge) | diff --git a/README_zh.md b/README_zh.md index 1337fe84..5caa37a6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -143,8 +143,8 @@ Web 版视频教程见 [BiliBili](https://www.bilibili.com/video/BV1S2421N7mn) | 配置示例 | 显存需求 | 描述 | Linux 系统已验证设备 | | :----------------------------------------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | -| [config-cpu.ini](./config-cpu.ini) | - | 用 siliconcloud API
仅检索文本 | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | -| [config-2G.ini](./config-2G.ini) | 2GB | 用 openai API(如 [kimi](https://kimi.moonshot.cn)、[deepseek](https://platform.deepseek.com/usage)、[stepfun](https://platform.stepfun.com/) 和 [siliconcloud](https://siliconflow.cn/))
仅检索文本 | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | +| [config-cpu.ini](./config-cpu.ini) | - | 用 [siliconcloud](https://siliconflow.cn/) API
仅检索文本 | ![](https://img.shields.io/badge/x86-passed-blue?style=for-the-badge) | +| [config-2G.ini](./config-2G.ini) | 2GB | 用 openai API(如 [kimi](https://kimi.moonshot.cn)、[deepseek](https://platform.deepseek.com/usage) 和 [stepfun](https://platform.stepfun.com/))
仅检索文本 | ![](https://img.shields.io/badge/1660ti%206G-passed-blue?style=for-the-badge) | | [config-multimodal.ini](./config-multimodal.ini) | 10GB | 用 openai API 做 LLM,图文检索 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | 【标准版】[config.ini](./config.ini) | 19GB | 本地部署 LLM,单模态 | ![](https://img.shields.io/badge/3090%2024G-passed-blue?style=for-the-badge) | | [config-advanced.ini](./config-advanced.ini) | 80GB | 本地 LLM,指代消歧,单模态,微信群实用 | ![](https://img.shields.io/badge/A100%2080G-passed-blue?style=for-the-badge) | diff --git a/config-cpu.ini b/config-cpu.ini index ad5b4b92..8a63e6ed 100644 --- a/config-cpu.ini +++ b/config-cpu.ini @@ -52,7 +52,7 @@ local_llm_bind_port = 8888 # for internlm, see https://internlm.intern-ai.org.cn/api/document remote_type = "siliconcloud" -remote_api_key = "=" +remote_api_key = "" # max text length for remote LLM. # use 128000 for kimi, 192000 for gpt/xi-api, 16000 for deepseek, 128000 for zhipuai, 40000 for internlm2 remote_llm_max_text_length = 40000 From bf8d0ced2b860f8d5eeefb74f1504673b7522f62 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Wed, 14 Aug 2024 19:13:38 +0800 Subject: [PATCH 08/11] docs(project): add requirements --- README.md | 2 ++ README_zh.md | 4 +++- requirements.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c64b8097..ed40b399 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,8 @@ Taking docker miniconda+Python3.11 as an example, install CPU dependencies and r # Start container docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash # Install dependencies +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev python3 -m pip install -r requirements-cpu.txt # Establish knowledge base python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini diff --git a/README_zh.md b/README_zh.md index 5caa37a6..ec33bb54 100644 --- a/README_zh.md +++ b/README_zh.md @@ -259,7 +259,9 @@ curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: app ```bash # 启动容器 docker run -v /path/to/huixiangdou:/huixiangdou -p 7860:7860 -p 23333:23333 -it continuumio/miniconda3 /bin/bash -# 安装依赖 +# 装依赖 +apt update +apt install python-dev libxml2-dev libxslt1-dev antiword unrtf poppler-utils pstotext tesseract-ocr flac ffmpeg lame libmad0 libsox-fmt-mp3 sox libjpeg-dev swig libpulse-dev python3 -m pip install -r requirements-cpu.txt # 建立知识库 python3 -m huixiangdou.service.feature_store --config_path config-cpu.ini diff --git a/requirements.txt b/requirements.txt index 0cc090eb..0ead912d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ redis requests scikit-learn # See https://github.com/deanmalmgren/textract/issues/461 -# textract @ git+https://github.com/tpoisonooo/textract@master +textract @ git+https://github.com/tpoisonooo/textract@master # textract texttable tiktoken From 412dce3867341a940f249fa469333b032bd273f9 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Thu, 15 Aug 2024 10:41:12 +0800 Subject: [PATCH 09/11] docs(README_zh.md): typo --- README_zh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_zh.md b/README_zh.md index ec33bb54..cb1c5fa0 100644 --- a/README_zh.md +++ b/README_zh.md @@ -254,7 +254,7 @@ curl -X POST http://127.0.0.1:23333/huixiangdou_inference -H "Content-Type: app 若没有 GPU,可以使用 [siliconcloud](https://siliconflow.cn/) API 完成模型推理。 -以 docker ubuntu 20.04 + Python3.11 为例,安装 cpu 依赖,运行: +以 docker miniconda+Python3.11 为例,安装 cpu 依赖,运行: ```bash # 启动容器 @@ -271,7 +271,7 @@ python3 -m huixiangdou.main --standalone --config_path config-cpu.ini python3 -m huixiangdou.gradio --config_path config-cpu.ini ``` -如果觉得装依赖太慢,[dockerhub 里](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags)提供了安装好依赖的镜像,docker 启动时替换即可。 +如果装依赖太慢,[dockerhub 里](https://hub.docker.com/repository/docker/tpoisonooo/huixiangdou/tags)提供了安装好依赖的镜像,docker 启动时替换即可。 ## **2G 实惠版** From 7b5097813af60d7a345058c5786a0360d0449c14 Mon Sep 17 00:00:00 2001 From: tpoisonooo Date: Thu, 15 Aug 2024 10:42:47 +0800 Subject: [PATCH 10/11] docs(resource): update --- resource/figures/wechat.jpg | Bin 106053 -> 106639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resource/figures/wechat.jpg b/resource/figures/wechat.jpg index 4a4076846524e09d6ac41a2be381ee62e116ea10..8ffef612de612694831fac20265a623df1d65167 100644 GIT binary patch delta 68087 zcmb@uc|4SB{6BmTYD%H93sX_lBo!)(p$?MD(xQlIx1C5-vfWy!lvJafR7RFViK&B> zFd?L}Z$+|Z4Kvm;%RSF$P)D8b^LxI}^LqZ6GBfvF_jO&L{r!1gcR|+7u-uu?dd#7g^#cz*<~#=vy;3jxzBriK6bP=m#vUpBpjq7jNl3opWt%^tDFiB zv7&lfi}Evr`p~35BNsZ`FRZv@hL1!V;|9o7l<_i0g^!f)=OdE?l3F|jw~S8ZBTe)9 z2vZx!o=x3$+FRmHnXLDK1Su5|hYLS@AP23Sm>C72tL@W`{Pu6u&T&r3g4dGl_b_!+9g$_;4qk%#u z-BQZprd!C$=x=+1@Mgx)4Sp;njXL%gQtl7iew<5h-uQEx>= zUASg>f^NcI*{g0TEB#1`U+Z5KA43v-Falm~IM*J?b~(Fv3a0jm1-5TB@RH%6G;`_{f}Vtt8%z zCQKmqtFcpeHTzdF7M1G8;Un8HLXS;m(M7N9T~;?sJUkF*#lSg=Q71Q~ytNeS2x$&j z)>9uG)SSfIwJki?Q}nSZvS95SWpQIaj~{oSPTnC}qI|W#cYN6{ z@;lm-fVtk+_A^PB+N)2$zUZmfxarV4;cvLDinv}w<=4j2ms`yrfA`Z*{Aw%M!pNyD z>=Hf|!e#x?=}L4`uP8n{HliWi@!vkz8B4jPouRPf;oBn5ViC8ZntMje0<{xkLJ;+- zm)lPN$FtjRxCXKACAlaNE}T}fF%Qr85Pf8Zh&)~u`{-SlYnwak02#EmOGE9ac+zY- z>!#KtJGGwJZByGx{~xbHCj=cfHPk9TGNDLFz(s4D&a&UOCY};!sk_Xkv*@?a{|ABT z2&5~OmOei8W9z4c8%Hs+(p#b<%}mHAU5#2_x5+)h+2`+=p@yCPN}`KIL=NkUN?4JP zl}C8&$1S0V%vq4S$ofwliZeck#WT0O$uu6WKmO>ks?EH0pXbFsOsEPfuoi4CqYb;z z4f+B)^R|2j8j23?7$I?v@R8WPIXK#LgXMnP7<=i>zC5B?wYBfPtn6EtPVW3H6}9br zxlNH5V$(=M(Do=}K0?;wBWuWWY-*`^Y%yzNe!$$Y?X@(t_UkvtJ0a4iPQE?Jm=!IN zz^T(|BaKUqklDlc`G}#;hY6-ZZR&ZYFPFR%Mn!bi9P z8_W2JJ$XEmXEN=>sn3ukW9jS`J~FpANQf-NFpP3pQutfOTCF?hCX&Du4lRa$FC0W@;0db8Dg>2u#4E1i4gnU z14)C6z|%&ol)%ZMugc`mcaM|Mx(9rOy|T{qK<9N*`|cWgZz;-?aS|RffOsSEbows* z;o3$%AEm{p3(^?*qi#T^x|At5__X0da9Jb1HhT;-J3ov)2lQV};GUGj`G`kOFnx{A zz_>V>kJNo^V$0KBH?>LR-@YWaUumroHk-tXWNFz%ZVcVq-zu4@t9C1#9emg*dT!5M z63kGT!D&U$d!aMBeU8V2mdP+gZi(uyIOW~4HKpjcyUk^R&{cbP^O4h}VI5l8dyY!0 z`&+t5Z;oiSb~e$VV<$Az-%8A4 z9mzZ8T>p*Tu+t5VG7LJvjo&Ri_*f}XxaUddrbB#W$=}I*#K98`J@o($EblU5SQgx~ z1-9(@CNt)d7T(z9^o1Mkt(5mC9}ks1`XF^Naw;bc$B37*GMph@?R_>kN0qtnUfP^> znez^s1zTmDwa7A!_o6Zq0t7*EnFXhxgslkNR7$t7@*<4mKB0AKBsN03$VXasas^>> zJ9X>}%H8AN9k)dzhvMwMx%pDQoOh(U_3$X#~Xk z_1jTX%ku9E9Z1L8mT_r6+77VqjgTmum*6A*MSP^dijRn%g?J%Ez&H>u zLZT(s#$;<@QUegGM2e>_0-rxC z`=7>IKQ%KFZw!K{Gb`)OKRv zWX_k#-p6+`2DK;2sUbu$*O}F^R$rt`C_gHzYEHcMhCTOo`W?Qp&+Q zQk1wpMan^gS*q;Uqt1N9>M0+|mKbzl-_5b*MbfKXNdsHJ(Zx^PLzsepwo+l(w8er; zX(V9Twb`OsW7FPHq@0$b69(h_(_lF6qvv3a3S>4n0z9oPG8Up9tBqKh+D=b_C;k>5 zYHO3_hWp!LnQGWI+abN-vg_nQs}Xm07nQBy`msy6%0Qft6q{C}y|J)8FKFdHTowzG z(xn8DPzoqcOuaDOsEe{XT8?5EElRm%svL3hAkI3~>A(scMJ!AO{Sm8)M+~1`+gMBSXSIqXrF(=|x1{6C5K_=grRfYNg zi4I&Pp{&dmZ#crkv*I}fEl&u#06E0lb$qp1TI2UP4{t)gw& zE^=M?@3mhZ_}yV%Lj40`=vGfjZG}zPE44u>TnqWv>3#Q;Wu)d~Gpm4XjqF9dN9$1gY6>LmL0#0+m3*HV&M(c{P%`Zg@aBC8CCz$)Kl_pfz_aN;cNoQ!8PC6C|c+Oy3yLYGZGmcxI`8kwouMZG)pqyWKt13 zyZw&**M++8soO>^SdJDm_()mf*o5uPKQQpUhL0#`)^Ql98ZFTn@y2tt5-#wO&VCNY zvA~vu!4GDlKd*P-BPQy?Z$MI;WI-e{PE}~asSJtB-?>(;uk}TF4;Q~&DOYHCC`j%4 zg^fNo)r4OKFh11W$qlz($wy!yHjY-l0%1r@n;$r?`KG^4Kbwz0S`mom>v?Np0R!{_ z_+=}RXwSm8C~5tXQ+@DN)>=Ee;uSN!a@FImb02P?#Spoh@39f2~klNBg_V%dXJ`nR~a~Y(;R_eyLkjH{!nQbsJl;O~fPu+mi|oUmG`w za*eWy6-THIjyvDM`1hZTI-n6Kj5aRVpb@3jz z7F9|h;!$0<-WUmpLfyJhbcm0f;3Fu7>x^c2Y4H)w(WbF+QXzeH_eM)RNH@)XP{@Il*MaU7lWx*J{DrZ3#sRVW(4tT} z@03Nmx=`buS6S()MoZf(^5UB1<u)SObP7 zu;8w4UDTt+=MxQ1iDUim$cOGVk zAlzImua4B2*NO2+ENeb;uy;yYWG-=d^md?u6Yt?l_dk*2X(sT1)&3*n{#CP6uKJv--E7}I*bLlLb zCh8msu3d>NmVJ3hrzsL)ZETUb^`ybisA}2Ev?%SpeyN4;@$Ycp%4F8si)(1UCWy7WV06s~IrTEB}I5Q=?oDslbxaV%P* zir&{KiJ^V;fk8CXg^0(6UzqhyVQqu2vaHS`7wr`lo?N{UZ6{(Yt{gB!Ub(_Zxc=%*yGlHxG(ot#z?f;eVCTYygnPpf4FwT#A}S8>k<`XeD7Yyd$vr5({iu7vFI#K4)M9XTJ1XXP@-< zCpOVr;`vC=Y(7FE@PZKkUF11jzeU{55!CTBm&X-gNiFg8A76XBhh>rZ$n1_7 z=d*8bjr(Tsa<6v^(<%cuJn)Fz=1?7uBG$Le%%cp#k%Mj8cX~-;0dk-bxgn!Q{-dOS z*3LcWJLZn(J-U84dkZEXi^Zbju&bZT@gLUkM%W=dV~}o`9I5v?iKMtVYa@%P?7!=2MQpj*ALEk}VaEV=rZ-VrCs7 zX!j>n%br>4+C~-So>Nv}jNHA*qjd6atRdX%JkfYzr^mClA&?%+84Tbfh;SK+BZ`bz zgpvn$>X10-u?e6;i1vik0)OT;5*W0Cu+g)lVce{9QEp|#G3r^D&W#?m5x*?|{U@EV z^=}B{^He~+uA;*$n&~Z(q@ML4bUwm+NMPJY^KL?cMEFjxzfVF(L$RHHUp+D;s(K9x zyDP{U(_|#zj3u#$ z$~qp#4uwRE4xtnhAyb|S9~lH)OO2LrWrwj}sx za8!7NvLQdZmSU${ZIQ!0R2#P92zC%1U&BZ0yM%ipiQn9(DFf(G2G58z2+8R)&>Y;m z=8hY8U`TD+)TilnFSwf^kx;W;=t?@qS@tN^Q%$ApyB!bbj+mXvMb zcyDyy(fe*5uMgiX^mUZN#lk+L|EG~YQbEc6_4|ze3Dw_AkS9eCl zC+wXf`gCZ$dl5%PuHc-z3$l%;UyiopA!a&&&UXpXS0@dR8S#;uN|$-QTOdgjMXCq3 zZjB#^Y&KZJYQyGfC}`NB-|4PHj`O}ZrOI1-K1`?#)E5dFx5E_3STuEVOMa969_don zE_*(5>3;sG&%m8_|CBXq5wFjD87-ZOuIPe-HfIf5yrG8v#eydX7$7;iNsiwBz@@2Q z4ZCj39gh~uY2Zbn?IsZzeJNzfPJ44yX)i#G)baW8AhTk0SUOt5Tc^}JA2Z%zhh=qw z`#PSf^*;edKbsWUoKY|EwWYn7z~PuI^%nCT`wo#s zResFag>Bmj262KuyvwF(gn`es7+9C@xE=)mpsYCK=a8>8#Kj4;ZI z2FBGFpmkX!wix1l=f{Zfzj)XHt=gvhYY-nnVkZtth6kII%qjUV~>;G*--1E{dU9cE>BirS=eNIhvPX>Gs}?KJeaG| zHda`+|0~WWKnAX9@&Z1%OC?f(dUC!UXGLpf7E z192JqPh|rI$A7B;#BV?4)85Sof6L(O-rgDQ{5}NLfi``FqlsMz$Mtu-YLbPi{EG=1 z9~ig>COr)l)DtZC#|!TFQ^-_Gz&Ba7ZBRXu3=BZsOaVxYLA%mvTD0x|Nuep;w@fJc zvldVmS?fjwtboQfh#d~pCqQ5nb;OL;p<}Hi1}N;MEqT=8*{P6H&j+x|%f2gG{H)*_ zw0v0^#x=#HsEjM#yRh?I@rpwk#d?QJZ{IUc=-+aO7}~Jr!O=FI zp)u}K3_NRedM6K}EZ4P5qTUWO7$&njNkiMl&jjjevdp=A(GHDs;JhJoQ0OdPX}+d- z({1N8OPs?11HYpx-9pM?%VRs^Q`cO!?zh~4KiMgwzzAXg0u!a-Mvfk0Tn-|2NjgHl zFUbWT|Jz!QL&n*`ia6o!*jD0DU#SzK-al)%q4e&9Cy{2GIp6|*L67FFi#WSiGPx$4 zCi3_pZK3p`DDhbxFHnkRukwMM4A%MtXH13R;$pBUKQ%ZAiS6J;GI%cN&XHFA>Axz9{EtLRa-B<5+U^qzfz2tIH|zzxzD_eZpkJAAP% zU{)hT>#<-bA)x_E@)_gf`UNEg?PZ>4RX(h|yt3b1i(qZBEPAT1^B)lScl$)fd_wlc zMy(4zCVoTopJ(qDjfnW{PP4>A_BiXOpdj9hQs#QDJN~O)ou9wNNb%r`L^kCDI=`V;!FZz2EZE>V^OCEr2ivBJ2-*qA`wre8$9=e5$DKdP2$ zb-!3QTDiL3R=sAn@;$`EcGwaJ?Gp<3unT4E$pk7VP4VH#q^6v6z@an8g0uAfTa%BA z&Do)z`o*pO=mzo~bH$qyhaA8_=MJ^e#(P28Q9C|zCB?Iu#tjeE#2V19ziISD;Z{J5 zs7h#=9f`wk;u`yC24@|M<0@6j<~l6je>6L^{?v)^O}@vzom;xoyEBAsnhgx{% z0kkZQ^sXHL5*5$i%vy+@b3mKGFSI_PobXjZqiQNx>oJoOH7utEJZS+EaQ9-bx!Db8 zC*k&iUkp1Zh8It78!(32-;JxRLE(kYGyyvC4mOBnVPwD zX>S`7>VfL^7pv|1zN(zE>o^#TB=)-&yABM5s!Ci|xL3qwPIj9jBNj^loblfl(zI}a zl;b=6A(j&MVI?GHZ!${91NwZW?gM>#pG_Mn;)D+Cj9;ap?@j^km~$LfUJ~%I7)$=W zcN9JMNv|KFxzVn$K0MOlX2nVh_oW0Hz zQ9j2kJNGAjpcjDM(MEhc5^%V;UZ?JIy6;07*5jxe?kiEn6#p>kFo*L@i?AztujFu6bIKy{so>7(D_IkmA zcehq`q19rgC$)x#J96YxuH_L1b)e)N$78|j!ozujtc1i&c)MfHo3KAK-+yd!nY*jl zvgxdR>DG}H0;VAQrqSrhDTlp22364R*?CY>pInFQe{4mNt85N`#o! zflbkG&quh<5WbxsMeJ0tML6!5!ooN7q4#4L4>EFD$m~hy>7925cx*DZDn~UY(4YZk zahajX9fV|K!UQTHJC|s7AOGeHF%rD}HQJSKC*eN&FB?T*lw&g?kB_pUk97ky&$=Bl zaWlf3rvK-gfxZ3T-|Q;v=?i+xqbxB#vWZ3-sGP(gC*90XtF+x!JREH-o?pZsiMYzO z^G|VZshxAz`hrn#i($KvZ%CW7v-l}!9 z0wRraj?4u3C_V{3=KapA7-$mbu8-kvWz>7$FTC4kS?#cYiPoAG+e}Wz&N6K4HY6wv z*=t_(W*)x-Kr?7y*WsM6For&(6FU#7TT?NK^(57q5&K4Fy_JJ)w-aS^Zbince9`6a zHl-szOZ9HwrAURSph^>Id9|wqi|>J{L(0HD7NyPJcPC|A&4_GN{Q>vQiMaYxx`=Yj%PkbiORGf$O?t}K zON!{@814>cVMfyDF81GXL$(fOijsv^h{gM7PW;SgQ8o=(R4;a4mh3iB_vM2 z1-2sWH`y!OaJ2bIQA#?-B|tMl=o~E^Q-2Q$%<<=!->7lcxYwGzoINzK=BDh%u_N;O z#A1MtCfQl&k2M6G*X1?%i-8ZN4=$vG-@Qj4*0QhU9%O}llsQ@N6Q9M-ss1W@n1ijdnBA(C@XbV6Z-I`Py zu*`|=p2rhcI{OBF7P@ZKxwi4!<^z9y-XGHPLHvmTvi6!rfSF{^L`*Ske$b?(u;o9+)5q) zEt{*C)M!86ZYR6?w$&Sv>({+>r5k@(!w*gz!kfXic}q>xAKl1DYg za!MgNFlC;%x>h8*M$GIud0}-sZt(g|153OQl1JI~W*I6`Z}SsG&16$X1x68zzFt3M zJMI=JL*F^9`7aybqu%ge0UF%RFM>CEpSIc8W*M^1^o>=ZK_3!u$tDo1AP+9Hb;QtJ zkgxlU3wUtTcw_r~0H5>Q-+E2wd7XP?hm^-6>nE$<%*Rv1#ewyw1jXV~T|KBf50Qos z*L9WzPTX|3?s*T&!}E9G zwM26XR|)*gbi{Kcx0^JVEBUBrzDwrb2R9GAF*6l!zhELU<4Tun76tcZ38mjyiV}Fm z5RD|3p)D?41IS1V=)4hox3FYqAKuMsuXD8>U!Gfbzk5GI2tr&!Y-dPKpy<}eUzMCK zh*DyF_#?9K`Heqyyr49?VMEMh%ouq642 zx#YC~v_4v59xwisTN-&x4Ra=I)|I15++ESEyZ#z!#FzcGBH>$)=+^H`(HgfY3rDQB z;nak)&vr91T1Bp;L>lML#o^tL7%HQbgb*Sci%HwO)np5QL7TL3ooP0Y(7Y!sNOlZ; z!#Ew`eni&A3#8|hYkW`qrzU{FD*EF&#k$7pb9BbFh3$vl6u;r^@JP_@m;_R~vK9lD zw3h-laue-3{Hnf@QSfnHz>=zzemPYu`_27DF;8BqQlp_4vF!jeatJMB+0rGRg9Esa zmWKQ7X8Fw|z0iQl=Ly0D!@~^H++mHbWKElLvJ%%Snibi$&0lnI<0GMMs%swft+bJ6 zx90fw8j4%w(5)clk>Cb?hm?oFLu@)!C%X3*+W*>Tw3savBBR$Kms?wf(P5SmSrRyT zbf&MR5Xai)L;_cj`FGMWnb?GG&mHc1{h`$-^Q-q%F=(x`-(W>AK@pvV%hXfKcCq~1 zG!zPVnD4GSe&Op%*;_@zYSM)h^%l-v_7bVx6$lf95mcV)nHbG|Vwb?ef-#w~gnU1_uv-Q~jeobej z2PjW@8u*$v?brJSzovABDy-Lb3>j(?C}SA6aN^cROq&*loI(k`(@;5hx$DO5)!%jQ ztEX$MI~Miz;I^&Va>5tcAr%yy@KQ>c=}L<0jioBmV-{~T$JL&rz^VIalUFnSttCEP z&{d>_hp9;j{+RZPP}apQ)sPCrnHwW=DUO9uYxdAby4PcYu79VvPJ0XNKa=Yf#)!Oj z#BFih3OT6n1vNfytFu$I#0nO$HdLE-kw!j5JQu>rQ*Pdvq3Z}zoq4VV{uKbTKj71g z0H1FB37=k}3Ro@{+W)fJz)m8PC2$(3BhZc@C#`yh9e>R{fS=BeR2Tk$twoFVYDkS1 zyty`=-vZ8aA$hue`U#pzWh>Nme*4+K6ZG)p+oX;I z0CXlbER%#?bK&6uL092DqPL&BhGn&Z0z~C}iAZ(FsZdC>@hZZ@3*lKgz*$o{Fp{~32cPWZ%&2);w^A2t; ztn|)RWI7~KjI1TqH+>K=bkKhpI)buD7D%wuI2BN-pcG=o5>g%4iW&R%E3rbY{PY~( z-51*2IzkVuuFnl#+kRU4PP;&8KJ-flnyj$g$hBROM`bEEr&J2Bc>X8d;rfho#uSZ+ z9Ve6qZWt_fRo1^V%8v7%Z=eK3oMNM4+e8?p^flvxUJs0fB zaOCou&?aYK7Fz*;)ZJV*&dC9X3ESRE>=IR{NR_Fgf)#l>Xo35+*aMN$j6N)EukqUl zhJ;9MkgzYBrx{$Y7IvumXiZ0yic`gPDG(^g=d@kUGIEVerBZc(|=hMvn&9~iHu6oyF zzNC=JI!fWJ&X5tk@!p9LvGW6#Q-lRN34bI3w*?BUU$`x0Qv|a~{>yMuu1`(fl`M^f z-m@WBb-pYy-7TW78g);#ZkGefph5Q&5Ev7O{PDGj1#3x?_#ErM~0_~ z;i2@4bC1ri_OZDspe1Qd(2^kDutL~E0D}YmB474n#R-&$w~6TgC+f!YzbrbO|pMw-$m;N(Jl!?f{dU=|DBC5 zB1n6mHA$~nncSzm@Q=ZYhl%{Thb_7O60O$tQjv(OeuF8Uyj}jJeU6`a0ul>Ut zIljAtn~_4DlldabYiG2d8Kx;ytPCV6-)K7~+7tiFx*efi;UiA&nqE!o=aNXW)Bf5~kXd%5;TGqdDe(C>Y@5x}jcXm_K~_0wei2e)>p#>NGumOXb>o zP@&%#+tV~sK^nHLyH}2{!Gu_KtzlB${}gSeXQXX)+ka8pr6qV?hWsf3_3jR3%@G}n zP>C`sLc{SMt?VE0TXRGyZteCw#sU@Vw_>56Y;wlpI-&Xd(c-&?JLb0v9jJmu595i#?=!Eq2E4Z4_Cn%0 zt@>$Q9;h^_{n3Xq?&~ewP{do1<_JQetj1GK+LnbI&pFPi`>^>_Sqd(R^vg8ao&t7m zn7u2HMWWPI>%x@x4QsgjbmX<4LP_7iMuGxDKfC&@-Jh52?;umN#Ig8k_v&|tqXuU+datNVROvxy|IJ6-o&IHwxekGe z2ND=m*D2UZ3<-E-l;mu)_R5L==DQ|ckC&tpqGzVLHWLIC7O%1&6qabA)Px$ZhW(rF zCnX;8D6PA-hrYzsaF`a9V{jC>Dk_-$Bg`bwpc?yHK@#x7_=p;-P8V(ExtdV&>f=1V zHl*r2jmE85u(RroJgwAi&45`YoO^S&Fft@q2b4wzg@(R(rnzd#MbG@OH8q}rjcZL@ zyTkgRM?1wr3OMA^3rKlFQ$sz03&0oJBxuxG{Bi{(rU~?)-&X_DrNE$v?aF`ZeY~=1 z>*=gpZWpBjukI5|xGk$D%}fVQw;uZLvv1h=Fo`V(wGx&Kfb$d=%!(J34A^f_8E+i? za#j9Fe6+Y^%XRDAW+i0-S1AKY5}Ku}Z^ro+1~W_Jsa4P zs7-GRHLPinR15?HNXomkaid|Qf^PJbh=Xc}&nX?bG1_?V2D0FhDs`vp(9BE{9-6py zah({t2oUiLfh6;xZOb1FmFW6-8WPk3Dw~)i;FDgF09f$JdGw>jaSH9YGdP0ik){ihk@3MR`v|#jy|v7JhUNu^In~m9?!?$(7XX5Ta8jqMUf~sqZY$U31w5XapOU} ziz(>ThQxC4V5S1@*&qN+Dk2Z6|%uXt4}aNW$`rXmw1W?0*~N z0-9{Zbv@`+Q^2ZH~+o?_zqCaI71xflDx8KNcWHrln^ zkf1n86m0VZ2pfokXkNWRvU@Aher>C-ZrhEGFZVab9(?jBSM~O4gjP`nP58l0%ahLX zku6jxn6+`f9A64OQUMLwhOiSxA<4d4H%b~MvEA=<4sb{VXF88-rEVzQSXYAn>S=Zf zJk4KCJh1(~ZR`Zt@?dQx_v}d>eOhQ@qMk2u(x^u)AYp;8O~*30Y%ydu-#XlRbIWJ$ zBsKyir1!NQNw(4KtoF&rsoPE}n22g+Zz!gT8hojQO0#piYKBdt00tb|bwjI28`s50 zWIBR7TV;~;Uq8K4`q#0=;aH!~e1{Vhw?H#o6$K}vgo+H9jl_@A^jX*1(i!3rGvh`J zyDZkXr)YwVLy56pK=Ii>4;?y^tGtvv%lmHKTd~$JbHf?OiVK-9IBub5)aiXv0j8ht;^D6H=$+yA5X> z`_S{zU~504;K4t#WvEAw$}Oj#;c~7Yk?y84N_Iibb_g8+FAK+izP%^*So|t0U@^jd zi~y0Pt4$nn65u4CtEe&#otQry@FAkmTL^ZcVi$i*$>B1isbyu9P)$emSFZi+M}sde zHQX>NJq0VMe;#?j`gDS&(BZowJBN|ucVAh^v+jUCet78rh+~-Xne*&Sr{|2`vSjZ` z(c^!J{w_4QdC=e%vS5!v3^1Z?hP&Rmj0N^VX@tTjl9p!85v(swj0eW2v}-71%I2DX zncgWpE2PJRoeP-+%^WCGpji&6`!AC=WJ#_My22#7xNfK-Ren6Y z_G42e;x`~=Zh*Guk@Y-`z}-P$RGvJI6}ofK3Ehn6Y#TZYp`}iD;s8ud4^=WiMeXhK zKaZMgIgc9OJeb34csHuF#=*u=;<}%JO@12o?cD<4G(7?d%m_HVxc>du;GCFeyqK1M zo@b=$ZCQV7fhkqnGYv<5-DgO!sUU0orYtF(md=7tDA)Jthq(BQwtqEixvzCAm79A+ zxHeFqDs>pJfC4$7d#o`5YSLUQXo(8{`{7P?+6q<}bBzH(KT`kJYmTL7eqTd|a#3wo zKBizT!4{reCgN`vvf5R!_I;$TxlLm%lnE5YZQvga&rJFW6dc84;v14vj-NdE*va!3 zo@7SlGqhZuJ^-P+)wK*$;T{Mp%3Nu?&AFqxK(8dL`1J!> zFXK0c$npcg%G}*FEH`QDQ6w47H{jDQt=lL5cO@D>{{E|_vg3ygy#BfoSWBp9j;1g+ ze~Rr_F6IY%C$=0<{{F);#<|~k?j1*%oAOt=k_8W)xAmAdqK*Am18kd@#UUL7u+nxRIp+hkBa^ z6$Sq59y+W=-ae%b=#ObVDH;v9zn|0~yk}p*0NbQY{b7!K+4cTTs08jN(h?!rxUZ&& z{yDixC((J>{145*x!gZc+hw-l^1(2-wQ-%g5M%&F3iFvfyY|00nno9zb~Npgxw51P zEsEsSziIg)&;^LkhA!+S5wu-65=x$a5_*(IH_&m!qzemoq!rb?ShXC3GqupjLe~(i$O{^_QURMK0qUJ zs>0@ZQpe-{UKbz+o5rt!xm`sMg+0p+8*it+v-$SLdq}p2II4{hM5!=49B7WnINd(Y?_)BAm0*g+w~4u03@~!v01Vl3rZjmkx>EbRf3K@Pdgx#Cuj=Vg4_ofvfLUyB2O8=!cg(u`Z zhH9Z>HkA$KX8q~G3@OB>y8}j;0C@p;0*>sJHD4!Ml|uWNd}LKQBs@ZaI<9X}C)&8A z2B~+PG^~QB1;`IL2y=|lcM(6GCD2q&)DsjPLtx?A&nfPKTPGf3GI7WgeSFr@6&1Gq z`j(RSsZlXRey47`GLe7*Mj{G}T=oIusj&FO_CxC?COIM|l#ihdz9; z-egnXP^?Wu)XOj8D=z;^m5TwYTm=D>feYKC@^#y%c4}VhbRrfpysqK{{KE>6B7v$#N)p zSSA~S4IHTPOtYME?f$*~4%hpS74{$a_OST)ksh=58Xo|Bx3i2Kolz!!M|yUUS`fFf zcuDZCE1#}kEbs^T<5$We>soUHH~irbx?Pv>gWp-1e%F{`*43MI+sw2}P@z_PgK8tm zStlM8$Q{S4xy)PZTh}m^$$7#dbpjCX?!%^%TWRb$4zR;u845>pRxq`hX<9C4-EPb} z=KSD-gr$+r`N`8ZF8f^dUWBJ)9{W<;zyl zRo0mkY+191dra_%@c9GlVcS;>Bmp;2Fw;gBBMaDz@t;WAFQTFfIG)@Eb| zePkM}M@>~z-g}fSda!pXG(zlT92V7ibp@0pG<|7!JA31jI~AvV9th1iQemhhJ6)V` z(C#1aq_v)n}gNh>wNCoYP*Q&wR(W9H5Wb{4LEDbU}Ln)m3FY4V!TKK(h)0!4v-vRN>ktgySpPtZo za^=7y)1~Ii#Mk1bhmHMyhfWc{hm`U&t0{Yvbbv!Meby)w9?~J=BFm;;RDD^!YU#_H z_U~TDpXv*ITxj9KI-K)Ga9qF`jtd+EHMI`Z|0>vUc+GvyTez)e+ryoe3zP3PbQIDu z4DO#Va0IO&w2&tWsF%QIyVaw`J*UrJiW2EP52q)frJ|kaFSJy%-_^FOD_V54ws!QS zMtYp{+^wq<16Q2?ju(0gSpK=9f@?`h1jHrc%&DxkG0W;gL!R+kZ_H}9$rET*(u7t) zB2$>tCZMFNw(W%{R7|oQ5HEC6?@&;@iAz#g#3RRA$+OK8y+dY=EWCAE^{`#M#K9TlNJkWz=2%Gizhw*ZtkK&&tG}R+_4wjQMBn zqs8IpZyL2L48Vc3)ez->9!T3CEg@Vc*-A0!B%nRiKqwRUpsr}%4#@0DD#e32b)3HY z8T}3P4W%&hjGlda0uWd?cON^91QAyU1PMV3rDAV?LmWUbC($tn>(c z5{=*y+^5~)mO{AQ?rDvrU01dwHWYX&Jj`8c*dvi5r|iO$!j%|I2yKcrED+kywP_00 zX0IfL8*gJe)YcXivE!2;n9beVovvc}gmfk$aD#N`9*WqJ{etJk0K~ z9xIYO@L`3yV|wFX$4$C+qOZs<6@~;@+7#gvo#1;CZ!#nD;4n5zIZwlMpSOZ;++f7} zQ$=THcP{d=>9Y45;Ft=|I80?`#_5Qk^Hh5i@6PCbyACywX8PZK{TT10!7a&Kb9ek! z_`wAmBiRv{!gTf^c|P3R4b9&&!7m1P7Y{jpZ>gVobE8e0OroD_%ifTU^79W0eG4(m zT(vMz?nsm_Ez-508b}_rSS-k2A+APCh(QTGk1k1Amha z^DRnQCsg`wUCq1xz%PBw!lxq(EN)3u62O5JI3pKG(sr7nvY+x7oV_!722k=sls%E) zeVpJWOmDnRI7YBjiBh`YtV;UiSrr`6Ug7kNayq1WYU8Nr5F7alic8Dd^ECI7bG(di zXv6l)a|RzDxsCfytVLU*{7&-`{Xc;`At2P2`bqstCY*SF*ssiP+A5}Fkd7war6ks+ zWZ}+4+Fw_X<O@MB-(36q!GWZ^whc=TZE3KHUkI?X5LEtUb6P9 zx&FG^$^-7fa&3Qly-&nT2ZSKR&fhrssOb2w)D1%)g!RvqgA+J8p`05mD<(PLL6lL| zt&n*@yX55H#RW6%pGt|Fi+j@^K#M$s)bfZ6qTNJ8e-K1})st2|$P~-|OIZ~Vihod6 z;amz&jzNB_BvIsgioNz@Om!lvvzhn3O-`-ybtXIo0CJ~)AL zFek5X;bEP^-OX*Cw3D=tN@u4IMrgvp2;uMOFE>6NCC*A{p)c&Qi0__#@*lKle)xEz z)Cm#sM`4@lIzJ zC3X!25u~{~%``ECTS@Wx+v_CM*DN|#pKI9uYN%bC#U z3T-P+dJNUQwCNCsENwABr|faahPD7;Px^(_oLPqF?q)QFrZ3mdUc5?XU+Y?`j!dT~9E2>y)=FrJK+*93%kxvFafvCpeaaDww%~|H0(uCphdu*sk8{3^ z!EFU6FFeVA*&3uKb}3x4eQ{@baEIdeleofWqh^jme^DXn%G+j_PW0n!GyEjZ zGB37DowY8EjGeJx4)=Tm0tnT>`|+mHFbIfnGL(+T0&AKtw-MeRJb4-lr5s5vk;;?^ z+qA1SY2WwshI|hxi|pM`uCH4Yg?x7s4vV>Hj6mNXQcIg??y8EkMznBLVQ;eR!xQW8 zWzSaGc;v92ZwO}&Ce@TrWzV4TMj-}_oqK`u^o zNxu^*J)=IW9w%zoMo?6>Osr;heQ60cEL&8L53L|alNoC9cu5go;llX*~<6!#VQ*_ z#2b?yk?;nJ7x1ItnVZ}(coYaV*TF#bUrkH8P?4j+(lV?*T;ugn|In+;8x^$_-9(gU z59@rL3!P?L){(X`Zuo3c?^gnEW=>~{iaqOWi^PJahZeTa_xFnit=fNiPSqpGMT(3A z&I1aTcc0}62^fU3oF=XltaiPCOzsK<4mztG-}z0NjfY&uy`_vNREKd;W*liIrgWxq z;_qmQRX@T=%1r^>ko1jkwUVB9_30tTe5}NL6c7wev{ZAI|2i3z#n!J4h2^*`2^9|1HnGV!LQov#E84Em1f-gOK*Y6$vE_evH!J+<<(6sFIuAP z{Tg!gmgdfU%Dc%`((VRmd3$bU6os@^4R4*1{n}3TK2EJ;>G@5RZbM~LW9f{R*ZVZH zj1uMB+@=7UD3Hd z=7dsgs_%8YLd98I@^hplmtAzxxYRDc+N|{JL$i^T`=mRFK?G_82Z(;emZ>wr3{lt& z0i0q^6Tm5Q;keOw=%EuHHc6xHj|4sEn$Gkt^1pA=;E8%60|pAtG71BsUVX$}S9YA( z`0Unfl|?G^-*qO^o`-=&Ejrr$Qvv#ZC_pi!U$$_6fm))!YK`#FV5a6Q6k4bNWw#U9 zt8KpEo15}e>L;~W=iXE9YKF<>I0sifx2R{0`}0*9@8oZMR;YxLgo? z5mU~qX2P&w9PkQ1txli@(&GZLqyHKoJhky(voj<_A8n}Tyw_YX_0EVh%}`AzdpQz>N3_efld9~%n9x#mGF~|L78>V z{>P)V1c&zI@IV-pfJ3~32bn3O=NJnMq7)f5IZq<{hW7@ay;P{G>L$8y!<)WHwge`V zkYC0YvkPzp-;zD~AjFz6F1nM{oy149$kAm?^Om9IR>^h?O!TM>TBuuq9Ev7E378q9{q-NM#u< zC=y92+f=e8Bo#4Ssyj-SqEaeD5+$aEM4GX?OAL`I6~?|KGu9bq=6=7|Sfc0m|2^;V zKi>B^p69urnfo%=b)C!iJkRgPHj?Hei%rF@7|d(+2#NUk zc}J?o5RKR_l3>X~hF~BXXa?{?xqa;YPdn?>A_jOS2OhZhH1+%Z!xM2hqRx&T)t>gO z)MBCR;(W;9}6mA(4x{Jn8s${-cLG7K^$bds7kMMbJYEQrRI8wroiMAmNx(l2(C%(!}kEkdi@m z^A{xi)ZTFUn_d?mUDTZg>EZDf7!$kr1@j$6d-Zk~1}^39VD61+e4t`hqj_lgn~NB- z{GwZ1bQDzJ1b8|5_Zwn*J3p6n2I?_AG6NJIB%Qc!Yg_iDcCeKGx!Qf<%w_oc)==R#%Sk>q)|cm z56hrtkhuXa)WHe?q#$57>zo$*mL5s%g3(aU*LBynWVM&Aw@lemYAeSgvkxHisU35@ zITEb+c{4&`5Hk*NcR*=Zm4>w7OJev^gd+UNjPEcq02AGj4iL59-?*)`W)eb)+9$GN z>tU#@XGpbRW)v-opj-^v0@*MCt)6?0o5yv3de|Vvwa&8t+sC(wH}nf7_LbW}lUT8U z!Jmb{4ht`a%Jk+NufL7dAv_GdLyCxR#LadFJY>DfOX#$pj?b%mOk~qnVOEG(h#iJw9i(Aq5mu_Sf`Ih2^X>K6R=e?DSO~ zwzK-QL{3K52I=@kXO}HQ3MHW&h;d(>?r#Hx0OF=&a)U7CPX6qlE&qbPn>Hf<);NPoy*1uYB(95&INVFoztrEdFzYfmQ>?AoPmwxhZ(Xlpd8 z!UO5{*eraXApZ`Lgj9$$jEH}OP@*DH)UYsD)Q3@STuVQc6?NF%X{Y(#!*^$4yuMe8 z=4+U{2og;|Hc-jE28H?Lt4bk;5Knfj!5q2x7nIT(!zb`;4XDmU@U1*plT!su1HtU_ zLkb?Kn6rJFW@nP*^Xd-Vf88^~6hFuE__}A34myn85X^q68=S&T>0YC0ZD0C3e4=1B zI>N&5e_pHO4XnH4WxBcPmlgMh%}{P;N#&VG>mAx1Z8W+Z`7`GbASSVy9EZ46k>(tN z?TVV9M1)~~nmlFN*I=WeXgvtr}Zp_JV8 zHhxaVKKOZK-4r2>o0wd_WiY5WX-oui(hyw`$hj(w5*JHx3kC`!Ij{$R7)Rm93qJ(* zrgggx0bVIR4F)hYS%*l@+BWVHFUDkr5)LPZtwBJG@c0!W=E(l=dc z&}>G~(qhRvY>)oD2@MU{Fp^tfFQf2|PeFKz`009q@%!}K1hhXKJ1cfQz8&TLT>!l8 za|gz+P<5ztpbJzsk9_6q^yN8YJWH#}UX8I-zwU=8{GFv+F~R0p31V zhuh!V^IaKhP?}|9f!gGtp#pJFI_$GQ9Jpf?|^IUYA5kto|&Q zq?ICL!w5@Aj)k1u)uqAsc9arq)wWDlbHoRe8<+I4kCKKT31nC0Topi1AYHAbe%2?hcXDC!iI*lzPL;Ji`8&wxNW^K0 zC3JTK4D7D~7T!Jq3$F%U0Vs@5hjCXzkZW@e%}5Q9D2fffapGf@wXM!Bg7B677w$X6 zws*fo7vq(+FeV6)WixzJhv}gxu+No22d;ZnNv_{{u{p3=7E7^QxAD5sKEhr|M{S)7 zfzH#LgS_AU2~bv9{LmAl+)7aztd|uNQ0g(cQGoLT9&B91azI!T+Yi#xlTh2p|M|HQ z^XWf@V&g$26F;VsiNbXQu>bGdfL1=L?Yn7%od@}4E{{EmJy_b-BB^GJLE9cf^Hk(gEXF!oUxH3JajIUTuM((Q<6a-xCU zZTULum09M>!xtlLm)==b7Irl91SgOZ!n=>30zQHf`1^&&DtCj&8q?Lvi~q$Nf2n9zfEfZ4GM z8{tKj^4Z83xP0^#L#9a8q4av!!V^{77G$~8_`tS}n} zb=r5=oM%y`OUz}le+PZb!d7f+j_v#Kr%Vi_Yt^q$~A18A}fkyZ6p26pix}IxvFKW=C!W% zz9_My+a9(FKrGR#9n;&47Kz!4Jn6#YbZnlw4$GYxs&!lX(QMHzqnRpUONhMlWU+@q z($}jDFn+j;dWvrLGa|ZlS1eYPaWvt4D3wcwu|n0dC`jy^F%-tK&CSVFrUdJK-$wL>dT_!_&<`m9YReX zgkjESxZs!Q>}ULtqwjo~mEr5Yvaj-=^g}`ZjJz~RiIQa_IcHx$;sA$=3SdA6_gWEE zj%e2OKrUb&zy9;GH<*n;o5>eN!d@(Wr&8(yXi~k*0iy!i-xM5+8iKR>tdt#b;6YQ- zo`iELGtcg)Y4&IShq@CaLxZQ%C!~B#*}|X)Yo1jTp96ZZ#!>Okdlvq^fE1l9_3Mm) zb|w0N-M}abZ>GO?w?5o7s*D_o+r96>mx|L8NvDu$17VWHfyTQ*$VLP}lrFL=^}CgG zxJzr3SJXjSdrg|H%6xS~N-%Z9*(vACc(L@)Uwzd|{74rDw`kg}-~8zIuCSeZX4^fw zgpyU7A*+qRqhDi@xX7fgYL#GY{7E``K0vx`En4KRhm&{PZ)8feQGJ$3B$qorWdSE+hW4dW&99L1z@v)_O3)dPO^`|oK8wU-!G)@6MUE?sP(Sp3pn5&u9rQ#@`8xL!N%Gw>}&n)#t*n zI3*S)xu`QbG_#&^o|kx4LF##a>Btel%(&|-H1kjx9*vj9gUWm2GAw6!ZLHTX*6mp;*JmNScbk0Vve4#^?)NZf_D=&9bi;0r6diF`9MQl`!M7_3 zY1TqPc;V_@Y?f3(Ly?g4j zgA!CPubETThKI}baVfR*TEr@QlXt0JJ;f%tB;b^(4LOGD^w_~I?Rb0pC5~>u4t+7? zO~VetU3#6uYftADB`ud=e3dB;>U|z@-P2b+n%E~Gn@pjS_-Ey9cBue(Vk04GA^$M`-?EiE`WtY4xg2Q=YTlo zzZfdOJUCYc&uet&Urasz0mjd)$X$IOg+)pAadgvQzTdJ&(WN46!xk~D$Q#mBP76$P zX8wU(Y~pQ9vbhMeo9?a@swodrxN9SeLMu?n*$Ro^k-Hd) zL0xL+Q+nQ+m>Y6fX=IAwhQ7k*`fRF9;(Laj1u7f+EFNTI>-^?)YJWNlVwHCY(_rQJ z=b6+o1!Fb7)QC@O@~}~>(byvpsiS(&tK2is)=GQ{i=4g9Yc%bP_7)7vU~6a{-28>xD97#{YiUH~5ufO}mr-tfjpz1Uy-c^o0YDfCkADD(nCSXwYQPv7WxBJsk%V z;CBW>mHNFdELhz?+$vLP(>|gKnaF>U$V89kf~@doH1`Kt;l=6aerJh)LY5eg#^I2x zmi*=U^W|vuwp}}JJV^Q?)=W{?m({SU!86OPj7U8x07A%z>V5~T zj;>I>jITk4q2dqhiiGJ@UCzPTup@};cR!7XV_hcy)c#>TLUhSIA7|~SrP0`iO-M>= zJ5q<|8-Uy=7dIkw5cctzPoPM!js!_t`8^yxL~z`lhNis^ywU$x(DJr<<*Ov9?I zf%)k+bwD_p54jrtifF!^HJu+s1|~!4zX)c5K5n0d9LIm^x0ppB5SI44QzWsSRJlI{ zRkpJmnggiUxSD6pmyI1qhlJSW_&`TKQ*phJ~p&8eoPE4P^F&!H__J{m66^!HMTtdZ!Z2UdH2&* zd9N{?V=Izg>UX~xpwzhl!)`DHCN6)|JD0+NJdhy@~OCP6&!UhI?etzaB9{v~3dM%4S52_L~ zM>Ua@TBIJ0&L;ZU7;$Z^QS>m#2?0*nfFZ$=8wVU4&S?+PRWUK5%Gsy6K6!cmVx3E? z%6*S7dCj`70Q92&kYb0+NP}^7CwfVP90C~M462<3zaBsIfS5+Ggk*~B+{jl*QQ{~L z>H!Vs82xE=Lze*vcdq2o+xQmh9nQ&f|wCM9teJLb=hwvBF|nub0=dcW8-$G`r{J!H!Lox zf_9{#9*_TDfi=b|y`jp{o8#vTJa1ZNFO1If&_y<(H6I8!9hzUQ(}D+gE>?o9Q)aT=Lqi- z?8fI{cuGP-k{LmtDn;LqmvnxaaX%fP*p~)}Cc+fij8b8M_S)TOK{p#~YL zr$WUIv?1Wa6{pgH{6RK0x}m>#iNz;#jOdnR3+GH#%y}z;ky*@JbL^4>n8r^eYtW#_ zIEPu8G&XSLHAo|)5c+mVeWZ_lCXaoFg8ShJ@4b(!Q~tYXXLMwd6!IrL&RqB@r*{Zm z;KtsvtdT>|dg1|d3A%v-0ZwFGf0O7Kfo_D1ZJ9=Ct0XZQf46)ZdL$@72x-JOK~`Gb zB+hk>Dag9P$R8I~>+V`WPzx-#trIxO#ZeOCXWWho0|9CSRY}ZE3xNHuQWXi(p*G_n zGdz(P4s4_92ToNvVYb%=N#2ih>xul@f)q=v`4^M?9zP&SzKW7b!M6--nEK3k!pcHF zx~G~y#mjYcPc=PwSdIyFhKeC}7fjabiOKrsTs4Xtz;to*=}5~KI9n6`f}!O^H3K@9 z+Z2U2Sawl#P4q&<0M*yPB#D53)r`~LyJN*66qupsKA88d7tU7-Hk!RYE8-idsgdc%mI0-u3n5po&x8W7}w;O}GXx$NuPy2fH zamz{g$r84p2J@JE6fkDt;AOWHj&Ebmq>ghxksIV36_7qc zxuCd#9*I5;Y*VI&x(N^nJleL*zxXU!cvr`^rQ$mbFlPu8Aa+CmCXNJsXe;NgTRCe5odhBOwKff}LA1_)B)>b)o>L7?6X{i%OXfY`NHVwKc36!4e6%4L2xd0f7# zGQ1^ohP30CQr9&X4ULRPwo-g~iPRR$=lB6Oen2E%;ue*m#sYIZ05Nh&+yl6YcM`l( z_IC>~`lup@$SnH7#_i6|xsi0MQ5dBnM88*xwktk`pIbr1Cc^#ckm{%m_sPJvB7()b z>$mzDLWDX^l)#h)hw`7qK_j^bcToXXnTeZW3*S@j)^zL!<#l+_GbQY161@Ad)n#H)6$1!^Yr2uo_`jr zj7b9ZN4+7o8-p~j-dDmXJEz*+@wUmS!}XHrYbze7dkfI&$b^8m%3KC08%_AN_%sFS zm>^C6VnTHA4$RSgIWMYv;&17sy);^=BKA+>MG-efAt8`E)+CW5aHIvh%o%_;rLl1g z-rRl6zihjtrH2>ZZ56bL#FFc##G_LTYsSu(#)2lHP70v2G->SA0*dUV!mal!JI^6k zI$hOO)+x_gK5y3MZu`r&?bj-RTt4Ok3D!L2I6q~6hJvz?apB?58S%|~-p1AJ4zITi zyZ546dsn#&@|)lRzCqcv6Sm&^OBDmxv-X{>NIPGHl${4RA;U99=QaG-EV=EJbtUS& z;!RmdZr}vk`Dw_^_MOk+z&2+8QiFz+eY;$+E|34kSgV&~ta36kDHqbnD|SCba-)3`pm~&;eGBhp$@^W%)an^aJPfDL+hS z(#=a1Pw)Ae{;W~z$lh5iQ_@9#ANNpD5TJN`rcF9!_(A+M<#|hU(GP^ByO|kv)@%re zPP1$GDtZ5k7O^bRu@H2|*`B=i*^ zO5GYpVV~T5XkPD1ji`-I$95}4?e5(NtBIt;7${8W|Loouq*Df#Rng%#yrO@jm$@uP zeiAOo%?o+r` zs4a^0cB$z;3Hp*KeS|!wY7XPYx1pq4+f0mYUq4!SWA(jL!*22>C!)DsVx|)I*im4h z-93SH4&nzy<0Yb}1pyO5%fm`25cO~1y4_Fe)t6qM{>#(Z?cRHPS3sj#5Q9U8J5w3=qv17gG`uCESx97}o>QgDHN0TA3haDwlv)%wG&L$nabj1OEU!EyG4 ziE*3qfNjNiu)n~5QKV7rjBA%;ch52u+fW)|;CEm(CVpnAM%TtMPFc)iIqY&oS$5gH z!JY*~Vb;Klbx)R3mWbfqEtSx0ULf!%n!=GV7cC;8_d#gd$S5~s+B4C=*O-eI;-~%V zi@zUP@ULlp5=FgYzH^OUGtW`_()f4cb~b6ON8Xm|Z0N4dq%s28P>e1!i5wmH+((~c z%|Pn_5dI?H(Lx~hWS}h+d}C04qJ^{_Blk-F*+5451kM^cXMvrG83D?`Q=cre%YGrt z;|BBEE`|9l|E&mNWk%8V#|Gb)f8Z8SZjyub@W^}pXg+F7TmQ9op$kTVHRcy=Nu@ha@?x$sAn=VX_|}^SIfiWLc|=hN|5%K^t#vsZ|hudzkbPN z?U38F)zMPOdhueE&x@`CA&c7p0M+O2)~t3(L;PT~5jDz7H}&WBb;rX-fPMe{qv)Ib)OedT*+DZ3Fn)y$bcm1`oLcc zv4r^NzrkU%H8s{JpDN2f&2K1@i`uSwhGWro?4^k3`jzjVYu}$UdLo;K$e;wH-%G0I zAJ6w6kC)7P=FZAYOJd@CWlY6hod>I;{AgjXxOd{GsLc3W*h2}XNf$jKu$<7PNg7Vz zn-ca_iWU;4jn0V)WdJj3a`bYMu=8V`bDQN{h?DeHH#0S}-gSG8;sWuA#lM!tUFswx z{OAUPE=A|JbODBFg__0~{tu=x4HKp@959W^!2T+7dcoc1yeTn(&^gr5?V;Lw^{-P} zTd)5Wn3)MkgpGixl!(Um${`;2=RgYnT8RRNC3=Bh&U^EMT$jZOv<+d$mw(Z2w_Z}#BcD&ywfMcww|uy(g# zo{MIa->K?}5w}Fu%HsBv{k2A^Jmv|m7X`ZkiJh6GCy!LBLv;fdq+)tF@dO)Tn9X*P zj>!=xeQmwnVp#s}o63ub4MoF%Akl<_87ew9Zil1;J3{v{KP1kjhi-l`A(_gV0V@cr zfzd$}K^Ywvw+am6&T@CdE+m=14bH zKe-pq;-9k>DGBUUxK#$|yqj!yIHgdz;c5bPdTjDVs|V$EAyGYI1d-7^HSCHQH6`#3gDNFa;+@rl&D@plmFOxh66E9}0 zoJO8!y$xv+*DLAtDc1eCFQ;Ah;Hg>v48@CC$B@;qatMh7eqrlj=Ql-V$g1W*nXg+u zh-6>f69?mQ!<)6oIbUB|Z5}Neq3?&0LswkBGDV6Rm&S^0TKYQG$5SLJb4A?FeXRxs zsq;mD#7n$Qfsw(OHeARAwi;`0F-3)?TS6k+2RxL_bd@@XtYBP;>J^c!I2Yrxm*{_T3nO7ZfF`r>`NxEwF@zdM3T;JHD z3|WMu^exR294SrDk$w_j4`bPp)5iJ5emJQW$&cb*T2ccY_{*y-X1^q{=aDRicIq@pL4E@EV)n#BH+xV zKnO}|Fro!fsZumgb7Cjx!%N}AjJ4v}y zy5m1*u1(+)HkAc{`y#o3TeXRlHNTF9(?48sJe;?3d4RGI@(Z;a(>uUR8tFiWpzM*+ z-FgB9(PFw2K;W;GUp>BvfDrKSMdaOfi7;e=YJ5bUxXDQ`WwgK6hGB<6&`oUL@6a(G z2<)j$LX*rj5X*N9$J^(Q?woz%)7qZ~g`)fJr-&wKYG4P^Xp5V|s1T&1IwD>%v+_px zNt@E1emPH9zkVF~%iuluKpojS=VNrs$c5w{0|M4mT$fo3D$N8j)~j88Nu$t|xXM%)hNU^Egu<^uUluq2p{l zi*dGIrvNk^$q7d}Au2_bE$p%aFc@onkRF0aeSh&o)G;h-`_H3sMo&PD2VkQpJiit% zfaezr;Q2NmwG=}|7W+k3lC}DQ&zYvp53{fSh%_+IiwXpnP*(%kVR2Q> zCh+w8ATsa&O<(Acw1Rk0P{W3+wxxQ0gdYNpf4vE^gZ|Q=0u2vjs)z@@Lz`&9_WNMz zcChG1L^4;U*@P6Vrx(|Bm{FB}Sn-_WGO_u4w?T!#+rS6l8c+GlAy!GZ_u-lk%h50C zfK0FBTe=3veZ{#iY0EIuC0_aQ^Xx;d_n=KQ&R~Eh8kt}))D_lv3m>F14icluqpAoM z9&rkpq4jLQq63v`^GMIWy-Ul(hI!IO6TmxZ3gY-V(vff51WvEJu z-Isv_Rx?q4$B6qx3sDE;QO3c4w2&MOLbf$Y0^gzO&WWxqn2)>@;Aif{4s%0b8Sh3l z1L+N(EHegcyGW*cl!P~5iaGDay8T`B=z{#H-7StQ-~3l%!h|!(1TW!#SwawdL|=i# z^yC{)S}<)>>Ca-EOFo+D+iX^IcK7YMzIU(v({T3mBX5E$0^RFB*}WLQ{}k&@#shyt z7uPRfTv)xQ%4`0>73r;y7&XW?NilflVy}1a(k3J5QC$Nm-OOBs?z5oo+w!Qxwkwpo zK9*u6p2W{mo=#SQ4A9hG!VE#Ge5$Q`dn5ibcN-(+tO5OG%PVya8=X86@LP8HM7bO< ztjW1FVUXnrLkCl6CcaZsf3)av`Y%1;tHcBMr#0BZq<^_1^`ySFi!y z?lqZk!rZnK;_oSK`wHj(T)BJlYL8eS$5}k*j@I0xC+TLJ~Wf_pe z^EnNZKIbw_`U9tS<3S|$ag7y8B9tWI-#te2B1{fn5)fad{#ZkrhPk%npS|pD_$JRG zHLV_;b9-6;rk)Bfx+^!e7=ekm1LCYI4H!y6o9C!dq5Y@!Eq;>2GfwN(?&-U005w>k8IDq2}UkAmadX1XIpgATm!+}L?|IbcmhI|V4KfnxDF zq9v3aCaqcD384uET{k|(tWT;lTiNE~PG2q%fgxkAN|V7+FI3wGP1=6BFG$xELoYd}xp!S}8N&bJMq zqe_12*6R5+k?(*^%C#6PcS0KECN!v$2V!h(jp2^4izC^G>R#h1q!CrOkEzvq8zR|n z>ZZ7i$E+2`9-Y`t0BQ$P!Q@1WEEiWvbY&c`9h`gbN|NiN$diue7!pgjk?*@f{RHVV zJ2o(i%Cd=owm0c-kSreR?KfjSBP`$wGfN`l30<#;eN0!Sa_(!zn6AoLXq*t*c+0}jm;AUpmUSKY_mhqWU|?Xm&jj3|$qcnZ z_m!p3krN9~c$d%4a4F#I^v+$S89mYsg|VvXS6x1`tc_Yz@%796nqIp+s!$fX#=g{~LY>Xt z+iU+~XOvsOy@PvtwC}H}(97)C;%Ycw!?%B^?t`MpdG=?4-w19H);cgv64u9UN1)%b zCi?AXhM7nw^dGX$C|qa9y4zWMON5qEX1HGq3OsqA{{9bFc1$TeDL!4CKHET1sC2JN=QH{iI0JH|&W;@sL3x>zusigew#9-k;IhJ<9QYRJ z6VP#wC`X{i5fVxMGU&XbaVdhqe0fyU+bs=szeFpydj3;yj}D37{nO;uVZ8^)LICQw zUj)pWZ`5*p!CTx+u2Pkb_|x4_JF^y*Dc)GT>t_0fUAjR7WXp0%jD^C@40F+|z9EgC zNHEtZ&Cboo$7+r0_h3Uym5fE5Hz2w)D*`8ejU79(FuE>CZx5?C-)tV+rXVY}=ho+s zQQt1FO}BC_!&ZpCBNl=xg)F}1!HKwg`%&ZtXH;J}K%DuOu|A|3nWy(6<-3{WHSOZp zE8d=pU(zR`L#Nqo7TAR&&yWIfC0-zU#7)A$o^eVu+jlo2Avzn~)vo;fm42LLYM2vl zn_lZ{$ebr~YF8U293_Rq7GSCp7K9l_qK*yNVH_ky3*-i>;#8qkRLtI^5?=mr^y^`p zup|z!e){x2DU`$`j>0M*2)+R2V$Gh2c!cdvw*^F;hc>YPk{MZ*s9x;0#=yCx zNi<#MHhH^Xb0s>yxr(1xm=`Nd4%Io}kYKM}Dqgn1X^+QZi|@{z1T)lOV{)3!;f$i@ zGwu`SGczmjau`lO7Oc}7eo$Fdjje`>$1PKIC%Vc)1BLvO=i2$q!HS!cyn~R#y(5O{ZC!L%QK=<|tXx&1Y**m!h}r^~3wM4KeoW1JC9F z(4~_kinJvD=0EEO5{ZyS7MR@4rW>I|3PSwAn4izg=z!$qlY273>WnoA$Y&~a)v1NB z6kk5ns&iu85vuW{7uGcX+PVms3c({~kpowtB=&-@S;np(D$m6|TU#!>`h!sVfwm%! z9lMf{`Y~QXU}8W{DL-%p&{Im;RYTo44*ME^6xU3M(H3w#J|exiiXi+xN`nKeYzvSE z2d*pY`?o#Dg)6o!y>)?qfa5NB2EvZOiaq5`soW@}lEx3tzz&&t8t?V$-dhY-2sE>+ zuVYU8_FJ8~xLrIyf6gwQIY=YF5OSV+eMo~SS7mTf*`AglWDY?UpMzhEVXHK@Ec!;= zQSFo+{?MTRbj6$tqmO^N{c6a9=|L+Z41Yn_XMFhDRA$3|M-1*eEYnY|^4_9P94}Gh zDY(p+iZKaDr$X7uCW3UW4o#zLe);-Ml`5~i?(y6@rN*Cqboy%|RR};~iuU_a!ucmsyKA3bxa7WJRr63Wf zEV$L=!;C+(-efBxT(`b<2>w8zIgi4*D@D7ses>aX>hF0A8hD)N0)@d^d_PNuH-s;V zoZ1}R?6DqIfPO+*IV0giqZ6IE%k!r`7UJf!j=;*plWo5f{ z)SfDOuT;0*ixHcxn9!k4xMzd*W_o17dI+kwT=k>2;z z>3Mx(muvGsgI;{iW7|ewkH~RB;RWe=%Y?WSc^7)-H6#HGe>XNssyw6o^_qRamz#kY zb6=0OHmD19eIz=cnXSYV1|W$>yhKK)xav%&ptYK@ZK&2*$T?KkgT)WV4?MV6 z6qkK3t0?64_YfZM6fzxWL6uRT+c)!eQit&?oUk?dq`D zQLQqwUy&bjC5Eodz>Cz~Y`j;q#0%j{=qq&pjyFc%e*TV7f~Sn!C?ND;29Qwc;YI9W ztJqm_JKZksSQ|BKXvG`c^9F>LOA_3cHAZ#RrPc$u@gOpnu__m`D)Y}g zNnh!5!>;dRl0HZNiH=o0$@AsE2qun&1~WIB-iD{-!nDITG24~QMmjP%Ji&B>=u4V1 z30clPLK^#ho0P8JqW|>jOJ3QFPxAG-BZ#3IuN0CsF!7^S+ZZcSo5Gb}8({aXe?Ys^jMJ9OXelwtC1I!(or5IkVw5J z`;{(Bz7selPImTwC4(PMLxmoVPU3VD_H$f$RUC8C)-NyKK>#|B@4W=1D~E)O8aDF{ zCB8m{v$U}5;z~HobDuOGIbtqkRGTY5bUE~JuBH5m>#190o)la(`;Hh7KVLKFNf(@< zV(>@dhvF7OXd8GIKw%iXvbJkdT9MN^_e1muDe$p7> z*>h9AW|PjP5<`TlzX!LG4ln;Igc}Z9l|dAQoJIz=3U!jLBC~&6d{*9Sz17(_Pn7xT z{j*|Q%Ppn$AJK2|K8s<4bJ&3R7PcX(Zcveo?ro4p761=n9|1yAp@}s#E^H)v*-&*s zk2<$AVWX=84ab5Cg}LZsF8t9Xm@yNIRCAjnBc%Ci%yN22wTZvC_p9Mx^E~%cKQ=`g zbGm}&{K4^<`Vm|r-h#Ruo`XmZPD-W^KYQn9w)OQ5pT{a3r#6{uf$CYlC0@YClbmeO zNU(M0uLs|{$G!YZumf+n1XW`&{;08f%r#s!bsrMy4@9#BPM-|$)l%Kza zk+ewpo(GHCslamj{`@5k-;}jdPM#MH3xgU{V33}}Lkf(mU{`3E34YBPq)_7rGzVx! zKp1H15NfSc=~5eOXe9yVMTx6!+Q1V@1Q3)coLb{0Vp7ro?b6}SNDJ)Wetu_3#SYo) z;t9{UHs3!ejPXmJesrd{(JpX}>94KS{ifSE()VF`Qz zZzM?X;dF$HTNT1cN<%v403-#NJDQCU5ps{jX|d+I-S~FzNY%rWs?=X$&YLB%g;vq) z17#6-gG6CW;2l(1$=u~(&|**p29fTKniKTsh7nnEb&32kMQ=jjRI^3L<0_T~jy?hOnHl__cVcOITcVA>MUOb8I zzqD(&Zx-UFY*@1<%PD}C=J!s309ra;FOAtNjopsyo+OtwFq8Sae?>z6PXJf_J7YDI z7IHFU_07HRD;IEr+{Gi zMt7`E**%kgY?MR<(AgMKML3!gPjOgCuV_ISL@$3ffYxoz_|4eSGh00Z>rq0X~y;^+Tt7uX2AGP3iY zaK9j|g(0|jkdy&&q+!(IG3`4C2{cLvZrZ;XW7ubog(C=P)Mf!bBPE3#Gi{7Z4ZI8g z=iSSTOmWH?*VS8_Pl@8vFgD|K(f=zx$|5!!ZoUc!-H;AV|FCX~dT0z|7goV$M(Cc( zy%}R0RIForYCz82mt_GEmv98S~n6RD4?dv@ZN); zja6AqbXo0cXGsg^`>3-+EF@@u3Y0V?U|~Q-dx32Qt!8d%T#HTJp z-`YEK>SxrWZI8>NazMgaK+0eanyg2CakY#)P^C2pWG!(Zr}h(nm#i;fpN!kh`hndn zB&blkSt%B-M;z+$HH5)-km?!0GQi_u>K&u2uFK3)e%zk99>CV9pCvNxMtKL^DDk}6 z-8~=J*92vlHYmeDmv8sOFk}Bv&CRZxQbYvb?R==KXjlkAQ*f;Q!zf}(Q68f*faV;o zuL|LzrfKnWxcNdyHAnmtaO@jjz&n62f{7PDu|`-Hw4VNdQM<)$Y`<^yH!+}QP*tsP z)Bet^zihPaE~_q*R6ObNm&y-bcY9==Hs8QT457htQ7IRsXC0G|11Rb2tQNP@C_H4P! zw%1ReQt92wMhMOA+?#8V?mAqHi%_xKQ>GR-!cU8bY=Z<=Xe)eHoAMdMTZ1;A%D$>I z;fE4xdX{HzZ1;@4Fw~x|ez<`d%r@a)*hCs`#TVq9*r>nc6mGdRIFW@;Y ztmoP_-AQi?*EGpIa1Jy39!97dNejHbOV1F_O1Tt#XEkheR0*6|Or73eEN7l@I&6me zAv5Q1lrNu3l4J+>(TZMfN}74@@y>{jy9Wq<;dz_%H*YQ{w)}_bWDOOb$4r#JYV3h7 zG&am`#(y1uI%@z5-fPm$d`CjU2W{Le4?T<^u3c7;lGlj+^v16@PzhqozZg)IhOh}9 zG?rrkfFF3EcE&;MC8z@xi7?j+zM%g8LSF$j(pw!RA$G#Mpm?dODv*ixNFT-(;kr)K59s6zZ#vi6j{E|LBeT$h{fx$4h(mN$Vcx_B~>9_1s7U0rj zuHidWA=V%yuGXNlYbEmuCq3) z@)-+%FGtH99pcyBe&N_Vh)NB+$KQ>GN4IX{&}36WU{lZ*GF-3eUXK0K$9=jw4w-qb4`4}&LFwMr8v|G-xrndfA8TG^Id(W+8ORYIn z{vw?85m@x=dSNdM=|^hdr~77!WAqP?7=Il08G^p#l&KbQfO)K-tcbrT+MFf1CguCJ zCo30zPq|efY$4-)yzw}TAVdF#yrOZhyhK$Bs3UXCMN@DhP$T=ex|+{TW?SCyH7T5L z{A$Nz>y-&&&sLakTXJ!pN+RU!Fml!)>washeK6SlAXBkbn0;x%%vwc5GObFVHrAL?MM++8+ju$N6BNg(ZM;MR(7wr-H4w%h}nS{dvl zlZZmGn9+D55ygw>dLZa|N3M?~!`^Y2o&78;jJV{a? z!63~{4OD>4sSOycA|(_=8j2L`qsj-KDLr-;TeGh;`p<{SLn0pjXxU`o=rw1Mguz_@{U

M@mn81Nn$Jmxo}!*iM*29tna7HwCbeXD<4bC<&Q|=p@wE*pnv- z0(wLtRFT8*?gak324i*4m()c5J9p*Nr7I6dIo@B9JzMr@aErHn^dBhXN>K`+-*^+l z9k#x5r7TgvEzCHvbt&(1MvREfHP>T%3iQ8s>A$BvAzw?07k&g=qG9qS{Cpy^rSXcU z@?iTd)9RfLeU;Sj;`jMydm4TZM?sgDFLe?6MPd`ElXLFB59#yxVyMj2OS4EJ%c?bu z?xJ}A6}vu&CmcFE63fXMQ$pb(i`cMGEB3=SfP9o&?lYK2TnBA)9ugSz=X|HPBLJcw z{3appy5DVUf@4_uU{yu+a%t4o|m|+9G-s z!UH6`pa!M~?|505HJ5)Lu>nV1z9c$hF8`h}qlp^X@e-G*jBUj6FvLax)EGY=Bf0nj zN+)OrZaDi+VOan3dX0dc%5Po=L`eRURS15WT1ins_XhZGMk)=R0)R1j4G3OZ2>z=e#UkbDEoV`OoQLM@Y;f!+(l!+Q6x;cJMpz zm45EJ(c1VysrG^(nb1*^;D`*0n5c21->(6Qt9l!mC2<;Q3y1%Ceb9M!Ya+3{ruAkl zh9|8z_`w&PtjnEo{jGSgPHbvm+YuJxJ77b8{TM_gH=7-7QnTkmw@raKq*g6=RCT*= zPjX~gxKF8yu;Yi6mCFW2fi+(eMd9Ow_2l85VR;i^$gAM2w4ag1n*j(M4JztiDrE3q z*fSE>6ppn6u54AXUa=_ev{{QvU+dm^0V*|#>~fZzB66sDe?bZ+ux#X+PzH3Q@Kx0A@1P9(wyW`v&zLnTLF|OI6ZQ%S({zaZ$fV_aVJk|u>6BEMwK9Qx z{SWp8kZn@OGzA<>xuZL{;m7~%XFyKi=5~-4fJ8)5se~$?As(i&sr@FLT6<)zJL1D|a=R&M4Jr6;r3{q(}K4U)p(UbbJ zh$O;cUxxe>a0K6AEBgr^z##4YJJ*%bttRCgMeErbk+3+niLkNSKg#gj6U>>PDhOYa zT<8UmgfYs3ls-;{C{5$83)pZ0Rs{i#sxayN&^0DC-(I2S;pYs097?1@Hhz=8AR2sZ ztK7f>{kgvvQ^daz;voI|AhWczm!rH8q37a9#4)}P#>1kpvaAMpuzk9JPUA^gkl#X; zUujBU(^2+d{in77E$_0K9;M|mQ6FaG{etus{x2+$aS}+Q+`d6F6=4_?H^FTQd_x3Q zH1GtL{C_HY^LQxNKW=;`g%AnJIu&InA-mBcNi{{1bxx5rTgW!tmQb=3F(q4;RF=uU z8)Zvn-^sr3V{I_zJl9aCI_LZRey`^b=j1laeP7q-x~|Xi{=DC*m3AA{(5NNgq=K~( z^=d2o*pbU1m6S;`$-}4($3HX{BcLBjU_^$#H0D_LU|w^%=JU;}N`-t)jw`DNyW*ci z4RDMDAVCh`LGkmcK>2!kqio?rx!j^`SgD%z5R%QvGv{)d=<&eYJjVwU{%INccY97# zgw2M4p%ME7i7?UE1`qk!MJCt_qb2TIlEKZ0oqUO z4S=%qDFC=+dWN(_uWrb0=0GyIsF`V0kM6ItG-4EY6hf5?%CDs04ig)PJ?%bvaTK&k z3y~Y8`b6*Kr`@?&RC9>AjSjX(Co(9pW5+-DG0T6iQig#bKm+0&wn4O7?rCm!Hkh-! z`to{P^0KWs7beX6BxvXulIKrY5;ab_888kZRNG#)=cX|oAK!J!Zu~+Q|9)3d^|QwQ*da;yk?{H> zSR5RF04skIu0p#<%>_?LBSF}xQ*fb^$RS-&XCr%eBEmt;0JFcY3*s+2d@ZQCQx}r3 z+uA;8(3Jbn3v{sUC*i8%nAksNdVzQV-w2u%;fW%5fxWmL=WRU)4wX1T!r!pqQa-n#0s4M*=#AmJ zmrki(|0cJT$zev=aj4t~Of?$h_BW^uy)=PL(ud!3SCAwc?5}^^;fC&zS6LE~<>T7~ z*YYvx-qmOg1{QOBb$RU?_a>Sq<;hGM^1(o`|7#;IueEuA1-V_E!qAnX(T}5587I7sEU+2+!SS_fUg*JtB$x=7g8+DwQ1?0C09oue~j~ zTW6u`0USf#q7S=2$HXzQ^*|q+iQw=${SvCjfAm(I7J1UD7fPa4~s(((#VIQCeD#1G!Q1C3+n42N#R@_5-;Ct&^&wM z*zV`+`KvX1SJQ=oa;T?QA|53zu@0x?vXL903sjE0v8Cn}JBeJPdJlZ$-^otEaQqj4 zo=Sp#0njO^LV!XWO`L)jC|=ge0lcEu!RhU8imkKT-4rn;1*apAmbU@BNdmS-_5+*# zOUNJQ(xwX*B>mYTV9nIPJqDNQP(*zs6R<`pyY&K~9pb5Gk0j)JA%OE`Hn40*NFpB$ z1C|1m<9mUru)+tf83M5pCK%Lo`^TRHy^)iWJ8upkx8P@1?7o|kt<$Ri{ zc%Ry}H#vH{#~JUl*L<7cvA3?q|1p>_dz}rB1h6YIR+1?k!%cgJC1S{+ z2hXXF&kHwvEssdB4Pmf3Z3J&$uEg&?aZa#aUoM_{SuMGywo3f9I(@5i}BjIF;|&tGUb zp=hex48&!?1VKM$D1mV!Xb?YS;YvmJ1JMzfXBTEo?nQn={-`rUveV&UKh*=xY9=!Z zYhgn(cS<`lwK~}U@)?j5JuNpfrOL&sO9f0q{dbc9%`e*{_roMS{ak5__Ei!BJ$h)6Gtx*@g@3&|zFgQ^?h}yHCtO?d%g)PysFvwNiRceuI{2teuU zG;oi3ewDs(t$nm%vx-cB>U8_A(nThKQAm)_eL-(*p|;>N1J$lHbL=1kXoK7GN~J(n z7w_zNjeyro9Xp1B^A^Y}!5_oVmbSE*u;#+oTNlrg)wj&a5#P-3!-1r*jm9ku zh1zV7Di6>(*Kiq9zCU1`6>=`z|v+5zM>nXV< zPlKry@49*19z;BstZP)Fpy<^rkm~|jCS)(rrRseG8j8VyV+r{^J_aIPKJ83Uuk2^b zkEqIb!D=%cfpRc;rhGLnc@kt84@&?!qHSjA`st7TtQr#6trle;C zfT<6^0T7eWMPua1$HpayEL`rI0(R_q;^jrA>x(P8OVjpe;h?kOp$nz6!9gW*HWG-s zw88T_!nUL-4Bl$0wC4v;FQ8TGHx-CCERApAOZRkTjjK!``)~=92~4q+J`1ItJC24H zrJ+hWpcL=RzJ2?i5EJ0404@N-$Pj9S*4PV#0oyNe)h2^ISeg>rPPz+HO~ez;ndqR# zt%(V+f3j$7zX8my#Lg&qGGA79O<;PliVoz|{phh*$t}_FvZG7t$d{lIyQqH4rH1?7 zuKQ*T?MTutL7?qs=Sj!2BiNRJZ}g8J69E50t6`)UZN7(K zP8N5(VzvL=Qf%{B(LR;x?Qy(f+Ps}Na_O)-NF0GG%j9+VT;9{n0gcxdoyEbb+YJp| zuLP65Jqt73$4SOgLq-PVQebaQ`(PLxfEI8Km&XIvSW-O2&m%YYgM&Jr9{CvsX230} z+=8k}$z_q{`XVVR+ln#+j4Yq~8c61fk5+IhDak(sC|m1)Z?R@XmgBE&qqMzek)Uw7 z1-nX};fAe7a!&K^X*$xLJ9#@=L;T=@?1@9dwMZfEMdO#o5a410$HNP2@Dz36cvgnF zVM)@^k3ovqj$}9$tIfmS>yc3a(xltSn&^G1^lXNq>tp@mj`?PjsCkrRyA5OF zh?r5gf>D~ow@c|i{;!g`6^(DRMlPHsMVF`GqVQ(%tDKFSAcJ-s2fY=9Uj}T_1HZFL zx3BwwRO_dEzuE)t{M-*V_Hwn<}?UDCxB0kR4tBOov7I<-6~6OdDFU^bIFG zI=}j@g)ACFKWw~gb7X&GWh|iO0;eIR``&E=(D_cHi53e0XZFwJp8quaW z1`3FW^W_Rgvi?fyoO->zM@sa>?r|6I_vRT3I5Cljsc`#_^V*7qMjeY0E5pDA=ddEbGk|I{h~Jq47HgO2V%UOmgdkUgq_`bgL!?h8(G8`W#H zu!DKGQuy?$DL}i)@9C9Hs-ar~Lu)}O(k9cmb$J4l7_pwKjxeEhp{+R|Ckt+-Cx*JAs@Z~Ff+?ziiZAj0!^I-ZeSk=LJWk7+K<|` zEy6rWvx`n6M8NEfFX;N!S1?hk=VmG3zj|RL`jlBt4D8SWjk6WF`X4twNjf@@(Mj1= zi@58D-@NTDS^mMZ^AnbWlh;_|+5x5mIQliE+!-j>>$=+yeC|M(1b*Vd4qRE~1BhLf zj)B2pBI-^{U=cte;erVTBJGWo2(S6>9)I*o5)u4uN<7fRELtq6v^n5l^DcS@zxN&; zUus?+n&V-zB7NyJb;uZi)sj{2EReeaT|Cq84?#fn@Iuo)b~O6wCzImyUwwQpFpo^Z zSZ-sRPn+2$M{mQwDG7PX550D+wd2Y8AOVNzcNeyV8S~a^XV-%Ish>sCY@GxoH5ODO z2#O8vr2vdsJ$*OHuHdwztbsPPI1=1>*!F+Dk>Xw-JSklr0BcGEkpzEVEp2wczf#3$ zE0(G2ReSZsysY%iWfen0A0Rpf#)@9<-)>aElx%8v&3huuzbw_ahR&cR*26j-NRw2n zOut^^Wj6DG$BGylFm4PDn&n_9i|5o%i!(?FVTDh_xowt_k3TJ86x+j3=miN-tSDWd z$TTb4r_#97IB+d!0w+DN0~sfkZ0=bg5x{K9ti{i6vA!oBy&kE@)XOjT*Xhl-GsYv^ z@aHRZun~#^9R>}2vR`cA#ljPqa9fURyE3){R=m$0U?u|}o9>Os%FLN_UI(Qzx9TQv@C53yDdes^4%-Fi?r%t3z%-}Zs6sCmHP!N|pBH}ASe0%7iBxd-bpOyu z-Ih%K?RTJ=IxUzO!5YMe&6EekMMjG=hD;yv>jKddTOO2sfiGs`D(nJxwEkG>onak9 zSfH)*ckgfXp7AC7a`VUOJ83#f5rCGx3Hk~S%^h$y)ifv390*5;E$w7o-6>@L|3HS1 zmm-GQgIQ|GM{MpXI$Ci`U-mE9Y7U;*9Geuxa{MmtIz< zH`f=)^=0|gcrF6ri!wnPJ{e&4B6pdFp8^T3*zvwbvF>`oRwMWBuPgmk>a~D zWgW<+E`P(kbyzl4zRjGQ5lQ1e^L^{^kq5EmcJ;yY6C42cYw-tq6?m`CA24T#YO>zB zu6dTudrA&`J<`P|6#Q1c0fdCSGC$&=?XU@L>(z){Itp$RoejQ!QV7l5*&F{8Lo&OK zAt`Cz35MWHfpa2c{CQvl6hXp1PYsZ5!!_gkUan_g3buEapF0d95dj#(;(;T(I-fVG zvpDgfT*-iFUYYOCGKG@N4OSoDduF~CVs}@Z_5G@U~L4sfR2a-0u&t%O% zzuoC&*$ImUj-r4IE^ZajTz`sVNNf(^?`IAYx(qO@x^GBwL`#V(Wd6Etn)w=1LjKHV z!Tfi?-rolOiL{0<5jf;uEBG#+DF2s-HS!DkW7d3N7h!L4VwMS4CRk?_It ziY|(cmq#b|K7Oq|_WUv1NB@~ZcU`G};f(aA@$MS)$i>G9IU=# zvY55HwaQ!&`WwRkc*9QKZESIEb>pKuzc{Z7{ILWm`Hw-( z-MpUKo6^wN9Za|T#=CNzt!Rar_@C?!*r)g5Mjf(}bK^x~AejXRYI{v)6dGWl7PXd2 z;p5o&$;Sa}qR|1!z|LTB>r!BTpiz*>u56PLJF8PD>mFN$mD&$?yIJy=6NgeeCjcj4 zKj4>6f$GV3CQ!qP&V+vdU65*}7~Bp+2CbjAjCoB z02GEU8T88i+)g$G2spCF_+#+LU+f-$ph6nxz#u5(9yE~{5EK5+*K0qf?4wjMp?vBp*I#>WAj~Q}W{tRTF+_4*|Uepmh{J zX~fRMK);^m8NhC`e}Gn~fwxPc6o)?258^kdZUP7A0XW{LCleU8$@|fJz+7wL&!PKYB z2hHi;rK`fIDyUMtg76SUU-RqbpGhQHa)1Su?!RVy2GGPTEMU-pi%hXvTjO1?x>ZZM z?p-t0p(bj&b&d~wDyz$Rz|owY0wGXJCq?}@_#$j0^}jdkOG#q>yj#E&k0L^+F(5!x zwMJ|bD1gnw<@A&sdDs>aMaawyf{h{n$C&*ZG7y}CA&cJ`GG>rXwXy6mewnOllUrhu zahpHu!jq$;t8$0Na{&|0dnIk@d@^U-gwNM|>VSz{=!4e&C#RuRHoW0PFmm*IJAk7D zJz7wfn)vhiL6OFJ1i8U501Z%x$MprNrU!iK%1=JlwmTR$XJXs@Fl1sOpPEL|WrCwt z4hII}*FEh85L|qY?ncNDI)GgG=Rc@utUy&R#h1B_xUg~V#R{I&QWne z4EnCSYP`_U3N1U?+%rZ)p6vEgUR6(!L-fPKr0*|G{*y0b|>^7 zN)Jsm1sLH)^B@@}gynsgjg)VM3-i44$M4^$y6d)kxO@!P81@G?C*sA^b2}wQ!cjn! zLo{)OzA6g|8Kg)%@k~;Eb`A^f-`nJ@8Nzop+`-}QCby`Xw_GEQQpYx{_6|Y_v51RQ z5d>DfcP+~Yr24k=3KdN>?SI1#c!?8btH}Gxnx)?gSic~Z`3wu=MbfxqHt5#RP+(FQ z35_3d2Z<#YjZDsm9xO07-7^Z&b{m)KMhAgblp{1lv+9VYW!Ys`_#i-Ik#`WQWaP(u zj3VJ|t-D#-tv5z$wnqyJL)EJ)V&7H7Fz9yUt2<(Wqou8`9cMxHa1p9PFcObyI&~WB z$Q}vy(r5o_ zo`TqviP%}it|eM<@^id%-*eBs^}4@uXDVd`*&6ijNnYkwC(kn))z#@o`01H%#l!_F zbplU{et8INI8mKQO7~_6=7f+sIE&ij4*>EoFgA>3%}!u!9{n>mG?c+PAG8mfPYdUR zuIJHrIn4;I()*sfv6vg?6Ol)Kg{CDFv_8U6{?2zD$bp~$Wt1F}mre&##{ki3y(WsC#2=cf-9{Zgsk!v{ z{ruIvBa66tlg^g%uB|w5$kHmMlH>Wo38m$&JYLiYw)S7Szs%5I$v`LuPSHC*kfJc<_+Sy?{QxJGIGQ3FQ`9jx>BjguYhoW^7kfL8dW z-kZSmSP-kC4nMl>6H)vk*bf?>A55*PGR@Kd3dMo9{-O=|jQ`(07))e`0j7__08_us z0HgBnT~4^nWQgW*t{t^&=uJrXLr$Aap=;Oubdt91P5XDyDZh*&gBElIZr z$g?9xMSO8h^{63rADP%QHmGZ@q~1rpPGRF0PI~!xzeE0u>=qEPO6ztGu&S^V1uFaZ zmbb4w2GAsggvoknSKm>;4*;9APQBf4ydQ)h9$+s^eSv?D^G}cn~h;73vN;X#NHk^+- zL;{FFt7DmO1ClwN#k2puzrhyqTU1C2YSD;-TJ!_~D}XZoa}7aZ%iVbk zCB_Eb*#D0o1pWMU@JT;0liQ#4pIh3#dhO4z|G(VQ&kx#u3UEs@zY+KX$WdhTuStU_ zbHQd*5C}%iRljPgn49<9p0EBcQkW{(m79P~N`Y=d>DKR=u^b~Fozh%ia*KgT$^h$R zGliV`5FcOMGx)mAALAnmke>hJsf>g~`pL3HsmeI!2vFT+>h+8<6b%5(>GxYCzdu^V z?M{H{PR;uPXN^)seGE*CZQUQ8TFgZHRdFi#FJNGUstf}NGS36i4@;2jsN1eHVe}|I z$z<4X-k6uGC2&!k6D>Ch{OaCMs|NYv(-l2-{il}!imq0FFv>I5QpnyJxQ9q%z&XvUQkiebgavm%fN@`#$ z8aWQGfMnCAR{1+zq!-9cSHIe1qJVUWSjb|XlC+5`A!U?69kw;H5O3tzHbnUs-m-~i zU>)~W>^J|=8V}t5_C-FjFGNAB9WRf~BB~GaT&&r5e)-CP@>dP%knvBlir!!Cxws=# z5!9Q_(VO2XfJ-nVpMI_;u<`(F^slXgT@?XqMq@Q^!|4y~YzV(C(`+oyZMcZ!aS{)R z55tl{WT_5{Xn=ibbs}2!bQ0X^U?zr_E$|<2R-=F0-%c> z0gsTd;@86!tNdVJHV5p>?6@p4y(_*_0qHZG-JU06tyi^_6{@!@fA1kVm0b7=+Fd?V zf_xC+;}}1ehTJ6ON%JZZ?zGj}*roMohz7H<-O;)k2ixVXFs&bPAm|vhU4R5inU25_ z&i+m=1L1bwa-ZkkO?l{3H#kv^13|72w5chwYDHg!3D#xmFV~Rwx6UVLm$%#e6~~u1 z;>LP1{*3TjV&LFizpXrT5ZVLq8&yud*u6F0lZ4l4_oEbS-z3An>Cn8xf2h)On+$v9$@$`K+f z17xEI{EjgS25xqeEhL%fcdqk6NRshc*P|7VECIc7FKRPIOm`aPK)3_o5l5!#L557k zZN!dts#8l3ZI%BX#oV)48pMHm#daM7oh8T$Y=W-35CuAwngUTOeZUDdw8)puug(zQ7LG1XD zTVfiM?ypnOOX8cIZ4f70sq^ueeg;I(j1@l%7pi~#&cm9yq8fYaN@&Q~bt3;Um?E#2 z0M-`_KpQ}eBI>6YMH@zG@e?>Qk1cF%*b#6APIw;IQ;nB$bBo*Ojz%9c2|Z_d{bcr= z-OOEnst~V#e<{`4<2u5f)NKl~)hUSZzLdTM*}L%pmXH zQ#ms_<{Eoob4xluYBsPWw)-_7zh5s7tws6I3q%~@3%>J`N(pWxNAwtZ;<)wL?dQ!_ zvvorsOBWlr;CAxBU|x%n*&3&#Ro*5VK2|pFG0+GK*ee`qlltnzoGM4*PNRw?a2Ff5 zWsRM<%?dCQ(x>|QTW!U;)wx~lbF$>DgjZil{z?O*O5_ldfKze? zTR32UT55yW{KV|Q|At)wrTNKSc;&##0Uj!_*@N!erGX*zt7z$aYRP)lf<05aFII$} zrh`*^OZzKCQwz1hI8V3uY4k?Jj4iFB$`|7wtdtaq<|*&f>gjj=#KavA`||YZ6VG=z zk|=5yIFhZtY@0sK9Z*}CRk9osyD}&0G^f=u?B;r56OJky6W)W3dDIKRey@2mFA-tJVvc7I0}pI>Lh~l>kN7oFd^WOKUfQT zx8mFg(m!L9a{eDuJK&tc?MUsoM7XWe|0SyGrJ)cNcddV{62H}5}; z3an-zrWcU@5oi40vnsIjmXexku;W4&iQZHiK_FAL)7NTq6UyHq@{M?9pi7}mDj2kf zm&36E4sbptdgryb$4&OWKz=Vv=M{LzW~J=#QKr zFH^W69^qaA2o(nb_W{L3)HEvSOwXLZ6Gg$1nY9*Oj2Sw;U|g@pyEEiE`>uRc*1l5I zYIaO(T;S0Qcke-8b-auh*47a&z~5N9m$0BoU~suM6;Ur&B8YD2t@%2_rMikc4VDxI zFDICYz2XFy#&$Wv55VzQq%)lF)HIonU=!%G641OdWg2}dGNn&k_Kx?VK}NaEk3lfu zfYe*OZ&MAYat^$8WU2aErfPqhZy*|v6?K3MLKEe?iOwmmd&DCy6-GNaT`zc95K>fg z$_J3i03*@$Huh>XW-l#?p4XhIwoJC9-E)GTI|h@t07Co*3cDvSD?mML4m2Ri#{s+N zHu`H}8~ueo2cmpMKnx|fjS_iycymB2Uch#daw^7`5&DUuQZlIe8@3yxJREy(!nm}wILIK8Ui8kXU_s-P z$x>_SO#iPBM%h{;@aI?od)WWW4_m~%8f(P+i;LO25mXU_=b7n*(my9T?x%U1KohZe z?s$ZKD(#*lhn*AlL`WvE1LNX;S(S*l9cNT#J&UDoTyGMklzh zFfM$}kG z5sJl1VRi1EMRC9jP7T+Jv)O+&a9nYLDKd^O2F;WzzeD#E^_8`pH!s&|7+8E<5c~>* z4jtmC;1PM^ceOpY_i-5WO!QrxJeSUkX1*Ym&)%b>Wb;LN*rd$@$%d7=`Z$(5G>Ek* z_VjHU0p8WFp0cW_=CZciLVBm2XzXvyOAWAnm@3Yln;=#ixa8&BxtTFocla&R)hDDX zF2Hnt97r$5`=-#;uQb znoh(ONVM zcCJ^{$%_13SuFG{2|o6I(A#~4B+?`e?YAsmh?br(Te?60EyZiip>xWR=Algenep$M z;YQ-F46;YsZw7yIUDpNLmeJ>voZr+y3(n?ZFV4X9+zSd57!^Tg`$1d{nRd?kio`so zFmXxyyTuj9h?u!U8HFn$82xW*P`VU5oz}TWS8H=e;}k|63^Oy*zxBKJ(0dB+DGsmZ z*SYc6JJVasG4ZEs+iuh?vi0=`rYq0V+sH(ho)fX<3L11=m4$$u%xsZfxkv@}q?L*jIWznr@uM=jbKm z83rVIlrNtio!K2O}(BIOL1Y^@YPHx*QF$ zBz_eT`zL^e5k83|QkK4@eKioe*gD=Ot}-ipMHzLA7$-=DZ7#j`){3*$qO)UHcgx9z z<#~}-j*5loS@EQ31Iyb55ucwv^;MC|dpJ0_`eD2Xv1e*o-CR7+v%VvlY|YZm)Q9Mt z3cENhv)Ar$PPmZ@vxCCO-8;UvqvjD6731FW(5wUiy*If4HmX*V^?r3|IA@sZm3X$s zL9$y!-4&I)CWua%qAlDAYGaoGd3P;YrmEL9P#1j=WiQH>`5JMDnL|~_^aCH%ClIC& zn}gn$8i4pIJt8%bqme{THZmCjB{#b z3TK`ZYIFu}Bi}Gt3DNZWs*c*z#aC|CvVWlofyNlUp zm1;c*)&(r;J3v^(>7u_+f*8rpqNhEo#P<6QgSDjR4U16jJd8I!1eG!!D}r zcr%|`$d|?T)s>5^yehFtdzlpFtr#?ZxI82fFH_ zzH&X<&8GzjnJh!oxy3p)DY7T~zLjK=@R=4=cIZWJPJzf>Rh_chF>1S*+NwQKM{itJm@-W^~n8=5>`=mU$Ic z-nqwzI+bbFT$Z|g-fh0AN~td^apBMxNx5NfWx9r?esYK6m-7Rx?>G#4vPst*lwbR} zIo=(YXFi!;y8oUKnWq(@-kh;Abx862-rEXy9+`yMLRJN!PN)a!Lzz(9Rcf99pY;Bl zAMW?`wY*5wpsfJhXFQhCasKIDmYuR+GWsNa1)d1O%q;|XEPKoYo%Ni0^-MZ!O>_fJ z(ZW9ZCJZ|EBz0hv-+f5#zkEE)L74x5h)=QNd*79NMg z?)rV=ai@cg9$OT6oN^(H-Wh@<86 zrCp_d>-zY5UW@dLdE1AI&k`kE%nnhJJku0EKniy^%maslM%lv8+!1zvW9v5)1j9kY z9^EqREf{&rweBHsAG5tCXInW&1QtnC#dbkBcCE{ZsvGeph1e}^p>Ls{QjJ4(B$1?6 zCl4O}uN@bze?z`F!XIw1m?>f8)QQncwaiD$w|=Wv3=M*77pUOHRgyiLXp%c)zNx)~ zru#+kt%Gf5xk3GMFK(!)e@#*{)lJgHeo0qLBDQLUTL{ExupXvmaXX|l^C@8;j;OX8 zzron&b(Ul;?BR(%*D3vmzipy8bo#~(-N9R`-{oiY@r0OesL>zf<<}7;oxcXG-&?C3 zZ9aGE`+hfV70gO^Y=zV4M5;7=N;g6iva{wVoHKjM8X3%ekng?Bhw13o1@ESoEtf5{ z-+eCpc8o*2YpcJejI%VZE&tVT4_dLnvc~Irj7j`Hp zrFEn&rA@5*#S`|3uki10W*wd&WA9FJW^|;zp1x;f^OdEM&gRhiwewO7^)L&Ijku&Y z(!QH0kHg+J;zI@InI~v2W~9upGo|-ixQ8}N3w-r|_Lq`a>mt}4Hrm;Jx(fW$yF)b& z+=FJ4ZlE64Wi8r1dZYf~MRZ@;hr>);5F=PQMVech)%%U=*aDsv>=)wVmr9%;X^IK8 zPRrD;iaTl~bg-t%ttX`R3xj6eZURRcKE72NUWJ}EKH8c0P_!l{Wu=p)j_zxUrbB_F zIk&s>jFI^oJ_I>Q8&70L5IYe=*84QSlsTkj1b-YGdB&-ajMRIsFWzZyuX|h>?J6QRd0g+p$6DIW31xXuQk-Fh z`@a@=ocl8E?O&}!-JSx`Ssb14MJTGbQGSMay<8mM#uRXnM~m6U!BL?$`_#bF+rF5v z6q}joE6<8;V#8wBG!pLweZCg^MO=>CeI$Od`d)PGTH02LiQ}6ZPBEcZq2sEwsFSwo zQe6(jEeondH1DM3o(IP&M(C6>?U9^wV&j_6`3i&gXA3$!^$XzOV5>7^b{>IVT43X| zdXxgYKHUdZ0Hc&0RzucoQAwr7*f;dt_w1>yK+N}fL8ePQ)7TGDW?IUyt~F4 z>=QTkI$CopWF|LX7`=4G651+9ECp|QL>Vhd$x zx98$m^*@vOJh~Y2uhxZR4Lnfa#%xQpE!b9 zBh6YsEspkyQU0QExCT^Lb7G&Bt1-w~bLMPiPP^_)zgMayc!oQYr?6H_kjk#=pnhrG zjikhHCsB9zE}=GxPEXUWu8?CbAm+zL;=Nb#xrHGUgH>;fo^6K8t-M*yP_H^AA)0^C z;j#^@$Cuof20l?EJ&BKNIIk;srgUTo7KBeDPaM|06+H5W+FKq8(Yq!#Udb*DGVs7k zIP!bf@(xm&_%))fat1Ho?-4uId%+_0q;#n4gRhuyx4UgBEQ>e%UQaEnc{+~O9jgkT z>zW9tk*8Bbe((}#TG=|dPhn}qbhCG~Yhpo`+n*w8bv@IiO5xl_Y__I};s=+zm!4#2 z(HZa@ayq;M)`Tf96!gZ*rP(ryW#Fso&8r2FexA^U%ASzO>X``M0$x)AqB(yy`7fW<<7H;vlB*e4C)duCKDHiJcyua; z?To#_0B>ad%)MHPM=N_r{%Xd@d!H~xXI1LIVP#Rf9a=$)FQnb(kLxCX z7#7M@s*B{G%VN4g649RHgtqo+>p%xI-^q5VG$+PZ2XZ@!hneBzLG%no0ZInkI<4BXd`CJw9{=az1VqHf72A6ehzn#?}+dI zd#Rm4Fz8M$`9y1SUQ>^aa>^0_;&L^Je~K}EBDubrhO}Jd&B(M;@bq8Ot2Ax)#-uzA zC7_^`I~Hrag6)^Q1VWegL=Q`d+Jz}zl^kUBdm8PTt4}Hy=m>Z$ExDht9(OMy{@KVO zntpGEdV$mNsAAbpcmL|quPIlS9}?V#;-Yi4;xn0N-sPQS+3H9hXt^)5>L0`lX6MpbF4S7>_^^O37TUI$Y8%30FGHzM_*>d@yL`fcTz6Z3w`_aoyLnhRLZ9{ z>>_MrWsbcD-{BusKo}OQgX1x+-BBg_czC)?0atYFcVi_ z6x+)_iMeL|y?a29L^e^Pw!dy_1$VXut?&A6Mq`(X7wT}xUz$rds&?Qo6bw1Fzk4P=1?I?iDYvB*{rZDhGEM0CdH&I; zyHFfzYUNG3gh|#*gK%pNJLZM_=ZUBjD+0$*wj$<+gm2LeS(Z*qhvtSt+P~UrU5a}? zlA2*anD_2sE=9}MYmD_j2*1f-GA{5ofdMgBFGcq6#glgH-|Xln8NI%2W4C6rcJIQ~ zg!5e4Cn{XnTC6&Y$CBR^--wf7#*Ui^*XBVvt@}E%S8QbUz0lNNR?R4 zf74J#8n3{+J7ZlRg+0WawjO$jnm>HhBU_n9jTAPmEYfVvbg?Nz`sL%7Z{jq}N<5d8 zl38Yayidp{>`T#6qf3I>!&XwS>AC0jJUe)4;}X4cjjGQ59%|Rda^;hPyHX#@d~#A` zfuu0YbxW1|$6_0=DAPcTnPw7u`w;tRU*u1lq4W1DKHBh!Po>Fs%P``ryjShAm6KV& zRBoQRdlb#8bD|i zdR{z7b$HJ=)K^bcfc?_d87hP4x+WKvMl_ZeDW+lvHj}!%@R4VSVpWztkAT(tnf`Gd ze;7aJ6N)Bi#)9gxg3MaP@NRtbbuNRN>Tmo**H@Qm)@N3SK{>IuJmqiLt-W|CydyN# zbJ*G=KkQO#_&CF^Yz`ephfjy&YLW<1SkIKH<$dRb8y)SJv)_I)s$eiDBrpaoNr@#l zGvc@;#zjJjE=TbJPt~tSsUZy~HaeXR*#kxt#7o{xo+Q1M@_g6ZlxAPyDTnY$V3-4y zBjSdLm6uwUjzmv%RvcvfEMX@;f7|$JX}rV|`DVgiFNC3yp{uh>iw5(jIu7L~ z6U5U`B;n3b^CgU=%k<`csN%W#O1)FZT9dwNRL3X2P)UECLAc>~S&H!t#Od#!0~0;! zC|#=*g(@ywktcikdP`+sBynmstY`J3PrOO`E98P9qhDORu3nRKq4dkT*B!4T5A5Yg zl6=FkE5_Hczl^gGw_kJEBeJ+<)@d|q?~|IG7m=>c9~3njIi%qkL+EvOQREB&^9nb$ zIdfeWZqS_R?B+IE6nIZn1j!r>G26HwVSPtv&(I^Chf`1FYe%kws?fe}*GES}*EFu4 zx#`0K5AT|J;tq<%6DzSN7ph$9hLxR9eJt%U;0-E^4SYMZuu|>LX3sY?|D1Zy-1n_5 zH2G+;5&L<+a|IsOZg-w5E*v*ws2F6}MQM+!UZgH^Uh^lA{Wj~*+=7;6Ev~MbDTk&0 z4SU05M0F;w=`QKvm38!Zl=k=7GOswljq&%#h@L5$&SFEwC3?rq%tVfcmEME=q(=#i zv0UlCYb%k5gFhFJ0P-%LJ_k;7QkX))mIK&gPwUUkG>T&s(~Mo?*4DUQ+`aXbkDlKY zGb*^-M1F)7KIvRZjEBhQd8o)G8llD%%`vyxeK1EdSNLZX$EA*g_iom%v24EOTk2K0y$t2DbmU^hK-DtVoQxL- z9C>M+VsUwn!yK!9H`;CB1^fALRYojRH>kIyw)Gn1c{~ne19SinU$DI1PTFqLdHEmP|{rE4sm^_WT-9?fz zBwdTOuF1Gx3iVoETQ?}-cqvac73WEtz_^wrCe5SJs8!hhRMYSqH`cMlqV|Ym6SPt6 zVBmC>CTMEn9J@l;S`^m7Q=r$Z>PeN+nTh#u+6j4Nh?O~@GM1nmU;zM7l9oXz^Y8Pu z+BGkQi1ieG8_sww%da+5cs>J4(<>H#nfkJ+V)PMoLpj-LH*skp`IW`=~SUV&n^-xFExk)y8`spB)JTWeE$ zF6DQKr#3!?I(WS%*Z4Ljn!Me{W8bJTJruj$W*awg`F-SYe&L;U*(WDB%1RKmDel;Z zWB*VL=wJm$wEg!^jU+?x z^V^P$6jkSzI^s_WYY6U z3l#JzFw}G+#VI4z`fK%oTFV!f@C&=cA-eif!djV&gN?$JJLYKLm&i+dB2pP7KPBvy z$XrvM*OItE?|C}xAU=^-dDL*Sf8tUfK@^Lp#MolFAJ_i1hwiVt*z zFvc?(4p&%Itb#9drN{jLbKB3TaO#r7kB` zK(5cd<2CRl0%po3&lpQgF57lPod(G{`&c;HS#v z-bnIUfM)v9Te~}Hwi5H>7~f|Xgw>ao1%572J$cVnEUv3@eY}{*@nV}y*%|u1SL(%d zzrM7-m2B^N_8TLp5@K`^vpnYKV(@{S5Qk0n#olkD&mYZ<;jc0kktV4UTcjV`NL?y; z-xYjY_0S-9!42f2ddGfPz(GPA6?I;w;1mmvwi_Yf0 zv^YYO8e7N^hcaxUfsEbWfIscE&5oAIG(crL9BcENFA069d>*QOpE0oirnP=N6ls#$ zkl)c_K6ij&{rN2WjDEB+);y^@B_wDr#EpOHW?XXIXwx3lev7H}DLd0t)5n4Sfn28O zVkouc9nr*4A>&m+$+|=aG#kB0GCR-;n=njgA+~dn#6VmnEl6z$4p^ z9`{If&(VWobugC=UB2NoiL8Owr2@s?CuvQ?`*a>yFy8By|DZCk<+XACsfpPmm%`oe zB7);8l}2Q0@)|={*(58?A-mKxdwuien@oR|dI7?7u3mW=W!+2fb32Ze9f(jNGd*KT zVsIG@_2|4M;qS~b?0USz_65IIda^hOt#w3@uv$~RMIJtmt2MS&-CsM-;XTBZZ#9VG z>Ba=DVJ8)&{J7#PZh;W9RaEy**ew@+8;4Ha+K)=MeL~7NN?? z0{yd!WWb+3j!{mT9JLg3;ZD?SbJ07OEb)R1fwgPx2lGykK;G<>Q1A1Ie272`?uTMk zS}T_Y0%qsTf@Lnwg}=D9=lwz33%f%TSQ<@&+6Xn_8>hn9(!YJ}4mar(C&hV;qzZVN2$O87 zN0wrWGPW!UGuBzpy!Tkzp67SHzxTS{Kc?%NaXRO|zxQ|jd_UilP%czeBJ}v1C7PgF zM0jSofzUB1lC&WOwMf+VSdu1WW1hU(P?WIzz0^Y4Oz_o2a^_>6>`3U8R7~oyM2Y?U zhj74P9-+Jgo7b}I3QGMB-B91Qiy#S!^(8wpvTMka`u=x#Q(THflVe_D~Rdg&8-2_t?@gTa^1o4%4s6Acv%;66c zH|wg(Q|;Cp3X7NuLFia$LSZb{e@hC%XBQTBs3pooEDx%vh}ameZ9?`8^$|O#W+Ztse`SY{WxAppEwza?!YxS=B^VlUE-%Lim-FwX;^Ugw}=+j_l%T0x4 z)swr)ojze^JSZ)X2TgB66U7oWB*h`JVV3k~FS=cDmd*assjbHj(61RvNT#M64dRb9 z5Uzwu!f4-jYFo!MN&>qbE@+WN8Hd_=kn!^^@3%EoJ42_lWV(BWpG%^?`xP+hJV^9n z7mh1LvfNR)XsuU3lHPh%g@Xs*A4xMuD^2@W9R9CZ9M}33GNs z5oc`hL_F;|f0GR^JZR)R4`L+GUG>Yu#7)9HX!TugaTE3O1Ff$2@0Coxe-X3&JPIWf zzx~VyhfjV#HJPu`1o*gQlEZD3Qj+K{lvEzZ&JqO+_gPh8nAiy-i-BX!&lPz=jm3`Q z25}QKczltw6eMYXHzD(zBa*I^c;}T{(JejYc>)2^Rw_{q4!%4{BoG{k02X0`4aKHm zHj#=WARbh!e-SBjLk^W40w<&gbUSJjO!Wb}y?0!=g1oq&D9+iEDof9K`F;YHyl%cr zLUiF(h5K)W6u9o_{)lmhn=lX&xZdap5RuBUexeN5Qv!L)#zZ$BE)R`{K zV)s<(JWFmshm+y3ve?VpPv&)h6@ILVDm4jC;;h$*!H!E?vAe90RS67dyacX)Jzw95 zK;goC>3a|hr2-y=S7nNfv8FIf8KPCG8|W*6R5@D@Q9}GVcb$S06OGelUW*wi>c#f* zpw7QIe2cdPy3apaJ8;1>s}mPf3jX~uJZ)LNO z*^9_p#7h!BupAnJOuY~w)crq%s{Rk(ljwl^=EGB!U~K`qnDhDGOFk)@89$faAiOaN zO1N?0$`};dAoNwz#!!&|k$TIxSnVoL%iLbf?P|R_dic$gFNLHpLPeyX`{Zvk#EW3F zf4On#U>^@^-HhybYsEsWxTrzkv}Vq(c2;pH@f=V3$ln~B&d^YC5$4HbmIYU?sf($Z z$uc8{O`xzs0@ZiBlZ{D$BPLScr7KkVo_m0}FXStG>W#s{XtRIJav-04hr7ViQ}~u{ z7z5O}8>sQUIdj19pzs9 z?&h;L3Y6_Pi?l=a`q^L4x${j3MN)JM3QO=V zUPEwkdO+yOTuAu*evxvTK-p3T2wf{B@T|!*OgZ-5GO3yG-qYV9`fTf1RxP@Ss%^1ocDCew$q=1helI1}w zj%IUVRU~pkU|f*!)>jrLz9KVKlQp=Zm|o8Y^?F1KIH_%oW?Vh+ot%c*zyL?DG6+`W zj~m~JCnmCl=}Cxr%YEb<-0z7eUV{Qjg*Z1Na}n@4q)+g;Bt0)dLvyR?1pW#mn{szI!=H{;!c)>>vIygbX6WC?TRviLU|BJ6Ca1` zYpFpPFtYwd3c5VRt=dH=sI9WeQn2SzgJi+-!)?+~=NW~udacoI;f*v5V*cF>Nj-)f zirfTuri1~Z;~c0{tuP&yB6MLQQx`YoTYj-<@1@^)7Hw{~Iep%PtD8stNLj&kzOI{i z&=sq_V9(~{akL>eId%LHp*2Yp)$cl=k&wfX(7V&LJle?5ntc0_Po7S-Ob8{I2Z>8} zTQSF<02=|R%taEaZsqR_2NB?6CL*T+R};qu4+Bfa{SVtKHS091+6LF?eyL7*Q0}eZ zV|Y|Z>VWTaF@-1H$&9kHzqu(G&fcUwfyGz1vYYRjLJ>u*fHdAn@1{Ib{Def~D7>SP{t&X0!YaH%u#QmO+d2>0CaAWi~{ zG0_TV$3ip=!_7*N2i1R>;!YAdCL!d%^;NSOo6TILcLl4Qbs@r=E<3F~9RjJ}s{#g* zK%!dSbuTQwv`ltgPEVq--`d!n!fml1bG@t422#!B!FMnhY7hqMPiu7DJTU2mWp@G{ zDaH&*b#H*Xo8C@`){7)4OLbM`Ha3v&oOnSPu4=%!ljN50Af@a0sR|GT{5vGm;Eo1i zGHGZ@_onjQIq?gSZvO9+N=c`ulKl%l-}s`rTKVwZ{YE;Zw+w{EHHk=ZSC(6O4%gR; zAJdp^mCG=P7RkOxQvYXpX~b%+{=o!EevCH`q=qYqazk608a^TX_XrEDs z8rNo4U^@JIaRpME0n$A|D5#LLIXjvM^$oKTwiU7>;rH_n z+w&kF-1s3%UtPqRFp#QU^?3+CC$ctrj`c>0u)QaN(Or?lvp7j06LVx-EoxdgL)94n6%QKjjfF8g@U``bK-g-XK! z^X$whamRg~butNm_#uJa|ARgMs&AA?8$%@?hz!%tU}DZ0@XdbiG8`c)QED6TVx@YW zR#o_gL4B1=JRBcwq1Q;g*d?~6yimyI-9CC<*ily=#MT8drq7=TxmtlB19GbDWXD~$ z4Fsdm!5}+7oLWOCIm9U!hgL{Q8zjXOUMF zT;Hocgo|T14D?IpqnM!vga=MLjuqvns!<;5pGb>PvG)(>(n*Nl$)c`EoE5In&x#3j{uvJ<-$%6SgGdfd zxnUdcx3#q!sa$n@^Wfr&yp3y4LBcAOK1IJWad2XJd|iAFk8d;tKKY|=ABW)#QZ(>X z5&U;WZzOCzJn|kS0THP-$q=Wg=t=3*H5$vViht3sRKFeKCiPcYIyVdM9x>rTxxPF| zlazE1hH`AuJ&zpO_xUnB0M7C3F|#SkH}wg*3Pj(F2Mu0@mm6Yl5Pgn()bfc#H=DVZwVjo?{|WhS>X0c1zMP zr1@WwzYM=InismrmO|vL#nHVrLBfUohmvM3oE+v7?U@kMdV9<3rk!tY>=nGgtT9yY z=uGzv{|-(W_&9LNw%A;d87yIoIstznN@R}$JH+F|M2iZW^yOFP=B(NO&&i&H3g4Bz zvK)lP&%{o(GLk0G;^@x6MYw{*zTe6a#vX>f-RBla;CR&L^f&3m>$L?O(}67hz8Z>3 z=Df_k_zF9!HGrF73L}>!QLMv2cEWG4`GqX5Joghhnex_uLUm{zLN?_=0p7g@qhBB? zmQ99d$Qt)&YbY(9XG;vvtPz-RG=jec{^LO3RhoImGY$c zNlh+_^!CjZ5Bfww%=E~tQJ5>Ph90s|<7b!|1odw!@X)V$lxLU4pZ*M*I^*1Tkq7nI z%?3e=spoAv%!y4d+vlH)};!Lzo|r^lm|`v0gHP#h?D>o zGPZ!&wjepf)F)iR6CRy9Oi+9~lc5n{*KJ95dr^0jvX7&bVTC55g~b#e;>a3k(?&hX zt?EWmpX0BAjnSJGJZQ;q1lHVhM5{b%hkZi0)IcB$N?E^ zE7cwOhX+}VmZZGkkZzqE>XWT;mV;QC?NNAj9#mloe*opiDjeHIm7_`W&4A&a8Xk1e zaA8Cfkv^3f$W%o<@EQ)y24);hRhB6Xoa)nvT`*}(f7q8JQ%w!QbtdD!X@l^RW=E6(d z`r{DmZYW(d{=)Cn+v;M6 zm0Kcldaznm_#Tj;H1Z%DGCz!d?+59NM-8rR`W!zs2p5E{ofkG&h?EMe#J;5Ef~U`? z#yrZ`(5`1I9Kvmu4?K|SLr8d6j53tCqnU4wHhK;6RA8i7y7LZr;2n;;U~uRG9eW-x zOzza9IP60j*{eRNDfuq>`T_DC0Vw~oK4Kr9n50)iE0v_zwV|5?E=G9Ev=Xl%) z(J7dCG`ff_bK%GjHh8vNLuU8)cQ(C=@$|6>KvpCq^Lrpsx%sWa|UZ9jC#KumqN!1BI6VrV3h($w?<2QPR8l*&P2P zHA2nH=EMi$CqRr~ZB&2hNhP~kF%^c%gTO70AF>v1%?Fo7giWVS2@he)4j$C#>FVZs z6Rgj|AcqIJEJ6ky%|IJ|0}`FZH{W#@e8dP;yx#|Ska7xz8;nEF zC3Z-S@StB#Tn-%Ywg2S-ukl7%=2`n=QLup^<4TwJqSl~3`)36oax*TdF1K0bZ@obW zQV{9Fb!yN-MNh(waERSbuq3VrZh;3ofJjz62XKb>*^sh_xs2Wl%a+{UvizifsDhPo zDqhdFD6gqJ6TKOK6o5&~JFQsrK7nNJJor+!g0ULFFRDPHpHW$>AC9o}p*~#aLRvOg z-~_;oE8$MZN&BUaegbV)(Rx8qsz)pqoesGzCQ<~{Tk?sCzL(e=MTLcLDwdu3u<*De zq49|d{O)m+W~thS_v@AyEIu4~E%w%v_j-nP?u{mpqGyW1BHF{nby@qb2CKeK;HYP0 zL~gBT?9c~`Fua9h)3FSPzr!LL@?A74mil@;vv$&RfgIJE7sxwZic}wXz#N>s1VZIs zZ;`SZB+?z-Eys5$Qw)zzqJ(P@W@l0HdoTFtb<0yO?UKh16KYkCP(b!MKjtj1e~B9p zide!?7wrTfQJpkW8Z)NVFNx~HN^`b$vN18Oan12WuX(j!y@I+-4&IbhShyK4PN7o& zurNcx|C_?2g`a&O+ze5ML1g;*WMz;!_xGn^5@2^PWy_{qzo@&86~|S~3xn0}zYz3E zNjp|{l5`LD@Mf?Z;m<^7R#>T_px^4QdP&o0oe8I^y%3~&84@91J^zp z#-(KfC+%E*AMfm3z4lAArLvB&Q;2W0mDbqaIW5hiaD&LmrMdJX+F&K)R%JkhUdy^1 zE%kdD@f~M(+-$Zxa0=3qdIdBC-$iDLy24Z@uDY;n4P0y7LLLNJ@p4z#P*Iv75^O?1 z9!tQ*7!k(9%J)IHEYEq_4mA#U+S}iJz3lmG*eWeP$OHyVM@ zx&>zr=%(+v-)ek%k6zfv=E=TXZFKBA9`v{IE8_TqJW!%60*Q&ApSAGTKLyCZIMA&w zcujb|4bswk-W?yTEG2d9)Po0=5Tp;OkB3ouB|tW{{4MadOb{oHHBo`cV#4H8tZoLl zKe-AXUs?k0KMKcTX-Nzc5=|jaAy)Lk8D>stNYk8N!`D?HpY z?NmaJpsG9@4Fw6bmnR6%cTMMvYx5>TT~G&wAB0JQus{G_t<9S&**wJ!L(u?M{}&LF z7kPY-19cpd8zoC|oM`oD3IZDaj}&<{FWr8~@NLXM?LpUjo}JzaXRr&i`Vg0N#58hS z4I(G~vI5x;ieDi zVG4zZL^}={E^-~?LAxk?*v==`Bv~a8Ng?k!2Ncpp#o!@(u=zvKmXuX8{fJm@zrQRT5SzkDx!!iBgSw5 z2nh-3G5{%+9KdEpJ5HwV9pSR@$a(-@c%$KZ^?LR=nd1^>*?xx76lst}KcYeZG&HO) zf1Y9~Mu5XfQ%7!|aya>g$<7At-wJ(39}@Bjrnc~rs$y<359+2k0^wnLn#Y+6A=bi` z*^{C4H__Ej>v01QVY*hAL%@iK|GGOjzKG^~#oyd>CYEbTkeBcG9FxayZ5d(1cYw*H zCquO})rns*SgQ2yodOY3bjrFGv#KUNCYlRk)_7=roxUrVE2vW#{7CG0H{{z3OZsI}h05hcY zOHFs##3s6l%s~*IU?(1Qp0t?`|Lu@^43Q*&0XULP1g6FglJ9!ODo<-wIFz`HU;Wiwtpc8xcAFP+ zt*Wu2`eq$*T3Wjg4z;dcxd^lR?Ut?u{dPW85z*bfbwSvn#fG$_BhNOfj6`TIThNbdY9q$2iWpPIsT1v*}*sX@3621^wr_=TyUV8Z$^ zX<;)cFiKN3DSe0h!(vQn8gqmsWjDssw!{^TWhL!ROD9 z?p?pX{MES3JQ0Y~oqMaiBLb#c@o;^>&DxdHRde+jSk%d8SJLo14_Io~i)hj~{xvz5Y$tBJm zYvEr1l@ZZD7e?U4%BqF(6OJ!42?w=VeDm(7jo+8Q*X*y{(y;z@!{Y4;f1N(gg@W;f zj?U$T`7s)T!17){B7qa8V zwU`%3@axk&2q`-T++!Vd)Pan=BaZ62%v4sDBq-+#jIKNlIiI%rEc_If>_TNZ9CK7bW%=ojzJL7KYN^h1ZZica9 zh)pXZUBJ2HnM#MiQgnbXR?T(dQ#5{F<2poZGj1wd)sN}IDi~_XkEf);i;L8DxT8e> zUZbNjnTClU{E^U1yOeOSJdr@a91u7>r-7kXeVV%7{yzbJ6h6Bc!-@Qk8|3#e9D1c9mZbLMeilN8a?K$A5yGO(VgG~3tJ%sG*PNe% zbAFs;*?ee;(_fIEO5{t0cvFmxvj%5174+&CSh0t#kTsDwgL{*t25-h5GHBT^!E&cm zd61OZ>@^7pn=sA1s83cOmq#Q=0RLfL4LAY*0oeSx%X=_-tT&bm4&|&K;3`CI1X;jo zm~IEp+9SYWMv8EZ2M~vuK{g_T4nGih+AetNGm*0wOUsS`y?Ov7%`VIR|9w@{dhzdN z{r_8)feAwo=q=V9pw|gJD5I5ZGdLYARw{9#42-=dtY%Zehe#w*s&yQCz#sDTm; zm{iMA*rccw)G|WTMNQb$vmWj|&M^S{t|D_M%sA`tG30R>5dX^Vp&RnRpUIt`VcUNG z+54A2`~LA~unJL!&JnKAIw05w(73L8YD1V!T2SKAaFJ2HCny1{wS68ZZ*m@{r_Iy1 zUM-I%7_~kZW@L59e7Z7m@s!< zvdS-3W&L+El-&{Ht4JMPjH9DC6n>Dc$%ATg3lI*r_Te{W{Q93r$Z^*B1~hh9yMfZX z>PO)LQbc1dRsiUV{pots#2!pT%K&umPvTPviUE}X=5q%~VvJC_ba{wF^mq==rg{0f zuK5DB@=A6+G|wkkDFf_j_}R?iXVD3Bgn~n4oaYc|ng$hClx?Er^u>g23{{*=_mec9 zXSVk1z9muPQe)DbHKg;naiQlx@!Zk%b>VsJZSd>3^&lJF4J_Wsf=Pt=62Y+0&JlaZ zHC_$5?P3IX)--TDbO(}hPL0h!)_Lg!BtEVJpkQ^)VRzqF%6frz zXEP74hlZp7lrJ*s$x}c9R?`R{Pyx8iwchYK(0)?224288Q<>)xm%iUTu;)>R)mpcc zJF7kW;1R}NcwwY6Td5mireX9+@4pBmhS#wZL()j3dWRKbFMc81kMRMmcnY;uf0NOj zPvvV8gF52&Dg7fUpoTWnr$i!+VT?@_gbcQ17&7(QH?2_i9hH)dw5F7OSID{cAQrc1~s?}8{O3yya zS=||m!33Q{)b-dpfBTj%uTYk#E+#S@sTjC_+M29|(;8jFft@=2uv06BBl+i_)0>W! zOd(52bF#A|(+bK!U~M=AQxR4g#;Eqrs*-Vz9U~0xoxY>pBz1|tXt0k|&a)i!TlIM+ z!q<=Kpy`^Zb`eLTc#mni3QAm3!2zXR$%9bk>g8KVCrR>B2dtKziB?0CYcsX_&H#G{ zyW4mXW`9fhZE?`JLK7twBq}Y81))F>_*W1J%**Q(wr1sO>|&hl_;QT3PJ7pf&eX0$ z+fB2t8rF|WwGwPZmJ=3^sU=-LjS?h~uBaqE--8l!3_tvvg{)x)PI`bm%f*w+z!4li z*g>^IHoz3tlj>Q1JZPes2i+un*l*AsE*{|wvXYwbvp=SZ6pZ7(gUV6a1@s7lv0NEK zJzh8IydFwz8z?@p<3pHHt*PB%>*!O6g#vYANO)8e)m6~M;Mbh$vo)vh-rv82lF}xL zCWq2Vs*^`a!ZY}-zwb1-T(>EkHpd z+`+}Hn%}l&TIRw{hJO@a6N}P>%#oaFvmaqf;QyU*{ho5op`LDPm^mCTMT@RZ8vk1eLQNR78VaJ|n*c}p@MN^^qbrA;s~rMRM{wJsz7dC=wJ zt%oE#+$6TAjyEdi%-$4qTCEK1_k;cKhLZsk1IQSLE)=2!c1Dq)W7)zEvZ~& zBz`@exN)fYy3JSgvex}KezIiTrdcvTUn?CVe}W{s(f! zcF?D=WR66s1@6;S`PBKs(=M*(ZU`^aJ-n{ArWP-wI^9$A#J~(XNHucJIcK3D&>_h_ zgX{Y?Lj!q%6%1S3pf7rF_q~D5&gLqO!Mj!+RhAX6mh`O(IW=>{`cAG4`A+=8L22FQ zuXU|;e(cZ6%dCCkH=TRRr-;l7<1@@L2?w;VAdm(kq71ibrWn!3k53hYpKg+3Uc3Kp zp;AmsX*3AcBvm^63L_Czzu(JyPipVhn@cyXI4b&ghrccCI1xK5Lfni9CjlMFzLcTb z35z;fc3FyEEPVdn`g7&eO_CZ5Kck_@ZsknL$v6)ml63N`Gpi* zlGjxeS73#9zs|azs9bK)zhq- z;wr_`yH#Tubq0LeRjT{16^hd8X&zNQlU>GxB+I&iG}NYP*aB`R@Jm zHTV0&J%Qu=xh=r@bqQbaJN*(hGHq$cW=#!58i3(YWrJ#L>CYY&m)%#edGloLzVi#8 z+wS~krB7K2S{$Lf%aV3|ST`X9d92IgQ7E8JPmS8JCz-6^^j{prxladf=;_=+-RZaTRqdtNneICsm4o zn5820S=Q`SP;berQuZ9FLZt)`0Kt_|2x-O>@(FPXKpL5d>`z4`Xg00I2KAjQ6A8k< zVtpxlanL7VtZlLN&ewK5Qhg#A*l|*q5XDj^D7?iIHjJ(Q7rw=zyu=FPdhQIslWo%^ zOT-;|#GG*1)MB;f+cz0!Up^&^wZ!3JXF>w`2j-QA$>%}8ad__Mcq}fiQ5<^lLP2nG zx=QLxH2#LMLT@n<4Zr@Hc>sPp4*KqC81JS?U(21_2HeqK6IwUDVoeU3Z3!6tM`~65 z^|Mc|rkoNFYvi8Eh3n7ObD1!W^#0o}kix7DSz1`?d+_vD>}V~>{m7=@3rc0zcG{N} ziYZ$gC2pFJZZA}+zfLvACnfcW4#tDT1MYQ4kJqpR3FT>W@V_ZixWarZpn17Ns zVxH4&@JUrePi&ISe16GN^kL>{*}sffi}pC6XeC63AzxRqtVOv<QwL4?n=F9XEC_tYVH2!jAZ&d4{=zWH(a2SC)~yvhJ1 zx8Jx>TJIQne2&{@z;Sb(;kaFxVZe}suz<5Z)C`>8Au|O@F$>o#A>bZqne%lJJ(yb==bmsik-afiO8!Ug-c4&9XidK8!8{zH+g;RA#4Sf?+5xAC0SxW@%#CanmZn+77q3a)qV zg0Cq7h+mW~?EI7AWj48VRuTi#lJMPWC5d8QN?!U*;n=G_*;zH(k?-PZcG2u{swD`6QcnL0=;;yScC9@+2kDa4)hkY3jrsqR?I#L z-tf=MBaco#@)?Zu9Q{`M3n>bi?=&gujLGr=L)oW;y1N!@?rArYzh7>jXoR)iy_p@l zV+01^Hca4nc3@+}INMvE;=b~mmCg&Qh1Ad^*o(zwiM}-Uo^$QP3tSR6++UH0z3m54 z%Jcm)BE|Ny!!>MQzzKEv&ytV4(>9aeP=D9(ftjpKZ`ns~>IKl7Q?h9rdJ6ZiHpD6d zDH4B35o^+N*jVMdgUEwT<`-Tt#~0#=%6yR*lKsp%;&9%^aKW@Yhxz^-B=}+uKixY&-=A~uH*oH4 zqJz}$_$TFS*t1>+y5<(*b5$@r&IFRps@p=i?OO%8)jjgCAI;a6K4m)C z>}p~U^gx>(3aS9WAY%h=1YS7oPz`6TmlJntXrA=cL66%WFKt{P|~yEeeeuHY!eRRux%a6_Gtm$4kK^LG^lg-*RG zD0KS4jEQrOzbn9DfBs;I&Egl6e0)q`>&7Q}{hp(vY~0D`23K)oTCDTkr_{fxf4!LS zsVunfs`t=Y`H#=^37`AeA5f!DG#x$=X?1%*ls^iO)Pj8G9*D!oiO3ms;4-WLT5O1g z*ELZ-z_dEw1m-0Ymx<%Uxpltd$R|8|6b69r6!t6+8eQfND!yUdxU$Pc*kht}H{*0> zIpzQ*#++Mz+x#&uY^b~j?-z8@(G z*O|2pH#WOWycxq|AFf}FI>mqi5(5Twumd3)eCQnTn+KZ>R&h+N&TrmtasAl)?-!Sq zhqo18`L27YB=^6I41Ql}D{Z!lpfnWp;TG$QAlsUjzhB(%c;nbupwySt`PG}YxueJ4 z#Ih0)*#|I7Y)0xDoXyUF#`*JL)-}2a$7&jPk{7!6@7B2OwQ}PKTK=(aR}l-Eu!<&* z+WL{%z(HmtgH(_dIo+8{O4CD$qYfv17CFF=>cU*q zErbVQ_|t;F%5Gv_Wblb?aS?|HKOt>oknxdjlE*jWEenTK(W~*_kM+Ry2f?i=Zz)qa zud?SQqJ{N|JvDc`{=zvz*{*!fJ^XqAG*n$dT*+JvNVKAzHSy9f9X}VpXp>J@5tn$` z=5u||?G6@zTH}Pu05EVa@eC3vG3B!*Y#EwZk)|WxVUEyMAjj`&NENPvIxH(4loJ65gnX0BJ|gH zfS><;B7tF)#fK+w<5IcGGuLZOn@}iR0h0s^qcm4FruMijy_wZ6?cEL?mNmGB>X*s& zt?EWr?{-2;`oKNK{rqn}+NbDX{^C9QgtoQ$((i8aA98($00p1?(+1|$!i=-o4s^O? zXN{A2GIj!U0V_`J9Q%~nE=n$l(O*^f!e=<#^DWG30Q8wDU{})&bQ5s4BAizDB^De5LMl%=rjy zE0lZV@sZ;v|IRxfAk=p0{?>PxEGRHPfNuhKI`nzaVQnsg{ad8rv^QH5>S#c7NRxMhsQn+Ln5a3POnPuQ3Ef5@EO} z6L8=Gc02Cm(7!Egq@zBH5VWWASqS^Mr%u5!gMF8BPiKYnuqcfLpKDTAO~jH8i=mdo z1kIEIiBM~F|D8-pDBt!_EHp}v!%sWbqtl7B0Euj%<%URAXAiK6T)6_;vg+X8NO}H% zf{Q~G+~eCw9%S)xy(F0t7po|8&*{b|0Q+j-Cy+P`wL7v|V5VoaS{1ZzrA$U7CSWbX9rvq?EQZ$e@ zxc@GRJlKJaCXd=`)=zt<)G{u(Gaui@(jN1TWttVB-U%lJgOuj%7AwvRV@@0tL@Jdm zIoy6Va%rrEPqj{(Q1a@C{A*A@0`n1x%zbc89GQFCsz-gacbXVka?915RQ#0 zf70oT7!nvoa=;bkPO~GwQ(oc(pEIu94dg*i z)Bcp3CP+GGfP-rd2?$);3AXJy9N`IyJnE>m3fMvrJT3>(b=9(DYDyu=8(E6(I(5)W znao(5OJj!6ovgHOdfL3%0Ri;5Rj1nzaw4_sIp!=pYV~7M1H4l~4zgG+{MBshWSX#z zdF)0*QIl1xK?#UywTobz^5B_~h(1@(ZyK#uICr2w)3anH8-z=tE&pP|ZUedJG!u4~ zQS12<=U$Q^1biiB6m(qhhJhp^pxM+jm#o7LGJzqdVJ`B~ChkH8aBwPYGq62>k}*kt z?kmjyzP$-~v|jH@s3N4WdLwG~!2B&8-x~16LH=gr^U^zUcuxI7nX7wn1V0 zEloXw>cJ4n0$Lzq{*Hij12d~8?Ivwt=n)wXx>kG`AXBbXk8dlsNi=tqGT~vUlGZ$u z5Q7KZKdy^EcjE5*lSiKgyby6zRonToW?r-oDP#n%9|0sgx@I3t=>WN<7ntY-IQu@v{GIG+##xB~2cK5Z#|5PV$1^1(Qdfgv$#DQlq>*Pmn+K^f!0Hyj zv0$_7fZf2rpUCxr$6Q1fgCq_psN)!j$e>&w!9iMeo_2Jt@4AWKr_`REjeZMgqamXx z)nw+?vKa1dT+eYbIOENr(~tv&Cz-BxPZN{nZ0vkn)Y?0^yCB(Yt6PxG&4eQ5fjiSWuL*3DQ18aita@(Ils2N= z?__qsGyP+;t0s{t8{&}mSgp@$NILQ2GvO1d$5SLB2a`^n3?f{~IOwL+Tm$rHm}Yz0KZ`0vgpvCdfTeBLw71!GLNC zYFIwi)HmN+m`cX@DM~xg>=W;Qd$wgNaL-G*{dePzTxnCierz6LBKIv{e&A^M6j4=% zJ@(f*b2W6e!5R-a`!gM$TnCfDSk4~>2kW|>gKPa$AT=CT7o5QoMQ!Ov-M29_cd8{jTJNZ5UJsX1Tb?(x)+yvBarD3U~2QM3) zny=hc#Og0u4x~8^DLjVz%MG#VWLgY^FFlwqy`*1gu7vCU%Z!0|{ru#hUi>R1sZjvS zANkZLjOaA$Z7>I;y}|I(|B6C~KF*+k%o! zI=csj(Vl(@$qiC`ncg6){bsk$(D>lsMfbWpUe8wVmtJ}6wg8xqJsW1mHilnMNOOa$ z`28^`*nv+~UYZ3z{FaORmpu5I=Z0zkuI?Ag6{=f-`y9#v-~Hj@`JxT z1uIf5Ln*WXzH=k|gGxOGEU{l-y^FMDcbC^D*#gT#vlsB${f=(SqB|CvhB&1D!C{2B z8w9R{?ntk9D|TOVKIuFB{cTZBSUoO~puFn)iRyqWw;lcgkoGTBX8uHuM?^7}TLKU1 zG97>ThB+(ymurCk+c(&qq`dDLi#T?D!W z`iw!qeFaHf%WQKYm`>qL-0%0^ z`UhniMaObB*T<9la)?~6_wI6268x7LiH&uWn1gP>5rc%``~x}C zOOmXIoY%Yi{wvdCTT~M#Usnzp5uys=oN$VBc09Sq5)5=yPmk00md-*)B0@XSrJ>jG zc~kMLF09;tg7!@&f?m9cIP4JfA2YPle1AgykthE3#s^5g6{$52#E~y8TAU&Lnsj$e^SNFTxaIK+ON> zx{MK%9%k2cm!^HL>$uchT?b$oHjciEdc%|2h5gn55eFycDp6%`Bjq`#fPMC1&W)SZ;D1n&oHC$CqXAn*PSY*uT7h>ETp+UPZAH!(zvqmEZTIR@95Q+SL(As zdNaE_cxH3GRJOja$@`?)w6o|+ z-nm1j5f*ISScs88&?#C58z}#2iZ*|m6{!&PoKpO}W5KJ*LbKW!>MoY&%9;qTZ@h}i zT)UNNeqd)`Q$D#fy+o7;>4V`Zpd@tvF;)KL`0SfC>if|nutb;}jd)?=yyiAty{y!Z z%XfF6f@I;Ia|VfniOeaQH)63R2^j;4gb>*XG_`LVsB_fl8gXG3$z_)(2SwWM?6COi zbo#n#WOlq~KT8eWg=4t=;u7|JFhDcj^Tz~Es^o8MGaW7aB$IM)lh5cxS6P2K{CIJ zbl?5wvo&Vi(XG2qrKz_(J=SYii1|FzJ!*%JC4*T1mAJG-{sDtp(clrHx8rHe>8C`k z*tqn4{?J%mND5!Cf42?r9*JLe#CLKaX1A`R#`uS($Z_tcHow_wocMZN-+o(&bd@(4 z%&*~wX$22KCxZfwO6dl9-suBX^QKh(296dgR6&1q0B-wtr>=hOcSIHj?92Y2+nMpM z!ATc6%O3=(+qoz^xVIjQJ1Vql?TdYvw=N5D7}!0N1dM?spk_J=2r}_={V%m_2B;zB z7qv9l%gti-!;^F#lt1|OI4gTH2_Og(s&m0~ngc^;)gKc~B*Y(7((?WPG#gqC!vB*O@l5E^(4 zKI;?-{8nM`az2j?vH@_sr^5h# zoR$ew7x1T%dKhtl0S104Tf$L8i~b%WJ%8)ta z&GhiX5G79tco9xHe?1ifIs%$D{N++BNGw4TcM=TxSONigch++SChZM&>bp~Sm0y%<@035Ma{5ku z;#du5z14Z@xTH55Rg)WP zP9KhmqSl4z<8-F(E^Kr@ZG)9$J3n)<}$ld%eIr(*mkW!pEW z{?b_yxh+A{p|6j#9(TTUVs=qDS%Ve8CH*iZbViA2hxbK}QPP+n=+ zDzha7HQ>62A>oeV(*f0HUY+Sr5)I!D6*Isppudu5Qfq|9Bht zh4)~frk&4Pc}PB%yR2pNmvk^pW9yd8q0mMyam+h6ZtB-Q)>gl)R_^Sclv3R@gD%?O zMTqAsmvHY*TL2Uh)ku(ku^dj`70UtoaJGgnFN=JbHl9+rqcEXlZ&$*S`h&Ucw{F-r z-`Tq+Q$i8aM}16&SXcRveK!&djH@7l#W+6J%3a)SAkDz;C*RuhA%Eg}SHKtL;e!N* z94PF5GyJOt5&*?9YqH{M!rZ0JEb-)y9e%`f5epg$Wc+t;DnD5CH$nc~;b>FLyzcbe zu|i3R#YmVu2Yk^t4c6F2aK<6*6Mp=u7;mN$zy?S<(ynXuN+V`UcW+i@>!JLsM)FVEIeBWTrBIkWNWJ+DnxF4P3ZG{n5w=x1 zZXG;Q2#Vk_>IK~`DYO?mF1pe*jsGf5_Ad;UOj@`uQ{7m-{-Sax3= zo+70H2y<9}^#R-f4el4m?{GNJ@uf``bZJO@YIP5I>he~xAz)|qPM1rY>xcmB#)b*1kL(>h=A9CLvoY zB$TNTB}qsNGg^>J(n1kasccCOLJe{ArRzGlWcn3?mv z--Dv_`FyYMb^U&SIOn1<=KVbP^E~%+->;?Xxx)q~aRJ9HuU4tN-^guOaM|FeAB*A? z#H8k5TKUNj>5%fqvC{F%NQrArAAxDCKv1NCV1xfZn1>)*=vJ>oJi*<_wh75`VQSK6 z`dJSeZL-Sx)>QM(-1jRpMnNckI^zq0m5g76l+11I#Wfl$BSdem5Y!vbKt_{vhjyEK zGvXcn60u+S6*q*#C?iPv@RZ)*=UPhp6}0Q=7j0M)xoLXq%-O|z*l_?L!G_w_6PPYs zBlz@{nJvh>2#zg9n$V{2K1^Z7Lqy3Qekn~J9Z^8?#<}PMa-3Up$B1+zJ=BoUV0U+E zn&yX}@5NSLD=@F#m#RxSU_-xw@xFgUlC_ROqDkZ~-14M7;lRy9*G{g(6$GsjAHfZJ zLfD}Ln?vUZr?=u*Pxw$t8hav#$1KW@*Bq~u?;W`Ym(ybQcXMwyzhok|7H>1coMNW| z$E`!>fGAgs02CR;3@)7m*}ueYW-Cu5g{I4w#nv^1*;cu0inT>mdvg)lKwP1VIR3?P1;?GHmeZkub=pF%6`tUC zSOP!EGK4ZVx&<*y65L^4V$I+IlU z<>)!TH%k}K5;3*h3R%OSkJ9C1iQ#-Uc^&}T$*q~0SCk%{T%Ec@#Q&#-X90-D#nek9 z+)J||JAY3bkt696?YRnU`h_%U5w+mwk2krUTyi~h?ZWyv>8+R%qFgb^8kpEmv`7y@ z-Z**sk95|}%8^TM%dEDJdGgk%@<-0KygB4oO+L$>tm!a^^h87_S~|rS#^d)YgCq+P ze}QsBRv?QYqy33{VKYvvjLW?z*Vp<6k>H%qJ5~L}t!kI8LhYP$#v=9iUWA=KmU}*4 z7|k#K=$ugFHS_svILLms{Kb@~{o8D^@0_UoWV&kO1y9PC647@zY#@Oar#9et*c0eq zOkg6!_k{5+NT^%;BIQ>fh_t5jfJa^tzHb|A$IYY;9X*3tZ{}{Pc@i5Zbt<+*G>Ur! zg5B#0l0^Z19(pxzHYXvmu2WgG!n=FL+?OwJ?o@TFV4iKrT>(pDKU*UAir26-Bj5e_?m52(N+l#znBD4ICzt8H9y%l_XIPjp*sdbHVbe@2EfEh%o z=g@%6hLZ@rQyoK;qw+DW zJPJkoOHJn5Li?m%Ds*_JoLKdm{e2stv9ER(&I^H{4mRQ<1=vp#=)_Y?q?8f$^X4zk z9o*Y^PXFNPU~#N0Hc`+{o7_!1!=Yk%kR>#2hg^;dEu~TqW1L*6i55mp?wboun(Op* zzuBYq=mSG@d(Z9KEaZkc=}Awq|Kqb{LnCf0o(!@^IOvS02cBo-6D9GAI8M14yDH{U z$LRwsyLUewOohb7SGH1i6J5ueaseaC$n*!DWp~F2D}f$=vOP{Aa2Ph728EJA58e!h zg0XTk%Z9(z^v;C5#Z8v{TPQ}1IPu`)LzOEKmx9rNzAfB_KW>cTzXnx{+*b5^G+4HVqN0pn9Q&wUEjI7|0F8*D8oTwWClWUZixZuo@=tGFKDL9AF+9&-ll(N9yqqEg#3#F6`S4OGIyxTnTLV z0YEmCYolkST)-&%ffKU;c?cJ4LlB&_d9z2*h=G>dnA`#vo@tX@F~h8$O_BR8+-Kio zy7vCXc4LW9-E9_*p%@?8YAih+vo3V22G;pOLOMpdVwX_iR%{D&@quNjMlv7W*vOZ; z4Ll}+64^DsevoK(hO=Joy%|4tMkDMADEgeNmA?=vfOipU##QgepNimj)fKrl7jxtW zuu}6C#S(y`0nK&Vu#7v>IRKq>YQX&S4#J}*$cH;_@FyWP8EaVg zm@J(K{sYjk2H6*H%18^U81r+xH6C&zpleBYMsX6)N_Qm8MD`lgu z`2n>}dQMf~bdKeL%GC5T$uar2ep=srw@z62>W)<*p^yg!d$A^Qol2m#SY%t5m!iZp zjnTf!B`oiA`0Dt1+b!!$!xv0_g~dC7%Mlz&J%Rvr?bvrRQ)C{ovWSH;DM;|H`Q=e5T9Q?Dogqy!OrEf2#! z0_khKB!%Nd6uBB$88A)=*%znEkzyLD| znFB@xYzizUkl#y1@2`W9uv{QYrCBQHJ^GkjWN2ENAlCGESdUa}vt{BL^{L4J)ISJ{ z*uK8N;##RNdAsB;Dj^VG#}e-yCOG5dzh&E+xgD{d<2XH`RpJeuPINLK0@ajWm=-?4 z393I-ZV~1Tm8(Y#D@3?i(BiC~~Fs<1ZlLsY5WSFHyZ4_!4rYONJD zzyA=?e7uoTG}cHlR-~X}&48HQHm=tEwFGa($=qAj<^8SuY1R5Wjh8IIXi4RQg zv-ndM7MJ)ErB4p8pCqb*l;zt0TUFz)uxFyLsDF3UoFYVu++|aI&P16whU|FYGMFp&33qqzxYV^fxMFMybm*1KRXn5 z4#-u#+9-+7H#_$avJCm#okhs-zt}Zk*`VP$> z8u20xH+Gd83olBHFPVOP>%Bu?6*)t@dGVw}PLIAHhH8D(&%5TEtKAbLuoWUZjKWqNKiG#CE_KjPUM&{-3fL%d-kW^ zpR*=xR(#~?BhekgCkayrV;Ynmrc*W9QSXS-oEmF;;-2W1wbp7^X8nD!`oZ-|AV57S z9b;y#^8>~s0+h#>z?cf9`m;8@tDTny;$a5z9-a>q>^Gy_ycxCkf{Z<{)AjpJyy8~E zeeXA$7G#@g|KX!U@ZOfcv`#F!0ska ziBs8tNVMImUU2bsLf-Kin-;%1VSeJ|8{ZTf2xlt*m1MGle*m#wJ3LT>ZFXh3wB;gsQl9?D_PR{JqN0lfSk$0L1Ba$EW?=DtQTuOB>t*B4l8h^fRgC4#6 zH%BM%3~7_E^4h8(bw1%(55F|k;0w}&gUT+OlO5prm?LEMqCDwpi*Ij6zC_5W_n|xF z%z;)IGAc1nKmpu7)8Ra8|D9Zw_RvC=p&Z;uQ{_-;I3bI$48whoOlA_)5T~W2x*H0~ z8dw=x4qm>>*p)Q>YQgJ}&z3(UKE-0@V?!~v&4xa}7ApW5xX-GRlv zgLAF3KIFU%okO2#SSS%I+8(Ld9x?G~0O<)lBytpK+mcudB36XEn84WF?EA+T5^KB( zy_Fy)y{5<8!iBoz=<}BgKfIqUbImmHX6V&XMvIZzY2Josn@1&a5Q%&4zP<>`m;ap{ z@m27ohH3mZ?juxs`43_a0?~wWl-f~d&JM2NWxeI23BB>s1{|-)?HTwPWhvSx?wxb4 z$2MNCT(HAZ*nGN;`(y$Ubehy(Xh)<6JqTKX;xo_n;YJJl;-xEQ=({g#v66cIea?p; za;nyj{g|q7k}zxUckY&QEF42n$|*%GiGd|pq67)f?Z^;q-kA;D@PP7(?+pasWBgLQ z17~?h901{BPTz+w8jU z?DK5#q3Vr7CrtrGFF~d{%Og;Y_;bAUIPQ;Ks_?>P%X?A}WyY;Psl0Z9RYenP&$SJy zty4%PSkkUz#7nj%yNu=4lS*u`W1kYkGvwEnfYv_0hA+V1O&R1bMm@a7;nMM!BE0oe zUR&9DCdTb0w{!olJ?*dNt>oqh+WAMo>lxo^n9F!fSR7KO2C45Op&Ar=Iq~JlD;QDa zeq-nvxeO$?*eRWpdjDXyOnrE3HEpX)jEo=(zF2lG@Dd2Me}-$=2Cs)5t3gg2$%vLy z$9xD&z~5PD(;qtLM%UaOlB+%I3$5*Njg<}w+)37HW@X|&Z@!e zP29QRV~1F3&y*p#9rOL97`JU=Da1|D_;#N1tc(`rAsci;$K$M=Hfc>cWM-x300-;l zlI@M&$LBefFQ#d6(dqB(rhaQ9*Snan1@%8Nl=V40U>MJGavddqFaAqSJjgexAj2;JpyRC5VXekl1W>M)fCz! zP4J4}Yj8sTT(CDWS&Cx)$lUMWJ4(}IEsnJ*BPS2<$}g#(`-Z+ivuo^ES=!aA(^*IF zZ8$&J)LzIk!ki&aX1|gD7|J?We+gsTq>({`L(HQ(PV3(6=NY#e6x7vJFDsOWJUTc- z|J^^BNh~zY9_mHc6xKf&Gh{h-$#);>hVgA+Y5@E}%~~&bQG#HD3?k?dCJ)n~=$qu2 z0F}3Y+jf*C#uofEon8EHb@kqpC%>+to)P>WdNtd)yKwgO^=4li+Y8x7n1FEJYvkT9 zZsJp%Y^S0{VQ~7Ig*oTURnzWR%`rK4Ov4x>6loFYITqn2OnJwK6pcAs46Z@>?cuo~ zjsLzG)(HYt*%t8*+=K1Ibu}LfILb>O{Oo`5N0WQstW;0hLt`qE)P8U8Y0T0JP|wQ9 zyzgd_LR|Ir`>Ydc3olWod*CZac5nZR#ydb3kV9_cWJn5kDkp8qUmCtVDI5}}Y{A5& zK*6W+cj1)|gYujD;7s%JEg9WnA|@}sz`MiftE`%nhRm9caUvv@GJQMb(&;^nxn6~e z+0D_bcM?whFqa!j*IPW(ScsK`Dy?8e$?@A3qepGj6-n$YV4)eBv8J^;FsSK?m6415 zb-&Ddx8q0Qn&O{sGJJ)Ho&57~S+0ZIIO+#Qf{T45`5`hU_c=_xahT(#{Z3w)5I5i> zTs~6sWZvgtTK4Gs44rJFWj$QI6ewRh4$peW>rvwxw{>biBeOOYoT!hS)pGDu z&o15YoJKutpBh?Gx&PXRS(vAgyU65>s?BGo!PUka!g9OoN5dTD+JT2=KOC*FkQ-zEA(&Ih=v#HGJT~mj$9A>AgU<%w^qG zsK>u(2;oMI(0L>rZ@P^z@gTxUuGcEO#NFVZlKSM@l~;yu6)t4dVhVgE83ceMl|yo| z;Spfj-lEJop)a|As2^HgVLqkJI6yLn37B4|j ztQKNO*|&VPJ@q-Gw%OAjzc}&EsMssIPZ9IipLaK#=683WO5dYC0U(8o(!PaPWfInY zz30_>^`s8(6J{hXbl4XjWYTk(k=fO&5h)JTKYoh@%9V1ccEmZHtE2Y&!WRGBy)f)< z{$J~y!We78qpi|UH_x)6XSSWQ*vd?|aL%>V`s_WK+atCPA)I5$@Y!`H=AccexdUw^ zjQ0`hM&$e%TwMzWPh|WF;UdF1Q$NH)%tE3zMb_KPU2m$DKRA5VGb|eO)A{}j{WI*u zcHh%??Ek?Wz1XZ7dtyf7a|&sv$X&T!STKoH%PyPo1p}g)lb#At8&+0CD6i*j14K*- zEJQ)np_h+a=}_o_5}aU->=dooWAmBDvirTT09lAGI=H6|aZ!fhfE)?#3#L#da)Bz* zN3=@hMPp5ReJ~=UUHCwGPG(TM#qq#zt?T{YTo_*6jYVj&t?3xjW3o=IhT?$q)aM4+ z;rMPrZuzkc`O~6E3}S`hQ)eq!eJfsnX420!u~uBD*8L%Izg#c-UDv#WB0A)DW974d z_#!+lI`*a`%cA8r?z6$pk2AZRP7G+qo;emo&~5`|Yi9HbTAMy@BGi7ZZ)8~zs;JhX zPZ<)l-5EDXt2iFod2Kzrww3x0-oJ22+`We46_?&|r?t~q`hH9isXSoR)Na26{g}6} zd>xjFnUfIO__~xu0u^Zd{=-Uh0K60SLdfRwz;b`KVKDH_|Hf zqNyfLn9ui$0~&0i+)fbnntl4(-+`!vHTb3oqWt+pu@>rxC@r3;nVqq^E z*DVF``U>Jet33b0zz<+m0!X!F{F6U6xKP)a3{A~t=Sg1+K+ny$L4S1paw-`6Eg&EOnw+^eG`1qH`nTEm%`N3MzpKX~p<6*^c3tT9 zvXW_5AWP+MSrCVx?>b>?AR-J^=wvW#NCI04+L-APs+|?zsCQ#m31){2z)A7Xrzmkr zkzSALO_J?fhPi6_vJMrt;MURU+;pVHZD1x*@Ds>A5H=5H!5%jpjd|kM=JBnNZwh^= zkv_tRB7-gmqGXLQF?&%_G73-1S*Yu&Rp^1T5+P%uHHnG$gA9`!Z6;GKye3>29{=h% zW3&S30<$~fK%YUM9P9PC_uW3If!u+=e;crQJ=@T3%EEc{*8qKmvf52TyDe-QhXu8K z5SrMAOw{_pXk{mMTl7{9Op(IMEi0 zu^Z-JWjn}p2=GlMV~8Kk7V(0*3D>D4bqN2m91)Om z@qv00>W>3SKPm;W;}%&jO~6EGA)Ge<{7MHkaH;8&u>^32i8K<}DM!#~2F zVbl;`Fu0e>C?h1n4T5tkx0)A66WEYJdAnp+kWocg!(9_VuV~Cme4yMfcy~dgBjh^T z43p+0Xo>RhNni%qFN|)=CeRuftKBaiE8g3gn`pnrEO9OZVx*BleGm6WfI2@59AaE~ zBIFWpeQXsOJAJ|GadKJ5I;TI#W#8xMpE!A*H^2=WFEqn+mFN=?ohMRu6x(yF5P!GVl_&zvIO-Po2tS>8JEBHPjCO zxHx$CRnrX(&uPCU$my}#w0vrrsthEs(1aX{($Ej=HKO3#n|8giW01Ha~_tjo#_ zPT{H~TZ-IqnScG;mrtL?r#)EocKXa2)Ghj-IGqt9ZiInG0_xAucd*o~>|e5ZdwYAj z%0am@i!sVV)TxgiVP_P1CB4JXJ-k0Hd6lK=@NBrSdYgrAFrGieU92n%18fn+TMO_$ zstl*m&4Y=|{8sCCLaqE*WSeQP?OBOcU5;rt-m0i11}LtT>!WusfV<%@GDB}BP=IhE zuiJXYj0!7C^#y`z9_&&`%bRJWyLKu$B&!ty(~KYSnemmz|ej< z60r5zxT$@*lBos-;4NYM>D}iYL3S=IGmlxS7cOsHVY&NkzWu||66_8TE49VgE?tW4 z1S2esz_L2_(0w>S;7%KD^ErJ$x_g;eR;lHv%$Qdi_Tx|?SP7{`T_7#1I;rboedKlp zxyh^z%(&eJx?8_lt{Or({wx3w{372maX4=+`R%Oy2h~0|<`y%I=Z5NUG%YVMo^MmG z-&fZykI*l4y#Q9Pqo9An4wvoN3bfLIc8zJ3iOrSa?|$t2#M=6*M)mmKVAGj1S~7ln zj*J3a7NxU6@c%(D93|BI@O~!LJOA_%YkGNuu2H8ce_W_gJ({yFw}k`2ym&e?z&7At zG*+%TxbyRvkwW9iy};BHeWX(M@L}|?I70~`5NFyGGf&~c#HR*_vB#o84vhx*Sp>jm z6DHB-;GlcYFfp$VTH8ZzX;xlvFJ2MTibgzj!pVed;YTm%TyH7qXG`ADIZu4{M3>u* zT>5N1auymVa=w*Rq-WwW(@9I%>X)>00v=Y^b{On_-!&QuluoAf&K;UR&k`v#QNmX~U6v}_`ae(kbCnX0lFpHzRYI@4d_0eS0-V~5R`Ojjj;XWL7uVIYm0#5>G!YxL2% zOARJzdjv2)kS$)GNe@p5**-l`tNy}ZC z>{S8Ln*d%cb>PJU2`cKv0^my9jCDT2ccj1dclvcFsGs_9ZE@4kn?ilC%h-{Aw&C%S zkUV5vY{_%8ZRU6O@0hV@<_!G(%#ZnAx!O4P){Z=WY%AOL*gz(XQLf)hMK=r`+9({>)i(z$Fj}%V zpdCPmu3s9Fd-)b^U<3+R4B9v`81g0tSnf(2Svyg`Ak7kRtSHtL+5nPyoayi+5c_0)za{H1wS3S|;b+Z}-r7NjX)-zKL6Q z%aC%&;a*t8R*pNTWjbi|_I){;ulgnAkT(CFI-M(4oiRQKIx8|LQ4ekHL zv1^30WGT{FjcWoUUwUC32BB|@811jzw3n03l$LXe!Yh=;9m}q`e`cRh!QSnW;Yf1i zY00>Bw#A>~Fv=HUqmXF8{)4%73ho%gU_EclH3njn;5)aUt{Ot`10^}qMAl}-8>brz z9=6|K+WgYvrPA8Ms@GY7fOu-GV3eHS%;~GwIN}_)E&S6~$`9p)&_VyTrcjT}q7m9* zniz-;|5y;d8izB)h>mZ@qNnRdqMD|3hGX_nivG23~h6F1>I|8HysZi-49g!+urUJfn|5uv%VtgBC|@u_{w zJFYAeQ#cgd{(-t3y@<2f;#U7)cEIOTF;*VLj%^mJ$>j2N%EkT>kgaYeh-n6TXSB{?3e?=A^0TbaIp)9@vmD9tb@ z>Q=V+D(867rFqlND2|?DG}43aXN0OpHfn@I$>`BM>Lq{)zoIb_5&>o_3OCfA9KpVo z>By#7UdiK&FCVfl3%I{wWpC6$VL$a;q6LXe+!$Dl^saV*=|FRWe9RH#-8nWOrus7B z)|w2VEiPL452mlU4vG$tokG3$kejVh%lRkYorjp42UB+$C#k5ER9V1$Oon`;+%59( z6IJAi?ZqxdpBp0=i#J=d(^8E;R-aeL-L#+Lh^B{aL5Be1O)x~eA|i4z1%{u|;AIt` zf$m~Ltu(v#e53bE@+Oygwm0YaU$VJXtV2mW<@bRag)*8r0|4rIfC_If!yH;~YU#@U zOt~5-^x_qAt7^Zxw9MbD7q9sgt2nq8(^~^}Ch`tz8QQ;;#;S4XY>ZDv(1e}#xUP0X z(mwj51Cfg}!xKr8zzOpk5q~w+Sh7SkdUCuW4i*+$p7{N zmgxAeIDB;XT<{~!#>(l)htya7hprQErH`D6hs^euJ1NH1F}jy>rFM3e;Zf#Sy&~B~ z(d%#C2sxO5I9o5Uta|MzdmFS@6{ZYL;*{?M;}n-BeK~F(OXRwH{XdvlhRMjo_~1i| z@;mcNLu|jo%XzRX@J}B=>K;;W@ihU2RYmAzCexjTetK|-D5jz{v5+_7O8C71koII| zG{6A=+rN+h^KSMKa~QTeSTfDYDnt&mKEP}vt-SjIrRZhGE!8iZrHUU&QXW&ooIl13 zuchnV0Of*KmQ&$~C~RA4pv|agGHP?P83fLLo+$Plko$>@hZG{%f>}kf*dYV1c25W_ zrLsmoPk&0t{rjK&E%WOx=gwm%g&Zu`v$^^3YV*OW7^`U#aiOis^K)$ z4x!0&7ci-!U!AkaUsh;Up1%riE%21nP5=?O9PF-P+zt5idS#eF%?n&9HHtf9UK_ z;A=l3pY30Wv<5DMAQ(tN7)^Dc*SI>hW8KBt22Y$i#3=ym-=8^)Azi)5kX(S&oG>2wh78;+!eITgnmeIKj@&~H5XlHS(yJGA*ra_^r9HrD6cDlaJ(KUAMAh=@^N zo~O=vBvgMIVD2a3n)+k{=Kd2P#ff+&q(m1Ej%`qux4FwTD|?EUaj_n$3u@%kvtPw@ z^?oF;XdAp$p!$6sxbr_;i={Ok zhe82$6JV86NJHphMErdiRR!VcdOMwg_(DU;%j0IOSmM1b>O0CbH(TVwNNQrzaHfG& zu36s^1%mOLz0#ar6e(I8Y&6$ldueDBs#nzP>wAUUFT$+t$`Nf)-F&vJ@!lT9f?IX-x>09>i8q{oR5k<|#(Si8@L+ zan6jl6(a!2oCieTK84oE7XKAX)<3v;<@vy|wM&8)Ma>n$YRnwNQ3D_m>USELsP&%U zT!y)yd!DnXK0?YbSIZ)2gWKTw6Q!5=iQ7x79KLE(zkhnVGz4z=zhOarctck~hd@ zJoa^;Q8Rz{hfpKAeP@joDkiR4HeV@Hfpa-HBFp-2vTKI2&zH6qvp&@W*SbWpL2a5@ zWW|xQQ4-s+nmCM={p+Z_i21IouM_vX7|eOQDkQu@a#h>~FKd)*L6+lbnUzdoUA(ez z++3+Ak(bmtx5xdYtWG$eB~=1^fSXe`od2-@MBQ7Bor}m9QpGqC?=#o!ZnIuhJ7d?u zmX$Nh@;zfm8gik81nB$UyrO!6ckqO6EJKyQk%r&R^mspu8DsJaGe1t0!}w-hCzB&2 zoVL^AX^mz<=u@$^(-w{b$=R#6|f_egq*)hldlTbUR87pTBjYYN7HBfMU~dLi@WoPN_F zg6?Mn{tPHG(C`<4*Aq~}!Y`J@zy2A4f1mp6eXJJ^vlP~8(UtSKZs)w6zbAyrsm^XQ z!L0uGp5^e3wf?=Lc8H;wk8jEkGZ&c}S?g={bH4-M_@|eN|Ij4r0eTUbOgX`v7M9y) zS%EAcuv?QZh2I-pd-TWrE6}@7PQ(5mEPG|G?2kp%d(7<|blh2*_ANE#<1$rao$D1o z@iTXX_Q1wrjpE|A@#;UNhO>O(K=6iyP_Rmk6bofJ)rfv#POxpN< z4jglnW*$QBF{_V%-n#A%tX@pNxMp}c61dRMVBhNq%d&wjD^{o#e<-~lMpTthG#$C=;Ki@uqd z@!Ui8@9Hm?;$vT|nuTcu_TD)ZA2X6Y==u-F>ghiS=CyRM>&UI#bsXsi+Dwk?0xO@I zwiL;t*_JD>XJcCDEl6%7a%>TBX&Ixx{a+pn9hOP-x%4y-ML)auHWp6zI%fU&^29!J zmAUu~g(mNh@iUPpa4JqEcLJiY?ibT@9LPWow^W2%xq+U~od`EcmD%;bU{Aq08U@nfiUVElr+=z^()W*{N$1-nR zYukTCvfg*PVizIG6pM8ydt}W)f~j!?=bmA?<%CWkW@f}ohoh6nTgdT+V&Mun5*;9$ zYFnJWtTAu_xIA~6&tp-+?D8YqYH1Z-q&p;J0fKHSoO?(efG=QE> z)yh4XYMn(BlIA?^_dqQo5zHn~hO;tRxE7Sa(D4CXLtIYVvXgiw< zP?S|pE_CHPBSzYwjaVYQ$-H}8Rir?{gkX{j$Kr>x2?zsjU?t(c>2J8&@QILLKd6{k z?!Q`DLgT$54{~v&#Cnufh!0eT+ybUThbY1LLBP8T+&K}RMTPZ(z@kp51%l-I3%>bJbR6@BqkGzf zKJoQovJ5J~kTHTEZp#tL<#8&>tU5?;GKeS@bs56q@O_E75_NUV8rjWixjinZ> zM(LHc7 z?_()KoMTgi2zo^jZ%vdD@tvYsv4e^EfIbc@D;+EE$_<}Sg^51_TVU=n_@qUFapX{L z9<;J1ttz)VP_iZp%-%o36Y5bh)Ib5lEep;@h#OE1!B-TiFK|u-*>8OA%09M|HGODi zxj-`X=4qz_ZQv_K=}pHTN2aH07Vu}3=lCS$CAt3fQGAnjwB){zYg9xRvWretK<_XP zHl5B2YiL2+h&;G)_L1qde##+MZF>TjR3A{jmQgk0V!_ zg`XK0?Ez#$7~6w8Y#V6F*KelHCj6#SDH-E7B1oR8-wQq|g#dp6XsHQ?`vH1tO(8=! zfHY)+2@pf!$qmJ{!)aU!*X<+8vEg%%BA~B2bP8ot6wNXWyWe?tMmIyXBP}i5)E>7d zz0&=Si`G%|8wMLc{DmECc;PkvE(#6w;a>DzpxE;3Ga-Eaz8`SU4tZyJ=`=OsUWIA} zGgQX8^kVOeV%E1}^M0x$z?H^oMRn(gjRH)JB|WcaR^^Bgztz2av9(XX`|FDQh@};= zBQ_;^=YsHO1UPUCm;ZzB!k25N$>qwmcU@z0`w2p*YR%6X|`0 z2EY1A5j^Z8p~kICo%1n)p`@v6y%H9G;~T}5(6{syFf?AE=E3uj>Iq*u4BSV&xljv> z^q1$6=-hmg7SWqDt&!OUO<_WxEVg^^b*k%}c3ZWdi5Mxhv^iVvWzBiArO0h4)v18k zyR?Hg&~AV>)tV8GGkq>@_^%gS2>wt&pXlaO`kGya2~esd8bL8&d2qHUVCH|Hv%pWVa|!2 z=Eln3m_1kVs;r+^-0oG|))-n}A9gKP@)Tl-K(A>^bPeaLVa}+Bf-6o)ezaV{`2aWf z`36e9dWSNlp3ZYrug*DMY7(X6fK@^EkK7GA9Y^tc_+@M27fYGh6{6GUpXsxO+!oYj ziUWTwhbUkKyhHj%eTS1cHYG0~j4z&?|FU>4Bp9^`wbD3F+7RUQ?z$FKaY8q0VgR5Z za6!H7RuCRJ4Kei>Ya*svh~VDY7AIV?sM5RUa&x5J#S53WHVW?>6e`Ot`-2;ds;WYk zhTG$(TMwtF2{V9Tm!0*>{6xc?Rrl;s@`jBCKyq4{gkfGbx16h48aP*yJ3-pxVMvw9 zBz>d|KX;#b3k#c4PYS>3P3{0dCI8b^KMx$90uPtCRS_nti=N z_=|$2<}|VV9rW1Yu&@V?R_V`c3^loYxJLOD(|m2KVc&rey&Hm-IAAZZ-H2GAQoFLz zRo=kbmXIzaaoX^{)>B@w=i40$r+iupFPamwpDqCRSdGGTP$utAASXrmHL}Ew#{l`z z^LvJ%{9zw)E>PAvPNY(GY$UgYL4eIuozD_Aw0YbRC5t(qs0EXZg!+Zl`$Dq*^oN_T zugy}e@~ER5vp3-Hpj|G>=3isiW(&&hOZxri4^a{e!aSex)iq83y@9# zS!ZGllkbrN*!ZNB{VwJEtDhX8&)>dhwecxX>348zac$#5ZEZ|M^O`_JGqC|Q9??5H zR?2TdYu7N-?enYDrkrdkz3@etRIDSp`t@Ms#HMu{Ohp9SdNyu4SEGb0^~C&1d#23i zm8#e0ZMl)Ua*I(=Yv{l-Tg*}y4FGW919gy(X5d9}A8AXc>Qc9J^6Bs0_dzLvJoD|2 zC<#1P8Qg5dkm86-t9?Bh1CEv{%rJ za`OV<28F~xX1D?qg z@bQXV zUj!*1f~fGa0xd@vJwKri)I|K|$j7S?SUPqX3`(OdhDCT*+sfNa6FepHnZrP@8nDTvIW`@*$@t|~I4@76 zFj-5k%l1Av@eH#Uxjk*I5pWH3!xvovI|b9rm`Z0Msoa@}It7rSHpcJJM*9Jk8i!B=d^kmc(@k71O4P$qzA zmrUD*mRZPwk;{$}@%$3jX9I9sR&!e1buym9#NX_fTN!^Mr$n^*>+{#GJY~2hU3kmt*CI2Ti}g(K$tjW5N~gbY$p z6$DNCu9cNm1ucwhn6Yxn_1#7Wvj}%LdOU@56*oJAGY_V2*kka zqdpYT3Kky}kH4Wo#zx@rTtC%^ziCqBrd>7blP6dBQp>}qgCF}Fl-^Ao!Ng0sLStYs z3<_JT%EzrkN2cW>JTm|4oG6d^SZSn=fn`zo<}Kq%biX*J4;MD0mt3-DD$=5qu>il{PFyyWBsg#W!> zGza7b82*ScE(3UPdN2O8fY|Ny;d`m$mtn6w@1g*m!O<1EhQy(-=)n07M)JOo;OSAC z20PZLkY2YCCPwc0uaPTR7@l{{4e2cuP!VW5(pGqHlZ5kceB8#9{$;P59~`%83E$Eo zfideeAMC^pbFT15ag9U@B4=Ote!;A8#jdI=bpldgZ+#ymHy-SK8rr}tLzNzl6}`31=rA43O&6XjHVu~5e{ zO{Kh%XN`$s?jmWdmCA=2HNYCupfS%Ks;ZuL=lDi{$ zdOtv23Ggb=+=!ME&c*7m^}i%o+m#M(m?N$Fn1JuP`)%}P0r2!cX*Sn!T-=Bw(`bLDXm5{Vc`S_qi&$jNN;;m^3}ypm5>&fiTDQ!ESdaK5hjv z_Cc_{19Cn1RUDirQ+noi&e;L|mv-)y?AdbaUc#L(M;PaK{)ZM!Enb;>?ED+Pvb|J) zRhFj8#}=`T0gbVah)cY5M%x7O-fk?f85J-Oy9Kd=L*P8jA}l6>(1t%+vjU-w*VA6riM6>isWbPr zS!&$=_FiQbHf^=o+QK!dp@Q-Wc1(Z9$ONL_6KS&~p0K&dHRTu)t`0O+IaXL#EZ3Zw zr+{Rc7yC+sgY~)JqPhYL#uBicfhR%69wn<3qR1)cuBLTbMR97R#QuaB=L_LA^H%F- z=fWaD%Qg6&yhMc2R`;`rTa{}^ea%-Vt%_-oy=A$eqdMT)nzb88GaJ7ePVvg7_2G3=b8kg2=@wbQ?S?jq{DThj4Q&ZpxjEK^IGa@u| z!1VkyPPubc`|S2^=fgooIUjIq4(f!#GY-1K$0V%JT?RF)aWH^em*rfp=u5W2C8|$F z<*wPji^!i>-qMWw_FZ!|D|yMP!COjY@$e8I8pL>b6JLrnNm@O^=d$;u zpBL|4m08^z(-$l>58Q2L&I}#70-TMW-wZFoJ^`TUNsOa!_@z^;v7dn|lftx=+Rq7S zkr%1l2Nqbt-}AGMj4*bSJ-4foYnm34>+5Bs=I_F;w~*a!p1;Bw>o%tInJ8%AT&HGv zP4IcgimZa7st9CE|GNPhS?s4O`D~;Nk`;E8(V;mo2R5Miw;)qP9u~*EBChAd$Nz1g zLEOZk<0kd5#w)=Zi7K*V*G-!rf~qKFmqcBtiV`KzAE3Gp?+NWK9xWO|)!C)y(FZ2A z7zM4yA3s(+!d8Hcqb+2X08R%h9Pk)Rw;Xi@Sop47ER~k4?*;Ww+(01+Yow5b$m{=7 zkaZmi{cY-+6J~F z{4#)*Oa~N71TTrGX;Vo^quERIYYBsQNhxEkoipQYox2PksGtSkte5Mldy&Mw>PsAe zbY?_xr)YKBM5lD@|54g^u?wPt?qV0BT~RqQ;$VdI;jRp2o<@T7m6De_h><_ls2)j{ z{1D%(*??`t&2_?6GaUIf1P-GHCr~e(;AIF!W7-ksDz+0(7>=i&J}4VF)^^BO-)QL^ zH$H6 zUM~VbBrBM+4XiCFL(PPD&-qZXg-So{=P%*p4fp*TFD=EPF)7mfNQ8$t5bv!jrm>pH^y|@SvxFdqf58pE!u;4@pYi~ppR#?y3c_`6 zw@bjI19X|jzsyxu#m)~q=Qd*iQhBtaBlv>)mSS{ZdUE?B#`f&!ujb*kyKnrsdM6*s zyzh=SOs9{C|A@M*g0+m=se-+P{%4b$^fQ7}5pi$j!OEE3*|%ChJ7)~15>IS$QfMb` zkpXlda*Xy%?)*_3)~!#%0kAyHK+++7hJlIZ8KM63lVvm%0-o@K6Cb(!b*I1PvepC5 zw6yJeuD4&`&x^XQf>l)|nBoadyeE3DMjLqfyq)g7uma$p+oAOR25HiQ3=W}i0S+iw8>!=K!)wn@ z(MrNJTEkO*C6Eg!{3VD}FNvBSICQLgG~;Kn?paDAT1%hdae>w*;{vDZZmwo4ZL+x3 z)19KaaLi49g$BHp!8>BVSQ?~^OZjr;hr{4Sg4e+2Gx0Wsc>Qm-9J{=^Lwu#ndNmO2@F8=G{{F`bgJ+G~y-uEOD_@xMVDj@>%Cu3Bi0;YyO@@qj&Jk8cgkFyPk08v*6 zZR1Z2(|O@@KBa#y)l4f0bM|Kg#XQqwhr$*+0#0-m&~pc+Dc9{1-9} z4k8`0gQtGf@&nkJ$lLg7nK{~sN1|WEj*6eXi!xq??w_Hs-s^qWqiN3T9XZ6tLGUQzPd%nY)?le;BQ~we1VKct0{KTIJyPY2p zXz%J+@wtl-x+dy42O6$BiL+>}H^CJc$0T}}Mq@a3Q+{V}w{HDVxS{?dJI?vtSFvtM zA2Ma+Zs^WvTpuI!>P+^X4ocy_3TeH+ zGUrJB39qx{fhRtte#Vfj?uEi)cMbIE`so03Jv8Di;u3RBonlKO|xU!^*{9_R9vjZy2sB>gC$#G|gLgYGG;|BIsR8OZ0c z7|U&c09tE2Dt9595}bBt;O?$9!Z{fEr5cVO3x(L!71R>#FiP)^bjzXL(2$iI2GPRP z>KAk6{ip@&Tq?acUyR=`x%uP5O|OjABMNeQI#IzrVy_Ae5-}2Bn8wOVQwKcCR}HI= zeBz7xa8xsO{5pp$9?Lo7>7!kjdX?4`tPAadMNcwnZ#{tCx%m36NLtbePl@-97R~q^ z3yzSl3Jd8Ww7#lVxNWnkmj||8`kB5y`H|P(`ycqw=j+Z{7VM&HEbN1qWd%QPG}_Sr zW46`9tGY{P)`*lCcLb)x5wdKkXO^I){2ajlmT;sbVva^#QpYYvhM54^i<4)GM4)iy z*@ll%IJ2kh(UO(@hpICE+>M^I?LaIS-h?RSaK;#SloQpU9?VyD&Qz9Qef~6Z_5QSt zYMT`2JXrf#Id&-(mJvE6cGQ z-{2Rq%myigy_;#Sy*V_kJYLba;&nc9Khp%rqgd#Aj-MiH&Puq*%5)_J`bB&8oyQ=G|I zmDOa{5y{_KZ*N4nKa95LW4PFF2U@Ysn*?fNU~K2(x-}C2cOLYr$c$0o)vyQNcV$-s?VCE#bWOHyZ!dU zBY`7C{)$|C3b;+IcWKHD*;O-tyOr5ZoOqHD$62Vyvy*7ZFU{dh*iwLeM^>&X@(bx| zbDy-f(u2ReBU4d5;t0PaEhD6Rw%OUa-wcF{9jh&;mxv-o3)=lVi2*PGHzy5%;N#jb zX*{9vQF7x#NBp`d)Ck6f3*8!|Wl5XazUO^L=@Oh%eO=W77vlEJ_IDnY!8CeJvs8ce zRQg}o><-&uAtsV9t>ky5pi0q{inJnj7A+o@EA)%Me!aT!e1#389}|)!duJfY<`Ygn zP?hsJPg#e&wbX-j#pj&5SaZT(d1Msj+wqo>=oPpMK9Z$1d?a&PzYtLdo}q&S<2eqw zp1rpxP@802p{4dbyTUhb-8{hg7N9s^I7jZVL&vJ$2>76Wz#f$LBA&J-&Et5fJza&r zbL+@1%NXrlggSttxwR3$l?&zj=KO>Cd=Ksn7theSOD%)z`XLqg0QWlfUK*1gQY*{4 z+gRJS8gYu={I%$Hjb?14qy^@bQ^wjvV0H_ckQ^I(LTlHRl8!!Zw}q8>)7@k3mh#VG zO!<>*KJIdhfcULstMktx!yk#E{MxCWFhJ+8_rD?$Y7&e$v2?x1gvefXnygjPhm3(+#aP}RShSM8DTjy$NAHCe$oQg5drA{b7tt_Sh$Z{m;AW{8k_A_b2*|0U2fD*7E$`wVbig_mfg1z2h5AMIn2{uj#5lUAOmGd#+cOH6x6dC8$Rdurav7 zf(AjG5h}qgIyZ;Dvcd^LYdjWI^&Fb9BHy{~qWZ6>MPMMW^BCfwpSu>feQcQ`YRuTR zNtZIzMp$>KZDM>CNBTz_5>C907Y$=%c_mj1^FN~IM*}Q1EU3_+Ls9E9fF{tNc)TvX zQ?13?VfpQjPms-IdEQD%w)QJKJ?|1ufbv&!i+sIi;12WgeBTFssB71xY=B2E#y!LhA)AS?d+?ga8G0V^Cti5aku{uaw<{%s3~ z09T#eR0n$Cr=3fBMY1ynuWVC#g)OuvXCd<8@idkt_`MS?QmrHn)cj*xMW~Uw!1M3q z8TGeZ_hg>QxgC!%VtbQ(GC4SS4rCMwsf9)JY@{xtVDZbJ#Ue-kb6!Kl{lJB(P*){H zgjTR*Ax;a(c0&gJog7};k3+e!s#>V%?%-W{{wY&##$uP!gWgxHp@| zW43Ao0oHdaNcZTJ6F-H4ZGpuA{$iSv7lM2Y8%9I9v9-w$zV=fives6KzBNOcS8SZ! zIihCvp!C{K#L=`R;Xqk5@JZ*l8;*6Z<49v|A{aRsUK=u$lgL z!yTQE|9Dj^V$(8kPs~cT4wimlTTs2T++4gG;5tXE_^~* z`C6sRU4MIqKwgZ0KsxSHE{ae$NGTIp;8?qJpJf-wvv67y6{|_gage|_uUUfjYFrL z)=Ega2vGg|TVI{Zq`E3*A}WlmpFOoscdsO#6U{AcnAv? z#55L$@TnpcoP<+6ks3A>V`_yi-{_q@&q*lIpz0ED!I-!=PlKkVWdDo#2t^d4iphC%3 z+Aj=*LVm+0{V9VGI~n%*iv7OP*yub82eCM7eynb_C#q}a#bd>XEvFjx2Oj@4eLl9u z*@mV`bns`W6*pD86=_WcLy9GWhz~p;bqIv~mXY^*%%9D#)|{H+mkcR#XhEi>M(gg+_NfYn+G8z615~utIB49*I$r| zAS6UqNQ@vEPWBp&mk`Y^l-PY|mt$b7^X3Qg0*|Sc0~}w!`k{!aFRl3~n9L?gcIHo$ z^jtpCc4G;tGH}6!+fr~vNQ$}sBgETd(%|m;ygBx4X8!{&hV2rB5o+x3KXq^^mKds5 zdI9KW2ZZ`q-u(qT0RFA7B>~4nYTe%g<5T}php#2aTzg-vbA!;r+lL5&Hv-^%IG+A) z@b?il;%N!?Q_vZnx>?bkd#ETRS-`W7arZ@8d+eY3 zUHb$>@BuAq9SA?>&kmNLlh)86`H`8#<`_5`m;>(WZWU<#OV#UN1>I{Hbp5IT%^}#4 z1hOLB^M8>!-a%z~etLUbog+HIU*fL{+(ICM9KQIr9R9Dy^7JGHAahOtK>-)A!GZ1q z{;3}y0A^jD$E<(@PhHB#VOwA0@0135Xo?{r@CuwM;jj~2pyY@$JOU1-+sb#mQ3qNE zfOroI;3e)WaL+R{Hxnw+seH`ffZ!sCCW1R9)OkLO449AraM9M_TdRE-jA3gS1CmRo zpYUsRefgmpyrEBkVrjngn!^u_*q99>aG&#G8xJwPACFv?vI}3TJJ8qUVJeUs7R=XnB6c)O?%-i7fInx*-I<~ zo&>o*)=X!Bd{cFDFdHpRsGFq3_5CzdD%I2JTjPiD=bKfFK~rG-*e&=?>kgF0af}MS zjojchoV(#(CYO}dwkr#o|?W@R!781ftWmtXs;ksnw5V&HF#)#G|;S@hpcrN z(vC^Yovc{CI5^KUBurWzG2=087oX1KSCBp~c-+7ChJvcBV=2c9@6!_{d{F_GQ@_Ie z5LGRk00HgVm|$6T**ae(nMJ#Pxi$0W%HpzSHVgz7_7#x$sg7xN9HfymWylw>kC$L)P{|efWa8 z54wQ)vi`8g%_YK!F`y6?1p7kV8~r^#Lkp9d4h=`&?(Mm=bBux2XC+AUJm`7>4#T6N zS=aerW|>YD?0M5j7fC zabuhDjV4-@3$p;wInC2YcOFf{n2JBaKS`ZQ&)R?Q2}#@XN0%MtcSE;V-v9;mWH7CME42j$-ffdx*~pBv=*U4pAcZUPYKV8k@M z{@Ez&?88tbmh3W&?{??P2vtiN>D}istRgoGIzqJAZP>5)Ac<$rqb3%BcVR=*Kc$*N zyiX2KEZOu1cUXFpn|?D1GIOsbgayA5cj z7~>^WLbNiV@6}1bg=-AK03U#?@9+Dfukr`R!BgO)_MUSpcYBeKDVloXG+(mtZbCAOlkUe;eFaAWtj8Di!4AoQfX@Dsb_1ezil3(rr~Z)x#DseUKR zN`kB96)%5|r3zO+*}daUBi$+oio4XI;J@gbtz!b z5~M!asQjqShJOs1thR*`VjF)Vzdr@Zm-o-C{6ckOwnpnGNk}dS+SXA0;`3ip>Q29Z zmu?xoJ4ewf6~+Px-(!AT>edbmPpvtlG7@+osBrY z6H|!&JHYJ*(|=>>rE+lBPNfx|mlj#Zdqh?a-KU`_J)5Car?E;lvA(tdwDmTJY!#2+ zoj-BIjX(D}B)_S#rGN|+zxTXS+;5{ovnd7I(SZ9mJHh%I2yL^TB2~nQe5^DQq<+xS=8Rbx6@b~aj(G~w(NrDf-P(*Ep1w?cMoamKiqtKCX^Og zo?0}kyrqyll*S^PM)ya5>p=VTe9t=M@9nNJV9n4%ZK{gj7A&pn&C-$s^d)|q?{&`= zJAJ2_%4#HObxQ+E14w#M#gr3Hf7qb-gZ)kAtS90_ceaVaxeoS7Y{Cp;qp14c9tc$x zM)O@8XVk<7ScF_l zFPH(4cshQN<#b@cqxE0Axnx=k?gob>pJpmQNT{e}?D0N=6FMh~ z(2a|<@6R~1xD>8~BG}&~f`tQ>P7y4~Xa^k%*s0@P*g#Op%IoJGTub3DpI^6ia&(jq znE%YhoWK7?+k}PEwqa)`6YNF2^%#T|oWT#>C`;^WgWT!bi+r`U(O>p;2)%QS>Sb|B z1ZwtayrZ|OEP#m=UkJPxaD;6M)?NbF%n@7s1~3b@6>VuISsqO;vff9rjgW#lM&B4o zWqsqOXPoQDDSzau_3?+D+}7MQ93*a{Hr=V3o7|z_1v?Etmmmw=J3AU=SXVwXPn+9t8N8^|>Tl%BHdAlnLhX<$MR#>$X}t>pL2NjR*`-HVyKB>TaJTxj;mI)Pqar zS{zA6n-9|jsy2JyuW;3gcKGVjSj@__;(rKN0+*@rAD78fWW5*^s@8zA-ruw|bHD+r zvsHh%?JWo5pOK}BH32)izjuum$=tZg!xyw-DV8PoehFLy>p(H}C$0U^mT+P$a$n!6j`pWukh(meA3yO| znBO~f(MgGIW^hY#8g#Jvhrk5WSMZ5k^=}}8)EL8pkVkZoT z)LKHGcs*UdlHyjdQn35MOBdFb5{2kfk=6jv^G|qv0r=c{o0%xQO+RJJMheaZoT$Zo z>(x}SpRAype$P>JC2JMLsM5NgU<&uE-_W~OL}R7>(&Fi{Gq1NOxh1&8N$8`L8CVzF z5OR4H%4WaAUS__+`;Wds842w0cc0fzMI~Ev17UU7=2PZ08)`fJAZ~lG5y48>mUJ%u zrL7*mIQIa^wF(et!}g*1Q@cZtbUXY|S^Mz1PC5n@n5=_~8+DdyG&d->fiPLWeQOS( zWtX2<$$ZQG+e;$tsg|}}F<_L+4PtEC8M5m--;~>!Q-Sz7aCMIAmz)wKR z7Zpaz<~Sosh6czbQ284nHH6xhbjSW&tw!bbGmH+eCS-iBwGEtdWqLTf7}Q&Ner&%2 z5PQ2OxK}Un{Qu(Y-x~>M_G|OsQ0w`&E#Axo#GB`-;>~3aAofI)XuRf2)oX^n{C&jy zS1%Aa>VpTXAG`$SryxBDv}cN={uZl;`KugfInwoobf!jY1jlM@Kd~#&j9%Huq1L(9 zXUNA;n;QOvBdL0Q0-zTCDB8?KD8Wz%+ykIhHX8$zlrdFIiDaj zV@VCo)aMt0EOW!u9<(Pj&Q2})r<@fHl-<>ll`2S{10ho>o{nISz$PR;>qKT7Id4ExAF#L~}f965| z2t>H10xRpErcE?vd}tRhE}?uNtK4ainma>Ita>iBFgV>s$uRm0=7SvVS{8?GS3+-* zknrq;HZAymduqJrFW8f@O%T(WUS8Js%h`~0$D?Kj|AN^W{x=byC-QUT&^fT2e>iP2 zwbJWTLr+u_C~Xh{jV9ZoW>J(#yQQ8*iD2BqML@wLw_^cYS@Mq|8 zivtQv*n0;1j1yv(rj$-|%?dy=Zg&pfl#P1uNzok(T6EIQTWrE}A*$M_pW&ZER!a%y zMsN}6F1RR}fkha=PCg9SCeHWJ68#&1mF%K$bhWAQW|0M|7^(5S7^+5T=GG@L+n->g zI)hhq=?$Ky+w>fFZ@)cUxaV4F2#1X23uk&4Bj`>jKM&2b5m37T>GP7Yd zv47HYkLefdM^29;is3_6`1g-jWvY>Dy9L1k8HQUr3R~$JQ@s9Xzm~1lrTeF7H{0)J z+*1B$ZQwEgm8Y<&08xdzpCqo5?}Ih_`=UwkI|x5`!-sBbqwc6uCQSi4aYFfIG+wI{ z)Vxc_7JtgLGL65TOw(QUp5sr&^(FWZi|tq5e&ZL~1Qe&~*?FCu0Zra;{jRJ#T>Lu) zm6%Ed@$$iAk(}YxM~~!P>1J$reFw1!pXx!L5U-8m-I{}g0tMf~sw zyOXEqR$1Pktu=R^&Y$ZMFRBJ}|6^$LZ)rf@W||dZ8$v7OgkwxKL{e{r6lDcwOqbRhUbJsd%ZkC?x zJNG8-_(Pc!)eTL4+-4HN5H$t}ZN&hA18AdMSUBK%aSPz0e|_j2U#%a?eSZH4ZOO^c zu_g|lw~$qQyIL~+Yc;7r5bVQU(tSXQIc!s6s9{yAKv1gmAm}0y;@_qQqe>M3nhi-N zf;>KsKt0Ps=>De6I95R@Wep0+v*k==dFhfd30JGlnSOYrPsW#jIiUxfa`=My=hoySel~}a_`hY-VTtG20HI=V;-zu zLr{F7@fTPec#so+K8R>n#etE2|Md$!wpVwSl&kX?!Dd|slaK~%?4_Pq#1%OM5 zqO-mV-cL1+6(m`KR^-o72iRBC2r`*c%rYk)V*e+};}umFZ3 z>YM<@hM&Bc29>1x`}wft0J>)(New}$-ChU9)!aLyv>VkgH#)ENCXeyS<4ZeKK2lQoxeXE6JEhRBDbvM zaNw#{u}x64%9zVdB^1Ltid5ddc^KZQD-Ti=*6vMKcZ@eya%rBUFPUisAJ2Ag!#x&C&DcK=avLs z=hmM5wUkuAAp=N;-@XQD&_DM11_&sVEdKB3AMn9HCnp$8j^Do~2LiLBe77nV1O|^N z=&_^%sj{BcI#zlwCj*Rn=V-F2$wOWtwMTp4TyjfWTAdriW7{||f^7c-6?qmQ9bNY~ zfccx=T>*-pRU}y>m6?dbdGrx4G!;xu_v}_wQ&$l|V^qzvEHEfw*kyoj(ZE(&GBF&N z0!8K>Q=HokLD-V`|AzERxNg9C;Hr(1dWjc-_E63U1e!9v2O2BEzy=B*%2_&fnRIDL z+gdm2a;Mv0FzFGGe!;akCQRD3qcK$63jBH|DsPqW*aj||U}Od8@(6Gka%lYa&9@+k zeFZ+x>{bOn8e0JPc5ErMbi(2`I9UIH`-~D>KD5w`^Jov0WnT4`@zAZ9*Zgs=*DBKS z^4%In?BirGf%bbg9RLam6wZ_AljTG9c61e;g+;ALpHng6rA zfdj}-If3sBR#ld;zTn`uPd*~}T58^Gunv0A*00TDcU9j5=6%TxvDpLL=m{s$P&f^1 zV%B#7YJ(~Z36K=Ob6`yVk&~P#n&1k_Sww zb;@k~$zJ`pnY;w1os$q9%$JS>XdwSFN!vq19hn0l;#&jesA|bGU!ht)bGB%x|CD~` z^F>ET0an2wreYmgE{?CQJ3#&cmfHEKXaQe`dXRc-9vj}F-8!cj;Zdx~Fs;58cJ1O- z_MSlH7wJ_nu!{jc^b^v9go`WWh|mZ#sE3x^5Y5x@yWCg1AN!c=b3vf`>2;tXPA%Bt zdR_}!rwlB)gqz|&7?*%Q;n9=XUnn2{m5_S&()Y8@91mz_TM_-~(ej{OJat}yX@B|W z5(AqGDCuedCEaZyT^R5K)cioqiuzUP?=K_&T$ruf_?GcDzV!#a#a_IIgjsFU(^=~x zUTkG#W1a&`QUkz54i;%&u`L+jm|a@JP&Lxl#dDVU1KBx%niqJ*zX`3C?|vE^XP{5Z z+TaKU0V!~Vda1dYgL^6VZ;^w1k3S1rfgu#NHKW_^ARQMt@Gj^9dnUlo?*?H+ITzK7 zzTj(7!fp#k!=WAF8QFHy^7W56C|G}{|HpZZYJUgco0(vU>#jv zX7&r4N7VSR1Uuv$EAVB0`gr)rk5aR*%*od6RbgNw-5;`8X9gc0>`G&44kw_7Z+LM6 z*-4AlWhzc{HI)twWEWLECupvbYGSR{TP)F4 zf`JjI?1is@@3yRoBm!G7>pHo~bEM*ab=6U5e(-+9ffwWD1J9gp`r7Y|wh?xYQ?E|t z))An-D*KC4F{sNdz~zd^+0; z{Y^|#^4ubfj(rDOlZV7kFEt~&15ak>w@}wNXgcXlUFm*Vk(Gg@wDl_pGSnek`blYbghNAi^;?(sG4_8}Zd^X%7BY9w^ z_!>;%KlKs#-m8QOi-TmB`K&N^9G8aob6Y&8bHvrwic9fk?~?=tD`9@^bl}iJ67_-Y zvWB2Ua-=EqdyQ@F*9qK*Vn3xJ?7oS_<5_G*AOWZ zL*`(L+FRa~SiUEh>D5lbyUZ|;y+yS#56tP2i`yGk%i`Dm1=bBN2WS?5b5-}s2df_$ z1lsUXSAYCNzccg4xp=6%1#A}92<78rlzqy1pmP}emIz^r53*M2bjuauK$d^j7j*BZ zalLcR`hjW)T3>uBny>ym`kB^GOO5pkPlBftkSYWUcQj+e(;Af2Ta+7B`>^E!;Pz`! zjG$CmQ*Hz*WpOl-S}1BthLlPwjmYd)Ih9Dnki&hjn?BlL=kmbFZPcjc6!KdZMadnz zIz-X7xCA=R{pPNvZubPutm92*0HTOervFS12(qZ(pa z!of7$`JXCwM7#|l_g7FU4p!qOp6yAdPBi#;DUFql$XbnK@rp4n#~;HU%ZQ&nBY1Rg z&#tA<=&QJ22-ILRiXvR~0QgUyAbs$Y@}jr^BU2LkDC>2Z0?k3P;C4=2KbY{;9WHWm z4Ae_(J>c9Q54gG*1M0l6>0ptiqK1I zD33R}b3QjoI{B{Lhv0!qu>N6KVIuZ}IBPvm`4mr7SItQ=t$kmMXFkNaeg;uRqyMjO zCJNq+@0*w4uyWXtnSr*Uc3Ekn=E38S6C979lN)03!s?^p%V zA)b=Z<=uU5^#`_11*!OJkWpKM{O8E|vx4r9gMk!YTV>dW#mdc^j?IEwf58--_MeJ$ zpy{pz01^xftJqAfknr}D>Px_fcbseA&2Vj>oa~zY;P~G&0sWc^k)NySx5-MOs-bSr z7LGbwKzHXSO0_kg&%kf__ecK5=l)(hiY6bCw0=s%4tILZ3#e11cRn{nI{Br!bJ{A_ zHa($T3(kYSro1a*MhniLkmd{jfu2)iN54K#d`pX@K@X|3?LM*#W(E8NZQA2>7gcAE zbfR7C1me^<(Sdes+y6l@X=?pUz@&KZMPGl!xl>yV4okMqluw8?fpa7{)53Q%G8}#p z&ls6(9S(C(I1vC88v-xvN!X18A4un#aB3>Sr+y{+W#(sSWyR{r zL6#gYL5i`0%;kwX#3HQV87gwA^4Na17tcQj&R9Ryzl(mjbEV0X$ExSvO7F6QdvTJ> zevXfk9&#Ktw(GTXVG`&o52d1$vK+m4`i1d&2w5IQm}e~J&*|WbJEsX%g<>}z-PGEV z6&xKJc)W;Nt9G}!CP(O_lHEu43qhw2&JtRaqwO#&^^qXmu@gtyjUkv-?uFMtw?XP6 z5S)%dyC_n4V@((FLOnB$(a)b5>r;A7N#>i2K-M;&wm&fdn=9{r$(vw%l_VDjq4?>%!X0nD$nnWaZCHD2%L9RP97`QX9 zx2d#%>Vu7L&#*$qcj7`d0b1J9vMZRi8O1L$C9T$=-?+jhB0X4pJ7*r}Mb(5Z)TTey zG=Qh*G{yN^fig|CcCt~%6sM^5t?8RL#qZu@kzY`ANfp6MG2&QS?3EuYl^>5eS+-xD z?_3KXF?@PS+}Jn|VaeMTX6%DwX3z|#@zL$N?xlu0 zh`}SeJOvv*+CD4s{av`Ig;dGB%qbW>qD1P<2jv584&ukMs>;v3C@+t@v0v_^<=#zJ zv-X?Bi!Z$oeP%6`JpLX(2D?Te?g&(pE6%46z8#gaV@ zPI=y!3f`Pu*gK==#oZ#yYp9f4KT>%hk8|VNXT0Yw82|ZWGM;nEJ_f^GWNUO38~DrE zCqN9N6vuAA{>3LG*v`kNx;ttmmg|x7&`wxfSv9RL!g@1g1R7iiz}9Fv6y+wb{UXEK zv2D^5y8W>1zzXe$A(x;zGzl-8h>x_=cWcwlfvIwy7z!qJ&&GJ>nR(-SR!+A#_CJZW z>Q5e!1OxFCYcj5SvsXgiTb)V>Pexj1k>(Hg7&dR^>>A#3L)M%tmra6j1n7~ z?0TREm5RS081@jb7~<23GhgCt+ic>4{hP?IUIpiO$%#xP>(Xf>>wL81(YCfihKEK% zC2yZS_eqz91*E?-NJ$sF$KravoX747y?$_Jh$5NF+wHKdrE}=YN0-~Qt}27x48%2; z_qlm86pcJXN-0Puq~*apIlkDH*VgzHrG{lWoN9(VKYuy7hokwT>?k3IQt7rlR)rMa zsFQEnST5;xtokTRppZ6gXmYxXa+DZ84Gf?oXY`TYPnLF+K_pE@!|(p3HrCUm{*osC zTk>OEEyKn6qz+AtUdKr4w}+b60!e#zzp8epO+^rDZ$Td#+5(y?>LpTQmc6+8DJw3K ztDDean!GmmBqfhe<~{6@Gj2{5k@CSNKtt}7CPwE?;^g;-4Za7GZd;wqR)PrzlU&SF zVBU_wH}mXUP0fn2uJnyz2N^0NkFjDa9f1ockqfnYOf4V@rudLT`;85)r+My2@^+h@ ze`9xR^k-ltSh?U5ZA5DU^p$EFYQv8pyGETL7h1O}6UEeKv=SgvBe=AM$ zkWV*%c+`l416k`1>o@VR@CMd9@>Qr7)OlsVZ41v)aKJ`WJr7$tx`1Q(YVR|PSy}Bq zc0gFeEF*RSla0NCJvg5bHnWr`G7ZJ`n0+~$?|Fv*`{??pEGK+qMF6c<nG#EThjo9*!7& z>`_`x6ARjl^QGqbdSAB_ep_T;FNTQED;ye2erO+v$10x5?Nxtc)6omH&_;;qg~5A1 z`$jT_FC}$b6+CSnj+YjQP7Abebi3LP%DO`rHfmhoO9swH_8smG6>z@j!vGt{4GR%P z5@M#0coMKrPGDZh2S`OcGb2Tu3%68`QL3%|5^7|1lCD7s#wEK0>ve}RDJt$j>0Z6t zG}sWQKKL;FoZOP^%*Ad9J1fO{za{&EO8iyAt={UECzW7DcFfK#Ga};cEiILe^o10N z^-M0&r+QwJoQ%*0MX|@KwHl3hjqqEj-n#IO zG9E+qn7V=>S%4dST)$R1k8tP)r`qYXWMrWLVKKkEVyd>BTma<`AX^s?y;qmnBheo& z#eE&4C##$y-%8IBnmHnFY2viIXClX0ytVgL*rS6odS}=d`E_)Rr%**?TM)`R3se># zfMc#~kmc$mjasq2nY%b!XUXI5>K<_y?`SrV?N**ky<=AP{YpMtAbHMIAY8zNuN?DT zg-;m`jqFcC^FujV;?GTXu^cjNw7PZ;nRj!FlV2D!vybt`NAcWu=?1oswT=uq>(G}w z2-`YJ=4gfWy2YvP2R(~)HAl)6Voucr2x%GU#q*dZVVvueluyDC*?a*{r`2GW+l7fQ zo?7mEB6c9TH=FxZw!`z3GT6Ga@?#cU?iq)XloNB0iMBM5)^x$@ zCRXJeh;@EFc{qHPZTGqOL0LJPmwfM*R&r5{gJwD-wsY9@6ZtRqOO-jkyKx&t#j3nf z7g`aj)&vKh^%=1=BXR@Z(VQ7OKwRirymF+PZe&vo&{27 z>Vp>wAp4}aVa?u0dS7;pJBwZnhbnqk8ynsO zl+8*BKx4_)d)P$l$!tw|Q$(gfE!PRQ@WJ@+1P?>ng~swGx^U|{;Qcb`8|g+tPfsPkZ{ zbQ#g5mh0-cb}+<~IoEK`imm+}pWu0(3(SZf;j&}B-+nmM3tc1hRCJt%IE+Y_+!+}o z+zUhHtag6Z8a9gXs2AcjbQpe0R`qsFSkG;(fKJu$OGTZ#rlUxVegvmaPn?Z;2_U1Z zPbKn>@fp}V=5Z_1$Mkjv1f*l5q#C^>r}B=BJZTU*!}9V;oxh1>c_?l~`{cQSg*uLs zIQIv#XptX^ysUQjYbnBQxB`j75R8nkA5Ow@`BRI6OUFQ<+>B*Sae_H3r7@Z@1la zC-UCH6_&u&e1Zm1`Tk2ugMEhlR=vuz^+-3@22h0ZBL7rPvEfQw%pTigKkRRAP9F-> zJjfdxUE@wz!-JaKNC{)STHFB5#>NNn5y<@e^C8O9H0FN!b>@q8`!_6~{LuZ_+lh4c zlJY2JWfUEkPiYCsy73uST5L%^-*=qMx|~l5JRv21-L0`v!FxnmyWmjdE?Br+D1T{l;t_I)9AIm811%z9a8mrD*MI56LYO zEqP8jD?{Zh!yTzh@C1MNY*N)aP1d>i;)lv4#Rb(;_sP@C0<08V8pfcr!Xi_P?#;WR z#F6hG>KW8Mc~|Ael_s?>HrALQVyl)Uxs8SdMcX1zzUUzDKN0id1bLE@a5HPXoa|vp zZ_=bbDR`tj0RgkxO@BuoS{|p#hu)Q>PS)ndThivIdB5z^xg*+~C0*R1R9PP`qA7UD zNJm%Wl6jW-r56p^!q{9!C2=X3W3Q{^gQSZ(NLjfwrkCyliG|X!r0`osUf)ukW?whc zD@|RV*@pni2QOaX^(ODvO5g4gvppWP(!2KNu}jMr6E3dROrlkJ#`yHHLK|or{Y7UW z_e~i>>3ABy4Nd#BSpKwFBBIQ<>`tv!QfD;0-fl=Dk9>JV#Gd^aX6)$1F3>+r*ATc$EOmtO@}E=!^1taF zKpjfwRb^CPJd^n6&1X0E%;`cO1!>7;Qcp8V!u>RF;Vmzlocj`)q{kt?fVmv0-_Lr6 z$Pv=3aSAL-}C&Ik8J&8;S^cmk+0FGYHfMJujXQ zEUI|do$oQwFBzvrtG!USOV0E!SW!o6@N&imjTt_?BrN}|!CK2VaT4O&;LA<_2Wd>U zbGa32{+##RJhlAgkKMc}d-9&#HzZaO4Gm%){ZDhiA*sE4$1IVYS881pMhZPCi2oj_UV#&PLd02X*6TX)0RrI z{Dtr@X0`$1l|-7!!{Wo|JT-2&^KI+~=3{nC^GE!I)<}-lI|=&!T3YUXjV_C za6toy(X~;--|%|jO%8AP32gqLXvHNm*}cyGZR;pe@MM&+!p)L}(2u)r03qZ%E|Irh zW64S*{d%>98%?Kan1VbHM!J3M8~JL z4oWXFcreOSmx>2!#_k!wSXB*B#cD3T=QJ&f`u-vxK5BFiUJCV$6De7EBg*Mt8*Htd3^LIxKc0f*#>^mP zhxnDQ)aC^~;gO!&+>~Y6%eP~9_61=s_Tk-gOeZ~FizS++RiHh1P6@;?1~=>Q*(X5J zjWs?4dDfn}CiT`1RusEBUWI~{Q7acvSnq4<6eXp3d+B>eZrMAWlkjo*q51mddV<;G z>y6Khb@DXwFDxjwzWCG{`yE@#)$FbG7c3K|lRn{Mvu?%XX1Zp-h@3m|J!Zt-AUyi? zWMo_RE!Yh05c^l_WL;VrPmdMpIZqD=oR{3UlydI3PJ>opoC5-#8KTV7w+05JClAa* z{DU!eaeRyh_}M+HwPJ!nES7I0$z{`4g5a`JbjzR7*FT*Ng9jp14%39scah88W`@o5 z15>|38D&G-ob!Y}mcb$W(p)9i(}j5P8drASwv@ID$%+n>tXU~MI&IkLsTiPSL$gD3teBXZ;o`6>^=!xctMA|6;Pz@VZSx77YE7??zX|-ot4;abChoB ztXNp9Huq5IO#Q+-Ltf@SW!Wx>G4e#oeF5H~8S~}JT(hb8JkAxhM6=N5&Gwz230zZn zshHszg{zdWRd}ffdi}HdY&Q&812eckwWOu7bXP2X;EJ&z(lW1Bwe$KsG-_Gq7v5Q?3>%Z3+X+0Gb^D>PgRpF7;EZWkiR!lhQ6z>o($53iDv;)G}&C(}j zZYE5B7(falJ?&=dlpom{o5f@8x;=?uf{N?A{Z*&!fr%Ib-7IO?@E`jIfT)Bg(u9r| zsPnL#A)_63@Q$sD_BmKYuA87)l-hhJ=<<{eH&-ms&9zW!ry$r0_mt-9&4a0Zcc}y1Hq_w>E zD=Z$YGvultm`b&wNuC$Gb7nJVZ~jfU%2=aM?{CNo#RL=$;zgigrBk}E@6P&b8_3Na z_8g~&>s<7S}NlN^u1iuVh!2}Wv*gSZ{Xoxb#;X$WQxzsjqyaj{9va;MMG8E;m{ih0) z%QN9v#}XgD+P*aDiMX%wC0~4`y{ktmeTY0-av7@eHecbB=khJoV+`6Ak2vi$NOkBl zqNjJhpP;nvCYQ$I`Z15uHsGMWSK`h_tzu!b++fS==cY9>wLp4D=sh&`h}tnNZN4f2 zqV-(mfoeqe4>q@R-QlHktmj7LSxp&GggSFyw2HfB=gmC*kehymfo;FQJ>E#di^DV( zZvYo~7&Vm$9WYh6;q+jtgmSgVZ!j7nJu~aGEAE~Qsk-`ThYs)IN&E2mWCjh9D3p{HP549FtCouS~<`}?#Cemp)Pmh0Nd_fc}wlw`jlO_(k})VF8N z`gY7N{mYXV2|?zMhjUCK#2MP#C#1D_u0I+Y8eKQg2s?zI9Vu%Ua~qUCg1*t`{TcCv zUK6?;Tz|0;j(Y}u+Vhc8nGPn5?iMM(u6gW&jN#>?9OHl$xknE#Z8*!1y^vow=uWxa z?c-mRkz9B}$SV5Yt02?N=Ac*hi5Z&tpEGpsUij#Q)I8CKS*OTWPr~mHIcUuor*!tb zW6SP;ep4pDm3WxKioG0jA$s{GVd_Di*o-42fNrb`cjz8gzk2sLf&a;r8GXr%+}D}Q znYpe5-^ktXqgz?C91*%o8g|dpy?jO(GjgZmHeYSl!6`cvHnLGzAWi*{QJF2e8O72g zjt=9zWpsbFMFR8eQ+}w?jk~ABX0sz2ZUJ&ZxOBWwTSu*%cAeYcbvgP}@0;q#RG{WN zOmdsZ8hNs`R%rXI^UGb4qp*ksiRA1{!Eri8FsF>?*+P+_2Hdo~cUhh7&X18LEM2o~ z_QiYpoe$Td>GC{r4Fz*Cql0nm?ND=K*H*~22^RaJ0`o=dbt}J>qAy%n8b45GeOvxw z!09J)>x<#A#hD|sOTH&8ZF45*rEXm_gBAu)w3voB7j;S`)FgN^X4~cno(}w6zbZyA zYwi=h+z#EE!Yt9Y;|@?bQaJCsla`OTvu93BzfHTet3JN8UcmBYElg7~ZgAk6PWEJv zVS=rE(*uk>Ush~`)I?l8NB_H;dN-$zpqUXgr3E z&BQ|Tc1|IhhyV1@^2B;80zJ9z7$y8vY5>Fg`9S5q_XDRt54j5%Ub<6t zB@4tL0`!L4S|3&;jU6+d*`1guHs^X>bnF4KNxXZA)8itARmpqZU@(lVbS!J5N$GTx z>-YDHArETcC3`rZ$<dNw8FbUIc zf%%zS-}oc@?C~@(fzboXc@+L-$y6SUS!aHJg$Cx(uIO$d*AR=fiF#LPjzkPUP)H1Y zctIL>3l|e8^ytx(>~XFoS+dOzC*JoAUdF^YDOzvSKm@tacv5UQOnk&j?eZIM7)^U= z?-=pcv)e14<=Pc*G@}~FhIo0S@A0P>6~4o;SGJ$YHag>U8{R0E^yHc|tR)ZT7T3so zqOkq-Srwxv$uNYT{oVEmh?HeY8HnBa`t4_9dWy`PFsj9Is5|t{vq_)Xj1}Lq8e=^= zr(>t?KYkawf*_B@cJ;nz&OPGf_)IX8P9G@Y!yCP@7t7)fyXPH;UQCRB3WRu|J-(yR zrGer?e`CDyYpsnlj~%&vGdwTPiW-#W#IRaStYcTO_l2(3%qn*{LVv*~zp^(nxd~zy zmIOGLXY3YrY;xTi4azFY5RHUYJA{GdOe{sJe!Tq3%!q@APWLUDlRCE)fAIP*l+m=Z z+^sY&6E#y_KyjJdNpSC5Fi963z4p2{cB&^(?m>mjuxp-Hx>=7FZPJlY_#z*clH2@;=hEMDmPkkQ?;U(Sn!IO!7N4`f z=#CG)CY=>p`%1FEZH68euz7qTLhuy(%raoItLi=vUM!6M+{qDrAcS_5fW2()`pW1< zRPN^%$kUQz<0;dpoY6UvS#)Q>!Af@B)P5uAWjqr*QQMH{X$mUPqP+n}f4k?1kxPzIydVf0sozXYC;^bUJw5o`X>i-BL%H z4h1}de=EPQ7u6bxgz74^Oxl+&y|9a$Ab#QbaCLZ(V&>zoubE&g*h_E9@J_e$>At`E z+6OZbD~;6@3{~)@F?D?Q%&wS%jQMC_P&c9#cPMqti$SQ5yC9jp73QxrzN0zjdb({- z#gK;B<*TPf3~aT;9*leLRw{_eyE Date: Thu, 15 Aug 2024 11:40:53 +0800 Subject: [PATCH 11/11] unittest(service): test unittest passed --- huixiangdou/primitive/embedder.py | 2 +- huixiangdou/service/llm_client.py | 4 +++- huixiangdou/service/llm_server_hybrid.py | 2 +- tests/test_internlm2.py | 3 ++- tests/test_llm_client.py | 3 ++- unittest/service/test_llm_client.py | 6 ------ unittest/service/test_llm_server_local.py | 13 ++++++++----- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/huixiangdou/primitive/embedder.py b/huixiangdou/primitive/embedder.py index 9c3fe6ab..e04f1bd1 100644 --- a/huixiangdou/primitive/embedder.py +++ b/huixiangdou/primitive/embedder.py @@ -68,7 +68,7 @@ def model_type(self, model_path): def token_length(self, text: str) -> int: if 'bge' in self._type or 'bce' in self._type: - return len(self.embedder.client.tokenizer(text, padding=False, truncation=False)['input_ids']) + return len(self.client.tokenizer(text, padding=False, truncation=False)['input_ids']) else: return len(text) // 2 diff --git a/huixiangdou/service/llm_client.py b/huixiangdou/service/llm_client.py index c9f916bd..d7cde265 100644 --- a/huixiangdou/service/llm_client.py +++ b/huixiangdou/service/llm_client.py @@ -214,4 +214,6 @@ async def wrap_as_coroutine(): async for text in client.generate_response_async('请问 ncnn 全称是啥'): print(text, end='', flush=True) import asyncio - asyncio.run(wrap_as_coroutine()) + + loop = asyncio.get_event_loop() + loop.run_until_complete(wrap_as_coroutine()) diff --git a/huixiangdou/service/llm_server_hybrid.py b/huixiangdou/service/llm_server_hybrid.py index 766b74c8..e38d782f 100644 --- a/huixiangdou/service/llm_server_hybrid.py +++ b/huixiangdou/service/llm_server_hybrid.py @@ -443,7 +443,6 @@ def chat(self, prompt: str, history=[], backend:str='local'): str: Generated response. """ time_tokenizer = time.time() - loop = asyncio.get_event_loop() async def coroutine_wrapper(): messages = [] @@ -452,6 +451,7 @@ async def coroutine_wrapper(): print(part, end='') return ''.join(messages) + loop = asyncio.get_event_loop() try: output_text = loop.run_until_complete(coroutine_wrapper()) except Exception as e: diff --git a/tests/test_internlm2.py b/tests/test_internlm2.py index 15c276c1..f06170f5 100644 --- a/tests/test_internlm2.py +++ b/tests/test_internlm2.py @@ -21,4 +21,5 @@ async def main(): async for part in chat_stream(): print(part, flush=True, end="") -asyncio.run(main()) +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) \ No newline at end of file diff --git a/tests/test_llm_client.py b/tests/test_llm_client.py index 1b6caeef..bd0717cb 100644 --- a/tests/test_llm_client.py +++ b/tests/test_llm_client.py @@ -64,4 +64,5 @@ async def main(): # 运行异步main函数 if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) \ No newline at end of file diff --git a/unittest/service/test_llm_client.py b/unittest/service/test_llm_client.py index 9447d84a..db36e406 100644 --- a/unittest/service/test_llm_client.py +++ b/unittest/service/test_llm_client.py @@ -1,5 +1,4 @@ from huixiangdou.service.llm_client import ChatClient -from huixiangdou.service.llm_server_hybrid import start_llm_server def test_auto_fix(): @@ -22,8 +21,3 @@ def test_auto_fix(): assert real_backend == 'local' real_backend, max_len = client.auto_fix(backend='kimi') assert real_backend != 'local' - -def test_llm_client_stream(): - start_llm_server('config.ini') - - \ No newline at end of file diff --git a/unittest/service/test_llm_server_local.py b/unittest/service/test_llm_server_local.py index 4946370f..381f6e6d 100644 --- a/unittest/service/test_llm_server_local.py +++ b/unittest/service/test_llm_server_local.py @@ -11,7 +11,7 @@ PROMPT = 'huixiangdou是什么?' # PROMPT = '“huixiangdou是什么?”\n请仔细阅读以上内容,判断句子是否是个有主题的疑问句,结果用 0~10 表示。直接提供得分不要解释。\n判断标准:有主语谓语宾语并且是疑问句得 10 分;缺少主谓宾扣分;陈述句直接得 0 分;不是疑问句直接得 0 分。直接提供得分不要解释。' - +llm_local_path = '/data2/khj/internlm2-chat-7b' def get_score(relation: str, default=0): score = default @@ -25,7 +25,7 @@ def get_score(relation: str, default=0): return score def test_internlm_local(): - wrapper = InferenceWrapper('/data2/khj/internlm2-chat-7b') + wrapper = InferenceWrapper(llm_local_path) repeat = 1 for i in range(repeat): resp = wrapper.chat(prompt=PROMPT) @@ -34,13 +34,14 @@ def test_internlm_local(): del wrapper async def test_internlm_local_stream(): - wrapper = InferenceWrapper('/data2/khj/internlm2-chat-7b') + wrapper = InferenceWrapper(llm_local_path) async for part in wrapper.chat_stream(prompt=PROMPT): print(part, end="") def test_internlm_local_(): with open('config.ini', encoding='utf8') as f: llm_config = pytoml.load(f)['llm'] + llm_config['server']['local_llm_path'] = llm_local_path server = HybridLLMServer(llm_config) resp, error = server.chat(prompt=PROMPT) print(resp) @@ -49,12 +50,14 @@ def test_internlm_local_(): async def test_internlm_local_stream_(): with open('config.ini', encoding='utf8') as f: llm_config = pytoml.load(f)['llm'] + llm_config['server']['local_llm_path'] = llm_local_path server = HybridLLMServer(llm_config) async for part in server.chat_stream(prompt=PROMPT): print(part, end="") if __name__ == '__main__': + loop = asyncio.get_event_loop() test_internlm_local() - # asyncio.run(test_internlm_local_stream()) + loop.run_until_complete(test_internlm_local_stream()) test_internlm_local_() - # asyncio.run(test_internlm_local_stream_()) + loop.run_until_complete(test_internlm_local_stream_())