diff --git a/README.md b/README.md index d86ec4d8025b493baf64c41ec8f6528242d92f8e..0ccef2fc6b38a0a30b53ffe507d9c2ae80a809e9 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,12 @@ Load the published model, fold two protein chains together, and write an mmCIF file. The example omits `num_sampling_steps` and uses the model default. ```python -from pathlib import Path - import torch + +from pathlib import Path from transformers import AutoModel + model = AutoModel.from_pretrained( "Synthyra/ESMFold2-Fast", trust_remote_code=True, @@ -109,11 +110,13 @@ state mixture and projection, followed by one trainable transformer probe. ```python import torch + from transformers import ( AutoModelForSequenceClassification, AutoModelForTokenClassification, ) + model_id = "Synthyra/ESMFold2-Fast" sequence_model = AutoModelForSequenceClassification.from_pretrained( model_id, num_labels=2, trust_remote_code=True @@ -123,11 +126,11 @@ token_model = AutoModelForTokenClassification.from_pretrained( ).eval() sequences = ["MSTNPKPQRKTKRNT", "MKTIIALSYIFCLVFA"] batch = sequence_model.prepare_classifier_inputs(sequences) -biological = batch["attention_mask"].bool() +biological = batch["attention_mask"].bool() # (b, l) -sequence_labels = torch.zeros(len(sequences), dtype=torch.long) -token_labels = torch.full_like(batch["input_ids"], -100) -token_labels[biological] = 0 +sequence_labels = torch.zeros(len(sequences), dtype=torch.long) # (b,) +token_labels = torch.full_like(batch["input_ids"], -100) # (b, l) +token_labels[biological] = 0 # selected biological positions; labels stay (b, l) with torch.inference_mode(): sequence_output = sequence_model(**batch, labels=sequence_labels) @@ -147,6 +150,7 @@ python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20" ```python from peft import LoraConfig, TaskType, get_peft_model + peft_model = get_peft_model( sequence_model, LoraConfig( diff --git a/fastplms/attention/_core.py b/fastplms/attention/_core.py index b02356f940cb9f640b509d4367747118501b7e98..64ead3cce3be544b83543732cf18bcb4353a8cb0 100644 --- a/fastplms/attention/_core.py +++ b/fastplms/attention/_core.py @@ -9,6 +9,7 @@ from __future__ import annotations import warnings import torch + from collections import OrderedDict from collections.abc import Callable from dataclasses import dataclass diff --git a/fastplms/attention/_kernel_lock.py b/fastplms/attention/_kernel_lock.py index a859f57c9af23eb30743234d333c2f4ffbdf0406..03f506e2f1e5e07c469b7934e57b89da86f905f8 100644 --- a/fastplms/attention/_kernel_lock.py +++ b/fastplms/attention/_kernel_lock.py @@ -4,6 +4,7 @@ from __future__ import annotations import json import os + from pathlib import Path from typing import Any diff --git a/fastplms/attention/interfaces.py b/fastplms/attention/interfaces.py index 1846eb5c6c064bf333730f27c99c0181c0c1def1..8d3fef566b65e3b961f1e2e5c3896e133507ecbd 100644 --- a/fastplms/attention/interfaces.py +++ b/fastplms/attention/interfaces.py @@ -3,6 +3,7 @@ from __future__ import annotations import torch + from collections.abc import Mapping from functools import partial from typing import Any diff --git a/fastplms/embeddings/batches.py b/fastplms/embeddings/batches.py index 815938be3ec5a093ec4c3dbbcc38b29025b0a5a8..b2edfbeeea4a7609e71f920e560969be7b9a372b 100644 --- a/fastplms/embeddings/batches.py +++ b/fastplms/embeddings/batches.py @@ -1,378 +1,379 @@ -"""Execute model-specific batches and return ordered residue-aware CPU tensors.""" - -from __future__ import annotations - -import torch -from collections.abc import Callable, Iterator, Sequence -from contextlib import contextmanager -from dataclasses import dataclass, field -from typing import Any -from torch import Tensor - -from .identity import _model_device -from .inputs import _planned_batches -from .pooling import Pooler -from .types import EmbeddingBatch, EmbeddingInput, EmbeddingRecord - - -_MAX_PARTI_RESIDUES = 2_048 - - -def _validate_parti_length(M: Tensor) -> None: - """Reject an oversized attention graph before model inference.""" - - # M: (b, l) - n_residues = int(M.to(dtype=torch.int64).sum(dim=1).max().item()) - if n_residues > _MAX_PARTI_RESIDUES: - raise ValueError(f"parti supports at most {_MAX_PARTI_RESIDUES:,} biological residues.") - - -def select_hidden_state_embeddings( - last_hidden_state: Tensor, - hidden_states: tuple[Tensor, ...] | None, - *, - hidden_state_index: int = -1, - store_all_hidden_states: bool = False, -) -> Tensor: - """Select one hidden state or stack every state without changing values.""" - # last_hidden_state and each hidden_states entry: (b, l, d) - if store_all_hidden_states: - if not hidden_states: - raise ValueError("store_all_hidden_states requires model hidden states.") - # H has shape (b, n, l, d), where n follows the model's output order. - return torch.stack(hidden_states, dim=1) # (b, n, l, d) - if hidden_state_index == -1: - return last_hidden_state # (b, l, d) - if not hidden_states: - raise ValueError("hidden_state_index requires model hidden states.") - return hidden_states[hidden_state_index] # (b, l, d) - - -def _residue_embeddings(X: Tensor, M: Tensor) -> list[Tensor]: - """Copy every sample's biological residues to the host in one transfer. - - Boolean indexing packs the selected rows in batch order, so splitting the - packed rows by residue count gives the values that indexing each sample - would. Each returned tensor owns its storage, as a per-sample copy does. - """ - # X: (b, l, d); M: (b, l) - residue_counts = M.sum(dim=1).tolist() # b counts r_i - packed = X[M].detach().cpu() # (sum of r_i, d) - return [sample.clone() for sample in torch.split(packed, residue_counts)] # each: (r_i, d) - - -@contextmanager -def _temporary_eval(model: Any) -> Iterator[None]: - was_training = getattr(model, "training", None) - eval_method = getattr(model, "eval", None) - train_method = getattr(model, "train", None) - if ( - not isinstance(was_training, bool) - or not callable(eval_method) - or not callable(train_method) - ): - yield - return - eval_method() - try: - yield - finally: - train_method(was_training) - - -def _biological_residue_mask( - input_ids: Tensor, - attention_mask: Tensor, - tokenizer: Any, -) -> Tensor: - """Remove padding and tokenizer-declared special tokens from M.""" - - # input_ids, attention_mask: (b, l) - M = attention_mask.to(dtype=torch.bool) # (b, l) - special_ids = tuple(int(token_id) for token_id in getattr(tokenizer, "all_special_ids", ())) - if special_ids: - specials = torch.tensor( # (n_special,) - special_ids, - device=input_ids.device, - dtype=input_ids.dtype, - ) - M = M & ~torch.isin(input_ids, specials) # (b, l) - return M # (b, l) - - -def _generic_embedding_batch( - model: Any, - sequences: list[str], - *, - tokenizer: Any | None, - max_length: int | None, - truncate: bool, - need_attentions: bool, - model_kwargs: dict[str, Any], -) -> EmbeddingBatch: - config = getattr(model, "config", None) - model_type = str(getattr(config, "model_type", "")).lower() - if tokenizer is None: - tokenizer = getattr(model, "tokenizer", None) - - if tokenizer is None and model_type == "e1": - output = model._embed(sequences, return_attention_mask=True, **model_kwargs) - if not isinstance(output, tuple) or len(output) != 2: - raise TypeError("E1 _embed must return (X, residue_mask).") - X, M = output # (b, l, d), (b, l) - preparer = getattr(model, "prep_tokens", None) - if preparer is not None and hasattr(preparer, "get_batch_kwargs"): - prepared = preparer.get_batch_kwargs(sequences, device=X.device) - input_ids = prepared["input_ids"] # (b, l) - boundary_ids = preparer.boundary_token_ids.to( # (n_boundary,) - device=input_ids.device, dtype=input_ids.dtype - ) - # E1 wraps each raw sequence in BOS, context-label, terminal-label, - # and EOS tokens. Only amino-acid rows are biological residues. - M = M.to(dtype=torch.bool) & ~torch.isin(input_ids, boundary_ids) # (b, l) - if need_attentions: - raise ValueError("parti is not available for tokenizer-free E1 embedding.") - return EmbeddingBatch( # X: (b, l, d); residue_mask: (b, l) - X=X, - residue_mask=M.to(dtype=torch.bool), - ) - if tokenizer is None: - raise ValueError("A tokenizer is required for this model's embedding path.") - - tokenize_kwargs: dict[str, Any] = { - "return_tensors": "pt", - "padding": True, - "truncation": truncate, - } - if max_length is not None and truncate: - # ``max_length`` is a biological-residue limit. Tokenizer limits include - # boundary tokens, so reserve their declared width instead of dropping - # residues at the exact boundary. - special_token_count = 0 - num_special_tokens_to_add = getattr(tokenizer, "num_special_tokens_to_add", None) - if callable(num_special_tokens_to_add): - special_token_count = int(num_special_tokens_to_add(pair=False)) - tokenize_kwargs["max_length"] = max_length + special_token_count - sequence_tokenizer = getattr(model, "_tokenize_sequence_batch", None) - if callable(sequence_tokenizer): - encoded = sequence_tokenizer(sequences, tokenizer=tokenizer, **tokenize_kwargs) - else: - encoded = tokenizer(sequences, **tokenize_kwargs) - device = _model_device(model) - input_ids = encoded["input_ids"].to(device) # (b, l) - attention_mask = encoded.get( # (b, l) - "attention_mask", - input_ids.new_ones(input_ids.shape), - ).to(device) - M = _biological_residue_mask(input_ids, attention_mask, tokenizer) # (b, l) - if need_attentions: - # Validate l before either the backbone or its quadratic attention graph - # is materialized. M has shape (b, l). - _validate_parti_length(M) - X = model._embed(input_ids, attention_mask, **model_kwargs) # (b, l, d) - attentions = None - if need_attentions: - output = model( - input_ids=input_ids, - attention_mask=attention_mask, - output_attentions=True, - return_dict=True, - ) - attentions = getattr(output, "attentions", None) # each: (b, h, l, l) - if attentions is None: - raise ValueError("The model did not return attentions required by parti.") - return EmbeddingBatch( # X: (b, l, d); M: (b, l) - X=X, - residue_mask=M, - attentions=attentions, - ) - - -@dataclass(eq=False) -class BatchExecutor: - """Model and batch policy for one bounded embedding window at a time.""" - - model: Any - batch_size: int - max_tokens_per_batch: int | None - max_length: int | None - truncate: bool - model_kwargs: dict[str, Any] - hidden_state_source: str - normalized_decoder_inputs: tuple[str, ...] | None - decoder_input_ids: Tensor | None - decoder_attention_mask: Tensor | None - _embedding_batch_fn: Callable[..., EmbeddingBatch] | None - tokenizer: Any | None - store_all_hidden_states: bool - full_embeddings: bool - dtype: torch.dtype | None - pooler: Pooler | None - attention_backend: str | None - need_attentions: bool - model_type: str = field(init=False) - resolved_tokenizer: Any = field(init=False) - - def __post_init__(self) -> None: - config = getattr(self.model, "config", None) - self.model_type = str(getattr(config, "model_type", "")).lower() - self.resolved_tokenizer = ( - self.tokenizer if self.tokenizer is not None else getattr(self.model, "tokenizer", None) - ) - - def run_window( - self, - window_records: Sequence[EmbeddingInput], - *, - window_start: int, - ) -> tuple[list[EmbeddingRecord], dict[str, tuple[int, int]]]: - """Restore source order after length-bucketed inference and pooling.""" - - pool_slices: dict[str, tuple[int, int]] = {} - window_results: dict[int, EmbeddingRecord] = {} - for local_positions in _planned_batches( - window_records, - range(len(window_records)), - batch_size=self.batch_size, - max_tokens_per_batch=self.max_tokens_per_batch, - max_length=self.max_length, - truncate=self.truncate, - ): - batch_positions = [window_start + position for position in local_positions] - batch_records = [window_records[position] for position in local_positions] - sequences = [ - record.sequence[: self.max_length] - if self.truncate and self.max_length is not None - else record.sequence - for record in batch_records - ] - batch_model_kwargs = dict(self.model_kwargs) - if self.model_type == "fast_ankh" or self.hidden_state_source == "decoder": - batch_model_kwargs["hidden_state_source"] = self.hidden_state_source - if self.normalized_decoder_inputs is not None: - batch_model_kwargs["decoder_inputs"] = [ - self.normalized_decoder_inputs[position] for position in batch_positions - ] - if self.decoder_input_ids is not None: - # decoder_input_ids: (n_records, l_decoder) - indices = torch.tensor( # (b,) - batch_positions, - device=self.decoder_input_ids.device, - dtype=torch.long, - ) - batch_model_kwargs["decoder_input_ids"] = ( # (b, l_decoder) - self.decoder_input_ids.index_select(0, indices) - ) - if self.decoder_attention_mask is not None: - # decoder_attention_mask: (n_records, l_decoder) - indices = torch.tensor( # (b,) - batch_positions, - device=self.decoder_attention_mask.device, - dtype=torch.long, - ) - batch_model_kwargs["decoder_attention_mask"] = ( - self.decoder_attention_mask.index_select(0, indices) # (b, l_decoder) - ) - custom_batch = self._embedding_batch_fn or getattr(self.model, "_embedding_batch", None) - if custom_batch is not None: - if self.model_type == "fast_ankh": - batch = custom_batch( - sequences, - tokenizer=self.resolved_tokenizer, - max_length=self.max_length, - truncate=self.truncate, - need_attentions=self.need_attentions, - **batch_model_kwargs, - ) - else: - batch = custom_batch(sequences, **batch_model_kwargs) - if not isinstance(batch, EmbeddingBatch): - raise TypeError("_embedding_batch must return EmbeddingBatch.") - else: - batch = _generic_embedding_batch( - self.model, - sequences, - tokenizer=self.tokenizer, - max_length=self.max_length, - truncate=self.truncate, - need_attentions=self.need_attentions, - model_kwargs=batch_model_kwargs, - ) - X = batch.X # (b, l, d) or (b, n_states, l, d) - raw_mask = batch.residue_mask # (b, l) - if not isinstance(X, Tensor) or not isinstance(raw_mask, Tensor): - raise TypeError("Embedding batches must provide Tensor X and residue_mask.") - if X.is_meta or raw_mask.is_meta: - raise ValueError("Embedding batches cannot contain meta tensors.") - if not X.is_floating_point(): - raise TypeError("Embedding batches must use a floating-point X dtype.") - if raw_mask.is_complex() or not bool(torch.isfinite(raw_mask).all()): - raise ValueError("Embedding residue_mask must contain finite binary values.") - if not bool(((raw_mask == 0) | (raw_mask == 1)).all()): - raise ValueError("Embedding residue_mask must contain finite binary values.") - M = raw_mask.to(device=X.device, dtype=torch.bool) # (b, l) - valid_X_shape = ( - X.ndim == 3 - and X.shape[0] == len(batch_records) - and X.shape[-1] > 0 - and M.shape == X.shape[:2] - ) - valid_all_states_shape = ( - X.ndim == 4 - and self.store_all_hidden_states - and self.full_embeddings - and X.shape[0] == len(batch_records) - and X.shape[1] > 0 - and X.shape[-1] > 0 - and M.shape == (X.shape[0], X.shape[2]) - ) - if not (valid_X_shape or valid_all_states_shape): - raise ValueError( - "Embedding batches must provide X with shape (b, l, d), or " - "(b, states, l, d) when storing all hidden states, and " - "residue_mask with shape (b, l)." - ) - if not bool(M.any(dim=1).all()): - raise ValueError("Every embedding sample must contain a biological residue.") - finite_selected = ( # X.shape - torch.isfinite(X) | ~M.unsqueeze(-1) - if X.ndim == 3 - else torch.isfinite(X) | ~M[:, None, :, None] - ) - if not bool(finite_selected.all()): - raise ValueError("Biological residue embeddings produced non-finite output.") - if self.need_attentions: - # Validate the biological graph only after mask integrity is established. - _validate_parti_length(M) - if self.dtype is not None: - X = X.to(dtype=self.dtype) # unchanged shape - - if self.full_embeddings: - if X.ndim == 4: - values = [ - X_i[:, M_i, :].detach().cpu() # (n_states, r_i, d) - for X_i, M_i in zip(X, M, strict=True) - ] - else: - values = _residue_embeddings(X, M) # each: (r_i, d) - else: - if self.pooler is None: - raise RuntimeError( - "Pooled embedding output was requested without an initialized pooler." - ) - Y = self.pooler( # (b, n_poolers * d) - X, - M, - attentions=batch.attentions, - attention_backend=self.attention_backend, - ) - pool_slices = self.pooler.output_slices(X.shape[-1]) - values = list(Y.detach().cpu().unbind(0)) # each: (n_poolers * d,) - for position, record, value in zip(batch_positions, batch_records, values, strict=True): - window_results[position] = EmbeddingRecord(record.id, record.sequence, value) - - new_records = [ - window_results[position] - for position in range(window_start, window_start + len(window_records)) - ] - return new_records, pool_slices +"""Execute model-specific batches and return ordered residue-aware CPU tensors.""" + +from __future__ import annotations + +import torch + +from collections.abc import Callable, Iterator, Sequence +from contextlib import contextmanager +from dataclasses import dataclass, field +from typing import Any +from torch import Tensor + +from .identity import _model_device +from .inputs import _planned_batches +from .pooling import Pooler +from .types import EmbeddingBatch, EmbeddingInput, EmbeddingRecord + + +_MAX_PARTI_RESIDUES = 2_048 + + +def _validate_parti_length(M: Tensor) -> None: + """Reject an oversized attention graph before model inference.""" + + # M: (b, l) + n_residues = int(M.to(dtype=torch.int64).sum(dim=1).max().item()) + if n_residues > _MAX_PARTI_RESIDUES: + raise ValueError(f"parti supports at most {_MAX_PARTI_RESIDUES:,} biological residues.") + + +def select_hidden_state_embeddings( + last_hidden_state: Tensor, + hidden_states: tuple[Tensor, ...] | None, + *, + hidden_state_index: int = -1, + store_all_hidden_states: bool = False, +) -> Tensor: + """Select one hidden state or stack every state without changing values.""" + # last_hidden_state and each hidden_states entry: (b, l, d) + if store_all_hidden_states: + if not hidden_states: + raise ValueError("store_all_hidden_states requires model hidden states.") + # H has shape (b, n, l, d), where n follows the model's output order. + return torch.stack(hidden_states, dim=1) # (b, n, l, d) + if hidden_state_index == -1: + return last_hidden_state # (b, l, d) + if not hidden_states: + raise ValueError("hidden_state_index requires model hidden states.") + return hidden_states[hidden_state_index] # (b, l, d) + + +def _residue_embeddings(X: Tensor, M: Tensor) -> list[Tensor]: + """Copy every sample's biological residues to the host in one transfer. + + Boolean indexing packs the selected rows in batch order, so splitting the + packed rows by residue count gives the values that indexing each sample + would. Each returned tensor owns its storage, as a per-sample copy does. + """ + # X: (b, l, d); M: (b, l) + residue_counts = M.sum(dim=1).tolist() # b counts r_i + packed = X[M].detach().cpu() # (sum of r_i, d) + return [sample.clone() for sample in torch.split(packed, residue_counts)] # each: (r_i, d) + + +@contextmanager +def _temporary_eval(model: Any) -> Iterator[None]: + was_training = getattr(model, "training", None) + eval_method = getattr(model, "eval", None) + train_method = getattr(model, "train", None) + if ( + not isinstance(was_training, bool) + or not callable(eval_method) + or not callable(train_method) + ): + yield + return + eval_method() + try: + yield + finally: + train_method(was_training) + + +def _biological_residue_mask( + input_ids: Tensor, + attention_mask: Tensor, + tokenizer: Any, +) -> Tensor: + """Remove padding and tokenizer-declared special tokens from M.""" + + # input_ids, attention_mask: (b, l) + M = attention_mask.to(dtype=torch.bool) # (b, l) + special_ids = tuple(int(token_id) for token_id in getattr(tokenizer, "all_special_ids", ())) + if special_ids: + specials = torch.tensor( # (n_special,) + special_ids, + device=input_ids.device, + dtype=input_ids.dtype, + ) + M = M & ~torch.isin(input_ids, specials) # (b, l) + return M # (b, l) + + +def _generic_embedding_batch( + model: Any, + sequences: list[str], + *, + tokenizer: Any | None, + max_length: int | None, + truncate: bool, + need_attentions: bool, + model_kwargs: dict[str, Any], +) -> EmbeddingBatch: + config = getattr(model, "config", None) + model_type = str(getattr(config, "model_type", "")).lower() + if tokenizer is None: + tokenizer = getattr(model, "tokenizer", None) + + if tokenizer is None and model_type == "e1": + output = model._embed(sequences, return_attention_mask=True, **model_kwargs) + if not isinstance(output, tuple) or len(output) != 2: + raise TypeError("E1 _embed must return (X, residue_mask).") + X, M = output # (b, l, d), (b, l) + preparer = getattr(model, "prep_tokens", None) + if preparer is not None and hasattr(preparer, "get_batch_kwargs"): + prepared = preparer.get_batch_kwargs(sequences, device=X.device) + input_ids = prepared["input_ids"] # (b, l) + boundary_ids = preparer.boundary_token_ids.to( # (n_boundary,) + device=input_ids.device, dtype=input_ids.dtype + ) + # E1 wraps each raw sequence in BOS, context-label, terminal-label, + # and EOS tokens. Only amino-acid rows are biological residues. + M = M.to(dtype=torch.bool) & ~torch.isin(input_ids, boundary_ids) # (b, l) + if need_attentions: + raise ValueError("parti is not available for tokenizer-free E1 embedding.") + return EmbeddingBatch( # X: (b, l, d); residue_mask: (b, l) + X=X, + residue_mask=M.to(dtype=torch.bool), + ) + if tokenizer is None: + raise ValueError("A tokenizer is required for this model's embedding path.") + + tokenize_kwargs: dict[str, Any] = { + "return_tensors": "pt", + "padding": True, + "truncation": truncate, + } + if max_length is not None and truncate: + # ``max_length`` is a biological-residue limit. Tokenizer limits include + # boundary tokens, so reserve their declared width instead of dropping + # residues at the exact boundary. + special_token_count = 0 + num_special_tokens_to_add = getattr(tokenizer, "num_special_tokens_to_add", None) + if callable(num_special_tokens_to_add): + special_token_count = int(num_special_tokens_to_add(pair=False)) + tokenize_kwargs["max_length"] = max_length + special_token_count + sequence_tokenizer = getattr(model, "_tokenize_sequence_batch", None) + if callable(sequence_tokenizer): + encoded = sequence_tokenizer(sequences, tokenizer=tokenizer, **tokenize_kwargs) + else: + encoded = tokenizer(sequences, **tokenize_kwargs) + device = _model_device(model) + input_ids = encoded["input_ids"].to(device) # (b, l) + attention_mask = encoded.get( # (b, l) + "attention_mask", + input_ids.new_ones(input_ids.shape), + ).to(device) + M = _biological_residue_mask(input_ids, attention_mask, tokenizer) # (b, l) + if need_attentions: + # Validate l before either the backbone or its quadratic attention graph + # is materialized. M has shape (b, l). + _validate_parti_length(M) + X = model._embed(input_ids, attention_mask, **model_kwargs) # (b, l, d) + attentions = None + if need_attentions: + output = model( + input_ids=input_ids, + attention_mask=attention_mask, + output_attentions=True, + return_dict=True, + ) + attentions = getattr(output, "attentions", None) # each: (b, h, l, l) + if attentions is None: + raise ValueError("The model did not return attentions required by parti.") + return EmbeddingBatch( # X: (b, l, d); M: (b, l) + X=X, + residue_mask=M, + attentions=attentions, + ) + + +@dataclass(eq=False) +class BatchExecutor: + """Model and batch policy for one bounded embedding window at a time.""" + + model: Any + batch_size: int + max_tokens_per_batch: int | None + max_length: int | None + truncate: bool + model_kwargs: dict[str, Any] + hidden_state_source: str + normalized_decoder_inputs: tuple[str, ...] | None + decoder_input_ids: Tensor | None + decoder_attention_mask: Tensor | None + _embedding_batch_fn: Callable[..., EmbeddingBatch] | None + tokenizer: Any | None + store_all_hidden_states: bool + full_embeddings: bool + dtype: torch.dtype | None + pooler: Pooler | None + attention_backend: str | None + need_attentions: bool + model_type: str = field(init=False) + resolved_tokenizer: Any = field(init=False) + + def __post_init__(self) -> None: + config = getattr(self.model, "config", None) + self.model_type = str(getattr(config, "model_type", "")).lower() + self.resolved_tokenizer = ( + self.tokenizer if self.tokenizer is not None else getattr(self.model, "tokenizer", None) + ) + + def run_window( + self, + window_records: Sequence[EmbeddingInput], + *, + window_start: int, + ) -> tuple[list[EmbeddingRecord], dict[str, tuple[int, int]]]: + """Restore source order after length-bucketed inference and pooling.""" + + pool_slices: dict[str, tuple[int, int]] = {} + window_results: dict[int, EmbeddingRecord] = {} + for local_positions in _planned_batches( + window_records, + range(len(window_records)), + batch_size=self.batch_size, + max_tokens_per_batch=self.max_tokens_per_batch, + max_length=self.max_length, + truncate=self.truncate, + ): + batch_positions = [window_start + position for position in local_positions] + batch_records = [window_records[position] for position in local_positions] + sequences = [ + record.sequence[: self.max_length] + if self.truncate and self.max_length is not None + else record.sequence + for record in batch_records + ] + batch_model_kwargs = dict(self.model_kwargs) + if self.model_type == "fast_ankh" or self.hidden_state_source == "decoder": + batch_model_kwargs["hidden_state_source"] = self.hidden_state_source + if self.normalized_decoder_inputs is not None: + batch_model_kwargs["decoder_inputs"] = [ + self.normalized_decoder_inputs[position] for position in batch_positions + ] + if self.decoder_input_ids is not None: + # decoder_input_ids: (n_records, l_decoder) + indices = torch.tensor( # (b,) + batch_positions, + device=self.decoder_input_ids.device, + dtype=torch.long, + ) + batch_model_kwargs["decoder_input_ids"] = ( # (b, l_decoder) + self.decoder_input_ids.index_select(0, indices) + ) + if self.decoder_attention_mask is not None: + # decoder_attention_mask: (n_records, l_decoder) + indices = torch.tensor( # (b,) + batch_positions, + device=self.decoder_attention_mask.device, + dtype=torch.long, + ) + batch_model_kwargs["decoder_attention_mask"] = ( + self.decoder_attention_mask.index_select(0, indices) # (b, l_decoder) + ) + custom_batch = self._embedding_batch_fn or getattr(self.model, "_embedding_batch", None) + if custom_batch is not None: + if self.model_type == "fast_ankh": + batch = custom_batch( + sequences, + tokenizer=self.resolved_tokenizer, + max_length=self.max_length, + truncate=self.truncate, + need_attentions=self.need_attentions, + **batch_model_kwargs, + ) + else: + batch = custom_batch(sequences, **batch_model_kwargs) + if not isinstance(batch, EmbeddingBatch): + raise TypeError("_embedding_batch must return EmbeddingBatch.") + else: + batch = _generic_embedding_batch( + self.model, + sequences, + tokenizer=self.tokenizer, + max_length=self.max_length, + truncate=self.truncate, + need_attentions=self.need_attentions, + model_kwargs=batch_model_kwargs, + ) + X = batch.X # (b, l, d) or (b, n_states, l, d) + raw_mask = batch.residue_mask # (b, l) + if not isinstance(X, Tensor) or not isinstance(raw_mask, Tensor): + raise TypeError("Embedding batches must provide Tensor X and residue_mask.") + if X.is_meta or raw_mask.is_meta: + raise ValueError("Embedding batches cannot contain meta tensors.") + if not X.is_floating_point(): + raise TypeError("Embedding batches must use a floating-point X dtype.") + if raw_mask.is_complex() or not bool(torch.isfinite(raw_mask).all()): + raise ValueError("Embedding residue_mask must contain finite binary values.") + if not bool(((raw_mask == 0) | (raw_mask == 1)).all()): + raise ValueError("Embedding residue_mask must contain finite binary values.") + M = raw_mask.to(device=X.device, dtype=torch.bool) # (b, l) + valid_X_shape = ( + X.ndim == 3 + and X.shape[0] == len(batch_records) + and X.shape[-1] > 0 + and M.shape == X.shape[:2] + ) + valid_all_states_shape = ( + X.ndim == 4 + and self.store_all_hidden_states + and self.full_embeddings + and X.shape[0] == len(batch_records) + and X.shape[1] > 0 + and X.shape[-1] > 0 + and M.shape == (X.shape[0], X.shape[2]) + ) + if not (valid_X_shape or valid_all_states_shape): + raise ValueError( + "Embedding batches must provide X with shape (b, l, d), or " + "(b, states, l, d) when storing all hidden states, and " + "residue_mask with shape (b, l)." + ) + if not bool(M.any(dim=1).all()): + raise ValueError("Every embedding sample must contain a biological residue.") + finite_selected = ( # X.shape + torch.isfinite(X) | ~M.unsqueeze(-1) + if X.ndim == 3 + else torch.isfinite(X) | ~M[:, None, :, None] + ) + if not bool(finite_selected.all()): + raise ValueError("Biological residue embeddings produced non-finite output.") + if self.need_attentions: + # Validate the biological graph only after mask integrity is established. + _validate_parti_length(M) + if self.dtype is not None: + X = X.to(dtype=self.dtype) # unchanged shape + + if self.full_embeddings: + if X.ndim == 4: + values = [ + X_i[:, M_i, :].detach().cpu() # (n_states, r_i, d) + for X_i, M_i in zip(X, M, strict=True) + ] + else: + values = _residue_embeddings(X, M) # each: (r_i, d) + else: + if self.pooler is None: + raise RuntimeError( + "Pooled embedding output was requested without an initialized pooler." + ) + Y = self.pooler( # (b, n_poolers * d) + X, + M, + attentions=batch.attentions, + attention_backend=self.attention_backend, + ) + pool_slices = self.pooler.output_slices(X.shape[-1]) + values = list(Y.detach().cpu().unbind(0)) # each: (n_poolers * d,) + for position, record, value in zip(batch_positions, batch_records, values, strict=True): + window_results[position] = EmbeddingRecord(record.id, record.sequence, value) + + new_records = [ + window_results[position] + for position in range(window_start, window_start + len(window_records)) + ] + return new_records, pool_slices diff --git a/fastplms/embeddings/identity.py b/fastplms/embeddings/identity.py index 2292e8eb11413360b279c744683c06f516a58eb1..6816f39ad9cb244e1a012b8f757b326791a2deae 100644 --- a/fastplms/embeddings/identity.py +++ b/fastplms/embeddings/identity.py @@ -1,510 +1,511 @@ -"""Deterministic identity for embedding inputs, models, tokenizers, and execution.""" - -from __future__ import annotations - -import hashlib -import json -import platform -import torch -from collections.abc import Iterable, Mapping, Sequence -from pathlib import Path -from typing import Any -from torch import Tensor - +"""Deterministic identity for embedding inputs, models, tokenizers, and execution.""" + +from __future__ import annotations + +import hashlib +import json +import platform +import torch + +from collections.abc import Iterable, Mapping, Sequence +from pathlib import Path +from typing import Any +from torch import Tensor + from .inputs import _InputSpool from .storage import tensor_sha256 from .types import EmbeddingInput - - -_RUN_FINGERPRINT_SCHEMA_VERSION = 3 -_MODEL_STATE_HASH_CHUNK_BYTES = 16 * 1024**2 - - -def _model_device(model: Any) -> torch.device: - try: - return torch.device(next(model.parameters()).device) - except (AttributeError, StopIteration): - return torch.device("cpu") - - -def _attention_backend(model: Any) -> str | None: - config = getattr(model, "config", None) - for name in ("_attn_implementation", "attn_implementation", "attn_backend"): - value = getattr(config, name, None) - if value: - return str(value) - return None - - -def _attention_kernel_metadata(backend: str | None) -> dict[str, Any] | None: - if backend not in {"flash_attention_2", "flash_attention_3"}: - return None - from fastplms.registry import get_model_registry - - spec = get_model_registry().attention_kernels[backend] - return { - "repository": spec.repository, - "revision": spec.revision, - "version": spec.version, - "expected_variant": spec.expected_variant, - "dtypes": list(spec.dtypes), - } - - -def _fingerprint_jsonable(value: Any) -> Any: - if isinstance(value, Mapping): - return {str(key): _fingerprint_jsonable(item) for key, item in value.items()} - if isinstance(value, (list, tuple)): - return [_fingerprint_jsonable(item) for item in value] - if isinstance(value, (set, frozenset)): - return sorted((_fingerprint_jsonable(item) for item in value), key=repr) - if isinstance(value, Path): - return str(value) - if isinstance(value, Tensor): - return { - "dtype": str(value.dtype).removeprefix("torch."), - "shape": list(value.shape), - "sha256": tensor_sha256(value), - } - if isinstance(value, torch.dtype): - return str(value).removeprefix("torch.") - if isinstance(value, torch.device): - return str(value) - if value is None or isinstance(value, (str, int, float, bool)): - return value - return { - "class": f"{value.__class__.__module__}.{value.__class__.__qualname__}", - "value": str(value), - } - - -def _tokenizer_content_sha256(tokenizer: Any) -> str: - content: dict[str, Any] = { - "init_kwargs": getattr(tokenizer, "init_kwargs", None), - "special_tokens_map": getattr(tokenizer, "special_tokens_map", None), - "model_max_length": getattr(tokenizer, "model_max_length", None), - "padding_side": getattr(tokenizer, "padding_side", None), - "truncation_side": getattr(tokenizer, "truncation_side", None), - } - get_vocab = getattr(tokenizer, "get_vocab", None) - if callable(get_vocab): - content["vocabulary"] = get_vocab() - get_added_vocab = getattr(tokenizer, "get_added_vocab", None) - if callable(get_added_vocab): - content["added_vocabulary"] = get_added_vocab() - backend = getattr(tokenizer, "backend_tokenizer", None) - backend_to_str = getattr(backend, "to_str", None) - if callable(backend_to_str): - content["backend"] = backend_to_str() - serialized = json.dumps( - _fingerprint_jsonable(content), - sort_keys=True, - separators=(",", ":"), - ensure_ascii=False, - ).encode() - return hashlib.sha256(serialized).hexdigest() - - -def _tokenizer_metadata(model: Any, tokenizer: Any | None) -> dict[str, Any]: - resolved = tokenizer if tokenizer is not None else getattr(model, "tokenizer", None) - if resolved is None: - # Raw-sequence families such as E1 retain their loader context on the - # model/encoder rather than exposing a Transformers tokenizer. Bind the - # non-secret source policy to resume identity without serializing a Hub - # token or forcing lazy tokenizer initialization. - for candidate in (model, getattr(model, "model", None)): - settings = getattr(candidate, "__dict__", {}).get("_fastplms_tokenizer_kwargs") - if isinstance(settings, Mapping): - token_value = settings.get("token") - return { - "mode": "native-sequence", - "source": ( - str(settings.get("tokenizer_source")) - if settings.get("tokenizer_source") is not None - else None - ), - "revision": settings.get("revision"), - "cache_dir": ( - str(settings.get("cache_dir")) - if settings.get("cache_dir") is not None - else None - ), - "local_files_only": bool(settings.get("local_files_only", False)), - "token_policy": ( - "disabled" - if token_value is False - else "provided" - if token_value is not None - else "default" - ), - } - return {"mode": "native-sequence"} - return { - "mode": "tokenizer", - "class": f"{resolved.__class__.__module__}.{resolved.__class__.__qualname__}", - "name_or_path": getattr(resolved, "name_or_path", None), - "vocab_size": getattr(resolved, "vocab_size", None), - "special_token_ids": list(getattr(resolved, "all_special_ids", ())), - "content_sha256": _tokenizer_content_sha256(resolved), - } - - -def _software_versions() -> dict[str, str | None]: - try: - import fastplms - - fastplms_version = fastplms.__version__ - except (AttributeError, ImportError): - fastplms_version = None - try: - import safetensors - - safetensors_version = safetensors.__version__ - except ImportError: - safetensors_version = None - try: - import transformers - - transformers_version = transformers.__version__ - except ImportError: - transformers_version = None - return { - "fastplms": fastplms_version, - "python": platform.python_version(), - "safetensors": safetensors_version, - "torch": torch.__version__, - "torch_cuda": torch.version.cuda, - "transformers": transformers_version, - } - - -def _adapter_identity_metadata(model: Any) -> dict[str, Any] | None: - """Return deterministic PEFT/adapter identity without tensor payloads.""" - - peft_config = getattr(model, "peft_config", None) - if not isinstance(peft_config, Mapping) or not peft_config: - return None - configurations: dict[str, Any] = {} - for name, config in sorted(peft_config.items(), key=lambda item: str(item[0])): - to_dict = getattr(config, "to_dict", None) - if callable(to_dict): - value = to_dict() - else: - try: - value = vars(config) - except TypeError: - value = config - configurations[str(name)] = _fingerprint_jsonable(value) - active_adapters = getattr(model, "active_adapters", None) - if callable(active_adapters): - active_adapters = active_adapters() - return { - "active": _fingerprint_jsonable(active_adapters), - "configurations": configurations, - } - - -def _execution_identity_metadata(model: Any) -> dict[str, Any]: - """Capture runtime policy that can change persisted numerical results.""" - - parameter_dtypes = sorted( - { - str(parameter.dtype).removeprefix("torch.") - for parameter in getattr(model, "parameters", lambda: ())() - } - ) - return { - "device": _model_device(model).type, - "hf_device_map": _fingerprint_jsonable(getattr(model, "hf_device_map", None)), - "parameter_dtypes": parameter_dtypes, - "software": _software_versions(), - } - - -def _first_metadata_value(*values: Any) -> Any: - for value in values: - if isinstance(value, str): - if value.strip(): - return value - elif value is not None: - return value - return None - - -def _model_identity_metadata(model: Any) -> dict[str, Any]: - """Resolve model and checkpoint identity, including local artifact fallbacks.""" - - config = getattr(model, "config", None) - checkpoint_revision = _first_metadata_value( - getattr(config, "fastplms_checkpoint_revision", None), - getattr(config, "_commit_hash", None), - ) - return { - "model_id": _first_metadata_value( - getattr(config, "fastplms_model_id", None), - getattr(config, "_name_or_path", None), - ), - "model_revision": _first_metadata_value( - getattr(config, "_commit_hash", None), - checkpoint_revision, - ), - "checkpoint_repo_id": getattr(config, "fastplms_checkpoint_repo_id", None), - "checkpoint_revision": checkpoint_revision, - "checkpoint_hash": _first_metadata_value( - getattr(model, "checkpoint_hash", None), - getattr(config, "checkpoint_hash", None), - getattr(config, "fastplms_checkpoint_hash", None), - ), - "weights_revision": getattr(config, "fastplms_weights_revision", None), - "runtime_revision": getattr(config, "fastplms_runtime_revision", None), - "source_tree_sha256": getattr(config, "fastplms_source_tree_sha256", None), - "runtime_bundle_sha256": getattr(config, "fastplms_runtime_bundle_sha256", None), - } - - -def _bounded_tensor_chunks(X: Tensor, max_elements: int) -> Iterable[Tensor]: - """Yield X in logical row-major order without materializing a full copy.""" - - # X: (...) - if X.numel() == 0: - return - if X.ndim == 0: - yield X - return - trailing_elements = 1 - for size in X.shape[1:]: - trailing_elements *= int(size) - if trailing_elements <= max_elements: - rows_per_chunk = max(1, max_elements // trailing_elements) - for start in range(0, X.shape[0], rows_per_chunk): - yield X[start : start + rows_per_chunk] # (chunk_rows, ...) - return - for row in X: - yield from _bounded_tensor_chunks(row, max_elements) - - -def _model_state_sha256(model: Any) -> str: - """Hash named parameters and persistent buffers using bounded CPU copies.""" - - # Never cache this digest from tensor identity or ``Tensor._version``. - # ``Parameter.data`` and independent tensor aliases can mutate shared storage - # without changing either signal, while persisted resume identity must bind - # the authoritative bytes visible at the start of this run. - state = model.state_dict(keep_vars=True) - digest = hashlib.sha256() - for name, value in sorted(state.items()): - if not isinstance(value, Tensor): - raise TypeError(f"Model state entry {name!r} is not a tensor.") - if value.is_meta: - raise ValueError( - f"Cannot fingerprint meta-device model state entry {name!r}; pass " - "model_state_fingerprint with a caller-owned state identity." - ) - header = json.dumps( - { - "name": name, - "dtype": str(value.dtype).removeprefix("torch."), - "shape": list(value.shape), - }, - sort_keys=True, - separators=(",", ":"), - ).encode() - digest.update(len(header).to_bytes(8, "big")) - digest.update(header) - max_elements = max(1, _MODEL_STATE_HASH_CHUNK_BYTES // value.element_size()) - for chunk in _bounded_tensor_chunks(value.detach(), max_elements): - cpu_chunk = chunk.to(device="cpu").contiguous() # chunk.shape - digest.update(cpu_chunk.reshape(-1).view(torch.uint8).numpy().tobytes()) - return digest.hexdigest() - - -def _input_sha256(records: Iterable[EmbeddingInput]) -> str: - """Hash an ordered input stream without constructing a duplicate JSON payload.""" - - precomputed = getattr(records, "input_fingerprint", None) - if isinstance(precomputed, str): - return precomputed - digest = hashlib.sha256() - count = 0 - for record in records: - count += 1 - for value in (record.id, record.sequence): - encoded = value.encode("utf-8") - digest.update(len(encoded).to_bytes(8, "big")) - digest.update(encoded) - digest.update(count.to_bytes(8, "big")) - return digest.hexdigest() - - -def _run_fingerprint( - model: Any, - records: Sequence[EmbeddingInput], - *, - pooling: Sequence[str], - full_embeddings: bool, - max_length: int | None, - truncate: bool, - dtype: torch.dtype | None, - model_kwargs: dict[str, Any], - tokenizer_metadata: dict[str, Any], - model_state_fingerprint: str | None, - persist_output: bool, - embedding_context: Mapping[str, Any], - batch_size: int, - batch_window_size: int, - max_tokens_per_batch: int | None, -) -> tuple[str, str, str | None, str]: - input_fingerprint = _input_sha256(records) - attention_backend = _attention_backend(model) - model_identity = _model_identity_metadata(model) - if model_state_fingerprint is None and persist_output: - resolved_model_state_fingerprint = _model_state_sha256(model) - model_state_fingerprint_source = "computed" - elif model_state_fingerprint is not None: - resolved_model_state_fingerprint = model_state_fingerprint.strip() - if not resolved_model_state_fingerprint: - raise ValueError("model_state_fingerprint must not be empty.") - model_state_fingerprint_source = "caller" - else: - resolved_model_state_fingerprint = None - model_state_fingerprint_source = "not-computed" - payload = { - "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, - "input_fingerprint": input_fingerprint, - "model_state_fingerprint": resolved_model_state_fingerprint, - "model_state_fingerprint_source": model_state_fingerprint_source, - "model_class": f"{model.__class__.__module__}.{model.__class__.__qualname__}", - **model_identity, - "attention_backend": attention_backend, - "attention_kernel": _attention_kernel_metadata(attention_backend), - "layer": repr( - getattr(model, "embedding_layer", model_kwargs.get("hidden_state_index", -1)) - ), - "projection": getattr(model, "embedding_projection", None), - "esmc_source": getattr(model, "_esmc_source", None), - "esmc_revision": getattr(model, "_esmc_source_revision", None), - "esmc_files": getattr(model, "_esmc_source_files", None), - "token_policy": getattr(model, "embedding_token_policy", None), - "tokenizer": tokenizer_metadata, - "adapter": _adapter_identity_metadata(model), - "execution": _execution_identity_metadata(model), - "embedding_context": _fingerprint_jsonable(embedding_context), - "pooling": list(pooling), - "full_embeddings": full_embeddings, - "max_length": max_length, - "truncate": truncate, - "dtype": str(dtype) if dtype is not None else None, - "batching": { - "batch_size": batch_size, - "batch_window_size": batch_window_size, - "max_tokens_per_batch": max_tokens_per_batch, - "input_storage": ("disk-spool" if isinstance(records, _InputSpool) else "memory"), - }, - "model_kwargs": { - key: _fingerprint_jsonable(value) for key, value in sorted(model_kwargs.items()) - }, - "residue_mask_policy": "attention-mask-minus-special-tokens", - } - run_fingerprint = hashlib.sha256( - json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() - ).hexdigest() - return ( - input_fingerprint, - run_fingerprint, - resolved_model_state_fingerprint, - model_state_fingerprint_source, - ) - - -def _ordered_string_sha256(values: Sequence[str]) -> str: - digest = hashlib.sha256() - for value in values: - encoded = value.encode("utf-8") - digest.update(len(encoded).to_bytes(8, "big")) - digest.update(encoded) - digest.update(len(values).to_bytes(8, "big")) - return digest.hexdigest() - - -def _embedding_context( - model: Any, - records: Sequence[EmbeddingInput], - *, - hidden_state_source: str, - decoder_inputs: Sequence[str] | None, - decoder_input_ids: Tensor | None, - decoder_attention_mask: Tensor | None, - model_kwargs: Mapping[str, Any], -) -> tuple[dict[str, Any], tuple[str, ...] | None]: - if hidden_state_source not in {"encoder", "decoder"}: - raise ValueError("hidden_state_source must be 'encoder' or 'decoder'.") - hidden_state_index = model_kwargs.get("hidden_state_index", -1) - if not isinstance(hidden_state_index, int) or isinstance(hidden_state_index, bool): - raise TypeError("hidden_state_index must be an integer.") - store_all_hidden_states = model_kwargs.get("store_all_hidden_states", False) - if not isinstance(store_all_hidden_states, bool): - raise TypeError("store_all_hidden_states must be a boolean.") - normalized_decoder_inputs: tuple[str, ...] | None = None - has_decoder_inputs = decoder_inputs is not None - has_decoder_ids = decoder_input_ids is not None - if hidden_state_source == "encoder": - if has_decoder_inputs or has_decoder_ids or decoder_attention_mask is not None: - raise ValueError("Decoder inputs are only valid when hidden_state_source='decoder'.") - else: - if has_decoder_inputs == has_decoder_ids: - raise ValueError( - "Decoder embedding requires exactly one of decoder_inputs or decoder_input_ids." - ) - decoder_input_fingerprint: str | None = None - if decoder_inputs is not None: - if isinstance(decoder_inputs, (str, bytes)) or not isinstance(decoder_inputs, Sequence): - raise TypeError("decoder_inputs must be an aligned sequence of strings.") - normalized_decoder_inputs = tuple(decoder_inputs) - if not all(isinstance(value, str) and value for value in normalized_decoder_inputs): - raise ValueError("decoder_inputs must contain non-empty strings.") - if len(normalized_decoder_inputs) != len(records): - raise ValueError("decoder_inputs must align one-to-one with embedding inputs.") - decoder_input_fingerprint = _ordered_string_sha256(normalized_decoder_inputs) - if decoder_attention_mask is not None: - raise ValueError("decoder_attention_mask requires decoder_input_ids.") - if decoder_input_ids is not None: - if not isinstance(decoder_input_ids, Tensor) or decoder_input_ids.ndim != 2: - raise ValueError("decoder_input_ids must have shape (batch, sequence).") - if decoder_input_ids.shape[0] != len(records): - raise ValueError("decoder_input_ids must align one-to-one with embedding inputs.") - if decoder_input_ids.dtype == torch.bool or decoder_input_ids.is_floating_point(): - raise TypeError("decoder_input_ids must use an integer token dtype.") - decoder_input_fingerprint = tensor_sha256(decoder_input_ids) - decoder_mask_fingerprint: str | None = None - if decoder_attention_mask is not None: - if not isinstance(decoder_attention_mask, Tensor): - raise TypeError("decoder_attention_mask must be a tensor.") - if decoder_input_ids is None or decoder_attention_mask.shape != decoder_input_ids.shape: - raise ValueError("decoder_attention_mask must match decoder_input_ids shape.") - decoder_mask_fingerprint = tensor_sha256(decoder_attention_mask) - - context: dict[str, Any] = { - "hidden_state_source": hidden_state_source, - "hidden_state_index": hidden_state_index, - "store_all_hidden_states": store_all_hidden_states, - "decoder_input_fingerprint": decoder_input_fingerprint, - "decoder_attention_mask_fingerprint": decoder_mask_fingerprint, - "decoder_alignment": "input-position" if hidden_state_source == "decoder" else None, - } - metadata_hook = getattr(model, "_embedding_metadata", None) - model_metadata: Mapping[str, Any] | None = None - if callable(metadata_hook): - model_metadata = metadata_hook(**context) - if not isinstance(model_metadata, Mapping): - raise TypeError("_embedding_metadata must return a mapping.") - context["model_embedding"] = _fingerprint_jsonable(model_metadata) - if hidden_state_source == "decoder": - has_decoder_batch = callable(getattr(model, "_embedding_batch", None)) - declares_decoder_stack = ( - model_metadata is not None and model_metadata.get("hidden_state_stack") == "decoder" - ) - if not has_decoder_batch or not declares_decoder_stack: - raise ValueError( - f"{model.__class__.__name__} does not declare decoder embedding support." - ) - return context, normalized_decoder_inputs + + +_RUN_FINGERPRINT_SCHEMA_VERSION = 3 +_MODEL_STATE_HASH_CHUNK_BYTES = 16 * 1024**2 + + +def _model_device(model: Any) -> torch.device: + try: + return torch.device(next(model.parameters()).device) + except (AttributeError, StopIteration): + return torch.device("cpu") + + +def _attention_backend(model: Any) -> str | None: + config = getattr(model, "config", None) + for name in ("_attn_implementation", "attn_implementation", "attn_backend"): + value = getattr(config, name, None) + if value: + return str(value) + return None + + +def _attention_kernel_metadata(backend: str | None) -> dict[str, Any] | None: + if backend not in {"flash_attention_2", "flash_attention_3"}: + return None + from fastplms.registry import get_model_registry + + spec = get_model_registry().attention_kernels[backend] + return { + "repository": spec.repository, + "revision": spec.revision, + "version": spec.version, + "expected_variant": spec.expected_variant, + "dtypes": list(spec.dtypes), + } + + +def _fingerprint_jsonable(value: Any) -> Any: + if isinstance(value, Mapping): + return {str(key): _fingerprint_jsonable(item) for key, item in value.items()} + if isinstance(value, (list, tuple)): + return [_fingerprint_jsonable(item) for item in value] + if isinstance(value, (set, frozenset)): + return sorted((_fingerprint_jsonable(item) for item in value), key=repr) + if isinstance(value, Path): + return str(value) + if isinstance(value, Tensor): + return { + "dtype": str(value.dtype).removeprefix("torch."), + "shape": list(value.shape), + "sha256": tensor_sha256(value), + } + if isinstance(value, torch.dtype): + return str(value).removeprefix("torch.") + if isinstance(value, torch.device): + return str(value) + if value is None or isinstance(value, (str, int, float, bool)): + return value + return { + "class": f"{value.__class__.__module__}.{value.__class__.__qualname__}", + "value": str(value), + } + + +def _tokenizer_content_sha256(tokenizer: Any) -> str: + content: dict[str, Any] = { + "init_kwargs": getattr(tokenizer, "init_kwargs", None), + "special_tokens_map": getattr(tokenizer, "special_tokens_map", None), + "model_max_length": getattr(tokenizer, "model_max_length", None), + "padding_side": getattr(tokenizer, "padding_side", None), + "truncation_side": getattr(tokenizer, "truncation_side", None), + } + get_vocab = getattr(tokenizer, "get_vocab", None) + if callable(get_vocab): + content["vocabulary"] = get_vocab() + get_added_vocab = getattr(tokenizer, "get_added_vocab", None) + if callable(get_added_vocab): + content["added_vocabulary"] = get_added_vocab() + backend = getattr(tokenizer, "backend_tokenizer", None) + backend_to_str = getattr(backend, "to_str", None) + if callable(backend_to_str): + content["backend"] = backend_to_str() + serialized = json.dumps( + _fingerprint_jsonable(content), + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ).encode() + return hashlib.sha256(serialized).hexdigest() + + +def _tokenizer_metadata(model: Any, tokenizer: Any | None) -> dict[str, Any]: + resolved = tokenizer if tokenizer is not None else getattr(model, "tokenizer", None) + if resolved is None: + # Raw-sequence families such as E1 retain their loader context on the + # model/encoder rather than exposing a Transformers tokenizer. Bind the + # non-secret source policy to resume identity without serializing a Hub + # token or forcing lazy tokenizer initialization. + for candidate in (model, getattr(model, "model", None)): + settings = getattr(candidate, "__dict__", {}).get("_fastplms_tokenizer_kwargs") + if isinstance(settings, Mapping): + token_value = settings.get("token") + return { + "mode": "native-sequence", + "source": ( + str(settings.get("tokenizer_source")) + if settings.get("tokenizer_source") is not None + else None + ), + "revision": settings.get("revision"), + "cache_dir": ( + str(settings.get("cache_dir")) + if settings.get("cache_dir") is not None + else None + ), + "local_files_only": bool(settings.get("local_files_only", False)), + "token_policy": ( + "disabled" + if token_value is False + else "provided" + if token_value is not None + else "default" + ), + } + return {"mode": "native-sequence"} + return { + "mode": "tokenizer", + "class": f"{resolved.__class__.__module__}.{resolved.__class__.__qualname__}", + "name_or_path": getattr(resolved, "name_or_path", None), + "vocab_size": getattr(resolved, "vocab_size", None), + "special_token_ids": list(getattr(resolved, "all_special_ids", ())), + "content_sha256": _tokenizer_content_sha256(resolved), + } + + +def _software_versions() -> dict[str, str | None]: + try: + import fastplms + + fastplms_version = fastplms.__version__ + except (AttributeError, ImportError): + fastplms_version = None + try: + import safetensors + + safetensors_version = safetensors.__version__ + except ImportError: + safetensors_version = None + try: + import transformers + + transformers_version = transformers.__version__ + except ImportError: + transformers_version = None + return { + "fastplms": fastplms_version, + "python": platform.python_version(), + "safetensors": safetensors_version, + "torch": torch.__version__, + "torch_cuda": torch.version.cuda, + "transformers": transformers_version, + } + + +def _adapter_identity_metadata(model: Any) -> dict[str, Any] | None: + """Return deterministic PEFT/adapter identity without tensor payloads.""" + + peft_config = getattr(model, "peft_config", None) + if not isinstance(peft_config, Mapping) or not peft_config: + return None + configurations: dict[str, Any] = {} + for name, config in sorted(peft_config.items(), key=lambda item: str(item[0])): + to_dict = getattr(config, "to_dict", None) + if callable(to_dict): + value = to_dict() + else: + try: + value = vars(config) + except TypeError: + value = config + configurations[str(name)] = _fingerprint_jsonable(value) + active_adapters = getattr(model, "active_adapters", None) + if callable(active_adapters): + active_adapters = active_adapters() + return { + "active": _fingerprint_jsonable(active_adapters), + "configurations": configurations, + } + + +def _execution_identity_metadata(model: Any) -> dict[str, Any]: + """Capture runtime policy that can change persisted numerical results.""" + + parameter_dtypes = sorted( + { + str(parameter.dtype).removeprefix("torch.") + for parameter in getattr(model, "parameters", lambda: ())() + } + ) + return { + "device": _model_device(model).type, + "hf_device_map": _fingerprint_jsonable(getattr(model, "hf_device_map", None)), + "parameter_dtypes": parameter_dtypes, + "software": _software_versions(), + } + + +def _first_metadata_value(*values: Any) -> Any: + for value in values: + if isinstance(value, str): + if value.strip(): + return value + elif value is not None: + return value + return None + + +def _model_identity_metadata(model: Any) -> dict[str, Any]: + """Resolve model and checkpoint identity, including local artifact fallbacks.""" + + config = getattr(model, "config", None) + checkpoint_revision = _first_metadata_value( + getattr(config, "fastplms_checkpoint_revision", None), + getattr(config, "_commit_hash", None), + ) + return { + "model_id": _first_metadata_value( + getattr(config, "fastplms_model_id", None), + getattr(config, "_name_or_path", None), + ), + "model_revision": _first_metadata_value( + getattr(config, "_commit_hash", None), + checkpoint_revision, + ), + "checkpoint_repo_id": getattr(config, "fastplms_checkpoint_repo_id", None), + "checkpoint_revision": checkpoint_revision, + "checkpoint_hash": _first_metadata_value( + getattr(model, "checkpoint_hash", None), + getattr(config, "checkpoint_hash", None), + getattr(config, "fastplms_checkpoint_hash", None), + ), + "weights_revision": getattr(config, "fastplms_weights_revision", None), + "runtime_revision": getattr(config, "fastplms_runtime_revision", None), + "source_tree_sha256": getattr(config, "fastplms_source_tree_sha256", None), + "runtime_bundle_sha256": getattr(config, "fastplms_runtime_bundle_sha256", None), + } + + +def _bounded_tensor_chunks(X: Tensor, max_elements: int) -> Iterable[Tensor]: + """Yield X in logical row-major order without materializing a full copy.""" + + # X: (...) + if X.numel() == 0: + return + if X.ndim == 0: + yield X + return + trailing_elements = 1 + for size in X.shape[1:]: + trailing_elements *= int(size) + if trailing_elements <= max_elements: + rows_per_chunk = max(1, max_elements // trailing_elements) + for start in range(0, X.shape[0], rows_per_chunk): + yield X[start : start + rows_per_chunk] # (chunk_rows, ...) + return + for row in X: + yield from _bounded_tensor_chunks(row, max_elements) + + +def _model_state_sha256(model: Any) -> str: + """Hash named parameters and persistent buffers using bounded CPU copies.""" + + # Never cache this digest from tensor identity or ``Tensor._version``. + # ``Parameter.data`` and independent tensor aliases can mutate shared storage + # without changing either signal, while persisted resume identity must bind + # the authoritative bytes visible at the start of this run. + state = model.state_dict(keep_vars=True) + digest = hashlib.sha256() + for name, value in sorted(state.items()): + if not isinstance(value, Tensor): + raise TypeError(f"Model state entry {name!r} is not a tensor.") + if value.is_meta: + raise ValueError( + f"Cannot fingerprint meta-device model state entry {name!r}; pass " + "model_state_fingerprint with a caller-owned state identity." + ) + header = json.dumps( + { + "name": name, + "dtype": str(value.dtype).removeprefix("torch."), + "shape": list(value.shape), + }, + sort_keys=True, + separators=(",", ":"), + ).encode() + digest.update(len(header).to_bytes(8, "big")) + digest.update(header) + max_elements = max(1, _MODEL_STATE_HASH_CHUNK_BYTES // value.element_size()) + for chunk in _bounded_tensor_chunks(value.detach(), max_elements): + cpu_chunk = chunk.to(device="cpu").contiguous() # chunk.shape + digest.update(cpu_chunk.reshape(-1).view(torch.uint8).numpy().tobytes()) + return digest.hexdigest() + + +def _input_sha256(records: Iterable[EmbeddingInput]) -> str: + """Hash an ordered input stream without constructing a duplicate JSON payload.""" + + precomputed = getattr(records, "input_fingerprint", None) + if isinstance(precomputed, str): + return precomputed + digest = hashlib.sha256() + count = 0 + for record in records: + count += 1 + for value in (record.id, record.sequence): + encoded = value.encode("utf-8") + digest.update(len(encoded).to_bytes(8, "big")) + digest.update(encoded) + digest.update(count.to_bytes(8, "big")) + return digest.hexdigest() + + +def _run_fingerprint( + model: Any, + records: Sequence[EmbeddingInput], + *, + pooling: Sequence[str], + full_embeddings: bool, + max_length: int | None, + truncate: bool, + dtype: torch.dtype | None, + model_kwargs: dict[str, Any], + tokenizer_metadata: dict[str, Any], + model_state_fingerprint: str | None, + persist_output: bool, + embedding_context: Mapping[str, Any], + batch_size: int, + batch_window_size: int, + max_tokens_per_batch: int | None, +) -> tuple[str, str, str | None, str]: + input_fingerprint = _input_sha256(records) + attention_backend = _attention_backend(model) + model_identity = _model_identity_metadata(model) + if model_state_fingerprint is None and persist_output: + resolved_model_state_fingerprint = _model_state_sha256(model) + model_state_fingerprint_source = "computed" + elif model_state_fingerprint is not None: + resolved_model_state_fingerprint = model_state_fingerprint.strip() + if not resolved_model_state_fingerprint: + raise ValueError("model_state_fingerprint must not be empty.") + model_state_fingerprint_source = "caller" + else: + resolved_model_state_fingerprint = None + model_state_fingerprint_source = "not-computed" + payload = { + "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, + "input_fingerprint": input_fingerprint, + "model_state_fingerprint": resolved_model_state_fingerprint, + "model_state_fingerprint_source": model_state_fingerprint_source, + "model_class": f"{model.__class__.__module__}.{model.__class__.__qualname__}", + **model_identity, + "attention_backend": attention_backend, + "attention_kernel": _attention_kernel_metadata(attention_backend), + "layer": repr( + getattr(model, "embedding_layer", model_kwargs.get("hidden_state_index", -1)) + ), + "projection": getattr(model, "embedding_projection", None), + "esmc_source": getattr(model, "_esmc_source", None), + "esmc_revision": getattr(model, "_esmc_source_revision", None), + "esmc_files": getattr(model, "_esmc_source_files", None), + "token_policy": getattr(model, "embedding_token_policy", None), + "tokenizer": tokenizer_metadata, + "adapter": _adapter_identity_metadata(model), + "execution": _execution_identity_metadata(model), + "embedding_context": _fingerprint_jsonable(embedding_context), + "pooling": list(pooling), + "full_embeddings": full_embeddings, + "max_length": max_length, + "truncate": truncate, + "dtype": str(dtype) if dtype is not None else None, + "batching": { + "batch_size": batch_size, + "batch_window_size": batch_window_size, + "max_tokens_per_batch": max_tokens_per_batch, + "input_storage": ("disk-spool" if isinstance(records, _InputSpool) else "memory"), + }, + "model_kwargs": { + key: _fingerprint_jsonable(value) for key, value in sorted(model_kwargs.items()) + }, + "residue_mask_policy": "attention-mask-minus-special-tokens", + } + run_fingerprint = hashlib.sha256( + json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() + ).hexdigest() + return ( + input_fingerprint, + run_fingerprint, + resolved_model_state_fingerprint, + model_state_fingerprint_source, + ) + + +def _ordered_string_sha256(values: Sequence[str]) -> str: + digest = hashlib.sha256() + for value in values: + encoded = value.encode("utf-8") + digest.update(len(encoded).to_bytes(8, "big")) + digest.update(encoded) + digest.update(len(values).to_bytes(8, "big")) + return digest.hexdigest() + + +def _embedding_context( + model: Any, + records: Sequence[EmbeddingInput], + *, + hidden_state_source: str, + decoder_inputs: Sequence[str] | None, + decoder_input_ids: Tensor | None, + decoder_attention_mask: Tensor | None, + model_kwargs: Mapping[str, Any], +) -> tuple[dict[str, Any], tuple[str, ...] | None]: + if hidden_state_source not in {"encoder", "decoder"}: + raise ValueError("hidden_state_source must be 'encoder' or 'decoder'.") + hidden_state_index = model_kwargs.get("hidden_state_index", -1) + if not isinstance(hidden_state_index, int) or isinstance(hidden_state_index, bool): + raise TypeError("hidden_state_index must be an integer.") + store_all_hidden_states = model_kwargs.get("store_all_hidden_states", False) + if not isinstance(store_all_hidden_states, bool): + raise TypeError("store_all_hidden_states must be a boolean.") + normalized_decoder_inputs: tuple[str, ...] | None = None + has_decoder_inputs = decoder_inputs is not None + has_decoder_ids = decoder_input_ids is not None + if hidden_state_source == "encoder": + if has_decoder_inputs or has_decoder_ids or decoder_attention_mask is not None: + raise ValueError("Decoder inputs are only valid when hidden_state_source='decoder'.") + else: + if has_decoder_inputs == has_decoder_ids: + raise ValueError( + "Decoder embedding requires exactly one of decoder_inputs or decoder_input_ids." + ) + decoder_input_fingerprint: str | None = None + if decoder_inputs is not None: + if isinstance(decoder_inputs, (str, bytes)) or not isinstance(decoder_inputs, Sequence): + raise TypeError("decoder_inputs must be an aligned sequence of strings.") + normalized_decoder_inputs = tuple(decoder_inputs) + if not all(isinstance(value, str) and value for value in normalized_decoder_inputs): + raise ValueError("decoder_inputs must contain non-empty strings.") + if len(normalized_decoder_inputs) != len(records): + raise ValueError("decoder_inputs must align one-to-one with embedding inputs.") + decoder_input_fingerprint = _ordered_string_sha256(normalized_decoder_inputs) + if decoder_attention_mask is not None: + raise ValueError("decoder_attention_mask requires decoder_input_ids.") + if decoder_input_ids is not None: + if not isinstance(decoder_input_ids, Tensor) or decoder_input_ids.ndim != 2: + raise ValueError("decoder_input_ids must have shape (batch, sequence).") + if decoder_input_ids.shape[0] != len(records): + raise ValueError("decoder_input_ids must align one-to-one with embedding inputs.") + if decoder_input_ids.dtype == torch.bool or decoder_input_ids.is_floating_point(): + raise TypeError("decoder_input_ids must use an integer token dtype.") + decoder_input_fingerprint = tensor_sha256(decoder_input_ids) + decoder_mask_fingerprint: str | None = None + if decoder_attention_mask is not None: + if not isinstance(decoder_attention_mask, Tensor): + raise TypeError("decoder_attention_mask must be a tensor.") + if decoder_input_ids is None or decoder_attention_mask.shape != decoder_input_ids.shape: + raise ValueError("decoder_attention_mask must match decoder_input_ids shape.") + decoder_mask_fingerprint = tensor_sha256(decoder_attention_mask) + + context: dict[str, Any] = { + "hidden_state_source": hidden_state_source, + "hidden_state_index": hidden_state_index, + "store_all_hidden_states": store_all_hidden_states, + "decoder_input_fingerprint": decoder_input_fingerprint, + "decoder_attention_mask_fingerprint": decoder_mask_fingerprint, + "decoder_alignment": "input-position" if hidden_state_source == "decoder" else None, + } + metadata_hook = getattr(model, "_embedding_metadata", None) + model_metadata: Mapping[str, Any] | None = None + if callable(metadata_hook): + model_metadata = metadata_hook(**context) + if not isinstance(model_metadata, Mapping): + raise TypeError("_embedding_metadata must return a mapping.") + context["model_embedding"] = _fingerprint_jsonable(model_metadata) + if hidden_state_source == "decoder": + has_decoder_batch = callable(getattr(model, "_embedding_batch", None)) + declares_decoder_stack = ( + model_metadata is not None and model_metadata.get("hidden_state_stack") == "decoder" + ) + if not has_decoder_batch or not declares_decoder_stack: + raise ValueError( + f"{model.__class__.__name__} does not declare decoder embedding support." + ) + return context, normalized_decoder_inputs diff --git a/fastplms/embeddings/inputs.py b/fastplms/embeddings/inputs.py index ee680733e0667fe2891e4eee053d6d883f8a72b8..4ffc8d592cb47ae335e7f183446f37c8e5aaea86 100644 --- a/fastplms/embeddings/inputs.py +++ b/fastplms/embeddings/inputs.py @@ -1,263 +1,264 @@ -"""Normalize ordered inputs and plan bounded windows without retaining a full stream.""" - -from __future__ import annotations - -import hashlib -import sqlite3 -import tempfile -from collections.abc import Iterable, Iterator, Mapping, Sequence -from pathlib import Path -from typing import overload - -from .types import EmbeddingInput - - -def iter_fasta(path: str | Path) -> Iterator[EmbeddingInput]: - """Yield FASTA records in source order without reading the file into memory.""" - - identifier: str | None = None - sequence_parts: list[str] = [] - found_record = False - with Path(path).open("r", encoding="utf-8") as handle: - for line_number, raw_line in enumerate(handle, start=1): - line = raw_line.strip() - if not line: - continue - if line.startswith(">"): - if identifier is not None: - found_record = True - yield EmbeddingInput(identifier, "".join(sequence_parts)) - identifier = line[1:].strip().split(maxsplit=1)[0] - if not identifier: - raise ValueError(f"Missing FASTA identifier on line {line_number}.") - sequence_parts = [] - else: - if identifier is None: - raise ValueError( - f"Sequence data precedes the first FASTA header on line {line_number}." - ) - sequence_parts.append("".join(line.split())) - if identifier is not None: - found_record = True - yield EmbeddingInput(identifier, "".join(sequence_parts)) - if not found_record: - raise ValueError(f"No FASTA records found in {path}.") - - -def parse_fasta(path: str | Path) -> list[EmbeddingInput]: - """Parse FASTA records while preserving identifiers, order, and duplicates.""" - - return list(iter_fasta(path)) - - -def _normalize_input_item( - position: int, - item: str | EmbeddingInput | tuple[str, str], -) -> EmbeddingInput: - if isinstance(item, EmbeddingInput): - return item - if isinstance(item, str): - return EmbeddingInput(str(position), item) - if isinstance(item, tuple) and len(item) == 2: - return EmbeddingInput(str(item[0]), str(item[1])) - raise TypeError( - "inputs must contain sequences, EmbeddingInput values, or (id, sequence) tuples." - ) - - -class _InputSpool(Sequence[EmbeddingInput]): - """Immutable disk-backed normalized inputs with an incremental digest.""" - - def __init__( - self, - values: Iterable[str | EmbeddingInput | tuple[str, str]], - ) -> None: - self._temporary: tempfile.TemporaryDirectory[str] | None = tempfile.TemporaryDirectory( - prefix="fastplms-inputs-" - ) - self.path = Path(self._temporary.name) / "inputs.sqlite" - self._connection: sqlite3.Connection | None = sqlite3.connect(self.path) - self._connection.execute( - "CREATE TABLE inputs (" - "position INTEGER PRIMARY KEY, input_id TEXT NOT NULL, sequence TEXT NOT NULL)" - ) - digest = hashlib.sha256() - count = 0 - pending: list[tuple[int, str, str]] = [] - try: - for position, item in enumerate(values): - record = _normalize_input_item(position, item) - for value in (record.id, record.sequence): - encoded = value.encode("utf-8") - digest.update(len(encoded).to_bytes(8, "big")) - digest.update(encoded) - pending.append((position, record.id, record.sequence)) - count += 1 - if len(pending) == 1_024: - self._connection.executemany("INSERT INTO inputs VALUES (?, ?, ?)", pending) - pending.clear() - if pending: - self._connection.executemany("INSERT INTO inputs VALUES (?, ?, ?)", pending) - if count == 0: - raise ValueError("inputs must contain at least one sequence.") - self._connection.commit() - self._connection.close() - self._connection = sqlite3.connect( - f"{self.path.resolve().as_uri()}?mode=ro", - uri=True, - ) - except BaseException: - self.close() - raise - digest.update(count.to_bytes(8, "big")) - self.input_fingerprint = digest.hexdigest() - self._count = count - - def _require_connection(self) -> sqlite3.Connection: - if self._connection is None: - raise RuntimeError("Input spool is closed.") - return self._connection - - def __len__(self) -> int: - return self._count - - def __iter__(self) -> Iterator[EmbeddingInput]: - cursor = self._require_connection().execute( - "SELECT input_id, sequence FROM inputs ORDER BY position" - ) - while rows := cursor.fetchmany(1_024): - for input_id, sequence in rows: - yield EmbeddingInput(input_id, sequence) - - @overload - def __getitem__(self, index: int, /) -> EmbeddingInput: ... - - @overload - def __getitem__(self, index: slice, /) -> list[EmbeddingInput]: ... - - def __getitem__(self, index: int | slice) -> EmbeddingInput | list[EmbeddingInput]: - connection = self._require_connection() - - if isinstance(index, slice): - start, stop, step = index.indices(self._count) - if step != 1: - return [self[position] for position in range(start, stop, step)] - rows = connection.execute( - "SELECT input_id, sequence FROM inputs " - "WHERE position >= ? AND position < ? ORDER BY position", - (start, stop), - ).fetchall() - return [EmbeddingInput(input_id, sequence) for input_id, sequence in rows] - position = index + self._count if index < 0 else index - if position < 0 or position >= self._count: - raise IndexError(index) - row = connection.execute( - "SELECT input_id, sequence FROM inputs WHERE position = ?", (position,) - ).fetchone() - if row is None: - raise IndexError(index) - return EmbeddingInput(row[0], row[1]) - - def close(self) -> None: - connection = getattr(self, "_connection", None) - if connection is not None: - connection.close() - self._connection = None - temporary = getattr(self, "_temporary", None) - if temporary is not None: - temporary.cleanup() - self._temporary = None - - def __del__(self) -> None: - self.close() - - -def _normalize_inputs( - inputs: (Iterable[str | EmbeddingInput | tuple[str, str]] | Mapping[str, str] | str | Path), - *, - disk_backed: bool, -) -> Sequence[EmbeddingInput]: - is_fasta_path = isinstance(inputs, Path) - if isinstance(inputs, str): - try: - is_fasta_path = Path(inputs).is_file() - except OSError: - is_fasta_path = False - should_spool = disk_backed or is_fasta_path or not isinstance(inputs, (str, Sequence, Mapping)) - values: Iterable[str | EmbeddingInput | tuple[str, str]] - if isinstance(inputs, Path): - values = iter_fasta(inputs) - elif isinstance(inputs, str): - values = iter_fasta(inputs) if is_fasta_path else [inputs] - elif isinstance(inputs, Mapping): - values = inputs.items() - else: - values = inputs - if should_spool: - return _InputSpool(values) - records: list[EmbeddingInput] = [] - for position, item in enumerate(values): - records.append(_normalize_input_item(position, item)) - if not records: - raise ValueError("inputs must contain at least one sequence.") - return records - - -def _validate_untruncated_lengths( - records: Sequence[EmbeddingInput], - *, - max_length: int | None, - truncate: bool, -) -> None: - """Fail before inference when a biological-residue limit would be exceeded.""" - - if max_length is None or truncate: - return - for position, record in enumerate(records): - residue_count = len(record.sequence) - if residue_count > max_length: - raise ValueError( - f"Input at position {position} with id {record.id!r} has " - f"{residue_count} biological residues, exceeding max_length={max_length} " - "while truncate=False." - ) - - -def _planned_batches( - records: Sequence[EmbeddingInput], - positions: range, - *, - batch_size: int, - max_tokens_per_batch: int | None, - max_length: int | None, - truncate: bool, -) -> Iterator[list[int]]: - """Length-bucket one bounded window while retaining stable output positions.""" - - def effective_length(position: int) -> int: - length = len(records[position].sequence) - return min(length, max_length) if truncate and max_length is not None else length - - ordered = sorted(positions, key=lambda position: (-effective_length(position), position)) - batch: list[int] = [] - longest = 0 - for position in ordered: - length = effective_length(position) - if max_tokens_per_batch is not None and length > max_tokens_per_batch: - raise ValueError( - f"Input at position {position} has {length} residues, exceeding " - f"max_tokens_per_batch={max_tokens_per_batch}." - ) - candidate_longest = max(longest, length) - exceeds_tokens = ( - max_tokens_per_batch is not None - and candidate_longest * (len(batch) + 1) > max_tokens_per_batch - ) - if batch and (len(batch) >= batch_size or exceeds_tokens): - yield batch - batch = [] - longest = 0 - batch.append(position) - longest = max(longest, length) - if batch: - yield batch +"""Normalize ordered inputs and plan bounded windows without retaining a full stream.""" + +from __future__ import annotations + +import hashlib +import sqlite3 +import tempfile + +from collections.abc import Iterable, Iterator, Mapping, Sequence +from pathlib import Path +from typing import overload + +from .types import EmbeddingInput + + +def iter_fasta(path: str | Path) -> Iterator[EmbeddingInput]: + """Yield FASTA records in source order without reading the file into memory.""" + + identifier: str | None = None + sequence_parts: list[str] = [] + found_record = False + with Path(path).open("r", encoding="utf-8") as handle: + for line_number, raw_line in enumerate(handle, start=1): + line = raw_line.strip() + if not line: + continue + if line.startswith(">"): + if identifier is not None: + found_record = True + yield EmbeddingInput(identifier, "".join(sequence_parts)) + identifier = line[1:].strip().split(maxsplit=1)[0] + if not identifier: + raise ValueError(f"Missing FASTA identifier on line {line_number}.") + sequence_parts = [] + else: + if identifier is None: + raise ValueError( + f"Sequence data precedes the first FASTA header on line {line_number}." + ) + sequence_parts.append("".join(line.split())) + if identifier is not None: + found_record = True + yield EmbeddingInput(identifier, "".join(sequence_parts)) + if not found_record: + raise ValueError(f"No FASTA records found in {path}.") + + +def parse_fasta(path: str | Path) -> list[EmbeddingInput]: + """Parse FASTA records while preserving identifiers, order, and duplicates.""" + + return list(iter_fasta(path)) + + +def _normalize_input_item( + position: int, + item: str | EmbeddingInput | tuple[str, str], +) -> EmbeddingInput: + if isinstance(item, EmbeddingInput): + return item + if isinstance(item, str): + return EmbeddingInput(str(position), item) + if isinstance(item, tuple) and len(item) == 2: + return EmbeddingInput(str(item[0]), str(item[1])) + raise TypeError( + "inputs must contain sequences, EmbeddingInput values, or (id, sequence) tuples." + ) + + +class _InputSpool(Sequence[EmbeddingInput]): + """Immutable disk-backed normalized inputs with an incremental digest.""" + + def __init__( + self, + values: Iterable[str | EmbeddingInput | tuple[str, str]], + ) -> None: + self._temporary: tempfile.TemporaryDirectory[str] | None = tempfile.TemporaryDirectory( + prefix="fastplms-inputs-" + ) + self.path = Path(self._temporary.name) / "inputs.sqlite" + self._connection: sqlite3.Connection | None = sqlite3.connect(self.path) + self._connection.execute( + "CREATE TABLE inputs (" + "position INTEGER PRIMARY KEY, input_id TEXT NOT NULL, sequence TEXT NOT NULL)" + ) + digest = hashlib.sha256() + count = 0 + pending: list[tuple[int, str, str]] = [] + try: + for position, item in enumerate(values): + record = _normalize_input_item(position, item) + for value in (record.id, record.sequence): + encoded = value.encode("utf-8") + digest.update(len(encoded).to_bytes(8, "big")) + digest.update(encoded) + pending.append((position, record.id, record.sequence)) + count += 1 + if len(pending) == 1_024: + self._connection.executemany("INSERT INTO inputs VALUES (?, ?, ?)", pending) + pending.clear() + if pending: + self._connection.executemany("INSERT INTO inputs VALUES (?, ?, ?)", pending) + if count == 0: + raise ValueError("inputs must contain at least one sequence.") + self._connection.commit() + self._connection.close() + self._connection = sqlite3.connect( + f"{self.path.resolve().as_uri()}?mode=ro", + uri=True, + ) + except BaseException: + self.close() + raise + digest.update(count.to_bytes(8, "big")) + self.input_fingerprint = digest.hexdigest() + self._count = count + + def _require_connection(self) -> sqlite3.Connection: + if self._connection is None: + raise RuntimeError("Input spool is closed.") + return self._connection + + def __len__(self) -> int: + return self._count + + def __iter__(self) -> Iterator[EmbeddingInput]: + cursor = self._require_connection().execute( + "SELECT input_id, sequence FROM inputs ORDER BY position" + ) + while rows := cursor.fetchmany(1_024): + for input_id, sequence in rows: + yield EmbeddingInput(input_id, sequence) + + @overload + def __getitem__(self, index: int, /) -> EmbeddingInput: ... + + @overload + def __getitem__(self, index: slice, /) -> list[EmbeddingInput]: ... + + def __getitem__(self, index: int | slice) -> EmbeddingInput | list[EmbeddingInput]: + connection = self._require_connection() + + if isinstance(index, slice): + start, stop, step = index.indices(self._count) + if step != 1: + return [self[position] for position in range(start, stop, step)] + rows = connection.execute( + "SELECT input_id, sequence FROM inputs " + "WHERE position >= ? AND position < ? ORDER BY position", + (start, stop), + ).fetchall() + return [EmbeddingInput(input_id, sequence) for input_id, sequence in rows] + position = index + self._count if index < 0 else index + if position < 0 or position >= self._count: + raise IndexError(index) + row = connection.execute( + "SELECT input_id, sequence FROM inputs WHERE position = ?", (position,) + ).fetchone() + if row is None: + raise IndexError(index) + return EmbeddingInput(row[0], row[1]) + + def close(self) -> None: + connection = getattr(self, "_connection", None) + if connection is not None: + connection.close() + self._connection = None + temporary = getattr(self, "_temporary", None) + if temporary is not None: + temporary.cleanup() + self._temporary = None + + def __del__(self) -> None: + self.close() + + +def _normalize_inputs( + inputs: (Iterable[str | EmbeddingInput | tuple[str, str]] | Mapping[str, str] | str | Path), + *, + disk_backed: bool, +) -> Sequence[EmbeddingInput]: + is_fasta_path = isinstance(inputs, Path) + if isinstance(inputs, str): + try: + is_fasta_path = Path(inputs).is_file() + except OSError: + is_fasta_path = False + should_spool = disk_backed or is_fasta_path or not isinstance(inputs, (str, Sequence, Mapping)) + values: Iterable[str | EmbeddingInput | tuple[str, str]] + if isinstance(inputs, Path): + values = iter_fasta(inputs) + elif isinstance(inputs, str): + values = iter_fasta(inputs) if is_fasta_path else [inputs] + elif isinstance(inputs, Mapping): + values = inputs.items() + else: + values = inputs + if should_spool: + return _InputSpool(values) + records: list[EmbeddingInput] = [] + for position, item in enumerate(values): + records.append(_normalize_input_item(position, item)) + if not records: + raise ValueError("inputs must contain at least one sequence.") + return records + + +def _validate_untruncated_lengths( + records: Sequence[EmbeddingInput], + *, + max_length: int | None, + truncate: bool, +) -> None: + """Fail before inference when a biological-residue limit would be exceeded.""" + + if max_length is None or truncate: + return + for position, record in enumerate(records): + residue_count = len(record.sequence) + if residue_count > max_length: + raise ValueError( + f"Input at position {position} with id {record.id!r} has " + f"{residue_count} biological residues, exceeding max_length={max_length} " + "while truncate=False." + ) + + +def _planned_batches( + records: Sequence[EmbeddingInput], + positions: range, + *, + batch_size: int, + max_tokens_per_batch: int | None, + max_length: int | None, + truncate: bool, +) -> Iterator[list[int]]: + """Length-bucket one bounded window while retaining stable output positions.""" + + def effective_length(position: int) -> int: + length = len(records[position].sequence) + return min(length, max_length) if truncate and max_length is not None else length + + ordered = sorted(positions, key=lambda position: (-effective_length(position), position)) + batch: list[int] = [] + longest = 0 + for position in ordered: + length = effective_length(position) + if max_tokens_per_batch is not None and length > max_tokens_per_batch: + raise ValueError( + f"Input at position {position} has {length} residues, exceeding " + f"max_tokens_per_batch={max_tokens_per_batch}." + ) + candidate_longest = max(longest, length) + exceeds_tokens = ( + max_tokens_per_batch is not None + and candidate_longest * (len(batch) + 1) > max_tokens_per_batch + ) + if batch and (len(batch) >= batch_size or exceeds_tokens): + yield batch + batch = [] + longest = 0 + batch.append(position) + longest = max(longest, length) + if batch: + yield batch diff --git a/fastplms/embeddings/output.py b/fastplms/embeddings/output.py index 53d683e012b930ad16fdf15bb7a1c3d51c818fc7..ebd3e8ccbb08eb6b9a03f032e16c161ce7ba49fa 100644 --- a/fastplms/embeddings/output.py +++ b/fastplms/embeddings/output.py @@ -1,215 +1,215 @@ -"""Resume validation and transactional publication of ordered embedding windows.""" - -from __future__ import annotations - -from collections.abc import Sequence -from pathlib import Path -from typing import Any - -from .identity import _RUN_FINGERPRINT_SCHEMA_VERSION -from .pooling import Pooler -from .storage import ( - SafetensorsStreamWriter, - append_sqlite_records, - initialize_sqlite_run, - load_result, - load_sqlite_result, - safetensors_result_exists, - save_result, - tensor_sha256, - update_sqlite_run_metadata, -) -from .types import EmbeddingInput, EmbeddingRecord, EmbeddingResult, LazyTensorReference - - -def _output_exists(path: str | Path, format: str) -> bool: - path = Path(path) - if format == "sqlite": - return path.is_file() - return safetensors_result_exists(path) - - -def _output_descriptor(position: int, record: EmbeddingRecord) -> dict[str, Any]: - tensor = record.tensor - if isinstance(tensor, LazyTensorReference): - dtype = tensor.dtype - shape = tensor.shape - digest = tensor.sha256 - else: - dtype = str(tensor.dtype).removeprefix("torch.") - shape = tuple(tensor.shape) - digest = tensor_sha256(tensor) - return { - "position": position, - "id": record.id, - "dtype": dtype, - "shape": shape, - "sha256": digest, - } - - -class EmbeddingOutput: - """Own the resumable prefix and the commit state of one output destination.""" - - def __init__( - self, - records: Sequence[EmbeddingInput], - *, - output: str | Path | None, - format: str, - resume: bool, - shard_size: int, - run_fingerprint: str, - input_fingerprint: str, - model_state_fingerprint: str | None, - model_state_fingerprint_source: str, - pooler: Pooler | None, - pooling_names: Sequence[str], - ) -> None: - self.output = output - self.format = format - self.shard_size = shard_size - self.completed: EmbeddingResult | None = None - output_already_exists = output is not None and _output_exists(output, format) - existing: EmbeddingResult | None = None - self.start_position = 0 - if output is not None and resume and output_already_exists: - if format == "sqlite": - try: - existing = load_sqlite_result(output, run_id=run_fingerprint) - except KeyError: - existing = load_result(output, format=format) - else: - existing = load_result(output, format=format) - if existing.metadata.get("fingerprint_schema_version") != ( - _RUN_FINGERPRINT_SCHEMA_VERSION - ): - raise ValueError( - "Existing embeddings use an incompatible run fingerprint schema; " - "choose another output or set resume=False." - ) - if existing.metadata.get("run_fingerprint") != run_fingerprint: - raise ValueError( - "Existing embeddings were produced by a different run fingerprint; " - "choose another output or set resume=False." - ) - if len(existing) > len(records): - raise ValueError( - "Existing embeddings are not an ordered prefix of the requested inputs." - ) - prefix_matches = all( - (observed.id, observed.sequence) == (expected.id, expected.sequence) - for expected, observed in zip(records, existing, strict=False) - ) - if not prefix_matches: - raise ValueError( - "Existing embeddings are not an ordered prefix of the requested inputs." - ) - if len(existing) == len(records) and existing.metadata.get("complete", True): - self.completed = existing - return - self.start_position = len(existing) - - self.sqlite_run_id: str | None = None - self.sqlite_replace_on_first_commit = False - self.sqlite_initial_metadata: dict[str, Any] | None = None - if output is not None and format == "sqlite": - self.sqlite_initial_metadata = { - "format_version": 1, - "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, - "run_fingerprint": run_fingerprint, - "input_fingerprint": input_fingerprint, - "model_state_fingerprint": model_state_fingerprint, - "model_state_fingerprint_source": model_state_fingerprint_source, - "complete": False, - } - self.sqlite_run_id = run_fingerprint - if not resume and output_already_exists: - try: - load_sqlite_result(output, run_id=run_fingerprint) - except KeyError: - pass - else: - # Keep an exact prior run readable until replacement inference - # has produced the first complete commit window. - self.sqlite_replace_on_first_commit = True - if not self.sqlite_replace_on_first_commit: - initialize_sqlite_run( - output, - self.sqlite_initial_metadata, - resume=resume, - ) - - stream_safetensors = output is not None and format == "safetensors" - self.output_records: list[EmbeddingRecord] = ( - [] if self.sqlite_run_id is not None or stream_safetensors else list(existing or ()) - ) - self.output_descriptors: list[dict[str, Any]] | None = [] if output is None else None - self.pool_slices: dict[str, tuple[int, int]] = {} - if existing and pooler is not None: - pooled_width = existing[0].load_tensor().shape[-1] - if pooled_width % len(pooling_names) != 0: - raise ValueError("Stored pooled width is inconsistent with pooling metadata.") - self.pool_slices = pooler.output_slices(pooled_width // len(pooling_names)) - - self.safetensors_writer: SafetensorsStreamWriter | None = None - if stream_safetensors: - if output is None: - raise RuntimeError( - "Safetensors streaming was enabled without an output destination." - ) - transactional_overwrite = output_already_exists and not resume - self.safetensors_writer = SafetensorsStreamWriter( - output, - { - "format_version": 1, - "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, - "run_fingerprint": run_fingerprint, - "input_fingerprint": input_fingerprint, - "model_state_fingerprint": model_state_fingerprint, - "model_state_fingerprint_source": model_state_fingerprint_source, - "complete": False, - }, - shard_size=shard_size, - existing=existing or (), - reuse_existing=bool(resume and existing is not None), - publish_initial=not transactional_overwrite, - publish_incremental=not transactional_overwrite, - ) - - def append(self, window_start: int, new_records: list[EmbeddingRecord]) -> None: - """Commit a complete ordered window at the storage format's granularity.""" - - if self.output_descriptors is not None: - self.output_descriptors.extend( - _output_descriptor(window_start + offset, record) - for offset, record in enumerate(new_records) - ) - if self.output is not None and self.sqlite_run_id is not None: - append_sqlite_records( - self.output, - self.sqlite_run_id, - window_start, - new_records, - replace_metadata=( - self.sqlite_initial_metadata if self.sqlite_replace_on_first_commit else None - ), - ) - self.sqlite_replace_on_first_commit = False - elif self.safetensors_writer is not None: - self.safetensors_writer.append(new_records) - else: - self.output_records.extend(new_records) - - def finish(self, metadata: dict[str, Any]) -> EmbeddingResult: - """Publish completion only after every window has committed.""" - - if self.output is not None and self.sqlite_run_id is not None: - update_sqlite_run_metadata(self.output, self.sqlite_run_id, metadata) - return load_sqlite_result(self.output, run_id=self.sqlite_run_id) - if self.safetensors_writer is not None: - return self.safetensors_writer.publish(complete=True, metadata=metadata) - result = EmbeddingResult(self.output_records, metadata) - if self.output is not None: - return save_result(result, self.output, format=self.format, shard_size=self.shard_size) - return result +"""Resume validation and transactional publication of ordered embedding windows.""" + +from __future__ import annotations + +from collections.abc import Sequence +from pathlib import Path +from typing import Any + +from .identity import _RUN_FINGERPRINT_SCHEMA_VERSION +from .pooling import Pooler +from .storage import ( + SafetensorsStreamWriter, + append_sqlite_records, + initialize_sqlite_run, + load_result, + load_sqlite_result, + safetensors_result_exists, + save_result, + tensor_sha256, + update_sqlite_run_metadata, +) +from .types import EmbeddingInput, EmbeddingRecord, EmbeddingResult, LazyTensorReference + + +def _output_exists(path: str | Path, format: str) -> bool: + path = Path(path) + if format == "sqlite": + return path.is_file() + return safetensors_result_exists(path) + + +def _output_descriptor(position: int, record: EmbeddingRecord) -> dict[str, Any]: + tensor = record.tensor + if isinstance(tensor, LazyTensorReference): + dtype = tensor.dtype + shape = tensor.shape + digest = tensor.sha256 + else: + dtype = str(tensor.dtype).removeprefix("torch.") + shape = tuple(tensor.shape) + digest = tensor_sha256(tensor) + return { + "position": position, + "id": record.id, + "dtype": dtype, + "shape": shape, + "sha256": digest, + } + + +class EmbeddingOutput: + """Own the resumable prefix and the commit state of one output destination.""" + + def __init__( + self, + records: Sequence[EmbeddingInput], + *, + output: str | Path | None, + format: str, + resume: bool, + shard_size: int, + run_fingerprint: str, + input_fingerprint: str, + model_state_fingerprint: str | None, + model_state_fingerprint_source: str, + pooler: Pooler | None, + pooling_names: Sequence[str], + ) -> None: + self.output = output + self.format = format + self.shard_size = shard_size + self.completed: EmbeddingResult | None = None + output_already_exists = output is not None and _output_exists(output, format) + existing: EmbeddingResult | None = None + self.start_position = 0 + if output is not None and resume and output_already_exists: + if format == "sqlite": + try: + existing = load_sqlite_result(output, run_id=run_fingerprint) + except KeyError: + existing = load_result(output, format=format) + else: + existing = load_result(output, format=format) + if existing.metadata.get("fingerprint_schema_version") != ( + _RUN_FINGERPRINT_SCHEMA_VERSION + ): + raise ValueError( + "Existing embeddings use an incompatible run fingerprint schema; " + "choose another output or set resume=False." + ) + if existing.metadata.get("run_fingerprint") != run_fingerprint: + raise ValueError( + "Existing embeddings were produced by a different run fingerprint; " + "choose another output or set resume=False." + ) + if len(existing) > len(records): + raise ValueError( + "Existing embeddings are not an ordered prefix of the requested inputs." + ) + prefix_matches = all( + (observed.id, observed.sequence) == (expected.id, expected.sequence) + for expected, observed in zip(records, existing, strict=False) + ) + if not prefix_matches: + raise ValueError( + "Existing embeddings are not an ordered prefix of the requested inputs." + ) + if len(existing) == len(records) and existing.metadata.get("complete", True): + self.completed = existing + return + self.start_position = len(existing) + + self.sqlite_run_id: str | None = None + self.sqlite_replace_on_first_commit = False + self.sqlite_initial_metadata: dict[str, Any] | None = None + if output is not None and format == "sqlite": + self.sqlite_initial_metadata = { + "format_version": 1, + "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, + "run_fingerprint": run_fingerprint, + "input_fingerprint": input_fingerprint, + "model_state_fingerprint": model_state_fingerprint, + "model_state_fingerprint_source": model_state_fingerprint_source, + "complete": False, + } + self.sqlite_run_id = run_fingerprint + if not resume and output_already_exists: + try: + load_sqlite_result(output, run_id=run_fingerprint) + except KeyError: + pass + else: + # Keep an exact prior run readable until replacement inference + # has produced the first complete commit window. + self.sqlite_replace_on_first_commit = True + if not self.sqlite_replace_on_first_commit: + initialize_sqlite_run( + output, + self.sqlite_initial_metadata, + resume=resume, + ) + + stream_safetensors = output is not None and format == "safetensors" + self.output_records: list[EmbeddingRecord] = ( + [] if self.sqlite_run_id is not None or stream_safetensors else list(existing or ()) + ) + self.output_descriptors: list[dict[str, Any]] | None = [] if output is None else None + self.pool_slices: dict[str, tuple[int, int]] = {} + if existing and pooler is not None: + pooled_width = existing[0].load_tensor().shape[-1] + if pooled_width % len(pooling_names) != 0: + raise ValueError("Stored pooled width is inconsistent with pooling metadata.") + self.pool_slices = pooler.output_slices(pooled_width // len(pooling_names)) + + self.safetensors_writer: SafetensorsStreamWriter | None = None + if stream_safetensors: + if output is None: + raise RuntimeError( + "Safetensors streaming was enabled without an output destination." + ) + transactional_overwrite = output_already_exists and not resume + self.safetensors_writer = SafetensorsStreamWriter( + output, + { + "format_version": 1, + "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, + "run_fingerprint": run_fingerprint, + "input_fingerprint": input_fingerprint, + "model_state_fingerprint": model_state_fingerprint, + "model_state_fingerprint_source": model_state_fingerprint_source, + "complete": False, + }, + shard_size=shard_size, + existing=existing or (), + reuse_existing=bool(resume and existing is not None), + publish_initial=not transactional_overwrite, + publish_incremental=not transactional_overwrite, + ) + + def append(self, window_start: int, new_records: list[EmbeddingRecord]) -> None: + """Commit a complete ordered window at the storage format's granularity.""" + + if self.output_descriptors is not None: + self.output_descriptors.extend( + _output_descriptor(window_start + offset, record) + for offset, record in enumerate(new_records) + ) + if self.output is not None and self.sqlite_run_id is not None: + append_sqlite_records( + self.output, + self.sqlite_run_id, + window_start, + new_records, + replace_metadata=( + self.sqlite_initial_metadata if self.sqlite_replace_on_first_commit else None + ), + ) + self.sqlite_replace_on_first_commit = False + elif self.safetensors_writer is not None: + self.safetensors_writer.append(new_records) + else: + self.output_records.extend(new_records) + + def finish(self, metadata: dict[str, Any]) -> EmbeddingResult: + """Publish completion only after every window has committed.""" + + if self.output is not None and self.sqlite_run_id is not None: + update_sqlite_run_metadata(self.output, self.sqlite_run_id, metadata) + return load_sqlite_result(self.output, run_id=self.sqlite_run_id) + if self.safetensors_writer is not None: + return self.safetensors_writer.publish(complete=True, metadata=metadata) + result = EmbeddingResult(self.output_records, metadata) + if self.output is not None: + return save_result(result, self.output, format=self.format, shard_size=self.shard_size) + return result diff --git a/fastplms/embeddings/pooling.py b/fastplms/embeddings/pooling.py index 08d577c0e03c8a41d0c5e9ca924a6eb0cfd307a8..d72605bf989170e1a4b6ebdcedb7385ced995116 100644 --- a/fastplms/embeddings/pooling.py +++ b/fastplms/embeddings/pooling.py @@ -4,6 +4,7 @@ from __future__ import annotations import math import torch + from collections.abc import Sequence from torch import Tensor diff --git a/fastplms/embeddings/runner.py b/fastplms/embeddings/runner.py index 6f8170833c7c8338bf7203b015447c934a7c3af4..d0286af96ad96500facf3968380e635deb767421 100644 --- a/fastplms/embeddings/runner.py +++ b/fastplms/embeddings/runner.py @@ -1,425 +1,426 @@ -"""Coordinate input preparation, run identity, batch execution, and publication.""" - -from __future__ import annotations - -import torch -from collections.abc import Callable, Iterable, Mapping, Sequence -from pathlib import Path -from typing import Any +"""Coordinate input preparation, run identity, batch execution, and publication.""" + +from __future__ import annotations + +import torch + +from collections.abc import Callable, Iterable, Mapping, Sequence +from pathlib import Path +from typing import Any from torch import Tensor from . import identity from .batches import ( - BatchExecutor, - _residue_embeddings as _residue_embeddings, - _temporary_eval, - select_hidden_state_embeddings as select_hidden_state_embeddings, -) -from .identity import ( - _RUN_FINGERPRINT_SCHEMA_VERSION, - _adapter_identity_metadata, - _attention_backend, - _attention_kernel_metadata, - _embedding_context, - _execution_identity_metadata, - _fingerprint_jsonable, - _model_identity_metadata, - _run_fingerprint, + BatchExecutor, + _residue_embeddings as _residue_embeddings, + _temporary_eval, + select_hidden_state_embeddings as select_hidden_state_embeddings, +) +from .identity import ( + _RUN_FINGERPRINT_SCHEMA_VERSION, + _adapter_identity_metadata, + _attention_backend, + _attention_kernel_metadata, + _embedding_context, + _execution_identity_metadata, + _fingerprint_jsonable, + _model_identity_metadata, + _run_fingerprint, _tokenizer_metadata, -) -from .inputs import ( - _InputSpool, - _normalize_inputs, - _validate_untruncated_lengths, - iter_fasta as iter_fasta, - parse_fasta as parse_fasta, -) -from .output import EmbeddingOutput -from .pooling import Pooler -from .types import EmbeddingBatch, EmbeddingInput, EmbeddingResult - - -_DEFAULT_BATCH_WINDOW_MULTIPLIER = 16 -_SUPPORTED_STORAGE_FORMATS = frozenset({"safetensors", "sqlite"}) - - -def embed_dataset( - model: Any, - inputs: (Iterable[str | EmbeddingInput | tuple[str, str]] | Mapping[str, str] | str | Path), - *, - batch_size: int = 2, - pooling: str | Sequence[str] | None = None, - full_embeddings: bool = False, - output: str | Path | None = None, - format: str = "safetensors", - resume: bool = True, - tokenizer: Any | None = None, - max_length: int | None = None, - truncate: bool = True, - dtype: torch.dtype | None = torch.float32, - shard_size: int = 2 * 1024**3, - model_state_fingerprint: str | None = None, - batch_window_size: int | None = None, - max_tokens_per_batch: int | None = None, - hidden_state_source: str = "encoder", - decoder_inputs: Sequence[str] | None = None, - decoder_input_ids: Tensor | None = None, - decoder_attention_mask: Tensor | None = None, - _embedding_batch_fn: Callable[..., EmbeddingBatch] | None = None, - _embedding_batch_identity: Mapping[str, Any] | None = None, - _allowed_unsupported_pooling: Sequence[str] = (), - **model_kwargs: Any, -) -> EmbeddingResult: - """Embed protein sequences with stable ordering and residue-only pooling.""" - - for name, value in ( - ("batch_size", batch_size), - ("shard_size", shard_size), - ): - if not isinstance(value, int) or isinstance(value, bool): - raise TypeError(f"{name} must be a positive integer.") - if value <= 0: - raise ValueError(f"{name} must be a positive integer.") - for optional_name, optional_value in ( - ("max_length", max_length), - ("max_tokens_per_batch", max_tokens_per_batch), - ("batch_window_size", batch_window_size), - ): - if optional_value is not None and ( - not isinstance(optional_value, int) or isinstance(optional_value, bool) - ): - raise TypeError(f"{optional_name} must be a positive integer when provided.") - if optional_value is not None and optional_value <= 0: - raise ValueError(f"{optional_name} must be a positive integer when provided.") - for name, value in ( - ("full_embeddings", full_embeddings), - ("resume", resume), - ("truncate", truncate), - ): - if not isinstance(value, bool): - raise TypeError(f"{name} must be a boolean.") - if not isinstance(format, str): - raise TypeError("format must be a string.") - if output is not None and not isinstance(output, (str, Path)): - raise TypeError("output must be a path or None.") - if model_state_fingerprint is not None and ( - not isinstance(model_state_fingerprint, str) or not model_state_fingerprint - ): - raise ValueError("model_state_fingerprint must be a non-empty string when provided.") - if hidden_state_source not in {"encoder", "decoder"}: - raise ValueError("hidden_state_source must be 'encoder' or 'decoder'.") - hidden_state_index = model_kwargs.get("hidden_state_index", -1) - if not isinstance(hidden_state_index, int) or isinstance(hidden_state_index, bool): - raise TypeError("hidden_state_index must be an integer.") - store_all_hidden_states = model_kwargs.get("store_all_hidden_states", False) - if not isinstance(store_all_hidden_states, bool): - raise TypeError("store_all_hidden_states must be a boolean.") - if decoder_input_ids is not None: - if not isinstance(decoder_input_ids, Tensor): - raise TypeError("decoder_input_ids must be a tensor.") - if decoder_input_ids.is_meta: - raise ValueError("decoder_input_ids cannot be a meta tensor.") - if decoder_input_ids.ndim != 2 or decoder_input_ids.shape[1] == 0: - raise ValueError("decoder_input_ids must have non-empty shape (batch, sequence).") - if decoder_input_ids.dtype not in {torch.int32, torch.int64}: - raise TypeError("decoder_input_ids must use torch.int32 or torch.int64.") - if decoder_attention_mask is not None: - if not isinstance(decoder_attention_mask, Tensor): - raise TypeError("decoder_attention_mask must be a tensor.") - if decoder_attention_mask.is_meta: - raise ValueError("decoder_attention_mask cannot be a meta tensor.") - if decoder_attention_mask.is_complex() or not bool( - torch.isfinite(decoder_attention_mask).all() - ): - raise ValueError("decoder_attention_mask must contain finite binary values.") - if not bool(((decoder_attention_mask == 0) | (decoder_attention_mask == 1)).all()): - raise ValueError("decoder_attention_mask must contain finite binary values.") - pooling_names = ( - (("mean",) if not full_embeddings else ()) - if pooling is None - else ((pooling,) if isinstance(pooling, str) else tuple(pooling)) - ) - if full_embeddings and pooling is not None: - raise ValueError("full_embeddings=True cannot be combined with pooling.") - if not full_embeddings and not pooling_names: - raise ValueError("pooling is required unless full_embeddings=True.") - pooler = Pooler(pooling_names) if pooling_names else None - - if batch_size <= 0: - raise ValueError("batch_size must be positive.") - if format == "pth" or (output is not None and Path(output).suffix.lower() == ".pth"): - raise ValueError("Writing pickle-based .pth embeddings is not supported.") - if format not in _SUPPORTED_STORAGE_FORMATS: - raise ValueError("format must be 'safetensors' or 'sqlite'.") - if max_length is not None and max_length <= 0: - raise ValueError("max_length must be positive when provided.") - if max_tokens_per_batch is not None and max_tokens_per_batch <= 0: - raise ValueError("max_tokens_per_batch must be positive when provided.") - if not isinstance(dtype, (torch.dtype, type(None))): - raise TypeError("dtype must be a torch.dtype or None.") - if batch_window_size is not None and batch_window_size <= 0: - raise ValueError("batch_window_size must be positive when provided.") - if _embedding_batch_fn is not None and not callable(_embedding_batch_fn): - raise TypeError("_embedding_batch_fn must be callable when provided.") - if _embedding_batch_fn is not None and _embedding_batch_identity is None: - raise ValueError( - "_embedding_batch_identity is required with _embedding_batch_fn so persisted " - "runs bind the family-specific embedding behavior." - ) - if _embedding_batch_identity is not None and not isinstance(_embedding_batch_identity, Mapping): - raise TypeError("_embedding_batch_identity must be a mapping when provided.") - if isinstance(_allowed_unsupported_pooling, (str, bytes)) or not isinstance( - _allowed_unsupported_pooling, Sequence - ): - raise TypeError("_allowed_unsupported_pooling must be a sequence of pooler names.") - if not all(isinstance(name, str) for name in _allowed_unsupported_pooling): - raise TypeError("_allowed_unsupported_pooling must contain only strings.") - allowed_unsupported_pooling = frozenset(_allowed_unsupported_pooling) - if allowed_unsupported_pooling and _embedding_batch_fn is None: - raise ValueError( - "_allowed_unsupported_pooling is only valid with a family-specific _embedding_batch_fn." - ) - resolved_batch_window_size = ( - batch_size * _DEFAULT_BATCH_WINDOW_MULTIPLIER - if batch_window_size is None - else batch_window_size - ) - if resolved_batch_window_size < batch_size: - raise ValueError("batch_window_size must be at least batch_size.") - records = _normalize_inputs(inputs, disk_backed=output is not None) - _validate_untruncated_lengths( - records, - max_length=max_length, - truncate=truncate, - ) - pooling_names = ( - (("mean",) if not full_embeddings else ()) - if pooling is None - else ((pooling,) if isinstance(pooling, str) else tuple(pooling)) - ) - if full_embeddings: - if pooling is not None: - raise ValueError("full_embeddings=True cannot be combined with pooling.") - elif not pooling_names: - raise ValueError("pooling is required unless full_embeddings=True.") - store_all_hidden_states = bool(model_kwargs.get("store_all_hidden_states", False)) - if store_all_hidden_states and not full_embeddings: - raise ValueError("store_all_hidden_states=True requires full_embeddings=True.") - - unsupported = set(getattr(model, "embedding_unsupported_pooling", ())) - unknown_pooling_overrides = allowed_unsupported_pooling.difference(unsupported) - if unknown_pooling_overrides: - raise ValueError( - "_allowed_unsupported_pooling may only override poolers declared unsupported " - f"by the model; unknown overrides: {sorted(unknown_pooling_overrides)}." - ) - unsupported.difference_update(allowed_unsupported_pooling) - requested_unsupported = unsupported.intersection(pooling_names) - if requested_unsupported: - raise ValueError( - f"{model.__class__.__name__} does not support pooling operations " - f"{sorted(requested_unsupported)}." - ) - - # Constructing the pooler validates names and duplicate operations before - # any checkpoint hashing, tokenization, or inference occurs. - pooler = Pooler(pooling_names) if pooling_names else None - embedding_context, normalized_decoder_inputs = _embedding_context( - model, - records, - hidden_state_source=hidden_state_source, - decoder_inputs=decoder_inputs, - decoder_input_ids=decoder_input_ids, - decoder_attention_mask=decoder_attention_mask, - model_kwargs=model_kwargs, - ) - if _embedding_batch_identity is not None: - embedding_context["family_adapter"] = _fingerprint_jsonable(_embedding_batch_identity) - if allowed_unsupported_pooling: - embedding_context["family_adapter_pooling_override"] = sorted( - allowed_unsupported_pooling - ) - - # A pending automatic attention request settles here, inside the caller's - # autocast context, so the fingerprint records the backend that executes. - attention_resolution = getattr(model, "attention_resolution", None) - if attention_resolution is not None and attention_resolution.deferred: - model.resolve_attn_implementation() - - tokenizer_metadata = _tokenizer_metadata(model, tokenizer) - ( - input_fingerprint, - run_fingerprint, - resolved_model_state_fingerprint, - model_state_fingerprint_source, - ) = _run_fingerprint( - model, - records, - pooling=pooling_names, - full_embeddings=full_embeddings, - max_length=max_length, - truncate=truncate, - dtype=dtype, - model_kwargs=model_kwargs, - tokenizer_metadata=tokenizer_metadata, - model_state_fingerprint=model_state_fingerprint, - persist_output=output is not None, - embedding_context=embedding_context, - batch_size=batch_size, - batch_window_size=resolved_batch_window_size, - max_tokens_per_batch=max_tokens_per_batch, - ) - destination = EmbeddingOutput( - records, - output=output, - format=format, - resume=resume, - shard_size=shard_size, - run_fingerprint=run_fingerprint, - input_fingerprint=input_fingerprint, - model_state_fingerprint=resolved_model_state_fingerprint, - model_state_fingerprint_source=model_state_fingerprint_source, - pooler=pooler, - pooling_names=pooling_names, - ) - if destination.completed is not None: - return destination.completed - - attention_backend = _attention_backend(model) - executor = BatchExecutor( - model=model, - batch_size=batch_size, - max_tokens_per_batch=max_tokens_per_batch, - max_length=max_length, - truncate=truncate, - model_kwargs=model_kwargs, - hidden_state_source=hidden_state_source, - normalized_decoder_inputs=normalized_decoder_inputs, - decoder_input_ids=decoder_input_ids, - decoder_attention_mask=decoder_attention_mask, - _embedding_batch_fn=_embedding_batch_fn, - tokenizer=tokenizer, - store_all_hidden_states=store_all_hidden_states, - full_embeddings=full_embeddings, - dtype=dtype, - pooler=pooler, - attention_backend=attention_backend, - need_attentions="parti" in pooling_names, - ) - pool_slices = destination.pool_slices - with _temporary_eval(model), torch.inference_mode(): - for window_start in range( - destination.start_position, len(records), resolved_batch_window_size - ): - window_stop = min(window_start + resolved_batch_window_size, len(records)) - window_records = records[window_start:window_stop] - if not isinstance(window_records, Sequence): - raise RuntimeError("The immutable embedding spool returned a non-sequence window.") - new_records, pool_slices = executor.run_window( - window_records, window_start=window_start - ) - destination.append(window_start, new_records) - +) +from .inputs import ( + _InputSpool, + _normalize_inputs, + _validate_untruncated_lengths, + iter_fasta as iter_fasta, + parse_fasta as parse_fasta, +) +from .output import EmbeddingOutput +from .pooling import Pooler +from .types import EmbeddingBatch, EmbeddingInput, EmbeddingResult + + +_DEFAULT_BATCH_WINDOW_MULTIPLIER = 16 +_SUPPORTED_STORAGE_FORMATS = frozenset({"safetensors", "sqlite"}) + + +def embed_dataset( + model: Any, + inputs: (Iterable[str | EmbeddingInput | tuple[str, str]] | Mapping[str, str] | str | Path), + *, + batch_size: int = 2, + pooling: str | Sequence[str] | None = None, + full_embeddings: bool = False, + output: str | Path | None = None, + format: str = "safetensors", + resume: bool = True, + tokenizer: Any | None = None, + max_length: int | None = None, + truncate: bool = True, + dtype: torch.dtype | None = torch.float32, + shard_size: int = 2 * 1024**3, + model_state_fingerprint: str | None = None, + batch_window_size: int | None = None, + max_tokens_per_batch: int | None = None, + hidden_state_source: str = "encoder", + decoder_inputs: Sequence[str] | None = None, + decoder_input_ids: Tensor | None = None, + decoder_attention_mask: Tensor | None = None, + _embedding_batch_fn: Callable[..., EmbeddingBatch] | None = None, + _embedding_batch_identity: Mapping[str, Any] | None = None, + _allowed_unsupported_pooling: Sequence[str] = (), + **model_kwargs: Any, +) -> EmbeddingResult: + """Embed protein sequences with stable ordering and residue-only pooling.""" + + for name, value in ( + ("batch_size", batch_size), + ("shard_size", shard_size), + ): + if not isinstance(value, int) or isinstance(value, bool): + raise TypeError(f"{name} must be a positive integer.") + if value <= 0: + raise ValueError(f"{name} must be a positive integer.") + for optional_name, optional_value in ( + ("max_length", max_length), + ("max_tokens_per_batch", max_tokens_per_batch), + ("batch_window_size", batch_window_size), + ): + if optional_value is not None and ( + not isinstance(optional_value, int) or isinstance(optional_value, bool) + ): + raise TypeError(f"{optional_name} must be a positive integer when provided.") + if optional_value is not None and optional_value <= 0: + raise ValueError(f"{optional_name} must be a positive integer when provided.") + for name, value in ( + ("full_embeddings", full_embeddings), + ("resume", resume), + ("truncate", truncate), + ): + if not isinstance(value, bool): + raise TypeError(f"{name} must be a boolean.") + if not isinstance(format, str): + raise TypeError("format must be a string.") + if output is not None and not isinstance(output, (str, Path)): + raise TypeError("output must be a path or None.") + if model_state_fingerprint is not None and ( + not isinstance(model_state_fingerprint, str) or not model_state_fingerprint + ): + raise ValueError("model_state_fingerprint must be a non-empty string when provided.") + if hidden_state_source not in {"encoder", "decoder"}: + raise ValueError("hidden_state_source must be 'encoder' or 'decoder'.") + hidden_state_index = model_kwargs.get("hidden_state_index", -1) + if not isinstance(hidden_state_index, int) or isinstance(hidden_state_index, bool): + raise TypeError("hidden_state_index must be an integer.") + store_all_hidden_states = model_kwargs.get("store_all_hidden_states", False) + if not isinstance(store_all_hidden_states, bool): + raise TypeError("store_all_hidden_states must be a boolean.") + if decoder_input_ids is not None: + if not isinstance(decoder_input_ids, Tensor): + raise TypeError("decoder_input_ids must be a tensor.") + if decoder_input_ids.is_meta: + raise ValueError("decoder_input_ids cannot be a meta tensor.") + if decoder_input_ids.ndim != 2 or decoder_input_ids.shape[1] == 0: + raise ValueError("decoder_input_ids must have non-empty shape (batch, sequence).") + if decoder_input_ids.dtype not in {torch.int32, torch.int64}: + raise TypeError("decoder_input_ids must use torch.int32 or torch.int64.") + if decoder_attention_mask is not None: + if not isinstance(decoder_attention_mask, Tensor): + raise TypeError("decoder_attention_mask must be a tensor.") + if decoder_attention_mask.is_meta: + raise ValueError("decoder_attention_mask cannot be a meta tensor.") + if decoder_attention_mask.is_complex() or not bool( + torch.isfinite(decoder_attention_mask).all() + ): + raise ValueError("decoder_attention_mask must contain finite binary values.") + if not bool(((decoder_attention_mask == 0) | (decoder_attention_mask == 1)).all()): + raise ValueError("decoder_attention_mask must contain finite binary values.") + pooling_names = ( + (("mean",) if not full_embeddings else ()) + if pooling is None + else ((pooling,) if isinstance(pooling, str) else tuple(pooling)) + ) + if full_embeddings and pooling is not None: + raise ValueError("full_embeddings=True cannot be combined with pooling.") + if not full_embeddings and not pooling_names: + raise ValueError("pooling is required unless full_embeddings=True.") + pooler = Pooler(pooling_names) if pooling_names else None + + if batch_size <= 0: + raise ValueError("batch_size must be positive.") + if format == "pth" or (output is not None and Path(output).suffix.lower() == ".pth"): + raise ValueError("Writing pickle-based .pth embeddings is not supported.") + if format not in _SUPPORTED_STORAGE_FORMATS: + raise ValueError("format must be 'safetensors' or 'sqlite'.") + if max_length is not None and max_length <= 0: + raise ValueError("max_length must be positive when provided.") + if max_tokens_per_batch is not None and max_tokens_per_batch <= 0: + raise ValueError("max_tokens_per_batch must be positive when provided.") + if not isinstance(dtype, (torch.dtype, type(None))): + raise TypeError("dtype must be a torch.dtype or None.") + if batch_window_size is not None and batch_window_size <= 0: + raise ValueError("batch_window_size must be positive when provided.") + if _embedding_batch_fn is not None and not callable(_embedding_batch_fn): + raise TypeError("_embedding_batch_fn must be callable when provided.") + if _embedding_batch_fn is not None and _embedding_batch_identity is None: + raise ValueError( + "_embedding_batch_identity is required with _embedding_batch_fn so persisted " + "runs bind the family-specific embedding behavior." + ) + if _embedding_batch_identity is not None and not isinstance(_embedding_batch_identity, Mapping): + raise TypeError("_embedding_batch_identity must be a mapping when provided.") + if isinstance(_allowed_unsupported_pooling, (str, bytes)) or not isinstance( + _allowed_unsupported_pooling, Sequence + ): + raise TypeError("_allowed_unsupported_pooling must be a sequence of pooler names.") + if not all(isinstance(name, str) for name in _allowed_unsupported_pooling): + raise TypeError("_allowed_unsupported_pooling must contain only strings.") + allowed_unsupported_pooling = frozenset(_allowed_unsupported_pooling) + if allowed_unsupported_pooling and _embedding_batch_fn is None: + raise ValueError( + "_allowed_unsupported_pooling is only valid with a family-specific _embedding_batch_fn." + ) + resolved_batch_window_size = ( + batch_size * _DEFAULT_BATCH_WINDOW_MULTIPLIER + if batch_window_size is None + else batch_window_size + ) + if resolved_batch_window_size < batch_size: + raise ValueError("batch_window_size must be at least batch_size.") + records = _normalize_inputs(inputs, disk_backed=output is not None) + _validate_untruncated_lengths( + records, + max_length=max_length, + truncate=truncate, + ) + pooling_names = ( + (("mean",) if not full_embeddings else ()) + if pooling is None + else ((pooling,) if isinstance(pooling, str) else tuple(pooling)) + ) + if full_embeddings: + if pooling is not None: + raise ValueError("full_embeddings=True cannot be combined with pooling.") + elif not pooling_names: + raise ValueError("pooling is required unless full_embeddings=True.") + store_all_hidden_states = bool(model_kwargs.get("store_all_hidden_states", False)) + if store_all_hidden_states and not full_embeddings: + raise ValueError("store_all_hidden_states=True requires full_embeddings=True.") + + unsupported = set(getattr(model, "embedding_unsupported_pooling", ())) + unknown_pooling_overrides = allowed_unsupported_pooling.difference(unsupported) + if unknown_pooling_overrides: + raise ValueError( + "_allowed_unsupported_pooling may only override poolers declared unsupported " + f"by the model; unknown overrides: {sorted(unknown_pooling_overrides)}." + ) + unsupported.difference_update(allowed_unsupported_pooling) + requested_unsupported = unsupported.intersection(pooling_names) + if requested_unsupported: + raise ValueError( + f"{model.__class__.__name__} does not support pooling operations " + f"{sorted(requested_unsupported)}." + ) + + # Constructing the pooler validates names and duplicate operations before + # any checkpoint hashing, tokenization, or inference occurs. + pooler = Pooler(pooling_names) if pooling_names else None + embedding_context, normalized_decoder_inputs = _embedding_context( + model, + records, + hidden_state_source=hidden_state_source, + decoder_inputs=decoder_inputs, + decoder_input_ids=decoder_input_ids, + decoder_attention_mask=decoder_attention_mask, + model_kwargs=model_kwargs, + ) + if _embedding_batch_identity is not None: + embedding_context["family_adapter"] = _fingerprint_jsonable(_embedding_batch_identity) + if allowed_unsupported_pooling: + embedding_context["family_adapter_pooling_override"] = sorted( + allowed_unsupported_pooling + ) + + # A pending automatic attention request settles here, inside the caller's + # autocast context, so the fingerprint records the backend that executes. + attention_resolution = getattr(model, "attention_resolution", None) + if attention_resolution is not None and attention_resolution.deferred: + model.resolve_attn_implementation() + + tokenizer_metadata = _tokenizer_metadata(model, tokenizer) + ( + input_fingerprint, + run_fingerprint, + resolved_model_state_fingerprint, + model_state_fingerprint_source, + ) = _run_fingerprint( + model, + records, + pooling=pooling_names, + full_embeddings=full_embeddings, + max_length=max_length, + truncate=truncate, + dtype=dtype, + model_kwargs=model_kwargs, + tokenizer_metadata=tokenizer_metadata, + model_state_fingerprint=model_state_fingerprint, + persist_output=output is not None, + embedding_context=embedding_context, + batch_size=batch_size, + batch_window_size=resolved_batch_window_size, + max_tokens_per_batch=max_tokens_per_batch, + ) + destination = EmbeddingOutput( + records, + output=output, + format=format, + resume=resume, + shard_size=shard_size, + run_fingerprint=run_fingerprint, + input_fingerprint=input_fingerprint, + model_state_fingerprint=resolved_model_state_fingerprint, + model_state_fingerprint_source=model_state_fingerprint_source, + pooler=pooler, + pooling_names=pooling_names, + ) + if destination.completed is not None: + return destination.completed + + attention_backend = _attention_backend(model) + executor = BatchExecutor( + model=model, + batch_size=batch_size, + max_tokens_per_batch=max_tokens_per_batch, + max_length=max_length, + truncate=truncate, + model_kwargs=model_kwargs, + hidden_state_source=hidden_state_source, + normalized_decoder_inputs=normalized_decoder_inputs, + decoder_input_ids=decoder_input_ids, + decoder_attention_mask=decoder_attention_mask, + _embedding_batch_fn=_embedding_batch_fn, + tokenizer=tokenizer, + store_all_hidden_states=store_all_hidden_states, + full_embeddings=full_embeddings, + dtype=dtype, + pooler=pooler, + attention_backend=attention_backend, + need_attentions="parti" in pooling_names, + ) + pool_slices = destination.pool_slices + with _temporary_eval(model), torch.inference_mode(): + for window_start in range( + destination.start_position, len(records), resolved_batch_window_size + ): + window_stop = min(window_start + resolved_batch_window_size, len(records)) + window_records = records[window_start:window_stop] + if not isinstance(window_records, Sequence): + raise RuntimeError("The immutable embedding spool returned a non-sequence window.") + new_records, pool_slices = executor.run_window( + window_records, window_start=window_start + ) + destination.append(window_start, new_records) + software_versions = identity._software_versions() - projection = getattr(model, "embedding_projection", None) - resolved_layer = getattr( - model, - "embedding_layer", - model_kwargs.get("hidden_state_index", -1), - ) - token_policy = getattr( - model, - "embedding_token_policy", - { - "unit": "residue", - "include": ["biological residues"], - "exclude": [ - "BOS", - "EOS", - "padding", - "chain delimiters", - "non-protein tokens", - ], - }, - ) - model_identity = _model_identity_metadata(model) - metadata: dict[str, Any] = { - "format_version": 1, - "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, - "run_fingerprint": run_fingerprint, - "input_fingerprint": input_fingerprint, - "model_state_fingerprint": resolved_model_state_fingerprint, - "model_state_fingerprint_source": model_state_fingerprint_source, - "model_class": f"{model.__class__.__module__}.{model.__class__.__qualname__}", - **model_identity, - "dtype": str(dtype).removeprefix("torch.") if dtype is not None else "model", - "attention_backend": attention_backend, - "attention_kernel": _attention_kernel_metadata(attention_backend), - "layer": resolved_layer, - "projection": projection, - "esmc_source": getattr(model, "_esmc_source", None), - "esmc_revision": getattr(model, "_esmc_source_revision", None), - "esmc_files": getattr(model, "_esmc_source_files", None), - "token_policy": token_policy, - "tokenizer": tokenizer_metadata, - **embedding_context, - "pooling": list(pooling_names), - "pool_slices": pool_slices, - "full_embeddings": full_embeddings, - "max_length": max_length, - "truncate": truncate, - "truncation": {"enabled": truncate, "max_length": max_length}, - "batching": { - "batch_size": batch_size, - "batch_window_size": resolved_batch_window_size, - "max_tokens_per_batch": max_tokens_per_batch, - "input_storage": ("disk-spool" if isinstance(records, _InputSpool) else "memory"), - "ordering": "bounded-length-bucketed-stable-output", - "resume_commit_granularity": ( - "not-applicable" - if output is None - else "batch-window" - if format == "sqlite" - else "shard-flush" - ), - }, - "residue_mask_policy": "biological-residues-only", - "record_count": len(records), - "descriptor_index": ( - "memory-metadata" - if output is None - else "sqlite-records" - if format == "sqlite" - else "safetensors-generation-index" - ), - "storage_format": format if output is not None else "memory", - "software": software_versions, - "execution": _execution_identity_metadata(model), - "adapter": _adapter_identity_metadata(model), - "torch_version": software_versions["torch"], - "transformers_version": software_versions["transformers"], - "complete": True, - } - if destination.output_descriptors is not None: - metadata["outputs"] = destination.output_descriptors - metadata["tensor_hashes"] = [item["sha256"] for item in destination.output_descriptors] - status = getattr(model, "esmc_precision_status", None) - if status is not None: - metadata["esmc_precision"] = status.as_dict() if hasattr(status, "as_dict") else status - return destination.finish(metadata) - - -class EmbeddingMixin: - """Small delegation mixin shared by FastPLMs model classes.""" - - def embed_dataset(self, inputs: Any, **kwargs: Any) -> EmbeddingResult: - return embed_dataset(self, inputs, **kwargs) - - -__all__ = [ - "EmbeddingMixin", - "embed_dataset", - "iter_fasta", - "parse_fasta", - "select_hidden_state_embeddings", -] + projection = getattr(model, "embedding_projection", None) + resolved_layer = getattr( + model, + "embedding_layer", + model_kwargs.get("hidden_state_index", -1), + ) + token_policy = getattr( + model, + "embedding_token_policy", + { + "unit": "residue", + "include": ["biological residues"], + "exclude": [ + "BOS", + "EOS", + "padding", + "chain delimiters", + "non-protein tokens", + ], + }, + ) + model_identity = _model_identity_metadata(model) + metadata: dict[str, Any] = { + "format_version": 1, + "fingerprint_schema_version": _RUN_FINGERPRINT_SCHEMA_VERSION, + "run_fingerprint": run_fingerprint, + "input_fingerprint": input_fingerprint, + "model_state_fingerprint": resolved_model_state_fingerprint, + "model_state_fingerprint_source": model_state_fingerprint_source, + "model_class": f"{model.__class__.__module__}.{model.__class__.__qualname__}", + **model_identity, + "dtype": str(dtype).removeprefix("torch.") if dtype is not None else "model", + "attention_backend": attention_backend, + "attention_kernel": _attention_kernel_metadata(attention_backend), + "layer": resolved_layer, + "projection": projection, + "esmc_source": getattr(model, "_esmc_source", None), + "esmc_revision": getattr(model, "_esmc_source_revision", None), + "esmc_files": getattr(model, "_esmc_source_files", None), + "token_policy": token_policy, + "tokenizer": tokenizer_metadata, + **embedding_context, + "pooling": list(pooling_names), + "pool_slices": pool_slices, + "full_embeddings": full_embeddings, + "max_length": max_length, + "truncate": truncate, + "truncation": {"enabled": truncate, "max_length": max_length}, + "batching": { + "batch_size": batch_size, + "batch_window_size": resolved_batch_window_size, + "max_tokens_per_batch": max_tokens_per_batch, + "input_storage": ("disk-spool" if isinstance(records, _InputSpool) else "memory"), + "ordering": "bounded-length-bucketed-stable-output", + "resume_commit_granularity": ( + "not-applicable" + if output is None + else "batch-window" + if format == "sqlite" + else "shard-flush" + ), + }, + "residue_mask_policy": "biological-residues-only", + "record_count": len(records), + "descriptor_index": ( + "memory-metadata" + if output is None + else "sqlite-records" + if format == "sqlite" + else "safetensors-generation-index" + ), + "storage_format": format if output is not None else "memory", + "software": software_versions, + "execution": _execution_identity_metadata(model), + "adapter": _adapter_identity_metadata(model), + "torch_version": software_versions["torch"], + "transformers_version": software_versions["transformers"], + "complete": True, + } + if destination.output_descriptors is not None: + metadata["outputs"] = destination.output_descriptors + metadata["tensor_hashes"] = [item["sha256"] for item in destination.output_descriptors] + status = getattr(model, "esmc_precision_status", None) + if status is not None: + metadata["esmc_precision"] = status.as_dict() if hasattr(status, "as_dict") else status + return destination.finish(metadata) + + +class EmbeddingMixin: + """Small delegation mixin shared by FastPLMs model classes.""" + + def embed_dataset(self, inputs: Any, **kwargs: Any) -> EmbeddingResult: + return embed_dataset(self, inputs, **kwargs) + + +__all__ = [ + "EmbeddingMixin", + "embed_dataset", + "iter_fasta", + "parse_fasta", + "select_hidden_state_embeddings", +] diff --git a/fastplms/embeddings/storage.py b/fastplms/embeddings/storage.py index 7fc4d1d02284bd5ccf1f7a0e330fba0d4a8449f3..56091fe03c3e2ac98422ead1d94b79286433a1f0 100644 --- a/fastplms/embeddings/storage.py +++ b/fastplms/embeddings/storage.py @@ -9,6 +9,7 @@ import sqlite3 import struct import numpy as np import torch + from bisect import bisect_right from collections.abc import Iterable, Iterator, Sequence from pathlib import Path diff --git a/fastplms/models/_esm_rotary.py b/fastplms/models/_esm_rotary.py index 2c6068210e3d1f09976eccc7ac39cbed5ccb3da4..bf5ca5244e0c0f3aa466a22e00bbe06b2301f0aa 100644 --- a/fastplms/models/_esm_rotary.py +++ b/fastplms/models/_esm_rotary.py @@ -9,6 +9,7 @@ Transformers implementation. from __future__ import annotations import torch + from torch import nn diff --git a/fastplms/models/classification_probe.py b/fastplms/models/classification_probe.py index 61ab5d77ddcadbdc87e3b7d200f4541b28389a3e..1533d2387caeb13a9336458f8478d305c3a4bb17 100644 --- a/fastplms/models/classification_probe.py +++ b/fastplms/models/classification_probe.py @@ -3,9 +3,9 @@ from __future__ import annotations import math -from typing import Any - import torch + +from typing import Any from torch import nn from torch.nn import functional as F from transformers.modeling_outputs import ( @@ -14,6 +14,7 @@ from transformers.modeling_outputs import ( TokenClassifierOutput, ) + try: from fastplms.attention import ( AttentionBackend, @@ -145,7 +146,7 @@ def token_classification_loss( if problem_type == "regression": targets = labels.to(logits.dtype) if num_labels == 1 and targets.ndim == logits.ndim - 1: - targets = targets.unsqueeze(-1) + targets = targets.unsqueeze(-1) # (..., 1), matching single-target logits if targets.shape != logits.shape: raise ValueError( "Token regression labels must match logits, except that the final " @@ -210,7 +211,7 @@ class ProbeSelfAttention(nn.Module): sequence_length, self.num_heads, self.head_size, - ).transpose(1, 2) + ).transpose(1, 2) # (b, h, l, d_h) def forward( self, @@ -220,11 +221,11 @@ class ProbeSelfAttention(nn.Module): output_attentions: bool, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, sequence_length, _ = hidden_states.shape - query, key, value = self.qkv(hidden_states).chunk(3, dim=-1) - query = self._reshape(query) - key = self._reshape(key) - value = self._reshape(value) - query, key = self.rotary(query, key) + query, key, value = self.qkv(hidden_states).chunk(3, dim=-1) # each (b, l, d) + query = self._reshape(query) # (b, h, l, d_h) + key = self._reshape(key) # (b, h, l, d_h) + value = self._reshape(value) # (b, h, l, d_h) + query, key = self.rotary(query, key) # each (b, h, l, d_h) if output_attentions and self.backend != AttentionBackend.EAGER: raise ValueError( f"output_attentions=True is unavailable for {self.backend.value!r}; " @@ -241,11 +242,11 @@ class ProbeSelfAttention(nn.Module): dropout = self.dropout if self.training else 0.0 attention_weights = None if self.backend == AttentionBackend.EAGER: - scores = query @ key.transpose(-2, -1) / math.sqrt(self.head_size) + scores = query @ key.transpose(-2, -1) / math.sqrt(self.head_size) # (b, h, l, l) if attention_mask_4d is not None: - scores = scores.masked_fill(~attention_mask_4d, float("-inf")) - attention_weights = scores.softmax(dim=-1) - context = F.dropout(attention_weights, p=dropout, training=self.training) @ value + scores = scores.masked_fill(~attention_mask_4d, float("-inf")) # (b, h, l, l) + attention_weights = scores.softmax(dim=-1) # (b, h, l, l) + context = F.dropout(attention_weights, p=dropout, training=self.training) @ value # (b, h, l, d_h) elif self.backend == AttentionBackend.SDPA: context = F.scaled_dot_product_attention( query, @@ -253,7 +254,7 @@ class ProbeSelfAttention(nn.Module): value, attn_mask=attention_mask_4d, dropout_p=dropout, - ) + ) # (b, h, l, d_h) elif self.backend == AttentionBackend.FLEX_ATTENTION: if flex_attention is None: raise RuntimeError("'flex_attention' was requested but is unavailable.") @@ -272,15 +273,15 @@ class ProbeSelfAttention(nn.Module): block_mask=flex_block_mask, scale=1.0 / math.sqrt(self.head_size), kernel_options={"PRESCALE_QK": True, "BLOCK_N": 32}, - ) + ) # (b, h, l, d_h) else: raise AssertionError(f"Unhandled attention backend {self.backend.value!r}.") context = context.transpose(1, 2).contiguous().view( batch_size, sequence_length, self.hidden_size, - ) - return self.output(context), attention_weights + ) # (b, l, d) + return self.output(context), attention_weights # (b, l, d), optional (b, h, l, l) class ProteinTransformerProbe(nn.Module): @@ -451,7 +452,7 @@ class SequenceClassificationProbe(_ClassificationProbe): embeddings.shape[:2], device=embeddings.device, dtype=torch.bool, - ) + ) # (b, l) outputs = self._forward_transformer( embeddings, attention_mask, @@ -460,8 +461,8 @@ class SequenceClassificationProbe(_ClassificationProbe): ) if self.pooler is None: raise AssertionError("Sequence classification requires a configured pooler.") - pooled = self.pooler(outputs.last_hidden_state, attention_mask) - logits = self.classifier(pooled) + pooled = self.pooler(outputs.last_hidden_state, attention_mask) # (b, d) + logits = self.classifier(pooled) # (b, num_labels) loss = None if labels is not None: problem_type = resolve_problem_type(self.config, labels, num_labels=self.num_labels) diff --git a/fastplms/models/esm_plusplus/modeling_esm_plusplus.py b/fastplms/models/esm_plusplus/modeling_esm_plusplus.py index bb13b80e661e870706906aa132f61c4ee86f39ed..b57575d9576b266ccfb318c2a2954bbceac74605 100644 --- a/fastplms/models/esm_plusplus/modeling_esm_plusplus.py +++ b/fastplms/models/esm_plusplus/modeling_esm_plusplus.py @@ -6,15 +6,15 @@ import importlib import importlib.metadata import math import os +import torch +import torch.nn as nn +import torch.nn.functional as F + from collections.abc import Sequence from contextlib import contextmanager from dataclasses import asdict, dataclass from functools import partial from typing import Any, ClassVar - -import torch -import torch.nn as nn -import torch.nn.functional as F from einops import rearrange from tokenizers import Tokenizer from tokenizers.models import BPE @@ -378,7 +378,7 @@ class RotaryEmbedding(torch.nn.Module): inv_freq = self._compute_inv_freq(buffer_device) self._clear_cache() self.register_buffer("inv_freq", inv_freq, persistent=False) - arange = torch.arange(0, self.dim, 2, device=buffer_device, dtype=torch.float32) + arange = torch.arange(0, self.dim, 2, device=buffer_device, dtype=torch.float32) # (d / 2,) scale = ( (arange + 0.4 * self.dim) / (1.4 * self.dim) if self.scale_base is not None else None ) @@ -446,18 +446,18 @@ class RotaryEmbedding(torch.nn.Module): cos_angles = torch.cos(angles) # (l, d / 2) sin_angles = torch.sin(angles) # (l, d / 2) if self.scale is None: - self._cos_cached = cos_angles.to(dtype) - self._sin_cached = sin_angles.to(dtype) - self._cos_full_cached = torch.cat((self._cos_cached, self._cos_cached), dim=-1) - self._sin_full_cached = torch.cat((self._sin_cached, self._sin_cached), dim=-1) + self._cos_cached = cos_angles.to(dtype) # (l, d / 2) + self._sin_cached = sin_angles.to(dtype) # (l, d / 2) + self._cos_full_cached = torch.cat((self._cos_cached, self._cos_cached), dim=-1) # (l, d) + self._sin_full_cached = torch.cat((self._sin_cached, self._sin_cached), dim=-1) # (l, d) return centered_positions = ( torch.arange(seqlen, dtype=self.scale.dtype, device=self.scale.device) - seqlen // 2 - ) / self.scale_base - scale = self.scale ** centered_positions.unsqueeze(-1) - self._cos_cached = (cos_angles * scale).to(dtype) - self._sin_cached = (sin_angles * scale).to(dtype) + ) / self.scale_base # (l,) + scale = self.scale ** centered_positions.unsqueeze(-1) # (l, d / 2) + self._cos_cached = (cos_angles * scale).to(dtype) # (l, d / 2) + self._sin_cached = (sin_angles * scale).to(dtype) # (l, d / 2) self._cos_k_cached = (cos_angles / scale).to(dtype) self._sin_k_cached = (sin_angles / scale).to(dtype) @@ -574,12 +574,12 @@ class MultiHeadAttention(nn.Module): ) -> tuple[torch.Tensor, torch.Tensor | None, list[torch.Tensor] | None]: # x: (b, l, d) qkv = self.layernorm_qkv(x) # (b, l, 3 * d) - query_sequence, key_sequence, value_sequence = torch.chunk(qkv, 3, dim=-1) + query_sequence, key_sequence, value_sequence = torch.chunk(qkv, 3, dim=-1) # each (b, l, d) query_sequence, key_sequence = ( self.q_ln(query_sequence).to(query_sequence.dtype), self.k_ln(key_sequence).to(query_sequence.dtype), - ) - query_sequence, key_sequence = self._apply_rotary(query_sequence, key_sequence) + ) # each (b, l, d) + query_sequence, key_sequence = self._apply_rotary(query_sequence, key_sequence) # each (b, l, d) query_heads, key_heads, value_heads = map( self.reshaper, (query_sequence, key_sequence, value_sequence) ) # each (b, h, l, d_h) @@ -596,7 +596,7 @@ class MultiHeadAttention(nn.Module): flash_padding_layout=flash_padding_layout, ) - output = self.out_proj(attn_output) + output = self.out_proj(attn_output) # (b, l, d) return output, attn_weights, s_max def _attn( @@ -682,9 +682,9 @@ class MultiHeadAttention(nn.Module): attention_mask_2d: torch.Tensor | None = None, flash_padding_layout: FlashPaddingLayout | None = None, ) -> tuple[torch.Tensor, None]: - query_tokens = query_heads.transpose(1, 2).contiguous() - key_tokens = key_heads.transpose(1, 2).contiguous() - value_tokens = value_heads.transpose(1, 2).contiguous() + query_tokens = query_heads.transpose(1, 2).contiguous() # (b, l, h, d_h) + key_tokens = key_heads.transpose(1, 2).contiguous() # (b, l, h, d_h) + value_tokens = value_heads.transpose(1, 2).contiguous() # (b, l, h, d_h) attn_output = kernels_flash_attention_func( query_states=query_tokens, key_states=key_tokens, @@ -693,8 +693,8 @@ class MultiHeadAttention(nn.Module): causal=False, implementation=self.attn_backend.value, padding_layout=flash_padding_layout, - ) - return rearrange(attn_output, "b s h d -> b s (h d)"), None + ) # (b, l, h, d_h) + return rearrange(attn_output, "b s h d -> b s (h d)"), None # (b, l, h * d_h), None def _flex_attn( self, @@ -1015,11 +1015,11 @@ class TransformerStack(nn.Module): # finite without allowing their states to enter residue attention. attention_mask_4d = ( mask_pattern[:, None, :, None] == mask_pattern[:, None, None, :] - ) + ) # (b, 1, l, l) else: attention_mask_4d = ( mask_pattern.unsqueeze(-1) == mask_pattern.unsqueeze(-2) - ).unsqueeze(1) + ).unsqueeze(1) # (b, 1, l, l) backend = ( resolve_attention_backend_for_call( self.attention_backend, @@ -1837,7 +1837,7 @@ class ESMplusplusForSequenceClassification(ESMplusplusForMaskedLM, EmbeddingMixi inputs_embeds.shape[:2], dtype=torch.bool, device=inputs_embeds.device, - ) + ) # (b, l) output = super().forward( input_ids=input_ids, diff --git a/fastplms/models/esmfold2/__init__.py b/fastplms/models/esmfold2/__init__.py index 6868ae5312cab54842dd34ff5a6c649d4974a7bd..1a047442b02a6ba8cff5fe8b385ae1dfe4369bca 100644 --- a/fastplms/models/esmfold2/__init__.py +++ b/fastplms/models/esmfold2/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations from importlib import import_module from typing import TYPE_CHECKING, Any + if TYPE_CHECKING: from .configuration_esmfold2 import ESMFold2Config as ESMFold2Config from .modeling_esmfold2 import ESMFold2Model as ESMFold2Model diff --git a/fastplms/models/esmfold2/configuration_esmfold2.py b/fastplms/models/esmfold2/configuration_esmfold2.py index 2a5901e5619567bfdcb3061237aab5964cfdae7a..7eff5d3b0ee7f583c066835fe211ef5250d9f203 100644 --- a/fastplms/models/esmfold2/configuration_esmfold2.py +++ b/fastplms/models/esmfold2/configuration_esmfold2.py @@ -18,7 +18,6 @@ from __future__ import annotations from dataclasses import asdict, dataclass, field from typing import Any, TypeVar, cast - from transformers.configuration_utils import PretrainedConfig from fastplms.attention import canonical_checkpoint_attention_backend diff --git a/fastplms/models/esmfold2/embedding.py b/fastplms/models/esmfold2/embedding.py index e22160f1f45ae92c08ebb42d81409f59874d036e..c9ddb7f7469bd8f4bc63f220a41de98be0da00bf 100644 --- a/fastplms/models/esmfold2/embedding.py +++ b/fastplms/models/esmfold2/embedding.py @@ -3,6 +3,7 @@ from __future__ import annotations import torch + from typing import Any, ClassVar from torch import Tensor diff --git a/fastplms/models/esmfold2/esmfold2_affine3d.py b/fastplms/models/esmfold2/esmfold2_affine3d.py index 17c47f2fbbbd90bf8006b58f0b0a537499b4fd23..387b508d1f075bfdda78c07c16a9ad17a9bf063a 100644 --- a/fastplms/models/esmfold2/esmfold2_affine3d.py +++ b/fastplms/models/esmfold2/esmfold2_affine3d.py @@ -2,10 +2,11 @@ from __future__ import annotations +import torch + +from collections.abc import Callable from dataclasses import dataclass from typing import Any, Self - -import torch from torch.nn import functional as F from .esmfold2_misc import fp32_autocast_context @@ -19,23 +20,26 @@ def _index_tuple(index: Any) -> tuple[Any, ...]: def _sqrt_subgradient(values: torch.Tensor) -> torch.Tensor: """Square root with a zero subgradient for non-positive inputs.""" + # values: arbitrary shape; every element keeps its position. - result = torch.zeros_like(values) - positive = values > 0 - result[positive] = torch.sqrt(values[positive]) - return result + result = torch.zeros_like(values) # values.shape + positive = values > 0 # values.shape + result[positive] = torch.sqrt(values[positive]) # (n_positive,) selected elements + return result # values.shape def _quat_invert(quaternion: torch.Tensor) -> torch.Tensor: - conjugate_sign = torch.tensor([1, -1, -1, -1], device=quaternion.device) - return quaternion * conjugate_sign + # quaternion: (..., 4), real component first. + conjugate_sign = torch.tensor([1, -1, -1, -1], device=quaternion.device) # (4,) + return quaternion * conjugate_sign # (..., 4) def _quat_mult(left: torch.Tensor, right: torch.Tensor) -> torch.Tensor: """Hamilton product for real-first quaternion tensors.""" + # left, right: (..., 4); their leading dimensions must broadcast. - aw, ax, ay, az = torch.unbind(left, -1) - bw, bx, by, bz = torch.unbind(right, -1) + aw, ax, ay, az = torch.unbind(left, -1) # each left.shape[:-1] + bw, bx, by, bz = torch.unbind(right, -1) # each right.shape[:-1] return torch.stack( ( aw * bw - ax * bx - ay * by - az * bz, @@ -44,7 +48,7 @@ def _quat_mult(left: torch.Tensor, right: torch.Tensor) -> torch.Tensor: aw * bz + ax * by - ay * bx + az * bw, ), -1, - ) + ) # (..., 4), broadcast leading dimensions def _quat_rotation( @@ -52,9 +56,10 @@ def _quat_rotation( points: torch.Tensor, ) -> torch.Tensor: """Rotate points using normalized real-first quaternions.""" + # quaternion: (..., 4); points: (..., 3); leading dimensions broadcast. - aw, ax, ay, az = torch.unbind(quaternion, -1) - bx, by, bz = torch.unbind(points, -1) + aw, ax, ay, az = torch.unbind(quaternion, -1) # each quaternion.shape[:-1] + bx, by, bz = torch.unbind(points, -1) # each points.shape[:-1] product = torch.stack( ( -ax * bx - ay * by - az * bz, @@ -63,8 +68,8 @@ def _quat_rotation( aw * bz + ax * by - ay * bx, ), -1, - ) - return _quat_mult(product, _quat_invert(quaternion))[..., 1:] + ) # (..., 4), broadcast leading dimensions + return _quat_mult(product, _quat_invert(quaternion))[..., 1:] # (..., 3) def _graham_schmidt( @@ -73,17 +78,18 @@ def _graham_schmidt( eps: float = 1e-12, ) -> torch.Tensor: """Construct a right-handed orthonormal frame from two directions.""" + # x_axis, xy_plane: (..., 3); ... contains arbitrary frame batch axes. with fp32_autocast_context(x_axis.device.type): - e1 = xy_plane - denominator = torch.sqrt((x_axis**2).sum(dim=-1, keepdim=True) + eps) - x_axis = x_axis / denominator - projection = (x_axis * e1).sum(dim=-1, keepdim=True) - e1 = e1 - x_axis * projection - denominator = torch.sqrt((e1**2).sum(dim=-1, keepdim=True) + eps) - e1 = e1 / denominator - e2 = torch.cross(x_axis, e1, dim=-1) - return torch.stack([x_axis, e1, e2], dim=-1) + e1 = xy_plane # (..., 3) + denominator = torch.sqrt((x_axis**2).sum(dim=-1, keepdim=True) + eps) # (..., 1) + x_axis = x_axis / denominator # (..., 3) + projection = (x_axis * e1).sum(dim=-1, keepdim=True) # (..., 1) + e1 = e1 - x_axis * projection # (..., 3) + denominator = torch.sqrt((e1**2).sum(dim=-1, keepdim=True) + eps) # (..., 1) + e1 = e1 / denominator # (..., 3) + e2 = torch.cross(x_axis, e1, dim=-1) # (..., 3) + return torch.stack([x_axis, e1, e2], dim=-1) # (..., 3, 3) class Rotation: @@ -129,6 +135,7 @@ class Rotation: @classmethod def _from_tensor(cls, tensor: torch.Tensor) -> Self: + # tensor: (..., r), where r is the concrete rotation representation width. return cls(tensor) # type: ignore[call-arg] def to(self, **kwargs) -> Self: @@ -137,15 +144,17 @@ class Rotation: def detach(self, *args, **kwargs) -> Self: return self._from_tensor(self.tensor.detach(**kwargs)) - def tensor_apply(self, func) -> Self: - transformed = [func(component) for component in self.tensor.unbind(dim=-1)] - return self._from_tensor(torch.stack(transformed, dim=-1)) + def tensor_apply(self, func: Callable[[torch.Tensor], torch.Tensor]) -> Self: + # self.tensor: (..., r); func receives one (...) component and determines its output shape. + transformed = [func(component) for component in self.tensor.unbind(dim=-1)] # one func-shaped array per rotation component + return self._from_tensor(torch.stack(transformed, dim=-1)) # rotation tensor: (*func_output_shape, n_components) class RotationQuat(Rotation): """A rotation represented by a real-first quaternion.""" - def __init__(self, quats: torch.Tensor, normalized: bool = False): + def __init__(self, quats: torch.Tensor, normalized: bool = False) -> None: + # quats: (..., 4); ... is the rotation batch shape. if not isinstance(quats, torch.Tensor): raise TypeError("quats must be a Torch tensor.") if quats.ndim == 0 or quats.shape[-1] != 4: @@ -156,32 +165,32 @@ class RotationQuat(Rotation): raise TypeError("normalized must be a boolean.") self._normalized = normalized if normalized: - quats = F.normalize(quats.to(torch.float32), dim=-1) - self._quats = quats.where(quats[..., :1] >= 0, -quats) + quats = F.normalize(quats.to(torch.float32), dim=-1) # (..., 4) + self._quats = quats.where(quats[..., :1] >= 0, -quats) # (..., 4) else: - self._quats = quats.to(torch.float32) + self._quats = quats.to(torch.float32) # (..., 4) @property def tensor(self) -> torch.Tensor: - return self._quats + return self._quats # (..., 4) @property def shape(self) -> torch.Size: return self._quats.shape[:-1] @classmethod - def identity(cls, shape, **tensor_kwargs) -> RotationQuat: - quaternions = torch.ones((*shape, 4), **tensor_kwargs) - selector = torch.tensor([1, 0, 0, 0], device=quaternions.device) - return cls(quaternions * selector) + def identity(cls, shape: tuple[int, ...], **tensor_kwargs) -> RotationQuat: + quaternions = torch.ones((*shape, 4), **tensor_kwargs) # (*shape, 4) + selector = torch.tensor([1, 0, 0, 0], device=quaternions.device) # (4,) + return cls(quaternions * selector) # quaternion tensor: (*shape, 4) @classmethod - def random(cls, shape, **tensor_kwargs) -> RotationQuat: - return cls(torch.randn((*shape, 4), **tensor_kwargs), normalized=True) + def random(cls, shape: tuple[int, ...], **tensor_kwargs) -> RotationQuat: + return cls(torch.randn((*shape, 4), **tensor_kwargs), normalized=True) # quaternion tensor: (*shape, 4) def __getitem__(self, idx: Any) -> RotationQuat: indices = _index_tuple(idx) - return RotationQuat(self._quats[(*indices, slice(None))]) + return RotationQuat(self._quats[(*indices, slice(None))]) # quaternion tensor: (*indexed_shape, 4) def normalized(self) -> RotationQuat: if self._normalized: @@ -192,9 +201,9 @@ class RotationQuat(Rotation): return self def as_matrix(self) -> RotationMatrix: - quaternion = self.normalized().tensor - r, i, j, k = torch.unbind(quaternion, -1) - scale = 2.0 / torch.linalg.norm(quaternion, dim=-1) + quaternion = self.normalized().tensor # (..., 4) + r, i, j, k = torch.unbind(quaternion, -1) # each (...) + scale = 2.0 / torch.linalg.norm(quaternion, dim=-1) # (...) elements = torch.stack( ( 1 - scale * (j * j + k * k), @@ -208,54 +217,56 @@ class RotationQuat(Rotation): 1 - scale * (i * i + j * j), ), -1, - ) - return RotationMatrix(elements.reshape((*quaternion.shape[:-1], 3, 3))) + ) # (..., 9) + return RotationMatrix(elements.reshape((*quaternion.shape[:-1], 3, 3))) # rotation tensor: (..., 3, 3) def compose(self, other: RotationQuat) -> RotationQuat: with fp32_autocast_context(self.device.type): - return RotationQuat(_quat_mult(self._quats, other._quats)) + return RotationQuat(_quat_mult(self._quats, other._quats)) # quaternion tensor: (..., 4), broadcast leading shapes def convert_compose(self, other: Rotation) -> RotationQuat: return self.compose(other.as_quat()) def apply(self, points: torch.Tensor) -> torch.Tensor: - return _quat_rotation(self.normalized()._quats, points) + # points: (..., 3); quaternion and point leading shapes must broadcast. + return _quat_rotation(self.normalized()._quats, points) # (..., 3), broadcast rotation/point leading shapes def invert(self) -> RotationQuat: - return RotationQuat(_quat_invert(self._quats)) + return RotationQuat(_quat_invert(self._quats)) # quaternion tensor: (..., 4) class RotationMatrix(Rotation): """A rotation represented by a dense FP32 matrix.""" - def __init__(self, rots: torch.Tensor): + def __init__(self, rots: torch.Tensor) -> None: + # rots: (..., 9) or (..., 3, 3); ... is the rotation batch shape. if not isinstance(rots, torch.Tensor): raise TypeError("rots must be a Torch tensor.") if rots.ndim > 0 and rots.shape[-1] == 9: - rots = rots.unflatten(-1, (3, 3)) + rots = rots.unflatten(-1, (3, 3)) # (..., 3, 3) if rots.ndim < 2 or rots.shape[-2:] != (3, 3): raise ValueError( "rots must have trailing shape (3, 3) or flattened width 9, got " f"shape {tuple(rots.shape)}." ) - self._rots = rots.to(torch.float32) + self._rots = rots.to(torch.float32) # (..., 3, 3) @property def tensor(self) -> torch.Tensor: - return self._rots.flatten(-2) + return self._rots.flatten(-2) # (..., 9) @property def shape(self) -> torch.Size: return self._rots.shape[:-2] @classmethod - def identity(cls, shape, **tensor_kwargs) -> RotationMatrix: - matrix = torch.eye(3, **tensor_kwargs) - matrix = matrix.view(*(1 for _ in shape), 3, 3) - return cls(matrix.expand(*shape, -1, -1)) + def identity(cls, shape: tuple[int, ...], **tensor_kwargs) -> RotationMatrix: + matrix = torch.eye(3, **tensor_kwargs) # (3, 3) + matrix = matrix.view(*(1 for _ in shape), 3, 3) # (*singleton_shape, 3, 3); one singleton per requested axis + return cls(matrix.expand(*shape, -1, -1)) # rotation tensor: (*shape, 3, 3) @classmethod - def random(cls, shape, **tensor_kwargs) -> RotationMatrix: + def random(cls, shape: tuple[int, ...], **tensor_kwargs) -> RotationMatrix: return RotationQuat.random(shape, **tensor_kwargs).as_matrix() @staticmethod @@ -264,23 +275,24 @@ class RotationMatrix(Rotation): xy_plane: torch.Tensor, eps: float = 1e-12, ) -> RotationMatrix: - return RotationMatrix(_graham_schmidt(x_axis, xy_plane, eps)) + # x_axis, xy_plane: (..., 3). + return RotationMatrix(_graham_schmidt(x_axis, xy_plane, eps)) # rotation tensor: (..., 3, 3) def __getitem__(self, idx: Any) -> RotationMatrix: indices = _index_tuple(idx) - return RotationMatrix(self._rots[(*indices, slice(None), slice(None))]) + return RotationMatrix(self._rots[(*indices, slice(None), slice(None))]) # rotation tensor: (*indexed_shape, 3, 3) def as_matrix(self) -> RotationMatrix: return self def to_3x3(self) -> torch.Tensor: - return self._rots + return self._rots # (..., 3, 3) def as_quat(self, normalize: bool = False) -> RotationQuat: m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind( self._rots.flatten(-2), dim=-1, - ) + ) # each (...) q_abs = _sqrt_subgradient( torch.stack( ( @@ -291,7 +303,7 @@ class RotationMatrix(Rotation): ), dim=-1, ) - ) + ) # (..., 4) products = torch.stack( ( q_abs[..., 0] ** 2, @@ -312,29 +324,30 @@ class RotationMatrix(Rotation): q_abs[..., 3] ** 2, ), dim=-1, - ).unflatten(-1, (4, 4)) - floor = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device) - candidates = products / (2.0 * q_abs[..., None].max(floor)) - best = torch.zeros_like(q_abs, dtype=torch.bool) - best.scatter_(-1, q_abs.argmax(dim=-1, keepdim=True), True) - quaternion = candidates[best, :].reshape(q_abs.shape) - return RotationQuat(quaternion) + ).unflatten(-1, (4, 4)) # (..., 4, 4) + floor = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device) # () + candidates = products / (2.0 * q_abs[..., None].max(floor)) # (..., 4, 4) + best = torch.zeros_like(q_abs, dtype=torch.bool) # (..., 4) + best.scatter_(-1, q_abs.argmax(dim=-1, keepdim=True), True) # (..., 4); selected index shape (..., 1) + quaternion = candidates[best, :].reshape(q_abs.shape) # (..., 4) + return RotationQuat(quaternion) # quaternion tensor: (..., 4) def compose(self, other: RotationMatrix) -> RotationMatrix: with fp32_autocast_context(self.device.type): - return RotationMatrix(self._rots @ other._rots) + return RotationMatrix(self._rots @ other._rots) # rotation tensor: (..., 3, 3), broadcast leading shapes def convert_compose(self, other: Rotation) -> RotationMatrix: return self.compose(other.as_matrix()) def apply(self, points: torch.Tensor) -> torch.Tensor: + # points: (..., 3); matrix leading shapes broadcast with point axes. with fp32_autocast_context(self.device.type): if self._rots.shape[-3] == 1: - return points @ self._rots.transpose(-1, -2).squeeze(-3) - return torch.einsum("...ij,...j", self._rots, points) + return points @ self._rots.transpose(-1, -2).squeeze(-3) # (..., 3), with leading axes set by matmul broadcasting + return torch.einsum("...ij,...j", self._rots, points) # (..., 3), broadcast leading shapes def invert(self) -> RotationMatrix: - return RotationMatrix(self._rots.transpose(-1, -2)) + return RotationMatrix(self._rots.transpose(-1, -2)) # rotation tensor: (..., 3, 3) @dataclass(frozen=True) @@ -378,7 +391,7 @@ class Affine3D: @property def tensor(self) -> torch.Tensor: - return torch.cat((self.rot.tensor, self.trans), dim=-1) + return torch.cat((self.rot.tensor, self.trans), dim=-1) # (..., r + 3); r is 4 for quaternions or 9 for matrices @staticmethod def identity( @@ -409,12 +422,13 @@ class Affine3D: rotation_type: type[Rotation] = RotationMatrix, **tensor_kwargs, ) -> Affine3D: - translation = torch.randn((*shape, 3), **tensor_kwargs).mul(std) - rotation = rotation_type.random(shape, **tensor_kwargs) + translation = torch.randn((*shape, 3), **tensor_kwargs).mul(std) # (*shape, 3) + rotation = rotation_type.random(shape, **tensor_kwargs) # rotation batch shape: shape return Affine3D(trans=translation, rot=rotation) @staticmethod def from_tensor(tensor: torch.Tensor) -> Affine3D: + # tensor: (..., 6/7/12), (..., 3, 4), or (..., 4, 4); ... is frame batch shape. if not isinstance(tensor, torch.Tensor): raise TypeError("tensor must be a Torch tensor.") if tensor.ndim == 0: @@ -426,17 +440,17 @@ class Affine3D: "matrix-form affine tensors must have trailing shape (3, 4) or " f"(4, 4), got {tuple(tensor.shape)}." ) - translation = tensor[..., :3, 3] - rotation: Rotation = RotationMatrix(tensor[..., :3, :3]) + translation = tensor[..., :3, 3] # (..., 3) + rotation: Rotation = RotationMatrix(tensor[..., :3, :3]) # rotation tensor: (..., 3, 3) elif width == 6: - translation = tensor[..., -3:] - rotation = RotationQuat(F.pad(tensor[..., :3], (1, 0), value=1)) + translation = tensor[..., -3:] # (..., 3) + rotation = RotationQuat(F.pad(tensor[..., :3], (1, 0), value=1)) # quaternion tensor: (..., 4) elif width == 7: - translation = tensor[..., -3:] - rotation = RotationQuat(tensor[..., :4]) + translation = tensor[..., -3:] # (..., 3) + rotation = RotationQuat(tensor[..., :4]) # quaternion tensor: (..., 4) elif width == 12: - translation = tensor[..., -3:] - rotation = RotationMatrix(tensor[..., :-3].unflatten(-1, (3, 3))) + translation = tensor[..., -3:] # (..., 3) + rotation = RotationMatrix(tensor[..., :-3].unflatten(-1, (3, 3))) # rotation tensor: (..., 3, 3) else: raise RuntimeError( f"Cannot detect rotation format from {tensor.shape[-1] - 3}-d flat vector" @@ -448,6 +462,7 @@ class Affine3D: translation: torch.Tensor, rotation: torch.Tensor, ) -> Affine3D: + # translation: (..., 3); rotation: (..., 3, 3) or (..., 9). return Affine3D(translation, RotationMatrix(rotation)) @staticmethod @@ -457,13 +472,14 @@ class Affine3D: xy_plane: torch.Tensor, eps: float = 1e-10, ) -> Affine3D: - x_axis = origin - neg_x_axis - plane_direction = xy_plane - origin + # neg_x_axis, origin, xy_plane: (..., 3), with broadcast-compatible leading axes. + x_axis = origin - neg_x_axis # (..., 3) + plane_direction = xy_plane - origin # (..., 3) rotation = RotationMatrix.from_graham_schmidt( x_axis, plane_direction, eps, - ) + ) # rotation tensor: (..., 3, 3) return Affine3D(trans=origin, rot=rotation) @staticmethod @@ -478,7 +494,7 @@ class Affine3D: def __getitem__(self, idx: Any) -> Affine3D: indices = _index_tuple(idx) - translation = self.trans[(*indices, slice(None))] + translation = self.trans[(*indices, slice(None))] # (*indexed_shape, 3) return Affine3D(trans=translation, rot=self.rot[idx]) def to(self, **kwargs) -> Affine3D: @@ -490,9 +506,10 @@ class Affine3D: self.rot.detach(**kwargs), ) - def tensor_apply(self, func) -> Affine3D: - components = [func(value) for value in self.tensor.unbind(dim=-1)] - return Affine3D.from_tensor(torch.stack(components, dim=-1)) + def tensor_apply(self, func: Callable[[torch.Tensor], torch.Tensor]) -> Affine3D: + # self.tensor: (..., r + 3); func maps each (...) component to a common output shape. + components = [func(value) for value in self.tensor.unbind(dim=-1)] # one func-shaped array per affine component + return Affine3D.from_tensor(torch.stack(components, dim=-1)) # affine tensor: (*func_output_shape, r + 3) def as_matrix(self) -> Affine3D: return Affine3D(trans=self.trans, rot=self.rot.as_matrix()) @@ -509,8 +526,8 @@ class Affine3D: autoconvert: bool = False, ) -> Affine3D: compose_rotation = self.rot.convert_compose if autoconvert else self.rot.compose - rotation = compose_rotation(other.rot) - translation = self.rot.apply(other.trans) + self.trans + rotation = compose_rotation(other.rot) # rotation batch shape: broadcast(self.shape, other.shape) + translation = self.rot.apply(other.trans) + self.trans # (..., 3), broadcast transform shapes return Affine3D(trans=translation, rot=rotation) def compose_rotation( @@ -522,28 +539,31 @@ class Affine3D: return Affine3D(trans=self.trans, rot=compose(other)) def scale(self, value: torch.Tensor | float) -> Affine3D: + # value must broadcast with translation (..., 3); rotation shape is retained. return Affine3D(self.trans * value, self.rot) def mask(self, mask: torch.Tensor, with_zero: bool = False) -> Affine3D: + # mask: self.shape; affine tensor: (*self.shape, r + 3). if with_zero: masked = torch.zeros_like(self.tensor).where( mask[..., None], self.tensor, - ) + ) # (..., r + 3) return Affine3D.from_tensor(masked) identity = self.identity( self.shape, rotation_type=type(self.rot), device=self.device, dtype=self.dtype, - ).tensor - return Affine3D.from_tensor(identity.where(mask[..., None], self.tensor)) + ).tensor # (..., r + 3) + return Affine3D.from_tensor(identity.where(mask[..., None], self.tensor)) # affine batch shape: self.shape def apply(self, points: torch.Tensor) -> torch.Tensor: - return self.rot.apply(points) + self.trans + # points: (..., 3); result uses broadcast transform/point leading axes. + return self.rot.apply(points) + self.trans # (..., 3), broadcast transform/point leading shapes def invert(self) -> Affine3D: - rotation = self.rot.invert() + rotation = self.rot.invert() # rotation batch shape: self.shape return Affine3D(trans=-rotation.apply(self.trans), rot=rotation) @@ -551,6 +571,7 @@ def build_affine3d_from_coordinates( coords: torch.Tensor, ) -> tuple[Affine3D, torch.Tensor]: """Build residue frames from X with shape (b, l, 3, 3).""" + # coords: (b, l, 3, 3); final axes are N/CA/C atoms and xyz coordinates. if not isinstance(coords, torch.Tensor): raise TypeError("coords must be a Torch tensor.") @@ -567,39 +588,40 @@ def build_affine3d_from_coordinates( dim=-1, ), dim=-1, - ) + ) # (b, l) def backbone_affine(positions: torch.Tensor) -> Affine3D: - n, ca, c = positions.unbind(dim=-2) - return Affine3D.from_graham_schmidt(c, ca, n) + # positions: (..., 3, 3); final axes are N/CA/C atoms and xyz coordinates. + n, ca, c = positions.unbind(dim=-2) # each (..., 3) + return Affine3D.from_graham_schmidt(c, ca, n) # affine batch shape: positions.shape[:-2] - coords = coords.clone().float() - coords[~coord_mask] = 0 + coords = coords.clone().float() # (b, l, 3, 3) + coords[~coord_mask] = 0 # (n_missing, 3, 3) selected residue coordinates average = coords.masked_fill(~coord_mask[..., None, None], 0).sum(1) / ( coord_mask.sum(-1)[..., None, None] + 1e-8 - ) - average_affine = backbone_affine(average.float()).as_matrix() + ) # (b, 3, 3) + average_affine = backbone_affine(average.float()).as_matrix() # affine batch shape: (b,) b, length, _, _ = coords.shape - rotation = average_affine.rot.tensor[..., None, :].expand(b, length, 9) - translation = average_affine.trans[..., None, :].expand(b, length, 3) + rotation = average_affine.rot.tensor[..., None, :].expand(b, length, 9) # (b, l, 9) + translation = average_affine.trans[..., None, :].expand(b, length, 3) # (b, l, 3) identity = RotationMatrix.identity( (b, length), dtype=torch.float32, device=coords.device, requires_grad=False, - ) + ) # rotation batch shape: (b, l) rotation = rotation.where( coord_mask.any(-1)[..., None, None], identity.tensor, - ) - missing_frame = Affine3D(translation, RotationMatrix(rotation)) + ) # (b, l, 9) + missing_frame = Affine3D(translation, RotationMatrix(rotation)) # affine batch shape: (b, l) - residue_frame = backbone_affine(coords.float()) + residue_frame = backbone_affine(coords.float()) # affine batch shape: (b, l) residue_frame = Affine3D.from_tensor( residue_frame.tensor.where( coord_mask[..., None], missing_frame.tensor, ) - ) - return residue_frame, coord_mask + ) # affine batch shape: (b, l) + return residue_frame, coord_mask # affine batch shape (b, l), mask (b, l) diff --git a/fastplms/models/esmfold2/esmfold2_aligner.py b/fastplms/models/esmfold2/esmfold2_aligner.py index a23b9d6d6b727ba020cef08f9208281e65b02393..5f120b9c2c314c10f70a4dd57a247c6ab9e41fd0 100644 --- a/fastplms/models/esmfold2/esmfold2_aligner.py +++ b/fastplms/models/esmfold2/esmfold2_aligner.py @@ -2,11 +2,11 @@ from __future__ import annotations -from dataclasses import Field, replace -from typing import Any, ClassVar, Protocol, TypeVar - import numpy as np import torch + +from dataclasses import Field, replace +from typing import Any, ClassVar, Protocol, TypeVar from torch import Tensor from .esmfold2_protein_structure import compute_affine_and_rmsd @@ -30,18 +30,19 @@ AlignableT = TypeVar("AlignableT", bound=Alignable) def _coordinate_batch(structure: Alignable) -> Tensor: - return torch.as_tensor(structure.atom37_positions, dtype=torch.double).unsqueeze(0) + # l is the residue count; the atom37 table has three Cartesian coordinates. + return torch.as_tensor(structure.atom37_positions, dtype=torch.double).unsqueeze(0) # (1, l, 37, 3) def _shared_atom_mask(mobile: Alignable, target: Alignable, backbone_only: bool) -> Tensor: shared = np.asarray(mobile.atom37_mask, dtype=bool) & np.asarray( target.atom37_mask, dtype=bool, - ) + ) # (l, 37) if backbone_only: - shared = shared.copy() - shared[:, 3:] = False - return torch.from_numpy(shared).unsqueeze(0) + shared = shared.copy() # (l, 37) + shared[:, 3:] = False # (l, 34); retain N, CA, C. + return torch.from_numpy(shared).unsqueeze(0) # (1, l, 37) class Aligner: @@ -57,16 +58,16 @@ class Aligner: if len(mobile) != len(target): raise AssertionError("mobile and target must contain the same residue count") - mobile_coordinates = _coordinate_batch(mobile) - target_coordinates = _coordinate_batch(target) + mobile_coordinates = _coordinate_batch(mobile) # (1, l, 37, 3) + target_coordinates = _coordinate_batch(target) # (1, l, 37, 3) if use_reflection: - target_coordinates = -target_coordinates - atom_mask = _shared_atom_mask(mobile, target, only_use_backbone) + target_coordinates = -target_coordinates # (1, l, 37, 3) + atom_mask = _shared_atom_mask(mobile, target, only_use_backbone) # (1, l, 37) self._affine3D, rmsd = compute_affine_and_rmsd( mobile_coordinates, target_coordinates, atom_exists_mask=atom_mask, - ) + ) # affine shape: (1, 1); rmsd: () self._rmsd = rmsd.item() @property @@ -76,12 +77,12 @@ class Aligner: def apply(self, mobile: AlignableT) -> AlignableT: """Return a dataclass copy with all present atom coordinates aligned.""" - present = np.asarray(mobile.atom37_mask, dtype=bool) + present = np.asarray(mobile.atom37_mask, dtype=bool) # (l, 37) packed = torch.as_tensor( mobile.atom37_positions[present], dtype=torch.float32, - ).unsqueeze(0) - aligned = self._affine3D.apply(packed).squeeze(0).cpu().numpy() - atom37_positions = np.full_like(mobile.atom37_positions, np.nan) - atom37_positions[present] = aligned + ).unsqueeze(0) # (1, n, 3), n is the number of present atoms. + aligned = self._affine3D.apply(packed).squeeze(0).cpu().numpy() # (n, 3) + atom37_positions = np.full_like(mobile.atom37_positions, np.nan) # (l, 37, 3) + atom37_positions[present] = aligned # (n, 3) return replace(mobile, atom37_positions=atom37_positions) diff --git a/fastplms/models/esmfold2/esmfold2_atom_indexer.py b/fastplms/models/esmfold2/esmfold2_atom_indexer.py index 676cbde76427c6c64672c0fe4e991457cdf57d70..e7dd68c6033373d3fe4538fea26ce3ba2fe02528 100644 --- a/fastplms/models/esmfold2/esmfold2_atom_indexer.py +++ b/fastplms/models/esmfold2/esmfold2_atom_indexer.py @@ -2,11 +2,11 @@ from __future__ import annotations +import numpy as np + from operator import attrgetter from typing import Any -import numpy as np - from .esmfold2_protein_structure import index_by_atom_name @@ -19,7 +19,7 @@ class AtomIndexer: __slots__ = ("_get_property", "dim", "property", "structure") - def __init__(self, structure: Any, property: str, dim: int): + def __init__(self, structure: Any, property: str, dim: int) -> None: self.structure = structure self.property = property self.dim = dim diff --git a/fastplms/models/esmfold2/esmfold2_conformers.py b/fastplms/models/esmfold2/esmfold2_conformers.py index ddd0e9b55e4175aa0d144c7330ecfcd1d3d902d4..76295d3fd78a4bcccbec73209cbe16743fe0b64e 100644 --- a/fastplms/models/esmfold2/esmfold2_conformers.py +++ b/fastplms/models/esmfold2/esmfold2_conformers.py @@ -11,21 +11,21 @@ import os import pickle import stat import tempfile +import numpy as np + from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass from hashlib import file_digest from pathlib import Path from typing import Any, BinaryIO - -import numpy as np from huggingface_hub import hf_hub_download from huggingface_hub.constants import HF_HUB_CACHE from fastplms.registry import RuntimeAsset, get_model_registry - from .esmfold2_constants import RES_TYPE_TO_CCD + _CCD_ENVIRONMENT_VARIABLE = "ESMCFOLD_CCD_PATH" _CCD_ASSET_ID = "esmfold2_ccd" diff --git a/fastplms/models/esmfold2/esmfold2_input_builder.py b/fastplms/models/esmfold2/esmfold2_input_builder.py index ca51bc2c91fd4c1e4139f7bcca71898e38e23065..a33bbb3dd089b443b92db00cd476670bddf283fb 100644 --- a/fastplms/models/esmfold2/esmfold2_input_builder.py +++ b/fastplms/models/esmfold2/esmfold2_input_builder.py @@ -2,14 +2,15 @@ from __future__ import annotations +import numpy as np + from collections.abc import Sequence from dataclasses import dataclass from typing import Any, TypeAlias -import numpy as np - from .esmfold2_msa import MSA + MSAInput: TypeAlias = MSA | None diff --git a/fastplms/models/esmfold2/esmfold2_metrics.py b/fastplms/models/esmfold2/esmfold2_metrics.py index fcd665167967a7524b7de15bff5afcc9e4c63bf6..572d221408aee6efc66dbc4cc04534157019f86c 100644 --- a/fastplms/models/esmfold2/esmfold2_metrics.py +++ b/fastplms/models/esmfold2/esmfold2_metrics.py @@ -5,6 +5,7 @@ from __future__ import annotations import numpy as np import torch import torch.nn.functional as F + from torch import Tensor from torch.amp import autocast # type: ignore @@ -18,8 +19,9 @@ from .esmfold2_protein_structure import ( def _distance_matrix(positions: Tensor, eps: float) -> Tensor: - displacement = positions[..., None, :] - positions[..., None, :, :] - return torch.sqrt(eps + torch.sum(displacement**2, dim=-1)) + # positions: (..., n, 3); n is the number of points in each distance matrix. + displacement = positions[..., None, :] - positions[..., None, :, :] # (..., n, n, 3) + return torch.sqrt(eps + torch.sum(displacement**2, dim=-1)) # (..., n, n) def compute_lddt_from_dmat( @@ -31,20 +33,21 @@ def compute_lddt_from_dmat( per_residue: bool = True, ) -> Tensor: """Score distance matrices ``D_pred`` and ``D_true`` with shape (..., l, l).""" + # dmat_pred, dmat_true, pairwise_mask: (..., l, l); cutoff broadcasts with distances. sequence_length = dmat_true.size(-1) - identity = torch.eye(sequence_length, device=dmat_true.device) - scored_pairs = (dmat_true < cutoff) * pairwise_mask * (1.0 - identity) - absolute_error = torch.abs(dmat_true - dmat_pred) + identity = torch.eye(sequence_length, device=dmat_true.device) # (l, l) + scored_pairs = (dmat_true < cutoff) * pairwise_mask * (1.0 - identity) # (..., l, l) + absolute_error = torch.abs(dmat_true - dmat_pred) # (..., l, l) score = ( (absolute_error < 0.5).type(absolute_error.dtype) + (absolute_error < 1.0).type(absolute_error.dtype) + (absolute_error < 2.0).type(absolute_error.dtype) + (absolute_error < 4.0).type(absolute_error.dtype) - ) * 0.25 + ) * 0.25 # (..., l, l) dimensions = (-1,) if per_residue else (-2, -1) - normalization = 1.0 / (eps + scored_pairs.sum(dim=dimensions)) - return normalization * (eps + (scored_pairs * score).sum(dim=dimensions)) + normalization = 1.0 / (eps + scored_pairs.sum(dim=dimensions)) # (..., l) if per_residue, otherwise (...) + return normalization * (eps + (scored_pairs * score).sum(dim=dimensions)) # (..., l) if per_residue, otherwise (...) def compute_lddt( @@ -58,16 +61,18 @@ def compute_lddt( sequence_id: Tensor | None = None, ) -> Tensor: """Compute lDDT from coordinate tensors and atom masks.""" + # positions: (..., n, 3); all_atom_mask: (..., n); pairwise mask: (..., n, n). + # sequence_id, when supplied, follows the point axes (..., n). - expanded_mask = all_atom_mask[..., None] - true_distances = _distance_matrix(all_atom_positions, eps) - predicted_distances = _distance_matrix(all_atom_pred_pos, eps) - pair_mask = expanded_mask * expanded_mask.transpose(-2, -1) + expanded_mask = all_atom_mask[..., None] # (..., n, 1) + true_distances = _distance_matrix(all_atom_positions, eps) # (..., n, n) + predicted_distances = _distance_matrix(all_atom_pred_pos, eps) # (..., n, n) + pair_mask = expanded_mask * expanded_mask.transpose(-2, -1) # (..., n, n) if pairwise_all_atom_mask is not None: - pair_mask = pair_mask * pairwise_all_atom_mask + pair_mask = pair_mask * pairwise_all_atom_mask # (..., n, n) if sequence_id is not None: - same_sequence = sequence_id[..., None] == sequence_id[..., None, :] - pair_mask = pair_mask * same_sequence.type_as(pair_mask) + same_sequence = sequence_id[..., None] == sequence_id[..., None, :] # (..., n, n) + pair_mask = pair_mask * same_sequence.type_as(pair_mask) # (..., n, n) return compute_lddt_from_dmat( predicted_distances, true_distances, @@ -75,7 +80,7 @@ def compute_lddt( cutoff=cutoff, eps=eps, per_residue=per_residue, - ) + ) # (..., n) if per_residue, otherwise (...) def compute_lddt_ca( @@ -88,11 +93,13 @@ def compute_lddt_ca( sequence_id: Tensor | None = None, ) -> Tensor: """Compute lDDT using only C-alpha coordinates.""" + # True coordinates/mask: (..., l, n_atoms, 3) / (..., l, n_atoms). + # Predicted rank-three input is treated as CA-only; otherwise its CA atom axis is selected. ca_index = residue_constants.atom_order["CA"] predicted_ca = ( all_atom_pred_pos if all_atom_pred_pos.dim() == 3 else all_atom_pred_pos[..., ca_index, :] - ) + ) # (..., l, 3) return compute_lddt( predicted_ca, all_atom_positions[..., ca_index, :], @@ -101,7 +108,7 @@ def compute_lddt_ca( eps=eps, per_residue=per_residue, sequence_id=sequence_id, - ) + ) # (..., l) if per_residue, otherwise (...) @torch.no_grad() @@ -114,22 +121,24 @@ def compute_rmsd( reduction: str = "batch", ) -> Tensor: """Align ``X`` to ``Y`` and compute RMSD.""" + # mobile/target: (b, n, 3) or (b, l, n_atoms, 3); masks omit xyz. + # b_eff counts unpacked sequences when sequence_id is provided; n counts flattened atoms. centered_mobile, _, centered_target, _, rotation, counts = compute_alignment_tensors( mobile, target, atom_exists_mask, sequence_id, - ) + ) # coordinates (b_eff, n, 3), centroids (b_eff, 1, 3), rotation (b_eff, 3, 3), counts (b_eff, 1) rmsd = compute_rmsd_no_alignment( torch.matmul(centered_mobile, rotation), centered_target, counts, reduction=reduction, - ) + ) # (b_eff, n / 3) per_residue; (b_eff,) per_sample; () batch if reduction == "per_residue" and sequence_id is not None: - return binpack(rmsd, sequence_id, pad_value=0) - return rmsd + return binpack(rmsd, sequence_id, pad_value=0) # (b, packed_length) + return rmsd # shape selected by reduction above def compute_gdt_ts( @@ -140,36 +149,39 @@ def compute_gdt_ts( reduction: str = "per_sample", ) -> Tensor: """Align ``X`` to ``Y`` and compute GDT-TS.""" + # mobile/target: batched xyz coordinates; masks omit xyz. + # b_eff counts unpacked sequences when sequence_id is provided; n counts flattened atoms. if atom_exists_mask is None: - atom_exists_mask = torch.isfinite(target).all(dim=-1) + atom_exists_mask = torch.isfinite(target).all(dim=-1) # target.shape[:-1] centered_mobile, _, centered_target, _, rotation, _ = compute_alignment_tensors( mobile, target, atom_exists_mask, sequence_id, - ) + ) # coordinates (b_eff, n, 3), centroids (b_eff, 1, 3), rotation (b_eff, 3, 3), counts (b_eff, 1) if sequence_id is not None: - atom_exists_mask = unbinpack(atom_exists_mask, sequence_id, pad_value=False) + atom_exists_mask = unbinpack(atom_exists_mask, sequence_id, pad_value=False) # (b_eff, max_sequence_length, *original_mask.shape[2:]) return compute_gdt_ts_no_alignment( torch.matmul(centered_mobile, rotation), centered_target, atom_exists_mask, reduction, - ) + ) # (b_eff,) per_sample; () batch def _batched_contacts(predictions: Tensor, targets: Tensor) -> tuple[Tensor, Tensor]: + # predictions, targets: (l, l) or (b, l, l). if predictions.dim() == 2: - predictions = predictions.unsqueeze(0) + predictions = predictions.unsqueeze(0) # (1, l, l) if targets.dim() == 2: - targets = targets.unsqueeze(0) + targets = targets.unsqueeze(0) # (1, l, l) if predictions.size() != targets.size(): raise ValueError( f"Size mismatch. Received predictions of size {predictions.size()}, " f"targets of size {targets.size()}" ) - return predictions, targets + return predictions, targets # each (b, l, l) def _valid_contact_mask( @@ -178,14 +190,15 @@ def _valid_contact_mask( minsep: int, maxsep: int | None, ) -> Tensor: + # targets: (b, l, l); src_lengths: (b,). sequence_length = targets.shape[-1] - positions = torch.arange(sequence_length, device=targets.device) - separation = (positions.unsqueeze(0) - positions.unsqueeze(1)).unsqueeze(0) - valid = (separation >= minsep) & (targets >= 0) + positions = torch.arange(sequence_length, device=targets.device) # (l,) + separation = (positions.unsqueeze(0) - positions.unsqueeze(1)).unsqueeze(0) # (1, l, l) + valid = (separation >= minsep) & (targets >= 0) # (b, l, l) if maxsep is not None: - valid &= separation < maxsep - within_length = positions.unsqueeze(0) < src_lengths.unsqueeze(1) - return valid & within_length.unsqueeze(1) & within_length.unsqueeze(2) + valid &= separation < maxsep # (b, l, l) + within_length = positions.unsqueeze(0) < src_lengths.unsqueeze(1) # (b, l) + return valid & within_length.unsqueeze(1) & within_length.unsqueeze(2) # (b, l, l) def contact_precision( @@ -197,8 +210,10 @@ def contact_precision( override_length: int | None = None, ) -> dict[str, Tensor]: """Compute P@L, P@L/5, and binned area for contact probabilities.""" + # predictions, targets: (l, l) or (b, l, l); src_lengths: (b,) or None. + # n_upper_pairs counts the entries returned by triu_indices(l, minsep). - predictions, targets = _batched_contacts(predictions, targets) + predictions, targets = _batched_contacts(predictions, targets) # each (b, l, l) batch_size, sequence_length, _ = predictions.shape if src_lengths is None: src_lengths = torch.full( @@ -206,30 +221,30 @@ def contact_precision( sequence_length, dtype=torch.long, device=predictions.device, - ) - valid = _valid_contact_mask(targets, src_lengths, minsep, maxsep) - masked_predictions = predictions.masked_fill(~valid, float("-inf")) - row_index, column_index = np.triu_indices(sequence_length, minsep) - upper_predictions = masked_predictions[:, row_index, column_index] - upper_targets = targets[:, row_index, column_index] + ) # (b,) + valid = _valid_contact_mask(targets, src_lengths, minsep, maxsep) # (b, l, l) + masked_predictions = predictions.masked_fill(~valid, float("-inf")) # (b, l, l) + row_index, column_index = np.triu_indices(sequence_length, minsep) # each (n_upper_pairs,) + upper_predictions = masked_predictions[:, row_index, column_index] # (b, n_upper_pairs) + upper_targets = targets[:, row_index, column_index] # (b, n_upper_pairs) topk = sequence_length if override_length is None else max(sequence_length, override_length) - ranked_indices = upper_predictions.argsort(dim=-1, descending=True)[:, :topk] - batch_indices = torch.arange(batch_size, device=ranked_indices.device).unsqueeze(1) - ranked_targets = upper_targets[batch_indices, ranked_indices] + ranked_indices = upper_predictions.argsort(dim=-1, descending=True)[:, :topk] # (b, min(topk, n_upper_pairs)) + batch_indices = torch.arange(batch_size, device=ranked_indices.device).unsqueeze(1) # (b, 1) + ranked_targets = upper_targets[batch_indices, ranked_indices] # (b, min(topk, n_upper_pairs)) if ranked_targets.size(1) < topk: - ranked_targets = F.pad(ranked_targets, [0, topk - ranked_targets.size(1)]) - cumulative_contacts = ranked_targets.type_as(predictions).cumsum(dim=-1) + ranked_targets = F.pad(ranked_targets, [0, topk - ranked_targets.size(1)]) # (b, topk) + cumulative_contacts = ranked_targets.type_as(predictions).cumsum(dim=-1) # (b, topk) - gather_lengths = src_lengths.unsqueeze(1) + gather_lengths = src_lengths.unsqueeze(1) # (b, 1) if override_length is not None: - gather_lengths = override_length * torch.ones_like(gather_lengths) - fractions = torch.arange(0.1, 1.1, 0.1, device=predictions.device).unsqueeze(0) - gather_indices = (fractions * gather_lengths).type(torch.long).sub(1).clamp_min(0) - cumulative_bins = cumulative_contacts.gather(1, gather_indices) - precisions = cumulative_bins / (gather_indices + 1).type_as(cumulative_bins) + gather_lengths = override_length * torch.ones_like(gather_lengths) # (b, 1) + fractions = torch.arange(0.1, 1.1, 0.1, device=predictions.device).unsqueeze(0) # (1, 10) + gather_indices = (fractions * gather_lengths).type(torch.long).sub(1).clamp_min(0) # (b, 10) + cumulative_bins = cumulative_contacts.gather(1, gather_indices) # (b, 10) + precisions = cumulative_bins / (gather_indices + 1).type_as(cumulative_bins) # (b, 10) return { "AUC": precisions.mean(dim=-1), "P@L": precisions[:, 9], "P@L5": precisions[:, 1], - } + } # each metric: (b,) diff --git a/fastplms/models/esmfold2/esmfold2_misc.py b/fastplms/models/esmfold2/esmfold2_misc.py index 022edaf3916ab22b21363018e3cbf54963440c0e..78c6643e9f4e84ecb5ff89eb34916606a6ea5c5e 100644 --- a/fastplms/models/esmfold2/esmfold2_misc.py +++ b/fastplms/models/esmfold2/esmfold2_misc.py @@ -6,6 +6,10 @@ module therefore performs no device selection, compilation, or remote access. from __future__ import annotations +import numpy as np +import torch +import zstandard + from collections import defaultdict from collections.abc import Generator, Iterable, Sequence from contextlib import AbstractContextManager, nullcontext @@ -14,13 +18,10 @@ from io import BytesIO from typing import Any, Protocol, TypeVar, runtime_checkable from warnings import warn -import numpy as np -import torch -import zstandard - from .esmfold2_constants_esm3 import CHAIN_BREAK_STR from .esmfold2_utils_types import FunctionAnnotation + MAX_SUPPORTED_DISTANCE = 1e6 TSequence = TypeVar("TSequence", bound=Sequence) @@ -50,44 +51,47 @@ def fp32_autocast_context( def maybe_tensor(value, convert_none_to_nan: bool = False) -> torch.Tensor | None: """Convert an optional array-like value to a tensor.""" + # value: array-like arbitrary shape, or a list of identically shaped tensors. if value is None: return None if isinstance(value, torch.Tensor): - return value + return value # value.shape if isinstance(value, list) and all(isinstance(element, torch.Tensor) for element in value): - return torch.stack(value) + return torch.stack(value) # (n_values, *element_shape) if convert_none_to_nan: - value = np.asarray(value, dtype=np.float32) - value = np.where(value is None, np.nan, value) - return torch.tensor(value) + value = np.asarray(value, dtype=np.float32) # shape inferred from the nested input + value = np.where(value is None, np.nan, value) # value.shape + return torch.tensor(value) # shape inferred from the array-like input def maybe_list(value, convert_nan_to_none: bool = False) -> list | None: """Convert an optional tensor or NumPy array to nested Python lists.""" + # value: arbitrary-shaped array/tensor; element order and nesting are retained. if value is None: return None if not convert_nan_to_none: return value.tolist() if isinstance(value, torch.Tensor): - nan_mask = torch.isnan(value).cpu().numpy() - array = value.cpu().numpy().astype(object) + nan_mask = torch.isnan(value).cpu().numpy() # value.shape + array = value.cpu().numpy().astype(object) # value.shape elif isinstance(value, np.ndarray): - nan_mask = np.isnan(value) - array = value.astype(object) + nan_mask = np.isnan(value) # value.shape + array = value.astype(object) # value.shape else: raise TypeError("maybe_list can only work with torch.tensor or np.ndarray.") - array[nan_mask] = None + array[nan_mask] = None # (n_nan,) selected elements return array.tolist() def replace_inf(data): """Replace infinite array values by the ESM API sentinel value.""" + # data: array-like arbitrary shape; the returned list retains its nesting. if data is None: return None - array = np.asarray(data, dtype=np.float32) + array = np.asarray(data, dtype=np.float32) # shape inferred from data return np.where(np.isinf(array), 1000, array).tolist() @@ -118,9 +122,10 @@ def slice_any_object( idx: int | list[int] | slice | np.ndarray, ) -> TSequence: """Slice tensors, arrays, dataclasses, and ordinary Python sequences.""" + # Array shape is caller-defined; idx follows that array type's indexing rules. if isinstance(obj, (np.ndarray, torch.Tensor)) or is_dataclass(obj): - return obj[idx] # type: ignore[index,return-value] + return obj[idx] # type: ignore[index,return-value]; shape determined by NumPy/Torch indexing when obj is an array return slice_python_object_as_numpy(obj, idx) @@ -172,20 +177,21 @@ def concat_objects(objs: Sequence[Any], separator: Any | None = None): objs if separator is None else list(iterate_with_intermediate(objs, np.array([separator]))) - ) - return np.concatenate(pieces) + ) # arrays with a common trailing shape, interleaved with a separator if supplied + return np.concatenate(pieces) # (sum of leading lengths, *common_trailing_shape) if isinstance(first, torch.Tensor): pieces = ( objs if separator is None else list(iterate_with_intermediate(objs, torch.tensor([separator]))) - ) - return torch.cat(pieces) # type: ignore[arg-type] + ) # tensors with a common trailing shape, interleaved with a separator if supplied + return torch.cat(pieces) # type: ignore[arg-type]; (sum of leading lengths, *common_trailing_shape) raise TypeError(type(first)) -def rbf(values, v_min, v_max, n_bins=16): +def rbf(values: torch.Tensor, v_min: float, v_max: float, n_bins: int = 16) -> torch.Tensor: """Encode values against evenly spaced radial basis centers.""" + # values: arbitrary shape; the output appends a final n_bins axis. centers = torch.linspace( v_min, @@ -193,34 +199,39 @@ def rbf(values, v_min, v_max, n_bins=16): n_bins, dtype=values.dtype, device=values.device, - ) - centers = centers.reshape((1,) * values.ndim + (-1,)) - standardized = (values.unsqueeze(-1) - centers) / ((v_max - v_min) / n_bins) - return torch.exp(-(standardized**2)) + ) # (n_bins,) + centers = centers.reshape((1,) * values.ndim + (-1,)) # (1, ..., 1, n_bins); values.ndim leading singleton axes + standardized = (values.unsqueeze(-1) - centers) / ((v_max - v_min) / n_bins) # (*values.shape, n_bins) + return torch.exp(-(standardized**2)) # (*values.shape, n_bins) -def batched_gather(data, inds, dim=0, no_batch_dims=0): +def batched_gather( + data: torch.Tensor, inds: torch.Tensor, dim: int = 0, no_batch_dims: int = 0 +) -> torch.Tensor: """Gather along one data dimension while retaining leading batch axes.""" + # data/inds ranks are caller-defined. The first no_batch_dims axes index together; + # remaining advanced-index axes broadcast, while slice axes retain their data lengths. batch_indices = [] index_rank = len(inds.shape) for axis, size in enumerate(data.shape[:no_batch_dims]): shape = (1,) * axis + (-1,) + (1,) * (index_rank - axis - 1) - batch_indices.append(torch.arange(size).view(*shape)) + batch_indices.append(torch.arange(size).view(*shape)) # index-rank tensor; only the batch axis has length size tail = [slice(None)] * (len(data.shape) - no_batch_dims) tail[dim - no_batch_dims if dim >= 0 else dim] = inds - return data[tuple(batch_indices + tail)] + return data[tuple(batch_indices + tail)] # broadcast advanced-index shape plus retained data slice axes def node_gather(s: torch.Tensor, edges: torch.Tensor) -> torch.Tensor: """Gather node features for each row of an edge-index tensor.""" + # s: (..., n_nodes, d); edges: (..., n_nodes, n_neighbors). return batched_gather( s.unsqueeze(-3), edges, -2, no_batch_dims=len(s.shape) - 1, - ) + ) # (..., n_nodes, n_neighbors, d) def knn_graph( @@ -230,31 +241,32 @@ def knn_graph( sequence_id: torch.Tensor, *, no_knn: int, -): +) -> tuple[torch.Tensor, torch.Tensor]: """Build nearest-neighbor edges, using sequence distance for missing geometry.""" + # coords: (..., l, 3); masks: (..., l). With sequence_id, inputs use batch shape (b, l). length = coords.shape[-2] - coords = coords.nan_to_num() - missing_pair = ~(coord_mask[..., None, :] & coord_mask[..., :, None]) - excluded_pair = padding_mask[..., None, :] | padding_mask[..., :, None] + coords = coords.nan_to_num() # (..., l, 3) + missing_pair = ~(coord_mask[..., None, :] & coord_mask[..., :, None]) # (..., l, l) + excluded_pair = padding_mask[..., None, :] | padding_mask[..., :, None] # (..., l, l) if sequence_id is not None: - excluded_pair |= sequence_id.unsqueeze(1) != sequence_id.unsqueeze(2) + excluded_pair |= sequence_id.unsqueeze(1) != sequence_id.unsqueeze(2) # (b, l, l) - distances = (coords.unsqueeze(-2) - coords.unsqueeze(-3)).norm(dim=-1) - residue_index = torch.arange(length, device=coords.device) - sequence_distance = (residue_index.unsqueeze(-1) - residue_index.unsqueeze(-2)).abs() + distances = (coords.unsqueeze(-2) - coords.unsqueeze(-3)).norm(dim=-1) # (..., l, l) + residue_index = torch.arange(length, device=coords.device) # (l,) + sequence_distance = (residue_index.unsqueeze(-1) - residue_index.unsqueeze(-2)).abs() # (l, l) if not (distances[~missing_pair] < MAX_SUPPORTED_DISTANCE).all(): raise ValueError( "Coordinate pairwise distances exceed max supported distance " f"({MAX_SUPPORTED_DISTANCE}). " ) - rank_distance = sequence_distance.to(distances.dtype).mul(1e2).add(MAX_SUPPORTED_DISTANCE) - rank_distance = rank_distance.where(missing_pair, distances) - rank_distance = rank_distance.masked_fill(excluded_pair, torch.inf) - sorted_distance, sorted_edge = rank_distance.sort(dim=-1, descending=False) + rank_distance = sequence_distance.to(distances.dtype).mul(1e2).add(MAX_SUPPORTED_DISTANCE) # (l, l) + rank_distance = rank_distance.where(missing_pair, distances) # (..., l, l) + rank_distance = rank_distance.masked_fill(excluded_pair, torch.inf) # (..., l, l) + sorted_distance, sorted_edge = rank_distance.sort(dim=-1, descending=False) # each (..., l, l) width = min(no_knn, length) - return sorted_edge[..., :width], sorted_distance[..., :width].isfinite() + return sorted_edge[..., :width], sorted_distance[..., :width].isfinite() # each (..., l, min(no_knn, l)) def stack_variable_length_tensors( @@ -263,6 +275,7 @@ def stack_variable_length_tensors( dtype: torch.dtype | None = None, ) -> torch.Tensor: """Pad arbitrary tensor dimensions to their maxima, then stack.""" + # Each sequence has the same rank; its axis lengths may differ. Padding uses each axis maximum. output_shape = [ len(sequences), @@ -273,56 +286,58 @@ def stack_variable_length_tensors( constant_value, dtype=sequences[0].dtype if dtype is None else dtype, device=sequences[0].device, - ) + ) # (n_sequences, *axiswise_maximum_shape) for destination, source in zip(output, sequences, strict=True): - destination[tuple(slice(size) for size in source.shape)] = source - return output + destination[tuple(slice(size) for size in source.shape)] = source # source.shape slice of destination + return output # (n_sequences, *axiswise_maximum_shape) def binpack( tensor: torch.Tensor, sequence_id: torch.Tensor | None, pad_value: int | float, -): +) -> torch.Tensor: """Scatter a sequence-major tensor into the packed layout described by IDs.""" + # tensor: (n_unpacked_sequences, max_length, ...); sequence_id: (b, packed_length) or None. if sequence_id is None: - return tensor - sequence_counts = sequence_id.max(dim=-1).values + 1 + return tensor # tensor.shape + sequence_counts = sequence_id.max(dim=-1).values + 1 # (b,) output = torch.full( sequence_id.shape + tensor.shape[2:], fill_value=pad_value, dtype=tensor.dtype, device=tensor.device, - ) + ) # (b, packed_length, *tensor.shape[2:]) source_index = 0 for batch_index, (batch_ids, count) in enumerate( zip(sequence_id, sequence_counts, strict=True) ): for seqid in range(count): - selection = batch_ids == seqid - output[batch_index, selection] = tensor[source_index, : selection.sum()] + selection = batch_ids == seqid # (packed_length,) + output[batch_index, selection] = tensor[source_index, : selection.sum()] # (n_selected, *tensor.shape[2:]) selected rows source_index += 1 - return output + return output # (b, packed_length, *tensor.shape[2:]) def unbinpack( tensor: torch.Tensor, sequence_id: torch.Tensor | None, pad_value: int | float, -): +) -> torch.Tensor: """Restore sequence-major rows from a packed tensor and its sequence IDs.""" + # tensor: (b, packed_length, ...); sequence_id: (b, packed_length) or None. if sequence_id is None: - return tensor + return tensor # tensor.shape rows = [] - sequence_counts = sequence_id.max(dim=-1).values + 1 + sequence_counts = sequence_id.max(dim=-1).values + 1 # (b,) for batch_index, (batch_ids, count) in enumerate( zip(sequence_id, sequence_counts, strict=True) ): for seqid in range(count): - rows.append(tensor[batch_index, batch_ids == seqid]) - return stack_variable_length_tensors(rows, pad_value) + rows.append(tensor[batch_index, batch_ids == seqid]) # (selected_sequence_length, *tensor.shape[2:]) + return stack_variable_length_tensors(rows, pad_value) # (n_unpacked_sequences, max_sequence_length, *tensor.shape[2:]) def merge_ranges( @@ -386,7 +401,7 @@ def get_chainbreak_boundaries_from_sequence( boundaries.extend((index, index + 1)) boundaries.append(len(sequence)) assert len(boundaries) % 2 == 0 - return np.asarray(boundaries).reshape(-1, 2) + return np.asarray(boundaries).reshape(-1, 2) # (n_chains, 2) def deserialize_tensors(data: bytes) -> Any: diff --git a/fastplms/models/esmfold2/esmfold2_mmcif_parsing.py b/fastplms/models/esmfold2/esmfold2_mmcif_parsing.py index 25d2aff0216ce4cc287efabdf526b1ee30e2e23f..c8c0016fe420df469a8d7bff8d722f815cda9137 100644 --- a/fastplms/models/esmfold2/esmfold2_mmcif_parsing.py +++ b/fastplms/models/esmfold2/esmfold2_mmcif_parsing.py @@ -5,17 +5,18 @@ from __future__ import annotations import functools import io import os -from contextlib import suppress -from dataclasses import dataclass -from datetime import datetime - import biotite.structure as bs import biotite.structure.io.pdbx as pdbx import numpy as np + +from contextlib import suppress +from dataclasses import dataclass +from datetime import datetime from biotite.structure.io.pdbx import CIFColumn, CIFData, CIFFile from . import esmfold2_residue_constants as residue_constants + PathOrBuffer = str | os.PathLike | io.StringIO PLDDT_B_FACTOR_SCALE = 100.0 diff --git a/fastplms/models/esmfold2/esmfold2_molecular_complex.py b/fastplms/models/esmfold2/esmfold2_molecular_complex.py index 3c8921534302e524a9cc3e11242a6c54f16a53e5..adb7de74c39b23ecedf91545277597a953054f9c 100644 --- a/fastplms/models/esmfold2/esmfold2_molecular_complex.py +++ b/fastplms/models/esmfold2/esmfold2_molecular_complex.py @@ -11,18 +11,18 @@ from __future__ import annotations import io import os import re -from dataclasses import asdict, dataclass -from pathlib import Path -from subprocess import check_output -from tempfile import TemporaryDirectory -from typing import TYPE_CHECKING, Any - import biotite.structure as bs import biotite.structure.io.pdbx as pdbx import brotli import msgpack import numpy as np import torch + +from dataclasses import asdict, dataclass +from pathlib import Path +from subprocess import check_output +from tempfile import TemporaryDirectory +from typing import TYPE_CHECKING, Any from biotite.structure.io.pdbx import ( CIFCategory, CIFColumn, diff --git a/fastplms/models/esmfold2/esmfold2_msa.py b/fastplms/models/esmfold2/esmfold2_msa.py index 8db1e20250d5a71edbf03755fba46c92677ce1b4..18a594e25714b39c6f777f9c50fb36c73b08e95d 100644 --- a/fastplms/models/esmfold2/esmfold2_msa.py +++ b/fastplms/models/esmfold2/esmfold2_msa.py @@ -4,13 +4,13 @@ from __future__ import annotations import dataclasses import string +import numpy as np + from collections.abc import Sequence from dataclasses import dataclass from functools import cached_property from itertools import islice from typing import Any - -import numpy as np from Bio import SeqIO from scipy.spatial.distance import cdist @@ -20,6 +20,7 @@ from .esmfold2_parsing import FastaEntry, read_sequences, write_sequences from .esmfold2_sequential_dataclass import SequentialDataclass from .esmfold2_system import PathOrBuffer + _A3M_INSERTION_DELETE_TABLE = str.maketrans( dict.fromkeys(string.ascii_lowercase + ".") ) diff --git a/fastplms/models/esmfold2/esmfold2_msa_filter_sequences.py b/fastplms/models/esmfold2/esmfold2_msa_filter_sequences.py index bc13db7bee2398d291265e6f5a2e95752b2be61e..5005abf2bfc3edefae831c303fc9505e99aac106 100644 --- a/fastplms/models/esmfold2/esmfold2_msa_filter_sequences.py +++ b/fastplms/models/esmfold2/esmfold2_msa_filter_sequences.py @@ -4,22 +4,24 @@ from __future__ import annotations import os import tempfile -from pathlib import Path - import numpy as np +from pathlib import Path + from .esmfold2_system import run_subprocess_with_errorcheck def _byte_matrix(array: np.ndarray) -> np.ndarray: """Return a two-dimensional byte view used for Hamming comparisons.""" - matrix = np.asarray(array).view(np.uint8) - return matrix.reshape(matrix.shape[0], -1) + # array: (n, l); w is its row width in bytes after viewing its dtype. + matrix = np.asarray(array).view(np.uint8) # (n, w) + return matrix.reshape(matrix.shape[0], -1) # (n, w) def _hamming_to_all(query: np.ndarray, sequences: np.ndarray) -> np.ndarray: - return np.not_equal(sequences, query).mean(axis=1, dtype=np.float64) + # query: (w,); sequences: (n, w). + return np.not_equal(sequences, query).mean(axis=1, dtype=np.float64) # (n,) def greedy_select_indices(array: np.ndarray, num_seqs: int, mode: str = "max") -> list[int]: @@ -48,20 +50,20 @@ def greedy_select_indices(array: np.ndarray, num_seqs: int, mode: str = "max") - if depth <= num_seqs: return list(range(depth)) - sequences = _byte_matrix(array) + sequences = _byte_matrix(array) # (n, w) selected = [0] - available = np.ones(depth, dtype=bool) - available[0] = False - distance_sum = _hamming_to_all(sequences[0], sequences) + available = np.ones(depth, dtype=bool) # (n,) + available[0] = False # () + distance_sum = _hamming_to_all(sequences[0], sequences) # (n,) choose = np.argmax if mode == "max" else np.argmin while len(selected) < num_seqs: - candidates = np.flatnonzero(available) - candidate_scores = distance_sum[candidates] / len(selected) + candidates = np.flatnonzero(available) # (n_remaining,) + candidate_scores = distance_sum[candidates] / len(selected) # (n_remaining,) next_index = int(candidates[int(choose(candidate_scores))]) selected.append(next_index) - available[next_index] = False - distance_sum += _hamming_to_all(sequences[next_index], sequences) + available[next_index] = False # () + distance_sum += _hamming_to_all(sequences[next_index], sequences) # (n,) return sorted(selected) diff --git a/fastplms/models/esmfold2/esmfold2_normalize_coordinates.py b/fastplms/models/esmfold2/esmfold2_normalize_coordinates.py index 45e065247f9b1ac048c697d9b10c0a14605ee8e9..df45d34ceae4e379e58f773e40d6a9fe475cdd68 100644 --- a/fastplms/models/esmfold2/esmfold2_normalize_coordinates.py +++ b/fastplms/models/esmfold2/esmfold2_normalize_coordinates.py @@ -2,23 +2,25 @@ from __future__ import annotations -from typing import TypeVar - import numpy as np import torch + +from typing import TypeVar from torch import Tensor from . import esmfold2_residue_constants as residue_constants from .esmfold2_affine3d import Affine3D + ArrayOrTensor = TypeVar("ArrayOrTensor", np.ndarray, Tensor) def atom3_to_backbone_frames(bb_positions: Tensor) -> Affine3D: """Construct a frame from N, C-alpha, and C positions in ``X``.""" - n_position, ca_position, c_position = bb_positions.unbind(dim=-2) - return Affine3D.from_graham_schmidt(c_position, ca_position, n_position) + # bb_positions: (..., 3, 3), ordered N, CA, C on the penultimate axis. + n_position, ca_position, c_position = bb_positions.unbind(dim=-2) # each (..., 3) + return Affine3D.from_graham_schmidt(c_position, ca_position, n_position) # affine shape: (...,) def index_by_atom_name( @@ -26,42 +28,43 @@ def index_by_atom_name( atom_names: str | list[str], dim: int = -2, ) -> ArrayOrTensor: - """Select one or more named atoms along an atom37 axis.""" + """Select named atoms, replacing axis ``dim`` by their count (or removing it).""" single_atom = isinstance(atom_names, str) names = [atom_names] if single_atom else atom_names indices = [residue_constants.atom_order[name] for name in names] axis = dim % atom37.ndim if isinstance(atom37, Tensor): - index = torch.tensor(indices, dtype=torch.long, device=atom37.device) - selected = torch.index_select(atom37, axis, index) + index = torch.tensor(indices, dtype=torch.long, device=atom37.device) # (n_names,) + selected = torch.index_select(atom37, axis, index) # atom37.shape with axis = n_names else: - selected = np.take(atom37, indices, axis=axis) + selected = np.take(atom37, indices, axis=axis) # atom37.shape with axis = n_names return selected.squeeze(axis) if single_atom else selected # type: ignore[return-value] def get_protein_normalization_frame(coords: Tensor) -> Affine3D: - """Build one frame from backbone coordinates ``X`` with shape (l, 37, 3).""" + """Build one frame per batch item from coordinates of shape (..., l, 37, 3).""" - backbone = index_by_atom_name(coords, ["N", "CA", "C"], dim=-2) - residue_is_valid = torch.isfinite(backbone).all(dim=-1).all(dim=-1) - weights = residue_is_valid[..., None, None] - coordinate_sum = backbone.masked_fill(~weights, 0).sum(dim=-3) - count = residue_is_valid.sum(dim=-1)[..., None, None] - mean_backbone = coordinate_sum / (count + 1e-8) - return atom3_to_backbone_frames(mean_backbone.float()) + backbone = index_by_atom_name(coords, ["N", "CA", "C"], dim=-2) # (..., l, 3, 3) + residue_is_valid = torch.isfinite(backbone).all(dim=-1).all(dim=-1) # (..., l) + weights = residue_is_valid[..., None, None] # (..., l, 1, 1) + coordinate_sum = backbone.masked_fill(~weights, 0).sum(dim=-3) # (..., 3, 3) + count = residue_is_valid.sum(dim=-1)[..., None, None] # (..., 1, 1) + mean_backbone = coordinate_sum / (count + 1e-8) # (..., 3, 3) + return atom3_to_backbone_frames(mean_backbone.float()) # affine shape: (...,) def apply_frame_to_coords(coords: Tensor, frame: Affine3D) -> Tensor: """Express atom coordinates ``X`` in the inverse of ``frame``.""" - transformed = frame[..., None, None].invert().apply(coords) - frame_is_valid = frame.trans.norm(dim=-1) > 0 - normalized = torch.where(frame_is_valid[..., None, None, None], transformed, coords) - return normalized.masked_fill(torch.isinf(coords), torch.inf) + # coords: (..., l, 37, 3); frame shape: (...,). + transformed = frame[..., None, None].invert().apply(coords) # (..., l, 37, 3) + frame_is_valid = frame.trans.norm(dim=-1) > 0 # (...,) + normalized = torch.where(frame_is_valid[..., None, None, None], transformed, coords) # (..., l, 37, 3) + return normalized.masked_fill(torch.isinf(coords), torch.inf) # (..., l, 37, 3) def normalize_coordinates(coords: Tensor) -> Tensor: """Normalize ``X`` with shape (..., l, 37, 3) to its backbone frame.""" - return apply_frame_to_coords(coords, get_protein_normalization_frame(coords)) + return apply_frame_to_coords(coords, get_protein_normalization_frame(coords)) # (..., l, 37, 3) diff --git a/fastplms/models/esmfold2/esmfold2_output.py b/fastplms/models/esmfold2/esmfold2_output.py index 64c7d3019ff1668cf260b0f47ee8770cae0b7502..7cb6ba460cbf3551602893b685911d8de7269745 100644 --- a/fastplms/models/esmfold2/esmfold2_output.py +++ b/fastplms/models/esmfold2/esmfold2_output.py @@ -2,14 +2,14 @@ from __future__ import annotations +import numpy as np +import torch + from collections.abc import Iterable from dataclasses import dataclass, field from itertools import groupby from typing import Any -import numpy as np -import torch - from .esmfold2_constants import ELEMENT_NUMBER_TO_SYMBOL, MOL_TYPE_NONPOLYMER from .esmfold2_molecular_complex import MolecularComplex, MolecularComplexMetadata @@ -92,17 +92,18 @@ def build_molecular_complex_from_features( tokens are collapsed into one non-polymer residue per chain. """ - M = atom_mask.bool().cpu().numpy() - X = coords.float().cpu().numpy() - atom_names = ref_atom_name_chars.cpu().numpy() - elements = ref_element.cpu().numpy() - confidence = None if plddt is None else plddt.float().cpu().numpy() + # a counts padded atoms; l counts model tokens (ligand tokens may be atoms). + present_atoms = atom_mask.bool().cpu().numpy() # (a,) + X = coords.float().cpu().numpy() # (a, 3) + atom_names = ref_atom_name_chars.cpu().numpy() # (a, 4) + elements = ref_element.cpu().numpy() # (a,) + confidence = None if plddt is None else plddt.float().cpu().numpy() # (l,) or None records = _ComplexRecords() - def decode_atoms(tokens: Iterable[Any]): + def decode_atoms(tokens: Iterable[Any]) -> Iterable[tuple[list[float], str, str]]: for token in tokens: for atom_index in range(token.atom_start, token.atom_start + token.atom_count): - if M[atom_index]: + if present_atoms[atom_index]: yield ( X[atom_index].tolist(), get_element_symbol(int(elements[atom_index])), diff --git a/fastplms/models/esmfold2/esmfold2_paired_msa.py b/fastplms/models/esmfold2/esmfold2_paired_msa.py index 44f050b37c83ae70cf70b532380eb11c94a608a3..a78a2edd616e007c9e7401902f8a453ff4c3265c 100644 --- a/fastplms/models/esmfold2/esmfold2_paired_msa.py +++ b/fastplms/models/esmfold2/esmfold2_paired_msa.py @@ -3,10 +3,10 @@ from __future__ import annotations import re -from dataclasses import dataclass - import numpy as np +from dataclasses import dataclass + from .esmfold2_constants import ( MSA_GAP_TOKEN_ID, PROTEIN_3TO1, @@ -15,6 +15,7 @@ from .esmfold2_constants import ( ) from .esmfold2_msa import MSA + _TAXONOMY_PATTERN = re.compile(r"key=(-?\d+)") diff --git a/fastplms/models/esmfold2/esmfold2_parsing.py b/fastplms/models/esmfold2/esmfold2_parsing.py index f906d1ff2804c726daf283d6b323f44b43d2a662..6fbbd3ceba47109cf7bb2eb124e2b8686c498121 100644 --- a/fastplms/models/esmfold2/esmfold2_parsing.py +++ b/fastplms/models/esmfold2/esmfold2_parsing.py @@ -4,8 +4,9 @@ from __future__ import annotations import gzip import io + from collections.abc import Generator, Iterable -from contextlib import nullcontext +from contextlib import AbstractContextManager, nullcontext from pathlib import Path from typing import NamedTuple, TextIO @@ -45,7 +46,7 @@ def parse_fasta(text: str) -> Generator[FastaEntry, None, None]: raise ValueError("Found no sequences in input") -def _open_reader(source: PathOrBuffer): +def _open_reader(source: PathOrBuffer) -> AbstractContextManager[TextIO]: if isinstance(source, io.TextIOBase): return nullcontext(source) path = Path(source) @@ -93,7 +94,7 @@ def append_fasta_sequence(header: str, sequence: str, path: str | Path) -> None: handle.write(f">{header}\n{sequence}\n") -def _open_writer(destination: PathOrBuffer): +def _open_writer(destination: PathOrBuffer) -> AbstractContextManager[TextIO]: if isinstance(destination, io.TextIOBase): return nullcontext(destination) path = Path(destination) diff --git a/fastplms/models/esmfold2/esmfold2_predicted_aligned_error.py b/fastplms/models/esmfold2/esmfold2_predicted_aligned_error.py index d6ab3e74beb4ae86d22fb7d879126f5b23a1096e..ca3cb21ed19915d121373900124ec89e59603ab1 100644 --- a/fastplms/models/esmfold2/esmfold2_predicted_aligned_error.py +++ b/fastplms/models/esmfold2/esmfold2_predicted_aligned_error.py @@ -4,16 +4,19 @@ from __future__ import annotations import torch import torch.nn.functional as F + from torch import Tensor from .esmfold2_affine3d import Affine3D + _CPU_DEVICE = torch.device("cpu") def _compute_pae_masks(mask: Tensor) -> Tensor: - residue_mask = mask.bool() - return residue_mask.unsqueeze(-1) & residue_mask.unsqueeze(-2) + # mask: (..., l), where l is the residue count. + residue_mask = mask.bool() # (..., l) + return residue_mask.unsqueeze(-1) & residue_mask.unsqueeze(-2) # (..., l, l) def _pae_bins( @@ -23,19 +26,20 @@ def _pae_bins( ) -> Tensor: """Return the representative distance for each PAE probability bin.""" - boundaries = torch.linspace(0, max_bin, steps=num_bins - 1, device=device) + boundaries = torch.linspace(0, max_bin, steps=num_bins - 1, device=device) # (n_bins - 1,) width = max_bin / (num_bins - 2) - centers = boundaries + width / 2 - overflow_center = centers[-1:] + width - return torch.cat((centers, overflow_center)) + centers = boundaries + width / 2 # (n_bins - 1,) + overflow_center = centers[-1:] + width # (1,) + return torch.cat((centers, overflow_center)) # (n_bins,) def _masked_probabilities(logits: Tensor, pair_mask: Tensor) -> Tensor: + # logits: (..., l, l, n_bins); pair_mask: (..., l, l). masked_logits = logits.masked_fill( ~pair_mask.unsqueeze(-1), torch.finfo(logits.dtype).min, - ) - return masked_logits.softmax(dim=-1) + ) # (..., l, l, n_bins) + return masked_logits.softmax(dim=-1) # (..., l, l, n_bins) def masked_mean( @@ -46,10 +50,11 @@ def masked_mean( ) -> Tensor: """Average values over true entries of a broadcast-compatible mask.""" - weights = mask.expand_as(value) - weighted_sum = torch.sum(weights * value, dim=dim) - weight_sum = torch.sum(weights, dim=dim) - return weighted_sum / (weight_sum + eps) + # value has arbitrary shape; reduced_shape removes the axes named by dim. + weights = mask.expand_as(value) # value.shape + weighted_sum = torch.sum(weights * value, dim=dim) # reduced_shape + weight_sum = torch.sum(weights, dim=dim) # reduced_shape + return weighted_sum / (weight_sum + eps) # reduced_shape def compute_predicted_aligned_error( @@ -61,30 +66,31 @@ def compute_predicted_aligned_error( """Convert PAE logits ``X`` with shape (..., l, l, n) to distances.""" del sequence_id - pair_mask = _compute_pae_masks(aa_mask) - probabilities = _masked_probabilities(logits, pair_mask) - centers = _pae_bins(max_bin, logits.shape[-1], logits.device) - return torch.sum(probabilities * centers, dim=-1) + pair_mask = _compute_pae_masks(aa_mask) # (..., l, l) + probabilities = _masked_probabilities(logits, pair_mask) # (..., l, l, n_bins) + centers = _pae_bins(max_bin, logits.shape[-1], logits.device) # (n_bins,) + return torch.sum(probabilities * centers, dim=-1) # (..., l, l) @torch.no_grad() def compute_tm(logits: Tensor, aa_mask: Tensor, max_bin: float = 31.0) -> Tensor: - """Estimate TM score from pairwise PAE logits.""" + """Estimate TM score from logits (..., l, l, n_bins) and residue mask (..., l).""" - pair_mask = _compute_pae_masks(aa_mask) - sequence_lengths = aa_mask.sum(dim=-1, keepdim=True) - centers = _pae_bins(max_bin, logits.shape[-1], logits.device) - distance_scale = 1.24 * (sequence_lengths.clamp_min(19) - 15) ** (1 / 3) - 1.8 - tm_weights = 1.0 / (1 + (centers / distance_scale.unsqueeze(-1)) ** 2) - probabilities = _masked_probabilities(logits, pair_mask) - score_per_pair = torch.sum(probabilities * tm_weights.unsqueeze(-2), dim=-1) - score_per_anchor = masked_mean(pair_mask, score_per_pair, dim=-1) - return score_per_anchor.max(dim=-1).values + pair_mask = _compute_pae_masks(aa_mask) # (..., l, l) + sequence_lengths = aa_mask.sum(dim=-1, keepdim=True) # (..., 1) + centers = _pae_bins(max_bin, logits.shape[-1], logits.device) # (n_bins,) + distance_scale = 1.24 * (sequence_lengths.clamp_min(19) - 15) ** (1 / 3) - 1.8 # (..., 1) + tm_weights = 1.0 / (1 + (centers / distance_scale.unsqueeze(-1)) ** 2) # (..., 1, n_bins) + probabilities = _masked_probabilities(logits, pair_mask) # (..., l, l, n_bins) + score_per_pair = torch.sum(probabilities * tm_weights.unsqueeze(-2), dim=-1) # (..., l, l) + score_per_anchor = masked_mean(pair_mask, score_per_pair, dim=-1) # (..., l) + return score_per_anchor.max(dim=-1).values # (...,) def _local_coordinates(frames: Affine3D) -> Tensor: - origins = frames.trans[..., None, :, :] - return frames.invert()[..., None].apply(origins) + # frames.shape: (..., l); trans: (..., l, 3). + origins = frames.trans[..., None, :, :] # (..., 1, l, 3) + return frames.invert()[..., None].apply(origins) # (..., l, l, 3) def tm_loss( @@ -96,32 +102,32 @@ def tm_loss( sequence_id: Tensor | None = None, max_bin: float = 31, ) -> Tensor: - """Cross-entropy loss for discretized aligned-position errors.""" + """Cross-entropy loss for logits (b, l, l, n_bins) and residue frames (b, l).""" del sequence_id - predicted_frames = Affine3D.from_tensor(pred_affine) - target_frames = Affine3D.from_tensor(targ_affine) + predicted_frames = Affine3D.from_tensor(pred_affine) # frame shape: (b, l) + target_frames = Affine3D.from_tensor(targ_affine) # frame shape: (b, l) with torch.no_grad(): squared_error = ( (_local_coordinates(predicted_frames) - _local_coordinates(target_frames)) .square() .sum(dim=-1) - ) + ) # (b, l, l) boundaries = torch.linspace( 0, max_bin, logits.shape[-1] - 1, device=logits.device, - ).square() - target_bins = (squared_error[..., None] > boundaries).sum(dim=-1).long() + ).square() # (n_bins - 1,) + target_bins = (squared_error[..., None] > boundaries).sum(dim=-1).long() # (b, l, l) cross_entropy = F.cross_entropy( logits.movedim(3, 1), target_bins, reduction="none", - ) - pair_mask = _compute_pae_masks(targ_mask) - loss_per_sample = masked_mean(pair_mask, cross_entropy, dim=(-1, -2)) + ) # (b, l, l) + pair_mask = _compute_pae_masks(targ_mask) # (b, l, l) + loss_per_sample = masked_mean(pair_mask, cross_entropy, dim=(-1, -2)) # (b,) if tm_mask is None: - return loss_per_sample.mean() - return masked_mean(tm_mask, loss_per_sample) + return loss_per_sample.mean() # () + return masked_mean(tm_mask, loss_per_sample) # () diff --git a/fastplms/models/esmfold2/esmfold2_prepare_input.py b/fastplms/models/esmfold2/esmfold2_prepare_input.py index 870c45754d5e3d41d1f0db90ffb56eec2a5eb3fd..d8e99a8d2f0873158d8c68af09b72c7b5aaf5349 100644 --- a/fastplms/models/esmfold2/esmfold2_prepare_input.py +++ b/fastplms/models/esmfold2/esmfold2_prepare_input.py @@ -10,15 +10,15 @@ from __future__ import annotations import math import warnings +import numpy as np +import torch + from collections import defaultdict from contextlib import suppress from dataclasses import dataclass, field from itertools import combinations from typing import Any -import numpy as np -import torch - from .esmfold2_conformers import ( get_ccd_leaving_atoms, get_idealized_atom_pos, @@ -62,6 +62,7 @@ from .esmfold2_types import ( StructurePredictionInput, ) + _ZERO_POS = np.zeros(3, dtype=np.float32) _ENCODE_ATOM_NAME_CACHE: dict[str, list[int]] = {} _ELEMENT_ATOMIC_NUM_CACHE: dict[str, int] = {} diff --git a/fastplms/models/esmfold2/esmfold2_processor.py b/fastplms/models/esmfold2/esmfold2_processor.py index eab4d1b47480977f7f8d967b983100e20896af6b..891f86ec1de177beb59bbe0b90d9509712ad3e64 100644 --- a/fastplms/models/esmfold2/esmfold2_processor.py +++ b/fastplms/models/esmfold2/esmfold2_processor.py @@ -2,13 +2,13 @@ from __future__ import annotations +import numpy as np +import torch + from collections.abc import Mapping from dataclasses import dataclass from pathlib import Path from typing import Any - -import numpy as np -import torch from torch import Tensor from tqdm.auto import tqdm @@ -20,6 +20,7 @@ from .esmfold2_types import MSA, Modification, ProteinInput, StructurePrediction from .modeling_esmfold2_common import MSA_CONDITIONING_INPUT_NAMES from .reproducibility import seed_context + # Backward-compatible private alias for the pinned parity helpers. New callers # should import ``seed_context`` from the public ``fastplms.models.esmfold2`` # package instead of reaching into implementation modules. diff --git a/fastplms/models/esmfold2/esmfold2_protein_chain.py b/fastplms/models/esmfold2/esmfold2_protein_chain.py index acaaaf585e354204bb99916f9d2b2f515c58ea29..c0ba1cf078689a98c88a5f694dd2a4e782362de4 100644 --- a/fastplms/models/esmfold2/esmfold2_protein_chain.py +++ b/fastplms/models/esmfold2/esmfold2_protein_chain.py @@ -4,18 +4,18 @@ from __future__ import annotations import io import warnings -from collections.abc import Mapping, Sequence -from dataclasses import asdict, dataclass, replace -from functools import cached_property -from pathlib import Path -from typing import Any - import biotite.structure as bs import brotli import msgpack import msgpack_numpy import numpy as np import torch + +from collections.abc import Mapping, Sequence +from dataclasses import asdict, dataclass, replace +from functools import cached_property +from pathlib import Path +from typing import Any from biotite.database import rcsb from biotite.structure.io.pdb import PDBFile from biotite.structure.io.pdbx import CIFCategory, CIFColumn, CIFData, CIFFile @@ -42,6 +42,7 @@ from .esmfold2_normalize_coordinates import ( from .esmfold2_protein_structure import index_by_atom_name from .esmfold2_utils_types import PathOrBuffer + CHAIN_ID_CONST = "A" @@ -70,27 +71,30 @@ def infer_cb( dihedral: float = -2.143, ): """Infer C-beta coordinates from C, N, and C-alpha coordinates.""" + # C, N, Ca: (..., 3); xyz coordinates with broadcast-compatible leading axes. def normalize(X: np.ndarray) -> np.ndarray: - return X / np.sqrt(np.square(X).sum(-1, keepdims=True) + 1e-8) + # X: (..., 3); normalize each xyz vector independently. + return X / np.sqrt(np.square(X).sum(-1, keepdims=True) + 1e-8) # X.shape; normalized along xyz with np.errstate(invalid="ignore"): - n_to_ca = N - Ca - n_to_c = N - C - axis = normalize(n_to_ca) - normal = normalize(np.cross(n_to_c, axis)) - basis = (axis, np.cross(normal, axis), normal) + n_to_ca = N - Ca # (..., 3) + n_to_c = N - C # (..., 3) + axis = normalize(n_to_ca) # (..., 3) + normal = normalize(np.cross(n_to_c, axis)) # (..., 3) + basis = (axis, np.cross(normal, axis), normal) # three arrays (..., 3) offsets = ( bond_length * np.cos(bond_angle), bond_length * np.sin(bond_angle) * np.cos(dihedral), -bond_length * np.sin(bond_angle) * np.sin(dihedral), - ) - return Ca + sum(vector * offset for vector, offset in zip(basis, offsets, strict=False)) + ) # three NumPy scalars, each () + return Ca + sum(vector * offset for vector, offset in zip(basis, offsets, strict=False)) # (..., 3) def chain_to_ndarray( atom_array: bs.AtomArray, mmcif: MmcifWrapper, chain_id: str, is_predicted=False ): + # atom_array: n input atoms; l is the selected chain's sequence length. if not isinstance(atom_array, bs.AtomArray): raise TypeError("atom_array must be a biotite AtomArray.") if not isinstance(mmcif, MmcifWrapper): @@ -106,14 +110,14 @@ def chain_to_ndarray( num_res = len(mmcif.chain_to_seqres[chain_id]) sequence = mmcif.chain_to_seqres[chain_id] - atom_positions = np.full([num_res, residue_constants.atom_type_num, 3], np.nan) - atom_mask = np.full([num_res, residue_constants.atom_type_num], False, dtype=bool) - residue_index = np.full([num_res], -1, dtype=np.int64) - insertion_code = np.full([num_res], "", dtype=" ProteinChain: """A simple converter from bs.AtomArray -> ProteinChain. Uses PDB file format as intermediate.""" - atom_array = atom_array.copy() + atom_array = atom_array.copy() # AtomArray copy with unchanged atom count atom_array.box = None # remove surrounding box, from_pdb won't handle this pdb_file = PDBFile() # pyright: ignore pdb_file.set_structure(atom_array) @@ -596,7 +604,7 @@ class ProteinChain: "residue_index": self.residue_index, "insertion_code": self.insertion_code, "confidence": self.confidence, - } + } # arrays share l: positions (l, 37, 3), masks (l, 37), residue fields (l,) for name, values in aligned.items(): if not isinstance(values, np.ndarray): raise TypeError(f"{name} must be a NumPy array, got {type(values).__name__}.") @@ -636,7 +644,7 @@ class ProteinChain: ) if not np.issubdtype(self.confidence.dtype, np.number): raise TypeError("confidence must use a numeric dtype.") - atom37_confidence = self.atom37_confidence + atom37_confidence = self.atom37_confidence # (l, 37) or None if atom37_confidence is not None and not isinstance(atom37_confidence, np.ndarray): raise TypeError("atom37_confidence must be a NumPy array when provided.") if ( @@ -686,7 +694,7 @@ class ProteinChain: self.atom37_confidence[res_idx_i, i] if self.atom37_confidence is not None else conf - ) + ) # scalar atom confidence atom = bs.Atom( coord=pos, chain_id="A" if self.chain_id is None else self.chain_id, @@ -700,7 +708,7 @@ class ProteinChain: occupancy=1.0, ) atoms.append(atom) - return bs.array(atoms) + return bs.array(atoms) # AtomArray with n_present_atoms entries # Coordinate transformations and dataset adapters def get_normalization_frame(self) -> Affine3D: @@ -711,8 +719,8 @@ class ProteinChain: Returns: Affine3D: [] tensor of Affine3D frame """ - coords = torch.from_numpy(self.atom37_positions) - frame = get_protein_normalization_frame(coords) + coords = torch.from_numpy(self.atom37_positions) # (l, 37, 3) + frame = get_protein_normalization_frame(coords) # one normalization frame return frame @@ -725,9 +733,10 @@ class ProteinChain: Returns: ProteinChain: Transformed protein chain """ - coords = torch.from_numpy(self.atom37_positions).to(frame.trans.dtype) - coords = apply_frame_to_coords(coords, frame) - atom37_positions = coords.numpy() + # frame is a rigid transform broadcast-compatible with (l, 37, 3) coordinates. + coords = torch.from_numpy(self.atom37_positions).to(frame.trans.dtype) # (l, 37, 3) + coords = apply_frame_to_coords(coords, frame) # (l, 37, 3) + atom37_positions = coords.numpy() # (l, 37, 3) return replace(self, atom37_positions=atom37_positions) def normalize_coordinates(self) -> ProteinChain: @@ -736,36 +745,36 @@ class ProteinChain: def infer_oxygen(self) -> ProteinChain: """Oxygen position is fixed given N, CA, C atoms. Infer it if not provided.""" - O_missing_indices = np.argwhere(~np.isfinite(self.atoms["O"]).all(axis=1)).squeeze() + O_missing_indices = np.argwhere(~np.isfinite(self.atoms["O"]).all(axis=1)).squeeze() # (n_missing,) or () when exactly one oxygen is missing - O_vector = torch.tensor([0.6240, -1.0613, 0.0103], dtype=torch.float32) - N, CA, C = torch.from_numpy(self.atoms[["N", "CA", "C"]]).float().unbind(dim=1) - N = torch.roll(N, -3) - N[..., -1, :] = torch.nan + O_vector = torch.tensor([0.6240, -1.0613, 0.0103], dtype=torch.float32) # (3,) + N, CA, C = torch.from_numpy(self.atoms[["N", "CA", "C"]]).float().unbind(dim=1) # each (l, 3) + N = torch.roll(N, -3) # (l, 3); torch.roll keeps the original shape + N[..., -1, :] = torch.nan # (3,) xyz row # Get the frame defined by the CA-C-N atom - frames = Affine3D.from_graham_schmidt(CA, C, N) - oxygen_coordinates = frames.apply(O_vector) - atom37_positions = self.atom37_positions.copy() - atom37_mask = self.atom37_mask.copy() + frames = Affine3D.from_graham_schmidt(CA, C, N) # affine batch shape: (l,) + oxygen_coordinates = frames.apply(O_vector) # (l, 3) + atom37_positions = self.atom37_positions.copy() # (l, 37, 3) + atom37_mask = self.atom37_mask.copy() # (l, 37) atom37_positions[O_missing_indices, residue_constants.atom_order["O"]] = oxygen_coordinates[ O_missing_indices - ].numpy() + ].numpy() # (n_missing, 3) or (3,) selected oxygen coordinates atom37_mask[O_missing_indices, residue_constants.atom_order["O"]] = ~np.isnan( atom37_positions[O_missing_indices, residue_constants.atom_order["O"]] - ).any(-1) + ).any(-1) # (n_missing,) or () selected oxygen mask new_chain = replace(self, atom37_positions=atom37_positions, atom37_mask=atom37_mask) return new_chain @cached_property def inferred_cbeta(self) -> np.ndarray: """Infer cbeta positions based on N, C, CA.""" - N, CA, C = np.moveaxis(self.atoms[["N", "CA", "C"]], 1, 0) + N, CA, C = np.moveaxis(self.atoms[["N", "CA", "C"]], 1, 0) # each (l, 3) # See usage in trDesign codebase. # https://github.com/gjoni/trDesign/blob/f2d5930b472e77bfacc2f437b3966e7a708a8d37/02-GD/utils.py#L140 - CB = infer_cb(C, N, CA, 1.522, 1.927, -2.143) - return CB + CB = infer_cb(C, N, CA, 1.522, 1.927, -2.143) # (l, 3) + return CB # (l, 3) def infer_cbeta(self, infer_cbeta_for_glycine: bool = False) -> ProteinChain: """Return a new chain with inferred CB atoms at all residues except GLY. @@ -780,30 +789,30 @@ class ProteinChain: calculation between two designs for a given structural template, w/ CB atoms. """ - atom37_positions = self.atom37_positions.copy() - atom37_mask = self.atom37_mask.copy() + atom37_positions = self.atom37_positions.copy() # (l, 37, 3) + atom37_mask = self.atom37_mask.copy() # (l, 37) - inferred_cbeta_positions = self.inferred_cbeta + inferred_cbeta_positions = self.inferred_cbeta # (l, 3) if not infer_cbeta_for_glycine: - inferred_cbeta_positions[np.array(list(self.sequence)) == "G", :] = np.nan + inferred_cbeta_positions[np.array(list(self.sequence)) == "G", :] = np.nan # (n_glycine, 3) selected rows - atom37_positions[:, residue_constants.atom_order["CB"]] = inferred_cbeta_positions + atom37_positions[:, residue_constants.atom_order["CB"]] = inferred_cbeta_positions # (l, 3) C-beta slice atom37_mask[:, residue_constants.atom_order["CB"]] = ~np.isnan( atom37_positions[:, residue_constants.atom_order["CB"]] - ).any(-1) + ).any(-1) # (l,) C-beta mask new_chain = replace(self, atom37_positions=atom37_positions, atom37_mask=atom37_mask) return new_chain @cached_property def pdist_CA(self) -> np.ndarray: - CA = self.atoms["CA"] - pdist_CA = squareform(pdist(CA)) - return pdist_CA + CA = self.atoms["CA"] # (l, 3) + pdist_CA = squareform(pdist(CA)) # (l, l) + return pdist_CA # (l, l) @cached_property def pdist_CB(self) -> np.ndarray: - pdist_CB = squareform(pdist(self.inferred_cbeta)) - return pdist_CB + pdist_CB = squareform(pdist(self.inferred_cbeta)) # (l, l) + return pdist_CB # (l, l) @classmethod def as_complex(cls, chains: Sequence[ProteinChain]): @@ -824,23 +833,24 @@ class ProteinChain: "atom37_positions": np.full([1, 37, 3], np.inf), "atom37_mask": np.zeros([1, 37], dtype=bool), "confidence": np.array([0]), - } + } # one-residue separator arrays: (1,), (1, 37, 3), or (1, 37) def join_arrays(arrays: Sequence[np.ndarray], sep: np.ndarray): + # arrays: (l_i, *trailing_shape); separator: (1, *trailing_shape). if use_chainbreak: full_array = [] for array in arrays: full_array.append(array) full_array.append(sep) full_array = full_array[:-1] - return np.concatenate(full_array, 0) + return np.concatenate(full_array, 0) # (sum(chain_lengths) + n_chains - 1, *trailing_shape) else: - return np.concatenate(arrays, 0) + return np.concatenate(arrays, 0) # (sum(chain_lengths), *trailing_shape) array_args: dict[str, np.ndarray] = { name: join_arrays([getattr(chain, name) for chain in chains], sep) for name, sep in sep_tokens.items() - } + } # each array retains its trailing atom/xyz axes chain_break = residue_constants.CHAIN_BREAK_TOKEN if use_chainbreak else "" return cls( @@ -868,15 +878,15 @@ class ProteinChain: raise ValueError( f"Non-polymer {nonpolymer.comp_id!r} has no coordinate table." ) - chain_coords = self.atom37_positions[self.atom37_mask] - distance = cdist(nonpolymer_array.coord, chain_coords) + chain_coords = self.atom37_positions[self.atom37_mask] # (n_present_atoms, 3) + distance = cdist(nonpolymer_array.coord, chain_coords) # (n_ligand_atoms, n_present_atoms) - is_contact = distance < 5 + is_contact = distance < 5 # (n_ligand_atoms, n_present_atoms) if not is_contact.any(): continue - contacting_atoms = np.where(is_contact.any(0))[0] - chain_index = np.where(self.atom37_mask)[0] - contacting_residues = np.unique(chain_index[contacting_atoms]) + contacting_atoms = np.where(is_contact.any(0))[0] # (n_contacting_atoms,) + chain_index = np.where(self.atom37_mask)[0] # (n_present_atoms,) + contacting_residues = np.unique(chain_index[contacting_atoms]) # (n_contacting_residues,) result = { "ligand": nonpolymer.name, @@ -890,7 +900,7 @@ class ProteinChain: self, indices: list[int | str], ignore_x_mismatch: bool = False ) -> ProteinChain: numeric_indices = [idx if isinstance(idx, int) else int(idx[1:]) for idx in indices] - mask = np.isin(self.residue_index, numeric_indices) + mask = np.isin(self.residue_index, numeric_indices) # (l,) new = self[mask] mismatches = [] for aa, idx in zip(new.sequence, indices, strict=False): @@ -922,20 +932,22 @@ class ProteinChain: # Convert to tensors and add batch dimension coordinates = ( torch.from_numpy(self.atom37_positions).float().unsqueeze(0) - ) # X has shape (1, l, 37, 3). - plddt = torch.from_numpy(self.confidence).float().unsqueeze(0) # P: (1, l) + ) # X has shape (1, l, 37, 3).; (1, l, 37, 3) + plddt = torch.from_numpy(self.confidence).float().unsqueeze(0) # P: (1, l); (1, l) residue_index = ( torch.from_numpy(self.residue_index).long().unsqueeze(0) - ) # R has shape (1, l). + ) # R has shape (1, l).; (1, l) - return coordinates, plddt, residue_index + return coordinates, plddt, residue_index # (1, l, 37, 3), (1, l), (1, l) # Sequence access, interchange, and compact storage def __getitem__(self, idx: int | list[int] | slice | np.ndarray | torch.Tensor): + # idx selects residues; an integer is promoted to a length-one index. + # Returned fields retain atom/xyz trailing axes with the selected residue count. if isinstance(idx, int): idx = [idx] if isinstance(idx, torch.Tensor): - idx = idx.cpu().numpy() + idx = idx.cpu().numpy() # same index shape sequence = slice_python_object_as_numpy(self.sequence, idx) return replace( @@ -955,11 +967,11 @@ class ProteinChain: return len(self.sequence) def cbeta_contacts(self, distance_threshold: float = 8.0) -> np.ndarray: - distance = self.pdist_CB - contacts = (distance < distance_threshold).astype(np.int64) - contacts[np.isnan(distance)] = -1 + distance = self.pdist_CB # (l, l) + contacts = (distance < distance_threshold).astype(np.int64) # (l, l) + contacts[np.isnan(distance)] = -1 # (n_missing_pairs,) selected entries np.fill_diagonal(contacts, -1) - return contacts + return contacts # (l, l) def to_pdb(self, path: PathOrBuffer, include_insertions: bool = True): """Dssp works better w/o insertions.""" @@ -989,7 +1001,7 @@ class ProteinChain: "mode": CIFColumn(data=CIFData(array=np.array(["global", "local"]), dtype=np.str_)), "name": CIFColumn(data=CIFData(array=np.array(["pLDDT", "pLDDT"]), dtype=np.str_)), }, - ) + ) # each CIF metric column has shape (2,) # table is a duplicate of data already in the atom array, but # needed by molstar to render pLDDT / confidence @@ -1039,10 +1051,10 @@ class ProteinChain: need more than 2**32 residues...""" dct = {k: v for k, v in asdict(self).items() if k not in ["mmcif"]} if backbone_only: - dct["atom37_mask"][:, 3:] = False - dct["atom37_positions"] = dct["atom37_positions"][dct["atom37_mask"]] + dct["atom37_mask"][:, 3:] = False # (l, 34) mask slice for atoms beyond N/CA/C + dct["atom37_positions"] = dct["atom37_positions"][dct["atom37_mask"]] # (n_present_atoms, 3) if dct.get("atom37_confidence") is not None: - dct["atom37_confidence"] = dct["atom37_confidence"][dct["atom37_mask"]] + dct["atom37_confidence"] = dct["atom37_confidence"][dct["atom37_mask"]] # (n_present_atoms,) else: dct.pop("atom37_confidence", None) @@ -1050,9 +1062,9 @@ class ProteinChain: if isinstance(v, np.ndarray): match v.dtype: case np.int64: - dct[k] = v.astype(np.int32) + dct[k] = v.astype(np.int32) # v.shape case np.float64 | np.float32: - dct[k] = v.astype(np.float16) + dct[k] = v.astype(np.float16) # v.shape case _: pass if json_serializable: @@ -1074,15 +1086,15 @@ class ProteinChain: for k, v in dct.items(): if isinstance(v, list): - dct[k] = np.array(v) + dct[k] = np.array(v) # shape inferred from serialized nested list - atom37 = np.full((*dct["atom37_mask"].shape, 3), np.nan) - atom37[dct["atom37_mask"]] = dct["atom37_positions"] - dct["atom37_positions"] = atom37 + atom37 = np.full((*dct["atom37_mask"].shape, 3), np.nan) # (l, 37, 3) + atom37[dct["atom37_mask"]] = dct["atom37_positions"] # (n_present_atoms, 3) selected coordinates + dct["atom37_positions"] = atom37 # (l, 37, 3) if "atom37_confidence" in dct: - atom37_conf = np.full(dct["atom37_mask"].shape, np.nan, dtype=np.float32) - atom37_conf[dct["atom37_mask"]] = dct["atom37_confidence"] - dct["atom37_confidence"] = atom37_conf + atom37_conf = np.full(dct["atom37_mask"].shape, np.nan, dtype=np.float32) # (l, 37) + atom37_conf[dct["atom37_mask"]] = dct["atom37_confidence"] # (n_present_atoms,) selected confidence values + dct["atom37_confidence"] = atom37_conf # (l, 37) dct = { k: ( v.astype(np.float32) @@ -1091,7 +1103,7 @@ class ProteinChain: ) for k, v in dct.items() if not (k == "atom37_confidence" and v is None) - } + } # each converted array retains its serialized field shape return cls(**dct, mmcif=None) @classmethod @@ -1111,10 +1123,10 @@ class ProteinChain: # Surface and structural comparison metrics def sasa(self, by_residue: bool = True): - arr = self.atom_array_no_insertions + arr = self.atom_array_no_insertions # AtomArray with n_present_atoms entries if len(arr) == 0: raise ValueError("SASA requires at least one resolved atom.") - sasa_per_atom = bs.sasa(arr) # type: ignore + sasa_per_atom = bs.sasa(arr) # type: ignore; (n_present_atoms,) if by_residue: # Sum per-atom SASA into residue "bins", with np.bincount. if arr.res_id is None: @@ -1127,12 +1139,12 @@ class ProteinChain: np.bincount(arr.res_id, weights=sasa_per_atom)[1:], np.zeros(num_trailing_residues), ] - ) - sasa_per_residue[~self.atom37_mask.any(-1)] = np.nan + ) # (l,) + sasa_per_residue[~self.atom37_mask.any(-1)] = np.nan # (n_missing_residues,) selected entries if len(sasa_per_residue) != len(self): raise RuntimeError("Residue SASA output does not align with the protein chain.") - return sasa_per_residue - return sasa_per_atom + return sasa_per_residue # (l,) + return sasa_per_atom # (n_present_atoms,) def sap_score(self, aggregation: str = "atom") -> np.ndarray: """Compute per-atom spatial aggregation propensity (SAP). @@ -1141,7 +1153,7 @@ class ProteinChain: Protein aggregation sums positive atom scores, following Lauer et al. 2011. """ sap_radius = 5.0 - arr = self.atom_array_no_insertions + arr = self.atom_array_no_insertions # AtomArray with n_present_atoms entries if len(arr) == 0: raise ValueError("SAP requires at least one resolved atom.") @@ -1150,42 +1162,42 @@ class ProteinChain: raise RuntimeError(f"Biotite AtomArray is missing required {name!r} data.") # compute SASA and residue-specific properties - sasa_per_atom = self.sasa(by_residue=False) + sasa_per_atom = self.sasa(by_residue=False) # (n_present_atoms,) resid_to_resname = dict(zip(arr.res_id, arr.res_name, strict=False)) - max_side_chain_asa = np.full(len(self), np.nan) - res_hydrophobicity = np.full(len(self), np.nan) - resolved_res_mask = self.atom37_mask.any(-1) + max_side_chain_asa = np.full(len(self), np.nan) # (l,) + res_hydrophobicity = np.full(len(self), np.nan) # (l,) + resolved_res_mask = self.atom37_mask.any(-1) # (l,) num_trailing_residues = len(self) - arr.res_id.max() max_side_chain_asa[resolved_res_mask] = np.array( [residue_constants.side_chain_asa[resid_to_resname[i]] for i in np.unique(arr.res_id)] - ) + ) # (n_resolved_residues,) selected entries res_hydrophobicity[resolved_res_mask] = np.array( [residue_constants.hydrophobicity[resid_to_resname[i]] for i in np.unique(arr.res_id)] - ) + ) # (n_resolved_residues,) selected entries # compute SAP score - is_side_chain = ~bs.filter_peptide_backbone(arr) - sasa_per_atom[is_side_chain] = 0 + is_side_chain = ~bs.filter_peptide_backbone(arr) # (n_present_atoms,) + sasa_per_atom[is_side_chain] = 0 # (n_selected_atoms,) selected entries kdtree = KDTree(arr.coord) neighbors = kdtree.query_ball_tree(kdtree, sap_radius, p=2.0) - sap_by_atom = np.zeros_like(sasa_per_atom) + sap_by_atom = np.zeros_like(sasa_per_atom) # (n_present_atoms,) for i, nn_list in enumerate(neighbors): - saa_nn = np.zeros_like(sasa_per_atom) - saa_nn[nn_list] = sasa_per_atom[nn_list] + saa_nn = np.zeros_like(sasa_per_atom) # (n_present_atoms,) + saa_nn[nn_list] = sasa_per_atom[nn_list] # (n_neighbors,) selected entries sasa_within_r = np.concatenate( [ np.bincount(arr.res_id, weights=saa_nn)[1:], np.zeros(num_trailing_residues), ] - ) - sap = np.nansum((sasa_within_r / max_side_chain_asa) * res_hydrophobicity) - sap_by_atom[i] = sap + ) # (l,) + sap = np.nansum((sasa_within_r / max_side_chain_asa) * res_hydrophobicity) # scalar NumPy reduction + sap_by_atom[i] = sap # scalar entry in (n_present_atoms,) match aggregation: case "atom": - return sap_by_atom + return sap_by_atom # (n_present_atoms,) case "residue": sap_by_residue = np.concatenate( [ @@ -1195,11 +1207,11 @@ class ProteinChain: ) / ( np.concatenate([np.bincount(arr.res_id)[1:], np.zeros(num_trailing_residues)]) + 1e-8 - ) - sap_by_residue[~resolved_res_mask] = np.nan + ) # (l,) + sap_by_residue[~resolved_res_mask] = np.nan # (n_missing_residues,) selected entries if len(sap_by_residue) != len(self): raise RuntimeError("Residue SAP output does not align with the protein chain.") - return sap_by_residue + return sap_by_residue # (l,) case "protein": return sum(sap_by_atom[sap_by_atom > 0]) # pyright: ignore[reportReturnType] case _: @@ -1216,19 +1228,19 @@ class ProteinChain: # https://www.mdpi.com/2073-4352/11/12/1539 # The non-overlapping-atom approximation can produce globularity above one. - mask = self.atom37_mask.any(-1) - points = self.atom37_positions[self.atom37_mask] + mask = self.atom37_mask.any(-1) # (l,) + points = self.atom37_positions[self.atom37_mask] # (n_present_atoms, 3) sequence = [aa for aa, m in zip(self.sequence, mask, strict=False) if m] # type: ignore - A, _ = self._mvee(points, tol=1e-3) - mvee_volume = (4 * np.pi) / (3 * np.sqrt(np.linalg.det(A))) + A, _ = self._mvee(points, tol=1e-3) # A: (3, 3), center: (3, 1) + mvee_volume = (4 * np.pi) / (3 * np.sqrt(np.linalg.det(A))) # NumPy scalar () volume = sum(residue_constants.amino_acid_volumes[x] for x in sequence) ratio = volume / mvee_volume # The paper compares the ellipsoidal profile with scalar t, a measurement # of elongation. We want a single number, so we multiply by 1/(2t), so # that value is normalized between 0-1 - eigenvalues = np.linalg.eigvals(A) - R = 1 / np.sqrt(eigenvalues) + eigenvalues = np.linalg.eigvals(A) # (3,) + R = 1 / np.sqrt(eigenvalues) # (3,) # ellipsoid radii length triangle inequality coefficient t = max(R[0] / (R[1] + R[2]), R[1] / (R[0] + R[2]), R[2] / (R[0] + R[1])) elongation_metric = 1 / max(t, 1) @@ -1239,50 +1251,51 @@ class ProteinChain: # Finds minimum volume enclosing ellipsoid of a set of points. # Returns A, c where the ellipse is defined as: # (x-c).T @ A @ (x-c) = 1 + # P: (n_input_points, d); d is coordinate dimension. Hull selection changes only point count. hull = ConvexHull(P) - P = P[hull.vertices] - P = P.T + P = P[hull.vertices] # (n_hull_vertices, d) + P = P.T # (d, n); n = n_hull_vertices # Data points d, n = P.shape - Q = np.zeros((d + 1, n)) - Q[:d, :] = P[:d, :n] - Q[d, :] = np.ones((1, n)) + Q = np.zeros((d + 1, n)) # (d + 1, n) + Q[:d, :] = P[:d, :n] # (d, n) slice + Q[d, :] = np.ones((1, n)) # (n,) row, assigned from (1, n) # Initializations count = 1 err = 1.0 - u = np.full((n, 1), 1 / n) # First iteration. + u = np.full((n, 1), 1 / n) # First iteration.; (n, 1) # Khachiyan Algorithm for _ in range(max_iter): - X = Q.dot(np.diag(u.squeeze())) @ Q.T - M = np.diag(Q.T @ np.linalg.inv(X) @ Q) - maximum, j = np.max(M), np.argmax(M) + X = Q.dot(np.diag(u.squeeze())) @ Q.T # (d + 1, d + 1) + M = np.diag(Q.T @ np.linalg.inv(X) @ Q) # (n,) + maximum, j = np.max(M), np.argmax(M) # each NumPy scalar () step_size = (maximum - d - 1) / ((d + 1) * (maximum - 1)) - new_u = (1 - step_size) * u - new_u[j] += step_size + new_u = (1 - step_size) * u # (n, 1) + new_u[j] += step_size # (1,) selected row count += 1 - err = np.linalg.norm(new_u - u) - u = new_u + err = np.linalg.norm(new_u - u) # NumPy scalar () + u = new_u # (n, 1) if err < tol: break else: raise ValueError("MVEE did not converge") d = P.shape[0] # Fixed: use P.shape[0] instead of P.shape - U = np.diag(u.squeeze()) + U = np.diag(u.squeeze()) # (n, n) # The A matrix for the ellipse - A = (1 / d) * np.linalg.inv(P @ U @ P.T - (P @ u) @ (P @ u).T) + A = (1 / d) * np.linalg.inv(P @ U @ P.T - (P @ u) @ (P @ u).T) # (d, d) # Center of the ellipse - c = P @ u + c = P @ u # (d, 1) - return A, c + return A, c # (d, d), (d, 1) def radius_of_gyration(self): - arr = self.atom_array_no_insertions + arr = self.atom_array_no_insertions # AtomArray with n_present_atoms entries return bs.gyration_radius(arr) def align( @@ -1375,8 +1388,8 @@ class ProteinChain: torch.tensor(native.atom37_positions[target_inds]).unsqueeze(0), torch.tensor(native.atom37_mask[mobile_inds]).unsqueeze(0), **kwargs, - ) - return float(lddt) if lddt.numel() == 1 else lddt.numpy().flatten() + ) # score shape follows selected coordinate axes and per_residue + return float(lddt) if lddt.numel() == 1 else lddt.numpy().flatten() # scalar or (lddt.numel(),) def gdt_ts( self, @@ -1409,12 +1422,12 @@ class ProteinChain: & index_by_atom_name(target.atom37_mask[target_inds], "CA", dim=-1) ).unsqueeze(0), **kwargs, - ) - return float(gdt_ts) if gdt_ts.numel() == 1 else gdt_ts.numpy().flatten() + ) # () or (n_samples,), selected by reduction + return float(gdt_ts) if gdt_ts.numel() == 1 else gdt_ts.numpy().flatten() # scalar or (gdt_ts.numel(),) @cached_property def residue_index_no_insertions(self) -> np.ndarray: - return self.residue_index + np.cumsum(self.insertion_code != "") + return self.residue_index + np.cumsum(self.insertion_code != "") # (l,) @cached_property def atom_array_no_insertions(self) -> bs.AtomArray: @@ -1433,7 +1446,7 @@ class ProteinChain: self.atom37_confidence[res_idx, i] if self.atom37_confidence is not None else conf - ) + ) # scalar atom confidence atom = bs.Atom( coord=pos, # hard coded to as we currently only support single chain structures @@ -1447,4 +1460,4 @@ class ProteinChain: occupancy=1.0, ) atoms.append(atom) - return bs.array(atoms) + return bs.array(atoms) # AtomArray with n_present_atoms entries diff --git a/fastplms/models/esmfold2/esmfold2_protein_complex.py b/fastplms/models/esmfold2/esmfold2_protein_complex.py index 688ea5ce68efecdcc522e1ec4015d60d12bf782a..5dd6998c3d8ef952bb57bd0e509f2001b6ee082f 100644 --- a/fastplms/models/esmfold2/esmfold2_protein_complex.py +++ b/fastplms/models/esmfold2/esmfold2_protein_complex.py @@ -7,6 +7,13 @@ import itertools import random import re import warnings +import biotite.structure as bs +import brotli +import msgpack +import msgpack_numpy +import numpy as np +import torch + from collections.abc import Iterable, Sequence from dataclasses import asdict, dataclass, replace from functools import cached_property @@ -14,13 +21,6 @@ from pathlib import Path from subprocess import check_output from tempfile import TemporaryDirectory from typing import Any - -import biotite.structure as bs -import brotli -import msgpack -import msgpack_numpy -import numpy as np -import torch from biotite.database import rcsb from biotite.file import InvalidFileError from biotite.structure.io.pdb import PDBFile @@ -50,6 +50,7 @@ from .esmfold2_protein_chain import ( ) from .esmfold2_utils_types import PathOrBuffer + SINGLE_LETTER_CHAIN_IDS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -73,14 +74,15 @@ def _parse_operation_expression(expression: str) -> list[tuple[str, ...]]: def _apply_transformations_fast(chains, transformation_dict, operations): """Return transformed copies of each affected protein chain.""" + # Each chain supplies coordinates (l, 37, 3); rotations are (3, 3), translations are (3,). transformed_chains = [] for chain in chains: for operation in operations: - coordinates = chain.atom37_positions.copy() + coordinates = chain.atom37_positions.copy() # (l, 37, 3) for op_step in operation: transform = transformation_dict[op_step] - coordinates = matrix_rotate(coordinates, transform.rotation) - coordinates += transform.target_translation + coordinates = matrix_rotate(coordinates, transform.rotation) # (l, 37, 3) + coordinates += transform.target_translation # (l, 37, 3) transformed_chains.append(replace(chain, atom37_positions=coordinates)) return transformed_chains @@ -124,16 +126,17 @@ class DockQResult: class ProteinComplex: """Dataclass with atom37 representation of an entire protein complex.""" + # l is len(sequence), including separator rows when present. id: str sequence: str entity_id: np.ndarray # entities map to unique sequences chain_id: np.ndarray # multiple chains might share an entity id sym_id: np.ndarray # complexes might be copies of the same chain - residue_index: np.ndarray - insertion_code: np.ndarray - atom37_positions: np.ndarray - atom37_mask: np.ndarray - confidence: np.ndarray + residue_index: np.ndarray # (l,) + insertion_code: np.ndarray # (l,) + atom37_positions: np.ndarray # (l, 37, 3) + atom37_mask: np.ndarray # (l, 37) + confidence: np.ndarray # (l,) # This metadata is parsed from the MMCIF file. For synthetic data, we do a best effort. metadata: ProteinComplexMetadata atom37_confidence: np.ndarray | None = None # P has shape (l, 37). @@ -141,25 +144,25 @@ class ProteinComplex: # Coordinate completion, concatenation, and comparison def infer_oxygen(self) -> ProteinComplex: """Oxygen position is fixed given N, CA, C atoms. Infer it if not provided.""" - O_missing_indices = np.argwhere(~np.isfinite(self.atoms["O"]).all(axis=1)).squeeze() + O_missing_indices = np.argwhere(~np.isfinite(self.atoms["O"]).all(axis=1)).squeeze() # (n_missing,) or () when exactly one oxygen is missing - O_vector = torch.tensor([0.6240, -1.0613, 0.0103], dtype=torch.float32) - N, CA, C = torch.from_numpy(self.atoms[["N", "CA", "C"]]).float().unbind(dim=1) - N = torch.roll(N, -3) - N[..., -1, :] = torch.nan + O_vector = torch.tensor([0.6240, -1.0613, 0.0103], dtype=torch.float32) # (3,) + N, CA, C = torch.from_numpy(self.atoms[["N", "CA", "C"]]).float().unbind(dim=1) # each (l, 3) + N = torch.roll(N, -3) # (l, 3); torch.roll keeps the original shape + N[..., -1, :] = torch.nan # (3,) xyz row # Get the frame defined by the CA-C-N atom - frames = Affine3D.from_graham_schmidt(CA, C, N) - oxygen_coordinates = frames.apply(O_vector) - atom37_positions = self.atom37_positions.copy() - atom37_mask = self.atom37_mask.copy() + frames = Affine3D.from_graham_schmidt(CA, C, N) # affine batch shape: (l,) + oxygen_coordinates = frames.apply(O_vector) # (l, 3) + atom37_positions = self.atom37_positions.copy() # (l, 37, 3) + atom37_mask = self.atom37_mask.copy() # (l, 37) atom37_positions[O_missing_indices, residue_constants.atom_order["O"]] = oxygen_coordinates[ O_missing_indices - ].numpy() + ].numpy() # (n_missing, 3) or (3,) selected oxygen coordinates atom37_mask[O_missing_indices, residue_constants.atom_order["O"]] = ~np.isnan( atom37_positions[O_missing_indices, residue_constants.atom_order["O"]] - ).any(-1) + ).any(-1) # (n_missing,) or () selected oxygen mask new_chain = replace(self, atom37_positions=atom37_positions, atom37_mask=atom37_mask) return new_chain @@ -176,20 +179,20 @@ class ProteinComplex: calculation between two designs for a given structural template, w/ CB atoms. """ - atom37_positions = self.atom37_positions.copy() - atom37_mask = self.atom37_mask.copy() + atom37_positions = self.atom37_positions.copy() # (l, 37, 3) + atom37_mask = self.atom37_mask.copy() # (l, 37) - N, CA, C = np.moveaxis(self.atoms[["N", "CA", "C"]], 1, 0) + N, CA, C = np.moveaxis(self.atoms[["N", "CA", "C"]], 1, 0) # each (l, 3) # See usage in trDesign codebase. # https://github.com/gjoni/trDesign/blob/f2d5930b472e77bfacc2f437b3966e7a708a8d37/02-GD/utils.py#L140 - inferred_cbeta_positions = infer_cb(C, N, CA, 1.522, 1.927, -2.143) + inferred_cbeta_positions = infer_cb(C, N, CA, 1.522, 1.927, -2.143) # (l, 3) if not infer_cbeta_for_glycine: - inferred_cbeta_positions[np.array(list(self.sequence)) == "G", :] = np.nan + inferred_cbeta_positions[np.array(list(self.sequence)) == "G", :] = np.nan # (n_glycine, 3) selected rows - atom37_positions[:, residue_constants.atom_order["CB"]] = inferred_cbeta_positions + atom37_positions[:, residue_constants.atom_order["CB"]] = inferred_cbeta_positions # (l, 3) C-beta slice atom37_mask[:, residue_constants.atom_order["CB"]] = ~np.isnan( atom37_positions[:, residue_constants.atom_order["CB"]] - ).any(-1) + ).any(-1) # (l,) C-beta mask new_chain = replace(self, atom37_positions=atom37_positions, atom37_mask=atom37_mask) return new_chain @@ -281,8 +284,8 @@ class ProteinComplex: torch.tensor(target.atom37_positions[target_inds]).unsqueeze(0), torch.tensor(aligned.atom37_mask[mobile_inds]).unsqueeze(0), **kwargs, - ) - return float(lddt) if lddt.numel() == 1 else lddt.numpy().flatten() + ) # score shape follows selected coordinate axes and per_residue + return float(lddt) if lddt.numel() == 1 else lddt.numpy().flatten() # scalar or (lddt.numel(),) def gdt_ts( self, @@ -317,8 +320,8 @@ class ProteinComplex: & index_by_atom_name(target.atom37_mask[target_inds], "CA", dim=-1) ).unsqueeze(0), **kwargs, - ) - return float(gdt_ts) if gdt_ts.numel() == 1 else gdt_ts.numpy().flatten() + ) # () or (n_samples,), selected by reduction + return float(gdt_ts) if gdt_ts.numel() == 1 else gdt_ts.numpy().flatten() # scalar or (gdt_ts.numel(),) def dockq(self, native: ProteinComplex): # This function uses dockqv2 to compute the DockQ score. Because it does a mapping @@ -452,7 +455,7 @@ class ProteinComplex: "entity_id": self.entity_id, "chain_id": self.chain_id, "sym_id": self.sym_id, - } + } # arrays share l: positions (l, 37, 3), masks (l, 37), residue fields (l,) for name, values in aligned.items(): if not isinstance(values, np.ndarray): raise TypeError(f"{name} must be a NumPy array, got {type(values).__name__}.") @@ -489,7 +492,7 @@ class ProteinComplex: ) if not np.issubdtype(self.confidence.dtype, np.number): raise TypeError("confidence must use a numeric dtype.") - atom37_confidence = self.atom37_confidence + atom37_confidence = self.atom37_confidence # (l, 37) or None if atom37_confidence is not None and not isinstance(atom37_confidence, np.ndarray): raise TypeError("atom37_confidence must be a NumPy array when provided.") if ( @@ -506,6 +509,7 @@ class ProteinComplex: NOTE: When slicing with a boolean mask, it's possible that the output array won't be the expected length. This is because we do our best to preserve chainbreak tokens. """ + # idx selects residue rows; masks retain separator rows before repeated separators are removed. if isinstance(idx, int): idx = [idx] @@ -513,8 +517,8 @@ class ProteinComplex: raise ValueError("ProteinComplex doesn't supports indexing with lists of indices") if isinstance(idx, np.ndarray): - is_chainbreak = np.asarray([s == "|" for s in self.sequence]) - idx = idx.astype(bool) | is_chainbreak + is_chainbreak = np.asarray([s == "|" for s in self.sequence]) # (l,) + idx = idx.astype(bool) | is_chainbreak # (l,) complex = self._unsafe_slice(idx) if len(complex) == 0: @@ -524,17 +528,18 @@ class ProteinComplex: chainbreak_runs = np.asarray( [complex.sequence[i : i + 2] == "||" for i in range(len(complex.sequence) - 1)] + [complex.sequence[-1] == "|"] - ) + ) # (selected_length,) # We should remove as many chainbreaks as possible from the start of the sequence for i in range(len(chainbreak_runs)): if complex.sequence[i] == "|": - chainbreak_runs[i] = True + chainbreak_runs[i] = True # scalar mask element else: break complex = complex._unsafe_slice(~chainbreak_runs) return complex def _unsafe_slice(self, idx: int | list[int] | slice | np.ndarray): + # idx selects residue rows; atom/xyz trailing axes and aligned field lengths are retained. sequence = slice_python_object_as_numpy(self.sequence, idx) return replace( self, @@ -569,7 +574,7 @@ class ProteinComplex: @cached_property def chain_lengths(self) -> np.ndarray: - return np.diff(self.chain_boundaries, axis=1).flatten() + return np.diff(self.chain_boundaries, axis=1).flatten() # (n_chains,) @cached_property def chain_boundaries(self) -> list[tuple[int, int]]: @@ -664,11 +669,11 @@ class ProteinComplex: # Iterate over chains, build KDTree for each chain kdtrees = [] - CA = self.atoms["CA"] + CA = self.atoms["CA"] # (l, 3) for start, end in self.chain_boundaries: - chain_CA = CA[start:end] - chain_CA = chain_CA[np.isfinite(chain_CA).all(axis=-1)] + chain_CA = CA[start:end] # (chain_length, 3) + chain_CA = chain_CA[np.isfinite(chain_CA).all(axis=-1)] # (n_finite_ca, 3) kdtrees.append(KDTree(chain_CA)) return kdtrees @@ -676,25 +681,25 @@ class ProteinComplex: def chain_adjacency(self, cutoff: float = 8.0) -> np.ndarray: # Compute adjacency matrix for protein complex num_chains = self.num_chains - adjacency = np.zeros((num_chains, num_chains), dtype=bool) + adjacency = np.zeros((num_chains, num_chains), dtype=bool) # (n_chains, n_chains) for (i, kdtree), (j, kdtree2) in itertools.combinations( enumerate(self.per_chain_kd_trees), 2 ): adj = kdtree.query_ball_tree(kdtree2, cutoff) any_is_adjacent = any(len(a) > 0 for a in adj) - adjacency[i, j] = any_is_adjacent - adjacency[j, i] = any_is_adjacent - return adjacency + adjacency[i, j] = any_is_adjacent # scalar matrix entry + adjacency[j, i] = any_is_adjacent # scalar matrix entry + return adjacency # (n_chains, n_chains) def chain_adjacency_by_index(self, index: int, cutoff: float = 8.0) -> np.ndarray: num_chains = len(self.chain_boundaries) - adjacency = np.zeros(num_chains, dtype=bool) + adjacency = np.zeros(num_chains, dtype=bool) # (n_chains,) for i, kdtree in enumerate(self.per_chain_kd_trees): if i == index: continue adj = kdtree.query_ball_tree(self.per_chain_kd_trees[index], cutoff) - adjacency[i] = any(len(a) > 0 for a in adj) - return adjacency + adjacency[i] = any(len(a) > 0 for a in adj) # scalar vector entry + return adjacency # (n_chains,) def add_prefix_to_chain_ids(self, prefix: str) -> ProteinComplex: """Rename all chains in the complex with a given prefix. @@ -715,7 +720,7 @@ class ProteinComplex: def sasa(self, by_residue: bool = True): chain = self.as_chain(force_conversion=True) - return chain.sasa(by_residue=by_residue) + return chain.sasa(by_residue=by_residue) # (l,) if by_residue, otherwise (n_present_atoms,) def to_mmcif_string(self) -> str: """Convert the ProteinComplex to mmCIF format. @@ -727,7 +732,7 @@ class ProteinComplex: # Collect all atoms from all chains all_atoms = [] for chain in self.chain_iter(): - chain_atom_array = chain.atom_array + chain_atom_array = chain.atom_array # AtomArray with n_chain_atoms entries # Convert AtomArray to list of atoms and add to collection all_atoms.extend(chain_atom_array) @@ -735,7 +740,7 @@ class ProteinComplex: if not all_atoms: raise ValueError("No atoms found in protein complex") - atom_array = bs.array(all_atoms) + atom_array = bs.array(all_atoms) # AtomArray with total n_present_atoms entries # Create CIF file f = CIFFile() @@ -786,7 +791,7 @@ class ProteinComplex: data=CIFData(array=np.array(entity_descriptions), dtype=np.str_) ), }, - ) + ) # each entity column has shape (n_entities,) # Create _entity_poly section poly_entity_ids = [] @@ -814,7 +819,7 @@ class ProteinComplex: data=CIFData(array=np.array(poly_sequences), dtype=np.str_) ), }, - ) + ) # each polymer column has shape (n_polymer_entities,) # Create _struct_asym section asym_ids = [] @@ -835,18 +840,18 @@ class ProteinComplex: ), "details": CIFColumn(data=CIFData(array=np.array(asym_details), dtype=np.str_)), }, - ) + ) # each asym column has shape (n_chains,) # Construction, PDB interchange, and compact storage @classmethod def from_pdb( cls, path: PathOrBuffer, id: str | None = None, is_predicted: bool = False ) -> ProteinComplex: - atom_array = PDBFile.read(path).get_structure(model=1, extra_fields=["b_factor"]) + atom_array = PDBFile.read(path).get_structure(model=1, extra_fields=["b_factor"]) # AtomArray with n_file_atoms entries chains = [] for chain in bs.chain_iter(atom_array): - chain = chain[~chain.hetero] + chain = chain[~chain.hetero] # AtomArray with n_nonhetero_atoms entries if len(chain) == 0: continue chains.append(ProteinChain.from_atomarray(chain, id, is_predicted)) @@ -855,8 +860,8 @@ class ProteinComplex: def to_pdb(self, path: PathOrBuffer, include_insertions: bool = True): atom_array = None for chain in self.chain_iter(): - carr = chain.atom_array if include_insertions else chain.atom_array_no_insertions - atom_array = carr if atom_array is None else atom_array + carr + carr = chain.atom_array if include_insertions else chain.atom_array_no_insertions # AtomArray with n_chain_atoms entries + atom_array = carr if atom_array is None else atom_array + carr # AtomArray containing accumulated chain atoms f = PDBFile() f.set_structure(atom_array) f.write(path) @@ -909,21 +914,21 @@ class ProteinComplex: # Frozen dataclasses do not make their NumPy members immutable. Work on a # private mask so requesting a compact backbone payload cannot clear the # caller's side-chain atoms in-place. - atom37_mask = dct["atom37_mask"].copy() - atom37_mask[:, 3:] = False - dct["atom37_mask"] = atom37_mask - dct["atom37_positions"] = dct["atom37_positions"][dct["atom37_mask"]] + atom37_mask = dct["atom37_mask"].copy() # (l, 37) + atom37_mask[:, 3:] = False # (l, 34) mask slice for atoms beyond N/CA/C + dct["atom37_mask"] = atom37_mask # (l, 37) + dct["atom37_positions"] = dct["atom37_positions"][dct["atom37_mask"]] # (n_present_atoms, 3) if dct.get("atom37_confidence") is not None: - dct["atom37_confidence"] = dct["atom37_confidence"][dct["atom37_mask"]] + dct["atom37_confidence"] = dct["atom37_confidence"][dct["atom37_mask"]] # (n_present_atoms,) else: dct.pop("atom37_confidence", None) for k, v in dct.items(): if isinstance(v, np.ndarray): match v.dtype: case np.int64: - dct[k] = v.astype(np.int32) + dct[k] = v.astype(np.int32) # v.shape case np.float64 | np.float32: - dct[k] = v.astype(np.float16) + dct[k] = v.astype(np.float16) # v.shape case _: pass if json_serializable: @@ -948,15 +953,15 @@ class ProteinComplex: for k, v in dct.items(): if isinstance(v, list): - dct[k] = np.array(v) + dct[k] = np.array(v) # shape inferred from serialized nested list - atom37 = np.full((*dct["atom37_mask"].shape, 3), np.nan) - atom37[dct["atom37_mask"]] = dct["atom37_positions"] - dct["atom37_positions"] = atom37 + atom37 = np.full((*dct["atom37_mask"].shape, 3), np.nan) # (l, 37, 3) + atom37[dct["atom37_mask"]] = dct["atom37_positions"] # (n_present_atoms, 3) selected coordinates + dct["atom37_positions"] = atom37 # (l, 37, 3) if "atom37_confidence" in dct: - atom37_conf = np.full(dct["atom37_mask"].shape, np.nan, dtype=np.float32) - atom37_conf[dct["atom37_mask"]] = dct["atom37_confidence"] - dct["atom37_confidence"] = atom37_conf + atom37_conf = np.full(dct["atom37_mask"].shape, np.nan, dtype=np.float32) # (l, 37) + atom37_conf[dct["atom37_mask"]] = dct["atom37_confidence"] # (n_present_atoms,) selected confidence values + dct["atom37_confidence"] = atom37_conf # (l, 37) dct = { k: ( v.astype(np.float32) @@ -964,7 +969,7 @@ class ProteinComplex: else v ) for k, v in dct.items() - } + } # each converted array retains its serialized field shape if "chain_boundaries" in dct: del dct["chain_boundaries"] if "chain_boundaries" in dct["metadata"]: @@ -1030,12 +1035,13 @@ class ProteinComplex: # TODO(roshan): Make a proper protein complex class def join_arrays(arrays: Sequence[np.ndarray], sep: np.ndarray): + # arrays: (l_i, *trailing_shape); sep: (1, *trailing_shape). full_array = [] for array in arrays: full_array.append(array) full_array.append(sep) full_array = full_array[:-1] - return np.concatenate(full_array, 0) + return np.concatenate(full_array, 0) # (sum(chain_lengths) + n_chains - 1, *trailing_shape) sep_tokens = { "residue_index": np.array([-1]), @@ -1043,22 +1049,22 @@ class ProteinComplex: "atom37_positions": np.full([1, 37, 3], np.nan), "atom37_mask": np.zeros([1, 37], dtype=bool), "confidence": np.array([0]), - } + } # one-residue separator arrays: (1,), (1, 37, 3), or (1, 37) any_has_atom37_conf = any(c.atom37_confidence is not None for c in chains) if any_has_atom37_conf: - sep_tokens["atom37_confidence"] = np.full([1, 37], np.nan, dtype=np.float32) + sep_tokens["atom37_confidence"] = np.full([1, 37], np.nan, dtype=np.float32) # (1, 37) def _get_chain_attr(chain: ProteinChain, name: str) -> np.ndarray: - val = getattr(chain, name) + val = getattr(chain, name) # (chain_length, *field_trailing_shape) or None if val is None and name == "atom37_confidence": - return np.full([len(chain), 37], np.nan, dtype=np.float32) - return val + return np.full([len(chain), 37], np.nan, dtype=np.float32) # (chain_length, 37) + return val # (chain_length, *field_trailing_shape) array_args: dict[str, np.ndarray] = { name: join_arrays([_get_chain_attr(chain, name) for chain in chains], sep) for name, sep in sep_tokens.items() - } + } # fields retain atom/xyz axes; first axis includes chain separators multimer_arrays = [] chain2num_max = -1 @@ -1070,7 +1076,7 @@ class ProteinComplex: num_res = c.residue_index.shape[0] if c.chain_id not in chain2num: chain2num[c.chain_id] = (chain2num_max := chain2num_max + 1) - chain_id_array = np.full([num_res], chain2num[c.chain_id], dtype=np.int64) + chain_id_array = np.full([num_res], chain2num[c.chain_id], dtype=np.int64) # (chain_length,) if c.entity_id is None: entity_num = (ent2num_max := ent2num_max + 1) @@ -1078,9 +1084,9 @@ class ProteinComplex: if c.entity_id not in ent2num: ent2num[c.entity_id] = (ent2num_max := ent2num_max + 1) entity_num = ent2num[c.entity_id] - entity_id_array = np.full([num_res], entity_num, dtype=np.int64) + entity_id_array = np.full([num_res], entity_num, dtype=np.int64) # (chain_length,) - sym_id_array = np.full([num_res], i, dtype=np.int64) + sym_id_array = np.full([num_res], i, dtype=np.int64) # (chain_length,) multimer_arrays.append( { @@ -1092,11 +1098,11 @@ class ProteinComplex: total_index += num_res + 1 - sep = np.array([-1]) + sep = np.array([-1]) # (1,) update = { name: join_arrays([dct[name] for dct in multimer_arrays], sep=sep) for name in ["chain_id", "entity_id", "sym_id"] - } + } # each field: (sum(chain_lengths) + n_chains - 1,) array_args.update(update) metadata = ProteinComplexMetadata( @@ -1153,7 +1159,7 @@ def get_assembly_fast( ] if len(structure) == 0: raise NoProteinError - unique_asym_ids = np.unique(structure.label_asym_id) # type: ignore + unique_asym_ids = np.unique(structure.label_asym_id) # type: ignore; (n_unique_asym_ids,) asym2chain = {} asym2auth = {} for asym_id in unique_asym_ids: @@ -1167,7 +1173,7 @@ def get_assembly_fast( insertion_code, confidence, entity_id, - ) = chain_to_ndarray(sub_structure, mmcif, chain_id, False) + ) = chain_to_ndarray(sub_structure, mmcif, chain_id, False) # array fields: (l, 37, 3), (l, 37), (l,), (l,), (l,) asym2chain[asym_id] = ProteinChain( id=mmcif.id or "unknown", @@ -1217,12 +1223,13 @@ def get_assembly_fast( def protein_chain_to_protein_complex(chain: ProteinChain) -> ProteinComplex: + # chain fields share residue axis l; splitting removes chain-break separator rows. if "|" not in chain.sequence: return ProteinComplex.from_chains([chain]) - chain_breaks = np.array(list(chain.sequence)) == "|" - chain_break_inds = np.where(chain_breaks)[0] - chain_break_inds = np.concatenate([[0], chain_break_inds, [len(chain)]]) - chain_break_inds = np.array(list(itertools.pairwise(chain_break_inds))) + chain_breaks = np.array(list(chain.sequence)) == "|" # (l,) + chain_break_inds = np.where(chain_breaks)[0] # (n_chainbreaks,) + chain_break_inds = np.concatenate([[0], chain_break_inds, [len(chain)]]) # (n_chainbreaks + 2,) + chain_break_inds = np.array(list(itertools.pairwise(chain_break_inds))) # (n_chainbreaks + 1, 2) complex_chains = [] for start, end in chain_break_inds: if start != 0: diff --git a/fastplms/models/esmfold2/esmfold2_protein_structure.py b/fastplms/models/esmfold2/esmfold2_protein_structure.py index 1d41330753a1d2d82194f77004645d162bda3617..e445a33f27b4397f0c98444da4e72e712df0ce6a 100644 --- a/fastplms/models/esmfold2/esmfold2_protein_structure.py +++ b/fastplms/models/esmfold2/esmfold2_protein_structure.py @@ -2,12 +2,12 @@ from __future__ import annotations -from collections.abc import Callable -from typing import TypeVar - import numpy as np import torch import torch.nn.functional as F + +from collections.abc import Callable +from typing import TypeVar from torch import Tensor from torch.amp import autocast # type: ignore @@ -15,6 +15,7 @@ from .esmfold2_affine3d import Affine3D from .esmfold2_misc import unbinpack from .esmfold2_normalize_coordinates import index_by_atom_name + ArrayOrTensor = TypeVar("ArrayOrTensor", np.ndarray, Tensor) @@ -24,7 +25,7 @@ def _coordinate_operations( if isinstance(coordinates, np.ndarray): def normalize(X: ArrayOrTensor) -> ArrayOrTensor: - return X / np.linalg.norm(X, axis=-1, keepdims=True) + return X / np.linalg.norm(X, axis=-1, keepdims=True) # X.shape; normalize last axis. return normalize, np.cross return F.normalize, torch.cross # type: ignore[return-value] @@ -42,16 +43,17 @@ def infer_cbeta_from_atom37( dihedral in radians used by the checkpoint's training geometry. """ - n_position = index_by_atom_name(atom37, "N", dim=-2) - ca_position = index_by_atom_name(atom37, "CA", dim=-2) - c_position = index_by_atom_name(atom37, "C", dim=-2) + # atom37: (..., 37, 3); ... contains optional batch and residue axes. + n_position = index_by_atom_name(atom37, "N", dim=-2) # (..., 3) + ca_position = index_by_atom_name(atom37, "CA", dim=-2) # (..., 3) + c_position = index_by_atom_name(atom37, "C", dim=-2) # (..., 3) normalize, cross = _coordinate_operations(atom37) with np.errstate(invalid="ignore"): - n_to_ca = n_position - ca_position - n_to_c = n_position - c_position - unit_n_to_ca = normalize(n_to_ca) - normal = normalize(cross(n_to_c, unit_n_to_ca)) - basis = [unit_n_to_ca, cross(normal, unit_n_to_ca), normal] + n_to_ca = n_position - ca_position # (..., 3) + n_to_c = n_position - c_position # (..., 3) + unit_n_to_ca = normalize(n_to_ca) # (..., 3) + normal = normalize(cross(n_to_c, unit_n_to_ca)) # (..., 3) + basis = [unit_n_to_ca, cross(normal, unit_n_to_ca), normal] # three arrays (..., 3) coefficients = [ bond_length * np.cos(bond_angle), bond_length * np.sin(bond_angle) * np.cos(dihedral), @@ -59,8 +61,8 @@ def infer_cbeta_from_atom37( ] offset = sum( vector * coefficient for vector, coefficient in zip(basis, coefficients, strict=True) - ) - return ca_position + offset + ) # (..., 3) + return ca_position + offset # (..., 3) def _unpack_alignment_inputs( @@ -71,12 +73,13 @@ def _unpack_alignment_inputs( ) -> tuple[Tensor, Tensor, Tensor | None]: if sequence_id is None: return mobile, target, atom_mask - unpacked_mobile = unbinpack(mobile, sequence_id, pad_value=torch.nan) - unpacked_target = unbinpack(target, sequence_id, pad_value=torch.nan) + # Packed (b, l, ..., 3) coordinates become (n_sequences, max_length, ..., 3). + unpacked_mobile = unbinpack(mobile, sequence_id, pad_value=torch.nan) # unpacked coordinate shape + unpacked_target = unbinpack(target, sequence_id, pad_value=torch.nan) # unpacked coordinate shape if atom_mask is None: - unpacked_mask = torch.isfinite(unpacked_target).all(dim=-1) + unpacked_mask = torch.isfinite(unpacked_target).all(dim=-1) # unpacked shape without xyz else: - unpacked_mask = unbinpack(atom_mask, sequence_id, pad_value=0) + unpacked_mask = unbinpack(atom_mask, sequence_id, pad_value=0) # unpacked shape without xyz return unpacked_mobile, unpacked_target, unpacked_mask @@ -85,13 +88,14 @@ def _flatten_atom_axes( target: Tensor, atom_mask: Tensor | None, ) -> tuple[Tensor, Tensor, Tensor | None]: + # Inputs: (b, l, n_atoms, 3) or (b, n, 3); n = l * n_atoms after flattening. b = mobile.shape[0] - flat_mobile = mobile.view(b, -1, 3) if mobile.dim() == 4 else mobile - flat_target = target.view(b, -1, 3) if target.dim() == 4 else target - flat_mask = atom_mask + flat_mobile = mobile.view(b, -1, 3) if mobile.dim() == 4 else mobile # (b, n, 3) + flat_target = target.view(b, -1, 3) if target.dim() == 4 else target # (b, n, 3) + flat_mask = atom_mask # (b, l, n_atoms), (b, n), or None if flat_mask is not None and flat_mask.dim() == 3: - flat_mask = flat_mask.view(b, -1) - return flat_mobile, flat_target, flat_mask + flat_mask = flat_mask.view(b, -1) # (b, n) + return flat_mobile, flat_target, flat_mask # (b, n, 3), (b, n, 3), (b, n) or None def _masked_coordinates( @@ -104,12 +108,12 @@ def _masked_coordinates( mobile.shape[:2], dtype=torch.bool, device=mobile.device, - ) + ) # (b, n) return mobile, target, atom_mask - expanded_mask = atom_mask.unsqueeze(-1) + expanded_mask = atom_mask.unsqueeze(-1) # (b, n, 1) return ( - mobile.masked_fill(~expanded_mask, 0), - target.masked_fill(~expanded_mask, 0), + mobile.masked_fill(~expanded_mask, 0), # (b, n, 3) + target.masked_fill(~expanded_mask, 0), # (b, n, 3) atom_mask, ) @@ -147,18 +151,19 @@ def compute_alignment_tensors( atom_exists_mask, ) - num_valid_atoms = atom_exists_mask.sum(dim=-1, keepdim=True) - centroid_mobile = mobile.sum(dim=-2, keepdim=True) / num_valid_atoms.unsqueeze(-1) - centroid_target = target.sum(dim=-2, keepdim=True) / num_valid_atoms.unsqueeze(-1) - centroid_mobile[num_valid_atoms == 0] = 0 - centroid_target[num_valid_atoms == 0] = 0 - - expanded_mask = atom_exists_mask.unsqueeze(-1) - centered_mobile = (mobile - centroid_mobile).masked_fill(~expanded_mask, 0) - centered_target = (target - centroid_target).masked_fill(~expanded_mask, 0) - covariance = torch.matmul(centered_mobile.transpose(1, 2), centered_target) - left_vectors, _, right_vectors = torch.svd(covariance) - rotation = torch.matmul(left_vectors, right_vectors.transpose(1, 2)) + # b now counts unpacked sequences if sequence_id was supplied; n counts atoms. + num_valid_atoms = atom_exists_mask.sum(dim=-1, keepdim=True) # (b, 1) + centroid_mobile = mobile.sum(dim=-2, keepdim=True) / num_valid_atoms.unsqueeze(-1) # (b, 1, 3) + centroid_target = target.sum(dim=-2, keepdim=True) / num_valid_atoms.unsqueeze(-1) # (b, 1, 3) + centroid_mobile[num_valid_atoms == 0] = 0 # (n_empty, 3) + centroid_target[num_valid_atoms == 0] = 0 # (n_empty, 3) + + expanded_mask = atom_exists_mask.unsqueeze(-1) # (b, n, 1) + centered_mobile = (mobile - centroid_mobile).masked_fill(~expanded_mask, 0) # (b, n, 3) + centered_target = (target - centroid_target).masked_fill(~expanded_mask, 0) # (b, n, 3) + covariance = torch.matmul(centered_mobile.transpose(1, 2), centered_target) # (b, 3, 3) + left_vectors, _, right_vectors = torch.svd(covariance) # (b, 3, 3), (b, 3), (b, 3, 3) + rotation = torch.matmul(left_vectors, right_vectors.transpose(1, 2)) # (b, 3, 3) return ( centered_mobile, centroid_mobile, @@ -185,16 +190,17 @@ def compute_rmsd_no_alignment( """Measure RMSD after alignment using a declared reduction.""" _validate_reduction(reduction, ("per_residue", "per_sample", "batch")) - difference = aligned - target + # aligned/target: (b, n, 3); num_valid_atoms: (b, 1). + difference = aligned - target # (b, n, 3) if reduction == "per_residue": - mean_squared_error = difference.square().view(difference.size(0), -1, 9).mean(-1) + mean_squared_error = difference.square().view(difference.size(0), -1, 9).mean(-1) # (b, n / 3) else: - mean_squared_error = difference.square().sum(dim=(1, 2)) / num_valid_atoms.squeeze(-1) - rmsd = torch.sqrt(mean_squared_error) + mean_squared_error = difference.square().sum(dim=(1, 2)) / num_valid_atoms.squeeze(-1) # (b,) + rmsd = torch.sqrt(mean_squared_error) # (b, n / 3) for per_residue, otherwise (b,) if reduction in {"per_residue", "per_sample"}: return rmsd - valid_samples = num_valid_atoms.squeeze(-1) > 0 - return rmsd.masked_fill(~valid_samples, 0).sum() / (valid_samples.sum() + 1e-8) + valid_samples = num_valid_atoms.squeeze(-1) > 0 # (b,) + return rmsd.masked_fill(~valid_samples, 0).sum() / (valid_samples.sum() + 1e-8) # () @torch.no_grad() @@ -215,19 +221,19 @@ def compute_affine_and_rmsd( rotation, num_valid_atoms, ) = compute_alignment_tensors(mobile, target, atom_exists_mask, sequence_id) - translation = torch.matmul(-centroid_mobile, rotation) + centroid_target + translation = torch.matmul(-centroid_mobile, rotation) + centroid_target # (b, 1, 3) affine = Affine3D.from_tensor_pair( translation, - rotation.unsqueeze(dim=-3).transpose(-2, -1), + rotation.unsqueeze(dim=-3).transpose(-2, -1), # (b, 1, 3, 3) ) - rotated_mobile = torch.matmul(centered_mobile, rotation) + rotated_mobile = torch.matmul(centered_mobile, rotation) # (b, n, 3) rmsd = compute_rmsd_no_alignment( rotated_mobile, centered_target, num_valid_atoms, reduction="batch", ) - return affine, rmsd + return affine, rmsd # affine shape: (b, 1); rmsd: () def compute_gdt_ts_no_alignment( @@ -240,12 +246,13 @@ def compute_gdt_ts_no_alignment( _validate_reduction(reduction, ("per_sample", "batch")) if atom_exists_mask is None: - atom_exists_mask = torch.isfinite(target).all(dim=-1) - deviation = torch.linalg.vector_norm(aligned - target, dim=-1) - counts = atom_exists_mask.sum(dim=-1) - score_1 = ((deviation < 1) * atom_exists_mask).sum(dim=-1) / counts - score_2 = ((deviation < 2) * atom_exists_mask).sum(dim=-1) / counts - score_4 = ((deviation < 4) * atom_exists_mask).sum(dim=-1) / counts - score_8 = ((deviation < 8) * atom_exists_mask).sum(dim=-1) / counts - score = (score_1 + score_2 + score_4 + score_8) * 0.25 - return score.mean() if reduction == "batch" else score + atom_exists_mask = torch.isfinite(target).all(dim=-1) # (b, n) + # aligned/target: (b, n, 3); atom_exists_mask: (b, n). + deviation = torch.linalg.vector_norm(aligned - target, dim=-1) # (b, n) + counts = atom_exists_mask.sum(dim=-1) # (b,) + score_1 = ((deviation < 1) * atom_exists_mask).sum(dim=-1) / counts # (b,) + score_2 = ((deviation < 2) * atom_exists_mask).sum(dim=-1) / counts # (b,) + score_4 = ((deviation < 4) * atom_exists_mask).sum(dim=-1) / counts # (b,) + score_8 = ((deviation < 8) * atom_exists_mask).sum(dim=-1) / counts # (b,) + score = (score_1 + score_2 + score_4 + score_8) * 0.25 # (b,) + return score.mean() if reduction == "batch" else score # () for batch, otherwise (b,) diff --git a/fastplms/models/esmfold2/esmfold2_residue_constants.py b/fastplms/models/esmfold2/esmfold2_residue_constants.py index 42655c58c794c2862036bc993a4970989afbbf05..b5a481ba5ea76f69699c0e8193b44b218d7454f2 100644 --- a/fastplms/models/esmfold2/esmfold2_residue_constants.py +++ b/fastplms/models/esmfold2/esmfold2_residue_constants.py @@ -24,12 +24,13 @@ exactly against the pinned Biohub implementation. from __future__ import annotations import functools +import numpy as np + from collections import defaultdict, namedtuple from collections.abc import Mapping from pathlib import Path from typing import Any, cast -import numpy as np ca_ca = 3.80209737096 chi_angles_atoms = { diff --git a/fastplms/models/esmfold2/esmfold2_sequential_dataclass.py b/fastplms/models/esmfold2/esmfold2_sequential_dataclass.py index 97b1a47c3c65d51625541f41abfbd0821617301a..f232ec93ded89a68a2b9693bc44a3719def803fc 100644 --- a/fastplms/models/esmfold2/esmfold2_sequential_dataclass.py +++ b/fastplms/models/esmfold2/esmfold2_sequential_dataclass.py @@ -2,15 +2,16 @@ from __future__ import annotations +import numpy as np + from abc import ABC, abstractmethod from collections.abc import Iterable from dataclasses import Field, dataclass, fields, replace from typing import Any, Self -import numpy as np - from .esmfold2_misc import concat_objects, slice_any_object + Index = int | list[int] | slice | np.ndarray diff --git a/fastplms/models/esmfold2/esmfold2_system.py b/fastplms/models/esmfold2/esmfold2_system.py index 23b69dc10fae713e3b7be76fd9475ab054a58913..91669fe9aa820d908fd5b37cf2b000ef9cdb58cc 100644 --- a/fastplms/models/esmfold2/esmfold2_system.py +++ b/fastplms/models/esmfold2/esmfold2_system.py @@ -4,9 +4,11 @@ from __future__ import annotations import io import subprocess + from pathlib import Path from typing import Any, TypeAlias + PathLike: TypeAlias = str | Path PathOrBuffer: TypeAlias = PathLike | io.StringIO diff --git a/fastplms/models/esmfold2/esmfold2_types.py b/fastplms/models/esmfold2/esmfold2_types.py index 38d581ec65c1f918063c271f15b5a71ae1fe7c99..dbf17d52a23ce99885d818485c3b06034b6034a1 100644 --- a/fastplms/models/esmfold2/esmfold2_types.py +++ b/fastplms/models/esmfold2/esmfold2_types.py @@ -6,6 +6,7 @@ from . import esmfold2_input_builder as _input_schema from .esmfold2_msa import MSA from .esmfold2_parsing import FastaEntry + Modification = _input_schema.Modification ProteinInput = _input_schema.ProteinInput RNAInput = _input_schema.RNAInput diff --git a/fastplms/models/esmfold2/esmfold2_utils_types.py b/fastplms/models/esmfold2/esmfold2_utils_types.py index 13590243d386bc83fa3c01bb28f8ed6fb79762f2..965a7ed59388a77bd11589b10660023303ea47a0 100644 --- a/fastplms/models/esmfold2/esmfold2_utils_types.py +++ b/fastplms/models/esmfold2/esmfold2_utils_types.py @@ -9,9 +9,11 @@ from __future__ import annotations import io import os + from dataclasses import dataclass from typing import TypeAlias + PathLike: TypeAlias = str | os.PathLike[str] PathOrBuffer: TypeAlias = PathLike | io.TextIOBase diff --git a/fastplms/models/esmfold2/modeling_esmfold2.py b/fastplms/models/esmfold2/modeling_esmfold2.py index 14b35fa3abea1bcf577ab9e4bc3f8b0c1fca405d..b613e1c0d76f908046a526d6c1bad5a815e51d61 100644 --- a/fastplms/models/esmfold2/modeling_esmfold2.py +++ b/fastplms/models/esmfold2/modeling_esmfold2.py @@ -2,10 +2,12 @@ Quickstart:: - from transformers import ESMFold2Model + from pathlib import Path + from transformers import AutoModel - model = ESMFold2Model.from_pretrained("biohub/ESMFold2").cuda().eval() - open("ubq.pdb", "w").write(model.infer_protein_as_pdb("MQIFVKTLTGKT...")) + model = AutoModel.from_pretrained("Synthyra/ESMFold2", trust_remote_code=True).cuda().eval() + structure = model.infer_protein_as_pdb("MQIFVKTLTGKT") + Path("structure.pdb").write_text(structure, encoding="utf-8") For multi-chain, ligand, and MSA inputs, use ``model.input_types`` together with ``model.fold(...)`` or ``model.prepare_structure_input(...)``. @@ -17,15 +19,15 @@ import gc import importlib import importlib.metadata import math +import torch +import torch.nn as nn +import torch.nn.functional as F + from collections.abc import Mapping from contextlib import contextmanager from dataclasses import asdict, dataclass from pathlib import Path from typing import Any, ClassVar, Literal, cast - -import torch -import torch.nn as nn -import torch.nn.functional as F from torch import Tensor from tqdm.auto import tqdm from transformers.modeling_outputs import ModelOutput @@ -33,6 +35,7 @@ from transformers.modeling_utils import PreTrainedModel from ...attention import get_attn_implementation, set_config_attn_implementation + try: from fastplms.models.ttt import FastPLMTestTimeTrainingMixin, TTTConfig except ModuleNotFoundError as error: @@ -195,6 +198,7 @@ class _ESMFold2ESMplusplusAdapter(nn.Module): compute_sae: bool = True, normalize_sae: bool = False, ): + # input_ids and optional masks/sequence IDs: (b, t). del return_dict, compute_sae, normalize_sae output = self.model( input_ids=input_ids, @@ -206,14 +210,14 @@ class _ESMFold2ESMplusplusAdapter(nn.Module): esmfold2_hidden_states=True, ) if output_hidden_states: - hidden_states = output.hidden_states + hidden_states = output.hidden_states # Tensor (n_states, b, t, d_lm), or sequence of (b, t, d_lm) tensors if hidden_states is None: raise RuntimeError("ESM++ did not return requested hidden states.") if isinstance(hidden_states, torch.Tensor): - output.hidden_states = hidden_states + output.hidden_states = hidden_states # (n_states, b, t, d_lm) else: - output.hidden_states = torch.stack(tuple(hidden_states), dim=0) - return output + output.hidden_states = torch.stack(tuple(hidden_states), dim=0) # (n_states, b, t, d_lm) + return output # model output; hidden_states stacked on the leading state axis when requested def _load_fastplms_esmplusplus_for_esmfold2( @@ -547,14 +551,15 @@ class PairTransition(nn.Module): self._chunk_size = chunk_size def forward(self, x: Tensor) -> Tensor: + # x: (b, l, ..., d_model); l_c is the current chunk width. if self._chunk_size is None or x.shape[1] <= self._chunk_size: - return self.ffn(self.norm(x)) + return self.ffn(self.norm(x)) # x.shape out: list[Tensor] = [] for s in range(0, x.shape[1], self._chunk_size): e = min(s + self._chunk_size, x.shape[1]) - sl = x[:, s:e] + sl = x[:, s:e] # (b, l_c, ..., d_model) out.append(self.ffn(self.norm(sl))) - return torch.cat(out, dim=1) + return torch.cat(out, dim=1) # x.shape class ConfidenceHead(nn.Module): @@ -569,8 +574,8 @@ class ConfidenceHead(nn.Module): d_pair = config.d_pair d_inputs = config.inputs.d_inputs - boundaries = torch.linspace(ch.min_dist, ch.max_dist, ch.distogram_bins - 1) - self.register_buffer("boundaries", boundaries) + boundaries = torch.linspace(ch.min_dist, ch.max_dist, ch.distogram_bins - 1) # (distogram_bins - 1,) + self.register_buffer("boundaries", boundaries) # (distogram_bins - 1,) self.dist_bin_pairwise_embed = nn.Embedding(ch.distogram_bins, d_pair) self.s_norm = nn.LayerNorm(d_single) @@ -594,7 +599,7 @@ class ConfidenceHead(nn.Module): max_atoms_per_token = 23 self.plddt_weight = nn.Parameter( torch.zeros(max_atoms_per_token, d_single, ch.num_plddt_bins) - ) + ) # (23, d_single, n_plddt_bins) self.pae_ln = nn.LayerNorm(d_pair) self.pae_head = nn.Linear(d_pair, ch.num_pae_bins, bias=False) @@ -604,7 +609,7 @@ class ConfidenceHead(nn.Module): self.resolved_ln = nn.LayerNorm(d_single) # 2 = resolved logits ([unresolved, resolved]). - self.resolved_weight = nn.Parameter(torch.zeros(max_atoms_per_token, d_single, 2)) + self.resolved_weight = nn.Parameter(torch.zeros(max_atoms_per_token, d_single, 2)) # (23, d_single, 2) def set_kernel_backend(self, backend: str | None) -> None: self.folding_trunk.set_kernel_backend(backend) @@ -614,14 +619,16 @@ class ConfidenceHead(nn.Module): @staticmethod def _repeat_batch(x: Tensor, num_diffusion_samples: int) -> Tensor: - return x if num_diffusion_samples == 1 else x.repeat_interleave(num_diffusion_samples, 0) + # x: (b, ...); output repeats the batch axis by samples. + return x if num_diffusion_samples == 1 else x.repeat_interleave(num_diffusion_samples, 0) # (b * samples, ...), including samples = 1 @staticmethod def _flatten_sample_axis(x: Tensor) -> Tensor: + # x: (b, samples, n, c) or an already flattened tensor. if x.ndim == 4: b, mult, n, c = x.shape - return x.reshape(b * mult, n, c) - return x + return x.reshape(b * mult, n, c) # (b * samples, n, c) for 4D input; otherwise x.shape + return x # (b * samples, n, c) for 4D input; otherwise x.shape def forward( self, @@ -638,55 +645,56 @@ class ConfidenceHead(nn.Module): relative_position_encoding: Tensor | None = None, token_bonds_encoding: Tensor | None = None, ) -> dict[str, Tensor]: - s_inputs_normed = self.s_inputs_norm(s_inputs) + # s_inputs: (b, l, d_inputs); z: (b, l, l, d_pair); x_pred: (bs, a, 3) or (b, samples, a, 3). bs = b * samples. + s_inputs_normed = self.s_inputs_norm(s_inputs) # (b, l, d_inputs) - z_base = self.z_norm(z) + z_base = self.z_norm(z) # (b, l, l, d_pair) if relative_position_encoding is not None: - z_base = z_base + relative_position_encoding + z_base = z_base + relative_position_encoding # (b, l, l, d_pair) if token_bonds_encoding is not None: - z_base = z_base + token_bonds_encoding - z_base = z_base + self.s_to_z(s_inputs_normed).unsqueeze(2) - z_base = z_base + self.s_to_z_transpose(s_inputs_normed).unsqueeze(1) + z_base = z_base + token_bonds_encoding # (b, l, l, d_pair) + z_base = z_base + self.s_to_z(s_inputs_normed).unsqueeze(2) # (b, l, l, d_pair) + z_base = z_base + self.s_to_z_transpose(s_inputs_normed).unsqueeze(1) # (b, l, l, d_pair) z_base = z_base + self.s_to_z_prod_out( self.s_to_z_prod_in1(s_inputs_normed)[:, :, None, :] * self.s_to_z_prod_in2(s_inputs_normed)[:, None, :, :] - ) - - pair = self._repeat_batch(z_base, num_diffusion_samples) - x_pred_flat = self._flatten_sample_axis(x_pred) - atom_to_token_m = self._repeat_batch(atom_to_token, num_diffusion_samples) - atom_mask_m = self._repeat_batch(atom_attention_mask, num_diffusion_samples) - rep_idx_m = self._repeat_batch(distogram_atom_idx, num_diffusion_samples).long() - mask = self._repeat_batch(token_attention_mask, num_diffusion_samples) + ) # (b, l, l, d_pair) + + pair = self._repeat_batch(z_base, num_diffusion_samples) # (bs, l, l, d_pair) + x_pred_flat = self._flatten_sample_axis(x_pred) # (bs, a, 3) + atom_to_token_m = self._repeat_batch(atom_to_token, num_diffusion_samples) # (bs, a) + atom_mask_m = self._repeat_batch(atom_attention_mask, num_diffusion_samples) # (bs, a) + rep_idx_m = self._repeat_batch(distogram_atom_idx, num_diffusion_samples).long() # (bs, l) + mask = self._repeat_batch(token_attention_mask, num_diffusion_samples) # (bs, l) expanded_batch_size = pair.shape[0] - rep_coords = gather_rep_atom_coords(x_pred_flat, rep_idx_m) + rep_coords = gather_rep_atom_coords(x_pred_flat, rep_idx_m) # (bs, l, 3) rep_distances = torch.cdist( rep_coords, rep_coords, compute_mode="donot_use_mm_for_euclid_dist" - ) - distogram_bins = (rep_distances.unsqueeze(-1) > self.boundaries).sum(dim=-1).long() - pair = pair + self.dist_bin_pairwise_embed(distogram_bins) + ) # (bs, l, l) + distogram_bins = (rep_distances.unsqueeze(-1) > self.boundaries).sum(dim=-1).long() # (bs, l, l) + pair = pair + self.dist_bin_pairwise_embed(distogram_bins) # (bs, l, l, d_pair) - pair_mask = mask[:, :, None].float() * mask[:, None, :].float() + pair_mask = mask[:, :, None].float() * mask[:, None, :].float() # (bs, l, l) # FoldingTrunk handles the bf16 cast internally during inference so # each block's fused trimul engages. In-place residual avoids an # extra fp32 pair allocation. with torch.amp.autocast("cuda", enabled=pair.is_cuda, dtype=torch.bfloat16): - pair_delta = self.folding_trunk(pair, pair_attention_mask=pair_mask) - pair.add_(pair_delta.float()) + pair_delta = self.folding_trunk(pair, pair_attention_mask=pair_mask) # (bs, l, l, d_pair) + pair.add_(pair_delta.float()) # (bs, l, l, d_pair) del pair_delta - single = self.row_attention_pooling(pair, mask) + single = self.row_attention_pooling(pair, mask) # (bs, l, d_single) - atom_mask_f = atom_mask_m.float() - s_at_atoms = gather_token_to_atom(single, atom_to_token_m) - s_at_atoms_ln = self.plddt_ln(s_at_atoms) + atom_mask_f = atom_mask_m.float() # (bs, a) + s_at_atoms = gather_token_to_atom(single, atom_to_token_m) # (bs, a, d_single) + s_at_atoms_ln = self.plddt_ln(s_at_atoms) # (bs, a, d_single) - intra_idx = _compute_intra_token_idx(atom_to_token_m) - intra_idx = intra_idx.clamp(max=self.plddt_weight.shape[0] - 1) - w_plddt = self.plddt_weight[intra_idx] - plddt_logits = torch.einsum("...c,...cb->...b", s_at_atoms_ln, w_plddt) - plddt_per_atom = _categorical_mean(plddt_logits, start=0.0, end=1.0) + intra_idx = _compute_intra_token_idx(atom_to_token_m) # (bs, a) + intra_idx = intra_idx.clamp(max=self.plddt_weight.shape[0] - 1) # (bs, a) + w_plddt = self.plddt_weight[intra_idx] # (bs, a, d_single, n_plddt_bins) + plddt_logits = torch.einsum("...c,...cb->...b", s_at_atoms_ln, w_plddt) # (bs, a, n_plddt_bins) + plddt_per_atom = _categorical_mean(plddt_logits, start=0.0, end=1.0) # (bs, a) sequence_length = single.shape[1] plddt_sum = torch.zeros( @@ -694,80 +702,80 @@ class ConfidenceHead(nn.Module): sequence_length, device=single.device, dtype=plddt_per_atom.dtype, - ) + ) # (bs, l) atom_count = torch.zeros( expanded_batch_size, sequence_length, device=single.device, dtype=plddt_per_atom.dtype, - ) - atom_mask_t = atom_mask_f.to(plddt_per_atom.dtype) - plddt_sum.scatter_add_(1, atom_to_token_m, plddt_per_atom * atom_mask_t) - atom_count.scatter_add_(1, atom_to_token_m, atom_mask_t) - plddt = plddt_sum / atom_count.clamp(min=1e-6) + ) # (bs, l) + atom_mask_t = atom_mask_f.to(plddt_per_atom.dtype) # (bs, a) + plddt_sum.scatter_add_(1, atom_to_token_m, plddt_per_atom * atom_mask_t) # (bs, l) + atom_count.scatter_add_(1, atom_to_token_m, atom_mask_t) # (bs, l) + plddt = plddt_sum / atom_count.clamp(min=1e-6) # (bs, l) complex_plddt = (plddt_per_atom * atom_mask_f).sum(dim=-1) / ( atom_mask_f.sum(dim=-1) + _EPS - ) + ) # (bs,) - expanded_type = self._repeat_batch(mol_type, num_diffusion_samples) - expanded_asym = self._repeat_batch(asym_id, num_diffusion_samples) - is_ligand = (expanded_type == _NONPOLYMER_ID).float() - inter_chain = (expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2)).float() - near_contact = (rep_distances < 8).float() + expanded_type = self._repeat_batch(mol_type, num_diffusion_samples) # (bs, l) + expanded_asym = self._repeat_batch(asym_id, num_diffusion_samples) # (bs, l) + is_ligand = (expanded_type == _NONPOLYMER_ID).float() # (bs, l) + inter_chain = (expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2)).float() # (bs, l, l) + near_contact = (rep_distances < 8).float() # (bs, l, l) interface_per_token = (near_contact * inter_chain * (1.0 - is_ligand).unsqueeze(-1)).amax( dim=-1 - ) + ) # (bs, l) iplddt_weight = torch.where( is_ligand.bool(), torch.full_like(interface_per_token, 2.0), interface_per_token, - ) + ) # (bs, l) iplddt_weight_atoms = gather_token_to_atom( iplddt_weight.unsqueeze(-1), atom_to_token_m - ).squeeze(-1) - atom_iplddt_w = atom_mask_f * iplddt_weight_atoms + ).squeeze(-1) # (bs, a) + atom_iplddt_w = atom_mask_f * iplddt_weight_atoms # (bs, a) complex_iplddt = (plddt_per_atom * atom_iplddt_w).sum(dim=-1) / ( atom_iplddt_w.sum(dim=-1) + _EPS - ) + ) # (bs,) - plddt_ca = plddt_per_atom.gather(1, rep_idx_m) + plddt_ca = plddt_per_atom.gather(1, rep_idx_m) # (bs, l) # PAE - pae_logits = self.pae_head(self.pae_ln(pair)) - pae = _categorical_mean(pae_logits, start=0.0, end=32.0).detach() + pae_logits = self.pae_head(self.pae_ln(pair)) # (bs, l, l, n_pae_bins) + pae = _categorical_mean(pae_logits, start=0.0, end=32.0).detach() # (bs, l, l) # PDE - pde_logits = self.pde_head(self.pde_ln(pair)) - pde = _categorical_mean(pde_logits, start=0.0, end=32.0).detach() + pde_logits = self.pde_head(self.pde_ln(pair)) # (bs, l, l, n_pde_bins) + pde = _categorical_mean(pde_logits, start=0.0, end=32.0).detach() # (bs, l, l) # Resolved (per-atom binary). - s_at_atoms_res = self.resolved_ln(s_at_atoms) - w_res = self.resolved_weight[intra_idx] - resolved_logits = torch.einsum("...c,...cb->...b", s_at_atoms_res, w_res) + s_at_atoms_res = self.resolved_ln(s_at_atoms) # (bs, a, d_single) + w_res = self.resolved_weight[intra_idx] # (bs, a, d_single, 2) + resolved_logits = torch.einsum("...c,...cb->...b", s_at_atoms_res, w_res) # (bs, a, 2) # pTM / ipTM from pae_logits. n_bins = pae_logits.shape[-1] bin_width = 32.0 / n_bins - bin_centers = torch.arange(0.5 * bin_width, 32.0, bin_width, device=pae_logits.device) - mask_f = mask.float() - n_residues = mask_f.sum(dim=-1, keepdim=True) - d0 = 1.24 * (n_residues.clamp(min=19) - 15) ** (1 / 3) - 1.8 - tm_per_bin = 1 / (1 + (bin_centers / d0) ** 2) - pae_probs = F.softmax(pae_logits, dim=-1) - tm_expected = (pae_probs * tm_per_bin[:, None, None, :]).sum(dim=-1) - - pair_mask_2d = mask_f.unsqueeze(-1) * mask_f.unsqueeze(-2) - ptm_per_row = (tm_expected * pair_mask_2d).sum(dim=-1) / (pair_mask_2d.sum(dim=-1) + _EPS) - ptm = ptm_per_row.max(dim=-1).values + bin_centers = torch.arange(0.5 * bin_width, 32.0, bin_width, device=pae_logits.device) # (n_pae_bins,) + mask_f = mask.float() # (bs, l) + n_residues = mask_f.sum(dim=-1, keepdim=True) # (bs, 1) + d0 = 1.24 * (n_residues.clamp(min=19) - 15) ** (1 / 3) - 1.8 # (bs, 1) + tm_per_bin = 1 / (1 + (bin_centers / d0) ** 2) # (bs, n_pae_bins) + pae_probs = F.softmax(pae_logits, dim=-1) # (bs, l, l, n_pae_bins) + tm_expected = (pae_probs * tm_per_bin[:, None, None, :]).sum(dim=-1) # (bs, l, l) + + pair_mask_2d = mask_f.unsqueeze(-1) * mask_f.unsqueeze(-2) # (bs, l, l) + ptm_per_row = (tm_expected * pair_mask_2d).sum(dim=-1) / (pair_mask_2d.sum(dim=-1) + _EPS) # (bs, l) + ptm = ptm_per_row.max(dim=-1).values # (bs,) inter_chain_mask = ( expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2) - ).float() * pair_mask_2d + ).float() * pair_mask_2d # (bs, l, l) iptm_per_row = (tm_expected * inter_chain_mask).sum(dim=-1) / ( inter_chain_mask.sum(dim=-1) + _EPS - ) - iptm = iptm_per_row.max(dim=-1).values + ) # (bs, l) + iptm = iptm_per_row.max(dim=-1).values # (bs,) max_chain_id = int(expanded_asym.max().item()) if expanded_batch_size > 0 else 0 n_chains = max_chain_id + 1 @@ -777,16 +785,16 @@ class ConfidenceHead(nn.Module): n_chains, device=tm_expected.device, dtype=tm_expected.dtype, - ) + ) # (bs, n_chains, n_chains) for c1 in range(n_chains): - chain_c1 = (expanded_asym == c1).float() * mask_f + chain_c1 = (expanded_asym == c1).float() * mask_f # (bs, l) if chain_c1.sum() == 0: continue for c2 in range(n_chains): - chain_c2 = (expanded_asym == c2).float() * mask_f - pair_m = chain_c1.unsqueeze(-1) * chain_c2.unsqueeze(-2) - denom = pair_m.sum(dim=(-1, -2)) + _EPS - pair_chains_iptm[:, c1, c2] = (tm_expected * pair_m).sum(dim=(-1, -2)) / denom + chain_c2 = (expanded_asym == c2).float() * mask_f # (bs, l) + pair_m = chain_c1.unsqueeze(-1) * chain_c2.unsqueeze(-2) # (bs, l, l) + denom = pair_m.sum(dim=(-1, -2)) + _EPS # (bs,) + pair_chains_iptm[:, c1, c2] = (tm_expected * pair_m).sum(dim=(-1, -2)) / denom # (bs,) return { "plddt_logits": plddt_logits, @@ -803,7 +811,7 @@ class ConfidenceHead(nn.Module): "ptm": ptm.detach(), "iptm": iptm.detach(), "pair_chains_iptm": pair_chains_iptm.detach(), - } + } # mapping of confidence tensors with shapes traced above def _inverse_softplus(value: float) -> float: @@ -834,9 +842,9 @@ def _convert_esmc_attention_outputs_to_te(module: nn.Module) -> tuple[str, ...]: device=child.weight.device, ) with torch.no_grad(): - replacement.weight.copy_(child.weight) + replacement.weight.copy_(child.weight) # child.weight.shape if child.bias is not None: - replacement.bias.copy_(child.bias) + replacement.bias.copy_(child.bias) # child.bias.shape replacement.eval().requires_grad_(False) setattr(owner, name, replacement) converted.append(path) @@ -953,15 +961,15 @@ class ESMFold2Model( self.lm_encoder = None self.parcae_input_norm = nn.LayerNorm(d_pair) - self.parcae_log_a = nn.Parameter(torch.zeros(d_pair)) + self.parcae_log_a = nn.Parameter(torch.zeros(d_pair)) # (d_pair,) parcae_decay_init = math.sqrt(1.0 / 5.0) parcae_delta_init = -math.log(parcae_decay_init) self.parcae_log_delta = nn.Parameter( torch.full((d_pair,), _inverse_softplus(parcae_delta_init), dtype=torch.float32) - ) - self.parcae_b_cont = nn.Parameter(torch.eye(d_pair)) + ) # (d_pair,) + self.parcae_b_cont = nn.Parameter(torch.eye(d_pair)) # (d_pair, d_pair) self.parcae_readout = nn.Linear(d_pair, d_pair, bias=False) - nn.init.eye_(self.parcae_readout.weight) + nn.init.eye_(self.parcae_readout.weight) # (d_pair, d_pair) self.parcae_coda = FoldingTrunk( n_layers=config.parcae.coda_n_layers, d_pair=d_pair, expansion_ratio=4 ) @@ -1101,9 +1109,10 @@ class ESMFold2Model( input_ids: torch.Tensor | None = None, **kwargs, ) -> torch.Tensor: + # Encoded batch: b sequences, padded token width t including BOS/EOS. del kwargs if input_ids is not None: - return input_ids + return input_ids # (b, t), or caller input_ids.shape if seq is None: raise ValueError("Pass either seq or input_ids for ESMFold2 TTT.") sequences = [seq] if isinstance(seq, str) else seq @@ -1122,13 +1131,13 @@ class ESMFold2Model( (len(encoded), max_len), SEQUENCE_PAD_TOKEN, dtype=torch.long, - ) + ) # (b, t) for row, token_ids in enumerate(encoded): input_tensor[row, : len(token_ids)] = torch.tensor( token_ids, dtype=torch.long, - ) - return input_tensor + ) # (t_i,) + return input_tensor # (b, t), or caller input_ids.shape def _ttt_mask_token(self) -> int: return SEQUENCE_MASK_TOKEN @@ -1142,18 +1151,20 @@ class ESMFold2Model( SEQUENCE_STANDARD_AA_MAX_TOKEN, device=input_ids.device, dtype=input_ids.dtype, - ) + ) # (SEQUENCE_STANDARD_AA_MAX_TOKEN - SEQUENCE_STANDARD_AA_MIN_TOKEN,) def _ttt_non_special_mask(self, input_ids: torch.Tensor) -> torch.Tensor: + # input_ids: arbitrary token-ID shape. return (input_ids >= SEQUENCE_STANDARD_AA_MIN_TOKEN) & ( input_ids < SEQUENCE_STANDARD_AA_MAX_TOKEN - ) + ) # input_ids.shape def _ttt_predict_logits( self, batch: torch.Tensor | dict[str, torch.Tensor], **kwargs, ) -> torch.Tensor: + # batch: (b, t) token IDs; backbone output last_hidden_state: (b, t, d_lm). del kwargs if not isinstance(batch, torch.Tensor): raise TypeError("ESMFold2 TTT expects input_ids tensors.") @@ -1163,14 +1174,14 @@ class ESMFold2Model( self._ensure_ttt_lm_head() if self._ttt_lm_head is None: raise RuntimeError("ESMFold2 TTT MLM head initialization failed.") - attention_mask = batch.ne(SEQUENCE_PAD_TOKEN) + attention_mask = batch.ne(SEQUENCE_PAD_TOKEN) # (b, t) output = self._esmc( input_ids=batch, attention_mask=attention_mask, return_dict=True, compute_sae=False, ) - return self._ttt_lm_head(output.last_hidden_state) + return self._ttt_lm_head(output.last_hidden_state) # (b, t, vocab_size) @classmethod def from_pretrained( @@ -1287,6 +1298,7 @@ class ESMFold2Model( lm_mask_pct: float = 0.0, verbose: bool = False, ) -> Tensor: + # Input tensors: (b, l); n_states and d_lm come from the loaded backbone. if self._esmc_fp8 and torch.is_grad_enabled(): _reload_esmc_bf16_for_gradients( self, @@ -1312,9 +1324,9 @@ class ESMFold2Model( pad_to_multiple=pad_to, lm_mask_pct=lm_mask_pct, mask_token_id=SEQUENCE_MASK_TOKEN, - ) + ) # (b, l, n_states, d_lm) progress.update() - return result + return result # (b, l, n_states, d_lm) return compute_lm_hidden_states( self._esmc, input_ids, @@ -1325,19 +1337,20 @@ class ESMFold2Model( pad_to_multiple=pad_to, lm_mask_pct=lm_mask_pct, mask_token_id=SEQUENCE_MASK_TOKEN, - ) + ) # (b, l, n_states, d_lm) def _discretized_dynamics(self) -> tuple[Tensor, Tensor]: - delta = F.softplus(self.parcae_log_delta) - a = torch.exp(-delta * torch.exp(self.parcae_log_a)) - b = delta[:, None] * self.parcae_b_cont - return a, b + delta = F.softplus(self.parcae_log_delta) # (d_pair,) + a = torch.exp(-delta * torch.exp(self.parcae_log_a)) # (d_pair,) + b = delta[:, None] * self.parcae_b_cont # (d_pair, d_pair) + return a, b # (d_pair,), (d_pair, d_pair) def _init_pair_state(self, ref: Tensor) -> Tensor: + # ref: (b, l, l, d_pair). std = math.sqrt(2.0 / (5.0 * ref.shape[-1])) - state = torch.empty_like(ref, dtype=torch.float32) - nn.init.trunc_normal_(state, mean=0.0, std=std, a=-3 * std, b=3 * std) - return state.to(dtype=ref.dtype) + state = torch.empty_like(ref, dtype=torch.float32) # ref.shape + nn.init.trunc_normal_(state, mean=0.0, std=std, a=-3 * std, b=3 * std) # ref.shape + return state.to(dtype=ref.dtype) # ref.shape def _run_one_loop( self, @@ -1356,6 +1369,7 @@ class ESMFold2Model( # otherwise leaks about 2 GB of l^2 * c_z data into distogram/sample scope. # training=True forces dropout under eval(), matching the per-loop # dropout strategy used at train time. + # Pair states: (b, l, l, d_pair); pair_mask: (b, l, l); tok_mask: (b, l). MSA depth m may be subsampled. lm_cfg = self.config.lm_encoder _per_loop_lm_dropout = ( lm_z is not None @@ -1377,19 +1391,19 @@ class ESMFold2Model( if _per_loop_lm_dropout: if lm_z is None: raise RuntimeError("Per-loop LM dropout requires LM pair features.") - lm_z_i: Tensor | None = F.dropout(lm_z, p=_lm_dropout_p, training=True) + lm_z_i: Tensor | None = F.dropout(lm_z, p=_lm_dropout_p, training=True) # (b, l, l, d_pair) or None else: - lm_z_i = lm_z + lm_z_i = lm_z # (b, l, l, d_pair) or None - refined_lm_z: Tensor | None = None + refined_lm_z: Tensor | None = None # (b, l, l, d_pair) or None if lm_z_i is not None and self.lm_encoder is not None: refined_lm_z = self.lm_encoder( lm_z_i.to(z_init.dtype), pair_attention_mask=pair_mask - ) + ) # (b, l, l, d_pair) or None - z_inject_pair = z_init + z_inject_pair = z_init # (b, l, l, d_pair) if lm_z_i is not None and self.lm_encoder is None: - z_inject_pair = z_inject_pair + lm_z_i.to(z_inject_pair.dtype) + z_inject_pair = z_inject_pair + lm_z_i.to(z_inject_pair.dtype) # (b, l, l, d_pair) if self.msa_encoder is not None and _msa_inputs is not None: msa_i, mask_i, hd_i, dv_i = maybe_subsample_msa( @@ -1399,26 +1413,26 @@ class ESMFold2Model( _msa_inputs["deletion_value"], max_depth=_msa_inputs["max_depth"], enabled=_msa_inputs["subsample_enabled"], - ) + ) # each (b, m, l); masks/deletion tensors may be None b_msa, m, l_msa = msa_i.shape - msa_oh = F.one_hot(msa_i.permute(0, 2, 1).long(), num_classes=NUM_RES_TYPES).float() + msa_oh = F.one_hot(msa_i.permute(0, 2, 1).long(), num_classes=NUM_RES_TYPES).float() # (b, l, m, 33) msa_attn = ( mask_i.permute(0, 2, 1).float() if mask_i is not None else tok_mask[:, :, None].expand(-1, -1, m).float() - ) + ) # (b, l, m) # Bias-free MSAEncoder.embed requires zeroed padding. - msa_oh = msa_oh * msa_attn.unsqueeze(-1) + msa_oh = msa_oh * msa_attn.unsqueeze(-1) # (b, l, m, 33) hd = ( hd_i.permute(0, 2, 1).float() if hd_i is not None else torch.zeros(b_msa, l_msa, m, device=msa_i.device) - ) + ) # (b, l, m) dv = ( dv_i.permute(0, 2, 1).float() if dv_i is not None else torch.zeros(b_msa, l_msa, m, device=msa_i.device) - ) + ) # (b, l, m) msa_pair = self.msa_encoder( x_pair=z_inject_pair, x_inputs=_msa_inputs["x_inputs"], @@ -1426,19 +1440,19 @@ class ESMFold2Model( has_deletion=hd, deletion_value=dv, msa_attention_mask=msa_attn, - ).to(z_inject_pair.dtype) + ).to(z_inject_pair.dtype) # (b, l, l, d_pair) z_inject_pair = ( msa_pair if self.config.msa_encoder_overwrite else (z_inject_pair + msa_pair) - ) + ) # (b, l, l, d_pair) if refined_lm_z is not None: - z_inject_pair = z_inject_pair + refined_lm_z.to(z_inject_pair.dtype) + z_inject_pair = z_inject_pair + refined_lm_z.to(z_inject_pair.dtype) # (b, l, l, d_pair) - injected_pair = self.parcae_input_norm(z_inject_pair) - z = a * z + F.linear(injected_pair.to(z.dtype), b_mat) - z = self.folding_trunk(z, pair_attention_mask=pair_mask) + injected_pair = self.parcae_input_norm(z_inject_pair) # (b, l, l, d_pair) + z = a * z + F.linear(injected_pair.to(z.dtype), b_mat) # (b, l, l, d_pair) + z = self.folding_trunk(z, pair_attention_mask=pair_mask) # (b, l, l, d_pair) - return z + return z # (b, l, l, d_pair) def forward( self, @@ -1488,6 +1502,7 @@ class ESMFold2Model( disto_cond_mask: Tensor | None = None, verbose: bool = False, ) -> ESMFold2Output | tuple[Any, ...]: + # Token IDs/masks: (b, l); atom IDs/masks: (b, a); ref_pos: (b, a, 3); chars: (b, a, 4); MSA: (b, m, l); bs = b * samples. output_hidden_states, return_dict = _resolve_structure_output_controls( self.config, output_attentions=output_attentions, @@ -1508,9 +1523,9 @@ class ESMFold2Model( disto_cond_mask=disto_cond_mask, ) del gt_coords, is_resolved, frames_idx - tok_mask = token_attention_mask - atm_mask = atom_attention_mask - disto_idx = distogram_atom_idx + tok_mask = token_attention_mask # (b, l) + atm_mask = atom_attention_mask # (b, a) + disto_idx = distogram_atom_idx # (b, l) n_loops: int = num_loops if num_loops is not None else self.config.num_loops n_samples: int = ( @@ -1521,37 +1536,37 @@ class ESMFold2Model( total_steps = max(1, n_loops + 1) if res_type.dim() == 2: - res_type_oh = F.one_hot(res_type.long(), num_classes=NUM_RES_TYPES).float() - res_type_oh = res_type_oh * tok_mask.unsqueeze(-1).float() + res_type_oh = F.one_hot(res_type.long(), num_classes=NUM_RES_TYPES).float() # (b, l, 33) + res_type_oh = res_type_oh * tok_mask.unsqueeze(-1).float() # (b, l, 33) else: - res_type_oh = res_type.float() + res_type_oh = res_type.float() # (b, l, 33) if msa is not None: - msa_oh_profile = F.one_hot(msa.long(), num_classes=NUM_RES_TYPES).float() + msa_oh_profile = F.one_hot(msa.long(), num_classes=NUM_RES_TYPES).float() # (b, m, l, 33) if msa_attention_mask is not None: - mask_f = msa_attention_mask.float().unsqueeze(-1) - msa_oh_profile = msa_oh_profile * mask_f - valid_seq_count = msa_attention_mask.float().sum(dim=1).clamp(min=1) - profile = msa_oh_profile.sum(dim=1) / valid_seq_count.unsqueeze(-1) + mask_f = msa_attention_mask.float().unsqueeze(-1) # (b, m, l, 1) + msa_oh_profile = msa_oh_profile * mask_f # (b, m, l, 33) + valid_seq_count = msa_attention_mask.float().sum(dim=1).clamp(min=1) # (b, l) + profile = msa_oh_profile.sum(dim=1) / valid_seq_count.unsqueeze(-1) # (b, l, 33) else: - profile = msa_oh_profile.mean(dim=1) + profile = msa_oh_profile.mean(dim=1) # (b, l, 33) else: - profile = res_type_oh + profile = res_type_oh # (b, l, 33) if deletion_mean is None: deletion_mean = torch.zeros( res_type.shape[0], res_type.shape[1], device=res_type.device - ) + ) # (b, l) - ref_element_oh = F.one_hot(ref_element.long(), num_classes=MAX_ATOMIC_NUMBER).float() + ref_element_oh = F.one_hot(ref_element.long(), num_classes=MAX_ATOMIC_NUMBER).float() # (b, a, 128) ref_atom_name_chars_oh = F.one_hot( ref_atom_name_chars.long(), num_classes=CHAR_VOCAB_SIZE - ).float() + ).float() # (b, a, 4, 64) # Bias-free downstream Linears require zeroed padding. - atm_mask_f = atm_mask.float() - ref_element_oh = ref_element_oh * atm_mask_f.unsqueeze(-1) - ref_atom_name_chars_oh = ref_atom_name_chars_oh * atm_mask_f.unsqueeze(-1).unsqueeze(-1) - atom_to_token = atom_to_token * atm_mask.long() + atm_mask_f = atm_mask.float() # (b, a) + ref_element_oh = ref_element_oh * atm_mask_f.unsqueeze(-1) # (b, a, 128) + ref_atom_name_chars_oh = ref_atom_name_chars_oh * atm_mask_f.unsqueeze(-1).unsqueeze(-1) # (b, a, 4, 64) + atom_to_token = atom_to_token * atm_mask.long() # (b, a) use_amp = ref_pos.device.type == "cuda" with torch.amp.autocast("cuda", enabled=use_amp, dtype=torch.bfloat16): @@ -1566,9 +1581,9 @@ class ESMFold2Model( ref_element=ref_element_oh, ref_atom_name_chars=ref_atom_name_chars_oh, atom_to_token=atom_to_token, - ) + ) # (b, l, d_inputs) - z_init = self.z_init_1(x_inputs).unsqueeze(2) + self.z_init_2(x_inputs).unsqueeze(1) + z_init = self.z_init_1(x_inputs).unsqueeze(2) + self.z_init_2(x_inputs).unsqueeze(1) # (b, l, l, d_pair) relative_position_encoding = self.rel_pos( residue_index=residue_index, @@ -1576,9 +1591,9 @@ class ESMFold2Model( sym_id=sym_id, entity_id=entity_id, token_index=token_index, - ) - token_bonds_encoding = self.token_bonds(token_bonds.float()) - z_init = z_init + relative_position_encoding + token_bonds_encoding + ) # (b, l, l, d_pair) + token_bonds_encoding = self.token_bonds(token_bonds.float()) # (b, l, l, d_pair) + z_init = z_init + relative_position_encoding + token_bonds_encoding # (b, l, l, d_pair) if lm_hidden_states is None and input_ids is not None and self._esmc is not None: lm_hidden_states = self._compute_lm_hidden_states( @@ -1589,26 +1604,26 @@ class ESMFold2Model( tok_mask, lm_mask_pct=(self.config.lm_mask_pct if lm_mask_pct is None else lm_mask_pct), verbose=verbose, - ) - lm_z: Tensor | None = None + ) # (b, l, n_states, d_lm) + lm_z: Tensor | None = None # (b, l, l, d_pair) or None if lm_hidden_states is not None: - lm_z = self.language_model(lm_hidden_states.detach()) + lm_z = self.language_model(lm_hidden_states.detach()) # (b, l, l, d_pair) or None del lm_hidden_states - pair_mask = tok_mask[:, :, None].float() * tok_mask[:, None, :].float() + pair_mask = tok_mask[:, :, None].float() * tok_mask[:, None, :].float() # (b, l, l) - z = self._init_pair_state(z_init) + z = self._init_pair_state(z_init) # (b, l, l, d_pair) - a, b = self._discretized_dynamics() - a = a.view(1, 1, 1, -1).to(device=z.device, dtype=z.dtype) - b_mat = b.to(device=z.device, dtype=z.dtype) + a, b = self._discretized_dynamics() # (d_pair,), (d_pair, d_pair) + a = a.view(1, 1, 1, -1).to(device=z.device, dtype=z.dtype) # (1, 1, 1, d_pair) + b_mat = b.to(device=z.device, dtype=z.dtype) # (d_pair, d_pair) _msa_inputs: dict | None = None if self.msa_encoder is not None and msa is not None: msa_attention_mask = maybe_apply_msa_column_masking( msa_attention_mask, msa_column_mask_rate, - ) + ) # (b, m, l) _msa_inputs = dict( x_inputs=x_inputs, msa=msa, @@ -1631,14 +1646,14 @@ class ESMFold2Model( tok_mask=tok_mask, total_steps=total_steps, verbose=verbose, - ) + ) # (b, l, l, d_pair) del z_init, lm_z, _msa_inputs, a, b_mat - z = self.parcae_readout(z) - z = self.parcae_coda(z, pair_attention_mask=pair_mask) + z = self.parcae_readout(z) # (b, l, l, d_pair) + z = self.parcae_coda(z, pair_attention_mask=pair_mask) # (b, l, l, d_pair) - z = z.float() - distogram_logits = self.distogram_head(z + z.transpose(-2, -3)) + z = z.float() # (b, l, l, d_pair) + distogram_logits = self.distogram_head(z + z.transpose(-2, -3)) # (b, l, l, n_distogram_bins) structure_output = self.structure_head.sample( z_trunk=z, @@ -1666,13 +1681,13 @@ class ESMFold2Model( return_atom_repr=False, denoising_early_exit_rmsd=(0.10 if early_exit else None), verbose=verbose, - ) + ) # tensor mapping follows the called head's shape contract - sample_coords = structure_output["sample_atom_coords"] + sample_coords = structure_output["sample_atom_coords"] # (bs, a, 3), or explicit (b, samples, a, 3) if sample_coords is None: raise RuntimeError("ESMFold2 structure sampling did not return coordinates.") output: dict[str, Tensor] = {"distogram_logits": distogram_logits} - output["sample_atom_coords"] = sample_coords + output["sample_atom_coords"] = sample_coords # sample_coords.shape confidence_config = self.config.confidence_head confidence_enabled = ( @@ -1697,7 +1712,7 @@ class ESMFold2Model( num_diffusion_samples=n_samples, relative_position_encoding=relative_position_encoding.detach(), token_bonds_encoding=token_bonds_encoding.detach(), - ) + ) # tensor mapping follows the called head's shape contract progress.update() else: confidence_output = self.confidence_head( @@ -1713,18 +1728,18 @@ class ESMFold2Model( num_diffusion_samples=n_samples, relative_position_encoding=relative_position_encoding.detach(), token_bonds_encoding=token_bonds_encoding.detach(), - ) + ) # tensor mapping follows the called head's shape contract output.update(confidence_output) - output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask - output["residue_index"] = residue_index - output["entity_id"] = entity_id + output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask # (b, a) + output["residue_index"] = residue_index # (b, l) + output["entity_id"] = entity_id # (b, l) return _finalize_structure_output( output, token_input_state=x_inputs, pair_state=z, output_hidden_states=output_hidden_states, return_dict=return_dict, - ) + ) # ESMFold2Output/tuple retaining the traced tensor shapes @torch.no_grad() def infer_protein(self, seq: str, **forward_kwargs) -> ESMFold2Output: @@ -1743,7 +1758,7 @@ class ESMFold2Model( if not self.config.msa_conditioning: for name in MSA_CONDITIONING_INPUT_NAMES: features.pop(name, None) - features = {k: v.to(self.device) for k, v in features.items()} + features = {k: v.to(self.device) for k, v in features.items()} # every feature retains its shape return self(**features, **forward_kwargs, return_dict=True) @property @@ -2056,14 +2071,15 @@ class MSAEncoderBlock(nn.Module): msa_attention_mask: Tensor, pair_attention_mask: Tensor, ) -> tuple[Tensor, Tensor]: - pair = pair + self.outer_product_mean(m, msa_attention_mask) + # m: (b, l, m_depth, d_msa); pair: (b, l, l, d_pair); corresponding masks omit the feature axis. + pair = pair + self.outer_product_mean(m, msa_attention_mask) # (b, l, l, d_pair) if not self.is_final_block: - m = m + self.msa_pair_weighted_averaging(m, pair, pair_attention_mask) - m = m + self.msa_transition(m) - pair = pair + self.tri_mul_out(pair, mask=pair_attention_mask) - pair = pair + self.tri_mul_in(pair, mask=pair_attention_mask) - pair = pair + self.pair_transition(pair) - return m, pair + m = m + self.msa_pair_weighted_averaging(m, pair, pair_attention_mask) # (b, l, m_depth, d_msa) + m = m + self.msa_transition(m) # (b, l, m_depth, d_msa) + pair = pair + self.tri_mul_out(pair, mask=pair_attention_mask) # (b, l, l, d_pair) + pair = pair + self.tri_mul_in(pair, mask=pair_attention_mask) # (b, l, l, d_pair) + pair = pair + self.pair_transition(pair) # (b, l, l, d_pair) + return m, pair # (b, l, m_depth, d_msa), (b, l, l, d_pair) class MSAEncoder(nn.Module): @@ -2110,12 +2126,13 @@ class MSAEncoder(nn.Module): msa_attention_mask: Tensor, ) -> Tensor: # Every input tensor is pre-transposed to shape (b, l, m, ...) before this call. + # x_pair: (b, l, l, d_pair); x_inputs: (b, l, d_inputs); MSA features: (b, l, m, 33), deletion/mask: (b, l, m). m_feat = torch.cat( [msa_oh, has_deletion.unsqueeze(-1), deletion_value.unsqueeze(-1)], dim=-1 - ) - m = self.embed(m_feat) + self.project_inputs(x_inputs).unsqueeze(2) - tok_mask = msa_attention_mask[:, :, 0].bool() - pair_attention_mask = tok_mask.unsqueeze(2) & tok_mask.unsqueeze(1) + ) # (b, l, m, 35) + m = self.embed(m_feat) + self.project_inputs(x_inputs).unsqueeze(2) # (b, l, m, d_msa) + tok_mask = msa_attention_mask[:, :, 0].bool() # (b, l) + pair_attention_mask = tok_mask.unsqueeze(2) & tok_mask.unsqueeze(1) # (b, l, l) for block in self.blocks: - m, x_pair = block(m, x_pair, msa_attention_mask, pair_attention_mask) - return x_pair + m, x_pair = block(m, x_pair, msa_attention_mask, pair_attention_mask) # (b, l, m, d_msa), (b, l, l, d_pair) + return x_pair # (b, l, l, d_pair) diff --git a/fastplms/models/esmfold2/modeling_esmfold2_classification.py b/fastplms/models/esmfold2/modeling_esmfold2_classification.py index 40ef916274039d645951ce86268e70381a10e588..3f228f4331ada4b8dff105c458095da30d446a99 100644 --- a/fastplms/models/esmfold2/modeling_esmfold2_classification.py +++ b/fastplms/models/esmfold2/modeling_esmfold2_classification.py @@ -2,10 +2,10 @@ from __future__ import annotations -from typing import Any, Literal - import torch import torch.nn as nn + +from typing import Any, Literal from torch import Tensor from ..classification_probe import SequenceClassificationProbe, TokenClassificationProbe @@ -94,46 +94,47 @@ class _ESMFold2ClassificationMixin: if not sequences: raise ValueError("prepare_classifier_inputs requires at least one sequence.") encoded = [_encode_single_chain(sequence) for sequence in sequences] - sequence_length = max(map(len, encoded)) + sequence_length = max(map(len, encoded)) # l; b = len(encoded). input_ids = torch.full( (len(encoded), sequence_length), SEQUENCE_PAD_TOKEN, dtype=torch.long, device=self.device, - ) - attention_mask = torch.zeros_like(input_ids, dtype=torch.bool) + ) # (b, l) + attention_mask = torch.zeros_like(input_ids, dtype=torch.bool) # (b, l) for batch_index, token_ids in enumerate(encoded): - length = len(token_ids) + length = len(token_ids) # l_i input_ids[batch_index, :length] = torch.tensor( token_ids, dtype=torch.long, device=self.device - ) - attention_mask[batch_index, :length] = True - return {"input_ids": input_ids, "attention_mask": attention_mask} + ) # (l_i,) + attention_mask[batch_index, :length] = True # (l_i,) + return {"input_ids": input_ids, "attention_mask": attention_mask} # both (b, l) def _classifier_embeddings( self, input_ids: Tensor, attention_mask: Tensor | None ) -> tuple[Tensor, Tensor]: + # input_ids: (b, l); attention_mask: (b, l) or None. if input_ids.ndim != 2: raise ValueError( "ESMFold2 classifier input_ids must have shape (batch, residue), " f"got {tuple(input_ids.shape)}." ) if attention_mask is None: - attention_mask = input_ids.ne(SEQUENCE_PAD_TOKEN) + attention_mask = input_ids.ne(SEQUENCE_PAD_TOKEN) # (b, l) elif attention_mask.shape != input_ids.shape: raise ValueError( "ESMFold2 classifier attention_mask must match input_ids, got " f"{tuple(attention_mask.shape)} and {tuple(input_ids.shape)}." ) - residue_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool) + residue_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool) # (b, l) if not residue_mask.any(dim=1).all(): raise ValueError("Every ESMFold2 classifier input must contain a protein residue.") if input_ids.masked_select(residue_mask).eq(SEQUENCE_PAD_TOKEN).any(): raise ValueError("ESMFold2 classifier padding tokens cannot be attended residues.") - residue_ids = input_ids.masked_select(residue_mask) + residue_ids = input_ids.masked_select(residue_mask) # (n_present,) valid_residue_ids = torch.tensor( sorted(_VALID_RESIDUE_IDS), dtype=input_ids.dtype, device=input_ids.device - ) + ) # (n_valid_ids,) if not torch.isin(residue_ids, valid_residue_ids).all(): raise ValueError( "ESMFold2 classifiers accept residue-only single-chain protein inputs." @@ -142,9 +143,9 @@ class _ESMFold2ClassificationMixin: batch_size, sequence_length = input_ids.shape residue_index = torch.arange(sequence_length, device=input_ids.device).expand( batch_size, -1 - ) - asym_id = torch.zeros_like(input_ids) - mol_type = torch.zeros_like(input_ids) + ) # (b, l) + asym_id = torch.zeros_like(input_ids) # (b, l) + mol_type = torch.zeros_like(input_ids) # (b, l) with torch.no_grad(): hidden_states = self._compute_lm_hidden_states( input_ids, @@ -152,9 +153,9 @@ class _ESMFold2ClassificationMixin: residue_index, mol_type, residue_mask, - ) - embeddings = self.project_esmc_hidden_states(hidden_states, residue_mask) - return embeddings, residue_mask + ) # (b, l, n_states, d_lm) + embeddings = self.project_esmc_hidden_states(hidden_states, residue_mask) # (b, l, d_pair) + return embeddings, residue_mask # (b, l, d_pair), (b, l) def _classifier_forward( self, @@ -165,7 +166,7 @@ class _ESMFold2ClassificationMixin: output_hidden_states: bool | None = None, return_dict: bool | None = None, ): - embeddings, residue_mask = self._classifier_embeddings(input_ids, attention_mask) + embeddings, residue_mask = self._classifier_embeddings(input_ids, attention_mask) # (b, l, d_pair), (b, l) return self.classifier( embeddings, attention_mask=residue_mask, diff --git a/fastplms/models/esmfold2/modeling_esmfold2_common.py b/fastplms/models/esmfold2/modeling_esmfold2_common.py index e032243b2bf50c9669f1e8ffbc602341b65da1aa..e07e299e4cb61b2ad4811ced5a98c9cc8c4522ae 100644 --- a/fastplms/models/esmfold2/modeling_esmfold2_common.py +++ b/fastplms/models/esmfold2/modeling_esmfold2_common.py @@ -10,13 +10,13 @@ from __future__ import annotations import importlib -from functools import partial -from importlib.util import find_spec -from typing import Any, ClassVar, cast - import torch import torch.nn as nn import torch.nn.functional as F + +from functools import partial +from importlib.util import find_spec +from typing import Any, ClassVar, cast from torch import Tensor from torch.utils.checkpoint import checkpoint from tqdm.auto import tqdm @@ -24,6 +24,7 @@ from tqdm.auto import tqdm from .configuration_esmfold2 import ESMFold2Config from .reproducibility import seed_context + _seed_context = seed_context try: @@ -197,15 +198,16 @@ class DropoutResidual(nn.Module): self._impl = nn.Dropout(r) def forward(self, residual: Tensor, delta: Tensor) -> Tensor: + # residual, delta: same shape; dropout shares delta axis self._batch_dim. if self._use_fused_kernels: - return self._impl(residual, delta) - # The unfused path broadcasts a row/column-shared mask M with shape (1, ...). + return self._impl(residual, delta) # delta.shape + # The unfused mask shares the selected row/column axis and retains the other dimensions. if self._r == 0.0 or not self.training: - return residual + delta + return residual + delta # delta.shape shape = list(delta.shape) shape[self._batch_dim] = 1 - mask = self._impl(delta.new_ones(shape)) - return residual + delta * mask + mask = self._impl(delta.new_ones(shape)) # delta.shape with shared axis set to 1 + return residual + delta * mask # delta.shape # --------------------------------------------------------------------------- @@ -217,7 +219,7 @@ XYZ_DIMS: int = 3 MAX_ATOMIC_NUMBER: int = 128 # Input feature dim = 3 + 1 + 1 + 128 + 64*4 = 389 -ATOM_FEATURE_DIM: int = XYZ_DIMS + 1 + 1 + MAX_ATOMIC_NUMBER + CHAR_VOCAB_SIZE * MAX_CHARS +ATOM_FEATURE_DIM: int = XYZ_DIMS + 1 + 1 + MAX_ATOMIC_NUMBER + CHAR_VOCAB_SIZE * MAX_CHARS # d_atom_features NUM_RES_TYPES: int = 33 @@ -246,39 +248,41 @@ def maybe_subsample_msa( max_depth: int | None, enabled: bool, ) -> tuple[Tensor, Tensor | None, Tensor | None, Tensor | None]: + # MSA tensors: (b, m, l); k = max_depth for subsampled rows. if not enabled or max_depth is None: - return msa, msa_attention_mask, has_deletion, deletion_value + return msa, msa_attention_mask, has_deletion, deletion_value # MSA tensors retain (b, selected_depth, l); optional tensors remain None depth = msa.size(1) if depth <= 1 or depth <= max_depth: - return msa, msa_attention_mask, has_deletion, deletion_value + return msa, msa_attention_mask, has_deletion, deletion_value # MSA tensors retain (b, selected_depth, l); optional tensors remain None - indices = torch.zeros(max_depth, dtype=torch.long, device=msa.device) - indices[1:] = torch.randperm(depth - 1, device=msa.device)[: max_depth - 1] + 1 - indices = indices.sort().values + indices = torch.zeros(max_depth, dtype=torch.long, device=msa.device) # (k,) + indices[1:] = torch.randperm(depth - 1, device=msa.device)[: max_depth - 1] + 1 # (k - 1,) + indices = indices.sort().values # (k,) - msa = msa[:, indices] + msa = msa[:, indices] # (b, k, l) if msa_attention_mask is not None: - msa_attention_mask = msa_attention_mask[:, indices] + msa_attention_mask = msa_attention_mask[:, indices] # (b, k, l) if has_deletion is not None: - has_deletion = has_deletion[:, indices] + has_deletion = has_deletion[:, indices] # (b, k, l) if deletion_value is not None: - deletion_value = deletion_value[:, indices] - return msa, msa_attention_mask, has_deletion, deletion_value + deletion_value = deletion_value[:, indices] # (b, k, l) + return msa, msa_attention_mask, has_deletion, deletion_value # MSA tensors retain (b, selected_depth, l); optional tensors remain None def maybe_apply_msa_column_masking( msa_attention_mask: Tensor | None, rate: float, ) -> Tensor | None: + # msa_attention_mask: (b, m, l) or None. if msa_attention_mask is None or rate <= 0.0 or msa_attention_mask.size(1) <= 1: - return msa_attention_mask + return msa_attention_mask # (b, m, l) or None batch_size, _, length = msa_attention_mask.shape - col_keep = torch.rand(batch_size, length, device=msa_attention_mask.device) >= rate - col_keep = col_keep.unsqueeze(1).expand_as(msa_attention_mask).clone() - col_keep[:, 0, :] = True - return msa_attention_mask.bool() & col_keep + col_keep = torch.rand(batch_size, length, device=msa_attention_mask.device) >= rate # (b, l) + col_keep = col_keep.unsqueeze(1).expand_as(msa_attention_mask).clone() # (b, m, l) + col_keep[:, 0, :] = True # (b, l) + return msa_attention_mask.bool() & col_keep # (b, m, l) or None # =========================================================================== @@ -296,8 +300,8 @@ def gather_token_to_atom(token_features: Tensor, atom_to_token_idx: Tensor) -> T Returns: X with shape (b, a, d). """ - idx = atom_to_token_idx.unsqueeze(-1).expand(-1, -1, token_features.size(-1)) - return torch.gather(token_features, 1, idx) + idx = atom_to_token_idx.unsqueeze(-1).expand(-1, -1, token_features.size(-1)) # (b, a, d) + return torch.gather(token_features, 1, idx) # (b, a, d) def scatter_atom_to_token( @@ -319,20 +323,20 @@ def scatter_atom_to_token( """ batch_size, n_atoms, d_model = atom_features.shape n_out = n_tokens - idx = atom_to_token_idx + idx = atom_to_token_idx # (b, a) if atom_mask is not None: - idx = torch.where(atom_mask, atom_to_token_idx, n_tokens) + idx = torch.where(atom_mask, atom_to_token_idx, n_tokens) # (b, a) n_out = n_tokens + 1 - idx_expanded = idx.unsqueeze(-1).expand(batch_size, n_atoms, d_model) + idx_expanded = idx.unsqueeze(-1).expand(batch_size, n_atoms, d_model) # (b, a, d) out = torch.zeros( batch_size, n_out, d_model, device=atom_features.device, dtype=atom_features.dtype, - ) - out.scatter_reduce_(1, idx_expanded, atom_features, reduce="mean", include_self=False) - return out[:, :n_tokens, :] + ) # (b, n_out, d) + out.scatter_reduce_(1, idx_expanded, atom_features, reduce="mean", include_self=False) # (b, n_out, d) + return out[:, :n_tokens, :] # (b, l, d) def gather_rep_atom_coords(coords: Tensor, rep_atom_idx: Tensor) -> Tensor: @@ -345,8 +349,8 @@ def gather_rep_atom_coords(coords: Tensor, rep_atom_idx: Tensor) -> Tensor: Returns: X with shape (b, l, 3). """ - idx = rep_atom_idx.unsqueeze(-1).expand(-1, -1, coords.size(-1)) - return torch.gather(coords, 1, idx) + idx = rep_atom_idx.unsqueeze(-1).expand(-1, -1, coords.size(-1)) # (b, l, 3) + return torch.gather(coords, 1, idx) # (b, l, 3) def _compute_intra_token_idx(atom_to_token: Tensor) -> Tensor: @@ -362,12 +366,12 @@ def _compute_intra_token_idx(atom_to_token: Tensor) -> Tensor: Index tensor I with shape (b, a) and values from zero through ``max_atoms_per_token - 1``. """ - same_as_prev = F.pad(atom_to_token[:, 1:] == atom_to_token[:, :-1], (1, 0), value=False) - ones = torch.ones_like(atom_to_token) - cumsum = torch.cumsum(ones, dim=-1) - group_start = cumsum.masked_fill(same_as_prev, 0) - group_start = torch.cummax(group_start, dim=-1).values - return cumsum - group_start + same_as_prev = F.pad(atom_to_token[:, 1:] == atom_to_token[:, :-1], (1, 0), value=False) # (b, a) + ones = torch.ones_like(atom_to_token) # (b, a) + cumsum = torch.cumsum(ones, dim=-1) # (b, a) + group_start = cumsum.masked_fill(same_as_prev, 0) # (b, a) + group_start = torch.cummax(group_start, dim=-1).values # (b, a) + return cumsum - group_start # (b, a) def _categorical_mean(logits: Tensor, start: float, end: float) -> Tensor: @@ -384,9 +388,9 @@ def _categorical_mean(logits: Tensor, start: float, end: float) -> Tensor: Expected value tensor Y with shape (...). """ n_bins = logits.shape[-1] - edges = torch.linspace(start, end, n_bins + 1, device=logits.device, dtype=torch.float32) + edges = torch.linspace(start, end, n_bins + 1, device=logits.device, dtype=torch.float32) # (n_bins + 1,) v_bins = (edges[:-1] + edges[1:]) / 2 # V_bin has shape (n_bins,). - return (logits.float().softmax(-1) @ v_bins.unsqueeze(1)).squeeze(-1) + return (logits.float().softmax(-1) @ v_bins.unsqueeze(1)).squeeze(-1) # logits.shape[:-1] # =========================================================================== @@ -403,16 +407,17 @@ class RowAttentionPooling(nn.Module): self.out_proj = nn.Linear(d_pair, d_single, bias=False) def forward(self, z: Tensor, mask: Tensor) -> Tensor: - scores = self.attn_proj(z).squeeze(-1) + # z: (b, l, l, d_pair); mask: (b, l). + scores = self.attn_proj(z).squeeze(-1) # (b, l, l) mask_bias = torch.where( mask[:, None, :].bool(), torch.zeros_like(scores), torch.full_like(scores, -1e9), - ) - scores = scores + mask_bias - weights = F.softmax(scores, dim=-1) - pooled = torch.einsum("bnm,bnmd->bnd", weights, z) - return self.out_proj(pooled) + ) # (b, l, l) + scores = scores + mask_bias # (b, l, l) + weights = F.softmax(scores, dim=-1) # (b, l, l) + pooled = torch.einsum("bnm,bnmd->bnd", weights, z) # (b, l, d_pair) + return self.out_proj(pooled) # (b, l, d_single) # =========================================================================== @@ -460,6 +465,7 @@ class InputsEmbedder(nn.Module): X with shape (b, l, d_inputs), concatenating atom encoding, aatype, profile, and deletion mean. """ + # aatype/profile: (b, l, 33); deletion_mean: (b, l); atom features use a atoms. a, _q, _c, _attn_params, _intermediates = self.atom_attention_encoder( ref_pos=ref_pos, atom_attention_mask=atom_attention_mask, @@ -468,8 +474,8 @@ class InputsEmbedder(nn.Module): ref_element=ref_element, ref_atom_name_chars=ref_atom_name_chars, atom_to_token=atom_to_token, - ) - return torch.cat([a, aatype, profile, deletion_mean.unsqueeze(-1)], dim=-1) + ) # a: (b, l, d_token / 2); _q/_c: (b, a, d_atom) + return torch.cat([a, aatype, profile, deletion_mean.unsqueeze(-1)], dim=-1) # (b, l, d_token / 2 + 67) # =========================================================================== @@ -511,38 +517,39 @@ class ResIdxAsymIdSymIdEntityIdEncoding(nn.Module): entity_id: Tensor, token_index: Tensor, ) -> Tensor: - bij_same_chain = asym_id.unsqueeze(2) == asym_id.unsqueeze(1) - bij_same_residue = residue_index.unsqueeze(2) == residue_index.unsqueeze(1) - bij_same_entity = entity_id.unsqueeze(2) == entity_id.unsqueeze(1) + # All input IDs: (b, l); r/c are relative residue/chain bin counts. + bij_same_chain = asym_id.unsqueeze(2) == asym_id.unsqueeze(1) # (b, l, l) + bij_same_residue = residue_index.unsqueeze(2) == residue_index.unsqueeze(1) # (b, l, l) + bij_same_entity = entity_id.unsqueeze(2) == entity_id.unsqueeze(1) # (b, l, l) - dij_residue = residue_index.unsqueeze(2) - residue_index.unsqueeze(1) + dij_residue = residue_index.unsqueeze(2) - residue_index.unsqueeze(1) # (b, l, l) dij_residue = torch.clip( dij_residue + self.n_relative_residx_bins, 0, 2 * self.n_relative_residx_bins, - ) - dij_residue = torch.where(bij_same_chain, dij_residue, 2 * self.n_relative_residx_bins + 1) - aij_rel_pos = F.one_hot(dij_residue, 2 * self.n_relative_residx_bins + 2) + ) # (b, l, l) + dij_residue = torch.where(bij_same_chain, dij_residue, 2 * self.n_relative_residx_bins + 1) # (b, l, l) + aij_rel_pos = F.one_hot(dij_residue, 2 * self.n_relative_residx_bins + 2) # (b, l, l, 2 * r + 2) dij_token = torch.clip( token_index.unsqueeze(2) - token_index.unsqueeze(1) + self.n_relative_residx_bins, 0, 2 * self.n_relative_residx_bins, - ) + ) # (b, l, l) dij_token = torch.where( bij_same_chain & bij_same_residue, dij_token, 2 * self.n_relative_residx_bins + 1, - ) - aij_rel_token = F.one_hot(dij_token, 2 * self.n_relative_residx_bins + 2) + ) # (b, l, l) + aij_rel_token = F.one_hot(dij_token, 2 * self.n_relative_residx_bins + 2) # (b, l, l, 2 * r + 2) dij_chain = torch.clip( sym_id.unsqueeze(2) - sym_id.unsqueeze(1) + self.n_relative_chain_bins, 0, 2 * self.n_relative_chain_bins, - ) - dij_chain = torch.where(bij_same_chain, 2 * self.n_relative_chain_bins + 1, dij_chain) - aij_rel_chain = F.one_hot(dij_chain, 2 * self.n_relative_chain_bins + 2) + ) # (b, l, l) + dij_chain = torch.where(bij_same_chain, 2 * self.n_relative_chain_bins + 1, dij_chain) # (b, l, l) + aij_rel_chain = F.one_hot(dij_chain, 2 * self.n_relative_chain_bins + 2) # (b, l, l, 2 * c + 2) feats = torch.cat( [ @@ -552,9 +559,9 @@ class ResIdxAsymIdSymIdEntityIdEncoding(nn.Module): aij_rel_chain.float(), ], dim=-1, - ) + ) # (b, l, l, 2 * (2 * r + 2) + 1 + 2 * c + 2) - return self.embed(feats) + return self.embed(feats) # (b, l, l, d_pair) # =========================================================================== @@ -575,12 +582,13 @@ class SingleToPair(nn.Module): ) def forward(self, x: Tensor) -> Tensor: - x = self.downproject(x) + # x: (b, l, input_dim); d_down is downproject.out_features. + x = self.downproject(x) # (b, l, d_down) x = torch.cat( [(x.unsqueeze(2) * x.unsqueeze(1)), (x.unsqueeze(2) - x.unsqueeze(1))], dim=3, - ) - return self.output_mlp(x) + ) # (b, l, l, 2 * d_down) + return self.output_mlp(x) # (b, l, l, output_dim) # =========================================================================== @@ -604,7 +612,7 @@ class LanguageModelShim(nn.Module): self.base_z_linear = nn.Sequential( nn.LayerNorm(d_model), nn.Linear(d_model, d_z, bias=False) ) - self.base_z_combine = nn.Parameter(torch.zeros(num_layers + 1)) + self.base_z_combine = nn.Parameter(torch.zeros(num_layers + 1)) # (num_layers + 1,) def project_sequence( self, @@ -641,12 +649,12 @@ class LanguageModelShim(nn.Module): # Match the learned projection parameters at this explicit boundary; # this preserves the official BF16 path and leaves FP32 models exact. projection_dtype = cast(nn.LayerNorm, self.base_z_linear[0]).weight.dtype - hidden_states = hidden_states.to(dtype=projection_dtype) - projected_states = self.base_z_linear(hidden_states) - layer_weights = self.base_z_combine.softmax(dim=0) + hidden_states = hidden_states.to(dtype=projection_dtype) # (b, l, n_layers + 1, d_model) + projected_states = self.base_z_linear(hidden_states) # (b, l, n_layers + 1, d_z) + layer_weights = self.base_z_combine.softmax(dim=0) # (n_layers + 1,) # Preserve Biohub's matmul path exactly so checkpoint inference does # not change through a different reduction order. - projected = layer_weights @ projected_states + projected = layer_weights @ projected_states # (b, l, d_z) if residue_mask is not None: if residue_mask.shape != hidden_states.shape[:2]: raise ValueError( @@ -655,8 +663,8 @@ class LanguageModelShim(nn.Module): ) projected = projected * residue_mask.to( device=projected.device, dtype=projected.dtype - ).unsqueeze(-1) - return projected + ).unsqueeze(-1) # (b, l, d_z) + return projected # (b, l, d_z) def forward(self, hidden_states: Tensor, *, lm_dropout: float = 0.0) -> Tensor: """Project pre-computed ESMC hidden states to pair representation. @@ -669,11 +677,11 @@ class LanguageModelShim(nn.Module): Returns: Z_pair with shape ``(b, l, l, d_pair)``. """ - lm_z = self.project_sequence(hidden_states) - lm_z = self.base_z_mlp(lm_z) + lm_z = self.project_sequence(hidden_states) # (b, l, d_z) + lm_z = self.base_z_mlp(lm_z) # (b, l, l, d_z) if lm_dropout > 0: - lm_z = F.dropout(lm_z, p=lm_dropout, training=True) - return lm_z + lm_z = F.dropout(lm_z, p=lm_dropout, training=True) # (b, l, l, d_z) + return lm_z # (b, l, l, d_z) # =========================================================================== @@ -700,62 +708,63 @@ def compute_lm_hidden_states( was trained on per-residue inputs, not per-atom), then scatter the hidden states back to the per-token layout. """ + # Input IDs/masks: (b, l). Per sample p protein tokens collapse to u residues; t is padded LM length. b_size, l_size = input_ids.shape device = input_ids.device - protein_mask = (mol_type == 0) & token_mask + protein_mask = (mol_type == 0) & token_mask # (b, l) lm_input_list = [] lm_lengths = [] # Per-batch maps from (original protein-token index) to (LM input position). expand_maps: list[Tensor] = [] for batch_index in range(b_size): - mask_b = protein_mask[batch_index] - ids_b = input_ids[batch_index][mask_b] - asym_b = asym_id[batch_index][mask_b] - res_b = residue_index[batch_index][mask_b] + mask_b = protein_mask[batch_index] # (l,) + ids_b = input_ids[batch_index][mask_b] # (p,) + asym_b = asym_id[batch_index][mask_b] # (p,) + res_b = residue_index[batch_index][mask_b] # (p,) # Collapse: keep first token per (asym_id, residue_index) key, in # input order. ``inverse`` maps each original protein-token to its # collapsed residue index. - keys = torch.stack((asym_b, res_b), dim=1) - unique_keys, inverse = torch.unique(keys, dim=0, return_inverse=True) + keys = torch.stack((asym_b, res_b), dim=1) # (p, 2) + unique_keys, inverse = torch.unique(keys, dim=0, return_inverse=True) # (u, 2), (p,) n_unique = unique_keys.size(0) - token_positions = torch.arange(keys.size(0), device=device, dtype=torch.long) - first_pos = torch.full((n_unique,), keys.size(0), device=device, dtype=torch.long) - first_pos.scatter_reduce_(0, inverse, token_positions, reduce="amin", include_self=True) - ordered = torch.argsort(first_pos) - first_pos_ordered = first_pos[ordered] - ids_collapsed = ids_b[first_pos_ordered] - asym_collapsed = asym_b[first_pos_ordered] - remap = torch.empty_like(ordered) - remap[ordered] = torch.arange(n_unique, device=device, dtype=torch.long) - inverse_ordered = remap[inverse] - - chain_ids = asym_collapsed.unique(sorted=True) + token_positions = torch.arange(keys.size(0), device=device, dtype=torch.long) # (p,) + first_pos = torch.full((n_unique,), keys.size(0), device=device, dtype=torch.long) # (u,) + first_pos.scatter_reduce_(0, inverse, token_positions, reduce="amin", include_self=True) # (u,) + ordered = torch.argsort(first_pos) # (u,) + first_pos_ordered = first_pos[ordered] # (u,) + ids_collapsed = ids_b[first_pos_ordered] # (u,) + asym_collapsed = asym_b[first_pos_ordered] # (u,) + remap = torch.empty_like(ordered) # (u,) + remap[ordered] = torch.arange(n_unique, device=device, dtype=torch.long) # (u,) + inverse_ordered = remap[inverse] # (p,) + + chain_ids = asym_collapsed.unique(sorted=True) # (n_chains,) # [BOS] chain1 [EOS BOS] chain2 ... [EOS] - parts: list[Tensor] = [torch.tensor([0], device=device, dtype=ids_b.dtype)] + parts: list[Tensor] = [torch.tensor([0], device=device, dtype=ids_b.dtype)] # list of 1D token tensors # Per-chain LM positions accumulate; track them for the expand map. - per_token_lm_pos = torch.empty(n_unique, device=device, dtype=torch.long) + per_token_lm_pos = torch.empty(n_unique, device=device, dtype=torch.long) # (u,) cursor = 1 # position 0 is the leading BOS for i, cid in enumerate(chain_ids): - in_chain = (asym_collapsed == cid).nonzero(as_tuple=True)[0] + in_chain = (asym_collapsed == cid).nonzero(as_tuple=True)[0] # (u_chain,) parts.append(ids_collapsed[in_chain]) per_token_lm_pos[in_chain] = torch.arange( cursor, cursor + in_chain.shape[0], device=device, dtype=torch.long - ) + ) # (u_chain,) cursor += in_chain.shape[0] if i < len(chain_ids) - 1: parts.append(torch.tensor([2, 0], device=device, dtype=ids_b.dtype)) cursor += 2 # EOS + BOS parts.append(torch.tensor([2], device=device, dtype=ids_b.dtype)) - lm_seq = torch.cat(parts) + lm_seq = torch.cat(parts) # (t_i,) lm_input_list.append(lm_seq) lm_lengths.append(lm_seq.shape[0]) # Map each original protein-token position to its LM input position. - prot_pos_b = mask_b.nonzero(as_tuple=True)[0] - expand_map = torch.full((l_size,), -1, device=device, dtype=torch.long) - expand_map[prot_pos_b] = per_token_lm_pos[inverse_ordered] + prot_pos_b = mask_b.nonzero(as_tuple=True)[0] # (p,) + expand_map = torch.full((l_size,), -1, device=device, dtype=torch.long) # (l,) + expand_map[prot_pos_b] = per_token_lm_pos[inverse_ordered] # (p,) expand_maps.append(expand_map) # Pad the language-model input to its longest sequence. FP8 callers round @@ -768,32 +777,32 @@ def compute_lm_hidden_states( 1, device=device, dtype=input_ids.dtype, # PAD=1 - ) + ) # (b, t) for batch_index in range(b_size): - lm_input_ids[batch_index, : lm_lengths[batch_index]] = lm_input_list[batch_index] + lm_input_ids[batch_index, : lm_lengths[batch_index]] = lm_input_list[batch_index] # (t_i,) # sequence_id for chain-aware attention; PAD tokens get -1 (no attention). - sequence_id = (lm_input_ids == 0).cumsum(dim=1) - 1 # BOS=0 - sequence_id = sequence_id.masked_fill(lm_input_ids == 1, -1) # PAD=1 + sequence_id = (lm_input_ids == 0).cumsum(dim=1) - 1 # BOS=0; (b, t) + sequence_id = sequence_id.masked_fill(lm_input_ids == 1, -1) # PAD=1; (b, t) if lm_mask_pct > 0.0: - special = (lm_input_ids == 0) | (lm_input_ids == 1) | (lm_input_ids == 2) - do_mask = (torch.rand(lm_input_ids.shape, device=device) < lm_mask_pct) & ~special - lm_input_ids = lm_input_ids.masked_fill(do_mask, mask_token_id) + special = (lm_input_ids == 0) | (lm_input_ids == 1) | (lm_input_ids == 2) # (b, t) + do_mask = (torch.rand(lm_input_ids.shape, device=device) < lm_mask_pct) & ~special # (b, t) + lm_input_ids = lm_input_ids.masked_fill(do_mask, mask_token_id) # (b, t) with torch.inference_mode(): esmc_out = esmc(input_ids=lm_input_ids, sequence_id=sequence_id, output_hidden_states=True) - hidden_stack = esmc_out.hidden_states + hidden_stack = esmc_out.hidden_states # (n_states, b, t, d_model) n_states, _, _, d_model = hidden_stack.shape - result = torch.zeros(b_size, l_size, n_states, d_model, device=device, dtype=hidden_stack.dtype) + result = torch.zeros(b_size, l_size, n_states, d_model, device=device, dtype=hidden_stack.dtype) # (b, l, n_states, d_model) for batch_index in range(b_size): - M_i = protein_mask[batch_index] - positions = expand_maps[batch_index][M_i] - gathered = hidden_stack[:, batch_index, positions, :].permute(1, 0, 2) - result[batch_index, M_i.nonzero(as_tuple=True)[0]] = gathered + M_i = protein_mask[batch_index] # (l,) + positions = expand_maps[batch_index][M_i] # (p,) + gathered = hidden_stack[:, batch_index, positions, :].permute(1, 0, 2) # (p, n_states, d_model) + result[batch_index, M_i.nonzero(as_tuple=True)[0]] = gathered # (p, n_states, d_model) - return result.detach() + return result.detach() # (b, l, n_states, d_model) # =========================================================================== @@ -844,7 +853,8 @@ class TriangleMultiplicativeBlock(nn.Module): return self.flow def _triangular_contract(self, left_stream: Tensor, right_stream: Tensor) -> Tensor: - return torch.einsum(self._einsum_equation, left_stream, right_stream) + # Streams: (b, l, l, d_latent); equation chooses incoming/outgoing contraction. + return torch.einsum(self._einsum_equation, left_stream, right_stream) # (b, l, l, d_latent) def _triangular_contract_chunked( self, left_stream: Tensor, right_stream: Tensor, chunk_size: int @@ -869,17 +879,18 @@ class TriangleMultiplicativeBlock(nn.Module): chunks = [] for start in range(0, length, chunk_size): rows = left_rows[:, :, start : start + chunk_size] # (b, d, i_c, k) - product = torch.bmm(rows.reshape(batch_size * channels, -1, inner), right_columns) + product = torch.bmm(rows.reshape(batch_size * channels, -1, inner), right_columns) # (b * d, i_c, j) product = product.view(batch_size, channels, rows.shape[2], -1) # (b, d, i_c, j) chunks.append(product.permute(0, 2, 3, 1)) # (b, i_c, j, d) - return torch.cat(chunks, dim=1) + return torch.cat(chunks, dim=1) # (b, l, l, d) def forward(self, pair_grid: Tensor, visibility: Tensor | None = None) -> Tensor: + # pair_grid: (b, l, l, d_input); visibility: (b, l, l); d = latent_channels. if visibility is None: - visibility = pair_grid.new_ones(pair_grid.shape[:-1]) + visibility = pair_grid.new_ones(pair_grid.shape[:-1]) # (b, l, l) if self._use_kernels: - p_in_weight, g_in_weight = self.split_kernel_weights() + p_in_weight, g_in_weight = self.split_kernel_weights() # each (2 * d, d_input) return _cue_tri_mul( # type: ignore[misc] pair_grid, direction=self._kernel_flow_direction(), @@ -893,38 +904,38 @@ class TriangleMultiplicativeBlock(nn.Module): p_out_weight=self.proj_emit.weight, g_out_weight=self.proj_gate.weight, eps=_EPS, - ) + ) # (b, l, l, d_input) # Every tensor below is as large as the pair representation or larger, and this # block sets the peak memory of a fold. Each name is dropped once it is dead, so # the allocator can reuse its buffer. No value changes. - normalized_grid = self.norm_start(pair_grid) + normalized_grid = self.norm_start(pair_grid) # (b, l, l, d_input) bundled = self.proj_bundle(normalized_grid) # (b, l, l, 4 * d) - signal, gate_logits = bundled.split(2 * self.latent_channels, dim=-1) + signal, gate_logits = bundled.split(2 * self.latent_channels, dim=-1) # each (b, l, l, 2 * d) routed = signal * torch.sigmoid(gate_logits) # (b, l, l, 2 * d) # The two views would keep the whole projection alive. del bundled, signal, gate_logits - routed = routed * visibility.unsqueeze(-1) + routed = routed * visibility.unsqueeze(-1) # (b, l, l, 2 * d) left_stream, right_stream = routed.float().chunk(2, dim=-1) # each (b, l, l, d) if torch.is_autocast_enabled(left_stream.device.type): # The contraction is an autocast operation. Casting its inputs here, as it # would, lets the full-precision product go before the contraction runs. autocast_dtype = torch.get_autocast_dtype(left_stream.device.type) - left_stream = left_stream.to(autocast_dtype) - right_stream = right_stream.to(autocast_dtype) + left_stream = left_stream.to(autocast_dtype) # (b, l, l, d) + right_stream = right_stream.to(autocast_dtype) # (b, l, l, d) del routed if self._chunk_size is not None: contracted = self._triangular_contract_chunked( left_stream, right_stream, self._chunk_size - ) + ) # (b, l, l, d) else: - contracted = self._triangular_contract(left_stream, right_stream) + contracted = self._triangular_contract(left_stream, right_stream) # (b, l, l, d) del left_stream, right_stream - mixed = self.proj_emit(self.norm_mix(contracted)) + mixed = self.proj_emit(self.norm_mix(contracted)) # (b, l, l, d_input) del contracted - output_gate = torch.sigmoid(self.proj_gate(normalized_grid)) - return mixed * output_gate + output_gate = torch.sigmoid(self.proj_gate(normalized_grid)) # (b, l, l, d_input) + return mixed * output_gate # (b, l, l, d_input) class TriangleMultiplicativeUpdate(nn.Module): @@ -947,7 +958,8 @@ class TriangleMultiplicativeUpdate(nn.Module): self._engine.set_chunk_size(chunk_size) def forward(self, z: Tensor, mask: Tensor | None = None) -> Tensor: - return self._engine(z, visibility=mask) + # z: (b, l, l, d_pair); mask: (b, l, l) or None. + return self._engine(z, visibility=mask) # (b, l, l, d_pair) # =========================================================================== @@ -989,11 +1001,11 @@ class Transition(nn.Module): dtype=dtype, ) with torch.no_grad(): - fused.LN_W.copy_(self.norm.weight) + fused.LN_W.copy_(self.norm.weight) # (d_model,) if has_ln_bias: fused.LN_B.copy_(self.norm.bias) # type: ignore[union-attr] # FusedLNLinearSwiGLU.W12 is (d_model, 2*d_inner); transpose nn.Linear once. - fused.W12.copy_(self.ffn.w12.weight.t().contiguous()) + fused.W12.copy_(self.ffn.w12.weight.t().contiguous()) # (d_model, 2 * d_inner) self._fused_swiglu = fused.eval().requires_grad_(False) else: self._fused_swiglu = None @@ -1005,50 +1017,53 @@ class Transition(nn.Module): def _swiglu_pre_w3(self, x_normed: Tensor) -> Tensor: """SwiGLU through silu(x1)*x2, before the final w3.""" + # x_normed: (..., d_model); d_inner = ffn.hidden_features. ffn = self.ffn - x12 = ffn.w12(x_normed) - x1, x2 = x12.split(ffn.hidden_features, dim=-1) - return F.silu(x1) * x2 + x12 = ffn.w12(x_normed) # (..., 2 * d_inner) + x1, x2 = x12.split(ffn.hidden_features, dim=-1) # each (..., d_inner) + return F.silu(x1) * x2 # (..., d_inner) def _addmm_residual(self, x: Tensor, hidden: Tensor) -> Tensor: """x + w3(hidden) via single cuBLAS addmm: avoids transition-output allocation.""" + # x: (..., d_model); hidden: (..., d_inner). ffn = self.ffn x_shape = x.shape out = torch.addmm( x.contiguous().view(-1, x_shape[-1]), hidden.view(-1, hidden.shape[-1]), ffn.w3.weight.t(), - ) - return out.view(x_shape) + ) # (product(x.shape[:-1]), d_model) + return out.view(x_shape) # x.shape def forward(self, x: Tensor) -> Tensor: # Inference-only fast path (addmm-fused residual + pre-alloc out) #: diverges bit-exactly from ``x + ffn(norm(x))`` so we only use # it when grad is disabled (binder-design / bit-exact tests run # with grad on and need the reference path). + # x: (b, l, ..., d_model); chunk width l_c <= _chunk_size. if not torch.is_grad_enabled() and self._can_use_fused_path(x): fused = self._fused_swiglu assert fused is not None pre_w3 = fused if self._chunk_size is None or x.shape[1] <= self._chunk_size: - hidden = pre_w3(x) - return self._addmm_residual(x, hidden) - out = torch.empty_like(x) + hidden = pre_w3(x) # (b, l, ..., d_inner) + return self._addmm_residual(x, hidden) # x.shape + out = torch.empty_like(x) # x.shape for s in range(0, x.shape[1], self._chunk_size): e = min(s + self._chunk_size, x.shape[1]) - sl = x[:, s:e] - hidden = pre_w3(sl) - out[:, s:e] = self._addmm_residual(sl, hidden) - return out + sl = x[:, s:e] # (b, l_c, ..., d_model) + hidden = pre_w3(sl) # (b, l_c, ..., d_inner) + out[:, s:e] = self._addmm_residual(sl, hidden) # (b, l_c, ..., d_model) + return out # x.shape # Reference path: bit-exact with main: x + ffn(norm(x)). if self._chunk_size is None or x.shape[1] <= self._chunk_size: - return x + self.ffn(self.norm(x)) + return x + self.ffn(self.norm(x)) # x.shape out_list: list[Tensor] = [] for s in range(0, x.shape[1], self._chunk_size): e = min(s + self._chunk_size, x.shape[1]) - sl = x[:, s:e] + sl = x[:, s:e] # (b, l_c, ..., d_model) out_list.append(sl + self.ffn(self.norm(sl))) - return torch.cat(out_list, dim=1) + return torch.cat(out_list, dim=1) # x.shape class PairUpdateBlock(nn.Module): @@ -1087,12 +1102,13 @@ class PairUpdateBlock(nn.Module): self, pair: Tensor, direction: str, pair_attention_mask: Tensor | None ) -> Tensor: """Fused TriMul+residual call; weights from the corresponding engine.""" + # pair: (b, l, l, d_pair); pair_attention_mask: (b, l, l) or None. tri = self.tri_mul_out if direction == "outgoing" else self.tri_mul_in engine: TriangleMultiplicativeBlock = tri._engine # type: ignore[assignment] - p_in_weight, g_in_weight = engine.split_kernel_weights() + p_in_weight, g_in_weight = engine.split_kernel_weights() # each (2 * d_pair, d_pair) def _bf16(t: Tensor) -> Tensor: - return t if t.dtype == torch.bfloat16 else t.to(torch.bfloat16) + return t if t.dtype == torch.bfloat16 else t.to(torch.bfloat16) # t.shape return _fused_trimul_with_residual( # type: ignore[misc] pair, @@ -1109,17 +1125,18 @@ class PairUpdateBlock(nn.Module): g_out_weight=_bf16(engine.proj_gate.weight), mask=pair_attention_mask, eps=_EPS, - ) + ) # pair.shape def forward(self, pair: Tensor, pair_attention_mask: Tensor | None = None) -> Tensor: + # pair: (b, l, l, d_pair); pair_attention_mask: (b, l, l) or None. if self._can_use_fused_trimul_with_residual(pair): - pair = self._fused_trimul_with_residual(pair, "outgoing", pair_attention_mask) - pair = self._fused_trimul_with_residual(pair, "incoming", pair_attention_mask) + pair = self._fused_trimul_with_residual(pair, "outgoing", pair_attention_mask) # (b, l, l, d_pair) + pair = self._fused_trimul_with_residual(pair, "incoming", pair_attention_mask) # (b, l, l, d_pair) else: - pair = self.row_drop(pair, self.tri_mul_out(pair, mask=pair_attention_mask)) - pair = self.row_drop(pair, self.tri_mul_in(pair, mask=pair_attention_mask)) - pair = self.pair_transition(pair) - return pair + pair = self.row_drop(pair, self.tri_mul_out(pair, mask=pair_attention_mask)) # (b, l, l, d_pair) + pair = self.row_drop(pair, self.tri_mul_in(pair, mask=pair_attention_mask)) # (b, l, l, d_pair) + pair = self.pair_transition(pair) # (b, l, l, d_pair) + return pair # (b, l, l, d_pair) class FoldingTrunk(nn.Module): @@ -1145,22 +1162,23 @@ class FoldingTrunk(nn.Module): def forward(self, pair: Tensor, pair_attention_mask: Tensor | None = None) -> Tensor: # Cast the pair tensor to BF16 when the fused triangle backend is enabled # (its bwd kernel requires bf16). Other backends keep the input dtype. + # pair: (b, l, l, d_pair); pair_attention_mask: (b, l, l) or None. orig_dtype = pair.dtype fused_on = ( len(self.blocks) > 0 and getattr(self.blocks[0], "_kernel_backend", None) == BACKEND_FUSED ) if pair.is_cuda and fused_on and orig_dtype != torch.bfloat16: - pair = pair.to(torch.bfloat16) + pair = pair.to(torch.bfloat16) # (b, l, l, d_pair) for block in self.blocks: fn = partial(block, pair_attention_mask=pair_attention_mask) if torch.is_grad_enabled(): - pair = checkpoint(fn, pair, use_reentrant=False) # pyright: ignore + pair = checkpoint(fn, pair, use_reentrant=False) # pyright: ignore; (b, l, l, d_pair) else: - pair = fn(pair) + pair = fn(pair) # (b, l, l, d_pair) if pair.dtype != orig_dtype: - pair = pair.to(orig_dtype) - return pair + pair = pair.to(orig_dtype) # (b, l, l, d_pair) + return pair # (b, l, l, d_pair) # =========================================================================== @@ -1201,28 +1219,29 @@ class OuterProductMean(nn.Module): self._chunk_size = chunk_size def forward(self, m: Tensor, msa_attention_mask: Tensor) -> Tensor: - m_norm = self.norm(m) - x = self.W(m_norm) * msa_attention_mask.unsqueeze(-1).to(m_norm.dtype) - a, b = x.chunk(2, dim=-1) - mask_f = msa_attention_mask.to(a.dtype) - n_valid = (mask_f @ mask_f.transpose(-1, -2)).unsqueeze(-1).clamp(min=1.0) + # m: (b, l, m_depth, d_msa); msa_attention_mask: (b, l, m_depth); d_h = d_hidden. + m_norm = self.norm(m) # (b, l, m_depth, d_msa) + x = self.W(m_norm) * msa_attention_mask.unsqueeze(-1).to(m_norm.dtype) # (b, l, m_depth, 2 * d_h) + a, b = x.chunk(2, dim=-1) # each (batch, l, m_depth, d_h) + mask_f = msa_attention_mask.to(a.dtype) # (batch, l, m_depth) + n_valid = (mask_f @ mask_f.transpose(-1, -2)).unsqueeze(-1).clamp(min=1.0) # (batch, l, l, 1) if self._chunk_size is None: - outer = torch.einsum("bimc,bjmd->bijcd", a, b).flatten(-2) + outer = torch.einsum("bimc,bjmd->bijcd", a, b).flatten(-2) # (batch, l, l, d_h * d_h) if self.divide_outer_before_proj: - return self.Wout(outer / n_valid) - return self.Wout(outer) / n_valid + return self.Wout(outer / n_valid) # (batch, l, l, d_pair) + return self.Wout(outer) / n_valid # (batch, l, l, d_pair) # Chunk along the left (i) axis so the peak einsum intermediate is # X uses shape (b, chunk, l, c, d) instead of (b, l, l, c, d). length = a.shape[1] out_chunks: list[Tensor] = [] for start in range(0, length, self._chunk_size): end = min(start + self._chunk_size, length) - outer_chunk = torch.einsum("bimc,bjmd->bijcd", a[:, start:end], b).flatten(-2) + outer_chunk = torch.einsum("bimc,bjmd->bijcd", a[:, start:end], b).flatten(-2) # (batch, l_c, l, d_h * d_h) if self.divide_outer_before_proj: out_chunks.append(self.Wout(outer_chunk / n_valid[:, start:end])) else: out_chunks.append(self.Wout(outer_chunk) / n_valid[:, start:end]) - return torch.cat(out_chunks, dim=1) + return torch.cat(out_chunks, dim=1) # (batch, l, l, d_pair) class MSAPairWeightedAveraging(nn.Module): @@ -1252,18 +1271,18 @@ class MSAPairWeightedAveraging(nn.Module): batch_size, length, depth, _ = msa_repr.shape n_heads, head_width = self.n_heads, self.head_width - msa_normed = self.norm_single(msa_repr) + msa_normed = self.norm_single(msa_repr) # (b, l, m, d_msa) bias = self.compute_bias(pair_repr) # A has shape (b, l, l, n_heads). - bias.masked_fill_(~pair_attention_mask.unsqueeze(-1).bool(), -1e5) - attn = torch.softmax(bias, dim=-2) # softmax over j + bias.masked_fill_(~pair_attention_mask.unsqueeze(-1).bool(), -1e5) # (b, l, l, n_heads) + attn = torch.softmax(bias, dim=-2) # softmax over j; (b, l, l, n_heads) - v = self.Wv(msa_normed).reshape(batch_size, length, depth, n_heads, head_width) + v = self.Wv(msa_normed).reshape(batch_size, length, depth, n_heads, head_width) # (b, l, m, n_heads, head_width) gate = torch.sigmoid(self.Wgate(msa_normed)).reshape( batch_size, length, depth, n_heads, head_width - ) + ) # (b, l, m, n_heads, head_width) - output = torch.einsum("bijh,bjmhd,bimhd->bimhd", attn, v, gate) - return self.Wout(output.reshape(batch_size, length, depth, n_heads * head_width)) + output = torch.einsum("bijh,bjmhd,bimhd->bimhd", attn, v, gate) # (b, l, m, n_heads, head_width) + return self.Wout(output.reshape(batch_size, length, depth, n_heads * head_width)) # (b, l, m, d_msa) # =========================================================================== @@ -1283,10 +1302,11 @@ class TransitionLayer(nn.Module): self.out_proj = nn.Linear(hidden, d_model, bias=False) def forward(self, x: Tensor) -> Tensor: - x = self.norm(x) - a = self.a_proj(x) - b = self.b_proj(x) - return self.out_proj(F.silu(a) * b) + # x: (..., d_model); d_hidden = n * d_model. + x = self.norm(x) # (..., d_model) + a = self.a_proj(x) # (..., d_hidden) + b = self.b_proj(x) # (..., d_hidden) + return self.out_proj(F.silu(a) * b) # (..., d_model) # =========================================================================== @@ -1302,14 +1322,15 @@ class AdaptiveLayerNorm(nn.Module): self.d_model = d_model self.d_cond = d_cond self.eps = eps - self.s_scale = nn.Parameter(torch.ones(d_cond)) + self.s_scale = nn.Parameter(torch.ones(d_cond)) # (d_cond,) self.s_gate = nn.Linear(d_cond, d_model, bias=True) self.s_shift = nn.Linear(d_cond, d_model, bias=False) def forward(self, a: Tensor, s: Tensor) -> Tensor: - a_norm = F.layer_norm(a, (self.d_model,), None, None, self.eps) - s_norm = F.layer_norm(s, (self.d_cond,), self.s_scale, None, self.eps) - return torch.sigmoid(self.s_gate(s_norm)) * a_norm + self.s_shift(s_norm) + # a: (..., d_model); s: (..., d_cond) with broadcast-compatible leading axes. + a_norm = F.layer_norm(a, (self.d_model,), None, None, self.eps) # a.shape + s_norm = F.layer_norm(s, (self.d_cond,), self.s_scale, None, self.eps) # s.shape + return torch.sigmoid(self.s_gate(s_norm)) * a_norm + self.s_shift(s_norm) # broadcast leading shape + (d_model,) # =========================================================================== @@ -1326,12 +1347,13 @@ class FourierEmbedding(nn.Module): def __init__(self, c: int) -> None: super().__init__() self.c = c - self.register_buffer("w", torch.randn(c)) - self.register_buffer("b", torch.randn(c)) + self.register_buffer("w", torch.randn(c)) # (c,) + self.register_buffer("b", torch.randn(c)) # (c,) def forward(self, t_hat: Tensor) -> Tensor: - t = torch.as_tensor(t_hat, device=self.w.device, dtype=self.w.dtype).reshape(-1) - return torch.cos(2.0 * torch.pi * (t[:, None] * self.w[None, :] + self.b[None, :])) + # t_hat: scalar or arbitrary noise-time tensor; n = t_hat.numel(). + t = torch.as_tensor(t_hat, device=self.w.device, dtype=self.w.dtype).reshape(-1) # (n,) + return torch.cos(2.0 * torch.pi * (t[:, None] * self.w[None, :] + self.b[None, :])) # (n, c) # =========================================================================== @@ -1360,14 +1382,15 @@ class SwiGLU(nn.Module): self.hidden_features = hidden_features def forward(self, x: Tensor) -> Tensor: - x12 = self.w12(x) - x1, x2 = x12.split(self.hidden_features, dim=-1) - hidden = F.silu(x1) + # x: (..., in_features); d_hidden = hidden_features. + x12 = self.w12(x) # (..., 2 * d_hidden) + x1, x2 = x12.split(self.hidden_features, dim=-1) # each (..., d_hidden) + hidden = F.silu(x1) # (..., d_hidden) # Without autograd the product can reuse the activation's buffer. On a pair tensor # that buffer is twice the pair representation. The values are the same either way. - hidden = hidden * x2 if torch.is_grad_enabled() else hidden.mul_(x2) + hidden = hidden * x2 if torch.is_grad_enabled() else hidden.mul_(x2) # (..., d_hidden) del x12, x1, x2 - return self.w3(hidden) + return self.w3(hidden) # (..., out_features) class SwiGLUMLP(SwiGLU): @@ -1386,8 +1409,9 @@ class SwiGLUMLP(SwiGLU): def _rotate_half(x: Tensor) -> Tensor: - x1, x2 = x.chunk(2, dim=-1) - return torch.cat((-x2, x1), dim=-1) + # x: (..., d_rot), with an even final width. + x1, x2 = x.chunk(2, dim=-1) # each (..., d_rot / 2) + return torch.cat((-x2, x1), dim=-1) # x.shape def apply_rotary_emb_3d(x: Tensor, cos: Tensor, sin: Tensor) -> Tensor: @@ -1399,12 +1423,12 @@ def apply_rotary_emb_3d(x: Tensor, cos: Tensor, sin: Tensor) -> Tensor: sin: S with shape (b, l, d / 2). """ ro_dim = cos.shape[-1] * 2 - cos = cos.unsqueeze(2).repeat(1, 1, 1, 2) - sin = sin.unsqueeze(2).repeat(1, 1, 1, 2) + cos = cos.unsqueeze(2).repeat(1, 1, 1, 2) # (b, l, 1, ro_dim) + sin = sin.unsqueeze(2).repeat(1, 1, 1, 2) # (b, l, 1, ro_dim) return torch.cat( [x[..., :ro_dim] * cos + _rotate_half(x[..., :ro_dim]) * sin, x[..., ro_dim:]], dim=-1, - ) + ) # (b, l, h, d) @torch.compiler.disable @@ -1418,6 +1442,7 @@ def build_3d_rope( uid_base_freq: float = 10.0, ) -> tuple[Tensor, Tensor]: """Build cos/sin for 3D RoPE + UID RoPE.""" + # ref_pos: (b, a, 3); ref_space_uid: (b, a); s/u = spatial/UID pair counts. device = ref_pos.device batch_size, n_atoms = ref_pos.shape[:2] half_dim = head_dim // 2 @@ -1429,21 +1454,21 @@ def build_3d_rope( torch.arange(0, n_spatial_per_axis, dtype=torch.float32, device=device) / n_spatial_per_axis ) - ) + ) # (s,) uid_inv_freq = 1.0 / ( uid_base_freq ** (torch.arange(0, n_uid_pairs, dtype=torch.float32, device=device) / n_uid_pairs) - ) + ) # (u,) - pos_f32 = ref_pos.float() - spatial_freqs = torch.einsum("bna,k->bnak", pos_f32, spatial_inv_freq) - spatial_freqs = spatial_freqs.reshape(batch_size, n_atoms, n_spatial_total) + pos_f32 = ref_pos.float() # (b, a, 3) + spatial_freqs = torch.einsum("bna,k->bnak", pos_f32, spatial_inv_freq) # (b, a, 3, s) + spatial_freqs = spatial_freqs.reshape(batch_size, n_atoms, n_spatial_total) # (b, a, 3 * s) - uid_f32 = ref_space_uid.float() - uid_freqs = torch.einsum("bn,k->bnk", uid_f32, uid_inv_freq) + uid_f32 = ref_space_uid.float() # (b, a) + uid_freqs = torch.einsum("bn,k->bnk", uid_f32, uid_inv_freq) # (b, a, u) n_active = n_spatial_total + n_uid_pairs - freqs = torch.cat([spatial_freqs, uid_freqs], dim=-1) + freqs = torch.cat([spatial_freqs, uid_freqs], dim=-1) # (b, a, 3 * s + u) if n_active < half_dim: padding = torch.zeros( @@ -1452,16 +1477,17 @@ def build_3d_rope( half_dim - n_active, device=device, dtype=torch.float32, - ) - freqs = torch.cat([freqs, padding], dim=-1) + ) # (b, a, half_dim - n_active) + freqs = torch.cat([freqs, padding], dim=-1) # (b, a, half_dim) - cos = freqs.cos().to(torch.bfloat16) - sin = freqs.sin().to(torch.bfloat16) - return cos, sin + cos = freqs.cos().to(torch.bfloat16) # freqs.shape + sin = freqs.sin().to(torch.bfloat16) # freqs.shape + return cos, sin # each (b, a, max(3 * s + u, half_dim)) def qk_norm(x: Tensor) -> Tensor: - return F.rms_norm(x, (x.size(-1),)).to(x.dtype) + # x: arbitrary leading dimensions and a final head-width axis. + return F.rms_norm(x, (x.size(-1),)).to(x.dtype) # x.shape # =========================================================================== @@ -1479,9 +1505,10 @@ class SwiGLUFFN(nn.Module): self.w_down = nn.Linear(hidden_size, d_model, bias=False) def forward(self, x: Tensor) -> Tensor: - x = x.to(self.w_up.weight.dtype) - x1, x2 = self.w_up(x).chunk(2, dim=-1) - return self.w_down(F.silu(x1) * x2) + # x: (..., d_model); d_hidden = w_down.in_features. + x = x.to(self.w_up.weight.dtype) # (..., d_model) + x1, x2 = self.w_up(x).chunk(2, dim=-1) # each (..., d_hidden) + return self.w_down(F.silu(x1) * x2) # (..., d_model) # =========================================================================== @@ -1524,7 +1551,7 @@ class SWA3DRoPEAttention(nn.Module): # indices: (t,) flat positions of real atoms; cu_seqlens: (b + 1,) int32 row offsets. indices, cu_seqlens, max_seqlen = attention_params[2:5] flat_shape = (batch_size * n_atoms, self.n_heads, self.head_dim) - q, k, v = q.reshape(flat_shape), k.reshape(flat_shape), v.reshape(flat_shape) + q, k, v = q.reshape(flat_shape), k.reshape(flat_shape), v.reshape(flat_shape) # each (b * n_atoms, h, d_h) has_padding = indices.shape[0] != batch_size * n_atoms if has_padding: q, k, v = q[indices], k[indices], v[indices] # each (t, h, d_h) @@ -1545,46 +1572,47 @@ class SWA3DRoPEAttention(nn.Module): ) # (t, h, d_h) if has_padding: out = attended.new_zeros(flat_shape) # (b * n_atoms, h, d_h) - out[indices] = attended + out[indices] = attended # (t, h, d_h) else: - out = attended - return out.view(batch_size, n_atoms, self.n_heads, self.head_dim) + out = attended # (b * n_atoms, h, d_h) + return out.view(batch_size, n_atoms, self.n_heads, self.head_dim) # (b, n_atoms, h, d_h) def forward(self, x: Tensor, attention_params: tuple) -> Tensor: + # x: (b, a, d_model); h = n_heads, d_h = head_dim; r is rotary-pair count. batch_size, n_atoms = x.shape[:2] - cos, sin = attention_params[0], attention_params[1] + cos, sin = attention_params[0], attention_params[1] # each (b, a, r) - x_input = x - qkv = self.Wqkv(x) - qkv = qkv.view(batch_size, n_atoms, 3, self.n_heads, self.head_dim).permute(2, 0, 1, 3, 4) - q, k, v = qkv.unbind(0) - q, k = qk_norm(q), qk_norm(k) + x_input = x # (b, a, d_model) + qkv = self.Wqkv(x) # (b, a, 3 * d_model) + qkv = qkv.view(batch_size, n_atoms, 3, self.n_heads, self.head_dim).permute(2, 0, 1, 3, 4) # (3, b, a, h, d_h) + q, k, v = qkv.unbind(0) # each (b, a, h, d_h) + q, k = qk_norm(q), qk_norm(k) # each (b, a, h, d_h) - q = apply_rotary_emb_3d(q, cos, sin) - k = apply_rotary_emb_3d(k, cos, sin) + q = apply_rotary_emb_3d(q, cos, sin) # (b, a, h, d_h) + k = apply_rotary_emb_3d(k, cos, sin) # (b, a, h, d_h) input_dtype = q.dtype if q.dtype not in (torch.float16, torch.bfloat16): - q, k, v = q.bfloat16(), k.bfloat16(), v.bfloat16() + q, k, v = q.bfloat16(), k.bfloat16(), v.bfloat16() # each (b, a, h, d_h) # ESMFold2 does not advertise FlashAttention. Keep this atom path on # PyTorch. Models that advertise FlashAttention dispatch through the # precompiled Hugging Face kernels interface in fastplms.attention. if self._atom_attention == ATOM_ATTENTION_WINDOWED: - out = self._windowed_attention(q, k, v, attention_params) + out = self._windowed_attention(q, k, v, attention_params) # (b, a, h, d_h) else: - q_t = q.transpose(1, 2) - k_t = k.transpose(1, 2) - v_t = v.transpose(1, 2) - attn = torch.matmul(q_t, k_t.transpose(-2, -1)) * self.scale - attn = F.softmax(attn, dim=-1) - out = torch.matmul(attn, v_t).transpose(1, 2) + q_t = q.transpose(1, 2) # (b, h, a, d_h) + k_t = k.transpose(1, 2) # (b, h, a, d_h) + v_t = v.transpose(1, 2) # (b, h, a, d_h) + attn = torch.matmul(q_t, k_t.transpose(-2, -1)) * self.scale # (b, h, a, a) + attn = F.softmax(attn, dim=-1) # (b, h, a, a) + out = torch.matmul(attn, v_t).transpose(1, 2) # (b, a, h, d_h) out = out.to(input_dtype).reshape( # type: ignore[union-attr] batch_size, n_atoms, -1 - ) - out = out * torch.sigmoid(self.gate_proj(x_input)) - return self.out_proj(out) + ) # (b, a, d_model) + out = out * torch.sigmoid(self.gate_proj(x_input)) # (b, a, d_model) + return self.out_proj(out) # (b, a, d_model) # =========================================================================== @@ -1593,11 +1621,13 @@ class SWA3DRoPEAttention(nn.Module): def _rms_adaln_raw(x: Tensor, scale: Tensor, shift: Tensor) -> Tensor: - return F.rms_norm(x, (x.shape[-1],)) * (1 + scale) + shift + # x, scale, shift: broadcast-compatible arrays; normalize x final axis. + return F.rms_norm(x, (x.shape[-1],)) * (1 + scale) + shift # broadcast(x.shape, scale.shape, shift.shape) def _gated_residual_raw(x: Tensor, gate: Tensor, y: Tensor) -> Tensor: - return x + gate * y + # x, gate, y: broadcast-compatible arrays. + return x + gate * y # broadcast(x.shape, gate.shape, y.shape) class SWAAtomBlock(nn.Module): @@ -1619,7 +1649,7 @@ class SWAAtomBlock(nn.Module): self.ffn_norm = nn.RMSNorm(d_atom, elementwise_affine=False) adaln_linear = nn.Linear(d_atom, 6 * d_atom, bias=False) - nn.init.zeros_(adaln_linear.weight) + nn.init.zeros_(adaln_linear.weight) # (6 * d_atom, d_atom) self.adaln_modulation = nn.Sequential(nn.SiLU(), adaln_linear) self.attn = SWA3DRoPEAttention(d_atom, n_heads, half_window=half_window) @@ -1631,19 +1661,20 @@ class SWAAtomBlock(nn.Module): ) def forward(self, x: Tensor, c_l: Tensor, attention_params: tuple) -> Tensor: - mod = self.adaln_modulation(c_l) + # x: (b, a, d_atom); c_l: (b, d_atom) or (b, a, d_atom). + mod = self.adaln_modulation(c_l) # c_l.shape[:-1] + (6 * d_atom,) if mod.dim() == 2: - mod = mod.unsqueeze(1) - shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = mod.chunk(6, dim=-1) + mod = mod.unsqueeze(1) # (b, 1, 6 * d_atom) + shift_a, scale_a, gate_a, shift_f, scale_f, gate_f = mod.chunk(6, dim=-1) # each (b, 1 or a, d_atom) - attn_input = self._rms_adaln(x, scale_a, shift_a) - attn_out = self.attn(attn_input, attention_params) - x = self._gated_residual(x, gate_a, attn_out) + attn_input = self._rms_adaln(x, scale_a, shift_a) # (b, a, d_atom) + attn_out = self.attn(attn_input, attention_params) # (b, a, d_atom) + x = self._gated_residual(x, gate_a, attn_out) # (b, a, d_atom) - ffn_input = self._rms_adaln(x, scale_f, shift_f) - ffn_out = self.ffn(ffn_input) - x = self._gated_residual(x, gate_f, ffn_out) - return x + ffn_input = self._rms_adaln(x, scale_f, shift_f) # (b, a, d_atom) + ffn_out = self.ffn(ffn_input) # (b, a, d_atom) + x = self._gated_residual(x, gate_f, ffn_out) # (b, a, d_atom) + return x # (b, a, d_atom) class SWAAtomTransformer(nn.Module): @@ -1699,14 +1730,15 @@ class SWAAtomTransformer(nn.Module): attention_params: tuple, return_intermediates: bool = False, ) -> Tensor | tuple[Tensor, list[Tensor]]: + # q_l/c_l: (b, a, d_atom); each saved intermediate has the same shape. intermediates: list[Tensor] = [] for block in self.blocks: - q_l = block(q_l, c_l, attention_params) + q_l = block(q_l, c_l, attention_params) # (b, a, d_atom) if return_intermediates: intermediates.append(q_l) if return_intermediates: - return q_l, intermediates - return q_l + return q_l, intermediates # q_l: (b, a, d_atom); optional list contains tensors of that shape + return q_l # q_l: (b, a, d_atom); optional list contains tensors of that shape # =========================================================================== @@ -1721,13 +1753,14 @@ def _prepare_atom_encoder_metadata( num_diffusion_samples: int, ) -> tuple[Tensor, Tensor, Tensor, int, int]: """Prepare mask-derived atom metadata outside compiled diffusion graphs.""" - mask_exp = atom_attention_mask.repeat_interleave(num_diffusion_samples, 0) - seqlens = mask_exp.sum(dim=-1, dtype=torch.int32) - indices = torch.nonzero(mask_exp.flatten(), as_tuple=False).flatten() + # atom_attention_mask/atom_to_token: (b, a); bs = b * num_diffusion_samples. + mask_exp = atom_attention_mask.repeat_interleave(num_diffusion_samples, 0) # (bs, a) + seqlens = mask_exp.sum(dim=-1, dtype=torch.int32) # (bs,) + indices = torch.nonzero(mask_exp.flatten(), as_tuple=False).flatten() # (n_present,) max_seqlen = int(seqlens.max().item()) - cu_seqlens = F.pad(torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)) + cu_seqlens = F.pad(torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0)) # (bs + 1,) n_tokens = int(atom_to_token.max().item()) + 1 - return mask_exp, indices, cu_seqlens, max_seqlen, n_tokens + return mask_exp, indices, cu_seqlens, max_seqlen, n_tokens # (bs, a), (n_present,), (bs + 1,), scalar integers class ESMFold2AtomEncoder(nn.Module): @@ -1805,6 +1838,8 @@ class ESMFold2AtomEncoder(nn.Module): ``inference_cache`` caches step-invariant tensors (c_base, 3D RoPE, attention indices, n_tokens) across diffusion steps. """ + # ref_pos: (b, a, 3); ref_element: (b, a, 128); chars: (b, a, 4, 64); other atom inputs: (b, a). + # bs = b * samples; d_out = d_token for structure prediction, otherwise d_token / 2. batch_size, n_atoms = ref_pos.shape[:2] layer_cache = None @@ -1821,46 +1856,46 @@ class ESMFold2AtomEncoder(nn.Module): ref_atom_name_chars.reshape(batch_size, n_atoms, MAX_CHARS * CHAR_VOCAB_SIZE), ], dim=-1, - ) - c_base = self.atom_norm(self.atom_linear(atom_feats)) - cos, sin = self.atom_transformer._build_3d_rope(ref_pos, ref_space_uid) - cos = cos.repeat_interleave(num_diffusion_samples, 0) - sin = sin.repeat_interleave(num_diffusion_samples, 0) + ) # (b, a, d_atom_features) + c_base = self.atom_norm(self.atom_linear(atom_feats)) # (b, a, d_atom) + cos, sin = self.atom_transformer._build_3d_rope(ref_pos, ref_space_uid) # each (b, a, rotary_pairs) + cos = cos.repeat_interleave(num_diffusion_samples, 0) # (bs, a, rotary_pairs) + sin = sin.repeat_interleave(num_diffusion_samples, 0) # (bs, a, rotary_pairs) mask_exp, indices, cu_seqlens, max_seqlen, n_tokens = ( _prepare_atom_encoder_metadata( atom_attention_mask, atom_to_token, num_diffusion_samples, ) - ) + ) # (bs, a), (n_present,), (bs + 1,), integers attention_params = (cos, sin, indices, cu_seqlens, max_seqlen) if layer_cache is not None: - layer_cache["c_base"] = c_base + layer_cache["c_base"] = c_base # (b, a, d_atom) layer_cache["attention_params"] = attention_params - layer_cache["mask_exp"] = mask_exp + layer_cache["mask_exp"] = mask_exp # (bs, a) layer_cache["n_tokens"] = n_tokens layer_cache["atom_to_token_exp"] = atom_to_token.repeat_interleave( num_diffusion_samples, 0 - ) + ) # (bs, a) else: - c_base = layer_cache["c_base"] + c_base = layer_cache["c_base"] # (b, a, d_atom) attention_params = layer_cache["attention_params"] - mask_exp = layer_cache["mask_exp"] + mask_exp = layer_cache["mask_exp"] # (bs, a) n_tokens = layer_cache["n_tokens"] - c = c_base + c = c_base # (b, a, d_atom) - q = c + q = c # (b, a, d_atom) if self.structure_prediction and r_l is not None: - q = q.repeat_interleave(num_diffusion_samples, 0) + q = q.repeat_interleave(num_diffusion_samples, 0) # (bs, a, d_atom) if pred_r1 is None: - pred_r1 = torch.zeros_like(r_l) - r_input = torch.cat([r_l, pred_r1], dim=-1) - r_to_q = self.coords_linear(r_input) - q = q + r_to_q + pred_r1 = torch.zeros_like(r_l) # r_l.shape = (bs, a, 3) + r_input = torch.cat([r_l, pred_r1], dim=-1) # (bs, a, 6) + r_to_q = self.coords_linear(r_input) # (bs, a, d_atom) + q = q + r_to_q # (bs, a, d_atom) - c = c.repeat_interleave(num_diffusion_samples, 0) + c = c.repeat_interleave(num_diffusion_samples, 0) # (bs, a, d_atom) result = self.atom_transformer( q_l=q, @@ -1869,19 +1904,19 @@ class ESMFold2AtomEncoder(nn.Module): return_intermediates=return_intermediates, ) if return_intermediates: - q, intermediates = result + q, intermediates = result # q: (bs, a, d_atom); list of same-shaped tensors else: - q = result + q = result # (bs, a, d_atom) intermediates = [] - q_to_a = F.relu(self.atom_to_token_linear(q)) + q_to_a = F.relu(self.atom_to_token_linear(q)) # (bs, a, d_out) if layer_cache is not None and "atom_to_token_exp" in layer_cache: - atom_to_token_exp = layer_cache["atom_to_token_exp"] + atom_to_token_exp = layer_cache["atom_to_token_exp"] # (bs, a) else: - atom_to_token_exp = atom_to_token.repeat_interleave(num_diffusion_samples, 0) - a = scatter_atom_to_token(q_to_a, atom_to_token_exp, n_tokens, atom_mask=mask_exp.bool()) + atom_to_token_exp = atom_to_token.repeat_interleave(num_diffusion_samples, 0) # (bs, a) + a = scatter_atom_to_token(q_to_a, atom_to_token_exp, n_tokens, atom_mask=mask_exp.bool()) # (bs, n_tokens, d_out) - return a, q, c, attention_params, intermediates + return a, q, c, attention_params, intermediates # a: (bs, l, d_out); q/c: (bs, a, d_atom); metadata tuple and intermediate list # =========================================================================== @@ -1935,10 +1970,11 @@ class ESMFold2AtomDecoder(nn.Module): return_intermediates: bool = False, ) -> tuple[Tensor, list[Tensor]]: """Returns (r_update, intermediates).""" - atom_to_token_exp = atom_to_token.repeat_interleave(num_diffusion_samples, 0) - a_to_q = self.token_to_atom_linear(a_i) - a_to_q = gather_token_to_atom(a_to_q, atom_to_token_exp) - q_l = q_l + a_to_q + # a_i: (bs, l, d_token); q_l/c_l: (bs, a, d_atom); atom_to_token: (b, a); bs = b * samples. + atom_to_token_exp = atom_to_token.repeat_interleave(num_diffusion_samples, 0) # (bs, a) + a_to_q = self.token_to_atom_linear(a_i) # (bs, l, d_atom) + a_to_q = gather_token_to_atom(a_to_q, atom_to_token_exp) # (bs, a, d_atom) + q_l = q_l + a_to_q # (bs, a, d_atom) result = self.atom_transformer( q_l=q_l, @@ -1947,13 +1983,13 @@ class ESMFold2AtomDecoder(nn.Module): return_intermediates=return_intermediates, ) if return_intermediates: - q_l, intermediates = result + q_l, intermediates = result # q_l: (bs, a, d_atom); list of same-shaped tensors else: - q_l = result + q_l = result # (bs, a, d_atom) intermediates = [] - r_l = self.output_linear(self.norm(q_l)) - return r_l, intermediates + r_l = self.output_linear(self.norm(q_l)) # (bs, a, 3) + return r_l, intermediates # r_l: (bs, a, 3); intermediate tensors: (bs, a, d_atom) # =========================================================================== @@ -1983,8 +2019,8 @@ class AttentionPairBias(nn.Module): self.adaln = AdaptiveLayerNorm(d_model, d_cond, eps=1e-5) self.out_gate = nn.Linear(d_cond, d_model, bias=True) # adaln init: weight=0, bias=-2 - nn.init.zeros_(self.out_gate.weight) - nn.init.constant_(self.out_gate.bias, -2.0) + nn.init.zeros_(self.out_gate.weight) # (d_model, d_cond) + nn.init.constant_(self.out_gate.bias, -2.0) # (d_model,) else: self.pre_norm = nn.LayerNorm(d_model, eps=1e-5) @@ -2043,22 +2079,23 @@ class AttentionPairBias(nn.Module): conditions every denoising step on the same ``z``, so the PyTorch path projects the bias on the first step and reuses that tensor afterwards. """ + # a: (bs, l, d_model); s: (bs, l, d_cond) or None; z: (b, l, l, d_pair) or (bs, l, l); bs = b * samples. bsz, n_queries, d_model = a.shape - x = self.adaln(a, s) if s is not None else self.pre_norm(a) + x = self.adaln(a, s) if s is not None else self.pre_norm(a) # (bs, l, d_model) n_keys = x.shape[1] - q = self.q_proj(x).view(bsz, n_queries, self.num_heads, self.head_dim) - kv = self.kv_proj(x) - k, v = kv.chunk(2, dim=-1) - k = k.view(bsz, n_keys, self.num_heads, self.head_dim) - v = v.view(bsz, n_keys, self.num_heads, self.head_dim) + q = self.q_proj(x).view(bsz, n_queries, self.num_heads, self.head_dim) # (bs, l, h, d_h) + kv = self.kv_proj(x) # (bs, l, 2 * d_model) + k, v = kv.chunk(2, dim=-1) # each (bs, l, d_model) + k = k.view(bsz, n_keys, self.num_heads, self.head_dim) # (bs, l, h, d_h) + v = v.view(bsz, n_keys, self.num_heads, self.head_dim) # (bs, l, h, d_h) use_fused_kernel = self._can_use_fused_pair_bias(z, n_queries, beta) use_cueq_kernel = not use_fused_kernel and self._can_use_cueq_pair_bias(z, n_queries, beta) - cached_pair_bias = None + cached_pair_bias = None # (bs, l, l, h) or None if step_cache is not None and not use_fused_kernel and not use_cueq_kernel: - cached_pair_bias = step_cache.get("pair_bias") + cached_pair_bias = step_cache.get("pair_bias") # (bs, l, l, h) or None # Expand z for num_diffusion_samples, unless its projection is already cached. if ( @@ -2073,21 +2110,21 @@ class AttentionPairBias(nn.Module): and attention_mask.shape[0] != bsz and num_diffusion_samples > 1 ): - attention_mask = attention_mask.repeat_interleave(num_diffusion_samples, dim=0) + attention_mask = attention_mask.repeat_interleave(num_diffusion_samples, dim=0) # (bs, l) if use_fused_kernel: kernel_mask = ( attention_mask if attention_mask is not None else torch.ones(bsz, n_queries, device=a.device, dtype=torch.bool) - ) - pair_norm_w = self.pair_norm.weight + ) # (bs, l) + pair_norm_w = self.pair_norm.weight # (d_pair,) pair_norm_b = ( self.pair_norm.bias if self.pair_norm.bias is not None else torch.zeros_like(pair_norm_w) - ) - z_bf = z if z.dtype == torch.bfloat16 else z.to(torch.bfloat16) + ) # (d_pair,) + z_bf = z if z.dtype == torch.bfloat16 else z.to(torch.bfloat16) # (bs, l, l, d_pair) bias = _fused_pair_bias( # type: ignore[misc] z_bf, kernel_mask, @@ -2095,26 +2132,26 @@ class AttentionPairBias(nn.Module): num_heads=self.num_heads, pair_norm_w=pair_norm_w, pair_norm_b=pair_norm_b, - ) # A has shape (b, h, q, k). - q_bhqd = q.transpose(1, 2) - k_bhqd = k.transpose(1, 2) - v_bhqd = v.transpose(1, 2) + ) # bias: (bs, h, l, l) + q_bhqd = q.transpose(1, 2) # (bs, h, l, d_h) + k_bhqd = k.transpose(1, 2) # (bs, h, l, d_h) + v_bhqd = v.transpose(1, 2) # (bs, h, l, d_h) attn_out = F.scaled_dot_product_attention( q_bhqd, k_bhqd, v_bhqd, attn_mask=bias.to(q_bhqd.dtype) - ) - g = torch.sigmoid(self.g_proj(x)).view(bsz, n_queries, self.num_heads, self.head_dim) - ctx = g * attn_out.transpose(1, 2) - out = self.out_proj(ctx.reshape(bsz, n_queries, d_model)) + ) # (bs, h, l, d_h) + g = torch.sigmoid(self.g_proj(x)).view(bsz, n_queries, self.num_heads, self.head_dim) # (bs, l, h, d_h) + ctx = g * attn_out.transpose(1, 2) # (bs, l, h, d_h) + out = self.out_proj(ctx.reshape(bsz, n_queries, d_model)) # (bs, l, d_model) if s is not None: - out = torch.sigmoid(self.out_gate(s)) * out - return out + out = torch.sigmoid(self.out_gate(s)) * out # (bs, l, d_model) + return out # (bs, l, d_model) if use_cueq_kernel: kernel_mask = ( attention_mask if attention_mask is not None else torch.ones(bsz, n_queries, device=a.device, dtype=torch.bool) - ) + ) # (bs, l) out, _ = _cue_attn_pair_bias( # type: ignore[misc] s=x, q=q.transpose(1, 2), @@ -2130,36 +2167,36 @@ class AttentionPairBias(nn.Module): b_ln_z=self.pair_norm.bias, return_z_proj=False, is_cached_z_proj=False, - ) + ) # out: (bs, l, d_model); unused kernel auxiliary else: # Standard attention with pair bias - g = torch.sigmoid(self.g_proj(x)).view(bsz, n_queries, self.num_heads, self.head_dim) + g = torch.sigmoid(self.g_proj(x)).view(bsz, n_queries, self.num_heads, self.head_dim) # (bs, l, h, d_h) - logits = torch.einsum("... i h d, ... j h d -> ... i j h", q, k) * self.scale + logits = torch.einsum("... i h d, ... j h d -> ... i j h", q, k) * self.scale # (bs, l, l, h) if cached_pair_bias is not None: pair_bias = cached_pair_bias # (b * samples, n, n, h) elif z.dim() == 4: pair_bias = self.pair_bias_proj(self.pair_norm(z)) # (b * samples, n, n, h) if step_cache is not None: - step_cache["pair_bias"] = pair_bias + step_cache["pair_bias"] = pair_bias # (bs, l, l, h) else: pair_bias = z.unsqueeze(-1) # (b * samples, n, n, 1), a precomputed bias - logits = logits + pair_bias.to(dtype=logits.dtype) + logits = logits + pair_bias.to(dtype=logits.dtype) # (bs, l, l, h) if attention_mask is not None: min_val = torch.finfo(logits.dtype).min - mask_bias = torch.where(attention_mask.bool()[:, None, :, None], 0.0, min_val) - logits = logits + mask_bias.to(dtype=logits.dtype) + mask_bias = torch.where(attention_mask.bool()[:, None, :, None], 0.0, min_val) # (bs, 1, l, 1) + logits = logits + mask_bias.to(dtype=logits.dtype) # (bs, l, l, h) - attn = torch.softmax(logits, dim=-2).to(dtype=v.dtype) - ctx = torch.einsum("... i j h, ... j h d -> ... i h d", attn, v) - ctx = g * ctx - out = self.out_proj(ctx.reshape(bsz, n_queries, d_model)) + attn = torch.softmax(logits, dim=-2).to(dtype=v.dtype) # (bs, l, l, h) + ctx = torch.einsum("... i j h, ... j h d -> ... i h d", attn, v) # (bs, l, h, d_h) + ctx = g * ctx # (bs, l, h, d_h) + out = self.out_proj(ctx.reshape(bsz, n_queries, d_model)) # (bs, l, d_model) if s is not None: - out = torch.sigmoid(self.out_gate(s)) * out - return out + out = torch.sigmoid(self.out_gate(s)) * out # (bs, l, d_model) + return out # (bs, l, d_model) # =========================================================================== @@ -2184,8 +2221,8 @@ class ConditionedTransitionBlock(nn.Module): if use_conditioning: self.adaln = AdaptiveLayerNorm(d_model, d_cond, eps=1e-5) self.output_gate = nn.Linear(d_cond, d_model, bias=True) - nn.init.zeros_(self.output_gate.weight) - nn.init.constant_(self.output_gate.bias, -2.0) + nn.init.zeros_(self.output_gate.weight) # (d_model, d_cond) + nn.init.constant_(self.output_gate.bias, -2.0) # (d_model,) else: self.pre_norm = nn.LayerNorm(d_model, eps=1e-5) @@ -2193,15 +2230,16 @@ class ConditionedTransitionBlock(nn.Module): self.lin_out = nn.Linear(hidden, d_model, bias=False) def forward(self, a: Tensor, s: Tensor | None) -> Tensor: - x = self.adaln(a, s) if s is not None else self.pre_norm(a) + # a: (..., d_model); s: (..., d_cond) or None; d_hidden = lin_out.in_features. + x = self.adaln(a, s) if s is not None else self.pre_norm(a) # (..., d_model) - swish_a, swish_b = self.lin_swish(x).chunk(2, dim=-1) - b = F.silu(swish_a) * swish_b - out = self.lin_out(b) + swish_a, swish_b = self.lin_swish(x).chunk(2, dim=-1) # each (..., d_hidden) + b = F.silu(swish_a) * swish_b # (..., d_hidden) + out = self.lin_out(b) # (..., d_model) if s is not None: - out = torch.sigmoid(self.output_gate(s)) * out - return out + out = torch.sigmoid(self.output_gate(s)) * out # (..., d_model) + return out # (..., d_model) # =========================================================================== @@ -2269,11 +2307,12 @@ class DiffusionTransformer(nn.Module): ``inference_cache`` must span only calls that share ``z``, as one ``sample`` call does; each block then keeps its pair bias across steps. """ + # a: (bs, l, d_model); s: (bs, l, d_cond) or None; z follows AttentionPairBias contract. intermediates: list[Tensor] = [] block_caches: dict[int, dict[str, Tensor]] | None = None if inference_cache is not None: block_caches = inference_cache.setdefault("token_pair_bias", {}) - x = a + x = a # (bs, l, d_model) for block_index, (attn, transition) in enumerate( zip(self.attn_blocks, self.transition_blocks, strict=True) ): @@ -2286,11 +2325,11 @@ class DiffusionTransformer(nn.Module): attention_mask=attention_mask, num_diffusion_samples=num_diffusion_samples, step_cache=step_cache, - ) - x = x + transition(x, s) + ) # (bs, l, d_model) + x = x + transition(x, s) # (bs, l, d_model) if return_intermediates: intermediates.append(x) - return x, intermediates + return x, intermediates # x: (bs, l, d_model); list of same-shaped intermediate tensors # =========================================================================== @@ -2343,45 +2382,46 @@ class DiffusionConditioning(nn.Module): num_diffusion_samples: int = 1, inference_cache: dict[str, Tensor] | None = None, ) -> tuple[Tensor, Tensor]: + # z_trunk/relative_position_encoding: (b, l, l, c_z); s_inputs: (b or bs, l, c_s_inputs); bs = b * samples. sigma = self.sigma_data if sigma_data is None else float(sigma_data) base_batch = z_trunk.shape[0] target_batch = base_batch * num_diffusion_samples # z conditioning (cached across diffusion steps: independent of t_hat) if inference_cache is not None and "z" in inference_cache: - z = inference_cache["z"] + z = inference_cache["z"] # (b, l, l, c_z) else: - z_rel = relative_position_encoding.to(dtype=torch.float32) - z = torch.cat([z_trunk.to(dtype=torch.float32), z_rel], dim=-1) - z = self.z_proj(self.z_input_norm(z)) + z_rel = relative_position_encoding.to(dtype=torch.float32) # (b, l, l, c_z) + z = torch.cat([z_trunk.to(dtype=torch.float32), z_rel], dim=-1) # (b, l, l, 2 * c_z) + z = self.z_proj(self.z_input_norm(z)) # (b, l, l, c_z) with torch.autocast(device_type="cuda", dtype=torch.bfloat16): for block in self.z_transitions: - z = z + block(z) + z = z + block(z) # (b, l, l, c_z) if inference_cache is not None: - inference_cache["z"] = z + inference_cache["z"] = z # (b, l, l, c_z) # s conditioning - s_inputs_eff = s_inputs + s_inputs_eff = s_inputs # (b or bs, l, c_s_inputs) if s_inputs_eff.shape[0] != target_batch: - s_inputs_eff = s_inputs_eff.repeat_interleave(num_diffusion_samples, 0) + s_inputs_eff = s_inputs_eff.repeat_interleave(num_diffusion_samples, 0) # (bs, l, c_s_inputs) - s = self.s_proj(self.s_input_norm(s_inputs_eff.to(dtype=torch.float32))) + s = self.s_proj(self.s_input_norm(s_inputs_eff.to(dtype=torch.float32))) # (bs, l, c_s) # Noise embedding - t = torch.as_tensor(t_hat, dtype=torch.float32, device=s.device).reshape(-1) + t = torch.as_tensor(t_hat, dtype=torch.float32, device=s.device).reshape(-1) # (t_hat.numel(),) if t.numel() == 1: - t = t.expand(target_batch) + t = t.expand(target_batch) # (bs,) elif t.shape[0] != target_batch: - t = t.repeat_interleave(num_diffusion_samples, 0) - t_noise = 0.25 * torch.log((t / sigma).clamp(min=1e-20)) - n = self.fourier(t_noise) - n = self.noise_proj(self.noise_norm(n)) - s = s + n.unsqueeze(1) + t = t.repeat_interleave(num_diffusion_samples, 0) # (bs,) + t_noise = 0.25 * torch.log((t / sigma).clamp(min=1e-20)) # (bs,) + n = self.fourier(t_noise) # (bs, fourier_dim) + n = self.noise_proj(self.noise_norm(n)) # (bs, c_s) + s = s + n.unsqueeze(1) # (bs, l, c_s) for block in self.s_transitions: - s = s + block(s) + s = s + block(s) # (bs, l, c_s) - return s, z + return s, z # s: (bs, l, c_s); z: (b, l, l, c_z) # =========================================================================== @@ -2453,7 +2493,7 @@ class DiffusionModule(nn.Module): ) self.s_to_token = nn.Linear(c_token, c_token, bias=False) - nn.init.zeros_(self.s_to_token.weight) + nn.init.zeros_(self.s_to_token.weight) # (c_token, c_token) # Token transformer (DiffusionTransformer with pair bias) self.token_transformer = DiffusionTransformer( @@ -2499,11 +2539,12 @@ class DiffusionModule(nn.Module): return_atom_repr: bool = False, inference_cache: dict[str, Tensor] | None = None, ) -> dict[str, Tensor | None]: + # x_noisy: (bs, a, 3); ref/ID tensors retain base batch b; bs = b * samples; l tokens. bsz = x_noisy.shape[0] sigma = self.sigma_data if sigma_data is None else float(sigma_data) - t = torch.as_tensor(t_hat, dtype=torch.float32, device=x_noisy.device).reshape(-1) + t = torch.as_tensor(t_hat, dtype=torch.float32, device=x_noisy.device).reshape(-1) # (t_hat.numel(),) if t.numel() == 1: - t = t.expand(bsz) + t = t.expand(bsz) # (bs,) # Step 1: conditioning (pair z is cached across diffusion steps) s, z = self.conditioning( @@ -2515,11 +2556,11 @@ class DiffusionModule(nn.Module): sigma_data=sigma, num_diffusion_samples=num_diffusion_samples, inference_cache=inference_cache, - ) + ) # (bs, l, c_token), (b, l, l, c_z) # Step 2: normalize noisy coords - denom = torch.sqrt(t * t + sigma * sigma) - r_noisy = x_noisy / denom[:, None, None] + denom = torch.sqrt(t * t + sigma * sigma) # (bs,) + r_noisy = x_noisy / denom[:, None, None] # (bs, a, 3) # Step 3: atom encoder a, q_skip, c_skip, p_skip, enc_intermediates = self.atom_encoder( @@ -2535,10 +2576,10 @@ class DiffusionModule(nn.Module): num_diffusion_samples=num_diffusion_samples, return_intermediates=return_atom_repr, inference_cache=inference_cache, - ) + ) # a: (bs, l, c_token); q/c: (bs, a, c_atom); metadata and atom intermediate list # Step 4: add conditioned s - a = a + self.s_to_token(self.s_step_norm(s)) + a = a + self.s_to_token(self.s_step_norm(s)) # (bs, l, c_token) # Step 5: token transformer a, _ = self.token_transformer( @@ -2549,10 +2590,10 @@ class DiffusionModule(nn.Module): attention_mask=token_attention_mask, num_diffusion_samples=num_diffusion_samples, inference_cache=inference_cache, - ) + ) # a: (bs, l, c_token); unused intermediate list # Step 6: token norm - a = self.token_norm(a) + a = self.token_norm(a) # (bs, l, c_token) # Step 7: atom decoder r_update, dec_intermediates = self.atom_decoder( @@ -2564,26 +2605,26 @@ class DiffusionModule(nn.Module): atom_attention_mask=ref_mask, num_diffusion_samples=num_diffusion_samples, return_intermediates=return_atom_repr, - ) + ) # r_update: (bs, a, 3); atom intermediate list # Step 8: compute denoised output sigma2 = sigma * sigma - t2 = t * t - out = (sigma2 / (sigma2 + t2))[:, None, None] * x_noisy - out = out + ((sigma * t) / torch.sqrt(sigma2 + t2))[:, None, None] * r_update + t2 = t * t # (bs,) + out = (sigma2 / (sigma2 + t2))[:, None, None] * x_noisy # (bs, a, 3) + out = out + ((sigma * t) / torch.sqrt(sigma2 + t2))[:, None, None] * r_update # (bs, a, 3) # Collect atom intermediates from encoder + decoder - atom_intermediates: Tensor | None = None + atom_intermediates: Tensor | None = None # (bs, a, n_atom_blocks, c_atom) or None if return_atom_repr: all_ints = enc_intermediates + dec_intermediates if all_ints: - atom_intermediates = torch.stack(all_ints, dim=2) + atom_intermediates = torch.stack(all_ints, dim=2) # (bs, a, n_atom_blocks, c_atom) or None return { "x_denoised": out, "token_repr": a if return_token_repr else None, "atom_intermediates": atom_intermediates, - } + } # mapping: x_denoised (bs, a, 3); token_repr (bs, l, c_token) or None; atom intermediates as above # =========================================================================== @@ -2647,24 +2688,24 @@ class DiffusionStructureHead(nn.Module): [self.inference_s_max * self.sigma_data, 0.0], device=device, dtype=torch.float32, - ) + ) # (steps + 1,) p = float(self.inference_p) inv_p = 1.0 / p - k = torch.arange(steps, device=device, dtype=torch.float32) + k = torch.arange(steps, device=device, dtype=torch.float32) # (steps,) base = self.inference_s_max**inv_p + (k / (steps - 1)) * ( self.inference_s_min**inv_p - self.inference_s_max**inv_p - ) - schedule = self.sigma_data * base.pow(p) - return F.pad(schedule, (0, 1), value=0.0) + ) # (steps,) + schedule = self.sigma_data * base.pow(p) # (steps,) + return F.pad(schedule, (0, 1), value=0.0) # (steps + 1,) @staticmethod def _random_rotations(n: int, dtype: torch.dtype, device: torch.device) -> Tensor: - q = torch.randn((n, 4), dtype=dtype, device=device) - scale = torch.sqrt((q * q).sum(dim=1)) - signs = torch.where(q[:, 0] < 0, -scale, scale) - q = q / signs[:, None] - r, i, j, k = torch.unbind(q, dim=-1) - two_s = 2.0 / (q * q).sum(dim=-1) + q = torch.randn((n, 4), dtype=dtype, device=device) # (n, 4) + scale = torch.sqrt((q * q).sum(dim=1)) # (n,) + signs = torch.where(q[:, 0] < 0, -scale, scale) # (n,) + q = q / signs[:, None] # (n, 4) + r, i, j, k = torch.unbind(q, dim=-1) # each (n,) + two_s = 2.0 / (q * q).sum(dim=-1) # (n,) return torch.stack( ( 1 - two_s * (j * j + k * k), @@ -2678,51 +2719,53 @@ class DiffusionStructureHead(nn.Module): 1 - two_s * (i * i + j * j), ), dim=-1, - ).reshape(n, 3, 3) + ).reshape(n, 3, 3) # (n, 3, 3) def _center_random_augmentation( self, x: Tensor, atom_mask: Tensor, second_coords: Tensor | None = None ) -> tuple[Tensor, Tensor | None]: """Algorithm 19: center + random rotation + translation.""" + # x/second_coords: (b, a, 3); atom_mask: (b, a). bsz = x.shape[0] mask = atom_mask.unsqueeze(-1) # M has shape (b, a, 1). - denom = mask.sum(dim=1, keepdim=True).clamp(min=1) - mean = (x * mask).sum(dim=1, keepdim=True) / denom - x = x - mean + denom = mask.sum(dim=1, keepdim=True).clamp(min=1) # (b, 1, 1) + mean = (x * mask).sum(dim=1, keepdim=True) / denom # (b, 1, 3) + x = x - mean # (b, a, 3) if second_coords is not None: - second_coords = second_coords - mean + second_coords = second_coords - mean # (b, a, 3) - r = self._random_rotations(bsz, x.dtype, x.device) - x = torch.einsum("bmd,bds->bms", x, r) + r = self._random_rotations(bsz, x.dtype, x.device) # (b, 3, 3) + x = torch.einsum("bmd,bds->bms", x, r) # (b, a, 3) if second_coords is not None: - second_coords = torch.einsum("bmd,bds->bms", second_coords, r) + second_coords = torch.einsum("bmd,bds->bms", second_coords, r) # (b, a, 3) - t = torch.randn_like(x[:, 0:1, :]) - x = x + t + t = torch.randn_like(x[:, 0:1, :]) # (b, 1, 3) + x = x + t # (b, a, 3) if second_coords is not None: - second_coords = second_coords + t - return x, second_coords + second_coords = second_coords + t # (b, a, 3) + return x, second_coords # each (b, a, 3), or second_coords None @staticmethod def _weighted_rigid_align(x: Tensor, x_gt: Tensor, w: Tensor, mask: Tensor) -> Tensor: """Kabsch alignment: align x to x_gt with weights w.""" + # x/x_gt: (b, n, 3); w/mask: (b, n). w = (mask * w).unsqueeze(-1) # W has shape (b, n, 1). - denom = w.sum(dim=-2, keepdim=True).clamp(min=1e-8) - mu = (x * w).sum(dim=-2, keepdim=True) / denom - mu_gt = (x_gt * w).sum(dim=-2, keepdim=True) / denom - x_c = x - mu - xgt_c = x_gt - mu_gt - covariance = torch.einsum("bni,bnj->bij", w * xgt_c, x_c) - covariance_f32 = covariance.float() + denom = w.sum(dim=-2, keepdim=True).clamp(min=1e-8) # (b, 1, 1) + mu = (x * w).sum(dim=-2, keepdim=True) / denom # (b, 1, 3) + mu_gt = (x_gt * w).sum(dim=-2, keepdim=True) / denom # (b, 1, 3) + x_c = x - mu # (b, n, 3) + xgt_c = x_gt - mu_gt # (b, n, 3) + covariance = torch.einsum("bni,bnj->bij", w * xgt_c, x_c) # (b, 3, 3) + covariance_f32 = covariance.float() # (b, 3, 3) u, _, vh = torch.linalg.svd( covariance_f32, driver="gesvd" if covariance_f32.is_cuda else None - ) - det = torch.linalg.det(u @ vh) - ones = torch.ones_like(det) + ) # (b, 3, 3), (b, 3), (b, 3, 3) + det = torch.linalg.det(u @ vh) # (b,) + ones = torch.ones_like(det) # (b,) rotation = (u @ torch.diag_embed(torch.stack([ones, ones, det], dim=-1)) @ vh).to( covariance.dtype - ) - return x_c @ rotation.transpose(-1, -2) + mu_gt + ) # (b, 3, 3) + return x_c @ rotation.transpose(-1, -2) + mu_gt # (b, n, 3) # ------------------------------------------------------------------ # Sampling @@ -2766,6 +2809,7 @@ class DiffusionStructureHead(nn.Module): so we inflate the underlying schedule length here to land back at the requested step count post-truncation. """ + # z_trunk: (b, l, l, c_z); s_inputs: (b, l, c_s_inputs); atom features: b by a; bs = b * samples. n_atoms = tok_idx.shape[1] device = s_inputs.device target_batch = s_inputs.shape[0] * num_diffusion_samples @@ -2774,26 +2818,26 @@ class DiffusionStructureHead(nn.Module): steps = self.inference_num_steps if num_sampling_steps is None else int(num_sampling_steps) - schedule = self.inference_noise_schedule(steps, device) + schedule = self.inference_noise_schedule(steps, device) # (steps + 1,) if max_inference_sigma is not None: - schedule = schedule[schedule <= float(max_inference_sigma)] - schedule = F.pad(schedule, (1, 0), value=float(max_inference_sigma)) + schedule = schedule[schedule <= float(max_inference_sigma)] # (n_below_cap,) + schedule = F.pad(schedule, (1, 0), value=float(max_inference_sigma)) # (n_below_cap + 1,) lam = self.noise_scale if noise_scale is None else float(noise_scale) eta = self.step_scale if step_scale is None else float(step_scale) - x = schedule[0] * torch.randn(target_batch, n_atoms, 3, device=device, dtype=torch.float32) - atom_mask = ref_mask.repeat_interleave(num_diffusion_samples, 0).float() + x = schedule[0] * torch.randn(target_batch, n_atoms, 3, device=device, dtype=torch.float32) # (bs, a, 3) + atom_mask = ref_mask.repeat_interleave(num_diffusion_samples, 0).float() # (bs, a) gammas = torch.where( schedule > self.gamma_min, torch.full_like(schedule, self.gamma_0), torch.zeros_like(schedule), - ) + ) # schedule.shape - x_denoised_prev: Tensor | None = None - token_repr: Tensor | None = None - diff_atom_intermediates: Tensor | None = None + x_denoised_prev: Tensor | None = None # (bs, a, 3) or None + token_repr: Tensor | None = None # (bs, l, c_token) or None + diff_atom_intermediates: Tensor | None = None # (bs, a, n_blocks, c_atom) or None step_pairs = list(zip(schedule[:-1], schedule[1:], gammas[1:], strict=True)) num_steps = len(step_pairs) @@ -2810,12 +2854,12 @@ class DiffusionStructureHead(nn.Module): for step_idx, (sigma_tm, sigma_t, gamma) in enumerate(step_iterator): x, x_denoised_prev = self._center_random_augmentation( x, atom_mask, second_coords=x_denoised_prev - ) + ) # each (bs, a, 3), second may be None sigma_tm_val = float(sigma_tm.item()) t_hat_val = sigma_tm_val * (1.0 + float(gamma.item())) eps_std = lam * max(t_hat_val**2 - sigma_tm_val**2, 0.0) ** 0.5 - x_noisy = x + eps_std * torch.randn_like(x) + x_noisy = x + eps_std * torch.randn_like(x) # (bs, a, 3) is_last_step = step_idx == num_steps - 1 request_atom_repr = return_atom_repr and ( @@ -2846,24 +2890,24 @@ class DiffusionStructureHead(nn.Module): return_token_repr=True, return_atom_repr=request_atom_repr, inference_cache=inference_cache, - ) + ) # tensor mapping follows the called head's shape contract - x_denoised = dm_out["x_denoised"] - token_repr = dm_out["token_repr"] + x_denoised = dm_out["x_denoised"] # (bs, a, 3) + token_repr = dm_out["token_repr"] # (bs, l, c_token) or None if request_atom_repr: - diff_atom_intermediates = dm_out.get("atom_intermediates") + diff_atom_intermediates = dm_out.get("atom_intermediates") # (bs, a, n_blocks, c_atom) or None # Reverse diffusion alignment (Kabsch) with torch.autocast(device_type="cuda", enabled=False): x_noisy = self._weighted_rigid_align( x_noisy.float(), x_denoised.float(), atom_mask, atom_mask - ) - x_noisy = x_noisy.to(dtype=x_denoised.dtype) + ) # (bs, a, 3) + x_noisy = x_noisy.to(dtype=x_denoised.dtype) # (bs, a, 3) # ODE/SDE step sigma_t_val = float(sigma_t.item()) - denoised_over_sigma = (x_noisy - x_denoised) / t_hat_val - x = x_noisy + eta * (sigma_t_val - t_hat_val) * denoised_over_sigma + denoised_over_sigma = (x_noisy - x_denoised) / t_hat_val # (bs, a, 3) + x = x_noisy + eta * (sigma_t_val - t_hat_val) * denoised_over_sigma # (bs, a, 3) # Denoising early-exit: stop when consecutive predictions converge if ( @@ -2877,17 +2921,17 @@ class DiffusionStructureHead(nn.Module): x_denoised.float(), atom_mask, atom_mask, - ) - diff = (x_denoised.float() - aligned) * atom_mask.unsqueeze(-1) + ) # (bs, a, 3) + diff = (x_denoised.float() - aligned) * atom_mask.unsqueeze(-1) # (bs, a, 3) per_sample_rmsd = ( diff.pow(2).sum(dim=(-1, -2)) / atom_mask.sum(dim=-1).clamp(min=1) - ).sqrt() + ).sqrt() # (bs,) if per_sample_rmsd.max().item() < denoising_early_exit_rmsd: - x = x_denoised - x_denoised_prev = x_denoised + x = x_denoised # (bs, a, 3) + x_denoised_prev = x_denoised # (bs, a, 3) or None break - x_denoised_prev = x_denoised + x_denoised_prev = x_denoised # (bs, a, 3) or None result: dict[str, Tensor | None] = { "sample_atom_coords": x, @@ -2895,4 +2939,4 @@ class DiffusionStructureHead(nn.Module): } if return_atom_repr: result["diff_atom_intermediates"] = diff_atom_intermediates - return result + return result # coordinate/token/optional atom-intermediate mapping with the shapes above diff --git a/fastplms/models/esmfold2/modeling_esmfold2_experimental.py b/fastplms/models/esmfold2/modeling_esmfold2_experimental.py index e7b1942ee235cb8dd83a770057ed871c71a1c3a0..7ecacf38b1ff30f21d94fbcf58b2f5be0de01c86 100644 --- a/fastplms/models/esmfold2/modeling_esmfold2_experimental.py +++ b/fastplms/models/esmfold2/modeling_esmfold2_experimental.py @@ -9,13 +9,13 @@ re-injection and a different confidence/MSA stack. from __future__ import annotations import gc -from collections.abc import Mapping -from pathlib import Path -from typing import Any, ClassVar, cast - import torch import torch.nn as nn import torch.nn.functional as F + +from collections.abc import Mapping +from pathlib import Path +from typing import Any, ClassVar, cast from torch import Tensor from tqdm.auto import tqdm from transformers.modeling_utils import PreTrainedModel @@ -66,6 +66,7 @@ from .modeling_esmfold2_common import ( validate_prepared_auxiliary_inputs, ) + _EPS = 1e-5 _NONPOLYMER_ID = 3 @@ -82,8 +83,8 @@ class ConfidenceHead(nn.Module): d_pair = config.d_pair d_inputs = config.inputs.d_inputs - boundaries = torch.linspace(ch.min_dist, ch.max_dist, ch.distogram_bins - 1) - self.register_buffer("boundaries", boundaries) + boundaries = torch.linspace(ch.min_dist, ch.max_dist, ch.distogram_bins - 1) # (distogram_bins - 1,) + self.register_buffer("boundaries", boundaries) # (distogram_bins - 1,) self.dist_bin_pairwise_embed = nn.Embedding(ch.distogram_bins, d_pair) self.s_norm = nn.LayerNorm(d_single) @@ -105,7 +106,7 @@ class ConfidenceHead(nn.Module): max_atoms_per_token = 23 self.plddt_weight = nn.Parameter( torch.zeros(max_atoms_per_token, d_single, ch.num_plddt_bins) - ) + ) # (23, d_single, n_plddt_bins) self.pae_head = nn.Linear(d_pair, ch.num_pae_bins, bias=False) def set_kernel_backend(self, backend: str | None) -> None: @@ -117,16 +118,18 @@ class ConfidenceHead(nn.Module): @staticmethod def _repeat_batch(x: Tensor, num_diffusion_samples: int) -> Tensor: + # x: (b, ...); output repeats the batch axis by samples. if num_diffusion_samples == 1: - return x - return x.repeat_interleave(num_diffusion_samples, 0) + return x # (b * samples, ...), including samples = 1 + return x.repeat_interleave(num_diffusion_samples, 0) # (b * samples, ...), including samples = 1 @staticmethod def _flatten_sample_axis(x: Tensor) -> Tensor: + # x: (b, samples, n, c) or an already flattened tensor. if x.ndim == 4: b, mult, n, c = x.shape - return x.reshape(b * mult, n, c) - return x + return x.reshape(b * mult, n, c) # (b * samples, n, c) for 4D input; otherwise x.shape + return x # (b * samples, n, c) for 4D input; otherwise x.shape def forward( self, @@ -143,109 +146,110 @@ class ConfidenceHead(nn.Module): relative_position_encoding: Tensor | None = None, token_bonds_encoding: Tensor | None = None, ) -> dict[str, Tensor]: - s_inputs_normed = self.s_inputs_norm(s_inputs) - z_base = self.z_norm(z) + # s_inputs: (b, l, d_inputs); z: (b, l, l, d_pair); x_pred: (bs, a, 3) or (b, samples, a, 3). bs = b * samples. + s_inputs_normed = self.s_inputs_norm(s_inputs) # (b, l, d_inputs) + z_base = self.z_norm(z) # (b, l, l, d_pair) if relative_position_encoding is not None: - z_base = z_base + relative_position_encoding + z_base = z_base + relative_position_encoding # (b, l, l, d_pair) if token_bonds_encoding is not None: - z_base = z_base + token_bonds_encoding - z_base = z_base + self.s_to_z(s_inputs_normed).unsqueeze(2) - z_base = z_base + self.s_to_z_transpose(s_inputs_normed).unsqueeze(1) + z_base = z_base + token_bonds_encoding # (b, l, l, d_pair) + z_base = z_base + self.s_to_z(s_inputs_normed).unsqueeze(2) # (b, l, l, d_pair) + z_base = z_base + self.s_to_z_transpose(s_inputs_normed).unsqueeze(1) # (b, l, l, d_pair) z_base = z_base + self.s_to_z_prod_out( self.s_to_z_prod_in1(s_inputs_normed)[:, :, None, :] * self.s_to_z_prod_in2(s_inputs_normed)[:, None, :, :] - ) - - pair = self._repeat_batch(z_base, num_diffusion_samples) - x_pred_flat = self._flatten_sample_axis(x_pred) - atom_to_token_m = self._repeat_batch(atom_to_token, num_diffusion_samples) - atom_mask_m = self._repeat_batch(atom_attention_mask, num_diffusion_samples) - rep_idx_m = self._repeat_batch(distogram_atom_idx, num_diffusion_samples).long() - mask = self._repeat_batch(token_attention_mask, num_diffusion_samples) + ) # (b, l, l, d_pair) + + pair = self._repeat_batch(z_base, num_diffusion_samples) # (bs, l, l, d_pair) + x_pred_flat = self._flatten_sample_axis(x_pred) # (bs, a, 3) + atom_to_token_m = self._repeat_batch(atom_to_token, num_diffusion_samples) # (bs, a) + atom_mask_m = self._repeat_batch(atom_attention_mask, num_diffusion_samples) # (bs, a) + rep_idx_m = self._repeat_batch(distogram_atom_idx, num_diffusion_samples).long() # (bs, l) + mask = self._repeat_batch(token_attention_mask, num_diffusion_samples) # (bs, l) batch_mult = pair.shape[0] - rep_coords = gather_rep_atom_coords(x_pred_flat, rep_idx_m) + rep_coords = gather_rep_atom_coords(x_pred_flat, rep_idx_m) # (bs, l, 3) rep_distances = torch.cdist( rep_coords, rep_coords, compute_mode="donot_use_mm_for_euclid_dist" - ) - distogram_bins = (rep_distances.unsqueeze(-1) > self.boundaries).sum(dim=-1).long() - pair = pair + self.dist_bin_pairwise_embed(distogram_bins) - - pair_mask = mask[:, :, None].float() * mask[:, None, :].float() - pair = pair + self.folding_trunk(pair, pair_attention_mask=pair_mask) - single = self.row_attention_pooling(pair, mask) - - atom_mask_f = atom_mask_m.float() - s_at_atoms = gather_token_to_atom(single, atom_to_token_m) - s_at_atoms = self.plddt_ln(s_at_atoms) - intra_idx = _compute_intra_token_idx(atom_to_token_m) - intra_idx = intra_idx.clamp(max=self.plddt_weight.shape[0] - 1) - plddt_weight = self.plddt_weight[intra_idx] - plddt_logits = torch.einsum("...c,...cb->...b", s_at_atoms, plddt_weight) - plddt_per_atom = _categorical_mean(plddt_logits, start=0.0, end=1.0) + ) # (bs, l, l) + distogram_bins = (rep_distances.unsqueeze(-1) > self.boundaries).sum(dim=-1).long() # (bs, l, l) + pair = pair + self.dist_bin_pairwise_embed(distogram_bins) # (bs, l, l, d_pair) + + pair_mask = mask[:, :, None].float() * mask[:, None, :].float() # (bs, l, l) + pair = pair + self.folding_trunk(pair, pair_attention_mask=pair_mask) # (bs, l, l, d_pair) + single = self.row_attention_pooling(pair, mask) # (bs, l, d_single) + + atom_mask_f = atom_mask_m.float() # (bs, a) + s_at_atoms = gather_token_to_atom(single, atom_to_token_m) # (bs, a, d_single) + s_at_atoms = self.plddt_ln(s_at_atoms) # (bs, a, d_single) + intra_idx = _compute_intra_token_idx(atom_to_token_m) # (bs, a) + intra_idx = intra_idx.clamp(max=self.plddt_weight.shape[0] - 1) # (bs, a) + plddt_weight = self.plddt_weight[intra_idx] # (bs, a, d_single, n_plddt_bins) + plddt_logits = torch.einsum("...c,...cb->...b", s_at_atoms, plddt_weight) # (bs, a, n_plddt_bins) + plddt_per_atom = _categorical_mean(plddt_logits, start=0.0, end=1.0) # (bs, a) length = single.shape[1] plddt_sum = torch.zeros( batch_mult, length, device=single.device, dtype=plddt_per_atom.dtype - ) + ) # (bs, l) atom_count = torch.zeros( batch_mult, length, device=single.device, dtype=plddt_per_atom.dtype - ) - atom_mask_t = atom_mask_f.to(plddt_per_atom.dtype) - plddt_sum.scatter_add_(1, atom_to_token_m, plddt_per_atom * atom_mask_t) - atom_count.scatter_add_(1, atom_to_token_m, atom_mask_t) - plddt = plddt_sum / atom_count.clamp(min=1e-6) + ) # (bs, l) + atom_mask_t = atom_mask_f.to(plddt_per_atom.dtype) # (bs, a) + plddt_sum.scatter_add_(1, atom_to_token_m, plddt_per_atom * atom_mask_t) # (bs, l) + atom_count.scatter_add_(1, atom_to_token_m, atom_mask_t) # (bs, l) + plddt = plddt_sum / atom_count.clamp(min=1e-6) # (bs, l) complex_plddt = (plddt_per_atom * atom_mask_f).sum(dim=-1) / ( atom_mask_f.sum(dim=-1) + _EPS - ) + ) # (bs,) - expanded_type = self._repeat_batch(mol_type, num_diffusion_samples) - expanded_asym = self._repeat_batch(asym_id, num_diffusion_samples) - is_ligand = (expanded_type == _NONPOLYMER_ID).float() - inter_chain = (expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2)).float() - near_contact = (rep_distances < 8).float() + expanded_type = self._repeat_batch(mol_type, num_diffusion_samples) # (bs, l) + expanded_asym = self._repeat_batch(asym_id, num_diffusion_samples) # (bs, l) + is_ligand = (expanded_type == _NONPOLYMER_ID).float() # (bs, l) + inter_chain = (expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2)).float() # (bs, l, l) + near_contact = (rep_distances < 8).float() # (bs, l, l) interface_per_token = (near_contact * inter_chain * (1.0 - is_ligand).unsqueeze(-1)).amax( dim=-1 - ) + ) # (bs, l) iplddt_weight = torch.where( is_ligand.bool(), torch.full_like(interface_per_token, 2.0), interface_per_token, - ) + ) # (bs, l) iplddt_weight_atoms = gather_token_to_atom( iplddt_weight.unsqueeze(-1), atom_to_token_m - ).squeeze(-1) - atom_iplddt_w = atom_mask_f * iplddt_weight_atoms + ).squeeze(-1) # (bs, a) + atom_iplddt_w = atom_mask_f * iplddt_weight_atoms # (bs, a) complex_iplddt = (plddt_per_atom * atom_iplddt_w).sum(dim=-1) / ( atom_iplddt_w.sum(dim=-1) + _EPS - ) - plddt_ca = plddt_per_atom.gather(1, rep_idx_m) + ) # (bs,) + plddt_ca = plddt_per_atom.gather(1, rep_idx_m) # (bs, l) - pae_logits = self.pae_head(pair) - pae = _categorical_mean(pae_logits, start=0.0, end=32.0).detach() + pae_logits = self.pae_head(pair) # (bs, l, l, n_pae_bins) + pae = _categorical_mean(pae_logits, start=0.0, end=32.0).detach() # (bs, l, l) n_bins = pae_logits.shape[-1] bin_width = 32.0 / n_bins - bin_centers = torch.arange(0.5 * bin_width, 32.0, bin_width, device=pae_logits.device) - mask_f = mask.float() - n_res = mask_f.sum(dim=-1, keepdim=True) - d0 = 1.24 * (n_res.clamp(min=19) - 15) ** (1 / 3) - 1.8 - tm_per_bin = 1 / (1 + (bin_centers / d0) ** 2) - pae_probs = F.softmax(pae_logits, dim=-1) - tm_expected = (pae_probs * tm_per_bin[:, None, None, :]).sum(dim=-1) - - pair_mask_2d = mask_f.unsqueeze(-1) * mask_f.unsqueeze(-2) - ptm_per_row = (tm_expected * pair_mask_2d).sum(dim=-1) / (pair_mask_2d.sum(dim=-1) + _EPS) - ptm = ptm_per_row.max(dim=-1).values + bin_centers = torch.arange(0.5 * bin_width, 32.0, bin_width, device=pae_logits.device) # (n_pae_bins,) + mask_f = mask.float() # (bs, l) + n_res = mask_f.sum(dim=-1, keepdim=True) # (bs, 1) + d0 = 1.24 * (n_res.clamp(min=19) - 15) ** (1 / 3) - 1.8 # (bs, 1) + tm_per_bin = 1 / (1 + (bin_centers / d0) ** 2) # (bs, n_pae_bins) + pae_probs = F.softmax(pae_logits, dim=-1) # (bs, l, l, n_pae_bins) + tm_expected = (pae_probs * tm_per_bin[:, None, None, :]).sum(dim=-1) # (bs, l, l) + + pair_mask_2d = mask_f.unsqueeze(-1) * mask_f.unsqueeze(-2) # (bs, l, l) + ptm_per_row = (tm_expected * pair_mask_2d).sum(dim=-1) / (pair_mask_2d.sum(dim=-1) + _EPS) # (bs, l) + ptm = ptm_per_row.max(dim=-1).values # (bs,) inter_chain_mask = ( expanded_asym.unsqueeze(-1) != expanded_asym.unsqueeze(-2) - ).float() * pair_mask_2d + ).float() * pair_mask_2d # (bs, l, l) iptm_per_row = (tm_expected * inter_chain_mask).sum(dim=-1) / ( inter_chain_mask.sum(dim=-1) + _EPS - ) - iptm = iptm_per_row.max(dim=-1).values + ) # (bs, l) + iptm = iptm_per_row.max(dim=-1).values # (bs,) max_chain_id = int(expanded_asym.max().item()) if batch_mult > 0 else 0 n_chains = max_chain_id + 1 @@ -255,16 +259,16 @@ class ConfidenceHead(nn.Module): n_chains, device=tm_expected.device, dtype=tm_expected.dtype, - ) + ) # (bs, n_chains, n_chains) for c1 in range(n_chains): - chain_c1 = (expanded_asym == c1).float() * mask_f + chain_c1 = (expanded_asym == c1).float() * mask_f # (bs, l) if chain_c1.sum() == 0: continue for c2 in range(n_chains): - chain_c2 = (expanded_asym == c2).float() * mask_f - pair_m = chain_c1.unsqueeze(-1) * chain_c2.unsqueeze(-2) - denom = pair_m.sum(dim=(-1, -2)) + _EPS - pair_chains_iptm[:, c1, c2] = (tm_expected * pair_m).sum(dim=(-1, -2)) / denom + chain_c2 = (expanded_asym == c2).float() * mask_f # (bs, l) + pair_m = chain_c1.unsqueeze(-1) * chain_c2.unsqueeze(-2) # (bs, l, l) + denom = pair_m.sum(dim=(-1, -2)) + _EPS # (bs,) + pair_chains_iptm[:, c1, c2] = (tm_expected * pair_m).sum(dim=(-1, -2)) / denom # (bs,) return { "plddt_logits": plddt_logits, @@ -278,7 +282,7 @@ class ConfidenceHead(nn.Module): "ptm": ptm.detach(), "iptm": iptm.detach(), "pair_chains_iptm": pair_chains_iptm.detach(), - } + } # mapping of confidence tensors with shapes traced above class _TransitionFFN(nn.Module): @@ -288,7 +292,8 @@ class _TransitionFFN(nn.Module): self.ffn = SwiGLUMLP(d_model, expansion_ratio=expansion_ratio, bias=False) def forward(self, x: Tensor) -> Tensor: - return self.ffn(self.norm(x)) + # x: (..., d_model). + return self.ffn(self.norm(x)) # x.shape class MSAEncoderBlock(nn.Module): @@ -327,44 +332,45 @@ class MSAEncoderBlock(nn.Module): pair_attention_mask: Tensor, msa_track_mask: Tensor | None = None, ) -> tuple[Tensor, Tensor]: + # msa_repr: (b, l, m, d_msa); pair_repr: (b, l, l, d_pair); msa_track_mask: (b,) or None. mask4d = ( msa_track_mask[:, None, None, None].to(dtype=msa_repr.dtype) if msa_track_mask is not None else None - ) + ) # (b, 1, 1, 1) or None - pair_mask4d = mask4d[:, :, :1] if mask4d is not None else None + pair_mask4d = mask4d[:, :, :1] if mask4d is not None else None # (b, 1, 1, 1) or None - msa_update = self.msa_pair_weighted_averaging(msa_repr, pair_repr, pair_attention_mask) + msa_update = self.msa_pair_weighted_averaging(msa_repr, pair_repr, pair_attention_mask) # (b, l, m, d_msa) if mask4d is not None: - msa_update = msa_update * mask4d - msa_repr = msa_repr + msa_update + msa_update = msa_update * mask4d # (b, l, m, d_msa) + msa_repr = msa_repr + msa_update # (b, l, m, d_msa) - msa_transition = self.msa_transition(msa_repr) + msa_transition = self.msa_transition(msa_repr) # (b, l, m, d_msa) if mask4d is not None: - msa_transition = msa_transition * mask4d - msa_repr = msa_repr + msa_transition + msa_transition = msa_transition * mask4d # (b, l, m, d_msa) + msa_repr = msa_repr + msa_transition # (b, l, m, d_msa) - pair_opm = self.outer_product_mean(msa_repr, msa_attention_mask) + pair_opm = self.outer_product_mean(msa_repr, msa_attention_mask) # (b, l, l, d_pair) if pair_mask4d is not None: - pair_opm = pair_opm * pair_mask4d - pair_repr = pair_repr + pair_opm + pair_opm = pair_opm * pair_mask4d # (b, l, l, d_pair) + pair_repr = pair_repr + pair_opm # (b, l, l, d_pair) - pair_out = self.tri_mul_out(pair_repr, mask=pair_attention_mask) + pair_out = self.tri_mul_out(pair_repr, mask=pair_attention_mask) # (b, l, l, d_pair) if pair_mask4d is not None: - pair_out = pair_out * pair_mask4d - pair_repr = pair_repr + pair_out + pair_out = pair_out * pair_mask4d # (b, l, l, d_pair) + pair_repr = pair_repr + pair_out # (b, l, l, d_pair) - pair_in = self.tri_mul_in(pair_repr, mask=pair_attention_mask) + pair_in = self.tri_mul_in(pair_repr, mask=pair_attention_mask) # (b, l, l, d_pair) if pair_mask4d is not None: - pair_in = pair_in * pair_mask4d - pair_repr = pair_repr + pair_in + pair_in = pair_in * pair_mask4d # (b, l, l, d_pair) + pair_repr = pair_repr + pair_in # (b, l, l, d_pair) - pair_transition = self.pair_transition(pair_repr) + pair_transition = self.pair_transition(pair_repr) # (b, l, l, d_pair) if pair_mask4d is not None: - pair_transition = pair_transition * pair_mask4d - pair_repr = pair_repr + pair_transition - return msa_repr, pair_repr + pair_transition = pair_transition * pair_mask4d # (b, l, l, d_pair) + pair_repr = pair_repr + pair_transition # (b, l, l, d_pair) + return msa_repr, pair_repr # (b, l, m, d_msa), (b, l, l, d_pair) class MSAEncoder(nn.Module): @@ -407,18 +413,19 @@ class MSAEncoder(nn.Module): deletion_value: Tensor, msa_attention_mask: Tensor, ) -> Tensor: + # x_pair: (b, l, l, d_pair); x_inputs: (b, l, d_inputs); MSA features: (b, l, m, 33), deletion/mask: (b, l, m). batch_size, _, depth = msa_attention_mask.shape m_feat = torch.cat( [msa_oh, has_deletion.unsqueeze(-1), deletion_value.unsqueeze(-1)], dim=-1, - ) - m = self.embed(m_feat) + self.project_inputs(x_inputs).unsqueeze(2) + ) # (b, l, m, 35) + m = self.embed(m_feat) + self.project_inputs(x_inputs).unsqueeze(2) # (b, l, m, d_msa) if depth > 1: - msa_track_mask = msa_attention_mask[:, :, 1:].any(dim=(1, 2)) + msa_track_mask = msa_attention_mask[:, :, 1:].any(dim=(1, 2)) # (b,) else: - msa_track_mask = torch.zeros(batch_size, dtype=torch.bool, device=x_pair.device) - tok_mask = msa_attention_mask[:, :, 0] - pair_attention_mask = tok_mask.unsqueeze(2) * tok_mask.unsqueeze(1) + msa_track_mask = torch.zeros(batch_size, dtype=torch.bool, device=x_pair.device) # (b,) + tok_mask = msa_attention_mask[:, :, 0] # (b, l) + pair_attention_mask = tok_mask.unsqueeze(2) * tok_mask.unsqueeze(1) # (b, l, l) for block in self.blocks: m, x_pair = cast(MSAEncoderBlock, block)( m, @@ -426,8 +433,8 @@ class MSAEncoder(nn.Module): msa_attention_mask, pair_attention_mask, msa_track_mask, - ) - return x_pair * msa_track_mask[:, None, None, None].to(dtype=x_pair.dtype) + ) # (b, l, m, d_msa), (b, l, l, d_pair) + return x_pair * msa_track_mask[:, None, None, None].to(dtype=x_pair.dtype) # (b, l, l, d_pair) class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, PreTrainedModel): @@ -477,7 +484,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, self.pair_loop_proj = nn.Sequential( nn.LayerNorm(d_pair), nn.Linear(d_pair, d_pair, bias=False) ) - nn.init.zeros_(cast(nn.Linear, self.pair_loop_proj[1]).weight) + nn.init.zeros_(cast(nn.Linear, self.pair_loop_proj[1]).weight) # (d_pair, d_pair) self.structure_head = DiffusionStructureHead(config) self.distogram_head = nn.Linear(d_pair, config.structure_head.distogram_bins, bias=True) @@ -648,6 +655,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, tok_mask: Tensor, verbose: bool = False, ) -> Tensor: + # Input tensors: (b, l); n_states and d_lm come from the loaded backbone. if self._esmc_fp8 and torch.is_grad_enabled(): _reload_esmc_bf16_for_gradients( self, @@ -670,9 +678,9 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, mol_type, tok_mask, pad_to_multiple=pad_to, - ) + ) # (b, l, n_states, d_lm) progress.update() - return result + return result # (b, l, n_states, d_lm) return compute_lm_hidden_states( self._esmc, input_ids, @@ -681,7 +689,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, mol_type, tok_mask, pad_to_multiple=pad_to, - ) + ) # (b, l, n_states, d_lm) def forward( self, @@ -731,6 +739,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, disto_cond_mask: Tensor | None = None, verbose: bool = False, ) -> ESMFold2Output | tuple[Any, ...]: + # Token IDs/masks: (b, l); atom IDs/masks: (b, a); ref_pos: (b, a, 3); chars: (b, a, 4); MSA: (b, m, l); bs = b * samples. output_hidden_states, return_dict = _resolve_structure_output_controls( self.config, output_attentions=output_attentions, @@ -751,8 +760,8 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, disto_cond_mask=disto_cond_mask, ) del gt_coords, is_resolved, frames_idx - tok_mask = token_attention_mask - atm_mask = atom_attention_mask + tok_mask = token_attention_mask # (b, l) + atm_mask = atom_attention_mask # (b, a) n_loops = num_loops if num_loops is not None else self.config.num_loops n_samples = ( num_diffusion_samples @@ -761,46 +770,46 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, ) if res_type.dim() == 2: - res_type_oh = F.one_hot(res_type.long(), num_classes=NUM_RES_TYPES).float() - res_type_oh = res_type_oh * tok_mask.unsqueeze(-1).float() + res_type_oh = F.one_hot(res_type.long(), num_classes=NUM_RES_TYPES).float() # (b, l, 33) + res_type_oh = res_type_oh * tok_mask.unsqueeze(-1).float() # (b, l, 33) else: - res_type_oh = res_type.float() + res_type_oh = res_type.float() # (b, l, 33) if msa is not None: - msa_oh_profile = F.one_hot(msa.long(), num_classes=NUM_RES_TYPES).float() + msa_oh_profile = F.one_hot(msa.long(), num_classes=NUM_RES_TYPES).float() # (b, m, l, 33) if msa_attention_mask is not None: - mask_f = msa_attention_mask.float().unsqueeze(-1) - msa_oh_profile = msa_oh_profile * mask_f - valid_seq_count = msa_attention_mask.float().sum(dim=1).clamp(min=1) - profile = msa_oh_profile.sum(dim=1) / valid_seq_count.unsqueeze(-1) + mask_f = msa_attention_mask.float().unsqueeze(-1) # (b, m, l, 1) + msa_oh_profile = msa_oh_profile * mask_f # (b, m, l, 33) + valid_seq_count = msa_attention_mask.float().sum(dim=1).clamp(min=1) # (b, l) + profile = msa_oh_profile.sum(dim=1) / valid_seq_count.unsqueeze(-1) # (b, l, 33) else: - profile = msa_oh_profile.mean(dim=1) + profile = msa_oh_profile.mean(dim=1) # (b, l, 33) else: - profile = res_type_oh + profile = res_type_oh # (b, l, 33) if res_type_soft is not None: - res_type_oh = res_type_soft.float() + res_type_oh = res_type_soft.float() # (b, l, 33) if not self.config.disable_msa_features and provide_soft_sequence_to_msa_and_profile: - profile = res_type_oh - msa = res_type_oh.unsqueeze(1) - msa_attention_mask = tok_mask.unsqueeze(1) + profile = res_type_oh # (b, l, 33) + msa = res_type_oh.unsqueeze(1) # (b, 1, l, 33) + msa_attention_mask = tok_mask.unsqueeze(1) # (b, 1, l) if deletion_mean is None: deletion_mean = torch.zeros( res_type.shape[0], res_type.shape[1], device=res_type.device - ) + ) # (b, l) if self.config.disable_msa_features: - profile = torch.zeros_like(profile) - deletion_mean = torch.zeros_like(deletion_mean) + profile = torch.zeros_like(profile) # (b, l, 33) + deletion_mean = torch.zeros_like(deletion_mean) # (b, l) - ref_element_oh = F.one_hot(ref_element.long(), num_classes=MAX_ATOMIC_NUMBER).float() + ref_element_oh = F.one_hot(ref_element.long(), num_classes=MAX_ATOMIC_NUMBER).float() # (b, a, 128) ref_atom_name_chars_oh = F.one_hot( ref_atom_name_chars.long(), num_classes=CHAR_VOCAB_SIZE - ).float() - atm_mask_f = atm_mask.float() - ref_element_oh = ref_element_oh * atm_mask_f.unsqueeze(-1) - ref_atom_name_chars_oh = ref_atom_name_chars_oh * atm_mask_f.unsqueeze(-1).unsqueeze(-1) - atom_to_token = atom_to_token * atm_mask.long() + ).float() # (b, a, 4, 64) + atm_mask_f = atm_mask.float() # (b, a) + ref_element_oh = ref_element_oh * atm_mask_f.unsqueeze(-1) # (b, a, 128) + ref_atom_name_chars_oh = ref_atom_name_chars_oh * atm_mask_f.unsqueeze(-1).unsqueeze(-1) # (b, a, 4, 64) + atom_to_token = atom_to_token * atm_mask.long() # (b, a) use_amp = ref_pos.device.type == "cuda" with torch.amp.autocast("cuda", enabled=use_amp, dtype=torch.bfloat16): @@ -815,58 +824,58 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, ref_element=ref_element_oh, ref_atom_name_chars=ref_atom_name_chars_oh, atom_to_token=atom_to_token, - ) + ) # (b, l, d_inputs) - z_init = self.z_init_1(x_inputs).unsqueeze(2) + self.z_init_2(x_inputs).unsqueeze(1) + z_init = self.z_init_1(x_inputs).unsqueeze(2) + self.z_init_2(x_inputs).unsqueeze(1) # (b, l, l, d_pair) relative_position_encoding = self.rel_pos( residue_index=residue_index, asym_id=asym_id, sym_id=sym_id, entity_id=entity_id, token_index=token_index, - ) - token_bonds_encoding = self.token_bonds(token_bonds.float()) - z_init = z_init + relative_position_encoding + token_bonds_encoding + ) # (b, l, l, d_pair) + token_bonds_encoding = self.token_bonds(token_bonds.float()) # (b, l, l, d_pair) + z_init = z_init + relative_position_encoding + token_bonds_encoding # (b, l, l, d_pair) if lm_hidden_states is None and input_ids is not None and self._esmc is not None: lm_hidden_states = self._compute_lm_hidden_states( input_ids, asym_id, residue_index, mol_type, tok_mask, verbose=verbose - ) + ) # (b, l, n_states, d_lm) if lm_hidden_states is not None: lm_dropout = ( self.config.lm_dropout if self.config.force_lm_dropout_during_inference or self.training else 0.0 ) - lm_z = self.language_model(lm_hidden_states.detach(), lm_dropout=lm_dropout) - z_init = z_init + lm_z.to(z_init.dtype) + lm_z = self.language_model(lm_hidden_states.detach(), lm_dropout=lm_dropout) # (b, l, l, d_pair) or None + z_init = z_init + lm_z.to(z_init.dtype) # (b, l, l, d_pair) msa_kwargs: dict[str, Tensor] | None = None if self.msa_encoder is not None and msa is not None: if msa.dim() == 4: batch_msa, depth, length_msa, _ = msa.shape - msa_oh = msa.permute(0, 2, 1, 3).float() + msa_oh = msa.permute(0, 2, 1, 3).float() # (b, l, m, 33) else: batch_msa, depth, length_msa = msa.shape msa_oh = F.one_hot( msa.permute(0, 2, 1).long(), num_classes=NUM_RES_TYPES - ).float() + ).float() # (b, l, m, 33) msa_attn = ( msa_attention_mask.permute(0, 2, 1).float() if msa_attention_mask is not None else tok_mask[:, :, None].expand(-1, -1, depth).float() - ) - msa_oh = msa_oh * msa_attn.unsqueeze(-1) + ) # (b, l, m) + msa_oh = msa_oh * msa_attn.unsqueeze(-1) # (b, l, m, 33) hd = ( has_deletion.permute(0, 2, 1).float() if has_deletion is not None else torch.zeros(batch_msa, length_msa, depth, device=msa.device) - ) + ) # (b, l, m) dv = ( deletion_value.permute(0, 2, 1).float() if deletion_value is not None else torch.zeros(batch_msa, length_msa, depth, device=msa.device) - ) + ) # (b, l, m) msa_kwargs = { "x_inputs": x_inputs, "msa_oh": msa_oh, @@ -875,10 +884,10 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, "msa_attention_mask": msa_attn, } - pair_mask = tok_mask[:, :, None].float() * tok_mask[:, None, :].float() - z = torch.zeros_like(z_init) - prev_pair: Tensor | None = None - prev_disto_probs: Tensor | None = None + pair_mask = tok_mask[:, :, None].float() * tok_mask[:, None, :].float() # (b, l, l) + z = torch.zeros_like(z_init) # (b, l, l, d_pair) + prev_pair: Tensor | None = None # (b, l, l, d_pair) or None + prev_disto_probs: Tensor | None = None # (b, l, l, n_distogram_bins) or None loop_iterator = range(n_loops + 1) if verbose: loop_iterator = tqdm( @@ -889,32 +898,32 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, ) for loop_num in loop_iterator: - z = z_init + self.pair_loop_proj(z) + z = z_init + self.pair_loop_proj(z) # (b, l, l, d_pair) if msa_kwargs is not None and self.msa_encoder is not None: - z = z + self.msa_encoder(x_pair=z, **msa_kwargs).to(z.dtype) - z = self.folding_trunk(z, pair_attention_mask=pair_mask) + z = z + self.msa_encoder(x_pair=z, **msa_kwargs).to(z.dtype) # (b, l, l, d_pair) + z = self.folding_trunk(z, pair_attention_mask=pair_mask) # (b, l, l, d_pair) if early_exit and loop_num < n_loops: l2_converged = False if prev_pair is not None and loop_num > 0: rel_l2 = ( z.float() - prev_pair.float() - ).norm() / prev_pair.float().norm().clamp(min=1e-8) + ).norm() / prev_pair.float().norm().clamp(min=1e-8) # () l2_converged = rel_l2.item() < 0.25 - prev_pair = z.detach().clone() - sym_z = z.float() + z.float().transpose(-2, -3) - cur_probs = F.softmax(self.distogram_head(sym_z).float(), dim=-1) + prev_pair = z.detach().clone() # (b, l, l, d_pair) or None + sym_z = z.float() + z.float().transpose(-2, -3) # (b, l, l, d_pair) + cur_probs = F.softmax(self.distogram_head(sym_z).float(), dim=-1) # (b, l, l, n_distogram_bins) if prev_disto_probs is not None and loop_num > 0: kl_per_pair = ( cur_probs * (cur_probs.clamp(min=1e-8) / prev_disto_probs.clamp(min=1e-8)).log() - ).sum(-1) - kl = (kl_per_pair + kl_per_pair.transpose(-1, -2)).mean() / 2 + ).sum(-1) # (b, l, l) + kl = (kl_per_pair + kl_per_pair.transpose(-1, -2)).mean() / 2 # () if l2_converged or kl.item() < 0.05: break - prev_disto_probs = cur_probs.detach() + prev_disto_probs = cur_probs.detach() # (b, l, l, n_distogram_bins) or None - distogram_logits = self.distogram_head(z + z.transpose(-2, -3)) + distogram_logits = self.distogram_head(z + z.transpose(-2, -3)) # (b, l, l, n_distogram_bins) with torch.no_grad(), _seed_context(seed): structure_output = self.structure_head.sample( @@ -943,8 +952,8 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, return_atom_repr=False, denoising_early_exit_rmsd=(0.10 if early_exit else None), verbose=verbose, - ) - sample_coords = structure_output["sample_atom_coords"] + ) # tensor mapping follows the called head's shape contract + sample_coords = structure_output["sample_atom_coords"] # (bs, a, 3), or explicit (b, samples, a, 3) if sample_coords is None: raise RuntimeError("ESMFold2 structure sampling did not return coordinates.") if sample_coords.ndim == 4: @@ -953,15 +962,15 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, batch * sample_count, atom_count, coord_dim, - ) - rep_idx = distogram_atom_idx.repeat_interleave(sample_count, 0).long() + ) # (bs, a, 3) + rep_idx = distogram_atom_idx.repeat_interleave(sample_count, 0).long() # (bs, l) for explicit sample axis, otherwise distogram_atom_idx.shape else: - sample_coords_for_gather = sample_coords - rep_idx = distogram_atom_idx.long() + sample_coords_for_gather = sample_coords # (bs, a, 3) + rep_idx = distogram_atom_idx.long() # (bs, l) for explicit sample axis, otherwise distogram_atom_idx.shape representative_atom_coords = gather_rep_atom_coords( sample_coords_for_gather, rep_idx, - ) + ) # (*rep_idx.shape, 3); gather retains the index batch size output: dict[str, Tensor] = { "distogram_logits": distogram_logits, @@ -990,7 +999,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, num_diffusion_samples=n_samples, relative_position_encoding=relative_position_encoding.detach(), token_bonds_encoding=token_bonds_encoding.detach(), - ) + ) # tensor mapping follows the called head's shape contract progress.update() else: confidence_output = self.confidence_head( @@ -1006,18 +1015,18 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, num_diffusion_samples=n_samples, relative_position_encoding=relative_position_encoding.detach(), token_bonds_encoding=token_bonds_encoding.detach(), - ) + ) # tensor mapping follows the called head's shape contract output.update(confidence_output) - output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask - output["residue_index"] = residue_index - output["entity_id"] = entity_id + output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask # (b, a) + output["residue_index"] = residue_index # (b, l) + output["entity_id"] = entity_id # (b, l) return _finalize_structure_output( output, token_input_state=x_inputs, pair_state=z, output_hidden_states=output_hidden_states, return_dict=return_dict, - ) + ) # ESMFold2Output/tuple retaining the traced tensor shapes @property def input_builder(self): @@ -1058,7 +1067,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin, if not self.config.msa_conditioning: for name in MSA_CONDITIONING_INPUT_NAMES: features.pop(name, None) - features = {name: tensor.to(self.device) for name, tensor in features.items()} + features = {name: tensor.to(self.device) for name, tensor in features.items()} # every feature retains its shape output = self(**features, **forward_kwargs, return_dict=True) for name in ( "res_type", diff --git a/fastplms/models/esmfold2/protein_utils.py b/fastplms/models/esmfold2/protein_utils.py index 1d7d0eaa25d0f4285056c0a8a7eb7ea7f2bd54ec..4cb9e6bbbcf6f93b0db4f2d1cac7d0e590bc999c 100644 --- a/fastplms/models/esmfold2/protein_utils.py +++ b/fastplms/models/esmfold2/protein_utils.py @@ -9,11 +9,11 @@ lazily from a provenance-bearing declarative package asset. from __future__ import annotations import json +import torch + from functools import cache from importlib.resources import files from typing import Any - -import torch from torch import Tensor from .esmfold2_constants import ( @@ -27,6 +27,7 @@ from .esmfold2_constants import ( PROTEIN_UNK_RES_TYPE, ) + _GEOMETRY_ASSET = "protein_reference_geometry.json" _GEOMETRY_SCHEMA = "fastplms.esmfold2.reference_geometry.v1" @@ -122,57 +123,57 @@ def prepare_protein_features(sequence: str) -> dict[str, Tensor]: raise ValueError("sequence must be non-empty") atoms, residue_types, input_ids, representative_atoms = _residue_records(sequence) - sequence_length = len(sequence) + sequence_length = len(sequence) # l n_atoms = _padded_atom_count(len(atoms)) - ref_pos = torch.zeros((n_atoms, 3), dtype=torch.float32) - ref_element = torch.zeros(n_atoms, dtype=torch.int64) - ref_charge = torch.zeros(n_atoms, dtype=torch.int8) - ref_atom_name_chars = torch.zeros((n_atoms, 4), dtype=torch.int64) - ref_space_uid = torch.zeros(n_atoms, dtype=torch.int64) - atom_attention_mask = torch.zeros(n_atoms, dtype=torch.bool) - atom_to_token = torch.zeros(n_atoms, dtype=torch.int64) + ref_pos = torch.zeros((n_atoms, 3), dtype=torch.float32) # (n_atoms, 3) + ref_element = torch.zeros(n_atoms, dtype=torch.int64) # (n_atoms,) + ref_charge = torch.zeros(n_atoms, dtype=torch.int8) # (n_atoms,) + ref_atom_name_chars = torch.zeros((n_atoms, 4), dtype=torch.int64) # (n_atoms, 4) + ref_space_uid = torch.zeros(n_atoms, dtype=torch.int64) # (n_atoms,) + atom_attention_mask = torch.zeros(n_atoms, dtype=torch.bool) # (n_atoms,) + atom_to_token = torch.zeros(n_atoms, dtype=torch.int64) # (n_atoms,) for atom_index, atom in enumerate(atoms): token_index = atom["token_index"] - ref_pos[atom_index] = torch.tensor(atom["position"], dtype=torch.float32) - ref_element[atom_index] = ELEMENT_TO_ATOMIC_NUM[atom["element"]] - ref_charge[atom_index] = atom["charge"] + ref_pos[atom_index] = torch.tensor(atom["position"], dtype=torch.float32) # (3,) + ref_element[atom_index] = ELEMENT_TO_ATOMIC_NUM[atom["element"]] # () + ref_charge[atom_index] = atom["charge"] # () ref_atom_name_chars[atom_index] = torch.tensor( _encode_atom_name(atom["name"]), dtype=torch.int64 - ) - ref_space_uid[atom_index] = token_index - atom_attention_mask[atom_index] = True - atom_to_token[atom_index] = token_index + ) # (4,) + ref_space_uid[atom_index] = token_index # () + atom_attention_mask[atom_index] = True # () + atom_to_token[atom_index] = token_index # () - residue_type_tensor = torch.tensor(residue_types, dtype=torch.int64) - msa = residue_type_tensor.unsqueeze(0) + residue_type_tensor = torch.tensor(residue_types, dtype=torch.int64) # (l,) + msa = residue_type_tensor.unsqueeze(0) # (1, l) features = { - "token_index": torch.arange(sequence_length, dtype=torch.int64), - "residue_index": torch.arange(sequence_length, dtype=torch.int64), - "asym_id": torch.zeros(sequence_length, dtype=torch.int64), - "sym_id": torch.zeros(sequence_length, dtype=torch.int64), - "entity_id": torch.ones(sequence_length, dtype=torch.int64), - "mol_type": torch.full((sequence_length,), MOL_TYPE_PROTEIN, dtype=torch.int64), - "res_type": residue_type_tensor, - "input_ids": torch.tensor(input_ids, dtype=torch.int64), - "token_bonds": torch.zeros((sequence_length, sequence_length, 1), dtype=torch.float32), - "token_attention_mask": torch.ones(sequence_length, dtype=torch.bool), - "ref_pos": ref_pos, - "ref_element": ref_element, - "ref_charge": ref_charge, - "ref_atom_name_chars": ref_atom_name_chars, - "ref_space_uid": ref_space_uid, - "atom_attention_mask": atom_attention_mask, - "atom_to_token": atom_to_token, - "distogram_atom_idx": torch.tensor(representative_atoms, dtype=torch.int64), - "msa": msa, - "msa_attention_mask": torch.ones_like(msa, dtype=torch.bool), - "has_deletion": torch.zeros_like(msa, dtype=torch.bool), - "deletion_value": torch.zeros_like(msa, dtype=torch.float32), - "deletion_mean": torch.zeros(sequence_length, dtype=torch.float32), + "token_index": torch.arange(sequence_length, dtype=torch.int64), # (l,) + "residue_index": torch.arange(sequence_length, dtype=torch.int64), # (l,) + "asym_id": torch.zeros(sequence_length, dtype=torch.int64), # (l,) + "sym_id": torch.zeros(sequence_length, dtype=torch.int64), # (l,) + "entity_id": torch.ones(sequence_length, dtype=torch.int64), # (l,) + "mol_type": torch.full((sequence_length,), MOL_TYPE_PROTEIN, dtype=torch.int64), # (l,) + "res_type": residue_type_tensor, # (l,) + "input_ids": torch.tensor(input_ids, dtype=torch.int64), # (l,) + "token_bonds": torch.zeros((sequence_length, sequence_length, 1), dtype=torch.float32), # (l, l, 1) + "token_attention_mask": torch.ones(sequence_length, dtype=torch.bool), # (l,) + "ref_pos": ref_pos, # (n_atoms, 3) + "ref_element": ref_element, # (n_atoms,) + "ref_charge": ref_charge, # (n_atoms,) + "ref_atom_name_chars": ref_atom_name_chars, # (n_atoms, 4) + "ref_space_uid": ref_space_uid, # (n_atoms,) + "atom_attention_mask": atom_attention_mask, # (n_atoms,) + "atom_to_token": atom_to_token, # (n_atoms,) + "distogram_atom_idx": torch.tensor(representative_atoms, dtype=torch.int64), # (l,) + "msa": msa, # (1, l) + "msa_attention_mask": torch.ones_like(msa, dtype=torch.bool), # (1, l) + "has_deletion": torch.zeros_like(msa, dtype=torch.bool), # (1, l) + "deletion_value": torch.zeros_like(msa, dtype=torch.float32), # (1, l) + "deletion_mean": torch.zeros(sequence_length, dtype=torch.float32), # (l,) } - return {name: tensor.unsqueeze(0) for name, tensor in features.items()} + return {name: tensor.unsqueeze(0) for name, tensor in features.items()} # each shape -> (1, *shape) __all__ = ["prepare_protein_features"] diff --git a/fastplms/models/esmfold2/reproducibility.py b/fastplms/models/esmfold2/reproducibility.py index 92194cec1867567899ff7fd26646961ac44bd607..c3db977fdab392ad4df12994922b1f2e0b74b4f5 100644 --- a/fastplms/models/esmfold2/reproducibility.py +++ b/fastplms/models/esmfold2/reproducibility.py @@ -3,13 +3,13 @@ from __future__ import annotations import random +import numpy as np +import torch + from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass from typing import Any - -import numpy as np -import torch from torch import Tensor diff --git a/fastplms/models/ttt.py b/fastplms/models/ttt.py index 56b4dbae55b65f1d6a9d86111a22815c103d0162..823f749cbb5639da5fa16091b0154219e74acc2c 100644 --- a/fastplms/models/ttt.py +++ b/fastplms/models/ttt.py @@ -6,6 +6,7 @@ import numbers import torch import torch.nn as nn import torch.nn.functional as F + from collections.abc import Iterator, Mapping from dataclasses import asdict, dataclass, fields from typing import Any diff --git a/fastplms/registry.py b/fastplms/registry.py index d3b3b8b6907259d343ccfaadfd13669324144553..f8416c595162c7dd29269d882ec92b3562b35833 100644 --- a/fastplms/registry.py +++ b/fastplms/registry.py @@ -9,6 +9,7 @@ from __future__ import annotations import re import tomllib + from collections.abc import Iterator, Mapping from dataclasses import dataclass from functools import lru_cache diff --git a/fastplms_bundle.py b/fastplms_bundle.py index b73cec8eebd1a3be236c0176d9cec08bb3afbcec..57e7f73ba75a19f1ad67af8d82aef29a1a0fbdfa 100644 --- a/fastplms_bundle.py +++ b/fastplms_bundle.py @@ -1,6 +1,6 @@ """Generated deterministic archive of unchanged FastPLMs runtime sources.""" -RUNTIME_HASH = "88b1209bd2c3097e992a4ee20994a608525d77aaf584e34a5c90829f81764d4d" +RUNTIME_HASH = "ab074f9ea4b20dcfaf9e2ca9082480ee0f9dba02c92d14da5dd3879aa86bf7ed" RUNTIME_DATA = ( 'P)h>@6aWAK2mk;8ApjK1*rl8T002P;000yK003rTb98WQZF4VQUukY>bYEXCaCwcDT~FIE6o&8fE6#kCOkKrpD{WG>fiw*S0w!&%' 'ka3ff)~XXnw!boXxCNXR+v-n*MVGJ(3@IZTN~r{N?M*ufm;3>HuCy*r6&RFNuwlZe%(L#&$HcFHeV}@c@v+N-%hknGGL6g99r~qdhxS' 'K!!%lG$EV~_4L4MgTc&x?k{K`Y-XmR9?VaBk8L#-%QtP~KfbX@AVGdze%S4b#Z_j>f~z$GLd~{' 'q`ImN*>y43+8$NTg(n_=PKC}V^Isu6-+MqbfwJNBE}+?s&+ZkZYq;BJc?r3mCsjwL=U%ybjOp=e(jT@2at|y6*65UH{q{dlO9KQH' - '000080000X01(K8P~##102Yh@02=@R0A^uxbZ~5Kb1z|ZbY*UIX>V>XUt@1_WiD`e?LBLA+qkvg^(#>2!&1F8n@6Y9(O%uDH*va|' + '000080000X08$8ind2e=02hn^02=@R0A^uxbZ~5Kb1z|ZbY*UIX>V>XUt@1_WiD`e?LBLA+qkvg^(#>2!&1F8n@6Y9(O%uDH*va|' 'O*ZZ(yX~D!hN30NVoi|>N!jtb`S15U01yB{S$49!?ab|THWu+ZI5=+{0ORraWp#B_$V*v>tZil4=2a;!WL?Ul5m~tuo2hqe' 'dbhyRWHframSVd*EAoXn%N7?BszHsm&K9jG@=Mu>ycE+-wVcjZS<`NdO_P}3=ZSc|#XquQWCp6GsAW?XmvSkpvbYx6s+G0K#3CyS' '_(Oi&!f1I5CAIvplTF)*b}cutXeyC!wpHEc<@xA2jCT6+RU=B6Q7x9$RasQovJq7c5N@`40kAEqrA$WS@pv>^)zwC%>1x;RYMG|0' - 'Czy9xwHd>RrDfJ;iy~_pX*x2WxP~lu8(nk)fAiON' - 'UCV3<(CMNdURH|>Rd&7YdV7^^w@~f0u0CGBg#y)SURGOE2fC{3tUOnRG|!|gb@6IfE_n4s)`;f{he^synHDgxu8OFq4|1uBN25_&' - 'Ur$E@{^#`Gkw>yBm>9;58$@g!6cyaOu${#)c^C(rMwoKhVr#4x~1^8E`!nYLv^Y*vSdw5cuXr*+}9Nk+%}av1}{g)U#?=E`fau2~2oY%LPojxIWr7@QnsYkmYC@@Yg#S0D-2yyU)p{x|C^K' - 'r65!ys4}6Vt2Lk#dM+8%iTEEeumlWb*WI?-B9IZVP&<2`6^-N`YY8l0s?ixA3xK@#mW=V&L=$oJKeYA+3D4?kE3~{h0zre*RO>FD' - 'LnkD9NuR!cb@~GSeRlHAkAEGXRSkDsmTOt|K!%U%3^a)`V#q&6ECU4Xz&g(;+(c0CI7dUeEpKZBS>SxFy($m?rGn0#fEkVU>*`r4Jm@YHShN>2KW#NECd!vH?vGW+A{sLT6op=&eA&;D_w8%9k+g(;uH5vyQti7wftz^PgFJ' - 'Y{3m<>04JjRS3yaHiwre%3heq3#*=3d!X(eORGjp3>rkTS)K-l0huA@oPR!UmV' - 'l+aAjNzc!B)ea3>P&-aLl8j0py#^7=jgH#th*n%v0z!rG=SUL9qCn4|XiVg&szCK6;>SjJ$GS?74Rc;$M@#5pfgU{&KnwmWuKxy5' - 'P}8EK{zFNAdJQXD;u@*Wh@!4rsZC&j2dv&TXk0W}Nq>UEY?J{D<|=eV6bSHY-KgvmoLJhSQDdS*+<39wjSt3~`jR4a4fJPspIqkh' - 'D&m#i!DRkCNx*&IUeh{JgQ!CHYc_s5rZMPS=NF~EqWludFzO_0>RS7M>GSN5p?$-#QB)L(^u4TrK$C$`LeYb9C}94}3?xgbbXQX;*FXgrcQ_JOZn6!?zP#DuIwowu=i+eu*xab=$X!A9u-=YkH^Ekn@TAL4~9mvs_8+g_P$Y{j7Pp7q|7@C^Et?g' - 'aM2CP#YH}G7So!1FwlV_PRQHtVf}~IB2{AfP_aF4dk1dpm#0LbC~6|j$)!EOo5hEmyDFvO2JdKXh8n-Yk`*Nq8SR+?_4CmSIa{lZdIeQuzF3p0yc+q6qY3*E;84K' - 'X8o{j)U}SiUN7?o24@ymVb>G-2UiMA9oa=+53!e4;~z_kRRG=*4x$858~F+r>1dmm7`IZ8j=h#Q9h9H`auWjc6UmGl7jeIbP' - 'P_|4uy@N}J1-Pda^(5RLX~tH@mqww9(K0THKwYGbvt3)A*V*z&5-l%<2Q&%gFw~^kdZ;wI(?(Wk2|*;M&tD$D`8NITw1O5rcH~2-hBo~gx`C8zXP>zt^Xqfv#qvo+0G?-E!e;P3zWL_H`I$MGMXrDYb)etbJ' - 'ySzykyJhB7;HFT5JiGuko+=)W##lEXR6KyW1JvNA2WB9#P;XfOhbx8s)n#6D4Qg@SJ4' - 'o9B=IBs8tDPjvd^@ewd2MPhv2q_+76G>kLUZF+%R&VLKVz=7RPGmt=XQMMT=M!sO^V2%oJ!m1q' - 'vv6Z!LrwXH|NS(?np~`T5~hP0OSTQzq#WnCv-7R?2EeCrOh-}yZeNB5Rg;`8;owdwSD~*e{xUltRKO#+fcSD>xu~jhHJtjQ(Bn!f' - 'mB;XFh$`H%$cicLE&!agJ*PJRt5v(nKBf)$KyoTbDTns^1FMIeY}^H<6Q7J75y&tY5lA76qi#D}VIUTj%De-;R9%9To0IILxEI4G' - '2tE~D`TJUyV(vh$W2p0(S`k><&d#>mBFAL2mVz5DcDI54-)8sKisZ{x_t' - 'ww6a!c`5z|f|hp{F4a6K+34(AoVLVJZQOKCj-*+rwm' - '|Fzljm)SX{FPa6ws&Ag19=pM!`)Qb7SRx(_QXDQO_J=8EGdBQfX`5ZU-QievyTo&NBb=(QBuN}Gw%RrD!YZ#-9L~hXXAXLtvWuC8' - 'ET`mhW~0t6Th`SUAWpZlhsi^?$R$MQms^ope8&I8qkgnqNhHP$C@{oo-ry!zR)WFA-<3N);msL^MGs4NSdu6Iu}kvh@V*bt?#4xL' - 'g+hq$RSeb&m<1Ta?7W*`@LKOhOv915Pvc2glj%s&uc**=X(K%VGvI-+m5=39)VorJ81aY93Q%1auxJTp^8qw#S!`w9Q2Ozu' - 'NxX*sROE|u?FbG!Z-72ZEt^)CRds%z<2fH}iAsdh_$b|pk7uF2N=9@#NxUeR^5b(P=;M!hqY|C;Mp*J(9U4-o{|i!TzSziiT`g7I' - 'XQh>Cv}ixZU;xm9<>iv1Q(?u-dWfom$|)&{e)6?$1fMo>w}Q}Z(qxwsHEoK?0W_E%z#$y$FoA-N_;2ypqZV4|pOD`IK_F7BIieyZ' - '=1wy}2KUDrdkp}<67nG#a3d=LqhPjLRW^PM*oy-*;%|tABLn28m)Zou7~heZMONHx8VucS6T!9XAixL$' - 'm|Q@kb;{k%goO~`;n?~8X!^cOUXzQeq!`x^Lv}KtVYxR#P#aM6U>=Y+_g+O)Y{lYX!Wy~t%jqkVM;Va' - 'g-6j!+NYqga@_bJJ-d=uvPNxN2B>oE^4}o!xs+y4rcYm1Db(%g!ulSh4ozCyx?1iQEq$H1y|Nh5TZ});x@wpr%b{kJhaTdcz#QJc' - '_l!ACEVOHek_>65$gM@Yg#ne^O(Fq}j(MVKbO1|)V4b)G?K7=4l4hL{793V$^4Iu8JjOc9&C+nEyY>qzIWpTCgMwJN5bo7_+_q!O' - 'FQN9njH&>%|HkS9Cd26YTqW*Jg**MSnp^>T+mwt*32}cs6gkX86R>K_PZT1+;8ptoMuWr>^<^8ml' - 'ElIrj+5_7bf&Cq%Mo(MKJR>>5eTC~$kkV#-aiglMxFBb>T1}#046hRk_UGX^*VAgNvTv)a2FZIqS3S&W$EP-jYqZ!CS2X|zN|rnk' - 'ib3Tq?F>o@UW49A%JE!IE3!^Mw-pSs!*d+io$dvl5Zpgfa+}YG@{yxUS>yf@Ct9KZ%n95@b`9;e*>%HvtJ?yPWfE2~h_8&I20?`Z>Jwa' - 'QPKzgAW_@LW!vG>sXc^JBlbdE5;s|t-HJvw' - 'hvDrDU8nh|CwKY85e*t2I%YSc(daMc>XB$wS3k>g_O{-E%fZ$V#WPMhr0_Dkh9Uh}If(ZRyWm7Xib2~9TdgQLM8y{HR9-)>ZXHgf' - '(1x)kC{>S6cEGBEYyfE`WJX848x%4mF#My4ZKZTg4O%!6$EA=Zp2@Do*)A`ZC?nXXz@yxBNCX9v0)_6G0l7rLH)2^~=7UbTms!ga' - '>8h$P67hoMAG;$Y6)6LS64~((QcGt5amK6}~Pfh3`h' - 'x33DA8WZG1Ame#EKOq1){Es}@K6wmvH#@ve;F1)JuurCUQ9+U}*xFiEt?oEHrb1Y0K?E1wf+>;G&ekXtnZ2C|eTPo!g@dQIh;PE`1Ol%~2Xd+?Qn(m+=0toPq6Xp47CJ6zJO9|hgMOl?7mZGdl$+l8Q' - '$-5ewN^Hr}$7X*KEIHg^A+{!9_BMB=r=`?kIApP*oy@^Pbxdxk_!e4;)kd04U(>*J{S8LTHXVh}Y~S-sz}yWS6SwXUf&akA_fuz6' - 'kI)f!h>Kk`*}K_a4>N>!2%oe`O6sKCZBn$!V10FJk4f65F|5sK0B1}eY$t+C%V8UXoo?)`c~OqHuRcxWj-kIy35J|~F4wTrxBrkOq_g#~9&eoLn+HEP3Hvr-2C}HH8+F|4v7hv71&YSrKAt@vJ21o{9?SZ+ujH8|V|`#!9i!8+WhXzJ' - 'jq~YalL5^Eqkttsu_FYXYq;G`_Dsh;8?a|?YC<%@4f!#+XauhEw$LQ^&oJnDJE5`eL}3uxPjMI4@15}6kAdJBL@T@AAQVyM;U16l' - '+DZ3&>K=?wykVuk^}GMPRENv}@Wu20^Gi?(@Ho#pp*E%)N8Tw;K(DvcO>oqLP7me-C{k%>7DHGs8bL}8mX72jw' - 'U-G988D>IRB;@2R=z36?JY6k9x8mdtZ=qkQFqgiLQyn`{F(*r?4T>iuM03elXnBY3ps^RyKj+?Ey2jHmB)G>ZRjbG?^vxKa#nT6_' - 'TSk3arEAYTg>*T^+!M&6xt9Hop>ovPj7+#$@JlGY_qM5;1M*w>K_A' - 'S)9LW9_Z)zf+36Ol*Spm^i_|RT8rBuhD5yY~n+Q6ey7&IT=)(sw!iO-dDHJocF#Gs0ENDkSVRLSKafAv=?}52F!F_0D)(_p$+d}ijT^&P' - 'xjNo|+aVW54^#GemV;S;oy&qx6~Zd7R+8^ig$e1o%=JMMR4B_jUqNr#qOKYpVbm+`lHr{0WTVSbC`vi!tM&Q{a&C!o?x@Fe`UuWh' - '1(erR3<@j>OejBMM@`2^Ib=#BEBjDeU5D0oczuH~#eG|(AARf3?H04!{TYjaNZfsL' - '`42?Cb7Nh~akxERGX-3JF|4cTkDv7%4WEpdcs1>W>exw_wrXOgf)gxvVrB90pZH(16N3sTvoaihMH1}WQ=$AYN}y+@!L!9_?>+w%' - 'jv4wBn;kOC*&!4DE%NNU)cTo2EVtHo67DY!N}s<|3ckz7vyhf?*Y`n}lps_Bpf+agEk|&uX}_GF_E6iy-99BbG<+PGswMtre#a#}' - 'dWtQE?F*8yT5xYYRAUUBFCWxVzYRtHnrn>56n<=EeYDCJIbFD_F5jb*Y@5qmp~8DS|-rdcT#S}d=9PW' - 'bFnRU4gA38PT&_11$UaSz@pS|8eBK#;&pw*Mt;njmJd9D*RjgaceSdnZkXj?HKlfQig30>4-CMc&)>8)4w$5X+d570qGV2dFfRBv' - 'JjICa*221zDH1?geaNQb$J2*@?B%G~XJPD4Un&md*FYdkS?3dd22J2dQEkbww`s0uT`#4=esn#X' - 'tz^fFAAR6^pD%Ddy0rqgZO1R^lnZc@bjCwgs?O+@8bEOdOiZ_P0BRR_DIl`4`)&}gWnz$kzP*DBl`^=kD{wH{Ym+XR>kA}shk;yg' - '3=K>J*;AO=YF)BK4_tVQnDj6k->v?Wu0GzU_S5mp7tgHXZ;qdSck=x+d?1E{NM{K#E=8!$5I%o|33Aly70@`)s@QD-Pcqv8_3vuj' - '$^>88Y_T{DHJ`hL0~|wFA%O!t!ZbH@o#%6+t}&FES3MSMxwzm^gu^X-nuzjS`MxUNxg%Hbf#ukFqJbPrgbTAi)J@Ln{%}gVCUSu_' - 'BG{M(M-LPecC@iO0}gI?e0e894GT_Sdk?2%XZU)=6vLSMem(=;3X1)BC|=x2U}9Y0d?%Ym14$36U|>6QhabH>`Rno1|4pBMd-C+V' - ')7LM)e;Zz2|Ai$sK5rP|(-0xq#LiUlX`};ni>$0l#BbWIkHx-cMg+mKF7?l;brEgwURrRv->S8tc~Wbe0`vmV>a?8rWI!<6Ksm06Y^byDDEh88T-H^o^5`^G(V;}sNW;{u{hp4)U-M%h*)3>|8`{+7AW?h&5`tv2|>TS6M' - '|LELRx{wr+_K9S0*Wr!%6EB%^-b%ArMB{C2b=W{jv%2oKHb_#NXY=sI0TP8b24Hst&SZpYU8iGOBg}J5_(>~z3u=w|v=|da#e`>Y' - '`Pm-1!K;aPjr`hQNUOt+-S}~cUCG_?djlwzX={|9$!Zeo>O*ya?C*?E`^tLgl3qESJ#q6a2G^I)%Ph>jV)tVa<$ajW8{WWB^zS#d' - 'wjBv5LQzMI8ZC$f#K%hvY*ct_D#OM$zXWZq4J(~Bjb14&$m`>4DsJ8p`1B%>)PYNSr~-a>I4~T=4~kUrbfBW?mp>i' - 'R}|L!6nN^e+l{NMvrIktvbYxayJ-^ZeF{DHNDgh#-d$%0XY~2jd>mlx&;`VszjnY|v=s>u@xnf>C;8cK1HN$Dn=gq0^Iu^9L8ek0!$mRZ)D3mpFQNRaPQWfFv)dXL|+=t' - 'o*sDN5ORzQ&;^%`QXFqlvfkDlhRkK&l#=*7oc?JDQBdYH6>s9GS!3E4}?e(eQa&Z3qN&=d3r9|7D-Z' - '1vdGHB&M4vO9=z?co^7JI?KRGeBcM|8WB2Q;(hw#vj1w4pIzXi@{nY^y)L_Z*f-ezpoC)Y

vmL-UFE+#U{+?LyoH0Q-uwLl1VcF9$qGbSmJl' - 'ZZ|*?!p9l$6*~7x$FVxp-xG^>44xP!>J=qA)Zb&ofNb!N=BopJ0bUKjd#lYkbzXA4{dLHLZ?^eGUWs&fUs0v$SL+nN=F5-junhh6' - 'f&aO<#DBd@(lAZWtLckfJSPYOM^lnzI5qWaM?W%FDc!' - 'UxU?tRjPdydLCgX61;@(-PWX!!E?R&j&@GV^DWkL0f;_Q~3icaj#hMpa?-xr+e!&0MtZw!WO?RjUAMU{8$' - 'Pv6p*g&u+Q-&36RA6A?k%=JFHdjh~1DSFoviS!Qu&@L;CWl~%dVPSS8)9$S_h%waerPGdrXoo^kpZ&Xu&1*2@@C' - 'z<0)G&V-(}R!Q+17FF+I@{lXV%(F_BP0W)CYIB9sPr$v>dFpdBfn-DH!qOFplc@VzROhtQ3yTp=VjevD3di_rqEg?@i^)}~fmbJt' - 'YDHb{SN($a9DA7yd?Bd8sm*b+D*OK}(ys~8;uXH#v7kg87gxB+3UjCy9NlH4r~&BwE?wYL3Y?C~>!yt$zJOG-Op|erKHgLfViCaL' - 'SXx$cK8%ZJiVdpS5P~tZPB3{KV9`ZnE=$Q*-S;l5xwEEya~OB(gKxS739{Q4aL^r#^GZEPH@oqM|I~8|^lY6Qz0xATkR46Lwr*t)' - 'z)VqhU{h0i8##@U5XKW%D`-ee_x}e_O9KQH000080000X078XVW0(j40G1m703rYY0A^uxbZ~5Kb1z|ZbY*UIX>V>XUu$J@Ze?s=' + 'Czy9xwHdpg|}WoL!tO3SRx7Dd)H(sX1#aSd7SHoE8p{^qak' + 'x|Z1zz|%!PysQ=%s_c5(_4X>;ZlT&~U46WM3k9mvysWmS4s=!5S$VDsX`)G4>f+U|T=4RVtP#%@3X+u9GA&?WT@`UpALLRMk4B@m' + 'zMhT*{LksVqmN`&$d749jH<71u%A~zjIme%gsn`^@JqU(Pi~u${J4n2GPJQZhX5xK@c7TYns-ua5tbK7V=g' + 'kM!yB({E4GlkeaD@Z#hRls|g>=TWL?ZJD}#PHnPy*Y383j74?!PoRtU(9|l7CL?a!f!YLH8_Ycb}6@bt%)f' + 'ND;mi?))IKWRHHLK765teEg9pli6-Lce`xIu5}wu7R%n591d;})sn%UQ' + 'hfYX#A#bxFrIjRc+JFefPTsEbMl5BK!x8{lf^1t>P#I(oH5F52wdtH$M0H&!ASY!zj}aw{wYaKw#gegy(2H$dEo9Riofp*^aOhiX' + 'tH2_GCK?cIQZTq@*J4-J5=!6)h-(nV3H5ah9DXUqs;WWkf%pVuWgyZ);^sAQH9!ZSRzNG|Ld|Xg#DK#T5_)g470f2lnBv)9Dep>Uvd{}#!*Y?&y5&cu9fMB980mCkn?D5u_NlwH#@r;td8IhXRPoA-I0ZPzqkGz6}}OlcJ#R87uSj3!R}H(&YE' + 'A2_q)c1vCuo$x^XV3-1B7;-@XND`{MQY>9ZGaj=y<%l0JR?>huNt`|RYKAOAXz1q<39_5$Mo=m1k>Y7+g!;EF5NItUO=' + '5=lSe?~}OSfcUdtA3s~wnQ+Zdm%I4;oz@!+(~Um9@8RhvG2On;`Xg9o2uq=;@-5$e+Ds&vXVXmk=NIX#;wl)OG-+;UlbDiw+Q+{5o{HppJVL!cLsa!oOy^tf4%Q$' + 'vyTWUBtyiA#=huRR#*&D1fN*_3))z~7k)8D!gkSI8HWdoWX%tC;NgwDDG(OZFl!4KJGl`mn~r$0VBW*v83F4lEb=0CIc' + '*n%I%(zmXz5^-WQ2%{Nj6@+L5LxMqBt+X`dWtWQ#tVd6qZ3}>i?2TqC7kO3yS)gu0eRe5P9#^~bHFc!sL7laPMyS=uVudq1t17S?' + 'N^}i@LgYD^R=!T?y9<4_kmUg34?CcYjU-Paa`lNA' + 'pr%Dd{fCnL^cq&Q#5Gc#5k+0MQk%d44_Lix(70%{lKupR*(d`R%vI=!C=lS)x>4CBII*-tqsBysxbb4U8y}1}^(95<8tBjNKDo^0' + 'Rm3a3gUS4Pl7RcZy{2`d22q9X*KGWBOk>cu&M!)RMfoL^Vbn?1*b%64PmbCu6nZkm=Jn{H;0vf(Pi>&K?b-jRW(|%UW$U*N>fHVi' + 'Jx+9a+Lruruf#%Wuk2C(lY{kMslN*S8V-lM#e-&nkHNSjvDin)UgMy*6t-31Es1Rcc1^%Ucp$#rou4DSKhHp^%vDfuj(l8UB&;d&' + '^EJweWxgV(vlYjuFB;78_?yu7Qd$?rhF^p_Ji*%_#7Rp%i7Ppf)Z{Xc`ng2VN6#>~(eYSdT(|anyJP1c9VbvmH_S' + 'ZJxwvGmv)z)tk&U8^|{^QSVxw=P=7_9mPaTm;x=SE*UASm4c^e(SZEv*8(lkMKctt-?>PBua<{A+^R-pVfC7H1#AxKC@f1rTx6~d' + '&H7>6sB0a2ya+G9eXWrIw(K=nsTv}2vky)WhkD8wpaE`*te7mjNl_PD=-`M2__Sb%' + 'plq3RdIy&b3vg2@>Pfgg(u}Q)FO5PIqh(wYfx1W=XS=pKue0TmBwAhy4`>p~VW>&7^-yVar;V)85`suhpT9hQ^KJUw$q(P3ynHhS' + 'Rkmo~p~o5!z8HVM`K0AI7#jH!4Ih`N$;68+pPNF+O~_G4b7i=c4VmHcfYMO@uH~3p9xu*=E!{>`yS4$(gH|KoY<4XP5hWWAfJrV1!PuH9{-qNH8t`ingTfBU3a2=_dkUMDCs9(7b*oi(z)q>vFZL-NlmFC(Z9' + '+!=K?KXH-r3Q8ADi`5R`ke=++&R`?{7K`sV2K*C>Z}5w1NiH0X^R>V&p&TK-(J=RgM$Kb0XfUNd{xo7b$hbhZNh(LQ->sv%%b' + 'cX^X8cFWAEz)hh9d3XV8JXJg#jj?V(sCWQ#2dKeK56nPzd-{^1P$MGoB0dW6DM~i_YwX8_&ZywgpP-8Vvsb69;@0@o$1_0!fLLIW' + 'Q{_}5iQ#@ylyg6mr`c_{M5XsOc$gjJJu8#do0ji1UmgA}-8C3q)caVEd(c9J' + 'XW_=ehMMvX|NCi(HMv;xBuocWmTViaNjc7OXXjh(4S-MMn2w|Z+`bGAswO#G!oi(Vu0mf`{AG4PsDMXs0rBO&a#2<1YB=>pp~sa}' + 'Dv#mU5LLKikrh+gT>v;~drocsSF3iDeM}qhf#g(>QV#9+2UZU`*|-ZzCq5ZFB9LJ)B9KBBN8NU|!ayu4m3jwysk#IwHz(OgaW95X' + '5PT}Q^7pkY#oU2h$57`nwIZ;zot2W_1TF6@T&j6eveDVKIC=BxaV(ylzI?_1g&!x6V*LEPDwY!t**wqkf@WW7Tu5<+GyWUzDVTBeya-K%g!T$qmeP6vvWrHA' + '|7)}5FSB#ZUo;DVRo^^2J$8dd_tP-FutYo5XY_)6Pg;idwIGl-%&m8nPWfwCG' + 'Sx(92%toDCwydiyK%8!850i&(kxPiqFSjDI_>BLFNBwBKl1PjhP+*AFyunSbtOSFJzbkiq!kaS+iyoHluq03ZW0&O1;e8*P-HnUh' + '3WX5gs~D^mFbgn-*?Bj?;I-b1n1&;9pT?80Cex9kUs0j$(nfxOL7Vi$E#()Xa#~EPkmhB|n`0lWq>NDcg;oA*b|)jV)WBlZX(IurjT9>xD*y2=W~vV3fRW8r9{c;?`H' + 'sM>{H?ZT~Q@!E%3kTD1AUj*x4{Ksk6_7_&I|4%`mMM3{I#z1?!CD8s#X21hsD<8|JsCT6bG2#!I6`;B~BZ' + '&q^!PXwiO*!2qBI%gZH2r^1Sv^$=ABl~Ym@{p4%i2tIA%ZUv#+q{%KNYT6W&186WkfI~RgVFCpk@!#UHM=i9_KOw&bf$cYG#I+uCW33%L4Xki' + 'Fu8z6>y*2j2@4^>!?E-G(e!d(mKEp~#wPzy2>f=c)9vXU1fOSFA%b#WR' + 'Xo9=xz-n?wQ{9rHxf=m3@o!8&mX+Gkp8B+WV@EI6#h6C=xNXOl' + 'UqbDD8C3yj|BclJOoq|(xk}ud3U~TtHMs)xwka8r65{@zKI&1rJqpSzeROZ83Y>#{=A+5C2*L7Bt%f0{c5ijh?oec}8-A`wG{iAf?Uv;zm_haY4>%wVFi17+xn7?9an-uBX*jW#3j;4U+eKu6mf$j!$h4*J!aPu4(`blq`87' + '6oblJ+8LA*yav6Kl;gRaR%D%iZYvmMhvzu3JKYO9A-I2}>Ao_v+IWUR<{Kn%OtE~5N%Oyb33(L' + '(Y6-@Gcm3`P6S95wIi>$byJ+o)UquL%I0v9O~$E93Xu-58TTe+SSk|Kz1q8-+`i*LyEzlcR%<|(ZR;FdxH7#1jl}HlaVS=>-%dY<' + 'qNES}L87*g%eKR%Q+o)dM(l;SByzg2)F^wZzgJK*cF*8<5yo(5r5OVas>7-nBdx(d$I$VNx}I2D0j5I(syne#UZpIpU8q1PyA_RW' + '4#V3Qx=!;^Pww)GBN{Y5bj)r>qtRc?)g#fWu6~x~>}|aRmxHY#if5d1Na1C64MX~|auDwscEO2&6oa-Iwpvkgh>9)Xsl0w%-8!5|' + 'p$%h8P^uoC?0{7R*#Oc?$c&D7Hz;ICVE9K7+e+!08nkdCj!Pj+Jd<6Evt3>+QAV&&fk(ONkO&GS1qxj>19FLgZ^W{~%mloq?XPA;+QSv&UqIlbXyX1$mp>zPV_>7zQIkAuLUj(bX{KnUaU9}^I3?sRrm@z(8u8$efF}o14%+1' + 'Z(kKKH73Z3K*sZSenJ3p_#b()eexLUZgzN`z$GabVV_LzqJkt{u(h?STHSGYOog!0f(S0U1ydxahUoM`2+uZN^^FZUL14CC?WEal' + 'A~3FI;H?1Mk>;R@haB9rN&xUk{Z0TE*z}<`wp7X~;t7V1z~gy1nb=75&_u$pHQhl$1Q6gIC(85FOcDYbmlD1~i?S+FEJaz9l5M4q' + 'l6N&UmDrM{kInufSaP_-LTpXI>}~E!PfMx8aL8gqJDG!p>X_V6@h!9xtBo|9zNUfc`WuXvZ8{2{*}mtOfVmquCT`sy0{?-J@2AeD' + '9-$-d5Er{>vUjt;9%cyd5I$*>l+;PN+oWid!TRdd9+R|9V_2tS^Oc9+{&7sNe0C_``*iQtPjrDz&<=xo^{ukKZ0I%X%6wwRne+|z' + '@J54;JRQ8~aL?w$Ao3@M`K%+hX7%p^hH3PLfUxN9E?`*Az7QZvl0_ke$NlNYAyB%HTGVy!r7iQizwJZU9i2E*QJ;$Tv#wnh=#C@*' + 'vUwl3%FBJZ5)3)}T&`iKZ~q}nNN4L|J>EFgR}X$}683Gv3}jJXH|n_8V?XKF3KWgKeLQxHek=()P!h)8}egt(Fk1SZJ|l-pJCARc0yy_iNYYXpW-g8-#g*C9|OTNh*oyJK`5fi!#y79' + 'wUh4m)IAuVc*9D6>v#WosScR||gr#g0^VosJ&8x&7Sh~|>9(DDx5L1QnZf6l$Tbd9HBNN|r+s#cL(=$kP-i>D7<' + 'w~YFP=sB1|+DkCKPM' + 'V-lt>C)sqOZP?U1cSL^i^$fMR{ZToc>Q20WuWPRIBo&conUxvtX7bD`^8Se9I8wYImhaYKGz}izlF0dT{&alb>*KGKj6lNUlt(X' + '!TtezR8S72(;y_iw_>b1_lWr5(TWa!x~b;g{n&HXJJI*)Ysl8>A8_nfo9%bkJ*#N{x(==F@cIT}iu<-mKl;|6+bw3d`!f~;k+}Qh' + '@*jwN=f=8}<8XVtW(v6cVpvzvA3y6k8a^2@@oL%$)v=Qh2Ck7QxW@R}1iX_;#r$YH*lt9l)gJ+A=-h2Kj' + '95eJMHaldNvqL8QTjbeysr565SZ=NFB-~#dlsX!tlVRZINM{Eka{' + '^b}hR+ZQBZwcy@*sKyvLUp}a#ejAGXHP;xADg4;T`e>Cca=LI;UA{*r+3>E$qNuKDwcpZ3vQ@po<+pBJFM%GQwM?L^@1)#{`5ao$' + '=VDvz8u)?Foxm?33hp#tfkmm`G`Mce#q0Wrjr^E5EgyIQuVa;;?`lpTB8q956`%w{@E0Mai7_U|jHT' + 'c#09-t%Y?bQzU@0`jAbTU^~WIx_HnrGld!X^15z&#{pfl&' + 'Tgi?UKl;G;K40K^bZZ4}+m4^oDHq@*>5PZ0RGrZ)HGtv_n3!(o0MstS&-yA>v?&SMt_&^K?kfhD4' + 'l?lGG*V=' + 'M6fXnjvgo`>}X?m1{~b(`0`GI8WxJj_T=ez' + 'r>|dp|2Dk5{tHWNeBLm^ry)YJiJht9(?|#E7Fk)9h~KnZAB%m@j0l2dUFx4x>mu6Vy|mzTzg25P^Q6``1?UB!)oD5L$$(-cgcaH0' + 'D9jbCB6X+)EWLuQO}K>lr5tq$l$5KbGEG4T$d->*x)g?*fW$rukOc=oQA+7VIE>rtBiH~NOwob8q5Q91xxuVc' + 'HPJ0UyrqYxkcd>d4~!4>Mp=#T=V;UApXzd1H;IdKMeRko{Y4S=`w%*#t9ji1V{bf7LHKT2pJGzHac;5W_8_dZIGlk@8;o)10)J>48ZOPoXH5)x=zQmMwsWA@RL^b7StN^X)z{#H@tzN=-+Q@' + 'Z95WBgrbfZHChk}$RpOFUAk$})>jS1WIvxP?3;Xhlfm$+L*+f~M#k^7pKq<&7~;QGLOFBj*0sljvoHv;)d@1x%-c}xO^tG$-F?#4' + 't|+YcDe%-`w;NYiXPJ8P$-i(h0|oU_KURVM#fBQ6Ui0H@T_<#@Jtr0`OxZh%aX*JCJlER@`;PSN?v4+VtV#Om%r_nDZ}Nhk0<;K}' + 'c1DyT=*XH13GhnE1>RQo{)2EA?eTqoiRE#|I1-SP;IE=;61(-Hs-^iACK6}Vr;ohw|VUp{(h`uzO' + '%Pr(u5@BdM-j;_ejP&aqdzU2DaM+J@aEoMb{SK~>fZEf8-|&GrQ317ntbigx>NUh%VKAW}9zD%rAH^}f(Tk6@+EvbL^)P7zeQz@L' + '-fP>vGYz_ta}olOvg@U$V$+;Ig+53A#d};87MWgSwm8+=(};%Lp0EuVe6~*IDUk-SX=v2U8' + '-EM#)gpV`gD|GIYj$?JGzb6*&7(6jd)GJDKsK3XE0omXk%~uEd0=yc4_g0&8>b&H7`|FSg-)!@Xyb|f|zM@LguhuDk&6gk5VHx`C' + '1OIbziT`?+q+yz#SJNqfAYKtgsC$pqZO2nj?4#!aG|`yLdVrkx3bVxP?S-m<8>N_S2Zjd*I}ky%Aw^rp)LI#8HD?8K$jIw#m6v%d' + 'zXq%Qs#N8KKqeVwc$=%PPm(ZM$l1SLcs!P' + 'SP@>&$O)b2eK(CAYh-G!GJZt=FZLhB2%1v9?6iA}?T7z{a!>DQCg16F#$Xn*gT{NNI;r6G>c^P>1aGza!pK~Po1&upxbPQ@_e>dyk1H4R9h*o?*6syySoqVLCz8^H?`Rq%_e~J$V)GfW1t(P&P' + 'fbWdWoC!T`t&-w3EUMna9wc6dP2tAp~P+onZ1fz@m%DT$Ylry6;_9b7xKa<}mKm2j6rF5@fe8;GjDe=aqVpZg%4h|EcE`=-E0qdZk5vAv>CgZQaTq' + 'fSIE1z^113HgXyxA&e)kR?v``?*9)^O9KQH000080000X05>y5Wta#60GAs803rYY0A^uxbZ~5Kb1z|ZbY*UIX>V>XUu$J@Ze?s=' 'Y;R+0E^v9(S>1EnHWGi=UxBg@A!j6}Zm%~t?&#)xtv${)wKH~lFBuO5k)VZ`0$2dFqw(l}zg>KgAZ07g-P}X{kVyDgEOvkU+g&J0' 'k}IZVbI%AB6}hJkuc%?S;+CAzl94;61Zy-|v05rd_@fDsyNemk|eWPt>lIjMctWBv7+#+LAsEJ8ZL#N&HUIW' - 'Ek*b&wcFWJvu^k*YQc|#ag&6Yf@nLzDHt5_*FEpJ%0' - 'a1w~>gy9>wrY4{=;X)hQz`VU4iZW{JWa6LKCzyzQ3!DC+$K>oU-yPXS>~qOlL*CeRr%Fjhs3r_Q3{n*3nvpAavR#z+b5{FzgP!E9' - 'p?ZB{3=?S1U;rIHY+zi!t{FuKzz1BNbCD;##Ee*rS{=LO-t~mB3gC|uv4yC' - 'c_%y5ThEQ#Gsw~&kOErZVnvbV%3(3h>{37*DFK!al3b9%D3O%Br3$b7;pm3ECW-e%j=ClDgByN$ZbKINw;oi;xwe|7(Y*DZGfcm3' - 'Hv5uKM~5QPHe73jw}bk65`YKo>NF01M1ia-;C^bG_TD_UhMt3_t<>B|KrqopSsz!tG#}v09E0lScpu_c3JQc62bW>gg5I>!gM4rt' + 'Ek*b&wcpxOvu^k*?0W}a-Gte;c%~me5!=~pRgy9>wrzW5?;X)hQz`VU5iZW{JWa6LKC)kL53!DC+$K>oU-yPXS>~qOlL*CeRr%Fjhs3r_Q3{n*3nvpAavR#z+b5{FzgP!E9' + 'p?ZB{3=?S2U;rIHY+zi!t{FuSzz1BNbCD;##Ee*rS{=L>{5UmDFK%blU$I&D3O%Br3$b7;pm3ECW-e%j=ClDgByN$ZbKINw;oi;xwe|7(Y*DZGfcm3' + 'Hv5uKM~5QPHe73jw}bk65`hQp>NF01M1ia-;C^bG_TD_UhMt3_t<>B|KrqopSsz!tG#}v09E0lScpu_c3JQcE2bW>kg5I>!gM4rt' 'tqQ|Fo0N%CR-pMZ=}f)&TasmQ7^Iii))aFm=3m#B7ry|qD`%kWWuZ|gzYDr*7%`II!Ba>clgD1OT{1$ucc~3*Z9`j>ERAnCCk@vo' 'JE*|eeVa}jQc7VcK$H@^la-MtSvZf1Z8|yAdT4H~pIM~a0YrJM%@_1Iy=Smzq>U3}by~5Yj$D0WWv|eN8h{V_@`GFb?D43(oyc#Q' - 'NfU%m!7DK7av5RiCS0>&BK5*E^39SQy`16d12$!!0sFw(K;~pn_;FC+8@2lh#yZGgkFgHBnDk~*sQLvTzPkx=BkQ{1f)yLUqT{}m' - 'l+Bd;Aa4NHlq0*cWLlF4gzV?G;Uz~ME3Ofml~vKKA+%d1aiPnOHWVy)s{)d3XT9*493^v-NP+(v)iL~k%XG4fbw&*Jw~;z$;+`vr' + 'NfU%m!7DK7av5RiCS0>&BK5*E^39SQy`16d12$!!0sFw(K;~pn_;FC+8@2lh#yUu0kFgHBnDk~*sQLvTzPkx=BkQ{1f)yLUqT{}m' + 'l+Bd;Aa4NHlq0*cWLlF4gzV?G;Uz~ME3Ofml~vKKA+%d1aiL3(HWVy)s{*oZXT9*493^v-NP+(v)iL~k%XG4fbw&*Jw~;z$;+`vr' 'A@q!(8wPulEQdhmZJL1bKLY>wY{NvrVv?7PQrCXla5BmVD$*H}+mwO68<_@zuvFAFB#S|YFVB)U0O0%!wqxWkMM6MBYX!noDpujFvj@B#{0agOWNG8%B' - '2czwB)PsBsHDrL<$53$F07fhXMUbTSsEyZqHgfgrV20kfp#=Iun!RGR3$W?UA@nq_fI5tJ*Bn(Ak73;|8L@5{9%>aR8Uls*!o{7G' - 'PB7_{Qv-45&AePXk6O6na?cexNkk{{5$kI*!cK}#zF#V2y1OaR8Uls*!o{7G' + 'PB7_{{R465&AePXk6O6na?cexNkk{{5v!|*!lQ~#zF#V2y1QVRa1vC5S(xy>V^Dk4_WFlDtIs)?YtyL%@ffx^L{UN2VzTg4}4dBy9R' - '0cxRk8aX`c;Xlav+Bo;dc`l%PYx#;q!%+^e0UiLmy$F9bC$I8Pl8bauEZfCl`%J0|MBqUQ#EmzljSr3|7w4Vf!Tlrz<8j{IVo(u9wR56' '7%GdKhGFxxZwRiyiqxs_5}!okU?vg3>B(2v@ifp;+<8Pf!P~e`4dZSkw)ana#`JB9z1Mz!5-w#4bs3!rtxA03y}SpDN33Yey;xdk' '!`;ma;Qh6W32KZQF)COZXTjq5e3m`aGmj+AU15(M1*{rPK}SNB(O9sjiC)w1fBfqrc}c<=y)mJMZp`%IUWV*PElwMMz8-eSEX&' 'jD7Tpo@_lv`U33Px8VOS(ayu;@4+;RCcySENcq){Lr;76=Si0Ra~YPd`%l4kO*9OvhFj(+tib2I^qJSk_}y{HZ;`wlcma98hs#&y' - 'JnSSZvY~?45dNgawDt@k&!I9k;}(eFAs<}%i}' + 'JnSSZvY~?45dNgawDt@k&!I9k;}dW+S7Yk`cSadB&7Gbfh@PT80~T6}W((URG9#UKx_Cxi*o^8$`N{#}WF_wzuyaW}-*O>?!aP!l#$wHI' - 'I`lFc1B;75rDMXZ0=&m2Oe2r=?uf>mTb2_r3@b)Cclng_{4WXzxpu5%Hv11yO9KQH000080000X0FIaox-1d^08c^y03ZMW0A^ux' + 'I`lFc1B;75rDMXZ0=&m2Oe2r=?uf>mTb2_r3@b)Cclng_{4WXzxpu5%Hv11yO9KQH000080000X0Bi+0fGiRK08l~z03ZMW0A^ux' 'bZ~5Kb1z|ZbY*UIX>V>XX>N37a%N#;WpgfYdCeQ!lH0cRU0;DJ50)}oPGV1AHlt3qN!(@b=JWY%wyVpdh~r(`w6%(3' - 'ksYhDZiFm~vXMAd&t~RXQ`X6WCnRNVAnQnO6FcjLtg5Wo^O#**Bu!c7+P}um~lyctEOls;*TU*MpgbQmLMrRn_I&Ov7WD' - '$ADa}jhChSRYl5x5?Pc*mcTN}K_$0UnH5bBFy6Xj_ljN>L5yxyU4W>>yIksn0|9wEz&Q+E' - '19YIlF58C}>9)oz#d(?B+8x&F_cjBev`AC{Snh$zGa$d{9y?f-^_{F!rVI#qo2yHf+Nda^7xaM9`n^^4$-cW$g)VEm;o#%9GH+Gr' - 'MZfqF1koQM{w-dXg<0`){_yvIDRg51eg63^;J(+Fs0%A68%?Qt_*}%bJ7G2U#hCvDmJ}VI}gFNS8FCR$x~O_$tb_soKVwK!bA{pDrVDb5Qsk' - 'l!_pA#)^>?GL;o*xoEZ0up1?EM6rgC6F?RjngGzN{1jQ*@UA{%Uayd|E6Yb0&96p0B)paa7>LAXeXq+3msyKlUdm>{yox|~7IX7%' + 'ksYhDZiFm~vXMAd&t~RXQ`X7B%t*@IfYy=RCU)8jSyfrF=P|psNSdiHs(WcS^nKB&dM6XL>Yu%k' + '`nG#+u!~~ZHl>}um~lyctEOls;*TU*MpgbQmLMrRn_I&Ov7WD' + '$ADa}jhChSRYl5x5?Pc*mcTN}K_$0UnH5bBFy6Xj_ljN>L5yxyU4X2_yIksn0|9wEz&Q+E' + '19YIlF58C}>9)oz#d(?B+8x&F_cjBmv`AC{Snh$zGa$d{9y?f-^_{F!rVL1Vo2yF}+o&j_7xaM9`n^^4$-cW$g)VEm;o#%9GH+Gr' + 'MZfqF1koQM{w-dXg<0`){_yvIDRg51eg63^;J(+Fs0%A68%@Qt_*}%bJ7G2U#hCvDmJ}VI}gFNS8FCR$y2P_$tb_soKVwK!bA{pDrVDb5Qsk' + 'l!_pA){2o7GL;o*xoEZ0up1?EM6rgC6F?RjngGzN{1jQ*@UA{%Uayd|E6Yb0&96p0B)paa7>LAXeXq+3msyKlUdm>{yox|~7IX7%' 'z7k(VU(huCWNz<_7)D^%vH}T@L@_g%i=;#{(@Fp{o9w8*tLw5}bnj?4A+`-!oq@qDz$~dEQ5R*ApTylk6%HruCgwhDmgj=3TOksT21jvBMzWq;;w9Sv~E?78j1F3(gYts<#WUsbxOSji34a9f&sEL72<+KX}yBqN#3To4u@!>EoudZ' + '_w3#ik%08*i@PjU;>gj=3TOksT21jvBMzWq;;w9Sv~E?78j1F3(gYts<#WUsbxOSji34a9g8{NM72<+KX}yBqN#3To4##MsEoudZ' 'PNld}OOa^y{ObA!zFqeLP5k_qm*3!*YtV#oz_eI~=DfJN{=pmX1ykWU-8!%5Lq&T!e*?fwl*bx3?W9BQa2)`NA;O2D@$A#1@r=+w' - '{u%31$fH8;EkYQJ!2Bf}`za>8cHkoRj~WNMLekm^M$gCWJ}bH);rdi0;Qov34>jfF`$U2KbJFStqB0gz@USr01PL$b(M}$-JX5H4' + '{u%31$fH8;EkYQJ!2Bf}`za>8cHkoRj~WNMLekm^M$gCWJ}bH);rdi0;Qov34>jfF`%HoSbJFStqB0gz@USr01PL$b(M}$-JX5H4' 'jbtItYSG7dJZp{wP22ORhZBYTi=Nn~A*=21uIvkXO)`G5sj^!#H`1x436-JFZ)@dbygg14o25)w`x+k#i>(6|e' '{5AUWSs!Ge&bPU&!R^bvNZY#8P4tIga9W~TB(4lBqRdH@RfFDG^lmtV2S-_CJEa=|`>D~CfiY+@TuU}0SHMLI=E|J`h8Q$=6)Jxb|lw09$txOsB5lLO+X4-a|f57_~RRep38Y=94p@15{mK^$Mtd&e?Irn$c@s~DJhvov&hRVx$cK(v*DkfCO7HUWWI&oP&p' - 'Z#FA&%rLG35#v1bpgfK}(G0*KPZ=1&Mv*IQ#tcf4N8-C<)qpUo-#}$pOfXXczDAS8=;2+Os2&4<5k(Pd`@(MQ8Ym5Vm5S|&;Chm|' + 'Z#FA&%rLG35#v1bpgfK}(G0*KPZ=1&Mv*IQ$_z@8N8-C<)qpUo-#}$pOfXXczDAS8=;2+Os2&4<5k(Pd`@(MQ8Ym5Vm5S|&;Chm|' '*}%SUFdS1UsUe`&96)k*kc^jinjPU0RUc7iPs4Cu{QMK3Et5mPTQ8hruLlB|@_r00P4WQi>@t<7&H@H*tz@b>6@Y0cj^81ggtS8|' 'MlMwV87$=2q%UiVAeu$^bwuTgm>?g)vTVSu6-bOCRX0BR}8Gl~W<{hm%Rf3c}?UC%|z}7eB' + 'Nc^lI+sK--K3)9Yf~xJ%00FioEL7j2oU40IuC4#+nTFbqhDOLFQ5D6h9hRA?G>Su6-bOCRX0BR}8Gl~W<{hm%Rf3c}?UC%|z}7eB' 'OK-uk+@c}!?S+%HfTF5;0XqC)%3}X~T?p#L+WR#gJR;G0pgfLRTRn{?&1L}Whv`#@caIHq-}_Vqh-B^vr$8uiIoj^S#`RGKwhOP$' 'g-y}ev@X3TpCKYdN3J?$`$Kc5@V|I<0y9&9A+5kkLNs!s)Q}aL3dCZY6|z1J;HBkVK@y90#FRQQFwVWg+ChRztH3LhPAT!tFW+8?' 'J)jW{WHeYKT`WVA9N_G1_UWX3vWon{LjRz^yFQGe#3mfz5Mm`h?~AE^Z8Z=aXp{vkGOk6r#i-69pDirHJ6pZ7OUy$giR}bX4hy%3' @@ -228,23 +228,23 @@ RUNTIME_DATA = ( 'cK;*hg{n$X6anwpfP;S2Svm;Iz*zMd{>A#!v4uL{9`77?K(@L#&4xc-;M2!E{ft4OvzNRAhyq3-;mmI!W2WY58RkHi|P9+*MRvcW2NvG1D0;EgzZwSb*;MM=@bM@qvhh@rctb0sg$6_oW)g%X$$R)&j>KH~@32AnzX>6Ir-1@mZfw' '8B>40psPgo*mG5NJzX5@q0I*VnTwo%Ri-Z1Oxv6EGmpMn;s2Of=$&{<0pWat}4vXkY>pNVg(1@o#|r0?9q`Mm1p~Go8?(^;uDsyo;~e*pH3h;y*E!pVl0)?))pZF' + 'Mn1_VI1BWo2iuO=jMLcD=Kv+oRsBSh@lLSoW>4vXkY>pNVg(1@o#|r0?9q`Mm1p~Go8?(^;v<%?o;~e*pH3h;y*E!pVl0)?))pZF' '(J>#K0k?0@G1xwKT!9kc>_r@wzL*`b9ls2gQu-BoAK@RLyPQquV_bk4Ojw3|!I_k7$`$n4*I)sRg1OojXlFlU#&H$59dgz{OxQW3' - 'dS{QUVXew)@c^5LmH-G!pe8e5s-xHH2qCBI?41*sk8Rzs#kHpu`4{-0QSHTNN)' + 'dS{QUVXew)@c^5LmH-G!pe8e5s-xHH2qCBI?41*sk8Rzs#kHpu`4{-0QSHTNN)' '1C6!;P{0dY8vq*`Cyu6BIQp_1NICe|lY+Dl>jX&Iw4);nxu&xRCX{F)I+uWV>NjM92A{|kGQNK0rkfFNqC9Z4' '@tn?6AX7GOb%v6`=_qhg-;K3$9FTQq!JbcGuOAjdJSH9pnE3PI%xYL}pE{(z7&(lUQKq#Gwn!4!^nbaUjS2R@cjaSNbS4i=uITX^' 'uBsT%bH3ycpS~Hw;;QWwqbeR{asX%-CKXQbv?A!enh3wWpcD%Q1EgRi&eq*|li&dkEQ{6l93URka3yw90Uy<`91>|*7@!kIWYx2M' '0S4{$G1JK#mxpu)!6^h2KCFz9o4ERX4-6Q($44c@edD`6{h1pDJC5hZ1G)R;PCUXmVyQmW0Sa@GhI4cH_vT{`N{rI6Yr>qIi|vi3' - 'UDk-+icjJ3(IIn^w#n#R9u;gnk;k4?ANz%Re|8tkQ6V1)6>LLeujt);*g>A473?_h^!4149Q?zq@?(3<^TCxV?#N)&BfT3ITWSMT' + 'UDk-+icjJ3(IIn^w#n#R9u;gnk;k4?ANz%Re|8tkQ6V1)6>LLeujt);*g>A473?_h^!4149Q?zq@?(3<^TC}d?#N)&BfT3ITWSMT' 'bcV|54!nEvtiM=JY!k(WJ5%X#g4$0' '#K|l^GB7xwCWPGMaQ9~+Ow&08WZJz4^?4+3UhX&v%Qt_MW^eV7X`8ZaHvZsK+1Azk>E{;$XK=>n7Di9PvDVoFw5J)~W|yHd`Q(kf' '#l&39e>hI;4n*=#y%?KrJ5bZWvB9D1Dn;wJ(Wz%=4}9~v`GIuKN7&B0LlO2tpu@|i)4(-tX>;SsYRF~KvIo(m7cC?O1>PqbzGh&$UN6qr9(Z$ZLukNrg;8XDW%yoB|pU&r+;Fgb`Q!j}vzBC4YWiIO7)ft|~Cr|Gj&&P{2-#1)Dqhs6oiO=z3M^>1`' - 'I5piMK5zknSVAX$quJM9{#-|CrX}7{9)@I)AHvV{BE>XS!lXAd)5v&4G3)$=PcTjOzD0G?_V6{O2-xzN4a~{7sAaFJh3{A_hj;k;' - '$`%#yd&kkcRVwmqRbCc*jb~2e;9|D*w??SWm3NlrE^OE9a<5Oj-3@!E2Q=E>Fhq2}g3H*Qn4w;WP~GD-H0|5~MM1&aWX@eC8$X6F' + 'I5piMK5zknSVAX$quJM9{#-|CrX^ld9)@I)AHvV{BE>XS!lXAd)5v&4G3)$=PcTjOzD0G?_V6{O2-xzN4a~{7sAaFJh3{A_hj;k;' + '$`%#yd&kkcRVwmqRbCc*jb~2e;AXbFhq2}g3H*Qn4w;WP~GD-H0|5~MM1&aWX@eC8$X6F' 'kD)NXs&Tn#}wbri5`@zp-lTgB?dQPQz?=E?2!U%i!s=q8k=Ey7(celoZH*UH+M$8VpS9g8_' 'UD4se+{=%9(yhhoPE_xX)S>p4|0|XQR000O8001EXuApMdcn|;ps6YS!9smFUW?^%5' - 'aBOXJFJ*0FWn^S&ZfA2ZVqtV+Xk~LQaCyxeYmeKw@q2*$2i5^vX{!~IYfuy}d}wmrL-WDCLy{|eFbqYu=rtQzawK`3z1MH=%%80Sc+2vtU=8n%O~vY_;0?#0ttgKCIzQzNXJ38$9qV}2)=isECX=U6' - 'o<7+&^`2$f_6W;(mNBtE)J+Gds=CWNQCBTOsnM=(HaoRuQqTm0' - 'pI7-UZxmROcloBw+ZIly;LK3Mwt|<1g6!@OqPo>Uuc~`Jj^yg$_jGP^O^<1=QCnzR`5GvC{0CmICf_H>`($wBT>o4CZ@56XYF6LzrWHScyyRVnY^f`D+vJBGTk~z*D1l?5+JaEQMQY(eM9&!P5v>ze#&V#_' - 'l+d+6rKq}So_2LqARlIQBf!<}&oAS&J?^7I>}N0Hbf15Y;#72eAH@od*xH!BWC1Ryqu^hYiiszG)>(^+X?Z3wZ{W;9+abl' - 'Q?woXF@z}jnXN@#*0*Alms)8+N8+BrEk|+BcA@~m$=VKC&GmJ)kt79V(*@D&PvpEk)lOM=Jd}K?AXu8FEA|szk^;X9EXzby@Xu4^' - 'Eb#aGg#u}TwLAmm%e+0-AdLX{bzZiJmgGS0*h|7YIvazH)mDsFgB8L5Y(6p$0&=egPomq^M^M^bUfrVD-yzqPL6H(MU;~*Zo`W_z' - ')G?5?=3a?U!ixUt$BxtAILb%ev9al(P)tV9!i|4A3b=M9=r$9i6lj#s*gx1VZ&|y`51i<(6zK^&?La$N#kOD>>r>0Rol=}Xv18q)saIOASMODjqS}0pN5jbW+)ZY!5+gqQ8-)Vi2wxTI;0#VtgTslC`H%dA^?}@aJ62szSpFH' - 'lRSdT--k7bdv8yuHh2h=hKVD+g2n!q-qT7c(G7)W-`4!pURC}2|05lQu$DVQb`&hck#v7E2cf`eA86B>0`M(bFnJVV)C@Tu$z2kU(wHO;nF!}G8{ogc' - '`7sK~Avi4XE_rjGak!3%M1G192uYrffR<>|wd|epHUkL~6<*hj-NO00u8~UC&BE(~L+pz|Jq7+?q-d&T6lc!en9R_a49K$ebPmGd&ik<%UA8g9YMCMc@dE;iVj_(6' - 'h#(l1il={}>S+lyrvWKMH*8@7d4%B43IX~l>;BXz?MnnoTNV+CmByFEZX{sgO57P4@6QasQaRv55l1W_{B6Nl+aXKm?Dy;!73+c)' - 'Mb>R-`UgB!7Hn>h8-cv#6>r3*_jfV{Q3BbU9UTm+gbO5;ykFZjD=(yU<jsOv6(JibCtUj_cfo6c1%N-&izG0t>LFm|WD>_IL}|Q4k%vIdk`{oE9{OZc)cRYxfEvqi~Nu>V9XrbZK)o9Fq2PtMSo_-HhtMKpU1(VdV{So3LCBo67cdJNQ>_pHo' - 'i7KKFX~%boqYo4cG#s+HAkT3slLpWg1a(%k6xfdEHmrx}oe*gNzy-LaJ}ItEV(;Jz7G0@cmimkA3sOd9p^S-VjEptt(G|;y8J(5Tg_rY&dJ1)QoG3+oBa$7Ew?7Ch?r{3)?$)y@&1@17SEd`Jt6DXp^6e8A1o}`kQx&uJc?&2!NLe;n5x)wEFEM#M`?0=ACkL' - 'Df^}>?^zBj>g#+XRDg_Stx)RjA|s<5c5S1U+U9l~x)RhV&+c8yKV?lqiDZ743krsMZ%KZB+i;HDG{)Z1AEjij#f*j#xvhcIpg1jN' - '3yUOFcV;kcUuL76-V^6?DnM+mpoJ_%ZbQ9|w6BTa34B1d?MmamUTbR_IPXYXF' - 'j^F#sfweK-(k>x<_|UKT@B!E4mJDC(gs&8P(WUIYVHOQw7P%>p1-CJ3Syj41nI)j%4Fr>rD~g7h;PE61q!XL~&kM|>il#nb{nW;1' - '(qG6eG1ugu^G#=VNeAJWvOF?How4UuCUM+n4iH4#bp{N#Y`Ps3j=~46yh$fUm-(hVOfUwoqbML(6wQnZXmLD@2$T>mC;g>PPy{Wh' - '_+w~WH&n~c1sG;BGvMTU1}U83#}KW}2$%uPcm+c^+mD&SnRP{1SKi5Wjtcwer(;k3u$n;;yjGM(-Ufe#+n)+TMGlR3FsFGPWq{^RCe>b2N9eE@W`Bv(D%l+F$Apuu_-wxd6p(NW0hh=HnVI78qbOFrSWj(SX(4' - '`0W52Bid;|*nm`H=OBeMxrNa(w(P`MdqPpa@T1)`Jn$miKpC^=0iZ<%c>s&tpgS6g*!qyj^PFBR?$PYR%B?IiW}e&W-F$|Q&7Zc!' - '(>bZO8al4;W!HqrR&@(lwj-{2A&rG{=*+_o{k<-9gC8!)|EVCw!ezYt`ks*p__g4R0nL0E#kdiS8@(hUF7+3qFHo2FOlvXXpOmeC' - '`h*4;5hPo$cCBVkCq^ePYr2QJ6q|dBVo;aJS;BeyAm${hqCTNZ%~>b*P8Y3D;pK0cSzu3&Qt&=6SDxv>8|eb<4E1!_T--R`*uikL}*5V^!!c1zAfaDZxU`M%QihL?C!TUMDr}kKOdLcpkNAnhMcGZ$g7XL3B3(Lh(K^apvrSkro-b5$y!bVXef&w+hhDPM&l9tCIVEAQRX5cqL$&+#*K`Ua7K7NeG2bzMg|A*)B}$DEEI)Bl`(Cs' - ')bz(;1fZEhYSge9w@%`5HhHH_#vxUWI()`(w{Fl*&b7UYM$fZPujT!@vZjX!w-ElnM3M93-lT' - 'roIye*Y&Q2`o^QDc_6kx>>?E{K5@&DI8BWn8zsiB=nkpb&=(=p!OtODCG6%?k}$uBjBQ!x_`DUoIyTvU4KI!Wl{1a-nh*j<$c=|+' - 'uus0J@e%0f$lMp!$s*nO-(nG~zqmNf%QA}NN3zNhC8Ah!OX6p1QDKi(zn2eljR=dPUQlQ3dCY!d&d7`Sx1kaHR)(#nsbwCzB<$kT' - 'LH$Slplp%Jeo-(!TBH!{?D4pN8cw1fEM%K)`Fw>ySaWewlZ8brQNEwf##_qY-Zr6U#!l)fRQJnM~!5E9(J^s;r+>=j?|sc' - 'tnkXv{|mOA*@`ApFv0I+<85__t8NrB9j(ftdMk?ws(1YL7`(h#?mW^R^wArU6' - 'PA3uF7y#a-{w;9?H%Ki2i|B6jEl5DnfYWwCDlqUhNO7bdx2K+mw9@uT8O0h4?Mbl5e_lW8WyU^g;6i3GTlj-' - '`p@bif=bNw&h{Aoz8-L{XLgISSR6MT+pg8Mvf;7;9C9$BI4wYg^VL^@WE3' - 'gGvVFgXa@S3W@^tgwTA&Ull7q^7FfgICWyPPu%M0J2I?;^Y^N^a+>f)Qc9?nN24T&X2#>' - 'B^9~Slmx3x4z%oQ^ez`8>C${;+)BP=B6BhE8g!GTrVNHJXJB5Z&OLRip0~)k$O;JpSd2{32VviPx&Wpe!qFmn9^0bjaMQ0Q+Ew*L' - '^-L1Ejn)po$msS{6maY6Pe2&mrlVEZpZQ(KNcDF_k@&41wOedkC45b0b-zY>kKV51T)<OVW?^%5aBOXJFJ*0FWn^S&ZfA2ZX=G(?bZK;XE^v9Z9BXsi#__vn{2x&CLjaXx' - 'Z1*u4w(N|XD7D+h@mNYH9gPNtz@vx=1W-IAF|Pi5clRE+CrLT2zKF!_?d|RD`xcDj_!~B`Zd=x6Ka@pOu2?;k!!BAkUBtH6Y_%%u' - 'o2abYap-5!wppPf^Ei&5KYRXc-8I`N%huyCb}Y-Ha@#iD0I+p4u|L*M(f>Eo>>juGlR4E^k}dz$`jv{~lRgFfp;s2V7E4cn@F2$gpc^$9&**y_3&zS^4;a)qT^cjm|lL' - 'o@tU-~T~?z`9IfIFp+GLrAI{%?|1MfazXhQ0' - 'zW?UzAKCfE>x;AO&Fk|w+1GD=c=z}0FaNqYI|q=Le~3<^moHxZ?&Rdvb3(gf>nNjR%~tHLELcKc7P#{?I{h=>_neO{o)+cEy##26DskWJX7*K|rI=jvb5Yv^tqkib3' - '|3vSaTJEQ4>UDVo?73keoI}Sa%%YeN#4`dGM5AKVup`ikD2Z{kI)hWI*cMhLiUB^}_gIh>>pQ#4tC49G@Ret@lCeDgm1_>K&DXdC' - '+e>Iz%n*+ehnGvYtgGSl;f(TCKIDlYZ=vyt*tRN*;S!$Bkdap=2Z16nEdm8c)_jWB6{xkg&MRbq{qVQ(qkF!bRAM%&#yanZw%Ydd' - 'j@^Kj>UL5~fLTIx9Z13mQSWQV#)_hn6)~yxO-?&tM^t!Joliv%$9*m~l9fBze-B=?u5N1R3hmJK_Ebe' - 'swRhj*ANl-q=+U2txo|q6IFX1f8rd@GJ2F{@CnS@SV4sKIQI#jL>AtGgAYGOPzcv!Y*ugu{5>>' - ';pULt-sjy7ZcI8og?MH{2nVR|04-dZSDeT`+vaTmj|b3$jys8M{*YCyz8N+FWDb}IRhv`bmh})y2hbQG9uz}2)&<23`>@zhCL*Cn' - '=mGDVBEQyR3mu6XH-j7y@FK4&GGYou7hG}7mobfuRSvH4N_bp)l%x{@uwC?82guN%_7kH4dP&m8O~SMRNuG>a{A7vINfk`Orr``@' - 'r3r@^z(b;;@P14b-tEY65qDvN%T4qQ;(-e2!+oIhnXkre+b0t;zk3o(=(30Y4C10)zr5(chG_!~19`BM{W6JX7|JaSZve`LnO@!(' - 'Wx4z|uX;H-okQ9MdXt#psL1)|!lq1HNaq{&uqtmDIF!_Dj#Q*N#r0Z7O5ekA>q1h|H`N_6OwGiD(Z2K)%9449AdxxAK|m+~i9#;i' - 'gLbOFjy~k~r@cr&I8rYega%)^b1nEA*e!F@SwnsHD9$X9zaUh4>HJ@k-3K!Ffd{_{>}G+26?O}1=4O~tsW$m>-J' - 'ht1P1!Fu~7=)XLL)XIt=a2sINUQezhQka`%C?8n{(?30?a{z&)PG%gM&o1e9d^8ZV;(G)^KT0gQRMdEe_#3K--{&GCFbka}AsR_=)5N' - 'Fi!M&)K&5tb<$$Hkt6OPZbHu_FE{4=iG)o|ZJ79k&dlYSkvn1~1IsK_(a?760A7oE6(z#gV}bOqw`b^!H~k)khV' - 'KUh?bdSAWd(Y$b&$$N@Y' - 'l^wOEWaX5X7OQ-Fy~?TT$g4c~>GH*u5gkJkiHmw}9bz#Q`>q~T?ka%nAXkRAVmQ%eqc63fO(nN++~r*_sImeop}H5lmf4DHBjNLn' - '%v$3-@;pKQrpWX?wQ)f}UZC%m65e|R$$|(c{}!OmNOu+MiIq<4Mu%lpQRUx1*C;@UFp)5@bz0@Sqq^>1_HnhcGYh-@1+E2m9F?^nr)1yck3c8IVvGw`~Jwt~?}OMHJOY{T!6yMR_-rYcK~8t5g-177ayu(yjQkUMBNv' - 'ZcROBI)4e$sGM_8UUV`@%tq{4C`l&sTMR~3$w5rBu(Yl90%00TQ57A_Rwhiauk%!8`J;|T%' - '=obg5(kIc@p2i*}*%p=~&+#*-`S=o;Z!qYdl{cHA*SQnM?Erf?E*$T12pwQw{!SIXZ0H!9vhFZS5BMG`ug7{-9fj9F!?`C@R`|LB' - 'K(@-n6?L&0>)Ss0aUpKgXA$1~G2YSxE(y+ytg`GjyX0V+UH(_es#WwOI;g6-;So!7f4a?o0^ikiSh1IB%Qhd3BKmr)DlozAPB+QF' - 'j)0hCKA%s)iumdogclW^#8t8!}t?Kpw{S&-|M72Z?IEn@@O{s)xPlJ%yfzGHWIafPLA2<0I};&*y{OCm{E$vb@h_>rs6' - 'TtKh7ntk00Mp;W-%dj~|J)5(sEnjYuR$P@uerpda&w?TFitVEY}=DOxZkN~w30T;P6KNmplmc~j@$3+^{%rRN;>)z@}}y<7tY' - '5+Tfv@)3}BKUhzD7;$l7R}4MGi!WWvw7J<7KPJ' - '5$q%)3C_!&P1$#{j=!VWlWzlf)a{~ANW!nWM;RaI;*g9qO2yx0Prll_&OZ@?FXV>jYe*omtvJ#Wod{8#@{C4A#XidZ2-4p7k*BI3' - '>k6bJM!A8VU=Gi^)8@V=(V`1VAsTZFownHxquboTCA}vzRn>U#b9A7d;a}Fl^`tJYN9%jATi}?E0lP6^lI!`{;_Ylwu{z-_gEY6v' - 'NS~6gurCABk2E+LzNSA8dBPANgKNCP>_P!}LIIQl}11&BBbPXrhB$|ZvTMulNc' - '8ANPtUIK;Uqr7Ca#f>Z9ii)fx=Uf@%aaf;zWf!_!y%f_AD5g7C&ePeWby}qYfB`3`xBg28@IWX7Ed=%$1go8AqDyHZuSA^n' - 'uJfVTkR>H<7*FpZmTK;uL1dul-Nf*kU@cbi*Ho|_T;0R!m$CF&rY4TUW0lHhyJWTBewhT}18uS%#AW-BtQ$ixeG@#L~WrD2K|Rdnor5MvovhFd~bzPS|<~<)NrD817M+k1Je5?6_?)x_cV8C{9$o>YvC5vo+=LazN>+' - 'bwI)^S`FC7bV$**c{aV6a^O|7nbF8?JJ5xzV{RGK*#k^F#Ev7dWU8@kKY)u$>!jfsSfGu@9Sro)oSc|iuP^D?4_MfF=AXb{S;8|4' - 'zrf|7GRkOGzGEG+yX_8?Lnq75C(R7!!<)r6ijUI_dZB7HZ7EaM*ie>nI?6d#a~ykiz(Jy6k7VEt+vlmTSU)}4_ju(1}xLXtV-WR_Kz>O&z#5G<{OSe{2EP^cPzs9Q_yIa' - 'tLT2iY9H}SSADER+`odMGg6c?nuzp`uSJ(8U64E6_l{OE_s^inMfadJxUR;G!q?13%&WfC(b&SEvKaOiKhH0g$PTfIsMqi' - 'kB}}+p^(4n!>dK|w^aN%xj>>c24wYf^`24diyL*Y' - 'kl@4g;Fu4A$*KS*d&r9jjSco$V9kb>KBZsm!UE>LC|9O#PRULbb5uX^rfF_H&GA~^EkVtm0`VI%>SET(Q3G18-WoC}(ACq#jH!|i' - 'nVg)+n(97lU?K_rl7V9eJp1N)C2AG$jbk!YNl^a3%)l;*G}gD13cYHUlpW^sz(I9Ok9RVtA_RZYEpeQkKd0izIhwSYlP$o8JUFx?B~z^3s6e~1QY-O00;m8' - '03iTyl-h>*3IG6aCjbB)0001HVRLkFY;AKdWo=?*WMpY>XLB!UZg6#Ub1raswOVa++qe<_o|*gy96ynFG|x_7=0Lkk5Q^(2J' - 'x@$8Y4F?h-iw)lnApKI;|Gm3-AwdX|owW7b#S#fDb{Ee+yI42~g7cb}w8;O(NX;|GSw`}z=~_W(m64{P6%3rE?5Zvb' - 'wvs{;x?CCAr!C`jS+H1rYHJ>o5434O$CzBQ-#b>VjEIJ|xT2Z*3BKw#?LAH~FX|h{i<)M=NeR4X!n~f9OO|CY{uI52gI>PK*qVU$' - 'JY7@K(hyggpmWH-kh|q3I8_HWTAw@3QD;T?gQCAB!W{rqQl~VBdJuQTujS5L4rPWbVH>?T+9>fG>)~Mq&=-TzcPeDYefNig`f@zzT' - 'WDYArBU=RSk}+I<_@O3q>2BE>72Vuo1c3Li!o}#VW}9Pll-&Jt8l!LQRLBaP;e^kx;mOHjI$EaKnR88sU3Wp%G+{{D$d7yhQ>5+VagzZL5QLx;Y!zx)#?C;S+_W_~Z1;3n' - '(=*3G3=9g5VNon4M!Q8yiz4af0`iF*O{P=w>RIdHaNw>;s;0gjEes$Pt+0=Cr8tQQI84UnEEz7RO0*7R|_;RqMt1nQO_1Ni4H<^W@a?7g~XC>g37BJ(QiPpxZ1RmfmZ`0JWTd-ms4_}pC' - 'lfJgPdLHT)T7-n2Lom2Pl440$bIR{0eRY@2&9iqo1R#`0_p0>h>y$ks9bSfzUFUbx00XWmN^zikb6~M+|{P8^OHAcGlPK8Ng{y>HgbACpZzksAfGNyKb%~ACV!iK' - 'j)^9njLc{Mn3MC5@c*x8X9I}G&m+GN6~e$cUCbw9Ltp*r&vxCl0`kTYhr=UGSpXwlXetyT(UAd4PF`;TGGptoYBUBaG8vbP3d6oK' - 'tH5nR*jHty#Nn5AOd&oiIPmqb1`E?LIONN=hJyvgzsVyvz' - '>@ha-)zi51KTOGyw;Dqu^iq=9kJ7_ef861-U2##;>OKrk&o5^ebAG`{HyF(NDzg8V' - 'Zm_J59ta;FlHJD8B0A1_UQmq#e)0=<#(4c%AY(h_~m$h$$AW' - '&MQkTcDG_pOWshyX7VHQwMT2OJ<`!G>zP23TeLq4rDVl1YuIMS8=gy2)RH#rPJIeJxj@mU{5?wJ9JUe?q2*?oQe^hs&O?^10IRJH' - '@}Gwkz%F3ryfhF~$zNdog0rN*vJsqgS>~=Wu0bIBp#_exkD?$*AL^#zU_X>TzDQE7@Mo&Sr1teZsU-pZJNY@-|xG5AkxTM{DU)xQiKuffUJti=qd{fq@e#_rUKg#73`UXUb7' - 'zD1&dLliSIy~j0M@>9w|!6gNHG=x`sPJ_AyWKq^mq!?X1hZoc%)*86{f*mBRZ|VmBWeuz$#{)HFFkXZf;I~tOobesxNpDRf1Lg_^' - 'Tp0i^Mn#f@3kC8CxAcgd4vIwQlss96enoV68XUpj|2n&v4f-8V$!l_Q{%&~kGd$Q1D-ZqKCPi)zP|-z;VvE(a)1Ho#CnC^tU$48~' - 'TJpn)#}O>@@z3N?HmcOu5t=OX4~e~j$5UIDm#R-unj&f`*k8us4z>=en4wa%*46%p_mn3rJa4Ycn|&{5YGHD' - '3dYmEO&BVx_xZwEa}@Q!EVOO8=9R#Td$15Ej;$Ey&>bD$Z>X1s?z}wzm_w;Y!Tz0BzhUNX^@bxaR=-u(`1P}uQ0m!?oUy`A$-eV{' - 'Z~Lwun9yKUUnV5{+71Lf(w9HOLre%mOHi}J_xd+BRZ`VdnUE#aG^Ta7t2JYqXhOC1Qr$opr2teuR?B%CTzY`hHQPSpx{Jszg_<2D' - '_!3HqaJ$C+cqyai31QX^j@Z;)k)^7po7z4>{uxh%2eSFUZB8gXZv58Y!kIewTboMzJSbf}aT`~-9Yfz$`%4XC#k00zj~u0m?GZ`i' - 'SL*d*|1!oU-lbHN39A|r_UewVGMur!XYBpkuA=RRqwb2BMz!pBPrOCZ=<4rlTP<3$=^#9-(K5a*FuLuzDENlMuJ(u)Zt)P9kmF%4' - 'g~ZCc3VZq)9#O8_jp3x;Tf3OSVV08KX$ht*MM~5!%_3vYu{bjbg7-8p$dZBgu}WAoE|c)>hJlr2nb$>qov&zd07vCK>lo}JoY%=M' - 'LII{pGGG}V&8)lNwWXFxz8KQIJ}!EE2+$3*5eBq(#vqV-^(R>D_qbi7aX~PeaBO?fS0C)Xo?Gj4D&V0nQXb5wN7XIQGxE@Pw7%ny' - '*u?Y|s_Wokq}rq9eWNr4w{ke%7Y&L}ABNA5-Xd6?2aW%yl9k=-Ks#gNpJ!A6&LxGSihV6OjR#<)JPSCOl|oYifok78p$TpMg;gSL' - 'fDYv#52n8X@cp@6G7wB!SXXmrQr5w;gG8j_dHh!?<|y6&Ns3VSBz4!~&iAIe_aAJ%#wzQEX>G&NZJVbXp~J;SuowD' - 'PL3__$P{AyAuqVbF=>i4l~~ksqrdrk9XJWp95|}i9q{*s-A@ZzE;DM~ABG1z&5h!IoFQMmwf$~bNua1J-ReKw<^<#dt(wnoyHyy)' - 'zYk|4hv>^d6ngAO<^P%|kP#kC`1(TKOBX)X6ce~lA3dSo9~!`1m6QhGgN#sri?u79`j2HouL8pzgP(Hncr!BRJh2' - 'MLYJw+x>!FR;!|LTM(RW2lBy;G`7WUrKkp%vrdm-&mVXLB!ab#!obbS`jt)mmR~+_RcjO!2lMtf`0+tdXqL&#We0e^c|)QrjJAMFj#X0{a`ND&E|Z2i{ce' - 'gIs4>_VUHc7dz4J38lMU^#Uj*eBZS~q2Q)fMv5G`%C@e-%p=$Aru3|r@KX;>3685{>aONDp86Ia9Yq~Ft$-tZ+Z+bTYhFR4xH@g&KhG}SUc5ekM=!to*Vzx>(jU(*E?>Vn_rtojtpky7@ezb4O4SN>3!a$2d_nNRal>oN?pWq6_%2HX^K~qur' - 'Q;jdFfD-*D9gR0!adiEA80dZDXm!gfWS4!d!Z(AQfFMT&9D%|kmr4%w``HM~P30Z?+u#4;-t`?uK&S}agJSrOAw%I^fF1|w9dPDD' - ';C0jWYBfGz7&9j?%*E{q-bboD-r' - 'qcXo5*@pP9tANZaBsfR&Dx?_Qk!U_cf>8_!=5t0sjr0(ssv~{0!BW;t$}tGun6Po)LkNBI&=7S8L' - 'D(z4o5S>8ETprhI9%IAk4+lvzBvOAM1HZEl>rMb*P8@7kg$tcBp)*yi%wwW%{+AZ(a`Fck62I8Yo^L~tUQ@9j_>xifILPae{P&28v7`N?`%BNY>h`fZIouY#!9rF63KXTC(&yvtTclBH^)wPLY4Z;Y^5DoH*%hHfz#$u@Dhl{{#}zhko?h*B9$sP_EA%bxU>$>TuSu4WTi&&;5z`{D3m>MK@+Bx2o!jpjF2Xt@;bhOw0?E2_' - '4yB*gIS*KSVBTu0zQm)#&4DmXg*#J`sJWN^F!$8ZDSE49P-eHe6@;kr8C#&sT>uPH^=ME9IRu57PW7-S=Pg`fr%;?hbJh{5aLSZv@FLE6FF>=jg6xj&&TxQ+V~}l92bS1XV!qn>$^X$' - 'sO#_y85tX|*Yw|?Q6h920O6698g4m4x=zK~T650q8^vqlw)0+RYTgV@nz+n6CdV|danv9H@oVSLsz_h(X@=aR8S^5D{vxjA' - '*i7?^PSZq?C#-&>XUuCeb0l@?!#I^SFZEG;6lvN{SEgBq|6OY@eE2v?K7#?;-}cBKI$#!QhM}SlyfWQMKltj^_1Zv%bx>Z|7T47m' - 'UtKRkd?Mm6CV@{n38t8TbxOIEU7{PoXw9DCnj(7bA#{&}eL}jAL;^Q-MaBbx&5ClRFvOVu4|tGl0Ro5u@XC%x|$|x$rZ}N;o=Vb`lgnBwTb-' - 'E%kfUQ@^^V`b~A!C#$V~r~2xY8q3sKk#}PkaXWrprTawNDeSLeCzxIW9r$%aI1ehbVSE6!F)`+W7-cn7ayU;Ngh`9P0&VS@xZ+huBzR`SYxug<$^Ib(p`Zw7c#zNp8`IzGkTSA-o#!a2e0JM^am2#2Oxzwmj0TTi%a_' - 'jv;@=qxeq3a8H(KLQI)-0|FWh6M=!B2VPCL9H!PTYN+D{&ddqX%iTP2mQNz)a>@u(=&J}hFg?1xY+dXV`Zj-lF}tY#5~X^i4|**V+7scs9I*(b?}&=PifYFy32QT' - '_y4y2kp2xp9&UkH#1EdB1%D(`&?H@$(5Q^yQ0R08mQ<1QY-O00;m803iTu28;-Z3jhFXLB!bZ*OdAZf7oV' - 'd97MoZ{s!=e)q3nJr9+#vg%}}MUjq+YPYkC1=dqzI=k>72ozePZEj>rrQ}>@HvR2;4oQjB#g0?d559=xx$#`SLwYcY5_sp5Wsl^ZS6i}p{FuvR8;-~0(P%C69f{-hzS>I`$G9w)6`?}p6|HzK%F)O?+tF%Uf3J{&UYz7v#u7C(' - 'q^qQ+@GJXm&qTuXBsISt`p86?%h71G_~n;>{qy}l;`h<~-LGUuVCrWWW0m)1ykk_112W#x2mBDZ+^L@|<$9p3QvA515XXTj=IiB%!0Q!(Y2GRzSwK?%2b`&X`D&D}3D}14l8dsUprN-68cMnB4$hlFO&nj6' - '@{*B{j|F=tWiGw(5)7V_Ioa*YimVu^)QU13H!PQ-NcoO@H6yRPbpAuLy&<#)N{Yso-Z4^cX~C=_0oj0PU#dMghPMpWsQ={fK*^UO' - 'FXMHV!`_QcT;yC--f_tlzdbyqq+T~s>j>MeSel|UrWLGkIL$S8RL2N%b$M}pRI&N6V*aeH5Yq9VTB^n9ii1elhf??*B(3Txg{xNp6(M!q9PX)W7d3MK{ly{k_>XE0E=2Xne2nPn58kTD$KyS@S1OBoqRy%I#XWhC)WWv3&@IA$u=(e' - 'XLd;-mU=k~lJ}fGOppV~=p&QgY#B{S!+=DPb4{S!+?X7Y`8PM=NUguQQK|CghHQBWmPGZzZ*CbYN>Xhh#wwYM4TCL7p>`yyjHDX~' - 'gGA<~nxg2?JIXV*gA)Dw4fXIfr{IZd|*XJ6U+whS#_Vw+odi?su(F+$|G=plPv|-zysVuGB7`q4QstewJSJT)*3cw' - '?CET}@f*kd&Cswd0>xCDvsJV(b>3wOaxAP' - '8{ad&*#Z_+E20Jvs-I{2BZU$Lnd(v_8nE~@{Qg@#R6$CYm~-slIh%Z^2VqxYC=imG9FHgn#;4O@G?c;r1^{GV*(6DfXarIx;0>%e' - 'f^C4gbNZW~I&ab($w1^81otM}W{7VDM+iV<1mXRH$+!rPf1%ThSHI;0Y^#0M*4tH4qZl+qq;fIEb~&9y^Kn7243t' - 'j^N|y6bKwSKug14r|3rw`n|y2Z~L%KQ%3=$WdQg9?4mKEwwpnU(E(5#y$}>fM?kUN$1W)DSk3q#I6o{4G#QN;hbAecc0?pI(J`{Y' - 'GTMm&f?cV^-H``-J`1tb+?uCFuT%1NR)-9EOU{qQ`yZ6UQMn1gK0' - 'ZfsknJLNgkwC4|r222W*QtbsxRHP}CO6Rw)C-x%GV2^r;@ta`D3?gTRb;TzFHBC~QK9LJ*gGfD&X8!l!~A6TnW`E8zE!a?hGI=X#AP7FPOwmc<#r' - 'W!?hjgj4d3*{d$IMYH67%OvwU!>&9(l4;O9TmVHg;OJtQbFt1nS6j%(kG@@Oq}2~JqoO6c=V@sJzb!qWb=^KMI_rr*$1^AG^Um<=U%LY=U6ysd}@dq8Rp;lDJQ_>6dq9xnfh6m5x&hihG-@Y8G)-a)^Y%NT)!XE;nqfxs(8!1;FOpr96P' - 'M5~zvT^{&aNk(r+&rCKLt^k7+%HOQKqY-Q^G@lFXt1XO*iUL9kBNwc;%bi2eylC3vLx|r={k08kLKbwVOiY{R' - 'm*i{pv~)WZ^#E1g5Z9YuN8~@(_~_Eo5m(VB{!4A$XJ|r|PN2ZZINXY_VzSWmv0u&vi?YTTgo42xh2DPwH~mi(8T3' - '3$PuYfotg@@><8_t~h4?Gs3LL|2A~{VR1>m0Mf2l>KR%V`>KF|t$=u`)P9)4=u8`z=p{4gBN1s2nENCe{EA`Ly)S+W&Vew0&QDZB}9*G{W3#v2%wvhdhU6I75z-eHqmmV6_i^@9Av@Nh_-' - '2}+xD4uJnb^p^Gj+c4HY0Yuf?(Sc_NK>LXVP3l%20zDyz5|koJhoS^!1Agf6^&C(?W=~AKF8_P~-oT*s810VZH}ZoK;q%xQp&N^KnJ>rR$4z)#3;$e0}$p9Yd|gpyErmFOs#XgnlKG9n(M7_c&&d_?Gk}ryD2#B+R5m&%Ttz-zG!aEb-M_F' - 'U2`{KV}9{NXdHKV*Zd>7n7;Y`Wuh2(r4C4TIbeW`0B8(!QiC$#UB$6WQ1_a1wroyw)DcB}Aw6)v(QS4R{Pk-_cP5sOWenJ3j&D)S' - 'F}Hl?1y<5IJW%}u;60rxhRyT}Dd^K~^~VtN1HMM}^8p#v_J~wD71mZBid=kVGWTd1D`2U4UYQ7!!7z=e&Tx3115|(}dx)V7LZZj^' - '-th3-1Lt6rePycY9x7Z%4(mSk59?l*cGlxXuai?)_bd5y' - 'XLB!db#88DaxQRr-5gzW+cxq&Gx-lV-a{y#q`9=cKIqDuNmHlw' - 'O!G0e+dgVd|9-ppAn{4bN!y3(Ln8u<#bUAF031cp%cg0Ivd%h6%6i{-WZ%+#)@EJV)N|7IH7N^PcV%}!C+n=s' - 'w}jr&ycgYBU66ghuF9OlFQX`W`sC@8P2229nr`~8Z)uv6a<^~V4xsC%i>pBK`UF{Nn8G' - '8%17LWP8|xRAEVXw1c)T8vwcvdzw03XZa1S3%BEjwl%GskpsDD-qanv>xK>`Z{b-tFpReQwyeAKSJu>AL}cHtDPXqXP>^ta_^=NR' - '5MTZdOC1JiJVZrdHV4nJ^AZqQIh$A0Zo-|MFDP@^TC' - 'r;5REd9wmSf;GK(2cNW6ouD(LoDT@NsL#i5zH;sRoU*>^P{*G>NxwUN_2Q==FVk;dT)zAv{pIY};Kwf1P' - 'Q!rxBR~kWnR4AGfEFmUW1q9fren*EnF56y>9dv^;)Qf(X-AREKi}t}N(f3VJpx~{9XsaVOjSzDM)CMV?iKfn0u' - 'DG@Mp!t3Fp9QlNHHjq4t~+p)xFYl6&*?SBXzceI0k${kD-@xBluq&jYcDyYZgSUK>&?H4?yFG0-CKx;BrGC_97)KYakbB' - 'kNLcF6ww)w?&(3L)KPGokd=&*OHjwtwr$#Y6Me*0Jdj<_I(9zxv^%9+HPGs2zvHAPS`zffzYA^R6-g3rppJdvLXZaYw8rMBStfZ~{kNNhZyaGjcBD' - '$3w;`a_1aL6#0nlG@KF3lJOk}m<>@JJ!M^#JMuS7GC=U$ZivP9^y)c;ok#er5D&K5E!9;Yagul~Qg0Q4&JNQmvL|H)' - 'MDhgE45p;!B4^F|NV#(-&zs%8qIdD&e^8yRz))@j1D{fMA^y#lU?k#$' - '(cckk;+(|FT?S$%<|ON~&f0qsNHKeL1KDwaHm*H0Nc|@IpU>n9{sOvX8pi`mH1v8{2yPs#X*8dym03X{pp{P}7XcXOqJV_v7py4Rjf3;YhGtqlq%eB=8N=WS$fcIj`y6d~{&D!P`kHG^$%Lf@KRc_H{)WBR&F+-J@7x' - '5e3fJm~p13dTg-&PH51HCSb2R0g6mMMlsd%F!NDQPk%+5He#_Ec<3l)^9fD-4Y8REtmluzeEJnvF0x=5CF{Em-$Ua' - '7we1x_i!B1_C(G*)Kom=N^cS_+D;3SnKeAqijN|k6h+-hLj;ej>zLyFI=qP%eQO8qe`$%;4kVI)^(>We{' - '(HO~`O{Y{MPS6~pR(tg%R!%j;;fM=yq|)d^j^~A^Ea}ZUb6Kg3Jp(2gn2(rlD>7x+sB+L{5#?PYx&P8`e98&8wk8iyy0-Q_}!CzEFNh' - 'EEF_zbj(ACN6A{sn(7vCyLQzGU$t1}gd94sH6mEA7x2Wg4xAmJPLTSIeuVmI-UFhIie~tGz%&R@3wc3XU_kGj*T?eMo)jg!5f>VY' - '#Es5oM;z;G+?Eq+mEbWdNyeXAzrvQNA9I6!|3`_*Dny1SL-`Z36k16x{rUMwxPZu$;>TAJN1ldID_0ukxx2goF2Pp0;E9zA)S8)t' - '^`SL}6+s@}ie=78G@3JyNrWYWfX)=Io4WZ>tG=|krELq|896s$R+mM&+0YjKU95GFObg=uqwM-FyBCICVU|vpVV$SSgxrjLvSO}H' - 'w7%!j7U$T%6}e<2lYC@+WE@<{?7`zBv^g~%rTrc)S$x=Oq6|=s5>s=Rb-Y+SZ<)B*XC>a61MuKEp7)#RBj?OAO@Wz=r78TvDbnN<%Wa2sy!JELEn!SMRwB|Wx)_2Vg^dKk0z9?IQo~8p&OtzpJcu>gD>Y`F&' - 'x=!0?Y)~^ml4mm2;9?H>FVAhY16dU)V!OQD3cn$=6c<#f(z#JScu>o)uEP3lm*)cmPp1WxxGO$fc6#gsS{4)xLTi{%VIXCsjbdD<' - 'C7vVfU^KTR@xWpC`YXzp+p1PG^hnaRZ5A5lUa{piz=5A^p~Lk1|eDA``=2YW;QyWPSeu(f)8RG7*)%;|-60' - 'M-$H-=Yw2L4)2Go8>J#q^5jeN3B#6zu7R6*YBK(G`n3cw38$6ThS){e#Mfxrafqlod^jQ0+qzvnneQ2#(PfAi#uIt_#!!ZS#}@BQ)(NMjXz9jlcRKc3~&7{UgQ#c0>JwSR~*p3WNxv^`FvFr6y#lI6<>#4g2A0N(P<0YGU@DHHFz*7*h^2L(%_mBj;p-Ix1j+J$6(DaW9' - 'q~`Q9FW#}XDSut*=vUh8@6GY<*`kSYJddzfH0J8(J?#8W6_PF?YA$!XUR;(R&xjcAt2C=%b;WJR;b=n4YFr%=vZYaCij1F^bh9Ub3FRJ^7M!7-vs8>+K(YZDyj|U?DB28Q=nJrtoYi{YjrJM3D' - 'j)ZfKc>PdZ5YbB#yoM;~)@O{IU;(C>3Lql;$uP=E@ELLJpwi!3M8brvEJLV6Xr^2gzy<1veXLL(%ODXeH<43p<' - 'qD@&*b_k0A@}Mw`>B_Ln4j9Iz3MN!7I60Zh+tnc%V5v&hSygBoQ2KJ*h4IUk$hieEf_YW*6T&P1%zyP{sLGd7s7bDWFCSWhv8yV7J~17JlY*k`CL7Oi&t?L' - 'o2qA93ufl=>1yHpPf1`lIx4gl*(_9K!yltY=2+Y+U|P>ToRQAx{(!Q)E%&hdGO}}6DmQkaEJNgE6H~mDYv-(4NCI38hqx%QjCV;F' - '*R+<$Y70TPmWS!!M3rQR{n(z9$Ajn?$_mAS&t9pFLqf~VFp95MPYo0' - '{k%{O$^H7CyvkVj?#I_mM8Je&!z+j5(|Gq!craRR=EGm?!1wwPZ)nf=C*JOBOAF&1(aANYyu6m8K**Ju>s4wW890a5@vpTEP1+x5' - '8QP}*W)FgaQ2h^3O9KQH000080000X09lsASC=;c0D1!f03HAU0A^uxbZ~5Kb1!9WVr67xX>Mn8FLQKna$#p>E^vA6eQS3c$C2oF' - '{))-?g8>;tNZIlA;s(jm5@mBEQMx3R$ji$!8Uh1yBD^vKP+}PV_gjyCRQGfbKuXTt-4h)jlbq@4M|E|*yDCl7my5cd7IocAs$y9!' - '#_LgeIW3ZUwW#u|BAG0zWH4Kd2k$3&y;@FZb-$Qh7UOX_zpCH&(=^@LnN*8el4XSe7;!at8y`~cXrgX>%6|6' - 'mY4d^a-o0wYrUB3-|N3k%T@8M`EymRM=Sl;d_7ylx?b`7(PBDwZ`emT+&k5)yM!?Jt)' - '8+6!9PK&>-i}|SF51080C$2wu1Ap?1)$I~Mp`RbjZ+poIAk|A2H$^pFsOtf1pXar' - '$e$FWMK$jE59{@G#SdTRf4}AKPKrrU;W~Od-JPAC?D^R*Zw|BLgI9;AgJfKeR_8Q_{usyWA+Rr!VRA>k@S6#&VzvMMAW7A4X-~Yp' - 'w68Bi@9EjMPxaT|K5f4J{yY8l`|q5$<(%KqPyY?|Nk9EJG#UEozxjb*{NOxas~79Wo6E&witB3(*=*`*d6aEO=' - '?`P0>Cp$YlK82@0ADsT2J^T6X@t?9Ee>tPM?SF52dE+$EaXAH5tbCXZkFmXWEZnFCa{5haimy@Kd%Xz)Z0bjUPk8q^R@9|$%' - 'z$u?6cYr9J55;YFAo|5Mb_o*jrkCKK2nXuO0w6k4XIkRHS*@4TqT3wuJT#Cq#znkuJh06!aS_y@8~;)qkG2l_RWXAjzpRQ$`Kgog' - 'SLx<{i<@#(Y%??Z3;Icp7xN;)r8fZxUjf+X07N=Yy#!G=07`1VBOV~_*W)Q{Sr%0d`%%nS*{oRQ<9wAf71Dnx@>|KK$Z=?Bm$L`t`BUIB2M6M}LAliTHffTS-=Ae2Nj_0)j(0JR;%+{?*rvbrvke7(9}' - 'R3-AG8$6saC1zgO?`T@&^J1Jah=&5Bb%$mbfTc;mJ6|@>N)8MtIRnf&tg1!TNe!*GGo7t#U~xrN=F{@;1z<&zC;xH!`Z!r!{uMZt' - 'MjeUzg#Rk>0-TFu2*&1Bj9$M4OYY^GUUufu-SR)Z)S?)`VTdPhq%hMoy*q0bQkicdKR2BQU_#MOGSuBj#AeNh9Rkere%CdoUC7GEXMccEv*oZHx=@=IAv$4RyLxI4@L3iRUHo3{z~n0zeZG^|(9-AZ8|' - ';v}BI$$C0v&;X9X!zeT+(|iTfEXFQWR*Mdkv|$PWN|7wq*ZH#O?Cvvs<~?(kOcw}{VhR8p4wF5fSTQQ%yt86j%wVr-7!m-{*%$Cg' - 'e*0U|pK6%ZaHKk2HDLe&oLZDRf!^iwtD>{lYtEtPjU|pVy@0XJH|N~nK=u2L_vr;Mn72_aTP>X)^%2Blm_|4G4RCBbkQ|I#j;63V' - 'K#gXL>NbOwz{U+3r7Qxf=iSJtw}cMdt=qyqRY(XLnCD^|L0lmkf|WZ(i6?@WYsSfX*G0K{Fw%_BQkwvIkJ|p#v6f&46' - 'w$>eM;-FMOr{igUi?0A(d2<+p$1Tu!J)hnts+k(01`TPB@FAWv)&q>os{-IQ)JkGs?Wug*UoU}c799z!U7ygP+x0$k5ju1z#OAwWPy>hhtM}K%C;rWndLC5u@8K;?keNLFPoB4b1`ZKmU?ZY1gLxIpoQOd^?4&&q8`44Ab(S97' - 'B6IdU9&W%gE+km!qm0!o1_ypk;+cBDf++PupkpX97*^G-*eO~n$aX@9JGcF_>QH4U' - '1TG7y)T0Au1};GxEe3VRE>pqutAxlP%2Vqa23X*!g4yM?^xbK3Q%vDhRaIF4P)Me!Zjh({_yRP+%My4dQM&1EpZdeEGH9OP!tnED' - 'A18LXo=ggWIY6|>D;UBXMSelbzU^q%Lg4OMsYvPa&_~8(+IHV%QqJm=`dstJo+l`2QGszx@jUv7o@f*yX){BEOb7o*hyW@CX?TbN' - '_zQ0szJ}?&(M=5@!$0~pthfAxGDq4+P^S$w37bVfsq;yp=vny7J`&Q5Kk29;oE6jbev1|f)JEvNUSqYMXR~}>PJm{7fu)i~0%t9T' - 'YMC=O@1!sXIq8RiO>{I#@AyMd)$jXT^!iZ9dR=tB5?ZWRcp8v9=`Q;~gW(_{JI0=EN3Hbg)Pgs;qov)$h{{TcBxO=giv-ww4LmzR(!n|;' - 'Ff-84;H=;FT}YexXb8iD#k?HlQzBuC#xL_*ByPS2>uYVQPsy*Jv^({Kv=#>(*FzluUUDzUQKp?NDWuP' - '&ojpaz`NfuXGb0_Wr1KW?AbUJj<`>)&h-)oB6X9x2QG=$W}w%X$_lYs%*s&$D7mXoH>jkuS-DcGuN6K);=G&|g4iMN@<^ZB1)o-6' - '%P;|P%ovR(h-tX@8}R2_uAx_y)~(i#Zxo8q-tn^a==>%*hXa!RDSAw7nia2jB|UKz`;h' - 'v73B~?h-mfrh5T-vq(p6uIKr(zFw^2E6EfoNgZWetVnm!SPIh#`WU*`&79Xf0$U@7?L^QHUrb>FX5z}aJ80>*K^OR#Xr(wjUvbPt' - 'Z+#ZFJMN(pfbq85(BS}VHg7lY=H`RNP^mp2d=XUAaCV0Jv}6*konHE1ili_d1OSdwP0wx1sw%YhbXNiGuG9kU=l47AJ}Uy=7B' - 'i$MOTQaoar0lxK)r6NtZ8VBut(y2NiowNIB=rg2AgeYqbe+h7xqlJXACd66Jv7(t0$%d&Mx-o`6GMmoIZN`o2aF6JT>a?CTw-=xYW&N-N$EVD23(YE2hZ3_r08ERyH+obY6(9E4W8*q*' - '-3H{X#TL$hzju5h?Kq}gqWGP)LW3Z(JBJA?-{-2hDymjF*4$%@2H0x`Z*xS-F*z`y3qs0QAKnezw9OrK(`J+' - 't>=L3(J(agc@X?O5t%$4&0RLoq}hc5{Ydpm&wA5Djjcv#_)L9z&V-dBFUTB94(sVED?Y)wufygsir?{k!5^;+(8Mbg4&9gl*26o;TqGcGG0vjqS+|Kcg96F9Q`XgSyT{xL=CU5d`isFCdxEV^3!GEYg{Bw>UE-yf^tVvl3hGRyLy0hb|I_fm;h$RfdKy;&^>SA?{Us~s%R#qBPmDOTex6{t4' - 'yRl0hi&m^U#xV$s8`x=~rfS|&2&9b)qQ;2I4rlQjC_&E-svLu)K@J=JaVMfp)RZIIgj8B-mk(;84f94oA9co1Dyf|@e~~&Jxi%Jt' - 'KtCUkJ7zu>mO8dEE*^)~fA~y-iG`&vUVPY5#_iKf;l{iT2LuYmbf$QH@-u5y`{t@5KX!BaqOniMSH&Goak9}RyG!#=H*5k2>|-@g6s6>gI%0t0Ku3x' - '(797+&e0cUtGYck3(MR|ZKLo8DF_M*BHhPGeuGX<@Ak$Z=2U_2AL>bA-B7?`v-YZ1RgN8wQEJynt!}?KZ}r1;gT131yF|@Ae#DNd' - 'wH-YVY-V!;dWP=!`spKvH*jcBB~%PJ*x=`;R*&>@`_M>0gQ;#Ku27F+3kBEH(-?sOaYzgk$tid-1x@zSooiiP#pTkoA2+%#(2`*BOzz8NO_aS*pCm;yu%qTIa4g#h#=476s^9OOIC-TyF{PTEC!#%0&zC^S#~o_yiTIgqX2f~>sL@z459&#(M<7)x?TD0|(utGM&M4D^_!9O`DGf@uVlS-8^S#`Q#K' - 'C5PxoM`#-7Vj36cOrS2ho&>Pd+>DL8qxBkoSbz_{aH368`YUtz}hv4WqPSiyyYBaHlE3_+I_kd00_BkYSwxk4w$Q>k!v=p?1;' - '_{6hkMik-6RdG{d#)@Hs)=K)4>?ZpcxCZ%*R%vDQ=>qQ~2r!vncg--)aa&9#b+OX1Y`6;r=1Hhf_HI2(-G-5cyFwSQJBt8o)H!Kz' - '%$-e8)|NoQC{I^aksM`Y5m7=+m@' - '_*f5@#B}0(LP2SLCtF&b15ZYdD%QP4' - 'cv>Vx?KHpn5)o<&*X@2Kzjsoagf$4!T&sM1I&KQ%HZX!lIF!u1-r~Zbm02Eob1*Q9j?$QY7?&7%h=1^{%pN0Rw)nu8G~FIVh;KaA' - 'lz&)yF*6j+XPAmz+O=nPm*uRU$8>Bk?r^Uc5W=iP!CNyF+v;se6XlV%HlKQOut8z$yMfMb6xWvFUG83Slp=o98o_8k_$oO-K05}a' - 'mPUWdqy_2)@(4=6lPB}yWAc$GES~`+A$Wpv(U)c^(_2Gh$7D_heWj|_OD_x%?^X4aH>M>*qZZu(ZVBQ{G0nw{9Nao+5YuN((Wis=' - 'd;qBu*xYi8S2aLlDObr)&-&gLh~bBvn6X2bO}u6Xko=3kj}C+1^WPRl{3XXjMqdZqLgfDNTtcoVgg^BzEi?vRS9nw<`%wv`@cz!d' - 'zkX2sh%?aW9RB2Iqf9o$M+Il8rlC*Ro}{84SMmjl@c9T{p=?coLxMnI(%v)z!)yPNfN#TbCn5-2aE5+>{RX3UxN~Kaqgb0@0~)0P' - '1H3N>LOLH}c_XNvM6Dc_F(IGsDiI_20M=Kn)G7sx14zF%9U@(aGHco84=j`i%%=GZt-j4' - 'Y)&H};{FU87!ko!eX6qX1)zsn1k;GYy46|+YH%P_nkN}*LG%}hjfh>u((5dA3>~1' - 'dQwbSegg@L_^yz&>ToCsDGYaF4!)hayR*Gdk=TJ&K!)wW0VxS@+%m(<)qyvB8_`$_B?iNBogjdB&DYD)k85NR`EmmAri}kX0' - 'OmiN0jh<rfs)`nzD+5Tts#XGgCV3' - '5-d(s%250slY4beOMg0z<7*vp@mNJy3k~=KT6An@pVS^TcO<+Mca83N7vvqwe{uRpb9&FP)p54DXIJ-NM&?L(+tGtc*(GemdB%r8' - 'g`F#@L`>4kD7eEE{%VWtk$3{FW^1)#-TL}omyOY{I(PVg?}})5s3*`&~Oa}m6~b@edn^s_VouGooiUG(r?MqD?({z0TVLND4|vZRNy*1BVngPnB(UWX3yLN#c8d4ATt-2#)L3|1W7+P3#972-J?Hp+6NOhyA#l;~' - 'izoz=vaui-G}Xl%XKhBijRqzEv(}U7wB~Eu6Ke>|40;Uqei+}UJ5ku5!?za(!*@3A(NJwt$Y93%5S25+I9-WiVE(@-b9b_{%M*MN' - 'ZqFfos$m;zck3~zaLV2Wl3iT*Z(}#V5ho2`j1%SMxmgV(;V=tI{Kn@qX?GR<)d' - 'Y*d0(yQ3cU-4ji`#*`XdSkx2k|s!WS6rF9aS$|`j;VCn1tMG&y;)MFY@tp>R!a;QnXKIVu*yPZW{^`' - 'FvdxcgyY!S4tL}WKHYczbfX8pIU5I)!{I`Zxqvf%`soelH^sap+*OP0C*(_B&qZf8k8>?@wN1(j' - 'ua4IX8@h!gqqK-Q!beJSLA!17at>e-$X1HUz}I-`eK;QEKBwJV4S}X?2jgUKAYNvZw)r`7({E*%P^L?(Co&$a*!-YHkHZW' - 'OGBZPZ$>^uNnDHS|3&2Q7t?jkp%7ZBW-AY>)_FrV&l4e($7Jy~J=eLb6xUkyFFmu^p)EM7GoIcQA;_!T8MM%F3FvU>xdQxl0p)S9' - '0wgHBo;&h#riQCaBJioYB+uFcQ$>*uV-frvCz@Vb5UHQ0P;D1Cw7*8f*oV<5cVm-~O}b*v)`Ll4*Plvjgu(>XzM;S(%6Ghl=#&mh' - 'Kk8A`eAgr+jh)3{p3d0jQJtDx34wmfTWV@{ajKHHDx;qg?Sxe{n7nt}+??_B^={-GM)isqvzD|kI~#H~ZWB%y7Vs$uP0XX2HrdUa' - '{g4zZ;%a9!#dBNo{bm{+LJB$EURy40lSoKe<;lYqxHY2e|AF+B=O(c4V&Gf(^+DO@0%2gwsN8ofT@bBv%O%nQy2`7|{HlOmnqqY(' - 'XCC6BPiHd~9Rnu?@hNb<1^5TZOtWmh$np^(zKTfC0MnQl)c!Rak!g~10%7R1U--Hy*oi48`DDeZtxd#x@o5CCue?FYdvsllKB$|6' - 'r;P(&D|d*{9Is2PXNbJf8RfDV<)~ujEI8&pJ~|C_Ne4u@MaAka)kkDPjY)-x?*oGe*j%miDhIKM?)Bs=fTy}EfU3E^&j7!^e~&}Hf3L0>u(g)AvJWrS&QpXK%iK5yU{{W$' - 'ldmQqqVjW^J&*Tsv7U|rf(n^&PsfJTj;{0hmFAU=qH7uxV#R6IoK#AlmXfThp-3-*U&NE@ZG+64N8P5E_9^Lk>nxpH9kQM697L%^' - 't%$`$V4QInrG8M{<|z@<^rfh;N__rVDNt5a5@+G6%9qyw8p3g1+MZmO^V`)m{ZgYIH7yuX+Qfve%eTY#i5|kmf5l>TDtTC}CeY4<' - 'uQ@HN#j07SCRA2Sr5)}C%`wqNF=&R`J_%$FZ_LCnHTB{B7fiM-vX6x`ZMUY_Zp%7lz)TsMACVu*!b=?xljXTJ0r?VZvsZLs+@5eO' - 'K`O$+p6ov9ht2k~p@+t)=T#5)HH@Lqo7o*?pe}-m;UJVa#-*&->IS' - '`ViSAF+9K}tTs6}XZtmOm7;KH?PdcvJFkNk3^IA1M9fw>&1aY6+JKv*hsI(dpSKQoGu73L$osCsmX~vy{mKCs=oZEd$#GJ!LxWbni7KO}&$tYPv1P7aT7z+`-E;*g(b(qB{rApC`{=zkK`ZICjiad29VS' - '9FNPq21cTYik6gt{9eId)W?X$csBBmlj5eRZZVNeRZw-TzUc1_u%M#4DOhQvyub)Bp%Bw7RfctxzO92p+dSG9>}m~f-#o{|L0z66' - 'o;6mCxpP03{^h;&FFykkHNkVrMD*bJxjtbX@556-qi1Kye)8hv^(%A!xH~<2b^fT7uKug*`zP8~mZ~Fvbo~7AU4XS@-o&B*Pl)40' - 'TfBGO)MecdL%6S!Q{uGAdI?$-$hmX@Ss@791?q&X7od2R71P>PmCx&ZhSpy' - 'LIR}Q#YfSXGA;1!%w%CN9@7j6or6fksMc{^1T0ci+8h>k3?vUS5}(S#dh{RZtM2&HOMDTY*?' - '`hIkLdU$e%jO(=&oq8^=^n6m@O~Q2hk3IbF+3SOsho{dDJFgDjHRvT?-cPb3k#xpxVCp+RopJjXHnve~4Szm(`4*(O4)2NmubValDH}3guXSnqf>O8FayRh76W83nIq@y0Y%etV7nY(E4e_o}=n0qDg)EL^Oq0n&C5q~VFa#m$(TDnsSCQwp`F8g)E%G-pPww!HWf-FYaa@Qf1twd6$lJEfLr(P?7bm5(-69geqn#8+mTeoc<(V{*ywXDHPK4GiY(|<(3V;B8isDsl<~XFhqkOK;Ouxl5s`tmKZ?%yIs>9@#' - 'Q7>l86>36A-IZzE&e)pSfa-X{Gw=eGQeYc^Q{9{K6)`6=zt;WVGjQXCohh*LGUCVH7KmjQ&;n>OJEbv07p8@#>S6-VGn?den' - 'S@nNQ9!6>O+~XO)IiyE78@XncL{6f~I' - 'EbTg0HJ+lt1<*IDw}07k{$5Zg_+?Kx%{T4EUs9u6T)4hvsOSw%iFYlJ5je;;8k*2j|vz%b)PPBiJ+0^f4' - 'OSK*N!V7ic3ows&V)}lNKW=13LRN^%2-+wZv{aL{8A!;o)xp~AgzYqmx8=Ndbb9^8m`x8-<0$gsd>1dap~=2R;bH_Ql*' - '2_b4*ZL0V$$~hTrQL3&M{wBWT#wAz-01(Gr=3L~hS*3kSCE~9CY&jB5N' - 'o%#t|yAi|Je(f3=rN-Mp`HK<`A3R$TkUqfNO(IcF%nCqa-2dWgLi8wR>Xo(Wfw+aY8PrKMPWxu0;6NH<+6$RuMuFqS>IIg>;YEmm' - 'YZbTXoiGe|b)uO2d7Z7RveUi)Bibp3)dEfcJWxIhaSHX)Ozl0#*Rx$1qz+v$>$k*(BMrQ0?yAAG+XJ402cHsS(4ki|o&jM38pZfX' - 'fqlc|Ya0LS)IakSA|(R0l^cb^&QtctCL$Mls)Ld59Z!w=2)43bUt**)ueQ7Gy@pA8jFz|L1}aq0)(>-@G>' - 'x5wcesSM~0jt*Psa+P*n7SEwsx_2BH2c)l03Hf$oJ?7$ArD@Z-LdC%Hf%fC_h3G#C_5pQ;FheU3VeW0!teEN@iXd!ojhN88i4rL{' - 'q$WWFoAOLkBX!!daUu1tCoo#{*b24*&D2fg#Yd5rnZ2vxJ{wg6vE@OW|6LfmMe-^POm(!k@J?D(7WZVxKrlDe={XpT?^NnF`}z<2' - '26WcVxE`mq&q_scHPwJti@#^pYMLRaPS7phl1!{HA>8Qo(lYQ3w{BmS%%EL%X8QD5M^+Ou$4$z2kdc5z`A?Im' - '1xtN?5t_0Ul~t5>N8B1T83D9O&S#U7$VZs3UbYNASHQQ~j$Y<}zvX^U^iz59XI+hoA&OnBgzd;1Iv_eW8IV9F0?sgmLj@M-8{6RoZ(XqY7p1eIhI{qni$s-u8U-g9=>t6x>%p%y2sj0j{iZ;6)6{VHei!&AnBx;lFOKj' - 'XsD7HbAwrai{Vz7CVpBL<9>2dEcq^;Zc5%T;kr%zdXs+q1Tw*#?bzu)Vflacqs0(d~pn5-ilWS3x' - 'SLk=($rE|tTYrlYyVqejO!SWf>0!M*D~*V4YXRl0QOnKjB8OB^Qp!HO9%' - '>0n{0mp81~k6P=!_KufvGS(&sfO*oDG_h`aGECI5=&l^wj_fSD)}Kc$w(82v+H`0k$Z6;G`*?R`P6(`3exU=lV=;}MB}g}KF$ZB2' - '$vgIf7jWFmc6M^%s7Sg0b{&qVITYAwwj4)hrIkY^-K-iW-&si(Y<^XX2|af;ZjTxMz4pnW}CFbZ+hToaqDlqMWYi@~O%G0!mw7(l*>U;?$>!ho_5z-m(e--%0Y-LO' - 'pL34`)$=#_?M1VUA6~<|UQ98OE&`>O_K{(B(m$p&t&DV9?7z7c3Q#~7SHYTk+s20rQ@Nz8Xd}P#_U%3ad8BhEXMNv|XkV@dCW&*j' - 'xl6Q@C%o`*`>68XynR9&VhWrz0kF4~U5qSgo&BVhYH+Kk-IlvBZ1x?ixN^emAf{iqUA1M;x^bro+Fg_!3z$iXn2z>rM)n;&gS7uN^0JVi4`NP@2Fci}I2`q$g(HC(?z#wE{' - 'z+=?0#buhef%tQzJQvOSOCJEY(?NhJSgUfnfh`BgMdKSH4AQK;p;wQvMEbMlXP4si{N(Li*i6Nt*8cnPdPzwnv9uEWf?;y$QYDJq' - '#YpXPm!o+tMj&XXQm!GRO4=a|YmTb82_)55JO(drI7!dg%(8@)XPnuSHP(43vTf8SqH`2)N^pl;?E' - 'm*8T#+AZgegd3shY@2=mCC>o|H3erzRL6qYal+l%;xhn*x{-W8Faf%ZK-C~rsQRi}*Id!ei%%CB@liZlRHJLM$T?Sw07lt#!Ffc7' - '>1eqI9qMCIUR|$has$Z9KY{3Aop9eX*fll91HCB(5KEG)' - 'sd&YW`twWgnt@)r@@Qd65W*+-*Qi_o~wPv40fCXbp7&G~RvZEo;);i7Iuxue-3Vc;~~xRd|iqdXF?9dwKZN!LwhoXRlwq' - 'Ie2!K{rT|Z^yu|5Y~S9ey?yce`5{+wK2?$u#TT)VG+(m8mBaBBN^^~8OXBxn2X#aIWdeJ>+W)?*+nc{tjS95~Kfc)iKDnGOE^BS6' - 'c<~0F=mR9Gr}M=Ty2dy9faOW?Q}(g%Zy5X19sFsi`L|Eqsp8)|_d4QN-D+@dtz?0ktXYec}#$k;B+&xQxD`N1{6A#g`lUZ' - '-$aP8ipG$!xay7r5qfm-eor7G{=0WU#n;2zK5#>2vk~WoLBWJR-goaNi$+Rd4B<=rVVk6H=O|i`;H;?;)+EbEE0xon2Dr0>IpSew' - 'J}zfu-Q}_>{q=l_5Jl0alm5O;^}YC~F7be`bRZS|{)Yt6N)@YTxcwf$_D%9#@`M^}q8YF9DIUJ&8Y~sLrV1^N4@U*{vyzNpCK}>7' - 'mjer%q`$wzS>4<9ecyE!h;}ln1CNU&;ST@6-l(Sf_RBFod;jtk(197?9MvndLjyTq-e%eXlUE>J^Iyp@gdOL0TdkJCi6N4qvjHMo' - '-cpxUL5RTt#e2RStE~dyhwu>Ieg9pzi(lI*F>?V(_~q7k%XjtAzZsfgwqzE~?bb9(<=ODPK%~^YfDIC!G1Q' - 'qx^BaRta&Q|B!!Ro{Z&fha!ovLxxc|=$9hSqUalH4b*FgCe`h)79Ac-ZfMs|w0T{HV&Odjr(E=ZL=t%Pn!&w=m}B!$s>>~h^aC9X' - 'ScM2lp?9qXQNK+-{DPAcGMKSox)i<5?c0|8r73|RFChh_D6s;WLAN=cY*;7Aowy9T4H>WirEjXmO$lVwn;%6j=O2Noe*0AMCl1M4' - 'g*thN+^cVU`uSw|=8)aF7Q1)kpu0;5p7`&4gM~ff(UZO1&dA*{fNMYL?tJ@!$c89PBwc7B09Hit4?G@Gcl@RaCH^`X3LT*lk&&8m' - 'Iv>7(EFLATwJPrFBx}?#ca#(~`LQ?DyLQThdQ!Db9K=#w`0nwJy?4|M*X7!I(J)x>(+uL4-64D;$tZQHA{WlV^%ru^^AK===AQltRa6h*mq+oy{S<`3T893iY?T=wUy3OTbYD^QKy59~T-=h+^xy' - 'hgA4L7OGT61vmJXC!fg}Tz=FQ+w8pNM>b5>b|9)sfut=ywJ)|ndSy6eoUENvfH~~>gf57!bn!;&-d<8|av>+O(ULVXzq*a79OiAu' - '*RHX$vAJv<6fE6-x|;(9*zzFYSU0-wh|-3(Kk>' - '5Ihs;j^k)V)&}c`!oa_lj21dOh2y*5dc|t7i2Y)86KT7x1q-{ulsD!M?R8#XPs>XcPbO;uhU{D{ceuIo(C9mOuraH7oqbFDm339?' - 'T(y3R;o8e)=$CvYaf1Xz5B-$cpI&ubo4>j~KHO*?k$t(QaF;|3J<-pVlLEqN;8`e#kiN4X&icoHx%pj;Zf3TKU{UJqT' - 'k?hsH&g)TGroQjbMKN755v5i(2!IBbF^GQv>Tp?JrOl+Pfe!fN&o)OY2Slj%=MH;%6CFNHZ)J&I$A0+!0V;YHw2mazjTg7`C7Fn}Iy|%ry`IWm^_}YCSKJYEOttg+O+Bhv%+5{}?yrNrI' - 'LrCV2@}PH~T9L#&Bv&4V*&++{+<*EiakF}q876AcZjLW}WkR&l7wtu%Dmg~1Ox5O#c)Wf58j}__in(&}UzCCPR_jm~mCR0mJ~(-v' - 'ogV$?VVf9^(i)9{!lKiE^D;I;uvuxj5;eDaVnRdvYd16&?L};uK$$OjX~gXcy$o&q`7c#T<;EBvcab@dJ(rVluNq^pQQ?|`Baf_k' - '+1COlUn8&lnrHttaeC|K6eV98F3y&H{vvy~xk~nSn-eKv^JO5FWk5Z$%tTtGO;lD`pPt%|PAPxlU#RA|ejThApFfL' - 'F$VwuKN$c38~^|SW?^%5aBOXJFJ*0FWn^S&ZfA2Zba`-Pb1ras%~@@4+cpsX?q6}$KEx^%7Z|X8@Zh0c*I@;^t!T5sfMLjtMaM*C' - 'QYERhu9yG5JCb^}9H+%lGzc1ryf@xG_d+#_qSxhhF4l0%@{)ty7u>)`X~>vmhFjpfYo297-b|t>8jUtu?I2Azr7bm2QxLmCX$wqB' - 'WtkO9nn1HwdCu2vWx}r472$K1=j=M?3B0siGplq0f3TuJ1qr<6?@KP%+)E$});TiX(a2uWJd^tbUI~((C!lV*' - '&K0XDm0oWv!YeL~(xcJnX)Sh)RG+wLb8?dE5^XFMqyZU~+J}1nt*r&Zm#~Z%kx-kl4^$e`rLD5QMTvcg}$WS}qDZR%;$bj%h' - 'J2KzHhb@<&$wXs$)&$$AQdi-n_ji2XykwMyEih~uz1p(K`9esWz+^I6R$Jfy@MA-oI^B9&=hONLS7855^D9)iXd`YIqJC~1H|' - 'Oq|I!F6U^zgIjRbI0pDG0FeBAk16xYlV|MML~lJd0#dvpS5*&A5eEB_zVmi!j+IoRtzL95gG+#>4uMtmGct_=51>e;FT`V9EL59oCq+#g&!I$5=G@@!~l$b=4o>n#&1' - '2<(Pe?Zq3B`V1X)=soh$M;)5+Jnm4DQ#@fS?3C?C2S0Mny`I=Gk>l3e-=AHgG=CtmcFnsTlK{+lFFi&h4^*d(N`cq#?@-bFvflcJ' - 'ua(X^%@yA{9j6{f&^lA*_94xaR=gXg&@%noXsZ6K-&9u(COxpNo-xcjK7sL7co;xQe6FnbO$(rI3=0^<-vRNi`M0}T>iNH@xxJ~9' - 'Lh~D@vz#Mv)rPvf-C~KiTVZgEtVCCYnc(u8t>4i+fSmx%``Qf;HJpmdP%wHL{drql6E_%m#WVlMbHv`#H*R1E(B+m<$pOiE=ErWDc2rg(l#yW9xZme~Nc-AyhlvUTq5H@Z%OjN&1Q-4BECWTx' - 'x$C@26f^Ec$vXiE!VCHtIph$<&@$VA*btzs}ugP#^o' - '{<5)vnbd{(M20+W9^D-#+`Jc&$PisojWj~7TWbbIL>tW=+_bnj6*gkr2yHAqV6Cv8JM1+HhrC*_Tzul4;@!$GvfQU(tMxBCCH=XQ' - '7(!bo5mpR_qFn|%zobRr3pxT~JP4RO}c1k-2tYqws|P&MI!ns`Wcr7YSTAS@=N4' - 'khqKcJAW%K4{KvBhWmRNUbgAG%ZadJ_z!nAEnT;&R@{a7bf9>qBahKC>FW$CMAd+%%V*Uuy&&(1trW' - 'GzQ^#{}ZgYN};eu`z!aF1U>%YT@__b%k;Ew(T))Bloide6Y3}aMeGhJP3l0k~9oK#j-gL-w&(iEma`0M8p;xlV}}2Kfz_&D+KNh64cvKkyB>LfHMVp%8qL$@(}Dl8SzQt7<7RhY' - 'Mo*_H%kwnF1&_<9>(DS2^u7%rt$N@NEED{z<$+sKwbkl2xU?Mo2T)4`1QY-O00;m803iTf&1IR3Qvd)UMgag60001HVRLkFY;AKd' - 'ZEs{{Y;!JjZ*6Ssy=ik>$+a!`UB3dM_oX|YNaZ}(aX+Z0ck4Zu?RLrC@xtL?9mx6~$+1q{+QY8Df$(%PvaKQ8nF!' - 'Gx$h1MP(^u49aI0bd%Ysn4Ui4Ltp;q_ka54;*T%>^zwgS{QeIw(ZrAES9R4!)wWsTvR5IvkgZZtIIpTTDa7JZs!c?ThxHh1E+LtU' - '&(_#tN!mDpHvanL$zT4G7K?Vhz`I?%X}8zya{kx9KAB$KT(&FxI4#DPe%de7{Po4=v+C)S+jcXXPZzV>cl^Ne%V%YFb#-?=UA%if' - 'eB}&ZxgI|J({wq2PiuDBN^cBCqk7?_4$c@=M65kT(Tl6G8{eKj*^Ih+(%#;*vS{_T_' - 'y}l@SHC>c+lk)TuJ>y5hvx)eK*Vm`KzP|tZMt6~`a?yJ)rLEEQ9+YdMN~sH`N0ump&)csr54^tq(AU4c!wX%ti!`6N#qjQxPj2(8' - 'KD(^yqLljZRr7TA`Yu2J_QxN-<45kloNT#^rcIE^%3xY{(#vWv$+q~|bTHWjAKlX@H)-*j-#`oJH*YSVJ{j$rKZ?B@lVw!CxE$de' - '_+IZYXBA*BaFrxt(r_?otroepmNME+Aqaf1SUhX`_l74K>9ue+m?vd+J-@qZw--HKO)uYVzvI&T;^NKpdU*HO9MqfX^)&|j' - 'W?J4{!VoW~rCrbY$frMi|MKaRS?`zA>voab>-5ufcFSMfw*R`D-nKc08Zo0XWK8=Me_*6|%E7+7nJ;czy4s9$x_pHuA' - 'zWUhn@wa!kmqWJ}i<|jxoV{sNyp$M5b>hfvuxWlOU<#2gzeuE5J&v&I#tQkP1)9`3*4*I%D~^}o-4' - '|Ml6AFV4)lSpRMl@>cN<;RrUi`gNVYYV*az53J%&47bgNUYc#xN-$RM(h#b$?a;)IF5#!wq(Giet~j@c!p2VC22' - 'DgWB<^q561ZqxO=&u$Un<_Asl{)Z3R==R6oH&zX{VO`IrnwF%?qLt7P@>}bwfRgY9Y{~tevA(!rh&)q|2A9Vp%W7!7@6~uIYU{BT' - 'T|{?E*&sv{O4}}NGRk7S!HTkgS4F9O2kijjN`>)07*;-EL%U~icDeZbBXc(XdjBl_?dRF+%b)SB@uTn0P$ni9<67y~cMC>Js*zle' - '0;Dr%sZ_B#mlMLtaZ}TUa~L$+oGVs>+NMxzN5R57ps{l>n=@DV}leIO(' - '*dPD!=RbY@?ZuzI{rV4o`erMS9C85JWxK)AbGLU4T1kFY-+Wzp`Ht3SW|{tv(BUq`SyNCH-4' - '9a;xaTI)oV)<9EgVPL?BdIEn3&C<1GZ6Iy4_Rgjta|p&EAj+IukmF2l^Y1)KXkP)Q2O8Xh>dslnOe+xpswvfKFC?v}P$IXOi-t7E!x1kbJ&xoOzt*9dY}@1c;_c$odiz&DesiYJzP?O%' - '^S1qT@Oel=-&L2sX5T}qc<<_y#$U5fEu}6}b|FWfvdOVxgk9IkC+|`V@iB%mBk3aMpBfrqgNR-}sYsu0Ow7RDhWLlx9idQ%A;*TbzrGJMp{=$K;C' - 'CW>T2%g(vx_Z!0Ord@O8VZR}4KJke7|F6rGXSa#VGV3qAZwV$t?Aqv}aqt=~6P!dwn?Z(R2yo^O&(OkuVHvBqvoA9A3Wk=F6u~7?' - 'VsxcP=d*@QNSG5m%861Fti~4KwSzr)#C{lsSE8l^=4L{PLe~sVsw85WR0y@H^_+@>M1%(y-#SdX_EH4RTHv4Jt1IBP197tdz-R&^u-~RXmCSh$i!;r3#=&w%}vp4N}`uBEwaW$*M7uG+4Io)N<$i?*f=5AP#esMLuo}Hxv%bU0V0y8Zby4B|^' - '{xkt@P3v8oeE-e->67+$ahrxk;Mr}X`Sf~f@EAZFNlP}WUVsOm7{GdQnedbH?)-0a' - '^l5v&e^~C+@oGw+J^?zEH#f8C_2S~8_aanm' - '|Llr!;paAe^?EVC7|kV(uYY+t`?)bfEpG4HllcN~cd=e;S}xG5i`V@E!_?7Bi0JvnPY_)&j^)nC1;Q30Sy~R@_cX)bOct-(WL&?W' - '%zEeY_~nE~J{j7W)M;S{((SwR$;(xHL!W*#NjEo_?}i6d4iT&wkziUTySp-Z)80+4(#_=O*N7358*;MSpQhKZCYY%C>~``~f;yW|' - '(sjkh@I3Tn`uE}YXaS>ICxhoapWQ9!K`T67z!{_h4>5uQO7q8#Jo4!N;{`k8e=9A4FolpL6G+dYO' - 'c-t;p!sLv;GC^3!pU_x)o63T(GN2FZhgLQHZ2*C5we0Y=92tHf;`R!!2E%I!5()4=ih3AvsO<@nKvA%vcx#pe>EaDQq' - '`WfA6lhrQDBu%c{&zr|j=oSlZsN4<@9ecYmg$V<|`LI{TX&CnIerjto3Lw?+ue*D@dV|9;s$I?zb^qHK8u*;S;zw5)MvH9v+F*6H42!~^3Ruj<0HmDqtU@Hs6}Xh-_Ebbub~A#;|G4|0c0_ehzOa627B>gC0Q509Vt2h)f0G2CI?{vvbu`W}{^z!@I4KUt6NKpXJ5+3>|RW)F?QvF;!_9*22+a%Hh6BRw(x' - '*#sg3Hu7vT+RC#f-~+M=?ZY1cvzG+XJpjH0tjWe-_>Hw4z~wJn8GnwLb3I$&CnE*Ii;Q!+!T0$->@FAcD@^uL@O_33FdUSBHyFPc' - 'pgtP4*N@r&%)Os(+1Al8e!??90M}1=`X>VV(^~w*$bVYnM^OR2lLF|^P61XUgLs2LJ+wVWmVS3V1_wO7yqPm@+zj$!@%nal_v-Z!' - '^{#+7Q?vqs@VZ^z45;*vWC{9Ic5oQ;{{cE4GVWD7`{m(Se;PQ}9}36%aX7|9hVLy+plwL&sNgrYxJ2ls5WV4lzWPqOt$wYvIyvrl' - 'f7D!inBBrw4uZP=K;1sBZHyurzuh6*`HE{uUjpA+sozKTyX(yutm4CDc=ai4XpTp{8}K|Yxd9*FZ)aC%a5_gv;hA+Gp6e6vT>l&5' - 'xc+$j*7smRkE#%+b2M1*d%!MaJ_$AVJ^0f@Sof#&^Caz(ylZUBV`RD>9pYO#S%S+4EQQODMKk#j' - '%YI1+J6>&`C?fZz#5@498K&X(-cK*C(UXTyn0$@jzF9Gn-z~qt`?2`%^_%0efu85fS$=+nX??!h+WJsbl^=tt_g-}iya*F(!$0eI' - '+YJ=b9>Li5BJX=WnmiP_c+9LWmvPq%^b2ryaJ*EhOo5eLTOJH(vz*Sw@BwEZ2hlw*bpT8`WJiD~Y1Xs*yx>nS{>Re6hi5+!(0jD=' - 'y#bk*@qG~k@S^k(peXTm4WMW_k#1~lXvkr-cB8DkkX9VC;w}H}09Jl1yvi+Lk>}saBSH1y*G?9>4#Zk)cgYp%8RFhi?;{5YI1c>XYn=)qcj+styc8jP4evSx+3)WDh3+kr?K_y)Er#' - 'v0V1J;g-!}v05v|Pwvr=aj;HdGkXb%I)Ra>FO=jyB~hP%lc@W6ss=z+$J3JC-~SM1vinJ&ik$3y`lse6dmGwAQ8px_@OS$kyZqpY' - 'QCp9Gx#1*Rbcbb2-e)^6e*eGz^C8(83Y>h}^&P^(3Ssca)kn@2v-O7e1h{fK&l!ucd=$?aAMx1g&LO@-cOJg*%Wk?HmoV^N-IxdD' - 'r4QOcG_prAcT62$?oix3$jV!Of8Coi_GXjIw_N4g' - '*XPHvcSqk&P8(R|5w6z-piAd%@ob%RcQ0FD-rA_0m;HS9VC8Pygqc5t=C$sg)L9#$7OhRMR^v4E;_iCghjkj?Ao97!La>`%H!hm&' - 'g5#Cr9Kzz&=N_Q(#zgmWP6=lN(L$06inPZ%Ncy)u0CJ|!PVnKIuTty+)d' - '+3Xq*Tg#VKtE-jlwVf|bdE8;2bUyc9BKeRm!q9s?J{qs_s?E^3+joN_v;`5vzPJz;%YT#!&y_8pME0#L=o`rW{|PaGIwC%XXEnH+sXU}9ANeG^WRQ@&S#hHCyL$K?av9E' - 'J{4|>Z5^@x%{4%f6-+w3uInqpkGiVM5kYdHw#exxsTDX8DthZLRTcc-b*$-q(s3;JGY*)3#a9' - 'c4GtnU%q<=V^3qVTY??*Yru4B*2nn9nyhWO%y8~*=2UVg{nzW1hJO^DYzedyZe4E@av7V_?xMTliVob|vg_W+ssC)oVfw!>%@cefMw)!8caPL@|$ESrbmY;BzR#q5kt;YDdIsjj|#pRWxh' - 'fB4w+EeO~3v?HdSU4oyCV>`~ks^KAXalNqg+u{AD^Ly|7Y_Rp|(EjCM>6>+@q58ovVOthDvi4;Wql$l*C5Ps_&@Kl4xx=KEqc_~q' - '@fac8yC$>xJ!|^^J9ELql<%>sN8=EFuF?wQ1l-dr7waFd?C&JbsO}wfy#Jt&KCii_fs6Y?yl(IFP{z|bu$MpnWqj)P9X4a#uX|U%' - 'sPS@`3w*P^THa{0JdnIegt=H9a^r8WXN!v=1{8+2;qI3~?p?e>kJ|NM7hIb_P)utU^?dy0SZ52p7|u$pYcEG{TX2=NmX$Xl(n=+N+Af^6He*eT%W1' - '!eJgrJ(I|W+73%-<6S`NEd20v_3N$sfP!r$S9IIiDy&GMrpA(~QKIRpwJ5g7DwbcpS8H?hqH*M2O&4c)4fz}6sz{#qM|EDYpo?p{' - 'U?_!@HdwD^$tf!v$I6<6N$eVn5|n8UaA$k=vUrt?O+{EOgo`m6A7m6PAFN!dsk;z8-k>@B-Pk7c`1pay!x9F?FlUr_(A%6ukJ;*)' - 'F~vS=)4jE}*{WsJKG&9_#+xile{bdE?0R)wda*T&;(QdrMod>x$y9bb1*uc1&Z=m~hy4WZl!v<6>j$_Tjf+;5qOH|lDc6PX)r)9-' - 'YpMsBVi1guPyV_w^VMvxDo>9lws05Whl@Y|$%=2DZhv+&^#zk5jOe9?>UAx!Y+*7ipgFA~3!!JxZS7lMKRo?KVZvEE-B&n=sK#Tu' - 'QsX@>PS_ko@kZ9F4@|!@h4rAlDJFECy)`*F2pw+~EJ6l6JEPw;f}Y@Zc#EjAYF)EnaESkq>F<2q7L05#a`e=))_G?=YwLng0RsX5' - 'Zl7lQ*YWgxhCq0^bwk)K^epFh$+tH(g|{jTZkxyX&HE*V>*aaRX*vp^Vb+($&BqOm=Z`&Vb5EGT3ka2B@S;SFW~rj35Ih2=>~N2e' - 'T((ZO&=Fzrml#Ec@311g%u$8)Jz?;A_#Ox%>Kr}HvzcIE(?a-$K&UjP' - 'A0Z?bk&5w2B$=gxv*nz55y^76q?K|X;A;C%fw<_#J;-eU(5eeUzJd*;XjD}QAxbM9q)Bl6Xp)dB))I@UP5EM3Dm*&pEkeOT$Yq1O' - 'a3DwK!-' - 'X1(ef4@*MSES*v&MlZYeC4^=UMO|5=P1(ViQi?M7Rs!sQB*o>KU' - 'aH1w`4S-oupa6B{)q*fnIA+IRdeTKV#24oTFAY*9q@1dNP=vVIj9IjCAO4PKS`Gs6RjbpxtIvtR?8rR`E&_s98(0BEEj%>I5+s6E' - 'Zc(*{kc41R;FSotVEIs|&MQO}-e;g7i}OJU9$MUm!9oM}qIAF$3I7KGgBYS&gZ64M(o{%e850~c(yTqmVKoSLDI}x%0Srd7yFfVl' - '*aRIL(xHhWFrh(%4QL7%!Ur1h!KI@?7}yP=lb}vi_EiIdwDm@S(sgN}P7&w7SJ{1W2#^5`BXz9;7?8!=dn+PzuuF*Z13gU1' - 'my%TTGTQ{rt9$5He*s}FXljq6FvN!KP8i!%!w?6kO(#-UqDWn`wi?6Vv?wQ`uvJYM3A+Nur|O&oKAM(nDZEQNR_`Ed0riL0K6D4+' - '3P3RnW(cvY3Q)ELuvEk$%sd80LFRUFelRt-iFQ3$+sTNnb6-&S5DMS3kdKDp_n)?<>4GdkUywRplidSEuyRBZZ>te4+W@4|yx`Ur' - '5GvPyGinabXmSL=s8$8s3L}>U6kv@aD+oSBy%;?2EeG?gx$zQU-g;LI^kVXxg#)Aa' - 'Dq(jV5)WsqJ|_^nhd>Mj(HhPI4Vrbz-PWoMB2=Y|XHn%qy-C0UkOhLZfvya9Il)F?A_sxk?E_*BVgu(*wF>Q2XihbZXWTraI*a3hIE3yphD;#vWEG26#?&5Egc^oj1#pROOFATm' - '5>VH`&LH(WFMn;dwwSf|(Q#?JIT2fK(nV8}k+MbY3`Bctnzx0T^aY5url~y+#3FEs%QZA65#W?l9)D7R48!YobK?EX*_{T&uo#Cq' - 'tO%K1BQyjrs`gF*Ev0k_#Vu7}emi6VBEWGoh9rU>csN~=5Y<3(VS07&kpQY7gwIe^wrL1$hHW8=>ENtiwFJ}tuQ((t#yG5LC#r&@CIQZNlU6sC7bMc>7S0!iYcd_g0M#0%&V(SIJ_jC' - 'cj`YpG68c1{J(MSn=1{$76X0CI|?z{3~R!Rj)uV)dx1nZ2yZ?Bp&LOMgE2(Nr!P0D|I3pMlOMnN5R2WRvAIe^nPqP|EnGRrT2i)B7^7#vy~&T*DL6C;7{@~L-=9(jCDuz' - '7r>1H)pfAsl#GB{U?^|!s93sjfSw|=TCKJY4s|B*KY2Fm!yoi6y8@xMo*khFlf`7%4gz{=N+J9SC5c0Z!7uZYRNhcY*H02e8y|hx' - 'ntHS~ZK5?PC1G=c#~>7VJl4T=fG#!J09;Yd1N49;-0IT20$7IzXc-kSyva_zAM$}wv-r}hWde>J~z0vTP9CWiu#%xUH_QEQi@azCo~P@`jal-5bP4Q^Re{8-lp+ICZP0haY9BBsJfwXZd`vo*YcKsS5QK^GTg&mP)U+6;iqrF8@DS)u8q' - 'F|ZAIfq^Iq!GJIds{b!6@gPgw80W`x#0SsX+A^svWFufFP<|nD5kF;8eD4c1T|oTxsms>0bWj4Z!FJ)km;k&wRKRAb21pEu' - '3~|%>&&CfAH?fuAfk-YyOBur21j)VmP{4+Y&$0^DA>%Zpyo76rED+mea21TkSJ6u_n_VU#!JZwH_4Q)+2;$alfWP?G$+AcHxMI#i' - 'AzGKN3y7LQNXQY?8LkdkYSk2LsXvVlRR9@VAs1W9S@^V9iM;5($+<1k=&6ynX`sc4<' - 'Z1WWvzPm1X;483UjBa~7d2#Oo!3P7bSKk_ILJZXh)0_u9&hW!@xoEfJ@n1|W$TtW7-R8o<;b_9y>ZHW$*|fCzZ&pVME@y>B-~SWr' - 'bezDYOXDq8^ES@Vvmwj>%zgE+zbswY>U?+_r{UfdZq3rp_v&!D`6Rc?+0V-d#AqK*%jO~DVDn@}S`|y=A!HtHxLz!4UW^6WHxg@j' - 't2Y>-b~)h~yhh}imfud$rB!{V<++)&t*ew-8*n`0FqBY(v7iC=md`(*{23lR`0Y?@Z4gLHUtNwfe*B)-4COlSc@59FU9Kt*J|c@&' - 'p?DLh9|$DlitU4jXa=09NF$xiMw#Me0!zw51a6rfEs$zg-5BT+X^Fr^dr&=yV^B~JfxArpPKbQsTXJ|A383$E$!-eR@fO~-85&Jl>NIiQYXV@84GgKTzi*LIR%h{?p3AcGsVUF;AH' - '0Z<39Q$|4jg$Tk8&T(Ms1tfY6y*UG{v<)K%1624ro>5}LFi2Ygpdj=G5T2{H&>=-MfzN~V{-RUQEyUEPYXc50jG$OSC!#kYwIUPnA8_DPGD?J_37PfUfrLd!{s!DM2(1ABrbQ~CcRbCZJpQo@He&4psBZkorypk@tJtw{z9h|4X(' - 'LNwpO4uN>0jqQ&!a#k})z}&_`M+*L=JE+_?Coc#WA6S&YJ+I1QA4gpABIK2o;Ym1n1X={PJ#t-C!|<~' - 'I3l^-Zo%_q>M50`4{|ZLb0alH11qtZ2#Y8L(U5#@oSX*XS44`#IiG}xqp}pes8&3gKiAnKUhfML`$ELN5V0>r>=O_%w<~V|BG5-X' - 'NoN7nZJnFg8SdO&Db%VZ4(Ae&XJO47QF7@@%C4*gNQt6slD#`25!%2?l20z~%^Pq#SIM-fG601Q6QyETRk2V&a88RDLhuT$gw}G2' - 'NdgCbm3Rm3V{C!hReSRWcD9yv4d9<4Wh)f;k8}vsu5n)}SFj-0@jKvb3c7@${6RHAk}O%9i`0Ab26nb)m5tVd3)rYlEVj6&y>5=_' - 'p@!rlwB#yt2`t3ClT8`#TlPV%mjDMQ7${H=VtRm|#' - ')SW?kU+M)bF(g}k&%*W8daia@>&&&Zjxn~*;3d*Y#2v28v}(*shbQ-JGc_PaM#8le50$sFD--AOXpw6a67sBr);7o75K$7W-uPrw' - '6SXT*L2hO3XqKl{5T0Z#72^$44C%CJ(YfjHIxSjs7eUXVN=kHaDLAq5' - 'ASlW^NETM8$hNA62{BHN|X?1eRcWPTbpI' - 'MyEA4v<`=aOYSFmYNu8%oh0KX?+vkO=R7@(`EE0;iVbxgWcCk4bDEM~b!r?f&KEz%Z9PMb|FVAl#zTmm?122?zXHF9q?17|_T@DOmPtb|h$!6GH-Rs|Ja1y9dprh8k&B~AH>pd`Hq' - '+ktgKBJBs7Kia<#AAovFFEpg`9!9SveCA^-p{1ttQTfk*bduczg&' - '0~(>MqePdD<4LK;3Xr+w?g0@%n`+>2DIXY}oRqUpYs?j%XJc>}5Dod%5xQX%2&~{p2oA=UhR=kSfr_{X=?&-oS`0HYDK35t~s=~W0-k_K4fYOo0yqgOQ2rVFl<(4h)sY&d6gpI4NQyX' - 'L%gs_WIU5qe?TrlmC)A=uVhij6jcRz?ke-3gff`6BVBW}#GS5blM3hq#vX{AA+Hrz!CY{SbezE%;h|@JTBexfA-V{kj1IFX5q=PM' - 't?W7!2Z(2h6^7yiqoWZUc$Ukk&Sezkm~)e>5g&D&#KYJKHT$67*uW&Z8`xWtkg$(rrYhA' - 'u7nJCFF}Lf10sX-z)=;l6teZ+tH2MuQAc|wXp=1>YnU!V2jaQ6tk(<0C#3TxAm~-FkVEIsdCCWUl3Y)fKoPHdvkEg54Ra_>No4ro' - 'sI@1!ONgZxAp@RlJaz3T(gmz@XF6({rnhgV*ROJYzU5oH' - '*DPZ8Qdwvt1&}r}u+&`F#q@?3M}Je1EXRHtM~~3dS*R|95iMf+JoPj;MT3e?nABMy)9g6)xqZwNTm(ukMysJn1oQ)}o!f#SSqY>a' - '3K<%LYni%Y@+2M`K`aql@oPvN`Hwi}2{KhH!Z(m}sI1bOmK66++6Jnaml+=seb&1(P$*8w~D' - 'jM4}Sm6<=FGLrd83aq9YX^Q4{Q3wHA$hr-3>m3JKY)' - '(L56o8w`)7Ga%~|w}GN2KNAJPDX0b=Qg~lPhci-}woJzVtBs}iE9LBoq;EfCD1A^PC)w;OXnQO^GXWgjCt}iMb;Vd4APW_%L_3I}' - '0@Fy)M9O&~Rp5EOb~upku94%R#FGe?OOa7Zt$CJO@NO1_r0%#cU|mRH3fkL<$x5JJ7NpE*sDIfGgwdU4UOdZp)~YdKt#puV(H!Pe' - 'GqolOfqx}tI@qIwsP7g5DZ-d?gTQo%blhl&!v-iwiE0SXO{vq$yuhvzg&}eTOSkLhD`=@f@8J2+L>jsc^TTaD2x|qrgOHXk@w~bg' - 'w#3uQya>Z`SiN<`!InbN4$=-n-T_-#?nNcj!Q{b-Ge~fETrwyug#jPn?%Qc)UUEbXFt8rySk1ZsFQSLcU^%u@d6X-F;tOC&qgTej' - '>Rlk^Rq@IIW53{q&ijNuixyUsd}rBZ3^QCB9fmZo%+~_4d#FDvPeP0C6#Rl&dSLNNv07r)zBA>~R^D&D|Q#&Cv>p7%e~fW!el9`PYeves#YKs*Nqj6r*m5g`ZUWBsWBoNh9ZZFILxa{P{jLM' - ')4N;Cxn(dvlfZy%WP}?oK0zFS;zo>PQ6+pSGZh9R7`zFVfuC{LRT;OhHM6rdg|AC*Ag3c_l?N(o!$KYr94j!gCCO_rI`=+hjIxy`' - '^&ol@09^QAD-5fh>qF`nT_D;~z`ZKo6~>J=1Sf&%L!|N^ffz`lU{Z{h5#x;+}1s' - 'cCmqsl-v8pq1yvH;9X+3LQn4JQ&P97{`T?L&w^a_uMuGB@&7?' - 'cwzxR=lS)ds^hYqBsFNBmD!-Yz@0oG$l2r()XK4y!<%Ex83lz0bAq!t#5`z?UZJ2_qE01AXv;@AbXN$Qn3Sf6T1((0iS;>~%7{#$' - '{7eK=>kH#{ICS*FackcvRs^U8Qw@_weAW%7g^}RRX1*T&`JM;0-H`J0K$qXzG5P)5wG&LU?7O3XB5lWacZY#tBgd)CG>$c}|T1Wf2;S+x=#2c<_q$wX3aN#%U}-k%0BVyo1}FvBfcH2p6+6h~v<~4stG|' - 'o6b2>?#jj2hVbZ4t4(7De?+&Hgha$Va}5&%i--t#&%6R6LVAY-p*&RAF!Nyc4MLs?SK#?jy*%+=N&wQ7lt5oYmdyTfAP%}PBa?X_' - 'rRQI7D1U!-?6E@9|825W9eFH}IGHD&*}a|gG#w5&_StQlY(hSMQqmod0bkC?k-b3s)A{80v+H(_C;m7d*j;e(>X' - '!x6*9YaWYSmtj8Nhcs)0+`7;G!%*kJS{}`=3B~gWnU~;56=md!kj5N+8(?&@#Fo3^%B+#H0CXrr1LCz-@i6A>Ud#*73=)hQyefcH' - 'LEVcotP_TYFxoZKj(}>>L9L0>8$v@5kdgRFmWYG<3SO^`Fnfh(i5K|d1TTkipUz+JnY<4)' - 'g!88#EQ5-1tw6^Q*P*z=0RvezDccr>gXF1@x5{_;5mf@%gzEv5oLzy|7ms7-&IM5(kjN=F9!cUYB}!{n`T!CkN#nJa@x3gQYYJ#9f$^{%7t4xzcTxGlo6=ju~!4*bG2Zi(' - 'TyI%N(DfLq%rW|wqSP|+u(JRuj2SlWPg8pegI}64rMe2%7G&h*J;IWC_?#gyJz}jsZAG*W$r2!k4n2xs81NUz3k#UiA9%_p>ZW*Y' - '4c;de54FjZC{SFNp%>+8E26w1G*viEv7I~SvMQdRFj2z*@$+FFsOmmksH>UC&8rJQ({t|8WP9R@Xyx_L0d&eB-dH?~i^(A6x;4@)' - 'II+ZAM$0kevAUe#3J~cAI;EBqS45>p5Cs{MR^=iggrKgnDXJl!X$UXik%J|GdBfsJ%vp%A=E?&d@a*_&wkM0vD63I^=7B$`x6pkXRii@rZR(k6viGX>k__2nf=YFz}LR&W5?|9U_pbqA&mykQt^y' - 'R`{!xU_BATEJ^{H%Ffof3+ImVCl;%qTHTm5IfUS1=2' - '-&_u=qHIZpXGGwrOT;O>wben78-Kp7_V;3E)' - '%?NsMNueT-fP2*tcuZ!seGGI>EU@CEeEeW0Rkm%Q3jop1%CZ_^D@4Tq)}a_QoKV^#jtio2eV+Vi+F*)3#I-FA>IZxiF}0auc{xR{' - 'VmGPy_VPNtdX;W}m+}x9&PLPP$twU$B?CkpFP^F$G!Y}f<%o)iWbs!4dkFcoRb&QlleEj$xY%Oy5K*8OMu5WF#A*aUW~0=BrH7bR' - '3S3+?U4R>N6A%FBL0)5ROnJ;OBpXVIF@WeRp+I$8^yoTpqx@Rte-8$}R~B~<2!5WGxdCCge=JTY5^5Z#9$}>`R{Zlpi5_}q%|8hw' - 'L5z{$pQ+{3Di`?3VjN;Px?MoYbw*^8f{Pj|@baLTB~-6Vm6zK?ba~BkFcHGJTls!brN=85C?OoAI>b&d@Fpl(jZYe@{IH2?`P*nK' - 'sJ!DjV1#!Jo8o@E;T2xx6wr0q3txEqmj^IZ9@FMU>#ZagxO<%&_t16!*A2SWcd&?@94jz}8VjP6gg^yPDzQV^H?G8pcqHg9EqQk~' - 'tYLm)%{@Ukip0zH5Q@C1T|nK598?7+CwE#2rIeux8}BF9_SZQOE%s^NgJLYD?C8s8t4`)' - ')@$&%z>8-wZ6JJz<2i(we#;xrTEy_uSTAKb-lh;Ev~Q-@462rkkwo2KrF4MmWDeo3+7G)&2;GkX' - 'uiA^eUh$66B4ZF*y40L>te_Wy<^dyC8&FPY-H#Wb)m9s!FcAe2HqdQM^pF)KHH%;' - '_XgKg#weYwM>ng|@rbRA+>!L=v-rneR-kIPunHKUWY!|cvqI43!m;q(z*WHd$!J}r%P{B!7JrCEKwdfFekJC#n-%DP0#=}R' - 'umTKj<`Do$Z6*OFA7H!r96%>bXR6yJ$tVj6-ZBf85y=}a*' - 'NZ1Gmu3(i%vw|QsX4n-iqct({%1CVm%W{fXG^?K}0aav=i+YwP;tPwtw#X|dedGMTHSObP%a' - 'i`=LoYO`cuU}234!Ed=0ZDg)2P6G~Q&1tNl$i!p)h|b>O-8C3q7h)iDsuo~NFFutjyHZtiLlfb-aS=Z9LeqOEwGOg^1Y_u|uLAYg' - 'SpQF|DuNRRsz(=A*@DpX`q(fIeg~3>j(1uJz@ai@v=vr{Kp=12CSZB7)#IkEwDX~-Yf7ZDZ8GPIiO*gZ0Ido>G}t!Otg}u6cpQu0' - 'B5=3}+fUxuf*4;40=9S@AfkZKjwTyjf;7=(8U*;&aP&8_#8`zGEXM(afR|y;Gi$I07lZLSo6fs=ooih?oulqq$>aEjestkLvHq@f' - 'O5uH(MC)&Rqg&8tf*FXQ`yO;Fr$$YqZV*nj~9bKE|w(LQBTQ2WvtG^k#' - 'mhYyQd`G{Pm}BR^JGnxCR>v>cHqNVR;@}RATs?gIjazxwDqu})?IBnLX5NcQ4K~{' - 'hyv*Iz=JLUA*2TISpPW7db_Jwb5+3(2iU#UHg#SqQHG<#!mtXL@*wFfBX|4}?MUP5jeF-TT$m*ShYfrq9$G~F+>@M3pY' - 'wFjq?{I?Lz@N5B4h}lf02V)cfg&#TkuB3862s`%_nV-GXEw}~sy' - 'HS3aFlg>q7Sh6WrbW(WUH8Rx4EFhzyySco!nCJ`9J`U+=Z1nL`y=zn*!bun?}BaZ#F=sd!N-AVH9=vEUumF}X5i7lcbN' - 'RKKb=|491lz1D<9V^%}zA|`9FaGRXQ+u8&u;{75mXuL7+DT=+YGA1)UoZ=c8K*oOY$WKnn^Xzi*cePE)S3i0G`2VUR`_UC#*YxzO' - ';hE%bH@CCV$-%Ft*BkaI&&B!V+u;(k@ip&O$5GfS3`RfhdW$gT{5B>i3#~=Rte;)=4fBFuZMXm0JoA7S9`QGc`zFJ$&1JVg*X+r1fc}tQP' - 'Q$qeDsAR;LprLd?KfPo@i=)@|Tu_C_w-8-@gKZa|w3VHCL8+Tya))$Ehsvu$wmFv05h6iL=_J6uSZSlFQ~@JdL*0>2^{TaNXj7FA' - 'hqj7!A4J=;*)zBbEb;2>|4pHD72C!I1RgkZsQVAt0rcW8&xvUx?' - '7Ntmrn~ki{2i{i5-_T}q9?_x~X%u4GYUnQK=Zkc^DU=GtaWy*fFqchZa8a4ttgh?CYkrbL7|XRt&zg22bU->*y?1TZ@~=^!LvA$%LWIm>Zj!j#L>2?zFFa)s~-uBeuTy8kwc&GystIMB)E1TZk@<' - 'bnMMZ*og_S9xju|=8|H8bMA5ElJ^$@W8hi3_pTG;A@DXrFnr!CQ-)$VuuhXjauU`gR85#(eSBqFbY4UWkyrk6di)$aM)l8~n7g2=r;Ga!RWz65XU(PPl~2g8YMVv7)KvC^p6YNcEq0j`)5uM$K2oIAS}LH@WzmCF`wu*(+#&0Xhpb@vZ3I&)3e75L' - 'S!xUgYhjxQ6|2r?i1?T7BF*P*K{UCYT@H(H%R7UfKDkMY*TbQ#;qz<5&|i)BBt3m{cYDd-zg{eE=D&IVyk4H;HKyZH`C-b>0p!nn' - 'n%' - 'fy_jhzVM;)q%&r#Cl>O5^nSeWRkGWe635#i(s#$l;Ac}jVL()BV!TVU#u(tN-U9ZdW*cpCh<6RV*1;wo7PM(uq0>PcM1rv?XoIz>gX_?AQQ!fHv' - 'jUW;V0$hmJ!N7P5G1=MxLxQ^}zr?ug_ty)~@-OMMJzdT})N6;%-g8~Slh(qpc!dVQ`3x>1p)z>XTUW<}rBOBm8@Jt6+YOvs2^}LB' - 'ZFv*l5w1J7y6f7p#wv}xJx@r?Di8|b9U~x2B}`GWnFmf~3y3YObOW!Fyin1A6iQgdVgEh*ZaF&-oY){1c$<0D!6<7%73B_Ah6={hN`nsNO%ufr_Um~@mA' - 'F9&6(C$mH-bG{)7EH4k|X-r9_EDB2}D4-s54An`V%zb{V926`Fdk@-x5*gB&%Oya31mG_V%XTPPsIMw<>oFyrB}x' - '{)-^;zV6?1cT6JUG%wiX8fij|Gt_T9^462;yr|7|-d&#{Lo{?MB7lg{Rw_bbm2#B3@9W5}Vjy6?hFkNE7uGuNdIi-q*fN+?VeM7c' - 'F{pvpRWlvLvqmK=LvlSOc6USs_;#x|XJ6l&)rVIqY1M~PZO|#)`w9(I!NGP||2u>CX3Im>$6wzT~=Z%ASf39GWTtO%dw=hCA7tz6KvH)9v>TM2O%R@>4jAhox1%&6oJqQ23cLlTSh9PJk&w>CB`dZ1gNqG?_<8i*F%p-Svp_bTwcx3m!f4&QSU8jU#x$;8shN@aG`bj#iLIf{*JJ4' - 'vD2~EPu-rk;i20Nd^fxOahdz^^(9o*6mv1wh&rw1a_`GK+uqyMgZ{jv^85ulv!fkGi(}*e;Nt%n5cnvD_t;C^g92H>s#%3o@{)0*' - 'yY{S9pDVyv!yk+n!g7}`FbN8LKvbs6D_x9Ey~fj_;9+leE#S-X%4}h)CrxjbC5@zVl}CqdQ#pGmZsNYy%yU)XtG4n!;0Pzqr{V#b' - '(Bq(hFr)xNRvG{jfFLT~nE?8jf{wB>zqgp2%F&>ZW7V-mBLyIg_iI6Tq{LgofOnNQbcZ6P(FcN@X%UDC<~*De_QEt)i%0nC)kbDe' - 'I3{@}XvvNs+C|WT2Qr{8oyF(+2Y|wRgSGF}>QFu@cT5RrTJY3VtPEOR#5;oW1J8p#Q44CQkx(nQh$;*GImYXfgTPoRC^z2Jp~^2Y' - 'XkU=wSBDH*h+iH6#x=|F0o(z<8v^a{Q`T^NdMMB>JN;gzppYEvw`frj`MI^KwJcmRM-X+vSg%vf>0vzE!*G4$kW*bwdAT5ykS0C(FF6_yacx_ttDx?szbSmSReq4^SwU(pg_iQH@x-uY`$nW(hBirUpBq88bUaN' - 'D3+*%Rl#@(C%@)i(HT-0wzCg@o-STb(xl(c{tg<#AzNk?' - ';6ZQl?7FRpz*!p-JEqrTX2tNm#iq6?$C;NC@PErJ-DeYJCUhm*eZHKPH}hwc>)GUoA74ziePT@Wz%Pd3tm{-n->#?@WMqYd^Rv&;hD1H&F?M#;o^bXTXVtNKpH7&xZSU%BoN-*7sy`1emvjkV{uMDd`B;<)ekR^KTh_@|R^%JJ)V)qf91' - 'FPqi+@f=`b=T-Ig8f49YO~6S2oGs9!E0!*k5}Yw5+T>!h8zCmok%QG`G)fu?b`yzK-KBH(uA!o4L7lIL7;D&~I*oAXp-wa1*Z{M`' - '64{2IWhM%J4~F<=sw--SY_W%G^u48`|3!8j$K^|=x~C#}gg)5f5YpRZF2e2OI)E3?_HjW>G8XFH1+sd}+B!uk5dB034PCklDT2dT' - 'vD|K>dRh1ixI2jwOodRAdPT-rG=K)?jfeJTd$xXj9KGasu>3X?}H4+#tjLe9ObRZPrO)e' - '^j4e}$s_~tRyr$PYwAc%t4m{%+SI9UR5@fx@+a;=rNx4jVaXyu&NLgchD@OnpIw*mddQ&nK!(k89Jfp~)Qc1`&Q9gml4%5r;wbAx' - 'fl=E1kf9{bT&9$!EQ&RKIAj' - 'C-AYWt-ryF{|Q!{W+ZRI3cU;~g49{uV<>fI9SFRLy0+vvn}$fkF>>>1?{W)P(A8^FE4u~hSr$4zO&ai+Ps;wMl`0|XQR000O8001EXmU6cI@Bjb+Pyqk{8vp?y-E^v7@kHJa=K@3Fi{S{$O3hR7;;6VgIaKVE&ktXzHX2W!Mh9o1*e!W@2Yt^e%Y8ZxhydKbGqWUmn<9a~HMz&!g' - 'm_^|g%|G7HHl}pGQBW$HkpU`!l1crc3H+k6FBkY!>=v;`YVf?fy!_Ms1`+tN(Ug=HHrHc>x3-Bo_a$65h}snwx}i#OhhUOOcX6nK' - '6O;>;brbGxCn!qjlw4Noe2vDCq-|UYR?<#2gjm3xov{WHUjUXul@vW3-Me$6F%83z(yY=T=Xv*CDQDon*CG?`=q%k*N;zY#IX{B$' - 'TcxiUae{Fizu^h)?$a+&O9KQH000080000X0DvuI?d$~r09+9O03HAU0A^uxbZ~5Kb1!XgWMyn~FJEPIZC`S4bYXINE^v93S6y%0' - 'HWYpLuOQqPsgt!N*apm<2Nc8hvUM2RJ`4x~nU=0>qB5nBlG6bD@jI8)r!BXs5hyl!dG7bQ)N;A}P|ICG;`a~lgehxjo&_Culx0fq' - 'tx!6nity02D0A`ANNw}d9LexrO-9SY#*4*d<42Jmq~22|c4d7K^?`(zM-ozJB9%oU(o)w(rVdioR#ZlvWbJHNv^E^5sK~_P4{7Uv' - '{(5HtN$IDmgi**txGRw7mIeSE=_vy2i(_AR_n!Lkx?!ZjzUezK^A%7JzIOx_@nz3iq)@^h4n(<14G`slP)~DLUu%^' - 'V5xZ4zh;t|^sSHwOo1Lv>N;Y=hFxo58sSe^ejzku^AM6Ps2jP7jU#6x>CuaI?A%`Fwj$d@t_8X!6S;' - '8A7ad>qjc-qb?EQCxxQ72U(HWZi8JU' - '4v{F>@a=Xswj+r*Y+>Z>32q~Szanv}`shOdF~{?M9SQ#H;_o!xukIeO_Tb{x0`x}#i=%jc`_V2_zR6xYE54g6(-51ihe1Y;A!lhJ' - 'trhQGz<%!-CeS*5S7uE?;aPy&$Re?dn`cZ)i1K4Y*knTaM9-~RqsmqKl;nmUr&;xTuD=qcRh=Y(r6PAlwq`nWN&mal=Yd}Lm' - '822Z_ev1_}q*Fzlk=ytNg&jD@U5Q77B!6yilAD_w@%pt0yiL+DM+P@eeI&SX>gS3!eizpK^g|fuMTxcufd`=haQOC0{7F=y-4$pQ' - '+@!!_ni&SnMV8FTQvtSt>)3A~Qejgj#dvTQ(TEN08N?*JCePvfvJ2)i8oL}$PbHQflY+Fb*35-d{O5v+;PAgQc~np@J5JIU523PN' - 'a%jkNQ)OJBtc=gPQD=h*YSjO_&Kq9#G3mL~`?o>X+`eav22$fm(fkP7HV$VZAS(L@oTFwxZPN9yCnt' - '+GD%z(zo66Ol$`O$W;{Ru_wz@L5{$R5><(j>MjMyHY!H8zjlo(fQpl(_u#l97H>gxm|QsEDg&sg)MGb!wqk4&_PeRw@>yldlgemn' - 'qOi5XxIRehL|sQr;rwy&1PN~sk5M+HHOgN-_==Em7Z_rxO!#4Y7=xvaG2AO-{ci@f2tM5Dx`^$8=Q3wsLF9EAI4LIe5$4;bF82o4' - 'h+~7pR{HjX7F{)5*zS9XQ#0m{|`W8yy' - 'TdqDQ$|ev|Jj#O!Wt}%F1H!&(WAcX!?Yu!>B0Qmt7aSN&i8DH>6b;|^h+nuk$cR&~T&H`EwFyTwX`>m07jldcT#$y;ac^Cv4S?Z@*W7+&$Q@MeW{j4`$w(I@AM)U+&#aixp!n2=BSeBH^FwtJl73(|16_+KbS7I9vh=' - 'uR1T{mWk|FsO6RT4KI59cIHQjp_KR)k3NHWF%kW=TTiTt<~?pNyXPi$x8}aU)(_EzZ8Ji{b)=>1YxfU#sl9gnz&&?v>)UT0PD|wR' - 'CRg-+I<&7KqT`V>{aB^>AWiD`e?Hp@!+_v$%eg(o#Ytp3{S#~n1' - 'GVDaQ<p6BjyN4-9(f^1$rsiA?Oi-bys48Dw@xSd5*@HuEcU$%&_NKKFY^``sB80@ESh$OTeNPQ872sRp%#;}=rPLc' - '0ulUEkLx*xd0dq9u54=5<>W0%f*{!0S+vaxW!a+d`W9yyDp%{K?ORK60' - '>UtPo^feWV+CFek_O?8Ep`I)+H*AW@T=3H;b-u#rvlR9d+zk+APQ8(*W*}f}sI1Xt)y(pWgb}l1HUpzx?(rQDq<-Wj!R*%`(6cJ<' - '(BJwQnl~UkQg+}P3cM(5jPMl(nG)_n$YwQb!1!j(6-M2%B#1P9JIBe+&Q5mz>h$#F>;m?CeE8(MqvNMkV4y8OEXomvlmDI$PO9RX)R2HWxl&o|`(c>Z`7tf?{O7' - 'I#vifIeGE?_*;#nn_ym%AV$GDZ@V&h4<2U+M1rz;Q!mQP>?*H%9P%Fr)T_o!1r8AD+L#J22Qt`1gm(W*|IbCw*1=AoxuBM}4TM=*u2RQ|Fobp>b^-@XI#4g0s(@mxGjJ&tUcp+yMukowOd=4(DM~(to1q>p669N~^MGR&' - 'qp-?r!{LZ~BxG164Afx}k}~{mjpv*&x_9zpQ`R8?$BBy&>~EEDc2m=)KRx)ONVjM7ce;vsRp~UC*AAX&syHM7q+c^{-PF|v-3&y%' - '{~FFc$K@5!cOy>43Hq#kp9CXjO;rS(P=|qUbRUpi23Y{+B&j;f2AHXk0}<{oK>wST&)nz#tNT?p0F^Zjzf_w*(K$OIH96zEu^eU%' - 'i9kspyI5j?fswo|(1nsEpt;QoAdx02nCCeZt4g>_d(?STBek`bQUZWH5lU0^;6Rg=v9s!pr215Vg=0txzDum)pvabsLP!s)i7LhN`(N' - 'JEw`QhIwLeT@JDl_Cg50gS~i^A3|PPNY$+=Q}^~pc2dVQgHj`8a0$)SqsZbLrzIi%3R3m^Xm7`!6K5fC' - 'n0d#sMac^1bvPWiqjl&f?;jYI(h0=iM(0&e3ocPdFpNmZ41w2xQod`@4?j@8{_w*`^BPe6K>&B-W2!mfUszHh5F~X`uIQ*(%zo~g' - 'K1l91EbD8<)u?+70L)}*@i%xY`rGhqlhKXIf^{p9m}?ext2aibUcXuP!#>+*e8TN$#)^Ob7%yn3wW7omT%erZCR@Mc6ms09uBLn&1' - '2q1IK57E=$CEbIYKKBr+S}t?eOMoPTb}%^poDBE_sXN?Pf>CV|h#ahTvea|~u_0$&<4k^TqtTq|Ate4<5s*m=onM#VzIYYZb;7%9' - '(dd%-49bD}D_-RE1TfDwjMJxW)0KO<9r<5~9c&~JfX2TKOfclYgFgSm^yh;' - ');MoNWzQHr1jW?`sKC-eat06hg@qs4)`&zGJ~bQgq0XR4QS2eGaQp7K0dH&HGioVpQSI2M1cEIr5EzpH+V&#|8;u^}f!pa+)kv' - 'y!m^hk}&pQzjkKotb9cGaDIu~dt+>?Hr{kRiw&*J9u+aaZJbrj{4E>f%}?W+E3}PkX2z%wwqsondBLYc_%1yit7+ckO5CL5*Wy6A' - ';J?8uK**$pE4}F0OZFUge2vS?C9CtOrK#Ga$BymvhRh**r^vXKd_z@KaZbCCEP*Yd&)L;_LVjvH%cX18z`~X-RWMrV=t+V_0RLm?' - 'N-WB%3jgiY6UbV^V7IInK@@r9BY7mSl4jAZ@^_)>8$^S+!|ytl;|qa=7OEJnQ!yE%K}@M8v&KipV^#fFDKE' - '(Y%M22GS^OBj@5!6G8MQ*q5vmE}' - '_#9$UgHYUe_qH&&4@sF!oDPKnQCBg*=x$>)5gKYz7J7Lg;ZZWOA(@;AUgbd4oTR}zHyLnz$#>Z?3_RR_?>7_TY-}7tJ>OFFvUFcUssNrrdcE-`*d4u9_Dj}_' - 'zs!6zq2uLc-}D3uxnDC=R!a(hXG5t@LP25_+YVl>%{yGy%7rVd)?EcUO#DbQmf;1Oe1F7}ua>_+>6UmkRO|U1(iwIqw{G$7i({@+' - 'o;sbEGujgg)_ssA`rRsKy-bDWq3baQ=+{(%9K`602m4C6RIA#%;2hTh#z&DWui^1|TYO?2VB1R`DENjlla|1=L8|GIj@&Xwv74Q$' - 'D!UK(0~^dkx?F*teYURTy=+bhIBM8LKHNk1p)D4q<5BJX#a~Z=^*`|&Ij7t%|s{Tjx1D{@>Tq;Y**%n8}Rh3_JJbnNT#@T?ueGQF$(yfigG~k-Ub+z%-U~4' - '*IYv2%>ovZ@+y<=)IkIfI^Tf4L(u3pV+c}Tma#CTKUgxO>!>+;rm%Gs`x=~cd-;;pH9tVc%$t' - 'FI^^*e+d`RlF-;f{jMkV)?WiMZ|-x9Ojjg;>I_eoB)0swp=Q|I;Oa*u=8gH}O^oq;X1Z{@?nzWhGecZt+kndZ&t4}V5oKSI|AGNx>y_WO9xk@<|)K#Y)_&3wO;biS=>JBY1E5O=_l<=+rvoOK14xj8Oc^Ei8' - '3)AQU{mFP{RtWwx==r_p7&^LNcoGfSh;0?o7i{7tpHEobMqbLqcWfG8{0$EG3!@z9k)A**nPoc)P6S8o%grFxoPL3{!*@GxViQ83wTkXD2+chQVdMu^CB3K#Ol(R53bbb' - '?erb`fJZu=kZ+*BgLgbR5`6@3)oXlp>HI!di!Vb0q1mq+v98IJog7dVKVjO_&#iw1}KkeUV`Or{5L~k|Hx5H;tjR^Z=' - 'tp8+UJWup~5Aq)p7rl8zwk-NW$97uAM&' - 'msJgOQ~B2b<24=tc(d~#P)h>@6aWAK2mk;8ApigX0006200000001Ze003rTb98WQZF4VeZ)9a`b1!9cZC`L~b#riRb#pIYUukY>' - 'bYEXCaCrj&P)h>@6aWAK2mk;8App)bE?bK~003D(0RS}s003rTb98WQZF4VeZ)9a`b1!9cZC`L~b#riRb#pImZ)9a`X>Mm9aN&jYc~=^QK-#SvFsH>!!#uEL=BT' - 'lvh>V0j#=ecXq@({`1Tx8wr?RD2#oKjJO$%8Fx_OB(<#+LBnOFHq(QqkjYP!hVwrEv{' - '+gUm7Ci)YXoUf~CSJw+wZk0D(nJ@TjceO&~iBAuzt4Z_->;95AYSdlbOi!&JNmW5}QB}Eb6KV|xm@lxvV{W7jawkm(~0FCzBuqoteSdSv~6ubD`4{Tu6V&;V6>tV^r4;C&C+Q3MN`1*vMOee>S|t|Oz_QXe)A05HeaZ2B02dm' - 'xwFt*RJqBI}!Z%EN{<>+0$n~(#HwE!y4ubNf|UH<-9B!^DA}0|1=gPtvgV(%WSn+xA_08%?sHRSR(TEs|N>9^Q)qnM2k9~' - 'VT~!4%og|(7Qjx|T>^b8T8U&$q@Dm^PN%gXo;!#sVF&t@_pq~Y0h7$Ed0TXB~VPgbAKilzcm6{U=R!TQjpnxd^2=Y{e8*u_0r{i7S{~Q1J092PMbw>^ZA5dM0FTX3X2~AE_&R&~s1&D=}5Uv;m;26PX{oRbI8Qs-=F{~>Ot}j3)62{1YzsvAPO!R`A' - '2I{_mRRuz##bLcR33*}+wA=dw;Q@4O5StH4D1?8G6lpw#N+AMR@Nx~TY+2B+v9pgUCSjG*yoh$xQ2>Y2dcBwtmpYbm8m^spAgC8H' - 'H84)H3>j9Iy@&Vo>itLo9`i;`uO){>Xs3A#EToBfd=uQQ>b(2<(Yk4XBfkO`47_zT-oE#t&d+LKde|DUi5hB$+L%Nar{(lC0>(G8' - '0X87*mJO?D$ZO7_JWw^fa5#$|?2bX!9gK=crxs|D%~xN;_Oi;4%SG8;#gfyozQVr@Qb!g*OyS62g9QBIw16%()GzoBydbmyWEZ1p' - 'UPaBi(j*SEz^x@Xk+e}w*Ry=&EzZZVE79}-D1nextRfJy^XSpf-#w6^M&<q>g=m99I4f5u|(wRiR=)X`X5I@s2`Wgg;nHU*<9gnwf' - '_8mcapS+{$?=|hW>lF*cFTM_;6Uj6KJod9*6LTh_HsS^dQ?LjoI@l7AkqyE;(b~tRM?f67DAbgXCbS_aaf&MeS?G<+8-oe_2CO=P' - '4vfZ$+Y<#o$aX^uBJF1?A3&srLk1h3_^#Ig^GfQ_m1>mf4oJ7;paxv2rg*uq=0aY;`8mV' - '5*9$mY%j|0H11KZ803+p52QuW?=kMuK^$%`BPqd!=M?371ybR6*6HxwkTCp;HpTLIgT' - '7G+Cn)e+7(V>B?aqIVjJ^+g5kb1729Q=Yj76w|VcM+xjudtF&*@anna*ENri6iyme@NKU_FiWvO+GWlJqHsK+_YX@WwF`9*s21nAUrG^vI`wc' - 'Td?uPI5Z_m0g`<@PNaBC9nWH7zPdsygch1#ri_URMVC}0nB=P!tasuuwrYT!E>`7Gga4c3RUys`99P-NNps*bv1P)TL_sY=vCjjj' - '(>Myu!^eAH?Y2-J+M4uf4x%+nX&_}vw5|qxRThhxvvuPcJ4M;?Vget!v6F?gQn$9HM' - 'B|>M`YS?BzgE&@Y`5_7EjC$chXR=j&0f6pjEqpB6ls9d?mjFy`EALVXk$xrL|IfphPrfsahsN^B*Ihl$+fHaN87)>rV}nVjS6upd' - 'j-A~5%0Mf_Dl{O0dSQIyuB3Xv&N}5O-SG``?1+k8&y*%N{=IIVo6iJ' - 'PXaks7jL6on7We&9fx%V8Ws@aqWF9XVrI#pLr~QMChQYn6he(z9ck-imX%f6Wm#-+zQueJoTrlZv#-9Kgr|l-|JPUdCc{&~<@WwE' - '8A3f5`oe)F@ysSu+!dgZz318Z)J9OR;H;@vFiZOpttR+JfK+s;$E=e3ivp>J+P0hDY^7?6|L{m' - 'WidFW6|y_@%Vy' - 'CfhYA0x?=AU4nKIlbSzl`Nu*~YWmPZ%=!&Mmfm1Y|317%35OqR+)6qxp|EV7HbnHfGs=nIJvc0qI1LkaUIY}I$}+=*V+9eFvl' - 's~lN+Q%n+?auR(LTfHzz_3aE15M@sbr1Ft*rE^(pk3xukV7u&-I?5{p*#r9' - '75iJH?re0z;9X>WPov9ZU7Gv&LW)R' - 'ld1(3KB{ME30i{OGeeQO7AR<1^k={XMF*EF+?&}f4;q31w^|=B%4u|5oaW~x?EKPe3Ci{b=q5#URumNXg!M>AC#(H2ko@#?cMiBr' - 'c>gYEtD2c~e3`VT`KmbF-8+gt1BEpD{ByzlENp-n0Jym1VaxjafDZ9$U%oRiA`CyEy)3S>hDZu&Gb4z+MqDxyx_29T4MIxjbuZ!Z' - 'XW-hTTe;u*$?^gAH<|*$0o8D~VbV^}Z~#;F4}of&prScPZ0FTgw61t{%p$2)E4l<(N%aC}@dt8pRy?^t5>%Wexzl2L#xn!tQFj>l' - 'H!C6;{m1q+o*TyIMLC1{O{4qKUBsH6O1U7ca^FVAWV@P^?aGKpV&Gg8O|->~>CqN*Befn!(iTJe+O%F1eaRL@m!Ol`e7#s`hYQjb' - 'ygl4E>H%4KuQXX94r5eTvt}#_BLEcMx3%zuH2OEx$ClU1d' - '6ox(iCNP|Rg%Z{SEt-JW(O+Q?4$-3o1uIL5excV-Tu02->!?|*CIAdZJ*gYnW$#Z9RQJ7@PUkj' - '%PUQL))!Nf97PXry;9u}s4-#hyOe57oYJm&(^h+Stb$kBhCa6T-39{|+LG3>mKTIZ{V;EVk=4wiRyi8Nq0(d0Qum{S>J+WhcBll=' - 'hXDLS0#JM+4H@2mynmS?U9G;jZ}p2uRp?0p*`S=V?1P1OY)k5X^oT03b%fv9I{G#`IL!b#PHbFDHFNJC{AVk#gL|!uK8Dky' - 'Qo1NqjhYR5C=-p6^~uADX}AnEOT5rlFg@+g5vl=26Mn)Wk>>zjP+wzwGj^2^g&|z}z&MCNhoe)>Dd7x*agv&(' - 'qCRF*=n{r2g8bo*AElfwBU^s~eMeuFCNHy|ib8YnOAWNe+iZcmE(=v@bkFSpbe=!j+nPO#F4VHRekE+_>}G9UtJY;zhz$aOYRHseW7_3?hoy-Ky@vR`CYuKADFsNDQW+54$WK7$NCFrkvE)Wb*|w&l8jqrg' - 'C-tpi&n6Y1G4kymuh9{QW3B3HcZE*L76nzcJ6uJ_>-ii_5x_Cuyx|t!yisDh70%S}A7UVxT2N+xtr#J8>;uG6S)FItixDz&#+>X5' - 'PPpOlwLTu|iXgk1s2vY?sTWG(tbRN0pcj-=OTtqviM=b6A7Ts2_&Uty1ShQ#eUEk5C#uy+q~|tud~3P1_Xy_){fzHT3|4+$Qd(+t' - '()NQ8d;jA8*xuAU!%i&P`z*Sbe2D_1Xa#iWDBg2k0U@~i!L_VA9&QNGNNgh(S9YgkaDll?%U2bYLxv@kl05!}xvqeUTOlr%Xq70Y' - 'YeO!vR+tcc_Yg%EGSwdI*gB(lo5>uSMQBP*gBv(N2qOc9z|y=Tr`Eb@3rpA-Tex_}LGFd{)=%)cLXS#O6%Dd??$2(!DyHZ|0m=Bq' - 'fhniDTt^5GJYneO#L(w`A3+}T6yqOFS8FR$Sj1T=@rK7}j4l;_H;|bf_o3IL-?cE^=|Kr@-~}l|k2@qMsDrB+9K-sI+elE-`ZhI(=+)s;N;>q!L$@5;=2ITGSBhd%*%U3#5}B1@' - 'GL)k|9*0+=(0N%P`UG<}z~Q9gm5D({786lx)Rn`DZ##r;AxEi!?zUtMqlb;zU6LlMu@4$p+j$lTlm{aeP4gaaAG5pP)p5@*X!0r-' - '4~hb9G+?Zm1%DErsGDJzGi)rTaj5|vBo{Q@@-+)-#e' - 'EgF)kn^8GKYioB!fxOdGB%N7s_wach4@r(j^mrVKAl})euQ4bsXt6n5axr_^+ReJNqP@Cproae0Sl!0_s;?5xo>K{4ZKz~PUSSur' - '%Z!ByQz&MzA{cdnI$8%sY^d%H)^QXr$87C%W#Vl8^4U(>T8LIoxJGe5{PEQ0j3' - 'TZ)&}=dLvE$?C?OgB0oa*f8sw!wrx^Y|!dmA#VpJI6x4wL9iQZnwANt)z7zS^(@?OpswPK^SZgno0*V(-?~2CGy8=RU2T^$e;rwz' - 'j(usuE^qjyyPD3o;BJIF$60YDZO^`)82^K={Y2+sI<_tR#0F=wwp-Tl#Ht1Z_@Pjhe(0~HYVo-!r}fu3W;CowA0MC15j_{+TXWoo' - '(dISU(95dAaLKYhT_2lvb5NU_97J1`%2|AFJq61&Yu}Mpc~8lT9=-UPdc-HGN|fHw%9I~tI73abn6OX({PYDhV8CnZWx*Ppae}Q}' - 'E@7NanJ>^}fl;2(F+j)}yfz9j6hBqsU6*r~2=o*}Y+woiFC{*k~>I{-^2$^CW*!uKx75p7zIK2fKty`H7~+(Pz>9zx;)RW&ZLNe1LzoCzfHx' - '0oN;k0Y=)D=e!7HW}zhPx$ASQ_-?D7P5m;ohX*y_%RMN4pJFyBVky4w3xo1N8HnABb$q#pe)7xv_M|OfvS7B%S(!xPxuf0ynm8d4' - '@&-L`O@W3qfv`EOO);*hD!PlhIpfWoTqM9nnx*0^?p#{%jkFn6LaBkCLtWWfgA&!DEBS4*6;Vz!iQGl|Bps(=Gqr{-=p)$L^%_x6' - 'ftEBqKEt@~?}3%6WH2Gxq*A-Y@iYT8<37yo1?@UALLZMRiul1`NWP*3N(80XpC0}8!Sr^-@{bjXWupSmx{M~#sGY5H_=`szog^~J' - '4cy}<1^b<~-ujW$sqD>-UC-P+I&JyM39;4DxS0@o9D-3|`aI=fLV!15`@sy}<%SU%q@!v&z7t^p`b28vo#-vW5E`37KW|C$a)NqI`kt6;VeE!ZWv3d{86GP5IH0h%a@`5U;48P=I$U@XnOiLPr0=WP' - '(-5G)gUQ)FyXIOlZo0Vn>Hi3M4WEp{;VPxuEPeMWYLi$M<^zS&(gM`jz%ir{Nw#P$b|i=^J0!3tk`6~1&T' - 'IGiKxO?nw*8madtSb^}Bnm^o5`1~Us&X#FRlsLAT+TuoR%%8{O2d35BfUSwO8NV^beOrK?JV$NaF@B9VG6a@l$By' - 'c60jp>A^p)J4&g&Zy22Sv1jud+`s}{#V@d7PrDIfUVZoC!F3UXHieH$?Y+ZjxA8#CM?V{0hDIh=RhE!<9wy$KR&;wU%Lp=h8;6H8' - 'Hh3P__CqH_>#}zj{hO0$#t)?A?l|!x{d;TQo~pM1pR;RBzs*qQS7Q1C55}G(=LLrI&?g%&&Y>Wb5e-#xBP@eT1|bm&vSUid1mZt;' - 'GC{P+)35C10>$L4@7p-B&?pX|r1~g;0YuxQ+(|t&x^j4WL^k|W^is-51Ff=;j|@2}xKI2Z#HDTJo=w8ZR>H-$%IOxYWZzXvEFPxW' - 'RwyiFnRmg-IIZs?vFMMa6@3!@!dc&p0rDLqVHpT9)*j_XNRTwjlXbl|' - 'vlJqFRPE5Z9jMEgQ?(65Zdc!?;h}8)A#(>MeQ#3}0lx7=6w;#%8A3!Mb2uMp7MbSjHea}L@WjsKJwN2%OnqH~df}$$yOyEC3#ga8' - '!CHdkR5kTG-7Aw=w%9D%MpjtG@1;-&C;ljJ=mrE' - 'l#gWlOgTlNS(M(71L=|40%WwVoVMvm(CH{dq3*Ug|5SF(Z4Hhjlo+wAZmYufzW%w?a>D9%IMWZUkio{u*96rA%?rTfD`5GOiENMkA=fXoLbUj)txssIu<' - 'Vkb8;3v&D%#gOm<$QLrVqKX0;4jFk;;YRO=jF>&<8^QvG%u)Q<*M1ZOdh+$6qo`yVRj^|Q@@hCbRT};%=hiRxIHwu5u{jpWi8;`K' - '?J%LgANY|&DtMr;k^rthn^W$E3;kSSF0z>srcB#rC}zk^0YdzdCtGh-7${^0Z*k_gy3xWgjcvd0=@T+$}?5@UnlZ8|n?1J(P8*uv=)C0caNw8%w;S@0UYX8>UP7QKxrOU%M3l6v%i6sTk_' - '*6nJDRm+9_wZr#OgzDvmyar?6xW#Q_b5PA08C50;Zm4o#o{S{Ua-OW4T#YbcdcXVhnT%gOFEbSHLhz*_dzz=SIciL$_q3mlV' - 'Isf;{uXgzNO+&*OVJtH;hM!_CWotV<^xWWm>Wf#0@LLD9HB}{Xs6WU@j5Sfgc-R>(Ga?x~G`aV-*Wcu8b8#LkN)eMuIXvDITw*`jA_qR8R%ykeTd==g>M9*PT9#|7I>%J(~' - 'qGh5mUIF{QU6L=(4WGZ5eQbSdJFQ5@uhJC&*YIdNB8+igZLfOd=}{ZXo9a|w(V|_>>&5JTus?p9OLd97+W2@GTwOg~R98rXi3cLFaKx31L$Dz=5+?99%;(Z06`l~IfsMbr$cNZI*' - 'm6KqohbQ@$u4!YsU;shy=ykF-sO$~j6Wm?C@qJn^7UG7-i=0jN`g#yfl#o~Qax$5Y$;sZ(FK*olG9S$D(TL;%C3!Tunh$m`E@9(Y' - ')EC7p08S)|@b$z7Ga`B}8bpW6Vu94B68gV_D*A>}TNzTOkqfo>LqDEAom#l!<3Z;uq0qLGnH-kyLNoX4' - 'oojJxAL202_W8930lBs}h#gnY_dN;rEn6;t)W=IdB2(*EkDIAY`_=#xy(WaoFbY!djdy17Z!#s~a#b#hCYhD3x=Wtyh8yL1Thre{' - 'q2FGUH}8v(0={E(6k`1r30k+vrt4V_iuoKxxbs3=E?qm(2YF(9yrjx68hq(Gfejxiv1xG$^vJ;Oh~*RY+m9g`*%LRiL5gG#oS;(!' - '*#Of9X)b>YXi)y%!lH}U)RJ`LZ;(BT$j%3ELcw!+s1Mm=^?T$|Qrg=oLz&f&lfW2Rp%s7q0(P$74F}Cq|Ke?}_y*M8?V>52T~uAn' - '&Z!Y$Pca542Q=;@l%&Gwr$6B>Wazo2fKTf?M|@h>&eN+<-0LqGP}if+%}4LlLVC{znbZ8d5D6XM*}&g_6O958KocuUG~vGM?dbcV' - 'RDD)?kr?<-GH>?2AJnQ>s8thlahhb{qyw}TufGU4K{l^+2HrOAHz9eZ2b%-9qu!j7$!A{~-(#L>$=rDd{EMRZdwtF=Gyqb=1@sZW_c*!?ZgimHa=qG@2r>#0|>3Y_~%xB|2&9^$H4WOH)QN9Ae`Uj7v0!`|=F5csN2#hD(TYM_V63y?%G>' - 'EWd77%&$(O^Ktm@IKBVH&X)7L-3@`fyUk*=^a7rEoHn6yg#2IB{!5U0ft~rvR' - 'lPGNK8~qGDAX_hNiL+HsS>~Jr9ejx=tjXQ{0yD6pZ6A%pW@A0jx$V&gJB2LFF' - 'mj9fc-o&mz>' - 'Nq5>WVB=X0KA5qo4`%TD?7gii$a4+?K-?R=q}>@?$!q-OlvBl@z#RNNLE9E0YWd5PAxQPD0x`;6O@n^4vpAcM!jIQSH^D3_qj' - 'i0);WB7Z-kh`L|$=1_;M!9Pbwu2m!YB(w=Q^gX-3vBVcvi8-KqwOF=>lwhAxwGaNw{H*$g5nx8Yon)=n7yD%I{h^Ov$e^{}' - 'ZszN*&e+KXJrw;I_at7Ph-u&V=X(;}&GE!iR3HBnq$2ov_w2%avrXLG)C8qLc$Yimf~u@gxbJ`h+{n8Oh;5q9S6{1GtMKQH?=3@S' - 'bE*PZh?-OYOQIorUoD#n-Rswap5JCpC1gxdAn;uF?_Ir0Z=+JuC|4ef=hy=2cyh@?_kNuDXFz?7C8y;sgT+=w>wg' - 'ixHEV!3F``A;TLN@Mv|`|1EadCrloEL3;yca$}Ejo8I#+xn7`K5SKdQEre3%vP-@ZiLQZIEy^iyF+?Hvh!7*@heGMmL(Gg#-ZQB~' - 'P`wOc!M^F`!4rcdbdCNVP$j|ViB3z{_@p5bvuJ7QmvQL' - 'sUQLYpy1c{HrY`S(=Zr~ESeIgoI*wkkm_&@m~yUUu-VE$=D9FkxsbwM_|S=Xq3LnoZF2gCLw5=g0pl^y1PrAx33qIcL6MTU2$bT^' - ')JtjMLu{y*Dmc@;75Tk67WA-rfXM*5cZ0h}^iVMvrED;ukFE#SJrV?Okoh-g6@_hXCNH#I=BqYn5p`|~!Qc?g^7O8ypE0I3rJcba' - '(14{>T_9T(mt9=arEOmQiFv)OVWgC44JKlPK!u~Dxyz>cDnBk4Wp@<^45wC7gpHV<+eEakbk*n`xBT9qlnPBDbKowi6LXH{=p8rY' - '8edKVG=uMF_I=up7NCib>?bu{;+)aW2jt8qLPegA?SzW&eYNAqbrW`Y05)QZ%N!*)qdh-`xEEB3OH1}{{GRT5b^*qSe=)3mbPMnR-;aB#mBb*VGO?#Kx6(qRI4F{V=-3s0qTh*wP~nC!#ezXT-GAw+AfUD' - 'N0;9yQ$-aS{%HV_ESoT8Vv)f9<~z3V6o2(<)F{?4`K@X66)q^UtFhN' - 'x5*3W!PD}Lq<$mfR))08lWG{%ZhXFLLOrKm?fX}>Pwd-nvsu~nwcJ;c5-Q0`cG$jM5M;4@f$7x' - 'Iss!}Z}${tKm?MA_(>#3A$`r8dKu->_v>S03Q%aHK0`ANC1Si1w@1U0$T^ICX$ZY|dz^r`Kyt4|rXQN}KeuwDrKrwz1z2eDZhIWx' - 'n>SILU$rccUI5pg$TUGZhCl$(+myJEv8}NmS27MXDq(SnycxoQq})qfC0ds45)F4f(yR{o$52W$OR$Xf?nKy47#s-v3q(7g7s*4f?M_9jkgUSgsMW#KzF9FJG_hHqcmsDPBi1KhDl|AS&+N$))h3YFcMTOK4!m-ZT)h4~$RWbit^rDq`zZ%K)!k6U#tu5j7oYJyxXmcn*yWz_Ngu>1;aU3dc#uduO?kZTEKS%TfqT(kQvv9&>9Br&Q#a>)' - 'gq`j31~4d^dPjfWBP#)}+?KOKYVWY~RSH=(p2H-z7pnaFN(RV-X0Omygs-4!GCWMr)IsGQAF0bS;o' - 'qjB(bkh8`E!(*kJhGfTRj)x8r=|Rp1S%JJp_#HL_vkjAtm+ZQu!dRme=9nM|q8Ygpg@vR^AnV&yu0y8}D(W^KT@=R9rZ|D^8}Gcv' - 'h^TkaBJ}wiw(j9Bh{e&g$s_IA7C`Ny?S|q$Hd3HFoI_|7y$R~gjvv2_-!`05Hna|vUpOyV($}BpTvb-ODF?lfopopdnStK0GZdKLm$zj`8r6ga#$GQv' - 'BX~~Bl{%p>+Z+lgpouYyWT-D<%#v22f-lqeP2D(N11hv' - 'Z`0_Wed&fz<6D5VD-DOw`T>a+D2-@G5?O&*E%EG_S}?CW1tJS`J6d^he@U8R3FN?G1hJ5d$75^HbXkP2^$C61=%&Ge?QOf)d~~f0' - 'Q;r2^8y0JDcXhlq#NYW!d&-=No*Y#oaB^cfpO534q!az(P;fB|m^p;Eg|)_lERK$WMkEl0zf^=>el2Wol>A6(py)D0LnY^A!SGv{' - 'Ucq=M#Q+*m3SxTtxQbM}?D&cVtvnt=Kv72V2y?DtBs8Bl2G)grQ{5+pTsLsiny12#ymNT^9zKbf#8F?o0a(qTy6LSuw%nQJlIa3yBpzF=w_k9U%fr!{RoZ>=9l)Fb!K`5w_UXcok|Y6dREIo$53z4!' - 'hPQpOa)d7AcdW-O65!sx+el+U@e5|DaR8>dRSbbrIKb4FYSKZ?q+4aw3s64;`9qWnqks$$fa-KtXT<%BxaU&G)>MQ*I6x;J%T|56' - '@V`;JQO!${csotVkOT$*f1-~%XF0#~Rjw@}R}{`CLy4ItHgX(=tOd1%0wD}Zy-#c>BC>#4(lSE|Z$BTg!?Ga9P9vwJhE;kV!)p%w' - 'cBaeOoHKR-eHLYCGtM5r_&R&=^vU0!KRbB-I@*o)#(1@O;G1#J)7`=E^7uyTbZOTe%#IaiAu53y@yU?cH`R{wKr3j`V^kJmqwV?^>sS4kZjR-=PF^am12V-Oqlm&`Px}fC)|)3LMn4%Lz`HH+vs<$^CCnC{So~<' - 'awyXF=QXkx=KlSoNpDG4r5z|KyqFBS?_fkY_tAJ~5TiWL?pg}X>OF7Uyy0SW*FOj3qKH;bLH{_dXL=v*nsR9ccewH(|K9f#QB3-n' - '=f1ZOZAu7et{}n1h8R9~sXry4ZL45A1AfFKnEOsce3c?=Dy0OSSRlvY%w9=v#t{E_q4jp$laDaa?fwOOm?Vimn!xyisZotfmDHGf' - 'a;)`@KAe1J>!IH?y@_bu`w*PlPo4TibY`pMC$jA394KDoJTX}i`Wqwqyj{v*rPuPSW#M@QUGAE-P%o7wLxEyT!v%%lfEskMtBO2$' - '@kAu@D?2-6URRcBz%ta-vkaBVc%%=WsDifTQ`Zl!ZWDZHd~wL_B6@HbZ2gGtj}*UL($gPg>KXCd&6sPWUvTUFU>60~3!Q@5uD1l+' - 'P?PA$j0p4-%`e@c3YeoO%Ai8iEh?Dyp*xt79!>1%f|3^MW!{{%6#r1n%ve&g|Gmk|HaneXhOy>NV!(TKR<2Z376xRX+)-VLI4>gx' - 'SUcyiJ#@^;HWSO3V}%VfOf%TLDW+>sE=B6){r&)Ahbp~MgdD9*6V^Ed!=AvMndm!(90>3p;U}z~XQF(<(*QG8sFzNJgJ}%h@09CiD3s}#&q&qw=N1YtA97@0ruE*C)' - 'AP&qwoHJZZk6G)JWDPeC-V2MVxZ-}}A<7#h$U#A$#}2C>9YtC8KAP19&xttY5xo-~qKE()uuf3{^*k7T|8CnZ_a6!2Yh^eklUq$E' - 'P1g*+DX?%;cX@M_X>TYG&N&Y^hr*|H7+{5=*e!N0;;JG_L*7VYMBX6h1&E2cIUx6yOkB(p|MU#!;Z^dI+U#Q?#){<8S?55Q_UMGM' - 'qnkLpRXkmQZliyTBgZt`bwkGhCedAf>6|Rxck`eLB*3mPuRYP5QuT1n!%?8Z=!jB)2R|YHxGGD(_Hop)5x`A*ZmLelx`VyZI0HEq' - '&E7fblsJ0GK?QfrFPQXFX7IFp0Jt;uZ2(GtF866?=gyrwBE;+&A6ECSW#M!2{YT~}I;exr$~#Bn8Mt-qQJw%~Lj6%XCb^_uSFMT)' - 'u;gvJ39>i`DVs!XG40)HQ?F0hF!$!ogLPL^=iaNJyf%IQg$LnW&6YeH#wRvmw~?-^%F!Agx`BAoj*g5r#kDNiXV#xco(w4&DJ{-Te}fSb9$iC<_2Bywtt_H86B{' - '>KuTU0%mN~=pmC*id|GnS8w~O3-xlmA0`(^Kg3=LM!#J*Ec_hq?j6}(3c~_1xVSK0x}EcY(g|3zhOwOKQ@v' - 'yKJGvgnfn)6F?OiCLq~(6AHWgjFv5_cN^QgXC+nxPbzV(z@Vc;C1Pi6s@mhhTV}9Qc$CPleKa{a4RsKK!+D(hEz`jpE(fpI2=zJ*' - 'z1(!`g>w!rbkY!{e+#Bb^#SO~hQ!Jtqj10dkrABwuN*UCc)HS__ns|ri_4IPUF;j~j{QVeByIM)Z=K-y11@0-*7*2$DltWp&1$#@' - 'UKF@EA3YdJ7&sHUeMF&WorkW4Zqdb&Zw$J@7(e%ZB9f@(rAGUt2Uo=je-p=-j8Fm!' - 'x0_#aqQX{)y}Nq|m_K1^QDGwjG5m>fagos_oBX*~;;y3!M>5J2X?-D#3fO;(v!u>ACq1nY%m#XwQw|L3riLUYZX' - 'oo#w|bIxWjI9bJ4JqFVNL+3*!*mqqOS!7xcjTIfR#WTVzJz9e-Q9p6eXA%L!l95RUH=w~E=pYd30Z9L^Pey%Q^%REJ}2' - 'K2+Y+=cqZ{);XSF-n14x+2m|rsqs7s2L+`Xsh{kwHY0y;uRtA)De8+`+p$~~7-lDJ4V=cOhF$4WTui2d@a%j*`@I=CwiLgEBKS2i' - 'w-xq2P-eeTif304+i>GfSP)CYuP4Dvw}ffKSO9&7eto8-himknJn5h9p>P4SVZ9#x@0L)QTE7ChUp?V4^Ap7_ck{hIt`LuKi(uTC)c`RCs2R8$df?D;b4>y^@Iac1OcYT^MI->aOdpGGHW_~!oY71R~oCNwPu1PZBb@qi>$S;i?(|Wveavl' - '{m#o>yUBLH6ExoQNf7dA@v+K9UY)G-lY+mXbWf$9NU^-t25yFsgKATD#T293nUm+eu*krFYumoHZT}H%+ZZTUw<5jnBlHa&R2_8r' - '>M1GCO*;;Mi+7#edvn+B7cyGvp??mTO=ucR;@jz;|+N(8B*MJ_C8SlJ0&h=jD`-%!C~aTZQ#&&wvWgpIX4#' - 'j(>0oXq|ap0j@3F0d~ER6X2tn_gA2l;SmZv;)JBl1(Q|1ibvKrFEt1b9PTzACY#OgU?m@wXPs`D9SK~Dv^04~wNpXaSmq5<#' - '%8}RZbL(?2eYS&7n)aXk!h(8Y?GYag9-cvEx~-~G}-<+3fr{gAH?LVl}hv7LYGSQdQv' - '=m0}ApVFQkD#`Q(g*r?;Hm)EI)(6QO|z2c2h(YXB=CN>^|tBb0?qbhYcru)z(d!o' - '%Q5RhRS}yB=EAlUPMERTY8&YFHSM&#!Z!+o{EF^HFq+4(}MjTFO%FzlNiC)TPM_t-DB`l9j`n;pEFVe)zwL+8e6P)d#$#WFLms0(r)PgV1-' - 'ISy!iHuy9eHA-dk^jpG91MBzP6uqTp_3w(b@hT3cMdi6ur4_O!6|oTfI`KX7hMdoyUF' - 'lF;T0;xnyuhT{Q?Pwx?tn!)uQPjqlb#dBfv5V-*`>pEK{6S' - 'CBIj>rKzI0923yUB`C)sABzd+lxX7H_sqTCH@Wnx_+rp&nprN(t;n75+I8{$Jpd_Ece4%qB' - 'U-0)fK2(!1(YT)&D4%FdPkaO)*$u2};LM;QPC-Sj>7;kDTrGgFyx@0ucw9@6WW!2%B}V`6L1ZpGriHQ*pZD)U0r-cC;E#vV' - 'S9~~9k`qSt*#gq&C}1h+(IGTBSxnGcxT(cPt@WE)Ox0e-spclis90-|4jFhvmnyDA3`Uq=1m5+=u9wf2tjZs`dU9kWHGaK%>_AL~' - '1-+*1Jlq-=#!|D*tO_G~0ouLr@Z!Dc%|9?b<^pJaax3YQLQ?lBh4Q>8B=XIJkPFb)1M*u-r)^Jg*svw' - 'CS9&^~{;Km_l}E-(tAyFf+9)9#Td6Xua#b6#tZH$3_2I<}5y7vwvDm`*!X%T)' - 'nKbtD{DTpCyDQXImDUKmR$|vI3T@UZ7r*Fg-7Lg9}SGMLRu@cwJujemN;!vDwP{imBn6M?ysvlThGLG' - 'ElUG?+QQyuFfk{V*;-|rU8PGav^A<&CZ4ykx+p|-U-I6N`@PbuwKc~RyVeGe(-cb7O6D|0*TDg5wX2M_RlOI5+*ggA!FE7SYgxnS' - '?JXQ+saMHwrYdi5#ja>@yJ~WK3$s<7KIk%6Umg=9m&&TrRJDO=xH=a*Rf~npHVY(3)A=~1x`PoQuZ^wsqOl6MRe@R@1(5s+FA%#L' - 'HlM*x)~beGNvq9LD?X`_TjfNGs_A4d4a&D#E_J45;p7BcM(u)lKA?INqK}(}r8TSP_V%(?Abwq{9OmL-@gL8Zd7c`nQU(XClUb&A' - '00^aFGj%eVPN$Q}vaYrwO_z;rYL%u4oT`Q$m1PCk&{b(B6ZZ{8>Ypp)pDco)_mY$)o~SH`Uz251W=Ipx2oug6_pB-kMc+*#7n$F}' - 'mHNE_$SaX(t8@r|jlgR{hmA?IW' - 'arXXi>Dl}9Q!y9Q0FF}{|8)BIf4u*6`mURJdj9STrbg48bv2#gvRo~yYLn)Ai|?ChJo!O5RU=T3mP(?^1e!45QfDR+R|sRonzRnI' - ')~x`IZ$-7$mYzB*1*991tEFrT3kp#5VRWHh}CwV(BVE_Ucdi%adMtsU7uW^rWYq4PWfoC' - '7LNgx;283?x' - '+Hvt8ae>6=^b5`hNERu`t<1p?K!~a1mXa(@)PMD4VsZ3W_t>8IEx;P=2Ty4-3DHP_*Y=wici^vpJ)Zww?mZ+7#4*5vv$^hoKZqgU' - 'sGNyBW(5)>Q1^T=OB@Y8BwHoR2=qKZdNmUprFQsC8blre&02v#%Z~I*5&7rD*!&3^FT@ju8Iqrjg5?}R7J-NVK%qNs#duxqjy5An' - 'jHu8%q3#YX%o*e<&O{>$^l8S_FvFnx8$3c}un2U>Tf{v$rq(zZt$13OyMV%+L2keR3t6v}0W$@J-Eq)M#5L$NSG-xU(8hoqd2J;?' - '5e9wlIOOlw8e@YY4)^Ol?aH8`9O;rQQPn$8k@umwSk*GuprE6LL^fandR3BxfgK*22gdROWH;~);A4A+1J1U6XXk;|k!qllrV*xO' - 'AUXD!qkfdk0u76`i9e=+&=bW;OC|1xFvVkX0A+sY5B@6!<--`jgcv}ets~A2)OKh05id%dkMo;LLKhb^7W0|N;vsd=);CZOskJN|' - 'OGiCx4XLHRc$)ejs;*2lN+_QV$kb{Du^Q6QMYCK21s4~Y5XR!@=`&|vC5F-UJBSgrH()!UQ@d{wLIH!|K>z@_cKQdbh4$)#CuvTL@zq2u7j^2&Dj}p2@-}UP4EHIuJhR%_-ECZG3wzG9TH!' - '{Lv{{D3~_%6+HmS$j8*~u${xhu<-2xZ5Rje2Ru#S*YLCeoT7=M9+#s4=CODa7;H;3Ft~Q>bB8ut?KV-L)2ydoaKRjOh}@kl8q6U8' - 'I6CtZ+;sF0U0%zHtSv3{Mh*DI3B+lOX4#U#9}IJTyNTl$eBqt6E5;71G3&L@0;$K*p?eHO(c!`7ho9omQd(vt?GeGe3Ekg|&|4j1' - ';km8#&hSn9oDGhBAeVX6-~Uk$*>SR~?xR1&VOkT<+^)Ml$DLPLDN7YCXVor(aalj`1OS!|q}W?&HoYY8dCNxcM(GLJXH)K=gr<%I' - 'TeL4Ap3GSA=Ni3WNrgV9ju6MWOp`TJ9V`E_UTX<_Uri7A)j4I0$PN-!Yy$=7nFMaDFR5q&vO{#d?g@)N$4_`#KwwDZbGPuOL!0x1' - 'v@<5%*%xPj`SGYCfI6yl=+lPibu@go-}Qs2Jda!g>^~lZhOkbkOoAPOb8sA=;mZw!7gwONXcm8Rv7=UZ+5nLKdd!1(Bm?~^2&MH?' - 'qncvZ}#GA%tzFKxr=2wIyb*' - '>#E3I`-)p^+|l6nAi^JNqmC2`Gj}Y|Ny8Lm&s~D0hFm(M))ErSt9zpBJ1|Kgl8)k>fj)qfTia`S=PPAcE#GRF6p9q0`' - 'u-JoP==pKM7$Jg*)KJ)*!N^5r&p?j(DHZ|Ia>^}&R#6WFeve{}J+ewXc(#wp^Z@I~dJ>j&B#U;|poJ9;9P+M8bv_5^h0^PLZO?q}' - 'ABIkytzhmS`opa$%&80qd9`0K_o0vAr?5#?NA1vH3uGa4`oBLgG>-Z3!z>6AE>y5jpk^KVNzP?ykXpPc3iOzy*t7QM%AlExKX;Hi' - '9J?%p-`t4Z@45keX-#}2=1`@~7~^3zp|jmgK1+rowqy+P-D$`@W`2Kz?8a8IlmBS#2E`FoEg;C7uj64wY;IATHAARr2%jR8X0QgACCUQ)(c)D9{SJ$!xud' - 'Yw#ACjp%WPJ_O$8RID-mtu_W5L{J+vJ0JS-hw?8)|)h*u=RTgW1#|QAz{1_+BMU!1df&?#SgRrl)-Gw58bp0;yDQ3^%X#F2DNx_$;+bk=^`d(L52djJV+s3Ve`@T' - 'O#3Nddj-&OblyYOe0%GX#DEF8Z|{xbJ~DV8)=%8J47Bi;)-$exl#ezYC>hsjyuSm1L$rWM$k#AEF;nNR' - '4d1NR)FsH{l~v!8y9{iQd568aCsW8SeQck)!O^tv8&kqKI1ZcyFJCFDm?$`6f(T8m`eGyHt{Udk#x9TkGL3uj1;{I`f#%}#L1202' - 'Mt2ZdnDfmb8aUd|CuhN?N3$~?r}PZ3c#U}IQ*SE^c_(!tIo|rnWg`LsBau6~?QS`*Y*#sVKAnjujvZz!-ksRgdAWH%s|u5sE*u9K>Gd#vIcJ`t>-Bw@k0wrL#`-kQ`*&pCKlZ{d53dVj|;wsO;(z8RfEr=0U2B=r%}e|058' - '&m;#ks5qhjXJ5(Z3?y`EDj{c2(erVD8!=ahp6OUeZ+wh}?#IRwMi{%9{1;G50|XQR000O8001EXwr;?dO#%P_?FRq=Bme*aW?^%5' - 'aBOXJFKusRWo&aVWpiz2Z){{TFJE72ZfSI1UoLQYtyNKP(=ZTz&#yTAlt`fuJXRXgR5C!&b)rQAO_Sv&mtxJagYBS|vHyZ{6dNB4V2Yd@Dx(Pj6o9vFG{5?W+2#aF1BD5+#;5sO3RgG0`SgiW`G+dn8Qs7AwiI9RRE=VWh;%8?pT0H' - 'sVu7l%?{Hz1Yft1=7$z4s{}R}yDhogw1Cz3tAx%zB(qN+FW<*-F1JCz^S*f+fct##Bvmr!n@an%6it!)bMDyMbI&{q+(G)54v~|D' - '!U;LZF|Pq#3U7E0!=kcfWxEuOWsHORR`Hyt{)8=M=ws@yrAqEf)Vx4xnYd7Ti9aipDbD)go!ln{#K-ci)Gf-BlRSYQW;jXxGZY_J' - 'Ii@Tc?GAR%cUo@-*J$7z=G9ngH!&$+bHVMlO=J)=>IT5NA*~s^|AT-g->w#4R&>62_w{qKgfqDJl%zibWD4ZK6p^?eFi`4a$BQz?' - 'aj=I6c>AMpg!zev{uk}bihMy~BOTa>Hfv@a+r2ywBF(r#ICuH@y0VyPt#p`^CI;@Q52N&zet;it3Zw$oV&T5_IS*-4{bHi31?}{Qz2_+GsEDDZjbivGbtBZ82@^)w' - 'we7|+c}DNk=w3s)Gqi=ucwHo48^Mjec7?i`cHzc9zoBszc_feE57dUevPF@jrIZQbp4>CfH-H3x0Z>Z=1QY-O00;m803iTgt;|hg' - '0ssJV1^@sh0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoVRUq5ZggpHZZ2?neN;`0+b|Hl`&SIUG+q)PwzrfnEM)_QrYzmk%d!Z?' - '9%n^l$!O&67W(5m^0%8fIwX?j&DYZ#$8r4ANkfHoB|6$K&UZmb_|1ur~S_A{PqKd?H4%6>~ydO(>RW!sBpFf' - 'A&T194ut?!R@Qlt##k>y4C*kM)m>CdN~Ozbm2+B&@v8>g@PzV+E?$opq3r3oFSZTVS(3$Mvi4S3mowpl{Mu~il@zUD93H}u)bOtqT;b5)qAGV|XoD$aN' - '6MEn~$fU7GWl}@mBul#=93J8vl&5R*4oejmN*SzSIjIQYm((>rIA`5zW^HO3lV%xKoYWfqr^%ip&$HwFhgxaBQD!oQUDfmn;`5OB' - '&hu-yw-Mh}2OJOh0|nh=V4lwXlQaD+z37Fz(b*wc{Tr`ICkP?!^)ppRdPRqQ+`%D$+9Zs0-H^m0T*vF?`IG5$HE=}@1#@lm!4~`N' - 'Q?uA3(Y(0e#Lw~NuWkGi{R2=-0|XQR000O8001EX31GhZ-wgl&bT9w_G5`PoW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJo_RW@%@2' - 'a$$67Z*E^@b8TjCY-BPnaCxO#ZFAc;68^4Vfv_JUWn{)y95?5TZ%%cTtLJPdV>#_~JRSrhAqi)`9RWpgefjUV3xFg@k&@iYOlzB5' - 'EcWHuH-JBp^E7+R#bT+*=x}sGei7+%HS@_hiimzJNzNtDzwyxT{nY!3T#A4vl80oKgghr|$;mil0sOFUhU7P%OOYmI2?^3Tiv&voPS!##^){9Xh(Z3aEYg``K+S+YgTLl39brlfjQ^HOWv8!Suh(mzX<_{|' - 'U%ZYCp?rOLaXz`8P7Xj|z2r+0afyomXC-n_b@oVD2J!@K22w_BP12mOMb6=!N>Sc*E|f?XLn70;TCp#f9F0MZe2?tZbHvD^*qx>MGKr?FBktydj' - '0p_U@NM<|`a}j`E$zsJ8oGj9BJWoI!lJPti5}heQyb!oVA{L4%{bWNazt{6T?>tTBVzJ6K6_H@=nCabe9&v__NkWv7=OX5bViB24' - 'uRf+xI3hqBJY=azl=Oj9uQ$)rm{2-jsa4J?MSap-0S5q&k;2l1OtBzh65Lr5$xsApSiKqo%6Jr-1?n+F4K3|Bc^s14$Bh5R@*xSB' - 'RF<#GSt3FB7%=Szu4Z(lL{x6}Ip;ts5*`}$?eaNr%A#2MOexS64PqArEJ+hUR75MKX-P$AEO_8x@m`ODb$S|H~ZD0?$9Ki}c%UN7WxLb;3sE#xdsxT}H)Wq&{p' - '{!Y{?i})QN9D+jS_oqD!L~!a22VW){HZe=YoJ(~8u1GKl=72*1fIx(N1k-)Kypo8HbHGlPN}h;qSw3Eq+)KV{n9*|B}V);{ACQGH)aB&p(bVDEBN{aTN1CNA|VF|=bY4e' - '`koQ5cG)fw(Kn3x%D>vBB1CLLNai|;LZvBPUg3$v' - '0`0P|%e#DB>`YOWpF}w&tq@qyRGhl-' - ')574sw#YFG2>w)~Z$VNMWIyU6wBfP_K8L_=+m6R7hhp*Ly2Rk0edEd6GmTjCUxJ0AN>%UxO*HHkShf#JNdE@gK~*vJRAbquYi)U5' - 'GNR%&G&h*FAkmYrUok0v{YnxR^9s>mMt1zZ%&zMPby6Q!_TIH~1L9yQ9WdHpR9jz<%uvI=viz>F-!N=r{aO@pn)4xB|nI&YpBUZBLkY+zNqiH&%Lr0Nc2-`nxpIL_)>GnJc2zyS9gY(0gK7B8ITzhtcTY;Bco``hQGg4BYGeqwJ-7`u}9C(s*28w4uQik|c<9uog*}t^vwWxGOyCOu2*+>LA210A~z{' - '$D8!?1XNZyOgXZL6jwvCfrjABbn$&~4_ig*>}d3EV2M-;`JkQZ=Mzf>X(LL52iZ>crXX5!7Ix4a8ydN0)Tl-!xMzoO5PwW7N%i#5' - 'PCc@mGa$kCBF#7?Imzi9M^T;xkEdiFrL1M;p?_G2UR$0qkxL4*7G>Xs+`27HG$M3Xb!cSwDQWi;@aPc!p^Pmc`XJ`>724)14ObCw' - '!01B(4?TiHBb=#aG3p0tnghnL)`kTW2otqV^M?vOQY>Odao0}iP73n{`yegY3uyuUv%}tRPYMc2MUt&l7t-Dy6&>OVqCKY&Dmz%h' - 'j17Yl&1uZOcR+kpqmlU2M=;FPqx<42otC*Pw{lX?s1BWncRfeGCs#PD2TM$9{PY(XRzU&uhW`6_v6!0|$Hj9?gOFi2iQ' - ')?7dWR!B+8Oy;DnghuR0^3O>OWcnRABcD`YJ~h%JmVovb0AQ#|2m%g1}cR-?gkIc=*I' - '&80B%1kf!%p0&^p?ABP7DIgc-vqUIL`;tdM;rxG0LpX_PK#%vsWf$Acq()V@lpurm?+Z_Szoiy>zpc7y%outph+' - 'q?_l_;m1)k`6GogqMC5q9!e2' - 'RlPg_W3E!5K' - '&kFpxDTq{?@3f^pmP<>hG;>hI;#tVr*7l1US+oX!36(Vww?eW;IcyQA6^vKY@x;`J1`t1G$_y{I*Wd@zG{gJ=2De3mL;IFmx*dWa' - '8O&eBd=pEya|T)pDD%no)^B@HQg2DRH?>>Bdre7o_t3p@i%Bo9+AVrxE{YD!sU`MVrTySP=)FR73|!n;Swa|J6_^F4CCQUyg6+' - 'hsnqBm&@Dfsl9D-?wri|i|_mQz`|suz86x{uX{uC=H2mNSUyIK!9yIQiia?%^GJ94@QW1TcbzA3<*1D&SYnf7NgN7D@#r3W3Zt|8' - 'xT=hj;o%;VysKmq5`Bi@M{R$O1STI8L>0c^6*d>e3uq}J^;NHFxWZFgTDAsltYRh~XcjbiAs2K0Z!A=MqxIA7Lh13clt?mO^o@`jCLh6bz+9-4bOv4}?A<*~}2;VhT?M!ClpjlYX~k61sP4BT!Oi>M@=S=Xfkw)oQ0q56P=n4{Me$' - 'q&^I8soD`=u;8&c#RqQtUgQ|I{(>ugH>N4e+W>Fi9Jym1*46*{o74`}-5vF2bz*PO+r03|&Aq3)_oh5B`PI$Pt8aPNy4}6zRcBu{' - 'Ln|mh{efS4qws_6s>+7RI@{F^AK_iU{Zhlx3-24tMfTR@$#cKraM%5K?k&{ZtHa*xqi?=voV>JrU!uKUx|_Znk_8C!RLb@u|IxgG' - 'D*wbe8?WAmMQ^IFewwAEwi*Hj9FSW?o5ha7YTO3SRsKl@cm%fiyGOml#a(RcEN{a!`SEGT@aM{_&{t7n|%>w3!Be2qpl>vc(MDA~E|{#w>#-B6LNmpp+&' - 'ntp?BA#@ck?b}ec-*9oe7n&bdv?y+)!zf>)*H?6%s<)J;BK|hSeR0+);j^Q#-Iy^`!IHG_e-CLV;Ci-e3&W>YfguPr44%5AZi~_`' - 'Ur_w)5kKU_s)+vXH-WlA2+2(kPtDS*J30Mn6E%mQGDvl?ZRW~$Ld=IAX=rA8GWAabUa!;gUb&F?vi=DJ|#=vCEx`kbO~_PfKC*w&kmmivp1R-iW4' - '=r1|UN<0mrEQ%LACCw0Gb8A5cpJ1QY-O00;m803iSi5a4DH1^@s!5dZ)s0001H' - 'VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWo=?*WMpY>XD)DgomW|J+c*$@_pjh;A8Hk{uxY!Cz=an`6BiA(N480WVhw|#CE8{~Q7TEb' - 'Ihy~z!$SuZC+MypoLDm)elzpUp(u*xr|({uJb#9m&~zh-W~D&uQX;*jNNtIvIeJZ$K6(32AzG|xo-?sQ2PemA6h))aT9yUEcwOsS' - 'Qj8HRs#0o1gebK&rbZ+GOqVj-x&!*UGFH6cKwKy2kQ1fOiS!Caw~?Pwp-SoRO4GE3p&E^O^W+uL**58Zf26ABI&sKxPBc+edqY$e' - 'YvVf30Hr2ED-4gGH#Se_e}9_4Kb+%}gCl(Q;jj7oq&{ljm' - '!siEXkB{)j`RVb|r}-(E1DrSDJJkc_#f$0ly=9Ch=r&5D1Vt0`^&`3iL^)j}Ohs1a6f1}Tr#RaZCdP_>t>HPHBBdpq{S$Gf^aAp}' - 'oQ@EDL@5ix*$rfTh8pd(t|}_WvDIK}P|`g`chGz!VTz)2!fQI0vXtYFSw8AL_DC?!BvpuL#3`@~m4c#5mYPC{K)~{vrjfr?e1pp@' - 'CP)j-$0i;f1QUfpzeM^ZHfEd&c+uR~m7X^_F5@Bv=y)S?}T)Fx-=i4aYS}5RyO#`mVlAFl%X?h!FTp2D_L~0h@#e?&l' - 'TvG`rBEmV|-!t`=VW>S#RWwkA*nXjzZAYNr)+%?7;Km2+e7GJSw*?0nIm@o6ZF5=VEYl0%e}c-DF~3}zs%?`I)q?40iXtEFDCtB^' - ')NNNYI<_)0;7MPLiHz4d1^FU!=hJJA|UL}YpXn!w3&wl^oxA@`${6hbr?@Vva' - 'Fso>%1PUPUNNzsA4iWbZ=Gc}+{Bi)>s>BG88^CGLR*r8FQ+AMg)}kJ%zq4F#rv`>EZ_@}}g}N%twluq}O6bq3vkFZufS8J$73h~4' - 'dSMWRBJ1#C@@#43`+L(M`Hy$Pup9PJx1HN~Rj$`8V}!%iIW>#I{@%piJ?MmuwL_#`j^Fe%vLnW6cs}ki#m?v' - '%I;Y(UY;cAN&2NU!`_WbXFiVanqlg%wE?t+Y#3^Yy9>g{T4+|>o9Dgt%hLhzlH}Ih>`Zo(+jeBTiJ0lVcyud?Z56bWiHR<{%U*Z!' - 'O`lV-0RqiXL9WIHsmAaiY3_<`B6)ji1$rPKH)11QwbGn^V;Qv@55&a%>Ov)|T4R<{?c`3ztZThR}qz-&47dSOBJGV>sEQ#;)~qkCy}ZxxM)L_!0k<3Sz=PQ)p64kB`suF2l6%n{g5v0blwbitfm(sAqMu5(4nu?gUR' - 'J{Yv(rtaV!tbjA%Me)#T5oA9s5${8?GvqGLfvCZrJeZ?o=Tzv=s8+!b9{<(^zWmId^uK4)U=oI-CA9x})#w' - 'i@{s9ABr|bZumVq3*DP}u+Lc;+O;(+RGY`aE(M#@1ePe;jYR({5bkt7P!B`xP{!tk9-CJaZ01$8`<*IUj{XHuO9KQH000080000X' - '0Ft{9kW~=?0I^R104)Fj0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMi2W@c$_WiwA9%JK' - 'jpUuCtTwlDsJNUIMLT$RC^iCFlCh*_?c!I!4iG=bVw>#-?D7jL)zT3ggx`BWClcB)}k!N`?5}(^fL#__lKsY6(;iPL)NgRL?HmwahBe5NenTerY{-YX7AXQG5@+9uu_l&' - 'a}tJrYKerVw!#2r8d#*y9HxnDUKOzHC&>a<`tEQ8Q{py' - '-EDK~QvNN;)2snp`>Na?HneUPPqOhYt?I_4hPX0TFnNer0=k)i`Xn9govw0arGpwp2XF&xO$E}Qy$qHIpslI;gn}J<;geYNnv?bQ*bg)dGt+rR#=`CVUKFcgPs!VOVAd)4z1O0ttheIr>2qI' - 'Ghi)h_esAF{32j;IgK4^VKED^%}JI%^Q~VSPF4dw#ZZP`MIajV$W|+trzpetB|7kel|OU6kF2tx@W#fLj7NRuDGaZ{V?=DaShacr' - 'ruR0<<9c(Or`v`^^B5>j**!6*QV7@X*CKCVVvt7VVDM' - 'f{_vrr~sc1V6xeOk1=$X#3_Z$okQ^8QUhR?%M^}_5' - 'O_e^5*8meVN<@%)543kxgsFAud&H4*P{8eo^DTmibr1' - '#;qLRa3GZDaU9g}7{b4;5jDEhfST;VIy|ZQGX4-#yW4hXBGT>7`v6|RAuy?9S{OaHUCy8Jes{cBHpz_gEep0T%M29leUjDO0`pH8' - '?f}`85Ny|60x6qY4$ORL@q5Gw)-66z>j0#UXuQEB@&K' - '#=92oW~-r#g&c%x%^TQ8vG5VG)L`69jTCWb1yHOmPM8ujat!-DLm4uNoy&?}Z;~t49IBupPVXeL7}a+l);*`=&R$@o1VcyteisED~SCcv|mEyBYsfE9+2%poq#%E' - '0K%=rAE#=UK!!Cf(neWTG0Y8)jL6q9Pg-`6p%;+)8d*9*ifk2KSOH>{q%~(BK^=dpDiFy9PGR{0$X#4nidtq;rT4=Mn9T@77R^&06!zaP^`Xyb$SQu2k8yoHDWQ)uf)G|NXlaF`OS3' - '3;>hLxd81vFDCPcNmgJmG8axJq$*wolT`=7rG)i1?7}k@2jXiZlr}0iSM=9cwyCYUjpKYwk}eg7@SMo;90{0i4_Oe*^pHz)mmeN' - 'MUB`UB#5U}YjC4L=p<^ev&j`Cj5GxtMUvehB5R&&A7KZ8GimC+B2KrEBmRg)n6O^5;0`|SAc4Jy|L?=7Ggu8wao`vSR>ub31H`*i' - '2DYeu11&nm;Peza?-2c%rKiz3K2GPIWGFp{PL!)N*!kKPs%+oxY!y<+pj~MQ_Q_=gmqsL}{XxX$@HaFAS8tAH?v;VfGf+6ft>7;k' - '_3e102xo16q)C3WDIdr(RRnv|)}OR62v=)VOl;?;rY<6GSEyDyp>`!Y4`SHlqm~Y6@3H+rhofrB;5CM_n`+v-5vS2hA_5&PO' - 'YD2NT7=B)8FJ_z;*^7}B*uR6gUX-`+j*jag-v7?QR-!4zaff1;B@I9hM7b}ZUFC=WXEqagg9(1Nz`TK`otr!uOxmdfWeNmTV3ZWX' - 'Qaz+w*r?x8LUG|qN30}6Q#z1ikS>`b`CkUfh(JlaAN0BLvau+O@$6N@Wj9cu?V(&Tf8t`LdIMxNTyCq+DSrqqgDKVrVyp|`3Q3FZ' - 'i%R88{`' - '{5`>ceIAM~vO_r84)TO?pW_8' - 'o++)+)KAZw9(smJ?Ez@eGwY{^kWSAO0wL`Y_?x5hnfvHDw%6Pk?X4)uXe9z$-z`5~O{Sp;WU#-pL<*?aqia!gLfz`dngNVtSf' - 'D)sz^1tM*~G+7ZAbv4P8#{da12wL;ndngMhN7SiW(o-zqS`#ObVFIgFjJy(&;93P9Z%;a6b`RVtV`y%!1?-3|Rw}V1&=h+NW*)|g' - 'i4&7e@*jgP<<%0-jw58>w|S=c_ElNtfFJDUnQhqfI+^Cn<)i9$W}Y&~G-$f$>`lp%Jh=3|ITgE1$RkFY;$Xz51Lx21eIELjM8?CA' - '^&Rry0yar{7s2PI|8PD9R9vdwMe$6O%mN_PUQ5`;W`d1ku#cy9lWJ-w3RLH%);Q&~+}ei`n24BDTNp~?2~(Qysj|gjzo^5?3XVRq;!vQOw3~OveXD~vKRXFG#L?Hlo1d6(yF?9l' - '_Bt#qBoD2a>GsPQ@~P@NX<(rdsAM=7clw#@VG9WjzaVgPh$7j;_v@tjE)lPG7zE+I_E``D`pQQ^5UB4Y$k$b8Yl?mkqai-pUw3_d;M' - 'Q9pbW@PR$!i-(NKVJ{-Qpr`z##4oy94hsD4Rz|hVMGTAk$e9L%hWhSGIu=Ez5acRlD}G-pjd8W^(auBTP*Bv*(?lt!s$g{{4Jx;+' - 'f7zxqOd4f3#~uxw!;FCDgrqWaFYd3U-K&9vt2tk?-hofbUF' - 'n<{KM;>a!77>R(&WE>)Y27#cCMocW!4rq6pj)Y7yh?OzVyFY^8afPcl8D#a;PzG*Z)Le*gdwNclkEI=?*jXgSz{|Tu1;2@hJ4U6*=!;kDCtVG4uU>N@tgzCWKwbA12xIjIkMZ&ijHijCqZ&RL6pN)Fy9LJp$Pk|FFbP@vW-bzm<;1aT9;e(f^vdEqk' - 'VAi6n9iqrbJ3$177Qj)_%JuqcC4C=zBcApx;iP-CT&&KujPPYB;^k4khXnu5=zMw>^MszK?FV!k+I!IP+<>!9b*}-f*&Fr0(s10=' - '87aE?GDDO11;|p-Ph7AwwjvLSNT7=w|(Zq' - 'cBpZqA&WAkL1Fi=Y1_!K4g?v_llmjw@(lJRsHI}FHRv8Cw%G7$ieRTfgQi#`!2r!1jDvh`(#0WrG*;QdV_HyVsM$20DqdhmwY0x7' - 'p*6(qyp7%~_txdVlH6ZU+$Zf4t4U_=4?IRIcLy2*JNjb3QN(q&*=3gmPFq|9X`a;g5~sLy-&zuVo>-KXPTHlIzqAvqO(4Wtl&9<@(JKxfqhnupjw&JCBUI+5GTHr>{uVT>8@U&jS8nRN&)xQWV;aJ?Hf=Bf9R`5FEH;q41y-RnJPpXTFw*ncy$%*%d&;1Tp!z}z*@oL$nHXOw9H_ff&+t-y-9XXR2' - 'tI8$SIfuM2Kje_+k)$wl{Z_AGEcS%5P)U{4vk@9?Mw8gZsy*v$0RJu=bptmC9A$^cAj#7!hSzfnD_#Gmr<-bP' - '9ORNm<&B5r>!d7PZ)Y`dmuC1j>gl(*@q)43qF&)!?x^G7t+;_uOa7QttnQDOeur&54?g|7}UU4sZhqcoG@@_*h+DBI5rf$@5E6UCG=}?h}!x^ofm@u$Uf@?tP8@C7^J9QTg' - '20Av?6UX^Nf(nbyQqn414{?&exxEq>CGz~jE(ZHQ&a~8Bp-QnyEtYBGkM)|w*(Y6=?lFghAnqCvut;8~4i=xj7}0vcC!Z&~@G' - '_`^X&Crk~t)>H{4`Qhjv1D!INdC3_!Kpt`KYWITQw8pB>C-rcc&4Tr#akEZZ#xX%xRF?IC2{XJM0XIfz^D^70vgk#Fr1z~kG2AHj' - 'uGgi~B_!ZAGJQ)!4&Y4lHSnnmQ-id0x?+L>{+*spzA7cc`Zp(Dy=l*JLp)bmN' - 'qu%q4le-g&*(CbZ#|bmF6B`C`^N!WNb)G#~dQ6Del3xQTOP>nF4VKW#Ob9YyHM&m{7bjUJ+jln3q1e~%nyvrO>`cy>X+Pn%=fxWt' - '+?^b!>DA|TzLv*{*Yn}?B2nwv7jqmUbE9@uX?2h}&@AiZSvSL2lyw-Z<*MM96oV?=C26X>Md?' - 'cx7@faCvP~O>5jR5WVYH3_jVg>qC1eEVvMQ>Lmo)TPa%E9w%z`VWe@ghW_}D?6n+2bg-rQczW+?ilTT_7SFAU*u$&Fw+Pyi2jxJK' - 'w`XvZ$ZjoqA8%1pzY`bafkDyNP8FWHK~a~LarbR&q#z}&C@(;_q=5v+$_X>?It@M0l9&|t?`ab3S?jj*zIm<59$KtaCgZ1i6Q-~QpoS}Z_6Ul87c8o4|fhb|eQ' - 'jF};zRoX>}mXFY4r>2N-y*{n{zrAp_PWOKPa=o&9{el4i?&noG)@n2Wt6;nnJETB)zzU#Hn2jwRXTCW9Ue7@KqaJ%$g;xL_Zva*H11a8482tSFC1' - 'Tr|KxFgnsN-u|xO{FP^}cD)Kg&AX;oWhQ=u<^u-hQE!!d0`l2?FqY+t_MXXNWV5NR#D7pr0|XQR000O8001EX<5F2%n-Bm1`91&u' - 'F8}}lW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGAkFZf0+CZDn$EE^v9xTUm44MiPGKub5yT0wf?}JIC%a#jVJ)9I4_+' - 'E=kF*OQk|UU?`#l0xTS|Xh;8jyL$$6;Gn3)TN^#tBrwy{$JagGJwwNF-b5d^EQ(`ZRje-9;fk-*ILg>zvECFp&ujK7jq9|?qjJmQ' - 'B3~BenwM2DnOv?oTk@!GO3pUvhG%KcS;99wPbyaAEUJq&%cC`~e1@}tXuOKb8~nZ~V35x;9^Iw+%@l`DmRWQI&!Rkm*+8=7OJ0^d' - 'Vf2CLG1uz{?4YW64X>&qYmhybR*c_ovNTR>SbFl?Z&?u~VH_vF{l@Ba&A(?UNL1Fa9;;U{dR-(<2J7$=X;v&RSW>*taT+U1woKur' - 'P)sm!9A`3FmLO0VE*lg%3?)5aI4|l5d8j56`K+kgPn$Hp&3OB*f`RSlny)vwh@cx6S;k4xRS+%WmiS4{OVCc)4$5nOUuWr}eJ`J^' - 'qddCda#Rx4QJh7fpsF3F9*GgFs9G6Bkc%)$!7{aYzlmz|{ag4dUewzS>Q6pD$hSUwnIbe!&XnwVv)%xtK%pDCT61#)vzAfJ!&RfG' - 'u9o;MY;`_3HURlRXL+q8Ier};|MD_CJUBc)k|tS3RlUj9RZ#Mqw5kE8a_o7NqvZ)IKD*&{2sYd#jxvU4)nK' - '-X4XQXW`-DtH}ia4UbNLJvl!+{rTwhGW_-6{N&)}nxI8SOy?}&H?E;cnfC$' - 'uyhIdn1Z#yCDQ~VLt1ZHv830M2?o+i#0TUug2Anzh<~&;@Y6Sj>jkA?LNHieS-QI>68t{qo0|Q^w?}0OP6{aF_(3mFMrp-cacGdM' - '-r?WU!ML@F;#+VSB^G!-#|Qu)IA|Usn&d??{L$j&v+^Nu6jhHAiii(-m@dVvfV2|crV!CS1Jk3|h2TG+z)&d3c?#d>yE$TOSd2A5' - '1P#((OW25Q3J8%~lYRPB3R4qpL}e$g5N-+x?cp6S(`5=&X{g{)07~Q1r>8BzX0{*FyD$7FV%MJ#OxtjH)kd9b1=e>d&k`2FzLDNV' - 'HD^^GZK_ovLS&TY7`OO6@RMyxL#-eZuWYfcc{PGs6fMNqjqGS?gAAX!o(av@5KB*s`gPId$!?7HM!HJ}{YThW0g~T?d(7BJz3hY2' - 'gB+R$Esc6JIc)KWddfuYYemX3o-*Jkb=?xYw+k?_jOr_=XqaSx``Ej+8${;p25RGVq?&*z)QPPB*Rl!+!{_5=X%{y=$h^|pxmu><1lyeroJUKl$' - 'f9Lq@nb&`L_~z{BKSzi3)*$)&ygTsp4EDA+XaCkC6JEt-x&eQju@o{zd$;L%ksdA>Ms2!=N`0|(4-rcHRj?$bYeJrh+nx!``2$Ay' - 'RE98E=vY1BcRsTprFr4uZeXLg%PCIWIRMBQXnlfAkb>to5cS@#Fu#Rd`8~v^1;^+p))>Ya{#8X&#;qX|vYxgCK%`t4%CL;B@PL2s' - 'Q*dxW%U4NoaLF;_f|!nB(M3zmo%de?MU0=V-`jG6S2s}>R_O=BHajP1wjQlpqPtG3byUZ&6*`0C' - 'mkyLEkm-XBK3WVsh%Lwmu*E#R!}kWiX2#KvQuy~bsM#R#21hIav6xcxQl@KX7o=#?FJU-f$B-bP(V^VxPyx%q))-~!2P$fCv&>Od' - 'RkIekMFlk-B*^Qgjux5TXnim$i#o(8nwNZ&K|;UAY7+2}=2QCK0_uI4f`7+UN3p6|RFGLVHSbVr^1&UOMN7yD*d6ej5Wj)#3w$m2' - '{*Jk=QNkJ2*gh2ERg>QW3BfTG8d2h|)7*Xj?8P_i3x@ysdcNoN2*~h6bY_FNSOC%6-kVmr0V&C)`F-kI%IO@6>U?NiTx>9Hlis4}' - ');!BxdnDw2WfY=zI}$(lx(=lm2D|EceF=*Nfa*@KZ*pVN;aSD4w_lP#nzr|e{B%bk6w7}C2aalNalj_>(USjhUsmQ>W=*wnM_yK(' - '-@4Ddp`eKQKd9NL>|O1rPPK|&eEXf_1uK3p-+Cjlk_cI`lIDJbXnApb0EAS${2u!}O-1dQg2iQl4Jn8=!$xpq1~+YE0cOz<-S*e)' - 'kn*-vL$pIgHUx!6mTj#B+1T4sk)-z%W7s3(tf;t^tjGd<&(!wmWmUI^l2sFaG>Up!oE5n&gmff7ZWg+1LO$ARI00l^Z2(;-QE4zY' - 'Rt~^BU{irYw8WBLtmZ<6pT@Q9ZAdjzplV@N^1HNx@J-3lH_tGhMEO>O2wR6R4njTZJI$H%?D>nYzxnpNzyIT((IN)DI@o12#-%yy' - 'Q4g}>J@Pf_q)k2W5>8Pck?b>y`8p-c4ZVHyY^s-yi}gAM1+{f`2D{Ro>}i0a&=y*1G_KNzJvK9~$i3H4(vA=={NUfV{I{j#9xE&qH?cV+TtIvJ*`E>fZ_u%xafiC$$s02#Zx^QuItJZdV' - 'CA4n{?u}O7V?j!5N;4;r&A^GH4HprKFPGS2-V2}m*cIwwN1I4_ymXOyQrZQ*Q_{i=f&nv(H*?D4lV' - '2$B%5sH_FZ;s34-oCn5s+VnoHnI*iAA6~%?1?%ZYg&bjQQMESB}H8i?gV-2UkZ(unEOI;SauVH(#u7I*=}r&' - 'hV4aGELy<2Lx{xzm9r!8TY6XIMpSEZXbn*lEfZ)}ehO_93|nodb!A2cTwmyWFI5{QfD#AdJEmtMwwq6-2e{r|L0z$1muWElksZC5&*yz;K0WNqw9e#k(#8>7tLAvQ@#l>%PXVp^)(Rq' - '_+U@RV?=9amR9vu-E1;0zSBQYAYTtor$fm{rVr1Q=a^xS0l>u-%^H}5Cruy>1WE)I9{kCfVntw6tZ0fYl{d&kdIR)Eh$;5Ri7E49' - '=YUM>%d9R*OVyGG8nyw+{LZhKIVZT!Hd*Esk49go4j`FQVqt`zoIZ(RgOh|f@f' - 'C)q%+fLd2*MR}4)fE0tZoyLw^23n!rIY;y#X7l|`Vl3De4HSkVd`AgcS{JfV<%=!g0irC?8#tk9Qz#*u;Ra+;s%gOB7!x3K7NbPu' - '8*J`gZZ~bAa%u5G(O;f;!4?p!TO5hH#Ss`?+KzY5TmE22GUCgyZ_?`@u-}@-YhJI4MDGxm=efJWNT)!3g0mA(Bb(Ked|$Zu-r{It' - 'n&TY;`aQXZDtA)D&}qM%xJ+-FlAHqaZJytyB^0x;-Mga~60CCxO<~qQH8ZMGImok$c=GNBUTM#SbD#cy$P}P6wlQ1FCms?3vG;' - 'lY~bZe12}_wxXSo<_W)tNqFqi*%9*_*fK>kQ7-6pOm)k*^DJ5~l86zVnPNj~ubp^Budk8)w`H-Z4l2C!#keOUU+`Doj|Zh' - 'gqF!|TDSMdaCg%0S5PKwL&J*rIymR+;*K9zN_UW{M^IovA~=5?K{T}NY18{&xp6bZM(hv6@e`-feC' - '7$u_IfM@nbsHeA`>0pTw*^2Z{vcnrv4AGTy?$S^GKW!BH*zz1fO@h#2^LP' - '@G$aPMbj6SD?~S6J)VtjpSWwLdBX<>Yzn-6GB>UVOX1#1ZM&@iC&|6*c_T!mWv&!EXbrGURFQ35m(~fuA;gDm`{4tGgrtU)d=8_R' - '5S8n%Uvy5LY=~W%0Krw%;$|`1WOWO*v8i33le0jtI#8n*kw_(vi!!NpI?{N@v2*ZVlrU&&>5xC^@c' - '9=;+q6GR)li|?9bTCOg(>qU{do__M0Zs;G<1$}Ul0r-+ld&rfGjo@}*&c2YvZrmS*>=V%{8h`&cwEiie(hn?f*BC{lihHxiLHQW&' - 'dy6~_dJZbIOW`FddCYT`X;~p?NE%`YNzgP7wE?caBXn1w+&meaev~JVOtxK)7%|*UFh)%`JxHu)j1mv__sDh?nl|KXgvclQ+Q6KB' - 'eN9iYI@vqju5E4a=VEq;e9+bQu4)wQXNn1$J8+5Re;Djjv8x8hQHAd0I;Mz@4%V%|^#(wm?mTzKK{K$CC^zjbE}zrwDaRS5i7DC6' - 'y+i3Xh?2y`PXp+*jM+meO&{zX?wB{3Ou~!Hvvd3%%}7trZST*tKkRb<%<+WdIB)QmUX(`D;ic8o^=0>ie(h9?%QEljB4m_hJTdOS' - '$kLyf7ueMuFUc_e*ai3<*(jb;#n?Z}(La%++KPTgXVe6Xt^N((7{gqqN*t){#Zk0i$>2s^TfiYQ7D=fGj^2!t*a%_s?k~V7EVYYy{W==qyHm5hC~4Mo9HTo`#M;>v7Q{07&Zbipp{HhD@Js4Zs%' - 'Afa}i(Rro#J+Jur?^GB!y|M&Z5B!m#y+1$B=8G5>VzUl7iZjGv{CSR_@G|ZzXPMc)SmplBVZ28DHA>Ah_S>E(AS+6v@' - '=V87}Ic-zEl@dwZRTTdblyLbue1LoyNB2C-Dftp-lw)SM&GM9o8%p_8ltF^Rctz`I$>U6DN}hNe#!KEM|1rvxO|sgpIq|PpBg=T9' - 'W0|D*wN2tx6h9DvEkAFQD9+#Bhf6`hkU#JWbC^M514y}&q${5C6&8n%Rn`&eVge1D&1jt@pLbiz!@D(p-KWV0^9f*;>;e;~2Oe{5' - 'mamkOF@y=8!gaI~L<)0?Hrpi4=}QFZT@L9`0=_(Z?z#mbtZwom{|g{PjRIZ5@Q(f#6Dlb|Bb}J#!|DspW#+SCoHBw-i1i>0yd8' - 'gdhx*+!p~;g$T*L(0;dFa(Nc5b{zB-Vlaa;*j<=5r~FGocYGP{5CMPjG?9^-#9TTY0m#!Q`1|ggTmpg0*VTzABCo6Lw-^-80p5JK' - 'UGtVSPcbZTm*!EK+HT89anQ;wMKKiu(oy_`G3#emJq&pJxC%7M8TMW7avdUSM^atP3`*SnD&RvBySDB{pyyrbfo;qT`9h#tnWj55a8q>WmX>&L*emq~aq!806~AE^s{U' - 'Pj$wr<+Ev%(G!eyuGz?9moF?U_hVT2K2nSjWV2I|I*Vty%sXOnj=biNT;@0CrB#lY+!_1u5;S`c&FTZ5fQawoQ1llrTl{!yeRE;' - 'TR?c7$@10PO3&QBs)N@wML~vqVQa`~dF!ue+fwzRF;_d3hVcV$X?=iu*Y1eK4URwjbjOi*gr27QcZ&IA?Jd>Rp3V%l8S1k-rO9;gX5V*u!hMzJ3h$Juvvk?9$ArTgZhAh_cW{Y1RjZkv@ASn=g)+9+{y(oI^xdc*i*>L$Rhpt)rRFo=' - '5C*NR>({J1RL{DviL_>tatK_iK8IqI@&K(|@5g}u@GMO5N584q>a4950s?gwL2gzSYf_0R|DCc^c~vJ2|3k|<)UM(pA2lwl=8+c3;39sKgk_bSQA' - 'Jn(|oi)?}2ymExQQO-A63+dv%Qt}TGVF)$3$a9gaG>sME&?zBQp-?-v@7p3GWI)K%Cc{A-ZPh*riI?<<q' - '=rOEAKT|e>N>%LaPpn6x4?(Aakto2Zw;0Ww3+8C-Z8_>!qYE)A4550}lvhnnhYXKKpKOK)H9%*0G`h4jc4=WSwb@imlsXKD;X%zQ' - '1}HRhh6e;x_A$P%P?1sj(M9P@loy5vY9|cO0g7(;%=zdGimjV~ZtsV7&zADD194Uo$5TxA*Ak_IFIiMnrox(L' - 'p!?3$t75MuURTg)#(9TCGHHvVl(&|TVfw%i?O}tfLj{BE7f-s%Mnie*$F_9s%yP$$IW_`sF&#J)uj$xeIIUbE&n)#%IJS2ZYvjGjTD~EKS+zRp~FzTdZbAY7N' - 'T)5JjSX0aP#km*WgcUdIgQr%^;{RYHmIYjooJp{g5y;Goz6eVY%8&?Cz' - 'wyN#z8E53o@Ln?0$ocIJ8RB#Ch8%p1KEClWG<S5f66~;%-^qAS_B&`KCwx{zdXN*1y&3h9b>{xYxAtevmcBU`ccQ>CI^(&2(TU-1V6fYAZFBe&V#9-&@e^XkLCpUNF<*#1%OGm(xH@F+' - '-&0lc-_{Rc(I1MJh3cwjf43DBw;C;Z)nlXbuqyu~Y0g)zjOVS_+8}umgk;ffi&P~pi}no(`Wmg>W>v?PhEc}J-Nkvd;Z~X^DGo-O' - '@sno^MovQa@T`If<;N|cC}ttt@7oX$7gy@iAwvGWUm~{-JD@6xn9RAE~Fq@VSP%L;hnfg{kDf2=OOlj)s|tL#L*Hc' - 'OXjQww9%d3^e%3T&Jl@B;QSjemtgN*ypGo6*l)7o`C|kqF)uJ?kK&$7-fbObYR2bZYe3HzhXXoEfoqlXn0NtSB' - '+28C+oZIp$PD0Hg37`*egiNj6tdLaj}' - ')WbTt3)khVgCKUJY`5L=wAH><{tHk`0|XQR000O8001EX&epdw?*sq@?{#nZcl(nIzx>&h?tf5MtznDMlQEwz;^BQ0o^)M&P>*a}#Mgz!hlnln(FmW*e0#tKM^RO2OGWJ%thSJ6BX' - 'D*lv}Ty9FgmU*_wYGI7trL?5@QPep)PkEg@Ga@CV#L2m}I%zy$|pa0&0hgA4czu3-v)f(;*F0(1BXKA3O_-{Boz)u(e^' - '!;|AK8De=kafWd`d*z|=O={oD%lQfzd$l@fZ#ye6Ih&Qt3}e%m-jCjkcN0I1Bma_k!o$}m-zrb_yBEaFa`qrZRMrt>?-?S3>j-;l' - 'x!xoo*5$8(-`7FcR=CH!jq(~Mj4aQw0w=bt_}cY;4Zr$Nr1jy~$Pb9rZr+^WJEv0dv*Z55*Kn@qMM' - 'Sz-3>LjMkIxx`wMW+DaD&jShBOaVg|;n5F3$J-EJ6ZM6+DZX9;uJ?M{kCu?*2E>kB-;1yOFpMXzWHspc&hWpL9gkX>2t_ML@zKES' - 'cg9za6Lsd}J{DLM%J!cLJ{j*W!VARHYXk0P>AlSORsi_QBL9$`^v&3?Ao#Z' - '$JC9;B(!y@J=)SvNvmjKk>8nITQf9Bvqf$ElasVF>C`5~wy`kqnLxv{sA?$Ih2rhFp&gLO{b-#j^x' - 'i;>w^i&OJhEe6NtNUeq=bF5ZJL-Vw?A9jH8pwk`?%%NJn>34P~gKodmBlD=;x&Nj!91O?ZDFfUHmv@>d%q|x!&+{Y6zOCoz!gVg<' - '8!UD|=o-`bBt9Muw2Qf?T7XL3*deoMit3Z@jm3vEOR>eoYT_{D8|$%xs^>LZLQaeACkTE!lVa>v+p3UI~kd%52xvBR$GJ6{MA?$e@kod5_pW-4ZSb-FBG5=&~BieCID-0H_8eVr)aj(}DYh+K2U5&$3' - 'Bc;)k0aSKXQan;aJR=7%8uSPMCxDpN%|c9AusI0+Y?Q@Gp5vs3$uFNB@eXzG&tuLWk{tO8w}jBlcvIr6' - 'X!P#1e*sWS0|XQR000O8001EXm!`g+&jtVh%NhUxG5`PoW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA$KaCLNFVs&Y3' - 'WMy(LaCy~ONsrq$6u#?MusT_RV<0&iP!V?~ZBsPUAd?m-3_+kJdZJcaMQR$y^?&cZ4WZ4Edy+R4>)UT{cTog?-f)MvRt`$}5&8?=bS;UZ=#{lW{6%lO' - 'vN~3U@NXL-_y@7rq0eOKo>y&2K9Ld)gjVH95FHPcRcz$EXr$bDjIOS(As}EyOIjua$))rit%OX-0TYy>D7T2RK|Ii=stFa($~dre' - '$;Nj>$4J#m-^BH*)K}|@vOWCFq*#>#tEh4O1AT(s^*NiU7k4P$A|1>J?0SE*`$udFsJ{O9RR37r;kJB=_Qf2VaxfR3;4!dvcLQ#&' - 'y0#Rh1@$KlC_dt<%`2z8O3l25zS*=T9tM00mR48%PS{-Jf!F?!&m|XK3lf-)9UJ?aa1YkJ&a*6}l!XOeq;y85ElT}KIe{k' - '`Hm}11s%?)57TZLuIFWS)DQ#`E)bsuv>&YHNeKFE(^z$0U(9@aTM3fiX2jUk15G$-lSmwoK<#M~K=' - 'vRqGYe><{uIyM4?QPE0bSU{@9!&DMyTys?62(gLVF*a0(k(uOi)YgoYp#za}p_PEPLEnMwvypf~TnoYFM&)njH^stRA!BN^Ad(vN' - 'pG`t-I{=f(D!sTK+et#SasZ-Je#{PEH7!>5DUhvL$onqJG>=$rt-!6``F#t9ZMhV>4l#&Rzw?mhWp0' - 'cknKF6-E?~dl|kLNXj{O{FMt*yM?S2eP#$7*6}^QN-mx>cdtIpl}Tck`ZzDigQIrZG!|)ukcf=gfd7PW$dSS~+hi0cE*Jsc$03NT' - 'YNgRf%Hy%O;3Qk~Hw;DPdpb+Juz{v5=rqL-xN6?87d(SuYy9D-^EEy3N%`$83%IrxsuMo)=Z8|0olnVHBp`VN3Xo4d%tt^1e6if~' - 'GuZzi?yEs>eq|b8GW6L^L83r<({wE#A`Y6^^WWJe0E^PpBJf$z-A%so`svv-smC8N+T&Cy' - 'HIOIs`Mi2gA8Vmxa+%^ZT!0Q^vfwIzrOFxCBPApDQCd?fI5rSaL;+U-?J|l*xUab4#Gp`F}DZ3PU6glluY>xp8j>m$kVy|FGa!*#vB)' - 'j^zCevP^1B1C3OE4qB;a1rX%}-#MO4gI}`DExO4Ej#*3=5J%$QqmwvI1O`LpUG~vS@SBPsKlk&L-nuqiR(RU>8%)LbxRqqH=qF1&' - 'k;x+F@!fH-jr?{8M$Q8_!Oe+boyN*u4~9u!@jewn$8+0!h+zaNyVRgq>Z?^Woo!H1ODlHr@*zvgSO>0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUu|V{a%p38E^v9xS#59JxDo!ozk*ePgXCxx' - 'uUi-BB6~<+H~rEUhl^XZFbsv3xNE`^r4uE`&RzQ3JHr=|l(bHeHs~$C4v%cenB>Sc#vhRkXA38=JXvIs~^G0M*6s=bKuBl0$?+2XCbHeMk>3ZNJnqHB~)yj;Cp>CfDl|;05sBgOBE_lg=$o7LMa4D^j' - '`mL6sW-a?GCS=oj6B(`BQTEU`1(iJ^za{-s%eI6cMAI>|+04>0S?`;wJj**Kc{#AWfZg^~^b*C+2F>bmRn28#S>1Bc(&9cPgD{U%' - '>b7fo#zpQdX_m$-0-vI(+o5MUSoa`mCi=WrD#|pNK9n#abI~}$kf5t&IhjsYt5wPNBriECQn0+H;MvExZ6sG7+8TSNgthRpuNvAX' - 'Wc|Gv-YN;Ox~*uzkSW=baq)GQWhwbvBUnndH)MTe1}7Dtj)C^VcuoG)^)Uz~FYLfj$G)VKld}{=*Sqy5v5Ht-s}ih%X3k5{NZS#g' - 'z}BoBb#lXCc@_@0ly@JvM2pD#aH;?h?f1qQPRZ!w9Dgdt^cz;La9c>wjm5D3(Npv_OQ^|fB82Y(l;sB&Or+y~+M;3!E6(KLd!n}V13T=J%#Nx?nus(Z$4ecBcJ)l`g+M^6-1>E)Q+Hb(&Y*E*uoUpR7K@3' - 'w3JKUI&EY0jTHnTHY+D{N2q$DVS>KnMGwNhkcaWL&jP$j?Hia&aJy' - 'sGi8hnpW){^+MkZ@`C0ZBI6M_&rY&%>?=HZ`VtnX' - 'U6!+iH~(X}PJF%do*kZFn`=*#A39pb$?CPezr;~7lr(}ADClj)%H3NE_Dq5Z!1KPgPu<+|YI45qX?I}#1m~zbdxW5spDlM^095U>' - 'a!?nxEnXC1D!Qe8aThJa_zhl7;5_?JILh`7Jp9WXag9#uCh7qg&WH?=qZLd^p1Luupc?DUeG_Jz0q=0T7KSg$RG{(rL_kH3zMtI}uNX?ZnwNS}HT5{%0}viNlLpOL@F$;=^g4b>6{UxI2(B;+@4' - 'g&y&84uw-U`3E}nPk0k{zQWHwdI$8R=CVfXW#mUzF#f>GK&u8|hh#s`MSn_3G~sBq7*~Z``{d{Ye0jV_FIm^B_NjHOfX+xA~gAMJoiZ3_u&-WMA6!DiC+dSecN_88@QFmRzd9B&PcNbnm&tc6vkui#pZ0S^3Tp*OKfk~v(I;gFTb`1_r)' - 'L_)qJvGo>=I*4h*Eaw-^^1GeC*H>nRBEm)+7tUNqw0Pw^D$v76g^?CA4XB@)Jaac6jU2#O!Lf4^uO^YZR69S9tEGbIp$x|Va13d|' - 'n&yFZ9WO0J7U>i)al6-0XD3<8yXO4x`Y$Q|d3mnCmVnGJAe?E(sQOOAm;!WfZYiW?aE*WQS&BkzIXstKM?&p6MVe!sfv0y&&i{lT5)S~cP@nbi>+R01`WPI8%zx223_jFkGCqy{djK}cfIBJc6iceJn=f;|WI|E)x&' - '`aT^;YcBSY?J_nW>|L&Cs-YI+rB<|A-|+$8v28xjQ6@tuHbaX?x^Or1EnaWaV*+nHHnSS{>^dEdO?&V6d`xBmz)a%Cf=1t1$' - 'CM%4cs6b_mG4Q}N)fjU50m7{}=SIxo5{@n<7l$2oC=%3SiyOW1V!$yB3W6UX23g?_Y)M$iiki+DF)-?NAU2)obu(UL0w%gFZ!jU_' - '{{54~MAq93&WCY0oRaI)RC{QBgz=3zWe;#BgmUq~#w3E>m2k<}b#taqGFWL_-*{wK+zzzA1I1lJc(#}X(Z{G*K}Nf^3q}(G&8Z_WwNkC3^Gsi)iZ!%4$Z1MTASj5fr|F4#V<~HzC3KjACObKCS)(P)h>@6aWAK2mk;8ApojWN}kvd006Qy001cf' - '003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZE163E^v9x8*6jpw(+}u1*%RvJHeKYx>i88rdF736G)N^;qwAE-B' - 'h=gRu6v+~#ZCxI}z54(_K%#v~I(=l4*u-OZvF}~L^ZESkK~|OMRjr#Y5nBCoP<5^nA?pJEtD9c-WmAixFRQXIl@^0mh1ed&tG8c%' - '(Nx7{I-9+_R^nP!t?INWYtdhqS{#~UsFaYM5{0VDt?Fd2s-xI-N{MEN^QsaWz^hceKD15Om-Qa@s+rjezjkWZ0000CKMz{e4FJ0>' - 'a|MJ}Do4r^kvE67tR#Olo#@n|=>c<|E3MPnd_JGec3pE2S+*Pcp;K8VN2d4IMhmD}8|`=hGykjTc@' - 'J^Z$OAe~PZVDOztu#xcywaff' - '+wb&JRHg3MBT<`JEI#pwT#7UWd!6CtUHkkplLJsFb)OksiwtbEG2-(QOted81ZP@T*Pu_Er4YXryuRGmV6&D7ZxmQujYs57VAZl0' - 'H%he{l}B}D4*ur|?5Q>Qms=(7n(hX@$QM`7FURwjJD)1CT8a6g)pO=G{?joc&u*glp`CdDYAEC{$5r5u6|dS=jqIOaPGjt3sg?LfR)c!gbxjxT=3m!(XwfvlO&e#)+rIZu{5X$|8XV+t3uEN}qgw=o9<{zzU7yuW4O2~4' - '%X$e`0h+uL=J=ovOx`=%DSi-N!D6TSFBt+jSkbg-l(K?Q(8(jKKj8rYpcjle5H!z13>iUsyu(4eveYGckz85QeWK#Cy|n1z&1gL_' - 'N4~0xym<&V2UYhc;K{Dd6lyF%i60O)K+10zk<-g5KD*t_g%x<#RBGa#C6l!S#;s~h?YYC~83n9Na2#+X!XqOf(pQ5}L69|P>LF{Q' - '8l`lu9=PM@s0HK1!2Q*5xH__$pl;MkqYhsk!ROTkPX9_ZfygjP^rU*EeM5(hf3cDw=!4WZzy@AXY8W!yOu^|yaY_L*8YRZ3T^ZED' - '8W1}QIs(rQ*#>WcOlpcClxsntrpbYMpOy!(PvX-)o5%w%UXA9X|IDAO7|Ir(pjYramQd`BwNIfJK3kZqR!Ax_a}_VHUSrIGoD)JWA3umS0QJMFW!dMMC~kbnLT!k$#z|7aGqB_maq;ZgvxK3>' - 'r)Sm``tFvDA!89`QfI6y1Re&L!sRguAEmg5cu}JvQfdIdZs2zU20urMm=N!_JC43+TM#Ol<^uEs@^TFk8}mgJ8716hU8ws7GXo)0' - '9^Y8QMUITTg~$a3NBLeA8PWDQRlwWGaUYjOuqobnwE^8&$' - 'AdGd}U`o@}8wwQm3nK+-hU~!!y;ogxfTX&Y{ta;&r' - 'jCA8065vd;j1uE4djtSxn4q5fB!uMKm8_4(sQ#0-^p=*gL)6C5;ip&Haor82L0QZ0Xido04q^N|<$mN*5QeCjtkkO(al^SNPzg}J' - 'XAC3*Pac%RqppcaL#;OaMqyLL(9CK2b?NHlwfhCjiVbsKt5#BF?XAOUlU|sxVc1Txp_;_9{m+1s@c<>YR@`a8wuedHG&Ur@{nnxR' - '_FJ)4{hd;%{q1m)cT}p52wIFT;Mo})*UTI|C>a;IaoVtcw&6&C24-TCC2f7N+>D?A&3I@xI)Y81zEO1%xkY144;L_&ApYvns;;%*!G<75dHCBFh)d{ze' - '*>-B=D(~~DI+^0Cf%(NHx6yL4BYmQjiLb6O5GrZx=fbAWR|4n9s_nWtG<#rZGia~Z6zCvkw1oR4H}fjNInIm2pegt5C7c~9?@B?I' - 'NBB(AV^~ynFiepHwRFP8(2B(CC7k;YS~7a%)U+oMX8bTj3>tyrpjMtcYF3v)vh24#j(RC~Nv5F;&gBz`oVRN26zXRp(8iY=Dt_iToAnR$+OjTd0o;zzN5!#8zrxP_FRgo|iQP' - 'u8^$&0`40b&qVm2K+ITr5>ewf*F(ma(}y=lT`4z7U&(A_>`03xvs3z70*j)HBoh^UM5AfAe70i(Rr_MS0uMXfOlhgyTXR__%o' - 'h2Uku#^Mhmis&FPNaw>LKFUO|MBTU1BJy!Od2;Clwv~N;4f*n3;x&dT3n7ofbgw+DpvxaTZ@)K1s$zjFM~sD{AvW}k?AZW' - 'gk^<;KY29r=veTYg}4~)DWE=;ZHpO=>7Bx+Jp>1!E>3Stbr(Hh@_9G#2D?#fN=+jQych5ZS7HmnOY5jG{Sd#iBQE}oeZ9}T~Boe0g9cHc*~`^Jt;vay5K9GTwz5EDcyQj9Qc%cFO+QIYmm&jtsM>=B*Kzr?p`;8`0(bjG' - 't;+rN7I{n@5QC<~!d@;FB?mPU!=cnPyI0LY_1$q~J8)&+U!!-U6C1-?Ty8uzjI*|TIUGz6(E!d`Sz=E7Q#7(?ou;Wx#g`lLdvQ9o' - 'G%31i_1)*yP=MqOD4&xjpnjN`w7_OA0y{=y4927`Y^H_yepvZCJY549{hNp8E=@DXvYK)O!zoW&FUiZF4L^_L6gwLcI^@NLb@93^' - '25c>&joA2Syba4Lu_4QVQkHH`{' - '1<@3Pq<76^4?7P)?dH+<(=`1Ur`}p4MGONz-_CfM_RV;FP9x&-XM*@R~8' - ')Ry9zJ*gKEpFp_-0I^))N%rZ%D*()>++Gj?=01=Ex~4xv+js;xU$h%Q!yGc7x{-vCv!{8uLkyL^VJT)d{EJ_fPG{=j_OpB)j|qy}' - 'qYI%YIl37-KJojqjhNu^rIDtxzRdg8yDqpr^ijz4?`Iwm|HDv@Qv%q|e2ZB~HVYRt=S+{r@%gG+A%r8k5i~_oZV?uxZtl=$G' - 'f;}em;~S8J{0_hvg@kRShXRQLZ(7MCkW0qim0Nyn_4@O$2YC`*C$Fg(e0Y?+8L-_M22@m569ZFIG(Hs;JJn3Hds^@8B@+E%36l{zicmW!0_?a--)$A5d$R+z6X=mQj7u7jT30KbFTlUwGbv(EB|WS`9Ie{' - 'Jf*9tS%U&<&$V(B<`a+R0SG-Ah3y`bsBW(^BFf|n{JXgbrVryYcN+DJ#5uQ(jq2sljkSLeRIMIt7%45i^*YO*gbXh}aV9&F$RuJl-(JkG;SMXmLGnzGc}fepdtJ*+w^#@15x5;Qs3TG1)Zu' - 'x~ZZ1-AlZuNyHwG_q_-7M~jO#7lCL;vqCM+hpsbkr#J_VSR#qIQO8v!58FcGh0S3}|Hn5}U4MRAzcI}}1||TXP~7`t4G4TBv1#ML;22(DNZ$KpwkB`D@csOK?60t|2_TOcn@0q7' - '8j+)HcCI5;$*rnJBinb)(4wl~BkMK3IQ5kV`R6^traM2cO*uYN$15(60ZtMI{Cb>BiGYn2H%rHxHuCXCU>SOLClU?RgxX9X6Kt^4' - 'xh>WdRaXZEIBm8!w1JA9Gdd!$D+?UyS|JKcB7m^ppzuNa)CGf<{6Xk^iOeL_f>rQ!dc$GVkf}FnxL19aU(2%Ic1qr4^l};^g3=jA' - '>deNiuq^bdLJ;-FDB5_twLEz|d@ZZpqG|Dd7*X+6Erb<__-$48m><)`f{1|B;|79|*8_J%p}frHCKR=@aw(%<%29993DpEc)1(RG' - 'wc8PRB)jxCtJBx)%g4P5UWG9|E1$fn3(tp|H^eu*{uz)(;1ii)9$)Yd7*)C1d4KCcf3N}n4z;>(=@CsDc5>nm5s5qt3B^AX_%+0V' - 'm_fau8pr;ViOSOyCTGwoC81aJtIOnp1B}{W4KOk?Ho35~ac`w@6M>qT7mOO;laK<9>tpddaY>IuPTu|5SAyPBcbkO`o=cl2LGh;`' - 'vBEc3E~Ua-g(WC^j|lKNemz>s=L-J{20&@|qBJa`VZX~BUDhdI33sWtIP8{F+?&7' - '-TVzo%GqK43y>CdhcIQO9KQH000080000X0DLg|PHPVU0O3FY05Sjo0A^uxbZ~5Kb1!XgWMyn~' - 'FJ*IWW^Zg{GB0IwZDwz5WHMiEZDVO>UvOb^b7^j8E^v9x8(nkT#_^rM0>w-^Knr2)w9^MpnM9W5#8XT1*h(`V4(A3WjuL7RKmgD&' - 'qw2qR_r4Ez04XQ!)DJRo*xTLPuiM=_gkkuisQaR)XX|Y9k>u4td)R2OpeX*m)' - 'Yxb?IX;G(5zW$70_`d|K_PgeQgH^*RkIxh{K)e@qx!+X@{(K31vmdXDk_tJd5|nnkElQoh(u;gg2?(a^v#Rfq' - 'qBA&~z0LajoA$+iyQOWgKxGC02C1ev{-*dy;R(2T-?yOK*EazF=H<&<@`AiNzqq}5N8VqYzqy2Y&!0U@pUueC74StaZr=QObxmGg' - 'UcA0KfAbz*Kg`Vhs&4YV?3M9m>mSy_1m`YX;EFaZQVwfpEtBYON{57kuZFh6=>cC7+YIqWqAmGxu+Evt#E>MS#Lfv' - 'W6X*QG+YO}I;Z7XU4gaq^-kFS>^p6Iob)@c8BR#52`ptz+c}a0HC!W-1^bUd0Rh?~d!Wvo&Y9{Kz$ANlPy20|-OYnF_zeud%F2%V' - '2w&|s#r6l9UfFKO0Mg7!bxUg6RQF2N{~fqAek>&(ZaGHJ4-{e7J)E`PlK$Mwxm*C>meZiBYoS2@`sFR&Uk^)ABSTQKptwDmYR' - '`-Y*`>cD9rZip&H1)PE+@Uhw!IjuG{5U4;@K~rWhu&rC*AX}F-pr6rUghH4+w?T+HA{{tm2)gTWp5!8Bs^ygV>S0+GdsLHPVK(^E' - 'K)7PC$Uv3tXcFXfQ|v%1NW||qDNNgSQLKpqi#S>GrjTn0{D$bQ9`Jf%BDN97OQXrqQau1<#p8fn**MIVJp7^8?S' - '@Rg)ZcTIng+d#y~4T20KCdMK=<}A?7l0k@Bgb+AIU|Fz$q85730tmsqh%n3NLS{b`5s+AjG2@LLFOnJK(TPWVA^;h0BDQYvrsy~t' - 'W>S2NP3+r)HO4mx1O2`WQK$pgWQv' - 'bW#iwQS9IXUN|}oiTu|eAntw}x@7U+FGYW^Ql{vi2+uab*blB1lbZVl@GdtDw8sX2aGQr6_p1+QU+vDm%ENeO3k)IyJzWn1-j#gW' - 'ksR%v+!uHEPnNVj`~O6;a)ZU321Brjo|gPzAM4B0#mB*2>GA#f$zl`Jen)wU#Sb$Fpvq$(ND>18n@d@S7N|r`V{9Ie99FBg$U93{h0qh^?v-;kXi(B5ZoGPuQxK%S2g43Qvr?^l~*=J8A0jNdn*##-*U>jjm4b*Y-@d0{|r0@zit_PMiJ0B!mAmbe$z4gTiq^A+Nv(' - '%8H|nht8|PfQYhN$Geo+6+BAZO;R+|u~G(iN0N1i-D%1&Cun8hBYA>dDo&rOLsWtwmI!S~4M-jm1N(nX3oPhi4(t*BNg7Hsh<8;k' - 'xsJ`Crox2?YiRj9NW(fPl(GKlBBl%-n_!V_rGm7An-5;IFy(hdQF?f*A&qc$s)v4&f>8mAl*dKs&jdm$DQOR7I!j&2Cab)teuJ;0' - 'iIK!&#^>-JTh^6=wUWdaffB3ck`~HG4Lg-cSO*~eS6x(*iCa_*vF#w73^&-xFkoA%Vt6SLpuxz|nb#hKg?5{Jx}&UlaXrj&q5d3)ALv`ZkTTaJmVku246IO=yt}L!TH@65q)`)DjJZ^K5#9Z6jN>*v<~rJQ`kL^`s^MxZU@j4Z' - 'yIY&19(MLen-zPDWcc|W(NF~E(TZ09x=tDScS&FEtEG8Lvw2WLW<*bo>4;wnh02#%)=t;gPDpsv6R<%gmEeuvt-hegR(8{gI`k)Re2E{M@' - 'GCf|BFdUr|Lt2uq+wiu{EOpHd(^1#tMwm4H(2p{K0yLH>xAHh^1rkub4)WT9CJTyM-lc' - '-gqUlhnfH^#USdDR!ct>^&Z2P>pc$i=?*sHXhMO1mK=NM3_MPL=YU0<' - 'odKptV>i>W>PweZR5|@T_jwp{%ifOpHwFco%gEMw*%TWMXGa}V)w>3YWvUto!uG|UBA-sO{nPB)-gjgfndmasH51n?1z#XW$)OqG' - 'HBvLv8dtd_6uS{yO%TlwLC%e$;)N9!hh`{+p5-~wP3~=0mUx}!OGK`)%!758p7-_c9BU^>)Z#QYG80)oQG0ro2lrW*^?e(uw~%W|' - 'VH#&Q2&!+a=vIQ>lc%w!T0tSfvxAlpfj;&gvkyBKc)!ENW}lXu@JgoBS*?t63iZ;nO330=@54Hsx8%z&*IyyYUo2a451ZG;giL96fcF!O}3(Oh68;f58=j%@#7=z>^>(e`UMITEo`leD9!Aq+kDQF?1tS28&2KAzED' - '1*usF`50bVQ%t65=4sAk4^@Qb6V7-}?4v*7Y-ez;BYqDk;tW|ft-exUcz0>#UjQwdx' - '`z&YIB6TyJ#Y$OZssusZ_F}ffZdMQiF|XVvK_RZESx388kG5d?oo7mJ)4B&{C82_w&HKg*v7Pbvwwi40gqW51@6G9a!L^' - '7z~zMg^xkJ;Wy5t-H(u_fek^!Hc}=6n;iy9@FF$Ul8*fE@4dz#UP_x7d9BX}V`I%jx;VqL9;a8S??a0fLK!wdgX>SY>n@%<2d5f=' - 'ybOVD77Vam!R{$7WP2K)1=S6pK' - 'qiy&q1A=+b*X(vaH7kV&hT$=c19pL&Jvqi8jBLabP<1lyxoc9f%X2wUPD{leZA@@2QW^6&8)z(kR*d?=Z@gv*W!0MJYLJ&*2^f#+' - '$*CjVblBFu^V%w$EKq+|QiQP(l{G6zB&oBK5kvn0h' - '!vH}y;v9c$zcYJ@aq!2ZNdjxjv9Ev@3yYnJ_LtrsIXh`q9isTeQ%CmQnv4TRS5zW%?vfj6JDUr3X7#15p!B}aaQ|ef2fOBkGveSI' - 'U$$*%W;v$XmH5c18y0!S9A*Hwt@%Q{fYTp?8{%wt`aZ109)4P#JF*SX_fPhCmIaeOShb#JzvG+2;?6B5T4`xiVOVn' - 'J^s1HKR)ZY~^zd*XMj~dlceGvaK8D%rj5TvF&M3O0B)FYFy$FjBugabo`;#nKv{Kf6(>v' - ';lw`9z-Y)Dh;Om5xbc2cGKghNtVZDR^E#e-9*t0*9CJQ)(*c{Fk-36Fd}0s206J)gvi6z!_&wcZkYeYgX=})%`-2K%5WR>_tMCi^' - 'C&jNY46m{#pnx4woe5M%dstaGb`lEwlw5zx?5n_Zz!Qy}sk+Pzn@)MI?h)(el_&E(wFIBSCL@15`wvh{0|XQR000O8001EXzawac' - 'fg}I`ou2>zHUIzsW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA?PY-MA0Y+-U=V{dJ6Y-M;ZaCz-LYm?i?k>B|%5Txo3' - 'P#{KYCC(KuOl~d7i7K|NljN%;3=6`N;4UH(paD?QdcFVsx_e$d4}hecy7=lYRgssN>7JgRo}Qkbo}M_4zuXpmv}?BI>aZ=^hpT3{' - '-bq#)Hho#u51V>>8(p>4`nt@b-R{ZrFQezb{3eUKzCEn^Lt93>vTv(Z' - 'mtnILq#S!ytorCf)grO&uIQoZ=Y0g^R#!wV;JhiisBYlfzO2_}y}G3i0Qg}KxXWTU`J!rW4p$N2dRJVR|4xPbs;+@Y-Qi01qLN$_' - 'blyhmYO^U@=u1^>BVbG4O($_2PbQld_>kwD0}`F*QMKDQZ4X4(jp%nbnaFQdqyB0-^;cU84C|sVR@Z)(rTUqaRkD{Pz{pIDe{K+4mJ^9n~7k|j2%lcOL>8fh_' - 'sxPNz+<^aA-C*&wYNq@3)kmy`|J4;iF1D5Wwd<~Nc+}7Ou-o4vM7>v^fYg=j_i!7sZZe7Be;{H{Kt!&gkD30^Y!ACy|9lFZ)<3=k' - 'Hi;iy+2M+u_{#EC*??)`v1*!Kn8|4dM%xjfO_sGnd5Sr;5&}B^<{SduqfP^{yN7U+fvuLwzKy~?r)oAIyD379FS6$y+' - 'x5e%_Aw|{ZtDB;#yIiz;1VC^oFLzhv8g+QymERpe`IYBGQrr6mxVSzFtVFv4P5bd^ZB(B9ZC)M6+ZAQLDGQu#-3%uEv|bl&Tin`J' - 'qv+?+pGn_$H^sh;l5G~%8ZMm@s=n{l1e-;uPZuzgNrf$)hEI@SCGA>qYUHv' - 'l8m&7eV@`y^L6oralZP`?7G2w-Zf|BVY' - '7~%ZIw@E8cIO%e-Bfy?326rGR~s-4gT{7{O1Y&^DX}KluDjb(NijW`Xs(jC;97Vuby9i_582Tp5~XYUS0mh' - '#z^Gc(Z3`?_AtkpQ=$gZE1*I0z6wtB&qA{LMsSpuh2|zStg0' - 'fB8wHhrc5s&@<`@8f6g~$V9v_t8t1kn=vZ3)pafO2ds!yD@^TmX~j+Q)4@j=E9EZl@M(jaWht2M~qZ5i7|ljmR~}Z$T6g6zXBJC$oskI@HG;%@lUMyDnjZ;S05Z' - '9FX6T41+G9M(@MoE7ZpwO?81go#wP!=lQ*oZApqK7wT{h1_`P|`AsOkN2K?Z?hnz$WZV!Y1)8H$vMVs%OOMsiRI`QAnri|ui#|JD' - '`P$e#Eh&ab+VeOCDD%iBA^gv;^edjF*_AFe?vgpy0' - '!8H3*KrWWn60x7+UO+bZ+vpK(5A;_=`CCbjU-CO;X&sYESAW1BwMUPZkO80+rZ*sO4*kPs^Y98Cl$~@n?2G#3xy^TrA&ZKNtodr~' - 'Btsyg?3|hV6Iyh~$r#czo{9PCOR!}=>b>)3TlCt+Ol4va<)@kJXP(f;xj(>&+Mu#' - '0aqYkuf;#awmjeruIfW+t1P+Wa<*6GuqwKDP^uaf%+B%a!lu+{C*}Pz`#uj|7Y!moR?tMI@7Q}7D-^)GslNw4CrE-L>snVpgWida' - 'PzyNyI%Ixgkf%!vi`nTGdpv(ky#6c<>@u}r^=3?hYKqWIhLJH8mRp#X@srqwC+YHl?K(ovRHZKqKx8VAps6ykI%Lk!rs+gFDf>gq' - '^)N0G9ZiOWxn5||n-lY!qJR7uQ^O3vY_diD=IZa|st?ztPM%Ux^i@=|(B3mj-s9K&i=N;P)G3uvh`n#nkQ' - '$=~*gDov3jsX#?wrZGyFH5;Tp&|W>G4aPieQYC08e_p^BWL*BZZ!QRA$xSPV#u39Ue$i;XD;fZ_D>pbLetqnauF~' - '%{uVJ6Oi_OscUotjuCEoO&sag4x>obt)uU&z2U4wxq;{uNJmq$TJ`fc?ZNYME8=jta2YVWM_i2)x#3H3fC3$&;jqB?h^@r-MTi_;b65NvN9b6YS7MT7thZ_AIXa^EwBX#?)@{FY%LXnklUtR@WhAS3*=^~%%TkzTcq' - 'Tjp|!s#QFUa>_e6Q`@YDN@yt@w%a5zpjq_zH{#d=@fUT#sF*NYQz=guMQT|BBMx{nFqA4~E0z>qW7}YZ#`3lzAu@H|t;|o)_|`0N' - 'w*MLJ=#Opa8FuChDCxkx)XEo6V^iNI#tvro>AR;WJqcmFSrDN!q;r`agJ8D$5ERq5rob`IYYg_=R^P+inI;)Vr`OQ^cQr&' - 'Hh0Mm(V);vVvU+9&W%MufW!aP$7kphGS3cD%elElysj7tqFM=p4S*95S6<dJiqYM^P!i)CC5' - 'cA%_YL~3grj2hIo&jssgQrra()RaE0`FZ%m3wyis`LEJogv2L-OJDfRC}InE7|;^NPk^t8>re|O;JOqD)E;jU$IIwJ6u{)|AY&ss' - '$BFQFwXy4?U|h7399`9Xq$rJ?C>LQG*zMk1F-5|E&7y{cm@5_b=sZW#`3|H3ItKNyJ_efms$*eM?|YDB8^4%G7tD?l6J}v1$U`190qcH~{D?uOTW8Ms*TAf==qWB9X|2ZMUGs+I;Z#u6eAx' - '#*&FB&pGJ+@?+l?@Uh&kyZIu%$~Ohx8;F-`O`@$z@~85ab}AX$wVbf|8KJ@34n3p)V6f|%m{f1j#M1l|QSrH0F;?=T068i$a;=&%' - 'H;evEs!@bQxsy-}tgk&`H?R!#V6%#9u-`2@(Ms&gg{U)CH4Y{Pt{9p$!' - 'z7lj0@`s2#LT%7jAP->F6q_DYZn58QZ}CQgxM=gQ@>CC6hf)*4c(rX-xMfq6P3eQ;nmDBcl~Bgwbgh;EusX$7-|X@Q16+Fi)ovqQ' - '2?+4vsEXY2&mGaw)iRe3F;ef!axXTb&{mhA)g~~7x1ce~Y5wx*7dgo!D(0AsL)DDw17ZG`F?s^HQd)>Fv{ypWyzPl~<1NwIcvUAy' - 'IUuk>4;S$iLxq2j|KR^Gm0l2|PqwOH#{wH!a^Y50V7p7gO^!6=pDLifcv34IYcil+Y)Nb%(10!^E1sq|UIv&cLkX{_IAv&G-}`D)' - 'O?OX*m6;G*r|vNmRSk;Lj6&g+1a+Dq&)U;#Q$xGT5S`l5YmJ1BFEgW|M59aX?i5Esp0x(8Ep!XW2iC@9Top@_@9rml2jnM86su;=y^p0FY-%2Fxtt4b5ug+bp!ax_mz*ymi-&R{tRCOvALzanO_n8yyW(ZnH+)IAgX)TkI6Fl~y' - 'FS+8N3R2WQSq!X0J+Wjc>CDVe<|4=jD>*+Y0)YK?kNsH@Z5R0mI#`tV%@n=jn-qH)cN9FR`N@VjcwQaWc~e6;M#aTB=CNidX!?lF' - '55+IjhnYA*^3IZ+ncTE}xlW|Gr0Ko!{_5IJotdSo)!qwdpjUT6IF_CYmNU?erS7qhPGDP~CT2!=vFPcEAPfOO!5{t{UW;QqIcA=?3C0IHde|3|7Qia6l8CaRgL`U~pLR=@{T?;Tar4?aferz!vJIJ)2X&vly(c=@3agPc1)D1sN$v@jWgBeTUjxzY!t{1&S?4iRWj)wap!zm9&P' - 'nqfjL6$Z09WGLujnQAo>KA+}iWHMkxJBnx+t5Px1VYCdRez--qL*X->EfI8DtVg' - 'FZ1h>L&mO2!Vs!Uem=$y2aYq!0k^i*^k?oX$()>?p2cIS91}xhFORl8fV&7e$L)IQ;7Eu~IyqHO84h0k)5k=4X#J}EJe)(IW=n!g' - 'Qfk)pNM{6D9vugz{<(Qaih9VrlfE5GpV|cZ=7ZG;x2u>Nk=s_r#FJBu;ybLJI;|kqKYY)w6z`c}|JN4^5kH;yIzK&fBV$PItKlQ(' - '@I0cGWsfe~b(wsX+E-Lp1uQAmsz7UA1!sB_W1LZBYs#Dwaq?%->)|e2XvcKFLw^#`iBO}ht{P%ggB^>#O6Z0EHXbBE2Tl9vbrb8d' - '`FOM)71qUt;BFwJ$}w5+h&!pL>16nqfy=jL-%C#zk~fQDMdM6ng#kimGB*rRJHu-y_eZ70Z-EO0{U<%vZ_o&I%Yvfg<4gq!TY5n1' - 'F~n^vu$1mC#=nmzt^lDTVonrpg=LnMWsHK%^fFDoNE1RS{W%tEJpA}u-XWE20Jhf7J)RBCcB~YHw~-C(eq9' - 'dpSH`!vmrRyrvcWo~B2Pr5r&|x0porlb7hBb(bA`qaQ0h(Uh{lF2Z1BRp~(=L-5%cg7Ui@_E1vrDH;v-boCgz#~wQG^uq*JoiL`n' - '<|m9PcHzu%C6ND-vBfj(^M`iV)Sot_`e^D?2TvVA72EAqv3i%AKA=2;p^J+dk+_@$UErJe&v!DCD!RCj%}EMTT-4Vk1wzQX*rKxQ47RxxEW7izUoqIGESKagyFkYH~' - 'pL0_U8GyLXGT|`lDB(ySta~5q(#C1@' - 'Sl9nf<5Ue&u5Od?*bw9Y4g1^^cBdm9^SRsiJ5ncUkbe~}yRUIKgy)Q=5wXsA{*Xcp8BeIp=Xsm7`aSe5eyxC{@cS' - '55)|U!Br<*V78g5HImDmz`a;Lp`r)D!_bMBZNNormOxuqg$Bny*^zVOLT&=sDk7qMECJ}emBvKcvttV`9!fRF-(Gwkr=d1v_5=oR' - 'NsU49Z$7+UCBz?YG0)Dx@EbN7_{yb*KBXa+z3i9T-n{Z' - 'xpB)Z$kz#wHdss$wGkb!R*;hOqoQ>%H#BlH0+7@H)ku%|I<|Kyr+cZ^5AvXT+vX6k5)k)' - 'Lt8_o4>BcSNZqdLFhl<=LQ~g@3E_FrvuOC2*$3j(6O^Tx2mpTyQqD*ZDoHJQu0p)>-l2k(_oh^$UJ&U!*5-Zmz7rp0tM_hFLQ#=;' - 'TDGHsgaX4UH7QwGICRi4y6yd@B$}~CQYQ^_{fq8gi(e;Qww9zzH2N2z5x-7CBimQINlAcnbDEPpV-$PTC^V7)2Ms5A#wg}TB6J5;' - 'ZfiMhY4p43k=FdYIVy3_EfgY^8O@mnC!Amqyj{_jD;pLnJ4>cHNo}d?85{i3){Y+vrF-@n3x`RD!' - '`F)u@O0yFX*!(<#py;G#7yMGzgeWBULjXLro<~SThI`>f9e5o=Uy{01InLFQB<|tNg9#+^*N5iN@kf+|+5R%33?#CI{6AGq&oEIc' - '_`rh*QS?SM2Ige+_HFg{EtqSB>+Rc@3r7o8m_2glf4o>O-@cs!gxUun7Im>pV8aEXIP^E>a&rqc=CD+T=f^Clf$)o<9XyYMEqiWEy@@R-kq;LWm|4zCG09?Z2x|p>xvu05kSy7J~UopQDYX*0DG8*aS_(JAl5&e6wiWl94D@G>y$k8}4W2pp9c' - 'xy2cR&H`5@;QJi@pZPf|wMG+|PEE68X*33^J{^H1JwO+xaYPKbl`csb7f#_3#6lm;6Cbtl?uBss4nyY@j=!Tbf0Q{k9D7N}Ei^X<' - '1e=6+&mJf#(f_e;@tMT_R?9ch#+Gd}Q`}qQ_J!(~87;7QQ;l#uG&qEPFu4A!>(osA0)ipwCBp^u|4p;jB$IjNJpPuf(BZdVxoLU%' - 'Bz5c_`~F76Ud|ByIhBx;?q}e(K9CW3ze-0l!7jXeTI4KpQf#e^;?XIaajzWz!0s@qMG&wKt%6A?P+gpB%F>cyX8Kk~^w^mWC;' - 's=gdmn1Sm>zH0O@9Qk(L+__}LjW-_l51rKKZ64vvk#t7duye6#-87ERPa?!+ey;u)RP@drD_2{)cdjh6#Ua_`K|PxT!oqRz$rPq%' - 'A!7yz456#*w2HQMol$G{Y;Y4;1}8;WHo5_pb)kNDL(38?NKuiFW+xT~oSXHADQcc92eEniB88b2%-u+GfdfMdeJ~T;KZ!Zv4hM^|' - '^C69RsH^W#k5QbVIIa+%LV0;*Wxb!})lF~G&=M4WRCRO#@`GDz6$P2jj^#rQMwgP=FtB8y%4Q8@ytVX5^r;=tjY{0%)KK)4wnUxVzaPWP7AuO>m)9-LG09i' - '8pg@^7g+jv?lBGKCnXC4qDF9GM8dJb-Gb&1OQCl10-PSR(CO`FVV76+UWN@yKbXDoKVqb-^FvhGI>8@jCFld_>M!Kw*JHOq%e^W++}vpHCO_Ms@`Cxc8aXCDGlUeI4i}1>8X445={SK%pcLZclp)wY6$yK`!e*i76W&@U1Z@a-?sO_' - 'y2s$$w2%xsa>-j;6!50DI|lw}xZW^bpUinJVR(2acrRnrx`2t?9Qyr%BF;)Oz*k)R)g&U^j@bX!#V@1Tswy~)@&ikn57`#omq' - '_fBK`#ytc^CHxc^{GYSvjvBqUuiK`yKeNNnhteC%2rn^SGJ@{Bw8k_vxd^@~b3z1DwLTQvL@=6bwP3aX7n7fin9!Ntn-Z4iwrFpk' - 'R&9wv=C?_Tl&{s#VEh@Hfao|x^HA4JqhCg`IJ8_}#i0PSVKB*m4^T%%61Q3Fp>b5zSZhu-w>x4$KIB?Ou5QZJJNYwN#Kh8AT%G2P' - 'i!Z#LrEjilq^5=Gx9(a-=k6)zRzTuP+4h-Yr&*$GS@6v}bl' - 'h=v7~vp8K`EY$-<=}}B!URk>dnMB{{sEmcRq{oR3ru961MEv&|pEQT927s-F0iZ@(E5l)XaK' - '23DcDBp|Tz%neNe*8{mqgiqf->Jt*WhP?;wj^is-c}^k&#g&l*8le+pUM$UXbV0zb;wQv*5xqRbb)?tkItabkqaYz7|Dch8>5En{#?06AWS9rEIP?E>+MRNFVJs@y-Pm}tyJPB;9BH-60cF9`!u{}DQTA12Pxbr3A=|iNxT6ZK&ZY}a3o0>' - 'MG|>m0bhC^5d?Pec{tZKP@Fm%}n^wn)|y|FByu3(q4t)@7~VP1|MJ=&M`%M0h?~LnM-oWZ#d)(JXvhI7*&p%D' - '0z1scM_^Eo$M>Th3(BW|L?j!+^zpW5K0XV3Ux{Pm2)#U!qk<(Ed$Xnc%7Q{+jB6TEs2Bemh@Ttn4h2C&{Y{4~L%`i<}jYxl{lm7-#O9KQH000080000X0MXh5Sb!1$0GCk!04M+e' - '0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiEb73xUdF?#?kK4wvzw58q@&ONIq9aar3%m$nP`kMT1`?;R-2$ziK=8!X' - 'i7`d0Bz1T4<@&e3nc43pDe2B`-s?jRB$CM8*_qk-+L_r4g5bw}*%iBzU$p%1dtR;hMOGF!)s|NsyUofyXZ7{ZeATrqt8!M>ZCmoT' - 'WiYbNi|VG0gCLkp)=j--X}aEb`-Z0}E4I73>0nA#cUf1|RXdr;XL;6Tt1@d_-l|7!*8nI1WL1|XEQeF#?0Tgjy_eXDK^6?=gB2sz' - '`)bwIb*bO4veky?>8`1Fyy*^NRMEkAdsNYug<7OL>;NnE=2dmjBvkwD?!dB^RXZ{Cbx~W0zWGT!ZCAza5Vt#EKvu?i(RLY7pb^3c' - 'l5;U{x9hshU!>cj)ie_>X;vLlu?4-cZI-T!5@<-(=4nE18qV_rJV$1xMU?}ncFHyzF~JA5%bM0A_Kz9hdR2AJVaghwGxMZTz$AoKfHSV!xc+Vh~jPbk#|j2wc&)p' - 'KOnk`aeNXz<;H><4W>}wXpK{SabDFA-+>W4Rfdqv>?a`o;#d;;XzB>nT7w;x`P+jhSV-8CW!gt(FJ>b4LJ6C8kzs_N!GZ(JVYBZXV-_0x3s<89(>ipZ1R+qU`7|P`^%-+@LP~T6vhPjC$vAM=>%#q' - 'pTWvOS#({=1IgyKp7Wc84O;;LnuyE_;QwFHGhQ`K-GuAl&sDqMp}pd{f;49j^4p_a1%#&LV_41Rzw4ou^If+Y8UMTaH@)%Kb$)>1' - '^!}UqQY^ZIA!r-L;$I1%^oqFG6Kw>tnA5bS66fHT_p>01LGL%&j)w$dDnW{+H{c>r?-RBVU)dTglKfg!gxZ3D&X;k{Nok^(9)!Vv' - 'S3!h(Acim1mO$X3;gY2xc6`S!%~n{;2Eo#yPKu%w5joHmGo{6&qUKwYqdPcvTtshkjA~fSrisqH`54nLW`^UE3P()@n#|SUBQOlgbi|ZhWf3Axv)a*Ck@ZYfB#9b}9G_zPTb!LjS%}9qMqq7L{r%VzX;cSN_?rTYPR|{AX#snaif?mnT' - 'NLhQm0X+fgZGz8iTPg*Q7qjJQ8u|z-6u^DvAcx>%Rfs3lbB;1PWn_Bk`>;S~TQH`%FaiQpyxPEf~QNKC>NK_V9A5)`$@0-|K)d1TFZ)tK>to=g~yOh!}JJ`2JNm5F|$0mA|SOi~(m~H-I=q;2G=g>WjR9^9owqtYmNZ+dmzM' - '>ym5QoA>s|HePe%NWiur71)K<(k-vgN#v>pfa(jc5=eGPJH6ev9lHjK' - 'Bzcmu$X?d018WppbY?FwGPIrxnY&zK*l;=cZYJIhA!qqK=PmwQ58=DXAaI0;;eu~>-GR{pO!*H``I~M7G1Coj>%qt4F%FzH`h+Uu' - '4`DO%n>LJ2sJsg80TJ?n9th1iPFMi!y#}XMueEm}Qbc$tYz}apfc##cC6$2Cdm|X2cUIh>Lf_tIj6oB2@rmGOOIu!HzAdSv5X4dv' - '5mLD=98l_9gR}!OZ*fJt>unI1W{x&^af5Ja6Sd5PK^61*T&08GfYMo>Mu' - '+X@fu3CKr&zoZbAzG8%`s62;dUhra5tR>&(rGh;j!Y+}YcAVlfT6%fIZ?S3Bo}RVF!S`5*&Pa~JL+7X&G^Sw0Ja`*E6nGfm1~prG' - 'c3GZ6sAFv8l$ls%6nznhe0#nHUk87ntRt?+x>^EwppQpPyj1cL$jyU+`sn&ndH8xF%YYjqT1uD-pv-(d3>hIZtMcrAAr3&6EvGX8' - 'F0WDvLa(o3sB2+G>;@3~QW;wcrbBiN$uyeaeDU{XqcFr38PpvO9q' - '7rQUE`)#UnGw@p>=#B$w6seBga{xr+3=p!M3LlK1LLa`-u(kWsiib(*q?V0x{iseaoMnSwf|v}udo~n&7AeUTqdzsm_eVBU?T2|Hkn0(PPgZ52aN^Tz}3g++6tL;hxg4vhroe1xVJRNB^qdJ(6!De0H(<8J2=JSq3;;C#ln~U7_C)l3N1zpT4KJ8d^2-0)+GGu' - 'eFKLFqdYcifK*=*djRCvnBRAZmJ6?so24b5VhHl24qQh5K?;Q%ED|7Mj)o(Vx9zbf!wfkZc&`Hp10QI!9s@Vzh7j_GTTx?VBo91I' - 'Q}Dk1F=a_}1UIaw_T1UaU(z~!OBQVkw{?wY#eg{hc{TzI|fM6Qb}SG>z`d6Vao' - 'h)KmYOk2v`7bUdT&V(7wwu7#9!Am#=^L4f_yF^3@t9lEkWt9W`GRZNCu&CvnN{nHWNePHaiz!QIsKu>ry%ZUlNNdGYRu$K3fb~U#' - 'obMng$#5A=HOURqwLo_vw2U65T$Ixdg2GSIV^&lSUZ;3)bqj`$DNfkwK?B~#=s(yyV1_`NQK8mUJKLB6k=dQ9*21W^W-Aa2v6#%i@aJDNdWX$WL-t4al$85C%1s9^b%Yc{p1w6KZ(Sf7cQ`ZL*I^5!^u-3vVve2ljH7km&;hah@4&R!9JZSB@r^(t2Rn!MeSXZVbj#egz<+Cf-|fWkw1V4$3p-@;U`f}YYovWX|@1D1nzO*j4-=5' - 'f@~fs7RZ`}Y>r96;P`cbgD=?z-1&ot!z0siB@TLk|2>L<26aE3?1sDAA~Jp6mk->OfEwkeoIOR?1D}%aM~0Xc2Gji_c>x~bw#a$z' - '8Jv^r%Oa|LC9=i1qwWUj?71>TZv5=A-io82S}<@3Mtjs0_g2+Fpcf3(8N!Dij*rv6P^iL!M{&QdAme|pJIu<#TRAuKXgV71rIHt}' - 'E{&V_<%v~gWb%YHt+M;k6`xbG=<)k=3Kl*v$JmYmrnug~)$K`so^k-@2~Lx47|4Ew)U9rc!3*3B-2)_?xZd)@6jX%Vcm@5_;#TCx' - '(ZPyFs&ao;%J8<}ccI&PWMLo9*oEjg3=II_(*lhc;AkFBS+L2r+oHM&qDWV#o?lK9GaJ5Z?5RUrxJH{0KKMH+MEh~hC|{GhfR4' - ')X?z*4qr`fSrBSxkEuRR<^$Y=CM-B%ODqk?gt35)>hf>p`_+|i9Xv?>7x^Po-{G9SY6{9GJqClCiHX_7R)%PDzN-+EG2tmI$h!Tv' - '&r?yBw{ya>(Y%)zjwu|JR$mN(tqm@M<%ors8Vys=Z6s+&bk@=}9g?4;wSLvlQATQl2fDXCPk6!$9`So+w!O|XX0^xrPH@oRS;@jM' - '0D;h`895vZcvY+$eMSe-7wjH#c(~|E+B37r=VX$PCXwAl@)v1p>NCyoYPg%}oLwK`hF#VZ^`bL;An0t0*Kj2Mt8PxWa!_-oc&BRF' - 'r^EK&Fd@F-T^Q(x!5Lfb(KCKl1MrJL%Wtq;PKP6jBvj1}P8P@%S@Cq(iu0kBe$m%KRFDx3w)u25ok&D$Lq9AS$fJ9Au)Hifh4~Kn' - 'H88}j_l&i}sV-cnCStc!I&g((YM@rDvjx)C+VcQ=!aue`5P%DAimWU?^;7aKyDLB>_Z?=8i$<5%be+V;v8OEtPrD(@$g|k3pIZni' - 'TZuw;U2@mt6V9%@+w5ugWltYq>VqYcb1$Fp*z1EE%uD*sO1RR(Qle!q)fsm+B{s;f_Qmkr8({PG%H&WorY>>HVpBGOc~o)M_)=uO9_$fanGh&cWHY>VW+n0Y!;+ElM?Lp)a{S|xB{Dw18;i9!1F' - '`o{@%(!Y|7Y(sD4S5=WpDl~QX&L~?rw@hloBBl0FCP4Qs3&?' - 'L#tVk^{}!1>aU|pd2;4cZh!nM-~2e^)(5}u8q9|J$%)`1U|%^W5^{d2X^|9nHJ0P;Dgy_t=w#=LOBDW52UTK(I0S#Tp;*~HE~%u3' - 'IgQZo=vW2D`TG-AdPFZ8=~mS*=+y0~fqgCb|MXD(b34O7zbpI+HDb~C+pp^U?=)GfVuWmG#>~AsP7U^1tNe|q7#tO9)nzGX9Bs??' - 'xTCApVs0OmW3|YBvy=xtLd${AiM1|y@^bQncikE{NaO@CA?bp9BzUv2Hmf6g<^dX2FsrhYJh$BWlv`D2-lz(G37&bO>Q}r>_3tZ9' - 'PruUi>`O(x`|t{nrg~0<_5&maX)6REV_`e`M@0*wWUMmc6=|j{VhXjy1bh|' - '+Mr3ZIBu2)Y?ouQW)XIXV~hcz;!RaIe33Ob7x-ZLWr}~}Ed+MC&n_Ezw@37!#ye8?ds5`_KTt~p1QY-O00;m803iV1rBn*P1polf' - '4gdf-0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUu|};obo`Yywr7bWim1U&I%w|1JnefZ!cLRf|;+{ffsyMs`iR17<+By{(L!rc`zDE;l=aih`K#WNx}utJRtt' - 'lW&Esa;~+~>x!?x&t|iNmyoY^mgfz#T0F;0Yqq;Y&6zCR?*!&oljG$K@Pkp_a12fY1G`o8LSXa;17kIiHh2hx_wo3iFF3L|1v^xm7u<>ljO>_X12I)Y1HJ+`V-oE1RPj' - 'tK&gB1*bd78qQ?Qo`qRlq)^yh%NHn7){5ERehq$YH0Q-G_t46PECi}JPJc?(My^M64zrL#qY8cr#%j!1)UfBsIa&*2@9};))aTYI' - 'hR?Uxpw-r3S$dN|?hPd=P>>OZ9gb|peGnv*`TE?bE&Ri^0$~v7TedTl1|}pgGPt(D*}6i1cj59e8t8jYLo;6?ugn%13xX`9R=h`0j;oD!1*Eh!jlh;|HbOAw?5p^r=0pyu_umk$vI9Gy91{X;)1ufN->#oW2+>E' - '4F>))G5Hz|8VBDei3u=FVWm`^kiQ418#xe&Z>23Tj+v7r!Z%!xX&ab6yq$()Z5~z{k%xK-xf7-P9ut02>ZP~5C8kM@ksEncQ%XfF$wIAI28yKHdOH&qI!(hYJWc*%K2r' - 'Th-hTY$b7M^~my*&Y@$U)nObyvf6MTQs2qAni{fxFrf#}+$0}PUa21|{kG5>%=b`)ML!UMqoHswqz`vn;c6xD$04W$KAgzfnqj9F' - 'G?u)LvSt=riC`2D0VT)mxmhdigr?EmkLs3iwr_sWA^Edg0GyT}_CY)f-Mga;IX*O%B+F4p(3-Kf<+6xJog>d!kMVZDFh|At`K{_q' - 'v>cVG2cK~OpTyknWP0l;&GatUO4-=0Fj{H&vlNUmxMF$eH' - '*85ad`1r!H5$|`$rC+V++%gR}qHwzyefs@8?Gas;f!34O>gj+okqZ>Fz6AF!K-&D>dDhccLNa=!^jki-7NxTvEc=@dH~Oc!=>)#L' - 'JTonKM9f$Tw*-gPNZVdm10Sz%@2;WZ4432NQRen@c#&pzpbuwy%+UZ#eo0V4C9`=K|N*Y9rc{lkR3)jTy&0Jli<>CZp_cC1l(i#vO~Is=!Jt' - 'qRy7{_t9i3SNIl|wJn6wH' - '``BcsIL&&(xg0_B=0B#mFI$R(zjc4^JoFZKx|?i;zMZhRlN{oo6;^nt9Kj+pZ7pmqXqzU78{91cy1T!)TxRtCXsOi*yF?|x27@^6' - 'h=fc`TSLyr(N&ZzXa50EO9KQH000080000X03%f4O4tJc0P+a{06G8w0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiF' - 'Z*py6Y-xIBUt@1?a%5?4VRU74E^v8`Rl#oKHW0n*D+VeG$SAFD;=>jOyg-`vngVT66fgpYwnjD)iBw4{i5JCwdxxY%OOCg#4}wH;' - '-n@ByGtTq;S9y@tvNobcP};Pjk)MT=T0yN12&dcCN7!p^Dyf7+TjqJ5Wwp^Qa9;PWH^@22w$sLeP)fTHZHF`N)Jb(1_kTZi_=hlA' - 'HhinT?M@&psBTvYB{c=ZEM|Bx7?tWRgZj6shv>HdenD|Lv)G~buAUHsuAuke*ctZcgBd*FD7P%&B(mSXB#;q' - '=B28HUt|NvMV4h1)=@j|G~bE+^G++|L1kO)cD&P82AS4l5G+5X^y>`hM?&9w;-=q|vJkH*6!5^{ekq#nC>Tjz!965AkP4oj{(O3h' - '_vCR@%En+XrXT5@3Y?NEd$p6QDk|A-mbZ(5Vvw#{C5ojd!Vg9qMa%8}*viTk`>$m>Qy9e|czol#6Za}{MWq!XO*GsZW-_z16;6_9' - 'qze2EjkIn{uaEHn(Ona;qiUA7EQ^v(agwgTVS^+p-3id`w%U;Q3AhS5$U2QC&k8l#U%W|s*3*)q8w|uLlcY~?u00lKg3J?)x`l5#' - 'w>|GZf~;pK*jSvD7ve}DxsQ?WY+Q!3)RJhnKH@R-s(A9F;>h&UmTF!1eA47pXdF++Vxe)TbVI7&4T!8Za4-B`M?T@36PRMgv^*CL_Wpgbu(0!Aam8_i7t3w$yKGETw!%JR#cN3!z6`JoJ}gQ)7gMel14JJt!DYhp' - '!JEbP22(1-rJV6+?ir0NRXrBK' - '(oCwyyGg-(qUW`CzPO5qG44vUhC))uQcjT68_97|uyi4#wf@BqO9KQH000080000X0Q3A9$^Hib0PP(B04o3h0A^ux' - 'bZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiGb#!obbS`jtrC3{U+cp$__pc!IB(I9lcEcVFXjm`lP#{i%|mni4FElNfNMS`-9w;d{(;Hc&m&HIY97dfw+ihf5e&4IK_qbOP|_ARdw' - '#`|9MEyWm>b;Cg{VT=nRO3pe%lk*A$sg;!Ma!dGL(3b2fs)-6BNM4byqn#yiO9|SSv??@V32cJ%%8}i*yl-|#y(^AQ$?ojdD|TEg' - '%x~7$%@L6fvBoY5-sT5mWJi0U1+JEGaM}HH|5g3x)bv$pD2Ge?u~N50&(N5FK%dkbD(jmuG*BDltswl(?smOV>I-zXG-iRJD' - '-H)+4fj-7|V6!fk-qowB*snRGt3Wn}h6^n*LlvA#9wv49v~+~BJneE~GbS05Kvu0b)UjCK4B}GKG?W#wU068p|vnQ5mo4AMdcemvk-UY@G274UI2eBdSl8KaI>#Tdc%9$6T_pA)rZblMn1EsQ47%R-QNw3y<6)Yta4u3dgo{vw_OR!t-s-r@NCi^nk' - '979s?C>B8--|TA*q5{s#IF4f>U5Ytr4%F}HMYbNmV#EuxwIfGcR`x}$w)xu7MCnzz@lV9c-reY}39OSMA;h}Fe|7HD_0a~8xF' - '^m~e~$~#FfJuo0M%_J{Fr0Iao*~U1#Xn1w30gPKg4g6N#N$ml(QEM2IE-P-?TPpDu@s+O=jss*S1I2^CQJ;Gbc' - 'c@L;zH7R>=#CkI**iI?r-%&zA6wmId2>T-31t~d6KXZGO7aJQ;U)u7FnCXRX$f7{Tya*FC>0*`L(z|{Wa7<-~p)71wWTqCN<6+t2' - 'QI^f#y$3j<-zoh5yU`*HLfSf+b`DOMgKlrQ&6?(qyuyvtO))T9<#%NF7Yi@`0pV&isR+YC^-Oa>mkUfnNgQtm>Wxl6jTdwv!hF}K' - '&{+?0p36;7H&iO)LRYKoXFm6IUdrQY%OXZt6bcW&tcz!^GIKYV%vZd5zD@m2WwW<${C@TRuV`-Ms_17yE=r#v#vGPZyJmoSHeZ9hz6KKw*OJDcn{`y(@^0`N}jT%dT=Fyi(y{FVTWk*9%mx7IV=tF3;5Pjuqw8A{`1I$NGr@' - 'o$sbUUrnL0bM+nm(d6ho=L>ew$rKjj)1~(F8WVqHiG(cH=wc~U{URBhpg07U-EcF`V=fUylZ!;@8q+vIOxYu)y*u_QkRJi' - '@jW7_X=wu}tr!gVUqVkq5qB=7&LD>jJs)8qZ(}+K2bF5ct2mR^z$Ztx+TBEEG1?9pGK@!`(60KF(y5|KUO_E' - '-E0M}?PTsfZrF}e*>5xUsmap}TcGxPXsxB@jRLzq^RqECAXcAQhIP0Rc!4#G-6oK?@|BI?VDLI7wHjK8zXDsK)gFg3JAkHB?vdFV' - 'PV0wr{|Wi*YaeLYY-jOvcyuMudrVq6JGZkCSvE;YvWXzjP?jB}M|1)!yKy<*2u@>Pv}Q$|2HLt^ZN}C81kA0EWj3O1S_)dn%g3W`' - 'S7WEJ_K#O4J?mM9UM!w8!DS0@X?D^;JGXx@vGpKSzc=GH>Ku1|9-8Q%9DqR|`2i>k+xXzC2aHOLNmXGi%SDf=6LZsm*62Y`JeGKx' - 'Xt1+g{0~q|0|XQR000O8001EXSIg0=e+d8p(Ifx>F8}}lW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0EX>w&`Uu|>zc46uQNlmS}s=`%rheNmQS|duPbyb16${jWf{3B4>v4_|1cqr_d^*t?W_*ia=`ffg*PA8Mwwy8*--}XqF=cK5%P0IxNTazyXuVu+}n3y`h}0' - 'OWJiHdf3uSnz-((?T*ln)Z5{ZIqNDs`c=M0wQ0>Ydo&^N13KsbK6{^EUH)`_k-vSDh}HKWF0antUgW1&mkW30!}-r|-~4h8^!RIj' - '_3Qg{m*AI+pRAqOSEK5vQUH#gO!BL<<*5S430~!dnC(o35zXeFK=y5umrek1`>Bngr4D*vzqm}M80*7ejDo=96LVnLE' - '_%f3Jqy)kwVg`|tfvPB9--BC6$e4GGw(I+WGe(o;uY{EyBNyN;TMrNz30d>Iw&}OgA{H!Cd;vMqaKR(FXA7k*s|xVo1{_)6@%yM_' - 'zx5D*7SMD!sOzf;lDwsBbo}3zWICThE^ovMdShO6W%C&fk42yj14K%lq41q=7y>S*r&ZoIpJk>xiX_g`plgb%C02y(Fope' - 'OSY=F^Lj(umhKW_e$SM7A~8CM1?=7SWf^$~CIOUeFb$W$0Ed0|twhF#0V=VqNm0MEw&}*QH0N#EP=5MK@~)e*uWDdC5g&`XV=c;p' - 'g4WkuukZC~YNv>ySjsTjTqZ(>Cv@aDOR{hXptSO$?wNDap!RYUB!~5;W%NO>D37lscLvr)4!Q4cS5M!*+0@w=IJeUMj&XxqNd7IF' - 'o&+=8)ETGZc%E@h(?Pea5s{5Nw)k0Zs{mVA&;b|{P`NEJXAX%3-Nxz7X{2j`L=-!jH{*`-=ko`;#T${wPz=b~={rKp;;yb3' - '7zKhFB4|5uef{zJTGWf{>o@S*=Yrpp?w)QLiK>K@v1vOVl(0y8)e5Yc!vSppAUetBrm$G>!jjUZq%1ma8GzwgWlp*tB*dh{({`9}' - '*3sz*(TP(7g7rAT%gFb2~NKQ&%j`{eu!+zabngTSy-Bcj|6OB=N4;n3Eii;WL' - 'o~5VlO4vctED4vUrS3-2BJoX~izI|JiUYmL6=;0DT+J4lkGpUdJYSq(H?e#hdojf%3=dW^f7Fn)FGaJ-GrkSzn^Y)' - '6IV3|F8Uaa6@o1rKhY9^JPh4Pq_&jygV5IABV99}fVr2&MZU7FOrBL>ks+~`!`1X4_Xn($dI' - '-8fbFQwBypW#qRdy?d5!KMal!tpJUJSQ-p+GF%D2>w5l^AxxkhCupgR6N-)`YC=Ei86@$k%$L_' - '6Z36J>zACd{H_@Sm0E*~wbNW>w+>=EhoI$o^I49QWinpO7V>P^7V3a+s#X+v{T!sHjq{M_+3QHvu' - 'V_#xI5Jx4H^0djJQkz~Qg1' - '67jxQaeSuZ@n3ojS0ci1-~&1;+C3c4bi~wQ9we6}ba9#Z(?dQa8=OTu;lF4FOIBUB??phHg?(UvrV{ZeiGn!WXRv|A;3zkI_^hO)' - '+VvX8EW6$C_3~x(|47%#Vs1KdF&`x%p{@R}GKy5=BQn~5*w&zy3z;(QCY6MPXSmO|r' - 'W1Y=sY>I=gZl-zMa0' - 'ucLNZhmTJ2Hjll|%sP7WH5^vu{L6*?U;OYYH0a=!?7P$NzYPpLX6w)N4f9t8LxfC#67l9Y40r0w9`DN?' - '9`E^^f@lB2K@6Gxx_(L*lks@pZSLlg!~i4qy1etJ4Q5OVe|O;0p*T~*lK_SPP``)RI3@I5&Y{;ZHHjp8-U|%dj$e{-Z?x7l-rOaD' - 'Gj|E$WxoWehxX?tudh%<7&UR;01~eUs*Lp4hfGw;{Q*!*0|XQR000O8001EXq)$S_>jVG*+YSH#EC2uiW?^%5aBOXJFKusRWo&aV' - 'Wpiz2Z){{TFJ*IWW^Zg{GGB0Ea&u{JXD)DgtyfWR<2DR_&#%zD7aMSmZBIjh6zC1t;a;x8R%{q@fuY!Rqg9bTNnYCC?!S+eWLs|B' - '?rxYLT3aG1@+0|CNs{FH>X+Lq)^Tlwx?^0`?4*UI_mi*(1`nMSm9WfM4gAR3lLBoHqAQXlS*;q~9$8s7z3nxWB@@T4)fUaD)^aOa' - 'Wmc=OcK0N@;a#*|vudS;%E=XcUk&O%0v^I`t8@0w;@e)rAg?SuSSj{{sOqH*t6rqz_7Ls=gr9!HK6VsUSbxutP~Y~Qgq+=?^1GW5' - 'OabQDw6gxL?5&WdL^Uw6wHy7WZyM06)vA)*7C2-1xL=0Vm_wYp|PG1K__GI9' - 'fnQmzYG_=dpls;Kl!A798N0ZgK-*3Z)T' - '%$iAI>=s3LD74))=(sA@^umk(?jJVR1dL*Gp)<#S}5dWWx}`v' - 'Dag6VLO8cNI%jwu9sW_-c*8m7rud^3DxJfYWit!Gi;eSn`|Wy%erwUCbE&zBqLRRMBm(j?;(2C+cO9rYrH{;K=wEEShQ)uwAmlnW' - 'JRPUGFu;E0vWK@?w>nL(Ne(r}NDS4IP+f16EHqTvVpM30PEUPbu4jE7>Hv=xNRj9-+6jU=5?SHd;|(_uRg4BCL>04BQ1NjPNg*P1' - '%xqmWZmTeOvUn(DdqSr&w%M?xxO+;@>=UOJ6hKO9IXkvBY!YpgoME_ajlH``dfQxlpZEiUo;$?p1&u`o4-i39jsPx0^kw|>Gg_Qc' - 'qG6|lkif7=|024nIF1Ecra$C!)?(HC174-5GQ&=IIf&cMib8`7ui4=(C+TU(H=rc?Lf%*YM=_|)xE*pMkp>Z#U~fY2yP7q' - ')!oJE9N}oQK--{falEgEPW^}35W!*}6ZB@bz0;o+nd9-2a2x@F~$lAC`*TGl-;j?L0P;ZDQV3#4COQNoF_m7r__4WeC6#Ht!' - '_Q?m8IF)RwgIFu#B)-H+OJzZoHxF<=#zLFZwH8uxV2W?H)g+f6eMx-!pgs=u@X0B}v%*R1G;;eh21X?PMN=cvJ0Bg3n_pNPFHD5f' - 'D-*#15VO+owjQtRTT7SVt?w+n@9lE-GG1q`^&1^9%?2tGYO}rM>i&W{=AzL6QH$-to&Xejj8~zUt)ek;p0>qm@^ayFVZw}<=)sJ&' - 'uq3w*za=91S1@@NIMJ6-H9X{OybTRzGU7B){gFm2Oo-3#!A|>sEq<5tvc!3eKko3(?lUxvy(IT5iv&AtE{>^TGaAUl(!vOyZJpV8' - 'R*<;7Tm1u2O9KQH000080000X0N6FVt5pR60L2dg06YKy0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiHa%E&`V{~O?' - 'Utw%%XKrO=UuAM~Z*nehd97D%Z`(Ey{_bBv_`@KjG8-q|uo-Z%B29sP=nxFqFrWw+TB2;C5~-0?(iFpf`;KptC_62N)ktEAJRa}m' - 'x#up+jrX1c9qQ(X{R*Icm6DvG`F?{60ia&_}Nd3*iGFIU%S8*ZxD120*&EW3W0FBXf6)rgd`?RvvVM;U3U{-`ti' - 'cN1>R(du=0+$<1$APl_fy(lQQUw9{FljUI>VjvPJ+zb6z&)8>{t=2jE?%Bt*b>Xh8k9dEdKLSTmngVnW@y{M?-0GSy32JGx8ME|^q' - '4i!in*6D3@1Fg`y2*z*ymk;VAugt*(+nqx%Q8qbq;e|>tmMMGT`QdvgyaKL5XDF0r9n6x`>aM7(i(57eU66o+y{TrFSYuE>PE)d4}4px4Z!h+O(d1-*g2>CTztda}*Co9OYy&4fc9lB)pn6CE#XaAGb70*l;D0(TR~-9A4v' - '5V|!=$#L2=0}Mf>6JEuQQ^`5WNvE=NB?TnJIKBvgzQ6zT{vIj~>QWzQ$4~|kC{W{mL=N#8_(qS}saP{L&aDmDx9oOIb+RDlX3(+}4dv#V1r0qS#Qc+y6&C+pZZ8wpVY3`tdS0Wj2A' - 't!T>t+Hs&e062iqlvkJw30XnIynyL<=@GhaD132Yn%#Y=Ze*sWS0|XQR000O8001EX@tzB|%5MEUl@Is6pzP%hP+&Wp7y{cVFE=ikn!eXHy5|R*u00V$pYjw(RzwVy*10W?k' - '$)#>nkx8JZr>Cc' - 'v%1NvGPp~d;HFyFLH2o7Qh$czosqVilJ$}p112dErOdYg%8vf' - 'drAO^EN!dhPMcmAS+GhMi@dxY1;LM5wt`QC_O5C&P67;ZmDV`oW%>!e;^40Hs;F-BIrNa1i|jMB3F>u$Z?lFW{Urw^>vm97DIyP+' - ')gmi`U$Xr6u5Ctx;cz$@+|<=FNRpd1(wHPczFbvx3*?nmOJivU1O9B8ws+$5FKJx@%7(R{S4EN0z#4&Ik=>;0qFvBF)168`_w$@{~zx5>%bho7&Kleg;S&u2ec' - 'O^$y$K0Q9WLR{F($x(9l^C^FId76BH#--7vvc80fFnYvT`lne-pKhoL2b*v{X~MPJvglv`3>kM(sgWvb=78hsli+rC@#6e' - '%s$i^Dh+7J-$sK$^5^4=^W?+%WiWw-JNh-Nt0p{%g9VZ}fhRYh;@X4#XpkJA9i6{ryh+XuPmhzM!=oRL#{uG;g8q$zB5&F$*ofH-' - 'usuFO6E1a15oXOBY2rEvoRVX`j!76l-jK0^|4XTncGPqo%b)7y0!M_C;WEY#ebd?3b' - '@@PcB9>9-YK(dMn87S%sIJL7!Z%B9fUNoz8o+ayiVLi{AmMs0{N' - '!Gd%P;%yL@Z8Q}iezcC|T;u8_Cn4mXM{<+S!L&b25H6yY+s+9hd;ATSUhY?t~AH*5`-y}{5xGUKWx6P!4Ji?Q8Oo$' - 'OGvK|Rt{l6tD?CH2s!oc<>`gXPL#qG0EH`Z_kcAJMQz' - 'j66jd{wD@$J{6Qs@K5Z#;uKH#zb*hHe!~87U-My1LjGYiuT~FX6y!JJ6==BtR1%E^x`PNn+CB~(xo8ok4dy~=f;A!LXb)UyC2c(3*TcyN2|xWZc|Ua4^6G7p)mEf^+GA{evxN7z$^1oBZOZl90sE&' - '#1eZLtsU?S6T$4>^}|S8Wplqt' - 'B-b}~_3mt@Ex}ie4l)*i1_JJG8AT?5VX;=ee2-q3BnkaojXr}Xgut*ouOz6&r4Bt}&5>t%w5cher6R>ruOP}2|5DR(Je%fh>wi|90T40`~q`&B&O*ufdNUuWD*SFf8;k*^&-TFFreUIpJtO_U?LI!sQAmeX~Wmibo_cYlUO^*' - 'nqm|52U)K?e1UMi6i*mmF-ygrBVukoPs;T&)IB|7Fz{A+$>|D$3E^9z=Dn^k{xX=!knI4nYvB5QHBYbCMOr`j7~2uf6Ki=d+jG~O' - 'lG0I))*z}iI9$PQq_MGsh$(Ni1utP0-DI_3XDDS=aS{Gy6qi-KWGO*FD*;FFK3|1$tms!N$g$?=Q0K#=p@I}p)7{x9Zx;D&-iDFW' - 'M1$L#VMElC>R}XoAM7z{9R47Y+b|%s`xH%TqW~fGDdU#Xvg9n!>I`9O^`gGx8u3^t`6H<&xnVqU&=WF$o6f%0Is^$*AT' - 'NT!kl9#7c~+q$fj)H!LO^mydby(RBWxVOY_f>DNp@7HZHlX@1-!+hb1XP@WAdLg9T' - 'nO2|-JXYM~kTd%(1t$EXr`DV0@};&YSp_qu2xpVlyJy)w|C=T&mXvX7#OyV?GE{Smeh)wht{bx}GO)>t?N2erh>8MjYPZ#C@t`Zk' - '<7SAQ2P}+XA%m?6oToCdF<+64F!8X`z_FFyRnZMkVP^s7g4CNauTfy{c?|)K8CI107>)x^s_aQ$u?D?j)k-;_>Kl`ldFk77HPfCCyXbWRoDBoBcQ1@#)w~cME=*0#Gq+*1AyvDHG(8jBL!ZpX`n~Us>~}G~dSxtxu2d;oF(7j#ZWGHVqWT7eVPjE_' - 'VWT2lRuvj2bzt{0bp{YxP7`-~{7-k8o)iKf4p}L|A^pWa=R+PHv>kcP3GaXJozw_gcTiNws+Pf5stw' - 'ffs2mF3F)-jwO1r)Ap6!FeMXgL`W-cTD$4vf1)!*E=bw{VfSf)yTn+G$EoMfT`shsQd_nDWkdt($ufRifW?s@sw|w`UZuqTf^em2-e)DP-XIO+;V|-71fzm1o!>E!i)A^g8-Tq$' - 'J^AVQlB*`Ea_Fc{z4(;3;(*Q3UADAdjqsT~#a0vqA|m8$zAkW__;7Su74M+gC7U8PJNlMA19minM=QKbp)HOnSfw?*WaN*7M~3u?' - '>jj&D!^PrILn0^D&H$#G8w>{B%ef6Z4C+30=NGW1bwlSA{H' - '&)W&^nreEOC+W#RK=Vr~M+zfR<`aC2?LxcbZ-c$a+=?%mZxnnVyxypaxr_)N(@7dtSyoi#EwHb>OUvLjZF#}Ud&E~ynwBPlFSGVT' - 'U9G|)MQ?Gy93g_KR&0k{&BF23k8j`KAB5YmoVOm$LG#x~ApVWml@wBjOHi*~SA!O9YghmvqZwHe1Rro`m#rnx3_5Yk>uhn#=TS~*' - 'ERbzOGg?IhIUXf)$yztr1yOcXfnYS^V3FpG|AXlJjY%IlG2)DDc(DczyJQj*Ytj>$!X?Q2`Q5v7pkxVZNfNmQNtO}hV?ruV789LV' - '2xHUFCv8?YlfB*DT`QRJs)&nhS>0!jme3RwuD7%G@<<-N4JnzryQRXq6$)CZXVerO8cYFA3<6~D;chheqOx0zqD+OwLS08Usqu;0' - '608msbO^u?{9Pzi)5+p<7)74)U~ln%=q)@)JetOc;!Y@`2)iNPQQ6%Qd8j@(U{Tr-i^8cG=m`8{`T0Tn{OinfytBV5{%gAN8=O?d' - '>yFbWlU@JOlgSXgzM*^eq~Fur(#zc5!|e1fJ3Pxyuaa?#PShD&U8c0*dz6qj*==5;EQ3Am6+F@4xLj-kH;Y6l=&)AV8>Eg0AhWGJ' - ';Fyg_Q~+VrBQnRyr|coj+iXe2^JWD>K1BcRwg_v{wX-V8)' - 'e*7l>X7raTFR3M#+eJ@Q2_e3+cu}_Ds+&{7)%T#o5d6${P}qr8X6PO%|BB5c)cbrsWn@D~RGFyCDUIF3t44L!KRMEo;Wbg%*d=6k' - 'AoTSjAeO5#_7*l}==<{j-MRmv&MJ6s=xzfgp1Xd9%r2YMpKuWuq>S)`YkO~oXBFP2fE8EwIVM+7doe-J' - '7f*4pZg8Uz{}h?J;Rf?rL(dL%o6bKK*?m?__PcDPP;(;3-`b~^htA=4Uq' - 'HTvdJ+nd&MVmEWaJ*6la<1d2ftcPzZh*1zJ-3Gwg3W*jn46lc=A);FdrJc++rU#yyQtsp(' - 'CqjQyrHQFB{Ym)v1yi??V>$vilcqI~NpVl`kG;yANgry$5iHkD3&X$9f(*h%2xf3H@U8(uD^1?@IxiM{{0SzQSOHe3Zk-r+-@ww9' - 'U2{x}yg@>kl5GS{-*Fs31wRp0}99{XHugoW}69RPYs%ixkUPXi8g|;&eelo^lUf(%#0}Ful@D>oa{PKwq8po7Z' - '^T26guow%)Bix?OJoaORNentkN7jQs>SUZmJN9Ja@meB%uG#rO=@98B10A`TN;o>}!ti|??D=*B0=6H@DQmq{uXh$kS2P7^;JvUYeV82^vZps_0bkb{8ge%T8df!$*HTEWK=-7-GAXTIVj`g`hlHp_Zl^_-pVGKMobt8iulF6v3%%?eRlr8|!us_VGxkKBL#Hp+T?3pw41wSW' - 'pZl8{4|aupK0w@U|6}74CS+|7OL)kw-ZKwL7=DAlgc1!I(bVlCFp_aTc`}iMny6>i<#zhasH>iEW>`f;E__>KZrIId-ovgcH#zRF' - '^TKITT~ilFP5k5aHe*vHI5G)B?QW~U|9L}M;|05yQtocB' - 'dP^pgBXP4AdCg*%+Thzvpw&v5p_Mor7c@a}58(b#NOJhy>' - '{i1jSi_O*F11WR@aK9?UU9}(U*=<`vi(7icMb5ioryr|;*@!+G72>vO9SL)41Q@=80Xybs%u7)u2}r|1' - ')P;ckrC3EeTRqf?xTc3hHPS7J^YBN^JD*HJluqJriLmk^{D?06u4(tHI={_vGwkL*tIfru~nm%3AKl37Ag=`#gu(Jmu5s8oq^oUd*fXEa*E(8!96-8)FYPQN!<`{*#' - 'G@**r1&?M+e_#Ua&kRLp(DK{hZ?c!)8e#z`*|=fu_y1jNg^1Hx%sb*yX@6p%Pvo&pIQKs-0U!e?4Ha|KZ3)TWfoSyjd?b#XD$e`viI98x&WN{Rjeg8g-vFU4b<;+%R~E' - 'k!HFZFkI#(cV`JMqSAQdNyOiKHvxEH1' - 'vlmCIU3g8PC78u3Dcc-n?zd|GmYICn6Z9hOct$Nv#`mG8kl%)*L2^>TWbAs1%N)M' - 'j`MlD1%yw8Z3Zvd#i*)3#Z)2@U9^1hc`w2uMBfN|kZR=mvF>}Yay`X2+M#DIN@@hmkurexIgmzD&pP#2Z%F)8v;SHD?~+)YFdOf0' - 'Xw20FFNaRl&Bi^eSh-+dZNI&DSHnl(3-+xQVWDhg!pELHX}jZYpP+mQw+qSoLWE6`z2GkJzTFWAx8pjD3gg~U3&K8Ix4c`vo>3@HmBCOrD^V~+$M;}?hdO&eolP|$' - 'mZ}W)M!Pl;f6ZVGARYoTI4hp3Gd#=-<_-Lvd#;|t=cSFhW3i*V3V$&e%@gkSSGRS#WWEF5e8LtP3;l^ek$^n;TKPqDa82K&>61hm' - 'x-B$iDbkNyB@xy7?%HYg?J>VlBby`VG>SdMH~bjBzM*UF-!W(T3pQu{8?4xT|3Bj3z~?9T(oDOl{(W4HPP3x}@W)@n|9=nv|Ig=k' - '_}XHUNkHW62|pA4{+8%n)DBv?q3VZd{8ectkeMnnnu~nlvPl#5qngc@2Q@k}ICeoIo;%}*)W_OsN9fZi?a;uq!wSqz=A|{e+n)z`' - 'VO>5BMLahHhoq-0-*EMtsYdk7UNqaD_%p|WyqX%`^vt=`O#BCY9MPE@$(3+w95b6mzVn3IxdeE+p={Z|DEeqMej>!4S^B9gyX2S<' - 'Sr!fDl>fl}mO|Q}YTTIQT&ACqkB#_o;Jxn-e*ao8xus0GZXnez+=n!;(HQP9@ql@0bc={TtSrW27)20@A~LV;QNNPC0;FJ&XyWvr' - '%I5x5LwwMn;(_hR^9xMdDlvc4FdA5k6RohV7;=5(GnoLPsvr2TcROc^M)It(Zy%5L@+|G{vC7KW$b%^p6TNj$qHjOWJ-lzFRkR!?' - 'j#}Nuy2yZKAW$slkGpv6z6^)g;lN!9s&kD`vA3GT9HNI0ddUg%WBL%8@xwFGxk57OmLL804Y6P*;)A{$x+ykQY}0%#&XCBT_Ryya' - '_wK#+5SCu@{_sO`b^hb=nf@zA4u;(h45pCEn`E@6BU-&vR&1c%A)~{v?d+R%QH0^15llp}L!>i1YP;DKNWHMcMeA0V2oaYqGg*sr' - 'y<9znI-siW6>E3_%i|637p5z~`3O$cW#?I843d#25>Q5w0lRgE9hTLj' - '%&=64AQNx6iI=JIE9|iM=QdX*c;e_j`MZ9xaJI~^>D(4}tga2gJhX6E{aX%(1@dk^@Y5oQ3So68{!0%8S*T5$*moL+ZH=w56e`!U' - '0R<3^huuOUuCbKPSjr;1PEQi2^Hjwcn|Q9y%$j^AOQtuG{5C5xd2|0w$rJex9c7$#WdJ>tQ&E*LRdQYKk)fIV3)zO7700TSk}' - 'QZ-~5a*BA0OnftjC@Ep461TcdTpKPf^xSIlR%V_U%15h@r(jWKP5DiW39|Ve7iXaG7ToPeol<_$kLMC+b=ZQh$@Tmbn1Ww3{gjIm' - 'z3F&oZ-(b&uEV`(G%wQS3QKCGtnKJmH%bwvEEk&<&pdH}F4ZEm)6|Dfp5=+Xy-is)!S+jqo' - '!g}DnS4w{L-7Gb9-|072XM!+%tAQLY^;8^SD$@jdvw;r9CfNdH4-Gh)BnwR4)*uK|Prjc7yUe25bu{MX' - 'p=8KSv_k1iK>D@`NY0l9s1JD4jSF&}C>&VL#vjr&=5tg>tp1rlj~pyi3no!yCEl!`$XZ%eJndeeA?4H>h0Z_Y%#%3nq|P}h!FA6&' - 'ee|VCWCQCol@SRlgk>s3' - 'LAK+8hj_j*Va0BIPsrjx7wjOY?^F&U1U^Jjybm?1K(p^USqHbi~jiX^d0WHh<%!X_mS2QGL6JbQ^iKk#P8W^CQ=7A' - '{1{Id6zPYd#Kwc~K$&%ZNF<)MzPwv##&(W4g%qj$A5RC!)Cbo;+oUJuEXC%oh_?A*->B4Cz;WPDNq7!gDHZhUl{qucjjvSl2?Fms' - '{a~F=Gk3ilOg$;z?m`r85fQ3Yg=qyru0*n-oQ0AAu{LN!#Poc&oGMYEs13cRP{^&98Zv3*C>%AzkI{iC4fTo}n-r25%&SW=^8pBk' - 'ni8mwR~KhfCBcwZX-&WX%eGFx@*R{N$|bfHol-YPRe7J)E!9ln9AdH>%s_)@*7@6aWAK2mk;8Apo+4Y(*#y005aY001rk003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQD' - 'aB^>BWpi_HaxQRr-5T4D+_v?7zJgUBs!3$QN#6!hQO9+P1?tTrcKb3I$Q+5Yt5^~hk+OH=_}_aDZz3hm?#4;lrUq4X`7?ny@H>5L*5}8yZ)@d+ic8pKXt<$p@Q^-U6OpP>^Xe2$6m8u@X>7iPfdxaG7itEyf?b{yEBS~|j+ZfM!9lYrfLuGha~Vru2`xY3C@bDWyCUZX7WPgLo`O4}K^8o6YM63%Ail^mxEsks_i-6-{6cvw4LvljGLdIIe_FvA&t`TX0Z$NZ6ojONx@i++L7#5ZZ>};U(@RCdpg!nP*gzbv1Pz;^NvbJXxo5!pq0xTSZ(^A)nI8OqPt=30NTyTC3{aQ' - '3N}0nKqqea)YkU0H*fsw-n=2&f)LqsY?}%IPE^RD?Sy7oIC;K#1K0*2j$SkHEg=~N2RxCH(dtH7bgxaYWgY9aOA){^wM^uj(!ZA!' - '3$8bRb>5Q#ML)8>cs)*RyHQJ|d^Lc?jJ{q0YPQ=D_|w#4M^IUNA;*lgjZjaAgWBypZLi^cTd)TN4d6hhP8y4<9vUCq-Ny' - '5KS7PEM%6JVb@>}rJHGbQOG|wgHfYdRR51aingm6AqX8EMjiaz6R-oI_4gCFBSP)nBL49?ae6C!`g5ndC;EnrKt>2bU|}1eMRt4$' - 'WM?gz;0VZ0Gw@YG;`e`re7Z9S_hjUl3nsyEC?uTJh;o0}<`6y5Canavn}`NNivu73jSS5T7iSI3gNYcHp#K4Lg4?vGL*0WXEtP@3' - '%;H`_BB_=@&;u4nrPZ5A`yq#ttiNWdF`%`jZXedmp!JZhwW6dfCrB%MCHqCBJ!FxfX%wIZ0@NqFB>$cp^tp|`xw<+{eoD4m`@BoG' - 'p1R>^EbwZOBNb4h)K)l6y?=U8x)hilDO`}64INmv1kx$9!1pJx**H+yO<%LyY!1*2Dn$lmFthZ`%G1;WUL>m)akp7R+u(jmJ{Yz>' - 'mLJr$A7_C;0a8SSozA9Jivs>;ZXpN=MKtK6I1FpfbhjP`@Yllk-Fx6Qp@TM$AQ&!e#z&HA&Ssf--ZcvoK|G(SFfDr0U<@*;YVlX54eIV`u8Xv6OwZNSS1v%bxw0CR@rU9Z6qn)OJi0X!Q6nZ*Ik`S-!AjA#u' - '6l1X7hqH0_})YM=bbMsArhx8^glZ79ha;w--&X;&`!' - '{}m+bkpJewRTB850ko}`T<`V^X>rzA@plx(ULL_-MBEBzOZ*pYCl)b4rO@XF715|DxE|s{1w!`^8hn4ldNK*rtO`&^' - 'Fu=DWUl@kQrHiKy3Ttu%BhZsKz@hs54l0TU93s#U;m7ZxI23t^&g&FyMl4-9u`d6-^27+$RQcw#u{e`Q!>(tQT@}JeRhF&Ot*g$0' - 'QotInp=F)r&n1L(Wp$#kG-qD#j3KM=BmBG>k5k2cL2N!8npF|AwJ;@?M=GluHJ2ddUYLRluQ;k%q(BStRMC;5u5jd@BiTkXs$cDL%(}is)}eV{%5%+9x2JrCu}yBWTq$CdBk}T5t;xrvYHwB_)6Bg)e?alXt(SAAXS(Xbt7Hhx&j$T)#E;jD^5SdghV2aKpuCy~lJ#9VqQ{Q`Rsph~DT+mo7(lof?ky;CE7qY#j{sxeV3Vc@ny' - '_S7BR&bWg)w;wOyrrD71FG~GF9P{sNC59+3oIK>M!3nSLUbw!_@opY(=V@NV21Mrz?AiU^Hy}ZO5}e|XMV7bT=h`v25)C~55+F4c' - 'WD0Mg@P%1aTjVm>&9|6<6I{1enX;Js6{kRUD6-$FGCg;);(-DJN<`>Uz=n+%qH4e~lO<%5Y4^IPSX>^-pakOWHdaA>H$f%VnHnm24Gg%FT2nx7@Esf{Ujqg(rZwR+e=Cxfupt5L7OI^}k21`VX1}Vu`~lH&' - '?0Tk*UgJ1DP)V!k=T9ANoDEPNmP6bn1H0AD4_B$mv|eL7GRxgr5P1)>2jMmtMKC__Nm`ql6@bJiQN*WYWGdJ@Qt`BYXJ*@NB|Jes' - 'ESdSk_%UB&^(I4SA`!ZM9>(%Uzf^QF$cB}|M3QS)v?' - 'SKa>3UK4!qXys!q;#X?el1h%955k2}I6S+0pAwBx^Yq3X<2YV|2(-S;kNN8_C5p#=@Q^M>1Bkc?2)8210!SsaLj4lXXg?EM3N@y5#^%S2iAaU1$xu)d-9X' - '&qm8eYuBzlS$3gDZ4kL66#MBmuZAgI(g`Agn)km-=hO2f4zgQ%x$Jx&TLUy9gdrjxSJIedLw~ohE@XRSQ(;il!i6oXxg4aE4l=P999Vz=-(@CARCg+P{w+IV!r>osKHM7G;!1oW-lr%v_Xt' - 'vR!|dqG7O5*54!qoaSJS1IE5>ID9r+W_Q%coC&' - 'zHvWT#h)F1t{~6NL05nej6_%PjE?PH$!;5vBaQ+9;2R3cd8~`7jLXI&7c7S0*YIG>&*p}}j@4KUKjKX#m$WUuz&=D&#q5FjX>0A=' - 'B+1CsH?l~e-M@zPfu9gVuX_z!;(SYZZxOQ;YCDai$luQqfV_m*cbg#wp*=J6~t34N@3ilKT3S(8VUWy-E#jYZ{x;{Jyi(-CUgXt7e' - '*7mEqHH7i!!u+2_#N*U@6aWAK2mk;8Apl@8lEFzd007D3001%o003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjC' - 'Y-BQDaB^>SWod3-V`yP%ZZ2?n?R{&L+s3iz_x%+(bk#8-bD@3MR1K{q`y2@|A{g@fd3uc>S*LGlVX+B<#yyndF)k5nMPUqOH`*t?k$Sa`{we+w?!781cSlg;9yY}D=!Qe' - 'n|f0wVd$l+by3z(H7{zaQymuZi)g)uLL=`o`FWG%^Mp$ykn=36s-#k#RdG76N6n{^S0?K$' - 'l2sO)d|nqtrYg*%`Bf5!>#|rUWxeH6>!`lU(wQvt8XoyWy+t_W`}2IOMl?%{I<1qSs>{tBCjdb4W|jI3vzw*zX;m%P(frza4D-!u' - 'y_GNN0UOEJ@>N}wa{-9#4#zo*szicR&a0VOLID({MX-*WK|Oo=ZJH(hh3{nPi|5~7M0K((5E<~H$Tq8-em)~qfgks1u1f0Am?`5$' - 'h()=jYM!pQK?Mr}sHcD_@|)!DPn#?odEY;KTPBI4a1f_e9RaSWmBtq%Z_WQzKX0Nk0VG}t#0_M*q*^Tyslzg<(s+}EbHGMusjd*v' - ')=N=a)ruC2G*3=rg(Up^tW_>cmw8gEV*J=DRTrx;&Ew=wD+=$P^DDbJ?$&hPAa}7^Z|Wpm#&uZ3R*=tG3@_$Ut9DwcEmvjf92R0*' - 'Uln;+%zlDh8Ag>rQM=x1o-V?5R94#N7#?`==k=eTJ$oBI4Zl6Vc>C&2czJRD=Wj;zp9TqyZ4=%B%9}X0d>k' - 'Jh=GN`SX|I^Jn43tCyE=y|Fhq2cmiqCkqez3$K$c4j|2I{5iadvQ1K*c}(vBc?#G#@Bvd;%78^' - 'pxH3vKvQ54Ic#k}F#HI^kIJowEqT$RhPVIc^3}^jL>-u$S(cc5fJi?!#}LHO7YzKDB!?Y6)S&W!T?~Dn*Ot4*vhP388)PO>YUI(V' - '03QBP1wchs)zD{%shfqD0-EvG=1CY4+&=LrXv70{gDnVvnyUDR+aGt&s4KNNXbP$i;g@fLM#F(#6kI_HylI)%L*Gj>' - 'pxE%L)#OBPdkT#cOk&ai-*Qz_Vg_tTz+J#!poWrSRl_Rz8Ghs78lp;!{Y>)G34lG4TW(6I0dLOquBy#yC|V38*@Nz8NQo+%WkN1o' - 'JMkH(y|e3pmlTnJVNY|w`*1$veR{!r@+JRw5%K@C0;oWiXy2oJnI-z?-=93;&v6ROy^ONv_@;y_{J6PheHY8D*cLBdPzpsONSyYuM' - 'IHDHBeg^@CS{vfKk=GQZ+M?)4G{#km#R3QjRZ?JTxZpjaR*GuaAb>yWE(zOGFWFR=2%=R!+^;siv}*hM?zxCyrEm#vfOWzac_hX}' - '`-R_)7(-uoRJ`daH!61bA}2+VZCEBOpr*>|@2^PBM2E7BHaeim' - 'hskPPZ)p|trPi$~Ef^Jn8>n?<0*c+c3pxmF4{QsV4Uo-AA@brPVZsg4YaKDMCVk3VF;}PvJ6Q#MFT4M*@`Hpv$zjR12oj4Fih1(~' - 'fml@oEWJ-V7`YlB6z7U2v(|g#-%Ndg<>3sCJ=ml}=j=^n(^H09jdSenD1Xpwr*%=Kq>3V;TL%l^u)~SyeAHG10;+@p9&*W%cRD3f' - 'mq)qZw7H6^>xVQ4ZPC62SqEQ@XQ0C>VTvk}1nOuSY9bTiYVZ-b{MTO!Tm&JlBzbpU#EHA^U?A%b{_w+>1HpCx%0(KB8HHMa3YA_<' - '^+s&>^h8!8#2RbVYSR1+6grH@Ub7xOdvyl^db0tXS;;;6RFIo05f%mz4tQq?f(>+{MT&%MBPJJMFM$OBv*Us4Jwl&-qc=wm9W?hY;8eLIM_VhtQ3UwasvYPru10fZ?&bvJh*%|IOqnIhwLJ#07O!rx!YC*zd`j;Z#xs!<8&$~lE&$7ABq11n?}*U' - ')hH>PjpU{lrdgDWbH|#P1l-)#G=y2Wh)8CA?6lt;=rmbzRv|7$z&_+VzfWemELIKf6Tr6-3n$e&0n(YmfaXP6!k)+!Jr+Bn(FtT%' - 'jreY^r1c(GVC1HdqYkpyyl>{Ld8b4asR0}u{88DShinO9z3qTrdBV(oLCUyPL7&MQtj{5+r%%vu4G=;PY;cF26M1N1%Mwr6p_s5X' - 'TBs1{4UwmLV+j4YHKR>^rNlj;Ifo#>*3m3WOj+*?Ed#=o)D}5(Ro5X^v_A9Tnic9QfJ@c-)+|BmlS9uj&0l)s?sm-bM7+E!G)`Yd' - '>%<#^rh59s4|u9SLn}1%bX*+VdcpuopZ`e9xk_MyV`U%^3jiDkCpkp>0$Vs6e4b|-%HlD^nPDXa2-|j3ZRO1oXrDDQ0SY1rk5GgK' - 'N>v8U3}IDlN}QB-5p6RAzWqH-ve*O0Q*KRUY_K*VxjsTo02PqtEI?We)v7^9I8@7khOaE0j}2#rHdiK5EQ+SFZ!kZB*EurgX6l8)' - 'pun*>wG>fVWPzHCG=o}Uv`X_LjOJ-vvFPA~5b+OPh?v^!2(=SY6ln2Ln-D9Rtpxzf@1pjBNU2`aLiL^STg_%9lu_+lz$iBy1{;(L<*ZzP*S&' - 'to;+Rc%#iNkr*I|?PW>hF{85NyRJ1GcQ2kC^jMAlpcAjh7K^tRS?6WreXv6Cw(-6gGQJnSjQ2;MaUUkp{PHa0`fJRAiLl9EgzVGF' - '`93A9GsTz=hUXMs+HCPR=9+JWLT5;U9-prAaYH1CvlMW13qV2KH)A' - 'hklu-ooW(2g$-JIw^vCnMH_&e7&SD??OToM%E*vRRA=7v1t~A6?L?se%{-x703~3xP+5|&K(VOKxE2Kgr!;+d5nMq%NVk_>nnLT=wPQqIvTr2%Q!2r<>s3#l3Z6?I+%bw0kIzD@|}eK9UgXg?^K`fkT)u#IKn-HGf-jcI7^GP' - 'tZLP?hEczoAmz5xYR7&k@fxgMesNhobgu9k^_-~oRs{?RTPn03l-L3fmaRjZN6j!^nn1MY&^3i!gL4n8Jt)BtO>t^3j;MB+u)yQA' - 'd9sCp-zngM^f|PoH$0Rc`aY>C>HNAiNN=|FogN*U)4_ZVk~_|}ZRbr2yye)nE={^4EV4LTb7D;{yGX*=`YJE!KuE_qJ~' - '#$#)M69vQ6lt^Hr%LG{y5iX(yq_5*6VY84*W68j)|M9Rvk2InZ#8tsT$-)zKiT$hSZs-{f3+rD`Kgm@U1gQ>WRX&+O1;cIq+r(qWwXOAu9f+{Xf6&*G2w' - 'MuqWyn9-@g+)sJgskrc{J2aO*dnPCrmY_1x2w=6REaumyVh7bb*4`1Zzu3ww%A?Wb$SKbOENN=^3QtlUmSwy' - 'uY&n{GxP)UeI5F)QzxS|=mjgZ_~wdH+e^xk;syxgo_Z#_mhK(e=pCSFZwqDvs1s|-ta>WOn!$D$se(W(wlpNzcIT|A{NF1P`|icpXg' - 'M-t?vhUXI4!N^;}thE=VdjTzzsIQY9Xxp_HB)T57PuAEMB&`-{4x(6V&HMma?BTJgTz2YMD1i+IMp<>(=&YeR`3%kn-Xk%HgM+>0' - 'R!s;e=WEj^Lvyon)^>Q}MC>neJdzXog?Fq=)+XID`C}i^vb!>#`^lAARMM&wC#^Ee{+5Kw9N?Z*$^q`)B&v5t^`jT3^M}4{`x7R!' - '&%LRVv3pDxF85AyxOuU#JQ)l~vkDv6CdN~LYFU69D_xiOQQIGkn{qjWb!`Amp6kPYqqpXvAMdqt{YbAB{G=Ga*n#res2Y5KZ$q;;' - 'LU&A3KPu}BuspD4%^y)vqFwa&I)a<_&pR|&^6;=^|!x8YxbpH}0VZqkHgqD>2~QDYulM{%4qZefi`' - 'hhu}5)0~8+ZNMXd){5K8+fc6?*ln5~im&E8UC?qPg{?7$wM_B7>v&0lq8HR~wsR8K9AJwBTbEa<~h34o`%dX!LYC>UzAtBDrG@oEqg0g>n}n' - 'wdU=h#~2{v4oq*H-Fr?czD7j~a#)z}MASrQi}YP*lwHtH&PLuDOwQS~57S-ZO`zKncgq%vTkd4q>v*=eS4Ef25ENcMk{En4?+A)}' - 'Y`eSO#}xw86_6u@_wv=-Z_e->4UgZ%F$u^my^an&kf_uJ2#z(6-eH@cM_%Ey%Ps}m3BzY&W(ob&^8OX&!};&*h-VeJ;#(QWS%_FZS4qi' - 'X5hvc`hedsr>_r?i8PqdJkJqw&q~gQcD*W4HnhAR8Z#K*X)fC|sb+Ts58yqzC_C' - 'YA`JHNNqZBxurVXH2_0(X9cWZS;kR4a!W3Hr)A9%@ue%J#sp}AVy0y1^vl(TW<~q_L+g~ib?tDV9dT$KVL%U<&_#PjaR`Sv1fJZU' - '7|a9LG4(NO!7uUXakE}$Df?YRCq&W?4qWEZLGV+NS|`-)XcWvffboZ?b#aV{2k4d;tE7bGQn5cHZ}6kztX`B9-q%XGMgMEpDmA~%' - ';7)ZOd(aWz(Iq@QC{D9&s_GC3euXg3j|}DfxNy#o2Moa^=~;%405qo|SLgvvtd>M6fjOwwO#(8HPxYJr)yH5}TC~gSKZ+' - 'UnJemXu5ku)P0hux=$Thw>!G-<0I@GQFiI4imo#w?9z{qtoz95IxE61yoL1HbdY$F{Rr!6wwp$VPtA_9rD)+ANH<' - '-=j_yeQZUP?B$N8rT$TeGTk|V+{@cz!P&!rbIzwLWIDKkEe66q=YG_Bwi{^F{U7j1LE2qVxB_JzZ4qRhSpiHSKN^9e9cm#61csSk' - '`!Xx;v`F4ESrusI+?14A5FJ128#N6' - 'J!n3;9v=CfTLA5XP;MlSQca{YU13E?^D8#LVGgk}<&2j?=<#mS;l%Ut2|OKT}#%$`pCf' - '>l-&X<5}uIqc_;JS)=W_ZV4Wuc#ni=3mc+FpQNa>e^J~eF@7*~ZMM5`Iz@`H=SgzMKeX|fEA||!xt4h`c#qwFaG#p-LD72>dboK%' - '2uV>2!w+^W2S6mUd&)D{Bg7N&PCrkzQ03u~M*?8j=x@?*~5&K;^i^e_jnf>=Ic_!>cwc%+2-W$u5|pd^>c}Xdz=PByr^Qx^4LD' - '0y91jeg6S)$^gtF&!q5SobI^zjdf0M@yU^cPg+W_4T`K|rEIe+X}oe|^Gd(dKcZnb-Bic_Xx7qlq-c{ENnZ^1vx{ML>>bT5^uD{;' - '_SW37e_I)oys<&<9uIRsZTIH(?crPoWpM+gVrvsxma%;yGtELmTQZ6_Xy;}uAK};29yj+zqvzvqBb!!VXh_S^' - '7$JTLFWh_T6dIWL?J~6Wp!?o>N(Yj3pue!eShs^n{g=*ZS?@&~hjt@F%jl|oqY5KWwl;1nF__AJH9kDqr+-?HPX+^NM56oSy}D-;' - 'lH>Ebtryq?GEukk0mxvE{&3yn%j|ONVht;KkrD~vr{=#h5D6RyV#Bc4^_M`' - '{g0k{yelz)wc*4mFaRm@nZq6hii>V40Fh7rdd=Y9p&3&s|sNRPGc37($HY5B7_EL`Om19K()W' - 'oJARrVP;0|&(a``L2VN1R@M>%%Cu0qeUWKm#?BpO@2s(Vi`jd1-0eAXToy2GY;g$O;{q?vF6rPL~@1-~503pm7W97b#g8f{ct-$6Tvm-DMt8Ur9@' - 'eN`;cb5dSd<2|o2yMDS%$um^-An*538K4_0Jv*yfP9JIxfVl~0aU8aM&BJlVMhJ(Ev50*jV?Dx=aQ`m7j;1pU3VuIbf=%W@3_Cl2>eoez7r2#%6g4AlGv8QC8?dQ*n*ko)56A}iwgM~%zTGXakpKHGlYInUr|A|#c}^H(mTM0' - 'CE+p>nRECnD6Yp>1EsZ8#2eL78^bHSHw~#8%6aKLsbqxhl7*jt=rQpMbE?VU)nLjY@p%8u_}KUH?w=(21x9$FwJz?Dk!Z*pVK_Mo' - 'zCQW#2t5LWqpy!oN8V9zbbNHGJoN<9+99`Uh<#{XO(uhvX#2W2r+)`ifRtIDA8hg&40MQ^f)>mEQZ-l>FeK>a@KpOc34(yV7b$Rp' - 'OXg9oCwTiVsWtKetDEA<&x~ox#rfgI;Y%|*j|$>+OOn8YS(edNv(z5CE@RCOFkOWUr)0w79v' - 'hnuvpYlm|iVinGCdyJ+Bb*tUR!MR0QG=Lur5AOn4@nNt3&BNMnRFVK~sfQX~=E-d+b2{IXG>%%nWa4*==BiG1=*>i}@OHO3UN+Xy' - 'E7$E{>Si)3t3{0Bb)T_UFtx(nY)~U@e?zP2^MKT)cWaM4*p`|f!DrrOl6adcT9U=NE}y{yEHUq8oM7jHR`RN@*VWnM$4d~vHZwry' - ')#K$)MV>yEwI0v1V)l4(5`Xpk)1%p!Pfn62Pi7!B&rcR#o<5nK{{HK)lPA%Wqu)ipi%*|CJ~}!4?%89yD=}DaKl}6Xmq)6%i>Gwk' - 'p13rQFJ{BB@^x_H+Bb2*TWg6fp4v>KS%Z=OCItB)TxQ!jP!!WCX3u~oKn6WIpj>kVpRFMg`EjZofnIVqPu8{f-Jk#7D;9OY*pBd_' - 'f)(DdMK{_-U0u*P`QzTL?#mGOYw+KN1Jdow;l+7B@4GnH5fMaXU{+JjFOJ*5GU^uRA-x4*&u_sd%kKx;(|*uo' - 'DOcwX^zVe`X^)p@gjP2sMtAJ(E%KGB+s`LD6PotlduDnY1zOWS{vuMtZAc#cuq3Eb#gRNUpN*P*+#tqrbhWn' - 'J6e-KbN8SR4EAz)6+P`S5hFayVJw^htGQO(o9^Ff`6s%vz;-z6RpwbaaG(rW8E3CH{9AXP>x;`5^h&;YSqXQz^AOho79HUm;ncF*' - '%JjcmhjnqC5XFAq@A1}TiaI>DUFX~0ZhBs+R%wUBo{CR97Jf!k;s3Gld)@Y+%r8krQDWS!nqxJt=nO)e53)N*fmFPEa{2q!Z1FbA$-u%%r&f?uYrI1=9}~H!?&-#|K?>I' - 'kNB{sZANp&_b~hv`Pdb-r9s4D!EC2fzK$;P)Dbf=I3IL{OSCTxamBabpGS|HwQYq(P!t4^C(LlZF0$Cn{ljI4-I=vV23V2+?Er+OH=UpvV$DJGB;(|Tt`E`;cfnqF1BA+E?^L8FwytE7MWr*2y*bH;?r9hqeOZ?vrIo}tXhk*n2k$GlkGCvL' - 'N`SxbjbmH~ZEn(CL!I1PO&%?;HjZ8j@6hl{K4bR_1rwfA+Tq6}jqfZ+FmyHG' - '$P;2+@C#pIeq?61R3n!_Hi)8U6;5-9+wjQl-_U!-#+{gZQ;sunkjf|;Nnpr>CArl?i!7yJ' - '#9@h>y`pge9ziY&9|-2r5=Qilg!eEyXL3;6' - 'J#Al?f#y(Ep8aZS5=EK*s9!;=&wpbCrn&7r(A9k8o!wC?kux!Kow;cC8J>w?L006;-l4s*hvonl(qIMZGj' - 'Rr&&ItPc1^K4hmmYLfv%*)y%4^uq$Bz&+%TfOGAGuwl;7k2<_S8RK@>YW~ZoMUVEojANb' - 'vLtUuc?+a)#O^kxTRJZ>wDXZtV#4|g*8Qr;fZY>W0u23KaMWpd*R(0>PubBeg@S0YrYW`i^QCiiJB5~PW144p~#j`2BN+xCIr?_Da7%0EuPdGzzWO$L&{prD(uUl+)2159ciDLJ4if5$KEO-&!kZ%qN0<' - 'H(ZQ7YhF2bP&mtqxu9gmZJ(dvC?h~t5#U*5fc_;2$w^%Cy?r?fqib|__8#lv8en*#HA7j)ln~27)#P8hfE{><*(5ZC3<#XbU=@Ww' - 'N8u_#^=dHXL0$msUl!$d*s|r0&GI(o#Y|__*kKVM{T^GUB9td+43E#?f3`G_n{xt?N=~MJE9aiWe|@mxLy$ci9xStB7G>yz17n9D' - 'MlUdPp|cYl_dPnHb;3UQ$Id=Jv^aoRO|+RI|A@R8G>jB2^LVC*y?arHxV)7P9Ha^eT$^obtrf%~_UMy%)rV20C>o;d7cv~So#!)<' - 'Bg>flljw5Ai2Oq4n-bc_tiK%Fx>>Ia5b%ZnxwvmqW%xBujk0K#WFQ=ESD}S1ttD|Rz=Uo1&!2@CuU=lhoqCTNHfe>$TFYT7wj&o%' - '!WwlCpxl8J4Cx-B8*>@6!3O~<2w5`xj!+3>V7sCm+(U&P!rhzIWfbb)t^-ynvR|cf8yP|5xW#>eVTM2n(esNoJSa1W@f-|ng2x2y' - 'L6|o<8GI^q6HpLi&sW}6u|}KNEqZk3pVykP93Wd2' - 'v=yS2jsQJbnHA9;?v(udn!^dZq?WZ256#?P' - 'ChfKj8w`9V?$-_*2S(VLnO<*HPwZ&Hp?~THq^&j~TwrcxH#-rotU4qF8irAtnNW|W&ZcOTHRx;I(grhJ!o%jMr)>*J#!>NXc0FT5hYGcT-cFz#LTU<' - 'b0ErUeA9Cj+kwX}&z0dd9eO*~7!Pi4q^FSYkb4cfK-;bHE8BhFA(B=i40OE81~>MMfPX>ae2Rqi`73xJ5^IWi6yx&8Uoxaq9yaBD' - ';Zjs*9HcOqA-' - 'hJxM2iDGz+5hHPFRd`ht*B0l*!Vb=ABO!uu@Zpw1M' - '`+_ddFV8jCNZnN)UKH6)BJR1+UD5&gaE)$pDqRYVhdS-b_>HPVGg3?2zuc@mXz-94B#?k37fL4@%u*tIWS0h_36FD7VC$geL{paH' - 'CX!v=iiXR{;AvXmk+RpM!p2L;II^|T%+zgJm7OE?Gw+Qk^!!;xHVEkYkZc1ABtOa<}#UHs@d^vj>eid^0bC$3H{wV=K|#seu#^hNHjo4RGpg1-y--#_eIqJ2QnK%' - 'dm?TmgswGxNRf@UMu#n5ZDSbHRWVEFxO4WcN63KCcg7O8VpAW&I(mjUX+dGAzUCEi?X$0TW5$9^Qc(5CU74aGl&g9Jp+9w*##=M`' - 'kzlqP`xr2tsQ#LXlz!Q3GdUQ&+}yypPS!PklEx&WEbfgkF`B_KHze#juFC{*{`+Td;RiuWR6+OX1~xuJ*UYgf9>BQDEp(k_AwCcJ' - '^+={-zVt!jH^+1dATNA^x@pc6Of4CO9p' - 'rfz%w7v6Dl_`5x_ESjq@`9~Lxw#9QpMAw_xC7|yX(qDg+jNZmfTS{+`Q#90r+prJy4Wv`i|MiZht{ahnGvRq82P$Cj`gAX=%)Zfo' - '&+{9Yj@Xd4*~o_hB+bhYf%ihB14pT~Sae+PG58!u^ZCg8T+Gwwl;-7gnZeK%GT5Zd%!=72i%Ot6%_%n7V$X~y17fh1UfkP|ELs;e' - 'Iv?QCIed>(N;HSQQZN4c%{O|=rN3bknrrr1pewl2#g>eFYXI~vD`BBx=t@>8ef9o`SKaoz}4Pkrzt1!qp9kLJTWL4`gE?fm%u|{2Vf-p|kXtz&nAity7}Y' - 'r~Fj?T-JD;kfEd0GKiCUcWcEc*C9WABi*mbbT%!@|GP=R|%mZj^eNMm%YDhs-<' - 'jl+WGQHG9&pf+KeSF~_r24Jm4pa%(ts)+2ce^VKygr^{J$^5!p=N?JUpp={9-u1!p0NVbyQ7G%~&OYI?}wzfg>lm' - 'xLAXjiK3AL;@cmoq)!<)(R6j}ly&0&;(t!LvP%pZ}gh__FVkB{Jwarpafn#X7i$kWwkB{2`sGAoE8H&Y2H' - 'qS6}kw$f>!2*krJI2RHUFW^5=C$CBKBguee0#061DjQ$vIn!K&P56k!$n0CF!3x<};{)HC1+30D0' - 'GyIV%`PO{}cZifD?+lAlcFrp9~|n1mN7x~T0PdNKSxCc4h6hQj%ZL?' - 'Uz1^-l?8^dH;E`00h}9M+R=(gwFSm$0L>+^qi6Uo$l<7VWyPqFqHqh5Gd!ejU2~h(an;+p);f)R`_^eM<}~-+~u7hTRiyx' - 'ZPG|O2EyNrpK{Fg&1#q@%g&h<^97plP?jGVH8oy=DLBG;)Wt4%Qz9mRgC@^(Ta!;;SXI$m^B|dqV' - 'e}D1fo40SCUznF2%`p(Lx+*pq)Xvb!67AFLGQlH%)LP{{^Mv{)C)uD4%VH;7(8*4g6rJVO<)*&sxcKIuOYHlT8aWn=hDFoNB)?go' - 'gtDv~gT%!4DXvfZW-WCc6?Fl$7rlujcuo5i?&)`$qtiRw)s)xK$y!809=VNMd#1^uBd&9C+mm%lVF)stgCtNF-RKcRsfGQe&wpw9!6OoL' - 'w-3>9?`%dkZExGy*G$CN*h%d(V|3<~XQ<@NwFed8i(4Eg?~Y5Ktb05AwmePr{oxe`hh9?i>p)B}@Npz3W%{VOZqYS)KaRW(*KY5>' - 'ch3g3*`5e@o1cV^GA0rw<)02(+I(s|J?HXz*~O*{qOkw`s+ni%@8e(~@B15ir?1QY-O00;m803iU7{A~SOF#rH#!TP*@idS<*?C%RHzY>&O?%i7*s' - 'JGbwiqGBkqHA9a?YDsFwTF3d@AAtV;prr9SxmT~wRl7rx-Dor#jYgvZ^l&)*p{YAoRws*ky;-r3QIU7~D9YQGt>>#<#6E8Fsx9jZ' - '-c?0($?7%hnq9Q4o9M;6*Dvc;ah?u`!@*$L)axkAmfLRIuq=zp^`>q*s8!Wn-T{PmFc9y`T0M2F>FRpb$`=i^Qm@r7CZBHerYft;' - 'mN&DgS1Yz4plLo|$hKYqw0yo|qv##`dCRH=dta4dpxvdt2n5?kK7gwyvHch<&ly|(;Chx9R9M>eY5#(c=a2Gd$b@T+6!5qN(*F!=eWurHi_{VaVVdVs;4-I$-V=CeE5qhf#ECg6)I2?aGw|12voSW7gn|5*6A-x!I*HaAKLO' - 'a(}vb-!LZTWGdgXcD=;;nl-E~i!I9l$+pX@uEjojFGX!tD_<_liajnAC-UDHUb$6yS+PbH<3GJpUA@lAs$d_DqVVn&f8`cOsZ%cW' - 'YyxSu?O1kM09k>O^7pEM7mM7hUA9U{w5xIfW4PME<}A7H9wc`N3DTW`H6m)Rz7T4M&rg9!e;#!o*r`3A(nh~B)Zg)AjD=kJ<&' - 'TNMJ#f|#-$4HCDR4X>9iuJW?7n-L(N;};GRIVQViyR55$ejPsY4^$i9Rz==4xp+$?&gMH#@G4(3`F_coY%vd@gB;gpAa=C4M9KEH' - 'dA40brNQ9ct2f_$|04VT#ryX!e$1Z#+tXKXvR4=HqDeG-`t13|i!iG=~DOw7P>=MRw6Vv}41mK5g6#r@D%c33Y5v&kH1UeUH1Eo4%d8K}5(Z@y!Ft#MnlZ3f48nWM62g-nW#bahlVM^5w{c(}JH!Dx9)tuE@sh=6vk#*)' - '17O2I239sqJ|aY{7Kuqu1eQOQ=D4O}RgZ_sa0JYdNW9?%l9Z~<42Vzx1b53~j)Xt}M=w^&N(vLU0P!3@h<&C=Y*aYZ1o|Xc8@X)f' - 't_9f=`X18^25Dx0%($kaXwW8#vw!5+sq(CVaovmvtTre1' - 'W~fvMYKzX%;-Vz^%;`#i$QSkE`aj-*@~~p>7Iniod11`T8z#nP92pCjA{9YiW$eML)egO3fC6M!W32oTF>&khB' - '7+x`}*2^>V7vYfUcYXc)k+s`Z$Gg_S1X*PTy#Cn)C3>tn0jV' - '5?#WpHrmx&Vz5nKHULt9l3;+&Ma*j_QF~QyR|VChYIy`)7GaU7Y+wc2;3oBkn61468MQ=vY^QZ71b+ApNR|r2UJJbwO' - '(T3S@1061uzJ&kY;&u`@gvcbYmqg_Or~%9T?GnL*P_' - '3Pfq^CZ3+A-=2SSI*Lxt($jCx9*?5a^z`iXF-X4xw`vn!n#9H9bFH7KE=qXFGJLmdA~zkrLH+poQ~LjK2AC44I7zqF96A~opJdSh8l`o`*c@hiaS;Cl`P%c%+%wqk#CtHh#75BiK~#pejlfnp6S6=UuP-jhF~AQ7#}_hV8mk!jjz*R' - '1rp{{wTn*-zO2}-u%k?*MVI(Ipg_AXMrOw+#ykMIy`tJMxN;SJZZ7!YtSr3*`j94b)=+!bpMMHF_r6mNGphe!yL7vi9m)G!iL=uxx#8B3DpetQoalKr8' - 'f+dNy)%^~Z3sjOSSi7PNw#;GKhvj)`;H~7!+xIWx1~%x|+Y+&R50au`c`Hx>IB2NraJA&hJ>Lp2ZrO;qf!_rRg$b(`&o)YwFtA$Q' - '0^>nvV@c=)DXnK0HHVgho$SoWgG)s|<~XSIyw0x?yPP!3+p>lJm&o7?CN`h2$!~xrRH%x6eEsgi-)82k#g=a)aUgCPQuek67HmOl' - '1;uJVbe|vt8e|~@d1Xj@V^z0jBVHJu=pKS4`cA(uUX)B<)#bnkZ5=(^~c0rR#H2iKT7H(AY%vIZy-{V8_`uQ`y' - 'AoO7qTy>6ydh9ZD9Kip_)|Eff-VKD(Rb8lshE$$<18aR-ZyU4^t)PX?!d2i6^D*on-d?O&3_3dIL_pk*6Rh+p9' - 'Z5Del;W|5Szn*`PMs6F8+1?dyQ_Rt%i$+I)kd_6v`^`U)%@1JCOwM1C*5AdV7VzpHqBEQ4n;hlpkJ}0bIUU5rp1d1A7n=>a{dG3I' - 'umI$XriNW}QR0CI6vXpLGCsjtv8~$e22Vv;F|=TN!)f=LE314fYa``Ym${LYN|)k9J4Wi5#tN5ChRr&%' - 'Z?6bA=i?gI$z~51sR6)k*Ka2DgX$!qA^1-;@$&(YJ_hDyeA|juU+uvG++}w)Ox*UgjIxY13I+O3csK>V3Ra*R&tXdlOu`3lXi%3-' - 'L8=iVqWZ6c1MC`QjQBJ|?VEVLWAFJ`Nx-U>#k<>`~=O' - 'lceIwIH+tq=MaegsQ+jk2UR-j3?t%adVJ*fX}FvOQS34{DUb3aXJl6rpv8*M6DV1y_7W2w!RN9#X{PiZv~)g#)*%7RbsL~H%CC6^' - 'Tr48BF~XH!s?OqN8tAG3@t$|>jv)4p`j4!|Q+U2>Z_&_-1d@gJDv{i#DHTDo562u^G>Pz!<+7K9A_j~`bq5YiG?WMz14jkWPz}nW' - '04HuIfTGOQC@>p^mW}pc<%3' - 'u`EBbY8p#`@HhF=Rp7rzu-`#UkQX(-t-8YX~IwliMfD*Z3&51-$!*6lk{uRDSXR*KH%tgldhYNV66~o*@mjT=aBhL?1?$~!ru!dR)+@`tyLg|yfA>Gq>`SB>?~|b5' - 'F(T}rCTBR?8~IGMz)nu%{g4xH@Sh4u;{qRQP-K1T5UV=yKh*hdfmEw`2ZT5@}GR@#s8Fw?Z|$$$THm>>kx' - 'r$rd%SCQ+SAGy0CJqBdE{?I4g`BdSi9`3)g-(rZujiMmXNVdh$34(BQE=7=(isXF5r*s-UV~c!?p#dEo?ts1_JbMh#^$o~1JgWlU' - 'YRkFu?-)U?`)%~|wgkxSs=h^Vc+$j{+Z72ObRfZP19^*~1%O+X(QqBD>Uv`~((v<~D}EqEhfV+)H5>G`Bz)mh8ola}Jb30ru4TWA' - 'p8xP?L-^e5x1!3v-ZC;8;XxJ;4?#yYq$mm;)Adprn&>nLed0z2bQs=Uz10#QeJIES=48cSFdOVr!!``fw!#GR>H@tJks7xp&~urW' - 't2Fvy#V|Ny0Tec%zd@(SRrw3kRY3qRQ4aF~yuD)lEpHgh1yt}x1_Q0gz~ktR^#(WN_wQ^u|;oRr#ludSW?O0~9^w>%9jN7|#7;UsR**L1V6EtkHFE3ifx{ejG#' - 'J-+vWN@1(#`?`iHS?#1x6|nqmgA)!O(8!H0M8hP60k+Of(~=Yc`+I-GKQY?PN5XjC%8iI%Zq1cQC}M*U8rrShL1hrY7S9U!If1Yh' - ';}0KS57*`JV#`AT%-I5eoS8h1J^{qf4xU1GD^GP|#V{w4E&Wg-k2T2wz>gM#^#CMBSCoz2MR)<+h!GqyezcLLInvb_(-dRDN70a8' - '!EdKq!ei1dD_-)_w&jK*-N@xz2foEqKEY|Vv>Q`z43IO8!K$ptYlp*03x9cWAEFE2K@v+!8cnGE4XSp9NuWY&GOT?+^v2(~o' - '+b4m3Q*L5!&Y;X)dB@0Bw1_v&9BK%1M9~;e>EF;;!gENd;c7vv>Ux9cfFyZf6<-J!K+*+Z1zNGTO;dZD!d9Vyc#povQgj631hi{1' - 'C~@c;f+x9Y%W_aMAj@?}7JNyF%Ckap1ThM+k|5upPk|XAen4KaFVHnmseAyFhPY=IZpWTXqm>IVSN;Dg-;aK3U#&~)*lk!W&%0!csE(4Py|eh>CMt7)57CP;LqkBp!NB6GZtsv5t@qjO$V3X(`J*WRoUVVTN%$t#;f8BpeIlXiaV*f8tm(' - 'c0!>baWQqcW|oX_X+dMeI4UnIfaC$9rY4+Fg;Y#t0h@*udZR0p@GDqeN95?j-;2V0D<*c%i+oddxGCfQeA3$_;G&4(JQ^7O3brhw' - 'e9V&cB3M|&|LDTvMX>P8GhOcGS+Ix<7jj6N1&W)jSl!6rADsO+o!gd%|}aZ1chMKjtW?D!6O+FnCC|F' - '+0e(dY0-(#Ra>?|(2&c-$^gYDjg8sV=kX~3Q4O^5^P2ug>rXz1G2BM8Zlq)VT@X$Cytge3T!2eXt2p1' - 'Wx}p%MT<>5<{0$|vB@WoUE|Uf?$gs5sGZ*1vsp4WrCZ6JugZv+LbhPR|2ztvr%&`)gg(>x^29uo9t{E$MhHsYbs1Ijmhu?jWXhWo' - 'eH%xV=KyFTVrhtlZ%TICYUePt?V>y{yDZ~d(Xr=e+L8^X63&jwO_Y?Qpoi6a5V>N!nk{juw+O?6*!89bqMl>o-nO6>=v_suwOyAz~Cn@!KiJyGY}Y^RBA)Cbx`Z}9;es!~Cuv6J`z-sM4!x}|F9Xe&YqX-DMlvfwn^N2w$O1GP' - 'Iox@K?I@@nE8KZZ+$Xc8&)iVXU3$=6eb4@JsppiKE}t9>p|5*(i_r0~?>@&1IvhLjFnACCJLF91``CJ61KPQgH|FEH9L8REWcy-#' - '_h9e#ytmgBez3R0U));?cKGs+hI*;7y;}$AAPzf-=ivJV^|A;3XHb*n6+hGKLybOKGfd2Pa?EeHb1D#r2TnjnIY554hgOs@Q{gd1' - '9+9aI<6RM^iBCvi*+UpRvV9bg?T!W;yV89WlI~t5L%xq(`R*0g5`N@Jcpt8m@grNtdj*A*ADL3#DCCQ7Q&a`#OY*CQqB~&%lS9r7' - 'OK=_%bv<0DDSl`E(^Ze+JTGk8353!gOtk%4;y8>PU{ITLmQ%07o|Uz4O1V9A*H?Z#sNjde{+-|Q<_UYsuZ5`q~NH9}f-R~6SIk$$ej~nhR&nJDVlETdOIeSq@XbPn)252$GJQj^K`w+tL`kJ}k' - 'LngBt>SeMI4c1gG`>w?3yCrRULxT+M;NQASpA2@T?jpdr|M5wEEsstYGPpd$CE8-T9(m9s0R58x*{NL1RV3zAXE`+xh}{m4jf' - 'B6~fs>AFGi_w(5VmP*&Tv$=>{vu93x>Fv{@5gHOF<{E_p91DPmj' - 'HaVp#Ik7cMp0@)b1(L@g*>sIt$' - 'TaCJtFU?YlC=_~n~_;UrZ6;}9mq&3>$PEg6O&s)oA@S0;o*6H' - 'O#8$MUkwMcPihC#{mqMaX!~_*@*2p)1uV`tup}eN-t3sn&AO?4gSrN^N9?_k&5(-TV{oH(X&E5bX-T6nv{#&NZd6Ycpl}x' - '*CyHaLAqx3QaFrBtyyMIOU+D}(hTl0rU_s&QT@}2m|Ww9g=}aP{=|0n7`(cOK1sNr3C0o!z}~L+Q_?2}=93nzGZD%8p|5qxt@s+d' - 'q>tIs0QWnP4c7CNJh4l#+X}m#2HZGbI|&47leGv?%B9i~ov=`vp3aOl(l{VivQOSkFg5vdW+&7&X10A;t*5q=Tvtc{V@6xwcO`@U' - 'z`~s6dYO{%_-upr7nB=CI2i3oQ)SO6ijm^U?s5O%Y;Uk@sck(UMfh0yP*3O=*0=S7GwGy$~I{@!6N#Je{l)!c0^aziB>ekAL0Kf9*L58j|;<+Y<_WM%swAIx4%pM$-QNYE$z+t-~@^=s%^)_BQ5pCiANIOLdx|zQ4G&ih}Q+^M9AcoS`b9;5vOzX' - 'JT;H8+9^gq&IZ3yk)f{y`T5g6ZN?}hAE#!nUHQ^TvU}oMRz;WOmXG&e*Yy3YL4Hp(eCg}#57@v0`Up3*>^a$jkgghy8B<)nV=z}?u(' - ';*&jAw7o22a!enSC>{xzX95H6so5p&f_X2GyPjq*U-UF9Y#}}DqQ`u8EU)yBXI`LyX3LWHwsw>sTJ`XYzA|WU(qD9ppb%*>kRpq%' - ')S<<}D^K&v0q@;c@gh-7PMeg7LA((G)pQFHywypDUtP3m^g=BOJid6&0^ay|=?rLf!%rOK=S**_hleslI5dc!5(aL{3+ipdcvJh5' - 'L#Xtb?H(s?&j;K_350WlA=YlfWgdB4_*R>~fNaNnY+GYa3?_g)(N)x~Q=Mdu|WSR%O5=t9fF7j6UCEo}Kq!8rEcQ@creb*y-HX1XYu&^eL#`&`TpuR{=Lm7bjkIPBoc**2Q2LC}RBgapZL)3Yi<*QOcUenW5`wkF8+E~ss-~?B' - 'C|O-E5A_wsev3gZQ15K;MS{ohbr6ey+nBOYiGso~r77g=gZkT4c`88z9@>2s6+Nb2m02{2f+&XR@(>B*3tBZ!ZeW+j(n3BUOXNVe' - 'j#yPVIIDqp&oAl>eJNPGXv&RDHa~TC>6#;OP${8x?2;?JX1mtQNun`Qfo@$B8#d!mX~I*MAL1N@4MW%nXCf|Z7?xyWMA2OINj3X9' - 'F@?Dl4=D-Nsqm(z5MJZD;CSF_vlCg;wHT2|3W0LL@m2h7ZPHn#78{X)e1R^SjSm3U(iOfeq$7RSQwbfG4' - 'ID16%=FoXq`1agUU))^MKS4--d$R;zW_?qIJ_aTVy|3D?$W~=_onHpOo3ixl%^?ThV1rsL-uFMjl-GsEA01=l*GaA%Ia`!v;qc+?~{hwm$N)#8RP>n({5;D`)UG4t$AGQjcIVyF1*t4of6qNmkiva_hhG7Ifb1snZeYRYisI@VNj^J)Dg_&K%h(JQ_Icnz{Q+lPz' - '8Ep4Hc1;ef%vMD^nGWaKGRI75Lu>6iWG|ez<^(qE7y)yO-11Dhr+IqCpiVs-^kvcuMLqOdzipGC^Ug9O7`CK1aZWO9{f' - 'HX#mohru+J9;Uo!OPz0*z2+J-BOsIuL7(`q5)(G`j$Oy69=qWunzcrMDw&s5ADGaMW|G&elk9gTnhRu_%9aO>o}G9pBvG^#wGn7V' - '=33B^b24ROd;-WfjM;R23GQK#*&?qrT67yM$WcH$y1gnFSNbC1ilGA<-c+tqR#MBHc-nx>wTGU>od*R`IA^GTAyQZz^_@llPSbMc' - 'yQqpRqx%wN+6h~~AfKo$$i%tEuMtg47)5o9J8=*m*;ww4Vo}m==y-9s?%`!!7r~<=jBdq6G8(&HL7{8n6Emp7b8UeF+9O5#*Q1aOwB>zd(!Yn_-=}(-9_lLVG?QZv>p0|>WHQ-A9U>4_IuIN@g^@S_l=tbN^DIwuIEp^Bbp`vdrlh=N^A!{M' - '5B&gRg2xk)!moM*LILDuAsm26pBzCSarEf~E}~rABq5fYvu`P)9B)uHj4eDeF_jt8@(#r@UUX!o4n{Th@ws~o!6V@Yn*7g19sDtH#iEExudNq$2n^|e$A{@?$rfdn|!ybVI5m=MqR8}PUrp(P|O_8n){fcxnL*avNbwHE2|S)PSQT7K1{8-' - '=%!ZS(#*XX)~SPcb3UfaM)hvM7zzg%x@qX&gHS;ovNdz?F}_TF(3aGP&*bXqCkhzuk@6-`qlSxz`DrgAzNALHHuWZim+ORT@-9~E' - '%g$tGW;&3Ri6=(5;fXWCi_ik|w4+;I)qVTTxHs=9_WBy>aw8+lc^&ia`Vd(1fco|u9%w6Wn7CsrRO{^9J?&)u5Um{d1`$2E83th>' - ')YWu#Wgu0kJs|eF`dWzlz~Cs@c}r6MCb6f7T2g25jEe>FrM3UUdmmWKRbbF?fzj}?X%L;{15?%2iP^=7hc64afQBFBp`;b7K*JV>' - '31Sdr;nrhY>$8k~>?z{=dwFHW>Iqe>O%z`5+`kTzo;crirk|>mo|9n~PK)h&Bd(}67FP=#+HJVM`$W{@k=`lHSWFP)ZINKG' - 'S0y@DVs48z7LAUgpSM8uZa4XDa_DYg0~NB-K+mmB6fUmgD5SokTzf!^Y|DxVxKQ(~JhVx~9JPku6<;KAADY1jG(N)Q#m5-Fj6}{=' - '{Ee2EP-B)A=F3D?AOnXr50oYCCUI}D=3kfF)hdo31ePqy)xr~x@hh<1tUe*J-M`u#u}<-q<`NVT@q#|%KJRm*Tm7Bl9Wo4xLX0MT' - 'P><-VxuPArA{uX9-OKjb9!I{m?FsbI!U06QMv_fK(cE@2u7e#>q476^{)m(wJ`SW&#PM}~a>K)m$w-NcUh&yc&`iPK1KC^|_O#-Z' - '^|I5-wJCn^z$xmx2d-LP>Yz9yK}~|JtTx+@TOBdq2AP=4I(>#>`qf+bK?2*}sbJQk_>aG=$|@O02r7CQwLH-}$X^s|4~5Ky2ilf2' - 'tk~jNFR0;lqg~|G4bXfm``h#H9-X~5j2`Ew#tYbs&~E2iTvdc_g}76VmSxlGO-Xx&u54W-r9G+bXu5(G0-rVXqHM2I+TgTXez|bN' - '?qg;oGJM_UAeqe_3|G@Qy#}6kLsx+$oP;5B1Cdc)8A3o3GTUYJ!BJQUq8^2P@CcOY=yiEnTXa&>ryY58{{54~njGGPi#OJ!FC(E&E>P4doUyQPdK|)Jlu>s9;({T' - 'rDJmH!^R#oLNt`AfMJ!`4c$Qz2u68zBDf1?7v>d{*OKu|R`iuZ;&LXL3XbgMB&={u$Yv$L|7LHT0S(QcD>>zqsS7_~irtQV?C=<1' - 'iMM0gfsEtmEqM*)EA7OJCi|)@m!((S7@&yfBpXMsmi)F)Ol)3&aE$nIAKi*X!4oR=4Hl?D7DX3aHy`tnOT!VHG0nYNUb(PEZrp1a9@O-$@yqcqSyHMrv@#yjXu@HxpjmMFktnwY`zC3nlPz{XPUSv1Ntkncv-m%Ms`QWRzG@YAUab^' - '{G@(Bk`am9?97`Zl%0MSg61bH7w8`F2p3jb;jQbzsH{*V*4C?yooHB6boH(tlBGHJS#!>-LX9j+D&Tv7%Ea^u^|Co-=q&{}$A()kzdZfZbEnQ<2ohIh}4x5apb@Vg8yII-Q' - 'o(~fVC5JMy>(WE-l8R7LdAYfoo{7ZNGqpe5n_6DQ)$ximE2wT?4tR~=sfnL)`31%uUgd3O?*cKDZLvS;m%bK59;lf~Xnm-zm$w{~' - 'zU`e&tJ%JNr!_M&ooprVq<7QU3W6VztC2p@3xz?7Y(Ibqz|(+u)lfcob)-)zb};}C^GNx93!IB8Zsr}<{v8K<9smQV7^6#W3V2Pd' - 'DH#p<;LQk58B@GWgA^5nuw5=VCvc(T?`(M%ZTQ5!Gb959cDZ_KK<#AB8i9**uN}5_j-jUO9J4l_ocW*Z8}dLK2$vTh?9%)~5_!en' - 'sdubyAE}F3U=J@g*3brIVdo!E*93T}FO}n+jB8MR>B)VGpQZ~dVj#9jGoCoKdgxvXB`YiGtC&&rU^WUjW-ujL?301tA;ZZzbTn!>' - '?(-pt&0!i(tWonY4=46?+SL&vVLBQtFg5DU?uv}pl@QQyD8xR>Ymf)X&0QP!fhrF`a3-8ws=Z!*3g>b$E(@_)Dx9sw@~?DA(Nja;' - 'Gh+1BFZ;Rg!YFrQKb)vZ`p^tnLNPUQ)B*KT+#`I1>_kHrzMhfPDCm+Mm&jaRPI_e~H$fUX`mNOdk|MK!l~9ZSl%pK;KL$Od65Vf&' - '3uH72Iv|)F+AHk49trJ>Zl)YA;6Y^fr^}Hdp3bfC9;K8!YCZe$#nV4!@8ACE#hVn_Beo;k' - 'LW`)Q)=B&)DlL+88N429t6zhA(X$dW!<37Bb)wv3*vCy?Vf6JtW>U1bg@c}b8Ha7|7WIyb`_1a#Mo6lDnXfu{GZ~U`K=^lWTbA)H' - 'uXMJhefU7Er4%qgx4TX_N@S!eK' - 'k@A~Wb#%Y4wfm6-EVq`TP)!G$N8(R~ai^-04}$sixT}_Y#7&BY6b19+Burf6oDjuf%H8Z}11Z!jIvqD53BBNs;9LqAX*A8QKb4d1' - ')kSpwlL74Wec9`K_wGf{^VNb*$thf2-+a)(1XVH1eDm+T!vj>Dy3Xr3G!g;-AKz*UKFJ;qT$Wv>`H_' - 'kF{Y{GTDfW0y8LmBqsg{7P3`7XR9oi%ht%B|J*=>Ip;zK<$JbGli%||*HcrL5t+Uo9?{`Ino=af&^5+wm_ksJ&#s;No=#op#uE5+' - '3vygvuiKG-Ls!?SO_nh_`s?%q6a7V}h=meB9*lR&0rNV`7nI&b7EGdliTQ~%%mEhoPbDB4@+;8XEF$C?w5xXxQXT2v8R#sqyYPN}MyV`yAL5PD(zn{=;Uk{y(<*KZ=AoSf%uf%<~p|$ft_dll>V6`I-$B1vVINtLv)1t%i5HZQ!e?8ay;UGRNRCV>LNGui%j>1@~tYbW`hF' - 'BZCvq92k{u$ddl1hoJn+buOMamaMLoW{Xe|{ZM7N6' - 'xlO-Nj^O?4zJ74r5J5ox_q`WEl*}CTHzYNRuLHz2-G%F49l=ya;GxCL+eH' - '$Y>_HN}2Guw{RZ0U`NJ~B*6`?%!*{T>CyQa<>qv!rDh4A6Wp@XoQn_lJ-IZ~tenmgyk^L=yJ#r{RXF+K!6m)%iV9m`W&=~bQN27?' - 'yEz;HBtfd>&g~}dn<+aghmomOQ@!9&LuU)Ut`z8F^|zsA)Kvz6@g2gKczCujMhR~#emj$4t|Hf9yNx6f7hAX$ap4k`Z_zs-Hi0H`' - '?-Z_J9=}Z~k+EA2SVf;_%zU&BUCrmpITLh^o{=DLc7n2v1K_TIM1p3e*sWS0|XQR000O8001EX?E@$`Bnbcjbs+!%HUIzsW?^%5aBOXJ' - 'FKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VZ**m8ZeMeBa&=>Lb#i4caCyyHTW{Pp7Ji>!!OlKJN;Fz~oT90*Q`DO@i$1gkT5pks' - 'VaObbnjs=lQ<8Gxblcy)hZl(#&DawsSkx~b=^UOL&*dXEpU*F3*CIh0QcBvf3~{=l6~YbOu$C~Hp`U;FJtsg?zmWt8go~l(@kh' - 'Vtw}xRwB$dz3M;$5mt5;ECU{8H8Ngh=0Url`%_>y#0_y=QOpGD1*y>bP75fvqq&b^M6Aqev5+qg>$t(^?7U~*uu5Pe|kz;x&eosDI' - 'wtZwat-*PtG#6XEBUdM;Se1P)IV{I5CGV1Th7|fZOVx&10t7lq(Q1XB>p0ujPSBVe=EojEmJkavuRV**c7@3d%N-FC2Oz$7QJ)V+' - 'ij#fj^1v7BIGuUHR(mnYe7`ccbl6_?8+P1-8~?Zcb#3CFYI3BZl7K9d0EGr=alX9H0&1l|qAlTd*EJE9+|rV)hV;-+jwAiJW<}ob' - 'U?*L%j?}zo0!oy?^o_R4d16hl8&Xqf3x5UzGj!q&Ih%*avb%0!N6u{at!-;vu>q`?q_b~_b6YYm`w9a>B21k(s?}=@IiBi_DZ3VC' - '4)KPN0i=(~8v-W;DfXog1n-!cG_pOIY={0|kMR2?_yYJ_m7?~|gWwi(3j?|}%gxPyM)>``p2BF0Zvlp@Bt)tvnd%rB0$bG+ASTm8' - 'g?gQ9$Zk>q&^5P5eG{z#-~SA%ws8D*BF~ISZKBjy0zI25K20MazBg&T*ek4iFSPK4MEMsbm?zXx{ORQ39oLkN>0gXZS3i3=0aIwO' - '7eWxOg7h8d9h}ksiDkJ}SgJ48&57|}psLedwN=zF=IPP?9~g6w_xyO(hg$Hb=r-8DZwq}KF^M-cB#%8;2-kp*@tE!b(-IPaceI+!' - '*Kyf0TBa@nLalpR-zYYowx3C?G4g8S{tBI3gR+x-M%=@FxV8I{+;0!zK6JH+Rh{KRThj;ncqnfSo5iiZya81nBd@!~oT`hWakfI)' - 'w0)CAmgjKNX97+oB!M(pLI;XWG4dKx%fcLnpve{bGITq>8Fh%;DsigqFtiOYHY5}o$cv3SFjXU0vVh?2KsH?w*IE=~M;NSkZ=z&!' - 'Ggo6SO;NWBprzOMtjYN6T(val6HU{-BQWmFOw;f3Jipd+&LJe|q2o&3_u$+)ERFi-uj)u(d*{CyCuO%`^n)5cPW6-D$A>?j%s(9?' - '@K#g>tR1I{y?0ZOq31$Vf9c6Pt)ZT(XCp|Z$BsJ#*pP=r603Pad&DYQ^Kv1&(0D82Jp?TYK+|#lr' - '5E#Bmc-TD(r0sUN;*Ixy74_|Jk' - 'xcW7f7B_cHb_O_W2x}v&?v>4FGu_eaj<=euOGekLE5g5KUx#=cJ{3KL0E62blkGYV&EA2yh}|2FR2dd|Vzyjw49FRFDA(UXjCsWl' - 'Q|^N5DV|2bLpU0v!70X#V1?MAYJBLc*ZJt@K8WG@;kSkd2FX;-5!=0Y4}*cS8Yju*cGg5QGc9En6y~OaO&6as?haMwW9HpUW7Y80' - 'S>03Ph7(-fjT2{jzWvB)cVN5?7drkiXaBuUco2#%N)O$SsymvnHtcst0}Scyg8#Im<`Mc|MWz{84gVOysi^!E0YCtJ=#jX4o3m?^1TdHvW(L3j{u?`rR*!LTdzUi1WB1tPOSD|4' - 'K@@uNZ@lX$_R=VRELJ=A{ngtbT(B#Deiuf|=r-^Z_9nOwQh!nV8#H(m%>6L&7i=9a{FtS8K091_' - 'bND4nTI@GJP5>EZw>zwX73!j>-uyR!@EEPxz4yq%C}r!!2M|HRZi1!H{I7F=m9ijY^XPuH47_mevxgwPqizI{Hh}mbKt$K62emz@' - 'zk;_Ly&m&YLYV&DrRi$?^Un_t4{eXIZAbC#&r4o2`T5Q1(d6uMasU`poA=?;PZAdUf3AZVXuW=9-U<+!d)I*G(tBW0%)Hy!hq5$6' - 'gdgG{4Z_+=4(Ru~omlv}7;Uzo0tg}~#m#3F4fbsk3)yet$EB5>0#l_*-)#>DtonNq{^RwgAtJCu{' - 'c>9GNo_%1yo}L}Im=C;yZv3yS81V-r1LUK>K&D;#zJb6^#1TqX{yew|=0I0?yY_B1;}FPUD?h#u5|m~F=z#{XCF+8g(w72J' - '?OLr~uOE3~6auf7%)1Z5=)ju?3wG;A_kJ2bvJ`okpyY|KlgqciM9YN@O|JmZ5)>_lmLQ^gpHs9TZ}' - '$p9gQ&Rzx4-TIpNOgIv2+Q^1l?IwoKT=!<30s*dzm7+KW=%D(Xg|(XacC!xWX%sCJ-e?{zm;Rh{S=3ngH{N=g0!?X)g+S3FT|=LR' - '`fcxeF5A52gz`EoFTGm^*Rt##yz+wd5i7~>hv8$3&AlYea0}P>FuJ^ig{xX^?zwZ1y=2|?uw!>dgYKX+>euFXfeYFLf_0I@2~_x2' - '!+}>nd~;YIv+1lQUN2tbYmJG2)B0Jx#p*|g^z#+{d|el{d4*gV{yfgrFw5ZQ*(6uPEW_uIvPOp5n?V2o^_A1Mz0^es3I109@Mt*W5hXFcgkxD4Z3B!rRH!kE3?E' - '0kz8>ZCzBsz>br~c?*O3MUrsJ6+MjDf@am+xmWIsL#k(J)=IX=6j{*4Y@XaPYm(|{%{VImnlWxka>-Q?o*{P^_9Jvuo>=OK*gH|wb-?antFpSgzc@0>mG%&}g@h>HK80|n' - '1p!c!PU#0Y;@NCg@T9px*#TS+I_;j_YcZ?c?RQ&&D@v*`)~WNn3AM+8jaKP|!B2rmbH_n`UhtUeBU>-BD@ZXb^i;uh%Df9m^?!N5M;<*F#7EuQw1x;F@*EDhJgcSI+ew$sEnt_wh|}t$ZA_d;%%p6<+?t$' - 'vbz0lmpbo_%7+OLFt=EMXBj2~QEA&K>E}ktaFJdn@ga>b)eJf<~e&*8jEFA+Rw;Yr?QgHSWB8#' - 't~|E2lfo9UyHt!F*feJQy>6*;l}{^lK_d&S=U_p9Cl&}(Js6CHZVi4|xB3N9?yxpMtOkR7p' - 'U`Pz?k@POzT>|$q(`qTd&5fH=A+guCswqP2VUG{V(I_un-N*AFH|OMYi!fD#;Q6LRJ5L+zoZohK83vo3)_|M-L83s<+q6)4^TJd<60rZmNWgjaC^OTmErCF9#OGqAW?!3&^?9{2dwZs)O}!qUGz$Tr%a;r^' - 'PuUsC;cHc{F7TRzkqpwhRl%BA=dF-yUo0?K&+1e+3)bNp*tbNmJlfz>`NXx~-y8sShs0XP>1{Sr&Vc0a*iNZc5lh-tR%Iju#lLQC' - '%^Hd^Al70m>-)yQxwdM+%|+MlmGVF=*jJf>L1tN>&7*B&K|TWOedp9l3#)`>vBWU5^IlR5)*LaNsaOjT8y8y~(X+-?dpaU}&z0s9' - 'qdD3%zfeYV$d{kXmzj)-NRKarTaGY=`Q>ok7+S(%_2p7)er|m<>8c=@qDq?ryt2Ayx;-W(?#tDPT=;o(B2nnVglR}n<;q2V5^(fU' - ')wPFQPkS=D%n#Y4j}l#Q#yH78=z{$k6-}w(S3dvYp7$#F@1A>' - '7JYsq>N+rcmMUvk=(+C6CA`X|SFuG|H9dCz8Pj87ZAQIkoF4Ppu|3*4JML<>4kc3HnAolTXKe)1F0tJ2EYJ2KAjWnx?LVK_a~poe' - 'dPa7_$E+pK**T|2+SsCPtK7aw6|}RgE#Ez$&4Jub)yB4bJ4b=Y?HW{4D-w6i_1(=hYq(B0t`obSTcY0q0$-t0fO2b`I)t2L#Ij;_' - 'A2!e2ur%&iMwhvDuQEtHo94AG^A(C{h=rYMG%O99eA^F~J1Eyg5%AG$FQ(y@?I$@AXL&Aiwz(VDf`{A;>&mV$5wo0~R3;bhZ&J#0' - 'rIaEtF6+V0H&SYpj}NiA_Ht;vy)xcj8*j)E{na*JC%M%%f#sQyj~ic{IC%5$4Ncs=C+N7oG^u{H^*zi0K2gt' - 'Vyf+}n>=_c-5m2I8ZugAZug!HYxIq*!$>7xHichsKUg$3T*nRN4W#Y!(B3KiOK-3tQZips)!U;_MoIlz)' - 'u;_94RC^VL3uvK|UK@4%RL2A;q~D^n8|)k3?Cfpnhqvh+J|*d5;eV;mngr_*6LU;}oS>|5D`01381_vBtYaz;iDqvaBUR}WDj?E#$XWulj8*@JVVVOW_QrJA18{eWf1zPpqPnHShN!aOrIOg' - 'Ce;bybA!I{calOJJzzV;tU-;J-u?9gM55JhW5)lg(F8>~s8;~XzYF#HPUu2P8HOzl9thw+v&{4M91lj2M3#{fh^c%HO&SC~m$^ns' - 'Ypp%L)|C9-K(9*IvI7H{jK84Ne)c7Z)3vuGViHnLeBuNpZ7x1MMZjtH7t|vSEj-4;4-Y)@a|h{N?E42xKLsjf_8u98Hv5hDk_nb^' - 'J3=QQ7;wvv@zhBg@KX%@3Fd2n-!0SVhq4U-4MU4R99Ew%qt0j&rsORz6CQC6Um|8xR2=C^?`;jAu(C1stm&' - 'eFzey6NIu@&+#ZkTe7icYGljQpYp-B!BDlS0gi)4rcvDWkQBzHQbqgzq>>|}2h54gP#R~>m*+ux0W>!AFFDK6TA?)saL|sA!$#9=' - 'of#hXQh^gyaTH{=4Yb>dpNbO|xy;Y$tl9QfXapNsQ~Za%A!q7ZTucp)Y*W5+oSUlW61GjrwIiRVvs$58q>k>vTP5QhbI~9y{XBbl' - '^@Wc|m=eil`Mwjk#qYV~I`3y2-vwdc$9?k5=4TDB^6%Fb-{-~eTzCd&n(Sw04KW4y1q)a0RZ!K+WZ;%&n-oJZs9HUQ3EX4w6$MWj' - '*=1q@zj6;IUa8PBiaxJbSwJcOsi=b@0u?B=SoVR(O3g8SFWbxQz&Pe}35m8d1d%^cQfgfOaIwUa;Nv^Y?Zt~Rgs;)TtA;`nJ_4wy{|{#iA6)9Y|`o<~W85NcLv' - 'HA!meMr(AfFIlJEgYm#2F<@!A4#jXXR~+9qN)1GObk14J3ab_1IEwMuC<6nkC>!3)R2Uc6v-q5e<2' - '1&}W=+Nri8sj_-buzUzgBh9_yhJn#7^XF!0YS@5Q`Ot2a*((TNul@AF_d|$k5?;bPa_4S{#uIl0`96n+;gh6UdVwEAWn' - 'kgGh@0d@H~$SJM4@_eyQx7V$1;mHngUE3Lm78c;e9q8kVTrUr+vlR=qnvyNDs3nzb?Lw`q@bv`wXlE5)tEa5|QZ+hOPpWyg9K+Z6' - '-s^iY@La(ByIh_>bQ-$JeFI&D_bhtVPVf)a5GoxjItBUd^OhX' - 'wmjz-@i#C>9k*2r`kR>~HWhY-dAR!s5&wGA;J0=^sB==8tkfOevy&2|q?JYfAya}eVm{;V|H8QKp&$88jE~=;A#xjNN5*euk=!EM0PlQvGbFc=HbT?&cRR%V' - '_R?Q0!n4gH{Jwpts??YN&-$5#Punz9?r!aD!?x;x-1*8w+{a%$$=FJZtFvhOB8&Xw!*L^tntgyBYKfAJ=pKyQ4wE7tjUA&3O#nR~u`3)HTM?Ht5s<%I?qAg&2' - 'eVyf&l&RIMcK5i5qt#t>9n6FDF&`**+LqNf!d;xF8Q~&)u=|b??H%yUNIh5sKtm(m!e-9M2$rx}Z(u|^JmpD`SfEn3+cSJV&I7xK' - '!#2Kr5Y23BV0cUT(j6M!PLal41-7vaV-H{~$1*H7=JfOoiwhVIhK8BKBxGZVcPPacMixxQeRqE61)=LDUY`AZr#%>UMuq@v3p%t7' - '5m;6i*gG&}U|D!zePjq>)B~P(M}{0MXV4z@dWIk@r`sOcjv)yfy69$9a9LPR9~eC{gkkk8=)^X}VRc88LZ}PG8VuUqe&3LY1@!g@' - 'jv*6k*l!OEsaTdp*!B}A8Rxp?`Oi(Aa*Y!bU3w)>eaM7p|dCo_qNh10e&' - '%gC{^G6M>g$BT#f9kJ&FyAJDJJF;(mtC@5J9h' - 'hCOPHHR@*F{YKPvvi5&u%zF|$*opt=8aMQC`W}V;T_o1?^v9=9EDD#8?BUK2' - ';XRKf6bA_b^FzGXZP8|DQFtKx3g&nR-7H%' - 'aK*iE&>)H8)L%3bKP|T7E0DoWvE9FjO?H0Ci^e!@Uj(mG^x1C+Y!b&&Y%br{aSvMb$nMuk%I-Yez2=*^bmv+J`3vq3xEcJ3X*^zX' - 'V3F9OHQo^S$Zow=(q?sY=cN8RO2_P*Agn;tQ(fCy5@JF9Sd2B@;fv1x{VILLoy&&QoMwwH&_@_2s@T50(1' - 'ti56nu(hDi1|d)ZuYcs=+Cf4*ZZuI_jPInH`i4}c0B-81LY=@V#oiqE9!Rl#Z4`dvfS>B-nzmrlRMI&kW#hcXtATi{Z2V3!=1~wZ' - 'wPn03yeWqzHub<(xOXVgymbEI$5GPAi$~K%Q&wHOsl%JXSC6Z&0RdVG)DR;}UxODd!Jf%j&Pt=M=`hH*YaWCAy5=#+scRmC9J=N)' - '$eC*%gB-c$G02H)9)ldX<}%26Yc7Kvx8^d)X=^Tn9Jb~%$XRPHgB-QyGRRG9E`waO<}=7WYd(Wqv*t6%Eo(l5T(agfXilTeO5AQ5' - ';bzshFJtzZ2gsjW>`PPZi)K=g+pCsZW<3b$Y3kJ1R7pk(|3zVyrb9zbzeala(J?}$Y&%A4PEm-h' - 'M@U^g+;TKcvwC`HA`yG95lAt}KvTYKR`Q45Cl6i3%SVk72NZy#@|_+dgB80oMt8<)c5IE+EZ82asd0C#=EBZcO>BE(H8t*!)$DjM' - 'RukmmSWS&bV>Ms4uwyw@wyU!bA1-D$B' - '0aL!+toHWsoyOR!5AP<(>ht#(;td*V*`^iH+cXDnT55fHm+n`ro{!m;UzEW{tRA58IY`MSrrkb^|m^SxxIozwu$%xotWN>0^o;FPjc2qk&jpK%(-%wkax' - 'kt@?25qd8LpNtJahL=}|XUB&Z$L`_P`CIqx;pMNG##bp+A@%G6LMm&>j(nI{0AII=9vUCeMTHPM;l099T%1(kDNsB;=@_EV>W_Is' - 'lIlYwGg0)1`Ne$E1|!i&ZNrpwpdfN7V<}lBSaHMT%ff|=(`p$jk(A4qU``dsW;1n}C1Sz*EJFDS_x3I0HcU~ZTcv$D`-13Zd%' - 'fmUufvGeb66Epb_?YE`_yGaI8|Rb4FOI^aS(Dj)0Db5C>G_98cre}%!vr~H(sr?qNerDjd2y~v|6nacZ%{qh@C' - 'jpRK^5Wj9TXsH*|R#tSNnNCWVBNlMAE4C-Fxj9-HlctI;e4Rpt&)~Qg?mcNcAc*d*ATRV`UvqK' - '&39_Iq(*u{@K>GZvD`Y)(Ojc2Up53T7QVxwU-15`yz}gT{MsR;(%_btmrxnQd&Xxzp|gP!(|Y9%iDGcz#9HnG1-gL' - 'yXZE80v9c*{|I^wRToj(@PP&n0~65rlLCyjjmCv8^wOqk_zynR=3s@A28Sy85w&7*uRTQ}oG6jvM)oJyM*RMV!EXovysts>veRr|' - '5loGvnBOc$d&0+i!#v4KXK|aY?e=gUg>z8f5LLKQf^{q3twO7*&5})b0!lu{7%2xvziL>!' - 'Wq2c8_90s^ggc}o+~r6YZ4-w+N6X=bsW@132L3d&D(|Az8U}?=Ch;JR4hRM2' - 'B}s4_iob_JXJZIKMqriE(M~jdSM)5m;(j!U)>7FvWx7@5erMi?`wihGI1TeC!h>9ZG*4)+ykFadZ8|D3V&ulB76xYB&I%0xX}y7@' - 'K}_AUhUO~cMx#+SF_36spr8@H324YsVSS&{C+?LXO}>&z*@`&m_eYUeK1Lk+CBZ?wD>ui&' - 'fYkNrXuRUL?D$8S3sdI|KV)=~mHV<@N4Zs6s*l@)jRSW(y!_#$T5V(SB?2_6B8)x$bv8UFvJMvsrw0El+R|HrBU^%uXh)F(JajQK' - '7eYT*lGSr>Je==o)q9vtKp~$x!Kba2WijBZb+BCU9rPC8sBh(tAwM#h$0RtJEUwRCq-&WAbOcIJwKYnn2wV?iX)1_V{160w-sU?S' - '?{->9m@+ziSACZKpSiaD96_;XtRs8bU!TdHMB%%1r91Fi!{R18jE^)3*S={==mHNHQ@+Y>_LyoA3pPGO{MtbLdLP8|$cx4~mavyE' - 'jh@D(=A)EADp-2SoeO#wVajDj@=qH`zRpMnCsD8z#lx7|I4s>2@Nz6tv8Tdqf2h}GeQblOkNEv?wS23yKe$RH30!T@bHsSiwaiFt' - 'n-wZm(gv)U;^mG#H;aw0cW7*vG$si`E2@+oGu`A>*b82nTh&^D(>Y*2eJ?O^+W^i$mh1Zvxn6Q=wOSuaTzI2e+_~B%Mqrb!Yk1Dc' - '2R!)usdWwjRV@Gzer!wIJU_*9}i^#N|U{i5f3@$}L%fd-3kCgWEMOZc?!S4C`j{f$lB_Pu|$HkDU|uL2X)m$tg{KByF-LTNie!6ms8ADsCK+R;%JZlCn}Y3#n3Vq5qyogn+DJV&KaB' - 'y$1iRk5w8AP$wG-$3DfZg2V&L6qEa;GP{)2UtNGjT3NW&t6p#acN7%zOQymXQznZ1bBG)-ME;1fx6a(N^NY7MD{^`xOME?2Uwd}X' - 'HkZT;YonY)+j3NReOw#o_2q>nZ%dOEjENB4m81Q+McR{9ISRX_AM83rw`L;bR+Ybr3_N;fI9X=kWrG07k{hd29V%tAa;w&9' - '8VfQii)YrX6=2Yx_bkcJ5PG7`+4*f#`ShJCG4lmsF2G&HY&lZQ-N7a?=jOZO+AHn0T(!hs&63?su4!}ge+E?MLQcmA7Oy$0;IQTF' - '%hrp@C8d~QS`gp{BMhH_IWM1QY-O00;m803iUv*^dJa1pok8' - '4*&o;0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUvp(~b!BdJX<=+%WMOn+V{Bn_b1raswO37V<2Dez>sPQk$qPkD' - 'doj~i|(OY^kNt?EsbMtC{jz(**Mw!_s)>|wqmsCHiyKPhHu`?yctg7IR1q@UPx{&v&XvD#<9INj7xE+pkxNDD35R@;Q=Ju' - '3y`JF;y8|?z0noR^Zn5s4dgi!l|Rgt(vCZ!m2GVJu4rH0{Pc5*->oyea22?NE`v>>rG&zt$_Dng4h-K(2u5Yg6l`noo(QG=o3x>r' - 'BJo=CqB-eKwNQ5*3w27_N09p{YM!WLRi7BQOw|qP3~aU6viu>hgzfkgS{2;odiM#*q7y4c0XbKv=1~;gsuCXAiV5Y|S0;sZYy7yu' - '&)ydQsyb68H-?|0C@Nvka$$1|pO2uN;4<+6EJI{#q`FzKs~_1;Yq^XV{usc7EB_$aW|&A$8OZLyC2@c%AWqqPE-fq;t)KQy&L4$+' - '#SfAVZpbFYanVa&iV8hlqZc|Q_TeCE0FScdI%VH40-)#4g$4FOyW6UkP+`PSzB5LfB>vcC;7pMCVc?BDaCt-pJb}@uwvuTDS-cqL' - 'hA4DF`x5yxiT}L}^%qpACG+)4{fQ+7>yv;6%TYM16}uip5xh)`AK)A0z{i`En8x*)F}1*bYwTOjKcTF7oXs#G|0K(EpO!pN0&Z7W' - 'fo;-PSAx282Rn*(9ADd3UYeI8JyGQu0tT=U6riZ4CcbA9GW1c*1w?G$c8!|gi6>C2KMlPJ<(`p1f)k_B4-TE*0P4Qin^x~e6_*-&' - 'oHKjirk`94B?HRhWXU3rbAz(l1%Qg1`~G|b_eHn^+iqLAub%skS~8;hvfXALX}cZja=S&m6Wc*hrXQl{++eXg{W5%ra&2DrL(jwM' - 'eMiM-&SvQ0lNKs>`W_VGV|{8Ge`uhYI{rr7oGS5' - '7VgL;g?gz0)7iPZSe!ee77UQzt-3s)oAq<_FTxMwbJofejei%m;;uLl(N2efVqdT&d+syzlCk&#;Ja1=&n?Lpx^4>)5A-m^@G_6E' - '2y%Nn*#UY9BC(>mOkrlJ=W6pM;cklD>G~3{{Up!TME=+wsldNzke=VsTT~^5h%2^X?B(TjHK%PUD#}=A^W#@bwdv`Op(x' - '68`&!gdl9&w225el%L%+dV{MD?pWiL0vzt8DBK#en$pDIAl&oIw`|P+z5B@Xdq@%f0?' - '7cz3#TpV4I;)A<`(YWAUbs4RK9tPQ&U`>2Z(J1J_w7xC~&BoFC4&o8+^vojVuV6p+@HoaCg8UuFMqXB2Ifz1w7Z@H38' - 'fU(-u)@cN1x!{(gS>}13Wvx~nR8{LdSXIEgUTI6aN@bZRn=DHN)H}O^jZ$I(e;miODB#Oi;L_5jH%j6v1G;#7' - 'Oblj!X(Y28Rb#onT3-yTxbY{>-!FdO{JOr*MyNk_lmu#BS$wrK`cPDX5&4`6fz3LuE<7Bfr{cvUBbzD}sLQw%{?PPVxt$N{hTZJv^E!NMoK%V!iN6GY)S%pRl#*ZYWoG+O$7(GVv' - 'P63pESJ3d<-cvjaAJk*go&5YOx|uBs@ge^eYiHR;Q2WM~J>kyh&o85Zqd+4GRo4r&dlm1=>oFFVM)QZ0z@S!L=O^6XT-L%hI3!JD' - 'Ps%h*H&J84p=DeUlx92i0TqYB`e-Pon!PQ*mtf^iZE^vsBtXsFa$A6M-~u%' - '$AOXk2T)4`1QY-O00;m803iUf73B0Z0RRAN0{{Rj0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUvznJWpgfYd3}=0' - 'Zi6roMfdX+Bd;qK-Di>JAyq0B3d^dpCSX!4*hcnHDSux`;?xbKtjxW~^YREGN~V3Q(2`N1qe@}pJZ{Q->)QGmwe5PwP<<&w$fr()' - 'Ob7wccxO<`rsv)(DN&owc}B9>fC0i;d2FG+0-X^)K76@zrhNy2d~7Isgh<+G' - '(ilo1j%N%AEq2>Y8MGy|$>ii$wptvJUzWgi&=?onT_ESxGs2*n3;%`^4)!VrMQ3pJcWelBKN##&9{UVcV^j#$lMCfnj`^%F' - '_yn4(H4P55;`+$(Jwm4qiZhx4eb_QVV`7`~jKy=ACBGWMwGG6)V=iSu4FeA)e=J2&6hd^~^&n*z(-@>o8Js7MwQfox#N^X%KDjX7' - 'YZa;71dn+7w7A9y9)CidJ|0<&gVBm}&K*~)6ZT0E_yw;9^2nZ34!-xdvFp(Pd0~#@TiTUJ{7IkgZ&g4c#D@jtCKyLyfAkSIB75L2' - '+9q{(So*94@WV4XfXATQh&!(=tGSK>Vo1$6i@Ta@w4(+=|3GM*^DKrURz1%(B{QlCV|$x?W#(jsM(w`Vl6#p?#-Bt;hRCZOpy' - 'uzL^LOfyJ&Q+}OB&Avg?DHLO^+Wl<=SN?-$FiT?CH$Ro0V5egI3BFm+}5bXJcP85dT*u' - '76zN<{BMxC9_(IWz#DkW(n`ro&C9xn$Kr1#i!BuMUd-otdy13z4Nyx11QY-O00;m803iVIbu%7nN&o;JK>+|Q0001HVRLkFY;AKd' - 'ZEs{{Y;!MVb8TjCY-BPoZEs{{Y-w(1UuAP`W^Zg{GA?j=?S1=u+sKjN@A@kc=DrKyfiRXl@y#lWe5J4}T5^(IpALoyP{Nud' - 'I0R`)_UeDXdi1*+ASF*Wx$kbi!~%it>Q{AjRdqc^qtWTzdAl56M@MHTui9y~AI;jTo*qQ2>pJRI<-97FRkW<9b=lQbR6@~aRgYKe' - 'Wu5Kp{Oh_Ie}YoW)xp8eP6Yo=mhCKBEz5Z~X_vEl*+tE4(JogC{t1HTveZ(P+n+LoP%M@;0BPoR6^|~P_IiE!y(~3Kvhlhq<0Pv;' - 'm(w^QQ0=0g$D{S-&)K579Hr6d28!J*p{p^qk~Q;5y#%oBs&3{**%eSO9-aK_@vA@o>HPKiAO3WnW!Wf6c6MI1%V@TquA1HPHS{2j' - 'rp*;hLmI(9(aG6i)XW#_RhLHVu8ux_l;GiAvASE--N%p7s=caV{+2s8&FWf}nLzJjXeEIX(1v^sLtT{1y69HRHO*^5z(mPxXEYk^' - '?9j9p#biwd3OT9eeBQ3gRnyM9ogMM+YAm1l&$PMpUSzX+RaWJylwW4$>Y5uFx6>)CFM`a<%du?fq+BeT`IRg)U)8q=NPZS?X63xR' - 'f^kzRgg>5^T~{k4D7&f|uTuSqOD>>w1K6qR3`{6AD&HT@@6zZ6R{L|gOrzJZy32B!M&q(uso}2>o2?(&d=3pq^SS#ro2=(F(sGIg' - 'UWqnoJBXs^^}K7vcve4Gv#eaNTKN%Q?05*D(Xv9fi*~(2Oi@#a@88n9p<3%zGgWm?m-RXC1F>(YBA`aOTGeyxyDSM9SHR180l%m9' - '3_cQGq)`VSV0k9ZRp|T9&T4sgpttS>=5jHeiT-riY9-ModIg_OU!R=U-RiuValfF$C(SMHlJoQP7u3p5eLJofG~D&He$%dAwd;9x' - 'v|Iwt!z|bMzzsGN{*}$kSsnc$C_hk5QJUv$y`*U>>TWh6^5|6M7Sd=A%UDjEFEy4N7fmIg' - ')U(UFsthES6_4ZxvrX9wE*KKuRfpi7$+|Jk!`aclzCU{N;;8uP?OAdD_D@G|Qv3Z;&-;_Zvp)qtoF2XmzCSxZeDm_~-OJ+e5Gwz5' - 'bD85e!7_h-`{MAYbSDv`RH$wYcQ(_@!oz&=$HRAAvp75c_ag?0y*NC7dvg4uc=P_`r=xe`_1R(Z;_aK4$LGgy-yFaBLvj4(^!<79' - '=J4d`j7vc2;@uI<%fFqLtH>UCyu8K=5d9o?SOH)tl3@S^lMNuC7;gb@&;`' - ')D=MEuRsCn<>?Z3$9Q#8mvesmuI`Sj+r#c|c3hp|KSzL&t2_KVZYx#!U3;UD^|WoL>ciPz4xhbz*Pb4!&*J?}^M}{(PhOw$&+}yi' - 'eV*1Qc)%^D%^0xzbNzk+1i9uC1-z=S+9iCN7Bkg9Tq2kNJe+`R6|44B4Wy*H<*)MNbXHt9Fh6rR%>YctU$06$3=25tfC9ncK5pA(' - 'Wq)IMtCkAzmsxptSr>5ZOz(=U)fZhJ0cy;fPr%#hbTpPT#)%x09oH#qmpc`@_!8ucMds' - 'qy)@};7}q4a3#uI^Xf+K+%x~%7Lny>K9s!OuyEo{bRQ`OPu' - 'rUY1$38O<2;T9dxaKU^14hGwG(VKP-Gtov>)0LOgn%hT;9^-LUFGUBtYouM%=n_Wp_U*|oEGyIl2*CC%;KywPOjbR;i=IDynq)i0' - '%cEC^?_Zx6FaG%c&7X+G!74n5W!U)_%^uOe>dau~AvPF^gbzyK5zh@~Vi)%#EC}|NnGon5Vrg`FXYlf{9Q7?MT|?Z!louvr-u&Z7' - 'XnAGrW?);JjwX20Aj^c90MK9ubdsCMqQ6|%b9njl8Wx}yTk7LSizombWEbJAW@xnccWKu+;IK#2_J;cnMVSrgZ~UBO7D3Dbd10Np' - 'sA}PGUg1bvVA$0;T@f&Nx7zL632?5rk>(Fp#P$J?52*X`BTu2GB3%yW0#3)I1XhjU0CXUx%?g$mhP48WjM@qG4>~fZ!+(xTGObrN' - '@xGZ{HJ)s5st*L?8T}_?Urv9htTmM&rmX?$2QHl=V|Wp$qkFz(1weI>VV32rn6_8VsvGE!pu7fQA1G`t*yn94QK>~)5$PWU-Zj-4' - 'PpGQC9Vl1R$qI+pK$*p~s#co^PsKKZfh|Tu-B@s34isSox-B-fDa$n*J6G0QjIbO+#znOah^ii5wRZX$4$#eTRzow`41Y#?QIJ62' - '6$3?gcB*<36{1syOfF~u60T)C?P4+2$owC;!T^1H!hby4aS&Ys-zJ*1$~ZgQE5_8MKndFUnnb(57aJSjr}XvWz@A^-1sH6suF4d~1v@c&O*PyQwN=^)ZjV0jI_wlU~fBh18USX7CA@Of96LrL_j&pawxQ3uGXa' - '(KN)kw9JndN{MlHvY4(r{QqH9E`S-1=kts;KY}$SWPlMhtD=azdOAs^hFO=yxyRmX@&J1eU?rKVndnadBa^D*nbs!%3-H)bX?3TX' - '<;_5iCI;o%yuu(fh!c1Ee#7Q2>L-qBSDP3*cZu2D-W?!qmsEYg%@AdYmZ^(H?l#*TcZOM2`!ayD67dY(dl*!&uj7' - 'UQB;dy&B-bQUffo|MPL(%VO!G8;`!e8a)T9SG@1g^a@H9Mu!X!70e?%3v>ys;xo&p^*m`O!wdxw9Y?m4XDAx+6JpRn|Es%=&WXz2DzB;' - 'zJs%_A`V7SASERW$Pq)%Mv2>U(>0_H9@nuw?cyqf_gLchB{VD;ZT~Ql{BI408;76cPca`6E}Kv_v;1k|l3oTU8HH&JqMcOyqe@tw' - 'C*RGdi>e;x' - '7XbOIJ}ljtA02p$!9%>45{A=P+>^M!?0xC<;loJ27-9Pk2#saT6fl2huYh+$?F>By-pgrJOzq%JTx>L(TKLShf6eMGXlY71N4lVh' - '^ip)uqM1ji9Nr~WE6^3z2}cO#63@KPw3WtJ7!u@Sz^BN#(}l!afzM6;li{#-zvx' - 'OjH^yFkh?dgK2pOjFG@-IQXJo)i(>`8X;*bE8=uH0rIlidrsJ5cau0>DlPUskfCkRlsgN^X|9+4OzmI0#+AKOpUhZsy{!;KDim%dOdGBz6Y+#-)+W*0(F#' - '%9&nEFxZm5LuTs3%Q12aKW#?)V2dA!EM#=sXbS?#g`ZU+zROT6W(i5gOy^EUe9`cfS*S#%hE' - 'nzuK6i13$1+chiPFK{%K)0^_Hlgk7|7yGQvF1(GwgHCoC8*S#|{zMvMw6iJR!sr_Yz97M+EF(GiOAsxA^x0PokLCxId5FMTG|ur3' - '`>ei&c?CQ_2=Mh{0l$iU&(7LK4S9sw3q0Y*NmeDJXJXw9' - '=P=*P?>YXRES~N2-ycU8)~$TZd^KwzqHfx*SR0Y(>w4Y98F2UO&N!}EXU@Z3sE#1{' - '$MXQzrb1l|T9;O6buhke@w%f~X=R{N9MFkrT?d$N%1&q|ElF7ZHjEF8dVU4GR`D5F6{*b>=dM;BmL2J5SeuGRv~?hh;uoHpl`THG' - 'mShk5n~n*xhIil}K?1IC%9=?SBLn^}!xvOUoIQcMr+`4^m2Iu)wQ1fE5)bMRBsRDgx;8-hK`s7ksAOt>qa&&mP0KGp^w8K~+%9X;' - 'KfJtx6R@fmAOZqP-0e=5b*-_$nw1r@o_bvTgv4k)t*r?W{md43B&du`<;=KQJQNHY?qyupp5^$MYSUDv^1M&2gDi0jLbqXfC!@dw' - 'j@$K=b!(S$`fx9NMW#Kb2Jk8Y%}k5p9^TWddxKaolR&Q}c|&N|We0-zGUo9CF?faIorQY?|rEA?#*VXjWoKs' - 'ZqO{bb1yQH7&J>|BBLzR1ciBZ`Xe(&J(Lk|n0Rz*ch&`vykcA~%1dA}R(EkoiRN87BUkP++5BD8>hNQ)-;G!JTFGdcxh-?L}~?pkNOppuJ=65ivC}4I%n{9{m$?6_LZvN>+K-!-w`=N(}|c%' - 'y2)NrY|flHQ5)w5l@&}i7zr24_OfO{fXD$hJp$yweWI2ecm+HlltzLpdF;G31{3%rsCS0ENs_re(Ta@_B|d}^G?(jD{ZPsrTFk&+' - 'zD8e*7WfwuMrmi`2px>Fm(raR*t=+knse1y+z&BJBUmkGjORcdjzayl0NI;q`!i|tsMR=oH;{3O!@WwGei~avtsrz*T3A5A4^J-=' - '<7ROMg-~-%=MsJ2yCBnmX+0NjlIZu*9_>k0g7uxFQ9y;&(`=y4kwp$vy}p?e^5n_uAM>UMAwofLJCV42WZskrqEleE`Ud' - 'n%;I2eeH;YiUpA5K8>yb>{rza2xd~PU^Na6_jLH)L=qtb<)VxY1GU`*LfR*Z4@P~Y1D1Y*7V{7)&B|3XHsq>`E!sO2rSSIPcX+#w' - 'Jb}Gr`EOu0f8+92#svseZx=MN@U)SiaX2n-n$!@z%hzb6h0`0LMBLlDGA?60R?gTi0fdSjf++fsJ4V{>;^gwd{Y*5N|Ja{|-rUgi' - '+u!@ezI;$5bT9c8rARd<^m47qCW7TN?2}OtH*5y)05=0Rem}DK>A?jUoP0I$w6W8j4KqG8+ut_Rin`HxHJvhblPZ>2+rS-=N9Kl$' - 'd11+?W;1X(&+@)W979GrTV2d+UW+&|rRK?*T+)#kn^s@hbXMTKBVRb8eDUMcNGd0dGL=v?LV^9?K7X1F&{b`-iU$&)jWQ&Su;0o}' - '1yN#oWU4+5>pk-(by' - 'Hq@o9$K@^pVv-4Xd%Y>!5)v$l179uB_k5hgQHZWjjA_D%nv0Jdh+oZ)+-K2A``M^;Sx-!Q3Xu8S' - '`E41Ds&VCgqw)?j3mJ^B`UXBV?%nO8nx;C=Szfr_(XLMAnW#nlopJ|>nyT)qL<$eI>HVdWu5f*ZNVft;ZxHoVn-G}MtUJltkN@GRt(7rQ~k' - 'hd{UIRn3MJ`gT>)&DapjEfx-$g`N1m*XrMm&`y^D{-7fB{o}<&?' - '?)y6~%(~3itJR9)6rcr-?e$kJ+Xc%<4f1ci3TVo#WS%t&e#&npRoqT391MEAnV@@<=GKsWcyZ+Iqib*I$ugtS=;(GlT~{?;j-aGU' - 'Wa0bs^K;QF;qs<8RKPw|U#g%W!}U_9EBb1frhq%nu5Ca7_~$*%Og2JIsCs40s+40;&iPc|k3' - '2)nOgeSqp0S|LX)4NI((z30ToAk9a3U2KT0AefqW4Iv<4-tfID8`n$5pFu97~#Qhtr-cP2j6}cXg@fN@^Y?1VR)`@c;k<}$s2$beJ4V(M3cI%' - 'c7-dJ@L7lbdI|gRlQh>21vfA@ujf!F8CKY8D>C-PF2T%GP!4*F0X`n#W{2k*9&htZ4I~zgIVC3(#2OWCMwaq=lRLlD=&~uh++fHQ' - 'NFyKzk3lJg0!@w2hUI#RA@$=meHqH{qSAs7`lo!KmhsCqHrEmseu3zd7E|D<)X$bnt#syk%z9|cAVF&)VcqA8Fg' - '5#|9|*bf&wrcGQ&c6a6cs*azgMi)}A0}k!Q37Iv}G9a{D)C6Rk?RBW9yZvwg;1B8x*I6tp7ii-W2Zq&6lf+YU3tdaOilH()8SUv!' - 'NlqGMLxtg-LYoq35FQ*|w0QmU<#`&N9v;CzFOO11YIZSP1KsDzTWVAlsi+u$vvcD(y+o6t63v7q9`x?iaKwOz?acADcJv;r!P3#I' - 'X&@{J2JX=%cuFe5mj(QdTokN|0~U0kjw6PS;YZfcYo' - '?K;&EE`p>Hp}BBDVc3|GslaQo*Mn3{O5!-(c14&F@{A^KVV)lp>(nA(ML0;xWh|#JRckar0tv{M-ul?rjkTGhCCj33g9FOR3VGDWW!WT@VH#zkqXVbfzR7BfCacX&>hL{VtlO`neR4n$N&$}e6U86abES+@UoVoZr={?UK41~|lf-5h?Kptq' - 's5YI6H}H$rZpi`=l_Tdb$))82UOWL>wFW=}xMs7pX)yeo83qY6*WM6d>?U}' - 'iADify`&(1aj;GrJss}+WJ=VrkTJ+^n=anSi{EB*;25z#KiI78WlC|MVeGIH$geUp8)Cq52=p<&jGh>^JXZB~>*yQuEb)VHeDPo1' - 'qIg~T?=?F^LDQP*)-S^$X2KCY%nKZ-kBVt5-Sc@PjP7n`!~jW+3)N}C?)UDOeze2Dm8R%FTDLyakHHR|pK`I=M4s22PNstc?f)MpB5GDu5k{b))776F^^w96bAs|FR6`UY7rE*D_EEKbFAFM(fyF#UrfDD3jV%cm0' - 'im`@)`t^bI>0}?o3|&uSllAOAg<*#1>c)N&myE931Vd_^aCwC$<>|$aH7JgjiB<=}pvtkau&JH}dtz|(XosxD^)bG3uN7rusr4)a' - '8KHVFA5|?7hyugS&1P&fupUpFikb+SDLN8=9>rFNjLo{chepga&xV|zb!%WOn^_K@-K7zmg8qCrs0fJdZa5pO5NnKojO~2ECKqvn' - 'C%*V3x4Haeia4go6kWqABNv3)UDRSz&Vq;-pm{l+-bK}V$#G&Q^^*Mmy4HYIm*ZiJc91rrw?&vzHoa$IYb' - 'a{0M!09DGl3FsCmWrW5dJWpugJ1%u75B!gkRxn-wx>Cji?5#3&s^@4RRZ%!Qw3))!GE9PPYvSk*X>zN2x+>)kvRK%dg%2v_M6y!L' - 'Vy|{qR#g#euyPp^D-M_bt`BXgm+2R?MQ^y#0I`~oxsD=T3IjA>P_9@A(VNwZavn=@#G$Dn5G-9;QgW=n8vWzw?6BwA>>wDjJ??GL' - 'tgW82@f6SiWvbj0s}+&5b$2%`AlqH$8Xpu~eXyCqb`?pgVPnExA7xoKPVt}1-QU9>OzvXMODbDS9C$M7F3p4!(>^wvM1P-hxyqkr' - 'PZ7DRd@p-yiMb*=+7$L0MNZ&_Q>t#KUFfwM1dF4#0^O(hJ(1bCy|AqV_aJ2J!28gig3Pgou>keP?t32xcyr1}>QMpDIrX>aGwRxWCpQ9gpdNYCZ+){DI7NjRupPxiA' - '5H29ZY8n!|+X)=!<*mK;yw;&8ijMY_X~PYiVzm>LQnr?n>zM2F!M3XF=@d$Rs^h@0(r6#(ZR$k2^owok*IIO+riC-Ei7Sdv595TEw`}@IFIpi|j|OrJT`byri=TOF*&jqS%~{@1Z4vuy5@u)GJ+LhhJqPuZgv%1XVB_Qs&!8f)s!!' - 'Mjy1OD+OMQ7zM=VV!zUJW*?qU0h-N`3!w#AKdt$-&=%M*BKmn~L5Hoc{XqBL}3LGSJC&U*>%|H)#{9sPOI9s%C!5oHh(hW_E' - '+r{Cd!Yw^~T9`dRv2fdI!#g-`1A|myqYg`jm6HskOo6~oP748D*zcpKd};L5*p-~dgYe5}*!W^8peAb>x!)JcrS63SH;A2PL==&8xpMb)DHxWoM$s%2V$)4Z|tGb%l&XK_c!!XQy8*e5dC$ji)^E3tLc@R' - 'lWu?ksyz@RVMiw3-3fiDg|u-Db;kP_ee@~`K>waQu;X4BeziT&O+Cfvz|xeYfs(R_VoO4W6q~02YH@j}+PG8|#${hoOD@F#xaRLo' - 'l^VBJGdvn~u3^XYA?KCi&+hxmtX=ihu4?|Ax?z_r&I-ynZ(KMln(kHa95-B|87ShS9u3QW6{vcz-MP_>xh{|`Kp(%DNcKTQdycL-' - '>FL09XQv{t6)-<=1_O(C=?PGLwioZx*F^b?Z{_CXc~RK$Ud==+6iAAd+Nd}G@#*FO9jv@QKZ>Sg(IT_i30Ec5u-Bh4$`MC|qB4YR' - 'c*DQ}L~4!@G=_7c6V2|Di4*&#)~lO#`H54_=@1ZFZBJ3Grp>Go@kst~baFz$CrE`)fPT~29;N;;vM3nkgj42e9}o0S5P5J^z-QEc' - 'jF&Y<=$F1CKd(!8_`?3G*nKk1?Btz;RPZ|yW4Da2G?rdq!sHm^fU&)?vV6EHr=Q~XW?nB%=NZ#0hC<5(>|F3@!KiI$8q-WG?Mgz>' - 'C~t68&?lJvIPvZqNV3pgD%n@8aX%8l0pJxg7x;D{=K?j5YCEY!B*Mgv0BA!_D?OP$183lpQ5wBqku9mE@s(GQ4@_t}gY#?xOS)dV' - 'nKtZVm`rrI9NBSkVSF?%_`?+lvpSBPEh-AFiNml2m>|1&fD5u2oxt_eyd~eAI53OOOv+})?c%P8&6bm3RbqHT(-3aSZ4GN#ZQ+Y#' - 'XsI_lqT2d&4m3t+o{BACKubcL8E->rOs}2dhjV+-<`fvtb#~Nirq#;%6}>!FJ0#l-C#`)zSVR@8kl~XPlH#N4+j5LCc)YL$!_D6h' - 'o)o?Z@`Mu9eziw=-y1lQ=#Vam5n_=&u5Ur+o|ji}U~r(&nc)g03dBw{lyHD3bqF3}4rCE6+>9P;oH`QT=D>;Qa^eO?dA4sjFyA%x' - 'v~t+e9rs>X-U*ASg+w0Y{qTmH;9y`D8!vTG%FXAG>36N_@Iw}gr0LUHv|d=WHvo9Q=%7$7OkI6qg>7IT%2h5~pkLV^|Lv!D$1nSW' - 'J?qJ<^~}jzbPjOF;Bq~P0Lil##^25SeaFcee4_J6iD+o&b++8VGjR3~Lz' - 'uqO`jB{)GP2T+QjmpUvCDxR+y)9um=o' - 'SHfwJQ%`W>6i$`WMH~nhV6-B^^a0m-5`FwgaSz&dftinE5s(aC8<7me*W`dZg&lz|BcCd!|jNl_Z~I1&E?dh-zw-Y6haW' - 'FOMRkq05L0WzlQg`s@_9sL=xb8rTN$1~_N*{u1kd!Xx`0$34MR50{;>%E3h9fL#AcHryXS9$?VZPUK7PqFClu2Y7E!PonR43@av`' - 'Up?VO5s3JdCL68t*iJlC+1j7~074uH91>TYeB=9{++^!&F|cc5Jwz^ZHID+=4d(Nv`1+Uiw7tRPAdDPHb))lmuP*&T_V&DFfc9HetTPu6qEUK~U(u==0N!j_-rQuVOmONZ&i(@kLk^kc;U4#%#o>gk9PGE^&joywZwozEzZHax;w6J54*eBadn3O98o?O{L4nh' - 'v7?n$=FW8Sa!c1qxg>_cuW26y)+F`-&>tPuuH^<5Q0USC4RdL0j`p^goGFlvH87Ye>&R>Ig3jKv>&Ch5u8J>)TNno=@}d#3afR^f' - '3WetzgDCXsZ75Dh`Y3?=vt6GaSo-lbi^nw%&&-1;VuSPJkP{K@JE5KFT=MGFhAx' - 'uo7dgQgqQI8HW6*AVd9K?1?utTF*JK(!_wErX2eIWr=gJOIr(V<|hCUamp9M&;Mx{BE$GeTs%K' - '(edFF`3V1u{Wwh}ilP*pI%b&(x#qxkozR62Y~yG(M5}k6{4@8-lWcVR-uH-TzBJ`@*;@c%+kyguSmq(W-cYS+dsUP}Zj7R`dA&~+' - 't9o4C(Fsd;psVYw`+2z{>#y&l-)2t(6{f3FR@ha#d=FfIKc&_7CMR07^ope)X_5vm9lS0jw#GojHB9>Uqo{vLx|Hzj)pxZ&cV=av' - 'Jk?`d$DuU#7#k?!06ZMPz#;<1J*l86o*R$RC3r?@}rTjEMmAkhFb5Puy_TlA;kV5LGUFPPQQK{Pr6nY=+HQ<}2T' - 'z4T)Sxg6xb)t!Nmf6O~J~ql1a{_#gL2@OHv?y9v}pxPK@7*0xc-w~6en+;DACS|-%e7ox;T%J`8Y`vvl#oE>yX' - 'TTZHifq3WPISb5N>g0Bjf!lP*;}B09x*gjxz^$WB3iN9uAB1s@;0D{%v7nVlff=w9b$#lT' - '@P$J-p=Iz$UDx_5&J<5T2#8z5g78B`;E=={2xe)zzR8iKs@LlC6^7ECqUtS9Y)9~o@z6E9N5$&dVs%%H%kg#X_Ah{odTIWKTO|(M' - 'sP;?Yl#AJY*BKG@gRxWt(P)i*2q%1pTnjD`I(%x9Zlk;k4yRNj7Vu30Era6>Wh-S-8v}-_O!U^<&l)I`x9f<;CPLwhP2>M&IW}U;rZXyBjnYm2S38fxBxl3w=KoFb(F0huDWBR`))Pbb^`;S3@Ljyoux8U1n^Un)*h9C!Ve!BHAUZulh-GF&r39J6pR8-OLKo9yBB@+`guRT28?l%uxXizQmE#d&d|MA>VUQWH#O5kB*M&UkV`^P*8KOE-e9gpakQs1;~>PkxMOHmk#7agE3)UW63S-r$i' - '`e#S~`u^z6i=*PtZ(kh#l<31&=AQRs6^phbqaU3sjc{xWnl#nIPjAnP^S6IGdUN6VtCW~*wkXFu<`bb;b%!R`F2' - '&-ZEB-lT4~B`(Q6960`MdK4e1`axt*#f2Iimx}w>J)kY^?YP}}i;%3`u+T_HITi%Aq)!K6fjtGEn#z;Ivp?}%I*=A+#n*ff0H)T(' - 'fn}H(GrUgNxb%?f0U~V0>lSXiVI(;_KYa7@@ZHPe@US>JeiK5NaM{DZ29b-|1l8jKCp@E!>w;&`%N(_;i+bFkZ#b;nx6Vqe&x7CR' - '8)hPj{=4VIDJ%c;hMDOdkTAX!{z`p^IeA5ThqIQw%nuj8*g;K6Z1tf>ov_#3KhSaER)?PfK' - 'LyFx~9;7_plWXR$W>WryU#RMEI7+1xnN6-lvj5iHg#OQX<($T=^v6V<{B1Lg$R3-E1pu@jK`WGJkztX<>oBz&J@&O8qyn*vuP' - 'la7fSBEHoj`$H!Y2xV=Y>W%tK!v`07hS+s1p7>`zKwCPz)g*8fA#@A' - ';1)ATjN`z>B|S54q(A<};=7MVqqC5KdZzX6haMeOpp}Jv?AWoV@JU$5^H;$Zz*hAAlXTW`p%>jni^3Hg2F(hV8S#zqDpo#AKk446_>T;x@3KRNwO+4!$0@iKeAkL=`$~=(()JY{' - 'v!|`=Ii-k8qbGYq*ddk0hQN&z%7tfdgX{Jt;z(;$zf!*KE$O-%&R|Ae0~$);I4P^o+HEdZ<=}4-q7X@+@-=4QTiwvHnj`Ac>V+4vQDEp+ACawXo1^>N_2n$(1B&UzPk3H' - 'o!*_JQ1hEEq8$o*d{@L7eT7|8CPZm?6ux#sj>;O+@e2A?gV=++6>aOfm2}MlE{hg!b!`L+mjC^{E-SPv980e*b9pI3Wy%o;=!&?D' - 'NHarb8-isXrUwoh;)rbJ4=c}EMTeGiLYM9;d@Kd2Ol*1fm&0c--?gVlY7PTFm0UOr>OKi_fG=g!zR>~@v^#%$_0uvOp;=E@!3=SS1uLCN2U4j9E`A%!m&v`K1z|4558EAK9AwH?BdUHp{{Z&zo){d-lW&Q!7Mt>)!tgHK0sh_j8b58ww7' - '*5L^yxeLdBly-S+kL&N0wC9&%ZZ?1q@;2Ml39&X?4#hNp9v+KPa$|xc5rT;IJkyz_sOi;i8rs{fv)71-jv#VW_3cBF+pOA8J(+T*' - 'B4+FYlSq>X1fVxY8r5fJy}WEeI_xuz?M*tWEDcNImT<>3;yPQHsJqAMFKctwqqO;h0JkeT!M3~gZ9QI-{&CPstw-51XuRg@RQe>(' - 'Z`Jem=y-{td3_Rz2t6!hak!_EnIbI^`UwN0h=W%`kEY0UUIHsd>8F~-6j{S)?|Ei8|5U)RVEoYM0dyc)*)kZVqdpw3WWdBc)2Z7`' - 'uRM?BO9bwT_w#c#i&yQcoaX2PTX*AJPpCA-28L=qZ&vvTTE41B39`hCWqSoZ==vfAb?6|UN1N$FzrkqQTSifj{vtAV`9N49gPQag' - 'Q}NV$OGxU{UxE>$koc1G@BZ=zRmzR$fljImO&D?>xV8*e4TorwE1Ru}&51+dl}JUziySiI*nZWAR(oKTdslbEs`jsD|0-^~dRwpB' - '16RvDW~-(f!yLomTNQ$(HY}XjQ(I;d^fIPNUqTL0OiqD8cSGaqNe8aJUBtT#;ED0dbE`DezL!v$YRD+Y7cvo-4df1Y4v9TVI1RKy1Kh{?L1g6Bhp4Zf1nK@qvjF*&4`(;_B-AgI>moWqwxw{rMD$nN>>Gn@^4' - 'zRbm=e<6nT$@-4k#(s;u8b%(;^}K+&rARh?%vKGGDKgTtbF<-x{s-Bi6rE?B~%RM09)t3x`d*uhp=mOP;rnM;`txXrlDu%' - '6vcJEJ&a<$okP`3g28k!fP8~-Sx9Z(fwWrJ5wS>MlF1|!SOH1r?t8;OE&(V9LZrm>F' - '%z(KP5uz0TLQx}^?E*os=g7aay(Jm}t;W}MpTwNnE+T>fO4jODXt{HB7f})`jBSH$M94>F=IXO6rfIaUFPMQTmeCZr)ifFfpsMSn&9$vcu-!0?8T<*{HyiMa1tzZ82x`Z;%!2+QW|S+k)nels12HXrHpqOrAczMGT;Ld`Lgy9oY(~2Q' - 'gDIPv1OD-DjJlRl8o25}_70mI^*VYY8(px?wb3(=-wfe~&m`hSn!u0#=#L?8|5Ttja`Y1O6yp!?C^U}@#%VJ6CR!GapLz6ECcq&Y' - 'd`Y84ZlOtPZ^~^VJaS8Ti3krn4xcok>&^hVb=}E~*#cn3exhBlwTi-RRc>-!H_VcAyLbrkks(f?4e8-sgJ!Rr^sJ2fJF3BlALiZVb!WBTEfgIFw<=Sf^7sz#8x2u?o1J#>>$P)vXx1UBTNhXJPB0HtJ%ip{|' - 'Dc&8O73cqUdUWRJo-!N3`y=m2wIql@n|%PKUkg?1J(EpprkF4*5hte(;9&aj|5<;F{+nM%KQ(2yON^9ObY~PGO;d0Tz=9seY*(B0' - '&%bzhq9)7EN8nYwu6kz^x8H;35-V*xlRC1DK(?vEIbkr%IOJx@>7UH%bMI)7j68TWM0@^jqrviKW@UrMhR1lzp*Jk5o%WOzQ8+A8' - 'A>a1WB|@&8|AdQ}6fwW9`T}yBR4}hT_c!O9q!OzG6s-5W+I0NIy^T`=dBXW&SPG1-db0$Uk8vP&4^jyu*>vtxa&?0V89XI79p5IT' - 'Ee~<}URM^jd9kEyK=zoBZ$cHw7ij-g#y9z~1(tfExFiyBeGq{pyk9nE;C0mlU$h!PT)y=w0EDtBd~dShTBY|ortf`Cfg*#S`mbov' - ')vx#RMn-Jx`)E&D2>1k}T82N=>|7iEIKIYP(eO92TOdnCEY$;@GCmaR&}0PCtM?R=NfG_eem?a;8A*gt4;2#j' - 'fLk=)Ga*}eF%B1WHPM^OtKiV)-Ey#$7vy(caVjAM7IIcDkPN++yIe+PwALvx{ney2RK{!i>_kh}WAQ' - 'V@((JE$S3~6&#Jc#We5~oqEGia9Xx1h~w4qr@Q8AR&J;vj8_$sb^T#-jN%UF4An5aA%+Vs+VQ7ar48AHj#n5rpj~dp^MaO0hGs}O98;-;IotdtvZ>3UmH8A#tn3+Nwd5UTj`=Xt8e$1(CAmD+ih`K4fi`lwTaHY`jPqXhu&2FFK{j`6{BYd`ZT{H$*;ABmQ>ZFNePS;%A2' - '_8yyLtH?l(Src!?o&VE|)P1#gp;T9T%hK<@yD&p=^!Kj%6vSop4?jGAdvg4u0JQ$;=$((x0qN3h#l1~(%Lki&@yEk=9R8y?JO1}0' - 'KMV3sNLgF8H*?^a>T(uws95cVo6MQ~4s+Gws{|x!m?*RYTX$y&PMAB{5mK_#3eN^0}B?EMm7&UuFDp4)Be)ZnbYttUN<}#y9ERn&)^nJ{>Y$c8f7TZhQ+iQ`F1y+dU3|17Q6-sX9k%@=x^YRVWdP4P>_gedj' - '?|q+@CNft~XW}d}lvB=7NqQqUzQx9~#P}qgp{e~Y*zQi?(%cd$RYX<1p$^93(wFMW^17dC^1#ao6A!cW-C)5xXuZ3I' - '?e^Bj&Zav(HU^gW?_k4_O2q2l0y&1o2Dg4vy}A&=Yu@iaR}~6qcc7&#G@UOc|8N!YUqRiNX$;' - 'j_x>^&od0D|H{+VMQ>xRVVciQlE5Y$TN>d7x`-z?|;E9YXzzdy|b@0@n@' - 'J#KD1JL6YZxVe4|UT)EXF6+fIl=~Z)%C?~p*ZO|BSkAgCkDq3HPm%Z0AGBws!*%!4Mw2S=LCDuHobAz~5+6qDsD%D8XFi$gFtbQ5DSIIr#F1+4wtX0*^IpE3bo{LI|?Du5-LJ|^}>emt)cA!g4jcVfM7LE8f' - 'oVmIH9rW|vmT*5xjrVvh=hpf%S3Qz%_HKUA8T8G}%@&InQiA27mm6R`#`!sXBlc}O<)OP|`yKL#-SP09@rYgV;2rVs-LTb8*m4&<' - 'Y6tu+u>Y69e3xyIc<$kF;xPrZ3h4aKUPI>1p1vx0N--kU2W9xM$0w}(-~qQdasqC>@yaXaoXVbOpn|o!J+MKjzE7c}p>31gS!6aN^$!kwQXv%*)&$7@tX4ix)CZlV@pn;6&*Xpo;aXnW}VJ!m>)XBaNjx>oVVNibL2M' - 'W0LHY)K21$4cRmNR6EviqjGvv-gROUI}Fpp{NC@Zc_m>3TW#iK5Rr}ggVH!-AP!V8Wy(F-?(}BPOitFr_%c}wP`+>WF;7X*G^FQP' - '^N+L#H=@#?*v=!S72<)H2n3Y^=uCWac38Z4`{w2G`SIH~$8Y{n9KShze_p&fJUKe+p8!H61mVU%Hah?H(?RqZRqtka9L^n;MxU{5' - '1+7`tvo1Drq8Jg$W7sdEoWFNcFZ_9L!dWXNJ;&?_M~w<(un&uqyf;-1XmJcnj45DvgGyvOMo>R7q#fT5w6D!P2p~>+)8Gbx3{M80' - 'xt%)|JSsT`1(wQsE-<0b2wx(urux*PnK}YVA{t41*7r#`(XTbGOg6yrsu}o(gtgqNQwaGp_$Qb}!%VgnN+y9LAB?YNWQFMUY{;W$' - 'o4phA=(kU|@_K-S-b_aooZtU?mww?Qp&&J&_4=D-{E}=Rlfg!_m<^4(8IOmm}=-b$#vfqUh*#V)OwIU^4%djyzvjFEL*4^9+$2WkHaSDd=*({coIGvcngxtf8WNQO&sIa_a' - '9XeTb)9)k``K{v7E-<~LQ3AZ^!r~_cY_9&RT<5+M1s|QL1O_aiQj~7&HHA$w_F_1)h|VW%lkCC0#IKGH2fS5-JBBv' - 'he_$fA@=}~>+r!u!5Hb3pbn`jl*@-y+A|~<8?x;9N6!DH2h1-(!2bnh@Mi3!5#mH~bvZ8<)2bqu3<`%2vNACs2acl$e9WliqXL&w' - 'w9M-gaWh)He);lTn5s0H{$f1PjKNzUR<9WYY1r(DApN|oyI|FgjVm@F{u' - 'k=1J8leqXkz*XqWK&D>P_H@=8AT{r^zHa(Jc`X=^ifQ8zUjoQj?' - 'U1LtJA2(zg>nF)O55R^En9%FkK7auaUj6?^7-pBu|9@@A-<#Awo?vgUEBOb>(rfdN4(B6r33!z-8K5|s2a!h!MiPF-HPRg!@wN4?1<8GRRadcolgL-r)PEh3;ELJp3c=-bV&W0{3(ruEQHbtg>=PJgjeI+8==)GO' - '3s+8>)tF+hGkO?g%LX_yg9E@=+Wrl$Ue4Tj9&%W~D_fdg?j7FTihh0=8Sg=I^c4|$Sx6zN3V#q1&&5EE4%1Rm-ct1{2?eq?+W5VJ' - 'L1D8>3&V8H{;~qotiIFQU-Z|};;vas$Z>W(qkP7@M`FNN-_imFM<#r7{>7U>9L@;W0ssX@M0r#ycc8B-cVa=d0YzbW**awp!dX$U6z$-6P*KS}L<_ocGcnbaaPB`svFE9M=pF*uhLaYE_j+0`U+}^3j*+Z{' - '|8W|DQIGB|6DLWASyX1&%Pj}As4oBE2P97}@hVO-?s&oauh`6DSAMRGvZHk}^{=Tk6mvP*KrTCc9UH}yjN-BuI$Kl_9|FC=$^EbB' - 'g|Uc6FD#oadr~%7s##cIijeleJspEq@L3>-`lP4m+xsM8ZP}+CirM03gp75HM{aL48oixUJZ+ium-Ch$L~l<|INJ!tMy3+@w0nbT' - 'a#1M>Xs|4=(4h?2fTo;ZP3wrQ!!k?{s`cgUV_oz~cvV3{nx!h|NVwihws~}3&&f8r3qV2c8ow1cO(kMlT7eFU7#Cu^JrM+*Opqc0' - 'y&98~ThRsz;OrN6VZeVzmufv;$w)+R@#QJMJE4F`92{E?J{5D5_zHPzJmHj#WNIo`uuv<=cemF#7{>Ic&WsPR' - 'WU5ExX-QUo&!0sAnyR>3)B&IK->aJsE#Ot#XTah0M=$&O?BtJcrQVZm(Xj|1uCoDnXqF!{sdWiWvO' - '|B{(ZeZ1Q`cc?Md53bY9w@fvvY(cj|X+5D2$eL;`#8wGPHw4I@`C*fpf_V|FdqWgENw9_rZIi4`L?{=b&U+RtIyNma=H~8a2;96|cZh0@w^AR|;LkIR5<@atU(T9)D@&555nwEF<5);k?7pe@XIRQO&' - '3+PTsM$><@Lt2I%=|8(s8M<#C;M{5X?T7xe9lriJc)nB8)JGrRoJ%CfYZI2zHyMN8@F;rua{FhvicN#^xIxz-i9gY9w3loX0|xa$4a(P?drj82' - '3toh7Exzsa#deu7l(Idqx=l+dYM-0Qo$DpP#8GuygioehwR1yfR(tKg4+Zky6uN)=ldvc4;?ZZc!((eWwsB%Jl6qGK?Z9-|5k=;V' - ';!BX}DYZ8Z$doD297}-q+EaJ{{T=+0|XQR000O8001EXxRhx`;s^i$h$8?1JOBUyW?^%5' - 'aBOXJFKusRWo&aVWpiz2Z){{TFKusRWo&6~XJ2J=ZDwz5WHMi4Y+-YAX=Z6-VRUJ4ZZ2?n%~@@a+cpyZ?q9(Q1tJAn;oAFVz=sPG' - '2MF5a+U~Yrd<%k>Xj@&qRiu)A>HhbdA@xE^lQWUKYeAlVC' - ';e{ycR(ll1851Hsq_LWnR&}}OooMIiYfI)Q6bZ>qdB+RKmsW9H7KilM)LJbU`9T5EYVLq}t)$u^Jm2#qF}$TW|M_A20sf`$-h_ny' - '_UgxXZ|E=E>vwN{-Cn~Bu&qou6)+^@G(NGk=v1}yQ7}VD2C$D3!ONjokFeHo~mHTXRW%n4I|U-3H4c_CRg' - '9=SEXX)UW&2OqC*YB2sfE-OwpU4VC;' - 'Fl<+Q|BWCLvSt)QFZs6snbMT-1EFb=%9Lg4BR6LD19GcD{I@IllNYw8&adFP!qla}a1ANJ+6NlK=qOPTJA!FXYWz3#b7V`51HBX@h`sv%FVJT!' - 'eN#7va1Z8YW5H*mr&MOFI5zCasj6Mko(WDLA@A}%1b2CsGdiFUa(02Cr~wOROj4nuZ_}=yMaj&d)N@`H0u*d1{*u*4vDT~P%a?bb' - '`bSq1H&BoLAR2V2(Zj$6yQem5ZbDs@P~tT^y?*sqRS@UF)B&)oj6>NXpHg`$AyF9Fv-oZgIwNmHfs0U3Tx$`)n5@aZEHj{;LVxP9' - 'mPg6I5*q%IW&WU`=FrI$N&@X30yEdKhbSipXVQB>|C-l2rCyI-P' - '-H$|LGbh#IoXM?$Gc`j`4AYJ+F+EWZKx>2TOU!*O5{@hlTr9kS^>a+Yp_T~J(g~W)vEQCWh_#*6)Uf<2i!R5}t1M?C*&xHhQY8ERLhE{9_vPjavBh@6Zt@CM9g_' - 'aHpH&rl_<0k=Lbw*65D=y<4F*A+FSe8pLzYWPF04CHy|r%vE3jy1Zy|*cVFPyKb|gM;OuRN7@wZ&DdGgfvjgMg^%`&XJaF1WJrJTFsW_5?sZ$5!jZ=AJ(M@>mWG<9i(NNpQrED6bh@J=NpoCbzyIw0j$?f{' - '5=d=(PV6J3`H8_I?kkYP?i?UkSV5Kf18F0rAediD!JknMINRh&o){o-f10UAQOYJ$XrmCgk)2-z#*wKC%N+4D35Qs@Fl_O2*&95o' - 'bHmR@xD8qd+(@-7{WkjsjcvJFs5kBEVycc<@!$g=H{S&jgSs1>TXD7@dHq0UiKu~tRz%_w&ALUpn{(814#uM`&}j*E2H~3)1`+?m' - 'MHOZFd2=0xR!ko|>OO(Q>J%2w3lzw>6t?pV)7;J`y;NOfc|R8RPH94I4fBN;Uci3QpPu^#U2u({<8xn%$7GoAF!b!$)cA&3;ndR0' - 'XmL@FK`#==yy^*frH+$aJ)MSEOp>F2)XRrRA9?6#IYwDF4IwT&eF#|1ilfohX*IK$0w3=yNNL+=)1a>rGom$U)0uuVC&PvhG~;' - '5^?N+yQ_dw9UF`zc1n{3R&sShitN)ndpw+IB)FVYOJEw$p$JYswVq8sSa452cRtP!#3Tx5>f^A8ciyx|Vd}VIAsd(xPa^r' - '#M)DigjOuAgLX-1vk+Ld)_Nk(<(>m#|14=nK3(8acWL{zJu>g7zi9BL7vF=|=C`jNO!IdBtpBd)+RmR3@&E1l7moSZaY~_urxXwPR#tC*5eK|*agYO+' - '@ra96&#Jp!{SQz}0|XQR000O8001EX)SfskWMTjSak&8iGynhqW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFKusRWo&6~XJ2J=ZDwz5' - 'WHMi4Z*6UFZZ2?n?R{%=+{lsOcm4{P6LAAb=oU%MXuTY_!yJk7D6H3s(#ST00fOCV5@Npup!u4c`|X#HdRGD6q)6}H?oQZZ1E|WX' - '%F4>h%*x9AE_yy+Tvz4kSrhGS?L3QqF6U>}H-' - '+UajaF&5YqpEi@90K)Zr8BO!+Xf|)6WnBP>vW~{(q=<^EVXhFQPcAPn`#E*CKd(-oOt@hE0(|rQ9grF=M7+|-q;ZD_|K#~;Y#D>Y}m}_lUmkU>jm~(eB7H|r_u9CUe~|oRT>TRx>0R6^J;izJ@jXQ&pMjT+_!z|81y`!V1bvSDf}v1`UvQpSH=hGWZegZ56>6#awZ2G={KVE' - '*U_|}FPph6fG-3V`@{KcT%Il~!dq6<(=knfC@H7pIh7Jss$x;iN6TS(QclX|T9&PgVw3^6rnmweWaeWuaK1Lx_3j1|PdV23jW>tO' - ';_I^H$sQKjd{JjK)XgLUGJZRs6r_~OA7-5~%OLdctDmM!wK' - '%1%m{xq$(;58sLz;Z+sT6`QKerpt+4qrqlQCk1|KN?4^sn3szpTP{X?{jiT?6x{KYYJe|#};t;4BeobKvN1=YNRmbh^%Y0J9K)#FKUw@>zsn5zq&3uZm3=`Cw6th!cOfO+wo$W~^M)0S)j)<_+' - 'sGbA!nvLd{#R!1q4G^Y6cQgZHgJm`crq@(CEb`%behN%$Ho}+4|9}#(WM5OFv9Mr0Dp-B!6m&-tIX@BpaG_?#vz!A1p*3J9ZZ|Z0?FKXidC$NhEaRbx%PK+7%gJSv<{tIhoF#!g92Fn}j!$w5hUh|F~' - 'Rskfzd_=fztNA1vPv(~Z?D9;2ig2VjV`?^`#u>nx0xV$afMh~?T};M31Ol&duLIT%yPQXW@;#6kP*{eWQwAGkIa=luRpccWdHwb^' - 'K+3Cw%kr3-Bi*4A#zKi-k*&P' - '3&fU}u=_+O1qeDI)GSd>0V#snehn+CKuK!`y&FXg9b^fVXMlwjL*VBu`G_r3G)Su)7Og{o?jHFoVx70h@usXgUDm)MfI9#)0@?02' - '2gdJdbPB+K6UF~f{Vp~=qxHeSrf|Vufi<-(HMe6QmOwvK(KYpr@Fm(9d{1leduIoSjt_GbZ(ErjNyauvi+d_rQ?OCSImnl)PSpfVktuXo_AB&kC0UBbn><$fJPuJEKP$4T@U7sjsx9?;_$->z%u|?*?#d)UfvaT?@fR' - 'DMt#}b?_W2Ns_^?jcqRXJN+w&ZG`$IF{2n60-+Rtq&#L^=REj+*%Z?{P7rnC6H_g1*%GM|Y6lyzm%>L}9%@H16!Ob_^?cGAuZTX}' - 'H8$Qf35Hk6ZuHytY4m;n3p5bRo+iI{t+xdz>}Yh!^5qq1?0I!6gH^>O);XI(YP!opuTCmz}By~' - 'o7S~p55SyR-ZP2G<+JP9Dfov$w3QHL#ECoQZ|4oDbCUuUvtksUd(eWGCzG;1gU%@;01~{+D}ygWx9jD!5WTcU@srD1XY<)jQO(^E' - 'i3JQeG7;j5EHn+LAn0Zw0Ov!rAE4;0XQu--Mj;a|2`G_~9=J2nYr&(E65fU-U1gtSLye5T$%Y%$rnAweB' - '=Y2yzQ?V<^r9&&`WImr58wv7r6n{Wqs{q^DIzXV&PQhp*`v!_Zz>kGU4D&Pu$&Xfz-W-gHZ4=y80i4~_0$?|l=zSV(I_l77Dl{Nh' - 'Kd_+STri!$q{BRnvSNm!{YX%UEXN4JLc`@K->{I0QfVtL9o&hr+UBUc4hQ9b=)@51zL8^Zh;kdJSn8B}|y+e1x0Hy&uWh(eqnW${(zl|#(Cfx4V@0xB9nVYf1q4^+`xYDD6=8N4HP*5LWiON_>5u?~PBrOWKk}anZtzC;t' - '^UvK|V?*#I-Kki%CR))$`7XjuPOc@gw4PM+e1wNiKmn44ET=QcMQC$+!wd#qEeayue!rje{f<^hHe3BIq*9y%dP~x)rBN82HklfB' - 'Rg(v`HHct07JRNxuM)e&p+jm%P-R;cq0SFX%rK-`ahU;m)G>ohHdgd1dPJZ$(5~LQzyAOl=%&axn);q0KmTR#L-y;t=X*bA2e1CQ' - 'PkawB@$837y3jdLmDtU=N1#E50|L^<}{(bhtqaX0YAOD?g' - '(J%M+K7RVJk8MeKvT38P*EYO!h6D4Vh5?fT#j+3k2ieDeyx-S@c)GEX?Y~Ex?{?Aq5qtfj80X7LL&_Z*tG+Jt5uREOBY9|+M(^Ig' - 'Nr~^_KGg%-oz*1Sfsj=231^Kb+^;|k%olaEDDv}YT1@BFHJ&HI-US;X)ZR(AwzfbGY1q~^i~hJ3{k8l#`s@2o{b;X=CWGztho^r`' - '@m%F>IXfR9#$Zv_5pAfG>*(K}VTDwjlyISEKSYxvzkpI_1$2Fm$I(0%fHTRf)1t=FO-3ih3~es>rh)DKJ?_3FTQ?+!k^z)JlE6x0' - '9Uk%n7l0jlOvgR-8K200T>>m<@nlJs8HNGOt~Vd|#J~&Oub@P6jl!+M!LzNc{zmp<|K;AN*B@cBKE3@L&mPesPRQW?{sU;J7MHVe' - 'QDKLBXv~7KUZAr>a~Y_uMqglGFV9q{!;!Nu8qrjW(*-H8%artxVG1m0tm|WW|n}2hTSY-0Ecp?vcYyf9;qXF' - 'vYaw#+znI*0_I2NxoANO*@$dgkTYhv-6G%t*6!EkO%ZQv#gIPzX~2?`deZo~$8MCM1P;kO###w#eRZswf=YN^JK&F#`Ro+ITtJlp' - 'cAS4ECZxmd-6I7JIME1L&@|>j^rG!h^}}5Q)llXLnX1z}@vD#aE)Z#1^W2b>$#^{6O=YkEgNMnst+7Fz73avO{tKI&W0gJibA{Hvqzf(6h3HIIOUn1ST4h=X_Tdr' - 'aLO12;iD)f?DrE8bc=;W`mqU$;h6OALKDj&`tyK>=E9Xf`^#Ak8(cv{>larGXerBK(RvV*{t%XIA@n|3A8BJNjcA#Y6HO;h`p9DA' - 'B>G>f{sZiSd(C{>Yv$+0EJE)t5MGLh+3!wsRK+uD3jU>IxtO1XU|4HyhtBMv5PvU6SFM^4X!Jizl?24AN{mH{X|Rp5*rH@0Lb$0we|0WT-TO{=RanFow11YU0zC$u4I!voP(WjD2ATHX;n(JInBZX`qJCMWb?OQKJcOuUwnS@e+u!h*gCEUr1HciGfeMQ(C4OU1hv9' - '=m4*E-E<*?o8bmbxlOfhRJMDh9|b7m1#e6?mieVoh{Qps48BW^!wt0H%Jo?lqvf#3VqU#!aW=Q&|@y#ZYnefUb`U3~su>gVB~!' - '4G~NlF8Z;fpM*hQ-qgP$l<+}C' - '2re0k#Hk%Q{?Z|zI1o`#j*n3tBaV|Tq_l*J+3~SWMTkF;Jn^Lc0?#4(i+p5HDK1h{2pv{Ti?_FZl#*q3D-kXW=1NCfr($pTkWI?-' - '!Uiur+Nbq$s>CH4V)V<(BclO2za=+$vJK+FR&i0xCfB`skq_~9N{NO-g0@fIbfB}~aV1d1a}DNAd4)V%^faYf' - '1AgF5`H&`7papuI^l4-Xl22ec$PvHBzmj-uCf4uwaTCOr3`QwXGbzT6T5$RR1Tn>zkkq&EqH(Y$I`9v-0}cn}-k`DH895uMMyCe#' - 'PM{=WIMz6k!$Gk?>4XB5*mEt@3(fh{9VW>O*>+6r9O6m>K>Ps9Es35)J9u#ND;7lEUXFtsO~qK`LAYp_+K7{WJs&qn-ayUKUqoY;' - '(g{WMR`CGp;7ehnXV(}$(MQfT$!Dj_{Iuw?M7XHt=q3YS?_F2qBkm9LOLYz7J&3nx<_H{fL$My7?i%if3ogAI@r@JcXo@lm{pxp}' - '|MKksapXJ7_#~PhZ+E~;$49DhKJnwb12H#2TQpumFnO-3Df=3%D7$6XZ#hJ~(X7nWqJoEq^>AL%vax#~-`E6ao|MUZ6sD+do!LpN' - '9UFdkkAyuVwF((BH19IplVGXwaxyUsp@3BUyHPX=jE8?b)?HR*E(^X9iQA`KcoML=d9knBd_qbC_oOJ%rzhS#nN3sp|ETxp6Y|>-' - 'AOZK@A)z(7F~fU^6-;Ms_5Spv7>$bRftIJ;NN8o!x0+oDwXV&&1^FPN-Dv`w-~@Q`a8B&Ugp_wd`c!h' - '@@r)`#XgBT)Gt7xyRuDpa#;u<$bO*2yU%a!^)MpAZXnw1^JD51A>qm>L(g_0*h%OLt12a5xW(|)&n!jHb|L)8{@J9ms>05DyAo+A' - 'cqcfc9YR%d-a9Qx*M5}oMkQ??%)nCIFX@&XDlo9Xk>%NS@md&&Y1MpDWG6ZLH&lGmc&&TfUdF|XRTLR33)}fsX=1CRlnZcKj*Mak' - '4Euv!O?_(_P|J6d+j)%NB2`^7&-v<~X>b$X%y?7V{}A*xPYsqf&KC39|KKbRbK9|NlTe1}34b1*;aPO}TfyFj;g8e-^u#6*Z9R0=' - 'USUBAZH$tY31?^&2SQfFzUC61l-p{96Hd_y@iNpFW_;y@lupSNZKC0sOy$h6?}0ktnvxaJ=(-+u8f9PM|3mn{xQbilXx2izKv7L$' - 'MUpXP(8c6p$0dh|U?BcF{qzZ85O|k*5EWqztS7%9lgL2-^os~e85qC)LJq|Y0`CGtlN2B3Dnmk;Rhm!nP^{7?apzg8+7JW8VFDX6iBMoVt`UQkmP_gv~SO7cGqbSCI' - 'Dyb&!B*NE-UJvzarB=TrZvl1};%bAZJEuL!*IVA=_m1PU%^R>2+960LL|{P5#5' - 'gA?9P;R*MRtni@^t(A3m?YjECU6n_Gr||S+!$1MfX)UgE7h+;tx(So%Rb&Q#>uS_dznU?wP%W~56-4QlOJXx`Km?&Ogb#a6kDbmw' - 'c6#wR1mDJAL7SVYYZ^^TrFez9*+QwTNpUBo}6_m4eM!ZAwJY=E#@#}WvI6#^mgbps4(;+m*7#v_mq3rfJobQOelE>b>M)k%OB_OQKpLV' - 'Wc{@;{J%lF|G`-~eJHu#Y&s<3kLH&%aqK__ebAh<>0}Yb2V@vQQw<)wu@I9+f8BrmDHTsiryY@$aO1X@(U9?0v?yQPK|(g*TJn}^' - '-)q`G8IXgf`g-8@iqN2OojEYgkvJ3y^$=^Z|5||4HCv#x9z|Sr9zI-2vxCvs_$sl!x2^uz*%Ti|_EwmrQS9xTPLaS;eA=PPJChM#' - 'J;X}%*4+awJFlJ4+Qat|#_b{xjfzQE`NXDgo<<`H^ahubj8WcOYExy;%FzfU?YcoDX~^=-`cs#)YL|uiz1S8o' - 'NYehno97YrHsa>k#d#iR`PsxkG`(' - 'h~s2Ft0LB{vaSC!I3y%ja{y{F%DW>bUivewbb+i(X+cqAl(UsTte<71Kcks6su)iDl`pt$9G-9y*?_i;0B#x6Jdinn84tbk#2s6aqSZw8HB;Ps$EoDRp' - 'UD3}kx1aS5SZ~m$*k;~StW|rj@_Mr}Q' - 'db}}aO++(~*)_~mhi=ZKtWy@xYk1JWq0Rj*W)Rx`4@5ZH({=xa4e0@U?pHz%)oB7BiXi-E3`Mlw7I#T)X-5o=a&|kSKDKNm~4XbOU4bKo~QW&L^4gNW_N_zN^n7NAEQmSH3Vq30O+egfDkxi=oX3WGDjoByX*lN8KSW{13|+vjmRhM(>k*t-XX' - 'J`_C4F)mqwm^zAo`N#V-dPDK8pTj?If7#&>H|VE^@mYDm8_r^|%nMiHyW2mn4LPU!n&L@+6QFB0b3aSBk' - 'eEmk;dRfp#DOR;a1Y3DwhLUK!;nW?k-w-S_O5~g>D?N=}l2VSWDsE81uGgUGC@z*!01?eMCF7N$w}`JP2Pt5h6jjW+Y$)$#(_acP' - 'An4P$TF6_J^^sr6IGjyUs&E0ZBu$Dagjc3DRc=TsV?Y=o2x=T2$=7^4yw>kPl7jBjJwCh)B<}0P^Qt^8QQsA9ivjXX;lN`udZE9o' - '@{!?ji;Ln2YnLJviC7s&y3MhQ`^9&wKnSl;_=*`QPkPqW*Juj2nJxj6lmxGO!SiabeD6cC9{sY`4YPFf|vC)RGK7l>a88|<5+eo1z_I_+Pk#6rLdN|!_qfa' - '^J(ebShW#H^kF#e=OFxH923=Wpf8!Oq~0BhclP4agfrm9cIW|dmX}$BDX6OornS&?dWj--9e;Z?BIqoZ&xh5(zV*_{hr=c8' - 's&OifQ86dX)YVXcls*~XEh&pHUxxbR@=1Dlzm}4WloObSu%k(XMDqr~@CYYmO+RKVf_s1HE`%' - '*S9-Z*s)0#q{p?WHfg~E_LJ@$;u3mn5kpt2w`&q+2y{T}AeMkzCA5Sk+vI9iAt7(sB@})w8|E9}eqB4cS`p%gwkLpDb}44ED6_C&' - 'vIbpi$<5jOSaijmueW`R1+C#7>JA~b2bO}xe?whTDAcb6_JFJ9oU_BZx5?NM*ut)11VE|V_oJ8Z|48wgDLoUKZUwXnR>_rSgMJp<' - '&xq4}#E6IR1DH&-k2m}amDof@lADmv6`Cyd?Ca3?wwCBc_IAJj*<4&2dK}B&kG(3e9+T+F6Zbv(3OKK9O@X&GW7bu>vD9EhqlOE_' - 'wineS9LK}ZYWOMuPTJm!!L||1V|}H-?6WC@r_rvlUY&11zW?=kQUPG4MO2~y9wk39WE*io1`c?%I1V9>a' - 'GWws6Rb;|2>xhID?^2?H=_pR^$@I#BGXDw_|D+' - 'h&P+5Kzr?*Co&e~j|%9{S^N-AQy)cK-Zxe*%TJuj@?rd^!cG2+_i;m>RI+$vr`wD;-6rRz<+X@0%tjo7e?sx|t?VP&' - '%h&HdXCL2X`>);}e0sC1GQu9>)!M_lq4*^gO@@(u2f&=p0np}dw0TmVkJ6Jb=XiO&{34UlaUQ$ba6XmA&SBx6oPV*4aW)*0dJ6kO' - '$?`P()BuvKaJ|H1Mvkgao|dR{&Ahjm2r`tmYjw1JtSKYub7*e|cdHKOU(G174FMYbhX4Md`n|7%m&in14BSA8eI!qx?GY>6oP1#y' - 'b}&IFs@5C3I1O~bDKqWR^Bk@q15TWaK&T-^9AeEQ=Vlg&b}9mo<--*iP8t=9dVo>1lK_-yc@>1=jHwj}9z|s$*`Db?8|=sr(!<flH3!bVr1S2-Xa74yPgXA5jlRBfzK|nWaVeGf-2l;jfRpn76T?' - '62hQp;f!*)tM5j5M7QhsDYwCBgAnc7FigHI#)cdq8k_pHz7k|@Rzr3JNq0XpLAQ(xea*`t1IVyW2Jb|ulr-tKW6F0CPVO~&+C+i7;l1GLl' - '$Wf*aX!a`N#Oe(#cmo<9la63rry{);=r8CwRAM@-MJe32F3;w`i#gI4<3jqX=8fsngc`#feT?vkhdR@bK4JhrfOU-;|1=*JTmq#=' - '*jkNV&UAY%}Zl!PM-AQK()@(B!ZbWQ$c+#Zd%7Dt}i%)e~r' - 'R7U9aGM}aH@8x_sF)|Y&Y8~tZ-7;j_6yD|srZ8u_Q6RyhyDiEKns}9j590k(3PH6)Ix)_4nMUV0-$<)8^Zd#-I5N$1=y8N?t^o)5' - 'P8w}1;9q>;@cgAKVa|F(uv$5%F5lk}Ec~3)C_08D2vNDJYjln$UNTD' - 'HeW2Ubd6D!h;d$BHHVye!' - 'W~yLLss(6nLa<7sQ~e+{m7oBYgfc{7iay-l(WV=dyD>Q|ff_xpio?ID^1@Mw)jvSV|JX<38E;7EV}9ofrv5DQ0TGT>2zL>VxtwOTRV2QI2&Gb2}px2q!w}<`7vo}*ti%0QW=u6XhBP!pzr*6kBZL1s-q;(K#1ztc6c}}NopzUaMFH~vF=o2!`C0Q4;mkO2?L;V`NwrnkE9v0qR#tss>DC%BQn><5G-RAVxW;gHQy6p}-8e2%rag8*@eiDBhhNeOfn|SBY)hq`=viP$JkjY!yQ%U!' - '>0d!P85pzw4`g8P{>O7S-d&V!{C' - 'Q08AM?B~9|PlK;$+5u(gytfRgy@r-$W!>fW;~P_B8&KM?hY0D;mpwmX7{^_9*eTgm>dbR$6@9StQrUipCB1$dJ&q18%fG(<6uo@;' - 'mibOT8cnA?CPA5M_a~i&9+q(fjOg{-$CukXk40rBs1@A~13Hq|3@4q)+U|coxT9e)Iahj)CAtSau^T2hWi58h4h$W!f7nw^@`V1UEE{B*GYg0$0*A6|WY_cr_6{)e~wuMe`lU-w?U-uwCWen&!eXGm@N)U|_-S|pu!>$#3Joyz4RJ%MDr' - 'R{I`jUT2e;%uAq~p%<{qAGAtmvJ=k5`;Mj8Hm4r0RsgC<9FJ2CD?UNw4D@<^kQL0X~S!5' - 'HV2V4AH|99`%FmazkZv2?&FD1re$e?IN!5bBzG0v-woflmSBJOz+$Duv;M(aoX>iB(^S3<@Vn6Z?0??gLBi3DdFPQpeL~j)W;IC7' - 'g+A0Ln@qn20RYVGs6#uXnyWNevE%LSGgtKVAg~4S2&jwk;Y+-XgtL&v&fegwRY8b0v(1M2jKni8h2mc@o)^mTE6%$G2-LeTB-_5?' - 'q*#HDxK6-V{gJ%fCk_e7m|?gQn1{Oz1k(&;mrrF|8PY{D>O5t}NLS3nq)=R!lVyChojkhQNlhcdn4HlspIW?{cXK(nI(iw<&>#}C' - '9m|dw(apA@dsqYt3gcFQ(FN>UjOUkq*-4CjwqxLdBDHB#*p;<<_>>3Fhy*~4p5mD4c}TjXcy23BGx*e4t{oxb&}Wt4iq`KK7!R|#$OQHC+2lGJQ<&cvwdi0_;' - '$oGRqq`L;O6JbyD5?HIduKF7fKo_EWR}!n(pb~|!Rg;C%Rgbn?=-)iccbnmk*_}K0$?8dXlrZB-$BDWGqtDcZIl?i4glrPxFgX+9' - 'NB9Aw7}E)u-tX2;E+->C-lN6A=who~Q`muV{4%J&vY0wob1j&3GxQE1gO0E^#t5)72T2esY#+(3jR(e>onQ7S_{Jy_0cCpHpi~XE' - '`0}g@i536yG7|UZYW=9MyS^(vt`%bkOqoN33BckIp(wm$xp?XOgzR{Rr' - '7z~6Dz3^}swu6I}?(?!b1=Knzl)JnG=XVaR^_wXEhwAsub}H`&Min-RXfj^Ej=QkeuDII%vTBCySnq30cid*qN!rdJzR`T<&O(a;' - 'y=xNIsS=ulRknIZ?1NUZQQVGQEr^-%Dit=Qv{K|OaWl27TJ=)hr;ffjp2lXUQtf#}3smej;nZqk#yb6ti47s&0T%vP=?i$-_a_~=' - '8I=J(Bd?&yVm_nj0^+p9RNi1XQiwA59$*%#+qR1;pUFAIU}SbVr2xAc>ol4D>DvsVFK~W' - 'LfmxfX{O9U9usYJ!Dwjk2;ctThx_j$oHhK~XfNFey0WE#puYKJ5U2sniBq?63RjA{BX+@E%-E=+vQ5dUoI(`?GV{szb%bUq5{)-H' - 'yxthOVm3VQH#ZQy>H5}IlPrH}n{%P4X1sCAo_b|ZTg$p$aDJDxH(XoQma#~eIBz6v&AEPY-mp3=8qRS}MVkEp>v0l-dj4~rUaOkP' - 't!8Q~JXb^NesIYU-`@3>h+_a~t(r#W-Nrz-lWzg7ToqP;_N};PLsF~|P+jgUUboF?lH3U}D0^$eHDNmYJI{|7U6eAq302KJoDS(}' - '{QMfjyMcJ=D0X$Hb~baO?C<>W&>9;j?B!Ep-SJhKZCq`Y`#89@=49&nnb!3s8tj5c58%u~C(pF4%S*Y85XbWiLpgg+3RE)b6w2>O' - '-o2+@-p#MQ2?@xd6uD6u5#v#JPuDG?wg`FH-)9beHNGY@$nPbLdjL-_!!Tb@Z$JI-4`D}' - 'gYW_G4bOOS0|JN{UQik#JXSnD=Bg;H6jjE@tr=E>+9$;@$Lk9^1icK(%+YZb3D%&_R)q?0OU8IzNoIUbf7m9irgQxrSv|DC;l(bPBYzj}=-zt>wa3=T1*PTM-?ogt#^$`qdE?;xk+8&FW%UW_2q8uNy)0-FWOc@!=-?' - 'lOLtw*$*;CgB|W+Z9l*N053dSEHbXy1Cm=rqM#5o&BSc#)1qnO=brl-vFFZ6a2Cs{)u_J-*q2F-G?-QP;qIeXl7Pv6M<&WgLRc~VY?>B*NVM+6$8_=Lkw`r`?WC+_Ve!87nyw$OQo' - '9b||bt4kYv+Z2fDlE03sfcHT_rcI*!3iq(BpSv^O~gA-Fl4M%!D-Dkpg<30PY_4Dt*}TjwQABYYOAu2h0T6qjVUxb7RiH1kUAiIPKFTB(p;kZIcxV`}H1*Qt@+px=_' - 'V&RxH0Memcorz475{|wa9S#tu_+@8M)0Wc~CQ}(RcS99oQ5DUrgN5umFWJ18e5jlR8iZ-pKWUjr`' - 'U26Jp3SqJXt9>P$)>`H2*~96Qm(0xIS!Nkkl7AR1WUR$PSH{y{Pr7dBf(tD!UJ8E+@;e(WP>@$KfrcbnZ>wl==z-}k52Fb-qGj^LHb^~twXmWZ^fYlU2ZS!45CGwr$X)6}dgIE;a36&>&&qMLM&<4$lxr!m?p#f|H1oXdbKX%}RUo9sCR#CL13RJD|' - 'J(BU5E)RJr>>f!%K2dKTM1&Ip(-VD<{z1}iW>S3-*9&EAeKdft4kbVn2Y+|XfcQ>9Lw1r&M_d$c7ZXWmT;MVGD7xI<;S*?<-^K03' - 'wGVwtec8}C&T+0HI$u%Nus(&7d`jib3i@)F10b_C0AoFp^S*yIoXL`IVd}l8G_O|TiH&#Pk~SxD!gC-!4akK?J+n*gP@HNyS%d2S' - '-DFA{rW~;;9tN(NhC1ROSSaCM-!wIh;Gcm2(Po4ZspvAF8R=F>oug=U=toV$*7rID-Mg7QV{SA*ml5nhj^c8Z@az)r>9<7L>(i+;' - 'NB@m-aZ8sX0v9DYq+I6LeSb*mzzs0d?)bo7yW)B%I=#hLJ9gA3z-cNl9F)iP1zKlmW7H4+_PsY@&YzO)F;3lcPCWVuR' - 'w;UC^*5H9&V@dm}cJt6l8XP#Pn*Jb_FmsI!Y;n>!kSD9g{qHxQ_t&k{K{IYwCyDda5~QrGYJdGLGBVzh2?t(?{>%A?{rf' - '*(SOiH;7rvuc_7QIs=g`dpe4Zi{l^)7*cuJeuW%$E(8^Qn7`j=VkVmyljv-V5Yii>Wr{yJTdTA;Pih2*SBL$6A0#R69d-vh^4OseyAWy?HQrYg' - '@A>ua(UIw|fwM2RW1xY*NRe;4C?~+B#T9R&jgw_L8392+lhX896=Td<=jIU~XEm^{B3rsC#>u89TU6' - 'fFTRK_R{4qvLdCOA6La!Lj~W0|70d{tRF0c1y&_;0J8WQ2QKNAPF7fRYu^(j3i^)J!csv*shI&B7*V`AMoOY' - '3PO}v!&G)V4$>N!e&pa!VXl2tZdiCKXBUJ596QMKPYlmW50&c6qeqSdrj?#^D-E=2=RqWNKizTDn^`cQg!*ounk8x~JBDG+v^503' - '*NUpv>MnIrRgqoV5Q)07@zWh+CPlzi3;Eci+BdDw^7Op-XZUlznMz2hH)CN?)^pIfE{H632}blB$D@0$)~wZkD$~+;?mG5dKuWC%' - '60OX{JvutrwdwY;iB!&`W;KO8@X#7&sylRKY+f=(gly_hYW?gQmk`Ll;3R@;J3_Hfe+3?$pi^Rn_Efv+#+s3qLKGJ`CJtajU_lJV' - '#R2b5Oc1EICvE=4yfJY>_}yMm>`4HOc6=cxd_8CDSi4N3g49&gT9i$r_=;17Lx0nRn!J);PY-1tz+i$g&K@7Eh>WGedHjLwUaI8|' - '{apn)q;#twqJDCGZISV59|Q*Efc?IEhps=Bhin&MjkupE@_K}&ZesMMB5QpMVIYGR#a6-Kd;L4G_97(76Mac=v6)hhJ4k-JhB6FW&LDt' - 'cAZ9`$sKjx_owwnnY%#Cna=AbIzBcC_7Eg^p!Ft0n*VD' - 'pu}Wya63!fuw!54rLR!PlMgVCaw>wJY?7p*QTDwghwt@ux#yo=>15e;<`1M8T^@bTJ' - 'RjUGGuzeYY7H^YyGOo%=nOE0DLWQ{2i2*!>3}LKOTpiLDK7nP_H@)8Y^MLQI' - 'S{*lSG%9fCMP9*lBOjaj0c*edkz>#OR~P;(gQ#xxO%velzR{LC(7UT&f|n@G_aJt6*VNl@oj3_*Fi5&3+XUH2E6uRmt(+aoti_6{' - 'LAxryTPvfIaqbj#X^m7v|a#|1aFFk`$5Fh;4RhIka-|MgyOA8i-`NcpBRfYvPNT>E>%Z}V7PrrpBrwSXekRB_P?eIVH)OnOW|{~@P?frL*C>5>RjRmx)-lx2(j2Od2ByRomf8)j`u_SO^)z~eVo%K{lx^@ZFzN!y!A!AX=02_p' - 'VPGFczbsGjT1WIU9~O~JhCw%v#`p&9&A_b}lWE;o#2bhu=}M)Zp#S;R+ZXRX@4pB!00t2dT*U!vF($=9r*8Pj*v^Z|z=GAeW4' - 'S>}SuTy&PPT)C%tgQ@?Z{S+H8FUjptO2@)4LWNQueVMRW8|sLJT{h%cEDLXYI%^=^tJ=`L)EC4' - '`^?s~V!`{86;7oTS@=PW5_qmE0%(XQ!*}$7&Q_;&mXGoYTC*Q3x0fWCT+717~5RA_KXd!LR!1' - 'dR0F#7A*O0K8mhQTa3bQ$O@w!U@-~H=RHG#F_W?g1@Sp37nnbYaW_R+xzL4;+fxVS>rcp@S$(JOf|^!G7+=_{+TRv7wY%I{#|7Yc' - 'CRui#4a;Ef+I#fpHk+v~fsri)=g!!{xxS~1?Z`ng?JED_&4F-~$HAq*W$F4JCXLQ=(7!-wlXX5@=o~oYBCxzA%huWC7ye' - 'Ap;K7zyb2vN*0^&yo?V#aG!|mTX^^+NXdkjZ1@O70`#qpvb*{Yj3$GDDc84W=s`=q!lLWs;@F22SJB5dOfj*|a1z|S0|dn`M|N4K' - '37cVf5ptYXefF}VX}`ki;BKamNH)2fm=8ptu0J?b5d%<5oq|t&*qmdE+_U4{0wg$CLi=18dpPmlEEjAZf6z9V3r3%f<(IMeG8WL-' - 'lK;#(ag)-6QXU*K!0kY_B-*y`qGg5@M#{DZ*ClzWOC-*OVS{o+61>q@5&Efq;d#Vuh%RDm-3PHfDHyU~5jh;5zp#dXzTxc#y8ks-7A!Rf)DP' - 'SZ+3V*!CQ3A??_2I!;nZQ)@j)X-A;pI1%{jp6qlW)$(Tue0M?F_@>(SE4xAq@}XYKmyP=fG-!#6!Q@Mf`GMFEgGI!Y4CIy@EM#uO' - 'E*;?6ak$p1{CHlaHDVu1XMmNl4>Q{Zj<-J72QKXYw#v=SV1ofaJ6(d>8i>EC_ffSwu$~;p6|sy4)}nGfUR-~y!Ri&^f>5qKgWwXd' - 'i+e6;gHfjPJJLQKfm`oaNi%{+*DzoOm%g8YcT?i($b%&Zb2J5@3@v_1nfo' - 'D=0^*3p8{1-?>gBCcJf*(@c+$4{@a)-pT{@U-HXx>X4^lbfU!#?+-lT5tVLmld)1vBZFO)-_}' - '1ohDxnK>8vNSNY>%PIC*9rR-7w?chSBQ$Al33xM}{8}_@QH0$*9yps8Dmlz_)j@Nr8c}*dV6}x51OCKR!b_0-iR^pdlr)U}k9w~E' - 'QSerR)D;M>aMuHL8Q;Z%pg;JD_^gGSC_g=|ic{)=r)d~Q3P&Oec=Va>jLeAj*40$YAzqQe)ebo3U=qMi>F!-L6b8KEd|r)eY3@OE' - 'P*}6^ax#%)Sw|yDqcw#BZ~t#sFrXBXWHMs=>OtjED~_GO+5+W>>Bg5jIi~Tcn9Cl_j1$*%A5BaO<=#U`yi*Lu#Q~wj;|z>e7-!gJMJMI&Bawu5jXR' - '*#d%h)7BDiS6pI~pND68by|eKiL;jQM`jE8v>*^|Jq*j&1VgKxZ8z?5GO}uWRk1oNR~5O*$}g+Rvh6fi<)WR>0eoW)LhyT)qgS#(bqnt*(;mD3^)hEPH(H^!4~SqQ5n)j$+X(XBRmpSZJivXA}=<6{k|n2^bHwa4d!3B+7@?ysoW)' - 'WyPXi9U-vms-X@?OCfKl-){OuSmw?M&3w4_9%X}Zs$o(L5o(=1WrhB^nMS|;ZtsRB93wj_W`8g~(ecmLuH(u9iwCcW+ZU%i`@><-' - 'z8JKu`K#UzG#D-&(lFPkX0VU;0uBr5zWn!Q@9)|3U-mv6M2{l;oBjIk`QFdj!K;7n2l{a2zcOzLhwZ9N(E1ch$UU(uQN?<8oZ!2L' - '>t0Y$={v?(IZ-yJ_F#&;+PBs0jho}*{`OrV+(F&-PISo~cMZeM8Wh+;s&d0=vFQg#yTaV=d23lb6~`&3eI*PvN_u<_YcQ}Fm*2O2`)R`JHwYmlO~4v{RX%z_p(4pYBfy~&G@l$uLf{yAW^pAvRmwd8>hxLySudG}ho<{3eA<=_ADPdpenxSPE)*S5I3' - 'xScGr$<%W-Sz$jj{XQ#K7yo^vT-DxOsQHO2-88Q3Y&NBF3gzn=v$1Pqbw!i8x?kn>5T2W-eh1ZEA$(a;ZLC`lAO4ZFyzb(sVlaZX' - 'Z~_2K_(ACrd?cc-iaeB#NSKXOj))UT' - '$#8zrQF~{tpgyPH6w>DoA%RR9XzM8Tb1}yZFyCP%ElZdbX}RB+(IDN)xSd07=c!C>&5W|}iGv#wDDwyeE*3ROtQ7gtK?ogJ80%9e3t*QE%UeVDjk3#Al6DN2M)5!RQUrQG' - 'VGp4&4Us%LCKJvzz^^JJ=!}q#>ImbJUBk@I=E%BE2@R2moJKhbj*oASk5id#SVR?}cq4}E4vmC^I?>n!fN@y?!zPe)uvCz@E=3~Y' - 'QAYU~G!rDKHm8vK##no5DT`|av>' - '`lm$`YsR~2#{)uJDDZk(+$~n0MidwuaW{aeoAvEqbPYrY' - '9#ijU^godiw;c`ku42x=HxkD~>fQ(LbY&3T_0kWc#8Zw<8n#;&;<0Gfceb!Q;$M&(uZu>ha7IRAKabaQF!J2V1ttP;ftc0hxufL1' - '>{4+JdE<39m3~j?1;Z5Kq&x9v*q)z^(-1D{68|' - 'J20w5PBAflS+ySxobAc?9?_^97{9v;o)`sB9GAgR!sjzO+CF!(Yk$p7&c2S;h$AM7tqLV3imi?&CPG}iWD^=}({r+1U{b44L|?nV' - 'spwEDS`+~|=!~BdHv!pjNjG-5PmZVIWYDKlxcN45iMFZ+g}hTtKPd*@i5(;Mhl&RVAhhdgSh!3)pQXCnUC)fKW({51+v1ucYQba3' - '0D!MO`(>Ln@n45S&oNj`Mwz;n1;uQ=J=>`VSN_WWI`EcYfHe)yR~Nb%T&ym1Gq?%GcZCJi$^O=7X_rK~+0EcK98**}9T;nHRpI%-' - 'S}PsJCo`)%#-{3*J_(j43-pHXC{9OiH`M;XxnH4ZxXy&k+Fq{9to>bd(12V9N@wet&%c1L*>@1;r)A^ktLgXqQ5l^@BMPD(eZik-' - 'b>R2#xGAlAGHo3$%NwQ|!ovPe!(0y2!Jv3A?^hQ=s8!lVy9?^|`ZKAE~n9VMi' - '!Bv-N3i8@glp%gR*6rZl%nX;msk}66Gkkv#({h$wP=q!S_O_gj=dlI94`qVSIAQ' - 'xE28eeTfnWC|vuvyF_rHT9$2W)UV9$@e+4a$y!8Co6jYWlGn0+EwE@F4(rY&qaMP>k)ag+<^EZDGPF|x>=(j2>tH9|tK' - '`NhP6H6(NN5(AipBb+xaU||pJkO3PwIY6#Bpct%*^$=Y3gEd1tXygs_&#s^#5)*o|D8l@G#*4l3gUCPp1q~$?KR0&6hnCdMwmK~<' - 'f*p}}50sr0fEiHSY9NeU-Ih{-cdp24Fwf^oQ>{a(MpE^Q}^(&_=V2&b6+_wBNw' - 'HDv831Mw~(Zz~?;e`%`a?7Uqbx4)aRF=nT^C^DIPm%}oVX~1j5<8t39n0E-ZLN^_gR-q6ZsJeh)0JKv*g*c6ov5Aqn3' - '%EwR78OBs=R5_#4qcB2D$Ict;PGJ=DF>6b-JjCRvK-mHIL%jAvD(J4;5bZ(06$u$mbM' - '0=3U(?BGYLL|Y6bHaGb&ws%5hEGXM|&=J=Ox%A&AJ6pGE>u9T{3^r)7TR4$OZU(3UIxp+NP{wJ3ox}oLi@;sDWe=UG|291j3=Pc*' - '=qd*=&_C@W22l@8RcYg!k5{Zc5`#%ag-jU~iXS=PbR8s9Sy2^3X$B;uK`=>5C{ooh5UEOp$Wl`f4rApk>u@wHdu88aP%9@hTs!9e' - 'C)!nj;zZQE00qf-dV^pjzW>S;^gn^nSg@O`oGPZNOE6>gV~xDe4nXC>*Vm;b%8~6#9uE0u#pw(Bwrqrs*n=c0oE*H3NqaUp' - '(uQA5(6}qPxb4spey|UBKD+nM>}@{iPdOdrge|1huOPlV%iq`WeRzKFR7=|;0axuSA{4@MA2358w|p3Nm0&BH6k7' - 'G8Ms)tqtn{18;2aI>u(&nQllS=`uSTd^FW&)nTh{MB^Pe_rH{K)1un&8XbtgsbgK{k2j%@?TdKO{;6B9@^{5M`5ib!J#d~|1YMe{' - 'on4jhv7qzrYoSDG|I1=V>E(oL@Ykw|8??$rj|D+L5+*q#ZX^RzOCfr~Q1m4nS~y;3o{FrILQB&uF^#@v^?A9VgE;!T5PzX)=Ekt}' - ')V*7zhYkdL9mq}Uj)|o8{x{G+{UTzs8?NV6i~JGB*-K1Uk1' - 'h49!F`OxRxgZ!h+UTO$kCbecx9Nab;+hG6>wj9|SeVYcBy)59{6xxjcGWyWRsXER+_XNKKAI4km)$AhNRy>oM$br+SD5OR@C1OctW~;)jN9w' - '8e%M~@=^DWe>(M>1#|N%leDq9OW_sl&suPY&$%&j{ecBtTRotWVD2TLPReeAd>0W3+LbGfiINXwqTwoR?zS51DI)' - '-G)C0Oi_49L*M*!8mW!Y=ar`|P->k67bwZamWA!O' - 'h4!{^R+TZ>*##}K?fw?%2Ma5Vt+1W44>h4Ab$SwQ=dc+NbhH_3S4k5f3ZPV@f*bl4V@C#YL*9ARv6<3f@z{LEB`r8Mh' - 'ix=KypqKpxNSU&B8pT^2Cg=j=;iJVttnt6pcsFJUtp992(qbf_aD-~iT-=g9U)3BT{8=2&(&&doPKpIh' - 'OqhuX=JbX}7=HyEd`Ql6)96cTklS*00#Zo))psOrF6VT=' - 'Wrs-B?X6kTTBfYDSrp~3*=?XCh7Z*D1^)R0qtF&)lV#3uG7yi)O;^0*Xw6*bw9s|0hPDu' - 'C*jp8>^!;&!u9D3(DqaKpOqw_KqXf$u6%hql~>c;-AbS7>13Hkh?4^CpBWqVI}8)8cOLv>E(|z7Ih|LaWKN^)f8UL`ub8TqdK*br' - 'Aa6BJa^=M79F_TpWGeiy7>OU%?2Qu}HAimM_s%OgPO@C?DP2UypLF%rbRX7BYg*(e!Ns_tVfCc7zC6*`uy1kp2!x&X*g$`~6TMw*' - ';5-Ykngyu9=N|8+SY20=+g|lu*60TmJDHBslTqFK^T`yiDPN_L9kTk4Bj{|zDohOq&NYeeb8zA-;>No$3%f_jhIe!J-VtuX)Hi0W' - 'GM!r@d-7m5=<*a4t$YGIL~N|ntL#*JHAI*Cr@4091;`WFt0Mx+?h*78P|?f@GMj(64|Q~DtvifN9n%_n6kR6%;`;0?u330-UFxlA' - 'r((Wzt^VTUpa34hd`G-&GV9yeg*skq3i60_;}L_90gli3#_;_?d+@gp^V+c3O}m(gFKOS%}w;Z^1Effpqd' - 'HKY>B@?oN|zL4+A{4}ErF0m=E93qf3;(q~*zF3uT(|CWTW%$h60)vt36sGnssyjv~UD$b}-cEvX9BIPuTWys#l6%%$|H6pVN&tH0' - '2?`>0pU!^ToVZ#&lJ4&0bQ@o$|1v&d|5==}9@5u}tObII+sz+_pmvoRswwK4o}<{#kJd#P!#=mdr%W5@Ruo*M@cGMU`s' - '=u%Qv{g%u$Zf7**v^T' - 'LY0hkIHXYU5K)z_?T%t^wseMw(ates3B?qXB(U4tK1wx3Z12JoW1D}QA;l6~nh@+{f_`4@C(v8N+Gv1L%hxfA3>^4vW7A#_%~ZtxH$B~(X&;6#jh(O=2C1QcDofu6P' - '`7c`eu3`oD(W4zab1@*p8#>8KqDPM+GcCArK}Hy%B;-c{>y?`y0eD1SH$--l=wXuaNKR8@&&btg$ShjYCC+IJzZ7^S4ka(q?P2)b' - 'E-DWu=5Yovnr23#ws>o&;!o=71LJo9N!+!zo`qERHQcqoIUuFya2dGv9nz!+9>+;n#rKHbn#d' - '&e-SG&lT~kuiVQ&Iu?@ALMUO*chS2S`%eyD?9+}Ik`+RtLQqmrLI`HW&g8wjSoW=F43<2{<>ulCW)(FalZN$3iDA@}p00?F9f3yO' - '0lrYC7}|Mzxbvbv)_f6NqWuN4bQHrSC3ewq8YprWz&tHnB98+?=xz6QVyhrGxT4{z@=9#^^S}{R0(5#`^v>S#K-9!FZcFn4)l1R(' - '8WmQT)$8T1MUo(wV&ZlC0R$*;kx+Wd;EB1PGYpB7HVdYUP)0}DE;$VC=mR%-h=Pkow`X{%gwNHr!vp!^1e2#Z-Rh%co`}UA{i(w$' - '8|H4bb|u0#)*yt{6;7%mKX2Q^0x--og5{*yZM6WSmCnBUAP_+)X3c7I*A0rjN!yoo!9qWnmm=1_cEbHU+-%twDWqS^H}@2dL2dj$' - 'P)h>@6aWAK2mk;8Apq}DCvzJn003~Z0024w003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*pZ)9a`X>MmFIg(YkC^9' - '+3Z!`4`**q`rzgH$*Z<54+C0@MO&_F67;L(vh9XG__1nlR#$)M^_o{zQ<844Bz<+=1jP+0' - 'ZkKJ<41E$@+>o7))FkgoIku}d6;w^|;R7^8>Z-ZUNWYjPOtQTH@BzvOXrgMHysqy7(LbP09}G8bPlBRt=GAo{T#+JQ!SCUQ=wuDu' - 's<{oIh>}}ZMKuJ=yz2Jqwp{|Uy{bWcfIY~YGRT9nn$JlGl^O1mGzIzQ(FCDq)@~R=_36}FAO!9%xt$-8D<5(1&RCiUwq1kM&Iy%I1m6%nZ' - 'Qz%}AEUrjdDuXAWUWx*>0c!<{TH#Ir|Df)3=txoZ(0**cI)}+$^|FAr^L8~XR|9*Om0i2c=x9~YJ8Y%MU|kJ_m(MGhjOu~F%Wg$?' - 'M%JqJs|KL!npM1l^?21bqHev&mV%HBMrt5;q8e4-$WaRelb' - '#W>4sIG_LJ_+9qbx6hA%%+6o`_e+NVg@*RJDew;mjd-~$_#p}1HuTOuy~u22viwc;zr~*Q^v)' - '2@OcOTTC&H-Vs{me+>$ZcUI$KDhahhRN2rN`sCfU&5k|wLlJ6_roQtC@nz{BFbsbCx%wjKCdy9NdbtE?l-EFanh' - 'RBgLbFJny%Ed^jDpI{3irL0b__f!_%lCB{&-9)4*jlxBrBb{Z16>%L`bg~U7*lx?bgD&N(yQ;48?q0%0J3HCSv-2Pg4#?iOJK5>m' - ')3dj4{(kcEUH19~ynVW}vr`}k51vaYgUc`kw#*i5bVM5iar07@XQNOGZU8<28p;Z|I54>7n-?!G;^6G~WgILoPGTz1DtJn$%tCBe' - '?W!sBt|I-B;O+E%N#-EVs-_yUEbK`=kGW($GPr6K?EMg&0@GpC;Xlv|NEb$ltRKlD5F{x_l#GODNVtS4v%bQ$HbROw3J6MU6##!L' - '#cb3^0QNf(1v?US>^}fAIS8AhU*-h~;dxOtSy}Z1^d5fXcjFIyXn|ucGSGASU@tfrdx_(fbmSU9U_oB3P=5$#V^gzOY1gQRco8M-' - '(dVis85LMS9V`SXqC$&;m=8*{qvPwd2ACGB{wBXC-6{MG#fU~GFdHA-K5`PUBJlLQ3&rroa*m)QRoVgZG$2b2G_3LlNN9U>j=p4i_*A=>2P-t{Ja~ZFy3<#G`*~!v*^-GIsObq`03$vf7pcU(oa3IpN#;!W' - 'W>lHiTh@vIT9wmblhsIfpDyQ#cosu02>`PI^)aBE>Xqp;b5AYnvK+FyS--20WuVaMv!xJCQ0efg3*Tp|3^1H$IqYi!+#{5b_>VED' - '2hz2D=s>MbJSy}nPyi`vWDp}!##AkH#(`nwHfx`idI>hv9i3' - 'LY~PNM`%&{$@8?TARHfgqkl%Fr7B`(V;UekTf;j{d%>c_76$iGWKm0BRZ&vbgP;$Q(fCJYHo5Uj|%q`%3R#Mvk40xb-$f?cInWc77i2%x}!' - '&buLz~1|c' - '8Q|RL5IFgsf@-JIEFHTKrfS)5REI7j)N0=YABj2O<&iaSR6bE<$(C_pZwa*r*ojoNP^4S<5UC5*-^ZZpgQgt}UZJ7o4^JkrsBO7M' - 'Wt&!9^>_T>ibfI|0~jT%rvJDi1bEwFv>A?3SDV6iuqig7Bk2OrrPEo>y+4Wr_>V51;OJ6AuT+t`YR5rL(dHzL2fJKwK=S`;M#%P@`xQGl#Bc7_4a!Y6K~JbnhSveqh6^n5jv>0u8rX' - 'q)=?m2;U3z&yz_zHq^;5_EW~E7XK)__%fN-Z9aq%%6VQScDE>+qD9l)LZ;Z#=Jb`4N)i-}Q&_=%tA-T#&*QB`~(8tB5jQ9M&>;RA}sb|)lt^`Yt6myJV*^y6GK~j#F{eris' - '!KHd168h4?0XVZ-Q9bT=X`JqWLE8`WWN1Sdj>sOfq%Uyy0aRIL;eoXwVskck)n;7-u^yun0c{4EjGWDHRHS?_R84w7_P$e=2ky0+' - '+-0JEI6?1R-+QQ;=LON+J)wqaSEG412!VqDW0b=X>10Pq4op>_dp6gOO;#B^m2L9RAW?{pLAC}GzOBP3' - 'cDR}5tGWiLTM|0D6$gjF`1EQnuysUE3qBpaCR4OQvi9xRY@&ea3_#%1*%8iZ|=e@3VUeG95AZhh$MQ~DzLesqp3=$u|rj)o8b_9O_^LHGt5' - 'p%@Oec|&{l3Tb_n^zD2=zM;+jRQh%`{MN2g3myANYXaBf' - '*btuTNweltUayF9I;e1JVVdY0`0?^a;ZUP-qjp2#ms7)Hk~qz9Oz6#cPQ^y8+H~0PXo9s-mCQ&m1T$8Sl4>A}FpALirP$y<1p5K0' - 'dlKv`TaaR7yHG*>BskEQ9|Oxcy_=}G+I!K?CT}@gQ6@v^#X1<(ku@l)U^zw^Z1|u!z`!@g+C;mN?)PK8FAgkq2i2wGATn&_Y_2bW' - 'xkNzcKEhi2dJ9a>fsNa&bjLfT@Ng}K3YCX$Di7CCIr0;F+{fuPd5LJlv$4jRYN%ny5Xu!FA_qss&s_Ju6@{FzfI7wDrH?yB4(#7B' - '0(Q((iGOMh&Qv3FcBC1)u>(?oNU7@rlDdIJIGR{f)rwqJC~_aDX3GG)_Iq8W%B)pQ_eM%FBy1FE78ENs-w$*E25+w`22o9bn2Uin' - 'q`z`h{Y3`#WHf$^`u<K!p|nZxoDh~63C8Bch(@wrvhX!AQ%?_-12(F6_YxHpf3FV`x~INLwsBF-E17)u*bYfuN`!5|~n|H2#C?z`h6r>=E59pLx7vmWzN#pu`so>7j_xp$uVcjMx+vgWQE^HbcKyL_d<348TTMNjyKdh0q>vo=U$gMa{BGhj`3_8~bPUUv~z' - 'T6Zk(IDT*=f*CAswQ}zP`LF^}`d-A8Scl|@<5_8$FnS}VxyO!QhBjo1tQZf|Maxx79hZ7`ty@i~-fM4NcH}c*Mm{U~ST9vNKx~t6?@?Y(ex`%b3=7DV@bOTTte}-a%Sd' - ')FtBWkJ6xg+*PaETUmheu83-8Csy*fN?}l;vTnx1#IMtd)gU{u8Xnb&)nIg@YRo=V&9B%6+MIZLLzANNT>bTB_45XP#5+Fqj{Bwz8zhHa0FJYyvrLr}s!Ny^I|vq%JS3R6tlb5+f`U' - 'kwQ982*sRu;vqqvuo3vd47)ZIVb=(8o%Z19GRd2J<}^HrgTqKUWe0w<2CwSlRwsx1ooOi^@yc^Bw*0I!u%TT~>i*@F`9g_dNVL(y' - '(9=qMb>wxrm(#!4mbk?uk?MyK-k<1Vi`hvX7thl`B-P6#0%{lhmua3?8*`C{n?#uft' - 'yw`tqKam1JI^Ea>y^K+xnpd{zCDB`QPvdm~2fCYJ(COvnRU`cGNAm9b`x>M#28myykN$l(`=2BucNj)SZ;}4T?DAK*-pFc3TUv`l?$;YK(TrD_`rbD2K*zn~aw4VBM5=G!`J`BjhfPeHhpgkq5z8Xb2CtUS&F(#%OzO' - 'j1D#!oSNSXXa01kZe0^Mou3cN^*CBd^rv2A(k+jXR{Vb3SUSG+^@OGYI2tur-NEF5&F0JRG0FgY<|)otj*tu!Sb$2yTvPP&Mv^4-' - '{LDo}*0-y!AndxB+!eH>D*f;5|v14zQs5erf>5v(V%NsO' - 'Os_G6H<(jn7DdyTdzxB;u}m}Jgc(l`LbnG#nNE-x8&zoPhccb58f3PloS6W5-!|zC8%L#$TGCg9GywxoX3pbs8~_#`&h!r>?#!D=' - 'BArMwp&q!IV>S!}%_IQ!$rcNoC8%JxY_C>T4NUe3bF56((=Q>qR-$o>l`Zby`X;M=Mu2&5=ot=UW9PV9fJ5iC!IvF(e&a3gFDp@1' - 'O@i`;X|5nbQKOT%Vi$Gq4=$rbL~k2+nPq|!evj3i(Zek$#|sJmOR#LJ^k%I4_q4-~!yO~f!I6>RVO%8ocpGUTIPElm!46_?r<+vX2!KC2WKZv)V^eIt+RVj&xeq2WPY2y52J{K|I1$<' - 'S5~%b8~eS-F6fO~a`3Xv>lsk6zxb0r(E3F_KG3K>(ouE{FBod)1oM;yH|^(uMQhXgCCq+6^5Q0FG42?Rcg(d$gP{2)?`guBlF%Q?' - 'xMGjHYty=?6nywFgCXn5?8Ap31d8)jJw!oGz?32&?C%+JrSUPH+Y&d2g~th4{$aKw*MI{' - '*7h@~J0Ct+*gz>7x(ZXx(Qearfr{^*p*B>*dI#Bb)k3e^hCzURf(hulc6EJYVLQ9Oz}@qQ9#(KyAGc3~5in7n|NP<@C0P&T*HpX(9Ss-<+<9YR`?$(W)ESCkK^85HvO2x=mJ7=!1U6K24Gn{pL1?gez;c8o>H@O&s<8~QN4i6Zrx+9-' - 'A@&p4$B}vD3_5JX?qq}vD0bwuas((c;KZ>f7%{&~?uf6?b2e;9mCVzPD3BX@!$pQZn$Zg-j&`S8ca4t_M*)ow9gB8jb$Jvjwya!r' - 'EF%|9(}?CHdOT?)&5HOBmE@2yBbp(DTH_BV?Aqo5#abAjfOXn7!n+jT=G8$^aQ|X{YuL#6PI!VxVX1uD$' - '5L7wfGF(*{e90k9Lt9QKH730C34G=a^okP1_#pDD9u$v}+<@7-$WdkbQKwhZpIgz+N1%%xW8m~cNl3cXlSUe5vMZ9@9G2945xY*z' - 'lthodss24C(-*>IP3b#Ie^BI2+febsrk9$5T)n9JBJauocNF' - 'kSWd#vmsP#1d;OPb&>EKvtgv?8KGxzt1?fjKFdGlRgLP0DO?pWvRDrHSphl?F{yGauW_t@#no@yj8ne4Y983{dL?$uEY*!IYis?P' - 'o>v&DkTBCJ^4x(Pn~W3|b+2tV4#3$&11u_QJA+;9vceoxWpy|4!FGr^T50$8^H?L&QZCKd+MZG24tf=X)Tf-idoMsQtpl0F+sDni' - 'Cn@xti0NVrnrF~p-Y!GiQA%|#FRMWl2rsvC6xU=3XC@3FVJ@l>4bO3lL(N5Y>%!y!b0A~RP-GkY(8wIWb;G5s0y%ctW_HkFn^tuk' - '82w>{O`SCJlL-T`QgjDL1s^*Y0dx)QW(&`_*$>JP%X0fh$Bm}lGxZR#{@5}&)3irC=^qNiW(xV_m=9NnJq$Wxt_+>v5MgGcc^uEI;@KfXHn' - '?vK<=S>jO^9?=CeH_o1PpJ+;FX28H^Up374QYLnl$j95Xp>wkgkVe4E-Z5?ZW!JS`IFnHx;^2CZi3g0GT47Ikmqyt@4?>cedOf`a' - 'UWhl^zjI8WeP%%|IA;p`aKge(#K9#$LgL4a7CR72&KfypIDRY_;n0Hdm*QzY>5Ft60$~UOuU<9Pkj|jxYch+_s04fd8Yt+GoRbJ8' - 'EXmmVed^b3V=s){&3+J(lf?_5?QTDq%oF2<5WC$E!4?F)g(U*L0jf21tR>iM#SbkHuq{r<`}=^E+92QDeo=>whRh|yNtS;sfv$K9_%#xe7158#%3vBr_;u6' - 'lAl(F+Tz8Wb=h2Ll>an{D^%GB>keF=XxuX(z8aR+`{CxBzq>%%xeXn?HfVFmImoW-WZKo2y~Jk^^P^GNp4AmIlDKAN!O8v$ZI5$ToBKz(22Yie-wPp-M%' - '@6uGjjfFH-gNY`3P(Ka01XGC&8CSyAEx7A}ClXz|KYH=ypK=}O9MAQ8m4$dWH8LIg1(nSqH`>kXj0gRzOX#(xp@{U0Ss03VG&xhT' - 'IvaR}dhIunLeII}wJwLPRg_w9oE0?cQYXm{J%^H?D$lNnxpm*V*|29k>cx6#CR<$>Yp;roRQ+N4j8$oo>OVc8DAZEz*W`G3zv`op' - 'h`t?D{ik_7MXd33eDr~^;WB=^2u6!c?($gO$FZ_gT~|I_Dv_d2ECifG{ZOw1qBz3JuIUBT|-Lyj}E3$OTEP$JyEWqB@zFuahnzZq%znph&_6}?`w&T~0R8}-1{-*Df' - 'w>LtfYyF0z$99>=85&oQyhP~f{p?$6ya|1fobh>DSBw|l99EQtprRww?oQAkQb)loDft2;hlkuvhJqmY#%y|`hxQVfY=vIcXU$Bk' - 'qOrQNo}9aeDuezR^sI5LEt~-79' - 'Cd_k+RN)=5j|ruMGxJ$>gt_|_Uw9LwN(GKiZ3c3|FJui' - '_NC)QvP>M)(_8UOm=Pz$aNF$j^B0nalT~)uLuXEp*iYT' - 'v@Dx(KiS_P;|a9&Am&HE`x~0$GG-A~1|c0EZRGaa#pWt?`+#uz52gIXGbQ8xBlIMH$n@W' - '9Z4nL9iuQ~YYO0aE+aRTI^zI);|a!?>B9w(<)?{u^&Q4(z;$ic`DMq>b!Ye*JfB`ui@l#;qY}wwfiqzgnGxZiv~*Y#l83@FFF5^5' - '#RgUA-SKK59jNRWYbo=MQGfK4;2_#KF33bhLxi03p+UN*jD;2{bO^82cp5uKM0pas=|M>yDeenA@}0TCS)>xSH;bpj48`?3+6fRd' - '7$~lF$R3#oRr7G%+gCrRYRCn?_naE=MmN$l8btF-&5$KYN*^$IdTcSG%8@IFtw84ZY~aJ%Zw9)$smv&LO^)lI#CeIkL-ipZ^}uSp' - 'rqSd2wGpEUxrwZSl&$^X`ox1`L}vA&m+i}8k(^*}Y;uz6Gv$LOZAXu^Rf!krpG*7RkINVoMmPV82n^_ca`>$$PD~6CtP-hTL$i3D' - '%iSDWLUd8dPCpr+lkrQpdm#Gup1N@qtIqL39!JiJkhUfTsQsZH*Muva@R9R=Thq@$@PV10eA$;tsNEoxfaa`<1)$Jvf}nzXJ-L?f' - 'gu^#5tV`GvwPR1zZmJW^3wGR4o?v@`}9' - '*ItJ4K1!%9DGgVq8TlYTwrwOXphu>m!&;^6G=1=U9>vF)Q@_I1b1ZHF>G$2F{-Q^CK(}yHZr~mLEj*c9d0|oCtIE46hv!Dl!L3}4' - 'U(Z#zfnU-U_mKJnY@I;?seE!*(1Y<(`;{a3*A7Zc9q%0NjRA70^6VHF86TI9kFh6ynyoBMOPw0AfulBxtznNweJAS{eVK;)$-%y%' - '2uan}vG)zXqR;uuzmG0I$!#*F_cQSY_3;DUvrFwfQp2$QO$gayAZ8Odb4lf}j4cP$RYRS6v&h?a(tx;kc)_T>8;`Io<%ayoY|QXy' - 'w6cNGc0p<7?9K~I{$W;y-HDZLzD>yi39r%PJ}L2(NqVO|`ux+CK$jh+obd0<(vR5HB>zN0eRTG%50l%TyiG@9bE=Fcai5SLv!hZu' - 'qbxxGr{g!GFm{pTyeh}`fXlIKx;bX1Hi3=?HMW%I4_h;8x(TjUYjW9zN4{kGU>' - 'OKi9u9<>#IjcxEXwm{^#+#nXcwV=)99PejzCh&eEJ?^+vW821qynULkK^Dm4<^v{!MS+D}vrs8=?TrO54OI#&f0+p-vN;$7b@gCh' - '9tjKWO^`gZ};X&tLdDkD2*' - '-;oUHhs)vcS3K}#?goo(&b00ZdHp%R@AFd*`0BMof@f@QMqhAcM#+Aqrb`XcOcawHI(nx)-' - '_B%8k0xYQ6IEtQW8raP8B3K@7XMH0Q!T@n;iFQk>_*Xd5kwc)qM{-c?i&0`POpeNc)!YkpIhmEmnfcVXGoA)d*Wde2gKzgYx=#*z' - '(H57!f2*t5Uvza{eq6qk*|yLmnQeA^coVUx&*R2&P)=kjpKO3F4-P!$f$Qx?LV2UJgbmIS9(9;8&TTqA#)iXAOl~?p>LA|3gh=0s' - 'Qho63NO3jH$bzfvO!g@uWt|<*{w25V*V++=Do(Xgp(~xS!FPWc6nGiV3I1ayFw?CAUsTJk}LV)*;?y^1@;ndjpk%Q;HLLv-oZ' - '-0dy<|8#?Xg$?j$H2JE+$YaXVC7~oPs(HwMQNyP6B6a>EWS%d3C+4haR6zruJUN;n#K)p)gy=BCf_%%rFD#7rMSHn;3$?rvZRXYlMukch8C1w2YVcc2qyW2!ahdT>yI' - 'gZo=hp-iGeg=QJ)gR;gR)K-DYD)f#^?x9$l9~~&CXc~v;%V`q0Hl%4M%P^TaUYlSaB%bMC7~(Qu=KX@C3Bb%}@peq%X}_UKJ>m7`' - '&i?^WO9KQH000080000X0JV!fZ}1fW0Awrx05|{u0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0p)Z**m8ZeMa`W@U0^ZewL%XJv0~' - 'Wpr|RE^2dcZe^NVk6lHQh2Q6A_}P^5e(5(FTc$M(ATBLI3n4cY(@5Axwn0KI{`-7iu>MB|2^}}tCz2T{^|bR`|p2x' - '`ReCi{@~_^yPo{{^vT=%cR&C5+xM;$Bk#BB8`H}+X}9lbWz?mNdCe{~IZF~@GTmcI9>u|g|rm6|&1j#WzMQrWxC$)!C<>T3@c`1G=eoKnrJ' - 'mbyw>Ww)m;nr2OT^`6!&D^;&?o}Na|DeqicFD&s{*kRY5YT21rv60%J=DQYVDQni%5}Wf38Pn{^ts`v=x5{@I_rot+cb}u>ag0~FIyliBqwKiGld}}=&FnJgdSg(0QhOeoKZ{Mo?Ug0=#vzzA' - 'kI^vcUcIco=E^v#&+ZD(SRvi<`&wED1$bf3hCNckovU+%R%hvh=PT~=XRNn(TiNGd#Pd0`>Y6jw-gqE(kaHQm?ZxdA^Sgsl*Y1ns' - ')rN6h;9C}`cWLt&FJo=YQ+-(hHbqCL$)D|tX{R;dJ!}Xbh^vv~' - '?p*@u1SP@3f%ql(m={z7#)VcClG0QWTB?yANNU~y-Fbnk%mm!CeungA(8O>4NYi{s@}W3R&9ytIP))nk7pvk=Qc' - '2VR~xR6*Q|3Sy_@Hbc5DSc1KbNvz<$h>R*4z#qhsC%AmTHBrEGYvYzC@Mb{3ATu@$ZD5(>n$cXvQ|~E{vm~pR{$c=e(EI-x-m1jfQ2$Z(Z;}G;Q2Xu#OI$Kr*b$+kYzZEA;1L)Vu7%>dvC@i4=x-`>~PrB' - '^MgYR5GMY09h-v<&tAJeBVyqxUMnJ6aVL-i$THwu;8{LQ*Ji?A6V$f^mAl)-Vqhs@iM7jcg^??}z|pzOUI!4LKLUct#X%ap54Md1' - '4UcLXfo6;|0^CfnbPH~r#u9hlz<~p7P%jOFqnQu9$sz2e6Ou{lc^Z(1hrk|Dw}9xl0-|9i;?N+hbM!+qq@iT~fqd}m3Q%>Tv<|((' - '8*9%uh!CccnQ~t#aGeDRcO~qz2S`4!%qA29eX&T(50J=rq=^zl%Z3TFlYAgwz?z5VG00pM-mC=z0)U;3J$N1nQxB;HoSTc4qc~OIMkgk#QF!*{;wSJ8#Yde~OIa' - 'c`%QWLR2kmGJ}G6z8UHg8*Fs(sJbj0SX+4z(_oUjr;K_>V|}%bz!g(' - '2l(y%AdSg52oScz}>v?mr3r0wo=+>5mtm+%h-%4!l5@yrzNlfC0uClRZL_HSDmEJ?8NVZM4zGW1MD$1A$P0IOzfi' - '9%yj|Tp*W_Zb^8TEFtm+?8Cd()f4Ox8=#vC@9S{$p>5C!e$+Ghl20%eVLsIoczBIRJ%TYNI9{Hd>~u+5I1adT=8ZY$IG{JoRhZ#KyrK%d#+#88?II0*8=HluI4S}LBOrZ(hz6k^S+}?X?+Pk7YIDd?Q>Z2J11}^)2N4))g|vq1K{HG>;SK#0c7#2dK~2$(ha`z%UJSz9' - '&^=;viAzLMmRV{T^nxoaVVZ<2z`+hqscZ`Qjd=(_Fd8le-8~|xz&CYVg)SIVSmBKwHPC|w!lw!0im4CK$a0*ftKVVQb(20&zg8$)' - '(%q#-{w7)nTOe}5)Q&iBsOSzrIa@nBxL|_nt8lvDD)R^cK!gy?vIlsXGKaO>G^K%u^}i7T`9i7Wc&r%JhV>XeCz7!|Iw{VCqAH|b' - '?qeU4Z7gv?^=L>HvT%+vPAI7RtEFOr#_K7AGTCn|NT`t4_~Bscn+HsJM*OZUl8NB#03rah`~pYaTBAoMCGA_FNbd-hb|We*El3`+' - 'BI%YJ3}`U1Jy9q6W|$LI2o(;ZUM5VrUHt)R8_)!wQhn@m`XzOc4Sg=1ILArkIkh#%f=eHZNirM}2_SQb<2c8_x44D6!*WKoM5PIu' - '7B_>+Ub^P^_;1%ts@UW5bwysM@A9AW}>98%B(k2Z_br5c5Tib;_<}(Sif;7Ve1^NTZM=M9@u~C(fTAd85L#X5rpB4+xq>6GbAj@=}ZOOLn9l' - '&tl_B{pgr!ctFQNi9t20IQS#l1Z3Y+BohKi`Hi&ae`Sp;F4z%2Q(l2llu=^o@`qiKp~#*H5V3l?4GEDe(ke_2#@Phe' - '{M#qbZ|EP^7-|zXf?MlhS%}YMG34u(PibnF=^h(8E9;j2M`&KC0Nxavp$Vao0;>~zgvB~}o-l%N2)ttLx%Z88P' - 'j!DL)SXmM}?hOH4t?oGFxP<`oZZN=K{NUY?$OJ>c268<`1tn6lPsOG(aU0k=3v|Q{QjHBy`zT?+6-x}&0l**O7WsUI_)zZd' - '5WFwSrGwQicM-B1=Rg_fpY)eH*zN+P>2{to)p*L{)gl9B=r?}-@YG&y!xQtg=T)`ka3b$(}!8q*8gsq^BW+1VF' - 'vLp|I4#<4jK!BQuEip1g^w$K(C(pmWHWevaNi?>Ux+PpbhqpDSy5#*M!j<|AtFb5%i70-=2H;BCj!aRFw|+zsr7yg$a52Rnm9k|4' - 'sycXq?7R96ZJ2jtb;N4nsVlXqb8#>-B2w<5V}Ua;c9&q#n>Zk7i$q?D4o*%oM`wjC7cN!=' - 'ZH-txmCPv?IM8&U;#wFM@k!dQ&;g!=AhCoU&hG!`$ettICa*_B5Sc(kKA?g(paizH?8%qG' - 'mbfMee33&gN+q&4yCB>wA#b_`Sa4a5u?I9lGSV$*R!1%-<$60sCB#TxQdEM_e!TJQqI#)?43le8N~TCu|EVM%*RC14RdQ' - 'D(oYZ9Ymqqt$<-f48J26LR6M3HoS|{G1?|7Aox4mr@Yj)7eIiXVHM!o8vN88%knr1Cn9E_+C=K8R`W`PaOkI}nDT{GV+njNQc3hkXH(1*RwO9|Mr}M&`~my>DXvKZ9AJNoJmfoBokZcc6{EhQ_5B&!viW>+zChZ6yty_&oq' - 'z4%|xzq%p{5(~Y-A+V^tZ5vips{``s{E(eY#(0;dQKwgy!Hu>>viTbv9r%%^kgj0+6s-YwJ>J~Hva5BQf3kg;?Ra4$?2$kE4vxz9' - 'f?J8g==6SO6ciGb04R@$sP;>;ZQdw{f=)=eoCm{Z#qB9H(KQr>(~cq*o8_^tjZE5?b|eh&{^|BF>n$8QZ3RMJPa`R1`;4SY$p3UJ' - '4o9G(sy)#dE!3mXh%ol=E^J|+@dw$Y8pF68E05Y9If8U;Iar$ASa{XgwnbS|3H_O4O$zO4=hEVu*Jy<=pIw;NHX$k?ShO5xL|fj*' - '0h?{@G*)y`Y7eyDL4j71z&Gn+Nx$;3bk&4uXAw%O)m=CK`vhgjeG8LIH;wmUQ^^oe~=x1I7dBV~|K$eo' - 'VwGqESj{;bRK9Gj=XRsI^OBcYC%RQtvk?MPbI6*g`eU$UCL`On7y=z^pUTii28f{YEjA7VL#$^lrHECWL_tS2)2zA^2*z>qT3-(}' - 'GK{X`-KTYAzb0uPJAnw3INFHv-Uvas0siYGiKMZlQQQRZPtV27Q0*hZHxaZFBsfP_DVfhG?9%nYE=`4??s#bLMqS30Wbs}~h)(;$' - '*{a=Cly3@j7G&?G8_Y3C#BX-9tK4delKu+t+H!-<#vHKz;p#aM' - '?PgNUSy;l-oyQ5CpafoCm59|b(h(TYU$P(0AM!|DkBxHM$WZ~I#7rGIWY&yM9{K`_d(I_p@dF@(+pflhdb-5{@HC!J*)!~|K!' - '>LU4}xw|ti8^)?^pTEF?Gdh#UXwF2+kyQ9VLrAqHN;<23Xos9kqJd{ZO?>t77q^3pZPiR@0Jj>vWUsxNw3;)Nea3BFnp3RQsC938' - 'Px0wMvZ;x-KesdUWbF!kn)w9Zg>k^F4CQa%6`+Q1TbjmZ$&=E2zTzV=ZZtbq)>fVlqeKaSmz4)4*l<' - 'IN5<07N>B~SmUPAQSC}4xoYXbC{M`RQGI789U;Bdt0jhXsH(QxmG4M-BBg{7ZP6OwrjD{oY82y+l`*$m}YY(0K9fM6we+Y#0IN)A@$g' - 'l072~u}%;0#lwe2M6$xeIVzUW@Fojq+0y9N3-OUg3r@pHT>7L)FONNd?&>S5T0hPSQGz0f5!kfNk{`=ATV_sFYqU|)*{cIyP-CZkugl0bK79b`E#VC6?ugHCzPR~VN`b!g' - 'pPh`rWwp#+qv(Rojm+8wsaAd_jdLlAjC7{I_bod%vTkbmk5*vBvq`' - 'y0rVf+L^HJXwy&_uoUDwZ{&KjeIBH#P$;npLUF~1I}bC6#F?r^1FBCpC^yqEf*W>7#$xkJnRuZ|&f%yZ&IoiZ3!XxKi)Vdd*&D35o{%8o_jm;TB$YhQ&@=u{B|F7N7kn' - 'qT>WDvL83KNaZf@aeVjHCD1L;I54|(=crfhQ>92BF=RFw^Iy_&N2MAYUw`_aS9O@4UjDsNd^eXq{eM?J`#)D$@b4dfaxEe3?>WOy' - '7K-zHWf_hB6k@_`7F)&n=+ER=J<-Q|e0_8%dko7{AEzy@u2h#*g}9^bQ|n5Eq+Gp_Zsew4Wyq2U89N~nZB3nS*MT4XR?R(Bo5ygI' - '6Ju^IkInY@5Y6g37r*ubZ0EZU=Sn3$VjAm`(_#AfS^`ds+0kvcec7qY9UG)p?B|d8+~l!aQnzz_K;y8I3>@W(lFv5euniPCQ9Yj7Jd_v%h422vr|*9F*Zr%PelPs;-TPmEdG+q@;X~iMxA#BY' - 'zrBC`!~OUFegEdw{rk6n-2I-P{P`dM15ir?1QY-O00;m803iTDSq$&;2mk=#9RL6+0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPo' - 'aB^>SWod3-b#!TLb1rastyo)c+c*|}_pjjUN!oEVZBr};8Dz0(#yc%`+!Tph%)l@dMxtyr5~&rbv~H09zK2&)7pG0P4iH<^x$)z<' - 'G6;fq4HKlGM~s$xw76RSo0ZuMloKr4ruc$I!6^Dvh#hMM5<7xk6>Qhupr)mws0gZYdW*Ltp3UCS8b}I`7$pc-1!YG#Ei%N(e_KLR' - '5~CHciw0Akv!>FL$>$o=j3a{69cl22UtA8e)k+4VfHV5h#ASzgpM#mNB=}wk%~1J&D{GXT5$Mv4h|{i$p6}GHYaMA))BxUHScmJSdKt-^^b(T(Q#tW4Q0jeG(aUn>>LvLFXC|FTo' - 'LL`rW@V>8Mgwyw8)q_Wt!U~tN){)F&<>R$BwT&=Cn;3P}aY{IGk`LX39d>O4zUT%Et+VP;L4AUL-hu{TL12nA?GPk4(5B!l5uz04n)4h}KsT4+b' - '3;I!SA!Ptv2$U@@^}~s@RBZ#kPkdu4{5h2Ea@eCsKcVC25&HROuqr}-m;bFI11Wr`umv94?sM5^~cK~s8wZ*3vh*2>Pn5=%uLMIlF)q{yb(&;^*9Fp`31gMrd0R7zl_1&9BRbT)Ga3^;=z4' - '_WR=ID1gS*OL$&LpH4;e+LyoSvq64`?)_9{Z?(f{x)8bYqxAG+R`JEShAuH1*Pi>)iu?o~UcpHZy=i)>7YMu3HBClJQo)h=l|@wo+V~8X$0(`84!i5gLnl=$Ffx=N&yiG)@wnzY1j1(BJ;y=(G`>p9P0R^bX8(gP%Mpu{w-!LU|O8' - '99M;y75K>b)$?YJ?9?pgX>cKGrH_Wj$p{%yt%E;+4@l' - 'qor>|6ixS@Y9y|&=jhW8612Z0I}q5A63C%FgSk@-?q^^>V70UkUZWkscYBjUEG{#DxN(3Enxqf1Su-t?yT+-Y?4lb2jP!_9wbbuTb*P!W|r(M)OB?cUwNc%Lj6E?2YUEX!RnadVmHmUL+CjLSi#>3DEnWJOKKx)`M)$h%)8#!i^0S5vczt0P0>>n>XPMr?Fk@1&~' - '&m|2d;jrm1qes>A8Uv7Rkv-9Lr5;1vQ<;2|3iI1$c(M1TFiM^(' - 'q++Ey7AGTb%Q74Z0UsXSdVdvfD|q7gJwjLFoC(ImyxRAuCha0OjCvwWW*=cOT6lh-T8-rQBkc*0t2EvNM&(R}i~2Qq*9{2X(}2d!' - '1>M7dVvx!V>0ouhVxgp%dagWO@BWNbs_{U1R}cmU*hA_=cMIU5' - '1J(^YtSH*tfZh*0E=Q@LofoNK79B`$y<4$O0lVq?Nu4>)Hh)PR|Rqz*lQ;lK292)Ml89~b7tCM0g`UgwH72n~2s=4Ep2rM6oJT4QUPush_R!h*EKg2kgLfIv_u9di^eil#^#Zz#F' - 'nyhNO-RQc?o3og;*0{Fw{MH_~TEX)$);' - '(;B1>)KR@GI1W;ju{~dD=x{M1f-b$$U)y_W~p(VdUd8MFjDkuH2MkH`cyJ=>__P}+`E!gmpQ=5f{&7dvr@' - 'sK7Np%y*7E@QB!na>Ua>j_kw=1Z}Z)v)&Gg!`STEU{6zcF>X#y%!^|YYc|+3|F8t1+^~B=knwKU-trX7)9c}puy7y5SioJ^+|#BL' - 'M_7f!a_BFM8zIT;_NZVO0^l>6o){YBO?=Iaq6*ZJ|jVV#`>oUhw^n79-);f_o8KJ25bzqnzu' - 'jA6en7dQQ-$!&$AkeTa)i}ZWSdB|%kWJlrVS#H=@ujZTURP9>mD8QA=p-enI~EH9cV|hQu)rF?N}?6!w_iWzjTtOR$%#JLTwtcBr>DE8r@N=I' - 'ZJS+^=i5VnXhohU!xLhm5p7o+Fpl6o+RP6UhQWb`thgdFaG=GtJi<|>;KG7^4G6l=dXTv`RwtJ&;IxE>t}y@p8w^Cm#^Ue(B}-`{Xvg?LjO&m!}m?S' - 'Ew7d*3H+^Vb_a&w@k_!pZ@M4Ju9yCle6E`lgpxCU*}!5f(equ=z0F%(IRpCP;_Tn(<+^S5PM@=fk9bg6' - 'x<0Q&@lNFXwz+irng*@dSH(Kkq}li74uHd?y1qrSbyu4iW7X_L-5+*#C!X1RQC?m5`9`dZo8j9|hz;ZVx8!+K3w*_tYuW;)S-({?' - '`FHcUsP@-|f%Usb3#(6Gv{%B+WY=sCm0*PR5ct1A4{iy|&>!}dIEM~dvREuG0^Q2G?8~CcJCGm&Lv^BGCYMc9L8I60fg2Tvz5)3u' - '*ZHOB`e56qMb&X*@eaU=0#_WkBhcWgEjA^PIA2%g(f9eT_>k94Yc0na(1;pt#1_=Sz5$eQ8hJkNM72$m)9+}0%(uKW)pqetwB`0@' - 'o^szm5R>mjf8A^}Fch}@?Ry|%H(ysEgkOHizm_O;1lW@b{0H+bH+fy`K%Q60M-tox{!F$_%W@24mI94%(_7v5P`|C4_qFZ~B%Sk+' - 'Fq#cn_b9h+CxZw6Ym2fI$zO`{f!`y_*NGyQB?GCW}1%6)(K)WsJ' - '*CqjOMb4;MBPZ{#%Su@A-U>mw414VlmsPn&T@4(1Sc9enT@DZqH3+9j8d$Nm+yGs0$Wv$oI&xtXqU=iOTGVSHiM9alpU(oKFiW$f' - 'D!V>)$;G|SgFP-_IcUc7(N>07t$=;0PGEuOif>ZGs5=4uHpHQsnc7lq4J@Rn)sc)Cl1;fXs}L27&07' - 'Hs>FFreCtO4xVeEM)mTK+fn@{l*EtgxSH_vctZHX+fvPrhUzy!lH8D@Tre^EX(DW0pskd3H_TJ-ACwl{Ty4f$hj^V9C^$x%F' - '$so6XO$7PdYi8R5s;T|VG#}+{cfFzvZpkZk&1lz569kBCKX@%I+{H^#RB22YUV~ayvH+Yb**@7Fx*lw(qyR<)GnYXvJZV6wYuKt@' - 'l-(BhUood0no&s8Ng@~+mSN#|Mq`07eL_;wOk}mHfmc4~MNMbTkkJ+%DA%x7-QA~S' - '`Y@;lC$ghUqbNI~p(f>Du>$tIw*r#G2KlXOlW*ztb;JTD$N;6S(e8xpOy~jN=m6{YPT0y#voorZ^U1QGn' - '(}p7p=`L(*IvwHJknYq@jtqSUQ`DyJVaXt#yXvdZluT=Bdw-l2n_~BVma#kKcFGioLw-R(zCexr1%UY?G_o500Bmb+`Y=wf2}5b2S_KW`iK6BAvSjN#BwN8zZM*s|<2wD0jU1fhgBTXy-DmO-k~mu5Mx@i(6UlNJIWbHW;7I2wn@`;RXIogy' - 'kVWn*fjFp;RtRc_5o`t~RzO`?$Q{9Gft~mBDMyE+NRjO!60zI&H^)dUojIRGTL~j$pV3{(sxTPRlbvqORuZT6zMei%}Tbq|Fp(gLd{|W' - 'tXZzXpeC}P$hgsu(DhmUe`4K>&5vabhWEU#7e5gYIreNed(k$%C~Md-Q!q7PDwpjOzBze|z?4+afWXi)@@G!6lNM9Z^z)*i+WS;' - 'P`n-;+xmGa3n}RD+hU(QdLm~nfjO#*WtCqm>V#e{TJiG%c)!chxX9-eH1oia{H$oft+Rt+4bz?#6n`B+VsyExN+%;ce=Px>dLUMx)-(DTi++Lu_iB7cdFiuqT%HLU37BD4Pq?BO=O3;XCf#J^%th!C|wCMU5KmJ59X>7hN' - 'uMRCo{1@)z2?3Zr&BXE_=37x<)&RP9@B*str{V`+(pG(;=99WB`L}yr?PUna|w`ER;g(`ppVXE6G<9b4?3Fl_5+x_comk^01cxedVCX>l;b>mE=(V6>1N_rnPRxRoMX_<(G%;R(NL3Ebn`c' - 'u%oL}BQncImUW>=>x=a@>;|MR=Be8UII+WV>wbl~2Il1_qIcLwk<;|OKWdym0D^^rbfY(C25SOy_69YG?b$lr+6B+m4tNn81-Ujn' - 'hTNW~4~3xAK$QK^wC{^ng{VI)-7PjgrQc#(wwpGv4rc8xi%ujf8-e+U6x*xDAdsf@MpS(P&F9e;{z`h1IR8N09uQ?Piodk>hX9*N' - 'S`o$%7!?BypDyUnA@-Vj$tOk=i0Gl<85n9|dvP8#`WZy01mozb<#;HJZt=XZ%!tDC?!3HraRp' - 'M{*59_P*M6NL2MW05Lbl&c?~v(w-GP_MLnws+Z1`oODQ)w6JlvD7#}wzDM94D)y!(' - 'o>IdyL&-No4}$6fmVZ2r^4`vZKi6pIQ6`pa=qmh}WfT&#Ae8Owqipb-_Vyb*-q86w`4~s|_NcqTT6aqjU7|@&N+&O|mZQ6@^ee+x' - 'E{Vus23FgZT}LUhU@1#nBkS*ol$O4Qo%;9Xmoo9j!#VY4X=EYiK|MO$Wdx1yHBZ$QIdcRa}L`tC4kg<$ZwUiEF1R' - 'l?+V{nZr#?I_B{8WF=#_3~Zkg7MI&_icjSkhN#ki(?MbL|fr' - 'CYhaM%ZmU^5QtwyeGh;>u)cXKY9MXQY1N6JRZcU(hCeStt|k__J)Gf6OkNF`zJumY`mW`$^7@)QJo|+;HLwR|s;HAhsgQ~9b&@8g' - 'C7O?ySyFlNh@T_bn*$+#ftHy7>k-cU*bPtG8aO&kfEjIZR>a>Z8%jMuQ{d-JBABxmtXs|Yxcl#m%?30l9ef77s3D%u^vJUdYhv!|' - 'Q^2447#1q)e!w>HZ0;hsFkN8`AFjmlo}i(36tWB^q5F#4bkb^yFyH9O$m-**yGCZPGDb3B6Gzj$+^E!WGlQb4a_hZ;`>t6Rmz)8y' - 'GEsFo-VnOEjmP!_t' - 'n|2o(-5EJ%>8L@>n$nXy4J6m4Z>*)6KAzp%%CACZB~1*Agk(Rzu+Jl~Z}$|j|{+aiWSAN' - '5JD@urh12&o;MinXlnHA^WvV;;?mg3vnQ5kTr-j3i9L@m?cO%Q6i;OXkEKTFo8' - 'H7ExTW0D0x-isc+UBYrbxqEn;2CO3&C%qyoISY4`mHTtR0V8O;>q$Aam~0ufS?Ok*6HTp}t{VhX{~5L|UHNmbmSpno9E0)&7TbPI#fcS_YUSq3~5J~9!^@}W<_MCTO@ds`8eK|Eg_x)PlL5RLGMMwJg|Jl&a%eNHa)JcqxK' - '*m>@Yish6DpIz9C!UqHGbFkkI9Vl`s*?YgDHt{66h|C1~`K*E240}3IJ{%^E*jI~{wGHt6yCE)h?cQF_i3(oM*879!r84pKT~QxU' - 'VZ$JEHJWvz;4lRed_L4D3GCh5@&jugN38^65~RPHx@yqOk#ulgnwt~r>yocIqTZL|(k;kMPOn$2!fyUR4^5S{uo+_TbE&?t1}7~MTcGlcs^OJoFPIeSUxy+_h2e&YDj`+4i;H*G_#HOA3mxCUz&X;i@qlWqHhEFZqc@yj@f7v' - 'OdwckIdpH0h5k{ti|G)!hw1+dBqzi>q~TX-@TpP{53sC6aQ+RLX!Rk$ku0Nfq5Y+oYi`Lk3Y+iwG=A2&Z`8lG#KZdS;5<9a1|l->' - 'C1`C3>Tt^a0Ll#0**ai`^GRx3!sd*}C>~I7-VW' - 'ls94^^&dx4sIAn8*)kcdQ!J5(j&`k|jWPtUlOjCeBRN6@no>Wqq#eS(jkq9GlD;Iy-PwQxo#+%%^c-@7dNuH8cd*RDXNQboF2`9S' - 'U<4UUF~$KWUcS28o^@tA4xCOSf|>nSlJ(XNG{S)Ijywt$9gb(!7!J?^0SLo^kfCuAV@OW1vEiQ&`S~Ip' - 'h36;i1fUuAy%;z!>JCd}S#ix+Cer7MRJyiYEG)~MdNMUidpts`VzQKdHCp~BMwj%H)SFzgaPpVGDsDmn5%`2N57IaFYaVch$I?HHE>t~})V@QmXWa39JVADqE__2zIib91Z`gO0d};hxfr?A;e?yJgZ1xI3I7tmM2O6(lU5k=^#64w8' - 'VbWp7F7#_*rC}|M5&76ppayHRsILUyGm-4jw?fc$U$54Y$}~B2y|rGto)IT?W9Q^j0CxcjV9R&#*ACE4s;1cRFsy@+n2Q9?rEs(}' - 'I_(BH9Eyc$#v7b?UH6!_HxSFcun}KD{JU&QC66z-&vJSG' - 'E^qKw+^CSIyljt8nVVd_C=+{fxyY*L`AO)IHat!1^LVahnh)WQmUgNcm9r6e|' - 'zri>WoaLj00BwXGxs0j@C{Cb7MW=T-du#?^)^GO>mZuXR*42T^L3NEU#ELIua)P`vW&JGW<9(WxxU%%=WEl8#h-Ku!ii2lw)Tg1SCEyZ1(nv^;(FR4n`|DidY2h_KKsrcP%Gu-inS*!?RP~c_#``BEO%3TOV?c|S>fdt4?KsO_ry&?m!Z00);#vGzz+Ki{NFOflvCa%0r-p*ABm6Nk#f`^mgy8~8%=~l15tzd%-j_T9sUCt?6BVy' - 'J=x^6c?UwqvV}{-)o4nbMN`@PWI{rnOYXXUshAzdsdl+0uDvk;A5fa8x6&XA|7xoaF^}vZ4%MfQ|F5g$?_n(*UXB4@l3&FJxxo4(Y5F-B^U$`iqhPTUzA)5T' - 'sdiM7jOo%Xk{8ALE$qs$xY1*o%g)>}k>v&WKn6QXe`C&M>!xiFdvcL9m*gzL+C>4jAE{Ak8xnM)o*{RTQJ_uFA*ECsl|^=LdR>ex' - 'ek27A)yqgPuG;8yorU9{4ijol3j|Bk1#v~t&p>bHb2G(Z(zX-;%0Wct_p;og3^tv~?#pQ37_}H-G=i)rSF}QASgi#*4HmOVVA&6x' - 'c;3o^ac8WnAT{n{5~%7nT=87{)h^2h^9C{TL)v4!XThYl?5RMv2|CjPS*=pdXL{Gjs1PE%CUr_H!wVY><`_Xqu{oAG{LF`lc;S~%' - 'IY->vKpgjlIOd}^2EXO(CG+Ws=#kDiRj3LZ@Y8kCdL%`!Q4Ua~^1PVvD58!~^30Q7j<8zbcXwP?lM9rQau4vlt%Q}ekbbL@T_' - '(gF*@fNGqaKVhshPMM&BpGs!QmmL*Vuiy?}kX>2TO;GU?+Gp9I^!or~SIuLW?Bq@vHb*Tg3tJpnUgxoKIakxTAb*YRI~O%w`J#>w' - 'T&cM8As>5k#^1-=NV;3ZeY6awov|B=4t?L@p^pMI{Lntv4Fx=sn8xVo%KQxKe3|j|mp}?_CYXBNm=QeOkuj_8E~A7LjBFY8SscME' - 'qVY#h^ZL;LYpH!oq@D;mwik?RboQP(4tr~>(-XAn>ZJGIWkOCo&|vN!a5rs#rhqzE#jddOQVy_vo#_o{X-w72Rhka0yJ*d@sl&A0' - 'NUGn_o^o>xHyQ&P~68Nlb9rgy$ngu76SMdw`?j$uBvx|xK1VSjwi5f=V~s0;XR%c`3H-N4C`)3b*U$AnmRlrnr5$z#l-' - 'ylRjj82$T(mXkVVsnk%;olGAfFt+BQ?}pnt7GKQu2e%COXwH#C1+Ie($03pYNAe7nXXmP<);zu?QSs}ykIY8~*6@JtLi%nM0)^hP' - 'qx6}A(gX97itER^Qe>u(A6o!$}V}aAbM&^Z1Ly&PR' - '3fi5u8X*fieuv}h-78E(icqLIqmw38C-E$DYi9Dzn~8E3zKL@mR*}=V1GmI`Aj1radG0OJ#Yw!u9LWO1nQ&R6JKC|?Qn>B3-hiFfU+!%%lw$YMh|>$wdi5{0okjU&aJVrRDh' - '6RKw2vxevK(gd*-_tLJbHd;7x<|h}=2Ghsf=EWyR^S;OIJM@Q}u{coW' - 'lmz6`l9J-O3}rMdcXUqtsjASV_+tZ~cUC;wuQIJL4(?>f@73G?LIj!CJNQ07H1y1x5%F{a(%In6DcD^Uv6?d#R>>' - 'a@>>k+vI7nE2|s(1K1tL<#g>#POw+uez|It%fBmb0`Q=#z)Sfwb#)_PkvvYeW!v?qSRzZ6Mb+0_D4Y-D1i}3bM2*b{+pL@kZCX^TPhfVKUz~opd5_yXgvpeZS' - 'v#K76yhhl|8tbF?@Nca%ll3te1V=o$WW(J3RE#+W1djl>P8lnquH94BDDS>KmW#@vUt6xb!R2AJ>%8{nqqaw`8oSn{+n>i|>=q=IQnEP8IFVGOR9Wc>?F9aoUD=w7?n-KIapMAHMu^hmp9n>!7`LUC8nGIJcS7csGh3@Xc#Hzk^1qVTrs#vPO&b' - '7_BJR(1pKj%T7H0o89QeRTm_O>1ARL19%aFNMC^o!1xhoR)bNuLd^6qTKD8=*_FFgRIzj5r2o4XbMkbL=KkH~UZE2l=8K_tv$jsa' - 'WH>Fa>IUnj)8I@KGzDa(#v*xMJeL)-#nZEkG|nv&anpOGEw2%KAy&v6_vJ8^{k+9pT+DIa#~JJ?>SbVt7kGtq0-J5+*Ns)(srrrlCv_3<3Eo_0?C=Jwu7G{!jy162u;+$cvvt=XVymrmg{%j;%J;5fugIs$jv;cmXmtr!x76qYeYLNMkfv' - 'Q7O;SW{CL8V4oe~BtDjmHVPeCMms1?epjZFbftnaOs_zkG(;9&y<^}6YC{FgK*_$vp>KAe*VnlL$453IhBd~~;(>OEZ%~fN0Pky!te;*>%{5r_Mg1LW!`)RY{$&?-`F~JL0|XQR000O8001EXkTN!w1~~u#Qw;$C6aWAKW?^%5' - 'aBOXJFLGsPX>)XPc`k5y<$Y;)7oJ7lN@i8y6a-2u=tSqK^-SoG%Mo_rQuZy~=Zlj`(in7VerdX8O' - 'Yz8mNNxsbCpR$P-(`dA)#y707QI(bTbWzRos_w)4#jGf=qj6SlLGM)#Uvj8iFY|E|)vM)lQ8fVL_$D8JST3NCx);rgabDIr{K5vI' - '4%9>7>cy&pp+ujG=4P>Kwu-W7ifmT=1AEBCfT|S$InSd-6-^eO%Gn~Du+QafG%aR%-QSAicx!7~E#^_0PFKyU%F{F|<_HNCD;G_M' - 'qpY{K#Jeh&znaB-HY=`pY(s2fu;otdN=sr?#' - '354+P%dELEzMjHkFFJ+Moi6I)vwr*urf%`6#uvO|15~0q6+oX>i_f=6KB@>JQIy#&Z+p>EfdifOU~21zms(Ybo&GYb>RgtEckFp<' - 'Yb$*_{5d@vMhQUPA1~%hn2t^r|K;N4&i~4Grk7v+kGQ*){' - 'mJh7W!~Nm$=GAHnp{MS}UwEDV$!{hyvpU%_6;~#gA4)@d3-O<~0@9pl{' - '?%rs4b`G@xPyayLZaUlFem98!`N!XE8}-hH=fkrfhoU0(N$YGy@ZVj$`~K&+7sY!g$N1;?{Sp2-Jv)x?S%H5>cT@QI?he=J=leK<' - 'HOJQLXZSaQ<$0ZVUiG3^FS|zhqtnqk|c}MfelL_}TDacs4xV8>TR}(eB}~f5H?;*pXBB' - 'Rp*Zb{m`{r+uh$i9eLVf!-obN;CS_~NVr9F`)gmYrCGUVa_3`ILR`kA69|=bEVAZnR{e_oe)Ua!Z&W%x8}1#R1E6-r' - 'Nj}Y1vj!_>t7gIaU&@A{5%&Ch($<@USKp+=pND(zN1g_2WXt0e<|v!zI2E!<<5h}XAwR-)&E=~ZfZgo8;4m(c(eAwNzr?5Uc<1VN' - 'ryTFp+0JkIs|D^}1l-Zd55wcbKLc64JK1*-*DOBdW$_O&QTg9jNJi{;Q?16h>5#_Gp`mndcQpL|A3$FTp>!k1ekApcR3SEL+n$P5=iy0ap4phxWInf#emO)<^J43}3h)SG!$eSoHagsHKcHnG8g-|^$e' - 'eAxNs3R?RHpZ*bAT?pNj$' - '_%r^%YP|sx9)8ZpD};rNZ6hkZ+}g*@_ZU8yjrbPbrKXkP&!e+ln?&4sCA2y`AEhIQ`~^dKmboUpY6wDq#`WPL;hQE7$J_Nj-y$9_50DH9EjfXbE&72$@9Knv3z8SvV{qgX`L5+a0<}%+T{g_w9w16Kdwvu*t-;Ykxy(4Va' - 'H=pcZ{4d@GLW7kuEw1Apefth&W$(*_MfEPLKjf36ccJef95#7T9xP@kT8B!Vi;3Ms5f?@>&iL*WEd3h(4|h63hU#sLQQl9(!|&gY' - '()0HR2Zuj1v~=SBRl#<{KByLqTKnqH4f|VXQ&^E@y?}LU;P(Vb>PSx8*~yW^k3Z$b^-WXrB~yYF-=v@^!Q9md6;9Q`?%`QFJbxz|' - 'd3$m`f;QsGtUtZViskAGNRz80u;yZrRZdPuDeO;2`|UGY18K}Nq>b7_<}AMkie_z^ul08SA+O4OMn0~Vu*mXkPF_y4c`++!D@?T_' - 'Z-knaqDIry=1jalJs+J7Vg0W$oMK|Oi5d(6uOcK`?-FJsazpOK!476b2;(~`g' - 'fM_Kmq=;UE2u2@iQ7%`F`98Iz-c2w>YA>Vt&a+ZZT2AwVqnV#mkU1Bha&!-qONzA&^B`FNcO@krU>E`bbx3)Tfn$D' - 'SlAj)drjZ!%<*IpFx)=%n;4J)vZh+)_q4ZgJNO4RX*' - 'v}Y2~*ma8a3((H5fU~ZeyhA>V8GN|Fqyo}1Kg7TO8spF$m;nOckk!}L&#W~@DeQX&w1qz?OwJrLGQwS55X)WMzvc$8KGQn!SDG$w' - '*+ZHNgYr5PMW+-BdMKaI=s=aKkd;_0XByv$wSJGhgwHEy4}<8Ajrxn~UQAuZmpg+6n8}}%N2SAAb~36~c`vGGi>7A3yMoOfz^1v+' - ')BwdghM&9W' - '0k#I#yrOppb~a$f6QSS$pJ7c&wvkY_LFy~Gdy%S4Ta&E}0$~Jz1>JXp' - 'Z+WTjwvkUVrxWXAOZ5@jOgM((J*6VBd1QzKVlsxosUV%@@^zlsOZZ(sJ*W<7|$`R#rG8l(aRpGF68EW0V7=3^kE' - 'htV;O-^KoMPz$#j~kN`247*' - '5{Dua0isw$pjg3{*W9WVB*?W>=d&qWH~{Ow?hd#Me(!6L2mA$Z{65}@i)dmxXM(TdB_}2NKj$k(h|$~C^)-4=53(_O)TN9V)n&G<' - 'Zx)SaCipQeCYpIj*-^h?;{G6NRv=(qXx`9^`u+Z;5ILxBAPX}&3QM7FEQm&|f&#n7qwomx`ntzU2f4QExN}!D`Ml3LB>v=%Mf?F9' - '2tzvq@V>hd)=!vz!wmKc;cv70W=HU6*gBZN$ybiF`$ku{Q5Ka_@Iyym#5E-Y{fk+yF-P5HklT>25R-)AgW6a;V5g' - '1~OoKEaU;Ub6m_I*cT1VRh?HX_42l7&zC?^rsLHlOUKzVyDC5;yp;gK' - '0D3qIHb=kjMK3p-C+_s&JVnmWIjE8_2a+vfXqfg_M$*Qoq;)bh&Yt{>!7OMj3Td(TQ#G~S8!OAO-0Qc@Zm$?2^T`VeaLo56d057K' - '17t~ne&8Oa4Qi1eM8u!X2cpdG{yvleeh^6?Ki?4?8@vYFFsBA)#_!*9R}0z#`_vN6fdOmcEQmb>t`WL$Rg(' - '*S`Ysmnr4536C-Q>tFlPaK41iZ=M4=F>c3KKFreJV1?HpefH@JGW=XE*!&W-A0@YQvms}u&`}M110mu3^KT&EpxYjI;sXr0Qd`c>' - 'w5WjZvaY;9NN1W4BLQh$5^F$OM^b%QOrI3vro+`DLi^pIl3-GUl!Mj+L7Ql~?nri8Mh-jUteQxn0YvzGHmNn=HU%%Hn@cPAfa?S89faR#sI+3LFPS*?A#K~;p&i?ox&WB|YCGz$9+(8SI(8)KIs|14+OEo+>K' - '9adwYI|<)b&O*2AyiZC<{9MGut%=m^3u*1wt1D_GWD~U$_nenrEJ|}CE#B=5gZ56J8Yi4Kkeh^O!W|6r!{T7PEj>u=vcVv2*W7xf' - 'nXD#ki#_Ku%F|bF64Gx`B0~(S=W`t)5;1YUKuOf14~o3}&ua_nj??^Y?*<7-&FMDbbvAw6^k%+KM{7&(ZC}TUwrHRki_4?AM9wBq6TSAuYQx`&|+oP1m+&OG1+%CGgUU&r@`&8fLlURz2u19|0Wh3heL`9uW@_d;p(NS}6' - '9KRzBF?I-oNFw$Vp(s+_<5{FHx%XK!ObzXuOK86Fh(XA*LGUG+YtEoY;TIG%X' - '$|%euR((YoVst?o$ZAzEP1ne9-6^~`1YalC)@sdTeSEl4Bd)jA4$5mV+NNSOqar#JC|Qyhw6K~PA8?EpsS*YU;)~WsL9hv@q}`O9' - '?qCbDfL)PqPT_j|vt5hQL9j9{W;bF?7PMeInak%oZ;*S#3StWf8HslHTjHM0A;s7(pw1;(k_|`~sb+8T6zYZ71c0SGW_w+;tyx~0' - 'O~Imd8bB9wp#IgZa0ZFd@dqjL7)LD2_3c;E%CMuu2sk`S8Q=iC5dMp3nH3e?vxMC&p_so=fM1%^W%;7SoJlc68LQi-%`rAiW6sl~JJshG)jcR?' - 'Iky#L6X0D9w&A#AqS({g9R(8F>eFJHUWCgh39vL4^Fdo9CJs^EA4(zO5IAc1r1*e-&0O>`8qQUK#$ypiE($PGv-ZSd)yd' - '{aT1;0|B}?zz9icxe(?sL^z8ARo+tOa2H`y$ha%mVE{NCkU&XH$&1MYD-ijBz$Tad2=BR&WiESq8g!q72o#Qn@j>Y5;*x|Nnk<09' - 'LT5{{09T`7;wUCSzLvO~IVH<&B7}&gHwtqpaZK)YFXmE-ApEnfqeoemd)dx|yHO}7_yRBX5UB42d7E^s`K4?~hl' - '(<}z<' - 'N-13`FO3QdXT;T$3ayG$?rm9%N%V#E&X{rs&3mk~ZhultrN$~c=&IOPQE&agN#RYbRgZjhfClm6zHcIe^9X3F6CAk`Qaqv9D$t6@' - 'q$V7Hj7vf-H43&X)Bsh36U2k0d%O`5z$?NPnc;Z-Fwy=U^m5O`2Cs4q@>!HSIdcPHQ{{D^MvL+|Tx;-oxtgyt`3eo5*={8cKc-yd' - 'GfXQaE9CJCVq=w^v|?z>+WrFQLY@km;GLjINqhHyK*MYfTJJY8(^Nq9`L~8' - 'XGgtt1kle4`r`#CqMi85xEp;Jy>iYOtj|43cRq@x1#rxYp=O^_9=9v}HW+L)t+H$84wvEQ82Qhv&j{+5S2^HFT4rME^Kh#p}X-jNF1CP{BnWxUtHksYi%8X81E_j?X9LL|Ij3w-SN;9onF{' - 'GHIMh69)vCXPKAkGLmI4#fweBQu5|x#e8*5&c|0<6Dr!5Q@U94baMZ;IqiY|x9+_D`=;{$jR}0T?jYnuh7)#X7PIOTtIjtlzL' - '4*UEdQ9rzA?SduZU(3K5zh5is!}F|p!0;^J!2tO+lpHR*7@;!jXgb1;6Nu*wYP-}`E#}tzE!L-sZ04#d3+5sj8hn`nt2aSL_g(Xt-tuTlRrQqvF$)I~RH;A(jj6O(#TRbS%8s' - 'J^|M#WgdStsOI_T`Oeckx+#zVW2+^@lnZL-ypHbN@!M7w9^b?B!+Ew_KbIs6oyAwKQ<*2YY2y;8{BT+%G-$`tGOepMs`Ej#Td0TG' - 'zD0iqZg)-;FY;<9pmb5rZar?Gm^mzr(#bX((00%lBt51I3q7%;nbL;DfIa%hFAYv$8x47lp-Jr<#DPgF2ty|5?clRl_g1BO6akZj' - 'VwQzBc2Y1Hh0DB|Eyisnl!mO6T&QoW8V_+&!U~mLW_A4ugGlYN8^~G?I@OLooIIifl' - '3t$0>*_5#C7!V;<@k7v-JYeWnKJ&6XcrSGg-84_-EDd>6B?_ZR#Cy{A9ISt#4*U`YNpx^&sxPdH)>}Z(=Wm`vbzv=8QzT%Jb0S0f' - 'Rp@Vr!~lCV6}^_l2xJ8|h~K{SpR3n}Hnp6X1gHajfBSMrr&Jj)<{-SNbA*OZhfDlMTwj>fxQq}QwLc6{oKp{ooiGUA=)K5$<+B{q' - '9MS}1tnEjbnn0m)=e&Y+O<7;KYO&_RnjNDO$l)^2?ntGdLuOe$(Xp@iu_w%J_^f%l>S(!|KRCMQv`OhJBP' - 'AV;JkqnMEmY|dMFduwqRR`S{T3mMmgj7E?E+Km*DeV9ZpirWcFyDt2X2TcXVh>ZwS9q_XYB3n(@n;F6u3q$kjW}CC7n5FwsWn^eU' - 'IG!Msq{V6k1XQgEnUN48Yl}CuKy}a0cs@z)RCtas#c_?AIOzr@{VR~NA_XKaQZwGgBUOqG$>yvbL*!%}FcE)^P*t%+6Sb6eFbg6$' - 'VgL3#K`?v*68nYFDCw?20**IvZv{?b45Y7!nDy*5g)Np@s4DET>n{+}I+5kj%F0e}A}DI{%IIGfD`bX)>zE;RD?-oYRG9q&SOEfQ' - 'fX4&Q1vHpJFa+9#xOCw%CK{F3W{L5q-E}ha+>!203nIsV+;#IE8D=<)M@O>*Pf{q-@ai0eA~&IRzOs>=+*G-3lh}qJ3yurzfxLr}' - 'fWoS0wcourM}*MZ`?x@$92x10un|^S7{gl9L2sJF$vik9vdrA!AExbhiJLP7x|oar=uSc~?Ho*HtL=%gPD~Ij{wi{Nkr8f?-b<&7' - 'TG*WoRGKI&pBGsSWGKWFyKLg;>tFNDY8ILXz7_(rAaqh*UFp^EgV30hkx$fV(XOe^i)C*Xb;HzSDD6u&-cps(!xzYtUQDy1+R5wr' - '3lXi?TC2+DbxVQ2ukz})<-7FpYGcGEGbbUXQwW+kn#Ze|HG!N}h2~H(vM?WDG9xR-7}__#^=o&s$Ro0*k#U^A-Q7We&Ae%3JV5;_' - 'o`UV3j0F>rjr{^Y_Skth<)L=I7A1FcvZlXHqL+{0(tTFh3>g8<3J*$M-8Ogtb)`M}dXJS+qog}y$8`lVXBU#p&{kii|(f8H7bvJ' - 'R4E-cZW(U4-YAaSSduC|*}INclSIA;otWMe!XY`<%BroTSS-1YtJ){+GEt8$Pn<#!EpZ0!Vwt=Eg)n(<#r-3_85Z1riECT-xXm=1' - 'ZP97SraWMPJDCDes=QY+G+Qd@it`S~peY#(0OP;FN|z6d$f$~}#mGWv=rg&@6q$|unZi8FX+Nzkm{SH=1YrXc`~MLu3@X(YN3#s*wYPa*qDdyvb%1G4AZ#n?LaGeukA$BMhh5E85ThJS1Ew7gwo$eFG+T3V+}nv' - 'deVafwW=@7#CVrHxh4kJl=(G3>5WP2uUkTcIUX)vUJ6^l#VdQJeF>)4I$aV>?e;*(ja}LexiL{i3c{SNN=&+d#_l' - 'fj~lUuw^uUse%%~FC)C}cZT^F#y5*%EX4Y1g_kJ+_CU9bCNO4^WQHF+tQR6@BtrsEQ2i;pt))4%?#n?kNG^6>T}tDMeT(J&a|{xX' - 'enQ*Ztm$oJF5B4nwj3mc1{VFIafs9s)@2r%oGOfApR+9Ze3)1qL!0}{POcM?e0zIuppSb|(sgLP;hcnHv_FG$9>AlRVjwxF6Y' - 'QN%*a<(C(6*9^kf6?ne*7xB~Bcxc%fS|{%EM$EecEX2GSMtcg<5Z@N3eNp@#M2jmtEy&od7IA&~90m^>vE`14?Y8119+?T_hR$_|W|^l5^KADf_j$dS+A0x@l8)aPY6Ora#V(Ze1EA|GRTzlIT%#Pu@NM#X@Zt' - '4C#B)oQndHbd?F?(cJH|tByj3hOo@d=Yj1b+BZLfmHbNtxiO-+2z*z#Dn)-Dz0+9y5mv^8I5Otb=t*-Z#0cFFE' - 'jW~#qv&0}eF9siq;^37ZWgS(6*%5;=z`fJIoiX{tIP{48!5bR|OjvOen$a4`bqqtTT!1Yu0E0KtmV^pr8`Uj-X4gT*%C2HTi1yyi' - '?C^U0vJH>fS7=RMz9`mHXlh($4{L3erSe!oH*xyjkZAnH%77t%!jYNv%zFt&Fy+P#s(29d~q5Fo@E5Py|PT=4A' - '4&^)JhEaJW4U6h&250)%vy!IB1)u1jc9CFcgppW3vDS(|E?&%LV!xopIWiKw$GhWA%+nJ^tjxHcjWcPn4hdS26wvdX=WIGMg%+*{' - 'e0C_ayRoRbgpG*Bw$Lora(59Zw5|055GqrvY+)_3w3YSk-y=qY?uv;+8g`QW;zGeGWNCF8&q!4lgjkEwBDuWG{UFl#cAq5bDhU2_' - 'B5u3vW?%d4VOlErX;5yPgd`1p7|C+L$|fHrWk{q(hx-EzE)i`x7iww1F{(|`!balRT7CVe1ei4fPo6*I#DwX215R4Ea|WAvrc#}v' - 'pTN+^OvL2BX1Ct75={Ns{+O4OMfE~X>kH=uNg$e~(}pvVh+y!VWgX#_U%vnk&f4IUvK&u~XG4yrhpHMI(!q@-`e)}>Bn' - 'W9BIryFF1nfJ5%-_Xl`eE%1m0a{1KLsM{Wp5EDSkvCvlVw8u5eKf_J@ApzgH$Lb+8xix&4BBkcp5(SU8i}u`B?@10kmCM|NXDd&e' - 'Y>D}XZ8(^|s~YRmMWx)nx&`CBobkFZk1&0aXvpMCHzVCz6YV5cac73jcTtXKt4Yr9+2dKOmn>E|4mL@K>#BPWM>Axg1hn2)w*OzG' - 'QcUw##P+KAr#PMMv&ij^MrVg_-eVdU-{<3#5!CfoR41JK&1bI}@Xh)D&euD8vusu8p)&7I_CY|~V`*g0F-1Dyk7gTOuKQr|?@}2sU$s2i9L{ppDF;par&Vgz$ggdKklb-0!jv-p2r9uKNZOT4ZGCKKTczpP0SW~cfZhojlwa$WC' - 'Sx|)>K8%p|ZD~HnC6z(7-zei7o_062(TWbEXg6Z(U>A=a8w4BVJ}1ZB$2;R(Cz}=rW6!9JP>f#ao<6_8bPw&hN$Z$#cht@18t!zwuq4=iPZix2`_jpXeKuFHTQ?{)6{Y1G%^HWPQfQ7U<-@CFr}U' - 'GP!8Yi;8>J($pSX_^1iHrx(=l=h4~jxv`n6yMnz;L0%u+#t&qtswHp7Kc4NrOGlWo%)4y$4Z$ft#Svb*x@en_qf1#S-APA;_VngI' - 'JRb>Iy|iasXZ$pBGl29ub;Xs*cW%dFVj%-JJfQT(<(`Fc)6e1N%1+u+hg)7yPeU?0bP`UI9cqN8s7+dOJqN)sua#~kEK)MCEwF=J' - 'W;uGGi+tJ8W=sL)nY0&L6II8th?Ack+A%6JoswLnO<-^wLv0x!%yuj{6#$2412KO@*+BX470O${Em@C%)LcCeUtLb3nYOIB!-uO|dp^6-yyxF_Z5zuPt&0GuCvwQO(4co`>@v^FHosNea=Uvh^*}36`=dZ_=|@4V' - 'QYnH=fL9VXv)dr-ZS4s{xM&?*+ZnxF#SkFaAtOC0@' - '680SYqdB<&vTG(37|_)nE5`2o(Mh^@gsBJ4J;p!XneTMw!$!be=y{KIEbpzf*qa88GB;!HGz|2l2M8|9*&O#e1l%YkE?2-0-$t)D' - '1^M*0xdD#B(dFaFx+fQ8H5KBRe8)d?U7x{<_ecoZ`WOf7Q@m#IY?N!u!V|3ORs2kTIU62zw2F3~Y^&$(R)3o}' - 'T7^Rt<*RO2rDR4~@{>?1DN&mU!^CI-<(Fl&k%wFWO=*pOqoiil3m-c2Ob@N=nH|i{5%N&b%F0dgY+*6hz%=*fGoU>_;;P-?*2^yv' - '2c5dCfnm>s$xCQ4p9%plM093qT!}2AxQUtI@-S' - 'W#M5T*p4QD7o`U@78W};4|aIRsTwC@ivtGsX3b7pt52jT|Mc+y1)1LO&VmOM(Pe>)FQsxf&&M}FjrDwIT2yr-5mf&{dBE@Nk%uh+' - 'o(nK4(=tIiCPt@(JXz$dOlyh$X@O;0' - 'P!jUg^U9nl6aN#DaYwxD_Iwdat;%w;mhMiPZPDeDHoY;h#F;3Q*m7Nn447*_aeV#3;^_BJ>=%x6!)4-$_pP<~h&+*4vX7qu7+%N3' - '>%nZRCcT+S;C&z{M{WSt&4lE3^gv{ej%Xo)Ic89c6O|)85k;Or}k`T>h^~wfV>-n^3LBJs}m*lVP>rEEbp?yD_2lstx?fiY{s06SF72+*w82&Q&)Rpep?UyPP-Lz}${USo8-d`7EJGy5mQM' - 'A<_MBboTyyl>TsdyuVIBk{kOSn)jB#*Tr;-_X(y~`Azn*SX7}V>~aCxahkIo2Xw$E2M0%o$HVl^@a^u8hbL#}Pfv_UyDv!C18_9E' - 'L>p0|0oq#%LHr1v-?H=#H' - 'LCHCNe|AKt4zC$mpH6*3<~oG?)Bk+KlEOmwEN${))kN&(4_rU;ijSbAi3pXH4Zlh1--k>gTaJ3B3B+QRJ~SFvW%)#$vx)@(@bP^E0&}4yjGpM)Gm6qRC<&z-LK$}}cnJ*T^aef&4(leK&ud_1;~gTEbk!8F(+G?OY;ulyU&mI;eP@n7r4z@wZ;VfTfS#h=Z6AzOTuZB#}j' - 'VYUO&j^nn_dPONoU?V;M!9G-ENC+vdHcDN4W9#Gt2Zp8b(L8u=nOZ#xPtva6(jz2Vg*DN^bmmpUOwksY6!P{L);+=-K|4?|IVUCX`W2&Ha!0it>L4vP<^c-%Ee@WP_>>y;#mNL!>(Y(^)?5NdEbSe!$7f@BJ)a_kjaAs_a489<~VBw(#D~C*XR{uiT}FV&J' - 'wyPv`hnQ$UvSnYIjN%`FLf~%KEsk|kD8OrJL$-V5{P54ibZ>Vw{Ql(ZaP#aC%xkC`w_?6Y&y@XM=Os#DQd7a3=DmKj?^U@yQS4cq' - 'XrAsLZ-q_VtD;|Z)7%82;FGzt$Dd6X_x_Q;LDKlx-O8QQ=RmWi`PgXU>6tF2$o><48' - '=*L(J88m#wYC-LLLLSizeZxJ^Zdh)3+t@I_#zi*0P2?|`d@beCnJNmw7@5CJ`@nFn)mR1_pC<8l89r2krbvGsVVd++a!r+?ChP+b' - 'Pd{B^ScLsE;MzIIi}}20&^8pvr5Ce9YNg`SJ7O;6+_awa$!8d=;D=?S`<}KQG1!7#chpqHm?z~44xw)IE{C~}K4m5R%MH72&GItn' - 'LgUqEOHSqgr)@J)DgeJ)SFz%Oa|>euO?Vg;J})ZlDYLLksq)0^x6aWD|kBH@C=ftg-0Dk' - '@}HFxWL98qQYL1a2#2xcmcGMYA-`@t4`C' - 'mRBCSmgkV!(hKlFMB(%lAvqK&eX-X|S2+p2Q8j' - 'd8W6_EhbuHEa@*xm1N4Ho4px01X9n-P5=iG-T;WG;VFgaI3UbjP~m1`UtbMQabDN8g#|yAa3^' - '1o3*cr_)uHpQ3m76ypq(lGN?vDL~4DOJ7d*Y%<9wDFdeF@c_R267%XhuS3bx@n9&TVOiwkynnUK`+Twbio(c|3-bQhZWHRW=FJ70' - 'QuqtDV4R@D^?DsF$dt?nzPe+-cf=p5l=2BxH@t7IpL&R9WK)*7hDXriO@{K4_M%y8tY56%Xw{lt0iv^#uR2A>|+Gcl7A_UNa2-r(05Z43E9sB+NFz@bU9nq_VJK7C6-{quZjX8hI`&|B?c' - 'Kv_(|6QE`D_DGP!b-U&r*r2Pd08;NN;S2HW<)Ls#~t`}7usMqxf10s~(90JK!OFlew3F(Oxy#&ULEVWk{PE!1R1@h9kTA*=>O#~NInC_^OuC2;d4P>bFK~J`DgiPN$VdiJ1' - 'h7c|g>3dbo@G>>^Tm}@flZ+bc>q~1W!W7zUFgH4NBbC}n-m?MHn#|-|=F$_D2`q7tSdM4f=e>R4CYyZBtERw0Q@r+-upxSf0%0#L' - '=pp6YYZwH<Lc98J(H|5jx}E1u^%WLCG{$LbulhU=PondB-Hs)hDZHdmGKvuIkdhNEgK{ya6zp!4G+@E=pnM-BY5=d-rHmJJtsq' - 'qMdRi0ZiiBNtnHWHs{E-qPDH%$lDG?*Xni((0nVAa7^4|EWbTTpgkbbn?lD!I5PVx<2yPYEEjOl++-hdnMIx}G0?-kC|kWX!gs}Y' - '4%}A6>TKCo0^72BaMJCPKGt#EdC??PVdB#vX+?}wR@9UvnxRFAlws92u8pE!s0ySW9;FWl2U*A*5frF9?=T*)Er~i0WV-a1BC6=~' - '4bfv>qgtuoxaNh?H+o@_oh`CSDi0Qd;q*`zebU6o`|c0cPZD>DV2O%F)S-$(s`H^o^ELJbHV0wx6Evo{fG<`E`}-B1xrn+y(B2)g9j>PJCTGfF^}#bI3#o' - '5n!@4?1xig&Dk@3_e6' - 'H;{t4=gxtyF7)M^c$f%dGV}zpM3PrM_ZHAZ_v*PtO!S_(@Hm-N69u5<9OiCc<2A)=LLYT8gOn5FH$A|{SvT`5D?IXhgn32N9LrX$acg2)9-iN(sW0?3#9(8QKp>oN@$RhNwo0ybu@`*7cYjtq#q{l9_C!VZlJ^6BFyKqMG8>)wq^kza*}' - 'C;7WBAr^17Q<<)-YgOFZ`qQjhrDMEf7aIS}l6<`CGVB9|+sJQ0?-}nNtsoM?6Ts8>Lw1ds(s=1!#Cn2xnBsLfXrbllei15-VKEfn' - '^h?svz@bvoA-Og*l<3T~A=Kgpb5>2|$&jmCZZT%TFM2NVFt~(^hywyhAC`2)ged_0kO$MB>1{BHaIaxB((yIjwYCb`sH1}miAtSu' - 'lGZ2^v)zx*RnLd}sM;w=Ozqp09k#Yo%x{{eD*tu-_Whew>_A48;khs-#(PRoKNkVq{QUu}jD5CS%QtS=zWic%u*oY)hU6vAx_oiM' - 'LO57%9r=pvuIiHG`nAFV@RH50iQ0n{tr+~0|XQR000O8001EXi^2h}Py+w}?Fj$?' - '6951JW?^%5aBOXJFLHHmbZKp6E^v8`R!wi)I1s(-R}6Hrfz*fOX28WRg0v0#fuQhZ5fmydjclSc>5`))UhI$G;g@VFwi{n4kTY-I' - 'yqO_gmgU{cPRUx*6*PuyERC%W$EAgtam&p%RWm^44w!w?Zk7' - '0kjbW&q5Q3B1hR-(@KtQb_7zxv!o5o+fLW>pUI?3CO<*whUwNQpOCRoTK2{#X*yTrV6YPPFL>Ud_7*+|!WRO3k>qAYS(Zi7TC;_!' - 'YCGyIRu#yQ7lU&mAy=1dlCzSlY?F4jLW?!pXw?W3wGz&;6v?dLiWaordDYV4@}Gyh>h{y!?dSV%AFtqxB(y>mMIqba*}MS$B_=Fj' - '9k|WJs?JdN-j$4H=dXgcoqAw(q+>H!WZ=hgBi9>rR9->&^y?NI*=@@o?D$ttcEX|lMZH+s4zIu|LoR$zi*O9zV>upTH6l9}^gI6U' - '7#^EZ*`YNS1guP_8$Oh}MT@%TN?RZ+r1qMul&OEsLRmtx$_To-uC^f?E__O74zKg;^9r8aVdlA__U3xdA&r9n;G5CNc7E}Bj%wFn' - '3!?^`q;)<@b%-^Eckg@UAN|gcUW>nXJGJMehl7+`Jhrc6x6rH%jbgjtp9ILN0jp-^%RAPIq@V84%mJzw' - 'GH_5Rp14_;AJ}JS*l-U#Z()L)S>i5pGjr*T=A`mS1-v{~Y=!(~*)K~Fxu*stj}>OuK8eJ^^_OO6!5^I49q!j4%m@{+F?aycz4z*%=V{rWmCs<24-G6qUo+^&WQ=' - 'KBjOs`XIxz!G}6riXE0HkGoW}+#R!X7BLNu-CG{09W$8sDP0!H&%s8xKT1A50KV?3V-AZ`%#O*N7wwqKg#wJI3@qQqgzWx+v6rZt' - 'c=3>3AU?+#@EZ(2$bqZ}v=yK6cP1Qv@7ix9_i(jp(Vk7>KvVg~O^aT2Me0Y8OozauVPyGK-O928D02BZK' - '00;m803iSr%h;uy0ssI(2LJ#R00000000000001_fdBvi0A^uxbZ~5Kb1z?CX>MtBUtcb8c~DCM0u%rg000080000X0E&s!MtBUtcb8c~DCM0u%rg000080000X0AIuw8k`6K0E!y`' - '02=@R00000000000HlG21pokMVRLkFY;AKdVRUq5ZggpHZZBV9b#!koaCuNm0Rj{N6aWAK2mk;8Apj7_gizxm000(@000{R00000' - '00000005+cTMhsKW?^%5aBOXJFJW|aWo~q7Z*DJNV{dY0E^v8JO928D02BZK00;m803iTEg;!&k2mk<<8vpV>XUu$J@Ze?s=Y;R+0E^v8JO928D02BZK00;m803iU5mV>XX>N37a%N#;WpgfYc~DCM0u%rg000080000X02=`jzW4zE0O17y03QGV00000000000HlGr' - 'OaK68VRLkFY;AKdWo=?*WMpY>XLB!KUukY>bYEXCaCuNm0Rj{N6aWAK2mk;8ApowRV#;_B005{!0015U0000000000005+c?oI#z' - 'W?^%5aBOXJFJ*0FWn^S&ZfA2ZVqtV+Xk~LQaCuNm0Rj{N6aWAK2mk;8Apn^&o=R2}0078T0018V0000000000005+cqF(?2W?^%5' - 'aBOXJFJ*0FWn^S&ZfA2ZX=G(?bZK;XE^v8JO928D02BZK00;m803iTyl-h>*3IG6aCjbB)00000000000001_fi-jh0A^uxbZ~5K' - 'b1!9WVr67xX>Mn8FKKRYb#!wsaCuNm0Rj{N6aWAK2mk;8Apm17``fVy002=W0012T0000000000005+cX@39!W?^%5aBOXJFJ*0F' - 'Wn^S&ZfA2ZZ*_EVb#yLpc~DCM0u%rg000080000X0Bi<~2#5;+0Fx#F03HAU00000000000HlFchyVa)VRLkFY;AKdWo=?*WMpY>' - 'XLB!bZ*OdAZf7oVc~DCM0u%rg000080000X0L&A+5eE?f0HR3%0384T00000000000HlE#lmGx`VRLkFY;AKdWo=?*WMpY>XLB!d' - 'b#88DaxQRrP)h*<6aW+e000O8001EXS(e0Cmp1?adIJFf9smFU0000000000q=8$d003rTb98WQZF4VWZDM6)WNB_^b1!prZ*pO0' - 'WiD`eP)h*<6aW+e000O8001EXS>FfLF$VwuKN$c38~^|S0000000000q=7Eo003rTb98WQZF4VWZDM6)WNB_^b1!sxaAk8YaCuNm' - '0Rj{N6aWAK2mk;8Apl*?Wtodp001CH0RR*L0000000000005+cndATfW?^%5aBOXJFKusRWo&aUbZ>2JP)h*<6aW+e000O8001EX' - 'mU6cI@Bjb+Pyqk{8vpMtBUtcb8c~DCM0u%rg000080000X0DvuI?d$~r' - '09+9O03HAU00000000000HlF`IROA>VRLkFY;AKdZEs{{Y;!MPWpiy`a&L5Da(OOrc~DCM0u%rg000080000X03iA=lmrm~05VPh' - '04D$d00000000000HlGZKLG${VRLkFY;AKdZEs{{Y;!MTY+-YAX=Z6-VRUJ4ZeMV6Z(?OGaCuNm0Rj{N6aWAK2mk;8ApigX00062' - '00000001Ze0000000000005+c@K6B&W?^%5aBOXJFKusRWo&aVWpiy`aBOvRaBOvRFJE72ZfSI1UoLQYP)h*<6aW+e000O8001EX' - '&NVJui$4GWSw8^)H2?qr0000000000q=7h50RU!Ub98WQZF4VeZ)9a`b1!9cZC`L~b#riRb#pImZ)9a`X>Mm(^b0000000000q=6lw0RU!Ub98WQ' - 'ZF4VeZ)9a`b1!9cZDwz5WHK*dbaZ8IbZKvHE^v8JO928D02BZK00;m803iSgV7~g_4FCXiFaQ8D00000000000001_fx@E!0A^ux' - 'bZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0CqZf0p`b#h^JX>V>{Wpiz2Z){{TE^v8JO928D02BZK00;m803iSi5a4DH1^@s!5dZ)s00000' - '000000001_f$^~c0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IqVr67xX>MmOaCuNm0Rj{N6aWAK2mk;8ApnxQ5Rg?7006O1001oj' - '0000000000005+cL%0C|W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGAe4W@&C^Gh{Asc~DCM0u%rg000080000X01F8g' - '&bk8t0Eh|z04x9i00000000000HlHA$pHXnVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUtw%%XKrP3E^v8JO928D02BZK' - '00;m803iT2_hkvd0RRA)0{{Rq00000000000001_f#=Nu0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMi2bZ>26X>Md?' - 'cx7@faCuNm0Rj{N6aWAK2mk;8Apql2SzMbC008+u001ul0000000000005+c_Rj$TW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IW' - 'W^Zg{GGAkFZf0+CZDn$EE^v8JO928D02BZK00;m803iUXVp=vl2><}B7XSb*00000000000001_f!^Q&0A^uxbZ~5Kb1!XgWMyn~' - 'FJ*IWW^Zg{GB0IwZDwz5WHMi4Z*FsRVQzGDE^v8JO928D02BZK00;m803iU**0(b61ONbG3jhE!00000000000001_foAFf0A^ux' - 'bZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMi4Z*FsRVQzGDUuAP`GcIs>P)h*<6aW+e000O8001EXm!`g+&jtVh%NhUxG5`Po' - '0000000000q=BaI0RU!Ub98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDX>M?JbYEh1X>4R=axQRrP)h*<6aW+e000O8001EX' - 'EqB(fwFm$J;U541EC2ui0000000000q=Ciu0RU!Ub98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZDn+FX=8IPaCuNm0Rj{N' - '6aWAK2mk;8ApojWN}kvd006Qy001cf0000000000005+c#Qy;RW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA?Jb7L-W' - 'c~DCM0u%rg000080000X0DLg|PHPVU0O3FY05Sjo00000000000HlHA5CQ;ZVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{T' - 'Uu|t;X=Yz=VRCb6Zf7oVc~DCM0u%rg000080000X0KX$>g@Ggh0G*!z05$*s00000000000HlGQAOZkpVRLkFY;AKdZEs{{Y;!MV' - 'b8TjCY-BPoWpiz2Z){{TUu|z}Wn*=0VRBz%Z*6dFWq2-dc~DCM0u%rg000080000X0MXh5Sb!1$0GCk!04M+e00000000000HlF)' - 'MFIe3VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUu|4?5a$j?0adl;GV`XzLaCuNm0Rj{N6aWAK2mk;8' - 'Apj#(;Y!#8008m{0024w0000000000005+cPhJ85W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA_Qa&2L3X?kT}V{dPA' - 'WNB_;bY*icaCuNm0Rj{N6aWAK2mk;8AprCI7|H$z008YB001ih0000000000005+ce_{dvW?^%5aBOXJFKusRWo&aVWpiz2Z){{T' - 'FJ*IWW^Zg{GGA|XbZ~WaE^v8JO928D02BZK00;m803iTZ%h9TT2><}mBme*}00000000000001_fyQbA0A^uxbZ~5Kb1!XgWMyn~' - 'FJ*IWW^Zg{GB0IwZDwz5WHMiHVQF$@WM6G_VJ>iaP)h*<6aW+e000O8001EXq)$S_>jVG*+YSH#EC2ui0000000000q=Axj0sv-V' - 'b98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDaA9(DX>MmOaCuNm0Rj{N6aWAK2mk;8ApqDlyQ@_N006}g002Ay0000000000' - '005+c$9Vz(W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VWn^h%bY)~;VQgtM?J' - 'bS`jtP)h*<6aW+e000O8001EXvW09#C=CDrnKJ+YE&u=k0000000000q=8_g0sv-Vb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjC' - 'Y-BQDaB^>BWpi_HaxQRrP)h*<6aW+e000O8001EXU@?-xNi+Ze$>RV3G5`Po0000000000q=D410sv-Vb98WQZF4VeZ)9a`b1!9c' - 'ZDwz5WHK*hb8TjCY-BQDaB^>SWod3-V`yP%ZZ2?nP)h*<6aW+e000O8001EXko;`@TQL9tW5NIcGynhq0000000000q=9Ya0sv-V' - 'b98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDaB^>SWod3-V{dJ6Y-M;ZaCuNm0Rj{N6aWAK2mk;8Apq?IC^sYt004C%001@s' - '0000000000005+c8W#fqW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VZ**m8ZeMeBa&=>Lb#i4caCuNm0Rj{N6aWAK' - '2mk;8Apjf($pGUX005VO001@s0000000000005+ck{|;BW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB6Kb7^FCWnW`&' - 'ZgX^DZgg`laCuNm0Rj{N6aWAK2mk;8AppYJj{^+_003AI0021v0000000000005+c$v*=CW?^%5aBOXJFKusRWo&aVWpiz2Z){{T' - 'FJ*IWW^Zg{GGB9Ladl;GbZKF1Uu0o)VPkAzb8{|mc~DCM0u%rg000080000X0Ea@yq>lms06+!+04o3h00000000000HlE}MFRk4' - 'VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUvqhLbY*QWaCuNm0Rj{N6aWAK2mk;8Apo)!4;}ba`-Pb1rasP)h*<6aW+e000O8001EX' - '@O3jDYDxeA9zg*BEdT%j0000000000q=D&60{~`Wb98WQZF4VeZ)9a`b1!9cZDwz5WHK*pZ)9a`X>Mm1f6#xKaEC2vF00000000000001_fdeT70A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0p)Z**m8ZeMa`W@U0^ZewL%XJv0~Wpr|R' - 'E^2dcZcs}B0u%rg000080000X06|#{@A3!$0O1_~04V?f00000000000HlFQKLh|~VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoaB^>S' - 'Wod3-b#!TLb1rasP)h*<6aW+e000O8001EXbuk8xw*mkF&jtVhD*ylh0000000000q=AA+1OR4Xb98WQZF4VeZ)9a`b1!9cZDwz5' - 'WHK*uWpHwDWOZX{Vrgt?ba^gtc~DCM0u%rg000080000X076AmDEJ@%0DY4H02TlM00000000000HlF}O9TLBVRLkFY;AKdZEs{{' - 'Y;!MkbaXCoc~DCM0u%rg000080000X0FW{^mIgTh08}b98cfE^v8JO928D' - '02BZK00;m803iU2!U3*O0{{T+2><{S00000000000001_f##(I0A^uxbZ~5Kb1!mrZggpFWiD`eP)h{{00000Kmb4h)fE5$XQ>1L' - '000' + '0J}j+F3iAC?)Ps}&L66dx6eyLy^$H9%g2t#rd9s?Sf!}JcqMpwz(T~Y>>p4|0|XQR000O8001EX3YkqjVh{iTFFybP9smFUW?^%5' + 'aBOXJFJ*0FWn^S&ZfA2ZVqtV+Xk~LQaCyyJZI9cy5&rI9!8$-IZM8yj4T{2r4^6IHG#}hMB)P%|!%$?4UbB%UN0N85_x9U6Gkg(A' + 'QTFa#f$M%+i^Jh?IP=VKMw(8iH=p_T)NyvG3tnEgN4^z1v1OaQ+wOVG@~U7B?@mp{>Zafg$DgezPW(E*&l}FZ`sQ2K@v5zxHl0qV' + 'lgX~B4=l@eCz!;ujETdsZaQF8)m`3+x@spA^{lI#?Ox5;)@8}JG@j<0t)BK(UY7Z$' + '$gBL8HIuc`<27)8}j-_w4T;HfBh)rsywk7lIGtl)P-%b1Glct(Z%9M4r' + 'AfBkYXpwexRN(ICbOfNu?$0mdv^^c7LLBBV;`ESzj^b2w{1C;Gi`dzWzGQ*j%U$4KlZ%$Kf9K_i-!x6#M7t?zfVHP1Y8!l@gAKGD' + '`ys?9`H5{rUDmf^o0r-*z!hSp*OsGMXM0hAnPhE;8s_?Z+K9veBXlXW(i0lCN7@rdo&5T!0AtXZw+Hu!sls@LqKL`UGyd&#POs?>p4BL}Yg9@~mJWVDF7Vid>;d*vj5Xc_1Vyja3*' + '+MB5aVd(^<w!|y7pRHMMHaFbF_t-zrye^6i0{M8EplCooYx1hy;pq{x*9hbQlO!!Vz!6SQp1DK^460}aFr6ZTJo|*THEWNh=sKJPj|3&Ee8a>6L|Nn' + 'xZ{$ubTari@A`$wrQ~ji*L{5|i^@rEgyCKL9~=WMxJtkZ&n6YK%`x5ty55CJ1r?~vY@s$q$jbV_p4`' + 'SCAq#WC`J@`j#IclL9j*yrLLrrd<~ta#c*fDMbeWqM?>idbqGNDkEJokjV1G1z3SQ>}OhZt_EA>EVVdthxwF=M3|KQH4l=hsnD-<7iK&OQDoH0%DkAwR%`J(Ym~L@OoB^4e&yA*1`M@J0U05{uyC>Z}rdnH<_LP8cyW&k`Y|H2Z;LN(sIBE*PqaHKzj4~v*wI!Vc8o!wK>!|c@E{Rfk' + '$dNh4MA=2=I_La-bAk|jb!F>0_VZf{qvbM*^wAg*7Y5yv^A58$PfVFfTrLbx2Zd#FIMAWlVe~x`L3OAMtFI==SF5h!2R#WqIy7v5E|7{?{C@@`c' + 'rZ%a0uHm#rgjRs32R+0)2X0|NbEdn@o-we2d$`B9GI4D3dxJIz?XSOnm*`^4H3V`zJ;-hj7;V*Wj~^b>&DZZ#>`K|!Rr$bjm{4Em' + 'TcMI#tSW`B)DADg+;GGi1!7y#ao{k}i9CS2a7C1b0%eH#T`nk{>0=;8?p?zAvyc@n=Sx@H_KbO0Remr#' + '50Jy{zjDddovx7k-;35ZNavk*p1SMfZSyNqq97;4oi#a=O_ob&WUDDm8>`(gnDInt7(6lQ?xqE8vYns)yccEbBTi)' + '%nUij7XPYcbTQ;=DFFt6FDidXW``;BaBdyW)s?qxb&mwqgjNVL9^oMa3`}xL-%<(j#E*??4JR}bJjs~q0kfM1_0usKj<' + 'pTdUR!eklum@w7|kdiO`#P!Vfy<~M*N9?x`T+tscD#uNpqj3J!r#Nmz^fGEsT!&PyV~H?#gGwK|GmKf@Xm5?q4mCLF)Vw!UljvGi' + 'CeYNLl;vh97RNz^4oB(ty0#4AwV>Fhk`9ZN3Ei6qM#t{eU@zt;i$PN2BqVM!k_7bAABQ?Ms;`+IUBo{r7@yE@MuN!ZrBy;%&^|G2' + '%R=h0F2(kN5)$+oih!`oJ|DRkRZ-t#oXlA#4pwWbzh~1=c_qLu7hRRj^;w0RBX12t7fK;yMo0N&zu}#g4K%4{VG}KJHcRI@V7=N^_{8G5&f_^AF0ziQyo`(q~9&#' + 'F0TsGwM`K$iFoA-IcYB2%z&7p)3&U9qb~1&i?{UKrbyo}En0AsEOG)ObCR)vyr>gZ}iopVD)M@Qac}3zK_TBU9<9cipv1v_EGu9y_9tgG8FW&F#mK-I}1Lok=c^U5-&G295f^pMFHETZR$)@%God3_7;>JU#bgFzGa|J)Y5uJCVV5MCW;J;)nJ' + 'ZNS^Wm?1pU#N4Ob6C_{0=Pdi9fbwSWx=d7_^3@2w08FpYVbWXpz1~^L67ad0u|M97h&7Pi;N%?=?JxxyY9*)nx' + 'x*D45V-4M{uwFa^r`$SRHHcJR?|0+97EHJ7I4r}U@9SHr)Bbf?+KsI7o$6hlf2|?t(b8)szrZ3rYrJ5DPc^`&7~d~9Jp0(^kFh_m' + 'BM3UDS?SU#y&^Wz8=~gT(Y_+vTgAPuw@Vgw0dJ&_J+#ARV&1Y;Z+uyUdI>`_w4C8P38rqRzfU0JJT~>6D7dagE!9^Gy~Mtuf~d<>' + 'wD<;Cj-qK<^s_Nh?6T@mmMwkbKppWMGEZV|K8*7>D`8hHtg=MTr_tAG)' + 'XX=kEPV=&i;`nK5a!iQi)tZv**+x{@0@bgF2Q@}wMNw~`bM`!DKQiaZi}<&Y5W7T1rKW>qUKUB%rI+jUA2ndIWhVPGp|oe2LJo7l' + 't^R2+hPJ-#JSbRXD401ShFv!Ca_JjNR?1?Lq7XredZk!xfgojkHF?pn|c{sn}Bnq2s}Q)s|o+_)pbT&Pt<7C%aNG5' + 'U##OZ>Q!D5xh4ZH9M=0|{2>^ME+OxdzVfg;it?>5kYpNWh(u2O!EYieD)Q6Iuch}7r2-VEOj8`*xT}hbwXFYkGR=sUKL9wp~D(kh}' + 'qWu-8EyuQIb*(^JwpRo3Bb0d!MX>ts)Nk+Cm!GuN^v)WU++`)8>iBI#-~Y9YgRyQ((e7c71J36=>x04rUFFVR{V>B@_HTCe(K;v29rxUZ_#~Xb*YJa~G`oj|65@~=NmiWFGM@%&r2q=y2#>bDW^o`%n%&vmp62W?L' + ';G2-kthMH4(=pt{cirLbslwLu8F(fTr0%8N+g2}2+~=~t54K6KkIAp`=zAp5saHZ&3pAZR@=p7mu6@XPrd(HL@@6aWAK2mk;8ApoFl$sRxy' + '007QX0018V003rTb98WQZF4VWZDM6)WNB_^b1!LRWo~q7ba^gtd9@sCbKA!8yM6_Veh8pajO{)q!)>-<' + 'uOjxyig75LdLGB|$;rBFwo#U?$6@SPmPO^ZZMp#<>t@IitUoys&o+6#smdGq^{2k6<>$7_2OwiBzYk4UYy|S6sVY_ws(F4>$Q9ng' + 'UhP*<1X2(Kbyb#(S8X-!cg>(B!P&a+@%noSbAIet4IC`}W;m&p*8X@b=whcJcL_^Y30~|2+S2@%H<7(K7n&N%r0M-<0P*q<(P{MZ#jD?)p1uNcuPQ@M+L7*JH3I=jgWkj9n0m44`=|3vSaT5P0f>UDVwJh^2cghR*SXHm=t;u*nm' + 'a#8wg*a`@Kl*G7Joxw3xYzu1=tpFbHdn|~GwLRVE)yTB<`MNV&NM9WP$~6a{W@}u5oh3BN;l)!#;4YM' + 'l_2|CXVWJ|QQWb%=}VBYUA!R7^XbV<2fHtOJX;0CziS}(Ao3c7_^JW0M|eK4Rd%0uWnK>w(tfT@S&?-BzzQ~)5Hx?3a-b)9*lVyE' + 'tZTcn9x}9_XaKo>u)=KcECse1jv+~&wPHHMZ7)G;x-`5vIvLM`rV@K+a);wN7Q-rVNr@K|N-iiVNb0t^2jbYed`x0)OyhKB&IXS}vq{YO' + 'bb9201J4D3h9?tym|*vuwy@?z+a7)d$UcadyPG2z%?)wclQD|$P(1>JrVwNRq^4?e_;&-I-6J;yAFwY4*h5s{b^M9*HOuHxmcb`5' + 'UtB`0p{V&@I4lteY7G)DPM=1u8Dfz`39`l&7Qy4F9McBZ4k$AF$N-0s-QX-96;pE$&FVFokwzLI`)Jk&gDO%qvb|pKbFt' + 'fW`ypV&k@dn?Gh1t8a%*0G9*iVrp}Wxw0N2-vAZ^#KkalV_lH*--pD8GVpMnL8o@#6#0!7Bj_m9xDl#=JQsOYk(p8unw*N$y^3jI' + 'ta9*n*TRL;qa;@Y5tlC*e1!nnp7Wc_tKM0MCYof_pJB__iX0J=}o_EjG|I@a`4RhI>G-' + 'GGC3`woeo)zuOTDXwrsm3c{ORzr5_gPEF4l#@=8E`(+Z(Fc@3th8{EtGqJoc%5wQ_UiD&XI)~H%utM<4QsvtDG15S4Drs)Wp}A0he6`{?{7vL*+&' + 'KWtdpMc@%uKnfkt;}A9UT*F4Be92kcMQ|<~_&03w8uB0vFd@;*qsuO@d(3YkSe*7ZkN#4Eq?`EarUvQ?paBG6?103!smfvp({NWK' + 'CMq&)GFA$*0f8Oal22)Ox$zaXZObmbu$oES)Wu6-errT#g|0VxZGl_xtYk)O-Jrj~RVQQA4Tx1!{' + '{`8d20R$2-nI~w%COl1Q;5Nepxsorym4AdVlc|}U!Iz~mYlUDGX80y>0&xw@yJt$O;+Y?uA1F*nyyL_%V3CJ1VuP5Troq|3lS7kr' + 'r3&I9WeY;2f$izZlR>6`Tc2A*Z2l+q_w$S{?!u03^1QALt&<7Dz+(vb+fVDnWCDw7YhY;Vo-Eg`2b' + 'nnUqmR>JM0t5rd9SOA}*%+*NAUDP{^(RwnsgV+aMjl4RT^CuECF+_gi4mu~7' + 'S9*?z6$UJQP^CfJstuDB<0@Bx0&I>Cjf`>HKEisqVW|Uz)$IVEV)|%6=?{' + 'Vo!NrrI!N6icMuiDn^rOE_y{1)=*>{!F}HKf*>;-5(;{`Yni2NNClMk4pMFnJ=x9}LvNe?0()sQ82C2)^2#%j=2Szg;^{DJ{p' + '4%Z8{KQq_CBUh957n_ZFYqBI_9;%;XHq-?o96Q&&1x6Q|_;Xzj9Uw0P;{m$2oGIP6YG<}ti*Zqo^dGuSqd80FxJF6QWGDI~>yhq;' + 'M5Kf3d!ioauBYnSRMr5DWSFjTgQCx>=Q;oMB=m3A5Hd+#%y8oGX!$h(hY8Cy6ey3_~dEC)FoNe=;z$G;u*2QItvCRjgR=pmp3ao6qn{4i{BcLRi&*v%({Be!}O_ik4av8m_lLHNERc#kG;sT2oJ#$fg8mDY9LtKP^qxPGmI?ebzE@77>9ZFU^C9zWmZfnHu$aaPJ*8?Q|FrCNE}+{1?|V' + 'YZ1%03r1aW6bpgsH`b(UJf4I;W;im7)Hzw)MREXwSZE&yZyEGUA>jUyuyhQOH-tI-Zj5h6WX>w1hR+{Aay6OD0Po|+xj^;tMg4gheQa9lJ^j#W0b!g' + 'aqAImy#pde$DzO|)r&F#+zTt|9;z>I>m1Dd!=|kC1i`*Y+K#Z38z4a7gUL}o0?w`+sE!)l>;|9%jUZTn2oetj;X=}^IYYn%@g33`' + 'p%7D}AU`omZ0{IrvHOYNXmYs`Y+Twmk*Sr(tI$@YD}+`i@Tn2$T2+TogAGY+UiNIti<5Qy9Yv3P3&5Rj7kxqke$_q6*ftlZqh~xS' + 'a4vgtzrI!ei2!^VC#+sWl7}6$k(QQ3i0F*xA0nvrk@ZIq@V<}Snt7~ijE)zj1$KeiHS5lrhnnPxuK9#O%uQ_5J~xbRL;V-qZt|$A' + '-Qc_E7~Rod)X?;trlu$JYv^|Uh#Pk6CVi6P`PkykW>T>_;hcgzw8=4o1A8ZQ;qZ2Yvnaog#K@JEuIeVGT?@RWOa8VdRlD|qZGH9_ObZ9u2' + 'QzOq2I6a*V)X_cNOcW5NK&Yca9J4|D+V5WSnnyR-W`JM_!Sd~7jUlTay0I8|tg{;13W6h0v%g(@|4x=!wbBfdu?0L#?|u=qQPUNY' + 'tSPcxF4HTm6D~W?7p&1BjnwO;NQq_$|+8WJTuu#55nsna>+hYR>Dy@y?FoCh-*C>k(Dr&aAZ6HPk{dDmbsc9RdqhD;=JL4WuF!BF#Hk0IMcvm(aC1ZpynzgdAj&g$+`zrdu=' + 'B9n0R8OudJyCkaV_Dc{59(Xe%)p)6byybxco3bOJxIuQ&YEiUgzh+(dC2tl&vJ6M^n(OmV7RfWYv8~qh;gpT0rHQc`(Asnf=AaWXPwsc{aUNao|m;nck*s`@@B+Q%)Vxp&w=);>2Mj' + 'nQHCY58zCZlYYRHI0+w~2dExmxHh4JUmyH}udOfSkcdR2ux844F' + 'Q*ZsS?V6wX>w{VW&0S6dbL(-e-xkvm+ek8fI562$ZS+mwS7;J&SKgadin*UjFsFf$Cm03PaiHHh$!_RC=+L*}_Lx*Q&aIUUkdfJAdCLg8DnUL=}gA8briUuHfi' + 'Ef`Y3+B0j8FGvzQw;x+GMexH*TiD!)J&bo}Jsv=8WjHD?(%!~RMH}IkZSh4v{Y@pu@T&J)OupQ)-C}QN!}J22U6C>jl`KqoDFA;{' + 'u1366?(ZgboDA+64xDYvdhE|c_uUz1U97)EVx<#q?xt4Msx`tR&+Gz-zvy9=I?R^>w47&JYL5rD=$#!MiOi0SLF7%RNVi0Cj_+pB' + 'J3ZPknq=^z{n(84GqcsBGgZXMX-hvpUv-S+{~}{Gyf;5LvTY*KWb`5h&x7n!G7>-o5}Q-y{goGL3QthCyr5q!~4' + '+|Ei}^^9*mpI@8ps$jT0o1R{<7B7#W$_a9fm#Ljh!H0}j@LBYQK=uXRL3|<5eIZi@H*!*W&M^U2psmjZpYcmW^L0xf7`@cjxj%jGJ1*s$71`qEJuGiUbiVY#&T@Z9?!Meg)FEFD' + 'b>#BTIVbP+jR_T~t?5EkuJ?#csI|}u)rEY!1X?i?2(KLdYDo~X)c84+5XVtGAxJ$#A)*UL`xk|DvVyXRq7&QX_tZ%M8Dzo}F{Li4B|h%rvZLe|iOxjJ^QFg270-v4OL0em-qg>#vd`#e9;oPpJ!r|Z' + 'Y|TXY*y}-vc+r-~I9AlnU)OM!G3_=bi_bIL$7n%V7|q#7Hc*80XNq&Y%@bbDkw4ktN6F<7g)ktZpQ-6Y=@ym3Llq(R9j|>l!P7NY' + 'OHZbs<#;^svmBmqC0_V~m|0*BkK`AgEI@B(3uX7E)%XLB!UZg6#U' + 'b1rasy;^N^+r|<8&R=oCCm?|k$!$6rPAFHl=~Po$_DD=xk4A$z5=Rj=UYI+;lA`LrclX|a1CEr`W~xan@^H8B`|RFAK@gnRyrf0`' + 'J0msE7-t#Dt8Lo|LaU5y3tEw7-BuZ_-{w_T-wOEMZ0fcloHaDB^6G|AvTlolh=wy-CP5HPCTm`oBu&?C({h%kBrmr$Z-8i3H?+y?' + 'N=zntZ9~PT$d~4;_^rqr_9yeTVdZw67fh2})kVQpl0ZV2D?|6RVVo`t7OPK9&13R`Znq#^OfK1PEvr^cQP|Q3*_o4{;H!Gj+~an$' + 'UVml0sA;D8Ch(LA^Kf1+S(d@>Q?%7&GRfGQfYdx)Q_;{6c}&qB*<8R4_NJU&Wen@Ke=3-fN@vg4`5gkQMY`h' + '-P1EZMGBsy#8LOInoilEvIj<{5X7ZQZA+&AME#-}vk|Gz)6>PnRO;^`)%NqTkDuH12JuQSfi?T@dq`py7' + 'Z&?)vJctQUtWmsK&^GIXAA^Wc0e-Hsf+=kkT@LIjmR2no1RTccZHf!1G=oJn7Yo&~7~}^DXGc*-0Dt8EnLV0-==nB`oIUxPK-9^t' + '?qU2{)nK8jWy2Gi8d?SQ41?n!8lQ%ZosCH@BqKELc&o>h3qIg$e(q%?xN#D8jADqE4;kOV#zBrr@3I{S6Z}e&Uwwlb?_k<0)teAbWfDJQjUA*`TsEq#^SsBbunpv7{uZ;yt3OPBdNW+r0)WQBP62@C0lSK' + '%K#Hp3UbkCJvWe=yLI%^e@gKLpo~>nXxy%XQ#vowPJc!u?u)+vE#h>D^ci$)J%Mpv4`5E7#Jqn%D3PI{Koz(MwhvV$VC_QGPk4I3' + '>UM*03EmTof0cl5wG^>J*;p38tOWzF0D?pZs8I7-UP-3m0PiCsa$1>^n#xj|!p2g?)wUKn)~+eSb*xMW4~Qxdy_3Z&0){9_;I(m(~Jz?K}+p#wP~x0eeEDQWQ&i(N2O=mq=Q+fI1;Jli7^CdJY%d4cHV(xzx9#YaM(lz7|lQ2N@jb' + 'PNr-jHxcJ4R#P3nVrU=o6(dxJ0D;Jl?aoMvE_Cjqn^y%Df~4}?<+iSi&|t$b6eAmpr)AkT*rSjv7hevRboB+ADjO@d?~+v&+OE7>' + 'aaKZT(gIH0Fwt~jD8me*D{s=Y(^s%!9ouh8ovCe&uAUcT*NTD+G#5UQe3D{6R&&blr?xdq7G~{T4p{~*&b?|OY`d{%rwh1{mh1d(' + '7GPR!i&7jYqa1Xw8M*Rc<^coQg$x8tDq4c`zBh4@sLnDNCGnGj8~W;pzw}zC6<{)MIJ7HRgCKHr' + 'm#6{b!CMJ&N2*B@=J`@00$&oX2$i6rWKXu4bP8J)B3Hbcb_@-&qw' + 'Y3g)&4|OH{5$ftPzX_t}kTAzbTeVwElh8B99z`QhCyf67ZbpuL4HYV&8A;eaN)KQC*>9~~X;9MYJ`7IJFXtBvjO&jk_u_-WV_k5pLIWr=Oz6T6C9uqQToFm9Yj!Gp!U1X`%N' + '_zGkbD$H@gtr3ooj&jofPcu3kQ>xYl-u7(`?9R(ygG!#}=FvE1$aaZG1Hp9K6`-dG8ZdxvC' + 'yp<}79-Bow>gVPBZ2op(i(s$py}$VQ!Q}48i+4~q-+Z?9##aT^Wq{*YYdX^+C2Q8KHWCvg$VKB4=3xy9jAX{i@4s1%2t>-7f3~E_Yp2+q8F@`k&fj$_Kf%H{gT|=d' + 'S0@@kyGkTl6hpYy=6X6Oo=CRNa8_>ZQSzMw(HJ6f`6qHHXGZF4KZJUkACkV~j%R&RJ{&$pS_*|F*&gK9x6g3pGu=4w8MqAEk*?Y6' + 'xN5^g)#w=>R77d^l#@Mbc<<#v4zA=elEuq-hYYGj+fv~2bJ80Sgf?)YSNIY15*$>+bB_FORrF66a?qB0_yPky8lpt@?#?j#SM#req0yiJo8Wnw98DmQX{rRA22K' + 'Y)8Rk#mYBm($C%5)#wsBTHzYL+A;@QDge?Qs%~fQr*iDv8mf4OI1HM>)Qg+#XScLa>72=B$ReG2D9(9boct-j(GN%PMUT)' + '7$Tt;x?!ib)I`nSpW}f&RFoKfV@P3eO|`u692dQdUxW)tS+*&wL<@6ld3`_OdFz5ZE~' + '4`&{41e^SU7g22sP1*nr$e!bMD}@rt+X|=V8QvytnvDh>YouM-??6XM@APrgPa@c@pnPI5pkbf800R6;7$F7RU+L2%*j3D=zVcy+ncmR7dUd=z27>MgbtQ1)D@Mk?jLKI`>p*;4WZsZ)NO;VwmQ0R7T9`?-O*R31r43o' + 'R^!QlPK$o9itZZZiCc{!CBDg&XXBn(WJxfVkgto})HBVMDims6$!`9D1se960{~gMA=bXI`&mKDWk!21;lhKRsz$Nhr!iIg(|Vx;' + '_M)!zEd6kl_n7ZmE|0->v+w7)FR&a-=yz&J^VlDTe=HVY)<2jW_2sh<5`1#0Y#**Zj`G=d_n@h&Go8(WLa4vR+6r#(#xkK@6aWAK' + '2mk;8Apiw%%s`)0OGilK<-nFbYv;7|RyrG;n' + 'h#u}})3ELyJ+ZFopTsuHvdw1C`vbywKgy9)j1fBwJ(obX>!qe4?3%i7TdL0xTXI*sh1c|#k#;pTiv}VeT6X6qU&EuBkjFtCZO4D;' + 'j+W|{HMEmV9^Dka`u8RN>E-3$F0Ni*y}W#buYdge#lL>QKVMv3zkGFRcMW~tdh}kyBjt8Z$expX>gMtdg3mSCQ%O6~b8#&>C5QiT' + 'CMhrVBpC+UHCX)8!WibX2Av5r#X2S#X!{+VrpL}qwLNKIu^3zF4NOGlaWS85$1#1&gcNT2FY6n`z+uCl?28}' + 'xhbsNpkgYV)ECa$FS}us<@|i5r3(i1$>^Wt-SLe+`HJqrW0Xxdn}+TY?nenkEYrENafO8B=)c;)CEEA=K%^ck(AVFiJ1|G3`BrPu' + 'PA}8@3ZvA$-GX*Ilo_Eivq6101z+s+-YpY&&sv(Bjdo^Lq4tcB?S_h)vjMy!A9}%*2UlQM5ZPF|S}I4&(6CzGfIy{k*4xT649H_P' + 'Zq2h3i3!#TNkDJ8nMmrinB{Dg#ekYwz15G?Bv{{{)Q|H{c29*2X2DhTG;IZqj{re(|CnOS>7jq51E+iTHqSsg^}{wR;^W7mb@CV{' + 'iZw2*!`j7x!QM}MGG~IULhffd&$0%_t#(=CO=zxRPJesjnk5+5-{Cm$qBdwI&CCbzjJhR4%ocy8eZ(jX@bJ}BhvWlA%Bv3KuBFIm' + '$iOU2)cxVWBqt2wRw#mTK5Lh@`_l7w4LH?=r8i;p-p)(EoPU~A{Xm4TOWD$Zno1#+4${)M' + 'HwW+noHMa8F9)pI11FRZ93~dTnBwom{Go4Xi?z0*RS~-@i?QfOUeg8TK|_9Jp+BN!69n$af%=*S8djY;%GIVSFw53jb7vJCPCD4K' + 'stqS+9jb_%9>PW7KoC)qhOnbB%*p_J)z`w#n6!|!nxh4}i9J9a1ex0GR8G18YxCqFdX8N^4M(5Lg%MB^F0l(+Sn^_ag#8*r#^3bM' + 'q$soc&qxjKBl$5N^yLEP;&eP|Lpe2Tb_?GwHZirD4if!?9xpiW`FTe!&NAZcmIjEi`qSxw^4#XFs}HvKROVR_PW1yl5d28FP&izm' + 'KkQH*aq(Oo=C3VcAvj@zqMu1SQmwi?sk-dK$vRc10*yj>3qXqs6@XKP!eA-n$$*Ru-=Qo`wtnb)O{E9S=FT%Abqgx3L+*Yep#5!3' + ';9Qk1oX6;KiI$&JFHcZFKsxH1v4(Q~?uZB^x4kYaV`d&ZVWcGPof`H@-RKP9Vb;jmpNWmsw1HKDDRg;m#v)r5Q' + '7(xg;;+nAxezY~-{QeBLo=|R|RQYerD4GeHEvTLS?X-z~FB6nIg69vZ6J2p7|m6O|*D(^utYRnRni!7p)6pHqLZimQ=K%>4J' + '>AbNLfi9J1C3?e0nsB)=iC-y`YY*xipZDQX_u?o9aX3nQ!3*L27c0q#T6cUy3o^Z*BRht@@MNtAv;mgMr' + 'bSJB|->XC$;LaECOjcj&^97KjgF-p_medMS8DO?bu}V&TEgU<^T4ZDQpfWJ)T-PK`@_~psdn>He(T<$O+|AN7=e9{A=fQ@;!j#?4' + '*d41Q+5bvs3~RY(IbY&OdWWq=%vYJnA~P`RejfIkLhH99ALr)vS?UIer)zl&UVXFZ5#6F?(R_2O6rBhvp9fWF#q26i)Td0DMJ>#W' + '+1raGhjVAkaT%roFAo^*X07O9eQunX)ZH12meuO!JX6%Ipc~ydgFm-Q1Mfd<5{0tXw7H-A!iUbK=NtUQ8r>mudv9LcZZ#?x-Qw^5n8TC(g4H~4D6gEd4eG(-}pRE}Y__xxcrYf0nDwoLz|QW#R%d3W}%Ysymx|mDaS~1Jnc+{q9dZxE#AvOIIT0)aIJ&n&x8&' + '6m!4ZyiUt?liSuaSl^@UYnP5bJ(FA$z1b|wel!&hnQJC@2{nfx$n2`HaxVZ)l>JGddk|o32{3(}JM#_`)A@8gajk6F(zjAc9XHbA' + '*HFGr&{uGG-U~Xtj!HKS!ZoT{Ks)da4vwCC`dn)dgn~w8(dnuslt1~hG*4f>*I+R|FJ9v-DW3IYPQ{X`Ze5z*1tdDU*jLvpqd3)l' + 'I}zod#4hY)oa7;Ij@sIHi_dQ9pLWN-Ex*>7mH+g2j5+oHBWd@N+s*IN;5wH??i6@g(5&6' + '_qN1)c~=IhJA7|fEdK|=(A@kFP)h>@6aWAK2mk;8ApmS*t|*8L005LG0015U003rTb98WQZF4VWZDM6)WNB_^b1!gjZ)|C9XD)Dg' + 'ty)`e<2Dw4_pe|*50$dA>SU%xk&cUMx3h}{)>C6TyYL_g6k4KfZe&WOqHCXf5*{iR1OY+DjJ4xGa|yp+e*pt#~fV(a1d8(P~?NuaJUSo#a`@5;ZrZ' + 'tE49JEBkHFM8fnWwV)pQ$V8dT(P*^z<(Gf`^Zh^K_tE^_uVhAG>Sq{ZmG@=5V^oX-GTzY#{1Ca^sh=$6dZ4UQ{J5hMKa#A(kAljI' + 'kDq)Xp0YKG?`XzTTCtdmVqcZs^3re*$h`jQlgW?f>*a{R>lJ}%-YOtjKvMq)oT+~KYLu@D7>DqZi?X7ip|=bgO1bO~&YM9^9AA?1' + 'l97*(1$!rDF1_&*ES{1%+3m}UtQe`(iZUEGESI53`Hp-wBd@!3{zJ39A+!ccipG}SF;Z@6!K@+y*??$Ysy%pyw+z*&|K#vM$(JE7' + '<8_w9=8H{ST$ck3UHZJ*R' + 'c1a+XdN~TR_nbaVkORr+Ba`238BIyUfJBgUO`zP|m>iJ#H#gx(t-rZZsq*HAY' + 'MCPTMqUg{&$}_rx{j{ui6lkp2I){3+pd0pqirbKUU`0j~%m(jSb)U=IrLIS+8Yx=JBXEC{Ed|!V1KdI~Fh7zFYrRLcD>zx!8a8O`' + '>1?|38^`?3(6CP^;{fl3@jK8Qf}ZV)>d{fMG+b&g(1qh2g)$e#Tvv>(v3l6dh#>5=--6)Dt*0}Tl?#%zj;7j~gr%a0u&iX4Epm2o' + 'y(SF9dP!}nNS6=DDc`oN5}B+yy~bQ@ole!Gdhq-j7djrr5^4&I57*2?7v?*EYIq?w0Z1T>dFqFN7Kc@MiO{eA6rn$EB|FXbEs@*)+9w-zi1dL=1~K6pBfz40V2@2Ij@mZS*|>mA1WtMz' + '-!s110v1#&q6QGEpJ)0bg%Sms>QW;bu=q6m{#!j%K}wgHbL`+bn|!ASVOL@(5R#f4k0=Pnr_*3Gl)?W70Ayd;BuR{D1X3s94XikV' + 'ZGhNaJH1wtKfp%n=#Ic;(@M**Z|0QdmxqA{Yjn?Z`v0Z<&h5EMs8K(XD&E-3C;&G;ZVKP(G08I2f+CMl$LL?koOF|xrj' + '+KB;zU8%&~kq4VT3$fJPnx{ptQ}T9JhYWd3&X2|WAC$vUxe37ImT{&%XPaiKBNdb~uHw!W?*!vCw6s8$G?-|5%rJjc2uJ01_6am@' + 'Y+I!}Q{l}4}qtkOlr}P)m;r`4fgLZd@@Hkfap}~u4@4tJa' + 'v!NO4j$yHNAHBX%)E3HCDFej0^?cRzsUFXA@xvs8z>IfXcvFoiY(j@Z5SM#^5@Zp=r-B_5z)sjJ;P;Pm&zd#odW|U-R{DLG#Tmb4' + '-U8-?Q}T`3t1h!ev*douB=b7Mt~@`IY0x}e07Wz4=wg_2vCchLTgbkf&5o|3qp9}1(EsTna0zwKS7p%6+om0@fXz~gGpZ>Xm0y$v=WmMB1V`#>)SF&s!)RZof$yPKIu+Lgf2cF-+9(L8YXR{?H*~dlNboKvbtGZl?M}h~G*5wGC}T7IddfOq=GH' + 'pdrm$FR*za}n5R(oqWNAoXZ!$UFd|@Vv1#dLSgU!@bxV<3PkP=6W~fU~>T=kNTan)j' + 'upOR(Yw02KTF2zBIA;Dc!mP*tHgx-8aY?=a(ymzQ8Cn+ms(^s4fOx6YewvA2ly!S%R_0NzkmP%zrS~C6pFg!MXemA`_0RRxp>WoX' + '4dC~EQ7E}U6HB|L1O{?gJEi)x(XLPO;u6-e(u90!PuxIfd4`Cmi7SKFxEc-MAh5TfoBIm`-uci>Q){CJt2n@lp;!pq6B3Fe(3PzLE}11ouI>(d3KllCpR18xg=!c)^6I$y==GdS_qZe' + 'V?6ZkkDl4DCmqJZBh2dHIruTKI9}1@mWi~k5WU&G#((IFsV%f~+Fqa4_iwU-)}rnyy;>Kuhk}X}yblliTL8!n' + 'Cu^2CeKu=W$vLWs2=+C#ihx*^O;s`2yeA853s4(pFm->zuZ+xHnLf{@B*w1d0Vs_UF' + '1;>+nQmBZ)D|D$M$Xh}!MrQuYeY<{vCR{c7K&~czc6Q;P?t-=1Yf54Ho2)#)<@IPNjinlXr))Fcp&!`et4q-B`XH5(_16LAUXsM)' + 'x-r9p*a1d^wP$daD!cU+P1C_wB5wZmpisi>YsKnoKrRHkI5Iv=t#iDZFby%9>#cBjt$$SQ5`kX3DLC!g$>_ApQ=+kcX#}M-azDD))0U6cyh*UWh)>a;hTzqCS_h=a_V5xatnFy1?Fpa3raCn{rRDdRXh@lKZqQ~~$' + '@bKFM=XStQCvuqnJvOJ^Tat#Ub8xf_ouiFI((0WFX;DDnd-vogwB13OHLW!8kJL4NWvb~ODqKen>pt}l>t2?2*5gI5lT%ptEBSQf' + 'e*#4P18O*bj9170d@zu7+S(e(R2%t|Kk8y}Z3AeEJTq4peis2U;eu}k(?_FG94p|Bu{3Tqpje6Z0gV2`Roh?2*Q5UfP)h>@6aWAK' + '2mk;8Apn1N6Xfs^0000;0012T003rTb98WQZF4VWZDM6)WNB_^b1!mrZf<3AE^v9>99?tUHu7D+0>^uZ**(n3I`4`d' + 'q4%`tMR#6r$e~|XWx?T>Ns>$^+qT)0EZg>7-_k52<^IsL9l+L2$HCZSBHOyAEp~E5(Nq;Jc>gkA7i!qcysGkbMd#$AqpkS#IzJrB' + '`gTsP=&wDk3o0-i@@`j^Yc=v6d=(wt1A?pe7xjZ|#@SWtHLY3GN?MkxRjpk%a|TeRdZ!Zt|Gve>bIz2eo%5!wrL5faG^6`9-2i)U' + '8Od3wS;BV|G3M<A<~qB2`NR3^7unC}msb~W-$=^xO@08z' + 'X9`EQryaC)x$5sa>}cw2ofjWyz46*V(6*+PJ8qyeE1J5a_nm52+7+E~3xjBTXv@0G{>_@2>xAgqHyfBKHV~BA9IRO10M+GhutW_$' + 'Fb*xk5qEZhjaP@Jk#frFrrqZipvVNgYz9G=8x%=jcQ7@W2;F2At#7-X1SyfR+Z+Uk($wEXFIXX?y4(1w32gcfZB`O^u6X*EH%l0#' + ')KLF-@Rhc*4=h8g^Dvxi^jv@ED>lAgQr1`9WHQOVJAd`!rysAgZ(m%${2}}0;>~w&f5~1$%f-7NFU~JXN}hi`$*z8S_wMcG_4#+%' + ')%DxU7vG;}uijq1esO&T{XoZWV0yHheo9!rr5(3Jmdr`QeyxBf$>R)W+R!cGDwCmv5S;Uf3w(tpEjh{sup(3CEN)oWlK(hMfrhR>' + 'h|W2I|K7icCh1jlBeaRp=t*W`f-~`lJA#aX%vUgH2kZDk3QaJ3B}Xa;btCb;X=)0<_(#BR`>HZ*c16~J03crF6_X%B#w&xCAR<&k' + '36&C?l^g(kPrs+b1lMgZM-B$S`53~z&+nz;iq!@{l!gcJZ&1lr!rLzS7mYwPZmTBmzLXqfyS&}ltR-jU`QN|z>g?>xxt6;0cZNYL' + '7J0-8X?-l~P4m&f99b#n2g?p%jeKAf*z&uq>Dz)1{0E@}k*(zYhVpNjQqQAmVGRI>24lnqH3qC*?mlN9#zq+4SMX+AuZErWX1QF>' + '9cS?VBrMADu54QZtqPBa37d~#(tXYP1G*siJQ$G8qm)dQ1)d4b@iYuTEsTe(ny`}Ar1i#-(%P$cLw}7O|)xB=7yh;)C(A`;{c<-NSN7b04=u!q9#(ZvIatd>M5Udjw(3=q60l>' + 'e+C9_`gzGHxdzKSZ`-DwZj(>AfJd_LSx45CTWJ^4(^j+mjdv4S|A7EAnM' + 'a|9sq!Rk|68}zD=PcYQqXO1|E=CxX@k6mo{JWesNyOMAOSnv*Ga=sX@8n^qz=~~9nMC(YQDTh*$42jAXEpcJEP' + 'i4O*}EuP1e(FYH@vJjYK9-bxz2p+g!yE_VlaFSwFVWq?@xWou7tdob{v86_a9bZU{wlSOcqCnFy#(a-Aa4v(vBc3SnXVmnGbi&yf' + 'yaCIS@kIKF{}0Sm@CuQb3o)EJVZAB$efV%nJ5MCYV&2tDLkMByc_3!dKYT7!YB-6D>lT|=DJ4&fYew5jZ+y_(`G9NO0`TDEJ' + '-zTFUpbV7A3>XtnW8G{CPGNc^q&s0foQGIT%R#xs' + 'jAUKbdHWy&B<8GaAbA=gj2p}h61S25=QBzEpH8(*!+5ZW9^HubV50`VnapQuSx%%!C^&S;AFbsNRWZg@e&`agQ`s$W8tSjwMS#J9' + 'c_~SC$Vmocb4lS9b3L_haX#l96e(6#4DAIy1cpI^%hTB*TeUW@5Ea;NF?EavgX9+6UD?8XeO*z;NQgXBXcX%qqUboa=9?K}kYeTh' + 'jc`#DtsCZP~}flCSkBcM#P}I$pI2cG~Q_>2F5T~_RW^8-)_tMB^E4Nuz4K6L#fOS6);R;d;V@zdLry!C' + 'Kzw55juEe;j;fEH0z5M+gCa&0W!7hASU%*|9#4#}WAZRSU3o;qafh2)`mu;ruM(qBTpk5sWa-+ToH>OoNl;PB}yvYY6BeD;ZckNo>?@Uo+eVH>BH9' + 'vd#Bp^{`+ET9n(e(5?fR0)c6XF^i5KqRcSK8%y=01BY|hCkb$vR%`rTpoz-SBxa;Utf~>~2gsvlLl|a^9>^R=Gn^s^3j*fiM$niw' + '7Kx8ETeSo7?alEg7&jTjidq&AD^zKVJN=P4KaW-wb@3{_(|miycE2v`JyNPqW6cNdOsNBtMZACYgI?6`;bitCq5+' + 'x&zF<3pFEBBN90y$IeL2pfu?1{qWQSvt!N?D!(yLE`Qe+0ii@ibNoCYQcBZ8h~I87IQNdkr}AW+Y)bY)+!WZPe%LiTesZq4ET_|@' + 'S{-nt`lIevs8aP|Zm{kDBfVHfNbu8)KR(r31C8ON-*A_t~I4V~gUnWOvcrsWUdRtfxq|dEb;+!Pf' + 'V+Fh(EDv;YCQ*H;n~$~X%9=acwy^6W(?%TUa#L=%w8gz_YIG0$ilY5v-1a_y5Pn>e^yqGglBXE*Wwvxpv{=+`nYaq)WX#&b2hg0#^lkEq^JJN2z(vNg3_ckc-' + 'kI{;cL8=@c@{fNdF98p@;euz!C?y#uDbLSD#KbLc0}c=`7*dmlHH9FFN^E)kK#CnLJ{%f6p4sJW$DOFW*&^>U;$cKB1xA{p=v%h@' + '%qj)0|XD)F65<=y0E=A>6TOT%Y5?myhW?J$IY8W1a|7;VuE)^' + 'f_DbvnU1xSk6lu+4B;p#o`d1EN#n6l9SOlL*ukb=KtfsbyfE*(W)E6lkU<*CW#B&2!4^w)U?p' + 'hK!Y`qsBp~iukZyUjKy85F(0OoKzZ^)(IY(^6Q(-){XGokK4U6*>t7K?' + 'o$_1NN`@XmwwA3hC+`j_H}=rwR4eC*opn1V<^fn=6Oqhy_+o`@KEW$K&@%IQT)1xwwHJwc4' + 'Hh~mp9(P@7zE#o*RN(@>-7qaiNp@V$VrVdu*6NOop%#LyXDo=)X`7E|l{JzoX<(arjG00$ewr$#z(ZeCdS%Li&' + 'NScqnAfhr(Y@ZqhV5}J{C*S2QKQL@_@FjL+Y0l5#GI8dE2@;sWRJ!fie58sPyQ(lu-yHDDRas9hg8nvw(w=-4LaVEb^3#opePvAk' + '-Wu+o-q{Fs{oh%R&ZIAUkoR7dcCI1VE%*CgTpZV@F%0D?{U&%-aXD}}3lNisqzP&tt$+%=+!{Uha_PyK7QlTv}Eqfz+ON?4^Nxl' + '9BGj8$u5TVb=f5=lE~)=1|aV)>!Ru*XoCOtPKX9Z!!j_z{<@T7N>>KxaM=U{9!AWgjZGg*zLi?O@OILFCh' + 'X)+#o0JfeUI`uFa?+g;*qgIZeGXM;~xH5dH>2TP-(6VzqY-V;#&*~0t>0K$+d~CRei{ROo6=lbe2p}JW5Y%3B<' + 'y9_Av+0=@hQh6A7;Jwvak@3{caF&=bw`LJGVP?*oDe1KOy-@U9P#BleN6>WI{iOcBBWu%nlLOFnBaykd)nmI=na&m9{5#C80`f}ysme%sPY9UCT1elmeCC&tT^ciBF' + '@Y5_yZA~mP=W6q^ysie=l!COiA6c@EN}O8pfyBxR*Bo0DA)g03HAU0A^uxbZ~5Kb1!9WVr67xX>Mn8FLQKna$#p>E^vA6eQS3c$C2oF{))-?g8>;tNZIlA;s(jm5@mBEQMx3R' + '$ji$!8Uh1yBD^vKP+}PV_gjyCRQGfbKuXTt-4h)jlbq@4M|E|*yDCl7my5cd7IocAs$y9!#_LgeIW3ZUwW#u|BAG0zWH4Kd2k$3&' + 'y;@FZb-$Qh7UOX_zpCH&(=^@LnN*8el4XSe7;!at8y`~cXrgX>%6|6mY4d^a-o0wYrUB3-|N3k%T@8M`EymR' + 'M=Sl;d_7yIXr-sXe`Qs9b-m*Eqs4Rzk8o1`{Bool9<7QhhjsV(H|VjKoECpu7xPiUA1?D1&Rl=+2L9w1' + 'tJ@{OLO(y4-}aIbfU1`)Zi;HU$j97ly)MVP5&r(2`Wol1pPv=;dQqvB^r7RT)~`A{3H&?Mpg$=_i)!5SAJ*&XiXXnr|9;EeofMOz' + '!gcg^x;r~N+4Hks-W+Df2d@rK2g$e`tCa-K+7_W`A6b*Fp3^Zf9|!P}Q-' + '+3C**C(pCfqyIdFuAU}OlKs7>-#vNq?N0XU;9Yid`0Vw`^V96j;Ys%V@buZq(VMf^C;Sn#-_M}&PIh*9d=Gp4O)tSe' + '5f0Rm1weG9&a}jXvsy2wMYlQRd1xSKjEi{Rcwn1d;v%R)H~ytK9&H`;t6~O6epwZh@>3_}uhPx^7B}Un*k)$*7xa@HFXlypOK$=Y' + 'z5=k%0f=;*dI_R#0F=~zM?65{ug6o^vMj0^_M@1uvRSdp$N4H}Dy08XZt+q0ctyjxtEjMWOZF6`FeG|s7ho=H+VQkiE>08f*EcfM?%l^hsQat4@lSXGOvlNwrWXF6Ngz~hRl%%|nw3&4sbPyXZd^>MPe{3~!OjY4W5ALcMwX9}`yf4Nw8(#3kUT(9c1' + 'N5Jhi8?1;;WXPI}V6*eo-^>)p^&e^o$?ZjSMTlXa7A&S;*_8T}4)U' + 'S4C&9*PKJo8%rE#dI4jbZ_c^Df$H}g@6!ujFmIz;wpuzp>LZB9FpX~V8{pV>AUPPf98FEtydr3@G5#SXQe88DeBLR{<{oVK8gaN+hH2e(9(gGS`WRI8xlLhXCiHF7bCb@@&sP0>c{5z)U' + 'm%wyX$3hNx$C3nh?23r-E+^^RdA(kuCIIJ6O_uw)QytzrW0W=VY`@1pBa2Y8d`9{iX=E@@Y^^)i#6jtRPRG;y7GD9n^5!rGk6WPe' + 'dOp2PR5LX~4I0uM;X^!UtOppER|UXrsI|nt+Ee+qzg_~@EIJZy=L!TDeNb~2V{_o$FOa48$ACn2hXl;dyFQ^ox9ff8B6R3bh|PD$' + 'pau^0SMRTjPyCxD^*pHR-@{v)ATxRTpFD5>3>+fBz(z!22JnuIEMds{zJlud~Tu89cM;WVG3=aI7#547P' + '1ySmSK*vyIFs!Ouu~W2EknMyHcW(P<)uH$_DweC{PsJ?(4ebmC{?c-gT)5(yfgcL4w)@{y2wWCYsYeIS3|xXXS`6xrU8aKRR|%0p' + 'l&97;46wjc1+&X(>ATb7rkKL1s;aU8ppZ;c-5^i@@daptmnHB_qIA>SKJ|xRWzamoh2iJRK2Gd%J(&~$bAV`%S1^P(iu{6<{T3|}sEyEjy~b)i&u00&oB+-E0!t-}1kPFv)iP&n-brB&a?%e2o9Jkg-tmW^s^9mw' + '==GtH^}6(+Yu*~z7WHC!Qz$$dU6<2wr&=sP7e(lyA#_SwqM|TQddW1uET;6Q2X);vli=46l@bL~Mv2@3UehozC+9N~k#Rzuu2rGL' + 'dt~PMCA3(t@H8NI(p~m}2E#!@c8opSj#}x}sReIxM@zeh5tWq?Ny?<0774KV8hCbsq=R)xU}m77!CAlUyO1{X(GZ3Qi+MT9r$oXO' + 'jbG-sNZfo4*4NrppORmzFMFi6&sW2zzz2ZbW*>^%dU#f?fv|j&r2iN8U$gRby_)R)kQ%OnpJ$ERIPFlu!05|Jf>o>{#CK+q^yCZq|c?=W3p>JCcE%O' + '+6voaSWce2fG^*?d<8t)*o(FX@C};n7IQSFG^VHC=@k$%nUf#nv}1hz&D+linZzL>%U%*2&-chJ&rgD&tf(MoZ6zT%jP-uf(TciclI0OM`9p~C^#Y~F6(&CLgk' + 'p;CK5_#&vJ;p`0cX~`s7JH7P36iHz^2ml8=9WU8x1y&+m8KeO3g#G3t)N7J>XvrFg_L1AOZpOGTP+H4fVQq*HZ3' + 'I%oIM&}T@K2vOD={u1CWM+*sKO^CCcV?{G1k_}TibYl#CWHz0Z$Dfmjoso7ZUKMjRui?%<_K;%(' + 'f07S6NoM!H?AdXN#U=$khPV1u-Kk$#V>j+Qk5>Y0^>QD@?#7FPJtH74U;x0XcH!yo>L$S=qB1=|z))VgJ9}8H%qBfLLw9113#S|N' + '0l7?VT3qF$+l(94;U3Ww)oDFxZZAL)%KBjkj!&847MfM44kc!L0GJkYZ}g}*Dn9J7$HsNsx(&!%i!Gc1fA9E2+Hp*~MDaUog$6-n' + 'cMcO)zRy*0RaC8Vth=kEUrML#V=&&C+9vcj-cb)X^m9OtyZac>b)yH0ql)M{e%VbLfNmXtTF(L5qhV;~^C0+nA~Jb8n!9YENwW(B' + '`jP6Bp7o}Q8e5If@R|DboCzyMUXVGI9M;oSR(yhUUx&?O6u;y7fOlr_G3s7ddb_2{N>MIOYvIYXuCpRD39It>r3e9Vf%IT(=IsgGbq7S<%q8O#u(SCJqgSf9<4;b5I>_-ysKstd#tm1rvXlLAxxJW?WVw^?I' + 'vu+b@1_hFHr>v_z&mel=SjZ&3^Ls%{tCbGwR3c6)pR4r+&{wO<;U#)eDE|-~-VM8$0+f~6E1ynp#VKpZyKpk$Oy1<>6vh9@aWj-6' + 'ga1&0_~#ryTwZ`+S(CC749AQfb!W#_b<}Cx5la|^f#^m-)y3)@zqHOjt*kVpDyzk`Zl|47cVm}27OhxyjAIZMH?Y$}P1U@m5J(#p' + 'M2!)Z9nRu6P=cNvR5=DogB&*c<4#1Is3}LZ38}QwE+5oF8|ICGKI)92R8l)*{vvfca&0UOfqp(7cg%b&EOl&STs#h||L~av6AMdU' + 'zzRU2CUX^J*6?jKz#|+TzK=sk!T64h;jjtikXNG}b{cJ|fj*)n+(Q%Xc(&WbJI#-qF^xfoJpWCBmi>PJg2SeHdcZf(Bg%0f1ao*2' + 'wV+a*EOBSpZ+wUBdP|8*AewUL4t&b7)vX2vTn=1b3_^EF@jF~l0Gz`XpmV3toTD$yR&{%77M8h_+D73GQVX6;q2svJ8UqtvdETHSte-s*?x275<2c8QvK{D>V@Ydd-#*v#ex^bFnc_0vZTZ{W}zUEx}Q' + ';l^gSQrgCYHdo3Mbuv@VS2egXK$u-MVt}Lwg6(f&`F77#6vU$sVZ;X@kDemhhKjXuX?`L>-|9rbBN=cQ*bZHIh#t3egUiKRDqW4m' + 'E}^4VO@NH4>Sn|wA*DqrnhnKV#?bkiy_hDc_Nq_m#0^~ivJ9YJtSeBC@pg&mN|@B4&mr!R8#g!P{n`<-7d+(MlMi7Rhl;7V29ly0' + 'iZ$~~hah{z(V-;fOq*q*Vm<=ZvQ9Fw4g9ITx7X3XoO^v=bZxi>z$z(cvo(2~5)4(NdwYqBW0qKrH1wsGI*oFgX=8k5y2%9XWGju{' + '_6#tzTxA)Msp+JbNUqUPiw5Qbq8Ea~%*0jH0wSE>U;KL2BV1G0xwV%|VyS>*WdU4hpQ4^PM9F=@?M(n=W6rjp(R#MlQCYYQl)X{;' + 'JioAd<6@Oh6JH!NN~jocu))twtsd#+_Mwr022o{B6}~Dto+&QiVLrgQ`;XVVr+8Xdzbqj!GdK>QP}9S(bCfhero9;mAwy9J' + '4%D)IqxAhu^wFNN|at}2b=soP{ldV3jF2mM%Nvx;koGwns+B9!m;1}IsJAfp^6<4iNQv37GEF7KqbSqddX24A<5#30@?Q)6XwEdI&qP+P@owSPzlN$QKPY99@LXmk3gzY+7T%^r4uKiol&L-@g?k?QW}(Q#a>z!)YP~t?Q4YMn%H)tjq8cfN)FMFj?gsD#WXI?nLu51Jqci^xhHLW' + 'H0>pgaQGNw-G-@d#VGl1?lz`S0Sup|b{$O^cTDW{7JFm#IR>b`$4&NX3w2+)<;io117VNE7Tcei-oKl~J$ACdUWhtq&E4WvFZpfI' + 'xQl+F2lrP`OEhzEk5Ti5=zG)bl7DcYp((<<_z)4FD)rn`eR}RVyaJC0V6nvi7fT&@+lT){^SUEWuCD@feiv+TzFgEL(yt5JZvB_t' + '>&7@;YXUH>)zHZ|4#ClJoT!UZ)M#Q8S7=wb?g5*~@&7QCU4@J%N7xsWa)nNgr&8hU&`CSCp1*>D#M%#%=|?A>~nx(y=>cZDuqcNPKGsB_Zbm^+)GtSy0pQJ$`_3<@Kx2AF$l9+@Ub2y%`8$q5r|#ZD^m1yUbDslM~A(z' + '`c}odE;9Qa8g-xv1)Yh-Fq~+~Wj-~{(8w8THLB>IZ?I*+i0Q=ngo4ugPPVi<2cC=^Rjhl9@U%#X+G&3CB_h-muG{@ee($6-32P9d' + 'xmNl3bleoiZD0hAa44C1y~Tw=E3-WG=3rnH9i=h*FfK9j5dYv?nLS3tZ1I6FX}Ud#5Z`#JDgUtaVrD3s&oC9cv}@1oF3VXxkLlQ8' + '+~Hm?AcR?og12TUw$D6TETyWG9vC`J6HHG()1`bt%;mtGhk-mB^-Z%j*sMlHGn+!Dl@Vw#H?IkPDBv4;0*l$`wd3zaOcV-N3k}+1~f_o26$f%hz!Wl)v|)^@nRDxs7DfK6>{i(' + ')O|Q`^F~lTiCQ@mBy3wvMWO+H{ViZJbTnm88E9I>GTp?;binL@FRY3oDHhTk)POg+ydp}K%?A}Q+qDEv1XU|(1h;)gQ;_l}' + 's5#`-t=7mxq0f`FPx*oG`>WY9l@kThq6LoV*Z8G`wi&Xo?mKZ{uIHvAU`;E16N|;O!7@~i8*L?Hv22oWh27*j+$PxmFwtiG_$a(isdxtkRQDh' + '-A20R;DzkAL$N+%1HF2(OBlQe?e7GAbk=TdcUM7VBm0$g$Kc^p!8MPHZ@8IZuL2#*1TERVt9}' + 'T)SYc7htZ^(_oIKS|Cq%*$$h9T4qxnQ=}c_zlWsKyp3iMyrtJ;0@e' + 'de(3qa^AEgL@@89VtB8TQP-H^XZOxmE$?@65dGW^IN1B$_@1u$P1|k>HDwhAxrpo(W~Ox5C0LxOl%e=NCim)`mi}}a$JaXI;<1XZ' + '78>vewCLE*KB+xw?nrni?i$_kF33BU|KjwI=JcLntK)2Q&#vyljLebnwxb7?vP;;A^NbIH3OiR)i4G1mMvlngiyynYuuxr&Dd`F2' + 'bt}ica3gc4!ZbDU&fyOfBN;?SZRhV!E4-LCNStWzMjO5;HjvpQ' + 'O&g{57Sckr@pU&k6Gw;sX4sI_!lt+u^WRnmJi`q9WxGn85$2*ks-U{2bV}t>@~%ur97}1MzF`l89yR%RT^j!$S*<}^Uch_ojYDe|' + 'I<>g)_-!c!3jbM#D>gCNnpz(78Fff>=f^Jxr~Bfsj=|3#)4!2Ewv=I&%?mnZll+@3@FRKqsb?$%>a;gr1%DBlfO76ZDP' + 'dedS@Y+y))`v{JrJ7bozIY1-Ibi6`r4*xl@8BmH(fLH}{8ko~AP;lk^wrP{B7~(P?k#Z_rTIX@@^|*`uv)$|wum5?f!A^C5^dGUt' + 'jY2^!r#awPb@WB+NcW6l+G#ULo_aPcm3?opG4-DJopJIF+y!~foYf5iYxCMSeL#bl>>0{4YIjV<4s0haQ`-%)vV^)7mP4{~V-h;=' + 'X-rQJ&bp~YW_~;9GdPw=Q17j~e;58BpyPLgntNjojw&DS{GYGXGjzghjqCTy4sBq&Wt!s_*s!F)-#NW+^|iJc1eYN_R`k%^1oZq6' + '?zm357PkX=(K>8X58`Re$S!9UI;vi_^e;oOFbTQWo+3CFRu9qz~%e7f)a=|&HHb2bhp' + 'htGu^w%gj5#_RBG1!8}dynk=2dc1#+asg-j^wS&8Z;E+IxT_Y~Pso?No{P?G9_L!*YMYc5ULCI&HgpR~$Tgse){oTvrd%-bugLd2' + 'FFr={zKK{YzBsSu^~ErqS(fVc!8m^(*dI;yd^WWS-x_S>F^JZrmtib(q1lah%t@DO#o+m;mkICX~daiRsTtp}69u0NI52!#o%eM5mol<#;8(J392e$=C=`L0Pu8as=@Je{%4qdGOY5(52{' + 'x75_^;#4JXRYpH0+6k*@FnRB`xjEzM>)psXjOrCJW-V!7b~fZ}+$NkZEZ|cRnwUp3ZL*s;`ynY-#MRDdis!cE`^_{ugcNeRy|!H1' + 'CXtY`%9DpJaBD=_{{!hM&rM+8#lW}n>w~h*1;W6VQMvC}x*%HTmP@1qbd^_^`BeeCG{x#n&OF3LpU!3~ItES(;#1&y3-Aw+nP%C1' + 'k>w*od=-(N0j4oAsQqg;BGV-21j5j1zwmWauoF{G^2v%*TbqdY;?oFNUwMO)_vpG9eNZ#-rJz}iMmBwn;M|2>uRySLZ*x?m#PzbmBWcUGoQw7sQbxtkW8!fNC~vGeRLY=k`9P)i;C4#fmbq~Zz^)ugCtpoKMCIo+dmiuOVm%!L1Qjylo{kNv' + '9bM=1E6pn#Mb|VY#ER3ZIjNL9EhSl1Ly=wrzlbN*+Xk68kGf4S?Nie8)>%5YI%GTBIfzn;S`mwhz&PVDO8ua?%~K+z=}S>xmH7O#' + 'QlPA;B+kNBl`pRWG=$^2v^}{l=eMhC`lUuaYFaR&w228_mv4vf6Fr2B|BA)xRPwM`O`x3zUvpYki&e8uO{lDvN;}*Onq#7kV$ck=' + 'eG+HOs;-IjIAfSEEhKO#Srg_k-YCd+ec0`eu+X0PbNxIN)mf>eZsJ=uNI51Z{}LlGVFLk9PwzfH+%00P+z%>nZ{vlYN=' + 'dh>1nnR({Lv)IOcP~1lAl{&Q@Y3(q-zd1Sh>D2))swl7K6x0s8_WJmLn-#Umhk$FOuWeXUXBaqtmlfq;|FE6hiDM' + '$I02@yEEwf=+(i=FUg+{f9VA;>yk-|+hVWBZ(qK28-jj~cTzGCi=0;47Lh!AdvbDke3qRZy*fNSJ9zcR{odRu1?Gwp+|lvb;ZKJr' + 'cE7h;re^fG1MIwh6cGi`OTIM?W3Qw%z39@Wml)$FoC54fFiKh&' + 'LjdoygVSdR&krBC0a~m7l{Ua8x^CM9owD`U1E6RtL8;KM*{I>=+X~M5^jm2Qq}KIpj_3Z4?jtNdQtUs)u`NCl8E(*&PO2z}W+{^e' + 'PO$C*TL!iXddhU#>E3HfntCTO)pT2oFF0OcxPzBxuz`#nM0XCJKTn>$e);y*aqO6<^49uuI3AaI4U9w)6)hsRLdad&$5>ikhDUHw;2w0@3v^gy77)TyuBtEk%Bku?u6|^o9%n8%UQVejPqZ@8o{v95k{-2lV7$C~wScyYvOCFIy#2_Z$t_qmW{rl=YSWLH3`g}t*' + '0$znRjgh`@WSjG^y+OXd@M{+`OCQSng*GSp#*nP2b0ZRwMccn?u2#oWouCn$I~OcV{Oa9)ls*rtzNQ3Jg^sEQ+p3%ui}h;w?H=9N' + '6cVjn@&?(r+sYplJNoMaMCyE+%@$)+KmKy?GHw6PJAxZpEB4A%5s9%Pi5lbni0ZRq)ZOm(^!@1g^zh^i8P{tmI`v#!>G`C*n}q51' + 'AA9)Uv)2bN4^N*Rc3vI4YtT!)yq|nyC_Iem;Y4@}-B2DJnTIW$2~6RWt0lerViD|(t2+u@I=+#kck}@7BI%6Xz|?nsI^*^&Y;2>}' + '8vcCn@-0Yl9o`fBUpH+AQZ{6~UhC5I1*LAU22myG0~`' + 'M>{EuEZa6>%QI;rd8LKYod~U4*o-un6aWGG6veC9%yCG0NBLZxnSP6BRPT>d-)bWbRENneqh8FGE7XLL%D0pubD$cm{O&i|T0EKLhd&uDmuWHiO*Dvg-eqJdD!lxyLhpb4azM`?~u$05d_ow1kUwVF}3Y94Q5?Rs!)9$!q=gPCb' + '>Zyuj&0x*5^-PfYS^mj+5|%E5YX7s>Z;#J9Pk;`+Iy&xH$3sE}0OMN-*>nXwf>r>j_eTU(XJvdP*XHUf#wnHY45w#&*NzROg2m+aMjA~aCqnbeIH7{C#ISPdQ=NlQ7kHH;GHwg_=T)ZS=x21YCJ`O3!raQZ~wC8{L7XTOais|' + '_yC&a8pF4v(;G<W{huk)NO!=Jh&0>Zp-n0kYRz%2pkC}%&Ar=ZkQ1q6GGIs+EnphlyfrLqEuZi#_LgeSxzx3' + '-5dZ8UPpd8&EY82bOi`K`N&7bb)heyr9F&@X~AogXtgZ~3nXP$6h=@tt)clPTt?F(p94DlI`tE_b|Z$b{n|A$N{zRH@)so>K6thw' + 'Abo(jn?$0Vm=%D;xc|k~gy>Ps)GKS#191y)GpLhjoc7H~!GSc!v==hRi~`4t)e9_%!;26B*D7w&J7E~`>O?X3^Ez8sWv6@pN3>H6' + 's|B0@c%Xb1;uPwonc91fuV=e3NFBOh)^CXmM;dt1+*N~Vw+B204?ZQvphK@_JOjc6G>Y+&0{e!^*EIgusek4vL`no~D>n*-ou}-P' + 'O++sAR0kv9JDwW#5o~3>zQjmpUTt^VdkvHH7%gwf3z+$vm#=DpoC;fYtjQp`;viwVTM*7!ra@cSuxc+6hYYF8Zn`F6D3k^NKJwUHszV7M(VU@<3j3PPhhm@u@!6s' + 'nyH(}i;p5LGkaIXeKx8DV#|X#|GO}Bi{w=pnCfV6;hnUoEbhsWfnaW`({nHw->KAV_Vpk34d|?!aXn6JpOuQ@YN`RP7Jtvy@06F@' + 'V3uzz*w)2sRF;idP4AR_^b@}DMA3zqu)A~ahhlpKM&cD)c4askUTSVeW)aYM1hj)i5lsxMArU+iLK^J*unz7pwHeR&vHDjk?F06S`bmHfx+qhouEJ$ZY2bo^83l1DIF' + 'zv>G$*1rP$nMJT4Q&V|^6m51pDoQJ_7iTOENYp0Tm)O$#%(tbt6N9-gMmR$;yAH~f0c~>SIOvKrDXIMIhNaLxlXcA=a8ZXYtLsuhb2AMITMlVaF6ul#NneQnnxX3kw(c0I^LY{MX7kinssezyE*5R5Gl|&i72&;DAh|AOLNhu6bWPiew2TN1M?ISImB!&&`>2Y<_5F;7Q?MDP5iVh#{J}^Sn^#w' + '-K3@xuDYsrKRMt8GN4PMI-|uF(;?lxBQJV^(OlD2xx{p)>cSp!0-I!IQ5V_}LG^z4CfA}Wuh8$nlPB`PxBeC*cCW*5nCKq|(!+Xr' + 'RvHo8)&j~~qn4Z5MGmQ;q?CPlJ<_T_p%v>FAk7%u5gJ5oh0of%ydD_#57JH@D9Cxepxkz%(!s(~FK<|}AGOwd?Hw=UWUNgN0Q00P' + 'X=2^+?$zO#}n*zSf34$v`0ZoFo8YZ_@WU=Q?=5vU7LQcGoG!YR|=I9Ki>=#H1QEx3G>3l|RY^@2kmX>Q)y^TA4)%OY7_' + '2y;pIdUvxF!wMPaWMM9R;~$@M<9jDwhbMR|H+CcU;S!3Y^5RT8|L%Z1xo*)J)d6#H9iNa$$>mLu0E>o+G++;8vAG`2n40?&|NpRbaTVKasW7^+A#0wK4hz' + '8?2Y6P?=)Aj++9|CU=s^@R;+lyuyKfH!_y_jMmT?9%m' + '?IXkNq<>6lS{doI*ne{?6rg}Eu7Wl7wv7)LrgBMF(MEpf?c03<@<``S&icL^(Y{;_OcLj4bC+l*Pk7BK}^4J%hhT`RlBbFjY>?ap7vzkP;`Tl+vchzj*EUr|J?~WMZT!8L^s^05-_o9)v' + 'JQb%0=ah|Aj@im5AKDnEvz)2vT=)z`^%}cXD>T|bbd_e7AF-$pRXjx{caYqvUVl?Hio23f085A+Is$*|$^v_(VU1FtGVFCcQgpE+' + 'q9OxyK2jvW=_4GtVSGm7fKhzD5cvFis?Ff~uFRl-0BQ?8@`tm3VJM{a6Icl0qA%i(fI-N@fXAp~i_0``1M%lbc`lmump%Y)r-J}d' + 'uvX=C16vM~i^exZ7^GQwL$4lTiS%d9&o0I3`N`Y4u$hWOt^N1o^^%fEVreD#1;gahrAidJi;>zzz1n5VrytC=sceN`_J_QaD%g6d' + 'JWuX5oF_fBf&(X7&oPyCwk$^DT}3&c91JhI2yNgn@I@^0W=@!d88STuOoQ}K!$_2-w~H3PkL<^XtMD4J^&V+J_VVzjgJ-{F&tAWJbMWje`}5(+>Cx+B*uK3_d;8+`^FyxW' + 'e5xcRiZ5azX})BGD~IDNl;#@Gmc;MD4(f*Z%LMj%wf}urw>N*Q8Wm~}etfb2eR4TnT-MrB@!}0U(FaIWPv?sxbd7KH0n3x(r|e_j' + '-!S&2JNVO3^KYNJQ^mh`?sdeky4B#^TFG$QOn&(0B2Cr_^Oyi7!RcmXryj^l3@CIe3qem+zljiG6^$Wdan&6MBJ}9u{hmNX{CDqy' + 'im!*aec*=5W+ToEgMtZtyzkyk7LAm^7{ZtK!!}9Z&QY`=!C6xytVx!SRw}1C4RB`%bHu~Wd|b}Ry31u%`s?`;A&R0;C;fey>U;4|' + 'UE%>>=|C#_{SOJCl`2-xaQi)g?VIGgKbv%0tI`@ZWe5bb1C2Obwm' + '!X5sBy-`i|?U!SG_WtE7paV0&IjUD^hX!)Kyv?)&Ca*xc=D(6*2s_U0wpuNN6GJ3JX9GmGyrnLyf)IlPiuZguR$B$Y58)xa`~JIb' + '7r(YsV&(#n@XM|7mhbAJe={`0Y{@K|+pTGo%Cq6yU2(b+aCuebShTi-)Q{e}=I15ZPdM{qNBQG;trFrq{~`atJQ>T|4n-1ShYX``' + '&@V-tMbS6Z8mQL}O{&{pEjm1w+|aI_X!E)X#lm|6PPyp+h$QgnHG_K#F~{biRF_*0=?6L(unG~9Lho7&qJEov_ys2?WH4jFbSZk9' + '+qW(GOH%?tUP1~;QDOx$gKl#?*|1KKJ8>Cw8!})4O5aqAn-a*VH$RG6&OZWE{r0KiPaKl93U%@jxmVxz^z+H^%^|yUEq3q7L3fuB' + 'Jn`T81`B({qbGa2osqj^0M~xf-TC$dkquFpNV?EM0IZ1OA9y^X?)Xg;O8j*&6gom9A|o~BbUu6mSv*QwYgOFUN!F-g?kFi}@?&qP' + 'ckPr1^`vT>IEbaV@ZIAbd+(?juFJLaqG7z-$#+f!?nYg^J=gGq0(GD9f+-co%^v9D_F}bAr_94HsOkDxE25*qbg{fQg9vF9RyZJ+' + 'q6+!#C(jhMVnH5d(f98qDTR*D5UqBsJDWpz^AVDJ6zXqv(Zhc7mVl!Q=1sNAJ}xw#5XIJY52^5hEL5qC3U2T%Pd<||xcsOqw%K{j' + 'k8GH%?LbtO0!dqZYF})D^vZC`I9WTT0CU*$30)9d>Eeyly}hK`8aDPIMdBLoeM`ojF&?76ZoW_NZAJz)({0TcUfKm0z8g-u7nW5&Ab2Lw9mmm#tPR!=g@J!987*{n3deW9' + '^@`PE5&OmHCen6W3l?^RDR0aj+UvZ&o|cy?o=nyR4B5F@?r?MEq0x8nU}IMEI{TLPE9KfUU>' + 'Hh*<}e7Mm(BKvYp;Vy|7dZM2zCk2Glz_U;eA$?~(ob`|Ya`U?w-Q4U{pA!mp()DVx`$LMxBH62Xo!6tXOnu*qX^&!gqrZKv)3&yjCOUkb-' + 'sF^MgKS=g1#xxQU5B$Z6wu<*OEV5>adTo1Y^DB3;@U{CueBfJlTTwnmwQ*XkvM;=-8vabb9zD8d8HP8NQ;`G+dDN4RHT%0ZY' + '{6+R|bCvAvHYZZT=F31T%Yb@hnTfPWo2aa?K0UP^ol^e9zfjF_{W@4LLca{^+4|*GUR~x_NCBw^x&t#?BHi>AEYhQ2dd_XBU)BGmm@+P*yP4k=JId)%%26N;R{V<+W6IxvC{6A1j0|XQR000O8001EXS>FfLF$VwuKN$c38~^|SW?^%5aBOXJFJ*0F' + 'Wn^S&ZfA2Zba`-Pb1ras%~@@4+cpsX?q6}$KEx^%7Z|X8@Zh0c*I@;^t!T5sfMLjtMaM*CQYERhu9yG5JCb^}9H+%lGzc1ryf@xG' + '_d+#_qSxhhF4l0%@{)ty7u>)`X~>vmhFjpfYo297-b|t>8jUtu?I2Azr7bm2QxLmCX$wqBWtkO9nn1HwdCu2vWx}r472$K1=j=M?' + '3B0siGplq0f3TuJ1qr<6?@KP%+)E$});TiX(a2uWJd^tbUI~((C!lV*&K0XDm0oWv!YeL~(xcJnX)Sh)RG+w<' + 'UFi}nG`X_IJ;x)r4KFHZyp%<0rz5~0bjx3ogJe6NL7|o9LV^h=0aF%5E^x;xm~KkBp01on)8OBgcPj}J*%XY`?z!UkD{d*{8+3f3' + 'j7^0UHciKd=bIQVe}+GmLb8?dE5^XFMqyZU~+J}1nt*r&Zm#~Z%kx-kl4^$e`rLD5QMTvcg}$WS}qDZR%;$bj%hJ2KzHhb@<&$wXs$)&$$AQdi-n_ji2X' + 'ykwMyEih~uz1p(K`9esWz+^I6R$Jfy@MA-oI^B9&=hONLS7855^D9)iXd`YIqJC~1H|Oq|I!F6U^zgIjRbI0pDG0FeBAk16xY' + 'lV|MML~lJd0#dvpS5*&A5eEB_zVmi!j+IoRtzL95gG+#>4uMtmGct_=51>e;FT`V9EL59oCq+#g&!I$5=G@@!~l$b=4o>n#&12<(Pe?Zq3B`V1X)=soh$M;)5+Jnm4D' + 'Q#@fS?3C?C2S0Mny`I=Gk>l3e-=AHgG=CtmcFnsTlK{+lFFi&h4^*d(N`cq#?@-bFvflcJua(X^%@yA{9j6{f&^lA*_94xaR=gXg' + '&@%noXsZ6K-&9u(COxpNo-xcjK7sL7co;xQe6FnbO$(rI3=0^<-vRNi`M0}T>iNH@xxJ~9Lh~D@vz#Mv)rPvf-C~KiTVZgEtVCCY' + 'nc(u8t>4i+fSmx%``Qf;HJpmdP%wHL{drql6E_%m#WVlMbHv`#H*R1E(B+m<$pOiE' + '=ErWDc2rg(l#yW9xZme~Nc-AyhlvUTq5H@Z%OjN&1Q-4BECWTxx$C@26f^Ec$vXiE!VCHtIph$<&@$VA*btzs}ugP#^o{<5)vnbd{(M20+W9^D-#+`Jc&$Piso' + 'jWj~7TWbbIL>tW=+_bnj6*gkr2yHAqV6Cv8JM1+HhrC*_Tzul4;@!$GvfQU(tMxBCCH=XQ7(!bo5mpR_qFn|%zobRr3pxT~JP4RO}c1k-2tYqws|P&MI!ns`Wcr7YSTAS@=N4khqKcJAW%K4{KvBhWmRNUbgAG%ZadJ' + '_z!nAEnT;&R@{a7bf9>qBahKC>FW$CMAd+%%V*Uuy&&(1trWGzQ^#{}ZgYN};eu`z!aF1U>%YT@__b%k;Ew(T))Bloide6Y3}aMeGhJP3l0k~9oK#' + 'j-gL-w&(iEma`0M8p;xlV}}2Kfz_&D+KNh64cvKkyB>LfHMVp%8qL$@(}Dl8SzQt7<7RhYMo*_H%kwnF1&_<9>(DS2^u7%rt$N@N' + 'EED{z<$+sKwbkl2xU?Mo2T)4`1QY-O00;m803iTf&1IR3Qvd)UMgag60001HVRLkFY;AKdZEs{{Y;!JjZ*6Ssy=ik>$+a!`UB3dM' + '_oX|YNaZ}(aX+Z0ck4Zu?RLrC@xtL?9mx6~$+1q{+QY8Df$(%PvaKQ8nF!Gx$h1MP(^u49aI0bd%Ysn4Ui4Ltp;q' + '_ka54;*T%>^zwgS{QeIw(ZrAES9R4!)wWsTvR5IvkgZZtIIpTTDa7JZs!c?ThxHh1E+LtU&(_#tN!mDpHvanL$zT4G7K?Vhz`I?%' + 'X}8zya{kx9KAB$KT(&FxI4#DPe%de7{Po4=v+C)S+jcXXPZzV>cl^Ne%V%YFb#-?=UA%ifeB}&ZxgI|J({wq2PiuDBN^cBCqk7?_' + '4$c@=M65kT(Tl6G8{eKj*^Ih+(%#;*vS{_T_y}l@SHC>c+lk)TuJ>y5hvx)eK*Vm`K' + 'zP|tZMt6~`a?yJ)rLEEQ9+YdMN~sH`N0ump&)csr54^tq(AU4c!wX%ti!`6N#qjQxPj2(8KD(^yqLljZRr7TA`Yu2J_QxN-<45kl' + 'oNT#^rcIE^%3xY{(#vWv$+q~|bTHWjAKlX@H)-*j-#`oJH*YSVJ{j$rKZ?B@lVw!CxE$de_+IZYXBA*BaFrxt(r_?otroepmNME+' + 'Aqaf1SUhX`_l74K>9ue+m?vd+J-@qZw--HKO)uYVzvI&T;^NKpdU*HO9MqfX^)&|jW?J4{!VoW~rCrbY$frMi|MKaRS?`zA' + '>voab>-5ufcFSMfw*R`D-nKc08Zo0XWK8=Me_*6|%E7+7nJ;czy4s9$x_pHuAzWUhn@wa!kmqWJ}i<|jxoV{sNyp$M5b>hfvuxWlOU<#2gzeuE5J&v&I#tQkP1)9`3*4*I%D~^}o-4|Ml6AFV4)lSpRMl@>cN<;RrUi`gNVY' + 'YV*az53J%&47bgNUYc#xN-$RM(h#b$?a;)IF5#!wq(Giet~j@c!p2VC22DgWB<^q561ZqxO=&u$Un<_Asl{)Z3R' + '==R6oH&zX{VO`IrnwF%?qLt7P@>}bwfRgY9Y{~tevA(!rh&)q|2A9Vp%W7!7@6~uIYU{BTT|{?E*&sv{O4}}NGRk7S!HTkgS4F9O' + '2kijjN`>)07*;-EL%U~icDeZbBXc(XdjBl_?dRF+%b)SB@uTn0P$ni9<67y~cMC>Js*zle0;Dr%sZ_B#mlMLtaZ}TUa~L$+oGVs>' + '+NMxzN5R57ps{l>n=@DV}leIO(*dPD!=RbY@?ZuzI{rV4o`erMS9C85JWxK)AbGLU4T1kFY-+Wzp`Ht3SW|{tv(BUq`SyNCH-49a;xaTI)oV)<9EgVPL?BdIEn3&C<1G' + 'Z6Iy4_Rgjta|p&EAj+IukmF2l^Y1)KXkP)Q2O8Xh' + '>dslnOe+xpswvfKFC?v}P$IXOi-t7E!x1kbJ&xoOzt*9dY}@1c;_c$odiz&DesiYJzP?O%^S1qT@Oel=-&L2sX5T}qc<<_y#$U5f' + 'Eu}6}b|FWfvdOVxgk9IkC+|`V@iB%mBk3aMpBfrqgNR-}sYsu0Ow7RDhWLlx9idQ%A;*TbzrGJMp{=$K;CCW>T2%g(vx_Z!0Ord@O8VZR}4KJke7' + '|F6rGXSa#VGV3qAZwV$t?Aqv}aqt=~6P!dwn?Z(R2yo^O&(OkuVHvBqvoA9A3Wk=F6u~7?VsxcP=d*@QNSG5m%861Fti~4KwSzr)' + '#C{lsSE8l^=4L{PLe~sVsw85WR0y@H^_+@>M1%(y-#SdX_EH4RTHv4Jt1IBP197tdz-R&^u-~RXmCSh$i' + '!;r3#=&w%}vp4N}`uBEwaW$*M7uG+4Io)N<$i?*f=5AP#esMLuo}Hxv%bU0V0y8Zby4B|^{xkt@P3v8oeE-e->67+$ahrxk;Mr}X' + '`Sf~f@EAZFNlP}WUVsOm7{GdQnedbH?)-0a^l5v&e^~C+@oGw+J^?zEH#f8C_2S~8_aanm|Llr!;paAe^?EVC7|kV(uYY+t`?)bf' + 'EpG4HllcN~cd=e;S}xG5i`V@E!_?7Bi0JvnPY_)&j^)nC1;Q30Sy~R@_cX)bOct-(WL&?W%zEeY_~nE~J{j7W)M;S{((SwR$;(xH' + 'L!W*#NjEo_?}i6d4iT&wkziUTySp-Z)80+4(#_=O*N7358*;MSpQhKZCYY%C>~``~f;yW|(sjkh@I3Tn`uE}YXaS>ICxhoapWQ9!' + 'K`T67z!{_h4>5uQO7q8#Jo4!N;{`k8e=9A4FolpL6G+dYOc-t;p!sLv;GC^3!pU_x)o63T(GN2FZhgLQHZ2*C5we0Y=92tHf;`R!!2E%I!5()4=ih3AvsO<@nKvA%vcx#pe>EaDQq`WfA6lhrQDBu%c{&zr|j=oSlZsN4<@' + '9ecYmg$V<|`LI{TX&CnIerjto3Lw?+ue*D@dV|9;s$I?zb^qHK8u*;S;zw5)MvH9v+F*6H42!~^3Ruj<0HmDqtU@Hs6}Xh-_Ebbub~A#;|G4|0c0_ehzOa627B>gC0Q' + '509Vt2h)f0G2CI?{vvbu`W}{^z!@I4KUt6NKpXJ5+3>|RW)F?QvF;!_9*22+a%Hh6BRw(x*#sg3Hu7vT+RC#f-~+M=?ZY1cvzG+X' + 'JpjH0tjWe-_>Hw4z~wJn8GnwLb3I$&CnE*Ii;Q!+!T0$->@FAcD@^uL@O_33FdUSBHyFPcpgtP4*N@r&%)Os(+1Al8e!??90M}1=' + '`X>VV(^~w*$bVYnM^OR2lLF|^P61XUgLs2LJ+wVWmVS3V1_wO7yqPm@+zj$!@%nal_v-Z!^{#+7Q?vqs@VZ^z45;*vWC{9Ic5oQ;' + '{{cE4GVWD7`{m(Se;PQ}9}36%aX7|9hVLy+plwL&sNgrYxJ2ls5WV4lzWPqOt$wYvIyvrlf7D!inBBrw4uZP=K;1sBZHyurzuh6*' + '`HE{uUjpA+sozKTyX(yutm4CDc=ai4XpTp{8}K|Yxd9*FZ)aC%a5_gv;hA+Gp6e6vT>l&5xc+$j*7smRkE#%+b2M1*d%!MaJ_$AV' + 'J^0f@Sof#&^Caz(ylZUBV`RD>9pYO#S%S+4EQQODMKk#j%YI1+J6>&`C?fZz#5@498K&X(-cK*C' + '(UXTyn0$@jzF9Gn-z~qt`?2`%^_%0efu85fS$=+nX??!h+WJsbl^=tt_g-}iya*F(!$0eI+YJ=b9>Li5BJX=WnmiP_c+9LWmvPq%' + '^b2ryaJ*EhOo5eLTOJH(vz*Sw@BwEZ2hlw*bpT8`WJiD~Y1Xs*yx>nS{>Re6hi5+!(0jD=y#bk*@qG~k@S^k(peXTm4WMW_k#1~l' + 'Xvkr-cB8DkkX9VC;w}H}09Jl1yvi+Lk>}saBSH1y*G?9>4#Zk)cgYp%8RFhi?;{5YI1c>XYn=)qcj+styc8jP4evSx+3)WDh3+kr?K_y)Er#v0V1J;g-!}v05v|Pwvr=aj;HdGkXb%' + 'I)Ra>FO=jyB~hP%lc@W6ss=z+$J3JC-~SM1vinJ&ik$3y`lse6dmGwAQ8px_@OS$kyZqpYQCp9Gx#1*Rbcbb2-e)^6e*eGz^C8(8' + '3Y>h}^&P^(3Ssca)kn@2v-O7e1h{fK&l!ucd=$?aAMx1g&LO@-cOJg*%Wk?HmoV^N-IxdDr4QOcG_prAcT62$?oix3$jV!Of8Coi_GXjIw_N4g*XPHvcSqk&P8(R|5w6z-piAd%@ob%R' + 'cQ0FD-rA_0m;HS9VC8Pygqc5t=C$sg)L9#$7OhRMR^v4E;_iCghjkj?Ao97!La>`%H!hm&g5#Cr9Kzz&=N_Q(#zgmWP6=lN(L$06inPZ%Ncy)u0CJ|!PVnKIuTty+)d+3Xq*Tg#VKtE-jlwVf|bdE8;2bUyc9' + 'BKeRm!q9s?J{qs_s?E^3+joN_v;`5' + 'vzPJz;%YT#!&y_8pME0#L=o`rW{|PaGIwC%XXEnH+sXU}9ANeG^WRQ@&S#hHCyL$K?av9EJ{4|>Z5^@x%{4%f6-+w3uInqpkGiVM5kYdHw#exxsTDX8DthZLRTcc-b*$-q(s3;JGY*)3#a9c4GtnU%q<=V^3qVTY??*Yru4B*2nn9' + 'nyhWO%y8~*=2UVg{n' + 'zW1hJO^DYzedyZe4E@av7V_?xMTliVob|vg_W+ss' + 'C)oVfw!>%@cefMw)!8caPL@|$ESrbmY;BzR#q5kt;YDdIsjj|#pRWxhfB4w+EeO~3v?HdSU4oyCV>`~ks^KAX' + 'alNqg+u{AD^Ly|7Y_Rp|(EjCM>6>+@q58ovVOthDvi4;Wql$l*C5Ps_&@Kl4xx=KEqc_~q@fac8yC$>xJ!|^^J9ELql<%>sN8=EF' + 'uF?wQ1l-dr7waFd?C&JbsO}wfy#Jt&KCii_fs6Y?yl(IFP{z|bu$MpnWqj)P9X4a#uX|U%sPS@`3w*P^THa{0JdnIegt=H9a^r8W' + 'XN!v=1{8+2;qI3~?p?e>kJ|NM7hIb_P)utU^?dy0SZ52p7|u$pYcEG{TX2=NmX$Xl(n=+N+Af^6He*eT%W1!eJgrJ(I|W+73%-<6S`NEd20v_3N$s' + 'fP!r$S9IIiDy&GMrpA(~QKIRpwJ5g7DwbcpS8H?hqH*M2O&4c)4fz}6sz{#qM|EDYpo?p{U?_!@HdwD^$tf!v$I6<6N$eVn5|n8U' + 'aA$k=vUrt?O+{EOgo`m6A7m6PAFN!dsk;z8-k>@B-Pk7c`1pay!x9F?FlUr_(A%6ukJ;*)F~vS=)4jE}*{WsJKG&9_#+xile{bdE' + '?0R)wda*T&;(QdrMod>x$y9bb1*uc1&Z=m~hy4WZl!v<6>j$_Tjf+;5qOH|lDc6PX)r)9-YpMsBVi1guPyV_w^VMvxDo>9lws05W' + 'hl@Y|$%=2DZhv+&^#zk5jOe9?>UAx!Y+*7ipgFA~3!!JxZS7lMKRo?KVZvEE-B&n=sK#TuQsX@>PS_ko@kZ9F4@|!@h4rAlDJFEC' + 'y)`*F2pw+~EJ6l6JEPw;f}Y@Zc#EjAYF)EnaESkq>F<2q7L05#a`e=))_G?=YwLng0RsX5Zl7lQ*YWgxhCq0^bwk)K^epFh$+tH(' + 'g|{jTZkxyX&HE*V>*aaRX*vp^Vb+($&BqOm=Z`&Vb5EGT3ka2B@S;SFW~rj35Ih2=>~N2eT((ZO&=Fzrml#Ec@311g%u$8)Jz?;A' + '_#Ox%>Kr}HvzcIE(?a-$K&UjPA0Z?bk&5w2B$=gxv*nz55y^76q?K|X' + ';A;C%fw<_#J;-eU(5eeUzJd*;XjD}QAxbM9q)Bl6Xp)dB))I@UP5EM3Dm*&pEkeOT$Yq1Oa3DwK!-X1(ef4@*MSES*v&MlZYeC4^=UMO|5=' + 'P1(ViQi?M7Rs!sQB*o>KUaH1w`4S-oupa6B{)q*fnIA+IRdeTKV' + '#24oTFAY*9q@1dNP=vVIj9IjCAO4PKS`Gs6RjbpxtIvtR?8rR`E&_s98(0BEEj%>I5+s6EZc(*{kc41R;FSotVEIs|&MQO}-e;g7' + 'i}OJU9$MUm!9oM}qIAF$3I7KGgBYS&gZ64M(o{%e850~c(yTqmVKoSLDI}x%0Srd7yFfVl*aRIL(xHhWFrh(%4QL7%!Ur1h!KI@?' + '7}yP=lb}vi_EiIdwDm@S(sgN}P7&w7SJ{1W2#^5`BXz9;7?8!=dn+PzuuF*Z13gU1my%TTGTQ{rt9$5He*s}FXljq6FvN!K' + 'P8i!%!w?6kO(#-UqDWn`wi?6Vv?wQ`uvJYM3A+Nur|O&oKAM(nDZEQNR_`Ed0riL0K6D4+3P3RnW(cvY3Q)ELuvEk$%sd80LFRUF' + 'elRt-iFQ3$+sTNnb6-&S5DMS3kdKDp_n)?<>4GdkUywRplidSEuyRBZZ>te4+W@4|yx`Ur5GvPyGinabXmSL=s8$8s3L}>U6kv@aD+oSBy%;?2EeG?gx$zQU-g;LI^kVXxg#)AaDq(jV5)WsqJ|_^nhd>Mj(HhPI4Vrbz' + '-PWoMB2=Y|XHn%qy-C0UkOhLZfvya9Il)F?A_sxk?E_*BVgu(*wF>Q2XihbZXWTraI*a3hIE3yphD;#vWEG26#?&5Egc^oj1#pROOFATm5>VH`&LH(WFMn;dwwSf|(Q#?JIT2fK' + '(nV8}k+MbY3`Bctnzx0T^aY5url~y+#3FEs%QZA65#W?l9)D7R48!YobK?EX*_{T&uo#CqtO%K1BQyjrs`gF*Ev0k_#Vu7}emi6V' + 'BEWGoh9rU>csN~=5Y<3(VS07&kpQY7gwIe^wrL1$hHW8=>ENtiwFJ}tuQ((t#yG5LC#r&@CIQZNlU6sC7bMc>7S0!iYcd_g0M#0%&V(SIJ_jCcj`YpG68c1{J(MSn=1{$76X0CI|?z{' + '3~R!Rj)uV)dx1nZ2yZ?Bp&LOMgE2(Nr!P0D|I3pMlOMnN5R2WRvAIe^nPqP|EnGRrT2i)B7^7#vy~&T*DL6C;7{@~L-=9(jCDuz7r>1H)pfAsl#GB{U?^|!s93sjfSw|=' + 'TCKJY4s|B*KY2Fm!yoi6y8@xMo*khFlf`7%4gz{=N+J9SC5c0Z!7uZYRNhcY*H02e8y|hxntHS~ZK5?PC1G=c#~>7VJl4T=fG#!J' + '09;Yd1N49;-0IT20$7IzXc-kSyva_zAM$}wv-r}hWde>J~z0vTP9CWiu#%xUH_QEQi@azCo~P@`jal-5bP4Q^Re{8-lp+ICZP0haY9BBsJfwXZd`vo*YcKsS5QK^GTg&mP)U+6;iqrF8@DS)u8qF|ZAIfq^Iq!GJIds{b!6@gPgw80W`x' + '#0SsX+A^svWFufFP<|nD5kF;8eD4c1T|oTxsms>0bWj4Z!FJ)km;k&wRKRAb21pEu3~|%>&&CfAH?fuAfk-YyOBur21j)Vm' + 'P{4+Y&$0^DA>%Zpyo76rED+mea21TkSJ6u_n_VU#!JZwH_4Q)+2;$alfWP?G$+AcHxMI#iAzGKN3y7LQNXQY?8LkdkYSk2LsXvVlRR9@VAs1W9S@^V9iM;5($+<1k=&6ynX`sc4ZHW$*|fCzZ&pVME@y>B-~SWrbezDYOXDq8^ES@Vvmwj>%zgE+zbswY' + '>U?+_r{UfdZq3rp_v&!D`6Rc?+0V-d#AqK*%jO~DVDn@}S`|y=A!HtHxLz!4UW^6WHxg@jt2Y>-b~)h~yhh}imfud$rB!{V<++)&' + 't*ew-8*n`0FqBY(v7iC=md`(*{23lR`0Y?@Z4gLHUtNwfe*B)-4COlSc@59FU9Kt*J|c@&p?DLh9|$DlitU4jXa=09NF$xiMw#Me' + '0!zw51a6rfEs$zg-5BT+X^Fr^dr&=yV^B~Jfx' + 'ArpPKbQsTXJ|A383$E$!-eR@fO~-85&Jl>NIiQYXV@84GgKTzi*LIR%h{?p3AcGsVUF;AH0Z<39Q$|4jg$Tk8&T(Ms1tfY6y*UG{' + 'v<)K%1624ro>5}LFi2Ygpdj=G5T2{H&>=-MfzN~V{-RUQEyUEPYXc50jG$OSC!#kYwIUPnA8_DPGD?J_' + '37PfUfrLd!{s!DM2(1ABrbQ~CcRbCZJpQo@He&4psBZkorypk@tJtw{z9h|4X(LNwpO4uN>' + '0jqQ&!a#k})z}&_`M+*L=JE+_?Coc#WA6S&YJ+I1QA4gpABIK2o;Ym1n1X={PJ#t-C!|<~I3l^-Zo%_q>M50`4{|ZLb0alH11qtZ' + '2#Y8L(U5#@oSX*XS44`#IiG}xqp}pes8&3gKiAnKUhfML`$ELN5V0>r>=O_%w<~V|BG5-XNoN7nZJnFg8SdO&Db%VZ4(Ae&XJO47' + 'QF7@@%C4*gNQt6slD#`25!%2?l20z~%^Pq#SIM-fG601Q6QyETRk2V&a88RDLhuT$gw}G2NdgCbm3Rm3V{C!hReSRWcD9yv4d9<4' + 'Wh)f;k8}vsu5n)}SFj-0@jKvb3c7@${6RHAk}O%9i`0Ab26nb)m5tVd3)rYlEVj6&y>5=_p@!rlwB#yt2`t3ClT8`#TlPV%mjDMQ7${H=VtRm|#)SW?kU+M)bF(g}k&%*W8daia@>&&&Z' + 'jxn~*;3d*Y#2v28v}(*shbQ-JGc_PaM#8le50$sFD--AOXpw6a67sBr);7o75K$7W-uPrw6SXT*L2hO3XqKl{5T0Z#72^$44C%CJ' + '(YfjHIxSjs7eUXVN=kHaDLAq5ASlW^NETM8$hNA62{BHN|X?1eRcWPTbpIMyEA4v<`=aOYSFmYNu8%oh0KX?+vkO=R7@(`EE0;iVbxgWcCk4bDEM~b!r?f&KEz%Z9' + 'PMb|FVAl#zTmm?122?zXHF9q?17|_T@DOmPtb|h$!6GH-Rs|Ja1y9dprh8k&B~AH>pd`Hq+ktgKBJBs7Kia<#AAovFFEpg`9!9SveCA^-p{1ttQTfk*bduczg&0~(>MqePdD<4LK;3Xr+w?g0@%n`+>2' + 'DIXY}oRqUpYs?j%XJc>}5Dod%5xQX%2&~{p2oA=UhR=kSfr_{X=?&-oS`0HYDK35t~s=~W0-k_K4fYOo0yqgOQ2rVFl<(4h)sY&d6gpI4NQyXL%gs_WIU5qe?TrlmC)A=uVhij6jcRz' + '?ke-3gff`6BVBW}#GS5blM3hq#vX{AA+Hrz!CY{SbezE%;h|@JTBexfA-V{kj1IFX5q=PMt?W7!2Z(2h6^7yiqoWZUc$Ukk&Sezkm~)e>5g&D&#KYJKHT$67*uW&Z8`xWtkg$(rrYhAu7nJCFF}Lf10sX-z)=;l6teZ+tH2Mu' + 'QAc|wXp=1>YnU!V2jaQ6tk(<0C#3TxAm~-FkVEIsdCCWUl3Y)fKoPHdvkEg54Ra_>No4rosI@1!ONgZxAp@RlJaz3T(gmz@XF6({rnhgV*ROJYzU5oH*DPZ8Qdwvt1&}r}u+&`F#q@?3M}Je1' + 'EXRHtM~~3dS*R|95iMf+JoPj;MT3e?nABMy)9g6)xqZwNTm(ukMysJn1oQ)}o!f#SSqY>a3K<%LYni%Y@+2M`K`aql@oPvN`Hwi}' + '2{KhH!Z(m}sI1bOmK66++6Jnaml+=seb&1(P$*8w~DjM4}Sm6<=FGLrd83aq9YX^Q4{Q3wHA$hr-3>m3JKY)(L56o8w`)7Ga%~|w}GN2KNAJPDX0b=' + 'Qg~lPhci-}woJzVtBs}iE9LBoq;EfCD1A^PC)w;OXnQO^GXWgjCt}iMb;Vd4APW_%L_3I}0@Fy)M9O&~Rp5EOb~upku94%R#FGe?' + 'OOa7Zt$CJO@NO1_r0%#cU|mRH3fkL<$x5JJ7NpE*sDIfGgwdU4UOdZp)~YdKt#puV(H!PeGqolOfqx}tI@qIwsP7g5DZ-d?gTQo%' + 'blhl&!v-iwiE0SXO{vq$yuhvzg&}eTOSkLhD`=@f@8J2+L>jsc^TTaD2x|qrgOHXk@w~bgw#3uQya>Z`SiN<`!InbN4$=-n-T_-#' + '?nNcj!Q{b-Ge~fETrwyug#jPn?%Qc)UUEbXFt8rySk1ZsFQSLcU^%u@d6X-F;tOC&qgTej>Rlk^Rq@IIW53{q&ijNuixyUsd}rBZ' + '3^QCB9fmZo%+~_4d#FDvPeP0C6#Rl&dSLNNv07r)zBA>~R^D&D|Q#&Cv>p7%e~fW!el9`PYeves#YKs*Nqj6r*m5g`ZUWBsWBoNh9ZZFILxa{P{jLM)4N;Cxn(dvlfZy%WP}?oK0zFS;zo>P' + 'Q6+pSGZh9R7`zFVfuC{LRT;OhHM6rdg|AC*Ag3c_l?N(o!$KYr94j!gCCO_rI`=+hjIxy`^&ol@09^QAD-5fh>qF`nT_D;~z`ZKo' + '6~>J=1Sf&%L!|N^ffz`lU{Z{h5#x;+}1scCmqsl-v8pq1yvH;9X+3LQn4JQ&P97{`T?L&w^a_uMuGB@&7?cwzxR=lS)ds^hYqBsFNBmD!-Yz@0oG' + '$l2r()XK4y!<%Ex83lz0bAq!t#5`z?UZJ2_qE01AXv;@AbXN$Qn3Sf6T1((0iS;>~%7{#${7eK=>kH#{ICS*FackcvRs^U8Qw@_w' + 'eAW%7g^}RRX1*T&`JM;0-H`J0K$qXzG5' + 'P)5wG&LU?7O3XB5lWacZY#tBgd)CG>$c}|T1Wf2;S+x=#2c<_q$wX3aN#%U}-k%0BVyo1}FvBfcH2p6+6h~v<~4stG|o6b2>?#jj2hVbZ4t4(7De?+&Hgha$V' + 'a}5&%i--t#&%6R6LVAY-p*&RAF!Nyc4MLs?SK#?jy*%+=N&wQ7lt5oYmdyTfAP%}PBa?X_rRQI7D1U!-?6E@9|825W9eFH}IGHD&' + '*}a|gG#w5&_StQlY(hSMQqmod0bkC?k-b3s)A{80v+H(_C;m7d*j;e(>X!x6*9YaWYSmtj8Nhcs)0+`7;G!%*kJ' + 'S{}`=3B~gWnU~;56=md!kj5N+8(?&@#Fo3^%B+#H0CXrr1LCz-@i6A>Ud#*73=)hQyefcHLEVcotP_TYFxoZKj(}>>L9L0>8$v@5' + 'kdgRFmWYG<3SO^`Fnfh(i5K|d1TTkipUz+JnY<4)g!88#EQ5-1tw6^Q*P*z=0RvezDccr>' + 'gXF1@x5{_;5mf@%gzEv5oLzy|7ms7-&IM5(kjN=F9!cUYB}!{n`T!CkN#nJa@x3gQYY' + 'J#9f$^{%7t4xzcTxGlo6=ju~!4*bG2Zi(TyI%N(DfLq%rW|wqSP|+u(JRuj2SlW' + 'Pg8pegI}64rMe2%7G&h*J;IWC_?#gyJz}jsZAG*W$r2!k4n2xs81NUz3k#UiA9%_p>ZW*Y4c;de54FjZC{SFNp%>+8E26w1G*viE' + 'v7I~SvMQdRFj2z*@$+FFsOmmksH>UC&8rJQ({t|8WP9R@Xyx_L0d&eB-dH?~i^(A6x;4@)II+ZAM$0kevAUe#3J~cAI;EBqS45>p' + '5Cs{MR^=iggrKgnDXJl!X$UXik%J|GdBfsJ%vp%A=E?&d@a*_&wkM0vD63I^=7B$`x6pkXRii@rZR(k6viGX>k__2nf=YFz}LR&W5?|9U_pbqA&mykQt^yR`{!xU_BATEJ^{H%Ffof3+ImVCl;%qTHTm5IfUS1=2-&_u=qHIZpXGGwrOT;O>wben78-Kp7_V;3E)%?NsMNueT-fP2*tcuZ!seGGI>EU@CE' + 'eEeW0Rkm%Q3jop1%CZ_^D@4Tq)}a_QoKV^#jtio2eV+Vi+F*)3#I-FA>IZxiF}0auc{xR{VmGPy_VPNtdX;W}m+}x9&PLPP$twU$' + 'B?CkpFP^F$G!Y}f<%o)iWbs!4dkFcoRb&QlleEj$xY%Oy5K*8OMu5WF#A*aUW~0=BrH7bR3S3+?U4R>N6A%FBL0)5ROnJ;OBpXVI' + 'F@WeRp+I$8^yoTpqx@Rte-8$}R~B~<2!5WGxdCCge=JTY5^5Z#9$}>`R{Zlpi5_}q%|8hwL5z{$pQ+{3Di`?3VjN;Px?MoYbw*^8' + 'f{Pj|@baLTB~-6Vm6zK?ba~BkFcHGJTls!brN=85C?OoAI>b&d@Fpl(jZYe@{IH2?`P*nKsJ!DjV1#!Jo8o@E;T2xx6wr0q3txEq' + 'mj^IZ9@FMU>#ZagxO<%&_t16!*A2SWcd&?@94jz}8VjP6gg^yPDzQV^H?G8pcqHg9EqQk~tYLm)%{@Ukip0zH5Q@C1T|nK598?7+' + 'CwE#2rIeux8}BF9_SZQOE%s^NgJLYD?C8s8t4`))@$&%z>8-wZ6JJz<2i(we#;xrTEy_uSTAKb-lh;Ev~Q-@462rkkwo2KrF4MmWDeo3+7G)&2;GkXuiA^eUh$66B4ZF*y40L>te_Wy<^dyC' + '8&FPY-H#Wb)m9s!FcAe2HqdQM^pF)KHH%;_XgKg#weYwM>ng|@rbRA+>!L=' + 'v-rneR-kIPunHKUWY!|cvqI43!m;q(z*WHd$!J}r%P{B!7JrCEKwdfFekJC#n-%DP0#=}RumTKj<`Do$Z6*OFA7H!r96%>bXR6yJ$tVj6-ZBf85y=}a*NZ1Gmu3(i%vw|QsX4n-iqct({%1CVm' + '%W{fXG^?K}0aav=i+YwP;tPwtw#X|dedGMTHSObP%ai`=LoYO`cuU}234!Ed=0ZDg)2P6G~Q' + '&1tNl$i!p)h|b>O-8C3q7h)iDsuo~NFFutjyHZtiLlfb-aS=Z9LeqOEwGOg^1Y_u|uLAYgSpQF|DuNRRsz(=A*@DpX`q(fIeg~3>' + 'j(1uJz@ai@v=vr{Kp=12CSZB7)#IkEwDX~-Yf7ZDZ8GPIiO*gZ0Ido>G}t!Otg}u6cpQu0B5=3}+fUxuf*4;40=9S@AfkZKjwTyj' + 'f;7=(8U*;&aP&8_#8`zGEXM(afR|y;Gi$I07lZLSo6fs=ooih?oulqq$>aEjestkLvHq@fO5uH(MC)&Rqg&8tf*FXQ`yO;Fr$$YqZV*nj~9bKE|w(LQBTQ2WvtG^k#mhYyQd`G{Pm}BR^JGnxCR>v>cHqNVR;@}RATs?gIjazxwDqu})?IBnLX5NcQ4K~{hyv*Iz=JLUA*2TISpPW7db_Jwb5+3(' + '2iU#UHg#SqQHG<#!mtXL@*wFfBX|4}?MUP5jeF-TT$m*ShYfrq9$G~F+>@M3pYwFjq?{I?Lz@N5B4h}lf02V)cfg&#TkuB3862s`%_nV-GXEw}~syHS3aFlg>q7Sh6WrbW(WUH8Rx4EFhzyySco!nCJ`9J`U+=Z1nL`y=zn*!bun?}BaZ#F=sd!N-AVH9=vEUumF}X5i7lcbNRKKb=|491lz1D<9V^%}zA|`9FaGRXQ' + '+u8&u;{75mXuL7+DT=+YGA1)UoZ=c8K*oOY$WKnn^Xzi*cePE)S3i0G`2VUR`_UC#*YxzO;hE%bH@CCV$-%Ft*BkaI&&B!V+u;(k' + '@ip&O$5GfS3`RfhdW$gT{5B>i3#~=Rte;)=4fBFuZMXm0JoA7S9`QGc`zFJ$&1JVg*X+r1fc}tQPQ$qeDsAR;LprLd?KfPo@i=)@|Tu_C_' + 'w-8-@gKZa|w3VHCL8+Tya))$Ehsvu$wmFv05h6iL=_J6uSZSlFQ~@JdL*0>2^{TaNXj7FAhqj7!A4J=;*)zBbEb;2>|4pHD72C!I1RgkZsQVAt0rcW8&xvUx?7Ntmrn~ki{2i{i5-_T}q9?_x~X%u4G' + 'YUnQK=Zkc^DU=GtaWy*fFqchZa8a4ttgh?CYkrbL7|XRt&zg22bU->*y?1TZ@~=^!Lv' + 'A$%LWIm>Zj!j#L>2?zFFa)s~-uBeuTy8kwc&GystIMB)E1TZk@yrk6di)$aM)l8~n7g2=r;Ga!RWz' + '65XU(PPl~2g8YMVv7)KvC^p6YNcEq0j`)5uM$K2oIAS}LH@WzmCF`wu*(+#&0Xhpb@vZ3I&)3e75LS!xUgYhjxQ6|2r?i1?T7BF*P*K{UCY' + 'T@H(H%R7UfKDkMY*TbQ#;qz<5&|i)BBt3m{cYDd-zg{eE=D&IVyk4H;HKyZH`C-b>0p!nnn%fy_jhzVM;)q%&r#Cl>O5^nSeWRkGWe635#i(s#$l;' + 'Ac}jVL()BV!TVU#u(tN-U9ZdW*cpCh<6RV*1;wo7PM(uq0>PcM1rv?XoIz>gX_?AQQ!fHvjUW;V0$hmJ!N7P5G1=MxLxQ^}zr?ug' + '_ty)~@-OMMJzdT})N6;%-g8~Slh(qpc!dVQ`3x>1p)z>XTUW<}rBOBm8@Jt6+YOvs2^}LBZFv*l5w1J7y6f7p#wv}xJx@r?Di8|b' + '9U~x2B}`GWnFmf~3y3YObOW!Fyin1A6iQgdVgEh*ZaF&-oY){1c$<0D!6<7%73B_Ah6={h' + 'N`nsNO%ufr_Um~@mAF9&6(C$mH-bG{)7EH4k|X-r9_EDB2}' + 'D4-s54An`V%zb{V926`Fdk@-x5*gB&%Oya31mG_V%XTPPsIMw<>oFyrB}x{)-^;zV6?1cT6JUG%wiX8fij|Gt_T9' + '^462;yr|7|-d&#{Lo{?MB7lg{Rw_bbm2#B3@9W5}Vjy6?hFkNE7uGuNdIi-q*fN+?VeM7cF{pvpRWlvLvqmK=LvlSOc6USs_;#x|' + 'XJ6l&)rVIqY1M~PZO|#)`w9(I!NGP||2u>CX3Im>$6wzT~=Z%ASf39GWTtO%d' + 'w=hCA7tz6KvH)9v>TM2O%R@>4jAhox1%&6oJqQ23cLlTSh9P' + 'Jk&w>CB`dZ1gNqG?_<8i*F%p-Svp_bTwcx3m!f4&QSU8jU#x$;8shN@aG`bj#iLIf{*JJ4vD2~EPu-rk;i20Nd^fxOahdz^^(9o*' + '6mv1wh&rw1a_`GK+uqyMgZ{jv^85ulv!fkGi(}*e;Nt%n5cnvD_t;C^g92H>s#%3o@{)0*yY{S9pDVyv!yk+n!g7}`FbN8LKvbs6' + 'D_x9Ey~fj_;9+leE#S-X%4}h)CrxjbC5@zVl}CqdQ#pGmZsNYy%yU)XtG4n!;0Pzqr{V#b(Bq(hFr)xNRvG{jfFLT~nE?8jf{wB>' + 'zqgp2%F&>ZW7V-mBLyIg_iI6Tq{LgofOnNQbcZ6P(FcN@X%UDC<~*De_QEt)i%0nC)kbDeI3{@}XvvNs+C|WT2Qr{8oyF(+2Y|wR' + 'gSGF}>QFu@cT5RrTJY3VtPEOR#5;oW1J8p#Q44CQkx(nQh$;*GImYXfgTPoRC^z2Jp~^2YXkU=wSBDH*h+iH6#x=|F0o(z<8v^a{' + 'Q`T^NdMMB>JN;gzppYEvw`frj`MI^KwJcmRM-X+vSg%vf>0vzE!*G4$kW*b' + 'wdAT5ykS0C(FF6_yacx_ttDx?szbSmSReq4^SwU(pg_iQH@x-uY`$nW(hBirUpBq88bUaND3+*%Rl#@(C%@)i(HT-0wzCg@o-STb(xl(c{tg<#AzNk?;6ZQl?7FRpz*!p-JEqrTX2tNm#iq6?' + '$C;NC@PErJ-DeYJCUhm*eZHKPH}hwc>)GUoA74ziePT@Wz%Pd3tm{-n->#?@WMqY' + 'd^Rv&;hD1H&F?' + 'M#;o^bXTXVtNKpH7&xZSU%BoN-*7sy`1emvjkV{uMDd`B;<)ekR^KTh_@|R^%JJ)V)qf91FPqi+@f=`b=T-Ig8f49YO~6S2oGs9!' + 'E0!*k5}Yw5+T>!h8zCmok%QG`G)fu?b`yzK-KBH(uA!o4L7lIL7;D&~I*oAXp-wa1*Z{M`64{2IWhM%J4~F<=sw--SY_W%G^u48`' + '|3!8j$K^|=x~C#}gg)5f5YpRZF2e2OI)E3?_HjW>G8XFH1+sd}+B!uk5dB034PCklDT2dTvD|K>dRh1ixI2jwOodRAdPT-rG=K)?jfeJTd$xXj9KGasu>3X?}H4+#tjLe9ObRZPrO)e^j4e}$s_~tRyr$PYwAc%t4m{%+SI9U' + 'R5@fx@+a;=rNx4jVaXyu&NLgchD@OnpIw*mddQ&nK!(k89Jfp~)Qc1`&Q9gml4%5r;wbAxfl=E1kf9{bT&9$!EQ&RKIAjC-AYWt-ryF{|Q!{W+ZRI3cU;~g49{u' + 'V<>fI9SFRLy0+vvn}$fkF>>>1?{W)P(A8^FE4u~hSr$4zO&ai+Ps;wMl`0|XQR000O8001EXmU6cI@Bjb+Pyqk{8vp?y-E^v7@kHJa=K@3Fi' + '{S{$O3hR7;;6VgIaKVE&ktXzHX2W!Mh9o1*e!W@2Yt^e%Y8ZxhydKbGqWUmn<9a~HMz&!gm_^|g%|G7HHl}pGQBW$HkpU`!l1crc' + '3H+k6FBkY!>=v;`YVf?fy!_Ms1`+tN(Ug=HHrHc>x3-Bo_a$65h}snwx}i#OhhUOOcX6nK6O;>;brbGxCn!qjlw4Noe2vDCq-|UY' + 'R?<#2gjm3xov{WHUjUXul@vW3-Me$6F%83z(yY=T=Xv*CDQDon*CG?`=q%k*N;zY#IX{B$TcxiUae{Fizu^h)?$a+&O9KQH00008' + '0000X0HqB5nBlG6bD@jI8)r!BXs5hyl!dG7bQ)N;A}P|ICG;`a~lgehxjo&_Culx0fqtx!6nity02D0A`ANNw}d9LexrO-9SY' + '#*4*d<42Jmq~22|c4d7K^?`(zM-ozJB9%oU(o)w(rVdioR#ZlvWbJHNv^E^5sK~_P4{7Uv{(5HtN$IDmgi**txGRw' + '7mIeSE=_u9Pq^=$u-1#kBBNX+9EO^bgDmpEySDbC@kjS76sun+3+siz2bR2dCtZAUh4hF_!Bh(a*tdVh?pgeNBQn|>B2J>Hq%z2r' + 'ZFaVjDcRV$!)UKWr#gt^I1*XtmUCq+2bd7-Ol(@AI6XA_Q*alt!p-V7Z1<<#2nB2btL$&i@(!&zq)(C+JlQ%3(y}0ERN#&?MJ&z`7V3ytoUxOOhat49tIgXhMc8^v{t-#0sFn9m_Y0JU70lng=Yb7Ba6f;' + '?w&C#A7Np5a##Ov20@HR=qBpKW|^^xGlsh=y__+41@(+^>s7bV&v1RjJ2z~S2~@h4G*c30q0kdp$9X{H!Z7g;hVPX*WpuA{$!Nrg?B' + '730BKL?bq|XAqO@nmmW^%PyG9XzX%0J(XB`ObXJzS~C|;@t+GOg2VsL^MnZJcP=6$)O?7O_gzhvNAsFW}OWts8RpxI&XN{' + '$E4>{@81SlbNikx8jOYe$>9mq!+MmpDYFiZ)@ed02Ws{GIWgd2g!R6B61DJ?+KN)6d(beoYme==OW$_OL$MtUAXibK$DS-t1vvsM' + 'N>n9gs=E~++o%}X{@OLF04h$B-h<ms%Xp39tl1(DZf;G~$;N0@J$y4)MwBaRIYTj|>mj<*xSr9f%)6}pI4sUehS' + '=HqKQcDOB!;C5}?G9J`YFtUQlE7E)bUX|ua8jMRip02m)Z@K!MD4Re;@hA@_ly%;y3<&$CjmaM_' + 'wDSgiiSUFnUT|PACC=!iQZ#(uBYxrHAR|t_a-Hru)+QX$q>W|}UdS;*aG@LL`V@kjK|kltpNUL?!wVxX4eTjkGaIW5dDw6XG6wo(' + 'KIZeWb>-iw?wTW>HsicEb0bRSE(`NTx1OoEGaEOZ6BE(yV!*R^GEiG#FsiGF&7?lUn4L(Z^@F;I!kN&Ioc?0s_5}^v%LdF4^j1Yv' + 'oUm0lzWrVSa`#}r7PWiBJ(ziC>QE0Hez|uyEmn-RAiU=;i-do!uU`AEP2T|xYcE3U;cy8gyz0D&TPCt!p_W(TH@xZb>zN-RhEn2p' + 'Jo*gg#YFVeZauLon)kT9?4Fz0-J1IXTR%h>w#^6)*O8X4uiZb~rS{tO1NYpyt#7}1I4zOKn_SWR>CnD{h>m;aFHz?yj;@yvbVZAE' + 'R_d=Zo7G)3LmO=trnENu(${*_*&X)7dE<6GalyXxcapCHv)yyBSpO<$xcDDXO9KQH000080000X0EOdDgV>{aB^>AWiD`e?Hp@!+_v$%eg(o#8#1LBTTU{mGS`Xh$Z00Fq)2k79gm0M9dSCwJn}-2' + 'k}s*q!zuUT5tmN!=yD6$GHS?(2%4Qs*TB}NHOsYz?FT0APl$9i7|(' + '(KcO{^iA4NPnXruZ*~psIBl4*22Krqz}LFGrdt18UpDgKPSJ*nHLbywxbO#>nb+@$zJ&@+8<`uyzl;+xCk^!)g{7vG#8f0dpbJwLu6GmV&mP0e7~dLYo%TP$q!Ew*3HSH2IjG%1>2C*Wl>Q=uPN}9' + '2@}G$>v;{@H?OZLX;%d!tnQixO+X*gix+2Sr{|Zj-s7Vu-yWZQg$x5}`H5DHwqg&rqsovRAANm%9*_55e06p-(f#z<@jud|%gf`F' + '%WqCk#K5gB' + 'i{LGq9AF7a7j?BPuF~tQ>}V)H4$+XtTm%jYYnvFEmjN2QBO?2M#s3BC`8-$&Bp1Z8kr2e>n4HuV6;jzyF!GfGghDE20?%;}0j;N^' + 'Ng?J)xsIJc!eZK?TnQ{NYeBqRtOA5urC?G@x`w%uj2fPR*Z@ExrwREKY!15DNZ@b2$pQgYw!*AY9S(cgJtAtBR8Wsb44Bb38@dpP' + ';o8X$by0;33@6P)(7$ye$c?IvpCj<1$kb=@cbvto>R>du)efE*qNpMO)@``8uB&oOZWU5*zkq!&XmJhr-O61Fgg$8ACP5F`5EVI2' + 'ii4sXt^>SF!SPdpk}k8nfvXBTl>Yt_6miWr+v' + '5$GUP9dV!Z=>4`t8gm_?gaN~j$ZSR)9vZx|R@SZ2AU-8P5fGAGEK`wNLBX=F073B^$n|y_`jl1EmSc1p%!JkJ=Ly;+)>Qq|OF&nh' + '3`>5vvkJVZq_~4v8!O!EUm)UN4C;xtu3nPB0(C1@bquL!R85TS3SCiQw?(+LtQiw~DC?`D^{U97%A>C*yu*Y}=}>y*dICW~*Mf3E' + 'h;VgIFJ>?Xl' + 'k~Y=1r72}QbQfGSuv!X!r>QqvwFb~+m2D8|SR=)-si8mOrXZZaXF_W|=*)`>g6P!1D!nP%RVq;1jV_Ls-a|2yppSzv*rs_(MPP7K' + 'DBehYVT|5GUq*xc4H6Zx|2kwW*MRUTw@Ss8hNUFS8W2>CZKlg1LG_0nkb(LmnFyji7B#8-{qIK3TR!AXX' + '8}`K%xqyzAg2lBP^F(FX@Y^zila7$HjaN3vKrBV^b;nwP6H(eMGxv6tiJ%JdLSS3q!W27b0T_@4WCX#kvn`o}3tbm2YWK|wRvidD' + 'dzhGT9IHF3=N@w`DHOxpyyK;!Sx|uk52C*B=bC0+>n*F`=)a-of>nkMm@xGQj63YUId;qkn8S*zs%~gnR5^V!L+oQKSb1k>F@&Y>' + 'BB#M~Ttiq1VN7(fTxGoM0PzMEI|TfUDfq!4JHo7j{_WtiJf!xL)V3J0#b;CEY9eM8i{*A7T+&qq%}Yx2eaNG3FsUlDEo}yyjebgYh2YATF*pva!kW%!#EzQ6~Hjd(DtJqNk1yY`SQ%R1BAWzu~(QCtRgrt=~Lh-kHBfyef~_x${f}s2!s0H79?o$z#$)hJT^n}0LIH-X7^AtQk4*+`^VSUh)k6{D=L~bp|)mB' + '9)aK*4al&W;+#bTakw!KY-d2SU_Y~K^`S|iNE2-3ScHDJLVTCU#H-JFOIlsEtC*x1F7XM%Q`l-Q`>qRmW7=#Ds?lz*V0G$J' + ';_2&muUYbhA2w}3@k8*z2O^N01Hr*&gLz6bKpdq%VzCeIQ@mgLlS`EEdA>' + 'W*d{&bQ|{tXr&aPF)@02FzzCQegH#Oj?E%}gUSG4)Ccc$fbH2zOxOX$;2@>raN&dYl7nzxCP3G9(c^e4K1!#JO@QZ)}_gGR=dd!TR^&iz=XvJxnm*DZ!>n9U4o<(dg$z?^2Z@J=Jg-fiZ`Z^)pT%P(iJBvLw%%QPE5n>gnA@u4lA<`n?<' + 'Ovf~^=McIUYZU~4Xiol++(6PgoJ`J9vkl{_N4@>^k7WgH^50c(*(2=IjC09rr1&5-KWdC=ih4CeKR4sig78_wu}AJm&N)Wc86dS_' + 'vtX0i7cEQ^r_*)$-zMlF_j3LBBH)9YnptkkLuk~#*~8?)lnwQv(^Rxf>y2>Kx53%@@x_y)XUFMx-v);S9d}HE$Inime4Cy?!=r<@' + '_fZ_?t=AkeMjKpks7S0JAOY(!*v|ee+#e(83>laqf7xMng6+jsS9c8Z(_bj&' + 'ON&Z7qvll$U!ejze!xIV-P#+jZ1I?|5tnw_6UMYez_iyvcd8oK}NM2O``y+WjQ#;p=wg&9f}_p2P3={iggefL*DM5hV`K}Dm2#VD|F>;wOu=l{hK03>E@cz*Xuk<$g=JlL`X~+Y!yd#G!eN^-AO1*1v)ItGgu)#fzNPIqsTW!' + '!(OlJ472W0))|bE4-pN_*gTlxDVur|8OP|3)Kt7t+vt@sOFY#+eW?=YKLv}K7m!iuc7{9A1ue=j{2YAhqsXkVLDRvfpJ5~ZXVL*{' + '+^MYJ+>?a*R5453FK&y>G6N&Ey92<;*^{*xG0aIVCFG-SD#+jXV^_!n*gXMm7S8-p9' + 'J%-~saVi(`m@HOd3WO98Awr}Y>4Bd%f2JeQ)}csEy!J5>5te;!Z_rN_PVBj%Wu(JlRn2`5oEN-_{UrfI3_Ud`(+pc>&Z&~oY^s9f' + 'R_B}+pN87xF~9%p*Wl2m#LtE25=l3hq2Kyil=HeWsEG' + '9?<*mv;#$1x2DSZ?DMMayTESYd45xLdyf19`<#&Td={1RU-p@{;QJ?n`2BNCRh^3mSnCug{J' + 'V?u)~;O(41o9Xx`5B~C55B}eh=ppnN$xSfcx9nOplACC_^NTkSC6)ziU-pLk6@Y)~#Q1&IVhh!1K41@wUAdXgNC4D1' + 'olHqw{_lc~THB)Pdqw7zef7`uab{WB=1@Z4w3yF0686$-a)wPiJG|GH@7TJzEmL6KEo7SwZeap9OMY$KZ13K$;3UW&IjieePgG2(' + '8PE5c-QAtbjg0f9a{5qTo_09CnDOkD*SpaiI>Q_X>jKi;yD^x%2@X}ga?;{3FnkWA-U5yX-ERWiD~}r^>&({}doCdJv|u)r3dAP+%c0gAcs(uf?(6)kop=jEB' + '{bEMsZj%xFk9w>u&?Ue3=r_2qHC|?7+5wGFybk|-fXr325+HBM6_G^0bPFtT>NWO_u%7O@e)S*k=qreWAqvG({S+ssQ{!L1kMT<2?+)hI<6zPhQo%Is%awt9+eB7ngOQ^d)jg59_Cxsf2=z7Q)l|Pvt5n}q{H4Z>Dh~{Jwf7%T' + 'O9KQH000080000X0000000IC20000004M+e0A^uxbZ~5Kb1!XgWMyn~FJ*IWUvO-7b8u{Rb1z?CX>MtBUtcb8c>@4YO9KQH00008' + '0000X0Jl2cw7@?A0F^)i05t#r0A^uxbZ~5Kb1!XgWMyn~FJ*IWUvO-7b8u{Rb1!XgWMyn=Zf9R*b8TO6Y;|*RY;|)kaCz;0`;*(Y' + 'weauzSFrLoQ_9vdUMJ1uZput;XWe*WJ5KE6_I^7%G*_Z_Sxcl$Qu~PO|NWi^-T*0SZKp}EGp&~*fdk;+;NZM)FdB`%Tc4ek)mii?' + 'pBDSmdb!HG@^n!|M=zc}jF$DRShP{OTrG-aQFX;EI=zOXZTI}i(>586Mtghnrd~!_HeYw^rpPiZTsK{mS5@5sth#FV_QX5>vnWr!' + '7s;~d@>$;H^2;*s&gFC6%BQYwrsvj=q^hF4jjGCho6OhM6r0Z%Sm2T9=(Jue3VPor`RP>l_C@h#T~yOT7J{K(VZY>e@n)G<`B~9$' + 'DeUfak+*HpszJB2a@tMwCoV}{sq2L*x5}HY%oqH%yI$c0h)?&c>q+zw>;969hKjPPSE_DP~+ocA+^c`' + '0YhFbmOz4C2g?em(HeK6@sen{PDWhMol60x2XzRsAVSGPzan)NS13Js5`C`Fk$AQTymZt@EPto3ye8cFCp2MQXNpA~e' + 'D%p8Cn-x{o0^ch#T{{4pS7Oq2k_XBed|9;J%W}z!L^iZnTuqBrhXlS}6wm7JQN6BaM@>^V$lD5hVCu>q{_ynq4=*0SJj#AL`tj$-' + 'KOKFWJ-h$(=tYzoq&0GBYorl2Qq*O>vmHub2iqO(PPnlIXT96<;83%u>6K5p?)ugbh^3;J~{dQzO_(`(=(c^7@RK8;WYsoS!H' + 'U3FH>VW)_Si=w%X+Vi{t`J=sFp4PCXpVk*e)SZ`YM01+}kIhhK$sRrbDtq$y+0p%8zOv{eFAl)(=7&yi8|{*%Xl' + 'fA;j~*-I#P@FhU`o7%i%ShK$tRr<177qG?`b=T7Kc#l4t{2w5_06ec-9wP`wKk<}7hY|l#J%`T1P_9;sa$3T+s<*o6s5--8lEejo' + 'KsxBoS++u0MU{g*Hamj*DGFgk>}?DhocIdGT^rJ5MX' + 'YCewkzlreIF#wtfXiysd#qJ5vC&o+{AQo0aFk%pZV+5P^cQdADbXO0>u&zD0z5tm>7$XDzKEod|(F<-GsQVmN6$p41hxOVd>ox{S3=xVaMCXrd-hQ~QA' + 'SG$6NWxK4agzMUfjvrHP!saCU;e~kkX$`DwSBo4E{ttB{-v{6mh' + 'vwY+&&PT8-(ewZ)fsj_LA`r6k=;6=b-j|?8<^=PQ5NJ}iS$>h13tVOKxVH%c1=FyuRA~XyT9C{9pGfUXP~sX`Q${-|MsQbr9NP=%' + 'J}8+D^5($OnMA+ji}k=VvU)w|ZtM0UP?3Pq%wYtyP|O7(P!rP-qHogZzfdR;Khre&3Iv3i7#V&YkGF63EkSvgyrt^zH0`(R6$`}A' + 'zY3uf$ut8z_Oo6Sb0(rT;syv)um~nP*b+~X4Z=Lp+Q+6xKpeOz)Rd1Vv>_;QiYozG=#9%8g9-cwtU7@XjK+!E69qoVc0oRG%F}gM' + '?8=5ilNngM=V*ngqjig2;gyUdbSTQ6N*5=vR@aLTkU~{sZZ%_ZM@U}I7+(T6I12UG0%R|y?Khy!qgLbWT|i}9RA(S-*3p0p1pLGgI' + 'EOKwEE-Dj4f%;R!^XyYF)y{lsHzfHtywj?JrVS>0)aRn8c+6&J&)wzBuUIIZ?(v$DC_6b4&*uD1Ch4#a5o|i' + 'Gg{%`aEE3^MvHZM1DvM?{2wbs$GAOCqUm|LnCXc^9nAQEsu&NA)Vi5qDQ&E4*n~;7CWwY7fZ`k%1JDo&3!r1Rmt}Vz_b68k@A#%>KBLAR{(f5KZOVo45qC^vF1Tyj1y^~K' + 'FF^%G!B14UEXuRU;VHTd8V)Sm}NWJ(U61H?0)Y(~+&rEU8!Gr`itykAsY_>4As=z7;56gz^LdVh;YWFP^9brRjso-G(ZQGdEtH40CViTN' + 'Xw6a@NSP9?s{vn^#bV~{U9tlAsT4>RRpVj3Zp8Wp_5r55tmZ|7UWfgX+c$}h+S&_;l!Gf8)e(3!Owwwcc$j#XKHNJ2WJ' + 'UbxViY*k+ZpnF*hAB#5SO^Wdk)-268e*yJw?H-(ZDde^zeK`I' + 'qztX=r6ISR7x@h2j%VxTDbPDq(i<6lvZ{%u7@%3nprKH6AVq<2XjTxLB!3``Oogj#N~qd16`4h`q$xinfgG!gx6wXK-C2WTd_6aZwp~kF^v~@De%Bt+LEH*gbVm=AZQ%U>TmtRc6Q^TMC>&v^7;i=$q2Y;Cip`HtU?!c0GW|JxI3Q)-2' + '^K5)-BdAw!*3>JQrNf9;6MVj#7_A{zN?85B!dfSHu~qJ#E;v;l&@2G)AXsn(d>q{#*vHnCTza~S)_3W$7#!0I*&X_2vvQ^oJuz%4' + '?Q8G*=f+@e-+p}w0+Nt53?_klfx^G9o{N2uH~|Q0qF5R+G_=&jqjGkS-jD44-T<4D{OiFXEzO5cn%O-~wrfxXVzf@W1nnXwHGkOh' + 'kAr2O87mA*8NcjefrSRe18@Cm*1{8&tao-5bA1U5f&6I~6srNHcz&`!dkYMWIqo~VioevL' + 'j{mZ^lC|tCk1qa)t1#{>%{15t%8*i-ER};OFzsDgWS4a_Gkl8B7ZM}vg667+66R0Xs&C!875!9q4!NxNXL6s0@*p%<>~E2}v(X8Icailyjjob)' + 'h3-tC+j#$A645{R#$(r9!kq;jeEK?$_qqCUk|dz7olGM0_uZ2*NKoZ6-9H#73iLgpmEMv@<@0s*I>ISFt6EUuqk4vxpe4vXGZd+7' + 'fr6$*e+EoYbab`Cy_wDOpdkowtM%!koJOa`d45sC&M&=|pln})Zc;?Ai-Ka7upa5?WVJs9lAoUM&jFVS@89KYRWp;0uafpWUlqsu' + '2Pe@TP)MWCJ`>E(!Ul)|fQu_0wyb{$=n$_Cxx&$ERt%qqDzpKR4;HAzb7YW#ghvpLB&~;J1?fMd1in->J9_{W>2TjabuDDHnuQ?wiP%Y*%x#T^aF644iACiME(AJ=$V!q}Jm|+G1#5o7QWhFWI8#5_B?~uNMpLa6!6)x5tM@Js>OZl_o31VT|f(' + '){G@#1c1U@7pV7Y0H~n;OfWsvgQ$^=oV;y(HBp0^s2*5sP&w55Kn$(`D%rCPdcS#Vp_k5YVZ-t1B!WJl!?4F+2ZnQ~P{MkkMHBEk' + '`YY_gA$pXc9D=+OTmb1punn`~2~C3TlbnicT`le@VnM&H+h14hn{`qATEvE_?T)>ZiE8%T0T77=AILbkywbF1eK94;QS|WEYt;>b' + '8WZ-ROR2`hDeam!ZMA2|D&m%H=woZ&Z7^V=EomKVc|mB@5AqfmSbFx+$r?tkov?-61d^IW4?Qa3Vk$hIG(gDT0_k;R>FTvFLzB=ALAQS0tG`W5edyNnbT;?1{ug`O0U4azCYK3I6qwxk|L52*rMNBEtsqi>^w(+rT~#KyH$GY{^mr3rHewz^5jDrYtI6B3g63#FfC#gv)>SH#AE@8ML$RFD5$F4;W|29&*x~00FL?U6}Rx}l@imfaHf9$5Ch57f-?JS#R#!uA0Up(>LSBljF6c#=44lJ!VQP7_3>C&1liR@?RdCL' + 'y-*To_1kd=y`Y?05}s;F>|L4s5L-~jyD*zGoU}&tJ=R^Hs8%PDp4-&%t>x0*Bb*=fGrl`9Sou9kX{pso+YdtQ{quV)YRw{0h$mxv' + 'gY%3$v1t2FbT|0|g-6jM=+jYr;JgB&aQB9tEIl5c2+&AuDHexzr)F@mxy#H~88@DKJ*dfF;X%9(R8)8qJ~Axl@fJ$jK=6v@pl86*)bq`ZTejc)14la;0#VcfwQiA?b|&pM357Y*IWZwE(1JH+4Xu^RZq9GAA|;qGmc)p~`YE40{@{@_p-{QCyh4HMKh%4PrBH|6HAh#rqa(ZqY3bTpU-ebO*>ftvs|}Sb$t&z`cA2p-X9^_^R)n-nV!7>d=C2crm$K(h' + 'cmWJAcvsVD8r<)2Z+cx^OG~$JmB#;|Yd_NIn~p6RKeBn7Z2Ok&J+Z360KP9&rSJPIsakvr%W3^3jv383(xb>Hfke**_|_b^VIq2o' + '7W=ZQFgUZU&)27>l^ztcCI{ge<$e~>TTg{Ejp(2$qZV~&e9S{8Y`ZeV-7w&Bf+V#Q6VF1qHe;D-N3xg`|?ky?r5G(u__2R2u|l*JB$5u@Jn4DK$QLhETpg&#$1' + '`Z9J!*!oKru2&7Hn^SZSw-{(kG8aBv;GwfNv4igiHo&lzE9?`-uvP67Hx^jl6cMmOJ|Tuaa1PXbm`JCvhd*=22_i92U?V1XLY!wo' + 'O-2@)?n#r4G2ce4#5d8KkhAY2IMjWo`WxG+?l{(iNVfgnp10d^qX(y93lKqlwb%dsChS6Y9Vw2PpcmZhoxw>DeS2A+E!HqFkd>z*' + 'UqxJ8t#X=&jAD?5g9-;s;jg3}&>n?>Fnf++On@qk>7+&R;FvYfF8RI0{Mxi#?hVV-e3O0p*er~5<8u' + '-kJhUbpl~?Ses&8QB`!8b@Q4xb8^T47ipG?ueft*!8ek0R0$;_dIoi6XAMeJN4(^>$yP);(Ij#kEueIGhmGPIwxEw-Yr8d~o&qgt' + 'dVGpekY{v`MA*iQ{PoXhxZs+Y4HbgmBU{=Q0b(sNp9eb' + 'H!0Zfto7E9+fHR~ZtUph=FvsWPfkFuj{ObA`_Aa4au^9%pM7Rk_TAhC`};v_z`Sxv`=;SsBm&-eFmAOs`A%#WbYRd5#s!?cd3}MS' + '2`|_FB^z3*a=f>r&#jfwXTX;EZn*s(XkkcH{|#_GQP=I@K&{s|ss&g+rwE`_X2*+FraKIV1>`qXIy#NoC_V@6hRoa%=snR4md1H1' + '=H7)W1G?OxC4+QSO~*SUOiUk1jl31TAs9kqGwA0HNnTC}u}R+(b1jVBK&|XtgL;ie7(NUr?5$jP0x|f??Y0h1o65oi^r6pjbZZEC;A5Lm^s7}`*c' + '%`FMOftAD~&xPE#m-#9VI+2OJ1OM4&V#hrwIOo10gR+GcUNaN^!N;?a$>9>x$bfHl7tN8`rnVyZ+8D9@!Q(xt_hzud7hZ)goC*%V' + 'NqZA>23bw&y$Mzze5K|Ow-Y|sNry9f8WSasmZr9l5*zbpvW1zqO$^MjxrbY`Z0{Y$eE01lYUcY27&P!{C;Hv(MCCj%x=>CdW(mu@' + 'TIY)ZXV{YOP5eolSXgt+q@J}*>y!9~g&NRf^YgvZ5(c3sY' + 'oUy_4xV9hP8CsXUyXfDX%s76uCwIpQYU$rw`}S151^Ap@WBN^oYJ-yW7kHBQEV(E!=7>JocyUfKp}=TpqZ?rvR5A#OP>`Kc_9)O7' + 'xRVK@MIP5>M;Yigulv4@6AO*v#7wHsE*LhPTrYZ7ctS~BFf*()pp3&5VbQ8ZuI1dva=&-h>9F}TS<(dz*}PmVVh?1psLx915>R{>kL5Y0(SBLYN8?_EJWASn-r-uX' + 'd6Mg{5s99Z#%ZC>u4+t+gA{*elv<+fjW9%w;2C{Do(Wd;NOAA646UuGhrY72a`-v!|F%hz*h(hK7-_x=(&DU+da6|lw3Cin?$g!IG8VL2m%|f_~tz!V;' + '>hyr*G1gKh|Hiq;VVF=&sN}Uj7axq(9#9YdEsK12W5f;E!nVsA*sAR}-9FOi5!8#UdR7Wt&aVM)i|V3m>WcC+8u2}I^n`^kDv3jh' + 'D@~5$=B-qRKkQO|5=^qRrku3VEK2XkfrL?Q0YYF`PTO=ONO}}H6yxr@^N(dk-PXKGQjZal>$Zx_EUQ;+`FuNi{?pNmhxeZxWj}sD' + 'I+Q7fA3XWt;rH1yc=7qYca#3J0J{nW-pUNE%HO+Ez>{iG5gVz7!6K_tXI' + 'l_5c9NRuxDrzKU8iyKvx8@U!a#*gA$cmd=KnQc-%m}5XZ8H?tWF|!+{>YQ9w<-)~G6T#w7hdwwVugV4${9*>efc&f' + 'M)Lkp*j$7cN_-Z5zHNrfj}V-MAOT7svP@tiRa7F7kXDBMP<1CAhzy#OOc0DJ^a2XULm4|bG%0}dzBGnFMc?v|SiBs6injOz`?$7s' + 'c>6~2t506*ZN1hCWf(#n-Lck`?XvzXv`B~RONJ0%jW^X7+t^i)xyQJ+PV6Pd2Ep5OY}}8k_Ytv$(^*n98Jmfsiwg4sHhRwh!1ld@' + '8H^8$y}`4)ex(e3;Sz_Z|Dfs%L{oi$f0qs-Nxpinlm!0OcLBs<-j}{X|3fvS$EqSq0IDt|H)GsK|wpdynr*BV-EB-' + '%I7kF{&$Z^B5#0@^c3C{Q@c<74RAY*p1$jRH0M(|knP*Gl{T@0DNe5cQjeuQS3}W@HRM!R*r3c6#jj+56NNub_e7N$_LU7mm=cX{0)|UnK(vZQ{#u7>JmgRQwGW>XqsJ' + ';$uH@0=QzS9k8-fcEOXr*nQ!k-Z6?EJ@q9_bEZ@LVoDU5o}2ypD;RRobl3WZS7T)QY?d39;bj^PlbmS8GN`u&hVS>cH;K%35FmUN' + 'KQTnlVNxDg6Eo`uE!LuC>NH*fhrV5suM!TQ5t)5xeQG@UA&E_Sm7yy)kmrz=o<=mW2>AyqPOoTG|z(I' + 'w{RfL_ltB-poG$lVlz1z)#;*ad1o3shc>S`' + 'OKQuapF)Q&ps9wShwOO0p8g@Q9f_iUsxS@hi#4-O{WNpqD7iDE)zT;>6kn#I`3Z95v%gapzX$s!tt`4g_ftEPl_FUKKwYm0|i1%$Du%Z+?99%' + ';tdX<`l~IfsMbr$`WG9Um6K+vhbQ^h&}n13U;shy=q{NhRECM~2<|??1xBz~h&wMYb2i!Q3sX3mLte@2+GJ8HC&NU)xOFSYd@ut^' + 'BN8B#_n7?NpL`GAu~8rQ-m75NwjPmGC2x~?`}_v4hL7=t0S97LZ1FA2kiTSESmM7*uXcww2z5ewk_pqggm#!a9JvjBJ{R-u$srwD4&Ryb$&^mb4sZkEul{L8@=Q4!dL7+xC1j=JJ`smp#CGVgR@2m0NPFtnV' + 'bUpaSP-xr8Ob*Mp!{$+q`5Z;Ki$Ys2T|3cxd18CKq{=TEeCaxY4Ie47X>l#~$iVK1=((kUPwP8Jd}0Z^p-|lGFBnkQqtDGpZ`DG2#|D}6{Gt#^C*Rt@-+dj8' + '0uVqGD@ruszU}SkyPUUJ!f8' + 'hsX@$btK8FYdVm2FiwzljK>?O!(og}4tOIZ3=%{f*!w9lC*9sonM5(G50Q1$)R%6m#!bVtJ0q3+KesfRBCo`q()(<;Ln$RXYwGn1' + '3Y4`th7`CXi-C3Z8Q+YB@|CnL>U_cSa}tT@9+lpeZ`E|5&C!}skSP&uOF3E=#KkyeBkTfXexMufg%?XxMlu{?;}MKYG{=YXkhOR?' + 'K}?2Ah;k?Pa+3!P4%}m7zLK_k_naKYPS?z@PLk@ebunIVANBqiJ6qE4cDDrb_jWjR6{i|`i&4Ubq' + 'M=zeyvsF%+@|*)7eC;W$+5P+yv&f?DAC1RmV?EHh?a>B1hCZ0Lg{wdx' + '4X`I>%Pav~vCV+=BdRUynGiF|+0}$1#rf$C|9O3Zf5&0-FD6ZsHF~`Bgb0i87_CtoK4&sYaPud70W;CRYfK0vN;ixtSd-d~HV<|H' + 'liYa9MfZlnDy_3d`t>khPR$=Tmc9A-1_-NX-Fd$Nj%PXe;Krts8$|V8r0^b&B-l-6Duuz4TdcNVJ=P)xqZw{y+%pKE>Qp{V_C65v' + 'aj0W&?>$(U2+Vrk6fe;!`zihhiHl1ER(v>*EOJ&A7Tc%mt)PyY#05&XRU`qF%}O8pM1p^D{{y6)K5wn@W1_9h5!&@Wply=ttEq2%^Odfnedj#f{V~=y2-t{fLULaf$mpURYgi_~vP`)>c' + 'uCiDy$|-O$L?QQx5F_S?LhaFm%#2Om%&9|Ey%c4^zUk$`6N4nQLw}E`65kNpH9)qJx-M;>gmd$h*%2X~#)H&pTrcua(Sp7ukj(=5' + '0`HQ&2HN5KHc03%BApnC@AcrzJ`}v8t9;r)>*V>++5!hD>p3OEf*0nCq}LRyMLq>G)%KUgBC1K%$0-+_5Vd~%@Z$cF4lE(fo_d>D' + 'cb|Ak%a}(b(whKc5t-ut^5tE1!-ObOt(Vg2AOZ!T;8%Ay*;x?NFc^(2ni8g*LPimg>Tn8}a;}xI*~&obyD(k3kiuU2(204W>2cp}' + 'a{8J>dkPQ%<1x?#45cs$cWjP9k&?JNmEzLWOKAZ_Y^awic+zY%5(ICM`PXPQg>7yo' + 'uK-@=t2Sscb#6Ps;1NtS^|qy-F(z82oxvZ_fF)I3xmy)iU0l-Dc3%C7c_FW1#FS|dCSrp?g`=an%cl7%KP?w!cO3@|t5(v4jhLR>' + 'M6|AS)#xp^{LY}13QZw%;4Y~XlaA)!NmgAn^)MdFQ^iimh9d59o>%Y0*n#=VpxahHsAri8~0Kxi9twZVozsorG-XukR$`qu`B$v' + '!xIOg!cAd{1%rIL|7uu6oKwR025F8m+r!@vF27MGi;(yhbmWNj6Frnt5>Fj<4d!iNzimVFKMD$hxET6&RcEe)vLW!bICy#Q$CMB?' + '8U|89Y$c4rVxJkxL0@P9h$-l6sGI9UaRGhZCeNVIL=c(7)7vV&4gz&B~^)<-UrPn@Lu(!}{%l' + 'AdBS-%nw(z8E*c4@%-Dx9*{h%&sdtL0`DKu2^sr}z9%pPBG5#{S0XtI>1*E9%P5b&Tb~+JfI=Jf8JcM*6XdnHV;h!4&SCCLL+HgD' + '@C3XCl6x&O0nwEIxs@9&MSZR-z(O0ndPU=V^(t!f>z3uwOW@iQnMO#*7ziMGn-aHBwl((STE>Y+B`hwHS3@|Ml>3RRM9Z>WqT#Mb' + 'n$<@FFct{umS|hQcD*SQUlS(?$mW+OA*S+S;@>ZgB$%ovXRRZtrSz}Um_u5r`y$L!v`7<*W#`X?GT4&ELu)1#v8|2nF{7Ea;_*PF' + 'aoj>A@QlY}pP{HintwRJ#BIUq#UZB$PzHyS@Rh&!nQr@5L06j^C_y%FtDH`YRo7Ze=G7~Qu>mC$>Cfk&>Y@a}5!kO@p>hIEk!g?b' + 'e1Njromh3~MU_`GqC8wkWe<9cwkmycIZU=FNvM9vKsbRNS8k&~^H!?Zu_`%<-1DQpJoOUhe9*6j7)GR$x_gT2T|-5Q1Fuaa7qCAM' + 'a)>asYk<<@e#${lbvG1oK^O&GI;@}H)Xg~`VQ0I%0StnZZJb`(Oxm9L<&VJ$' + ')>n4Pid-0KiBElWA#46A?K7FYAEs>-vwYvmR@1lQ;ZM!G3ElK+u)97q8!{}~J&m5GZ+nKY8S#Pu(fEN9dmO?)`t9`5c!|*WTqHKo' + 'SOf}l+^e|j6CFBZcg09O85wIlDra+4K$rQ|XdFB}!G4&Q&gg$@G);-(>u{fGGd89qt0;pZI-B8@eMhbL?a}155H$lDG^W&TG+lEujhSs4H' + '9Onf~`uY={yUI#8C7~D6{1GCh&2VD-tZ*=t*v=VBYgP|S?q6b8hyLSBCymLCUcRzyW#{sfWv!H6slUbrn2oSIsDP&N^pXuR#FbG2(Q@0umx$+$f6zD339tD?3|l!opL7' + '(%s^qvkomFGY}khh5}Os^R~=Lqnfb5*y}}S2rp>4QYZ9Qa|U?G2K=?989b*~WNGVlO=VG^tFtRVNQJi&^^`' + '84Ku#Fp{Wa*E=YtJh7hhAQ;4c=&NV?sI#u|O&Z;`Z~gFTd<&3vrQz^eKS0p}r4j8&A}bK9C7vBq3nrGQKxJWWM=LMxFG*7@fgCuD' + 'AQp1*cx>&NE{pKBKA|rg-849`y>0iJk9NvH$l~Z2' + 'Xhech_)A6D<=4XYM#+(s28u32G*oiV77WjY=@pEJQV^g4r68uKkF!X%%Z{%|(8}W>1QcZyk1!`IMoRN}WMEy`H`RSo$aMo}O};v(' + '=3&?^+kWy$2B_>~NQ>SgpiEnyRd|9JIqs?}m*|khX9{}2WD2M2a)Ghg`o?Bp9P=Uvf-KsE@Rc`!)@9L^Q}W)?=7A=$q9k^HnO_Sz' + 'g_tD<{}V@JQ&>%@U`g__ly+xJ-D>}I_s-w}bvpH+*ZI~-y>Z;<+n-D~nd8RHRVv&^%F!TcO&n8&gMg&$ie(!+d|oQ!$aern58H9Y' + 'S9xkSiU?=XGYQUaq8VDjGU*uX$?x#5<>_Zv~WyNgVaX8-Udes+-=rW6PacE}1TXM&hx>diw=uxjY=L' + 'S>@{&-vP`S5X>4@VV^GSC`l6VMs>*3_YnIwW_a5dD@W);e#d&uA_4C0yNxs!6u)4W8V6vSTg4D4g#%1|sU{uNOuE)ay#VzykY7Zp' + 'Fbc>30jN%Qbw=F3hcoJ1h%w>@;#pYFMT3F}&u$Z)duk%{gNi(48nln{oE&`B&NfCy)RB?CH_7m(hN7FvjcA1K*5`PHqo=m&Z3!r%U_p' + 'VD_vq3sDKwh>wQMzNvPc2UE(v?*brxq<{28)Ep}rT&zGwylEg4EPa`XdXHZ@m-Frsgx3QVu2ioGk+z$8Abf#' + 'h1T0~PrkxHxBD0DagrqdXaeI4rcO03T~cH2$-&k)`fzfZt%rWo^e3Wq??Z5IKXz&q(V4B1qsX$GbEJ5Y^TcF9=x>bZ^L8nNm0rrP' + 'mWAgbbh&HRLcLU$3jiq6*rUPhCH_x=rw*@x>vxi|D~|u=NwVz*78j' + 'Nl$-}scFP-H>0kNe!;B|gIyF{FLVlKyWSFPLrtP1Ga}GWG{1C%DqxNtD}xG6x2Ry+hi+lUdNi@43rbq3mwEHLrTB+pX2z0|{qI#)' + 'w%PeSGmJH_5(D0g*X2q@Wnn-D${p3Ui1RXHfVFcD+e62kY%{TpIab&(!!(1M0tY*IwYCv<1r~1VE^n?g?G5F@Ip^W#Q23k<1FSFkxhPu6JnQ4mt0uu(pR-KVu%Bp;*k=26|qE0SKl;v=4_b$Qz%k|23wrO^&9^W#Df{' + '{REK&;rE_O=g-jNsm_0(K3;(2d&-AZWdY-zU6$yb54J0cj2$SlH)cSj(;t&mQ~wU1P^f9@1GRNmdHbRf468T3z|}D_NH*Pk(u=ww' + 'E{74NgLlMOcf$lEmfn*B$^t+OFLm#S4Gi6#ItQSofEgP#Itkf4*S|@dVi%Rt)!V-6LcJUxhRMaz53v`5(Qnrc3qQyE2Pbxy!mvOL' + 'E-sCiZs$CpbOP3_VJv5Qxud`D{cdnE%`Kdomo1c-(9bYp0;nRx1SA`8N?~`O(Xu7=Zex4*ti)>INhPiw7<6=~MD2`CReL;m3k_BZ' + 'j}qCnk0vMQp$;N&IFEDxWjc5R=HT@kpPgmOW-m@icaaq!^i+#i0' + 'vLERRrOkf#trH->$0bd{8Xx}7C8kKSSq=BVivkztqX#1i17||Fk0|u4^U$@>ExQ==ZR@7%ijGR@92NQ!Ap15W;(r+3T+FbT5n}l2' + 'l|eTcR)0Xs)@Fyn59eMHmjG_uT4z!GLD$^#9arPGt<8X;;M*(Jz7>bt`pa10QV}>Aj_+Kin%3e=a+9' + 'T9ZN}oASZu!i|C<$QaP)1ubR+t~|d)@Ln=6CdL#4`tM2FxaG$ici3%iHX3&F1-cq;VH{$6ly+VJf*x2uhw!p)i_9pz=WlDfxz2&R' + 'obg2t;rJeL>zEz9cB8h&;at+YomkPNIvi{BzVfC%N6q23&hZ5ECbr~*Fgs~$' + ';50rp>`GVS;xZM4XXgVt?9IrrrT85b!FR;mR@i$_nf*p7o?Ss~!;L&)K`afwo&+!D5~dAf0rVOA^_h|$uF-q)q<^-D!UfER^?LNb' + 'TS8%K{R-rM^@PLJf3+d)wvlvU+h~w}I`0io@?Uw}+uUsEG)TI8yh&hAcDjKlP=C9~lR(AcV3ZK`gacCq0j9?DfT}ET=j_TdYdUUDw5LM~k}2GfvWxiA77bi4KcEnD@L_UT@m^j@#A8DRFQMMm&6Zs4+Y%SaG=wP@wrTYq3^!Up6X;rB%m?;3BVc4YJ>V&M5mD@3g<*`vG;hh`7iDo7?;rR^v55pNN{q*QzT?0Xf@81U6#sFPY16p)&ybs7C)dDM?tp$5fbZnkpoRZi' + 'dj@CZXQpU|EiD#`Q(g`d-dODS6&ANddW*nD!Od~&6HI-l^L;Oh7-PbawE23G~x=@i1p' + 'HO)$%A55nmlEC}j*4w6!3pCr4t<8jH0S{fTF2}43RYhzjm_q-L' + '8**P7yD`dR@hK={hFGgNPnnP)WdHt6P1_!>?D0(kW}?*QNjI|9zTY0d$-HYYlfd1Qjy)`JdH7Zyh^@@1?`w%Pk_{VTv2TQ&SnFfl' + 'W69KtjLKYWcJO8<%H3>-&fU|Xo}4an(&k-w9MJu2@M$z^l=9|l#KdgfX1<9I$aNNGGGfFI!^fPWbQfdrD`*2gv)Fwk' + 'cq!YW;BIEN78e>?TkPJ%65|MaTG)C{Qyc5=Ik%?e8FW%fX!8Z}nOIwy)tm7S$OuE1Faulj;HZaY$Zy}asfW$!8bu*<`fj}&nB3vc' + 'olbqk3|jL01k69KZQnH28*aJZ#9~ib5I=^wo-Yo%sjWU#`=eUy|6?qKlYjxR$LROl+Wc@aW^+>Pc8^^DWP|%1*x-h=hEFE9PbRld' + 'Cbxf&$<5lse@FJVPv(hF=85+SlrqcbNTSSeE3m5Jd+QPGnQ0$3*D8!t?pWs0+^H25mZG*uMWWC9ww1ZA7%cQ6CqJLN$z-s$^Zjh8Sc+x>P0!dR!NwR!YSQ;82?(XVw?CTe2IOGK^yj$@tDGjB+nKG' + '7nyxK)qRg2JU@zcTbRBbG_*E7KAC6~r|QWDl;ks*FLaaS3;y25$7&KL8ut?eRo`YS@EIkL{bi+e?DpNTR<9}1S};zI)o-CiwRl_H?`QPwSH5JsoKjp)!alG6>AOB' + 'Ap?)-QpJ^s!3gtFhs3EI*HD@R' + 'h2gzTP>{K80|+mU>w)FrZ4lt;aK}7gn>tum7XUy^B6I6jgeth8`tRjfEL3BC=_4r8l(%*FR6;LYi^2wA6F}^QtN=y#{ufY70|XQR' + '000O8001EX*jqgyAq@Zk3MT*nIRF3vW?^%5aBOXJFKusRWo&aVWpiy`aBOvRaBOvRFKusRWo&6~XJ2J=ZC`L~b#riRb#q^HVP!6G' + 'd97M)Z``;M{=UD0*!ytOWvyHa6et{Q5np2W0yvHvyDiYbkhv0fm$zDJPo(VZx&H5+7gD0MvXc}wkXRDQ8FJ>CS9&^~{;Km_l}E-(' + 'tAyFf+9)9#Td6Xua#b6#tZH$3_2I<}5y7vwvDm`*!X%T)nKbtD{DTpCyDQXImDUKmR$|vI3T@UZ7r*Fg-7Lg9}SGMLRu@cwJujemN;!vDwP{imBn6M?ysvlThGLGElUG?+QQyuFfk{V*;-|rU8PGav^A<&CZ4ykx+p|-U-I6N`@PbuwKc~R' + 'yVeGe(-cb7O6D|0*TDg5wX2M_RlOI5+*ggA!FE7SYgxnS?JXQ+saMHwrYdi5#ja>@yJ~WK3$s<7KIk%6Umg=9m&&TrRJDO=xH=a*' + 'Rf~npHVY(3)A=~1x`PoQuZ^wsqOl6MRe@R@1(5s+FA%#LHlM*x)~beGNvq9LD?X`_TjfNGs_A4d4a&D#E_J45;p7BcM(u)lKA?IN' + 'qK}(}r8TSP_V%(?Abwq{9OmL-@gL8Zd7c`nQU(XClUb&A00^aFGj%eVPN$Q}vaYrwO_z;rYL%u4oT`Q$m1PCk&{b(B6ZZ{8>Ypp)' + 'pDco)_mY$)o~SH`Uz251W=Ipx2oug6_pB-kMc+*#7n$F}mHNE_$SaX(t8@r|jlgR{hmA?IWarXXi>Dl}9Q!y9Q0FF}{|8)BIf4u*6`mURJdj9STrbg48bv2#gvRo~y' + 'YLn)Ai|?ChJo!O5RU=T3mP(?^1e!45QfDR+R|sRonzRnI)~x`IZ$-7$mYzB*1*991tEFrT3kp#5VRWHh}CwV(BVE_Ucdi%adMtsU7uW^rWYq4PWfoC7LNgx;283?x+Hvt8ae>6=^b5`hNERu`t<1p?K!~a1mXa(@)PMD4VsZ3W_t>8IEx;P=' + '2Ty4-3DHP_*Y=wici^vpJ)Zww?mZ+7#4*5vv$^hoKZqgUsGNyBW(5)>Q1^T=OB@Y8BwHoR2=qKZdNmUprFQsC8blre&02v#%Z~I*' + '5&7rD*!&3^FT@ju8Iqrjg5?}R7J-NVK%qNs#duxqjy5AnjHu8%q3#YX%o*e<&O{>$^l8S_FvFnx8$3c}un2U>Tf{v$rq(zZt$13O' + 'yMV%+L2keR3t6v}0W$@J-Eq)M#5L$NSG-xU(8hoqd2J;?5e9wlIOOlw8e@YY4)^Ol?aH8`9O;rQQPn$8k@umwSk*GuprE6LL^fan' + 'dR3BxfgK*22gdROWH;~);A4A+1J1U6XXk;|k!qllrV*xOAUXD!qkfdk0u76`i9e=+&=bW;OC|1xFvVkX0A+sY5B@6!<--`jgcv}e' + 'ts~A2)OKh05id%dkMo;LLKhb^7W0|N;vsd=);CZOskJN|OGiCx4XLHRc$)ejs;*2lN+_QV$kb{Du^Q6QMYCK21s4~Y5XR!@=`&|v' + 'C5F-UJBSgrH()!UQ@d{wLIH!|K>z@_cKQdbh4$)#Cuv' + 'TL@zq2u7j^2&Dj}p2@-}UP4EHIuJhR%_-ECZG3wzG9TH!{Lv{{D3~_%6+HmS$j8*~u${xhu<-2xZ5Rje2Ru#S*YLCeoT7=M9+#s4' + '=CODa7;H;3Ft~Q>bB8ut?KV-L)2ydoaKRjOh}@kl8q6U8I6CtZ+;sF0U0%zHtSv3{Mh*DI3B+lOX4#U#9}IJTyNTl$eBqt6E5;71' + 'G3&L@0;$K*p?eHO(c!`7ho9omQd(vt?GeGe3Ekg|&|4j1;km8#&hSn9oDGhBAeVX6-~Uk$*>SR~?xR1&VOkT<+^)Ml$DLPLDN7YC' + 'XVor(aalj`1OS!|q}W?&HoYY8dCNxcM(GLJXH)K=gr<%ITeL4Ap3GSA=Ni3WNrgV9ju6MWOp`TJ9V`E_UTX<_Uri7A)j4I0$PN-!' + 'Yy$=7nFMaDFR5q&vO{#d?g@)N$4_`#KwwDZbGPuOL!0x1v@<5%*%xPj`SGYCfI6yl=+lPibu@go-}Qs2Jda!g>^~lZhOkbkOoAPO' + 'b8sA=;mZw!7gwONXcm8Rv7=UZ+5nLKdd!1(Bm?~^2&MH?qncvZ}#GA%tzFKxr=2wIyb*>#E3I`-)p^+|l6nAi^JNqmC2`Gj}Y|Ny8Lm&s~D0hFm(M))ErSt9zpB' + 'J1|Kgl8)k>fj)qfTia`S=PPAcE#GRF6p9q0`u-JoP==pKM7$Jg*)KJ)*!N^5r&p?j(DHZ|Ia>^}&R#6WFeve{}J+ewX' + 'c(#wp^Z@I~dJ>j&B#U;|poJ9;9P+M8bv_5^h0^PLZO?q}ABIkytzhmS`opa$%&80qd9`0K_o0vAr?5#?NA1vH3uGa4`oBLgG>-Z3' + '!z>6AE>y5jpk^KVNzP?ykXpPc3iOzy*t7QM%AlExKX;Hi9J?%p-`t4Z@45keX-#}2=1`@~7~^3zp|jmgK1+rowqy+P-D$`@W`2Kz?8a8IlmBS#2E`FoEg;' + 'C7uj64wY;IATHAARr2%jR8X0QgACCUQ)(c)D9{SJ$!xudYw#ACjp%WPJ_O$8RID-mtu_W5L{J+vJ0JS-hw?8)|)h*u=RTgW1#|QAz{1_+BMU!1df&?#SgRrl)-Gw' + '58bp0;yDQ3^%X#F2DNx_$;+bk=^`d(L52djJV+s3Ve`@TO#3Nddj-&OblyYOe0%GX#DEF8Z|{xbJ~DV8' + ')=%8J47Bi;)-$exl#ezYC>hsjyuSm1L$rWM$k#AEF;nNR4d1NR)FsH{l~v!8y9{iQd568aCsW8SeQck)!O^tv8&kqKI1ZcyFJCFD' + 'm?$`6f(T8m`eGyHt{Udk#x9TkGL3uj1;{I`f#%}#L1202Mt2ZdnDfmb8aUd|CuhN?N3$~?r}PZ3c#U}IQ*SE^c_(!tIo|rnWg`Ls' + 'Bau6~?QS`*Y*#sVKAnjujvZz!-ksRgdAWH%s|u5sE*u9K>Gd#vIcJ' + '`t>-Bw@k0wrL#`-kQ`*&pCKlZ{d53dVj|;wsO;(z8RfEr=0U2B=r%}e|058&m;#ks5qhjXJ5(Z3?y`EDj{c2(erVD8!=ahp6OUeZ+wh}?#IRwMi{%9' + '{1;G50|XQR000O8001EXf;=~{O#%P_?gsz>Bme*aW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJE72ZfSI1UoLQYtyNKP(=ZTz&#yTA' + 'lt`fuJXRXgR5C!&b)rQAO_Sv&mtxJagYBS|vHyZ{6dNB4V2Yd@Dx(Pj6o9vFG{5?W+2#aF1BD5' + '+#;5sO3RgG0`SgiW`G+dn8Qs7AwiI9RRE=VWh;%8?pT0HsVu7l%?{Hz1Yft1=7$z4s{}R}yDhogw1Cz3tAx%zB(qN+FW<*-F1JAt' + '@VsxI2H-v)JxP_!`KHo7FGW-2{-8U$_TV#*0(X+Wr9RqLa?EQ$m%)RgNi(M!SQZ^PSe4!8IBcc2~r61sjn*ynT' + 'wOF{Xea=IgRKJ+0YC${wVedT(J1SymX`>kaN!aMIOl`_ye_J' + 'uWV7|XenhvxHtFA^9>-uUrK&UZmb_|1ur~S_A{PqKd?H4%6>~ydO(>RW!sBpFfA&T194ut?!R@Qlt##k>y4C*kM)m>CdN~Ozbm2+B&@v8>g@PzV+E?$opq3r3oFSZTVS(3$Mvi4S3mowp<' + 'r;C=f6y{j4!jw^7h!tzIShJW+p^j^~k?u(RcXN6Ry973O@SU}}9SY&z(XEnn2(aWjc(i~Nsmxlc#^+j)To-dvE-e7ZvO*if=>l{M' + 'u~il@zUD93H}u)bOtqT;b5)qAGV|XoD$aN6MEn~$fU7GWl}@mBul#=93J8vl&5R*4oejmN*SzSIjIQYm((>rIA`5z' + 'W^HO3lV%xKoYWfqr^%ip&$HwFhgxaBQD!oQUDfmn;`5OB&hu-yw-Mh}2OJOh0|nh=V4lwXlQaD+z37Fz(b*wc{Tr`ICkP?!^)ppR' + 'dPRqQ+`%D$+9Zs0-H^m0T*vF?`IG5$HE=}@1#@lm!4~`NQ?uA3(Y(0e#Lw~NuWkGi{R2=-0|XQR000O8001EXK$ujv-wgl&b1(n^' + 'G5`PoW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJo_RW@%@2a$$67Z*E^@b8TjCY-BPnaCxO#ZFAc;68^4Vfv_JUWn{)y95?5TZ%%cT' + 'tLJPdV>#_~JRSrhAqi)`9RWpgefjUV3xFg@k&@iYOlzB5EcWHuH-JBp^E7+R#bT+*=x}sGei7+%HS@_hiimzJNzNtDzwyxT{nY!3' + 'T#A4vl80oKgghr|$;mil0sOFUhU7P%OOYmI2?^3Tiv&voPS!##^){9X' + 'h(Z3aEYg``K+S+YgTLl39brlfjQ^HOWv8!Suh(mzX<_{|U%ZYCp?rOLaXz`8P7Xj|z2r+0afyomXC-n_b@oVD2J!@K22w_BP12mO' + 'Mb6=!N>Sc*E|f?XLn70;TCp#f9F0MZe2?tZbHvD^*qx>MGKr?FBktydj0p_U@NM<|`a}j`E$zsJ8oGj9BJWoI!lJPti5}heQyb!oVA{L4%{bWNa' + 'zt{6T?>tTBVzJ6K6_H@=nCabe9&v__NkWv7=OX5bViB24uRf+xI3hqBJY=azl=Oj9uQ$)rm{2-jsa4J?MSap-0S5q&k;2l1OtBzh' + '65Lr5$xsApSiKqo%6Jr-1?n+F4K3|Bc^s14$Bh5R@*xSBRF*1di3HhWfV3aDiqVx4QMuRWoCB#ycxbe@Qq6%;7RAzMN`b0q54#{>' + 'NtytlB3kK8ODZ~J!2<___j(kJb56&%x07r1IK8;~d^x#-KgI?CX9RZo6RofMPaX&V<=&8Z^N4@1_~8>7X3YCBuna%nOwKQ+EgaZN' + 'rAR)TADw7M*_)C1`3_h2dLf?^%4HmAA!ljAT@^$q`vY?DccNBV#P0y$5EClDKkZ>0f=_QaxH8eGiCH4%T&e@`M1nCe2N(*#0|MkD' + 'nC|oCl|*oy19Y-f3YF%9TVx=+x{Y(bz<_!zOnhVip5%E7QY^TlaT@Z77KDaLx$LZkq(3klu~nS2khfnQog}{QQ;?#wo76$jVMUGKG@TVGm3zC{3`%xF64VN|WIRtjwc05)&6l)*X6$bz88&B4r' + 'X~dHM608$ds)7e-qG7MVvVBlO`Zw4Pii#4B5f!hYx52ChiJpA@ib?tFSCX)pSBM5X3Mmq)u-oa^1srP8>VDUWwLv!^' + 'FV0Bc^M-@ERZWTkc~{)zmkpbHW$j%u=ok{KP*txAZ@kyN8bf' + 'nz2#6!N&mJ9W}PHDRwOZwrgk5=;iQBf5*~6Z$DSey)TJeWtnDL^+UC`!L{F0sl$u>$9v*6n3Zy(%Fq@+6%Sko4cwS2`9V}&LrpFk' + '8PL?WMSahG?v<@TqMuUJ9QD7Uf~TgRGLI;9TJQw_f9GY2Dsm8fnJ9ROcM=FxFRrm37y?SVua9p00bDeLS((fxe3&U-V?(TF@z02Oh*3(hdaH}|6>|s;9l<^Wh>p&|0iRW' + '#^VB`4GpG{BtfKuwMfEr4N!){UEx`0$|X!t2O&-YIATCN-lU%=pt8bQ%8@;!xEhiTGz4d+iSL7Z*eX(IN27NGOQcfB2klfppI9nL' + '8&MiO$abXGG~0SUGjX~rSRNlxcDiSi_PJSFoeWi2ZY{lh}^+VYHvTv8adDEltt' + ')@@;;5uvlHLnFISNxPqbM~CnaWo!Y_2Qi(`' + 'YJ)GyoHIOLA!`vrY+;n?WDW70;sH-vKHdwq8V#1qXWraP(UKR1){B*kjHaO_>|gL4?HIc?9Go#t$%wy47SWRWunLudeYBtCt_MNvFX' + 'qjW)I&XO)X9vu0j_El1aor&0gYt}4S3_1I-8|){CHOg2d-8_#Df3{(!WsuP2@~vT{@uPUQo%E<6EuC6q)2=FW*t()y^kaT-++2`#' + '5x?7Hj`g53-7ga#f%Ra%h2XO`wV0vCnY7%u7SUkZKeSqUEO`{F_K>p_zIAF*u' + '3PZIeO5Pqiv?1R@d2;MfvVAB4BNQXGv;Ql65AfSc__m~cR^ZP~L8RJzr!DodTv|eW_Yo^20xId8RiEtxGfSK+PBow?GXIPVE!uRn^>})Gtg2%nNPO2e%pJJdP~y1sofIZYf7rShwhDAOnQ0MZqXZa' + 'Pjp~TEwRrk?Faus?-iP3;Nrr{62kbZxcm#jy^lGfnC@)AMz$p3xD};oM%_hQglI9%XO;(yZ?#LASr2fpvY&W9#wtr>P#-0x>MeEs' + 'O_^#SCv7P%M^!>{sLV;~W}dSHQ5rJ3qhm&M(dqg4a(qKSOg@gkT;5Jk?QN5D=VZ=beBZwZ7A7n8y^xxI-5ZiO?~Vt<@-boz9^x2P' + 'JcLP|N4nF8U!(}X>pY1oM{P905}O=L;!r?}NB7`U7@gh6Rb`Y65BHGdT_ux{=rasIYWs5}F!`V$s_+G`u(>E+KuZa!uX;_x6`tDC' + 'vNdR96*Kujv!KZfxtQ~RW1-p`t)F%mN{_DwJ$gNv-cCNy^H0|wFa9}o>~E$9pllmT9O0e(?6oH7f>aiLw@yLcaBD|iuB!ThVp&_K' + 'QIewzZ2ke#W*sQWG99u&;l{3OtBOY>@oB5|Y~&YbzqU3uFO~<@`ISNM5~qShIW~^Gvc;8qqvbQErp8FApyY9zx' + 'Z=vR19rk7)ee*rzuunDFHtJ-B;^?k~-Z!{ZhOBA6P0GOBfWefhqFI(UzVrrhf' + 'gWtRsTvqU^vjw2}dmzoZZqd5cOXF^^HE!1$*c;R@#%dPU)~qgdvxWA*Hg~sYc!%+uS;4($wp{sCd--fdNhKt+1(EPBXMR6M)M)?}OzM|_?y`?l2@wXxFi?dD%' + 'pB;tm#*CQ?mZXLMdq_J0*Rx$)7(TTM3_+-2@YE%BTa~pPsOOix&w`<$hf|cGD7fkzXTV6leO}Fk-62mUXgI%oc' + 'oQC5rPTkvCyEEU>7S@|)>R?;9$?@UePPTB}Q&d;T(Kh`d()6?kc^C~F3Av{`-G#8torlL3-o5M3ayC&gzlcCpCy4bgHLCakku$9v' + 'egoWZe$P=f*9ALAud3$L=M;Uj-yN>Rw%&ZR++S?80=2P5f5~B1;%NwFQA8=MzU$RK>9Swym=V-#44uW}<~&XD)DgomX3L+cp$_' + '_pjizhgyX!ENwRgE<7MjTr^lOS(66E41=L1+GayhDoM4uH2-~v7hR}0K{r1*G0%nHxgSPRG(Ua!y5#vY#Du0BNi-`3T9*>(Ek$Ze' + 'B+b!lqV&nzcM8#BMf04A4LUeEPNOIqjn=X(5XS3T*OFq4SW%TyBO*kptui$l`7>S0Z0jZTb!F6e%7M5}&><&EofGL940A(2qe7L^' + 'i>7JX;!q98yg7MAbhb^p-yf-}xlSB(oD)qH)m{-*#oE}8Gk~dy&Q1CMyhq~gs5k5b7dwhgH&QFhzKFv=-9pJnH+o>KfFJ4Ta?=53ALAOyFB`BJhuOHDJAj;_)' + 'VJfmRr&xgpIK|nPFfms2YYpdgij-ISIUOPRh*B1Wvl|Hc3^m+oT~$<$W6QzTz@&SOPSAWLVTz)2!fQI0vXtX4wY=AR' + '=_5folT;z15vPz|s1y`cveXn@1O%4XG>!bF;u~CMF+o^pJ~sa7AQ&$UJlBHtqK}|#DdzJSWRhjH(#is@#fE@a@)ogm@Jkd0+SXBn' + 'TwNEdlB+4Y^(S{}$LhdJ3I!z4qMuI-?}(*MB>%(YP-pxS0-l+|K@Ida^WU*6CYY^Z$K_JWxt@Q=u4p*+g' + 'jeP3`Tnh#HV8ei`vgF1y{WQIeGOkQ6RzzwR-NgfWPOhm0iHLBH_xDV_Wf*EtQxy$Z!M9&%X8RHFx3$WhA-J&tI~%Tt;iJ4!lI<8|BC43Djh4DzJU#YD#IoC19jx$>LX>=uRe!|cjr>TbQK>F7ql(Qb8xVWT~|hm@^z;pV4C_q-jTg4a6!~%Lq$K(dsAaG`^WLxX649TDMNOi' + 'I6&wPvJL&>V!TQaPtg8ef}Z{U$8Yh)1=xlDLEo9)nrT+ijtLlm-X*#D{5nM3Qa!O0lKMN#' + '^>%7t`0_T5&{e3b!fZ>k%c_L_tU9gG)B=d9$XS7YnV}a3K}fO&FDB2HhQ7Zy4TAr8Ck(q`4|UtQjaB7(%`!$fT%A+1DeUh}?A?P-' + '*l0UM+Qs-yKO#GPoQCpoFJo+*G+CB%A~rSIP$!c%+1WIP(4fFGNf5%W6PuuUG!VPE%Y5ARm6cgA#x~8{J_?CqM=A7`WrChQz5Gt(' + '1`c7bv8fwwC)0i~J?oCpoLyHwGVAD*pjQx02XaK|7h4=)Ak^br;|CITag7pcyL2)wm$l7!H!=uGj{Ww^A$M1Nyij8|td1=JXrOsNHzLC+=70' + 'DpA!Mvs?k)uB7R@<~&R-lQ9EeaoA{gY?1*zhjp0FoPJ)4&46kl&D@`4N4Ca08)}RAhRRa?1mpn53Wj?UUj69JCU~|qaDU;oo(q=V' + 'CI(@;wc#{jY9(D5F$-ARh%AEZrcTAOwc2%>`(3Top4@l)1NL?{1DM2C^n)lcTTVS+SWvylJc!)XPIu4fURd3$qOp)jXn=G)h~vPB' + 'IEBtZL~g=0+1nL40^2FJ>vb<(FlU!^+*-Lyt|&P+4*bu8K`UKLx^_&O7R1' + 's2$4KJm|4`nqV`pqTO$*XgT^9P)h>@6aWAK2mk;8App6!>QD<6006^a001oj003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjC' + 'Y-BQDVP;NRr?EEBYMZAUcv_Nyh9&a~jNCc7y#gi@V9-aDZVDwA8YhNTfni@nd%Gw_iQ_%_b!!&KZfoM|V|q' + 'b#=YEn&;=|f6doxS;?Zwv)hdnReqPRL{&Cflb1y;vSKB&^*S%4XsWEJ*JZV>1^kM?efaf#xmiue=jZ2VXX~omiZosCn|&qIROH)T' + 'SvAnT(4fxF)U&3nmiMa1vfOOslHQNA+okUIF57I79B#DAnrykr>RQ&ifqBF&nx|b}-07FU6i-9(L2lOSCE^7lbj}h^!Xx' + '6(wU?Z`U}Ubeq?Pko9ghO|yMdF0;Bxmu1n&$L8$pY$ews&5M9z&5A' + 'O(KAubPMmrKgvRKd;F^;FtiY=b#mrBa}Xz*r22E!r1k#xuF6(9OzPlEw%N;iu9)zL;785RE8%t+fLsm5RWbyG' + 'Aj!)T1h%00$*Z~9eiGMTiKSEAqT?Z-Fs`ja)bWc!#KU?wezU' + '^zYd=-!y=CSCy;%k|h$EFruNml<`Pi*+lFHpCgZ@UMGw^f;~Py;9d%^rp#gSDH%^3I+OU-!i=ti}PO' + '7)^+6GF#pgQWk^jIdGa_+yabSfN={jZd)*jKpzM_3ZZCKz~T!!@zcPl4tvEk!#v$Sh!IeZSC4r0gjY{^^^8}~!$_A$@mh6xz$@zV' + 'Y`Q$fx;z;y&!!7?rY?`MF3$$blOgQUba}8{5_<`POV^1ja6>y=k@@t*QCm4?RM%=8D(wP_x(?rT3+ZnNTdHniUn3a>*v@Q|KZD|k' + 'imNN9v8Dc_p@N^x;7LSePm@-Q$QL(93xtx}DYZT^TlM+S;&r_R4f{H*^DH)Ch(K_jm0%H3K#74' + 'eYQ>O<^48aHO$b*G<(eJn0P-vrMpcAjz{DjSn0V~Z^{g(Ya&OJX`kS~gAp|q&TYmjYjh7jB#1n?Q}-n=0kN*KtrYB?J(R$UN;`pe' + '1u(HghcqkoaDg_b$#Qg2jhn$>Z?gvEE_(#i%L{-!nW!f>U?j@UjlqqSiS@nY1O}|pdpy=HR^7SmxbQ~VKoEo%C@D=NUmK_QB7;h5o' + '7%`vMZeKaQi2+dSi~IO*oiVE+h*)8`);tZC8=#Xk' + '>s&<~fRrPSp}4qU?t`Iz)M^a{JA&NeQKQ5(t&sG#!A?yoP9J&~ZgoKIp10fMw5h=udp;MpWw`+ve4lM<>4Et#XdVC|SG|@BNC~bd4CaHzA7LXXE7bkz$sM!ZZu6v?#~heGrjnqs&8a(3' + 'wE4b54I1*D9=0M{Y@{L)ZqW&obaM+RJ?)1|O}fBV^79_dPK}0gWufCl_Codg^(>A|PHXD|x-lHk%QwJGEOd^Ym@T5@{Z!p64Z+51=%=bRYba&+G??am4VW4?JxH*88FXAznwBiDrT86I@!zTnX5*Z?sEmaK7W|HV72C(>' + 'iHnSyjtiJ4u~>*pOhED@j?*Aa6aTUhS22YDjaekSBkR&pp6N50~up3~71}vB(L*' + 'EuXNX%nFx9EbC6|g(C?*Jw`q}%*+Q6ma%D}1_|1bCzV2~*))lGm{*AhjH%He84Q^O2FY>Y9OmpNm`e!1>1iCC1lgS*1m2?2Pw9Py' + 'w-#3>jP(fE-40z3U^nUTJ-lhp$2)UpUgdL4R04XU9tiz!z{Sy)c*(KmFvWZA;zD' + 'M-Opcx{eEt9>Ev=oOE@zjdG6}&=Da^oJ_E?z-ydte@|%j*y6iq(HvR59$Y9Wz_$%Lyl7mKe#WgXW*M7sMsn$C5(%#PC!O9JG@HN>WXSka0c?B_&5}wj!mJG(`Q#QxY)QkH634yHwpk)#%yy(BzRB)gW#%f-f-$loVJsv_=mY1' + 'Mt>m&AK~Ytcn!n25TBA^tFdXAQ^OH8tPVB&1Q0(S(@>+1HT3Aj#`!UHekAlGksn9r=rElh)qwI-=p?=dje}dA91ar1Z#%UFOYcC>' + 'Is~6=2nQF@^t?(=5~4}nC7s%^C(};+sgF#jm>}aQ6*Sf`m`JVM+_3ZX9E=lCwD)_Q' + 'v11HL4T6!j_ACR!gUzqD64ab$>|W;7<*$bDCPqYe5~-J|5s7g6A1ei|_))E+(;`Ul3n6%T|DUs|%HH-yj{qk&vtfwH&Gfp*xXW3V' + '-xM$+~UPUh%Gr<#F`2eJbGe-MF=9_2G~lnQq*o=tT$N$kO#N{FkmGW=F_9-{#{IwM=rMMoMKaka%zC;jM)!QIT~f4h*1#?' + 'vOu{s@s@+u^GL|+1+tdXo;a3e+2v4Az~w0H7m2wDpP7%TZ--te{Bq`NKAL{t=qvQ_c&!=-Nj^!O)o8rs3!dqyR{NJ+J`64f6H2nB' + 'lw6~ERa2rOHV~eTY|28r;c6jcqRqVH1e^T4mo;h^++T`?I0~Y2Qxt_<-XUjw6ILq}|UG+_fXa({K!55upBR11et69T93QT#bln5Ud8nIw63I27pbO&K_qctHG#}eTgV_' + 'dwDs8p9%g<;b(%k6TF?^?G$gPcsrej!DJ*i7iw%js8IXreqa%aKR>70ElEJDM$`k>8CJ$aOA3b|qJ52H#^1>Vf2Y&ofu`#Dp4c>~H(LyAy=S0nF6EK&hcK3Ckc9nv^~Bcf_SUkCVcsslj^k~mtc+a6~ss=s77Yrhyf;`F5K~<>b@Cov&R9E' + '(M{zo_%E@tY=Sl9-Twu@rOlY=@cHg~-i@Ws+DEfX((*4Y-kJLr~tpJYpq!EdKqv_tv*5-ut#2vRI|;4~YY0pXIvZ=WWUlXt`383oysc}r0#{-{dhpWUvs' + 'Cr=EcAOZ{VAO?}oa6b#uIz+7=Bn|}NG?)j0@o)DVJ79PplPMK!9' + 'lV@3|O%2X5{X(asW`A{aN_2?IR75ay>2||-R0bs|xNz$JsIf1%XDX{=myH`1z?d30Ar(nJwNpl1EP|*`OB_*`<1$Avj4+6y2y$_K' + 'Nh-#aZ+Gh85N5~p=`#bz)>{S?*g^XB4$gD0=wWtpPRU_MSIxuh$n;T2RDUr%|R0H1PlkUNF42uX&{v_aT' + '6GL)j+%J=w3W<2JN=AbF(uIr+^o8pf323a0@!PVCs{R_BB0HCjs-m)pW7RGd_~2-2slM@w(rs>O0^Gi}HPlOO7vk9?lTWnCR-Ee;' + 'EB{DQxAI=%pGJjS_gzbT%0-g+d#yDMo)`2jjO`aBy`1!tZzkHuK?5>}rbD1088DFWM|<6p_?^$gA*-Mv(w-zM)`4F5J^qZ4G2ZXc' + 'n;!U?K0k#)>TS_NVUJ@*d5&QQt-zogH0mp6GTiYfI)g)04+}hcnr_RJN=5yUn?<0Q`qHe|a;(GC|{&;y$<8' + 'IlS^pP2D3<2e%BhdOpeb;|iIhoImx@xLpSv5f3(bQW=E=aEIF|#+!1n@a~*m@79xZ$xv-t^{fGfYOm3UG`(S_6|`G)l|oh' + 'T6F-3;sO0Z1wNhJza4o++mYu{pzCu^E3yBj94VLnmK)`}kdbvtomD~Uj8b2*b5rg@wDCwwa|}Xo^V!YefXixkckX_eA4&FTHa|?X' + 'Ln`HQ-j8?SJ^AF`z>32vOJFaZ6-K' + 'FvW{)6ki_Tpe1OiP{+25RhrG@yGEG$fwALs@((3BMl^a61HpIj(aD<(&+oW0U7>F;BK&8dDMcB' + 'yJni(M|))$p|SynIo{-TbFCrrS>HKkl7J1D-C-N*4-MjgOQV{xjaNt3eYeJ>2?OUTE1m{{zt##3DJ)jr1j1!xhg`TQO=YopGJu6f' + 'SqDV1pAsi' + '*&bVn#>xJ;Co+dNTllpHH4o!%S2gI|mVaJz*lvh~bvJwA1D-M69y{czGFEiZbDQmIr{KalkJ*$EA1(QF0P*9UHurB%capEkE1NFA' + '=~Sm$H=SoXdsZS>HCO`uQ=MK2({ZMgxzt199%V#>!5)`dy`||DhdO=?@07MPl(ebHo2&k?' + 'dOa0|Ep^PjAvoBoB^zXskiYFVrbaRUA;h6}YcX@zorN(z5vwUBLEMu~NuNF@e$%?=9-L|h@v}44wdH7c>C~p12Sp_k|Oh+ZShZbushW*D?k4%LwhH+9j&O`3HZk_0Pt+&YJ7xA6c>!pT0>_j+pPYiMuL~&8F3^k#hma_' + '`sI~^VdebCwl;Ma0jtrmpV877J^h1#>8x_ALdP*6i8?ymE{1~MPC-!Qe3S(e)Ix=OIk5_XsHtI!9T!t&$h8o+w{r|3%$@FR`@-zt' + '(QF3gT3kQn6k-JL2?D?NH&5DU1cM>>4Ob6GO+mksq>@iOSroD55i_TH' + '_3a2)2KVd2k?udX{5RX2fpMU(vfb|U%}Q-J%~t#^jb&L@EBv^XtOx3=o$#RJQ-l0jS?vt?4y-9a`47Z`bC|F8l6M4ZPHg=TD^QhP' + 'z8#8<3bLK=u4(k{Yw&b)`d%&;2H_WC|MBMCFK^yKAX{$v8^MoH&%zmr`a)29%YmVHZaK7KRc?P^yCGjIsyUI5U#<}JVHA4Se{QHh' + 'eedC?6ussh*F8Ycd)M5XdboBOgeie=iSg3%%Fb-F$9%irrmLJeMTw^$tU#owER1T0h{j0^wjbHYdA-K6AWc9b{#B?;2;@Vghl*P5' + 'rTdE2KZ=WEGx3{dx1iOOv=8(*e(NLmz?}Bp3AvL6(c}z=Vwu5ztcKOFyRAmP(s' + '1Iu4GCB{v!|JO0X9${4Ln{=VNN!Qx`a8`WLtEyRtju3!u%a`bP$' + '4A}uHl9T`eWuZu^2AWIW38St#02E3{Z|&+$;h!D3^Py;KK73-F;ud#Q^BaA5%K`q@oj!Hc+Yey^*qmHC1a{_0h0=r8dkT`cZ9mGw' + 'a#yue!L3drIxpF0Qd|nXCa<7q;YBN*hF0l}j&1$WPjw^;bE7ed8nzrs&tHwqgjy#l@0%}9-NEP)GouaFk~1hmF;)!KQhWep3}V#R' + 'bY{kBt+Pmx7$CODgDAk`Rx1=pERzooKz}OTQZ{t$Toi_kU1J0|XQR000O8001EX4LtUtGXwwt>I(n>EC2uiW?^%5aBOXJ' + 'FKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGAeAX=iR_axQRrl~!$w+%^#Y?q4x=q1fP+=|75qgU9z?oi2@!kJ;S5X$6YhIJThsN9xpAK=~^Y4cce#G#wCfh' + 'j`p?c`oS_n+m=iGk`2+FX_6%I*}}bpidKSU)9v{o0sJ67zjMjEp<4jRrDeLsYJk=?>n&@by1>DQI(+r9nEo2DbwLQ3aUrdP!w_Y26||J1M+s8!wF2huCUJvEtOqRrvWPe@5A8>%(E9H1=AR|0g@yu9M;tO*fJ5)v)3_|YVpQG0~r' + 'L{^r@)3ks}4YE1TM>%@=sI^kM;gTXJ6}9y_o6$W)GW>mk>cGbLAb=aNXNLV`cr!3$c98a?KMI9`b*^xx&Q3E1pQyHIOeMriP3hxl' + 'hBwO4S|v5a`nzH-|9{Zc9HStSK>' + 'Qz>lCEi554a)2yS<5?V|mkY9|+(U>Tmc~Ac`-B8mUh{9!JDi15eVw^K;O!>c*>z4Z{8QGdzhpcAXn8u|>EQ_-9;h&EHhiCdbevHx' + ';WJ9cJ^o!G(uo4`LY0MZ-(V*H%yzN' + 's4UdNVA6Z>m@?eW-WtMJ(Y#c{4D+WEXg7ES_s>|~1PS2VA$Rw!yCYol<)m~moh-;0ZPEcVm+=!3<_7!jxbO9)Ei>kv4_' + '3u-}yfF2v(y_Y#8mRl)^2MXjY9quz(|1W*NqOCnPiC5w6zPm#AZu6bSES}cnF4v)l_U_$+)vLNIUBlzow8>GaE2crUD_F)2?Hee7' + ';?~EijmyALC@U}HO=aB=Szd+}5bl+`DO(m7plt^sh~O_QyMaOBUz4a@p!?0RGiDfnDq^3^*n;B=@;u|)V8`u2p8N+;O9KQH00008' + '0000X0K*A|Z^Z!s0H6Z^05Jdn0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMi2bZ>26X>Md?cx7@faCvQ#O>5jR5Qgvi' + '6@yP+*!7`36c$_vIrS0>?X488Y>yMQ`a&8fYv_;f$X;t>h!3_kAFtkLq^hc(l*Lo05)SaH@hyRNKk3EBo9i94JpMcTV0#4Y9~8)i@mT)6dRAr_9Mizq%L5ymx7bI|U2%RadI*4JRNqc_7R$z}+(lZoQgXA4`gb^~5zH_L2X' + 'I>r)0^hTuw4;=ZH(-_YJ_+gMAXs~CCTsJJN308*=3o!T*6qKD#M*l_d-5+jj#3Jr5Kz~y;x=<)!RF*8K8O1lKf@)A1i)szq}' + 'm+!0p#~02vdEd`pE*GBOu;@iJir7$?I-~o|h{gnX4W0!w!Js1>Fmtn^tX8B<##7=GX`!wpPgQOl)e)*e+x&SSDqz)Nl^Aej8SOMl' + 'Stm5(@eY5^r*SN!Ip@rgz1VXGKfviLJn`(-`Q|70H}_L&?^64cGVHd9?+bVj&hdL6-Vt)a{VLU4bce+`O=t^Qxh830fq!6hqF?;`' + 'd!ccmaf6Cc&3C3(WhOq0_5%m?p7&6CBJ$dOa6Ri)?Y)y_pzTI|5dQ&CO9KQH000080000X0LX)CpPUc?0Qo)u051Rl0A^uxbZ~5K' + 'b1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMi4Z*FF9a&2XDb1ras&0Fhp+sKjs&c9+7`LIAjVr^%W%Q3~P$g&)%;+0&IvUe_(3JU^5' + '5hV~{;USB5^uJ$s&l?Yl+V#~%A8Zhq>FLL>d%Al7&+|USUv?}`5?SnqmIW^w_AX1BtSsVc$C9#GmDPqx4X>vRX93Y<9anewds)JuDa(2MkQH|`96DL$@g3ZYixg%9$%?OdRq>S31uqhA)(_cH' + 'UGoMW)n(oydn~INf86F-k~OgOZZ|bn(cUj#4PUYCkwm{1hRHp2XHxbyJ=aCvGriZ-Vtjaun6ZICmJh_UlKYcujt}mkF<9CzE' + '1pkXp&VN3;yg2{q>qr0QdQuj;4K_4%mP)M)m(`~gM9rA|5hHxt!EfhnMfFr_j?>14b|+KImq)60<_mN_3w=MEed*j~*6*uYdw0p(O7md|&S8h;3jo)&dbUNPjJ1BepFe' + 'MDA?%>8cW@CfbO~P7ER3ln~mZ2VP~X45-pj!J`0_!R3_hDuB&hKcr{BpS}>g(+R;;g}xgVVQw{8KV>{mSq%F|_7FFm)kVCm*QE%D' + 'aaLfA;*Y>jz9S7OK_p(;a@X*B1hptygs~ghG17)P-tz++n(rZQo|n!0vMtj6810SpS04J0u&)Lre+2iKv#(~^C$9%NG!0rB^=5OZ' + '@Q8edSJBzkhx324qih->A=f_)&8c%=q0qfM' + 'EaJ4|6g|^hEIPIarJ>E8)kZ3V*w{ieqRqIcHzGz!6LMCE7t!Uri}Mekz;PhD#!XZCCFE1yMfCRU{OIzNH)XGa{=?%B' + '7bpL7a!gMxlK)zC2cBQR-uB_*zs<;$*GZLa!5`-=gG|xgZF*j0h6{#Ko4%#esaU#)2(A4(T#?cZp}@v%&jj}T0V8}XLs%?yte*0R' + 'DRXaSMH%33;G(z7DNfuw0LWNqeSu7nf){rX^**mLzlB`+GsLGQ$LJ{57{(d?)n!{H$`C18&sYK=QY|fIIL6j^z`xHKIJlrS)k$!8' + '%`xSIn2ur5M@ucd&wl`l7(bQYyK;h8w{afT*%!+NYX;ar#fA`XxU>iL*c(AxkI^mB-DLG9ZW7oEy}|J-56TqC>cIwI' + '9R{An7BmI0B|Lk;=N7+q#?g;b`1d=g*`R;`M=Sxcm{RjfrfU~hq-fGFVK`)`kRYJZq1@_F0n5SG80Xm+Dr#`EEKpT-yAioX4K*EP' + '$eXr_m$}(!r(jeTcZg9mulP2Hgnon7B;X+{X7pPD>T{KWekXg13?@(%s!5y2$E654h1Mr&?zoF|3d?WY%j=7yt!Ws0~' + 'J`|F5TigQ)!7&sXaq4ff!hikh&5!K&4F8*&`9aVlAj1>UnGfP(2}JLEPn2>)Qj$;e`_zq;vjr5@#n8B<++x}$y+zY)c%J+2NXYxD' + 'I701qBz_%q9m*^Wb~W?*5|&E<)q`2z=EkAJ^O`$P|3Cs6+QB#S(>;MuEdLE0IBu}T0h`EINB-x1S(|5-xAoc|d02CP@4pI$f+FVs' + 'pk}Lz_l=)=^*Vm@r$2i^xaN=YX)qEiiI5X3Y3?_OmRF}oKuE{Szha-Kt*JdzvZN}pAqCN9*a)7?;AScoU=|J0?QqQwDQ`zLgc>5U' + 'At*HReCH&{*4~zhB)y*)!ycLBWzC&rMHb)(rnWCHtGYFmteWuBD(Y!*Ru-}lGLihWU7E59`B2qx3dnZa0H#i&(qLh&9D;YirUHd%' + 'g(baM&4&sS11fM!qJU' + 'w5bPP!YS$_mVHJs-=u_vrMDkn&CIe%x!Gi(AXQiAuq!>to(3oiZDFKF<7$1lW5cYD;w7~=xgVfDyfE}os?NA=Ts8HxrG=4&wq(F<' + 'Th#Fivss6m@2V%w`nIey!0k?yNWeT9`v-`C)Vir>v#*wh)#y-e_x|@kd_QI1&t~5TPhP(o=#n3VN~mRRN*`BuYHhz)LQgHhgVD-+' + 'EJztm8Ri7C88~sg1DFkc4ng6XdD?R%mp@JQRwqUs_1@PmVbx@Nb*v$Z`<3$=ua@6_^Dw;?%^' + 'XAbHNP#*zafygqwq5LI+RK1BJzvlUh5)3!}!Q0`c%z4u0yq>cZ0B{31nBw8k?Le1EPcr9=_AGN%Y(vcQs(5#M3k1GCxzq6&(V3ZN' + 'b#v3S+nkHv^bZusw}aE^Q1Y4S!!zYMX4GQNa{Wc!|Em)yeTcFVC(&%_$)FP9H$v' + 'WeKXoI%+1n6j&MVX}jLx^MPIgwXV>L' + '@+6S}DFz!mjUBfPv_iXcPUt_(=KGt(Sg)ma(&N8jzcouXyjhp2*&!Uy^Y?|3PJ#LYXD6OUHmeu;zHsq_#WBPT$9n{v_T(C>' + '+(`{Xr~Pu`D!Xedatg?|dGU}{P|U)1?~lHa;G9bs3iJM{nN^j_L4i}m8~@3Xl#+Ekmb+Bdc$cVM&p06#8-Vl9Vcl*%9JbUNgDs-Y' + '`z6DJR3#c^>0+RkosLezqzYedS~yZn_bei6wneXs8=ez9)5kh6mhC%k9%;~7p-yy)Fxs75{q!DRTQQ*~WLIE-u@>pqQCvP@aM_ky' + 'NKG3-`&7L1!`|bWfanDbbn%7)svQU%-|5SS)>(|~bt5|(LCb#MDClfrll?rHB?FUPMLHx$xJgGk0#7vLG|98y_z!bEahqP9WGLLaX#XYt##5' + 'xI3BWYbX~+r@+*w0ZGzq`MhvhHN`-fYASS6y|famT;' + 'Xr`-9a<}w!bZ7Y2BXoT+>dM33Na*jBXFZ;s!fwEaowU)gG`bfPF0mhiZ{vcnw*HT>T=j$D^GKW!A>gqy1lN0AVvvGtcvyL@ra2Xs' + 'D@8Y7-=2?dpSWvgMau^VY)gFmWMRD?EQR|hwe2eb&eBIe2u6rV%iL&o&>CQyxF*~9KCKgiLx_u=y7&SiA#ETfU%==UMCIm>Z#t(=' + 'F2rtZfZ!^6al4pp^F~2!Y--o%4eq%`w=EHBv2?IF(e@8mLj+y!0!0AG=s3F9rk' + 'i|^ZH6jxWf&9cn>z}$IHZ|EP>3;N(91MnrA_K+(V8^P_soI@dt-MC*1*%zW!H2(hI(E6KzN*`F@t}%*89rxzXgYr4t_ZE2?^c+-Z' + 'm%>|A@|fqWvZ_YVkTk>)lAviEY6Dz-Pw1{exkWlS{iH~rne4h8F=Dx!V2qk>W{_CV8YONV?vZL0nl|KXgvb~A+Q6Jcea%dAI@x>O' + 'PPMiVb1}C=KIm!(S2Y^;bIkdMW4}GHNkRczQG$~cx>-7jAvMrb' + 'QBAvIngm~q1C_lviWV$c-00U9aEOdWQtE-DH>)JJLfE4F2^fVXwOG)vqY=J)HQS4lc1=g_>lEmt4BvoMw-V&4!>da{L(FIl8Sm)E' + 'g~G@h78K+W<1ix7f(8Y@9u;Dd--rdx%wc%wot++LSbL$MYqBoQZ*Q={75e7F$U_lVW~<^GTz}F9yRh5jC1s$ooE3nC+IddrmEwC|' + '@%iseEUmMlk&xuns$)^fl8y2n|JT+AMg*dRZFF;JhOEqJAKu20vW;=@6aWAK2mk;8ApojkS~ffh0068P001rk' + '003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDV{dMAbYX6Eb1rasrC3pO+DH<9*RSZ)Jp}d$wFvJeJ5_sCLXv?pNUS8B' + '5SL3O!`M~@BuWxavMK-f`(`AB5ZKw=xr7;dx_iFq?&)rgs;VYmuER9UqbE+AWX+d5{G@F8$TuO~Cn;Ip^fFnmG`iBXfJj*Hh5@(cSX1C4ql!qHi`BRiZg2H%3>uAa2OlV4;cpSz{-X;Gr%9KsA+O0Y9uUI3?c%fsNr1-T>;#Cws' + '5P&T|Z<8p_-`6rb8MEcl#(%o37*1r' + 'v=T%LbBZ?GB+cnd1nFH4=}-c_kHFob6>PCi3Q~wY<)9_0vf1UZAZW0`5__c#zj;?tRaH?WmLRy_33~^@euXfOlU&+8Qxwk_SyMfj' + 'xz4p^>$YLhAM{-z{Hc341oz=4T7Od+sXW`<3n!|bf@PV+c?uJWxw|NN+}#CxmLQ6^yF7U6f%jQaloh|H;EOOw3)|jt2B>M9-u?$}' + '%<>!jxMfml$^kbf$<)oZ>nLxvAwP8{fxnnp0;+F~ZQ%U%Ks0R~gx-PRN`iJE*p5AOCX1=%g5Fn@e@eF$J|gL1iZX;C43yj#0aJwt' + '$-U5iw_S317Oi$1^c7+-gEH7%m^P>UOG0;i8SW4PfAKVtk($I@IvfGW(EO3|R' + 'QJLCq%Smz2$}L4P6#~*x`b1|sv&qoOAcu@W>A_;s9Wu8*Nyd;2lWdEOPh^jJ_-IE0p3Us0fMbHsZ1ByPd5+IH<}(%^GmiMYVSRo$' + 'vDu$z06G?yw6tT^IkR41b(*XTHmq3dGo!J`MolF)a2uvZ#_6KtiN(eZelQQgZ1(Dm6Kl>Ur|6{OBR&}9>dY>1Jnc_)#;N7AX_L_t' + 'jCHQr$YPf-EGze8Sol6tj1XkAQ;|B0XSvKfVsVbV=8s(FH|C{Pj+opT`|uJpdk@X(1D=40@8eMP7cN`;cx&X0>QdMGgk7I=Sjp5@' + 'kaG5?(6zj=Iky7e5kr|xsw9~fiGYbrz`x4#Cr2G>l~WxG);!O7ib0qso2)I4CXaU;Sr1x`!`>mSjePma+BfRdv(#Hac%8}e)!RzX' + '+`g)V*EB^zhJ0ab$Z2`&uV~v+^`S9WJCug;18-@4fP2^Oh{O$!Km2sZk#~firuuh^`D5)Z)zqO}HHY%QA^-boyISvkhx*#ddgkeR' + 'eN7qavpS{8bns^1cY4BomFEiYl&Q0H*|Eoj!y0aSKGJt^i8@uQnV#?T#Y%-Tw%7hYuO#%{s2_`UusKzlqFklsGv5#ft*q$2Da|RVVuJ8X3VKo3pDw?aRfKMjs5dizaU!E_q8O0YGglLU_NUYsb(BN6yHT&pZyIm0WxOqf?pg' + '_~e}6=+H&``E-T7(fUPH;xic!b=wTa_H6D4V^f6eg);Tf2k9ol^~$mf>aUV0Vyi~%;p~)QB|_UU%qkuH^2_%s$;dqLg4c^|f!(}v' + 'gu7ABH(3km;=WSy4-sJqHMz)hk*hR~72(h+AylDIJGSrJA|hl!$kQgnK^$$>J_(7J^oZlwZ`UI0_}q&B=ah+vF-GVytV2IjHiAl3' + '?CejhN1_iwr-6|uz^Jzv&72G7XzXn{>Q|!+F)9q9dexLyO-+Xkk4B$th6go3XLvNav@~{UVKBAXR7{jQ42R)C%_#;bG;@Xr1XT7h' + 'zOPV`QTfqD=}eRth6idV49@|IZurdk=nIOin}BZbhj!1F^0NbRRuackO!wJgKl+q;CVm4bo<8Zl!Kg1;R8*$Inr5K;&eW@7uO(hr' + '&}hbaheR@Ii=vdbmXBfjzz^+VgR4UYgX|Yiy2?gFdF;owbnVP?$BsER0&g)LI1{hw-4Tr}nv7{+5vnZND7TlQvqXPB9g;pFZ%i}t' + 'U`Y6%u0%=p4D!cxO@<36!3JbF@B$nHXYNfj)M;pu?vV$*kaNjJ*XYuMKDlHMXzGw}kUJ-LL^G3g**@W|!NW5k-=Z;ngkp~dI!$!Z' + 'Se1%r5I1QyC2vaBoQ5X(12PvhatMjepVDGLlR1qp$=B%vxfVGdja{1gq=P&pe?qeX4X`VRbf4S`@+C0pq+@e{q|;ou(wbOP%l5^&' + '7v6*!?fJBFY4&Y%m#mp|RJ06``R;gHdGd(bDVdwiMZ-HC>J_1Yq*4lqoH9a6d9J_!!oYF`rb4R1bF?DcD-c+UVdt1bU^%zFNIgpoEu#w-%cu)2_Xe8s`WaB~VLrz3`yim(Zfr|2?gi}d0g^aq8' + 'WN}IsvM&nhig*4puhZo9f5`i;WPg^vITv@Lz%e@Gxqs1#;cj5C+j4Dl_!DBogP8FXV#Yzt{|PZ)h&{_7YV5c=WbWTnRr24~4`9(B' + 'ikF4zs%L+<6%@A`EqT>rqw=sS|0HS7SFMcat=HNhc@czU(Qb=WB`%Bh4GH=ht=(o-$CZXr#>w5qd9>kHnkFd@Mw;Xt=+fgG8`|tL' + 'aiQ~sEOm0%gG7QwN$YmCURSO1<(kK5dQOQ-`;}y`9LwFDVJf*^$0{zQAX;I4N|xcBxJUiAha2Z1_JY-xVVuO#5-3aNtOm5vo!;~=' + 'Zi~(liA>=98!wk&?_Io(*5lZ3vf=q-1Sl~tFlLY9o=e_s9cF6A=U;0;&liUWm=H%^n(TK?0M_c2q*`I*5@+wY3' + '%^?Y(4{wEFS;O9zHEa`ZYlww=8(Bl*{C>VCe*Q5yadRnF+@p)cOQ2}dP|>(p89mm?SpqEx*Xtm_0ltaDP4Rjm2e8z`I=Kti<*S1r' + 'cB5>!-SV{6zE%DUP)h>@6aWAK2mk;8App+Sw=(Yp003bN001)p003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDV{dMA' + 'bYX6Eb6;h1Z8I)#d5u<4bDKC2e&<*0@r36R#&+y9=`@+apq!Z`-~!I|n#mAiY`HI!LV$RoA;`xTy)bF*fv{QlkqJm49Uiozo;XqD7xwyf9+ScQb}N5`5oP@9&FXLZI3NQzYBC0%4m-kw*{JP|7Xl$Bg=O23wQ' + 'w#aH>jNYZRr1(+PIXX{yojl}JP{_E3w3-~jmlbthE*7sY(-5?*L6*kKE&~|shf(%' + 'ac7Yf1*;Xu)~0?gRcwX&Iw>(!kF$z>eU4C}X0' + 'aXfqFq47;>-^$DR3K)B}I%sb@D=;~mmCOud)0f_l-ivn=Ka3;)l6bq>1EqVAhawjxBPM$hlP$!0rte0G=&d7msw{n0opbv9okTc?x{{i>Vp2EqeImtMMpee+FK&pyWh90Te>*vS>pV=aL' + 'X!4|bmUB~rvlyg}7+;W-dJVa$_c}6r8u%gXc^i=Uu)m+;A}Lsb6ml{DWN9PXaZM`>7OWaxcT{n&*A#1HPmNuT!&D=s(USpGc2!b5' + 'QbRl=2QV7+2mdF4nAXigOjxiv2>#@liE&M_%pM!oNP0M%yt-_Z#Yvvyq=v~apB?cIb??t(&K{B+`3kp$(9C#K;;d-&?zDdaP)h>@' + '6aWAK2mk;8ApnC!Nv+TZ007Jy001%o003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDX>M?JbYEh1X>4R=axQRr)mTZ7' + '+cp%w>sPQkS%G69IT}zAcP4F9G}9oH7AOospe1^uR$E1C8prj2@8eEN)Xe14C5MUi`1tnalj1o3aOz39MnByCbn~3zBSBT$kBp*Y' + 'H=x(IZ@=y8@+CSF%*KJBejq&_Fsr(D6UT8B9fz(#d43%Eah{{9>AQg;+_oJPY&41#tsR^GgfK;IukICHU6Vp^ZSbMcmv6}*BWVjF' + 'nM%xXQDaI8)eMFtIoPSM+B(B{ZJ$s39_=iA#|6h-j&4fl9!FQJs5p}){g*AifPWfh9Q=xtY4$EpwxZX*Q$' + 'AQ(IJnGD_Ysx8SUQlf#-svHTTwi!6kJTM+%cp2x%&{p4bKwad18a9T;O44pOHo=-f6{>B' + 'Bd*%Ka>}dJ%v)&DOv9AgDV9o11%R)M?x_ewhb9@6@e1l(DxzJ-k`1Hc|3uaw2A?bTB' + 'XZK#t?n(&5CW#{*n0+LYFzLSx!0$wFKXR&l%VHd%X6L&S)H&;GR>jd' + '_K0A!O~i^5SyzzK)<#P`@6WNG>@n0ABh5xXuTcy=t*P+yA{H-yf%TPh+>!X4ie<3R;mh%yYU^6J{H)@0_+*$~90' + 'lM@O|7I!!DaqaU|r8%$4$(i(59>VX*DJ*jW$8jwzitXtoa=MvwK`}X{wYei~#mUm8B+tt5s8q*^>oC2__zM-`3`o46fgLvY9zZ4x_j@AHxvJ>y@EhQ7#5#ba_64B9JqOelmVRk@^JwhDQz{YTP5k8ZZQ_`X' + 'T;><|l+Ry7ZYhNe+2Q&`zLLyj{?-nJ`XguL5Gr_cez*co)11BZ|kp' + '4BrbRkURnf$fq9WBOn33Snl~5?0*pV)u1=O' + 'GL0`8{-Gf^D!A6YqcKZDf%EWNAawp)$Q%GUVHEfvQ6Rl(x|Ru`(vBZy>2t-0Gbpk@Lwp;6qAb{ilL?1>>!SvYTahx~@Ke%J}Pi' + 'xM3fBuLnf+p1_8Yk`enTttk~88we<(fGdDlO>+WWD)cD?l{;+' + 'e!BxB=YgBx=ESg0V`Z-g!=$fxpNgR4xotkgFoKj_YEUfo)he3KHYlj26+3zPkR|1;Xs4!R)v1DB&(rM{QfjNfvihM*#b0^KXHA~t' + 'y3TV`G_e~P_0=QRquo4*DoP(Zqg0bL@G{TN?AEk-i<=W?^K07siA5ehF~o&j>GP+}Sk3)J-YohXP)h>@6aWAK2mk;8ApnPqhkD}*' + '007V^001li003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZDn+FX=8IPaCzNY>u=l05&y2gVygfL(xGU}X$n+{4hft~' + 'zqG~Sk`^rtgISR)nFzV$aY@I{HT~N=vk&frRBZSf^iU&lqKhp' + 'Ap52xqVI;hA38>EXjK-pFB_gNm&?Us-!(PKvi*RwSw_mbZMq)l@TQl`#A2bx_)xcZgbKo2Gt@U-e(ilpIZyWk&v7fQaP?b#h@3X`' + '-!m?n&Y4JQ-HwM3eUnqs6Y_-gcP-nH^1z#pX+o)4$3(qvs^UEBm?(>ZWjV0hQ{D?acQUAVkGtwp6HW4OBQnx2(B' + 'v#QivqXmJVys6uvXBnvXz-z|)td}B+#G5`8Fd;JD*oFRrt`oXy&g)jT6p?yqN-|OLx' + 'JmCp()<6KB&74fs$*Cj=gKb4~hFp@ZrG6=C|Fz*PA)70*Ix-_Cfvb@f$&IAkF;Fnq' + 'jv;>T`UvDAPtCwkNAB4tC+7(;Q*T%6IQA(T)fw$p1?V3Qn-w65vK)Q_>#}0hq8$cXGk>_HW%sERXan)lpUS~)_j|2=@-ICHnprhX)KHNj{{jd`<9hN^' + 'Gai!@Up)98t)GfjM+rgg)A!Tvw{5h1oP}-8^5s}F}Z-Om4Q0+984I65pM$LQccp4BO#xz87E?B+p0v+g&L!3KB+7$b1$K{far$222*z10>fZw30Pa78Jk@Fw5sC-foZHDFuo?`#ME14' + 'JuykC(qiCmW!?iT4dfOv2' + '(~2p+R{-xwiOm@m(YRt(cN*iqRx};)fn=QY-7T5&X@?YWY}G$@7#w8_f1RxsCAKbF>a=17c?_OrIsMj<>I39&0~ZHT@&jpjbw@5%' + 'v}&)ZgOV;D;<4`3X;2b!DYZb5#Pk~sj~Mu)@fiX^{BhM^cML9492k;!kd9HDgWKE1>lJeK!ZBUhP+wyU0B&*rfD2sTz=wb8i=CE9wtSorRf3%wS)!dTS_m+MU;HS2p-LXo)MZa@?>?Id-DPZ#Y8&wWKgd=HM5GZ==)%R%' + 'bZyt%z^$pkR)XHSuV@cN7E|SwxeexUvtk$+ir0i>i5*jn%dt*3pAtiEd%VG7BSaImIY<$--XB*Ux!px~rC^Xs%2V)|g+7v}2Q3T1Q' + 'vwtal`QDmIW1{ZLMW~|0FQr>rWH+<|5IWN=;~2WFZX0;uDpiivxa@|Q{6Thi4ySb2+_0cXP@TKqAcR=uKNd$+!}vIoNPB{g043a*' + '_@5vMCgDJL0e@EB*0b7>VFYsr-dTS&dM9WCRg4D' + 'f&-WRGClfBLY4tVBht9bXYGq!@A>ka$xwdzPBYre_>Fw-GhQQ|F>wh1dPAJXcLQjt%2~IOg2m9w-gF4r&!me|DXW' + '1!VUUinIWAofgcGul}6iuhZx1P|&rMXTGFq-mzF)>OM7!pb=|1I6HPn3U%JDY!d}m@w1COH@@Gnt93wGl0H!Ep*rYxUuw5^ycyS=79bP);jRp0Gy?<(0T+u3`lR173S8fGYxN02ik' + 'C)wYA{Qy9K;*9NFZRK3M8Ib_G8~yGEIG@ko9c5XHPF1>Y6QR}r^s3saM93i<<$oMxM{Mwel_;*GGc4gJ6hYlEM7ror-u9ds)6_0!?D|t`=q3X-h%;u%J?BuSLS}R9bp)>cECl$3F`|8vw' + '{pPRyx;r%}4U4imCE~iRyBao+};TBtK1ZrIW{eMO9Y=|nl3&Axs4pV_^>)3$vGK?q1Flv90?mU)dKi0Q~d$' + 'R*8uMq_6{y!i&fw&0EEEa$Ah+K*^1%sX8;(hb{Dw91>D0P<^)rkyjG%{lJ5^S_hW&~$i_12&so23xH7d*c>RA9802X7UaT!mZYMPSyl6Sqn=' + '8cj{L#SR?M3D{FgD+}G_bx{+_LpI;68FZMoFVzm__39{raxavR-VdZlUk-k-nvA9M1X_&>Kj>V6;JMW' + '3RR!W&&QSHpB2yAm$mGkUrl3dWucY$n=E_vx^3$=+RwkQbl;$DfS)$rl9&DDq4;GU8#Oq}(-z)Q0Eq4o5W3XrPPJWD)fEiYStY9_' + 'SOsYEN|^0~Ixuc3(L;9*7GpjFBeVnHiUtp0=t0DxXFW)d{6{XLH@{n@hJ7NB@7XvvL4_5eOfs*aJQ$Q9V8(IOnr' + '4NP$URw|4S!qPybCk=dQXBN63$Fx(H8U8lov$YJ_lARI(kHbQmV~(w58F}Ln&W^B+5jJuhW(ZA!pu~@86F}PCG9uEIsLJR8e8V#X' + 'BO~&hSxkx9!RC#vAPlBblQ8TiSUKUnubBiHXxyu|1qpBzfxMtn8Wpjqn!cNYeUDK-3gnaEd!Qu|!)C_Z4R-D;?~}xMI>iL}CV;Xz' + 'GXPl;V^C|(4S+Q86BM86L#_R;KVF}h4Ag$Zoa+;K^@_mhDMK*YzcAWO%fDoxFPwTnI8iOB7DC17jky=_NfwpLe~l?XK%Hd7qzR;5' + 'O$>}b+mI0SQR-V@Ezc-4yfV^E!Bt0bN=aEXsR{Ux8M-oK!4gnjRM!Z?BV=Hc5FkCBA{Uh~C&<&p!njZAr_uH))M%f@PZ}W>ic27RSJxu-SlG)szw<3s_u)3FeRjz2y3aufw=H9JRnZ@x#t`nDG#!X6)X{#7Z(=^V~oe_)g^}Q' + 'hP*_^s*_2bv4#_Pc)1iVg-v*y`2(a16?!Pj3gPDleiJbGJE(@q?%Z-Gyq8S_+C@HV0eTBHnT9ZnnLp|dh2f&g)x&}rg^;O+tF1@F' + '?GGs&BrQ;E79Uie5p7RX1$>X3WN=ypi{cGACS)FyLNCc?aKGaeX)W8s0$*$hQaqb*P9ar+*|Rqd#?#zpb{Gu?)p0hn5^E5~x~Van' + 't*Z@%aQlQYoU}%E;7LEIwjQC6H>~FNdJ*svAJHkl+f(!xb<+8rlTy2?r' + '1NibUnk^R1wCzjfrO0D}M&72v&>fTEd#waqa4uccD^!^?nGgk`Kwg;LEc5x0S{fjrNb?-BPvHuz71jXRen9{HN_q_d=*p`95G' + 'K=q?ws#*5*bLo08Yxe_d+zso3R*j^tjW-X6O(tj}s!;^e7AlafBK#YmWIRAgr4{$RunA|wEfzID{NT|1@PpW@?p~>ilU5xR#ignu' + 'f)=Awx;T@rzPNaT5l7Kcn=r#%29gQX1(g@@CXkNn' + 'l`#*zX@=>puePSn71tE_-$4rhRj3jhoWXLF8(VH;i%RW!YDy624VOIrcZ>)4ia(7YXy`-;Dr`g8StD*Ev+dBpRbJ;)H5#I|hF{$O' + 'FX|J>jN;f?zPv)40I3+1;RZ_TfMf#9LL46*HltqW+uQfl}cg_Un!gM85R{Syrx)&S~}sO' + 'Xhz~Sw$9xTO&MZDYIvS3PM$ox|xuwgBMfCg9$_k`Krf#0&fPBb4ty9vg3h~loN2cb=}tbc!baj1w>I2`?;$`z(FPNKCY_9DwGC2%PnyyTOJGlaN^wMva+sZa-2x{|5XHS}srV+~hj{Af_`JXwe?Or*r=ozfKNgYo@}T#1Xr?ce^(+Z5c=?' + '5qBA6F-t-9Aw9_l_f%zD5HIJGD{=XUvu3LE%CD' + 'r-}V4CQ!(S!sdqtu4~Z%uL%91cC_Kdf0PfyBSRdXxqvp|lpee>>QcKwdg4*r)+AW*h%meLR?!Bd=rW1LGaIt2yf}(4M6`gn4CXSy' + 'vk)k}tw7=z!DOo|V2X!Qb)dTPL9tcY6F{tr(Dux-zS0nR)dvMwK&Fh!_GdiBI^-ASGI!83@KE%+PtGQ#~}%BJ$Bcdv+B+' + '34;}NE4$qdlyC=$r>f?Vhngy(+Cr9nrpmw!tE(3Px6TMy2CtnCU(6oY;lB_{NN{UlCdL@*y})Wb@Byyg-xQ_oQ?hL`-w6xxeAEc~' + '5{U%0uWmK9IK%QM6?huW8AL!Uf#Na(2t6pI>V*N@s-wX!^E-6A`GP0Xnr&Ol9M5PHLoAnlG{$tJF%>QE%=i{#IE){F73`-?I!@3t' + '+ykcMkiz4hQqTEdmWcKTX$FWHQ$ER)JhmLKv-7)@h`uPJUSLUXCW>JKORt@vYUv^*%d50Kt2T2#pzw4' + '?xSZ+K4~Mjg$SG`' + 'RTPTu3xXX}HR}{&tS-#-rtGzAyt7ga8e5sp@yy~>ei%oTkJ=Xk6niD{f`jIBBMEG7>w64*Q0O2~L+06$8z76w8CDs>!M+4|' + 'Lqo=B1b(REaI=Nb9vdY$yqj_|q>8?0d>#*;o)8R=iz_c+_|jXU?DZh{OFtYuN*!*(aQn8ZvO_DI8zyR3%$0j#-~ObnWJlZ' + 'blmo8vLc@vewLUvXa@9<>ooN$>DxNg0sm26`SY8WYcO##c^ZpaJHAxRsKg{IMis*kE`PoDHGJD8PaBA$cragW2h' + 'I2Hea$(gsq#B?L@LXfd(R{v;A3s^Obb*~H(W4%3t9b4`da;du)u)Ac*&bnnS$N0ICoSk3CzwqAca?_47DRvF#gy?X57Cnb;NR' + 'Uy=8C;)4#wriCUbu+S5m$r%j%#L!BUzPY_PT?Ik|*k@VQqoa@rrE;qyi@_5Bay)X(SpVJEqmB5Zn7%Ir;L0+JpXh1?CDi;?&CS~m' + 'LI421FSPO`05Vn(4loP!?q}F-&jV2VdGymXO}~I84s(r|G0yrNJmYHG)x-8V-;UE`Uq+Ye3U-<2(IXrfv2u(kcs3_nK5vs@|I=FZ' + 'Bs{b)u!(&EQV1->ddH&LPlDCNTn8wzFVIb%VZkw@>8P<9vUap?@0t$<(lL(vA}7BF*?hzvJu%KM$lE>4##PY5O)@4j7)=Y!$EDE(' + '0Rcu=iqh?m>`t}?w!<?wKv8nd=WX5tWbFUmVht;ki(0G2x`83!{ag+%@' + 'RlGqjYQ09LxLAjoBqo9w;v%110Wy@cjj)qvJMU|WO-Io<{L' + '|1g(#Ye5eX@9JVW^JQooj{xUh_dQsGL*`RAkPu35TyZx+#M8)nA*!|wr3*W}^yiOSqfNW?ST82Z4>Ff|*MH#r^3n|r&u?px-~' + 'hoXsiYr}P9P2JXF*R9^S!38J}hRNEwY@o7)*>1`wMqqMC%=`^T%znKgld0ExA9Qf=$Rhdl?V17{k|n&J#Z?O1!{=m>#=2Y{I$@ko' + 'ncTab>^iK%hij-u`6GZe)(2q7=mZjpHz?!@HcIZWEw+3b_vUNA61RH*F7y?z^4S4Q$ZQ$_jtdgXU~8DCm_^2E((NeN;cy^1%9ESUw|rOkc&HX8PH3ejy-7`b?F(^fQmFXj#^=Lg#rYy2VSTmS4CaF&j3u^mvKyPGSv_rdcJR#P#(V@t9Ft*VA}YDd*DjnH' + 'x-wTiB*GTGxyCziz_&PTJvh41N`qox#O}~wnczWs#vh_!E5SJu9l9k$*B#(%LAmydXpPm&}%lfL?m-N{?H%!T?_#ubV2YDXL8Pj-^O{+PF%O{|LjWs' + 'pW7Mk)^+#TCIV{DZO>HER_mST|CkrDS@Dy}Dw2~7T4{5O$HL5~;5PSsReMkw@xi3T{M%ebqX*-)dFnwnd~(Bf@yPWKUgwPCTU;vY' + 'JJpuJQ%o{Z3jKax>)Y1ctLIuQVu>W;R-IO*JZ^J|7c}tn1OAKm>)Y=9ascDO' + 'o${~s;&KCX>ju0)xLJdc0fiRGORWu>iD5LuYZFD)568Am3)ukP%2;`rXCXv<#T74@zFoiskn#BMY7qE9V%@}n$uhjaki4(w*qXc-' + 'hVSQJXZ;HMngH^Iv3W$$F$8jy&CWH&OSx6$U}T53?i*AU{9td5pF8+Z$Pdik&k$BCsn99O+si3QHn@u+O0I!P$|EKMnbV(D@RXNvH*@;K{p8;Hx21Z`AOh' + 'x@>nNi)z~{d7IH^dk|BU&M*mafg$XEc+C$G^~NaLaGPa0`f}|?mitBB;C(Pe#TOPJFhMfYltqV?5e+Pe2*{UiA^Hr;H4ugJGMAa?' + 'nBC>#em8-RH=IyS{537lFhy`H0*_>;{%$q?mU8*DH^EycOwY^BVFRCikfo1a%U(<0Vrd|?bNSAeIWYv3+RyiH8+n2X1JnS!4zI4iAFL&J3+ZNs`_?-B' + 'Z{6hsRM5OQ9p>^kC+F8C5Xqng<^$)*$Kmzvj^KC7dPi<=MPG;xWA}K_*69rQK$*5F!Ds&qP)h>@6aWAK2mk;8ApjW#lfP;Y0081Z' + '001%o003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZEa&|W?yh&a&u{JXD)Dg%^O{F+s5&ozXHWfIzS6y?6lJdPMJiO' + '<-}7<^4Ll<9uDUQB#shl5I_LXF{A3gclW*zcK|6T?bHu4aoF44+ppW*JA`5QqNw|#r)TSI^O5GkZg=teRnTN@S5$YwzJsClA-H^h' + '^{OuOuY;~{_nUs-(x9cAy3M;Z48z%M+t#~)knJ8{5fT);rf&NntE#%s`l7D7*-VUW_tmDa>#~yrMJ<2TYHZD!N;SEx3)bVW)1pqB' + 'eEk{0@P7$d?RU)q2dhRfxv8t3e(uX+Et%~0P1Dk@eSHJ)Z(hE)d{;^xhdSJ&j_<;Cl(^EdC|^}{THf8j;e_7(XY&V#=s=E%Vr`PCSCL5i*>bxY{a`{EP4_&S+A&dBx6_1l{_AFeLn' + 'k<07b*S8&-`IF-WOPY0*ppnkC>ALiROR!30U>bjQPk!W{b3=(YQ)yD5Q|G}DzVMMF6%xnl(6v0WP@8Z*?=p7d)`AZAZ5|@eD%x;m@(tn5*}94_0Xn*C7Xam' + 'izbShG}wreM%p!H(MMqt#wcP7d?jhqUDF@rJ`gc-gCN6*iLuCzISX{NWDsH&Ap}klSQadxsD<9M077sxBFyr+klD{f1SA$>%y?tR' + 'i)6-lbm9@82tdZ0h^%o6zagWnGZoUxvSd)&=sESoxQRqArVM&KIH^$y~s+E)20S1' + 'L{y&kd++Z=M!4NE>rzJnw$NKVg@o{ihdVGI=ve?A5-%(y-@x#mksPfnclEeVO' + '=2DiS1u9X~7#qxRj4&tSf{cx@LG}eGdm>;-7|4N&O}~U3EYb0L<&9lFz`2DCQ{hLHh#Ih>mmF332pc%~wCm9VwLO#V000SgJT)DK' + '(`LUg$>9GCU1y2Npm1DJ$Sd%Hvf^mtq4R1mAfoKn@h&BH1&Y-nxU{ru2<#AE^' + 'Gl7swO4>u2&Qe#h$to|Z-{9+LVkEJc@j1N5mUX3Itt9b9pv0=Vq=oWP!%ig<)&WTWRTouc;uaM{Y&!@i!wq&a4A_>c7+y*QXfSeg' + '=Cuc5q21=5?x-&Dg}em;Z*&lYUKQB&3C@e0ki>KeDRjqRMgxW@5Hz5f327bTF1-xHRq&^vq?L*#98YYvUhGwf!6A7@R=zVSN=nxw' + 'q_RCEMXfZo@pX@yuv)N&aOq2$#_EJ-BPXOM9uCkGUD7^CTn}?xs6WTy2m018q|Eh*B_QE011ppz?=EYGmN>OMY1Bj(V=h%*M0dX%' + '?+RgEJewmxcEdc1$psbLR3y%{hw=SG7t-+LZ4J}' + 'Njutu;br@>kJPds9*y*SSZqngjf_JM!EPQrw}9=|jWLA}Fjv=)qWIztt{SyQHKj|`BW*=QYm*kmj6KhmtZq@uczP6C^3Mi9`%#!;' + '2mjkpv=QYyM+ZwOGc-Nf=$a7S!hTF97+ctnHmK~88CEOH)?fiE`VbCVmLjSui%A9^EhAS;Icp@}!-kGAKt{1HdXg`SidAciRV~tX' + '3xk!EH(<>+v~nx5KBz;ALGcT}Opli&3`gh0kd~zDHoR>!OI>rrbksGu5hhJP^rK9m0F7nJtvt?Jfdo|enY28_#ukH{xq^>_zb}I4' + 'F87YJSxZX0y6p$87UZqoZefXK8(n)oTWKuW#uzNXPE2oft=_7' + 'GduxtJCkNWVsR*|?r4PmIbhLdXMpL^*v)jT`qE_;RZc(8eIAC~vbSUYjX{CtGO~4EHpNE6*-^(-^{#;;u' + 'E%~yINUU3=bs-)xS#p#D;<1;%_DXRmNppfL!+{*VOO=~4Z@Gx@fnqgL8(~CB(CYg<%sk<1G#402xM2meBinx%x**nJwEfymjzsL#' + 'B<<*F2t&_(l-^p^l?;x$k7u-3L2A}PK89D;6q9M1d73lXLlvRz^w^@r<@AQ2-V}IJwKFis?NZm|lu~HV9DnU@Uy_oH=n-zpW%qzD^P>Abk*3qujV=uQUKLyGI5T((_V6ULupR>E9skpG_' + 'xe%v-M>cI6$ba|6hB%e27~2R;bRbQ_>D7Z_amffU_;Qbjg*PNW`}_iyh%;9q$B_Pd#^Ewm(nIiUhDJ0*jV$BF3#|*$LUq-' + '8`olmP=*cA;QABpx{K$|!Kp?dFGFCP1p{nXu#3tYY9^zmyLdVoEnd+bUgZt1YlF;|T^lXrrCro!82j2ZY6|PyJv)`yC1(8QvL-0m' + '!NANrhkaRf%LT7;Q@20MdC}e5XdAxDfM6c~v-(^9cV8xvfMRK`5c1{#Z>' + '6{CLe8?PBcS+(Z58sueH0>-0ya_UGo9k#XaytWD_3)EkeS+$u3KV6_5r#DfpjZ_;x={`K4v%QHG86yPZ_|XkYDq#H%Ho))bF9KVu' + 'lY1m2v3RdB8ogl=yBAFnl$~hue&W~-z0sDxapq?pRGL}jki^PX$mD5(qU#Gs?tU*N;HY@AZR+&w)MCA_Z2|aUeCqKvb^TArT3yG)' + 'kjzf!#h^OY>lr08@Lo44ipqgUDfZojb`6$~^`P>>W@%hF!mpKD?JwS-aKulnn>&;Bv6jQ>wVIxE*Vg++W~ppdMmKTvtZDEu$Emkt' + 'ho5WM7jlLLTA8hBNv1BSSf3+|+;!quj$diBioOvnQ`MiR7h*I?eRUy5W4lS>I2x9XN;t`Ioi;A-6@rP~!%XKPT$k^GiQMFhJ0aIL9B`@629e9Q^TUlEB(>>?@$f!eVEl{iU}@&Q6+DhbTVr)RBF+CgXt76_v=GyW~dN&gO!h' + 'S$%0MD8279+&@|B!LB*sj5zqlmu(xGS&pf8B|dWMhDDw+c|4km$o!;OI_w#lt>8g+e_}ot`|=mFtHjGLz?QW+F>ah-TIGD?i3WrA' + '(kgzHUIzsW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA?PY-MA0Y+-U=V{dJ6' + 'Y-M;ZaCz-LYm?i?k>B|%5Txo3P#{KYCC(KuOl~d7i7K|NljN%;3=6`N;4UH(paD?QdcFVsx_e$d4}hecy7=lYRgssN>7JgRo}Qkb' + 'o}M_4zuXpmv}?BI>aZ=^hpT3{-bq#)Hho#u51V>>8(p>4`nt@b-R{Zr' + 'FQezb{3eUKzCEn^Lt93>vTv(ZmtnILq#S!ytorCf)grO&uIQoZ=Y0g^R#!wV;JhiisBYlfzO2_}y}G3i0Qg}KxXWTU`J!rW4p$N2' + 'dRJVR|4xPbs;+@Y-Qi01qLN$_blyhmYO^U@=u1^>BVbG4O($_2PbQld_>kwD0}`F*QMKDQZ4X4(jp%nbnaFQdqyB0-^;cV}zpkpL' + 'uljOoH~>Al>IRFaRWseMuRdZm{I9ME9Nt#y*RH$9j;f#aVYk0Uhm~dY' + '1;9cW<5k&d46B=R^)7D?{r=F4I(@m@Zz|-nsPU%6{}%1-QyAJ65Ogc+-R@B!Rjt3ge3n1?zK6>~oDgo1~IiQ-#0mb~txF%R$;f@A^ku$XuYv9ASG()h' + '9^h_OZSsB5c8p6ezk2%gP5wpx<>iw%-@M9SKe_zsSr)a;pL?}_ryjH1nkIbA2QJ=m6UDmYG57CPTNSM=qM6G@|' + 'i#FQ^RF_{^jh60<@+kUs)%DGFTkMV#QdDiex+$u<%SF3K00f8fa(7j(QHSSU`P~7OUwJ+xwY_hEi|eDnO0+A`v>%VwM&;Sx=GAe$' + 'T~X$nvcT!q%|KO6>vhq##jRa6ihdsbne=^kQ|!wq*=A9#;nFFg>ib?zuvvtfYymTwl*iI(_yh@7(ynFr8Fh)|mWC_Z7FXrAi&j8x' + 'rNp!_E3*<)Be#+%+opMU*h>nlx~GM3DI^;ak5xc*y}R1p(#&nT3g-^*>lWk=|5~2j<5Kq!4Fg*0J1`bXv!n}y0+ES1N+yEo7X6$d' + 'jpiqeJO~NUE~o~ixgysdWf<3&d|c}sD}yEQSPm+|mfLa%gm|FOLZBS5E_TPn>O~Mo^x>wgBgp`U@TTm`wi(Cx^{E)uoPxElByItc' + 'M^qxEC{zS%%cfe32`^L>luRGN2JMhdo(Mk42q{v`>rhdIui5;cfk0S%J(RdAYr7LwIBf}^}d%$sV>ziAc3KX$h}=L;5QG9E!+5CgO!zjZ=)-j8U0&vO?-Ep' + '%=@VUvPJ?JTymgLDWFdgA_Xg0PEw&4i&Ldxrp*lk@)h+2FVD;UgBAz+Dvx`G&+J#UBo?dn<(pROdgRIqRAGh^5>P8{Eom#LpVhKGQKoo9AtR$y2BG(+h1yMjysE5g(' + '%pxl5P#<$NQ`q(Hx`YXaFVq5ZKz>6q47z|Cy$_48P#_`s{S&Yh&}2ud8YkLnbyO8G4++Ptv~*BBOu7dMvNU6>z9uq=3;dAZS|ydu&!SIK<#F_HMLN@K-X%yi_$D' + 'oG50H5@rexP?%<{HZu~3roUBR*^j}QoO(;So)}zDkP*lQdXQU3=Eg}38z;k;DM{Fv^qind&XOHc>gNj%ft|*L6K0Pf;vA!ewLfC3' + 'VAGz`1BKo8owp#^vv4uXqDM;x)9gb*bi%b29(8Si?~)?RRY#N>Uln=OSSv6C7h;APr8OQK@*w2WA91@%%CI`m->w%hZC^n=uKhDMB+DM#fNBZed!+PhuOMq{{=g>j*hhmA)(h' + 'k*Pp}rpm<=y1!?;9rG#L`+dZ9&cPRwtL{_$r_4Ko0<$rkmStG}14K3tbNc}hjmQ&khERJa~>no$bFtLfCrOX)2w' + 'aG*tT469Ws)z}d)pry`eCgb!af7>UjG)0o60u_Oo#wcCZY>@gud-aSq81uAAm7t;gc>!OLarxuExgd}wH?15RM+~?4MWgkubOdhD' + 'Ai!2#UdB=emF@1^KdNNm5U{k5p%BnWutvwbKH^5uhgD+kcI8mzFcpHsVvk@G0z-{Ts>`?4I~X5DRys_VaXTC%O4J;q$ebkjd?n*H' + '3~bID1FPG%bFFK-%}EuF(xRM!4lQaim*2j3QOHj=rzhZ|4F?GSu<^hgcYF>?oQs%IdGd1rNj?d~vSV1hr3dWW&zXNkkGG8;iL5Sn&-R?wzQrXsOq?+T$Gp->nPFI*iu)TH6ZNVTE5dtv0EkCZxea{r84Y(DevG+ZL=CGp`~!xZj;1-X3^u{h+_-HU(^AkV!~`qr952}sbvX_IN-^^P^y%zSW?P7G1zZg' + 'eGhYQjsVQOogP4owMmN6U(f~H+$B3igF-KfNmiBMzDDtLTCCTJ!H{}82AEi4kOwynO}j4J={1bS5FTmTYgED018;+|a3qeSz|ptZ' + '@1dbR*bxkR1ssxnp&USpI%RPOe$' + 'Y#=*;voboUEB6Jcfu#=iv`8?Cs9yze~sh%Mk@KuZ`u0lp%xLoJwq' + '>rx<4d%Q&)FQW%h0F$?ajE(3VC&J&=#;%WoanVL{bXD__qBL@%T!d+0w|j5J6bb(|iy9JQu2k5g^BhU%JCFwG7}Uf17-;URj)g_N' + '??IAn{9+zmFgr?2n1z`nOV^8pi?1vUz_G*O0HCkDhNv_c)k*LOI;rQ5v3|t$q>fqsN<9I(tH?0Nvqrd' + 'He2?!l*qMCwWSuhwk+29s@f!7b7Zxx>uCG2$CF~TDtD2M1N>wv`O>tAtzfl0GMWT*UPcYCM' + 'L+odYL?Rcq-GUZt^TFG@=CSS?OD3W`=b-z`k9}Lf$8x*w=8O0$-xL_JA1~FKL|c{QPvtG`R5G?}IbriNLW8#*dPe`jVAnG-sotQ8' + 'rTHhK;&ZWLtmH)ja#UpGS~X*C7X6u2qX>y|C!rQtUwguCU>WMcW);<7zgu*omDraHQD>@ZB-kVu3AU)=eoW%jil3^>{?LXsFQo49' + 'Az6Ha)1^V!z+s;*A7x(dJ*}sUEZrr6z*$YTK-E%cdxs(g(#gaY_d&p^U}p' + 'S}g%!b&9RN+2socxb*m|-A1|+5a7d66}jV|JEEbhWiB0Jq~4X~UTj36tu8^UO<)RdL1UED{N>Xxa*|0@%rO~y&y)PY*oRI1vawe!mX;nc9(>k9BIfuRX~05q*gfAWI(&vlGr|=0bNK|' + 'JWX%B3@}rM5?)bp%Fw>P_tmDF?w$-QGaNG)~wWrsnhIW-9I<=$M8VMU;W=2DaMwi;%DUO0XYYkdk=oXL<' + 'tc}UIDwZVQ-B0`u$Z@)Eo5S86PPuthAEgEsGBfiOle;0Kzt|}$R?VDyA4@sd)I8pDITzf>gMHE{_Rx-ztUAJ&D>j3ABCr!dl6awa' + 'xciqR9IH(|)0DZ`WPhez*#mnEV6<>GXFzSr<~9l}O=5KPlH}+g_KBgmK5DGSj-$3j1(4caox50tfi8f7vBC|%t+t@3>QpR-EEB)(' + 'Gbh;15VVlEm;A8PS|%kYcYRvq^Nze7+8mTV#!d_nVFx=MUV|va(+?-0Q>D8`?Dh2F7gj_uqf}FDSE{>DfTk%D0oowlMQk3' + 'ygID&riO5gii>m1W6e;|^bwmMieIJ=GjW3Coh3OlxoP`yok(#>(|hCn)wP{EGfP#gy%)|vukM0yEIk!0XP{kc!B8z-XQG-Udd@A$' + '1o-1Oe~eFbeX2luXS!K+gcO-GB)84fuPp~g!B}IVy6?D#N5f0(fD71{C|xjJHOMukoRyK&4a8e;Ncr&?tcbPXfF?TQ2&fFf;IQJ;' + 'F~HNpGdP6Wo1y%GE!0cLdAE3kI$7`Bvgp^3rjJtPh?WKwesjGuYWgWEc3>OoMt(VP_4opCr{Sm(8PYlNMl+kZqr`$YCTD7c$IS6INr7}CKt!1`EQ|dognfiAWM;0CX7@V<6ctUUQ*E^X&EN$m?%@nnR!654TMA+WucBj=X5q{VKM?oW{Y=n' + 'r4L5=EmmrJIe6r9DuxQ~J&v' + 'ZUaQmy$c?~dLwj+AFN{nROWtfiR%Dwg&lYyE%p+@5%Ti~jWmUEy9mY}=qd|bq|DZV*{&h3+1m{Ob*87rN=)2vyl2%tB>FTjw-E%huG#eiK#-&)nPmPW_t@dF%|(rqGuS7FX<6+' + 'pmkF}R27|&A`x0`%{M+qs+~>zQoyTVmpe_`eWXGbQiio(8;>V&_oheqp2&;{*JO%d))BON`6)cGHIBOPxIOMQ_F=lyU63^(H' + 'tD0`;1Eo}%-+2ahOsAn-zhEq1=GP;Kj9rt2Ayk+Ae2g6q9A}gRZf&dS&)ip%IXOK&i^ozqCWgjd9&LL7cM)=q+x61Hkr117a;ly(' + '9K8CckBRco`c?UPIEO&ZmIRli)U4@|&IqzRIu1(xbMuZA^^kcdeLI#uwF&ag2dfcoS1~stx2=qcC#M+2cUU`hT0yLT_?}%U-ZR1e' + 'uP+oLeme1WetP6a#*o@q!$;2Hc|E0f!4eV&h#e6IHSnclsP5hHN>a}' + 'I~IGD&rZx+Rh#+l3t1BA|GZWy3;hSyH+k4lT*0v8DS' + 'PkOB1pb_Yn1x3flnFWtw`CCWKP@b1c?)`0=;ALn_$-Y^|MpJR6$rSSfg6' + '!z6Yg7ts77bypJ90bE;8oD~uGa(KRm2Sg8eO)L04O^+B$If9;UF^TFYFVREmE<5%{KUR98DP@6Ogu%$F(t|#R;IlCV<###kp`_qb' + 'G#c#b>M?YWJ#^mbhY74YVN7|=PZ(3|!kObrApav{i)Y&B5ACk0KW#|$(bT67o;rdmw%e;>^)5Go)QQQFhBq1xbsvbo(1ArKS|~Hy' + 'O{&Bq2t;2*UI?b07x4>91a_&@fqfH07Z)=kaXAUPz&G)q?_?xZba5Y>lN6%3sIN;3gphZ!MQ<%lDxzDkn7daNoA}8wyaLzKAxy6Gigyfgtn=cXDm1YxuZLzjpoVsmw)-l;9mG3KL@^>!l0b;mD3=(JDH^+ujoP;JwokV2MSAQ>WS$B|0eIt4}^' + 'ZVXdp!eP`=!jV2$_deLAjnn9{uK%6JsT!nQ-6r9&A;$k3_PHnQPDeWCbGPqzq)yTx|0-O_$?baUa}{MUa5r)ccHGk+M*QPE{YHRs' + 'R2aWGhForWAK6}KUVULzM2iq*^_$z_`MkKg2&iJBkMY&W$6#E8cKx*Vt4_GUY%^19B$qjXd$D{%MGu09p%X9LfQ#5Hfwry+4UT=XBj?72+yt;yL`3;m0?>IYjfu2p' + '#}-^XlxmE>z4$y%Lv6_H2@Kwn8iU~9e0aS|h(Fw7o}Gc=H*7S>f8`^`(-o6jGvO!S0*Y1?4~!k3YdJWEx>>Cb`=Vam9>J~au~5-d' + 'R=RMjJY$sru4=zcewb$@>x5*4-c{rQvq#mJahf8g*p_)oiHx$ag>5YTqC2{_hbV{|46Ul(z!pqlNlY~>9>IJzvGkM$TLiZnR)vD7' + 'PcR_aky&Q_TJ' + 'r>WL?PYKE8!RxNMqM;5St%5p+wuVX{WJl3Mayg?Qz?Lj^1EO{qk^' + 'AkufN&HLzmCqBql@7<(?q9XCMY)1nL1%^{uIs%PR30sh&PKu9JH-F?r!r7vxfO$s-fc=w*' + 'FdgcW=VNuF^CSYsph)IbMadB>aUq^AP$e$j^wI%3%+dOch1-xbUmhX6`ug?Lcr1_Ni$)M2BLYP-9-M1;qKSL?FbNqT@nCo#YB0(?' + 'LGR8a6!!xAnZ#ydnobXO2YcG``!acyW+x!9`FR9E(MioN_@%4~QAqBG0C;LWkC2EA_ri-h@H&LPBz38BoU0>A+{2j%6G-H*56z+D' + 'k0=SV{bfWMNMs55f2x|EVWL#(y=#6L$%*p8O+v@FGFxLpz+qW+ljuxsgd*sajc(Gi*eLDpRwGTin>SC9`h6_Y-=x@x)^M@OJ' + '!biW=^7gG>w*eaG{@b_027sUV_6dmJq3UjgtEXzV%{3hxskNIx!15|7GoOl6l-YBpinsEJV{P7+2&5kR=RGiCv(|-tZbO<}!&57x' + 'l?(PH2quB#(>6B5>oExBshJJFZ}Pw8t&E>XZ*EGp$$v;sCV=QXe+shHGRfS?H2k2fK;TpvoK*' + '6mT3BU#8^(AOc0V8c}H1oM?XM;lA6V{hcK37Up?0DX`7X3@AMBT=kq8k>_g' + '+|SBGy>hkZ+Dbk^98ldz{7VpXi!%hB1+GfK_c{DO^K(>cjV3Uinr6q+Xbe<+Is!?0fG$kqh!}7yU6L>^oWdiBg+7=kK5FCL3*q)1' + 'hR!J*e@AEjD06H$_L7cUXl@J$HVN;ZJy24j|6||cGl~7JmT#hsE!$?MxVOgb3)L?(T43>}8sT_oa0vTgaQ#=;shRi%1VhqGh70Kb' + 'n`W&^CiBX9{4H6b!*9QG)AI63>exN@{f&sdoFV*kDj_G`&%kYcAS3X8m5yeDU3mAj$XVp1*jgFIqf<8HUOE1O-CBPARIFLfk6^#0eJ!-gt^uUCrL`E6%+y;>tW{t^ebyXK2Cg2OKQ<}o>G' + '*eJxzX{eL1Qy1J{du)#zV1^6k30bIFJsZ#?WDI;qdwJi?bF>5Q~t=VH^kX&j%QM2O4$T>UYq=$$)OuC{jX' + 'Tv=v|L$b+(dNv1yh2!9pDNN5o#taY`LRZ&m6>aM}qt@)%;3lvPPKvN>bOS8wLjCTBmL*n@q9Ps5PAm*KH|q^k)I3=ZV)ODv3NtO3' + 'yOHDq2Zj{-U?#YK5_7^G4i;nQLmKf=SKpx?qc}rxTp>P%^76{cdOypno8F|MB`Ex;>gWRG2e;NL3NoD?%ZD0_E+w;JV97$2%^Jvf' + 'Yw3~bQ#+s=mAJ#Hq39`ZRSb7pgeS}#`n@m&Qe)7#C$F0bmn3>%bwFni;F#7I}?hp4i3' + 'UNb;xg0tQ027$$oTG{IIVOYWHc7F*>O6#OEJEYz*NTcoD+mcL+B0LPTIxLZE>+BfPN-!G+(D2n<*N)*55if*rjO;}3yM' + '3;IZ87CoE$0{qcAOg{m{XSV}%oXMpi)Kf7+%{t#|RjT&=Id=B`=HqhP3mv>&smr#Uz%=Q#}Pw' + '>xFn=H$G{1ZyFvjB0}16U~`?bJj$ax>`=wPHak>JnysO>3k@fo+WVh_)Te+_*3P7Xm0lKXz>A$pzvH<*K0L}i$BF3(ezPW2-OCJ}cI(KWEV$HF|Gfw@qn(W`~~-r8kxlUShmt1l@UQjcI6d5qwkTgb1c;eJHkxU^Lfi' + '!D|06CO;W5p)sWXt}RV3PkHppJ?pZnM@y5Hw963Rk=' + 'IKs8XBQ5>vhOTW)yRv9kH%ZGeOdrgmWbwE4boq-k&IngJhznXLbi=J_7C?){gMv{T!`0q15X%1Tv%)2jIC?h^gRHWC$zy(fk' + 'RUYWLN6{5aQ9Ki%m>F2lO!z4Nz!do+lq(iaeLLZVWDXjUFPi>KG{6FHqM2^IIsh+rL#m#_Fhpc^+O`x~e)9JSLx{*8dP53TV7OVj}M8%ZgPy^S^nFk)>M9oaJdGNwSg>(+ARAH2B`J$Uf$0~B`TswG2!gx!sv$LC-+Wh;_UsAEI#' + '4h3&4bE1&v&dakRW?~*GdxfYCtU_~1Kw#yW8=3;H2Xd7NpT2$6CnR(Ydk@?l$5*KGoJ0nSDK|)0SK_dav7p-87snHu`JDa!l4j)~!qT25Z$vIH%<7U|cfBuG@22a+' + 'PWpGIY<8s3-S1$N2!b{L_0c0q21w_U>s3E<=GBq2NPtjNZOso3hGZEkog>M}NMQ@goa_1PXRn@Le)asX&z|O&uU=jL<+VSK(1sci' + 'H-qbsB#^9&^JYQNkONe*KjN7Mc9@Niz@Q$F??*cplu!SNNH&D&<89A;d=~b=6x6t>6FQ' + 'TZf^BOm$Tj1%BdE#o{CQ@fDu1QnB(P0`}p9A$C|pL>--rqzhDZzyanVZt=zQ#_(^|Ous<&@cbM2L5kwY#I&aqq0Gc#lz7HCbzdO9' + '1qoU!&jQs?>CLj7T1%YVu|94?;UH?N$^D-rr=vEIx40`Uh?TlT>PYkqNG&PhQ%VVb%ck^W34{|!(}0|XQR000O8' + '001EX=fgsXd=dZvm{9-#C;$KeW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGA?TVJ>ia?Hp@w#hYF' + 'Aq;xgxWEC)rMSBmXjOrr#g)YzMXDraTj#jHy)(1#B`L{1H+`T7B$CM8*_qjS?aZu$AozY)cEz^j=Pm#Hj#o>5o|VOQwc%CAZnAR6' + 'S$*{*Uv@3as+^T|+m^g-8H}v+qPlM5APCORR!zNOX}a2VyN0JJD>mD@>0nA#cUf1|ReN?OpXFJXEz7KJd8;0^T?3%huWGm1?pfBd' + 'YAXRR>#_tC5KNq1Efw_l5@RvQg3J7+#E8|dT6T3^>i5fRx#oGgZR#y=y1f`xbnx9CRkUTH7U}j|z*N0?RmnN8i`qi!%@5*fyDYZ*' + 'xZMH+vNFz#w#xupy*NIQ1jfAGtm-mTxU(T1P;#-pNO4*!>&M>W`qu?$~z7kQnA}L@7BCw8PgL07U&BD5}5|aHd*tLH$uk{LL;V|' + 'NlAj(v$`?QVh~s)E{r6S4d2u^+#uPe$P&GUq55J_hEEG)oXrfs8r~N9jthg3*If_YmeHn4Vw(E4hre1eroAe&j)w>KR?&f+&OxAY' + 'sdQQIs;(Wtz1qfAo;6Lj*ChNN#~L(Swr%*5qxoVSOaP&|$vXH3mS(r948CCGfY1biB28B$iF(nPtRN2q5~(1XupA5oPhj+}sJh=q' + '1jBGHVW9x{O#(ZX!@MAh*ss*nujSKE@RVk1jMGileuU`;)K6+sA}7Ua6416P?j(N%>dU%X0*@=?b2z_POz7cm)9yB*yGA5|5I53o' + '-4=pjf&;KoRb74JO&$Kjn`iGX49$zxDs(WJdmsS`c)#UUo?NI6CNspri}ucfmbSOsfwP*MO`fn4?C2bJf3Xl7' + 'eghJS!k7U6g!ZR2oj}cJQ&>4Di>@nqAlbaqbAFMqVJjd&6OmZ~{Qon$#>=Lun{XBUscLsyG*~=WkY?;&etVFsfY6kD46EtvH$Aj+' + 'zU|f{<9{>zsyF_s&i62!-hVY)h()(B1Z|^O{0jk;UJ>_tqK!c2Gn%$g;vD?+ei}qE=>0m|@{mAGBuKIJ8hixmeZuDAD_em@l3$C8' + 'P@5Cb*&@z4DNPj9gE0786-2lPV)#OB2}BMWE?F94$2aW4Y=yOK5G)+(q$o-ekpo>ZQ(8PKYQ7~oxPvptMf5husD{OCn&`}%jWPXv' + 'YB(;baMVPg$!xulM9*}bL`ouD65>5GQZ_NP(d3vGwaf{;xC(;!$GWIO&3^L@{|)O++|`mxQ;Q`NXQo)&jC43MI*Sm0%p|c9&UQ%R' + 'zLZpVwSX02OaNjj=#_kol(p9z&=a8ECiu*@rBd*CK3yEAp^udmuG2ra$1`vlsLdLq=' + '`aCb-z=GB`E7{xK=8t>gy5ySn=Dq!~jn~XL67a5X>k9ofew;}?0d}MN!f3ka(iEbAaz$*#LB=Ra(^Wt`EdqWT7)zzJWMk%tvv4aP' + 'K=p-J2_!qDo!;!)j$Hvok~~RSWG`#hfi;Q^IO&dl>R9=PlfC%|O4}@kMCM*EJ<9#-Iti' + '_(*Wmg)Ofz-&(rGh;f!Y+}YcAVlfT6%fIZ?S3Bj-Iu~!S`5*&Pa~JL+7X&' + 'G^Sw0Ja`*E6nGfm1~prGwppG+sAFv8gqc`n6nznhe0{nFUj@IXv?H#^x>^Fbr;i6ryj1cL$j!Zh`r!IfdH8xH%YYjqT1uD-pv-(d' + '3>hI(tMcrAE)GDJEvGX8F0WDvLa(o2sB2+G>;@3~QW;wcrbBiN$uyeaeDU{1&|yYtKp0A)^C7Dz<3~c~NUS0S-AeCZ(oFqcFr38PpvO9q7rQGqyG^QcGw@p>=#B$w6seBga{xr+3=p!M3LlK1LLa`-u$BAMiib(*q?V0x{h&_Ion?ccz`d6Vaoh)KmYOk2p^7bUdT&V(7wwu7!!!Am#=^HsJhyF^3@%X$N+Wt9Vbp5&NBSk!WkCB`txqy)sI#gwHp)Z$jR' + 'UW$xNq_yHPtBPwi!1|&=&UX-$WVj5bn&bxQTA;fST1JmjF6!w9LE$IqF)JzuuVXy8ya7YU6esNTpaE}V^h@>*m?4m6RH!x8&NgO1' + 'WOl2nwJ@r!*%HJ8Ic!RfAb1u$tU8;D>I4o}1wic9oNbE=8S}cR*SjmiF6mFKBQFk;mdBFW=5aKrrh#UfTOB4<~7u#aa-Nra5{s*TcFQG1tU*fcRRVf>+k;1ugbu1+pd~n`2TiIDQr2;0v|}cYg2T@W6CjiG%Lpe-C1yLETR$yWy_3h)kdN77QGM(H=F$omDju=mi6HhVZ_J$e#ur0u@L-jUvT(t&flOcI$?xhe$pGKKw9mv!n{^n4Sh+PzoKu_n7sZ6Op*' + 'tD)Px86tIH7##$oUQVq~XU_~fSgB+5P=B6{mls=TbbTK*$ZIcZao`RjEsp*6xRHXgitUC@1mgkap!xEr>NKW8^pptd^eBq_sLDb4' + 'p;SB)N(nD|DY2OKJ?DhI<4a!L&`SsXhhc~=-I6g%CtiULT)YVvL-OCq)X%&4g' + ')gTl7?RZhTtCt_wb@^XoYUua@hp#5LEC}_p$5bCD^8xNb6BZn?C6(Y%)zjwu|JR$mN(tqsnD#fXKM8VyrVZ6s+&bk@=}9g?4-wSLvlQATQl2f8;sPk6)&9`Ji*' + 'wzC=?TK0BQ!>d1lgMr&`B|Ep`b;yt8t!H~W!FczVVCtpz32oV2s)eM' + 'H5`fmvYXMZ9Mqf%UZ7g^>9GAbOo*>}7Y6!aaKe^*^o*a?0Q@}A@*6A`li^4r2~~50lLazGRy-ZH;(TbOU-Wel6=Xz%Z9ZL1Clb-x' + '&<_g+^61_rEH8^rVZH-?1q^ZPJ!9=~stebtiP-Iw4qPFc8mQIkY=LyO_B6nr@Q?|' + '>}iX^({9Kz@+@}i#}-1$R-%wymE1M?gtIH}HhbD#+0zG@dT)v3%*!V{^!nfi^OAnE60S72lxWdQb;eyyi4F3rJ+iF+oqlpu@=&Z|' + 'o%-d5=tE&Ul-ZC0>+CRHLp0H%gROx>X->O6&B_);$D!Jp4MV&kQ^q#d(U;QEQbJ^V+>@!+UD_T|*r}~Ln*}LPqy&3Cb^GJLxW{wU' + '&O5zhBR1AU+cLR|$IsEeDXnNSck)n&dPc{5hH(T3dOHUq;%8&<31#&Dfy+uKPx@JV85p>U?nB)13JCqe{9w9zyzu@RJF5KS$ac~C' + 'qsrVT=jZ7Q|CjZIKfiGJ_tiZq@jJ@@S0rPUDoa3|>-a$KGjBHle`F-u!2zLBF!2>xtCv9fzyQb`SS8lm6Op$d%i_b06Mh+Z_(t*X!H)a|H&eJS|=_)z_GJHtP}EBp~PV$t{8uj>4JnyghZLbfww=3X7A2K%g4' + '{zg;`4hpsEvXnCpwq<+V(bZ}(w~xx9T4cXj%7Y%E<-q5}T9-U|Ir-kZZjBoxasrr;biq9kyy;k*)d4;80F5e`RoO|NTJC(zt*R4m' + 'R0Tf;PrOj|GhU|p|COf4Uuk;srJ~;P(7s^g+(Pkho*ZxLZ}~O@gIH7G9(b4JoE(&S-meMvDAQm~HDwrc1LE@6aWAK' + '2mk;8Aprku1FP' + 't!Qh$G~u1qVk;Xit;wP&S}aPf8p!jqvz_L74x(w5w!oxRmXRK_SoAby!v8F9+EUa!e9NwB_rMIuw%6D)Th-z&sQZkc{Y%D8Q>wan' + 'lbgLkMS(?kGB@2_tJQ`ZlkbGBa;~+~n~HC~Ef$M{myqA>EzcWfwRnh`)@*-)HZxhc|0l3~Kl!{^0RCZ=UpNLQfq~trWg#$ngMqOc' + 'NE_S>zJt#2!e#6uYZ@W9uu)CRv@n_e0Y2PMfl&tL%C#QWVUOdy>RIKH3|LHz%&etg{Urz=5+*Llb+^U?_b&P<}2kA>8pv3%dR$oCc9`91j!$i>A%%N*_#A%bAU&If{gmfVcJ&Q2SYNM$I2H(#6MgsLI|sOY;PzXOh{g2aA|?FO@;pO)=B$#5PzFWDTC-CqUH_qqdA{U2Qfq#`oj=l' + 'cH#sI-riD(D;L}CEg+@lY{umD42(h|V+OuE5dM8Kk+H7PmQkHXF{KNxvydJini9nQ*XkR4SW3%Gr_ul#S{%wVl?V!7EOj&Gr8p+0ZHPaKa4NbeY}n4K%wU)WZi>V_Jxz54G=bT%QejHJkH;$8IwWUxz0A*y;C?-eNt5BCYy?37pLK??U=?>?%A1s5#;gY69<0WOc)^' + 'lM5PK-bPt7i>*d5i-+)fm-1om_0AnA$jwG+r)C=W&1hf^XETSM*DCpgTOyoRG4@hCs=0BWE-mrgc#^C~9l=h<+Lp^A9(9g9V+F_8' + 'Q>8pA=Z`PdbE50n!uD`Bu4TrYgqwgaIi;E2=UOQnyQN4=@?ZFb$n^EY=Fz;!8{R0~a4UDr#nD;8@6SxtLF`GQxHO%>%hEH`xq$c?E8*7aux#mmpyTkv<<<2iRGi_uot&I*|;#g>Zptf9pgpd8QXBD^tVvK;dugO=UYzo' + 'LoUaB29c0$XR{KI2^LMpq`}`7b5_;alf2k91+^?r@b`}nu}@#+W2o&@Y^O2fn1=@&M_*->{jG$I' + 'd&9W3QB)OJiB;6ua``rz3>MWiR34nr2d9BAeR@EPmLfW&Oz=KBlgy?5oJmFOr)ExCs%KSCa($#)6f-e|YV2C$h?RQd1T&G3c(G|t' + 'up;${^ZQ8;PT9vMJH=@>6VCMrniqc$#(mjRTnw%I>FS}ky4D>Zi`%yo7I%_E{Mp0`50xWWWTvf!tp#o6WPTI4l|XMduP@daT{JDV' + '8ex~H1lV8@#~qQ7iD_%d`8awXCF{k1P)h>@6aWAK2mk;8Apo43?t*Cq007nt0024w003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*h' + 'b8TjCY-BQDZf|mJVQgu7WnW`&Z*pX5Zees~b1rasjaJ)k<2Dd|_g4&56p&C_-NX+mTzG*r?Q06OMNz;oWLg^8L?lWjsU%)3`t2QF' + 'baT9$4HCm5#WQDS&KxF+qCdn@6w6Za8bLOy=9T!wjc6p4jRJ0(dUXeR)2Kp7ZcwLD6fG8|YHG-`vNN5+ECW%ujWWQcY>Ydt7mKha' + 'yShCC*C5+|$uufI1rT#?g*^5Ef1O+Wo2#&8UmJE%>PCe_X}^lPE}N>j%@k@;beQE7+Hh&Kjre9UoE}c`vJ?_mMGtrHpMPB}7WYc=' + '^Y6-sg-y>aj%FJXqqI_4aJ$F?$%KvyZ0GgoFE9}ZdDXyNL#?hnE8;}Czn0Q#XyKUn6f%c->Bd*m{BWbm-$ReR!$' + 'YF@wt40s?U?Dzld_r6gU=R24OjMFq_u%h3D0W}sC77p^B{(w4fP6#c^t}>#go&$dsI(1NTz+sT{>BI1yYBdd)cJd%(5f`G~EN>Ib' + '2067gkVqV?Leh~O(ob!Y*-`NmuQQ#WYEhUt|5E6YlSB7C1%2WzhFtrG5D$FK4(H5vCX-ao*oF5hU&E}mY}j&dQr)N%@EIzh&6d9I' + '{0o%Vnt%Lzl3^36!4?p>#%tv#`GzG1&vCHfe?k~YDOtrvVBDo%qVY9FDzB)_^F#>rPO5W!ahan6t$w;cy|' + '3<~lR-1xn!kw;p=XWH6?X#UBGB$lB#$$+h0d#E)%yjP1~9w^x?nCIcu=AoV1;6<2IpQoJE&(?H(Ef9H!;ZI}2Ln;' + 'J=Onqh@S}CpYz8pwoN8n_BFyhHSXgkDX%K;Z8x*;6J(G1g5r2GwrQ8>Tld-{q503<' + 'jMWYOdW5kuneObk_axGq>nAL-Qc(Q=LLdeol9b-_gjN$mW1ik!-ZC1`-6UUOH&UyS%WTq%3#vat>_hzkH@N)iDwiPNFW#B65NTO8' + '+{DRyhR@r!I(r0*>Gg0SRp!OG9^~q@d*R)`y|z>&?dZ)U>aJSrboR~^@);Ek7U_1scc}AgxDR|G=bytk%rfxztx;TRGSu4Hg?sHy' + 'LsA!(s*$>MLwXn5z8ebdt1*$bTS#4`)K2yw;g9fP00|dJ82pJ#zMSaahjvA5cpJ1QY-O00;m803iVXalwy52mk=I9smF<0001HVRLkFY;AKd' + 'ZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUvG7EaCLMpaCxOzQIFd;5`OouAapO%MJu#v(FYegT-t6A1=hPkx&;~-20>f&T8%{NB&v;p' + '{`d|#q(n-xv&rTmSd%lu;mkKP9~DK>k5c@}TSabv-TW-8;x#!)*%qZ>iW9{}C)U#T7Cf0E{mdQwK}%4' + 'CX^m~)wi5dQr3+GvrGu7m@1{{Rx7(E`now2))CRTB_-R#$r?Gx3QjUA8QUM6rC$_p*}mc^R4~O373(_QIe}p*CC8Fi1qzm+rlhO}' + 'v8Pt{&HjwL>fDs#$!)z6XX``8yZWfZK@Z@DVnPl2w|BRj+xtg)|J&w=+jsQwf&Th_^TWekN;VI7^zr@MTYCR+|MubT{pR-F)Y=e4' + '>VjykZrsL?xSq~dHe4}%#noz6@FRKRit>urTqxR|>%FXEres|nD1>*<+qKSGLT>&;I@PXM1pYyuHmo6xsFsxi?tP=mniEtbs0Nt;' + 'fy0Tz5~~PW#tdr{Er;}qi!sQ+tI&;f9Ua_tm+}bIq~H(C9ASiaYD)?IkAo@)dq&(OcaR{' + '!mEx4+Ws}7DEmW}B0ikj7@`%M;}Y_UeDgXXN7>@}5J=|;95~7)8TeT?vA*4Z2k@=T|AgACy~7&8>va78J@^7Q)!uDEeFt|lV2vR@' + 'tgjS3vI7*uIoA@2(K?(UTeJits0JQ9M@`8OTu>!Vw4Lr?;8_PtaV!hZ;bSG)v(9d0SDFI$k45e0s9RijpBJYARbQfa;?O6gRop|r' + 'cz$LoDL?ehniS>1#5N^Y57uJ#nnIQ8{AK#afUYT46x5_THcJSz>X;6{{yqNL@~$j;ZmSJW8MhczI$+Fnjd8W9ibAc4$($#PfN~9a' + 't9n4XpJQ_ayvA-+b0OBz#Z^=6_fqh6V4Fh2g_f9;3T~w@lMQ%E20>k(ZaH;`Nk-`=gE~)QUFQdTEKuCMneWUII)JuH(#gZn`FVR1I^5=+i+Ip{tuu!6BA+RXEwrZ&p#;Lsduj@' + 'rX56PMhEPK_P+aeV!%LUu023%AzTabF;z0NGHzWBk5@U%CUDQ$(Cubop|(^izr?XZq|JIY;wo55XBz%+$h{a}lGj|e-dAVC3@!R{' + '-5f(v?<$T~nL?XWORBjiJ#wZQc^BJ}ZrBL&?d;L;do*ZUy}v>JCgb@gvNh{IEGf7A=rDh2dq^&YFtm#' + 'v=CscKN+G4ZoVijr>)rlkAn=;BC!|Qonk@e1FgiFn}v)=Gf8Rwo>sDM$J$6Z(z)bg1ozbgQeOL2EyiD!3(lKyEzMH' + '3;r=9cjc3o*AW<*87k?w4r@A48+;Q|+%&Q}*FeUdp$7go(aGEe(W!rFt}c81I%$9ia6F{@o>XobBVbRAEA+(vtl63Dk^grIT5jM(' + '{h}~yFoB1xXS$5yBs(-c{BK@xK)}I)n58&5eg|=c7gqiSVQ82Wwg!3&09jW4HYA8v)&9KV+}NN%MT1&Xc>{S;cDP_(3TEkw|5ch0' + 'C=9J&k!|Qb7@ZLQ*c*i2tEY=G_8xoE7&l;k9-mqV49Hm1RL7bPT7?!cQ8hT!QY{lt99_u#Nj_bHCy=*fpp>j<9ZQGM$IEs+Y&VzI' + '|IV7xl6Y~?V8}Nyyz|nF@(Yh({XTQ>@U10R%*@}VBRT^c%O-59b+}ED>PY*&Y)~pY3f1k3JGUhbjhpI8Me=;{6T--f@N4wp~O0$m>6mdGH2Kn<|Oi{%Yl6ArM?o1}+mcsKGe~u;^f*YvFi1N^cR0NP935A@=bWSOJjdC7gO^L%j>Bu;z6$' + '^tI<>UtbqJiv$xv5|aPrbENiT)!}(%iZ+Lt0{DLsh+OZ)S~qD' + 'GMdoZD?ki=y9~vj85}spqW|U3Wf0$}Bf0zprN&qf1krr4Oh2Dkzl73`pXWQ$clzVCIw6NM-OMpY^Cs$uJOV?6A>9DkEJnxu54x8L' + '%y2JroMjO?`D((byp8!B9FH_hUd35hgPtDQW_KIe#b`GoWE_vapj-7Nr{ncSgVh_Ua0hp+u2=ua<+1AdIm}kj+Rf(P=MCEll>Ij2' + 'zLmF+C64(Cp%B*L-%E{NO7fo>8' + '0tT2}bkNQ1PfTnagy!$)=>{kB(P)h>@6aWAK2mk;8' + 'Api)KkJWz(007b?001ul003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDaA9e3Wn^D%b73xUdA(YDZ`(!^|6iYCPrw1>' + 'l@hGDC|Ux_ozWPFpokr$E|5Y;&}(UJ^UQ}zE|aJ}fA`MpgZtphQd;8-w6S()X6Nyn$F4k`PJe9bj<@}q6HdQ0byMwTTUxYiL*D&-' + 'Ms6A9eakv>+q9(WOJ1z+X;H(+a#PfI^XYUtncTKbMe_W%N76heMYU~OPH0^>obmz=m`v1K%k*z`Uu}1UcBI}a2^-4kx};qP$0?#=' + 'Ns-K1SK-mG@-?bWYd(-h69PY=bN=tM_xaW3Pv;l;+c$|=egEO|>iq3RetLDea92K@|NQpNFXupyzvfrJzCU*fe!2L`+KGKNs*Y*}' + ';ONO@l3$&Dyu7%4_iO(C?CR?L!v$G_(ergvZHtmc?eu}|meK5=|Ji&KPh()%uv@Zi8_tS4FB#{o&3Ti9%X7ZlvM46A*JM+y`3jUDij3Ie_-J7n(%u@nwrfdH=NcLo1T$R&6?iyC2e=4?;xBvJ3)z2W%at*uvSJ7g;}&q@+g+@2c#;U(&s$3;`c4nl-a^=T3c2X;K24!I!Xbd#%8R;Z&Pht`rKn(!?2<~zLMM-SQk0uzPnvLefwrpXJ6plO7}a)4Q?U%w`h72%y3g@oQmUl#x-pSx@C=s' + 'Y~-=U&w5)2*uugP0F{8+ZHYNkB@(n7yF28NmH9v;T?-_l*vY&Zca%S$KhQ1Sh&+a3K+aCz5n2{^b;ZCa5Y!Ms+mY+*kJs0tUR+lrAM@(Q(TF49_Za()A!ACLNx(!-TVrPDhANoEi|UZv#wG;GlWp' + '?D;jr*&N3%3BcBN!khx=>rJG!G*pfYG}&N+)9=7DFj=i1B3p8tm~}@MmiT`SV_F0q@po1)ui74BB$i12BTkHG8Y^xsPA!Yiy7^nrKjym*g?`P374kj(2b%+' + ';+r}bNeF2a2YQn$(D-_}nk_OPci}8}zBs{dV)-`qVv0!^9;{^k=pkueie{5%d>ha=vCfW1gPoYLR6&){DmFIkq(cU{bqXmsuK0&*' + ')EELGMb=yK83ksl#>#`!gVZ>tKiPwVP!WA%)O7}N$koAQ;X4KwFxKLHY|L0`=4@_xC-u8B$P)E^5C=2jY~hj_W1#eL@GgbRW289}' + 'sJO8%Ly`DKLs~Q9seT>Em!Iq7mXbC`8' + 'LpKuXttZEAX2ekDrDWw+nC7$d*%H^%`i^1!KqnpoN9=>ci>A0VfKaq+Aa(DELG-E{oerEpipftCKC7i0rwV_{z{t0Z{I;Zb&(iIO' + '!SSILpwSRZgF#NzmGDcBigoISBQSxbU1FeVq11uW@?=0m*R(JbnHJ$;!IWowTqtREv!RkE6)}7qJZ!rgpzPqs64SCztnHX+<8n0~' + 'YRv#hS4*qEK}7PKoovZrFPQ4(V<2%bo%)b9g6*nen60XU;B$^>*6Y4)aoREtIlHK6O7US-4Xr-3w9Ieu3S10BAbm6{p6nO>6hDhZ8N~fa#Dl?!`L?9>OU_t+*9?J5uffII' + 'X|A$c2eBPQyfcpD4d1^S3??O(4Y8XI$mflmLAFGl6A$T%x1l3rrCu&D${yF^AXj*}Ah2xzmg4-C+BA|54C5NG=gHn32Ra>IwuN?NL2uW`&WY;#)g{E2b&zAxXQ' + 'mDj|82aziyF|$h7T%$+5EiE$Wstcn&bmn4kvg`EM_GAZY8`6-~&_=(VMT~' + 'uqx+YF4V_;;i41-Cz(g-e}pC+p!k0A!>iDsgIBWePQU*)Fz}eIKhrnNUlphbnE)l?%})(?`pX{g%N-u?`I~}g|H45Gng06llrARY' + '@xI&K%_E5cM(lNY=T95Vm=yl*z@Oq`$5*Q7iWcP)h>@6aWAK2mk;8ApjNii`WAN001}-001li003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQD' + 'aA9(DX>MmOaCxm)-*4MC5PtVx!D%lNU<=!x1_KU|7R}IyHNzHk7+gc3rBlp>rZke8lXU&xcSlkZ6)RagR1b+L-W~70`|;69l3ZQ>' + 'c5}&Eu8mN)jH`;Bw6OGk686C0zLlaBmKm#oA6au!pv^(Fd6FcHMXj47D~h_aora=d;@CFYq8Zg#ZbhTaViCUGK8m(~7fnzt8!4f5' + 'f;r!pz5Y*tr*PZoC3|b}Z7-phR~GKA6#HKEa*v+z(!TaPKXb)z0rgZTWsvr&Ew_hA?-yM94g1hixZ(RdeuV0#Yb7k%4JyCA4zA{4' + 'j&&oeABxTjX$n*W6I;91ue!Phy;v+t$&F!G+*tlbS^bcXR>*x+pos!C~*`3(JsjhC?ae;9;Z$A-t_' + 'G#3WgdoDY8qjjUx2&*i^7dv`v-_0ck4i?oHC!di7)C4+whh5T_S3W)(by1YR*f{5*)C' + 'KcRj_i<6;g*y$i7Ff8N0X&{vx2}Q4nrmDbMhLOADWOBSVliiGQ!w$%RGAdPHj)6eN3G2mj(oeHVxV6y64naR3^B8FqoI}_ifH^Bu' + 'c5ps$pMM-@ZY28v$I$xPp`w`ufAF=$4zNC$NQjL0Bbz*So(g3L7hjWF@XI%*8F-<;Lkyg-$D)9U5(2q};-#}D&Fi;wxJNFS4Dt}w' + 'cVqT~s4K&>;+sojFk!l5IyccLl;U-Jp!{%qf>au@+j2_BOEwGQH~G5Hh|RsKR-9XK=R`ic3uK' + '$ptciTY*7!cjr1sINHq7HYl4P?<%2Fzc3pb!Pv(HebL;xrPovdl_^G#HY^=Ljji@s8#d`C84DnMHqCPC4e+n+@A1A5`L0vat?gt!O6kxtTOq7F2oD0H;J3+MI60kdgyqe3Pv@x%lWy;?oE9v9E_uP9bI!C#}=S?XN;9k-abCl}PWLTxK`F' + 'uzH^1jMEF8;UJ3VR{yphehO?YU8J|Z_wdfRTe6oCoiyL?bil|SR7TW@d&$+^ih62QYk;T;_h3%|3O&Z_R6NfjA~{d&Y`uB8a6vJF' + 'MaBVQhH9CUTZi8xk^BpoJPDizoKO|qFWGRT>dj;{^+0t;B4HR2pWdaN_E&8Wm-C`P#>QWcc<=Wa8i_Gk`Y*F1xZj*5vVJq7?EcdX' + 'Nl&)UY&_+o2W!;B$dlB?6>dmCW*3~JZv?R' + 'SR#22&*eMcA+uaAKTBG&Lep|ZYPRR_O{EkPsX|Dq5aA`#k}%Hr9@RoAyj(69i%NHMns`cUZb53%C0C^Li!m@Ra1$&JkLp0F-|YauWfOu-;LigMXPuIakD`1d4b$?f-%leoo49%KqW;rVhZU4MGz@gb`%w&;~HBTKV;Q|G3E>Jgs79$=nI=q4qFmD@F)q-fS=VG1N3Enk!*#x#$DJpQ4nEz6ya=f6eobjug(`2loqU}+yLT^qLL|1' + 'IB2N=U6eqcKfk|3trR=5V>Q!{2<&j^GP-sU%92P17!0Yb!OoUIGS9N$P==JIt=a}R&4V-=y-ux3zl' + '?iLkbJ(b3SkvXrt>}zyBuUgzu330y9oj6E)J=d%C=BAf7vO&Z{!1-Aaog`i)n`%qP{$zc+4K8Sz58(t$lUnSVRsq%-Y6+A1Ge{3I' + '1EezKHcQ`*6^tNm#1os_KEnzA|}??UQD' + '8%lW6JJ`q4j@*&Dqwx@^cBS+;(p_8AYk1BOY;pr5zX)!M$b7lg0NtS^*L1Z$o3E<(fB~|nZbM}u0jvy!1CX$o6cr+9Cj}`BqV&q}' + '8&ut{rjB^}s(Rs6paYODkvj&|E+3IPke0rMn3Y`tF56dlYhWM8Z{z_UIB95!c8|tKm-2hc_6M76y9<42phr(urhdmo5BSEsx_Pa%RmKdS*PC9TKC*|ENIO*c61qu#-zu4F~WMm#C-ZSq?GW@uNTEh`}?5d?sS{G#Ab)HizLHGUWU};ld8!?~kn2O+Jtyq{*>t-VyDiy@}e3L?FA}Q&i_d>lUTYw70&ad!cO)6l~Zv*Ve3L6w0)R3<`+Z_1HB5r1}|Uqwqhe' + '%=Yg|d)f3yW++f1!x03w8jjoMlgyOD)LFxFGs_|$(lk|7JQ#f!r_{q0eK!kzD1vKG%~~^T(01R4oZ^?(~z<`3x`2_jy3;s*)BQQw;W%hqNF@?0_Z7WM%0w0' + 'WoQMx)VA9b)E~L!m_IRR186Qmt<%V=KsM%oEhey=kw=y|C!1osm(wzL7qrt}XEqfMV*dhAO9KQH000080000X08i9r0-PfN03e?L' + '05Sjo0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiHa%FH~a%Ep>Zg6#UE^v9}J!^C0$c^9iD;Ql>mYk&(Kj!vws%Yyx' + '9?z_6*Y=d{%}t_Gv1p05nW0F9q`c$xIluh?^!q_d_DqsX-KOG^y3uGf8V#V)=+#Uq-L0+!bZ3F+b' + 'RnXo+byhZ2-30ThY}QMFynYCdFHhf9#bSRn7+e7eRNZHFlUHSMmo~vowXTEg^Qy?_c^fosdYd)lAS>IveF)NW5zOz>ybRK&$#2Ug' + 'l*d7xHThzl4ch8cR_4E^E#QiwX}zAe>pCrhn=FM7)DA0uD7nxe`TfRnPBu2lJ{ZG8%a!dRSyP>AGka`Meb{Wt)9&i~L%EH0#w0*xRuBMcStG0$86Z9HJ-=Zt|>HFu=Uc' + '>b9y12{x~m*LlfDLm(hH$PiwCSUzw%M_IGHLGeg{nwzR#f@}z=a1g+Mw^^Ia=ZmDs(tALW;2buwtjZTzTI9cG3#v#~l~z~ew=nC}' + 'np!0|KM6=w4O8p|yROQGTjT2^^3V@bP_`ENM?V~1ygz=M9A2HDUNX#Y&kmEltMdc(?akrQk8jS;j%}zPjt~F%C%f?C`10iK&&SEt' + 'Ietv8{`BEktAzjmbn^c2>}_&#_TlHN<@m#4{xi}S1Fle6TH=SPQcSn=ukPt-mEgjUJf' + '`Pql_pZ;`ud|_2^G*;<_L~wbSyg&Q^MErPsCJ7dOD^7`$gRApBv&7+(sOaRC0B~7yVa(1&$Lw7AX6K@Bb}pO=a6|~Tt0g|b8##X`' + 'sBPJ|pGYvZg41e|-+*o+UB^aPS8bM;8q9@(;*u-O>_eTQPJo8IHW~~D$)Are&XW)4m%#)y%;?vwuA1;54i-q`1WIl|$F&Fh(I7cK' + 'J34>Mn3J3xo*pMhhetmgj{`(H1qB=jMc%YiFcPyFV0(OmCS2^4%(;yao`YZg_j&Tu@zvGwg=mRn0rP$Zdy}={68~*ym*y)YUIQ4jF-UiH0L%rX(P(KO(h`;AOhZ#%PJ?TUKOfuv7t4' + 'JYyUgeXp|{)FoqNaJfk9I(-O&e+w?iFgADTDhq(+(TIROfFHepWEK;$P}CK0YG;pXNO}1xnpHZ_l6AhY%JU|<2gL`)*Pz{@{Hg}K' + '>*3lJ_Gz%~jWU+f@Ip7GZs)-G*FgpRWoD8HSIo4>!C6MOzpL1sfMr$Ck}}||67paYK61vT=3!|R*x#9RVzxofg7&Xlt88AaOJ3$-' + '+Y$OUjj%vvQY@o3M3`Vfx&`qzh|4yb3J^b9$8xT5b&?Yha?c{UN$22$JWLQSqL$o%l3C^L+x4o**b;|7' + 'vrSk$Y)h^*&+ih1DUwOBmsS8zNfEaqx%sJYsAP?l;MA3kY=f%bb^0kr;1ZN;s3e-jQ9!r$F1hWn1uYpXkM)z!YIgZL=|Yc08|o<' + '1-gSRfV6!aIC9Y{E{K?+iTL1=ZZ1eZLtsU?S6T$4>^}|S8WplqtB-b}~_3mt@Ex}ie4l)*i1~$~)GKx$9!(y#`S&v?rBnkaojXr}Xgut*ouOz6&r4Bt}' + '&5>t%w5cher6R>ruOF)KYRn5x67lGlc2h}flVQ~cjO?X^E>bXCqc(Xb#x0mNW{lR0geH00e*owJrdJ&m%xCeU@{4Y@IUe!s(KM(' + 'Aq*%u*r(Yf7??=JKPvvRZrbp5G#$U5%_P22Z%j%@HCltH*5GgjyOGAm4kD(!)fT*jRdkcpf}Nq1RmDa4mr-0+^^(N|0j&faL4Cdo' + '`yFX&n9_k=rmJwEMK1)J6e9>Wj1V5HRqQwL25soL5Wm2C_u>' + '4Qk29rUz&WC=e`aky1N~=up%X>yv9c_6Gc--u~X69Xfq=#9yhOfxV@Py8?n5S$0^~_B*_=%BU?WDVLV' + '<{Z924t}$R9CU=gSsWs3(RNIX7_z1LIU2>PK7?{*5nis$w0$-#Kqr7s{jnCYTRRDQKh@}@e}2=>c4qhn$;AI;|rJr04CjKDbyy{(OT;?Poc;~7n&9Hwy4' + 'vj>%Ky1ChudfZ^{K-=^3vm+xQ8V{5Qv#ozP)<-gx9PoI`?zXMVN?9JVaqvW<7aCi^Kx+twRCqLa_(|fsYoha?wS_t-4U`^_T)Ma9' + 'y$Sc0_)Rd%aPR}WEoM@WqIsAvJn`)FyjU-UlsnT3l!3>Jha7Td-=)BWfArLPqg=kw76q$d#uVXf(s~aqyXSw?WW|y)PK}toMpuSv' + 'Zqe@n2*Gt@c0~j>S+N5u#u$-OpiS+zIxQY_g?QWukyF67F>K3VYXYZKZfwjdk`X2zRv0+8!nPu5p3=CW<1A1+$r#za3fHO$QrjPo#}Srs;`luVy#{AM0~pW(5r4HKCL!|>SBiOq*Pi({R$vBOXv;YKRW5SOw1Nwi>Jl3{{|' + 'BrDRl5@l85*%JeYE!yev)sV$}H8i+)JH0&SyEnZ`VXBtPN^EoSG}2gwLxafSIOvJX4TUcQVE%(~3CYD40KLJop{(}S%7ri-2cA&b' + 'lfGgNdc~@hazN=fCM)yOcjj&eCv=m565(tEOq{X9j>wL6wArX9xTfr)*9CA649woWFosq0LU_0^H8IcJhS7#l?IzOnNNf*%G8*gm' + 'zBjVp#rW!#u@JgKrEJB3%$0adEK5ZC4G6==tQ^BeMY^miG*0Ni?q%u>Ahet&?)LagcbT3P0v`@pD#0QB#XsjmbIdB3BP<>e5n3^}' + 'R-8KNOAb^JZqB6+3wFfz-JAB*!JSCo3Z;3;f>abJ6J{I-qH&NJYuKyXrg}DT*PS;|N-NIisC7xVPn;RCp0fbtmRqd6oeAR_Yfk}<' + 'JxeI#!3$1AN=f&x<;p&EdX2=>+(}s' + '{Zc|zlPPa%QKqy<^#_EsU_!D?R{$xJ`EX5PTUxv#hm64QUYApyi5SSTh4i2-L(D1z6pG7SI0AIHWJ|BF)7mITXvWL@##QzOoypWP*(dX~jcpH+=k0bf(A!NgE*SJ}vN+7?UwsnDYxg' + 'EsRGo7BuWE-pb_-R#~lfBuKRMvc8rn@37>QIbc|DIW@Wof7TMm*3Vhmb(+Jf`D0qFv*Wt1>hNZ01n`1K#P~$|_@-Ky3vE-Wty=%G' + 'BVv#UXJ*-ARR=gPT!h%cmWyw@yfib=f!GJ$JhaZ+B!BlkT5pD0Pj*^wX4)4imS&k38J{TjS=-?KlT}KSW&FAT+ed<^vhC#dDkb(O' + 'gey(+J}YVU25BH~hmpS`7!_pc{Em5CEXq;c0PN-I$xp|ZTs29RLw9ZJ#izU#H*AjXvZYlu!ZLYFttbdY?2xnhy1;Q_;pns~-a)fV' + 'Hbrc9^dox)>}Up$R(O{}TO3ocN^7WO=Kwyk$S' + 'AW?762Ks4GtaZU02GFv<<|~P8^ge4D`Y5)^q4~|{?F3IvH9gFO^kg8Q`6ZDgykO-$V%3wTrHSCnto=||t8hrWw>V&q5W!R{c0#UZ;rQyux9{%{!fja2TaV_T`RgMP|3;ij3Ms=SsAsRM' + 'L5sFEEC7(vj4TO)54f|-))Ht2ow((7wm9YUC?_-)$hM&wt)hV(uM)Xrt()wEC_Ab^FdA{ONOQ*jLG=B`q>r2!aR)ZMSc8ULGKq;b' + '>4{9?66F2-?%g?1vIMmxiQIxD%Lwu@A(bbKiB2qRW7E$kZB{pvz1`hiYcu0j5f|CAy3ZUfp(!X_Z)fY}k-U2wQZRLQONDhS6tq&$' + 's3|%$m;#y@1jydQ-DvPdX}7qGG8Gov>N>hfjZfs3V0NIOLjZo@b)isACyUQv6nWBvy~+QfH}M?tXc{AmJE4Rk?1p$pX?I8Dq59x}' + 'MQKAU3a4VABk+&q=LhZcuQSi}&i<_Uuj$5ba8eboJ8qv$cKugRCPVQ0hVI>yeou2tFLQejv(vlm@GLvMO2#d^QD+==nbL;!C?RjM' + '+q^_s27B5oc%s2^x!445CW+9d!&+r;kUG8rnQi3($81C*0|<9LB66I3${xbJ&6borZ>HgbD$ARkF5b^G0*Z}`&u#+k%~0eIJ|g%?' + '^zt<9L@2Ia`;cLh=+=lXdJtVa2a$ID+lPw_A#69tkKe@KjQ&#PCAGwKyXc8BA;edgJSi^*!h?1V8f=6n0~k8M;S`zhd(U' + '^**0ZxwD}ws!UYnl*aDmRUakj82Lf;qv?~eTsbyh*Wp}RHQigSoxQ|VeC79X)UtB65AJ$L;I' + 'nO!!iKj0!>NEzV=*Y@5F&nkRQ0W+@da}2Ja_F{sbFP`FH-QYnX{wXr`#0|!?hMp7ZHl2Seviq!R!FpvVW=L)hKOzwuKrQC@-PK5rZOcPUO`h)QC3#M)($8-d4Bu#4`lj5G>AA6NKlRnghBUrAR7KVSH1sQA?' + 'VKal1fo}~ET50mG*Lktv<4-Wa#1gPV_3Fg9`w5n==$d0%qQlx!2gKcuH4nEA+ok;c0c^J0Jc(TvnIP$S@Fxo7dV3vFuCRWI?*U2Rgj10l&Hy{jfeJzCKS&NcA=IF}rd}Tg)oe-dFS7j@p' + '@G2SvFSL_!@RM;b=JlN;cVhvt9^L}tmOnlbLgSd?Y92Uk8!W~``w?zWXCC`8!XO6Sq$BIWi#i!6(T+Wlczl*fpKEqMP&!2V%|J&k' + 'rV@_sx-fj-27A8KfPkHcGC8qv{*nkqg`c^y(w{uR>F~L}wtn?KYe9p5=|%W2ylt<0fRt;;2HFvd&;f}HQO}DN0@&{snuqenDxLIN' + '72%3Ajox?EPmR3+8@e_lR9Hv$FrSx6_XKUl|E*_oi^(#g8le(Pyh$p(JM?>Gjpe>|C;lo0KGDS83#QBcME~N+gYKCg{+I@=SDYTz' + 'Y2@6WF@TuZ4f-SwZ=<7xK2r2dT~gOWg%Z`1eb)>H&h$&B7Lk+0jaW@#~6zwI)5dTC@$kWQ1' + ';`zQK`Jk75q6*k3Us!+MV#Z3uJ#_kFy*0qu!w?95@VURK@nTol=L5vu_CGcrVM5mSu!L{9RXy{NgyA>%ODNHh5l!7L0wWpclLr$y' + 'sEK-JJ#MGZjC$$`XNF}&`ZGscJHZ4ZcQC5Gj4~`zSp6KW^|A4a}!39z4g#o' + 'I^|>uEy)>Sgmt5tP}QCx@Y7p%V{^mwtEtr!Mz}td^P*8Fhu~ap?YL;*W7f=^e7oBlRb{pwxOWTq8K*%*6#OALFsH1izp?9lbP_Q3' + '+couY)WkntZ!k=HCvsSUo(6#7NZT-1hPZ2nreVHjRa$1Fc_7VHEUQ~DIaJ~7yj' + 'M$LK|?(AuTU1lj@Z#hZzZBx~#m^oe;)#tpKXs6Xhh3_H~^t>w8%QB(8dKhv<*!SKn4z#bk=GXmMWTBI@@@T$XhlP?4I)l9#&RnXV' + '2G5qJ9?4J&$edJhWt5ZSi|Kjiocz|zve!hpT!Xv{}ZBne2vLDYqS{iVH%bhdh^6Z@JT64gkzAkM=dG4Fgb1yMSQza_%Thwvl1@Vlnnuj>3Z' + '$HTmjQ*MRG$j|tVvYXbb+MS{2!CJAd@kazKpnV$%5heH-6Z0;LyiAMR5f+44Ws*b~JD{n~1ZP9VTAB^j)%TNNFWdPp0tSQ{PEpJw' + 'u1!v1Y{_Fis?;F`D9wzcTHnSUEJQlhDrek@TeI<+Rj=5HjY2uZ;CGhRljHLuace9cmC`XHQM1Ty>kL*tIfru~nm%3AKl37Ag=`#g' + 'u(Jmu5s57m=n<(r0FfzrTnJ!uR1~2xso5$^nPU{{(u68f7d)CRy}$(6pBakIpyjv0-()YpHN*l?vT?)Q@Bh0v3K6%nn0Lf?rTu|{' + 'KAp#W99?s&&+3rlLi5jrN}!H|cRbIJy_;U38loc*8VJ6#eQE(j^033)WcNgVBG-k(24-l)I_1)kN1Bl^H#$dHkfeal?E)ktn}g6$pvg=vkVSaXvmT>J!Q!WbE+4bjCP(Avp3zoVFFSHn4uV(Vm#$wUn~wPV4$sAA9)HhibVa|ZrG8ig^9typR(_nB&l!z7bVj{Qq2#k;!h!-l3u(u)1wCSiiENfSQ?LKYw~|4jlo_fp' + 'aEOdVH|7cHMV>c}X<6K~#w+`@9LSxuO9>Q~1H)`?Op#`K8!%kvC3j~DE~3zzLEEZg8{5V@U' + 'wP!Txo=zZQKk~+BrG60WZy*!4JP9*nD{}BxNBKMl3Y$b*' + 'Co_%RJuqYa#F#8vxn^RMxocqJxn0wJCvK_r%M}3noI1|uofZ&2v28PW$sR^k{VApriRhx`i_d!zCL#Jp*n?Cf*N=7IgO%$ke$Wm*' + 'b5T+wV2+dlw9kPwl6uyuKYK&spPK#8`hSOx(X_a>KDvYekPi44Ky%vL61YWU(W3~08KM^PbkSAX&zGx1v>6M;sjZ{N!Gm=`_`Uj;qmWc60##_-pw8@8SRd`P>d)TTC(uh@3OwXQJQV61|JsK`Reb{Sb{mE6oHlLq$e&5ieXeX`+5qv)S^Z' + 'Mn?q4PDsRiXZ(`-Si9{AeHy798klxjfw{>%wPttw`v5P@%g3RJa&zO5^rYn*u6{Gsh@RPtX4@No<~Wd7Q^T8{Iggr&|A3Dpx^pAB' + '5>Ab4X0yn5pHRD(0Hqt!mi>#Ok5=O+LhPBPpUSdRjtP-v(NIqLFWhe_r2V1BjX}<3`WgAyh#v>u`|jZPul1B$ij?aHQk}wmNb?$v' + ';SLiIn3qPki1@?GVl0MH1feJ*^XVS-GubOZ3I>TLPXDcJ?oTzu2MsD7*p57Zz_hIr^EVBnfwef%3fqby*H=E12@tCKf&YBBbC+l&' + '?<)Jw@#rki((WFstc;Dkm@+ZZTkj=0db+b!^h)b85tVOwAt{y_2+GyZ*=4)e-V>KhU7mXE=u*!qq1^ZF(iX%zuIhaBV0e~DBXPep!' + '06-o-|EsIDq1h8un*|yq-rE(GIvnfvd;M2`6xL;ymJZSpmWGRbInmSghNLZFb(E}S+I*r11#qY!xJ^>zpE6{zxh0`IEAcRZY47CD' + '3TT9_5Tb>R>_5cvQP;^F#*)=z!@_aP2rCs~#sLWy%<%qbR~b#KO4fnMB-ca|GpOIaHnMk(emd;x8&DqW1~!>0dmV69!CT$Bz71Bz' + '8Y*CUyaE2gbOksc!Kr%eJS&VrGV(+K%1AO`ugCRuWTJy9)wtV~w{66UT_He?xcig<`jd^3h9DPg7(kGf4<8y+t7ylV1RMxGeTN2`ygU{PgF`Av%f' + 'viThsXQ1#F-0er5QvT47=MiUh*@Cah_52f3C;vhWBKy!@X!UFVf`-Q);EG?dVT8N)e_g7n>E&JaK?7)grXh)Q3*q' + '<%zw$O<6X>m?uB5v1;C2^XZGR%J&UjwZy{@t8}ptQMR7jqh=A4mKL9XLqh@+d^ehBc@b)9H^0MZ6u27)`!eg9?Q;+I+35>(%!2VM' + 'rBHO>{%q)EIQ0Hxom0-9O`h!9kN48etV#0ccK>j?1{>+r)' + 'MOW(Rh7%F7nH8sghI`Djh`8)*xNhDhIi);nP?9{*)~T_rD#Z&I-Pbm-nfO(4' + 't*LD3;|I3kKPtJY$?sJQo|fdH?se4N53T;KDI*SZpS7n2I;e@(0=#LF-?3dk;swsF*)1Bf1e9%wvu=T!_do5V0g>h+1flbJDh@D}' + 'VFJC`K!;+JYyq-D1CA!i0#mOw2*Om!_mg0kSu{J3#(X@K47rI`D18Y?-!=is`LY1@0dIP7L9P>p1GCw9Ax&dGM|H&VpLu!YV4++v' + 'i6SfUW&K3v(yHWX_xTJdr`9NR{uy_k#BC>a&q)ccd*A7!FH9mEShuN+jiClF0Q3t;U9eOuH;M*oB32V_r0pYkp&zFP*h~v)#KEU5' + 'Tj57~tXwsn{!NjAJli!7p&xa#MQdO<_Kl0GqS%Dyv)t7}2!Yj#EupSPtk!NLC4}lbX|#SFcqo@AF1)Tr1faLNZDGHN4}C+6k4%H%' + '>+WUq*L9Y`9>Rzp5oH|#B&{R4qMf!vtUFr?0KFwFQy~hn9p89}@{Iv2_Tn2q&XAV*3qw#}l3~8p3C&^CWXtO!qm*)txfL1s9_#WN' + 'H;b_7k1tQ(;i-!_rwMo;Y5gF>NIW%F9OO*=o~>pgbx^~P@oa-4{W6p|c<@~)v+fUxz_Zqu_Xy26&JnkeA{GDR;Q$%>;QD8q@T8oj' + '*gO@{4nOP{l{ynR4*Ver&qXVxf?mBcXU2K)l~O)I;C;6rtlMeksh5MPC*<3mh@vebLba+etRTphNH(OiP!J&225pF#9?zCjB@z_1' + 'q4y*Tx%pB9g;%urU7}6@O>Gyxx(dk!ygR(=p#I~YS>gK2_@3Xq4oGF|`3|4~~' + 'XzZo4Fd9v1I(J+l!sAgh(uf!T$nKO9KQH000080000X03^a^vndS#0GcxZ04@Lk' + '0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiHa&Kd0b8~NUE^v9>8rzcFw)I_KfwB*!5}M;y(GQa(!^j3YQrYmH(7q;olDJ&6iq}nlO-_6yFJ8U;hPU;Xr0Gv=WPQc*' + 'BuO@#)5tqgmZwQhBP&bNbORqHpy|0(7{z8|*7~U%?g$m6AMBFkV|8PYRJ?6jrHG{ivVP1_vp' + '?iGC18}bg(*!5@qomoZ-?CNXQ3qG2SAL=frljL?27YwO66Ws}=QC7SM>2z##^t7DTWmVNn$c_VdQ%grU(+w@Vbqv~#=X(7+CZ<*{' + 'j~ktsGsmfE>ov*}50xiIQ5j;1LaUjTP48iPenXr70+`5%-FF8R;1UbSaTfl|SI;tFpl(i0rL>)qtC35FDB)~IUdeH)keaKJ)s52b' + 'g@?7{HETgFr6C(Xo)nDLs2#~}GD6!1%o5F9-oR?p_pAm>8WG6{kx4n7e-}mMX(I$k*reoVw0C1v04s9ni&%z1x%^Scr' + '0CDu1fpZDTD0twBjEq({%3^zMgDvY=uYHODj;UoL-<1Bnq*${3SLYomQ1m0~i`V1Cwi~rX%2xx3%qZ*?pk})bfj>$#{' + 'Jo$_M23k1mEJ}dg&C4M6z2GHfBUQ#5dqEwP&|(-(mZ_AT8@PbzAZ|`9gt=4E(A$SS&jN`78q!rCO(w' + 'b)m#EwZ39Gi!+&Rlq~Mmwr1^mnX?4*dx1ENVIl!eUFGVB`cp>#7%FADj`W-w|ri|YRoNYQpRBLtzN!>EIwdjfU<' + 'wElhqcSNYYTf{#;Cr)pLPk-)o_e9^25l9Ik2rO&^w8)MRf$Xd$6C45AX$HP3Nc{ePigfg(tgOHChMXc`4*fdKW%F3G>=27PX$Z?3LRlb@39)_&e4TTk6^G!}R@$dL-DQEDrkrrtk2C|wH7jub9P&4vyvTLS5nS>XE<*lZlA' + '?53~TZ8isJ1{ErUI+$5{X60#W0WXqOi@4jYp>1$KB_9l1AIlHw+K;n9pa3Z%!cJ$?szm{RGq(@~gd!UBQ5=RfXS!Pt1Ndv<`|dsP' + 'n$SU;M-U7bHsd47G-tC+Jnx!?iE;-i;W?3RSH6TWn>lV7LpPL|8?3!U!OPostTel+`G2=0QrW%l2s;0zh8^x!*u=O{qUyLIk(;%!Tt~)l!paQQ|' + '99IRrz}8B@VYGK_3Z?;~5u=@^fQagbbrhO9NsmbAp@a1kX9UgQl1GZ0$)FU6jn`BPDfi~mnA1?(+<1}HLEN|fc1zOVq&dr8s' + 'Agjn0qy$oxNHp!CD{7zo$VP6NGPmY4vTZ2I8saR!9%)xD`TrFp>yZEE!sR3OiKOOC6At$YNDRM*c$@2+ig7L!~ebTPk~@0-V7IWr-?C!sHpY>D#bKF?ZVBh45jaiv4BNPiz&d9#s1S8O+H^8C#{0=IL1{@;L58=n}p*R$Ih|cR2' + 'ZAL6zIk7JPyz;~d)l~WBw6QpoN5igXm0cCWNL7}t)2*w{f>OX5uAya}<AhN-dc(Z#JwY@mQ(3OqKh#$oZs!}ZRzIMrD(1@xEdFP?CLSrt&L?a(' + 'mSmDjOyXEuIV%E&l@HsP#_L0FKfrvpaRsFO%#' + '9bzuMlzxFd2v8+dn(awVN6Lyqs@|!Qy-^6rTh*ATl`wGIDSPS;ZfD%VpxciZaMNta_ZOvpA&&WXwh}{>7fv4X*5HKKcQ0ID=Xf`e' + 'xAQbFVgsV{1@`QI?;DVyKM79p%OcBL?{n=KT!{uAe+5Vl1)0KID12cS)fTx7cJnPJ-~`vLRi-TFe#I$}9g6ICs!Y$FtazY+fD#dU' + '6ma6o)`wu!)!PATDfuU%CS0h^kydi{B-0Is^>#s^gQHT8sS;I=Z&VF9X0n7#GVNaX6pPCv8I(Z0-Nq`Y?uYmzqQfmsx' + '4Zi(x@-<)pV_FkF^S2^t2^$i?ZlT(#^eDp&Y4)qi%O4Oe$F66}=rxYh1C_Lje*V^Spi6V5=DGUMy7(jBNb2UH#6IAE8z+9Vad!N#*g_Lt2Y@s6N%96^Dve-`c=`zARAT+6J36u+1QMUn!oR*ajgwk' + 'x(s|sbkem|g}-kVyz' + 'I(h<(KkUx}8``>-_F=y;T&d|>qPDy!sJ4VL2<)hMTi7f1iCTm7iUUR7tKHOgp&nJ3ly>|RHvQ7C#NSYfzqvNIgzc^Y$41}mVJY$IfJQrB&vb3A~f0rmXZbGZqQSNhLg+_pq5`Z' + 'QY;S+v>EmDr6?8EtGmMe<$3Vtrk*f4)}W!q<-z9iq<+Idw_Tia8~zC|uj;h=zm`uwn?LqbSJutxGzpBUTA)H#GzIzMY_`RLGt?S&' + '@?hEpM$E5JV!M8;{rkv~qoRA=>8SE+QATORS-cv}%te_e+x72KGz=EX`Zoyyr#V>TfU$2I`JsskT83WpmLF9&DL~b#2f?2>m;+nA' + 'syy8+&1Q`Qir-{Ru&~UQ^TAo;pTj*xJpP0cr+W_Q%coC&zHvWT#h)F1t{~6NL05nej6_%PjE?PH$!;5vBaQ+9;2R3cd8~`7jLXI&' + '7c7S0*YIG>&*p}}j@4KUf5n?hE@@kQfqjUmirEA4x2?5vlO!Wk-^e0?cK;gE2mW*!dfjW-66agOd%M`^c+3Bz0J7*-6Pjq~^tQ8#' + 'ZkujPPlNQA?4Ewy`%c<#?|eunFNIDU}ZnGg52pkB;Y4-WoF-yhz`oMth7)qQr)OQ<7iD48A{$GUu@=w6HG' + 'v(KD}S9>l*74B0UD2!FbdMSQv6}yV)>iX~;EQ@6aWAK2mk;8ApooywI265001}m' + '001%o003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDaB^>SWod3-V`yP%ZZ2?n?R{yJ+s3u%_xTmLbk#K}W2jlYBy@Ny' + 'qmk|CC0ZiMiBqg87$iUrG9W+$;4D^@-~P_hI~pKoBs;#Zo~k4fKrg4ePoJgF(u2X^RaG`=Rva!a5' + 't1Qm5pW`Mgi(pw+!8dQ7e_Q6s$#^gr92_jGavem`a@%aHG>U?3y(y~(supEKb?Soy@h&Unuba3kvSL-s=Xq8(S(A?IrrIvBp&+h<' + 'x&91L=9zq2*Q-suxN;t&V!Phl$(QtijT9UCswu0*rGR%)=6Sjx=*IDUA%T7#Z#Do{7`#b;+NQ-K<+;OQ*++60_T$XtSkNlyzLpbF7vqF@Y(}caw<2scEszp7wOQ?w)XXSX4=q{c<`8Lbb{=&Di' + '^!c-I&*LUtm52=ZQ0CipK|h}os=$wXG*_ohWUa9EBEq8FQoYDFcjFqS2T)G|Qx@0h?Vq-J9tMAY`nF0_PvLQr)lCd5r{*1Bgu#aY' + 'tAE*sMajSM#s|`@+*#Z{guDL9WsGR>3_DB@hVr#ke){ATzZQ`mnP<40^z@JxtdHVEi^d$QB?ELM^*U_8v' + 'v%h>3(wFC0>F*Wnv$WzjuX$?up(?jUB3fP$ven_i$nCj+^+S?`>rqxoRsfFHHU!W?eitpPc%4Q~$>nP9V+BaJVX~)MB0|N*J^jhj' + '8h0f@!}W;fcf4Q))OM?Gn=G#**v=Y2+~qH;C)?!`R`THB{7+}kUPRBHM&~bIym=c;gTWb4)q^Bm2H0VAmEPe9vZBGC!|OQTru8IX' + '@&njY!p?yY@G1uspFx$2;9tRuvPhvV{BIN-{tp$O(C~q1!;}Ltgi#c*w*kZOBaA<;?gDHnh?fn#{XcJBzBokOfytTYsm%z8_G5Dl' + 'K>~fj$bU`?*wRA-ssQ-Ka5Un@K5&*x!76d@LD*oa2r`?t)MQ-b~D{>g{?cS{zHd2i?t)6xB4#gj~3G;xkTrXV+t1QbYoVJu3k5qs5#z>N#)97yRFO%>U0zAOm??teVS7u*}Q2' + 'fk`P3fAST7PBP%NRh;XOhbQCX&re}*t4;6>!3fR|=V=oMCLN(2d>#Tww1nq}alW~yAl4v#}X-4yufx}LtRwm?=s3y#yn-;JpMaeWzYQlsxAi1QML2EFsZXcnN^w5njMH0dxa05h^=' + 'I^f+lFm`)EG&@ED797ItVi$6JP9a&QRV2OzY@Hf&DH}I^E%DNVnLTYW)t_ngJ^)kfoVW6u~OQKpR8m8*aB?vA7Ensc8' + 'RW6r6JgGG`Mq?iYkEr>w9%|$th215AB$*|3b-6i=%7^>a#+OcQV@xl$>sNOmjm0^L1R>+zaAZ;zSjf+aya;rBB*sj%gx`ho6_Avl' + 'v(1nuSiY=@vI5bcej5WRb=HHa3(*{0G_gyBIDq__cMYf&W0a-N@F@&P)|l9GiD)ECCfFaah8jeG6uC{BaMzNKd}6f>%%=cJi{8~=' + '2RYKq<`Ow4DjTpRsNGLd?q&5N;3?;wm@S!wXCBdgX#pxCMEMgwJ;uxI{Kvf2(9_(>yC=+' + 'o(ijCcQ0~+1*?SR#{z2Fo&NrY97J>|%NU~rK6sd}H_aVkN3k-xRi%Y+K;VuQjxf{0?)?QlOsxmDCCmmWM5Gf2NtrUU1eKwQnL+ZJ' + 'ubC6qg!Qa(4>US!?hqsv~uQ63ZsTw5{*!>qodyBYWk^q$V^efE?u<$yruO8Akv`jl5q)mJ^or6+ubfP3%YJ>CvPcP62@=tC7K7z>g' + '<>#^$fvg}oSEPk3$|Uu-F&M};27mbe^MP)FT7Us8vqVfi)CQcX&DyGWl8H`F-0FmhQ-h$*^v|)vpXpTL(gil4NQz+aEcB5=R3j!;' + 'fRTMfUccpjCAEh{(-5TF)~6N0K(92jE-JPlYpPmI|k6p9;#iwc1~R6rx~Of@a=S&Y?b%p*zTrnWaBl8USV' + '!QTW)BbLz>z_z0USQ=)26Rqh$^>HJ~&Yi1RnKNOg=&P1hFX{Q2XGLvhWD^#efO)ixX~2)YCxt!a#pEPP&=AsN49rr#8}*5hZ_2gaQ#6#Dm}RwklLEodP*N|e3Kk_-' + 'Ttw_Nt(wWMv=(J!i1Qv7F7(s&u!AJ)_j)$dalhx0TLN2tsZ*l@&~pOhpNF4fY3zEv#pEgg*1=_amXM3t(%fzB?|A`pEDrHOSkOY+ojEIp1UXxm&WsT*kM' + 'A*c~eJkL{G|S' + 'gtdS^|B(=Aox+MF>gY?X7%*pCl?cz`*c_lY-Xb^l7Fy6I){#0u*fug4sA7-6_-s(RNq}NL6k*X{mB9nBs4lk^PD;CoL3IGb|AE^u' + '0M1+8+32tLNlN-yj2av&j@oc04;a;|K}R@L=dc-HIl4L@GFw`BmBxuEnk6HP`-_cGpk$(_UMPAB97j}wPvPK|p{o9nc{GyaJulPkNLGh79h(>|&9JRTQ5}sS2vR2D*$QQGti%$Jl?F4hC?yq&2x1t68vfoqe' + 'ajMOCe`U7(TByvHU>mTn1Ka*$LEB-s=IK~(h_hx-45c*eK3&mTV9AbfT+l+&!=`!Bt&4ed%?B}XOGLMu3cIfC0olpRX%rJU*?|vjSUgu_d*U+A)?6CZI?`+JL$IQ9}PCn&pBYRi8g1OI1WM#65#EP-E*jOUtaP8`ZRdQNNg^=0CtU' + 'Z15;jTM~i%;yYV1xxy<{-Qp%#moOx3sWf^}(hfX0r%A><8Wxkv2BJNOzA5Y)oPS{LK?#OvOJRF)M76_&X$Ge)kSz>GopK?Fw?jwh' + 'Ll2Q5+aX_Ni>uZkgZW+X?5VYod#smL$}&jZW1JmQpB{b7LM1@{@tx}Y{g2~d(9zJ}(0H$=Z4$imPR;vWVxo|zr_KOp3Wf_?B7upn' + 'Qe;g;xQG^z8i9|5%|e(>Bm=L%4zNK!HmE?#6&#d~$i`jT!aBYk`b8FxL;*vc=J9%-2ErOrYEip2sE?^ViSy!?>eQ(=?NFI^s7pIlrF*GKUKME^*JwdznX(6Grh8-1bU%dZ' + ')RFF|AMMnQc+?%rQSYJczc{#H{5CD>(omBg?XBimXV+bYKBCsueV}cRrBf~IKI-lQzUw@BS9c@R(XqV@nh21u@o^Za7h|KMQn0ko' + 'D$o^>3sBTF6ri}GV{F}+(TXk7yP$;DNJf+1?I%oO=Bx2yvmK7cR' + '|1}Mc{j7RFv6C3@IfVoTmoVXZ8qDJc17g@?4(6^kLE5S?=xlR2c;^TDy}!kn`$uKE0`F)ho' + '?b3SQMFY7KEv-xJ>~z?=Iqh$N#i`=?oHr&r(2V@QNQbwxwiWgG`sJ;shPFaY{xVIw)417vT`vS3`lE@wA&ve_2|O!' + 'ew2Gn3j$ppdTk#|ZnDq8vTN5Ef^FZHv3k`HyEcna&AVfamio7gQShVgmF14jVhlj+%eXwemxH+1%1)N@Q#L!}HhXR}}UjTT=JV@OPB(rOulpiS(&%oE)JyB;gA' + '7-d+igF`)Rlmmc~Bbi&`k5WXXD5pqm=-KTOgk4K7ohH3){H{8?{C@s(o;{P`%=VO5AO%1EC{gOiABC;BHvAsg&DIip+DB)8w+CD+' + 'KFP|G9?SO{c`na$TgAhqNVqXT7Wo&>sm~H^_eG_#wb2+cC#qqkvfJTc08)(`?4-JgcC|V{!~dYe`YVz!X?LH|cx7fn8m>Wo$)_MD' + '0qRt7#P-hVY#;+?27G+cG8t39#VQ@N=;vEYo3fyR9jN@x||zBb7KDfZFI_x=x$Kp' + 'j!4&C!GJQre}^mk!`5KgUIZNVAYqbHL5z~@tOjDvWAM(piolV==-0G$xAJUH+jP7lWaEHlTQq8)pNS!ytD`m`yJ5v2inbbgFR@GY#72E+x`=It}Ty@ZlqHW>XQ1*CtRp!AOrm@Y9Sqv;9AQEdyTf;nV*P^E2l' + 'FwejxMjD)^9dDh9N1st3ecNaZsXwQ0LOS3_i3@$p>j0?y|x0qf#E~lQ|f(k{roP2bP*{i{2t#@t(' + 'XI0S9S+l8AjLiBza2<+UsKvG1*KV&Sl36wI&ETR3-3-(aGpk;>ojeFd@SvdUkR+L_3|_y42i>lqqi+0MLc8Vo4dtqO_7cSd7{z#*' + 'f?7Lds6ncN$zWN^Ys7PppPaBR>LqlMU$p&EiIp)6Q{#8od2%szVz>74$}}>$DMMR#N;m)V_8e^Oajtd77Bf0gxrQwUnhNJUHhOj&' + '(5E2ZqnSt^N1@QRjxmKaA6l&eCXi*VnC}iX7z7PzX3@RkJ61%u1*~GRN)u^s+ZK(d$??3rH3ImnbY0?+^0uPvyJ)q60--vD3~tKe' + 'Q)TgJF0Bdnroq5%?y78c*|gABmLFS*pDhJXCp6q*D3!5MhNE^nHx)W?LD7*}51LP}hDW2$?Se5lAoj(oV$Ym{G96Smwdo;Y?s9y|Yu@^)#+M0`}-aC#k!CNB*3C)~8)E+&z-}DfhZu1fSgb' + 'g5zwG=!06*HQbE^c25yvas9R(V>UHhMuJ3{bQwBAFwODE*_W9(<&lQ5ipD_#r4FZL$_KsPn1cz=QooOe#nSB>?HCb9ND;+*q)AsW' + '5jFaxN>fx2#eLG{dleQ!4nkEL%*W)W_NX9h38+<e0S98*rM4@H`N(^Fl%Xfz_dw>q%Rix*~KtA_Kx9deBWK{' + 'dj9U%zpbQe!PFvm#s_Wgkuszw{>OG?w`5{}#L~n3o7=tl{j@~lzVaGMCC~Zg@Q)pnLyzk7fX9w)926Z4w$Bq!kLC$qqQoSa7H>=fn++tjc)H?q)zEOoRkgZMIIuc$cvz{KF?9)H3$47&K6fV*I' + '@m}5YX_)HYJc6271O<)VvkQyKu+8%}%EF*!%xCj_+iIG$O#>c%twXPDE02f=rt_X<(6vh91rVOpV&' + 'y{O`SIls&cF1oUTI2%qF#{o!rYXKYbImbim3o$4gdK}YM&VtR5sAI^C8AErdt@jd@yeR+Yv|lRX0gZ<*h0m2R5rbOJfb=VB}r8At=qmIm{lm^yu7gNb|' + 'rB8CuRB=(W*?=@c|A8-$6TvZx)y9ECE2O=CWL&f2_QAE_l}9eJ$B4E9C7u)PuaGOQrGGh6Hv=c6?*h8~}q9!2>vK`I?904c`%dEmILnL-qs?' + 'fAMmRE5JfDhiv' + '-I6AD;^mHTEQPvpfQXgo?dHF=jW(~5hQk{1L(yd1ltbQD)H_StUC5&v_g-@;a7L{2zR?jB6N~lCU@lKRbs-_$b}KMyXWYZcbGw}t' + 'MX9J$CnCO3*V-xfGC@Fuk6Am%yaO}Oyt5s~UG#Efz|40RmbZ5+VUXzu^(7Tl#0YU~ncV_9u4pqMIXr{Ef{O%v6=BxMlL)OS8)JBh' + '7que&jb!E;5$L{RQSaYoN(+T!^=$BRaKRZZ@G`pT@o0oM`=#m6!d5#hWWyrq&4{)2^fpFMDb5bb40*-H' + 'SJwcF?PPT0n}i3Cf++!@-LTjB>tXF{C3K7j>M~1Cn~1oh#UW{@{q!c1*9hE`lY}jwNAbJGqEsh44poS_xybD&7ci&a3>f%@`' + 'YZ#jGI(8T<6L+Lt7V$Qy8MKQ2Hb{(n30W8b89C}CT%QDQ(lprC@rn!Cd^r$_V8zdA|3`f3h()Z%3M`RP~l)8BvjW%^b8)zRv(;%L|zs8#Q5g0VZBVD`6dF1En4Mw3m|p2f7hNNi-0d?Mp=m#2n#D{F}WnG#InF57wM)6zWd8RdL`Ws7~3&?R1n2b' + 'zLPkv<|yOCb`mwZlKbFot0-51vb0@cDucLD-VJDXXZNk^6nXhLkO6>rsp`1xPmdv)!|FP%u0W9k=uo+<(zq6&Ks%Ii8zV7mn5V_1' + '^ycUIKibP}C;ghCs>^kXxoAP@L;vgo6M7HjM2~Q$HBsbzvvhq*W!@3*9({qJLY{dPMHFAj6zN%YgXhpm#z*3KDqm%{l#D$osZbHT' + 'e*WgEC%`YqEaD`%!9v`mII}mHE*7^62V2ELl9N7cAPO`oKmlNf!Oi1Vn-Ycm86p1D{L?0YZ8+@jo=q#gUph8pdpCGGo2jf#WM;L!' + 'o+wam@ZCTNRvdjL7?0?MSgm3@cwN*%ZzOm>t2=+fytH#_)Fj9q9R$H({XY+uH`UxOW-8gp9;kbk=@d4txhOB$l@vSRs;dgDAoKY-Tdai~h@4ZD+l)(e#Lt' + 'oy?JLCV11;A{G~2E#qrG+(8S-f|NAJGCELSI2s?>TC)YFOG&spT9ZI?_n?9e_VO+lJ?$|OA=)Y#Z|A@YuatB__vE$Qdwr?RJA7Sh' + '+dTw0WDcyBuVk=X$#=T4h>M8KN+bJ;PHI>>Ho+kJHtHRV-GjN@Iu!>CTtcX;eZskEI%&4hlf(jI?T6{dPDxa5U!' + 'd0LKoy!GZz@KsY8H0`CENw*WnC%W$(' + '&2*0%K!P0AKsp&1eXXG+@LC1NuH%ohJK9njUV^e9MJkxsMnmQ+YcF!}`)3v8BEvR7S2outn5q1VTwf1sB#sd=@XDeV*O;brCRM4@rhxjmVwuArK3<;mu' + '_fC=#>1u87C(?#_!C*f48VXE7B1TBE$YYrz?Z65BA`CUsplbQ){7+}kUPMn`e{=Td=(vUrOI!qZY$Iy|SN;IQLYM=)(#=BfJAZRABE@`KGqFT;2ZuE)lu=|FL&;m=6#9g5Fge2IFi9q!azB&?WABu*9ce)ci' + 'S=w;!5fTB5(p2Z^p55_Y_W?!9z~$A0XRdO4Ayh%jp56+%S4RQUU_qj`e|ka-26?=hXDgh0(X!K(leZzOC9*6rR3~fH>bA&$5)O^tW-eeCqc+6qP|R5s-li>fMzn@k1O+*b' + '%x!NtqbSCFTcri+dn$N9gQ}jl%ER1lS-aiFyd#{@a;u@a&hV4g1IG#>IG34}Fj0Py$6>cj=80ZK-T3J&txA(rErpEdKj2_~v$#a@h7e1`*!9P8Vr*P4D9P' + '{lHxi4az|`v+*2jn4q?EguoK(J!M|t7Qv!}u3AN$3D((IdV4h6O`gu|tt7j`CS4n21+6K_f*cc6emu1#m2MhS*V@T%3E-Hz#gzbM_@NZgcj(b4N2O**#&b$ATPl|bhwS|||?o+QG?o&Y`-' + 'HH^-iFWYsg-Pc7(dp1Z94kN~$Xxa}(qI~-*q^~=!3Zkj*bh0oaf4sd@!l#LtNYO=i7P5UtZR6cIFN;<0h+n%SRzvk<_}PW$#1?F{' + 'l{jKH`H4Dr3E~A{NgZ+~2hP3_iYuko8x#O*7-qamjr;(hETg1B8WX1OjK' + 'zWXp1v9*VI(#WBKMr%whprnY6W<6VL)>_xT754M>Yli{mQ|?I>tO_z$N_*tGuNfwh2_)zwME_UtYE^;uAAxubWnDRwLV70Sn<9!ABHo=`GHcH)P;{ie7XBUgi|fNma_df(NS' + '=g-p2jng)mx4N~@Mx8`C3SarWA}UIwYTs?aI~p?F(nU%g%1Z1`V8oHj%Jrsy&JaPN!epVVw61mk1V;GKc+Afs&CXHDAzNpfJC|FqYu4DeeNUmqs(he3cTwxiu&r' + 'VhNk~5EB#`2stKjW`lJc{S-&*6xGwg1rPEZWTRDC-3?nO{!_cWOTICIXFc_ttk5|LwoFARP2Ch8pThraxR`3&8HiXX7o%2MT2Exs' + 'V9kcXJsTdZ@^T*M=voJ3haXliFmsW&6CC$FI-zyKKKRGZKHfW57?5ED@8RY-7PD%~w6J#?bVfeQ(!av55L&>)Ur262u^e~0-7Xw_Of' + '&q~eM!t?BDbpGnu76(-SUJmo' + 'k;YwQ1hM7@_eIS&{-RqaQ3g;85#u>|gbg0=$_HWI;AHTz&`m%=0Dc^7a|G^t#Z9`YyYGonU8_>@svzCMc$N&-)^87;AzSYx@#AoHilIKAX}HT72+Z|dGtuPP@|)pp=cyzZG3k%39e}{S0VhsLz`Mn-bfmXQUAD-S(pGfag_!a' + '@4eS$;TUXX0(x>X0Ha6hDS777v?~`(&=bix!)IACSv{&vcV&?TFCL$tJw8A1wQJ-eMjNXC%d8E_Zc8wX4XP0jZD%F{cG`J8joF!*' + '#@K<+{IoZKeYzV$yD2w5XhQmrwQdi@X1@HKk3|K~' + 'xo3w|>-fw4x{SIT$IBmmml3o`k-rH8Hfi>C$srW6Xfg)rbQ3|&1#{P!wzo}uhrTh;%er1|pw2kit~Vm?$e^rNbI9d5LS!mx@zLj)' + 'jZ1B+U6oCqkpUcVx*m#7!{DbaOhI!u{bFa1=W>%4!fA*7aW{*J?aJ!S?*8b}bzDJ*i_y*=jKOkB`9))$pCj#_1bCn4dY<2rIBx{+' + 'J|Y@kr^zGa1Dt=1m|k#|-Wfq1OoNeaM(_dsa{#^Z=McUqBRbXrNyFC}D2*SwnGu&f+gRb7QdqC~M4U8B73)NK8zlC^a{+n>{D+OY' + ';S|PoFvCY}LN^vyf1(kE<2CvNSG=^clk|6yd3~Dbi>~bEFk(o40AY?j#8)iKr0ZDPn4j+1>AFYAd#jJr!5%Ya@hLCkF(*u^03P+H' + 'hx?gh_CPHJ6FY7E#FJI1MWtaHd=&PIr4~;PFFSXk58MN9PNC}_AAyct==0JVUXeG32UXA_crE?zeEzI5t$gC~Y&Etc+2fq3bBiDn' + 'ozV`SyRWrIoy>n*FDRr;wu+>h_Lc)ku=' + 'BEzoR2WcARy@S-qh?Jg)J~)U)E$UV1Ymb^jv6eQkubpT|Wj%AGBWa-l*?Bz_d_GAPr)P`^#r2-iWm#T1A_Erohzu$bzp35fPiJ2)@8+kO=dcm`phZ!NZ4eM0lMF>s`k5F@y*$rGsBzN4E})^WqvJgaRcD{b)TYvI-Z^<=UC>=b{#+`htvsy34%GJN^H^GJw%qp4^o?@%?{?}%4s&fq' + '2uq8z_6+VeIve}g?Qw)Il4@1)V19;<2h40B<^m63PR{f41}V&6;w>K`*>_^a^2uF(mAVa@YM9EdPEoh?4' + 'y95S&S`m6O#ufKe5C?WG=!rI=bqmpG9lW@WZH}Ifg@@+_A8CZ' + 'yWL(fNj)&)q4bnvo(p|}_a@ym_(`5XDmk`iV_92tW;KqJb|k8+OSxMo9qw{mB~6v${`~XPxA23eh3J|Y9|9uK938!B;zLWHAgNBZ9O@F)7Nkt8{1!S39OaqT?cL7;K*@uw2*~scs_t9+805qL^w=' + 'qoTF_)tqiL*$-6|@ICnQAe?FA4dc$^j%4f3Kxcl?8OmMIT=>%^d=?z1hrjb@R&U{Rdo^bN?&4q%*TXDb%|LqQRcM0(8OLw&vdXpWx`ice5sNdh{*ecn!?ras-hwHrNc{$$#@d7HSJyOU{FtI|jND8UV;=z`bhNf)LkqF&j;Cqr$j$QP7eEzp@zA?jJ{*Kw=uGs&Gu6@mxcXWEZ0YGmv' + '&ce=~9;~U6M@Ue@KY6?{Tf*ddCLri?f`>*zR!TUo&J5tY1=d-cO+3MwN;&VW4M04H9z%5q4!Y%rbfP&Z=AP`2H#axqb+RE#?cdkSd1!bZ?2RjZuW_' + 'MdzK&XJHUYc%$_-NQ68Fj8)2~KwLTB<=F(Y!E#7sicw?yYy>o5-H6c?BQrh+K>*0;CL{K6%1`xARfBf296csi<0Nf{XCuxN#$thB' + '^u|%4Y7q&ICx6^JE6ONdfZ`yw$n2JthFf+nbuS>T5C_nKf!lp-!hPW2(k9+u&Qvu#iot`{V*&mM%kF|eJwn__eNgJ^@u>$ZO-qx@R' + 'tyI>pq2Mvhk6bpRiFV;n^uz?HMP%|4%m(ir#?fU(Dm*J-Leu3EAo>*{G}(H9-|P(#}*tH9!ZLjjJ^)YBx2-8Q8B~g;7{8;7ori7S&Azatb>|xBP%bl!j$8Z' + 'Z?X39m0p!sXlkZnR=Nm1EcW*WX`I*3DMPu?@uO!GZ!&O^NjtD#gzcH5#?}jhrP$f@$c4jjVW-4giHWNjI)?07ZIB27h36P' + '7a6_(`7&NyW_Q3|&+=6XJoR#I>HZOt)iWwi+eH*{wZ=^1F?9{=ZkT*;wR<((8aRY7>x;?@Y)C9g`(Bq5WqbIaFJ;708m{vJ|BF3a#3KB^|M(pG@)4xVbv{t5JO' + 'YQVq&;&!50Clm_t<1-UMG?M7)LoPF9pbmp=FW*)eMWy|~f@Fd{`~eB3$I6c>#(F$V+ol7Qh=CySErINtvP#X<^+fM#k>%%Gyx?L&' + 'xB3|$&@(NKiT=7o;ClnROx%=1wD%)Q5oaQaGi?EbKx+|F9~MUCGFsg+O>p(vU&fxKFRGILigQakgf%V@Le1Eh(S*be*v)BUdu!^i>v9fq9Y#+WNrf)ltOj&j+SG}x' + '=7t|s75QX@sr%%qvYo6*q+^+QZF*-pCHbZ+W;j$$9R=vCj#zVK2zyh%a}mHX%a@gqS$r}Y=M#vgwA}-&DSS>3Y_?Pp1h_|?u!6A%' + '3em({?)RQew5F7^*M}5Afx+2k9($5g#Aq&4<*eMB0v$`Ih~;PetDQZ8)Gs|5?Q8aC##iUCPE0nmV9bTx0)Lnp-<>vEQ_#u+U~DQe' + '9&vdJZD(V{uj}Mt`w62;qQzypxQeQDDda;RcK?SrpE%!8Qx)Nm@39M|GXbIxCh6CS_%At;iID(_GVc`RT=Ri@aY&7f+6k6~;@aAq' + '9m{bpi__H!OCc%na#)Fgn)0{1**li-b;@TVd^onC*ral=EpB_NGnk=ud+q)2VSXcGt56^?TY~Z7B`ZnD@`#y9oBR$DOu%CH-m(*I' + 'cen!oZy`|LWeYu_f${ZkDpS@_RVTkq^r)E74x;uilHnYS~U$`fmy1eMcl' + '>2FK?di3b(M&}UL$MjZZukRSr6v{ZTC%Grr%n4U{_6DDVH~)D4{F}G0pPk#+lP)mAyS^;9In>V4r5lYEARnVk4r<|Yo_Rt?HYeHQ' + '0i?rDxS*GvtSIa@XsRtp;r<&a|FgutKP{kVv1nK{%}nz3`lNI>jx|WUjwr+RY2TWwE|8@zfcB!dpaf61U*n$s(QtG85qCAEbM~?p' + 'k;6`IE^`kgnpxfNonkyb76N=j0f$;Op^1!#wcmlzChW#PML0by*SE?9UN<4j1*^b!@Xt9dh23Y$^eM7OSTaFfyvdDVs=F~PuwZ%!QeL37$ImbAOR4uESJ41q}CxutPC+ly>2n3P7r~=<OapZW%jmnjZ?D=&6`*' + 'GAMSIqs&HoWH{4Xj1!&tTf9u0v?_flYO$A&PU#zVThFo%|4tyoC}{1fjCk#>FL8Iuv|2un?v-b&Tr9SmxLDjxkH<&5l58mgrY^}s' + '4_1%ahhq!)){%SIkNz)EO9KQH000080000X0PNKzsO&TV03FT%05kvq0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiH' + 'a&L5HX>MO*Z*6dFWq2-ddF_4ubK6Fe=-' + 'b*@ST0y8}`Jw4q$J>5Nn!QlI*?uxQHp4aQms`wZ!@-81ndD|B2*=iRRA2)f`mURX1szr2J)a#;acG0qKqL=UAysB4=vve>R933s2' + 'dL3oia@%d2BFmz3y{VfHYE^ZYcL1S1Iuh^7T0M0|)7AB=l`k4-rCzIFg?zfro2slXRk>MNcThQPyJkDb7NN`0On-(^txEZ8-Cl0;' + '`L**itG4USPQIic2&CG`S6$uAuf)LS^=ehjX&7lfo6FI?hW_WXRWXd-7k}9n)x6;1h{gE|vDhj^w2N}y4fUrX3~#f_WtHW&niDEy' + 'g?T=|Di+zMsW*V69hchV-PNj`$ui%=PyWzuXHarpw7ULP0fVf!-DcZynXXuGmgTCDB|a4RH*aq^|EQ|#xAMmFD>hIv(N*(c!4$c7yc+qpTB-}o_ED1j9^GV>eY5#(c=ZtGd$b_Tw8RRMOfC042#|Y' + 'DV^8VO@Rc?5VK2w&;fC`uyEGA9EQ=Q3APX7wkuZ>4AgAOk6D8&N>pg)uZDzyuviVhBR(3N2x=i%I2t^Eaendg)wlol`rrTf-J7@XzW(#odH-Bl`ZhLe4arg7d>Dkk-e*5*a-~Ars' + '$PpBZ2&J5Lo31d#$8X;^@e2qqJVgTK_xqG(vM' + 'Ar$Ns$-+f;36r{s>Hkg#;D=}w|LM+`i*}?-0G1|GRSE?u2$40mPcUIEugWeS3aQvfuCmQA$;&21KX;oVzVz?u6(7&R(pvl@wO&0{C!y$2*WWMVDgN|A^)A;i?3w2Xst8V0fS&N8MG9%GajV8zbODut9(?1@Q)4Z`-PbJ%Au(0S=iZR+jZr(1%v_' + 'ZA%olZS%_lYyP>tExY+uCQJR9SC9Oep?V{*Ee1zYPKlJMGn4?5FY5XAf4m1ZW>vhO*G<9bg*hv43NbgM$dI54aa3^>Emw8k3C#i6' + ';1cK|Yu2s#0TexCh0fb0v|<0QE9VvV@T#zCy*f32Q6Dn>u5Wxl6zz7^@z6S0JgaO0um56#5+hcFfL7tzpdpxM-2Iq%R=6;0(Psx?' + 'Ll4bUK?gsVo0t{jMau1AA{ofb5V^yG$7Z5i&+V(3t1-uO1}~;1UJCYH(29B=USqUhD#oVh>}+fNvmQHq7^RCRZ+#Q*mNhz59X`w0@d`k' + '0Ob)HXh2-SLc6^xsz@}$%c@+E-bT+7f_@`74i>65snDgdRr-L^5hw(gzF6)OLTk{FUwI95S;9C3cZk;I4lTX;^UNwU7!p^RzcuL~N>n' + 'ald(kNf(&!aTd%LUPbyv=XG3&O77B#GhSpj^ySOVu2gdl;AdPI;pl%B3yd>4EBeRPEegR6W~$f2f^ZqJpR6qDJJVGTgTrlY9x' + 'PTAS;bKaC~U8&s%Z`kUOyGtVc)sprdq2(r)+&d~7$tgq3Ez6GuGl?pCJB-es!+$iuHjQ2*{HW|uDaJhptiKx=@ItAI`0pKVrExQi' + 'R0aEJRAm4gpk}}_Yl`@9@S|*(Wd)3kI!U@%I~lwiOp`QUt>XM+*^WWOPg__v#m|LM%PI+Yn84O6g15A8i;wxd18IxM<)Py+MA0Lo' + 'TQ_9CLY)kab}3-9t($mql74;m)yXhAK21-)J_UXCBt1Djc?yhrf&1GrmnP2k^o)D*bg0!Lg(C?UX^u?==IADax45@Be@_1&Okt=5' + 'GEUNMH3Lk=i*gNvCN6^NHW7x=jDp|PfFfx1_^Hi!1trsj$WQWKR!QxOLU_3b1aCnFReg~n#(4?%GX&tzgm}zE+*s+qqj5|wjV{a95fy7^GGjq+Bnh#XSSlu4W+bcisd{T' + '$WgUj4h4196dk@9Fc_Bu0AG1^lcV1DC*E2a`ZhKKo{_DA)(B3RkA{h*r+aYK*J;Y6=`JZ4AT27Kd>9h%2gPzSnh7D{i$}qn0LU)3' + 'I{)inNr42OQtjg7(>^BRjuwZcDp$p=Fu9GTDV|n!V29(r7@9a58_%BQLN#*aaPs*Kq>O0Kc$0y6$}U&CIjogYG^^_s^zK!@YKy!0' + 'y|4x3*reE4ldeU;8^FQ&3+D9^?ni)?2t2pIW#`4Fi@yEtzfw)l&znnUtKHMfD8^nUqv-WALQPN%rzMFC5__IEGY~&y6yX})23GcD' + '7!@d*I#6aWuXy7mK7j-;+Uj8k(iOGX3PgQ$Q7m&%t3dr(8vRy$@ZE=(adT5N*V__%_W?LrQ{=7a3cx{Su|ws8cVE0$E7%4W@yUz}' + 'v~0OoEgplF$N@pQxCI)8!NwBN31V8WD{2mHN;^52p$C_We9UoD>3N-BWAAcO{cp<_#$V!Em>0rwFhl}h)KsX7et7f#!nX?KtNB*!' + '`e7n&3&iYg4J6otTF*PsTqpzbxB;XI9uh$D9!yC>sY!VM&#+)aTm$!6*Ea=f8+#@7FakMw(kq!CMDL42?9-7vnhQc+gt{^w' + 'fu%;ttFGI$qbE-;VbN`8(9iXg%b)71d?IT-nXT&CljYgsx4(aSGW+V;S@G=I40e}-{rqsJbm`$<-<^JSa%3&V2CXWz8cmGLRT`tHJg83cMTA$1T`;#Iv+QjYY&dIOqFTW=e*' + '9~2i=ir39zh9?hbyaotqxnPU&>?g7o' + '1I($ph}bmI#iACF{l7-1_VQ_RWa>X`E0iL1U>JMMc5p5>hSE6Vm_$JOaop(*%JJc;57kiIacrDfX+' + 'l|va=0NiDFHAvj{G`q7*d8!Nao%-Pv_*HrZivJ9@??5CxaRURpWGGaP5E9idJ|S?islS8bS&M0DiluOYvGr12cwNuR6&al58Y`;~' + 'ZPR@Q6Sp5zg&Z9KX8HUYw9P`;EW`bjg?S!;VSsR6!|GAxNsXoA$tbAloHGQ%KPq2Z!9sn~I%zpNpqDS' + 'nUGyhfaXYECs49ZZLt%c!E;%h)Z(R(w>*Q^ffB5B8=y7IZ@2;qi%4zYP>GOwyLg!fhAKdO;Gx|U#J*Yop=i-tgiRVPn&A;avd~@)' + 'mMz{=@1)a%n0t%nEB;t6dnqU?fY7M!kpmNLQ^FO-Q42Iw1GC7$iP{OEC^In%Oot)j0U!o$Z6N$%VtjXC^rLimQRCd2e+^E|P06%hYFMIHXzwuOO(vF0;j+-6mA*^6I+HBgmeS$=5MG(>>#i~3S2@XulF_h8fb@6n^{TWG8mj2qQCvcg(zS?t{8' + ';@@3deCXlgg2(pq@hx`hKFpk8+=qw%^H{Ee!PX^blmYzG0127Qcr{83vOZX{5e+^iBE!>tVIxL|sG&)z#FjNXKdAk^VI|9H' + 'KLcybm`s<~@u>=8n0&YJ0Zrb;DkjUqDbJ34jdr4Rxym~jTrB#Mukwa$!)D!~!716J^nVy-FQfeS;zRb~{r|AaFJYBDCV|02_~So}' + 'i80w5Y|LE;8nEl!1o$z1oMhL<5aS?|3' + '1wtd{1H3A0=}e>@CPOVWW_XZ-?zg3IkMJY}6EFMs(qlkM=ns44gk9g16uj)%CdF=8`%Rn3C@IJXt$ACF3?c|OXOf*s4of~{%pTI{' + 'MKRB}7;Dqf5mxz%f' + '=x7cgqY8$;%hX@^lt!;RL=T?Kk%#2(qVw<|ene=B!P' + 'g19wRBBF>4Lfz0|B2HJ10=>mU4L;BiMs57z_t)#|a&WQb&>C~Kl$E=ZN6{yM_}S^FVC^dMgGez)fglgW5FlUfL_6GXIR4V*Dfnwa%tPdM1hWu+%s(Y|1EAp>mIO(FmBkfp4$NYC?' + 'w*LThT0BlC@pM(>3#6}lP0GB+Hs}K#dz<3fZt)RlMJ`fo&0eF_KJVh-DW%HjhjA4T-at=QW>k^vEB&1q?Tn_TIShTjq130(#xc3{' + '*S6WmeB0n@nf3x_cW$n1#h}QJQ_~~oG>o=lqWev`iM=(0Ja^?Com_H(!4;nmjPTL{}g*6UyF%u^1E%ARZ9w%ZkSPu5_ytdN?x?-ef>AZ3A=wU?tS6o^p`bS(5C9$J1P;4-NWS*V~|6Cn-n4e' + '#m4N-=EFv{e4p^!e_F0K%CA6a1BDL6FFA@!S|dI;1nSt$gucTcFxXGHN8f_>bNRo*l$IQ@Uc~AY(KLgw-r@6}V0(J{m3($91_Hs{' + 'BFNF~ZVg7F-)fF0r&GzlRP1RqNIoAhU}jBw#_M%xV$~tiIjlQ=Z=kT`%zncd6)mDn-4=$?=>eFV00h|&y`~AGe4SWlU3bjuLGx)T' + '%Nznt3u|FUBR#Kn?5q+F>2kCtw~)VZtQ9+^h^BZkaj0er8*mUUXxJD<1H)V&LGWL3uZZQEzPK-*^!0=bFWf81qmZTTK!Xk`K7Zxvqg7M#iw=>c1I9*QCi@!+M~IYnOYc' + 'uvuBRabjpbnrkB{1fzCTK#Fq?-9=!YzsqMsAJb+^C*ECc*#fa|E)gqZ7B3pRXA@(v6MO$MohC=YzU*;!QpJ)zC(x>pwE85$X5^VI' + '2QC4l!zWXTE;N2l`w)D$9t3syQ&rRIQEER*u%uepFDDnOP9G%PNtxCdT&ps$;jkxC3}gL5fO!K!GiyJ7&`bL>$wPh' + 'ri1XYd3ZiN3M?4GD0%2IRO=lj2EoOYHzhh|4#_i=qA4gqi%9sUEN)xv#fElbxgo^u*KBN9*w)B~e?(D~e' + 'j{kf1qD#F##nSua^bp3nXXgn64@XOIzhJ2uF~6V7LF^4jlPJyap5D77?;SOTcl%HuR+w1t6rs>=XH*~#Pn>{^e1M#b?~+boqQWDOJR-9n24o{lvp*q*Wlv%3$cbS`IDZNK$x}3|TRBWrbH*ON*f+Equ6AUJPw{;T04TV`xeYqma+KO;#0LFUc=vitd65EDpIcEXH{ktLvdcjq!WapRRfk<#}=aPOzEr' + 'LC2b*B(jdgU?hHdaoR?{I;bo<64tA*N7(kQGIy}9$dzd?WDLKBdwa?c?wPG)ql=GQTvKA0OZJiS;Y{2ZH@8KjXX~?-bRxvqKlOC@' + 'Gxqpk#%}2QmjxzkTnlX<_BxA?BaxZN^x&eHGRwQl4lA%-1E_QxIOIm%quyfj!wPtx%#9^OKB5`yhOYIt3FBW!(T_+5V&@`KuaM9|' + '(LpLX9}6hQ0Xo>H>;in;B#5c59(D@yjEybD#|`^qFds-&>ZF-I#$VJCT3RXd1{!2B4ND`fO4MO^eO>f9Iz2#)3dawXp^}0EzY$t7' + 'Y~CnynVs`RoWrZbef631lQndhwMZ@dov{p{QA#l7okHWm)&%}c_f^E1KQyT4TxQ)B(aq{Cm&BTyGXzO#UWBvRAlcjXUT#vhnSpsu' + '-O+Ni=A@A|u+zL3rU+q5^sFoCGbMqJ(*BxhOi^#Ww(5' + 'trK1*=tRT^RfuFVKL`|3$%lXa>%+)MJ|wYlWSLC}R7TJ(Tw1-GxG*MV1WY1&9Gy)Wc>I`Sn}e)lW*zxzsB+mEroBk8MWZ$ZX^ZAo8EFMK<=Yg4s{(FmjLgD}Xg)Kz!Pnl2KRzBG1}^gBB1R`gz5jk;{0M;kY^' + 'Yi}_-j`5bV29)lbWo3F--$css(c=QGq2K@1-`o{FvQ0cF#hCj{GmkvEg4VP$gU' + 'zf{5R9t)nt^BD@r$C&g4AMt6L1cmfW>Ki&Trs%}U+ib^7PGv8m1cRtBn>iG)=&0YfmGp)o;6ueRxEP-h;Au$L!}9D1HUa=s(7d>7' + 'h6|Q(mn9M#y;0a4wD|(Shv~{rQaBAk=NBQ?2C`+k+Q3(%#4j$!S8#pd?D)}-IVrr(*l?MhcVVVHOV>LdVh0HbcSe*^c!XJyBJqNEZqE8a;XM(ZB39zjg{h0KTfobst>5Ro4BQVywQyQGiU' + '(g5{4kPXuF)Jn05vD*qe`v#n6-#E!bX-_y0V9LeP0fVr*G&z|XBGWjQSG-SNcrr2Ra%$)8HdeOnBA}PHlfzqZ0Aod4-*=>`&dpV>' + 'rIvihs}0(pQ#u{t*0m>vn!TbZ%8l>rz>SQvKkqPqs9AAuCI`P0uUH0oXsz^vkWoPErzKJu4T#X+G;X3q8dhfSf&a7hYZ=?)tpS_?GXK3-N%5uS}Dbc@bEQT' + 'R=gNnkqr!^EnSfE@cBcu5s2uY9tT`ljT_tK;DpQFXV>m+' + 'uralD1r(1){!P8AFLz{V;@ftLk7&}~zuCf=f1bbVMb6inC~{>r7PD>jgn%KSZ6v>A88}*G@2g' + 'b!sF<{DmqFeWfn>xd&HZLGBV6Wg43A5GN|Q`e45JDW3xs?1aWO-*)wKDXs^A' + 'PX8`F>D{Hh?6p_!Au70hj<|3y|ztQKp3L@F!e=eH3jbmMKXn;v%{%=05u@' + 'C1-MXg6Ex$zfbW1_pI-L6b1*w;wDqzg3T_Q!SqwbSoB0rg?mm5EU%Tbh7*KYBvDn$aRYK9(!iph*y5+eZIm%OfwBKI<*F7i)5K1*' + '2rrvm4-mjv`2|2^aws*ueY4d^r~T%N`w_22g?p@gdkNB{$6gXlT+0#9aKL;NpdxP@P~t9~2YuKjV0($Jr&(dE>7hYQNYgtA(4Yp9' + '(*HixVD1YSC}xUfNjr2qLKIDv_(fmewl}RdI>JzRy_kKGCtB_V#Hl(bQf9z=cZXagipk-Z5`u^~A{d`;goC#_jrZ$|HZ||*qS*9V' + '5s2i#3&21(9eiStpEGS77~GYi#KA%IoOg9XM{T(UR1widzf' + 'dstXlUF7@J?jlx%OlyZLAs<6olDFC?zQ?2FE-R0?ftAiTo}&b?>e4O1#0)rL)La' + 'D%15^VxFZFOQBJqtHQ|TEoS1106ZvOEI2iZy!zM)vJfMbS3J7?wc^LcI%f3ui!jl09S2F_g_3aPVJ' + 'BrI2LB>}urEKcMs0)=0Hr1DB4J@vR1;A@CuGg`yFSvVG{x-;QAkDIk@wyNjXa*u2w_Bl+FygjN?cM(c;8gIGd6{7DMwzGMloRm9b' + 'T|YJ?c;F6O{A@ap$b1*P@{YWe1!bMSr%$in8Dn3jlqe(SV*Z_a@*AO^?*Qz*J|k9>UOYSv>}(zU@6OAG`=4k-FdT}K}yCm)L|>0{#VV|7`EMx3%uz&E_=I|)Pv53s;}VH27Uf{tRyM77mY' + '(A<4M^XAlfz3?sM!@l@WeOHT^XPyA_+Px~1Om>*`FDK&m-kQRfS>M8ArgY9(Cc}DvY3~%B' + '{W^R`NTeX65#R1$I00=^??u?r)ZrNPwvP?b#|1j;u0qV@P<>3cd`JQ~r#~i}xkJrK0)Rx{6&u+no(pys*IE7h84(7zl1YQ{VxM7(0-V|CQQ^$CR~fdWV+_u{@bMFGGPmy(bd30oL-vq6x<)nQDchw_Mm0ncT-#>)B~VibipC5F#bId>oJT#aVsZXT`NB#ByQ3' + 'muV0ngv^0>+T(SF9pl}yFkxnyFEJ$Arb+CQh=!HDvF>5f?;mqvt4W-8XpI6@=JV|uFRoNsd2P$!Qm-w`m}HsKOv?5QPP7=^={1el' + 'ch9J2-JWfiy@oC`Cn%H)K_C0C5)(FzuDFg*JTk^l#9)l^R5CNMKK7z}93>4~UKQ_4G_=S>wJnEwpB{TjWRVLmY9mIG4Ct_5$jNAo' + 'p)^3o;7Z3{sp<{Sub9gjvKJ1B&%*MM_78Ou=%s74lPuM8FTj6Zo>O7r_HS3_wSD9*tZNHPs$|7M@p$8Smq{*+xbe^;EPnbTgdu' + 'M)|Qv(q@Ep!f6P44rQJ0Pb0Jg;zM;`(6$x5Nlkaj%9wW8YBZH*q*zLUeHA`-l$M-Vd}QVxwh$l_%pJw?P_1HjXN(kBTB3' + '5*T^ePHZ^ix|`uqSOw%1k(06(R$w' + 'p96s4aVlc?Rd0YP0KZ%a7c^3RhcHH*eL543C>uXAD8IYU-bhiZ)KP6QBzfh*hlfYf4d2HXiwnunO9eNh}6S`8rKn8Zoi=U|G-b?4p0' + 'im96hZo%%Uj<*ayrQ4Q?=v0lb5-~!GFeX+?{h1ZJ8U*g!C+E+foZD&7yCAGmefhJjj-e}_RBed}tv?KhCL*q$a^zP}M-Cop9ScHWY-xp9`5675rXMxjMCZ0#@c2AZN}' + 'RU9;JAV0Ck;@w_d9h*apIKp2zf;4;-iyU7_qo$Y}R-2|3$O9jduy2dS' + 'BUBhTrx$HjF~%TQ#0#@}eH3N<#EI_~rLXJCOOI@UT+X386O8712y-Cfimg`#RK$r*lEAiyzE5db}M' + '5`ZzR<#x4-<3|BOOOQFqAu`pxGK8)rgt%HzJoHMtKFJokAhA6_sEQ$lJBTUMeR(6MWz-i4V&JDAJB%5_NGurnT?z^|Z~(?E(HxYu' + 'Cc;#r2+l`$sK7VJ9M)vKEV-c0`g;~nG65PwQso*kKMAF4uoDr6x*NPYA-Xy0k1^|6g?2hxj-6rWKy)M~%N*~pKvBgThV+u+>@l{m' + 'MGABM;ll)m`JiboyyDY?pqUb{2eK&=_q5`a^%8_jp&UPY;Y+NT?8aS(8vAm^_@z$T>OFRb5{@nhD?XG;}?wL7zY$G!*H=W^vSOG6AYhU0sA8?@gsZfrc;hf6}Qylean-#V8x6j1L_$*^^Y&M6IN0A-xoTdx0H' + 'QZ^pYN7Qh@MJ{qQr=_m$C' + 'X**b;23{0h@TO$MGna23iA;v}Kt|R$dRHLE4aojU{eU??j6Ao!b~n4&Ak;VmtwKrbUPL3A@1X_9F}Nd' + 'HZ`bqs9U^2J8CZD2;4|8!oPK6(N8s=7t@^v>`h|gS(~J$nBaOd>Nir!ixEJ*US;JldL)w=W~6T>-*El-)c<6YJ8oOl33cH5P;pLO' + '$@cu5!3uyn{qa*FhYLa%lm5==ACr-pD$GuA2Fn{08sYXY)-{IFiAdi|8Q^SP`hnRYnI$~' + 'D~B#U@GdF}T`VH^my=VmXPv6O_TKA7R8TEfoLUjJ`|bhJ9Xv7nM|&Vxi^uX9L}wLN^mvNd&f`;k^8tp2pYm4m$c(wZ%G=D|Mq}9P' + 'e1ASeeX)`of{vaQK2$fpLe6O49@ob7^sbG&J1z>n(K)2@PI~{1tlS<$a>dh!!=Yemk;Dx81n@KXL@$p)!}Ag)EZPVG3rh4+Zk^tA)w*7s(q88LEe7`*sFOT^z!~dos}vzBM%p!!U?&Id$w#^45!?(ysaIC_0-T;bQ#0<%YN>=' + 'YtiKhkH>0tKQ^Nzkx7mnElNmOO&Ofj;1CXuhDdxpCAPDmd+JrmbQ+89OF@`}-^yf>Ha)l0Q@%qA)y(3@z@bdW|vk&k7`0{OvBo*6%' + 'xFPZHXwtI(W0e=r`uymTo@#4cM-QSGC8h=|=lSYb`J@#eH+hALD~@C;UE36J)Uy|7*rtU*@1ohSUjH^C==IBd)xn$bfOHQLDY9*g' + 'jLjU<8J}z~M?!#8#1dVqKISf^O@4bMkS*|5O;Q6T0(3Fu%O-swKbUCdNXli1~3GMy_!vjx01~-&We|3*C-fK}<+O^SCEC7XwBfWpn7y' + 'wn}P-IFtbNV8CS#J+HHTPMNV}!6f>Z=x^$mi24a)K&?|shop*>8TZ)+jeiA0&-Qbvr-' + 'h=4TG_p0`-Y4VP9zCmvQ2hdP5(cSt?dhLrn9KFLi*00!&oND44bYSUWwA)#B2%39(jz|%Y)7~>FD_J~3HQPjquQq~x5aOUh;OF_=' + 'm)i8MJf{1Smi8qm?a9h&1`dvO#tdQ4h^_GtBXKaGGQG-!LTND;W`>bL`FubcGNn&gX9W&5R?>?1fPgz3?)?{CTarnp>RUXL(^^vu6' + '@bf(hH|(c5i(;xoYD5f_*c3oD-Wh|B#S|pLnD}UH%}m;ER%I6th688!{~&rrlGLXwBse;M-k8~G+M&Dz5j7o9' + 'c$~~c7Q-XkW{5XAB1?6ev`XtW*PObYe$MM6pxT)oF;+!JE6LUHM9hrU=b;OBXiP~G+%3xta%P(zo?lR=XLng@k_e=pb;?e1W*_c*' + '3LKzSIhmpjW8xWrG#7#@jDC1p2mE#~5c8yW' + 'LYes8azLH>AZj{I{08t!$-Rvj!_^t9H+e}JK4Z5|l7!nh9Y(^VRS=99<>_ZkcVXKBgLH%BZ@hE>{N75dX18d?yBbN>`+9fernmNV>T#noF8PKeTsf' + 'b43n@A#)|}E)j`RNy^Tq*Zb`^d=X!yUE7IshX!bEX~=ow%ri5TXS3N$*)~X!nv{~ZEJK`bXoYZ1H>@E{X6WZ1e|eoD%qsM^*Kd#C' + '{(?GA8!G7)5&3L3J33l(;7}CnUiO?61)@#YatS+FE3slKj*jd*);HY_VS!jTypk<1x6wn+SibI=)(6*W^&2asY-_8h$nmNiB)!6Q' + 'jaM}>i{!4OY%|c_?mF@-=9Z+`I|^Z<4GB4JI;VUu+Y$?j&;!MgED-pk<-{tI6Va^Ox|$bwy{3$uRl~-Y=K1w_T|W_P%C_YdWmpn1?4qn9ABxqkz;I#(!wo2N^pbPD`#U$bpygn0GK&ba3>==b3agiywLFFG6@aDE=GxyaD?*{#g;d`!JqXqj}4sGv+p%u1338iYoqizB^Q{ZmFb&n53hifT2ZHw(8@{o@3}mZ5j(iDF-asCttt' + 'cF8-4`VUkrk54o7o{+Ahjaa_rJxNtmyvxNFcjSc^jcOGzYD?~wv+Ug>ZA8giA&vs_n><)xf>Ey-hu^tbe|&}Oo?O@iq-;%iQLc!@' + 'g$gJgAZO3a37X$0ho?xZmQ_Vf*hX#_XkE8hLI9oSU(e?O9kWeMLh?8BXNFwSEvY!JJ@I&+pFTaak+H|+fEGU}UD2y!rEC~oNN2(d' + 'ass3hBuzYDUcS4$w9b6HB}kOG#vBe~cMUrc=9^w<6@)Z3Ww4jnWz!t1|PTP0$Dy=?#8E99I-GfqFqIwAyI~<(8E1yOuKfP$0=M' + 'WeQF*3A`w6lcLUDOK*Kug&' + 'rY2wUUAeq^c@vvEbGd;@5YZ--TVU2xv' + '$o6Qh_JQ4zMJ&_l{uLHfIqW=KXZ@HMo+BQa6?LH@W6KG|mdZdeI@y*$fhwua8sP7o5`Y1Eq%o=$iAR7mJG>IWI$Y_u51AHhKEB&-' + '^>VcPLjS*AuLV(9Bl;$BT(6)h0JwM*nupf##t^dTJ*53d+9le>5e1%vt`|XA7C;7YO=}@#Hsa21otaa' + '0-hqshd~~Azri1h0lDp_+$LbBITl7R9KUO!l0lb1?+kUgDs+up+Kv)4eUbyCKo_LTcr3=)6^R*4{Q_dZ$kmb#F3LdohE3PQHRuWVj3i2R&ECEhENzrnNp6X+@uQdn_3VE(Bq|GmTxLU%P' + 'tKGPttBo5U&UTsi5fmo94}OBDlv9Ts@C+aaU{usZ-5fcJSLVzIZ7A5U^!B7q*9fo!-)5mpvVdV|WH$LVbr#t8RC^#~16=#R1*pgC' + '8=%CK1{eXdNTTs43d693Op(~YTh#eSVW+k3XeHMr_o7!1FdHEoYoYe@;?#x' + '44uH~PGZ|1*yru_=xLe=2$=6|g=mWn3>`^&^xbg9kj%=y!q7+)rY7TR`36JYr8>l*NCNPXpnWit(dPsjfx%RYeeni9zDqaZmfaei' + 'hkjnTIq((O0>G%UUE3B`N^7E-{`8W|%YV5!&_FTT;wxKI!#uTM97A-jXGaqpP^q}Rl``HM2F;&A)E0|?<(XlrRh0Vjsb^Ecr|D=S' + '+#B10@6{M|V^bI~83kh&2Ux(=Q^IO(gUd7)g_;?U!bbhz}+b7SF`la_8%zh' + 'pKtlyMIXZK`^dIU!U~*mJ%ovGjE4Sp++1I)XzRPKrleAhgWagZ)?NwpO`(Th+e|Dc8susWGTooMqw-f&CO*yF2T{gbT1~nO$6!8Y' + '@MJ81Km4Bh*>aFE?lt}wGK~7?6BjMf$puJ0(OX$TnojNxY4{1d4;QBJG;XO+r3SrljIq;;x_*caOK&MW)4JoBJMY_>C1gGf4WG=s' + 'y~3QT3t87nK+-pTos4YCCC5y_WK0q`v^n7CNH53y%rURYS{7zf2fQxyCy1?k;{=MUD)D;6d`ySte)?hEV*Oa_SY#w`Y{4FdCw<1q' + '30Xu4PBokE7-P)ns61gX#lIOvukST^9gSq=c*LUAa0&cmT8t*A{TSs-{S2Y^in(K++G*Id?^hd#+&' + '^Kj#-L2%DH!V7zM3|`TX0yl7a>dB~(+Xfbt3(CF#D7G_M}HK;BZ' + '5-JPjI~9VqBcYT6nB|H7B^*x#Bpzy!gbDexQaA*L&d%yZ@Fio^nUu3=VuM2=3-?Y!;@@G&^m#K(1(#55H+4C;9yAeVu|{V!E?*+7z0&HlGxXOx+OZ' + 'bkej*AM{^PO9KQH000080000X0PXo=-s2ts0GNOP05$*s0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiJWpim{b!A^;' + 'Z*FsRVQzGDE^v9xJ!^N{xN_h1D^T4Jm9nx{q^M_`o;tQRb~mweZD)5A9UX?2Xq$~pYDp@NZ|eWvnE^lmeCUz5dz-UslLRoB7iI>)' + '0R9_0idK(taC?_ByJPp*Cxoua&iC|Q=9kU(oYf=`+u&37-+qI' + 'WZnu8ntRuP=F)p$QOvyC*oU$-LWCdUAPvIX7E7X=^udc!1dAX^D9^k`z!YQ' + ';l;(_+12UflAT|$qw}-l)2q|-GkE)j9iDw)zn-2Qx0nySf^Ph;s~GVIBm?B5zd)v4`o4j{O~esOR{lJ=3Fbgoc)Rv)eRdmt@#7H4' + 'VJknr4-%AS0_cGTuqEn(m(rI4QSDl-Uauc{VH5(dmdv{k!sx)82Mc!VNB4djKe7~gn4si|uanESzeLN04Nb2A&=M3ahL#|rd*sd|' + 'yZ60h9sBowm;w&5&pxA$d`!54sf?}HxK|y5#2+D)e5NN;I7xfGG)sM@LfK#YkwI%' + '@S0EtSj<0z;`?8{c?zZ8EvQ?P63GA|gw9?C(cSu*_)ItwYud<$TJ0u=&Rq9qodN-_i5Jy!A2#I@1;lfyza?20RM&+urqDwt35m=XF+IdbbR&W!XD;@R+8Tj!^akzdr4ZW)#jc%_t;C;Z4Wzk' + 'XEf*zI-`DVeiyi)J|I~aS)4$HZ#5iw^}{!Z^)Z{yTH^KMHNMuE_&2Se)myB7bVxs6(a+a)QJYuDmEq6hTn)1fex6NoHOw-6{wQl?' + 's9l~RYE>AOVsd_L)n^L8yB`B^^x?7sz~yUI>-C$nzk-G56D!Z5*Kgkc(|Z1(YthN+<=&h=(>ZN{jDuZBg^fVA}Chzyg!34mi' + '{eR8PqYXphh=#&hVJN(vT>Us|mm5&K%%LVbes?nYF{Zw|IR6d+s{Q5UqN4q|HUzIus%vS;TwT2TF>0Kb--wxkw(sNnu{n@W#' + 'cs=Q+96c#ma;^tctKFHg7i{T=joUa{uaYMFiA_8FYr|m&dQp>HGk>9oF6SWHU%choPa4hItmeKub>B@cPS1}|kKCh^Q*<7}n0~XK' + 'TGH-(v+;I61J4}m75oP=Km`8Kz?ua&)IUT7{#QgM-(maKPa6<21&g)sq91Ls22kKHAxr}Lh!g`r45auCr#YiOco3pmm(dGk' + 'LYy5eTw)1*b5~IkkbfRCbm3EocUlkuCFzuYfY+VPW(7~08^yVEl(pF`a)b=$WGh+d~}8(nXX3bJk-PMTec(4MsQcBRb`v<(GqqiWjvWwdG5*5UOms@ENr_KgOyNA-GrqSvvU5_lB6' + '^m#pm1n_!;GJt0%=&}Y>)9yeEyWQ5R3cY30geTRSOi5xYyp*nv%Bw%_ZPDp&cmLKif$zNa)t!hjpuO@i8(g^^%$i9uBg>' + 'U1C}%wlc!m=oVEA!+T<7mHS_-NhIV*ZG3u4t3SCaak+1f9W-N?o`D_n$#qdi#ym9Xx?BSlunULKFZ;j' + ')91YIIC(*dwT5y*Il|iwy3gdbcMFEZz#d8O(%mI+FEg!{^4r|FITaFnZL69hv>x{OkQ|Nj($#%D4{~!(KDP){H3*(>O0@H|!Or<@' + 'XP05H*=Y^9=^vDuevMbXE*HSI5!37T)Vxg#g*Puuy}YTDH6@kzsQP^!)4%WfGL' + 'EY5i6nWn+JFup4o-xu2aeL<+f_ii?xOQKc9`9X&&_D8+VobS>g9}N5DUQ#S5RKA(kfcAPQwaxArfT$gzi)Ex}A7>GfKwAm^T' + 'mvj>`==6ncE{z~GPib{*4zqm8K=YKHksQ8O<>~^jIT*kWiFMuzx%R~ZgY~RVb+cd{u7Q0^1k0lhK9x^g`~A%UP3foxqO+)n27ZFGPvakQ-x}D(Fkno?ODKTzVB-lvUGX=bte>7S?9ed&cQ8pB>wyt+V5M>dZdjl+P2E=i&Q~7%i8kY1KJ$O?Nn`S%eQkBh}^D0CAA`P$6VjtOtXgTgyTA~' + '>$xTR9U$-(Dg`LF#;HTdNk%LyR`+4^%neK9j%9S2TlXr1#ItE$+cICFh=y3$sYb)nu*tXmaJhqWO%wqi&Gup%UfF(<6LFU3B4?Ys' + 'VJ&#b&9JWQ3KKEQ*-2$`;r=G2JXcC70^_nC?0h4oM)~*4{N#X1q*{UeD{LvB?h$' + '5r3T2b8c?iXvhgun0j-(77VZ(`yuAq-nz+yx6;iqPog2CHRg8j$*@M>$U2O@9*dp$J5cap{I=!Y7=-)X({&jRT7whflRvQMiB>D#Nu=$4_-kfI|8$O1r_n@y*WOmVS7f-r-Y{E*Acm`m9N?4lyyu1jq@>3b%sB' + 'w{G@=O^`CZt6H;Gb1$zZ7n5`M=w$Nt^yu)73j_MyrTgyi>IAySblXPV*RuB$%-;VwN#pe#bB5u`kNwD{Y-lKQS8=pT+UeJ{4!~;*' + '|Ax8OfU^79e{7H3P2}bZ^Hs<8k>lZ%sL#gLc3^F>v45yV3Kf;s&P)cpW^!hJ=7kt9b+LYUj?PK0p^ej7V' + 'V!9Ro#Qy!I_{ejqc+G23)5~axeQ)6kS`(48+(`YeX`TSei$VfKo)kVRVlG$9fG20C4-qDsua{oTBtZ6XhldOn0WdjUV0JUq3}SW%' + 'o%?a3$XW)Wj|z&Z=!8W(LBRC6(QHzk5I#5P3x6jm#L)w`L(Ce~c8AFukVB|q?BRU(%^vr{xeHG' + 'Z_n{$1W9BWDS?>E*U+Rv;B%R4l(g2`<7-XH?+x^-bS*nEfZ6y9%I#-gf;e4!OClyA<-{jWP}1h&!(#-TW`99F($KwH@8LLSt6=4$' + '1X&UZt-Jvd;E9q0YQ%W9BvQb!3etxlK{`Pwi}f7OLbN3tYoNw((sM_I=zZ&uo6y@GAd)UGaTh{LY1EaHh$AX4Vi>fM2k1)m{Zv' + 'txN`PX|_o*1cR#8Lzuul247L|l#yK~7VsJiUN6hp' + 'u~DsRdiO+cHg2>rLqz*GDSBF>eX2!RSl1v{eG$~o3_$#3?e+&' + '_T_-tl;EFLgEzenSLb<@Gzg()g;tZKmTt60*ZPum+C3N#974X%;?}#rN6L2TI0-`0F}%wT3J|X?0N2RFE)W' + 'nP01vRztDpg%G@OEbPS#W*6%J#9ILQ0;8R3E0QX!=LE}#pfu9lD{dGV-7vVhF>K2~t0N1shfoNd?Ufh8`uE_QBusU0@P^&4~B8ys5$<{8^x(Z)U' + 'kdJm&@wIx&$}d%;WA&t(XUj2sjZZ!*pz7}5mTg$D^@Fhh7Uwu8-0GuccPgFDY6}K%!)sN~)wg0FP$jZ$F%va+rpnx{%&HF+W}iKRL5%7I{gNy@4o};`BtbOs-fGhpI|_`Twk+S@^U~L*?$)&NghT4#=IaJj8wc#gmM!w75EprZ2L{Pd*$M0`0%c' + 'hb%hHBIYcxJe3b`;jMi5MLxXCf@Jw{B_Dp54iZxn{g@Gy%+#RokO6`yiZs3gAmq+-l00?i6*>+rKH%lQfSmrSdl@zBMq' + 'm|QHKzL4L5(SOu)II4R4qbk}0#@E+bZb_M1&1!d#i#S@{Mc2VRNFVcoa;I%seIwk(d72R}!UwzW7}4GV&y3WAH2^d;;w^0EjErCj' + 'oAm}pq{CC5^oRv2b-O*o=i@xEYdCD<%LmcSwg!f`gfHEp;q4S@+*M#3%P{r;#&RsfVq;EE&#<_F;b3T(DNI5(hIofkY++=' + 'cU};>UgG81-*?)BVP|9rz_y@6+Yo_eb%DJDLk5tb%S(M$S%VkKpzav91RBtpeL}S|{ItVZ}*+QhN+jcUOh*&snE3=FoD=RaQ@ZK3_TK9Ls`(b7-k$1xk9*V!;$(o-KySr^WGoi>r#jZEG_h0Exx%*4=MJT_k4-r|KPXgC3oJ+XP-VbmMsRObh*0ouKl$6>66$geb2jN>($DS=U(D7' + 'z8#t%GI+7wr%!);`oyAe`N$sb{1D#rSVD1-5HLT)i`^D&b{2&Pvaeu{m(VSv2LV3_SL+m9z&+lh25(=yWkA~i>7AFdd!Qt@i>c5C' + 'PTYH68-j(|^)Yw{jYU%@=0_zPED{00q@C{F!FBk|K>JH7%L+!WjWi`Zo6m%M0<)AmL1E=8aHhQKCq9L47HZ5{WZMUU)$ouur}' + '!`*AXiAxu(b&$W{{(zgopP0tuB?lIXEn4FhagXfQTP1B)H+N3zucLI#z6rt#L_O8DttBBA)Q`nj(;dF(?BB1_N8Gt=NX==s*aDsK' + 'ODF}MTs5;TBzaLyizScu*PxY#@6OsQ_5fQ8`fLya74ZH?4z3*}#N$R2wZ-^Os;O^CRSMvyek#-noKoz~aqoc?%hyKXHxBrzZmwwy' + 'CQT)sGg3CrTf7>Gx5~!v6k{F*0aIJXyTY4tSYlHTY=wJ=63t8JAATGqjl6g?T{LCYwVOJ;DSY*~`Wg_Rl|T(Kvh+20(Gu*LjODB}' + '>Y5INe7oi`$ggW2gPgkNG03579)p~@<}t{TYaWA~xaKj)fom>Voq7huv|!xT5cop$7(L@jMc=pH&#>Q{#eb92V*rs9*))2cr;e?WeYo&Q)LT#mR7;SuH_K3ux~jbx3F_L*evW_j&uvVm&4q`{(qA%h=81BTZnY;~O@PuK!*{@L*Q;MX}x49^ib~I@VRuCS#+BOzMm08q=&?|kKz7zqmo3@FDpDG-ip2$M{QTxFZ|CX80bNuG' + 'u@hb*9L2>+1)c)M)02)N`mFw#CnTvpL^2aaf0$p)7i};SebhEgNe2odmok=;Re}{aOuj5!xHzqru@XtSd9+M$&VQ`odPdvZYaLDD8(f8=ARoG%w{_U3z9&5>Nh$qhQ}?Ns5rz#1Z}w@Ak;a_HOXqK' + 'YIb!syP3ZfS|3eYq%|uGT{gg@co%5phIK0OOEB7*cIZ4C{x>sZlhqQJm8w?7`#ZBS3Rcy{GOhzIw4?H|ei!#Nmu)X{^Y~X7e0<7J' + 'd3su_23Bg;gxZTd8k4EKzt}IoQPoJ^lLYbWMuV1mF>Pf<2b$@mbU9)HSG!_+0-Kwol`(0m=)%{T)a-_VDDSJu)h7o5Ifkq%gKoMz' + 'e%UH%IaIi(v~1V;>TnG~E2xhU|IvJ>c1voc7gVmo97Q2Nw88G8`{>pW{b-%Ataosw{8sgch+TV0kgzYpsM1aIm@f|KhRTYr!yu&<' + 'bpI>+89Q7Cp|`wkhXAbMe-)FxIK7K*BPejulKPLJ*HCp4r41iw;4m-&jXx>CSleh^=t3`Ts)qmILv0RLC~0u0q90K!78l!76vBxT' + 'DQ;wca&5%#e;E9R0KoeiBriM7_7%a@IEwkrVzeiGyf@5~taKK)+1hRo=TSHZ^$k&l8zoq`^4%)5n%W$h65IwjchWBRt&DuQ$IK!e' + 'bi;W_8zZM&dnlmfV~mkKF!bkR0(=yS9jUYLr5' + 'HD};YGpq70TCHJF_+%0f(&&ItU|y00x1soh7<4v<5M%^a86E9J(|1MBax3mfgJ><4ZBwROMecXzeYoEcUV_swk0LzC1xWLR_R9OU' + 'P1vTR5+g=#d}?7}*6pm&0Fc%jNE*b{Eo*45GHx^)WfKF576u9$;hTU~%`tRP)+PX@qiGvNO%Ro^3#OwCsO0Oe6bmb|4V1MBz|Iuw' + '5Os<(;92X!d5KIT#=7&;fHVsSBs8UWpF`_QPE%~Pc6lz0?U|agInD0}L~wQq)C#P{T!0w_I@*V$L;dee(5{gdAXvst7tPiNu&r7#' + '<$Z6`x<5eL(RR{ug=Y1$O|?z>cAG$#s84VsnwVzvCWw<%OfX=6r#db!mhk4&&TLjXEO+L#voEJ}cus9(pZ>v`wKg{B?AySeRh|4Y' + '{-i{UmCVcpo2>B+XOUivfb{rKS3~J=i#Kx5LXHPO8;5245mTqbkDKskw*#s2wsS|wKT3HqYzFG&%1>Zq$@s0Xc?ilhTgLzDXlgZ-x97ej9xj;vt' + '1XWw3WQxG`FqWo*h{X><;OA|=v+-`Hg@h@i!*|tZ+5eeq%g+%Md&WAlm;Lpb+({I^OINxBuQe=gvcvdDgK+Jeri3o=a53ep>}HRt' + '2C-n{GsLeA#IN^3JdeC+oMQ=l`O@fVTxvc_38aFhm)yCacM+ysW+eZ#f#mCqWN;D%OHn+Gsg1+ZT>&r0A{Bco-1dihUDn4osQQTC' + '4_C{#I{SmGM3TVO_B=<72VKjI#I{+XQYCG`iYZ?1*mJYk_PkFU19__>AHsJjC{a@zn@y?08rHe0O7~Bw9WHVEK8~xVqK%8J~rsmGVV2Op^}cfR!LkwG?u9GqNm)FCAb&w{yMl_' + 'nCMmr}M+_STC(W6l`e7juArwO8CBLwg3?(PGfVuU7HD&hHQj' + 'xo=m#PR6I+{64MR4sY(dt>Uc-hCXO6p06wE**{f##m^TZWVfRL5f$j}V({dRP5an6aUayC#h0AYf_lp>XU|%qmDcpiD8jPb#xZN&VFYSfrJOTfOS__J2n~A-`lQd@*IB$Ule3@j~Q}' + 'D0}P7Jv+a6OS2-UH?qXnBlWds_iS@Xys$RPIkYWDh1bWmab90uSn{?sS;3eH(Oo&(pIf9oS(T%(Tl&GSLv(8gKvkDGd&c1BDm|Rkd8KwmRZZN{|37GS8E4PidFy2I47(Xw!4#?Wk$>Hgl`|4tH_$xI0b#jKOsQ**1{Vz~U' + '0|XQR000O8001EXvq&Gw3`$~NP9HkV$*gPNQ>^FTl8WWGA)f`ZYWYq(%CrK{P)g~`nF=U=r)JMmWFTM%)A*+;yC_=J6=d`EwjhE' + '*2b~DHjGPgr=Vm8tSFCgCE)=i+Y6AT&EhzYqP@`-%k%xw9S!6;6O}*AmC}wop_PrI=9xNH^@(xIRNb88yP`dC^V82Mez(r>!d2i7' + 'x(qgjmJ$k&lnv}}9T>io5RA%}J=oUZJ&{cNH)%sV#b9d5i{_*|)k58MEYvAwA3^RLf*IIquVwi|UJ2XzF0?AR%k}OP5<@^MMFBZi' + 'r{+-Ie7+Iq>l&C8lwGW=wj&Z;gG=' + '`6rY$kFyyDPk>|?qEmJj^k^4$V>B5q$jF8L%;wQf&vuP)WkPULWVwyxqyi6+pa4UJn;l-^{1gXq1-bP' + 'NN_wf`oXF58$jJRcGK70tm0B*Lvv;i-1MW1p=3Z=93xreac)pny8uvebKjqD;JyfVVB2jg_tkUXQAX3P_G|p=nq=#$yor!LXfBM;N{EVncd3' + 'rnqy)6G#V`iz%0I0CPKz^r91Az``B5q);z4U^+W@7mIU8)Pe!>yH%IxbF+Sq{zdplJPFzhMl5dXTAq9(ci#OW' + 'YD-*{%xOGR#+;Nk6~2CgG9P-POTvHOkPw7zn>G>QhVrwUMsIM{!5wRyQh>v~6op%3R#Tez8-#m)`Ie3Ozjq&bp6|dW#C4BC;ho>?' + '=~-@HYrg4K-fV^q+c$3-kF8q$Z84E*7`aIyaStToj`w7TjG(Jnq=eZ>yg3uB#k' + 'K2}&$#=t7NI5+0jt}KLxBt9SU`a(tyn~S3>Qhac?i2l0(F?HWwe?BMqRE0hj>+8)$B@U1NyW5~C40iC);mDcK06nDycsiRAM1RjC' + '%-q7=8^mYKXGK0)DcFZ1#Ex9)0ByVbs4RK9tPQ&U`>2Z(J1J_wwnXw!BoC=P&o8+^vojVuV6p+@' + 'HoaCg8UuFMqXB2Ifz1w7Z@H38fU(-u)@cN1x!{(gS>}13Wvx~nR8{M|vZ{c0z0#JvN@bY`n=DH*u7=g&yyT!~b}RU1K(A>V6%-Da' + 'PlNnS?iO%I5uORo2%CAsYyOO@gA__1Ec}a3{O4AGb8U-yYBWH}lB?xUtLdDpTk_dp3ze~ra<*#FSS~{zv-tvQ)`v7n)jPX_jZ$I(' + 'e;miODd5Xj;L?)Q8zpg-0X@7u1_rTzG?G}3>akp3tuF>tbo`0&_lw^*zpk&dk=7qON&>a6EWX+qEfiH?1U_d%V6%?93$G5*Q}N=F' + '5lxi}&}G~Xp(Q0ZqdZ4ZP-i4VZn!j-$r@)7v!K4RXo}Okf_m9tts0E+nljhe3sz%ZWD`@JycH=|?PPVxt$N{hTLl;O7VGC(0MC2X' + 'qh$KYtU@OR;YXBG&X-IWj20t$rwWvRSJ3d<-cvjaAJk*wo$UNCnwc#M@ge>dYiHR;Q2WM~J;BcB&o85ZBS9kyRo4r&dlv7>>M<6U' + 'hVzG$z@S!L=V#o%T-L%hI3!JjPs}t-H<4q~L(8}zRhsV92UHvi>!YEVmam*{(`c088ws>}_gO;|jkCRa%GqKf544td+Tm1AoRst)' + '3HOCA56h`ZAgK)BBSmPl@@PU&Y$)L}{-&ydz=O0%+}GIK&&Ltyo{D3;2X~}nPrgUm{5%os86zrv%SUrRKkyc(HkQ~+-j@&ZLB5?{' + '3dMVVV1#jlK#gO0harerIJAyq0B3d^dpCSX!4*hcnHDSux`;?xbKtjxW~^YREGN~V3Q(2`N1qe@}p' + 'JZ{Q->)QGmwe5PwP<<&w$fr()Ob7wccxO<`rsv)(DN&owc}B9Rb&j{$|Ni6AJHGY5RKa^`1;k*u5|3sc69Z5fKe|(+9HkJd}8_)`8u@lUX@i>#@+W(@EGl3JO;p75{s2' + 'ionW!Q>!e$4B~`OO(@y-)%f&gw0|XQR000O8001EX{Oe|g8Ug?SBm@8eFaQ7mW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGBFcX>4;}ba`-P' + 'b1rasb(7zV+b|5r-~CsJJY88E*d7BN>%k5N8#i`+JBrUql&e?A4z@hjV}JZ)=huyGc}OiqmOj!~EsElySH{2)Yoi;W>wp2mS$S-s' + 'z5<;QK0bW8bEbUOPQw8`Y;SGHOlkYAR-b=It' + 'bUzsEQy%*aRbx~L)sqY5SC09tFZcwSt2GS{wBq{6@jXJP4T>|G0e#pqL1SW@@{GlEnI*p(!L<#;EH}Y83j3pvxDnX{chNSfzr)gLC4e8E$^kqE-A3GbWm(U47!X71##!9eT%#RT2>J&?BQk?SZG=fgB-;kBFc~Js-t&DydQ5gq41JB|wJ&?Pg)i1n7D`bKyo!%L' + '>^oYdY-UA?C(^rnQ5XmuU`Rxm!FYg?Na^&yr#|`{4a|`8-fdpuw<}_R?&|99>gww1`j|{6XLlFPYI+kKpP#;H>S8~bH$_<=1l>&;' + 'v|YX^@>LP6%DT+kvIuf0T6N{LTd&GwXXjtn)$~1-T6G5pJ39gVH(NFHV3~I}b#)a~^JTN@f;0H3KX$8p(axIHyj-=i#NoPYPO)7E' + 'LMQ~Oew|>=Y`H3-aAJ|nC@DYWbr?|tEfl3bKqJ&xQY~iX3R-}w)gsH=' + 'Y*}1|lhc1adGVJ&U%b5d`Og;(drs!wrHE&z*aah*=q-E8*3As=HBTW-th0k|Yt7fHvf77_()*Dl>&uM!-bLWM?v&?Cj9IWZ7&@1u{7+' + '`C`#@c~>=yc4tStyPnD?{sYt8dy&k`E-&&fmtW>c0OGkrYO{VMi$##P!D8XQO=j!G6r0a$Ebu}gI&JE@r1x!-Uri;pr}=UTV=c=p' + 'a54-c6mRDFBEN>Qqf&_Dw9ebMRFs&vMK$eW{b^^AqM4fSuu(uj1e(5XCC5<6xS%ohVO>O%%T<7i}YO>3%EbNe%=r' + 'KjMp>-ik?RTA|6TS$9Y&s{8o&UK=eQ)B|dP2)r@~7i+o-Ne@KIg' + 'Y<BCyZ|_{LpI)D57q9<({3^EJ9}m1gJv{%j_ruxYk@x-i#o?=?!#79S;UQH1+vYMSue@dc^7{GVPw`G9dZ}pL=FmuhjLW~B$yZ0!Y_@J;' + 'a?d5}{iVzc{)STo7{d0nc+YQ6i0$*Nl`DStGGAP;fm_kyJin>t3Y)XMTK%=Gu5Y@sIQ#%S<{F^!S6~6<>TCt8W7?gT`GViRDch6c' + '_OQL1pA_f#&oK~WcZYv@ixS0}kBV5&nx&_aCdDU;cXf@{E68tSZ2{E>CfXTh`SSsQW|t>k_zW$t5y)RbDqM' + '_*7?eg&$543;^ywhV7+%*<0sb=2@2<)W_Kf;2o3}aCs@L;H' + 'HqYDls@GhmU0=01vNbHnA3&0*uut4zZjXuj_`X~%$~wEsr_g%geB?G4MTHfSTE)h1mlE4|k>%^#s;=_Yohk>yLv`ERGkZ?cmkc>Dd%&NsnPIm>}E0@#&EzU&0K#KG&cQx<`iK-s&rbWWU|r{{YuFN~p(_yaZh{WqbZwLbuVFE+s-g@&R5`$sQW))$2sdkihYR8BN9b(Z2Cte0j6@R@' + 'RhwVcCAW_pJ;d#*TnPl*8{}Pea0R`1{rYqlrWNV|1dv&l@Z+`uDXXmSf@e>jM9EHebo}D**OwRB^Iv{_^=D#nFbmIM8g~9g%Qp0{' + '+A~;s2n|Uh;*Ao7#0x{3*v0h-6N1>X6av^Gl?GRL#!&&Lqr8Qwt4JKEd14Zl@ZY_ImOE=TgWOuRG{Cb8B|fACfCej|mEuGa{Pm_>' + 'z{}s(Faf1lQt#ebOab7atOt8FL!-67ORL5KhZxn(N5&h9vKTPj_yrw_wU(4fTPLJcEB{>PzVlm9>TvPJwHH`j$(_' + 'C>dUQ>S)ZjtN^4AhnQ79&+6v7>e>-}1m`sv`$%DP!ai(UiApW=f>{43@U|+}xI-1??MS(jc2?NDM#?PfqUbhvo{DV*151p$y0PFi' + 'A1T5FbenBzQywWj_68|Y>@-Pj0LJ~c8P}?*+fWQO3jYW0VSv6p' + ';y*p!aS&XA+$Nf}$~Y^#SB$wyhR0v$YZUDMNi1x9AJf;%1ABZm3eee5ov*kLNe@pvoq1l0?GTQJW&bk)jpW(R)3S4{16bXOJdc)q' + 'M%0)Qi&R>#qJgd6%gFnDn~#b>O~!68vEkC@kOY3#j@?`@+Vv8TBW023x{*Dm)|8D5t6ffytn=P8xK@cB4+;iV00N@fbz52=s##$D' + 'G#fHL7-U8y6#6?F#m-j)L?o9GZPR!jcGpm7(ELGwb>2deSgglOYxRg@25Hx' + '9v`idNS=cP3umC~8)8gD;=1N#hi4~=k`V2VCqo!|-bK1!(A>v-RfHBqO)*S$m6pZyCxz9B3>F(;LHu7#%Rv!K7u~q^4b>RfP%7_rFo%zvXtlg?SqQmDys{MyHIgo2YJz(2f-gDy%`Wh89_@7TmGw$X5g1@O^SV(+91pHw}?' + 'RsnmCQ~Re~)R^GZc(x1qrZ)Ax^j0%=62)Sw#8tHB?jer_u=0^I$fa_VL(wDMO{>xllGs!2Kg<_*+)x7BITvJffKI0|3J;S+*mVU+e%Z;YnrYYc$3C^' + 'FWP=$dD34?QO7WJLW?`XFhkpo7qlmPoombUH!)LZ)^0RJ-@Fb`SICsK?_foI3!' + '*#(Uh;NQEb37ydt7tqamHANx%L)FqfS_XB26)vh7OdHpo-jvh#%cfe0b74O1LZ?9~cQKUgJ?y{Ntts__Y%eu5p_;I5@nW$QKiA-vObijAbJhbL|=EUHLm*k3j@=8Wlr3dLtPc&BhizbM0?f%LgshqVSgbj)MXW*kx>=A1Q*Oi1?TJ;}1~1N@?!$+2x+cvd_X}~#R4e9b(CCw5R4`Dnx4>epZc*y|4&*~Y' + '(XfRFgEs)IkI)23OIfkOs~NDDZtodUi;X4;{8U=3dtgJ`kSQYz#A(i#{!HyM7Es1T%si$zG~&=U^Qwe9%8v(oRvW;DL(bBx6}oD6#cK*137?*o$3N7EEGG-@IFl?B<3}4}MEkpRpw2pUX>i{qu%VL~^a>^L0lz' + 'RT6=qDp=`FS7bT#=eok1D;NnHGUoo`=I14DHU_npXvB*9_ue$jm7O@zNJo3`q3y};I#*B|q{' + 'R!$QzUBp@KU3gf+jZUJBjaCbBjU;VSnt6?vQTn2V?}>0K^FVg~3QpL-`s|yQ2g?J>N=8sES(&+;eNuva*8!a$cupu{0zZp&&qgw@' + '&yTe|46~p^bG%ND@QypU!dtQi#teyd6*PEf{t;J1wV<15G_1*4Odv}r&5`g1bH^i#F$v&|X!{}Vv;49nNMDpoJw=38Ml#1tYGvg2' + 'WR}mX8m92=L<+Ez%fv1&IhO;Zcee4JX(V`MHv^Lo)LxX+I$xCzNO`Dffd?2kzO_qTIvc62)y29Aj$v%SYGyN#5c1mC*KGcz0a~wd' + 'Ig0)+^r&6kWz@5~P!C)0K*oLQkD8SHdhHl18XD`fPcRxrA8efo_b(`OdN!^ZaBeh*H5qa6*#Y*4)fYLnxnkM9sNcZd3*sl2MfV;g^h#>pN{Q%~sAjQZUEfu;3hQGmEpQ_V3' + 'QFVl-9n-oMG2i5^P|#W@VfouI!pzFWHON}o2M|@HQdjJ|T8Emqq-kQMGH%hsI;Py>=6A5Et' + 'z~5!~f{Tc~$5ZzV2q?d{jW&C2nir)cgZ4WT8qy2hp5XaGP5w)$WGcXeW2zL?`6pm{Xmc@bR;9p?G%9y6_DeVd0!iHM&Q@iqslXbQ' + '4nHyJe5^ayY4|(^3*VjfIe7vd9JiB%8bCDTTD`hF94Db0F)S$RvvfM@rchnz*Y0$' + '`L9QZ67HuhTY%CfMYG1|Jl' + '05%mo*h3F!?U-vs3{6N~2>z4?|Af-G_z4oo543-A&+uy$ZQtx?g7PK#Ox3^W)*+0>pvqL`^x!3b;WijRaTfiSt$;OyCdT-U-SkQQ~5v6&pTId0*&ah+0k`FMvB7`|z~@*^6oWJ!x~VRX+z8ka3a3SjEggjisVy5D=DzCQ$J0lgr4sBkiCNYOZNtqVIc`WFb+P3-Klj' + '{uJ!dnp7oN-#M9hR9FMedfJ>=>_F8U8Yxi^R-A9h@C36WmGTcB)+r#djIB4(O@IU#Vl6*PKbbc8Sk*PENI^#Ij2(31*)g>tg1GlW' + 'Gj-|JHIa8Lwz(W>PLwbo+5&nU3~r!|pXm+nmV@O`I7xmI(+W$FbQ&A3sQa0K|)$-ewkjZb^a)1(4`V99#p~&#DzT' + 'm`SyQ)i^XffbbWSNW>W^6=iG~sO=^Y(o9QU80z}S2Q2*rO(r^J&hxIC8t1Bl4f|U>O5yFnk7y@=GJ!p1>9;VN-@3AuaRCC=np=tb<)bkAQ9r?l$&lf*D38Zq;C{qYUBNW*GM6FzByz8+gj)FF#w+3lagBT1v2f+c' + '_9SwwHpKOi&z{3%AqH(rra~B)W*z`)s1I=?NuS=Fy}s8_ws%pwNktGZVufNxds>au4y#;<{uQi~4$Mv@@`rbp^' + '9)aHH{y0!^HcUvQMlicx0mX+TGKkAJkQ!OjLL{sl1?k{MUV?DWPy_2G@9(Z;LkfMnDtYKVBzlG1' + 'xM1(}!7JbUj7&bOiX`_2&$(AH3ZGH);C4LvVxF9TbS9ilsq#g$s2r<{j}$$i1x)6-FlcjaQwGi*gq_PgHps)G)^G{;^ua{UQ`z61' + '>a^hyF5sE_@1;w$bh7x&e`)@exh!-xyBF7tKQbrh}vEof}#' + '$70ni`FvEN{Km6@=F95Lv&Ms;a^H!Sv{MQ{ivg!DKyOyv8Ydr~9C`cb+8g%HGLy;V_;y;aixMwK@T5v?;qk@Ag}_R9{^V8q$O4;TRd{6Zs>Oi&Z5UKyh*&oOw;d0*a<&V=mH^+-jQRRa`CC{$OAaihY1^Nv{&qOW1Sh3c1DAx9z&pIB#m' + '&q$6zo{#X_&^Wfj!PLAX?~nMeu=yeL3Az&1=lKe+Xc0k-)&K(X3L|StOUWfN3sUX0<>~{zP?+w&`w0xK-Fj*LAKB^Y^%O+s%m&dCG34RK$??QM~$j)bByr{rvgRHK6K$WlFQQs;LZTvd6S8UmRDX~ZSrJ}9M-CxnRY#Ark`' + ';SutK@{NeoGP)M4AAOHPhOUAx5I}x3EgNsy)vYFhQcYL%V^C*P8CpbW4Y)PnGlgl)h?1v^m{ei_m%UBen|xW~Ezduv{lW9y;K@k~' + ')jWr{5%~lOV5WoJK~Mv86R|T;AbxwfV_Mj?WW|>+uFLRAY+w`jaB(;}MLDbh30NL(Q4^4D1lXe1@AmBhfIlcN$&U#~H=8=+>$=w^' + '`4VkuLQmh@I*NMAbfMG9yAUd~+ti*lM3N7XUkI6PVIVCZ9{{$8dsQ1OUmhJ@#KGC&G5m9M94qd&i-9ue3Q=B+qjpNgBY|bIFm~`O' + 'v{K5^GFg(|Aan4?YPcUWnBHg?`>E{;TdQu!=b~4pUm@H' + '{X++M!2u0+1D?^a|2}ruk+dDpuGX^|4Aey9Fo_L>wuOf!@FR@@+B9brGLPA<3X&K0aE`?=MvjAYoB6pl&q3rC<`KoSr' + 'Cm^JH1Q`2NQ>nJ!pV8Kvh=WWPbx3Mbzi2z(PDGj9{SO%Weyv3Z>#+d-b*QWzwPZvmC|pZegj' + '&Vyl|Nw*' + 'mV)5?ROUd{cncOP2|Xsoav>w2W*o(>C9KA!cMOgAqqGTP;4HaQ3+&;3btl5Y8Eiuclec7q;Ah@%QKB?8;Eln;bA04+aoxW7k7bxi;fIb-#XCTCJT_LvGd>ChXQ~)C-XQ2iITo^' + 'ATb9O7ZiS^anQ9qW5ciUwDSEC#{~J&a3^!yP?PpRINOGvk(+Zr^313Ht6Nm66#e&FKBS0&RdL%d!||2;;cU!nJE)J>5m1x(_7ys;Eg128zNu$$*iQY-?7ZthGxMZfWc}@JbX6>WfKCv$-Ga~-uk1B_F))Oe&|d}' + 'l&lx+Z|kyz&1ygT3c&hMG}gr4S2ZC|D4`SQsDGmB;eZO5gIM_19b8%f9(hkwUJ#s9fn8pJZHI=6S14_V' + '3c7c1q;eV>En7bm&E|eQtwI~o%^m2-c}F2TL9FfpAA^;{4l)G2zt;=_Pta*QG=|(f8ny5J;Cr3@%YbL7z)UAa11v4Wn3nT7TjZ>#byZLk6L+x)s*XHj0jZ$UAhggk' + 'Sjg@kItE??$y-j$UkNL0XVAK`!{m;5nCU0tHAtQlBI*QlD&mjqTgi6$CdxGbksMG(X#WMp;n;' + 'lED{wUEc-8dd0!4XXT2#bKAy%Rpuc7(*@4AZ7^fU-BktBXizS$^J^ID;AFA8tn+E9FMRS34adkc0o?+}572ar#}du&r@0Qaga1)#' + '9i}T-2b7luu~mkMCJA<`>coek?F6$*M{ZpvF4uCPjTVmo0;9G2p;H53~^3l>m(2fO)WDk0M$F`=C%Q++%ec=AZyQQypd1k$uP3-x3FcT5@}S<0{6$XI#as#U72sG1m0%)nc1n(<$%&QuK*CbQZ6eVGJoBJnsTf' + 'Q<$_<Z@a~3`vE(dd9I94i~s4w(bth*Fx04z|5lOqWJAq@sU??=&qXr)v+WxgU~`kcbD+ahQ1&%qCy01&1#' + '5S6TJlhA_u$m-9dAOv0tTZ|g5$nH`El2U$aQxH?k_zJI_%NgcTA7Otloe;ya;e_00UB5TB*I(B)lzLx=o*u@*KI{gu6Sufu>>l`z' + 'OC)wn)}Cz*U5`^}3=)%Qh7PI{w9O%L0zJ5TfY>=bhM9&0w*}jfMeoZvWTq0u1QmDZ5k|J^J0M!ejKSlxnctwXggosnb&e!{#%u*OU8}hW=UHMvpxu0' + 'O0)Y>&*1T4QtS#9$%Kl9?!GlL%ZpQ39Tol&Sw!Vv>ycSVy@BzOMbKSC2E{WzS*t-ooEHGg6|E9e%yY8RUCD*DSn@|$!3so75MuL%' + '5a0|Hqz5F2uU0q8wswimZ}>2_DPlwk%AC}S!j?1<2k*;riO&>pLIKz_&ZJN9ej?fb9`_&(&6I}zDZ;bQAEV$AZc{KgPw7SSgAZOe' + 'C#G^mn=}@N8s9-FV*npRqXcYUV>G}FQ0+=L2`>_n=*waSlJ#w7EP$4B@j(u(KtD1t*6J6jgt2+?z#>(;gKWRhvtUakk36sU2X#>b' + 'rB#EQPH5u($ZD;(RhS<=n}c60?oik>+C*zJr8MJs%bvZ&5MQKrYv9y#ZvoBNptXL?8s!1GYjogTgva+;xAaz9t&0pQ8SXu>Xf(m?' + 'VrWD}#F2FoN|a!1LsU7TtDHw;{HNdv-^V{Ocm?OsA{sT?_%7J9Y1v4%^;O4O+2A&BrTe6wvXP^%Rch#*tJ+adk!A5a-P0z&s>Fd4' + '`4fj|y4O2z;L&Be7dh%%;Mg8QP;oCA77iqtTC6<*K=(HyYaqPuM|gjGgf(9y6E=am%hP0YTX8i#(+koyF?hAELfo$K' + 'bS74pUKw7z3>rnYJ%!5C{mUVC8F`v{EHLS>Kw&xY*^Z=QYQiQ5mXa>^l$1r3jOQz)1aJjV>s!XEO>_(T3g|)i(^-Qz&L*#6Oqagnc}@4htMiHM&1sL1Pk~n6>&Z(rCuaJV5eBEy4_dg%U?fcTkx^D)AM^Ts*nC`+' + 'E7N_{^b4inY94z?+`lkxDVk7Lb)lUp30i`mii|$N=!a47PLIxI+D|k2j5WSYM0gx{#expL9m#w|4aC~WHWC@JaUlTNklW@!%5Tp`' + 'yJC{YI1nfzmC|yzSCDsQXgYzNat2enUb$Jb>|&Tze7qbP0drw|G|h(m6^OE0&K)5t3PqVCJb45%ySPUpvtxL9u6P#>d9Q|^QFKO9' + 'HZyIOcUfq*OfrRqO5dK+lC^w;<8R-^0c%+p;0vRD`tEPt1hX+{gMI(QK{;tb#A2}+&?29p$O~3Fgy{e>;~BST8H=^Rb#~Ni=ISa?' + '1V%(q?a-NRJkR|-LI|t4z6_rnc9qap-sV$`SJw;iGv54{(LJRftz$+x?LXVS{4yA1m^hX$h|$zhS}$+AdA`W6VQb-_+H=EgSQH4I' + '$YK6qifX$&TrTT7ky*oxv~KKA65i$@!)TxB1`&I@Zv^|etIE1?MBg3PN<&&`hzP&LNTjvK2D=Pz5Hp)DbpYqhJCzxuMAhMk#20nC' + '&ympF2IL9x24rk>TNd+w' + '$~}9G{hnc3q^s7LWu45|aO?r%`gigQdiU-C1BAnX{IC50<}wJoRh5&S*W=*z*=g{2$FMu>Q~-vElPV$M<7MS8x#Um-NpK{_hJV?~A#`-f' + '$_ImPu5RhtCzZ_5`!ybdz}n0K0Q#e&khZ*WJyDIRgND0?HAZ_|3{DMfV+{hP%Fy*vyr8}J{HAhDA&Tsi;hoF@{Cbi5*n&s67yH87' + '4fTZdLWTyw_B`^G#qd~moZEpy+lH`nYI0C9semwJ0ae9tc@?wF^dKGSgc-W*z)9qX5zfE(DtSVh4Gw@C=ty=5gConF4KlH+6Wkf&' + 'lT#$_C>n?Spn!bCC=SHto2(Zc1aaaG%g1Lw&ai;-+Ht?3;>lz%7B`#Y|2+e8>oGH;fZc=4IZrvqI=EWH=^E8!hl{(>diE~rqeLf^' + 'fXLnZ9~@4=RIDgY!D-f3i8$9B^nI^%p#$4hL3Po>u6O=f^x2babbIUvj%fYUl!J0_0)#me9uUMdkA;`;)vBB8EFbfh7nMcu74DH~' + '4M9+fa+=@Kc1zc|-A&T|w(7`w@Nw|RVc-OChh43>Q+NnDBYgbF>EVZWXh!gdMOBE|ihw032n&y2t9AhRN~O!La#5x-N`{PIfYYw' + 'g+!)wZIdrDFbJIN;RM;;sgtNzrqd}c2h;Jmf%67pV5)X>=+1$-55xH~oMahpx>Au`#|d`1Z4QlX@@S~=2lioHrmxhOuOy4gXw4$m' + '6iTt2i+fDpPRfRn>>~a#^Nd^UWKxr1Qgwm>pH%F-iQ6)qys->$p3NYw12g@fF_%LDEqXKhhVVD$R8A+W;BSoVOU6Zn8`@T9UQmuN' + 'W_WDW7OGvMf4qbpT83)Yb*-;rPw5>oJO{S%qx5|a(Y`aYCv>#wo+^71E3DP$YYbdpqw+3vLRoCgKB8qrDpt;y-CZ`#r#GdGpNAIp' + ')chTnO6a+h9X`k7p0EjU5wz^z%jP4X&^s#304z&C}D_V&}48=Md87@k|DBIV>`W3i$+!_@Q!?G$?l' + '`hm_0Y8>jX=&Un1k$*h;xhjX!&#N0GtA}dnNJLB~o@UGiG-zC4T%*!OBKjxD93=_ye#(2F$y_D;4o~jpTai!Gu}lWcWdT+@$3vfA' + 'G^^9ReP0$YPh+2y|8R!qNd+*dVY7zNspp-pUIv_OyRzt!{r!Xj6Xha~Ef{?1(mG1ISZw5j{e&hKW7J8>Nyy3rJ^VOJ-+dRHzC7idV&ZWVZ_X8(nlA?u' + 'z2I{X&o7e!MV(9-wLmi(y%-a1EEz@-o3WWBg}SkNj$Z6ii>x)sv6+l15{6o%QWAo)#(%i<27@okT2ar9jS%bNilI?Oxk*TH#*vP$' + 'DU~Mr@*i$%6sO9!x~)s6gLM6c_v1q$1k2y#kwKfE4xATQ`C1jNG@lxen)m3DxoFu;*{EiQ!(**V!ND-$7^1>5v}h_<6l5rZzTE69' + '*cnkOe|mlX`1tjCVofwRY|e(leUs#1loV0KP*sX%6l8y*KRcF)G~>UGO|_Zik*osBit^iIwZ;HUyvn2mbH>%cAeVz5myWG1yoxy^' + '2}#57)5MSxqe}>zaP$*n4h*Tp07p3G%T?2r)k48EF;y<(?pq%XD^`DPlUG{u_a?B>5<~J_lk!#Pm)E;(AXRspjDH@chp_2@WLWy>zL04`6CS9ax4@HN$I#MbE&t' + 'GO}S<88(D7xSzVtFAiTF9lklr4iB@_lUF`U^OrsRo0lqCq|w7a>?moJ@wrKkw;Jqj>O$mRe^Z4' + 'x)U~WCr9ic)r(IZ>fP&4=?2`R;J@}fer4r<-Y^~ZD2^`!Ve%?G(S`&dA#ykZjXtlMA1)sx+=v!<6^lLZ0ki|Ubm>8j0h4wqs7+PriP_9OodmpKRAiKIu}y2Eksp_%4aR#<+rG<8DF52_)ibsHN8WO2uX' + '&OBMO;=7*U5iM6uMC#^>{Q~5I=JjkTeE~XBie=|dS5^~NZA^ISEyS8U@MuDMCbK=2$|mdudVS}12B%V&D$p+6ksR;wab2@uz17xwk2G2dB5E>FoE#a|DSrokWGpIV4lB+ewbfz;`uP(IW_*l5jK@nvO6=PW_K<*7~4;cE3' + 'NZtwy>1DN8-v-ZrJvt2bo(4Bf+ZkdXUYW~h={rA|QRjh)cwBMGu*)1&oH+%zg6~?}ZC}YT7u&v~W1hBkJ*N~&ar9&_2s?7VSP-~y' + 'd>Ml6WpLAc3^<4()h|@M14<1;`XV4I^5_#|%qBBM$pb#NZWsfgSs1dGNmY1wML}J{e$D|M2)NO@e%aPd*XKbw6M8e7Re0tUOtomr' + 'R%JP!fdqH6i}z6pw*RqO6wOCkoSr*x7$VAWN!_M)0gGqf<862yEYPmx3S)Gjvoe-wfdT-5RkOan=|MZYyTC)ww{1Wx6xR5z2!{Lu' + 'QBtN}vA-96MUL#1)uodT@G9Y0gsT;8Alptl;enJz6W84s0ovt%zpe8E?LVi|f6ts=iiaC>6bib^Zv#?dk$H^}q{sPzLx$KRTTjN9' + '9IV$w3rBJ1;p)aLt+Gt_eg4h%S>(@x(rDZ!>AHyh?J?Ez``aPRQD`9;RU' + '5i%z?44mCh6zG}G$ofyZ@*^kbrsIR%IStCZ$VYX)EI2ukP`>QgMi-W01v_c5>#pCnLYX9s@FxC`MqC%W1SZq|b%`tBC!Acb(S?{M' + 'VGY{$!sZRa*bar!9%$3yVf;vQHP7#^O0^vP(SG_f>0ho+hlkgyOen5$j#$mh&xV|i$05$f#onH8;jj)*c#^wx?0jjJhjzdIlM?>@' + 'Uc$`=@Lt(wn>s$xX3MUahU(*eF-mSsjwr%XvGxb`IfnXXP@`(-kGIb5CW4>Bk)tSY@00wkYu*oJlB-3$=;gE%F}i@fF$l2UGt1Rg' + '1E<3w6Xw1umppo<9#o`hi+&8`h_Fxw1BENEWHyLfyErB5I17Uog#>}uU?mJ0gmZ$sLI1wih68G#p9Q#GfdpG^m$&6~O&ZTp`@{kD' + '&8XR8UrRG|mi=1I@_<&EM5LFefrtae$2JZsHZilfdIF4LP#v+AOXxw3V(b+Nca&bTTGl9R27AvE!&j^T5eOspKJ$3`CXst(b>h)8%!F?$c#EP4$3E>IM;Qy{Ni$0xe&clL&?E<*K;`4BDYMR4oQqNQ2FsX4q6X9xS8y(Qpx&' + 'Twx?kmBF8ei>cfXgC!)t8ZN;Ekx6<<`S);ngU_YL^9YjadKkK#dM<(eRn4q=a$>~Ilt1*DnDb;!B2^f9cn_(g#Pn|mUDVm0QpPA`' + 'gfa#xVgn@%Q^4@dZ#&~#&-6WKcx-017*JI06hB!2##K|Mo>+3q;b(-ra_P5J$m!Ul}UiG|vhkSXj' + '01{P~5yvlOwn^Kmb<9&#&|n?|G_#SoUqufexuj8`QW{_(=&s7yhD^y+qL<9l$Ux8)mOC;B(GUL!e;#3F9dzG}QGh7uF(s!E$}R#G' + '{C>d#702jJI^Lq>Jtep7yv{;`4u|+MUvSn1z%hmY$3dR%KE?6I=c`ma4#1Z8i3p6$NeL?~r}H7{T1MvX^&*3LrASXhBC01nG6z!e' + 'ijkk3T)+AwI!CHZzH~HpOQ_iC9$~ou>I#Z>_YrbwGv**nbW-{n;r5rZMkNQ|xdtKMX`(_a!eCRF1Ea#YMReKFf^b`w0f~S(O(qnrYWk(lJhp3SBYh(3O5%EM^Y*fZP$WtUuq' + '2GXv18nmqg;G`kLn{cafF!4ZDw|<*zTa}(*hPprDACIws0Y6(}nxT!Lc8ohmzz<0=@5m03?Z1qbU>y>?60Hk@1Y6HNn^DTQp`##d' + 'wCgbev_)hP%I~JA5}U-H8!42TvB}Ax-!z_0GIn7wFzX5NKHBI@-gw_B=wmoUtM3SPCe{=llO*M-(-gd;SXnZTu6b%Q$ug_@3DjRC' + '0vwXTr#M)q7Ma907w~6!#>>3*GhPP0}5w?c7Fg~ADj+4&f-7o?Zt@ld_>v+hY*RrN&L{@{P!+Ttr%f}pk(958>dBC=RxLeb;rYLyk=3$~JxV>uL7p_&mG#x3lAOCqp6;EMW2UY2(A*VORP@(~o38%$};pQB9KrR3e' + '1HIoKx@n&d2Qi`oa@Xz0fyyvE^v4wl$xJ1oyW>k3(Uu}jZ@agIuF>s$lkj0e`Wg|0DwTyrti8Z_HuNo~1tm(yWUU3H=pR~Ja{K6L*5R3v6x{U1t>E#Ut+' + 'y+LCG|2HxRA_HJ7H2|HG>tt)+VA$qFLuxfM<#7MAj!MV*d<)M6D!zy+O#cxtHiOY^Dq=F@O=WGKyhY;!1G0q^_jp0~YGqS-6r-N?4tUFkz;5(VV!I{n&?H3!TPiE7M0F@9WC{|sW5~mhWFpl' + '6&#kW3SxhC0ROhSp645C2%B(4WbJU2Fr&CbIb$^p*Q4=*%Vzq%R2iB!k>fRnO=(t}>Aa$($8*qjGx(l>^' + 'jgH51Rf61#2~^j&RbAz)JMW0t93i!TI+&i$B0OBqZy&;F^R5_8Y_gKrU>3c5Ji2-9i(yJ)H62LjvW7?akrndDVJ8@6BlUk(!0j?*UUROQWIA+fVOB<-M3Z|K;!v2X)HMPyWZTX2c$ZzK?@v' + '-<#I9T8m#a9~U4Amiav3AoSYSc1Vpc_fT5g@<*@LXn44t7mo~x{zb6$m=1gxZeg%JXUHrih!h^wZw+5el`#qm1SV?)?jf)Qu|F}h' + 'T&6P1rEQojLh{U)I{l(dRw?h~1l*0;D^2yd)D${7w)zV(&xMtk;sWsy=Sd~Q5tQJw-WZf#hbxJ}O2uD~WociIOXYOu|njMSpL!XrfIaN>bP^>b-$9Nu|ivh*Gpy8i;rBDv>e0+7321XHE05D#zg9(sS6WUB0Zy+BI_Cvybb}I%*WmI5Zl_9jh?*yN41BIGO~#0-s7ohHkZ;kh2zE)w}a`E2Gylsp*Fpz`t$*6)V?{K' + 'b$8`2+%tDehp(tSQ({xLDZI5h?vU>;;w%Z>@$xa@@LnbaUK7(!%{i3}L_)*XD>2TgFZdFnl}3Uij%g79WOl{8!t1NWhsbddxx2!V~JOsq5w=ho7RL-UWq}{kG)*%YjU-V=xrnw)w7+(^Fdv=Iumn?9f#%Ho0Ve' + ')r#wSiXmR!!c0w}Bgn_(9P@W8x`Nf-{R1p0`~pcEzdmpX~-&$GfD!0;8-C0SoHhi|PIgX{$xFnp1#6s1{xP$o!w' + 'yss-`vHqR5Y{oi~P&3vWkBx||sg5aBOa1cH5Yk;X1JSOoKJ_g(LywuI|L}uXAEZqlIP+?CvwK+nyBTj%x-!ei_A!~*VpaA|Cn`a5' + 'z^r9VM?@t;H~%kuwaRUtOe_4X!J2}o7XODV5{wKv-fU9' + 'rGvBG=S(x?gI;1Z)UjXyiqrGM?D^|gM<*92uV0#G3o7%uY{`32T-~-;Sm~rYjeP' + 'Cj`933jN!xl)p%%?_SI9Ndm!sz-6dibbX!R?R4RE#!2HqRmxSxc_QB0L6' + 'R>D8t{9$HFR9`}J?D=SPRU;48paWJKJl*U>l?H!&vXyfd2pMM5Q1AHu$9pb~ps?A!3hglJ@|N5#MW}H+4NSIkw%LZ3?0MXpc+^Cu' + '>glU>umd8x@9kEU{ClZqAEX=BbDQeNe#$DI+*ChqtFJYp$>9{LjTB_IuIolp>2jJ-64-nzbb153l1Xoe6UUGAaB(;0u84U~WO9`=' + 'c;4jNsPmz2^052|VI05Lnsjzh+C=2e1F}mTobi;N@P=_Ncat;D83?_gFJpcU(&B<9WhASxKld*%x_KkA=r#-+&zl0AsSX#e4CAeE' + 'h#BY^Ct%8-&O=#=Gq5U3NPDyqZSA}P6;^kA^+4Vl^gBvKX3uc43v%&f5`nn7wB!i^o2&mJ)#-vo!3U=)!=QQ$m2z|-))fBFSd0EB' + ')UyevH-sbFXBV44tiHs-HGuj|pyB6a%she^I1I`V4Y?bDoQJn235Lk01b2v4p;SJ^(r6{R+>m*%zvcX2y21Po4E$g4gkMdaB!ZkG' + 'x6BvWvMvg8%%WsCaGWLqd%MwXSMfQ`A~07ci)>rUeIzPw9y5=lK#z477-&R}2^P$JL4T^hbQp;GGxPVGGW)^09$T0|VL' + 'w=TDcso><1^%nQ#jwLT`eUAomz-4R+#C9-c-Q-9HSd|NY+qADVf0*I4YGrEoUCK1bcF)XL}|wPnrxuCo~bYl6COjks9ev?PVwypblO@9KHal8KgY`ZtDxPPA0Cv$Y+sB$HH$z}_Mo#NvLXjbhUSMW?S4pGHKeEybv<{46x(r226~p3z~JymJp~*gy$`!7oNA;NG+U|A@fs' + 'vibk7?fB1T^$%y*+v`e%nt#!vHXR$pY*OLxmuU$F%7JW4!}6me`AAvxfm>|KCH0Sb$iKa9h!2$N5u161o9*5A@d{=o}lIO>;@H;At3x$R)kVif6*{H-UOBwabR<=(WdilHkS%S3Nu*=|_' + 'U~D)0hJp$rUB0#J8p#fgv<-UI!pVJgT^6B!6UkTB(0`uL!4(5fXF|%SXmP;={pjbY#>fPE7#|7xS)$xd3;K&MsEvD0nwOYhw>2>I' + 'ie)1bnIQpSiiZ9rub$4-cpj1wPsgAYd=|(tob(WVJx&tVmN@NDKqNOGbf^mSYKg+WlBo*Egt?1Tt9V' + '*XY6rVnCHIuIn;j1F{5@gKK>`yXeeq2p#DrlCf9#C-h)AX|BG*?k' + '1Oy}ygA4UScAe66&~b!3zSHlWK?mx?q?Q?{Cc6-{c*&)TCP~W>wwb(MzxH1>rilp^`-XmwaH4$5-C*Kgqf8LMo&K#^4Xn!!0x|7Uttk5>8P`^Q(WU>(vz=7AU$R_AbGdOKlu;nb#I2YIg' + '#bpkfdDW4zxiovtZ>u)3utjTN&M`4SVc|UXbbW&l{kF6)$LJ!m?GZMINbm~tjIfoQ#U8Km#xx;?^KF25#>-e%AtR_`6WnXWMqq=m' + '@Oy)H)={`cq7Qiy&r>iwTCpz_Lre!!9XOT;iBnqxd7@QKgAyh&4Lo+|I92bww' + 'pohkdtw~85lq1c7owY7jWANC@Ydklyj?c-dNQFfP!P{B$A~%^e*mm&R*cod3D~3x30e*LH#~08D5&fek{JB(X6DJHAITZ!eW^_X#' + 'LoMKrWMIVY?@zyor}yMC!INs}i?i+7)Qy0AHXi@g_kPa_!FRu<2_3qXIl<_AsNVR5zs*wEm@wc76ry3T?D2m9P)h>@6aWAK2mk;8' + 'AprceTKPf=007S<002Ay003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*pZ)9a`X>MmV>WaCyyI' + 'TaVi|7Jm1yVATa81sY-7eRJW(28ja%+oavjv@hNPK})o)9o;HY$y_@Bea|6vAtg$7wh6F)n6aqCbNSANhon8vyXL<)yofnrMM7#W' + '(xl;}s(F&eGA#>o;w%y5P}XF7{r+v4CEt_y#N)fFObaO@&+}HRLtW;C(nBMgno~;Byeex69E(yil?b`6XRpHph' + 'YFdoJ3_uwb3552pv0VE;y-$nviogfr_m>6&kVq!(hz=NtdU@hTm|vBj3Z9_xuZigLWw1gg0dk{pj{(&&Lm`|I1)xv_cl<$6S<>`a' + 'l#nYBOj4avUX*&!suz#5V(0xvZg56IQu$%nGw+IsvT' + 'byR~@DIiLZHE83%WtrfCO)~HUy@m&y2wY}jATbcAz)0{l69r1-CJ6@1&hQC|`o1Qvm~+_ZVV%nWU-Ie1!M8Ozqy@i{O@Rs8R0$v}' + '!0*|E@_ZWxHA$s__^KLdcpC~(Id~jD=|QVQpxj|NJ~>1Ec~A#GpswKLHKg?mwW`iSO5P+_>8G<>c$}M!Q5;t_-ynP%ZwGrh8;OowJX{)!RaI9UA_nBE=xJX0}3H$7buDxuwcd{70UZ|*!5Br' + '%?wI?%&S6xgl&kwWc5+3^$Pg%<=vHDO#``>U>QrCQslG' + 'Jvi&ywP}Q>Q_<@ROG{`Op~rR|DJ~jzKN5}AoK%N%Dz^&G)C?^c#EvyFEl~zQYlH1e%zaG~jx-HiQFsIE=Qsq1S|UgrPS9+Q?e?sI' + 'Xseo9j=#YLOhhxQ_2<^@b8>67AzRhuL#v_siyV4D' + '(iBHlRp2_^`d_Km9~%zI0mr_u3g{1^*Ms&0wkpEzKzdEV6}F#uPJ?bO8rx;g2_Yh2FQ3|%fQWlW;E0yC5Dd63XPP(bwIRirBpGMW' + '5Fw%+V>9*jwhHrN=h|ZR$_Hvw(RcT0K^$oHmS(&-$`dfn*}b2$%7=lllNAJnd`q$)$R0C;7k)P$4M?CFG)>T9dKY|Xvdng!Z%z%z' + 'vV+hfo^Ar|_lYV(T2xjR$I)BxX;c9JfuK4UfDo1vF20-0uwxEz!7^yBH$&dnV%(I5^?=p^kC|' + '5O{(YO%8iV$$L2L$mzX-i1jnI7ien7dZ*XU61UbYZhKRt%H3(P@41IlNF6ZRwOSbt8ijTy*=Wuxh7FK|LtF8lyxuf#O$_)wn?1a=' + '2cD7Vz6AVQpA6eeBk%g-j7i1pMXlR{7mfsU<4`oQ2OBD-E}`9|NwxgeWUY1kVZvIEV=YDAky&c`*-??CIWGF&fA$v4(Q+8Mt9D{1' + '_7N)2i9uiCE7yhH(L}JYs4LrtEQu6@V19K6dqzp;?A#}bX@J1}X_0W{BF`qmw0a2PNaHU8=tyaWYK};nVT`D`&}@-(=^H$&L(R{|' + 'x^-U%;fS>?{Wi}Bj&04nsMNKKpeZC`#e)wqy7?}M7%T(9xrK52k=GAoHaIne&@oBuBv{u=bTUVs@n8n(0u7;1MBQ#ZU%&R^`uhhAit2xyU5R*jeAN5iu(nlUTdZ$rFPMwTPVDA)Gv*Kv9r<%wO%mun)Il&tt3JTMGnhsKAUU-NrrsXP>Eu;w?evA^jOpvn+WCo?grTDkLgPcyDGPoC' + '1B^we?>9O-+p|D*UNbV(dJ4%j{k5zFHn(X#>6`{p_SEUjaIr;#9u~IA$*4b?w)}mcG}BIx`X#U*{|(d`+r!@L92IOCQC`nlvx4ZL%MgY&k+{dxA+ca7L8>V2vKaO)e' + '#{8vwv(}|5`qc##rQMB_I-*&RPxaeSeY>@AkK3OswueP+ShV@t<|oA4l6jFMrKjLI?u-Bbk(;~Z{_3!G^WK)V+j}}A' + 'FA2?Xba@>N*SH)Hg7h`5ZfVql%7!x?`_}x--om' + 'Ih?iM@6aWAK2mk;8Apny~8Fy1`007D80RS`r003rT' + 'b98WQZF4VeZ)9a`b1!9cZDwz5WHK*pZ)9a`X>MmDE8AKygJ7R&3ZI6Z5ky`8-$(T~OAY&9N4`(+u?>pH6PI0s-dt#6`N#U!8C`7~P1r+F1MXL+>0%qH+hd`Y5T^QtZu^Js6d6UA6yTYTE?Jp~BYi&ZqsuA}*)iB@$EAc{IVDat&`uO|7jiHdnN' + 'SpeRvbqt)up_e62bj)9WqhCp>H$pnq`iqlm^^OolI?1Yv;l$1;Ij7o_rRlcki)77LH' + '7iH00%d&NzPg4NbdjhM>l~?oZ' + 'A}h*lT*6i%AW%95^jg8@7#Zuvzr+_0rsxgk-+d!ssTZ(n3(4FmZm' + 'dVBqW)}}rymNgS9nq^p^ewoitfik^-O>wp_CozIQ)pbOeokaBlsMmbDxXh;jENcKW)pSR5KsMOY3!r*UmB9|3oM)#%wdPZNi3AWZ' + '0ZR@vCK@{n_P&BOfKEYoB$o3N%^wnl)OeO_U`)A~akJ6}(+1<=qe9HhVkxLly;xO~ypO~a2trjfFx0cWT*8?!fQhY}x36B;(R7j5' + 'bO4Ou7y;x4s_~5&Gad-}>?!^WduLe!g*}7)4fWw9B66>ZM<1)eB*A(_xNoaP8J(1iO8|CxCO}0vQd}{$no#2mV9fv)P<5DOn)Es^' + 'Px=T1UgKd0v>T2&4*}(SKrz6u6i=rVPRL@q%1Tw_1r~Yr<`qE7s`r=0UtaxUe|ugnmWx&M&Z+p}-OCR@ze)dk@b1mQtM?(C{Bi%;' + 'Uk~0qPhb4<{@^*h-lpDctM`D){~KT1Bw>3i{dND<%jc;m_8yAF*kTe{0QDon>22+Q`1y6Z|KY>In-4FcU(fNBMCemG&E3x*U%q+%' + '^GEKM#Cz4UT^8F90Z*b9n7ysn@AuPZKfihY65qdk^OyAHo43DwNZ;(gKA_B0FN(TZ;K4NY-^hXW' + ';5?h=C$Ra;0_kvio>%j{OvgZ1^7%9te}_@sRMGz-bn8X^KN$xW@GURk^ohnfa5})%tdLOw7J$iq4Ld4FR%;Hun??*B7y)ExfQ01}' + 'py$l`h$B-pNV^<1t%HE>8Tl*DIvHx$Dn7iK`7{4dcDFFXX6#ql@yEycW)<+{7!v%c>($uom+>X6l0shQH*VH$f' + 'FVV*6TiS!)Iwv@Ee3Bu5+e-9^HMT)o)Kf8=f{gN<1B0pRbPbm%fgG)M!rf+wHa+Yr1L|8q=hXzaSb|W$<lW=?;@bEzLT3}1CsA(}@u9}+r' + '%>v2L5j~hRKj1%5e<E`n#5SHj2TcloD$(nN61_JP|1jvn?f)nU' + '4cr+u&?ATaJEun)4YFFlsjqaT4{_oW>z#YH4+C>+)UeL{p*6uqDTfOfI`bSVNu0sZo^3AoJN+w=ZHn?G5u+Fh0!=CYNO;V+&w22J' + 'qRD4<+{398pBQWD$d;2Tp>~h~dn}l`!uKl(Ii5;~rX|}ookv*%f' + 'tsN(JmCaOzv0iaTYd(iJpT8~kCqgNeqaHFL3nh}Nd' + 'uRTaXt8rP>XV5u?1b~8JN~0)~nMoHAW$GT6!QvMtb1R' + 'LT?0*N=kTJ7K0V&@cplYYIL)uFeV13!kvH(R)+Va5O8^rF*7s62u|MD^fM8sg4{Z^W5$a`X&fX-&yoEBhOL6x*4hCAjd}{6Cem*p' + '8wB)Nu*9%V6X5)4*XYB+SlBkkT@}F9Js2Ctap6O{CBmud^A~<{hAfzqR@b;+j~6KP(j3m0M>Q9Xjs`&52E8^Sb2e2' + 'LXH`sQV>8tgpcE_nVhB5Vm8`6KIXXq$jJ7c!H?53C=$|}K<&6LP#M`@;xHE%fN!X=5lg}}znJ$)jTET?X47d8wNYhRljsDi44ZES>Q#3n3*lXPQhxzp;RLiClT#ki%RoP-O@LzmMUG-EmLu^$xpj`Kj0+1M0q8Wj4)HB$1r)j^8b53z`swv' + 'd+;58_~AcUeg5KL|HCiu4zMi=Pd07T_1cDa&TwFP)G%m`SdAt6Q^Ptz⋙D`}D)#-yZ03J>J?%58k5YdN=QXj{|w0pJc1DArTe#' + '%I8%!#mm=8B(MIG=;ybu6WUaG+V){rz&0XgZUM&_UvT3^{YzkGvt=DE^XxpD<+DX~jkjWO0>iNmwfB;pogEOS8rIm&qaSvnzZ5@4' + 'e|h`MAlh%DarS^Yal-BJBJ+BDSEsj{f5bR!GEs4;Om!T~y}T1(Z6=q3a905$CZ0oHDCU^BPB2PRIEiHAncS0Vd@w' + '@(?5;G{jAl8kay4+fB?V+A3sq1H|KmF0JY_zF7Xe0$9-Ect!dph5^*Gzc}fOf#-PYLW%quS#&iAPj+?&Tj}$I7yG}w`T*5RbtMhIenEkvDjmI6s7@' + 'cZWv`DiGQ!P}o__^U{xYLtqYv1|mS2BRWP1HdX04Jr=(PsGtNHM5qd6U*u-w`K#+%~uD}@>--Ip$8<0x@dCQPLL?KB6L(mi@L<)d~2i?d;XEqeYCXrSMuH7#z' + 'X$_H=5Fv4ai{-L9!yAIxa)g&eDST#gpi!_<#r?x{fEu9l{4u$x8' + 'F}-S))VH^{f2s8BuL~l@h#nJsiYDnR3zy78KLy!?^K>-JQS}YFq_oZ7U>mi|Qo_r@-WJR#isMkoQ' + '8<7fM01c?+HD@4kUtux)Od|ZhJsRHSP)y-5wPPYE?>?R%XN?j(Q=vttGOuwxLA0(?tA9)uEpf}{WLp=TKhOC?&B{YvBS2b(rIpPr1%Oot}si%a2QK30Y~##RS3|IM0KL)v=r(nGJ~?SDQ_gYo^IV`T@w`8V%(a^1*@(YknHa1M*-J(!6%`;4E)k4M9j5Q' + '2H$ZJ)k|*7=;=W2=qjJCCV3k3=2!EQ*z1pqRxCH#W{W_;=SjJmfCnKTU54I%;54^%7bSM{tj#7Ds8iYn1CRM&ZJ|?*D{_#MdTaO2>4}PHbqz*@IK3BqfU$ky6PFIUnO}6QL' + '*~;zK5#L)@t2r5bFdc5rvWEPB8u;FreHf$aDyy#Bj}ATP;5je@83P+4m^55;{y^Iz1M@tjcu7;j;!6lFsU(H?O2+bZF)l6<6_n#+' + 'l#YpxrAx`NplMYshqNrtbGsq8&SW*KS2IR77L7susGhon' + 'CG%$b9ok?AS4+McV##@Nf^K8F0kW4Mw-nN1<3)0QkRumB|veoLE1YyNnT5!R^6D0d+{6H~*7xGmu`KfvDYMUSFAtokb!M16}K7=ud0;G`vr=m<3$_XhRiq`^Ia8}84dF-zixBKkza' + 'cC1Ok-rR%G`$AkpvB4VK*&uB!v-#;NJI(vd-7c#I8n40EJC}&r@Z{a%QaMMx1y&uENCD#$DAq@t3&RL_$)$%8`+-466J(y~*PzQP' + 'gMD^{A;vUi`w)7(nb`&1tg6QO9-cjVVs3&s0(1=$RNbyA`x>k$yJgpJx!V7t37#ntwp{6d8A|S?lylL08fm>+X%dOaqN1&6k2U_{' + ';j9GNv+kdzW0WbUSQq0`fy;Lw!y};uN~}VLq`(IscPm)xWL1`CA>`}w|1^rW^j43Qe>^n$AFObhv-dNe`f`iQCY|%uu84R`;uLo{' + 'FVL1R-X70q3H*QB|I?W4wFG=vT@#L2udR@sghZ&ATd=D!I`!^>lF+qweJ~s6(`jDa6Pd6Z2~i08RK9C*OJPiB6Ilo#$bKMif530;&BZtTBhlu79}}ktF^@(WdbSI}F-IQm' + 's+4@;7Q@?gvlKnsh43SLZ;+I)3On!ZN+hMB8BR(km8#^tcUqE;Kgh363p&J@kS4e@$cGl?HlP+rdhNRCGzv^*wOHoqI73^dif{0*' + 'b&uQ2xbtEa#SP421zOh_v(-_`1-L4vMll11{lTuLzO@Xf<$Gcp0J{f}g{rz_ma${7sl*h%4j9P6{}A*=NenV~k}emu|H0WD=J99O' + 'CKeK>C;WMGhBhAIZ#ml{hd)vW&`*OvwDr&x@TJlza6n5^CR`rFoavFQ-@g_4VxK^gaG_04v>G<6pr!#BjLG&tZrqD@p-x$h@t0Ut-d_9<{PW)LmvKqmQQWDn$)_u_cv-KLMK_9Utotu$%Ux' + '2T_dwR8o8fahh!p>GedvR+@?zWS+yef?RF%crOt}M|;C)@4=HN0>~4zFMRx;T{iVCXAKf!WDs6=8Jp5lCT|$xm9q_ykSrR' + 'B7XS(i9xP!CLM=aq{zj>wsaE~)2qmv@5a@rqkc7GT%lT|aLftQEz{;^(Ey)BWoSO^' + 'F+FrT`_Spd!w`IX{&G5jO~Kq~Qc7WKWb+nu6}?PJc^_aGV2h_AE~V`~cR%<^+%otLwP*$~rWeakFQ3=?RIVOP=ypI6J28YuJe}i0' + '+{zlK^tkxMlPlL<49lSJd*qDdzi$(p6-bS`)>L|++rF-CKp~u(S`T*UiwoEAH2kz#r~5bVvkh9Dv}Lh$fWHQtP!%YB{54T>zc*mqou)&z?8_-k(LH1cY3yH;v0hBv%qtWir(8xEGc8#Dj2)bUH0&&-QK@=wd@fEw0F)n|6krFO*}jds24U^' + ';kDhBYc0EMuQoS&hifg#TH9KOs9iq_(Yl8nXZ?*AI8?P`eeF?%_hNT5;sQp(TC+H^J$rFBZ78%h6K8ECOQ2;AYt9a>Cx=GJ3!9|1' + '5Z^qv7IO{|bpd3_H0{u7uwm#&?mj}HsAY*0gH<=V^w4yf+GApQVwyk)p42(ty`1%)l+qs-Z;@Gz(YEB3xPE$#w@>fSirIY$RA$p5' + '*=f4CoQumZx>f?ND4mtdD1J|BF;vvy6)|&%N%WV4SHC3Usn@ARCI-v!1U4En-ij9Gi(7~u23(7gRqcCC`}b3F&{SU!+}>0gG@_UT' + '(;SI|)KCv+E%sjvaJps-l-3Ket2I|(S4y9uW&@Q{DRv&s?TkUtt%_Eawz86;)fkeJu<8IAQD-oEJqla?LNTHzZN2Ff=f)-(LvT?MB' + 'x2VL-(P_}o+8!Ur^R&#aF|s9DD~QMk3IbnT3?pM_zQW>f7U=&Y%r7NXk}nW5zgVJc&xQ(%Zh^EYCDoPxAZ(+(F9K5c-TN0)uOf{(' + 'jdwdqG+)icxaIpFb~dAcHU-lI@+es6ppXOw#^u80>0eJA1509F__wS|TTlZVd$@IlKCx+3tEZ8KBw_txy-W-eDyg<~Rc%vXoE6h4' + 'NGo-NdiKzz2#c^?w&`7$A8*B(h0*;E-oJhpQEww|j&DP7E0!>M7?Dc$7O-J9)AokGp;VyjKRLUNX`Hp^lohkI{0k5BXX!ZgIsg*u' + ';=ub-`bGatpAD>X>1j_kiNGo2IGHM|P;2Y1ef|*~65_Bq0JTG5QDfl!%bQb@W?HJHHF$eyUmv&dYC3Uw)S;vN$;@CdiM{c3D&y' + 'YYoGSt(&IHVoDBa=r`k1#6LG$qG3e_Eq_8R7!A3v6*nKiEV7*2U1&ah3yueZtVlsy&`)TM|3Cp7mDW4j^sKinvP^j(I#D+N(HQD8HyaE~k?H+`j' + '0e@=Vt&47?Wczrdm1QP|JtD%C9{pmig=iKr+gQ2{n+#)kVTdRXM8R&xkMMI_!sW#FIkpgM9g}m~PG!C^6N4-*S#UhEQ' + '=HJ%QENd`?JgpEdMMNw5V0wwJJtI41A9tf3wU6JHL|MX7$h6-t-VG)uvJBED<}jt-7BYDi%=Zq' + '+oA_hah8fuPQpAsA`j_x92(HAzVHs2^4Y^pNL~%?R#35H`V=Xl=-wGLb5J5W7EakX6B$3yg}p$&iup;pCiM{5>Uor%poE6O=%ies' + 'uDaV9GQNoojYF%}T;9wW!xu@rJodbmD%y0=0HOOG<7-dNXHyA1fzh9$ouNCqUr3QF;{f7|BwCJir6iJ}uqcjSS7QR938-J{?v)JE' + 'a1wnNs)EAVl&@I~B`JTg6o#Te^gnRqTl#!2PT|rq3`TFQC{vcao=wcynj$U-YG8_~>qH#7BJQ|uo-NAMH`C+0DBqB7nW5#4%;}Pw' + '0dOq2u4E_FbUCr9i433vpQQJjX`>PH$a%M#GhGqE*$_$ENCHoqO%9aj8{5ty--^6baf16^OiUfcKmGk}61}F}63^hDH$Uwq5xT6P' + '6)46==K*h7Hl>m9lpV|PcNk5cbPCuhu@Z?r`xqwkJQtZ-CX2GnmNh3cp-@mTkFQ<}@3|$pVzRU-qQ^_eJIY}7n)7D8dQGs*2+a$s' + 'tVB&V_)9pdx$yahgSA2KS2(s(*egwZCH0nJFNruR^FyG#6w%Y9R5G&J%jfkYTwi56Fo@owc-esZJQIfCf)}aka#7S2ZwE4%rOWWMx>=BCCAj36=hdd1C&H|MR$17^|24F' + '>x)a+f|K)@dpb^dK6-2nDcHg#hHq7sSMvgxf)ruk5U8697=MiUBZ)eRAf~A(Y8=ojgqzsQF;5u;Kr|X+C+VG@V{IhyRu3j)EHvvV' + 'pS_T?9=a&^Je;6q6s8>7(st-Jy7Qt?dZ-39kZRhslW!{`ItC`&82OF3>Z2n1BeHMC)ME?sC^>>Xm~uPrC?<0KBoAh' + '$YZU+e)EjhOsaE8Grx{Ubboh4GEfHL9_kL_t_W-yi{N*4QBAFWC1w?9W5(&p9A|i*H8G3WRSXj>^)Z9!#oHe!H3em&L}kB#R$`S*' + '36eyYpAen;fMJp02au@f00Xn+Du$~D$=rS#dK}B&54|dVw6A^i$bFASD$XlgNa8cpzyj57EX6p{' + 'sA18w>qT`6$8j=5J9a0+McaQq+O>l@HN6{3MrQ9#fg_2A#-_AH5yV4lQ~QDxQviVt5?`PRkv0p6V?Vnj9fI^Jdy1oxg7RtJz`g-S' + 'W1$NOvCsqt%VJSHp77=qL7nImE(Cg^`_-}WEZ};4_CLCEP' + '@!klcX;x)I>X4!y9#^`@s0{eOvYUP*4x(Go%{ddDNo>`Z*=^S0Q2s*BB_pqmNR?&gb3$#Ph}DKaW7XowY(6p9Z)E&d##@%6k?Hx?' + 'maTuEoQp=Vy@7ob$Xu_~xZxn2t3CZOrT^(zd1eEK&ITl0bP}_J!r9uMfReA}D6!fI+(^2r!W+_v*J*Jh)rlz)8^@2W&j6tQVa4Ia' + 'NNzF5gz+*g+GGf$!=nL4e_jE@yGwCc_msY%W#D@HZGs0qYCrms=&9bd!{4&AML-*aX%0g;i`T5k-Rt~6h|C19t(@e)a7yy~u`vuc' + '`6EV72w5A-;*p&;J>sSk4P@&KEh?TBYzOV~BGePzM)*HJx4Rpk*&+QaK9DXK+Ln0H=&qEV&h&7KK_f+6pRkSzD' + '&M`2YB+8fd2=j>a0#IhfRSG)up{3|M-vZvYp5oIpU;ZMhwcFhw%HAtBJ!^7_gcV#3|ztFVut7_Gr3R>lbjh+' + 'k&Faz0Yx00x#VdCG`is&YE?1iL_r3ucxM%brdeuEGsn{lt0ruwe4M6A&e$3&lW)XIbbEvg|rmU8q*{YWv2xil(Euvuv7k3FK|zBsY4E' + 'cjnc~2(_Rh=&G*I^*fGkD&pwoQo?~IfZ|akLm;6b%1^S=xIv3Dx@+eKc?R+3Q5bT;WTIJYR>AxApoCXVomIjL?zD' + 'H0Zj&my1+&A3sgO=IsR_JmpUKrMc1Yf=0fTon0b99~1cTy#HS7>Kq&' + '=ed?@ZrOr?TziaPI?OpkQ4#5bL7W(>WsIN?xK`s+G;>T(E5kZ1x+v;`Z85`cLM^Ag0m(dBi7|%b-h`?=(Vs?BUqZ4ndg^L^UNnjk' + 'tiO+R7j!O7{YppeZHotDBJy?Cw9dc' + 'j(BKcrZM3CP65^Q?D_>rKvXj@azszT*tSYm}xGnbfqIpGm06g8b3=WQ22rhA(0WU+zHt~nF!FFw~p*)qT1pjwdm_`h&6r=P5Y^SKRh*JD^bnX#M97e$--|lJw1aZoYPC*>m' + '6xbCoh' + '0md;YHbZBE61U5jMi@S(Ft#VTaa=4`>@y*>lMkdtTX8yD6w}yfKID?#bJ93BmoUe0B-VmeC5t}I@Ee|2FcBB|z>Fs<2bYA#?sr)H' + 'cwmtFR(A~D-7Skz49S2Bw2B8pCuqFaHLFugO)0L0>Qr*{sYvJ9z%68jOLzF&jBGXFVr1q!QvHSeHS_rdBvxsegJ}=`l?0QK}e*FfZ5HA@We0IRl' + 'HMKfr$;s0cVKWWFS~N|(PIS`vE}3bwKE@ksP3+lgt)}f7iYtd;;z1Z&2WhN3*;_|~u~l7MTXFJTs!-nAWX?WMJxaZ%bgw|MjAuwZ(JfZHO8OgVBt!|$fr0sh__mG+2y5=+@%0k0jZvjAlgiL-7@o4uD8=7ia2+f`2$>QtjGCn%_n-as;LY>&*)IqG`)(EcqG<<|' + 'p=`KevW<;o*R`oP{FJt?R$X*F{$dKyBg)PC&x+dhf<5LxU%qG>2e00z`@inLe6|1Ms{@;5gpHh6O_YX)WbndH>Jn*GrZ!1ufD*1FHQ;i)' + 'NYgeM;>~tP&+4?C%Opm+8G6y$FX?}%ngBAQ0&HkcM9ZSJt+*E>fT`9fI8}jQ1UyG0FK4PVAtUkEFJMf;$oQSK#XWHCb#1#{Bjai~' + '?1EtiIbQ&?oK54N?=V1Um02*qMuYKWm!GSDk(n+O30+yL*U6noRFK' + '5DU>G@0)oI9C@w{Lr7^gXqgTGW_HxUBvL%6^qz6go*@wskel``2`nOvc0|-*4zvWIVxE`+T@-P((%3meeDN>{(I(32B%2dc$fc0!' + '3`TZD>0ymEg#dy2h6_pFuQ&~8pd+sR{MBG8orA@l5GipCLlyHbpMhYyq4e^xY%4{K$)}w*3>a&WiJfG{>!Mu6SG&CjS9^)6JUJo%' + 'j?2dbJJ*VCg|j_M?%=6G@dCt7qY^U?5^;3UN#w15T{#i-DXJjE}je`=Bc5l^_l;' + 'mAsvcJ2Do^x;421-Vqj(3->5nnDFtRtbTm8|DGAiVU%4g&NINC*wGOe|&xAaxc>cF7*o`W51X4kz2W%QdrXQ%CKMab=MM' + 'zIeo&OaTxhY&q98+(TVbJhv4mta)tEA2%AQP!Gme=ABv4I$0*h=C(x-35Xo@Ed#;6l&1Fm#k{' + '2*`YqSD3d{F~A5A-Tse{5s_dzNDmZW_0TA;UPPB@X##~-mPuaGuuzZGlrATjxt@Y(hnCq@f10B{8#qDDX|rJ-2w4UkwiHGnBD3c_' + '&!@r^MTVuqq4oyBz_<=}vPB99WGmB2^he-8G^cA_r6K`U8f!Weq_)~5y8#M+<(%}A)$!?chb9aTJY_Xwr_K8zyi;Io}~+S8~5uXm8dw_q*YBebril>@F;u3q4j}9UNf8km}5ed0fjV_C|3Nm_O1Q+4d{6*r+F8dS)Y8r{~XML?$tVTN=l|Y4qjDLC&376tpza}2UI*z4c|64k>zQyFJ%UXtgC4o{zwtFWz6rsBm' + '+Ddyw724uJdFZ}VEynmOI*IhzEEMxzo~y4^Y8i?n;ejLkV}uUr{aWW6S)HOsj&tQ~QO$Vc' + 'ls)yzp0<{CEmi$4X^Yjisx4!YW~<&v+M081SG{3%Rx}J)or<*j0V?9;n)hs6dy*`5>zZsTyPm49H*>!eGN|Rp^=BA0G4%GHql0O!' + 'nnb3Tcc7y!CJJBCp0e<{O?&>`gxR`erbJWjK_XtaAyw~&W9@1UgyXMk%xuWve|XKitIL4n2&KiwgoR$y+#@9M^7AVZoX!!E>rOd$' + '?qmeo`|iGFU$QN)+3rGjd|g68SMKLNj&3Z2nyQslt5)PR*aeU7!Pty!If$!~!4Y^u99vJ!FyWc~P|04WcymXR!yWbVW+q^FOX;6U' + 'H%{7L!bhT6aQ+XSri9q+M#cz{!WkwQVePsBF(xmC%c)QUMDm7~T6Bf!mU6Z%g?~!wo7l19' + '7TP&p+#Uv#vhM(3Do*nT@7~RV6!>u4!9h4Gcr{$XHjr_PLaaeQi#naGrWv)PdWjFlkpAHE%E6N$_ve5}9*HNvMUboIw(#5|1h`ehv%-8StqXS@n' + 'rg?QwrjI5jg`SM#smG;Ls;?wc}&FUB!?i7xh4V{0J-<' + 'yf-|6gku7VJl=_W)WYY=-6d^r>pmjr47fv+bS-v5rTquocM4hLhXaY{r$2ADYM;j8+9%6EirHmuw2wKPo5K^T31O9mI`4p5x2rq9zk=*aZgk~eUg' + '&gzVfV~uyx?7?}h7(3%-%R1V@ZH(lCH_Vrts=Gz`(<#u>K2~V?w2>KaojE=EXvG4e64KCy`_&Oo=p*0MnAOFw%<7UGB{23C%{S4{' + 'Cnt^}FR~3!{rEmlzLU{?>_9;q+xq?sEOBi{&WM--lib3@wXkh7#p=w`X}-iz1-zI~gRtzfWSDhkpqieuSt{^EUq@OVQBG@qM&ge#' + 'gFrJWzbCd&Bc5?w+7Fbd8WN~y1cwLAA%mk1PexRtx&1XjP%z9By!rKei0*V&cU)LzY62#Qn8<@6{#k$?DDz3ORqF5coa|ZyIOAr^' + 'cvj3uyMrCKr4r*JY;p%bbT636*3OC=v^_3nlVtp9#&Pl{$bsS%_68><4L$Df1=>dhaVFmEODj3-G%E=n1fjNlHY^OrILOw60bz&i' + 'h;%3+^yUM3r59yac+xGj8@Gx-1dYt{X@Smfmhbo9*bPc&jHAV&b|(}f1?}W>w0}A=_%Zr4Fx+-A$_NJgS9S)LJQHIdsQ77)91;t%rpPof!bzx(W7=}V4yRW@XWPuIa=(RS|ZR-&Vc@jc%$u|Svi^_(Nq=e3=' + '`xkkYodSOt;-9f#AJ1Xn@Q7btT*`=_@&1d)(fieMS>||Y6z!L%AdfU>vuJmxx6TtuvRW1|1*1QM9ih`|ClNl2q^v3%*KJ&s7%;LP' + '{LB8VTftbi>LCQj?coE{#' + '3GKC5wrDYb9)D>%L77`GR%<}@_bEi3y9%O<#*AQ9GfsLM|KGr>I4m0qdNE~@$iH_KGO`8Dq&Cf5`{j#FzvQ~a+$tY?;sXU8j6OXL' + '0o1N@io?E$^^Eoc;laFx4XmCs+o2L>An=wL14Kd0-stXFczUzGZH2-W)?vBZVTm-@9ux9e*MAL%JQQl0&-BNAjH!LkGtNYdJ8~EBfDLy$54_K*k6^+`uJzE?uKBiJ?pl' + 'x|{dEzZt`!^fav0pcXcEnC2ML@PFqB&7>~JI*Yl+TCL0YYbwi$w5n@OjR3R3p}o%80g+Ew^dX90ZXZNswr`Zy%y%bhOF?*fNqvvu*8##S?_q4CQZiIHWEX0Z8RY)=svL%~luio_kkyne}4L0x;qxVej' + 'W)};ktMuUx?qhC`zTyx2BeaiJa!W_ls8do`^KN>ZRoN^D0Z`nxQLIJ=raWNrA?dk50-QM&barIGz8yMnR-7~&RPNr+ndY?XH9I@=' + 'cXq8`(P)`Z9aoENinrmExE*i-uSsMUz3j@=-DXk``C>pAM2|6u53#v&nA3|5>iA!|GBmlF!^74=khKQMOOaR$8HDV`tu@7Vi$UU*' + 'jJfF^(h4!Kht_Jz0%UbBCrQl=v$GFPkMjEntFo$!ygHbT^C`A^PsGD*CPX_#nJAZqhS6kE$9oT!#e=wca0yc}?iqyRQsO6PX;x2d' + 'RAoXWH^nR$h96HO' + '5?Zjv08>|@gT1zB*8aS8DsmN4QI!aZ#&tQc^C8KXq)}HqvW#drPujF!xZ;By6~b{@z|WW*=y1`FWU$D~L*AgnBe|!>>P=6!5l!x6' + '%LwlEk&bwC=bMJj|Ao9O9Q!i+(Hms?(70y>?^X4!%CIKZSE^~^4{LvgC<>|Uz-U(4VdBea?P*7y$`=s`}G' + '#g3ad&z&(ILW64$^56ngeKg`cbch7sL>~eFQSXVNH_6Z4%xs_|_EIEYbhIJP_gfvl_vg7ZL}~QdC8PfWQ_JNj)$k<-^l6DuJRs8*' + '&h`*xB1{KmfXEB7v%Ac$2mUD4tvpO(yS){=TZk)SGzE^Y_By8pnQ2&&M66Fxf-*u{C`cye_8c07fyDTiM}y##O^4V&B|(P@((rwK' + 'ytKq79>R*uqZ?{sHz3wtW4(6rN*APH+?*fjj%Dm5*b2S(H=ksU4GuP5;~4`4k45W@V;z7M$yUxJ+3ch}ULXMu84ngzoQ2YdQ{O+k4@Q7zCX6#48$tz)|Yp%ON6l;0h{Y1RB5a+OO!b<8=r' + '>trk`>6xL5T(ji)jbpzx2yb!KSP!ob2^)r796OF(e&`^mT?oa(8a?mDdwxAUIx?-#5M`1rS1W$TSueK!EScunvMAwr5n)>htH!IM' + 'oB}FA7o=Xk%1o5&=qD8XlCifORXenQhsQ2@Zu^_TXV;4iY`I75G+D*tRKa(3Xq!48+h`oS_-' + 'U|oVUK&T&a;1bixQsVJ*-VzU^UtaQ0<^K7No)nzhEQuZi^Ww}2eL?lcBa9j*#_95Qz?!l5%nO2fngOM(je;T^' + '(HZJ0bt9HfAmSPN`+U?m!#3paK${jfK~Lvw{V%d*uj67EB5IuhNxBkPzpM-6aSI>)$b|Bq^?4h_dQQeQcV' + 'rQ2>sZKDyf2u?syK@0k--DGRSD3~Ff_Agptkby=FR}g;&#mtGZ2IozJ6w}6sI3pmcd3r4#DO^IUTKVJ_Z;ScH^~7-vO%dBWD{FMNC%CfD=d3AS!&Yjwn$+8>{Dd`WM>4@)A{p-+$kuCVFU#I&YJ{-LH@bc$3>GOj(?+=`0+c?o-vhY}|_HSwfqN=U0ua@`*04&EFd(hGf6e^LIcwm859C@cE*6FRAQ7mMjA1?z2' + 'L{ea!2u7c^+U4BHAgO<9v%w`^`2)JLWV%1UrhtG)oiU7Pzfp2wkZ)&;x`~dD4Fowp23iDs1Ivjp0wP;AxxpzgAI=JLog;m-8Y_9{' + 'otzXCU~xsrH_Um(e%&Xq>(BaRsAx!8BD{Y-9Yh})TLA#2xFhK{TFsZ_o+rA4yB=67ymhIw*%B|zYZ!;c0z=`tTu`pNd5)HjS#+LX' + 'b4ES%6_*K}>j)X-u-WQ2(Pv8Ldci`tl2KgG9uWcdv-Ft`g>ica;{eu-1G#Mzhoh(LTZsv-qxW3Z8@>{1q2&ijNu##z#!k88^15qZ{6F*@1t>Dgn93-in2gK' + '0TC=0;TJGuncz8$U5tB2Uhb4uGz0Z{#PKDpj+?qW6^i~Ms{k30%FX>$4WIp-3FrQ+3;&gYayR;>(eq~C=)fK6v)3=d8<^I66uW0{' + ';+@7$m?keycoQTttvJKJ5OQ@WMK6o}4rX^FwG0=muuFq%>y+=CWOrqKqi&@0pi-!d4oH#TYhj)E(0YP)$Q3NS8xia^H!vHss)f1m+GuMq`nwqIz2hZzNyh>T|5KQr7<' + 'm5W=fY4p?T6df0%7uh6_WQIO+$vwe0c&P(N-?E(51GVh}q32uyz>}#yzI^li=Z^=^L*$b|1Y`uUF8+@1!Ze>qe@+`(TJ11yT|S-(' + 'df=Q5=Tz(bhP5uJ*2N8LS*}d8tihD+(CY*{Vfwl6p=T7?xEhkN1v(1?exdxM*dxc$9|x' + 'RoOMjYAWX&9N?_q-$1D^?HUr=F7Ym!u40p@0%r4l(-2(N5dBgQSdbs&RRvj_PgR&ocOvkse!A|S11wC1I)-lz6B9d--@=3Fx~2aV' + '9aa3k)?HWnM};*Z&7*c2!K9iW_YI-lOtLAIBF{je#_Y0;5i^993H^`ZsQtcp^$Q-Ktg@nsIM1)^2y>TsRR_C52Ys99Lr4!C`&CP~' + 'xn*7QxjIh$x$S;Jm%zAZ;s{OIDX6}qAVo-GQ|;aM?)7`&+lGTn;Bccz0he&-r`ZW?hCHY(JUO`?5H0%Bx|E!g&ek|Gj3>kp^Y4(-' + 'a*me{MZ@>8X>7u?GWeNr_^&%5K62TC0PsnmnAu8i%b!?8OkV>po7WDh8a+m)6x*JO6>rnj?7EhRj;>VEP<_YCm=xROwTIkQ0MXcD' + ')Gq5ZVUs8?*rL;_Ptevh?N?%aw6K`i?=7CC{559Ii%>_W%3%W=6gB13XdF+tnuoWsDEA2`T7bH5O=I!0rO0&i}w@?X%tB0fTpo=#7rbaFZ3N2e&Wneb7p' + 'O&AIKWtJKm#USAIXRi+CG7X' + 'QCr}vd-2;bsg?~{;JXXTo^Psczp^W|Og_|W`LcBv5y364I+$sgXMQA3kzf(ABqPa%1Phs*iHiql(h{zLWvk+rE@gF@G^cC>y+xFD1{4&Kz2a^AdD!57ywB*ff2K4UKCqh><%lqKIAv_4Re7JX@TFwJ>QI*' + '65MKV2K}_@=4Tmd58AaD-}RuxuA~lI#l&>`Q@vQ?a6nkW35zgsaaQ0hEn5U|$Y?k|gFyjsj_w5R-K**3B>y-J5}SIkI4Cb3iF!^pkUs(^=jC)7)gd(QRG76%{Vg1)2r??_8(g79K0BS*l0K<}6Y>(@O63U$Rhxf3yS2TkbE#' + 'gzaOH?h9cz6Uk1a#+Tw~t2+-Jo9VR5mS?r$pb{9{BWF0$L1m150$uio#>51HBGtliOE;MwWZ@9S8>!5J&cs1jNhG7!)=En+F8xse' + '6m1O9!e2NsS*GY@)uI4!b#dvfR1ovU95pqu0w#lpqE@9&X;p-ZM}K0^|5W(O3dojC3}y=uJssffLvK(tIY`W0RNF4l6~!`}id(zM' + 'YKDVRb~ZSwozS3?2(KS@q^7=RJ09V+K(`s$vUH-L^q;WC$Xj^(Y;D!dZT?;4N>T@j@;M{tGVFig1dT_8UF!B2E_SrZy%r>9kZNt-aMKQ+&o<3mG}UT?Hq1D^urM6V49+78HxNyP' + 'HhQvHRMT4OE^#_2itx#*Eag}>kr|U{L$=N1(Aq^n=^F>f&`ELnRVr>*K31#5-~Qvtzll&DTr)3B#D6FGT}a_l9CttRej53xN{oGA' + 'I#D(cy!O=Juw4WCe7dH8aj^g4mv;y0^OvuaPOZKM6%N4X=-<#hAcO$648!q8%}r}|@JYBwfi9V(V-!+{ZEYx$%l$G^6l`l_N!{5t' + 'u9WU%BD{%U-myPM8w-4$J7#oaK~)zUbqlI3yK1zlv?&r-uobg$*bL=95mDL&vPBx%5?SKWqdk$N8;?#a=8RO2F{Bo(<;xFQNcjgl' + 'I4BOpuB$WA;u`OtmLnkOR=yEezQ+Gtex96V)oC97CN2)cADJwGL`)#sdKeZ92!>V+v;`T$#Yn5&b;atmSXbmrT6|hprd?LSQ{d*h' + '3Ff;+M%r|eP0nDN5xGJg*gzO!6-k>~0~iYt&pN`GexE5Zf~z-hr1PHP?QneT^!4~SqQ5olj(piK<`)^}s%w-i9ZzT%Cz8zxsCP6+' + 'Ejj02lufDyFu`WP+PjoH1Gjz<+fCQi?qV3@9si6*odhDPE*QZ@}' + 'Nbzv#kTkwQH3QSy3pj+ZI{?7z{lBHpe%gQc9xx66roaCDZ2!mf{mXwG1p08~zcRy?gw5d8q;u#e%X@%!tiH4rj9u)qjImz0-a1{Q' + 'VZ9yX%?8emfLvAkaPll{h1swX3&Lnn1gyE0KBWt^4rFuZpl_pPa1A-g=VSwo-3Hj-u}$QW-EwagoQ5@5O1;e#CQ4J{n6z4F7|x2s' + 'xwVt&sl^>y7ABky@jTq-eY1@keX)hQ_k;()9mMugz}cZ2ghU{!suYcmny|ID#3`yRAJ+BRWN>B6wqniSCO1GDC*`Y)C><8wijIX>' + 'se5q(2(@Odf*aYbW~@)kq8R(SWo`7aJLy6-%SmI>RX`f-2L&9|l>rytz7IYNuftoB!j^j}!PAGctr<+e9pNQK>(RIpHd~dn*&^S{' + '+6?=nr3+~YWW-ge@P10bccZE>lqW6}!1T{LNs6Jgh%3t&sC~>di9q25tUzgd(Z6=S4LPL-=C18nj@@g=k>js8fmf5HbLWga`66&D' + 'XbVcC(90y8sCoUp<7fA}aU@5L_k%-sZl3b3;NpaCa{GirQz_4MIh0s%I}zOJ&7rZSKO+KUboEi?-*w*?}jCOQ@' + 'ZsU!!Q6<2mo5g*4=$``{QRxumb;rlX7h)yLMVSA&OZcC=gf&T|' + '0zN1f%?RrB|MvHP;O*J_+bQ)kt+2k0_1rQoXP(8}8g2VV*9CX20S_p(H_%>JDW^;Il-0Iom?lx5r!;OnQMGZObE>Yh>#;0({yF@M' + 'C0>MxS`4GqRm*@Qh6Hh%p%OZ^fW-VYzy@_nEo_5`|2~xP?^MnJDa@BN+?SrLfmr`fmoI#SMoYp7;@K@_44U+>moh4XH7FR09IP@{)9?~uf|_}C`Bib=C5id8W`' + 'y(!0P1o|`H>V(l2tq=Li8X4i*E=t)`Q1|hx^T2G9IZpK#&tv&}qZotX!ka`Hbh6dqrI$+TW6%+AK74x@=>X~{`ZGfMaYi}JW=YBS' + 'd?;v1aH}+S;>4s*;Oq}YUJtagq~iTOH@~*&&1*Gox$`=`?**a2m%7R3jSGl#_xAS&J2tQ`odQ8vR(ZNMBPh?Iz387e4yhPO19*PX' + 'QG0KrsF0^$6%+CnF(pkjWik-`$-SiIDaBOH;W_10>K$lFb+Oe@??fVYxe2=@v6SP{06#wgHf2hIDs38Mhq$zh3s<8+CmD=M=6H4*lO8IKL;+DKzk#DE^1)_w8QisF4}vUk2GPYKnN4j`JpyOduF(rRa$q^S&L8Y}o>!zYp(GhAIe!)W2_p{%(jjH+nbfgy=D{*P2gmefBvV+$W|+|_Heihj1(*GAUR1>1UwmYqr`dQ9Oo0{Q%@=kQZEyvF>7yGi|M`{AHx)t' + 'i}`7d@pdVy8T+}R!4&oo3eynDqO2@AzZ$=)j39u5fvPcYRCNt2i@7u@St~RZiO{>`f_HrU<@lJQ;^He2D~+P48K#3Y6877pu?YYt' + 'MFj+#K+^qZPTm6K6vm^BvJ(($5TRO)(kw;3nV(m^)=m7?Epx_?0' + 'bxlN;trjO0XO5OZ2C~vRI||~X132y;S+~#zdxfFlIY6S*Z<~(~N4l(44kjy_pbFI}m@pNsk<0Ie!ZnHzf9Dq+f$H7yn&Au2tx@3c' + 'zh(#*6ejL2AX;)5AA8I~sL&H_C$PO7HvLrxj)H~g;x(aZdCMu))drCZqeFsrnS;b}s*Phi7D#&I-FUWkRDLkV<=Bz<9n$R#PV*+#' + '2het}y`N?nNYul_VMx@kMH{%9!=IW0bqhL0wz3$IxPq0x7X7l~F2>y*7v>Z636)S&Ij&vWfa}k_tTsb#I>HMYcTQL?t~B}L=SvhEW1y>H~7h>Gb)91VO;~CYmeNE%t@J(yjnzN1(vkU' + 'M$q}7*yZ$6QaJTWC?kaWfMm_K%H!aSIN+>2o~{v5bFt&-!rbGDLB(gu0vQ#gF&^t4WE*&0;dxN+gm' + '0#xk>5N6+dVUF7+Jl$5xX3$=1(xj3#t|=ZWmRr}Fj#b|+3P_E28&maP@(@~JP`i|}QLl1J_;7PdS&y!KV)S|B(T)H$9i6W)bTPVE' + 'U+Bx|ODH)6Y~W6cy*U-Wqz#aM8Qp})i%O>>W5cg2yck(quA_K4x4L8S4Swlyur%FNZ28VVbbtJX+8;Tde+r%JR50u9_;3bpPO?jgyNCG2_PA>s3#N0=WX^&W>LNXiRk$O5Jk;$V3&JFxznQrW' + 'I}xP)%<)GxE9U711=B<2%kp1m;!?utP+u1H*}A009fl?dgNc%x' + '(D-J#j58xch;t{}67W6qvm4j?m2<0R86KZ3zWV?T#efY$=2*' + 'G7=X6tuFM!cc@+YqQ^cGr(v*&JKrPs#ce`_D?Dir5^no&Y2>)w0(3BgU$YWxXD^7=qu|?ruj#)l<4Fz&iaD<5-Y#HPzE>+Yj^bmIS8`2kFtU5Cml-B1FR#h4t5&hKD`lfahF7_1H)fiGQLao8cb#p<8cd^po&$Hw' + '<`K#%LWB+L3>J2CyDUJjvRqsSTsSCpc$H0>TZDS2q3}%AVy(zP)3f1oR_<0&zRbqfg!y9kFRk9=P+umSoOLP~R0w;(+G&o7_gD#|' + 'X&>fPk8;=od4>F;eUn`*W0zbcEoVW36bNR^5#Jn}3q!#vEQ!#r+nf&>u8f#_^NTqz){x8@k+Xtn_`*590~b1hV>xi;LYCNTuEs{|' + 'LN*0S&1l02O?vW1`e)L18`g4XC}&46ASGJb=xXyTG8A0-nUVtTYF8n05c19oxv|}+X!6WB>+f3*G27}?k_lo<27XYs%wcLpn68Y!' + 'gB*gQ3QSQk%MoX8CX7tdFN*j7{)zd%4#rV{_ol2ZyHB~P@1I}Cc}s;@Wec~PZp~Rm@vGL3O2l{c*4<8$VdbZAEULr|NZUL' + 'I;rjhB!U?0S58@&ISL2$#eGfpQ)%tJ7W2km#e*aHeBswYB@-%%pfy%9*`x2dP&}6(Rhj;2)iHfZ_4Kb}{WW_}eT)h6yoKcsb=u{Y' + '_j*UIQ3C3YiP5vU!Q2Ggq0IX5yJLC;?jkG5zYZvA2yS8nt$S{O15EK5>bOy7gPt=SulE*^?(WxLwYv44ukHqtM3VyZ8jvOw4f4M<' + ')oOm;E^XM~P0k;4P+sI129pO!VH`>E;Wgq>xvvyEIz)+ejwNeSbA2O6{HSYy+7AMDF|y~Wj>(IRxj3-(PU&=wpy`onPlKeQZqs-&' + 'xxjW>OL0UfYAfDW^H`Lju*MUNIoqgmMx~b2Fs60*7t6UjirKiJB_=Yi-ZV#s2hLzhU4$sw+fYj2U|?S;_?Nrrp2@aT;1KGmFp7G$' + 'Ixa1~q*cxV4BD1n-wTUNb6khVI+c8&8QD7{gr8iN7GMc?g7hA2ah6gqw+(%x%mCqF6u}n#W(+$SY!ebB1J=ETQQb1-H#esvD~4Mn2)7C2;&e`r' + 'Oi?2Pn@pY}wwi9ig>ht0nK{UfLzU6UD=*`ooef#NM&qVkd#2l7mZaK$Gibvq%Ez?p#59bkpf4WDTH8!9#|=lDA^Nw`00ni0TuEFGHM=e?>C)n4Q5z6n*%X&1CAsd09SYT@r!xPz)iIXME#^;eCUpsOAX9kvZhiIDpLKL&T*)YxnL$HgxksGdjyOsy;&do2mxMG5CKU_?t(*jcR*L9(Y6-#-EuC#04s2=f>^qEa>Eya@pSk}Dd>Ozv' + '5kNP9K{B-NC>WIYzcL2>PfS#12Nx;_p{b}8#8~|Zsro{elp3IANa+H!Q1pXqoz)V~&vs%7#{{+l`39}Awn9cBLBthiIo@%lZE+kP' + '#4jc=-{oiAcF5=?*oRwNn|M|xHg)t7fo=iAT5qvrf%)z%e^&-4;q|=}`;jY>*{*%XYl}L#H6P!)Tzc&Z`o1p9-kKDDFl%Wsz52J)' + 'YiITKEDh%*tc&R?=j(X?Gv0?c*J;a8^Lbmnw&ph0uQT*}H3i}(@Yc1>4oDOvr06R56-s`$q$p_V`PHT|W5EIpDOJpq6G68hLqKt}DKx+OjTM81{5jI*Ie&a-i>+DbD%2$K#4IW6bjP-S(`$;RSZh<@6lIiFJ!X&u_GP`&84rW' + 'tQcGMrivahIPGCbJKTu~?e_7pNEt6gPEE;^=yO`17fVw9(ch)`3q?~mR<$R)#tsV5`8o>behdV$AIY;d@xZ84ab!LD1^L(>>7RZP' + 'aYm0?6G8^g>QUfb(w-n^B#vqaZd#7S0(zS;yu)sd<6Ys@)rs>p+aeo!JHyFB0(Qdsm&p7xQ~B8-iBQN#f1u>3u}OFCUB2ZwHjxiX6x`1ACKN?' + '$bN0HfbdBDdI+_8J@-g_pw7&2x&(%X|2~Z3SoWsr0bt{#Uekn}t@Z=>*`h4-N#oC8&4F5_8u+1y+_XZD;d)4DG20gFDlM@EQ4R%V' + '^yCp6Im;58#$Dud0uTM&=7nSs^@jmFX?IkbHEOcSc`U249mrnq2E%hNIe)WS-@ZyEQf?0^Dv6=hf|dsm1>(&!+c%gKISwy2$-lp?P@~Wz}K@Y=?98%i2dnoyc$_C~HVEAr2rT5!0nI1g6w){UvQP@3' + '^m*--AC%hU)(?ucUi>*X7g)CQGQ+;au!;v1#jbW3%Xe#x9lf(%A=E-pi;T6_E0RN(E`VEu&dZn1+Z74mZf;p5xUacw(J*dv>mqC4' + '>|*Y{ARy7%HT06QT;+|K{oKEfw=mgBEt1XP{SNX(XYwIbV#X=%&-7!+_FHAgIWu24JwutvD5#(9a#~2JfiJz{!p_T%8nVC6%eERz' + 'xAd~DByJT_in%q^hr7MNaH;3|`-^X){#XAe0R5DgONm=|0#;3pF>eA>&T{)G;F(gh8VVU8G^<0w#PL(WIkGLm!C$kg!u$n`%e?BB' + '*`;t)l6|pK2dXu1LkD2mj0aPb3>}Zvr=A<0ybkDe1ry1`5*lWYojnZhlLsyVp!i=$z9*m1N{a}W{^x)uJK*NWBX}6?8p6_2n*cex' + '%ozOqf;QdmUS7=+@aSgBnPj-bSq^;auFd&i7Z1rs}~1?gYqS=Ooh6Q2Nm>g(r#q=sFH9k@fps(6<89Wpq!XTHtEp0rhc}D=OR(el!KLuy@n6r4H(>QpVhh|{COPDljysi' + 'Tu2MrNP@v$Q!!(@_}nn0L(U1HtFXl' + 'YQ7&8oAtB5zMr4OfQp;+ljhYa>^$;{8vLEQ3IuF=jPkcct;brW!#SRE6t?9S%~q!~>1XSnoBA?aC#*N(YUQZ!PWiSdtlH^z;PUTx' + 'F<(%5x~M?hokhFt3_g!YnrrEe7e&lqG^6hFxpcn^d;q`8R{d-@PRY6qsgNeSz+))u#l#T5gkmU^;r+<(%CZS*x' + '_>&?Hn0Eilfo9hVt~tvyIwVk74{)P^QLDr<~)Tu`vfD|tX%4NPi2mMCow' + '#}48-o=ubSwC?|DJi|~ER|#yryB=s~i&kO3^?(%sooJk^;fhX`A*|W(sMocE52cp*zLu}G0Ui3luAs9Ui5dwi`s^}V)!D*4u<>3F' + '0&Q^$Vr^E!i5VNT=qf$cCQH$!{%O*uc6NOXTuwwl+4hNk!fZARg3QV`?n512dbEXG#{tfnq+CAI^ki=AdQ2G-6YY2qUG{vU@zEg~' + '^Dxo4)Cc%p2g%6$KeSPem82P$y>?jL6l1o+nL#l4_iG?sr4vbgRW3YWAO?0<0W3F' + 'AY8|7`%#C(VwBq5{z-LG&im^@PUaERPoEI;}|dl;Rz1D$WBvo5sXd7@(_U~5&sL|w5M#3yMi8xEz`v(LCfGhss!lsXVo_&F#+&P' + 'Q6FTyhasf8Yqf{evE8xWh9)aY&g_~;lgzMD' + 'sTaVZq5ij|xLNRd1seyJhlVA)Qs(nh;N?(NgHTGe=t4Djgc(=Qasz3AGpjM*10`pjELMQ#K&P5MlD-KaBQ1gd6h-ZdF5<@(p<8bm' + 'FB)(p8ed0QSGW+auvm$BL1U=Ny`xw&TCLZTKGxmCvWHgOcCt@j6(zPMdS4Sfpi' + 'j}BqWXkYw1RPX*M_1yuGJ)tK|G28KFid&)A2HDf(?qW0osmX0`qUSbYs(CpU+02d~z=~8{81vCLe>XlG!512BO0RG8Wa^k!So&+v' + 'Nj)qll+f7J5}Q~rO>k;r9XGuX@QtsOE{NjJoZK+a=1$L+ClI_b9J(HLRn6h${*!aR>R#2;P*!EhJiJB<^WL@>s$`^&l(l+}rJcIr' + 'auY!-V<2>py;yJb_1HJ?GW}6Hs|QSqG(CiwPj6?1w$?(6@R085DFr~?#1vyxBH^&Vdz5IL+#SLbFDw5xLyY!pj!O{25-o&PBR!|(' + 'qQNMqi%Mnf*>V|Mt?i9cbyeroz+&2;{' + '8_b{D2%s6yoAeS3>|b`OQs+W!pN@cAYRNV_9z&T4JG^K%bbB$S{uZ0^jlE-T' + '?ICj`UminQ5Vn@zz`8*rL@`!*J!!{bP*pT3DSujeY^c@zmhsoLKcLtlHe)>5y' + 'lNDtU)NzO?<|VXn>g}L7zt-Z9+*98O_~Z0dk6&' + '=~43eR=buC=xE)}M;iw{F;}S++1f^)D30_5b%_4hq16wQPTKD3FzlY10Pb{D4<32S' + 'CaKD^^R_d2W60+1)T(TTt#=QowY43?#1zshvAnuHbW@LP)6r|WpXmp~Wo&czJ?`h>cFUPg(SKXMxtGBViX;0$_lr3cdPLoOv{)j^' + '1X_(y`j&U7WSf{60nbESl5|V^e^5&U1QY-O00;m803iTk@EJ5sDF6T^!vFv}0001HVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoZEs{{' + 'Y-w(1UuAP`W^Zg{GGAqQaAk67ZDnqBVQemNdDVUWbKEwP=w^bjl+HzB~sNbyD' + 'Z8!AMPgQ%nx&Bjcw!E&Il68+u)>k)8w76x959_vShCYohZ`nb|YF6~D9Q)O~imE1h{~kJGb=BPDtY0m$PP)E-{~pQ)=%Q+yqOR`&' + '(0@UjJ{oS@o<)ncSynfFbj=pU2L2vynTgiWZJG}e6cM_0wWx+@T~yuUx^340>~Yl~Jb)e*O&JwYSuK~WgT@?o$(jZG=KSqh)DOkt' + 'LwYcq%?=KhUAv0%e7Qm7^E|3nNE(39v_pZw`hx@U?Pei=4K0k=_#xJz0yjZV+Y`o68' + '7;fw8T9#eFpS)nW$4c`1S#zI6&+4M@|5|iOv;ejc)er?)>ykBn+lhkT%2iryhE^8h2T{}&O}}ir73=y`&EjUL>R#c|vCFQg8djcT' + 'xfoDd3}S9&J;@$tvf#YBs~S<8&-SJx5-TpTn?9pjq6u4Fv$E6%E^EDz1$qY#2g+9QK?MIn+h;J4Mb$(9iTmj-O#Y@<1=P>$&9L4K' + '{99gj?K&rFRWLg2Wsw744NR0TE12=>fx*XaLn|Y1_4ZW*wd9**R-&U`q3kFz-Z>d5DexWVIS+nUnL`zn{Iy|N8pb*-!b~m;d(#hkt(dfAX`-*XJ*v<*$A{|LMgW@%imp{_ORu' + '=Pxf`zJB%c)nD?LuP%PQ%wL_Izj(`QK<)g^i?{jZe_qH>&tWZZ5VLaNIvch=vpUY2k1|6afv8Se4lMO!L}' + 'Z`x04{amzdtqR`$$JvwTZ`z9&s#tvgRQ=_bU(bKJ;D26r6%3?i=bL({P-wz}`^Y#pf(zgiyJ_hBod>(Jj=E' + '0Ex1KJpm-#`j_X=FO%rv>_rl-FV7R&(JJ_r(x!#buG>vh7G1^qQz_i(d&!otpH)pY-B9' + 'IVd`P^f)@6N727TvHLw4hmV9zJ9YzgfK{(Is9D6b5rt4a1' + 'v7CdsU71r9SDczQYzW&x(Z!PBiJDayVO8|m&qdw4umH$Ius!0swKd<3)~E`88Jso9Uubmvb-?Nh5`ZJ0jZCUwua_tVZqp^-uXED>&7O@SsI719ag*1Gcb~17sr)TTt`GoA0#!J`n^e*4' + 'slBGwby*I1-E3b~C}eP->hrbS&7jfA6Cb=!q(5+&7X_?q24W>vHSs?#(jHjX_OS=FKJjR1Y(Qru&?q2Aq_`+Ld7@Wuyfry?1sCaF' + 'Zk63Y9WI4*hEsb_&jxEp7Q2x6>lDndF8XiUc|2QCU{{aDzw-$ONIhMvW(IzXTc{57cwlmeR3A>!NDZSG=v4za{uG`58TFuQ0aJY2' + 'mg5!;8V4(25{hAQ8{f%29A*bdTuS9N?~B#C#w{MOB6N7Q5utyHply7eL}{ANe~7rEA93J48PU{3RNR66e|;YbfT;pdE&W|a87Mrb' + '21|AfbnGU&Bc8gB4ka+&c>*{t>J3?F3PyB%V7E@i$k7?l)vWl)VlM(obTr?urzvtTYZ6fcc#h;4sXk3v)i*4<#XM>|(8i*o?qHqV' + 'M*=SF+5@#W2z!@;;<-XfJ~b%`P*Ke)B1J?y7cJ5an82AFMtgIlm_>wLIhGq)tXtdVsSZB}YB(O@6#EAAW-2BCy0' + 'gcCG-rCbK%@UOwv>33>FOpgY~I}pf;sCcFG|2T8F4C+Lw=OAbv)W=$>qd%zWi?4x=iX?hM%f(nk^fisHami|2mpc4H3J7V#t`ZZ=' + 'zr-pqu^Sl-#4G8Ono1Kt=xs*i^>rJbgb1W)(+<|IVDu$^Jeq>C4FTU=?$fom{bZ*u3{8uTtvQ*eo2LJ5!x-#$C-W}=JSzQDT^xT=' + '7g7T^)hnOH;NbA?G>PDUv}U5yD+9ox*IMv`5bX^Rfgz3BFE^8^4^vm*gA6DN0&E{56>@Sri-SX_DhHh>jomENJeTwBLXMSFJKAcIMFNNvuNp' + '=h39o#&Y+B)}aA87hHn>XrksSUDj`o$BYxH^`9dKoF_DsfJNDFsK5{' + 'Yz79~BEkQ=e*7c+b3IEmGA9N~24$zZLN5rmClXu77qN~_f@i(mkR7E*NyM5mJ5G-b$|%4!Yi@wCj{`' + 'P6R}t9{X4=SSH}|zu+sH37I*Zls@{zpS3wIK$Z>v1bEO&9gKCmOoul1ftz4RAZpq#P>Mi*@xN' + 'zJOd7=CJaOgSff7Ga8~4$h^2Ms>T3?I9b}l|H`6Z@yUF?W&cHgDIJRzi^0)LqJN9N-v)p%T^0*wIFVxm?x8WB!zc!!1|nZkYik48W_pEJ)^5Rt%_rDvnF&mF3_4a@$MeMF$6jvg8ftxDv1f!ybOK`8Z04)B2fowVf!UL>hZ>}FB*ln9sxwW@U835gTv' + '!PNsq%8Y7@(2(GE5F4`Uj)KD!)QdvNKqcHcfl;dZj0i*HWuuN@oHmfUXsex#I%v?sDG4BP5{#' + 'Jl0eI)cQYFC8-Gr7bwgd8Z{OSS$9-~imqsGSbUUz3)7`QBm^PRKZp%lLxF$cSda8bOf!=dXZ$fG0UHbo67`H-BZ)pRw#MI=-A3D_' + 'k2Q1a2wjxv$y1ay1W4cT{&kM(=C@$1pdR^tzBA@#nN}mjg;Uof9N?Z|5sO' + '3quVT1GyXH5%i&k@o0ugW6WG`0oGD>Ren;A6JTyShn~+TK^sq?1eO;ftK)uX^yWwdz0kA%{hH5!`?=O922&{G`D0OUSnts@v>i%X' + 'xu!7s{5@Ec!K0mQP&u%*P7=;Wcv_U&wRe)HHZo|}?M+xb3g-P*B{vK$bq1l#(`sO=cs@s8vgR=QF*=G^-LvRO6AT26%LWbfBkkml' + 'h|80SN*78<5b`|*WwuCmbsu-HB2Wvr_AEmQ6NzlT^!FXrwvGD&$J8ozJx_=Ge-bN' + 'W@mi!Z-%Pbn1_Od?8#PSHH1(62%qeZ@F>M7AVZ*TOEB5pPBLtq>4vO$4qmOI822Sqx5Zj=^^p*3qL5xdTo7Gyx9Ie5CMkZs4HHhny6xP1$AV8' + '@-+QBPXCHW;79FJkI&?1`+fAO0_Bes!yZHB7r<7X?cgmbKjIDC_S)1dxme;N#mR$RbTsMTmK7uRV!ltcDfe%}KJTPy|8!^S5UdKFvCcwDv{dzHY2*C3qoZL-EkjH8vU}Y>mkZ1UHlz' + 'V9e*vig9&5@~CJzqfbt>*Z{JEdIbuKzt>_4`hv>2nP9j748~|NN}WzDH;aJ`D1c{&u3P>B#4S;S7$XLVI~iB#iSHRlBLWdt313(9AN28f' + ';(TmOpbAC$AO@-7op@S0rk~l^Hmmsw=+MSoy_YzOLj+rsHeu-@GN!eOMvw7T#a|rr|0(o6mitp_xIZn$G41K`74^;wwO+@bw!=r<' + '#mIrpE}c{(9-@mlr5*=ODsiG`uRwQ;%jU(_$Z=0f?5&cs|?iW&HC_#1J)o-4w~k$U8`dgS3qiW' + '@5++x(QG;&2Z^C!p?)43O((liJSp1?LMT|7TP3}2510BZ*tREUu*nTv&-<^#czEc' + 'Y4J5jY;lw>' + '8f;{2u%;7ScMR4AnN^ORj=`>ly)-#(6N8p^0D(zKv-B$Q1Z5=N5OEysx0==|w?`g($}yeGkWW)M#geqasC}>Y%*phleFw}kxO`+3' + 'Hq!CwRa!Ln+|v#w(8*l(sl^R}P}~AvKWWluQk?!!DaM_s^K;H*#@V^v<#3#YzN8Y$X#Qc!{}KCB%_M;wH;J7iCy)TbpFnAYxs|T(C(iw`' + 'kaO%aJ8cdn>iEF!>52-#K;JA(7$uO|H1b^RROJqOSA+I}m%*;^?Cf1P`+sT9?l2&jGI{*(+12lmVS=}u?~wswODYLd#S)isn8@t{' + 'wasKNPXFSj7-9F>DFj6O&(0mtu^KOp;T`&IX<}mQJY8KngS`@iu`tjj6BP27CLsv?L^l9nEVMQ;zg}s3Wykwz291t)AWkhZ^YG&$' + 'Xzrxyu}=yfw0r~9NRFpo8ql4hEwbgu0~BJbNxw_yB!' + 'y;>DiQkCKQFr^P(r=a7Y}25yW#!DSQ1oq+&9HM+9?3Ov%~%uh>gmiI4}%2YSCF`BW5wA9lX+zmiN5snZ<}Vr' + 'FiS*`bq2NF|OF$w2&>*8H32co8m~NrX?I*^QIrh=|}h9ktR=omzRfM#eGcz{<`gaHnuAZ(6IAHN|xex|rwi$UL%G' + 'EN@1h#1D+?18f#d4BWMwn_CCk#r-9&oZ' + '&(Clt>jC|mwzsIGt1tk&XdGz2ZHf~0h()wws}`uNPcPjf2F+_h@4A&$s&Fp1R9(!<361o3fdM^u1b' + '+wco(F|U+(a*cYzjd|tNvnDt)|BwV+Uti|z*pa?7Pj{l#-06fsW=^I|tGMGBsZGq|g0aSFMA6=3(P5&q$3lmemz$1fN~QGKXi}ns' + 'l|s=rEPT+WNX89o4h&k2e|YO|?H)jEtrOZ{y>{QY-+IiyQ*ZdG-+&xvMP+3UO$D_lK|^Le?G9S_mjrxjq69bvM%?~IiK^1Vdp(umoY!tz9&_8V2xk^5M6$J+%-b@Zeb+~9vSjv$#CM9QCEyNC^*>@V' + 'LotBPl)fSSqeaoQ4Q(%cddV~t>P6Krimr@6whk3cHtbyRlk&lX3G4nKSvTWIqM-KzPAlfVg2pJhSTn5bm}b!HBm9kkn$!ZheqE)jYtsgA~PvI7qVTw~bsH7bw~pi`j#RYg*y?p*bFo*wn$c' + 'IIXZMR+{eMX%G?zV+B*fCO-ZVLc-z&ZWLG$0)k}E{nF$?p-06Y!p#E|=Q4!bWl9_r>vesflN6=IO3<-wZ{rrbtnOg)d-fYxf51WI' + 'J#av^@a>bH9TEc{cmpZU%t?`j8TE-bDQDu7?9oWx04U}Iuo$BQO>+vp&qB!m!p~PYHS`xT0x@EV&vQw~xU1s+nkj1X4O4UWdngn(' + '6RiPUXduz%M*4zw$?u8SvCxXX0N^IGMTE5=X@*5j0&d54~v-(){?TM^*NFq>QDL*bSx#E7{#X8D(XJ' + 'iCglKb=Pgrf_lCim_>);@|L3rqMH8@$?G+20=P$ah?c$k=' + 'C6UMDkNJip#n^44Ej^=Bti*Kths&VhqdJ-SVzf+a21&*D_Fsh7vX9ui&wCQlpo<5oFOQgL$$6kGY$Ujudjzzsi%KPX3V|y' + '_v}Ja}vl5<-U1nj5`n$#!i|myCZD`9b72MrI-x-B5?U_RzkO=REGM?yj^TKy*i7{9H' + '0#-BBLi~jw*gWgF+`jfYrCy)OhMN9*_k5Cm`i1KL8R44Wtbnrl2&#S!_xSPC)}Q' + '(7b6ezj6;9GI4+r!U+eyQ$+U9MVni;<>U@J)*X38pKq!IWb|E>ya040kh#ftk>`6352%!r!mBk8qz#ByC|CCoQZ*R7)AvhVV^_A-HB9@SKc~L$~!U_RPf`I' + '@8?ra7`szzT{-o=m{>2~dy*252t7@r@1D9Ujd<~9mAlEit@o-ddRZS1HHd!6w=+=!1>qio{W)XSTvSq*Q-6=}jpvxm7h3SODA+Ot' + 'mSBa66_UyaA!Zm_Gc%7TL<;uRI>pEr`pE=b>qa?E$MJX8xy0RgJJol+)Lr^TK|nQmmW0G)CU+UZ37*>(VPhiPNDwO%' + '|9X7cu3DYR>F|S7{>sew%U_v$N3-##znb9Z%;ar)3!B)>2m%C+#wycg`Pw(XTeKKkI3' + 'l9_RJIG#S4$hMqQ2!DPSJm(PQpwsDvpWwqd?v' + 'iFsHYwjmtwGW2r4qt5YAOdWz*hhF0$*LbKUy>^-S_uzAn4+C_X9QGKA?jMpraNR%p<({GIG7gqv(xAu-m8axSk9J6Z1bsconKLiK' + '$2MKY4A&YFvhk;%_f?uoyJvSDATF3^gMXOLuwkp{B3ROWe>iX*a)-MWv>GN2{L%UzT_jKafdJwe14=QBiE!jIYW!2qO+(M4-KKpl' + 's;ybKf)y4YCD94l8&7;LkCoq`@tdgIaNHfifF5kswZ|~u?hf&X3G6;_VaYYYb?fmnI9Q6_LW_CY5ffglQm`4XcJfCS_@e^HVot&s' + '7!(LOXp{>=WWAv~Xd&lDfR49ikb*|ys(opvH{O+Mb}A=gUVUW=Mi#a(tyCT#v@iY2kHJw}DY^C?x7qxkZ{(wR#dIA0ST(7C&Q6s{' + ';}7eZarT3HBH5Y0md*=M_cjo}G&5-jHOr3^{hBLN;DAfOZS(g7H>;=R{0Y3Us~7uU+4VhM^5AoQ@QrW>opRl=k47dxbJyE_V%$|KvSJQc8KgQD0y@2nd&{fxn5{xm$KFLMlR7(*2Uc`eOo!HzBN7Z^P;^H=8~gxKFK' + 'nTjZuH*IMUuq)!75Mw|tWgc{O%9FMue%OJTw5?1md#{PG(~VREF^QAbGUnOTvIo38G4?noHE>b`^!4C*pZa09$EhdvOF}a' + 'TK-KQ5&DN-pWulnF!K%FGchliSzV}g?2ugb_M`7yzL{rD&z((c`hp$!&_OTJ%&39*;PUM3MP$?f0CxVTcxKIQHVHF(B(v' + 'dh%@`)>D!RxJsEN&^ccJ+|vw^&IjU9MVCk8@01d7fN75JPk|=(WJiXi$fix_c~j7n)g~qsl&WC8H5in%lYFF!@y}$Z4>dg*B`7`3' + 'Q);M_wGN|LRdkr;8wYQ@3R(#_lPrp-c#n@KHd=FhI4pgXkMuF?GcO+Y;wN^9>d6+~M#qBA-oSq!YMr-_zCE2J)pf^;4`KG#XEz(E' + 'Z@o;qeUEknHmH*Mu&TwZ541j-AIPRT-14Y9}LKzl;=5O!|&G9^)1i|qvzzdjK5M3kU6c<90Ij%iq8ZI&vv)I5gGSA(&926WywTB@pyXzE_-+@B2c@lK0bXvL;~V|$fAve@7fH5(9ik*nLS2jj&{ae3;aRxvv;lp_;~gc}MKadqS&R7K' + '={Ggcsj&so^+X!w{E{EfpJX=$F@TNT4T8{7VYLDW|EXV~OyTQ2baLj1GdjI;4p_SPDgf0rD==>yn0z%WKC;-P)zREI;HI}$$!Js@' + '!s|!HT?K`d2<#KixTi0ZqOO~GSNeYLSbwL1pzvTlCdj7ES6f#RI$r*p*J=B+19pA9MC)?gs47CA~Ol7djs78kVNNP=)2*5' + 'k#Y!vL$5S70BFFbdfJ5=R<**WvrmtB1wyV?KptA?VfuUoiJ+<~>#@xTMUhL)dcE`gw;KL+3c&w`d_Y!KT!aivb?eww^0yN~j>' + 'cf85xhvV$5S=+ZOv+Wmz_ts&*$f(+37Jac5neXEkSYJgs-2F1M{{;8TR@=TSZTD6787u4yR@Y~&ti4y&{wr#y)wJVE`m9y-d#s?Z' + 'v3kDR$`MIT#d>kpf}K6;-Hav?ufEe!;FTUZJD!IhQ9dD6pn^P(`OdHI61g$9vKzzYK;0N$Jn`4ErSd^gEA^MBg|Nl)B^Y8gD38v?' + 'zSu*m_rngDKM3=F?%q-)k8nhhQ8fBOK;oX5%GLv`De&eZHqr*dyKSQ2*x|faZnGm4z%}~|7oEABJStu`Q7&lPNu6VXSZMjMNL|PH}' + 'htE&+SB$dYULr@7&y3+lMg6I`@5QwBQ6a_q55|%wryllNHFV|5N^CTr7z3Gf!m?3`{Y<2{n>UhJ`x!UtI#U*;K1}y--pgkJz0TF?' + '_U^=U2g|+%ed+`kpwk`$3(nu3<x_b=WH-<$ffVm6KxEF7|3!;-Iwp}3n>V*}&F*6j$nd3Kc4oST2tq3Knich(9d(ssSO<0|Jw(EC@4yqTD' + 'O8kQ@ba`bx@lRb&rmXX(1-BBGWYLrDNk_8i+oPS5nSf5VN5+vqDi6pP!h$sb?Sl>^Cj0y' + ')$>#r&<|`AFaRn!Fy$Te6sj3Z^7q+09d?W}FX*1AIm+aZ`ITR}cbDw{rwjBeEPy|wL0K)VB;P#0AMV79Y8mstNVf)#F|0hz-_L7-' + 'QC;=J+^6_n0*i>#(F=SV6}j`gtr`05yJsB#H$1T*Y!O9-r>XjZsF8ax6$4K3&ie|n+;Kqb^7@~CKvG8)pD=!o5J&qDpBL31ZYl7T' + 'sHb`2){#V8_zl#<79bEV;I}7xFn*4;>httd2Wn$JW)1c7g9i&F?jM1LHi;S*hVf()WsAvZaDgr=%z-QEp&6vVc|cC#G?M7cagwlh' + '#OWZ<@ud&EDD{rNeQZ9zC-#6@@X2Mn0=KZ`*D=4f`-L)I!{@7m{{c`-0|XQR000O8001EXwTnG(@D%_6WGnyxH~;_uW?^%5aBOXJ' + 'FKusRWo&aVWpiz2Z){{TFK}{ibY*F7Uvgz;WpZV1V`X1wWp8a|baHtvYIARHWtv-$T}6_G-{)ue*_85r={Fi%rZo&8E-gU|AvYA$' + 'NZ3ZUK|(G5`+Q&IsXAw7aD%&c?VUR_V#SIT5&PdyetiGOulG;>^5myq-n@KY%af;1etrAq_xsl`U;n`6e?R%*&FlAXU;gl(TmJd;' + 'n}7ZG&+qTwz540RFF%&=e}4VzZ|}eVJ@d?~m#=^R>HgjO?|*vv>gQkn;O2+Bp8War$=myPKmYjK{X5tD@RJYy>!T-s`A5Dhxy;^H' + '>m%=y@_d@^+N`s$mB-9G^4`Ns$8E;Vk;byb$xv+eEuv$Z)#9V_$1Z1pKO_E~lvy*AeFJI5M-bq#hg' + '$8NQjzW1oHLMv;PnmX%_RZ8bl*}Klkr9DULYY!Iq^s`Vb`5%*_l_d' + 'k=mc;yB20CYu42goAV4A)9lHuBW(<~%6Ay|!!KSu81#dB*EmW`SgE9*=8CtDTvldxjaQV?T#2W<8=fs~tT7)gTG(W&X|>UJpQGh*' + 'j90ljIME!V?6}60vlQ;l>@w$iV^DljdmfuVi%rDsl_mDZA(%9e(J<*=y{x|G$~dae?h4OXA>Hx&T3QDMcwx?lJyOD*t8;`_XX%6I' + 'EAH}VthaYt+2>!x^EtEXnlskkcp!F=a~Zwu#qAUGyMs~J?u+BqhH+isTNbEyY4aE_V{Ob+eOUoEMMtN}pY4iir#3TIU}Ly-mIq8=' + '#XM63wLH-B+#(x9&$%@fkOEBlhF`UU$62CP(BT5lS)trz76(vi>$O$G1RDSr98pqZ|FLE+II!TFxxlYZ`1Iq<0u3Z&j92DLxpOv;' + 'uv!~~&9DXh5GHJfNL_>&&cSD~K#3hp4%HJb>|C!sYzQ8RtC8dGT>|L@CBedh_$Bz57gPhrg<;w#%~%Gr+|9Y_z*%4b_cY$_s>Tdp' + 'ac6*Z?|}7}pFYBx04YRGYq+qBLm`kF*CS;g)%_V' + '#=v3V`8j#S=bs&?ayUtlWjKla`Sw2kHX2M<*' + ')VBncyW7NKU@2jVwaajYkt@5v(YecB2N0h>0)oiJK^nXdwv7V~k7^o$W{fie+)S`^3vQgo5_jIffdgz%FAajDnGd|lA?&3Sl1b`$' + '8jy#Fz#dVzfatgaqG2ZD&>*aH^g}bGp=ADneDLcEP<5iT4!yz~YtJ``5T=ota$hNModpPYCG4{YNItO4CKLjFu}I4gkjQtWi4sK1' + 'h6%Hid>~)Inuq2w$XpfPtOWuBfSrv!cs1X3IT@Gpcqf}mHKY*2n=FAvNm;QkQV~%`ZYB=E>Ui{O@qA=27%LH=dxRZu9}M0)DLyBh' + 'AXF2P(@1}nxWY!U!oj5%*5U}W6AR7AfNhCZ%%ZOeR2PLIU_)#*w?J$HY;kzsw@z8X7+$fSDBcRaTg@nuFx1eZ_WdMij3lUFprT!R4r^WgMxUz8R`-nY;^IevSuhoAZ|=Gi!Q-4Nj}+$=$jEG' + '@@+vN0B;iItxMbBIVh-+;LGj-3LJ;PNIZj$`|&C2hJ>JXVXzSg`0_iBIXmJHxAeWGCNqCnmA@T<7!@Jhi6YLNhpqmQs>u~d-ZO{pR)HC^#' + 'PcRl?KGhOHrPJOX48VC;|y`ka5_)FhSN~coV#LFkv+4G4ZSa*Asz3WRVOijp*g{GXp7<6zCSz~Kga4go|k0+3;9BU>L#fbyZKI5eodq6)pnn~@alA`N{Tn}w!0Dgp*0Abp5&' + 'Y$8_r1yi3r&mpU_VgjwOf>Kj}7&)m#fG)Ni6=#sKvj_E8z03ovuWw;RU^b7EwPkF@9Aq9Lpo!o(o`&U!2B98Vx3~iD3Mx2ibI4Fr' + 's3q|OFC;?;5g2KOw1(WK' + '2Y8t>hqc=@rGba_zYziXLaF3ZaE>xgD5(3ZrDB1`>nVdW*>5aJsF2tA;b7{U' + '2TXZJ{H`pLiQw!2A^@}e0!Q6iqemts?OUKo?+BH4BPuK{NFK5x>6RM|XfUxoQ78Ilm=je96%L|aCQP|q{Q+qk&;*}See84kC3TPu' + 'eJ-6i$4TTlwKd0rOCO6#G8_>JAajW0ILE-ZxP`jIaz?d8r3soAH-pMvy5{)!Z`Vw!*yHl#Ii3U|RvXa1f(j!`Tm9e_?`P#z50%qz' + 'sT*CW+NLxhQcGxVaA1lT-hw(x>t>9$$e^auM}>8k&&C!^$v-QW$i@Rz6Ey)srUt;_EYOVxvDzUUMvRpQiN)R!^F@ty%C2P5f&=gt' + '?uivhqmUy+&`q2t&YvH7qr$Xi;odn92%1C_MIy5DQj76RcBCH9V&h8v=$L7EK*vFeK{cv4_#@f`WZzRH69P#2jkM=~WsNH?*bzTd' + 'UV%}RQ9G5rXCR`B1T)+hxd+c+++qD61?fe3uh4X$%UB8Fo$QVv>dM2Cb~#px6xz(y>C}S}XVoo>YxIlx<>1P50n)06qa*#y}9+b7R&=pWV?Y7;hsTkBz2h|gp(z4jUXkMrQ' + '-V~dm389bzs}p>L#X5POFoJLhykhOS_l>l$lYv73Ggjpe_mM1)NyepESrR(#4FO!O(MZ?qD3-Xmg#hzzFu-5@;N6hO1Vg|Eay>-_' + 'B~r3a#ilZG8`wGvbi@r(jSWxxC}F@AOAOTkz#rij`Fw@=Q10#!yf4b7gVil}5waWSKpE%cSSLqa%UgL?Y6rk77_' + 'c~Ss*PUI=C`yMz~XOlJ~QtqK+fip06mtfGFI3Q??L|%ywPEImMXN4^nE>;9@l{zW(f{SuUjuMhBtV=C6m(sgQk`Tn|0u$wx%qbQ)&~%{US{N4b' + 'N!qT^0iJ{)v4kDY?*He=o+I2QuSY`=nLtE7pn^A`1h%#8$(O;FxF!gEkwY#@C9*fWAlxh=Z@L9oa9NG92Q)%5(k*CKM=mDidOJlW' + '#7JIJRD#fcyz%U!dZ~hN2k)=!BY;VK!dNgTYzr$!+$F;UMF({%>?4yMM4{WQfMG-ozatkyRF*3?yo=H?+9oO>_&eLDywtT9K!BcM' + '72w(${L~!F@;C}7B4(f3MCzzk^Gbwp=%=Tc@`Y4m35A3)PqUz5@hk&KhryYUR|;g;vooLn_MC#yQ8JS0*l0d&JDVt+NlvIF6IrcNu3-BVtpRsE-rT~nt96@yvVEBCcwr;#kw5wlj>`6eTZzKx^nPX(6cUvHD36G!_DiyD-YAEHPDr_&2g7E??I|?TH57%@' + 'jv^MD<*}}fOxl-rBnGm({EgU*+1wvj=BPnJ3jHF7)|8y%3N1&prJ<%8~)T7XdF!t{*Y+;}A2ic?=!?+wPkJ=tNf^=;;Seo5f' + 'c-7dpMOjh_{h4D;3him<(&C!eXoWAIU6|H3Au1qPv>azdTi(V2n{DkhR`DglRuXiCEkl@m{_(9a;Vda)11FdW4RTdB+anT^o0NKx' + 'udGcFFd#6Swoh3L(;+2&*Bu^oelSl{{?' + 's_^KxJ2WWtiG5DDo$^*;3=w3d>ADf!Bvvj|TQZ%P5duU' + '++e*3lTB-;Q%|d1vU54K@{VbeBF)>J8rYen=<=8FVStTNY&FwLpFg9k0I{OnQ$9&E~9=A=2Nw7o{u(I!#4q' + '5PX4Q4n^Sw@8i(Q(NtMFOa~FjfTul{_amCeFbS^Ok$*{uWE>H0o{70XD`TUEUc8B#Rp+S7|U~6Y`HDx$e!j@%2' + 'f_Epv7)(CCIy70bOj))#K(>DrlXJUp)I>U;qSi;hs#|fRF1YTa1h}ALD5g5>4vLDVL@@geb#Ho4@##RasfoC&$8LlXI3ue0OW9t|Knt?#_H~GQbm}$2oUbb#O6z9EB^KdDdcZTQfQImi6P*R(jOFEN;bY?9znih7L0rm);WY2S`4)kk_S=3iwe0~*(_Is%>EJ;F#AId|v' + '2DTx8$=5U*180-A4e&j$HHY7R>=+N&gH?QoHJakX$xB' + '{bQeaV7uHFp*HIrBon!4h#X}sOh>h{bPTXMm2fu%@apt~f>TXLBKLC&>N5%!8>3cMCzHe3evv|c' + 'vqPXqFs+SFOXL-p`sjJRL|2x`>?)Vgc=7E-vJ*UP7y;(f`G(b!JtGXUP7mY#%CxEulPIIy&A_HGEqylO(_0Py}H;GDJj3' + '8oF)S^qb>Sri1V-{^rRbCqyf;H~d5hV#%;sOo2!j{zJaP3IR&#p5LZRhK@dBzErU(4j2ui2W^P%>MN>RKh6nJf+C0!*tE@(AImpe' + 'W=>RVv{BO8s{>w8W2b$u%g8o9eE{h#;SB2Th|h1nxcOL0fxh&gos7U`waj6Ogk&0=FRX_9@myb%i-c{1t' + 'vW+&Dv>ZLj7FpmX|LIC|M3Z@Qv|t_bkqJ2jKt(@+?&6y!T^O3wnW{wt' + 's!uj3H`6eJ8+J&>V)ILxc%ezo;iwRri2ajzA=&{1n!1zUuIXfoFETZ_Qt!@s&0b~+iU#}|!E}n@' + '7G8FS#ZFqWHDPh%r$1)E9{sXQk9?C+atyD?PxI0o@iVj*pZzGJ;{+|TA2+o~$49im=r!B6oRF_qS' + 'xTEb;>q>;AT)mKPv&Gz^Z&FVTAzxD!b=erK)N+mvG8tanNVfy%50#1wB(QUVV' + '*{Sk_hWO_1^gpFjD92%gN6ac&*hy7<`M9D#n!U$3(AZk`4I@yx-+huXTy`EE8>Ck3=a2Z@G`?r7G{hpuv`5*rSP)h>@6aWAK2mk;8Apl9V' + '20lRv005mI001cf003rTb98WQZF4VeZ)9a`b1!9cZDwz5WHK*sa&L5HX>MP2bZKmJE^v9RS#59II1>KuU%}ITk#-er(*%b_7CBs+' + '^=*q=H$~zWw=fKak!V|uL~2DUtsCUO@9s1wCN2+@k5#?7ytcU!oPkqHT&VSQLz+Pled9Rv@t<' + '=vBcs?G0*LDvFAr8fUk7O_Isv4XuHs;D}Lza8*!tfU_b;oc!4mnvn#}fn7A1@)c_;jZ8k*nC2W2oNZ7;c#*dR6;!Z^l$C8rc3U%e' + 'Mp&>4aagEu3_^iLu5ct6!l-IXQPd?tY=w?q#t0x0sS3f-4Hm$YPXxOqG*&$NH81G8BnMVGG`l)Of>6#Ho}f#zB8^(k7;Ex^VnIM)' + 'Lr}?Z4sa#@Qh>Qu4Jd_C-LN}CG0-2}5ZnMV%1Kt@2KIR8OoTA!L?n|S2qqK7nWn22tW44r6;;g|*d?V*D9iB4#H@YfjN0FVHQB~+' + 'uUeW3#!7CPGq54i@AOJp+$3OA);1Y&RI8#STvLf{Ep;_7&gj-GOCff3u54mNCWKcj=^tqZ8VXE>gAFGLKIgB`E`OW;nx4%sW>>LV' + 'n*KhWP2bMb`Gs12bDq9^KQphcX6d`ji~01;+w{Z5`PnPIJiGWko&WJ}YTxNqOL#cHIO?ywo}PX9!xw2^Tu!gv{Q7<>MZ-^p*(Z4a' + '_J7`+Xfm0kzfCV@)A=RfU0qG*=oAID4v@65PLZw&tB7c}i9DLXd_&ynJlA@2YZRudXlEGMTy6T}EKWDphW)1VcrgP(&4d>d^b' + 'tV*IK%TJBBGFm{mG*cpfhnhx9C|1U4JmO?cL>TBW2h@L@qM>me2W$FFIe0VCJb^O9(aT^jaTl?$oBM}IuU42AE5b#(g7j5x4VXPa' + 'jTUtqY;eH|y3~nq+BB>Q1DWDn5_P9}U{6!$05BEA4ZXu9q{mK`BmfW8z@Ji+3ptptr@37ZoDm^oK-Mi(JZ2>HnYFuoHEh026rs-8)bLXe7w-nK@pV4*;TNFM*-eP6|iv$tc+90h;noN!-RG~6tpEihrerg' + '3TpfEO{c}z!)_IH4Vl|?UaVk^>mE!ti(ZGQHhgbnK3FR>1ez`C5TNv`nyZQ#Nu5HSY9~5-K>gJ@4EoR*_5PSb3;if`h2Rs8H7P%~' + 'T!hEb;^cVgOV^GEO=XyEpg{yLkP@J4l_qmbs_?)+Qi3gBsO@y`qWQ(W4*H' + '(P4ys`U$j(&=2yj6=X~b-x+LyhqiqlT1w`KMOOl#wOlTB=8#6*^V1m9o$%>+Kct9M^M8WIySq' + 'SALY7e#|Pq1lQ0d=DW4$erH8~0?%H-NzZ!Y^inPmcBN~ajFhB;Bl9U2FP3|VWl*gpYHx5+CJ%j^d08`x(TivgoUIxFILv(7^j;C#' + 'g?Q+f%ZcY5JwG%~5}dyZWYW;z{^97f5uBd|`yhG;=DER-9!jDPBODPPMLUkGLd*g_GJo~FSzC5&81p2!5Vhz#Q{!la>`*O&{|C#+' + 'cGx=qDKo_VO~jqVc+_kr7s87g!|ZOyLw#dH#&Tkpp!76t5?v4kuiB!_RrRXkrY}H11BeL@o|(w|o06@I43|3QIHB5ci*X8x~FQ(DbS0ak0dr!3`uCHVCX#)w`Uy>aNY)AoeXwP8o6oU&I' + '*bitewS(7aL-5_!q!4SDi9g&pgASUc53*S^Et0#(si5ql8v=~R_lCN{n' + 'mF)9nrz&q_k}srTJPZv;jE*1;sfqVgzc&-FlIK%7Fq0MsfR56~g4*`4<33V{qci3cVSe6+;7rM7mtH1U9=GW5mrhxo1wzg4irn%M7d(oi9@%V_a&>hKg%tal$w#MArhqY?UGTZ?J)}5RDC!dQIFyc5P46cehm5i@?<^4&LwTs;_>Oq;Bv}3ECg~L5H*~&qsq!|k92lY3ckE%DL)>DaG8BhI+N1Fmi' + '+YL9^#_VN`0&2Y)QQ*Oz}|bxL+%dTezgm#bFx-t<8ttN+mE8(b4P' + '0ZQ$EP*3VV8B>^h(p26b1^dQG=*<2CP)h>@6aWAK2mk;8Apok_lO*%6;GqN74LhtT+T4OdP;4gACQF7Sw{@`}KT5J4=cCQRfXFA3?~zo}H2sb(' + 'sv1?*UrDjsJ9xPJ3AWO$P&?Ra1GkUAe`r}>!;3b5w^F~@B2CjI*&5vdW82Pk1{njTP(8?ysU^86Rx*1QZYuX;T1=Wrw' + 'r%kmVLRMNztU`c-Z>llwFNcN`ZUiZZuTF~1=pQT%SA2&9s^*SYl3R;*giWT1aDCu8IKQcWF#CE&>k5^nyh(C1UCs#ghN@hd4)Y|K' + 'fIS43k3=4q3DB?Y-CnCD=*=G@6ik8p5?t3xTvNIQ6h-kATp=f`+HN^!oLRe?FF^|H*3tI_|KLt5q^fG%0;_l%+7Ojy))$fGaP{><' + 'Sw2`qKpEo~-c;<+F{5_T#kiCdKy2p-VHtnsLh_CD(NS8ARrs(5sRDJBFAI)?6lE;WXBsM8Oo*UNsoLUzVP~L8qcT=C!%mf7z*Ftz' + 'fpv7}qjUx>!A_(L-{ldRpr&UVRUA^=kxKX$k!K!zJ#n!Umxu~n^Fx2y}8AZbizJQI^G6#CmyjW*m;*}o)8!l+JSAC1O#%PnJA%D|Z2b(zaJ`h*_8j@O&;cF$k+CL^CPm#ucjeGXahQ>kBrr}b&k=7wi' + '8QFer=zDf1cV|E7d>I-k$`2rsXG=6' + '0(WOgoUp(ez)GSO=C@xz=8YLFNXdyl*j!+yr>Cd8r>DE8v2B}OlIPn)e`rOXC*^M6w0%<4b<-Dp+0@<1iF~$h>Rx>4tMXDk*%ke@' + '`nx{tE=Aj^pMBG=ubm%@x=xBNscZk~Vtc69I8;$#gQq7aTSRKzRF$CT-J-Z$E6kqtqAj3nmi$!g_ho&>tv5wqtgE8yM5pJ_ZOV0@' + 'nO9k|Ek(8IxJQ43y;bAK_07r2N&f2f(?9;lv;X?>r{^#J`{k?GfBEbG%ue#xuV3e{' + 'et7xp@sH2`_wnmze|nz(<%gHA;Q!F)4B-7ik9|V_O`yZ~O}#CzmM01PtJ-CtMY45Qn;B!(>_puk' + 'c6KM8*?Uo5UHAD$tc#oB+fIlL0;XBNRWtc_^SG$?*M))gyGIMFPhYfG!pvmXYz~!Rg!K^kzd;Xf3CqwQ_LVq?' + '4q38TEG`1w%DU{!qRKlEApt{mqF*MLO;bUm*X@BD6^Fh70V>z|rRe%#+owg_Zr)r3<$*-j2n064#u)?0hI-sZ*^6B__ppQM' + 'kg$OjfUOxPx5?ck0|WyB+Hznfd(Jai@lSv4H6-8wZf|iw-w;iVlEPnR-IqQvHVU}8fg;sdMALz31Mk;46Bk#;nZvAVXL5n6HgjK@' + '-B>j-PrRmP>(bEl6R)YaS*!Nm<4`Ai19!UFFT9T7twZ$=zxBx=w|`9p`P*w|+XAYo{me8U0`ia+68>OnHeh`unwD}wX!3Pr=!=3WyV+??MZ+_5Nxr?)vbO-wvHz6FCtW4a?=Ptqq9o;34vDE~z){xSW$zyOQ)Cn1d' + 'IAKG^MQ{Q*z9{M_cLhvjwW@(vKIcVEXU&k&79J?quvXpOr(^mss0Jspqf4VGJEEZ`GXBP0w%}+rLEEK' + 'gzZe|0pREW>-SFB%1yH~s*&W|Y8hI+{cW21cKQ=EPpEh-MlO6l;n|Sx)J~2JeFjt1rtV?MAfCJGtI(88YiWCboE4j5' + '_kNbKJLPuD6o*58K|sDhjr|3H`64v5vdUoXCX!P3P{Y(seOimFBBlUrYi{~5POk|=X`xyL4daQT<@d5=>pUb|!BK6y`Yz)-{f>OcdZq=P8>{-2G=;Sj&(_?kj;fsE}3&YK9SP1}0WOU0BE+!DxY<_wy-7hoeZ5?I9Ae+xIue' + 'NGzQ>pF~>;BV(V@UCF927}ArSZp~H_wwp>>|lqCb2yO=bA+pi+{;AM+q}~8C-caTJ(;IhqUNaOQbs-hs2;e{BkmCjwjB1Nou>xf`Xu++xi^cgIYwE9oeL<7-cq)GL#_&4u>*WDS!2#-C3R`t!LF=1jUBRk1+pgR-Ut#AjI)FlQgACtZ9njofpvZ|%wC^xZ' + 'RP@TzAdi~Er-KhRTBGv;0V(t9IZ#4(>80-b*EQVb6Hf$_b>iz1Z$ybVp9~UL%yHn{k5P-0!k8UGhBo&!9axuK}XNxmm8%46V_#=R3{7)' + '))#|upa|BaT;G@db%QGl*72deR8csfSS4CmzL(Gch&z!ec7~|fM~WIS)TY@jU{&A*Uti5P;#~=IT%BKJi68~KSs4T^Q_7Q0voCFM' + 'POC%{b8f9uMhu)Kmu1ndh|RNPgYKmjV?$c&caPEt=)>Lt%&_bQz5`?cgKNB~Ddd^Y-3cs|LhAa>3QjA@R}yng3qzG5OgQ&8oelD^' + 'mjHd`pvUVQN&1!KQ2rHa55T6iZpBsE0Uza;hwWB)X3Z?`dycT9t5YK~%SM)Up-1bB^)>7Uq%P*E+XpzY!*T0=g}DaiBr6+%' + '`G*wStHvOZru9ZteF4qq(H8zndXhN*K-?Y>WiX1rwDyMpn@L&`#t#@30}P)o=+7bcntJ51XZABhj)iG0wOTi{m(NGFq~h6iv%itr' + 'A}o~YQMMliao8I>UT(UtMc2PBcj7gg$*^bqRDLMyrJgp~cmPLo4MOA%dg){_gt8ngV=_P@B)SBpcu3_Gu&qegueVpOrDk^vW<H?O3JdN_+&VoPJXy;KTmTTxL{Fr4F60;zb?d+p$' + '@SFDb8$907`8)X-NBH)ryTMv_OAuY6Nlr>9FR_-RyR7sp!&fee$Y2Im+m&5MDY9TGOI#!C?}(I^zJ;Co_vM!|@y5eB^=4^gA?HBp' + 'kUUl!5G(JvW(Q3Nz$OJyr_jh2*=TO%0jDO-wrG@bzRRW48=!pAr~(dC0euM%&l}Qrc_8{4xsHZ0+I1' + 'JhMNk=$NyI1<=)7X&N-)6g2W6_LlSHYIiSKoiCZ;8tkC$0edGUy!BiWk+A%B6EnE>k%&ivR7PudzdI!u5WZE;q_-zXbO' + 'JwQ|7=S(7)vlpye&Gxwa?~Ba_G$$Q=2E3>tp3n5ivkPlt?&?#(pZXXUD(im0Ht=ljBDgSJVGJLx#POb>p?4Ir3?`xbiraM3YKk!5' + '=*h_Hb8oRODHA++mB~e3@gG|jILh3>NS#HM?^7TkC{LO-*sd*vYdemS$&m$tn;GC2AdI>xg{ByS8xD{89J0@x14Bv' + '+|7tNYO2`248S6zVLiT)QB>ZJ;1EPD)8I`IYPsO)>a9OZ)}}Qm2M%MB1wh`59=%<{ay_|wc$x;RBNr#VA}cuyca)X;bHD*3XuIo4' + 'IklK<8MRsIW}6dDt(vYI1XKSRwk=)xbFY?U^6ngi@(f!_8z%I#gls~MNak~rq~ph1*6AMA$}YFk24bX>cZ7QD`pNbH#!TS3y6_kX' + 'XBTTso#M-b=IONSj~21qv$fQV5_X932WL@BuvAjjF#jQ)2Fq2q@zkUfe}AyAKe^ooO*N^Hv`_CneI0ZQgV1+M)iGHHJQO}M5zO+T' + 'PryXy6%2b@5tTtaUmdyr;F7!NyzmV8@?u&}$ln9?)*o(pk1MPFL-wqupaw*w+zoItrB)N#p1p4``f!Pdu' + 'I#50wCXLuvi(Cj_Ez1Nm;zwwjLFXcJ5zSZO(QZ;gfiQMQZe5V(iw{|h81#5<(nS84F6QV$QXtVD4B4VY;4' + 'A;6I=qjI7BrI%}N$u$a_@A))-*0^ufzqZ7~`t9I6JIe+lGVmp6Z3yac%KZS!4Aa>SrFPs6h1Nan' + 's~z%Zz-cR~ha~j~si`*I+_?xL7Yd3iU-Ap}&cck^D^G;e#&t;qP##jmTXKfI5FdIxAWP+Jg6$TnUdSij@vx6{lSZYSPJlyRa=8PG' + 'M`XDLVihYxa>xq|)T4G+7yItIkrbc^nMd)+k<%i+g7N93R*;l8Vj%S&M^dP*)Q8zJ8LU$*k%x|Ut)GoD1h11KJm4caLIj#pKeD79' + '!oH2TAXJjRB*xv@fCHWA6jAgXa)Wv`@Mm|h%))1fjA1UvSt4Kr8A~z70ViI*y4s#~W;za>P9uVu{a2Fp)(temfbWhx3KkuXXVn-E' + '&gB1d8A7g;U&{SlU*e#rbddoF!-0^YaS>xkPO-7!pAY%@A{~Y2C+q~E8TP#xI56rCYh+n*%~&SV=ZaLiwp=VM%ba>LHA;ItLaSo3' + 'lzlZ?{wGG4^pn(^T(WTTm%l1*LILGGPc8=H-APxuD$9|Klq`6Sol+6_gfkD)H}z{CaE8ayKa4I^J(JYFL#}7s@qIi&c9kxCLr*!O' + 'ylHRPca?l;{8)jCOYna~joEDW3P3nX4KfEBuU=h?l6}NIWlLewVa6`>Yhk5fEsPQQ*iWDaYqO}Y1m81}?9jJD&~;z0){)9IIdr|X' + 'Ub>zUCv{`z)4c7HW>@xd%G!8M_^K;w=xtF}n5W-ar&P4x' + 'M6tmOWimCqzQV!?McbF#LgBooqI!wkItWLia$zo}IG0nVYXB~9@K)TYkfywBk58GKT)ik0dvdwRs^|Gh=#Vx%P3!Y`u4S4J;f|OF' + '4yH3!!{pVWbfy}Jk6ZMoLh8t|G<+fPVTGa+s#8_>2W^GgsUj|MHQ)OxQfz9NRQD5MZWv%' + 'T;gfrH9kN(NLI?(eC=E!#TkxPYo7^b&_tQ5yy_HJe@RBrr??4cafPOzBHEKO+j4' + 'dNvjvY4UZ|al;4{zu6%D%Yt>;9O?~b@T_d~C|Sz~Gf3I;K?L(>?{Ev_$%`LsM9mlD5xhAf?F#sP>ny9zZAPWC#s}3=b>>v)+' + 'r;h)xtK{!tEgW8s0bi0|#Rj>+`Xg!jIT-WMwy>jMu@k;9)LE%^RFaJ8(k+q~#riGm%CNZ6W0}j&+%b{m1^7S)J4%0J&SdMRZ4Y~L' + 'ku;a&EWz4E0k$8hQE3|zbfTUicaTw_P0t~vR2!8=c5ZrIj4Xa61r629NH4D1=yaWhNZ^QT>I57%LelXG4Vs%W4vd)hlqILmrprI+}l7L_k}p-qc#S=4@l&&Nx-53LEg#bojojTxRpElBaknWgbFju|Dj&!%o7?lKEMdJe1k86>Q~anH>VFH_iK;Vq{2j27m5QC0Sl1r|QizIdxvL' + 'Lak2m!2=sIe~SS0c-tEzM!ejCyP3&MbEoSZ*ltTRrTpn{)DX33Ww6;-d`4quR6S=CKY@eGp' + 'iI5Cjm1Y+2Woe>u4P`++hJ-pEGm~*evZ{P5<`&Qq7|47i4Vw?fhh$GmagA?Pph6&*wK95r9chh!E=Rbq6r~2xWdk=%0QiW~jHV?+' + '7e$?2@c|9*7Ef^VG4NlKwPgz3dkGCqyAiFU75Adu&5z9~Lb0%toSfk;wV}OQA-|qiK?PG%(2p6w=bWZ@zEp&}P>V(9T^EjFKDD};' + 'gneOue9jRT{)4Cs_-@Opn*ZIv$&%BvhY!buSay^$d>6@M%%Z$%kRTZS`-Ya2I%TQUP|lr9A0RNc=ArL~+d39s%=HJi4EJcxkwXQp' + 'gAB(Zk^D#U43%f+s-)IDz9mud>$i{0M+VmLfbK&2ZWRKB-m;_gnS#;-^VBXYKQdvdHU2*5ng30po{ph|=|a$teOQ5fQ_Z~RksCUL' + 'l|CmS%%(Lzi#ES-HXEy^#{hph&Da!%k8anWY%u1VFgF!+GTVdPe!B!u!vR?Q@e|mYJNYGuEzeypP`TTQO2A7~RU4u$iyi(mN|F^T=E>I0jDTu3OQPV7dSCj8XkNz3UA+dFdeVRn)*GLUV1;BD=QU' + 'a!q3S2fz-SqEraO&a0n$#X=^O7x9Np?^(d)SSb>Dixaau=|i)s9*Mk0*vlI0qxbM{tuvGLF&G3#Jh)`T-2GIHIR*re0JlyVE1|C4' + 'Q`IQ%zCD(U%AsFduDikIVYKVK_U5CuN3I&X)}-5?$6GpOtbz>@JE~0M9x?1=r9S5A_3=&>?aeZ*E@*iI=cRGlhIh2U8Vx?@6NewZ' + '{BwtqxU}n_y>(s4@%K2lnbLSSiXQOIYdpV$Myg?nyh5@@i}e_-DA&-1zii7+JpP;A=*3kRB!}r`Vh#g%5rRlxfeFC)5ocC|QMW?O' + '^e|fY!s7+OcOK(WTnO;d0sr17c~~~EWq&GWGy}?C)zEhD3?HQ' + 'g1(2|sX90aFIX`H^CcXHys&O79baJ7i?!JHV1^fXg>(X&ZROXERo$ujl!r^uv_%0vCDHz9iTQ)%?Do#=Md&AADOHj#E{pYB`p8X`5Oa6TH2D|R8&59}rjm4}f-+36K%6v07GK#?j(|c8G6Kj>rJ=' + 'T=XSViUMJ?ipG}{$}^zlqr`s9<`_-OXoZmvM2Cr1jru)disrQ-2wve;~mm07HxUQ5k2So1~w9cshfRV)5w' + '7k2r7P)h>@6aWAK2mk;8Apo>GQ9}kf002}C0RR*L003rTb98WQZF4VjWoKz~baHtvaCzl@X?Nqs5#V?I3K-tEB4dhkIPuFeh7*lM' + 'd9>C@dJ@M+WCejpP(p?T833FaMbUrXs=liah?JCLcm1A61kguyb#--JT^+~q=ysV;qHH|Q>pE%{QFD_=2U*>m9=)rh`C^jKqO6=m' + '%WCm4FSBx-NAs*Krg`1;x3)%5xXQ1Kx~XoXqK=BP$;+l#l-X2%;BH1i5Amnw5Y~6tg%s*mGyK{&GV}6!~4aoD6gY&R&GJ>' + 'RSsWrs9Z1eaTC?6<#JIq0OR;3AAeXbppUv2&5Che);avb2B8krL*VMgs)C_JpNi&Yv1+!8vS^BIR{R5d$i#rE6#zNUqeT@>7N5%5' + 'BAc+!epB%a%*%' + 'o0pzsO*WooHH=G^RIhl6>8czzfG_oZR;|);2B_iR`CI5eQh*7J@bAm4xiP+;!ecKwh2fnp>f*D0{0Zi6@u|iaykY}nqB<2opH_>{' + 'w@5##2x3u`*)4B-(NTc|o%LXD>xP$FRfwMcGOOxbmW6lhd24GceLMU)JsU;|K;9oO=1Z85P8I*<;^of&%66ugU;U4`yOsX5d%XW9' + 'J$rwgzCSzi6~AekWj%QD;uBEE)vx`mcyaNU7k~Wn@+0)5GH*caIMD)6?D2+jQ^k?%D3%Xn1xGwE<86K;mvX+uwdSi2wP=-)tN8&W7j1vmb||BKAq^Y(?E=lJ~*{y9B6j_+B4e@1sx`1tM)7wPBwID$pT7VKyEH-hzfop)aKqE|1wM){-D(K_V;*t6m3$@$^vLZfn`BF%' + 'cPNCtq>5G$a74=f$=>^SKxDh4!;|Cm;B5DMd}^aMFUY$`M<+kQ{O^uNNU%77#uP4h4|4wK5=QL{sf97wp`bNJ>U;AEu' + '9kcnAeeREbIko8R4(cqj=4)2{iv51|O?+=uIz1ci9i9WAcEw3P%~rDpD`u-^!TMjyhM*Dl{Cm>Yn}b*1q{E+wd+$e{25aQY;}qs7' + 'o98$cvPt7rid`W;!gkH&s~LdZ?7ZMGE|Jmhyzalmr}22_>UO6b@6_4OZ~3bQZeIl4(a8_P3{s-&aUR' + '>~~YG#<=T{#?GOkbZ>Vw{Ql(Z&}pom!={Us=lP^q&GFYPtFBplpVcqY@)_R;ho74&gVqGMzKF-L0n@^lTO%Or7eKnR' + 'B6FZe?B|*MnN>|O&BhFuS1{C@c~*UZrhkN~v70ur!GPcK|gvZ-UUK~l`<`^;~stc4uxg!%Y#MrE~`J}lcRT`?;spDc~KrL' + 'W++>SN}Y>|-9r%dy`PTW3>Pk!8Jrb!y=E1W4*g' + 'PTSeZk;9Ka<;C?)Q}ZQLf)wARpen)K)d&?%)xqxJSvov_CmMNsaz27K;>oN(y~>K^>Iz7c>m#t{Vvto%PDUy0Pe=RhGg<>_%rm5o' + '+Ct_mzXpnCZJMw3cK;!-%6vvXu9mRK@@!6CPP2J2D`+cBw<2$Zo|U3T)70inygxl3oeg3AuQ8lrVz!AI3<0kqBwFPk3un|^SMk4vP}y4c})Wzj`UdPcFDg' + 'rHg73sKmd+j7&K5HgL)GN%IW~d}%JXOES6T0b^b9*@lV2|?^5I&^ifnCEOkgovs*!0g7oL|cn_(XuhQi*iO0>hwK8lT+6i|YO-I4LZ?M!_Px7XS!' + '40UEPW?K6iHtw7*dm+-L+@eH?IV%?>ki;zI6kp$DuYdQAp8(w2K7s+^%-UV*4CiQ4;A~kEg(RT|$;x1yeAW@%Rc)=yDd$(5A38z=' + '71%bWPMqbsXe>hXO|iYxxkyF2wqUP;GgPU&1R7LZ(hs1wJg_qY0RR`v)2sX@`&cY0W9VC3Z1F~C!s0uuszufLF`KP&_Om-+MdLU=' + '%ZfUmM4xW*k{Oh_*;N{jxusY#oroEdVBe8OV;lvv4-Br)%&=RNd>W-_Y(W_uR>O>OM8?lLAF=mA1mN@{T*9#Xv8Qgd^Bpy8YT$C%' + 'dCLfBBPxL@qj4Cm%hjT;cb3pLa_UI-!Yrl;;uLg%XgpiMxHwqY8cyYBn0(R^hpEd7eByRgQN*ySnuS&+%9yDri%KA>tXX;6QPYC&' + '`W4C_%T65i<8Jgt!d|Kp03s_?gF@4QSPOFJJp0U^l2_e}mzVw35_Zf^7wvUz#zeD40t0_H>5OkOWRyAJ_}l2~*H})yB8>+uoTS02' + 'U7HN5yJePRh`}O=X*9$&^_DJGOd|~|u#bpg*c=VMwChUL@rkr&642OniuDW7&aZ&8uA00WZn0^g9;' + '*VfOhHAX4ydj_z(m8y`HSS)86--)$;kGzD>D`yXb=#Gv0' + 'i|SrXUB#C>g9Vt$pOitjpx5!y^RhT=V?BCvU6hy!9WhQX;Io#o{<><$Sm' + '5m+HRutC3y2hnd|_S9xQh`#yleg7Jy0tKH&2XHLADWK+KAft!TF^%7s+BG`WxG~A=PW{%)L39lLb$A7~mDq5=)Cf;pf4Z8@=2o;GsY&?!sHPH!A`=0kSVW*$!IszDsud*2wNvM_DO)%I>%i^~xC?&oYmf*01#kR5' + '-iV86VmfDnui_;qCHg<-D@KUX+tu|odQT6sF}l>Hj2P8rwybX!jbxB0UHQII|J~(yAjq;n0~_y_6p%|v-)O7@MqXMn83+bjB?JA7S*|fnIs2-b=?BgMjAsxt2Po-oG!WDEphj}2$VNsoV0$d)0k(5o%plko4a`-YS1kI0X)zH~0-~$Ms+?ritsWDlCP{m3' + '6h_A#9x6UrT8S>Eb&^5kb+NEg(zXJl62?>Af~~BG9dr{y5&G`;Ox^X;id2cC#j07Z0Dpi!i)c{CVTPp$tuGp}uz_a?%D`9?eCXVu' + 'Ox|w-_IqI~w0O#?1#y8YmNFILhB?74HwzKBNeC^Z?b1WrBoSMy!)QnmvAu^_7iUUbV6tto?R7T6?XEOm{=kgk=dd$+Rvuy4+-%PA' + '3*>-;pfZ9+Xo<*6Lu;h7vgy(jRztnM)ZYCOCN(P?L#$+0R*4zqMl=xrWz?Bxf5Skec>&6B7vr>$m~k}#tJr(LzZ;qMYWSFeh_PxG' + 'bM#>%gXZB)NOJ%fd' + 'qmw0L7|9LFH%;Wmj-w3Z5?~qDU_edm<^F+5i)D&}iYjB2gnus0CfBgE4`5?n%wQ=TWV4#v09p+0p3(u2NI0XQt3m4SWM3;r#P&Am5jrR2^mm+tq9j0UCb#V=58JMCuxSBX8dOnqlgthP9|KEdfMx(4hRHQKaBM(MQF3#vXfnEib)wOQPpPe*UIA+5' + '25f(uEI)KcpCW^|Q^sZ?W6L-Aw^AbWnb_8NAnfc{lslpqww|I}{YunZ(b#$hT' + '20jztyza>3nIA;n&`vv!k9TVNx8y18C>1swZS-{g~6ii@sCe&A&z>j^|folf$6nJUpyb8j5KBMdQi2!co=_7kBfQr+WOq%XPmSu;!x?VC$zzVV1b$g)B3C7El^' + 'phx1jZPU$8nZ~$7g&PcV&dX;DSRJYhW+-IwyfBk=$D@E_1c5{pU4=MBd<91N3{j5' + '7Zr2|{F)gk7N*&))jrNz>T|O*`^~pO_Dk;7gx%fiNDaoOrB9|qhNHHOOC7fHqmW@IAcC@@sKM~qjl+F@I*^?fbI-G}S^Q!l}i--{_w-T9ui#^hd+OLZGMi&r4lF2Buo&js40f%p+EPMHymrK^n+vRWME0$Z*{$yf*}2C)U<#&0~FhxKSgnx77~H' + 'YcSfTVl<;7Iuj^ak{7hFni(H(j2EdA1_$Dc)b7hIk}Jihl2L9~kS}YtQxVSJKL`qr(U|JW3hh0K5?Xi)fh@72UIh' + '-7KM)zfgc*n$u@w+osJiHcVsA)1y1p=NHvIC}ugg6=W0OT@AM3xMQN&)7l*c658t1Vwzrr%O?r2G#2wgTO%e8QQaR(' + 'A>t4?YWSr1>6CqA^TmG^RxRvFeGy(6{>7k7VVq87M1WY6jIRA!h-U)3S_+1DKr-7lC_Jca2b_' + 'GeMa~CJUe!lLqd19|({LI_4gMbX9&R7oX74jFx83hD5X>mEB4yT`Dh)3JYh%)sqUXic{`wS&K>Zh4jvtatFY@#4O3B7*Y>XsQz&xe`)5q1YOTZ{vGsk&%*|<' + 'at!iWlsh?d17TC;b)QCy@;F>;@OinKuQT}y4W8LtT}eAI57AIgoE{rsp2QyTz~W~|y>$f8&kFkE1t_AO_{+E(eHXoQ&Ka!FJxO;yilqf`%!;9ApHd#T' + 'EBrPXY&5O1YvvA@;pZ6n&#cf4S`~lh9IcV9d5(OrPJ<^e)JX{}<{ZKMgG<0K^!>ce{_xu`qd8m{2)<3yl3r#CE{Ppz!|?^E9=Aata-rjEZ@NZ`8AXrF1r|^GV5qM' + '!i^J%=L~AQ)Kx9!*8DBjr;2RmswoTRA{iQdnHfA88SYKZS~n)t8N;N<&+*}3m<}%4Fk2l+h7E52c*3H?+6R#ecSbB~1jHD?(vzvQ' + '??)-=+D!X)b!7#=X{BBCa4)BtTiT{PyOvj!qzJVINu$0PXCP(TBjp#Pcaz&0V>M#zWI+v-BfF?s&}Bjq2qNqWBsR=XSx%VilNr!d' + 'o1OUG;fUo_E1KBww{_xOk_*nr3`a`s?d`nzW#@Qr=X_V@h_lnik@Bb2VhU@rY<7mPc6P6;oLP@`YB^H)y)s=n1-ish;x12AA(`RMu1(>%H#bt2L_gL9|<_huOYGe+F)MP82WlYA2v{QO<5XZlIVsER530HXG1(&=({t&)aZ=3;jjZV*__h(0zv}IC9$vm?x7TMBV`3X3#=(G!90g2g^u0}V32bnL;6+dZ->MHdo&fjmcMWeHp8|#J5Z;YCxfc=|8sFT?>Ktq5#sw$hsycQL#)xeqta;q#~o3kqvClTX=hGaTr$e+4&0@*Mp2kkO10^6p($GL@tWk2}-*z' + '{Er7s1;vPs2vZ&KvkM|yP1ljhZ;=1||I~' + 'kg_5LBrZ}j-o+zTiVex;tQ|w-WE?ONe~nO8u|yNKlyxu*A~<3H_B=r_d;${th0rMJu0aBhH*s$TPGStCuZWoS>@)^Boyx' + 'IE+U}vjb03DAMri9EBn`p>@8pk(}IAxowl!h9C=$3+;itgOPy3s%N#|y*EdM(A)dCK%g8M>5H%tR#_OsTGBypn#0LFI3Tjj+~FUl' + '?RSZrGX%Pri~#6PLNM(dOl7O>iLp*h5H0>Ha(j^xZjjzfr;1wGoeWf(C@Y^ASqx+-#1p%0;^*sM^UZ1&ng+fW0<$1=QeIu@)$oJR' + 'n3IuD)M?SKsm_aKZx(gK)MF^^OE%t8mC?f&$dg`7v!dF`>-h^2t=C$s%I0-TfxoZv>bB*(^zmwA#3nN*A*E9YnmC%rtC=-{oK=P9' + 'P%*MFA7C;gE5;bwH^233ce2PMvZj%7oWI@OL4eJ?X=FS={VJY!J^Ff&l~JRlJ7dRn1v2E63I5}rNY|<3+z8ximcaj>Q|xTg68^{Y8OD?_90e?bU6m`0DJ1#~7d^biM|(-N7hYJYFW$lc' + 'v{4qBNw6s4Cd?**P!X+%zv=oSV!Y~+T@axm<=Bhvrsg#&k9kxn9X4(mZn)kkj@ww0Dm~e|j#raJz6YI{-V?$hIo8Umt)y5ixsI#a' + 'C+#v(k1bD}LJ%!+2JT{+ya0tTd2hx2BfS|G+dqX{rLDPA+Zg~d?5;H}p$?&SIb)p+}F^`hKXQgf1+)WVKw' + 'Y#aBhY~RNolt@`bcKLjBhl6LWD-WF`Y$L^T!h(@bbOJxIMky0ei9-@mQByDI!eWr>o22GTi(`2*rfsfatK4d#keS%i4Qkk!hwZYv' + 'Zio!ixb5vgD|xT&MAJqK7*81%K=@ZFfUtzp-$pMh^m8-qdIu+jMog$(|M6(%xVOi' + '_@qsG3P7Opzj;%N_1%oeoo>(B{&AOQ<-7f&u2)z1teV?E%JP9gLT|8TG=8aq62LDbyzh60`4`4Fi()Lq`f7!jDFF6Bw~Hn)W|3ru' + 'A3UrVB4;E+0#8u=DZ8zuIkfJ}K{7}#c3xdd_B&Y8>Wfx%rsd1=YUXlnf>y}I{&(Yc>htbob' + 'V7vnoy+wBtNZ7u5h;dkgTvUT~Xdie8(5Np+#nt{2oM$D?Ba8*sT_Eefb;)4;r!Mj)?8H;v^oK3FC--esSTzd3g!+-&BSIrIJvzL#(B9JKHQX2OHNNIhV=~jF;tWay623*DNXf' + 'wYqv{Q_H$(Q+IIiud${-&W>(f8YKU_b7PX|QF2e-J^#f*Z5Isbd(xbX0?93F=4|`QICr_}JDYqWCMkCBxfdjG>Q+pNg+yy_ck?5J' + '416|1BcF-SpR>SO0-Kupbut2x2EdnhpbZdpnt!dX_mu%)|yAUZDwABy7Ol^CL|TxR4YIj1?(+bEap1tt8;Cz@ECKHb>g' + 'P|s~iMno15Uu2C$isZgtR7kx+S+dN`q;CQrpN`K=%03xU}%JqSU<7Wia#!1%w}T0' + 'pv5^d61>N|<4w%d6Gg1dxSowOX|WCoT96db^PT5xIx>Y8t_OT}D6_k$!YEwZ$g_3hsyMuYB(' + 'i9;H8lKkRA!6{^EbsEn|RTqR3IWATDNlsn|Y>EouZ$>(8o-~HLQd-o=LAV0nx)f*Gm(g3@S0^J' + 'K;aQPVKG>upV$%Y&?@h3hHVC4ZzAUXSK!S=)`p>+-t|Vqd@Q1```KjD(OD7tpIF~iG3ZA5-Z^YK6PG)1~DHppvQ9OV{?&N4m_31+=FK;Pn&Fs`G##cn7*qT>(fQ0+`hU6URyYneNrvmHdk#l4WS|7J-dDE&U!+n@^H;?7s`#fko$a&8?T$ughi~3v8W-Q^@~PRJU_m7p3^81P|=s9t02W;Q&V%(@Oy>BiQh59^9yQ9T;I;t6}@6`OauU31&elDL{n6!EDx' + 'V5z#3M3wDrtr5TGja_>{#dWXgOT2rSn-9R>4nHwKp8N#`t4OTpo}YE7C6~CbdVYV6!Z_6`Wlq|TjLVXmWp8Vv^x~ExYNit(tQDj6' + 'WM_^XN&nko^4+C%GC^lO_rpkot5mLBL%#&r`s})ZPC&U-{p(b_G+HO9f9Y(7Cl~siUnux!>jlC=B!S1`mqhHq0&)bDVwXl5HlU?(+YMSa)cR=!;`jW{Tc~wMHo7pi`B#X|0YA}R5' + 't8J5>=*^BHTB4;w0xfOIK36h2`C)i`_-9yCuy<~Ls6@5Sf?8Qng&aPNkoIk9KE@@LLABo~;~SoKH?`4<4x?x{V(VZRj~yEX8{|GG' + '$KA&}<6I}376)U`sEkmIUg(}az4XJuUX%yumJ~X+{>wcB?``{k5O?%WQI=Asww&MOno-K$JehbO;GsN)tJ#n6b>eZ1oMnDL=&#Ub?zyn~lo*;pWOt' + '+ERyGUQka%GCOn%H!7#6tZY3;IGO#VMgIs1gdZ3GZ+0bT80p*#r7g`fl$FYc$pB>sUDl(mtT%=84a2!Kz86V7c' + 'EH@PamAPXr=f?(Kq76-ju!m;TTwSFp2W`sNCU_`ayJkP114teXGb$68f$)yR-E}QlkAT!%Jr7@9PNJE%thmF6t6O_MyV1Po-*s&p' + '%NnhV0I4T($hFX*cW3M}&&oExRorsBdo1-pD^L5QKyT?sL99|Kf=qx{5;wEkAna}J2|~DN9bDTPy_R}wPvoY9H>S&=`|MLHFUO?O' + 'dh%`DN|{D=*9@pWZFsyy!sAQ#>%?NHK6t_s>wZ|zMMtaxyEqc|9Q>m>xdF0kCKMRZ)g3Fw?)%Y6x_5-B2hKglKi!${bmqfGz+LEh' + 'k992Xt+d#i28}W|W9~Ez^rQy}F3Z^*_c{dJC?zgezz^R>uQvtx^tQPHj=|C82kcY4X7Fs3' + 'YsXabbKueH*UAoVMc!J4Llot!ZdavbMp^QcP%0@=n+U_iXaVJyWweopTmVgJjeet~X4MNH' + 'I`T{pt?QW`%*_$9-Qd>CFA@ixx~zd=&x6TJXfdA(0WU;!W@=oB7IKRC3ddemOSot2oZaG#jHUyW' + 'wp=v>j8Ga$koeV=`08ZU$ge-DI|JF{Q86#X9hU~3NQbM@!-ZwxVISCzCVv;D2Q(HIJ2nq?c*m(4Ct`~O2KHvnPFt%_q$vON@c;#x' + '-tW$W2NTg{fr~GtayQS%H$aW`d}mrzbt4f}|3P`c@9dF>EdZViLGj95?U!^s;My}u_q%F*15(GhSyj2;VQrQOdaadnM|;{W545$1' + '&_~NFZ}>hE^T7@n2mB}OU)xjfSTGXLyHF)zuSlFX7UrZcA<*({lVhs_fPB>j&s9h;)(aIwfKlUkyx^ip8*(N$HeQwY^x@{nMvS%ASg#}0M^ZfU0lBRmp3C+C0U' + 'a0nfmeL{qO9fTm)!8$tM6+bCLRd}t(KmF(k1?|7~{K(mRkl)|QG@HR76`q0aInR*D79VwlL>gXsdNy{pL$>D}BT=9m_H#im8-=#7' + '-RFPj8DG=zs^HrsTl@caCuQO@GULykoz-n!>gN(F^ZbO*t+8gl&HqfMO}bqEuSvD}$RwLkt;Rhe715Jnwcjijm>jz?q4lZ_{K<+g' + 'Y26dEC%)WSMcd9*Hx-~N{Q$e1H`>75jz?JZ2PpY0p-8&pM};BL{cm*k{(O}FaCp4GPC$|y`yHD1mcZA=bc**0rdRn*_OV!0p(gBd' + '0orkzvmFO?z$XU>M~BD5^v&???vIBjXXj5(j7YmLNZ12#G`mC_QK13cpexMKjHzrucrB5M7vm4JJZ2V{)@;=uX7RI~!js9au+?qG' + '&NPI{whLa%K|3yrBCjy^^j@SlMS7M;T?*E)fHFgpeS#PxTZD;`KP>@DCy2!}d$N!lgdyZyDH5R&Z7zDpFeM8`XqZr@^x{X-XHk@SZ&o3C{ZB)`5$^|7nEXiw+sh3XlT`mUen&I?IM!9ugO' + 'K!)R7Q6F^fy?Q=f%qFkXuV1o@E7kKi@ch_>!hU!G7`W4okGDv$MlioGfms+@9tADzM4*iYp(Tu-=-M-i(lsawTMLsEH|8UuGoIBc@~+RFuJ_6`CSg6NyMulFH|CSId>wo|wvU6q9eBYs' + '*djEODps%j8_$*ri$n2W>&C#NoJV2zg_6ae&3z$Te3WfeMkQgk1JRD-w$OS-DM?@>J^#TzRAfj9DXlh2U3+8eZeEVYAXA&!~D7*n;NVhnYqU-Z&MDk1vebYI_+%U' + 'uuJXxo*Q`O`Db>uF3m{gHm0;{d1EiBeZQHp-_yS2MWT6jXNGLP{~&bcRl-cs7MK+B_7~PY!W%(5P%t?sCGh$cqg`@GwH@jpEjH!>' + '3i&M#o|O2M8ui7=1Xb(OjsezO0!J+E9kIt}W47$wJJy*-1)4N}h>RA7>BEi@4y*D8ICrto?@R}0%u_|(dseiCW|u8EBfFdG8?Bh|' + '9~&v9n2sXzN|}(!g9G;-zli2IYX#EAp?lZp6=I41y4uhQJ^Qw+By@+EXh5=MUz&{KAAmyOZr3f2by6t6YiUEad*uA^&%<=4Xrs2aCozDduN{a)uKN?=k`!JFp2ezfmZxjj+rS)6E|?jPk%`+j&lJY%`2(!G=8(b?|aXdOqI;uL*54G7-Y)l@4Ug~}Ky' + 'oxU$ffk!Fe=d!wa)wL-zY1T^mPPSFYVJA;kHN_OLDN>$T$DZiNSPB_5e8p-(?R!EV(F=XUJU1C^-{WIX&Ime6nylBui6v(9)vqNg7;?p}~F67*_p7Y6P7^~oi' + 'WuyC^wjMFqf?apiRK=Jlnvs&Z^(c`auk`fV1NSRAdG30nBhK72' + 'uN!YW$@(0pxu5?;_j4T7{x=-f{udwE{#PE_{udwIe%#@0Va=98zB)wbxi4jsD5cc1#U;LAAVgMz|2WY+OhQTI4>t@+D;MO67W61<' + 'iRYcwxv8%wChjZi1{-TzNSxi(' + '4qtQa;EDHbAc~AH66ta?eEfVFcQ(uSOV2>5OA=|YndhFw4ZL}#x6CajT4OFhy+g4x&G1FLS^7Myo5|(TY6NBa0YTW^?{EzkN>AO$3{@894>a*s}1)Ea%3$|dKpv3ih9W2O{%m==@W50LAAE}h`2~{_|Z?2zuh-PF{mbivT' + '(Be&o@{;zVS!%3btlenUnqC2-vy!hmMaJWe7GjBk%;L5xvn52IZgN!D6;yhK%al=VODOuf77pK)Dt;)h(mjqN(Ld4XLT>COSE_^iE+C' + 'i9E1~BtE|_o15Dzdm)j!!vdDA@ZMr^v%1nnhDKt69dtnNU`!JR8t>FizI^q&moGmUZ5Z+pje}kTnr|Kp%{QAsgOIqLv&dQ6S?nDP' + 'GtYaQVDNa_;qSeTk*p145Mig&*DqiHZXIM1Ozi2Zn;_6z?O{M!Ou-YNW%Kq(ki&Jm<{j9etE>Q0?<(O81dloJXrI0r4IzA$daHD!' + 'Bo>tj!f`h#C(y^>Jc+}Q?`1HW4Xt?%t9-e^F#>J*5)MonoyIQX!J8oP;-%tq1QIo7Ptf5@wI}(Fa^bv?lN*t!67#G9`^nWxkQ#D!NJ$=+(5xTAyRUN3;^#}tZl-?Wy$yQ4~Jah@^i4?sA#*8erR~SxG' + '{Cx%T(zsfnafwX?7gU(;sFJR&%2f?ysPsWkws3?@-#TIDXQqY_E)eN^Rm|`*HT7Hu6ta_y8tdyzYbe4L+H5d4I&~wJ+DP8B0n(bx' + 'XH<=kr+1iF+$9=;i!ngJ0y&zyP3B+%6-' + 't7v;0&D5^y(!NL+#Vou5Fq**+d95x=VdmXaqRV^tXjD7a2OFZDawGvv;@U}=y?{37$hD%jt>nnt4n)`Lb_&pZE0J(a++!@iJxQQF' + 'Akmvb$3r+W`zqr*Ivy++aM0XjA90yQo+~lX!@Ve5y*0vj#di+eR>bOT*;WGEvU+gR?UFv$aol;)BvfJI(;;a^j8sCW%tu2eEv3Y5#BRSC{I+#761Z9gWJ-$&1OG67KYkmQg%<' + 'dw8~=p6;HFeo6UtmFyx(rFGl|?uOMJ-y=?ZT|Iy%g=llgL%CG{+m;2DfmR##H4HZ?F' + 'jST`eX0H2i-++z`h`as2fy}}VjSBMV<0e2P7&hzPjZ2YVg@-5kyDlLXZ?#jIuB&TR+}irntXidGyki#{|ICtnyy`OS1BKhjZ$a-F' + '?;fon62TL|)A&PnjhWJT>0ZQof_a$YbvS6D<>`JADve<=6yNkq($K)6Qqm#0HZ+vz%(NlY;stY7P36gut6Od{X2CCdF7Yt9go}s+' + '0!Sa0bi{-y0Q`^#)1T>WFo|%lVKmb5HQlwg3fZWmgA0jDopF-ZC=#>XkIq%khx@47DM(E1+ms!)wo=S*nx-oMb^P}In^f#TMwH>X' + 'Feb)(N>D!+0o?rk0j!LDwp+_LZrHy3VtBC0D@lgrCC<8hal%44SZ*EpitMiHrICck_fBQwvOW&^MPvM(ykSZ{Q}4cfE3Xuj&+at4' + 'Uut7Wo@D`_F1P*@6aWAK2mk;8ApncQ0j^L3008X?000vJ003rTb98WQZF4Vjb#8QNZDlTSd5u<0Z`(K!z3W#Dbh3fehva6!' + '#V&%h4f=tg@MRGcDlLs{qBQA}qa)IUnr0>Z{EC_AzhZ`-OEnNTGAC*d_wEwN+B$btv6shO>&D+8{NuvX9Y>4XGL+p' + '-5E<#ui=Am^zh}YgT|l(Z3ya(&})RXGAp4HjzjX+ZU(nPDUJcO5d_ad6Nn;5*;&&{j%{`XQp2;P4b0n4*Ylsrq)H|~LFk6*)+nEl' + 'u~1s}#wTeySL9%@67?^5-k|mtJ_o`V0(_C=W<^<+MbTQbg{o>h>MT|j$dDIq-AS&lv_NuuVc5+zJ2rN' + 'vFQgUim_x9W+^&FJ%o*7yWpP$$f^OWX64H})`_H_?$FEusuwbFP$-_bS(hK!XJ^=O4?J&Sf}2_5E^{+;>5S&2@<;`|JXUOl{AJlM' + 'OAxuI1|^RbcO3c-X0*j&-$HBxo1R}x6t_SXSwAC-Lsk)7lHl+35Widv4*@E>(JR55csATr6=Px9NH9>!`Ss6cN9LRa5nlN!?VGMI$Vk!mMD+ARI}V2vvU?P4UXMg9;h8NnD;4N7Rk@S' + 'Mz}vpK0N@w?y6%Bi&M;w$($GMn9GF%jHnDO-^PUO{(!NUsG4}|O3j8xM{g+D*%_T4=lteky{%v;hv+QcpXzlAZA|EL(N0a(MLHL1' + 'b^?oEt*T-p{*SVN$8z)p`geSIefpizlg3Z{|4>T-0u%rg000080000X02IsErJMo)06_-;02BZK00000000000HlEc0001HVRLkF' + 'Y;AKdUtei%X>?y-E^v8JO928D02BZK00;m803iU1iPYqL0ssKh2mk;c00000000000001_fzAQ|0A^uxbZ~5Kb1z|ZbY*UIX>V>X' + 'Utei%X>?y-E^v8JO928D02BZK00;m803iTh#1V>XUtx80' + 'Z!U0oP)h*<6aW+e000O8001EXQV4vR<01e67mNS^8vpy5Wta#60GAs803rYY00000000000HlF;F#rH&VRLkFY;AKdVRUq5ZggpHZZBVJWpZw1Y+r0|V{0yOc~DCM' + '0u%rg000080000X0Bi+0fGiRK08l~z03ZMW00000000000HlFWIsgD>VRLkFY;AKdVRUq5ZggpHZZBzWbY*g8VPj=;E^v8JO928D' + '02BZK00;m803iSy0TI6V0RRBu1poja00000000000001_fw@co0A^uxbZ~5Kb1!9WVr67xX>Mn8FJE72ZfSI1UoLQYP)h*<6aW+e' + '000O8001EX3YkqjVh{iTFFybP9smFU0000000000q=D{E003rTb98WQZF4VWZDM6)WNB_^b1!0HbYo~`b1rasP)h*<6aW+e000O8' + '001EXpl!(>KokG~&QbsX9{>OV0000000000q=AfI003rTb98WQZF4VWZDM6)WNB_^b1!LRWo~q7ba^gtc~DCM0u%rg000080000X' + '0PUL2=HvXLB!UZg6#Ub1rasP)h*<6aW+e000O8001EX1#rwj' + 'p$Gr~dLsY;9RL6T0000000000q=6`Z003rTb98WQZF4VWZDM6)WNB_^b1!dobZ~WaE^v8JO928D02BZK00;m803iTuVy-BN3jhF=' + 'CIA2)00000000000001_fdq&E0A^uxbZ~5Kb1!9WVr67xX>Mn8FK};fY-w(1E^v8JO928D02BZK00;m803iT>b`#|A5C8xGNB{sG' + '00000000000001_fyk2p0A^uxbZ~5Kb1!9WVr67xX>Mn8FLHHmZe?;VaCuNm0Rj{N6aWAK2mk;8Apjyp__&uh004Uf0RSEV00000' + '00000005+c^P~U(W?^%5aBOXJFJ*0FWn^S&ZfA2Zb98TVVP|D7aCuNm0Rj{N6aWAK2mk;8Aplw52h=eK002K3000~S0000000000' + '005+c#@qk^W?^%5aBOXJFJ*0FWn^S&ZfA2Zba`-Pb1rasP)h*<6aW+e000O8001EXUCm{gi&FpqAVvWI6aWAK0000000000q=7Ny' + '003rTb98WQZF4VeZ)9a`b1rmmZER3W0Rj{N6aWAK2mk;8Apn+gw*2q_002+{000{R0000000000005+c?l%DdW?^%5aBOXJFKusR' + 'Wo&aVUtei%X>?y-E^v8JO928D02BZK00;m803iUUb9%Sz1pokD5dZ)l00000000000001_ffqRe0A^uxbZ~5Kb1!XgWMyn~FJEPI' + 'ZC`S4bYXINE^v8JO928D02BZK00;m803iT{<4%QJ5dZ*V10A^uxbZ~5Kb1!XgWMyn~FJo+Bb8~5C' + 'X=7n@X>V>{aB^>AWiD`eP)h*<6aW+e000O8001EX000000ssI200000C;$Ke0000000000q=Da10RU!Ub98WQZF4VeZ)9a`b1!9c' + 'ZC`L~b#riRb#pIYUukY>bYEXCaCuNm0Rj{N6aWAK2mk;8Apo~J-n76!005Oh0RS}s0000000000005+cCs6?aW?^%5aBOXJFKusR' + 'Wo&aVWpiy`aBOvRaBOvRFKusRWo&6~XJ2J=ZC`L~b#riRb#pFoc~DCM0u%rg000080000X0N7hSAR!F^0177n0672v0000000000' + '0HlFBkpTc^VRLkFY;AKdZEs{{Y;!MVb8TO6Y;|*RY;|)lZEs{{Y-w(1UuAP`UvO-7b8u{Rb6;~|WiD`eP)h*<6aW+e000O8001EX' + 'f;=~{O#%P_?gsz>Bme*a0000000000q=B-Y0RU!Ub98WQZF4VeZ)9a`b1!9cZDwz5WHK*bUukY>bYEXCaCuNm0Rj{N6aWAK2mk;8' + 'Apl;j%uQkf0043Z001Qb0000000000005+cL7@QvW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJW|aWo~q7Z*DGdc~DCM0u%rg00008' + '0000X06>^jw%-i^0CO+^05Sjo00000000000HlHDqX7VBVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoV{dL|X=inEVRUJ4ZeL|{ZDwz5' + 'WHK&rc~DCM0u%rg000080000X0MFBfH4Fv-067r=03`qb00000000000HlExvH<{QVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWo=?*' + 'WMpY>XD)DgP)h*<6aW+e000O8001EXxwz_33l#tW!(adaEdT%j0000000000q=96(0RU!Ub98WQZF4VeZ)9a`b1!9cZDwz5WHK*h' + 'b8TjCY-BQDVPb97;DbaO6nc~DCM0u%rg00008' + '0000X0M6F8GVcTc0AULN05bpp00000000000HlGu?g0R1VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUt@1>b97;DbaP*2' + 'b8Ry&aCuNm0Rj{N6aWAK2mk;8ApnC!Nv+TZ007Jy001%o0000000000005+c{PO_-W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IW' + 'W^Zg{GGA$KaCLNFVs&Y3WMy(LaCuNm0Rj{N6aWAK2mk;8ApnPqhkD}*007V^001li0000000000005+c9Qy$PW?^%5aBOXJFKusR' + 'Wo&aVWpiz2Z){{TFJ*IWW^Zg{GGA?FbaH89b1rasP)h*<6aW+e000O8001EXK%n`z$P)knK|cThDF6Tf0000000000q=88W0sv-V' + 'b98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZE163E^v8JO928D02BZK00;m803iSw1(UyO4*&q-KmY(T00000000000001_' + 'fmj*>0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0IwZDwz5WHMiEZDVO>UvOb^b7^j8E^v8JO928D02BZK00;m803iT%rREZVBme-N' + 'p8xOLI?l=v>pHeD*ylh' + '0000000000q=Ct70sv-Vb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQDZ*_EVb#yLpc~DCM0u%rg000080000X00@9' + '0MaA?051Rl00000000000HlFbbpilpVRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoWpiz2Z){{TUvOb*a%E&+ZF6BRaCuNm0Rj{N6aWAK' + '2mk;8ApjNii`WAN001}-001li0000000000005+cAb$b?W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0Ea&u{JXD)Dg' + 'P)h*<6aW+e000O8001EXBJZKQ5C#AM84~~iJOBUy0000000000q=9XO0sv-Vb98WQZF4VeZ)9a`b1!9cZDwz5WHK*hb8TjCY-BQD' + 'aB^j2X=8L{WM5%yX=iR_WM5@+a&K}jaCuNm0Rj{N6aWAK2mk;8AplR*W&)fe001DL001%o0000000000005+c*NOrFW?^%5aBOXJ' + 'FKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VWpH6~WnXD-aCLMpaCuNm0Rj{N6aWAK2mk;8Apj)8XR|2{005dZ001rk0000000000' + '005+c!>$4VW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VZ)0V1b8m7kaCuNm0Rj{N6aWAK2mk;8ApooywI265001}m' + '001%o0000000000005+cIJ^P?W?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VZ**m8ZeL?)VQFqIaCuNm0Rj{N6aWAK' + '2mk;8Apq>vC8+E)0014%001-q0000000000005+cfA#_ZW?^%5aBOXJFKusRWo&aVWpiz2Z){{TFJ*IWW^Zg{GGB0VZ**m8ZeL?>' + 'ZE$R5crI{xP)h*<6aW+e000O8001EX3tfWub_xIhoF)JOHUIzs0000000000q=CIF0{~`Wb98WQZF4VeZ)9a`b1!9cZDwz5WHK*h' + 'b8TjCY-BQDaB^>SWod3-b98cbV{~{0RSxk0000000000005+c-eCg(W?^%5aBOXJFKusR' + 'Wo&aVWpiz2Z){{TFKusRWo&6~XJ2J=ZDwz5WHK&rc~DCM0u%rg000080000X0Q|IC`9cW*0M8@<06YKy00000000000HlGGu>$~R' + 'VRLkFY;AKdZEs{{Y;!MVb8TjCY-BPoZEs{{Y-w(1UuAP`W^Zg{GGAkCVRLh7W@%$#bZKvHE^v8JO928D02BZK00;m803iUAN*Q-k' + 'Yybes=m7vU00000000000001_fiSxR0A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0g!WMyn=Zf9R*b8TjCY-BQDV{dJ3Z*DGdc~DCM' + '0u%rg000080000X0AlbNG)*Z0042i!06G8w00000000000HlG{CjQ3L>HVRLkFY;AKdZEs{{' + 'Y;!MVb8TjCY-BPoaB^>SWod3-a%E;^a%FB~WnX7yZ*66Ca(OOlb8l`?O928D02BZK00;m803iTLv<5yw2><|{9{>O;0000000000' + '0001_fxu@30A^uxbZ~5Kb1!XgWMyn~FJ*IWW^Zg{GB0p)Z**m8ZeMkDX>4;YaCuNm0Rj{N6aWAK2mk;8Apok_lNWX>DaLaCuNm1qJ{B002M$KmgSh001r01ONa4' ) diff --git a/modeling_fastplms.py b/modeling_fastplms.py index bae1b1b2e42a5f467703351469460b5d03eb05f9..32b625d483fb85592b4a5e02f2570b425842596e 100644 --- a/modeling_fastplms.py +++ b/modeling_fastplms.py @@ -13,7 +13,7 @@ from zipfile import ZIP_DEFLATED, ZipFile from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH -if RUNTIME_HASH != "88b1209bd2c3097e992a4ee20994a608525d77aaf584e34a5c90829f81764d4d": +if RUNTIME_HASH != "ab074f9ea4b20dcfaf9e2ca9082480ee0f9dba02c92d14da5dd3879aa86bf7ed": raise RuntimeError("FastPLMs runtime identity differs from the bridge.") _RUNTIME_TEMPORARIES = []