From 672a7bdb958b6944d6b23e99cd49297c37e8bfb1 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:06:16 +0000 Subject: [PATCH] Compiled vector_search/cudabefaster --- .../cudabefaster/benchmarker_outbound.rs | 285 ++++++++++++++++++ .../vector_search/cudabefaster/commercial.rs | 285 ++++++++++++++++++ .../src/vector_search/cudabefaster/inbound.rs | 285 ++++++++++++++++++ .../cudabefaster/innovator_outbound.rs | 285 ++++++++++++++++++ .../src/vector_search/cudabefaster/mod.rs | 4 + .../vector_search/cudabefaster/open_data.rs | 285 ++++++++++++++++++ tig-algorithms/src/vector_search/mod.rs | 3 +- tig-algorithms/src/vector_search/template.rs | 26 +- .../wasm/vector_search/cudabefaster.wasm | Bin 0 -> 125150 bytes 9 files changed, 1433 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vector_search/cudabefaster/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vector_search/cudabefaster/commercial.rs create mode 100644 tig-algorithms/src/vector_search/cudabefaster/inbound.rs create mode 100644 tig-algorithms/src/vector_search/cudabefaster/innovator_outbound.rs create mode 100644 tig-algorithms/src/vector_search/cudabefaster/mod.rs create mode 100644 tig-algorithms/src/vector_search/cudabefaster/open_data.rs create mode 100644 tig-algorithms/wasm/vector_search/cudabefaster.wasm diff --git a/tig-algorithms/src/vector_search/cudabefaster/benchmarker_outbound.rs b/tig-algorithms/src/vector_search/cudabefaster/benchmarker_outbound.rs new file mode 100644 index 00000000..c44fb69b --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/benchmarker_outbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use tig_challenges::vector_search::*; + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32, +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + // Pre-compute vector norms + let vector_norms_sq: Vec = vector_database + .iter() + .map(|vector| vector.iter().map(|&val| val * val).sum()) + .collect(); + + let sum_norms_sq: f32 = vector_norms_sq.iter().sum(); + let std_dev: f32 = 2.0 * (sum_norms_sq / vector_norms_sq.len() as f32).sqrt(); + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if (vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs() > std_dev { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + + let distance = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use std::{collections::HashMap, sync::Arc}; + use cudarc::{ + driver::{CudaDevice, DriverError, LaunchConfig, CudaFunction, CudaSlice, LaunchAsync}, + }; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + extern "C" __global__ void calculate_distances_batch( + const float* query_vectors, + const float* vector_database, + int num_vectors, + int vector_size, + int num_queries, + float* distances, + float* norms_query, + float* norms_db + ) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int qid = blockIdx.y; + + if (idx < num_vectors && qid < num_queries) { + extern __shared__ float s_query[]; + + // Compute L2 norm for query vector + if (threadIdx.x < vector_size) { + s_query[threadIdx.x] = query_vectors[qid * vector_size + threadIdx.x]; + } + __syncthreads(); + + float query_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + query_norm_sq += s_query[i] * s_query[i]; + } + norms_query[qid] = sqrtf(query_norm_sq); + + // Compute L2 norm for database vector + float db_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + db_norm_sq += db_val * db_val; + } + norms_db[idx] = sqrtf(db_norm_sq); + + // Compute distance using precomputed norms + float distance = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + distance += (s_query[i] - db_val) * (s_query[i] - db_val); + } + distances[qid * num_vectors + idx] = sqrtf(distance); + } + } + "#, + funcs: &["calculate_distances_batch"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> Result, DriverError> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let num_vectors = vector_database.len(); + let vector_size = vector_database[0].len(); + let num_queries = query_vectors.len(); + + // Flatten data + let vector_db_flat: Vec = vector_database.iter().flatten().cloned().collect(); + let query_vectors_flat: Vec = query_vectors.iter().flatten().cloned().collect(); + + // Allocate device memory + let vector_db_dev = dev.htod_sync_copy(&vector_db_flat)?; + let query_dev = dev.htod_sync_copy(&query_vectors_flat)?; + let norms_query_dev: CudaSlice = unsafe { dev.alloc(num_queries)? }; + let norms_db_dev: CudaSlice = unsafe { dev.alloc(num_vectors)? }; + + // Allocate distances buffer on the device + let distances_dev: CudaSlice = unsafe { dev.alloc::(num_vectors * num_queries)? }; + + // Create a CUDA stream + let stream = dev.fork_default_stream()?; + + // Configure kernel launch parameters + let block_dim = (512, 1, 1); // Maximize block size + let grid_dim = ( + ((num_vectors + block_dim.0 as usize - 1) / block_dim.0 as usize) as u32, + num_queries as u32, + 1, + ); + let shared_mem_bytes = vector_size as u32 * std::mem::size_of::() as u32; + + let func = funcs.get_mut("calculate_distances_batch").unwrap().clone(); + + // Launch the kernel + unsafe { + func.launch_on_stream( + &stream, + LaunchConfig { + block_dim, + grid_dim, + shared_mem_bytes, + }, + ( + &query_dev, + &vector_db_dev, + num_vectors as i32, + vector_size as i32, + num_queries as i32, + &distances_dev, + &norms_query_dev, + &norms_db_dev, + ), + )?; + } + + // Wait for stream to complete + dev.wait_for(&stream)?; + + // Allocate buffer for results on the host and copy data back + let mut distances = vec![f32::MAX; num_vectors * num_queries]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances)?; + + // Process results + let mut indexes = Vec::with_capacity(num_queries); + for q in 0..num_queries { + if let Some((index, _)) = distances[(q * num_vectors)..((q + 1) * num_vectors)] + .iter() + .enumerate() + .filter(|&(_, &d)| d <= max_distance) + .min_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) + } +} + +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tig-algorithms/src/vector_search/cudabefaster/commercial.rs b/tig-algorithms/src/vector_search/cudabefaster/commercial.rs new file mode 100644 index 00000000..b2bdd081 --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/commercial.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use tig_challenges::vector_search::*; + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32, +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + // Pre-compute vector norms + let vector_norms_sq: Vec = vector_database + .iter() + .map(|vector| vector.iter().map(|&val| val * val).sum()) + .collect(); + + let sum_norms_sq: f32 = vector_norms_sq.iter().sum(); + let std_dev: f32 = 2.0 * (sum_norms_sq / vector_norms_sq.len() as f32).sqrt(); + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if (vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs() > std_dev { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + + let distance = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use std::{collections::HashMap, sync::Arc}; + use cudarc::{ + driver::{CudaDevice, DriverError, LaunchConfig, CudaFunction, CudaSlice, LaunchAsync}, + }; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + extern "C" __global__ void calculate_distances_batch( + const float* query_vectors, + const float* vector_database, + int num_vectors, + int vector_size, + int num_queries, + float* distances, + float* norms_query, + float* norms_db + ) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int qid = blockIdx.y; + + if (idx < num_vectors && qid < num_queries) { + extern __shared__ float s_query[]; + + // Compute L2 norm for query vector + if (threadIdx.x < vector_size) { + s_query[threadIdx.x] = query_vectors[qid * vector_size + threadIdx.x]; + } + __syncthreads(); + + float query_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + query_norm_sq += s_query[i] * s_query[i]; + } + norms_query[qid] = sqrtf(query_norm_sq); + + // Compute L2 norm for database vector + float db_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + db_norm_sq += db_val * db_val; + } + norms_db[idx] = sqrtf(db_norm_sq); + + // Compute distance using precomputed norms + float distance = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + distance += (s_query[i] - db_val) * (s_query[i] - db_val); + } + distances[qid * num_vectors + idx] = sqrtf(distance); + } + } + "#, + funcs: &["calculate_distances_batch"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> Result, DriverError> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let num_vectors = vector_database.len(); + let vector_size = vector_database[0].len(); + let num_queries = query_vectors.len(); + + // Flatten data + let vector_db_flat: Vec = vector_database.iter().flatten().cloned().collect(); + let query_vectors_flat: Vec = query_vectors.iter().flatten().cloned().collect(); + + // Allocate device memory + let vector_db_dev = dev.htod_sync_copy(&vector_db_flat)?; + let query_dev = dev.htod_sync_copy(&query_vectors_flat)?; + let norms_query_dev: CudaSlice = unsafe { dev.alloc(num_queries)? }; + let norms_db_dev: CudaSlice = unsafe { dev.alloc(num_vectors)? }; + + // Allocate distances buffer on the device + let distances_dev: CudaSlice = unsafe { dev.alloc::(num_vectors * num_queries)? }; + + // Create a CUDA stream + let stream = dev.fork_default_stream()?; + + // Configure kernel launch parameters + let block_dim = (512, 1, 1); // Maximize block size + let grid_dim = ( + ((num_vectors + block_dim.0 as usize - 1) / block_dim.0 as usize) as u32, + num_queries as u32, + 1, + ); + let shared_mem_bytes = vector_size as u32 * std::mem::size_of::() as u32; + + let func = funcs.get_mut("calculate_distances_batch").unwrap().clone(); + + // Launch the kernel + unsafe { + func.launch_on_stream( + &stream, + LaunchConfig { + block_dim, + grid_dim, + shared_mem_bytes, + }, + ( + &query_dev, + &vector_db_dev, + num_vectors as i32, + vector_size as i32, + num_queries as i32, + &distances_dev, + &norms_query_dev, + &norms_db_dev, + ), + )?; + } + + // Wait for stream to complete + dev.wait_for(&stream)?; + + // Allocate buffer for results on the host and copy data back + let mut distances = vec![f32::MAX; num_vectors * num_queries]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances)?; + + // Process results + let mut indexes = Vec::with_capacity(num_queries); + for q in 0..num_queries { + if let Some((index, _)) = distances[(q * num_vectors)..((q + 1) * num_vectors)] + .iter() + .enumerate() + .filter(|&(_, &d)| d <= max_distance) + .min_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) + } +} + +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tig-algorithms/src/vector_search/cudabefaster/inbound.rs b/tig-algorithms/src/vector_search/cudabefaster/inbound.rs new file mode 100644 index 00000000..b6a9f59f --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/inbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use tig_challenges::vector_search::*; + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32, +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + // Pre-compute vector norms + let vector_norms_sq: Vec = vector_database + .iter() + .map(|vector| vector.iter().map(|&val| val * val).sum()) + .collect(); + + let sum_norms_sq: f32 = vector_norms_sq.iter().sum(); + let std_dev: f32 = 2.0 * (sum_norms_sq / vector_norms_sq.len() as f32).sqrt(); + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if (vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs() > std_dev { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + + let distance = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use std::{collections::HashMap, sync::Arc}; + use cudarc::{ + driver::{CudaDevice, DriverError, LaunchConfig, CudaFunction, CudaSlice, LaunchAsync}, + }; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + extern "C" __global__ void calculate_distances_batch( + const float* query_vectors, + const float* vector_database, + int num_vectors, + int vector_size, + int num_queries, + float* distances, + float* norms_query, + float* norms_db + ) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int qid = blockIdx.y; + + if (idx < num_vectors && qid < num_queries) { + extern __shared__ float s_query[]; + + // Compute L2 norm for query vector + if (threadIdx.x < vector_size) { + s_query[threadIdx.x] = query_vectors[qid * vector_size + threadIdx.x]; + } + __syncthreads(); + + float query_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + query_norm_sq += s_query[i] * s_query[i]; + } + norms_query[qid] = sqrtf(query_norm_sq); + + // Compute L2 norm for database vector + float db_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + db_norm_sq += db_val * db_val; + } + norms_db[idx] = sqrtf(db_norm_sq); + + // Compute distance using precomputed norms + float distance = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + distance += (s_query[i] - db_val) * (s_query[i] - db_val); + } + distances[qid * num_vectors + idx] = sqrtf(distance); + } + } + "#, + funcs: &["calculate_distances_batch"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> Result, DriverError> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let num_vectors = vector_database.len(); + let vector_size = vector_database[0].len(); + let num_queries = query_vectors.len(); + + // Flatten data + let vector_db_flat: Vec = vector_database.iter().flatten().cloned().collect(); + let query_vectors_flat: Vec = query_vectors.iter().flatten().cloned().collect(); + + // Allocate device memory + let vector_db_dev = dev.htod_sync_copy(&vector_db_flat)?; + let query_dev = dev.htod_sync_copy(&query_vectors_flat)?; + let norms_query_dev: CudaSlice = unsafe { dev.alloc(num_queries)? }; + let norms_db_dev: CudaSlice = unsafe { dev.alloc(num_vectors)? }; + + // Allocate distances buffer on the device + let distances_dev: CudaSlice = unsafe { dev.alloc::(num_vectors * num_queries)? }; + + // Create a CUDA stream + let stream = dev.fork_default_stream()?; + + // Configure kernel launch parameters + let block_dim = (512, 1, 1); // Maximize block size + let grid_dim = ( + ((num_vectors + block_dim.0 as usize - 1) / block_dim.0 as usize) as u32, + num_queries as u32, + 1, + ); + let shared_mem_bytes = vector_size as u32 * std::mem::size_of::() as u32; + + let func = funcs.get_mut("calculate_distances_batch").unwrap().clone(); + + // Launch the kernel + unsafe { + func.launch_on_stream( + &stream, + LaunchConfig { + block_dim, + grid_dim, + shared_mem_bytes, + }, + ( + &query_dev, + &vector_db_dev, + num_vectors as i32, + vector_size as i32, + num_queries as i32, + &distances_dev, + &norms_query_dev, + &norms_db_dev, + ), + )?; + } + + // Wait for stream to complete + dev.wait_for(&stream)?; + + // Allocate buffer for results on the host and copy data back + let mut distances = vec![f32::MAX; num_vectors * num_queries]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances)?; + + // Process results + let mut indexes = Vec::with_capacity(num_queries); + for q in 0..num_queries { + if let Some((index, _)) = distances[(q * num_vectors)..((q + 1) * num_vectors)] + .iter() + .enumerate() + .filter(|&(_, &d)| d <= max_distance) + .min_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) + } +} + +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tig-algorithms/src/vector_search/cudabefaster/innovator_outbound.rs b/tig-algorithms/src/vector_search/cudabefaster/innovator_outbound.rs new file mode 100644 index 00000000..6e38df87 --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/innovator_outbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use tig_challenges::vector_search::*; + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32, +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + // Pre-compute vector norms + let vector_norms_sq: Vec = vector_database + .iter() + .map(|vector| vector.iter().map(|&val| val * val).sum()) + .collect(); + + let sum_norms_sq: f32 = vector_norms_sq.iter().sum(); + let std_dev: f32 = 2.0 * (sum_norms_sq / vector_norms_sq.len() as f32).sqrt(); + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if (vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs() > std_dev { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + + let distance = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use std::{collections::HashMap, sync::Arc}; + use cudarc::{ + driver::{CudaDevice, DriverError, LaunchConfig, CudaFunction, CudaSlice, LaunchAsync}, + }; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + extern "C" __global__ void calculate_distances_batch( + const float* query_vectors, + const float* vector_database, + int num_vectors, + int vector_size, + int num_queries, + float* distances, + float* norms_query, + float* norms_db + ) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int qid = blockIdx.y; + + if (idx < num_vectors && qid < num_queries) { + extern __shared__ float s_query[]; + + // Compute L2 norm for query vector + if (threadIdx.x < vector_size) { + s_query[threadIdx.x] = query_vectors[qid * vector_size + threadIdx.x]; + } + __syncthreads(); + + float query_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + query_norm_sq += s_query[i] * s_query[i]; + } + norms_query[qid] = sqrtf(query_norm_sq); + + // Compute L2 norm for database vector + float db_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + db_norm_sq += db_val * db_val; + } + norms_db[idx] = sqrtf(db_norm_sq); + + // Compute distance using precomputed norms + float distance = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + distance += (s_query[i] - db_val) * (s_query[i] - db_val); + } + distances[qid * num_vectors + idx] = sqrtf(distance); + } + } + "#, + funcs: &["calculate_distances_batch"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> Result, DriverError> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let num_vectors = vector_database.len(); + let vector_size = vector_database[0].len(); + let num_queries = query_vectors.len(); + + // Flatten data + let vector_db_flat: Vec = vector_database.iter().flatten().cloned().collect(); + let query_vectors_flat: Vec = query_vectors.iter().flatten().cloned().collect(); + + // Allocate device memory + let vector_db_dev = dev.htod_sync_copy(&vector_db_flat)?; + let query_dev = dev.htod_sync_copy(&query_vectors_flat)?; + let norms_query_dev: CudaSlice = unsafe { dev.alloc(num_queries)? }; + let norms_db_dev: CudaSlice = unsafe { dev.alloc(num_vectors)? }; + + // Allocate distances buffer on the device + let distances_dev: CudaSlice = unsafe { dev.alloc::(num_vectors * num_queries)? }; + + // Create a CUDA stream + let stream = dev.fork_default_stream()?; + + // Configure kernel launch parameters + let block_dim = (512, 1, 1); // Maximize block size + let grid_dim = ( + ((num_vectors + block_dim.0 as usize - 1) / block_dim.0 as usize) as u32, + num_queries as u32, + 1, + ); + let shared_mem_bytes = vector_size as u32 * std::mem::size_of::() as u32; + + let func = funcs.get_mut("calculate_distances_batch").unwrap().clone(); + + // Launch the kernel + unsafe { + func.launch_on_stream( + &stream, + LaunchConfig { + block_dim, + grid_dim, + shared_mem_bytes, + }, + ( + &query_dev, + &vector_db_dev, + num_vectors as i32, + vector_size as i32, + num_queries as i32, + &distances_dev, + &norms_query_dev, + &norms_db_dev, + ), + )?; + } + + // Wait for stream to complete + dev.wait_for(&stream)?; + + // Allocate buffer for results on the host and copy data back + let mut distances = vec![f32::MAX; num_vectors * num_queries]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances)?; + + // Process results + let mut indexes = Vec::with_capacity(num_queries); + for q in 0..num_queries { + if let Some((index, _)) = distances[(q * num_vectors)..((q + 1) * num_vectors)] + .iter() + .enumerate() + .filter(|&(_, &d)| d <= max_distance) + .min_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) + } +} + +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tig-algorithms/src/vector_search/cudabefaster/mod.rs b/tig-algorithms/src/vector_search/cudabefaster/mod.rs new file mode 100644 index 00000000..fcec9672 --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vector_search/cudabefaster/open_data.rs b/tig-algorithms/src/vector_search/cudabefaster/open_data.rs new file mode 100644 index 00000000..8c42c39d --- /dev/null +++ b/tig-algorithms/src/vector_search/cudabefaster/open_data.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use tig_challenges::vector_search::*; + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32, +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + // Pre-compute vector norms + let vector_norms_sq: Vec = vector_database + .iter() + .map(|vector| vector.iter().map(|&val| val * val).sum()) + .collect(); + + let sum_norms_sq: f32 = vector_norms_sq.iter().sum(); + let std_dev: f32 = 2.0 * (sum_norms_sq / vector_norms_sq.len() as f32).sqrt(); + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if (vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs() > std_dev { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + + let distance = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use std::{collections::HashMap, sync::Arc}; + use cudarc::{ + driver::{CudaDevice, DriverError, LaunchConfig, CudaFunction, CudaSlice, LaunchAsync}, + }; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + extern "C" __global__ void calculate_distances_batch( + const float* query_vectors, + const float* vector_database, + int num_vectors, + int vector_size, + int num_queries, + float* distances, + float* norms_query, + float* norms_db + ) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int qid = blockIdx.y; + + if (idx < num_vectors && qid < num_queries) { + extern __shared__ float s_query[]; + + // Compute L2 norm for query vector + if (threadIdx.x < vector_size) { + s_query[threadIdx.x] = query_vectors[qid * vector_size + threadIdx.x]; + } + __syncthreads(); + + float query_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + query_norm_sq += s_query[i] * s_query[i]; + } + norms_query[qid] = sqrtf(query_norm_sq); + + // Compute L2 norm for database vector + float db_norm_sq = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + db_norm_sq += db_val * db_val; + } + norms_db[idx] = sqrtf(db_norm_sq); + + // Compute distance using precomputed norms + float distance = 0.0f; + for (int i = 0; i < vector_size; ++i) { + float db_val = vector_database[idx * vector_size + i]; + distance += (s_query[i] - db_val) * (s_query[i] - db_val); + } + distances[qid * num_vectors + idx] = sqrtf(distance); + } + } + "#, + funcs: &["calculate_distances_batch"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> Result, DriverError> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let num_vectors = vector_database.len(); + let vector_size = vector_database[0].len(); + let num_queries = query_vectors.len(); + + // Flatten data + let vector_db_flat: Vec = vector_database.iter().flatten().cloned().collect(); + let query_vectors_flat: Vec = query_vectors.iter().flatten().cloned().collect(); + + // Allocate device memory + let vector_db_dev = dev.htod_sync_copy(&vector_db_flat)?; + let query_dev = dev.htod_sync_copy(&query_vectors_flat)?; + let norms_query_dev: CudaSlice = unsafe { dev.alloc(num_queries)? }; + let norms_db_dev: CudaSlice = unsafe { dev.alloc(num_vectors)? }; + + // Allocate distances buffer on the device + let distances_dev: CudaSlice = unsafe { dev.alloc::(num_vectors * num_queries)? }; + + // Create a CUDA stream + let stream = dev.fork_default_stream()?; + + // Configure kernel launch parameters + let block_dim = (512, 1, 1); // Maximize block size + let grid_dim = ( + ((num_vectors + block_dim.0 as usize - 1) / block_dim.0 as usize) as u32, + num_queries as u32, + 1, + ); + let shared_mem_bytes = vector_size as u32 * std::mem::size_of::() as u32; + + let func = funcs.get_mut("calculate_distances_batch").unwrap().clone(); + + // Launch the kernel + unsafe { + func.launch_on_stream( + &stream, + LaunchConfig { + block_dim, + grid_dim, + shared_mem_bytes, + }, + ( + &query_dev, + &vector_db_dev, + num_vectors as i32, + vector_size as i32, + num_queries as i32, + &distances_dev, + &norms_query_dev, + &norms_db_dev, + ), + )?; + } + + // Wait for stream to complete + dev.wait_for(&stream)?; + + // Allocate buffer for results on the host and copy data back + let mut distances = vec![f32::MAX; num_vectors * num_queries]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances)?; + + // Process results + let mut indexes = Vec::with_capacity(num_queries); + for q in 0..num_queries { + if let Some((index, _)) = distances[(q * num_vectors)..((q + 1) * num_vectors)] + .iter() + .enumerate() + .filter(|&(_, &d)| d <= max_distance) + .min_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) + } +} + +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tig-algorithms/src/vector_search/mod.rs b/tig-algorithms/src/vector_search/mod.rs index ae3472ac..68401c32 100644 --- a/tig-algorithms/src/vector_search/mod.rs +++ b/tig-algorithms/src/vector_search/mod.rs @@ -40,7 +40,8 @@ // c004_a021 -// c004_a022 +pub mod cudabefaster; +pub use cudabefaster as c004_a022; // c004_a023 diff --git a/tig-algorithms/src/vector_search/template.rs b/tig-algorithms/src/vector_search/template.rs index eddf8a0e..0f5fa1e2 100644 --- a/tig-algorithms/src/vector_search/template.rs +++ b/tig-algorithms/src/vector_search/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vector_search::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vector_search/cudabefaster.wasm b/tig-algorithms/wasm/vector_search/cudabefaster.wasm new file mode 100644 index 0000000000000000000000000000000000000000..270acaa4c839c5700ca8ed0bc40e5689c0853ba7 GIT binary patch literal 125150 zcmeFa3!Gg?edl>z_kC~oZK)--)v{gpSb+RctSn#&;|!|a_#tGl6TB>wncbMrWOkg} z4spwx4hhC;+p-bHfI>`SGdu)wka;MW!DJj_pbZ9`#1PL64CE6};sh_tFf8K)C*%1H zLHqsv>zs3Mt6P3B$?Sf1*KOTXr%s)!fBoxU|5yF1PW0Mqe<6;dC_WgkzcM{=;6Qxf z%EW%-1CdgZ-FySR#%ZviAE@)TC(fI!}P%Q z*QdaVt*jzcfi}Nm7r~H0GzBWLYE2I!ULIk`u*Q zE#lYS;F87JB=4rwp%>y>t(LVKq$6@_S(;FW?Bc&RRZu+zv$W4Oj_bgXMqI#8RG(T` zr~dew_=+oPv^7<4feXL1v>bIBgS7q&|MVArao;Z|QTFq%`}zIR9dYNMzHa}%U;f!^ ze(~pDw?DcwZvCS;@^R-?jeMeh&B9CeT|51}ey_D|dhUO`^c6q->bY0$e96QwHun7U z{cnnAcE04YQ_s8ne?4Vt^UrR1!OTB++3uhCl?yMr_*Fl3#sBm_zxJQ}+)w<%%xnM2 zfvbLgIDh>w|I+K?EyPt@Y1mS$1M#oLl7*N;-oJev3EE3(}#c9GBe zv1JZaO!vd{lBRMp7Y+UQzVq>K*3XNg?nJ0$K#}cHbfI1zuP!UAtBqF|DgmaCy=^(# z7DZG9aM{p*f_thtpL?}gQ=4TI&j4Zp*prWc?IQxr-Zz-Ly#mBqK$6hHfXz=NaT0a> zv_L;IZRV}H!(WN6paZQzn>N$kF7%`BL7Vd*b8i^{#c@QQ$v*JYDdQ zN3Y8>AMf`^-u*x{JU5D7O7oFV=3VCBec)=B3`4mSO2kx3+pW3d4}AUHR}UiB9<*H4 zi#h;2f~w;>b1!>S-n!Yf-nbIP?*E-2b~?#!7o82-rrgwPLE6-(f$n(uUv$%)=#KxM zIv)LWFYdUSsDhtn9v4Jbb6^0fQ071-bZsCTCQ=n{5-h`2t{Hs?ODl$H*`PD>mqBtrg8ccksdv(QNHGPTUtA1eM{OKMuoxThNstdUyechBc_*SrW)i~J9w0x^+)DkPaKAnn)4-!K|10mYbD5n$`fdI%cabW}iLg2s$_}i_&x!AR#)8ms1 zc|3c$Ne-XvaqYS2r}qI#yW}9vgDN)*<4taXL$>GQn+B7-_{rwHfB2X5B=*rIfRXw; zn8M57Uj~K#<`2B;YCpY5>cGYG%ur@}!)N>9K4+#;E)J3$%)9+xk9qA7uRUb1*~5-2 z@}G)_JUx;O^-9;JXQqde>+&9Xhb_-_UGLS$t^-L8|M5TmreI#hIK`cOQuuH6w2=-HX+Q}KXd_sL}O{lE^C16^KKKWbkS|WN z3i;cFW#K;W8}@sl<(Z*S8q!TQw!^@)wq`BD;Hb8A)W6|srk6i3h*KsR{J|vN zm+$<o?&Y-ojhSqouC$!x~>;!$OK4e0E`#ldtg(ehxrq_44FBH4h+{+Ff^(mbZ(E#$E z9_#}m5(D+oAW9CP^E@6c%?oI#ol1TbrAhspo-L5_o(aIuM0W96p9KCTp#&IHVtHz6%B;^Q-p&ySIE?hw5Ssw(XH9LstAyWXF zXoXg+BNML%II&KuReP>Y#rC9YbxXbK3|cf!!`-7uvw~K}Tk0GwATL|$!cb|sM}w^a zV7ul#5DQkd<|dEQhXdE{0|)J&TJRa>LkrG(`K&`k34UL@NaG!?0ZfSMXT&lHri|J~FhYB>rMk~UEH$v&jN z%_nJ@VDOS&>XI&6S*v;(nbuDkg81b$SK2BL4QK}{%kA-Iv1d)jq8|U-kC|nn`N-UB zs;XGUfx0Y1ebppRXcm4R^?!{H4E>J*Shq1Y$uob?qpHwDX7i@LnH0`6-GRUZuTNh- z>y!BaOlS=)d#trCid-g@DKh;>l7z}Yr2l60au65$)}?e#b0=UX@ux2es(QgbY*wwb zysSvFFBAy~vwku<{zJ5$jd!wcI(A+!cvIcF2`t5s(3e%?5Hk*_mugv-UsOPbo@3S? zt*kKgC@z@O0%pB=k`Ak^_W}(}5IvzV(cp(> zn3-fY!?^#BcylNt1zbpJ9|a%ZjPx#KHeZdj9*kMb0aMV4+0QGxV}_+~OZ`|`1rut; zKuZQNoyHnrDE2{0GMS@S+52Np4e1!xXbh->YxOiac~4PNs?H|XSsMR)=xkEVRdJ8C zJ3Ce)be0~0XlzRC0sY9p4r9kuDtiR9Hg?RMFu(C@L{*PWSJ%vbBpGgvI-kThb^te% zGcOZl0SUv!snkb4>f<-^h-))jb1Xe{YBXT}?Ug1j;nTF#foq}5L6Sg5(nX>{rV&dW z_T&~Wvdqp)rj^5WhSt%-R8Q)shyBxBN>>;|E+|nrved5KiPg9`m`cfc*cYjH0{dUo zZ7)y4q{6oyPnPn|_ zL$I4`7iSc)eo{kS{e;9`f$GfMq0~8W$&lm8y)CMXN21*c7zJwSjc0sNt zm^~jF$U@erXQ>%o)-$~Z9)i(TRABv@6uUuj%$!(U-t>3fg^x2w z)$AHoS5X)=I$~q3A&OevKN`(XY>r#{ufe~%rCO~T*Q}MQD1%2&5`V`H%Ta$j^!X_g z&>N4MesaOzUUVAAN7HAEqkHo~fVp{Z-4zb&t3hPE>gPv7RrCA^H;8}qI}hBD%E?Ia zA+7iaLwog(|6EAbI(|8%vX1{uNZ}8-Kcvto@3R!W%Rz7lFYR*VxA{Zl_J53V(I^Y7 zSV&eoVoudi7Zj2e8a7Xdoj}ta+)6R(Tfj)e->nM4$fwh|hH~a2v#R@YbH;bHJHD#d@56kbj=bb9*)9j010}f7pYA*Ia3oRrc^8gIt3zXLAl8cZgHv= z9$IYNA)YEV1ru2HQ1wxlw8gzlpL7trOfyD)>OmqfQY4**herVf_^{Y>)&KbE`a#_- zn?X!H(#Fi0XF#RfmEd%6KFYV={~rJ#?$@HuM_2<9kKTd!+Y0ycaKbK^kAC8TNW3kc zi+3i-Iw)npgE;yVE9uJO@UJ%t3^;M#-@~&{nsa!JwNK|nfjf0bm^Whk_-ZtpSa&-^pfpS zcaW-zD38@JBZV0gU2IK-J8?cqiWDGCRp)+t)Pq&JbbGYLXA4RR;K$#}ghg0Y0Lu!q zCtvv(T^WBOml%cssBlcggoy|)mSDJ*EHFH#sWjsxXVRgl!RL5!$3B|+Ix1S?+k0ju*g#&;&gm?ZH(MOYzUW^jT229D zZ?H=I5=$>0HcA$Zr!QOf*|fFu@1Q9xN9H`99pQsHAh7&F;OXOYTzU7&B*_lM*Mq>_ zu$mHobRY8)Ol^;vSaw2cmS;jG9TqBi#_hEKx<>8K`EfN26LM>_dQi|@4{1w5mrTXx zVKBX=c=IpxCf?8$h1J9W69ycgAI(aRpP5YWl)~cgqx^Zvi^xm;EYv$4o;G(TTU9C= zG{|7>VVCkzZ_Z23QEGeS&W|o2)!1oTQtG?ByaPz37;ORBz@{!jYCHU+0VvWh3bUqj zeuPLX{O=n3-Sb9uzxN|h-HVfPsxvbE5vZ=e2cgT0k&w=K? zkR-K0b8y?**2urW_(N2#7e<37lZDAx^DXpi2^=J%JzmP2VYx6un$|~>p>YC(hx}pt z4U-pGe{t;U_Dtdx>nSH z?zlo|iYbVpR86o97ECV5O8oF84*Uy@i2yKbU7FWv=dmnq;QV*SdzqnI22H;mm0OEN z;`L{v1U(pO?ruR(g;~Y3QpnL1aBcw~nww2!7$3#SNSW9HpmZT`h({;pdt-GgD55VP zYNbo+FMcRN-7s|F-a)Zc4Pw5=9VkG08e5;s40>x@VyExnzNq2omW?pKU`}|F#a%iW zsEDU$|6bEK7bGs3>$cvsL&6t^7W66>#?vT{kTeKQ3W~PDeY{AeT+oyj z2U58MTCF>Vk{Qi&a5!k303n_=S6>6d2BL)8;vK?iB6yvzE zHp7H#?oolxRS9t$k`2sL$p&VN)COdJFcVEjO8sqb7i(_1bS{yOlm_hxC7&raJcgFE zO`O=JN;;A$_N4|ZSg8FMN1Z>9#}yh#g^ruPM7c~deA?f06fFRkS0yi)3Q3`yxO9ca z(j5BVRcYj{Py3Vp!SJe-7Z%7T^`cZ4L>vWz%2H}2n>{0|yh0>IIhvc)6zMkx$Daie zF@i(GX;1!CPmn7Wgz}shdP>~6ccsKI8ab(g^8bs9LRtAiB`Q!)Br0M}2Td(FwJtTy zeGvgc@`~c97pUejnug6_QG~Vg z$12Oi6RO?%o3_(Tq%1fgIlO|u-V>kRgu7AL^v z_K2q#Vu8tgEc9i>WX_Ma=$SlziP+((x=1|^eZ&f-&qyA1Wy^rE)MSyQAMX=Bm6#%d z^T8egfTCdeVvHGHJXH}oKw<`)jc;YJG1kSnQbuc?8vM}CwEu*lwa|)k>&^_O*HMQYOUw}^_~0bai{Pj*AHA8(aIQx0exD7c{OnnqIqi6tRgSPlZ zDsOLRIGEsBX15u)c@r)I13ym8q-65@V=eTx%$>*O(FnI5@5%X^XBW%Zh7}S$q(PL+ zbx>2?wAw^hMSYK635iR;qTx(626s8F#QtRq_(3y&`U1Q*^FJ%EQJV-V*V;G048V`_a0eh!*s6TD`mvfTWDeHHqeo z6D9LEEM@(d18)OahJDT|v67SenFuJbJXrsp9%v%wi4y3cf|iTi$RVb!bR*X~lp>Z+ zrOf-v<>H<>&*A-*FPRtt2%FVB5Qz7XA?xNX>4?b~FtmX~8qbZ>XrdPfW+d41ru6_9 zv!G>>+g>IVNe%<`WRKQ@73Z|XS}jSmK;0~5duAClOo(S+6j%vTn9th)kajGT!f=3y zx=$Ly_Q?7y6h(oWR-lQgN+cfdGA-)pqfv<}Mxj_kv1bLoGFdlr(3>AucjI^?U68WS z4+IYt5EwccQhFcIX$rT^2O)0&XKw^=Rkt%8W*23}CZ(LmQRk+V>0}uM!;w$uY_^+O z!uUXWtP~-uH6S6JPoBU)>WmkUs)*!RoK_LZ6E%fq%ZJEgm&bY+?TClTe%~4T*DuTm zry@}GHyEd5rpx%DSSHB>ew4(g!ljwFZ60Uo?y6m^3!*vk7-_=G+io$@Yd}DO00D{B z5Rf^I0>@=0PXQA-MZe?zMqu4NKY;O>pf@YCBN@~bbc`l%7{IbHNER@s{Y{$e;t;Ma z9%0>(9mjfz+^G24wf@`+&&20ww5CU$-%qfng?*u#%P!?s{UxKqa~1hL!ZWT|eAs#| zbe6>G&L;g0SXDeS$hivDAtytSkkQQR8u+SJtR}-Uythx>SApd<+NXfa696ZHI&i`` zi*-X;TxN_IYUb%oz;YriBZ7tH*zkqfkIxdogk|0^SQSS+!g4%@<@yMd!g6Y;{AygF z^8XFp0;!=_>%!Cx)S;p4bS`3DH)y}9)-5%p7C{Q(7UENL7j;V`<5F|$YhLMA6*v)Y zr?be?LpA22536J%iQ=G6HBI}{e6*Q~9+t6)z;rc|(ID9vp0JJp2d>r;G&aQ&!l>Qf zV`m56qZJe^uQ1a_Bf zLUaOKd?IWjBh=D;A#lIFs_FP1cdcj#nz0XgBT@Zt!P8eCV=7MBgHn3m4mZ~ zrwm+Jc!f&reuIo6DC)_g?cGoaXFFf=q4gTY)?3n(HEDdee<;q)JKfcSFNluMOn!2w z;0?3kVcQ9y4#bKv7L|11n^EPA>wHH#0$mBS*e2}m@T<|xn5~p2r3qK_=>qyw&1Z_d zs^f-%WNQApg`90QFhg-V=&3m$uKnUVW7l-Zf5s+Vcr9b0B*_Tq(d$bHHiWwkbQpHQegTzN+=|2>%5C_`^am;cQ zf=rVP_Myp#Qg2g}%)@V9rr-SwDB~Zx2OIg{Y2Hl^{S!R!cif7m{=%rE#dc4#M)VZG z*HvL(3<*SxP1t|aPA70a$8Ri1CoFUunlmxQD6o6p2o#LkHxE$bg94RnF!H9xCiaT2 zsd^R20agP*b2t0}kjPza5=2$v6Eh*go)Pu5>8K;3j^;3L3rJ@FnV6YyC+kls+pgAQ1t-h|Ar#E-$&A=_TSVfspSJA@d5QcLTX1JhdE2c)=>F6-R_8 zCub&Ggt6c^_gf2$3E2M8yz9UA;hMAGJ+tOB>uQRlgE6pTj8)&E@9x#P4QqyAp-mfA zc%J%d9+^n*q$P3J4R`cHh{Bt1Kt4ZEOB32A;H44J)E0)su&Jh9V5tAxF;va==Y}Lg zc~6QY*c?b|F~o#N>lY-+c*N8iA*KU&WRD5-&Q$JzgE%0E!4WrPWF35c8(r>7i)r{| zanSJu!ZDDkN!gB;S4u%$5g8jTRuh?6z_!BQcaAh21S{AKh{S+V=#TdAG2mxK(O}BT zyI7j04S-X|IRoUP{2vU0o7!cF*UCF4|5?H5CH1IP-9YX!z+b(fYd#i@^|MG0t;QkEskcRDw}g_3%~R zWr{;fNUh?RX2nITyLfa5W4wXNUauBI9gJR(XC}?qf#YMCb-S}Gj<2ssl`Xr7Ek0)SR&=W}c^FT~&pO94s2G0JP9XEC1P{cCY6(ly@<+oqdwl4iAfqbZ^UlY!b{ zK$s8yZgc=|>B4usVyOU6_lZ$S-O|n79e_2_(1~ttrgRK(1~+#%kRUjcn;QTuE(!b> zZf;Tn5-rmw-vQgHz< zsguCl>dFyvB7Q&pT;Uyy`Z@`IJLB*cQ@nr|Pm;k)1Zx4W#j>u6--g(P(ZC}o!iWXT zs@kf)d{gDtJqO%RJR|N~<9%WIWeoSoNb3vJqUZ~$li=QBX00!OEf}%gD*B)=Om}<-ffZe}Qe z#}p_awI&LBvfK1zjb#T(ypui)zK^xvLlr&gd$Ise(jHhdYmjA*+tZ{GVjEEAYY3ef zeM%A>t3i?@{H0ul`E#WIMhJP0LSE>J7oj?FzYPZ_gQ29$v-wwG6DGvBNJ%EdSh*6t z)FDfk>lUJ8k@I2yL(Hq0@1GeqFC;#>7(gK|O8vAX4puaAjEHvs4M?khR>6F~dlNTjbHmbb z7fDbmF~S5ZaOgyG_s|8CdS-rCWrEnS_?g(hK+Bb^s(w%^u(S>%R4{yO&fPMX9z=!3 zxk#0jJfqEErT_R6FWev z$QuSduK;N+VT89dME!i7BwWm<1=n#?Lv~ZwYz>Qbsu-ea%wb+^g(S?nB}_*eM?+e~ zv}`qn!Zd^=CtYUf)^X`8Qsp&ZTWc_RdruF=&|;?Y)}EdMfY7qo*ywOO^Rn>K13U~k z2Y|zgwAKy_HmF*n&9=1FmYWo4xWDNQga9Y?Z$Pw_u;%IPphqU=%%MA$aYtAmSTb1t z@NzW73Z6@E;gE>+KWdNM`V~E5r&!e^6s2eCQQB-X>jUr?f)4Bp^{wcXx|pYQ%5Z?m z%T;~h()E#lL+Fg_vkQ<;vPi@rvdw^qb{R(3cZzX^S1C5@2X`|S6e_FT68E#0+HTsg zzK89m6WUQ5@{>%v?y>9S^<%E`xrP>!1L%khSmu<%a_(ADZXn>n= z4Fi?ACB+qU5aPw5h8vQ79+Z?7!5dT-oQC7CiC6JjmkXgQ#cLLkV;;g9e5aFl1qmC1 zVJA2OVeoCu8nkJ4_hNl9-3DklZRk5_+E!FGTa36Q7cg@%l`lBmbT`0G5d+Xr8COe8 zmoI0(SdN%PQ&Sqk(-wY4ui!0t%ec!_7_{b)#X6`>%tedNx&s+7W6j7nTr}&K4t7bU z`~6eLk2e+;T?#eeBJx@}@NfDMPLs2l_va4%Z68A3KgExHCNOLBbJ7__<}@t!vojI- z|BQoyH*9muvkcSw=-b7R|69WJ{%w}juLlPv>vyriizp?C0&#mmF51&v!g@@u7m?Yz zt)?mD+VvUinfcP13+|d1N0G!KJB@w3PmXD9)8Pw2UJDc*yTzpk#jx0fsURwvJ2TxO z7O^M0-b()Xcu9PpOk1IL*TS(|4(91j3e&C_xxI>ccQaB{hZ0?Ad6umh@ZL$H8xREakBbF)0z+{qj@F;sL-|b_H|KSl zp@ACk;XddALMk9r0m<9Ut<`YVZiZ3?JFTFE+{`35mki1{%OFA}*irxL0sbuXv8FW& zhAv^~h@lH(l@wzIM8$BaGv#n4oWNlthR>@SF_evc11V=A_|;{_@&T@y>(w(t48>zmSeWYCG`Jna+3SkC!_hTU zDhztj8CO8b5xJQXm*}yR!lU zU|LZCkCNes#dy_F_cb!)NNM1LmPajE-Nj}&v|5{nfLzu>JcjHZ#}cNxkRrzI_9A*} zT9s2td?L2}9XrDUb~GFW>_|iZ*}bU73XO2lK8&a^fKpb@OSXEu=Ov5WAzF(-9ldIi zJAzh`JGQ7;AI0iW++uaC_&)Q-C{_o3(I|sTsi6vNAvp?ck>^H%EvO>ibRjb&{uUR+kz z<(B2f)%!wT?$y{x@27<0CnH5l|ZFy0u`ED0aR$N1WNngPSzaD z9-);xs;*~iwi5ceqLt9__~K4zxA|c4~ zhvLwKO*03G@K@;qwxQY_gFKx9nH0uk1Cy(TZFpIHMK!{;x?FC%P1s#a*p3CnqwSwO zCjKQAs1cw>0u_lPP`g~7_;9E{63{SoDP7|FAvTcAzF3a9KX&t&!Mi`Df;qzgZ4W;`_+G-aqk|wknQ#vBdhgWa-)dDe_2( zjc1;tk1)Wbkn3`#Csw={+iQw?j_fs7qtdJLv#r#b>6M+qS-iM)=)e6%t4OOZnLCTe zR<%28E)FVw7?sRcfRt7IiDn)AbDi7bUu0C6xpn6yKPTaTUUH=z%I76lQ3s)dDP^9Fq^Vk!?1vmA z95-|dVm(cFyHf~j%%`Q>UWvvj_$b>8)`!HdVsHZl)5E-L0x2wt^FEGov%`Z8(naO% z;&NyI5W23M?^-s&9BrDDH`${*jA}O#%n_3oH8(-Fk%}q{7D_+*#Xz5>;4Cv#@JxbC*1;nP`d^IG>*&`VOK;~ zH?w6H6`-mH`5_<552Gq_-hZT0=ZX=uh8qjhcytTOPpHa393zAVMAH`hiN zEs)|J}Yh``enED+*Vc5d1dt36NFQJ?`;29S>ZyrN#?b0 zQ=zh+vy*&6=;LO03aw%wb{q~d%)f!{#2c&D)?Y>yQVa2yRwW(|g_Jg^@)8C02;=Yx zkl%xbs8SkD$538%hn0<@>GbcY=~prpp0;V(qqNEv{{p4BAxHNdz*&zO5r=bO}6THg2=FABEIVeKds{XqV zJ`m*J6l7~RXCn-OdqFDc2E9Qefg(skT@cLL%;KY=w4H zZu%9rD+&vk=_RV)00&^zvQA0dhN{`Y6k~qw z$IW_@xrMz`%&o-c7V0%(omn|J4U|rjO!pJ8M2{vFv6Kw3!a^?7JTMD78$>ijxCu_M ziIMFksicVgQGOF+iqP3aWft3tsIYLXq3cW6?UIj^fwf5s*I>HUQSW_1Wj1;6>8Z?o zKA{T0Bu(Sw8omN~lUkwl6?pI9S|QpaJmR3UR2s2W5uaFvlw)dv6s^SkK&OS2e~`|E z^lL6=EYJ||<5vLu7L3916XmDV7qShg+=*Xo*3nt1>+fZ%taC(bQ$BG#it_c`Qc^PTa*0bx1!rZuqY$jq|2fKU=ofi0FqrG|8p3QpznRjNx6oxNSt(XoSKuHwHFiC&vA{*G$ zr46#y1$HJma}5^+J!0SA&g3@1Bfak`G9-U~pcyYM|5=s3?F&U|Hog0I>qg2l)DUjO zEb4&QlkZDVvT<(SVgXxjvxII7k~rOz5$7CTHtG}qUN`Xq_ZB>_#ZTM}C?VuPwO~Rdnqkh{wU7DN<~(i7SVK4rW!#G4SU1 z->yqE_%Uf$7o#z3;auIp8C-S^2MTZ?plhpJH`uJXC??`Ii#`-cz_rjfl&YzKvn@+! zh+YVpmaRxrq@fXFrjY&u`p4XM9@P6cvee>FSv)p(!;J?I9X_(W6kk6$heA*zH8}=k z1N*bhO?0+9=jQw@oi2ISsPr3enp-MgJ#bTg*5$Tz)pck6%2A&#LjJrD<=Lc6-3;Ol z;!_Z>Z8iwU2g96!rPy*{aH76;XeEB5J4jK#SO<=O7+x%ie9%~KE} z0xQtDCLV5e3i6g@tX_WjOW|d@W@{|)a3P~E9b%+~nM#FtP!yWbR~NHi&oDs$CG7nd zNsW*6Beo?(4o;Wcn1PLPnI1q6+mfxwm}EDaFCOpbmF9)pnopu{=F{S%^4&0MQm@F& z@{MkzvQGtjj7;NdZsmj@pakGoqaRQW1$03hMnX$*(D7`?k24K8_K_Rhla1FQT>jZ2 z1*9a{a$mx0ppn5vaEV&lw3)h7aUF7y3T#dy8!@M)B`%66OvBr_7E?8>Fd-c?8yOkQ zH4csF;3kIa_z(Z~zy9a{^k<(v9vw7`MfCZcUeJmuz_sKkYH7N%LxAD2hKXH!whaeO z;#A+%u(#c*cn@)N?qnU3%H*O{rY&x=*RaSM zbT5D_OCZhX+dyJ&q0TJV%wnf%?oiVKUTh! z7t2r1kJLK(j5ZFjGM^0dN$L`?i)^+IYhFKEPW_1hE42t#WO{&A_Zs2}AYA#V$e^)p zkv>=;6%{6|$-PnPtn{~mo1Vu|Eq}ho8t=LY85K@?ATIXQJzuoV3L2dV z6Y#%C7rzYUt6Zl-&E!4eK;82sSG!zTb<$$IEml1b4j^EIirEfPmu-#@0aL8@;LLwM zGuQ&lrlqLbjJ4RZmPlk^87P0ESvR3UKXCK-xDU>96DamzV#?3=tv$1WVBALW%?bD@%h!!zwd3u1wjzlA*WUrXCH)`Q z?-&)Ee6V8#<{78c1?coDF+3?D;xKGaAEb=qgmEUJt@d1{vJ?kA8Y8(}8KCO6G{;twRMVv9M7yyk zTkWNDc**G{&Z??jI>!_!+>C@m^XO3ZDDxcZ&O8rpI)8+lraQ-ccK&W|;2@$2bJQJX zg+Pcxt?W1yyff3o9N@-7@a7r-tk*Wt9h04UvI%HiQ&89qedz#tsBqR^*g@5JJCVA< z11X=I6w>}pwv`-aM0`N039X#>=%oof(B$~|a1*_87U%Oz7MvAyCvgPqNFkbV4eA_- z!6+1=uQj9`lGDy+4qgV-@kVSe=KZ0GfKc8|8}mmOW|q|!;B>=LRx!~ahHW{$65bLR zu)>=O?MF>w0OAGM?^fN3c)0X)+v#>1%Jo5%U@`zvkApM|5BsOvJTi}>nQA3{VYIR0 z2f?im=X7zW1HDey+K7k_Y>sG6)I-KN(X67cRkU*=#S~qdk+rcIgDu}oNDlKKV}Yqd z2(6s}c&50;IwKX(P!J*~PpJKEC1FN^Qr+m@z?m7{8@&C|y>+Nr+{+SK)`@JKCv^W8 zpb|`?4<+MgY6#Ys#-r059Ke(95;QWwd# zAXcKQlzI3?wOM#EwzUKhp?zU_L_rpPcZWa6ZKiF9(y@`6@gRb~+2PCy2;!-|!7{O> z2PW8Kf!TFh2P>kE*?t2k<~LCT7?AoQPLO4a?a@?!5vGd5H&;xf9z-Hx)rw6IE&qXbB(IE-dX#-i&FxRqS zlB*gHzT2o>SS7YCwe1%l_Erk@xxORLCe66jmQ7u}=#-nxGlX=th*9CawH&QP8o%*k}Z6Q1^ z7W3^UC>zC-w0c5qa?6yPpTfQj!=lx~W7RXzx`nVUg#s+ML?8;3xDMY-*(4S`u_?B~ ztqLrvpa=u1GA#^&Oj$7O1d+ndIqG%buc0eqC@ErRS!bm!YfZ=cZDscpZx#UtzzRit z{lGtg7KE9~ZVPo^rf5yRuErwDPknKp$DnUQSeou9dBFa&wcn8IZFNj6A}Nio z5J(TJNRBfg5?P{jFZ$y*{GhQQO4oI$b1Sa){YC335oh3#(ry0($Wh)bgn@)qXb^UrgNnO{YEYKaPT9v9-sYdxCVr%qHvwGgDQtc|$u@oOL za_qX&tdMcZVB={m$=uvV5U9~<*g&Jcu073YT7$C%s#t4mMA>zPOvyc5XHYfSN=Cmu za;Ve=ugBQsnJv>1j=D(eTgC;~IMq^PRgA1)q@vuflf6SeRh<)GM~ zEst4c{>tQ0JSFr82S?RG28W3fNw5GvGqQv=+F?q-1TX z2KO?9AKKCJkLU@$wtv9;Mf(WuvUy@bS3?np@hzZWN{^sGY>Lh?-L!cbAVXA1jJ<@4 zZiu6qneb#A$*$Qw3`mfT#Z(p4yc}k0!mwpvh?U^HOUJ+)7bJaq1etA~h7tB0^j8QF?|*Y?Izs6`zVY9p*wEy(AbIwbdv z4O)i#MObl$I~YqRP=y?VlHX%kWG7$1 z+j8h`A_|DdMm8%(7K@=A*|2rUs+1wbgD|*QrD~4`n03Unhdun&7WK|cBIbfL1*zib zot}2oJS>T`m=HTqKekWQks_;!phuEYmDNEf=?hjz_=05+Iz(Ju(77uKp{&H-RDnxI zQCfTg9$PO@ibKec(Q-I~{J+O3&`;aOf8XPuKa}aXD{)wSY(gcvvS=9ZCzqz;O^jVJ zO|f)f`KBq=%RDv)*(ySUdQ+GctP#S?&6rVnXizNHw=dF)v}6gNkrGeRR1n+mvQDmf zsT+>x;uY6r+Bl$hB zNk}M^89wNLRFs97^%C>*x zt;>jG1sQ~mv0&O}4Oo@739rJep!*TlL>Lh5_>PfYg9u@vZD?3ZO~=5~fub6ktM4}{nQgK`CYxN#Q-A+kz#`IGPBx@4h9BHUkdht%y8?FQ z2fB-L<_DT1VAtG4$1Lv9Vkx(|#BLlcU#f)SO4*?STBShfZU=V>?yL~iDwBuP-!7n? z)c?(`K!6!;?E;vlVQ#98zxiI)+KWeeAMdHunZkjZAf>H=@?I_)T&q$8)dFW(a41L! z$yTjV9@}sRU1utV^U90))Qxw3`kmkV;un7R02>r-31sg7{M51E`SQ(Q{p)Y?wPS%e z4hQ@|*oySSloQla8|*XzV*OAXvd*oYz=~PpMO78=c)C9qPi(8l#eYeL8eZOvUt79q zFri{Qj#8xTmx#=ftl`Ayy0%$Iekprt3_%lX8cQ*H^xHV~!Z(IjJ$l?8t@+lY_K1DZ zt1GQba-0J86KbQ%vOZe= zI=e8SHlE9)QuDW|sc?vnRt@}yMd`GeuJAuJ6;??JVXS>TT#FyA1@nzwP}r3Nz8NVZ$~Skcq$uXN=ZGRNRA0DK!LH+p=ov zK_V;@9P;&}u@55YR08E4a$N)USU+T{LM?Q6S__*UAevrH3Lyzx$vNP}hq7xqc>7(3yV12mx^RII=8PXqk z59x`JzJ9^K_X)HBX_r?n_{Y9nq(Ak&_Pl4}D*uU_cFp_4KTx$2eK@agUsjmXyWh#> z!Pr0c>s&vV_V*TiEcPFG7dhMfU+Hor@$YyK*JBB>bQovH|H7Reu^v`amW@InyQwm&!PoFPlz)B~VZm}hb= zEc_U0+{mZ%;cHH-Y3ULAPt-}HxbtYtD`7=TTc;7UGCr;2)BgP*1_^x8yh6e=9fq-C z>@W-|oVKrrZmJ#+0865Z$R5W9&z0PATH)mz7;jf; zhRSa&Pa&s2qh8Y=RzwuY8S=~(YnTWfRO8m_pGor`CpOvj_GHzkFbZ2}Y>?jZTnA~z zku58SNGp6JaPi<*LzJ@Q{`WFOw-*G6D-;488y4&TLr1}kg_9PmV%C4ZL0-AUf0L&& zD45Jav%ll+QnM#UL7IIco!#{LC$x78<({mdx~qO0XmX^$2g2DA2uH&>cl3$39KQK? z?s(sw#Yv>!Km3tD{>Z<(@%x*#bh@4rep}$uM+}araLB)K^$8Z5Vg^1V-64Ans8w{gVp@G*N0L`&SYI;@ zB|X_HL2w=#CT7L?jLUA17-1y{RweIqcJYGhj%J~HV?Zo|%qf0zQgI@&zaE-qbd%jH z|MbolD37{dtP4EDAAjHW1(wfP1f%;h^fpLCkNr1@K|X~m+mueVejxsMp+Df%pY74k zfgKujIa^A|yfk>B`-AmrFV))NdTCIc5ez4$BV2VjiB`jHMSLY##ghQ6TvO|H8c6DI zBnk?j6)aunp5jVTTUTJHD~=oN%8x>1-4rXdL{hBRMTJ^!gVscwZUP^@kHXre7H&Dm zALLOX)m$)?cE^%>@O$KZQ%!Ec8!U2G&+*t%qD{b!X${sg3z9<`C%8=!ODaWW7KGXh zRpX{)A5KO^syrtiOj@v7HCIDzzw2B}c-VAsJa8)iQ}M#Ja*4LoST>ZJ8{5EKK+i18 z^~h0Q;TkN7O5y@E&I$~wDd`0C+&}&W#I)3a7F%uXM$z+zot`sIwddAL6%ot#cZ85&AyRO0{;hE7hq7i)PbFYRvT)kF};f$ROEOPkA zsFu}Mey^4HSewpPA*RT3sZb@XuyiNuh}nX^6V^{$k5hM`-lZj@AuJjwry?c%$dD0( zEs_cJ$yWe~2BlpoU-C3e^X^tH;O=&|D3tZkig1NNcWpJ;KI@ z(smuGVUWId#&EibX=Moqrznqos!L0J&lK8)WhjEfEFZz3ZCy=U#4!Jk9oelp5r`iL z4ilIvG?>IBj<}{4of$u-oRJvocrmOiWx^NSPz!1vr!X(80&z+_#f80;`h#Dj>$aK5 zFk!dC1f?YNQrJda=amZbrj8)S0VJKVxX06}5`Jk}-T^(}Is7JUeq&P?Q%5r|a0$+h z2VCyr?a_;vQ>w&1CoA)W_e~|(#co!r?Egcv?Iml71{Mq0&TQW-@>pKpv-(LJc}v zjf}^Z#N^J?RrPO6zOkf;73$aeU(KB|beko_tT4~4Fjp0Qv@EKHPZebn%Ou37R=x7L zUV(d_myohEO5vYuXx3d>r=C(5_p&U{69^(n)LaXxOyN!Bi!ZCc=0u?2Feh|Z z7*#Jg_o|@YaJ%#`!!1LLkVfsuZ0p$oI@2*?1i~9syE;_gb{qTu5EC}G$#r5I1^inR9IB#1KsVQdNFsxT&lTeL(mdorYGQ+Q#!$!8Ud{#T zxReX)@mmS!CGs?O2@AbH!``Aknx=kcI5Y-BNH4WzSHy0kmJ4Fh>Q2?W6*)~GFYRU8 zZffWqQ9`&aDSkbr5lrmkL& zBf~_!Jx6O59GB{NG5w{L`pUL=G+|R;Ia|*j>bF!+mz8YbL11C*jmW3C*O>&FpISt@Iexn7xO%P-U#J_Yha7`V z&lTi~yc%h%?O@C7%#@VIJu9xz*QUo%6xX)hU*a!cXqHwQF73Jjh zRwN_JhctK8L`ih(EepNsG#f94)wWg@*nB@y1&~jB>{teqJerZnD?~GB7w6X$w>Xvz z768l@X?aGtS#k9s2y#LwBnpa`v^c^`oH_yho>-Jn2^H0MR*W^WpC6uBdPl>)uaj}d z;w9eZB_bH>W85)Fp`wI>i$^6aYDNAE*%i1TK7%E2Fs6@(5;1JJ9S(k~jRt@d=$T373Is4SbYOSUm zK#hN?)d0#aIM*um&}zLx<16A7mmCcqT#3O`w6E=r2))s`nLc^KxFIaZ#tr*I!`J21 zRYSvQ*f>pr3QICz2&C@7cZ;D6BersEFQtqfcEAK3pIy2w=Yxwx3Kwv*ik9ahP)Q2P{c24!#_SU!9Klw;@p^XP8=7kVmwo9eVB&omXF2P#jWXP_%w{p4`w?-$kg*jNWz2NCZ5ZxWlzHBxdr>k z=xw@|*-ycUvt&=Gw|hhHe52{s6P<_Fv4|MGZx4fSpeiH&YUNNivWzdZt^ zkkDZ`meX~UAu$O>SdO;!Q=_zkDU|w^!GfdbT|hV|OM z3&IoInxbc9i*+l`SlMizuvf??f^)HRte@K5d3^E#R@?JoQw&sYK;DaR-lnQFj zH%$ohfdhq#KnI!$OfC$LecZ^lfS>e#hJvtyNDQ277M3pIJJbZz$q2)Gv2-dOe8IbN zL!f;}y4V@ImN#~*WxgwHEh#W}mH~tHL2wb)M+k;hO(QO_wc-rM59m{0p-E!~%#Kh6 z2F#8z1I9Md@)*mMFD)|Fd2RVs{< z7p$J*)_}GvnIN9Vh_U~T6c728H~^LCmMhi7HH+HT38{(A#hgQdHI$)QY7KRAdk`p! zs0C=Ov_^EvB0=(p}lCp3D(|APiSeH=XZs z&wuMzg7cU2U>Qk^dsFOzt)mm_q_2QC<|w|C6-I=B#3KQaV(PxRz`UgDEA)dNwk^42 z{xW@)meiIYymy3!0zM`b*2MJLH?2zbnAwy<6M?3i^67F{>Bm8f=@l_iAi>m32*y-t z6TT}f5!oue(pF(qI0hT~m8Mp>**M|freV78QrEyflOZoYXexB4Y<7& zv8J+10?|xOirS4#fWQ%|S-sMf^bdX>{&B80c*duxP zLRWRmghv%(om>%>XdL?JAJg%%rEJi_i3@pgw+np(WU_?CZ0WOmn8+F{!?fG6XJ7vx z%d~{J&>_&(Ie&lKz&YP^o-Xp03{A~0oZ3xA+mBG_=Na8+bx zi8+mwRmCgnFlDLA6z^598lNahucBp<9f7YlG&DzN#m1p*@Dsc%tO)PRVG7)B z&EFhk&v8e<5*{F&i!Wj5BOmP05!RK`VuMwQ&xB!9l`Pd7Q)07`>cp|eY=$a8HeIi9 zrEO=L8rFh+e#D5yv|VA3l_XPE!irE}{+GxS6loHBCsc4eoo!`i0tU;LD)o^Wb3kqn z#|w>BU1C$K71X3~Th7^Ki3{^vte}GJgqty__3enV7#J|!lf8-s<-t&ZOGkNenMNom z=)yI|9g$!}Uy_6VRdVHv)1p+e`*|@hYvqV(yi{e-(G)j4>PvGGB*4I$uCD-#@>K<| zB9`@!;WBr+T0}l*M8fnRZ|elE7bt~F?eKI%I~^LaJ5%d4eBx}vr&IuAchoJ5y(pHGKATg zw!?qQ#7mTn&`J5+Nh( zXhp1iA)>QrnDIAa|^7(HNj?(_9U2W4EwmV)KV}7$`@J zITpIDFHFEp>!DFzl%inm>TE!*YP)LQQA=%-tP_B&c0&!lwwM?Q+k zmx3T2=`q~|A7K)1^rUVDpC}8$g@fsdSc#CYw(aq4`cAMq&x#up2cXP0CS%J$TU?I*F}Z&TRccbv<*YcqwKqobB}Il`9BN* z#-|2?IELbD-i&O)kMEMbuimkZ!P|{`C++6m@`+UzZuN|%3?Y<8gh`DuBSQouxHK#@ zxs)Ed3uSG-7!`tVGDMv(ot>;dkmyMF!@Sbj8kKZY^elWaLZ{y>WiEoAk0gH5NB#eW zo7lT5y)-M)!sAn=DZiJ)T70B4W!`gY5&yY8dbcGL<^Bsxrb@oclGMFDy3>->vOT)P zlI)S$9{q+T8%qAVCF#ue=$)2qDS5jk+e-eLCF%6`=pB~qD*3CH>?wKJk`qe4-I4@B zZ;#$)$-a_rwd9nNw^?$Xl5er(dL?hQsg+G|c*|nS)i{>Ch+&pBU&*eiMDk!s- zJQh;I5;PpH(TY^~q65qO;@b~M)%HEI-uCQCP z`;e}t%*er9H}$7)FFek^5 z;W4Pz2RW9)_*r13#y!|XY@B|z*wyU` z^V~B`0#Zx*QtJ2g+6V;!)2@K=Jl3%4B}38&LPH-Y8yZ8B8VX2id96Jn;S|l*>WB#j zEWyQfyr0GkjG~2d^pDwMux&IU8yOA=I{C=7Y-CFKK)31%jj}kdk-@qnh%>;OR--~_ z!84DFloTpN>Z&V3MAwhwIv7CSU?63P246g61~z3UWRV z%`^&pr`R;=18~dD0GgOILR0E*wY@=2@Klb(Xvmv}6c+q76HyZUJAN3eb)4l|+*URG z!(P=n{M2TL>AM=VlOj?17M^g=teVAx;t$6*jge$qhQ7t=bmi@-!?`!T?cMi1@z?`5 zHNt#7v?^=7#1cGs?q=;aKk~qhwVm-1-P>u;I_YU=da6=#XB8qe z9uuKTm+GF>1CH|BzfT%w*9b5L8>N4X(`Uf13Q0$iDuL-rFooH=+;T!m$^?@&D<`JVZj;P%9CU^bj-K#(YQ0*@>FsNFFCYsPenc16#LU+;<8A5n!Z z&IGE^V6N2Btj8k?VK|=)g~)$mq_&zuf@=l}f#}wx5Nw81b%s9Pw#>Y3`c|ae;(&y` zZ4sHlI(jzr3OU0PwREy3y;ji37Ytn!v@@bt<_b$*K+~Rg*?#O!`I0c31PyzqNjz6* zWSgymhGvS{W6<#V9w%t<-Dz83HG7z3D3gBqbS-4EUyN^$qk;U<=~9JFpM1cQre%J` zlBRop&XT5)e%X?yr+(g&roBFFNz-W`v?Ro`J^DjSnv(McOPW^v2bMHl`HPk`4f^*j zX?pdSENR;ILzcuG+a5)N(G>H4W0|I||7#`H)FY?M^$1gE;nVB$LOM{{HAV>M)I9Wm z5YA~rGKMtnAW3|RkG__S3*3rv5r4!-AmpUuHZU$cS_&pFP!Ar(xXh1%WrNxw#$~9N zOP>t^Ym%jZ9&?M165I+5ATPV*M4wLrKPx1|LE%{sUK z$^GYOn0f%@A~YXdlOouH!H&a;!P_u_2%I&-X4Yb`C^NF}^RAZh&FD71yOP@ipQvdC z2eyEURe9pslkFOcr~E~PBkFoI5nYF zOuG`Nns%#kDpV>yDd2FzPm2Cjv@z%^2v$AOKHtz2)2eyc@ql<6`t96}cx70BM%oY; z#!|n+iyjO|&(JBs#bjSWC}W)p?HI8@(*<2i693&03g4e$SB2KvmJ|JdPZ=cM)!pMz zxvjoFNy4A)kLamZU!TSJKbtaWU+myVF<^G!v{+)vv8A&njlUlwtV4TJ9cmb#W`Vt# z4*-tmmx&I6n@K(6FlA!}pi!)VS*;n#Mln=Nmr{?gjApo zVhoRal*fpa_smHp|2_YhrxUtfh8C(UdG30-KK*;F7kr^P{AT6 z9^vcfC}-LgA)&4)(A)JCm}<2ZDipScJZYwtv3Fd)>aq<@h*6Yf180dAVki8QzXKnl zUc!r}#B?|ty@1BTWWveN8<>_HqH$P3n!?UTs<%_TvjxP_Fmoo zDPBR;G{b^EUm~;3D+XL^Gi-(^2R=T25ibcJhFYd$fse)td_ZWzN5bMN*x+MPB|dQU z@dcb7_0fgev-VjwP|)&sY0*%pVzf09ZH4gekih8OZvI0FCGLFkXMvwS#mm#ex6iAK1G_s0WxIidqO<6iJ z!Pk(Fj!xi?Xd<_1Cxovrvq`Z?7fqAa60ns~$cTwT)D3-dbrWUyw(=p-GU5=Vp`T(E zX<>NE-(GfE%I0Gd19B@;QMi%ou;rQ0{$3U2R`L&bFM>dZSEWdbubPN1rmvoT?hDHAh?HIJ)X zQpcoifwqYIv%DR#&T*C+78zRpusOw+!bjA(MGnG`P*TCxD?bWD&sp+b@~~qmG1-IE039K#r-v^RbMp=^{V4z%@wbr z75^x2i$?$d%eTdQD^a91`x>9F`}$D zXjbVF+LS(GxH6N~Zz#RYM6ZRk!iiw`1&w>LkAn~mJM%bVs~KBEMq!HN0&Nc{)wvS;HsICx^N zA>mM!TVUa~kg*yKB?{;bek+dAkr<NQDrz({Y3_d!8*qKscwHH7Al*%~s@^ z{t5AS$+w>CL2dfU%uR~=Bx9Lyy&4i*2AX&`A_le6zIB7@%&A->t^lHKWe#~F8QDXY z&@WLipAuqh!p&aR?$AP{3$cwwqo%NB;-c{I*qa*d5WQ8trRZzh+t0@_!!YxLZ3CdB zf9$u54)C_Wr@YaVP9w}~0ct_ASOd|y(rmspqQNA_;daN@DN{i1?7m0qWI_c^F zX^1QOgV)CTiB-B*e@5k3_KMd8#F38`y;4V(#%~UnH=2iVvtSJB+@uhj(UH=#tU?mh zV!cNaLY1XX!$OU`wEG0vG*HLI@tLifPsEEsV#P$$gfoRhaoh;UCcK1K{BmJ7z&&Ya zsk}o`WN757(v~g+DpZ04Si|Po5ROWW-WQm>?<(TKqI^=tzXcYlFI_@tu*5(oG#|#A zZ!gJUFQQLeR}Ntzy{3$=T!|ib^U72!V)wFwfqMfm8-*g2fk}4Pu-)LH#Tt=R^_0NJ{vnTIl?dq8mEK z%WjL_pzs@yraKz1LelW*$FfH?dL$kPiD&_Cgdl4M?yiQygi`M+e4vE5l(RY%JQ;<7 zGmH!-firXx+E6Dcsl(PLc^hwtDbe~+bgY>pECsL_Sn!hbtqLHMEf8MLyptPaj4%l2 zpdj6(ZU~sEJ@EIGyalx>s1N9HvqWJ89ujduaiBA$Mki|>J#RV0{bSf+IYAwqhhVFe zh?+reBygUEQsnj@h@4_p}X4S(A6=Jl(Em{ z+Ie$b>`TJ9PS8lW{s?8WE3^QOp#`310l8r;>=A2Shj~u2X=&s$@H>e1AC$i zG#%zWYT*0Ke-*;+>_2ykcez1lUosjLL-BF#li+ zpfXHb^|u~Y?lOM?hn`pvpN3%(?+Dj)KQZjO-;Zx#-#${!1#DI#gh^%`4yx{;YhI*^ zbj+ok^BkQ{Vv!XtXv4oyA24~Llxk&A@z)~d@Jp?sBFsLkh)^!Qt|JO_KEuTevOGD^ z({iRr&%inM?UP%2(ZBt@_di8d6Geii^?(*X@vVpNO!XintYxTI9-x}<3RHHiE-in! zrZOQRQ2ey4Pt@V6Tm8_jCR>DnLg1^}(*!=h)}VFq6W_8f=`z;M4--sHKe<#LC8WK; z*mMELk*^hCSi-=deSrZqMcJ96?1#&;mawuim^B1^Al$G6ZUT@2H^6C$aZn96**I=A zD$LrN-=c=_s)vT4(NRN;wSd}E05w6s3>tM%iO@yp%6tmUA%D1>0F5BgHECfhG!us1 zT9vqg{hKY}Z1q<$gIJbK47oUXhF67Sd?Zrvlnk+v5~ggPpxl|2bPnb*C&p<0JyA^a zEle09=j^yX{n&CuaT;9b*IVI$za>?&mBl=!^dO>AxWYLunZrxG)Ys8u>>WBkx}1wU zkC|o9FXi&x_dGz!OF{<6JnOx1zVlWl*iV*9>vO#t9CU1ACW@0ZtJNFLR=d;fO-%Nu z)~%o3uyNB2&Kv#7l>zSNC(_wC0ZpvnnA1YXU?kc)LR`d^r0^nL&E=7g3Erz*q9e5T zQ<~j8QE|fEQpyQ;eBCU(cg*^JSa~3sye1vGhAW1sdQFN?#Pu(LMd2lW4Wz>5AKRzf z(%zOXXGY z)i}#qR@N+8Gu=%^)}JVAGg+Iu8;h*JR@NzGZR~C+vfg%ttS(s_y3S z`grjD1rR;$n6^^Cp)7TXoFePv%K8Unb-KhcS*ib|tZA~^a;sX_O&=iZMP#+Yxzkkt zuatETSxqg8SgCI+Yb#j|$tlZv({GZcKGqdqVOj4~mXKrCnq}RstSw~Ot{KaEP+1xw z3u9511{Mhg1@>Qy=^$PNebz)Ho&ZHr;JqIzDUhJpK!N2Ei*|ncYaljL7zuj(^aJJ_ zmt#$tPkg4l4-^S?uSt@!^bqsVeo`HbA`;f7#gzKJx#~Cy0r=%}OmvPr2fI5$*owK} zAp6%_4kx(VOi&Kw&)YN`vW{qSFqg>tzu0>d_$aHh|9kFxW-^mZmI-S}SniC70a+CR zabr*r6cxp_QXxQ)JrV+9T?hi!eXCY&wZ&FN>r%9~TD5L<*Sb9}RBP4NR@+*OTh-Re z`~6+#z9$oi)jmG||MPx6@0$?r?X1_i_H)j4AO)L~7NddN)bJ`T;($!HA`Tx1wtRT7 zsr=gS0l?8Nd;TsxTng^$F!gVxy|ny$>zQg6Us7>-bUO3@ILhaL1>%-pIe+=z?*r&M zi-&j;Nqfw%|5GoD6z8+MIXq8%dw~|k<)xwM2u$m%a?6+Ov9OknBQcT@?TrdicjPto2{?j^ zZ;3ez8rk5*je{9GMH{p|(7EKjvYgz(n)v?W3Qpyj0Ty=^kxq(6pnsSn{l9# zShH849@9w}5JXZcz$>h8+h@ek=JS|7z`n)?u8hNVp=|{uMq^_71-_1#wo)1yE_uC1 zi#v#r8tOVTPJ?4o)sp$wV8oRi3f2A-x6>wyGl9z;Gi@eQGD(&aTq*X=9s9?jdN2Vf zKCYPA>sK-NBASiozt0C-ww=oN(`t3jG?Wh{l_$*h5|-+dWE@jPqGr2uE|+|jOKZMc z-XG}57P5-D55rO7ZH)8hQ2{iBR&4#(tkzcT5#auD?qprRv8g zM*HXMl`6fK*V$dDuRs9gPFVSc6TgB42YxApF@5sLN1qcAQN0&HDPlqpr(3_yljwbY z+Ef`Gi<+lVIwE!azdL7XRv@-37#RfynH z1MJbAOB&DHI{yFvc&Pv2c&L8|U)BQs-#Q*DL`W?Flv2Rl#Fsf9%2Z383A4s8gx*d_ zTxu~u;F+{x9-ME!tq%~|geTCYC4Qm<<~cVPcS7FeJHDX>dG6iW@@SfAQ92nxGIu&( zaW+XVvS~RPp)12|lm)Vili0z1^^mPOi?|?j;DqKVg_#L=lZL|V_Eg{7labfHik;xC zHRVV(Ax~9sEW9UBtD4{f_InNpg>(McU#>Y)O2`6^xH`6Z!+q+HtYOx?%)!Kmum;m1l+4&yFYzDPS}rU#G5J9{Fl=1l!A`0XcwCDFz0TE(C@UHJ79!=1~n8Akv)3_>zXrFQ0T<1>bvU#q3~yO{4xi?O)YRzA zyuu-RUvv&1fyo^W6zAUN{5iq0IKhAK(7sG|zTn_`?q%@T#vIv*(R|`>)$_|CC}(nT zNuz{sjxEf^#Dntd!%u>X>|w-q_zwSq!Ae#DI;2P{YSY?t)L69%`t77mQ@3rCYi1CcLu&bd)XeQ^2%v#@ zjR)j^eD1yMWXg^KH@*C=deG^995LZ{JYoXEkgg=RPJSaplDFvEp-%_B)R)O3T*7A~ z(-E4Lt_HLGxl)VQ)@7^m54f!2hFG>$$~YueUXQ3Dfz;~QzIvlGB|02xUYi zvt7<%==s3UcwYn)b@5kfr*${#qIe9%4$6uD0u0?5IG))$Z3hTAu!y#$(VIx_(|+Uz zaz%A}8VuX>4FKvRcE-6{RAMS7jYMf?o*{ycB#k?9LsGR{{tZwm&-4g}FubvvWyX-P zeF=}-X#Y}=)CmbQmU+bYk{W4w<3j%H#J;v{(k%VZ&Rf_eJv&us_+PCT=t-pe$~}$^+sn05HLgrJK?uE!?8p`8MIDmbA3d z!-ZKpT~K581y%juJ=$?N1kOm9V8jWHY`pzG=RBcf7u_4)k|c73);LlW_8F!*H%7K= z2KT99r2Y#$fkmo=h83^uKjMC>ATdGTc0Ec(<0Doh>hTDH&e(JqcAm-3arXZy`k$Aw zjAAC=#BdpwHdqpw^yyrW81$ch=-eBe3B~b|`fgU82Ez;^hS!K(rZq^-C}n3Eu7V)a z9B^(>PNGmwoN^}GxJoLsr_5uTTPWk1lxE$xDH_ZMw6(3Yswh7u2MPSU94N$@iG~W? zc3A{4wCHp=ysc+>GV>x^6>Y6vGHf8W8J;>?tTNf7AC#IZ5LbZp((%FR8*+j$JSc>4 z&eqYGnG~c=?DN7jT_^DQ-Z6bfOzHDMN9X;kj z*r+-9P}C}vm58r|?JRtt3#A*Z;sUX*@rq7u?BF{GNMbKnoh5<72uR3@4aY*Cvg8CF zGUry)YCLod9gNsO8a*NRcnWy(Q6hyBhRZ@JDY~>9@--h?=JGnfbXD9oG{ZAWz`&>9 zrJMQ>XyQsT<_L5Op~P^L$i0+*gCt-j!P#|}*Ew1F*L0`<$m??EpKwnwjM#xN0Q^a# z@LHDSAHX*$=VJ+*J^hndHc+B9ccc83+{o*0kNl5wB|BF!VUN@zjp^BA;U=v092=X@ z1!a967vMFA3rfRG<^HKkp3WUrkxR;iYP|esQH?lGU5y1#cckqQFA2`j&SJzXapNz@2 z=D0~PBTh-#d>cMmy5JlAhQ%UqM80WpMV8u&NE6vJy&OC$DAf?;ABttqhxtY&hVKKj z1o97uAJq)!N+EWZY_B|E2sQZ>kf13lJ3x=Ivo(ALYDnhyfC+G?4zgj@HP`VrJ5J5k z{+B8hIn6!zg^46|zc3LCFFb%CLf0(*s%11q+bSTS}=j1E)*oovmK_9l}K6>ecYy{0j25LZh;A(rl_ zIXYq@Ou+m!$twaCW2+4JNUYHS|7nvP=&SV%qktqoY%A_$FYFMB%h!96|fI`e|aWQ-^ zLtl(xHss4$pvppY+)9B2h>1_Qj(B}gGBBPkI?VQ9K(F=JFu#HEnxZ3Z4@T~bA5#Qy zB6oh%Pwvxd2-3yIU;C|F|M~OVPJQyy!dxNtiX;m_oDYST-yuX3TZk_iH-pT7@bvmaPJ!br( zM%2q2VPdC9LIVOZa>?dW)}*{)1Hn5vU<4nf&kDEv7_47UHKL!C?z-8|J;ped|A5L1 z;6K!Xe=3M_`mJgx^3<8!5|xic6M1G$w+XXKu#=sZvRNV(Y0fJ|X_4lF%;z0)-HB_n zo+Wm9sWn0-{w4Ao{Eq)kCvag(mnRtmbqSzg$wwgerO zo`DQK%fSG>!YL6&y)s3{RYO9P>Yelt?CPw0p8CUalbv=Cj`Tm<5p=n)-VyjzC*$<* zI|3)Yt1u$Ev#HZSYx)FVg2DpdNQ3;?5K?0T)q!Z8sf}oi4u_5`!XXO#kDDghDncqE zJ6oHGf-J2iA};QYoUj*dkVRz5W#*7l<(Y*H3rUbjYZPUuIcIZOnge8a{d0cy3h$qZy|di-Efp}gbPO_cjWH__|qo=%Vy$w6p{ zIBw`RiFG-WX?1DovJvuJuo1>_!A97f3xZ*c@|le=k~>)Va4w;Z@Y=RE!tjvH%|w_= z5ysCPdBXsCbX?2mTU=|3Y9A7tRBR&{R{Uo+f<}2i9Cz*71B#gdCH<_PnF}``3b#*7 z!URB5xP)yjn-RCFbrIw;CDt96xz)QI>57E87Ah4VVUHNhSdw>tal!Y0efq1n-H*dl zNxpr?SfhPQ&2^<={HrYXQ4Wr*LObfU(My&^-#Vn0W*dm|fVC|J* z1Ob+rw&A)l*5PbIm|~~z&UvV^keD!F>cBbev?wK)lszo(tm{0i2WLJY!9@%CyBKnm zM#1Pinr$Mf>A5PyvpU-D(79noK%D*1~_JP^La;uDQsrRkmWafG3AY>7l%XDNU^p~_!D;67X3kYT~mr4J#KhUOpP zqAla6Rr!@J9Ng1Pal~b&{<~k{K-S^FG8`gOemU4l>PQl7Nr;L=6hVE}>a`gA#_=oJ zoQN5kQt4UH63d@j^d7}l8mrV!!w2^)yPo=p$qeMUNt@~n@N6@PFYBbN2zUv#8h~Su}&^3+vbiro?P5Goo5YecLh~S=Z7!C)}$(%U?8+r zdyPreh-7CIXc(O}DWf=U1kO&3>}n=ewE@r9)Jt5-X4p|*N#aDgFFbRKSUf+b{X66m zhYSnf#TY0hde$s#Adn?UL^qKFZjXi_ySBZ@7JNJ=L8Kn@WXFTW#V>?;%+J2{kl7VE zV=i&yT8dT4;`ID$;iqbu1i)Xx*SK`6$N#B_pJ)@t%Nug?QTjqcon(ZM!yPZBEgu|) z0bQ`5Mr~Zl?h(={^q7{v6E-nH2RJZ)INq|%@SCud4O)jX0pj>o>&DBgU_qW%t*IxD1|*rZ z9RS&>)Lc^UL!E5BSO|1|a*)KQv>YFT>{i)ji^*?5q_H5&k(R7!-+d`OGe|Dfk_X#m zBWlf#_f8J7stt<8-zD1&lk9Q?M+vvRMM{R>+9^s~uQ$6zB(x zjl!g5hf`D{I|4WGDm#)9%M=fUzyyCG!f|#jh^eU?0-GSMGCA#95roAm%55)I37ckk zyD+=X3(D&0j_p&j-31B3Lc8E48o0yOmc0YhQn!2^vtqj_q~qE#4)v9&NixoNH_j+N zF_MFU$si6~l}CTgj@9N?Qi;V{CdQ6EBQ83o7-$L=385)kDG@FC*){z4f0CBQX|!$k zWZDK)2e$|;$N_MPyIj>m_Wi=%WuY;=>Wu(gpK$iE#H8?m6W8qE!dO0L<7T3dL1&u? z&O`nPrGxb+3y`}Kie`PJ4uEXSPR8?y$VZ)I!4LoQO$cJkPvBhL@@d!+D@5D^QXgJ< zXg!w~o>AafEhCjvcTU-i%bTD*3dIZiei_F-p3p3Y6m4uL~!P1)hBT zT*klu*fWoBIQ!SPd=OhVIv&`ozy9`u_rLwbZ*F@-uXK(wc^Y&X|N0vjzyHG9zrO2j zeIaZ-uReV2S8HCp;pN-j*DJ;UfTb|?k{!&g0zV(*UM2ERp(1&DO-Jf!3Es1Mw9{Rn zT(S*}j{$OA0&sy#7XHdM5%rHTCsfJ!F{iD~rPs0QC3^FN&)&_ z5`FAYWtce~eQeKJsJJ+%DpiHnBDKs5(kD^6is7C*Jf`lrUAG}~i`U`68W|N?5FDy* zTe%E}JBqy2rAn>z^`xMYAjo-;4mZ8|NN5q3NUvPF&>TO!65)5YA{8sUi+nJZXx&u1s91pf> zrUunlf*pUNT_)GLy5eA}ga`r{0+O)No#eemT4BK-Ql524LS{IecD@%l9E^lx>&4;3 zZ5)TAzD_ut0?8C{I5{7jy+yGiU&s@lrrH?}#g~qA!<2&ubI!*?2JEQ;d}wa~T<}Xa zfIbvAfDWqxTd`Qc@8()ub0pNnV>5sBSkMqK#*Bzqjf4o9_3?^V*Udq6>gyhhe`i0F8&dAFq?7vf3o0ZX8__s*gA?vk!)i1|DDAx znuL^VX9WOb`b4L^0gW$M-txtjb-KLeKdmc6n?U|(e~h&&zxLdFZG~dTr{{-ynQ&29 zS^rr6s`tVPf6cPJ@u7RrTuXj4593#%DCghOU0d5)h)WJf+>*R7OP=lQ`Ayomh*=9$ z$$!iT=T&(my2G!j+x1M?D#hj0S9sXSgBg_h8}wRdq~)Jh>etRtadEhD{t>>&yuC|T ztHp5gR(mvG5TSM~P5AJ1nBjr;6lUmvHu+8AwY%VN-*fiO-gz(~=%>xct((bT zOVe|y{FQ1`B7ZH(@*mJh7=qe+sh-zcdkvVDTpym;^2=C$40lCrDq>f5cs!E8CYoSmdZS%-oQD>+ zG_4)@FlRL=fRU0?{00awY*{*wMo|>&5UpjB9yWGr89XhWi*;z3e>_wbwhd|Ed<_f< z72*d7+oSHOx6-!o`?wL4pcL=21_sjM(Z)9|ljUH#Q(J&e{(M)R=B}B4o+tTo z*9VSo<|LtM8coLxhMmU+In0PG^WZc)jw)=>F$Dt{g^AH(fGlgKn3^P`7f@SWJy16N z#q^*WG)G#4oFGaRMRBK<>!LDBoLq})!8qeCR3B_(B)cC&mz>p2GPOd>s^R_Yf{D`} zbPf+gtB?@wKx-k8L(HF$T&#~7NAmYpV!u*~KGP!gIkZJ`P# z4NZJv9Z5>+s0mp@*+ka1MG+j2jux6i?k)yg zP4UB$BnM3swWKDLP@p6JL#S0(6t&4anP9aLz6uaI6qz97+fF9XCYPJ?Lud_hunLjT7fU8#67R%oiKESdR@!#Jcc$Xf;0%Wzj zL(aSe^pF^;cMhEN3(= zR2%(;TI1{*M)N(Eam&?4sYF0BoGp=W)V(Kt$wEHWYQqw*R4m&_;U{^_K{9f&qn!Z~ z%Wpa76A0OVG%uf!^bz@mV1P$cmM~fudZ{*guBki)p3FmT%v8h(vT*5wRs=EyV1%|~ z`Fq_!1RmIK4~E&H9#T;GuzA}DoZ>ZfNNa$`!Yzx79}Tq@8iXQ+!Hrp4>csZ+(qy4} z>Y46l9y2S~3CVWNPTF2XFKXo`HC;j^4xIn@^*rFc65wi)I~E#Kxtwn)cq|k?O9$#y z^>aMKxQD5ZpyUE+eHL>BbdqI_kdZYHWXp;Bq8HTUTTQ50PK6Y{GJH7ts5De*KnUJ_ zZq3p>Y%mSb)WVK)s~HXyj#ePYanUF-a-c5(0swuzC$jg)0eu^FlNPiQX%Q`U!>*&j zl2-*p@yAh-LlyA`hc8B_obKwlg9&A{lVWhnsSP-hVg+#07LH3(g9RXV^T!2|1^K;{ zOs?Z~UFCPcJ`S;hF!qt>WxQlB7X)Gr7H^~-bW7d#uJb}L3T z2W(mxy9eY^=%X+=QtfEH^bDtHS?EpHRZ5p|61ccJ^;$5H5l^25Y#K|sjz(xgNWON~ zO{g-&dFk*9iY9s%t`{aN{BaQz415_QhC?z!oHZwtPV^{8oT`!BurZZv50x30(QEg}dYcQF40gM73Ob5#WO+~HI-X!zGnVlpf3Gy+37*o}n zTSLAGJKN1zdYQ^+^PCw;9pq8KHp2&JKxxhb9JTssxAhFuT4^jGj??q$39o3m!=>mm zGg+D%R`_oj(usOHAfk4pUKq!AqyRY_p$ox7>r>0wiwRwJJkz@EgpTfM^kgPEJ%`XK zTBHrSiO|_cr6*8NC=clR)yrmz2wip(vk%xwF=r>ib<9pub1iRgw}eVAg-!?^+6!VB z)FGi83X2HtqHbTrBYaysG(;uMl-W~xN8Ur7zg9J8Qi@%qR<2u%&s`-=5!UutKF z?+AqNwn=$SEpqXcoFh!Xt#0G>Wa5_Z(l-j|Ow=J);^TrufdcFlY7=&N+ah7d zawYRBIAND?!j7`3pr}Ie0o7Ze00V~5*|o-@v*l1yLuW?YtW@LOa=Q5AEeFhaUa2lmQYv#?iI8o)n4%Xhb44t@3_h3S< zBDN9)2muB@=MvIePVSRTg)et>9wNu3MvLzd4%0#p(>dV?3I$MwUn@b+k+2&#}&gwGc?b z?ci2%F{#Khr4Fg9V4V}zTFn^>Yeoj=m>DxxJ2kaeD20+HnXslfX1Mg&K8+OVo+eft zj52PAf0~!JTf<=ng69!u*@U9k@~m}e&xI(EXqN^R3zcb zJZtjY?fc3sacEeKiYFES6;tL8ekQb?>gQMO=%zrdG%kz|N~vX)%R^~l9G%ttVqq@Q z)YOFZ1R#MeD2ybFF?3oXmBE)n!TB43=Vl{aLhGKshGvhbvfLyefCNNZlWEdc#VPzr zN7X`ebcwR}Gws zoN-21IOL@fva`;wqY5nC?i{DQAQO^=W#Vwy0-n&dOh^|-;5j!_N=YK&lkzLw_I>QI zrFBS&l}imJHJ1C=g>e;GrxbW{B*Zdjm3t*{3#p@MuM9ABmH?zKs7H zr8HP1xkwk)x)x<+d0}D3yP!1qMM|sBxD_j{Jj>-I5h^tpX&@jtr4ovuhLj+~lj7>3Q zhpGTXT?SPpT%*r!I2q8e$Ifn#*sW=o<~tQ&I;yR*=2TDt0Mzu~P+P6w4!+e+3I_9x zsS3v8dqPC+I;gGNA{_pV&*OCM2-k7UKqm5ZsMv4T4Yy2f{SR^L->I$4CT@*ek=oihZgtl3Vk8OGR$&nX8(AT; zr9&qx3wt8zg#G7{L~5%YacS@2X-;i5&4W(kWcAFA1A2p4a_X3#2H!S(XB5{r3qA=))dFE26 z3>T~KT&$oW*t3n!%C0yey6LQH93t^^bXHYPMeX&Kht*OaMN1b@?PPataVo22P)flk zlQ})XqFh)43v=^!rh^nt$=u$iGoz5&AXwz-Jge5UPJ%gqY@*@y@%%XE{#fUt2&94U zHm8}EnPn>n<*7{W!)ieMV#b>9!Cfq_g zowpmqLv=1@**7=8r>=pZ;o zhl3=PM?aj%if4WqXUTs4UN-6rEd@w3D13sTXnC^?i(HH$lpyiYDr9+p2A^uiUrSeH zV$h&4y;dpMrw4?l`sRc(& zFyi&}yWWN84iZXlfnM28FH*-2QF?&z<j&8fWw{?HNZZA3~IhDA7eZ+`^0S2_F~~* zt3VF()w*L)hcTFBm+Xphv7FYSL?dK_w@Ub6nPQnl%!_6AwhbJC_&f@`mKDi4sf%-t zsA7*ST)+s(N#~BB0YSv<29qx@o_Q+eB`nq$3GDP8IR$Z!s8sS=aq6zJLu^(Ul~5)y zRZ66(?e9>^u-m>%Y1XC&yOWgu*qB(35is_$>{2T3EG57euCRwwUK=Z7*Mmi4f0ME@<%J89ZsK zs>=c??kXU;U|@^_cC0Tl}xVk$uAk0tm3!XmoD0aQQ++eJhv zVXstP%p2v}IWU$S9kV)y#VU4V>xQMP8XK#kEHhqZUt}N)Dqrfho2a8%Iw~n%+~t&O zG0PEMi#bRs$q`Pp7{d=SM~gX^kK3xS#TC}#t`x7LSa~4U!axL`y3PPFMwl1j2s1kFI~V&Na-fJx#a^x~Z6|0uco7=5)gJ-%Ltv7r zzBDRZIK)RsKoXTai0?-xr>$dS*AXoQ?IZ^nN^@=%y%K{pqb_QL3fIO|aSBKHq!zzG ztst=is;Hu3W}(Cpm$1a@UdP1k{6dK%Mq!Cz9$O_gmSMaCwrjAYQ7{lqJ^7%%P0=~- z1uQ18uiHR^J|T$@IxYxND~bab4<%WYRI109Oe*)*mrSZ_h@3D{|sq9VkIL=f%IQ~}Nwo@G&4N_e9f8$0PGAb}{n-NrsA>|yXjgm5*=J%_%Ph@h-C z!W~%z#S*lTHPUaRgARRE9!**)HyRujMny=<(B%kns#4E(M2k2dKn;h~^h^Q7@XNs$ z#!Ag0d{f8_8ZBRm-|Aos7KI%Rv57()KR1!8K6CmQzy+M`Y3&ZpAMT<27Tmz@%BAt! zT~-rYcGt~C1R!0FY!iFeTg%2gi4Ji{lOV_w(;^+9p`{r3O!xsO=g_#4qGLJMZ2=g? z6uRw{5TipuhVe2`vzn#Q@F;D8IxWAEp4}N;*&WK9b&Io-#=>sxUCG#()M#oXo)<1z zF*zQM*9ncH!6l4G+~U^LjC#o+8=g-yF(;pdKHLWA61o^)z*w+HMqDUtAI%{z=>#E% zpIOyZNSG=l6thkg-g5vw1;7JYatbMdM$#t~rHlXz=|#>LEhl+W2YlL+ROJqpM9WRj zWjNYJXp#;;6MDXou$QW_6x!QN`fl!G%7MmTB;!hDa<_0-Q@HCL6~$;KmgZ|(axfi` zLFLA89KTvyQ%^zqi0(}#!EYa2vx?jIAu%K8m%FUZWE`#(S0UrWTBnHSnC^4-YF;L;>1#B zueQZlWae;BGecm~y|!#rh7V~$Bjo8`OK&`l zoN?Xc5~r`N=Sz?{-N_8GVBViz?~TV|Ga2hD^i}LgLiyW*37c-B0x(l&Ndb8ap{iOW z+``TG7M>w<_o4A%RqB+~$!vsvO@z!!2p}s?15t#5niM7fP@eI$O!dco>0eEMxnE}# zxb+6(S3kz~{PR6}d_HGs4PbRWL#ojf%XSsR#bt714bXo7^K*d4g&h8u%7F+iy(I+7|c1F>s z%IK4p(xHzReX5E+iAhT9X@_Mt`ow-}K2;Zes*XO1d3yOSMW4DvpV;BUr>;exx<;SM z>{GX*Pu-$Vj$pbMed->4(oD9O?@{!rNA$^^Fw(Q=Q_tv=JG!K%=#zqv3lA&Yu_L{T zKJ|(|Nj7--+M-Xj(Wh$rR9Ez=F8b8PKJ_m8)I0jr)jstp`qU@-Kp8MgaY^z4+`49^q-G`YxfFxVk1NE9Q3 zPsUut0-#kstdo>GcA9c%Cn*PZnsP`dDR<~J<={?IZr^Fj9Xm<6U8gArb&|5a)06`{ zN!hQ{lntGv?9*w=9Xd%_*J;WDouus5Y0B+8N!hd0l>IwN*}c=0+jWw%Yo{r5ousVp zG-Z7!DXTh7$wYD#rMiHh6`iK+*GWp$h)yuMZzm~BJ5AZAla#4WQ}*s8B~G?ZYOm`g zC6<0CDQi1P>2;b?N9)3@8Z2t7G%3X)l{$+S!v93>$@Z6jm!sl&+rYW#xxOxh-^7ZV zpsyoxIZ^#=JvxhuYw$1iaMN@s0vXBNh>FR8KOoQ4Pn&zJ&nHZ@*jY0$nkbFVYo5rd^SuFk+3@XHaXqQA`h~8e?qo zAPt#2i(Z1Lloi-b`c^kneETW6eT!%6dzc)KeL?Ob1G}%3v>t#&GXz?9pnJfBuMa&P zm4ndKU3rq-OP8Dul^7I@)eo^qJpMS-L`bCwgauRvkbJ;yc2g0l#JITLu(u*#zDNdw z3q7f0C-;u=Q$JA93_D7&H+gI|+oo1%b|O}vX-^m41r-1yA=@ltj*o$XH;6_K3I<(t zp%ta4fx$p~N-rpWDsO+PC!73%uESx-7)??UP>e9~1QhcyJc`S<9P-P7hKzzUKeWG& z#KFKZpk)|hCaLHZl2UYqi=qqBk&>t*EI`2GRGhV_Ln+FXG|QC~b*Ru6-B>qtV?DU= z^BW(wr>OBR@mB4|Zs|6mqP7-2 z_m(ZIR@Dz?Xi+B=zOcTF(BB_Nr|~1}k8^~zkVuQ9afP_A?mJyd2Nk&zTWBJN1FCdU zRBFOC;>}bj(I7LC9oP_#j#4oHDaI?<%;1Oar-2s)!@hM^F!UJVTUU}g>cZaJ>7mk_H2~FFS_h&t~<1_V@AU-%-#aMx&j{JH>B}BuqBmIo?9c=AY zUrkqcq&98uqh;JxF34rLgZ5Oa-cgjU_QuedH+Ho*HbT6y>qQsVQ<_F89#q4Ia@EG^ zszjBgK`)`DcG0o{sgk|mu5MMKy2HGT7vD8(!~qnUR#_2)z*$+WcE>(E-JH?6uE`)r#_`$(v_BX?xk9 zmuz9%oeTbuF?{Q`K#B1n=6f%&_^}q6)FT*n^nRq=;jRdUEnrp1`P9WSUg{CIRKVHS z+Neio6Kt}Z8+dy!PjV31OdCOr>hTe_Hy0)wE`uMKF1)DW4Ln)#ZNca`_!Ksd z*ode=@)P@N)ZqMPX8(Zzo}qjjz#vXz{dW0}dC47w&H=FIIudBr8m5)XD;TEyrCxER z3@Sc9%209cFe#O$;IWr_x$UfR1Hpz&eU7{z0ERI1u3+Z}xjp@%6M-=!{-^$JHq((?66$VQEl%y#SVKmQ1T5QS2l5y8C-no8ej*N!njZ8UNngMdnf7OLHD*Ny>3uXXP_L>OJqJF^<9 zz_-END{Sb~(MwDT7@Goz(mvH`SY7^sQ zV0^uo+g{i6`7prZgZ5vS+d=7fAjkHK!Z~1FZhLa^(ZvVlc?ad+A;<;&*I6nB3C+1Q zC*>$$dlj%fpK=x-G#3nrK5oB`YREjmK3cw@fAo=@1C(=sTNJvL0x|wsEsqNcmJ5r13b^qzs;tm zYOU%tX(u(B4_alw%S5g+Ao}itT+cjfkD04l#ABKe6P$*#f@ShxXFT9ulX^9f0P{v# z&}Okaw{VDMg1r`TEtO9#fwGQWK>`JC!OB7(BAa-gbP}qH)t6CcBIwD4SU$EdQ#(=} zV2D}?&1ESxlyJG(`5#xsWt#sX6l{!+6J&2yXSR`o^3zb`7qP>a) z+TagoV?*WATnc8F0(3N?I4*fEM=bI@Cj|r~Fz6av3?~99PS=0ai7_O#6Kijw+8MrJV~!Jt8fkVNs7I7poYK~#$&7YWyn)X3 zqx|ZiCMd_2SL6<~0%-$@$!u5Zqp^-F)I9_wQqy!X_kD!h{G)S@0K-uC?dPc>X33|G zu)QT{%efFxi-O%j^}XQ$YGoXOp2@Kngjq0#gN|_68tgw!70mRfz(kYQMzI!%H!XCn zF%m}P2xr}aW?RL*&jls*r9nCCZlH7!O!$U*2yz<@)HA4x`<$bd^n(vjaK|D*QG-no zAsfvD8np`z?}G}($4VFDdwsy%`nEW`U5M%1V7aD+KPQJ(oKsN!+#1@L4z?@cJ2{H* zJ^lIk4x!fz0}L${6&74cfz$S^5q3BYqSOkvg*k|BsU%_ex@vd2LbaW)0E(m-llTfT zqYcUO| zpMQfZlPq%8u|bUyLYOQQ3@h++N zgdDS>$ZdzzG=|Lp7c!r`rkF|CNSm>pj>a0sQnFMK(qN;eNc4zo6y!MWBE#oXvgs5# z+5*UuARq(1B7avzzLJ!hb{1_&c0Ez_VNO_uosN^^b1xecHY0$b zUVg1pu(S}{PCKvPjwB75q_C3;5@jyIlPrzTCTsFZksSmRFB?hq5a zy1Lr4wQc$JZf?H8--ta&_W{KX0s*K~6dl~lb}!F{3ZYF;NzJ4=T6j+mJg>AtUezqS zqvQ~oP*I7Oj0BaLV7uaht4To)y4;E8O5o4XV&PRtcC>tDIR6d= z5Va`hD+SvIzm>uBIA#NDv{lbS7cMh%$YCvz)UHTvfv6&#F3=+L-sCwzXSrxCI{!`9 zy<&r7eQHqL@e7*YRIo~`XO3Ab@|7!2`K2J0JAlD_&uF=jwJ-~l#GS$Lv`3ki^Es@{ zISMSwRzaBwnCU4q7r>z1ys7y|?z%6QXCBaK`F1_5%A=E<K&VO^><*CGP8_DYE7#h z@vGxP>dS+aj$JK=%HW*mx?6*svj*A*CPn6lk;qp{r+Pa}uc2PWK^5r#7V<8BzKV}8fC^f%6co`Hog#-%0(PxKWJ=kEk?*%c5%V+h+@>)7M|O~SaXrD*@S&AhpA65BTQk~a~n|B?Y!1REbb-2!rb__ z&*E#ww>=a)nDaao#AZk=o!@R>jBm{i!h~SyVp%!cXDPD60dMD~@ajO#b{ekZ8HMgFK3m^*66WS=kZzGHm&` z7>xWQr;)^_FFrTP?O~EUc86&wNMVFj{xja^cc8H03-Z@~ll$BGFD%QXV}6rT3<#^U zB#(qiq$oL6BuRf`^&a-429}4Aj0^hE|!niLtB=`aQbIs$HZc>Oi3)(g=_RY zG#!iWT=ZPe^EjTT=cZ4eK5P2i=}V@!OmDT?n8HBj)VXIA-?9(YuVEHEY*l zi{{U2X%yUA=Q!9k4_n+k$G{NVhdL%x=5@e9?fvhTO&6MHTj?vBW{1VEr|c<|c`=Pw z+4GmIXk0XZPN2@x(ZNB}%Hx`5w=(*%*r%keB2C86#xh(dH&1q{4{Mz}X2KFs0#pcJ zjc;yVwACwM*s|5DLmQ8c-t5!Fu+OLNZ#3nXoN!=s>$K&^9oO7K&C_@_W&Yy%t<##8 zE^k^gyXgQxJE?I+(-doCEOvRfZ0so7^ALZ%_?z9_(z5)x)?iN4+(qDCz!)|)E^be^ znKT+>7Oxb>Y)9^gEnd<(Z1%9jXvneC}~ud{@7mjAHW0J zZ)zQOeB-jkJMT1v%X}EYqNc`WO~Ym_pTB610Zd2C2uv?h@RSUzNPW_8Q?uNJV2V>q!F$piTKaNb>`y|kEfBH=A66O+Lg>26i@VB z%P`5egvcH&14VMx}w9G$tUhB*dSIYuQo7*~;xdMAXmdt%ivhY^= z$W@p*fomz(jyaVj%*(eZ&%7}2$}mrqSLLc-`nig442k_jSb{#G-IcL1`~~_s5l$AI zST?xq!M(=Qb`{)`XB~ z>XveOR?#_0k?8q!o@;q-X_WX_*4o%2%s?^*(O|eQbDLX&WsBy|ZqoRK?Dt5@6xkz~K0KAh)0*emZmJhv}?w zm3V2^xLk^>;MbF@@M+cwh>LJ=Ab=&!t&koJvc(Y$0(!SVguRSJ%jVB%3W5e@Sw>9^ z+RWG(%8_K;o2zhOK38c4OSy_8NA;+L4wUG!7xJ$sy~Zd?zYYE&AtL?YY0?T;lXO55 zVVkte;R({#jzg%lO3)aSjeSZ!X*t48(M$AP#=WFrtiX#)meq|!- zRr_6%U8}o!-Tj_^uZrHuKIwj5e{Vrzq5tFf&Hi2fQ~oplbLG#MzTm&;zvR7^eBFOD z@s|HiusQLu|Bv`Tz49G)+w;JK&imf?zJKCb-}?3sZn@*Mn@duqqxRVI@b{m7Cef{C z)ab*HSoNcue*BAFUhDGBGtT{9qN1|8%fJyM$4;0ydA|b>n$tA>XSdb$PNmB--FlAN zb?mj*ZTfZT=<_eQHdVIUo^$7)cVTt&jC8` zryX|qkzbpB%*@$Ma~CdKdGe|=ulmuCH{Sd7O+Q}JeD}AG$(@*tCx*o5#=T)f+fMBh zA5qmi(Z95Ba>wL8iONB3KPu^;=$~jvkIWpn_v+E5J<8HGyG__NK094Hyaz`V)+N1h zV-ou(hb78VrKxej4vF&8QSq_K+Ek)EHFfgnohx=u4NaG=9&pJ1JEjNqs2$L|ThG!1 zsbHUqUa7K@N$DL*muJTBIjCf}WLe3`Sni`atUNWF;Vp&77>!}-$ZrVSwYpQyj zP~wu0(`#QCRQiKASMOZq^(m=Lq*tGPMq**IB3_!xUO02#($?ME{$92$eO&j6-{@A} zt^A16UTxo8y>EQo_^R$}r}ir;X?uRh-s-0Zo%+YNj|NUj zlqLK%)%#4@qwW6POT5Hk$-0sL>dL{1Ipv3!wcRwPPsQLwY09rGY5UHaO^NDwMf~{0 zjFR$%S5=-EL+2XOxdT@pTHc4Yj7nFMv^3TB`1WOMOJZI;nJg*sQzhwCX?0oeOt13V zifmS=*nW5hB#0Y;UZ-alG|D(k9>3{hD zO#aLNEWV}m#+4_Wb>8B_w}{s^6}O??tJL6C!c!b53vHe?x%-|6AAahkmnU8Ni(ftT@rNX!cC1oYGB}b)pOinBtoB%oE zBQk>%wI!K&+YNjgHsXMI+g0h^<5lt9Q)ALQCQsc`U6USGJtSUVRbSP1cH-1adS$ww zeo=B*ayL+|rnK$uTx)sT3$?4W$+lNB?|m;mx^(sQZf&=x+a6Dr)$A58D;blXm@Y4A z&Ge0bEpb$7+nSo*Wj#u#B-+j_x&Es1o{14xCRV>RAXT1Bwq285{b9-r29@yjoJ8B* z@w#|b#pkl{kd@Dv)7aW5E`Co{)~wX2uxnYX(erffB|l>ESZqxqh+UAJ8T)$I^|3BJ zgMQ_~%zl3xyne?)!-K)i*St2^zjo%3zWU&#ej{2AP`Y5lgesuqe*GFGp z*|_Vlnk#o5-n((q`+cvRGHzt!)XfX7JoupI`a`a`>&io7Pc=?!dgjV$v6t!(jlK5z zVK@D*@rbwIs6XoIx2`-Yh`n>vW^dKkV#mc&u^}iKKEK|i%<%46uZbb_eJ`<{*SGHI z%-GUWuO{J@B5sm9#&=H-s_}x+WJshLs#KZZ#~Z8ci8M*d{94cVcSSBGd{D#d>&I~m z@th=q*Uj&N)FL%yr@d6X%Y9ReAi;tkpl($E`f6`0ui63@kDGb}25f>-{ig$$< zPj@%SdR`B&G8Iq$GOf<^6jT{2rSts1(6$n;b^c*#y;&f5@U-$GPKiwlU^oM>|@%~hwE4N+?iu# zC-d6vG{y|5#Z5AvC_4zfF?o!#od_Lx9UA&871FNv_&-zgH|96Hnj=4uv{_m5`!+3U zYH3`wc-gTBHZ{#zwod^}`xL-AK_1S%L)h8_`Sc@;QhzW|vlJ#ZE}Iva_8e^Eb~v=V z^Cx^|sLrF3SuM@SFByU}bmY*T4Y2qvLREhy^~|I_hw~?H|CQ@oJb%gjZ_H?3i6ujg zqlfNf6^2MOOt!HB_ATn2OML+V*_&(h%xscb579F_p3Kt_Js-hyK>Bu{fpdI~4Q9=6 zT_yvqp<%@Gop%a$-(3bT+b{w>4vg|&M}BzFhT|6!~0>PRoYSCqe!XK4x1a~03U@O>dH zFI(LuhL&7{1(+z0SNN5%#wgE4o-XlMb6WamKWx4j6yXlxZ_| zD&+n4R`p%MbHAeYyvcL%_JUfx&x zt@XazZ=LtmetUag<+qO);X?^-ow`{5)1M*b{!opIVLw~DvO2h~I(aZ})>rs1`M>*9 zppG)2p;-jx>$-O9*1dZVetPm#6aMrHe`>e-soUzO_ZR&1`GTKlbh^+U`ESwG(xv>A zg+H0Ce#*b#r{XLA#8BW92{{}}N>co!!ymOtKT*4)>sHOnd_nWdzo>bYU-75vEB;_m z{ICA7HuZn?M;Iu^5b=erS-=;O{#PY$;obk{k8u9~%Rdo6>J2~A%w_bvok@@O=V>AT z+*X^KGxWH|md3@)hR$8IeA&DiElrCXQQn!<*kg}ic%i(xbewjRt%fiV} z6#pq-o3YR2y$_u+ZSvP96qU0nEGH`WFFb30rubQHi=G3twPFpqa;xY4c$Pfcwl9mC zn-@0DYigV`!{%q{53rfR-1}lClMCgaOa5YwvYyGDTv7e~c`nva^;~QbJVyRWMd_d7 zx!8g@ne>uRQGZV1d3e$Ddps-u|6xndEs((0dv3dIY##N=Qm^MPdY11(nznwtSpIL1 zpzS;}DEQ809e#RsziHpsztOGzzu)#RS$*-|XDxd3pVz(n>;bc~-+r!iUT)FKZ(Kci z@7{m;-Kg5@_g->Z-R4gk*KVHo`_q5=akt#4BgagB|KVFlo%7e9uK(zv?1JsixwrC= zYrc8Jq#xh)o%dd>J^i2;=U$z^{rj)(diL<&oqIy})HPlD9qbMH<1H7xFyO7s(CVv7 z4}N_0yqjLQ?T%l(`0H0ss{8ZU4L=z9*LQzAEwgHTUmDN@4L*q;pkb3w8}*$hyS9~8|MJFZC)}LxHFN6Sf8W^e z*mSFZ)^CQ~ckI4Dx#qKDzSirI^#gZl-q?TSY2Uy9FQ4r{soTrF-Z}rhqxzixy;bwh zXnJAS?2LgMZ|(ETe@?k%rw!LWzun(^-EzlKU%z>mZ|`x#(f#f?{!b5dYnb)=$vYf# z{UwJ_p7PHNzj;Nr;);2XZhG|k_cm94#+7#+{k4{%L%y|kpYD%O z_~+0^Y9IUYP3N9<=J+e()e}lDy!o1sesjxyZ>;>$75^Mlm$)*2-(M~~`NJh^h8%YD zu!pjY}`|3?EpT2nU2M=xkUS^Ni>R#UNFB5yD?mqLE zWBNAy@LyAUO#azn&sHz`Y{}&__n&sqh!?9X&-~lZ4(fWz%`=aku&(CRvwB~B(0ZvzW>0I@}G7&eOT2duY^vYi{^a;%B|?yt-}Qc{Lv{eDL7E z9Ddjp7j*mf+E~e7PZ+e_GaGWRzH)MF_VComAHIIxE6=Phe`dtDKb=-PVbR~;s$KTR zhLL-0_+X!ZRdxT~;U7=?T|=K~U%%v#uP?ad*V$P|ob&Ve%Z+~-`rg!wv-f;h`__c3 z9)4=lfp;Bw^h>W?({w@iVBwlYzy7bKvv2&}>plDR%~$`b`ujIFoYwpMlM?aw=gj_Y zUFPMQyQeMO_=l1!b~&%lpSt(>LHB(huR7!XKQG+xf|fUe>#IlKwRGi_mv7%^;*aXq zF7_t$o-zBrhhFIZa6Ea~{3ZbQ$j-##q=WRH=n8h>;CeU+O|>#}6u!*@Bk zf6(MV{N}tj&c3S8_Peh9Xn3EtKYNRQ9`t$fXFb>cWAG>MZ@lczk=r#M(DR0myX^YW zz75k3oIc~wG4r~=cf;5E4jueoKVP0XdDY%Cr|om}nUiXc-0n9IfAswD%bx0W-Kdjx zTikWPv!Aq_ea5Zl-f~{$yPxdXz2EKmq38WDHK?WGn#>0e-}Oq(KfZJK)d%f*=Y6O3 zePMdZC2w^7`$xk|d)p$#LyW{bH z^?r2O{9jk!*88KMeDrd+yB<8#JH6XGH@055@xkez{5U(f^tbb_`RhrO5B+mn^F<9m zsqO#V8`Ew-;<2&W!@hIRieP&8H7ok;J@bU!W-a^CO>rwr^Up(^6+z)rW;`Z## zJ3YSh5p}EPEY2VDz+ETLtoY#EA54F>`x8Gq^VpZ?|Ld_o?(xe_2cEpD`qeX!Z+Wn4 z*!3?COP>42&@FXs&tG_SV%dAA-*EPVJ^S3#)MM^G<0n7e@Zo99EBpPRcFVW}Haxb^ z=nHRt`q3`?JwE@VIZxh{sa|sA)PruV>bu+S?>snl(^5d!1ii^XQ}Rj(+Kc z(#O_)@bLXRjoYK^%j5ra&)@IA_PXXf%D;Ep+(-KU{=0WPvEx(I>IYnW+$n<(SzmMN ziZd$w1}%}-qM!k-S=)bF-ee);{DYrBovp=|#N@4S3MufeCx?{)ua*`qdHSN((E z&lq^##Nk!vjapj!jjnfYSl#<4cU-;j{Y{&W*`fQe>n?u#JHKz3b?_PI-T2!6Z)bZh z|J!RHjj6oit;#7s{>}|u>rNPSRPDEhY}z=j<%R8kcldSPhaR@?q^i45IOjL_{r0xi z6ArCD>CVhcr+oXo?iXEt$=Oe(@2IW{CS|G|{NlU4ADguI_@0;l z{>&H7IJD-&fBmjUIyL{hT^>qa|BI?a&v|Fq!gtFzwLaK~kgcD!ta$P6vvcyM$8@cj~@5`>){J~t?VrfA@uhPdoVd zr3buo>*K#}Svj!!tt0;Q!drL0b>hZDZ#Zx5Im_#aS@P<%o$mPbs8`M?UvSk&-P^{$ zS+jS;BeQSXZ^?1*{OQ8#xeGS!Fs7oSq-XVhwLSOl+xsu|H@-disy!BVX^o7$mUYOQN(?iWEF(jx zs7RDTrBqaClc+OG(PbS>6})E-;>q zup@~_cW(=x7dE@$-Bq(s4FhZC6>q;%zu?R=+T*>haS{ExhSP2$own@TQ9;TCVc(xmJ3@W!oEDF-~7#9OKk zw^+R;xKBSOdA~8OMDa+D+ydTN&ts>Z@31sG=ppSk)3KSSiu_$5Ksw}Cw-xF5458`y zYOj76SFUmYooa}G@=~N^hm}s2?|Jo_U8WzU#hqqQM0vL_cs(-s!-`%{5;GUtupr-p z*>`Kn&wQDE_->7vIL430sG}lTIU_k)GVf*|+4pwjVP?(YRhIPj1>}ozZm(-|PneIS zeAy>M9>dS0SLR=@jk}d~pK{IbqOR-~88wNOyRSbG7VQhI2)s%zT3buVh;Mz)TA-Ld zHzkCSztd07vVMa`Z_wTa3tOMcI>dE5=-<(=TYS!rIVQ45;*pw4&;fQWk=5;whz8d^wH zADJeaZm_0j|JR3jCmDTtrCd|th#alOUFz2DLgeL=)hBK}zH}_JeWTaC*AMY$i0NX_ zl(bfL

