From d39a17089ef13e7430dc603a0e8a39ad76c5a6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Mon, 26 Feb 2024 20:29:08 +0100 Subject: [PATCH 01/11] Begin implementing predict_single hyperparameter in BoostAODE --- CHANGELOG.md | 3 ++- src/BoostAODE.cc | 31 ++++++++++++++++++++++++++++--- src/BoostAODE.h | 8 ++++++-- tests/TestBayesModels.cc | 22 ++++++++++++++++++++-- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bea3e3..47d09a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Change _ascending_ hyperparameter to _order_ with these possible values _{"asc", "desc", "rand"}_ +- Change _ascending_ hyperparameter to _order_ with these possible values _{"asc", "desc", "rand"}_, Default is _"desc"_. +- Add the _predict_single_ hyperparameter to control if only the last model created is used to predict in boost training or the whole ensemble (all the models built so far). Default is true. ## [1.0.3] diff --git a/src/BoostAODE.cc b/src/BoostAODE.cc index 9f11f02..10eb694 100644 --- a/src/BoostAODE.cc +++ b/src/BoostAODE.cc @@ -10,7 +10,10 @@ namespace bayesnet { BoostAODE::BoostAODE(bool predict_voting) : Ensemble(predict_voting) { - validHyperparameters = { "repeatSparent", "maxModels", "order", "convergence", "threshold", "select_features", "tolerance", "predict_voting" }; + validHyperparameters = { + "repeatSparent", "maxModels", "order", "convergence", "threshold", + "select_features", "tolerance", "predict_voting", "predict_single" + }; } void BoostAODE::buildModel(const torch::Tensor& weights) @@ -69,6 +72,10 @@ namespace bayesnet { convergence = hyperparameters["convergence"]; hyperparameters.erase("convergence"); } + if (hyperparameters.contains("predict_single")) { + predict_single = hyperparameters["predict_single"]; + hyperparameters.erase("predict_single"); + } if (hyperparameters.contains("threshold")) { threshold = hyperparameters["threshold"]; hyperparameters.erase("threshold"); @@ -116,7 +123,6 @@ namespace bayesnet { featureSelector->fit(); auto cfsFeatures = featureSelector->getFeatures(); for (const int& feature : cfsFeatures) { - // std::cout << "Feature: [" << feature << "] " << feature << " " << features.at(feature) << std::endl; featuresUsed.insert(feature); std::unique_ptr model = std::make_unique(feature); model->fit(dataset, features, className, states, weights_); @@ -128,8 +134,22 @@ namespace bayesnet { delete featureSelector; return featuresUsed; } + torch::Tensor BoostAODE::ensemble_predict(torch::Tensor& X, SPODE* model) + { + if (initialize_prob_table) { + initialize_prob_table = false; + prob_table = model->predict_proba(X) * 1.0; + } else { + prob_table += model->predict_proba(X) * 1.0; + } + // prob_table doesn't store probabilities but the sum of them + // to have them we need to divide by the sum of the significances but we + // don't need them to predict label values + return prob_table.argmax(1); + } void BoostAODE::trainModel(const torch::Tensor& weights) { + initialize_prob_table = true; fitted = true; // Algorithm based on the adaboost algorithm for classification // as explained in Ensemble methods (Zhi-Hua Zhou, 2012) @@ -181,7 +201,12 @@ namespace bayesnet { std::unique_ptr model; model = std::make_unique(feature); model->fit(dataset, features, className, states, weights_); - auto ypred = model->predict(X_train); + torch::Tensor ypred; + if (predict_single) { + ypred = model->predict(X_train); + } else { + ypred = ensemble_predict(X_train, dynamic_cast(model.get())); + } // Step 3.1: Compute the classifier amout of say auto mask_wrong = ypred != y_train; auto mask_right = ypred == y_train; diff --git a/src/BoostAODE.h b/src/BoostAODE.h index 7119194..c58bc3e 100644 --- a/src/BoostAODE.h +++ b/src/BoostAODE.h @@ -15,17 +15,21 @@ namespace bayesnet { void buildModel(const torch::Tensor& weights) override; void trainModel(const torch::Tensor& weights) override; private: + std::unordered_set initializeModels(); + torch::Tensor ensemble_predict(torch::Tensor& X, SPODE* model); torch::Tensor dataset_; torch::Tensor X_train, y_train, X_test, y_test; - std::unordered_set initializeModels(); // Hyperparameters bool repeatSparent = false; // if true, a feature can be selected more than once int maxModels = 0; int tolerance = 0; + bool predict_single = true; // wether the last model is used to predict in training or the whole ensemble std::string order_algorithm; // order to process the KBest features asc, desc, rand bool convergence = false; //if true, stop when the model does not improve bool selectFeatures = false; // if true, use feature selection - std::string select_features_algorithm = ""; // Selected feature selection algorithm + std::string select_features_algorithm = "desc"; // Selected feature selection algorithm + bool initialize_prob_table; // if true, initialize the prob_table with the first model (used in train) + torch::Tensor prob_table; // Table of probabilities for ensemble predicting if predict_single is false FeatureSelect* featureSelector = nullptr; double threshold = -1; }; diff --git a/tests/TestBayesModels.cc b/tests/TestBayesModels.cc index 0234747..c88f571 100644 --- a/tests/TestBayesModels.cc +++ b/tests/TestBayesModels.cc @@ -240,10 +240,28 @@ TEST_CASE("BoostAODE order asc, desc & random", "[BayesNet]") clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv); auto score = clf.score(raw.Xv, raw.yv); auto scoret = clf.score(raw.Xt, raw.yt); - auto score2 = clf.score(raw.Xv, raw.yv); - auto scoret2 = clf.score(raw.Xt, raw.yt); INFO("order: " + order); REQUIRE(score == Catch::Approx(scores[order]).epsilon(raw.epsilon)); REQUIRE(scoret == Catch::Approx(scores[order]).epsilon(raw.epsilon)); } } +TEST_CASE("BoostAODE predict_single", "[BayesNet]") +{ + + auto raw = RawDatasets("glass", true); + std::map scores{ + {true, 0.84579f }, { false, 0.81308f } + }; + for (const bool kind : { true, false}) { + auto clf = bayesnet::BoostAODE(); + clf.setHyperparameters({ + {"predict_single", kind}, {"order", "desc" }, + }); + clf.fit(raw.Xv, raw.yv, raw.featuresv, raw.classNamev, raw.statesv); + auto score = clf.score(raw.Xv, raw.yv); + auto scoret = clf.score(raw.Xt, raw.yt); + INFO("kind: " + std::string(kind ? "true" : "false")); + REQUIRE(score == Catch::Approx(scores[kind]).epsilon(raw.epsilon)); + REQUIRE(scoret == Catch::Approx(scores[kind]).epsilon(raw.epsilon)); + } +} -- 2.45.2 From f10d0daf2ee9d1d88db1105bcee38e44686d1598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Tue, 27 Feb 2024 10:16:20 +0100 Subject: [PATCH 02/11] Update test --- CMakeLists.txt | 7 +++++++ tests/TestBayesModels.cc | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d42041..acd389f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,13 @@ option(CODE_COVERAGE "Collect coverage from test library" OFF) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) include(AddGitSubmodule) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + MESSAGE("Debug mode") + set(ENABLE_TESTING ON) + set(CODE_COVERAGE ON) +endif (CMAKE_BUILD_TYPE STREQUAL "Debug") + + if (CODE_COVERAGE) enable_testing() include(CodeCoverage) diff --git a/tests/TestBayesModels.cc b/tests/TestBayesModels.cc index c88f571..db318ad 100644 --- a/tests/TestBayesModels.cc +++ b/tests/TestBayesModels.cc @@ -250,7 +250,7 @@ TEST_CASE("BoostAODE predict_single", "[BayesNet]") auto raw = RawDatasets("glass", true); std::map scores{ - {true, 0.84579f }, { false, 0.81308f } + {true, 0.84579f }, { false, 0.80841f } }; for (const bool kind : { true, false}) { auto clf = bayesnet::BoostAODE(); -- 2.45.2 From 903b1433388fd63f5e752eb63fd67f05c0d9873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Montan=CC=83ana?= Date: Tue, 27 Feb 2024 13:06:13 +0100 Subject: [PATCH 03/11] Refactor library structure and add sample --- CHANGELOG.md | 5 ++ CMakeLists.txt | 3 +- Makefile | 10 +++- sample/CMakeLists.txt | 14 +++++ sample/sample.cc | 62 ++++++++++++++++++++ src/CMakeLists.txt | 11 +++- src/{ => bayesian_network}/Network.cc | 0 src/{ => bayesian_network}/Network.h | 0 src/{ => bayesian_network}/Node.cc | 0 src/{ => bayesian_network}/Node.h | 0 src/{ => classifiers}/Classifier.cc | 0 src/{ => classifiers}/Classifier.h | 0 src/{ => classifiers}/KDB.cc | 0 src/{ => classifiers}/KDB.h | 0 src/{ => classifiers}/KDBLd.cc | 0 src/{ => classifiers}/KDBLd.h | 0 src/{ => classifiers}/Proposal.cc | 0 src/{ => classifiers}/Proposal.h | 0 src/{ => classifiers}/SPODE.cc | 0 src/{ => classifiers}/SPODE.h | 0 src/{ => classifiers}/SPODELd.cc | 0 src/{ => classifiers}/SPODELd.h | 0 src/{ => classifiers}/TAN.cc | 0 src/{ => classifiers}/TAN.h | 0 src/{ => classifiers}/TANLd.cc | 0 src/{ => classifiers}/TANLd.h | 0 src/{ => ensembles}/AODE.cc | 0 src/{ => ensembles}/AODE.h | 0 src/{ => ensembles}/AODELd.cc | 0 src/{ => ensembles}/AODELd.h | 0 src/{ => ensembles}/BoostAODE.cc | 0 src/{ => ensembles}/BoostAODE.h | 0 src/{ => ensembles}/Ensemble.cc | 0 src/{ => ensembles}/Ensemble.h | 0 src/{ => feature_selection}/CFS.cc | 0 src/{ => feature_selection}/CFS.h | 0 src/{ => feature_selection}/FCBF.cc | 0 src/{ => feature_selection}/FCBF.h | 0 src/{ => feature_selection}/FeatureSelect.cc | 0 src/{ => feature_selection}/FeatureSelect.h | 0 src/{ => feature_selection}/IWSS.cc | 0 src/{ => feature_selection}/IWSS.h | 0 src/{ => utils}/BayesMetrics.cc | 0 src/{ => utils}/BayesMetrics.h | 0 src/{ => utils}/Mst.cc | 0 src/{ => utils}/Mst.h | 0 src/{ => utils}/bayesnetUtils.cc | 0 src/{ => utils}/bayesnetUtils.h | 0 tests/CMakeLists.txt | 7 ++- 49 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 sample/CMakeLists.txt create mode 100644 sample/sample.cc rename src/{ => bayesian_network}/Network.cc (100%) rename src/{ => bayesian_network}/Network.h (100%) rename src/{ => bayesian_network}/Node.cc (100%) rename src/{ => bayesian_network}/Node.h (100%) rename src/{ => classifiers}/Classifier.cc (100%) rename src/{ => classifiers}/Classifier.h (100%) rename src/{ => classifiers}/KDB.cc (100%) rename src/{ => classifiers}/KDB.h (100%) rename src/{ => classifiers}/KDBLd.cc (100%) rename src/{ => classifiers}/KDBLd.h (100%) rename src/{ => classifiers}/Proposal.cc (100%) rename src/{ => classifiers}/Proposal.h (100%) rename src/{ => classifiers}/SPODE.cc (100%) rename src/{ => classifiers}/SPODE.h (100%) rename src/{ => classifiers}/SPODELd.cc (100%) rename src/{ => classifiers}/SPODELd.h (100%) rename src/{ => classifiers}/TAN.cc (100%) rename src/{ => classifiers}/TAN.h (100%) rename src/{ => classifiers}/TANLd.cc (100%) rename src/{ => classifiers}/TANLd.h (100%) rename src/{ => ensembles}/AODE.cc (100%) rename src/{ => ensembles}/AODE.h (100%) rename src/{ => ensembles}/AODELd.cc (100%) rename src/{ => ensembles}/AODELd.h (100%) rename src/{ => ensembles}/BoostAODE.cc (100%) rename src/{ => ensembles}/BoostAODE.h (100%) rename src/{ => ensembles}/Ensemble.cc (100%) rename src/{ => ensembles}/Ensemble.h (100%) rename src/{ => feature_selection}/CFS.cc (100%) rename src/{ => feature_selection}/CFS.h (100%) rename src/{ => feature_selection}/FCBF.cc (100%) rename src/{ => feature_selection}/FCBF.h (100%) rename src/{ => feature_selection}/FeatureSelect.cc (100%) rename src/{ => feature_selection}/FeatureSelect.h (100%) rename src/{ => feature_selection}/IWSS.cc (100%) rename src/{ => feature_selection}/IWSS.h (100%) rename src/{ => utils}/BayesMetrics.cc (100%) rename src/{ => utils}/BayesMetrics.h (100%) rename src/{ => utils}/Mst.cc (100%) rename src/{ => utils}/Mst.h (100%) rename src/{ => utils}/bayesnetUtils.cc (100%) rename src/{ => utils}/bayesnetUtils.h (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d09a5..fe8c2cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change _ascending_ hyperparameter to _order_ with these possible values _{"asc", "desc", "rand"}_, Default is _"desc"_. - Add the _predict_single_ hyperparameter to control if only the last model created is used to predict in boost training or the whole ensemble (all the models built so far). Default is true. +- sample app to show how to use the library (make sample) + +### Changed + +- Change the library structure adding folders for each group of classes (classifiers, ensembles, etc). ## [1.0.3] diff --git a/CMakeLists.txt b/CMakeLists.txt index acd389f..69156f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,10 +65,9 @@ add_git_submodule("lib/json") # -------------- add_subdirectory(config) add_subdirectory(lib/Files) +add_subdirectory(sample) add_subdirectory(src) -file(GLOB BayesNet_SOURCES CONFIGURE_DEPENDS ${BayesNet_SOURCE_DIR}/src/*.cc) - # Testing # ------- if (ENABLE_TESTING) diff --git a/Makefile b/Makefile index 2cda612..a7b6725 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ SHELL := /bin/bash .DEFAULT_GOAL := help -.PHONY: coverage setup help buildr buildd test clean debug release +.PHONY: coverage setup help buildr buildd test clean debug release sample f_release = build_release f_debug = build_debug app_targets = BayesNet -test_targets = unit_tests_bayesnet +test_targets = unit_tests_bayesnet n_procs = -j 16 define ClearTests @@ -59,6 +59,12 @@ release: ## Build a Release version of the project @if [ -d ./$(f_release) ]; then rm -rf ./$(f_release); fi @mkdir $(f_release); @cmake -S . -B $(f_release) -D CMAKE_BUILD_TYPE=Release + @echo ">>> Done"; + +sample: ## Build sample + @echo ">>> Building Sample..."; + cmake --build $(f_release) -t bayesnet_sample $(n_procs) + $(f_release)/sample/bayesnet_sample tests/data/iris.arff @echo ">>> Done"; opt = "" diff --git a/sample/CMakeLists.txt b/sample/CMakeLists.txt new file mode 100644 index 0000000..b56a20c --- /dev/null +++ b/sample/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories( + ${BayesNet_SOURCE_DIR}/src + ${BayesNet_SOURCE_DIR}/src/classifiers + ${BayesNet_SOURCE_DIR}/src/ensembles + ${BayesNet_SOURCE_DIR}/src/bayesian_network + ${BayesNet_SOURCE_DIR}/src/utils + ${BayesNet_SOURCE_DIR}/src/feature_selection + ${BayesNet_SOURCE_DIR}/lib/Files + ${BayesNet_SOURCE_DIR}/lib/mdlp + ${BayesNet_SOURCE_DIR}/lib/json/include + ${CMAKE_BINARY_DIR}/configured_files/include +) +add_executable(bayesnet_sample sample.cc) +target_link_libraries(bayesnet_sample ArffFiles BayesNet) \ No newline at end of file diff --git a/sample/sample.cc b/sample/sample.cc new file mode 100644 index 0000000..54b8639 --- /dev/null +++ b/sample/sample.cc @@ -0,0 +1,62 @@ +#include "ArffFiles.h" +#include "CPPFImdlp.h" +#include "BoostAODE.h" + +std::vector discretizeDataset(std::vector& X, mdlp::labels_t& y) +{ + std::vector Xd; + auto fimdlp = mdlp::CPPFImdlp(); + for (int i = 0; i < X.size(); i++) { + fimdlp.fit(X[i], y); + mdlp::labels_t& xd = fimdlp.transform(X[i]); + Xd.push_back(xd); + } + return Xd; +} +tuple, std::string, map>> loadDataset(const std::string& name, bool class_last) +{ + auto handler = ArffFiles(); + handler.load(name, class_last); + // Get Dataset X, y + std::vector& X = handler.getX(); + mdlp::labels_t& y = handler.getY(); + // Get className & Features + auto className = handler.getClassName(); + std::vector features; + auto attributes = handler.getAttributes(); + transform(attributes.begin(), attributes.end(), back_inserter(features), [](const auto& pair) { return pair.first; }); + torch::Tensor Xd; + auto states = map>(); + auto Xr = discretizeDataset(X, y); + Xd = torch::zeros({ static_cast(Xr.size()), static_cast(Xr[0].size()) }, torch::kInt32); + for (int i = 0; i < features.size(); ++i) { + states[features[i]] = std::vector(*max_element(Xr[i].begin(), Xr[i].end()) + 1); + auto item = states.at(features[i]); + iota(begin(item), end(item), 0); + Xd.index_put_({ i, "..." }, torch::tensor(Xr[i], torch::kInt32)); + } + states[className] = std::vector(*max_element(y.begin(), y.end()) + 1); + iota(begin(states.at(className)), end(states.at(className)), 0); + return { Xd, torch::tensor(y, torch::kInt32), features, className, states }; +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + std::string file_name = argv[1]; + torch::Tensor X, y; + std::vector features; + std::string className; + map> states; + auto clf = bayesnet::BoostAODE(false); // false for not using voting in predict + std::cout << "Library version: " << clf.getVersion() << std::endl; + tie(X, y, features, className, states) = loadDataset(file_name, true); + clf.fit(X, y, features, className, states); + auto score = clf.score(X, y); + std::cout << "File: " << file_name << " score: " << score << std::endl; + return 0; +} + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 461c6a9..e798319 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,10 +4,15 @@ include_directories( ${BayesNet_SOURCE_DIR}/lib/folding ${BayesNet_SOURCE_DIR}/lib/json/include ${BayesNet_SOURCE_DIR}/src + ${BayesNet_SOURCE_DIR}/src/feature_selection + ${BayesNet_SOURCE_DIR}/src/bayesian_network + ${BayesNet_SOURCE_DIR}/src/classifiers + ${BayesNet_SOURCE_DIR}/src/ensembles + ${BayesNet_SOURCE_DIR}/src/utils ${CMAKE_BINARY_DIR}/configured_files/include ) -add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc - KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANLd.cc KDBLd.cc SPODELd.cc AODELd.cc BoostAODE.cc - Mst.cc Proposal.cc CFS.cc FCBF.cc IWSS.cc FeatureSelect.cc ) +file(GLOB_RECURSE Sources "*.cc") + +add_library(BayesNet ${Sources}) target_link_libraries(BayesNet mdlp "${TORCH_LIBRARIES}") \ No newline at end of file diff --git a/src/Network.cc b/src/bayesian_network/Network.cc similarity index 100% rename from src/Network.cc rename to src/bayesian_network/Network.cc diff --git a/src/Network.h b/src/bayesian_network/Network.h similarity index 100% rename from src/Network.h rename to src/bayesian_network/Network.h diff --git a/src/Node.cc b/src/bayesian_network/Node.cc similarity index 100% rename from src/Node.cc rename to src/bayesian_network/Node.cc diff --git a/src/Node.h b/src/bayesian_network/Node.h similarity index 100% rename from src/Node.h rename to src/bayesian_network/Node.h diff --git a/src/Classifier.cc b/src/classifiers/Classifier.cc similarity index 100% rename from src/Classifier.cc rename to src/classifiers/Classifier.cc diff --git a/src/Classifier.h b/src/classifiers/Classifier.h similarity index 100% rename from src/Classifier.h rename to src/classifiers/Classifier.h diff --git a/src/KDB.cc b/src/classifiers/KDB.cc similarity index 100% rename from src/KDB.cc rename to src/classifiers/KDB.cc diff --git a/src/KDB.h b/src/classifiers/KDB.h similarity index 100% rename from src/KDB.h rename to src/classifiers/KDB.h diff --git a/src/KDBLd.cc b/src/classifiers/KDBLd.cc similarity index 100% rename from src/KDBLd.cc rename to src/classifiers/KDBLd.cc diff --git a/src/KDBLd.h b/src/classifiers/KDBLd.h similarity index 100% rename from src/KDBLd.h rename to src/classifiers/KDBLd.h diff --git a/src/Proposal.cc b/src/classifiers/Proposal.cc similarity index 100% rename from src/Proposal.cc rename to src/classifiers/Proposal.cc diff --git a/src/Proposal.h b/src/classifiers/Proposal.h similarity index 100% rename from src/Proposal.h rename to src/classifiers/Proposal.h diff --git a/src/SPODE.cc b/src/classifiers/SPODE.cc similarity index 100% rename from src/SPODE.cc rename to src/classifiers/SPODE.cc diff --git a/src/SPODE.h b/src/classifiers/SPODE.h similarity index 100% rename from src/SPODE.h rename to src/classifiers/SPODE.h diff --git a/src/SPODELd.cc b/src/classifiers/SPODELd.cc similarity index 100% rename from src/SPODELd.cc rename to src/classifiers/SPODELd.cc diff --git a/src/SPODELd.h b/src/classifiers/SPODELd.h similarity index 100% rename from src/SPODELd.h rename to src/classifiers/SPODELd.h diff --git a/src/TAN.cc b/src/classifiers/TAN.cc similarity index 100% rename from src/TAN.cc rename to src/classifiers/TAN.cc diff --git a/src/TAN.h b/src/classifiers/TAN.h similarity index 100% rename from src/TAN.h rename to src/classifiers/TAN.h diff --git a/src/TANLd.cc b/src/classifiers/TANLd.cc similarity index 100% rename from src/TANLd.cc rename to src/classifiers/TANLd.cc diff --git a/src/TANLd.h b/src/classifiers/TANLd.h similarity index 100% rename from src/TANLd.h rename to src/classifiers/TANLd.h diff --git a/src/AODE.cc b/src/ensembles/AODE.cc similarity index 100% rename from src/AODE.cc rename to src/ensembles/AODE.cc diff --git a/src/AODE.h b/src/ensembles/AODE.h similarity index 100% rename from src/AODE.h rename to src/ensembles/AODE.h diff --git a/src/AODELd.cc b/src/ensembles/AODELd.cc similarity index 100% rename from src/AODELd.cc rename to src/ensembles/AODELd.cc diff --git a/src/AODELd.h b/src/ensembles/AODELd.h similarity index 100% rename from src/AODELd.h rename to src/ensembles/AODELd.h diff --git a/src/BoostAODE.cc b/src/ensembles/BoostAODE.cc similarity index 100% rename from src/BoostAODE.cc rename to src/ensembles/BoostAODE.cc diff --git a/src/BoostAODE.h b/src/ensembles/BoostAODE.h similarity index 100% rename from src/BoostAODE.h rename to src/ensembles/BoostAODE.h diff --git a/src/Ensemble.cc b/src/ensembles/Ensemble.cc similarity index 100% rename from src/Ensemble.cc rename to src/ensembles/Ensemble.cc diff --git a/src/Ensemble.h b/src/ensembles/Ensemble.h similarity index 100% rename from src/Ensemble.h rename to src/ensembles/Ensemble.h diff --git a/src/CFS.cc b/src/feature_selection/CFS.cc similarity index 100% rename from src/CFS.cc rename to src/feature_selection/CFS.cc diff --git a/src/CFS.h b/src/feature_selection/CFS.h similarity index 100% rename from src/CFS.h rename to src/feature_selection/CFS.h diff --git a/src/FCBF.cc b/src/feature_selection/FCBF.cc similarity index 100% rename from src/FCBF.cc rename to src/feature_selection/FCBF.cc diff --git a/src/FCBF.h b/src/feature_selection/FCBF.h similarity index 100% rename from src/FCBF.h rename to src/feature_selection/FCBF.h diff --git a/src/FeatureSelect.cc b/src/feature_selection/FeatureSelect.cc similarity index 100% rename from src/FeatureSelect.cc rename to src/feature_selection/FeatureSelect.cc diff --git a/src/FeatureSelect.h b/src/feature_selection/FeatureSelect.h similarity index 100% rename from src/FeatureSelect.h rename to src/feature_selection/FeatureSelect.h diff --git a/src/IWSS.cc b/src/feature_selection/IWSS.cc similarity index 100% rename from src/IWSS.cc rename to src/feature_selection/IWSS.cc diff --git a/src/IWSS.h b/src/feature_selection/IWSS.h similarity index 100% rename from src/IWSS.h rename to src/feature_selection/IWSS.h diff --git a/src/BayesMetrics.cc b/src/utils/BayesMetrics.cc similarity index 100% rename from src/BayesMetrics.cc rename to src/utils/BayesMetrics.cc diff --git a/src/BayesMetrics.h b/src/utils/BayesMetrics.h similarity index 100% rename from src/BayesMetrics.h rename to src/utils/BayesMetrics.h diff --git a/src/Mst.cc b/src/utils/Mst.cc similarity index 100% rename from src/Mst.cc rename to src/utils/Mst.cc diff --git a/src/Mst.h b/src/utils/Mst.h similarity index 100% rename from src/Mst.h rename to src/utils/Mst.h diff --git a/src/bayesnetUtils.cc b/src/utils/bayesnetUtils.cc similarity index 100% rename from src/bayesnetUtils.cc rename to src/utils/bayesnetUtils.cc diff --git a/src/bayesnetUtils.h b/src/utils/bayesnetUtils.h similarity index 100% rename from src/bayesnetUtils.h rename to src/utils/bayesnetUtils.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index efccc48..630beab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,13 +2,18 @@ if(ENABLE_TESTING) set(TEST_BAYESNET "unit_tests_bayesnet") include_directories( ${BayesNet_SOURCE_DIR}/src - ${BayesNet_SOURCE_DIR}/src/Platform + ${BayesNet_SOURCE_DIR}/src/feature_selection + ${BayesNet_SOURCE_DIR}/src/bayesian_network + ${BayesNet_SOURCE_DIR}/src/classifiers + ${BayesNet_SOURCE_DIR}/src/utils + ${BayesNet_SOURCE_DIR}/src/ensembles ${BayesNet_SOURCE_DIR}/lib/Files ${BayesNet_SOURCE_DIR}/lib/mdlp ${BayesNet_SOURCE_DIR}/lib/folding ${BayesNet_SOURCE_DIR}/lib/json/include ${CMAKE_BINARY_DIR}/configured_files/include ) + file(GLOB_RECURSE BayesNet_SOURCES "${BayesNet_SOURCE_DIR}/src/*.cc") set(TEST_SOURCES_BAYESNET TestBayesModels.cc TestBayesNetwork.cc TestBayesMetrics.cc TestUtils.cc ${BayesNet_SOURCES}) add_executable(${TEST_BAYESNET} ${TEST_SOURCES_BAYESNET}) target_link_libraries(${TEST_BAYESNET} PUBLIC "${TORCH_LIBRARIES}" ArffFiles mdlp Catch2::Catch2WithMain ) -- 2.45.2 From 8bccc3e4bcaffba3c463586610cb6044cbe14902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Tue, 27 Feb 2024 14:24:58 +0100 Subject: [PATCH 04/11] Update boostaode algorithm explain --- docs/BoostAODE.docx | Bin 18270 -> 0 bytes docs/BoostAODE.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) delete mode 100644 docs/BoostAODE.docx create mode 100644 docs/BoostAODE.md diff --git a/docs/BoostAODE.docx b/docs/BoostAODE.docx deleted file mode 100644 index ec04a7098264233664bdb50f187f6582144f4785..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18270 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 diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md new file mode 100644 index 0000000..648b224 --- /dev/null +++ b/docs/BoostAODE.md @@ -0,0 +1,44 @@ +# Descripción de funcionamiento del algoritmo BoostAODE + +El algoritmo se basa en el funcionamiento del algoritmo AdaBoost, y utilizando una serie de hiperparámetros se activan cada una de las posibles alternativas. + +## Hiperparámetros + +Los hiperparámetros que están definidos en el algoritmo son: + +- **_repeatSparent_ (boolean)**: Permite que se repitan variables del dataset como padre de un SPODE. Valor por defecto _false_. +- **_maxModels_ (int)**: número máximo de modelos (SPODEs) a construir. Este hiperparámetro únicamente es tenido en cuenta si “repeatSparent” se establece como verdadero. Valor por defecto 0. +- **order ({"asc", "desc", "rand"})**: Establece el orden (ascendente/descendente/aleatorio) en el que se procesarán las variables del dataset para elegir los padres de los SPODEs. Valor por defecto _"desc"_. +- **_convergence_ (boolean)**: Establece si se utilizará la convergencia del resultado como condición de finalización. En caso de establecer este hiperparámetro como verdadero, el conjunto de datos de entrenamiento que se pasa al modelo se divide en dos conjuntos uno que servirá como datos de entrenamiento y otro de test (por lo que la partición de test original pasará a ser en este caso una partición de validación). La partición se realiza tomando como la primera partición generada por un proceso de generación de 5 particiones estratificadas y con una semilla prefijada. La condición de salida es que la diferencia entre la precisión obtenida por el modelo actual, y la obtenida por el modelo anterior sea mayor que 1e-4, en caso contrario se sumará uno al número de modelos que empeora el resultado (ver siguiente hiperparámetro). Valor por defecto false. +- **_tolerance_ (int)**: Establece el número máximo de modelos que pueden empeorar el resultado sin suponer una condición de finalización. Valor por defecto 0. +- **_select_features_ ({“IWSS”, “FCBF”, “CFS”, “”})**: Selecciona en caso de establecerlo, el método de selección de variables que se utilizará para construir unos modelos iniciales para el ensemble que se incluirán sin tener en cuenta ninguna de las otras condiciones de salida. Estos modelos tampoco actualizan o utilizan los pesos que utiliza el algoritmo de Boosting y su significancia se establece en 1. +- **_threshold_ (double)**: Establece el valor necesario para los algoritmos IWSS y FCBF para poder funcionar. Los valores aceptados son: + - IWSS: \[0, 0.5\] + - FCBF: \[1e-7, 1\] +- **_predict_voting_ (boolean)**: Establece si el algoritmo de BoostAODE utilizará la votación de los modelos para predecir el resultado. En caso de establecerlo como falso, se utilizará la media ponderada de las probabilidades de cada predicción de los modelos. Valor por defecto _true_. +- **_predict_single_ (boolean)**: Establece si el algoritmo de BoostAODE utilizará la predicción de un solo modelo en el proceso de aprendizaje. En caso de establecerlo como falso, se utilizarán todos los modelos entrenados hasta ese momento para calcular la predicción necesaria para actualizar los pesos en el proceso de aprendizaje. Valor por defecto _true_. + +## Funcionamiento + +El algoritmo realiza los siguientes pasos: + +- Si se ha establecido select_features se crean tantos SPODEs como variables selecciona el algoritmo correspondiente y se marcan como utilizadas estas variables +- Se establecen los pesos iniciales de los ejemplos como 1/m +- Bucle principal de entrenamiento + - Ordena las variables por orden de información mutua con la variable clase y se procesan en orden ascente o descendente según valor del hiperparámetro. En caso de ser aleatorio se barajan las variables + - Si no se ha establecido la repetición de padres, se marca la variable como utilizada + - Crea un Spode utilizando como padre la variable seleccionada + - Entrena el modelo y calcula la variable clase correspondiente al dataset de entenamiento. El cálculo lo podrá hacer utilizando el último modelo entrenado o el conjunto de modelos entrenados hasta ese momento. + - Actualiza los pesos asociados a los ejemplos de acuerdo a la expresión: + + - **wi · eαt** (si el ejemplo ha sido mal clasificado) + + - **wi · et** (si el ejemplo ha sido bien clasificado) + + - Establece la significancia del modelo como αt + - Si se ha establecido el hiperparámetro de convergencia, se calcula el valor de la precisión sobre el dataset de test que hemos separado en un paso inicial + - Condiciones de salida: + - εt > 0,5 => se penaliza a los ejemplos mal clasificados + - Número de modelos con precisión peor mayor que tolerancia y convergencia establecida + - No hay más variables para crear modelos y no repeatSparent + - Número de modelos > maxModels si repeatSparent está establecido -- 2.45.2 From 272dbad4f3fa0ef667add394f4c5bebe958532b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Tue, 27 Feb 2024 17:16:26 +0100 Subject: [PATCH 05/11] Update README and docs --- .vscode/launch.json | 109 +------------------------------------------- Makefile | 3 +- README.md | 12 ++++- docs/BoostAODE.md | 6 +-- 4 files changed, 18 insertions(+), 112 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a384091..65760c3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,102 +5,10 @@ "type": "lldb", "request": "launch", "name": "sample", - "program": "${workspaceFolder}/build_debug/sample/BayesNetSample", + "program": "${workspaceFolder}/build_release/sample/bayesnet_sample", "args": [ - "-d", - "iris", - "-m", - "TANLd", - "-s", - "271", - "-p", - "/Users/rmontanana/Code/discretizbench/datasets/", + "${workspaceFolder}/tests/data/glass.arff" ], - //"cwd": "${workspaceFolder}/build/sample/", - }, - { - "type": "lldb", - "request": "launch", - "name": "experimentPy", - "program": "${workspaceFolder}/build_debug/src/Platform/b_main", - "args": [ - "-m", - "STree", - "--stratified", - "-d", - "iris", - //"--discretize" - // "--hyperparameters", - // "{\"repeatSparent\": true, \"maxModels\": 12}" - ], - "cwd": "${workspaceFolder}/../discretizbench", - }, - { - "type": "lldb", - "request": "launch", - "name": "gridsearch", - "program": "${workspaceFolder}/build_debug/src/Platform/b_grid", - "args": [ - "-m", - "KDB", - "--discretize", - "--continue", - "glass", - "--only", - "--compute" - ], - "cwd": "${workspaceFolder}/../discretizbench", - }, - { - "type": "lldb", - "request": "launch", - "name": "experimentBayes", - "program": "${workspaceFolder}/build_debug/src/Platform/b_main", - "args": [ - "-m", - "TAN", - "--stratified", - "--discretize", - "-d", - "iris", - "--hyperparameters", - "{\"repeatSparent\": true, \"maxModels\": 12}" - ], - "cwd": "/home/rmontanana/Code/discretizbench", - }, - { - "type": "lldb", - "request": "launch", - "name": "best", - "program": "${workspaceFolder}/build_debug/src/Platform/b_best", - "args": [ - "-m", - "BoostAODE", - "-s", - "accuracy", - "--build", - ], - "cwd": "${workspaceFolder}/../discretizbench", - }, - { - "type": "lldb", - "request": "launch", - "name": "manage", - "program": "${workspaceFolder}/build_debug/src/Platform/b_manage", - "args": [ - "-n", - "20" - ], - "cwd": "${workspaceFolder}/../discretizbench", - }, - { - "type": "lldb", - "request": "launch", - "name": "list", - "program": "${workspaceFolder}/build_debug/src/Platform/b_list", - "args": [], - //"cwd": "/Users/rmontanana/Code/discretizbench", - "cwd": "${workspaceFolder}/../discretizbench", }, { "type": "lldb", @@ -112,19 +20,6 @@ // "-s", ], "cwd": "${workspaceFolder}/build_debug/tests", - }, - { - "name": "Build & debug active file", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/build_debug/bayesnet", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false, - "MIMode": "lldb", - "preLaunchTask": "CMake: build" } ] } \ No newline at end of file diff --git a/Makefile b/Makefile index a7b6725..d376787 100644 --- a/Makefile +++ b/Makefile @@ -61,10 +61,11 @@ release: ## Build a Release version of the project @cmake -S . -B $(f_release) -D CMAKE_BUILD_TYPE=Release @echo ">>> Done"; +fname = "tests/data/iris.arff" sample: ## Build sample @echo ">>> Building Sample..."; cmake --build $(f_release) -t bayesnet_sample $(n_procs) - $(f_release)/sample/bayesnet_sample tests/data/iris.arff + $(f_release)/sample/bayesnet_sample $(fname) @echo ">>> Done"; opt = "" diff --git a/README.md b/README.md index ee21fa4..2dd4bb3 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,14 @@ make test make coverage ``` -## 1. Introduction +### Sample app + +```bash +make release +make sample +make sample fname=tests/data/glass.arff +``` + +## Models + +### [BoostAODE](docs/BoostAODE.md) diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md index 648b224..431399d 100644 --- a/docs/BoostAODE.md +++ b/docs/BoostAODE.md @@ -6,9 +6,9 @@ El algoritmo se basa en el funcionamiento del algoritmo AdaBoost, y utilizando u Los hiperparámetros que están definidos en el algoritmo son: -- **_repeatSparent_ (boolean)**: Permite que se repitan variables del dataset como padre de un SPODE. Valor por defecto _false_. -- **_maxModels_ (int)**: número máximo de modelos (SPODEs) a construir. Este hiperparámetro únicamente es tenido en cuenta si “repeatSparent” se establece como verdadero. Valor por defecto 0. -- **order ({"asc", "desc", "rand"})**: Establece el orden (ascendente/descendente/aleatorio) en el que se procesarán las variables del dataset para elegir los padres de los SPODEs. Valor por defecto _"desc"_. +- **_repeatSparent_ (boolean)**: Permite que se repitan variables del dataset como padre de un _SPODE_. Valor por defecto _false_. +- **_maxModels_ (int)**: número máximo de modelos (SPODEs) a construir. Este hiperparámetro únicamente es tenido en cuenta si _**repeatSparent**_ se establece como verdadero. Valor por defecto 0. +- **_order_ ({"asc", "desc", "rand"})**: Establece el orden (ascendente/descendente/aleatorio) en el que se procesarán las variables del dataset para elegir los padres de los SPODEs. Valor por defecto _"desc"_. - **_convergence_ (boolean)**: Establece si se utilizará la convergencia del resultado como condición de finalización. En caso de establecer este hiperparámetro como verdadero, el conjunto de datos de entrenamiento que se pasa al modelo se divide en dos conjuntos uno que servirá como datos de entrenamiento y otro de test (por lo que la partición de test original pasará a ser en este caso una partición de validación). La partición se realiza tomando como la primera partición generada por un proceso de generación de 5 particiones estratificadas y con una semilla prefijada. La condición de salida es que la diferencia entre la precisión obtenida por el modelo actual, y la obtenida por el modelo anterior sea mayor que 1e-4, en caso contrario se sumará uno al número de modelos que empeora el resultado (ver siguiente hiperparámetro). Valor por defecto false. - **_tolerance_ (int)**: Establece el número máximo de modelos que pueden empeorar el resultado sin suponer una condición de finalización. Valor por defecto 0. - **_select_features_ ({“IWSS”, “FCBF”, “CFS”, “”})**: Selecciona en caso de establecerlo, el método de selección de variables que se utilizará para construir unos modelos iniciales para el ensemble que se incluirán sin tener en cuenta ninguna de las otras condiciones de salida. Estos modelos tampoco actualizan o utilizan los pesos que utiliza el algoritmo de Boosting y su significancia se establece en 1. -- 2.45.2 From 20669dd1610963c47fdac433d5fa72759065eb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Tue, 27 Feb 2024 20:29:01 +0100 Subject: [PATCH 06/11] Translate BoostAODE.md to English --- docs/BoostAODE.md | 93 ++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md index 431399d..b5a7b09 100644 --- a/docs/BoostAODE.md +++ b/docs/BoostAODE.md @@ -1,44 +1,69 @@ -# Descripción de funcionamiento del algoritmo BoostAODE +# BoostAODE Algorithm Operation -El algoritmo se basa en el funcionamiento del algoritmo AdaBoost, y utilizando una serie de hiperparámetros se activan cada una de las posibles alternativas. +The algorithm is based on the AdaBoost algorithm with some new proposals that can be activated using the following hyperparameters. -## Hiperparámetros +## Hyperparameters -Los hiperparámetros que están definidos en el algoritmo son: +The hyperparameters defined in the algorithm are: -- **_repeatSparent_ (boolean)**: Permite que se repitan variables del dataset como padre de un _SPODE_. Valor por defecto _false_. -- **_maxModels_ (int)**: número máximo de modelos (SPODEs) a construir. Este hiperparámetro únicamente es tenido en cuenta si _**repeatSparent**_ se establece como verdadero. Valor por defecto 0. -- **_order_ ({"asc", "desc", "rand"})**: Establece el orden (ascendente/descendente/aleatorio) en el que se procesarán las variables del dataset para elegir los padres de los SPODEs. Valor por defecto _"desc"_. -- **_convergence_ (boolean)**: Establece si se utilizará la convergencia del resultado como condición de finalización. En caso de establecer este hiperparámetro como verdadero, el conjunto de datos de entrenamiento que se pasa al modelo se divide en dos conjuntos uno que servirá como datos de entrenamiento y otro de test (por lo que la partición de test original pasará a ser en este caso una partición de validación). La partición se realiza tomando como la primera partición generada por un proceso de generación de 5 particiones estratificadas y con una semilla prefijada. La condición de salida es que la diferencia entre la precisión obtenida por el modelo actual, y la obtenida por el modelo anterior sea mayor que 1e-4, en caso contrario se sumará uno al número de modelos que empeora el resultado (ver siguiente hiperparámetro). Valor por defecto false. -- **_tolerance_ (int)**: Establece el número máximo de modelos que pueden empeorar el resultado sin suponer una condición de finalización. Valor por defecto 0. -- **_select_features_ ({“IWSS”, “FCBF”, “CFS”, “”})**: Selecciona en caso de establecerlo, el método de selección de variables que se utilizará para construir unos modelos iniciales para el ensemble que se incluirán sin tener en cuenta ninguna de las otras condiciones de salida. Estos modelos tampoco actualizan o utilizan los pesos que utiliza el algoritmo de Boosting y su significancia se establece en 1. -- **_threshold_ (double)**: Establece el valor necesario para los algoritmos IWSS y FCBF para poder funcionar. Los valores aceptados son: - - IWSS: \[0, 0.5\] - - FCBF: \[1e-7, 1\] -- **_predict_voting_ (boolean)**: Establece si el algoritmo de BoostAODE utilizará la votación de los modelos para predecir el resultado. En caso de establecerlo como falso, se utilizará la media ponderada de las probabilidades de cada predicción de los modelos. Valor por defecto _true_. -- **_predict_single_ (boolean)**: Establece si el algoritmo de BoostAODE utilizará la predicción de un solo modelo en el proceso de aprendizaje. En caso de establecerlo como falso, se utilizarán todos los modelos entrenados hasta ese momento para calcular la predicción necesaria para actualizar los pesos en el proceso de aprendizaje. Valor por defecto _true_. +- ***repeatSparent*** (*boolean*): Allows dataset variables to be repeated as parents of an *SPODE*. Default value: *false*. -## Funcionamiento +- ***maxModels*** (*int*): Maximum number of models (*SPODEs*) to build. This hyperparameter is only taken into account if ***repeatSparent*** is set to *true*. Default value: *0*. -El algoritmo realiza los siguientes pasos: +- ***order*** (*{"asc", "desc", "rand"}*): Sets the order (ascending/descending/random) in which dataset variables will be processed to choose the parents of the *SPODEs*. Default value: *"desc"*. -- Si se ha establecido select_features se crean tantos SPODEs como variables selecciona el algoritmo correspondiente y se marcan como utilizadas estas variables -- Se establecen los pesos iniciales de los ejemplos como 1/m -- Bucle principal de entrenamiento - - Ordena las variables por orden de información mutua con la variable clase y se procesan en orden ascente o descendente según valor del hiperparámetro. En caso de ser aleatorio se barajan las variables - - Si no se ha establecido la repetición de padres, se marca la variable como utilizada - - Crea un Spode utilizando como padre la variable seleccionada - - Entrena el modelo y calcula la variable clase correspondiente al dataset de entenamiento. El cálculo lo podrá hacer utilizando el último modelo entrenado o el conjunto de modelos entrenados hasta ese momento. - - Actualiza los pesos asociados a los ejemplos de acuerdo a la expresión: +- ***convergence*** (*boolean*): Sets whether the convergence of the result will be used as a termination condition. If this hyperparameter is set to true, the training dataset passed to the model is divided into two sets, one serving as training data and the other as a test set (so the original test partition will become a validation partition in this case). The partition is made by taking the first partition generated by a process of generating a 5 fold partition with stratification using a predetermined seed. The exit condition used in this *convergence* is that the difference between the accuracy obtained by the current model and that obtained by the previous model is greater than *1e-4*; otherwise, one will be added to the number of models that worsen the result (see next hyperparameter). Default value: *false*. - - **wi · eαt** (si el ejemplo ha sido mal clasificado) +- ***tolerance*** (*int*): Sets the maximum number of models that can worsen the result without constituting a termination condition. Default value: *0*. - - **wi · et** (si el ejemplo ha sido bien clasificado) +- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. These models also do not update or use the weights used by the Boosting algorithm, and their significance is set to 1. - - Establece la significancia del modelo como αt - - Si se ha establecido el hiperparámetro de convergencia, se calcula el valor de la precisión sobre el dataset de test que hemos separado en un paso inicial - - Condiciones de salida: - - εt > 0,5 => se penaliza a los ejemplos mal clasificados - - Número de modelos con precisión peor mayor que tolerancia y convergencia establecida - - No hay más variables para crear modelos y no repeatSparent - - Número de modelos > maxModels si repeatSparent está establecido +- ***threshold*** (*double*): Sets the necessary value for the IWSS and FCBF algorithms to function. Accepted values are: + - IWSS: $threshold \in [0, 0.5]$ + - FCBF: $threshold \in [10^{-7}, 1]$ + + Default value is *-1* so every time any of those algorithms are called, the threshold has to be set to the desired value. + +- ***predict_voting*** (*boolean*): Sets whether the algorithm will use *model voting* to predict the result. If set to false, the weighted average of the probabilities of each model's prediction will be used. Default value: *true*. + +- ***predict_single*** (*boolean*): Sets whether the algorithm will use single-model prediction in the learning process. If set to *false*, all models trained up to that point will be used to calculate the prediction necessary to update the weights in the learning process. Default value: *true*. + +## Operation + +The algorithm performs the following steps: + +1. **Initialization** + + - If ***select_features*** is set, as many *SPODEs* are created as variables selected by the corresponding feature selection algorithm, and these variables are marked as used. + + - Initial weights of the examples are set to *1/m*. + +1. **Main Training Loop:** + + - Variables are sorted by mutual information order with the class variable and processed in ascending, descending or random order, according to the value of the *order* hyperparameter. If it is random, the variables are shuffled. + + - If the parent repetition is not established, the variable is marked as used. + + - A *SPODE* is created using the selected variable as the parent. + + - The model is trained, and the class variable corresponding to the training dataset is calculated. The calculation can be done using the last trained model or the set of models trained up to that point, according to the value of the *predict_single* hyperparameter. + + - The weights associated with the examples are updated using this expression: + + - wi · eαt (if the example has been misclassified) + + - wi · et (if the example has been correctly classified) + + - The model significance is set as αt. + + - If the ***convergence*** hyperparameter is set, the accuracy value on the test dataset that we separated in an initial step is calculated. + +1. **Exit Conditions:** + + - εt > 0.5 => misclassified examples are penalized. + + - Number of models with worse accuracy greater than ***tolerance*** and ***convergence*** established. + + - There are no more variables to create models, and ***repeatSparent*** is not set. + + - Number of models > ***maxModels*** if ***repeatSparent*** is set. -- 2.45.2 From d6af1ffe8e3a25854d3c3b5dba85f870dc4ea806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Wed, 28 Feb 2024 11:51:37 +0100 Subject: [PATCH 07/11] Update gcovr config and fix some warnings --- .clang-tidy | 1 + CMakeLists.txt | 10 +++++----- gcovr.cfg | 2 +- src/bayesian_network/Network.cc | 2 +- src/feature_selection/FeatureSelect.cc | 1 - src/feature_selection/IWSS.cc | 2 +- tests/TestBayesModels.cc | 2 ++ 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 81f7209..ef88702 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,6 +5,7 @@ Checks: '-*, cppcoreguidelines-*, modernize-*, performance-*, + -modernize-use-nodiscard, -cppcoreguidelines-pro-type-vararg, -modernize-use-trailing-return-type, -bugprone-exception-escape' diff --git a/CMakeLists.txt b/CMakeLists.txt index 69156f7..0babab8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,11 +44,11 @@ endif (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CODE_COVERAGE) - enable_testing() - include(CodeCoverage) - MESSAGE("Code coverage enabled") - set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -O0 -g") - SET(GCC_COVERAGE_LINK_FLAGS " ${GCC_COVERAGE_LINK_FLAGS} -lgcov --coverage") + enable_testing() + include(CodeCoverage) + MESSAGE("Code coverage enabled") + set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -O0 -g") + SET(GCC_COVERAGE_LINK_FLAGS " ${GCC_COVERAGE_LINK_FLAGS} -lgcov --coverage") endif (CODE_COVERAGE) if (ENABLE_CLANG_TIDY) diff --git a/gcovr.cfg b/gcovr.cfg index 89e0877..01ad57e 100644 --- a/gcovr.cfg +++ b/gcovr.cfg @@ -1,4 +1,4 @@ filter = src/ exclude-directories = build_debug/lib/ print-summary = yes -sort-percentage = yes +sort = uncovered-percent diff --git a/src/bayesian_network/Network.cc b/src/bayesian_network/Network.cc index 32e6ecf..0357ba6 100644 --- a/src/bayesian_network/Network.cc +++ b/src/bayesian_network/Network.cc @@ -71,7 +71,7 @@ namespace bayesnet { for (Node* child : nodes[nodeId]->getChildren()) { if (visited.find(child->getName()) == visited.end() && isCyclic(child->getName(), visited, recStack)) return true; - else if (recStack.find(child->getName()) != recStack.end()) + if (recStack.find(child->getName()) != recStack.end()) return true; } } diff --git a/src/feature_selection/FeatureSelect.cc b/src/feature_selection/FeatureSelect.cc index b8300a5..fba1228 100644 --- a/src/feature_selection/FeatureSelect.cc +++ b/src/feature_selection/FeatureSelect.cc @@ -50,7 +50,6 @@ namespace bayesnet { } double FeatureSelect::computeMeritCFS() { - double result; double rcf = 0; for (auto feature : selectedFeatures) { rcf += suLabels[feature]; diff --git a/src/feature_selection/IWSS.cc b/src/feature_selection/IWSS.cc index 4fd11ea..e63bf6b 100644 --- a/src/feature_selection/IWSS.cc +++ b/src/feature_selection/IWSS.cc @@ -28,7 +28,7 @@ namespace bayesnet { selectedFeatures.push_back(feature); // Compute merit with selectedFeatures auto meritNew = computeMeritCFS(); - double delta = merit != 0.0 ? abs(merit - meritNew) / merit : 0.0; + double delta = merit != 0.0 ? std::abs(merit - meritNew) / merit : 0.0; if (meritNew > merit || delta < threshold) { if (meritNew > merit) { merit = meritNew; diff --git a/tests/TestBayesModels.cc b/tests/TestBayesModels.cc index db318ad..8488f05 100644 --- a/tests/TestBayesModels.cc +++ b/tests/TestBayesModels.cc @@ -224,6 +224,8 @@ TEST_CASE("BoostAODE voting-proba", "[BayesNet]") REQUIRE(score_voting == Catch::Approx(0.98).epsilon(raw.epsilon)); REQUIRE(pred_voting[83][2] == Catch::Approx(0.552091).epsilon(raw.epsilon)); REQUIRE(pred_proba[83][2] == Catch::Approx(0.546017).epsilon(raw.epsilon)); + clf.dump_cpt(); + REQUIRE(clf.topological_order() == std::vector()); } TEST_CASE("BoostAODE order asc, desc & random", "[BayesNet]") { -- 2.45.2 From 78d7ea7c4de9334da34aa214b4063c8486b42960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Sun, 3 Mar 2024 22:56:01 +0100 Subject: [PATCH 08/11] Add predict_single proposal detailed info --- docs/BoostAODE.md | 4 +++- docs/BoostAODE_train_predict.odp | Bin 0 -> 56004 bytes docs/BoostAODE_train_predict.pdf | Bin 0 -> 77384 bytes 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/BoostAODE_train_predict.odp create mode 100644 docs/BoostAODE_train_predict.pdf diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md index b5a7b09..23f5bd3 100644 --- a/docs/BoostAODE.md +++ b/docs/BoostAODE.md @@ -54,7 +54,7 @@ The algorithm performs the following steps: - wi · et (if the example has been correctly classified) - - The model significance is set as αt. + - The model significance is set to αt. - If the ***convergence*** hyperparameter is set, the accuracy value on the test dataset that we separated in an initial step is calculated. @@ -67,3 +67,5 @@ The algorithm performs the following steps: - There are no more variables to create models, and ***repeatSparent*** is not set. - Number of models > ***maxModels*** if ***repeatSparent*** is set. + +### [Proposal for *predict_single = false*](./BoostAODE_train_predict.pdf) diff --git a/docs/BoostAODE_train_predict.odp b/docs/BoostAODE_train_predict.odp new file mode 100644 index 0000000000000000000000000000000000000000..4931f0c6a9772e65ef294a17c26f7657f2a9363c GIT binary patch literal 56004 zcmbSy1C(V;(r($dZQE9t?dq~^+qSFAwr$(CtIIaJ>h+y}X4b6t-?=mIoptuwJ96)g zjLbL@u}^%F@>0McC;$Ku002MY4b_>+Dog1B0090tem4PFn_C+@x!W1*+u2!}8|pim z+uG2(*cj2->N}b{(%IS>+ZfpzI$ImtIMLZT7(4zx|Lqz1zcGb?fcP8J-+cc0!TxVxZA8(SrbT zC0$j@+FQV^)u?l%6)qkcvSM=r;%rr_P&GB7Dz#k8yeRGCTYX?fL6Q@ zpmt2Wb0qLcVGfWs1*iZ3Bn$bPv}5m2Czu6fO^naO4b$kW20F6}8$~@0ed++CglP@M>USw69`dB=}{T3lNeKOpVRL8vpv*fiQw3z}y8?-hou+N2QnsLLFEn zpP2&nFLrG~xnQ2L<^s4e3Cf2Jwe0O^<@P6qtl^ zL6tYfo&+?zAj&nMBv1k#G!A%Cls{!99yAgo=m4h~_WP+0U#lb_i>7xoCM`)TNi#M% zqXZ#KJ1!l_aY(-@!D3g>5%-D^X00Kx4s(X7cg3ogo8p;Xda0vS`U4ZQK63YJYIsk_ zD}0Y0I2w6Cwp9ufBPdCB0GFD@4S9e@EG;n_A-pdvs#kkD469XH1}wLC3iyl@bjln% zy={-vn)p6fx{ZKb4kROQ8iuKr#1Uu_ir@z!veZkn5G>fKsg=LIfZt+Vi6)jrDB3td z%>w`^t)CkpLL&C|1g1I0cJ3e#ewfT^FX<3nY29Hm3JbKN`nZ{1P#cP};!7Vg^D8N+ z$F5>mq1!l!WBneg+b&i=FuJD2H_!A0+t8DcITC zIothqfX@HFOZZ3Mzn;Qhj{nCB*y-CCTm60fKl2g4W5eGa_V3@4p{BOZglzu>7ZhN4fXPR? zyRFH#VS}lqUX9S=c5)#`32EPINvez!KlOi|-tDNV6yD{1h8+=8U-7x{!hrr{NJV>Ll> zo`4$0t4&4F*nzz*VQE294Z>r|v{HyKFL&cb4{0vG{vX_ctUJ3}U!b1ceZ!by%rT!Q z7_O1oX9vGVi{9g(iOe<&Nw^>jNcSX;d8WdMSA5ASePj}Xb2X@^gB##k<#FhH1Y;HB=2M%>&Rnc>;Z+(>6x=xb`-i+RWLQ ze}ewuq0vbrjd*@rG6m2-Ecwq?AN5~X-_h8~$=t@&@sH*2(%5iVXF%}2R%7sygE>^? z36d7iNWm26j+AOwCeiJx~2cTR+np131+G~xXt8FJdT+|oW z+mk;Gv@1NX1u*mYaqrTE>_2462C|SUWl3thyZYqO#ix^l!`$7--K8@VQ(Y{DX}Bh2 z&HY59xoO=ZclpAhg^P=OrryuwK=b-V#zMYK5G^d}J1f^Z$p1|}pvG`PQr5CoN~-Om z(?H!+p&{bR(|0CO({~Dxx3*WZX4E>j8bK^|Id`kYRR+h#1qRx@|BCtPuzc38WUHgp z^c{WosC@N&`gO>>?lvHi3pSLTjt|j+BpHS;v5ydmVHm{$&X6j(XKP!cNiPcXuGo)9 zY>ds+eHsH$q^b`H5pZdex=jW_*C3;TRyAs3{46a`%}qV{Gm}VqyfcNT^5g*}YA)_#eD+X@c z$juEI;!)!q)Mdk>8d=LnNra*@xgb9~$;SP;H4PO!{bVEzEWl~<>U*?QN%G$?l6$Y0 z1h=c#v6IR#fkSXvi?GaEiSrD(^mVoy$jC~!K(>Qfs92Ti@fG0N8st$^?Kt5i2NjzH z&GF>I3|Ytt#0*BmObINoM9Gaw#E=j#u%stLyK3@xS%Wd{IvGawN%Hf~GU5uI?lUZ? z&><0p*QP05Z6sTmMS}aR4kZowWgkbdVGDl#Xe+lJFxq#+q`8d>k$`!mpOCaa$Beqg z*(ZNfEqw4W6UA*;$PLJ&#p53qNHqpFTN=sE!Pz}B34W2bkrHszd zq;4v|5-|~XzdjYA`bwJ#Qc#7p87&N8zF~=Hq`a<@O!Ae9hP;UCI%b zobD-WVtE1o!>VmkqU{oa0RY0V{$bUB7N0SGTlN1Pmj81!{LS)z_4;pu zAkLrk`|n-?{!GsQ9CH3_y#Fp({@D!wn;41zCo}x7-%@$ckNL0!* z62JW6`Ch(WQ!#7<_Jwl?6O^%3zd0rQfyBL%#sMHfo^0jhwCwFdl9~JEkK|cv4gf~+ z9D>ii*}q)tewj>$`uco@x|%gX@x1I3+qMKNm=vx_K`a2$;S|9WKsCn>Xe03w#DSd( zgETXEk9LBi42Yw+kD3dHV0-3T*9|&h2eqRjXbuQ(V64=`B98FfF3aXnYqLm(`N*l@AQ5J8` z_ZGx_>YKv{<*{7hq;=peZw^mL@`D%@BUr$c#~{aX%t^~bR@gAu!UNOBX=n9}50WWO zTr^bhp-N|tM2(6I@Leq4%2LD(MYdB+mIuM3{w|Yco0O3Bt_u-{5RnIWt(dlhnKHD4 zJ(OBw!oEkSW7O0zcn>_sFb#wRI;e3~I4`81fSgRYLl?=Q$tmUDvC+V0`2SAj1@6g-qGY>*a2mD^Vb} zz)03b^85L!5P>7Rv~c=R%`WT!;2$>nU{27;noU+=vKR=P29&I&Dq@RQ~%xpWjYAX*2W4VHBYc@Ap)z+v${^xr;l4}{6! zS54TO;#qF8DAKAZ(5FV?j9dG040*aBiLxJYQ}653hxP9USzeC@RD^KEsP zN=>s9v0sYfiaaUr=Qar?-T3rwXwR47%~9yVcl-WGnBD=g^Nv@_i>!^AJ2w7aas+=? zx{^LUSucH<`w(Y4s}U1Zb&J|%Osj(S@Q9Lzt+RJmqp{#;i(dGfN{-wdj1^z#euOvS zltJ`zH)A+KqJ^6?Q2IcjAo7cA)rU~iy>v|zw}%q>5a~7rEF*QVjrz}0l=5l7xs1ju zO}_8R_42(e*E@B_K?p#$g9_6A!}-MsHS#w`)8Z6zVoyj1>j{tbX&)f{xRa!q zxHT&_D)it1C&d773b-E&b)T>3-@(zh;O99&=tCr#Cd>Lgchjt!>MZO2u& zl4PAn0NZC&`$!W+Wh50LnVDHOKNKfIS+7l7opIPoXh|nbHx2o7$Y|vh#aApY9jmh0 zoyq;8!fv&*!tN%Nv6pCX@rMapV0p}8fSK!-&;W(P^?tRhFP&X+;Q66-z+Oa8C2{<= zFVHx*k_sE9uem4))CB`v4_!pmc|5Ww5^&U2niC;ORf(@mimHlZN_on}HGceMw$>iuJ}PF-ky4i}dxbi?AHbS_>A#YDb%L#Sh27#zD(smC$Nmxg!oNgK9%? zqr@bAIgXgI0CREHj9X-VN7+aVOulh=-TBBOHOthSh&VyMW9cCRz{WZuC6-8zYS`Kdc`RTexn1EwaX}1Qzg4iIj?oF1#T~)( z;f+E)Y-D#qt5k{-n5Ll$c?U1`-ZLY7*<(P|>_Lh?lVI9N(CvM$fWgeMB|l;y%0P$nf>~yYM6t@=%+ie`eZSW2NjhT{!16npjZa^QYX^n3^e>pk zMs)A77J;*sG5WYsYLnU5gWtRF`+Ut+kWgN$oZJElg`KGMeK$|DtYqK{Dqqa&mnRFH z01xN4;`c*|3^*6m--TZiY=ZaVN*fdBV_QxmWLsS%hstO}q&W19dubCbp+$&$t?TB< za-fDvjHe?l#eZiBb#n0ooXxWOj6a^>*~WL^HK>0p6YrEi2D?j;Ys0K*dvLfuG5 zFiDCc9cOa5(@I9~R}ll{m=q6qEKXBm5L-mDZ?f`}gJ%NXnPE3$nbUJ@xcxp@n)a%g zPKl7Nb%=k3{Xj!Eh_-HaqCuqrk)t;n7JUR)uOPU zhFjRotHi})j!@{?yu`YKIgHG9LY{if3!lAwBZifWz;440zL5;5p$l|}??$AqhN8H> zONvp(M$3)Knb?YedS)}8Bbf+Y{u?kcZv-XpHR&RvWHZ5})MCU+;blQOUkce{2@frH#Eg>Xx=N#KjH+McAS#buIt+c3@l!S+@hXFx2g$}v{ z!JQ+BWu_2}Bx_=fdbZY}fPaa9?y#sbj2h(_DRd?xVuG3?bYx*ExLCrX0P+2KUm0Ou z7re~@tlm~D%QO-On4QX;@tl%MY!%UN3ULcj1pH;#H4Lq9uue04J!~`Opt16?2y(V| z+~@|X*qFrpg{dJQ6;%kEFTc`f*teV;ai4EtA980zJ@yOkM1aB!nob`L7GQ-jfL6LbaL2iuHgATM@)@Jj?cPPrwz=H(WK0gOf%Ur_U4L(5CswDrls>(S~ba{p^UJ7 zg}GEjVbE^=no$Gpu4Pc^4fun~O+s|WwBq}sJoT6QE16Gi)EV6@Ifnd*u*t+w1B7OW*6N-cx_vQHr;2oyaD&8-S#zOB-s z?VJc5kU>bpUF8F-OMrelM)Yx6Y#kNo2xjVKfKT;GWrZZhnYhluC??@2!3q}IkEyul zS`GufigYJ~lY&!=ytWN9F8#i?sXE=cuzPZ37fhy`ll2LJpZi7fR`Q4~kf8UeHVFi8 zVoYid=sRAh^(5=T)gpL0pjMqh4Xt&n&&>2vpDSOP4s+HH*bq}TkM)HMUPDHos_*iT z#$!rvvs2%d&RXG}T{Z5axBA-pjmJ*2bigjI+7B;eYucS%wH@qJUz}Vu=9_JXJhr81CfZu^aVS?`87d8-#XJn4R3>FYgTWkb2j!LszAyT&e@T$S--R@zl{ zCg|h4TCcWsv^klpI>)M-{G{tw1+N8{Z`&fc>crc>q9)|C5$a@WcwyTq3rn>r%nE6J z_Xy*z333}t-f{zxcL~gicqzml9H|Ly^)?NY>k#ysC$5fssl*;E;oWZ!oB7?a{6|AR z_TVw^{x?K9*NLdjQ=i9!m34$I>)rRftN3!G7GLA$zkQ#?;AG#`Omq)+8~@St_Z!1A zGCS%)6V5R*WxHNuGu_Dr`~;Wq;%fru@Z!c~xMjZpPp_Z-9%$VSJ=CPHBTls$yQqJP zA)ftlE_NMys7~8Pb=-h>tWJab&WQS%yq)Zztv2qq*`Y}HQxY?kh|^OIioDGKr|fsi za@)bMu~gI0UC0f1+Y<=RxNpA2Vi@LZNBw-YVyZn zU@v4$4oTG=LVj~(a~U$_n^;ZZOqUkTceCuRl^!{=9m*OBj6UY@^fD%c^Y5L|+Gt}# zSkc=@fS6SJOu1?DMhq%6SW%vY!&Ib|g%;l&xH&9HpAGb3V)J zaHPevLMN_(ynv`u^&h#!(mB12Jse7)C3}X6Crt5M%Kf?Li0)Bp5EgQlswl#_pe~2pSP~Vlz+T8w})n z6oC0LM9+P||7w5wJBM>XUXzpnMM7m^a5^iXfnU#=)s^Zt?AVi5Gf!Ki>V+y+>h|mc z4Cp-^PisXjp$cLAP25ar&ZdLJ$?Kb<#7Q8GEjJL6TnG3WUsYm)D&3Qc`$+agl{ZxQ zgWYDAam~;@#ATC}?XfU?Jncf_<}fRN>`48SL>%IyO?>P`aCkheR-gKr2s^OhB_S3w zsV)3P>!zue*mf#@V`p-<)l|s}*z=}TO$+oD?8Bzxb&C$xW(PA~Pm*=0nT6XVVwumJ zTC{Ho7qf3>`;yF-Tu;(Set+dt#R9ok-{zwTU2I%}k5lFZgT?21A+>~BxAiYcDDMG` ztvV-K z9I<}1*Lt(pnu@e8(Y1Pxy!5HtqHnf^&->f?eaNL}^$LBXC0f2`Rh3R+5p47}idLNY zO?#Ur`R!i#Mkql%`qlL3ndyWRnwcm@`LtTMi*le?CeccQ2SzAJFf57|#5^u!5t;6S zj2%}}AmH?XAS{^^V@w&{&smBf(TRoI0WTy43b+p4!+6fBe8!7rZXYMUgZr(rR-FUG>;W_BW-BuK6oKeBTfuTnUAT$G**wCRx3hR*- zvo@*dL7^j?7=})jquZ>xNUvn;7MYJQ9Y@|CWpoqmu84HP-6<^X3(#P`yoJ@O-YDPc zwHAZ=Yj+XU8vBVh`>!(8^=LfJI4Q8bNHRqjS=pzdmGXm4fYcKuq$Qx1dc}D)h_?O+ zo3A~AkMFKl7ni3=o+#kX$Z(Xks#d;obL=JVBPQj$Gra1b16)>rDAl9HxJ_o+}Mv7%`g(hqx@Ww$s_leS_o zy-hfR(~+i`+zr-H#8n1|7dqX5H8of_9@tA$GR?z+Q|e+2arM5~ms~2G;wpO8L#O>FI^Bc(_Py*DYZ!M$@-kz={ZcWQ*&eb@W7nHJa-)pXo(jzxEwKR!)YGc{ zK=UZ45tE|IZe|@OGi;*~*2dA1?+4C+wZ5#0lJ()bEezR~HH_?F>-_!3Yf z_8I|-dSorqycoX3qhgKcf(`xTnt@Kh=CGvW7c`g^$b(%qDMYnXL+#=#yNuLil$BloizYu?YN?5gUUGv4noV+!tI ziF|;@&pA0oI5_EHM)>X>Rh`VK-Cw?cBwLLIQY^WU0RW7)|0>z~FCZSv|0fvwFCg0g zX7PV#{r)WB`!6`;|7P%?Grxa1{(qAK{!0e`O@{R^C-9$B!hflQ|1LNDmlFKveC@yB z+5cMuf0`@)GdS*Fz)+Td1w*41CS(T~5IXNssiiWh_6s4(L}NS;MC5ug+p0IzIb4je zrBRLwUJ$#(rwV3;;lDraVaB?SSlw%2zsU2MnMx`Cu9Gk>+OYJb8JoBtn2NA^L9Gz2 z1Sn%mDvYRWD`6>0D%zNQC8k|IN8KVahS~TA&qB%>Qw2T={ez8Ni)JAu-NAw9?naAVaES z2p752e$uz+E6{QQ=ppuHbX&!Ye!7eQ1^9LSP5BC%js!tH<&(Gx>)OK+j`G?0MaoO z@K!jkH){%L|I}|k(}2I<8*@Ex|2m4GOs>aopNo5+Y z4_+^HSa23o|74x`4gqb#sOn0e_wj9Z`_45+nj**!xz)sTp@FQ( ziF=P`UqGFz$gZnwZ~A6!J4MsIm(C$_p_oF&zHdkz0zkuQLesj^MN2)MFRHXCh9RAl z1hESB6PP*-VgL#iJt86ktoM>u$b)KWKdmBJ59;gLOin<5p3mya2T7!_B!r@7WdkXU zx%-C_0Pqd-rMRk#i)?wFoY$pU++v2wo_$&!(__32)WyD3%b&^YOEV1loctKHNAw5$ zcEvo3uZ13zw$}q&LJ|xWS5aQS>QYjCyCbP0&g6q|SVs1x*a2T_!zWB42RzmvHk5n1Xbs|DU8NUGO zrHgBHL&Y;tvB`U|w!lOg7}k}`!aSd<%BezAg+qL={*VqbONVCTj=6_k!r8sLajAdVOjR=Ev?gd}@S zS+PSr0%Ou;rUQDh6SuH3#-vy^3*3RSZ|59?MYl%j8=Ev)Ollv}j9J>C{8xP?N=x=9 z20ejKuC1tKO{nT(9yTZy#`^xPA&ip>o1d!p#p@Vgw(UyT%wbH@B#7w}aEh~V0TISN zty|8V^y=^-b*S3!#E!IL`tbRY%3Cz)F&j$4D`msz-VEi8)uB=}Y{z?tTo-s84s9d9 zJX)<{#hI*8NYZnPY~kJWt2Bo3(k_@Z6$h{!GG<^sV~;}BY3lfwvugdl+Ge&bgpR^5 z#=^l1aNXJs&m75%`dIN_e~Y%>F}-dHzyJUt|AaRG%tJ!|#<%{ZO5{>|E#{oVqkBdP z15bK3FvQ4uo2tZOcEJ?L=%}6rL-$jy4Y8=K}>`uS?z;9`eHuJDA6$2i9991n-Mc?*O8<)`4zph|B< z#ulmv1xMD+mO+ZMNTw|=-mAG4PKuaI%LP@)I^|j#KzMew;kkxLQYyi@dKy~WFP?&s z8mx;c72=)cXCbJZ`*e8T75IDA&lH$s#Feo{=ekJyXj%t1@e1u&y|=!J7>#Cn?f~;s z&s}CIP9nMpOu2js9nl;FiCB#qfpA^y5WgnI;;ELVO*FKWQOXtJZmDBE+uWH-0ki1J zw6*1+l)hs0YCUk{qTZ^5tAvy``EOZ#h4wO?XCMugdXf9s`p`}k81gLvp>m9d8n+~$ zxcBz_zhHa;eYvH(fxu}!#M2d-P)a*@{5yg z9QbV?+0YFq4fGoyo994+f77y^!ZUKMzI=Y@3+(Jn*c65=k$?bj z7rTt+NpvVYO~X9=42Comwpr|4{%bEBrra$XJ+V6J6MT6$VIoKN9sypM@k2M3MlUV1 zNSxLb^FvAbN|QE<@|P(W%gP6bl@LrfG`DXXr0j!r-=%hG-r6YV0_r*9u^#OK9b0snk z6~b8nugl~e2`;pEK9+_Ua{*`^LxnnY;RMZWc1lS5(Ko;IC#)6gtCyAQdm_e3b`!Zx zZShSqthKu~pBfd_w`QKZsD4;raSY_{=5u_Anw;zhzk(KP8(jg*po=Vw$&pp}YdKW{ zG+XAnq|gUpmIY_$pMke|d52C3Fb?Ymlo1&!@n;gEp_|7Sz{L|FMA`)3ICzclJ0TzKknXiB! zT4>BldboB`61q~Q$t*JrpMNs_n5SVz^E@kYDwW21T*$1?dR3Bqzr9{kd8Mmy6c~(; z5lLtzl3;v!w2{b#>CriDV$%I+{FgHF5;OJw9a2^=f`y<1s+0!d$uMS7^M2~O+#FLh zx<;q+K~7BM*C53~j(G8i(1ep-Duqg@tP|A$Vqtrj(g!*q2%Z6oXu+D17s|~+LhlEv zqP}Uct+&aGg*2e0s|i$UWF<{}Zc!g21hbBFWXcY(qR}r*o4!6MP;f>RmJC4OO2uuL zvi$bh=`#hVmH-u}&wyj++ANqrFa6};LY~`rM+GhVN18Q9h}>Ndvl>{MJn>OVwXLcP z2=4m`*Y0ycw<@brk6mXv_yJ4fih1yAOJf8b>L00i4=ReGdre3eu0K^!NoBBuRwQ{= zfosayqPkV{bNTGF%L8vTAvwA3%fl?)F>p)i_Pqf*u!dU?Kh@|olJ-Fazb2o0oXYkM zSSOjN;p<#ri|T*XXlJyl(}U?PlMiW9$kQ-J2#-LezPc$QC6o45mY@2-!9iNAM+gIj zWW5lZnQ(vp1kt)4BoxHouy4f)5EcSi3K=lDdXZi6QXE}`>V7nUKqdr{tm4S5c}K5> zlYl0UiZR>oBC}SK;>^;aIB!V-y9w&EtN9XxMYJX)Bd@W$LpsLsEKIqRX4Xv$?I*yq%Xgf4nU zD8hJ+QCu-9F?R$y%B$Q%!+_eiluNyrdu^|$HF;;fjNS#5j{Ox?8Vq)LF8IODS=X&NITOQ-OV8l2EGTlIPPpx{BY|n zj{ni6pv%6G-NOw6Za;KQDOS#lAvL$oyy&D2Lifx|b-eAFetGn0SR7JEwqDw;=S{%+ z;$D{2kO*yH0qlx|-+m-f4nmh=Z!Jf)t)aL09+dYk$gSno3mEcwLIU;-Z;;zry75st zk$69EediWmcX`}aIT1qewGM9dF__5*C}82TJ6!%LV)hCu?*-FsEgFol7c9aIxUW1R zcsOY07U0_2FBC9Hl+n`{vw7c-jB~oIZ;0c1@Gu`I9sS&QT(hxc8UneQOA_xD)|lsD zpZ$TSIGe)qD810oDu5c_h@vV~M1ZJiTGEyflI z;zSZc*zPpz=GBrI1#IxqqB+i%LsKAISYerth|+Dv;!Z>twI80v?Vql<@wfpM+8i7BV(j+SZdzttZSF=U;%QMQIu z--`vUm>#qH<5ZW?8QC1AQ2m&dX>%{XZe_h!=3;5d_4yDy9y zkmdCwtmCO1CLcL(K!qW>=z{@LNoH#f^vs^4XTgt0!^d~BGMJL%QHJG0uhnD4TZV!k z(2hD~Qu!x!`{=9ot&PMn^>ej>%8XAu;xx^8M>W+?lV+sY88BkaKY3M|&H}YKuAA(K z)tiOw-qgo{;0xMTfYnLAIM}N6b0kT*U83u+S4@>1M99OTY+}a?PU;gJF;&Fb5=$A0aM|G2N_Et1CI{0AWFs z;<8IpQ^_fh1;Qv{9A;t9=XrOO-oAjG+f4)bQi^sN>kvf@1qKA?)q&-@P%YHSPncmxs9^yOn&p`dswsv zUS*%Na_I9ojX%`|jLfPfEhSfd|K2l6O36llu+iJQd zjBC56*H19+Ml&|a_-{Z@N{^%~5Z5wZc8FOw>ZvNF1sY0fhB}JiOTG8i<&T@+uk$s% zt=%5QRx6&n(@&;ELfPi@nW&nEXWz(nN8pUhYmE6b&@YQQ*y%m)^MyUaf};wv;ta zH18sL%$$w=Y64kIgBsseb1WImYmfTf`iBX(=I~UA~Q{U2oxbH`H{|%J~WZ ztg<-K4zJ*i<=elZgAaBndp!}_$?IZ_)T_R!VG`%o9e~n3y1?TPs+*h4F*?;JMBPhL zJ$O|5W&UCLo%F=h&+eti_k$N%vpO;JB%h7#QwL-P;A}#WI@|6cvCWu@T+A9Tl%z4;VK>*Q7x2>Bd680t5K3CGl`m*<8zDjT zhzfDuL&VHE8Dh#)SRApbLd0wiTj)$>zu^Kthx&1$VjE$;dQq;%Q{;r}7mH8$CbG!= zgu`N+MT)vs+t7N8`a7ECj=|+Ep;Kg&ZQT>?a`zvNo|_O`2A77FypDhSYrTum&ITdF zTMdE7M~aHQ;x^-R>xCge9))xYpOZgx;GqY80Dm+L$n~i$+pUWSkC%$AqiGQcfFEwq zUwFm584!pnOaA%^9XPaz=H?22nm6F8dq{4cVV8#wG|11_@u=r~k(CpJJ4T@USW3cL zY8zG?9V!6iwzfrTpghQ#!bm64Q&Cw6`qH_p9vu|+m0g;jN+Ipjcu@#Y7Ozz0EA;4q zAnODT;@`%amlFn)Sh9bgz$t7BkAsWV57*8H^XoY*6dk$yvrp7Z0@*x&6q@pt8I*ua z{5cN~$kep2kmYOY!~!x)YTU`jT>A-e54h$rviO}MmP@;AsJ#c060#Ro#iL28A0RK zgc%4I6gJsT>!%1FTp((!;wUM?PaXuCbkux?wGcAO?>yGqt)+%VhvxB=3~~kPYwnu0 zR71<(WX>7`_7RZ4X{GkP>PlbC6c(1<`J2&4^p znDd~o>@&DnzEPY!D3D-}q?)667Onz}Fl6BtLq>=MBibW))n*my``oo-dy9s}tM28h zaGEtS(PqfO=3S4U9wIk_;A*P&#W|NI9mj~Di3KxFNv^=EmdoJpPk7AgwGb^*)Xzto zr<-vPEYO9O2*^L)c~wBn0)aIWyOqSmb~f`Yj^V+goCn(S(1OIA`aPs+vd@Y~0-S+YB|a;yyzT=Xv|@*$JuD;UNUx>t z#mUhPdz~u!M7^U8waOtMTNdeCE;q7kN#l3BT3;jdk5z;}nf30h?ONaUd<}If=bkX( z@Zl+v*yoiUxrrke3GpyBoU!Ey9lBg9&|AynO{K2FA zGn!8;>b2*><~j?Pzc2TfDsDwAC*8%nwT|dmq}kP&Khslx_ac%{5`6j2avKitmGd?H z1&@jvDs%qX&`X!UN0b3tfh}M9Mvchyf(s*70N0wCa)1?6eY{w$Cet36;aGXBjy~`- z9W;E}4h0yxj|iW8v1}1V7;5jx7>P6;bL8xH)E_JY_N@m3^cA)v)sk zQl0I6CQ&l)=I)Zat6ZE-P8l7_w+a4ZAm^GfXKQQ`n7)V-K3IkBmY#PaC<9+9p3E!S zCg$LGp+%I~7HgTzF|N8}59i%|$5;zbiw8>g0aY`k0{eH1gAmItVz<`^?`atKy@RTJZ8;*kLf5=~ z^(29^?y%`x%VgB5P?+d+z^d4~;&_ntw&)Br&G#YRb8&6uZLQ_S^4mI#4V)vMUoc9Le>uU;tTlMZ?N&G7I%Wy>tu#0p8SKS=nfVhLe}fh z72j7z?a9iIWpadi&^J!<=0JkssUx$W6P+~7WR7do{0zSe$H-f6xpdYZGmEX_w7m!f zYCZvS?@1WtK;I#QmicCKvp&{)BR=@*IT2Ds&9z^t0%+)u$1K= zhh`~oHWn){rEBa&iwJ&)%%G5(OFC>Q3`->-)-7{itc$^S9`EWvBK+WB4^?_XwHV=QM&8`e$@%xjSbAlB11n>B2V)m=V^6SjYL3tKDh5*%L{)1LEUl)nzIP(+5i-1eg~d$6p0tVhreDToDNH6k|Sui~Kr zWYU6|(EzFUH~UdJB?ENQ=CzcN$wUY)=9T5quqIW7I4s6}gFPzG;~`}!4dJ?5lK01= zQWh4qC{>3|F3nS9RW1%;E6a0WRn7~cT9Qx3Dp%zeg*C1=xNRm>E1jNNX6tuaL-DFY zoe&>!F`$w_wIt{3?7uwpp-odV(dWMjK_b~35*OO3(s+=omCa9u1L{l?SqVB>s6h25 z9zq*IxegGaEan)-*xCJ-TpD;%x{q6D5P4_~7E?K4QnMyAy$6z-b*opAd?RhXX&wIM z4Q(n1y^7pJnjXmX<0XWoo%c2|qC4Io(l7!!n_>uM1mnO);)D0)hR$pVjW*n9Vq^T< z0u#@ZE_o5nXZs*}+tGFysxITLITx+jz?0P@RiTsOw3T~9=%!Vd%5vI#>&E!T4p%i| zl^(rq)tFc6+MQ|#FIjt6C+XkiO=ZnTQNl5dD#6o|P?qonTWf-1)kq&m8s16}EL=e_ zk%x%2AM{2WzWu#ZH!hAinvBUkg!zA5k*;|EEm4|v_jlO zDLEiXDTxA<9-R5$R9fX2`8!*vSmeNJSqNc|+BMus$y@Au2yk~IqxajCJMYgdkWGU)mfdblk|#8IcjooiDZlhCl0> z?Q7an2FWMFUM>#RFB8&RH@-g@@(}q{a+}blhj&bghQ5b!eEfp|uMFex>KX8!ev;bPIv`J;(s8RAJn`0Pl5W*>wR@H`m; z5B@LM1A&CY>@o=MT0P8;*V7CgEtfW?E-um~oSw(qwkB6?$3D-K>C6jNw7A&BvF3{c z!^Lw3^)?wYCql?`%%mb;xlddfSK`^qvMV(tNOC%|B#N>g*rEVOG#qpiXZn4#4&J zZ$4mcknV^kFL+G&8DfNLgn>gMrUV;|OFhD9$w*m#G&!OyFl}(|-IH<2bTR#DGgMjJ z!(Jh5O(d{cQ05jCxgk)C@_ATPio!z{r6Q1`OY%}>SQ$kj*C9vETn~Y9tPEKLDM4u>JzViwpt8H0c4Y~BKq}+qgUdq8@ORT|2 zRw71XQ*N`pVrjeztlqIgzaH`~M$52D)rPgu&QfvdK3pq7sY))5)KStwdf#V%dFDse zCR9R}R^J%eD7Ou3$Zx9npATB|nv7*8;3T@iwLX6D^{2>Eq1_Ne(U@Q(cvJj3@UGVO zA7m}42p~z>Gm=5*6KjC7>H6W#d5(Y0CY9|x1{D52)Ns?ID4z8I-;fv@LssMTY%v11cv~@o#5^k z+#P~l+}+*X-Q8V+2Y0vNI(+~4&#ak;nTL7kwYs~itL=2xsndJ!ULsGTQMAM%GGkLL zs@uCilYB*gFB$+pvPon(8MLs@75>hfCL-SvpVcB-Z;jN7&MEhA>BAZk)qbMK?8IAY z)ff<8p0SaxhlyEOenR)%)ILDw`Yj(k=H$vtb2Y?_%EpXe!@~!FJYu znYr^i{v(PiB@gQczE{a^$CAVy-vrxc~@ozLR#gT%b09RXP7V% zi_Z%}zTOei(l|^1rh(@u^p)XRkIJJpF8RVbDh@+6IjCnj;6J2>SC6N|lH7^$krv*~ z5fc7^?KG)Pi821wGhpIVR?J5y?+}bc93*bjWruHWD*UiCM%fcHXy}o^<9}0rHB?)YLz% zKV4Uk+y1U=nWWddnoLjpILx|S`{OaSzQT^gP+40Hfl2ZYE2mx33kqMv|6JrFAany6 zz0)_^BG`tuINygHz224*z2D|HkqqIgA@?r=DD!VoLR`<=N67GJwXzXv{8erO|1cLI^p)Mt_6jMvy5F3q|(Tln%L|qgX|;VB96_9OJO& z9_*QJFy9Re%I!rqg<8{azo^LJq1(*FaxleesfSR z|4O^2Vc@D-$YUADV_8R69;mHxPHPFBR<>Q=Pc=fy0CDOa?YL>Efp4;N&7u5l@}aGn+J+%4gW94Q@8RH zE$6-4%C=d+Se9+zs`6$T-hozrS(bDc1s~Rie)E~qj!_>G%;qyWLD%4rUR%_WVT`qa z)70bcZVoea9uLWZI+!1A1E=?^0o32rxg8FVBZQO0PM7hLaBNt;sG70{rGxB z9&|-do&mthpI*pm*H3PSf4YeEkjqvpOj>h&cJFTP;BVJxWc{;kPW4Cru~e>G=Mk^_ zxEYEnne5=!2*AU{{>+TKGn4xFE7@8SPDgnT3dwCNZ~e}_FZ+I1z-nxahP{^cZG1;^ z0dYH7Dp_OwlzoP7Ce38W*&;Bb6?YmU6|0YEDeU$m1qo$qPpI=d-eb}q!N#%MN(0;{ zdKw9u^6aDq%>EnQd3uk(C;#?6`wdx^6bXZwir`Y$9eh~>jg~OfFuPnpro^0DhTSw3AeS^mYPsQ1H2vh$|0%qM_9 zDKVT6K0L>tIpCCNhPF?{(F5sXO!#XxPHb5?M{Xu-A9m`&SEC(|DDZ)BDniamj3Z$K z@WvLrXds~`=ty#hHzO7h9xdSGd!k=lEjTTF-z|EeR(dgv$q!RTyT379G>MZ*Ee8k2N_jT$vmMl?(ar-nu-cHp2BXcBdZKI$81Aba7 zVWree8MFL^N{)I?Yk$f{v_G}K?lhDRUavV!%=nYZccnV3?^GDq%aG5gOYE5mfvU#F zhAf~x+mZ;6C3MEnq5|k$vin6pX53v%^=xj&Wf;a7^;XY=Xzt(y-s-o4u)F~`NfzAo*kU>z0*~Ce-DbJbT@Tuesf5fYJ5YT~ zaBqL^_?|c7jSm&$|J%fl_%}}SFMnLyF|y-2Uqfu42Jlf4@;&QfpopoVBisD%y7#)4 z&p7Pej)|@|ajlEVO|-m>N5vH9p24%#6wj4$`!UxU&w$ROHW+Mq8+ z(7@{Py~K6Ftd3P%0XXWDuW5p4F#**WTpc`IBt2;I^AwE#X`gyjcCwAKsCFPsXQ;6p zSc$$RbJ4M@E%@r{^=KX+h5wQwb#5xzGSSmsQD{{Z7=$J zl{NnoMzT8(s8+hQ*?WoaO7VWP-Y|KxOdPr7igJuq9JiWrBR0GxK1_p?@E& z;qqvq7&NhunOEJ))w>&Om(J|?J@CkqQMT2Me9dG+TbATGEj*r>5Uv@QHCZX7>tLrL zK)la-=)aq|5loY9a~K^sz|SseXFy*|v`nV7W(eJ~t2N)9mUob|c;8YfoieI~Y#i)R z!If81A+ehLcj=pvs+Qj3L`thN$?x>==9uzIhJ%PyX{@~F)|NgMYn2|B|8?xsZ&acF zAHQA8s*le?9|qRXhZmUpsd9*B{r{_@!~8!T9gR&(Of}4Rlijk>ulX82AEbT~38?GD zuwjO6I_x;{jqNgXqDXYm?G~z$2(*u84HkOOiXg$TvwHHxcK=nrPI59I4;ilP*?37y0*?-`;KMF zd~RPI7)UQqo(}KXH5AXwbO=LaZi&i12|m_1qRIPvq&>%AkNVWvZ!E#j&;w&KZQejm zZEV*PuaGqR+qn;p+ho~D8BDrDg#9S(TF(_Cd8ZD`(bmBeD$OM2{5=kH!57(BCp&2> z_fYlj{P_AGJs9x2&=z{VI8t2xwhFn6<~OgTl++N#^5itzypn;x5R>7`-8v7CBgFqb zl7GMB8MUcf661Rnb%--_j#==Lgh%kr676w}BO19xm(fu}7~}^=#J&p6NwN{fw2*(( z@*OSg!HdJNzU3JQ`$%+Y$bR{|V(~`!;uwE>)03S(q2KnkL34yzn3;9{SJ0P~+!kC- zv!E}jxr1V6%ZP>Plk(xXQYpCN zJ@_o3G1ijSPPQd+Zym#fojiQj4(drq>EalWA7D9#N1uDzxU`qzzWdbcW!?$pxGz#5 zfSQf(pYC5@EIaEG(5mS7qbDCO6iH5J7XQBvrunUPRx@(28h#Mtj8)j zJz!66sE#Zq%bXiy)hazae4W!yFAo>#wQUR5;xgvcXr^dJnR^ejwEoorf~6UiP^J8| z1PY~cQO$~o6F|-2Fie^sxpO?Pc@&@VXmmRmc6L;b-+i>$+HcUiVE%hl#Ib>X-Y6_k zx#TTS%4L}Fz9#TOl6D9s61cM%C*EV<=<-@OlWzL(BJ@f-I@W3MBJ2Ym#CC(^IeRva zxX&Xql(*W>A_=D#@5JbIw|O%DGX`OYV1tpHv$_3KkQ&Ygm`EEal#}WwkM5~kdbGjA zn?l5;W=Q@woj+sThuw{wHEkRpVFKm%(w+OqN+3?r{9#VO@7x@3?imN(wwCJs=xikz z9Jp1t%|OW?X}f#+HezJ%%`S&H>4g%A@xLd@sc#;&BB^Cy8?oSEZJKWK--5dN; zqL@`RXdeB8g$O5PzkY4Q#6eJ#7VVqoylB+!-)>iPYk}**&U0WA!FMTVOV(JYP2~0$KtvU*N6~JuiX|qjrYHA zt9h8xCX>%$N)5{YueTNAf5gB3Zz7KCn(L0MT&OO&PxH_FQWJ;sfk9Mj)PO2F_=X{p6WC(M1@+D^LTs6Viw60cSR_{q ze~d`pP9!T`G<3XN=e`I0$vGB@;{+)vvva`-us8f=Qd;u+`pqa*3=1zbs63c9$4U>t z+O!i!ERulhzr>Ua3WUUX^xD>)c8}bL=PQ2iqAs7UKICT}Bg(SI8NRmnOk1w@c6Zm1 z*{BM>;J9B2s#V*(SD{}&7pcAdM~D60S<})-yR%iS~FVF%uA{xIiIqda*T{qK6FSvkn?)KEcCid7N#Bst3l)y@)x}rZs~QzQyUe(kh!C@FY=iOAZ>+Z z3Mqd3{bBJ|_+}D19rqhDan%UYn-T=sm${h11Z8~nqIe~PM+D)OLzxkvWKgizYl)jE zLWDuog?_A5bq2@m6P(~Lik82!xpmSt5Zn|P0Y#X*R+zAQ%^W_Dv`q-PV8X1S>7={c z@|a5%%HQihWMwtQo)6p?=~u5YHf$o{o3SQiWTnkqvJh3PXBZ#)QuxA5F8$LZ{ZXWM zxE`67Bw6;9(uN~^6vt@m&p=Z`t{qX&OKZryMO~XQ)31CWczDpkFwoLk$4g$A0;#;tXV(fe zDCMaj+7SA@S}^Dcz1^5G#D2&d#ki7AZ3NW&0Wm7$Y9S@<=v%5ke6lO2He)P$x4Vf zrT7VM!QgIwga>*y*T?uurDUNwP9o)wRvJnD`r>cnEB{U*!nA{{aSu4opVXN^-%91> z`VIPSN=oP_w^j)CQUO-O+??0vnw zOjKrTG^sL99IhqV8Q2Hup(jPFSg}Z)l3RisHm;z##W})6nJ{J`fi>etrKn2Pw z_odxHw)}McB~`=`XHTLIMWvA#y^O@*2=(gJ{BZq6!uK^sGrx=tnR1RtwU16WSKm9; z_Y46yX2P?>^=$eQo%Au{H=*6$ew_Xt>G%U;@W@qu#8+x|ijq7MdX^hU`D!YtQp(r_ zD|WaC*M8*3g!C;Pob5CZp2K7$Uk}0pU=(LFJZ$*F!F{V9-d7AmQ9pEg7PQ)wSsvy5 zu*Img3gzWfH0%6{tQhgfrnVT|YetMQS%z$S1F3&nl@v48%=qp>1?nd?Ul(4KFAZIS zYmgw@(Yb|qbK*5y7l;W5AuZ1L9`|8|<0K>O8E!$7PpOPdWDY_r+uPay^wdBHMavm~ zX#9v;Vw0C?M$dU{gr(dhco0|6zjqB#i_xo(vUygzXM2PgEs@yR098D%!lgWAoXpY(l5@EzFsTIm*D7E!xK~45cs_;sm|Dn7# z%I1;|LjeG{pG4#T_wQ7n{aL`LpuWL>6nrC<*KN|7FuOLDh!?e(h62E%C2TV5jGC3J zEatAUMm!UE%Ofd8>sm4%0bR=zdQxd-;R8^7p8SW8__8i<>WNd3OOf&p`$GJ71dOGI zLSN_}PM?RQ*G^w$id=XEzng?LVI>chw~#~?jzObjCn(ZAv@^3q3oK|OE9f0V1PI3X zZJ$NqlZrMvXff21Mw-=-W7*c^w&crFK0--H$e=u|AoUiZ?-xQ4EasP$RgN|7Ii_m< z8-nn7lHNbaXjRR{r9*M8*}Q!Fk#-`U1Bk%c`2pMI!^7Mr-7(+TKedkYLw%u=(SuA3 z_|0A10&q)`QWISm!#`~d{f7D0Q_hAkASJNnX|Q_je)t9<2R@ObC%K0#xzu3 zRvZcb>wi>7kt8KV6aj$GZ~!1N>?e^EaJR!_1OPw+58 z39xqpSh+ws{l@STRJ9h?a#7H+kudO7H?Y+*b+@%~rPWKJ(#c^rif8?m?}g+4n={pe zEBB{GxVTl~Plq&l>qvQ<7-jn?73U;T+dN6fTn*1WH{mQ-PNg7el{ibCf@+JYO^1tnm#2&?Uu;N!(y?I=R zQ&^>AK)YLfqeoSWM`@XRVT*Rmh-TEeepIh{@{m>fykkheeb$tH){JrDj&1I!P0pN6 z$C_i#q*MO9UG38E@+~mDjX$DeFt%F^p?f5$S0cGbCW}`ti%-yRFEGAeC}ltxOJp2F zKq^N_GIx9)r(ZsIe3n3Ds(4Ch3ZjiTshIvtmXlSu?Vv%ZkjZAorbZUc2WTQrE zg?4VaVOFzAMu}NrnR9u8M{SE~L9=UFt7lEWWy+{aMz2-TkXzY+XU&XvVRXmubY_OR9f1BxnCB_f6aMPPq*JF&&(7SlSj{+m_$jRtWNs zj0%ZO2#ZPxj|od@@T<-YZwd>mOG#}HO=?aKsw@jEZHlNVOet(nYA8&sX~>QGhtgME9wob>`Q7G3alGSYZ}YV=-Dpm-KZPbC>h*o9N%l7I~wz^ z91E@;Nox8NR6ZA8yBc4}K3>@~ z-rm1b(KFZCf6~&|I|1&WD4m|F-JEON>uo>$(|t42zSKW7ymYd#eX+cMeKb09{AcKR zY36Qx@O^3W{qNkz#_s#})cf|I_nYN|^Sj`e@*V(y7$7MksO+|S)`{SWH|)i9)KGEO z(p6wyHb)s6Aan|{de*t1Gma~oQf>^_@N1?UlG2krYY5$4l(N(*QM8cQcO*WEeb0Q#Tk03KojdXu~IhH)c8?}~=l zK)K8Zm*WhSS>5OgwuVVt6w-Mut9Y(jf}nEbF&R~k!TisKA9}fYisK!}6uEsos<+lx z{<;{b0?*xiPUTOI44z}J_0%4yn-L>Ki>9&y{qeZypM#jJn@tFeEq-(({S;?O|Umz1}tAjBQ*{5Vno4t&# z$X)(*s1})TVM`G30t5hc2;p*5HYC@tEzp|ZCwvjCb_6JLzbF&71T}@(gmDXq?(4&p zl&n2t4_ZP&sAn^>RAf0vfVX4OikpuHG`Mj*mz}_>c{W9P$UO;f2B$QZKKBd2gkWRU z3P|~?eU46jAs{K@W$3~i76ZXnCpH5CI0_sE+-G&-b3OANh7RT?W0MiRk_awk1G!zH zaJ&H!NHaaMig>!;^tj2$G4(451mG%Olbu3b!2l79PgZQQVWRF*5j%;g#gzSGEAi5D z-}fKUO1#{MntRAS8f|#VY`S3>t$LixF7;?T6(ed zali24zg51;A@TFwbiPHSTzqUy!#sHZ3r2bSl2wBs;SEQ|mq(bzM0!nMmLqP@y=V zB4s>>S&7-W2(Z-@XBJVaeXayP$o_0E%_S~VKL!LHDrXNbKkpGBp@LQ7_Y7ZojK*U0 z)&B)4fPoMpALl5lB(K4)>+*p2Rr*fT&5iBLbMNOUBJWdiaqpHx7!U8-!}iS=^WlH( z5B%?$M6bnE-L9lJJw6+6dp%u55l(#VC}$fV13iCS&peJw#j`eCD==bI%uxkis2`yn z6w|^Te{IU|>|g4bKH5+#O7E8_S9nC2_^w(av7500Alcli)F9?6T$CDxDs$$5Kc_>L z(C6JEq(-4MhMdH4A((=qqC6Np#R*+PO1=jw@I}Evg?zliWP4vkm*Kr%eq5D)Fs`}) z#H^`6d%@6zEA1b-R4@-nyc@N)(>_m&N*-B$FYvKCfRZ10mYDj43WbpGa*QXAU|*bVAXLfR68#0#z~PzBFV@JQ=f1b7NQ^?gEtw# zQ3|_*iV9#K^Y}b)pW6)&+OjIe$J=6fKS=DJ(ycEu(_XInZkHqPZ~WX2MMjr1p@&Q)Ld+jKZGxHZ19#gz*S2Gtx{H{AuZyvtTdWp71L4egB z2x9iwh*T1=;!l4-kNi(r(p}0hmCb8VCR@zu-oUy+7c_EM%oH@1^nUioi1Peb*!GR}*zoEnE<* zpu8O7=oR)IU>Rs74^F%c1;auy%@Qd*;^O$rksXP2DU;1eij33uLANT!iT!&V2-1hTrvi(@OS-+LY|t^^zAdqd}W(th!ZZy$zLqH%IIn<1UdL) zECU`I813m=nS@ER9PJJak?T#8ns6`8XB)S9zkC6rl`tW3$q>^Cxv*qmqNJ(`6aJkg zE&zZ0tw22l&Ydb)Sw_7j?7i^*ev-d(iYlpY~w)+6`4rP$u=J+V=6M_x%(h z?+w58Fch1Ta=N)N@FQq8`wO`R+(rj{0bdZ-3iT72EcGLuQ1qTACJHjl4|{4Y-oEgWmWFfnT;QV5cn z!F2c|z+8cydM2a~4y^4q%2Zwk*?w@Jd43%ic3ub57l0;14APmH1*MbkyLwVTc7y|& zGHx*4zyj?xER+E0M9iNC;EP#CK!`xHA@V-XW6e2E{W6thbyT({&y2JXX^g=HT*R}? zwRpe!yc@A`3L6T}Hp^}~5ee<{M7UiuLITHjK4lcfT!_wE#)i#BG! zT(;gvBl}-@Q6CLQS3%6_5A(@n3hh9*_lvY|n1$UHD}q~Lay+BJF-ik-}*QUgn za9y0xei~1krQ^m?AOYLJ?acyH*wC{SL(cQ4Z4@uD6&N$OXi)st2B6rW{AKXtMuZMR z5}{hPL3)iaG65awC$qww$9YIj3>J$}k2Vq@AZ=?lB)0tP*`4dA>t^!BD{s)IRv_tX z>&$A3NK=$GV-Jy zGIeW4)|ZE=wua{n^e3Uk0rjHt0wX-(+lN+}4x1qn5i@Wh8_GFmbff#75E1cBk1U)3 zFjzZVJ>Y-&abv_-P$LBTlXpw~+i!ubbau*z1$5O*o?W+`W(^2FVXI9hf7kMq;}|Pi zf$Wc_mR}5lz9Fb})gB$*GvxQlGHUCuP1L<|^^#`^E}2dAfqTyg0}ljWEJ8z0TXtz0SFly+!hN zIv4>@y>{58-^wYgJOIlM9&E^09D+u) zV?$tE9?~zcBiMALQW9q_EC`*AnDZNa$i{?<{m&&dRORDvaZ?+ewvoroETZ=P6rk88{!D}J%NQkQ+yv7DPxB!DSXm0iQ76JaFxMdJ36bKX zSdNMY(;`UxnH!)_q>df1l|!>e5P%94{Fv-z5iQ%EkBducu0@ z;Mcwk(T^aI$8A==c-HT&M`B1TC|79Mo|7`@6c#vs$V?Z-lN{t8R6l7IToIxb>2tzW zo=2nA#gQIA2vm@IpYY=jmO{XY$6+A5>le`R9eXy$dy&aYBIz06PYk5`<8!hF-#15eez268W&^H2)+xr45mDH+w&~#e>p}r4{)*6sB-E`F?QNVjB*zaAnP=0UH*`3~P zEnMwTET9DV*x2EkFg5-F#9S91=ET{zr9Kfd}X!4L~9-lXh-Sy2JX#g??{dH~K zaz>!L{-EmHo8+YGai6J0Q-t^KQg;rjx2*)KRF5Bfj1^`$s!T;MJ8^?TJ+#gmc*VyL z_I4ObnKIJV5c0gzidtH#U{MDPjEF@xWu>3)F^WY6p|c_|j8}IW`9%;1Do6FX{iW;o zrq_cPQte0KKOfhn_0AOV$Md?Yb!ISE{v(R(W?(_U+^Jrh<#S_qD$Vx5x5?g*rOWP@ z3+sQ>JMYytowlb-2MS1fA2+IQ-kWM36Q}}*5FGXk@avJ*>pxwgpiX(`aK&T!I?Rc8 zO2BqL%ZnFbx^<1Ubq>g=0x`s!-i73j7ZUHTP*i#}i3M@nB5_-(5JS~wXk4WtSxHhQ z9HT!W&8W8qSMMe#CmALgT!2HIyTV8eL39*(g@d_QGIy0+(XF!mLAxVVW;R{5E1DDX zx*O*<>&rLkz~Q)6vcC(OT1z`la!-W>x(!uLjVsru+j;lqoPU=g=~D+jIC5JuaP^rD-%kuE8;~$Ylu;~n_W?i*k-qDzY=!Ef~0KPpikig+XlDM<%houv|bDIL* z#y{!MUv0~rJ+bb9)=Cz(R)`7RP-&LdEr8lFj;+s_;y^rO*v0djTuh??h-*Y<4lPbG z;Gl=)zrk4-^_<+-DW*@$!k_!*kpJm)R! zH(~!gX&5GZ;AQ9X+P=`K!t-a+HCNUUAvPv|rKZx16Ve9k%Xljyk0u^NP`jRDB|9** z3Bsx%mv+31(!tOCEfV3F8_nLA-6>g%`{H_{!k}c(AD1gLJ+>5FPr-vJUNx6y zl2^#;iyR!hIvoI$z86zxg%7%iIe1Af*?x6oJ-IgFqb~k7vB0AVn#HWY86&QPT$V|t z6~@lV2MckeW7Vy!&5dgay0LpaE@;b+bhRb4znMB9d{{}kxzi+jxJ!=o7Bv3_InmO0 z-m=a!z*mwDs_6y#NCzslv=D%1+I;cwBq%6ZnJLF0_!4sf*t;x%d)Q^%6s}?MZ^Q27 zMEl>DY)@`Sm5$j?XMug1T68_ZNP)4uXnKLdy*0RLIlsu^UED}5+$!?9$B2VpoFkkv zT$thN(Lh*OM%De+#f#spm#{xlVxIfy?Z$WC*H6Rlsd$~o64DcLD=$I2DP$EU)T z@gRNVm;U6<4k@PE4OQk#1y`oD@QS^h0V)K?8)&dxpE;B>D|itDSN(hSsY9k___-~G ziUsj!3km{e_@%7iZGzm2NvuA&Z)cP?z1!;JkOf+O9jhp2tfK*B?J!yT1pLUB#mK6H zJ=+y_9{2Z!&FRrm8ErqB=i8h-s+>#7JJx)`Vl?gBM~XI(_}jJ_eBT|6h3}8XgE=#J z5d{Splj&d!*PyTGOOg0_cu4X2GPSJuem|O0uwv`0K(F|)(y~t#3Uc?0DdELq;I%rj z#70vn&AM@?N%eWh$jJ&UZ^5dy+nOKZH*jm4C~+Sk0~0t<3?da!K}$h3$Y|yXTUb{2 z&Lqgr&~BqSx~TAqf>WY+5|_B`yQPOPK4(`QQ`BH-rBIw*Pz^qbz@g)(XqTDU5!Wse zR7Wv~tn|GV9_Y;U?}k@v4c`O0)tQjvh4RH7tnBm`RV3INC9UH_M86cp}-RSRKI z8M(meEK=)P2r>S;oidmE^VReQKAN00SEo}GLG%7-e=!1;-VYCDt_*+c4Nb0&1fC9{ zf+qz~#r;J2`f4`6IO?D-7^Rz5;MZ0!RK+2nrWPz-&6Fh}V&s${E#QQbhhdc`9#m~Vtz*S-jd5&L`G*21iHV23>IKYAUE*=yVjg*P&=m%&z(f9oA z;vX+SoIGwK8TT>25??%y3BvV)bhiUyMoQAd%M1^Y^M?t-Ak9RurO=w}>45hG+S%tMV}n2aqz5##ubmBMW@z+N(Z@yk$e?s)+1-Y zO%yP4ln|>8W`l|v8p^pBT!dbw0v5{tWwUmm&c{(>ky2%Ax}uSHB$Xp&D>p7usLtoo z*Q?}c9cILJ1tisWbbIojBQH8sM#_F0wZND^8(aB_mBR_qcED5+bdka<$7COb*}3bfDy z7K-8`_m8KdaLhnhT}UJ~dP&dV=$~O!63yH3my6E$U?L0}D!4VPdp@kYLV03tiY`fu zKI#2cpnZ<_*(2o=m1P`*+UBP(+Z!z8dnJ=ZqvGM-)*Sf&zm%GlH|16;vS>c7q*nYxU1zIpmHVZ67U6FY%Eq1| za%c9nmf3a@-svf=`zRhm8(5BJ8-@Qa*rx>Ft{?5D`~D@^AtA&6=QE~4Fvaty1?FZ? z-k%9I7hpVvX_VGhc%obreY&6EOc}+GIHi;H_hnD@s2BaK_4gGroo~r315(qdd(WK3^deZdD z%o8Yn{q;(bmqBe7-wS1pUfSs|Wi3`}2_s)vc0*LGw1bb}F0|WVK6w^u!rrGD3)a+yXKPh*PMd zpH~ARQt0^&kZQEq2uuLSEJA-VVbF5rsn<3Feg(=17O=-nr=`^lw9qs#1{_l5U?xPd zK=nV9LockvBg5eCi+jM%Q7d1gSbSB^ zz;vPYw0SA^Q0e)FK>(@@@GYh{wobtW=RYc-!&iOl20@s zx%73B4T6F<&*8*~KV;!WQhspinG!_=m>78`q)Jf?QPB)-ND;{)=Gw6a&y3jiO@^lt z$q2%cKqB!nT*?o+9jColx2Ut+s7P}@CC5Il4Bfg7hk%) zPSp0IdFcZ5RUS-Ipiw8-z(((DGFedsVX4|%uKK~e!fyEC?_w~jWm@klO+h-!8xmJv9-2!K5TvA|O4`Ei!TXo;{b7q*esPh2#D(VhE3v8szbJ zO}0`klL7QlMSt7Jmn%mxy9Kh0%y%5KGZ#=H3p5H!pYTsi<3c9D4zI+TBumZ5 zqWytbPCzF=*YF?1tj#}C>zrD`hCp_4jVd_{3AXQ{v?!1%TC=s&Asc=vMfa+%=Rk9OnDl{(vgQPob`Vgq!8_m!TSl~ zV&h3Yrh|;-Twj)k#^n66VAZJpLguWwC1UO&hMO37YiT-HJUi=~t9nW2$5Shm@Zrf1 z`JvHacpj;xBrZWIpOKfbgcBDamIeI&Re>PJh3GwXo z7{-Q~XS{sw&^))SQ099bm^r|@g^RVsh|E1*MbW7p#6lR(`v}u6>Nq0<#IHd>DDm!UCNIM(McgPlyIj z7}GDhr_?!FidtDt>&OUO!-rJVVwVJTvLOmrNkE5?Ftre&u_VvXZMq5{!{Nq-B>ITV)O_C@1o2 zb0@PR={3PbCiWdiHcx-CfgA*kHs9}xkW8y^0faZxk=@JhN~{Z8=G!+iTl?anpQ?9L zoQ6PD`TYP$Kf5b_5wd%{C*LlV81r=&g$)9iCDj#7&F`fD6a!Yg(;#s}v%(mZBf>lO zG}J*dZvguN(v2r!BoTt8)-7a6x5@0k;r@vBW^_p*Sr&MX1T8J}#zDfPuGLsyk!Kgg>LzH^Ea3aPG0 zDvQdNu2vyoUJ*K=?;jnGE{?`!pVj#kq(dOTC(*N`>E@>yQzsEKmT^{ za!LXps6c)7@o=Z^p+ZLO z+zXzQqXqpDb2G7jDiBs#fKkCEk-yJ`3swP2b!Xo#QL3(BVG5AD{N0%LgGj?dQIx$j zyR3lE0~~+dhG^?3PwA@PHLhsZFEi39nrM@TI^oy_TU(IKJg4(k@Fvy*Qp8HMsE{kRGe!u6}J; z_*`$1{H77O2fGX-;iJm*U}CBRM!~Gn7G`3FuX&@U&wR%Uk&POTuRYou`elfbElbIA z{fm|(D-2CGZ*9I8@p}YpDZW*|@`Qri5d?4s#Ljr0iv)7nueGfECB!Ml5%zmwp`W1; zkX;m4Th#zbFRa_Q7o@gvLn=IujpC^0iDAUROAPGn zN~$`e2dYNyNCs9a=uemaIk#F=QOyupq4Qre0|16F3TVOgd4L~-6|}O0)X*s=Bqd}T zy38Q;#WaN?Q5+)GOdncTK;|kWri0<2f|U|af$_|k`eH9y6j(y(aa9N?tBfJT;{W(2 z-*b^`wqi`LQ_2V~bGtTkB^t~)-I=xFJ5N&!L%6y!rFft*an}|YZ2tubRG)N+{T#wB z;jM=I*6LsUJg&88$D|+&(xM8(UX?V(8y0lEK0PadP^Fjj?YiDtxziLzq($0uHTy z>~EMNk8RKR3I{!3c}UbtrKi^$KYn;0WQCcx zNVqKAkcspZ3+20t$lBfN{2!U^@&2wIvKt8=yE28BRuE+2$%j8|B(=r zws5YxWL)l;ZUjTTs|3aqO}?dj6fcMacT`dWT3EKP#NQ)bz~#XZ@9cNJ+W*A)o^7!q zI-(&woxsuFdt@5Nn;7sVnoMN~>qsNjnN>7hPA}qmjBi?JfT&HJx$V54p&qt{4l#kb z&Y7NaApwpSw)vpymkPkfSXiqNmVBN&VPL2_HptebXidNqPYpAMfoiXeSG98jiC16} zehaLfsD~0HJ-9=$joyl#Xk`3<0J1<$zjs)p7Yaa&18v;6k@CHbRnVM1+UnXt0k2m5 ziKIhyw_3w*-s&2DzoOrU7er4KAZ9lr#{ylPpQEd5tO6Y116&LK2eIgDR%4|Qx6(Q&p>yT893^Y=#BzVM7FG%?qkAgqX2K3btOsK>{8X=Pdd0s(SMO^ zz0^h+ulead+evXchjdvRUl2V}fRv@WWW&=46QNo8$KjtXuLIc6F1V&qz*2S<9_XMt zWrG6j=mt5!QBP%$IH4vO6JoIUn9c3sAiVB^xzNrbl_~auvpLkH zX$M*AkmSt;kyzkQIPkfGv6q{@2tj3S%892Z43Y*z(!V9euF() z`zv#m(O2eB(>4L*ZkEDMW~m_ZI^7fwB6FY+g+0%P!lAXna62{%Lox6GAvt0l4OA;5 z>W~jEAh(f~XS1@}+fuD97IspFKk@nEK zV6Z*7F1R+hj**=Z!;5vnP&;x$jIY8$**|f(sXeqdgf4N9faRJZ!7!^?gpd%x83kxd z`WZYgSpSjh)v@vsfTs((YXb#fdMH<_{sV~!0$``co!>e`oBWj^`@djlT~lan)4HZ` zQxL3eS_{YjI(QKZb~UNa$$`As?K%dmYg&iBkvDO$AHWjpKrqay4h9TFpwla18bCFp zWC>e6%RhW<`jv`Css~yS%i#j5#0FRaK=n|-CP-3YgXqB%ovIC=4gtDk@AF-F;bNfp z9LHYh0s3!3umH%5nl`PK%|k)39cO96Ho_Vy$ZdMTn+uSMKy8rgzAJ1Yz}4ph#44hI zTy8bMjCjz_r3o=nEkZHK%&$wf_X|fLHfZXiKvysfFC(a_36CP`gd2)tBQQ0*83GDD z`H(jkAQOQ=hX7Xp96AK>-vLaf~c zxiYzES{EQ{p`P)@1+YM;0D-&=3kyhJG_3Bbr7{%Yn_907PAr=ZP=!Fks-S>-n>V#u z!i}xYVN;uO(vE)G+RW)~4&~Y!y#zrT5lAorFeHB!!{a>}(;e0*iDOqE1Bm7I2fyP| z5OpC?RhLtc?ya?%&i?`8LiK>4U8skWOk~s2)KYpu_;>SP*l__SzB`Mo?peb98sPX$ zj7p|6(RU^_zg{XoI96GEu2?%&Ei4FAFRcz=A|0fL4uLHL1$Zwxu?!qg4pr#yF^hlIyJJ^TSPdKi z*+6HvmCTARs8HzmsCIW#j2E>yy<=K&xCLvHAbY3GUU?sZ}q5z8G(vswEv$BG6fQ?ui%u=aT zENOuNo{JxJ9Aljo#)c%uUJxXRg%a_>rTjWWpH&utq28nx_JWw!s!UoM4@30OEaM2YB=F zf8QYJ_5gD8Q{043Yf^xZap`zWU)>wkJyy)-ilwp%Hs3a!l{1;kAt=w?cVAC&1-J>n zx&V!g(C&hevJJD$p9QpxdjIx+sMIK}Ap0J(Aurz-bEy=vjLZSy@BS3^ZD z6M^@BTz2k?9m6&&{$+2pb%g6XkZxtQxZ6K|z(El7=FwjsK75kY1pt6Xe>4v!l9yxo z37*eSu;Y`-cyb~?na(FMzoM~$rD8T)rUi#zPt?m|GApw$kEwT*XC8y1>DkX zbw+z*pmF8H&5jm9CE?w-jrIV5M;#!YO7oXlZu~OOCDG#DiR5HH$@58hC2hhcuv&Pa zA~pUpXS@~{rvCb(v}!b}WYGq8Gu=$Rb(Pd^yMyVrUrf~|Gt=#e0t}*H0tF<~$$awi zL?)3*rYADV$>cHso>qML-}&Mx_FQpsLZe2s+S{ZqZYt7WGuDi%RjQLx`- z;iyVQ!LCN3kPM|GaTbgLo@aS3&Qg2~`_f@Kz()aE+R-crC<$l%-~ktsePt%8i_3y3 zurt0se?bufRUHE;f}mxIf=|=_my4k6EP_mLsYn79M`enqGRn5kSwz9_Rsj!Gf&;{5 zVS(OlyWaM{{LzYV0SeDfih^tHWq_82kRI5(`f;%0Zh$hSvLrkj_a>nk!($DQZJ~S{mM4LQ~lCDM7#VV~G zKrk~;nkZO-06GXTt?#JmiR>nZ-R^m@UiHY|IHr7G5p5@BpH)nn1P;x%_L}W^$D;ms1%Exc8RUW_5|VmX=n$ zH{aOO+T1F~wytk!l@B=^{L^+A+U3RN&_y%M(%cUJh-59mq{e$aP=E{pXh+KeR`yq{ z1rr1?ex7iqG)*o+O8gWCpQ<1AjlHtRvQH@Pa7Z>`=;{py)oOQU@T2{R>83@v+2cO@I zWP!VXP7e&2~O;;>d~4)AWYjd%;5^1F}wtWzCj>*zth6c)SM+Sb;tu+T#HN$v7*2uQnS{ zl4g77vz3CC<>&$otBxw|T}upqg8$ZZJuDc0{@Bp)ks*WvjtmbS89Q=X_u-<00C|-m z8FsLqf}jdtQxu%Fi;gNH-|JGYjy6CL7UrgkTX%@g^;8K5l=O)q6E-NoAb?Xvy{JGZ z-Fe6G^Fv%GO~cb6=7=J(F$jxU0L&0TtPlk_eOS?fWpYpvU41sd=}j+u?Qg9BU=`Q^ zNreDZCT=q~kRbtpcFBU%!_N;t|NQgEMxGxY#{1APV*vmL;Ngh^Br`CO{HR&{bs<7U zbdCChkfyd>=v{Ym08v;t*S~8U$pfwy8-Ufp@+!kAKCaHm(Y1kuUlv{2 z3qb%a2QZ?Z(d5d8B($Jepwa;E`QQRdWo%C0+_oEJ&ng<{YmyDP(BHRnRJ7BRR~cks ziy+i&G{>i6_B4QLF-n5er$&zKWz1@Nqh^>@*Q@ox1&Bha@X9}yvAtRb7%3HA%?XQz zl5lOVztBHNS^}*O4p7;^h{b-x0|MxIJ3G$MMwtO)h8CQymoO(3kljep8@sFzERP=qAD#HfV_k;j&cBd2%sBk+%Yi1kiLV> zE9NqKEv|0j)N^DI5-}}tiLN8E8(R1uAr9&lcNAc5iBfTTdcJRAN~$sjC?5xu`g=!r z*}Y8FaeyRS1nJ@)&JP4ovophIj*(WHHV}ZtQ~+a^12}L2_S!(X`UrAH5n%w0Dx~q! zu4A4s(uDM-3tPW(u5a<0LjbSO_Vq}vFH^0xG|9=K6_|x1lOVrl;hUV?#F0dgViSnH zh06ymJF=A5&xHq8EhtmkUolI9yr=%n7e;5c6^gzGcok59%^emOcJA=3rz#7n*-j2+ z>U~ZQMM~ms#;--t3%)FZXroFIu#DmoN~I<>!$4Mt0HUL&#|ra=p11m@7Nu(m1xQzf ztaMc{0G=G$h&OqM8{BWi5q^T-j4QLPWpJd9gUZb!YF? z>~!Ip!2sXrH~58J4Gf^o3__DbwdO~sEoukw*l=i;&s_60E9ti!@^2Ajk|o;M9HXv5 zjd@CfY==;BcIuwoKg=y&S&s5p9sX)GdtR zykld2twMQ!7D1}Jc%9gE1OS}oE!-RQCIab><47C`ARkEkTUv%T`w6t;0(585x(1Tf=b9J+fN^4o=&v!=aDKLD=f&4{ zTxF>cexUoN=eEvV9Ug$39BMBIaN6kM?~ZjGd!Fqa8lzovYN*_oAgHAJ2_z;XQAQs` z1Q!atP=K39UvXi_+1`tuWLp)K0p`yCT7UmnM8SrkSp^Iri-Ii%KX!8i$qjaZBi|hX z9Ub%o$GsMfQWZ!>lQ4%qAnH@YR4rmOrOa2sPNRV4Fy1gZ)CUT%DvlTDdUAzQp(w2+ z1`vIh(n4SVj(K~gcon9Gi7EsjO@EA%K)VeKV(89Jx?`AC1xSv06;$rHUI6jpCB;vm zP60&o$vn%UNlW~Ao-#}tDH{cdW`MCMW^-Tj&k|NAoR@UJu&bpBAZG@t4*G2&0OPQ+ zAoBe1v7uu_!{jt>$K39^0KqB<(g6SusLTB2R6drln0~eMi8 zRw$i`Z-On4IzTi}5oSszSF!$?0AtRgc;Vvc=-k}>OIHQ{mx_Ht@8}f^30P$uKst&v zKmcXfc|ia?CE$+l|GYDTTM5k6x7sy;%oevemK*is%3K;^>^!5TM#dp#%y*_5cQrpFaoy zK<9Jc|FI1JBZEI4Iz~H}z$n8J1swgY+qKar8My#N14N@ymP3xAc%CIzk78XGj&u^w zI}WAd!nQYh=jK)n1C)_@Zocn}(@rGdDj0>9WDg;Xk@fbZ0Z^{ffBfJ-$*sQs$9L>g z#R0Ye0N7attM7X0;{^28X#iD(L{TR1P=r;&X##VX!v$FLp6~D3zIAcc%|)e!{{C$j z=ZaPmu-bwkqdfp%jl<3-1UTLq!T*o{@wZNNf(~Z@U?lKgw17?2#iMStUH6ng1nOTyX1BpWCT0IBY@JnSjR z{ZSMjI3gO18&I~1&AhFk;U9g_e~2XW78_K0cb+_^tms-Avu$P ztKlhBUOALck>Wrf5P$&#OlN239YZu=Iyykd(BKelwF)Gx2px<<5d>HgE*;iTfGSu@ z!3L+|DV~a^;vkxeb@Q2gj0edC;F2-rImcusvXIT9g#${@*RJ67< zp|nsq+cUkeutF@BcX(}T>rVT{fvU|0NaT_+XuwUzAe|leD+xrD|Cxc~XU-hA_zJZJ z0Iq_dVs7d1Cr=VYAnY}e=cl^!Y$|n`OJ`zSCLN1TCKKbS%jtAFHMvpI4;k6O+?&~4 z!5~*MPb;!R0VYJSu)Sx;?0l)RZ!VRm5H7to+uyTfY4BB^7a*_E!e>$wF&_Kj_XPp) z?7)uGot-xR`>dwHxFb-{IC}~$E@?D?3;{M@rlLtW=DBxb9RE(7^j;0c9H07TL01%;nC0q2UnU!D;i%2tyNARJ?qNdEwgx@e3KNgy%_7&<-% zd_n-$$MW$64Zx7vX->6P*X`DoDQr$Yf*>+bmXku`_=;B_FeBU2+|Dm=yEt2@J^q zyF&3bVZos96+K167$u#1Yuk?g3J1SG&>_7(b441Rmh2y6^+Z7jO)g@5(*Q_B!_a;q zKshLY^*7IeaTEn>mA#!E{o=*`h5iK_9#M1`3kdx^Jz{ZJ^=t*C3)_mqJ#UJRB;e}I z4^~XVxH2IC0FVPfVQ}EgpnP^lu?}>E08S{t=q;QwFB@2fw{V+Kc;l@vj`sKOT9}6e zvseyISX?M%dlu#{mMz`)%L52=e`}f#cr`>plEMYxNmm+Z^+N+hA{0DMN7x7oKqUvA z!~lS!+>Juz)2zC9G%7@U002L02TSMYa&Pp^Z0ny{n4Mm9r0s3y5=zqf(H%3h=L_Yc z0Kcsz2`@WB`c)GJl~gVOO}f%3FJHO?|_adxz)cURSHF>*Q4W|Xg%C`csM3^F<4_JRO1 z#Gt?ZQ%wr6=c&$!Y8=eiI**4C;E*>LpqquWgn@x+!4fZT;dcG}{NnWJ|Jt){X5s#M zN#_eZAS~=G=caog1W>1wuJF8otdH?uZ4{tF0D}{5F9;yBfz(68XJr2%fIjkc=V>

