From 9745c47cb94e81d8f6ee1d58a40b592cabf36498 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:50:35 -0400 Subject: [PATCH 01/12] ci: Add mypy function type check (#130) * ci: Add mypy function type check * fix typing * fix rebase conflict * more typing fix * fix type * moreeee fix --------- Co-authored-by: Averi Kitsch --- pyproject.toml | 2 ++ .../chat_message_history.py | 6 ++-- src/langchain_google_cloud_sql_pg/engine.py | 28 +++++++++++++------ src/langchain_google_cloud_sql_pg/loader.py | 20 ++++++------- .../vectorstore.py | 13 +++++---- tests/test_postgresql_chatmessagehistory.py | 4 +-- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8facf43d..509e97f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,8 @@ profile = "black" [tool.mypy] python_version = 3.8 warn_unused_configs = true +disallow_incomplete_defs = true + exclude = [ 'docs/*', 'noxfile.py' diff --git a/src/langchain_google_cloud_sql_pg/chat_message_history.py b/src/langchain_google_cloud_sql_pg/chat_message_history.py index 76dae6c5..4ce9f5f0 100644 --- a/src/langchain_google_cloud_sql_pg/chat_message_history.py +++ b/src/langchain_google_cloud_sql_pg/chat_message_history.py @@ -44,7 +44,7 @@ class PostgresChatMessageHistory(BaseChatMessageHistory): def __init__( self, - key, + key: object, engine: PostgresEngine, session_id: str, table_name: str, @@ -77,7 +77,7 @@ async def create( engine: PostgresEngine, session_id: str, table_name: str, - ): + ) -> PostgresChatMessageHistory: """Create a new PostgresChatMessageHistory instance. Args: @@ -117,7 +117,7 @@ def create_sync( engine: PostgresEngine, session_id: str, table_name: str, - ): + ) -> PostgresChatMessageHistory: """Create a new PostgresChatMessageHistory instance. Args: diff --git a/src/langchain_google_cloud_sql_pg/engine.py b/src/langchain_google_cloud_sql_pg/engine.py index 70eebbb8..02ec8fe0 100644 --- a/src/langchain_google_cloud_sql_pg/engine.py +++ b/src/langchain_google_cloud_sql_pg/engine.py @@ -17,13 +17,23 @@ import asyncio from dataclasses import dataclass from threading import Thread -from typing import TYPE_CHECKING, Awaitable, Dict, List, Optional, TypeVar, Union +from typing import ( + TYPE_CHECKING, + Awaitable, + Dict, + List, + Optional, + Sequence, + TypeVar, + Union, +) import aiohttp import google.auth # type: ignore import google.auth.transport.requests # type: ignore from google.cloud.sql.connector import Connector, IPTypes, RefreshStrategy from sqlalchemy import MetaData, Table, text +from sqlalchemy.engine.row import RowMapping from sqlalchemy.exc import InvalidRequestError from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine @@ -305,19 +315,21 @@ def from_engine(cls, engine: AsyncEngine) -> PostgresEngine: """Create an PostgresEngine instance from an AsyncEngine.""" return cls(cls.__create_key, engine, None, None) - async def _aexecute(self, query: str, params: Optional[dict] = None): + async def _aexecute(self, query: str, params: Optional[dict] = None) -> None: """Execute a SQL query.""" async with self._engine.connect() as conn: await conn.execute(text(query), params) await conn.commit() - async def _aexecute_outside_tx(self, query: str): + async def _aexecute_outside_tx(self, query: str) -> None: """Execute a SQL query.""" async with self._engine.connect() as conn: await conn.execute(text("COMMIT")) await conn.execute(text(query)) - async def _afetch(self, query: str, params: Optional[dict] = None): + async def _afetch( + self, query: str, params: Optional[dict] = None + ) -> Sequence[RowMapping]: """Fetch results from a SQL query.""" async with self._engine.connect() as conn: result = await conn.execute(text(query), params) @@ -326,11 +338,11 @@ async def _afetch(self, query: str, params: Optional[dict] = None): return result_fetch - def _execute(self, query: str, params: Optional[dict] = None): + def _execute(self, query: str, params: Optional[dict] = None) -> None: """Execute a SQL query.""" return self._run_as_sync(self._aexecute(query, params)) - def _fetch(self, query: str, params: Optional[dict] = None): + def _fetch(self, query: str, params: Optional[dict] = None) -> Sequence[RowMapping]: """Fetch results from a SQL query.""" return self._run_as_sync(self._afetch(query, params)) @@ -439,7 +451,7 @@ def init_vectorstore_table( ) ) - async def ainit_chat_history_table(self, table_name) -> None: + async def ainit_chat_history_table(self, table_name: str) -> None: """Create a Cloud SQL table to store chat history. Args: @@ -456,7 +468,7 @@ async def ainit_chat_history_table(self, table_name) -> None: );""" await self._aexecute(create_table_query) - def init_chat_history_table(self, table_name) -> None: + def init_chat_history_table(self, table_name: str) -> None: """Create a Cloud SQL table to store chat history. Args: diff --git a/src/langchain_google_cloud_sql_pg/loader.py b/src/langchain_google_cloud_sql_pg/loader.py index 93261a77..4244387b 100644 --- a/src/langchain_google_cloud_sql_pg/loader.py +++ b/src/langchain_google_cloud_sql_pg/loader.py @@ -36,24 +36,24 @@ DEFAULT_METADATA_COL = "langchain_metadata" -def text_formatter(row, content_columns) -> str: +def text_formatter(row: dict, content_columns: List[str]) -> str: """txt document formatter.""" return " ".join(str(row[column]) for column in content_columns if column in row) -def csv_formatter(row, content_columns) -> str: +def csv_formatter(row: dict, content_columns: List[str]) -> str: """CSV document formatter.""" return ", ".join(str(row[column]) for column in content_columns if column in row) -def yaml_formatter(row, content_columns) -> str: +def yaml_formatter(row: dict, content_columns: List[str]) -> str: """YAML document formatter.""" return "\n".join( f"{column}: {str(row[column])}" for column in content_columns if column in row ) -def json_formatter(row, content_columns) -> str: +def json_formatter(row: dict, content_columns: List[str]) -> str: """JSON document formatter.""" dictionary = {} for column in content_columns: @@ -116,7 +116,7 @@ class PostgresLoader(BaseLoader): def __init__( self, - key, + key: object, engine: PostgresEngine, query: str, content_columns: List[str], @@ -162,7 +162,7 @@ async def create( metadata_json_column: Optional[str] = None, format: Optional[str] = None, formatter: Optional[Callable] = None, - ): + ) -> PostgresLoader: """Create a new PostgresLoader instance. Args: @@ -255,7 +255,7 @@ def create_sync( metadata_json_column: Optional[str] = None, format: Optional[str] = None, formatter: Optional[Callable] = None, - ): + ) -> PostgresLoader: """Create a new PostgresLoader instance. Args: @@ -340,7 +340,7 @@ class PostgresDocumentSaver: def __init__( self, - key, + key: object, engine: PostgresEngine, table_name: str, content_column: str, @@ -378,7 +378,7 @@ async def create( content_column: str = DEFAULT_CONTENT_COL, metadata_columns: List[str] = [], metadata_json_column: Optional[str] = DEFAULT_METADATA_COL, - ): + ) -> PostgresDocumentSaver: """Create an PostgresDocumentSaver instance. Args: @@ -435,7 +435,7 @@ def create_sync( content_column: str = DEFAULT_CONTENT_COL, metadata_columns: List[str] = [], metadata_json_column: str = DEFAULT_METADATA_COL, - ): + ) -> PostgresDocumentSaver: """Create an PostgresDocumentSaver instance. Args: diff --git a/src/langchain_google_cloud_sql_pg/vectorstore.py b/src/langchain_google_cloud_sql_pg/vectorstore.py index d634cec7..a42aceaf 100644 --- a/src/langchain_google_cloud_sql_pg/vectorstore.py +++ b/src/langchain_google_cloud_sql_pg/vectorstore.py @@ -17,12 +17,13 @@ import json import uuid -from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, Type, Union import numpy as np from langchain_core.documents import Document from langchain_core.embeddings import Embeddings from langchain_core.vectorstores import VectorStore +from sqlalchemy.engine.row import RowMapping from .engine import PostgresEngine from .indexes import ( @@ -42,7 +43,7 @@ class PostgresVectorStore(VectorStore): def __init__( self, - key, + key: object, engine: PostgresEngine, embedding_service: Embeddings, table_name: str, @@ -114,7 +115,7 @@ async def create( fetch_k: int = 20, lambda_mult: float = 0.5, index_query_options: Optional[QueryOptions] = None, - ): + ) -> PostgresVectorStore: """Create a new PostgresVectorStore instance. Args: @@ -218,7 +219,7 @@ def create_sync( fetch_k: int = 20, lambda_mult: float = 0.5, index_query_options: Optional[QueryOptions] = None, - ): + ) -> PostgresVectorStore: """Create a new PostgresVectorStore instance. Args: @@ -496,7 +497,7 @@ def from_texts( # type: ignore[override] id_column: str = "langchain_id", metadata_json_column: str = "langchain_metadata", **kwargs: Any, - ): + ) -> PostgresVectorStore: """Create an PostgresVectorStore instance from texts. Args: texts (List[str]): Texts to add to the vector store. @@ -589,7 +590,7 @@ async def __query_collection( k: Optional[int] = None, filter: Optional[str] = None, **kwargs: Any, - ) -> List[Any]: + ) -> Sequence[RowMapping]: """Perform similarity search query on the vector store table.""" k = k if k else self.k operator = self.distance_strategy.operator diff --git a/tests/test_postgresql_chatmessagehistory.py b/tests/test_postgresql_chatmessagehistory.py index ab61c6d2..ea0b85ee 100644 --- a/tests/test_postgresql_chatmessagehistory.py +++ b/tests/test_postgresql_chatmessagehistory.py @@ -79,14 +79,14 @@ def test_chat_message_history(memory_engine: PostgresEngine) -> None: assert len(history.messages) == 0 -def test_chat_table(memory_engine: Any): +def test_chat_table(memory_engine: Any) -> None: with pytest.raises(ValueError): PostgresChatMessageHistory.create_sync( engine=memory_engine, session_id="test", table_name="doesnotexist" ) -def test_chat_schema(memory_engine: Any): +def test_chat_schema(memory_engine: Any) -> None: doc_table_name = "test_table" + str(uuid.uuid4()) memory_engine.init_document_table(table_name=doc_table_name) with pytest.raises(IndexError): From 8e61bc779bc8f803e40e76aaeffdb93c35a5c90f Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:23:44 -0400 Subject: [PATCH 02/12] feat: Add table name to default index name (#171) --- src/langchain_google_cloud_sql_pg/indexes.py | 4 ++-- src/langchain_google_cloud_sql_pg/vectorstore.py | 16 +++++++++++----- tests/test_cloudsql_vectorstore_index.py | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/langchain_google_cloud_sql_pg/indexes.py b/src/langchain_google_cloud_sql_pg/indexes.py index b4dfafc2..b5616a8c 100644 --- a/src/langchain_google_cloud_sql_pg/indexes.py +++ b/src/langchain_google_cloud_sql_pg/indexes.py @@ -34,12 +34,12 @@ class DistanceStrategy(StrategyMixin, enum.Enum): DEFAULT_DISTANCE_STRATEGY = DistanceStrategy.COSINE_DISTANCE -DEFAULT_INDEX_NAME = "langchainvectorindex" +DEFAULT_INDEX_NAME_SUFFIX: str = "langchainvectorindex" @dataclass class BaseIndex(ABC): - name: str = DEFAULT_INDEX_NAME + name: Optional[str] = None index_type: str = "base" distance_strategy: DistanceStrategy = field( default_factory=lambda: DistanceStrategy.COSINE_DISTANCE diff --git a/src/langchain_google_cloud_sql_pg/vectorstore.py b/src/langchain_google_cloud_sql_pg/vectorstore.py index a42aceaf..152e03da 100644 --- a/src/langchain_google_cloud_sql_pg/vectorstore.py +++ b/src/langchain_google_cloud_sql_pg/vectorstore.py @@ -28,7 +28,7 @@ from .engine import PostgresEngine from .indexes import ( DEFAULT_DISTANCE_STRATEGY, - DEFAULT_INDEX_NAME, + DEFAULT_INDEX_NAME_SUFFIX, BaseIndex, DistanceStrategy, ExactNearestNeighbor, @@ -902,31 +902,37 @@ async def aapply_vector_index( filter = f"WHERE ({index.partial_indexes})" if index.partial_indexes else "" params = "WITH " + index.index_options() function = index.distance_strategy.index_function - name = name or index.name + if name is None: + if index.name == None: + index.name = self.table_name + DEFAULT_INDEX_NAME_SUFFIX + name = index.name stmt = f'CREATE INDEX {"CONCURRENTLY" if concurrently else ""} {name} ON "{self.table_name}" USING {index.index_type} ({self.embedding_column} {function}) {params} {filter};' if concurrently: await self.engine._aexecute_outside_tx(stmt) else: await self.engine._aexecute(stmt) - async def areindex(self, index_name: str = DEFAULT_INDEX_NAME) -> None: + async def areindex(self, index_name: Optional[str] = None) -> None: """Re-index the vector store table.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX query = f"REINDEX INDEX {index_name};" await self.engine._aexecute(query) async def adrop_vector_index( self, - index_name: str = DEFAULT_INDEX_NAME, + index_name: Optional[str] = None, ) -> None: """Drop the vector index.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX query = f"DROP INDEX IF EXISTS {index_name};" await self.engine._aexecute(query) async def is_valid_index( self, - index_name: str = DEFAULT_INDEX_NAME, + index_name: Optional[str] = None, ) -> bool: """Check if index exists in the table.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX query = f""" SELECT tablename, indexname FROM pg_indexes diff --git a/tests/test_cloudsql_vectorstore_index.py b/tests/test_cloudsql_vectorstore_index.py index bb70b6d7..2de8ed2a 100644 --- a/tests/test_cloudsql_vectorstore_index.py +++ b/tests/test_cloudsql_vectorstore_index.py @@ -24,7 +24,7 @@ from langchain_google_cloud_sql_pg import PostgresEngine, PostgresVectorStore from langchain_google_cloud_sql_pg.indexes import ( - DEFAULT_INDEX_NAME, + DEFAULT_INDEX_NAME_SUFFIX, DistanceStrategy, HNSWIndex, IVFFlatIndex, @@ -32,6 +32,7 @@ DEFAULT_TABLE = "test_table" + str(uuid.uuid4()).replace("-", "_") CUSTOM_TABLE = "test_table_custom" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_INDEX_NAME = DEFAULT_TABLE + DEFAULT_INDEX_NAME_SUFFIX VECTOR_SIZE = 768 embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) From 3938700fc4debfd2558483ddc5874d45cdc709f6 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:52:24 -0400 Subject: [PATCH 03/12] feat: Add tests to reach 90% coverage (#166) * feat: Add tests to reach 90% coverage * replace alloydb occurances * add more tests * fix key value error * add is valid index test * test * set test orders * delete skip python version --- .coveragerc | 2 +- DEVELOPER.md | 4 +- src/langchain_google_cloud_sql_pg/loader.py | 26 ------- .../vectorstore.py | 2 +- tests/test_cloudsql_vectorstore.py | 69 +++++++++++++++++++ tests/test_cloudsql_vectorstore_index.py | 11 +-- tests/test_cloudsql_vectorstore_search.py | 21 +++++- tests/test_postgresql_loader.py | 47 +++++++++++++ 8 files changed, 146 insertions(+), 36 deletions(-) diff --git a/.coveragerc b/.coveragerc index 964dbb39..2c634d52 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,4 +5,4 @@ omit = [report] show_missing = true -fail_under = 82 \ No newline at end of file +fail_under = 90 \ No newline at end of file diff --git a/DEVELOPER.md b/DEVELOPER.md index 4118a0da..9e9ef215 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -28,7 +28,7 @@ Learn more by reading [How should I write my commits?](https://github.com/google ### CI Platform Setup -Cloud Build is used to run tests against Google Cloud resources in test project: langchain-alloydb-testing. +Cloud Build is used to run tests against Google Cloud resources in test project: langchain-cloud-sql-testing. Each test has a corresponding Cloud Build trigger, see [all triggers][triggers]. These tests are registered as required tests in `.github/sync-repo-settings.yaml`. @@ -41,7 +41,7 @@ name: pg-integration-test-pr-py38 description: Run integration tests on PR for Python 3.8 filename: integration.cloudbuild.yaml github: - name: langchain-google-alloydb-pg-python + name: langchain-google-cloud-sql-pg-python owner: googleapis pullRequest: branch: .* diff --git a/src/langchain_google_cloud_sql_pg/loader.py b/src/langchain_google_cloud_sql_pg/loader.py index 4244387b..15d247cf 100644 --- a/src/langchain_google_cloud_sql_pg/loader.py +++ b/src/langchain_google_cloud_sql_pg/loader.py @@ -555,29 +555,3 @@ def delete(self, docs: List[Document]) -> None: docs (List[langchain_core.documents.Document]): a list of documents to be deleted. """ self.engine._run_as_sync(self.adelete(docs)) - - async def _aload_table_schema(self) -> sqlalchemy.Table: - """ - Load table schema from existing table in PgSQL database. - - Returns: - (sqlalchemy.Table): The loaded table. - """ - metadata = sqlalchemy.MetaData() - async with self.engine._engine.connect() as conn: - await conn.run_sync(metadata.reflect, only=[self.table_name]) - - table = sqlalchemy.Table(self.table_name, metadata) - # Extract the schema information - schema = [] - for column in table.columns: - schema.append( - { - "name": column.name, - "type": column.type.python_type, - "max_length": getattr(column.type, "length", None), - "nullable": not column.nullable, - } - ) - - return metadata.tables[self.table_name] diff --git a/src/langchain_google_cloud_sql_pg/vectorstore.py b/src/langchain_google_cloud_sql_pg/vectorstore.py index 152e03da..1c4685e2 100644 --- a/src/langchain_google_cloud_sql_pg/vectorstore.py +++ b/src/langchain_google_cloud_sql_pg/vectorstore.py @@ -183,7 +183,7 @@ async def create( del all_columns[id_column] del all_columns[content_column] del all_columns[embedding_column] - metadata_columns = [k for k, _ in all_columns.keys()] + metadata_columns = [k for k in all_columns.keys()] return cls( cls.__create_key, diff --git a/tests/test_cloudsql_vectorstore.py b/tests/test_cloudsql_vectorstore.py index bda136f8..65933795 100644 --- a/tests/test_cloudsql_vectorstore.py +++ b/tests/test_cloudsql_vectorstore.py @@ -133,6 +133,19 @@ async def vs_custom(self, engine): yield vs await engine._aexecute(f'DROP TABLE IF EXISTS "{CUSTOM_TABLE}"') + async def test_init_with_constructor(self, engine): + with pytest.raises(Exception): + PostgresVectorStore( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="noname", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + async def test_post_init(self, engine): with pytest.raises(ValueError): await PostgresVectorStore.create( @@ -265,4 +278,60 @@ async def test_add_texts(self, engine_sync, vs_sync): results = engine_sync._fetch(f'SELECT * FROM "{DEFAULT_TABLE_SYNC}"') assert len(results) == 6 + async def test_ignore_metadata_columns(self, vs_custom): + column_to_ignore = "source" + vs = await PostgresVectorStore.create( + vs_custom.engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + ignore_metadata_columns=[column_to_ignore], + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_json_column="mymeta", + ) + assert column_to_ignore not in vs.metadata_columns + + async def test_create_vectorstore_with_invalid_parameters(self, vs_custom): + with pytest.raises(ValueError): + await PostgresVectorStore.create( + vs_custom.engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["random_column"], # invalid metadata column + ) + with pytest.raises(ValueError): + await PostgresVectorStore.create( + vs_custom.engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="langchain_id", # invalid content column type + embedding_column="myembedding", + metadata_columns=["random_column"], + ) + with pytest.raises(ValueError): + await PostgresVectorStore.create( + vs_custom.engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="random_column", # invalid embedding column + metadata_columns=["random_column"], + ) + with pytest.raises(ValueError): + await PostgresVectorStore.create( + vs_custom.engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="langchain_id", # invalid embedding column data type + metadata_columns=["random_column"], + ) + # Need tests for store metadata=False diff --git a/tests/test_cloudsql_vectorstore_index.py b/tests/test_cloudsql_vectorstore_index.py index 2de8ed2a..436a8634 100644 --- a/tests/test_cloudsql_vectorstore_index.py +++ b/tests/test_cloudsql_vectorstore_index.py @@ -55,10 +55,6 @@ def get_env_var(key: str, desc: str) -> str: @pytest.mark.asyncio(scope="class") -@pytest.mark.skipif( - sys.version_info != (3, 11), - reason="To prevent index clashes only run on python3.11 or higher", -) class TestIndex: @pytest.fixture(scope="module") def db_project(self) -> str: @@ -101,11 +97,13 @@ async def vs(self, engine): await engine._aexecute(f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") await engine._engine.dispose() + @pytest.mark.run(order=1) async def test_aapply_vector_index(self, vs): index = HNSWIndex() await vs.aapply_vector_index(index) assert await vs.is_valid_index(DEFAULT_INDEX_NAME) + @pytest.mark.run(order=2) async def test_areindex(self, vs): if not await vs.is_valid_index(DEFAULT_INDEX_NAME): index = HNSWIndex() @@ -114,6 +112,7 @@ async def test_areindex(self, vs): await vs.areindex(DEFAULT_INDEX_NAME) assert await vs.is_valid_index(DEFAULT_INDEX_NAME) + @pytest.mark.run(order=3) async def test_dropindex(self, vs): await vs.adrop_vector_index() result = await vs.is_valid_index(DEFAULT_INDEX_NAME) @@ -130,3 +129,7 @@ async def test_aapply_vector_index_ivfflat(self, vs): await vs.aapply_vector_index(index) assert await vs.is_valid_index("secondindex") await vs.adrop_vector_index("secondindex") + + async def test_is_valid_index(self, vs): + is_valid = await vs.is_valid_index("invalid_index") + assert is_valid == False diff --git a/tests/test_cloudsql_vectorstore_search.py b/tests/test_cloudsql_vectorstore_search.py index 536a3f10..f26131f8 100644 --- a/tests/test_cloudsql_vectorstore_search.py +++ b/tests/test_cloudsql_vectorstore_search.py @@ -21,7 +21,7 @@ from langchain_core.documents import Document from langchain_google_cloud_sql_pg import Column, PostgresEngine, PostgresVectorStore -from langchain_google_cloud_sql_pg.indexes import HNSWQueryOptions, IVFFlatQueryOptions +from langchain_google_cloud_sql_pg.indexes import DistanceStrategy, HNSWQueryOptions DEFAULT_TABLE = "test_table" + str(uuid.uuid4()).replace("-", "_") CUSTOM_TABLE = "test_table_custom" + str(uuid.uuid4()).replace("-", "_") @@ -151,7 +151,7 @@ async def test_asimilarity_search_by_vector(self, vs): assert results[0][0] == Document(page_content="foo") assert results[0][1] == 0 - async def test_similarity_search_with_relevance_scores_threshold(self, vs): + async def test_similarity_search_with_relevance_scores_threshold_cosine(self, vs): score_threshold = {"score_threshold": 0} results = await vs.asimilarity_search_with_relevance_scores( "foo", **score_threshold @@ -171,6 +171,23 @@ async def test_similarity_search_with_relevance_scores_threshold(self, vs): assert len(results) == 1 assert results[0][0] == Document(page_content="foo") + async def test_similarity_search_with_relevance_scores_threshold_euclidean( + self, engine + ): + vs = await PostgresVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + distance_strategy=DistanceStrategy.EUCLIDEAN, + ) + + score_threshold = {"score_threshold": 0.9} + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 1 + assert results[0][0] == Document(page_content="foo") + async def test_amax_marginal_relevance_search(self, vs): results = await vs.amax_marginal_relevance_search("bar") assert results[0] == Document(page_content="bar") diff --git a/tests/test_postgresql_loader.py b/tests/test_postgresql_loader.py index d295c027..8f4f5e35 100644 --- a/tests/test_postgresql_loader.py +++ b/tests/test_postgresql_loader.py @@ -69,6 +69,29 @@ async def _cleanup_table(self, engine): query = f'DROP TABLE IF EXISTS "{table_name}"' await engine._aexecute(query) + async def test_create_loader_with_invalid_parameters(self, engine): + with pytest.raises(ValueError): + await PostgresLoader.create( + engine=engine, + ) + with pytest.raises(ValueError): + + def fake_formatter(): + return None + + await PostgresLoader.create( + engine=engine, + table_name=table_name, + format="text", + formatter=fake_formatter, + ) + with pytest.raises(ValueError): + await PostgresLoader.create( + engine=engine, + table_name=table_name, + format="fake_format", + ) + async def test_load_from_query_default(self, engine): try: await self._cleanup_table(engine) @@ -216,6 +239,30 @@ async def test_load_from_query_customized_content_default_metadata(self, engine) ) ] + loader = await PostgresLoader.create( + engine=engine, + query=f'SELECT * FROM "{table_name}";', + content_columns=[ + "variety", + "quantity_in_stock", + "price_per_unit", + ], + format="JSON", + ) + + documents = await self._collect_async_items(loader.alazy_load()) + + assert documents == [ + Document( + page_content='{"variety": "Granny Smith", "quantity_in_stock": 150, "price_per_unit": 1}', + metadata={ + "fruit_id": 1, + "fruit_name": "Apple", + "organic": 1, + }, + ) + ] + finally: await self._cleanup_table(engine) From b4f40bb389b40853e3deed37e1385a7866741231 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:40:05 -0400 Subject: [PATCH 04/12] feat: Remove langchain-community dependency (#172) * feat: Remove langchain-community dependency * isort * change dependency from langchain to langchain-community --------- Co-authored-by: Averi Kitsch --- pyproject.toml | 1 - requirements.txt | 1 - samples/requirements.txt | 2 +- src/langchain_google_cloud_sql_pg/loader.py | 2 +- tests/test_cloudsql_vectorstore.py | 2 +- tests/test_cloudsql_vectorstore_from_methods.py | 2 +- tests/test_cloudsql_vectorstore_index.py | 2 +- tests/test_cloudsql_vectorstore_search.py | 2 +- tests/test_postgresql_engine.py | 2 +- 9 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 509e97f7..942944ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ authors = [ dependencies = [ "cloud-sql-python-connector[asyncpg] >= 1.10.0, <2.0.0", "langchain-core>=0.1.1, <1.0.0 ", - "langchain-community>=0.0.18, <0.3.0", "numpy>=1.24.4, <2.0.0", "pgvector>=0.2.5, <1.0.0", "SQLAlchemy[asyncio]>=2.0.25, <3.0.0" diff --git a/requirements.txt b/requirements.txt index ba25b966..ab3fdc9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ cloud-sql-python-connector[asyncpg]==1.10.0 langchain-core==0.2.12 -langchain-community==0.2.6 numpy===1.24.4; python_version<='3.8' numpy==1.26.4; python_version>'3.8' pgvector==0.3.0 diff --git a/samples/requirements.txt b/samples/requirements.txt index b90131e5..462950ce 100644 --- a/samples/requirements.txt +++ b/samples/requirements.txt @@ -1,4 +1,4 @@ google-cloud-aiplatform[reasoningengine,langchain] langchain-google-vertexai -langchain +langchain-community google-cloud-resource-manager \ No newline at end of file diff --git a/src/langchain_google_cloud_sql_pg/loader.py b/src/langchain_google_cloud_sql_pg/loader.py index 15d247cf..92dd7941 100644 --- a/src/langchain_google_cloud_sql_pg/loader.py +++ b/src/langchain_google_cloud_sql_pg/loader.py @@ -27,7 +27,7 @@ ) import sqlalchemy -from langchain_community.document_loaders.base import BaseLoader +from langchain_core.document_loaders.base import BaseLoader from langchain_core.documents import Document from .engine import PostgresEngine diff --git a/tests/test_cloudsql_vectorstore.py b/tests/test_cloudsql_vectorstore.py index 65933795..081853a8 100644 --- a/tests/test_cloudsql_vectorstore.py +++ b/tests/test_cloudsql_vectorstore.py @@ -17,8 +17,8 @@ import pytest import pytest_asyncio -from langchain_community.embeddings import DeterministicFakeEmbedding from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding from langchain_google_cloud_sql_pg import Column, PostgresEngine, PostgresVectorStore diff --git a/tests/test_cloudsql_vectorstore_from_methods.py b/tests/test_cloudsql_vectorstore_from_methods.py index e0439c97..e7e143cb 100644 --- a/tests/test_cloudsql_vectorstore_from_methods.py +++ b/tests/test_cloudsql_vectorstore_from_methods.py @@ -17,8 +17,8 @@ import pytest import pytest_asyncio -from langchain_community.embeddings import DeterministicFakeEmbedding from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding from langchain_google_cloud_sql_pg import Column, PostgresEngine, PostgresVectorStore diff --git a/tests/test_cloudsql_vectorstore_index.py b/tests/test_cloudsql_vectorstore_index.py index 436a8634..10baf13a 100644 --- a/tests/test_cloudsql_vectorstore_index.py +++ b/tests/test_cloudsql_vectorstore_index.py @@ -19,8 +19,8 @@ import pytest import pytest_asyncio -from langchain_community.embeddings import DeterministicFakeEmbedding from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding from langchain_google_cloud_sql_pg import PostgresEngine, PostgresVectorStore from langchain_google_cloud_sql_pg.indexes import ( diff --git a/tests/test_cloudsql_vectorstore_search.py b/tests/test_cloudsql_vectorstore_search.py index f26131f8..65c6d8bc 100644 --- a/tests/test_cloudsql_vectorstore_search.py +++ b/tests/test_cloudsql_vectorstore_search.py @@ -17,8 +17,8 @@ import pytest import pytest_asyncio -from langchain_community.embeddings import DeterministicFakeEmbedding from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding from langchain_google_cloud_sql_pg import Column, PostgresEngine, PostgresVectorStore from langchain_google_cloud_sql_pg.indexes import DistanceStrategy, HNSWQueryOptions diff --git a/tests/test_postgresql_engine.py b/tests/test_postgresql_engine.py index a5790f44..9ccd43a0 100644 --- a/tests/test_postgresql_engine.py +++ b/tests/test_postgresql_engine.py @@ -19,7 +19,7 @@ import pytest import pytest_asyncio from google.cloud.sql.connector import Connector, IPTypes -from langchain_community.embeddings import DeterministicFakeEmbedding +from langchain_core.embeddings import DeterministicFakeEmbedding from sqlalchemy import VARCHAR from sqlalchemy.ext.asyncio import create_async_engine From eb2eac303f64e809e6f3fc9bc3307be163602a4e Mon Sep 17 00:00:00 2001 From: Vishwaraj Anand Date: Fri, 2 Aug 2024 22:14:39 +0000 Subject: [PATCH 05/12] docs: added vector store initialization from documents (#174) * feat: added vector store initialization from documents * nit: fix pr comments Co-authored-by: Jack Wotherspoon * chore: pr comments * chore: fix PR comments --------- Co-authored-by: Jack Wotherspoon --- docs/vector_store.ipynb | 1133 ++++++++++++++++++++------------------- 1 file changed, 588 insertions(+), 545 deletions(-) diff --git a/docs/vector_store.ipynb b/docs/vector_store.ipynb index 8800fff5..60839763 100644 --- a/docs/vector_store.ipynb +++ b/docs/vector_store.ipynb @@ -1,548 +1,591 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Google Cloud SQL for PostgreSQL\n", - "\n", - "> [Cloud SQL](https://cloud.google.com/sql) is a fully managed relational database service that offers high performance, seamless integration, and impressive scalability. It offers PostgreSQL, PostgreSQL, and SQL Server database engines. Extend your database application to build AI-powered experiences leveraging Cloud SQL's Langchain integrations.\n", - "\n", - "This notebook goes over how to use `Cloud SQL for PostgreSQL` to store vector embeddings with the `PostgresVectorStore` class.\n", - "\n", - "Learn more about the package on [GitHub](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/).\n", - "\n", - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/googleapis/langchain-google-cloud-sql-pg-python/blob/main/docs/vector_store.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Before you begin\n", - "\n", - "To run this notebook, you will need to do the following:\n", - "\n", - " * [Create a Google Cloud Project](https://developers.google.com/workspace/guides/create-project)\n", - " * [Enable the Cloud SQL Admin API.](https://console.cloud.google.com/flows/enableapi?apiid=sqladmin.googleapis.com)\n", - " * [Create a Cloud SQL instance.](https://cloud.google.com/sql/docs/postgres/connect-instance-auth-proxy#create-instance)\n", - " * [Create a Cloud SQL database.](https://cloud.google.com/sql/docs/postgres/create-manage-databases)\n", - " * [Add a User to the database.](https://cloud.google.com/sql/docs/postgres/create-manage-users)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "IR54BmgvdHT_" - }, - "source": [ - "### šŸ¦œšŸ”— Library Installation\n", - "Install the integration library, `langchain-google-cloud-sql-pg`, and the library for the embedding service, `langchain-google-vertexai`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "0ZITIDE160OD", - "outputId": "e184bc0d-6541-4e0a-82d2-1e216db00a2d" - }, - "outputs": [], - "source": [ - "%pip install --upgrade --quiet langchain-google-cloud-sql-pg langchain-google-vertexai" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "v40bB_GMcr9f" - }, - "source": [ - "**Colab only:** Uncomment the following cell to restart the kernel or use the button to restart the kernel. For Vertex AI Workbench you can restart the terminal using the button on top." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "v6jBDnYnNM08", - "metadata": { - "id": "v6jBDnYnNM08" - }, - "outputs": [], - "source": [ - "# # Automatically restart kernel after installs so that your environment can access the new packages\n", - "# import IPython\n", - "\n", - "# app = IPython.Application.instance()\n", - "# app.kernel.do_shutdown(True)" - ] - }, - { - "cell_type": "markdown", - "id": "yygMe6rPWxHS", - "metadata": { - "id": "yygMe6rPWxHS" - }, - "source": [ - "### šŸ” Authentication\n", - "Authenticate to Google Cloud as the IAM user logged into this notebook in order to access your Google Cloud Project.\n", - "\n", - "* If you are using Colab to run this notebook, use the cell below and continue.\n", - "* If you are using Vertex AI Workbench, check out the setup instructions [here](https://github.com/GoogleCloudPlatform/generative-ai/tree/main/setup-env)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "PTXN1_DSXj2b", - "metadata": { - "id": "PTXN1_DSXj2b" - }, - "outputs": [], - "source": [ - "from google.colab import auth\n", - "\n", - "auth.authenticate_user()" - ] - }, - { - "cell_type": "markdown", - "id": "NEvB9BoLEulY", - "metadata": { - "id": "NEvB9BoLEulY" - }, - "source": [ - "### ☁ Set Your Google Cloud Project\n", - "Set your Google Cloud project so that you can leverage Google Cloud resources within this notebook.\n", - "\n", - "If you don't know your project ID, try the following:\n", - "\n", - "* Run `gcloud config list`.\n", - "* Run `gcloud projects list`.\n", - "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "gfkS3yVRE4_W", - "metadata": { - "cellView": "form", - "id": "gfkS3yVRE4_W" - }, - "outputs": [], - "source": [ - "# @markdown Please fill in the value below with your Google Cloud project ID and then run the cell.\n", - "\n", - "PROJECT_ID = \"my-project-id\" # @param {type:\"string\"}\n", - "\n", - "# Set the project id\n", - "!gcloud config set project {PROJECT_ID}" - ] - }, - { - "cell_type": "markdown", - "id": "f8f2830ee9ca1e01", - "metadata": { - "id": "f8f2830ee9ca1e01" - }, - "source": [ - "## Basic Usage" - ] - }, - { - "cell_type": "markdown", - "id": "OMvzMWRrR6n7", - "metadata": { - "id": "OMvzMWRrR6n7" - }, - "source": [ - "### Set Cloud SQL database values\n", - "Find your database values, in the [Cloud SQL Instances page](https://console.cloud.google.com/sql?_ga=2.223735448.2062268965.1707700487-2088871159.1707257687)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "irl7eMFnSPZr", - "metadata": { - "id": "irl7eMFnSPZr" - }, - "outputs": [], - "source": [ - "# @title Set Your Values Here { display-mode: \"form\" }\n", - "REGION = \"us-central1\" # @param {type: \"string\"}\n", - "INSTANCE = \"my-pg-instance\" # @param {type: \"string\"}\n", - "DATABASE = \"my-database\" # @param {type: \"string\"}\n", - "TABLE_NAME = \"vector_store\" # @param {type: \"string\"}" - ] - }, - { - "cell_type": "markdown", - "id": "QuQigs4UoFQ2", - "metadata": { - "id": "QuQigs4UoFQ2" - }, - "source": [ - "### PostgresEngine Connection Pool\n", - "\n", - "One of the requirements and arguments to establish Cloud SQL as a vector store is a `PostgresEngine` object. The `PostgresEngine` configures a connection pool to your Cloud SQL database, enabling successful connections from your application and following industry best practices.\n", - "\n", - "To create a `PostgresEngine` using `PostgresEngine.from_instance()` you need to provide only 4 things:\n", - "\n", - "1. `project_id` : Project ID of the Google Cloud Project where the Cloud SQL instance is located.\n", - "1. `region` : Region where the Cloud SQL instance is located.\n", - "1. `instance` : The name of the Cloud SQL instance.\n", - "1. `database` : The name of the database to connect to on the Cloud SQL instance.\n", - "\n", - "By default, [IAM database authentication](https://cloud.google.com/sql/docs/postgres/iam-authentication#iam-db-auth) will be used as the method of database authentication. This library uses the IAM principal belonging to the [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials) sourced from the envionment.\n", - "\n", - "For more informatin on IAM database authentication please see:\n", - "\n", - "* [Configure an instance for IAM database authentication](https://cloud.google.com/sql/docs/postgres/create-edit-iam-instances)\n", - "* [Manage users with IAM database authentication](https://cloud.google.com/sql/docs/postgres/add-manage-iam-users)\n", - "\n", - "Optionally, [built-in database authentication](https://cloud.google.com/sql/docs/postgres/built-in-authentication) using a username and password to access the Cloud SQL database can also be used. Just provide the optional `user` and `password` arguments to `PostgresEngine.from_instance()`:\n", - "\n", - "* `user` : Database user to use for built-in database authentication and login\n", - "* `password` : Database password to use for built-in database authentication and login.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"**Note**: This tutorial demonstrates the async interface. All async methods have corresponding sync methods.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_google_cloud_sql_pg import PostgresEngine\n", - "\n", - "engine = await PostgresEngine.afrom_instance(\n", - " project_id=PROJECT_ID, region=REGION, instance=INSTANCE, database=DATABASE\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "D9Xs2qhm6X56" - }, - "source": [ - "### Initialize a table\n", - "The `PostgresVectorStore` class requires a database table. The `PostgresEngine` engine has a helper method `init_vectorstore_table()` that can be used to create a table with the proper schema for you." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": { - "id": "avlyHEMn6gzU" - }, - "outputs": [], - "source": [ - "from langchain_google_cloud_sql_pg import PostgresEngine\n", - "\n", - "await engine.ainit_vectorstore_table(\n", - " table_name=TABLE_NAME,\n", - " vector_size=768, # Vector size for VertexAI model(textembedding-gecko@latest)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create an embedding class instance\n", - "\n", - "You can use any [LangChain embeddings model](https://python.langchain.com/docs/integrations/text_embedding/).\n", - "You may need to enable Vertex AI API to use `VertexAIEmbeddings`. We recommend setting the embedding model's version for production, learn more about the [Text embeddings models](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-embeddings)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5utKIdq7KYi5", - "metadata": { - "id": "5utKIdq7KYi5" - }, - "outputs": [], - "source": [ - "# enable Vertex AI API\n", - "!gcloud services enable aiplatform.googleapis.com" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Vb2RJocV9_LQ", - "outputId": "37f5dc74-2512-47b2-c135-f34c10afdcf4" - }, - "outputs": [], - "source": [ - "from langchain_google_vertexai import VertexAIEmbeddings\n", - "\n", - "embedding = VertexAIEmbeddings(\n", - " model_name=\"textembedding-gecko@latest\", project=PROJECT_ID\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "e1tl0aNx7SWy" - }, - "source": [ - "### Initialize a default PostgresVectorStore" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "z-AZyzAQ7bsf" - }, - "outputs": [], - "source": [ - "from langchain_google_cloud_sql_pg import PostgresVectorStore\n", - "\n", - "store = await PostgresVectorStore.create( # Use .create() to initialize an async vector store\n", - " engine=engine,\n", - " table_name=TABLE_NAME,\n", - " embedding_service=embedding,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add texts" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "\n", - "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", - "metadatas = [{\"len\": len(t)} for t in all_texts]\n", - "ids = [str(uuid.uuid4()) for _ in all_texts]\n", - "\n", - "await store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete texts" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await store.adelete([ids[1]])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Search for documents" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query = \"I'd like a fruit.\"\n", - "docs = await store.asimilarity_search(query)\n", - "print(docs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Search for documents by vector" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query_vector = embedding.embed_query(query)\n", - "docs = await store.asimilarity_search_by_vector(query_vector, k=2)\n", - "print(docs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Add a Index\n", - "Speed up vector search queries by applying a vector index. Learn more about [vector indexes](https://cloud.google.com/blog/products/databases/faster-similarity-search-performance-with-pgvector-indexes)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_google_cloud_sql_pg.indexes import IVFFlatIndex\n", - "\n", - "index = IVFFlatIndex()\n", - "await store.aapply_vector_index(index)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Re-index" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await store.areindex() # Re-index using default index name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Remove an index" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await store.aadrop_vector_index() # Delete index using default name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create a custom Vector Store\n", - "A Vector Store can take advantage of relational data to filter similarity searches.\n", - "\n", - "Create a table with custom metadata columns." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_google_cloud_sql_pg import Column\n", - "\n", - "# Set table name\n", - "TABLE_NAME = \"vectorstore_custom\"\n", - "\n", - "await engine.ainit_vectorstore_table(\n", - " table_name=TABLE_NAME,\n", - " vector_size=768, # VertexAI model: textembedding-gecko@latest\n", - " metadata_columns=[Column(\"len\", \"INTEGER\")],\n", - ")\n", - "\n", - "\n", - "# Initialize PostgresVectorStore\n", - "custom_store = await PostgresVectorStore.create(\n", - " engine=engine,\n", - " table_name=TABLE_NAME,\n", - " embedding_service=embedding,\n", - " metadata_columns=[\"len\"],\n", - " # Connect to a existing VectorStore by customizing the table schema:\n", - " # id_column=\"uuid\",\n", - " # content_column=\"documents\",\n", - " # embedding_column=\"vectors\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Search for documents with metadata filter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "\n", - "# Add texts to the Vector Store\n", - "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", - "metadatas = [{\"len\": len(t)} for t in all_texts]\n", - "ids = [str(uuid.uuid4()) for _ in all_texts]\n", - "await custom_store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)\n", - "\n", - "# Use filter on search\n", - "docs = await custom_store.asimilarity_search_by_vector(query_vector, filter=\"len >= 6\")\n", - "\n", - "print(docs)" - ] - } - ], - "metadata": { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Google Cloud SQL for PostgreSQL\n", + "\n", + "> [Cloud SQL](https://cloud.google.com/sql) is a fully managed relational database service that offers high performance, seamless integration, and impressive scalability. It offers PostgreSQL, PostgreSQL, and SQL Server database engines. Extend your database application to build AI-powered experiences leveraging Cloud SQL's Langchain integrations.\n", + "\n", + "This notebook goes over how to use `Cloud SQL for PostgreSQL` to store vector embeddings with the `PostgresVectorStore` class.\n", + "\n", + "Learn more about the package on [GitHub](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/).\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/googleapis/langchain-google-cloud-sql-pg-python/blob/main/docs/vector_store.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Before you begin\n", + "\n", + "To run this notebook, you will need to do the following:\n", + "\n", + " * [Create a Google Cloud Project](https://developers.google.com/workspace/guides/create-project)\n", + " * [Enable the Cloud SQL Admin API.](https://console.cloud.google.com/flows/enableapi?apiid=sqladmin.googleapis.com)\n", + " * [Create a Cloud SQL instance.](https://cloud.google.com/sql/docs/postgres/connect-instance-auth-proxy#create-instance)\n", + " * [Create a Cloud SQL database.](https://cloud.google.com/sql/docs/postgres/create-manage-databases)\n", + " * [Add a User to the database.](https://cloud.google.com/sql/docs/postgres/create-manage-users)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IR54BmgvdHT_" + }, + "source": [ + "### šŸ¦œšŸ”— Library Installation\n", + "Install the integration library, `langchain-google-cloud-sql-pg`, and the library for the embedding service, `langchain-google-vertexai`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "colab": { - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - } - }, - "nbformat": 4, - "nbformat_minor": 0 + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "0ZITIDE160OD", + "outputId": "e184bc0d-6541-4e0a-82d2-1e216db00a2d" + }, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-google-cloud-sql-pg langchain-google-vertexai" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "v40bB_GMcr9f" + }, + "source": [ + "**Colab only:** Uncomment the following cell to restart the kernel or use the button to restart the kernel. For Vertex AI Workbench you can restart the terminal using the button on top." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "v6jBDnYnNM08", + "metadata": { + "id": "v6jBDnYnNM08" + }, + "outputs": [], + "source": [ + "# # Automatically restart kernel after installs so that your environment can access the new packages\n", + "# import IPython\n", + "\n", + "# app = IPython.Application.instance()\n", + "# app.kernel.do_shutdown(True)" + ] + }, + { + "cell_type": "markdown", + "id": "yygMe6rPWxHS", + "metadata": { + "id": "yygMe6rPWxHS" + }, + "source": [ + "### šŸ” Authentication\n", + "Authenticate to Google Cloud as the IAM user logged into this notebook in order to access your Google Cloud Project.\n", + "\n", + "* If you are using Colab to run this notebook, use the cell below and continue.\n", + "* If you are using Vertex AI Workbench, check out the setup instructions [here](https://github.com/GoogleCloudPlatform/generative-ai/tree/main/setup-env)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "PTXN1_DSXj2b", + "metadata": { + "id": "PTXN1_DSXj2b" + }, + "outputs": [], + "source": [ + "from google.colab import auth\n", + "\n", + "auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "id": "NEvB9BoLEulY", + "metadata": { + "id": "NEvB9BoLEulY" + }, + "source": [ + "### ☁ Set Your Google Cloud Project\n", + "Set your Google Cloud project so that you can leverage Google Cloud resources within this notebook.\n", + "\n", + "If you don't know your project ID, try the following:\n", + "\n", + "* Run `gcloud config list`.\n", + "* Run `gcloud projects list`.\n", + "* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "gfkS3yVRE4_W", + "metadata": { + "cellView": "form", + "id": "gfkS3yVRE4_W" + }, + "outputs": [], + "source": [ + "# @markdown Please fill in the value below with your Google Cloud project ID and then run the cell.\n", + "\n", + "PROJECT_ID = \"my-project-id\" # @param {type:\"string\"}\n", + "\n", + "# Set the project id\n", + "!gcloud config set project {PROJECT_ID}" + ] + }, + { + "cell_type": "markdown", + "id": "f8f2830ee9ca1e01", + "metadata": { + "id": "f8f2830ee9ca1e01" + }, + "source": [ + "## Basic Usage" + ] + }, + { + "cell_type": "markdown", + "id": "OMvzMWRrR6n7", + "metadata": { + "id": "OMvzMWRrR6n7" + }, + "source": [ + "### Set Cloud SQL database values\n", + "Find your database values, in the [Cloud SQL Instances page](https://console.cloud.google.com/sql?_ga=2.223735448.2062268965.1707700487-2088871159.1707257687)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "irl7eMFnSPZr", + "metadata": { + "id": "irl7eMFnSPZr" + }, + "outputs": [], + "source": [ + "# @title Set Your Values Here { display-mode: \"form\" }\n", + "REGION = \"us-central1\" # @param {type: \"string\"}\n", + "INSTANCE = \"my-pg-instance\" # @param {type: \"string\"}\n", + "DATABASE = \"my-database\" # @param {type: \"string\"}\n", + "TABLE_NAME = \"vector_store\" # @param {type: \"string\"}" + ] + }, + { + "cell_type": "markdown", + "id": "QuQigs4UoFQ2", + "metadata": { + "id": "QuQigs4UoFQ2" + }, + "source": [ + "### PostgresEngine Connection Pool\n", + "\n", + "One of the requirements and arguments to establish Cloud SQL as a vector store is a `PostgresEngine` object. The `PostgresEngine` configures a connection pool to your Cloud SQL database, enabling successful connections from your application and following industry best practices.\n", + "\n", + "To create a `PostgresEngine` using `PostgresEngine.from_instance()` you need to provide only 4 things:\n", + "\n", + "1. `project_id` : Project ID of the Google Cloud Project where the Cloud SQL instance is located.\n", + "1. `region` : Region where the Cloud SQL instance is located.\n", + "1. `instance` : The name of the Cloud SQL instance.\n", + "1. `database` : The name of the database to connect to on the Cloud SQL instance.\n", + "\n", + "By default, [IAM database authentication](https://cloud.google.com/sql/docs/postgres/iam-authentication#iam-db-auth) will be used as the method of database authentication. This library uses the IAM principal belonging to the [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials) sourced from the envionment.\n", + "\n", + "For more informatin on IAM database authentication please see:\n", + "\n", + "* [Configure an instance for IAM database authentication](https://cloud.google.com/sql/docs/postgres/create-edit-iam-instances)\n", + "* [Manage users with IAM database authentication](https://cloud.google.com/sql/docs/postgres/add-manage-iam-users)\n", + "\n", + "Optionally, [built-in database authentication](https://cloud.google.com/sql/docs/postgres/built-in-authentication) using a username and password to access the Cloud SQL database can also be used. Just provide the optional `user` and `password` arguments to `PostgresEngine.from_instance()`:\n", + "\n", + "* `user` : Database user to use for built-in database authentication and login\n", + "* `password` : Database password to use for built-in database authentication and login.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"**Note**: This tutorial demonstrates the async interface. All async methods have corresponding sync methods.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_cloud_sql_pg import PostgresEngine\n", + "\n", + "engine = await PostgresEngine.afrom_instance(\n", + " project_id=PROJECT_ID, region=REGION, instance=INSTANCE, database=DATABASE\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D9Xs2qhm6X56" + }, + "source": [ + "### Initialize a table\n", + "The `PostgresVectorStore` class requires a database table. The `PostgresEngine` engine has a helper method `init_vectorstore_table()` that can be used to create a table with the proper schema for you." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "id": "avlyHEMn6gzU" + }, + "outputs": [], + "source": [ + "from langchain_google_cloud_sql_pg import PostgresEngine\n", + "\n", + "await engine.ainit_vectorstore_table(\n", + " table_name=TABLE_NAME,\n", + " vector_size=768, # Vector size for VertexAI model(textembedding-gecko@latest)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create an embedding class instance\n", + "\n", + "You can use any [LangChain embeddings model](https://python.langchain.com/docs/integrations/text_embedding/).\n", + "You may need to enable Vertex AI API to use `VertexAIEmbeddings`. We recommend setting the embedding model's version for production, learn more about the [Text embeddings models](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text-embeddings)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5utKIdq7KYi5", + "metadata": { + "id": "5utKIdq7KYi5" + }, + "outputs": [], + "source": [ + "# enable Vertex AI API\n", + "!gcloud services enable aiplatform.googleapis.com" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Vb2RJocV9_LQ", + "outputId": "37f5dc74-2512-47b2-c135-f34c10afdcf4" + }, + "outputs": [], + "source": [ + "from langchain_google_vertexai import VertexAIEmbeddings\n", + "\n", + "embedding = VertexAIEmbeddings(\n", + " model_name=\"textembedding-gecko@latest\", project=PROJECT_ID\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e1tl0aNx7SWy" + }, + "source": [ + "### Initialize a default PostgresVectorStore" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "z-AZyzAQ7bsf" + }, + "outputs": [], + "source": [ + "from langchain_google_cloud_sql_pg import PostgresVectorStore\n", + "\n", + "store = await PostgresVectorStore.create( # Use .create() to initialize an async vector store\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " embedding_service=embedding,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Initialize Vector Store with documents\n", + "\n", + "This is a great way to get started quickly. However, the default method is recommended for most applications to avoid accidentally adding duplicate documents." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.documents import Document\n", + "import uuid\n", + "\n", + "docs = [\n", + " Document(\n", + " page_content=\"Red Apple\",\n", + " metadata={\"description\": \"red\", \"content\": \"1\", \"category\": \"fruit\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Banana Cavendish\",\n", + " metadata={\"description\": \"yellow\", \"content\": \"2\", \"category\": \"fruit\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Orange Navel\",\n", + " metadata={\"description\": \"orange\", \"content\": \"3\", \"category\": \"fruit\"},\n", + " ),\n", + "]\n", + "ids = [str(uuid.uuid4()) for i in range(len(docs))]\n", + "\n", + "store_with_documents = await PostgresVectorStore.afrom_documents(\n", + " documents=docs,\n", + " ids=ids,\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " embedding_service=embedding,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add texts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", + "metadatas = [{\"len\": len(t)} for t in all_texts]\n", + "ids = [str(uuid.uuid4()) for _ in all_texts]\n", + "\n", + "await store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete texts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.adelete([ids[1]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query = \"I'd like a fruit.\"\n", + "docs = await store.asimilarity_search(query)\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents by vector" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_vector = embedding.embed_query(query)\n", + "docs = await store.asimilarity_search_by_vector(query_vector, k=2)\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Add a Index\n", + "Speed up vector search queries by applying a vector index. Learn more about [vector indexes](https://cloud.google.com/blog/products/databases/faster-similarity-search-performance-with-pgvector-indexes)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_cloud_sql_pg.indexes import IVFFlatIndex\n", + "\n", + "index = IVFFlatIndex()\n", + "await store.aapply_vector_index(index)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Re-index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.areindex() # Re-index using default index name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Remove an index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.aadrop_vector_index() # Delete index using default name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a custom Vector Store\n", + "A Vector Store can take advantage of relational data to filter similarity searches.\n", + "\n", + "Create a table with custom metadata columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_cloud_sql_pg import Column\n", + "\n", + "# Set table name\n", + "TABLE_NAME = \"vectorstore_custom\"\n", + "\n", + "await engine.ainit_vectorstore_table(\n", + " table_name=TABLE_NAME,\n", + " vector_size=768, # VertexAI model: textembedding-gecko@latest\n", + " metadata_columns=[Column(\"len\", \"INTEGER\")],\n", + ")\n", + "\n", + "\n", + "# Initialize PostgresVectorStore\n", + "custom_store = await PostgresVectorStore.create(\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " embedding_service=embedding,\n", + " metadata_columns=[\"len\"],\n", + " # Connect to a existing VectorStore by customizing the table schema:\n", + " # id_column=\"uuid\",\n", + " # content_column=\"documents\",\n", + " # embedding_column=\"vectors\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents with metadata filter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "# Add texts to the Vector Store\n", + "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", + "metadatas = [{\"len\": len(t)} for t in all_texts]\n", + "ids = [str(uuid.uuid4()) for _ in all_texts]\n", + "await custom_store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)\n", + "\n", + "# Use filter on search\n", + "docs = await custom_store.asimilarity_search_by_vector(query_vector, filter=\"len >= 6\")\n", + "\n", + "print(docs)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } From e96ffb6dc99425e4dafb8ac13730eed253e74c4e Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:32:20 -0400 Subject: [PATCH 06/12] docs: Add index choosing guide (#178) * docs: Add index choosing guide * update link * delete ivf and scann from guide --- docs/_static/index_choosing_decision_tree.png | Bin 0 -> 117924 bytes docs/how_to_choose_an_index_guide.md | 43 ++++++++++++++++++ docs/index.rst | 8 ++++ 3 files changed, 51 insertions(+) create mode 100644 docs/_static/index_choosing_decision_tree.png create mode 100644 docs/how_to_choose_an_index_guide.md diff --git a/docs/_static/index_choosing_decision_tree.png b/docs/_static/index_choosing_decision_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ea368aa2aab8d0be4373c326d5984056ae16ee GIT binary patch literal 117924 zcmeEu^;=bI)a|wj6%^?bq(M-+K@<^??v_@hyFmdF5RmQ?kOt`nK|&ho?rt~TcW%#j z|AqU@{nqoGb2zYFyz71E9CM5@=h9DBTI>!65e5Q*xFi1Rg**arQy77`aSiP%de+NGFF9L?9T+^m zk*xRm3WcmpxZ3EeUG?O`-VI{1b}L;yMZF8ApSQ$X1BJQ2m~_|DlGWzRM9SkuTud4S zi0gLD;+>gj{Moya@6U8(;N9bmKs-c zdLuCZ`zrRO=Z*iql6v&>zt4MqzxU?KzwZh6qy6~rhsp1*tN!;@FZ)%d|Gs)lbOrCf zuh;|s_jdpP?%l+efm)e*UJ;W{#<3Y^T$fR``|dS_rv@2>$Bacgmuk+SN%1IyoqNo* z3+c2*SI3W}iMAX&cjS2FptPaa&A2Y*(xlHPKiBu>m6r;-gU2t@BP=_pSdWj7H^)lZ z2yWlIcdy!E+y3}W$8A4CyI^>IzyG_G&G7<9&&4%0j`Eh)n+Q)Lql!QH5bFL53kzD+ z_H9KZ1Oy|6`mH~I3b!tk3%WX9UYxfd_El6=;5}B)bWmf8?9Y(do~j-1>gqBnx{JhQKq{HdS3GZ&^ctZrE%22-6`s^%)_Yd{F!ootc@iM!MSw8y>ZkK~x z>5MQ#9(MMwml-vgtfR90=I6L?E)mZiD4xYQzRYimyr zHrKbdwmKJRrQ^A6SH89xV4$JBl8`_i9N1sr(zCL$Ic$trZH{pyfXl&Dt&0!#>gwut&2INt>3c2i%4R)69_ut# z?K)Sd;e0JVhpmmqSK|QHB33#RwPK$JfLAow}m zohZPk*Mw&1dUj;hdS&RtwOi`t=6PSn%PlTveaXkBH8eF{XMTR5O>`6yD013eN=u_M zW}YwZ5sc$=$jAQXa=5j4Lp6#;N0~(lt zvT6|*506==&b2f8=~~Egl84~KhsnCXK1p{!F&it{Umwol^0+)dWY((U-u;O~E6sm# zG%xa5jNHxkWN+2*rt15l+?Xf2ADwN6a@APsj=r&lT^=tdtu0eMew>GKMOj^49YQ35 z->EnGnSRR`@mH^Q;M&{E-LRPy&X7q`ZZ#?%Ee*@~{ylrXq=fx6jw@?#Zw~@R#rPAx zw&MptK+kqPbP5Q0lR)v;ufNQ$C;Hssok&hwUr>|C5Hzl%D%ju|3b*N5$p<))z;S5-7~YjZQ-%I zKdjT!)8nx8!K5~{yxhSli8r13bt&u;{4!p)%5&cz7|pCAXbI9P8PcS*GTMcDqQPVW zCd2vX&Gx5j`nygki$~@j(K7^H{BGzQk`j|SV zYsqeXXfR8GmYscIF>D%XpjPL+zcyNI?1O#f-vu{m`;EGOs4`K1d_`+ES+zfXd2yIi zP`9=gfGhG@$K!l|K3M2t=NDFj=BpJ38KHAq*v~-|rC`-O<*+t`QuW>4U3jRfcl|Vr zN08<97bojVIm&~18s#PpYIh`Gzdk#f4=y(wAF6TMt=_I1U)=RL7&TrW%41|^-hzks zc^_5^yJ#_1@-AJUsaE@P7=G#5ZjZzfpHXM@%F0R~IYK;CPhbE2iux5qg%36qM26Y3 z<6X#7d5gN;Zaz4aq_AL80FLeiJ_vRXfx5o>zb(Y1eD*c#`PGncnu5o9U2oYTVl@e| z2%aRv0$FPJLb3J6%gg`}@{kN2rSFFiwk9?525jZ^L{c*8`&UW^rYxsi&Pu@Cp z#rgaD!w2{*XMSG0b=URccv4;a^@5Qf1FRpQFm9c_GAU{LhwiWZ{soQ# z_w$o58Yza|{K;wu2v>$HLpU=0Az#1B7DwGg{LK3@h=V{FYgJe_)X@)q6AggS`+z}? z`igI>;yGb~YT+Dmv4goLjn{6?+t-(I-VcXX*<_ITL34BSrfAOJ^XW2|%?&duJMA|;GpKp^7ATUwb!&1(D2N}FX(ZYLiSc3=4DT3>nx z1PuJ>tl<`UW~UPwMQzqq@KienFJ;_>lGFZ15X)z0nEg*y*U71ERe`8cHNpSOK!e)6O=-l|0gZLm$6M{3r75YvI=LhehQG3{OFr-w30g0=EO z3IWZbQBhHGaT;ARA6aFjrMvB%10av#oRPb`QZ)Vh(K=`;xm`8(o9eo{DNG%MNxeqJ zxDOs^)*mi>VvBhV3xq;t14Oq2n2J(eG!CkGk_aAq~0B3PC72* zp2YJ31sR^>k(rqp?ONv>*ROAk7K@=hwci*4#B6GAt~8)XsjfbCgIGP-7&YvOqz@~H zczN!7S807>;U|=-eC=8&&8D0hii(PWJB+TMM@^+AB|F;M?ui^79l`NOjl?jsp{{^Ut?mNW*ct=H1|OgK*_Jw zCV%h%mb3 zW*PpdW}Pc+3vplzCO$JAG2bV+yuBS=T*O30Bgzlw0M0NmFsQ_lJ3E;>URtn##6)dX z)#!wTgxJ{FurM4F5)xco$oR+P%146%nS^?HcMZaI-rv4>grxxTof@G8yXrK z7@!tbTtox_ z+~f%XK-Ah`j*1=&@PBgzAp)TmX?Pr^<1Tm6kBx(4GuMQ^e*|eZ5K2PP)EUFpWmG($ z1yNjoy2ZD62AJ^u`*$+nRlmPdw1<>WyYsQJ71y+8Q*&Osd7ExKUg+ZJInQ!_^%gIL zBNPc8*T15RuL`tl<>lquU1k8&)8dDqtfH!xJMO{3_VjeIvCLwjWF%A84(V6Jo=7p;9^^MtW-QCKGe>ytW zJ6P%!?~?J^zpf1hP#=tO`gE5xJD!}JoTUSq^4xcT5OydWQgrItU{4#0hrrP-@e~_% z$#<_pEwQq)dinAtu%L)XJpzXlKA8CJajvU<>1&4JT$TE*L8J-}6caU0#k*J&vbW7D`;<^6<>x!(jo8jB8DR$V_Hg-=)D1mY# z=ypOEwmnhVAEPr{Qv+pji5^R>#<382f2lixn4H|gBMVh?R(pPQ8Sz;!{Ep|bX@8xy zHju?;v(yRowc{;2Z{4Td+*}~nS(1K|Z~MP@L12@dY284~ILBE<**Vk3P=m-7>+9advUrgbiUUgk!Mk)$^=9X7{*uY-9WA>D9=KgrO4lUg)hX!4%deZ6f&em zJ}0@KtY##+TOZ#~85tP~BIRS4r&svSU0lP+$L9hNCW2;Ru^k;v0Ef9)flNSgA#yJB z_$n$k3W|n0trLs-bNl54hYP6WPEJnwZWIUMjB+gpcZSc;&MvkuFJuTA({&?a70~Vx z6Q2Sdq7y^?LR6Gc<1=*>VN=r4t-#`BgCO3GVYx(__l%}%FOL4sFP@WNVqzX{O`Za6 zze6r~)=KW7c=zVbn?pG&G6Ks9}Sl4t@Nhks1z{o#)}5v?Qe|2`NXU(m$8?Y^n-qR%T6Zb?%MK&aK1H| z#YSoJeL?LI)L&rziZ)$Zc6683nuIQnj+&L$ixB%R@c39H+;2Q$L$8jL(=#xP0a6Fx zJx+Al;wc?e2SEJw=}v@|iOCwoEW2)_cLJXSuPT-!MG7*V`RP-upPpAhIbn1~sh3K;dPP^f^vwj| zYpY^DC^|YCAOxQI42oY+R5NIVYz}T2!D9p9Km*l)Hjzwf-*TNu*x&ZfGjy~$AW_W-W0?)iB8{t z{IG-{88RRCukWXOP}^8rgC6CxTT`TH(sG#GpYg(i@~WsNtE{OBTLCbdeHK9gj1rU< z@ERq(bPb>SCFPub&;%B>`;qF8j{788sA*{6E*b^H8hJyYd{7*u9-rKGr-7yZtO$hL z$^JTDv_j;f*DWk44erN_QMJzdMBJ8dCadfwCnqCxN?@0nH7j<2M!!t`JE)=qP?AUV zbXpi90Ju-PMqbHXTL%XrAUG&>XS*V+qoZ>g#$`SMnILRP0y(=BXK`|JQdM2OZp)kc z$8oVe0tH&rb#9ExxxYI);#dtOKrBlN=_@M_0mB%O!cB5J7zIfQA@Aty91BHk)OBN| z5Xzhy+Eaj(&Q4USPwj|CAw0+Nz_3WZebbAAkMDHcqCSD+eWZNc&dGW~FbNMUh|PAJ zm!hKktNp;d1T@M_M}gL5E9F40Dfn>bzJ`kKcCyD~yV46d?t=|=>JNY@&>Ah)%DHZU zV^9q;iTw25swHp#gwrduzo(`0rlX^i?HHPypC61xqi6z&7#_F=|JVA3upjPzfBIm3 zFlV^TY+O8yrYrjCBVY$UEGga~F4C_k16P6U9PF@ka0vGI$GCmFv%P%`_zz=2jl56W zwX0Vz{sw!{IW?*1xD5i}gY2nhc(j8=67kxuKx`=}D0rOxZ3V0W4BM`%Q&F;nf|q$^ z2v#9+OEoJi3(87DsCF%~oe@q>`qir+So8*J5Rq0dUNn8Yf$5D(n>y=73BQ|4MNG7` zv^fFwWbQ|^pot-*)U}T3ynlCfnL#Oq1b};KQ8WRH48ZLn`nZn};~h}4@qU4X+VP`J zB;dA820C|+4p<@;$C<$XuJ7}`M?_ra8w(}WQ~oISniL5oAqaFIB#*>YTD&YUuIT6y zxS`d23p&MJGX5ig*X|g$)&`pdArE&zDL{_sC%TP4KU~w-*Wa8T_x8Td!cqW1vjY60 z{`_DZ>SjoI_(Eul>tLbns(h_>Qg}E!)Ia4sb)Qe4*7i>zLd3|N!1HP8=#UiH{T=ei zBxV74g6|z-Wy&HJX6B&JpH=HzkBuSHc6)>zfJTD=qmIoQH7)Ck;{q)^+};JeiQIGcf9+CN+~I1!n4do!XPNmV@9>tc4(R{=oJmlK zB&I#a#iKyCK@rnULoc8blc=o$bEFP*}YO;^) zV95#C9|$_l2EvOvZa%8pMC>c0#z~ulIZG(=ehdVZIXeWZ!F#H-G z9v&U7N?Chf1gHcS5&K&>0q}S9fYC8%9IML8{?Ko|o8BK&Uw?@@w%dfD5uTs!e*(Y4 zN7+)iCL`(=crq@|TxpbT3;2Kbdv{`&QZp8nm@ z&TM;^TCowSpld}_e*(9a9>8O3Fgd=+>loHWC`K$H+Ccfeya2~?&L}f7GQgw|NB@f| zr(nT*hURNkmpT5OgL6s{bW=xr>b$oCkl#{AFB1WAvj?1WvSA0xaGnUOJehdtR^>98 zom3Yb?GiHRF)6RDET2Kns?56^H*S~+rIHCahp!CeyJPO}?lKn4J%ZW@jFO6~=?1=5 z9RMSP*QYzgk*fCy@$gW@ieVlE!T0oZC0%zctYFwFE|Y;*XoV=lCr2eoz@^MN>P#pM zPFOALc6(c!TCH;_Qb3b$!^0K4To*hW?V1a`XK(Kw-n%3rKr4;G**4B&~8fAb-a{zv*G1;(!^)v_B-eE_!|q_N`k*Za1-S`bDw((!cO6;K&LF-v&cf@Le;Ua;qU z?u8|&Iu#}n$`0g@%RIpCKhVk_p+2UaxvY78HT>m;gyD z@e2v6*&THGU3CQ&0P>x$6we0)pRpNtD+Y*ZeuLOD=}jHNi!fp*(s9}3Fa*q;6XXJ=>JH3ceh|DYh{^|Xug zZ4ZmlqUL9d>V`LN-V7pmDnjUSx>*jiyXW_B%fW>~<7tEJy`1j0HV`lt;e~F&m#n2` zz;0^tJ*WHYPM3UCV!pax-2l8^@&Z27TjXtS%UqeX@tV0~j!KpD6G zJJ;N|9No19J`UqNILcYnWZCe;^7q5iXoUdgfZpUlK|XDGmbsrh8-9(B&U9}s#U~=l zZg~y`rUD`lH?g^^D-Q^b1kPKECKR+mOfhI#5Q6OvM^=Bb#<;kIDiHuPQ$IHsNa+5e za}foiw?eL$xLWw%&)gE=q|+S#`VYHr|mY{fN% z1kgJrC^-OiKft=8Xi5zh%W0RtR7?_A=*v6N)A%@t}l>RZ0&z32y)L z#hQkWN+`9YH#X3f_1+Y(vB1m|$R?D@%fNUYp6C04oH8yxz6Wr|_6k6gwITOxkibxP zU%W%(06qveDYuvwWM?k{qxtF6ryOO!h8NnxBA(gWmK3!Yc!N{l)bzse45g#U_$y4L zdaqu+N(NS8`X~VL`2ZALa7r>WGqbgiE_eR^1!m9zdsYvtfbs#whU0CoaMpJ9>_%$I zeIGff@AQ;hO(n_LRe`Ml@I#}Ef?ya>HuVoKA3}%vxE5e z?)?RG9lBzw@6`FBc>?9KQ7McI_8`zj+i5YP?}{=6MG;vx6e@7M8P4}dT3eUFuI+4V zbAsgc!3Li~t;oO+x>Z2+=H})oA3XwN3A~<_#@@V=@bCwQn@YB}wvdAz{9+mX;J2Y# zRDied-o1mM2lUH-a*xg?5e^1CO@NjZk zdu4~sh1EC?(&edRd1Jup+8l>E!K_{L_6Y&WbyX3x(yP*4_cm7dom(Tg2WPjorhpex zOGeTKeg)nIEQp5U1&WrInG)jS;t~;!!LJ6Bf#J;UhX*4pKLNc6YQ{j}yl84HMn?QS z0s_@pzOM-hdCUUR($elw{YVr^B|_68I~ErgM=fG?*@*_H>>*P@vN&kgW+s}JsvD}+ z&GK$0 zttpz|*f$QhK}SiX_#at_ZL%5z+j5!p!3R09+!dEW#Ud&y3Lb+j88I2zRDn)?OCWJD zDc=Ea`hCeLW~G}A4GmLMQ@60ln9e7aLet=T%}q^o=Xv0RKfuRV(R1OW1rW#q-xUPj z1(*`1oZ}z@plg}7m8&9k(<=qi2&8`!^w_ZVz>`2J(V}Zan zUHBamaqb=6WwamY|M_#~#z(Llmh1Y z>DLl)OD9`pk9{#{)$)}47!g13JcEis=QU`;L2w%&pM`S>`*DDoL3SXNw-QW7}oKv%FxdDBXM6S5k-f>iNI_!K_?o(zx*`YS`X z$X(x%zfsE@LUq~Zf&FsBscmn2fV#5X82JmVVFbtvG`KQ9NkBTliQ>**RdF(;Ah5fcTohGogm!*jOW?J$UJBDMRVU^!8GxR^{%7e7 z=~BxGc1t z0=gO2!HENP6L$X%s_stJILQd?hf5mai2xnj0jyGV;Rm^W3@(>VTPO4>Q1LkSCQr5S zTV^JB1(IkK9Z0ZcBW!>{|H)QjY~6(t3)l4M=@Y^mABDxk7sxR*5U}I+4E;78b-Mm=g{& z2cVtw!O9Z(jIv80twTaWxH#*Nz^WX4zdlx)bFO7>Zf;Us3{5zF@YH8#XSsN$@}3+P#l(KG#LB1xNT%IYO#Rad*rw*-QROyTW# zH0ul2!}0<@*JR^s#Lvg`G^f7w2;n<6Ha0G6Ss54m1s?QV;ENzZ7%@ujX3J#s1E_C- zaVTdl2zoXqCWcvapUq?-(|oduejcp+Gbmqq>PPZpzp#Wzz+C%+D*U&Z@B+r_1Slv{ zAPuCum!Lll4a1*5e*!w8+KaINCd5d{m664`A;u0)TyV%{?~dY&Kh~+^hmJ#g4>2}& zZ=MF%etK7;z$sMU{1dC{>gsZnK?(6zZ*0^YfX78mP8n(D)qv(AmqT+yRAc6wR2@9* zt5=^{LAe(Vg7`!t?LT^1iftmbuT4x&f?o{A4Q#;S&uds`%R-kwT*$E5Y}9 zUdv-O_o8%JlB2n$C0lA}u(!#S(*xN?D}n|L4-iH%Pr9L_gYn@*VXe8SkdTytvWl-o z6v^0gDZXS9`7kQd?vKkDzKhSIt=W|%4TKch%+C#LtQXq>SLv#sR=og6;MQG|%r7ij zReRu!LmRa2#cmeSE=rJj!FlxI1_%lYuIc3ivl!pLqe9P{ApI_v*}KAaHMFBx8bwc! zPES$phgdLKdkCN=Z++sQo*tPlcIb`f*^@X3(-X- z_8Y2FH`1i%w7~4Phd4a_Jy;3 zL`fMM8XE4q0mDBrL@`K$+n+{8RPEMVfPV}tt@GkSw*)XIkrn900a$q*6UE!kaL;M5 z@KV3TvA;t?OoVecxKB=i*-}(g#1OAwc4FQGttap*lv+?_3mivM8EfdCz+}iQIK27Y zScR{F=d1$zMLloyF0QI`t@fmnB_td)^>77;+!g-7FI2wJvv3AgWWwsQ(o4}w3tlU5 z%f4+BQ`20cJa@n~ny{?KWu@}Q>xh|84`E`D`+avj50>CjGOM(>IC$07Iej0X$$R70 zEyj7)`b?EpC=mmNISfC%v4i=MpQ{ojk6-3RN^zKu^wz0aqx)i!yU%>M1yDEt#`=^c zEI*F|G8B^{yjiE4A6&2RI`!`Dj=)Et`#4zX38N9uyl&FN1WXCkJ7wdt`>w98&@{-= z%xg2SUgE3lAmw-Lt?M}fE_!}`E}68n4D%tW!3^^}HcRpRPJIpWM#ZQR8^AYeK~C6% z$2w!sGDB?`z733m*uhFJA>&#DiI-pJg;BDx*#P)Mi>7p*QE}`8CIv(FrYGnPRM-B? za!=AWci|+|B_MO*R-jNA_g|EMfqeu&opBz+WfRr$1?ma~yxhW_*82&;?GGP5v>$ROKi zjf_msVnln(p0?&oe13D>?!~)Rb#YUM6a5MUCImm^Cr}`~0PcdHl-U0dqNa@qP zewo3-F;hu90A?@-Y7VDd+S!!b!Q)834NQ7Kxi<(}`I&qtW>YnlCz65i8%n__F_D%} zgi>9))Lk0C%5@1wXLj31?70XPZTbQ{;4l(e=jD*kP?5QR-)IRUt%f25Hog7kSVTp~ z__#~M#~bZOV7i}!!XJ2;3^S0) zsGbxzGjqQD=I&ySuwvTO)yB1O87#tq~8Ukx9&*;&VOPS?$k= z;)-i{^En_uL8AjDYe2q&>S44RgL(`&Ga$SRlAqb?BS@us_wxbKiXOq!4FwqirEfz^ z(9l6uZlJ!<7kTKt0t5$kCB8EnW=23ThoiV6H_#Jkjz(}BgC7j7F6HqUJCI97o4YV- zF(A4mwp{~Ao`OJx}6hiYm0qRxR@GGHF)GlIG+*OSp%R zKMY+t!!rn3-ujbV{>SPivKrZyn&1I}lw{OYS5gDtF6ttxpuhrv6xCv(O>(t{cEbVf zB@h#sv$N;dH85CsjwO)h?hm7iz?C!%q2tX5lP4;nY0zt(c0ZNk(FG62B-Pdhz~KPb z4A907@D{0R*a&8>om&p}9Pm&&8zudiwV}KmZBOhuh{Rw&zYr>VKu9>9_QAVg4tK=? z_nV*D)k%bc35XU9Mq6d2$K}OPo(5wWijolCCS-jfAOv>t2?>RbeMOy2SMP0>8Z581j-d$+3@mO$mdY1Ra;(;LevP=&g`#IA8dqWDvC=; zFfcLY?vR`sN&rzL2eR&WTe5c5)lE4`&}~D5HLCo6ySw!*DGdoUbaW@!@r0Nfc?rdn z5{qf=JI?L>sbax!>JcL`=HL^-q%E`666DkU`}a43SN5GtHAT)WSe7wBHD`5sf!GH{2jD)Hx57B{;)_mxo2IMp#;wNrp?ba%kRIc8i$9= z0T+Qj^uV9#&j@D2dI$D9bm@TVsmvV#;f9XXW^fHykar`^VU~~U(WCjSjKm@#M-YDZ zskOlOs$^xKSZ%o_S6~if55OtQ-6N8!4Xi!bcxXf*4-XycD7v|(UHf4} zAhqJUmcXz=N`pdz6o$4*4h)IxKI?4q!+~LB0z5pUQgsM(jVw-qMW>}0V-)3v8i+yh znnnSTYlGM^s;#SQ6HIeB(o-OR%DJi`Fr5U`X$c8ttTEw%mm(q}FsYUCI!4|o<~2MI zNOx2-kD>`$T%>#B3}A%bK}Sa~Vgl>p-w=mu3MB)>8dz-nz_tFoohW&y0K%uBpupJz z*dH7#cvT#-(gf$MC2p<+tION7e78an%iow2|ygi zn7kThUf;^;1Fs!4ILO$TBs}p@AZl)|t}wPrN=D`m&BKT-Ii(Z`J}}ox;e;X1g`D@^ zg2Hrnulw^hS1BhlI5-$?@9KI6Xb8|q(t~sVK2T)TV8sUX;^mfrC59P}4i68vw!(1g z9`qc6Di(MlW56E+*Sc?Cfj0pK-+*2)v$2k^;~D~^L7%iU(9xNODR2Jcg|PHT z#4e#!r54ju{r&wtJq3;GPe9~?sb-=(% z?O%;ImCP^!lMqTv-G3vmFg^w=T3BAj$HzZCKBgCng7$|mnRBLG>dT;ILa>s+EU&w~ zIE5v_%m=lQkPysLW%NTo2HYG*CMHuLC2-I-MHWO5z;*#~ti|A983-Am7LqDjN(%1o7vN$;_CuDShTR@L0*^p(bV$7% zHKGWuPzo5)hFLa#{u&UIa6);FJ=AbNWv zLX>`8b?<-QZ29j+o&SA}=YQW>0`v3V_5b#LuTR8osCUYOMhxJkm@x^mD5{hwZ>qsF zT=;JCe=nSw6}B~+;^4($i;S33wo*xoEU2d4x0`oP*kMCs@#++rmmUu>(zQeSzgQhGl-jNo2UA_s8?L%RfI%-`$O@@hc7;e zwsQsOFkf-VkxCqLpQn8@P957gnp;p7AiPLNhNU8iI#!eW|31xAgw4}Tnfgle@qJO{6bCz7Lm9t9WS6{o;m+>Y=K?^Mf zZu5RWaOipJXBrm&F8pKd^+41|+5e$?h4u$EX_nBUFG!BCv@oj2FH{Z&aD6Iith%VU zkYV1*M6g!jYLu4uWW=2bNlY5}8k*B;$=TA8RG@jnHvjI4Nsx#9!_@W`_ho>s zK3}QI2Wd14I75b_&m>_R%QT`VXdZ{eu1c_6S=9TKo+eM#7Xog`N6Ib4_@vdsy%qFx zuLjVS`R%9|MLnZzfp~_lC9FbL^kt6gQ=KYxDu1&^hUun15dzdL?eus~Mr}P(?8qp$ z33EUEn{aw;|K8YCvMPN=wTzJ}s_K(=YO!V0@zv+a_zU|&ZcC-?O$wr;_No7Z@9w|Q zASqTN>AGKOq#H(U)gh|FuxJ-!I%g8{n^odN;H~ zk-8b#l_w#q{aqAK1;YnBG^v_oj=tvKxtaVIq0wzL%nJb>HyRCa%BMayV61v$hgPUl z^9c&8_u0G^yzsF%&SHj`{Cx(#8(ramLJXz_UeB z0DjGseSe{%;=d5?+V(rPVNFgiH^51R1zinK>Wsb__R8`OY6Hx5S}v8faMh?1nqr>~ zTlV{5I*FjAguT$RnEkFFCju*7LuK)dy+N30il|^1V;|-_hec-zTn&lDS8X_d>b^-o z9@S#Grw>T0NQer8a{L^oFL;o`@|bBN0m+kt`>8Fe_{j8=I_vOqD#^wKE)oD`Bz0e`u z?(C0>EKAIycBlmTxE+L|@*mVfsZ$=+_T5gv<`s|5o71cN&F=qDhNn-1Qu{jWuP28>Oz0H0p%01wJQN4q%<84|XDa$bd6oZ#C#{k-9GW32-)4q%!$gC` z)9@t-MH@|0*Cf_$MbN|_sHN{7&t=QqWr&2&KYR+iEd1Z%F-(ho)8nZ%Q|72Xi1v~C zsy)dbj_V_;Oi!(&`Lz9xDeU5L^)3!9=jig^j#ttvsl>bt(PF~r6p8eqLU;Z+Z5vL| zVGtsZG)$lx-p31>qC!;@kAJJ7F)H%wxMA&_LRa!i!j}4;^F$}r>NdHdzf?Cp-m^r? zlA*vl8u;{`f7||@3ezf2H4WdN@nxGHbybxeU0@+bp8T_v3Zqaq|1?<^R6& z4eFDA@p_fNDb?ON*fGkVR^zL`$Y0&|LIKK5nJDDa&#o6)RaHizfp~2UR0$ADd9VEn z{qyEt=bMI5Kl=o+VXpG;3`qm+^piU%CI7nQx6F=r`8iqp?*hfBBJ#i^LWa z7o1hO|9{#E0|h($)K%?tp;V(;Qa9~*Yq2To`X{@_sfZ>fm=n&|qm(2gsv|uvbX$CE zSnAklxhdlpy}7}VzPRA$N0?4)ljYGd@(eow43msV5y(cv{} zbX6Hr?O@aQubFwJ-ScpqYSf%{wg~Fu?-aU)DIF)1MOZEf9-_w?OkyxrseR(DO_?J$p^Q?p4dn$$mB zPez984=%CZ+EVVjA`fkhYXyofGx{&A40^hqrMA(~&~bPZ_UcvMa>n6Zo+j)|;-{V2 z@mE=~$yiuWTnao#7J4Ps3g|Agtbcs8c2n$SyT}Ta6^DS?>35FZDL1>>PWs^@w)3kW z&#!&7bxyz*F~+Vh&^hkP&2lNb=%Xww_RYvZJ{XNrG5+F#{Mnu7>=2y!@{Ub$QF)$D z&>#5WF$5%dFOwZ+h|Y!aJ8BaNgdRQYilNW zakIS9+BDPJMn_;T>Udogjlvt-dU2FD__-nbwE%1pwBu^4pIfs|oXQ5q7`~2#?{BjB z&OMVjy*qg;XynI6Tzu}lrgHLQ#$wgPI=9Y-&DF7Y9m_pxf*#VE9Yf^jMKTso-l9j< zey4G3?o%PFNY5{wT+O-Cd19}%JarB6nbJO2=e%X!0e8vp+E?2X_?dl1f%C$?jIWN6 z5BhTLwVU7jiIpE;Xg)~$*_#={HT5kM`+7JYZrWvu!C)Utf7RhN{V|SP_NKAt)2+dq zli#)Yw)gRuYA+gpu?dmcPa4!VJVYC*G+)e4*SS(*b&^_l;jZqB+gxlvI*hn4&xdh! zgJWp^)ZzPONrl<4V2tlUxBKcn1gZ0F*+Cn&SZ*&$MxpLYG~w;Vfc!*9Oz!K+xw!@n zd~68K%K|ZK0R?`#$IM52rN%1E@i|(KMt|{h@I0O_#KxJs&#dA-)))?o?m9T;V{zNP zc)b~4l%1`hMmXK=G@mC*%zLoaO5lEUR;$n&{I*~zfj`j!$?X~Ktt>Di+ajwZSGZJt zye;5RZ!cm$^p`_&Im3Q!ziB+FnTpEhE*iJn+zLy8U=MFe&)b8O?GkZ>$gf7iIGdPy ztS`@cWEb_+@W_njKD;!-X4#b&fiwNec=v{?H@3*{>FkzYyYE%y?u7BYu&=}^U{0bM zyqMjx9pe!T?Bd;>E`PtO6ilJ7hoiof&^b|Nq-MWVNNYj5zox=SEpE%B#W!5wy7)ok zs>mZg+il`u1|_9IuW_#J@uH4IBtc96RJ%IkwZ z?jIi_uzymC--w7wfsq{#)l1|`gbz} zF{fU>Z9j+EGU8vX2?`IX9P%uf}G5ZF`Bhrj}O9g`bAGuEpr` zV4kaFFMB$3U4yDTn~qt%ej|=3XR!hO9DlP7E7+fi(`s$?%P+>dM9haYg?1Tr*9eSf zpIRYu@j6<_@oNt!&UtGK&4#B!9^c>KDAT`XPdZw?A4q$-e?*)l`l9tJw&*?KsVx1= z1WtN>&Zpe`3``6ss(OX7KI6&*J9O zmBWLnX37DzE$SX-2`vkY9BVDr?wUJ)3a?dTZH-M7s*uMuM}|4w7S@Zq3(wlAO{3Ht zv!UR^nS0&xu%E5!fY=0ipY<-u#f(t!=GYItrC;S*0vARX?Dt*g$ocX<1Hz=CilwsE zREsJFY*Dp?4b6Jw>Uhi9(PI4U@HAc=frsg3_qhhw)fIt_*jDY!CYIk~;a_`s&00Sx zG5hLoI$&DVY=?;Vlgk^vy`mgU@d*C*mUS{NMcizE|Gq}9q?%hle-md&SL~)#gul=z zeZTmvXWI2_%PnqLDI&uA+L}32bjiNS6(M zc)}*Lr%QuQX{PWBm76bpt}?+XF0f=|{^+19N&S6S)UZ#7^TsDO#|iVz!eAZG%;y(b zoopFwh%fc)y0zNdR?jR3q@0e9X)OqqoN1OvK1^zzVC^`@k5O~rb$r+)cBt0+@>J64 zU8}a&fti-e+u_KC66t$?9*6exUQw+-8{hA!C*?9c@hs%BT^H)y*i7vg$5+H#CULIS z?aVtotC&fJEj}Q5QlnoWh)XRRU{rtQ&5F0ixA`z0F*R{<-6?v+U-~R7`mAr#mONi4 z^35Pw?9EiKBv%zRzP)Q$%CXLp(^Okfzui<#Za!WbF!wI`bM?36;h*>7+%NKTA3AJe z>FBU6c7N;V8IydhFiv^mt&}Q zG*oN5$XiEzNpKDBe)Dy5r^2!;+{FcpI43*%&s%vz#8-i$p^7y*e)Hev!i z*FLpUQB@^8iM#U{Ui8@tnL>d5>`-R6zT zjBbNv#c(`BlCjPC^|v2Sz|kkZcp`^yJ7j3;cZpBCj4jyMhN?1gTP3DYqgVLdv|zqQ>@{K>y1# zPvtL~az}-3XgNv7&bah#Ac39lr~ZV7+2wB5Gfe50y}`OMSf&F2H# znU!@H9f6lhO5G2uCpnY@S~u?o`(fS=wB+HZDOS0Z!{OblU^s31(^#{i?^0%Qvgli$ zlt}h~Y}8?;0`s&brJvY5`eMY!LDS{UDUDY{vXmH++xlK+1y#Q1HFqG4fBg{9mSu3^ z|MHB{R7pMDA}1Sxoe-USRlBYZF=7AynY+EMJmvgO#U!Ec0Vg-j2i@75sp1`t%q={7 zG=*B_Q!ZS2`PLPT64~{RyVQus>SYopaoLU!#jEiA@(|vl&3EG*CF7(8ltzC=&)ALt z;AYdcy(6uysolSsI`pPtH8;5zO+1v~d0^i67WJ;m?0({_A3w63=ze(mMA+iV(JkMr zmb{t=L~*%SuU@|ru}GaELvp2;wKP@RBGV)9=f%Rl`~BxJq@oE zo!q&x(=o>a0N^}97B^UR97VoGKE8Xx{3lM?oi;J)mfGV8YGaAqNQS6>dbB68ofjAG z2-e_@4j!vcqe8_<;MycS6>g6_l}W2(YHNCuEcT6G*YT3;_oCAk>bsz`KF+jnNIyGv zKphg=aq(xCnw>_W$y-oF(pD{VGVN1(gG?Ds_&wY!8CzuaOU-q5^)FU&cxzLtvMG0e z*X89zdI+vp76`d+HZ@Elwp>kKPU{Xko*sGr+L+fd%i^wbS^rZzE{U|J^3vVvG%hbV zCH#5BM@H~u%{f83Ae*0wQ+IsL(AVCyC_HL)@8oQw(_*>i>E+~Qv9gK-IX6Eyf2Xq9 zWQpEtIMPy1nqoq6Iu=bn4+nJf36*qeGA z&{5xBW{vecX;^ZrH>KZ+p#OSveW)k{_NVpGC*TpZbR26rv^69`!D6{n8{%WV9KqsM zSKxXu3pEllK>I6jHLT-B;4Q|sweeg`v~li%q}lQot~Q=~#?t7{XRAa>WaN6)rw<74 z9ygH7%(mNfmC6;A8=!2Qb>Ca|GA>E_;^fxP@60rtzyXvS_LQ4vf2qoLD%Z_C==9JB z^?(We3dFH(^4pA$_;66nBZucAZu0%Jx9{E6YoL6G{wwKwnv64bl^co~i>8ZJCn1d$ z5(I{q4?a}TI+R6La6kNMwVu+|Tn9?J?cmQjOB_m}0+O^6Tn~XLk<;-&-noLPJIJS* zP{>sgf-#-?D}52en}!E z3_*bR2cJJkCOEQX)fECs@IEuW9?0K$o@mLo?0y)VYu7e7*sGvsc*aLe%RdVL>36dG zq5Vb5#o%ne5?0Rw&+b-p&RUE*O*H3PIU{)1w6YX=P*P~ipyxLJeVlR6aWX2{tN#;V zwIpCXk&;iKOh5M^%cg#b_j_5xd#`{T<|k#R=NIBHcFy+_eI)Fr2x{%R2lrrR`g8z} zmm+BhNc!4fG17kA`Ga_{gUYV;YrXzEMY#J&b;ZbCUKaWoOSN;6iwaaf*zZ^Nk$jt) z$8~LK*s7+`V$m~18plGQRyZUw6MZSh6y-eji`djx@N~ARq({Zaa3kF9t8@=LK4t*c z4H4a*qiBNn_0;W!>5!jYu&eTR+nXMxWX9CEJjfmy_NSo@uXp$-Zg#gO$P>FMZuHg{ zv=_Kc#|^!b^~K26zFF5`$kTRj*F;B?dz_0&%;dVZevyuZyZ}l9nuSB*M`&NLUs4}4 zANeGRUec4%GT@l|X7S0t&FV8#;oHvweC(^J@^H^ir^pO1M>RdU4(qfmo~ySB9RE~N zVhig?>Ijo{QuluEtgDcYYO%qPVW{F)HqI2*HY(v78jc86d6_)a4;PjD-IGxkN+k6v ze6r5(Mz$n>;Jq6DPydxamm7i({lziLV{I?+FsmXMha^0Hz1P=;!dBjvOXi{TfNO9e($G9qr6Mye3slfS6j{n*WX8K@kBnCKXB zyfU$ErCg03{ay^9K;3-ycZ#1KQQzCD*mNN1CyBWIoL zgP041oQE_%-=r5|Td%I6u9-m7+Tw%6`Y%>d==cF)fF_!s*y7p7)bf&q+M{;w2AC< zS-F^~z7>9$4UNJEslsfp3d6tS+iy+_?T_O~cZqPlt*CjJgg2W%S4tT;YJ4K-xcSQs zDv)EZo)GueGcd9}(;p{=?6;E`S%w=oMhs}S%cjl}_~Oew?gp8~AjW%sZ#*G#tA>QD zreCR7a<_Zhi@ws`g_OR{QOp6HVAZf zaAxI+b~OOJkmdRdLam)7mK##@wwFz<+iCKvLXX@RpX%Aa5t1aL8#F;iDDYB0OT!{vQP z+iP4gR})w;6^*+2y~U*VjF99{<}cef?>4YS0DH$BK=q|d4bg1+*cLu-(lfIxbPOFm zb{7}X>cz-&-B~O!a%*Qs<81RrKTiM^(dQGXHzrL6zQV!`=lOqMv`8-M`{%o> zzTWy2;oLo_Vqc8>dqQb(Ea^GE`W>Ii6p~hyU@Yd9tC4n;ZS&y^RMC) zo`;HL5C0my0xpkgC-{#ts3@|}>HA_yRsWpO@a6ID3G(puPTnXfjX3>101WkQ#kE7| zrnpeKm~b(oAixN`k>QXc2>ja{N$K|RSgt+;%H?}DHZpb>^=4>@e{8AY@K)tLQ`(0 zp7OKHt%?p2ZtfR9)#CtI5b!IP5T`31jQ2ROG-v33Nl3g1iQ<2t7WMhRm9ecRGQhy@ z+OfOJ6|`ziHql^@1JRh`y4hNRDUpskOfO!o97f7u$Hm9(FZ|>)LYHs2rUNI(Hq(B) z%W&jPovo_zh!fL`m(=0yV)maNR}nES+zt{agq~m+JuRr&1CF?`HzbxJdTPFAgW>m# z>_yUn#8a!)*w@*6?W-6Q{*Q>p6Ra2Elt%GlpIrFkPhVlRxP%{#-%V~*kNAtRgG-HF`zLG6rG*L>~=bEIPRy7ZMYD(gA=&wCIpFdvz*M~M%ZZ4(UUC{k91^_ zpvTtuwFmv|#f+jKu{~d}p3cBBJVDq-Pt0jnm|=zzz{s~UF#nY6EQ7~vVnRahaeh7T>dm0(=f`ABegwK}7 z@uv?RrVjZkQ)|Aw&H?(sgs#XNM@AO_Q*=#8N3eomGdMkdpo$?yf-d!aT5Bt0dLjYx zB%-xIoNUo6c27YDx7rZ5eg|#kyvl$tKvLaXh#5~+&sA{kua88@COWgQ&l#HIZwsRC zZXxeDi{_5S#2cXRtN8N!-aSQg^-&5V!^gX!st)&{+B&lDNP>7{4;e))psYL6@@sQV z;-o;~nS}ZN3SrtCNGAmkj#N<@4CZ0;0VEt!;ny+KkERvbAxc-~{X0q*1fB*^1K%$L2y5BBS_upq0B#0vROa;)#X*6#vhTYLe4#N8LF{TF=J9Nh`Rh z#73)p+S=yBug3c2SH0a%R%9X}973uV3VRtE@_M799-|^uHM#8D_#6BgqCYWAyGZhm!oBqrDo)S82xN#gUN-|5e2)Y_vHl`3+$i=RtoLkjr2FTelWd z1L=L$*>evd1U{%8)a}!pZ?vh%4n+7(eW_a`gDIe^ej{60vzIe255<@2h;RW@ZDbJP zKWMkStQF0b6ngG7KbL7-f3tPNI4J7rzL&B|Arqu_zaJnwRVIS2~fGBvTj-n_Yt187vDBlm^oam~T1LY?-KbmiXc^wj6?LOluTmvT%}yS!X8 z#2r^UB9yeN0d8DVE$(Mgk}pe--_=x!XW%1%yy&~!GZ6yWFw?uEiOczS;2Y7^4)^D@6wAN;wXyv?o9UZtS{ki~+}I8CN| zh5@qK2Do^830XrIPeH{vQ+c&9iiD-4Bz@f|M^;4GBySvN`F5~;d;Bnn#p_~U+2DiP z`@eM5FL#5BzmP`z4M@iL(e-56_wfxvG1nKCLEIc+|A-}U*uQN)9GCOiPsEzho~X7` zMiYdMA>-kpu-(?YaY`&6K?`4rQCVuc$alMbz;w92jk^M{eW%MPJ8A15`fqxuv620` zxRUOxk3axZD?!dEVl=|L0gpgaM`%xwLcKP!kCD<3C)bVRGG_)P#I0D;56f zvOZmHrtdZHBVRUug`-Ptcpvk`smh?m>b0^)@VM}NQWUI?ucQYE!ME&=;FaO?E z=8(JPgpyl4#fBNhISMkyP7!J>>=(%Wz^lVn(_#*%c^i;-;Oh!n*I|KrB?Wn_ z?PGu5iH~JE8rZfEQ|oeRTv>LB12Z!~9xxciUZb<6R_5LX_y^}e}7N>j585^Rv}CO$~!_$asl zOWyO=%Dgu$usPk;%F*(sGPV$-sJLAF=wR?Ul}|Y*a(x8^BsH!FrQ3dKXi|S@)w$qW z?*Fs^pCQ0syhO&_lHth;D2UCbmp1@WEf%lM3 zDsC{nuyl4Dj}#13Dp2?ze_*=2u(Er`SJ291Wu9~YmHa1P7n5-1 zifQHY;&MzLi#a2ttkSn{N=F2E;;~*$ww}>1ykr&A_^!8}@iR8;72adQn)%p~O_6qz zH61lNXK__nMX!elj3*3lg^Rw4C`pC$G zrN>^zHgoCBhFdHjFwU5qx(JwF#1X2J8#9Q!bWv@lAdrMFc-g<3uahwgb<0O%GQZ$J zOkO>YeXm1+nJ|QO21pSQ`*YX@0U>5q>kobrHjJU9dhs@;=8h=)!Y+ZmN->O@gNeO5 zKk&5Gk*ZeDD6$!tW}KBj2R8T1=IN9Qn(DzkBQt)uSnF$>Pv;$ZbqpY& z9Q1knnqu8HUC{h?7$7`)%}uN5s^(rjI1z2FU|ffr#VnG@SZpY{?awz6y()>&({|g< zaVH_CoohVIYUIzv$ztb2*H*6K&$%5|=ZF1x3@urpxrr2JaIGJlw43-E4(8(E=3s}7 zL?~H9+)xBglow0_`Xvr9>7Skqo1kq&`rX-N^|>E&+TfnAI*gu(A3_GAHCCrn0)w~) zUg=%7H=n0|fUYCk+FU{osyy}OpTS5;A5H^DB|vnxzJv$MRGUS#>|~&F*$CUVXl1_a z<2;N?PsZr@3#TI|tMftP%OubkB@BcXQQkLmRGw7V6rp#t|2VU#a%(nMjdo~O(^hqM z9+4zEaGdC_w{*L{>>Tfn?0#A++j?YuZjm^emk7c0AKwvmx#jV%IcRMrriM~XzEg%b zn5%r5)cz#smD=h$o#+F9y@PF6UTSvs&3=X*=!Uw(Z^v_{K{-Gf2dfj~c3|9_#U`kD;Ct?u z&#QqwiXIhZh}}HpR*#wxbeT~4F!%#TKhw5)+1c7lyd$W&H4Oct67a>{j&G&4Hc+kxJjrke*&u`1NC>E~AH`jw%pHT(96u)$j-q zJALo-B&pY-Os|L0&LRJjM)!Y=MBaBXn@wPFd$a5~M&o56t9wj5D1;YISjtZ}pYwyW z$q-kYesDbRO}-pJrk2zCmQ`e4+<^LvEBgYBedhQdOrLmQhulYRGhz?=hO$&DVH#0p?f zf{qm{TSfT9sr8qN(>~j!{1JbmRvh}}W}}%n`jddW)>6*%lXzdFR}c6;!)C zrZ@*tT$iy_DEPJF{|1oyzX9xrI<7hkaOjqP2LKm!V1Cs}-vK9Z`T`6VPFS07cOIQG zy|^H{Ra27_^_dg&zZXfyh~t)uOF>T%6zS zNo>38o&wqLxe9jM<_}|gpr?44+ki)j(Bh5zwaO1y+e;C)^^W3oIn1udIcsFC$QJUE zpl}}2Uc`Z9xQb()4R09K(w8jWptPp8Fia zZy+9%ZNpZ{%-COLlYA6Kv}pF)4DeR2Eh3eYt_|H4lX?T6WVY0s?r1&;>$$S~S>6Vb zvUOc59$)1>4r4)|eUCKo;6N5Wag#jXJ=!Zw0*VX~N>0z5${|dr?84e#gbli{L5WEb za(AmPC;Ki>e>)y$wXQ|kYfo zJg1@PC@vqrW92yZ!<^gd1Eu!QgeEd(rjp)b{zhK)i|)%GBz-Zsbixsq#=4KylUj$; zJ&ET49)4_ON#N>|5Kxww)_PywzUd{}w@jY1nEPP|Dw9z=J154px4uB+e44G=*S}f3 zjie3R=6Z~`rl-<=GK!X{G~G#rK8{S~TZFr!T8_rgG7~`G6l@vDX)VF?`CGxf_RQk} z0kj$WBLQLmzlajwpn`Q@N)xDD;IDRnhr?c0Wfs zt&83@_wu$ey%ygE{E5>t4oT{S>DjOwH`+3ycfH&S+kg1IL#H?ktZ`8P1G=Cp6iE7* z=9`^v=9t`UbQ`VK2NH_mTV>!ZPBQW*i+UriS7WpOFN$4mz9q4j)lCHD=4JUr`i>iT zTP>ARCHRP#%_chS>Ni<-UsCozgA(=}{gV5k--b?rTLB0WHh8-1(dbJ3Va&D#-A^P@ z{@w&TNCv?m;5)K4iCbrvBnb_1{q^U5y(xo)`$?Z91cE^XOIOAPSb8l#YZuQ&7dLTr zE&V2QM5zn-h3-YyYxooSigtK(V$26IJ{%$<$h{TEnM)5aZEYztm6DHXLbqY!EDhZd zS>eFJ-MP_7j{g%_h%W``*`Z$s9=+UYCn(_{dp{>G>I8!xZfV9Rd@SLVjqTsxl=zz> z-)|$P5Yr8j{#{QmX42GYXsozGkNP6DUa(Z*Nxtl#)$~?+Y%gTVzPY}_PxY<4UZZnv zzqspwvrKfWkGDujo=|s(N*q@@mQw&YD@VP-aqMP+8ozhbt_p8hBmSe8Wj#t(7DvLnDceb~Fdoows<=|kQ zDT~5u4PV>kul=}>%F|l-iv0w8C@nR4X&O8gR&+RLXD+Sy<5x>s_VjW>%#>O@9W@i& zxZNL6LoIGnQKvbWSLC-SMw_6sn3>T2{TC9sN^@)Uk{B2&t;nmG#>1HxlnN%v%)6Lx zx&UFpL_XTENEL#OgL~fS`O@gc>54v}q}YHnOg^qe|J@@xnDMs1DdA_ZuX*y?` zYVw)x`19x5;o^gdaeI-Se2OgT5>5TBV|%f%;4zV98;J1Q&5&KgAA~4Qtyp z@ckk+)_8J-hlk&YfQ48mK$`@DmE!wU_x-hc$$Um@c76VFM=D(@i1r=eqeZZzjd)?o zv*Gm-At`_6PLm7u1j^(VQzsDol6UVK^`lm#tP2-qc> z%6RsQypviugu{vs@Yn;fXUuusCu7Cf%#TN;q!cxnb6%6O@({^hy2b z{QbKH!+7$N*9+D0VP8_^StU9LWqU>pqID za}>t~-ann?+t?h(xS8Zq!9)*wDE{OF`9gy5&{g`AE2n5KcPM*@jL5gCUlsJ~*=YtF zdTr)ou^e3wF0XMf?_p1H{w-O48|XG2A=L22FtC>_1hXG5gcZbJVn~B;iIn* zN6yU5st8i2$oRpnmVGY5^CG}Ow}lz#YlZ@+QzxlfSl4=SD^zkM->IfaeXw_Ad3qTg z5cR{!zVXXNwxILR`%WeH#c839cl=+Tl#;E^Q{cm}PUAzy_)}Aa=E`+-dHL6j+kCiY zJOwxEk`q*%4j3UVE4AIu7blL^qea5f&;$3|cL%QI^}bN6!aoru%Z313`_Y4}e}_wK z4%FQB7*gFyZ5-*fm%@Zmvo~4+Y334TFd}z*NfDMk7eq_!<9o*&kAeP`CGzBp6INHx z4*8WHZi9?Qk2y0_k!AIneyI@>w7OnzX}Mv-xljJVP7Az$Tz3k2{P=ZgfntpFBJKkU z5XA~Uvh?o%y-p$cFT6a%!Fc?Je>UK!hox=)a4JPQD0)kU?CyadoiTg?PQchTPAM|s zd-R%5dr}4uqBh(N={+&XkimD)k@x67dhUJ8c^V4S+vGDLSK;6%H=g6n9e?ImQOs{i zVCd^EYH88{Y*-e16{{3EH%*NNaP)`fyfV)cP_hYxa_Bh+Oa-uy)&C@T=CAeHQ=9Q0 zRUiDv4IB6YrBHoi+4}+~;6-A`NBX)^?Ox;BJs*1+HD&6)X`*{990Nd4b9jWnXy<~# zPsEY7q?uzHUf+4$#WMgRpbQev7&*W0>fv#d;^S@fQ0E>FuAVMy8xL3r?VJGIr|3SQ z(J7n`>Fxf_numv3^1ScKj)VC&k%CO)xUJCDPlX;x8D%+srA}^N%{;*U zN4Bc}H=CB2;omMg`z`y@_)4E+ZbvycW&Qk?)va%)o=UYi()sxA}+b_mp8HO z&dwUdFz)$&6g|y;HaFfmt&BK9%vO3{eSgyo_Tn@k??$myoSq!7(g%Fy7ZCw-r`^Cq zDlp9FYwg}!-sCKd&rc(3K6kPKgzR{RpM+o~1xV@_8-Ip)1B=gEO)pMlvqOL3;S02- z@>^r)_w#~8soNWsfxvS-;jJ|aVbl!zDs!7Y{-@kHZCbfK=I=wmX6|d0!hieg0}GF| zXJZl6u;*Y-U=b0 zq%kC{H7VQG(|5D#$!3pYEjF1~K14OViDCtG@o-!gK6}?X{y9W6{%#c~yg-|i1C|YK zU`9a2q?&)sscr+U_z3_w`JQtCsP!i)ey2Hr0=~Wxa})R5b_bQsx%pg9&bFbxD3XV$ zH2HK9FMCVe)H1atG0;=amZ4RpUdcH0Z^bWL?Mxck4cP#^bLW2kz{yIh5a;z}I>VkX zKK_q1)a?syCZ^`eTC;Ilkda-Rm?-b3ea?&0TT@##{fNQxi5Y03AL^0+7o(?jI}@je zipS-~_#yJrrNo%#*A#ct%@zKSFi;{f`gR_i_$`csz372?3LPxVT^!fdu2!2vr_O>Y zmc;M6`}_VwIRL}zxcvPE6cg&_0nuU}J9yrr9yj>w@23S{Wq*UVa~@0i&%>V$NyK~c zb=g*XUR~B5%yb#@qzSi-XhTK|oFZ5Ld>c5aGP-ePuS%OJNY_{h6+Z|_%NKrJEA{o*j&6oKNXtomc$^l7`*UjaoF~eZL_*Ct(DiatCdn8}< zqGU+Kqm&&SQetVYZtd-8X=tDIB6BZka7VJq&P!e@BZvKItVp(tw&H7iBdRUk(16bA zg_i(YYMv?+_gVYsB2($AOLnwacnIQ2@j39^jgNCpuUXF~<&DYHsa(l@i0jHY+gxU% zUAS@-wZ`9Q)usb7+NKNxw`L9A+-d)! z3i_EJFAzw2rWH$NJixSagaVy0x=|FJeu^E<@aPB`A^-5}#PE)nt#X3i^Q{zr4Qr zE=oUF3rNW~wL%bf1~)AJ<;ajH`7Zn5&)%u9bQ2-w4%1p6@FB7_b^2hohSuNimFk5Q zi`;^}*F3D%*>ll@CLwg?8=ibXeP%mz=sB+Wxf=nfX7^9+n-Nsn^izYdM~|QS4y^Mp z`Fd(E^*=NT^2m;U#8t>*gjNQ@n=bC-hnJ@Anji!b^e?ZE%He?MP_)X+YL94 z#I$cr11%``_IG(m)n4Yno4 zx{03tJ>UeX1E#;d2)NE|zt~<|Dsk3_Oeqi`S#GrI8puf-7h1Y;f2WOG*agIplt?pi zjI6^Wt6VZ|#<_$x&tt0i{NHZi^LK-;4E9pud6*pTdb0O;diE!6;STD$IZLeWx@I@S zpI&+ann1#piBTjZelrx;)k?s>kMh3$l4Q=erp5ad_m2B*UbEz2?9aEj-cH;=v@RVL zwl)qsXj!n&sjf_sJx{P9OHK=k zq46-M)>B~c`wsuxCr_2xUb8*c!rbnwmm*7(^g_}v^xmX4&5Wil-<(90n>I!9`^ZWkJoM!ehLQ#2&IMXjF)uqO&J-c4~ zRu_mD{;fJwKBt{lzW3Qzqr}scr~;%UN+hdT$>n#$o2veu@mkJU%DjlmzrMiM(Ppw z)V~ARbZ__t0f&NuIAgs(!D$IjuuR9!!;sqEPo$$%*zz%~mzTJpzJBp7$CU%Z3{DHl>ntF3;l-gpj*uKZ5Qd)&wt=>%IO@=8@+1jawQ(#~yk zWk2cbM>(Zol+7N-0uS;Sh>9}N7=MLs=K}U)X>i?;S`2%V_TA|!uy0C=q)+6@e^k;k(2R8BC-^x_hDV4~T*HdWVyaqL{M>8Jf@(QxIeY1^ z1|2_@TXA>8pO!FKFGJvXCPWNx z=9w?4vmUZH-DzO@?EYJTTd&EO@0Zqs$x=+hC>Ue2_;+jLtpWt~@cM**Ivtc!Etb!1 zI>np(1`$~s80dLqE^a-0HiQ+GG&c(7`52Q#xkDZHSz1xo+4~)8CycEAGXBcIqDIEK^)&_3;jLEX@#B`;ok8u zu8QwV5-b=>o)4m(E*?P42>L7Z0c$}g%5(acW9EDUs~#WI-rOVe_9zv4`0T&oI(+@_ zT-%Z%H>CWS3SzxHiq+7t4w)cCNiG(TF!xZeH!!oxTQB;~xWq0JiGlEI+*s!v zc8tH0_R@~pY&{K#ut^eQXsPM(UyacL-M}Ynuj^TA z%avPLKP@LbU?bYch?+ujlwBfcb^LjYpeNTX^;mx;&808CbouEY$wOq0@7nS_y5J!f z);=pKZC-*E@|NRVg1n9LI3GE(itu=tJE<^aIVJ!a$yBp{oXYp18&Fu`t&CBQS5=AHvo~#o7c0skilPpET&$W;QKdHnWPpU zP#pP2BKd}gZYZ-<%!+GCkVl~c!AVVlFt7#U5W+rJkJA|yvHgMMMAvjViTw+&&HZ%f zW05i^X=`!+u2c=>6vRxPg^FStVn*o#OiGhTPLoKTI`&lf#e&xQOq=T_AV3x8+TOs; zM0v`gdfWGZ$iKz0H@>K=`jsK1)o`T(^drXtVkAHTpx+m;V%SCl{-4zuCvi9P!L9e+Gf%etuZfTgVsTeReXK ze=kT5VeMx*)-kxRh*aCA*KY$_hq1F3-bb^Z* z;MxvB`*<2?%pvdVWg$YGKXzx`KscjrRfL9Z)G<^5tx}*~;(gE6gZs6 zxMj~kKuDHw+Zvw}DHC%Mt8-(!{GF>cPgCv)qQVXd#k~M5sL{?_ZJbu^SB6_cOhct! z{oEaX05J6s=kf4ZKD{mNWqYt#w;N(p;#=hBq4;{RV+EvTYtNo#Pvq}Vu&6=u^VWUy zuLcH4!%xWX-0gI)p8xY&T-;Xk$=?6800;(h6?m=QwH|W9>(rp_qF}t2xc%Zb@LY${ z60UK2A%Eq3 z9#toMK@kP1Ig$!RgBK|PR^#hS&j0$1F+kPbqW7vLNf$=?;tTjFemIrG*EAiUHv1x< z!#b&(PrqvxNT6krry)u$$l`ji-EwukMulGdXssQ(KG8PRBKo5n%q9}GePE`qV7m;r z)=?F2*q+twSrlzONXwgogFm*hlvy!>ltYwKi6KCb%8Nk|xy;l+$MaBKKT68&BgA>~ z@zqIm>_O3TJ*kkQo@bVH#k3Qk4V$hVi6Zu!^XR9qU?fkM z)8w6n-ce0{qOQl_{ySo7SSvilOk4H9I85N$Z~GHr4pG<_2X}F*@@Qj#(~R!ahO-={1RSupSu0qf-;ORn7X>#(p{e}L?tkuZ2F{6ji>TX0U4qgu4tFbo@ z>p*5jTIt{`C0Ru%1v2u-kHiHf;mKoyWWI2`A%B$S17$a)I#-&IB9HbHU0!$W$!9I* zh&x>lml)xzFYut zfD+nS6IeMUV;e0?2Ahmu5=idGV*7ZklBn~i$K`zjs9v}i@1yOgMf4uzH`YC#6h+RM z7?F_P$qozc4r9d0)KmO^+agP>??3i&%-&k3;!xPK*{i+iqHy1}Au19mu`rB&6tW*o z_*F(0tM-B53@@NjPEB$whGkB_p)qG)dJieGeH%lb8aGti4JW&Mjt;Q!OJ>$Mhoaql zxrS`l8cJ#Tuy3}VA`6MAotIqEOK(pCXBxm_uC<SRSnmj2qZ>(83V=b?a8U%YHPV6As#LuqzZQ>HWKBe+q0&pL$3zc^s_(&W@+a z@&zKNuD3P^8OnFAJF z(NXos^q3yZin>-yD1s zTy!Jd^snPp+}!F$gn`%Pd~nk_IUyJsLWktpq1O`LY-j9aV-{F*mzF|iATA#TK0)Lt z*4U^)pg0^?;7zNBxK`i#c?(!_5n*r|vgh~=W?e08t-w2@)BUdaWE};?-y6iVZyE_k zM8s^gBdL3%1^6^FCVN0d_S!a%uh(tabAR>?Q`0B0wZLTwcwuc(~MGUS}|UF zqSkmC`SOy8Is_s?pjASzC+Y1=LDRK~w=efS?8G1i?d>k`fdg2Cw&K=wmRDmLFvQ!JQ^KCFnd$j}`EPa6n{N_m+YYA%i$p7G13isM1-?lamR;E}Yrsv{ z2stM$W3ry)Fg0*`8#dTovhO3(i!KeY6t6qEziE2`5Z?zvca#v zVEu|-nUP;dop1OOFI{=bH|h$~exsN(%`hSw_l94t;9F>ue#v9%&)>jzdt6T_c72l_Q1izVeWUsdqh-zsxdQ z*0-#t%G}pe!RW;r32@ZNe5U@{l{5tYMUJWc0wYAydzy(#9Qk*ZEn`mHSJzJEx;6;y zc~I#wBJRBWLuEMf_v0I;tLWqqCb83>MkSV(1o=3ao}2kp2A*5TK_lW0%bB(@H8){z zBE*o@;}>&w3wiysrR|^lOEl3J(hXxJiza|#TYIoXznf0t|F54Wc@Li_uAE2%+(?OmEx+(1L?7rys z;|5>kt+B;~z^#Es!qPl#{N6L3+UgF$Gg z^H05*F{Pz^<@$O@Yj%E!u8@NU$XMQ@s+2b(D(~{*1D|2doi@6vX zZZ55}wZ6)Sh-4piR5NdQL{lhR<(ytN0dx7tsqw#N=ZoDn9UbV$l}Q2R>)-1WD_1?{O1c-Pe!AkeCpE_(3fyX3 z#2pXeBNCp=LoAYKtK%cKjO_{rDi!Cn{%*E)loh$)23bC%+r=iN`IM%hVH$X6w$k8qmir}?NB-Y4`%G$kJ&dko2q2=B5KhP+;V zE56fU%Su^cM+*^V&KwyS-TR@hi@Gj}F=ga+tH2_DfTL#=CyvgMAQJ9&NQXkT4R%!u znQr&WVoXOQJkVVc=~df|ClDb=XP*yFnqF3bCtTWvH|-GR)TYwRIaZeH6v_L zP3Alih%=GA$}cGsD-79RUZ8!p5PN=zp{@&h`|gESBH^NTvDUNrAghFSUdx5<%4Wr~ zqS7OAkqFVbsb#kt&Jl5Ux0b5iqgY81B-YJg(+I}HLilQf@^{>i9wpEyo3P~2DETT= z01L!X+_@^^1X`(6Xz&@wVAPsT&e>I8kuijsl*K?O+NNuc8rig`N)N1cmgg5g(Qk(b zaZ?cWZ97HOTy9b&w70z-lY@ybyDa{iKonH2KeyU@vMwBPzTwpNlh(kt+=OW10%WhIY3n`A;- z4lv%Za#)c;AQ5#xcRuYLl>++2T+6VO&?>DRJP_!J^4|zWTB%b(Or$IQ6AfagnPVK~aJQXw@67*rio(?Tig-<-U3E{&;#;5YC4LOsEGjdB3!r_ZMAIeE zH<~c<{Umo8h@IKZGWsa>9@@Lc{+pSA>q*U)UIC}?>duwhlIx#nr_y+Om%6%(vy}a9k$-X_rM}dhbi8NSIF5-+YP+?i3!MCT|xPXHIX#_uA))8WR(6UL%dJB2rss z=4a16^p#=3zvC4Q^lCPD1+`qv!#qW`E3Qz9&CN|@=*Z+6`oqoE>>iO{kpKR+u+A zsPA`K4i->JW8K+sY7_MxDDSbxYx6cZXOZoNKE2L{nxxIGQ_nhd<^lEn- zdl{{@YWM%FE88-H`yJj@7C*C#C!FKG|A9iI8)Ctw0c1imo_7YAlGX#=YDN(BhRN?o+oO}vN1I@mzbF3Knv=tTkTaJCyVbrU)DRj z{m_~YZ*Sbc84(w(a9V7VJ3z(eNtnViPLHckJY;ez0W{_Hi)I_S*;9f-H192ctl=Rc zY`Z>bPe9!SHC;yoxrn)oUf}sli|JO=k*mMA?daorP2Jm`oWzn{x`S)Dz=w;J(Z#8g zw#lZ>KQ$6&z-5JuS2u8mvb_bvzgKchx`+XV_}T}0Hk|K2DQnzbs$^67sDC^>#$gXF1lmtl*&W zFwz!v6Hk?XGcY*PdVENQZ-B~H$#B|AY)>sMec{YG8EGxKxq$vAZB(!Cv(pr>fZw|> zc2mUPc9L%4HEd76?vWzw6S*#VoUEzuX}TDiAGzq&oEMpnj86_3xfkY+Z+kr)`5)pl5>o10Q?+|FQShZ&gN3+wcaH5DXBI z5(T8YQ$f1BbJN{jilEYs{E9i6?j3w2l=k=Fhj5x5+sm1wi&o9V-TlF}^5mlJ z=?R!6K6_PJ9S2cZ4NA2gActRbn7X;Cq@CZ`mkWKxN&P(Z!ROyMPCc50SYpCU5tUoJ zn=CAF`{nQL@I%{NvgEBTt8FXmJac>v6(w7u&fax?M$+$a)K80nZG4O)3$_5DF8V=~ zHHjcMPFVZ$fs7QpXye?RZ|&y##PvTdPZGP09EPib`Esn|Lt9UW2Ct?Za8)}nV!@~R zPJivyeTs`_W*8vazWT=roWsC~Uqo9oNyupQz4<{AdE?z2g!8<63mjT#>urf)I;+6m}TiRfv`BdCup0Ce4yp3blMw(1_S_swHm;xHYY$B{(X+N}R z@@@(xtI4t_MmFo0$Krm}D=|5%SF8-LFxC$Dx%!&qnEm19QKL`CYBHn9qClR3=_k^k z$){UnDEQAhKme`SLH+?2aLYn7AW*=big5%^b9ZBiXeGMLYh|6PJYsDlCQ zmi{^t1UNNQCU1C^hJI^Q(XcR??$_yfktU7#DZwMzIp{*qG!3KLPC^yAt#MY~n2S@= zF93(iq|I~>K=>l`v($Jh($!Gj3fq-Ba)qHE;(F=b@V>-;nap^us)S4nwAi&`fR%yUJ|LG&nBev6xEaq zlq_UMg4jQPAyNrs<4MbB8UkbdkmTdt}C ziI)}t+-)ifqz}M+aF80WSk#oHdXmp-d3o5bzLxoi2M>GtB2KhZ#%~{>ZnwZqd?;I+iM4({H7cL6zuS`QPv2 z9EY=UfF|7V4jJ|Z7WPEY2ygWy9Gg3vSjmm6x9JzYp=Hyh5PtQn{P>v&su2j3Ap7CS zohGw^hE$fRnGd=che(+yVm6Uela!1Zu(IR_F)aXP@GH8@XOzejC>AOLcYJd0{lW#kYKF6PoWp zWgri~ARoJwMN^nxlGm5ZYp6B6)%WV_Z*|_Uj2Z<-@)v#Y3N z;Lpc-EJk%@n$cx*nDA-Qrd2Hw?OlZob$RAuAPtBM6$3B`e&hty&lLGG-TqXZ6!1em z%P`>{z5yFFuSzlu^U)H~;dhhXM^3s(FAA>I&yiV4;gygs8VnybV}>7xxcD4ovIA~QGY*R)XMAYrG^=iTs47+8AE7O;{;e2J z&c2`^qHRy>>dh6Hzjblgo;)f@MP z0DRi6Tc6u@KeWf&RA7+id5KcKw9ZH5wHre58K?tf2NHn49ZGWP5lcOyN~ed8A1YxX zB;qar2Kc2RQ`wLmrj;LXe9OWZCjv`l(ehI54EQAc?m1NScdHS570VRGH>JW4kLA-E zW>!tx&KJnGMIz5N1T+4zs6ASiQznj88xDe=LETji)Q%O{ zJ8`jt?q0$ec^n~_fe{(@xY^9b?A}$4dfx6oNDJ|MHMzqqgn-46vAys2WaiZTd9 zo;=KFNExMe8KS(vpdzT?UN~AE9cLVz_K~}G&yX>s17X2imusucG41{U`$zT>~vWNl^e)uKAj;qPv2JL;Wr z-)K9jUPf$FE$6=>|2hjquTO=Hdm&jA&)A}HqB09%PV+dZg-Pqz7udcS^(P@pvUY{c z@IdXASG8YM!A=Oz4a`&n3NL-n0N|j0(s|u)fF--PvYuY0vE&+we@{i@&O~Z%8!EDL zLWL))$V5c%0z3%>-wOGb$UjtfG!FmoUw51Fe|7TvU zXCJ3KZJi;I|8N9Q<$j#4D=uV@v{SGI=24gk2va|r`9f#JOcYAXVLH9|F^LdP>X2EwN_ zq%&k{IGS?$rPwH$W#s`iG&bUpf}wMs%vfm>NHj{qN-W<{@(DQ?qcRJZqXp*93pl$J z6o`3yI}k|_-q6;cMj8BbMR*gj~ zb>$}&5#Fyg?}{#+GS*z{h2s#UU1Toxa|cGG=sLStj%n70g+y-K91?^?GDnS0ypzCq zNges8#>5b5o34>YLU_SrrfRzC;p+YBeLTH1h2BUOj(TZYXb@X2!-tRnGX0LNCqpWB zV%kJ64GZ7D->i7qkRS8~&W5By@!S1?Tu#sU%!zZ=Sl9i^-Cgqi;s~QGYm+=xIBuSr zPV2|xFp+QX^U75VCd@mwh+pbh!|@-4TwEL>3{@z{_@aXdMPb0)IZqq!@t}rXjOn$Co-Gg7N=nB*I7XAUJCYkw@4va&|n9Ddj zKLjE&YQh^cr82$sL2ah3i_1Zgqj9!)uaf$5Kx5*4Ua6|3=24F&%U&Yuw5xy^0^xD& zTx1!&k3F1>6dTeRF=d`1W~{`5ckx#*>H9qNY}Bf!u~_JSvTVC+j-*#^8~4q)KA$}g ziL|Y7sEjF!TJJ?j0u!edOz{Q8ajiXn*D5E+8D_NFSv0M4GA)z{CexIv{fJvVQZ(um ziIYXN+D!|MMu)PzbVHv=wg)SL=#Glh2{`<~Zx#Iv#~>@6vK(Wu2CLq+-1WF9k#|72 zms>^i)CUQE?z!078QyXpKMc|a{Crd|fVuHYd(8+_N_KabHMe>&X*vS*7th~Ep2bU# zb|N>UeA2n0+kP~WrC^)|8*H)_k_YGg)Ztq7^a;ZZREm-bv06D(%rPSo7@(-nFSo5Z zHl?Hq&GC{Wp3_{Q437nmaU<;m_VXvFp3m3att#)VqbBVr1Y+7cHtuUcdii)om8DJr zqIQ{)WcDX>k8x#{N>ot+GXo=_3;9>Pf&3*i?abO~#Z+;ULr1Z(qi7H9f=#{0l_6I2 zqo0y_Mdho*1)n)t=|)smH^J#lZAUul9(A}0uZ)~5x6-Q&7zfk{(Q}%htSg-_8o7FC zv90WzjWbe!3jNuiTOa#r4}^65$l$6cI(k7EaVp{%{4moEO`vtk4{0yD``4=fhDo}z^(lUJO#@< zaj$}3)u$9=ipG7d-pUqk5~L&U?nq?1;(KtTNIf%@=heMy`>)tRh}F8r*E zX!^tncHn-bk@|r6Q931%CFy&xnC<1Rk$4#QrIHv>6H}TWEO*EWoEw!|QoA>)aL_jM zY2qwI_2w5O-ffC3^0zE2y(iSsRo82{`ULSIW!F6~>myjI0N13%+}s>7F>yz6)@qjG z)*gM%$=&u-%MAdf;y{wZQ1NmwE`aeN)-kcM3UJ1@9;r{2CxL`E5fxTbWyjkSc=bt9 zu>({ps@sn+LKJL!hChKrJC9_Bk~b;ih*k8U1u59Hh!-B~leSRlF0=cKOEaUD?Jp9B2$Kfd<2lUDN2c4vhHvAO*r_udASZp-x z`##<^9dZO>VW72Q1?wLPXY^qFi7cf7FjxKegLHsH4#0}JMB5Vdu@$%I=PpAkP@&L zPn}+Wd*OcZJtxKYs&?QcOqw!LoN`Na-1}-rsi<(SF0-ZUXi|CDs1^OmMjEzdz)8Uz zfQ$L%eNqdEq)q!1oB?P2%lcf!Xvu9nhOb%IhHg9uLwoEA`vsC8lRYZJl8lIfe?0#kOFjA?me;X{p-IZz8WY6V^v+K zIT9=nI9C!qFFalK57#^N*Gsx>#Gomloq$=pFMapDK3V6KiDM}(s~h$`OfHWd48>=6 z*t_M`RqR5W^BweVzC4#X-1jiMm>&U%qIN4qt`Z(A(G+_1M}%Q3R>pzG#^pVlQ;1iK zDo%rd2VTQ8nPRi7mtn?vLdUk@01jx1`&0NY7yYprT+At`_o??K!m6Ab^XROx@AKMfj5wWeO`Wq)b(c^_&Yq*E}1|gm;GP6PT&OLE3*OwOi#Ome?tANK`cyaB` zy9w_LOVf$biL)kKBd>*9c%B~rPMa8!>(+s2SY=rgKO!?j(0R#}a-Vt3gD^qNi$IK$ zC{)}+V*xQKND}WmA?(`B19SP9{PytL@^-!V-7n9PHO?K!KTVxUY@E$w825@k`c)P& z)GVS(;ORIGMT(KcOJ;@fA-XR2>KP-&#ox3|qe36f`WP2oUtgcz+}wB;(9#IFZoT{2 zjUyX(>R?-_NnkhUH6LrgYUXV@b35mK8q3&x2u{L}lzC@F?WoZt={AarOr z|4A6{-q}I8Zy!@3eb-v_X6#h)E~-uK<$k!p^-om7+sVzEu+a)L@%r<;Qt&t|Xl ztV197UE*8MHFL)>UPE{v?3caC?Tf(_0n|&?|#Lhp!=|?+?03t++mOBO_fX{hobmz zYfsEJI4!Kf0T4$YA0O*@ACCoO=a0zp?gHBT`Q_g-GBUnbE2OryYcYeqH;2_oLtH&a znO9ru4tUYQNwtMSSL`re@(&X7y6gbDc+teg4SD66Qbl$g%uwrB$19B9=Lv!f9L}v@ z{7(~050v0>Ean zJe>qRzIMmn&1q={14-O~z@5S(tMBfKa!Peqr)nfTr~mBe zl1?4PNPYX-3IJM}DW~qKpxP}k^+ZP9w%J1nkjqEVyg!2 z7+0lW97eb}_sHM3{&*Za*>#|W^u7Blc8gKcn}1*LVe^$t27m^?AP}vkn(^@P_?-9Z z`D|+~(72GTkrUyI??AZmd5IO1AkSsdU-rn$vAhBGbiXXoLRaOmdIZ4)Ih`1FtV$wu5SG$ z{nf@Zv9XEqo~A~$A+z7x!4s~oVI(6J15COa_JTX7?r26alwe<_F#`;?pXC9rQVeI*fCDN$;68?FuBEh~bNZ?DVp9*nZ? zZq8gh{nxa$wPiAxLci&E?_r&TwVC=vovBp1`c2H=bA0% zcviT^r}gPLOjgAuU`5*VzxHC8S;AJqg>+U~ZeLhVkly@6;L^MW+7&`@HW8EK#^=+D z_D;Wvlgqz-~s~HpXk6!;9DW13)kk$xjEN`>z(pJpMSp} z37-ANep#RH@Lc|N;UCDo)YGdsZ$X;^_upM~*Xc5oYuL?ttYr+Ws)@sgCg-ZFG=n^T zcgo_)=Et?jIhHEZ!X%b5Z^pJkA;M z&T9&-e=1ofx@0%$x^vdOlI^X3p(bFzROx%`?Cc%N3_omePPW{?n$!D%8$FUOvweD5 zjVMM%ue(A)<5giwwfi`gROLK4z`52~4c8>-jQ_)Tkq`4eY#`NJ#K1IOQBGVM^?4=qTK##Mo#zwQo}_p*nkAu18Wf=rav}!n0r)Xzo!twA%fn`J@u2 z0X|RZx0Sq@T^CT(($cznD`XJ>pAVeCQ;JyaGLPdeq$9aF+U#I3ouuwFJVj`^DebOb z`(7Q8oad8jG~0ixuk=Mk`FW(LjywcR2ITH1DqYn|X$CtQ)XqJXBn|g12(Gd;zv3&5Wa!e)K(Ucf5-GjR# z7K(l!6$I08r7(fIy0YbN;MlhU*4ML!kJ-oc&fDLu4+yN5c$;E+p`=y@eZj?dxp&6N z>by0|I>_e;Lb-*jzntp>$x&`}amp;%_#C2RPvBx7M^N4(AQ~=TT4Y`@c)Ni45B6 z2^gd|v)y$&iS*qQa2y1NT5x+~iWMuVc1ry0G+z%M33XXSXIxAcLJ1J=vDyt^(<&rI z5Ag9mpeR#4uXbULSPvy;-ztOog8rvNjxS0nYDyZ%2i%?BR~xlO$<}$wzBb9sm4uf19V@fL9^0L6bgmC-vbOoI z*L5-3%>87X8{LOh#>vX^pG1{@F&$XblT1F0FH|j3(E3{CVf$%UC2mDvF-wD zD9ld~lL?N&f||B{2m6HfVJimX4lrvWGxs2va)R8W)wLh$3sL-*`cJQMX;lj~%Ku&+ z4u*?nbTH&J)H=LH$6g#{OxZWS-Z9yXCbzVnA2h!1ixDWhq2$+a7mPh%O*6}NzcSd>Tkf&Mi}M-0tgM{PKP-O{19zs(gCM3Mc1_?VXCh3+7<4(I-h|8;y~ z_hkg3S_M(3I6-^_+E3^jl^}}RZ+(r2>IZQYEz=(z9S^_1OF7%M^-aX01F6r>ODZsh zj*bq-OS(9IPZz3o!gAu*RGjJZ?v3o#WguUxooKbObkLCVUSMj~GTh0kD{lZxnVk13#z3-Vj0FzEDd#_Q&VLMN^?z+y7Rp z?BAwIv79fCIlpu6Ag`n}^5>8I4h^tn0>#)6eI^`}*bowao~Ms->SNMgzvj#{lQ%vz zdN~byzE+EPwnl@m%#x>n&UVdROhb!9vFe=&{9@!=v6uCrE)j$*qdW}P6D@j5!Dx|= z_M^mj_{&-sQxma>e7v$qI$uIbp$Z>Jv#L{_VpZ*!#Dn(K7k7EC;Y1mAW2WWK9#Lto zH2hsPe5>DnG|$(DzO2>cix2yNlRN(eoZVFI4jDRw^sEIiYZidfR8mPZY8Xu{@zRTy z7*iej|Bd5zSPpuCOSgvE+Y=}gaYBnD^Ex#s^D|lW4hx$lE_!~QVj7ECE=X>5AnPzx zP>B~XRuzgw1C1#^R29i#p8&)B;?ZwB4?btb8E6#fi@znbGA^6ULgwI+ML;TOSQ3rW zfk~i>Cix(?Ul8P;19t$2SuXGMF-HLaS|fknD;&2 zPJ*6XI!-+_?@gmno}Er;u9C4(Hk&SG6UnJDfPe_7?rE#sg9M_A$u?RDp+}1c zsu(PXW~W5^Q_+V(J3|yzj3e=$ztn8Wr(w^QC^ciI1$o{ze?fO-t~TyFM_!r^F+Yb& zJ4;9skCKYiKCWbxy$)~j)W^4)^}6GXLJ!7!oX6B{w=JL0J_2cb)s;HX66w?~Q!&M+ zn(*R2kMz$L1vV~ri6FV4&& zW77Uu$@1Z~Yf^L(C;M@>Xd%(Z@2sMsPZC%Z5RYuR)ApqlM;?b3zN-VdkeMK4Iyw#B zHJV@{VQF0uos>-aPD6v1cRnPH@Zm+OG=7GWkdo_A5c}3<iD?q)WbJc%nHiBrZ@}->NL_2v z*{Pl~mKzy0I$7dB$Y*ty@~-=(u?Z-QQ6L$nnSSCL7G}8^SMNHm?X=+cf;@RBoAX=g zxiFr0@w1{pE7FdV@y-^TrRHiTp7RM>8WJg2&$44S(1j&g(&n;rbJS?Z#T9K~ zAyfZKqtRC?3ZvI8oqiOmIIaxjyHFttr-v>2bBpvgue9tv9e%Aui4Hn-@6Xx=N9VWY zhk}fo4o#3U!~YaWndsW3-5NxDa|g4vMia`e?`^;l#@jN^LiQ#)23?YrY~E4iS;IYl zD=dnJH(iNV#kDjCUIyo}?a%X+QjJc)sG%7PI zGR5+eLL$pG%#MACtS9%`wk#xKONXNvWC|L<1UwWwgercZQjq zv5%VE?=@ojk;jXxN41i@$tUhO9E%zsCOQ7Q%7bp>%G6ZKx_!)0K5M3|XfP2LMo4=@ z20R%tmp0euk#FGJn5bW4|{R6VY}jIS>9M405i!B-IdZ+As?h)cSV z)7UgDi+`(o@8<@9)5&On8S@9#ik7`KgwMrjT!BcK61WkND#th`(!CDuJU!YATbW{+Y;yAgg3x zFBmPOPbw(0q8ocIY5Hq71MmwtZFTqLRn$SF3g(QupAA^We;N!R4+k`}5LL9$@vO1v zXxMX$hUs*nkg*>#PocYtX(dvez{KAr!jRwZeJkc+HSuoV>;q-=j1RxY0DvEU0k37u zk@|$0mC#tEA)sV$xuc^8?INMkinZ10w1Y7l3BOO*UI7s9${sCV7SkvLmB1h!P`S~% ztG7d}ihwYNY^PbWS?djERldUryllhBGxiEMH`JWl!@ea1J!J-qSVejz@FR+COC8DG zYhm)b=}|at0P%JH_sNTIG;M*Pz9-LvRAtcAvZPgcT)>VpGD^gM&cDzCdUI+2$?02!cstH0l{;} zW6QUPO>}xr)$?d*H@o7RlKAxCUDPfTBJ1KBdc@a~>(*@gpcT>0 zu13@Y;`fFO7$FFRJa3`A;6hrgS@HbIP&8SV`;WI8d5pFf{Pa6Ov@*15z?@+$sN*`^$%!e%$h&A~ ztnR0br|3sDUU9HZ)tXc9BuZ1Qza>1CV}R&){^xBW!H5G2NY1i}+zsScs4Cl(sja22!XK-Kgw@1&#u7~xeUX|55@l0x+MWf^Y3 zTkf!!Q!|NJO6BLEB7N7228!A;dIL?vSydJG#1Fm|2lcZ%=+N&ES?VMxX91f|qdf+J z{HGTn2s}O&^G=!cH^kR=3Un+a5>pi-aQ(B|*@Nmd$BQQF*lK%Z#iQs@Ayp`MhrTBz z8j+%!vy+bX!U5+^9gRwHq-8~BFWu&FrX(+id0{kJFDj(ru2%#}dYeb+ZmB zcfhw?RspSb1Gj_o-e1)ec5ETOrl&_S^Pb}3j~h8R8}q2%1f?6=;(0c1Rq8cdlMMdM>`d`!lJEw^@**{i)DU@cYF}_n)TarY^27azB4?vMzKNL2U?L zeG*F~c;71wJ|*XVaR>VY03QoR@paaw^iSWXI;a)Nr{G$Aa_7Ec`!_Nyk0fEr5xJu% z1)!$JMp)G$`5r{JA;o3``rG6zT|~9cRL`G6mwtO>KYJ~Yr2AOKxq}o0xF)K?f9<4l z9hPgJaA1E7DrUf-dH8)r{0Xm(NMZb=tvJ<8BnMfS#D93E9+MUMYl#NjJ9kl^dwO5f z=$}J|;lHk~LWA=+>3w|fF_ME6kh6dM4>uF|ynM8lyrzMwP%QFE$WU&9poNINkHV#W z6PjPk;?GEp{tyci^W_`O4eC3Z)&F^c+MnG-9GpSh3i|gT2S*tM{yI8 zjJicAVT5~SZ=m~4Y*P>P+H)s+_%n08(aajsh7-iRe(k7t5$V0jV0zspQGNfbNEd>e zxC@`lA7bY-6llZz5dQiem)5Un{N5$w_%S9+w}t?MG3|H5W?(+uV0X}+T@<1O z&MIc>UoGu3AL{zV90dWK$rHwTuamtUk0-0W+r$|A9qDyVQq?8o;Ve;|uy4&b^2Yz8?Wyb^eXbO?_GyiXp-20Q@Q=4ehvD9I-@FULC zC?FJy@1l3KVgOe$pwHkm72e;w6+hO`B{-o0C5kY{dM}?>i#g!e>w5ns1k@+39=Fy8 zI)|iH{5$&_9ZV|~R9w5;lzY~WA^2Ddr__44O!(5}?EKOE&4y!&BO%wr_eLci`0IAP zv*{)uJkGXn2Eu?k)*jvAX#(s327v*C!_?`H-eh1hStfR7nc=T)`ATz^NV7Ozu5-;P z^L{X^iJ|+&RvpGng`!XnC7ihzaVAl|1ejAE=Ojtu$nk_bJY17ndz$r?K2S20Gkp;= z$CK$v4!?bK=voWDf2xp*$W#tY07ZXvb>=>9&3(2_Bj{jsTjKF;W{!RO_+;J)B+*4B z{@tY8^ACQx@Xs&-zKAAvEkXbQTLj$d%=O{=7XD{?VKzY z<$umpS4ZYYH5CTkHKR&j>N<nnP5;$XN3d|qKo_(fb0}(4qXBd&g zq;(WIMW)mE=JD0XjTL*(Frvv0{PMF4v5t3LEU(|6h_6v};AV+1i9PM;s;Tnu|KymfoJ$9zqwOiz+U8ofsH9|RvVJd~ zx_Z8H#gN0wdHBAuZJ@SrgittR8P(sPd^59ny09u}`SbJ^`atc}1S9zyxEYHhpmzLV zQ`Kfe%MbEe-t6{44SkbOq&?btoWW8%OMohNf%9*%UVo>5LyAaCpmzViJ1z3I zo67!Ql!wV7lQG?}dtN^|%c$za&P)@v_3kjmf=>_1?PAH0(%y#P^46E11N}m8C=G3= z1KBZrD^%6yu6GF4LSL;Pqxz9|i(&7%Uw*H@{wyTA;N~eUgbJzek13_KUZ{FPT-7Ch)#W$dKM74#quZJ+zl|f@|Fb2V@ao;;+@j*{$y)b`ap!uD z!ft_@oX(bKZz*L8=;#eUw!%A>ZTJ$jSna2&+I3%#~?h%M1M?Izo2BmLM6plpFdBma+X12Xur&%R&InZhZK>uA0u2I8hNpA;DmlT*~?$I%%SvHg1p7wb|nmqT9QJ|f_G2n~GHL*AUL~Q=v zbe*-Itzxn0mroCK7F{g0`I_!c6%*A1Lu73kdrWBG|KwC4VYsq-D6^obc(&TxQAXr@ z!90rH`}oPnc%AbV#{j7{UMpT08z9Hr~&-Tga z+Ct;y^g$seByA~@m$PuX)<9(@Y)*~_(c!)|tao$WWerh&zhLm9s#v4p>{4!IoDRUi zo<0hxQ;ljk*C05b`ug*4{G|)zU!}T{#`F6)tA82JE>=Vqyirp-TOysuRK!m9uXZZm z0a}KuzDvEYBAqqcb9qwh&4;TxAed>KUtOuU+S~Ew<=`P2x3sC{ZwKTTr3p4R>O=LlC_2j5-!@Ck@@)RZbsd#7Jqi(J4}fVj69A-+X)m!uh` zv;T(deCaX%{Ejt5Iw0x)K)3*l%vL~{bxh@$s*4KU=?liZ(Fn2s+V4WQZKvaGEP3kl zBU>=(Puk2up+5jBO{sMKT2!Tp9&Wf3KD}NRvQcFXd&O=)60`s)*AhI>#Fd4#7}B!S zow}0-QOKP(p8pz+v>#Re>2o~LTl?r#mw)%ur&=0Gak*m!5U z*;+}l(`JboZP1s^)m3xrmfsoMFIM1^o1>H%qIdfmUgF%ZNYUtVT_D2zefRzcah;QG zQy70Or}cGAbTH+@Wy!?KP4l~bRHmhvOYq;^0evDio8c%@lUm}F>HMCEf8RX5S}vT( zNO~N@CxW3f*>(f0r&)uyh&G^Sv`pkz7rZ1*C*zl$b8hPuAG#rL3}4J&nSAeD;lP&} zzOBmFAHgq}?(nXpi?|aM(f60F1yf2Z9{A4j=yWoHtHO=137v>KE0Ild05SdXdSDbg|b{)ID34+ zNyK{c;{H$Ewf(s|tdpNxNLHXb5$-Q?N^8 z|K9=j#;~otD!h0_3_CU&C$O_U&U+>%8i(fV$7%`5jX%0k5Q3;L0&ShA_*|$&CVGM} zHNz0+ohdzYce@D=ILSk(M6xH*e0_Nh)BfV#u7QI6kag6}L64_2&J#*fNOV@f)T2{W zd}jOnA)^xyeh6SELH#U*(&uv=X=&3aNXH@T_(pAbGZc+DYIZNs;^3B-0-k?ZqRDA7 z$2ZW2e@}*n{7XbH+8quNh;)TY+}*zWkJZ%7&B*Uba$SD#eeh^vgY7$Y^1Jz)f3|wQ zT%LaqnY)PrT<9x;9}%N@^|qaR(nkG34EAWrmzauGYF9^N)~vdVNxo;xm=H8#-khV2 zcfBQA+gST^SgEIfNmL+ZsU~4H~OFuIZ3k# zYC0Z8L`BK!ugU=hB@L`p8Oim38yx9fKYvT!{PyWiuH$^D4jHwmlzYE;;!L|UK8+Yo z_xP%F;t?FS1}T{schg}o{_y_%qbhmt^VrnU;$@;|L#6fAHdKAx`Gr&k#dOi+5=G-w z^xM|XkP4pb5Kh5bU#r37?U-Hd>O#lnwB7pC?;%oF6IrJE;#`1ec{t>#2!aSV9=yGP zMoV^ZvuW<`RVYJb8I8IzG~WKgx1#7Rqw|+CGau?`E;h&#;_S^|qd`Fv=o53)WdvaG ztIKx?egMGfs&R)zcSnDWfvX|H*rkc4^4CjK)Thr0JUf5DFzfgJ66evX-dr7jo+5>a zZ_eNDtdcHO;+75Z112D(YVmr4dx&X>z-%OP<`bv{s=MQxW!3zV)!Q<9}jMT0g(hoJ9G ziehU1dg>4gSz5{qCA#R|p!A5*+RpHq@bz_~sgl*$js@Q;*IrFX8?4?K(HLI2J*^UB zZ0gPLw^7rc`tMty&u5vsL$Xh3s+L8sQ;r-v^9ra^^jsKU`1M)rWf-K1-d(-^Pw=_W zv9uQ8qPqLB(dh#cjRjgVLln?imW1_HPyf&Hh^e;cjK9X^2%gU&Kpn^6WJn|(p`2pm zs+n3O?e;HK%Jw}P#)M!rUypVQK7Q{Is|#r6BO4cqD9hy>V&@?)wwgM(ElTOHl0G?3 z7FJS-xIpXS^jT<^CYp8$>vhQ~B)LN2*pyp6gWA>_Zc40MZAJnK?gnu)Kv{vPC^d*X;2Ku z^1nU^)nFZdN2fLduPf9yD?iX3SeiX=`xPeSWW+bMfqyqXE;ISAE@Dpxu@f&_#aq-< zQfc(;olfct`9Xs5=!J-}=a{XaG7jFu0FjUIclHeR{0zp;ni*K=7_-&Zm+%F=q>cMr z_hQ##_Y?3Z(!|!A?!gzl)XbFCjWr?>b+6Hr$hh86rYUhkfWs$QXgW+~ejyTV(z`{$ zI}R(PqOMyiCI}7ZqfFa9$Phz!2xmC^yS?!xvt_ZQWMi`VZCmSi{N6p|W8Kl38d!`- zLXCuXTGKOd)lE|S-yJUO`;^2}Vdi&)X4zcbJ#B^s?~PE+xqeX_zU-dDKS)`R$8gx zo7qtD4Q9Lc%zBfC)7g83u{YXIeDC-uo90dC;_DhTdiSos(hzRP-k22SmCkI*TDNhd z3b(@NyiSL?%m4b`%nyu?J!7`VZoKd@Gy0-xD?jap=uv9=@SB_9@+7MW`$;$hb&pa? zlx7A2^V$H20=5@ZtZU;R0ao`ObPR|YBx8sUqUqn{+5CG#+Eah2nso4bzt7v8P(-29 zb|w&!&If^c3CPKRUtD}{<8$`Qb|4Ob&6VF=18D1dpSSO%Yi}dV$=z>3iqJv4Z}WX6 zhJa>!LhT0;Bwcg`%l~eBb^}T0R*jMgNj%zFeNV-&px%ZJx0eu0=Sn)M^sc%Sf~%9+ zXt`N7ht7)y*Cex(D=|U9b*h{(R;lh7B=ybW2itC>ug~SYC1g6&2^0OviHBT6Pukd~ z2NuyeoHO`Wy|oaC&}aLuAy?Pex;GXQ^$d+FG?9*vQM38}Da(j#<}dhk@p>@rZ+w6p zj$pv{Nl;Q7n6M%14f8e+B7BchN8LUtUACnS=XT20D&cgvrZjP0zV|r!c>_NAQ8rZKZ)44 zcg|S_moWA=Cb^WB7dqk1*woK$CI5 z&aaEb@*#WcTM&$IzwA?(j+43Y^|`Q&^$|8ASOjd9Nlp`(p{kdMg{~8Lf2pnE((yj) zO0dfZ4{5QrqnNyw`&HIDel?yrIcwO1zh3oNc$5puO}N_>e_c|0y04tSu{!d103PUJ zkCK;S@_>IRm_>7%YVSdhQev*p!MrQ2RQ|2Sc?U@ zRMfi>6q+U>8qkJw7%^FK=&*9Hs}pRBW9LCO-98XFfLme z28kBDP@$>rMSGv67)%rGKqC>=NP;b>FsYL<#PR{MXkX7~<7)Hwliwe^W84DGXOL1T+xWS1Z08zzcJyH<_tUHXy1+!i~gR_u;EgyAlu5X&6ferQ!V zin71J{cNZ3)guJkwOcP>V=8;~8X6HzaV0DKR9->V%RAN%cEa?h!J(7jbMyzC1*WO-fc+FVODkMq|^A(=MzZXg9~09YvLno@6@`Dg?8{r z+3-8w13+S*<8Dk;o!0fR8Z%Po3ZR5_;_s-gBYriZK7BFXgp2!#e%@WFp?QnJDKI5@*b5t8qeShJfdf_0K@rs`??H72ykO|#n7 z&pC`H$cFH&=@yfEpwEQ5IzIk4Nv_d|XMD`J0M~v#qCE}19UiEiSV3vIuYl7LJeM3S z8e=ShV1a*BFxoL}vJUxxkW81fYK%;O4ka!Su6*GG*766Lb+0x1Z(h8=n~0r(s4!5; zuqa)T7esPa=9V#bVR7xNRmpV-S_hU?x?Yjaa_pU1!;=GvSg3_zu3Jx?66A3vu@t<0LcI*CH3tm?v{q_=xD*GkkM;FmKM^gr-4FSzK zj{qY}phx{~$F7dy?KU;AlYBGV7VSxyYm8vLo`2;l0PQH4Gl^~}Dijxh>SK0p*`=b-2gjm;1bXyN&{cgh65#hsQc9aY zD(|Qv?Pca97hFtPXXpp!&l&8;-=B5_pAg~u7Zt0*I8h&mhp7eX*guqrTK7G_X|)v` z=oM(!T%wbUOdAgHAB)f$d&~khj8A*Ww;otY@Gd6gVHwV|o5ry>6~7%bLz2zg&$|yN z?mvF#LPMX(Vdi2LY-%>0ecnBoCY3`nTtV1Vg}Hc?;ggUOnmj!xI5k+#`Qk>LL(Y{% zLsZ>qwd6Zz2Ss(bgavHK0_EEq$>887uk8nro8JZ!#JnEd49l3E&7u$sZ7!P?`@g+w z&EVC{DgG-x-;#O-)dh6Ps$UobuOV3V{hC_z?tGAsmD7x`)icIuN-<*BdWIFJwmHrp z#?fa_Z@o&aRJCO~zj$PaH6X8~u~b;?DDnJ$_HMwne^s+Yn7}KgCE8k8LS&e4?ma*R7iYpBXX&q(_C?>7g1otr>6`SdHTgf zB8gmLd^*A7tIUbVm}YYK4zwihRl#>=AQj7=-eg(cb#01oqQSqqmq(VZdvrfmN}vaS ze{nzh^|vT;mDm^({t_+sFHq9LQ7Y*!FC27+RQjv>XP<4i2q?2vG)q`ZryR^*##PE0jzp3@mO|z zUPp=4hmtaF?k~HP5?(*y|Hso=Kt=hyZFmX6AWS+`q>=7cq)TGyQo5y^MNp8IZcyo1 zx??G6q&r1Qx>=U|2K;`1JjY|q*?r%sC+3;^@)$lGW_|$W;t#N?fXxk;!qMkCZSJOb_xm9;Jc~=n9Fc!GZ&=8 zm2=&?h}Tv^eE~*yeh=)@c_&si7}f;8a%JXZ$Z`s8{ez=N3{^PUV0H!))JrlqK!@vj z-Sm+}{a)0H7S$_JbEUzeGLd-XqyXgMv`ao+m!dAW2%(BV*Fld7JWw3G_d zjoMFmDTFX2%71wkMTbbnqUN?pDeB(k>}O6I zX`WwX2RB}o?{r#Ty59DgW_+?_@)ETWoMzgPzRAR>n^i>;bd;aE;l}^d;Di)FR z9og_hV*Yj`HV&-JArwp590u#!wj^LMOgnEfc$By|CrA1k3SoyQc+MM2L>Uj9keUd) zdl+cE0Sn`hm|M-1^D$+Q=H4(5BkaeDlDAicZe-COereqol-8JPH|sNh9CLOH zGnF4}qYkO3%eIr0!%eR~@2==-MXIeUKb2^_LC}|S)rM`N->4vTR{Y4RKsl}#gB65i zcgXA7Dy_2zQy<3yToE3*>JX#8{uMEY2nUfobN0b*>Xw7B`$4z~&S=bXzW3R}s^-oM z=fB*CQ%OIjUubSv42>~qH~#HFrZyl&L<(?1gN&c!W!YXV#dM&_d7SIh&z=&6&{#T+ z9=PbFRGM`0yn5D;IQ!hAS%m4SdxlRba@03bNnaz0XQQ#-6CorZb1y7FQoL%H`8GO) zl9Fn5#%$khaoYH7TVX-)zgmDw>-dAgMl>B*fBNhh{v*t07P?KHJbzsa-t3CocR(`m z29*n}kB$wm-}uIzz%Sm5Nz@FXH0;cS<1Am<)UyE9)YqHgB zA4Bu%(*vps^sL~8Or5_(n2ck){unx^6=BQM$l_7(NT)-&`uu_|qRN2G^rx}p%tQFMHn@(eKIHpU8WvO^H+6`rUHl+M$ z+T|7w*C-T1RBU-Ll|riS=RkAYq5@BJWp0aDpb9VHgV4E8fNHS5f%v z)$rfnj^}%kljF#rG*QNYcIMCWMrrNA;(%E>Rywh$wQ&uTEQmkNS13vJpvpfLGi(O$ zNS8>{|Bx}})W&@BP`Orcza4hq5SEc$sKxIx7IxHw7Ls7Uy5vC_tSEz0!N}F}NZ?ZP zk#5z#@&aWrmj5$zPg&m(MOc2$tGF^?{#IJ=dJ&n%1J__RYY7D~kjKPHAiP$NyKpvw z@%xwziW!(0m9@SUzU*EB-{4Z9pE^)-HrxM;jPtnm>nc(7fbc*Gbd@ZxuD>RK}MA;W+J={+9$^deb3eS}xZLiV74Yq9!9f7LX&X#Ju zyv?Ah7ELi%h9N2w?o9$$uw{?$1r;0A;yg14ISRI4jWyrq-YpHEfLDNn`&T5#$+n+4gmJg8l=XJbnY??FN(NRvi{*=e33J3sCZ zwGfaVM5V{&4gJw?L=iPLm5hS`R#Md9%iyNr-o0J?L!>*J=>2aziLa^N%iIkoT%rgK zvLz*s(@=qWpZ{H`-)yZ+Tu=3ry!T^(X#gOC)VYss=aVblt)<>L>RKPomE(3R1cx`O zF}mu6b|h2du5&r|f_xn0{2X-^T4U!fRA`Y-|*`3gU1A!WRV_A1~OH103AIUa7-?xSf-hOf#D0z zWNQoCYyT!GZfuZ)HHk~+p&LLkd+467FZvd$8Q7||YClH$YlWkcDun7Or_Q8sbZYEO zZh%@5erU)eD11}$p_1sg)U}(4Uq=JJX?yQFpt-+aHI%DS{h+4ezLuMBCQpLEaXo{> zd>@7YcabLRI18&$f9%l)VV}^($K>yYwfouOrsz4jc}$(c?}0|Eg%f|nGlpmM?1iE1 zHgq&>%dwLBo@apqrVV?CCA}dTeL~$q2eH2{hSXG?z+L_HL9?W^Sg}my1|gTxM?9eu zjW3U8O?P&$?+IO;p-E7mzw`tDZ>J*&D7baf8hc*+Ch&3+Pa*2FPwt=-7mjMBhLT}IR-ScEB z6rW^tii-%3!w7EGElg|&JiKkYIyADW7@9QUfj?v>!E0IAX%WOAtGo^BIilq(TFTE( zL+e*2wcCw0Czg)fc>~72?XVgA%r7JKJl;19aJCk5eE+ef!_<2WcK9Z}tA+2H3ieZ| z|7zN-lIilY%q^{9*!4RSrkOU98MpaaBA=9Z#U0+ayF)zeZ9eq78P+gf)BiL^OVEk^ zn{q#*a;&hPD_P8RTYCv$G?tV*wcm=L#D#_%fWX^*xSFTz!-MUep*4 z`#NZ4?p!(a<9*ezlD4j9Buo&Yiw*WgG77kIKFJxNhvxMJZ>d?jNq=k`p9fy|AHr{f zp(;D&<#hA9`uOeOu?35F#u8-c)}V$H(5zfj~)A#nW>u9l10msDk* zOh4%e8SF;QBls38nJ)r__NTA|$yJPqE1*unh>ODmo>atkA56!kqg83|j(wDDC;JR@ zwhUZ}*>0tOy!%;l>y~}s51ElKUlsW?_9Xh;86fwcLiCC@kE)&AQ|w1_19*;y7oCF{ zM$OOYeb}TZCb#U@L$J`k@=nluMZMT|=7U;PXuZx)A_oSae$RlDqrse=8(8eNQvL~+Pf39c~i8tAZ@Es?)Wb{=i+ z3u7LRc!uHo)Xb^2JwMIeH7eMmZ$y!p>^|lzThpf`uGyVNQ zs0sx!0qzg^-Qm~3ixRr7YrOqmF6gC!kHRMf{4#&d+0Kf65_QHeC0d8s?I*1MeO_1? zISemPPE%mU8MYh=Ui+mSW%%3M6CMW2+1vc-h_;b6+pSR)|1`?Nc@015bY7awhFBN< zcafij8TsOU5Pz^s{C@(H?-BfVCJ5D#&0hA}yzV7s45-XBO#`nd2dxNZt zd+}eip#Q*1l+{3~q*sf#q5Ax;p&eEXAcCWkV+rd8Z@07&o3&4fSLZy`T~qyX@G9q{ zgf2+sQvX-syFY%5^uCp7pr7d&Yxt+0ELH;3uQRE$0^D}ecW>hcD_X#)jWjG?hHVPn zpDy(K@aoTt_NF&qo>ybW^;ojz7!gN0KKt{U_$!~|^Y<@rb11qOK3>n^9iQoCPtW}US{srQlLf^et|bDFdTdy)e4ZwkBT{AjI&8^! zyj(lJKj__ZzM36=P3*_irLE3f$M6OBwsEWB$c67jN0E@an#Qh+h;`rhCvK(qr=On> z$*lqQcRAjFxwQkaz>+DH9rXjVBet=?+miXix-((P`o2D;E$2tFD=Z0T#_~{O!?)CK zO`5HW*tcpVFSjAGkxN{`-tHz-)mZ|oo+m4-9BKW(1bZ(lab&U19qVNw&wt&&W~F`| z^UJ!SlzsYoKtI#j5=vLTx5?kmm(jSTo+bY%cKDZzfWGx5Qp`1KnIwsP$hDFH$usTd zf50h_(bkH_`aqMoPQTXlp^uGrzzH^1Cp$0pBQ{j3q@0O^r+vVuWAYb@J+xNNeZx+d zBUtj&tXM@I7pG!87=H&Wn?|eVNJ)t6%u%)n!XBohEb{>W%+Ae+s#knxnZTDiJIcSspTg$B^uL}GRf+(tg=Je zF=Mn|CeQe@xi;u&MjG`ILs&!AJXS3JBwwTGC^RF{`r@DFsrr71O)|uZ?AVBSfyd1H z>an;IHG}#>vWF0b=8EsrFAUH{-!Slzk#Rjb;Qt;s$J?7vE*^D0T-AA7==Dbs@}i5; zjhcovMgk+jV}=64P*J<~Jff7Ti+QD>NF8zLI>Q_n(4zxaa|m?W;3@aGdY9Z&TJ_!5 zCN7vQOF18H#ksDTujvi6)d?Ik$mlJ*<&Id;1Z^&LFiLmpc0#}s%^mGtTqbkl3qZ>_ ztX{7bpdb-)E8P?~(s}RgyfQW_%ODeE!I0;9e%`N6&WxqODL5B^dV9@*gcJOie%g6x z{654EGRXycEjgShn5{awP-t4Y(PMQPV@=ypYj1tpRL5(h;hmCo{CYT~%>*u#SYkR| zYpfmHhjg74D@x%%Kh;kE6ZMnkR;-S~YAiIk3a(ds9%t*@(!zgCX=dzg=NA@%l$8Z1u@P}86DWSQS>T}r)mbiE*7(tct3~lwDa~di zF2gC_XwA4{Id7IIx;5%PY;V8Jm6=DxqZCu-?h_FRAAMhC@?H@y6X}_VF)4z(Z6)23 zldy&^lX7Hlem?1iYXF zIAG>?vQSV^(9xM`+r9_(ZOqTy%ou!5a#zEW4@_Z@Z}Mk0+}hS_dz($g zIjh1al^sw&>N^mI9vQWZ(+f}wIk`E()2KP*)%3wQ9%}4{S}hW(@SGcmfXtTcy)b&~ zRCkZ#>ZEK3a3sRb+=cVI&s48*ad5R>Pb#t;))6aNNSRaq81q%EF$BNybYZ|U>*N$W zbxOPat#Uk1rn+3&g<;kV>bLBb>2b+~^|3r70b*}gwXtbnRph*I_3J_@p|6jV?8?za9^^&k&Uv%%ORV(f#pAxTi%Z%f;X#+B z3M!hp!|n?n^y&o!ZPv_14ost3`K#RH62rx*Y+I6*m_($5k_6PCcwQDcQ@UP73CW24 zpefN+lALr}fi~drcLLNIb6JaQ4HEjb{+RP#-_8<9^Iq-wxJX0%%LJm3eGm{ncfLKV z4vD|;D}XM`B)O~rv45BQm^2VM%+;@gkxMZ~nhc#n`{9;nyqkU5a~JN18Rna?6A7gs zj1589>zWy8>pl5cB4B{b|G9Wn#n*x2>&|)RmJFQGKh(~=#w)Rxe}-&*efEDmr*2s1 zaGd#BcljfhlBf_9hv9j=d*}>3-O-2LON%_0V;)`? z==Z`+SuT&G=3&X!hkG@vYdj$YhBD)NkUT~ii#&!G_=MtWP$i}4INa;1NMYg2A0(65 z>*?2x+eUR86TKo5}H%S?sSVoH$u$eGMLRoIsZ!-$=X7=a5>&+}p-v7nn1voG^LMfnz^)P^ z_2k`l*Cr~rwwx-6*R;o(;}1@jo~1F(jv=&Jq|uQ-3M=_}4t(HQD-#hRESDEdKF30* zVAK0zktJt%UKahgJL3Rgfl9)@;5(Uqdc= z_+1e*qg`7S)Uv$!LCJ8Mdy2zayGI(Z5Ed00P(Ak5Rvxs?+1h8%kH@mcpZM@6ZT)R- z03_$>&=h-*@k_B9rno1|fh6+B&9nDRU?UCl_oJ3GAi;qeb$=y|&wyn~ygX$5@nwB~ zy=A5VY{QfSp1YkNXH(pfgT!*Z-3j7fK3VG7?}AdC#f_>-%(m zMVMPpO;JO@UaSbtT`*sbm{purW^GRt+XF)aHI6Z%Y;4~+CgQX>AE}E1%2xO|gJw>l zx{<~|E3C&W4F$lR4}Zu;q2%-TR4?w7BIRNl!Ks?Z2Gv!F%_GCAT3~(Vk|VIbA$RYL z%foV1b|@$axta86}zcFt#SU{ z82;C%J^P^GJzQs;^#@#K{ux`Kv|qdY`jE%B7YqF~$+VK{-~IxQEi=b|p4Ytqwl_%{ zpB{)i>nZhH&4~~jc1cOCaT>KmI|Ty~m_`o5U*0a=A%yODB%>wcqET&<`J++~lkw_? zxHGlq2fj#!-wXLJ7Vd2)O+I6jt!JxhS^k4Ar1mnlHjyD<*8PK`mb^Hhp>MO18XnWj zUR!RjP!3+J>rO+z9cU9(W2^41K(MNlIXDs>ht}CWvApN1&@dU2q8+ov#xr|Ibic{0 zQb**1vW~m3kI(a|$f{J_oR{Z0#wi{ta-#sLOn5P3dvcO^YvrB~}D@llRaINt}Lt*(gHRl5}UL9iAIWQAo}C z56bHmb!_-Er7h~pg2@J~YL4S3Zi7RuBMNx6N#c5;2|Ff;7`3tQ~J2jVEv5SWOT_^IwD?e{pw;;3$CBe{@Bk~_XcaU&F>Q1g!sg%VihT25wf9N*}?ez!fC z*bS~u`2s%(LNj}j(`W_4zrvrx3*lyihgLv>CUZV_dvC`N)i^#5~^_Du_M4 zF}U>uKu1Y_3AYv{C=bIrq3K0%CdRp1 z`#n&J3`@OuCHg_be%wYFty#uUTNl2xa-Z0+SZ#bvEk;_eXzsRe?+2(o{&6A{U ztNAh(pS;K)f6jGcAi!UJGL9wMzFofAdX~cNAunz`HI)f+v8c*sdQEI+J5Af$-3W)N#XgtCm)Q zz38B+ychOm2$VaD!TsArr1-*R(s9G(HwV%crHl$@ZQyh-^B5TKhJJ}w%#g0%3m<;; z$sb(_1+I=P6OO1syGlL%DRFqUD1NnR)rK+kDBbB|vyMVrS|%$wx%I+jJK^gPG@nN` zHYS?j$qDvnE<~GNxMcb$+pFDFcC-VW7kD3^i3V_+jP+4Q>w&C5jL&~pVuKYKpLcg+ zI2E)6Y7rJ8+3{@`T;S}#3CT{kJc`hz=8I3kr5K=8+%Js&l#`u?ox7Q_wL|3Z3ap+L z6i%(eE(@pi=5u#^f`(cu?yVY=PYfIW&WZnI6sq>*{^R$YXAs_MkLkW&mX7?biTbck z{^AYC*(kJz=7~xbN_)~WoIl^3oM0bQx^00~7j4)v;}};2 z`^t_xz?g2C_KK=7tb5_r;pjKTNA}VAJuyBdhUPoWku{xyNwyC6Gs8gPgKq_pKAS zCdg-AlkCUQNwE_)vdl8?Vkjzg5@ib;booi-U5WC07@*~oM&QklxnBKB{1FyNZpr-5 z{dw_MRW93CVd)jWnp*gK?0Wly77d-(?*kNyJoh#P6lWS>XYC+%$kwvaaWZ&MRPc~b#i;2eUxA9@ z7_6&Hn@NtUuv@>K`O!GTFrB@)`~Is1aH!J>@nicW@pI-wKNY{<0+Q0ybaNu3o@5@; zlObU0xXC4&JP0$pAgaKZt3o&G0F@Wk=z!J~41>(*!N z=YC>EBnOf{F(V~OwxY#Dx0t`n*EvV+xT$8UT_3#l>%;Q)9)>p@p_mF*EX+5*Y-@#yAN~HH0d{>onSgshdzF(~?@60VokSkrm zaFa2INa^9P>f9f=$Xloq+1qDHYrL*+-cV#}1A+A?X_sGLg2In#pRr2m6vFc`-)d?Z zRgK-{{Uyq>Y)|`x5k`()KJ(2)bsM-gg>)fTxq&Mn>bQ*Lb6Al};pd{IrDbN`evkZo z|Fzb~E7Kjs;$q(R`Bh_o!?0vSPo)l9dlu3Ayl3T^yrO^ay!cd!_L_v0`(B3kousd& zvg_eYUqz*!efm_6dx!MlYxGZguS1)w4GgtQ3z+58Jo%&T^e}Wvi~!on4~F%K0KOhIG7WYhz7@I>2Hh$$})& zq<@-GPjk;iY2bKuY4T!q zd;H`9(%j5u?wk(;4X$z8za2{M=hdJ~zM*Z>8}#Y9$95yH)`PNC&v|{+(7`O}l;6T` zxn^C3oV7>rv+Z`M;;Mf|eynMvv?Y9z*Mrhk;{}D=aYSQwRF@93$AlfNW`6S}Vl~Zm z0vF3-on+;ur{n@T(dG_pFE;?rrZNR2O6vl4WW#Ew#^IWK_RLWU?1s*k)0?8Ke|bs% z?&z0!n%jRc*jk5{XcW|b&n^6J-i$cIr6edH11b|t9@||8bb^-Q4WwGyRocaO*Yfx? zPKzz5xG6HU(VCX*XCpvaJtk0k!w0`j+{vvc?~D&6cFHoU>eVP@w2b~s*}ZB%>QvF* ziL4?@H4X`3+@NRUrAv1e&m=Q1JA1tSdwEk-UlA&wLduy+I}X(CpkKI5%H|P!bX0uZ zFE{{(lA!1VhDm`nrz# z*z_icy>V|=;HE9?&TGlS2(OEmF9j7BbYoPjaTb#h7@zNGU78fJpIar(nuU-_MxJ_{ z`F%Gie_n|?z!4dU5bUoloOJ$$c+8rImn)n%b${(HgeLcO)1TYVfC7t*>NPY6`q^6L zr(;&dGkSBCc7{#eAgHR;bU4>o9_w3rJx$FMc>UH)Gm(mlx-I;?zIS86B;R!6R7q|B z$=v|4h2o0+diYKm{Be*TOBP z*6BIz@|dl;>RD4#ZwH3_d~g^ka4xFPZnELNEQ^408f3SP5-D+-;b3E-8Q4=@%jA5^ zc*3q`+{rB|C#gk#htEW%eJY|1)7B#`Mc^xIYWP%82^G-1GssA*Fn!`YN}{4Wq3Zwm zcgq?Cf_^P>)cyJyz<+H<=YXR_|7e}AVtUi}^t}Q}4^=F6p1c}*0{M*#MjMDTg}!bN z!iKZ(wOdJbv;DA_gM98fJ$k?;2_;mTczNs#)WH0&g^&PS?oRw{KHLZneQp|wg0rZ6 zVSf)VP||$^SAu?iVmGOQ4Vl3E_tlD=1~0bs(v!1eFr3A@F>x2tdo>vK^X}xoEX@1b zlEDaRO8WPTI^y8hmXc+by4lg!trvzdcPy8sx!%c#=kphQZB@sG>~i0}*36-^eW(6T z{mCni^w;_RR)oa)9}ANha3LfTzFj2}pR^%D76$KhYr05C64{GE^mu zFmf+lm!JMvV3$j~nDe!e!8cJQ-=D6o5CWGi-H{K*gg`;}lgWlYwKTe_Wu1F>`#gw0ha0f18F7!V2Eo3D!!_j(gh z8DChgw_UbY&xX@ZB7`u4|3UkeqjV3+f4Pa-uQ9i=md9qX z{=7{O7XpdA>G-Cw524`0R^q3kDjAKH-HZhj0%J-9^S`Fo7K49x2ebBD^v$gGA%F6L zlgnnk#aT>d*4$Vy>WB-iJ&|w->h-4L0oNNVBXnC<3`jKX&6l&Hhbak~W%1|T%cnIQ z$4f}oZ>PO0l>aUL^vilnUigGmla%pU7X(7X{IB`@>vc2e6Ii$2m&ce{+N1H=arC~D zu13WJQfi>;AVYLB{u9#g)PDgUo|V{yqj5P~rYD~fP&FhNg1)JEcm1F^{~lELp|iY- z9Q55Z5jQEvx?Fb|bt1sARps5DGG|kvgmON;Zp-wqt|McYQl1qwJxaB}Lp*c9)1te3XQv;=%P7>pZy8D`R`$%#}S=%Z^wg zs-S&L)Zwif-#SVJwN#CH_89{QEbQoiW6i4eyfC8Nqk^of%0jV|%MgLvUB)hft0W(o z3-AlFV5UY3{p-WZmO`+b!4saiJ9SplhFBVfBoBIjZwYKW{gTxNl?E;4p5APw27;e- zKkNR=XX9WjcbD?hq~5ou<{*ja(n2X)zZqTx5W~M;!F+sl_omZ{M4m~0|GF|xw>&xi zkY;r6 zuqEA9UGVwNwoh$1?^-}By$`o&-G@8pD?4dO*h^l#d48?tc(Y6%2N=G{WdMlFf)Kbm zjjaa<%@$*f`czbey+p}3PsLPPX~byAT|SA51$lu=GHxBPD$s5^;H^<>UE%vzkM7AR zzw3yJut6+i6Y3u}b6Jj+S5)B6--a|9iQSB&Oy7sh{ryR6TTF;S>B2Wqq6>N3-3btF zH&f#JZ~rzW@F&1qyEZ=H2Z-rkn=9}mL!$l+NR~q0J}lVWEy5k%K!YUR>;m7CQEHJP zMt{kdU$7`f+IkUdPA1!rP1Wy(#&Fpe*o87`suwbjcUIen;6U#|XkOlYVE4z0eL**b zr+y^6CUKX{Wbz9>iRN!c%e;3njb89P9jCU6Nqk+^J?&ZL4j(Z*Bd?nz?6f(s?TTx; zI}*SC^^Sz~1e=0rVJp*PNW9TMBlHecUjP*@dLttyO&uxog8QtP>cD-qxV4cL;6&j{ z;yT~IJ%3jd=G$zi$6Fa07gvR1J|fATKCZKWacc>&g+bmGHE--uQ1R?DSpox8jh=%2 z6*~b$j^dvk&Zy6}Qk!k)UL75#?e8WcW^~D4+M^*YaCaXa%{LJJ=)>?nFISnf;6psF z<{}FVi)EP?$={NT&m2AiL-0yvUXgh9IW4EWd_@edJW!?FwDjtFP(%Z}Ji3iWri=P) z+}wz%43`L6hVfQu4VPpP*>ZXb<+BsU@9iqM^YWBmd1Fz~f;LH8VYKJ~WZQh6bm@X}9v2${n;a$j!#afx{!zupJ zk(?}nm$b%O)!jfmQLZ0L)L*Wc!{zdcM=42 z*Ku|n8dVF4ShX~aJ3UoZNne*4Rki!^foW+O*UT(^tBX-&EDn#!s35eGG2gMl!jOjF zxd`mc&u@l2sMkG}SSBx9vEgFBVNg*$$st*uIKN8Y8lG2Z#5nf1yJw@aLCCo&P9$s1 z!XYdfje4k?YqD16(JJpVh@9ZfqdbmTO(@iJvQp)y>LAX3sT4-VQdB`sB9~L5_TD?0im4W#zfFPqI<50TK9_Y4#OqQ zdq#jMU0YjGtLrDWcsle*Rbo>NXGi}oHF6L?VO(+B+fqk>}Eaty0;^$_Fdl~2s{VkOnqE@6LtiPEV9hJN< zR9V!I=e{4*5DQ1`pKdn2rQf=T0onYwsM{kdNQjg@OwC90eP06{oL0%W5_iF-5BYit z2DJtdvreer*9Gp9T*){(WCD6K03lTU!|gW9`_$@1cgb>Lq8EN`dXjFP6PrF*7m)?( zt&!Zbw1{s&Huz>u60v-4^~3O^z7We{bGL3d#R_T&Q#Limj**)TQIf9g9M_!8kT1D& z57Mnmf3*q5#V4@sU}TP7d#FJya>yJ$|8`S6x;Eq&o<(Ojcy-&>&WxDJZ#ESOz% z;jvwl?T8*55n<(K-N+y{^&2omvfYGYHDPh7v}wcsp?O7< zM^Cnr3kwUeDO|tpZbT9T3>5jC$1<;iD4PCQnOog5Cg-@FZcVCSzk8S+y6rx8RQ*1X zDi=wj8jsdhW%jJUc*2-KptO5y2TW?nTjx{k6cc(~fw&&5%B)-MM0 zE-uFYaiB&AX1W)fUtGEq%b+w;V@j5Wt1#;rQ$G(x}%+ntpx%)L+ooC*FG_I z{NSp>{ONA`{N;)-i|4j1<>d+OoQr}W#r%5~D61I(Se6Sdk}&&#(xNnDjz0gw!ksy{ z4nkXm`GINe-f)`0#b20tf#pQPtVVlhk0o8MlsL8#;QTM0YGd5#!$p7l#JIGoRMJGIUumFFC%>rH8GrB>udwW7%R2#@f=yyD!| ziCs2jqrsrwh8p~$mAp~F?y^J#b~x@qUW^PYn(vHWJY)u3g6l}i&?=jT05ukOSVMZ* z_z2%?wZ*N@R^XU!)8;t6z;3J@@S}$IBE$H8f8(>Z#^tKm;1`H1()3zWN0eI~CHBm@ z^o7u?IA^_cQ_GE3t$UQ#QdkYJB83gdTGD_?f!drbE1TSWq$p?yU@zq0+zuBPS0Mr` zNOQ7)yqT`Be!nBlt%V9H5MXC4_^CnnzCX%y4X&_M+7M*`Q7Nl24Msk1tsmF~&53bohHxJ%aX+E#ce zd*54jJCxISL%qc^kBHmoByF-8nfhdbJ)bdqiR?%#tv!s@wgD6ygj3rSbsZqMrjjK`@A=pF9y36vcmuM9uS35O5)^!| z?d(uiL2)_Qoj>j9OH2;;33!cBlnL*p_6YXhOZ*K+S8eEyY0Ajk@nTdZz2Q=)mCcDj z2X9vKE!*O%`GfNo+bHk#?jr9~{Qh(L`;IQ2+E9fWI5!ZC??xB6$tlE^PNoRF*4T|e zCJVu9PzOBTE?s|Gj{#mo09ow4^Sg=V(!PIH#I!7m%{ejVntzU+e<9uot;uthAgd1R7S((!~TzVx`~(t*17 zMVWP3c0hWBhTuAV8dCCjnUg)t&a=PYh+{fc_T_WKUS7{zXx}_jgx zSgg~4z5dvA76uzX=+P7YU5|9SL|wMm*1qhA@YmEMtFlwSs%k^2$CYf7?#Lrjh9=69 zGc~g==bTABHW!2B??PfC1Al59;>{yfa-(<7Qc_Z`X+T8{$5Rc+xieXSwYo*9GcYw( zyLA!Ar1|IU(yoKt1+c%kI60+iPyPg#OWys7mC#b)G1R%}*{$F`o5 z?z<4a-@AT=sLKl2$=Xhz@JWbS!Jqvi@BJ^2!V3y>3-jPp3v23;Mj^y9cyQ0l7~`I} zCzjC@l?4?~o;<;(^ekR=Yc^md2)>2>@sGj=wvX~nNEFXBwB~4QTeRwi*r_YL#wxDZ z*OW|uitJeiz`g43w>cJsde2wg&Y0q1b9IWBTNtpjZ5Hq28hkyn+cwA9?uK6T`g>EB zqkBQQ^1r5RI*Yw|32bw6W1^)p3syrnRk+(_O=Dl`p8hoqzlD*0-de^6a3DKXjTc*> zBekoShh$lVmZMb&SoCzD&?A#@{)JDE6nGateZEJ?3|3hip?*D}O|>rU%Ca;#E{5`i zZSPlZY&4!6OCL;ohCVewAsVNXXXma4@jcJV^kFCc_`jw-!lUM3_BKiHc0sy5urMq% zwQ3C<;?A`A9b2bPDQl{%op~EtF0K(K*PQvzrom28>iTX+?UqGX`^Musu5E3UClhXP zYq-E%vCg9K$&%PAh23#j5iB{yaY%Xx5THm%Ns-gGjd^(tAl`U<6Fkx&Ue~&xu4P{{ zeS}Va`z@k|ScFijF=etM4yQHe=8_Z#s9<5oUr1Y>(vxC8LgtxuJiov<+71p!+PGZE zdmh%23FWJ9A0If;Egl{ndT#VhHzH?uwtxO{`3X>@urBI2PA;x%FzJ98ehLU}!vxo7 zcf65x^B3EhvT>=-Q|5&mh5C)&1;|I5mMK2N><)=V4|Wc^89Y`R5vy4&-aIJ^u%-2I zN%V+<#z`AqMrm5aRkWd^P+yNnD+^&!i2C^G2}0j2%%*A9cJBuU0p$qd`$e%K{@JR? zrQ5~T+1M~r*BLK4k5+y*qNe%%r zpu=7p;ZEb#<(_N(L_UVkpBoQU?gK!rgbSbk#pMB)e}FXsh?+PZd+De5Cjb?ueL=C! ztv{)1e9ry&R?T*wNbu1UoK)BlJM0J(|r#%i-W2qk6&C%x+fy zZ&a6~ma?@t^eGX^)>-2b>U#12X{YDO-%s^7Qm&fZ)`}O?xFzY zEKLM9qJBhBJ37DMlK4Pj1?dIT>M3_S+uqwu?<(8C z;4n`O%ea@?v2rwEvnVzBm1tR5vjPx7SJ$*lK<67ShWK1N!mRHyfgVm@pSEqo5onW} zn~O@jLZwZXnf5314cjHxIIM;ZKlgu_zWXAT$Mm>>v9Iy>PTF}#73?^QPu4(7mSi|IHmiqG~ZJ5)3P4*Uml79a%fV?Ou<$`kUXw?5b-hl1* zwqEh>e@fpBd%{FzC?VJmOC~a0`ISU#-gt4mv9=Z{k)C!ov&vli_XjW#%NbD$d9L|F z{t(u^e|vS7Bmc$8VmNG>QNLz2)uT@cIimucM6me3ym`LZ5GJMJ%!y~QyiZH%{WF{S z&Pg!#;}7sU#vV#aCnu*kJ@>W4wP65r>XT)zSvmu{Pyin!)r!uV@c(aO+!L$o zGV6q#hh2pNw$;SD0I*DLjmpv_WS>4o4KatCmLkwU%J#eNAia?r2Su0XE0nn*?K>yk zu(XRcMiNU45xYJ&IQ-?lm!|fFMVbAtT7jL5#naG~wAx)i`3k3L%EL>75GAF#sz@Uv zoy8Du49>{%ceDQLdYZBQjSq@F=WVUb3v!DRglzlR0LnF#v9WUJ+1Yl<2XAqCAd%Ib zp$t2j+@U6S-+V_^bN}7s<6f`|IQn?g0g#ag@W-x+aBI3d?&I2WNNk_L&?{mL2t-b_ zp#kjC9y`};wd)xP7>&tKNqGcq+YgDW^beM)DX(@(d9*|x-%YXqsZ^zRkw&O@aE1P{ zNqR@{`9nO()`d3=T=8<}s0c}$i^lo+{e*mcjbe4)hY=zzXiH&AbiB1`l9~<`2YyZ5 z1?F)^%a%Q~dk3i*8zL9KI(9sM9?YGfJGnT*j$w%6zXKPRq?X|#Uedx*XCo-}-UNZO zfk$|-<F{8YbV$zFe^WQVa`e9Vs9OnJjtfq`vsYIKU;@Dn8T5b>zzD;L#VIoJ26=tE`@$UjCvjeYuE)AAJhP1~16=Y+763P*-5-o6jE zrDL@U27Tu>^Jk0junnux@8|X7Q`~J@mhcol)3MyuH1DPd*m$^jaNVP+7O~^A-_SU< zQPnM{ra1vbc6T?H9Ba;i6rui`ii!%;WIGA=g!;|Wo>b5ME^viTO6 z%+@+x`!)G_d2?5n$g0Xp0uMfA=x6scBZ<>1-P70%Ne`cOIJWWf!`RX#J;Q#KO@6`h%jqbT%c(3&VYbViy348U)^F~` z&O1qOr%WXIv3d>~{K5r)c6Oa<|77XlQZZGqPO7=!Q@h+SoIUU^tbjYoOh2AK#c05Y z(BOP{hcMOoMX-xu&?mVB>;EI{D}(B2ns5&gf&`M_4uJqUxVr`m?oM!bcTIx3yE_MW zm%xj=JA~lwZg(Ny{dcP_l^>^4yF0VJJw4q|_w-X#TvVi1V-l51=XDwacr^4-?>aVK zb2^D8c8yEgPiF~44}ahS@a;swiqI)i5X$LEu1=rL;WQ5OqY|~0d^O8cV(+`DqR9fO zxZL6J6b#&%?AEL3Z8G^?Nm?vqxSXg&{LiaL2CY@fzkSS-I4}HtZ1q_e3Vx?0t{>f| z*QC+;9^bdEJpJ|W!U6-O0Tb|(N{@0sG1_+P1*4ySH(Zll3nU`LWBwi4sb85}sjQ&DWIjVJMx|0F znJ3?^^&W%{^%BeK2&gqX9V!6~qc2?G+sVHcF_0i-M&G}Psd-QGHG@AYdM=w#n+Py4 zenq3s8N5Y)M}^O1h*LqTq^vxW$gmFV$@@n~v{&k&O!!wA3r&12aT2T`~TnwP@L0J3`l2H&sGXIj0Wg{&0H>6sruXsi&*`Gl8Q=mmgY*G6%DDdt!{`F0JU-PuzhMZQP9e4DF?wiuoxwk zaZyz~d|+S`H3gXNk!<1UP5&9}%rtCUo;v(*3L14xPOY)s>k!&7iW@QtqnuO5d0#`r z9P;n`vDee2f?2ZMghXxVpFxtW|6=A4fa$B2GJ(&FON1t@TT4hRB+`09RAt4&=tC9j9BNiw4m*gHa2Un$q00Z#;5|W( zn*Fi8MA8;UjkJMNdGR@q=u+XZn}1B^~T$A9CFmpg@7d(&@36k)W6Cx%<*-8J9r zd*;D>NQ3HX#ik1AA(*aYhO|c6H2G~i$i*{?hetz5NfWKCBp{tHCmD_ae1k3qE>yD& z7O82=F)^nP4`sHl`&*e^stX;IH0OY3RiE~Ym0^U=lI)vwF|HYseK|-6`W3g#bP1KS z|Ml~$<&Hch4O2Lmb4gUCndwIIuNUiywinjg;ny-3)tU}zo6AEKq~?4%EuGalxZ-n05O)Wnn7SkplK#xK zegtJU{L9ur#bG4#ND*k_;>*+0t>P7j8kR2k{QNpSTbO6dtv7Fg!B%OCc%Ah0)f=4C zLN{u{ym~0mgxFWA;8?1_6FiCp0)Ks-O02)A5K{p~oU(htTmOwswzM-m8b1e&_eFK; z!^n0GspE<7aO>q9fe$}=@!>8g)UJ(NtXaOQqyzvj(U`5mN=Y=KI!ON|V<>_|4nm9a z3$#5S#q;9^#TW8C~qGZat7amPd+|(~<3GoklgCCss|Y~kX<9GvxCx+u0pX~q4K(0ZPx#B<&1 z#|u~m0|_jyl!o%Uqe;$0U@p=UQQN#5@`=JZi(wql7)|(sqg_|QSKI+4(85Bg)+N58 z70Ppi>pK+_H5WR8q0XT0EX-rEddBDN+sV}CNqGJ7Fal#__1gt()k(0|E~ta@H2XB?p`K@Dx?)1@b}9at^m@&mp^gb*XIMj z{>csa0Z<*jAXva3zu0T&1pLa(eO=qYFWHG#EdqZ1zf+a}|Hny!XVk@Ju0sHo1o_DQ zg9BvEAjP~6yJVM_wcT1k5}>0H0p0zj&S4HmQ0-RPlVe{E*(i)*z)gI^pct!VdvcY) zW(jW5uv8CfTG(PNj&#(_ZHYa9P67_#ks^Mv6#N`GLjwu%*TsAqa}w>s;b&yudJx`} zD2*iKW~KVuSuYtIwnGFV2B128Y|nVE~cc~IpwnHFsTE%HN?7qlbFh$sq!b7@%9B6BG> z{q3Mcd;}9IhyS{EJPt=r9bs;b_sxFV_EU<&^tyW*Nm;{gK@w%8D{YU!UB)rbcGJ*FSfa`?k7=!A{DSRm)N(D`(TAbxvOy_V7R) zsBP%zqeub>tW~n)I*XL>;)Z8_us(AS;WkjCQTg*48A(`;dGW3F$(F!;SzX-t#JP z9q!yS);KmEoZ2sOr|96k$)8+(QTRE(Q*rvtmLttNo!pib{VYj4j9!MQdmTa_d<>-X zIm=lP91GaeS^$bJ73-cTgAH8dts#3J2r^K6H4qsFJ9t|SD1F|m@t`L^YxB3;lP=~d zQq+Rd7s%t>G22ET!FX>pY>zW7%uJI%5E&;|QgRP0f3Vn#G4zp(zh+5PTC34nP@ot! zS#|6In=nT*jOI;a$1#`%6_OaNTl4|X&k68(iMP#AIZ`eqWkd)lQU>q}hGgCyH3;Ni!w07vI>#W)hp2-a@QidPioiFCHdW zE2M}*4v>ZUXufLMoGVbuk}}>?<8NnYc#u2qc!}xHX5g4Ng{`D76i19B*bmI(UjnRx ziM#kI)QxdZdePu0%t|;5N4a4|PiDa(PYJK`tGhF=%@sa>Q7HAITRfP3x8kdGsampmdaQ5WegV+<(BQ`a=p@u5z zo@$dZ%9j7l_)AiQ6CwctuHe5czduqKb?+SWrV}drTPkO;1m2+`CQgNfy)TE4Z!?vX^K4>N2qn^CcR9Y}b%DKx2rJ|kp zX(VK^U=^jK`kTUE5uC(Ynx?I`YX2=Vhy?v;BhtF|%Nyl$+!B6vnwTdF70Ov(okheM zOlXqdlTMj^JEd&8NZI!?4(IDQjk#w|)0w*xRMuhEy)2qycKD8t77hCaY|B@N=I4zE zCx_0@2k{>~4ipMfpDX>hwobXruQ5G}FN;+iTAH|H#OJe*knKsS)QW4S2cXXPe2kP9 zO%HmYK{|{t#Q{G3*^y;U^41PiVfUd)UxsvGUtV8v-|xUgZymai-SgQ^w}pI&VYa`%RcU#45xai8yDDOG zDmJ%@Z{L0DiCecw&b8ukUBstn7Ln%Px-{Kiu#1o1A84pj_IkQBjybz!JJR_M>+hU- ztKnfXsKU@yF7FaVVueO!(RWr7e~r`I$2Jp}UqSp1%kdOdS!8~mr z!Dsh;d~A`z^}G`^I*>mZ@o?*0obY_UBm%1ktsPy)sq0L=mergGLUtDeV z@$zN!X?&WkXh8OJuiqB|oL0ZA*2s_-5vx7Sh6B7ZQeCM#oOB9?$6h?>e|TEl4ItteUUGW$cCK@F>AD;k zZ^EEzcOU+u(t44q@9>}4F~{+0d3yD>S2W?{;PUhB+Bpm{PR@USIs(E|zfb^xm3vPk zkbE${;<&L9yMEZ~9p!ntzL1^3=1RN}wEpKR$EmtoX22T~2KlgrncL}Lh}R*L6Aj{5TK1>*I-Yn(YD3_6$p{=-$d)<3-Y!w|93E#P2ZK z<;Ub!_L69MrgUe?Ehx2zexC!j*`X2!Fwv49cUv?QnD_l9XH+`CvW%97_UQ!_)kcr) z&yDNe-VTqePbbBfT+UYE?$ zb8o`a=?yO&K4*bjL}$nr01QTWpK=|mdMMg24!H<-FKiaax87K+B|qH$iJ4V*c1T<-Ym z+?%&&0nr%LzZJ=NN!!EFnmgwiiGt}i7Es{A&f{ONhgI8E?cW;aPP58{@g_=F-ZFK#7j`OPVa~;RI zhxQDluFYwoq%oSc%&WV;yBT%uy$vZ&qlh5K`IN}C|KS0p0zvd3JW$>|@LCrii?d33 zXsSQZ#%dt%3LAyucXspZdD9nYzyc48N#Hcpb7`*awN+`r zdAxmo3RzG!jLTi<$scTU@nlD>o^i%!V_{AlG$yOw&hF-r*;gqC79dRc_o|(=-YJ$k zqzlLA!!j;ym%qo0;U&)a#xC`-bSyGya*I<7i}O#N3K2t#?%J!O^1R1n{%=D=@od(W z;<_S`{eJzBHMyyTCI3ioXvbJyL2OQMp1xFg7nr7LN+}qP4odMJt2k?5qs>;ZnQ67BH3j-=g&HLfgRN7^QW%JjWU194;SJz0$@6nI+LlFX$G!^KI z3aag|9z225OyNR5cNMR81D+OID|!CB%^ylk8{J6v-0%`px(xf4ao46W!bO)bGhv)@ z_dB6{;YdP*N?&PCdGkKGef0-2&7>RGqV@e*K{(H4s!0n;idebo-9fLk)^`;(*R5VY z=ao$rt1HzUd0dBD`%t+)lTZ5TqQx9$zsizuRfd$tJnsP))+d{)nCK4JNetpnV~0#F zJD+ki2JfVLn4lEA_^MZaxg zE7NLon4Z{_;l4@8@Hpp-*}Yh#>zWK*-Xd~a>W^-MgR^S78c4|SK5JWje9-?w7q2+Z zXc^&me^2JgX|s)!6Pc4-D+ysqDKc9$rb{q7k6i3Qv;|f_9&G$0T&Cc zuy4}u>OJj{syLt$nzSG)^k?vSzTduXR~H!%sD?M zfvd;$?-SqCY`ZqQM}|G^{Sd{rCuv3k9{hplpR9n4lXSSFw)61)cDLS3?YNh1gy&rF z`DrkvEo$eudPe-r^EhmFC3Jl!)}C&7o3Y;WUc!0VX;QO(S+T+MiR(Sb<--R$&c7!6 z{XmkoS}rwSz0dG@Z38 z!60sMcT7LQm!z^whEu zc9!eF!{hssB6Aa~G{Vx75=haxC$Nf6$IaU;OT&~EZ#8Ih+KSdL-tn!l8f%%Em^_+O z!TgpLRM?-IxBtNXz`idg560)%&&d$~f@aw~Y`-MM$E;rLA74b&c$f0ShFw{W$Ah{O zA6drrSUM>@S6$C6gbKH2hLRJi4WMpLR1K}&yjOq&P8Q=cw)vemevlD4+BX&*hcRfG}uF?i1gA)f0AZGh8#QHpzjy9u?o9yD`)sLEKVG8*i zP`4Udnqv)ixmmx3$kQN}7B-00W#7dpxl?R6*`qgHQ;>Ctw!nWx!#TCgKfL*M8HPMl zXp`63Ns{J6sz$ZndfL=A+IaYLFu8U>O)6+m+`hP970mHcg#skcPyU9`dEGCrrgPXE zRVKiI*0-BJd#2dW{Q$NuaX;(0z9Kr{Sc(z7IeAZf@nMznZ3}5P6{V7#X=%%n)6tge z8MmK6mVeP?NqgG8s!Te1NU#2>%i8P&X4Zh)*@|9AOnD2P7;ro5u7axA8os zr0Z%V{!^NIPB0vY3Os;Iy@0zuB(>IVYbZJNP3G;6szVL~B0T6c&vDzVSPCSDI=YueXnZ1S7Kn zQDKG5k+hZ}Z>!KW5KLI5J1ZGog9zd;di6m`DwJV3E)6YI7Lbg>j5np1V19wM{l3@o z7LC;Gb>@>>mlY@Pb8}o@v zngV}XRjL5>&AG2Y=sM|fF<0J?)VFrH1GuX|vHQfdhMlI^85tMKc76iZY~T5G0z=zW zjGU&3qwe%KjAWOD{x_h^i`NJx5pM{>mLnwHibKZ3ArdaCVm)em@pn?jV4@$eZ))e) zteZ?Gp6pU?%8Qy#YdJaeUS3NWJ^HR~oY6o){slOIgPVi+f;(VKyvA$({#ARDiz}fx z0~E)D^gCt&9O0}^4iCo>)~cs}t9w(Q+BnXyX%iOoL*L%H0sT`vS2H;6+1Rj`Ai6j? z7UVHv1;u-iM~^7w#=-A75BoU9T%~t`0%c~MsR6Jc9m#)l#vYhQrkKpA`{lZRkJPmA zUVnrAjO=!CK4gRaZhJC<`*CcQbGGcRk7X6ydFK?rzt<1<{BR+7O$G-?*YY=cx7z8v z{^0;PkGgU`xL9)ep4+bfrze){rU&N)%fQ`2q`ddY+t59de8$53Lj1NxC=m8*IW_$q z01eXR2x+xhV>{Gp_7t|4*Lw~R=zN-+Uid;xDXC{Z_iF+|rMPye2cBzbx}*!=H=+XX z$S%k=gOTwlNq#m3J6eLvJf|}ZE3c0hFcdK zo%HWlC0nmp1-qEuv&Aqtq>tr%MD>U2VjtqoDnMz%51$+PE+=GXma=V3>-*8Ex%$fD zp6`4N5)zU6j5v1-b~Yxz2LA z%E@pgT{Ael#%wiDp&aT+^!n+rM~Ug|dc#VyS#P!unY@~%R1@$(?m1G!7lwr4`F~r# z+p=1%nAtA3WBAYoBK$Wc&KugecY0G|i z_rp&I!!R#phI+NtF$NQfw4fGv3>R%B&Xq?0W1}eOf3*NWkLR^g$V%PQb?Ls>wX#w- zIV&Fb8A%rYE{&|5R@2TR&ijOBsQ}udLrzhIZr{$-7A3yt$e3CMaUMp_veAla)M)ig z(=tWurrT6XX6H|x3w^(cNk}*u=sQSANV46~=s(gYpK$&P{%K}?xrC$O^GIWwp_@>k7G@N}y(chK-it7U=#gDr#nh z9SI%W5%Qega5tM#SFVA>>6$XaGunF4#6;V)bX1j)fX?H3ABR~C!*8nlw0+zruO{y> zaQHvevdZ`*1cHUppwc4R8%OZp;;r;5e5K z?~eUax0)HjI%?FNyIuFvz7tg5ssr9zRymd^BQ-j8=ITJ=3ljSwcR+Fe-+qg(i~7|E z!>4+8#(D$GRrpMx;gKbmWAj5D+~1g#X%R=YB#qT%4{I&yt9Ye$Xi@W3GLF1UGPWnG z9OLJ)NOUwiXJgOn+kp0=d#A+(j2}L9 zo3A3!p|Sb+bRLo3yzM_0e__?iR<2&>`8aU6uOd!N*JvdlqHO6Gtc^M3{@fTaOLtFE zlR~_izv?~zW@FpnaeH~b5+NwKBl@^K{d7bE!rGh-ewzuNYo=<}kzw_DSKb7)kOT!Iv=61QJ+{+<`gX>x|IA}q zs9p_b9~-sYjaeS`?L_I*eGoU@dF#`?Is*UJ1GJ<(jks|3 z44lkNynF)c_-~KTq6I*od$k@HALv-izBk!!%D0!_HQf_Mgvl@?Z5SMTJk)txs8*u$ zi;O-U6(;7Lw=Gqg4yP|KK3!gTb2%Otg8|~$`zvpvw(E;_LAHr}!EH;tS+DzDE@_zy z6hgjo(aR#wQ5Z*p~XyGIyXF!m$r_G{_ zJcc1n)B%j-qv!u@j>%hUDW|KbM&8^#a78do;qAA6JR$69vA=j%Hn*r8H z9NT}lN*Cj?5-ingxns3jPi{LefPVWiyD_ur9>=?IY~^>BTg`Nu-p>Z0K};{WOsQ?H z*(I(<`;0a+*gS1%*BqTutLk|YM?fKN_lGeCv4%Z&7DCkPOuetqj|-fSOhvm47`uG* zI^$Ueoh`KB{!E?s6p|~67b3>fq`&LH!Lfh*CF8u$mziAyd4wz2Fg~tO6{wyP0)r9W zdhonOO0k*MKWx78?^rOD;C0>XU-f#pYHTihI&Qgis&Cb?ewuLA4wxuDc7CfkP5R&B zyY}0M=Q2E=ZSP~}gYoOmV(UWcq6gh8V?1T*;T@)(;GpYkj!Ptv#?0f>!e{3)bw=;Y z3E|=7f^M?za*Z~FnPxg@Y_H!9-n=IzUnoW7;y5PxJj^D1bKG~b)PlSBRj6T4BHRi2 z%n#yDgMuDMv1=Vx5?0Rah?8w@F+id>4s*9bDS?4I*6R@pfJK53sZUbJ6y?EQ9uQ~< zXtP;Q@>1mGWf4=ddi@8x-ki?J{_&)noLnNPm8;&cOdD3`Kfi>$8q;GN&fE^y1kHB6 z)1NHu5A2(9z2*1U2G~0j#wSD0+Vgczx98W(OU@^zp8gT|_J^b)%X%hb%wULK?YyMX zI}m>|4QBQ5#WyY|Shb|Zy7FfN{{8S}bYTP*z2gb@KgKRikeZ!Fck4Z_jSB_f34mrb zcV(N-wew;5Ft9C+=gG8>S^Q3i@kie{xIOH(mXHvy@sYupYqB!5nt2aGJMwZErV9}W zm`fcQ!&;mvmq$1G-SO9&7jCRdi8J?mr3)`8qaI?}TUH!Dq0_BD7tX3|OTa%+DIv%N>P9a8cz4WE+U{BIOYg1ds5e-E@-?fCnD z{XB~2JjkPQ+!Nq%~pcz2?CPJcU^g-J5Lvb*?M? z3f=B#sp5p*FSSdJhJs)M_v?PR_nzp&u8r~my4WeobTB8w1=il@yNhs#=Zl@zh^9kC=kp_@Y&cCM2&$tQL^DOs^H7gY2QPV!j|N- z&2V{0ZRXXC_YtE8wSHL!*m39nFJaa}%DoB`Qi*n}M{mz1$Y0yT&+Kk}GC8(kH>(Olw9TDE-zl3 zuhE?=QTL)`5F)8tl{`RR=tdJc257?-xxUjzhbz|DJ_iIT@6PNo_~kMos`U@E=kkZY z{&ttY+rq_72HMT6wtn#0E$7=jS9*tG#N_qtXnJ)-0Ah5zMnet$>B_`=zcoo=?ycdm zHa**NI-G6Is#0@2@s5A1rY}{BtL31VV_VyGbFg~byPavQQiBKtFq1FTS*2yqc$5_vf>)cVsv%2M zieSWt>EEd(X@CRzCUTBMi6uv>zzXoRpukiVjXMh|v~5pYA?5A2LzQStQR9_C{gzx# zOh|0?=^`*NT*`9s_hFFYa7FVH$nM&}@OUZP zRAeVeDb~&VR(E8a*9{hSUd=w=lhOF@3v0o?zc=5RM3Kkeu5x|UWGL0!$)9@zl;g~3 zpER>ANt1W}1O*0mB2)cyn}sxljPxV2Msj_Y*8;;rsHnj%=RY)<+v|@jq84XLz0SwE z#9Mel(lWVv0`lT1HQ}0|jf3GP`=K%OV{c0L?f7S2ZVDYxhaVcbI07;Ke$K24ohJOB zvh5X~$G_gO2^fNMAQwHoO2@By+UFKrf3 z%CZs{xA5=l4DS|$BMT53k@w-!U>O}9#d|6G3!z?@?DdLOp=RSJ$IJNXL1`ZA(8xrl z$DHH*R=CXPKzpTW0~68Xg<{v@y8VRbi}{q%#FF@s@SDnu`(tAHGJz%~xEGmlH(R1S~sE z_Zd4JHbS~b>G$QF&pSlTAHh^KUYpP4@tBxMIIiOuF@)llv*Q{{6u!`(h>P<24a)A> zYUOZHOP8qIU-kQc9)pnjIIttehLEeRTYe&8yxXMnN^=>Hd}vj(=5@MGiCEUW|NH#t z?6&`2Gxxpl?>|hm;Qmqf=Oj4BdW(i@qV1_8`%BG;_w9dBH3LX&dtDJvO3FyzKv}mXXS_1t~suEorG?|6&g3V_UzJ1M$9) zVPsyq=sV8>?RUFddz`M|nQNQHrALi%|JFc(e=Ojl?7>*O{hPzqcC!<<;JM(i!+FX_ z*Go={{gxVYaaDfU%pVLyG~ zdn`A~iA8-It+2RsH%Aq*WYgbi&8yv@y(b2#LRO^aNTlK*9|bWiq_aK zzL}h-xcxi+BT6ihNeiX162k=vCQK>7rTP1?6nrL69J_PlJ4uJTs^3AMvTTGMiOO(V zxvlM$_Jz`HdP11Y@75@VrW0-6zT?E6u32R7BPy!NJnN%3Rh72)#>gH7a(0GVJ^wc- zoPhDHQ+GRT92)e=vF0d}VXZ6xOG>_T$Y?+RvfWC(7Ky)oZ9BQNZlQ{@ zi{H*{VTi?*j?X3q;5Poh@RiR$jcY)!lKR0-#7nv^AJ+2ycA%X8 zdNRxNs<$>Z?GRJaP(Z$nfYPGFZrWH$8yZG*nYl(?j@f z{IsH2;HXq!>|@jTg&T+hQIh=TvFa1sqD5WLf4x+Q4g_!>Ci0n(kI?SUhRvUINJvuO zX2N~ojnMl^*pXf$8PJ8XNKN>b`OwGH7g4EH>@L?AJ@IJ-k8b5&22?$ZAuRHZ2z}Le z#MGp3CC;>(V?72f*O$;2aM5AfpIiPM4UUKdQQ-ty`6d2bd-jyJocvBuP<``|+s6PN z3?EI6I@fIdoWCS_UVSG*!@P#whP>r$w3TW*dvKm!ijuNQvFNdHAD-rn5P)zKjEMfJ zP;f86yYsCMHax7Zz^AFo;*!FWsuFfi-ua^{?1kU@?~Pf;<9@=wQ*ZcaXn6oNj_`$w zYX5Rp8#r9`7(&;Un4G*Y*F{Xi>6Okg9r@J{LlxKr6`$poNV>x`F&OI+zf$B1$+9N> zr0(MX1ay92XcU&}voi_R<7WeXVrXoPg@5~Dp4dSL1WGQbVf1+n$u$!rSZMS3hKT)D z>ciV;`Na!-`tNeFg5IMp%-e5tcA7AKrj2UJ$RvU?pGCXO$05TRhiKxxpoY)fD1;HZ z`1ljksp7w~fbAwWn>@0!&D0@)NNWdy$c4D*H|RSGVSVrC&5RXwY(!xdR#>Zd(P@Eo zELT1N0wXQ-&%|ZrHVD7NI%}y??_HrJCacL;h$JHo%_&siOGeGgq5drIlUe3?#6(|f z8Q>dl>Qt}~?{^l!wcX~WaL-6e@qsoDa()uNr6~XR1i3uCTP~bK(l?u`nQ|F(eCP+Z zhzcMcWdGL)nB|jjW|G1iBzDP(@|;Xutx~ZOD(?3$j)TPUf*|~#3PbI0M>b6l;gB|79J94C@3t{Z(qbc^@kb}l>nMQ8^*o0YVHf z6|O<1G>3heGQ#VW%_QGf+a#h`qA{?lZ3tFsqu&#Ea`m7>uRthMmdn71`X%k8Z_r^u z%u>mc&_1+XP4YvaTwiD*^}JaC;M{*Mg64%$Q)=L*sw9IFr8%hyeGKV;<5hJ08&4G66I?ZbT@1dE0QvD| z`Adb8y%~^@*c!ew7?5~|I+?Fpes7EhU*XHIs})|K%?$HO z5i#N#AJ}oA{(1VkeTJB{Ox()Ez7C0hrVYN<(g#%pRt_0ScvNG8L_%?m>*kV$+%S;d zdTXC`1q+|wA#qi&p4jiTnn}XHdH4YgYpGdp_6KJ>4e0Zy!~!L&ME&H{v!Ou;H|D(4 zcl68OTCLjScZrPFWBVci9RQvYQ_67I6X-)|S;zcOK#kXeUYCU*MLn#KXc#{~+9~UX z(x8C$=U!F$PLwrVm#xBZ9vR!l&r9ijva_zjKhPsNbA7V;5o%o+b+GwOtd@!KDz)tk zlWD|XEi$8$AT&8Ej>H72eD71bDM4v#*x ziV#E!GZ8S^dvK_HCivc;G!qft_9P5U9=nZUt$MrokVI5aBX#V(;7X3>Kp-FtZWT9O z+|3WjaCRpF8nkYOhbBKhnFAr&F^h?d_~KO#Y$L-QAJ! z<qVq#a}!(JKX zO82yw#VlO*5^^hA!Yth^5>_jav~E3)!X$KHqhQy6@VB1 zp{uhTtW6~@@xYrgZ4?qNW05dUgB~eHA#r9`Z>^4nfi}xsT3X)ZHaml?v}p+GkDcND z+@=;H>pFXdjK^B8nUvD{u45%Fi7I{wE;4J^2v@gJ~yJ zu6otm^14pS-T7El%z72PyyS=w5^Urol&C9*qHE*;I8o?F|0F#52eDgKS@DhRpO~f} zB<-@{?!cOwDZjaFH6}D!EV@6GY1_3~tgf?8zUr~pn4xW74`O32Q+t}MudXlGp6AT@ z7V)FYKf^hQIrO%gPqWhHDO}2YV!?Tr#pJ)hkRIoqg)R`HeRVNJL+A z9EsBOo>*4v`^`bxY~9hE=M8Ma0>KEZwj|YDwfn<=_jXRl9*%oUBqws))7yiAZ74P1u%>2404jCsOZ`PI|KBNo%DXJb{#n~u@*wuL&~ z;qb8H^ygIp8HeL{BMYL1CflyIZIa|=orCgp4 z%9Lo-HS~6%fB+D^(Q|nqb(u?1So3DO1^}3$<^Wt2kUtVJPt>@WY0+N5~KG7NnCy3C><0ETuPdO%k2l78k3~Bz@312y=e>*fvl#G4>-5wh{OT$}NMD z9lo&}NIB-V<@5Q^sK0B3s9xu43f4WJHjV3k#CVnGFq*DvGQE7ITWG4L@#{eROnEzh)i)6xPu{p&#o(a&vLmwpC@M#@O zpW-7ZRa!!diI!Ng8mFVlFhu)#t3L6@C^=uMZMVu1@TM<@w}WUjmY)Y=tsebj6VPdH z{#FBlkJi~?i55%%n{^uH8){`dpUc67CC=O0^hcT6`Lwt=L#9>l%i@R?kNZ=1&TGQR z@#>6zNSWH50=RWxhX-JrS*cUEY;<>8bsCRErt(VXJCBJkos7|_^96yHBil#rA*+Gs zM{Y;0n5B!gCewAA30esKH0{ZEBheg8-?ZHqll05%*fmk)DudOQ8(lTPt+*~dus}oQ zrjLIIxCoo{Y}{(=%`PYWU`Da?z`^LC3jOuHug2QkYh~Z$7aV|G!~?VQmG_s``{~$v z+9o%>?*bEKv#aBk8mqgfl=2ZH1=jSZi?S3)B| z7r1s?6>sbPqe!cP;4JHJ0opU z4BugqlvaR$zN+WsKldq_mDmiiD!U~6zX(l?DiSa{lHw z?pFk?@_Y28C9L;2Sem?mbW&6vqhneXj_Mipd($5m91b9fQ?lL;nw|7W6l5R@91Spudbn~q+#f|g25KrzUd{bWa;#67%>Q0FhLd;l|Zzn zOV?isLwvpTyBOfiT>gA@X42q&eTIF7FveNsRB5PKe0{w!(m-B>8wcwhhJ z!f*I!$vV<|1AHGLK6?p6QaXm}jo+SrSSzi zAlXR@qCqn#B%@SJRKD5)yEner18Y@`r?azK4~Ik2JaHFi0Nj7siY`}0>5ps<e#a>TP9dzgIgkHa4e}B3FVR|zW@CT$nrm)D&^L;=K5Mm96Kp==68cJD( z!FoTlzA9CoEG7Y|DDBY}CcUXVfxtbp;{=5jPKTvnXx9#Z7+O6Lq(9e}R!*%9LarnX zE<{W-xtOk(=#rJn@9HtT6iE08ed@&QOx%f7i?SUBgcfoz|J>UzxWZGh%cLB_*)SZ_ zcJ4{=xV^+Z5Ph}r?DFSrJ9m-q^sKZ+-B-xwZ7Em3_wx8a?1ne<%HZU+aJTwCCWc|RDMt*WVCb|CDOWeW2 z!1k-ONk(egB1!h4d!4bA`@SC^R|NZ?t&OjA!rf#IpL0M%DNnZNQx}DE4WU{v2hv|a zrhNrRi8`W4oaYZ&0G(YsPJHYbsUoZ?A&wym;X8H>0G`+Tx^{tJYBl4l7NY2rS_;H`Q1Y9>ly>{3Vq}h2^l9VRL9CPmzMXzNc2OhGggU| zc?L15@^hWb$TKf+h}w3q5o_ueg+NGr2UU)>CY;|F_OH)v6tHX@vUMF<;LGoQo%Lh1GoW}j}gos%OhW)VfbasUchhI&O*T-ko*hFNMxa-LE z6bAGJIa~F*F&f-krENVe!!n|VsxTd9l$s>Xg!>6{vRuYSdTS14b;IQ!^H~hmI^Dqu5Tz2}xqpeI-;f)0(SQVn zgzf=a0LO=$)1;&%Q&ZD09!wGeqegV-6mehSA_*2{OJzNf0O7~9hsui;FY`|`R!iP8 z!oPczsL~q_Ap|M2ymwDn1weip0&#b;uy5c29QR<7E53>=RSH>6wYXYO`;?JoCE}-p zjR3DUF6Py!Dx=8YpGzgnd1S(tT+Vzp>7O<-mX@Q~#O0V)E9&6lV*xL#VaaNJw1ATk z8Ds84a<%WDQ;YIXKYp37A&cTy1^)T^jQ~}1x=WNa_aS8e z=}}_pD|paeV~iYnyzY(=09#O42wp*o9RsZ3O2$FMAnjP#Oi<6McomXnZx#dQ;X3Q* z;V@Nq{fJ*csE3R9icFAzOfZO%f(|Hg#=2K1v8#0TsVUaNPmtPeEC5eDy%1YTE=f;U z+W`zJfTf`QkD&>W4GHLVoIo=QXc|pueh6ODW?#9#6~A>N_8|Qjcj@r-vzsiUD8YUF zM{ZXfA4FAjb}3OOl9BNW1zbR93QhmLtOI?_Pp2qRTYVK+#9|bSmzz)6R5rPwe8tNY zVU>wt-Fu077J@^z$ zhyHhm{vYNLr>H`tz*enTSOXZ{BYZ`W+FFxI`gfc?HFU zdWQ_Za=oL3CYcSH@QFp$lEO|^abcJ5%qHm!#m4)KKU4}lzmWFdD@;c%Lf6CQdFhHC z6h`_Bx$?s+>mTMws45Dnf3y^CAuy_~gagczkQC8YR8nw>3lO~F-S%|Tf};D9pC_fI6qWD~)nRH;V9Wa5vE z?YNu2(MWp`S(*@)G`r_Mnj#CUNS43IBFK z!30BxmI067VgI)wJ3N4)QIs2~f#=J&Q-27AWR^^IwtkXGphx&`K1xT>7sJWRm-Gcy z49VaH?h6cO6YkLeF-Q&Fbn#X=iU^ihjUb*Oo;C{+xZEUQ5jrIZ_C+jH@c+cZ@?NRK z3Y8VZiYhwo|FHK~VNrcyALxJ}N-8NWh;&IeQqtYs-7Pr?0@BhjbT>%n0D^#YcQ-?K z$JzM*zWZ}?p66VAbHlKo*?X_`uD9Q{elPWT?_zvM#RsCP6BPEQszM1T@-ZN%UHWf8 zg(v?rUm4+cas$QhvSO*STQ@&d$>3iiWO95lqWFmVelFC6D%H$VEl~3_b2A{K|C=kM zrY}S$3bn8l&L-y8!)FsM$AdWD8Vh!e1R;PdKg0ig0}uM{_vWYH8wCXH$H%B=&G7ZVRidJk4IsFFe|R43WmhhW7jYX_&%l zU_G*M;Lp1*Rrt?^`@05N{`AkfJ}5GkuaQ$-qx47E7Zrq3weIKIizr&~e}NtX8`>i% zRO+YxD;lyttu2!GUEjc86cIqOJxe;+3GX=wqU~nh8ME8IME#6llIa$PPVDy``wa-; zJAB@jJ(YN1(SDBEn{Bx4yeb~SSdPjD76QWxxh z0x=pQJ+7b?$jGGGK25eQn4MvcfZ0fpyR}TDWrXphIc@D>g^=ce~&WygsP&FmHEaL=jU2OzB*YJ z`iYmb;(^GSsF}uUXVmA$(o#{?+VlQfU*}gKcFBZcx)S1F;+EENW_o7k(R}Gs%YREUz_bpk35uF_U-MB|%agS2!j&r@6SFM_xB8 z1C5`wo*#no-(cWRZlcv+WeC#W%D?_r<>ZI2_F+6Tu{YF66ZdnbP)^Ci>sQ2{t@KXq z34R)8&x{6Ee&=2H0burGJV?;Ja9o@)K?Hx?LtMNuFqRTRFCwZUkpE59FNK8fV%c4??{cl7v z!Fm>PYLfQG7~cVJ1@=ETr4BE%`IFHoY6j8<;i1vR>EZOU5PKR&Z8Ec2G*Nf6qfX&u2Ine!gOTL3$r?UGB*0WyJX8Y0S7ChGB?^geK99;gfN%>0+n zrtJSoO(SmjHjeT%`iqpxSIzH}4yZs*GVx~w|IZI^-%PT!qXN~#og}tsc`txsqQS`5 zfHdGwS$4sF_p2cAKzzkv(3@0T42sLg+?^yv^ZZ87m|zOOx`>AMlesTEG!wN)G7`9N z6_$Hv0z`bGRM;Q9%|nUP;|ec3x2-eB6XW7abs90-0UUW06coZ8+EqzmwcmtWo9eaj zBT@+J6o9kDqxD}Q)slsRws@rk{i+KIgB-!1p!fmJOkB!>CtN^E`sya!P z;SK+ptxV_0KKAEiRaW}0e)0VH||X095)Nd;#ze?}(sW~Y<>g=eiz zVQ=p9INtp?4g~y{QvItJB--J5g}l|(Vu|S4(nvI$aIfPOid&YnDFvsl#=@p;mhQ!a z@`?jL=2dv#f7@BmJOBIZVvoktOIkG+ z3|dLp)wmNP`lPqp%bd%JP*!b4Y%#?aoF)0?FR~ViM14{D18!&zm>d=wcd8S?-pLW`mcuiGmJCJ@R}JE57_IdFM#*qi@sw@+VU@ z72V8C1uuuRj|S4=3^x3PXa-~44j0c3h6NW-7RIY_hn|)<%(2$i42bTuoPLAuzZnbc z{*aQAYJI%j13*><9MRO+?9~Z;7QL4;-5ambn4%KaE?)R$8s~;Wr9r#BN zM~z*>$mhcrYAp&&LV(7JdNU? z9aF^0-773D9;X-1qK0pevu{i1a`Qf7)Xh8!%lNJ$iUxW^&dYfeby7B_QB= z4O6SfdYSoNW7Eg|a)6Kod|qds?tOFUWt3y|KFaZvC|RcEh)dFhaa&+#|KK3>VoF<8 z^p_aR6-L)$wYl{542ASdm&<_IfWEKkoN8bM%|-fclolzx~GL#CxX= zAnMQ<{iLT}Yh{p*=K&6!W-P;{p!3Lhh2UN%oMH~TNFssXfb4um^EPbns0~(?QXwHF z1OF#|VV@J&G6>_%;4)vhk*i;~NDyJmRF?*0!Fc5Xg_^__2;}~LK;CzLR&`xOpLAKD z(1WJ*bY0!_a522$^QNOC3+_HDpi;k+*<)jxa*2aQx3_lzV+#!{B)x9LqsL1juM z6(boDVwAo#@uzv3{h{cV1$$|!=Bd9j^oNyEErX6bYSdlsqs0gyTO#fD*H=$D_(C*! z2>##R`w5w&M@omE;R|Faro|;ql-bvZFQu_^+GE^Cxf#6|j0Dz`813=TbDWo7R4zvM z+u)a}@LFuWEIc!8mI_0^`=h9#B+Pc0|Ka$86a8p$+NhR@SzbZvmE$J&_M%UhY83P? zVS2l)p9^r_3>NJS!H9^6_N~A+I1XC?fS1$bbOS)no@k9rPk+2W8)nj~q5kk;JugwO zuebN+E)hi@j;PanAQKgx$N2BZ=_sFr=)^whzVB~5JUq_KhY%euySA;mAD6Mo-lHp> z2@gIg{ESuC)Wq@ZwZ8^7bgvF9x9)P*wDo$|)VlKIelspz0ZcT{&HtDTiC6E$KMC#0 zZ{XlX5VGH98-CDBzl4pkw~6pmSdUzs(lYtE(a^khbYw>F#mlW})cr();U~-MwyTwv zUD(8s`|8@eH8eD|udgqS*FBO{@G(EdZg;w38E7ity&11g`SIiAVZzU!%ytV67y{P@ z$1A^MDP;+G-S!h0v;j~^AAyWz{U?SE!cl$p6dnt6%j&tNtE1$ptjX;oITVDNq6%uM491<S526DA`z-@uE0WI;2tFhtB?SZB26A(ATL4(z3;r!+<`e~)~({{_{v2;NM&wApGp!?pmbNi2P z-QSjfLz}PYZ|w|!)hWnz7}WwtRhm4koJ9z1S;y=>E!NEK<)GW4V7LaGwfOA_K|$`k zM&3SKmgL8(0Iwf@BWPs<>WmLTj^RfLJY>m(W?@~~#l>Almiv`&-Uj0RvqRXeEqXw; zX_I|g=M2CY2B2u-pS;fV74d|5=Qu=y4gZ=A#^gV4_c~rJKU{m+%eUT43DS-uy67@h z6vG>QB87D-eeW-#meMrcRLsXg`l)d@U$vANB_`yk+l}FJFz4Fw{WYMc174!>*m$a1 z|NTh@{P)~mL|*XWyqnN|0K#5kr;aNBLY|myy*asqJW`-DMhSxL{IvQ2epuZ`Y07x9 z5}P-U!H_an<2k;4Bs1=>bzsO*V0=Dl3>^7m&NZw{x<%Vt<}15tXMJgEw^ZqS1Nr2$ zbTr%6lQz6Go#hqOn@uwL6`^HmH~uV<|72w&-r84ijxZBz8o-2}EYq%^nVCuF_i9=M zP6I8>0HT77_s?IU_8tT-FEls;e0~Th+y=*WHrW>jpuu&ly)5gW`T9Jk`-uI%KKBpq zLnM5;>UWXsKD87BX3oMcsD4(<8S=GQ*_jP9%;9E^C$2Pzv16+aeBOHpm&Qy1n8FP@ z<$yexnV8hr%${1HOUlR)3HsiTWQic5zEjZB+P%7y5BDUc$(uA=tTisE_SyK<^#k&D zpS@82h|EWwUQ6J7VKvRm{jD&OOHiwbd?R3+Lz+4QNA)UD=jTk z*U5ZU!l??Ky90Cmr~^*VIeS)Km!m?W#rX~smad*2eTS1kW2WZwogR6rce>M@b_+XK zcNw0$MI#BHjJp>cI??d)@kyeBCjq(opIuqj!|D9%-4QR20l;Ys2#3?gUqQWbwo(nG z@#VcO8nkzP0neQm^sPt>aB`LW>*77lL|!&CxI3UFEodOkvt z^uZJOtM~mfvQn1yb4VA2T)E3D#sqw6Uped+-n~KyLMJXRwvv+@3cwKHvYo4;D{oi> z7!>FUQ_%Yd-0*g^H}XjkaCJltcRNs+5FGZ(>eGF1VMuNHFfb<+Ir>Z3A2Zerk5zy0 z;?#ZTm5QpWNLn5C$Bs>cx#r>F67{sPM(JR+GIRk2Rd?VN2Benovy>C7*xQ~d2_nB) zm2DC|mp6(9>@;Y96{06H#9m+qMRwNYD`Ds5{`z`&Zl6VM9x1>oK=Yd!Rd+Wu+vRB4 zxji|cpzQ|MrtfpTBc3f(?_ir3s~3z;3W3=9kh%-Vr|vk3Y=I-d!O z$savVKC=2IJ@q%Sb zIx7o$Ui}UpCSm()RZDbsF9gEusc|47{`Q(O2hR7Cww_k2ZO^5k8X3a-Jm3&VWK>9K zMBmP0*QNvgqg|Gj(t0drE?Eh5QqZ?{r60qb|E?FsY;Tl#ZhaW>FHduW}$dDrs@47NV<3F2W_AD%q>=|Tl_ z2|A=ez{L9aKEUsN0P1dzPWh`fhZE0=Tb*;o)P_Q8^j9p@+2sB}LHy+_k(a_-hjPQt z>grMK>S@lGOV<2gl_=QZ2yslgvTDW^9XPi{F0Fc~$l0yzVX^H(8$fB}9%tRPBJy8u zahGpwgoRf?w7JYLGoW|V`j7obTs&+J-n~h~+S=NB9`gs_qoq3}(ucoKk1I&!P{71X zC-T?X?qO-y(SBu(qz9YhJo2XV9)Yzq(TXj%YAP-VQ*3#mX~R zQ>eXaS_#yO!4@{xzEgiZ6>-E)tGs9air2-|vf9n$yAmLqcY3c`?aJ4AN^E;j(R2{Z zq+N4;T$zIuMSMU?&~$KHTitBtQd?WmxPO}Obd#K`9+RyRxUu#!>MU$yuC$ElTwjGI zR=xE$-}-4w8axfPc!beC3cuPq|2+(*runm!@m4tS&D@+eN|!%IE41Wc3vY1@`t`B0 zQyk-hsITf=T+c^bInQZxZjzyF5U=5*QEs6L9u)0ketcx)b4QnT*9xyiDuI*NM-OKi zU`cmhkYBgZGYBFjX&5=Xy6`K!L=fWg?37#2;BaCtAGohRv};Y;9x5BtB8X$}u{^fm z4IDh^J6j#Ctn{qap=MX7b)qCq@9q2TI26JXY91A9t$g z;WBu35-vFaZIp=kz8-hXm%z1Si>&ehI4!AtVkIh!O;KLQeP;#v>hd>J%7@PPdrHoE zEY&&HjY`~)@#d*|ftkYDl%BJEDaZ+Y0ctx-cLN3HN37H|XE5%keji_!3}mtmfnlFH z6&iIdIm;F9WIJ%bN!Y^3%ja=bv+ zTSY2t;^`H$vQ^B3-7RC{(Z^UkDHW6;*%I& zIsOjqGAdGuIY;e#02v^^|M%jAWaJYHlwUfzQ)W{b0(h8RtYv5TE*+3JgK7{%Vk~~( zbjMI5&?-RgoEpHt`}a0UI!U%_>A}HR%1&gVylkF1N>3tXwb*#=vaXU*yab3&Y+qy% z7vq;aB~7U3g=iE!01^&nhXb%wO!Mn-2X|80(t!6RAaAM<6H`r>Cxk)>Jjk+oUA44> zqm9?r{x~j7{26`GelGTp4;#0tv?xWWO5do^7lx@C50}N@m}`l5GExwMQ--SK`YJ%6 z2I~7&pb=}VP9u?-i;O>c>B(tH{;6n`@h+Wa(JX|}mCP{7zFKx)=ryRli|pUD(^Vl8 zV`C%uYOiQ1AhX<~>q)pqVytC3opS^Xou|lQrOjQmSp09ECtRVDv^7gG{@xT-v;?^nyc(ViWL=Q}vivCKaL(PTAd&$41uVev;+e}9ixafBN@$)f%=E=m{=nlf;>11?87)i-f@>Q{CtV z(F5ss*nMg`Rw(B9M*!|}I`@A+r>Hb@D^{{BMJ*{4t2UE4f1h6=I!eE%W#HUTt9Ogq z-;Y=JYXX;putXdVbp7qWLFFoqX4|RKVnHor#zLml`*CH@xiTr~hAWd3xjIizS<9Ro zPXLJ370u7jbk9$aQf}}rYMP^J&GIsDE^jpQ@A1Qel(tMT=-4cZ;iT{N5a@F>%xn#F zgdTxFU)}y-@q+*WRr6%AXxt&?qQcIexG7UNNm2oXWJ@7Vp9%tnE=a;`$11ZEGf`R5 zqYj^FY`j@vtnUE`#Mk^!m{hQPAe`Egr7w`;%@PkCd#;F51ryJUquI6dW@*#flYlR2;|3v{6mkC>a$D2!+m z>#i<78KZYMW5Lm$$aT`p3yptW2Jk1sfAdTH1I&+=*Y!H-7D=^)ZkXaBQ&Nq&mrgE@ z0x~M(!aj?`K%+HQJ{9W`mzWqAxG-QOw$A&GheHT}o+SUbh%hqAp&ykLlf`{dmuShU z>#ZP1@&W~FZSfSdVQ=)Hun^94Rh<2=Mq+t5Kp*BGAQS+)kqRK4dhmeDPGRxnPg4%H z*j##W6Q$}AX zO>tyOY0eVMO+ziG!dRrFrtAUVgARxW8lVnAey}nXm8(?cr*l1}SHG)thG?87GysN` z;d*9Rzi}?AUJ9{MQ4D~yKs}CPZ-GC+2QA`xg#Y*L|7h@keDMEcSzruQB1_n?$?k!Z zp`H9Kg2j&jvcRU)Cndg(*0!g`LBZn31toz_L@_`hMqv__e)fbQPuu~SF^S@V;1H7O ztG8l|yREnGkC)Z_n$}SHrMnQ4(JS@HcrI0B#z<`8LJAZ6SPk|>41&_2Hzt&l549uL zZKD)U6*n2A-lM$Tbr*EQRKE)-C2SETt>}1ZB&PoGp;$=h6`K|3XC)xA5)Q^~MF=E1 z3`Nw?>-zNUHJc3xBn(JK1->Eh8HkaJMvm@7R5##(TR}35U28`&fbjAV)Lq@Q{`S85 zq<_Vc5D38ll;LlcSre}C8~XlgdRpw%6okAAG7V`=Ol4=A%If#Elv!n%x^2u(a*GVH zD>R?S>T+7!aNS4;yu|$xFYj~|2iPE6So}XxF#TO|{S(cw7rrh{ zX-*7W?(7~?vkY3S)u1Iwc#M?$wpA>X;vYZc_|n%~kraC{k4xkitFMDaWB(HKdDrOB z*0FJ#$F3S_yqY2k_E%Q6K{OZmesiEmG^={HFowQ`31aMcf(`~xIGVosyiQQQ_sBmx zD%4iSw9JzG!uYYWa>NE#ax`Qs8v__}3)fUuJ+hI*{gUe*p1OKU zjCn%Vv^No6r5pRj9-}R8^$m-zag>847&35^^xhRc(^kGB@~!3*M+B3XqYl()dE=3x zv8IeEeY#a>`!KeoA z;Sz+uiplGj;C@+9w9Ll>(NM0V6fQCg3ThOHwhF_}C1bOlo>TwWS2@bSV>$I|fWbiB7`5zt4GsA;V6skb;>v>fSg*sv#X=VcGv z9u{`Q8N*}|{E~FggGs{$`&e9ndGIUOzX-JsQR+k6U*}_#)lZ-A+!h?Gt}okZz8s zdc=@)j-G90nQe9L;%J!KA2w=`0|#KgBR0ng0qEo!&q6x}QT13U0ybP1-4dvy>FFt5 zObE%)xswUo{kSo#r#{cP5%Qy)5`$^7MJhUd8t0+oqPK7Mds}<8u~){^nY~(lc;KRH zd`LNE)EgBvtH^^bbgwJhyha>fXV4HciGf%f(6*3&^aC8|;p}!_p0p@pQ_ayyMvF53 zuL|46L`~Unu#B^oPF{P(vCK!`HEN4p0(ENGmfkz&`sc+(N78?e}{?$_xna_$ux~CK>W#x%1uJL$OYXx`5fFB7+l6!?Nfz z^E4T7(Sre~q*NN}!A?&1+={jWHMTBtD1;m{Hc_C7;ch;7H(0!(>Xi=BSV<8T%Z1uS z#r)XamFcr$NBa&!Y5#sRjx^RaM17HAlsd70o)LW3%#knN&4mL_P*r~D!kS(9%O#&; zM6PU|yF3@O)J#V#eX|0>3q7|grscIIKnCA_e)#6&o81GSspzow%E0LU0c#fTvVeh} zB_(_7&*k2HmA!yw#?Chx8j;E4F(wtxi|s$H$}zF!lKTPjTRqer@QedPa8H~Vle^ro z$q5xVPjBXyqi?$pUPUnA)DN(FZ=Q}tZ>=%Z%ty<=N;Q`o94*EzLgUw-%zHEl1dgG% zL*H5g5$l!JjpjI#o+dwBd@#{yzp91~x2;PXH(Sv<3A4J^ih5eKgAI*+p5C}>fB;$3 zaUzVmZu^=mz{G<7Sy|hpcTdy zdn_K6KOJ{HBZJb9av7{$^;;31ex;?3=q&2oOmnDrpyn4(2 z0n4~Z?|D(q%_YeeX1hy|B58YGERv6z&s2(%FM9-bnt%`UQ}^Zmt4)gG2?O@~p$v5gC@>GHQk$ePe zm8F@ZiTvdq^g*BiZjbf!@n}jt?%Iq0ARNz-_}zx_i&QcU&#&rr9^qkz*^}z!gDi0U`5oluqmj;v58{DqVV60$z!v&JbE2pPkRG7Yg0EXGYNNXcyCg9y3U>=2q>HS6Y%=*rK@A@>bgFi zYN1Qt5i$@SbiVvtaYdow7q?~jYj=}@IW4Iib6k-4gwU(%6AQh87Ld4fpea4NnD6;> z`x{oeb3Pj2arAj8?yTcsDhEbBg&}7zE2n?Bs{BG?Y_;-N^)jjK3o7RE*}j=W-nADVE<+r0_c%E2bUR)4I;<<=9t;6VyjZ}7UlreUM|Iejha3%=YAdbxP9*Rg#%>?jHIVw25?e40dFGqh6tW^wky>IDg;LZ>DfcY9XpWmF;$sqM| z;M`)c0KZ4nfM-nwzTJ69#;E(XWDI`eKp=s{3sR!+Fd}iq&y!kQ38CnRZ^T6nngCiH zrxHr5Ue)LX8jWXFwyTb#uql}bp-a*xdx|^J{_?XLB>kHt_nD;<07c?Ip#rr#GhX!V zsDNmqZ>^>Hiv|#36(^P7DB=guqhj>oVzA}UK_jnZ`Ycgj$w&jDp6+a{S?C!?>G>|n zq0;iHaJaC&i8-gps7gR0n~dOn$QK6?^8ui^z7Cl-zSaC36YDwtJO&f`_do&yB=?-1 z)8VPf1~M|BW7Oy1|M`=d^&D*f-?#sFH0b)5k~-c$-fm-#M=}+P2~|EBo4l@e&evc` z*%<}MxWfIM>}uag@H`mo9MuV>h;@nw4jAs6sKp}cE1`|%MtmL1Y*M%Bk?w7YleE_)fIc>@9r`{M_3UQM#n+2s@k;?? zD16DKx(Qw z2TNu?%VLo9$tNVm8-=AoFvC9dQ@MNv9``l!nTeQbh=)2-nsPYxlyzpdPBUeLA3Bjv zrLy;$bHvKZvFC^N^xu)7WGuu-EDK1)^0Xlf#dFTIIdhts<1v#` zGReP~b9K#3P_t9o3{0#2<{!CkP!SgS`pc5BBt4DnZ_CkUaSp$poq9w+Wc2KoY&Z0q ziK!1bglWt4dsHMtFK_c_C9AhHKSGR zz!d3u#)8%|H<4L)4=EkO!C6!OCR@g71({UT`p1$uq3hD4cBX=*Be{CEm;M8NigwH^S+AqRi~x}!@PCV`aJHR zkn#E}^Ngi5Qo=JTao4&>J~pbuO_Ut%ZSlMr65gccTB`X^1l>nghg0FJ-_}e& z;^X+7YLsE|7{QU{sQ3_5)4UA!uy6fbfSj1x>rr-lpVVTarTUAJx7PDGFKlhuSIF`H zsUign>STI)FFv;H-B9=b6BF83sE_7--8N22?0P-;r0P1cxGRcZb(<=+Am$3iN_RScPJoWpFC+}-(0>D&{{+D z(AHcXTYta0*;_H-Rz}5=lUVUX@X_GmMamfUUrBz;YuM(A7r4;n^ir^f`gSk{XfRUo z&_Cod)W}krJpVLRL8r-U zKvQ)~+ajt;Q5He0o-C*{lA)scu43Bib*>-1q}a#p+cFz~B{&UnabG^kE}hm-)|uQI z%k&%p>tjAF$aZasHcnp}`|c?o1@K7S7VV)N=@3MC<_S5|Udp=@$ycDG1>arW3`mcq zRD=uqia6R6oMz^Ie7HvwlSF#?;B{06-pKMi`gvqkm7$Y7Kud=~kRafoyBe5?ms(S> z`~-x1gw3a;Y%cK!oc2u10-bpUWu7idQpS$Vj{R_gjQE`QX|-Xt$o)m`@|b}jWHZR%a{VW&R)w(Wwxc#M_L<-LoF zdMr?*noj>}3-&I*%fpMK3qe!4*gTju&yev77NHrVtB_i1=E1)d{LA~S5qERopA7?L zB7~f-J0@;x)qa(c`6BA-e)+w8gSEBaA%2Pt8sq|)HW=vxQ=!X5Mn(eZ&$ zUr*UnR*Xq=1a@xMgg6gdfwd9SYm zWfxq9&XH#F{V7@?zG<_a-MIx=@2miq0XNNeef2iA=b${p0-`(G`gq?x98qK;{b7)+ zEO0XCu3G<5?jaqnBGdjs;^d}3M(n^&v%2JBi?`}H-g?c*q|+68U!Ai|f>s%~Sg~)O z@?l)Ng0TgBx6?F!*e4x7lg`PcH*pxW*W@hnAd4}%*~!Et_t=9Lcyn+#Cw4Lkz1jLu z5L2h?v-;6J@<6upZ+$TsEPcaJV$Ae#oqc^pTA{C5nS$3vEJjRp+U8|{z~sKo@-R`l zzsQNBKI~Rv)=cL%JGA;)@YzJ4Jy-h-VnT8&=_iyVX+?>F!2_>$@8$WlEUPc!LgcdcAw&{1#gNsiUK)Lbr$((em`?(f~|3kL`7tD?tgyISk$ ztlhLUf3ej)EyTB)y4s8_n9Y;AXd6j4lS^LE57@hbooXWlmsbLEhw?21HK2|y$IcTV}vHJMD4-ZF_WTmyU(?kiI(qxm=j=T+EkEoB>jXc=9J3RsI zHQ`@Qk{p!7_^(vJ>sxE4x^|Y$jC(SV8P>6j50A?7xf#px2ZJ3QX4Xt8x!X*k_IfT> z95I_&kicZwa+;XL>@W{abd2B&@lH(h7_C7!w7^kM3np!%ewk@^o^1-tgN3gL3*&lp zq}^i*!!Fw5H82fd7U4estvGf)YYw(8(!+ifN-DH1Lw9h4w_MV!32T@+f?O2oU)G90 zD$G6?e^1!I4T>o%h-U}dQWiDydtRN%;dpKeOpGanca~=N!}q@eJl3Hwz+VIsF`&h`Q%oeYbrnmY^38kVB*B+HNA1(T=vfOY)4| z6zb)~zcU4a-h(7Xg;W+e-1qKxobOJx%KeGKoNgaI?;u9*!8e&v0xC~SS@#=^-;Hm$ zZ}o%TC*SU_9!qG8e5ckrp)+i!5RQ7r=sHzbG)eiDJ;+RTr@&SRoGZHPfmi zfqivw%O+Epfj&8_@%rWEmd;G++jcvVYC}KpYMV2l`3i#_FXpRT0nA^Kwf%nW@rwO^ z6sn~qMUx>mB^3VJH?!PJ=VTJ=rlvxME&Ka_%Di6{3C4Rg#x5gEaIq4}Je+`E zju-imew55x zz?#gYFSH1ikt^6DG0n#UNk+o0ev5);Z`t3kGd{pYC2 zN=Pw`t{giMnlr~oASXCsHpY-Pg}rB>nj$W|TU*$hz@D5d{?bSPVIp^2IxSkwwdt>m z1-;EXLyxy|G?L4_nB{we73o;IpO}1mq@Ff)J?sfEmDNjuPGR?dPa7IDi66)Yh1pZ3 zJmfGWJ77oD<4cMM0$X@>jhB>Hv0*XImu9%&X_>-XPl`2sMq*`e$2i-<_7A13Oxo9s| zFQZ2ktLpqOP_Mm^{Bx%V>o!@ljn)ni`{nHYT&>!Lay)*P<{8v;q~s0+7e76chW`m1 zCK?n}K(RH|9y8>}xbu41VepEnaK<`;ipBQu3d&xas&;}SJulwz8?M4`(5jO3Fwza< zj&H}7HHW*5($Y)6)tW7})pd0|eBFTGhGz|!+m!!llK`0d=*-iwd6A3P%HBykY7@zN zX$WTqE~(=*)5EmPs*Z2Qf#C1}qd=boMW`BCYieIgk&8JW?Sr`tRf~&rb1fy0%6Ofj z;FhhBWIULRi5tR5+8~dY>^p-BwBWG;7*hQZC2Sclo#VzJj#K{vE0ql_Xgq?rg<^ z4rJU8NcAmWeYRb<_<9%Gu~#8M;bZ80Te4ij#N~*rd)8zY1l>ze;+}eF9?NL0Z;hj& zlB4GJf&Do0&R$ZYW`wx_cHH4&;n#yif@obszb2m9?*zn75sr>0uxm1__(XVzzdc&QYy3x6nZ2P2WVQC~sSG-YM(mV^bC~Nvo zmm+s8$XxC6H6=ikGKI$@Bu0x(-IDWz z#hZS}LI=lHBw-lmL9f+?!}{2U2MRgqdF!2({OtD;nc%%Sx0$Pmpdm^xSTWF4_=9U% z3Mtcm4I2g_`UdY8k$J?Hx=NUY?w;2v;I>t3Eif%BXf(4DlZ?wsuMy&kdW>w7pk>UO zGkCXtv&`qjc=B>R5l`$%u3sVpZ-c{KGu)C5XZ9wYvpMzjcWCE>^q$l)CP@UM9XyJx zE{6!iq|F)nI&6-T7P`?p*6m29(>nwOxvIN&O~SOoZ;I z&shPQfRgH;Cdd)ml!%{!kb5Mn|2$vgJ{+9JNvxUwO$HvW+yBsf#-U83nhm5m~vycU}R=BoY?V}JN@{qqt|a;^?7S1n!{jiR$r zPi`L8&Hku^8JCYyo4wR-1+?kcuR*({BfBL1cTZ|tt~M0jj?%Y{X|on~A^x)`m)iyE zH{lucT;@YGET)YQ<3puNHR`2OTQv>SIxvcx%$%InFnKf5r7aYp=<$N9ZPtv!HV;=r zyA-e0({-$nXf_-Eefy^7raFU|bfUjcnU+L)P?5@$?)0x$?9aeZ((tlM_8S9DFZZ6*CS=3b2%B;Uo1z4bo;1E%=s#+wR9O#0u-JJY z77tsJCVcOUI|j_Is^WV}27;_s4{dJ>>T-LmXdwe^t#c>m(;NcMs72G}S0-g2y$*Iw zx=`I`!`DKZ$K2iW!EO9>KFiCTenlSoDoLgTE5j_uatQOiQqMxBW6O1OiVnt(Ylb12nc26C)2~1RSlX#oReVy?!ry^>Kt97SU*pW zkqsI3^)K_0<;D+pQWZ=HAM-iPkzSrMK-J_*eT}?2Li*h44l(3mv#zua*G~UU#g0qY%x$r6=j!p@-njMt z*B`8@^IoJyM+C=AnwYhT0|VW=tCaya!5n$wCA2*8vME6wcceva_epKOk$d#bZ(L01 zy*-t0H(z*=hYcx{9%tO0?Jv#soxZGj|*;Eaqt?Bp*jJ~qy7 z2J{O8f{^L`)NzBw<$BJiW?M}L)##TR?QE?37x{>z*EpqCxG{hAYUyw0eI~8td5LbY z#zTxu_HZdG1c)B()Tf-*npaDGy{M+c+V=9-p!!_KJgYdu%yps9{ksj|+F2DbwC3&X zp>@Bk^7lwlo^DKTBhg@xYOB)#wzfs+SC6X3~CY zwDI^@h|DtgnxGFg6R);fjLBX}mLLY*2WzLEesB}{11x_ZZ@M6+oQ`NXhhw)wXtNDY#RrzT!_l z^d*NjNq($8iu|5eIhFPli=XU0Vt%y&Zei8dV>7SI*=m-xrAn9Z%57Upc-Y-i5h_99 zSPGbxv^~7GZhk+OI@7H`?@97#=yjQ12CMqx+P(U_l6jf!u4Wpc{k)O}gH&Jh=Y$|G z2k{9f!;kV5LS5B5J})a2H%TGLxDitmy>FG^lbt?S^Wq)Md&FPuif|gwcWddE{MmDR z^DLoXh16@CGW63*lL^t(Iukhy+HOTX2Q{_xZ!!Z!TJtQX2cq=cD3K2Y1*naj+%j}>K78|oA-X(b04W*6{3;S33^9M2xY3)hjrKsyuwt;a z@PpA}G*#_Sx$#Oa&!sKaGqqU?O{h%l;q{O%#RzQrZ3UoX+$q_p4@COfXjZMCc!F=zyF zp7HNoK{HSq2S27q>-??uLM{kKOm?H%u9ZX8ulx9u5^#W4!_1D29~x8^!<2Ftr`;M-N6cbel3%5c*E z=zc0FGN`a{oozG=HX|TG(_es)ygNRdTL5NQZd>>5tVc;6{WeNaZ8#vowaLAVW*;c< zfZA1R`uA`Wdt%VhgTZ`BSEj42&}2zz-W+w8hVy~fev-zM$APx6a${e&YCwho@9`o7 z9K+1KC3tPXkES(qxa6a2SW*y6=||%a#AfNQNo#I?aat-iKXmMFD2#9u=6sQR)>K~Z zK;dz3TbxOc$en6CO&nL|sp-Sr*3dw*oi64RSbAMcE*dD*C`%3Qsp^hap;N)i50YCv zm4KhfDA9~YF-3X}H__vAKg{|*4W_{q7Qu&1aK*t5 zG{$Ja~2I{t)3|~GTq+oT?ZA1xje~$aRHuwLW{+e z$6e8ve8_YUiToA%bwW1uvM(2E^sPT1$gh$$pB~315+5xXU7E5&UTUo`Y8i z#_f2|SjFWnFK2$tT>s^INlX`x^e{Vb>Sm4a%+uZ0{>smcaU*+;__*P=O$~ZmqVo(7 z^1&8h&D6`KQt_STfStuJnI_W~>#naSOQ*|amRie?&#X}d8!07IB_WghdhEQ#Y4HZN z5MYl6Z+-sV?P)TO*XE|9_D#!cH5(pg0)a;d=wa=zPAF_wSZ19c6iW)!ANa74tcAwZ zFHJBG`v+>ggYE)Vdk^myG{?jL3e+;SnTrGKSF-&jzQf~aDp`0$*Q?6jW;kZ1JP9X$ zmI0SbmLTqB+N0tbPp#jyw1)zL#Wj4UP{ii#&7p-^7sbI2N`NccG`o&ESho2VicyRW z#9E{I&hf7GUoMkhJX-UfW@$ATxdDCNK>f*5`Ai3cZB^W^`6XuJvLRfI@JahzpWyw> z$yvJcbyaSaU zX6F%8DyJR1`i7NImz1n}=3qdPR74QR!!BuA{-+4DBoPm%%Xl{17{F5LsJBz zV1p=CN@y8WK$J2{??~uK4Lv|q9O@7Rq&J5VA|>?RLXWhdgbtxYq=pbefROOUd;fv^ z`L6f*cGf!2Df{eocK;n09S5c{^sl_1m1o<{6`U5mBn^7+okbq~DNn<3{q9>-;^Ng< z)==72mJYO@aNsO9U~WRya}J|0>yC1>++6nbnu3c?&m+_;WvpeuZ7l;1{lkrtK)~HA z5^V0f?wfRh82D|zT0Z4b{ZOl?@xR&Gh+D7ir)30Lu3;f>_8G$2qx(5 zzdf@czV}(4&r)tiXWF~_v~TWYGwOtRkHy${x7Cc0)A7(d*gK-k!G{F?Amqw~g}}yD zf;0#*+hU7^gbA0W%XV(;VCmbqt13Fa0VhANXIw@%xjWabMknY__%#RG0_j1T`Z}fG z9?cEgTWLGwK^Dl(Z{{- zc9^Ssoornd;Sf2b|D@4FIbCPNr7G<~3@>jMpFPCTcUs31@kL5#kUX6lU#Rg)XzK9f zmakx*Ujw0#hu<&I;2>q_=#cx5U)G6S>eaLDe`gBJq$!})IIBlXEUqk5d)~V;?N|cz zF-fo5QFoHJ8Q+ZJ!K!vt$pv&gGyEVXv|mTK(7;2V4(iL|nUPuItgcY1Du06kL9*Gn zw#ScDK)Fym$C+{Lwit8HH84w9aNj^^h@|{C z&vxbBO|Fp4hfRQm5qJNCpYKkF+iV^xh1K5kQm;~q6S5b_e*@PGM8Tpy`g*e=tC8WLBh~C;SDos|1|3JRHnv|*Q_}oe6N6Vum zF!wDP_WoPp2G&X+?vbc0P@{8O_JLoN#L?%guV z&OtWgmm}d}0nP`z8n#27sWQg+F&oJ%f;5r{e8{~!s!Kyo?;-vVb$dWAEN1=Rce}JU zt5=MSsHZtD?~fLV!IFG>82uS7+P4ZQ$#3bI-f6`=dU4cmsj_1i!=kKz$d%2RR4IfK zJN(YJf6@LW7XNjS$T1Elf2lG&swux+ctAB;*LDsA%lPMzdJU)#7yh8Wa?c0Z3bBpM z`eH^%)w^Sg>V58}(jlVkM1~)-e14KFW+?wvqSd?1QW5MaH7yoJCGW4zXfsQ{^&(e} z_3PatW5Sbh9h5w(pv7HN028@u=Hc6yiI3K5Tgd2-|Hx!ViZ-Ym=1X($Ax{Qc0J;Y$unqwZfZSFrr&e9}(h+%QS%q3MX6 zDLtE!O%BGxH<*D#p-1h3Oi!sL@7Q_bNTZwam;_auys-^h$$qucP=dET!V%-+`t|uR z60P=su|lCcA#Oob5?ax8y=R{1cR+io_kTcu(ye8n?VLHCQ|1Rs4_r*0PK58ntrX4Q z*x2}&_|XXwj6z@G%GaVSCKxAW{z|t7R>*c(3!&;^t0zmT;Aw22PNQ==0=O;PYw4OV zjFF|Y0Vq)SMSCra9^_kVK79_|>M_reO%d-cz8^;((oYzraAsLrnxw&}DN|7#^=j(r z*S|yueMPBwiZpMWxOpn7`6#jI2dtg-bfFg$7%&t8qyJXUVyBAVKEuUcE;VldUc&wr z{V|{qF>~X6<)fVCK8CoD+-EjkxY&nbLD4@1<7$}iiiQT%ud>!g%xY^4!1HN${pNF>ha3$J0& z{7JoN25A3H+)x#gLk=xgpB@I23TSZyRncldugSkOySo^h*dV7#{U73cg^tEgb?M6q zpe?8>p8>wxIq)(W8FCGn4C{HZhE{{N=rPU8EUa61md0B=XDQvg2|Jr%R2TI+gA+Ob zxu#NyxKCnidM!rwjHSF7xRbofY~}?1C|hzIh>f7RiUC2l`n;p>u`phq$jOM`vly^c}PA3?f^0;|gEgCg6kc*hzrP~sjxlsA> z=cW%a0i(=2XqRgMK(@qLet>trvgXO^&LW1f*9q||f5|1rnH(iad$-AOFL%AMVMI4etjQnc`BYaeGFkmrpyz zB39wCv=?!Cjm6Y`BNoVDY#94)^SiF_OOfp!B&%PSafmmE;H0%tC)-bG1u53EX8?jY zu@8WDorap{NY-Dc3x23*%koW|3Te&myl@P*=^2}UeQNaS;fQO%Z3*Vc#>PxUOFas_ z#_OK;3Fu>|JYQdLW70jht~5OWtn{`CeyON8Mp?Lt4e_7y$a}9LqMC$cm~>nU7^FDHaU6Fb~I)WFddeuyfY@oIeWb4zf^q#P038| z)Am!rGn;qg>b*QcEwHr`&b{}d%=vp!P^@^G@$q(6(kM+Ur$T*d}iu7q7cK({?p@LJGZOrK{RoAXb5hXzO$v-%_c)x z$qcBa2tKw~h4-POIuPWo+Rb#sZXDJcTIDYN6{exsdbBs5$6A>zZz-(~8{T~;B`S)J zCT~9Lq1ZmhomMVG<6%F<3rLZPudHUDpYFY534nxkx%XbArFbD-otLwX3K|$wi{+th z6QDIm_=uO){L!rCJ@p_%v~@SD=;WP>)7sdY7672r2|TWndTWzUkyF*$)YPd#rlE|k*sFy;ba%n_EZb$ z1ICcMg`@VWMDgV#W|U6cB~A()2{l9(1g@H?O7lf=9PODj9VQbJVdmGt7Db0USn9gX zgVSm;AW-v-D_Sh#P?3Hx7ho#&%4+WUK=HnSN+c+B-(|(jq9;>AGnNJ^I~p_^1p?hm z%!~>|?b-Vo!Y_hU)uTVzRDV8%GB&?bQ?_fqAFyjQ_3p5<$L^8(l#xG^4uVufmy%}? zQBg&-uvK()x^3Y@#}y$txyl0Sx#_<$>|x+Bpf-B>y=ZJe>|qD!=mh7r-nQGU*&Qo^ zIYc!MW-lR5gPvEea&GstPPEm-8rqup{`O#3@@DF>!_fm)nMQW(1jkuGdt4=Dm$w49 z=hXmOS$#6z$HBa@%N+i}n&DfM&GY(pW8r!IX3q@EzJHRh zt(Z;f8M}@LKVGXzWQ{x7o90cz29!bfUtd zwJq?K!K#?d2TgO2gF3^5pKM0E*Zxo#r0fOv9KrP_Z%W!&*(fLqSKjGxQL(_$;$nMv z@UhadLPzW|`i@qcD39c3y6pJJfJBL*%}4>xoQ5&3xF=2IU%kPpX)y`$zANXPJ9{;IMgnxRd{Grl|CRvrM!Q-6 zL!BVP@kU?nTltYTnZcP`8*Ok+VNI!sBeQ4PoC5Km6yGzAT^IGtMVDwg4#+;4Q(y>?CE1FHcYm z!LegpgLmwnLJr@_Ih1A@pbdgt%JJg^m4DMg@%?)$&333tX`& zM8Ed9cPNQUT3@Rl0_x0UwUNj@-E)*hm`#+?DG+*kZuw>ZNEMuPVg6~Nv&7YRPv2v z^KJnKJWv3C(5~#t)k`S1o}MF%cm5y@`)s!?9m_i!D|;_oICXilnR(PB0&dbCo>RtB zPWIjxNZP&jgRtJok45Y(H!*R zJVw`tHoY-IskrF=WkrZOyLYBRysq?qnr_48 zQ{d2c;e1O=>-=lE=a;#cnVTgy#_KFnZ9C{kG4Ys^wE|`*>wY8h<9y)Dqv;nG)oNGm z6xeKdohFx7KZh-lvvDHeft16WSXa%)@S;^yXp?9ULa`o{%U_}OO)=z1IBS}#cMN>k zQD^<#Vj{kcy5y2xyo~1O-!wJ)HCXB|o;D!`g?!xlApbk+(sKNZfh@(k2iRko_}b+N z_S?%#%aZXVtu8RcX>C^C5SQ85qhdR4<8Z{6{noa%vAOMr++7}0S(m?+oOuhYYJ9Kp z|BiactGVU4d?Ek@7Y~;#7GodnjntI1@`{5EqYaaFYtvch#gyEAaA6 zp*R%^L7=wGNA8jm{`PO_dNW?vkwg6(-oj6)czCC@wiIp?4-Q{S8W&YL%od^E9oa7b zIVj}|jf!8va}yx1Vl5SG%B%^3UJ&nvm6q2`IQKyH2hu<5LTQ6~r#r`$mI}2_hzEa0 z+-|;qu6l=AKO7qFeEL(m(Oad21|JlsJ}fD&_t<`5Qm$WZ9knrkJi~^#iexH3&)cdt z!OKLOjmO%C>TIsXV=DVk@0>)dAAMiALe35LyW7iE|8v;hzgt&0Ypo#AYu&23e;T|1 zIi(aT)49%YxqN_Pk-I8%eUQi(R4~c>E9x6WGBcSqRe+hWUUFK$UWJrEZq2c9B0F?n zKmUa4@6y`nXs|#kd1`vDqC4YCxK)D4*Lmi85t{LGVlj1c>T4cM0tXi0gCZO)ThF!; zD&HE1FPsCVZzlq}S6{?kaHMQ?Eaqwqj=n6Erm9-%l9aNS9l7F=tr+;S@=R$iglN*_ z{Zd9@a9E^_e)&Iqs(p_9MzokeuV#y@2d&I~dUFeL0p0oDv&qR3qVP=R%Ea*HeAzT5 z#6Enh9M%=o;^|7s-E2;CIc^VRjA+A0`+k4$S);A3nKk$%qI^eiHc*Kk`C8tjiv|}e zUCs=4rFJew#$$#^&F0Eng`7vZ=#63MG}f7ouvzqMswqONL<}`9^Rjm>*ma<4k4@pR zImkuyw8snsd1QCm))hQ$l}|Z_IQd$sCe{raNX>t5dmZ0+e)n9^hpR4MIRm({yO<8O z;Y(!5ZJ7LzN=3D8p*7PXsWNM`H`ZisjcPBqkL0PB$n{DVDOW^Ynvd)E>m}wJ(XUY% zLPSxUw$GEHHa^S82h$f=$qRnLE?&&WD28pAQ_w_*lI*t}UPsv+msD7NM;-nJC&$sa z2k}M$cmc##lZK47RSySuQQDlg!A^(lnDVLgUBenfhw9-e!m!LglaO!a=4Siw*_wu+ zz6)amCLvB08mN70TH6$sTC_NEc?EHK^%E>VT0(UFcD1vzHlA%TKr-$Q_-$7$WpY^x zgg9NrM{grGq|{{_kQ2Kx>nAHlTbm?(U)UQ3HCt+48MWkITndcRJD}X9>}i3vIU}Xj zem2w73>VQHNhb^KK#b-CZ+byrfqvq4G&~DChX zt{Qhxl(gCEypI1WMETiv+{dKB5F6TbZaThuehU-G?Rzh&dMSf)Vr4I3AJ*JpCpxb8 zWem2yXN(!+8rjfs)?A3!Qj}Ip`?;(Tt~*?2C8GxMm_Y4`u73c_!;4ip@Ryo_Spco?6D9pFvC`D@cJAD zOMZNv%Dpk^Uo#i@2ni#37n;&H(ywbhmr;vuTkl?fWTIu>ZN_ID8&m1K?%=~}sp&mH zw)b6SujiHUk8=3a%yr!701lPbCd_?9zn0mZt%(vbZT-Z{!%A-Y%`y71-*P(Nof}FK z3+WOPr*ZrMDa+()EA zb9SlW9VX_k*L0oX@;0gP<6-OFZ?$;XRWJa6%OkdW{=d!trN;jQGt``3DaIsR0xaaQ RBcK3)j;8+8vd4Ds{|9H_DW(7b literal 0 HcmV?d00001 diff --git a/docs/how_to_choose_an_index_guide.md b/docs/how_to_choose_an_index_guide.md new file mode 100644 index 00000000..9d42bc33 --- /dev/null +++ b/docs/how_to_choose_an_index_guide.md @@ -0,0 +1,43 @@ +# How to Choose a Nearest-Neighbor Index Guide + +## Introduction + +When leveraging vector search in your application, selecting the right algorithm and accurately measuring recall are pivotal for enhancing search efficiency and result relevancy. This guide is crafted to assist developers in navigating through these crucial processes with ease, specifically for those who are developing with LangChain Python. To find out what is a good index for your vector database, the first decision you need to make is to choose from KNN or ANN. + +## KNN vs. ANN + +K-Nearest Neighbors (KNN) and Approximate Nearest Neighbors (ANN) are both nearest neighbor algorithms used for vector similarity search. KNN is a brute force algorithm that guarantees perfect recall at the cost of speed. ANN algorithms offer significantly faster speeds, but with less perfect recall. + +For example, you have an item description and you want to search for the top 3 most similar items in the database. KNN checks every single item in the database and finds the top 3. On the other hand, ANN uses an algorithm to guess where the top 3 items are, and search only those vectors within the calculated scope, sacrificing some accuracy for speed. While KNN guarantees you get the most similar items, the computational cost is too high for scalability. ANN, although faster and good for a quick search, might occasionally miss an item that would have been selected. + +![Index Choosing Decision Tree](_static/index_choosing_decision_tree.png) + +## Choosing between KNN and ANN + +In the context of vector search indexing, choosing between KNN and ANN is a trade-off between precision and efficiency. For applications with a small dataset (less than 10k) or requiring absolute precision, such as in legal or academic research where every possible relevant result must be identified, KNN is preferable despite its higher computational demands. In contrast, in scenarios where speed is crucial, resources are limited, and a high accuracy is still required–which is the case for most commercial usage–ANN is the better choice. + +If you decide to adopt ANN indexing for your application’s vector similarity search, an important question arises: which algorithm to choose for ANN’s similarity approximation? + +## Choosing the Right ANN Algorithm + +When selecting an indexing algorithm for your application’s ANN vector search, it's essential to consider your specific needs and the characteristics of your dataset. There are two major categories of ANN algorithms: + +- Graph-based algorithms are good at handling complex, high-dimensional data, offering faster search speeds by navigating through a network of interconnected data points. They are especially useful when the dataset is relatively larger, as they can efficiently traverse this network to find close matches. However, the memory usage and index building time could also grow significantly as the dataset grows compared to tree-based indexes. Example: +HNSW (through the pgvector extension) + +- Tree-based algorithms organize data in a structured, hierarchical manner, making them efficient for lower-dimensional datasets. They offer a structured and often more resource-efficient approach to partitioning space and finding neighbors, but their performance degrades when the embeddings have high dimensionality but low information density. Example: +IVFFlat (through the pgvector extension) + +Here is a comparison table between Graph-based and Tree-based Indexing algorithms: + +| Feature | Graph-based algorithm | Tree-based algorithm | +|---------|-----------------------|----------------------| +|Latency |Generally offers higher search efficiency, especially in high-dimensional spaces due to its ability to skip over irrelevant regions of the graph. Write latency is generally higher.| Efficiency depends on dataset distribution characteristics.| +| Accuracy | Can achieve high levels of accuracy by adjusting the graph's complexity (e.g., the number of edges per node), allowing for fine-tuning based on the dataset.| Accuracy is influenced by the tree's depth and branching factor. While very accurate in lower dimensions, accuracy decreases on embeddings with high dimensionality but low information density.| +| Examples | HNSW (through pgvector) | IVFFlat (through pgvector)| +| Index Creation Time| Slower | Faster| +| Memory used | More | Less | + +## Next Step + +By carefully evaluating your requirements for accuracy, computational resources, and scalability, you can select the optimal indexing approach for your vector search application. Once you've made the choice for your indexing algorithm, turn to [this guide](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/blob/main/samples/index_tuning_sample/README.md) as your next step for evaluating indexing performance and fine-tuning your indexes as needed. diff --git a/docs/index.rst b/docs/index.rst index 8982ab15..33ec4547 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,14 @@ API Reference langchain_google_cloud_sql_pg/loader langchain_google_cloud_sql_pg/history + +How to Choose a Nearest-Neighbor Index Guide +-------------------------------------------- +.. toctree:: + :maxdepth: 2 + + how_to_choose_an_index_guide.md + Changelog --------- .. toctree:: From f10ae6c9a8645874a5ab64e846ec540aeddf977a Mon Sep 17 00:00:00 2001 From: Christiaan Hees Date: Mon, 19 Aug 2024 22:52:13 +0200 Subject: [PATCH 07/12] docs: Update README.md to fix 404 links to templates (#182) --- samples/langchain_on_vertexai/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/langchain_on_vertexai/README.md b/samples/langchain_on_vertexai/README.md index 27bad116..63974b05 100644 --- a/samples/langchain_on_vertexai/README.md +++ b/samples/langchain_on_vertexai/README.md @@ -9,9 +9,9 @@ Use the following templates to deploy Retrieval Augmented Generation (RAG) appli Description | Sample ----------- | ------ -Deploy a pre-built `LangchainAgent` with custom RAG tool | [prebuilt_langchain_agent.py](prebuilt_langchain_agent.py) -Build and deploy a question-answering RAG application | [retriever_chain.py](retriever_chain.p) -Build and deploy an Agent with RAG tool and Memory | [retriever_agent_with_history.py](retriever_agent_with_history.py) +Deploy a pre-built `LangchainAgent` with custom RAG tool | [prebuilt_langchain_agent_template.py](prebuilt_langchain_agent_template.py) +Build and deploy a question-answering RAG application | [retriever_chain_template.py](retriever_chain_template.p) +Build and deploy an Agent with RAG tool and Memory | [retriever_agent_with_history_template.py](retriever_agent_with_history_template.py) ## Before you begin From 1489f818c1d62bfee5c5a3bab42d380556662e82 Mon Sep 17 00:00:00 2001 From: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:52:44 -0600 Subject: [PATCH 08/12] fix: add caching for background loop/thread (#184) --- src/langchain_google_cloud_sql_pg/engine.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/langchain_google_cloud_sql_pg/engine.py b/src/langchain_google_cloud_sql_pg/engine.py index 02ec8fe0..36850317 100644 --- a/src/langchain_google_cloud_sql_pg/engine.py +++ b/src/langchain_google_cloud_sql_pg/engine.py @@ -107,6 +107,8 @@ class PostgresEngine: """A class for managing connections to a Cloud SQL for Postgres database.""" _connector: Optional[Connector] = None + _default_loop: Optional[asyncio.AbstractEventLoop] = None + _default_thread: Optional[Thread] = None __create_key = object() def __init__( @@ -166,9 +168,12 @@ def from_instance( """ # Running a loop in a background thread allows us to support # async methods from non-async environments - loop = asyncio.new_event_loop() - thread = Thread(target=loop.run_forever, daemon=True) - thread.start() + if cls._default_loop is None: + cls._default_loop = asyncio.new_event_loop() + cls._default_thread = Thread( + target=cls._default_loop.run_forever, daemon=True + ) + cls._default_thread.start() coro = cls._create( project_id, region, @@ -177,12 +182,12 @@ def from_instance( ip_type, user, password, - loop=loop, - thread=thread, + loop=cls._default_loop, + thread=cls._default_thread, quota_project=quota_project, iam_account_email=iam_account_email, ) - return asyncio.run_coroutine_threadsafe(coro, loop).result() + return asyncio.run_coroutine_threadsafe(coro, cls._default_loop).result() @classmethod async def _create( @@ -228,7 +233,7 @@ async def _create( ) if cls._connector is None: cls._connector = Connector( - loop=asyncio.get_event_loop(), + loop=loop, user_agent=USER_AGENT, quota_project=quota_project, refresh_strategy=RefreshStrategy.LAZY, From e5dca973d625c4df4c3e741a3ad8e95be0cd1472 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:34:17 -0400 Subject: [PATCH 09/12] fix: Fix QueryOptions not applied to similarity search bug (#185) * fix: Fix QueryOptions not applied to similarity search bug * syntax --- src/langchain_google_cloud_sql_pg/engine.py | 14 +++++++++++++- src/langchain_google_cloud_sql_pg/vectorstore.py | 8 +++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/langchain_google_cloud_sql_pg/engine.py b/src/langchain_google_cloud_sql_pg/engine.py index 36850317..da284af9 100644 --- a/src/langchain_google_cloud_sql_pg/engine.py +++ b/src/langchain_google_cloud_sql_pg/engine.py @@ -327,7 +327,7 @@ async def _aexecute(self, query: str, params: Optional[dict] = None) -> None: await conn.commit() async def _aexecute_outside_tx(self, query: str) -> None: - """Execute a SQL query.""" + """Execute a SQL query in a new transaction.""" async with self._engine.connect() as conn: await conn.execute(text("COMMIT")) await conn.execute(text(query)) @@ -343,6 +343,18 @@ async def _afetch( return result_fetch + async def _afetch_with_query_options( + self, query: str, query_options: str + ) -> Sequence[RowMapping]: + """Set temporary database flags and fetch results from a SQL query.""" + async with self._engine.connect() as conn: + await conn.execute(text(query_options)) + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + + return result_fetch + def _execute(self, query: str, params: Optional[dict] = None) -> None: """Execute a SQL query.""" return self._run_as_sync(self._aexecute(query, params)) diff --git a/src/langchain_google_cloud_sql_pg/vectorstore.py b/src/langchain_google_cloud_sql_pg/vectorstore.py index 1c4685e2..a4a0b53a 100644 --- a/src/langchain_google_cloud_sql_pg/vectorstore.py +++ b/src/langchain_google_cloud_sql_pg/vectorstore.py @@ -599,10 +599,12 @@ async def __query_collection( filter = f"WHERE {filter}" if filter else "" stmt = f"SELECT *, {search_function}({self.embedding_column}, '{embedding}') as distance FROM \"{self.table_name}\" {filter} ORDER BY {self.embedding_column} {operator} '{embedding}' LIMIT {k};" if self.index_query_options: - await self.engine._aexecute( - f"SET LOCAL {self.index_query_options.to_string()};" + query_options_stmt = f"SET LOCAL {self.index_query_options.to_string()};" + results = await self.engine._afetch_with_query_options( + stmt, query_options_stmt ) - results = await self.engine._afetch(stmt) + else: + results = await self.engine._afetch(stmt) return results def similarity_search( From 50dc32f8ae476c98e3ed38a153096551ce02d340 Mon Sep 17 00:00:00 2001 From: dishaprakash <57954147+dishaprakash@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:10:00 +0000 Subject: [PATCH 10/12] fix: Fixed extra char in requirements.txt (#196) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab3fdc9f..8d57c3de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ cloud-sql-python-connector[asyncpg]==1.10.0 langchain-core==0.2.12 -numpy===1.24.4; python_version<='3.8' +numpy==1.24.4; python_version<='3.8' numpy==1.26.4; python_version>'3.8' pgvector==0.3.0 SQLAlchemy[asyncio]==2.0.31 From 40b6e5913d3b4940d983004f409400479a438d04 Mon Sep 17 00:00:00 2001 From: dishaprakash <57954147+dishaprakash@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:00:19 +0000 Subject: [PATCH 11/12] chore(deps): update python-nonmajor (#197) Co-authored-by: Mend Renovate --- pyproject.toml | 8 ++++---- requirements.txt | 6 +++--- samples/index_tuning_sample/requirements.txt | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 942944ed..b6627534 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,11 +40,11 @@ Changelog = "https://github.com/googleapis/langchain-google-cloud-sql-pg-python/ [project.optional-dependencies] test = [ - "black[jupyter]==24.4.2", + "black[jupyter]==24.8.0", "isort==5.13.2", - "mypy==1.10.1", - "pytest-asyncio==0.23.7", - "pytest==8.2.2", + "mypy==1.11.2", + "pytest-asyncio==0.24.0", + "pytest==8.3.2", "pytest-cov==5.0.0" ] diff --git a/requirements.txt b/requirements.txt index 8d57c3de..c6a36c54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ cloud-sql-python-connector[asyncpg]==1.10.0 -langchain-core==0.2.12 +langchain-core==0.2.35 numpy==1.24.4; python_version<='3.8' numpy==1.26.4; python_version>'3.8' -pgvector==0.3.0 -SQLAlchemy[asyncio]==2.0.31 +pgvector==0.3.2 +SQLAlchemy[asyncio]==2.0.32 diff --git a/samples/index_tuning_sample/requirements.txt b/samples/index_tuning_sample/requirements.txt index 48cf49be..0a7a2867 100644 --- a/samples/index_tuning_sample/requirements.txt +++ b/samples/index_tuning_sample/requirements.txt @@ -1,3 +1,3 @@ -langchain-google-cloud-sql-pg==0.4.1 -langchain==0.1.8 -langchain-google-vertexai==1.0.6 \ No newline at end of file +langchain-google-cloud-sql-pg==0.7.0 +langchain==0.2.14 +langchain-google-vertexai==1.0.10 \ No newline at end of file From ec2330822917cb95b1feb4400c72b533e787df6b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:12:18 -0700 Subject: [PATCH 12/12] chore(main): release 0.8.0 (#173) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 22 ++++++++++++++++++++ src/langchain_google_cloud_sql_pg/version.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d548423..5539b0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [0.8.0](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/compare/v0.7.0...v0.8.0) (2024-09-04) + + +### Features + +* Add table name to default index name ([#171](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/171)) ([8e61bc7](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/8e61bc779bc8f803e40e76aaeffdb93c35a5c90f)) +* Remove langchain-community dependency ([#172](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/172)) ([b4f40bb](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/b4f40bb389b40853e3deed37e1385a7866741231)) + + +### Bug Fixes + +* Add caching for background loop/thread ([#184](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/184)) ([1489f81](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/1489f818c1d62bfee5c5a3bab42d380556662e82)) +* Fix QueryOptions not applied to similarity search bug ([#185](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/185)) ([e5dca97](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/e5dca973d625c4df4c3e741a3ad8e95be0cd1472)) +* Fixed extra char in requirements.txt ([#196](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/196)) ([50dc32f](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/50dc32f8ae476c98e3ed38a153096551ce02d340)) + + +### Documentation + +* Add index choosing guide ([#178](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/178)) ([e96ffb6](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/e96ffb6dc99425e4dafb8ac13730eed253e74c4e)) +* Added vector store initialization from documents ([#174](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/174)) ([eb2eac3](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/eb2eac303f64e809e6f3fc9bc3307be163602a4e)) +* Update README.md to fix 404 links to templates ([#182](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/issues/182)) ([f10ae6c](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/commit/f10ae6c9a8645874a5ab64e846ec540aeddf977a)) + ## [0.7.0](https://github.com/googleapis/langchain-google-cloud-sql-pg-python/compare/v0.6.1...v0.7.0) (2024-07-23) diff --git a/src/langchain_google_cloud_sql_pg/version.py b/src/langchain_google_cloud_sql_pg/version.py index 85217a3e..74efebbe 100644 --- a/src/langchain_google_cloud_sql_pg/version.py +++ b/src/langchain_google_cloud_sql_pg/version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "0.7.0" +__version__ = "0.8.0"