=I@Oo_Qm5&f1xn?ePZkKo^f3IZ!d&#YbPaG{?d-zmBa>Y?fmy-AiffL0m z{U0RF&8sfnZ*)+>^FAddWBv0y#j@`-^3G(b_~RSy3I?xBbe?_l>C4!?;={r`-7}&! zv&+K|&)ws>RB>q=&aK#2S>*D^YQ+RRS(8eDt4B-_MRb!;|7yt|jGLOJ4c2 z?53*L+L`!I);m937x;ANw#E$6qM8wx}(w;C^-!Ky1-bz5m0>F%2a&Fm3-p>26p zb05n_N;I4G_Vaf~2- zV~Ntd=}BwqHUFG%O0EhG-TwW51W);6} zm-z)9d$CFM>FHnZTx{lH{W&b1r>~deb_my*aL-e^gwhKfh3C0ef02$0&_ zzIR7zw}_(htX%y@?@BxIE^$#K`6p86T&&d!6S7TSRJ}N|!?8nr>*?^4;JNFU2F#)t zrIa=+7T15g@IFnC>{8`Ym4A^XP20DPzU6*asYT9_Z(V`DB$3x`GDXLq4&HutZk}t( zBD`(B@{0VJxygiYTRSTFL}d6Kf(MEmSiiwfWMuiqEnrBU|^8rXjeBhhbG=n z>ZWb-kbk2YQ155h=8=1x;TFTUT2d~vmsz2naANhqxno)Pt`mRSJYiLr&Ji0H&wP?} zcA7%q%8q(HNGf*+Kbhtx~E|lA>){ zoAb9Z#-q16yZb}?b)@faNwD}amTEo7w)x;enM;Z-+Ld?nPQkt5Y`$SJ7e%}~?*)}3 zi|Fg+Dh2x2pK6vCGrU#v_JPYCiRJeP#)gYY&vG94&k_^1Af9QTt-mY`FM7F&SR~TV$_dFu~Mfl?#hsmcIEzWg<0uEabz4`d^xGSN;tZPl<451S1F_T{H zV`pW*X!ZXxEjINC8{4Jud6TMy-=_iXUsbo_(#OQke&wqrXuVt1yJOZfrG;bb&nZ1z zF89e(Fpau#O*M0DY)5~|8HumfCh*2m;Po0k{pXuM%pndQ`aH}NYGmfS>HUr)WMTPC z>2GuMFRb|08FnewjbJX>wr0iVuAj-;ed$4dma%(?-aAIyTEFM@ta@e=*s{4!{$%^@ zgqgb%H(gz$*>CaSwxlt@{&A#o)osnfKTp)NbxOD$Y2V-@l!M^_z-c=7hsl1R5Z`;x?u><*pDJQZ-Fd%L~Nn&sMxsWu-hYA#pS zrmiYHML+XN%ILsWT@Af&T1HoUFZcO=6(6Fqq26_%lC{9#=^5lqJ_(50g)JZxy5mYk@KBwrfr;k^GSK<*IkCa%7l4k4;SPMSok^^cyCs+h?2du zQAL|7}Yp`eiq?={)6?^zCoL(#TlyHxFI1Y`bJcL z2kGto2RYx}j1MMA2+J+ZZeLv+8#nW&rH0f`Lc&emo*d=xA1>M-4(as0CZ{Q|tj1Da zBeUsYwW7-@UP)#Cb^a|$-mFJr^LnE*Ly3ZsH}~2%4JCg#OR%63W91Lk9qbe=y0X`& z;rQqg(HzO%+fFVIHsBT|<`3LqfcFMj;!uZ#N}DJ6Ca*ISVEH|nh!O$wzM$$=WJr;9&-*DYsvlci(b%?nOyi~FT-3Liff z|43g*_rWbzdiw=Z`1ApVo2%6dw~Icke{d=n-|@VRaoEQS=OB50Og=eXM*7X{chwy{ zErrB@eHFT<$K}WZ`Xbb z+&;}B?;<&U#E^ewVtP|n^$bBqM<8KU!zDp{_R+ZRjQF+)=6%_@hPT6y9j*EP_TkQx zL-JG!#p^|v6cldy4~mI-Z?9+}Fdy>L89|4~Bq}Sm*t@QiTl^y5#ZB(5UWaPdBf)1) z5~4{CtJrTS*=H$FR|}Bl6W3{prKAbuC%#V|--4ZPdLoIVzQj(ME%G?(06OKT_!mNgzp?ZF)%M_J z+gEhGI9CSz3tC1EGc@>_J|HNDtI|90wfX^-8Alx*|ZPf|#r^z_#KFsO%v8%0H2K=5t z&Yh||AG-+N8^U8&XZYLj&GGduUQIIBO{qHYEYUgv?O(utxjDS58tR!7{x2Bt>D7EG zCyn2>?Bm(=r+d{iWS>XB44sWcMeV-1T>qu(WW!x)#t@I?zHlNf+jnqH^}H7D`?ri}Q_K=)zO+rDz```G%-0!Sb|yb|LlUsu@>CU#@;YC*z;XR0{3%6rHJ) zw41c3R_0yf5X<)Y7geDJQ7qDzR|u(sjdABUP+Rd;WYqPju;Cx`092**N%+bSPOr#3$guuuGEk zxi#;E+U-{0sFCOSql#WAp53<7)_%aO(B9^ALwEOE@mJd4y_X($Xk1D@^I3jHw^ZA& zssSCs@--DQ1<^<2r`(w z^(QSR#Qey9b3IU@dUc{)a>d{S$3?$PMK6vL10Urbe(6`5E-$^N@xEG})JM(-+I|rt0mdcrYE{PUUplF zu<7n?VL#*9qi4L@#CM2pc`%hLerd5;o5su%Hm3YEV?rM8&;h?l^>~U*- zv23tVw#%IIn~`7l)~9vV*b+9}{?y4@V$!6yUVH9yl{_*fN&8u5)aJEgD}D_v{`gzQ z;8b0Rq<}%Wzw7~{UF28ztJ39EKf#TAjRS4?f9xld^#Y`JepytMd}EE5z&V~}GK%J| z&wt!eW?hW%x<+`NfS)Ton`n$1bbNP9-N0Kqk2KVBvdw10iI?4phF4E^T#`N)Fi&va z(3Tn65m%Qq3rpa;P0rmijL>K?yEUwJIZKu#f9tTRb$a}U>87V@BV&}LyU&XVk-~O& zI^FVBn=TfvnDu#DS{dChB3th11?Jmg@t7fpW0JM8iz;W+RO=6CN@Yj9$Ue}&Sh}!` z{^``VQwqS=wB_afem^Antdd@-jb2!i8(8@? zFz%bYm4oO!vjwp=1@h6i{HE6ucRs&g-RQEohfl@Xv^q0XGTznEa^G%~iW!-Isgoh{lWbgXn zEoID4=WSmgK9A8HO|Xh1wNixhM|DJ>8e06hL*BVln*VJJk3}7UmU_p+Qa1lrc5ww| zmYqa1{=uO+LWGo``ogb5Pds@(ntOARjw5;Y2ri3!ZC8uCh1q%EO9e9L@~KWi@U&OE%*5SKHHF8xPg!G-DVM^&jbGLJ|)eQ=7wl_-I*w5bW>ePL>%z$d2K=`FF zZ>CWl;dE5*`zxxN@82yKkIPBbXj?j?bIx$gkE1FEQoCZ0)MO5e?2RtZUM;3gRd~^O z)BU@Ikbm$U>C&*eexmr=gt^snx4*4lSV+1)P%QampZBV+!|T5IyEaOHJ)S8)htjq= zvbW_&zOKB^h7m7e5@7 z*L&T=v4AjqujkJNVK7TuP|o!)=aU$;K9pP{diPYObIBVegH5t=TW7nfHCehm(RR4I zyHJlnj2tLt|H`4XtbIn_jK|4E#;o zcFnxr_5MYyOtp#62GYjGtRMKe=W|FzdY52iq4e-h+kAC3hrP}x*SRR$@Z-zBOIXFR zmt9tDdmf43XS(L$=;BxPua{oxPE3iGDtG$PEaKFry?@nvKdHmp6r8o!j`a=p_~GVO zntkk%6j%J^J65#PKkmwq|Mc*Vmy&&nI_n*6YjzF|)HK`rJeJpSPR^!fo?RyK#r;sG z_fevGho8QZ)}c?b6?4l#`0~C{dn_yH?1}# z@|39s&t!je{MzuFrShl&c8XQix4ND@aQ5@BX00u3xqz(J_%1?bv1>xo!aBo7!brt+ z^0GF~%%HaA*&5bn67jDLpAS2YME({wsQmu0QFg{j9<}&;7q2I!<675j@*}j6tv-8R zduQl(0=H>jPZilRpQxiA%S^3$i0c_sP?8lY5?--CRPlk^Cdx;DkFR*gEm>c$SREH+ z9_@c;T%t!lFz=JzD)Ghkm+uz$*t)Nmv0C<|eomR*2cxTLr^9z;YFHyb|3 zS{suN{YoqxTy>iDDZWz2on$MWzGkoeN?N|~N3YIH!M=MGbDjMoc?r~zYC{pD&hK^N zwTd-D)?S3GO4ixdPvgp^O8UBQ?<)0L^wcw$ zkVRL^thF(x>ApJSSiUCYs!Y?Cp*y8Lt*^G8qLr0(p2yEG)7U^(OxvTmH1Nq2sXmh9 z6XVYI;5om~51F;h?4ZbG^(x)5$L&49ZnG&2&Gf|U70w%5iuEGyO5L z%eMxSn+x-wom;GE>S28TN6u_sc~2W2d*ZH2zFZ}CzM1zdV%Tn;_O|#!e$)4^R!t{Q zNL>55G3UDdVLzjxo;j9=kgpMHF{d7+0X>HWC|`7E|#Q>%O#vFBQaC*dXTs!ECYZyOaKpH~L2PI!^1y4pLX z=Iv@fA!Nr&&c~UB{nqmzHSwBYEym z=Yg;-_0M;>4*=EF`P~ywzF8uXQp(t_T&?!t zVA?UCJ#rsqcf4Ei)QGUuI#s;!+sjpP1f!#?vo`J4Au*)Z#CqL*Ku)ER&i2%rOD7%N zd;i_WMY6;S=j?l5ZfJ`JjWy_1h!DJADzf=vYA)WNU(a6u(v@8HsLyp1?Wp?A8K>Pk z9vzl>-#t`8TztI!VsPY6VdgRX9EHdgJ}Qgvue>4@Py2I^bcQKpR33FSEp6yFscV!^ zCTrmhss=Ni?a}tfxjfklzbf0vJ$^x$TYu()!X*kGB%1#9IpnNVMwb??aj+*uTFEjl zdjF~;qx);=YgTq#RV3RxY{uQ%{Hl@%=*P~g3YUsrgg#>{f00Y9tp1M%8hQhwtZrbAB6XHPY=6O#JM?N@)w)h~-Z zs#98-u}i+tTlIomjL(J8kQI*6hUrw4G-XFV_j+?<`p8J>Q~n=lnxC5?Y$mNb5!K&D-j{CVs{( z138Ozn58bAomb^$qmOqV5EcvSXF-1FW&I;e}vut~1gtzQV zd|Y8M{b_rinc@3;c-s51Cyol+g|qHkY~k(YBP;ceZGXv(&KkDeJ}pi)A%{A#Bmv8A(c2>uwjiTQtYRKrE z9vbny7rsRZZ|xksw}$e&J;Fr&+Rc+QNS+<*lY3Mz5;AUnVW0P&DXsoQ@s+}7H3{_} z!#TlaZi;JqA3AzrQ;6&%fv!)2IvOJ?n!S#U^Nm@k`m7p~Ol zrF>#i##O$dUi!Y*%PbvTONmbk-c|6I1=Nl`i)od6<0(1QhWSe1$&ZrtIobT(vg+~~ zj$@@koAaAoYoDb=EjA;n%h&Jz9m0F*jcU&9+dYjEvE5o1fAR30l+%wbGgn+I*F+oj zw0Qmc-SDBt_i3N+5Y`r(mu~aj&m6o}8yuozDIwF`{OIFH+}PX1Fw2eRt+GQa#=5`m z4p^u+Ky7SQ2_%#mEnLCAXmW2si2F~Q)3szD{(FXQ?*vmtdHPHwmZ`~}3oM!yrd!_yHw$c7GQsL`m2SfMe;}J8r^r2{?=Z(_KL?{`wW3wb*VQF zDz=BZxh(mzK{GC>z&mFCHF13Ek$@=O*9q?HtOsZ_s-$#ubaZv}bo6x$bPRQjbc}UO zfG`YblXUfT^>qz&4Rwuljde|QP4#s2boKP~^z{t%4E2okjP*?PO!am2b@lc1_4N(( z4fT!mjrC1{X-vmJ*Feue-@w4Y(7?#R*uccV)DVct4D}544Gjzp4UG(q4NVM9jdYB3 zjr5H4jSP$ojf{+pjZBP8jdhH5jrD-N4Dpm1858NEnHZax zn3$SE7EK{}Q;6CWf|;UAS~}RvvLaxBf}LC#HP%8nS_0_Y9j=tH$Y>6g8XaR5iebja z#Y7i)*Qn7xM`u?&&I#h=0|ped zZOqoy4)|k$sfz-yUbDdyg-(fpm!JRRdK87j-Sr>05c?Q}{jWP1nG?5glmN4U$#V^$ zeTtz3`2G7*(&Q!Bwh7F9TzvY zxyDTk%QFxRa?61H0#6p^b3BVc>9Z=11spp-@C95(Q)3H;H{gMPa!dT4YSsOPmUcjR86|bRV`>!`tx_E@1n?3dx@A zco?G)5d2IAhRxb>&?jIsb_8OkgAxS67Gov@+d{0W0av4#X23ZW#EOWEj54FVSj-^+ z>VhM@(~WxvM?|#4B>>aW)zddHGyORK1;$3j1;=n*lSlL#I(5TQ7z;eh zanvKn;3O`FiHOwbQHWkDEEeeeJUC=W|F>ggf*UJ{84;n)0^&W+HfwW;*9G#f3|l6x zAo1gncm>)^ij8tBIAVN&XlFIFZy3WrE*f5}XCgP+fPje1Jb*Y7{86f7(XcH{jUx9P?^6>PxqFOt7x>K!fxfE^gOm^~{mfDsK^|6des&?bnY4pB*Aqa>Oc6B)+Y#}C6djLCq}6$P|^s4vAp!?XW!1<1uh zS+L_`3i)OfGd7+T6F&9Qlt}1-7;7R*aQrHhL1!=!c>o#!kO0?((Oa#CQGiIHxREko z`#J1E4~Ly1(P7c3OQG!3BTyA4LbbsJ$YwzyIMa1JD=vaT2?Sora1L1(12_qSVp%bX zKx%-P3C3L$Cs;)=Bco#zQG}YTsE9=1%8OvK6QMC8(GX@sXnF{f63JwQ#YIlJ2U|9x zkq6-C!lIoVeX2~Y5G@KjF%pJdI0gAr1NJkRxVeQwu~0(kQH+R*c`z(08u;$8O*KrI z(zr7cVpfR_gS64y7$1Y?mo>~7Aby<47aR2-Hj^2Si2358&>6d_~fZzd|l?4oeF&qYpxF|$e5yFZA@Lq#{V~OAwXA}#8l<>F9OLM=i$tXK)=jKnAk5WX%jkBdSbm$6`cww^l| z2InN3rP&M+mmeTbBt5^5$IZ8tL;WZKM-WChPO<;T!s!NpAYUB@qZu0-tLeZui@1yt zAu5M&1xh_pT1Zb7;-x{nmmobvoRA5G6X@~mRJnyTM)X7ZLNX8$I-nodjU*+C!xexY zH6uT6fW-N5s<;+5XtHuBxU%!A?;9A^ib`MZbQ? zDcX=XW4!hzq8e9VV^}a}VLB{!M{8;<+&DCr;W;$59B~;0@)5>$6p%9m7b%)_q38cQ zG!7RH;n1cw8Y|Wg>49z&63zw_aMlw7&YdIQrd|N?W)I@cYm!=;#*uDQu1xx;a%3B% zG=7cApn_;~i1K1H(Fxq3as}FWJT@TDtT1`z2I7p$kz=-)81q5G%L>i2D4Y#fRynXI z&nGT|4_hR)++k*R1)*g(A+sjWoI&t-Ah|Q1G`eGt2^|N{^z49S&iJ_v2pUf$Xk6oB zeUO}43S!2CBm3-N6heX?s3e#~fd?&#wi5yc`9N1} z#>I&>5+_Wy4JJ?65Y`0*h%?Ac_IRdH05l4zxgE7v6&I=mk3Jyss*Dn?PAq@`wV8CMVfFr2J3|6E+Fcovd{vUG?%8URthm#H5 zQb=Pi!~t2tNSuP9^&-$TigX2zTr;D@#G}3h`UghAizZPdtzg*9z~w@CZ3v5I07TJ( zUeZA42;93UCE50MUT8 zfE>V4Koy_?@EGt5pzjUz;(#Ck3y=Uv0qg{v05kwv0WSf=fDr%*q_z@34`2`Q1B3#S z0hxe308}1S4pf(@ZV*r%A~&}_(0x=UR4=G}+#|ZrZ39#vsQldeM)9J$MBxy)^^5MK z_|bh72Dwq1sJ^&16gt^n=>IsqR6JjBndX z0+V}aBt0ga8RHKcCPqljSzngW@z_ex0P0W|;DC`Ok87bE;%Z>(#bb5`L#2EcVYfE%qAxo$^rt4wicf!lJ5y9(SWUatQJaC?LM z-y72rc`@up2bB(ZqUX%POb_tnALeClSWW0)sE_?SEGsw|D=!aU zYbwp!*4fs^+TOw0#@dxeqgsJ~aKov)tsO);e(GWCX#>HXoosA9JW(?4wu^0CU2GP6 zxq8|zaq#f8qk1`e+Ch%&T|GR#ATSl;v~hQFaQ3iW>_BsKcU|NTd3K84RYb?LxTgv zW$OvY@swyNFwND<%g%%5YGdn7bG4;;!ru~GYnrRGwG;e7#57kITNn8A00vpe5Jc(* zo;=*ZkB2V=^h6ng1LO$)ZC%_vec|YCYwhZ7>rS;oDR_FgqS!p4X5k<1FL7{1ckP^A zeIRGH?x>dBY~5Yl9NcWNG_b#kroddDzTksOTV(6*YUkmBe0sQB+e5uN+j=^<*xI;y zd3rAKwDRy>f)&Nnn(Bt7&Kh5)vVy?G2cJFBqIhs@6Uh7nCKa?`ghdpX6@!pYjaE#F zT3m(c@AbCUq$&*SFl6k}LR#viC1%1I1xrLaS96`X8cdH2gY_c1Ik869;;MSEAe{)I zf$SoatNSTd8Ldg>dBTVTR-kCGh@yOGS%G0J7_>h0NeGORk`;qp8y^H(997x*e;O84W-14Sq+&- zn(w%E3oHM4#GLDx{RFAmT4=3;_&TRpOQAflTFU~t^~(Ugq3qL2F$w)G$*pjO;!YVr7&Sz(i&g%U~Hza z=9=27<0eU{4z5LR!uQmQoJbUPNXT_G*dU>%IHp0=y*LIR3TAw@JwZD+ahbm6dzg>D_ATQ%<`%Qi&U^{qL{%fg+88weHDNcHz#E%>6AI=AJYp8 zI58h#U$Mb*tK|?ykA|@i-x)yBqaF(x$DSNbOidGw!>Ix+|C||7%NzSN&O{GG<$&iv z$QlgQ3*7)&X1Uc24b6&!9uf}(uh70Q0w(7plslN5IHoQw&e9(I8fohGGq0z6peWg8O{#p2Lh zf{Kahd1yVuUEd(R4~&zT6$-eOup?U2pzGXq4DyfeAvYfY-QxnqiLP_wMr$hWIa+I@ zaL6zB8ahYmp)^o9?l}sF?s5H3J)e4w>yH}`y2o9Ep}4qyQF`b)$~!verjK-ZbPa{) zUgI8-e-s|MQ95fGur>v324n(q0fm5*fHQy^z(v4SzzskP-~r$zpdT;{_zvJ@;&B3i z82~W=2_OZS4VVki02l$x01E+Bz#@P%zzyI9SPGy6!T_;=wSZK>cEC=+UcdoBF`xuc z4yXj22h;&>0qy|q1D*n20eS&n0KWh{!SFm1FatmY$O04rvjM6ABfvs{4Zt4Y0$2?2 z2CM)u0HFXQs5i7mMr&iFL!-4cS{G>p&_)?#dq8~;ZE`_t<*7hxaAZy8`semj?%EoK zMgCEo2*_T6!l1T7_fUFD0Lu6|$^(i69nsn!#f|)-BibZ{{G%gE55H+rw zoq%ru@eo+M1L%M_z(&A6KpCJO@Bq*Q_y!<_g1r|I00;xb0X71107n2dfEK_Tzz@Lm zFo+*87hnWf0!RR)15h0M;CK>n5%3J~0U!+D*;N4;0BC@40P>p*$85kUzzx7lz!v~M z9P$tF21Ec70p)=EfcF4$1RkdbpaNU~%K>b_PQYQn1;9-JN}~;q?*TsnB9X{;=D|_7 zasR1W;cPgn9X7G!#;0gBt;ZlhMCOxRz?WegCJM?_5e zg2$)uy*zlP0V{?nO9KeVc{GS55$<171;YP__`>~%)_|L$%%eJuMd}2bcTQ20q0-Z5$L;DSwE?RPf!H{&QM1cH6ABKk6NFjrr~V< z4~sH+dV)DHDqQ-=l8y}fboX2QW?~O0RP&@qhN($#OvFw;wa&WJqTuok_oGrO;hD=$~PU#d=uVRnJ zIK7_pDh3XFoHg-|1kME%>#$IG$pfCyqlF4I_y4Hq;;6+#V6of_pHrdX+IYJ7FTm%T zvIIWxO7MTeJSSasomE6}(OYWZndvJFhw4Fkd(B8ebCs9b742qJT1Q6i+0d68}kl z8{99RQ@B*V09+4zizSzT1^+zU5*|~Yowx>pd>(6FJDzU>IO?Tg$e;*%x+)X{Pf_59 zE9IFE(t;5e1P_*Yg(BdKfN`*_=HYjT7bS2a|Y^)zH}3XttTQb_f{Y;{vt7RHYpev6}Oz36Z0( zi&FHA=(-FeBOP4^yw^!LH3~M+hjSf<3A`B^Xh=8EGd9wpGj)R0aHe8lgNCPN{=rN- syp+jg^UV{3ml2R8)eMPX1;U39=LrYKMTBc|1lBwuc34Q1CM;wAKf!j|N&o-= literal 0 HcmV?d00001