(JEo-y~AyI&r9Y#qwH#fI)^bOda(!!K1FqFLMZ>fm9%k&EV=aKn0 z2+8GKL_0O?4-X9u9fKXFPi<_!j4=v z+dH?be}1u0T#)>r@6!2uMy0P8SG)mASqFF4t04-~>&8BUWIsOsApq_xcYN>3F}maV z;pc}z=d(|BvQ9oil&;`c5Cqvk4*1!eXaF}(Toj6nQt_UdFK?flnw{+}_>$ul`*J(} zUJ}cI?cO=D*xLEdtAqnYZ%?7lp>y!(&;3I65B0#4W8P~F9`Zl|G7Uia3;-Ud%O}2z zvvaev^K%z>ywtaKerjQAQ838}thAsg&CM3h7JPqGzW|`%T7H#q0HOiRUieiMz`6!7 z76HQwk$!r3c+Be0-xyp4L0bDQWwY70eFd1!qW3YnK5)X(MU+&Wn(i6hx}$gc%0l6S z=wU~M;D6Tl8r439fmx?7E3G>0r+hA^ZecJ(39i{`7 z_{57oxPX;2!|>4uAYNZA6ncBIg}L78d7cYaVnQhy9diwfi7v^1!3*XJY>DCs0 zTgx5**gJ~6>g`pn4j?!=gI-A-Ao_*|=Y`px?O)0D_nw=Yb7T4ouR1SG>Hp~qSx5S1 z)lh(VH9J6+P=Lcg^jWfaVR7oO@7elh-`vzGxV^K?iX=0aA8jmogvF$D#WP6#ZfVKLiIx;q%3Xh3OrmTer{m&Gq(LaY0F6 zqq=MiP|DiVk)p85x&Tj9RUOHLn=T-0XQJrnt8UB>Gw6`V^IauLc&&HW{LDQ&c1_KH zV_vYZ2513-%7D$;e~qYHbYH?}Wl(^WrI<3*!LQhl-pWLCcC6t7Z03cT9ox6PwK&(e zc;0MMX5|Mo93;7frQ8)!Iem{f45vh-H7A+)p2y)>(0l_!fY0X!ek#XKUR;1WF~nDJ zLIF-D`kp!#TZmN>ipAOOqmU$kkYLV44P?*S6YkQ#y!aD`@ZwJnzK8=n!@tUblC)DI zQYrP~i{?Z9`-#KxqGKYiPcG~HlvjldD3t{P=vQ3nKX=-|*^!i8xUe|YyX~c&Tjv+% zrVB-(pi%KlkR>cxfsORxk8f&TPljgV*AkkQlxF3u*_zd+Q{dTQ; z+4Z%TL=O~DG0os^5!9vFD0g`roV``B=qy{SSeu~KvRIt$8-3}^yZZa5FHDjAK+alP zJlMs_o4j{%bQk*VRHI=m4G$LXn^|r%%!A|5A3VBYzRmKra&S0pGutH>fPw8^-`w5h z?3|hHZfoq0*r#=Mx3my0p4F$+mXGJ??prU3LRqVA`^N!(j6&tbAzTj9B&;kPAo`UV z*mi@tg&luAx_z`rEIU&Y-Zq&EAW(LjV%sJ!vQE?VkrA}I#&Hf^z@4S3Ar7s$5kUqu zG~F@OiPzsaGS(Rx>KM9Xh(^8_a2j5i1MNdBJjUb&+9DvrH8*nbpT!46h%DM8YD@%h ztpN^St9i7%Fs-?n!?ietu3Ilvjq`Bf0z?@FU`t^4gRu~SqPKuq^pI?Li=-r}c;Wnx zouk_eONuTLtB5ReA@Kkuf7}kxG1d|381DSm(a7*f$0NtaI`;pVdY+{_1|Q=HylNumy* z2Of3_gLN0z20H-FGIqB0Tw^n5bm`4{bpWp!kmb<;4oxLb4rPGTR0JZ8W&ELlGI4;n zlw1^rVt=8KJ)0{OOC^mZEEsMFAppWNxAvz%0M*() zI>}}9wSQ=$1NfE%tqckfjh;efU!k^>56P)vHZJ&`G^*tYG+mZ?#4-WJGmR47Hn4;c z0^klnKk&rkyPy2_V*`8lKeVs&;p307-yGaO@_+1oX#X$T9^*Q{`P2XTi3flB+>ieF z;fJ2+d}QFMhkx9-xGY%J8I0*F9{b_AQa^m5qjz93&cOHZA;mQ3!c<`rpJi2G!;G=tfxci9*pZwm_5AWT7eBa;`gZsYq z_?{;Qjt%U2G}ig;C-*=8@Y9bx^vIcq&pbA8f`kAXLZs4pDmg|Ii}fNJKx^?B&+&NL zoou76K>*ogfBZ6=n$Txmv4Fevl0V7t%5VY1)UYn2uV9c$bYJG;aW<9A8vy`gJ2S|Z z8!QJKh-K=OmmnZ`8zKX=HtAVe#9KlTKow{Vjy-#fKC*Z0$jF}Ej~*LhkBl5S*0JXp z7(5PXx(6R0WQRuRX9vbQh6Y9=1J4f)4Ib};MBvC!WMJqRJ$MGdcmVD=({To+oq&Yk zvwKGjC_pjhr$H)7rJ{tlKW>xK0dQ}K@_ckmw*O}c2%yZt8lMF57)Q8@b4LNo(*}5Q z0R}t|xYQ*7t5{|-`Ho>pbJAP5=is;E1AhWdpB=j(dnG9;qCw>P_BIuB(G()#J0TT6 zxcdy#8Nqh+ooI*A-#YfkFat`)2aKaTX=S=qr&6v{UY?Iu(&Pv`B5=YC=Sc{FjCTzv zpphe30B+JeIe=a*E&zsCbw5c~Z{dLAGig4*G0$;%lEmy=Uzsd?vEsfoP zVCK8bLV-3zIw4TtnC|8lLmEvyt6X{Dl20G(<>3G;7hrG}t#}J3DgR__s8&XzMVv1-60d}EK6p954Wm$9p1n~ISN16rc z!?l4x9o!lMfqM8CsA~w^+|Up}JKRtT{JS|&U*7=JTKeI;~)xTAvzWDr^1y%}b(716Rrh*r5Q@S+roJ0puk^@)LZT8qa4k z5z~Sp-u?j!rT*z%g$u%>Ak6Pt6wWUqpWwwsp-@~bo|`TTi?DG~TNVrGZxQ{&MEGAU zP8Sz1EW&JXU)VM$pcA2RUM!%(@X9!8{9U~dh~dUWRuBk8+FYKcjcQmm6maR5CG87J7A}AWT^#8#m|QZ* z%RfNr-2DrEi(l`bp6czNdaZAEYVNf@IE>**-|Xyc|E{U|zJ96LKP?DTef<|_`+Ilw z&A-+=`&$20Z(sky)UJhH(^Gv@vs1G$`>uug-r4@ysj2zbVPaq5^@|So%>C9!KMH%m zCnyM30a{xXt$zxkD`4=Y8k7_wMvI2o@Z?7=4>05aID(UjKs}Z-e{3j>K%)vst$=({uKo zo|kvdY}@|#JuhwT>3M5(JA~3bZ*1K@Iy(C0neAJ*-*Yy5Ur%nP=bq86+qeGxXwOU! zJRQC7uSa`c8tvJ!b#&$)xbN7ped`-2W&0an-jUtfGdc?Q(VYD~U-~HO03W#-R6Ur8 zK&u;#wt%B%%_Ir5$#APb6d*1i1r%;sD!g61y<|}bb5X++#pq-#X7CY1zf$x<0pgq} zExft7>-@Q0bMp&37tr!RU)wdmP<(A}0mjTt6)s$yFBGR|FU~Grn7TN>I6F5xwQYWO zetvdgdVXPQ+pfj)bMx~Hi~aL+Gq25Gn4g_qoSw(CN}c+A3q%3!g#%Q1t;tgyTW$wv zaqj>IIY7A`VATXc5dVG*a?gilD>-$^80=Am&r?8_WWs-n=Tmu}*9?Ym-#+aN=!o)B z!W7=zG+r1&MCT=CQWT~OB}^A0GI4emm6}Hh3)4kWog#&(Kv6>T$%K+voVQFO$FxB1 z?cCvyeB>nJ0vsG2a2A(btSs(Gv##rJvc`qoa0ieZn6+dibO9zeJ8GgL2y$BG6O@4h zgk^F8f)HrVoqV-K5`pN0HQXqit;9IAj3Hrh8`9t~dT17z+;>_MHl0+Z?-T6%vpNXfFmjML* z01o_d)B#fYR5F&Dpkz7rwb22PSJEu@Q%(XASr=gTn9bPx(}x2!4x&6-BUNcClZPEU z03de&W&IRjqdi3sfH8UcQDwheV~eI3Fs{_CLV(6gzUAcpWu~IDE-56i}5@UIe`hhj;!44PkVN@Di8fa z_N=|97fBTb`wFs%moZ6Wy$W@=Hg>z_Zwol317s2gT%X7m0#rvGY;P4zAy|OCj^Lp| z$%X+`1W;CI(N06YOybmUC>5tv!2fo^LB)VX!3~;dwhoDR9e>b8;A{4#;6CfYQ1sm5xoPB#yx<~*5O3RLk>G0E2FfW4|820O8P7H-2h~^)^wbTO zgKDY6>8MV$6P0fOfyM}NSFMN|*GB!mMG#cMx`<`Z3&30e>&scR7Z)H_JkXu`cu5k9 z#f7Qa-s!VFZ+kh5fhfT2*bb}v;Eyb71@e`=$x>KOm&+=$T~13=a&d}$7$+iapj=Q( z(>F#J1!w^Sc(d z?d%!dHb3R%DMl)n2z2p{%rH)pKt$w`Fw&IXR?j%qjqCwjZ|Z0dP(>6Vs&W8UTO?9- z8>{fE9Kj@mWupKmBG&dj&(EFPHMMQ#Y)|j}`Gw-V*GnRMfMvJ5M{W!O+;sun&Rjr? z2Nw{yBnm?1&XCJQ0a~Ia3AA%JO6meVokfeT_FkfI6i{5;wOIJZ!rZo=9dGpXPYV}B zQSxC_e4F+nWwxx16*ptwO;k~^$%Q(p3t*h;HnM>h+iP&OP=EpfjH{U&JPwnRkmYd! zzTU4SEWW<&T;Ijqww|8pV$pYvr2`}iU=14?&De^H8L|*iMYhUr36bu-<1%F&SfVcu3z+=@gC)t^C34-OK0FQa`1)*4&`^N0_)Q+B>9Tyjba=Md< zQGiV!RyLL~NQB`BXL-N_T{uT9(aMnS(}}kywSlUX-yjNZl~I6;nIV9JT{%J! zsA&Vm%7s3TSil7ZsMbHxrM34p6xxoJMW*b zqzlQDL#uBQ)Z(cNaIpw#^e_my)j<%n0#1qCxB#J0lsB|H9qL{XRvHB$3*f2#{$0I2 zFHP@U6keY$`k;jD<{+S{VP%N*8C3+s_|pbp*$>x>1VN@~UEGg#0cbT)Ml&SW1pTru zfN@a;tfnr&ERxYSWj7zX17cx?QNa0y*}ndn(ffKv7p96n>0QzGU63xIHc(gBpzkhQ zy<)bF{~LU4FjqY??XIt{Z>UEVsL|TpklA?cb*hx#R2Xg_yfNVXSb@5FM_oV_QNS`o zWhg+`@GQ>G^p=xV4`td0}z>e#q_3?3kK|T)*T@_SyFY(MA{01@P)OEX(=WT;J6s(uKwRF)uS{%R5{lBf-sv4ZqgzK8uw&3d zS#=pXV|DhBbQ`JEKx?VE?I!OGtXFQrF)CFD$Uk@g$VAfz1st|z=)mMLR zpjghh^*PSPGN-S}n*4K9i{fa{m*46|bH_xFyZE*@DjlH8QU>unh?V6bPi6zv5;UtY z&!q9M$_AP&f?0pMfLkvuTjEp3>|hsFbH(BuCr(q1pDPLr{cnmFzc`wk=|>*o#T5ZN zO5y<>0W`{Dhz zg<~5@rDCyf*VJ_HS8_8m^N{nGyzVD@Q5BNmNemEx@z`KPgj4TO(OA?^ZbtP8prQa- zkgLga6yY8T0hD;y{7P29D57A;m4T~819({k*(59}N5S`~6x}II*i;J(vs1lq?6_z9 zj)I?lS3U|56m6iqY%=4%p%Ix*P9#zZ&Sk}JtJ$}L0$4^XWl)!^5I~myD~+%Ei@*~@ zTpmbP2Uo{4wLJ=uoFM$|uS&x0S*g&vR2~)(ouu{)Q&W9Yqcc6d)5tM+W$Hq&n@a~a zlA1dA@yz1E$w&Dp>kj}Bx&WZeBB5ELGDQ^Jc8l7f4D)^%-AR=516>_mfbA{S(O)PZ z2beVbkiwsW(7Er8-h1BKxvL;J@Ol=dmoF3`no)q0HrNj1D!Ks1C(WR60nLh+Fozjl zOnZPcPYKg$yk^dg1b{f=QgviCUMN6XUiB`JVd`S>wYlEH+u50|(-({7l8$Af0P(hs zT{PVVH;!f)uboD_61;P(JGVa2O)D2#pcy@VQ^0(4B@2xpfa|U3s^|g)Z;K#*@Leni z2S_E{t7iMA`(|=Iqf5etK5-?wvMU!L-TR4KKVb-;blz=(pM0PEy$^lj6HcX3`0$4; z)oBqQ`p_p`Z}rI!7lmpPft3q_#4=ms!rbEG`Gx7J>6xuPJ9aKCEX>Un+$*=~xn5Yt zrbiZw>W*EI2$zT$(=MmV8_s=6aLPw+xJI&|C9j?;7}JV&(S|ISglZuGPeG8lk~l!R z(7V{zKi!LrdZrhVP1mA_f={e`8vMZ})e$iDz0cAg+V<^rSA;CXAx}$Ht zs5HD(dCzqcDZL+7QXO1UoHeLh$&e;{@sbZc$!T0l=e{@tv3}vAP%N*RMR%KV{RU2< zB$W(7ETKg>MEpwlrzC1SN+j~*G;bvrYBF?2}Mp2S^FDH~FFBE{|3YM^>ZTLbz3WX(ifKYh*>`d-_!9N)BCt%j-0)rOMQOYg?3cu>{k&^OXq7hZfx7Qk=o?`M;@CtZn|kB z1^<+EWjEzQ1<5>{sGHWq7)xzLCgUGIqi$(L8PJ9Nt2_Zd#g|Hyo&*0<>(L`>4}qSU z`uHu>Mxvlx4=(HTr)}f<+q_YLJU5UdH=~-AkbA(ZR~$$I?6IK?^2Q4VI4mt7{?|sH z(8D|a=?Z@w8cdpdv~x->Ye9MMzJ)SYLIlp{w&*O<-Q2txXwjUZgz=d;!!JkOvKbh2 zbhkFDQI5~y5jb$b2N#fa@~z~uXp|`wve`Ea9*ACa)9H(T$xi8)0nSzi=cEZn`nm|H zg+H#kSXR~36HyIqtZlWdw%h}2@$@7s{c%;SVs)Sz4QPiPB|n;W-d4xz&w-P;=0*k> z<55wIR+;ZkElL} z*(ae~4?}7g+USf3Q)T1`2d%Sr{jraYQ|6()WGM#} zP&!Ny^SO2(usIFmQExm0H2PH z=UHxIBAQ93V0VB4rbja|m@SnaPmY0F!<-z=OPj6vfa+CHg#cE)tu{<`mvoxG*g@rI z7)=Hppfad62!I=!F*>CrW~>qY&C~D!!j;?XX9vG=^9V@ln(Nr=Kok($@j&yiS zzG!B`tA%JZkOs35a5RDrq z*$&vEfYM99b##e<0Gul@F~0to7tLp;{6zn{Js)c-Ln3gq3eA%#h2SB*ZL%u65>3S@ zP9fDIb*evX)2mULxczi(goEoS+s_NAadS(0Ljagcr6+8r;j2BzxdT{@8{|h%r9Hra z83K^vnp>8N7&_Q#o|d|59lEZU~IlV+@lFTNtu^Z^MC;GUdcG0Fu4%aEJ+j;@J^iK zeifs-f5o~(0QBhNi5O2MW6m(Z1_G$s+Dji>dUFJsdlYX!Dx&~m-n>Fx02Y{MjbjH9 zxuPr&E|$~wD~AGfHZYoqCu50dI>{%Znelikk)D7gAN(TYiFh;xiNo=@#=P1>0F;o* z#N({ph{X_qu_pndc`jjEPR$1mKqb@BxCH>{js?(TOz?o4;NZc!Lja73(wQVAcqy87 zuz#|l0W{(7ElY2nJo&0r65cqf%nl}tu?m{uER?2yPobiP4)kSJ<_Z`@nFv%GHHj#| zC?o|ZVJjvPVVH!(-^2uf)IoYO3Ms(}R)YYH4FrHMNOOEV?gRlE+BiE{j^Il)L4GpQ zm8c^GVBoz{F;zx78yk$gs=~U^>qgS#R;wgyzAptl6 z2}2JEzyfDtnpIH%uo80`qA`;I`r2@|Hi$|&nguo;M@9j(e;PpX;z%*RX#lbjZf`kx z_;9X}70w>kB!OxvDmlreVBudSGU-grOd^k!Gya!vZeSw_QXxP*rgl#|*)>|#R~MMr zLIB3rm$=r>7zNk@0Gc)S8p02NI{U+Z(9#i_HF&Af&r~^pKQ4eVx-;`l0}y@Sm)XK8 zq4dkcx1W{e00glD8EMq+c|M;U&r^UV8CMqw;JWmmD3m+>#6GK}5rH)DFTqv?LFM@Z zsH?!=fTt4x$49K?%JfCxtJd2*bpfT4A_~_1sE_|DPmSlJd5{_F&KU87lZzP9PR3uh z<%DwZm<>b=?Xc>KJ>*!H*E(m^1V;AgQm3uUW7-&o28=o21S|-$EhCq+O#t1B zOK@w2ZW|)%8;EkaD#az8?b9i5P6OA43isbOUATz4wM4z#z4F9WJ2x@HJXLaQ20}>nP5b z;{akuD4-kYkH;$a<+K~y@0bfP+#b1c0UW@z;w~Q7ODr3Quec<+Y#cE73Kre?SCMrRaY>ISN}a6S*>koiwUFUQ`AYsdwS7^0IMkG@VXPsLRGBCsc1bV;+s|H1~X29^eQBV}JnE7r4;xfcD=7!C<&O1l9#Z zp|!zK`|pIBU<5~)($wk8AaJ2zI0%|Tpgj~0hfp!V!hb3h=BTy7%|Ku0Pqqkx1)4&k zkhLvBP3_iLnFc^5!oad5SrCN6Eb9tFgIm1-00zoQL_t&mNS{J2f$nxx6C;B-^46hn z$PcN(cnE?-1p+8`6Zu3YftQU-qZ`@p9?ZXR(3eHfW}vxX;MT#G z4#6!LT8n-{Yn#?mYr}90HDO$!io_cj)vE&n8?Yf@UQ{+1S{Gc`O|5N$e=xZ{$f=ed zgeW)|UKhe8Zp%yU6Q;2I1{x7hC&46nPZPxQCL?EDGlcRPI zh1%Dm{)QmJ+9rr|LZ}sj=s=Af!ovtb09Xyc8H1?$bucAJp%&Q;p+b;D9iTnb4okEL zgG?BgRh2=ogEocX#;R)*z}AH|f%YI+i#h=u`qb}+IF^F9M$H`F3~Pq*3~F^$5-B>Iv?XI`E<)pF z<77P>4GcfPg9{LyM1w1|w5Y0B(Ch)Q;))G6ZH7aQ3a*0)8Fu^szrAbeaT5l@=WVNA zd)SSX3)1eHs$K1+5(NY@;o3i&R6X>6Y)*UZxlD>wV+`2f$JjXSO35Tbc>o4Ho^Qq; z`=bwY*BN+P!4o|Mhk(HV5ZY3zlp_99r{C!VQ>5;>Wy!-sI?@1)Q2=!>nSm!W$1TZt zr893U6*__lbtyUoB8hlJx1nl~acJV8L7QAkKUIs4AhzPz(|wVay+^IOAk4dQF%ppzWPNwx$&^h8PI)X#b8N$`%D89|S^n1PNv zC^9wMLjTv}ZB@*5X;ZJ|KuK^>KslIFX(otBv@{I||G-B9*lE9L2Z!sPmd_ zN5+l*2U+-Ub#^y?&ube$$goqr$%cJvG{boP@snxid{)xBk-TUTl z6GzLEjK{_AKFm(UP8CO*Ql_iFiD&=B@887A>xzfB?H=e^zu6(+&r3p7!;C~d@^~%? zzzzuie)f~&^M8_K;IfZSdmbBW6aX(@N}gnEe+dgj;8vYml_RFqu9mI3G)TXo68|W@ zML7vufY$f|Xj;DaX*xy}T`BHzYFbcU+H~qxF6GpW`=1n-E`IKJZx9f$KkWDW!vXP~ zoqIS`X&lGjnbA-bmLjPnw@QDQ zovWQ`5;kC1WK(x$;=?_BYxS;RRwCO^+PQ;qw6t_VRmEEkyZG<6hxU=(6BCY4(4$rq zI$m^fEhXon^}$w|>Q`C2>RZ?46fN%v$xPl+)yM`~8dIqi;$umdYUst4_lyh|wv)Mk z*d$PB>BjnG>3B-l3ByFQY6oY>^uztNiRenhb=J3>?N%pw_c51}p45WJX2=>XI~VR6 zHM=8~v_0d}o!Z=wS*lgc_j$9nchC;#STZQou=C$?p6gsa^_oVZrFn*Qn|9{|1KR`= z%dF&F@aF7JC?knxY>Ald(!!cfES61B{C_q#ta|fLleXL z-XZ-dyF0YlJ+&Uc{9cmRGN~nh&ZjI^%Gy%(Z z`?S06T~?Q4!;I!~w5fxg16xF2&NvC$4_Wf~$mTl()0b$J&Cl`-%irdZR75H6PrcJ# zUX&{J(WjoT3yf}O7C5HowfCj~KKwUW^2`l2O)=Atg9D$Kzc6m~SaAM`apDgT93@Xl zi#iywGnn-aPCKiTyB|I`SoU_3x90euxg|pZtEvEZUF80)>{}PS7BLS`6gtzY`8Cb= z{|XHgaE4yQU)=a+u;plR-qXdCf3vKwIT>Oru;n?l4TcZY_2i5F0iBS104GoQdXEn9VxAK6*yHo(C;pPOwE1&Q$P>#e8 z6-6N&l#dD|V)}o#OdLmpOtPXa4nmMUd^R5R2;Nl=vX_J8eqfC-$6kOdPzvq+t8yka2ctU~LfsSxEXcP)Bg)vZ(4IdgT#R_`?hKP_bOS!0+&g4n>h)7IGorR;i zLw#ALhDJsS5eZ6c+gD2jln8}UAHw0+g*=YHT~UmX8-t2J zXd&KZ79mn7iWR|ZjQxluNhSiySRZm3Z(kh|P8LBCB0!^t?UPrVf%#VvF0dS|ptc{Z zT+E=t`WsFej!+G@+1Q`cJX)tN`7y%&b@*`uXI);GNE|wz{6`0}3eE2C? z6M)fmrAoR&TQK900*Y!`YK)-(a6#&k$4MJh>)sO0sdIgNQ6^Tq4Ex|nKuxkNdiC$-}pU3 z4=&SiA`kJ%%_%rFE+dxYEwT|EmyodMitoJjz-Va#xXo)SDe=k|KG z9XwtjFuwTCFwYg9S$P~uEIbX+1Ca12)Heo=#JVl(O1Q*gb@JOo5AI+&`E8#dO;(9B^cP1%2&(jeXg0%!G`DcE*-)yu1vu09$jA1tH5{ zk1~Te&>94AWDvJD0s%w;CU&L(ettM7kR!mz2F@*GTu&>Sw8g>a(m-hvUY7n60*jTg z*ai%o_WX;7=95sP#tj?`czsMo*Cj)W6mxt;`AEK#4$snsJO=jg%Z?!=Ht8QiyHB^5 zk97v$&u0bSx2mqqjn9X^*-hIWa;V(iaS1-oD%*kPo~`HP?5vNg_46u)8}Uz{=i7KJ zf!d3QbD$D7nSso0?9!re(g$@z*2m$o!X}@0uO}Hl(f~Y;|4b5oAhNg&BI;OMd(3#- z?>$6)KZColo#$7;whFCefB8{%r7BS;LvJ(h342~4Zpp&Uub zk{wUF9#1~rlT`?&1b`D=>u~E#O-y3j6yBp)r{wBM!>u&PiQEAocW&q(z>T#0u0-r_ ziXeZXjt(RLk|CI#R5(5)sfmg82^sD0#cM!wRAg7j!zEzZDTzx1+xWf*h1h{vi?Zlk ziTSmQ7)~iTT`fSB_Lg7f+Iln2X3DN`jTDX>fxdK%5)adFiu#w#iCWfG7 zKBog(U%;9ygrPJNA5vTD915`qg0DM-nv&_aOzCgL2vb44?@&NFYOTh!7RW>~hf9qB zRYdin65$&@vuF|xNKq0<><)IY>@vf^`k&qLmyDj;bMZ*+^>L!|TR7g&52EdX)uJL0 zt*Wd!1O>Q8&bC+)FfUQn{PIibLSR}7eR@ZfA_Y+@E&;CB7+<;;`Sx80sRB#`Do zHkVfn9u430BP|Hg!iN@t<;oev$^%sSQUq%C3?@jU0VY99nS{7d5aUrMZ?j4dgH$qX zoK|*9NZO1quEDhdHCIicV97$y&AODZG?;cVJP1yF7=sN8RGIb27fCJ*s{jBrY=;OX zg{fWYWKKD;Fx1r~;>dj%-LAvPRdjm0#uB$LHG#n;0TbJ^eDgxmCEEp7S29bV#EZ0)k0{|te-2rYp0@I9%j5;7k%Bs z!Sf+@G&#&^v2AEC=cEV{vIXD9+9@%=wOEP$Y$g(giixI>9PA-m4(K|?s^JF<4Vho0 zjy=FlMH}cl?J>4bBrf7;wB16vIV5k`L2;}I9{QJg`ScNKq8eJME0jPupZ zWJsJfwM_*Vv=;V57i&oRDLL)BhBy0yPA3VWATyYlf|0~6Hf}0D5C@=PFG{>g%VFPI z?rxD5)cF<^E3WNpg>T6grqeAGarWETLn$^MbV-2#n*gPcwA?u#`tU^y9+{84E8^Jp zK?rF|OxBON5W%O)a?G|By&94eR(ku4qk?yIRj8%C4bIOkqQo;t$T(t7(@W!ZiDGFz%44xAU&Yo9$#K%A4e9LP-G0u zqzSDhPG)+GO>)?fyiweg6v``<3IYye%FpA7B%MdfSovPlF&$||Cles~k0yuv8+O@8p87z>KJfFilhAy-wcy%PAMrigx7$i;3ROaIj?gXhBD~e}))< zn5f=Tkl$D5(R5pj$CdxaNmfB=`w*?Ahts`Vte9uw+?+5E)tQt_iH@i4whN)Yc#_~{ zO|UvJVmXTtkU0OIlPm*Q#&f^|uB^r9S!P4)=x$}_D6s`q zqSNNX=ndFB!2zVLOeT~JsvIR4GpiKo&P;D70$n{;!2o&7bT;_W{0(5udk+Zw-}{$B zfnRGdM|711&kO-MhnmG=Bt`ob9g0bDFAI$XvIfgdBjU2BI-a>>Wcw9*0x6xfb~ZL2 zb3Wuncv)Zzldu9;f;3 z=I1H^X{S>;zP0-ADpfiv23%`aW2(xWYqc4%aO$PnA_g1Fr!w>!wQv ziG2V9ksR*#1{LZJpmxCQ8fah=T27`~B~86?%}$%|Z4DHB5l}={Kq*I4e8EA7!XW+# z>405K(5RQPHrI$X8DfLEleB^{?LOxc*SbO! zDAxTnFWAf^#LJQ62b^n1CAzQgqu3xoQdelo3=`(b6AI&)Ab@+aH4hP?%Etuv=Csw@ zo?AvOk20dNHZW7UYIw*~DNwc-;lfU_!wp;lu%$`{5<-o)_!kh8v2A?)+0TQCRvfPU z(7GQ7fjC^`Q<`6OhL+>d!c17{5d0enz$yIAt#f)b;a(G5YQ>|HDX!QP9;;?+f6wER zQ>YG(2*qi8jBxvCeS?Hg%->TE%>!7_sQAa94p?z=>`|8HD8uc}{o=+fT3mpQSZaHd zFrl$&Czd%Ba~vibrwUA$w%B)}&Mi>OegGc+$DlPb4 za32vGRDQUqnG{n+Ce^?wZ;KJnl<FpcN8PCv zUX}A2Pj5zvlSGs2bjxt!5&|$B8WYOOSnD-e-;kr`G3n=b89+gSlZaPSvs_U9R#z%K zKFf{uKNYr8ZjsS5eSQ)o`IIC2vsW!}RC{~&Y6xrYn#ClPwjO?$jKqO=D*h|5nQ^nb*pcx=_8z#Z4V(Y0#1JRUA`Cjl!13EifwKs6Dpi(GBNCYH!c##V zoE*&dliqb331#5=RYuDg`KqNk#gpDrP@S_6+s7XDHJKc0h96f&Q_%glx9JsU(kN&F zJD+#h#W)b8f@CO9Muwjo^AqzR>#c2oh<3~MV^dWz$IiW{Iy~S*m;=lgLAizceHdfy z*RW#%Tg2h@quPZbOi5Q%@F%EN!u+~_3`e%XWaVlehxZ+K=+R)`S@R_@XubnFMFy@8 z=iqUsr{-lOf?p#2)1{i5!;MZoK0KN37D|8d1UmIxbQeqA_R)4rRk?Y+Haiw2c=KXp zt#5dE7IYOp#k1BMn6q+Okdk-xEnCx zO?Iq{ET4})iPYi_L^~}NSZc#(98H*FyM=a=k_j?@44QyBA6GGLdUsp!FO;MF=oH{= zfN^A$HCEs*WSpe)k**Y9&Ndvv1f6!~PVu|>)wp-b^l1Vyv;$m03Er5dyc=UB0tlnrl-7st1RYm13=5lpN!;s!$qBUWs{ z$frp5-PLU>5kV304`|+7v)EN`+{Mafl}l}D|4%O_&DsfWm9pZ~JahFfV;`OG_LQz} z4e>sXUX3%=XQD2XLfaCDr*Vw1-Ofm;BSrs>i*X z>!Dn{D6GugA$-Nw;cvHQ$D!Y`c&f_KRk3soJg2(eCRf>qsi zItuw!Ld1h*_|fZf_Rf|b>w*Y~G)^v|<>}V*{B7XM&J(P+*jk^6Rx zrf$%l(n-VNARI@9X+#vLn2|)rT<&!mRlZkR#S}7ZMm! z!?G^?mWOdsNWPp=&CJP}NnYqFB}ZGm>4*@Q#dJxb%HG_=Nj%wtUh=uYDB9B6S4?aD zfx7at)@g|`dBbW|M~}N6R8&W#YQ#4 za`wW+lZ<}G&5GTxb>|a=3#brn^|2UathCsy<>h@EBA00~UXETVEHGZq8@2z*C;c(P z3J`LZszq=10ELw+GaWaEEoKv?XM>1tr+>Rfw-QQpTWy+VrT-BxZMa1_c-_t;>9W1B z;Pkwt3|h9Ws5$mZXkP_&ewtfz6Odl8XP#4H>r7bhtq0#C!kLV=1T}AhlHvr|GO7C4 zi*EV8n9R1gtlMZWwMyc$gro0E*kD>^nq^JjRPP&OOw(c*;y3C+!kRG=+*F_UGNuNF z+*hBQW|`T9!=7F)CJZ87C(`3kmY&|C&5zsC+thVNW|orZTet{Y7wiwPbRG4UqnP&< zyMxZ1_>EmPvei-;&@;|hR99H|KB`wt+PVfqxy-P+54jPr5m!Kb`1z>GuJbN3llONj zEw3{=avb|&t-7;7?xEm|{UReDYQZh0@0wywpWE)%Q@jT!Z{GB6>pkSQIV8Mi|=-<}G3#vtTb-E-; z(gKkc8n*TGVQ0)+VF8Vum$ahRCqgIiIk_a&qXt5pZ`t}71B)fKc4EQ{qu(SG_VhytHrN}|kfU2P(bo;2IrI?@ z=4Mr2JgC+BvROM*`;u{%r-|6bXPC|4>`mn(an~M9s6+p;sif@+(JO>bW-*1I={n0i zYCd=e+^r;zWj6;SH065h#B{f@9f%Y?+LtI5V-ZgDL*>?5>m%TkE9+?bb@7P#x zKM11CU=#X=Jj@B|RNFd|p`aYw_J2xN?!>{vA<@Yr#_-Z>9Q>ecHT5YQJ6x$Exzi{6 zmL2#pO@COzOmm0x$T{lOeBFCmu5WqjLCK>z#b673<()rF^%~|1OhXb6P^d!G`eJs2 zFky8=yD1C3HFRgr#b(~lY4V+*;}tib3^YG(1^o=C7jlm3_2H5xb75Zq3}I?9x{A~3 zaR24~hnXO{-JIDs%ILm)#i2oP$#2cxZ}2XT##fg6^H}_%FzWrzB+W$Cr~Q&*98Vsr zsLXeO#;X8_V0N7!IN9;0c=>|8II1kx0F%yMfczspkMe3Yh&d;vM?7&hVMEaK;_O$v z$0MJB%z+w1DV`MkCHDpU*T*G|!lDU#Yg4|1EenKL9| zsR=NeBxTcx;)Tj2xR230eADgza`3_Byr5D=X|$6&(V5HD+%X4iF-zZ>*cVqYC{kT| zREEA3I!;`?;A@)kggF*@y~6JDO6zKn)jl`wsmPFZf3$X&a{hIe8C(L7Iuk`C14(tU4?d$>^*oIksYB3oR=k`I z<-t3DJx5%P8Os^aOY&UH=%bMjCY(g34?5OmbTXCaYW#$G0s-+kl=+}%T97IIrH^X{ zD{cp+S8M6AQ}U`Ww~z-fY&yMFV^|9@Uk2ehIXm^2-k8Qgso4&3*ra9PRpJ%wbLbE6 z#9#G!XbIZWEEJL=X_@kivJ#WJEZ(?CWTdi~qR6}QwYLlu^U^6oF=_D}Dhh*_l>`E! zKh)Fd39hHaW@#es2BrFXdQ_AjJ12J#wyzy=tG3fBHt~H!nQb~ruX6(R#CfA=@|1Te zN%-ER2Q`b(I)Vo&k2mZ@S$Y_MX8cT}$IHb5+ooC?`fbcBuWd4<0nA`Z!-kB~v1~=| z_aH_DBu5-&PKgFsWM8nm*fKKE%w37shp6&!^Zw=_xPincj|j-i50Wifq>wCb6Ewc* zQ)$#*Jw;R0;hB=NK#HnO_AZWYV-K70L>Pv?6JsfL6CRz`@!LQJR$w`KfLp=@tAp5Z zt9NGgLM-{i-5eBaQO%5R^I6vB0=N0VKl1 zM4V|@vE*dJhyGPfCEb)ask7LktU1K?^kfPx1?-kXm2#1|+NP9s%B&KEC$i}LxeV!) z`>7CaT3NMY=J72r;yT`G}^!uJ8$6gHl3YcS(a9_|{PV8pVs);F_owZ5lfZuF( z(4AGj@H^{BzF<5<#Cu{el|Nq-b^~7qX^X5$@1^8q-i{YC9Dk4{J~_aJeBd71pVcSO z6<6IFB@taYv{QwoMfeu!B#FJ8^+g2heO})9Q zhUzq<*=$x6#VxOEAHawxbl_pIsVog? ziwY|q9(J_5#V_Lk%ray`@c7(0v{#j0eBimlzTTu}4jnYQA`!>AxJC)n_^6p;n-Lb} z%LthZr~h3)q(NxCnIc}f`W8o1bQWC3FYVo2yS>QWEAt`x>@hDG6$)L}_><73+j+G}KMOI_8Zu zuH598*HEF@#d7&;L9-py$#EmcNskd;s`8TtRybhkFk_JAp0E|Zp%&)g>e7G53jClF zozZ@9(=^cbM9ybHb(5&m_p1Q>HjCU#Zkv)oyJE$hOErzerAquWe^AmN7A=~yQ8JdI zCgbkV5!nR{E`w*Nz&0)eQjrDPJ|;9vi-xpmR(M&eww+a#M1?@f$Htw>{H7A%a}sje zM8KaKV9?p$MLCAnJGbL9_04;u)gZK(m89*rzec2#O?ZRbk_!<_iOu#$exsJ7N1X&nRdRvZj{2K(!iQ~`I&VXqV zwonL}*;(_C;+r87W%NTB%uswc^S~j>IV1F6nD!3M!4CeC4#OU$Ge)s?JGWz2D;n-n z292x@4pFZQ{y^fb(vreTJ14m?q?C(*L=ng3-~3e#&XCy34=aC8D_wO)i7N);DKxrX z#X@$WT4?{0qt7x#_GdD91x7ta^JNTtAt5p%9ak|RiC!uH;TZ`-*%OllowuTnChncl zO`9L#m~g93n}PBR2fdqi{|~Wg54RF>sXUb7`e}#HTnpHarH#bCq@#Yyk%Ihrhf6&( z^s(&bXgV310y6w;nNag~DG55Kr&~5>u=5{UxVlLP;CSdo9T<>-L{1a>m z^FtjDU~Bp>iTbbgkE#v#kC^?B!NJMG{7?0NIGLIL%l&WFo0*CA|Dk*TOBZJ%WZ`0A z{@+FLeT`Q;l|{VIEGq*Z2VvAhFxCSHdJZb&)$8zrI*jf>PSQXW4rJ3=$x}JU-Db|l zn#_cd9Mu!;6GZZYl8SoUveMF1{2Y@2R#h8Ry*2`Jx|v|bn8v&c0o7AvlM`xZ6&je= ztle~I*3X`uH7EYf$!GqFN6*ejeV**h`P?590kFboEZRj%>@r`jB)%vMFeMk8oH7iD zen#n##hKn^Jp<%w4h)SLfgObP5?@$Pd{=!Zi4+@f;4PN zmO8wNw7(z{v|%%tG`$}aKg85fbje){#_U1<>~l8Wt~F)u`+VL+-ezux)5t;I2*z?V zuUSzQso!EAs-2?U_De_@E_*oK|;Z&&!>;w4$d zZpLE0tJ9060S6SwOrfIi9zyxJ zUZc5`OcFwpB7k4e^@*+;hrd;(OcKL4P>)sHeurpfnkY#lm-4MRu}V`6j?y=8aN}c( z%!2c29!<}I)BPsS7|EfN~{m?35r z)RCp-UyfuKNQl1p4ZV=CtTtKJ`N5EJXwSn8$-}#LMqg?r5lT%tQ9AD_dxfy;`F^t? zn-`0Nf&J3D^VKkZ&-aQi{<16g{I;SfW_YpHEwMWw_toHIu=mkTV}&|9QNMpJ{Yw6h zkvaM^FqKa%m9yv6%0eUUB5gCSF)BJ-`(EI!=~y}X)Mv6_=@=4fuN{mToGfr{T!Q&%!b{C?YybdCX^O$d)X4^B3x)WNE73wtyXVvqQ zrtny_dc)k(X}}m};Hk1)#pLF|=MeRn@OZ(Jb={&t*=FnddM#L)>AGQ6m#vk{?>5D| z7j5nz4>9a{wsov*mB=V6h&8t4I+ER+*Qz-s(|NX@Ga}_|%}D2ML|X=3iL8Q! zKFh@YdJKR}ls{ufl4G`uv_ua0YW7f9>TY(OTq+2XBeN9x9zv+bUBmLk)UcB1@+WG2 zsbGmrb1XKzi%>w5HIiES3ij^Fz$yVfn=&bHBh5Q; z@p0uBDmRXSgt4~Hbd;@&-)FUzvkN+obx#t;4S5*zce&3OceJTpd5w4~%xCG-QZFTTZVhTK?Q7?D4W5!f)LxYz6xXFg|<;tnP9Bo6MTGsZHrK+qZO2w?0~ z?Z7Ei;fExqwX`Jnjl*3GrZQY%Aq>S-H2n)1(T-!3>s-N-0+N$tcyq=fXFv;p;Cet~ zcPPP=o`$++kIZlRRjiP+?=~`An+~A5kgfeaAo~tjbJ_fRE?@1Am^<_bun*biD_SEq zljW$^Qc}KW&19#$qtG-*8fRpN!^*3e1cTN?j=o^O&WZoL*A-K=Pc9eX$q!6##_$bj z2Ls-Cqw-}04~5;V5YdLGj)Lu|#Tm_T9?J}IE*@&??Lp6#5E8vv;r6Z(yH%E{<^|*u zoV{&V>=u~!a$;0ER7WcHv+XVvO}vjMAg-`IePwbKKGZnsk++(7{u*tL81UB6Gxyu$nI~t;@_gYk`6& zCj3vHsSNj*H2#}}gBpC}V^%&rpI}Un zA_JY1st^UPX04j`DKv#(^8Q_wG5Ij<(LQPK=01C1j+V-3wKZG24jqK=qk$ztTj|jW z))oP>{~zOl1 z|Hgv`&=h3hq{B$a!Nv4o21J1fVh;gQ}gHE%2YL*uSoS z5dFVE=KS~7{ujthT>l*QKLGvT^9v?sCPvo(V}jxC>7_c~aHW69RK;sHVS2E~)SWRL z6PQ9F^(DX$7aUfoyFJuQP&Rlh5SBERhz0E061IUFl1Z~+MLY3w!OEMZ2l_g)N$s-2 zFr%qT_*|tLDRxP>MVt zkiycuN?@inCpQb_28qb3 zS4#4RRk)x^Sjcy4sEgk~awq~Jfr%q9^nS%Pa>=g$V@%QR?4@Au`Fttbs4Jgh?Rb;1 zxCQt1(xj_=(bs!V?1NGvbT!bsaB>r8jmPT|b17R)QrV6aHxdsFi(;?#^~kM20E@^( zLEYFF-DV|#vzwqJ?i=eA{~&x`j|H<%T%3w>1^aN;f&Iww-eUrham;U zF!s#>hm@GRt!7X6?w;Nal=}tF5$6q7L~Jx6+U?sHVRQ|-s2=V5Lx+CEnDwjg)?bmA z;n~@_aR9~sS8TNyh98-Nrx)ZDL+jOn87TQ)X=@@X1z^yf!0C(mdn1i`xINSG(>aVZXqwvApc-ivEPg1~j_H6)>GXEeA68xfsDa zz)1j+BD->JM7r>aeMK&Pzoeebu$6h+RFr9_rT@UG)NV0;P8>Z+UYMuX(yf!M&~MH; z342aVo=oR4cL7Q{C~cRwuVtieSV!~p5iOUOP)T9b4O^gm&4N(=-JhG!K5=<|hcYL#cvF|D-OwKwJ+LHLj4+M-OVK zHq&OE==BB<8Iv>HYT|M*$4YasWu7UE!yzNB^IMXfa>K_=*dXe3u{dgM zRz;b7r2loeY0nH{jvs;Pkvum5|9ftKJl-S8KoQn~Y4AWTcmg#e>M?whVc^L*f@shs zNs)1aE=t5HJw(5f`|f^9F;k-i=LG8}Itsb+mYV%?O49L0u>fOT?#HTP|It%;o9=b% zs<(rV1kAzfUCAOFLv%4)1u)!+%UOCT8tbj>!Gqq_y+A5$!0re65e5j!V`(?nc8RSt z&Uxt1%t#(Y4;k z(-!wwB_b=Mz7FrbO!6R)X>!5{DnY*5R4KsEmW=)&FFwYK8sY#EfL&W7f0F#`03MTi zDDh0X5c7{BSHs2%(z~4#CB#?CqoWspQQ~sT@G=<`#dnW>O9^S2n7&Ovuv7hBe{6

L}-O@x?vI1k34ltl5wcZL$csdNe6iDn$5-^dMnLxF1{ez#R!Sf-Sp4k}Snz+K&TWLTR}#S@gP1=Vd9+cBnX8<7we zTDi3}>Ow?aQ~w|1p@%dLJ!IT?=LV#z1;_QxKEi2!E=m(S9?64;Qo3S6xJWdu#(RuP z#S01uMyb7EXa|Qo9>IB?V>wb)_3i=s*&GG^^8@VM94Gh(fOYJ#LyvNGy}9%qS`gax z)|F@oH;cZ;R@%whlk!6L*@6LcyS1?#u3VL>E>K-lt3=21?}OQI}xzkc(e>2$7k_M8YBT zykzDJVok96LCkQCkl8l|=IItmlHo?d{QBnXuPvBC<%eU+=@N-@!aPr0NlGRPCTsq6W^YWP2AB%eMJ8Hx?sig_vCKgO#>LwVR$@~_8 z(2?L0IY#8{hmgulI&7~W%wWn%-baM3Z@4FrV1!xNA$71YR2?y+PvkTU_)>lsiu^(tpR);~wv4lM)M%_I&_F*tzf6xd8dETHM7qAhPN$ zC2tFck8fvq^CWm~OlzBVfajqPR4EU|X8cHE(FLtV?^uu*7kRO0Cf>boRU6HYKieG6 zJioVf4&rt9Q37#4btzFRiCyBPdQWyh;c{{Dac=sxSCzC}CCe0^*2;XXJZcF5W5xou zR|NLBmAzFn2%LA48xeY#ww(2^i1d^WVCi2xicHT}z~hwk2$!V*;!-@^FhY`587VVo z%1r5!?8^FP$r<<|y87%2dxWcocjyqV``G8WniFr&qct?p9v8z9PO7V`EwLdXa^6T+ zKhffZrZ*P&S^1uP8;h;F4kUtY+n>$2vyEhLB(813D-+A+TiT~3A$v?QD*VwT!;vLP z=)GC`$Ez0+R>%I%VwCS)-UInuz3$rtyeB>%iWz{9K;({o zOW6Ac6hQ+XseEnYCT$z?{P7fenSL`6w>y8y^x*14QC}8_GZX0=B-tGc;!H$RS)++{ z%+S9#{g%%k9K=br{7b*ly$-SdUJ{9fSNC}@i9(`5!Qis>SL;T9RU6aI2an14nlq#K z=0uG~ziWMD2a%=D!IMEV&!5rhMw+=};P7w~x=I+~&@ok>koA=o0(pFobO97WEE*U+ zL+nTiS-isn3#)6?UZx6v)tc7Doiwe~HQ(7bj@`PuozB}-ZmH#@f6RMh=9;Vie7(lt zM}?95$?S6AK3HX$)iejQS+B5~)ZzI<{qX^-Q{$q1T-^_^1=IAN^{rh*W(VHKi~2%sI(mKJ6}B6f5lvm3HZKrJY{@TQLsoSA#4)yQ>SZ zMKw0P+MXzcW?(pj3Nf6Q^cI82z@{wqR7-i9uUq4}5q%YT?8rdJ^nL8*Nnx;)5Y73P zzC2wj#i-CS$yAaYDvmzA0+GKi($&xuAbS@D$|=3)<}AUn?(ehAmLOt{fE%Y3YnFM9 zEQ$nD2!i251UsMeEJnwtvqMadPkmH+e+e@D@(r$jcg4WKSG(=DMMIL<$6~ZP%L=RO z7^;koEVBT`t`TCFg3G#ubojZepFv6f?un}!W->E?iIu!Jd8l6WE$U6?Zkl+4*0NYu z^OeyD8W}j~!G3RteIzj9UeB@;+wHu8Q_vuQ%0IR62ifGU6Lu8(%j0HHs5C3EC^A-J8KbFrft(Y&pK<;}89Dp^56 z567&?-r|8^A8t7@2wq+l<)vQ_s>zV&f&=rC4b7^>*p_X~sE+C2=sS5$k%~LJgGd@v zq6wNFjFaIZ#I;Gtr`LXISyF0)N#*1Q|J~}FGC=>uY3F9g;41yp!)V4L(Ua0G(ku)c7i_C2|&>iTe0wWbvtPdK_cnfSmVQp47;;E%v|E_Qe|N01kJ(JdA%)mI8m(l zF^gMIMWnCeTX_2?sFGl9%}MTvAzIhzWa%_p?$h(eS*UCw>p)Rqid#g-Qx z9@MOsv_`T*0qYM{@?|t0Wo&NDik|aOE>@-5B9RufD9ddAr>$rL%cHT$s;(V~kAbSr z!Of?WqPB}_KJ&_QJMY95q0@O@U03V^@yug;?J@N%4xZ<7*}rf65DeZ~E^b_1xXZsk zx7oz52#Q34Ww&O~CCqC|5(UQBNyt<_R;0?*yDy7HGjsfYD4cW}M+A_L5 z6HG%!w=EyF@NA(EQScmZm_tNR{?xk=bVABmJ;FY8;4kg^2_cK}PHp>%Bmc&y1o(zQ z(`Wpq2xsC;z?f2@`pQ6$@gSWv@O)$=W^c@t0ag#WgI!>4tNm3hdHg$v1;xOyCYvj~ zy8nmiJISkdNP*Zbde}~io}Z9=zG27V;CVe^d`R0R}%eUh~2FLSH zcxXD-{Y0_{>DJGrBPotpr$lA4gUpb}sYh-WEQiP2D|g{bITPPjDW0&9Rx2*%1?y^A zPn3&gwJ${H{4cf)ywsEJ>SaGYU1EMY6+iuYf{2Tt>CmT>WhpZB0GMf6pYy|K`8t#o z7{TJ2l2wzy&(Tr>3YCV_bV&`I^c}XKie&LMuVQ|tlV*vX?@%aMG58knm90Z9ia>lc zT6Vo0ncMl?U0d1VZ*ROrHu^%^2B=Meb65tf3`uP@TnqS=W&$=roQ)S8Mjz?d=XJuF zw%_SQNuyXrEP1k2MQP}5UvnU&@p3qSJ8T+a(#yDnQGfZRU4IOFUSSZ?0s8}TOZ|N4 zaBzd~@dWnb+`x)~kIHt@=Xa!+<4IZJSs%u%o#49^X^}ov+Bl40Tu<*VjfrEJMXv{m zEgAu;dl@Hflu2KBty8KRWC*44S5z@@!ur>r6bCK@HDy$aY2eau)r*@lWe6MTsAA~3 zztDaU9#3Cp_Lf%p{K>?xyVeL_Rj@?PnsdoEJ=A+~KMuj6f!?rbQqPq0iX+=IND6YK z;Kz^5o~>l2?Z$%&h4n7=B_!!@R%{ES?YtU3i?fr*KJpU@9=UcBPGEIRG}U|xEbsM9 z@ysw)#&UpMU41&k5Oh20`7)P3)fxyMxsjBZKJuAe~bvv(|*;-6*lQKtoy5Cs2GCjs36~}P33x%jiVfcR42KUGIRJf_&MlglS zdPQZFkoDIIaaShVJFXTqgS7%_l4|vib6XR(^#1LW)I2czxcb)^D)Lcvsw`UcX?2jN z0&i8H)Slxk5uQBxVwxD9JQ2>A51u^P_k*TnP}OCW^Ck)cC7AD66)2cQ=e2Dy#CW89 z!mF?xiWMZd&ekAx^-Q@k!G=3(HhkV#3OYBZSePYcflrLeS` z^IID@bV|*E?VN(6=)OwOUkf(Q-t6o)R1OUAz(qgUQo6gZO;Fp|)k0ldA0P|zF1sHj z65C0!&#x|Lg(>P{c|}Y&aUvgFU0*#tnhlOUh=gdwy1TlyC+E1wMUz$vI%T7dNnsC;9LxkbHIJFZg`fkcEhA z)Z2GK3DcA8jF{FN*fEXgy?gZtB*2h*C?5$>s4k&HSZB+eg5G92`Dm1w1~#khbm7$d z!+F2;uoo2cWr1}u2_5e&2ikv5egdL}J>D4-XK`o|1`Og}NJ%xDttcPQxuBzZ;brCg~ z*)?;OwQu$H78l%rss9Q)>(OcGw)G>3iVwB(WBG}AUc8S)pgG=01WlmQzsCNLkGBR? zkTBV+iY*dD8@$2WSFIfmiR0MDL>q2F z5G@&17K43!6-%7g`w7X8LAV~KW z#=@9Ijg|daU{wk6Chx5SokMHz>QN`^T?bB^=OGC9$mk1!$vV-zVG-2Fb!hr2`>PW})G)Fc<%aut zt-PjP=U!IejKf>oTEBvciQTpE$*5si=USLZv;Ce%8I#4|E_H=xr;7+3$-*~QU6_zp z^WiMEAml`R8OeZs#}oUnmuT&c7a775QxES&do`(T-(Hqz+hi)L|Yl-ZAWI7Ga_Mu+VE#;||_9W#< z&4YqGG{LB{8#Zd8G+CQfV;xdsp2zA2>~8@)Q`-~#Sn5BH&Qv@GgORNy>txpihROGa z)BfzJ<%PX*Up_qf&^kZT%P4s2ik&!ac<=#ZkUPlBXC+-a{UU?j|F7 z-tyGeKV?s$F6~W6*)-Yy)0>yIPqnRVC9M$q3Zl~;7%9uQ$GD*0_Tqf$P5M^;#N8-c zdkN;OTdpOV9nQu4jOx2vHYaN@%f&PF;bvI<25!f{_(^3Uzj&wT4Obb{Hx`RvUI??d zt@LwF?ZD^ylj{l0?KkFC@7qpyyZU9w<%TQ$Q$Rbr&Dq9fM_b3^e)~^f)9N*jb36n@ zTO2$*^s83xrHxvg%9a2R&-gz#IMcFc4WN%k=Gx`ze%GtbB zRqd*)nR2Al@apk&+P-qx2=uH7ok1IYUIZYjbf?>H_rJ5~SlNO!8LQ}LtpbU0@XfKD zaWa1AxvMg6%>yPjLS0P+eZY<^k8QR^daKbF4q8(djN8x7xGsy>PJWjQlJ_)vs8(nl zS|#QCit9@v7UbmAK(VrSFPxkjeC>XpsJg_(1OJ_R|J!+gTS#9L7SwA`bhrSv3Fh-1qMUbr*dYej9cV_9BqpC$^7% z7khTaiH-okdKDEj!Nu^(Wp zEjN0))_SvT<LL5GyPev-(=2>X3&1xY7*sc{$0TV|E;Une^%E2U`zau z1@^bv1n^I3{L7nQWhG!{W+4Fn8Ce(r|2P;K8UHcXX{Xf_Kx%bZq^Vj~r_Wm`; z`scp?as0W?Un2_(%ilQ||M=bfR|n%C)%U*@ZCv`>zfLo4FItLDN$P zmUuF&c=LgDn5of#X6LZx?sA};fu@cf@)bW(hOu~b;&QUVhzw|+y^iD=2PTD_L`L$L zphsptpp1a{HpodHxWo>>B;`RPlb=E|x`qsFrsAyFl~2xKbb^A%ByCY#>fQ_83gb~GirNmr8-cQ*oV@WDsgRr|CnGH>#E!iFeKE~dL2N(DG=RFaBR|1iNKP!RAxDn+{uje(f8x|foxK8IWeVkl% zs#+uj>vx5XC3MV-@JE1R{XGPr4PhbP>e@@faa(Y*=7edju(Kyh0!HQ)7W4Mnb^?LA!D!qpgk}~ z&^*f}Y9j@=709dV%8IkH5ynnT6zFWjh=WAK`qPgsA#pGM)}MqyqC%fpyN`^LzHzxw zaX%f8ysv?lC8f}-XZryT9R@4jKXf4+dz|{TYF|!+{DU!?|2}SkZXi0C9RXS4xE`~t|3bOlGcy`JV&RmE{!C86nl=)AV!SZlTE%I+ z5H+{DpWnRte0)5rBoe@+mVVykzLAjyb0xBYwaLJEhc=*8p17=$7;m{XIymetkybU3 zm#9#Y)dhRzq@xv2kq%lQo&JIe_`U{tr7K}yfokn{W0;%U$4p(gWL^|fgyzW2IA@Wd zVG3coUSgmq$TDJlH!jfMlDP5nMzyC5-Vcby0)7)qhYJ;_Gb8+*Vu{HT!92d^-h z<{s_-!=d<)9riSUrPA6o$58|chNF`O(&Rl?GYz}%a+M#}kddk^N3F6sjVVFN)|EjW zy6f~Bf<#y`R9v8H0A`t0$#`%$c3yV31(BweFOO2Xyd@9IVo2W+u6#?}mjyc(Fmq9K zBXaCYuUVC9SUnq!eaTOwmK%hoShpr15nN@S*FH}xnmaY3q{K{Gpzg;XQ(m-; zYo#k5oz|P&3Ok=%HRO(MVQ_S4K{^(OT?(!Wi#BOBNJs57&eqi*&SrMA+l`Z)iC#g} zQQJqCF?W=29fC#0ZmK`AW6(8wb$;I9{HV33Rm6d8D(5KRw+CcoDIv9iz>yQD5R1*s z406rOMS@nK25l~0a8q^yhjzne*olu%sdD>SR$Ct3mGu6is6#4ah>d-|bziqr&nx5U7Cu8g|1+{HW z+_2z8UtJ}k*P(i`{5P_dd_6!~Pxz5bAfc==Sp%J1)!gtIbkzr|!!Vn6f@GQF&7}0t zf6#B!cL?BSuwxZc=q#|u!Oc78Ns+BZE5X@m3C8VdAMo>`RsEcXXO7h-vuv`}En1RG z5&<%^D_crB!39<3lOn~Qhep5+*QmQjq^*PEx2z%dK4wIgw!>t?DR&LfSgfQ1C5ggN zAtZ}3jJU64w(PWEQVi8SP;gI~L_KEEcTAw!idvZ_k@QN#=<$An(cg}S?`A<~k#`#@ zMGS|&PbZFqVYBluj*V!+n5GdYI(?^%*;Q1K-4+6h6DG6IpBnEg8d=EDYwjW|QV5GQx58`RlOw zvBQB;F@Fm6#IX~*u7*tEfsq9}G%fkY*hn(1rppCY(4fMw4GnVlA^NsZ4*ZoJ8?s?hw*CoYM(zdMV$HMc4PBj_yR@r)>e(HSgjsyAT41?2QXFU+3Z_j(H{uEhogOoN*k*wZC6 z-}F1YiiYjW+5^8jmaC7}g-@R$SBWTg@S_UT2@{8lo105|)Ioi4)I@`2$&;VBb>&J* z%-*-g1M9w$m98{9kD)hxTX7MRkW3poc6blK8*0!^Ns<5@P+QC;*MS~~pIWHX7^$@w z&sj_3yMA8oQ{!(6+>82tzMVh0%RX&mq{MEaFP<+y1XLhgc^(a5h;KaUeAJQOE|+Hs zvP$XGz}NAbH$IP%;K1k$>43SHb&h1FnFEW83TdVHA_X+;{ZurSGR;}yI=@@yH;sXp zdF97VojBa2r8<_SN}(mlF`UquXcw`dao{7C)XgM{s1abQa$#Fll#lwq@Tn|{sjK<5 zJ5kpxs9T%VE5qafS1}47*pc+2HWX zJ-DQuJi>R2kK}%F5_!UQxN?^?b1mRpvA?VFe2knF;$3;*=(d~V*<~@zAEzm)%)N?s_&t>p`^heI~)s16V}v;_bCHUBINn23IqP zhj?vJc3R2dKNGu-9IxoG)2|zwnGys`-|d!-fj!9^H8%Z;)$=*xw^08J)_)|miuIJX zjq*gBPBA=g$-;igf)NcCTHS8oCCNhKM1UsAD$D;g$r*dHeHvaJdl7n>Ld#_rc36^ zQ>Tmr6CB-A6;`~(&hFxLow{4>Q+w_9s_Nou5!Hf*8w<;`Mx&{2^S$c}@FySTGOx); zf8G%tnyLhkNEBjgoVI!L{26k(&X!RP`Rv<%t4#R1lT?-3!PmNC6ZI)`K7#Aih#y%t zc(o<13{6R!J>u;fc+b}(j$ZpZH+ag3o zU7lumA9fsVyz;5qw@o@!@2z5|v9o41K?zueyM zgTm&NEFB#kg^dZv(=E;mR4Hkyd%9joHl-`^;Q!(h^y^})XdNqBA|uy~R2M1eS~LD? z9V5wx#z}tmFmeH^24KThqf8#Io}`2m!c|_*36!BEv5_|%FVCUJR;m2K^ZQg~aYdo+ zWhF?spdU7es8dC(#E=?oNJaVqTOgYIuHw5v-8!R-wT}q$QmggTt6<4fj^Cl{44ff$Ofb@{#(_PJiM+$Z4p++S9H=LQ|&aj#w#kF|hzrvLmK_6L_ro zZvnw1gqxA$H&vR=HtJkc*cgXu%Es_@$T)FgGWV5-FS3^A^!9eRbW0X2z`x@JGZdmN9 z{oPzS>7-Q~I(rHF{RLL-zG5=F>w5L9X6Wsi$_o**$BnTrW^4ir>&F$6KRS70w4IiU z1B(#~3Qw|!R@hfJ+O31pX>2xw=#({?A!OXVqRP%2SCB`yDwaB&WQ?~I8Inh;p5{Ds zT{x%A8?*b30#~&4q_n5!0Nk#zcq7NVQ`9|b@b_+CuduDBU)6%TEZpOy!B%WcS=w2dDeH2+#XDMHre*}XFGk*Ocd|0UVPnO2k?zs zNR9RnPj3t74{)|RU3aOqC%os9fBA^|9q_Ji{oXwpVt#KnR>VcPN#?Zq@aWAx-f@t0 zu>sa_8qM#@hEIG{X+yfy{tjx(wB=#se#o#6Uce8){4IOC0sqNJvY4Z$+Woj6hw*$sS zJWpz^lx(|697LCKKTNHc_i*lB2a6m|fO%tb=5ezkTW+bd71ZTaDq4vJ1_w$sbWOv2 z1T<|$!>wy)ILOax7>*J*cSH@_B4)HR&RRD zYNb{$5Z&HqZCurEU;F$n42Qq3QfrqJ6D~#7MMi;>&+Hf0z0w!P@|s99?tepn#(`Qt{1t7_{)xmzs-n%T;yd*|?GtSs#S zTN99@9)X(oPouA13CV!tGc|JOAOLUGab_V*qWIl~y0Ux`ou4^eAkj{^on9dkTldE1 zLOX&6!I!n0$ZdC81bTWgw{|2~;(b2?gt{)kWZ}D5QZ8m0sas93L}{O*KT9GEk?+?B z((i}-iHLO-87uI8m`dB2HlHnvU1p}M#!$|=-H^1Ef?b6Bbj{TK_y&gGg2{e~ zrkX(B`FthiszqnVo5vDFSV+8Pp!>j)%$Z919FV|C6LI73#^$c>a~v>T&1g~S*0Zjw zntCMYQ7Lf_10Y11pz`wJJuWHMl zp~N%kB>STqYTaa38J|TFi8$8baOa)9Vx613+ykvw;|$9Wzde@Tx1EWVS8G;{Vfy$} z;5@~jCYHV6nJfV`UE!8uzhz$)03sNBPMeou!;Hr#Zt-hgC>)5T7W+Mn0^P-2k0=hw zyH8`=f4eknoa=_-9#p+l)?+v#T_x)`8Ip>?6Mi@9o-(b;R1SQ-GSrbDo+$4BW)WHI zyzBF}?T5JihiqRub8dTW(jzSISEvDBF@O{(T2l2384kP2A*ZY)nrpljfJ8aG(V}QSd6{Nbu7~KR`hy+y@0#Yy1#fpRl zQ5?!VOK6SAMzyKz1Ic!EZ}@1hMBVsm8Ge#iys6CCn%fi&%%0%b4qB z87rPNf?ZO>cWmgFX-;@pl58CL>g+Gkj?Uea-c+|!qhd_2NVU{#;pT5ANCsYN9%??R z_*_^V?9TD5-~aS@&e&6pC=m<(&Z$95C2ElWA+#4(jq*N09_p1}Td2%s!n^2pe$CKp z7dbqnz^I9cPl}kg3?`})D%uV^(1Cy!w#BYQ;DaN}#^e^7!EwJscxh)U#k5YhIf>Ox z<&)HLkt}etSc4YMx}`?38CasPl6lZP0b%+yHE0r4txhymY}$lvL&Dmq91-$U3?Xvx z7bTnp@`da+Rxe{@iMQ?EW_8zlbuLBlDx5(rt?W4I3K%={3RpX~4_^e;7CtFyfzek& zRw_7~3iOtpjk#u_aV!tp(1hUG@hVcm2SHrH_eGY7R%1n}_0BSp^?4QAr*JG1Yl1Eg zYrX(VjPe&h0$DgO;;jM5mygav1dKLg$dl8x#i0@J0pltMT^$~s*#=G5N1Y|l+LWsk z4I-Dlv^nM3fwp!~?RCm{chIa%x5U&P2!rG$hYg(JjkBZ&)5W{9HQ+C2Ok477)uu3m z$^a)lxr%-n|~I#nvwyB%}8&CJl#--nHiH6<2fr7M8L+h zF%-59OD6L#xOS1kkAWUYKPQm1Q4(y2@O9wQma4|8y9CDBg_6@o1}d-$^?s&e-7zXZ zOGR%xU3!vTz{X(%zzHiY$(eM@Qvz(AUnp}!ilqbu{jfjLB4cXni?cJzSK<~cY-Cb^ zffDZ9nFSc=2lngxgT+B34Z;=3Q5ya-7lW&S=qutwBUfT!M#2b{OU*NbpJ32dz}9~x zE9F6VR$rscne)Ijpg>|p>xBTcYPVyJ9WMO1w_%~qr|C+uQ{%?Whf5fZ* zi7i>!{v%=gA8Bl+KiKF0dAqdE@|A6QCP+KdLy1{ z8C{ysr=`Vscxtz&sBG5NUJvq3c{a9-KD|(bj=iJt)R=v~*_>~)QL%DM?ehBSZ^pP9 zpP(rNpi`wXdn)^ddd*p1m(r+f1w!L7oV9^~(=Cnkp=6)foGjQGCZV9^2q0U;$ zx1eo7%dz%7NS3JW^xLvzn&@q{2>!0?=y$`YTc@*j}#4}km&GX8b`7nJ<#{GZ_QpY#7*`^Wm90QjGEn7`KkCI}AvKZD@F|1$^< z{F@*+@c$kJX8{5M|IW!bJl(yNlxL62I_~4|FKACl+x)tv$@NcS1DQZT#WV;QFcWLP z&gzM!JF&o@kZt@3O|LD*6q1X>5(=8K4n#Ws0uyfLv>wDzefJ_4wl>a61tyC%gf#2! zJarNH_50DA_tnRP&GDLZ`RRO_qH>8$mS=|8*sVrBv2`HnW_r=*^ve{_v)?y|%x{3D z%1|+@laMqhSCl@hQrHE0@9k^nBd8s0D0%#HTMgx->EKOLp%jtf+8k_)55L=>oJcYf zaxCLWOTJ&H%NoqhrUsl{`sVwCmeS?Fdt9K_W{UNqexL8H#C(gV*7w{|_#KspvwQ;A zY&+OIf3B;cX{(hLegO$Bu7wH{OuQy$N>7;J6MvGIygIGTh=50ERG;gpIFf{fI71`e73R=JzxD8^5{Mlg5?_L!CDRD? zC#t`ABp-j}RT*)%?djfX%P!_+4%xj&s4^R<#%=8miw<7_cu_5qu&+oNg|Ub2_Qhp{Gyvgg?K$?IW`>(Eg~jJK)@LwZQ+k!lATQWoOZ z$axn=umezUdz-JtBJ?)vY`H{ZOP4;7*w4sTrNO`D9pC`X+uq z?PwO|+Nnybaa=)>!|FPDUa!8S!`N_tibbl5h9RB9@*rWmlSoY_o2~}Xe{X$fXk%X~ zXb^#$8W(egcjP!9AuHggCm}Oqwm_SzgNvo1gjo=lXAuOVy=e>6`HmdZlnrkJyq#3y zDq1QxDhrQ zma=q2d6`(5yh@f;B1b*P+7*WZiDdak`w!~vpVdU8uXVPZn)91JqVxOF(N5Aa(VZvM zoRgg_=?CdLsLo>wl1KM3iXQf2p|@BfDf6GM{&V(OpEiI&{Gn1{q%S!T)3{hzUnWSg z`Il_So8*^PF0XN zglh>e-1t&spHuJ&2&S|q@ujLFJ;;dmoW*!nmJ+iLs8j{}RhD4Hfp8y-2 zu|?EP=#lGwG_$SU~Vx09t?UpM7SkzC8s3{oOG5IGys~UFYJ1QS8#Qf0j~N3 z4I-5XEaMeyJzp5BJj!c`am~@DBm&(iljG`u3N~U6g2_yfIM}1~JZGeZQ3~wgxfkig zd=xTsAz%uQf%0}D#<@})u_*wt9n|oam<+*8k=`cx?_C_BRzO?Os|B|v9b9670pxHg zSg1RKr7Uf10LM4J^Bx^XidU#cXS1&SzM0_fI>P<%2N)rAx@z`vqO~|(dwhxqu>Av@ zKPCsQi^O1t{Tv!<^14>Z=~)bfm$rFB{VieIN0`CU-#S*+J2j+3dw7%51|g2IuNayi%1`qjY0L4rKI5<3pT@CFg(bpkbm- zm8(?+o3aa`We>CK0|oj0MhtHwN2DFnGsVeDPQI#o`V2`ue*JlPq{8#NGwQ8E>-(2p z>}*I!rAcU91_mfp=}yP1zM0p+dWnS19+>nCJ6_n#tQ=(&%Gaws4Mm znY$2Y$Q>#+s2HLzezRkNZEwhBge`B#rGME26Pako$ax>Mz1 z1q0v^h;L}!AV7+ZQWEQtZ(&~~%3`^mOD3Dm6$Y0n*gcYTg>_ZuMnk7qzmUM1jdf7* zKBc*MwDmlQlihcJg8jCY_O$5z`=dSk))Mw191$b8YHPQel(KMAQz&qsBuOKSq_!-7 zM&;0vq=n2@h9_y9lpH52o~qD7(YdT$nM)o^d7*J745|rTJ3F=~`31QWA&!d5lN%CE zTjhA9%n7l!#mai#YTYdZk9I6I@-V_AxDeh|rG|ma@WkhIjM20shb`@~vb)IM5r^@d zw*x!f36Td9K@1dyE&DafXXnpz#@CiTYna6u8ugv52?IOFRufuN_v6DIRKzT3`Dx43)4Tg5RsE@N8TT)oCSi)sA88PW0V2QCmUhJqF{gf7H~ z)O_D#3do8$N_5fG3Kh_Bb=2;DTrDg*4=9hwT6^BchG#T(8&3+L6yjfm8mCOCghWQ= zWt?59Jx^=l5A8#ks4AgY=D6i8r(aDpx=cnKyR&HGD}s1DmY~vZ2kK6(~W^ z*H|tKVxbLe-0l&o3~B%q>u9VMhHaoYNzo$7D6aKQ(&HF=Qd46Z$O=Kg-7PA~er?Oh zaQkGA%p)vaHu|LHlC*tfffCOJ4X(38pEQvM=*gk@Q`V2C6as$?=#SM+)GM!OSZ7#s zT3ZBEee3;b#;`82GPODtSB7GMiDD)=^9et)-hA;x&Tw^82{RgNQeIQ5foFE0 zZqI10Uf3w3?4JBt&5Lk>8DgTfLfthWPL{+G^es!q+F(E_DZG}laGbo551104&x~1V z%o89V(J-&kA#{wYp8};RWQIaWBG3e7Otc?fdp_HmKrD^W$>yYPpq$LHr=-$uR1a4| ztqnhdXrCY zV(A_5J`Qt~D5RxRKt&0ClwieBiX52<;;kBLau2uM#Z_F_&#e++3Uj|QY0$3OB--C5 z=zM~8k|0REOi6no3S?O%<;uRE&CA|%iu72Yb9!e1CQ98%m-(*mmp!dN4No?!CsC0H4@U-rA7yJ^}teKqV^7H1f!4c2hXli#0^=T^S180&q>A5Rm zLcY!XD4`8Vl+hWpJ7hz;}UnDr!xyv7LgsOp4H0b}fs z=7a^;^g$|5d=Y|GdkVuliUvR(ctQ1D)rN~$6C`G`ehF9mY0utxH;zgq_qE*=nad@^ z`T~qe5EW8!R1tPOuyr3i7-3zG;-)A*1^ zOQ9eI?)Q`F41|r7=`_5pKFD91{9f`y%t#wY#~DRBbqfc0{$$1#9?6;$$MY31SwE4UsOo%VKVvUdQ|-AzjZnur6|E# z_tE7k9wR?1f_~#ScN7A^wE45joTE>|XfT7Few8uYhBlS&YnxX25@Af=cC(pZ2)=$k z;f-|pmDyv+5HtpTd40L-3=!8q;cCdF2Y<&_(y;3N2G&O;^x*y-;;0s?Jyl%_>aM!q zl89yqjQw;C`szyp*cTGWIi#lYOtP$GVLOC&my13h*D;0=$Cd<(E0Ty2qs7vR(?lgL z&q{S`eenDAI%+fyMZ$C#(~~Y%w06Zj{5EPdfwGjq@3e#C;6VWb2)iJM*xx*krN+@! zrM&KwkFT=vl(je8Kc)$pYb~p5R(=mc&Uw6nVZ%x-$WS*}v8l;-mW+yCZ@_5^sf zNM}`)3=KC1->`vD15+FTaG(jYWv`vh`4k4VHRe`)w+PdSDt zy?yyec)Lz#)!}rRvu3TNTCe?hTo|v-ZpmKaLcY5x--_BBoF@gRGCVTK@wvI}!)Wn+ zUaWB9Nj0_OS;|MOEc}&WoR3$#!t9Z*6@Rc_KZc-rK5Y^bi(7snXA+{tnOXm|gzzC= z>h_x$CoQk8#$f9QijR*uP!o2k_HlNQeXDia+K{hiD9w7HCd8mhumIUFHD3(DW54EVhooJQ zc6`;__c0a_i)~X*E$~xph!im3Z~7DI&}&B^!6iBSF~{G6z%$)T z&9Ph*P!-^g8tf5NISqjt$g{r31=;p~mu5gD9XT|I@;<~$y;S=R*-tIo?{T+&V6Qxj&>Tfk_7u8yvfi8D zoqmCeJn9);is^PI>g3FrvHPitOQX)(^0A1PF}9TQ3*E{}k~RxI;gOliA3}($F$4QU zu%&$HqV3wk3iNID6~l7t`1AarM_yyDd5|hKgn{*R1W+R{jf|p(5JA1DC-v=yNmq@n zv{(k1F3+GYok5+z(8Tlc-d*tagtz8;{w*Q0?df?bIP@_%u&Zs;8|8OSuAH9RZSeXfRS4}K%*1?H?L-UK zPkDC!JR*q)@Mq{+T^-CmpAi+S4Qyh~76r{4Oic{CEX^HiTif_SJs`PRQ)Tb?igh}P zVEjZO^EWQ2Z#O!el}&=b5I{J`>-v>ABbx-c3XpuPD< z_tv;*ee5GETGaZ-_MJpW6Ux6v)T1Y3!Aldc3Z^(d2jAex<`57=Bo=0kx&^=Qh)i7 z!#yIoY<#z4@n}%fyjbhPnkBw?sNz}TICfQ`bBsj)dXhyx28MF6dA znvNBa)p&%?sx*e}jzYKu&u&9tLprB2_>?yGlrr}8)C(6u@62m*(%sa4Rm4@BD=P$p zu1(+k!LQ{2WnPf=@^?(O^!yck*VawNvo%Q&%_DlPNQQF3g2s&z9kvxgEsLfCa@j4Z zy^!I&8?$jbZMt?kopg&xLBC+9OiB6eNqk}NlrOzNz}-2(0EiruXryRgt$!lb9k(4P z>H{~v`ZG-XOTVt(uMb4J(5}@7{qqUYJm_K{gcVh8?nA@Nqvf}{97FCpRl>bhQ=Q`< zSULSxt<3GNx2H`K=bMXfxNo$F!%7MVSw@Ry$b_T}q|!`x_ef#NwPs;EzR#h7ZV>p3 zQX``{0naFqCTtr*jCaz2n_F@cHInhXDY4Y+pe~G@IKb8JN|orRa?JY}?()$ExPd~2 z69jig;X)l5i+X`fuu+8wSQs{Jg{%QH#B`cklW`E*X(eC^WiA6}?E6o+>$=MCFvK4oP(5g@d!(CFJ{ob*sB= zUM>N{FP>Bf4%$9n3P>z9km~y%dZ?TU506M*2CY4x)Y@6@R;rgfAQ39&q;)%;Xx8kB zM(@w@x;n@)_Ik{>T$wvc$q#~}tSo-7G$r#ajgcH|&Oj6}P>PFYP9o)xnkcJ>#1IgF zxoc(=AHu3aFW}G6Ih?~@$-B*C@iF`JR+&=V=_D1qy5Wc+i^1Gmnd(c-rUe>aG8{Z# zF-chN=0>~UWRw%B$+T) zzUgF+vSVN2FpO_93Z5*noUenxZH>Oqen2NEUuND?J~9BuIA+U=kQR?C_v$6w#sMu7 z{6Q+nH-;#_qh_5{qXF)y1keVjVhQ8P&wuE=4J}u2-_?;LReecHx*Wsnbd(9Z@XQNK zqs*AV&$#T|4)1~tg&ZXNwBRm*FLB}^&3{hJ>o9N!;nr}9ji z^m8;2;AOYQXdT~a13IqQALzTfn>bG9bXfPFcpoo(Z>tqFgF#bn=W=*Yn|M5eGSiMNZlKOzdA}?i<938N z6oWD8GzOO19xcV9w*F|64;QK4OIRSA#_ToPXR=@>skZ<*bMTXt*%_FRA_~o7ZAnHA z1xN&=Pat>VV+QfCq2Z|t(g{wxu3cD0Sl@T2a{jcJ_1RkpI z{lAqgEs|XlrOh_`jHv8HC9*GNPh=OBrLtztlC==2tf5s%*0PnPLR2VGL>tQXKQnjq zF?Xin`|0<2{Xef)ALhDq&htFy+0Jv{=edXb*t)i}?cnITyX>LDSn0Br!KXAGIJ+3w z^4KpO@T(QNtEj@he!m}!kmt6FO+qDN72mujETWg?3W;)D+U;kn@Q95?(JE*e1DnPa z&(`U+TYbuwtvs8r-&?9^!MXpls!(W+O^{%aWLL?utBUw76--b0{L+O6I7|bV;U654 z+Ld`%#e(^o+?E5Ed^G)cWH4`G_XyhimQ(8VZmHAT6wYnOD;&zI(s8;vmgcd0fJ0kh zNm%D2n>GvC1z`z~96W}NK1Zh92Hm%E9{X&`IH=>gGOKCYI8q#ScYIybqoEE&l8#Ot zE;{NbO5)0tQ}2d*i`C0DFHYC%ei&%W>@Pi7J#l?B^zluuDUY!s_6@msR`m57x!-n# zOM9$;aJ?wMw{~&kgMuGipU0m~<_*gXEZn%x(wte!?@LC2(a^&Oj;!L0VG|Aa>K@)W zWRmNzDW={mcaQV2ZP!sT{^u1d#Ozw+vN^+)-vss^DNptPrDfm|cz<(e&QkwFeJ4)` zk4=8>JQ5UD@_p?`p51a5=Y&|sUl)Zd%W-+VY*(_eG@ueQLYY=CXs{mE%tse|djuA3tulFwgF%SSrJ)`1=a)k6Q|CYi~}a zR8?%;8QG}G=6r@Fnvo#K9KWdh!Ae<=g(Z92*(7}4oV~jq*)Xu4Z@5@s=hsoscectM zLViQN$wCXgcN~vm8TAey{1GGW!){g?VPG%pu+7^QDbei_bMk;c^5;v{#mx>q2Zsi` zjy?6Zd^GB-uAFeb^}bH&*~3K=@5d^(ny)y%6g~EG!Ty^1o2U=1)dQR(DEmdD3*1lN z`L^DB<@Cj1Elg&zmfA-L^!}q~K@Ts1L2IU+n-bkl7^{rN4##?5m{OW#m}C!-t`s+q z>+ikmR``hf=DI*h`Opo4MuAs+Lc2BxoV(Q2;ZV7Ba%73wgV;qVg*_j;B||VzoAC|n zpR9F?-?XTWGf-P$pFmjU5oh-Ppi17HtDR1IYTq@u1KCsA^3GPC-4J&C%Crx+1!}0; z^z^$MUibP{wlG#*a8XNjvsm3&%_5Ic!$$gqd&zoPXX+$!+A%FPoQSMB z4c4?`DIyw&NnwV89rUqs2TVohBq$2;LNsbC(T_L(o&~o{zPHcIUG8V8 zyu>khPb(9nJu`#DxvQF7cCtSfMFu=w{5_WitDJAE@s)8A-Wq)%-|DAko8sXvl@#Zx z#8|;*xjj?McI)##whvN}V|Q`Z$umkAj5wLWa4{+@%Edd3Q=cO%tT;_Ew(zEn$cg*< zBkj%~4UVsNv2KX5@^9KveeJ>;>qk+)%$(In;>T7UuBp-3aAB>zUEPY?Egu@TCaYeF zR?>_}S>|mobZf(|4HIJpgUZ(!o^Y%$48Ez6$56G~d7-*ex4S@X_2u}GChbQY3Mbe{ z;v5Z`gnWWtPad#8hc*w6RaulVd9Os;uea*Q=1Y?|RwbTZeMF;1ec;ZL%*7kkEW~do zs>Y9GZ>rzFa;LM=*?8V6bh>C!pr0~t{~MJtw~wB?nMQlQX`~~@YNxBc%fxT6ZCQQG z$xqW_ZJcJQ!!Dy$xR8T4eEmFcwK$kwZfVvzwfnqfobW@VI&Gzsb=aV$r-@r$mK@}E z;tbadoV2~RavX1a&5ppirdHOEulq%bfLNy-W?7TBmkUeJqqtX7ap`8_&+V74{l*&{ z_sVp4Qt+Uor%7;Q-sK*#>R7vw^ViNw+b{N?Y)M|&ZWx~1>+xhG_tEC-;lkD9zk0lF zYRUw^ZZ)X$D(oJ~OCOb7dxz)Yxm7xzqWkw)4t^AWDwoFjO>2DHj)TE1%iib8da_UD z24BCKad@3&%f>IaZbs!b1WXkPm$`8N7*I6a{8W=GV$WGK`DKimpS@o2?U51sbWrV9 z)cT#q{c8Ej>}!>urk}3yOF3~9`?+h`^V?5$pB~GxST4T?mAK~V;d8p~M?0ceBqbUq z6we0VVo1M4QuGD6wX_oO9 z`(G(^r!!dMG^(GOQgme-8?ALb0Masy2z6&HsUuSy77 zYO&(TrfJJ(eSCLyj&yI^xING1@$}u49Ni!T$G2IB`(Ija`G`l|?@bzi+5UB5_U$L^ zE{Japm-U6#M5J`zeScYP!z0`G&C-N`-rDjT8fgV=uh_Eo#kV7q9$ymZzv1YY;GCOt z!93j5a)RaM`MuHwrpsBEUgp12Rd)4E+y-_=qqA>TOe8BvOR{}oALi??Vmb2j&d2Zf z+3`aHeFLW_WprPztwTD)vrxI#+?5IX)>)73cHEd| zO&R;4E5G)j_x=G%&xNlgf^);46o;%Ga!vWT<5}a0Vws-c;*i1cvWuVHE(A<(oz8pr z>Z`+7m7fQvKY=%uY0A}2tDjh#V&arFu_ph0ig9_YPsNB7>pH0&7JZH5vBsj_A1v`N z2yI_9^{@2&Pxvifr5UEFquY4~g9{1cETgF65v5V&#goM&NWwmj z9n}L9GOnNd+bbK|OONBCZ*0FiUfo$*T*T;pd3)WlU5&iTb3bZj)v4{KJ1jmj(rxna@vB3V_YmPKcbnL1 z&MryXv^_E8+uF=S68pZi);|}xZRD?YUCDsMJ8zpnkKWDvs)AKz(O(TB8=eI>wX3dxWbpvl~^dZivwP zCi-4PGKTLP>wDJJX(l{>fSmmKOV1zhuo43F15!dy*AjkX9P4KonJcCHs0$KTe==^$h;nVtH`N64UJU>fZ9(or9b>DG3#xvR2AnROh1{llgYnF7h5P!A2<%4{p0aIZhh*B*R3iOpYcYMo zzb!~+c~b5_ER5$eN!!VE{cUQAl9h1D!t6@U5>`9*u^j)dgVM_?Q_zd2jV_2ye9M)b zQ0q*YaP#@f#mF+Pd9tExjnDR#2tGsPqG%&2sw z`BmS%Zz9nz2vd`s0p91fdfi{B-`04Vmg=JZ$0kC5(o~x8Tr_xt)^B(&5n_vxgkZTO z=q>Sg(Eo>co%oX$T$lVE=HC!r0M{k<3nE=7jyLlUj71!q_?dVw>3(o8c|K-5xJK+X zMEpDHCl3Gn-kI?Tz_N(YU>Y!Bk{wg_A|e?DCBz)e@!Q&G-Uo%6vlkIw68^r~ldgQ) zC#FA#TGU7Aecg6Us3J50U)37Lzm4hm<}X#ZEw*C|CN$1P?Lx=a#GVmjQaz^9XJ2x` z`O((+hZWAuYJ4iMWQtcsIYk-s?`Fw}SZsaA+Gd}=!7;5uM!neN{3h`xnO!;FqmNsi z%*fO7QS<2AUR@zy7dC)C!yhMxxd_+ITOM^&GY;fPB`^`XX>v527CO6{L7T4EOYwVV%R3qx!R)_)wxJdl-+N5rm@_( zSL~4g<>Mhmqt0SJ{ITWf3yuemR=9l5EqXn1bvjO4ulMXwrK-oWskKv2-+H|b#CtB@ z7;?SLKkljh&W{uMEXlu`^Bc{%C*n==o)4i+KfpB-0NeaJ6TcvxsO#NWfGLT2HsqC1f% z)?mCvY<;m&{ts*SH#okj2$eZ_ZHRC9sOO4ZtRh_k!uwu*aENETYc?iwBK*=J_5r^Y z-2N@qKD{;()93hwlpR~%N`!6RUQT$qeblfc!FXcF=ZF06U)IO$2->mojNB?y`@}a& z%L%%#18)2}dH8W*fQe)p2G1)}Shd&;aa-cz5u z^i#)GBef}E?~QBZ1*f}8MBhYDuf6W6{Z37{-1myp_kfA*F7A&N8@D%v4GTZGDB=G3 zH3u_800ZN%Nw4kp9|zlBcRug`cI@VzqtS+U?7!H(zUfpM_UUD=%`w8-J=xa}YL{u= z%iDV`ck3Pf`q-E?T>8u`4I;_HO&gAE70M^iq0Lb@>O@ZKkJ@R8Wjp%@^Ityq=ik$5kkC?opy{#Pk)?wM>shwc z*z`LpT(CKTa39K99#9#Uv42a}0Mn6@GxrIvlj=Lu&*P3P2U|(U;X#87MOhftwI;cd zN`pLlkDj?~+4!{K2UfMgixt1Nf089mZH>I9hQm1b=1iwSUY=zyp4NYs&v7B7Z20Be zbpe@_!W*lQ6qB??tTCfx8E(v%+YCG1!B*T`jJw=cfhgQ!q27sWO4lA4T5-gvbYJL} z&E?ZNnfAV=d7kAp)0Rhe>8;{dxSJPV;b@@~U}|wQb1L<5h@{=lrG?pu%TA_Q$DK^H z)}|(A9&bEzQm^9HAP;LVTFcA*z*nJedvO1fIDyZ+ZpYKW(ZTpb+Q#!E z*4K}m?`!nm{(bdf(afD5oE<;Ep==e$ZE!jM8jKi|_Hg6dbxABr%INiW5uhUxo9~F! zo)ulpftOQxK}Vuxm>#R`|HYMivACj%75;I;4 zF%Y(|lauFTR#>-MGl?<9z_fI!1K*;@mv1_~k~TZnYWkYDzKK`idb?@s{;9%LtM zEPT#A4y<z&&u5Eq<+-y=)Q{<-hRhQUNzB1?FT`DgO+8$#dD~h#za2qXN1kAC zLEp7lvLvU(aB+LZ*8u13C%KxIi**(2-t3CC;*-J&*Sn$~VxJ0Xx}!HOXOL;&eY5FJ z{<_OWTT=tSv)&>k4nKc^6^^W&L?H>%cei=G2>4J@DPOL|-_>4u^isn4Yb?neUzB}S z30pJd>Vje}MKYW#kUTlA&vPi)W{>)Vu}7i{MQ$|IFH^807+XDK&ow)1@m9mU8S}N| z7Lz5XF}B)bAaNmcf{|UUm~*62omkfZ$}f2DrFVphFbN^YdoiNxM|i&T9_rRdVzf5A zatqmV^V|>Sm5kyUD+ZX8#j6imZ$D|4hCN|g+*0FM@Wr}%o03iX#Qx2(EPAffn2}smMNJRK5W~X+N`6guc>`hT>N%ytd61D zxSWn{QLT>I)|{NY%~-C)aXEjT@W<%pcjRy~NZ$rZVi&tkw=Uv8Ye z(>Rv{!`GQtLo-s5w;x+d(s)Y+CqE9c2DH)$_Drp%lzF=KKjLSdtNj}m*{jpcF{ zHaG7atr-ds<9hAk@oBWt_*VE6+`05)f$3JCJ44pJ#6L<>?(KWX`q)}`Y7x%v7iV(n z;=&uL_wHR`PVF$z(KI_~qNAnVvr=0pR#(5#D#U+irG6~)F|%9ID+k`4Tl?w$DanG(W0n!{dg>4EYfmC_Y3_5)4+%2mJwSW zygs}&$^Kxz>idu{*Farh++LrbEIEe51B))Kmn*BdN08_!Ikd)~BW|4<3%1*w7wf|J zrC#u;iurKK6ej2nQ!L*XeEw;ck<>|@n^?r)tqrE0vd!Pn+-TYQiwupj3zLW35*fEA zH@dYi!FU{dX@e3yy%W{9NRN3RpX+-5DhIxcw|8;0*)=I@DE(r+7r)_=7=!VmmySF2 z$I{%Khj_oQ)_bVS=gK4c;g0#nDusRB4}V^hWnZ-i>)V^!(qh*nor62h+P-A3R(on= ze#dt|S*~5t{RCfezX|vH1Y?*13qT`;0)=kwN5-fR`zSXUVezv5lneN*kA}ev) zOL3IPl_y8+mzT09Iyr3JfpoJ2wOh?LBuJeLkEki^{;1;H+FWu)e?VPC`Qd>AnS{L? zpY6sbtK&mnT}RupF-j`LMIBvT%+6-*;HmfgXtgY6?YjQQTg|^bx~;CTN8$O+zE#)J zcfmoxh>J(0V;;N3*~Oog$LYW0dGhwE95z?sxzk3kq%G3{V&*4dnE8`8R)&X~V>q#~V$G*#GR&4J!)Wv(}R|E#MtdzkX!+o3L|QX#24 z4O!7y9??;aSqTrV)N4pjkwxetE!VO%&wY!WKr~%{g(J0oKMVd#z*N01#wq! zMp$qQE%S@lT)kpQK`KGBpgC##NeLda(^82E?^-ulU3tNz9o8JGH*#4jU~jyKXhKQY zW9tnGHFD`~@#g#A3)d=e8b_zC4(qOB%eAmDyO76Tap9uG-m{mZuc?J?(GNu?JJl4n zwTQ&oPESqlt=3CimKVw{Tzr3boXk1RupcVVHusp9-ko5&=gMy9)xUwE_+<6{HQhax z-3pbdsuvuZFpn`x%zlXEI;#r_WAUg20k23!+1~v)=rA8r+d6#aH!t4KYQ;}EAK%$ z#i1m&bX&#VBsr;CD?~)PZ|?GoXY00R#8kXys_aA5IDfiTt>?bk?x?nT?hQMJZyt(a z3C&qr>fH+@KC%1~(O9nTP=iw$(iZ->O#Jhndm`pJuN%@@nRJf2nubfYw;XABboq%# zXlbUspDPuTSxX0Umj7Itd;;Nvd*OQC){xsGZP9uJGWZQ)5mV)gLas#?e0J-R;bV<^ z+a2wclf(BGF1^xZ&r)|sXh`;_m8ZMGm;Q=kjv;xEq`oZ|_0j46EXr}^+s`>v6x6;o zjJ(=&>|L?r09t8XT`B&=^80+p`OEh)e~I~Q_9VJ$LtM13L=>0!>Q{(p&d|tjh)uxu8Q^-*MW3`P}|5V&v zF3E4A`p#K576mq2r!=tmxY+t0y1230neAt`^$X@FGSOUz4My9}XM0&sZu5U(T^sSV z-yc*3fsop=80hL3kKxhuYP`abbGRe3CAP$0JGSM9y? z_tW-gq7=fW$sR;Gy^A*!E?vSr75ilr-Bj8}G4@7e3IBwnzs zVYysDm!{Y&t>tpar<*rOIjo9}Yh7I5aqF}kZr$bwW+|KMRXMhqw)L0V%O-EszN%v~ zRGGMMTUeuOj2egRSO3wv3KQOyOCvf8Ck);s@7>>8nyugZyiM>xWbm$ykK04Dtt%er zn7lRoxnoD|nMY8$Fm@no% zPI0^6_EXln{OMkW8iBi4>IL*Z=B$f@h>%6@Z zyBd|Sab*MRax8YFfwPzK#7&`T`!N0&9~t|#KEKJ};C~^QZ|m+DaBf`AecwL$SHS|g zmXg=}b^XAbmR2J-UOAVa8?VoN^pd*Bt7`{0pkx{2ca`Vzh?sizWQ6ohENRy?lf#+! z@)##2xIehdt{$?uc9(EE>cj$dRq^UljC^IjhQ;fo%;ia}rORH2Y^xPrb!)*iOWXBT zduUeXX4*5LF$4nf24brQW+ZC9?HQD*YRZ<*HdbQvdNce}Tt?!tpty|qb87}X0kn}P zwz0RgcNG)^uScKVR34P2|InF%R3n3x&1iIHK+RcR*4SfPCAg+!e4DiW$`BDl17_L7 z?)r}|twRs4l>8ON%m+?TJ}Mh}W@p$Zo8V+6(8K<4q~%0h6(7Sh*>2x&oTe*Sc;Cu| zKE9FF`y#>B@;x>>TwUX~;fEz!p8}iE>Lt6>`up7Jo<<4%>w zX(rF^s4dlEtmqH=54mJcZ_f0xRXt|YsrBy3aDmcWx31?p)k|1hS{O9O+4O`nx*LDy zaC?mJ+irK(Tk;@pt$w6dUGXNxyW(S0*q+LfDGe*5&f^ACpZBTRb1EFsJEocSUh{F~ zP`g zH~yK$6;y#zu=&-C8-&J>4K3SWXt!_s3>zZu`>b zLJJ=3Tzsy5O#Sq+6%Kb9yEZQ9Iw;~C-T&eKv1!Hyu`d_cUEi~J?Zrng<4PB4oL5M2 zkIstnC>ngcePPy}^swS66>hy9N93gg$JW-mcDXE)HufJ|xBl~mx|;JxhdGvHAFRpT zn(HZkr@WATQukBPv(PA==}%AYNA8+1KDApc?m06PzF?7~p+wJzuSe|rioV=oQ|9@? z+PrM4fYIw5GJqv*>D11hFSPO({>r`h_7s20^^;Y)T{<3|hu+Hs7$?OA>Ue+i;qmYE zm}K(gG4a(JmiKr78ul#N60cO({^8TG%;myOOBf4egV&jlXzT5|zn;tYncYU5m)!8i z_&^7f4;z=*cq}phX*jmt#^1p-+oH?MEbvX>ou?niRbIKHwB#^*+4(Q%$xkArB`)}t zRy8Yqdc2@ikGBzLGvU7t6-kVy;gx>;Bk0 z;Q!EcrF!mDkLZ%kI)^4}-YPcqza%u4)!qCNvXbc_hhO$S;jfDERhL#76+7w_lroSf`xSsH5|83oBzbgy{cpEUaPUN>-@Ito9D~VA3R&Gs;ztk1k*Sd2Qbi3&V+%_eLKX z7jJj(yy5b2qI|^X}{c0Y`nK?g+P#?XO?cT=H%+YQK!~If8kH7zIbT(Dp#jvER?}V zi!zvT3nygf^hKU%3GNk%&%UORzk0n_c1)76p*#DEWrmBle^`=#-Q3Z+AqSSr(T+2lp~tGlX+W|t~erC#3Do2WR$USs|`%P&Ubb3|X-nR|)| z4bdH$FFk%JW$HRekKB+lD$2sCmM-k9&sh;3Gr?-&qUvhvtCd#0a$%gX@rj`m!DgCu zycgCjTI_a7d6O{P%9BR24Q>V8F^BWHw{2zFcB5yt+wSV3tSmF}Q~d!NOZJH7lsGkK zFqW-)ez9Of8xJNE#rj4Vli|e(tqL{q+$$i)#RyI32%LX_p zd~aLKw)m>#XDPxq3bB zp4xLu)1{i}YsV|#D!f9bj1&iAkc z`|DO;p8W^+K9}fIc1NU?mTy-(GQE{0 zVv9ve8kcYcCcrJ|6jU%hX}VtOwUZAFZleWo5I3ZF`)1srcds=RALQyud-h z2R0+xyr;*`9BR4{kS^8{$-2l{ltJ0#{B6NT`z9+w*iM%Rnj*S-mc>Tmx=f#zyyQ2% z?4ojf@le70CB4~?q`DL3bJ_Nq2;OjcUs!)GUXiISXjow^@zZemBTtSgsp7HF@Oy2( zgPL(wW<5`KpAyka_uP>vT9amXqg+XJ`4sy_mEr>RW_#AlIlHf)Yq2lY{Al%ETxkf| zXBlc=$+?k_DeH;ZpuCjnTF1@ZmGc#k>W2d(c*U8_DF5` zTFfA3FjuUqzsUHS(~H}8o(J!h-CF)3cD!ubcb(DLl)u0EKIvqMhNU{Pu^)NI2z)5GQn2`<9_ST(T@E4iLG^Z0$;ul-Jh(P*tQt8&-Go2=BVzvV#hD{ zU%n0#@pIWX{Uc>+(mi6Qx0y?4y~42f;C1IWo+s`5R^?w`JF$X2!f+gM)z|sP@|+iS zTk{D=TSQ_*utjOCIceG0 zyzNWFT1lIaSwbPZMNBA2btbhogA86 z<|#j+JP}~oaUyCQnW@#r+a{ zS{WWn3N5}}d7>ND<$O(~Q^ZIXxxu31e7>qj_IK@%?A{g?pHk&-COKj{M2;2qi49;U zvpiCm*i$*5`YpY7TSY{)!Z=ok{dQ`@WhTQeY<;wd5yOwzoj6lI&qD-EZ=Vn|;`n`rh+$Ta&(MfA84*LP@zvp-l5|+Tw(}4x2G~ zyoIg@8n22dwbb@+*;u9=lAYj}c(w7n(pkPQC!d$poWmZ>zTU(8O{py=05>6LqI%6{ zA>rC*T@&4IU)gg>ts=od2xFyi~j1>K?~(FH;NiOm!XGH07vFk0^ot=tPZM zEfx$3J<9z*q}CKM=WUFxeVkg$bBn1b`BAn3@;rD`oMB<+9v0P%2b>pmSbiQbN=TFn z-l+CL!sa+Gd&JVM=YYu=a8Lqdo5)}k4&d~3T99Qpbs&UMeK#puFJ%d1vRsOZPn zj+{AOm2cg$<$&Bnt+s(mI~n#iX@a(1^@x2vC z9Cu5nD4N*s-jaI$X0IA2bJ0cb=}2K^&tUyTyiQGE>|{#SHvYy;E19niyH6ZhR-a;; zk<|OcGK70AcUmQ(Vc#m>^eemAjRzZa4rcc+dKO>Dfli4{{A&3vdaCQ~s_$~lJ6~yO zJaA$?pt7L)l3Ey~4j{(k8I=TEI``z|qdNgpZD z?>ctl+`@HJj^91AZejhD8aDqbI>s)y_}V&U7Wr$q@Qz!@wuCzG2IrBk5SX5E2cC(M zNxc*sjARm*y~OkIYTFoks+7NZ_XTWdHiz=VJ(!)1_RM1YllvEa$X9tL{VOmsR{@1lOHdBra@iE*&hYsV^RW zXhVZTR3-Bh>oc}D1nT1xds^MTvrlSh=^{Qwb?vZf*qpF8I$?-uhi!)ggO`Rt%KrDC zytI-x`CE5hbLA~sD$b|;Yt7VJjJrPX4YM&`+~`xb``e;>gP(PXSK9^$^S|KGH@Oq~ z;h0H}tv=$HRmFf-otw)t>z`Y7zO^>kR_dgERpGfjd~s(uuU5w$Hp3|QzzW;!#9QLS z967z)O!HzCFZRS(EgQanXxtd%a$;RFyFBjJVGGBj4uy&8Up#JMZlk2z6>P$kZYQ+F z^w;WrS+g@jK>WG&nbD-kr%Ud-||3Fnp#=3NKpB=9QZQb{Dbf9*itfr?8BC5~#@E8^E z{Fx5>MPDW=4Pbe!Iy;@%q6jq@o9FRxeh)vW1W zDiBfQ(%)knuP`Nfg4@jfaL;WYe))T~C|!Ye(4AdygQ`dur(-ntz^1_U>La^0MScle z*Wz(}MYKRw+bd-$Q_lBNk@xw&7^{>qKG_Qzw&DYvF4UNoo2uMQ^;`GGo83XFgbDMy zJYl%}nz4%IP-)O;Pw^8^#z=t&?hmRMS3Sg41ud88GWOiDPxPy_{=MC=i+1;Ee{FS8 z?Ym=p~4wtG_4KH;{e^sQyWyt9gawB9&TwKgV z|K^kXZoOh%L$ALVuXVWhW=+Sp%5M>kAqBcYJshLk5#FJ(tXHoltZajR#&-M86g%q}% z8tL1UzT883x!-d*dY$D}d$l9aJ=vkJ4?HSg92&?U*byohxZ?KRl_QvY4mQ`98#ywPp*rR4C92)~b=jE2TV9zS&IWFE4wS|qHnV+^YH-3 zn^TkH^3X+MbDvJ%+pPV-kv#XX`yi^a7F(+ zzt@s=RzDwrQn}xXF5f^Q7rzP9?_-%-O{K>*9QeZe3L2lv%A7vca7us4g`^KyIrjt4 zm`$cuUFYhuxlq1+P_f%LQPzL;x)R=}!$Hk=SARh+T)YW?l4m88dPM4oFK_-?L}|e4 zXD`%UjN0Py8l!K07O#Jr*H5#gGXo(-BMGQ~s(tABB1q+ER{Q3FkRkyLj(|r}R{8#c zkj_!#0|h{u?~NqYc_?dqXle*4cwy9B-B&g0?JEUWm3-sieg>uE@f` zg;Dsubi#K#t5wh42KZ}tI4BoREH80jx|>zfmfhNSNVR5BqU1u`jFytpPPFgaDVqdiyOrDbd|M_FdtxedhMM3P%iwv}|L~?07K5q@`Z1 z{RWqN%~hF}LZJUgVCEkcfM#(IV2XbOGm#iFc#;gFq*34_sUal*lC-3-Rh|WaR6+Eqi{VsZ=XKLZn z(oip9FgVoXS3<+t!CcGI)lfo1K}q6(rH8BW>~GG}JJKsMHB7B7B{WQ(iB)SP0-{9I z(#65e+1%0v3<|+D_gGrkn94bL7!s`yM?ito>ykJ;4h_)VIE)|~jTJ{@uvh|;fIy+} zSYu!Y&Ti(e(6BCoz<}f(>|MbWU_>H=6r9W|-^)9=0WuK444hp6IcQSJR6^bKpAWNH zf&NZ>2msLJ&{riT4%oQbSxPG@Dk(`yVo;KjSb6Y|0RExDKY)HkN=izi5IabvdRIfD zG&Cjj^bNpIG8cjXKt3Z6G^va`i`svrE{Q7pnNmj9<=5R}6ZCnB@|r(vL?GX*Uu%0Xs5Y@jiSg3Yl%G6mjRN)#XEa5G+e z2H;OaLJ**yf+h^*AhYV?|07LkK~nvkaySHB>1hxJaL#}i!0W74BbN4OEk5a%Kg*k* zsK85!NxA?VaF%MTJIr9eJ**XTeq@axI z?bwuyc|e6|jtG*H&lbL{prIfzxqZM{9(QnamC&#=HMcwpJg$VQy`7D{CD3E&?*~lH z>?|d;%pDvpfzPyaJ8Dn-6NqqXhG0pd7Yf5DnunG`A#655U^tlhknZ{O+Wn`T5rZhT zo|BrH|Cu7PJE!&wK>U$P)l|gMe_&^>QO2C7qRlr{?K z4V8(431D7`)DoW#FA79nNWJta0@I&(`EBVWW{`r=Asra$^kW5ybn;1_e-vX@44i%R zjfR^$@K+?G1);1MMlyZtf=DJ=_bmT2n`HX_>+eX$2to&%U?kHw8;E35Zutk2=?CDy zBN-eBBSlC`l4rlNf!4zQja@-08KhR=BpS)QNT?lHbSGgS{%jAB*r-e!*cG0Vm)W=q zTD*{yl)S0>N6RJVXID5u2%$dNbScn^|67s?g3#U+ zMlyZ7fJi3!LfT47wIBaKBpJ9aXgdodnZ8{>B*PQa*;1zOO8!d9K)s>uA|=VBHT$zp z|4qDt%1VNeS_wk61+a^y&U4W8_fwGAXzW^nsZwMEq@67tUPyKYZQ5t4%>3*MczzO@ zFqZ%-F>~&5(eO|^d&P-=`>#kwgX6T+(#Fgq#A3*m0a~tr^tb2LoP$>2~kEuE$xV*kdjpaPB{q*j8^QVBxrQ`!S5&7{LNajOLkfmTWAPq7Bd zMz^bs|BbESm>=;mOL!(KSN!+zT(G^M)1)&fj2pJ*q6NLeT z!`}3)0a|BxUIOL-5sz{l`hJ++#vV8=5*08F#LmOcF>pHvl!3rTN!c9sPTD?WsB(z^ zi|sS;!z3=?oXx`>VBzk7$QjJ}lJ23SZ2u)^NI@(q5ulVcb{<{>3-=mC&R``E(mns5 zI717PxPY(OJnRHZ!%l$bpwLFrJ^!CL0}g{mnvb1FDuAVt3IJzF$r=3u15_CJkL(OD zNa6yvX1IBH5G)N30{lBAXaD~^2*@T#T);RZiII*Lk+x>w)se9JHT@6+awK4ikV^$! zK1gW{WUL_2K%Yn>3PLiYlo4(oTL&C`>p+|i=F>>`(BX`fP~xbZZICj;pX zg;C+?8x=%4Ra7SFC*?Tw-50ch`?m^%2TwLhnolt$o~V9*mKKS0JZT$1DRp2|l;hC% zPk%)^$U8`6!W0Jd`QH+B?|dHQm;@ms z7laH(5a2$Dk20Z!6vWfWcEI_sxrIq1Cjj1*Qke7|5F|`Il?R%a&jYy(iA=a2(6<^$ zg{k6)njuI8C`qU9fc}njtRNm*1BxjL^sNRWokmm#QHx?6@D^NptM_-L1I!S~0Z>j! zpl>xK(g{?tP0bLfJ_YHdd+3+~DMR&aN1*aR^YVGXffHziZP0j=_eU9O#X1MG8c%46jA@J{)5yCL5A{BN^K4R8bm*YfP>Kw zx8FIyet#){V83Jx5+(g};7|W%PmCbLQxRl1D1r>WM3AA52=Y@V1TBCdVm>%J1caxe z6f80Iq2!Tt*PneXS+WqAZ}wR8@{B~c4-pG@jU=SmpQk0O2Fa*r&kCNtfjkYy0evs^ zw;TZHLK7A@2bPS?0gaGPOn_h<(Dze+%K>mNBx2!fFbA{@;$Wun^zR4fpsGyFfhakc z1EBY}8~_JH1~b8?r5_f_9MH(wiD?jQsQD!Ah<|Mdz{QYpSg>j72SzdnG}3mW1Az@i zKil}rH6Y@{X+g*!k^I?`|H?^{A)yE|CKN$_<_=ywK+3idWb`6}79@~VtE5BMUt$Fi z1iUyJlr2#RoHzM(I}KS5`iZz4e>qSXe0>j{Xozsvq6SR z6O%~TlG0B*$l5{6OH$$QNf5+8i-R75071q$z-XJ#mWO;0Zx-eccn^?k!iJ*n|LD_3 zV?TqK4>%A(g$^WoHu&iP=&pY$H(DS-1PQbD=K-NLg+OJn$Op&wBW9gRqjfrp3>N== zOzrnUoBs>&1u^4>c!7tNIY4V<25BlS+$ob#HGh`Xf6O58*<@rArMi-EN`Iz`%pk4E z4@dhW<^T%*w+w=u9YMxiQ8G9OAP!;>LgK+x6b`sY%mF9-j~N8H8yQUoV{krs1BBay zGYHS!<^Ui5#|(nhjwVhHJUpjY|0S;=t+WEpUL1;@PZ{M{h$StM9cvmA&Z_y(Sewx!$eO% z{F5^}2;DayCMn<=#AFI07B1WQKm!r9uz|!R9mWCuB;hYP0BInC76K4K0?*Cmp=n_N ziAg#o2XmnSp~}g>T?3H7Lc~(70e!23w1^hgkC>#xIGDp!{*nXWf@mT75F{|%oNGX% zct}h)VI0u+W`C0D=7{&IJQLLkLKNfn6}CxA!fI6Oj}K-{Ad#1Uu| zNwZS4Q!DRWZ)5uKKiCXmK`nk1jigB zNcg)y(?@S8*f~1H9AHWMtO7qq23J8OQK<`k9VF99Yu^F~BO^%Ytv|C&mrh!kKg4{H za|j3}22&R}oEW<6&$N*0t+0n9nIOZBL z7YYWleF#T5Ta)Iu`d~{69vP781{A9g+GEkp(a9W;G=J8tv#@^1c>(Vr$R6zx5Trl; z*@kA+hN@;ePu4-38#4X}qLpe9>E~j97dmDhw$Q>XB1tf$KQm4eK9YnCB&zW50y=QK zU^@X@$N6ltktA#&#aQ!{A!txX#!SJcr7t@&2k<44R(2bIfwVU(b9P-9VBE*Sn3HC~$8UPM{p^)MzV3tTEN*qJL{$>DvpaD4t zhVZWm!x9MMpl*Off+__#8HL1)15grZZYYkG#QdfTlq4v{^@pcz2b?V}H60vWfxVGh zO%gwY!N{9Ca3q)%i6H%fRs@PozaL4H{!T^(lEHOkj2gKfKU4l8_qIWhn`4k4tAc6@ zxkyf~k08j8z!A`%ABZ2C9NJ2P>I?ZK5Ryy*ByL!wfEXYv_g=T7E{4pKpbr~0Gf=_^ zu>_gt3}a+9BojyU*_zBVY?3J=Yb-RzOphw)do!)7NZ$kUBuNSC2gXbWDUiWJWW)^_ zsX#`rP`2VBON`vzhWt31EH>x?ups&QBe_ur`C%RTkrw&E4{TLP#52zvGlc*KfX=bvX_ zr2B!Xkl7&X;y=&C$kYFRHbxdN`5D>FQ#|5^l?aq3KY3#Qr+Q2l$L}igjQb#uLsr*+ zs>)>Xk~NlG{|5BUe2;AQx_x+L>K4;`i=_yd2~sR5SZ1Xx9P0KLeB}tL;BA9-?z5*2CN+^FMCVcy>NT zg`6jL9d==splUSCEb+V3Fj$v)v>FECM?%fU!o?8kwM+a+ZX!H;ev)>~PD12OT}|yA ztU)vabjmHQkm?PbkfZ5-^Oe!+sIhQao3c6NfK z|4D*LXYK}0m}Dr#PT<5A$TNYD7&(cW#ta&C+FNh7bP-e3+5)&Wb+%Qtw{igG8se{( z7J}egmVorx)XxSj$hx^6c5t5QZw&S^q~6BFKF7eb;E11zeU3rzWALjaS^@eUs|fx^ zybkVB0{@6ViPwlfN%w(n&Pv2Fi1&jY$v9ci96;XL($w|$RA^!gW$L6LwQ6n17>AZi6uSIf-` zQXnxyOQJx>X3$w0f!rpE*d!^rY0E6>csN^HfvAhbAeaGZ>7&r91M01LyR);Ys8!e zb`3|wH^Q!AiGBxm4Nt5R!mm+{M_dD7QLy_6RPVCkr? zNup?6LlPr2<##1P197TrXd1KtXd~5qIHHH49FM5m@N1F;Vgf{YA5c=N@j!H<`Yr${ zP<Qf-SQ>MHEiu&`1mfPDl3z5QnEq2U7mD~Tpib}_^07wLvJ@BwZunz3I z)Ov*l+dH^jV!`{$spbXmeSzy07K92aK0p|x;sdxfD%m43#QFp!eHaXte+C|mDyCqO zc7H7zItjca&nS}^?kIE($VWs=Xw55f$}%;0eUq8|8Qe8Zm`H!?p;ddLHb% zG-L{5EnJ7dAv~(RBv>`7_yG#PRC)!j5zD6(^MV!?RK5umgQ?;SC=65OT%amQEGJWr zhsRUpG@x2YB|nhmP{nTW^o&ZckRUk1*AiSK*5@haMPgA@c`Op_7pU)}-VcHhK(${0 z*NC-TiaAhVYYNvj6nJ|XT&5^Ipa%9`@YX>p8wA&=@^&B<;xhz_dC{Qc0rwkd6tHyo zeRxSK-9Q7IhSLlVc2da?jmJ>!Nx+Vi%BIo4q~UrG!VLAi1ZsT&j{vEBI}YT3aDKry zg{n3J+}dn=b9FYgv9ok0zNt;i<^*{5he1dX9|(fFD3BDfE=nweg3Ewb(kBJt3yp|A zMIMQi$72-b!T;nDcsT;_DQHPKoSYmI4a$^r-$E=2D>#_TAGS2Nb#Xf?D1pWh Date: Tue, 5 Mar 2024 11:05:11 +0100 Subject: [PATCH 09/11] Replace constant strings in BoostAODE --- src/ensembles/BoostAODE.cc | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ensembles/BoostAODE.cc b/src/ensembles/BoostAODE.cc index 10eb694..7bc32b8 100644 --- a/src/ensembles/BoostAODE.cc +++ b/src/ensembles/BoostAODE.cc @@ -8,6 +8,16 @@ #include "folding.hpp" namespace bayesnet { + struct { + std::string CFS = "CFS"; + std::string FCBF = "FCBF"; + std::string IWSS = "IWSS"; + }SelectFeatures; + struct { + std::string ASC = "asc"; + std::string DESC = "desc"; + std::string RAND = "rand"; + }Orders; BoostAODE::BoostAODE(bool predict_voting) : Ensemble(predict_voting) { validHyperparameters = { @@ -61,10 +71,10 @@ namespace bayesnet { hyperparameters.erase("maxModels"); } if (hyperparameters.contains("order")) { - std::vector algos = { "asc", "desc", "rand" }; + std::vector algos = { Orders.ASC, Orders.DESC, Orders.RAND }; order_algorithm = hyperparameters["order"]; if (std::find(algos.begin(), algos.end(), order_algorithm) == algos.end()) { - throw std::invalid_argument("Invalid order algorithm, valid values [asc, desc, rand]"); + throw std::invalid_argument("Invalid order algorithm, valid values [" + Orders.ASC + ", " + Orders.DESC + ", " + Orders.RAND + "]"); } hyperparameters.erase("order"); } @@ -90,11 +100,11 @@ namespace bayesnet { } if (hyperparameters.contains("select_features")) { auto selectedAlgorithm = hyperparameters["select_features"]; - std::vector algos = { "IWSS", "FCBF", "CFS" }; + std::vector algos = { SelectFeatures.IWSS, SelectFeatures.CFS, SelectFeatures.CFS }; selectFeatures = true; select_features_algorithm = selectedAlgorithm; if (std::find(algos.begin(), algos.end(), selectedAlgorithm) == algos.end()) { - throw std::invalid_argument("Invalid selectFeatures value, valid values [IWSS, FCBF, CFS]"); + throw std::invalid_argument("Invalid selectFeatures value, valid values [" + SelectFeatures.IWSS + ", " + SelectFeatures.CFS + ", " + SelectFeatures.FCBF + "]"); } hyperparameters.erase("select_features"); } @@ -107,16 +117,16 @@ namespace bayesnet { std::unordered_set featuresUsed; torch::Tensor weights_ = torch::full({ m }, 1.0 / m, torch::kFloat64); int maxFeatures = 0; - if (select_features_algorithm == "CFS") { + if (select_features_algorithm == SelectFeatures.CFS) { featureSelector = new CFS(dataset, features, className, maxFeatures, states.at(className).size(), weights_); - } else if (select_features_algorithm == "IWSS") { + } else if (select_features_algorithm == SelectFeatures.IWSS) { if (threshold < 0 || threshold >0.5) { - throw std::invalid_argument("Invalid threshold value for IWSS [0, 0.5]"); + throw std::invalid_argument("Invalid threshold value for " + SelectFeatures.IWSS + " [0, 0.5]"); } featureSelector = new IWSS(dataset, features, className, maxFeatures, states.at(className).size(), weights_, threshold); - } else if (select_features_algorithm == "FCBF") { + } else if (select_features_algorithm == SelectFeatures.FCBF) { if (threshold < 1e-7 || threshold > 1) { - throw std::invalid_argument("Invalid threshold value [1e-7, 1]"); + throw std::invalid_argument("Invalid threshold value for " + SelectFeatures.FCBF + " [1e-7, 1]"); } featureSelector = new FCBF(dataset, features, className, maxFeatures, states.at(className).size(), weights_, threshold); } @@ -174,12 +184,12 @@ namespace bayesnet { // n_models == maxModels // epsilon sub t > 0.5 => inverse the weights policy // validation error is not decreasing - bool ascending = order_algorithm == "asc"; + bool ascending = order_algorithm == Orders.ASC; std::mt19937 g{ 173 }; while (!exitCondition) { // Step 1: Build ranking with mutual information auto featureSelection = metrics.SelectKBestWeighted(weights_, ascending, n); // Get all the features sorted - if (order_algorithm == "rand") { + if (order_algorithm == Orders.RAND) { std::shuffle(featureSelection.begin(), featureSelection.end(), g); } auto feature = featureSelection[0]; -- 2.45.2 From 0ee3eaed5380128e614b0734c0d410c3de023f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Montan=CC=83ana?= Date: Tue, 5 Mar 2024 12:10:58 +0100 Subject: [PATCH 10/11] Update select features models significance --- docs/BoostAODE.md | 2 +- src/ensembles/BoostAODE.cc | 62 ++++++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md index 23f5bd3..6a32ab6 100644 --- a/docs/BoostAODE.md +++ b/docs/BoostAODE.md @@ -16,7 +16,7 @@ The hyperparameters defined in the algorithm are: - ***tolerance*** (*int*): Sets the maximum number of models that can worsen the result without constituting a termination condition. Default value: *0*. -- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. These models also do not update or use the weights used by the Boosting algorithm, and their significance is set to 1. +- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. Once the models of the selected variables are built, the algorithm will update the weights using the ensemble and set the significance of all the models built with the same alpha_t. Default value: *""*. - ***threshold*** (*double*): Sets the necessary value for the IWSS and FCBF algorithms to function. Accepted values are: - IWSS: $threshold \in [0, 0.5]$ diff --git a/src/ensembles/BoostAODE.cc b/src/ensembles/BoostAODE.cc index 7bc32b8..bc75dfb 100644 --- a/src/ensembles/BoostAODE.cc +++ b/src/ensembles/BoostAODE.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include "BoostAODE.h" #include "CFS.h" #include "FCBF.h" @@ -112,6 +113,33 @@ namespace bayesnet { throw std::invalid_argument("Invalid hyperparameters" + hyperparameters.dump()); } } + std::tuple update_weights(torch::Tensor& ytrain, torch::Tensor& ypred, torch::Tensor& weights) + { + bool terminate = false; + double alpha_t = 0; + auto mask_wrong = ypred != ytrain; + auto mask_right = ypred == ytrain; + auto masked_weights = weights * mask_wrong.to(weights.dtype()); + double epsilon_t = masked_weights.sum().item(); + if (epsilon_t > 0.5) { + // Inverse the weights policy (plot ln(wt)) + // "In each round of AdaBoost, there is a sanity check to ensure that the current base + // learner is better than random guess" (Zhi-Hua Zhou, 2012) + terminate = true; + } else { + double wt = (1 - epsilon_t) / epsilon_t; + alpha_t = epsilon_t == 0 ? 1 : 0.5 * log(wt); + // Step 3.2: Update weights for next classifier + // Step 3.2.1: Update weights of wrong samples + weights += mask_wrong.to(weights.dtype()) * exp(alpha_t) * weights; + // Step 3.2.2: Update weights of right samples + weights += mask_right.to(weights.dtype()) * exp(-alpha_t) * weights; + // Step 3.3: Normalise the weights + double totalWeights = torch::sum(weights).item(); + weights = weights / totalWeights; + } + return { weights, alpha_t, terminate }; + } std::unordered_set BoostAODE::initializeModels() { std::unordered_set featuresUsed; @@ -161,19 +189,29 @@ namespace bayesnet { { initialize_prob_table = true; fitted = true; + double alpha_t = 0; // Algorithm based on the adaboost algorithm for classification // as explained in Ensemble methods (Zhi-Hua Zhou, 2012) + torch::Tensor weights_ = torch::full({ m }, 1.0 / m, torch::kFloat64); + bool exitCondition = false; std::unordered_set featuresUsed; if (selectFeatures) { featuresUsed = initializeModels(); + auto ypred = predict(X_train); + std::tie(weights_, alpha_t, exitCondition) = update_weights(y_train, ypred, weights_); + // Update significance of the models + for (int i = 0; i < n_models; ++i) { + significanceModels[i] = alpha_t; + } + if (exitCondition) { + return; + } } bool resetMaxModels = false; if (maxModels == 0) { maxModels = .1 * n > 10 ? .1 * n : n; resetMaxModels = true; // Flag to unset maxModels } - torch::Tensor weights_ = torch::full({ m }, 1.0 / m, torch::kFloat64); - bool exitCondition = false; // Variables to control the accuracy finish condition double priorAccuracy = 0.0; double delta = 1.0; @@ -218,26 +256,10 @@ namespace bayesnet { ypred = ensemble_predict(X_train, dynamic_cast(model.get())); } // Step 3.1: Compute the classifier amout of say - auto mask_wrong = ypred != y_train; - auto mask_right = ypred == y_train; - auto masked_weights = weights_ * mask_wrong.to(weights_.dtype()); - double epsilon_t = masked_weights.sum().item(); - if (epsilon_t > 0.5) { - // Inverse the weights policy (plot ln(wt)) - // "In each round of AdaBoost, there is a sanity check to ensure that the current base - // learner is better than random guess" (Zhi-Hua Zhou, 2012) + std::tie(weights_, alpha_t, exitCondition) = update_weights(y_train, ypred, weights_); + if (exitCondition) { break; } - double wt = (1 - epsilon_t) / epsilon_t; - double alpha_t = epsilon_t == 0 ? 1 : 0.5 * log(wt); - // Step 3.2: Update weights for next classifier - // Step 3.2.1: Update weights of wrong samples - weights_ += mask_wrong.to(weights_.dtype()) * exp(alpha_t) * weights_; - // Step 3.2.2: Update weights of right samples - weights_ += mask_right.to(weights_.dtype()) * exp(-alpha_t) * weights_; - // Step 3.3: Normalise the weights - double totalWeights = torch::sum(weights_).item(); - weights_ = weights_ / totalWeights; // Step 3.4: Store classifier and its accuracy to weigh its future vote featuresUsed.insert(feature); models.push_back(std::move(model)); -- 2.45.2 From d858e26e4bcc47e493dcb92e2cf955d95116cc85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana=20G=C3=B3mez?= Date: Wed, 6 Mar 2024 17:04:16 +0100 Subject: [PATCH 11/11] Update version number and Changelog --- CHANGELOG.md | 3 ++- CMakeLists.txt | 2 +- docs/BoostAODE.md | 2 +- src/ensembles/BoostAODE.cc | 8 ++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8c2cd..dc0a6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.0.4] ### Added @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Change the library structure adding folders for each group of classes (classifiers, ensembles, etc). +- The significances of the models generated under the feature selection algorithm are now computed after all the models have been generated and an αt value is computed and assigned to each model. ## [1.0.3] diff --git a/CMakeLists.txt b/CMakeLists.txt index 0babab8..327f09b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20) project(BayesNet - VERSION 1.0.3 + VERSION 1.0.4 DESCRIPTION "Bayesian Network and basic classifiers Library." HOMEPAGE_URL "https://github.com/rmontanana/bayesnet" LANGUAGES CXX diff --git a/docs/BoostAODE.md b/docs/BoostAODE.md index 6a32ab6..a04cc0e 100644 --- a/docs/BoostAODE.md +++ b/docs/BoostAODE.md @@ -16,7 +16,7 @@ The hyperparameters defined in the algorithm are: - ***tolerance*** (*int*): Sets the maximum number of models that can worsen the result without constituting a termination condition. Default value: *0*. -- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. Once the models of the selected variables are built, the algorithm will update the weights using the ensemble and set the significance of all the models built with the same alpha_t. Default value: *""*. +- ***select_features*** (*{"IWSS", "FCBF", "CFS", ""}*): Selects the variable selection method to be used to build initial models for the ensemble that will be included without considering any of the other exit conditions. Once the models of the selected variables are built, the algorithm will update the weights using the ensemble and set the significance of all the models built with the same αt. Default value: *""*. - ***threshold*** (*double*): Sets the necessary value for the IWSS and FCBF algorithms to function. Accepted values are: - IWSS: $threshold \in [0, 0.5]$ diff --git a/src/ensembles/BoostAODE.cc b/src/ensembles/BoostAODE.cc index bc75dfb..e8a5166 100644 --- a/src/ensembles/BoostAODE.cc +++ b/src/ensembles/BoostAODE.cc @@ -181,17 +181,17 @@ namespace bayesnet { prob_table += model->predict_proba(X) * 1.0; } // prob_table doesn't store probabilities but the sum of them - // to have them we need to divide by the sum of the significances but we - // don't need them to predict label values + // to have them we need to divide by the sum of the "weights" used to + // consider the results obtanined in the model's predict_proba. return prob_table.argmax(1); } void BoostAODE::trainModel(const torch::Tensor& weights) { + // Algorithm based on the adaboost algorithm for classification + // as explained in Ensemble methods (Zhi-Hua Zhou, 2012) initialize_prob_table = true; fitted = true; double alpha_t = 0; - // Algorithm based on the adaboost algorithm for classification - // as explained in Ensemble methods (Zhi-Hua Zhou, 2012) torch::Tensor weights_ = torch::full({ m }, 1.0 / m, torch::kFloat64); bool exitCondition = false; std::unordered_set featuresUsed; -- 2.45.2