From c1054ce9cba68389141a72e595359aaf3f6d26f6 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Mon, 9 May 2022 14:52:28 -0400 Subject: [PATCH 01/16] Implement prototype local join and train loop This consumes the binary parser to do most of the work and is implemented as part of rlsim. --- examples/CMakeLists.txt | 1 + examples/rl_sim_cpp/CMakeLists.txt | 7 +- examples/rl_sim_cpp/local_loop.cc | 143 ++++++++++++++++++++++ examples/rl_sim_cpp/local_loop.h | 60 +++++++++ examples/rl_sim_cpp/main.cc | 2 + examples/rl_sim_cpp/rl_sim.cc | 64 +++++++++- examples/rl_sim_cpp/rl_sim.h | 2 + external_parser/CMakeLists.txt | 33 ++--- external_parser/joiners/example_joiner.cc | 4 + external_parser/joiners/example_joiner.h | 1 + external_parser/joiners/i_joiner.h | 2 + 11 files changed, 301 insertions(+), 18 deletions(-) create mode 100644 examples/rl_sim_cpp/local_loop.cc create mode 100644 examples/rl_sim_cpp/local_loop.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 90e35abeb..7ff3f7bc7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(basic_usage_cpp) +add_subdirectory(local_loop) add_subdirectory(override_interface) add_subdirectory(rl_sim_cpp) add_subdirectory(test_cpp) diff --git a/examples/rl_sim_cpp/CMakeLists.txt b/examples/rl_sim_cpp/CMakeLists.txt index 5bf816e2b..fd4f0dcad 100644 --- a/examples/rl_sim_cpp/CMakeLists.txt +++ b/examples/rl_sim_cpp/CMakeLists.txt @@ -3,6 +3,11 @@ add_executable(rl_sim_cpp.out person.cc robot_joint.cc rl_sim.cc + local_loop.cc +local_loop.h ) -target_link_libraries(rl_sim_cpp.out PRIVATE Boost::program_options rlclientlib) +set(RL_EXTERNAL_PARSER_OTHER_DEPS OFF CACHE BOOL "") +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../external_parser ${CMAKE_CURRENT_BINARY_DIR}/external_parser) + +target_link_libraries(rl_sim_cpp.out PRIVATE Boost::program_options rlclientlib vw_core rl_binary_parser) diff --git a/examples/rl_sim_cpp/local_loop.cc b/examples/rl_sim_cpp/local_loop.cc new file mode 100644 index 000000000..c39d06a32 --- /dev/null +++ b/examples/rl_sim_cpp/local_loop.cc @@ -0,0 +1,143 @@ +#include "local_loop.h" + +#include "err_constants.h" +#include "constants.h" +#include "api_status.h" +#include "vw/core/parse_primitives.h" +#include "vw/config/options_cli.h" +#include "../../rlclientlib/logger/preamble.h" +#include "../../rlclientlib/logger/message_type.h" +#include "trace_logger.h" + +namespace rl = reinforcement_learning; + + +void local_model::set_trace_logger(rl::i_trace *trace_logger) { + _trace_logger = trace_logger; + } + + // Get data causes a "join" to take place. + int local_model::get_data(reinforcement_learning::model_management::model_data &data, + reinforcement_learning::api_status *status) { + std::lock_guard lock(_mutex); + data.increment_refresh_count(); + + // If this is called before init is done then exit early. + if (!_joiner) + { + return rl::error_code::success; + } + + std::stringstream ss; + ss << "Joining " << _joiner->events_in_queue() << " events." <log(rl::LEVEL_INFO, ss.str()); + + VW::multi_ex ex; + while(_joiner->processing_batch()) + { + ex.push_back(VW::new_unused_example(*_training_workspace)); + + // False means there was a problem and we should try reading the next one. + if(!_joiner->process_joined(ex)){ + assert(ex.size() == 1); + auto* ex_to_return = ex.back(); + ex.pop_back(); + VW::finish_example(*_training_workspace, *ex_to_return); + continue; + } + VW::setup_examples(*_training_workspace, ex); + + // We must remove the trailing newline example. + auto* newline_ex = ex.back(); + ex.pop_back(); + VW::finish_example(*_training_workspace, *newline_ex); + + // Learn and finish the given examples + _training_workspace->learn(ex); + _training_workspace->finish_example(ex); + ex.clear(); + } + + // Clear all joined buffers. + _detached_buffers.clear(); + + // Save current model state to a buffer and return to client. + io_buf buffer; + auto backing_buffer = std::make_shared>(); + buffer.add_file(VW::io::create_vector_writer(backing_buffer)); + VW::save_predictor(*_training_workspace, buffer); + + auto* buffer_to_copy_to = data.alloc(backing_buffer->size()); + std::memcpy(buffer_to_copy_to, backing_buffer->data(), backing_buffer->size()); + + return rl::error_code::success; + } + + int local_model::init(const reinforcement_learning::utility::configuration &config, + reinforcement_learning::api_status *status) { + + if (config.get_int(rl::name::PROTOCOL_VERSION, 999) != 2) + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; + } + + if (!_vw_model) { + _vw_model = std::unique_ptr( + new rl::model_management::vw_model(_trace_logger, config)); + } + std::string cmd_str = config.get( + rl::name::MODEL_VW_INITIAL_COMMAND_LINE, + "--cb_explore_adf --driver_output_off --epsilon 0.2 --power_t 0 -l 0.001 --cb_type mtr -q ::"); + auto cmd_list = VW::split_command_line(cmd_str); + auto options = VW::make_unique(cmd_list); + _training_workspace = VW::initialize_experimental(std::move(options)); + _joiner = VW::make_unique(_training_workspace.get()); + _joiner->set_problem_type_config(rl::messages::flatbuff::v2::ProblemType_CB); + _joiner->set_learning_mode_config(rl::messages::flatbuff::v2::LearningModeType_Online); + _joiner->set_reward_function(rl::messages::flatbuff::v2::RewardFunctionType_Earliest); + _joiner->set_default_reward(0.f); + return rl::error_code::success; + } + + int local_model::v_send(const buffer &data, + reinforcement_learning::api_status *status) { + std::lock_guard lock(_mutex); + rl::logger::preamble pre; + pre.read_from_bytes(data->preamble_begin(), pre.size()); + + if (pre.msg_type == rl::logger::message_type::fb_generic_event_collection) { + auto res = reinforcement_learning::messages::flatbuff::v2::GetEventBatch( + data->body_begin()); + flatbuffers::Verifier verifier(data->body_begin(), + data->body_filled_size()); + auto result = res->Verify(verifier); + + if (!result) + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; + } + + if (res->metadata()->content_encoding()->str() != "IDENTITY") { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; + } + + for (auto message : *res->events()) { + flatbuffers::FlatBufferBuilder fbb; + // TODO: real timestamp + rl::messages::flatbuff::v2::TimeStamp ts(2020, 3, 1, 10, 20, 30, 0); + const auto *event_payload = message->payload(); + auto vec = fbb.CreateVector(event_payload->data(), event_payload->size()); + auto fb = rl::messages::flatbuff::v2::CreateJoinedEvent(fbb, vec, &ts); + fbb.Finish(fb); + _detached_buffers.push_back(fbb.Release()); + + const auto *je = flatbuffers::GetRoot( + _detached_buffers.back().data()); + _joiner->process_event(*je); + } + return rl::error_code::success; + } + + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) + << " Message type " << pre.msg_type << " cannot be handled."; + } diff --git a/examples/rl_sim_cpp/local_loop.h b/examples/rl_sim_cpp/local_loop.h new file mode 100644 index 000000000..5e2cc3392 --- /dev/null +++ b/examples/rl_sim_cpp/local_loop.h @@ -0,0 +1,60 @@ +#pragma once + +#include "model_mgmt.h" +#include "sender.h" +#include "../../rlclientlib/vw_model/vw_model.h" +#include "vw/core/global_data.h" +#include "joiners/example_joiner.h" + +struct local_model : public reinforcement_learning::model_management::i_data_transport, + reinforcement_learning::i_sender { + reinforcement_learning::i_trace *_trace_logger = nullptr; + std::unique_ptr _vw_model = nullptr; + std::unique_ptr _training_workspace = nullptr; + std::unique_ptr _joiner = nullptr; + std::mutex _mutex; + + // This needs to be cleared after a full join. + std::vector _detached_buffers; + + local_model() = default; + ~local_model() override = default; + + void set_trace_logger(reinforcement_learning::i_trace *trace_logger); + + // Get data causes a "join" to take place. + int get_data(reinforcement_learning::model_management::model_data &data, + reinforcement_learning::api_status *status) override; + + int init(const reinforcement_learning::utility::configuration &config, + reinforcement_learning::api_status *status) override; + + int v_send(const buffer &data, + reinforcement_learning::api_status *status) override; +}; + +struct local_model_proxy : public reinforcement_learning::model_management::i_data_transport, + reinforcement_learning::i_sender { + explicit local_model_proxy(local_model *local_model) + : _local_model(local_model) {} + ~local_model_proxy() override = default; + + int get_data(reinforcement_learning::model_management::model_data &data, + reinforcement_learning::api_status *status) override { + return _local_model->get_data(data, status); + } + + int init(const reinforcement_learning::utility::configuration &config, + reinforcement_learning::api_status *status) override { + return _local_model->init(config, status); + } + +protected: + int v_send(const buffer &data, + reinforcement_learning::api_status *status) override { + return _local_model->v_send(data, status); + } + +private: + local_model *_local_model; +}; \ No newline at end of file diff --git a/examples/rl_sim_cpp/main.cc b/examples/rl_sim_cpp/main.cc index 6fe5eb88b..01ec85526 100644 --- a/examples/rl_sim_cpp/main.cc +++ b/examples/rl_sim_cpp/main.cc @@ -40,6 +40,8 @@ po::variables_map process_cmd_line(const int argc, char** argv) { default_value(false), "Run in continuous actions mode") ("multistep", po::value()-> default_value(false), "Run in multistep mode") + ("local_loop", po::value()-> + default_value(false), "Train and update model locally") ; po::variables_map vm; diff --git a/examples/rl_sim_cpp/rl_sim.cc b/examples/rl_sim_cpp/rl_sim.cc index 73c7a7f04..c7ce8ed61 100644 --- a/examples/rl_sim_cpp/rl_sim.cc +++ b/examples/rl_sim_cpp/rl_sim.cc @@ -8,6 +8,7 @@ #include "simulation_stats.h" #include "constants.h" #include "multistep.h" +#include "local_loop.h" using namespace std; @@ -317,6 +318,8 @@ void _on_error(const reinforcement_learning::api_status& status, rl_sim* psim) { int rl_sim::init_rl() { r::api_status status; u::configuration config; + + bool local_loop = false; // Load configuration from json config file const auto config_file = _options["json_config"].as(); if ( load_config_from_json(config_file, config, &status) != err::success ) { @@ -324,13 +327,47 @@ int rl_sim::init_rl() { return -1; } + if(_options["local_loop"].as()) { + local_loop = true; + std::cout << "Using --local_loop, replacing some config vars." << std::endl; + if (_loop_kind != CB) + { + std::cerr << "--local_loop can only be used with cb." << std::endl; + return -1; + } + + config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, "LOCAL_MODEL"); + std::cout << "Setting " << r::name::INTERACTION_SENDER_IMPLEMENTATION << "=LOCAL_MODEL" <()) { + if (local_loop) + { + std::cerr << "--local_loop and --log_to_file can't be used together." << std::endl; + return -1; + } config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); } if (!_options["get_model"].as()) { - // Set the time provider to the clock time provider + if (local_loop) + { + std::cerr << "--local_loop and --get_model can't be used together." << std::endl; + return -1; + } config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); } @@ -342,6 +379,29 @@ int rl_sim::init_rl() { // Trace log API calls to the console config.set(r::name::TRACE_LOG_IMPLEMENTATION, r::value::CONSOLE_TRACE_LOGGER); + if(local_loop) + { + _local_model = std::unique_ptr(new local_model); + auto* raw_local_model = _local_model.get(); + r::data_transport_factory.register_type( + "LOCAL_MODEL", [raw_local_model](r::model_management::i_data_transport **retval, + const r::utility::configuration &config, + r::i_trace *trace_logger, r::api_status *status) { + raw_local_model->set_trace_logger(trace_logger); + *retval = new local_model_proxy(raw_local_model); + return 0; + }); + r::sender_factory.register_type( + "LOCAL_MODEL", + [raw_local_model](r::i_sender **retval, const r::utility::configuration &config, + r::error_callback_fn *error_cb, r::i_trace *trace_logger, + r::api_status *status) { + raw_local_model->set_trace_logger(trace_logger); + *retval = new local_model_proxy(raw_local_model); + return 0; + }); + } + // Initialize the API _rl = std::unique_ptr(new r::live_model(config, _on_error, this)); if ( _rl->init(&status) != err::success ) { @@ -356,7 +416,7 @@ int rl_sim::init_rl() { bool rl_sim::init_sim_world() { - // Initilize topics + // Initialize topics _topics = { "SkiConditions-VT", "HerbGarden", diff --git a/examples/rl_sim_cpp/rl_sim.h b/examples/rl_sim_cpp/rl_sim.h index 201e85b05..aed0e3d3a 100644 --- a/examples/rl_sim_cpp/rl_sim.h +++ b/examples/rl_sim_cpp/rl_sim.h @@ -10,6 +10,7 @@ #include "person.h" #include "live_model.h" #include "robot_joint.h" +#include "local_loop.h" /** * @brief Reinforcement Learning Simulator @@ -160,6 +161,7 @@ class rl_sim { boost::program_options::variables_map _options; std::unique_ptr _rl; + std::unique_ptr _local_model; std::vector _people; std::vector _topics; std::vector _slot_sizes; diff --git a/external_parser/CMakeLists.txt b/external_parser/CMakeLists.txt index a8d21a53b..c5d1d1628 100644 --- a/external_parser/CMakeLists.txt +++ b/external_parser/CMakeLists.txt @@ -43,22 +43,25 @@ else() set(flatc_location ${FLATBUFFERS_FLATC_EXECUTABLE}) endif() -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ext_libs/zstd/build/cmake ${CMAKE_CURRENT_BINARY_DIR}/vw_binary_parser/zstd EXCLUDE_FROM_ALL) - -# set vw cmake flags -# This is not longer using the injected method, but implemented in this repo just consuming vw as a lib -set(BUILD_EXTERNAL_PARSER OFF CACHE BOOL "") -set(BUILD_FLATBUFFERS OFF CACHE BOOL "") -set(DO_NOT_BUILD_VW_C_WRAPPER OFF CACHE BOOL "") -set(BUILD_JAVA OFF CACHE BOOL "") -set(BUILD_PYTHON OFF CACHE BOOL "") -set(BUILD_EXPERIMENTAL_BINDING OFF CACHE BOOL "") -set(BUILD_TESTING OFF CACHE BOOL "") - -if(STATIC_LINK_BINARY_PARSER) - set(STATIC_LINK_VW ON CACHE BOOL "" FORCE) -endif() +option(RL_EXTERNAL_PARSER_OTHER_DEPS "Add the other deps." ON) +if (RL_EXTERNAL_PARSER_OTHER_DEPS) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ext_libs/zstd/build/cmake ${CMAKE_CURRENT_BINARY_DIR}/vw_binary_parser/zstd EXCLUDE_FROM_ALL) + + # set vw cmake flags + # This is not longer using the injected method, but implemented in this repo just consuming vw as a lib + set(BUILD_EXTERNAL_PARSER OFF CACHE BOOL "") + set(BUILD_FLATBUFFERS OFF CACHE BOOL "") + set(DO_NOT_BUILD_VW_C_WRAPPER OFF CACHE BOOL "") + set(BUILD_JAVA OFF CACHE BOOL "") + set(BUILD_PYTHON OFF CACHE BOOL "") + set(BUILD_EXPERIMENTAL_BINDING OFF CACHE BOOL "") + set(BUILD_TESTING OFF CACHE BOOL "") + + if(STATIC_LINK_BINARY_PARSER) + set(STATIC_LINK_VW ON CACHE BOOL "" FORCE) + endif() add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ext_libs/vowpal_wabbit ${CMAKE_CURRENT_BINARY_DIR}/vw_binary_parser) +endif() # Flatbuffer generation # --------------------- diff --git a/external_parser/joiners/example_joiner.cc b/external_parser/joiners/example_joiner.cc index 895caa73f..3481602ab 100644 --- a/external_parser/joiners/example_joiner.cc +++ b/external_parser/joiners/example_joiner.cc @@ -576,3 +576,7 @@ metrics::joiner_metrics example_joiner::get_metrics() { } void example_joiner::apply_cli_overrides(VW::workspace *, const VW::external::parser_options &) {} + +int example_joiner::events_in_queue() { + return _batch_event_order.size(); +} diff --git a/external_parser/joiners/example_joiner.h b/external_parser/joiners/example_joiner.h index ed642363b..bd4edb2d9 100644 --- a/external_parser/joiners/example_joiner.h +++ b/external_parser/joiners/example_joiner.h @@ -75,6 +75,7 @@ class example_joiner : public i_joiner { // true if there are still event-groups to be processed from a deserialized // batch bool processing_batch() override; + int events_in_queue(); // to be called after process_joined // returns true if the event that was just processed is a skip_learn event diff --git a/external_parser/joiners/i_joiner.h b/external_parser/joiners/i_joiner.h index cc83e0421..e90d47724 100644 --- a/external_parser/joiners/i_joiner.h +++ b/external_parser/joiners/i_joiner.h @@ -49,10 +49,12 @@ class i_joiner { // Takes an event which will have a timestamp and event payload // groups all events interactions with their event observations based on their // id. The grouped events can be processed when process_joined() is called + // True means success, false means failure virtual bool process_event(const v2::JoinedEvent &joined_event) = 0; // Takes all grouped events, processes them (e.g. decompression) and populates // the examples array with complete example(s) ready to be used by vw for // training + // True means success, false means failure virtual bool process_joined(VW::multi_ex &examples) = 0; // true if there are still event-groups to be processed from a deserialized // batch From 349e5550815ef1de1350ae8f5bcf558046b81a4b Mon Sep 17 00:00:00 2001 From: Byron Xu Date: Fri, 14 Oct 2022 16:29:36 -0400 Subject: [PATCH 02/16] Fix build errors --- CMakeLists.txt | 10 +++++----- examples/CMakeLists.txt | 6 ++++-- examples/rl_sim_cpp/CMakeLists.txt | 5 +---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be27ec0fa..dfc687413 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS On) project(reinforcement_learning) -#set(CMAKE_CXX_STANDARD 11) # Add support for building library with latest version of C++ supported by your compiler # Copied from VW set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake/") @@ -120,15 +119,16 @@ include(ext_libs/ext_libs.cmake) add_subdirectory(rlclientlib) add_subdirectory(rlclientlib/extensions) -add_subdirectory(examples) -add_subdirectory(test_tools/joiner) -add_subdirectory(test_tools/sender_test) -add_subdirectory(test_tools/example_gen) if(RL_BUILD_EXTERNAL_PARSER) add_subdirectory(external_parser) endif() +add_subdirectory(examples) +add_subdirectory(test_tools/joiner) +add_subdirectory(test_tools/sender_test) +add_subdirectory(test_tools/example_gen) + # enable_testing should be run after ext_libs so that the vw unit tests arent turned on. enable_testing() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7ff3f7bc7..66efacf8c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,9 +1,11 @@ add_subdirectory(basic_usage_cpp) -add_subdirectory(local_loop) add_subdirectory(override_interface) -add_subdirectory(rl_sim_cpp) add_subdirectory(test_cpp) +if(RL_BUILD_EXTERNAL_PARSER) + add_subdirectory(rl_sim_cpp) +endif() + if (rlclientlib_BUILD_ONNXRUNTIME_EXTENSION) add_subdirectory(onnx) endif() diff --git a/examples/rl_sim_cpp/CMakeLists.txt b/examples/rl_sim_cpp/CMakeLists.txt index fd4f0dcad..5414f7d4c 100644 --- a/examples/rl_sim_cpp/CMakeLists.txt +++ b/examples/rl_sim_cpp/CMakeLists.txt @@ -4,10 +4,7 @@ add_executable(rl_sim_cpp.out robot_joint.cc rl_sim.cc local_loop.cc -local_loop.h + local_loop.h ) -set(RL_EXTERNAL_PARSER_OTHER_DEPS OFF CACHE BOOL "") -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../external_parser ${CMAKE_CURRENT_BINARY_DIR}/external_parser) - target_link_libraries(rl_sim_cpp.out PRIVATE Boost::program_options rlclientlib vw_core rl_binary_parser) From 15f41433e7989f8ff5b49dcbf82d9ed3e97f5a9d Mon Sep 17 00:00:00 2001 From: Byron Xu Date: Fri, 14 Oct 2022 16:47:34 -0400 Subject: [PATCH 03/16] clang format --- examples/rl_sim_cpp/local_loop.cc | 223 ++++++++++------------ examples/rl_sim_cpp/local_loop.h | 45 ++--- examples/rl_sim_cpp/main.cc | 31 ++- examples/rl_sim_cpp/rl_sim.cc | 52 ++--- examples/rl_sim_cpp/rl_sim.h | 2 +- external_parser/joiners/example_joiner.cc | 4 +- 6 files changed, 171 insertions(+), 186 deletions(-) diff --git a/examples/rl_sim_cpp/local_loop.cc b/examples/rl_sim_cpp/local_loop.cc index c39d06a32..298467633 100644 --- a/examples/rl_sim_cpp/local_loop.cc +++ b/examples/rl_sim_cpp/local_loop.cc @@ -1,143 +1,132 @@ #include "local_loop.h" -#include "err_constants.h" -#include "constants.h" -#include "api_status.h" -#include "vw/core/parse_primitives.h" -#include "vw/config/options_cli.h" -#include "../../rlclientlib/logger/preamble.h" #include "../../rlclientlib/logger/message_type.h" +#include "../../rlclientlib/logger/preamble.h" +#include "api_status.h" +#include "constants.h" +#include "err_constants.h" #include "trace_logger.h" +#include "vw/config/options_cli.h" +#include "vw/core/parse_primitives.h" namespace rl = reinforcement_learning; +void local_model::set_trace_logger(rl::i_trace* trace_logger) { _trace_logger = trace_logger; } -void local_model::set_trace_logger(rl::i_trace *trace_logger) { - _trace_logger = trace_logger; - } +// Get data causes a "join" to take place. +int local_model::get_data( + reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) +{ + std::lock_guard lock(_mutex); + data.increment_refresh_count(); - // Get data causes a "join" to take place. - int local_model::get_data(reinforcement_learning::model_management::model_data &data, - reinforcement_learning::api_status *status) { - std::lock_guard lock(_mutex); - data.increment_refresh_count(); + // If this is called before init is done then exit early. + if (!_joiner) { return rl::error_code::success; } - // If this is called before init is done then exit early. - if (!_joiner) - { - return rl::error_code::success; - } + std::stringstream ss; + ss << "Joining " << _joiner->events_in_queue() << " events." << std::endl; + _trace_logger->log(rl::LEVEL_INFO, ss.str()); - std::stringstream ss; - ss << "Joining " << _joiner->events_in_queue() << " events." <log(rl::LEVEL_INFO, ss.str()); + VW::multi_ex ex; + while (_joiner->processing_batch()) + { + ex.push_back(VW::new_unused_example(*_training_workspace)); - VW::multi_ex ex; - while(_joiner->processing_batch()) + // False means there was a problem and we should try reading the next one. + if (!_joiner->process_joined(ex)) { - ex.push_back(VW::new_unused_example(*_training_workspace)); - - // False means there was a problem and we should try reading the next one. - if(!_joiner->process_joined(ex)){ - assert(ex.size() == 1); - auto* ex_to_return = ex.back(); - ex.pop_back(); - VW::finish_example(*_training_workspace, *ex_to_return); - continue; - } - VW::setup_examples(*_training_workspace, ex); - - // We must remove the trailing newline example. - auto* newline_ex = ex.back(); + assert(ex.size() == 1); + auto* ex_to_return = ex.back(); ex.pop_back(); - VW::finish_example(*_training_workspace, *newline_ex); - - // Learn and finish the given examples - _training_workspace->learn(ex); - _training_workspace->finish_example(ex); - ex.clear(); + VW::finish_example(*_training_workspace, *ex_to_return); + continue; } + VW::setup_examples(*_training_workspace, ex); - // Clear all joined buffers. - _detached_buffers.clear(); + // We must remove the trailing newline example. + auto* newline_ex = ex.back(); + ex.pop_back(); + VW::finish_example(*_training_workspace, *newline_ex); - // Save current model state to a buffer and return to client. - io_buf buffer; - auto backing_buffer = std::make_shared>(); - buffer.add_file(VW::io::create_vector_writer(backing_buffer)); - VW::save_predictor(*_training_workspace, buffer); + // Learn and finish the given examples + _training_workspace->learn(ex); + _training_workspace->finish_example(ex); + ex.clear(); + } - auto* buffer_to_copy_to = data.alloc(backing_buffer->size()); - std::memcpy(buffer_to_copy_to, backing_buffer->data(), backing_buffer->size()); + // Clear all joined buffers. + _detached_buffers.clear(); - return rl::error_code::success; - } + // Save current model state to a buffer and return to client. + io_buf buffer; + auto backing_buffer = std::make_shared>(); + buffer.add_file(VW::io::create_vector_writer(backing_buffer)); + VW::save_predictor(*_training_workspace, buffer); - int local_model::init(const reinforcement_learning::utility::configuration &config, - reinforcement_learning::api_status *status) { + auto* buffer_to_copy_to = data.alloc(backing_buffer->size()); + std::memcpy(buffer_to_copy_to, backing_buffer->data(), backing_buffer->size()); - if (config.get_int(rl::name::PROTOCOL_VERSION, 999) != 2) - { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; - } + return rl::error_code::success; +} - if (!_vw_model) { - _vw_model = std::unique_ptr( - new rl::model_management::vw_model(_trace_logger, config)); +int local_model::init( + const reinforcement_learning::utility::configuration& config, reinforcement_learning::api_status* status) +{ + if (config.get_int(rl::name::PROTOCOL_VERSION, 999) != 2) + { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; } + + if (!_vw_model) + { + _vw_model = + std::unique_ptr(new rl::model_management::vw_model(_trace_logger, config)); + } + std::string cmd_str = config.get(rl::name::MODEL_VW_INITIAL_COMMAND_LINE, + "--cb_explore_adf --driver_output_off --epsilon 0.2 --power_t 0 -l 0.001 --cb_type mtr -q ::"); + auto cmd_list = VW::split_command_line(cmd_str); + auto options = VW::make_unique(cmd_list); + _training_workspace = VW::initialize_experimental(std::move(options)); + _joiner = VW::make_unique(_training_workspace.get()); + _joiner->set_problem_type_config(rl::messages::flatbuff::v2::ProblemType_CB); + _joiner->set_learning_mode_config(rl::messages::flatbuff::v2::LearningModeType_Online); + _joiner->set_reward_function(rl::messages::flatbuff::v2::RewardFunctionType_Earliest); + _joiner->set_default_reward(0.f); + return rl::error_code::success; +} + +int local_model::v_send(const buffer& data, reinforcement_learning::api_status* status) +{ + std::lock_guard lock(_mutex); + rl::logger::preamble pre; + pre.read_from_bytes(data->preamble_begin(), pre.size()); + + if (pre.msg_type == rl::logger::message_type::fb_generic_event_collection) + { + auto res = reinforcement_learning::messages::flatbuff::v2::GetEventBatch(data->body_begin()); + flatbuffers::Verifier verifier(data->body_begin(), data->body_filled_size()); + auto result = res->Verify(verifier); + + if (!result) + { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; } + + if (res->metadata()->content_encoding()->str() != "IDENTITY") + { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; } + + for (auto message : *res->events()) + { + flatbuffers::FlatBufferBuilder fbb; + // TODO: real timestamp + rl::messages::flatbuff::v2::TimeStamp ts(2020, 3, 1, 10, 20, 30, 0); + const auto* event_payload = message->payload(); + auto vec = fbb.CreateVector(event_payload->data(), event_payload->size()); + auto fb = rl::messages::flatbuff::v2::CreateJoinedEvent(fbb, vec, &ts); + fbb.Finish(fb); + _detached_buffers.push_back(fbb.Release()); + + const auto* je = flatbuffers::GetRoot(_detached_buffers.back().data()); + _joiner->process_event(*je); } - std::string cmd_str = config.get( - rl::name::MODEL_VW_INITIAL_COMMAND_LINE, - "--cb_explore_adf --driver_output_off --epsilon 0.2 --power_t 0 -l 0.001 --cb_type mtr -q ::"); - auto cmd_list = VW::split_command_line(cmd_str); - auto options = VW::make_unique(cmd_list); - _training_workspace = VW::initialize_experimental(std::move(options)); - _joiner = VW::make_unique(_training_workspace.get()); - _joiner->set_problem_type_config(rl::messages::flatbuff::v2::ProblemType_CB); - _joiner->set_learning_mode_config(rl::messages::flatbuff::v2::LearningModeType_Online); - _joiner->set_reward_function(rl::messages::flatbuff::v2::RewardFunctionType_Earliest); - _joiner->set_default_reward(0.f); return rl::error_code::success; } - int local_model::v_send(const buffer &data, - reinforcement_learning::api_status *status) { - std::lock_guard lock(_mutex); - rl::logger::preamble pre; - pre.read_from_bytes(data->preamble_begin(), pre.size()); - - if (pre.msg_type == rl::logger::message_type::fb_generic_event_collection) { - auto res = reinforcement_learning::messages::flatbuff::v2::GetEventBatch( - data->body_begin()); - flatbuffers::Verifier verifier(data->body_begin(), - data->body_filled_size()); - auto result = res->Verify(verifier); - - if (!result) - { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; - } - - if (res->metadata()->content_encoding()->str() != "IDENTITY") { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; - } - - for (auto message : *res->events()) { - flatbuffers::FlatBufferBuilder fbb; - // TODO: real timestamp - rl::messages::flatbuff::v2::TimeStamp ts(2020, 3, 1, 10, 20, 30, 0); - const auto *event_payload = message->payload(); - auto vec = fbb.CreateVector(event_payload->data(), event_payload->size()); - auto fb = rl::messages::flatbuff::v2::CreateJoinedEvent(fbb, vec, &ts); - fbb.Finish(fb); - _detached_buffers.push_back(fbb.Release()); - - const auto *je = flatbuffers::GetRoot( - _detached_buffers.back().data()); - _joiner->process_event(*je); - } - return rl::error_code::success; - } - - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) - << " Message type " << pre.msg_type << " cannot be handled."; - } + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " Message type " << pre.msg_type << " cannot be handled."; +} diff --git a/examples/rl_sim_cpp/local_loop.h b/examples/rl_sim_cpp/local_loop.h index 5e2cc3392..0b7aa4e96 100644 --- a/examples/rl_sim_cpp/local_loop.h +++ b/examples/rl_sim_cpp/local_loop.h @@ -1,14 +1,14 @@ #pragma once +#include "../../rlclientlib/vw_model/vw_model.h" +#include "joiners/example_joiner.h" #include "model_mgmt.h" #include "sender.h" -#include "../../rlclientlib/vw_model/vw_model.h" #include "vw/core/global_data.h" -#include "joiners/example_joiner.h" -struct local_model : public reinforcement_learning::model_management::i_data_transport, - reinforcement_learning::i_sender { - reinforcement_learning::i_trace *_trace_logger = nullptr; +struct local_model : public reinforcement_learning::model_management::i_data_transport, reinforcement_learning::i_sender +{ + reinforcement_learning::i_trace* _trace_logger = nullptr; std::unique_ptr _vw_model = nullptr; std::unique_ptr _training_workspace = nullptr; std::unique_ptr _joiner = nullptr; @@ -20,41 +20,42 @@ struct local_model : public reinforcement_learning::model_management::i_data_tra local_model() = default; ~local_model() override = default; - void set_trace_logger(reinforcement_learning::i_trace *trace_logger); + void set_trace_logger(reinforcement_learning::i_trace* trace_logger); // Get data causes a "join" to take place. - int get_data(reinforcement_learning::model_management::model_data &data, - reinforcement_learning::api_status *status) override; + int get_data( + reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) override; - int init(const reinforcement_learning::utility::configuration &config, - reinforcement_learning::api_status *status) override; + int init(const reinforcement_learning::utility::configuration& config, + reinforcement_learning::api_status* status) override; - int v_send(const buffer &data, - reinforcement_learning::api_status *status) override; + int v_send(const buffer& data, reinforcement_learning::api_status* status) override; }; struct local_model_proxy : public reinforcement_learning::model_management::i_data_transport, - reinforcement_learning::i_sender { - explicit local_model_proxy(local_model *local_model) - : _local_model(local_model) {} + reinforcement_learning::i_sender +{ + explicit local_model_proxy(local_model* local_model) : _local_model(local_model) {} ~local_model_proxy() override = default; - int get_data(reinforcement_learning::model_management::model_data &data, - reinforcement_learning::api_status *status) override { + int get_data( + reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) override + { return _local_model->get_data(data, status); } - int init(const reinforcement_learning::utility::configuration &config, - reinforcement_learning::api_status *status) override { + int init( + const reinforcement_learning::utility::configuration& config, reinforcement_learning::api_status* status) override + { return _local_model->init(config, status); } protected: - int v_send(const buffer &data, - reinforcement_learning::api_status *status) override { + int v_send(const buffer& data, reinforcement_learning::api_status* status) override + { return _local_model->v_send(data, status); } private: - local_model *_local_model; + local_model* _local_model; }; \ No newline at end of file diff --git a/examples/rl_sim_cpp/main.cc b/examples/rl_sim_cpp/main.cc index 73fc8336b..729478f62 100644 --- a/examples/rl_sim_cpp/main.cc +++ b/examples/rl_sim_cpp/main.cc @@ -26,23 +26,20 @@ int main(int argc, char** argv) po::variables_map process_cmd_line(const int argc, char** argv) { po::options_description desc("Options"); - desc.add_options() - ("help", "produce help message") - ("json_config,j", po::value()->default_value("client.json"), "JSON file with config information for hosted RL loop") - ("log_to_file,l", po::value()->default_value(false), "Log interactions and observations to local files") - ("get_model,m", po::value()->default_value(true), "Download model from model source") - ("log_timestamp,t", po::value()->default_value(true), "Apply timestamp to all logged message") - ("ccb", po::value()->default_value(false), "Run in ccb mode") - ("slates", po::value()->default_value(false), "Run in slates mode") - ("ca", po::value()->default_value(false), "Run in continuous actions mode") - ("multistep", po::value()->default_value(false), "Run in multistep mode") - ("num_events", po::value()->default_value(0), "Number of event series' to be sent. 0 is infinite.") - ("random_seed", po::value()->default_value(rand()), "Random seed. Default is random") - ("delay", po::value()->default_value(2000), "Delay between events in ms") - ("quiet", po::bool_switch(), "Suppress logs") - ("random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true") - ("local_loop", po::value()-> default_value(false), "Train and update model locally") - ; + desc.add_options()("help", "produce help message")("json_config,j", + po::value()->default_value("client.json"), "JSON file with config information for hosted RL loop")( + "log_to_file,l", po::value()->default_value(false), "Log interactions and observations to local files")( + "get_model,m", po::value()->default_value(true), "Download model from model source")( + "log_timestamp,t", po::value()->default_value(true), "Apply timestamp to all logged message")("ccb", + po::value()->default_value(false), "Run in ccb mode")("slates", po::value()->default_value(false), + "Run in slates mode")("ca", po::value()->default_value(false), "Run in continuous actions mode")( + "multistep", po::value()->default_value(false), "Run in multistep mode")( + "num_events", po::value()->default_value(0), "Number of event series' to be sent. 0 is infinite.")( + "random_seed", po::value()->default_value(rand()), "Random seed. Default is random")("delay", + po::value()->default_value(2000), + "Delay between events in ms")("quiet", po::bool_switch(), "Suppress logs")( + "random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true")( + "local_loop", po::value()->default_value(false), "Train and update model locally"); po::variables_map vm; store(parse_command_line(argc, argv, desc), vm); diff --git a/examples/rl_sim_cpp/rl_sim.cc b/examples/rl_sim_cpp/rl_sim.cc index b5b3a0c16..2685e6eea 100644 --- a/examples/rl_sim_cpp/rl_sim.cc +++ b/examples/rl_sim_cpp/rl_sim.cc @@ -376,7 +376,8 @@ int rl_sim::init_rl() return -1; } - if(_options["local_loop"].as()) { + if (_options["local_loop"].as()) + { local_loop = true; std::cout << "Using --local_loop, replacing some config vars." << std::endl; if (_loop_kind != CB) @@ -386,22 +387,23 @@ int rl_sim::init_rl() } config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, "LOCAL_MODEL"); - std::cout << "Setting " << r::name::INTERACTION_SENDER_IMPLEMENTATION << "=LOCAL_MODEL" <()) { + if (_options["log_to_file"].as()) + { if (local_loop) { std::cerr << "--local_loop and --log_to_file can't be used together." << std::endl; @@ -411,7 +413,8 @@ int rl_sim::init_rl() config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); } - if (!_options["get_model"].as()) { + if (!_options["get_model"].as()) + { if (local_loop) { std::cerr << "--local_loop and --get_model can't be used together." << std::endl; @@ -429,26 +432,23 @@ int rl_sim::init_rl() // Trace log API calls to the console if (!_quiet) { config.set(r::name::TRACE_LOG_IMPLEMENTATION, r::value::CONSOLE_TRACE_LOGGER); } - if(local_loop) + if (local_loop) { _local_model = std::unique_ptr(new local_model); auto* raw_local_model = _local_model.get(); - r::data_transport_factory.register_type( - "LOCAL_MODEL", [raw_local_model](r::model_management::i_data_transport **retval, - const r::utility::configuration &config, - r::i_trace *trace_logger, r::api_status *status) { - raw_local_model->set_trace_logger(trace_logger); - *retval = new local_model_proxy(raw_local_model); - return 0; + r::data_transport_factory.register_type("LOCAL_MODEL", + [raw_local_model](r::model_management::i_data_transport** retval, const r::utility::configuration& config, + r::i_trace* trace_logger, r::api_status* status) { + raw_local_model->set_trace_logger(trace_logger); + *retval = new local_model_proxy(raw_local_model); + return 0; }); - r::sender_factory.register_type( - "LOCAL_MODEL", - [raw_local_model](r::i_sender **retval, const r::utility::configuration &config, - r::error_callback_fn *error_cb, r::i_trace *trace_logger, - r::api_status *status) { - raw_local_model->set_trace_logger(trace_logger); - *retval = new local_model_proxy(raw_local_model); - return 0; + r::sender_factory.register_type("LOCAL_MODEL", + [raw_local_model](r::i_sender** retval, const r::utility::configuration& config, r::error_callback_fn* error_cb, + r::i_trace* trace_logger, r::api_status* status) { + raw_local_model->set_trace_logger(trace_logger); + *retval = new local_model_proxy(raw_local_model); + return 0; }); } diff --git a/examples/rl_sim_cpp/rl_sim.h b/examples/rl_sim_cpp/rl_sim.h index f82ac41cb..adf87f4dd 100644 --- a/examples/rl_sim_cpp/rl_sim.h +++ b/examples/rl_sim_cpp/rl_sim.h @@ -7,9 +7,9 @@ */ #pragma once #include "live_model.h" +#include "local_loop.h" #include "person.h" #include "robot_joint.h" -#include "local_loop.h" #include diff --git a/external_parser/joiners/example_joiner.cc b/external_parser/joiners/example_joiner.cc index 0934c2bdf..123ecdc4e 100644 --- a/external_parser/joiners/example_joiner.cc +++ b/external_parser/joiners/example_joiner.cc @@ -595,9 +595,7 @@ void example_joiner::on_batch_read() {} metrics::joiner_metrics example_joiner::get_metrics() { return _joiner_metrics; } -int example_joiner::events_in_queue() { - return _batch_event_order.size(); -} +int example_joiner::events_in_queue() { return _batch_event_order.size(); } void example_joiner::apply_cli_overrides(VW::workspace*, const VW::external::parser_options&) {} From daee5a6e917b6a1e09bdc60881ce6d9bfd302fb7 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Thu, 20 Oct 2022 09:34:08 -0400 Subject: [PATCH 04/16] Bump vw submodule to include delta serialization --- ext_libs/vowpal_wabbit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext_libs/vowpal_wabbit b/ext_libs/vowpal_wabbit index 9496a6dd5..f0d45780c 160000 --- a/ext_libs/vowpal_wabbit +++ b/ext_libs/vowpal_wabbit @@ -1 +1 @@ -Subproject commit 9496a6dd5610910a495ca004a93c8ab6913293e4 +Subproject commit f0d45780cfc509a0e17558bc074b4b3f250fb945 From 8ed5ab722a1e3baa10135c2c17e2fef2b5376aa8 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Fri, 21 Oct 2022 14:35:17 -0400 Subject: [PATCH 05/16] add local client implementation (#521) --- rlclientlib/CMakeLists.txt | 4 +- rlclientlib/federation/federated_client.h | 3 +- rlclientlib/federation/local_client.cc | 93 ++++++++++++++++++ rlclientlib/federation/local_client.h | 40 ++++++++ unit_test/CMakeLists.txt | 1 + unit_test/local_client_test.cc | 110 ++++++++++++++++++++++ 6 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 rlclientlib/federation/local_client.cc create mode 100644 rlclientlib/federation/local_client.h create mode 100644 unit_test/local_client_test.cc diff --git a/rlclientlib/CMakeLists.txt b/rlclientlib/CMakeLists.txt index 323554988..f5c3f2855 100644 --- a/rlclientlib/CMakeLists.txt +++ b/rlclientlib/CMakeLists.txt @@ -19,7 +19,7 @@ set(RL_FLAT_BUFFER_FILES_V1 "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/SlatesEvent.fbs" ) set(RL_FLAT_BUFFER_FILES_V2 - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CaEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CaEvent.fbs" "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CbEvent.fbs" "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/DedupInfo.fbs" "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/Event.fbs" @@ -53,6 +53,7 @@ set(PROJECT_SOURCES dedup.cc error_callback_fn.cc factory_resolver.cc + federation/local_client.cc generic_event.cc learning_mode.cc live_model.cc @@ -141,6 +142,7 @@ set(PROJECT_PRIVATE_HEADERS dedup.h federation/federated_client.h federation/joined_log_provider.h + federation/local_client.h generic_event.h live_model_impl.h logger/async_batcher.h diff --git a/rlclientlib/federation/federated_client.h b/rlclientlib/federation/federated_client.h index e80df9f02..6796e1932 100644 --- a/rlclientlib/federation/federated_client.h +++ b/rlclientlib/federation/federated_client.h @@ -42,10 +42,11 @@ struct i_federated_client * try_get_model again until report_result has been called. * * @param payload payload represents the payload to aggregate. Must be a serialized VW model delta. + * @param size payload represents the size of the payload to aggregate. Must be a serialized VW model delta. * @param status Contains error information in the event of a failure * @returns Status code */ - RL_ATTR(nodiscard) virtual int report_result(const std::vector& payload, api_status* status = nullptr) = 0; + RL_ATTR(nodiscard) virtual int report_result(const uint8_t* payload, size_t size, api_status* status = nullptr) = 0; }; } // namespace reinforcement_learning diff --git a/rlclientlib/federation/local_client.cc b/rlclientlib/federation/local_client.cc new file mode 100644 index 000000000..7472be4a6 --- /dev/null +++ b/rlclientlib/federation/local_client.cc @@ -0,0 +1,93 @@ +#include "federation/local_client.h" + +#include "api_status.h" +#include "constants.h" +#include "err_constants.h" +#include "trace_logger.h" +#include "vw/config/options_cli.h" +#include "vw/core/global_data.h" +#include "vw/core/io_buf.h" +#include "vw/core/merge.h" +#include "vw/core/parse_primitives.h" +#include "vw/core/vw.h" +#include "vw/io/io_adapter.h" + +reinforcement_learning::local_client::local_client(std::unique_ptr initial_model, i_trace* trace_logger) + : _current_model(std::move(initial_model)), _state(state_t::model_available), _trace_logger(trace_logger) +{ +} + +reinforcement_learning::local_client::~local_client() = default; + +int reinforcement_learning::local_client::try_get_model(const std::string& app_id, + /* inout */ model_management::model_data& data, /* out */ bool& model_received, api_status* status) +{ + switch (_state) + { + case state_t::model_available: + { + io_buf buf; + auto backing_buffer = std::make_shared>(); + buf.add_file(VW::io::create_vector_writer(backing_buffer)); + VW::save_predictor(*_current_model, buf); + auto* dest_ptr = data.alloc(backing_buffer->size()); + std::memcpy(dest_ptr, backing_buffer->data(), backing_buffer->size()); + data.increment_refresh_count(); + model_received = true; + _state = state_t::model_retrieved; + // Return current model and switch into model retrieved. + } + break; + case state_t::model_retrieved: + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) + << "Cannot call try_get_model again until report_result has been called."; + } + break; + default: + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Invalid state."; + } + return error_code::success; +} + +int reinforcement_learning::local_client::report_result(const uint8_t* payload, size_t size, api_status* status) +{ + switch (_state) + { + case state_t::model_available: + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) + << "Cannot call report_result again until try_get_model has been called."; + } + break; + case state_t::model_retrieved: + { + // Payload must be a delta + // Apply delta to current model and move into model available state. + auto view = VW::io::create_buffer_view(reinterpret_cast(payload), size); + auto delta = VW::model_delta::deserialize(*view); + auto new_model = *_current_model + *delta; + _current_model.reset(new_model.release()); + _state = state_t::model_available; + } + break; + default: + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Invalid state."; + } + return error_code::success; +} + +int reinforcement_learning::create_local_client(const utility::configuration& config, + /*out*/ std::unique_ptr& object, i_trace* trace_logger, api_status* status) +{ + // Create empty model based on ML args on first call + std::string initial_command_line(config.get( + name::MODEL_VW_INITIAL_COMMAND_LINE, "--cb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A")); + + // TODO try catch + auto args = VW::make_unique(VW::split_command_line(initial_command_line)); + auto workspace = VW::initialize_experimental(std::move(args)); + + object = VW::make_unique(std::move(workspace), trace_logger); + return reinforcement_learning::error_code::success; +} diff --git a/rlclientlib/federation/local_client.h b/rlclientlib/federation/local_client.h new file mode 100644 index 000000000..fc7c6339c --- /dev/null +++ b/rlclientlib/federation/local_client.h @@ -0,0 +1,40 @@ +#pragma once + +#include "configuration.h" +#include "federation/federated_client.h" +#include "trace_logger.h" +#include "vw/core/vw_fwd.h" + +namespace reinforcement_learning +{ +struct local_client : i_federated_client +{ + local_client(std::unique_ptr initial_model, i_trace* trace_logger); + ~local_client() override; + RL_ATTR(nodiscard) + int try_get_model(const std::string& app_id, + /* inout */ model_management::model_data& data, /* out */ bool& model_received, + api_status* status = nullptr) override; + + RL_ATTR(nodiscard) int report_result(const uint8_t* payload, size_t size, api_status* status = nullptr) override; + +private: + enum class state_t + { + model_available, + model_retrieved + }; + + state_t _state; + std::unique_ptr _current_model; + i_trace* _trace_logger; +}; + +// Read MODEL_VW_INITIAL_COMMAND_LINE + +RL_ATTR(nodiscard) +int create_local_client(const reinforcement_learning::utility::configuration& config, + /*out*/ std::unique_ptr& object, i_trace* trace_logger, + reinforcement_learning::api_status* status = nullptr); + +} // namespace reinforcement_learning \ No newline at end of file diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index d83911f7e..d9915bae1 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -17,6 +17,7 @@ set(TEST_SOURCES json_serializer_test.cc learning_mode_test.cc live_model_test.cc + local_client_test.cc main.cc mock_http_client.cc mock_util.cc diff --git a/unit_test/local_client_test.cc b/unit_test/local_client_test.cc new file mode 100644 index 000000000..4ba05bf3d --- /dev/null +++ b/unit_test/local_client_test.cc @@ -0,0 +1,110 @@ +#define BOOST_TEST_DYN_LINK +#ifdef STAND_ALONE +# define BOOST_TEST_MODULE Main +#endif + +#include + +#include "err_constants.h" +#include "factory_resolver.h" +#include "federation/federated_client.h" +#include "federation/local_client.h" +#include "vw/config/options.h" +#include "vw/config/options_cli.h" +#include "vw/core/io_buf.h" +#include "vw/core/merge.h" +#include "vw/core/parse_example.h" +#include "vw/core/parse_example_json.h" +#include "vw/core/vw.h" +#include "vw/io/io_adapter.h" + +#include +#include + +namespace rl = reinforcement_learning; +namespace rerr = reinforcement_learning::error_code; +namespace rutil = reinforcement_learning::utility; + +VW::multi_ex parse_json(VW::workspace& all, const std::string& line) +{ + VW::multi_ex examples; + examples.push_back(&VW::get_unused_example(&all)); + VW::read_line_json_s( + all, examples, (char*)line.c_str(), line.length(), (VW::example_factory_t)&VW::get_unused_example, (void*)&all); + VW::setup_examples(all, examples); + return examples; +} + +BOOST_AUTO_TEST_CASE(get_model_twice_fails) +{ + rutil::configuration config; + std::unique_ptr client; + BOOST_CHECK_EQUAL(rl::create_local_client(config, client, nullptr, nullptr), rerr::success); + rl::model_management::model_data data; + bool model_received = false; + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); + BOOST_CHECK(data.data_sz() > 0); + BOOST_CHECK_EQUAL(model_received, true); + BOOST_CHECK_NE(client->try_get_model("test_app_id", data, model_received), rerr::success); +} + +BOOST_AUTO_TEST_CASE(send_delta_update) +{ + rutil::configuration config; + std::unique_ptr client; + BOOST_CHECK_EQUAL(rl::create_local_client(config, client, nullptr, nullptr), rerr::success); + BOOST_CHECK_NE(client.get(), nullptr); + rl::model_management::model_data data; + bool model_received = false; + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); + auto opts = std::unique_ptr(new VW::config::options_cli(std::vector{})); + auto original_workspace = + VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); + opts = std::unique_ptr(new VW::config::options_cli(std::vector{})); + auto workspace = + VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); + std::string json_text = R"( + { + "s_": "1", + "s_": "2", + "_labelIndex": 0, + "_label_Action": 1, + "_label_Cost": 1, + "_label_Probability": 0.5, + "_multi": [ + { + "a_": "1", + "b_": "1", + "c_": "1" + }, + { + "a_": "2", + "b_": "2", + "c_": "2" + }, + { + "a_": "3", + "b_": "3", + "c_": "3" + } + ] + })"; + + auto examples = parse_json(*workspace, json_text); + workspace->learn(examples); + workspace->finish_example(examples); + + auto delta = *workspace - *original_workspace; + auto backing_buffer = std::make_shared>(); + auto writer = VW::io::create_vector_writer(backing_buffer); + delta.serialize(*writer); + BOOST_CHECK_EQUAL( + client->report_result(reinterpret_cast(backing_buffer->data()), backing_buffer->size()), + rerr::success); + BOOST_CHECK_NE( + client->report_result(reinterpret_cast(backing_buffer->data()), backing_buffer->size()), + rerr::success); + + model_received = false; + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); +} From 0f0e1f8972c3ecf68a42d312efb8f0ef026f3a7f Mon Sep 17 00:00:00 2001 From: Byron Xu Date: Tue, 10 Jan 2023 16:15:28 -0500 Subject: [PATCH 06/16] Add local joining and training for federated learning (#523) This change implements a local joining and training loop in rlclientlib --- .github/workflows/build_rlclientlib.yml | 178 ++++-- .github/workflows/build_vw_bp.yml | 131 +++-- .github/workflows/codeql-analysis.yml | 202 ++++--- .github/workflows/daily_integration.yml | 88 ++- .github/workflows/dotnet_nugets.yml | 2 + .github/workflows/run_benchmarks.yml | 90 ++- .github/workflows/vcpkg_build.yml | 14 +- .scripts/build-cmake.cmd | 18 - .scripts/build-vw-bp.cmd | 22 - .scripts/build.cmd | 31 -- .scripts/find-vs2017.cmd | 6 - .scripts/init-cmake.cmd | 39 -- .scripts/init.cmd | 61 --- .scripts/linux/build-valgrind.sh | 18 - .scripts/linux/build-vw-bp.sh | 18 - .scripts/linux/build-with-benchmarks.sh | 11 - .scripts/linux/build.sh | 17 - .scripts/linux/clang-format.sh | 6 +- .scripts/linux/compare-benchmarks.sh | 9 - .scripts/linux/install-benchmarks.sh | 24 - .scripts/linux/run-benchmarks.sh | 11 - .scripts/linux/run-clang-tidy.sh | 3 +- .scripts/linux/test-vw-bp.sh | 17 - .scripts/linux/test.sh | 13 - .scripts/linux/unit-tests-valgrind.sh | 12 - .scripts/linux/unit-tests-vw-bp-valgrind.sh | 12 - .scripts/macos/build-vw-bp.sh | 11 - .scripts/macos/build.sh | 13 - .scripts/macos/restore-vw-bp.sh | 6 - .scripts/macos/restore.sh | 6 - .scripts/macos/test-vw-bp.sh | 12 - .scripts/macos/test.sh | 11 - .scripts/restore-cmake.cmd | 21 - .scripts/restore-vw-bp-deps.cmd | 19 - .scripts/restore.cmd | 42 -- .scripts/test-cmake.cmd | 14 - .scripts/test-vw-bp.cmd | 17 - .scripts/test.cmd | 20 - CMakeLists.txt | 43 +- benchmarks/CMakeLists.txt | 1 + benchmarks/benchmark_cb_v2.cc | 15 +- benchmarks/benchmark_ccb.cc | 139 +++++ benchmarks/benchmarks_common.cc | 116 ++-- benchmarks/benchmarks_common.h | 22 +- .../rl.net.native/rl.net.factory_context.cc | 3 +- .../cs/rl.net.native/rl.net.live_model.cc | 17 +- bindings/python/py_api.cc | 115 ++-- cmake/Modules/FindOnnxRuntime.cmake | 35 +- examples/onnx/onnx_example.cc | 3 +- .../override_interface/override_interface.cc | 3 +- examples/rl_sim_cpp/local_loop.cc | 12 +- examples/rl_sim_cpp/rl_sim.cc | 31 +- examples/rl_sim_cpp/rl_sim.h | 2 +- examples/test_cpp/options.cc | 4 +- examples/test_cpp/test_data_provider.cc | 5 +- ext_libs/vcpkg | 2 +- ext_libs/vowpal_wabbit | 2 +- external_parser/CMakeLists.txt | 46 +- external_parser/README.md | 180 +++--- .../event_processors/joined_event.h | 134 +++-- external_parser/event_processors/loop.h | 9 +- external_parser/event_processors/metadata.h | 8 +- external_parser/event_processors/reward.h | 7 +- .../event_processors/timestamp_helper.cc | 2 + .../event_processors/timestamp_helper.h | 10 +- .../event_processors/typed_events.h | 88 +-- external_parser/joiners/example_joiner.cc | 99 ++-- external_parser/joiners/example_joiner.h | 37 +- external_parser/joiners/i_joiner.h | 13 +- .../joiners/multistep_example_joiner.cc | 58 +- .../joiners/multistep_example_joiner.h | 25 +- external_parser/log_converter.cc | 25 +- external_parser/log_converter.h | 2 - external_parser/main.cc | 17 +- external_parser/parse_example_binary.cc | 8 +- external_parser/parse_example_converter.cc | 2 +- external_parser/parse_example_converter.h | 3 +- external_parser/parse_example_external.cc | 30 +- external_parser/unit_tests/CMakeLists.txt | 20 +- external_parser/unit_tests/test_common.cc | 5 +- external_parser/unit_tests/test_common.h | 2 +- .../unit_tests/test_log_converter.cc | 4 +- external_parser/unit_tests/test_metrics.cc | 1 + .../unit_tests/test_reward_functions.cc | 16 +- .../unit_tests/test_timestamp_helper.cc | 8 +- .../unit_tests/test_vw_binary_parser.cc | 4 +- .../unit_tests/test_vw_external_parser.cc | 1 + include/api_status.h | 9 +- include/constants.h | 40 ++ include/error_callback_fn.h | 13 +- include/trace_logger.h | 3 +- rlclientlib/CMakeLists.txt | 517 ++++++++++-------- rlclientlib/azure_factories.cc | 36 +- rlclientlib/dedup.cc | 18 +- rlclientlib/dedup_internals.h | 8 +- rlclientlib/extensions/CMakeLists.txt | 3 - rlclientlib/extensions/onnx/src/onnx_input.h | 2 +- rlclientlib/extensions/onnx/src/onnx_model.cc | 12 +- rlclientlib/extensions/onnx/src/onnx_model.h | 2 +- .../extensions/onnx/src/tensor_parser.cc | 22 +- rlclientlib/factory_resolver.cc | 48 +- rlclientlib/federation/eud_utils.h | 67 +++ rlclientlib/federation/event_sink.h | 42 ++ rlclientlib/federation/joined_log_provider.h | 18 +- rlclientlib/federation/local_client.cc | 20 +- rlclientlib/federation/local_client.h | 18 +- .../federation/local_loop_controller.cc | 95 ++++ .../federation/local_loop_controller.h | 69 +++ .../federation/sender_joined_log_provider.cc | 263 +++++++++ .../federation/sender_joined_log_provider.h | 80 +++ rlclientlib/federation/vw_trainable_model.cc | 440 +++++++++++++++ rlclientlib/federation/vw_trainable_model.h | 74 +++ rlclientlib/generic_event.h | 5 +- rlclientlib/learning_mode.cc | 5 +- rlclientlib/live_model.cc | 19 +- rlclientlib/live_model_impl.cc | 212 +++++-- rlclientlib/live_model_impl.h | 11 +- rlclientlib/logger/async_batcher.h | 19 +- rlclientlib/logger/event_logger.cc | 15 +- rlclientlib/logger/event_logger.h | 6 +- rlclientlib/logger/event_queue.h | 4 +- rlclientlib/logger/http_transport_client.h | 130 +++-- rlclientlib/logger/preamble_sender.cc | 4 +- rlclientlib/model_mgmt/file_model_loader.cc | 4 +- .../model_mgmt/restapi_data_transport.cc | 134 ++--- rlclientlib/multi_slot_response_detailed.cc | 4 +- rlclientlib/sampling.cc | 24 +- rlclientlib/serialization/fb_serializer.h | 5 +- .../serialization/payload_serializer.h | 4 +- rlclientlib/time_helper.cc | 45 +- rlclientlib/time_helper.h | 27 +- rlclientlib/utility/config_utility.cc | 13 +- rlclientlib/utility/context_helper.cc | 4 +- rlclientlib/utility/data_buffer.cc | 4 +- rlclientlib/utility/object_pool.h | 79 ++- .../utility/periodic_background_proc.h | 3 +- rlclientlib/utility/watchdog.cc | 9 +- rlclientlib/vw_model/safe_vw.cc | 31 +- rlclientlib/vw_model/vw_model.cc | 4 +- test_tools/example_gen/example_gen.cc | 181 ++++-- test_tools/joiner/main.cc | 15 +- test_tools/joiner/text_converter.cc | 3 +- test_tools/stdin2rllib/main.cc | 15 +- unit_test/CMakeLists.txt | 12 +- unit_test/async_batcher_test.cc | 27 +- unit_test/common_test_utils.h | 106 +++- unit_test/data.h | 1 + unit_test/eud_test.cc | 34 ++ unit_test/event_queue_test.cc | 35 +- unit_test/extensions/onnx/CMakeLists.txt | 25 + unit_test/extensions/onnx/main.cc | 3 +- .../extensions/onnx/mnist_inference_test.cc | 1 - unit_test/extensions/onnx/mock_helpers.cc | 6 +- .../extensions/onnx/tensor_notation_test.cc | 1 - unit_test/extensions/onnx/test_helpers.h | 6 +- unit_test/factory_test.cc | 6 +- unit_test/fb_serializer_test.cc | 10 +- unit_test/http_transport_client_test.cc | 96 +++- unit_test/local_client_test.cc | 46 +- unit_test/local_loop_controller_test.cc | 207 +++++++ unit_test/local_loop_end_to_end.cc | 142 +++++ unit_test/mock_http_client.cc | 12 +- unit_test/mock_util.cc | 48 +- unit_test/model_mgmt_test.cc | 5 +- unit_test/payload_serializer_test.cc | 12 +- unit_test/sender_joined_log_provider_test.cc | 353 ++++++++++++ unit_test/sleeper_test.cc | 36 +- unit_test/time_tests.cc | 16 +- unit_test/trainable_model_test.cc | 95 ++++ unit_test/watchdog_test.cc | 34 +- vcpkg.json | 8 +- 171 files changed, 4860 insertions(+), 2159 deletions(-) delete mode 100644 .scripts/build-cmake.cmd delete mode 100644 .scripts/build-vw-bp.cmd delete mode 100644 .scripts/build.cmd delete mode 100644 .scripts/find-vs2017.cmd delete mode 100644 .scripts/init-cmake.cmd delete mode 100644 .scripts/init.cmd delete mode 100755 .scripts/linux/build-valgrind.sh delete mode 100755 .scripts/linux/build-vw-bp.sh delete mode 100755 .scripts/linux/build-with-benchmarks.sh delete mode 100755 .scripts/linux/build.sh delete mode 100755 .scripts/linux/compare-benchmarks.sh delete mode 100755 .scripts/linux/install-benchmarks.sh delete mode 100755 .scripts/linux/run-benchmarks.sh delete mode 100755 .scripts/linux/test-vw-bp.sh delete mode 100755 .scripts/linux/test.sh delete mode 100755 .scripts/linux/unit-tests-valgrind.sh delete mode 100755 .scripts/linux/unit-tests-vw-bp-valgrind.sh delete mode 100755 .scripts/macos/build-vw-bp.sh delete mode 100755 .scripts/macos/build.sh delete mode 100755 .scripts/macos/restore-vw-bp.sh delete mode 100755 .scripts/macos/restore.sh delete mode 100755 .scripts/macos/test-vw-bp.sh delete mode 100755 .scripts/macos/test.sh delete mode 100644 .scripts/restore-cmake.cmd delete mode 100644 .scripts/restore-vw-bp-deps.cmd delete mode 100644 .scripts/restore.cmd delete mode 100644 .scripts/test-cmake.cmd delete mode 100644 .scripts/test-vw-bp.cmd delete mode 100644 .scripts/test.cmd create mode 100644 benchmarks/benchmark_ccb.cc create mode 100644 rlclientlib/federation/eud_utils.h create mode 100644 rlclientlib/federation/event_sink.h create mode 100644 rlclientlib/federation/local_loop_controller.cc create mode 100644 rlclientlib/federation/local_loop_controller.h create mode 100644 rlclientlib/federation/sender_joined_log_provider.cc create mode 100644 rlclientlib/federation/sender_joined_log_provider.h create mode 100644 rlclientlib/federation/vw_trainable_model.cc create mode 100644 rlclientlib/federation/vw_trainable_model.h create mode 100644 unit_test/eud_test.cc create mode 100644 unit_test/local_loop_controller_test.cc create mode 100644 unit_test/local_loop_end_to_end.cc create mode 100644 unit_test/sender_joined_log_provider_test.cc create mode 100644 unit_test/trainable_model_test.cc diff --git a/.github/workflows/build_rlclientlib.yml b/.github/workflows/build_rlclientlib.yml index 98a83359f..da8a4ccc3 100644 --- a/.github/workflows/build_rlclientlib.yml +++ b/.github/workflows/build_rlclientlib.yml @@ -9,71 +9,149 @@ on: branches: - '*' +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true + jobs: - build-linux: - name: rlclientlib.ubuntu18.04 - container: - image: vowpalwabbit/rl-ubuntu-1804:latest + build-ubuntu: + # Ubuntu build has ONNX extension and Valgrind test + name: rlclientlib-${{ matrix.build_type }}-ubuntu-latest runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + build_type: [debug, release] steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - run: ./.scripts/linux/build.sh - - run: ./.scripts/linux/test.sh - - build-linux-valgrind: - name: rlclientlib-valgrind.ubuntu18.04 - container: - image: vowpalwabbit/rl-ubuntu-1804:latest - runs-on: ubuntu-latest - steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 + - uses: lukka/get-cmake@latest + - name: Install ONNX + run: | + ONNX_FILENAME="onnxruntime-linux-x64-1.13.1" + wget "https://github.com/microsoft/onnxruntime/releases/download/v1.13.1/$ONNX_FILENAME.tgz" + tar xvf "$ONNX_FILENAME.tgz" + echo "ONNXRUNTIME_ROOT=$ONNX_FILENAME" >> $GITHUB_ENV + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache with: - submodules: recursive - - run: ./.scripts/linux/build-valgrind.sh - - run: ./.scripts/linux/unit-tests-valgrind.sh + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + -Drlclientlib_BUILD_ONNXRUNTIME_EXTENSION=ON + -DONNXRUNTIME_ROOT="${{ github.workspace }}/${{ env.ONNXRUNTIME_ROOT }}" + - name: Build + run: | + cmake --build build + - name: Test + run: | + cd build + ctest --verbose --output-on-failure + - name: Test with Valgrind + run: | + cd build + sudo apt install -y valgrind + valgrind --quiet --error-exitcode=100 --undef-value-errors=no --leak-check=full ./unit_test/rltest -- valgrind build-macos: - name: rlclientlib.macos-11 - runs-on: macos-11 + # Mac build doesn't have any additional features enabled + name: rlclientlib-${{ matrix.build_type }}-macos-latest + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + build_type: [debug, release] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - - run: ./.scripts/macos/restore.sh - - run: ./.scripts/macos/build.sh - - run: ./.scripts/macos/test.sh + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: macos-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + - name: Build + run: | + cmake --build build + - name: Test + run: | + cd build + ctest --verbose --output-on-failure build-windows: - name: rlclientlib.windows-2019 - runs-on: windows-2019 - env: - SOURCE_DIR: ${{ github.workspace }}/reinforcement_learning - VCPKG_ROOT: ${{ github.workspace }}/vcpkg - # 2022.03.10 - VCPKG_REF: af2287382b1991dbdcb7e5112d236f3323b9dd7a + # Windows build has C# bindings + name: rlclientlib-${{ matrix.build_type }}-windows-latest + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + build_type: [debug, release] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: - path: 'reinforcement_learning' submodules: recursive - - uses: actions/checkout@v2 + - name: Setup MSVC Developer Command Prompt + uses: ilammy/msvc-dev-cmd@v1 + - uses: lukka/get-cmake@latest + - name: Install dotnet t4 + run: dotnet tool install --global dotnet-t4 + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache with: - path: 'vcpkg' - repository: 'microsoft/vcpkg' - ref: ${{ env.VCPKG_REF }} - - name: Restore vcpkg and build artifacts - uses: actions/cache@v2 + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: windows-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main with: - path: | - ${{ env.VCPKG_ROOT }}/installed/ - key: | - rl-${{ env.VCPKG_REF }}-windows-2019-vcpkg-cache-invalidate-0 - - uses: ilammy/msvc-dev-cmd@v1 - - run: ${{ env.VCPKG_ROOT }}/bootstrap-vcpkg.bat - - run: ${{ env.SOURCE_DIR }}/.scripts/restore-cmake.cmd - - run: ${{ env.SOURCE_DIR }}/.scripts/build-cmake.cmd - - run: ${{ env.SOURCE_DIR }}/.scripts/test-cmake.cmd \ No newline at end of file + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + - name: Build + run: | + cmake --build build + - name: Test + run: | + cd build + ctest --verbose --output-on-failure diff --git a/.github/workflows/build_vw_bp.yml b/.github/workflows/build_vw_bp.yml index 395616cf3..60b4722fa 100644 --- a/.github/workflows/build_vw_bp.yml +++ b/.github/workflows/build_vw_bp.yml @@ -9,71 +9,82 @@ on: branches: - '*' -jobs: - build-linux: - name: binary_parser.ubuntu18.04 - container: - image: vowpalwabbit/rl-ubuntu-1804:latest - runs-on: ubuntu-latest - steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 - with: - submodules: recursive - - run: ./.scripts/linux/build-vw-bp.sh - - run: ./.scripts/linux/test-vw-bp.sh - - build-linux-valgrind: - name: binary_parser-valgrind.ubuntu18.04 - container: - image: vowpalwabbit/rl-ubuntu-1804:latest - runs-on: ubuntu-latest - steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 - with: - submodules: recursive - - run: ./.scripts/linux/build-vw-bp.sh - - run: ./.scripts/linux/unit-tests-vw-bp-valgrind.sh +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache - build-macos: - name: binary_parser.macos-11 - runs-on: macos-11 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - run: ./.scripts/macos/restore-vw-bp.sh - - run: ./.scripts/macos/build-vw-bp.sh - - run: ./.scripts/macos/test-vw-bp.sh +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true - build-windows: - name: binary_parser.windows-2019 - runs-on: windows-2019 +jobs: + build-binary-parser: + name: binary-parser-${{ matrix.build.build_type }}-${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { os: "windows-latest", vcpkg_target_triplet: "x64-windows-static" } + - { os: "ubuntu-latest", vcpkg_target_triplet: "x64-linux" } + - { os: "macos-latest", vcpkg_target_triplet: "x64-osx" } + build: + # Set the appropriate static runtime for MSVC on Windows + # CMake ignores the CMAKE_MSVC_RUNTIME_LIBRARY option on other platforms + - { build_type: "debug", msvc_runtime: "MultiThreadedDebug" } + - { build_type: "release", msvc_runtime: "MultiThreaded" } env: - SOURCE_DIR: ${{ github.workspace }}/reinforcement_learning - VCPKG_ROOT: ${{ github.workspace }}/vcpkg - # 2022.03.10 - VCPKG_REF: af2287382b1991dbdcb7e5112d236f3323b9dd7a + VCPKG_DEFAULT_TRIPLET: ${{ matrix.config.vcpkg_target_triplet }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: - path: 'reinforcement_learning' submodules: recursive - - uses: actions/checkout@v2 + - name: Setup MSVC Developer Command Prompt + if: ${{ startsWith(matrix.config.os, 'windows') }} + uses: ilammy/msvc-dev-cmd@v1 + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache with: - path: 'vcpkg' - repository: 'microsoft/vcpkg' - ref: ${{ env.VCPKG_REF }} - - name: Restore vcpkg and build artifacts - uses: actions/cache@v2 + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ${{ matrix.config.os }}-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main with: - path: | - ${{ env.VCPKG_ROOT }}/installed/ - key: | - vw-bp-${{ env.VCPKG_REF }}-windows-2019-vcpkg-cache-invalidate-0 - - uses: ilammy/msvc-dev-cmd@v1 - - run: ${{ env.VCPKG_ROOT }}/bootstrap-vcpkg.bat - - run: ${{ env.SOURCE_DIR }}/.scripts/restore-vw-bp-deps.cmd - - run: ${{ env.SOURCE_DIR }}/.scripts/build-vw-bp.cmd - - run: ${{ env.SOURCE_DIR }}/.scripts/test-vw-bp.cmd \ No newline at end of file + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cd external_parser; + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=${{ matrix.build.build_type }} + -DCMAKE_MSVC_RUNTIME_LIBRARY=${{ matrix.build.msvc_runtime }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DVCPKG_MANIFEST_DIR="${{ github.workspace }}" + -DVCPKG_TARGET_TRIPLET=${{ matrix.config.vcpkg_target_triplet }} + -DVCPKG_HOST_TRIPLET=${{ matrix.config.vcpkg_target_triplet }} + -DWARNING_AS_ERROR=OFF + -Wno-deprecated + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + - name: Build + run: | + cd external_parser + cmake --build build + - name: Test + run: | + cd external_parser/build + ctest --verbose --output-on-failure + - name: Test binary parser VW executable + run: | + cd external_parser/build + ./vw --extra_metrics metrics.json -d ../unit_tests/test_files/valid_joined_logs/cb_simple.log --binary_parser --cb_explore_adf + python3 -m json.tool metrics.json + - name: Test with Valgrind + if: ${{ startsWith(matrix.config.os, 'ubuntu') }} + run: | + cd external_parser/build + sudo apt install -y valgrind + valgrind --quiet --error-exitcode=100 --undef-value-errors=no --leak-check=full ./unit_tests/binary_parser_unit_tests diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0557f43ae..60aa365d4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,99 +9,131 @@ on: schedule: - cron: '26 11 * * 5' +permissions: + actions: read + contents: read + security-events: write + +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true + jobs: - analyze: - name: Analyze + analyze-cpp: + name: Analyze C++ + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: cpp + config-file: ./.github/codeql/codeql-config.yml + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + -DBUILD_TESTING=OFF + - name: Build + run: | + cmake --build build + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + + analyze-python: + name: Analyze Python bindings container: image: vowpalwabbit/ubuntu2004-dev:latest - runs-on: ${{ matrix.config.os }} - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - config: - - { language: "cpp", os: ubuntu-latest } - - { language: "python", os: ubuntu-latest } + runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.config.language }} - config-file: ./.github/codeql/codeql-config.yml - - name: Install python pip - if: matrix.config.language == 'python' - shell: bash - run: | - apt update - apt install -y python3-pip - - name: Autobuild Python - if: matrix.config.language == 'python' - uses: github/codeql-action/autobuild@v2 - - name: Build C++ - if: matrix.config.language == 'cpp' - shell: bash - run: | - apt update - apt install -y ninja-build - apt install -y libspdlog-dev libfmt-dev libboost-math-dev - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=Off -DVCPKG_MANIFEST_MODE=Off - cmake --build build - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: python + config-file: ./.github/codeql/codeql-config.yml + - name: Install python pip + shell: bash + run: | + apt update + apt install -y python3-pip + - name: Autobuild Python + uses: github/codeql-action/autobuild@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 analyze-csharp: name: Analyze CSharp bindings runs-on: windows-latest - permissions: - actions: read - contents: read - security-events: write - strategy: - fail-fast: false env: - SOURCE_DIR: ${{ github.workspace }}/reinforcement_learning - VCPKG_ROOT: ${{ github.workspace }}/vcpkg - # 2022.03.10 - VCPKG_REF: af2287382b1991dbdcb7e5112d236f3323b9dd7a + VCPKG_ROOT: ${{ github.workspace }}/ext_libs/vcpkg + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - path: 'reinforcement_learning' - submodules: recursive - - name: Checkout vcpkg - uses: actions/checkout@v3 - with: - path: 'vcpkg' - repository: 'microsoft/vcpkg' - ref: ${{ env.VCPKG_REF }} - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: csharp - config-file: ${{ env.SOURCE_DIR }}/.github/codeql/codeql-config.yml - - name: Restore vcpkg and build artifacts - uses: actions/cache@v2 - with: - path: | - ${{ env.VCPKG_ROOT }}/installed/ - key: | - rl-${{ env.VCPKG_REF }}-windows-2019-vcpkg-cache-invalidate-0 - - name: msvc-dev-cmd - uses: ilammy/msvc-dev-cmd@v1 - - run: ${{ env.VCPKG_ROOT }}/bootstrap-vcpkg.bat - - run: ${{ env.SOURCE_DIR }}/.scripts/restore-cmake.cmd - - run: ${{ env.SOURCE_DIR }}/.scripts/build-cmake.cmd - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - paths: ${{ env.SOURCE_DIR }} + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Setup MSVC Developer Command Prompt + uses: ilammy/msvc-dev-cmd@v1 + - uses: lukka/get-cmake@latest + - name: Install dotnet t4 + run: dotnet tool install --global dotnet-t4 + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: csharp + config-file: ./.github/codeql/codeql-config.yml + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + -Drlclientlib_BUILD_DOTNET=ON + - name: Build + run: | + cmake --build build + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/daily_integration.yml b/.github/workflows/daily_integration.yml index d310dd76a..e4caa99cf 100644 --- a/.github/workflows/daily_integration.yml +++ b/.github/workflows/daily_integration.yml @@ -11,14 +11,18 @@ on: schedule: - cron: "0 8 * * *" +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true + jobs: LatestVW_RLClientLib: - container: - image: vowpalwabbit/rl-ubuntu-1804:latest runs-on: ubuntu-latest steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - name: Update VW to latest @@ -26,22 +30,42 @@ jobs: run: | pushd ext_libs/vowpal_wabbit git config --global --add safe.directory "$(pwd)" - git pull origin master + git fetch origin master + git reset --hard origin/master popd - - name: Build + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV shell: bash - run: ./.scripts/linux/build.sh + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + - name: Build + run: | + cmake --build build - name: Test - shell: bash - run: ./.scripts/linux/test.sh + run: | + cd build + ctest --verbose --output-on-failure LatestVW_BinaryParser: - container: - image: vowpalwabbit/rl-ubuntu-1804:latest runs-on: ubuntu-latest steps: - # v1 must be used because newer versions require a node.js version that will not run on this old image. - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - name: Update VW to latest @@ -49,11 +73,39 @@ jobs: run: | pushd ext_libs/vowpal_wabbit git config --global --add safe.directory "$(pwd)" - git pull origin master + git fetch origin master + git reset --hard origin/master popd - - name: Build + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV shell: bash - run: ./.scripts/linux/build-vw-bp.sh + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main + with: + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: > + cd external_parser; + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake" + -DVCPKG_MANIFEST_DIR="${{ github.workspace }}" + -DWARNING_AS_ERROR=OFF + -Wno-deprecated + -DFMT_SYS_DEP=ON + -DSPDLOG_SYS_DEP=ON + - name: Build + run: | + cd external_parser + cmake --build build - name: Test - shell: bash - run: ./.scripts/linux/test-vw-bp.sh + run: | + cd external_parser/build + ctest --verbose --output-on-failure diff --git a/.github/workflows/dotnet_nugets.yml b/.github/workflows/dotnet_nugets.yml index adc7b0f89..a383cda5a 100644 --- a/.github/workflows/dotnet_nugets.yml +++ b/.github/workflows/dotnet_nugets.yml @@ -35,6 +35,8 @@ jobs: - uses: actions/checkout@v2 - run: | git submodule update --init ext_libs/vcpkg ext_libs/vowpal_wabbit ext_libs/zstd + cd ext_libs/vowpal_wabbit + git submodule update --init ext_libs/eigen - name: Setup MSVC Developer Command Prompt if: ${{ startsWith(matrix.config.os, 'windows') }} uses: ilammy/msvc-dev-cmd@v1 diff --git a/.github/workflows/run_benchmarks.yml b/.github/workflows/run_benchmarks.yml index 60c9fd65a..2ac81547a 100644 --- a/.github/workflows/run_benchmarks.yml +++ b/.github/workflows/run_benchmarks.yml @@ -6,59 +6,57 @@ on: - master - 'releases/**' +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} + cancel-in-progress: true + jobs: benchmark: - container: - image: vowpalwabbit/rl-ubuntu-1804:latest runs-on: ubuntu-latest + permissions: + contents: write + deployments: write steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: - ref: ${{ github.base_ref }} submodules: recursive - - name: Install google benchmarks - shell: bash - run: ./.scripts/linux/install-benchmarks.sh - - name: Build ${{ github.base_ref }} + - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV shell: bash - run: ./.scripts/linux/build-with-benchmarks.sh - - name: Benchmark ${{ github.base_ref }} - shell: bash - run: ./.scripts/linux/run-benchmarks.sh master-benchmarks.json - - name: Upload ${{ github.base_ref }} benchmark results - uses: actions/upload-artifact@v2 + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache with: - name: master-benchmarks - path: master-benchmarks.json - - name: Upload benchmark compare - uses: actions/upload-artifact@v2 + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ubuntu-latest-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" + - uses: lukka/run-vcpkg@main with: - name: benchmark-compare - path: benchmark/tools/ - - name: Install benchmark compare requirements - shell: bash - run: | - python3 -m pip install -r benchmark/tools/requirements.txt - # The above requirements file is missing the pandas dependency. - python3 -m pip install pandas - - run: rm -rf benchmark build rlclientlib/generated/ # generated or downloaded files - - uses: actions/checkout@v1 + vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' + vcpkgJsonGlob: "${{ github.workspace }}/vcpkg.json" + - name: Configure + run: cmake --preset vcpkg-release -DRL_BUILD_BENCHMARKS=ON + - name: Build + run: cmake --build build --target rl_benchmarks + - name: Run benchmarks + run: > + ./build/benchmarks/rl_benchmarks + --benchmark_min_time=2 + --benchmark_format=console + --benchmark_out_format=json + --benchmark_out=benchmark_results.json + - uses: benchmark-action/github-action-benchmark@v1 with: - submodules: recursive - - name: Download ${{ github.base_ref }} benchmark results - uses: actions/download-artifact@v2 - with: - name: master-benchmarks - - name: Download benchmark compare - uses: actions/download-artifact@v2 - with: - name: benchmark-compare - - name: Build branch - shell: bash - run: ./.scripts/linux/build-with-benchmarks.sh - - name: Benchmark branch - shell: bash - run: ./.scripts/linux/run-benchmarks.sh branch-benchmarks.json - - name: Compare benchmarks - shell: bash - run: ./.scripts/linux/compare-benchmarks.sh master-benchmarks.json branch-benchmarks.json + tool: 'googlecpp' + output-file-path: benchmark_results.json + alert-threshold: '150%' + fail-on-alert: true + comment-on-alert: true + auto-push: false + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Push results to gh-pages branch + if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} + run: git push 'https://VowpalWabbit:${{ secrets.GITHUB_TOKEN }}@github.com/VowpalWabbit/reinforcement_learning.git' gh-pages:gh-pages diff --git a/.github/workflows/vcpkg_build.yml b/.github/workflows/vcpkg_build.yml index d3f07d09f..cca32c743 100644 --- a/.github/workflows/vcpkg_build.yml +++ b/.github/workflows/vcpkg_build.yml @@ -9,6 +9,9 @@ on: branches: - '*' +env: + VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}/vcpkg_binary_cache + concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.sha }} cancel-in-progress: true @@ -23,10 +26,19 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] preset: [vcpkg-debug, vcpkg-release] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - uses: lukka/get-cmake@latest + - run: echo "VCPKG_COMMIT=$(git rev-parse :ext_libs/vcpkg)" >> $GITHUB_ENV + shell: bash + - run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} + - uses: actions/cache@v3 + env: + cache-name: vcpkg-cache + with: + path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}/* + key: ${{ matrix.os }}-build-${{ env.cache-name }}-${{ hashFiles('vcpkg.json') }}-${{ env.VCPKG_COMMIT }}" - uses: lukka/run-vcpkg@main with: vcpkgDirectory: '${{ github.workspace }}/ext_libs/vcpkg' diff --git a/.scripts/build-cmake.cmd b/.scripts/build-cmake.cmd deleted file mode 100644 index 0124acce2..000000000 --- a/.scripts/build-cmake.cmd +++ /dev/null @@ -1,18 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init-cmake.cmd -PUSHD %rlRoot% - -cmake -S . -B build -A "x64" -DCMAKE_TOOLCHAIN_FILE="%VcpkgCmake%" -DVCPKG_TARGET_TRIPLET=x64-windows-v141 -DVCPKG_MANIFEST_MODE=Off -if %errorlevel% neq 0 exit /b %errorlevel% -cmake --build build --config Release -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL \ No newline at end of file diff --git a/.scripts/build-vw-bp.cmd b/.scripts/build-vw-bp.cmd deleted file mode 100644 index 2b2f15b9f..000000000 --- a/.scripts/build-vw-bp.cmd +++ /dev/null @@ -1,22 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init-cmake.cmd -PUSHD %vwBinaryParserRoot% - -ECHO Building %vwBinaryParserRoot% for Release x64 -REM x64-windows-static-md is a community triplet that sets CRT to dynamic but all the other dependencies to static -REM using -Wno-deprecated because zstd's CMakeLists.txt produces a deprecated warning that causes the CI to fail -cmake -S . -B build -A x64 -DCMAKE_TOOLCHAIN_FILE="%VcpkgCmake%" -DVCPKG_TARGET_TRIPLET=x64-windows-static-md-v141 -DWARNING_AS_ERROR=OFF -DVCPKG_MANIFEST_MODE=Off -Wno-deprecated -if %errorlevel% neq 0 exit /b %errorlevel%-DVCPKG_TARGET_TRIPLET=x64-windows-static-md-v141 - -cmake --build build --config Release -t rl_binary_parser_bin binary_parser_unit_tests -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL diff --git a/.scripts/build.cmd b/.scripts/build.cmd deleted file mode 100644 index cde62c0e0..000000000 --- a/.scripts/build.cmd +++ /dev/null @@ -1,31 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init.cmd -PUSHD %~dp0 - -SET "flatcPath=%VCPKG_INSTALLATION_ROOT%\installed\x64-windows-v141\tools\flatbuffers\flatc.exe" - -REM The correct way to integrate this is to use msbuild targets and contribute them back to flatbuff, -REM rather than a command line execution -IF NOT DEFINED flatcPath ( - IF NOT DEFINED VcpkgInstallRoot ( - ECHO ERROR: flatcPath is not configured, but VcpkgInstallRoot is also not configured. Cannot find vcpkg. - EXIT /b 1 - ) - - SET "flatcPath=%VcpkgInstallRoot%installed\x64-windows-v141\tools\flatbuffers\flatc.exe" -) - -REM TODO: Figure out how to parametrize this script?! (is there a standard, or do we actually need parse args?) -ECHO Building "%rlRoot%\rl.sln" for Release x64 -"%msbuildPath%" /verbosity:normal /m /p:Configuration=Release;Platform=x64;VcpkgTriplet=x64-windows-v141 "%rlRoot%\rl.sln" -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL \ No newline at end of file diff --git a/.scripts/find-vs2017.cmd b/.scripts/find-vs2017.cmd deleted file mode 100644 index 2e8974381..000000000 --- a/.scripts/find-vs2017.cmd +++ /dev/null @@ -1,6 +0,0 @@ -IF NOT DEFINED VsInstallDir ( - REM Try to find VS Install - FOR /f "usebackq tokens=*" %%i IN (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath`) DO ( - SET "VsInstallDir=%%i" - ) -) \ No newline at end of file diff --git a/.scripts/init-cmake.cmd b/.scripts/init-cmake.cmd deleted file mode 100644 index d325917a0..000000000 --- a/.scripts/init-cmake.cmd +++ /dev/null @@ -1,39 +0,0 @@ -REM Integration points for toolchain customization -IF NOT DEFINED msbuildPath ( - CALL %~dp0find-vs2017.cmd -) - -IF NOT DEFINED msbuildPath ( - IF NOT EXIST "%VsInstallDir%\MSBuild\15.0\Bin\MSBuild.exe" ( - IF EXIST "%VsInstallDir%\MSBuild\Current\Bin\MSBuild.exe" ( - SET "msBuildPath=%VsInstallDir%\MSBuild\Current\Bin\MSBuild.exe" - ) else ( - ECHO ERROR: MsBuild couldn't be found - EXIT /b 1 - ) - ) ELSE ( - SET "msBuildPath=%VsInstallDir%\MSBuild\15.0\Bin\MSBuild.exe" - ) -) - -IF NOT DEFINED dotnetPath ( - SET dotnetPath=dotnet -) - -IF NOT DEFINED VCPKG_ROOT ( - ECHO ERROR: VCPKG_ROOT is not configured. Cannot find vcpkg. - EXIT /b 1 -) - -SET "vcpkgPath=%VCPKG_ROOT%\vcpkg.exe" -SET "VcpkgCmake=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake" - -REM Repo-specific paths -IF NOT DEFINED rlRoot ( - SET rlRoot=%~dp0.. -) - -REM Repo-specific paths -IF NOT DEFINED vwBinaryParserRoot ( - SET vwBinaryParserRoot=%~dp0..\external_parser -) diff --git a/.scripts/init.cmd b/.scripts/init.cmd deleted file mode 100644 index 79a095133..000000000 --- a/.scripts/init.cmd +++ /dev/null @@ -1,61 +0,0 @@ -REM Integration points for toolchain customization -IF NOT DEFINED nugetPath ( - SET nugetPath=nuget -) - -IF NOT DEFINED msbuildPath ( - CALL %~dp0find-vs2017.cmd -) - -IF NOT DEFINED vstestPath ( - CALL %~dp0find-vs2017.cmd -) - -IF NOT DEFINED msbuildPath ( - IF NOT EXIST "%VsInstallDir%\MSBuild\15.0\Bin\MSBuild.exe" ( - IF EXIST "%VsInstallDir%\MSBuild\Current\Bin\MSBuild.exe" ( - SET "msBuildPath=%VsInstallDir%\MSBuild\Current\Bin\MSBuild.exe" - ) else ( - ECHO ERROR: MsBuild couldn't be found - EXIT /b 1 - ) - ) ELSE ( - SET "msBuildPath=%VsInstallDir%\MSBuild\15.0\Bin\MSBuild.exe" - ) -) - -IF NOT DEFINED vstestPath ( - IF NOT EXIST "%VsInstallDir%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" ( - ECHO ERROR: vstest.console couldn't be found - EXIT /b 1 - ) ELSE ( - SET "vstestPath=%VsInstallDir%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" - ) -) - -IF NOT DEFINED dotnetPath ( - SET dotnetPath=dotnet -) - -IF NOT DEFINED VCPKG_INSTALLATION_ROOT ( - ECHO ERROR: VCPKG_INSTALLATION_ROOT is not configured. Cannot find vcpkg. - EXIT /b 1 -) - -SET "vcpkgPath=%VCPKG_INSTALLATION_ROOT%\vcpkg.exe" -SET "VcpkgIntegration=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\msbuild\vcpkg.targets" -SET "VcpkgCmake=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake" - -REM Repo-specific paths -IF NOT DEFINED rlRoot ( - SET rlRoot=%~dp0.. -) - -REM Repo-specific paths -IF NOT DEFINED vwBinaryParserRoot ( - SET vwBinaryParserRoot=%~dp0..\external_parser -) - -IF NOT DEFINED SkipAzureFactories ( - SET "SkipAzureFactories=false" -) \ No newline at end of file diff --git a/.scripts/linux/build-valgrind.sh b/.scripts/linux/build-valgrind.sh deleted file mode 100755 index 1a81aa890..000000000 --- a/.scripts/linux/build-valgrind.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -# Build reinforcement_learning -cmake -S . -B build -G Ninja \ - -Drlclientlib_BUILD_ONNXRUNTIME_EXTENSION=On \ - -DFMT_SYS_DEP=ON \ - -DSPDLOG_SYS_DEP=ON \ - -DVCPKG_MANIFEST_MODE=Off - -# Only difference is only the rltest target is built -cmake --build build --target rltest diff --git a/.scripts/linux/build-vw-bp.sh b/.scripts/linux/build-vw-bp.sh deleted file mode 100755 index 783d9adff..000000000 --- a/.scripts/linux/build-vw-bp.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/external_parser - -# Build reinforcement_learning/external_parser -cmake -S . -B build -G Ninja \ - -DSTATIC_LINK_BINARY_PARSER=ON \ - -DFMT_SYS_DEP=ON \ - -DSPDLOG_SYS_DEP=ON \ - -DVCPKG_MANIFEST_MODE=Off - -cmake --build build --target rl_binary_parser_bin binary_parser_unit_tests diff --git a/.scripts/linux/build-with-benchmarks.sh b/.scripts/linux/build-with-benchmarks.sh deleted file mode 100755 index 555e07ce8..000000000 --- a/.scripts/linux/build-with-benchmarks.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -rm -rf build -cmake -S . -B build -G Ninja -DRL_BUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release -DVCPKG_MANIFEST_MODE=Off -cmake --build build --target rl_benchmarks \ No newline at end of file diff --git a/.scripts/linux/build.sh b/.scripts/linux/build.sh deleted file mode 100755 index 5be85182b..000000000 --- a/.scripts/linux/build.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -# Build reinforcement_learning -cmake -S . -B build -G Ninja \ - -Drlclientlib_BUILD_ONNXRUNTIME_EXTENSION=On \ - -DFMT_SYS_DEP=ON \ - -DSPDLOG_SYS_DEP=ON \ - -DVCPKG_MANIFEST_MODE=Off - -cmake --build build --target all diff --git a/.scripts/linux/clang-format.sh b/.scripts/linux/clang-format.sh index 23cb2322e..8be045248 100755 --- a/.scripts/linux/clang-format.sh +++ b/.scripts/linux/clang-format.sh @@ -13,14 +13,14 @@ REPO_DIR="$SCRIPT_DIR/../../" cd "$REPO_DIR" # For the docker commands, re-run this script inside docker container -# The ubuntu:focal image should have clang-format version 10, which matches what runs in Github Actions +# The ubuntu:jammy image should have clang-format version 14, which matches what runs in Github Actions if [[ "$1" == "docker" ]]; then DOCKER_CMD="echo 'Installing clang-format in docker container...'; apt update -qq; apt install -qq -y clang-format; cd /reinforcement_learning; ./.scripts/linux/clang-format.sh ${@:2}" if command -v podman &> /dev/null; then # podman supports --env-host for forwarding all host environment variables - podman run -it --env-host -v "$REPO_DIR:/reinforcement_learning" ubuntu:focal /bin/bash -c "$DOCKER_CMD" + podman run -it --env-host -v "$REPO_DIR:/reinforcement_learning" ubuntu:jammy /bin/bash -c "$DOCKER_CMD" elif command -v docker &> /dev/null; then - docker run -it -v "$REPO_DIR:/reinforcement_learning" ubuntu:focal /bin/bash -c "$DOCKER_CMD" + docker run -it -v "$REPO_DIR:/reinforcement_learning" ubuntu:jammy /bin/bash -c "$DOCKER_CMD" else echo "You need to install Docker (or Podman) first to use this script in docker mode" fi diff --git a/.scripts/linux/compare-benchmarks.sh b/.scripts/linux/compare-benchmarks.sh deleted file mode 100755 index f561e6a23..000000000 --- a/.scripts/linux/compare-benchmarks.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -python3 compare.py benchmarks "$1" "$2" diff --git a/.scripts/linux/install-benchmarks.sh b/.scripts/linux/install-benchmarks.sh deleted file mode 100755 index d9d3d933b..000000000 --- a/.scripts/linux/install-benchmarks.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -# installation instructions from here: https://github.com/google/benchmark - -# Check out the library. -git clone https://github.com/google/benchmark.git -# Benchmark requires Google Test as a dependency. Add the source tree as a subdirectory. -git clone https://github.com/google/googletest.git benchmark/googletest -cd benchmark/googletest -git checkout release-1.11.0 -# Go to the library root directory -cd .. -# Make a build directory to place the build output. -cmake -E make_directory "build" -# Generate build system files with cmake. -cmake -E chdir "build" cmake -DCMAKE_BUILD_TYPE=Release ../ -# Install globally -sudo cmake --build "build" --config Release --target install diff --git a/.scripts/linux/run-benchmarks.sh b/.scripts/linux/run-benchmarks.sh deleted file mode 100755 index 6f5f8f873..000000000 --- a/.scripts/linux/run-benchmarks.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -./build/benchmarks/rl_benchmarks --benchmark_repetitions=10 --benchmark_min_time=2 \ - --benchmark_report_aggregates_only=true --benchmark_format=console \ - --benchmark_out_format=json --benchmark_out="$1" diff --git a/.scripts/linux/run-clang-tidy.sh b/.scripts/linux/run-clang-tidy.sh index 6ddf3fe90..8c6df475b 100755 --- a/.scripts/linux/run-clang-tidy.sh +++ b/.scripts/linux/run-clang-tidy.sh @@ -17,8 +17,7 @@ cd "$REPO_DIR" cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On # generate flatbuffers files -cmake --build build --target fbgenerator_v1 -cmake --build build --target fbgenerator_v2 +cmake --build build --target fbgen # check that compile_commands.json was generated cd build diff --git a/.scripts/linux/test-vw-bp.sh b/.scripts/linux/test-vw-bp.sh deleted file mode 100755 index bacf16c1d..000000000 --- a/.scripts/linux/test-vw-bp.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/external_parser/build - -# Run unit test suite for external parser - -ctest --verbose --output-on-failure - -./vw --extra_metrics metrics.json -d ../unit_tests/test_files/valid_joined_logs/cb_simple.log --binary_parser --cb_explore_adf - -python3 -m json.tool metrics.json diff --git a/.scripts/linux/test.sh b/.scripts/linux/test.sh deleted file mode 100755 index 2332de05f..000000000 --- a/.scripts/linux/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/build - -# Run unit test suite - -ctest --verbose --output-on-failure diff --git a/.scripts/linux/unit-tests-valgrind.sh b/.scripts/linux/unit-tests-valgrind.sh deleted file mode 100755 index 4c0e01a24..000000000 --- a/.scripts/linux/unit-tests-valgrind.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/build - -# Run unit test suite with valgrind -valgrind --quiet --error-exitcode=100 --undef-value-errors=no --leak-check=full ./unit_test/rltest -- valgrind diff --git a/.scripts/linux/unit-tests-vw-bp-valgrind.sh b/.scripts/linux/unit-tests-vw-bp-valgrind.sh deleted file mode 100755 index af5d77402..000000000 --- a/.scripts/linux/unit-tests-vw-bp-valgrind.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/external_parser/build - -# Run unit test suite with valgrind -valgrind --quiet --error-exitcode=100 --undef-value-errors=no --leak-check=full ./unit_tests/binary_parser_unit_tests diff --git a/.scripts/macos/build-vw-bp.sh b/.scripts/macos/build-vw-bp.sh deleted file mode 100755 index 5f5ae321e..000000000 --- a/.scripts/macos/build-vw-bp.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/external_parser - -cmake -S . -B build -G Ninja -DVCPKG_MANIFEST_MODE=Off -cmake --build build --target rl_binary_parser_bin binary_parser_unit_tests diff --git a/.scripts/macos/build.sh b/.scripts/macos/build.sh deleted file mode 100755 index 110fe5b76..000000000 --- a/.scripts/macos/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ -cd $REPO_DIR - -# On MacOS CMake is unable to find brew installed OpenSSL, so we need to pass it where to look. -OPEN_SSL_DIR=`brew --prefix openssl` - -cmake -S . -B build -G Ninja -DOPENSSL_ROOT_DIR=$OPEN_SSL_DIR -DOPENSSL_LIBRARIES=$OPEN_SSL_DIR/lib -DVCPKG_MANIFEST_MODE=Off -cmake --build build --target all diff --git a/.scripts/macos/restore-vw-bp.sh b/.scripts/macos/restore-vw-bp.sh deleted file mode 100755 index dc3b335b5..000000000 --- a/.scripts/macos/restore-vw-bp.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e -set -x - -brew install cmake boost flatbuffers ninja zlib diff --git a/.scripts/macos/restore.sh b/.scripts/macos/restore.sh deleted file mode 100755 index 7f8b4c348..000000000 --- a/.scripts/macos/restore.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e -set -x - -brew install cmake boost cpprestsdk flatbuffers openssl ninja diff --git a/.scripts/macos/test-vw-bp.sh b/.scripts/macos/test-vw-bp.sh deleted file mode 100755 index c9eccda64..000000000 --- a/.scripts/macos/test-vw-bp.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/external_parser/build - -# Run unit test suite for external parser -ctest --verbose --output-on-failure diff --git a/.scripts/macos/test.sh b/.scripts/macos/test.sh deleted file mode 100755 index bd355bc11..000000000 --- a/.scripts/macos/test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -e -set -x - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR=$SCRIPT_DIR/../../ - -cd $REPO_DIR/build - -ctest --verbose --output-on-failure diff --git a/.scripts/restore-cmake.cmd b/.scripts/restore-cmake.cmd deleted file mode 100644 index b8e0e8c57..000000000 --- a/.scripts/restore-cmake.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@ECHO ON -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init-cmake.cmd -REM CD out of the repo dir as we need to avoid vcpkg recognizing the manifest -PUSHD %~dp0\..\..\ - -%vcpkgPath% install "--overlay-triplets=%rlRoot%\custom-triplets" --triplet=x64-windows-v141 --host-triplet=x64-windows-v141 zlib boost-system boost-program-options boost-test boost-align boost-foreach boost-math boost-uuid cpprestsdk flatbuffers openssl-windows -if %errorlevel% neq 0 exit /b %errorlevel% - -ECHO Restoring DotNet Tools (made simpler in .NET Core 3.0) -%dotnetPath% tool install -g dotnet-t4 --version 2.2.1 -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL diff --git a/.scripts/restore-vw-bp-deps.cmd b/.scripts/restore-vw-bp-deps.cmd deleted file mode 100644 index 8496e73be..000000000 --- a/.scripts/restore-vw-bp-deps.cmd +++ /dev/null @@ -1,19 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init.cmd -REM CD out of the repo dir as we need to avoid vcpkg recognizing the manifest -PUSHD %~dp0\..\..\ - -REM x64-windows-static-md is a community triplet that sets CRT to dynamic but all the other dependencies to static -%vcpkgPath% install "--overlay-triplets=%rlRoot%\custom-triplets" --triplet=x64-windows-static-md-v141 --host-triplet=x64-windows-static-md-v141 zlib flatbuffers boost-filesystem boost-thread boost-program-options boost-test boost-align boost-math - -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL diff --git a/.scripts/restore.cmd b/.scripts/restore.cmd deleted file mode 100644 index ead079bfd..000000000 --- a/.scripts/restore.cmd +++ /dev/null @@ -1,42 +0,0 @@ -@ECHO ON -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init.cmd -REM CD out of the repo dir as we need to avoid vcpkg recognizing the manifest -PUSHD %~dp0\..\..\ - -%vcpkgPath% install "--overlay-triplets=%rlRoot%\custom-triplets" --triplet=x64-windows-v141 --host-triplet=x64-windows-v141 cpprestsdk flatbuffers openssl-windows - -REM TODO: This really should be out-of-source (also, can we switch to vcpkg for these?) -ECHO Restoring "%rlRoot%\ext_libs\vowpal_wabbit\vowpalwabbit\packages.config" - -REM Do not remove the space at the end of the "solutionDir" specifier, as otherwise it breaks NuGet -"%nugetPath%" install -o "%rlRoot%\packages" "%rlRoot%\ext_libs\vowpal_wabbit\vowpalwabbit\packages.config" -solutionDir "%rlRoot%\ " -ECHO. - -ECHO Restoring "%rlRoot%\bindings\cs\rl.net.native\packages.config" -"%nugetPath%" install -o "%rlRoot%\packages" "%rlRoot%\bindings\cs\rl.net.native\packages.config" -ECHO. - -ECHO Restoring "%rlRoot%\rlclientlib\packages.config" -"%nugetPath%" install -o "%rlRoot%\packages" "%rlRoot%\rlclientlib\packages.config" -ECHO. - -ECHO Restoring "%rlRoot%\unit_tests\packages.config" -"%nugetPath%" install -o "%rlRoot%\packages" "%rlRoot%\unit_tests\packages.config" -ECHO. - -ECHO Restoring SDK-Style projects in "%rlRoot%\rl.sln" -%dotnetPath% restore "%rlRoot%\rl.sln" -ECHO. - -ECHO Restoring DotNet Tools (made simpler in .NET Core 3.0) -%dotnetPath% tool install -g dotnet-t4 --version 2.2.1 - -POPD - -ENDLOCAL diff --git a/.scripts/test-cmake.cmd b/.scripts/test-cmake.cmd deleted file mode 100644 index c0f285cdb..000000000 --- a/.scripts/test-cmake.cmd +++ /dev/null @@ -1,14 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init-cmake.cmd -PUSHD %rlRoot%\build - -ctest --verbose --output-on-failure -C Release -if %errorlevel% neq 0 exit /b %errorlevel% - -ENDLOCAL \ No newline at end of file diff --git a/.scripts/test-vw-bp.cmd b/.scripts/test-vw-bp.cmd deleted file mode 100644 index 6f6649ffb..000000000 --- a/.scripts/test-vw-bp.cmd +++ /dev/null @@ -1,17 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init-cmake.cmd -PUSHD %vwBinaryParserRoot%\build - -ECHO Running vw binary parser unit tests -ctest --verbose --output-on-failure -C Release -if %errorlevel% neq 0 exit /b %errorlevel% - -POPD - -ENDLOCAL \ No newline at end of file diff --git a/.scripts/test.cmd b/.scripts/test.cmd deleted file mode 100644 index 61130dbe5..000000000 --- a/.scripts/test.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@ECHO OFF -IF DEFINED DebugBuildScripts ( - @ECHO ON -) - -SETLOCAL - -CALL %~dp0init.cmd - -REM TODO: Determine how to pass test failure out of this script so it can be used by CI/CD setups - -ECHO Running RL Unit Tests -"%rlRoot%\x64\Release\unit_test.exe" -if %errorlevel% neq 0 exit /b %errorlevel% - -ECHO Running RL.Net Unit Tests -"%vstestPath%" /Platform:x64 /InIsolation "%rlRoot%\x64\Release\Rl.Net.Cli.Test.dll" /TestCaseFilter:"TestCategory!=NotOnVSO" -if %errorlevel% neq 0 exit /b %errorlevel% - -ENDLOCAL \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 81d025879..a5fd8fd98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,13 @@ endif() # Set this to on so that tooling can make use of the outputted compile commands (such as clang-tidy) set(CMAKE_EXPORT_COMPILE_COMMANDS On) +# Options that add Vcpkg package requirements +# All additions to VCPKG_MANIFEST_FEATURES list must come before project() +option(RL_BUILD_BENCHMARKS "Build benchmarks" OFF) +if(RL_BUILD_BENCHMARKS) + list(APPEND VCPKG_MANIFEST_FEATURES "benchmarks") +endif() + project(reinforcement_learning) # Add support for building library with latest version of C++ supported by your compiler @@ -45,7 +52,6 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(RL_BUILD_PYTHON "Build the Python bindings" OFF) option(RL_USE_ZSTD "Whether to enable usage of zstandard compression" ON) option(RL_STATIC_DEPS "Only use static dependencies" OFF) -option(RL_BUILD_BENCHMARKS "Build benchmarks" OFF) option(vw_USE_AZURE_FACTORIES "Whether to compile with the azure factories components" ON) option(RL_OPENSSL_SYS_DEP "Use system-wide openssl library" ON) option(RL_CPPRESTSDK_SYS_DEP "Use system-wide cpprestsdk library" ON) @@ -53,6 +59,14 @@ option(RL_BUILD_NUGET "Build the Nuget package" OFF) option(RL_BUILD_EXTERNAL_PARSER "Build external parser version of VW" OFF) option(RL_USE_ASAN "Compile with AddressSanitizer" OFF) option(RL_USE_UBSAN "Compile with UndefinedBehaviorSanitizer" OFF) +option(RL_BUILD_FEDERATION "Build code for Federated Learning" ON) +option(rlclientlib_BUILD_ONNXRUNTIME_EXTENSION "Build OnnxRuntime Inference Extension" OFF) + +if(RL_BUILD_FEDERATION) + add_compile_definitions(RL_BUILD_FEDERATION) + # Needed for joiner code + set(RL_BUILD_EXTERNAL_PARSER ON CACHE BOOL "" FORCE) +endif() if(RL_USE_ASAN) add_compile_definitions(RL_USE_ASAN VW_USE_ASAN) @@ -72,11 +86,21 @@ if(RL_USE_UBSAN) if(MSVC) message(FATAL_ERROR "UBSan not supported on MSVC") else() - add_compile_options(-fsanitize=undefined -fno-sanitize-recover -fno-omit-frame-pointer -g3) - add_link_options(-fsanitize=undefined -fno-sanitize-recover -fno-omit-frame-pointer -g3) + # Flatbuffers gives errors with misaligned pointer sanitization, so disable it + add_compile_options(-fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover -fno-omit-frame-pointer -g3) + add_link_options(-fsanitize=undefined -fno-sanitize=alignment -fno-sanitize-recover -fno-omit-frame-pointer -g3) endif() endif() +if(UNIX AND NOT APPLE) + # Temporary workaround for VW 9.6 missing size_t on Linux builds + # TODO remove when VW submodule is updated + # This must be stddef.h and not cstddef because it also applies to C code + add_compile_options(-include stddef.h) + # Enable C++17 math functions in C++11 + add_compile_definitions(__STDCPP_WANT_MATH_SPEC_FUNCS__) +endif() + # For Nuget package don't use system libraries, use vendored ones in ext_libs instead if(RL_BUILD_NUGET) set(RAPIDJSON_SYS_DEP OFF CACHE BOOL "" FORCE) @@ -122,13 +146,13 @@ include(GNUInstallDirs) include(ext_libs/ext_libs.cmake) -add_subdirectory(rlclientlib) -add_subdirectory(rlclientlib/extensions) - if(RL_BUILD_EXTERNAL_PARSER) add_subdirectory(external_parser) endif() +add_subdirectory(rlclientlib) +add_subdirectory(rlclientlib/extensions) + add_subdirectory(examples) add_subdirectory(test_tools/joiner) add_subdirectory(test_tools/sender_test) @@ -146,6 +170,13 @@ if(RL_BUILD_BENCHMARKS) add_subdirectory(benchmarks) endif() +# Add a target to generate all flatbuffer header files, used for clang-tidy +add_custom_target(fbgen) +add_dependencies(fbgen fbgenerator_v1 fbgenerator_v2) +if(TARGET fbgen_external_parser) + add_dependencies(fbgen fbgen_external_parser) +endif() + # Add the nuget subdirectory last if(RL_BUILD_NUGET) if(WIN32) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 70ad1dc3e..7e0dbddf9 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -2,6 +2,7 @@ set(all_sources benchmark_main.cc benchmarks_common.cc benchmark_cb_v2.cc + benchmark_ccb.cc ) add_executable(rl_benchmarks diff --git a/benchmarks/benchmark_cb_v2.cc b/benchmarks/benchmark_cb_v2.cc index 1f4248bdd..d3993c417 100644 --- a/benchmarks/benchmark_cb_v2.cc +++ b/benchmarks/benchmark_cb_v2.cc @@ -51,8 +51,8 @@ static void bench_cb(benchmark::State& state, ExtraArgs&&... extra_args) config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); - config.set(r::name::INTERACTION_FILE_NAME, "/dev/null"); - config.set(r::name::OBSERVATION_FILE_NAME, "/dev/null"); + config.set(r::name::INTERACTION_FILE_NAME, r::DEV_NULL); + config.set(r::name::OBSERVATION_FILE_NAME, r::DEV_NULL); config.set(r::name::MODEL_BACKGROUND_REFRESH, "false"); // config.set(r::name::MODEL_IMPLEMENTATION, r::value::PASSTHROUGH_PDF_MODEL); config.set(r::name::VW_POOL_INIT_SIZE, "1"); @@ -91,7 +91,10 @@ static void bench_cb(benchmark::State& state, ExtraArgs&&... extra_args) // x number of total examples so x number of total rank calls to benchmark // compression (on/off) // dedup (on/off) -BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload, 20, 10, 50, 2000, 500, false, false); -BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_compression, 20, 10, 50, 2000, 500, true, false); -BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_dedup, 20, 10, 50, 2000, 500, false, true); -BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_compression_dedup, 20, 10, 50, 2000, 500, true, true); \ No newline at end of file +BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload, 20, 10, 50, 2000, 500, false, false)->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_compression, 20, 10, 50, 2000, 500, true, false) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_dedup, 20, 10, 50, 2000, 500, false, true) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_cb, non_dedupable_payload_compression_dedup, 20, 10, 50, 2000, 500, true, true) + ->Unit(benchmark::kMillisecond); diff --git a/benchmarks/benchmark_ccb.cc b/benchmarks/benchmark_ccb.cc new file mode 100644 index 000000000..50cbc6053 --- /dev/null +++ b/benchmarks/benchmark_ccb.cc @@ -0,0 +1,139 @@ +#include "api_status.h" +#include "benchmarks_common.h" +#include "config_utility.h" +#include "constants.h" +#include "err_constants.h" +#include "factory_resolver.h" +#include "live_model.h" +#include "model_mgmt.h" +#include "multi_slot_response.h" +#include "vw/config/options_cli.h" +#include "vw/core/parse_example_json.h" +#include "vw/core/parse_primitives.h" +#include "vw/core/vw.h" + +#include + +#include +#include +#include +#include + +namespace r = reinforcement_learning; +namespace u = reinforcement_learning::utility; +namespace m = reinforcement_learning::model_management; +namespace err = reinforcement_learning::error_code; +namespace cfg = reinforcement_learning::utility::config; + +template +static void bench_ccb(benchmark::State& state, ExtraArgs&&... extra_args) +{ + int res[sizeof...(extra_args)] = {extra_args...}; + auto count = res[0]; + auto shared_features_size = res[1]; + auto shared_features_count = res[2]; + auto action_features_size = res[3]; + auto action_features_count = res[4]; + auto actions_per_example = res[5]; + auto slots_per_example = res[6]; + auto total_actions = res[7]; + bool compression = res[8]; + bool dedup = res[9]; + + // generate examples + ccb_decision_gen ccb_gen(shared_features_size, shared_features_count, action_features_size, action_features_count, + actions_per_example, slots_per_example, total_actions, 0); + std::vector examples; + std::generate_n(std::back_inserter(examples), count, [&ccb_gen] { return ccb_gen.gen_example(); }); + + // set up rlclientlib config + u::configuration config; + config.set(r::name::APP_ID, "bench_ccb"); + config.set(r::name::INITIAL_EPSILON, "0"); + config.set(r::name::MODEL_VW_INITIAL_COMMAND_LINE, "--ccb_explore_adf --quiet -q ::"); + config.set(r::name::PROTOCOL_VERSION, "2"); + config.set(r::name::EH_TEST, "true"); + config.set(r::name::MODEL_SRC, r::value::FILE_MODEL_DATA); + config.set(r::name::MODEL_FILE_MUST_EXIST, "true"); + config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); + config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); + config.set(r::name::INTERACTION_FILE_NAME, r::DEV_NULL); + config.set(r::name::OBSERVATION_FILE_NAME, r::DEV_NULL); + config.set(r::name::MODEL_BACKGROUND_REFRESH, "false"); + config.set(r::name::VW_POOL_INIT_SIZE, "1"); + config.set(r::name::INTERACTION_USE_COMPRESSION, compression ? "true" : "false"); + config.set(r::name::INTERACTION_USE_DEDUP, dedup ? "true" : "false"); + config.set("queue.mode", "BLOCK"); + + std::string model_output_filename = std::tmpnam(nullptr); + config.set(r::name::MODEL_FILE_NAME, model_output_filename.c_str()); + + // train a VW model and save to file + { + std::string command_line = config.get(r::name::MODEL_VW_INITIAL_COMMAND_LINE, nullptr); + auto opts = + std::unique_ptr(new VW::config::options_cli(VW::split_command_line(command_line))); + auto vw = VW::initialize_experimental(std::move(opts)); + + std::vector examples_vec; + for (auto&& str : examples) + { + VW::multi_ex ex; + ex.push_back(VW::new_unused_example(*vw)); + line_to_examples_json(vw.get(), str.c_str(), str.size(), ex); + examples_vec.push_back(ex); + } + + for (auto&& ex : examples_vec) + { + VW::setup_examples(*vw, ex); + vw->learn(ex); + vw->finish_example(ex); + } + + io_buf io_buffer; + auto backing_buffer = std::make_shared>(); + io_buffer.add_file(VW::io::create_vector_writer(backing_buffer)); + VW::save_predictor(*vw, io_buffer); + std::fstream out_file(model_output_filename, std::ios::out | std::ios::binary); + out_file.write(backing_buffer->data(), backing_buffer->size()); + } + + // initialize live_model + r::api_status status; + r::live_model model(config); + model.init(&status); + + // run benchmark + r::multi_slot_response response; + for (auto _ : state) + { + for (size_t i = 0; i < count; i++) + { + if (model.request_multi_slot_decision(examples[i].c_str(), response, &status) != err::success) + { + std::cout << "there was an error so something went wrong during " + "benchmarking: " + << status.get_error_msg() << std::endl; + } + } + benchmark::ClobberMemory(); + } + + // delete model file + std::remove(model_output_filename.c_str()); +} + +BENCHMARK_CAPTURE(bench_ccb, ccb_adf_diff_char_interactions_predict, + 1, // number of examples + 30, // shared_feats_size + 20, // shared_feats_count (actual number of shared features in example) + 30, // action_feats_size + 20, // action_feats_count + 30, // actions_per_example + 20, // slots_per_example + 2000, // total actions to choose from + false, // compression + false // dedup + ) + ->Unit(benchmark::kMillisecond); diff --git a/benchmarks/benchmarks_common.cc b/benchmarks/benchmarks_common.cc index efd194aee..9bb4de7d0 100644 --- a/benchmarks/benchmarks_common.cc +++ b/benchmarks/benchmarks_common.cc @@ -4,15 +4,10 @@ #include -prng::prng(uint64_t initial_seed) : val(merand48(initial_seed)) {} - -uint64_t prng::next_uint() +namespace { - merand48(val); - return val; -} - -std::string cb_decision_gen::mk_feature_vector(int count, uint32_t max_idx) +// Helper functions +std::string make_feature_vector(int count, uint32_t max_idx, prng& rand) { std::ostringstream str; str << "{"; @@ -34,27 +29,12 @@ std::string cb_decision_gen::mk_feature_vector(int count, uint32_t max_idx) return str.str(); } -cb_decision_gen::cb_decision_gen(int shared_features, int action_features, int actions_per_decision, int total_actions, - int initial_seed, bool passthrough) - : shared_features(shared_features) - , action_features(action_features) - , actions_per_decision(actions_per_decision) - , rand(initial_seed) - , passthrough(passthrough) -{ - for (int i = 0; i < total_actions; ++i) - { actions_set.push_back(mk_feature_vector(action_features, action_features * 3)); } -} - -std::string cb_decision_gen::gen_example() +std::string make_actions_vector(int count, const std::vector& actions_set, prng& rand) { std::ostringstream str; - str << R"({"shared":)"; - str << mk_feature_vector(shared_features, shared_features * 3) << ","; - str << R"("_multi":[)"; std::set added_actions; int added = 0; - while (added < actions_per_decision) + while (added < count) { auto idx = rand.next_uint() % actions_set.size(); if (added_actions.find(idx) == added_actions.end()) @@ -68,20 +48,94 @@ std::string cb_decision_gen::gen_example() ++added; } } + return str.str(); +} +} // namespace + +prng::prng(uint64_t initial_seed) : val(merand48(initial_seed)) {} + +uint64_t prng::next_uint() +{ + merand48(val); + return val; +} + +cb_decision_gen::cb_decision_gen(int shared_features, int action_features, int actions_per_decision, int total_actions, + int initial_seed, bool passthrough) + : shared_features(shared_features) + , action_features(action_features) + , actions_per_decision(actions_per_decision) + , rand(initial_seed) + , passthrough(passthrough) +{ + for (int i = 0; i < total_actions; ++i) + { + actions_set.push_back(make_feature_vector(action_features, action_features * 3, rand)); + } +} + +std::string cb_decision_gen::gen_example() +{ + std::ostringstream str; + str << "{"; + + str << R"("shared":)"; + str << make_feature_vector(shared_features, shared_features * 3, rand); + str << ","; + + str << R"("_multi":[)"; + str << make_actions_vector(actions_per_decision, actions_set, rand); + str << "]"; if (passthrough) { - str << R"(])"; str << R"(, "_p":[)"; str << (1 / actions_per_decision); for (size_t i = 1; i < actions_per_decision; i++) { str << "," << (1 / actions_per_decision); } - str << R"(]})"; + str << "]"; + } + + str << "}"; + return str.str(); +} + +ccb_decision_gen::ccb_decision_gen(int shared_features_size, int shared_features_count, int action_features_size, + int action_features_count, int actions_per_example, int slots_per_example, int total_actions, int initial_seed) + : shared_features_size(shared_features_size) + , shared_features_count(shared_features_count) + , action_features_size(action_features_size) + , action_features_count(action_features_count) + , actions_per_example(actions_per_example) + , slots_per_example(slots_per_example) + , rand(initial_seed) +{ + for (int i = 0; i < total_actions; ++i) + { + actions_set.push_back(make_feature_vector(action_features_count, action_features_size, rand)); } - else +} + +std::string ccb_decision_gen::gen_example() +{ + std::ostringstream str; + str << "{"; + + str << R"("shared":)"; + str << make_feature_vector(shared_features_count, shared_features_size, rand); + str << ","; + + str << R"("_multi":[)"; + str << make_actions_vector(actions_per_example, actions_set, rand); + str << "],"; + + str << R"("_slots":[)"; + for (int i = 0; i < slots_per_example; i++) { - str << R"(]})"; + if (i > 0) { str << ","; } + str << make_feature_vector(action_features_count, action_features_size, rand); } - temp_str = str.str(); + str << "]"; - return temp_str; + str << "}"; + return str.str(); } diff --git a/benchmarks/benchmarks_common.h b/benchmarks/benchmarks_common.h index 17c30dd5e..7407f4b04 100644 --- a/benchmarks/benchmarks_common.h +++ b/benchmarks/benchmarks_common.h @@ -16,14 +16,30 @@ class cb_decision_gen int shared_features, action_features, actions_per_decision; std::vector actions_set; prng rand; - std::string temp_str; bool passthrough; - std::string mk_feature_vector(int count, uint32_t max_idx); - public: cb_decision_gen(int shared_features, int action_features, int actions_per_decision, int total_actions, int initial_seed, bool passthrough); std::string gen_example(); }; + +class ccb_decision_gen +{ + int shared_features_size; // size of set of possible features to choose from + int shared_features_count; // actual number of features per example + int action_features_size; + int action_features_count; + int actions_per_example; + int slots_per_example; + + std::vector actions_set; + prng rand; + +public: + ccb_decision_gen(int shared_features_size, int shared_features_count, int action_features_size, + int action_features_count, int actions_per_example, int slots_per_example, int total_actions, int initial_seed); + + std::string gen_example(); +}; diff --git a/bindings/cs/rl.net.native/rl.net.factory_context.cc b/bindings/cs/rl.net.native/rl.net.factory_context.cc index a4b43c11e..467fb5f52 100644 --- a/bindings/cs/rl.net.native/rl.net.factory_context.cc +++ b/bindings/cs/rl.net.native/rl.net.factory_context.cc @@ -54,7 +54,8 @@ API void SetFactoryContextBindingSenderFactory( factory_context_t* context, rl_net_native::sender_create_fn create_fn, rl_net_native::sender_vtable vtable) { auto sender_factory_fn = [=](i_sender** retval, const utility::configuration& configuration, - error_callback_fn* error_callback, i_trace* trace_logger, api_status* status) { + error_callback_fn* error_callback, i_trace* trace_logger, api_status* status) + { void* managed_handle = create_fn(&configuration, invoke_error_callback, error_callback); *retval = new rl_net_native::binding_sender(managed_handle, vtable, trace_logger); diff --git a/bindings/cs/rl.net.native/rl.net.live_model.cc b/bindings/cs/rl.net.native/rl.net.live_model.cc index 7260e30b4..88e4b38e5 100644 --- a/bindings/cs/rl.net.native/rl.net.live_model.cc +++ b/bindings/cs/rl.net.native/rl.net.live_model.cc @@ -26,10 +26,11 @@ API livemodel_context_t* CreateLiveModel( // Create a trace log factory by passing in below creator. It allows LiveModel to use trace_logger provided by user. const auto binding_tracer_create = [context](reinforcement_learning::i_trace** retval, const reinforcement_learning::utility::configuration& cfg, - reinforcement_learning::i_trace* trace_logger, reinforcement_learning::api_status* status) { - *retval = new rl_net_native::binding_tracer(*context); - return reinforcement_learning::error_code::success; - }; + reinforcement_learning::i_trace* trace_logger, reinforcement_learning::api_status* status) + { + *retval = new rl_net_native::binding_tracer(*context); + return reinforcement_learning::error_code::success; + }; // TODO: Unify this factory projection and the sender_factory projection in FactoryContext. reinforcement_learning::trace_logger_factory_t* trace_logger_factory = @@ -41,7 +42,9 @@ API livemodel_context_t* CreateLiveModel( // This is a clone of cleanup_trace_logger_factory std::swap(trace_logger_factory, factory_context->trace_logger_factory); if (trace_logger_factory != nullptr && trace_logger_factory != &reinforcement_learning::trace_logger_factory) - { delete trace_logger_factory; } + { + delete trace_logger_factory; + } // Set TRACE_LOG_IMPLEMENTATION configuration to use trace logger. config->set(reinforcement_learning::name::TRACE_LOG_IMPLEMENTATION, rl_net_native::constants::BINDING_TRACE_LOGGER); @@ -75,7 +78,9 @@ API int LiveModelChooseRank(livemodel_context_t* context, const char* event_id, int context_json_size, reinforcement_learning::ranking_response* resp, reinforcement_learning::api_status* status) { if (event_id == nullptr) - { return context->livemodel->choose_rank({context_json, static_cast(context_json_size)}, *resp, status); } + { + return context->livemodel->choose_rank({context_json, static_cast(context_json_size)}, *resp, status); + } return context->livemodel->choose_rank( event_id, {context_json, static_cast(context_json_size)}, *resp, status); diff --git a/bindings/python/py_api.cc b/bindings/python/py_api.cc index 068164fa2..889c2d6ba 100644 --- a/bindings/python/py_api.cc +++ b/bindings/python/py_api.cc @@ -77,12 +77,13 @@ static PyGetSetDef RLException_getsetters[] = { static PyObject* PyRLException; -#define THROW_IF_FAIL(x) \ - do \ - { \ - int retval__LINE__ = (x); \ - if (retval__LINE__ != rl::error_code::success) \ - { throw rl_exception(status.get_error_msg(), status.get_error_code()); } \ +#define THROW_IF_FAIL(x) \ + do { \ + int retval__LINE__ = (x); \ + if (retval__LINE__ != rl::error_code::success) \ + { \ + throw rl_exception(status.get_error_msg(), status.get_error_code()); \ + } \ } while (0) struct error_callback_context @@ -130,19 +131,21 @@ PYBIND11_MODULE(rl_client, m) m.add_object("RLException", py::handle(PyRLException)); } - py::register_exception_translator([](std::exception_ptr p) { - try - { - if (p) { std::rethrow_exception(p); } - } - catch (rl_exception& e) - { - py::tuple args(2); - args[0] = e.what(); - args[1] = e.error_code(); - PyErr_SetObject(PyRLException, args.ptr()); - } - }); + py::register_exception_translator( + [](std::exception_ptr p) + { + try + { + if (p) { std::rethrow_exception(p); } + } + catch (rl_exception& e) + { + py::tuple args(2); + args[0] = e.what(); + args[1] = e.error_code(); + PyErr_SetObject(PyRLException, args.ptr()); + } + }); py::class_(m, "Configuration", R"pbdoc( Container class for all configuration values. Generally is constructed from client.json file read from disk @@ -159,24 +162,29 @@ PYBIND11_MODULE(rl_client, m) .def("set", &rl::utility::configuration::set); py::class_(m, "LiveModel") - .def(py::init([](const rl::utility::configuration& config) { - auto live_model = std::unique_ptr(new rl::live_model(config)); - rl::api_status status; - THROW_IF_FAIL(live_model->init(&status)); - return live_model; - }), + .def(py::init( + [](const rl::utility::configuration& config) + { + auto live_model = std::unique_ptr(new rl::live_model(config)); + rl::api_status status; + THROW_IF_FAIL(live_model->init(&status)); + return live_model; + }), py::arg("config")) - .def( - py::init([](const rl::utility::configuration& config, std::function callback) { - auto live_model = std::unique_ptr(new live_model_with_callback(config, callback)); - rl::api_status status; - THROW_IF_FAIL(live_model->init(&status)); - return live_model; - }), + .def(py::init( + [](const rl::utility::configuration& config, std::function callback) + { + auto live_model = + std::unique_ptr(new live_model_with_callback(config, callback)); + rl::api_status status; + THROW_IF_FAIL(live_model->init(&status)); + return live_model; + }), py::arg("config"), py::arg("callback")) .def( "choose_rank", - [](rl::live_model& lm, const char* context, const char* event_id, bool deferred) { + [](rl::live_model& lm, const char* context, const char* event_id, bool deferred) + { rl::ranking_response response; rl::api_status status; unsigned int flags = deferred ? rl::action_flags::DEFERRED : rl::action_flags::DEFAULT; @@ -191,7 +199,8 @@ PYBIND11_MODULE(rl_client, m) )pbdoc") .def( "choose_rank", - [](rl::live_model& lm, const char* context, bool deferred) { + [](rl::live_model& lm, const char* context, bool deferred) + { rl::ranking_response response; rl::api_status status; unsigned int flags = deferred ? rl::action_flags::DEFERRED : rl::action_flags::DEFAULT; @@ -207,7 +216,8 @@ PYBIND11_MODULE(rl_client, m) .def( "request_episodic_decision", [](rl::live_model& lm, const char* event_id, const char* previous_id, const char* context, - rl::episode_state& episode) { + rl::episode_state& episode) + { rl::ranking_response response; rl::api_status status; THROW_IF_FAIL(lm.request_episodic_decision( @@ -217,36 +227,42 @@ PYBIND11_MODULE(rl_client, m) py::arg("event_id"), py::arg("previous_id"), py::arg("context"), py::arg("episode")) .def( "report_action_taken", - [](rl::live_model& lm, const char* event_id) { + [](rl::live_model& lm, const char* event_id) + { rl::api_status status; THROW_IF_FAIL(lm.report_action_taken(event_id, &status)); }, py::arg("event_id")) .def( "report_outcome", - [](rl::live_model& lm, const char* event_id, const char* outcome) { + [](rl::live_model& lm, const char* event_id, const char* outcome) + { rl::api_status status; THROW_IF_FAIL(lm.report_outcome(event_id, outcome, &status)); }, py::arg("event_id"), py::arg("outcome")) .def( "report_outcome", - [](rl::live_model& lm, const char* event_id, float outcome) { + [](rl::live_model& lm, const char* event_id, float outcome) + { rl::api_status status; THROW_IF_FAIL(lm.report_outcome(event_id, outcome, &status)); }, py::arg("event_id"), py::arg("outcome")) .def( "report_outcome", - [](rl::live_model& lm, const char* episode_id, const char* event_id, float outcome) { + [](rl::live_model& lm, const char* episode_id, const char* event_id, float outcome) + { rl::api_status status; THROW_IF_FAIL(lm.report_outcome(episode_id, event_id, outcome, &status)); }, py::arg("episode_id"), py::arg("event_id"), py::arg("outcome")) - .def("refresh_model", [](rl::live_model& lm) { - rl::api_status status; - THROW_IF_FAIL(lm.refresh_model(&status)); - }); + .def("refresh_model", + [](rl::live_model& lm) + { + rl::api_status status; + THROW_IF_FAIL(lm.refresh_model(&status)); + }); py::class_(m, "RankingResponse") .def_property_readonly( @@ -258,7 +274,8 @@ PYBIND11_MODULE(rl_client, m) )pbdoc") .def_property_readonly( "chosen_action_id", - [](const rl::ranking_response& a) { + [](const rl::ranking_response& a) + { size_t chosen_action; // todo handle failure a.get_chosen_action_id(chosen_action); @@ -278,10 +295,13 @@ PYBIND11_MODULE(rl_client, m) )pbdoc") .def_property_readonly( "actions_probabilities", - [](const rl::ranking_response& a) { + [](const rl::ranking_response& a) + { py::list return_values; for (const auto& action_prob : a) - { return_values.append(py::make_tuple(action_prob.action_id, action_prob.probability)); } + { + return_values.append(py::make_tuple(action_prob.action_id, action_prob.probability)); + } return return_values; }, R"pbdoc( @@ -297,7 +317,8 @@ PYBIND11_MODULE(rl_client, m) m.def( "create_config_from_json", - [](const std::string& config_json) { + [](const std::string& config_json) + { rl::utility::configuration config; rl::api_status status; THROW_IF_FAIL(rl::utility::config::create_from_json(config_json, config, nullptr, &status)); diff --git a/cmake/Modules/FindOnnxRuntime.cmake b/cmake/Modules/FindOnnxRuntime.cmake index 7b8e444bc..ce3d3542a 100644 --- a/cmake/Modules/FindOnnxRuntime.cmake +++ b/cmake/Modules/FindOnnxRuntime.cmake @@ -1,6 +1,7 @@ include(FindPackageHandleStandardArgs) # On Linux this should find the .so and on Windows this should find the .lib import for the dll +message("Finding ONNX - search path ${ONNXRUNTIME_ROOT}") find_library(ONNXRUNTIME_LIB NAMES onnxruntime PATHS "${ONNXRUNTIME_ROOT}" @@ -23,25 +24,33 @@ if(WIN32) endif() find_path(ONNXRUNTIME_INCLUDE_DIR_ROOT - NAMES include/onnxruntime/core/session + NAMES "onnxruntime_cxx_api.h" PATHS "${ONNXRUNTIME_ROOT}" - PATH_SUFFIXES include + PATH_SUFFIXES + "include/onnxruntime/core/session" + "include/onnxruntime/core" + "include/onnxruntime" + "include" ) # There may be many files names version.h in the search path. So we find the one # with the expected preprocessor definition ONNXRUNTIME_VERSION_STRING and then # read the version file from that one. -file(GLOB_RECURSE FOUND_VERSION_FILES "${ONNXRUNTIME_INCLUDE_DIR_ROOT}/**/version.h") -foreach(VERSION_FILE ${FOUND_VERSION_FILES}) - file(READ ${VERSION_FILE} VERSION_FILE_CONTENTS) - string(FIND "${VERSION_FILE_CONTENTS}" "ONNXRUNTIME_VERSION_STRING" FIND_RESULT) +if(EXISTS "${ONNXRUNTIME_ROOT}/VERSION_NUMBER") + file(STRINGS "${ONNXRUNTIME_ROOT}/VERSION_NUMBER" ONNXRUNTIME_VERSION LIMIT_COUNT 1) +else() + file(GLOB_RECURSE FOUND_VERSION_FILES "${ONNXRUNTIME_INCLUDE_DIR_ROOT}/**/version.h") + foreach(VERSION_FILE ${FOUND_VERSION_FILES}) + file(READ ${VERSION_FILE} VERSION_FILE_CONTENTS) + string(FIND "${VERSION_FILE_CONTENTS}" "ONNXRUNTIME_VERSION_STRING" FIND_RESULT) - if(NOT ${FIND_RESULT} EQUAL -1) - string(REGEX MATCH "\"(.*)\"" ONNX_VERSION_OUTPUT_UNUSED ${VERSION_FILE_CONTENTS}) - set(ONNXRUNTIME_VERSION ${CMAKE_MATCH_1}) - break() - endif () -endforeach() + if(NOT ${FIND_RESULT} EQUAL -1) + string(REGEX MATCH "\"(.*)\"" ONNX_VERSION_OUTPUT_UNUSED ${VERSION_FILE_CONTENTS}) + set(ONNXRUNTIME_VERSION ${CMAKE_MATCH_1}) + break() + endif () + endforeach() +endif() # The ${ONNXRUNTIME_REQUIRED_IMPLIB_VAR_NAME} is intentionally different to the # others as the value of this variable is the name of the implib var IF it is @@ -57,7 +66,7 @@ if(ONNXRUNTIME_FOUND) IMPORTED_LOCATION "${ONNXRUNTIME_LIB}" ) - set(ONNXRUNTIME_INCLUDE_DIRS "${ONNXRUNTIME_INCLUDE_DIR_ROOT}/include/onnxruntime") + set(ONNXRUNTIME_INCLUDE_DIRS "${ONNXRUNTIME_INCLUDE_DIR_ROOT}") target_include_directories(onnxruntime INTERFACE ${ONNXRUNTIME_INCLUDE_DIRS}) endif() diff --git a/examples/onnx/onnx_example.cc b/examples/onnx/onnx_example.cc index 775584373..ab4d7aa15 100644 --- a/examples/onnx/onnx_example.cc +++ b/examples/onnx/onnx_example.cc @@ -14,8 +14,7 @@ namespace u = reinforcement_learning::utility; void logging_error_fn(const r::api_status& status, void*) { std::cerr << status.get_error_msg() << std::endl; } #define CHECK_EXIT(x) \ - do \ - { \ + do { \ int retval__LINE__ = (x); \ if (retval__LINE__ != 0) \ { \ diff --git a/examples/override_interface/override_interface.cc b/examples/override_interface/override_interface.cc index ed58ae874..1a3331f8f 100644 --- a/examples/override_interface/override_interface.cc +++ b/examples/override_interface/override_interface.cc @@ -78,7 +78,8 @@ int main() // Define a create function to be used in the factory. auto const create_ostream_sender_fn = [&](r::i_sender** retval, const u::configuration&, - r::error_callback_fn* error_callback, r::i_trace* trace, r::api_status*) { + r::error_callback_fn* error_callback, r::i_trace* trace, r::api_status*) + { *retval = new ostream_sender(std::cout, cout_mutex); return err::success; }; diff --git a/examples/rl_sim_cpp/local_loop.cc b/examples/rl_sim_cpp/local_loop.cc index 298467633..0da4b4082 100644 --- a/examples/rl_sim_cpp/local_loop.cc +++ b/examples/rl_sim_cpp/local_loop.cc @@ -73,7 +73,9 @@ int local_model::init( const reinforcement_learning::utility::configuration& config, reinforcement_learning::api_status* status) { if (config.get_int(rl::name::PROTOCOL_VERSION, 999) != 2) - { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; } + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; + } if (!_vw_model) { @@ -106,10 +108,14 @@ int local_model::v_send(const buffer& data, reinforcement_learning::api_status* auto result = res->Verify(verifier); if (!result) - { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; } + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; + } if (res->metadata()->content_encoding()->str() != "IDENTITY") - { RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; } + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; + } for (auto message : *res->events()) { diff --git a/examples/rl_sim_cpp/rl_sim.cc b/examples/rl_sim_cpp/rl_sim.cc index 75e689bbc..8140c981c 100644 --- a/examples/rl_sim_cpp/rl_sim.cc +++ b/examples/rl_sim_cpp/rl_sim.cc @@ -191,7 +191,9 @@ int rl_sim::ca_loop() const auto chosen_action = response.get_chosen_action(); const auto outcome = joint.get_outcome(chosen_action, _random_seed); if (_rl->report_outcome(req_id.c_str(), outcome, &status) != err::success && outcome > 0.00001f) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } stats.record(joint.id(), chosen_action, outcome); @@ -444,14 +446,16 @@ int rl_sim::init_rl() auto* raw_local_model = _local_model.get(); r::data_transport_factory.register_type("LOCAL_MODEL", [raw_local_model](r::model_management::i_data_transport** retval, const r::utility::configuration& config, - r::i_trace* trace_logger, r::api_status* status) { + r::i_trace* trace_logger, r::api_status* status) + { raw_local_model->set_trace_logger(trace_logger); *retval = new local_model_proxy(raw_local_model); return 0; }); r::sender_factory.register_type("LOCAL_MODEL", [raw_local_model](r::i_sender** retval, const r::utility::configuration& config, r::error_callback_fn* error_cb, - r::i_trace* trace_logger, r::api_status* status) { + r::i_trace* trace_logger, r::api_status* status) + { raw_local_model->set_trace_logger(trace_logger); *retval = new local_model_proxy(raw_local_model); return 0; @@ -530,7 +534,9 @@ std::string rl_sim::get_action_features() // R"("_multi": [ { "TAction":{"topic":"HerbGarden"} }, { "TAction":{"topic":"MachineLearning"} } ])"; oss << R"("_multi": [ )"; for (auto idx = 0; idx < _topics.size() - 1; ++idx) - { oss << R"({ "TAction":{"topic":")" << _topics[idx] << R"("} }, )"; } + { + oss << R"({ "TAction":{"topic":")" << _topics[idx] << R"("} }, )"; + } oss << R"({ "TAction":{"topic":")" << _topics.back() << R"("} } ])"; return oss.str(); } @@ -602,21 +608,12 @@ std::string rl_sim::create_event_id() return oss.str(); } -rl_sim::rl_sim(boost::program_options::variables_map vm) : _options(std::move(vm)), _loop_kind(CB) +rl_sim::rl_sim(const boost::program_options::variables_map& vm) : _options(vm), _loop_kind(CB) { if (_options["ccb"].as()) { _loop_kind = CCB; } - else if (_options["slates"].as()) - { - _loop_kind = Slates; - } - else if (_options["ca"].as()) - { - _loop_kind = CA; - } - else if (_options["multistep"].as()) - { - _loop_kind = Multistep; - } + else if (_options["slates"].as()) { _loop_kind = Slates; } + else if (_options["ca"].as()) { _loop_kind = CA; } + else if (_options["multistep"].as()) { _loop_kind = Multistep; } _num_events = _options["num_events"].as(); _random_seed = _options["random_seed"].as(); diff --git a/examples/rl_sim_cpp/rl_sim.h b/examples/rl_sim_cpp/rl_sim.h index adf87f4dd..44afa69b3 100644 --- a/examples/rl_sim_cpp/rl_sim.h +++ b/examples/rl_sim_cpp/rl_sim.h @@ -27,7 +27,7 @@ class rl_sim * * @param vm User defined options */ - explicit rl_sim(boost::program_options::variables_map vm); + explicit rl_sim(const boost::program_options::variables_map& vm); /** * @brief Simulation loop diff --git a/examples/test_cpp/options.cc b/examples/test_cpp/options.cc index ae31b732b..fee5c193f 100644 --- a/examples/test_cpp/options.cc +++ b/examples/test_cpp/options.cc @@ -35,5 +35,7 @@ po::variables_map process_cmd_line(const int argc, char** argv) void throw_if_conflicting(const po::variables_map& vm, const std::string& first, const std::string& second) { if ((vm.count(first) != 0u) && !vm[first].defaulted() && (vm.count(second) != 0u) && !vm[second].defaulted()) - { throw std::logic_error(std::string("Conflicting options '") + first + "' and '" + second + "'."); } + { + throw std::logic_error(std::string("Conflicting options '") + first + "' and '" + second + "'."); + } } diff --git a/examples/test_cpp/test_data_provider.cc b/examples/test_cpp/test_data_provider.cc index 2c2e2c178..8ba7b3829 100644 --- a/examples/test_cpp/test_data_provider.cc +++ b/examples/test_cpp/test_data_provider.cc @@ -117,10 +117,7 @@ std::string test_data_provider::create_context_json(const std::string& cntxt, co { std::ostringstream oss; if (ccb) { oss << cntxt << ", " << action; } - else - { - oss << "{ " << cntxt << ", " << action << " }"; - } + else { oss << "{ " << cntxt << ", " << action << " }"; } return oss.str(); } diff --git a/ext_libs/vcpkg b/ext_libs/vcpkg index a4013afe6..f14984af3 160000 --- a/ext_libs/vcpkg +++ b/ext_libs/vcpkg @@ -1 +1 @@ -Subproject commit a4013afe6d3053e739b37c4cb6b5e64b55611e24 +Subproject commit f14984af3738e69f197bf0e647a8dca12de92996 diff --git a/ext_libs/vowpal_wabbit b/ext_libs/vowpal_wabbit index f0d45780c..16e9114f4 160000 --- a/ext_libs/vowpal_wabbit +++ b/ext_libs/vowpal_wabbit @@ -1 +1 @@ -Subproject commit f0d45780cfc509a0e17558bc074b4b3f250fb945 +Subproject commit 16e9114f41343eed0a5f3f9881b171ce4ea6774a diff --git a/external_parser/CMakeLists.txt b/external_parser/CMakeLists.txt index 3488e8a3b..9b2cb3baa 100644 --- a/external_parser/CMakeLists.txt +++ b/external_parser/CMakeLists.txt @@ -2,12 +2,24 @@ cmake_minimum_required(VERSION 3.5) if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) endif() +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() # Set this to on so that tooling can make use of the outputted compile commands (such as clang-tidy) set(CMAKE_EXPORT_COMPILE_COMMANDS On) project(vw_binary_parser) +if(UNIX AND NOT APPLE) + # Temporary workaround for VW 9.6 missing size_t on Linux builds + # TODO remove when VW submodule is updated + # This must be stddef.h and not cstddef because it also applies to C code + add_compile_options(-include stddef.h) + # Enable C++17 math functions in C++11 + add_compile_definitions(__STDCPP_WANT_MATH_SPEC_FUNCS__) +endif() + if(DEFINED RL_CXX_STANDARD) set(CMAKE_CXX_STANDARD ${RL_CXX_STANDARD}) else() @@ -44,13 +56,15 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/../cmake/M # ------------ # First try to find the config version. Newer, used by vcpkg etc -find_package(Flatbuffers CONFIG QUIET) -if(FLATBUFFERS_FOUND) +find_package(Flatbuffers CONFIG) +if(TARGET flatbuffers::flatbuffers AND TARGET flatbuffers::flatc) get_property(flatc_location TARGET flatbuffers::flatc PROPERTY LOCATION) + message(STATUS "Found Flatbuffers with CONFIG, flatc located at: ${flatc_location}") else() # Fallback to the old version find_package(Flatbuffers MODULE REQUIRED) set(flatc_location ${FLATBUFFERS_FLATC_EXECUTABLE}) + message(STATUS "Found Flatbuffers with MODULE, flatc located at: ${flatc_location}") endif() # set vw cmake flags @@ -106,7 +120,7 @@ set(RL_FLAT_BUFFER_FILES ) add_flatbuffer_schema( - TARGET fbgen + TARGET fbgen_external_parser SCHEMAS ${RL_FLAT_BUFFER_FILES} OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated/v2/ FLATC_EXE ${flatc_location} @@ -140,25 +154,41 @@ set(binary_parser_sources ) add_library(rl_binary_parser STATIC ${binary_parser_headers} ${binary_parser_sources}) +set_target_properties(rl_binary_parser PROPERTIES POSITION_INDEPENDENT_CODE ON) +if(WIN32) + set_target_properties(rl_binary_parser PROPERTIES DEBUG_POSTFIX d) +endif() + target_link_libraries(rl_binary_parser PUBLIC vw_core RapidJSON PRIVATE libzstd_static) target_include_directories(rl_binary_parser PUBLIC ${CMAKE_CURRENT_LIST_DIR}/ + ${CMAKE_CURRENT_LIST_DIR}/generated/v2/ ${CMAKE_CURRENT_LIST_DIR}/../ext_libs/zstd/lib/ ${CMAKE_CURRENT_LIST_DIR}/../ext_libs/date/ - ${FLATBUFFERS_INCLUDE_DIR} - ) -add_dependencies(rl_binary_parser fbgen) +) + +# If flatbuffers found via CONFIG, add its target as a library dependency +# Otherwise, the flatbuffers MODULE defines FLATBUFFERS_INCLUDE_DIR to add to the include path +if(TARGET flatbuffers::flatbuffers) + target_link_libraries(rl_binary_parser PRIVATE flatbuffers::flatbuffers) +else() + target_include_directories(rl_binary_parser PRIVATE ${FLATBUFFERS_INCLUDE_DIR}) +endif() +add_dependencies(rl_binary_parser fbgen_external_parser) add_executable(rl_binary_parser_bin main.cc) target_link_libraries(rl_binary_parser_bin PUBLIC rl_binary_parser) -set_target_properties(rl_binary_parser_bin PROPERTIES OUTPUT_NAME "vw") +if (NOT rlclientlib_BUILD_DOTNET) + # The build for .NET bindings configures all binary output to a single directory + # In this case we can't name the binary parser "vw", since this will overwrite the real vw executable + set_target_properties(rl_binary_parser_bin PROPERTIES OUTPUT_NAME "vw") +endif() if(STATIC_LINK_BINARY_PARSER AND NOT APPLE) target_link_libraries(rl_binary_parser_bin PRIVATE -static) endif() - # Tests # ----- diff --git a/external_parser/README.md b/external_parser/README.md index 93e680741..834dd0b95 100644 --- a/external_parser/README.md +++ b/external_parser/README.md @@ -1,98 +1,157 @@ # Parser and reward calculator for joined binary schema v2 files -## File format +## Binary log file format +The binary log file consists of a sequence of *messages*, with each message being one of five types. The message types are FILEMAGIC, HEADER, REGULAR, CHECKPOINT, and EOF. The binary log should begin with a FILEMAGIC message and end with an EOF message, although this is not mandatory. -The file format constitute of a sequence of messages writen one after the other. All messages have the same format: +Each message contains a *payload*, except for the FILEMAGIC message which has an *inline payload*. For an inline payload, the payload size field is used to store the payload data itself (which must be 4 bytes long), and the later fields are omitted. -- 4 bytes - message type -- 4 bytes - payload size or inline payload. -- bytes - payload content _(optional)_ -- padding bytes - size % 8 bytes to ensure message alignment _(optional)_ +The general format of each message is shown in this table. Details for each message type are described later. -The size of all message must be aligned to 8 bytes. Paddings bytes are inserted at the end and should not be included in the payload size. -Padding bytes should be zero but is not enforced. +| Size (bytes) | Description | +| ---- | ----------- | +| 4 | message type identifier | +| 4 | payload size (or inline payload data) | +| `payload_size` | payload content (required if not inline payload) | +| `payload_size % 8` | padding (required if not inline payload) | -### Message types - -Each message has an unique indentifier, those are the ones currently recognized: - -- `MSG_TYPE_FILEMAGIC = 0x42465756 //'VWFB'` +### Message type +The first 4 bytes in each message is an unique indentifier: +- `MSG_TYPE_FILEMAGIC = 0x42465756` (in ASCII this is `VWFB`) - `MSG_TYPE_HEADER = 0x55555555` - `MSG_TYPE_REGULAR = 0xFFFFFFFF` - `MSG_TYPE_CHECKPOINT = 0x11111111` - `MSG_TYPE_EOF = 0xAAAAAAAA` -### Message payloads +### Payload size +For all message types except the FILEMAGIC message, the payload size field should store the size of the payload as an unsigned 32-bit integer. We will refer to this value as `payload_size`. -For all flatbuffer payloads see `rlclientlib/schema/v2` +For the FILEMAGIC message, the payload size field is used to store an inline payload containing the version of the binary log file format. As of now the only existing version is `1` (one), so the FILEMAGIC message must have its payload size field equal to one. -### File Magic message +Note that the binary log format version is not the same as the Flatbuffer schema version, which is version 2 for all the flatbuffers described here. + +### Payload content +The following `payload_size` bytes of the file are interpreted as the payload data itself. -The payload is inline and it's the file format version. +### Padding +The file format requires `payload_size % 8` bytes of padding at the end of every payload. Note that this does not align the total message size to a multiple of 8 bytes. (For example a one byte payload gets one padding byte appended to make 2 bytes total.) It is merely a file format requirement and is necessary for the next message to be correctly parsed. + +Padding bytes should not be included when computing `payload_size`. Padding bytes should have a value of zero, but this is not enforced. + +### Recomended message ordering +The recomended ordering of messages in a file is shown here. -The only value accepted is `1`. +- 1 FILEMAGIC message +- 1 HEADER message +- `N` times: + - 1 CHECKPOINT message + - `M` REGULAR messages (to be processed with the same checkpoint information) +- 1 EOF message -This message should be the first on a file, making it easy to recognize files following its format by their 4 bytes watermark. +Only the REGULAR message is strictly necessary. Although it is not recommended, a binary log without the other message types can still be parsed correctly. + +## Message details +The following gives details about each message type. For information on flatbuffer payload schemas, see [`rlclientlib/schema/v2`](https://github.com/VowpalWabbit/reinforcement_learning/tree/master/rlclientlib/schema/v2) + +### File Magic message +The FILEMAGIC message does not contain a normal payload but instead must have `payload_size = 1` as described above. + +The FILEMAGIC message is optional, but it is recommended to begin every binary log file with it. ### Header message +The payload for a HEADER message is a flatbuffer of type `FileHeader`. The HEADER message is optional and does not affect how the binary log is parsed. -Payload is a flatbuffer message of type `FileHeader` (see `FileFormat.fbs`) . +``` +table FileHeader { + join_time: TimeStamp; + properties: [KeyValue]; +} +``` +(see [`FileFormat.fbs`](https://github.com/VowpalWabbit/reinforcement_learning/blob/master/rlclientlib/schema/v2/FileFormat.fbs)) -This message contains informational data about this file. It should include provenance -details such as how it was generated, the version and parameters of the program used, generation -time and other information that helps troubleshooting. +This message contains informational data about this file. It should include provenance details such as how it was generated, the version and parameters of the program used, generation time, and other information that helps troubleshooting. ### Checkpoint message +The payload for a CHECKPOINT message is a flatbuffer of type `CheckpointInfo`. + +``` +table CheckpointInfo { + reward_function_type: RewardFunctionType; + default_reward: float; + learning_mode_config: LearningModeType; + problem_type_config: ProblemType; + use_client_time: bool; +} +``` +(see [`FileFormat.fbs`](https://github.com/VowpalWabbit/reinforcement_learning/blob/master/rlclientlib/schema/v2/FileFormat.fbs)) -Payload is a flatbuffer message of type `CheckpointInfo` (see `FileFormat.fbs`). +This message includes information on how to join events comming after it. If a large binary log file must be split, it is recommended to split at the start of a CHECKPOINT message as they contain all info required to process the stream that follows it. -This message includes information on how to join events comming after it. -Checkpoint messages are the recomended point to split larger files at as they contain all info -required to process the stream that follows them. +The CHECKPOINT message is optional. If it is not present in a binary log, events will be processed with the default joiner configuration. If it is only present after encountering any REGULAR messages, all the preceding REGULAR messages will be processed with the default joiner configuration. ### Regular message +The payload for a REGULAR message is a flatbuffer of type `JoinedPayload`. -Payload is a flatbuffer message of type `JoinedPayload` (see `FileFormat.fbs`). +``` +table JoinedPayload { + events: [JoinedEvent]; +} -This message include multiple events, sharing one or more event-ids that should be processed together. +table JoinedEvent { + event: [ubyte]; + timestamp: TimeStamp; +} +``` +(see [`FileFormat.fbs`](https://github.com/VowpalWabbit/reinforcement_learning/blob/master/rlclientlib/schema/v2/FileFormat.fbs)) -### Recomended message ordering +The `JoinedPayload` flatbuffer internally contains a vector of `JoinedEvent`s. All of these `JoinedEvent`s should share the same event ID. For a binary log containing more than one event ID, multiple REGULAR messages should be used with one for each ID. -The recomended ordering of messages in a file is the following: +The `JoinedEvent` flatbuffer has an `event` field that contains a serialized `Event` flatbuffer. The `Event` flatbuffer contains a metadata field and a `payload` field that contains a serialized type-specific event flatbuffer (CaEvent, CbEvent, OutcomeEvent, etc.). In other words, the final event data is contained in a flatbuffer (type specific) inside a flatbuffer (Event) inside a flatbuffer (JoinedPayload) inside a custom message format (REGULAR message). -- 1 file magic message -- 1 file header message -N times: -- 1 checkpoint message -- M regular messages +``` +table Event { + meta:Metadata; + payload:[ubyte]; +} +table Metadata { + id:string; + client_time_utc:TimeStamp; + app_id:string; + payload_type:PayloadType; + pass_probability:float; + encoding: EventEncoding; +} +``` +(see [`Event.fbs`](https://github.com/VowpalWabbit/reinforcement_learning/blob/master/rlclientlib/schema/v2/Event.fbs) and [`Metadata.fbs`](https://github.com/VowpalWabbit/reinforcement_learning/blob/master/rlclientlib/schema/v2/Metadata.fbs)) -## Linux +### EOF message +The EOF (End Of File) message stops file parsing as soon as it is read. It should contain a payload size of zero and no payload, but any non-zero payload will be simply ignored as parsing will stop when the EOF message identifier is read. -### Build Linux +The EOF message is optional, but it is recommended to end every binary log file with it. If it is not present, parsing will continue until the end of the input data is reached or an error is encountered. -**Note**: To statically link set `-DSTATIC_LINK_BINARY_PARSER=ON` during `cmake` +# Build instructions +The binary parser may be build either along with rlclientlib or as a standalone project. -from `external_parser`: +## Building with rlclientlib +To build with rlclientlib, add `-DRL_BUILD_EXTERNAL_PARSER=On` to the CMake command line when configuring rlclientlib. Compiling rlclientlib will then produce an executable at `build/external_parser/vw`. -- mkdir build -- cd build -- cmake .. -- make -j $(nproc) +The following instructions assume building the parser as a standalone project. -vw executable located at: `external_parser/build/vw` +## Linux +### Build +**Note**: To statically link, add `-DSTATIC_LINK_BINARY_PARSER=ON` to the `cmake` command line -### Run +Run these commands from `external_parser` directory: +- `cmake -S . -B build` +- `cmake --build build` -`./vw -d --binary_parser [other vw args]` +The output is a `vw` executable located at: `external_parser/build/vw` +### Run +`./vw -d --binary_parser [other vw args]` ## Windows - -cmake build for windows - -### Deps: - +### Install dependencies **Note**: to link statically then replace `x64-windows` with `x64-windows-static-md` during vcpkg installation and in the cmake `-DVCPKG_TARGET_TRIPLET` **Note**: vcpkg doesn't play well with nugets in visual studio so if you are trying to build something else in visual studio that uses the below via nugets you might get linking errors @@ -103,19 +162,14 @@ cmake build for windows - `vcpkg install boost-test:x64-windows` - `vcpkg install flatbuffers:x64-windows` -### Build: - -from `external_parser` (replace `Release` with `Debug` if you want a debug build): - -- mkdir build -- cd build -- cmake .. -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -G "Visual Studio 15 2017" -A x64 -DBUILD_FLATBUFFERS=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DWARNINGS=OFF -DWARNINGS=OFF -DWARNING_AS_ERROR=OFF -DDO_NOT_BUILD_VW_C_WRAPPER=OFF -DBUILD_JAVA=OFF -DBUILD_PYTHON=OFF -DBUILD_TESTING=OFF -DBUILD_EXPERIMENTAL_BINDING=OFF -- /verbosity:normal /m /p:Configuration=Release;Platform=x64 vw_binary_parser.sln +### Build +Run these commands from `external_parser` (replace `Release` directory with `Debug` if you want a debug build): +- `cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -G "Visual Studio 15 2017" -A x64 -DBUILD_FLATBUFFERS=OFF -DCMAKE_CONFIGURATION_TYPES="Release" -DWARNINGS=OFF -DWARNINGS=OFF -DWARNING_AS_ERROR=OFF -DDO_NOT_BUILD_VW_C_WRAPPER=OFF -DBUILD_JAVA=OFF -DBUILD_PYTHON=OFF -DBUILD_TESTING=OFF -DBUILD_EXPERIMENTAL_BINDING=OFF` +- ` /verbosity:normal /m /p:Configuration=Release;Platform=x64 vw_binary_parser.sln` `vw_binary_parser.sln` will be available under `build` and can be used to open the solution in visual studio -vw executable located at: `external_parser\build\Release\vw.exe` +The output is a `vw` executable located at: `external_parser\build\Release\vw.exe` ### Run - -`.\Release\vw.exe -d --binary_parser [other vw args]` +`.\build\Release\vw.exe -d --binary_parser [other vw args]` diff --git a/external_parser/event_processors/joined_event.h b/external_parser/event_processors/joined_event.h index 49d426a8e..93c3f1eb8 100644 --- a/external_parser/event_processors/joined_event.h +++ b/external_parser/event_processors/joined_event.h @@ -11,13 +11,11 @@ #include "vw/io/logger.h" // clang-format on -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace joined_event { struct MultiSlotInteraction { - std::vector interaction_data; + std::vector interaction_data; std::vector baseline_actions; bool skip_learn; float probability_of_drop{0.f}; @@ -28,20 +26,17 @@ inline void calculate_multislot_interaction_metrics( { if (metrics != nullptr) { - metrics->DsjsonSumCostOriginalFirstSlot += first_slot_original_reward_neg; + metrics->dsjson_sum_cost_original_first_slot += first_slot_original_reward_neg; if (!multi_slot_interaction.interaction_data.empty() && !multi_slot_interaction.interaction_data[0].actions.empty() && !multi_slot_interaction.baseline_actions.empty()) { if (multi_slot_interaction.interaction_data[0].actions[0] == multi_slot_interaction.baseline_actions[0]) { - metrics->DsjsonNumberOfLabelEqualBaselineFirstSlot++; - metrics->DsjsonSumCostOriginalLabelEqualBaselineFirstSlot += first_slot_original_reward_neg; - } - else - { - metrics->DsjsonNumberOfLabelNotEqualBaselineFirstSlot++; + metrics->dsjson_number_of_label_equal_baseline_first_slot++; + metrics->dsjson_sum_cost_original_label_equal_baseline_first_slot += first_slot_original_reward_neg; } + else { metrics->dsjson_number_of_label_not_equal_baseline_first_slot++; } } } } @@ -67,7 +62,7 @@ struct typed_joined_event struct cb_joined_event : public typed_joined_event { - DecisionServiceInteraction interaction_data; + VW::details::decision_service_interaction interaction_data; // Default Baseline Action for CB is 1 (rl client recommended actions are 1 // indexed in the CB case) static const unsigned CB_BASELINE_ACTION = 1; @@ -76,9 +71,9 @@ struct cb_joined_event : public typed_joined_event ~cb_joined_event() = default; - bool is_skip_learn() const override { return interaction_data.skipLearn; } + bool is_skip_learn() const override { return interaction_data.skip_learn; } - void set_skip_learn(bool sl) override { interaction_data.skipLearn = sl; } + void set_skip_learn(bool sl) override { interaction_data.skip_learn = sl; } void set_apprentice_reward() override { @@ -88,41 +83,40 @@ struct cb_joined_event : public typed_joined_event // Reward of 1 when we are match baseline and 0 otherwise reward = apprentice_matching_reward; } - else - { - reward = apprentice_not_matching_reward; - } + else { reward = apprentice_not_matching_reward; } } bool fill_in_label(VW::multi_ex& examples, VW::io::logger& logger) const override { if (interaction_data.actions.empty()) { - logger.out_warn("missing actions for event [{}]", interaction_data.eventId); + logger.out_warn("missing actions for event [{}]", interaction_data.event_id); return false; } if (interaction_data.probabilities.empty()) { - logger.out_warn("missing probabilities for event [{}]", interaction_data.eventId); + logger.out_warn("missing probabilities for event [{}]", interaction_data.event_id); return false; } if (std::any_of(interaction_data.probabilities.begin(), interaction_data.probabilities.end(), [](float p) { return std::isnan(p); })) - { logger.out_warn("distribution for event [{}] contains invalid probabilities", interaction_data.eventId); } + { + logger.out_warn("distribution for event [{}] contains invalid probabilities", interaction_data.event_id); + } int index = interaction_data.actions[0]; auto action = interaction_data.actions[0]; auto probability = interaction_data.probabilities[0]; - if (interaction_data.probabilityOfDrop >= 1.f || interaction_data.probabilityOfDrop < 0) + if (interaction_data.probability_of_drop >= 1.f || interaction_data.probability_of_drop < 0) { - logger.out_warn("Probability of drop should be within [0, 1): [{}]", interaction_data.eventId); + logger.out_warn("Probability of drop should be within [0, 1): [{}]", interaction_data.event_id); return false; } examples[index]->l.cb.costs.push_back({-1.f * reward, action, probability}); - auto weight = 1.f / (1.f - interaction_data.probabilityOfDrop); + auto weight = 1.f / (1.f - interaction_data.probability_of_drop); for (auto* e : examples) { e->l.cb.weight = weight; } @@ -137,21 +131,22 @@ struct cb_joined_event : public typed_joined_event // original reward is used to record the observed reward of apprentice mode original_reward = reward_function(outcome_events, default_reward); - if (interaction_metadata.learning_mode == v2::LearningModeType_Apprentice) { set_apprentice_reward(); } - else + if (interaction_metadata.learning_mode == + reinforcement_learning::messages::flatbuff::v2::LearningModeType_Apprentice) { - reward = original_reward; + set_apprentice_reward(); } + else { reward = original_reward; } } void calculate_metrics(dsjson_metrics* metrics) override { if (metrics != nullptr) { - if (interaction_data.actions.empty()) { metrics->NumberOfEventsZeroActions++; } + if (interaction_data.actions.empty()) { metrics->number_of_events_zero_actions++; } else if (interaction_data.actions[0] == CB_BASELINE_ACTION) { - metrics->DsjsonSumCostOriginalBaseline += -1.f * original_reward; + metrics->dsjson_sum_cost_original_baseline += -1.f * original_reward; } } } @@ -179,11 +174,10 @@ struct ccb_joined_event : public typed_joined_event // Reward of 1 when we are match baseline and 0 otherwise if (!multi_slot_interaction.interaction_data[i].actions.empty() && multi_slot_interaction.interaction_data[i].actions[0] == multi_slot_interaction.baseline_actions[i]) - { rewards[i] = apprentice_matching_reward; } - else { - rewards[i] = apprentice_not_matching_reward; + rewards[i] = apprentice_matching_reward; } + else { rewards[i] = apprentice_not_matching_reward; } } } @@ -212,12 +206,14 @@ struct ccb_joined_event : public typed_joined_event logger.out_warn( "actions and probabilities for event [{}] don't have the " "same size. Actions [{}], probabilities [{}]", - slot_data.eventId, slot_data.actions.size(), slot_data.probabilities.size()); + slot_data.event_id, slot_data.actions.size(), slot_data.probabilities.size()); continue; } for (size_t i = 0; i < slot_data.actions.size(); i++) - { outcome->probabilities.push_back({slot_data.actions[i], slot_data.probabilities[i]}); } + { + outcome->probabilities.push_back({slot_data.actions[i], slot_data.probabilities[i]}); + } outcome->cost = -1.f * rewards[slot_index]; slot_label.outcome = outcome; } @@ -235,7 +231,7 @@ struct ccb_joined_event : public typed_joined_event { size_t num_of_slots = multi_slot_interaction.interaction_data.size(); - if (metadata_info.learning_mode == v2::LearningModeType_Apprentice && + if (metadata_info.learning_mode == reinforcement_learning::messages::flatbuff::v2::LearningModeType_Apprentice && num_of_slots != multi_slot_interaction.baseline_actions.size()) { logger.out_error( @@ -249,7 +245,8 @@ struct ccb_joined_event : public typed_joined_event { for (auto& outcome : outcome_events) { - if (outcome.index_type == v2::IndexValue_literal && !outcome.s_index.empty()) + if (outcome.index_type == reinforcement_learning::messages::flatbuff::v2::IndexValue_literal && + !outcome.s_index.empty()) { auto iterator = slot_id_to_index_map.find(outcome.s_index); if (iterator != slot_id_to_index_map.end()) { outcome.index = iterator->second; } @@ -280,14 +277,16 @@ struct ccb_joined_event : public typed_joined_event for (int i = 0; i < static_cast(num_of_slots); i++) { if (outcomes_map.find(i) != outcomes_map.end()) - { original_rewards[i] = reward_function(outcomes_map[i], default_reward); } + { + original_rewards[i] = reward_function(outcomes_map[i], default_reward); + } } - if (metadata_info.learning_mode == v2::LearningModeType_Apprentice) { set_apprentice_reward(); } - else + if (metadata_info.learning_mode == reinforcement_learning::messages::flatbuff::v2::LearningModeType_Apprentice) { - rewards.assign(original_rewards.begin(), original_rewards.end()); + set_apprentice_reward(); } + else { rewards.assign(original_rewards.begin(), original_rewards.end()); } } void calculate_metrics(dsjson_metrics* metrics) override @@ -331,9 +330,9 @@ struct slates_joined_event : public typed_joined_event ex->l.slates.labeled = true; ex->l.slates.weight = weight; - if (ex->l.slates.type == VW::slates::example_type::shared) { ex->l.slates.cost = -1.f * reward; } + if (ex->l.slates.type == VW::slates::example_type::SHARED) { ex->l.slates.cost = -1.f * reward; } - if (ex->l.slates.type == VW::slates::example_type::slot) + if (ex->l.slates.type == VW::slates::example_type::SLOT) { auto& slot_label = ex->l.slates; if (multi_slot_interaction.interaction_data.size() > slot_index) @@ -346,12 +345,14 @@ struct slates_joined_event : public typed_joined_event logger.out_warn( "actions and probabilities for event [{}] don't have the " "same size. Actions [{}], probabilities [{}]", - slot_data.eventId, slot_data.actions.size(), slot_data.probabilities.size()); + slot_data.event_id, slot_data.actions.size(), slot_data.probabilities.size()); continue; } for (size_t i = 0; i < slot_data.actions.size(); i++) - { slot_label.probabilities.push_back({slot_data.actions[i], slot_data.probabilities[i]}); } + { + slot_label.probabilities.push_back({slot_data.actions[i], slot_data.probabilities[i]}); + } } } // process next slot from interaction_data vector @@ -368,12 +369,11 @@ struct slates_joined_event : public typed_joined_event reward = default_reward; original_reward = reward_function(outcome_events, default_reward); - if (metadata_info.learning_mode == v2::LearningModeType_Apprentice) - { logger.out_warn("Apprentice mode is not implmeneted for slates."); } - else + if (metadata_info.learning_mode == reinforcement_learning::messages::flatbuff::v2::LearningModeType_Apprentice) { - reward = original_reward; + logger.out_warn("Apprentice mode is not implmeneted for slates."); } + else { reward = original_reward; } } void calculate_metrics(dsjson_metrics* metrics) override @@ -388,27 +388,27 @@ struct slates_joined_event : public typed_joined_event float get_sum_original_reward() const override { return original_reward; } }; // slates_joined_event -struct DecisionServiceInteractionCats +struct decision_service_interaction_cats { - std::string eventId; + std::string event_id; std::string timestamp; float action; float pdf_value; - float probabilityOfDrop = 0.f; - bool skipLearn{false}; + float probability_of_drop = 0.f; + bool skip_learn{false}; }; struct ca_joined_event : public typed_joined_event { - DecisionServiceInteractionCats interaction_data; + decision_service_interaction_cats interaction_data; float reward = 0.0f; float original_reward = 0.0f; ~ca_joined_event() override = default; - bool is_skip_learn() const override { return interaction_data.skipLearn; } + bool is_skip_learn() const override { return interaction_data.skip_learn; } - void set_skip_learn(bool sl) override { interaction_data.skipLearn = sl; } + void set_skip_learn(bool sl) override { interaction_data.skip_learn = sl; } void set_apprentice_reward() override { @@ -424,20 +424,20 @@ struct ca_joined_event : public typed_joined_event { if (std::isnan(interaction_data.action)) { - logger.out_warn("missing action for event [{}]", interaction_data.eventId); + logger.out_warn("missing action for event [{}]", interaction_data.event_id); return false; } if (std::isnan(interaction_data.pdf_value)) { - logger.out_warn("missing pdf_value for event [{}]", interaction_data.eventId); + logger.out_warn("missing pdf_value for event [{}]", interaction_data.event_id); return false; } if (examples.size() != 1) { logger.out_warn( - "example size must be 1, instead got [{}] for event [{}]", examples.size(), interaction_data.eventId); + "example size must be 1, instead got [{}] for event [{}]", examples.size(), interaction_data.event_id); return false; } @@ -454,17 +454,17 @@ struct ca_joined_event : public typed_joined_event // original reward is used to record the observed reward of apprentice mode original_reward = reward_function(outcome_events, default_reward); - if (interaction_metadata.learning_mode == v2::LearningModeType_Apprentice) - { logger.out_warn("Apprentice mode is not implmeneted for cats."); } - else + if (interaction_metadata.learning_mode == + reinforcement_learning::messages::flatbuff::v2::LearningModeType_Apprentice) { - reward = original_reward; + logger.out_warn("Apprentice mode is not implmeneted for cats."); } + else { reward = original_reward; } } void calculate_metrics(dsjson_metrics* metrics) override { - if ((metrics != nullptr) && std::isnan(interaction_data.action)) { metrics->NumberOfEventsZeroActions++; } + if ((metrics != nullptr) && std::isnan(interaction_data.action)) { metrics->number_of_events_zero_actions++; } } float get_sum_original_reward() const override { return original_reward; } @@ -516,10 +516,7 @@ struct joined_event typed_data->set_skip_learn(false); return true; } - else - { - return false; - } + else { return false; } } void calc_reward(float default_reward, reward::RewardFunctionType reward_function, VW::io::logger& logger) @@ -563,10 +560,7 @@ struct multistep_joined_event cb_data->set_skip_learn(false); return true; } - else - { - return false; - } + else { return false; } } void calc_reward(float default_reward, reward::RewardFunctionType reward_function, VW::io::logger& logger) diff --git a/external_parser/event_processors/loop.h b/external_parser/event_processors/loop.h index 355174c06..40f39ea85 100644 --- a/external_parser/event_processors/loop.h +++ b/external_parser/event_processors/loop.h @@ -3,8 +3,6 @@ // FileFormat_generated.h used for the payload type and encoding enum's #include "generated/v2/FileFormat_generated.h" -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace loop { template @@ -39,9 +37,10 @@ class sticky_value struct loop_info { sticky_value default_reward = sticky_value(0.f); - sticky_value learning_mode_config = - sticky_value(v2::LearningModeType_Online); - sticky_value problem_type_config; + sticky_value learning_mode_config = + sticky_value( + reinforcement_learning::messages::flatbuff::v2::LearningModeType_Online); + sticky_value problem_type_config; sticky_value use_client_time = sticky_value(false); bool is_configured() const diff --git a/external_parser/event_processors/metadata.h b/external_parser/event_processors/metadata.h index 28f8f9f51..e120f750d 100644 --- a/external_parser/event_processors/metadata.h +++ b/external_parser/event_processors/metadata.h @@ -4,18 +4,16 @@ #include "generated/v2/FileFormat_generated.h" #include "timestamp_helper.h" -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace metadata { // used both for interactions and observations struct event_metadata_info { std::string app_id; - v2::PayloadType payload_type; + reinforcement_learning::messages::flatbuff::v2::PayloadType payload_type; float pass_probability; - v2::EventEncoding event_encoding; + reinforcement_learning::messages::flatbuff::v2::EventEncoding event_encoding; std::string event_id; - v2::LearningModeType learning_mode; + reinforcement_learning::messages::flatbuff::v2::LearningModeType learning_mode; }; } // namespace metadata diff --git a/external_parser/event_processors/reward.h b/external_parser/event_processors/reward.h index d996263e5..c71a8e6b8 100644 --- a/external_parser/event_processors/reward.h +++ b/external_parser/event_processors/reward.h @@ -12,7 +12,7 @@ struct outcome_event { } metadata::event_metadata_info metadata; - v2::IndexValue index_type; + reinforcement_learning::messages::flatbuff::v2::IndexValue index_type; std::string s_index; int index; std::string s_value; @@ -93,11 +93,10 @@ inline float median(const std::vector& outcome_events, float defa { sort(values.begin(), values.end()); if (outcome_events_size % 2 == 0) - { return (values[outcome_events_size / 2 - 1] + values[outcome_events_size / 2]) / 2; } - else { - return values[outcome_events_size / 2]; + return (values[outcome_events_size / 2 - 1] + values[outcome_events_size / 2]) / 2; } + else { return values[outcome_events_size / 2]; } } return default_reward; diff --git a/external_parser/event_processors/timestamp_helper.cc b/external_parser/event_processors/timestamp_helper.cc index 1ea24e45e..26c969145 100644 --- a/external_parser/event_processors/timestamp_helper.cc +++ b/external_parser/event_processors/timestamp_helper.cc @@ -2,6 +2,8 @@ #include "vw/io/logger.h" +namespace v2 = reinforcement_learning::messages::flatbuff::v2; + TimePoint timestamp_to_chrono(const v2::TimeStamp& ts) { // --- date transformation --- diff --git a/external_parser/event_processors/timestamp_helper.h b/external_parser/event_processors/timestamp_helper.h index b6b2e31eb..ad28a38e6 100644 --- a/external_parser/event_processors/timestamp_helper.h +++ b/external_parser/event_processors/timestamp_helper.h @@ -6,9 +6,9 @@ #include -namespace v2 = reinforcement_learning::messages::flatbuff::v2; using TimePoint = std::chrono::time_point; -TimePoint timestamp_to_chrono(const v2::TimeStamp& ts); -bool is_empty_timestamp(const v2::TimeStamp& ts); -TimePoint get_enqueued_time(const v2::TimeStamp* enqueued_time_utc, const v2::TimeStamp* client_time_utc, - bool use_client_time, VW::io::logger& logger); \ No newline at end of file +TimePoint timestamp_to_chrono(const reinforcement_learning::messages::flatbuff::v2::TimeStamp& ts); +bool is_empty_timestamp(const reinforcement_learning::messages::flatbuff::v2::TimeStamp& ts); +TimePoint get_enqueued_time(const reinforcement_learning::messages::flatbuff::v2::TimeStamp* enqueued_time_utc, + const reinforcement_learning::messages::flatbuff::v2::TimeStamp* client_time_utc, bool use_client_time, + VW::io::logger& logger); \ No newline at end of file diff --git a/external_parser/event_processors/typed_events.h b/external_parser/event_processors/typed_events.h index 205dcd0a8..eb2a1e491 100644 --- a/external_parser/event_processors/typed_events.h +++ b/external_parser/event_processors/typed_events.h @@ -6,18 +6,18 @@ #include "generated/v2/MultiSlotEvent_generated.h" #include "joined_event.h" #include "loop.h" +#include "vw/core/json_utils.h" #include "zstd.h" -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace typed_event { template struct event_processor; template <> -struct event_processor +struct event_processor { - static bool is_valid(const v2::MultiSlotEvent& evt, const loop::loop_info& loop_info, VW::io::logger& logger) + static bool is_valid(const reinforcement_learning::messages::flatbuff::v2::MultiSlotEvent& evt, + const loop::loop_info& loop_info, VW::io::logger& logger) { if (evt.context() == nullptr || evt.slots() == nullptr) { return false; } @@ -33,18 +33,24 @@ struct event_processor return true; } - static v2::LearningModeType get_learning_mode(const v2::MultiSlotEvent& evt) { return evt.learning_mode(); } + static reinforcement_learning::messages::flatbuff::v2::LearningModeType get_learning_mode( + const reinforcement_learning::messages::flatbuff::v2::MultiSlotEvent& evt) + { + return evt.learning_mode(); + } - static std::string get_context(const v2::MultiSlotEvent& evt) + static std::string get_context(const reinforcement_learning::messages::flatbuff::v2::MultiSlotEvent& evt) { return {reinterpret_cast(evt.context()->data()), evt.context()->size()}; } - static joined_event::joined_event fill_in_joined_event(const v2::MultiSlotEvent& evt, const v2::Metadata& metadata, - const TimePoint& enqueued_time_utc, std::string&& line_vec) + static joined_event::joined_event fill_in_joined_event( + const reinforcement_learning::messages::flatbuff::v2::MultiSlotEvent& evt, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const TimePoint& enqueued_time_utc, + std::string&& line_vec) { joined_event::MultiSlotInteraction multislot_data; - bool is_ccb = metadata.payload_type() == v2::PayloadType_CCB; + bool is_ccb = metadata.payload_type() == reinforcement_learning::messages::flatbuff::v2::PayloadType_CCB; auto ccb_data = VW::make_unique(); auto slates_data = VW::make_unique(); @@ -52,11 +58,13 @@ struct event_processor size_t slot_index = 0; for (auto* slot_event : *evt.slots()) { - DecisionServiceInteraction data; - data.eventId = slot_event->id() == nullptr ? metadata.id()->str() : slot_event->id()->str(); + VW::details::decision_service_interaction data; + data.event_id = slot_event->id() == nullptr ? metadata.id()->str() : slot_event->id()->str(); if (is_ccb && slot_event->id() != nullptr) - { ccb_data->slot_id_to_index_map.insert(std::pair(slot_event->id()->str(), slot_index)); } + { + ccb_data->slot_id_to_index_map.insert(std::pair(slot_event->id()->str(), slot_index)); + } data.actions.reserve(slot_event->action_ids()->size()); for (const auto& a : *slot_event->action_ids()) { data.actions.emplace_back(a); } @@ -93,9 +101,10 @@ struct event_processor }; template <> -struct event_processor +struct event_processor { - static bool is_valid(const v2::CbEvent& evt, const loop::loop_info& loop_info, VW::io::logger& logger) + static bool is_valid(const reinforcement_learning::messages::flatbuff::v2::CbEvent& evt, + const loop::loop_info& loop_info, VW::io::logger& logger) { if (evt.context() == nullptr || evt.action_ids() == nullptr || evt.probabilities() == nullptr) { return false; } @@ -111,27 +120,33 @@ struct event_processor return true; } - static v2::LearningModeType get_learning_mode(const v2::CbEvent& evt) { return evt.learning_mode(); } + static reinforcement_learning::messages::flatbuff::v2::LearningModeType get_learning_mode( + const reinforcement_learning::messages::flatbuff::v2::CbEvent& evt) + { + return evt.learning_mode(); + } - static std::string get_context(const v2::CbEvent& evt) + static std::string get_context(const reinforcement_learning::messages::flatbuff::v2::CbEvent& evt) { return {reinterpret_cast(evt.context()->data()), evt.context()->size()}; } static joined_event::joined_event fill_in_joined_event( - const v2::CbEvent& evt, const v2::Metadata& metadata, const TimePoint& enqueued_time_utc, std::string&& line_vec) + const reinforcement_learning::messages::flatbuff::v2::CbEvent& evt, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const TimePoint& enqueued_time_utc, + std::string&& line_vec) { auto cb_data = VW::make_unique(); - cb_data->interaction_data.eventId = metadata.id()->str(); + cb_data->interaction_data.event_id = metadata.id()->str(); cb_data->interaction_data.actions.reserve(evt.action_ids()->size()); for (const auto& a : *evt.action_ids()) { cb_data->interaction_data.actions.emplace_back(a); } cb_data->interaction_data.probabilities.reserve(evt.probabilities()->size()); for (const auto& prob : *evt.probabilities()) { cb_data->interaction_data.probabilities.emplace_back(prob); } - cb_data->interaction_data.probabilityOfDrop = 1.f - metadata.pass_probability(); - cb_data->interaction_data.skipLearn = evt.deferred_action(); + cb_data->interaction_data.probability_of_drop = 1.f - metadata.pass_probability(); + cb_data->interaction_data.skip_learn = evt.deferred_action(); return {TimePoint(enqueued_time_utc), {metadata.app_id() ? metadata.app_id()->str() : "", metadata.payload_type(), metadata.pass_probability(), @@ -141,9 +156,10 @@ struct event_processor }; template <> -struct event_processor +struct event_processor { - static bool is_valid(const v2::CaEvent& evt, const loop::loop_info& loop_info, VW::io::logger& logger) + static bool is_valid(const reinforcement_learning::messages::flatbuff::v2::CaEvent& evt, + const loop::loop_info& loop_info, VW::io::logger& logger) { if (evt.context() == nullptr) { return false; } @@ -159,22 +175,28 @@ struct event_processor return true; } - static v2::LearningModeType get_learning_mode(const v2::CaEvent& evt) { return evt.learning_mode(); } + static reinforcement_learning::messages::flatbuff::v2::LearningModeType get_learning_mode( + const reinforcement_learning::messages::flatbuff::v2::CaEvent& evt) + { + return evt.learning_mode(); + } - static std::string get_context(const v2::CaEvent& evt) + static std::string get_context(const reinforcement_learning::messages::flatbuff::v2::CaEvent& evt) { return {reinterpret_cast(evt.context()->data()), evt.context()->size()}; } static joined_event::joined_event fill_in_joined_event( - const v2::CaEvent& evt, const v2::Metadata& metadata, const TimePoint& enqueued_time_utc, std::string&& line_vec) + const reinforcement_learning::messages::flatbuff::v2::CaEvent& evt, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const TimePoint& enqueued_time_utc, + std::string&& line_vec) { auto ca_data = VW::make_unique(); - ca_data->interaction_data.eventId = metadata.id()->str(); + ca_data->interaction_data.event_id = metadata.id()->str(); ca_data->interaction_data.action = evt.action(); ca_data->interaction_data.pdf_value = evt.pdf_value(); - ca_data->interaction_data.probabilityOfDrop = 1.f - metadata.pass_probability(); - ca_data->interaction_data.skipLearn = evt.deferred_action(); + ca_data->interaction_data.probability_of_drop = 1.f - metadata.pass_probability(); + ca_data->interaction_data.skip_learn = evt.deferred_action(); return {TimePoint(enqueued_time_utc), {metadata.app_id() ? metadata.app_id()->str() : "", metadata.payload_type(), metadata.pass_probability(), @@ -184,10 +206,11 @@ struct event_processor }; template -bool process_compression(const uint8_t* data, size_t size, const v2::Metadata& metadata, const T*& payload, +bool process_compression(const uint8_t* data, size_t size, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const T*& payload, flatbuffers::DetachedBuffer& detached_buffer, VW::io::logger& logger) { - if (metadata.encoding() == v2::EventEncoding_Zstd) + if (metadata.encoding() == reinforcement_learning::messages::flatbuff::v2::EventEncoding_Zstd) { size_t buff_size = ZSTD_getFrameContentSize(data, size); if (buff_size == ZSTD_CONTENTSIZE_ERROR) @@ -226,10 +249,7 @@ bool process_compression(const uint8_t* data, size_t size, const v2::Metadata& m detached_buffer = flatbuffers::DetachedBuffer(nullptr, false, data_ptr, 0, data_ptr, res); payload = flatbuffers::GetRoot(detached_buffer.data()); } - else - { - payload = flatbuffers::GetRoot(data); - } + else { payload = flatbuffers::GetRoot(data); } return true; } } // namespace typed_event diff --git a/external_parser/joiners/example_joiner.cc b/external_parser/joiners/example_joiner.cc index 123ecdc4e..fdc3e084c 100644 --- a/external_parser/joiners/example_joiner.cc +++ b/external_parser/joiners/example_joiner.cc @@ -30,12 +30,14 @@ #include "vw/core/parser.h" #include "vw/core/scope_exit.h" +namespace v2 = reinforcement_learning::messages::flatbuff::v2; + example_joiner::example_joiner(VW::workspace* vw) : i_joiner(vw->logger), _vw(vw), _reward_calculation(&reward::earliest), _binary_to_json(false) { } -example_joiner::example_joiner(VW::workspace* vw, bool binary_to_json, std::string outfile_name) +example_joiner::example_joiner(VW::workspace* vw, bool binary_to_json, const std::string& outfile_name) : i_joiner(vw->logger), _vw(vw), _reward_calculation(&reward::earliest), _binary_to_json(binary_to_json) { _outfile.open(outfile_name, std::ofstream::out); @@ -114,7 +116,9 @@ bool example_joiner::process_event(const v2::JoinedEvent& joined_event) return false; } if (_batch_grouped_events.find(id) != _batch_grouped_events.end()) - { _batch_grouped_events[id].push_back(&joined_event); } + { + _batch_grouped_events[id].push_back(&joined_event); + } else { _batch_grouped_events.insert({id, {&joined_event}}); @@ -243,7 +247,9 @@ bool example_joiner::process_interaction( if (!typed_event::process_compression( event.payload()->data(), event.payload()->size(), metadata, cb, _detached_buffer, logger) || cb == nullptr) - { return false; } + { + return false; + } if (!typed_event::event_processor::is_valid(*cb, _loop_info, logger)) { @@ -263,7 +269,9 @@ bool example_joiner::process_interaction( if (!typed_event::process_compression( event.payload()->data(), event.payload()->size(), metadata, multislot, _detached_buffer, logger) || multislot == nullptr) - { return false; } + { + return false; + } if (!typed_event::event_processor::is_valid(*multislot, _loop_info, logger)) { @@ -283,7 +291,9 @@ bool example_joiner::process_interaction( if (!typed_event::process_compression( event.payload()->data(), event.payload()->size(), metadata, ca, _detached_buffer, logger) || ca == nullptr) - { return false; } + { + return false; + } if (!typed_event::event_processor::is_valid(*ca, _loop_info, logger)) { @@ -359,18 +369,12 @@ bool example_joiner::process_outcome( } if (outcome->value_type() == v2::OutcomeValue_literal) { o_event.s_value = outcome->value_as_literal()->c_str(); } - else if (outcome->value_type() == v2::OutcomeValue_numeric) - { - o_event.value = outcome->value_as_numeric()->value(); - } + else if (outcome->value_type() == v2::OutcomeValue_numeric) { o_event.value = outcome->value_as_numeric()->value(); } o_event.index_type = outcome->index_type(); if (outcome->index_type() == v2::IndexValue_literal) { o_event.s_index = outcome->index_as_literal()->c_str(); } - else if (outcome->index_type() == v2::IndexValue_numeric) - { - o_event.index = outcome->index_as_numeric()->index(); - } + else if (outcome->index_type() == v2::IndexValue_numeric) { o_event.index = outcome->index_as_numeric()->index(); } o_event.action_taken = outcome->action_taken(); @@ -389,7 +393,9 @@ bool example_joiner::process_dedup(const v2::Event& event, const v2::Metadata& m if (!typed_event::process_compression( event.payload()->data(), event.payload()->size(), metadata, dedup, _detached_buffer, logger) || dedup == nullptr) - { return false; } + { + return false; + } if (dedup->ids()->size() != dedup->values()->size()) { @@ -431,10 +437,7 @@ bool example_joiner::process_dedup(const v2::Event& event, const v2::Metadata& m _dedup_cache.add(dedup_id, examples[0]); examples.clear(); } - else - { - _dedup_cache.update(dedup_id); - } + else { _dedup_cache.update(dedup_id); } } if (dedup->ids()->size() > 0) @@ -478,35 +481,37 @@ bool example_joiner::process_joined(VW::multi_ex& examples) // that way we can guarantee clean-up no matter where the return happens // without having to duplicate the cleanup code bool clear_examples = false; - auto clear_event_id_on_exit = VW::scope_exit([&] { - if (je != nullptr) - { - if (_vw->example_parser->metrics) + auto clear_event_id_on_exit = VW::scope_exit( + [&] { - if (!je->is_joined_event_learnable()) { _joiner_metrics.number_of_skipped_events++; } - else + if (je != nullptr) { - je->calculate_metrics(_vw->example_parser->metrics.get()); - _joiner_metrics.sum_cost_original += -1.f * je->get_sum_original_reward(); - if (_joiner_metrics.first_event_id.empty()) - { - _joiner_metrics.first_event_id = std::move(je->interaction_metadata.event_id); - _joiner_metrics.first_event_timestamp = std::move(je->joined_event_timestamp); - } - else + if (_vw->example_parser->metrics) { - _joiner_metrics.last_event_id = std::move(je->interaction_metadata.event_id); - _joiner_metrics.last_event_timestamp = std::move(je->joined_event_timestamp); + if (!je->is_joined_event_learnable()) { _joiner_metrics.number_of_skipped_events++; } + else + { + je->calculate_metrics(_vw->example_parser->metrics.get()); + _joiner_metrics.sum_cost_original += -1.f * je->get_sum_original_reward(); + if (_joiner_metrics.first_event_id.empty()) + { + _joiner_metrics.first_event_id = std::move(je->interaction_metadata.event_id); + _joiner_metrics.first_event_timestamp = std::move(je->joined_event_timestamp); + } + else + { + _joiner_metrics.last_event_id = std::move(je->interaction_metadata.event_id); + _joiner_metrics.last_event_timestamp = std::move(je->joined_event_timestamp); + } + } } - } - } - if (_binary_to_json) { log_converter::build_json(_outfile, *je, logger); } - } + if (_binary_to_json) { log_converter::build_json(_outfile, *je, logger); } + } - clear_event_id_batch_info(id); - if (clear_examples) { clear_vw_examples(examples); } - }); + clear_event_id_batch_info(id); + if (clear_examples) { clear_vw_examples(examples); } + }); if (_batch_grouped_examples.find(id) == _batch_grouped_examples.end()) { @@ -567,22 +572,22 @@ void example_joiner::persist_metrics() { if (_vw->example_parser->metrics) { - _vw->example_parser->metrics->NumberOfSkippedEvents = _joiner_metrics.number_of_skipped_events; + _vw->example_parser->metrics->number_of_skipped_events = _joiner_metrics.number_of_skipped_events; - _vw->example_parser->metrics->DsjsonSumCostOriginal = _joiner_metrics.sum_cost_original; + _vw->example_parser->metrics->dsjson_sum_cost_original = _joiner_metrics.sum_cost_original; if (!_joiner_metrics.first_event_id.empty()) { - _vw->example_parser->metrics->FirstEventId = std::move(_joiner_metrics.first_event_id); + _vw->example_parser->metrics->first_event_id = std::move(_joiner_metrics.first_event_id); - _vw->example_parser->metrics->FirstEventTime = std::move( + _vw->example_parser->metrics->first_event_time = std::move( date::format("%FT%TZ", date::floor(_joiner_metrics.first_event_timestamp))); } if (!_joiner_metrics.last_event_id.empty()) { - _vw->example_parser->metrics->LastEventId = std::move(_joiner_metrics.last_event_id); + _vw->example_parser->metrics->last_event_id = std::move(_joiner_metrics.last_event_id); - _vw->example_parser->metrics->LastEventTime = std::move( + _vw->example_parser->metrics->last_event_time = std::move( date::format("%FT%TZ", date::floor(_joiner_metrics.last_event_timestamp))); } } diff --git a/external_parser/joiners/example_joiner.h b/external_parser/joiners/example_joiner.h index f7db8b656..e5eb38d8a 100644 --- a/external_parser/joiners/example_joiner.h +++ b/external_parser/joiners/example_joiner.h @@ -19,27 +19,36 @@ class example_joiner : public i_joiner { public: example_joiner(VW::workspace* vw); // TODO rule of 5 - example_joiner(VW::workspace* vw, bool binary_to_json, std::string outfile_name); + example_joiner(VW::workspace* vw, bool binary_to_json, const std::string& outfile_name); ~example_joiner() override; - void set_reward_function(v2::RewardFunctionType type, bool sticky = false) override; + void set_reward_function( + reinforcement_learning::messages::flatbuff::v2::RewardFunctionType type, bool sticky = false) override; void set_default_reward(float default_reward, bool sticky = false) override; - void set_learning_mode_config(v2::LearningModeType learning_mode, bool sticky = false) override; - void set_problem_type_config(v2::ProblemType problem_type, bool sticky = false) override; + void set_learning_mode_config( + reinforcement_learning::messages::flatbuff::v2::LearningModeType learning_mode, bool sticky = false) override; + void set_problem_type_config( + reinforcement_learning::messages::flatbuff::v2::ProblemType problem_type, bool sticky = false) override; void set_use_client_time(bool use_client_time, bool sticky = false) override; void apply_cli_overrides(VW::workspace* all, const VW::external::parser_options& parsed_options) override; bool joiner_ready() override; float default_reward() const { return _loop_info.default_reward; } - v2::LearningModeType learning_mode_config() const { return _loop_info.learning_mode_config; } - v2::ProblemType problem_type_config() const { return _loop_info.problem_type_config; } + reinforcement_learning::messages::flatbuff::v2::LearningModeType learning_mode_config() const + { + return _loop_info.learning_mode_config; + } + reinforcement_learning::messages::flatbuff::v2::ProblemType problem_type_config() const + { + return _loop_info.problem_type_config; + } bool use_client_time() const { return _loop_info.use_client_time; } // Takes an event which will have a timestamp and event payload // groups all events interactions with their event observations based on their // id. The grouped events can be processed when process_joined() is called - bool process_event(const v2::JoinedEvent& joined_event) override; + bool process_event(const reinforcement_learning::messages::flatbuff::v2::JoinedEvent& joined_event) override; /** * Takes all grouped events, processes them (e.g. decompression) and populates @@ -91,12 +100,15 @@ class example_joiner : public i_joiner void persist_metrics() override; private: - bool process_dedup(const v2::Event& event, const v2::Metadata& metadata); + bool process_dedup(const reinforcement_learning::messages::flatbuff::v2::Event& event, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata); - bool process_interaction( - const v2::Event& event, const v2::Metadata& metadata, const TimePoint& enqueued_time_utc, VW::multi_ex& examples); + bool process_interaction(const reinforcement_learning::messages::flatbuff::v2::Event& event, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const TimePoint& enqueued_time_utc, + VW::multi_ex& examples); - bool process_outcome(const v2::Event& event, const v2::Metadata& metadata, const TimePoint& enqueued_time_utc); + bool process_outcome(const reinforcement_learning::messages::flatbuff::v2::Event& event, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, const TimePoint& enqueued_time_utc); void clear_batch_info(); void clear_event_id_batch_info(const std::string& id); @@ -116,7 +128,8 @@ class example_joiner : public i_joiner // (multi)example std::unordered_map _batch_grouped_examples; // from event id to all the events that have that event id - std::unordered_map> _batch_grouped_events; + std::unordered_map> + _batch_grouped_events; std::queue _batch_event_order; std::vector _example_pool; diff --git a/external_parser/joiners/i_joiner.h b/external_parser/joiners/i_joiner.h index 2662f41e6..a011c3a4e 100644 --- a/external_parser/joiners/i_joiner.h +++ b/external_parser/joiners/i_joiner.h @@ -22,18 +22,19 @@ #include "vw/core/json_utils.h" // clang-format on -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - class i_joiner { public: explicit i_joiner(VW::io::logger logger_) : logger(std::move(logger_)) {} virtual ~i_joiner() = default; - virtual void set_reward_function(const v2::RewardFunctionType type, bool sticky = false) = 0; + virtual void set_reward_function( + const reinforcement_learning::messages::flatbuff::v2::RewardFunctionType type, bool sticky = false) = 0; virtual void set_default_reward(float default_reward, bool sticky = false) = 0; - virtual void set_learning_mode_config(v2::LearningModeType learning_mode, bool sticky = false) = 0; - virtual void set_problem_type_config(v2::ProblemType problem_type, bool sticky = false) = 0; + virtual void set_learning_mode_config( + reinforcement_learning::messages::flatbuff::v2::LearningModeType learning_mode, bool sticky = false) = 0; + virtual void set_problem_type_config( + reinforcement_learning::messages::flatbuff::v2::ProblemType problem_type, bool sticky = false) = 0; virtual void set_use_client_time(bool use_client_time, bool sticky = false) = 0; virtual void apply_cli_overrides(VW::workspace* all, const VW::external::parser_options& parsed_options) = 0; @@ -48,7 +49,7 @@ class i_joiner // Takes an event which will have a timestamp and event payload // groups all events interactions with their event observations based on their // id. The grouped events can be processed when process_joined() is called - virtual bool process_event(const v2::JoinedEvent& joined_event) = 0; + virtual bool process_event(const reinforcement_learning::messages::flatbuff::v2::JoinedEvent& joined_event) = 0; // Takes all grouped events, processes them (e.g. decompression) and populates // the examples array with complete example(s) ready to be used by vw for // training diff --git a/external_parser/joiners/multistep_example_joiner.cc b/external_parser/joiners/multistep_example_joiner.cc index b9f1d92b4..027024841 100644 --- a/external_parser/joiners/multistep_example_joiner.cc +++ b/external_parser/joiners/multistep_example_joiner.cc @@ -9,9 +9,8 @@ #include "utils.h" #include "vw/io/logger.h" -#include -#include - +#include +#include #include #include #include @@ -21,9 +20,12 @@ #include "vw/core/example.h" #include "vw/core/parse_example_json.h" #include "vw/core/parser.h" +#include "vw/core/scope_exit.h" #include "vw/core/v_array.h" #include "vw/io/logger.h" +namespace v2 = reinforcement_learning::messages::flatbuff::v2; + multistep_example_joiner::multistep_example_joiner(VW::workspace* vw) : i_joiner(vw->logger) , _vw(vw) @@ -57,10 +59,7 @@ bool multistep_example_joiner::process_event(const v2::JoinedEvent& joined_event auto outcome = flatbuffers::GetRoot(event->payload()->data()); const char* id = outcome->index_type() == v2::IndexValue_literal ? outcome->index_as_literal()->c_str() : nullptr; if (id == nullptr) { _episodic_outcomes.push_back(process_outcome(enqueued_time_utc, meta, *outcome)); } - else - { - _outcomes[std::string(id)].push_back(process_outcome(enqueued_time_utc, meta, *outcome)); - } + else { _outcomes[std::string(id)].push_back(process_outcome(enqueued_time_utc, meta, *outcome)); } break; } default: @@ -187,10 +186,7 @@ class topo_sorter states.push(&next[cur_id]); top.pop(); } - else - { - states.pop(); - } + else { states.pop(); } } } }; @@ -202,11 +198,10 @@ bool multistep_example_joiner::populate_order() { const auto& parsed = it.second[0]; if (parsed.event.previous_id() == nullptr || parsed.event.previous_id()->size() == 0) - { sorter.push(it.first, parsed.timestamp); } - else { - sorter.push(it.first, parsed.timestamp, parsed.event.previous_id()->str()); + sorter.push(it.first, parsed.timestamp); } + else { sorter.push(it.first, parsed.timestamp, parsed.event.previous_id()->str()); } } sorter.get(_order); _sorted = true; @@ -222,10 +217,7 @@ reward::outcome_event multistep_example_joiner::process_outcome( v2::LearningModeType::LearningModeType_Online}; if (event.value_type() == v2::OutcomeValue_literal) { o_event.s_value = event.value_as_literal()->c_str(); } - else if (event.value_type() == v2::OutcomeValue_numeric) - { - o_event.value = event.value_as_numeric()->value(); - } + else if (event.value_type() == v2::OutcomeValue_numeric) { o_event.value = event.value_as_numeric()->value(); } o_event.action_taken = event.action_taken(); return o_event; } @@ -241,13 +233,13 @@ joined_event::multistep_joined_event multistep_example_joiner::process_interacti auto cb_data = VW::make_unique(); - cb_data->interaction_data.eventId = event.event_id()->str(); + cb_data->interaction_data.event_id = event.event_id()->str(); cb_data->interaction_data.actions = { event.action_ids()->data(), event.action_ids()->data() + event.action_ids()->size()}; cb_data->interaction_data.probabilities = { event.probabilities()->data(), event.probabilities()->data() + event.probabilities()->size()}; - cb_data->interaction_data.probabilityOfDrop = 1.f - metadata.pass_probability(); - cb_data->interaction_data.skipLearn = event.deferred_action(); + cb_data->interaction_data.probability_of_drop = 1.f - metadata.pass_probability(); + cb_data->interaction_data.skip_learn = event.deferred_action(); std::string line_vec(reinterpret_cast(event.context()->data()), event.context()->size()); @@ -286,15 +278,17 @@ bool multistep_example_joiner::process_joined(VW::multi_ex& examples) for (const auto& o : _episodic_outcomes) { joined.outcome_events.push_back(o); } bool clear_examples = false; - auto guard = VW::scope_exit([&] { - _order.pop_front(); - _rewards.pop_front(); - if (clear_examples) - { - VW::return_multiple_example(*_vw, examples); - examples.push_back(VW::new_unused_example(*_vw)); - } - }); + auto guard = VW::scope_exit( + [&] + { + _order.pop_front(); + _rewards.pop_front(); + if (clear_examples) + { + VW::return_multiple_example(*_vw, examples); + examples.push_back(VW::new_unused_example(*_vw)); + } + }); if (!joined.is_joined_event_learnable()) { @@ -358,7 +352,9 @@ void multistep_example_joiner::apply_cli_overrides( if (!VW::external::str_to_enum(parsed_options.multistep_reward, multistep_reward_functions, multistep_reward_funtion_type::Identity, multistep_reward_func)) - { throw std::runtime_error("Invalid argument to --multistep_reward " + parsed_options.reward_function); } + { + throw std::runtime_error("Invalid argument to --multistep_reward " + parsed_options.reward_function); + } set_multistep_reward_function(multistep_reward_func, true); } } diff --git a/external_parser/joiners/multistep_example_joiner.h b/external_parser/joiners/multistep_example_joiner.h index 585de5ef2..9ee40d161 100644 --- a/external_parser/joiners/multistep_example_joiner.h +++ b/external_parser/joiners/multistep_example_joiner.h @@ -23,8 +23,6 @@ #include "vw/core/json_utils.h" // clang-format on -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - enum multistep_reward_funtion_type { Identity = 0, @@ -60,17 +58,20 @@ class multistep_example_joiner : public i_joiner ~multistep_example_joiner() override; - void set_reward_function(const v2::RewardFunctionType type, bool sticky) override; + void set_reward_function( + const reinforcement_learning::messages::flatbuff::v2::RewardFunctionType type, bool sticky) override; void set_default_reward(float default_reward, bool sticky) override; - void set_learning_mode_config(v2::LearningModeType learning_mode, bool sticky) override; - void set_problem_type_config(v2::ProblemType problem_type, bool sticky) override; + void set_learning_mode_config( + reinforcement_learning::messages::flatbuff::v2::LearningModeType learning_mode, bool sticky) override; + void set_problem_type_config( + reinforcement_learning::messages::flatbuff::v2::ProblemType problem_type, bool sticky) override; void set_use_client_time(bool use_client_time, bool sticky = false) override; void apply_cli_overrides(VW::workspace* all, const VW::external::parser_options& parsed_options) override; bool joiner_ready() override; bool current_event_is_skip_learn() override; - bool process_event(const v2::JoinedEvent& joined_event) override; + bool process_event(const reinforcement_learning::messages::flatbuff::v2::JoinedEvent& joined_event) override; bool process_joined(VW::multi_ex& examples) override; bool processing_batch() override; @@ -83,17 +84,18 @@ class multistep_example_joiner : public i_joiner struct Parsed { const TimePoint timestamp; - const v2::Metadata& meta; + const reinforcement_learning::messages::flatbuff::v2::Metadata& meta; const event_t& event; }; void set_multistep_reward_function(const multistep_reward_funtion_type type, bool sticky); private: bool populate_order(); - reward::outcome_event process_outcome( - const TimePoint& timestamp, const v2::Metadata& metadata, const v2::OutcomeEvent& event); + reward::outcome_event process_outcome(const TimePoint& timestamp, + const reinforcement_learning::messages::flatbuff::v2::Metadata& metadata, + const reinforcement_learning::messages::flatbuff::v2::OutcomeEvent& event); joined_event::multistep_joined_event process_interaction( - const Parsed& event_meta, VW::multi_ex& examples); + const Parsed& event_meta, VW::multi_ex& examples); void populate_episodic_rewards(); private: @@ -106,7 +108,8 @@ class multistep_example_joiner : public i_joiner loop::sticky_value _multistep_reward_calculation; loop::loop_info _loop_info; - std::unordered_map>> _interactions; + std::unordered_map>> + _interactions; std::unordered_map> _outcomes; std::vector _episodic_outcomes; diff --git a/external_parser/log_converter.cc b/external_parser/log_converter.cc index 3dbb8d1f1..21e88d2be 100644 --- a/external_parser/log_converter.cc +++ b/external_parser/log_converter.cc @@ -9,6 +9,7 @@ namespace log_converter { namespace rj = rapidjson; +namespace v2 = reinforcement_learning::messages::flatbuff::v2; void build_json(std::ofstream& outfile, joined_event::joined_event& je, VW::io::logger& logger) { @@ -98,7 +99,7 @@ void build_cb_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i writer.Key("EventId", static_cast(strlen("EventId")), true); writer.String( - interaction_data.eventId.c_str(), static_cast(interaction_data.eventId.length()), true); + interaction_data.event_id.c_str(), static_cast(interaction_data.event_id.length()), true); writer.Key("a", static_cast(strlen("a")), true); writer.StartArray(); @@ -123,10 +124,10 @@ void build_cb_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i writer.String(je.model_id.c_str(), static_cast(je.model_id.length()), true); writer.EndObject(); - if (interaction_data.probabilityOfDrop != 0.f) + if (interaction_data.probability_of_drop != 0.f) { writer.Key("pdrop"); - writer.Double(interaction_data.probabilityOfDrop); + writer.Double(interaction_data.probability_of_drop); } writer.Key("_original_label_cost", static_cast(strlen("_original_label_cost")), true); @@ -139,7 +140,7 @@ void build_cb_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i catch (const std::exception& e) { logger.out_error( - "convert event: [{}] from binary to json format failed: [{}].", interaction_data.eventId, e.what()); + "convert event: [{}] from binary to json format failed: [{}].", interaction_data.event_id, e.what()); } } @@ -196,8 +197,8 @@ void build_ccb_json(std::ofstream& outfile, joined_event::joined_event& je, VW:: writer.Double(-1.f * rewards[i]); writer.Key("_id"); // slot id - writer.String(interaction_data[i].eventId.c_str(), - static_cast(interaction_data[i].eventId.length()), true); + writer.String(interaction_data[i].event_id.c_str(), + static_cast(interaction_data[i].event_id.length()), true); writer.Key("_a"); writer.StartArray(); @@ -232,7 +233,9 @@ void build_ccb_json(std::ofstream& outfile, joined_event::joined_event& je, VW:: writer.Key("Index"); if (o.index_type == v2::IndexValue_literal) - { writer.String(o.s_index.c_str(), static_cast(o.s_index.length()), true); } + { + writer.String(o.s_index.c_str(), static_cast(o.s_index.length()), true); + } else { writer.String(std::to_string(o.index).c_str(), @@ -316,7 +319,7 @@ void build_ca_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i writer.Key("EventId", static_cast(strlen("EventId")), true); writer.String( - interaction_data.eventId.c_str(), static_cast(interaction_data.eventId.length()), true); + interaction_data.event_id.c_str(), static_cast(interaction_data.event_id.length()), true); writer.Key("c", static_cast(strlen("c")), true); std::replace(je.context.begin(), je.context.end(), '\n', ' '); @@ -328,10 +331,10 @@ void build_ca_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i writer.String(je.model_id.c_str(), static_cast(je.model_id.length()), true); writer.EndObject(); - if (interaction_data.probabilityOfDrop != 0.f) + if (interaction_data.probability_of_drop != 0.f) { writer.Key("pdrop", static_cast(strlen("pdrop")), true); - writer.Double(interaction_data.probabilityOfDrop); + writer.Double(interaction_data.probability_of_drop); } bool skip_learn = !je.is_joined_event_learnable(); @@ -348,7 +351,7 @@ void build_ca_json(std::ofstream& outfile, joined_event::joined_event& je, VW::i catch (const std::exception& e) { logger.out_error( - "convert event: [{}] from binary to json format failed: [{}].", interaction_data.eventId, e.what()); + "convert event: [{}] from binary to json format failed: [{}].", interaction_data.event_id, e.what()); } } diff --git a/external_parser/log_converter.h b/external_parser/log_converter.h index d980c2960..23b5fe86e 100644 --- a/external_parser/log_converter.h +++ b/external_parser/log_converter.h @@ -10,8 +10,6 @@ #include #include -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace log_converter { void build_json(std::ofstream& outfile, joined_event::joined_event& je, VW::io::logger& logger); diff --git a/external_parser/main.cc b/external_parser/main.cc index b056365eb..0751e452c 100644 --- a/external_parser/main.cc +++ b/external_parser/main.cc @@ -9,6 +9,7 @@ #include "vw/config/options_cli.h" #include "vw/core/global_data.h" #include "vw/core/input_parser.h" +#include "vw/core/learner.h" #include "vw/core/memory.h" #include "vw/core/metric_sink.h" #include "vw/core/vw.h" @@ -78,10 +79,12 @@ int main(int argc, char* argv[]) if (log_level != "off") { if (log_output_stream == "compat" || log_output_stream == "stderr") - { std::cerr << "[critical] vw (" << e.Filename() << ":" << e.LineNumber() << "): " << e.what() << std::endl; } + { + std::cerr << "[critical] vw (" << e.filename() << ":" << e.line_number() << "): " << e.what() << std::endl; + } else { - std::cout << "[critical] vw (" << e.Filename() << ":" << e.LineNumber() << "): " << e.what() << std::endl; + std::cout << "[critical] vw (" << e.filename() << ":" << e.line_number() << "): " << e.what() << std::endl; } } return 1; @@ -97,11 +100,10 @@ int main(int argc, char* argv[]) if (log_level != "off") { if (log_output_stream == "compat" || log_output_stream == "stderr") - { std::cerr << "[critical] vw: " << e.what() << std::endl; } - else { - std::cout << "[critical] vw: " << e.what() << std::endl; + std::cerr << "[critical] vw: " << e.what() << std::endl; } + else { std::cout << "[critical] vw: " << e.what() << std::endl; } } return 1; } @@ -110,11 +112,10 @@ int main(int argc, char* argv[]) if (log_level != "off") { if (log_output_stream == "compat" || log_output_stream == "stderr") - { std::cerr << "[critical] Unknown exception occurred" << std::endl; } - else { - std::cout << "[critical] vw: unknown exception" << std::endl; + std::cerr << "[critical] Unknown exception occurred" << std::endl; } + else { std::cout << "[critical] vw: unknown exception" << std::endl; } } return 1; } diff --git a/external_parser/parse_example_binary.cc b/external_parser/parse_example_binary.cc index a4a6aafa4..d32e42f66 100644 --- a/external_parser/parse_example_binary.cc +++ b/external_parser/parse_example_binary.cc @@ -19,6 +19,8 @@ #include #include +namespace v2 = reinforcement_learning::messages::flatbuff::v2; + // TODO need to check if errors will be detected from stderr/stdout/other and // use appropriate logger @@ -84,7 +86,11 @@ namespace VW namespace external { binary_parser::binary_parser(std::unique_ptr&& joiner, VW::io::logger logger) - : parser(logger), _example_joiner(std::move(joiner)), _payload(nullptr), _payload_size(0), _total_size_read(0) + : parser(std::move(logger)) + , _example_joiner(std::move(joiner)) + , _payload(nullptr) + , _payload_size(0) + , _total_size_read(0) { } diff --git a/external_parser/parse_example_converter.cc b/external_parser/parse_example_converter.cc index bcc38f346..17eafaa08 100644 --- a/external_parser/parse_example_converter.cc +++ b/external_parser/parse_example_converter.cc @@ -11,7 +11,7 @@ namespace VW { namespace external { -binary_json_converter::binary_json_converter(std::unique_ptr&& joiner, VW::io::logger logger) +binary_json_converter::binary_json_converter(std::unique_ptr&& joiner, const VW::io::logger& logger) : parser(logger), _parser(std::move(joiner), logger) { } diff --git a/external_parser/parse_example_converter.h b/external_parser/parse_example_converter.h index eab0cbe75..8d09ee022 100644 --- a/external_parser/parse_example_converter.h +++ b/external_parser/parse_example_converter.h @@ -15,7 +15,8 @@ namespace external class binary_json_converter : public parser { public: - binary_json_converter(std::unique_ptr&& joiner, VW::io::logger logger); // taking ownership of joiner + binary_json_converter( + std::unique_ptr&& joiner, const VW::io::logger& logger); // taking ownership of joiner ~binary_json_converter(); bool parse_examples(VW::workspace* all, io_buf& io_buf, VW::multi_ex& examples) override; void persist_metrics(metric_sink& metrics_sink) override; diff --git a/external_parser/parse_example_external.cc b/external_parser/parse_example_external.cc index 9292ca07b..4c0a81e49 100644 --- a/external_parser/parse_example_external.cc +++ b/external_parser/parse_example_external.cc @@ -7,12 +7,15 @@ #include "parse_example_binary.h" #include "parse_example_converter.h" #include "utils.h" +#include "vw/core/metric_sink.h" #include "vw/core/parse_args.h" #include "vw/io/logger.h" #include #include +namespace v2 = reinforcement_learning::messages::flatbuff::v2; + namespace VW { namespace external @@ -49,23 +52,31 @@ void apply_cli_overrides(std::unique_ptr& joiner, VW::workspace* all, { v2::ProblemType problem_type; if (!str_to_enum(parsed_options.problem_type, problem_types, v2::ProblemType_UNKNOWN, problem_type)) - { throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.problem_type); } + { + throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.problem_type); + } joiner->set_problem_type_config(problem_type, true); } if (all->options->was_supplied("use_client_time")) - { joiner->set_use_client_time(parsed_options.use_client_time, true); } + { + joiner->set_use_client_time(parsed_options.use_client_time, true); + } if (all->options->was_supplied("learning_mode")) { v2::LearningModeType learning_mode; if (!str_to_enum(parsed_options.learning_mode, learning_modes, v2::LearningModeType_MIN, learning_mode)) - { throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.learning_mode); } + { + throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.learning_mode); + } joiner->set_learning_mode_config(learning_mode, true); } if (all->options->was_supplied("reward_function")) { v2::RewardFunctionType reward_function; if (!str_to_enum(parsed_options.reward_function, reward_functions, v2::RewardFunctionType_MIN, reward_function)) - { throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.reward_function); } + { + throw std::runtime_error("Invalid argument to --problem_type " + parsed_options.reward_function); + } joiner->set_reward_function(reward_function, true); } joiner->apply_cli_overrides(all, parsed_options); @@ -81,7 +92,7 @@ std::unique_ptr parser::get_external_parser(VW::workspace* all, const pa { const auto& infile_path = all->data_filename; const auto& infile_name = infile_path.substr(0, infile_path.find_last_of('.')); - const auto& infile_extension = infile_path.substr(infile_path.find_last_of(".") + 1); + const auto& infile_extension = infile_path.substr(infile_path.find_last_of('.') + 1); if (infile_extension == "dsjson") { @@ -97,16 +108,15 @@ std::unique_ptr parser::get_external_parser(VW::workspace* all, const pa return VW::make_unique(std::move(joiner), all->logger); } - else - { - joiner = VW::make_unique(all); - } + else { joiner = VW::make_unique(all); } if (parsed_options.multistep) { joiner = VW::make_unique(all); } apply_cli_overrides(joiner, all, parsed_options); if (all->options->was_supplied("extra_metrics")) - { all->example_parser->metrics = VW::make_unique(); } + { + all->example_parser->metrics = VW::make_unique(); + } return VW::make_unique(std::move(joiner), all->logger); } diff --git a/external_parser/unit_tests/CMakeLists.txt b/external_parser/unit_tests/CMakeLists.txt index fa8ba8661..dacce4744 100644 --- a/external_parser/unit_tests/CMakeLists.txt +++ b/external_parser/unit_tests/CMakeLists.txt @@ -1,4 +1,3 @@ -set(Boost_USE_STATIC_LIBS ON) find_package(Boost COMPONENTS unit_test_framework filesystem system program_options thread REQUIRED) set(TEST_SOURCES @@ -32,4 +31,23 @@ target_link_libraries(binary_parser_unit_tests Boost::filesystem ) +# Automatically set BOOST_TEST_DYN_LINK if the dependency is shared. +get_target_property(boost_test_target_type Boost::unit_test_framework TYPE) +if (boost_test_target_type STREQUAL SHARED_LIBRARY) + message(STATUS "Boost::unit_test_framework looks to be a shared library. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(binary_parser_unit_tests PRIVATE BOOST_TEST_DYN_LINK) +elseif(boost_test_target_type STREQUAL UNKNOWN_LIBRARY) + # Try inferring type if vcpkg is used + if (DEFINED VCPKG_TARGET_TRIPLET) + if (VCPKG_TARGET_TRIPLET EQUAL "x64-windows" OR VCPKG_TARGET_TRIPLET EQUAL "x86-windows" OR VCPKG_TARGET_TRIPLET EQUAL "arm64-osx-dynamic" OR VCPKG_TARGET_TRIPLET EQUAL "x64-osx-dynamic") + message(STATUS "Boost::unit_test_framework looks to be a shared library based on vcpkg triplet ${VCPKG_TARGET_TRIPLET}. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(binary_parser_unit_tests PRIVATE BOOST_TEST_DYN_LINK) + endif() + # If find_package is used then by default we're looking at a shared dependency unless Boost_USE_STATIC_LIBS was set. + elseif(NOT Boost_USE_STATIC_LIBS) + message(STATUS "Boost::unit_test_framework looks to be a shared library. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(binary_parser_unit_tests PRIVATE BOOST_TEST_DYN_LINK) + endif() +endif() + add_test(NAME binary_parser_unit_tests COMMAND binary_parser_unit_tests -- ${CMAKE_CURRENT_LIST_DIR}/test_files/) diff --git a/external_parser/unit_tests/test_common.cc b/external_parser/unit_tests/test_common.cc index c32ee46a4..8eccf6ce8 100644 --- a/external_parser/unit_tests/test_common.cc +++ b/external_parser/unit_tests/test_common.cc @@ -1,10 +1,9 @@ #include "test_common.h" +#include "vw/core/learner.h" #include "vw/core/parser.h" #include "vw/io/io_adapter.h" -namespace v2 = reinforcement_learning::messages::flatbuff::v2; - namespace endian { bool is_big_endian(void) @@ -61,7 +60,7 @@ void set_buffer_as_vw_input(const std::vector& buffer, VW::workspace* vw) vw->example_parser->input.add_file(VW::io::create_buffer_view(buffer.data(), buffer.size())); } -std::vector read_file(std::string file_name) +std::vector read_file(const std::string& file_name) { std::ifstream file; file.open(file_name, std::ios::binary | std::ios::ate); diff --git a/external_parser/unit_tests/test_common.h b/external_parser/unit_tests/test_common.h index 8db0aa426..ac14756c3 100644 --- a/external_parser/unit_tests/test_common.h +++ b/external_parser/unit_tests/test_common.h @@ -23,7 +23,7 @@ void clear_examples(VW::multi_ex& examples, VW::workspace* vw); void set_buffer_as_vw_input(const std::vector& buffer, VW::workspace* vw); -std::vector read_file(std::string file_name); +std::vector read_file(const std::string& file_name); std::string get_test_files_location(); diff --git a/external_parser/unit_tests/test_log_converter.cc b/external_parser/unit_tests/test_log_converter.cc index db2108923..c54dbb4eb 100644 --- a/external_parser/unit_tests/test_log_converter.cc +++ b/external_parser/unit_tests/test_log_converter.cc @@ -43,7 +43,9 @@ std::string get_json_event( examples.push_back(VW::new_unused_example(*vw)); while (vw->example_parser->reader(vw.get(), vw->example_parser->input, examples) > 0) - { examples.push_back(VW::new_unused_example(*vw)); } + { + examples.push_back(VW::new_unused_example(*vw)); + } std::ostringstream dsjson_stream; std::string outfile_name = get_test_files_location() + outfile_path; diff --git a/external_parser/unit_tests/test_metrics.cc b/external_parser/unit_tests/test_metrics.cc index fdfa010f5..604fda91e 100644 --- a/external_parser/unit_tests/test_metrics.cc +++ b/external_parser/unit_tests/test_metrics.cc @@ -4,6 +4,7 @@ #include "rapidjson/document.h" #include "test_common.h" #include "vw/config/options_cli.h" +#include "vw/core/learner.h" #include "vw/core/parse_primitives.h" #include "vw/io/logger.h" diff --git a/external_parser/unit_tests/test_reward_functions.cc b/external_parser/unit_tests/test_reward_functions.cc index a467d07a1..5660c45fd 100644 --- a/external_parser/unit_tests/test_reward_functions.cc +++ b/external_parser/unit_tests/test_reward_functions.cc @@ -10,7 +10,7 @@ namespace v2 = reinforcement_learning::messages::flatbuff::v2; const int DEFAULT_REWARD = -1000; const int DEFAULT_REWARD_APPRENTICE = 0; -std::vector get_float_rewards(std::string int_file_name, std::string obs_file_name, +std::vector get_float_rewards(const std::string& int_file_name, const std::string& obs_file_name, v2::RewardFunctionType reward_function_type, v2::LearningModeType learning_mode = v2::LearningModeType_Online, v2::ProblemType problem_type = v2::ProblemType_CB, float default_reward = DEFAULT_REWARD) { @@ -78,7 +78,9 @@ std::vector get_float_rewards(std::string int_file_name, std::string obs_ for (auto* example : examples) { if (!CB::ec_is_example_header(*example) && example->l.cb.costs.size() > 0) - { rewards.push_back(-1.f * example->l.cb.costs[0].cost); } + { + rewards.push_back(-1.f * example->l.cb.costs[0].cost); + } } } break; @@ -92,7 +94,9 @@ std::vector get_float_rewards(std::string int_file_name, std::string obs_ for (auto* example : examples) { if (example->l.conditional_contextual_bandit.type == VW::ccb_example_type::SLOT) - { rewards.push_back(-1.f * example->l.conditional_contextual_bandit.outcome->cost); } + { + rewards.push_back(-1.f * example->l.conditional_contextual_bandit.outcome->cost); + } } } break; @@ -107,8 +111,10 @@ std::vector get_float_rewards(std::string int_file_name, std::string obs_ for (auto* example : examples) { - if (example->l.slates.type == VW::slates::example_type::shared) - { rewards.push_back(-1.f * example->l.slates.cost); } + if (example->l.slates.type == VW::slates::example_type::SHARED) + { + rewards.push_back(-1.f * example->l.slates.cost); + } } } break; diff --git a/external_parser/unit_tests/test_timestamp_helper.cc b/external_parser/unit_tests/test_timestamp_helper.cc index 22cde6122..fe8985764 100644 --- a/external_parser/unit_tests/test_timestamp_helper.cc +++ b/external_parser/unit_tests/test_timestamp_helper.cc @@ -8,15 +8,17 @@ // copied from there (hasn't changed since it was checked in) // since we need the internals to check that time transformations are done // correctly -std::pair> gmt_now_and_timestamp() +std::pair> +gmt_now_and_timestamp() { const auto tp = std::chrono::system_clock::now(); const auto dp = date::floor(tp); const auto ymd = date::year_month_day(dp); const auto time = date::make_time(tp - dp); - return std::make_pair(v2::TimeStamp(int(ymd.year()), unsigned(ymd.month()), unsigned(ymd.day()), time.hours().count(), - time.minutes().count(), time.seconds().count(), time.subseconds().count()), + return std::make_pair(reinforcement_learning::messages::flatbuff::v2::TimeStamp(int(ymd.year()), + unsigned(ymd.month()), unsigned(ymd.day()), time.hours().count(), time.minutes().count(), + time.seconds().count(), time.subseconds().count()), tp); } diff --git a/external_parser/unit_tests/test_vw_binary_parser.cc b/external_parser/unit_tests/test_vw_binary_parser.cc index 5652bf8bd..30191743d 100644 --- a/external_parser/unit_tests/test_vw_binary_parser.cc +++ b/external_parser/unit_tests/test_vw_binary_parser.cc @@ -223,7 +223,9 @@ BOOST_AUTO_TEST_CASE(test_log_file_with_mismatched_payload_types) size_t total_size_of_examples = 0; // file contains 2 regular messages, both have wrong types set while (bp.parse_examples(vw.get(), vw->example_parser->input, examples)) - { total_size_of_examples += examples.size(); } + { + total_size_of_examples += examples.size(); + } // skipped first payload and read the second one BOOST_CHECK_EQUAL(total_size_of_examples, 0); diff --git a/external_parser/unit_tests/test_vw_external_parser.cc b/external_parser/unit_tests/test_vw_external_parser.cc index 558a1521d..cb6f1037a 100644 --- a/external_parser/unit_tests/test_vw_external_parser.cc +++ b/external_parser/unit_tests/test_vw_external_parser.cc @@ -4,6 +4,7 @@ #include "test_common.h" #include "vw/config/options_cli.h" #include "vw/core/ccb_label.h" +#include "vw/core/learner.h" #include "vw/core/parse_primitives.h" BOOST_AUTO_TEST_CASE(cb_simple) diff --git a/include/api_status.h b/include/api_status.h index e94fb6849..5ca6e23d0 100644 --- a/include/api_status.h +++ b/include/api_status.h @@ -207,8 +207,7 @@ int report_error(status_builder& sbuilder, const First& first, const Rest&... re * @brief Error reporting macro for just returning an error code. */ #define RETURN_ERROR(trace, status, code, ...) \ - do \ - { \ + do { \ if (status != nullptr) \ { \ reinforcement_learning::status_builder sbuilder(trace, status, reinforcement_learning::error_code::code); \ @@ -222,8 +221,7 @@ int report_error(status_builder& sbuilder, const First& first, const Rest&... re * @brief Error reporting macro that takes a list of parameters */ #define RETURN_ERROR_ARG(trace, status, code, ...) \ - do \ - { \ + do { \ if (status != nullptr) \ { \ reinforcement_learning::status_builder sbuilder(trace, status, reinforcement_learning::error_code::code); \ @@ -244,8 +242,7 @@ int report_error(status_builder& sbuilder, const First& first, const Rest&... re * @brief Error reporting macro to test and return on error */ #define RETURN_IF_FAIL(x) \ - do \ - { \ + do { \ int retval__LINE__ = (x); \ if (retval__LINE__ != 0) { return retval__LINE__; } \ } while (0) diff --git a/include/constants.h b/include/constants.h index 5cee4e177..7084061a8 100644 --- a/include/constants.h +++ b/include/constants.h @@ -4,8 +4,10 @@ namespace reinforcement_learning { #ifdef _WIN32 const char* const PATH_DELIMITER = "\\"; +const char* const DEV_NULL = "nul"; #else const char* const PATH_DELIMITER = "/"; +const char* const DEV_NULL = "/dev/null"; #endif namespace name @@ -34,6 +36,7 @@ const char* const EPISODE_EH_KEY_NAME = "episode.eventhub.keyname"; const char* const EPISODE_EH_KEY = "episode.eventhub.key"; const char* const EPISODE_EH_TASKS_LIMIT = "episode.eventhub.tasks_limit"; const char* const EPISODE_EH_MAX_HTTP_RETRIES = "episode.eventhub.max_http_retries"; +const char* const EPISODE_EH_MAX_HTTP_RETRY_DURATION_MS = "episode.eventhub.max_http_retry_duration_ms"; const char* const EPISODE_SENDER_IMPLEMENTATION = "episode.sender.implementation"; // Interaction @@ -43,6 +46,7 @@ const char* const INTERACTION_EH_KEY_NAME = "interaction.eventhub.keyname"; const char* const INTERACTION_EH_KEY = "interaction.eventhub.key"; const char* const INTERACTION_EH_TASKS_LIMIT = "interaction.eventhub.tasks_limit"; const char* const INTERACTION_EH_MAX_HTTP_RETRIES = "interaction.eventhub.max_http_retries"; +const char* const INTERACTION_EH_MAX_HTTP_RETRY_DURATION_MS = "interaction.eventhub.max_http_retry_duration_ms"; const char* const INTERACTION_SEND_HIGH_WATER_MARK = "interaction.send.highwatermark"; const char* const INTERACTION_SEND_QUEUE_MAX_CAPACITY_KB = "interaction.send.queue.maxcapacity.kb"; const char* const INTERACTION_SEND_BATCH_INTERVAL_MS = "interaction.send.batchintervalms"; @@ -53,6 +57,7 @@ const char* const INTERACTION_QUEUE_MODE = "interaction.queue.mode"; const char* const INTERACTION_HTTP_API_HOST = "interaction.http.api.host"; const char* const INTERACTION_APIM_TASKS_LIMIT = "interaction.apim.tasks_limit"; const char* const INTERACTION_APIM_MAX_HTTP_RETRIES = "interaction.apim.max_http_retries"; +const char* const INTERACTION_APIM_MAX_HTTP_RETRY_DURATION_MS = "interaction.apim.max_http_retry_duration_ms"; const char* const INTERACTION_SUBSAMPLE_RATE = "interaction.subsample.rate"; // Observation @@ -62,6 +67,7 @@ const char* const OBSERVATION_EH_KEY_NAME = "observation.eventhub.keyname"; const char* const OBSERVATION_EH_KEY = "observation.eventhub.key"; const char* const OBSERVATION_EH_TASKS_LIMIT = "observation.eventhub.tasks_limit"; const char* const OBSERVATION_EH_MAX_HTTP_RETRIES = "observation.eventhub.max_http_retries"; +const char* const OBSERVATION_EH_MAX_HTTP_RETRY_DURATION_MS = "observation.eventhub.max_http_retry_duration_ms"; const char* const OBSERVATION_SEND_HIGH_WATER_MARK = "observation.send.highwatermark"; const char* const OBSERVATION_SEND_QUEUE_MAX_CAPACITY_KB = "observation.send.queue.maxcapacity.kb"; const char* const OBSERVATION_SEND_BATCH_INTERVAL_MS = "observation.send.batchintervalms"; @@ -71,6 +77,7 @@ const char* const OBSERVATION_QUEUE_MODE = "observation.queue.mode"; const char* const OBSERVATION_HTTP_API_HOST = "observation.http.api.host"; const char* const OBSERVATION_APIM_TASKS_LIMIT = "observation.apim.tasks_limit"; const char* const OBSERVATION_APIM_MAX_HTTP_RETRIES = "observation.apim.max_http_retries"; +const char* const OBSERVATION_APIM_MAX_HTTP_RETRY_DURATION_MS = "observation.apim.max_http_retry_duration_ms"; const char* const OBSERVATION_SUBSAMPLE_RATE = "observation.subsample.rate"; // global sender properties @@ -95,6 +102,15 @@ const char* const MODEL_FILE_NAME = "model_file_loader.file_name"; const char* const MODEL_FILE_MUST_EXIST = "model_file_loader.file_must_exist"; const char* const ZSTD_COMPRESSION_LEVEL = "zstd.compression_level"; + +#ifdef RL_BUILD_FEDERATION +// local joiner for federated learning +const char* const JOINER_EUD_DURATION = "eud.duration"; +const char* const JOINER_PROBLEM_TYPE = "joiner.problem.type"; +const char* const JOINER_REWARD_FUNCTION = "joiner.reward.function"; +const char* const JOINER_LEARNING_MODE = "joiner.learning.mode"; +#endif + } // namespace name } // namespace reinforcement_learning @@ -135,6 +151,30 @@ const int DEFAULT_VW_POOL_INIT_SIZE = 4; const int DEFAULT_PROTOCOL_VERSION = 1; const char* const DEFAULT_AUDIT_OUTPUT_PATH = "audit"; +#ifdef RL_BUILD_FEDERATION +// Configuration values for local joiner +const char* const PROBLEM_TYPE_UNKNOWN = "PROBLEM_TYPE_UNKNOWN"; +const char* const PROBLEM_TYPE_CB = "PROBLEM_TYPE_CB"; +const char* const PROBLEM_TYPE_CCB = "PROBLEM_TYPE_CCB"; +const char* const PROBLEM_TYPE_SLATES = "PROBLEM_TYPE_SLATES"; +const char* const PROBLEM_TYPE_CA = "PROBLEM_TYPE_CA"; +const char* const PROBLEM_TYPE_MULTISTEP = "PROBLEM_TYPE_MULTISTEP"; +const char* const REWARD_FUNCTION_EARLIEST = "REWARD_FUNCTION_EARLIEST"; +const char* const REWARD_FUNCTION_AVERAGE = "REWARD_FUNCTION_AVERAGE"; +const char* const REWARD_FUNCTION_MEDIAN = "REWARD_FUNCTION_MEDIAN"; +const char* const REWARD_FUNCTION_SUM = "REWARD_FUNCTION_SUM"; +const char* const REWARD_FUNCTION_MIN = "REWARD_FUNCTION_MEAN"; +const char* const REWARD_FUNCTION_MAX = "REWARD_FUNCTION_MAX"; +#endif + +// These are outside of #ifdef section so that we can recognize them as invalid +// configuration options when rlclientlib is compiled without RL_BUILD_FEDERATION +// +// Use local_loop_controller for model data +const char* const LOCAL_LOOP_MODEL_DATA = "LOCAL_LOOP_MODEL_DATA"; +// Send events to local_loop_controller +const char* const LOCAL_LOOP_SENDER = "LOCAL_LOOP_SENDER"; + const char* get_default_episode_sender(); const char* get_default_observation_sender(); const char* get_default_interaction_sender(); diff --git a/include/error_callback_fn.h b/include/error_callback_fn.h index 749783560..357d9c544 100644 --- a/include/error_callback_fn.h +++ b/include/error_callback_fn.h @@ -5,8 +5,7 @@ namespace reinforcement_learning { #define ERROR_CALLBACK(fn, status) \ - do \ - { \ + do { \ if (fn != nullptr) { fn->report_error(status); } \ } while (0) @@ -28,10 +27,7 @@ class error_callback_fn { std::lock_guard lock(_mutex); if (fn != nullptr) { _fn = std::bind(fn, std::placeholders::_1, context); } - else - { - _fn = nullptr; - } + else { _fn = nullptr; } } template @@ -39,10 +35,7 @@ class error_callback_fn { std::lock_guard lock(_mutex); if (fn != nullptr) { _fn = std::bind(fn, std::placeholders::_1, context); } - else - { - _fn = nullptr; - } + else { _fn = nullptr; } } void report_error(api_status& s); diff --git a/include/trace_logger.h b/include/trace_logger.h index eb13cba8f..9bb6b3adb 100644 --- a/include/trace_logger.h +++ b/include/trace_logger.h @@ -17,8 +17,7 @@ const char* const STR_LEVEL_ERROR = "ERROR"; const char* get_log_level_string(int log_level); #define TRACE_LOG(logger, level, msg) \ - do \ - { \ + do { \ if (logger != nullptr) { logger->log(level, msg); } \ } while (0) diff --git a/rlclientlib/CMakeLists.txt b/rlclientlib/CMakeLists.txt index 63e94a720..b2cb2f251 100644 --- a/rlclientlib/CMakeLists.txt +++ b/rlclientlib/CMakeLists.txt @@ -1,241 +1,276 @@ -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/") -# First try to find the config version. Newer, used by vcpkg etc -find_package(Flatbuffers CONFIG QUIET) -if(FLATBUFFERS_FOUND) - get_property(flatc_location TARGET flatbuffers::flatc PROPERTY LOCATION) -else() - # Fallback to the old version - find_package(Flatbuffers MODULE REQUIRED) - set(flatc_location ${FLATBUFFERS_FLATC_EXECUTABLE}) -endif() - -include(FlatbufferUtils) - -set(RL_FLAT_BUFFER_FILES_V1 - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/DecisionRankingEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/Metadata.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/OutcomeEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/RankingEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/SlatesEvent.fbs" ) - -set(RL_FLAT_BUFFER_FILES_V2 - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CaEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CbEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/DedupInfo.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/Event.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/FileFormat.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/LearningModeType.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/Metadata.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/MultiSlotEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/MultiStepEvent.fbs" - "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/OutcomeEvent.fbs") - -add_flatbuffer_schema( - TARGET fbgenerator_v1 - SCHEMAS ${RL_FLAT_BUFFER_FILES_V1} - OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generated/v1/ - FLATC_EXE ${flatc_location} -) - -add_flatbuffer_schema( - TARGET fbgenerator_v2 - SCHEMAS ${RL_FLAT_BUFFER_FILES_V2} - OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generated/v2/ - FLATC_EXE ${flatc_location} -) - -set(PROJECT_SOURCES - api_status.cc - console_tracer.cc - constants.cc - continuous_action_response.cc - decision_response.cc - dedup.cc - error_callback_fn.cc - factory_resolver.cc - federation/local_client.cc - generic_event.cc - learning_mode.cc - live_model.cc - live_model_impl.cc - logger/endian.cc - logger/event_logger.cc - logger/file/file_logger.cc - logger/flatbuffer_allocator.cc - logger/logger_extensions.cc - logger/logger_facade.cc - logger/preamble.cc - logger/preamble_sender.cc - model_mgmt/data_callback_fn.cc - model_mgmt/empty_data_transport.cc - model_mgmt/file_model_loader.cc - model_mgmt/model_downloader.cc - model_mgmt/model_mgmt.cc - multistep.cc - multi_slot_response.cc - multi_slot_response_detailed.cc - ranking_event.cc - ranking_response.cc - sampling.cc - serialization/payload_serializer.cc - slot_ranking.cc - time_helper.cc - trace_logger.cc - utility/config_helper.cc - utility/config_utility.cc - utility/configuration.cc - utility/context_helper.cc - utility/data_buffer.cc - utility/data_buffer_streambuf.cc - vw_model/pdf_model.cc - vw_model/safe_vw.cc - utility/stl_container_adapter.cc - utility/str_util.cc - utility/watchdog.cc - vw_model/vw_model.cc -) - -if(vw_USE_AZURE_FACTORIES) - list(APPEND PROJECT_SOURCES - azure_factories.cc - model_mgmt/restapi_data_transport.cc - utility/eventhub_http_authorization.cc - utility/header_authorization.cc - utility/http_client.cc - utility/http_helper.cc - ) -endif() - -set(PROJECT_PUBLIC_HEADERS - ../include/action_flags.h - ../include/api_status.h - ../include/config_utility.h - ../include/configuration.h - ../include/constants.h - ../include/container_iterator.h - ../include/continuous_action_response.h - ../include/data_buffer.h - ../include/decision_response.h - ../include/err_constants.h - ../include/error_callback_fn.h - ../include/errors_data.h - ../include/factory_resolver.h - ../include/future_compat.h - ../include/internal_constants.h - ../include/live_model.h - ../include/model_mgmt.h - ../include/multi_slot_response.h - ../include/multi_slot_response_detailed.h - ../include/multistep.h - ../include/object_factory.h - ../include/personalization.h - ../include/ranking_response.h - ../include/rl_string_view.h - ../include/sender.h - ../include/slot_ranking.h - ../include/str_util.h - ../include/trace_logger.h -) - -set(PROJECT_PRIVATE_HEADERS - console_tracer.h - dedup.h - federation/federated_client.h - federation/joined_log_provider.h - federation/local_client.h - generic_event.h - live_model_impl.h - logger/async_batcher.h - logger/event_logger.h - logger/logger_facade.h - model_mgmt/data_callback_fn.h - model_mgmt/empty_data_transport.h - model_mgmt/file_model_loader.h - model_mgmt/model_downloader.h - moving_queue.h - ranking_event.h - sampling.h - serialization/fb_serializer.h - serialization/json_serializer.h - utility/config_helper.h - utility/context_helper.h - utility/interruptable_sleeper.h - utility/object_pool.h - utility/periodic_background_proc.h - utility/watchdog.h - vw_model/pdf_model.h - vw_model/safe_vw.h - vw_model/vw_model.h -) - -if(vw_USE_AZURE_FACTORIES) - list(APPEND PROJECT_PRIVATE_HEADERS - azure_factories.h - logger/http_transport_client.h - model_mgmt/restapi_data_transport.h - utility/eventhub_http_authorization.h - utility/header_authorization.h - utility/http_client.h - utility/http_helper.h - ) -endif() - -source_group("Sources" FILES ${PROJECT_SOURCES}) -source_group("Public headers" FILES ${PROJECT_PUBLIC_HEADERS}) -source_group("Private headers" FILES ${PROJECT_PRIVATE_HEADERS}) - -add_library(rlclientlib ${PROJECT_SOURCES} ${PROJECT_PUBLIC_HEADERS} ${PROJECT_PRIVATE_HEADERS}) -add_dependencies(rlclientlib fbgenerator_v1 fbgenerator_v2) -set_target_properties(rlclientlib PROPERTIES POSITION_INDEPENDENT_CODE ON) - -if(WIN32) - set_target_properties(rlclientlib PROPERTIES DEBUG_POSTFIX d) -endif() - -if(vw_USE_AZURE_FACTORIES) - target_compile_definitions(rlclientlib PRIVATE USE_AZURE_FACTORIES) -endif() - - -if(RL_USE_ZSTD) - target_compile_definitions(rlclientlib PRIVATE USE_ZSTD) - target_link_libraries(rlclientlib PRIVATE libzstd_static) - target_include_directories(rlclientlib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ext_libs/zstd/lib/) -endif() - -target_compile_definitions(rlclientlib PRIVATE FLATBUFFERS_SPAN_MINIMAL) - -target_include_directories( rlclientlib - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${FLATBUFFERS_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/../ext_libs/date - ) -target_link_libraries(rlclientlib PUBLIC Boost::system vw_common vw_core PRIVATE RapidJSON) - -if (vw_USE_AZURE_FACTORIES) - target_link_libraries(rlclientlib PUBLIC cpprestsdk::cpprest OpenSSL::SSL OpenSSL::Crypto) -endif() - -# Consuming Boost uuid requires BCrypt, normally this is automatically linked but vcpkg turns this feature off. -if(WIN32) - target_link_libraries(rlclientlib PUBLIC bcrypt) -endif() - -# On MacOS linking fails unless we explicitly add Boost::thread. It seems like CppRestSDK isn't exporting dependencies properly. -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(rlclientlib PUBLIC Boost::thread) -endif() - -# Set paths for installing library and header files -install( - TARGETS rlclientlib - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -) -install( - FILES ${PROJECT_PUBLIC_HEADERS} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rlclientlib -) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/../cmake/Modules/") + +# First try to find the config version. Newer, used by vcpkg etc +find_package(Flatbuffers CONFIG) +if(TARGET flatbuffers::flatbuffers AND TARGET flatbuffers::flatc) + get_property(flatc_location TARGET flatbuffers::flatc PROPERTY LOCATION) + message(STATUS "Found Flatbuffers with CONFIG, flatc located at: ${flatc_location}") +else() + # Fallback to the old version + find_package(Flatbuffers MODULE REQUIRED) + set(flatc_location ${FLATBUFFERS_FLATC_EXECUTABLE}) + message(STATUS "Found Flatbuffers with MODULE, flatc located at: ${flatc_location}") +endif() + +include(FlatbufferUtils) + +set(RL_FLAT_BUFFER_FILES_V1 + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/DecisionRankingEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/Metadata.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/OutcomeEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/RankingEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v1/SlatesEvent.fbs" ) + +set(RL_FLAT_BUFFER_FILES_V2 + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CaEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/CbEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/DedupInfo.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/Event.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/FileFormat.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/LearningModeType.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/Metadata.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/MultiSlotEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/MultiStepEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/OutcomeEvent.fbs" + "${CMAKE_CURRENT_SOURCE_DIR}/schema/v2/ProblemType.fbs" +) + +add_flatbuffer_schema( + TARGET fbgenerator_v1 + SCHEMAS ${RL_FLAT_BUFFER_FILES_V1} + OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generated/v1/ + FLATC_EXE ${flatc_location} +) + +add_flatbuffer_schema( + TARGET fbgenerator_v2 + SCHEMAS ${RL_FLAT_BUFFER_FILES_V2} + OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/generated/v2/ + FLATC_EXE ${flatc_location} +) + +set(PROJECT_SOURCES + api_status.cc + console_tracer.cc + constants.cc + continuous_action_response.cc + decision_response.cc + dedup.cc + error_callback_fn.cc + factory_resolver.cc + generic_event.cc + learning_mode.cc + live_model.cc + live_model_impl.cc + logger/endian.cc + logger/event_logger.cc + logger/file/file_logger.cc + logger/flatbuffer_allocator.cc + logger/logger_extensions.cc + logger/logger_facade.cc + logger/preamble.cc + logger/preamble_sender.cc + model_mgmt/data_callback_fn.cc + model_mgmt/empty_data_transport.cc + model_mgmt/file_model_loader.cc + model_mgmt/model_downloader.cc + model_mgmt/model_mgmt.cc + multistep.cc + multi_slot_response.cc + multi_slot_response_detailed.cc + ranking_event.cc + ranking_response.cc + sampling.cc + serialization/payload_serializer.cc + slot_ranking.cc + time_helper.cc + trace_logger.cc + utility/config_helper.cc + utility/config_utility.cc + utility/configuration.cc + utility/context_helper.cc + utility/data_buffer.cc + utility/data_buffer_streambuf.cc + vw_model/pdf_model.cc + vw_model/safe_vw.cc + utility/stl_container_adapter.cc + utility/str_util.cc + utility/watchdog.cc + vw_model/vw_model.cc +) + +if(vw_USE_AZURE_FACTORIES) + list(APPEND PROJECT_SOURCES + azure_factories.cc + model_mgmt/restapi_data_transport.cc + utility/eventhub_http_authorization.cc + utility/header_authorization.cc + utility/http_client.cc + utility/http_helper.cc + ) +endif() + +if(RL_BUILD_FEDERATION) + list(APPEND PROJECT_SOURCES + federation/local_client.cc + federation/local_loop_controller.cc + federation/sender_joined_log_provider.cc + federation/vw_trainable_model.cc + ) +endif() + +set(PROJECT_PUBLIC_HEADERS + ../include/action_flags.h + ../include/api_status.h + ../include/config_utility.h + ../include/configuration.h + ../include/constants.h + ../include/container_iterator.h + ../include/continuous_action_response.h + ../include/data_buffer.h + ../include/decision_response.h + ../include/err_constants.h + ../include/error_callback_fn.h + ../include/errors_data.h + ../include/factory_resolver.h + ../include/future_compat.h + ../include/internal_constants.h + ../include/live_model.h + ../include/model_mgmt.h + ../include/multi_slot_response.h + ../include/multi_slot_response_detailed.h + ../include/multistep.h + ../include/object_factory.h + ../include/personalization.h + ../include/ranking_response.h + ../include/rl_string_view.h + ../include/sender.h + ../include/slot_ranking.h + ../include/str_util.h + ../include/trace_logger.h +) + +set(PROJECT_PRIVATE_HEADERS + console_tracer.h + dedup.h + generic_event.h + live_model_impl.h + logger/async_batcher.h + logger/event_logger.h + logger/logger_facade.h + model_mgmt/data_callback_fn.h + model_mgmt/empty_data_transport.h + model_mgmt/file_model_loader.h + model_mgmt/model_downloader.h + moving_queue.h + ranking_event.h + sampling.h + serialization/fb_serializer.h + serialization/json_serializer.h + utility/config_helper.h + utility/context_helper.h + utility/interruptable_sleeper.h + utility/object_pool.h + utility/periodic_background_proc.h + utility/watchdog.h + vw_model/pdf_model.h + vw_model/safe_vw.h + vw_model/vw_model.h +) + +if(vw_USE_AZURE_FACTORIES) + list(APPEND PROJECT_PRIVATE_HEADERS + azure_factories.h + logger/http_transport_client.h + model_mgmt/restapi_data_transport.h + utility/eventhub_http_authorization.h + utility/header_authorization.h + utility/http_client.h + utility/http_helper.h + ) +endif() + +if(RL_BUILD_FEDERATION) + list(APPEND PROJECT_PRIVATE_HEADERS + federation/event_sink.h + federation/federated_client.h + federation/joined_log_provider.h + federation/local_client.h + federation/local_loop_controller.h + federation/sender_joined_log_provider.h + federation/vw_trainable_model.h + ) +endif() + +source_group("Sources" FILES ${PROJECT_SOURCES}) +source_group("Public headers" FILES ${PROJECT_PUBLIC_HEADERS}) +source_group("Private headers" FILES ${PROJECT_PRIVATE_HEADERS}) + +add_library(rlclientlib ${PROJECT_SOURCES} ${PROJECT_PUBLIC_HEADERS} ${PROJECT_PRIVATE_HEADERS}) +add_dependencies(rlclientlib fbgenerator_v1 fbgenerator_v2) +set_target_properties(rlclientlib PROPERTIES POSITION_INDEPENDENT_CODE ON) + +if(WIN32) + set_target_properties(rlclientlib PROPERTIES DEBUG_POSTFIX d) +endif() + +if(vw_USE_AZURE_FACTORIES) + target_compile_definitions(rlclientlib PRIVATE USE_AZURE_FACTORIES) +endif() + + +if(RL_USE_ZSTD) + target_compile_definitions(rlclientlib PRIVATE USE_ZSTD) + target_link_libraries(rlclientlib PRIVATE libzstd_static) + target_include_directories(rlclientlib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ext_libs/zstd/lib/) +endif() + +target_compile_definitions(rlclientlib PRIVATE FLATBUFFERS_SPAN_MINIMAL) + +target_include_directories(rlclientlib + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../include + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../ext_libs/date +) + +target_link_libraries(rlclientlib PUBLIC Boost::system vw_common vw_core PRIVATE RapidJSON) + +# If flatbuffers found via CONFIG, add its target as a library dependency +# Otherwise, the flatbuffers MODULE defines FLATBUFFERS_INCLUDE_DIR to add to the include path +if(TARGET flatbuffers::flatbuffers) + target_link_libraries(rlclientlib PRIVATE flatbuffers::flatbuffers) +else() + target_include_directories(rlclientlib PRIVATE ${FLATBUFFERS_INCLUDE_DIR}) +endif() + +if (vw_USE_AZURE_FACTORIES) + target_link_libraries(rlclientlib PUBLIC cpprestsdk::cpprest OpenSSL::SSL OpenSSL::Crypto) +endif() + +# Consuming Boost uuid requires BCrypt, normally this is automatically linked but vcpkg turns this feature off. +if(WIN32) + target_link_libraries(rlclientlib PUBLIC bcrypt) +endif() + +# On MacOS linking fails unless we explicitly add Boost::thread. It seems like CppRestSDK isn't exporting dependencies properly. +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + target_link_libraries(rlclientlib PUBLIC Boost::thread) +endif() + +# Link to external binary parser for local joining code +if (RL_BUILD_EXTERNAL_PARSER) + target_link_libraries(rlclientlib PUBLIC rl_binary_parser) +endif() + +# Set paths for installing library and header files +install( + TARGETS rlclientlib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install( + FILES ${PROJECT_PUBLIC_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rlclientlib +) diff --git a/rlclientlib/azure_factories.cc b/rlclientlib/azure_factories.cc index dd624eb83..ba78d8ff2 100644 --- a/rlclientlib/azure_factories.cc +++ b/rlclientlib/azure_factories.cc @@ -80,19 +80,21 @@ int episode_sender_create(i_sender** retval, const u::configuration& cfg, error_ const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = - new http_transport_client(client, cfg.get_int(name::EPISODE_EH_TASKS_LIMIT, 16), - cfg.get_int(name::EPISODE_EH_MAX_HTTP_RETRIES, 4), trace_logger, error_cb); + *retval = new http_transport_client(client, + cfg.get_int(name::EPISODE_EH_TASKS_LIMIT, 16), cfg.get_int(name::EPISODE_EH_MAX_HTTP_RETRIES, 4), + std::chrono::milliseconds(cfg.get_int(name::EPISODE_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, + error_cb); return error_code::success; } int create_apim_http_api_sender(i_sender** retval, const u::configuration& cfg, const char* api_host, int tasks_limit, - int max_http_retries, error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) + int max_http_retries, std::chrono::milliseconds max_http_retry_duration, error_callback_fn* error_cb, + i_trace* trace_logger, api_status* status) { i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(api_host, cfg, &client, status)); - *retval = - new http_transport_client(client, tasks_limit, max_http_retries, trace_logger, error_cb); + *retval = new http_transport_client( + client, tasks_limit, max_http_retries, max_http_retry_duration, trace_logger, error_cb); return error_code::success; } @@ -102,7 +104,9 @@ int observation_api_sender_create(i_sender** retval, const u::configuration& cfg { const auto* const api_host = cfg.get(name::OBSERVATION_HTTP_API_HOST, "localhost:8080"); return create_apim_http_api_sender(retval, cfg, api_host, cfg.get_int(name::OBSERVATION_APIM_TASKS_LIMIT, 16), - cfg.get_int(name::OBSERVATION_APIM_MAX_HTTP_RETRIES, 4), error_cb, trace_logger, status); + cfg.get_int(name::OBSERVATION_APIM_MAX_HTTP_RETRIES, 4), + std::chrono::milliseconds(cfg.get_int(name::OBSERVATION_APIM_MAX_HTTP_RETRY_DURATION_MS, 3600000)), error_cb, + trace_logger, status); } // Creates i_sender object for sending interactions data to the apim endpoint. @@ -111,7 +115,9 @@ int interaction_api_sender_create(i_sender** retval, const u::configuration& cfg { const auto* const api_host = cfg.get(name::INTERACTION_HTTP_API_HOST, "localhost:8080"); return create_apim_http_api_sender(retval, cfg, api_host, cfg.get_int(name::INTERACTION_APIM_TASKS_LIMIT, 16), - cfg.get_int(name::INTERACTION_APIM_MAX_HTTP_RETRIES, 4), error_cb, trace_logger, status); + cfg.get_int(name::INTERACTION_APIM_MAX_HTTP_RETRIES, 4), + std::chrono::milliseconds(cfg.get_int(name::INTERACTION_APIM_MAX_HTTP_RETRY_DURATION_MS, 3600000)), error_cb, + trace_logger, status); } // Creates i_sender object for sending observations data to the event hub. @@ -123,9 +129,10 @@ int observation_sender_create(i_sender** retval, const u::configuration& cfg, er const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = - new http_transport_client(client, cfg.get_int(name::OBSERVATION_EH_TASKS_LIMIT, 16), - cfg.get_int(name::OBSERVATION_EH_MAX_HTTP_RETRIES, 4), trace_logger, error_cb); + *retval = new http_transport_client(client, + cfg.get_int(name::OBSERVATION_EH_TASKS_LIMIT, 16), cfg.get_int(name::OBSERVATION_EH_MAX_HTTP_RETRIES, 4), + std::chrono::milliseconds(cfg.get_int(name::OBSERVATION_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, + error_cb); return error_code::success; } @@ -138,9 +145,10 @@ int interaction_sender_create(i_sender** retval, const u::configuration& cfg, er const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = - new http_transport_client(client, cfg.get_int(name::INTERACTION_EH_TASKS_LIMIT, 16), - cfg.get_int(name::INTERACTION_EH_MAX_HTTP_RETRIES, 4), trace_logger, error_cb); + *retval = new http_transport_client(client, + cfg.get_int(name::INTERACTION_EH_TASKS_LIMIT, 16), cfg.get_int(name::INTERACTION_EH_MAX_HTTP_RETRIES, 4), + std::chrono::milliseconds(cfg.get_int(name::INTERACTION_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, + error_cb); return error_code::success; } } // namespace reinforcement_learning diff --git a/rlclientlib/dedup.cc b/rlclientlib/dedup.cc index f7cbebadf..50587d71e 100644 --- a/rlclientlib/dedup.cc +++ b/rlclientlib/dedup.cc @@ -30,10 +30,7 @@ generic_event::object_id_t dedup_dict::add_object(const char* start, size_t leng auto hash = hash_content(start, length); auto it = _entries.find(hash); if (it == _entries.end()) { _entries.insert({hash, dict_entry(start, length)}); } - else - { - ++it->second._count; - } + else { ++it->second._count; } return hash; } @@ -107,9 +104,13 @@ int zstd_compressor::decompress(generic_event::payload_buffer_t& buf, api_status { size_t buff_size = ZSTD_getFrameContentSize(buf.data(), buf.size()); if (buff_size == ZSTD_CONTENTSIZE_ERROR) - { RETURN_ERROR_ARG(nullptr, status, compression_error, "Invalid compressed content."); } + { + RETURN_ERROR_ARG(nullptr, status, compression_error, "Invalid compressed content."); + } if (buff_size == ZSTD_CONTENTSIZE_UNKNOWN) - { RETURN_ERROR_ARG(nullptr, status, compression_error, "Unknown compressed size."); } + { + RETURN_ERROR_ARG(nullptr, status, compression_error, "Unknown compressed size."); + } std::unique_ptr data(fb::DefaultAllocator().allocate(buff_size)); size_t res = ZSTD_decompress(data.get(), buff_size, buf.data(), buf.size()); @@ -187,10 +188,7 @@ int action_dict_builder::add(const generic_event::object_list_t& object_ids, api _used_objects.insert({aid, 1}); _size_estimate += sizeof(size_t) + content.size(); } - else - { - ++it->second; - } + else { ++it->second; } } return error_code::success; } diff --git a/rlclientlib/dedup_internals.h b/rlclientlib/dedup_internals.h index f25514186..223d8754f 100644 --- a/rlclientlib/dedup_internals.h +++ b/rlclientlib/dedup_internals.h @@ -134,7 +134,9 @@ int dedup_state::get_all_values(I start, I end, generic_event::object_list_t& ac { auto content = _dict.get_object(start->first); if (content.size() == 0) - { RETURN_ERROR_LS(nullptr, status, compression_error) << "Key not found while building batch dictionary"; } + { + RETURN_ERROR_LS(nullptr, status, compression_error) << "Key not found while building batch dictionary"; + } action_ids.push_back(start->first); action_values.push_back(content); } @@ -149,7 +151,9 @@ int dedup_state::remove_all_values(I start, I end, api_status* status) for (; start != end; ++start) { if (!_dict.remove_object(start->first, start->second)) - { RETURN_ERROR_LS(nullptr, status, compression_error) << "Key not found while pruning dedup_dict"; } + { + RETURN_ERROR_LS(nullptr, status, compression_error) << "Key not found while pruning dedup_dict"; + } } return error_code::success; diff --git a/rlclientlib/extensions/CMakeLists.txt b/rlclientlib/extensions/CMakeLists.txt index 87daf7bc8..5d9e3acb9 100644 --- a/rlclientlib/extensions/CMakeLists.txt +++ b/rlclientlib/extensions/CMakeLists.txt @@ -1,6 +1,3 @@ -# Flags for building optional extensions -set(rlclientlib_BUILD_ONNXRUNTIME_EXTENSION OFF CACHE BOOL "Build OnnxRuntime Inference Extension") - if (rlclientlib_BUILD_ONNXRUNTIME_EXTENSION) message("Building RLClientLib Extension: OnnxRuntime") add_subdirectory(onnx) diff --git a/rlclientlib/extensions/onnx/src/onnx_input.h b/rlclientlib/extensions/onnx/src/onnx_input.h index b57a7ea69..a652bff09 100644 --- a/rlclientlib/extensions/onnx/src/onnx_input.h +++ b/rlclientlib/extensions/onnx/src/onnx_input.h @@ -2,7 +2,7 @@ #include "api_status.h" #include "rl_string_view.h" -#include +#include #include #include diff --git a/rlclientlib/extensions/onnx/src/onnx_model.cc b/rlclientlib/extensions/onnx/src/onnx_model.cc index c8c158ee2..7d4241941 100644 --- a/rlclientlib/extensions/onnx/src/onnx_model.cc +++ b/rlclientlib/extensions/onnx/src/onnx_model.cc @@ -101,12 +101,8 @@ int onnx_model::update(const model_management::model_data& data, bool& model_rea size_t output_count = new_session->GetOutputCount(); for (output_index = 0; output_index < output_count; output_index++) { - char* output_name = new_session->GetOutputName(output_index, DefaultOnnxAllocator); - - if (_output_name == output_name) { found_output = true; } - - DefaultOnnxAllocator.Free(output_name); - + auto output_name = new_session->GetOutputNameAllocated(output_index, DefaultOnnxAllocator); + if (_output_name == output_name.get()) { found_output = true; } if (found_output) { break; } } @@ -120,7 +116,9 @@ int onnx_model::update(const model_management::model_data& data, bool& model_rea Ort::TypeInfo output_type_info = new_session->GetOutputTypeInfo(output_index); if (output_type_info.GetONNXType() != ONNX_TYPE_TENSOR || output_type_info.GetTensorTypeAndShapeInfo().GetElementType() != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) - { RETURN_ERROR_LS(_trace_logger, status, model_update_error) << "Invalid output type. Expected: tensor."; } + { + RETURN_ERROR_LS(_trace_logger, status, model_update_error) << "Invalid output type. Expected: tensor."; + } // TODO: Should we add additional checks to make sure the next two sets are atomic? _output_index = output_index; diff --git a/rlclientlib/extensions/onnx/src/onnx_model.h b/rlclientlib/extensions/onnx/src/onnx_model.h index 88425e2c6..d57aa943a 100644 --- a/rlclientlib/extensions/onnx/src/onnx_model.h +++ b/rlclientlib/extensions/onnx/src/onnx_model.h @@ -2,7 +2,7 @@ #include "err_constants.h" #include "model_mgmt.h" -#include +#include #include diff --git a/rlclientlib/extensions/onnx/src/tensor_parser.cc b/rlclientlib/extensions/onnx/src/tensor_parser.cc index 6d37292f4..bdc9d7602 100644 --- a/rlclientlib/extensions/onnx/src/tensor_parser.cc +++ b/rlclientlib/extensions/onnx/src/tensor_parser.cc @@ -176,14 +176,8 @@ struct parse_context { _running = _running | (c - '0' + 52); // 52 - 61 } - else if (c == '+') - { - _running = _running | 62; - } - else if (c == '/') - { - _running = _running | 63; - } + else if (c == '+') { _running = _running | 62; } + else if (c == '/') { _running = _running | 63; } else { // TODO: Bad character @@ -272,10 +266,7 @@ class escaped_string _value.push_back(c); _in_escape = false; } - else if (c == escape) - { - _in_escape = true; - } + else if (c == escape) { _in_escape = true; } return true; } @@ -349,14 +340,15 @@ bool parse(parser_context& context) errors::error_context error_context(context._errors, "while parsing tensor notation", context); - do - { + do { std::string name; bytes_t tensor_shape_bytes; bytes_t tensor_data_bytes; if (!parse_tensor_name_value(reading_head, name, tensor_shape_bytes, tensor_data_bytes, error_context)) - { return false; } + { + return false; + } context._input_builder.push_input( std::move(name), std::move(std::make_pair(tensor_shape_bytes, tensor_data_bytes))); diff --git a/rlclientlib/factory_resolver.cc b/rlclientlib/factory_resolver.cc index 66536ce51..5610c0c7e 100644 --- a/rlclientlib/factory_resolver.cc +++ b/rlclientlib/factory_resolver.cc @@ -1,5 +1,6 @@ #include "factory_resolver.h" +#include "api_status.h" #include "constants.h" #include "err_constants.h" #include "logger/event_logger.h" @@ -8,6 +9,10 @@ #include "vw_model/pdf_model.h" #include "vw_model/vw_model.h" +#ifdef RL_BUILD_FEDERATION +# include "federation/local_loop_controller.h" +#endif + #ifdef USE_AZURE_FACTORIES # include "azure_factories.h" # include "model_mgmt/restapi_data_transport.h" @@ -117,6 +122,18 @@ int file_model_loader_create( return error_code::success; } +#ifdef RL_BUILD_FEDERATION +int local_loop_controller_create( + m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +{ + TRACE_INFO(trace_logger, "Local loop controller i_data_transport created."); + std::unique_ptr output; + RETURN_IF_FAIL(local_loop_controller::create(output, config, trace_logger, status)); + *retval = output.release(); + return error_code::success; +} +#endif + int null_time_provider_create( i_time_provider** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) { @@ -142,6 +159,17 @@ void factory_initializer::register_default_factories() data_transport_factory.register_type(value::NO_MODEL_DATA, empty_data_transport_create); data_transport_factory.register_type(value::FILE_MODEL_DATA, file_model_loader_create); +#ifdef RL_BUILD_FEDERATION + data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, local_loop_controller_create); +#else + data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, + [](m::i_data_transport**, const u::configuration&, i_trace* trace_logger, api_status* status) + { + RETURN_ERROR_ARG(trace_logger, status, create_fn_exception, + "Cannot use LOCAL_LOOP_MODEL_DATA because rlclientlib was not compiled with federated learning enabled"); + }); +#endif + model_factory.register_type(value::VW, model_create); model_factory.register_type(value::PASSTHROUGH_PDF_MODEL, model_create); @@ -153,23 +181,31 @@ void factory_initializer::register_default_factories() // Register File loggers sender_factory.register_type(value::EPISODE_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, - api_status* status) { + [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + { const char* file_name = c.get(name::EPISODE_FILE_NAME, "episode.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); }); sender_factory.register_type(value::OBSERVATION_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, - api_status* status) { + [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + { const char* file_name = c.get(name::OBSERVATION_FILE_NAME, "observation.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); }); sender_factory.register_type(value::INTERACTION_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, - api_status* status) { + [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + { const char* file_name = c.get(name::INTERACTION_FILE_NAME, "interaction.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); }); + + // Register a default factory for LOCAL_LOOP_SENDER that returns an error + sender_factory.register_type(value::LOCAL_LOOP_SENDER, + [](i_sender**, const u::configuration&, error_callback_fn*, i_trace* trace_logger, api_status* status) + { + RETURN_ERROR_ARG(trace_logger, status, create_fn_exception, + "LOCAL_LOOP_SENDER must be used with model source set to LOCAL_LOOP_MODEL_DATA"); + }); } int null_tracer_create(i_trace** retval, const u::configuration& cfg, i_trace* trace_logger, api_status* status) diff --git a/rlclientlib/federation/eud_utils.h b/rlclientlib/federation/eud_utils.h new file mode 100644 index 000000000..890c4bb8a --- /dev/null +++ b/rlclientlib/federation/eud_utils.h @@ -0,0 +1,67 @@ +#pragma once + +#include "api_status.h" +#include "constants.h" +#include "err_constants.h" +#include "future_compat.h" +#include "generated/v2/Event_generated.h" +#include "generated/v2/Metadata_generated.h" +#include "joined_log_provider.h" +#include "logger/message_type.h" +#include "logger/preamble.h" +#include "rl_string_view.h" +#include "sender.h" +#include "time_helper.h" +#include "vw/common/text_utils.h" +#include "vw/io/io_adapter.h" + +#include +#include +#include +#include +#include +#include + +namespace reinforcement_learning +{ +inline int parse_int(reinforcement_learning::string_view s, int& out, reinforcement_learning::api_status* status) +{ + // can't use stol because that throws an exception. Use strtol instead. + char* end = nullptr; + int i = strtol(s.data(), &end, 10); + if (end <= s.data() && s.size() > 0) + { + out = 0; + RETURN_ERROR_ARG(nullptr, status, invalid_argument, "invalid int"); + } + out = i; + return 0; +} + +inline int parse_eud( + reinforcement_learning::string_view eud_str, std::chrono::seconds& out, reinforcement_learning::api_status* status) +{ + std::vector components; + VW::tokenize(':', eud_str, components); + if (components.size() != 3 || + std::any_of(components.begin(), components.end(), + [](const std::string& component) + { return component.empty() || !std::all_of(component.begin(), component.end(), ::isdigit); })) + { + RETURN_ERROR_ARG(nullptr, status, invalid_argument, "invalid format of eud duration"); + } + + int hours{}; + RETURN_IF_FAIL(parse_int(components[0], hours, status)); + + int minutes{}; + RETURN_IF_FAIL(parse_int(components[1], minutes, status)); + + int seconds{}; + RETURN_IF_FAIL(parse_int(components[2], seconds, status)); + + out = std::chrono::hours(hours) + std::chrono::minutes(minutes) + std::chrono::seconds(seconds); + return 0; +} + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/event_sink.h b/rlclientlib/federation/event_sink.h new file mode 100644 index 000000000..b7c025748 --- /dev/null +++ b/rlclientlib/federation/event_sink.h @@ -0,0 +1,42 @@ +#pragma once + +#include "api_status.h" +#include "data_buffer.h" +#include "future_compat.h" +#include "sender.h" + +#include + +namespace reinforcement_learning +{ +class i_event_sink +{ + using buffer = std::shared_ptr; + +public: + // Add an event batch + // Input should consist of a preamble and an EventBatch flatbuffer + RL_ATTR(nodiscard) + virtual int receive_events(const buffer& data, api_status* status = nullptr) = 0; + + // Return an object of type i_sender that will forward data to receive_events() of this object + // Each call returns a new output, and the caller of this function takes ownership of it + std::unique_ptr get_sender_proxy() { return std::unique_ptr(new sender_proxy(this)); } + + virtual ~i_event_sink() = default; + +private: + struct sender_proxy : public i_sender + { + sender_proxy(i_event_sink* event_sink) : _event_sink(event_sink) {} + virtual int v_send(const buffer& data, api_status* status = nullptr) override + { + return _event_sink->receive_events(data, status); + } + virtual int init(const utility::configuration&, api_status*) override { return error_code::success; } + virtual ~sender_proxy() = default; + i_event_sink* _event_sink; + }; +}; + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/joined_log_provider.h b/rlclientlib/federation/joined_log_provider.h index b2de11cee..9326b6d0f 100644 --- a/rlclientlib/federation/joined_log_provider.h +++ b/rlclientlib/federation/joined_log_provider.h @@ -1,6 +1,7 @@ #pragma once #include "api_status.h" +#include "data_buffer.h" #include "future_compat.h" #include "vw/io/io_adapter.h" @@ -10,15 +11,6 @@ namespace reinforcement_learning { -struct i_joined_log_batch -{ - virtual ~i_joined_log_batch() = default; - - /// Returns next chunk of batch. chunk_reader will be nullptr when then batch is complete. - RL_ATTR(nodiscard) - virtual int next(std::unique_ptr& chunk_reader, api_status* status = nullptr) = 0; -}; - /** * @brief This interface allows polling access to logged event data. */ @@ -26,9 +18,9 @@ struct i_joined_log_provider { virtual ~i_joined_log_provider() = default; - /// Runs the join operation and returns the resulting batch which can be consumed. - /// The format of the data returned in the batch it implementation dependent. + // Runs the join operation and returns the resulting batch which can be consumed. + // The format of the data returned in the batch it implementation dependent. RL_ATTR(nodiscard) - virtual int invoke_join(std::unique_ptr& batch, api_status* status = nullptr) = 0; + virtual int invoke_join(std::unique_ptr& output, api_status* status = nullptr) = 0; }; -} // namespace reinforcement_learning +} // namespace reinforcement_learning \ No newline at end of file diff --git a/rlclientlib/federation/local_client.cc b/rlclientlib/federation/local_client.cc index 7472be4a6..882982ff9 100644 --- a/rlclientlib/federation/local_client.cc +++ b/rlclientlib/federation/local_client.cc @@ -12,14 +12,16 @@ #include "vw/core/vw.h" #include "vw/io/io_adapter.h" -reinforcement_learning::local_client::local_client(std::unique_ptr initial_model, i_trace* trace_logger) +namespace reinforcement_learning +{ +local_client::local_client(std::unique_ptr initial_model, i_trace* trace_logger) : _current_model(std::move(initial_model)), _state(state_t::model_available), _trace_logger(trace_logger) { } -reinforcement_learning::local_client::~local_client() = default; +local_client::~local_client() = default; -int reinforcement_learning::local_client::try_get_model(const std::string& app_id, +int local_client::try_get_model(const std::string& app_id, /* inout */ model_management::model_data& data, /* out */ bool& model_received, api_status* status) { switch (_state) @@ -50,7 +52,7 @@ int reinforcement_learning::local_client::try_get_model(const std::string& app_i return error_code::success; } -int reinforcement_learning::local_client::report_result(const uint8_t* payload, size_t size, api_status* status) +int local_client::report_result(const uint8_t* payload, size_t size, api_status* status) { switch (_state) { @@ -77,8 +79,8 @@ int reinforcement_learning::local_client::report_result(const uint8_t* payload, return error_code::success; } -int reinforcement_learning::create_local_client(const utility::configuration& config, - /*out*/ std::unique_ptr& object, i_trace* trace_logger, api_status* status) +int local_client::create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger, api_status* status) { // Create empty model based on ML args on first call std::string initial_command_line(config.get( @@ -88,6 +90,8 @@ int reinforcement_learning::create_local_client(const utility::configuration& co auto args = VW::make_unique(VW::split_command_line(initial_command_line)); auto workspace = VW::initialize_experimental(std::move(args)); - object = VW::make_unique(std::move(workspace), trace_logger); - return reinforcement_learning::error_code::success; + output = std::unique_ptr(new local_client(std::move(workspace), trace_logger)); + return error_code::success; } + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/local_client.h b/rlclientlib/federation/local_client.h index fc7c6339c..629964435 100644 --- a/rlclientlib/federation/local_client.h +++ b/rlclientlib/federation/local_client.h @@ -7,10 +7,13 @@ namespace reinforcement_learning { -struct local_client : i_federated_client +class local_client : i_federated_client { - local_client(std::unique_ptr initial_model, i_trace* trace_logger); - ~local_client() override; +public: + RL_ATTR(nodiscard) + static int create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger = nullptr, api_status* status = nullptr); + RL_ATTR(nodiscard) int try_get_model(const std::string& app_id, /* inout */ model_management::model_data& data, /* out */ bool& model_received, @@ -18,6 +21,8 @@ struct local_client : i_federated_client RL_ATTR(nodiscard) int report_result(const uint8_t* payload, size_t size, api_status* status = nullptr) override; + ~local_client() override; + private: enum class state_t { @@ -25,6 +30,8 @@ struct local_client : i_federated_client model_retrieved }; + local_client(std::unique_ptr initial_model, i_trace* trace_logger); + state_t _state; std::unique_ptr _current_model; i_trace* _trace_logger; @@ -32,9 +39,4 @@ struct local_client : i_federated_client // Read MODEL_VW_INITIAL_COMMAND_LINE -RL_ATTR(nodiscard) -int create_local_client(const reinforcement_learning::utility::configuration& config, - /*out*/ std::unique_ptr& object, i_trace* trace_logger, - reinforcement_learning::api_status* status = nullptr); - } // namespace reinforcement_learning \ No newline at end of file diff --git a/rlclientlib/federation/local_loop_controller.cc b/rlclientlib/federation/local_loop_controller.cc new file mode 100644 index 000000000..841241a37 --- /dev/null +++ b/rlclientlib/federation/local_loop_controller.cc @@ -0,0 +1,95 @@ +#include "federation/local_loop_controller.h" + +#include "constants.h" +#include "err_constants.h" +#include "federation/local_client.h" +#include "federation/sender_joined_log_provider.h" +#include "model_mgmt.h" +#include "vw/io/io_adapter.h" + +namespace reinforcement_learning +{ +int local_loop_controller::create(std::unique_ptr& output, + const reinforcement_learning::utility::configuration& config, i_trace* trace_logger, api_status* status) +{ + std::string app_id = config.get(name::APP_ID, ""); + + std::unique_ptr federated_client; + std::unique_ptr trainable_model; + std::unique_ptr sender_joiner; + RETURN_IF_FAIL(local_client::create(federated_client, config, trace_logger, status)); + RETURN_IF_FAIL(trainable_vw_model::create(trainable_model, config, trace_logger, status)); + RETURN_IF_FAIL(sender_joined_log_provider::create(sender_joiner, config, trace_logger, status)); + + // sender_joiner is both an i_joined_log_provider and an i_event_sink + // we need to convert to shared_ptr and create copies for each base type + std::shared_ptr sender_joiner_shared; + std::shared_ptr joiner; + std::shared_ptr event_sink; + sender_joiner_shared = std::move(sender_joiner); + joiner = std::static_pointer_cast(sender_joiner_shared); + event_sink = std::static_pointer_cast(sender_joiner_shared); + + output = std::unique_ptr(new local_loop_controller(std::move(app_id), + std::move(federated_client), std::move(trainable_model), std::move(joiner), std::move(event_sink))); + return error_code::success; +} + +local_loop_controller::local_loop_controller(std::string app_id, std::unique_ptr&& federated_client, + std::unique_ptr&& trainable_model, std::shared_ptr&& joiner, + std::shared_ptr&& event_sink) + : _app_id(std::move(app_id)) + , _federated_client(std::move(federated_client)) + , _trainable_model(std::move(trainable_model)) + , _joiner(std::move(joiner)) + , _event_sink(std::move(event_sink)) +{ +} + +int local_loop_controller::update_global(api_status* status) +{ + if (_need_to_send_model_delta) + { + // get and send the model delta + auto buffer = std::make_shared>(); + auto writer = VW::io::create_vector_writer(buffer); + RETURN_IF_FAIL(_trainable_model->get_model_delta(*writer, status)); + + char* data_ptr = buffer->data(); + RETURN_IF_FAIL(_federated_client->report_result(reinterpret_cast(data_ptr), buffer->size(), status)); + + _need_to_send_model_delta = false; + } + + // ask for a new global model + model_management::model_data data; + bool model_received = false; + RETURN_IF_FAIL(_federated_client->try_get_model(_app_id, data, model_received, status)); + + if (model_received) + { + RETURN_IF_FAIL(_trainable_model->set_data(data, status)); + _need_to_send_model_delta = true; + } + return error_code::success; +} + +int local_loop_controller::update_local(api_status* status) +{ + std::unique_ptr binary_log; + RETURN_IF_FAIL(_joiner->invoke_join(binary_log, status)); + RETURN_IF_FAIL(_trainable_model->learn(std::move(binary_log), status)); + return error_code::success; +} + +int local_loop_controller::get_data(model_management::model_data& data, api_status* status) +{ + RETURN_IF_FAIL(update_local(status)); + RETURN_IF_FAIL(update_global(status)); + RETURN_IF_FAIL(_trainable_model->get_data(data, status)); + return error_code::success; +} + +std::unique_ptr local_loop_controller::get_local_sender() { return _event_sink->get_sender_proxy(); } + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/local_loop_controller.h b/rlclientlib/federation/local_loop_controller.h new file mode 100644 index 000000000..093291b1a --- /dev/null +++ b/rlclientlib/federation/local_loop_controller.h @@ -0,0 +1,69 @@ +#pragma once + +#include "api_status.h" +#include "error_callback_fn.h" +#include "factory_resolver.h" +#include "federation/event_sink.h" +#include "federation/federated_client.h" +#include "federation/joined_log_provider.h" +#include "federation/vw_trainable_model.h" +#include "model_mgmt.h" +#include "sender.h" +#include "trace_logger.h" + +#include +#include + +namespace reinforcement_learning +{ +// The local_loop_controller will "plug in" to rlclientlib as an i_data_transport object. +// It exposes a get_local_sender_factory() function that creates i_sender proxy objects. +// These proxy objects will send events to its internal event sink. +// The initialization code for live_model_impl must register this factory function correctly. +class local_loop_controller : public model_management::i_data_transport +{ +public: + RL_ATTR(nodiscard) + static int create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger = nullptr, api_status* status = nullptr); + + // Get model data in binary format + // This will perform joining and training on any observed events, and then return the updated model + RL_ATTR(nodiscard) + virtual int get_data(model_management::model_data& data, api_status* status = nullptr) override; + + // Returns a i_sender proxy object to be used for sending events to the internal event sink + std::unique_ptr get_local_sender(); + + virtual ~local_loop_controller() = default; + +protected: + // Constructor is private because objects should be created using the factory function + local_loop_controller(std::string app_id, std::unique_ptr&& federated_client, + std::unique_ptr&& trainable_model, std::shared_ptr&& joiner, + std::shared_ptr&& event_sink); + + // This updates global state with the federated learning server. + // If applicable, it will first report a model delta from local training. + // Then it attempts to retreive a new global model. + RL_ATTR(nodiscard) + int update_global(api_status* status = nullptr); + + // Internal implemetation to run joining and traning to update local state + RL_ATTR(nodiscard) + int update_local(api_status* status = nullptr); + + // Internal state + std::string _app_id; + std::unique_ptr _federated_client = nullptr; + std::unique_ptr _trainable_model = nullptr; + // These need to be shared_ptr because they may hold the same object + std::shared_ptr _joiner = nullptr; + std::shared_ptr _event_sink = nullptr; + + // If the federated client has received a new global model, + // we need to train on local events and upload a model delta. + bool _need_to_send_model_delta = false; +}; + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/sender_joined_log_provider.cc b/rlclientlib/federation/sender_joined_log_provider.cc new file mode 100644 index 000000000..fb6bb03fb --- /dev/null +++ b/rlclientlib/federation/sender_joined_log_provider.cc @@ -0,0 +1,263 @@ +#include "sender_joined_log_provider.h" + +#include "api_status.h" +#include "constants.h" +#include "err_constants.h" +#include "federation/eud_utils.h" +#include "future_compat.h" +#include "generated/v2/Event_generated.h" +#include "generated/v2/FileFormat_generated.h" +#include "generated/v2/Metadata_generated.h" +#include "joined_log_provider.h" +#include "logger/message_type.h" +#include "logger/preamble.h" +#include "rl_string_view.h" +#include "sender.h" +#include "time_helper.h" +#include "vw/common/text_utils.h" +#include "vw/io/io_adapter.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace reinforcement_learning; + +namespace +{ +class buffer_reader : public VW::io::reader +{ +public: + buffer_reader(std::vector&& buffer) + : VW::io::reader(true), _buffer(std::move(buffer)), _read_head(_buffer.data()) + { + } + ~buffer_reader() override = default; + ssize_t read(char* buffer, size_t num_bytes) override + { + num_bytes = std::min((_buffer.data() + _buffer.size()) - _read_head, static_cast(num_bytes)); + if (num_bytes == 0) { return 0; } + + std::memcpy(buffer, _read_head, num_bytes); + _read_head += num_bytes; + + return num_bytes; + } + void reset() override { _read_head = _buffer.data(); } + +private: + std::vector _buffer; + uint8_t* _read_head; +}; + +timestamp fb_to_rl_timestamp(const messages::flatbuff::v2::TimeStamp& ts) +{ + return timestamp(ts.year(), ts.month(), ts.day(), ts.hour(), ts.minute(), ts.second(), ts.subsecond()); +} + +messages::flatbuff::v2::TimeStamp rl_to_fb_timestamp(const timestamp& ts) +{ + return messages::flatbuff::v2::TimeStamp(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.sub_second); +} + +void emit_uint32(std::vector& output, uint32_t data) +{ + // TODO consider doing this a safer way + // Check endianness requirement of format and make sure we get that right here. + output.reserve(output.size() + sizeof(uint32_t)); + output.insert( + std::end(output), reinterpret_cast(&data), reinterpret_cast(&data) + sizeof(uint32_t)); +} + +int emit_filemagic_message(std::vector& output) +{ + constexpr uint32_t filemagic = 0x42465756; + constexpr uint32_t version = 1; + emit_uint32(output, filemagic); + emit_uint32(output, version); + return 0; +} + +int emit_regular_message(std::vector& output, flatbuffers::FlatBufferBuilder& fbb, + flatbuffers::Offset joined_payload) +{ + constexpr uint32_t regular = 0xFFFFFFFF; + emit_uint32(output, regular); + + fbb.Finish(joined_payload); + auto buffer = fbb.Release(); + + uint32_t size = buffer.size(); + emit_uint32(output, size); + + output.reserve(output.size() + size); + output.insert(std::end(output), buffer.data(), buffer.data() + size); + + uint32_t padding_size = size % 8; + output.reserve(output.size() + padding_size); + output.insert(std::end(output), padding_size, 0); + + return 0; +} +} // namespace + +namespace reinforcement_learning +{ +RL_ATTR(nodiscard) +int sender_joined_log_provider::create(std::unique_ptr& output, + const utility::configuration& config, i_trace* trace_logger, api_status* status) +{ + if (config.get_int(name::PROTOCOL_VERSION, 999) != 2) + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << " protocol version 2 required"; + } + + std::string eud_duration = config.get(name::JOINER_EUD_DURATION, "UNSET"); + if (eud_duration == "UNSET") { RETURN_ERROR_ARG(trace_logger, status, invalid_argument, "eudduration must be set"); } + + std::chrono::seconds eud_offset; + RETURN_IF_FAIL(parse_eud(eud_duration, eud_offset, status)); + + output = std::unique_ptr(new sender_joined_log_provider(eud_offset, trace_logger)); + return error_code::success; +} + +sender_joined_log_provider::sender_joined_log_provider(std::chrono::seconds eud_offset, i_trace* trace_logger) + : _eud_offset(eud_offset), _trace_logger(trace_logger) +{ +} + +RL_ATTR(nodiscard) +int sender_joined_log_provider::invoke_join(std::unique_ptr& batch, api_status* status) +{ + std::lock_guard lock(_mutex); + std::vector output; + const auto eud_cutoff = std::chrono::system_clock::now() - _eud_offset; + + // Binary log starts with a FILEMAGIC header + emit_filemagic_message(output); + + for (auto interaction_iter = _interactions.cbegin(); interaction_iter != _interactions.cend();) + { + flatbuffers::FlatBufferBuilder fbb; + std::vector> joined_events; + + const auto& interaction_data = *interaction_iter; + const auto interaction_time = interaction_data._time.to_time_point(); + const auto reward_cutoff = interaction_time + _eud_offset; + + // Only process interactions that occurred before EUD cutoff time + if (interaction_time > eud_cutoff) + { + // Interactions are in std::set sorted by timestamp, so once we see the + // first event after eud_cutoff, all later events are also after eud_cutoff + break; + } + + // Add interaction to flatbuffer builder + auto interaction_fb_vec = fbb.CreateVector(interaction_data._data_ptr, interaction_data._size); + auto interaction_fb_time = rl_to_fb_timestamp(interaction_data._time); + joined_events.push_back(messages::flatbuff::v2::CreateJoinedEvent(fbb, interaction_fb_vec, &interaction_fb_time)); + + // If there are corresponding observations with the event_id, process them + const auto& observation_iter = _observations.find(interaction_data._event_id); + bool interaction_has_observations = observation_iter != _observations.end(); + + if (interaction_has_observations) + { + for (const auto& observation_data : observation_iter->second) + { + const auto observation_time = observation_data._time.to_time_point(); + if (observation_time <= reward_cutoff) + { + // Add observation to flatbuffer + auto observation_fb_vec = fbb.CreateVector(observation_data._data_ptr, observation_data._size); + auto observation_fb_time = rl_to_fb_timestamp(observation_data._time); + joined_events.push_back( + messages::flatbuff::v2::CreateJoinedEvent(fbb, observation_fb_vec, &observation_fb_time)); + } + } + } + + // Create the final flatbuffer output + auto joined_payload = messages::flatbuff::v2::CreateJoinedPayloadDirect(fbb, &joined_events); + emit_regular_message(output, fbb, joined_payload); + + // Clear data structures + _interactions.erase(interaction_iter++); + if (interaction_has_observations) { _observations.erase(observation_iter); } + } + + batch.reset(new buffer_reader(std::move(output))); + return error_code::success; +} + +int sender_joined_log_provider::receive_events(const i_sender::buffer& data_buffer, api_status* status) +{ + logger::preamble pre; + pre.read_from_bytes(data_buffer->preamble_begin(), logger::preamble::size()); + + if (pre.msg_type != logger::message_type::fb_generic_event_collection) + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) + << " Message type " << pre.msg_type << " cannot be handled."; + } + + // Verify the flatbuffer + auto event_batch = messages::flatbuff::v2::GetEventBatch(data_buffer->body_begin()); + flatbuffers::Verifier verifier(data_buffer->body_begin(), data_buffer->body_filled_size()); + auto result = event_batch->Verify(verifier); + + if (!result) + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; + } + + if (event_batch->metadata()->content_encoding()->str() != "IDENTITY") + { + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; + } + + // Enter mutex lock + { + std::lock_guard lock(_mutex); + for (auto serialized_event : *event_batch->events()) + { + // Get the flatbuffer inside flatbuffer + const auto* serialized_payload = serialized_event->payload(); + const auto* event = flatbuffers::GetRoot(serialized_payload->data()); + + // Read flatbuffer data and emplace into the corresponding data structure + std::string event_id = event->meta()->id()->str(); + auto event_timestamp = fb_to_rl_timestamp(*event->meta()->client_time_utc()); + switch (event->meta()->payload_type()) + { + case messages::flatbuff::v2::PayloadType_CB: + case messages::flatbuff::v2::PayloadType_CCB: + case messages::flatbuff::v2::PayloadType_Slates: + case messages::flatbuff::v2::PayloadType_CA: + case messages::flatbuff::v2::PayloadType_MultiStep: + _interactions.emplace( + event_id, serialized_payload->data(), serialized_payload->size(), event_timestamp, data_buffer); + break; + + case messages::flatbuff::v2::PayloadType_Outcome: + _observations[event_id].emplace_back( + event_id, serialized_payload->data(), serialized_payload->size(), event_timestamp, data_buffer); + break; + + default: + RETURN_ERROR_LS(_trace_logger, status, invalid_argument) + << "Could not process payload type: " << event->meta()->payload_type(); + } + } + } + return error_code::success; +} + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/sender_joined_log_provider.h b/rlclientlib/federation/sender_joined_log_provider.h new file mode 100644 index 000000000..12e58457b --- /dev/null +++ b/rlclientlib/federation/sender_joined_log_provider.h @@ -0,0 +1,80 @@ +#pragma once + +#include "api_status.h" +#include "configuration.h" +#include "federation/event_sink.h" +#include "federation/joined_log_provider.h" +#include "future_compat.h" +#include "sender.h" +#include "time_helper.h" +#include "vw/io/io_adapter.h" + +//#include +#include +#include +#include +#include + +namespace reinforcement_learning +{ +class sender_joined_log_provider : public i_joined_log_provider, public i_event_sink +{ +public: + RL_ATTR(nodiscard) + static int create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger = nullptr, api_status* status = nullptr); + + // Perform EUD joining on events that have previously been added + // Output is a binary joined log that can be consumed by the binary parser + RL_ATTR(nodiscard) + virtual int invoke_join(std::unique_ptr& batch, api_status* status = nullptr) override; + + // Add an event batch to the joiner + // Input should consist of a preamble and an EventBatch flatbuffer + RL_ATTR(nodiscard) + virtual int receive_events(const i_sender::buffer& data, api_status* status = nullptr) override; + + virtual ~sender_joined_log_provider() = default; + +private: + // Internal object to store event data + struct event_data + { + const std::string _event_id; + const uint8_t* _data_ptr; + const size_t _size; + const timestamp _time; + + // We must hold a copy of i_sender::buffer so that its shared_ptr doesn't go out of scope + i_sender::buffer _data_buffer; + + event_data(std::string event_id, const uint8_t* data_ptr, size_t size, timestamp time, i_sender::buffer data_buffer) + : _event_id(std::move(event_id)) + , _data_ptr(data_ptr) + , _size(size) + , _time(time) + , _data_buffer(std::move(data_buffer)) + { + } + + // Sort events by time, then by event_id + bool operator<(const event_data& other) const + { + return std::tie(_time, _event_id) < std::tie(other._time, other._event_id); + } + }; + + sender_joined_log_provider(std::chrono::seconds eud_offset, i_trace* trace_logger); + + // Set of interaction events, sorted by time + std::set _interactions; + + // Map from event_id to vector of event_data objects + std::unordered_map> _observations; + + std::chrono::seconds _eud_offset; + reinforcement_learning::i_trace* _trace_logger = nullptr; + std::mutex _mutex; +}; + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/vw_trainable_model.cc b/rlclientlib/federation/vw_trainable_model.cc new file mode 100644 index 000000000..00db16b02 --- /dev/null +++ b/rlclientlib/federation/vw_trainable_model.cc @@ -0,0 +1,440 @@ +#include "federation/vw_trainable_model.h" + +#include "constants.h" +#include "err_constants.h" +#include "joiners/example_joiner.h" +#include "joiners/multistep_example_joiner.h" +#include "parse_example_binary.h" +#include "str_util.h" +#include "vw/config/options_cli.h" +#include "vw/core/learner.h" +#include "vw/core/parse_primitives.h" +#include "vw/core/shared_data.h" +#include "vw/core/vw.h" +#include "vw/io/logger.h" + +namespace +{ +// Helper function to train model on VW::multi_ex +// examples is cleared at the end of this function +// returns number of examples learned +int learn_and_finish_examples(VW::workspace& vw, VW::multi_ex& examples) +{ + if (examples.empty()) { return 0; } + + VW::setup_examples(vw, examples); + + if (vw.l->is_multiline()) + { + vw.learn(examples); + vw.finish_example(examples); + examples.clear(); + return 1; + } + + // single line + for (auto example : examples) { vw.learn(*example); } + for (auto example : examples) { vw.finish_example(*example); } + int size = examples.size(); + examples.clear(); + return size; +} + +// Helper function to call finish_example on VW::multi_ex +// examples is cleared at the end of this function +void finish_examples(VW::workspace& vw, VW::multi_ex& examples) +{ + if (examples.empty()) { return; } + + if (vw.l->is_multiline()) { vw.finish_example(examples); } + else + { + for (auto example : examples) { vw.finish_example(*example); } + } + + examples.clear(); +} + +void vw_log_to_trace_logger(void* trace_logger, VW::io::log_level log_level, const std::string& msg) +{ + if (trace_logger == nullptr) { return; } + auto i_trace_ptr = static_cast(trace_logger); + switch (log_level) + { + case VW::io::log_level::TRACE_LEVEL: + case VW::io::log_level::DEBUG_LEVEL: + TRACE_DEBUG(i_trace_ptr, msg); + break; + + case VW::io::log_level::INFO_LEVEL: + TRACE_INFO(i_trace_ptr, msg); + break; + + case VW::io::log_level::WARN_LEVEL: + TRACE_WARN(i_trace_ptr, msg); + break; + + case VW::io::log_level::ERROR_LEVEL: + case VW::io::log_level::CRITICAL_LEVEL: + TRACE_ERROR(i_trace_ptr, msg); + break; + + default: + break; + } +} +} // namespace + +namespace reinforcement_learning +{ +int trainable_vw_model::create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger, api_status* status) +{ + int protocol_version = config.get_int(name::PROTOCOL_VERSION, 1); + if (protocol_version != 2) + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "Protocol version 2 is required"; + } + + std::string command_line = config.get(name::MODEL_VW_INITIAL_COMMAND_LINE, "--quiet --preserve_performance_counters"); + std::string problem_type = config.get(name::JOINER_PROBLEM_TYPE, value::PROBLEM_TYPE_UNKNOWN); + std::string learning_mode = config.get(name::JOINER_LEARNING_MODE, value::LEARNING_MODE_ONLINE); + std::string reward_function = config.get(name::JOINER_REWARD_FUNCTION, value::REWARD_FUNCTION_EARLIEST); + + try + { + output = std::unique_ptr( + new trainable_vw_model(command_line, problem_type, learning_mode, reward_function, trace_logger)); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +trainable_vw_model::trainable_vw_model(std::string command_line, std::string problem_type, std::string learning_mode, + std::string reward_function, i_trace* trace_logger) + : _command_line(std::move(command_line)) + , _problem_type(std::move(problem_type)) + , _learning_mode(std::move(learning_mode)) + , _reward_function(std::move(reward_function)) + , _trace_logger(trace_logger) +{ + auto options = VW::make_unique(VW::split_command_line(_command_line)); + _model = VW::initialize_experimental(std::move(options)); + copy_current_model_to_starting(); +} + +int trainable_vw_model::set_model(std::unique_ptr&& model, api_status* status) +{ + try + { + { + std::lock_guard lock(_mutex); + _model = std::move(model); + } + copy_current_model_to_starting(); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::set_data(const model_management::model_data& data, api_status* status) +{ + try + { + auto opts = + std::unique_ptr(new VW::config::options_cli(VW::split_command_line(_command_line))); + { + std::lock_guard lock(_mutex); + _model = VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); + } + copy_current_model_to_starting(); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::get_data(model_management::model_data& data, api_status* status) +{ + try + { + int example_count = 0; + io_buf io_buffer; + auto backing_buffer = std::make_shared>(); + io_buffer.add_file(VW::io::create_vector_writer(backing_buffer)); + + { + std::lock_guard lock(_mutex); + example_count = _model->sd->weighted_labeled_examples; + VW::save_predictor(*_model, io_buffer); + } + auto* destination_buffer = data.alloc(backing_buffer->size()); + std::memcpy(destination_buffer, backing_buffer->data(), backing_buffer->size()); + + TRACE_INFO(_trace_logger, + utility::concat("trainable_vw_model::get_data() returning model trained on ", example_count, " examples")); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::learn(std::unique_ptr&& binary_log, api_status* status) +{ + if (binary_log.get() == nullptr) + { + // TODO handle this as error? + TRACE_WARN(_trace_logger, "Received null binary log in trainable_vw_model::learn()"); + return error_code::success; + } + + try + { + std::lock_guard lock(_mutex); + + io_buf io_reader; + io_reader.add_file(std::move(binary_log)); + + std::unique_ptr joiner; + if (_problem_type == value::PROBLEM_TYPE_MULTISTEP) + { + joiner = std::unique_ptr(new multistep_example_joiner(_model.get())); + } + else { joiner = std::unique_ptr(new example_joiner(_model.get())); } + + // Set the default joiner options if no checkpoint message is present in the binary log + configure_joiner(joiner); + + VW::external::binary_parser binary_parser( + std::move(joiner), VW::io::create_custom_sink_logger(_trace_logger, vw_log_to_trace_logger)); + + int example_count = 0; + bool example_was_parsed = false; + VW::multi_ex example_out; + do { + example_out.push_back(VW::new_unused_example(*_model)); + example_was_parsed = binary_parser.parse_examples(_model.get(), io_reader, example_out); + + if (example_was_parsed) { example_count += learn_and_finish_examples(*_model, example_out); } + else + { + // cleanup the unused example that the parser was called with + assert(example_out.size() == 1); + finish_examples(*_model, example_out); + } + } while (example_was_parsed); + + TRACE_INFO(_trace_logger, utility::concat("trainable_vw_model::learn() learned on ", example_count, " examples")); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_rank_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_rank_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::learn(VW::workspace& example_ws, VW::multi_ex& examples, api_status* status) +{ + try + { + std::lock_guard lock(_mutex); + VW::multi_ex examples_copied; + + // examples may be from a different workspace, and must be copied to this workspace + for (auto example : examples) + { + io_buf io_writer; + VW::details::cache_temp_buffer temp_buffer; + auto example_buffer = std::make_shared>(); + io_writer.add_file(VW::io::create_vector_writer(example_buffer)); + VW::write_example_to_cache( + io_writer, example, example_ws.example_parser->lbl_parser, example_ws.parse_mask, temp_buffer); + io_writer.flush(); + + io_buf io_reader; + io_reader.add_file(VW::io::create_buffer_view(example_buffer->data(), example_buffer->size())); + VW::multi_ex example_out; + example_out.push_back(VW::new_unused_example(*_model)); + VW::read_example_from_cache(_model.get(), io_reader, example_out); + examples_copied.insert(examples_copied.end(), example_out.begin(), example_out.end()); + } + + int example_count = learn_and_finish_examples(*_model, examples_copied); + TRACE_INFO(_trace_logger, utility::concat("trainable_vw_model::learn() learned on ", example_count, " examples")); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_rank_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_rank_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::get_model_delta(VW::model_delta& output, api_status* status) +{ + try + { + int old_example_count = 0; + int new_example_count = 0; + VW::model_delta delta(nullptr); + { + std::lock_guard lock(_mutex); + old_example_count = _starting_model->sd->weighted_labeled_examples; + new_example_count = _model->sd->weighted_labeled_examples; + delta = *_model - *_starting_model; + } + copy_current_model_to_starting(); + output = std::move(delta); + + TRACE_INFO(_trace_logger, + utility::concat("trainable_vw_model::get_model_delta() created model delta with ", + new_example_count - old_example_count, " examples (current model: ", new_example_count, + ", previous model: ", old_example_count, ")")); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +int trainable_vw_model::get_model_delta(VW::io::writer& output, api_status* status) +{ + try + { + VW::model_delta delta(nullptr); + RETURN_IF_FAIL(get_model_delta(delta, status)); + delta.serialize(output); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + catch (...) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, "Unknown error"); + } + return error_code::success; +} + +void trainable_vw_model::copy_current_model_to_starting() +{ + auto backing_vector = std::make_shared>(); + io_buf temp_buffer; + temp_buffer.add_file(VW::io::create_vector_writer(backing_vector)); + + { + std::lock_guard lock(_mutex); + VW::save_predictor(*_model, temp_buffer); + } + + auto args = VW::split_command_line(_command_line); + if (std::find(args.begin(), args.end(), "--preserve_performance_counters") == args.end()) + { + args.emplace_back("--preserve_performance_counters"); + } + auto options = VW::make_unique(args); + + { + std::lock_guard lock(_mutex); + _starting_model = VW::initialize_experimental(std::move(options), + VW::io::create_buffer_view(backing_vector->data(), backing_vector->size()), nullptr, nullptr, nullptr); + } +} + +void trainable_vw_model::configure_joiner(std::unique_ptr& joiner) const +{ + if (_problem_type == value::PROBLEM_TYPE_CB) + { + joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_CB); + } + else if (_problem_type == value::PROBLEM_TYPE_CCB) + { + joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_CCB); + } + else if (_problem_type == value::PROBLEM_TYPE_SLATES) + { + joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_SLATES); + } + else if (_problem_type == value::PROBLEM_TYPE_CA) + { + joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_CA); + } + else if (_problem_type == value::PROBLEM_TYPE_MULTISTEP) + { + joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_MULTISTEP); + } + else { joiner->set_problem_type_config(messages::flatbuff::v2::ProblemType_UNKNOWN); } + + if (_learning_mode == value::LEARNING_MODE_APPRENTICE) + { + joiner->set_learning_mode_config(messages::flatbuff::v2::LearningModeType_Apprentice); + } + else if (_learning_mode == value::LEARNING_MODE_LOGGINGONLY) + { + joiner->set_learning_mode_config(messages::flatbuff::v2::LearningModeType_LoggingOnly); + } + else { joiner->set_learning_mode_config(messages::flatbuff::v2::LearningModeType_Online); } + + if (_reward_function == value::REWARD_FUNCTION_AVERAGE) + { + joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Average); + } + else if (_reward_function == value::REWARD_FUNCTION_MEDIAN) + { + joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Median); + } + else if (_reward_function == value::REWARD_FUNCTION_SUM) + { + joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Sum); + } + else if (_reward_function == value::REWARD_FUNCTION_MIN) + { + joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Min); + } + else if (_reward_function == value::REWARD_FUNCTION_MAX) + { + joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Max); + } + else { joiner->set_reward_function(messages::flatbuff::v2::RewardFunctionType_Earliest); } + joiner->set_default_reward(0.f); +} + +} // namespace reinforcement_learning diff --git a/rlclientlib/federation/vw_trainable_model.h b/rlclientlib/federation/vw_trainable_model.h new file mode 100644 index 000000000..cd43706c5 --- /dev/null +++ b/rlclientlib/federation/vw_trainable_model.h @@ -0,0 +1,74 @@ +#pragma once + +#include "api_status.h" +#include "configuration.h" +#include "federation/joined_log_provider.h" +#include "joiners/example_joiner.h" +#include "model_mgmt.h" +#include "trace_logger.h" +#include "vw/core/global_data.h" +#include "vw/core/merge.h" + +#include + +namespace reinforcement_learning +{ +class trainable_vw_model +{ +public: + RL_ATTR(nodiscard) + static int create(std::unique_ptr& output, const utility::configuration& config, + i_trace* trace_logger = nullptr, api_status* status = nullptr); + + // Output current model state to buffer + RL_ATTR(nodiscard) + int get_data(model_management::model_data& data, api_status* status = nullptr); + + // Overwrite internal VW model with another model + RL_ATTR(nodiscard) + int set_model(std::unique_ptr&& model, api_status* status = nullptr); + + // Overwrite internal VW model with the given model data + RL_ATTR(nodiscard) + int set_data(const model_management::model_data& data, api_status* status = nullptr); + + // Train model on data from a joined binary log + RL_ATTR(nodiscard) + int learn(std::unique_ptr&& binary_log, api_status* status = nullptr); + + // Train model on VW::example* objects + // This does not call VW::finish_example on the examples passed into here + RL_ATTR(nodiscard) + int learn(VW::workspace& example_ws, VW::multi_ex& examples, api_status* status = nullptr); + + // Generate a model_delta from the current model state and the previous call to + // get_model_delta() or set_model() or set_data() + RL_ATTR(nodiscard) + int get_model_delta(VW::model_delta& output, api_status* status = nullptr); + + RL_ATTR(nodiscard) + int get_model_delta(VW::io::writer& output, api_status* status = nullptr); + +private: + // Private constructor because we should create objects with factory function + trainable_vw_model(std::string command_line, std::string problem_type, std::string learning_mode, + std::string reward_function, i_trace* trace_logger); + + // Need to keep both current and starting model in order to create model_delta + std::unique_ptr _model = nullptr; + std::unique_ptr _starting_model = nullptr; + + void copy_current_model_to_starting(); + + const std::string _command_line; + const std::string _problem_type; + const std::string _learning_mode; + const std::string _reward_function; + + void configure_joiner(std::unique_ptr& joiner) const; + + i_trace* _trace_logger = nullptr; + std::mutex _mutex; +}; + +} // namespace reinforcement_learning diff --git a/rlclientlib/generic_event.h b/rlclientlib/generic_event.h index 97ed6055e..ce2f98dee 100644 --- a/rlclientlib/generic_event.h +++ b/rlclientlib/generic_event.h @@ -82,11 +82,10 @@ class generic_event _payload = serializer.event(tmp.c_str(), args...); } if (ext->is_serialization_transform_enabled()) - { RETURN_IF_FAIL(ext->transform_serialized_payload(_payload, _content_type, status)); } - else { - _content_type = event_content_type::IDENTITY; + RETURN_IF_FAIL(ext->transform_serialized_payload(_payload, _content_type, status)); } + else { _content_type = event_content_type::IDENTITY; } _context_string.clear(); return 0; } diff --git a/rlclientlib/learning_mode.cc b/rlclientlib/learning_mode.cc index 216016cc6..4c1d88d7e 100644 --- a/rlclientlib/learning_mode.cc +++ b/rlclientlib/learning_mode.cc @@ -18,10 +18,7 @@ learning_mode to_learning_mode(const char* learning_mode) if (_stricmp(learning_mode, value::LEARNING_MODE_APPRENTICE) == 0) { return APPRENTICE; } if (_stricmp(learning_mode, value::LEARNING_MODE_ONLINE) == 0) { return ONLINE; } if (_stricmp(learning_mode, value::LEARNING_MODE_LOGGINGONLY) == 0) { return LOGGINGONLY; } - else - { - return ONLINE; - } + else { return ONLINE; } } } // namespace learning } // namespace reinforcement_learning diff --git a/rlclientlib/live_model.cc b/rlclientlib/live_model.cc index f781d1c2e..ab4776764 100644 --- a/rlclientlib/live_model.cc +++ b/rlclientlib/live_model.cc @@ -4,11 +4,12 @@ #include "err_constants.h" #include "live_model_impl.h" -#define INIT_CHECK() \ - do \ - { \ - if (!_initialized) \ - { RETURN_ERROR_ARG(nullptr, status, not_initialized, "Library not initialized. Call init() first."); } \ +#define INIT_CHECK() \ + do { \ + if (!_initialized) \ + { \ + RETURN_ERROR_ARG(nullptr, status, not_initialized, "Library not initialized. Call init() first."); \ + } \ } while (0); namespace reinforcement_learning @@ -162,7 +163,9 @@ int live_model::request_multi_slot_decision(const char* event_id, string_view co INIT_CHECK(); std::vector baseline_vector = c_array_to_vector(baseline_actions, baseline_actions_size); if (event_id == nullptr) - { return _pimpl->request_multi_slot_decision(context_json, flags, resp, baseline_vector, status); } + { + return _pimpl->request_multi_slot_decision(context_json, flags, resp, baseline_vector, status); + } return _pimpl->request_multi_slot_decision(event_id, context_json, flags, resp, baseline_vector, status); } @@ -199,7 +202,9 @@ int live_model::request_multi_slot_decision(const char* event_id, string_view co INIT_CHECK(); std::vector baseline_vector = c_array_to_vector(baseline_actions, baseline_actions_size); if (event_id == nullptr) - { return _pimpl->request_multi_slot_decision(context_json, flags, resp, baseline_vector, status); } + { + return _pimpl->request_multi_slot_decision(context_json, flags, resp, baseline_vector, status); + } return _pimpl->request_multi_slot_decision(event_id, context_json, flags, resp, baseline_vector, status); } diff --git a/rlclientlib/live_model_impl.cc b/rlclientlib/live_model_impl.cc index 90854c0cd..3bbd4a4cf 100644 --- a/rlclientlib/live_model_impl.cc +++ b/rlclientlib/live_model_impl.cc @@ -17,10 +17,15 @@ #include "vw/explore/explore.h" #include "vw_model/safe_vw.h" +#ifdef RL_BUILD_FEDERATION +# include "federation/local_loop_controller.h" +#endif + #include #include #include #include +#include // Some namespace changes for more concise code namespace e = exploration; @@ -50,19 +55,66 @@ void default_error_callback(const api_status& status, void* watchdog_context) watchdog->set_unhandled_background_error(true); } +int live_model_impl::check_if_local_loop(bool& output, api_status* status) +{ + std::string model_src = _configuration.get(name::MODEL_SRC, value::get_default_data_transport()); + std::string interaction_sender = + _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::get_default_interaction_sender()); + std::string observation_sender = + _configuration.get(name::OBSERVATION_SENDER_IMPLEMENTATION, value::get_default_observation_sender()); + + if (model_src != value::LOCAL_LOOP_MODEL_DATA && interaction_sender != value::LOCAL_LOOP_SENDER && + observation_sender != value::LOCAL_LOOP_SENDER) + { + // no local loop options used + output = false; + return error_code::success; + } + + if (model_src == value::LOCAL_LOOP_MODEL_DATA) + { + // (model_src == LOCAL_LOOP_MODEL_DATA) determines that local loop is used + // set default value of sender implementation to LOCAL_LOOP_SENDER + interaction_sender = _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + observation_sender = _configuration.get(name::OBSERVATION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + + // check that senders are set to allowed values here + // currently, only LOCAL_LOOP_SENDER is allowed + if (interaction_sender == value::LOCAL_LOOP_SENDER && observation_sender == value::LOCAL_LOOP_SENDER) + { + output = true; + return error_code::success; + } + } + + RETURN_ERROR_ARG(_trace_logger.get(), status, invalid_argument, + "Incompatible values for configuration options MODEL_SRC=", model_src, + " and INTERACTION_SENDER_IMPLEMENTATION=", interaction_sender, + " and OBSERVATION_SENDER_IMPLEMENTATION=", observation_sender); +} + int live_model_impl::init(api_status* status) { RETURN_IF_FAIL(init_trace(status)); RETURN_IF_FAIL(init_model(status)); - RETURN_IF_FAIL(init_model_mgmt(status)); - RETURN_IF_FAIL(init_loggers(status)); + + bool is_local_loop = false; + RETURN_IF_FAIL(check_if_local_loop(is_local_loop, status)); + if (is_local_loop) { RETURN_IF_FAIL(init_local_loop(status)); } + else + { + RETURN_IF_FAIL(init_model_mgmt(status)); + RETURN_IF_FAIL(init_loggers(status)); + } if (_protocol_version == 1) { if (_configuration.get_bool("interaction", name::USE_COMPRESSION, false) || _configuration.get_bool("interaction", name::USE_DEDUP, false) || _configuration.get_bool("observation", name::USE_COMPRESSION, false)) - { RETURN_ERROR_LS(_trace_logger.get(), status, content_encoding_error); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, content_encoding_error); + } } _initial_epsilon = _configuration.get_float(name::INITIAL_EPSILON, 0.2f); @@ -86,10 +138,7 @@ int live_model_impl::choose_rank( RETURN_IF_FAIL(explore_only(event_id, context, response, status)); response.set_model_id("N/A"); } - else - { - RETURN_IF_FAIL(explore_exploit(event_id, context, response, status)); - } + else { RETURN_IF_FAIL(explore_exploit(event_id, context, response, status)); } response.set_event_id(event_id); if (_learning_mode == LOGGINGONLY) @@ -108,7 +157,9 @@ int live_model_impl::choose_rank( // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -140,7 +191,9 @@ int live_model_impl::request_continuous_action(const char* event_id, string_view RETURN_IF_FAIL(_interaction_logger->log_continuous_action(context.data(), flags, response, status)); if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -204,7 +257,9 @@ int live_model_impl::request_decision( // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -277,7 +332,9 @@ int live_model_impl::request_multi_slot_decision(const char* event_id, string_vi // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -321,7 +378,9 @@ int live_model_impl::request_multi_slot_decision(const char* event_id, string_vi // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -488,6 +547,24 @@ int live_model_impl::init_loggers(api_status* status) &ranking_data_sender, ranking_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(ranking_data_sender->init(_configuration, status)); + // Get the name of raw data (as opposed to message) sender for observations. + const auto* const outcome_sender_impl = + _configuration.get(name::OBSERVATION_SENDER_IMPLEMENTATION, value::get_default_observation_sender()); + i_sender* outcome_sender = nullptr; + + // Use the name to create an instance of raw data sender for observations + _configuration.set(config_constants::CONFIG_SECTION, config_constants::OBSERVATION); + RETURN_IF_FAIL(_sender_factory->create( + &outcome_sender, outcome_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); + RETURN_IF_FAIL(outcome_sender->init(_configuration, status)); + + RETURN_IF_FAIL(init_loggers_common(ranking_data_sender, outcome_sender, status)); + return error_code::success; +} + +// Common part for both init_loggers and init_local_loop +int live_model_impl::init_loggers_common(i_sender* ranking_data_sender, i_sender* outcome_sender, api_status* status) +{ // Create a message sender that will prepend the message with a preamble and send the raw data using the // factory created raw data sender l::i_message_sender* ranking_msg_sender = new l::preamble_message_sender(ranking_data_sender); @@ -514,17 +591,6 @@ int live_model_impl::init_loggers(api_status* status) ranking_msg_sender, _watchdog, ranking_time_provider, _logger_extensions.get(), &_error_cb)); RETURN_IF_FAIL(_interaction_logger->init(status)); - // Get the name of raw data (as opposed to message) sender for observations. - const auto* const outcome_sender_impl = - _configuration.get(name::OBSERVATION_SENDER_IMPLEMENTATION, value::get_default_observation_sender()); - i_sender* outcome_sender = nullptr; - - // Use the name to create an instance of raw data sender for observations - _configuration.set(config_constants::CONFIG_SECTION, config_constants::OBSERVATION); - RETURN_IF_FAIL(_sender_factory->create( - &outcome_sender, outcome_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); - RETURN_IF_FAIL(outcome_sender->init(_configuration, status)); - // Create a message sender that will prepend the message with a preamble and send the raw data using the // factory created raw data sender l::i_message_sender* outcome_msg_sender = new l::preamble_message_sender(outcome_sender); @@ -607,7 +673,9 @@ int live_model_impl::explore_only( size_t action_count = context_info.actions.size(); if (action_count < 1) - { RETURN_ERROR_LS(_trace_logger.get(), status, json_no_actions_found) << "Context must have at least one action"; } + { + RETURN_ERROR_LS(_trace_logger.get(), status, json_no_actions_found) << "Context must have at least one action"; + } vector pdf(action_count); // Generate a pdf with epsilon distributed between all action. @@ -616,7 +684,9 @@ int live_model_impl::explore_only( const auto top_action_id = 0; auto scode = e::generate_epsilon_greedy(_initial_epsilon, top_action_id, begin(pdf), end(pdf)); if (S_EXPLORATION_OK != scode) - { RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; } + { + RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; + } // The seed used is composed of uniform_hash(app_id) + uniform_hash(event_id) const uint64_t seed = VW::uniform_hash(event_id, strlen(event_id), 0) + _seed_shift; @@ -626,7 +696,9 @@ int live_model_impl::explore_only( scode = e::sample_after_normalizing(seed, begin(pdf), end(pdf), chosen_index); if (S_EXPLORATION_OK != scode) - { RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; } + { + RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; + } // NOTE: When there is no model, the rank // step was done by the user. i.e. Actions are already in ranked order @@ -646,7 +718,9 @@ int live_model_impl::explore_only( scode = e::swap_chosen(begin(response), end(response), chosen_index); if (S_EXPLORATION_OK != scode) - { RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration (Swap) error code: " << scode; } + { + RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration (Swap) error code: " << scode; + } RETURN_IF_FAIL(response.set_chosen_action_id(chosen_index)); @@ -674,19 +748,77 @@ int live_model_impl::init_model_mgmt(api_status* status) // Initialize transport for the model using transport factory const auto* const tranport_impl = _configuration.get(name::MODEL_SRC, value::get_default_data_transport()); m::i_data_transport* ptransport = nullptr; - RETURN_IF_FAIL(_t_factory->create(&ptransport, tranport_impl, _configuration, status)); + RETURN_IF_FAIL(_t_factory->create(&ptransport, tranport_impl, _configuration, _trace_logger.get(), status)); + // This class manages lifetime of transport - this->_transport.reset(ptransport); + _transport.reset(ptransport); if (_bg_model_proc) { // Initialize background process and start downloading models - this->_model_download.reset(new m::model_downloader(ptransport, &_data_cb, _trace_logger.get())); + this->_model_download.reset(new m::model_downloader(_transport.get(), &_data_cb, _trace_logger.get())); return _bg_model_proc->init(_model_download.get(), status); } + return refresh_model(status); +} + +#ifdef RL_BUILD_FEDERATION +int live_model_impl::init_local_loop(api_status* status) +{ + std::string model_src = _configuration.get(name::MODEL_SRC, value::get_default_data_transport()); + + // This function should only be called when the configuration is set to use LOCAL_LOOP_MODEL_DATA + assert(model_src == value::LOCAL_LOOP_MODEL_DATA); + + // Creating i_data_transport with type LOCAL_LOOP_MODEL_DATA results in local_loop_controller + m::i_data_transport* output = nullptr; + RETURN_IF_FAIL(_t_factory->create(&output, model_src, _configuration, _trace_logger.get(), status)); + std::unique_ptr llc(reinterpret_cast(output)); + + // Create senders with default sender implementation set to LOCAL_LOOP_SENDER + std::string interaction_sender_type = + _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + std::string observation_sender_type = + _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + + i_sender* interaction_sender = nullptr; + if (interaction_sender_type == value::LOCAL_LOOP_SENDER) { interaction_sender = llc->get_local_sender().release(); } + else + { + RETURN_IF_FAIL(_sender_factory->create( + &interaction_sender, interaction_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); + RETURN_IF_FAIL(interaction_sender->init(_configuration, status)); + } + + i_sender* observation_sender = nullptr; + if (observation_sender_type == value::LOCAL_LOOP_SENDER) { observation_sender = llc->get_local_sender().release(); } + else + { + RETURN_IF_FAIL(_sender_factory->create( + &observation_sender, observation_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); + RETURN_IF_FAIL(observation_sender->init(_configuration, status)); + } + RETURN_IF_FAIL(init_loggers_common(interaction_sender, observation_sender, status)); + + // Set live_model_impl's data transport to local loop controller + _transport = std::move(llc); + + if (_bg_model_proc) + { + // Initialize background process and start downloading models + this->_model_download.reset(new m::model_downloader(_transport.get(), &_data_cb, _trace_logger.get())); + return _bg_model_proc->init(_model_download.get(), status); + } return refresh_model(status); } +#else +int live_model_impl::init_local_loop(api_status* status) +{ + RETURN_ERROR_ARG(_trace_logger.get(), status, invalid_argument, + "Cannot use LOCAL_LOOP_MODEL_DATA because library was compiled without support for federated learning"); +} +#endif int live_model_impl::request_episodic_decision(const char* event_id, const char* previous_id, string_view context_json, unsigned int flags, ranking_response& resp, episode_state& episode, api_status* status) @@ -730,21 +862,27 @@ int live_model_impl::request_episodic_decision(const char* event_id, const char* int check_null_or_empty(const char* arg1, string_view arg2, i_trace* trace, api_status* status) { if ((arg1 == nullptr) || strlen(arg1) == 0 || arg2.empty()) - { RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); } + { + RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); + } return error_code::success; } int check_null_or_empty(string_view arg1, i_trace* trace, api_status* status) { if (arg1.empty()) - { RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); } + { + RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); + } return error_code::success; } int check_null_or_empty(const char* arg1, i_trace* trace, api_status* status) { if ((arg1 == nullptr) || strlen(arg1) == 0) - { RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); } + { + RETURN_ERROR_ARG(trace, status, invalid_argument, "one of the arguments passed to the ds is null or empty"); + } return error_code::success; } @@ -780,7 +918,9 @@ int reset_chosen_action_multi_slot(multi_slot_response_detailed& response, const for (auto& slot : response) { if (!baseline_actions.empty() && baseline_actions.size() >= index) - { RETURN_IF_FAIL(slot.set_chosen_action_id(baseline_actions[index])); } + { + RETURN_IF_FAIL(slot.set_chosen_action_id(baseline_actions[index])); + } else { // implicit baseline is the action corresponding to the slot index @@ -799,7 +939,9 @@ void autogenerate_missing_uuids( for (auto& complete_id : complete_ids) { if (complete_id.empty()) - { complete_id = boost::uuids::to_string(boost::uuids::random_generator()()) + std::to_string(seed_shift); } + { + complete_id = boost::uuids::to_string(boost::uuids::random_generator()()) + std::to_string(seed_shift); + } } } } // namespace reinforcement_learning diff --git a/rlclientlib/live_model_impl.h b/rlclientlib/live_model_impl.h index ba4a8389e..efa97203a 100644 --- a/rlclientlib/live_model_impl.h +++ b/rlclientlib/live_model_impl.h @@ -81,7 +81,10 @@ class live_model_impl int init_model(api_status* status); int init_model_mgmt(api_status* status); int init_loggers(api_status* status); + int init_loggers_common(i_sender* ranking_data_sender, i_sender* outcome_sender, api_status* status); int init_trace(api_status* status); + int init_local_loop(api_status* status); + int check_if_local_loop(bool& output, api_status* status); static void _handle_model_update(const model_management::model_data& data, live_model_impl* ctxt); void handle_model_update(const model_management::model_data& data); int explore_only(const char* event_id, string_view context, ranking_response& response, api_status* status) const; @@ -141,7 +144,9 @@ int live_model_impl::report_outcome_internal(const char* event_id, D outcome, ap // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } @@ -157,7 +162,9 @@ int live_model_impl::report_outcome_internal(const char* primary_id, I secondary // Check watchdog for any background errors. Do this at the end of function so that the work is still done. if (_watchdog.has_background_error_been_reported()) - { RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); } + { + RETURN_ERROR_LS(_trace_logger.get(), status, unhandled_background_error_occurred); + } return error_code::success; } diff --git a/rlclientlib/logger/async_batcher.h b/rlclientlib/logger/async_batcher.h index 53c44594b..7c4b8fa22 100644 --- a/rlclientlib/logger/async_batcher.h +++ b/rlclientlib/logger/async_batcher.h @@ -88,7 +88,7 @@ class async_batcher : public i_async_batcher queue_mode_enum _queue_mode; std::condition_variable _cv; std::mutex _m; - utility::object_pool _buffer_pool; + std::shared_ptr> _buffer_pool; const char* _batch_content_encoding; float _subsample_rate; events_counter_status _events_counter_status; @@ -132,10 +132,7 @@ int async_batcher::append(TFunc&& func, TEvent* event, api_ std::unique_lock lk(_m); _cv.wait(lk, [this] { return !_queue.is_full(); }); } - else if (queue_mode_enum::DROP == _queue_mode) - { - _queue.prune(_pass_prob); - } + else if (queue_mode_enum::DROP == _queue_mode) { _queue.prune(_pass_prob); } } return error_code::success; @@ -180,10 +177,7 @@ int async_batcher::fill_buffer( uint64_t original_event_count = (_buffer_end_event_index - buffer_start_event_index); RETURN_IF_FAIL(collection_serializer.finalize(status, original_event_count)); } - else - { - RETURN_IF_FAIL(collection_serializer.finalize(status)); - } + else { RETURN_IF_FAIL(collection_serializer.finalize(status)); } return error_code::success; } @@ -201,10 +195,12 @@ void async_batcher::flush() { api_status status; - auto buffer = _buffer_pool.acquire(); + auto buffer = _buffer_pool->acquire(); if (fill_buffer(buffer, remaining, &status) != error_code::success) { ERROR_CALLBACK(_perror_cb, status); } if (_sender->send(TSerializer::message_id(), buffer, &status) != error_code::success) - { ERROR_CALLBACK(_perror_cb, status); } + { + ERROR_CALLBACK(_perror_cb, status); + } } } @@ -225,6 +221,7 @@ async_batcher::async_batcher(i_message_sender* sender, util , _subsample_rate(config.subsample_rate) , _events_counter_status(config.event_counter_status) { + _buffer_pool = utility::object_pool::create(); } template class TSerializer> diff --git a/rlclientlib/logger/event_logger.cc b/rlclientlib/logger/event_logger.cc index 0d2076d1c..6ee4213b9 100644 --- a/rlclientlib/logger/event_logger.cc +++ b/rlclientlib/logger/event_logger.cc @@ -21,7 +21,8 @@ int interaction_logger::log(const char* event_id, string_view context, unsigned auto evt_copy = ranking_event::choose_rank(event_id, context, flags, response, now, 1.0f, learning_mode); *evt_sp = std::move(evt_copy); - auto evt_fn = [evt_sp](ranking_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](ranking_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -39,7 +40,8 @@ int ccb_logger::log_decisions(std::vector& event_ids, string_view c *evt_sp = std::move(evt_copy); const char* evt_id = evt_sp->get_seed_id().c_str(); - auto evt_fn = [evt_sp](decision_ranking_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](decision_ranking_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -57,7 +59,8 @@ int multi_slot_logger::log_decision(const std::string& event_id, string_view con multi_slot_decision_event::request_decision(event_id, context, flags, action_ids, pdfs, model_version, now); *evt_sp = std::move(evt_copy); - auto evt_fn = [evt_sp](multi_slot_decision_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](multi_slot_decision_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -72,7 +75,8 @@ int observation_logger::report_action_taken(const char* event_id, api_status* st auto evt_copy = outcome_event::report_action_taken(event_id, now); *evt_sp = std::move(evt_copy); - auto evt_fn = [evt_sp](outcome_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](outcome_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -94,7 +98,8 @@ int generic_event_logger::log(const char* event_id, generic_event::payload_buffe auto evt_sp = std::make_shared( event_id, now, type, std::move(payload), content_type, std::move(objects), _app_id); - auto evt_fn = [evt_sp](generic_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](generic_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; diff --git a/rlclientlib/logger/event_logger.h b/rlclientlib/logger/event_logger.h index 91ce59c30..254bd8d17 100644 --- a/rlclientlib/logger/event_logger.h +++ b/rlclientlib/logger/event_logger.h @@ -148,7 +148,8 @@ class observation_logger : public event_logger auto evt_sp = std::make_shared(); auto evt_copy = outcome_event::report_outcome(event_id, outcome, now); *evt_sp = std::move(evt_copy); - auto evt_fn = [evt_sp](outcome_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](outcome_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -178,7 +179,8 @@ class generic_event_logger : public event_logger // as a copy the ensure their lifetime. // TODO: See if there's a way to do this without the copy, since the pack can contain some pretty // expensive objects - auto evt_fn = [evt_sp, type, ext, serializer, args...](generic_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp, type, ext, serializer, args...](generic_event& out_evt, api_status* status) -> int + { RETURN_IF_FAIL(evt_sp->transform(ext, serializer, status, args...)); out_evt = std::move(*evt_sp); return error_code::success; diff --git a/rlclientlib/logger/event_queue.h b/rlclientlib/logger/event_queue.h index 6aa455e49..12ad403c6 100644 --- a/rlclientlib/logger/event_queue.h +++ b/rlclientlib/logger/event_queue.h @@ -82,7 +82,9 @@ class event_queue std::unique_lock mlock(_mutex); if (!is_full()) return; for (auto it = _queue.begin(); it != _queue.end();) - { it = std::get<2>(*it)->try_drop(pass_prob, _drop_pass) ? erase(it) : (++it); } + { + it = std::get<2>(*it)->try_drop(pass_prob, _drop_pass) ? erase(it) : (++it); + } ++_drop_pass; } diff --git a/rlclientlib/logger/http_transport_client.h b/rlclientlib/logger/http_transport_client.h index 10162db08..d0baab533 100644 --- a/rlclientlib/logger/http_transport_client.h +++ b/rlclientlib/logger/http_transport_client.h @@ -16,8 +16,10 @@ #include #include +#include #include #include +#include using namespace web::http; using namespace std::chrono; @@ -38,8 +40,8 @@ class http_transport_client : public i_sender virtual int init(const utility::configuration& config, api_status* status) override; // Takes the ownership of the i_http_client and delete it at the end of lifetime - http_transport_client( - i_http_client* client, size_t tasks_count, size_t MAX_RETRIES, i_trace* trace, error_callback_fn* _error_cb); + http_transport_client(i_http_client* client, size_t tasks_count, size_t MAX_RETRIES, + std::chrono::milliseconds max_retry_duration, i_trace* trace, error_callback_fn* _error_cb); ~http_transport_client(); protected: @@ -52,7 +54,10 @@ class http_transport_client : public i_sender using buffer = std::shared_ptr; http_request_task() = default; http_request_task(i_http_client* client, http_headers headers, const buffer& data, - size_t max_retries = 1, // If MAX_RETRIES is set to 1, only the initial request will be attempted. + size_t max_retries = 0, // If MAX_RETRIES is set to 0, only the initial request will be attempted. + std::chrono::milliseconds max_retry_duration = std::chrono::milliseconds( + 360000), // retries will halt before max_retries attempts if this time elapses + // first error_callback_fn* error_callback = nullptr, i_trace* trace = nullptr); // The constructor kicks off an async request which captures the this variable. If this object is moved then the @@ -66,7 +71,13 @@ class http_transport_client : public i_sender int join(); private: - pplx::task send_request(size_t try_count); + // repeat request until success or _max_retries attempted + // returns the http::status_code of the final attempt. + pplx::task send_request(); + + // repeat request until success or _max_retries attempted + // returns the http_reponse of the final attempt. + pplx::task send_request_with_retries(size_t try_count); i_http_client* _client; http_headers _headers; @@ -74,7 +85,9 @@ class http_transport_client : public i_sender pplx::task _task; - size_t _max_retries = 1; + std::chrono::time_point _start_time; + size_t _max_retry_count = 1; + std::chrono::milliseconds _max_retry_duration; error_callback_fn* _error_callback; i_trace* _trace; @@ -96,26 +109,52 @@ class http_transport_client : public i_sender std::mutex _mutex; moving_queue> _tasks; const size_t _max_tasks_count; - const size_t _max_retries; + const size_t _max_retry_count; + const std::chrono::milliseconds _max_retry_duration; i_trace* _trace; error_callback_fn* _error_callback; }; template http_transport_client::http_request_task::http_request_task(i_http_client* client, http_headers headers, - const buffer& post_data, size_t max_retries, error_callback_fn* error_callback, i_trace* trace) + const buffer& post_data, size_t max_retries, std::chrono::milliseconds max_retry_duration, + error_callback_fn* error_callback, i_trace* trace) : _client(client) , _headers(headers) , _post_data(post_data) - , _max_retries(max_retries) + , _start_time(std::chrono::system_clock::now()) + , _max_retry_count(max_retries) + , _max_retry_duration(max_retry_duration) , _error_callback(error_callback) , _trace(trace) { - _task = send_request(0 /* inital try */); + _task = send_request(); } template -pplx::task http_transport_client::http_request_task::send_request( +pplx::task http_transport_client::http_request_task::send_request() +{ + return send_request_with_retries(0 /* inital try */) + .then( + [this](pplx::task response) + { + web::http::status_code code = status_codes::InternalError; + + try + { + code = response.get().status_code(); + } + catch (const std::exception& e) + { + TRACE_ERROR(_trace, e.what()); + } + + return code; + }); +} + +template +pplx::task http_transport_client::http_request_task::send_request_with_retries( size_t try_count) { http_request request(methods::POST); @@ -126,45 +165,53 @@ pplx::task http_transport_client::http_r const auto stream = concurrency::streams::bytestream::open_istream(container); request.set_body(stream, container_size); - return _client->request(request).then([this, try_count](pplx::task response) { - web::http::status_code code = status_codes::InternalError; - api_status status; + // lambda which examines the provided task and either 1) generates a replacement task to retry + // the request, or 2) passes the provided task downstream to emit its response + auto retry_request_on_failure_lambda = [this, try_count]( + pplx::task response_task) -> pplx::task + { + web::http::status_code response_code = status_codes::InternalError; try { - code = response.get().status_code(); + response_code = response_task.get().status_code(); } catch (const std::exception& e) { TRACE_ERROR(_trace, e.what()); } - // If the response is not the expected code then it has failed. Retry if possible otherwise report background - // error. - if (code != status_codes::Created && code != status_codes::NoContent) + bool success = (response_code >= status_codes::OK) && (response_code < status_codes::MultipleChoices); + if (success) { return response_task; } + + // If the response is not success class then it has failed. Retry if possible otherwise report background error. + auto elapsed_time = + std::chrono::duration_cast(std::chrono::system_clock::now() - _start_time); + if ((try_count >= _max_retry_count) || (elapsed_time > _max_retry_duration)) { - // Stop condition of recurison. - if (try_count < _max_retries) - { - TRACE_ERROR(_trace, "HTTP request failed, retrying..."); - - // Yes, recursively send another request inside this one. If a subsequent request returns success we are - // good, otherwise the failure will propagate. - return send_request(try_count + 1).get(); - } - else - { - auto msg = u::concat("(expected 201): Found ", code, ", failed after ", try_count, " retries."); - api_status::try_update(&status, error_code::http_bad_status_code, msg.c_str()); - ERROR_CALLBACK(_error_callback, status); - - return code; - } + // We have exhausted retry attempts, log and return the task describing the failure + + api_status status; + auto msg = u::concat("(expected 201): Found ", response_code, ", failed after ", try_count, " retries over ", + elapsed_time.count(), "ms."); + api_status::try_update(&status, error_code::http_bad_status_code, msg.c_str()); + ERROR_CALLBACK(_error_callback, status); + + return response_task; } - // We have succeeded, return success. - return code; - }); + static const std::chrono::milliseconds RETRY_DELAY = std::chrono::milliseconds(1000); + TRACE_ERROR( + _trace, u::concat("HTTP request failed with ", response_code, ", retrying in ", RETRY_DELAY.count(), "ms...")); + + // using sleep_for is regrettable because it blocks this thread, but pplx doesn't implement better yield options. + std::this_thread::sleep_for(RETRY_DELAY); + + // return a new task which will resubmit the original request + return send_request_with_retries(try_count + 1); + }; + + return _client->request(request).then(retry_request_on_failure_lambda); } template @@ -219,8 +266,8 @@ int http_transport_client::v_send(const buffer& post_data, api_s // Before creating the task, ensure that it is allowed to be created. if (_tasks.size() >= _max_tasks_count) { RETURN_IF_FAIL(pop_task(status)); } - std::unique_ptr request_task( - new http_request_task(_client.get(), headers, post_data, _max_retries, _error_callback, _trace)); + std::unique_ptr request_task(new http_request_task( + _client.get(), headers, post_data, _max_retry_count, _max_retry_duration, _error_callback, _trace)); _tasks.push(std::move(request_task)); } catch (const std::exception& e) @@ -232,10 +279,11 @@ int http_transport_client::v_send(const buffer& post_data, api_s template http_transport_client::http_transport_client(i_http_client* client, size_t max_tasks_count, - size_t max_retries, i_trace* trace, error_callback_fn* error_callback) + size_t max_retries, std::chrono::milliseconds max_retry_duration, i_trace* trace, error_callback_fn* error_callback) : _client(client) , _max_tasks_count(max_tasks_count) - , _max_retries(max_retries) + , _max_retry_count(max_retries) + , _max_retry_duration(max_retry_duration) , _trace(trace) , _error_callback(error_callback) { diff --git a/rlclientlib/logger/preamble_sender.cc b/rlclientlib/logger/preamble_sender.cc index f7709fbf7..50d611340 100644 --- a/rlclientlib/logger/preamble_sender.cc +++ b/rlclientlib/logger/preamble_sender.cc @@ -18,7 +18,9 @@ int preamble_message_sender::send(const uint16_t msg_type, const buffer& db, api pre.msg_type = msg_type; pre.msg_size = static_cast(db->body_filled_size()); if (!pre.write_to_bytes(db->preamble_begin(), db->preamble_size())) - { RETURN_ERROR_LS(nullptr, status, preamble_error) << " Write error."; } + { + RETURN_ERROR_LS(nullptr, status, preamble_error) << " Write error."; + } // Send message with preamble return _sender->send(db, status); } diff --git a/rlclientlib/model_mgmt/file_model_loader.cc b/rlclientlib/model_mgmt/file_model_loader.cc index 629306b08..b4379cbf4 100644 --- a/rlclientlib/model_mgmt/file_model_loader.cc +++ b/rlclientlib/model_mgmt/file_model_loader.cc @@ -66,7 +66,9 @@ int file_model_loader::get_data(model_data& data, api_status* status) in_strm.seekg(0, std::ios::beg); auto* const buff = data.alloc(curr_file_size); if (!in_strm.read(buff, curr_file_size)) - { RETURN_ERROR_LS(_trace, status, file_read_error) << " file_name = " << _file_name; } + { + RETURN_ERROR_LS(_trace, status, file_read_error) << " file_name = " << _file_name; + } data.data_sz(curr_file_size); data.increment_refresh_count(); in_strm.close(); diff --git a/rlclientlib/model_mgmt/restapi_data_transport.cc b/rlclientlib/model_mgmt/restapi_data_transport.cc index 2abf80101..55f8af818 100644 --- a/rlclientlib/model_mgmt/restapi_data_transport.cc +++ b/rlclientlib/model_mgmt/restapi_data_transport.cc @@ -59,32 +59,39 @@ int restapi_data_transport::get_data_info( http_request request(_method_type); RETURN_IF_FAIL(add_authentiction_header(request.headers(), status)); // Build request URI and start the request. - auto request_task = _httpcli->request(request).then([&](http_response response) { - if (response.status_code() != 200) - { - // if the call using HEAD fails, try with GET only once and return the results of GET request call - if (_retry_get_data) + auto request_task = _httpcli->request(request).then( + [&](http_response response) { - _retry_get_data = false; - _method_type = methods::GET; - RETURN_IF_FAIL(get_data_info(last_modified, sz, status)); - return error_code::success; - } + if (response.status_code() != 200) + { + // if the call using HEAD fails, try with GET only once and return the results of GET request call + if (_retry_get_data) + { + _retry_get_data = false; + _method_type = methods::GET; + RETURN_IF_FAIL(get_data_info(last_modified, sz, status)); + return error_code::success; + } - RETURN_ERROR_ARG(_trace, status, http_bad_status_code, "Found: ", response.status_code(), _httpcli->get_url()); - } - const auto iter = response.headers().find(U("Last-Modified")); - if (iter == response.headers().end()) - { RETURN_ERROR_ARG(_trace, status, last_modified_not_found, _httpcli->get_url()); } + RETURN_ERROR_ARG( + _trace, status, http_bad_status_code, "Found: ", response.status_code(), _httpcli->get_url()); + } + const auto iter = response.headers().find(U("Last-Modified")); + if (iter == response.headers().end()) + { + RETURN_ERROR_ARG(_trace, status, last_modified_not_found, _httpcli->get_url()); + } - last_modified = ::utility::datetime::from_string(iter->second); - if (last_modified.to_interval() == 0) - { RETURN_ERROR_ARG(_trace, status, last_modified_invalid, _httpcli->get_url()); } + last_modified = ::utility::datetime::from_string(iter->second); + if (last_modified.to_interval() == 0) + { + RETURN_ERROR_ARG(_trace, status, last_modified_invalid, _httpcli->get_url()); + } - sz = response.headers().content_length(); + sz = response.headers().content_length(); - return error_code::success; - }); + return error_code::success; + }); // Wait for all the outstanding I/O to complete and handle any exceptions try @@ -124,48 +131,49 @@ int restapi_data_transport::get_data(model_data& ret, api_status* status) _httpcli ->request(request) // Handle response headers arriving. - .then([&](const pplx::task& resp_task) { - auto response = resp_task.get(); - if (response.status_code() != 200) - { - RETURN_ERROR_ARG( - _trace, status, http_bad_status_code, "Found: ", response.status_code(), _httpcli->get_url()); - } - - const auto iter = response.headers().find(U("Last-Modified")); - if (iter == response.headers().end()) - { RETURN_ERROR_ARG(_trace, status, last_modified_not_found, _httpcli->get_url()); } - - curr_last_modified = ::utility::datetime::from_string(iter->second); - if (curr_last_modified.to_interval() == 0) - { - RETURN_ERROR_ARG(_trace, status, last_modified_invalid, - "Found: ", ::utility::conversions::to_utf8string(curr_last_modified.to_string()), - _httpcli->get_url()); - } - - curr_datasz = response.headers().content_length(); - if (curr_datasz > 0) - { - auto* const buff = ret.alloc(curr_datasz); - const Concurrency::streams::rawptr_buffer rb(buff, curr_datasz, std::ios::out); - - // Write response body into the file. - const auto readval = - response.body().read_to_end(rb).get(); // need to use task.get to throw exceptions properly - - ret.data_sz(readval); - ret.increment_refresh_count(); - _datasz = readval; - } - else - { - ret.data_sz(0); - } - - _last_modified = curr_last_modified; - return error_code::success; - }); + .then( + [&](const pplx::task& resp_task) + { + auto response = resp_task.get(); + if (response.status_code() != 200) + { + RETURN_ERROR_ARG( + _trace, status, http_bad_status_code, "Found: ", response.status_code(), _httpcli->get_url()); + } + + const auto iter = response.headers().find(U("Last-Modified")); + if (iter == response.headers().end()) + { + RETURN_ERROR_ARG(_trace, status, last_modified_not_found, _httpcli->get_url()); + } + + curr_last_modified = ::utility::datetime::from_string(iter->second); + if (curr_last_modified.to_interval() == 0) + { + RETURN_ERROR_ARG(_trace, status, last_modified_invalid, + "Found: ", ::utility::conversions::to_utf8string(curr_last_modified.to_string()), + _httpcli->get_url()); + } + + curr_datasz = response.headers().content_length(); + if (curr_datasz > 0) + { + auto* const buff = ret.alloc(curr_datasz); + const Concurrency::streams::rawptr_buffer rb(buff, curr_datasz, std::ios::out); + + // Write response body into the file. + const auto readval = + response.body().read_to_end(rb).get(); // need to use task.get to throw exceptions properly + + ret.data_sz(readval); + ret.increment_refresh_count(); + _datasz = readval; + } + else { ret.data_sz(0); } + + _last_modified = curr_last_modified; + return error_code::success; + }); // Wait for all the outstanding I/O to complete and handle any exceptions try diff --git a/rlclientlib/multi_slot_response_detailed.cc b/rlclientlib/multi_slot_response_detailed.cc index 95406914c..46c14e72e 100644 --- a/rlclientlib/multi_slot_response_detailed.cc +++ b/rlclientlib/multi_slot_response_detailed.cc @@ -23,7 +23,9 @@ const char* multi_slot_response_detailed::get_model_id() const { return _model_i int multi_slot_response_detailed::set_slot_at_index(const unsigned int index, slot_ranking&& slot, api_status* status) { if (index >= _decision.size()) - { RETURN_ERROR_ARG(nullptr, status, slot_index_out_of_bounds_error, "Slot index out of bounds"); } + { + RETURN_ERROR_ARG(nullptr, status, slot_index_out_of_bounds_error, "Slot index out of bounds"); + } _decision[index] = std::move(slot); return error_code::success; } diff --git a/rlclientlib/sampling.cc b/rlclientlib/sampling.cc index 3ab59e685..66a3503b7 100644 --- a/rlclientlib/sampling.cc +++ b/rlclientlib/sampling.cc @@ -35,13 +35,17 @@ int populate_response(const std::vector>& action_ids, cons i_trace* trace_logger, api_status* status) { if (action_ids.size() != pdfs.size()) - { RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and pdfs must be the same size"; } + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and pdfs must be the same size"; + } response.set_model_id(std::move(model_id)); for (size_t i = 0; i < action_ids.size(); i++) { if (action_ids[i].size() != pdfs[i].size()) - { RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids[i] and pdfs[i] must be the same size"; } + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids[i] and pdfs[i] must be the same size"; + } response.push_back(event_ids[i], action_ids[i][0], pdfs[i][0]); } @@ -63,10 +67,14 @@ int populate_multi_slot_response(const std::vector>& actio const std::vector& slot_ids, multi_slot_response& response, i_trace* trace_logger, api_status* status) { if (action_ids.size() != pdfs.size()) - { RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and pdfs must be the same size"; } + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and pdfs must be the same size"; + } if (action_ids.size() != slot_ids.size()) - { RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and slot_ids must be the same size"; } + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids and slot_ids must be the same size"; + } response.set_event_id(std::move(event_id)); response.set_model_id(std::move(model_id)); @@ -74,7 +82,9 @@ int populate_multi_slot_response(const std::vector>& actio for (size_t i = 0; i < action_ids.size(); i++) { if (action_ids[i].size() != pdfs[i].size()) - { RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids[i] and pdfs[i] must be the same size"; } + { + RETURN_ERROR_LS(trace_logger, status, invalid_argument) << "action_ids[i] and pdfs[i] must be the same size"; + } response.push_back(slot_ids[i], action_ids[i][0], pdfs[i][0]); } @@ -128,7 +138,9 @@ int sample_and_populate_response(uint64_t rnd_seed, std::vector& action_ids scode = e::swap_chosen(std::begin(response), std::end(response), chosen_index); if (S_EXPLORATION_OK != scode) - { RETURN_ERROR_LS(trace_logger, status, exploration_error) << "Exploration error code: " << scode; } + { + RETURN_ERROR_LS(trace_logger, status, exploration_error) << "Exploration error code: " << scode; + } return error_code::success; } diff --git a/rlclientlib/serialization/fb_serializer.h b/rlclientlib/serialization/fb_serializer.h index b152c4ece..20518b4e5 100644 --- a/rlclientlib/serialization/fb_serializer.h +++ b/rlclientlib/serialization/fb_serializer.h @@ -384,11 +384,10 @@ template <> inline void fb_collection_serializer::create_header() { if (_original_event_count == 0) - { _batch_metadata_offset = v2::CreateBatchMetadataDirect(_builder, _content_encoding); } - else { - _batch_metadata_offset = v2::CreateBatchMetadataDirect(_builder, _content_encoding, _original_event_count); + _batch_metadata_offset = v2::CreateBatchMetadataDirect(_builder, _content_encoding); } + else { _batch_metadata_offset = v2::CreateBatchMetadataDirect(_builder, _content_encoding, _original_event_count); } return; } diff --git a/rlclientlib/serialization/payload_serializer.h b/rlclientlib/serialization/payload_serializer.h index dcb7aff73..8035c236c 100644 --- a/rlclientlib/serialization/payload_serializer.h +++ b/rlclientlib/serialization/payload_serializer.h @@ -86,7 +86,9 @@ struct multi_slot_serializer : payload_serializer #include -#include - -namespace -{ -constexpr uint64_t ONE_HUNDRED_NANO_DENOMINATOR = 10000000; -using one_hundred_nano = std::ratio<1, ONE_HUNDRED_NANO_DENOMINATOR>; -using one_hundred_nanoseconds = std::chrono::duration; -} // namespace namespace reinforcement_learning { @@ -22,30 +13,34 @@ std::ostream& operator<<(std::ostream& os, const timestamp& dt) return os; } -timestamp timestamp_from_chrono(const std::chrono::time_point& tp) +timestamp::timestamp(uint16_t yr, uint8_t mo, uint8_t dy, uint8_t h, uint8_t m, uint8_t s, uint32_t ss) + : year(yr), month(mo), day(dy), hour(h), minute(m), second(s), sub_second(ss) +{ +} + +timestamp::timestamp(const std::chrono::time_point& tp) { - timestamp ts; const auto dp = date::floor(tp); const auto ymd = date::year_month_day(dp); const auto duration_since_start_of_day = tp - dp; const auto time = date::make_time(duration_since_start_of_day); - ts.year = int(ymd.year()); - ts.month = unsigned(ymd.month()); - ts.day = unsigned(ymd.day()); - ts.hour = time.hours().count(); - ts.minute = time.minutes().count(); - ts.second = static_cast(time.seconds().count()); - std::chrono::duration usec_since_start_of_day = - std::chrono::duration_cast(duration_since_start_of_day); - ts.sub_second = static_cast(usec_since_start_of_day.count() % ONE_HUNDRED_NANO_DENOMINATOR); - return ts; + auto usec_since_start_of_day = + std::chrono::duration_cast(duration_since_start_of_day); + + year = int(ymd.year()); + month = unsigned(ymd.month()); + day = unsigned(ymd.day()); + hour = time.hours().count(); + minute = time.minutes().count(); + second = static_cast(time.seconds().count()); + sub_second = static_cast(usec_since_start_of_day.count() % timestamp::one_hundred_nano::den); } -std::chrono::time_point chrono_from_timestamp(const timestamp& ts) +std::chrono::time_point timestamp::to_time_point() const { - return date::sys_days{date::year{ts.year} / ts.month / ts.day} + std::chrono::hours{ts.hour} + - std::chrono::minutes{ts.minute} + std::chrono::seconds{ts.second} + std::chrono::nanoseconds{ts.sub_second * 100}; + return date::sys_days{date::year{year} / month / day} + std::chrono::hours{hour} + std::chrono::minutes{minute} + + std::chrono::seconds{second} + timestamp::one_hundred_nanoseconds{sub_second}; } -timestamp clock_time_provider::gmt_now() { return timestamp_from_chrono(std::chrono::system_clock::now()); } +timestamp clock_time_provider::gmt_now() { return timestamp(std::chrono::system_clock::now()); } } // namespace reinforcement_learning diff --git a/rlclientlib/time_helper.h b/rlclientlib/time_helper.h index 87a638b1c..66c6ae368 100644 --- a/rlclientlib/time_helper.h +++ b/rlclientlib/time_helper.h @@ -3,12 +3,16 @@ #include #include #include +#include #include namespace reinforcement_learning { struct timestamp { + using one_hundred_nano = std::ratio<1, 10000000>; + using one_hundred_nanoseconds = std::chrono::duration; + uint16_t year = 0; // year uint8_t month = 0; // month [1-12] uint8_t day = 0; // day [1-31] @@ -16,12 +20,29 @@ struct timestamp uint8_t minute = 0; // minute [0-60] uint8_t second = 0; // second [0-60] uint32_t sub_second = 0; // 0.1 u_second [0 - 9,999,999] + + // Construct timestamp with all zero values + timestamp() = default; + + // Construct timestamp from values for each time component + timestamp(uint16_t yr, uint8_t mo, uint8_t dy, uint8_t h, uint8_t m, uint8_t s, uint32_t ss = 0); + + // Convert std::chrono::time_point to reinforcement_learning::timestamp + explicit timestamp(const std::chrono::time_point&); + + // Overload that typecasts duration to 100ns + template + explicit timestamp(const std::chrono::time_point& tp) + : timestamp(std::chrono::time_point_cast(tp)) + { + } + + // Convert to a std::chrono::time_point with 100ns resolution + std::chrono::time_point to_time_point() const; + friend std::ostream& operator<<(std::ostream& os, const timestamp& dt); }; -timestamp timestamp_from_chrono(const std::chrono::time_point&); -std::chrono::time_point chrono_from_timestamp(const timestamp&); - inline bool operator==(const timestamp& lhs, const timestamp& rhs) { return std::tie(lhs.year, lhs.month, lhs.day, lhs.hour, lhs.minute, lhs.second, lhs.sub_second) == diff --git a/rlclientlib/utility/config_utility.cc b/rlclientlib/utility/config_utility.cc index 6c6adb02f..f5fb0054e 100644 --- a/rlclientlib/utility/config_utility.cc +++ b/rlclientlib/utility/config_utility.cc @@ -76,7 +76,9 @@ int parse_eventhub_conn_str(const std::string& conn_str, std::string& host, std: "Endpoint=sb://([^/]+)[^;]+;SharedAccessKeyName=([^;]+);SharedAccessKey=([^;]+);EntityPath=([^;^\\s]+)"); std::smatch match; if (!std::regex_match(conn_str, match, regex_eh_connstr) && !(match.size() == 5)) - { RETURN_ERROR_LS(trace, status, eh_connstr_parse_error) << conn_str; } + { + RETURN_ERROR_LS(trace, status, eh_connstr_parse_error) << conn_str; + } host = match[1].str(); access_key_name = match[2].str(); access_key = match[3].str(); @@ -147,7 +149,9 @@ int translate_property( // Check if the current field is an EventHub connect string that needs to be parsed. const auto parsed_it = parsed_translation_mapping.find(prop_name); if (parsed_it != parsed_translation_mapping.end()) - { RETURN_IF_FAIL(set_eventhub_config(string_value, parsed_it->second, cc, trace, status)); } + { + RETURN_IF_FAIL(set_eventhub_config(string_value, parsed_it->second, cc, trace, status)); + } else { // Otherwise, just set the value in the config collection. @@ -187,10 +191,7 @@ int create_from_json(const std::string& config_json, configuration& cc, i_trace* const char* string_value = nullptr; if (prop_value.IsString()) { string_value = prop_value.GetString(); } - else if (prop_value.IsBool()) - { - string_value = prop_value.GetBool() ? "true" : "false"; - } + else if (prop_value.IsBool()) { string_value = prop_value.GetBool() ? "true" : "false"; } else { RETURN_ERROR_LS(trace, status, json_parse_error) diff --git a/rlclientlib/utility/context_helper.cc b/rlclientlib/utility/context_helper.cc index a0557ea2a..bb53cefee 100644 --- a/rlclientlib/utility/context_helper.cc +++ b/rlclientlib/utility/context_helper.cc @@ -105,7 +105,9 @@ struct MessageHandler : public rj::BaseReaderHandler, MessageHandler> bool StartObject() { if (((static_cast(_is_multi) | static_cast(_is_slots)) != 0) && _level == 1 && _array_level == 1) - { _item_start = _is.Tell() - 1; } + { + _item_start = _is.Tell() - 1; + } ++_level; return true; diff --git a/rlclientlib/utility/data_buffer.cc b/rlclientlib/utility/data_buffer.cc index ecea7f9c4..d73c3c7cf 100644 --- a/rlclientlib/utility/data_buffer.cc +++ b/rlclientlib/utility/data_buffer.cc @@ -41,7 +41,9 @@ size_t data_buffer::get_body_endoffset() const { return _body_endoffset; } int data_buffer::set_body_endoffset(size_t endoffset) { if (endoffset > _buffer.size() || endoffset < preamble_size() || endoffset < _body_beginoffset) - { return error_code::invalid_argument; } + { + return error_code::invalid_argument; + } _body_endoffset = endoffset; return error_code::success; diff --git a/rlclientlib/utility/object_pool.h b/rlclientlib/utility/object_pool.h index 9f8a421d3..3e6e548f3 100644 --- a/rlclientlib/utility/object_pool.h +++ b/rlclientlib/utility/object_pool.h @@ -1,58 +1,83 @@ #pragma once #include "data_buffer.h" +#include #include +#include namespace reinforcement_learning { namespace utility { template -class object_pool +class object_pool : public std::enable_shared_from_this> { public: + // Factory function to create the object pool + static std::shared_ptr> create(size_t initial_size = 0) + { + return std::shared_ptr>(new object_pool(initial_size)); + } + + // Get an object from the pool, or allocate a new object if pool is empty + // The shared_ptr will have a custom deleter that returns the object back into pool std::shared_ptr acquire(); - void release(Object*); - ~object_pool(); private: - bool _pool_invalid = false; - std::vector _pool; + // Private constructor because std::enable_shared_from_this requires objects to be inside shared_ptr + // Use the factory function to create object_pool + object_pool(size_t initial_size) : _pool(initial_size) {} + + void return_to_pool(std::unique_ptr); + + std::vector> _pool; std::mutex _mutex; + + struct return_to_pool_deleter + { + return_to_pool_deleter(std::weak_ptr> pool) : _pool(pool) {} + + void operator()(Object* pobject) + { + // wrap in unique_ptr so it's destroyed upon exception + std::unique_ptr obj(pobject); + + // if pool is still valid, return the object to pool + if (auto pool = _pool.lock()) + { + try + { + pool->return_to_pool(std::move(obj)); + } + catch (...) + { + } + } + // else pool has been deleted, obj will be destroyed by unique_ptr + } + + std::weak_ptr> _pool; + }; }; template std::shared_ptr object_pool::acquire() { std::lock_guard lock(_mutex); - Object* ptr; - if (_pool.empty()) { ptr = new Object(); } - else - { - ptr = _pool.back(); - _pool.pop_back(); - ptr->reset(); - } - return std::shared_ptr(ptr, [this](Object* pobject) { this->release(pobject); }); -} + if (_pool.empty()) { return std::shared_ptr(new Object(), return_to_pool_deleter(this->shared_from_this())); } -template -void object_pool::release(Object* pobj) -{ - // In some test scenarios the pool is destroyed - // before the object is released - if (_pool_invalid) return; - - std::lock_guard lock(_mutex); - _pool.emplace_back(pobj); + std::unique_ptr obj = std::move(_pool.back()); + _pool.pop_back(); + obj->reset(); + return std::shared_ptr(obj.release(), return_to_pool_deleter(this->shared_from_this())); } template -object_pool::~object_pool() +void object_pool::return_to_pool(std::unique_ptr obj) { - for (auto ptr : _pool) { delete ptr; } - _pool_invalid = true; + std::lock_guard lock(_mutex); + _pool.push_back(std::move(obj)); } } // namespace utility diff --git a/rlclientlib/utility/periodic_background_proc.h b/rlclientlib/utility/periodic_background_proc.h index f3704187a..64b7d1ebb 100644 --- a/rlclientlib/utility/periodic_background_proc.h +++ b/rlclientlib/utility/periodic_background_proc.h @@ -120,8 +120,7 @@ void periodic_background_proc::time_loop() _watchdog.register_thread( std::this_thread::get_id(), _proc_name, static_cast(_interval_ms * timeout_grace_multiplier_c)); - do - { + do { api_status status; // Check in to the watchdog to report this thread is still alive. diff --git a/rlclientlib/utility/watchdog.cc b/rlclientlib/utility/watchdog.cc index 82a85a443..8dea2a517 100644 --- a/rlclientlib/utility/watchdog.cc +++ b/rlclientlib/utility/watchdog.cc @@ -44,7 +44,9 @@ void watchdog::check_in(std::thread::id const& thread_id) auto const it = _thread_infos.find(thread_id); if (it == _thread_infos.end()) - { throw std::runtime_error(concat("Thread ", thread_id, " must be registered before being used.")); } + { + throw std::runtime_error(concat("Thread ", thread_id, " must be registered before being used.")); + } auto& thread_info = it->second; thread_info.last_check_in_time = clock_t::now(); @@ -101,10 +103,7 @@ void watchdog::loop() if (thread_info.last_verify_time - thread_info.last_check_in_time > thread_info.timeout) { if (_error_callback != nullptr) { failed_thread_names.push_back(thread_info.thread_name); } - else - { - set_unhandled_background_error(true); - } + else { set_unhandled_background_error(true); } } } } diff --git a/rlclientlib/vw_model/safe_vw.cc b/rlclientlib/vw_model/safe_vw.cc index 8645fdbae..1cff6dff7 100644 --- a/rlclientlib/vw_model/safe_vw.cc +++ b/rlclientlib/vw_model/safe_vw.cc @@ -85,7 +85,7 @@ VW::example& safe_vw::get_or_create_example_f(void* vw) { return *(((safe_vw*)vw void safe_vw::parse_context_with_pdf(string_view context, std::vector& actions, std::vector& scores) { - DecisionServiceInteraction interaction; + VW::details::decision_service_interaction interaction; VW::multi_ex examples; examples.push_back(get_or_create_example()); @@ -133,10 +133,7 @@ void safe_vw::rank(string_view context, std::vector& actions, std::vectoraudit_buffer->clear(); VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } - else - { - VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); - } + else { VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } // finalize example VW::setup_examples(*_vw, examples); @@ -177,10 +174,7 @@ void safe_vw::choose_continuous_action(string_view context, float& action, float _vw->audit_buffer->clear(); VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } - else - { - VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); - } + else { VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } // finalize example VW::setup_examples(*_vw, examples); @@ -211,10 +205,7 @@ void safe_vw::rank_decisions(const std::vector& event_ids, string_v _vw->audit_buffer->clear(); VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } - else - { - VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); - } + else { VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } // In order to control the seed for the sampling of each slot the event id + app id is passed in as the seed using the // example tag. @@ -268,10 +259,7 @@ void safe_vw::rank_multi_slot_decisions(const char* event_id, const std::vector< _vw->audit_buffer->clear(); VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } - else - { - VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); - } + else { VW::read_line_json_s(*_vw, examples, &line_vec[0], line_vec.size(), get_or_create_example_f, this); } // In order to control the seed for the sampling of each slot the event id + app id is passed in as the seed using the // example tag. @@ -354,7 +342,9 @@ bool safe_vw::is_compatible(const std::string& args) const // This really is an error but errors cant be reported here... if (local_model_type == mm::model_type_t::UNKNOWN || inbound_model_type == mm::model_type_t::UNKNOWN) - { return false; } + { + return false; + } return local_model_type == inbound_model_type; } @@ -370,10 +360,7 @@ bool safe_vw::is_CB_to_CCB_model_upgrade(const std::string& args) const string_view safe_vw::get_audit_data() const { if (_vw->audit) { return string_view(_vw->audit_buffer->data(), _vw->audit_buffer->size()); } - else - { - return string_view(); - } + else { return string_view(); } } void safe_vw::init() diff --git a/rlclientlib/vw_model/vw_model.cc b/rlclientlib/vw_model/vw_model.cc index d3775b517..53d951a93 100644 --- a/rlclientlib/vw_model/vw_model.cc +++ b/rlclientlib/vw_model/vw_model.cc @@ -36,7 +36,9 @@ int vw_model::update(const model_data& data, bool& model_ready, api_status* stat std::unique_ptr init_vw(new safe_vw(data.data(), data.data_sz(), cmd_line)); if (init_vw->is_CB_to_CCB_model_upgrade(_initial_command_line)) - { cmd_line = add_optional_audit_flag(_upgrade_to_CCB_vw_commandline_options); } + { + cmd_line = add_optional_audit_flag(_upgrade_to_CCB_vw_commandline_options); + } safe_vw_factory factory(data, cmd_line); std::unique_ptr test_vw(factory()); diff --git a/test_tools/example_gen/example_gen.cc b/test_tools/example_gen/example_gen.cc index 97426c930..f2ec1b70e 100644 --- a/test_tools/example_gen/example_gen.cc +++ b/test_tools/example_gen/example_gen.cc @@ -78,11 +78,11 @@ void load_config_from_json(int action, u::configuration& config, bool enable_app if (is_observation) { config.set("observation.file.name", file_name.c_str()); - config.set("interaction.file.name", "/dev/null"); + config.set("interaction.file.name", r::DEV_NULL); } else { - config.set("observation.file.name", "/dev/null"); + config.set("observation.file.name", r::DEV_NULL); config.set("interaction.file.name", file_name.c_str()); } } @@ -186,11 +186,15 @@ void send_ccb_outcome( std::cout << "report outcome: " << reward_0 << " for event: " << event_id << " for slot index: " << 0 << std::endl; if (rl.report_outcome(event_id, 0, reward_0, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } std::cout << "report outcome: " << reward_1 << " for event: " << event_id << " for slot index: " << 1 << std::endl; if (rl.report_outcome(event_id, 1, reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } break; }; @@ -206,11 +210,15 @@ void send_ccb_outcome( std::cout << "report outcome: " << reward_0 << " for event: " << event_id << " for slot string index: slot_0" << std::endl; if (rl.report_outcome(event_id, "slot_0", reward_0, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } std::cout << "report outcome: " << reward_1 << " for event: " << event_id << " for slot string index: slot_1" << std::endl; if (rl.report_outcome(event_id, "slot_1", reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } break; }; @@ -230,11 +238,15 @@ void send_ccb_outcome( std::cout << "report outcome: " << reward_0 << " for event: " << event_id << " for slot index: " << i << std::endl; if (static_cast(rl.report_outcome(event_id, i, reward_0, &status)) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } std::cout << "report outcome: " << reward_1 << " for event: " << event_id << " for slot string index: " << slot_ids[i].c_str() << std::endl; if (rl.report_outcome(event_id, slot_ids[i].c_str(), reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } break; @@ -245,7 +257,9 @@ void send_ccb_outcome( std::cout << "report outcome: " << 1.5 << " for event: " << event_id << " for slot at out of bound index: 1000" << std::endl; if (rl.report_outcome(event_id, 1000, 1.5, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; }; @@ -269,7 +283,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in { // "cb", r::ranking_response response; if (rl.choose_rank(event_id, JSON_CB_CONTEXT, action_flag, response, &status) != 0) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; } case INVALID_CB_ACTION: @@ -277,7 +293,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::ranking_response response; // call choose rank but with slates context if (rl.choose_rank(event_id, JSON_SLATES_CONTEXT, action_flag, response, &status) != 0) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; } case CCB_ACTION: @@ -285,7 +303,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::multi_slot_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END break; }; @@ -295,7 +315,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_WITH_SLOT_ID_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END break; }; @@ -304,7 +326,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::multi_slot_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_SLATES_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END break; }; @@ -313,13 +337,17 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::continuous_action_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_continuous_action(event_id, JSON_CA_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END break; }; case F_REWARD: // "float" if (rl.report_outcome(event_id, reward, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case F_I_REWARD: // "float-int", { @@ -330,15 +358,21 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward_1 = gen_random_reward ? get_random_number(rng, 0) : 1.5f; if (rl.report_outcome(event_id, 0, reward_0, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } if (rl.report_outcome(event_id, 1, reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } break; case F_I_OUT_OF_BOUND_REWARD: if (rl.report_outcome(event_id, 1000, 1.5, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case F_S_REWARD: // "float-string" { @@ -349,9 +383,13 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward_1 = gen_random_reward ? get_random_number(rng, 0) : 1.5f; if (rl.report_outcome(event_id, "slot_0", reward_0, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } if (rl.report_outcome(event_id, "slot_1", reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } break; @@ -368,29 +406,41 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward_1 = gen_random_reward ? get_random_number(rng, 0) : 1.5f; if (static_cast(rl.report_outcome(event_id, i, reward_0, &status)) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } if (rl.report_outcome(event_id, slot_ids[i].c_str(), reward_1, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } } break; case S_REWARD: // "string-reward", if (rl.report_outcome(event_id, "reward-str", &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case S_I_REWARD: // "string-int-reward", if (rl.report_outcome(event_id, 1, "reward-str", &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case S_S_REWARD: // "string-string-reward", if (rl.report_outcome(event_id, "index_id", "reward-str", &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case ACTION_TAKEN: if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } break; case CCB_BASELINE_ACTION: { // "ccb", @@ -399,7 +449,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_CONTEXT, 0, response, baselines.data(), 2, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END break; }; @@ -408,7 +460,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::ranking_response response; std::cout << "choose rank for event: " << event_id << std::endl; if (rl.choose_rank(event_id, JSON_CB_CONTEXT, action_flag, response, &status) != 0) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } auto num_of_rewards = static_cast(get_random_number(rng)); for (size_t i = 0; i < num_of_rewards; i++) @@ -416,7 +470,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward = gen_random_reward ? get_random_number(rng, 0) : 1.5f; std::cout << "report outcome: " << reward << " for event: " << event_id << std::endl; if (rl.report_outcome(event_id, reward, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } if (action_flag == r::action_flags::DEFERRED) @@ -427,7 +483,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in // send activation std::cout << "sending activation for event_id: " << event_id << std::endl; if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } @@ -438,7 +496,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::continuous_action_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_continuous_action(event_id, JSON_CA_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END auto num_of_rewards = static_cast(get_random_number(rng)); for (size_t i = 0; i < num_of_rewards; i++) @@ -446,7 +506,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward = gen_random_reward ? get_random_number(rng, 0) : 1.5f; std::cout << "report outcome: " << reward << " for event: " << event_id << std::endl; if (rl.report_outcome(event_id, reward, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } if (action_flag == r::action_flags::DEFERRED) @@ -457,7 +519,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in // send activation std::cout << "sending activation for event_id: " << event_id << std::endl; if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } @@ -475,7 +539,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::multi_slot_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END } else @@ -484,7 +550,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_WITH_SLOT_ID_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END } @@ -498,7 +566,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in // send activation std::cout << "sending activation for event_id: " << event_id << std::endl; if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } @@ -518,7 +588,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision( event_id, JSON_CCB_CONTEXT, action_flag, response, baselines.data(), 2, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END } else @@ -527,7 +599,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_CCB_WITH_SLOT_ID_CONTEXT, action_flag, response, baselines.data(), 2, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END } @@ -541,7 +615,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in // send activation std::cout << "sending activation for event_id: " << event_id << std::endl; if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } @@ -553,7 +629,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in r::multi_slot_response response; RL_IGNORE_DEPRECATED_USAGE_START if (rl.request_multi_slot_decision(event_id, JSON_SLATES_CONTEXT, action_flag, response, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } RL_IGNORE_DEPRECATED_USAGE_END auto num_of_rewards = static_cast(get_random_number(rng)); @@ -562,7 +640,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in float reward = gen_random_reward ? get_random_number(rng, 0) : 1.5f; std::cout << "report outcome: " << reward << " for event: " << event_id << std::endl; if (rl.report_outcome(event_id, reward, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } if (action_flag == r::action_flags::DEFERRED) @@ -573,7 +653,9 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in // send activation std::cout << "sending activation for event_id: " << event_id << std::endl; if (rl.report_action_taken(event_id, &status) != err::success) - { std::cout << status.get_error_msg() << std::endl; } + { + std::cout << status.get_error_msg() << std::endl; + } } } @@ -621,10 +703,7 @@ int run_config(int action, int count, int initial_seed, bool gen_random_reward, { char event_id[128]; if (initial_seed == -1) { strcpy(event_id, "abcdefghijklm"); } - else - { - sprintf(event_id, "%x", pseudo_random(initial_seed + i * 997739)); - } + else { sprintf(event_id, "%x", pseudo_random(initial_seed + i * 997739)); } auto action_flag = i < deferred_action_count ? r::action_flags::DEFERRED : r::action_flags::DEFAULT; @@ -687,7 +766,9 @@ int main(int argc, char* argv[]) if (vm.count("deferred_action_count") > 0 && !std::any_of(deferrable_interactions.begin(), deferrable_interactions.end(), [action_name](const std::string& evt_type) { return action_name == evt_type; })) - { throw std::runtime_error("'--deferred_action' should be used with interaction event"); } + { + throw std::runtime_error("'--deferred_action' should be used with interaction event"); + } } catch (std::exception& e) { @@ -711,7 +792,9 @@ int main(int argc, char* argv[]) { if (run_config(i, count, seed, gen_random_reward, enable_apprentice_mode, deferred_action_count, config_file, rng, epsilon) != 0) - { return -1; } + { + return -1; + } } return 0; } diff --git a/test_tools/joiner/main.cc b/test_tools/joiner/main.cc index 6d05bc117..2575dfa35 100644 --- a/test_tools/joiner/main.cc +++ b/test_tools/joiner/main.cc @@ -45,16 +45,7 @@ void parse_and_run(int argc, char** argv) store(parse_command_line(argc, argv, desc), vm); if (is_help(vm)) { std::cout << desc << std::endl; } - else if (vm["print"].as()) - { - joiner::convert_to_text({"interaction.fb.data", "observation.fb.data"}); - } - else if (vm["join"].as()) - { - std::cout << "Coming soon..." << std::endl; - } - else - { - std::cout << desc << std::endl; - } + else if (vm["print"].as()) { joiner::convert_to_text({"interaction.fb.data", "observation.fb.data"}); } + else if (vm["join"].as()) { std::cout << "Coming soon..." << std::endl; } + else { std::cout << desc << std::endl; } } \ No newline at end of file diff --git a/test_tools/joiner/text_converter.cc b/test_tools/joiner/text_converter.cc index 08a91297b..3d9fb637d 100644 --- a/test_tools/joiner/text_converter.cc +++ b/test_tools/joiner/text_converter.cc @@ -47,8 +47,7 @@ void convert_to_text(const std::string& file) void convert_to_text(std::istream& in_strm, std::ostream& out_strm) { - do - { + do { if (in_strm.fail() || in_strm.bad()) { std::cerr << "Error in input stream." << std::endl; diff --git a/test_tools/stdin2rllib/main.cc b/test_tools/stdin2rllib/main.cc index defd4e848..fbdf9e467 100644 --- a/test_tools/stdin2rllib/main.cc +++ b/test_tools/stdin2rllib/main.cc @@ -115,18 +115,9 @@ void parse_and_send(std::string& line, r::live_model& rl, r::api_status& status, bool is_ca = obj.HasMember("_label_ca"); if (is_dangling) { parse_and_send_outcome(obj, rl, status, debug); } - else if (is_ca) - { - parse_and_send_ca_event(obj, rl, status, debug); - } - else if (!is_ccb) - { - parse_and_send_cb_event(obj, rl, status, debug); - } - else - { - parse_and_send_ccb_event(obj, rl, status, debug); - } + else if (is_ca) { parse_and_send_ca_event(obj, rl, status, debug); } + else if (!is_ccb) { parse_and_send_cb_event(obj, rl, status, debug); } + else { parse_and_send_ccb_event(obj, rl, status, debug); } } catch (const std::exception& e) { diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index d9915bae1..1375e20eb 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -17,7 +17,6 @@ set(TEST_SOURCES json_serializer_test.cc learning_mode_test.cc live_model_test.cc - local_client_test.cc main.cc mock_http_client.cc mock_util.cc @@ -48,6 +47,17 @@ if (vw_USE_AZURE_FACTORIES) ) endif() +if(RL_BUILD_FEDERATION) + list(APPEND TEST_SOURCES + eud_test.cc + local_client_test.cc + local_loop_controller_test.cc + local_loop_end_to_end.cc + sender_joined_log_provider_test.cc + trainable_model_test.cc + ) +endif() + # If compiling on windows add the stdafx file add_executable(rltest ${TEST_SOURCES}) diff --git a/unit_test/async_batcher_test.cc b/unit_test/async_batcher_test.cc index 45be7fd81..3eb5894cb 100644 --- a/unit_test/async_batcher_test.cc +++ b/unit_test/async_batcher_test.cc @@ -159,7 +159,8 @@ BOOST_AUTO_TEST_CASE(flush_timeout) { auto foo_evt_sp = std::make_shared(foo); - auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*foo_evt_sp); return error_code::success; }; @@ -167,7 +168,8 @@ BOOST_AUTO_TEST_CASE(flush_timeout) } { auto bar_evt_sp = std::make_shared(bar); - auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*bar_evt_sp); return error_code::success; }; @@ -204,7 +206,8 @@ BOOST_AUTO_TEST_CASE(flush_batches) { auto foo_evt_sp = std::make_shared(foo); - auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*foo_evt_sp); return error_code::success; }; @@ -212,7 +215,8 @@ BOOST_AUTO_TEST_CASE(flush_batches) } { auto bar_evt_sp = std::make_shared(bar); - auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*bar_evt_sp); return error_code::success; }; @@ -223,7 +227,8 @@ BOOST_AUTO_TEST_CASE(flush_batches) std::string hello("hello"); { auto hello_evt_sp = std::make_shared(hello); - auto evt_fn = [hello_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [hello_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*hello_evt_sp); return error_code::success; }; @@ -253,7 +258,8 @@ BOOST_AUTO_TEST_CASE(flush_after_deletion) std::string bar("bar"); { auto foo_evt_sp = std::make_shared(foo); - auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*foo_evt_sp); return error_code::success; }; @@ -261,7 +267,8 @@ BOOST_AUTO_TEST_CASE(flush_after_deletion) } { auto bar_evt_sp = std::make_shared(bar); - auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*bar_evt_sp); return error_code::success; }; @@ -298,7 +305,8 @@ BOOST_AUTO_TEST_CASE(queue_overflow_do_not_drop_event) for (int i = 0; i < n; ++i) { auto evt_sp = std::make_shared(std::to_string(i)); - auto evt_fn = [evt_sp](test_droppable_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](test_droppable_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; @@ -343,7 +351,8 @@ BOOST_AUTO_TEST_CASE(queue_config_drop_rate_test) for (int i = 0; i < vs.size(); ++i) { auto evt_sp = std::make_shared(vs[i]); - auto evt_fn = [evt_sp](config_drop_event& out_evt, api_status* status) -> int { + auto evt_fn = [evt_sp](config_drop_event& out_evt, api_status* status) -> int + { out_evt = std::move(*evt_sp); return error_code::success; }; diff --git a/unit_test/common_test_utils.h b/unit_test/common_test_utils.h index fe6c5867e..0bec3a356 100644 --- a/unit_test/common_test_utils.h +++ b/unit_test/common_test_utils.h @@ -1,16 +1,116 @@ +#pragma once + #include +#include "model_mgmt.h" #include "rl_string_view.h" +#include "vw/config/options_cli.h" +#include "vw/core/array_parameters.h" +#include "vw/core/parse_primitives.h" +#include "vw/core/shared_data.h" +#include "vw/core/vw.h" +#include "vw/io/io_adapter.h" +#include #include -bool is_invoked_with(const std::string& arg) +namespace reinforcement_learning +{ +namespace test_utils +{ +inline bool is_invoked_with(const std::string& arg) { for (size_t i = 0; i < boost::unit_test::framework::master_test_suite().argc; i++) { if (reinforcement_learning::string_view(boost::unit_test::framework::master_test_suite().argv[i]).find(arg) != std::string::npos) - { return true; } + { + return true; + } } return false; -} \ No newline at end of file +} + +inline std::unique_ptr create_vw(const std::string& command_line) +{ + auto opts = std::unique_ptr(new VW::config::options_cli(VW::split_command_line(command_line))); + return VW::initialize_experimental(std::move(opts)); +} + +inline std::unique_ptr create_vw( + const std::string& command_line, const model_management::model_data& data) +{ + auto opts = std::unique_ptr(new VW::config::options_cli(VW::split_command_line(command_line))); + auto data_reader = VW::io::create_buffer_view(data.data(), data.data_sz()); + return VW::initialize_experimental(std::move(opts), std::move(data_reader)); +} + +inline std::unique_ptr create_vw(const std::vector& command_line) +{ + auto opts = std::unique_ptr(new VW::config::options_cli(command_line)); + return VW::initialize_experimental(std::move(opts)); +} + +inline std::unique_ptr create_vw( + const std::vector& command_line, const model_management::model_data& data) +{ + auto opts = std::unique_ptr(new VW::config::options_cli(command_line)); + auto data_reader = VW::io::create_buffer_view(data.data(), data.data_sz()); + return VW::initialize_experimental(std::move(opts), std::move(data_reader)); +} + +inline model_management::model_data save_vw(VW::workspace& vw) +{ + io_buf io_buffer; + auto backing_buffer = std::make_shared>(); + io_buffer.add_file(VW::io::create_vector_writer(backing_buffer)); + VW::save_predictor(vw, io_buffer); + + model_management::model_data data; + auto* data_buffer = data.alloc(backing_buffer->size()); + std::memcpy(data_buffer, backing_buffer->data(), backing_buffer->size()); + + return data; +} + +inline void compare_vw(const VW::workspace& vw1, const VW::workspace& vw2) +{ + const auto* sd1 = vw1.sd; + const auto* sd2 = vw2.sd; + BOOST_CHECK_EQUAL(sd1->weighted_labeled_examples, sd2->weighted_labeled_examples); + BOOST_CHECK_EQUAL(sd1->weighted_labels, sd2->weighted_labels); + BOOST_CHECK_EQUAL(sd1->sum_loss, sd2->sum_loss); + BOOST_CHECK_EQUAL(sd1->total_features, sd2->total_features); + + // These will not necessarily be equal because in trainable_vw_model + // the binary parser will create and destroy a new example even when parsing is unsuccessful + // BOOST_CHECK_EQUAL(sd1->example_number, sd2->example_number); + // BOOST_CHECK_EQUAL(sd1->weighted_unlabeled_examples, sd2->weighted_unlabeled_examples); + + const auto& weights1 = vw1.weights; + const auto& weights2 = vw2.weights; + BOOST_CHECK_EQUAL(weights1.sparse, weights2.sparse); + + const float tolerance = 0.00001; + if (weights1.sparse) + { + auto& sw1 = weights1.sparse_weights; + auto& sw2 = weights2.sparse_weights; + for (auto it1 = sw1.cbegin(), it2 = sw2.cbegin(); it1 != sw1.cend() && it2 != sw2.cend(); ++it1, ++it2) + { + BOOST_CHECK_CLOSE(*it1, *it2, tolerance); + } + } + else + { + auto& dw1 = weights1.dense_weights; + auto& dw2 = weights2.dense_weights; + for (auto it1 = dw1.cbegin(), it2 = dw2.cbegin(); it1 != dw1.cend() && it2 != dw2.cend(); ++it1, ++it2) + { + BOOST_CHECK_CLOSE(*it1, *it2, tolerance); + } + } +} +} // namespace test_utils + +} // namespace reinforcement_learning diff --git a/unit_test/data.h b/unit_test/data.h index c160d97a4..cb537d1d1 100644 --- a/unit_test/data.h +++ b/unit_test/data.h @@ -1,3 +1,4 @@ +#pragma once unsigned char regression_data_1_model[] = {0x06, 0x00, 0x00, 0x00, 0x38, 0x2e, 0x35, 0x2e, 0x30, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/unit_test/eud_test.cc b/unit_test/eud_test.cc new file mode 100644 index 000000000..a70b81238 --- /dev/null +++ b/unit_test/eud_test.cc @@ -0,0 +1,34 @@ +#include +#define BOOST_TEST_DYN_LINK +#ifdef STAND_ALONE +# define BOOST_TEST_MODULE Main +#endif + +#include + +#include "federation/eud_utils.h" + +#include +#include + +using namespace reinforcement_learning; + +BOOST_AUTO_TEST_CASE(parse_eud_tests) +{ + std::chrono::seconds duration; + BOOST_CHECK_EQUAL(parse_eud("", duration, nullptr), error_code::invalid_argument); + BOOST_CHECK_EQUAL(parse_eud("::", duration, nullptr), error_code::invalid_argument); + BOOST_CHECK_EQUAL(parse_eud("a:b:cc", duration, nullptr), error_code::invalid_argument); + BOOST_CHECK_EQUAL(parse_eud("1a:1:11", duration, nullptr), error_code::invalid_argument); + BOOST_CHECK_EQUAL(parse_eud("a1:1:11", duration, nullptr), error_code::invalid_argument); + BOOST_CHECK_EQUAL(parse_eud("-1:1:11", duration, nullptr), error_code::invalid_argument); + + BOOST_CHECK_EQUAL(parse_eud("1:0:0", duration, nullptr), error_code::success); + BOOST_CHECK(duration == std::chrono::hours(1)); + BOOST_CHECK_EQUAL(parse_eud("1:25:0", duration, nullptr), error_code::success); + BOOST_CHECK(duration == std::chrono::hours(1) + std::chrono::minutes(25)); + BOOST_CHECK_EQUAL(parse_eud("1:25:1", duration, nullptr), error_code::success); + BOOST_CHECK(duration == std::chrono::hours(1) + std::chrono::minutes(25) + std::chrono::seconds(1)); + BOOST_CHECK_EQUAL(parse_eud("83:25:1", duration, nullptr), error_code::success); + BOOST_CHECK(duration == std::chrono::hours(83) + std::chrono::minutes(25) + std::chrono::seconds(1)); +} diff --git a/unit_test/event_queue_test.cc b/unit_test/event_queue_test.cc index 70fcbd05e..180c2f7cc 100644 --- a/unit_test/event_queue_test.cc +++ b/unit_test/event_queue_test.cc @@ -173,10 +173,7 @@ BOOST_AUTO_TEST_CASE(queue_push_pop_subsample) { std::string id; if (i % 2 == 0) { id = "drop_" + std::to_string(i + 1); } - else - { - id = "no_drop_" + std::to_string(i + 1); - } + else { id = "no_drop_" + std::to_string(i + 1); } auto evt_sp = std::make_shared(id); queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); @@ -208,10 +205,12 @@ BOOST_AUTO_TEST_CASE(queue_push_pop_threads) int n = 10; for (int i = 0; i < n; ++i) { - _threads.push_back(thread([&queue, i] { - auto evt_sp = std::make_shared(std::to_string(i + 1)); - queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); - })); + _threads.push_back(thread( + [&queue, i] + { + auto evt_sp = std::make_shared(std::to_string(i + 1)); + queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); + })); std::this_thread::sleep_for(std::chrono::milliseconds(5)); // safe timeout } @@ -240,17 +239,21 @@ BOOST_AUTO_TEST_CASE(queue_push_pop_threads_subsampling) { if (i % 2 == 0) { - _threads.push_back(thread([&queue, i] { - auto evt_sp = std::make_shared("drop_" + std::to_string(i + 1)); - queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); - })); + _threads.push_back(thread( + [&queue, i] + { + auto evt_sp = std::make_shared("drop_" + std::to_string(i + 1)); + queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); + })); } else { - _threads.push_back(thread([&queue, i] { - auto evt_sp = std::make_shared("no_drop_" + std::to_string(i + 1)); - queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); - })); + _threads.push_back(thread( + [&queue, i] + { + auto evt_sp = std::make_shared("no_drop_" + std::to_string(i + 1)); + queue.push(std::bind(passthru, _1, _2, evt_sp), 10, evt_sp.get()); + })); } std::this_thread::sleep_for(std::chrono::milliseconds(5)); // safe timeout } diff --git a/unit_test/extensions/onnx/CMakeLists.txt b/unit_test/extensions/onnx/CMakeLists.txt index 08bdd97f3..d45b50d6d 100644 --- a/unit_test/extensions/onnx/CMakeLists.txt +++ b/unit_test/extensions/onnx/CMakeLists.txt @@ -46,6 +46,31 @@ add_custom_command(TARGET rltest-onnx POST_BUILD $ ) +# Automatically set BOOST_TEST_DYN_LINK if the dependency is shared. +get_target_property(boost_test_target_type Boost::unit_test_framework TYPE) +if (boost_test_target_type STREQUAL SHARED_LIBRARY) + message(STATUS "Boost::unit_test_framework looks to be a shared library. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(rltest-onnx PRIVATE BOOST_TEST_DYN_LINK) +elseif(boost_test_target_type STREQUAL UNKNOWN_LIBRARY) + # Try inferring type if vcpkg is used + if (DEFINED VCPKG_TARGET_TRIPLET) + if (VCPKG_TARGET_TRIPLET EQUAL "x64-windows" OR VCPKG_TARGET_TRIPLET EQUAL "x86-windows" OR VCPKG_TARGET_TRIPLET EQUAL "arm64-osx-dynamic" OR VCPKG_TARGET_TRIPLET EQUAL "x64-osx-dynamic") + message(STATUS "Boost::unit_test_framework looks to be a shared library based on vcpkg triplet ${VCPKG_TARGET_TRIPLET}. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(rltest-onnx PRIVATE BOOST_TEST_DYN_LINK) + endif() + # If find_package is used then by default we're looking at a shared dependency unless Boost_USE_STATIC_LIBS was set. + elseif(NOT Boost_USE_STATIC_LIBS) + message(STATUS "Boost::unit_test_framework looks to be a shared library. Adding BOOST_TEST_DYN_LINK") + target_compile_definitions(rltest-onnx PRIVATE BOOST_TEST_DYN_LINK) + endif() +endif() + +if(RL_USE_UBSAN) + # UBSan detects false positives due to test mocking + target_compile_options(rltest-onnx PRIVATE -fno-sanitize=vptr) + target_link_options(rltest-onnx PRIVATE -fno-sanitize=vptr) +endif() + add_test(NAME rltest-onnx COMMAND $ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/unit_test/extensions/onnx/main.cc b/unit_test/extensions/onnx/main.cc index 1add8c7bc..dc66b0719 100644 --- a/unit_test/extensions/onnx/main.cc +++ b/unit_test/extensions/onnx/main.cc @@ -1,11 +1,10 @@ -#define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE Main #include #include "global_fixture.h" #include "onnx_extension.h" -#include +#include #include diff --git a/unit_test/extensions/onnx/mnist_inference_test.cc b/unit_test/extensions/onnx/mnist_inference_test.cc index e393a103b..0540b0274 100644 --- a/unit_test/extensions/onnx/mnist_inference_test.cc +++ b/unit_test/extensions/onnx/mnist_inference_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/extensions/onnx/mock_helpers.cc b/unit_test/extensions/onnx/mock_helpers.cc index 9cc0ed8f4..e5aabc2a7 100644 --- a/unit_test/extensions/onnx/mock_helpers.cc +++ b/unit_test/extensions/onnx/mock_helpers.cc @@ -22,13 +22,15 @@ std::unique_ptr get_mock_sender_factory( auto factory = std::unique_ptr(new r::sender_factory_t()); factory->register_type(r::value::OBSERVATION_EH_SENDER, [mock_observation_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) { + r::i_trace*, r::api_status*) + { *retval = &mock_observation_sender->get(); return r::error_code::success; }); factory->register_type(r::value::INTERACTION_EH_SENDER, [mock_interaction_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) { + r::i_trace*, r::api_status*) + { *retval = &mock_interaction_sender->get(); return r::error_code::success; }); diff --git a/unit_test/extensions/onnx/tensor_notation_test.cc b/unit_test/extensions/onnx/tensor_notation_test.cc index e58dbe12f..73880cf4d 100644 --- a/unit_test/extensions/onnx/tensor_notation_test.cc +++ b/unit_test/extensions/onnx/tensor_notation_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/extensions/onnx/test_helpers.h b/unit_test/extensions/onnx/test_helpers.h index 529187c73..cab47a636 100644 --- a/unit_test/extensions/onnx/test_helpers.h +++ b/unit_test/extensions/onnx/test_helpers.h @@ -7,8 +7,8 @@ #include "onnx_extension.h" #include "onnx_input.h" -#include #include +#include #include #include @@ -99,7 +99,9 @@ inline void validate_tensors(o::onnx_input_builder& input_context, expectations< std::vector input_names = input_context.input_names(); for (size_t i = 0; i < expected_input_count; i++) - { input_name_map.insert(std::make_pair(string_t{input_names[i]}, i)); } + { + input_name_map.insert(std::make_pair(string_t{input_names[i]}, i)); + } std::vector inputs; diff --git a/unit_test/factory_test.cc b/unit_test/factory_test.cc index ab4ba833a..d78c35f00 100644 --- a/unit_test/factory_test.cc +++ b/unit_test/factory_test.cc @@ -32,12 +32,14 @@ BOOST_AUTO_TEST_CASE(factory_tempate_usage) auto b = 5; // arbitrary variable to illustrate a point u::object_factory factory; - auto create_A_fn = [](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int { + auto create_A_fn = [](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int + { *pret = new impl_A(); return r::error_code::success; }; - auto create_B_fn = [b](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int { + auto create_B_fn = [b](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int + { *pret = new impl_B(b); return r::error_code::success; }; diff --git a/unit_test/fb_serializer_test.cc b/unit_test/fb_serializer_test.cc index 3f6ee8a2c..3eb7a9439 100644 --- a/unit_test/fb_serializer_test.cc +++ b/unit_test/fb_serializer_test.cc @@ -109,14 +109,8 @@ BOOST_AUTO_TEST_CASE(fb_serializer_ranking_event) BOOST_CHECK_EQUAL(event.deferred_action(), false); BOOST_CHECK_EQUAL(event.pass_probability(), 0.33f); if (i % learning_mode_count == 0) { BOOST_CHECK_EQUAL(event.learning_mode(), LearningModeType_Online); } - else if (i % learning_mode_count == 1) - { - BOOST_CHECK_EQUAL(event.learning_mode(), LearningModeType_Apprentice); - } - else - { - BOOST_CHECK_EQUAL(event.learning_mode(), LearningModeType_LoggingOnly); - } + else if (i % learning_mode_count == 1) { BOOST_CHECK_EQUAL(event.learning_mode(), LearningModeType_Apprentice); } + else { BOOST_CHECK_EQUAL(event.learning_mode(), LearningModeType_LoggingOnly); } } } diff --git a/unit_test/http_transport_client_test.cc b/unit_test/http_transport_client_test.cc index 2cde79791..43dfa3236 100644 --- a/unit_test/http_transport_client_test.cc +++ b/unit_test/http_transport_client_test.cc @@ -14,6 +14,8 @@ #include "utility/eventhub_http_authorization.h" #include "utility/header_authorization.h" +#include + namespace reinforcement_learning { namespace utility @@ -35,6 +37,7 @@ class error_counter void error_counter_func(const r::api_status&, void* counter) { static_cast(counter)->_error_handler(); } +const std::chrono::milliseconds UNLIMITED_RETRY_TIME(9999999); BOOST_AUTO_TEST_CASE(send_something_apim_authorization) { mock_http_client* http_client = new mock_http_client("localhost:8080"); @@ -45,7 +48,7 @@ BOOST_AUTO_TEST_CASE(send_something_apim_authorization) u::configuration config; BOOST_CHECK_EQUAL(u::config::create_from_json(config_json, config), r::error_code::success); // create a client - r::http_transport_client eh(http_client, 1, 1, nullptr, nullptr); + r::http_transport_client eh(http_client, 1, 1, UNLIMITED_RETRY_TIME, nullptr, nullptr); r::api_status ret; eh.init(config, &ret); std::shared_ptr db1(new u::data_buffer()); @@ -72,7 +75,8 @@ BOOST_AUTO_TEST_CASE(send_something_eventhub_authorization) mock_http_client* http_client = new mock_http_client("localhost:8080"); // create a client - r::http_transport_client eh(http_client, 1, 1, nullptr, nullptr); + r::http_transport_client eh( + http_client, 1, 1, UNLIMITED_RETRY_TIME, nullptr, nullptr); r::api_status ret; std::shared_ptr db1(new u::data_buffer()); @@ -100,15 +104,13 @@ BOOST_AUTO_TEST_CASE(retry_http_send_success) int tries = 0; int succeed_after_n_tries = 3; - http_client->set_responder( - methods::POST, [&tries, succeed_after_n_tries](const http_request& message, http_response& resp) { + http_client->set_responder(methods::POST, + [&tries, succeed_after_n_tries](const http_request& message, http_response& resp) + { tries++; if (tries > succeed_after_n_tries) { resp.set_status_code(status_codes::Created); } - else - { - resp.set_status_code(status_codes::InternalError); - } + else { resp.set_status_code(status_codes::InternalError); } }); error_counter counter; @@ -118,7 +120,7 @@ BOOST_AUTO_TEST_CASE(retry_http_send_success) { // create a client r::http_transport_client eh( - http_client, 1, 8 /* retries */, nullptr, &error_callback); + http_client, 1, 8 /* retries */, UNLIMITED_RETRY_TIME, nullptr, &error_callback); reinforcement_learning::api_status ret; std::shared_ptr db1(new u::data_buffer()); @@ -134,17 +136,19 @@ BOOST_AUTO_TEST_CASE(retry_http_send_success) BOOST_CHECK_EQUAL(counter._err_count, 0); } -BOOST_AUTO_TEST_CASE(retry_http_send_fail) +BOOST_AUTO_TEST_CASE(retry_http_send_fail_retries_exhausted) { mock_http_client* http_client = new mock_http_client("localhost:8080"); const int MAX_RETRIES = 10; int tries = 0; - http_client->set_responder(methods::POST, [&tries](const http_request& message, http_response& resp) { - tries++; - resp.set_status_code(status_codes::InternalError); - }); + http_client->set_responder(methods::POST, + [&tries](const http_request& message, http_response& resp) + { + tries++; + resp.set_status_code(status_codes::InternalError); + }); error_counter counter; r::error_callback_fn error_callback(&error_counter_func, &counter); @@ -152,7 +156,8 @@ BOOST_AUTO_TEST_CASE(retry_http_send_fail) // Use scope to force destructor and therefore flushing of buffers. { // create a client - r::http_transport_client eh(http_client, 1, MAX_RETRIES, nullptr, &error_callback); + r::http_transport_client eh( + http_client, 1, MAX_RETRIES, UNLIMITED_RETRY_TIME, nullptr, &error_callback); r::api_status ret; std::shared_ptr db1(new u::data_buffer()); @@ -167,6 +172,53 @@ BOOST_AUTO_TEST_CASE(retry_http_send_fail) BOOST_CHECK_EQUAL(counter._err_count, 1); } +BOOST_AUTO_TEST_CASE(retry_http_send_fail_retry_time_exhausted) +{ + mock_http_client* http_client = new mock_http_client("localhost:8080"); + + const int UNLIMITED_RETRIES = 99999999; + const std::chrono::milliseconds RETRY_TIME_MS(5555); + + int tries = 0; + http_client->set_responder(methods::POST, + [&tries](const http_request& message, http_response& resp) + { + tries++; + resp.set_status_code(status_codes::InternalError); + }); + + error_counter counter; + r::error_callback_fn error_callback(&error_counter_func, &counter); + + auto start_time = std::chrono::system_clock::now(); + + // Use scope to force destructor and therefore flushing of buffers. + { + // create a client + r::http_transport_client eh( + http_client, 1, UNLIMITED_RETRIES, RETRY_TIME_MS, nullptr, &error_callback); + + r::api_status ret; + std::shared_ptr db1(new u::data_buffer()); + u::data_buffer_streambuf sbuff1(db1.get()); + std::ostream message1(&sbuff1); + + message1 << "message 1"; + BOOST_CHECK_EQUAL(eh.send(db1, &ret), r::error_code::success); + } + + auto actual_retry_time_ms = + std::chrono::duration_cast(std::chrono::system_clock::now() - start_time); + + // 1 second between retries, 5.5 second retry budget, should allow at least three attempts (initial + two retries) + BOOST_CHECK_GE(tries, 3); + + // Should have taken at least RETRY_TIME_MS to give up attempting retries + BOOST_CHECK_GT(actual_retry_time_ms.count(), RETRY_TIME_MS.count()); + + BOOST_CHECK_EQUAL(counter._err_count, 1); +} + BOOST_AUTO_TEST_CASE(http_in_order_after_retry) { mock_http_client* http_client = new mock_http_client("localhost:8080"); @@ -174,8 +226,9 @@ BOOST_AUTO_TEST_CASE(http_in_order_after_retry) const int MAX_RETRIES = 10; int tries = 0; std::vector received_messages; - http_client->set_responder( - methods::POST, [&tries, &received_messages](const http_request& message, http_response& resp) { + http_client->set_responder(methods::POST, + [&tries, &received_messages](const http_request& message, http_response& resp) + { tries++; // Succeed every 4th attempt. @@ -189,10 +242,7 @@ BOOST_AUTO_TEST_CASE(http_in_order_after_retry) resp.set_status_code(status_codes::Created); tries = 0; } - else - { - resp.set_status_code(status_codes::InternalError); - } + else { resp.set_status_code(status_codes::InternalError); } }); error_counter counter; @@ -201,7 +251,8 @@ BOOST_AUTO_TEST_CASE(http_in_order_after_retry) // Use scope to force destructor and therefore flushing of buffers. { // create a client - r::http_transport_client eh(http_client, 1, MAX_RETRIES, nullptr, &error_callback); + r::http_transport_client eh( + http_client, 1, MAX_RETRIES, UNLIMITED_RETRY_TIME, nullptr, &error_callback); r::api_status ret; std::shared_ptr db1(new u::data_buffer()); @@ -244,6 +295,7 @@ BOOST_AUTO_TEST_CASE(http_in_order_after_retry) BOOST_CHECK_EQUAL(eh.send(db5, &ret), r::error_code::success); } + BOOST_CHECK_EQUAL(received_messages.size(), 5); BOOST_CHECK_EQUAL(received_messages[0], "message 1"); BOOST_CHECK_EQUAL(received_messages[1], "message 2"); BOOST_CHECK_EQUAL(received_messages[2], "message 3"); diff --git a/unit_test/local_client_test.cc b/unit_test/local_client_test.cc index 4ba05bf3d..1ee75a287 100644 --- a/unit_test/local_client_test.cc +++ b/unit_test/local_client_test.cc @@ -5,25 +5,20 @@ #include +#include "common_test_utils.h" #include "err_constants.h" #include "factory_resolver.h" #include "federation/federated_client.h" #include "federation/local_client.h" -#include "vw/config/options.h" -#include "vw/config/options_cli.h" -#include "vw/core/io_buf.h" #include "vw/core/merge.h" #include "vw/core/parse_example.h" #include "vw/core/parse_example_json.h" #include "vw/core/vw.h" -#include "vw/io/io_adapter.h" #include #include -namespace rl = reinforcement_learning; -namespace rerr = reinforcement_learning::error_code; -namespace rutil = reinforcement_learning::utility; +using namespace reinforcement_learning; VW::multi_ex parse_json(VW::workspace& all, const std::string& line) { @@ -37,32 +32,29 @@ VW::multi_ex parse_json(VW::workspace& all, const std::string& line) BOOST_AUTO_TEST_CASE(get_model_twice_fails) { - rutil::configuration config; - std::unique_ptr client; - BOOST_CHECK_EQUAL(rl::create_local_client(config, client, nullptr, nullptr), rerr::success); - rl::model_management::model_data data; + utility::configuration config; + std::unique_ptr client; + BOOST_CHECK_EQUAL(local_client::create(client, config, nullptr, nullptr), error_code::success); + model_management::model_data data; bool model_received = false; - BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), error_code::success); BOOST_CHECK(data.data_sz() > 0); BOOST_CHECK_EQUAL(model_received, true); - BOOST_CHECK_NE(client->try_get_model("test_app_id", data, model_received), rerr::success); + BOOST_CHECK_NE(client->try_get_model("test_app_id", data, model_received), error_code::success); } BOOST_AUTO_TEST_CASE(send_delta_update) { - rutil::configuration config; - std::unique_ptr client; - BOOST_CHECK_EQUAL(rl::create_local_client(config, client, nullptr, nullptr), rerr::success); + utility::configuration config; + std::unique_ptr client; + BOOST_CHECK_EQUAL(local_client::create(client, config, nullptr, nullptr), error_code::success); BOOST_CHECK_NE(client.get(), nullptr); - rl::model_management::model_data data; + model_management::model_data data; bool model_received = false; - BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); - auto opts = std::unique_ptr(new VW::config::options_cli(std::vector{})); - auto original_workspace = - VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); - opts = std::unique_ptr(new VW::config::options_cli(std::vector{})); - auto workspace = - VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), error_code::success); + + auto original_workspace = test_utils::create_vw("", data); + auto workspace = test_utils::create_vw("", data); std::string json_text = R"( { "s_": "1", @@ -100,11 +92,11 @@ BOOST_AUTO_TEST_CASE(send_delta_update) delta.serialize(*writer); BOOST_CHECK_EQUAL( client->report_result(reinterpret_cast(backing_buffer->data()), backing_buffer->size()), - rerr::success); + error_code::success); BOOST_CHECK_NE( client->report_result(reinterpret_cast(backing_buffer->data()), backing_buffer->size()), - rerr::success); + error_code::success); model_received = false; - BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), rerr::success); + BOOST_CHECK_EQUAL(client->try_get_model("test_app_id", data, model_received), error_code::success); } diff --git a/unit_test/local_loop_controller_test.cc b/unit_test/local_loop_controller_test.cc new file mode 100644 index 000000000..47d62792b --- /dev/null +++ b/unit_test/local_loop_controller_test.cc @@ -0,0 +1,207 @@ +#include + +#include "common_test_utils.h" +#include "configuration.h" +#include "constants.h" +#include "err_constants.h" +#include "federation/event_sink.h" +#include "federation/federated_client.h" +#include "federation/local_client.h" +#include "federation/local_loop_controller.h" +#include "federation/sender_joined_log_provider.h" +#include "vw/core/shared_data.h" +#include "vw/core/vw.h" + +using namespace reinforcement_learning; + +namespace +{ +// Wrapper around local_loop_controller to allow us to access member variables +class test_local_loop_controller : public local_loop_controller +{ +public: + test_local_loop_controller(std::string app_id, std::unique_ptr&& federated_client, + std::unique_ptr&& trainable_model, std::shared_ptr&& joiner, + std::shared_ptr&& event_sink) + : local_loop_controller(std::move(app_id), std::move(federated_client), std::move(trainable_model), + std::move(joiner), std::move(event_sink)) + { + } + + virtual ~test_local_loop_controller() = default; + + i_federated_client* get_client() { return _federated_client.get(); } + trainable_vw_model* get_model() { return _trainable_model.get(); } + i_joined_log_provider* get_joiner() { return _joiner.get(); } + i_event_sink* get_event_sink() { return _event_sink.get(); } +}; + +// Mock version that simply stores and reads data +class mock_federated_client : public i_federated_client +{ +public: + virtual int try_get_model(const std::string& app_id, model_management::model_data& data, bool& model_received, + api_status* status = nullptr) override + { + if (_need_to_report_result) return -1; + if (_has_data) + { + data = std::move(_data); + _has_data = false; + model_received = true; + _need_to_report_result = true; + } + else + model_received = false; + return error_code::success; + } + + virtual int report_result(const uint8_t* payload, size_t size, api_status* status = nullptr) override + { + if (!_need_to_report_result) return -1; + _result.clear(); + _result.reserve(size); + _result.insert(_result.begin(), payload, payload + size); + _has_result = true; + _need_to_report_result = false; + return error_code::success; + } + + mock_federated_client() = default; + virtual ~mock_federated_client() = default; + + void load_model_data(model_management::model_data data) + { + _data = std::move(data); + _has_data = true; + } + + std::vector get_result() + { + if (_has_result) + { + _has_result = false; + return std::move(_result); + } + return std::vector(); + } + + model_management::model_data _data; + bool _has_data = false; + std::vector _result; + bool _has_result = false; + bool _need_to_report_result = false; +}; + +// Mock version that simply stores and reads data +class mock_event_sink : public i_event_sink +{ +public: + using buffer = std::shared_ptr; + + virtual int receive_events(const buffer& data, api_status* status = nullptr) override + { + _data = data; + return error_code::success; + } + + buffer get_latest_event() { return _data; } + + virtual ~mock_event_sink() = default; + + buffer _data; +}; + +utility::configuration get_test_config() +{ + utility::configuration config; + config.set(name::MODEL_VW_INITIAL_COMMAND_LINE, "--quiet --preserve_performance_counters"); + config.set(name::PROTOCOL_VERSION, "2"); + config.set(name::JOINER_EUD_DURATION, "0:0:0"); + return config; +} + +std::unique_ptr create_test_local_loop_controller(utility::configuration config) +{ + std::unique_ptr trainable_model; + std::unique_ptr sender_joiner; + BOOST_CHECK_EQUAL(trainable_vw_model::create(trainable_model, config), error_code::success); + BOOST_CHECK_EQUAL(sender_joined_log_provider::create(sender_joiner, config), error_code::success); + + std::shared_ptr joiner = std::move(sender_joiner); + std::shared_ptr event_sink(new mock_event_sink()); + std::unique_ptr federated_client(new mock_federated_client()); + + return std::unique_ptr(new test_local_loop_controller("test_app_id", + std::move(federated_client), std::move(trainable_model), std::move(joiner), std::move(event_sink))); +} +} // namespace + +BOOST_AUTO_TEST_CASE(sender_factory_test) +{ + // create the local_loop_controller + auto config = get_test_config(); + auto test_llc = create_test_local_loop_controller(config); + + // create a sender and send some data + std::unique_ptr sender = test_llc->get_local_sender(); + const char* test_data = "Testing the sender..."; + auto test_data_len = std::char_traits::length(test_data); + + std::shared_ptr buffer_in = VW::make_unique(test_data_len); + std::memcpy(buffer_in->raw_begin(), test_data, test_data_len); + sender->send(buffer_in); + + // get the data out of event sink + auto event_sink_out = dynamic_cast(test_llc.get())->get_event_sink(); + BOOST_CHECK_NE(event_sink_out, nullptr); + auto buffer_out = dynamic_cast(event_sink_out)->get_latest_event(); + BOOST_CHECK_NE(buffer_out, nullptr); + BOOST_CHECK_EQUAL(std::memcmp(buffer_out->raw_begin(), test_data, test_data_len), 0); +} + +BOOST_AUTO_TEST_CASE(update_get_model_data) +{ + // create the local_loop_controller + auto config = get_test_config(); + auto llc = create_test_local_loop_controller(config); + auto test_llc = dynamic_cast(llc.get()); + BOOST_CHECK_NE(test_llc, nullptr); + auto mock_client = dynamic_cast(test_llc->get_client()); + BOOST_CHECK_NE(mock_client, nullptr); + + // create a model and train on an example + const std::string command_line = config.get(name::MODEL_VW_INITIAL_COMMAND_LINE, ""); + auto vw = test_utils::create_vw(command_line); + auto ex = VW::read_example(*vw, "1 | a"); + vw->learn(*ex); + vw->finish_example(*ex); + + // this should update the internal model + // check that data retrieved is the same as data provided + model_management::model_data serialized_vw = test_utils::save_vw(*vw); + mock_client->load_model_data(serialized_vw); + model_management::model_data data_out; + BOOST_CHECK_EQUAL(llc->get_data(data_out), error_code::success); + test_utils::compare_vw(*vw, *test_utils::create_vw(command_line, data_out)); + + // this should generate a model delta + // check that model delta has nonzero size + // check that model data has not changed + BOOST_CHECK_EQUAL(llc->get_data(data_out), error_code::success); + auto serialized_delta = mock_client->get_result(); + BOOST_CHECK_NE(serialized_delta.size(), 0); + test_utils::compare_vw(*vw, *test_utils::create_vw(command_line, data_out)); + + // this should do nothing since mock_client has no new data + // check that model data has not changed + BOOST_CHECK_EQUAL(llc->get_data(data_out), error_code::success); + test_utils::compare_vw(*vw, *test_utils::create_vw(command_line, data_out)); + + // delta should do nothing since we didn't train on any new examples + auto delta_reader = + VW::io::create_buffer_view(reinterpret_cast(serialized_delta.data()), serialized_delta.size()); + auto delta = VW::model_delta::deserialize(*delta_reader); + auto vw_new = *vw + *delta; + test_utils::compare_vw(*vw, *vw_new); +} diff --git a/unit_test/local_loop_end_to_end.cc b/unit_test/local_loop_end_to_end.cc new file mode 100644 index 000000000..74a1c4018 --- /dev/null +++ b/unit_test/local_loop_end_to_end.cc @@ -0,0 +1,142 @@ +#include + +#include "common_test_utils.h" +#include "constants.h" +#include "federation/local_loop_controller.h" +#include "live_model.h" +#include "ranking_response.h" + +#include +#include + +using namespace reinforcement_learning; + +namespace +{ +void pick_context_and_desired_action(std::string& context, size_t& action) +{ + int random = std::rand() % 4; + switch (random) + { + case 0: + context = + R"({ "shared":{ "name":"Anna", "time":"Morning" }, "_multi":[{ "TAction":{"article":"Sports"} }, { "TAction":{"article":"Politics"} }, { "TAction":{"article":"Food"} }] })"; + action = 0; + break; + + case 1: + context = + R"({ "shared":{ "name":"Tom", "time":"Morning" }, "_multi":[{ "TAction":{"article":"Sports"} }, { "TAction":{"article":"Politics"} }, { "TAction":{"article":"Food"} }] })"; + action = 1; + break; + + case 2: + context = + R"({ "shared":{ "name":"Anna", "time":"Afternoon" }, "_multi":[{ "TAction":{"article":"Sports"} }, { "TAction":{"article":"Politics"} }, { "TAction":{"article":"Food"} }] })"; + action = 2; + break; + + case 3: + context = + R"({ "shared":{ "name":"Tom", "time":"Afternoon" }, "_multi":[{ "TAction":{"article":"Sports"} }, { "TAction":{"article":"Politics"} }, { "TAction":{"article":"Food"} }] })"; + action = 2; + break; + } +} + +float run_simulation(live_model& model, api_status& status, int iterations) +{ + float reward = 0.f; + for (int i = 0; i < iterations; i++) + { + std::string context; + size_t desired_action; + pick_context_and_desired_action(context, desired_action); + + ranking_response response; + model.choose_rank(context, response, &status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + + size_t chosen_action; + response.get_chosen_action_id(chosen_action, &status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + + float outcome = chosen_action == desired_action ? 1.f : 0.f; + reward += outcome; + model.report_outcome(response.get_event_id(), outcome, &status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + } + return reward; +} + +utility::configuration get_test_config() +{ + utility::configuration config; + + // -q :: is necessary here + config.set(name::MODEL_VW_INITIAL_COMMAND_LINE, + "--cb_explore_adf --json --quiet --epsilon 0.0 --preserve_performance_counters -q ::"); + config.set(name::PROTOCOL_VERSION, "2"); + config.set(name::MODEL_SRC, value::LOCAL_LOOP_MODEL_DATA); + config.set(name::INTERACTION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + config.set(name::OBSERVATION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); + config.set(name::JOINER_EUD_DURATION, "0:0:1"); + config.set(name::JOINER_PROBLEM_TYPE, value::PROBLEM_TYPE_CB); + config.set(name::JOINER_REWARD_FUNCTION, value::REWARD_FUNCTION_EARLIEST); + config.set(name::JOINER_LEARNING_MODE, value::LEARNING_MODE_ONLINE); + config.set(name::MODEL_BACKGROUND_REFRESH, "false"); + config.set(name::TIME_PROVIDER_IMPLEMENTATION, value::CLOCK_TIME_PROVIDER); + + return config; +} +} // namespace + +BOOST_AUTO_TEST_CASE(local_loop_end_to_end_test) +{ + auto config = get_test_config(); + + // create a custom data_transport_factory_t that saves a pointer + // to the local_loop_controller that was created + local_loop_controller* test_local_loop_controller = nullptr; + data_transport_factory_t test_data_transport_factory; + test_data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, + [&](model_management::i_data_transport** retval, const utility::configuration& cfg, i_trace* trace_logger, + api_status* status) + { + std::unique_ptr output; + RETURN_IF_FAIL(local_loop_controller::create(output, cfg, trace_logger, status)); + test_local_loop_controller = output.release(); + *retval = static_cast(test_local_loop_controller); + return error_code::success; + }); + + // initialize live_model with the custom data transport factory + api_status status; + live_model model( + config, nullptr, nullptr, &reinforcement_learning::trace_logger_factory, &test_data_transport_factory); + model.init(&status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + BOOST_CHECK_NE(test_local_loop_controller, nullptr); + + // do some inference calls and report the outcome + constexpr int iterations = 100; + auto reward_before_update = run_simulation(model, status, iterations); + + // wait past eud time and update model + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + model.refresh_model(&status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + + // check that updated model has learned from previous outcomes + model_management::model_data model_data; + test_local_loop_controller->get_data(model_data, &status); + BOOST_TEST(status.get_error_code() == error_code::success, status.get_error_msg()); + auto vw = test_utils::create_vw(config.get(name::MODEL_VW_INITIAL_COMMAND_LINE, nullptr), model_data); + BOOST_CHECK_EQUAL(vw->sd->weighted_labeled_examples, iterations); + + // check that updated model now has better statistical performance + auto reward_after_update = run_simulation(model, status, iterations); + BOOST_CHECK_GT(reward_after_update, reward_before_update); + // std::cerr << "Reward before training: " << reward_before_update << std::endl; + // std::cerr << "Reward after training: " << reward_after_update << std::endl; +} diff --git a/unit_test/mock_http_client.cc b/unit_test/mock_http_client.cc index f7403db75..d0331e947 100644 --- a/unit_test/mock_http_client.cc +++ b/unit_test/mock_http_client.cc @@ -20,11 +20,13 @@ const std::string& mock_http_client::get_url() const { return _url; } mock_http_client::response_t mock_http_client::request(mock_http_client::request_t request) { auto responder = _responders[request.method()]; - return response_t([responder, request]() { - http_response resp; - responder(request, resp); - return resp; - }); + return response_t( + [responder, request]() + { + http_response resp; + responder(request, resp); + return resp; + }); } mock_http_client::response_t mock_http_client::request(mock_http_client::method_t method) diff --git a/unit_test/mock_util.cc b/unit_test/mock_util.cc index 188ca3961..53a204a2c 100644 --- a/unit_test/mock_util.cc +++ b/unit_test/mock_util.cc @@ -29,14 +29,15 @@ std::unique_ptr> get_mock_sender(std::vector>(new fakeit::Mock()); const std::function send_fn = - [&recorded_messages](const buffer_t& message, r::api_status*& status) { - // take a copy of the data - // the 'message' is a shared pointer to an object on the object pool - // if live_model dtor is called prior to the 'recorded_messages' vector (as is done in most of the test cases) - // there will be an invalid attempt to restore/free the object to/from the destructed object pool - recorded_messages.push_back(*message.get()); - return r::error_code::success; - }; + [&recorded_messages](const buffer_t& message, r::api_status*& status) + { + // take a copy of the data + // the 'message' is a shared pointer to an object on the object pool + // if live_model dtor is called prior to the 'recorded_messages' vector (as is done in most of the test cases) + // there will be an invalid attempt to restore/free the object to/from the destructed object pool + recorded_messages.push_back(*message.get()); + return r::error_code::success; + }; When(Method((*mock), init)).AlwaysReturn(r::error_code::success); When(Method((*mock), send)).AlwaysDo(send_fn); Fake(Dtor((*mock))); @@ -69,34 +70,39 @@ std::unique_ptr> get_mock_model(m::model_type_t model_t auto mock = std::unique_ptr>(new fakeit::Mock()); const auto choose_rank_fn = [](const char*, uint64_t, r::string_view, std::vector&, std::vector&, - std::string& model_version, r::api_status*) { + std::string& model_version, r::api_status*) + { model_version = "model_id"; return r::error_code::success; }; - const auto choose_continuous_action_fn = [](r::string_view, float&, float&, std::string& model_version, - r::api_status*) { + const auto choose_continuous_action_fn = + [](r::string_view, float&, float&, std::string& model_version, r::api_status*) + { model_version = "model_id"; return r::error_code::success; }; const auto request_decision_fn = [](const std::vector& event_ids, r::string_view, std::vector>&, std::vector>&, - std::string& model_version, r::api_status*) { + std::string& model_version, r::api_status*) + { model_version = "model_id"; return r::error_code::success; }; const auto request_multi_slot_decision_fn = [](const char*, const std::vector&, r::string_view, std::vector>&, std::vector>&, - std::string& model_version, r::api_status*) { + std::string& model_version, r::api_status*) + { model_version = "model_id"; return r::error_code::success; }; const auto choose_rank_multistep_fn = [](const char*, uint64_t, r::string_view, const r::episode_history&, std::vector&, std::vector&, std::string& model_version, - r::api_status*) { + r::api_status*) + { model_version = "model_id"; return r::error_code::success; }; @@ -122,13 +128,15 @@ std::unique_ptr get_mock_sender_factory( auto factory = std::unique_ptr(new r::sender_factory_t()); factory->register_type(r::value::get_default_observation_sender(), [mock_observation_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) { + r::i_trace*, r::api_status*) + { *retval = &mock_observation_sender->get(); return r::error_code::success; }); factory->register_type(r::value::get_default_interaction_sender(), [mock_interaction_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) { + r::i_trace*, r::api_status*) + { *retval = &mock_interaction_sender->get(); return r::error_code::success; }); @@ -140,7 +148,8 @@ std::unique_ptr get_mock_data_transport_factory( { auto factory = std::unique_ptr(new r::data_transport_factory_t()); factory->register_type(r::value::get_default_data_transport(), - [mock_data_transport](m::i_data_transport** retval, const u::configuration&, r::i_trace* trace, r::api_status*) { + [mock_data_transport](m::i_data_transport** retval, const u::configuration&, r::i_trace* trace, r::api_status*) + { *retval = &mock_data_transport->get(); return r::error_code::success; }); @@ -150,8 +159,9 @@ std::unique_ptr get_mock_data_transport_factory( std::unique_ptr get_mock_model_factory(fakeit::Mock* mock_model) { auto factory = std::unique_ptr(new r::model_factory_t()); - factory->register_type( - r::value::VW, [mock_model](m::i_model** retval, const u::configuration&, r::i_trace* trace, r::api_status*) { + factory->register_type(r::value::VW, + [mock_model](m::i_model** retval, const u::configuration&, r::i_trace* trace, r::api_status*) + { *retval = &mock_model->get(); return r::error_code::success; }); diff --git a/unit_test/model_mgmt_test.cc b/unit_test/model_mgmt_test.cc index ba45746ad..3516e792a 100644 --- a/unit_test/model_mgmt_test.cc +++ b/unit_test/model_mgmt_test.cc @@ -62,10 +62,7 @@ int get_export_frequency(const u::configuration& cc, int& interval_ms, r::api_st if (interval_ms == 0) { RETURN_ERROR_LS(nullptr, status, bad_time_interval); } return e::success; } - else - { - RETURN_ERROR_LS(nullptr, status, bad_time_interval); - } + else { RETURN_ERROR_LS(nullptr, status, bad_time_interval); } } void dummy_error_fn(const r::api_status& err, void* ctxt) { *((int*)ctxt) = 10; } diff --git a/unit_test/payload_serializer_test.cc b/unit_test/payload_serializer_test.cc index ea3ba9989..cc2e830ad 100644 --- a/unit_test/payload_serializer_test.cc +++ b/unit_test/payload_serializer_test.cc @@ -112,14 +112,20 @@ BOOST_AUTO_TEST_CASE(multi_slot_payload_serializer_test) BOOST_CHECK_EQUAL(actions[i].size(), slots[i]->action_ids()->size()); BOOST_CHECK_EQUAL(probs[i].size(), slots[i]->probabilities()->size()); for (flatbuffers::uoffset_t j = 0; j < actions[i].size(); ++j) - { BOOST_CHECK_EQUAL(actions[i][j], (*slots[i]->action_ids())[j]); } + { + BOOST_CHECK_EQUAL(actions[i][j], (*slots[i]->action_ids())[j]); + } for (flatbuffers::uoffset_t j = 0; j < probs[i].size(); ++j) - { BOOST_CHECK_CLOSE(probs[i][j], (*slots[i]->probabilities())[j], tolerance); } + { + BOOST_CHECK_CLOSE(probs[i][j], (*slots[i]->probabilities())[j], tolerance); + } } const auto& baseline = *event->baseline_actions(); for (flatbuffers::uoffset_t i = 0; i < baseline_actions.size(); ++i) - { BOOST_CHECK_EQUAL(baseline_actions[i], baseline[i]); } + { + BOOST_CHECK_EQUAL(baseline_actions[i], baseline[i]); + } BOOST_CHECK_EQUAL(false, event->deferred_action()); } diff --git a/unit_test/sender_joined_log_provider_test.cc b/unit_test/sender_joined_log_provider_test.cc new file mode 100644 index 000000000..5582c8479 --- /dev/null +++ b/unit_test/sender_joined_log_provider_test.cc @@ -0,0 +1,353 @@ +#include + +#include "configuration.h" +#include "constants.h" +#include "data_buffer.h" +#include "err_constants.h" +#include "federation/sender_joined_log_provider.h" +#include "generated/v2/Event_generated.h" +#include "generated/v2/FileFormat_generated.h" +#include "generated/v2/Metadata_generated.h" +#include "logger/message_type.h" +#include "logger/preamble.h" +#include "time_helper.h" +#include "vw/core/io_buf.h" +#include "vw/io/io_adapter.h" + +#include + +#include +#include +#include +#include + +using namespace reinforcement_learning; +namespace fbv2 = reinforcement_learning::messages::flatbuff::v2; + +namespace +{ +constexpr size_t BINARY_PARSER_VERSION = 1; +constexpr uint32_t MSG_TYPE_FILEMAGIC = 0x42465756; //'VWFB' +constexpr uint32_t MSG_TYPE_HEADER = 0x55555555; +constexpr uint32_t MSG_TYPE_REGULAR = 0xFFFFFFFF; +constexpr uint32_t MSG_TYPE_CHECKPOINT = 0x11111111; +constexpr uint32_t MSG_TYPE_EOF = 0xAAAAAAAA; + +// Object containing event metadata and string payload +// with helper functions to convert to/from Event flatbuffers +struct test_event +{ + std::string _event_id; + timestamp _time; + fbv2::PayloadType _payload_type; + std::string _payload; + + test_event(std::string event_id, timestamp time, fbv2::PayloadType payload_type, std::string payload) + : _event_id(std::move(event_id)), _time(time), _payload_type(payload_type), _payload(std::move(payload)) + { + } + + test_event(const fbv2::Event* buffer) + { + auto meta = buffer->meta(); + auto ts = meta->client_time_utc(); + auto payload = buffer->payload(); + _event_id = flatbuffers::GetString(meta->id()); + _time.year = ts->year(); + _time.day = ts->day(); + _time.month = ts->month(); + _time.hour = ts->hour(); + _time.minute = ts->minute(); + _time.second = ts->second(); + _time.sub_second = ts->subsecond(); + _payload_type = meta->payload_type(); + _payload = std::string(payload->begin(), payload->end()); + } + + flatbuffers::DetachedBuffer to_flatbuffer() + { + flatbuffers::FlatBufferBuilder event_builder; + auto ts = + fbv2::TimeStamp(_time.year, _time.month, _time.day, _time.hour, _time.minute, _time.second, _time.sub_second); + auto metadata = fbv2::CreateMetadataDirect(event_builder, _event_id.c_str(), &ts, nullptr, _payload_type); + auto payload_bytes = std::vector(_payload.begin(), _payload.end()); + auto event = fbv2::CreateEventDirect(event_builder, metadata, &payload_bytes); + event_builder.Finish(event); + return event_builder.Release(); + } +}; // struct test_event + +inline bool operator<(const test_event& te1, const test_event& te2) +{ + return std::tie(te1._event_id, te1._time, te1._payload_type, te1._payload) < + std::tie(te2._event_id, te2._time, te2._payload_type, te2._payload); +} + +inline bool operator==(const test_event& te1, const test_event& te2) +{ + return std::tie(te1._event_id, te1._time, te1._payload_type, te1._payload) == + std::tie(te2._event_id, te2._time, te2._payload_type, te2._payload); +} + +// A group of unique test_event objects with the same event id +struct test_event_batch +{ + std::set _batch; + + bool add_event(test_event evt) + { + auto result = _batch.insert(std::move(evt)); + return std::get<1>(result); + } + + bool verify() + { + // check that batch is non-empty and all events have the same id + if (_batch.empty()) return false; + auto first_id = _batch.begin()->_event_id; + for (auto&& evt : _batch) + { + if (evt._event_id != first_id) return false; + } + return true; + } + + bool operator==(const std::set& other) { return _batch == other; } +}; // struct test_event_batch + +std::shared_ptr create_message(test_event t_event) +{ + // create the Event flatbuffer + auto event_buffer = t_event.to_flatbuffer(); + auto serialized_buffer = std::vector(event_buffer.data(), event_buffer.data() + event_buffer.size()); + + // create the EventBatch flatbuffer + flatbuffers::FlatBufferBuilder batch_builder; + auto serialized_event = fbv2::CreateSerializedEventDirect(batch_builder, &serialized_buffer); + std::vector serialized_event_vector = {serialized_event}; + auto batch_metadata = fbv2::CreateBatchMetadataDirect(batch_builder, "IDENTITY"); + auto event_batch = fbv2::CreateEventBatchDirect(batch_builder, &serialized_event_vector, batch_metadata); + batch_builder.Finish(event_batch); + auto event_batch_buffer = batch_builder.Release(); + + // create preamble and put the flatbuffer into utility::data_buffer + logger::preamble pre; + pre.reserved = 0; + pre.version = 0; + pre.msg_type = logger::message_type::fb_generic_event_collection; + pre.msg_size = static_cast(event_batch_buffer.size()); + auto data_buffer = std::make_shared(event_batch_buffer.size()); + BOOST_TEST(pre.write_to_bytes(data_buffer->preamble_begin(), data_buffer->preamble_size())); + std::memcpy(data_buffer->body_begin(), event_batch_buffer.data(), event_batch_buffer.size()); + data_buffer->set_body_endoffset(data_buffer->get_body_beginoffset() + event_batch_buffer.size()); + return data_buffer; +} + +bool read_uint32(io_buf& buffer, uint32_t& output) +{ + char* read_ptr = nullptr; + auto len = buffer.buf_read(read_ptr, sizeof(uint32_t)); + if (len != sizeof(uint32_t) || read_ptr == nullptr) return false; + output = *reinterpret_cast(read_ptr); + return true; +} + +bool read_message(io_buf& buffer, uint32_t& message_type_out, std::vector& payload_out) +{ + message_type_out = 0; + payload_out.clear(); + + if (!read_uint32(buffer, message_type_out)) + { + // end of file + return false; + } + + uint32_t size = 0; + BOOST_TEST(read_uint32(buffer, size), "could not read payload size"); + + if (message_type_out == MSG_TYPE_FILEMAGIC) + { + // for file magic message, size is actually version number + BOOST_CHECK_EQUAL(size, BINARY_PARSER_VERSION); + return true; + } + + // if not file magic, must be another valid message type + BOOST_CHECK(message_type_out == MSG_TYPE_HEADER || message_type_out == MSG_TYPE_REGULAR || + message_type_out == MSG_TYPE_CHECKPOINT || message_type_out == MSG_TYPE_EOF); + + // all other message types have a real payload + char* read_ptr = nullptr; + auto actual_bytes_read = buffer.buf_read(read_ptr, size); + BOOST_CHECK_EQUAL(actual_bytes_read, size); + payload_out.insert(payload_out.begin(), read_ptr, read_ptr + size); + + // read padding bytes + auto padding_size = size % 8; + actual_bytes_read = buffer.buf_read(read_ptr, padding_size); + BOOST_CHECK_EQUAL(actual_bytes_read, padding_size); + return true; +} + +std::vector parse_joined_log(std::unique_ptr&& joined_log) +{ + io_buf buffer; + buffer.add_file(std::move(joined_log)); + + // must begin with file magic message + uint32_t message_type; + std::vector payload; + BOOST_TEST(read_message(buffer, message_type, payload), "could not read file magic message at start of binary log"); + BOOST_CHECK_EQUAL(message_type, MSG_TYPE_FILEMAGIC); + BOOST_TEST(payload.empty(), "file magic message has non-empty payload"); + + std::vector output; + while (read_message(buffer, message_type, payload)) + { + if (message_type == MSG_TYPE_REGULAR) + { + auto joined_payload = flatbuffers::GetRoot(payload.data()); + auto joined_payload_verifier = flatbuffers::Verifier(payload.data(), payload.size()); + BOOST_TEST(joined_payload->Verify(joined_payload_verifier), "verification failed on JoinedPayload flatbuffer"); + + test_event_batch batch; + auto joined_payload_events = joined_payload->events(); + BOOST_CHECK_NE(joined_payload_events, nullptr); + + for (auto joined_event : *joined_payload_events) + { + BOOST_CHECK_NE(joined_event, nullptr); + BOOST_CHECK_NE(joined_event->event(), nullptr); + auto event_fb = flatbuffers::GetRoot(joined_event->event()->data()); + auto event_verifier = flatbuffers::Verifier(joined_event->event()->data(), joined_event->event()->size()); + BOOST_TEST(event_fb->Verify(event_verifier), "verification failed on Event flatbuffer"); + + BOOST_CHECK_NE(event_fb->payload(), nullptr); + BOOST_CHECK_NE(event_fb->meta(), nullptr); + BOOST_CHECK_NE(event_fb->meta()->id(), nullptr); + BOOST_CHECK_NE(event_fb->meta()->client_time_utc(), nullptr); + BOOST_TEST(batch.add_event(test_event(event_fb)), "tried to insert duplicate event into event batch"); + } + + BOOST_TEST(batch.verify(), "event batch was empty or has bad event id"); + output.push_back(std::move(batch)); + } + + if (message_type == MSG_TYPE_EOF) { break; } + } + return output; +} + +std::unique_ptr create_test_object() +{ + utility::configuration config; + config.set(name::MODEL_VW_INITIAL_COMMAND_LINE, "--quiet --preserve_performance_counters"); + config.set(name::PROTOCOL_VERSION, "2"); + config.set(name::JOINER_EUD_DURATION, "0:0:10"); // EUD set to 10 seconds for all tests here + + std::unique_ptr sjlp; + BOOST_CHECK_EQUAL(sender_joined_log_provider::create(sjlp, config), error_code::success); + return sjlp; +} + +timestamp get_time(int seconds_from_now = 0) +{ + auto now = std::chrono::system_clock::now(); + auto time = now + std::chrono::seconds(seconds_from_now); + return timestamp(time); +} + +} // namespace + +BOOST_AUTO_TEST_CASE(empty_join) +{ + auto sjlp = create_test_object(); + std::unique_ptr output; + BOOST_CHECK_EQUAL(sjlp->invoke_join(output), error_code::success); + + auto result = parse_joined_log(std::move(output)); + BOOST_CHECK_EQUAL(result.size(), 0); +} + +BOOST_AUTO_TEST_CASE(one_interaction_before_eud) +{ + auto sjlp = create_test_object(); + test_event evt("id", get_time(0), fbv2::PayloadType_CB, "test payload"); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt)), error_code::success); + + std::unique_ptr output; + BOOST_CHECK_EQUAL(sjlp->invoke_join(output), error_code::success); + + auto result = parse_joined_log(std::move(output)); + BOOST_CHECK_EQUAL(result.size(), 0); +} + +BOOST_AUTO_TEST_CASE(one_interaction_after_eud) +{ + auto sjlp = create_test_object(); + test_event evt("id", get_time(-99), fbv2::PayloadType_CB, "test payload"); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt)), error_code::success); + + std::unique_ptr output; + BOOST_CHECK_EQUAL(sjlp->invoke_join(output), error_code::success); + + auto result = parse_joined_log(std::move(output)); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK(result[0] == (std::set{evt})); +} + +BOOST_AUTO_TEST_CASE(one_interaction_with_observations) +{ + auto sjlp = create_test_object(); + test_event evt1("id", get_time(-20), fbv2::PayloadType_CB, "test payload"); + test_event evt2("id", get_time(-19), fbv2::PayloadType_Outcome, "observation 1"); + test_event evt3("id", get_time(-15), fbv2::PayloadType_Outcome, "observation 2"); + test_event evt4("id", get_time(-5), fbv2::PayloadType_Outcome, "this observation is past eud"); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt1)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt2)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt3)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt4)), error_code::success); + + std::unique_ptr output; + BOOST_CHECK_EQUAL(sjlp->invoke_join(output), error_code::success); + + auto result = parse_joined_log(std::move(output)); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK(result[0] == (std::set{evt1, evt2, evt3})); +} + +BOOST_AUTO_TEST_CASE(multiple_interactions_and_observations) +{ + auto sjlp = create_test_object(); + test_event evt1("id_0", get_time(-20), fbv2::PayloadType_CB, "test payload"); + test_event evt2("id_0", get_time(-19), fbv2::PayloadType_Outcome, "observation 1"); + test_event evt3("id_0", get_time(-15), fbv2::PayloadType_Outcome, "observation 2"); + test_event evt4("id_0", get_time(-5), fbv2::PayloadType_Outcome, "this observation is past eud"); + test_event evt5("id_1", get_time(-80), fbv2::PayloadType_CB, "test payload"); + test_event evt6("id_2", get_time(-50), fbv2::PayloadType_CB, "test payload"); + test_event evt7("id_2", get_time(-49), fbv2::PayloadType_Outcome, "observation 1"); + test_event evt8("id_3", get_time(-1), fbv2::PayloadType_CB, "this event is before eud"); + test_event evt9("id_4", get_time(-15), fbv2::PayloadType_CB, "test payload"); + + // add events in order of time + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt5)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt6)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt7)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt1)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt2)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt9)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt3)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt4)), error_code::success); + BOOST_CHECK_EQUAL(sjlp->receive_events(create_message(evt8)), error_code::success); + + std::unique_ptr output; + BOOST_CHECK_EQUAL(sjlp->invoke_join(output), error_code::success); + + auto result = parse_joined_log(std::move(output)); + BOOST_CHECK_EQUAL(result.size(), 4); + BOOST_CHECK(result[0] == (std::set{evt5})); + BOOST_CHECK(result[1] == (std::set{evt6, evt7})); + BOOST_CHECK(result[2] == (std::set{evt1, evt2, evt3})); + BOOST_CHECK(result[3] == (std::set{evt9})); +} diff --git a/unit_test/sleeper_test.cc b/unit_test/sleeper_test.cc index 4a95b21d3..136f8389f 100644 --- a/unit_test/sleeper_test.cc +++ b/unit_test/sleeper_test.cc @@ -13,14 +13,16 @@ namespace u = reinforcement_learning::utility; BOOST_AUTO_TEST_CASE(sleeper_interrupt) { u::interruptable_sleeper sleeper; - std::thread t([&]() { - // test interruption - const auto start = std::chrono::system_clock::now(); - sleeper.sleep(std::chrono::milliseconds(5000)); - const auto stop = std::chrono::system_clock::now(); - const auto diff = std::chrono::duration_cast(stop - start); - BOOST_CHECK(diff <= std::chrono::milliseconds(100)); - }); + std::thread t( + [&]() + { + // test interruption + const auto start = std::chrono::system_clock::now(); + sleeper.sleep(std::chrono::milliseconds(5000)); + const auto stop = std::chrono::system_clock::now(); + const auto diff = std::chrono::duration_cast(stop - start); + BOOST_CHECK(diff <= std::chrono::milliseconds(100)); + }); std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -31,13 +33,15 @@ BOOST_AUTO_TEST_CASE(sleeper_interrupt) BOOST_AUTO_TEST_CASE(sleeper_sleep) { u::interruptable_sleeper sleeper; - std::thread t([&]() { - // test interruption - const auto start = std::chrono::system_clock::now(); - sleeper.sleep(std::chrono::milliseconds(100)); - const auto stop = std::chrono::system_clock::now(); - const auto diff = std::chrono::duration_cast(stop - start); - BOOST_CHECK(diff >= std::chrono::milliseconds(80)); - }); + std::thread t( + [&]() + { + // test interruption + const auto start = std::chrono::system_clock::now(); + sleeper.sleep(std::chrono::milliseconds(100)); + const auto stop = std::chrono::system_clock::now(); + const auto diff = std::chrono::duration_cast(stop - start); + BOOST_CHECK(diff >= std::chrono::milliseconds(80)); + }); t.join(); } \ No newline at end of file diff --git a/unit_test/time_tests.cc b/unit_test/time_tests.cc index 0010d9bfb..800b7cc47 100644 --- a/unit_test/time_tests.cc +++ b/unit_test/time_tests.cc @@ -34,7 +34,7 @@ BOOST_AUTO_TEST_CASE(time_round_trip) { r::clock_time_provider ctp; auto now = ctp.gmt_now(); - auto roundtripped = r::timestamp_from_chrono(r::chrono_from_timestamp(now)); + auto roundtripped = r::timestamp(now.to_time_point()); BOOST_CHECK_EQUAL(now, roundtripped); } @@ -42,13 +42,13 @@ BOOST_AUTO_TEST_CASE(time_ordering) { r::clock_time_provider ctp; auto now = ctp.gmt_now(); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - std::chrono::seconds(5)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - std::chrono::minutes(5)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - std::chrono::hours(5)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - std::chrono::hours(500)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - date::days(1)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - date::months(1)) < now); - BOOST_CHECK(r::timestamp_from_chrono(std::chrono::system_clock::now() - date::years(1)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - std::chrono::seconds(5)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - std::chrono::minutes(5)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - std::chrono::hours(5)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - std::chrono::hours(500)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - date::days(1)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - date::months(1)) < now); + BOOST_CHECK(r::timestamp(std::chrono::system_clock::now() - date::years(1)) < now); } // BOOST_AUTO_TEST_CASE(time_loop) { diff --git a/unit_test/trainable_model_test.cc b/unit_test/trainable_model_test.cc new file mode 100644 index 000000000..a5b9b3baf --- /dev/null +++ b/unit_test/trainable_model_test.cc @@ -0,0 +1,95 @@ +#include + +#include "common_test_utils.h" +#include "constants.h" +#include "err_constants.h" +#include "federation/vw_trainable_model.h" +#include "vw/core/shared_data.h" +#include "vw/core/vw.h" + +#include + +using namespace reinforcement_learning; + +void setup_config(utility::configuration& config) +{ + config.set(name::PROTOCOL_VERSION, "2"); + config.set(name::MODEL_VW_INITIAL_COMMAND_LINE, "--quiet --preserve_performance_counters"); + config.set(name::JOINER_PROBLEM_TYPE, value::PROBLEM_TYPE_UNKNOWN); + config.set(name::JOINER_LEARNING_MODE, value::LEARNING_MODE_ONLINE); + config.set(name::JOINER_REWARD_FUNCTION, value::REWARD_FUNCTION_EARLIEST); +} + +BOOST_AUTO_TEST_CASE(trainable_model_set_get_data) +{ + utility::configuration config; + setup_config(config); + const std::string command_line = config.get(name::MODEL_VW_INITIAL_COMMAND_LINE, ""); + auto vw = test_utils::create_vw(command_line); + + // learn on one example + auto ex = VW::read_example(*vw, "1 | a"); + vw->learn(*ex); + vw->finish_example(*ex); + const auto example_count = vw->sd->weighted_labeled_examples; + BOOST_CHECK_EQUAL(example_count, 1.f); + + // put the workspace into trainable_vw_model + std::unique_ptr model; + BOOST_CHECK_EQUAL(trainable_vw_model::create(model, config), error_code::success); + BOOST_CHECK_EQUAL(model->set_model(std::move(vw)), error_code::success); + + // get data out and check that it's equal + model_management::model_data data_out; + BOOST_CHECK_EQUAL(model->get_data(data_out), error_code::success); + auto vw_out = test_utils::create_vw(command_line, data_out); + BOOST_CHECK_EQUAL(vw_out->sd->weighted_labeled_examples, example_count); +} + +BOOST_AUTO_TEST_CASE(trainable_model_learn_and_create_delta) +{ + const std::string command_line = "--quiet --preserve_performance_counters"; + + // create 2 copies of the base VW workspace + auto vw1 = test_utils::create_vw(command_line); + auto vw2 = test_utils::create_vw(command_line); + + // learn on one example + VW::example* ex = VW::read_example(*vw1, "1 | a"); + vw1->learn(*ex); + vw1->finish_example(*ex); + ex = VW::read_example(*vw2, "1 | a"); + vw2->learn(*ex); + vw2->finish_example(*ex); + + // put the workspace into trainable_vw_model + std::unique_ptr trainable_model; + utility::configuration config; + config.set(name::PROTOCOL_VERSION, "2"); + config.set(name::MODEL_VW_INITIAL_COMMAND_LINE, command_line.c_str()); + BOOST_CHECK_EQUAL(trainable_vw_model::create(trainable_model, config), error_code::success); + BOOST_CHECK_EQUAL(trainable_model->set_model(std::move(vw1)), error_code::success); + + // train the trainable_vw_model on another example + auto vw3 = test_utils::create_vw(command_line); + std::vector examples; + examples.push_back(VW::read_example(*vw3, "1 | b")); + BOOST_CHECK_EQUAL(trainable_model->learn(*vw3, examples), error_code::success); + vw3->finish_example(*examples.back()); + + // get data in trainable model + model_management::model_data data_out; + BOOST_CHECK_EQUAL(trainable_model->get_data(data_out), error_code::success); + auto vw1_updated = test_utils::create_vw(command_line, data_out); + + // get model delta and update vw2 workspace + VW::model_delta delta(nullptr); + BOOST_CHECK_EQUAL(trainable_model->get_model_delta(delta), error_code::success); + auto vw2_updated = *vw2 + delta; + VW::workspace* delta_ws = delta.unsafe_get_workspace_ptr(); + BOOST_CHECK_EQUAL(delta_ws->sd->weighted_labeled_examples, 1.f); + + // check that results are same + BOOST_CHECK_EQUAL(vw1_updated->sd->weighted_labeled_examples, 2.f); + BOOST_CHECK_EQUAL(vw2_updated->sd->weighted_labeled_examples, 2.f); +} diff --git a/unit_test/watchdog_test.cc b/unit_test/watchdog_test.cc index d8476e6e8..7b4a97ae2 100644 --- a/unit_test/watchdog_test.cc +++ b/unit_test/watchdog_test.cc @@ -2,11 +2,12 @@ #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif -#include "utility/watchdog.h" + #include #include "common_test_utils.h" #include "str_util.h" +#include "utility/watchdog.h" #include #include @@ -50,7 +51,7 @@ BOOST_AUTO_TEST_CASE(watchdog_unregister) BOOST_AUTO_TEST_CASE(watchdog_fail_after_several_iterations) { - if (is_invoked_with("valgrind")) + if (test_utils::is_invoked_with("valgrind")) { // this test depends on clock timeouts, can't guarantee test success under valgrind std::cout << "skipping watchdog_fail_after_several_iterations test when running in valgrind" << std::endl; @@ -80,7 +81,8 @@ BOOST_AUTO_TEST_CASE(watchdog_report_with_error_handler) std::atomic atomic_counter; atomic_counter.store(0); - auto const error_fn = [](const api_status&, void* arg) { + auto const error_fn = [](const api_status&, void* arg) + { auto counter = static_cast*>(arg); counter->fetch_add(1); }; @@ -98,7 +100,7 @@ BOOST_AUTO_TEST_CASE(watchdog_report_with_error_handler) BOOST_AUTO_TEST_CASE(watchdog_multiple_threads) { - if (is_invoked_with("valgrind")) + if (test_utils::is_invoked_with("valgrind")) { // this test depends on clock timeouts, can't guarantee test success under valgrind std::cout << "skipping watchdog_multiple_threads test when running in valgrind" << std::endl; @@ -113,17 +115,19 @@ BOOST_AUTO_TEST_CASE(watchdog_multiple_threads) threads.reserve(num_threads); for (auto i = 0; i < num_threads; i++) { - threads.emplace_back([&, i]() { - watchdog.register_thread(std::this_thread::get_id(), utility::concat("Test thread ", i), timeout); - - for (auto j = 0; j < num_iterations; j++) - { - watchdog.check_in(std::this_thread::get_id()); - std::this_thread::sleep_for(std::chrono::milliseconds(safe_timeout)); - } - - watchdog.unregister_thread(std::this_thread::get_id()); - }); + threads.emplace_back( + [&, i]() + { + watchdog.register_thread(std::this_thread::get_id(), utility::concat("Test thread ", i), timeout); + + for (auto j = 0; j < num_iterations; j++) + { + watchdog.check_in(std::this_thread::get_id()); + std::this_thread::sleep_for(std::chrono::milliseconds(safe_timeout)); + } + + watchdog.unregister_thread(std::this_thread::get_id()); + }); } BOOST_CHECK_EQUAL(watchdog.has_background_error_been_reported(), false); diff --git a/vcpkg.json b/vcpkg.json index 909b57614..51b42b277 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -22,5 +22,11 @@ "rapidjson", "spdlog", "zlib" - ] + ], + "features": { + "benchmarks": { + "description": "Build Benchmarks", + "dependencies": [{"name":"benchmark", "version>=":"1.7.1"}] + } + } } From 3477f23b17e0a25fc7d6122bd35c1496b04dc493 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 17 Jan 2023 15:17:42 -0500 Subject: [PATCH 07/16] remove old implementation of local model (#539) * remove old implementation of local model * Formatting --- examples/CMakeLists.txt | 5 +- examples/rl_sim_cpp/CMakeLists.txt | 4 +- examples/rl_sim_cpp/local_loop.cc | 138 ----------------------------- examples/rl_sim_cpp/local_loop.h | 61 ------------- examples/rl_sim_cpp/main.cc | 3 +- examples/rl_sim_cpp/rl_sim.cc | 65 +------------- examples/rl_sim_cpp/rl_sim.h | 2 - 7 files changed, 4 insertions(+), 274 deletions(-) delete mode 100644 examples/rl_sim_cpp/local_loop.cc delete mode 100644 examples/rl_sim_cpp/local_loop.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 66efacf8c..90e35abeb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,11 +1,8 @@ add_subdirectory(basic_usage_cpp) add_subdirectory(override_interface) +add_subdirectory(rl_sim_cpp) add_subdirectory(test_cpp) -if(RL_BUILD_EXTERNAL_PARSER) - add_subdirectory(rl_sim_cpp) -endif() - if (rlclientlib_BUILD_ONNXRUNTIME_EXTENSION) add_subdirectory(onnx) endif() diff --git a/examples/rl_sim_cpp/CMakeLists.txt b/examples/rl_sim_cpp/CMakeLists.txt index 5414f7d4c..5bf816e2b 100644 --- a/examples/rl_sim_cpp/CMakeLists.txt +++ b/examples/rl_sim_cpp/CMakeLists.txt @@ -3,8 +3,6 @@ add_executable(rl_sim_cpp.out person.cc robot_joint.cc rl_sim.cc - local_loop.cc - local_loop.h ) -target_link_libraries(rl_sim_cpp.out PRIVATE Boost::program_options rlclientlib vw_core rl_binary_parser) +target_link_libraries(rl_sim_cpp.out PRIVATE Boost::program_options rlclientlib) diff --git a/examples/rl_sim_cpp/local_loop.cc b/examples/rl_sim_cpp/local_loop.cc deleted file mode 100644 index 0da4b4082..000000000 --- a/examples/rl_sim_cpp/local_loop.cc +++ /dev/null @@ -1,138 +0,0 @@ -#include "local_loop.h" - -#include "../../rlclientlib/logger/message_type.h" -#include "../../rlclientlib/logger/preamble.h" -#include "api_status.h" -#include "constants.h" -#include "err_constants.h" -#include "trace_logger.h" -#include "vw/config/options_cli.h" -#include "vw/core/parse_primitives.h" - -namespace rl = reinforcement_learning; - -void local_model::set_trace_logger(rl::i_trace* trace_logger) { _trace_logger = trace_logger; } - -// Get data causes a "join" to take place. -int local_model::get_data( - reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) -{ - std::lock_guard lock(_mutex); - data.increment_refresh_count(); - - // If this is called before init is done then exit early. - if (!_joiner) { return rl::error_code::success; } - - std::stringstream ss; - ss << "Joining " << _joiner->events_in_queue() << " events." << std::endl; - _trace_logger->log(rl::LEVEL_INFO, ss.str()); - - VW::multi_ex ex; - while (_joiner->processing_batch()) - { - ex.push_back(VW::new_unused_example(*_training_workspace)); - - // False means there was a problem and we should try reading the next one. - if (!_joiner->process_joined(ex)) - { - assert(ex.size() == 1); - auto* ex_to_return = ex.back(); - ex.pop_back(); - VW::finish_example(*_training_workspace, *ex_to_return); - continue; - } - VW::setup_examples(*_training_workspace, ex); - - // We must remove the trailing newline example. - auto* newline_ex = ex.back(); - ex.pop_back(); - VW::finish_example(*_training_workspace, *newline_ex); - - // Learn and finish the given examples - _training_workspace->learn(ex); - _training_workspace->finish_example(ex); - ex.clear(); - } - - // Clear all joined buffers. - _detached_buffers.clear(); - - // Save current model state to a buffer and return to client. - io_buf buffer; - auto backing_buffer = std::make_shared>(); - buffer.add_file(VW::io::create_vector_writer(backing_buffer)); - VW::save_predictor(*_training_workspace, buffer); - - auto* buffer_to_copy_to = data.alloc(backing_buffer->size()); - std::memcpy(buffer_to_copy_to, backing_buffer->data(), backing_buffer->size()); - - return rl::error_code::success; -} - -int local_model::init( - const reinforcement_learning::utility::configuration& config, reinforcement_learning::api_status* status) -{ - if (config.get_int(rl::name::PROTOCOL_VERSION, 999) != 2) - { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " protocol version 2 required"; - } - - if (!_vw_model) - { - _vw_model = - std::unique_ptr(new rl::model_management::vw_model(_trace_logger, config)); - } - std::string cmd_str = config.get(rl::name::MODEL_VW_INITIAL_COMMAND_LINE, - "--cb_explore_adf --driver_output_off --epsilon 0.2 --power_t 0 -l 0.001 --cb_type mtr -q ::"); - auto cmd_list = VW::split_command_line(cmd_str); - auto options = VW::make_unique(cmd_list); - _training_workspace = VW::initialize_experimental(std::move(options)); - _joiner = VW::make_unique(_training_workspace.get()); - _joiner->set_problem_type_config(rl::messages::flatbuff::v2::ProblemType_CB); - _joiner->set_learning_mode_config(rl::messages::flatbuff::v2::LearningModeType_Online); - _joiner->set_reward_function(rl::messages::flatbuff::v2::RewardFunctionType_Earliest); - _joiner->set_default_reward(0.f); - return rl::error_code::success; -} - -int local_model::v_send(const buffer& data, reinforcement_learning::api_status* status) -{ - std::lock_guard lock(_mutex); - rl::logger::preamble pre; - pre.read_from_bytes(data->preamble_begin(), pre.size()); - - if (pre.msg_type == rl::logger::message_type::fb_generic_event_collection) - { - auto res = reinforcement_learning::messages::flatbuff::v2::GetEventBatch(data->body_begin()); - flatbuffers::Verifier verifier(data->body_begin(), data->body_filled_size()); - auto result = res->Verify(verifier); - - if (!result) - { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "verify failed for fb_generic_event_collection"; - } - - if (res->metadata()->content_encoding()->str() != "IDENTITY") - { - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << "Can only handle IDENTITY encoding"; - } - - for (auto message : *res->events()) - { - flatbuffers::FlatBufferBuilder fbb; - // TODO: real timestamp - rl::messages::flatbuff::v2::TimeStamp ts(2020, 3, 1, 10, 20, 30, 0); - const auto* event_payload = message->payload(); - auto vec = fbb.CreateVector(event_payload->data(), event_payload->size()); - auto fb = rl::messages::flatbuff::v2::CreateJoinedEvent(fbb, vec, &ts); - fbb.Finish(fb); - _detached_buffers.push_back(fbb.Release()); - - const auto* je = flatbuffers::GetRoot(_detached_buffers.back().data()); - _joiner->process_event(*je); - } - return rl::error_code::success; - } - - RETURN_ERROR_LS(_trace_logger, status, invalid_argument) << " Message type " << pre.msg_type << " cannot be handled."; -} diff --git a/examples/rl_sim_cpp/local_loop.h b/examples/rl_sim_cpp/local_loop.h deleted file mode 100644 index 0b7aa4e96..000000000 --- a/examples/rl_sim_cpp/local_loop.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "../../rlclientlib/vw_model/vw_model.h" -#include "joiners/example_joiner.h" -#include "model_mgmt.h" -#include "sender.h" -#include "vw/core/global_data.h" - -struct local_model : public reinforcement_learning::model_management::i_data_transport, reinforcement_learning::i_sender -{ - reinforcement_learning::i_trace* _trace_logger = nullptr; - std::unique_ptr _vw_model = nullptr; - std::unique_ptr _training_workspace = nullptr; - std::unique_ptr _joiner = nullptr; - std::mutex _mutex; - - // This needs to be cleared after a full join. - std::vector _detached_buffers; - - local_model() = default; - ~local_model() override = default; - - void set_trace_logger(reinforcement_learning::i_trace* trace_logger); - - // Get data causes a "join" to take place. - int get_data( - reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) override; - - int init(const reinforcement_learning::utility::configuration& config, - reinforcement_learning::api_status* status) override; - - int v_send(const buffer& data, reinforcement_learning::api_status* status) override; -}; - -struct local_model_proxy : public reinforcement_learning::model_management::i_data_transport, - reinforcement_learning::i_sender -{ - explicit local_model_proxy(local_model* local_model) : _local_model(local_model) {} - ~local_model_proxy() override = default; - - int get_data( - reinforcement_learning::model_management::model_data& data, reinforcement_learning::api_status* status) override - { - return _local_model->get_data(data, status); - } - - int init( - const reinforcement_learning::utility::configuration& config, reinforcement_learning::api_status* status) override - { - return _local_model->init(config, status); - } - -protected: - int v_send(const buffer& data, reinforcement_learning::api_status* status) override - { - return _local_model->v_send(data, status); - } - -private: - local_model* _local_model; -}; \ No newline at end of file diff --git a/examples/rl_sim_cpp/main.cc b/examples/rl_sim_cpp/main.cc index 729478f62..e9011bbae 100644 --- a/examples/rl_sim_cpp/main.cc +++ b/examples/rl_sim_cpp/main.cc @@ -38,8 +38,7 @@ po::variables_map process_cmd_line(const int argc, char** argv) "random_seed", po::value()->default_value(rand()), "Random seed. Default is random")("delay", po::value()->default_value(2000), "Delay between events in ms")("quiet", po::bool_switch(), "Suppress logs")( - "random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true")( - "local_loop", po::value()->default_value(false), "Train and update model locally"); + "random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true"); po::variables_map vm; store(parse_command_line(argc, argv, desc), vm); diff --git a/examples/rl_sim_cpp/rl_sim.cc b/examples/rl_sim_cpp/rl_sim.cc index 8140c981c..b6f36a2f4 100644 --- a/examples/rl_sim_cpp/rl_sim.cc +++ b/examples/rl_sim_cpp/rl_sim.cc @@ -1,6 +1,5 @@ #include "constants.h" #include "live_model.h" -#include "local_loop.h" #include "multistep.h" #include "person.h" #include "rand48.h" @@ -375,7 +374,6 @@ int rl_sim::init_rl() r::api_status status; u::configuration config; - bool local_loop = false; // Load configuration from json config file const auto config_file = _options["json_config"].as(); if (load_config_from_json(config_file, config, &status) != err::success) @@ -384,52 +382,13 @@ int rl_sim::init_rl() return -1; } - if (_options["local_loop"].as()) - { - local_loop = true; - std::cout << "Using --local_loop, replacing some config vars." << std::endl; - if (_loop_kind != CB) - { - std::cerr << "--local_loop can only be used with cb." << std::endl; - return -1; - } - - config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, "LOCAL_MODEL"); - std::cout << "Setting " << r::name::INTERACTION_SENDER_IMPLEMENTATION << "=LOCAL_MODEL" << std::endl; - config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, "LOCAL_MODEL"); - std::cout << "Setting " << r::name::OBSERVATION_SENDER_IMPLEMENTATION << "=LOCAL_MODEL" << std::endl; - config.set(r::name::MODEL_SRC, "LOCAL_MODEL"); - std::cout << "Setting " << r::name::MODEL_SRC << "=LOCAL_MODEL" << std::endl; - config.set(r::name::MODEL_IMPLEMENTATION, "VW"); - std::cout << "Setting " << r::name::MODEL_IMPLEMENTATION << "=VW" << std::endl; - config.set(r::name::PROTOCOL_VERSION, "2"); - std::cout << "Setting " << r::name::PROTOCOL_VERSION << "=2" << std::endl; - config.set(r::name::OBSERVATION_SEND_BATCH_INTERVAL_MS, "100"); - std::cout << "Setting " << r::name::OBSERVATION_SEND_BATCH_INTERVAL_MS << "=100" << std::endl; - config.set(r::name::MODEL_REFRESH_INTERVAL_MS, "2000"); - std::cout << "Setting " << r::name::MODEL_REFRESH_INTERVAL_MS << "=2000" << std::endl; - } - if (_options["log_to_file"].as()) { - if (local_loop) - { - std::cerr << "--local_loop and --log_to_file can't be used together." << std::endl; - return -1; - } config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); } - if (!_options["get_model"].as()) - { - if (local_loop) - { - std::cerr << "--local_loop and --get_model can't be used together." << std::endl; - return -1; - } - config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); - } + if (!_options["get_model"].as()) { config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); } if (_options["log_timestamp"].as()) { @@ -440,28 +399,6 @@ int rl_sim::init_rl() // Trace log API calls to the console if (!_quiet) { config.set(r::name::TRACE_LOG_IMPLEMENTATION, r::value::CONSOLE_TRACE_LOGGER); } - if (local_loop) - { - _local_model = std::unique_ptr(new local_model); - auto* raw_local_model = _local_model.get(); - r::data_transport_factory.register_type("LOCAL_MODEL", - [raw_local_model](r::model_management::i_data_transport** retval, const r::utility::configuration& config, - r::i_trace* trace_logger, r::api_status* status) - { - raw_local_model->set_trace_logger(trace_logger); - *retval = new local_model_proxy(raw_local_model); - return 0; - }); - r::sender_factory.register_type("LOCAL_MODEL", - [raw_local_model](r::i_sender** retval, const r::utility::configuration& config, r::error_callback_fn* error_cb, - r::i_trace* trace_logger, r::api_status* status) - { - raw_local_model->set_trace_logger(trace_logger); - *retval = new local_model_proxy(raw_local_model); - return 0; - }); - } - // Initialize the API _rl = std::unique_ptr(new r::live_model(config, _on_error, this)); if (_rl->init(&status) != err::success) diff --git a/examples/rl_sim_cpp/rl_sim.h b/examples/rl_sim_cpp/rl_sim.h index 44afa69b3..145713db3 100644 --- a/examples/rl_sim_cpp/rl_sim.h +++ b/examples/rl_sim_cpp/rl_sim.h @@ -7,7 +7,6 @@ */ #pragma once #include "live_model.h" -#include "local_loop.h" #include "person.h" #include "robot_joint.h" @@ -163,7 +162,6 @@ class rl_sim boost::program_options::variables_map _options; std::unique_ptr _rl; - std::unique_ptr _local_model; std::vector _people; std::vector _topics; std::vector _slot_sizes; From f55388285d476f330b93f4f2cedf274957bac126 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 17 Jan 2023 16:26:39 -0500 Subject: [PATCH 08/16] fix model refresh from background thread (#540) --- rlclientlib/federation/vw_trainable_model.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/rlclientlib/federation/vw_trainable_model.cc b/rlclientlib/federation/vw_trainable_model.cc index 00db16b02..c04571bd0 100644 --- a/rlclientlib/federation/vw_trainable_model.cc +++ b/rlclientlib/federation/vw_trainable_model.cc @@ -190,6 +190,7 @@ int trainable_vw_model::get_data(model_management::model_data& data, api_status* } auto* destination_buffer = data.alloc(backing_buffer->size()); std::memcpy(destination_buffer, backing_buffer->data(), backing_buffer->size()); + data.increment_refresh_count(); TRACE_INFO(_trace_logger, utility::concat("trainable_vw_model::get_data() returning model trained on ", example_count, " examples")); From 5506c1fc22032138faa76ae99bef9430b09e41a4 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Wed, 18 Jan 2023 10:14:41 -0500 Subject: [PATCH 09/16] improve logging and dont learn on newline example (#541) --- examples/rl_sim_cpp/local_loop_client.json | 16 ++++++ rlclientlib/CMakeLists.txt | 2 + rlclientlib/federation/local_client.cc | 4 +- rlclientlib/federation/vw_trainable_model.cc | 55 ++++++++------------ rlclientlib/utility/vw_logger_adapter.cc | 37 +++++++++++++ rlclientlib/utility/vw_logger_adapter.h | 12 +++++ 6 files changed, 91 insertions(+), 35 deletions(-) create mode 100644 examples/rl_sim_cpp/local_loop_client.json create mode 100644 rlclientlib/utility/vw_logger_adapter.cc create mode 100644 rlclientlib/utility/vw_logger_adapter.h diff --git a/examples/rl_sim_cpp/local_loop_client.json b/examples/rl_sim_cpp/local_loop_client.json new file mode 100644 index 000000000..c8c115348 --- /dev/null +++ b/examples/rl_sim_cpp/local_loop_client.json @@ -0,0 +1,16 @@ +{ + "model.vw.initial_command_line": "--cb_explore_adf --json --epsilon 0.0 --preserve_performance_counters -q :: --driver_output_off", + "ApplicationID": "local_loop", + "IsExplorationEnabled": true, + "InitialExplorationEpsilon": 0.2, + "protocol.version": "2", + "model.source": "LOCAL_LOOP_MODEL_DATA", + "interaction.sender.implementation": "LOCAL_LOOP_SENDER", + "observation.sender.implementation": "LOCAL_LOOP_SENDER", + "eud.duration": "0:0:1", + "joiner.problem.type": "PROBLEM_TYPE_CB", + "joiner.reward.function": "REWARD_FUNCTION_EARLIEST", + "joiner.learning.mode": "ONLINE", + "model.refreshintervalms": "5000", + "time_provider.implementation": "CLOCK_TIME_PROVIDER" +} diff --git a/rlclientlib/CMakeLists.txt b/rlclientlib/CMakeLists.txt index ae3aab8aa..296d8e75b 100644 --- a/rlclientlib/CMakeLists.txt +++ b/rlclientlib/CMakeLists.txt @@ -92,6 +92,7 @@ set(PROJECT_SOURCES utility/context_helper.cc utility/data_buffer.cc utility/data_buffer_streambuf.cc + utility/vw_logger_adapter.cc vw_model/pdf_model.cc vw_model/safe_vw.cc utility/stl_container_adapter.cc @@ -174,6 +175,7 @@ set(PROJECT_PRIVATE_HEADERS utility/object_pool.h utility/periodic_background_proc.h utility/watchdog.h + utility/vw_logger_adapter.h vw_model/pdf_model.h vw_model/safe_vw.h vw_model/vw_model.h diff --git a/rlclientlib/federation/local_client.cc b/rlclientlib/federation/local_client.cc index 882982ff9..e3b4ae1f1 100644 --- a/rlclientlib/federation/local_client.cc +++ b/rlclientlib/federation/local_client.cc @@ -4,6 +4,7 @@ #include "constants.h" #include "err_constants.h" #include "trace_logger.h" +#include "utility/vw_logger_adapter.h" #include "vw/config/options_cli.h" #include "vw/core/global_data.h" #include "vw/core/io_buf.h" @@ -88,7 +89,8 @@ int local_client::create(std::unique_ptr& output, const util // TODO try catch auto args = VW::make_unique(VW::split_command_line(initial_command_line)); - auto workspace = VW::initialize_experimental(std::move(args)); + auto logger = utility::make_vw_trace_logger(trace_logger); + auto workspace = VW::initialize_experimental(std::move(args), nullptr, nullptr, nullptr, &logger); output = std::unique_ptr(new local_client(std::move(workspace), trace_logger)); return error_code::success; diff --git a/rlclientlib/federation/vw_trainable_model.cc b/rlclientlib/federation/vw_trainable_model.cc index c04571bd0..515555a61 100644 --- a/rlclientlib/federation/vw_trainable_model.cc +++ b/rlclientlib/federation/vw_trainable_model.cc @@ -6,6 +6,7 @@ #include "joiners/multistep_example_joiner.h" #include "parse_example_binary.h" #include "str_util.h" +#include "utility/vw_logger_adapter.h" #include "vw/config/options_cli.h" #include "vw/core/learner.h" #include "vw/core/parse_primitives.h" @@ -55,34 +56,6 @@ void finish_examples(VW::workspace& vw, VW::multi_ex& examples) examples.clear(); } -void vw_log_to_trace_logger(void* trace_logger, VW::io::log_level log_level, const std::string& msg) -{ - if (trace_logger == nullptr) { return; } - auto i_trace_ptr = static_cast(trace_logger); - switch (log_level) - { - case VW::io::log_level::TRACE_LEVEL: - case VW::io::log_level::DEBUG_LEVEL: - TRACE_DEBUG(i_trace_ptr, msg); - break; - - case VW::io::log_level::INFO_LEVEL: - TRACE_INFO(i_trace_ptr, msg); - break; - - case VW::io::log_level::WARN_LEVEL: - TRACE_WARN(i_trace_ptr, msg); - break; - - case VW::io::log_level::ERROR_LEVEL: - case VW::io::log_level::CRITICAL_LEVEL: - TRACE_ERROR(i_trace_ptr, msg); - break; - - default: - break; - } -} } // namespace namespace reinforcement_learning @@ -126,7 +99,8 @@ trainable_vw_model::trainable_vw_model(std::string command_line, std::string pro , _trace_logger(trace_logger) { auto options = VW::make_unique(VW::split_command_line(_command_line)); - _model = VW::initialize_experimental(std::move(options)); + auto logger = utility::make_vw_trace_logger(_trace_logger); + _model = VW::initialize_experimental(std::move(options), nullptr, nullptr, nullptr, &logger); copy_current_model_to_starting(); } @@ -159,7 +133,9 @@ int trainable_vw_model::set_data(const model_management::model_data& data, api_s std::unique_ptr(new VW::config::options_cli(VW::split_command_line(_command_line))); { std::lock_guard lock(_mutex); - _model = VW::initialize_experimental(std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz())); + auto logger = utility::make_vw_trace_logger(_trace_logger); + _model = VW::initialize_experimental( + std::move(opts), VW::io::create_buffer_view(data.data(), data.data_sz()), nullptr, nullptr, &logger); } copy_current_model_to_starting(); } @@ -232,8 +208,7 @@ int trainable_vw_model::learn(std::unique_ptr&& binary_log, api_ // Set the default joiner options if no checkpoint message is present in the binary log configure_joiner(joiner); - VW::external::binary_parser binary_parser( - std::move(joiner), VW::io::create_custom_sink_logger(_trace_logger, vw_log_to_trace_logger)); + VW::external::binary_parser binary_parser(std::move(joiner), utility::make_vw_trace_logger(_trace_logger)); int example_count = 0; bool example_was_parsed = false; @@ -242,7 +217,18 @@ int trainable_vw_model::learn(std::unique_ptr&& binary_log, api_ example_out.push_back(VW::new_unused_example(*_model)); example_was_parsed = binary_parser.parse_examples(_model.get(), io_reader, example_out); - if (example_was_parsed) { example_count += learn_and_finish_examples(*_model, example_out); } + if (example_was_parsed) + { + auto has_newline = example_out.back()->is_newline; + if (has_newline) + { + auto last = example_out.back(); + VW::finish_example(*_model, *last); + example_out.pop_back(); + } + + example_count += learn_and_finish_examples(*_model, example_out); + } else { // cleanup the unused example that the parser was called with @@ -375,8 +361,9 @@ void trainable_vw_model::copy_current_model_to_starting() { std::lock_guard lock(_mutex); + auto logger = utility::make_vw_trace_logger(_trace_logger); _starting_model = VW::initialize_experimental(std::move(options), - VW::io::create_buffer_view(backing_vector->data(), backing_vector->size()), nullptr, nullptr, nullptr); + VW::io::create_buffer_view(backing_vector->data(), backing_vector->size()), nullptr, nullptr, &logger); } } diff --git a/rlclientlib/utility/vw_logger_adapter.cc b/rlclientlib/utility/vw_logger_adapter.cc new file mode 100644 index 000000000..f0eb04426 --- /dev/null +++ b/rlclientlib/utility/vw_logger_adapter.cc @@ -0,0 +1,37 @@ +#include "vw_logger_adapter.h" + +#include "trace_logger.h" + +static void vw_log_to_trace_logger(void* trace_logger, VW::io::log_level log_level, const std::string& msg) +{ + if (trace_logger == nullptr) { return; } + auto* i_trace_ptr = static_cast(trace_logger); + switch (log_level) + { + case VW::io::log_level::TRACE_LEVEL: + case VW::io::log_level::DEBUG_LEVEL: + TRACE_DEBUG(i_trace_ptr, msg); + break; + + case VW::io::log_level::INFO_LEVEL: + TRACE_INFO(i_trace_ptr, msg); + break; + + case VW::io::log_level::WARN_LEVEL: + TRACE_WARN(i_trace_ptr, msg); + break; + + case VW::io::log_level::ERROR_LEVEL: + case VW::io::log_level::CRITICAL_LEVEL: + TRACE_ERROR(i_trace_ptr, msg); + break; + + default: + break; + } +} + +VW::io::logger reinforcement_learning::utility::make_vw_trace_logger(i_trace* trace_logger) +{ + return VW::io::create_custom_sink_logger(trace_logger, vw_log_to_trace_logger); +} diff --git a/rlclientlib/utility/vw_logger_adapter.h b/rlclientlib/utility/vw_logger_adapter.h new file mode 100644 index 000000000..fe0a5fbf3 --- /dev/null +++ b/rlclientlib/utility/vw_logger_adapter.h @@ -0,0 +1,12 @@ +#include "trace_logger.h" +#include "vw/io/logger.h" + +#include + +namespace reinforcement_learning +{ +namespace utility +{ +VW::io::logger make_vw_trace_logger(i_trace* trace_logger); +} +} // namespace reinforcement_learning \ No newline at end of file From d59215601842478c8e14fde861bf38db653ec224 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Wed, 18 Jan 2023 16:48:07 -0500 Subject: [PATCH 10/16] fix segfault from using wrong finish (#542) --- rlclientlib/federation/vw_trainable_model.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rlclientlib/federation/vw_trainable_model.cc b/rlclientlib/federation/vw_trainable_model.cc index 515555a61..e60556cc3 100644 --- a/rlclientlib/federation/vw_trainable_model.cc +++ b/rlclientlib/federation/vw_trainable_model.cc @@ -233,7 +233,8 @@ int trainable_vw_model::learn(std::unique_ptr&& binary_log, api_ { // cleanup the unused example that the parser was called with assert(example_out.size() == 1); - finish_examples(*_model, example_out); + VW::finish_example(*_model, example_out); + example_out.clear(); } } while (example_was_parsed); From 7703abb421560419eecb394e727db39cde8a6a31 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Fri, 20 Jan 2023 16:08:01 -0500 Subject: [PATCH 11/16] Add ctr to cb and add an option to refresh after every n examples (#543) * Add ctr to cb and add an option to refresh after every n examples * formatting --- examples/rl_sim_cpp/main.cc | 5 +- examples/rl_sim_cpp/rl_sim.cc | 63 +++++++++++++++++++++++++- examples/rl_sim_cpp/rl_sim.h | 1 + examples/rl_sim_cpp/simulation_stats.h | 4 ++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/examples/rl_sim_cpp/main.cc b/examples/rl_sim_cpp/main.cc index e9011bbae..d61b9babf 100644 --- a/examples/rl_sim_cpp/main.cc +++ b/examples/rl_sim_cpp/main.cc @@ -38,7 +38,10 @@ po::variables_map process_cmd_line(const int argc, char** argv) "random_seed", po::value()->default_value(rand()), "Random seed. Default is random")("delay", po::value()->default_value(2000), "Delay between events in ms")("quiet", po::bool_switch(), "Suppress logs")( - "random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true"); + "random_ids", po::value()->default_value(true), "Use randomly generated Event IDs. Default is true")( + "refresh_model_period", po::value()->default_value(0), + "Call refresh model after every N examples. 0 turns off explicit model refresh and relies on background refresh. " + "Must disable background refresh in client.json with key 'model.backgroundrefresh'"); po::variables_map vm; store(parse_command_line(argc, argv, desc), vm); diff --git a/examples/rl_sim_cpp/rl_sim.cc b/examples/rl_sim_cpp/rl_sim.cc index b6f36a2f4..2d895f82e 100644 --- a/examples/rl_sim_cpp/rl_sim.cc +++ b/examples/rl_sim_cpp/rl_sim.cc @@ -84,8 +84,21 @@ int rl_sim::cb_loop() { std::cout << " " << stats.count() << ", ctxt, " << p.id() << ", action, " << chosen_action << ", outcome, " << outcome << ", dist, " << get_dist_str(response) << ", " << stats.get_stats(p.id(), chosen_action) - << std::endl; + << ", ctr: " << stats.get_ctr() << std::endl; } + + // refresh model every _model_refresh_period events + std::cerr << "Current events: " << _current_events << std::endl; + if (_model_refresh_period != 0 && (_current_events % _model_refresh_period) == 0) + { + r::api_status status; + if (_rl->refresh_model(&status) != err::success) + { + std::cout << status.get_error_msg() << std::endl; + continue; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(_delay)); } @@ -163,6 +176,18 @@ int rl_sim::multistep_loop() continue; } + // refresh model every _model_refresh_period events + // Treat each episode as a single event + if (_model_refresh_period != 0 && (_current_events / episode_length) % _model_refresh_period == 0) + { + r::api_status status; + if (_rl->refresh_model(&status) != err::success) + { + std::cout << status.get_error_msg() << std::endl; + continue; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(_delay)); } return 0; @@ -203,6 +228,17 @@ int rl_sim::ca_loop() << stats.get_stats(joint.id(), chosen_action) << std::endl; } + // refresh model every _model_refresh_period events + if (_model_refresh_period != 0 && _current_events % _model_refresh_period == 0) + { + r::api_status status; + if (_rl->refresh_model(&status) != err::success) + { + std::cout << status.get_error_msg() << std::endl; + continue; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(_delay)); } return 0; @@ -257,6 +293,17 @@ int rl_sim::ccb_loop() index++; } + // refresh model every _model_refresh_period events + if (_model_refresh_period != 0 && _current_events % _model_refresh_period == 0) + { + r::api_status status; + if (_rl->refresh_model(&status) != err::success) + { + std::cout << status.get_error_msg() << std::endl; + continue; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(_delay)); } @@ -327,6 +374,17 @@ int rl_sim::slates_loop() continue; } + // refresh model every _model_refresh_period events + if (_model_refresh_period != 0 && _current_events % _model_refresh_period == 0) + { + r::api_status status; + if (_rl->refresh_model(&status) != err::success) + { + std::cout << status.get_error_msg() << std::endl; + continue; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(_delay)); } @@ -536,7 +594,7 @@ std::string rl_sim::create_context_json(const std::string& cntxt, const std::str std::string rl_sim::create_event_id() { - if (_num_events > 0 && ++_current_events >= _num_events) { _run_loop = false; } + if (++_current_events >= _num_events && _num_events > 0) { _run_loop = false; } if (_random_ids) { return boost::uuids::to_string(boost::uuids::random_generator()()); } @@ -557,6 +615,7 @@ rl_sim::rl_sim(const boost::program_options::variables_map& vm) : _options(vm), _delay = _options["delay"].as(); _quiet = _options["quiet"].as(); _random_ids = _options["random_ids"].as(); + _model_refresh_period = _options["refresh_model_period"].as(); } std::string get_dist_str(const reinforcement_learning::ranking_response& response) diff --git a/examples/rl_sim_cpp/rl_sim.h b/examples/rl_sim_cpp/rl_sim.h index 145713db3..861cd9c90 100644 --- a/examples/rl_sim_cpp/rl_sim.h +++ b/examples/rl_sim_cpp/rl_sim.h @@ -177,4 +177,5 @@ class rl_sim int64_t _delay = 2000; bool _quiet = false; bool _random_ids = true; + uint64_t _model_refresh_period = 0; }; diff --git a/examples/rl_sim_cpp/simulation_stats.h b/examples/rl_sim_cpp/simulation_stats.h index 237710d3f..14a1fd5f6 100644 --- a/examples/rl_sim_cpp/simulation_stats.h +++ b/examples/rl_sim_cpp/simulation_stats.h @@ -17,6 +17,7 @@ class simulation_stats auto& item_count = _item_stats[id]; ++item_count; ++_total_events; + _total_reward += outcome; } std::string get_stats(const std::string& id, T chosen_action) @@ -29,8 +30,11 @@ class simulation_stats int count() const { return _total_events; } + float get_ctr() const { return _total_reward / _total_events; } + private: std::map, std::pair> _action_stats; std::map _item_stats; int _total_events = 0; + float _total_reward = 0.f; }; \ No newline at end of file From ab01beedaea658aa8165bbc5b744a2862c96f548 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 24 Jan 2023 09:29:56 -0500 Subject: [PATCH 12/16] add schema for model update (#545) --- rlclientlib/schema/v2/Metadata.fbs | 4 ++-- rlclientlib/schema/v2/ModelUpdateEvent.fbs | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 rlclientlib/schema/v2/ModelUpdateEvent.fbs diff --git a/rlclientlib/schema/v2/Metadata.fbs b/rlclientlib/schema/v2/Metadata.fbs index 095caff49..d922f462f 100644 --- a/rlclientlib/schema/v2/Metadata.fbs +++ b/rlclientlib/schema/v2/Metadata.fbs @@ -1,6 +1,6 @@ namespace reinforcement_learning.messages.flatbuff.v2; -enum PayloadType : ubyte { CB, CCB, Slates, Outcome, CA, DedupInfo, MultiStep, Episode } +enum PayloadType : ubyte { CB, CCB, Slates, Outcome, CA, DedupInfo, MultiStep, Episode, ModelUpdate } enum EventEncoding: ubyte { Identity, Zstd } struct TimeStamp { @@ -15,7 +15,7 @@ struct TimeStamp { table Metadata { id:string; - client_time_utc:TimeStamp; + client_time_utc:TimeStamp; app_id:string; payload_type:PayloadType; pass_probability:float; // Probability of event surviving throttling operation diff --git a/rlclientlib/schema/v2/ModelUpdateEvent.fbs b/rlclientlib/schema/v2/ModelUpdateEvent.fbs new file mode 100644 index 000000000..f5df2270c --- /dev/null +++ b/rlclientlib/schema/v2/ModelUpdateEvent.fbs @@ -0,0 +1,10 @@ +namespace reinforcement_learning.messages.flatbuff.v2; + +table ModelUpdate { + // VW ModelDelta + delta: [ubyte]; + iteration_id: string; + client_id: string; +} + +root_type ModelUpdate; From 05f6d8265be0c12fb646482428726df956f0feab Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Fri, 10 Feb 2023 13:49:36 -0500 Subject: [PATCH 13/16] Change name ModelUpdate -> ModelUpdateEvent in schema (#564) --- rlclientlib/schema/v2/ModelUpdateEvent.fbs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rlclientlib/schema/v2/ModelUpdateEvent.fbs b/rlclientlib/schema/v2/ModelUpdateEvent.fbs index f5df2270c..468311ef4 100644 --- a/rlclientlib/schema/v2/ModelUpdateEvent.fbs +++ b/rlclientlib/schema/v2/ModelUpdateEvent.fbs @@ -1,10 +1,10 @@ -namespace reinforcement_learning.messages.flatbuff.v2; +namespace reinforcement_learning.messages.flatbuff.v2; -table ModelUpdate { +table ModelUpdateEvent { // VW ModelDelta delta: [ubyte]; iteration_id: string; client_id: string; } -root_type ModelUpdate; +root_type ModelUpdateEvent; From 9f4ff7d4fd9f0b10a0f4bf015b45b3b21d16cede Mon Sep 17 00:00:00 2001 From: Byron Xu Date: Mon, 13 Feb 2023 14:11:37 -0500 Subject: [PATCH 14/16] Update order of steps in local loop controller (#563) New order: Get model from federated client If new model received, train it on events generated with old model If new model received, upload delta to client Return latest model data for local inference Old order: Train model on local events If new model received on previous call, generate a delta and upload to client Ask client for updated model Return latest model data for local inference Also merge changes from master branch --- .github/workflows/build_nuget.yml | 35 +-- .github/workflows/daily_integration.yml | 7 - benchmarks/CMakeLists.txt | 5 +- benchmarks/benchmark_cb_v2.cc | 2 +- benchmarks/benchmark_ccb.cc | 14 +- ...nchmarks_common.cc => benchmark_common.cc} | 8 +- ...benchmarks_common.h => benchmark_common.h} | 0 benchmarks/benchmark_init.cc | 95 +++++++ benchmarks/models/cb_explore_adf_half.m | Bin 0 -> 2107681 bytes benchmarks/models/cb_explore_adf_large.m | Bin 0 -> 4194577 bytes benchmarks/models/cb_explore_adf_small.m | Bin 0 -> 21121 bytes bindings/cs/rl.net.cli.test/UnicodeTest.cs | 164 +++++++++-- bindings/cs/rl.net.native/CMakeLists.txt | 2 + .../cs/rl.net.native/rl.net.episode_state.cc | 20 ++ .../cs/rl.net.native/rl.net.episode_state.h | 19 ++ .../rl.net.native/rl.net.factory_context.cc | 4 +- .../cs/rl.net.native/rl.net.live_model.cc | 35 ++- bindings/cs/rl.net.native/rl.net.live_model.h | 9 + .../cs/rl.net.native/rl.net.native.vcxproj | 2 + bindings/cs/rl.net/EpisodeState.cs | 92 ++++++ bindings/cs/rl.net/LiveModel.cs | 261 +++++++++++++----- .../override_interface/override_interface.cc | 4 +- .../nonstd/{string_view.h => string_view.hpp} | 0 ext_libs/vowpal_wabbit | 2 +- .../event_processors/joined_event.h | 23 +- .../event_processors/typed_events.h | 3 +- external_parser/joiners/i_joiner.h | 1 - .../joiners/multistep_example_joiner.h | 1 - external_parser/parse_example_external.cc | 4 +- include/configuration.h | 14 +- include/constants.h | 5 + include/model_mgmt.h | 20 +- include/object_factory.h | 12 +- include/rl_string_view.h | 2 +- nuget/CMakeLists.txt | 2 +- nuget/rlclientlib.nuspec.in | 6 +- nuget/rlclientlib.targets.in | 8 +- nuget/test/test-v142.vcxproj | 95 ------- ...est-v141.vcxproj => test_rl_nuget.vcxproj} | 22 +- rlclientlib/azure_factories.cc | 96 ++++--- rlclientlib/dedup.cc | 47 ++-- rlclientlib/dedup.h | 8 +- rlclientlib/dedup_internals.h | 3 +- .../extensions/onnx/src/onnx_extension.cc | 5 +- rlclientlib/factory_resolver.cc | 74 ++--- .../federation/local_loop_controller.cc | 25 +- rlclientlib/federation/vw_trainable_model.cc | 6 +- rlclientlib/generic_event.cc | 2 +- rlclientlib/live_model_impl.cc | 186 ++++--------- rlclientlib/live_model_impl.h | 5 +- rlclientlib/logger/async_batcher.h | 9 +- rlclientlib/logger/event_logger.h | 40 +-- rlclientlib/logger/logger_extensions.cc | 26 +- rlclientlib/logger/logger_extensions.h | 8 +- rlclientlib/logger/logger_facade.cc | 74 ++--- rlclientlib/logger/logger_facade.h | 20 +- rlclientlib/logger/preamble_sender.cc | 2 +- rlclientlib/logger/preamble_sender.h | 2 +- rlclientlib/model_mgmt/model_mgmt.cc | 64 +---- rlclientlib/ranking_event.cc | 2 +- rlclientlib/utility/configuration.cc | 56 +--- rlclientlib/utility/object_pool.h | 74 ++--- rlclientlib/utility/versioned_object_pool.h | 31 +-- rlclientlib/vw_model/safe_vw.cc | 4 +- setup.py | 1 - test_tools/example_gen/example_gen.cc | 159 +++++++++-- test_tools/onnx_pytorch/common/parser.py | 2 + test_tools/reproduce_model.py | 1 - test_tools/sender_test/test_loop.cc | 4 +- unit_test/async_batcher_test.cc | 200 ++++++++------ unit_test/configuration_test.cc | 13 +- unit_test/data_buffer_test.cc | 1 - unit_test/data_callback_test.cc | 1 - unit_test/dedup_test.cc | 1 - unit_test/err_callback_test.cc | 1 - unit_test/event_queue_test.cc | 1 - unit_test/explore_test.cc | 1 - .../onnx/mnist_data/data_generator.py | 16 +- unit_test/extensions/onnx/mock_helpers.cc | 12 +- unit_test/factory_test.cc | 16 +- unit_test/fb_serializer_test.cc | 1 - unit_test/file_logger_test.cc | 1 - unit_test/header_auth_test.cc | 1 - unit_test/http_client_test.cc | 1 - unit_test/http_transport_client_test.cc | 1 - unit_test/json_context_parse_test.cc | 1 - unit_test/json_serializer_test.cc | 1 - unit_test/learning_mode_test.cc | 1 - unit_test/live_model_test.cc | 91 +++++- unit_test/local_loop_end_to_end.cc | 8 +- unit_test/mock_util.cc | 33 ++- unit_test/model_mgmt_test.cc | 35 +-- unit_test/moving_queue_test.cc | 1 - .../multi_slot_response_detailed_test.cc | 1 - unit_test/multistep_test.cc | 1 - unit_test/object_pool_test.cc | 1 - unit_test/payload_serializer_test.cc | 1 - unit_test/preamble_test.cc | 8 +- unit_test/ranking_response_test.cc | 1 - unit_test/safe_vw_test.cc | 1 - unit_test/serializer.cc | 1 - unit_test/sleeper_test.cc | 1 - unit_test/slot_ranking_test.cc | 1 - unit_test/status_builder_test.cc | 1 - unit_test/str_util_test.cc | 1 - unit_test/time_tests.cc | 1 - unit_test/trace_logger_test.cc | 13 +- unit_test/watchdog_test.cc | 1 - 108 files changed, 1514 insertions(+), 993 deletions(-) rename benchmarks/{benchmarks_common.cc => benchmark_common.cc} (95%) rename benchmarks/{benchmarks_common.h => benchmark_common.h} (100%) create mode 100644 benchmarks/benchmark_init.cc create mode 100644 benchmarks/models/cb_explore_adf_half.m create mode 100644 benchmarks/models/cb_explore_adf_large.m create mode 100644 benchmarks/models/cb_explore_adf_small.m create mode 100644 bindings/cs/rl.net.native/rl.net.episode_state.cc create mode 100644 bindings/cs/rl.net.native/rl.net.episode_state.h create mode 100644 bindings/cs/rl.net/EpisodeState.cs rename ext_libs/string-view-lite/nonstd/{string_view.h => string_view.hpp} (100%) delete mode 100644 nuget/test/test-v142.vcxproj rename nuget/test/{test-v141.vcxproj => test_rl_nuget.vcxproj} (73%) diff --git a/.github/workflows/build_nuget.yml b/.github/workflows/build_nuget.yml index 6a9940fb7..d84215623 100644 --- a/.github/workflows/build_nuget.yml +++ b/.github/workflows/build_nuget.yml @@ -17,11 +17,12 @@ concurrency: jobs: build_nuget_windows: - name: nuget.${{ matrix.toolset }} + name: nuget.${{ matrix.toolset }}-${{ matrix.build_type }} runs-on: ${{matrix.os}} strategy: matrix: os: ["windows-2019"] + build_type: ["Debug", "Release"] toolset: ["v141", "v142"] steps: @@ -74,23 +75,16 @@ jobs: -DVCPKG_MANIFEST_MODE=Off -DRL_BUILD_NUGET=On -DRL_NUGET_PACKAGE_NAME=RLClientLibNativeStatic + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DRL_NUGET_PACKAGE_VERSION="${{ steps.get_version.outputs.version }}" -DNATIVE_NUGET_PLATFORM_TAG=x64 -DCMAKE_TOOLCHAIN_FILE="${env:VCPKG_INSTALLATION_ROOT}\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -Drlclientlib_BUILD_DOTNET=Off - - name: Compile - debug build - run: cmake --build ./build --config Debug -v - - name: Create Nuget package - debug build - # this copies over files to nuget_staging and creates a package with only debug libraries - run: cmake --install ./build --config Debug --prefix ./nuget_staging -v - - name: Clean - run: cmake --build ./build --target clean - - name: Compile - release build - run: cmake --build ./build --config Release -v - - name: Create Nuget package - release build - # this overwrites the previous package with one containing both debug and release libraries - run: cmake --install ./build --config Release --prefix ./nuget_staging -v + - name: Compile + run: cmake --build ./build --config ${{ matrix.build_type }} -v + - name: Create Nuget package + run: cmake --install ./build --config ${{ matrix.build_type }} --prefix ./nuget_staging -v # Upload the package - name: Get file name @@ -105,16 +99,17 @@ jobs: - name: Upload package uses: actions/upload-artifact@v1 with: - name: RLClientLibNativeStatic-${{ matrix.toolset }}-x64.${{ steps.get_version.outputs.version }}.nupkg + name: RLClientLibNativeStatic-${{ matrix.toolset }}-${{ matrix.build_type }}-x64.${{ steps.get_version.outputs.version }}.nupkg path: nuget_staging/${{ steps.nuget_name.outputs.NugetFileName }} test_nuget_windows: needs: [build_nuget_windows] - name: nuget-test.${{ matrix.toolset }} + name: nuget-test.${{ matrix.toolset }}-${{ matrix.build_type }} runs-on: ${{matrix.os}} strategy: matrix: os: ["windows-2019"] + build_type: ["Debug", "Release"] toolset: ["v141", "v142"] steps: - uses: actions/checkout@v2 @@ -139,7 +134,7 @@ jobs: # Download and install nuget - uses: actions/download-artifact@v1 with: - name: RLClientLibNativeStatic-${{ matrix.toolset }}-x64.${{ steps.get_version.outputs.version }}.nupkg + name: RLClientLibNativeStatic-${{ matrix.toolset }}-${{ matrix.build_type }}-x64.${{ steps.get_version.outputs.version }}.nupkg path: downloaded_nugets - name: List downloaded files run: ls downloaded_nugets @@ -151,15 +146,15 @@ jobs: -Version "${{ steps.get_version.outputs.version }}" -Verbosity detailed -NonInteractive - RLClientLibNativeStatic-${{ matrix.toolset }}-x64 + RLClientLibNativeStatic-${{ matrix.toolset }}-${{ matrix.build_type }}-x64 - name: Rename package install directory to omit version number run: | cd nuget\test\packages - mv * RLClientLibNativeStatic-${{ matrix.toolset }}-x64 + mv * RLClientLibNativeStatic-${{ matrix.toolset }}-${{ matrix.build_type }}-x64 # Compile and run - name: Build test run: | cd nuget\test - msbuild test-${{ matrix.toolset }}.vcxproj -t:rebuild "-property:Configuration=Release;Platform=x64" - .\bin\x64\Release\test-${{matrix.toolset}}.exe + msbuild test_rl_nuget.vcxproj -t:rebuild "-property:Configuration=${{ matrix.build_type }};Platform=x64;PlatformToolset=${{ matrix.toolset }}" + .\bin\x64\${{ matrix.build_type }}\test_rl_nuget.exe diff --git a/.github/workflows/daily_integration.yml b/.github/workflows/daily_integration.yml index e4caa99cf..fe1845715 100644 --- a/.github/workflows/daily_integration.yml +++ b/.github/workflows/daily_integration.yml @@ -1,13 +1,6 @@ name: Integration with latest VW on: - push: - branches: - - master - - 'releases/**' - pull_request: - branches: - - '*' schedule: - cron: "0 8 * * *" diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 7e0dbddf9..f8118aa1b 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,8 +1,9 @@ set(all_sources - benchmark_main.cc - benchmarks_common.cc benchmark_cb_v2.cc benchmark_ccb.cc + benchmark_common.cc + benchmark_init.cc + benchmark_main.cc ) add_executable(rl_benchmarks diff --git a/benchmarks/benchmark_cb_v2.cc b/benchmarks/benchmark_cb_v2.cc index d3993c417..ba50c7e92 100644 --- a/benchmarks/benchmark_cb_v2.cc +++ b/benchmarks/benchmark_cb_v2.cc @@ -1,5 +1,5 @@ #include "api_status.h" -#include "benchmarks_common.h" +#include "benchmark_common.h" #include "config_utility.h" #include "constants.h" #include "err_constants.h" diff --git a/benchmarks/benchmark_ccb.cc b/benchmarks/benchmark_ccb.cc index 50cbc6053..f430ff4b5 100644 --- a/benchmarks/benchmark_ccb.cc +++ b/benchmarks/benchmark_ccb.cc @@ -1,5 +1,5 @@ #include "api_status.h" -#include "benchmarks_common.h" +#include "benchmark_common.h" #include "config_utility.h" #include "constants.h" #include "err_constants.h" @@ -65,8 +65,9 @@ static void bench_ccb(benchmark::State& state, ExtraArgs&&... extra_args) config.set(r::name::INTERACTION_USE_DEDUP, dedup ? "true" : "false"); config.set("queue.mode", "BLOCK"); - std::string model_output_filename = std::tmpnam(nullptr); - config.set(r::name::MODEL_FILE_NAME, model_output_filename.c_str()); + char templ[] = "/tmp/fileXXXXXX"; + mkstemp(templ); + config.set(r::name::MODEL_FILE_NAME, templ); // train a VW model and save to file { @@ -80,7 +81,8 @@ static void bench_ccb(benchmark::State& state, ExtraArgs&&... extra_args) { VW::multi_ex ex; ex.push_back(VW::new_unused_example(*vw)); - line_to_examples_json(vw.get(), str.c_str(), str.size(), ex); + VW::string_view sv = {str.c_str(), str.size()}; + line_to_examples_json(vw.get(), sv, ex); examples_vec.push_back(ex); } @@ -95,7 +97,7 @@ static void bench_ccb(benchmark::State& state, ExtraArgs&&... extra_args) auto backing_buffer = std::make_shared>(); io_buffer.add_file(VW::io::create_vector_writer(backing_buffer)); VW::save_predictor(*vw, io_buffer); - std::fstream out_file(model_output_filename, std::ios::out | std::ios::binary); + std::fstream out_file(templ, std::ios::out | std::ios::binary); out_file.write(backing_buffer->data(), backing_buffer->size()); } @@ -121,7 +123,7 @@ static void bench_ccb(benchmark::State& state, ExtraArgs&&... extra_args) } // delete model file - std::remove(model_output_filename.c_str()); + std::remove(templ); } BENCHMARK_CAPTURE(bench_ccb, ccb_adf_diff_char_interactions_predict, diff --git a/benchmarks/benchmarks_common.cc b/benchmarks/benchmark_common.cc similarity index 95% rename from benchmarks/benchmarks_common.cc rename to benchmarks/benchmark_common.cc index 9bb4de7d0..b3e83cbe5 100644 --- a/benchmarks/benchmarks_common.cc +++ b/benchmarks/benchmark_common.cc @@ -1,6 +1,6 @@ -#include "benchmarks_common.h" +#include "benchmark_common.h" -#include "vw/core/rand48.h" +#include "vw/common/random.h" #include @@ -52,11 +52,11 @@ std::string make_actions_vector(int count, const std::vector& actio } } // namespace -prng::prng(uint64_t initial_seed) : val(merand48(initial_seed)) {} +prng::prng(uint64_t initial_seed) : val(VW::details::merand48(initial_seed)) {} uint64_t prng::next_uint() { - merand48(val); + VW::details::merand48(val); return val; } diff --git a/benchmarks/benchmarks_common.h b/benchmarks/benchmark_common.h similarity index 100% rename from benchmarks/benchmarks_common.h rename to benchmarks/benchmark_common.h diff --git a/benchmarks/benchmark_init.cc b/benchmarks/benchmark_init.cc new file mode 100644 index 000000000..56a2037cc --- /dev/null +++ b/benchmarks/benchmark_init.cc @@ -0,0 +1,95 @@ +#include "benchmark_common.h" +#include "config_utility.h" +#include "constants.h" +#include "err_constants.h" +#include "live_model.h" +#include "model_mgmt.h" + +#include + +#include +#include + +namespace r = reinforcement_learning; +namespace u = reinforcement_learning::utility; +namespace err = reinforcement_learning::error_code; +namespace cfg = reinforcement_learning::utility::config; + +const auto JSON_CFG = R"( +{ + "ApplicationID": "rnc-123456-a", + "IsExplorationEnabled": true, + "InitialExplorationEpsilon": 1.0 +} +)"; + +template +static void bench_init(benchmark::State& state, ExtraArgs&&... extra_args) +{ + int res[sizeof...(extra_args)] = {extra_args...}; + auto load_model = res[0]; + auto model_id = res[1]; + + std::ostringstream model_path; + model_path << "benchmarks/models/"; + // Note: models generated using '--cb_explore_adf -b 18' + switch (model_id) + { + case 0: + model_path << "cb_explore_adf_small.m"; + break; + case 1: + model_path << "cb_explore_adf_half.m"; + break; + case 2: + model_path << "cb_explore_adf_large.m"; + break; + default: + std::cout << "invalid model id" << std::endl; + break; + } + + u::configuration config; + cfg::create_from_json(JSON_CFG, config); + config.set(r::name::PROTOCOL_VERSION, "2"); + config.set(r::name::EH_TEST, "true"); + config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); + config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); + config.set(r::name::INTERACTION_FILE_NAME, r::DEV_NULL); + config.set(r::name::OBSERVATION_FILE_NAME, r::DEV_NULL); + config.set(r::name::MODEL_BACKGROUND_REFRESH, "false"); + config.set(r::name::VW_POOL_INIT_SIZE, "1"); + config.set(r::name::INTERACTION_USE_COMPRESSION, "false"); + config.set(r::name::INTERACTION_USE_DEDUP, "false"); + config.set("queue.mode", "BLOCK"); + + if (load_model) + { + config.set(r::name::MODEL_SRC, "FILE_MODEL_DATA"); + config.set(r::name::MODEL_FILE_NAME, model_path.str().c_str()); + } + else { config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); } + + for (auto _ : state) + { + r::api_status status; + r::live_model model(config); + model.init(&status); + if (status.get_error_code() != err::success) + { + std::cout << "there was an error so something went wrong during " + "benchmarking: " + << status.get_error_msg() << std::endl; + } + benchmark::ClobberMemory(); + } +} + +// characteristics of the benchmark examples that will be generated are: +// x load model (on/off) +// x model path + +BENCHMARK_CAPTURE(bench_init, no_model, false, 0)->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_init, small_model, true, 0)->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_init, half_model, true, 1)->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(bench_init, large_model, true, 2)->Unit(benchmark::kMillisecond); diff --git a/benchmarks/models/cb_explore_adf_half.m b/benchmarks/models/cb_explore_adf_half.m new file mode 100644 index 0000000000000000000000000000000000000000..2347869bb3cfd1cefe07ecf3c71963b8dc4a11e7 GIT binary patch literal 2107681 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řAZtPz?=9XeuTI1HhzqE z@GjoNPw-Rx3_r&&@IF4khxjFq`Z#+4N8=bAi;v(qd=wwU$8kJ9ffH~dPQuAJ1)s#J zI1Q)cQ}{H_z?nD;XX6~4i$gfT`M3a|#piG#K94WpB3z71a49as<+uW0#Fh9GuEN#0 z2G`;`T#p;@W!#9Ha5HYft@sLV!&h-TzJ@z+C+@=CxCdXyy|@qe;{iN~hww1Ifk*Ht z9>e4KCZ52PcnVMB89a;U@H}3?VSEcO;w8L{Z{robitpgN_#VEG*YE?pjyLcoeuy98 zExe5%;~l(<_wW<^6hFhy@e90<5AY#=iKDJ@|8X>q!Lj%Vj>AXsF?<}y;}bXmC*mZW zj8pJQoQl(Mdcr=7N5g~_&mOV zi*PY6!KJtim*Wb25m(|%xC&R}8eEI(a6N9omvJL*!p*n^x8f_f4PV9W_!{oOowy5k z;~sn+_u@X>j|cD|9>T-;1|Gqqcnpu@n|K0G;we0hzYy_<-d`Jj({F@Bf1UQ*!3=#C z&*6EzfW!C}Uc^gy8Q;b$copBlckw-ZAFtsDcpY!xP5cl)!drM7KgK(F7w_RG_$hvd zpW_#JA0OaD{1Qiff<1tvaSV>dM{pcIijU#rI3AzC2{;ia;bfeGPvTUZhSTvWd>Uuq zOq_+YaSqPKAspa*T!7EwbGQ(n#}{xBF2*Ie6qn(0T!Am*N_+`d;c8riYjGW}#|`*0 zZp2Nv8Mok8dBQ@H*bW zoA@DqgtzcEevEhUF5bgW@KgK@KgTcdK0d&Q_$7|&;Qr%i9D`%=5gdn);$!%GA`Y&6 zDEwEYTcHoWXFfPikH;r)0#3w9I2otllQ-_u+m#fCupq9>zED2p+{_cpTrv6L=C&;b}aBXYm}K#|t=&Z{bC} zgqQJcynqH)6F31U;v}4mQ}9Wgiqmj9K7~)?44jFxa5m1txj2La zoR16eS$qx`;`8_dF2cpQ1efA6T#hU7MO=w5;VN8>Yj7>D!}YiUU&f8N2{+>w+={Q@ zHhdMg<7>DBcj7MGjeGEQ+>85gKOVq?cnA;U8+Zhd;xRmqZ{i6&iKp;1p24$t4$tES z9LBfsB3{DF_%>d_tN0GSi|^t4cnv?me--iR&*y~SHSzYmjFo>0*6AB~6F;W8&V{j}!g5&T}d<-AQ@%RKzz==2sC*u@+ z5~t!coQ_Z7(>McX;w+qvb8s#W;Q;630(=&q!-e=fzJQBxF)qQSxD1!$3Vab);!C&+ zSK}I7i|cSbZorpuBW}XYxCOW3E4U3`#qIbS?!cY63wPrld>!}VKHQH7@E{(-!}tas z!J~K#kK>zo0#D*8JdJ1YES|&jcmap;Exd@A@G`!QSMVyngYV*d_řAZtPz?=9X zeuTI1HhzqE@GjoNPw-Rx3_r&&@IF4khxjFq`Xu)sN8=bAi;v(qd=wwU$8kJ9ffH~d zPQuAJ1)s#JI1Q)cQ}{H_z?nD;XX6~4i$gfT`S`~pPW`zzg%3WFoR?Gn^`L-$7N5g~ z_&mOVi*PY6!KJtim*Wb25m(|%xC&R}8eEI(a6N9omvJL*!p*n^x8f_f4PV9W_!{oO zowy5k;~sn+_u@X>j|cD|9>T-;1|Gqqcnpu@n|K0G;we0hXYeeZ!}E9nhw&}Eh?np( zzKvJ#D!zm7;(Pc$Uc(RYI^MvW_#u9Tx9~Q8jCb%Z-osDuQ~V4+$1m_cKEQ|gC64N1 z58!AVgJba#9EXqMWB53Z$0u+CPQ*z#8K>ZrI2EVibbJb*#u+#hXW?v|gL82R2RI)W z;IsG~F2v{Y1zd!SaS1NPWw;zy;ET8tU&2+m8rR@jT!-s%1HOzKaT9LFEw~k5!EN{| zZpYVf2kyjOxEuH2>$n&9;eI@T2k{Ud#y9W?9>rsL9N)wfcoI+HX*`2x@f@DV3pk8# z;YGZJm+@`9f>-e!d>7xt_wgEjfYCb`_#{rnX*eC9!l!Wt&csJ?5 zCftl$a4Wuo+wfJ~j<4Yk+=;tzH}1jLaWC$}{dfQm;vqbYZ{QI;ipTIczKJLBB%Z?4 zcm~hnIXsUSa2Vghi+Bky~A;{_bXx9}of!przJUcsyQ4!(=;;rn@eba_d-w@{il5=<_yyj_2lx=b#8JP>{m0Qb2FKzfI1V4h$MA6+k5AwPoQRWf zGETuKaVk#3>G%{rjWcj2&cfL^2j}7t4sbp$z-RF}T!_!(3%Cdu;}Tqo%Wyfaz!z~P zzJ#lAHLk(6xDMCj27DPe;wIdTTW~ABg4^&_+>Wo|4%~^ma5wJ3*KsfI!~J*w58@#_ zjBnr(Jc`HgIKGJ|@FbqX(|88Y;yFBz7jPKg!i#tbFXP*I1+U^e_%6PO@8dQ60I%Z> zyon#;M|cZwk@N@hE@8bh}h+pEU>)d}Fjbm^uK7!-$QG5&^$MN_C zPQZyc2`A$ed=jVPG@Oo4;nO$+XW}fJjdO4=4&eaj;{tpZpTmXtJidU7a4{~yrML{2 z;|hEcSK>>!3RmMAT#M^)J#N64aU*WR&A0`(;w!ifU&ZbC8t%ZIxC?jV9(*15;y&Du z2k;;s!o&Cm9>Jq{43FcRcmhx2DLjp5@GPFg^LPP=BmU%%{an!Wt9g0LU-^gN7JU&f z;bnXqui#aD2j9i_@O`|7AK-Pofj99({0ML1ZTuMT;9b0jpWvtX8Gepm;C+065AjPJ z^=sUJ9F1deEIxwc@KJmWAII_d1Wv$-I0+}?6nqk=;xwF&PvO%z183qaoQ-pEE)L-U z=i>r=7N5g~_&mOVi*PY6!KJtim*Wb25m(|%xC&R}8eEI(a6N9omvJL*!p*n^x8f_f z4PV9W_!{oOowy5k;~sn+_u@X>j|cD|9>T-;1|Gqqcnpu@n|K0G;we0hXYeeZ!}E9n zhw&}Eh?np(zKvJ#D!zm7;(Pc$Uc(RYI^MvW_#u9Tx9~Q8jCb%Z-osDuQ~V4+$1m_c zKEQ|gC64Ol{^MvIgJba#9EXqMWB53Z$0u+CPQ*z#8K>ZrI2EVibbJb*#u+#hXW?v| zgL82R2RI)W;IsG~F2v{Y1zd!SaS1NPWw;zy;ET8tU&2+m8rR@jT!-s%1HOzKaT9LF zEw~k5!EN{|ZpYVf2kyjOxEuH2>$n&9;eI@T2k{Ud#y9W?9>rsL9N)wfcoI+HX*`2x z@f@DV3pk8#;YGZJm+@`9f>-e!d>7xt_wgEjfY;iLE%K91w@37miv zaS~3(DflE##c4PlpTehc2F}D;I2-5STpYpy&c_A#EIx+|@p*g!7vW-Df=h83F2@!4 zBCf=ja22k`HMkbn;d88#y$8t?!|q$9}nO` zJcNhw4LpKJ@faS*H}M3X#8Y@0&)``+hv)GE4&z&R5ij9od>gOeReT5E#rN=iyoMj( zb-aN$@k9IwZ{cnH81LX+yoaCQr}!Cuj$hz?e1H$}OB~h5{m0Qb2FKzfI1V4h$MA6+ zk5AwPoQRWfGETuKaVk#3>G%{rjWcj2&cfL^2j}7t4sbp$z-RF}T!_!(3%Cdu;}Tqo z%Wyfaz!z~PzJ#lAHLk(6xDMCj27DPe;wIdTTW~ABg4^&_+>Wo|4%~^ma5wJ3*KsfI z!~J*w58@#_jBnr(Jc`HgIKGJ|@FbqX(|88Y;yFBz7jPKg!i#tbFXP*I1+U^e_%6PO z@8dQ60I%Z>yon#;M|cZwk@N@hE@8bh}h+pDYefCwadiCC)gbQB( zU3o22V?k8JcfR`7;8p+s*Q;?fj={0`2#&)?@iBZH$Kw+?0Vm=loQzZONt}w)a5_GP zPvZ=niL-Dv&cV4jgae$93-DQd4j1C{_yR7%#kd5Q;xb&0EAT~Ji7(+QT#ajREw01$ zxB*|rjkpOn;}+bCui!R(6}RJSxC3|MF5HcK@O9ja`*1%Vz=L=Q591qn1drk|JdSVT z2|S6X@HC#mvv>~A;{_bXx9}of!przJUcsyQ4!(=;;rnj9yj32xDhwuX54~X@fF;Lui|!m4R_#9+=aVw55A6j zaUbr-19%V*;bD9OkKj=}hR5+uJb@?i6rRR2coxs$dAxwb_!eHoOL!UI#w&Og-@$kB zJ$xUp;RkpfZ{SV*5I@3OcpE>)J9roG;V1Yheukgp7kD2Z;6wZpNBwv1KaR#RI2Iql zarh`chL7WTd;%xnM4W_^aSA?(Q*jzj$EWaVoPjfO7S6^wI2VU-fb($yK8w%cLVO-y zz(u$im*7%dhRbmUzKARFC0vE8aSg7;b+{fk;LErXH{oX7f?M$w+=j2>c6<$Y;7;6y zyKxV`j(c$*?#Bao5D(#Dd;^c*Q9Opn@l8B|C-D@X#xr;p&*6EzfW!C}Uc^gy8Q;b$ zcopBlckw-ZAFtsDcpY!xP5cl)!drM7KgK(F7w_RG_$hvdpW_#JA0OaD{1QhEaQ|^M zj={0`2#&)?@iBZH$Kw+?0Vm=loQzZONt}w)a5_GPPvZ=niL-Dv&cV4jgae$93-DQd z4j1C{_yR7%#kd5Q;xb&0EAT~Ji7(+QT#ajREw01$xB*|rjkpOn;}+bCui!R(6}RJS zxC3|MF5HcK@O9ja`*1%Vz=L=Q591qn1drk|JdSVT2|S6X@HC#mvv>~A;{_bXx9}of z!przJUcsyXAD49;3y$N*d3`FGq|cd|WHQqy$xLQ4^Yb$^nas>2Gbd+~$w@MqnapG+ zNpg}TIZ4hr=Ojszw+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0i zPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6 zU*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpI9~>}A|8XD=!ofHMhvHv242R*ZsI1b0--#7s$;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$= zDqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2 zDLjp5@GSnTfB5hH9|POPf93!F({*<2pXb=;@d94NOL!Tt;8nba*YO74#9Me9@8Dg$ zhxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KNcC zn128V;vgK1LvSemg~M<-j=+&P3Pj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU z@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@ z2Taj_9EgK(Fb=_?_!kbt;Wz?E;wT)AV{j~v!}0hxPQZyc2`A$eoQl(MI?lkEI16Xv z9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV z9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi z9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpI z9~|(X(0?3=gK#ho!J+sU4#VL%0!QK~9F1deERMtR_%}|#i8u)-;}o2V({MV@z?nD; zXX6~4i}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}Y zcjF%1i~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mr zZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXF zf8!qSJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|M zF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?Z zExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39 zFZ_*vaKL{`|8XD=!ofHMhvHv242R*ZsI1b0--#7s$;v}4mQ*bIy!|6B! zXW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{O zcj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|Qkh zZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5 zf8sCvjel^!4E@J}I0y&h5FCns;V>MIBXA^+!qGSe$Kp5~kALF?oQRWfGETv%I1Q)c z44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cSGj74HxDB`C z4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_ZGG4)}cnz=P z4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~ z5B!P0@HhU!0sk5O$ALHq2jdVNihtoS9F8M!B#y$-I0nb!I2@0E;{=?DlW;Ol!KpY6 zr{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Ir zx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-); zuj388iMQ}J-od+g5AWjxe29zo#N8=bAi{o%S{*4oGB2L1|I0dKT zG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3 zHr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QG zHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb> zH~fx2@F)Jl-}nay{8jpo191=z#vwQq|H5H797o_t9EGEC435QdI3EAT2{;ia;bfeG zQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=| zTX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;br_c z{%}v-Ke;{nFJ}Cyuje*<r^e>iKjCA)bn zD!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0h zXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZO zZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdHbz<*BvaUc%D!8inm;$JunhvNtwiKB2d zj=`}w4#(r)H~}Z(B%F*>a4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WH zT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8 zJdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Q ze2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-e{jG&{l|eg2nXX39EyM8FdU8}a3qex z(KrUj;y4_Sf8zw4h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4f zt8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?MIBXA^+!qGSe$Kp5~kALF?oQRWf zGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cS zGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_Z zGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0M zGk(FZ_zl0~5B!P0@HhU!0SojW2jUZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp z!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7 z!q4~xzv4Iijz91x{=(n*2M7F@^dAS}ARLTCa47zT!*Do`z>zo#N8=bAi{o%S{*4oG zB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-Y zBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zBYwiq_yxb>H~fx2@F)Jl-}nayEYg1*h=Xu24#A=L7Y@VWI08rFC>)Jra4e3)@%T4R zz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18 zz>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpU zz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f- zz>oL|KjRntir?@%{=lF33xDGu9ProZKMuq}I2ecEQ2Yyr;cy&*BXJat#xXb+$KiPV z8zz;6hx4i*X4q#bvl0SKvxqg{yH5uElk@ z9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%` z9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR= z9zWnm{DhzJ3x36K_#J=XPyB_y@edAIqW?G$2jO5Gf@fE(tH~1Fc z;d}gmAMq1@#xM94zu|ZMfj{vV{>DEz;IGqv9EgK(Fb=_?_!kbt;Wz?E;wT)AV{j~v z!}0hxPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m zi|_C~e!!3T2|wc({EFZ3JO03*_zQpI9~`hu|8XD=!ofHMhvHv242R*Zs zI1b0--#7s$;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|g za4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5 z@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjel^!-=P0E5C`F49D+mfFC2!$aRiRUQ8*gM z;8+}o^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFanT?C2d@8}?3w>-^iTiv zpZ}3R7qge(Qe1}1aRsi#Rk#}0;96XV>v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(| z03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;d75a|@ zaS#s1AvhHO!eKZZN8m^tg`@xQKm6bRSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G z1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00T~}9{xDhwu zX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q* zWxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$h zXZ(U+@f&`}ANUi0;cxtd16Juj4#Yt?7>D3c{0oQSa2$anaTJcmF*p{-;duNTC*VY! zgp+X!PQ__B9cSQ7oQ1P-4$j4SI3E|_LR^H4aS1NPWw;zy;7VMDt8opk#dWwIH{eFx zgqv{-ZpCf59e3bP+=aVw5AMZ%xE~MTK|F+q@dzHpV|W}-;7L4%r|}G)#dCNbFW^PI zgqQIOUd3y89dF=GyoI;%4&KFkcpo3&Lwtmf@d-Y~XZRdn;7fdkukj83KmYLGx=7jn zi~r0&@u#`^^XyypJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@2mCGm0UU^fa4-(Rq4*aL z!{ImrN8%_Pjbm^uj>GZzH%`EbI0+}?6r76Fa5~PwnK%n);~boe^Kd>cz=gO77vmCK zipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ zipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wD!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn z@F*U`<9Gs3;we0hXYefktAF?}y#A%_zY<>ar+@8V{R@AdW1q(hco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtd1O6-i0UU^fa4-(Rq4*aL!{ImrN8%_Pjbm^uj>GZzH%`EbI0+}?6r76F za5~PwnK%n);~boe^Kd>cz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4 za69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq z@H*bWn|KRv;~l(<_wYVGz=!wD!}YiUH{vGTj9YLk zZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAPkM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw? z;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zr^8}uIs;vgK1LvSemg~M<- zj=+&P3Pj|cD|9>T+T1drk| zJdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwF ze2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@2mCkm9|z(f9E?M7DE@`R za5#>@kvIxR;}{%^<8VCwjT3MpPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb z;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F z;exBt?=klpw4?`8Lg&HeeD{Q_U&D}0S_@GZW>_xJ%n;wSu!U+^n_!|(V5f8sCv zjel^!CjG~OI0y&h5FCns;V>MIBXA^+!qGSe$Kp5~kALF?oQRWfGETv%I1Q)c44jFx za5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cSGj74HxDB`C4%~^m za5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_ZGG4)}cnz=P4ZMlB z@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~5B!P0 z@HhU!0sk%i$ALHq2jdVNihtoS9F8M!B#y$-I0nb!I2@0E;{=?DlW;Ol!KpY6r{fHq ziL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}n ziMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388 ziMQ}J-od+g5AWjxe29zo#N8=bAi{o%S{*4oGB2L1|I0dKTG@Onz za3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Ro za3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D z@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2 z@F)Jl-}nay{CD&p2jUZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!prz?{Nb;^ zm%Lpv@-P4Cy6x}(^9uVaUc>8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZO zZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdHbz&8H?4#Yt?7>D3c{0oQSa2$anaTJcm zF*p{-;duNTC*VY!gp+X!PQ__B9cSQ7oQ1P-4$j4SI3E|_LR^H4aS1NPWw;zy;7VMD zt8opk#dWwIH{eFxgqv{-ZpCf59e3bP+=aVw5AMZ%xE~MTK|F+q@dzHpV|W}-;7L4% zr|}G)#dCNbFW^PIgqQIOUd3y89dF=GyoI;%4&KFkcpo3&Lwtmf@d-Y~XZRdn;7fdk zukj7O#dr7~Kj26FgrD&Xe#LM29e?0Y{Dr^q4-WY6=|2v{K{yzP;86Svhv9G>fg^Dg zj>a)K7RTXu{2M3WM4W_^aSBewX*eBc;7pu_vvCg2#d$a%7vMr%go|+rF2!ZI99Q5< zT!pJ~4X(v?xE?p)M%;v(aSLw6ZMYqG;7;6yyKxWh#eKLR58y#Ogop769>rsL98cg$ zJcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr| ze1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr+a*rES85C`F49D+mfFC2!$aRiRU zQ8*gM;8+}o^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{Go zD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~H zC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X} zFYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaSIN*Pv|2Pl_;b0tsL-8*hhQo0L zj>J(o8pq&R9EaoaZ=8S=aS~3(DL56U;dGpVGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0 zT!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1CJ8>88#yz+f_u+m#fCupq9>ybh6p!I? zJb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9 ze1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y>bp`PQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M z%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t z&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=fd7&H<3JpQgK-EB#lLVE z4#yEV5=Y@^9D`$V9FE7oaRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2w)*AI|*G z{@?%ZU-@@4v;TwjzCRbS7vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6 zowy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bW zn|KRv;~l(<_wYVGz=!wT~}9{xDhwuX54~XaT{*O z9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtd1O6xaj{|WK4#puk6#v3uI2=ddNF0TuaSV>daX23T#tAqPC*fqAf>UuC zPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-ex zUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}Afg^Dgj>a)K7RTXu{2M3WM4W_^aSBew zX*eBc;7pu_vvCg2#d$a%7vMr%go|+rF2!ZI99Q5rsL98cg$JcXz644%bvcpfj{MZAQU@d{qW zYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*% zZ}=U5;7|O8zwr+a_}la!2jUZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFC zui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~x zzv4Iijz91x{=(n*2L~L`e;kN|a4-(Rq4*aL!{ImrN8%_Pjbm^uj>GZzH%`EbI0+}? z6r76Fa5~PwnK%n);~boe^Kd>cz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo z7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4# z6}*bq@H*bWn|KRv;~l(<_wYVGz=!wEhD;6$8+ zlW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QI zm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~ zpYaQR#c%i>f8bC2g}?C+4mhO$I1mTnU>t%&@h=>P!*K+T#8EgJ$KY5ThvV^YoPZN? z5>Cb`I2EVibew@RaTd@fE(tH~1Fc;d}gm zAMq1@#xM94zu|ZMfj{vV{>DEz;E4X?KpcdFaR?5@zi=21#}POZN8xB3gJW?Vj>o@o z0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s% z18&4kxEZ(LR@{c$aR=_iUAPv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F} z=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$ z@9_hE#83Dczu;H=hTriA{={GS8~@;dWBQK+aS#s1AvhHO!eKZZN8m^tg`;r{j>T~} z9{xDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP z9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC z9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtd1O8X~j{|WK4#puk6#v3uI2=ddNF0TuaSV>d zaX23T#tAqPC*fqAf>UuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8ri zYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@ zZ}Afg^Dgj>a)K z7RTXu{2M3WM4W_^aSBewX*eBc;7pu_vvCg2#d$a%7vMr%go|+rF2!ZI99Q5rsL98cg$JcXz6 z44%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^ z4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr+a_`CEU2jUZzFARfZQcm$8)F+7eZ@FbqX z(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*& z*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2M3(ee;kN|a4-(Rq4*aL!{ImrN8%_P zjbm^uj>GZzH%`EbI0+}?6r76Fa5~PwnK%n);~boe^Kd>cz=gO77vmCKipy|0uE3SJ z3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM z3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wa4Js2={N&t;w+qvb8s%s z!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA z!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW z!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}%!-Z4-cJ{wg-}a}! z|1sdt|IZ)(xBu2ZwEd60_x-8kp9}i)JNF;>6Mx}v{DT9|_y=$x4#L4W1c%~ZI1Gp5 z2pox{a5Rp=u{aLLD!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U` z<9Gs3;we0hXYeeZ!}E9nFXAP8uxU%M7%|8GC{?eaX{_@}V=MDBvyoI;% z4&KFkcpo3&Lwtmf@d-Y~XZRdn;7fdkukj7O#dr7~Kj26FgrD&Xe#LM29e?0Y{Dr^q z4-WX>=sym`K{yzP;86Svhv9G>fg^Dgj>a)K7RTXu{2M3WM4W_^aSBewX*eBc;7pu_ zvvCg2#d$a%7vMr%go|+rF2!ZI99Q5rsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=R zxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8 zzwr+aIH&(O5C`F49D+mfFC2!$aRiRUQ8*gM;8+}o^NPR1!X6{q2JoPjfO z7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB z7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I z7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD95 z7yiaSIN*P$|2Pl_;b0tsL-8*hhQo0Lj>J(o8pq&R9EaoaZ=8S=aS~3(DL56U;dGpV zGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1C zJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$ zH}MwU#yfZy@8NxXfDiEzKE@~b6rbU9{Qq-thtZgpYs1IWWQ>d?Nwy?Ok|fEcsJ>I4tv0!uovtNE3kllVL#X(4uAvUAUGHffkWXiI2?|EBjG4G8jgWu;W#)RPJk2P zBsdvPfm7i$I33P_GvO>a8_t1q;XF7WE`ST+BDfeXflJ{sxE!v4E8!}*8m@tB;X1e; zZh#x%Cb$`Hfm`7=xE=0*JK-+48&=^SxEJn&`{4n25FUbu;SqQg9)ri>33w8of~Vmb zcov?6=ivo-5nh6q;T3olUW3=+4R{mYg16xv*x(!Nf7lQ!fWt4ya8{*Tktl#0~<_Y|HFo` z5o`>bz^1SnYz|w%mar9U4coxBupMjx6YLDTz^<)Xtp0F3}4J)vKePKV? z9}a*6;UG8|4uM1AFgP5JfFt23I2w+DW8pYB9!`K0;UqX2PJvV5G&mj3fHUDNI2+D^ zbKyKVA1;6k;Uc&gE`dwoGPoSBfGgoDxEij3YvDS$9&Uge;U>5lZh>3jHn<(`fIHzX zxEogC9=I3ogZtqDcn}_fhv5-;6dr@e;R$#Wo`R?08F&_+gXiG|coANLm*Ew76<&ka z;SG2b-h#K`9oXPo?0?u0HiC^|6WA0sgUw+J*b=satzjG37Pf=!VF%a|c7mN@7uXec zgWX{d*c0}Gyue71y{p0a4lR1*TW5P zBisZx!!2+t+y=M99dIYy1$V#cd!x3;K90fov#2gkz+a3Y)pC&MXlDx3zV!x?ZUoCRmYIdCqV z2j{~Da3Nd-7sDlRDO?7Z!xeBPTm@IdHE=Cl2iL<5a3kCVH^VJ(E8GUR!yRxZ+y!^T zD%=D2!hLW*JOB^EL+~&>0*}ID@HjjHPr_61G&}>(!gKIEyZ|r4OYkzh0JULz_zd*Y!5rYj<6H#47)I2lfX zQ{gl?9nOF=;Vd{C&Vh5`JUAaNfD7RwxEL;hOW`uO9Ik*X;VQTqu7PXeI=CKgfE(c^ zxEXGNTj4gi9qxcT;V!rvR^cAF7w&`m;Q@FM9)gGA5qK0HgU8_scoLq1r{NiR7M_FW z;RSdRUV@k56?he1gV*5=coW`&x8WVw-~{$RYzP~{#;^%&3Y)>^umx-hTfx?_4QvbB z!S=8N>!fWt4ya8{*Tktl#0~>sg{SO<$MzAq#0-M5SusLi2Tf$bbHEaXh!gjDd>;OB$ zPOvlV0=vR)usiGld%|9@H>|({_J#dme>eaRgoEHfe7FEEgp1%}xCAbR%iwaj0;6-=|UWQlTRd@|vhd1C&cnjW!cVL5A?0?u0HiC^|6WA0sgUw+J z*b=satzjG37Pf=!VF%a|c7mN@7uXecgWX{d*c0}Gyue71y{p0a4lR1*TW5PBisZx!!2+t+y=M99dIYy1$V;k*OZm>J-0eiw;us5v0 z0``UdV1GCO4upf?U^oO0g~Q-*I0BA@qu^*b29AZ};CMIzPK1--WH<#*h11}4I0Mdv zv*2tv2hN4_;C#3ME`*EVVz>k@h0EY_xB{+(tKe$52Cjwc;Ci?LZiJiQX1E1zh1=kE zxC8ElyWnnEg?r#$xDW1!2jD??2p)z<;8A!C9)~C3Nq7pLhG*becn+S27vM#B30{U* z;8l1HUWYf}O?V65hIe3tIqZMf5H^C1VH4OCHiOM!3)m92f~{d2*cP^f?O_Ml5q5%| zVHemHc7xqv57-m-g1un{7O*eu2m8YTa3CB62g4z7C>#cd!x3;K90fov#2gkz+ za3Y)pC&MXlDx3zV!x?ZUoCRmYIdCqV2j{~Da3Nd-7sDlRDO?7Z!xeBPTm@IdHE=Cl z2iL<5a3kCVH^VJ(E8GUR!yRxZ+y!^TD%=D2!hLW*JOB^EL+~&>0*}ID@HjjHPr_61 zG&}>(!gKIEyZ|r4OYkzh0C^x!e+2JYyn%s zR`7+=@A-wO6F(H_oxLyiu|~InZDBju9(I5oVJFxbc7a`CH`pEafIVR^*c(=00sF#! zus<9C2f{&cFdPDh!eMYY905ndQE)UI1INN~a6FsLA!!z(KJO|Ii3-BVm1TVuY@G86p zufrSgCcFi2!#l9SJnjM55H^C1VH4OCHiOM!3)m92f~{d2*cP^f?O_Ml5q5%|VHemH zc7xqv57-m-g1un{7O*eu2m8YTa3CB62g4z7C>#cd!x3;K90fov#2gkz+a3Y)p zC&MXlDx3zV!x?ZUoCRmYIdCqV2j{~Da3Nd-7sDlRDO?7Z!xeBPTm@IdHE=Cl2iL<5 za3kCVH^VJ(E8GUR!yRxZ+y!^TD%=D2!hLW*JOB^EL+~&>0*}ID@HjjHPr_61G&}>( z!gKIEyZ|r4OYkzh0C^x!e+2JYyn%sR;ZeiUa&W;zykJ#{a}AM01kwM;9xie4u!+ua5w^v zgrneSI0lY|)?900d9nw;AXf5ZiU<6cDMuXguCEwScQAwUbqkLhX>$6cnBVb zN8nL-3?7Fk;7NE2o`z@OS$GbfhZo>QcnMyHSKw864PJ*g;7xc7-iCKzg9Yq=*bp|7 zR*aq$;XmH5kD59y_DFyA-c{<JULz_zd*Y!5rY zj<6H#47)I2lfXQ{gl?9nOF=;Vd{C&Vh5`JUAaNfD7RwxEL;hOW`uO9Ik*X;VQTq zu7PXeI=CKgfE(c^xEXGNTj4gi9qxcT;V!rvR^cAF7w&`m;Q@FM9)gGA5qK0HgU8_s zcoLq1r{NiR7M_FW;RSdRUV@k56?he1gV*5=coW`&x8WVw;3Vz=*bp{?jbRhm6gGp+ zVGGz2wt}r;8`u`MgY97l*b#PuonaT)6?TK&VGr07_JX}(1s1R`><9b90dOE31P8+* za3~xGhrX%TnE>~4R9me1UJJia4Xyfx5FK9C)@>h!z$bZ_riT} zKRf^r!b9*dJOYoxWAHdU0Z+nH@H9LF&%$%?JiGue!b|WnyaKPnYw$X}0dK-v@HV^y z8!Te~!-lXCYz&*grmz`o4qHgaK5&nSySrMPXK>8N65R^6hHYS5*bcUb9biY;33i5E zU{}};c85J+PuL6gh80-AzOWzc4+p@3a1a~}hrpq57#t2qz>#ni91X|7v2Yw54=2Eh za1xvhr@*Oj8k`Piz?pCsoDJu|xo{qw4;R3Na1mS#m%ycP8C(umz?E!P#a1-1Nx4^A%8{7_ez@2ax+zqR658Mm)!Ts<6JO~fL!|(_^3Xj3#@B};wPr=jh z3_J_Z!SnC}ya+GB%kT=k3a`QI@CLjIZ^7H}4s7r<_CIV08^Ok~32X|R!RD|9YzbSz z*02q13){i=umkJ}JHgJd3+xKJ!S1jJ>){5t5pIH;;TE_RZiCz54!9HUg1ccA?ty#ZKDZwqfCu3r zco-gmN8vGe9G-wD;VF0;o`GlKId~pkfEVEWC0 z*a$X;O<+^l3^s=?U`yBvwuWtBTi6b^haF%?*a>!qU0_$(4R(h;U{BZ!_J$Q$z`n2_ z><a2Om8N5GMA6dVo5z_D-~91kbJiEt8}45z@Ua2lKrXTX_o7Mu;| zz`1Z9oDUbkg>VsE441&Aa2Z?!OQRpyb7+lA=32(vM@D6P73-&*32phr1unBAmo5AL=1#Agh!Pc-1Yzy1L_OJu&2s^>funX)8 zyTR_T2kZ%Z!QQX}3)mO-gZ<$EI1mnkgW(W36b^&K;RrYqj)J4%7&sP=gX7@@I1x^Q zli?IN6;6ZG;S4wv&VsYy95@%wgY)46xDYOai{TQu6fT3y;R?7Cu7a!K8n_m&gX`f2 zxDjrGo8cC?6>fvu;SRVH?t;5v74Ct1;Xb$@9)JhoA$S-bfk)vncpRR9C*di08lHh? z;W>C7UVsHN7xB=hFxG+*bR1vJz!7R3-*Q;SirupAM6hYz=3cO91MrRp>P-+4oAR| za1RPd+zhwCt#BLM4tKzva2MPSt8fq83-`hO@Blmr55dFm z2s{dp!Q=1*JPA+1)9?&D3(vvx@B+LDFTu<33cL!h!Rzn_ya{i?+wcx-@GJH|YzP~{ z#;^%&3Y)>^umx-hTfx?_4QvbB!S=8N>@B*b26WZD3p24z`CKU`N;qc7|PGSJ(}9 zhdp3V*bDZC6 z6Wk29z^!l_+zxlZop2Z24Xbbu+za=?{qO)h2oJ%-@CZB#kHO>c1Uv~(!PD>zJPXgk z^Y8+^2rt3Q@Cv*Nufgl^2D}Mx!Q1c-Z15ZQKWqpa!N#x&Yzmvf=CB2930uL|unlYr z+rjp*1MCPp!OpM?>5I7VLgTvtnI1-M6 zqv04h7LJ4C;RHAlPJ)x+6gU-5gVW&*I1|o-v*8>#7tVw8;R3i2E`p2U61WsDgUjIx zxDu{{tKk~B7OsQq;Rd)7Zi1WP7Pu8|gWKT_xD)PzyI~dXfqUUTxE~&X2jL-j7#@K~ z;W2m|o`5IeDR>&5foI`4cphGW7vUv%8D4=`;Wc<2-hemZEqEK=felu%|6xPe2sVaI zU{lx(His=>OV|pwhHYS5*bcUb9biY;33i5EU{}};c85J+PuL6gh80-AzOWzc4+p@3 za1a~}hrpq57#t2qz>#ni91X|7v2Yw54=2Eha1xvhr@*Oj8k`Piz?pCsoDJu|xo{qw z4;R3Na1mS#m%ycP8C(umz?E!P#a1-1Nx4^A%8{7_ez@2ax+zqR6 z58Mm)!Ts<6JO~fL!|(_^3Xj3#@B};wPr=jh3_J_Z!SnC}ya+GB%kT=k3a`QI@CLjI zZ^7H}4s37=`yV!hjbLNg1U7}uU~||4wuG%72+2sgpa za0}cDx54dj2iysF!QHS5_rSeyAKVWQz=QA*JPeP(qwp9!4o|?7@Dw}^&%m?r96S#% zz>Dw_ybQ0vtMD4U4sXDl@D{uc@4yCY*#EF0Yy=y_Ca@`N2AjhcuqA8-Tf;W6Eo=wd z!w#?`>;yZ*F0d=?2D`%^uqW&Vd&3GWU|-k|_J;%DKsX2vhC|>`I1CPlBj89l3XXZ3+{$hxCicq``~_f03L*g;9+rcn3E49s3_PgpFWh*aS9( z&0urb0=9&$U~AY0wuS9rd)NVXgq>h#*adcl-C%dv1NMZyU~gD~1?&s^!TxXn90&)& z!Egv13Wvera0DC)N5Ro>3>*u`!SQecoCqhu$#4ps3a7#8a0Z+SXTjNU4x9_;!TE3j zTnHDz#c&B+3YWp)8LWA#4O2!zQpPYzCXd7O*931zW>5uq|u{+rtj9BkTk_!!EEZ>;}8T9^umx-hTfx?_4QvbB!S=8N z>@B*b26WZD3p24z`CKU`N;qc7|PGSJ(}9hdp3V*bDZC66Wk29z^!l_+zxlZop2Z24Xbbu+za=? z{qO)h2oJ%-@CZB#k4Zmu<6p=3Uwyr9{2fOzjy?f@BmGrog!b2gDcviBIx&epC0+3I zw)W7;o0Z06F5)}sd*8l5n>RU6x2MEHOiN!kca_$*lCRtM)wyCu`Y|n0J9^@3yq|ZOoLpxpc zxc<}X6JlO^`18-j8UO#!PtpgBF7N4i;E*Cb_KOAdlhUqt+^OBwG^u;ZP$w4Am*8c1 z1zwfDIo3e?DG(tLnP`0wwbB-C`5-Thd#h z9omgoHt6bRE)(16e@O?PdR%?&`S+D;KmJbaNMG`{gFfG_Pxq*jCeHZdj59ub=UT0x zb;R52>p%1c(uc#Y(;ghyuZ%hth+WbhRq4XB>o;Z3nxW5G(ihyDq`k6gsraNnCJdz; zFFUQ~9-j1$eQQO(Tl%a!thL?8_A9+-Jt>T&|M%awF>C^xN}u~&zhZM?yY768L~)LE zs`qZ~oysH1(JPyU8RpHU?|X5dqKj$NnZ?Bl3+W@FZ)#qr%=G!!H;D74leccvQg)7c z=X^G+x5RrZ>624=+Bxl4=sb?j=`WQ2vRctx55BB;-qs+jF>eFgO7FS!eZ9*Kv5I)B zS=gc5!5I7VLgTvtn_)h63qrOp}Zz$2-eK15sqDR4ZOOJi?x%P0#Gs>pn zzeF^841BM2&YQ>8j^Gb;@4lHVV$tK^csK!0gp=TZNPioAr51lzqW+EE|A=Ji58eKu zzr1mu@=;K=NWpxn^rz=KXn8OFNB6-6F(M5;9nOF=;eSeh^i804e&*TAtGe4n7J9by zPe<&8a`9z8Z@fI{lOx@8!(sKD?tkd-xb!TaT+HXe`S4TH%|D-3|NZ(Mx}(>*ivsC) zcYl05?za$K#y&Sui1{M;S?S3c_nwmhjk+)HJE<>5FOh!gvghh;@eP2$D_VLMmdY?P1#JkdVpC@Q9UVDfBUy0qK1M{8m`_j)m@}qXS*{JgU z8TX1V^be#j2zpiXcw}2S=lY|f8~wQSHM?HZ&Wz7e)<^Fas`Sid>*{q+eyh9V8ZV(q z?>VPQD_ULA-Bo_6=)wD5xDW1^{=Pfm_<_tXbpQQ#k{CcAgg=x1<+)e1@Z>#8{XMQ? z2>o;EjO}~WyN3!Di+zJ)7=1)~&eY-~PUO_rE{Vz54m1VhQ~h>6-Lx z?Y^E=rQ!N=u`GQ?=~4aY7q=_pdtMj6O27Z<9a`*7+urwv7>E_TUxnA;b$CPit0T?F zXT)8)-e*0;Ci)h<4ev-_|KDBuFPbk_+>Rd>2Alu)KHDYza@X|n%l5sl3wt6=7)o#2 z-XVtHcwKq=31{>xr3;EJHII3F{lg!g>EnX#3cE?qUsk2Q{$`@mwoxyxk-n>MpY~d# zrShu7-NGI79~X2Ko5k2;9xie4ux-*R?Oa4?<=*?CsltV z!lWPmy6yPXPZRoAHq3nPkUlnak9xb|Gs@0=gCZR7Bj8B*F6kS+6188iFVerYZ%Ra= zN5e62EF1^N!wGOAoCGJsDR8Q^@pFgO&%bm~*1aDPY3S*22Am0J!P#&QoGU%h@Ur@& zOPT)G_vXYw>5s3vRLlR*#o~sb3w`o1pD%q+*v#>Z>l$=-ToNXplCGI5(4M)fTe08! zv?#!QAzUQ=$;%I^zyIN>_Gs}Jo-J3@3Nef-LZ z$8|5f>MJTR|AO>S4*u$mlYbYt#J=EDiCzU)!!__L(k9;bYO4!#-XDan>uaSCU;nB8 z;8};nL*FO*)JcDPpR*co(54u^{h2r_-MbvD72RU5zvy(UsF(iVKhGMZ8!mcX4LBC5 zBzwFf8qu5JX1E1zh1=kFq`%02N4@={hxA`ZUFOq{-T}WSedHont++B%(ej!^Cwdp$ z4XbdE^otut$DizdTBkmGv*<C{0eBD|foE zcl3%FMjwGk;W79t>6c4gwDV5J>s{PN#W?!c(vO_FT79?U8zs~2FEN2W2~WY(@Qn0X z+a3Dbj-RRcyi_b^(dVQq68_K}V&7H%<&-3TlrG<`({_F7r+<9mLoqM?qDhsvds$eB~(-C4QH_Cg!mAYrvh# z?BG*k9rGK~|FpjCxHj;;@;OB$POvlV0=vR)usiG_ z{ph`WwEOGa^T% z?`6KDJe2l^P|)?#-&&e!AA228n)eqAf$j_6Abs@fFVxGA>{3n-eaRgoEH< z=?_cB^ld$6O4t2IL| zDf0)lD^CRKjcVFO1bU?OS-PL}X7d*+2mg3ML`h$?@a*xxt3KBKP#G`omX2L{R{gBk zMRD2ns))w>82DakhrQP7TYit~4_II86N?@P-!EQ*YY$hH~LE^&%NP1x}TIr#VYKwdZ=}qo2}58v0|>_uTie>X0y~-2Bo{A|3sJ zwElr2wfT$+<+8g!5gF*2a2A{m=fJse9-J?IFz{Jjyq&S~_#t_Zp%=nMa4}p0m%?Rm zxpc`;hx&VohyIo~yF>+gC0qqp!!>X%Tn8VOc9^PEKm5c@348E}s7G&r8{sCn8E%1F z;WoG(?tnYtF1Q<3VNLpsqmJ4g&m7QOz4eXgLGOk8q_6xUN&9i{xq5&1V$qL201v{S zNnhA}sdml}dgW5{3^9a0ENz+afqLt*pJIR33*rmu*Q+1WPOTkQw$G~)BbXnB$KY{z z0-l7Y;P0gG@v2bw&z-M)aQb;MjebIUPj!L%_UMRye{HnS4EiiQ2hYO`@FKhfFT*SF zZ_?H+?`k(i#p^axE)}cjYw$X}0dGq0|J#T9?@#Sko*gd{r=`End`XRc`jGf0_#vMy z%x}Xxu)!AYDcBG;f{kGl*c3K{&0!1J61IY^VH?;Mwu9|q2iOsIf}LR(*cEn@{;#RE zYW{Jta^I6jggd$i>?!@thvvsm2mYXYJoSL^LidIhSirupAM6hYz=3cO91MrRp>P-+ z4oAR|a1?=Kc9=&90= z3|*@F6zHRYgiBD@$ z@GLwB&%+DwBD@4I!z=JAyauns8}KH)1#e4#`nROxKOTKgx%k0jVh7#ePuvr*A#4O2 z!zQpPYzCXd7O*931zW>5@I}(aPj9GC9_!Q@Tz`+SMYn_PVF%a|c7mN@7uXecgRhaE z`M^VaxNBU0{*i6rj_v__N}oQMqCTFn?VbFXvG79oh80-AzOWzc4+p@3(tBTV(GFDa z(I5P+Qv{(0!y#}e90uPZedXR#b@Jj%l>-^oA{;#ezEk?T2X<@swRGtlpEUG|M2~`_ z;TSj;j)UXj1UL~+f|KDCI2BHV)1~)Uo})coJENR<>WIjYe&F2Q;^X62D<%nbA`|mj z()K?eR=YoVOnGX&QDmd%z`1Z9oDUbkg>VsE441&Aa2Z?3jHn<(`fIHzXxEogC9=I3ogZtqDcn}_fhv6@z zZ@bx3HTiH*xyx@|jG&LgWAHfqwRB1Jqw0eP6O=PH-WL<-lkgNg4bQ-{@Ekl3FTjiN z61)trz^m{Yybf=`oA4IA4e!7P+qgGiL)Zv5hR>GXcm7N2@j4Ub>YEM=6LeG93^s=? zU`yBvwwC_r&I`oBS1$BcXTjNUj`Ux9(zPFd zn%7<7U?-lC{$u9?ZN2BH(th_xA{X;{a6ViB7s5qwF?>jR^8Sz1&A-l9`foTSO3+K; zGPoSBfGgoDxEij3YvDS$9&Uge;U@T)^sV8g>Wc^L70sbqG^4k`t#BLM4tKzva2MPS zt8fq83-`hO@F&vO-~O$7?)AUuhO{g(fc~lUaT}qz6)Sq5k5n;;J|ulf+Vx`cnS_D;XE4nqjVTdE9ft9^CX4Ckj_EGtwbvdOuIO&CJAAEl=p*;3D`_*z`9GZ!9_XI17wj#)C-~{(4GXQx zTe?P}pbOX+_JjT58>K&XDo{_JsVGlxye)A>(#e0=YLSQHbYGTUC8E%y;TSj;j)UXj1UL~+f|KDCI2BHV)8Py_6V8IO z;T$*@&V%#e0=N(^f{Wo2xD+mf%i#*R5`IxSJnCcZR--aqS%_Nc$@|{c9?4tPC2l*3I?UHgU$pDuo=2bd)@^uyseeuS z1-gqjPU^eRyI~d9q$B5F*P3hYQ>tteM31z|H|^?V(R<3?wtCTv`98QG9)JhoA?eN^ zzfyNuJ+ALMVc;{2J_3I!UHq?n&F1%am5!t`F^WD0eE%wT>N{y}=*PxjjHU!S4>_rE_9bLjK%0=x(>!OQRpyefT0_NMO6ToWa|qfo4& zufzY9u6f~MEwe9BX}C2=Y@lz#Tktl#0~_q%zJU#4BY2PW@eSwI4VpqZwi}9cRH7ft9`okPdLZ;n4??3mavudhtC;ne{5W(clo$gSfks( zw(!N$2|ej*&n>aa?#JE}cIfu71MCPp!OpM?>9;XLIAbJoS42Qs>a2Om8 zN5GMA6dWyWoHL`okiJKsU;d7WL63#w;CMIzPLvKe{Y@?W@lWN2g=Ue2o-BRM&0WX; zcIln^?>;y!QqWW3G&mhTAbs&YzpMX^e_Q{{b>==9=$UYq^m``fslh*Fe)0 zrbgd=bq;Rg71>A86`E%{;xee3wQq7l7G`oP6W+JWsAol~14n$cU}R`_k{ zU2RWl<;Mb)gY%DyHuQG51MY<1mp(moNWCK1N%_Z8jp#z}hE?fH)-F+Uf9cd;mwSVc zChh*dlNR>zCyM!>C87uOy>K7gFa6M|1pSp;2I9%bZuA*IAB2bCVR!@{g~#A=cmn=L zx*_W6<6ky-iMWggpGov7cp9F8XW=<`9$tVK;U#z({#Dw4VvlyAb)(+txQWjS`ft+9 z6Mxqp3N2R_9zP&f(N9TV)4yN4{fe{o55CqHn?5@L$r}k1wg+<^ND3wpzswy1^L+|L60ByI@1u2sW19bNPOK zyZWW>L}r#SK{tiXU~||4wuG&uKehiufB4*ImG6Jmgf+SiYzy1L_OJu&2s^=7Nay|a zgZl9KQTpDAQQ?g40=vR)useLM^gAE?sP2qM=x1*HQh1tmu6y$mjgE8t4$4d)*9+E<@bz9@cFR7sx*bJT86 z)+^oa^4`IG4O|PqD*c~l3$=*Pyp-pH9ujrv^>72+D1GwQ@Z)QZ|JMI{)gGTF^k%pP zZiU;Ve<*iRKlFKC-(R!Kr(OE{Ll^4*`u<#fc-_~c1M{757u+rV*Lfyt(t{rQvvTK! zirxeF!hLW*JOFf|ubH zcoklQ*WnF#Q~H;^pX%>#x?OMb*%>}t=-bl4Zyr|DTsxH`A@B*b26WZD3p2PP*7DU;ESg4c*w0Glf06gLHPv z52{Pvqsp%{_XtOHC)gQwfn8xYX}iY?)Wn)TrSscE!X4cM_JqA)Z&-l^>931e^f>9S9yij`v;gJbfj5hI z^aMB&PJ)x+6gU-5gVW&*I1|o-v*8^03F#&I`P8_*59rVN?kACpo(Jc{1#lt!jC9hj zDz&m8LjOhJnLb76#c&B+3YWpce~{iF@Zh_Pr=jhjP&JJR_fCq9alU; zN5w4qoU~EKvtoU;R)0ZCh|iDzTAhc`uoza@i90P_XunV z8^Ok~32X|R!RD|9Yzbc=-Rs+~KAdl(cf0q5utK+nZD3p24z`CKU`N;qc7|PGSJ(}9 zhdp3V*bDZC6<MnV^G2olz>}f`y;S<|b$4j5+?T7^g&YuvrOz#V zL~|&(QF;E2Kv9PIa_QH8EjT{gaHjrW{{I!vOIKIfX-~a)mfkR=UsPbe60U-);TpIW zu7m602DlM!f}7zMxD{@b9{9agH4J-6ne+UYXh-jWJK-+48&=^SxEJn&`{4n25FUbu z;SqQg9)riFf6w(4W1r`V5BEIlGl4z{Pr=jh3_J_Z!SnC}ya+GB%kZz#-XAz=A16Jc zKe{$9R?t`BHFzD~fH&bScpKh<4R+zafX|Y?@bqWu6aTH(H#^w)7@`}&#?nWBFjW(u zeN7+TbheKPx+!c1o5L2cC2R#-!#1!jYzN!J4zMHa1Uthnu&Z>*;638ji$72{OP7Qj zx;yLvd%|9@H>|)y`rCi*Rp0vQP5ouBn)&#m`@#Nj030a&?j?U7uf9RAKhJ7O1fky| z?a<()73RFB_t}4ePcZte(tY!)dhbOSDSHl=iV*3aa-!7u(m6$2suZD^4}-(u{nB^o zx>VzHFH=&kDi;yxk#H0o4adN-(r@3Aq4Q_`!;7+&; z?v`#nTQ6+a6ZFBap68>Y_rSgIN77Me%kSIWxLyDB_IIKWy&oQc2jL-j7#@K~;W2m| zo`5IeDR>&5foI`4cphGW7vUv%8D4=`;ZxG?uivDNzjU#Fcj1R(4SgNnfH&bScpKh< z4bJ+%_r{shVnVOondqj^&HPO4LO)CT-IlM^?C=VGiRt-1hUmMc&pYc}EvN4V{U;xs z1Q6U);}`(o34MeK$xRjz?QHTYz^DMw$c}t zT4;G@9!fxkkFZ0(MA|gVQ+x8;CgsQt`NAIE0lrN7SMwLumxI%lAFgi~j_6LXGwcGp z!fvoT>;ZeiUa&W;zykJ#{oub#ub8`P^$nRy+0#wJA3XpLgoEHq9vo;RP5*Z2PWbL)HU>hOVu z-T(e;xyjpIfg1Y1y!ORM))r2GC@Jt=Kk?E0TKa8#2iM_x+<@=mM%;v(@jZMWKfn)h z3vR`2xE*)kPTYkb;comG_uyXq1oz>7Jb(xB5FW-OcodJ}r+6Gs;AeOePvL1igP-GB zJcsA;0$#*R*Z$V8`o!lCcm6!Dqu?9zm+33`1zyE#_$6M)8~7FegKPih51cRj*gs_E zm3(k9ut|T7x9~RJ!Mk`5@8bjf2EV=bcm3?&<$j_vE_X8iGl4_;5kAK6@CiP}XZUZg z{f|ci@Aduccjnc8&qCmwet|FX6%Gpi();9nI2ecEP#lKCaRiRUQ8*gkz%ls4*M8sE z{@%j3f5l(tCjG#2AeJ77<8cB`#7Q_Ar{GkahSPBd&cs9$HI4f>ZkKQ_lq(4jr1SBcF3>vE__GrPvjncVIj~&Z^rlVef;3s zpZ?%42Y&G^HIV<2U&w!W?Vr8>j}|^3`n7paf86p!Nx z{0vXxDLjp5@N+zi=kPpUz>9bZFXI*b0GXd0ViJjL*MmJ@;>^}wA^>;1A!!ZGETv%I1Q)c44jFxa5m1txj29e za3LBN7`q zfA%+hfj@5R%%7)!{@P!N$$2m2r#_zdqWAX$3-m?2gqQIOet}o<8h(k_@dkc{H}Pw{ zg}3nz{?lt8fAMSIYyRA6?x+5AIIv6K!~6IEzrkT~}9w%J;kG^O2^M98(lKXQJBY{ME(zXBLFl=EZ>4SOy`UfWi$@CPQiqmj9&cK;C z3uoi+xc0yMt;c8riYw>M- z2iM_x+<@=mM%;v(@jZMWKfn)h3vR`2xE*)kPTY0v+F$vD_dfG%<~tuAe0AUvy&FHq zJ=gx!2eUrk@QH}L&t866elPtA{;6wM{j0S1J{A7C+#mR%?m!>C9}nO`JcNhw2p+{_ z_$eO86ZjdP#8Y@0&*10yH?G~9_5%xl_9E;Ib7A>^{-Z$G?|oZ-3B44T;c|QnSKvxqg{yH5uEn?U z9bAX&aRa`K8*vkE#`o}j`~W}1Ew~l8;db1CJ8>6&guC%$+=F}Z6WoXU@cJBX_cSl}zi;II#Qzr@|JMp| zAuhtj_$DsFrML{2<6F1_SK=yMjcaf%zK!qTI$Vz%@Lk-9n{YF}hwtMD_#tk=t=InD z@B5iR^MCqm-p4m9^4sX`xC3|MF8m00cYO9B(8v3qzV>JKXBYnTccSvX{f zzQREt_|koO9}dPL*Z%CUeb++l!r$cnXvWV6Lg`^R97o_t9EGFt4IG1GaU71v2{;ia z;bfeGQ*jzj#~C;iXW?v|gL82J7vMr%gp2V_T!Kq+87{}Sa0RZ!Rk#}0;97he-@$db z9yj2-xDhwuW_%Cd#}DvB+=5$i8*axPxD$8bN4Oh5#yz+fKf!&t9}nO`JcNhw2p+xm zC%^a3!pskSD(^!_e;pX3KgHvC0zbo(cnVMB8T=g2;yFBz7w{rp!pnFCzrd?_4Zp^NPR1!X6{q2JoPjfO7S6^wI2Q-5{q?{1nY{7;e3Z8} z_nr9#^g>*Oi}6idf=h83F2}cU1+K(ZxEj~sT6`Pd!F9MEH{iRt5jWvxd=KBp5AZ|W zf?IJLZpR(C6L;Z9xEnvlJ-8P?!F{+N58y#Ogop769>rt$DIUiY_!*wWQ+OKB;OBT2 z&*6EzfEV!+UdAi<1zyE#_$6M)8~7F8#INxd-o`t47w_SHe1PBJxA+hr;bZ&`pWst` zhR^W@zQk8J=quTO9E?M7C=SEnI08rFC>)J%;20c><8VAqz==2sC*u^Hiqmj9&cK;C z3uogToQngv02ksST#Rqx5?qSQa5=t(D{v*Q!qvD2*W%mw4z9!XxB=hAjkpOn<9ql% zet;k17Tk*4a69h6owy4>!rk~W?!mqI3GT!FcmNOLAv}yn@F*U`Pw_aOz|ZinT>B@p z#uq*v^t*ZKO<$EiNuR>gcm_Ymvv>~A;|08km+&%P!7uPCUc)c(I^Mvq@FsqZx9~RJ z!Mk`5@8bjf2EWCJ_y`~4clZRK;xl}XFYqP4!a?EO12`Cm;7}Zf!*K+T#8EgJ-@q|A z7RTXuoPZOr-S^!e4*Z9M#lX^g{rO4sWSoLiaT-p?88{PX;p}UFA^RskpY~(_IIs8@ zekYJa&&2^;fD3WawSVZ3zbf#*Q-419n`(bAP)xsh?dX5^GYkK$bSn43_jd(K=%u&} zm*ZQw0)OANzwOtH7XIvGlezycYd=s)ufo;12G`=-_ztea^|%4w#f`WLH{*NwK7N27 z;uhSB+i*MXz@4}YKf>MkG5*PG|KbO}@4e6c{-<)M#y=70q4(k^xDWT^0X&F*_S(^H zJqy1*9TxcY%5Tgcq7UN{Jc@tu+7r{iw9puGlK06k=H-uFduu!+u<$qKf!VsU{HMGh z#}oJ&p2Sml8qeV8coxs$dAxuZ@e*FfEBFOo#cTK_UdJ2w72d?J@fP03J9roG;eC97 z-{80S5Fg=V{0^VsQ+$Tc@ddubS2*aaxF2vZ4#A-~42RLJBzKKh4DK5k1_!h3fmADF5;~HFxZ{s_- z4%g!bd>1$3Cftng;rsXjeu!IeD{jL-cJ0t#tzB69vV+{oKR*q$(>rh{?!u37H-3zJ za4&v>`>y?Sk$;spU)GiT>pwRi=%)|hK|F+q@dzHpWB4f^#}oJ&p2Snv{^qK^&;Lcl zH{=yI{YGG#K7*g*Sv-g5@d94NU%2+CO8)!8%EEW&{_MqQV2QqrSMUqGir4TT~}9w*>LoP?8c3QoOtOL*tP@bA~=%}(wI(&*_p183qaoQ-pEE)L*= zYiIoJPc6i}{Po=SPd*A1(u;60zKKh4DK5k1_Y$`M2m5xDr?4 zYFvYB@ojtu*Wr5HfbZf)+=QF)J$xTOzz?sT^9K!qgb#mPUVT+eehd9aul-v+JD-pJ zwY9)6oXq66(%W$RwST4X)1P0TEzbM;fAYJ54tgih}vb+{fk;JdgHH{oV{58uZR@I%~!TX7ryv1@1i z!_LA#KL1yF-}_U47-*+=;7;6yAK`BN828{_`~>&memsB&@em%yBX|^#;iq^UPvB>G z5>Mf2JcFO(Sv-g5@d94NOL!Tt;1_rmui=+?9dF=QcoVMkG48>=_zCXA{dfQm;vqbYNAM^f!%y)z zp1{xWB%Z?4cm_Ymvv>~A;|08km+&%P!7uPCUc)c(I^Mvq@FsqZx9~RJ!Mk`5@8bjf z2EWCJ_y`~4clZSVpKHJU?AXGGcD^%j=L;u+Q~DV`#~1h#U*Vvy`OUuCPRAKI6KCOU zoP%?502kmwT!f49O&B;YYX|KgK<{7eB#$xE~MTK|F+q@dzHpWB4f^zxEIRr_8|pUn|TH z`HJoQ3HmcUiKp;1p25%YES|&jcmXfsCA^GR@C&?(*YHccjyLctyoq1qExe6)@GjoN z`}hFA!Ef;)KElWN9X`RQ_za)p3w(*Ma8T5j-UIK$!8inm;xHVJBXA^+!qNB!{?N5Q z`1Ai};g?JPMc`L|;-BZo&|`5Nj>ic&5hvkfoPtwv8cxR&B z;YYX|KgK<{7eB#$xE~MTK|F+q@dzHpWB4f^#}oJ&p2Sml8qeV8coxs$dAxuZ@e*Ff zEBFOo#cTK_UdJ2w72d?J@fP03J9roG;eC97-{80S5Fg=V{0^VsQ+$Tc@ddubS2*Z{ z+zU7uhu}~ghQo0Lj>J(o8sES%I2Om@c$|O}aS~3(DL56U;dGpVGjSHq#yL0_2XFx{ z#6`Fm-^3-j6qn(0d<$3LN?e7jaSg7;xA7fZhwE_zzKa`i6K=-$@O}IMKg2D#6}RDb z+<`lB7k-4h@nhVBd+`(8hx_pW9>ha<7?0plJcgg*aXf*a;YmD&r|}Gaj%V>4p2rJ# z5ij9oyn@f8mGTJ8lLj6-lJ4#VL%;@a>3%YnS_{=-iM{^NJn z=SR|`a5TPwV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEE)L)VT!@QsF}{gQa49as z<@gq^z?HZPSK}I7i*MsQxDMCj27DJc;wIdT@8SFS0e*;Ea4T-Z?YIMX;x7CMcjL#n z2lwJ9xDWT^0X&F@@Gu_1qj(HI#p8GaKf{xF3Qyx1{2b5XIXsUS@FHHq%XkI9z^ix- zzr^c!1HZzX_%+_b+js}>;yt{N5AYlO79Zjxe2m}W6MTx#@HxJ~m-q??-C+N5Fb=_? zI1Gp52pox{a5TPwV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEE)L)VT!@QsF}{gQ za49as<@gq^z?HZPSK}I7i*MsQxDMCj27DJc;wIdT@8SFS0e*;Ea4T-Z?YIMX;x7CM zcjL#n2lwJ9xDWT^0X&F@@Gu_1qj(HI#p8GaKf{xF3Qyx1{2b5XIXsUS@FHHq%XkI9 zz^ix-zr^c!1HZzX_%+_b+js}>;yt{N5AYlO79Zjxe2m}W6MTx#@HxJ~m-q??eTe_%3e5O}H7~ z!}sw6{1CU`R@{c$aR=_iUHB31#*c9i?!`}VAMVEkcn}ZaVLXCI@fd!Jf9cwP`#+v6 zto)Zx=YDV0d|;eDfuG??JcXz641SJh@f@DV3wRMP;bpvnU*J`|hF{`!yn$cgP5c^f z;cfgU*G~J5poPr;`qP1!UmeWfq3_~7ypIp?8~hd@;v;;F-{BK{iqG&lzQC9G3J1k- zAK+jdfEnI;saTTt{HMkbv#&>WXuE!1dE^fq4xEbHW_wfV#5Vzn~+=kn6 z2kyjO_z~{Lk8uy~#ZPb_?#Bao5D(#DJc38@7=DV!@dSQ`C-D@X#xwXip2c%`9xvcU zyo8tW3VwlC@fv=K*YO5^g*WkQyoI;%4&KFkcpo3&H~1|+#7FoTzr!c^6rbU9e1R|V z6%P9Fm+t?UU;F?1(Vt(Kum6fb(Aan9zmFb_LvSb#!{ImrN8%_Pjc?!>9E;;{JWjxg zI0+}?6r76Fa5~PwnK%n);~boe1GoSe;v!s(Z{iYMipy|0zJ)7rC9cBNxCYna+xQNy z!}YiU-^Go%2{+?=_&$DsAL16=dhH+mmX9vY)ZIG(`I@FbqX(|86y$Ful1uU+&0p!a_Gi}QIOP5og0 z9DN=y;6=QIm+=aIfmiVweu>xd27ZM%@oT(=xA6|%#d~-kAK*9mEk49Y_!z&#C-@Yf z;d6X}FYy%)iv802;C(n4hu}~ghQo0Lj>J(o8sES%I2Om@c$|O}aS~3(DL56U;dGpV zGjSHq#yL0_2XFx{#6`Fm-^3-j6qn(0d<$3LN?e7jaSg7;xA7fZhwE_zzKa`i6K=-$ z@O}IMKg2D#6}RDb+<`lB7k-4h@nhVBd+`(8hx_pW9>ha<7?0plJcgg*aXf*a;YmD& zr|}Gaj%V>4p2rJ#5ij9oynY6LAtw#wj=zr{Q#*firOy&c-=77YA?wF2qH+7~jMtxD=P+a(oL{;7VMD zt8opk#kcVtT!-s%1HOwJaT9LF_warE06)YnxD~hIcHDtGaTk7syYXY(gM0B4+=u(| z03O6cco>i1Q9Op9;&D8IpW#V7g{SchevW7H9G=Guco8q*WxRr4;8nbaU*dJVfnVWG z{2FiJZM=hb@gCmC2lx$six2S;KF06x2|mSV_#9v0OMHcc;u^18z;|&Y zZo`*1%Vz=L=Q591L$ipTI%JdP*u zGdzi>@HC#m&+#mt!}E9nFXAPD3c9EQVj1dhZ}I2zx;F*p{-;dq>Y6LAtw z#wj=zr{Q#*firOy&c-=77YA?wF2qH+7~jMtxD=P+a(oL{;7VMDt8opk#kcVtT!-s% z1HOwJaT9LF_warE06)YnxD~hIcHDtGaTk7syYXY(gM0B4+=u(|03O6cco>i1Q9Op9 z;&D8IpW#V7g{SchevW7H9G=Guco8q*WxRr4;8nbaU*dJVfnVWG{2FiJZM=hb@gCmC z2lx$six2S;KF06x2|mSV_#9v0OMHcc;@N*3j6-lJ4#VL%0!QK~9F1?_7#xe^a6C@H zi8u)-;}o2V({MV@z?nD;XX6~4ivzd-7vdsZjBnx+T#CzZIlhG}a3!w7)wl-N;@kKR zuEX`X0pG=qxCuAod-y(nfFI%(+=|<9JMO@pxC=kR-S{!?!M*qi?!*0f01x6JJd8*1 zC?3O4@i?Bq&+sIk!qa#LKgY9p4$tESyoi_ZGG4(i@G4%zFY!9wz_0KoevP;AHr~Oz zcn|O61N;WR#fSI^ALDoU1fSwFe2y>hCBDKzU(f#IU>t%&aTpHA5jYY@;b?pV$KY5T zhvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5STpYj!xDXfNVtf;q;8I+M%keE-fh%zpuEsUE z7T?Bqa2>A44frl@#7(#v-^2Iu1N;!T;8xs*+i?f(#9jCi?#7RC5AMZJa3Ai+19%V* z;bA<2NAVbbipTK;eugLU6rRR2_&J`%b9f#v;6=QIm+=aIfmiVweu>xd27ZM%@oT(= zxA6|%#d~-kAK*9mEk49Y_!z&#C-@Yf;d6X}FYy%)N?`wSFb=_?I1Gp52pox{a5TPw zV{j~v!|^x)C*p6o_GIY)S~%cG$Zes+EmJsGFqRGfy>aR$!BSvVW#;9MNQ1-K9w z;bMFfm*7%dhRg9ST!AZb6|TlLxE9~WcW@o9#|`){Zp2Nv8Q;VA@dNx2x8PRXhTCxm z?!;aA5$?v1aS!grPjDaZ#{+l}58+`vf=BTfeu~HO1b&7m@f4oMGx#~4#dCNbFW^PI zgqQIOet}o<8h(k_@dkc{H}Pw{g}3nz-o<-(A0Oa1_$@xfNB9`O!zcI@pW$I24EBa2$anaTJcmH*gG&#c?&memsB&@em%yBX|^#;iq^UPvB>G5>Mf2JcFO(Sv-g5 z@d94NOL!Tt;1_rmui=+?9dF=QcoVL9=oPpUSK(@0gKP0^ zd&memsB&@em%y zBX|^#;iq^UPvB>G5>Mf2JcFO(Sv-g5@d94NOL!Tt;1_rmui=+?9dF=QcoV@f8kA<{rSo zI0T2{FdU8}a3qex(f9_A!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhIDiXqAuhtj z_$DsFrML{2<6F1_SK=yMjcaf%zK!qTI$Vz%@Lk-9n{YF}hwtMD_#tk=t+)-h;||=3 zyYM62jUVG4+>4*!KHQH7@E{(-!*~Rb;xYUbkK+ma3{T=IJdJ1Yb3BXZ@H}3?i+Bky z;}!e@ui`cQ60hS8{0eX4*LVwW;~l(<_wYVGz;Ezde29EnI;saTTt{HMkbv#&>WXuE!1dE^fq4 zxEbHW_wfV#5Vzn~+=kn62kyjO_z~{Lk8uy~#ZPb_?#Bao5D(#DJc38@7=DV!@dSQ` zC-D@X#xwXip2c%`9xvcUyo8tW3VwlC@fv=K*YO5^g*WkQyoI;%4&KFkcpo3&H~1|+ z#7FoTzr!c^6rbU9e1R|V6%I;a|8X!5!J#+|hvNtwiKB2dzJX(KERMtRH~}Z(B%F*> za4Js2={N&t;w+qvb8s#W-~wETi*PZ%iA!)PF2m*c7OudRxC&R}8eEHS<2$$x*W(6! z7dPT2+>Gzx`}hHVh+A+gZo}=k19##s{0MjB$G8Xg;wQKd_u~OPh==en9>Jq{3_r!= zcmhAelXwbG;~D%M&*C{ej~DPFUc$?G1;4v#jd!khRt-oo2>2k+uNypIp? z8~hd@;v;;F-{BK{iqG&lzQC9G3I}}?`;UWh2oA+zI2=ddNF0Tu@eLeUuCPRAKI6KCOUoP%?502kmwT!f49O&B;YYX|KgK<{7eB#$xE~MTK|F+q@dzHp zWB4f^#}oJ&p2Sml8qeV8*Z!lQ`j));_l@WEf9%WiXX$fz9xvcUyo8tW3VwlC@fv=K z*YO5^g*WkQyoI;%4&KFkcpo3&H~1|+#7FoTzr!c^6rbU9e1R|V6%I;e|8X!5!J#+| zhvNtwiKB2dzJX(K?6rSY)ZIG(`I@FbqX(|86y$Fq12&*KHWh?np(UcoQ$Dqh1c@jBkXuka>*jkoYN-od+g z5AWjx{06_phxiB|<9GN3pW-uojxX>fzQRG@%sqgEaR?5@VK^K|;7A;Wqwx(KgJW?V zj>ic&5hvkfoPtwv8cxRh}vb+{fk;JdgHH{oV{58uZR@I%~!TX7q1#~rv6ci~648$ZT9xEDXceYhVF;6Xfu zhp+vGm>&sjf7AcYovZ$e{1N&n9>Y)ZIG(`I@FbqX(|86y$Fq12&*KHWh?np(UcoQ$ zDqh1c@jBkXuka>*jkoYN-od+g5AWjx{06_phxiB|<9GN3pW-uojxX>fzQRFi>^~01 zAvhF=;cy&*BXJat#y4;bj>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPK0bGC!aS<-Y zH*pCr#bvl0-@+BR5?A4BT!U-zZF~pU;dgq!g_d>=o+4{-}_#cjA9ci>Lk zg&*N={22G(Ui<|2;eI@T2k{Ud#v^zXkKw0y98ch9coI+HX*`3U<5@h1=kWqw#7lS? zuizJW6|do!cpY!xS9lY@##?wB@8Dg$hxhTpwZqrS1AmkDfxQ3o)gk$B=x^~MKElWN z9X`RQ_za)p3w(*MaL~7~|2PCb`I2EVi zbew@RaTd#3i^Cm*H}J3s>MuT!pJ~4X(wv@f}=;>v03ViyLth zZpQcUef$7F#4WfLx8Zi&fje;*euTU6W88y#@e|yK`|$uC#6x%(kKj=}hM(ebJb|C# zNj!z8@eF>BXYm}K#|wB7FX3gpf?wcOyoO)mb-aOJ;Z6J+Z{cmcgLm;B-p2>{4StIc z@ew}8@9+se#b@{&U*Jo8g@e-Be;kZMa3~JL;Wz?E;wT)AZ{Qdli{o%SPQZyc2`A$e zoQl(MI?lkEI16Xv9Gr^-xBwU8B3z7b;u2hn%Wyfqg)49+uEN#02G`=-_ztea^|%4w z#f`WLH{*NwK7N27;uhSB+i*MXz@4}YKf>MkG48>=_zCXA{dfQm;vqbYNAM^f!%y)z zp1{xWB%Z?4cm_Ymvv>~A;|08km+&%P!7uPCUc)c(I^Mvq@FsqZx9~RJ!Mk`5@8bjf z2EWCJ_y`~4clZRK;xl}XFYqP4!a?84{^MXAfEnI;saTTt{HMkbv#&>WX zuE!1dE^fq4xEbHW_wfV#5Vzn~+=kn62kyjO_z~{Lk8uy~#ZPb_?#Bao5D(#DJc38@ z7=DV!@dSQ`C-D@X#xwXip2c%`9xvcUyo8tW3VwlC@fv=K*YO5^g*WkQyoI;%4&KFk zcpo3&H~1|+#7FoTzr!c^6rbU9e1R|V6%NW^|8X!5!J#+|hvNtwiKB2dzJX(KERMtR zH~}Z(B%F*>a4Js2={N&t;w+qvb8s#W-~wETi*PZ%iA!)PF2m*c7OudRxC&R}8eEHS z<2$$x*W(6!7dPT2+>Gzx`}hHVh+A+gZo}=k19##s{0MjB$G8Xg;wQKd_u~OPh==en z9>Jq{3_r!=cmhAelXwbG;~D%M&*C{ej~DPFUc$?G1;4v#jd!khRt-oo2> z2k+uNypIp?8~hd@;v;;F-{BK{iqG&lzQC9G3I}}~`;UWh2oA+zI2=ddNF0Tu@eLe< zV{sgg#|bzQC*fqAf>UuCPRAKI6KCOUoP%?502kmwT!f49O&memsB&@em%yBX|^#;iq^UPvB>G5>Mf2JcFO(Sv-g5@d94NOL!Tt z;1_rmui=+?9dF=QcoV!rk~W?!mqI3GT!FcmNOL zAv}yn@F*U`Pw_aOz|Zg`p2E|320zEMcn;6w1-yut@G@S(FYqc}!!PkV-oUT$CVq{# z@HXDTyLb=p;{*H#zr~062p{8j_ynKgGklIO@Fl*&LEp~)<6saR$!BSvVW#;9MNQ1-K9w;bMFfm*7%dhRg9ST!AZb z6|TlLxE9~WcW@o9#|`){Zp2Nv8Q;VA@dNx2x8PRXhTCxm?!;aA5$?v1aS!grPjDaZ z#{+l}58+`vf=BTfeu~HO1b&7m@f4oMGx#~4#dCNbFW^PIgqQIOet}o<8h(k_@dkc{ zH}Pw{g}3nz-o<-(A0Oa1_$@xfNB9`O!zcI@pW$GXd0Vm=loQzX&Do(@cI0I+mES!yVa4rtu0$hlTa527#OK>SJ!{zuE zuE3SJ3RmMAT#IkxJGc(l;|6>eH{vGTjPK$5_yK;1TW~9G!|k{Ocj7Mm2zTShxCi&* zC%6yy;{iN~hwv~S!J~K#KgHvC0zbo(cnVMB8T=g2;yFBz7w{rp!pnFCzrd?_4Zp6IS29Ck8I1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQkq3veMW!o~O|F2SX^ z4430uxB^$=DqM|ga4o)#@8CLIj~nn^+=!cSGrouK;|KU5Zo#d%4Y%VC+=;vJBixN2 z;~w0LpWr^+j|cD|9>T+T1drk|{1lJl3H%IC;we0hXYg}8i|6n>UcifZ2`}Rn`~t7y zHT)8<;|=@@Z{pW@3vc5cyo>knK0d&2@LPO{kMJ>mhfnY+KEvnu0$<`Q9F)!e<6sh}tJ)WSb8awi^ZP!MA5M?JkvIxR;~O{z$Kp5~j}verPQuAJ1*hUPoQ^Ya zCeFgyI0xtA04~6VxCj^Ho45p*;xb&0Z{Z4DiK}omuEDkVHok-Fa6N9ocX1T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPK0bGC!aS<-Y zH*pCr#bvl0-@+BR5?A4BT!U-zZF~pU;dgq!g_d>=o+4{-}_#cjA9ci>Lk zg&*N={22G(Ui<|2;eI@T2k{Ud#v^zXkKw0y98ch9coI+HX*`3U<5@h1=kWqw#7lS? zuizJW6|do!cpY!xS9lY@##?wB@8Dg$hxhRTeuLlQLwtmf@jHBiPw^Q(#~1h#U*Vt} z_8$l15FCoba5#>@kvIxR;~O{z$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtA04~6V zxCj^Ho45p*;xb&0Z{Z4DiK}omuEDkVHok-Fa6N9ocX1@kvIxR;~O{z$Kp5~j}verPQuAJ z1*hUPoQ^YaCeFgyI0xtA04~6VxCj^Ho45p*;xb&0Z{Z4DiK}omuEDkVHok-Fa6N9o zcX1UuCPRAKI6KCOUoP%?502kmwT!f49O&B;YYX|KgK<{7eB#$xE~MTK|F+q@dzHp zWB4f^#}oJ&p2Sml8qeV8coxs$dAxuZ@e*FfEBFOo#cTK_UdJ2w72d?J@fP03J9roG z;eC97-{80S5Fg=V{0^VsQ+$Tc@ddubS2!q-{l~#L1c%}<9F8M!B#y$-_y&%_AHH_R zKm6`OLj8Zq8#?*RKrB5D$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhIDiXqAuhtj_$DsF zrML{2<6F1_SK=yMjcaf%zK!qTI$Vz%@Lk-9n{YF}hwtMD_#tk=t+)-h;||=3yYM62 zjUVG4+>4*!KHQH7@E{(-!*~Rb;xYUbkK+ma3{T=IJdJ1Yb3BXZ@H}3?i+Bky;}!e@ zui`cQ60hS8{0eX4*LVwW;~l(<_wYVGz;Ezde29Gzx z`}hHVh+A+gZo}=k19##s{0MjB$G8Xg;wQKd_u~OPh==en9>Jq{3_r!=cmhAelXwbG z;~D%M&*C{ej~DPFUc$?G1;4v#jd!khRt-oo2>2k+uNypIp?8~hd@;v;;F z-{BK{iqG&lzQC9G3J2x0|2PCb`I2EVi z^lP8gl?Q(R$Nx{>Pkm!?eg-`gXW?v|gL82J7vMr%gp2V_T!Kq+87{}Sa0RZ!Rk#}0 z{C`~5@hdoh1LpT>NoJBHIg^rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zAN+`)@H2kFulNnW;}86azwkHy!M`{lkN)F89E5{$2oA+zI2=ddNF0TuaSV>daX20) z;6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v z;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ z;6L~gKjCNmf?x3)e#am96Mx}v{DXgSz`sQQaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~ zj}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{e zj~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!b zk00rsL98cg$JcXz644%bv zcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9 z_#QvtKll+p;b;7UU-27$#~=6;f8lTZgMV?rzfAveAP&O8I0T2{FdU8}a3qex(KrUj z;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y z;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC z;yZkgAMhXih@bE?e!;K!4Zq_L{E5HtH~zuDIG}+3<3JpQgK-EB#bG!cN8m^tg`;r{ zj>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5 zuElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Sd< z_gDXO4`aLkS!7V|fBWAg{$+-J7SG{%ynq++5?;nDconbVb-aN$@fP03J9roG;eC97 z5AhK`#wYj`pW$Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8 zT!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gm|KLabgrD&Xe#LM29e?0Y{Dr^q5B|jg zh4dc>;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn z`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t| z{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN z`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0sp~|_z6Gb7yOFf@H_s%pZE)Z;~)Hs z1O65Ij{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U;dGpVGjSHq#yL0_ z=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1CJ8>88#yz+f z_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy z@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;Ea^;79y~pYaQR#c%i>f8bC2g}?C+ z{>1@B^dAS}ARLVUnZNoa{Qvx4{`>g@kvIxR;}{%^<8VAq zz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18 zz>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpU zz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f- zz<=-~e!|cA1;64q{Ek2HC;r0U_y_;ufPa-gfCF(54#puk6o=t(9DyTo6pqF*I2Om@ zc$|O}aS~3(DL56U;dGpVGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9yp zdfb2;aT9LFEw~l8;db1CJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$ zdAxuZ@e=+Y|LXtuU-;*D|M&cWzxCgr{WE`AW?#Xpcnz=P4ZMlB@HXDTyLb=p;{$w% zkMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{0BecC;W_G@GE}9@Aw0M;xGJ-fAB93DCQ5~ zKpcdFaR?5@VK^K|;7A;Wqj3z5#c?Sx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P z!K-);uj388iMQ}J-od+g5AWjxe2968e#7th1ApQ#{EdI`FAn(E_yaf)2jO5GfxDhwu zX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q* zWxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z!-> zPxu+X;8*;H-|+|j#9#Ou|KML7P(uH4AP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2C< z!pS%Vr{Xl6jx%s3&cfL^2j}8EoR14|AuhtjxCEEtGF*-;a3!w7)wl-N;yPT98*n3T z!p*n^x8gS3jyrHC?!w);2lwJW+>ZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp z!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhXi zh@bE?e!;K!4Zq_L{E5HtH~zuDIN)EW|2Pl_;b0tsLva`m#}POZN8xB3gJW?Vj>ic& z5hvkfoPtwv8cxRn4#L4W1c%}<9F8M!B#y$-I0nb!I2?}? za3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxaP0^zyGg) zl>6{EOa9hu=)d@vTJ}0zj~j3!ZoSeNC+@=CxCi&*KHQH7@E{(-!*~Rb z;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F z;xl}XFYqP4!q@l)-{L!bk00{5Fg=V ze1cE$89v7s_!3{?YkY%m@g2U$5BLv$#83Dczu;H=hTriA{={GS8~@;69Pn?@e;kN| za4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlT za4{~yrML{2;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^& z@G(BYr}zw?;|qL=ukba#!MFGh-{S}T2S4H`{ET1lD}KZ8_yd39FZ_*v@GlN1r~fz* z2jO5GfxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l} z58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWj zAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z!->Pxu+X;8*;H-|+|j#9#Ou|KML7@Nd$8 z9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|o zT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8% zJcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjx ze2968e#7th1ApQ#{EdI`FAk`n z|2Pl_;b0tsLva`m#}POZN8xB3gJW?Vj>ic&5hvkfoPtwv8cxR*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO z=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T- z_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5 z@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mA*=;wSu!U+^n_!|(V5f8sCvjeqbj z4ydI6I1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?5 z9?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j5VNj!z8@eH2Db9f#v;6=QI zm+=Z-#cOyS|IdH*-{|?fyZ_-o3I9uR$@fE(tH~1Fc;d}gm|KLabgrD&Xe#LM29e?0Y{Dr^q5B|jgf0uuN191=z#vwQq zhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdzo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkE zI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@p zxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvW zcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!ze5BYwiq_yxb>H~fx2 z@F)Jl-}ndr;(&jf{^LL#goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R z;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtKll+p;b;7UU-27$ z#~=6;f8lTZgMV>AHT}ndI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9 z&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I z?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu z-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z<=-~e!|cA1;64q z{Ek2HC;r0U_y_;ufWJroaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUP zoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^Y zypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00rsL98cg$JcXz644%bvcpfj{MZAQU@d{qW zYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtKll+p;b;7U zU-27$#~=6;f8lTZgMV?r->3gL5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeG zQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=| zTX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvn zSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BLv$#83Dc zzu;H=hTriA{={GS8~@;698gRDaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ z1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G z1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BLv$ z#83Dczu;H=hTriA{={GS8~@;69PsbZe;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ? zh?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&!*W(7< zh?{UTZo#d%4Y%VC+=;tzH}1i`xDWT^0X&F@@Gu_1qj(ID;|V;8r|>kM!LxV{&*KHW zh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}T z2S4H`{ET1lD}KZ8_yd39FZ_*v@GlOir~fz*2jO5Gf zxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Gu zco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8 z_z!->Pxu+X;8*;H-|+|j#9#Ou|KML7@bA)p9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL z;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~ z;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A z;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe2968e#7th1ApQ#{EdI`FAiv+|2Pl_;b0tsLva`m#}POZN8xB3gJW?V zj>ic&5hvkfoPtwv8cxR*Zs zI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6 zxDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik; zcn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O z_zvIW2mA*=;wSu!U+^n_!|(V5f8sCvjeqbj4rrwRI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8ri zYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@ zZ}A{5Fg=Ve1cE$89v7s_!3{? zYkY%m@g2U$5BLv$#83Dczu;H=hTriA{={GS8~@;69Pl5|e;kN|a4-(Rp*ReO;|Lsy zqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4f zt8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL= zukba#!MFGh-{S}T2S4H`{ET1lD}KZ8_yd39FZ_*v@GlN%rvEq)2jO5GfxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S z5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM z5?|qKe1mWC9lpm8_z!->Pxu+X;8*;H-|+|j#9#Ou|KML7@E_8D9EgK(Fb=_?I1Gp5 z2pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$ z3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@ z2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe2968e#7th1ApQ#{EdI`FAiv-|2Pl_;b0tsLva`m z#}POZN8xB3gJW?Vj>ic&5hvkfoPtwv8cxR*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^ z442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{ z43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE> z44>l*e2K5{HNL^O_zvIW2mA*=;wSu!U+^n_!|(V5f8sCvjeqbj4rrzSI1mTnU>t%& zaTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAht zaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7 z@fkkH7x)ri;cI+@Z}A{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BLv$#83Dczu;H=hTriA z{={GS8~@;69Pppee;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_ zoQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&!*W(7r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l) z-{L!bk00rsL98cg$JcXz6 z44%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^ z4Zg*9_#QvtKll+p;b;7UU-27$#~=6;f8lTZgMV?re@g#xAP&O8I0T2{FdU8}a3qex z(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX z(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*& z*Z2nC;yZkgAMhXih@bE?e!;K!4Zq_L{E5HtH~zuDIG}_6<3JpQgK-EB#bG!cN8m^t zg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxq zg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*B zg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8 zg|G1qzQuR=9zWne_z^$hXZ(U+@f&`}ANUi0;cxtde{sNnM*ndj4#L4W1c%}<9F8M! zB#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSn zC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*u zB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>h zCBDMf_y*tNJA98H@E`n$pYSt&!LRrYzvB=5iNEkS{=vUEpp*XNKpcdFaR?5@VK^K| z;7A;Wqj3z5#c?6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9 zjw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>r zjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uo zjxX>fzQWh|2H)a4e2*XSAN+`)@H2kFulNnW;}86azwkHy!M`}5i~i$49E5{$2oA+z zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_ zcpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV z_#9v0OMHc|@eRJkclaJZ;6L~gKjCNmf?x3)e#am96Mx}v{DXgSz(1t_I1mTnU>t%& zaTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAht zaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7 z@fkkH7x)ri;cI+@Z}A6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1* zm*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3 zkK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHao zpW-uojxX>fzQWh|2H)a4e2*XSAN+`)@H2kFulNnW;}86azwkHy!M`}*zo7p(5C`F4 z9D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQM zT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#D zJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=V ze1cE$89v7s_!3{?YkY%m@g2U$5BLv$#83Dczu;H=hTriA{={GS8~@;69MD7maUc%D z!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(- z!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J z$M^)F;xl}XFYqP4!q@l)-{L!bk00xDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l} z58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWj zAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z!->Pxu+X;8*;H-|+|j#9#Ou|KML7&`bYu zAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzF zARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp? zAwI&#`2Xj!k6!@*0KdO8b&^SPl1y^WB$-T-%;W1MNis>2B*`R6Cdp*xoO8}Ok9o{v z<}oufGxM04nVFenk|aqc$s|dVzCXWyeD(PQ-k%eEiqG&lzQC9G3SZ+Je2ee! zJ$}HC_z6Gb7yOFf@H_s%pZE)Z;~)Hs|8T&+PycZs4#L4W1c%}<9F8M!B#y$-I0nb! zI2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYna zI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hn zIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tN zJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!&`1AqAP&O8I0T2{FdU8}a3qex(KrUj z;y4_S6L2CLkg}ZSN?!|q$9}nO` zJcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaD ze1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;Qvbh zaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dU za3LSeNC+@=CxCi&*KHQH7 z@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+( z@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00ic&5hvkf zoPtwv8cxR*ZsI1b0-1e}PI za57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37 za5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut z@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi z@H2kFulNnW;}86azwkHy!N2$q2Mo}E9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?D zlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_ zn{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08k zm+&%P!K-);uj388iMQ}J-od+g5AWjxe29Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_z zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$N ze#B4s8Nc9H{D$B02mZug_#6M=U;Kvy2I)Tz#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^ z0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s% z18&4kxEZ(LR@{c$aR=_iUAP)Jra4e3) z@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG z^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg z^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h= z_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)%7^dAS}ARLTCa3~JL;Wz?E;wT)AV{j~v z!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D z!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ z!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U? z!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|4;gl191=z#vwQqhv9G>fg^Dgj>a)K z7RTXuoPZN?5>Cb`I2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2 zcoxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd z_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWiD7yZY9I0y&h5FCoba5#>@kvIxR z;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5 z;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG z;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_ z;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^zzF@vfj9^U;}9H*!*Do`z>zo# zN8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZP zSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0i zPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6 zU*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKQhY{^LL#goAMi4#irsL98cg$ zJcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr| ze1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrXyD9E5{$2oA+zI2=dd zNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQ zNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0 zOMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;Gfce9EgK(Fb=_?I1Gp5 z2pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$ z3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@ z2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw? z;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsC2lO8Y;vgK1LvSb# z!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ z!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f z!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)| z!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0ps)^2jUa4Js2={N&t;w+qvb8s%s!}+)X7yhIFKi|>0 zi+L%z|EKzY^B;=Xi*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmN zaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG- z@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y z@elsRe>mWu(SIC>gK#ho!J#+|hvNtwiKB2dj=`}w4#(pJoQRWfGETv%I1Q)c44jFx za5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cSGj74HxDB`C4%~^m za5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_ZGG4)}cnz=P4ZMlB z@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~5B!P0 z@HhU!zxWRaOwfNEh=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2mFWh9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B! zXW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{O zcj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|Qkh zZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5 zf8sCvjeqbj{=)&2^dAS}ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq z183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k z19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n z18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th z1ApQ#{EdI`FaE;;|D68gKpcdFaR?5@VK^K|;7A;Wqj3z5#c?T~}9w*>LoP?8c3Qomo zI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2 zxE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx) zcpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K z_#J=XPyB_y@elsRe>mVjqW?G$2jO5GfxDhwuX54~X zaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{ z@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+ z@f&`}ANUi0;cxtdfAJp{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7U zU-27$#~=6;f8lTZgMaZK4)~AhKMuq}I2ecEP#lKCaRiRUQ8*gM;8+}o<8cB`#7Q_A zr{GkahSPBd&csv02a#7(#v zx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS? zui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dc zzu;H=hTriA{={GS8~@;6{D%W(=sym`K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z# z8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv z8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s z8Nc9H{D$B02mZug_#6M=U;Kvy{ssNVfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc z2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW z2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ z2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T z2|wc({EFZ3JO03*_zQpIAN-5|aKJ46$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_ zxCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+y zcnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS z@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H z@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!Fh~D!AP&O8I0T2{FdU8}a3qex(KrUj;y4_S z6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz z7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkg zAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pppge;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F z$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&! z*W(7kM!LxV{ z&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh z-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwrn(|;U@gK#ho!J#+|hvNtwiKB2dj=`}w z4#(pJoQRWfGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq z4%g!b+=!cSGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t z4$tESyoi_ZGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn z4&UPk{D`0MGk(FZ_zl0~5B!P0@HhU!zxWRa{ActZ2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajR zEw01$xB)lfCftl$a4T-Z|IRe2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IE zAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABBkM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O6 z1AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsC z=ky;3;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn z`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t| z{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN z`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u z0gLn>2jUa4Js2={N&t;w+qvb8s%s z!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA z!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW z!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABB< z!vX&V{l|eg2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4 zi}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1 zi~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=j zi}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tg zi~n%I68*=4I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogT zoQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl z+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5c zyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu z{EPo^z<){qaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgy zI0xtAJe-dUa3LSeNC+@=C zxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^ zcn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk006G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e z**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7< z-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E z+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl z-}ndr;y)blU(tUYh=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2dvP49EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHq ziL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}n ziMwz&?!mpd5BK8%Jou09`RD)ifB9b%KeGRotAu|t#6FBi@F*U`<9Gs3;we0hXYeeZ z!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U? z!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|26%`fj9^U;}9H*!*Do`z>zo#N8=bA zi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m zi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKI}4$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMA zT#M^)J#N5_xCuAo7Tk*4a69h6owy7CyZ`9_@4x?l+5Okv|9S3z)!FqA-RwQM7x&?Q zJb(xB5FW-OcodJ}aXf)1@f4oMGk6xy;d#7(7x5Ba#w&Ogui@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysda zf5{)ffj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$eoQl(M`ak+V`BwvT=l-q# zWA6W6_^j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq z%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0 z&-ewu;y3(`Kkz61!r%A@|KdL!utxuJAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2C< z!pS%Vr{Xl6jx%s3&cfL^2j}8EoR14|AuhtjxCEEtGF*-;a3!w7)wl-N;yPT98*n3T z!p*n^x8gS3jyrHC?!w);2lwJW+>ZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp z!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7 z!q4~xzv4Iijz91x{=(n*2mj(f9Pr=Je;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ? zh?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&!*W(7< zh?{UTZo#d%4Y%VC+=;tzH}1i`xDWT^0X&F@@Gu_1qj(ID;|V;8r|>kM!LxV{&*KHW zh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}T zh@bE?e!;K!4Zq_L{E5HtH~zuD_zwrH(|;U@gK#ho!J#+|hvNtwiKB2dj=`}w4#(pJ zoQRWfGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b z+=!cSGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tES zyoi_ZGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk z{D`0MGk(FZ_zl0~5B!P0@HhU!zxWRa{I~QU2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$ zxB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&j zcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC| z_yIrSC;W_G@GE}9@Aw0M;xGJ-fABB)Jra4e3) z@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG z^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg z^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h= z_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)(P9sS3FI0y&h5FCoba5#>@kvIxR;}{%^ z<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx z>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q z=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7 z@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^z$X32fj9^U;}9H*!*Do`z>zo#N8=bA zi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m zi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKL|0|8XD=!ofHMhvG0Cjw5g+j>6G6 z2FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#0 z2G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|3 z2G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh| z2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)a)MgMUi4#L4W1c%}<9F8M!B#y$- zI0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBN zxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4 zcm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf z_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!@ITOh9EgK(Fb=_?I1Gp52pox{ za5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+ za5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X z@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL= zukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCkMti0;vgK1LvSb#!{Imr zN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXF zSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}Y zPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({ zU*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0Xy^`2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x z0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2! z0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu z0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABB9Zm2oK{CJc`Hg zIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g2p{7Ue2UNT zIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tgi~n%IF8#-WI0y&h5FCob za5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQ za5=8PmADF5;~HFx>u^18z>T;GH{%xEirfDGT-M<$007|L`FSBpk|arzBuSDaNs=T< zk|fC_Ns=T2jUa4Js2 z={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9 z@Aw0M;xGJ-fABB)Jra4e3)@i+k|;v}4mQ*bIy z!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G z!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc z!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_ z!|(V5f8sCvjeqbj{=)(P6aB}5I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^H ziqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xE zira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)w zir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRnt zir?@%{=lF33xDGu{EPo^z&`!Qfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$e zoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w z+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rn zyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc( z{EFZ3JO03*_zQpIAN-5|aKJyK|2Pl_;b0tsLva`m#}POZN8xB3gJW?Vj>ic&5hvkf zoPtwv8cxRxDhwu zX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q* zWxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$h zXZ(U+@f&`}ANUi0;cxtdfAJp<_~-N=2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lf zCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfs zCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrS zC;W_G@GE}9@Aw0M;xGJ-fABB)Jra4e3)@i+k| z;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|g@IU_l{XhJ} zKiwS(>C68||LC9O)w0*&dfb2;aT9LFEw~l8;db1CJ8>88#yz+f_u+m#fCupq9>ybh z6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b z6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWiDnLmI7aS#s1 zAvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-Y zCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%y zBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8 zC-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fFt^k191=z z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdrsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iL zgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9nEvBH z9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4 zT!f2p2`Lkg}ZSN?!|q$9}nO` zJcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaD ze1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;9t;x z9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|o zT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8% zJcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjx ze29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O6 z1AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsC zOZtxkaS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&P zd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~ zemsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;C zeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}- zfK&R9191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1 z;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$ z;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}> z;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr z;y)a4M*ndj4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n z=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB z_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU z@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@ z|KdL!@UQ7V4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S z=ipqNhx2g(F2qH+7? z_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9 z@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou z|KMNzhXc;(KMuq}I2ecEP#lKCaRiRUQ8*gM;8+}o<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV z8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY z8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS z8~@;6{D%YnSNe|waS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO z7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB z7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I z7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD95 z7yiaS_!s}-fD8JM191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@R zaTdrsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R z;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5 z;7|O8zwr9lK$gB9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4Pl zXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOyS zZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i> zf8bC2g}?C+{>6Vd;D4w8I1mTnU>t%&aTpHA5jYY@;bUuC zPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-ex zUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}Aic&5hvkfoPtwv z8cxRJ(o8pq&R9Eam^0#3w9I2otl zRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(L zR@{c$aR=_iUAPm4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp& z;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt z;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X z;8*;H-|+|j#9#Ou|KMNzhXei%{l|eg2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)- z;}o2V({MV@z?nD;XX6~4i}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn z;}+bC+i*MXz@4}YcjF%1i~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky z;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c z;}`sj-|##Bz@PXFf8!tgi~n%I4gJS~I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2s zC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;G zH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZ zFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL| zKjRntir?@%{=lF33xDGu{EPo^!2e1AaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}ver zPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPF zUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk006G62FKz!9FG%l zB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-Y zBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zBYwiq_yxb>H~fx2@F)Jl-}ndr;y)blf6;#&h=Xu24#A-~42R*ZsI1b0- z1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj z2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w z1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW z2mFYi@H2kFulNnW;}86azwkHy!N2$q2i(zr9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL z;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~ z;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A z;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn z;d@fE(tH~1Fc z;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdad-{(9aS#s1AvhF=;cy&*BXJat#xXb+ z$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW z*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p z&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD z-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fPY8-aUc%D!8inm;xHVJBXA^+!qGSe z$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L z&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l) z-{L!bk006G6 z2FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#0 z2G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|3 z2G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh| z2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)bl@994d#6dV1hu}~ghQo0Lj>J(o z8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi z8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPD3c9EQVj1dhZ} zI2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlG zcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r z_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXej^`i}!~5Dvy6I24EBa2$an zaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEc zaTTt{HMkbn;d z@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaC;E>AaS#s1AvhF=;cy&* zBXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{Go zD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~H zC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X} zFYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd7a7<3JpQgK-EB#bG!c zN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0 zSKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJ zPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{& zU*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mWo{^LL#goAMi4#irsL z98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF z9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9KhS?1h=Xu24#A-~ z42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^ z442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{ z43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE> z44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2fWaK9EgK(Fb=_? zI1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQS zxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69 zcnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6h zxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaSNe|waS#s1 zAvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-Y zCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%y zBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8 zC-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd7~N<3JpQ zgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4 zi*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfu zhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?b zkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mWc{^LL# zgoAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iL zgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9|D*pn z5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~ z5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao z5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{ z5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK4tS^k zI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)F zxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X z_z)lAV|;>7@fkkH7x)ri;cI+@Z}AD!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$r zcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY z_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;; zAM_sw;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn z`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t| z{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN z`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u z0soo)<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPK zc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!gr zeYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_ zdw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsR ze>mWi{^LL#goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|% z#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9ztDdih=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f z!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz| z!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ z!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy z!N2$q2Yk_g9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv z&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz& z?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J z-od+g5AWjxe29Y6LAtw#wj=zr{Q#*firOy z&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV z{>DG}7ysdaZ~Bh|aS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO z7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB z7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I z7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD95 z7yiaS_!s}-fPbL>I1mTnU>t%&aTpHA5jYY@;bUuCPRAKI z6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F z6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}Aic&5hvkfoPtwv8cxR< zI1^{#Y@CC0aURac1-K9w;bL5ZOK}-4#}&8|SK(@0gKKdeuE!0y5jWvx+=5$i8*axP zxD$8bZrp==aUbr-19%V*;bA<2NAVaQ#}jxGPvL1igJGXd0Vm=loQzX&Do(@c zI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN> zxC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3u zcmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8 z_yd39FZ_*v@Gt(u0l)Mg2jUa4Js2 z={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01=ovS*01pojTJNZzFARfZQcm$8)F+7eZ z@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO z@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pt10e;kN|a4-(Rp*ReO z;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2 z;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw? z;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsC@qZkMgK#ho!J#+| zhvNtwiKB2dj=`}w4#(pJoQRWfGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJti zm*WatiK}omuEDjq4%g!b+=!cSGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K# zkK+kEiKp;1p24$t4$tESyoi_ZGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5P zpW_RBiLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~5B!P0@HhU!zxWRa{KG%~!|6Z&bM8O< z|NRpV#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C z^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPD3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqN zhx2g(F2qH+7?_uyXK zhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$ zhxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNz zhXekR|KmU$goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|% z#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9pZGrx#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW# z;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAP88 z#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU z#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m- z#y|KM|KWf@{*ME35Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy z&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV z{>DG}7ysda|B?UWKpcdFaR?5@VK^K|;7A;Wqj3z5#c?T~}9w*>LoP?8c3QomoI2~u; zOq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)k zPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!x zO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=X zPyB_y@elsRe>mWO;{P}h2jO5GfxDhwuX54~XaT{*O z9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtdfAJp<2{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$ z#~=6;f8lTZgMaZK4)~w>KMuq}I2ecEP#lKCaRiRUQ8*gM;8+}o<8cB`#7Q_Ar{Gka zhSPBd&csv02a#7(#vx8PRX zhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@ zhS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H= zhTriA{={GS8~@;6{D%WV_&*NBK{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y> zoQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8 z+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H z{D$B02mZug_#6M=U;Kvy{ulm_191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb` zI2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*Ff zD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W= zFZdO|;dlIjKk*m-#y|KM|KWiDmH*>F9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+ zlW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QI zm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~ zpYaQR#c%i>f8bC2g}?C+{>6VdAdLUxKpcdFaR?5@VK^K|;7A;Wqj3z5#c?kM!LxV{&*KHW zh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}T zh@bE?e!;K!4Zq_L{E5HtH~zuD_zwq!^M4$OgK#ho!J#+|hvNtwiKB2dj=`}w4#(pJ zoQRWfGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b z+=!cSGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tES zyoi_ZGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk z{D`0MGk(FZ_zl0~5B!P0@HhU!zxWRa{7e3i191=z#vwQqhv9G>fg^Dgj>a)K7RTXu zoPZN?5>Cb`I2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$ zdAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&c zd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgt#s6_24#L4W1c%}<9F8M!B#y$-I0nb! zI2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYna zI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hn zIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tN zJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!5Xt{>AP&O8I0T2{FdU8}a3qex(KrUj z;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y z;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC z;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9PqFCKMuq}I2ecEP#lKCaRiRUQ8*gM z;8+}o<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB z;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy= z;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%Xg_&*NBK{yzP;7}Zf!*K+T#8EgJ z$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ& z*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl z-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{x|-Q191=z#vwQqhv9G>fg^Dg zj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i z6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V z6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgt!~bz04#L4W1c%}<9F8M! zB#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSn zC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*u zB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>h zCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!5X1j*AP&O8I0T2{FdU8} za3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ z@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO z@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pn@XKMuq}I2ecEP#lKC zaRiRUQ8*gM;8+}o<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn z@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc z@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%W#`9BWCK{yzP;7}Zf z!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M z%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t z&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{&)V5191=z#vwQq zhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh z6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b z6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgt$NzC44#L4W z1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ z1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T z1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN z1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!5YPW{AP&O8 zI0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQ zcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&# z_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Psb?KMuq} zI2ecEP#lKCaRiRUQ8*gM;8+}o<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6c zco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy z_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%V)_&*NB zK{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyG zMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@c zNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{saHV zfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@; zg}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZv zgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4k zhxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|a6lse z$ALHq2jdVNioc zz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%V zz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVG zz=!wxDhwuX54~XaT{*O9k>&B;cnc6dvPD` z#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H z#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp< zNaFuE5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n z&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow z?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B z-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK z4){;}9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4= z&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq} z?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a z-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj z{=)&u{2vG6ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pE zF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1 zFYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8 zF5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI` zFaE;;|C#^eKpcdFaR?5@VK^K|;7A;Wqj3z5#c?T~}9w*>LoP?8c3QomoI2~u;Oq_+Y zaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmN zaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG- z@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y z@elsRe>mX3@P8bLgK#ho!J#+|hvNtwiKB2dj=`}w4#(pJoQRWfGETv%I1Q)c44jFx za5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cSGj74HxDB`C4%~^m za5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_ZGG4)}cnz=P4ZMlB z@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~5B!P0 z@HhU!zxWRar1F0ph=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2mDw5j{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U;dGpV zGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1C zJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$ zH}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIj zKk*m-#y|KM|KWf%{*ME35Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#* zfirOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZM zfj{vV{>DG}7ysda|AYVIKpcdFaR?5@VK^K|;7A;Wqj3z5#c?<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3Qomo zI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2 zxE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx) zcpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K z_#J=XPyB_y@elsRe>mX(xDhwuX54~X zaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{ z@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+ z@f&`}ANUi0;cxtdfAJp<$l(7t5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeG zQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=| zTX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvn zSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7U zU-27$#~=6;f8lTZgMaZK4*0+LKMuq}I2ecEP#lKCaRiRUQ8*gM;8+}o<8cB`#7Q_A zr{GkahSPBd&csv02a#7(#v zx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS? zui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dc zzu;H=hTriA{={GS8~@;6{D%WF`9BWCK{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z# z8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv z8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s z8Nc9H{D$B02mZug_#6M=U;Kvy{u}?tfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc z2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW z2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ z2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T z2|wc({EFZ3JO03*_zQpIAN-5|a6lIS$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_ zxCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+y zcnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS z@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H z@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!kj?*bAP&O8I0T2{FdU8}a3qex(KrUj;y4_S z6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz z7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkg zAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pr=yKMuq}I2ecEP#lKCaRiRUQ8*gM;8+}o z<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F} z=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$ z@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%W__&*NBK{yzP;7}Zf!*K+T#8EgJ$KY5T zhvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-Q zhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z( zhwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{vZC2191=z#vwQqhv9G>fg^Dgj>a)K z7RTXuoPZN?5>Cb`I2EVibew@RaTd zNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{sI5v zKpcdFaR?5@VK^K|;7A;Wqj3z5#c?SeNC+@=CxCi&*KHQH7 z@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+( z@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$r zcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY z_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;; z`TUOqaS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&P zd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~ zemsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;C zeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}- zfPcjQI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?5 z9?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~ z9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Oz zcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD z_zwsCWB$j1I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogT zoQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl z+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5c zyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu z{EPo^Kq3F*KpcdFaR?5@VK^K|;7A;Wqj3z5#c?v02a#7(#vx8PRXhTCxm?!;ZV z8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY z8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS z8~@;6{D%XI_#X%2ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qa zoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s z+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fj zyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ# z{EdI`FaE;;|CIl6AP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco z-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x z{=(n*2mj(f98k>vI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI z6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F z6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AY6LAtw#wj=zr{Q#* zfirOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZM zfj{vV{>DG}7ysda68^`5I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9 z&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I z?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu z-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@% z{=lF33xDGu{EPo^z(41I9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6 zr{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Ir zx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-); zuj388iMQ}J-od+g5AWjxe29v02a#7(#vx8PRX zhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@ zhS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H= zhTriA{={GS8~@;6{D%Yn1^?qf9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8 z#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z- z#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR z#c%i>f8bC2g}?C+{>6Vdpp5@ZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFC zui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~x zzv4Iijz91x{=(n*2mj(f9Plss9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4m zQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdT zTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{ zSMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu! zU+^n_!|(V5f8sCvjeqbj{=)&~{Eq{15Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw z#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@ z#xM94zu|ZMfj{vV{>DG}7ysda|BwH1AP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2C< z!pS%Vr{Xl6jx%s3&cfL^2j}8EoR14|AuhtjxCEEtGF*-;a3!w7)wl-N;yPT98*n3T z!p*n^x8gS3jyrHC?!w);2lwJW+>ZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp z!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7 z!q4~xzv4Iijz91x{=(n*2mj(f98kgkI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j< zH{oX7f?IJLZpR(C6L;Zm+=F{@AMVEkcn}ZaVLXCI@faS*6L=C&;b}aBXYm}K#|wB7 zFX3gpf>-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AD!}YiU zH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9n zFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_B zKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;mHdwbaS#s1AvhF=;cy&*BXJat#xXb+$KiOK zfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5H zfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6Ez zfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`w zfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fPcmRI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW} z#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{ z&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh z-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCYyQW9I0y&h5FCoba5#>@kvIxR;}{%^ z<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx z>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q z=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7 z@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^KsEp4KpcdFaR?5@VK^K|;7A;Wqj3z5 z#c?v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB z;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy= z;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%W-_#X%2ARLTCa3~JL;Wz?E;wT)A zV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH? zYj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0h zXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZO zZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|Cax8AP&O8I0T2{FdU8}a3qex z(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX z(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*& z*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f98k;uI1mTnU>t%&aTpHA5jYY@ z;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t z;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri z;cI+@Z}AY6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEc zaTTt{HMkbn;d z@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaI{wFjI0y&h5FCoba5#>@ zkvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8P zmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2p zlXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~ zm-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^z`y5z9EgK(Fb=_?I1Gp5 z2pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$ z3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@ z2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn z@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc z@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%Yn1OMYd9E5{$2oA+z zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_ zcpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV z_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vdpn?B!AP&O8I0T2{ zFdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8) zF+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKg zGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pl6c9|z(f9E?M7 zC=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_ zDK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1 zC?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=< zDL%vJ_yS+zD}0S_@GZW>_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)%{{Eq{15Dvy6 zI24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6h zxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysda|HS_|5C`F4 z9D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQM zT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#D zJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=V ze1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK4rt~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29Onh z2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4i}P?kF2IGj z2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1i~Ddt9>9Zm z2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g z2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tgi~n#yGymg2 z9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4 zT!f2p2`Lkg}ZSN?!|q$9}nO` zJcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaD ze1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;J@%c z4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g( zF2qH+7?_uyXKhx_pW z9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRT zKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXY#p z9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe* z02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f z01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL& z03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)(P zmH%-d4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^ zj|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X> zj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhY zj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL! z(8~Wf5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n z&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow z?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B z-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK z4)|~Uj{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U;dGpVGjSHq#yL0_ z=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1CJ8>88#yz+f z_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy z@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM z|KWf({>Onh2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4 zi}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1 zi~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=j zi}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tg zi~n%If9HQ3h=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f z!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz| z!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ z!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy z!N2$q2ek7)4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S z=ipqNhx2g(F2qH+7? z_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9 z@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou z|KMNzhXeix|KmU$goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=R zxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8 zzwrj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj z!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61 z!r%A@|KdL!@IUz<2jUa4Js2={N&t z;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX z;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii z;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M z;xGJ-fABB88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$ zH}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIj zKk*m-#y|KM|KWiD#s4@E2jO5GfxDhwuX54~XaT{*O z9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtdfAJp<=;D7Ih=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U% z;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h z;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s z;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW z;}86azwkHy!N2$q2mEjT$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4 za69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq z@H*bWn|KRv;~l(<_wYVGz=!wrsL98cg$JcXz644%bvcpfj{MZAQU@d{qW zYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*% zZ}=U5;7|O8zwr9|L{K!#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otl zRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(L zR@{c$aR=_iUAP za4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$ za4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR z@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G z@GE}9@Aw0M;xGJ-fABB6G62FKz!9FG%lB2L1| zI0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XY zxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DF zcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq z_yxb>H~fx2@F)Jl-}ndr;y)bF%l|kK2jO5GfaZ06007K%K1e1> zk|arzBuSFwWRfIFk|arzNs=T-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7 z@fkkH7x)ri;cI+@Z}AD!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn z@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc z@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;ef%E>;vgK1 zLvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s( zOK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbY zNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;F zPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0sn;m<3JpQ zgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4 zi*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfu zhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?b zkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>k9@|KmU$ zgoAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iL zgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9pYnek zh=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rg zh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OP zh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh} zh>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2Mq9k z9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|o zT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8% zJcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjx ze29D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$r zcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY z_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;; zgZv){;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn z`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t| z{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN z`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u z0sjO4$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L z`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(< z_wYVGz=!w9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=j zi}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tg zi~n%I|H%JwAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2> z2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n* z2mj(f95BrPaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgy zI0xtAJe-dUa3LSeNC+@=C zxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^ zcn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2mCYsj{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U;dGpV zGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1C zJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$ zH}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIj zKk*m-#y|KM|KWg9{*ME35Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#* zfirOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZM zfj{vV{>DG}7ysdaf6o7LAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bl zjyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Ii zjz91x{=(n*2mj(f95BZJaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUP zoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^Y zypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00Y> zoQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8 z+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H z{D$B02mZug_#6M=U;Kvy#`!-E#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otl zRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(L zR@{c$aR=_iUAPcz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo z7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4# z6}*bq@H*bWn|KRv;~l(<_wYVGz=!w9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky z;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c z;}`sj-|##Bz@PXFf8!tgi~n%IzvBNm5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia z;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E z;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP z;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3 z;b;7UU-27$#~=6;f8lTZgMaZK4w&TsI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j< zH{oX7f?IJLZpR(C6L;Zm+=F{@AMVEkcn}ZaVLXCI@faS*6L=C&;b}aBXYm}K#|wB7 zFX3gpf>-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AY>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_z zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$N ze#B4s8Nc9H{D$B02mZug_#6M=U;KvyruaVdV1hu}~ghQo0Lj>J(o8pq&R9Eam^ z0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s% z18&4kxEZ(LR@{c$aR=_iUAP88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$ zdAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&c zd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgX{*ME35Dvy6I24EBa2$anaTJcmF*p{- z;dq>Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn z;d@fE(tH~1Fc z;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaf6f1KAP&O8I0T2{FdU8}a3qex(KrUj z;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y z;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC z;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f95BQGaUc%D!8inm;xHVJBXA^+!qGSe z$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L z&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l) z-{L!bk00*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2 zHLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09 zG@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{ zHNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2mEjR9|z(f9E?M7C=SEnI08rF zC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$= zDqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2 zDLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)%t{2vG6ARLTCa3~JL;Wz?E z;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@ z;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3 z;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9& z;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|2zN3fj9^U;}9H*!*Do` zz>zo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfa zz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86w zz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|E zz?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKJqO$ALHq2jdVNiocz=gO77vmCKipy|0 zuE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIc zp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wj|cD|9>T+T1drk| zJdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwF ze2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!u)zOuAP&O8I0T2{ zFdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8) zF+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKg zGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Poege;kN|a4-(R zp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(KPWf;CN9+Ia|NsA9{!8{hxJvl%Qt4?p9cSQ7 zoQ1P-4$j4SI3E|_LR^H4aS1NPWw;zy;7VMDt8opk#dWwIH{eFxgqv{-ZpCf59e3bP z+=aVw5AMZ%xE~MTK|F+q@dzHpV|W}-;7L4%r|}G)#dCNbFW^PIgqQIOUd3y89dF=G zyoI;%4&KFkcpo3&Lwtmf@d-Y~XZRdn;7fdkukj7O#dr7~Kj26FgrD&Xe#LM29e?0Y z{Dr^q5B|k}IAD?g<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u; zOq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)k zPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!x zO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=X zPyB_y@elsRe>mX(;{P}h2jO5GfxDhwuX54~XaT{*O z9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtdfAJp{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$ z#~=6;f8lTZgMaZK4){0x9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy z!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G z!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc z!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_ z!|(V5f8sCvjeqbj{=)&w{2vG6ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwb zPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLk zZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68 ze#7th1ApQ#{EdI`FaE;;|CaybKpcdFaR?5@VK^K|;7A;Wqj3z5#c?T~}9w*>LoP?8c z3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP z3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW z3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ z3x36K_#J=XPyB_y@elsRe>mXZ@qZkMgK#ho!J#+|hvNtwiKB2dj=`}w4#(pJoQRWf zGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*WatiK}omuEDjq4%g!b+=!cS zGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kEiKp;1p24$t4$tESyoi_Z zGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RBiLdZAzQMQn4&UPk{D`0M zGk(FZ_zl0~5B!P0@HhU!zxWRatnz;xh=Xu24#A-~42R*ZsI1b0-1e}PI za57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37 za5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut z@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi z@H2kFulNnW;}86azwkHy!N2$q2mIgs9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k| z;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y z;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO- z;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n z;wSu!U+^n_!|(V5f8sCvjeqbj{=)%l{2vG6ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x) zC*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiU zH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9n zFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_B zKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;{}2Difj9^U;}9H*!*Do`z>zo#N8=bAi{o%S zPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSb zZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n> zUcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~ ze!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKJkM$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^) zJ#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y> zJYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP z9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC z9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp<*x>&-5C`F49D+k}7!Jn~I1)$UXdHuM zaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYB zaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x z@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m z@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK4)_oJ9|z(f9E?M7C=SEnI08rFC>)Jr za4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|g za4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5 z@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)&A{2vG6ARLTCa3~JL;Wz?E;wT)A zV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH? zYj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0h zXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZO zZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|B?UWKpcdFaR?5@VK^K|;7A;W zqj3z5#c?T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxq zg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*B zg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8 zg|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mVj@qZkMgK#ho!J#+|hvNtw ziKB2dj=`}w4#(pJoQRWfGETv%I1Q)c44jFxa5m1txi}B!;{sfWi*PY6!KJtim*Wat ziK}omuEDjq4%g!b+=!cSGj74HxDB`C4%~^ma5wJ3y|@qe;{iN~hwv~S!J~K#kK+kE ziKp;1p24$t4$tESyoi_ZGG4)}cnz=P4ZMlB@HXDTyLb=p;{$w%kMJ=*!Ke5PpW_RB ziLdZAzQMQn4&UPk{D`0MGk(FZ_zl0~5B!P0@HhU!zxWRaZ1aB{h=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~y zT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43Fap zJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l* ze2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2mHVM9|z(f9E?M7C=SEn zI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1 zxB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P( zcmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)$~{2vG6ARLTCa3~JL z;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as z<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U` z<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS z=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|C#^eKpcdFaR?5@ zVK^K|;7A;Wqj3z5#c?T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i~lQE zb+ifq004$I50WHFk|arzBuSDaNs=T9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mr zZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXF zf8!tgi~n%IKj42Hh=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2kh}b4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ z#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74 z#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j z#9#Ou|KMNzhXej0|KmU$goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R z;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5 z;7|O8zwrj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XE zH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(` zKkz61!r%A@|KdL!@Q?T(2jUa4Js2 z={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9 z@Aw0M;xGJ-fABB88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J z;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO| z;dlIjKk*m-#y|KM|KWgt%>OtL2jO5GfxDhwuX54~X zaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{ z@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+ z@f&`}ANUi0;cxtdfAJp*ZsI1b0-1e}PIa57H8 zsW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYf zt+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S( zt9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kF zulNnW;}86azwkHy!N2$q2mBNM$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo z7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4# z6}*bq@H*bWn|KRv;~l(<_wYVGz=!wrsL98cg$JcXz644%bvcpfj{MZAQU z@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J z@e6*%Z}=U5;7|O8zwr9pYlHr#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9 zI2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4k zxEZ(LR@{c$aR=_iUAPa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lf zCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfs zCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrS zC;W_G@GE}9@Aw0M;xGJ-fABB6G62FKz!9FG%l zB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-Y zBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zBYwiq_yxb>H~fx2@F)Jl-}ndr;y)a4!v8oB2jO5Gf zxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Gu zco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8 z_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp<_~-nO191=z#vwQqhv9G>fg^Dgj>a)K7RTXu zoPZN?5>Cb`I2EVibew@RaTdA$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^) zJ#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y> zJYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wJ(o8pq&R z9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@j zT!-s%18&4kxEZ(LR@{c$aR=_iUAPT~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5 zuElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Sch zp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1q zzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mWr|8XD=!ofHMhvG0Cjw5g+j>6G6 z2FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#0 z2G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|3 z2G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh| z2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)blulOGa;vgK1LvSb#!{ImrN8%_P zjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yM zjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*& zjc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao# zjc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0T=v_191=z#vwQqhv9G>fg^Dg zj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_ zuEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRW zp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>f zzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)a4$^SSI2jO5GfxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S z5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM z5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp<`2YDI2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x z0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2! z0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu z0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABB88#yz+f_u+m#fCupq9>ybh6p!I? zJb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9 ze1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgt&Hp$M2jO5GfxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf z9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@o zKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^ z442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{ z43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE> z44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2mBlU$ALHq2jdVN ziocz=gO77vmCK zipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ zipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wrsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctF zKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwr9-|{~W#6dV1 zhu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fm zm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPa4Js2={N&t;w+qvb8s%s!}+)X7vdsZ zj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_ zj7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i? zj8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABB6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW z!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s z!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A z!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)a4$NxAG z2jO5GfxDhwuX54~XaT{*O9k>&B;cnc6dvPD`#{+l} z58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M;cdKwckv$H#|QWj zAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`}ANUi0;cxtdfAJp<`1kye z191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdc zz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%V zz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVG zz=!wJ(o8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C z^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPT~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPK zc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!gr zeYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_ zdw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsR ze>mWg|8XD=!ofHMhvG0Cjw5g+j>6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1 z;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$ z;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}> z;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr z;y)blpZFgK;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yV za4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcK za4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6) z@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v z@Gt(u0Z;so191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdic&5hvkfoPtwv8cxRzo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkE zI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@p zxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvW zcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03* z_zQpIAN-5|aKH=y<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u; zOq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)k zPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!x zO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=X zPyB_y@elsRe>mX3@;?s5K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ z2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn6 z2kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B0 z2mZug_#6M=U;KvyUilvf;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@c zI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN> zxC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3u zcmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8 z_yd39FZ_*v@Gt(u0soEvaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUP zoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^Y zypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00ic&5hvkfoPtwv z8cxR^NPR1!X z6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR z6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU z6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O z6~Ezk{DD957yiaS_!s}-fOr1Kfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$e zoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w z+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rn zyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc( z{EFZ3JO03*_zQpIAN-5|aKQiIe;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(K zPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np( zUcsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE? ze!;K!4Zq_L{E5HtH~zuD_zwqs@IMa3K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z# z8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv z8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s z8Nc9H{D$B02mZug_#6M=U;Kvy{wM$AKpcdFaR?5@VK^K|;7A;Wqj3z5#c?SeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPF zUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00D!}YiU zH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9n zFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_B zKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;U;K{)aS#s1AvhF=;cy&*BXJat#xXb+$KiOK zfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5H zfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6Ez zfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`w zfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd9?^I1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW} z#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{ z&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh z-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCAO6RII0y&h5FCoba5#>@kvIxR;}{%^ z<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx z>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q z=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7 z@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^zz_f9KpcdFaR?5@VK^K|;7A;Wqj3z5 z#c?v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB z;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy= z;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%X6`5y=3ARLTCa3~JL;Wz?E;wT)A zV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8BK-fjs>4?R0D!U6ue~#8 zX6DSCIWu$4%*>o~&N)evBuSDaNs=T~A;|08km+&%P!K-);uj388iMQ}J-od+g z5AWjxe29Y6LAtw#wj=zr{Q#*firOy&c-=7 z7w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG} z7ysdaKmLycaS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^w zI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PA zxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4? zco*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS z_!s}-fPeVMe>nYb|1I|){%8M)191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb` zI2EVibew@RaTd88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*Ff zD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W= zFZdO|;dlIjKk*m-#y|KM|KWgt!vAp~4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^ z$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ z&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq z%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0 z&-ewu;y3(`Kkz61!r%A@|KdL!@Q?f-2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lf zCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfs zCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrS zC;W_G@GE}9@Aw0M;xGJ-fABBrsL98cg$JcXz644%bvcpfj{ zMZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#Qvt zNBo4J@e6*%Z}=U5;7|O8zwrdaX20) z;6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v z;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ z;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;Ggk-9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL z;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~ z;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A z;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29kM!LxV{ z&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh z-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCFZ>?|;vgK1LvSb#!{ImrN8%_Pjbm^u zj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf% zuEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#I zp2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QR zzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0m1wq2jUa4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajR zEw01$xB)lfCftl$a4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1Y zES|&jcmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7L zExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ-fABBrsL98cg$JcXz6 z44%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^ z4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrdaX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8 z@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc| z@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;9u~69EgK(Fb=_?I1Gp52pox{ za5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+ za5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X z@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL= zukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCOa6}oaS#s1AvhF=;cy&* zBXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{Go zD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~H zC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X} zFYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fH3}#191=z#vwQqhv9G> zfg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9 zjw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>r zjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uo zjxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)Y^&i`>B4#L4W1c%}< z9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6 zT#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk| zJdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwF ze2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!@W1kZ9EgK(Fb=_? zI1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQS zxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69 zcnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29e;kN|a4-(R zp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~y zrML{2;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BY zr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCYyOV|aS#s1 zAvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-Y zCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%y zBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8 zC-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fJpw2191=z z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTd6G62FKz!9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW z!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s z!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A z!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)Y^#s6_2 z4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@i zF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD| z9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6 zKElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!@W1hY z9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|o zT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8% zJcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjx ze29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O6 z1AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsC zTmFv&aS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&P zd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~ zemsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;C zeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}- zfEfOd191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdrsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|% z#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrdaX20);6$8+lW_`8#c4PlXW&eng|l%E z&c%5+9~a<4T!f2p2`Lkg}ZSN z?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz z-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+ z{>6Vd;NS6o9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv z&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz& z?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J z-od+g5AWjxe29kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB) zHr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5Ht zH~zuD_zwsCd;X6DaS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO z7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB z7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I z7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD95 z7yiaS_!s}-fO!6o191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@R zaTd6G62FKz!9FG%lB2L1|I0dKTG@Onz za3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3Hr$Ro za3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D z@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2 z@F)Jl-}ndr;y)aa!2fX|4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF& zGjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^ol zJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XE zH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(` zKkz61!r%A@|KdL!@PF`s9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6 zr{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Ir zx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-); zuj388iMQ}J-od+g5AWjxe29kM!LxV{&*KHWh?np(Ucsw) z4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K! z4Zq_L{E5HtH~zuD_zwsCpZp&O;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX& zDo(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>Bdr zD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`y zDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1l zD}KZ8_yd39FZ_*v@Gt(u0ZIHH2jU za4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$ za4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR z@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G z@GE}9@Aw0M;xGJ-fABBrsL98cg$JcXz644%bvcpfj{MZAQU z@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J z@e6*%Z}=U5;7|O8zwrdaX20);6$8+ zlW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QI zm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~ zpYaQR#c%i>f8bC2g}?C+{>6Vd;6L$y9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?D zlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_ zn{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08k zm+&%P!K-);uj388iMQ}J-od+g5AWjxe29kM!LxV{&*KHW zh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}T zh@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCXa0`^aS#s1AvhF=;cy&*BXJat#xXb+$KiOK zfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5H zfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6Ez zfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`w zfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fK>jE191=z#vwQqhv9G>fg^Dgj>a)K7RTXu zoPZN?5>Cb`I2EVibew@RaTd6G62FKz! z9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;` zT#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O- zJdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4 ze2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)aa#{Y334#L4W1c%}<9F8M!B#y$-I0nb! zI2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYna zI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hn zIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tN zJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!@L%~q4#Yt?7>D3c9EQVj1dhZ}I2y;` zSR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^( zSv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5f zTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXd02KMuq}I2ecEP#lKCaRiRUQ8*gM z;8+}o<8cB`#7Q_Ar{GkahSPBd&csv02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB z;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy= z;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%YnFaD1MaS#s1AvhF=;cy&*BXJat z#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB z#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X z#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(= z#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fDHbR191=z#vwQqhv9G>fg^Dg zj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdrsL98cg$ zJcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr| ze1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrdaX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQ zNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0 zOMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;Q!(OI1mTnU>t%&aTpHA z5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh z6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH z7x)ri;cI+@Z}Aic&5hvkfoPtwv8cxRJ(o8pq&R9Eam^0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%d zhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPD3c z9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0pl zJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!% ze1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXejU{*ME35Dvy6 zI24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6h zxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysda9R803aS#s1 zAvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-Y zCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%y zBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8 zC-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd8NW<3JpQ zgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4 zi*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfu zhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?b zkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>fnQ|KmU$ zgoAMi4#iu^18z>T;GH{%xEira8I z?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu z-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@% z{=lF33xDGu{EPo^z(3%B9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6 zr{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Ir zx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-); zuj388iMQ}J-od+g5AWjxe29v02a#7(#vx8PRX zhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@ zhS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H= zhTriA{={GS8~@;6{D%YnA^+n*9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8 z#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z- z#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR z#c%i>f8bC2g}?C+{>6VdAfNwnAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFC zui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~x zzv4Iijz91x{=(n*2mj(f9Pp3$9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4m zQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdT zTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{ zSMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu! zU+^n_!|(V5f8sCvjeqbj{=)$U{Eq{15Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw z#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@ z#xM94zu|ZMfj{vV{>DG}7ysdaf6V_l5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia z;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E z;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP z;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3 z;b;7UU-27$#~=6;f8lTZgMaZK4k+Y*9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?D zlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_ zn{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08k zm+&%P!K-);uj388iMQ}J-od+g5AWjxe29Onh2nXX39E!tmIF7)PI0{GO7#xe^a6C@H zi8u)-;}o2V({MV@z?nD;XX6~4i}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9o zjkpOn;}+bC+i*MXz@4}YcjF%1i~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3? zi+Bky;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LO zkN62c;}`sj-|##Bz@PXFf8!tgi~n#y5&z>r9E5{$2oA+zI2=ddNF0TuaSV>daX20) z;6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v z;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ z;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;Ggn84#Yt?7>D3c9EQVj1dhZ}I2y;`SR9Ar zaRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5 z@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_ z@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXacF9|z(f9E?M7C=SEnI08rFC>)Jra4e3) z@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG z^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg z^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h= z_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)(PjQ?>U4#L4W1c%}<9F8M!B#y$-I0nb! zI2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYna zI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hn zIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tN zJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!P{RK>5C`F49D+k}7!Jn~I1)$UXdHuM zaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYB zaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x z@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m z@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK4*2K%j{|WK4#puk6o=t(9DyTo6pqF* zI2Om@c$|O}aS~3(DL56U;dGpVGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlL zxE9ypdfb2;aT9LFEw~l8;db1CJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2 zcoxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd z_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|KWgA{>Onh2nXX39E!tmIF7)PI0{GO z7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4i}P?kF2IGj2p8iLT#CzZIj+E!xC&R} z8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1i~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB z89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+ z8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tgi~n%Izu*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2 zHLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09 zG@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{ zHNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy!N2$q2bA$Y4#Yt?7>D3c9EQVj1dhZ} zI2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlG zcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r z_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXej4|KmU$goAMi4#irsL98cg$ zJcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr| ze1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrj|cD|9>T+T1drk|JdP*u zB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>h zCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL!@c;2Y4#Yt?7>D3c9EQVj z1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d z1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q z1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXX439|z(f9E?M7C=SEn zI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_DK5k1 zxB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1C?3P( zcmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)(PKmX%E9E5{$2oA+z zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4T!f2p2`Lkg}ZSN?!|q$9}nO`JcNhw2p+{_ zcpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV z_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6VdppyS_AP&O8I0T2{ zFdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8) zF+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKg zGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9PqFB9|z(f9E?M7 zC=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe*02ksST#QR_ zDK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f01x6JJd8*1 zC?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL&03YHbe2h=< zDL%vJ_yS+zD}0S_@GZW>_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)%P{Eq{15Dvy6 zI24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I45iZ6h zxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaf6f0m5C`F4 z9D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n&c_9~5EtQM zT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow?#Bao5D(#D zJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B-p2>{5Fg=V ze1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK4yfjT9EgK( zFb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|oT!@Qs zF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8%Jcx(z zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjxe29Onh z2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4i}P?kF2IGj z2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1i~Ddt9>9Zm z2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=ji}&z8KEQ|g z2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tgi~n#y4gcdn z9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+9~a<4 zT!f2p2`Lkg}ZSN?!|q$9}nO` zJcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaD ze1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd;NS8; z4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S=ipqNhx2g( zF2qH+7?_uyXKhx_pW z9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$hxhRT zKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNzhXZQ) z9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4=&cpe* z02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq}?!*0f z01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a-oyL& z03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj{=)(P zj{k8W4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF&GjJx(!r3?n=i)q^ zj|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X> zj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhY zj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdL! zP{;o`5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v|gL82n z&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_gL`ow z?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmcgLm;B z-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZgMaZK z4*2){j{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U;dGpVGjSHq#yL0_ z=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8;db1CJ8>88#yz+f z_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy z@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM z|KWgo{>Onh2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD;XX6~4 zi}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}YcjF%1 zi~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mrZ{r=j zi}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXFf8!tg zi~n%If8c){h=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dMvv4-f z!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3yKpz| z!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;utx9~RJ z!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86azwkHy z!N2$q2Q=_M4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ#925S z=ipqNhx2g(F2qH+7? z_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9 z@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou z|KMNzhXej2|KmU$goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=R zxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8 zzwrj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj z!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61 z!r%A@|KdL!@Spe}2jUa4Js2={N&t z;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX z;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii z;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M z;xGJ-fABB88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$ zH}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIj zKk*m-#y|KM|KWiD%>OtL2jO5GfxDhwuX54~XaT{*O z9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o z8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`} zANUi0;cxtdfAJp*ZsI1b0-1e}PIa57H8sW=U% z;|!dMvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h z;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s z;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW z;}86azwkHy!N2$q2mBZQ$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4 za69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq z@H*bWn|KRv;~l(<_wYVGz=!wrsL98cg$JcXz644%bvcpfj{MZAQU@d{qW zYj_=R;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*% zZ}=U5;7|O8zwr9zw$p0#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otl zRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(L zR@{c$aR=_iUAP za4Js2={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$ za4T-Z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR z@G4%z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G z@GE}9@Aw0M;xGJ-fABB6G62FKz!9FG%lB2L1| zI0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XY zxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DF zcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq z_yxb>H~fx2@F)Jl-}ndr;y)bF#{W1F2jO5GfxDhwu zX54~XaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q* zWxRq{@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$h zXZ(U+@f&`}ANUi0;cxtdfAJp<`0xCW191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN? z5>Cb`I2EVibew@RaTdcz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_ zxCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+y zcnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!wJ(o8pq&R9Eam^ z0#3w9I2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s% z18&4kxEZ(LR@{c$aR=_iUAPT~} z9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@ z9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%` z9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR= z9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>k9%|8XD=!ofHMhvG0Cjw5g+j>6G62FKz! z9FG%lB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;` zT#p-YBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O- zJdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4 ze2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)blzxW>q;vgK1LvSb#!{ImrN8%_Pjbm^u zj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf% zuEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#I zp2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QR zzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0bTr$191=z#vwQqhv9G>fg^Dgj>a)K z7RTXuoPZN?5>Cb`I2EVibew@RaTdic&5hvkfoPtwv8cxRzo# zN8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZP zSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0i zPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6 zU*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|a6k|L<3JpQgK-EB#bG!cN8m^t zg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxq zg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*B zg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8 zg|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mX(@;?s5K{yzP;7}Zf!*K+T z#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+ z#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm z#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvydifs*;vgK1LvSb#!{Imr zN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4!CTT-DJk0000O+B`^-BuSDa zNs=T_uyXK zhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74#9Me9@8Dg$ zhxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j#9#Ou|KMNz zhXeir|KmU$goAMi4#irsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R;7z=RxA6|% z#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5;7|O8zwrj|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU z@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@ z|KdL!@DKSP2jUa4Js2={N&t;w+qv zb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z?YIMX;x62c zdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z>v#ii;w`+5 zcknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9@Aw0M;xGJ- zfABB88 z#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU z#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m- z#y|KM|KWgt#Q!)D2jO5GfxDhwuX54~XaT{*O9k>&B z;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{@fu#o8+a3M z;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+@f&`}ANUi0 z;cxtdfAJp<7~p>#h=Xu24#A-~42R*ZsI1b0-1e}PIa57H8sW=U%;|!dM zvv4-f!MQjO=i>rgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h;||=3 zyKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s;|;ut zx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW;}86a zzwkHy!N2$q2mE9H$ALHq2jdVNiocz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6 zowy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bW zn|KRv;~l(<_wYVGz=!wrsL98cg$JcXz644%bvcpfj{MZAQU@d{qWYj_=R z;7z=RxA6|%#d~-kAK*iLgpctFKE-GF9ADr|e1)&^4Zg*9_#QvtNBo4J@e6*%Z}=U5 z;7|O8zwr9pYT5p#6dV1hu}~ghQo0Lj>J(o8pq&R9Eam^0#3w9I2otlRGfy> zaR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$ zaR=_iUAPa4Js2 z={N&t;w+qvb8s%s!}+)X7vdsZj7xASF2m)x0$1WHT#ajREw01$xB)lfCftl$a4T-Z z?YIMX;x62cdvGuA!~J*w58@#_j7RV&9>e2!0#D*8JdJ1YES|&jcmXfsCA^GR@G4%z z>v#ii;w`+5cknLW!~6IEAL1i?j8E_>KEvnu0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9 z@Aw0M;xGJ-fABB6G62FKz!9FG%lB2L1|I0dKT zG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-YBW}XYxCOW3 zHr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRfB3{DFcm=QG zHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb> zH~fx2@F)Jl-}ndr;y)ZP%>OtL2jO5GfxDhwuX54~X zaT{*O9k>&B;cnc6dvPD`#{+l}58+`vf=BTf9>)`S5>Mf2JcDQP9G=Guco8q*WxRq{ z@fu#o8+a3M;cdKwckv$H#|QWjAK_zsf=}@oKF1gM5?|qKe1mWC9lpm8_z^$hXZ(U+ z@f&`}ANUi0;cxtdfAJp<_-Fi&191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb` zI2EVibew@RaTdcz=gO77vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo z7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4# z6}*bq@H*bWn|KRv;~l(<_wYVGz=!wJ(o8pq&R9Eam^0#3w9 zI2otlRGfy>aR$!BSvVW#;9Q)C^Kk(##6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4k zxEZ(LR@{c$aR=_iUAPT~}9w*>L zoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d z+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcU zyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm z{DhzJ3x36K_#J=XPyB_y@elsRe>h-_|8XD=!ofHMhvG0Cjw5g+j>6G62FKz!9FG%l zB2L1|I0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-Y zBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zBYwiq_yxb>H~fx2@F)Jl-}ndr;y)blFZmw_;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd z0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X z0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa z0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU z0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0pt9S191=z#vwQqhv9G>fg^Dgj>a)K7RTXu zoPZN?5>Cb`I2EVibew@RaTdic&5hvkfoPtwv8cxRQlfj9^U;}9H*!*Do`z>zo#N8=bA zi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m zi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKI%0<3JpQgK-EB#bG!cN8m^tg`;r{ zj>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5 zuElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Sch zp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1q zzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mXZ@IMa3K{yzP;7}Zf!*K+T#8EgJ z$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ& z*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl z-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;KvyruZKR;vgK1LvSb#!{ImrN8%_P zjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yM zjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*& zjc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao# zjc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0sof&aUc%D!8inm;xHVJBXA^+ z!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym z!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4 z!q@l)-{L!bk00ic&5hvkfoPtwv8cxR^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{Go zD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~H zC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X} zFYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fEoVBfj9^U;}9H*!*Do` zz>zo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfa zz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86w zz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|E zz?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKOLke;kN|a4-(Rp*ReO z;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2 z;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw? z;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwrn@;?s5K{yzP;7}Zf z!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M z%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t z&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{saHxKpcdFaR?5@ zVK^K|;7A;Wqj3z5#c?SeNC+@=CxCi&*KHQH7@E{(-!*~Rb z;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F z;xl}XFYqP4!q@l)-{L!bk00D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn z@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc z@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;^ZbtkaS#s1 zAvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-Y zCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%y zBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8 zC-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd9n*I1mTn zU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfN zVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lA zV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^& z@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCXa2{5 zI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8 zxCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq? zcnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q z_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^z#{+S zKpcdFaR?5@VK^K|;7A;Wqj3z5#c?v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(| z03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%XU z_#X%2ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XG zxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$r zcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY z_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;; z|CRr7AP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uN zypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f z9I(v)I1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?5 z9?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~ z9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AY6LAtw#wj=zr{Q#*firOy&c-=7 z7w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG} z7ysda75>M8I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogT zoQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl z+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5c zyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu z{EPo^z<=j|9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv z&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz& z?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J z-od+g5AWjxe29v02a#7(#vx8PRXhTCxm?!;ZV z8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY z8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS z8~@;6{D%Yn2mj+h9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&en zg|l%E&c%5+9~a<4T!f2p2`Lk zg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5 zg}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2 zg}?C+{>6VdV2%HAAP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco z-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x z{=(n*2mj(f9PmH+9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B! zXW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{O zcj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|Qkh zZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5 zf8sCvjeqbj{=)(5{Eq{15Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#* zfirOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZM zfj{vV{>DG}7ysda|Hc0}5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj z#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1 z#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN z#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$ z#~=6;f8lTZgMaZK4%py-9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6 zr{fHqiL-Dv&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Ir zx8n}niMwz&?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-); zuj388iMQ}J-od+g5AWjxe29Y6LAtw#wj=z zr{Q#*firOy&c-=77w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94 zzu|ZMfj{vV{>DG}7ysdaP5#G$I0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^H ziqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xE zira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)w zir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRnt zir?@%{=lF33xDGu{EPo^!2i$xI1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{&*KHWh?np( zUcsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE? ze!;K!4Zq_L{E5HtH~zuD_zwsCZ~n)DI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2s zC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;G zH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZ zFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL| zKjRntir?@%{=lF33xDGu{EPo^z&8KmKpcdFaR?5@VK^K|;7A;Wqj3z5#c?v02a z#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw z#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE z#83Dczu;H=hTriA{={GS8~@;6{D%W}_#X%2ARLTCa3~JL;Wz?E;wT)AV{j~v!|^x) zC*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71a49as<+uV@;woH?Yj7>D!}YiU zH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9n zFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_B zKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|Cj%9AP&O8I0T2{FdU8}a3qex(KrUj;y4_S z6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz z7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkg zAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9I(s(I1mTnU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%@!A6Ip>3IG5AhBgn9BuSDaNs=TrjwkRWp2E|32G8O-JdYRfB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHao zpW-uojxX>fzQWh|2H)a4e2*XSBYwiq_yxb>H~fx2@F)Jl-}ndr;y)bl5BMJk;vgK1 zLvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s( zOK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbY zNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;F zPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0ek$9191=z z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I2EVibew@RaTdic&5hvkfoPtwv8cxRzo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@; zg}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZv zgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4k zhxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKHio z<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>LoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d+=QEP3vR`2xE*)kPTYmNaS!greYhVF z;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcUyo8tW3SPx)cpY!xO}vG-@eba_dw3ro z;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm{DhzJ3x36K_#J=XPyB_y@elsRe>mVD z^FI#6K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pV zaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK z@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy z4*4Gk;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd0Vm=loQzX&Do(@cI0I+mES!yVa4ycn z`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcKa4+t| z{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN z`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u z0sn;maUc%D!8inm;xHVJBXA^+!qGSe$Kp5~j}verPQuAJ1*hUPoQ^YaCeFgyI0xtA zJe-dUa3LSeNC+@=CxCi&* zKHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y| zJ-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00ic&5hvkfoPtwv8cxR^NPR1!X6{q2JoPjfO7S6^w zI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PA zxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4? zco*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS z_!s}-fMfp0fj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv z9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV z9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi z9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpI zAN-5|aKJy~e;kN|a4-(Rp*ReO;|Lsyqi{5i!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn= zHqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0!L_&!*W(7kM!LxV{&*KHWh?np(Ucsw)4X@)3yotB) zHr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5Ht zH~zuD_zwr1@IMa3K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D; zI2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjO zxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug z_#6M=U;Kvy{yG2SKpcdFaR?5@VK^K|;7A;Wqj3z5#c?SeN zC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{ej~DPFUc$?G1+U^YypA{U zCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!bk00D!}YiUH{vGTj9YLkZo}=k z19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n z18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th z1ApQ#{EdI`FaE;;XZ()?aS#s1AvhF=;cy&*BXJat#xXb+$KiOKfD>^NPR1!X6{q2J zoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb z+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p&*6EzfEV!+UdAhU6|doS zyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk z{DD957yiaS_!s}-fPcyVI1mTnU>t%&aTpHA5jYY@;bUuC zPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-ex zUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AkM!LxV{&*KHWh?np(Ucsw) z4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE?e!;K! z4Zq_L{E5HtH~zuD_zwsCEB?oUI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^H ziqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xE zira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)w zir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRnt zir?@%{=lF33xDGu{EPo^zy<%~KpcdFaR?5@VK^K|;7A;Wqj3z5#c?kM!LxV{&*KHWh?np( zUcsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba#!MFGh-{S}Th@bE? ze!;K!4Zq_L{E5HtH~zuD_zwqM@;?s5K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVqPQ*z# z8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_zZp2Nv z8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$Ne#B4s z8Nc9H{D$B02mZug_#6M=U;Kvy{(t_*fj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc z2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7i|cSbZorMW z2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRpi|6n>UcifZ z2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8mi|_C~e!!3T z2|wc({EFZ3JO03*_zQpIAN-5|aKIJ+<3JpQgK-EB#bG!cN8m^tg`;r{j>T~}9w*>L zoP?8c3QomoI2~u;Oq_+YaSqPKc{m>z;6hx4i*X4q#bvl0SKvxqg{yH5uElk@9yj1d z+=QEP3vR`2xE*)kPTYmNaS!greYhVF;6Xfuhw%s=#bbCJPvA*Bg{Schp2c%`9xvcU zyo8tW3SPx)cpY!xO}vG-@eba_dw3ro;6r?bkMRjU#b@{&U*Jo8g|G1qzQuR=9zWnm z{DhzJ3x36K_#J=XPyB_y@elsRe>mV@^FI#6K{yzP;7}Zf!*K+T#8EgJ$KY5ThvRVq zPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ&*Wg-QhwE_z zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl-{4z(hwt$N ze#B4s8Nc9H{D$B02mZug_#6M=U;KvyuK6Da;vgK1LvSb#!{ImrN8%_Pjbm^uj>GXd z0Vm=loQzX&Do(@cI0I+mES!yVa4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X z0XO0%+>BdrD{jN>xC3|MF5HcKa4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa z0Wabuyo^`yDqh3ucmr?ZExe6)@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU z0YBm={ET1lD}KZ8_yd39FZ_*v@Gt(u0sn^oaUc%D!8inm;xHVJBXA^+!qGSe$Kp5~ zj}verPQuAJ1*hUPoQ^YaCeFgyI0xtAJe-dUa3LSeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym!qa#L&*C{e zj~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4!q@l)-{L!b zk00ic&5hvkfoPtwv8cxR^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW z*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p z&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD z-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fLs2@fj9^U;}9H*!*Do`z>zo#N8=bA zi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m zi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|aKOLge;kN|a4-(Rp*ReO;|Lsyqi{5i z!Lc|F$KwQ?h?8(KPQj@-4X5J_oQbn=HqODhI1lIJ0$hlTa4{~yrML{2;|g4ft8g{0 z!L_&!*W(7kM z!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw?;|qL=ukba# z!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwr%@jnj4K{yzP;7}Zf!*K+T#8EgJ z$KY5ThvRVqPQ*z#8K>Y>oQBhJ2F}D;I2-5ST%3pVaRDyGMYtH3;8I+M%W(y+#8tQ& z*Wg-QhwE_zZp2Nv8Mok8+=kn62kyjOxEuH2UfhTK@cNB9_@;8T2t&+!Gm#8>zl z-{4z(hwt$Ne#B4s8Nc9H{D$B02mZug_#6M=U;Kvy{yqQWKpcdFaR?5@VK^K|;7A;W zqj3z5#c?SeNC+@=CxCi&*KHQH7@E{(-!*~Rb;xRmqC-5Ym z!qa#L&*C{ej~DPFUc$?G1+U^YypA{UCf>r^cn9y|J-m+(@F70J$M^)F;xl}XFYqP4 z!q@l)-{L!bk00D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn@F*U`<9Gs3 z;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc@F_mS=lB9& z;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;5B!e^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{Go zD{&RB#x=MW*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~H zC-D@X#xr;p&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X} zFYy(=#y9vD-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fd9zt%&aTpHA z5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh z6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH z7x)ri;cI+@Z}AkM!LxV{&*KHWh?np(Ucsw)4X@)3yotB)Hr~Ozcn|O61AK^&@G(BYr}zw? z;|qL=ukba#!MFGh-{S}Th@bE?e!;K!4Zq_L{E5HtH~zuD_zwsCC;rEQI0y&h5FCob za5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8xCj^H5?qSQ za5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={= z@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q_y`~46MTx# z@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^z!U%DKpcdFaR?5@ zVK^K|;7A;Wqj3z5#c?v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(|03O6cco>i1 zQ9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!yty zQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%Xc`5y=3ARLTC za3~JL;Wz?E;wT)AV{j~v!|^x)C*mZWj8kwbPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71 za49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn z@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc z@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaE;;|Aqf?AP&O8 zI0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQ zcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uNypIp?AwI&# z_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f9Pq;bI1mTn zU>t%&aTpHA5jYY@;bUuCPRAKI6KCOUoP%?59?r)FxDXfN zVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lA zV|;>7@fkkH7x)ri;cI+@Z}AY6LAtw#wj=zr{Q#*firOy&c-=77w6%8T!0I4 z5iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdaSN_L= zI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2sC*u^Hiqmj9&cK;C3uogToQv~tJ}$t8 zxCj^H5?qSQa5=8PmADF5;~HFx>u^18z>T;GH{%xEira8I?!cY63wPrl+>85gKOVq? zcnA;U5j={=@Hn2plXwbG;~6}Q=kPpUz>9bZFXI)wir4Tu-oTr93vc5cyo>knK0d&Q z_y`~46MTx#@HxJ~m-q@_;~RX7@9;f-z>oL|KjRntir?@%{=lF33xDGu{EPo^z<=X^ z9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv&cV4j59i|o zT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd5BK8% zJcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g5AWjx ze29v02a#7(#vx8PRXhTCxm?!;ZV8~5N|+=u(| z03O6cco>i1Q9Opn@dTd4Q+OKB;8{F}=kWqw#7lS?ui#a@hS%{1-o#sY8}Hy_!ytyQ+$Tc@ddubSNIy=;9Go$@9_hE#83Dczu;H=hTriA{={GS8~@;6{D%Yn zJOATA9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&eng|l%E&c%5+ z9~a<4T!f2p2`Lkg}ZSN?!|q$ z9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-( zA0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6Vd z;GO?*AP&O8I0T2{FdU8}a3qex(KrUj;y4_S6L2CZzFARfZQcm$8)F+7eZ@FbqX(|88Y;yFBz7w{rp!pnFCui`bljyLco-oo2>2k+uN zypIp?AwI&#_ynKgGklIO@Fl*&*Z2nC;yZkgAMhi7!q4~xzv4Iijz91x{=(n*2mj(f z9PmH*9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B!XW}fJjdO4= z&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{Ocj7MGjeBq} z?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|QkhZ{jVyjd$=a z-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5f8sCvjeqbj z{=)$u{Eq{15Dvy6I24EBa2$anaTJcmF*p{-;dq>Y6LAtw#wj=zr{Q#*firOy&c-=7 z7w6%8T!0I45iZ6hxD=P+a$JEcaTTt{HMkbn;d@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG} z7ysda|H=P25C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj#~C;iXW?v| zgL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1#~rv6cj0c_ zgL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN#~XMPZ{cmc zgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$#~=6;f8lTZ zgMaZK4*2AM9EgK(Fb=_?I1Gp52pox{a5Rp=u{aLL;{=?DlW;Ol!KpY6r{fHqiL-Dv z&cV4j59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz& z?!mpd5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J z-od+g5AWjxe29Onh2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V({MV@z?nD; zXX6~4i}P?kF2IGj2p8iLT#CzZIj+E!xC&R}8eEI(a6N9ojkpOn;}+bC+i*MXz@4}Y zcjF%1i~Ddt9>9Zm2oK{CJc`HgIG(_hcnVMB89a;U@H}3?i+Bky;}yJ$*YG;tz?*mr zZ{r=ji}&z8KEQ|g2p{7Ue2UNTIljP`_zGX+8+?oJ@I8LOkN62c;}`sj-|##Bz@PXF zf8!tgi~n%I7ysix9E5{$2oA+zI2=ddNF0TuaSV>daX20);6$8+lW_`8#c4PlXW&en zg|l%E&c%5+9~a<4T!f2p2`Lk zg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2Db9f#v;6=QIm+=Z-#cOySZ{SV5 zg}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJkclaJZ;79y~pYaQR#c%i>f8bC2 zg}?C+{>6Vd;D7T!4#Yt?7>D3c9EQVj1dhZ}I2y;`SR9AraRN@nNjMp&;8dK3({TpQ z#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74 z#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j z#9#Ou|KMNzhXcO(9|z(f9E?M7C=SEnI08rFC>)Jra4e3)@i+k|;v}4mQ*bIy!|6B! zXW}fJjdO4=&cpe*02ksST#QR_DK5k1xB^$=DqM|ga4oLG^|%2y;wIdTTW~9G!|k{O zcj7MGjeBq}?!*0f01x6JJd8*1C?3P(cmhx2DLjp5@GPFg^LPO-;w8L{SMVxc!|Qkh zZ{jVyjd$=a-oyL&03YHbe2h=_xJ%n;wSu!U+^n_!|(V5 zf8sCvjeqbj{=)(PhyQUP4#L4W1c%}<9F8M!B#y$-I0nb!I2?}?a3W5^$v6e4;xwF& zGjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^ol zJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XE zH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(` zKkz61!r%A@|KdL!@WcN&5C`F49D+k}7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ*jzj z#~C;iXW?v|gL82n&c_9~5EtQMT!Kq+87{{axDr?4YFvYBaUHJ54Y(0E;bz=|TX7q1 z#~rv6cj0c_gL`ow?#Bao5D(#DJc38@7#_zHcoI+HX*`2x@f@DV3wRMP;bpvnSMeHN z#~XMPZ{cmcgLm;B-p2>{5Fg=Ve1cE$89v7s_!3{?YkY%m@g2U$5BL#3;b;7UU-27$ z#~=6;f8lTZgMaZK4*0+Pj{|WK4#puk6o=t(9DyTo6pqF*I2Om@c$|O}aS~3(DL56U z;dGpVGjSHq#yL0_=iz)@fD3UEF2*Ie6qn(0T!AZb6|TlLxE9ypdfb2;aT9LFEw~l8 z;db1CJ8>88#yz+f_u+m#fCupq9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J z;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9e1R|V6~4wd_!i&cd;EYO@e_W=FZdO| z;dlIjKk*m-#y|KM|KWgN{>Onh2nXX39E!tmIF7)PI0{GO7#xe^a6C@Hi8u)-;}o2V z({MV@z?nD;XX70F|GKJ!R6qa#0Dw72k|arzBuSDaNs=T@fE(tH~1Fc z;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7ysdafBcUFaS#s1AvhF=;cy&*BXJat#xXb+ z$KiOKfD>^NPR1!X6{q2JoPjfO7S6^wI2Y&Pd|ZGFaS<-YCAbuq;c{GoD{&RB#x=MW z*Wr5HfE#fWZpJOR6}RDb+<`lB7w*PAxEJ@~emsB&@em%yBX|^#;c+~HC-D@X#xr;p z&*6EzfEV!+UdAhU6|doSyn#3I7T(4?co*;CeSClq@ew}8C-@Yf;d6X}FYy(=#y9vD z-{E`wfFJP_e#S5O6~Ezk{DD957yiaS_!s}-fIt4nfj9^U;}9H*!*Do`z>zo#N8=bA zi{o%SPQZyc2`A$eoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZPSK}I7 zi|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0iPvaRp zi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6U*j8m hi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|@P8EI{}BKH literal 0 HcmV?d00001 diff --git a/benchmarks/models/cb_explore_adf_large.m b/benchmarks/models/cb_explore_adf_large.m new file mode 100644 index 0000000000000000000000000000000000000000..c2751bcc1fa8430205dacc93652e1f578f072d5c GIT binary patch literal 4194577 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ɲSD;H&r=zK(C; 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH 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ɲSD z;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q} z6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~*pZ>>zaS$972gCp2;5Y;hi9_MgI1CPp!{P8a z0*;6y;m9}&j*6q<=r{(BiDTi|I1Y}B^KL`iF4uHI1diMd2v3R9~Zy{aUon77r{kwFaV1Ws@XYo0F9$&y0@g;m2U%^-LHGCc4z&G(Nd>h}vckw-Z zA3wkk@gw{gKfzD&GyELCz%TJD{2IT(Z}B_)9)G|e@hAKlf5BhzH~by{z(4UX{2Twl zfAK#Y=mGtY1LGh#C=Q1I!@+R~91@4Zp>Y@-7Kg*(aReL@N5YYD6dV;t!_jdJ923XF zv2h$67stc#aRQtWC&Gzw5}Xt#!^v?9oD!$Psc{;d7N^7MaR!_bXTq6r7MvAl!`X2T zoD=85xp5vGfb-&fI6p3c3*th!FfM|N;$pZsE`dwpQn)lOgUjM_xIC_aE8ZpJ;%2xxZh>3kR=728gWKYExIONGJK|2b zGwy=B;%>M*?ty#aUbr{zgZtusxIZ3%2jW3^Fdl-3;$e6=9)U;VQFt^SgU8}=cs!nf zC*nzXGM<8`;%Rs~o`GlLS$H;{gXiLTcs^c$7ve>DFoI4ll_!{Z1zB94S3<0v>Pj)tS-7&s=5g=6D5I4+Kd&OPH~{Cx`EY(*02joC zaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8aAjNtSH;zEbzB42#IBUM z05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD+z0o? z{cwLg01w22@L)Uy55>dqa6AH!#G~+NJO+=&+pKK0dK^c@MgRPZ^hg2cDw`c z#Jlirya(^a`|y5z03XDM@L_xeAH~P;aeM-w#Ha9SdGLX>eMc4yVT% za7LU7XU17@R-6rI$2o9LoD1j1d2j&Ei}T_9xBxDQ3*o}J2ri0?;o`UiE{RLw(zpyR zi_78ixB{+-E8)tx3a*N);p(^su8C{m+PDs`i|gU~xB+g68{x*d32us;;pVsnZi!pr z*0>FBi`(J$xC8EpJK@f_3+{@$;qJHx?umQh-nb9$i~HgJcmN)V2jRhZ2p)=u;o*1$ z9*IZc(Rd6Vi^t*dcmke?C*jF>3Z9Cm;pun=o{4AS*?10~i|66_cmZCB7vaTt30{hq z;pKP*UWr%X)p!kFi`U`xcmv*uH{s2A3*L&i;q7<_-ideN-FOe)i}&IE_y9hL58=c3 z2tJCB;p6xOK8a7^)A$TNi_hWn_yWF&FX7Ah3ciZ3;p_MYzKL()+xQN?i|^t4_yK;1 zAK}ON34V&7;pg}Teu-b<*Z2*7i{Ih*_yhikKjF{#3;v3~;qUkd{)vC#-}n#yi~r$3 zkLiCL7ze>YaWMQJ4vs_MkT?_$jl4!HJk zNS>5AOX9t+3Dad2E5568y|a6+62C&o!|Qk)DY$0=}1oC>GLX>eMc4yVT%a7LU7 zXU17@R-6rI$2o9LoD1j1d2j&Ei}T_9xBxDQ3*o}J2ri0?;o`UiE{RLw(zpyRi_78i zxB{+-E8)tx3a*N);p(^su8C{m+PDs`i|gU~xB+g68{x*d32us;;pVsnZi!pr*0>FB zi`(J$xC8EpJK@f_3+{@$;qJHx?umQh-nb9$i~HgJcmN)V2jRhZ2p)=u;o*1$9*IZc z(Rd6Vi^t*dcmke?C*jF>3Z9Cm;pun=o{4AS*?10~i|66_cmZCB7vaTt30{hq;pKP* zUWr%X)p!kFi`U`xcmv*uH{s2A3*L&i;q7<_-ideN-FOe)i}&IE_y9hL58=c32tJCB z;p6xOK8a7^)A$TNi_hWn_yWF&FX7Ah3ciZ3;p_MYzKL()+xQN?i|^t4_yK;1AK}ON z34V&7;pg}Teu-b<*Z2*7i{Ih*_yhikKjF{#3;v3~;qUkd{)vC#-}n#yi~r$3Pw0Of z7ze>YaWMQJ4vs_MkT?_$jl~7oH!TGjq~6D zoEPWA`EdbU5EsIQaS>b;7sJJI30xAF!liK;To#wZ<#7dE5m&;MaTQz@SHsnD4O|n~ z!nJW7To>2F^>G8-5I4e&aTDAWH^a?w3)~X7!mV)|+!nXP?QsX(5qH9!aTnYbcf;Lr z58M;?!o6`H+!y!5{qX=i5D&tG@en)|55vRp2s{#x!lUsRJQk0`063cM1p!mIHbycVy+>+uG>5pTkq z@fN%lZ^PU14!jfZ!n^Svych4o`|$yM5Ff&a@ezC!AH&D-349Wt!l&^Wd={U>=kW!6 z5nsZW@fCa(U&Gh&4SW;d!ng4qd>7xt_wfV#5I@3?@e}+MKf}-Q3;Ytl!msfg{1(5% z@9_ux5r4v;@fZ9Rf5YGL5BwAV!oTq!{1^Yjfu7R;I4}-^gW_QLKO7u~z#(xc92$qg zVR1Md9!J0taU>iWN5N5XG#nkrz%g+w92>{MadA8xA1A;GaUz@;C&5W^GMpTzz$tMm zoEoRWX>mH79%sNAaVDG@XTe!|uGPo=*hs)y%xFW8EE8{A-Dz1jB;~Kaou7zvkI=C*bhwI}8xFK$Y8{;Op zDQ-i3GLJ$NtP zhxg+H_#i%n591^FC_aXd;}iHKK7~)?Gx#h%htJ~+_#(c9FXJotD!zuV;~V%UzJ+h& zJNPcXhwtMD_#u9TALA$ZDSn2Z;}`fPeuZD-H~1}nhu`B5_#^&=KjSa>EB=PR;~)4Z z{)Kt{fS|M}>{)&aM&rOFei z*o}aY{6pc;I1CPp!{P8a0*;6y;m9}&j*6q<=r{(BiDTi|I1Y}B^KL`iF4uHI1diMd2v3R9~Zy{ zaUon77r{kwFaV1Ws@XYo0F9$&y0@g;m2U%^-L zHGCc4z&G(Nd>h}vckw-ZA3wkk@gw{gKfzD&GyELCz%TJD{2IT(Z}B_)9)G|e@hAKl zf5BhzH~by{z(4UX{2TwlfAK#Y=sEq51LGh#C=Q1I!@+R~91@4Zp>Y@-7Kg*(aReL@ zN5YYD6dV;t!_jdJ923XFv2h$67stc#aRQtWC&Gzw5}Xt#!^v?9oD!$Psc{;d7N^7M zaR!_bXTq6r7MvAl!`X2ToD=85xp5vGfb-&fI6p3c3*th!FfM|N;$pZsE`dwpQn)lO zgUjM_xIC_aE8ZpJ;%2xxZh>3k zR=728gWKYExIONGJK|2bGwy=B;%>M*?ty#aUbr{zgZtusxIZ3%2jW3^Fdl-3;$e6= z9)U;VQFt^SgU8}=cs!nfC*nzXGM<8`;%Rs~o`GlLS$H;{gXiLTcs^c$7ve>DFoI4ll_!{Z1zB94S3|IhUr*A8e4M)c@a7-Kv$HsARTpSO_#|dykoCqhzNpMn}3@67aa7vsCr^ab;TAU82#~E-& zoC#;fS#VaI4QIzWa88^H=f-((0M3i^;rzG&E{F@^!ng=7ii_dmxCAbVOX1SE3@(ey z;qtfwu81q)%D4)yimT!3xCX9?YvJ0s4z7#q;rh4%ZipM<#<&S?ikso)xCL&BTjAEY z4Q`9u;r6%#?ua|#&bSNiio4#c>H-5|_fIaT#0|m&4_81zZtV!j*9qToqTt)o~46 z6W7AEaUEP2*TeO31Kbcd!i{kg+!Qy%&2bCd61T#waU0wgx5Mpm2iy^N!kuv!+!c4j z-Ej}x6ZgWsaUa|l_rv}106Y*6!h`V;JQNSZ!|@0_5|6^8@fbW7kHh2f1UwN>!jth7 zJQYvF)A0;E6VJl4@fFw4uXT?VE8{A9EZRmaVQ)b zhrwZSI2;~Fz!7mI92rN!QE@aJ9ml{iaV#7g$H8%NJRBbSgEFOo);|X{oo`fgkDR?TLhNt5hcqX2OXX80|E}npPo#li4@I5-Z0L*h_4G!BEq;&3=Tj({WLNH{W%f}`SS zI697jW8zpiHjabi;&?bdPJk2QL^v@{f|KH8I5|#%Q{q%OHBN)m;&eDY&VV!GOgJ;n zg0tdmI6KaPbK+b$H_n3ta9*4b=f?$bL0kwI#zk;ZTnrb-C2&bx3YW%Za9Laqm&X-w zMO+D2##L}tTn$&pHE>N_3)jYVa9vyv*T)TTL)-{A#!YZj+zdCzEpSWR3b)2>a9i9C zx5piDN8AZ_#$9k%+zoffJ#bIl3-`u-a9`XH_s0Y9Ks*Q!#zXK>JPZ%VBk)K(3XjHP z@K`(!kH-`6L_7&k##8WAJPl9BGw@723(v-L@LW6(&&Lb!Lc9ns#!K*0ybLeLEAUFZ z3a`d%@LIeMug4qkM!X4c##``KybW*1JMd1t3-88z@Ls$R@5cx5L3{`w#z*i`d<-AQ zC-6yp3jaSB_Z^kaoA538Hhc%Z3*Uq9!x``c_#ylV z&V(PsPvEEUGx#~21!u!A;Fs_#_%-|n&Vh5`w{RZ(4t@`RfIq^Y;Lq?E_$&Mk&WFFl zKi~rRCtL{sf`7wB@E`auTnv}M|6rp}xc^~e*aR*Go5E&rY1kYt1DAy@U`yBvwuWus za&URr7Onu>!4=_3aAmj(TotYcSBLH48gNax7VH4mh8^KLuoGMtt_RnL8^8_WMsQ=; z8EyhMg`2_6;TCX9xE0(QZUei(ZDCi~4c5U5til4;!|h;qxINqf?g)Fpo#4)J7uXZ- z3U`CM!#&`ha4)zw>;-$nec-;Z58My#4-bF`!h>L6crZK!9tsbGhr@pG2zVqs3LXuQ zfyct*V1GCO9uEh?LGT24B0LG63{Qcl!qebjcsd*c&wyvbv*6kA9C$7~51tP%fJ5Ph z@FF-2UJNgR!{MdyGI%+>0$vG6z>)AOcr_dauYsfCweUK4J-h+l2*<#i;LY$Bcq_aO z-VX18W8s}}9J~wO4adWK-~>1k-U}ze$?!gSKYRc_2p@tE!$;tw@GcY1AYKM zgdf3~@MHK1{1ko$KZmp6Z1@HI5`G20hTp(Ba4!57&V%2<@8J*dNB9%`8U6x)g}=f1 z@OStJTmb)s3*lezZ@38l1OJ7K;S%^CZ1frTKWq$}z@=bQ*bFWWo5N+`vakhg30uL| zunk-eE)Uzn6<|BKB3ucs3|E1x!qwpFusvJ@t_jzI9pKuqBU}e|g6qQd;QDX_xFOsK zZVWraP2i?*Gq^e20&WSnf?LCFU>CS8>VGp zD7+9}1c$+k;U#c5ycAvrFNasaE8z$@5?%$bhNIv$a5TIYUI(v-H^3X=7=+yc3Rtcfq^icz6$-04KtG;UqX2-Usi855NcEL-1kv2z(Si1|Nq{z$f8T z@M$;&PKDE84K~1M;B@#bd=5SjUw|*dm*C6r75FNA4ZaTFfN#RL;M?#W_%3`8z7J=> z58#LJBRCU&3_pRN!q4F6a2A{mzkpxDui)448#o8fh2O$?@H_ZD`~m(5e}X^5U*NCs zH#i^u4*!4);Gb|I{0sgK7r}qvzi=^J0{?@JzTp0cjbRhG6l@Bc!KGnyxC~qtwty{R zE7%&gfy=?=VOzKYYzJ3_E5ViFDsWY}8eAQ=hikw!;aac*TpM%dNMUAP`xA8r6Q zgd4$)VQ07r+!SsGH-}rmE#X#hYq$;U0=I=-VK-O@E3gU+SP!>@-Qo6d2e>2b0e6Bs z!(CucxGUTZ?hf~Wd&0fo-mn+!4flcj!ai_6xIa7q9taPDec{3I5O^p&3?2^q!6V?2 z@F;jRJO&;MkAwZ;0C+qc2nWFv;EC`gcrrW%o(fNcgW>6L2s{Ix3D1IO!*k%d@H}`v zyZ{b`7s89+FnBS%1P+In!pq?0@CtY(905ndtKije6ubtGhS$RD;PvnZcq1GGZ-O_& zTi~tmHh4R{1CE7v!g25}csCpm?|~EGM0hWp1SiA$;QjCc_#k`;J`5j$kHW{`Ot1Uxly1*WnxRP52gk8@>bIh3~=l z;SBfz{1AQwXTp!+C-77F8T=g1g0tZl@Jsj={2G1(=fJt}TR0DX2fv3uz#rjH@Mri7 z{1yHN=fmIOA8-Nu6E1{*!N1`m_z(OSE{03sf3VS4-2bpKYyy{pO<^;*G;9u+fy=@c zuqA8-Tf;VRIk-G*3s->c;EHf1xH4P?t_oL!tHbti4Y(#;3wD5O!;Wwr*a@x+*MsZB z4d8}wBe*f_3^#$B!p-33a0|F4+zM_Dw}D;Ywy-Pg2J2u2R$&3_;dZb)+#c=#cZ5CQ zPH<y2kr;=hX=p|;X$x3JQyAV4~2)p!(l&o z1UwQR1&@Zuz+>TYus<9CkB0-{Ab0{i5uOB3hNr+&;c0L%JRJ^!XTUSzS@3Ll4m=m0 z2hWEWz@hL$co7^1FNT-E;qX#;8N3``0k4E3;7E8Cyc&*z*TB*6T6i729^L?Ngk#`M z@Md@mycOOCZ-;llvG7hf4&DXthU4KqZ~~kN?}d}#WOyIEA3gvdgb%@o;Un--_!xW~ zJ^`PEPr;|*6gU-5gEiOypMlfiv+z0iJbVGZ2w#FP!&l&|@HO~4d;`7---2($ci_A5 zJ@`JH0Y88r!jIrg_%ZwhehNQ>pTk*jHv9s93BQ70!*AdmI2V2k=fUsb_wWb!Bm4>e z41a;Y!r$P0_&fXqE`Wc+h43%jl!H@G|81MUg;f_uYWus7TX?hE_C{owxa0C*rg2=;{s!$aVq@Gy8d z><5p4N5Z4v(eM~}EIbbOhXdg8a3CB6Pk<-Fli^63~zzA!rS2O@D4Z@-U-LSyWrh$JiG@^fD_@pa1xvh?}PWl2jGM7A^0$S1U?EM zgO9@};FItv_%xgXr^0El1{>fra5{VzJ_nzNFTfY!OYmj*3Vap5249D7z&GJr@NM`G zd>6h4--k2c2k=Aq5u6D>hM&Ms;b-u3I1A2(U%)TnSMY224V(k#!f)X`_#ONn{s4c3 zKf#~jFYs6R8=Mb+hkw8Y@K3l9{ssSri{L--U$_`9f&alq`MCdKW7q^P1)IWVaB0{a zE(4c^EnrL73buxA;Bs(z*cPq;+rbs#N^oVk3S1Sg23Lpe;TmvFxEAaH*M=S8I8g2u-z-?hy*bUaf3ar8c*2C>!cep*= z0qzKUz@6aEa2MDU?h1E*b!zpkooCa&K0X_q#!)M`h@Ok(Gd=b6`Uxu&1SK({$b@&E+6TSuC zhVQ_4;d}6XI0JqFKZGB_neb!y3H%g(20w?h;B5E>{1SczzlPtyIdCrg7S4m;!SCS@ z@JIL){2BfNe}%un`S5r62V4OEgbU$c@Nc*X{saGoi{TRZA8hm;_dje5o4}=DQ`ihH z4V%Md;Igmaaar1Fi|zf*s)6up?Xt zc7p4|_2Bw&1GpjF2yP5J!%g6(a5K0$+yZV1w}M;4ZD1F;E$j-r!8%xhRan4!xE<^c zw}(5x9bpf+6Wkf@0(-(;;cjquxCh)5?gjUTy<geSq1;VJM`cp4lGPlrR`8SqSa z7CalC1J8x$!Smq-a45VGUId51i{T}3IJ^{I1}}$Kz$@VhI1*k3uZE-GHE=Y%7G4Lh zhd00*;TU)mycymCZ-uwP+uEW8trgLlEZ;dpotoB$`nd*LKF8Qur)hY!F9;Y09Y z_y~LyJ_a9$PrxVPQ}Agx1x|(2U=23FXW(@BEPM_=4_|;U!k6I7@D=zfd=0)1-+*tz zx8U3G9r!MM555m)zz^Vu@FO@Aehfc>pTf`J=WrIB4Znb2!mr@h@EbS>&V}E?dGI^< zJ^TUw2!DbsR;kuiU12v^2P?1&3s?`g zgWciwa0j>}>;ZRzJHuUIPq-`G4ek#2fP2Ed;NGwo><#yU`@%kOKe#_U03HYrf_>q^ z@DO+?JPaNV`@tjNk?<&ZG&}|#3y*{S;Q)9%90&)&6X1#PBzQ7B1)d5|gM;Dea0omD zo(a!_XTx*gx$r!AKD+=9g%`q$;4pYGyaW!1m%_{7)`e926!VJ18;&i!&~63@HTimyaSGfcfxV-E_gQ_5AT5!;6!*YoCGJs`{4cX0r((% z2tEuSfsew+;N$QK_#}J^J`Jb9sc;&s!3OvYoDQFb&%x*63-Cqw5_}oH0$+u%!Pns% z@J;v@d>g(4--YkN_u&lq0sIht1ZTpJ;V1A@_!;~h&VsYy7w}8?75o~01LwfG@LM3;Y%S2Is@y;U91T{1YyOf5E@uBKQyd7cPcN;D4}D0q%d;7&d`R z!KSboTpBiq%fMw}3)m92f~{d2xEx#_wuLLec5p?w5?mRs0#}8r!PQ}VxCUGkt_3^5 zwP8oN4(tTih3mof;RbL+xDnhKc7~h4P2py6bGQZE5^e>zhTFg{a9h|Fc7t`W0;{lq z^>91b9c~YIfIGq-a3{Dk+y(Z8yTaYz?r;ydC)^9}4ST`fa38oY>;w0M`@;j^f$$*M z7aj}`frrAw;Nh?zJOUmGkAg?TW8ksyIM^Q!fXBmua1cBJo(NBZC&N?Vsqi#77@iJ? zz%$^P@GN*XJO`c&&x7Z~3*bOp8gSW#w;8=Jk90%`$cf;}U9ykF`g!jTpa5B6P-VYyu z55kAw!|)OKD0~b)4xfNe!l&TVa0;9Xr@+04UxY8gm*Fe$Rrnfw z9linIgm1yO;XCkM_#S*8&VV1l58+2}Cj1zF0zZYH!O!6=I2(Qezl2}Gui-av4x9_W zh4bKd@O$_J{1N^He}=!nU*T_XKKvd20T;kO;X?Qq{2MNU|GnU2OIsw{SO<% zCU7a(6gGoP!{%@qxGZb|Tf$bbHEaWygUiFVa0S>7t_W9xE5lXbs&F;9I&2TufNR3F zUD&h8`uSI3%kN@untyW z6&A1_ZU?)=?cok^N7w`I1b2qJz@BhdxEtIZ?g96Nd%?Y7FW4LI1NVh};C^s_cmO;Q z9t8WsgW)0YP`6Yxp+6nq*^fm7i$Sc47l88{t23!j6}!x!L-@Fn;%diQIh+M&!!O{M@GJN={07c} zbK$pe9{dh|4}X9^!k^&J@E7*hA!$t5P_%B=xm%#sEqe9&O zurX``mx4`UGq^Nt4wr$;!WOV4Yz14xHgGw(JZuYBfbHOla3#1hTm`NQSA(m=_HYfj zCR_`4fNR5!a2?nQt_#%$G;hHxXeG3*RCft$k3;O1}(xFy^QZVk7AUEsE`E9?gA zUX7oXSfUO33r9N!QJ5=a8I}w+#B|Sz2QD^U)Trk2ls~u zzyskyurE9q9s&=Ahrz>PKX?Q@5*`JQhR48T;c>7(8~~4p1K}We0z46(1W$&iz*FIA za4thZn%1@IrVI90o6jm%!ohQg|7>99{vhgd^Zccon=F zj)K>~(ePS$9lRdi0B?k2;7#ymcniE0-Ue@ncfhgmPB;$U1@DIA;XQByoCxoQli*}{ zAG{wv03U=8!H3}^@KN{}d>lRjpM+1rr{NSh6;6XS*Z`k_)8Vu5Iruz$0lo-df-l2Y z;H&U8_&R(8z6sxgZ^L)syYM~uzVu>`k~E*DuDSWU2X2k=Aq5u7Rg)GAs> zOHVb7GsI;zRZZuliWkxwvhB5#gJvnquSSTM(iJ`KYhz7}bx9^B;+3@XhZBbS$&-|< zFH^;9X*=UqhN9&m>Xj{7;*Ipp8#jc}|NF{;bETh5usm~Sbc7;W`G~jZdD4-OvbBMG zAL}-o>cl(r_wWb!Bm4>e41a;Y!r!DlhyQ2z80DvSYg;7p(Z9n#-~#Ci6@P1%0WM0n z!dc=cdZF}w`&g|&t)vWy@)f_(f5S!aAL&26j~hH1#p`xvJBh#O#c+vq-vmdod)+Ce z<%MVBpR~iFI%-(6^Sa5(6k+ts$mnQHjJD+9ZMU`$f2sdTM|rn4jJ@om+^xGr7)$^E zKVK8+&7?yPbl1$1 ze3Y}L7Kzf*|Nr+hhs(faVGGz2wvsNl>Y`G;%2AzNg>k}K+9ReU?a0PxC9YAju))3@ zTwdBQ#zoN$P0-nl7%ptlD@aH8-e+i=Z=;5}#tS>?GL1BC!oMW9dDmX56|t`bSC-Cu zy3k;Kw1KWw+-tRp^!@QkT9eSNN}FEsqAK>);OekFTtnL2@s!%weYheHB#WBpwO|Lh zHtZ<SphWu46HeIIh3mof;Re$EN4GFoZgx}#%uz%` zX}`pMihtWfx|}v+L?db4U2E-^S(wtnCrUKNduO-_+!SsGH?7P zbl5Af3JX{dw}ai`_HYNdBkTcpf;+=qU{AO!+zsvy_kerCz2M%k7wir9f&0Qfa6h=e zwAb}hsq5k+b-tlJ#Q^ky@F3V%IyQcjA*kmv<+tf7$El z-9jvpK4x|yZN%4}y6G;>L^$?K;bri0c!hMk&Q;FjbxP3P^2<|KqDR1y(u!Xx9f&0Z)^lk8Vcn2H{ z?}X#vUGQ!=9^L~dNZZaTr+s*@@Os&Vt)ue3?G4yO8do$XYhwO;f@*Qa?~VUSKb9%xwp?Tc>UT1RmP@6+M4 z@HzNAd;z{F{UUWtTJVfpx}S3c#3l60@D=zfd`)`l$^M4Ef#%AtAx*?}^c&K3x2nST z`ckEW%T;j`{T6&%`b+Rmty^Y&o&K4fxPyKdz6aljGvEi(5x*nUq7%8g7{@U25d9IH z2|tFPz)#_4@N?z!NgJ(>Q}5pLQXZJ^5uc@ZUC7jicP@4t*7HB{ z1@FJY-{5@sJNyGKfPcb;(izdoY1v|+?(B+I;urdFxCs6O|AmX;68N9A>rWH)R&q_H zSz44Z`u)G}@&90B*hG5k>E~%qE_-y|^SwnWbW`cyHTsB~iF=gryewfReQmzEq1nXc z$~MdOqBQpAa2dEPYyn%sRQlR&(vV-JVS}|4G=ZZYr?gp&6~F{%x_|%D(#L52Wjt; z1)~3UOLf|na{AiR8DF{>JX%^PIU!AiBi`4M{=5378t*(wNj!2`IHA{t>%sMovO9UsjUX38|xdRJHt)jrf@Uq9H&j%o_*t#!-cy8Ou&Rln60)POCS!dp7%;o-EWxsTNCg*N&=(qC_?!pn4-^3U(8 z=!^G0a6h;|JOCajopr~^@Y!{_dcftW7=-Q%4~B=pL*ZfYaM%wX0gr@7!K2|Z@K|^p z><geOUtSrw(-xKl?da2qWqqfddS!qebjcsd*c&wyvbv*6kA9C$7~ z51tP%kiHzhReRHTjoRbHYY{3PQgxCRcCV&z?^;E_5c@@NnDh|u?6jIY<8^%;yv1Va z46nL|WzMNeZOd@61p9D!DZC6`E`4dMzxLx-dqo>KQ>;K=2}i(@@G5vU90jj|qopr} zmexj9DJwj_y6V?T&ur_P)~H{IZbNK4u@3w7@CNB))B6jL#~nns;T!ZD(PQ9E@Md@m zycOOCZ

G|3&-u_O)9|?*es)bd>8AHMCTO@P9E*AB+7?=_h^-wNd{aR!k3F6LHeV zt*>j#yVX|hiw=ri*zbnprQ_}QXq}P3Gxh0np~r90(!63?ovQ=U596Bp1g!k6I7@D=zfd=0)1-;f>4ML_9*z zgdf9C;HU62_&J;fXG@nVKCbpkGgkDQSBe+tFX314Yw7i4ei`ZvKc>vCA0XaH4{}e? zOzyN-L*Ax~9O)Q~!)nm$)#_t|i#`|c-@>nT2zpovRDifKctKL>I|Pd^;ceAT`UT){|OgL&#c-j&2agJlH#2z zexd({i{L-fJCCi>I$qwZ?*CU#{};Vjda;Y9K8-U~!kr>TiS(KeifB?PM`@B@ApT)* zRD}B)UPu;F@qP*a5B$JHmBf zC%7(L53Ub4fE&V%;Kr~s+yrh4H-nqQE#Q`LE9uF@%nc)VwotuSX`(fH8)^T|chs=O zQ?;%Vj~@i5W)}|0#(=L06@3X^z^3w!uoW z&q^WC^>91bUD|#6qLg~kCQ8xuFwq{p1KbhzfIGpR;V!Tz+!gKycb86&yssWUW22lN z87+FC_k?@FyjNW1MMfP!Bt!8hok$!BjAzJM@%gZ=e*u1zLx96DD=_r7 z&K1+pgQXqsnu%ni^@@}4B{3a61fBuUglECC;W_YJcpf|-UI2%}3*kj@m~?2D6}nNi z$|xZb;bJlR5;z=Q3NM3~!zz5-u`uSs9BouXO4QWeMY)5Uf48}Lo(iepl= z9-rDPwM%Ufx1`cY1AYKMgdf3~@MHK1{1kpBeW72L z>hs!8Nhw+8g3(wN*qO;8&{Z#3eKGh5x7GGCP zF0T~Run&f(!y)htcqTjxo(<1|=fd;g`S1cb6kZ4~g2Uj&@Dey2UJ5URm%}UIm2d^63~zzA!rS2O@D4Z@-U-LSyWrh$JiJFb^y?he z_vSNQXxC7YfSw5Ng_Gc9cptnUJ|OMh{$g5k-vhd$mHov*^h5Ar_y~LyJ_a9$PrxVP zQ}Agx1x|(2U=23FXW(@BEPM_=4_|;U!k6I7@D=zfd=0)1-+*tzx8U3G9qGPtVcM%n z`HDs0E^!zAo^*vRn+(tLrYZ50JBj<~8Sn%6A^b=>edayg$zi*6(MN`eO!UX_6Zk3o z41Nx0!P)Q&_$B-beht5YbKqR~Eu06xgWtm+;E(Vp_%r+k{tADC^WpFC54ZsS2^Ye@ z;NNf&{0IIE7fWBM7LfKP?u1fvY?3HJ{|6iW#r+Q(!zOSk*c3K{OT*@H8MrKL0b9aW zur+K0mxIg0wr~a54z37Sf-A#S;Hq#nxH@bv{VO*qZBf!m-G8n_L=E(sa4pyYt_?fF zbzmpBE?f_;4>y1t!j0g@uru5QZVESpn@fL7+^hwiexTY#7mF6?E#X#hYq*VcQRoCM zWO}h%;M6k01-&io3cJBNSb<+hwJHQ=b54aQD8SWx&xxKL=c#F9j`t_vn zMDGfBgS*2$;GS?VxHs$td&7OCBa92x$2A?5;OSdLUvwY1AKYI$=<~lczx}7xghi(M z0q6taL9j187#;!-g@?hzVLx~TJQ5xSkA}yEW8trgLlEZ;dpqD^l`6H?U!R~Wk}yiA^|-S-U}ze$?!gS zKYRc_2p@tE!$;tw@GkGF%0&Dt)ehB@w#4ivC#OIej(s>aaar1Fi|z zf*s)6up?Xtc7p4|_2Bw&1GpjF2yP5J!%g6(a5K0$+yZVXojIjJTG^2D>V=R0>06<< zhTFg{a9h|Fc7t`W0;{lq^>91b9c~YIfIGq-a3{Dk+y(Z8yTaYz?r;ydC)^9}4ST`f za38oY>;w0M`@;j^f$$*M7aj}`frrAw;Nh^J^voMp2E*unO2(!BVg&j~coaMu9s`es z$HD$^06ZQJgoEG-@I-hLJQeLeRr8|%9pcR~nQ;zzc6AQ2pg%`q$;4pYGyaW!1m%_{7=(O1E%;V5_w91X99*TL)I4e&-d2Hpg3hPS|5;cf7Ccn2H{ z?}X#vUGQ!=9^L~dz=`l)I7zxxZh;yTTw5{J*&>qB_rd$&1Mork5PTRu0w0Bs!N=hf z@JaX-d>T%HQ{gmNgAMQ*I2}Fw&v;<5Gu+$LLSsr|>iQIh+M&OJBJ&OeA^K zR-3&quYZC55`G20mTugsKwIZNM{%%{fB%o31LwkT;XL>q{2u-Qe}q55pW!d?SNI#8 z4}XV$zy;w0M`@;j^fzq~~!?XbrBXoCGJBvZ+ zzVKjp2s{)X1`mh*;1TdhcoaMu9s`es$HD$^06ZQJgoEG-@I-hLJQPlcz!!SHlA z1fBuUglECC;W_YJcpf|-UI2%}3*kj@7`zx>0*Av(;bri0cm=!?j({WKRq$#!3SI+8 z!)xJn@OpRyyb+F(cBr?;(4+Jk-GHckbrbq#cniE0-X?84a){y4@rSz8YbuEC=sTnz z`yDb&T=-2luDHI4Mc)a>!MmjGO=_N*9PXwobi1SOmTvHGu)3~PE2Tx+CJ~SQ9ykF` zg!jTpaI$pkKw;>z(ONxqc%RsZz8^jSAA}FVhv6geQE7+d$W$MnHM)oOy~Hu}@UGJV~jW>ZIO9Yd%XOFVtII% zNXPyxd=5SjUw|*dm*C6r75FNA4ZbdY@=bHR3M%J@`JHA)Q&aw_%Tsl@buzT|7X4C_TAHc50kvUK{YGKOD;&T8We2SpY1s&F;9I&2TufNM&(c~V!L={8(3n|eaj zLU({`!;Wwr*a@x+*MsZB4d8}wBe*f_3^#$B!p-33a0|F4+zM_Dw}D;Ywy-Pg2J2u2 zR$&3_;dZb)++MmsOV_&nN>YLXw~G$w9bpgYxY`Z0t~U=WJDskJPUxMb8{XKjEnL-9 zZB=wtbV2uoyTaYz?r;ydC)^9}4ST`fa38oY>;w0M`@;j^f$$*M7alBKW?r_oVVb!b zw)Th^f<6=;1`mh*;1TdhcoaMu9s`es$4U3K`l>B`^H!6p0}83GhUC5Fhr$b`Z>;YmmW8fW4j#KJ7NLj1i{T}3IJ^{I1}}$Kz$@Vh z>Al_8Ym+v2QRn=5BqGsQ!K>jYcnus4uZ7o1``lZu4)%0bA6vc`>(MvB8{rsu6TDeE zCf>==YPwLSJqi?C(6_?d;O)}wrg@8^W%0_%`dMO!bd~D;4R?EF>;BHGBx14O3CF>^ z;N5UMyhl2}xULphvyOW3!4r{yo(S)i4z3ws2rmAjtAAJ#N$AP&K6pQT06qvGl8*gT z)37$FlMvt{Eyf0i*%KwcNXRuF)&%)>6^Y8`uqO^bh7`4%#|HPuH9{Nk@m*Fe$ zRrnfw9linIgm1yO;XCkM_#S*8&VV1l58+2}ru1l+WN|0Htr#&qUjG>V3H%g(20w?h z;B5E>{1SczzlPtyIdCrg7S4m;!SCS@@JIL){8@VH`7&CSstIcSGzC^t zf=yvFxHN1ImytHF=c69g?&z{+%o1hMEnrL73buxA;Bs(z*cPq;+rbs#N^oWAE46>A zQRP-BFFqSY74)ibHMlx#57&Te!nI%rxHjwv*MXhjx^O+XKHNY$HnK%pkli5l?4!G) zA$lXYG3*RCft$k3;O1}(xTSQ1N$t}9J?N)qd)yJN&|AZ8U>CTpbZFV0DUnHAbR#AX z5U%KMuntyW6&A1_ZU?)=?WM1Mp09P?_d_`lril*d9bpf+6Wkf@0(-(;;cjqu>8L@Q zlq(Jy2kr;=hX=p|;X$x3JQyAV4~2)p!(l&o1UwQR z1&@Zuz+>TY(#xH1Xx_O;RX@+?!XG_AdR2N~?Z68QH6Z?)7>^zZ2f-8IiSQ(NGCT#I zD*d8icIvZsA9T}{Sz;P`FgzU&foH%o;aTu(cn&-lo(Io|7r>$LLU<7zCLQ~}qWWO7 zqq0~2{|AfFmq~(ePS$9lRdi0B@8oYkl?C^t;wpS=H5F>_m@)cfq^icz6$-Af4BvZ`${Sx$0+UbA2NEUN{L(hWEky z;REnN_z-+p`qkRTTDpFndU2bH{s{U}=@$`I)RGtF)V@0(ieu=<;S=yl_!N8^PJvUU zOKdH)fU&LAQVViK8oCA>;4^SKd=@?jpNB8N7vW3rW%!Ep@AIz>^SU(GO)h^?y^4Mf zz7F4jZ^E~v(<@sjO)4jIV@51-s`)~&Q0DdSvKIT8ev8>)o=765!5qc*4 z7=8jjg`dIC;Vd{CegVIPU%{{8H*gM|3%`Z)q}Ro2Y4-=cQ;JXJiFfGl;Scaf_!ImY z{sMo6zrp$NclZZf0RMyw;a~7?>5rCI)RB9=REujy`Xcl{@L#xCy8FH;ZAz1qN_5X< zq6GaPZ1f-Qf7lo{flI-ruo+w$HkTeUATllAY^<(%6E{%?y)0}2Tf$bbHEaWygUiFV za0S>7t_W9xE5lXbs&F;9I&2TufNR3Fq*t~LGyJWZsJkC+EF92l!;Wwr={XZ#X^j_7 zRz7DB5l-lJ;d*d=xB=V{ZUi@mo#7^MQ@ELQt*J}2aof%-C!a)!=IAZpmT)V$HQWYv zf!o5aup6v{6gz(Z`cd=hWo&M zVIR03+#enQ4}=H7zVKjp2s{)XCOvViwYc`gO{qO?j~I^b2ak}>2q|rtV(6sI?cym$ zqK|?{!(-sF@Hp5X4uHqQfp8E!0iFm?f+xdM;HmI5I2fJ|hrl!7neZ%lHarKO3(te+ zOWWqp)kav3R;{1qiUsJQ@IrVI90o6jm%!ohQh1qkRNdR^#wGpKTV0Ix%h6ZBE8z$@ z5?%$bhNIv$a5TIYUI(v-H^3X=7=+yc3Rtcfq^icz6$-04KtG z;Uwv(K@q8^N>va?j2q~arN@r$tPO0mM!it=qu3{HQhKYFShbh>^y4M5AMX#q2jN5T zVfYAq6g~zYhfhe)J-1r8lzpjR@W-~@N$C^Mr)ymY-BGfJt`VoOKMkkAsc;&sN&9;T zqz$tzP(w#n)f>>yNS|9~sXe_=K~1Q=U!z5-u`uff;h8}Lo| z7JM7N1K)-3!S~?|_pTf`J=WrIBEnTm2Wi9+% zn$oKGUGW0_CHxA0EnRy03Q=%(hgxYuPyHL|wOe|KYbB;?z<;Lt9O<~gBr)gFI&n&W zNuP`NZ{a-Y%z_-XWdB0N*W$EzhyEV^0Dpu(!JpwT@K^X7oDYA8f4~LsPq+~N1^O_;Wn@f+!l6)-C!N8z$z@HUqrM_n_s+0 z9g$R4uSahOyTk3_4sb`<1MUQOhP%L?(ixw>tDY5?DUViZqAPkgxI5ef?g{sTd&6F^ zH{1vA3;V$R;QsIccpy9o_Js$-L*SwCFnBoZ2akY9!lU5P@ECY3JP!7U1K{y+ARGiw zfG5I};K}e5cq%*%4u+@0A@B@%COiwC4bOq+!t>zy(r!Ili?e;_DmmZIiUsJQ@IrVI z90o6jm%!ohQg|7>99{vhlrBFYRvUgJK}il?BqGow;Z^Wz=?**l7`FQ@P-^uI7E#hk z4(5hBOFWcyiCx4R?4#ke(z`z|NqZS@tj_NEQ>>G|cVo1+IHr_p+Vi4VkNpOCBOC*7 zf;Yok;H~gBcsslUj)ix^aqup9HyjV|ffL|FcrTm;C&T;T{qOh ztME1WI(!4Z3EzTm!*}4j@IClGoB=<|6pU-1TG~V>KmY&|8tj`T&KF;6x|Fi z4V%Mdq~l&$Y2#MxRtN7bt1pXg0b9aWur+K0mxIg0wr~a54z37Sf-A#S;Hq#nxH@bP z*MMunwO|LhHtY!3ft}#Ga6Py_+yHI}H-a0(&Ttdy$g4v{kD4jU@6{hfQ}kwVbGQZE z5^e>zhTFg{a9h|Fc7t`W0;{l)elpWiJCM6ib&9po>(Sf6?r?j!1KbhzfIGpR;V#n0 z9R8+laaUE_${&O$dRMrc^sIS{w59V-sCu7D`tImGq+=(4R)*QzE9OzFMNjG6d4sf3 zt6wXI4;w`rT0T00gr@7!K2|Z@K|^p><geSq1;VJM`cp4lG zPlrR`8SqSa7CalC1J8x$!Smq-a45VGUId51i{T}3IJ^{I1}}$Kz$@VhI8r(yC?oB+ zS$DOm(+{x^;N5UMya!Hz6XCsZ5}XY0gZIM+;DhiX_%M6~J_;X$ zkHaV6lkh3{G@JsbN*^q%iW&uD)b6pi`ZRP6Ho#}#boeZMPCBPcq_(oqOARl6CeEW@ zfG@(A;LGq8_$qu2z7F4jZ^F0W+wdLuE_@HZ4`;v+;D_)dI1_#hKY^dZ&*0~97Mu;g zfM3F|;MedQI0w#!-@t{Ag^S@5_#bRk3im&3EWQ8bKCS<_X6k|&*MtdrDd`vXb5rN^U9U!3 z+38Ku&7=>6)YImj-=JPfDWxxsZZ3Vg@+5J-cb*VQr}Sme%fc40C2R#-!#2{(g0`p= z?|#)?{Jlh!LoW~8!WCdUxFTE$t_)X!tHRab>aaarL%Pc93WkL?t&~pd^`a(vE!aW2 zXWi}Eq9=QmX!kHt8{HAE13O92{cum++I^bx%JrzIi(U_|4>y1t!j0g@uru5QZVESp zo5L;OmT)V$HQWYvf!o5aup6v{6P z9wKe@f4Hv4e=iIMjKe+Iu4UU;Jm+`9vbUvW+s3kO+gn~-cFS0{ZMXQG*VoJI^S%Fp z`%#U;@9L!w6dj%>q%OT4u8$kwhPV-KjGN%5xEXGaTi}+s6>g2&;I_COZjU?Qj<^%< zjJx2jxEt<{d*Gh97w(OJ$9-^L+z_o`h&SQQ_;0)gZ^hg2cDw`c#JliryeHJTVm8?R-Yw~iErWC_zu2{@8SFS0e*-dg}Ug>PeBc;B@bGaa(v)1{Rw`GpW)~D z1%8QN;n(;Lev9AX_xJ<;80yU(SMQiKvv6?TGCKnQ(LdqO_zV7uzv1sVOa%Tv4vWL# z@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5Phh=0I|aAKS!)M1WnD)eK-mqF=k z?h7QPC&S5c3Y-$B!l`i@oEE3U>2U^}5of}gaTc5vXT#ZX4xAI`!ntuCoEQIy^Wprs z04|6N;lelw2jc(^!9{RUTnrb-Kj9L%Brb(Z<1)BxsJAvry<^0J^ucWhT@93@m&X-w zMO-P=Q+xcp<3@ozfh1vn3aL!5f`7)p;9qf7Tn$&pHE>P*8?J?G<2tx5u7~U62Dl+^ zgd5`~xG8Rio8uO^C2ke!=;_#tZ`B-#9sZuIVJ_S$3)9`dW1JA^>@N7H>&&Bibe7pcJ#EbA^yaX@B%kUrg zPrMwjz$@`8yc(~;Yw=%r9bS(&;Ei|_-i-goTkuxA4R6Og@J_r7@5X!ZUc3+Q#|Q91 z{0}~a591^FC_aXd;}iHKK7~)?fAJZ77N5iC@dbPlU&5F16?_$6!`JZ*d=uZoxA7f( z7vID8@dNxY)GcO|-X8VMufdsrI~jOHe~h2tr}!Cuj$h!H_!WMQ-{80S9e$5L;E(t} z{0V=?U+`D_4S&aBBJ%%nSR4+A#}RNu90^CpQE*fo4M)c@a7-Kv$HsARTpSO_#|dyk z`~yye6XPT}DNcry;}ke0PK8tBG&pUj6BR8HoF+~7pjSDk1=7*e;|w?>&V)1LEI2F9 zhO^@wI4919bK^WXFa8nd!})OmTo4z+g>euL#sM6Hi{PTT7%q-~!XDy`~7dVf=7<~8fZgri`(J$xC8EpJK@f_3+@`~n-_C#tD5L`P|ImM z0^LHr^=`#LfrImcY8Sg4=+3?e?umQh-uQRi2lvJOaDO}i55$A;U_1m5#l!G$JOYoz zqwr`v29L$#@OV4{PsEe(WIP2=#nbR~JOj_fv+!&@2hYXx@O-=gFT{)RV!Q+|#mn#? z_)okXufQwuD!dx6!E5nfcpYAkH{gwU6W)yf##=(&WBjw=+wUg@F83J}vX#CKZx8kQ zW=nQNnwUTMpK&JwJLo&{F1#D>!F%yOydNLH2k}4n5I&5L;G_5$K8{b|llT-qjsL}G z@L7BgpT`&QMSKZg##iuFd<|a@b(+V;cC_F6E~v-#Ie{DWoA?&Kjql*Q_#VEGAK-`h z5q=!%-Q^wyh2K0TP`l^gkSFw~_!)kVU*MPc6@HE1;J5f4evd!kkN7|Q34g|4@K^i| zf5%}W@&9pH91e%a5pYBt2}j0JLcM#zTJ1=NBh5m3~seDC_!LwAc!7}1EFq^YIE@3pliY9k0uBSp%=kLaWPyR|Ab56 zlDJf;5A996y;+*@fi|U6hLon4!DVqdTpm}z6>%k88CStS<6rQvxGJuOtK%BDCjJfA z!nJW7To>2F^>G8-5I4e&aTDAWH^a?w3)~X7!mV)|+!nXP?QsX(5qH9!aTnYbcf;Lr z58M;?!oBhDxDW1&`{Dk003L`3;lX%FsOzpgyL0@C^@Y3NiyatBABKnH5qKmXg-7Et zq3#hs-}dGWVg*nA8YW~ceH}!E^CEJRdK>3-Kbn7%#y~@iP1e{u3|9EAUFZ3a`d%@LK#AUWeD?4R|Bo6zY_1 ziUrOedl_^nTw5I6t@8gu!qLOy2sk2+gd^i9I4X{YqvIGj zCXR(;<2X1jj)&vp1UMo70Vl$VaT1&qC&S4@eQIw=@TTpFf)Cce8c0D;iBsX!I1NsV z)8X_u1I~yu;mkM-&Wf|)>^KL`iF4uHI1kQ?f5iE4ep~<-#D#ES9E5{$0EgfrxF{}$ zi{qbg30xAF!liMUP#2vye%rnykpn}Hmo8o4;Ic|Yl;#RmdZiCz6cDOz6fIH$&xHIm8 zyW(!RJMMvd;$FBn{vG$heQ`hB9}mC-@gO`H55Yt6FgzTOz$5V}JQ|O|WAQjV9#6m% z@gzJMPr+01G&~*8z%%hIJR8r!bMd@TZ~wF;IOvbl!NdQJ6EdH^058Og@M63KFU8C7 zANWta9IwDD@hZF;ufc2aUw9o}k2m0rcoW`?|HfNF-6Kk;K(dBqg3~Qc6S9@Q4R6Og z@J_r7@5X!ZUc3+Q#|Q91{0}~a591^FC_aXd;}iHKK7~)?fAJZ77N5iC@dbPlU&5F1 z6?_$6!`JZ*d=uZoxA7f(7vID8@dNx2Kf;gk6Z{lE!_V;x{1U&yukjoF7Qe&q@dx}7 z|A#-}&-hEI!xYP~eNuU{g;S#tcE`>|uGPo=*hs)y%xFW8EE8{BoXZ#EP6<5X8aCKY**Tlc!TDUf@gX`jY zxIS)x8{$T|F>ZpJ;%2xxZh>3kR=728gWKYExIONGJK|2bGwy=B;%>M*?ty#aUbuIt z4^^EWXg_djaQ<_@hWt+NgZtusxIZ3%2jW3^Fdl-3;$e6=9)U;VQFt^SgU8}=czmdj z?Q0Xr5dM15@Z5FkWz~{TBmM=u`1D zJRQ%#Gx01u8_&UW@jN^qFTe}&BD@$c!AtQn{0IIMFUKqJO1uiM#%u6e{1;w_*W(R% zBi@8J2U^}5of}gaTc5vXT#ZX4xAI` z!ntuCoEQIy^Wprs04|6N;lelw2jc(^!9{RUTnrb-Kj9L%Brb(Z<1)A`E{DtG3b-P! zge&7J_-FhJ{uNin)o^uO1J}gA;aa#hu7m61dbmDrfE(gQxG`>mo8o4;Ic|Yl;#Rmd zZiCz6cDOz6fIH$&xHIm8yW(!RJMMvd;$FBn{vG$heQ`hB9}mC-@gO`H55Yt6FgzTO zz$5V}JQ|O|WAQjV9#6m%@gzJMPr+01G&~*8z%%hIJR8r!bMZVpA1?@Xt11n5tgqK2 zIMK70fra!%crjjrm*Qpk5Bw)yj#uE7cokla*Wk7IFT4(~#~biQya{i{f8#B9E8d2; z;~jV>-i3GLJ$NtPhxg+H_#plVAHs+65quOM!^iOnd=j6+r}4k|3_gp`;q&+czKAd3 z%lHbuim&18_y)d-Z{gec4!(=;;rsXjeuy98$M^|;il5=<_yvB6U*XsI4StK?;rI9h z{)qp>pYUh=1%Jig@OK<08vh@M#o=&x905nfk#J-j1xLlvaC964$HcL4Y#ay2#qn@_ zoB$`pKj1_-F;0S$;$%2EPJvV6R5&$GgVW-4I6cmQGvZ7*GtPpu;%qoO&Vh5{TsSw* zgY)7aaXy?M7r+H^AzT;-;b0sHb*f|IgO?3`7JO+-;gAq|5nL1(!^QDWxCAbVOX1SE z3@(ey;qtfwu81q)%D4*t8UKQR#Z_@NTpicIHSuq_7Osu!;JUaTu8$kwhPV-KjGN%5 zxEXGaTi}+s6>g2&;I_COZjU?Qj<^%_o`h&SQQ_;0)gZ^hg2cDw`c z#Jlirya(^a`|y5z03XEv;6wN@K7xpz!&i)d>LQC zSMfD`9pAt=@hyBC-@$kBJ$xTOzz^{w{1`vMPw_MS9KXOX@hkiqzrkR<8~%>NMCbqGus9qJk0aoSI1-MGqu{7G8jg-*;FvfTj*a8sxHuk;j}zd8 z_y?Q_C&o!|Qk)DY$0=}1oC>GLX>eMc4yVT%a7LU7XU17@R-6rI$2o9LoD1j1d2n9* zBhH8O;{v!KE`$r?ARLSXI0P5LMR74)9RGw%;F7o$E{)6JvbY>Bk1ODcxDu|6tKgsU zFZfqn6<5R6aSdD(|AuSf+PDs`i|gU~xB+g68{x*d32us;;pVsnZi!pr*0>FBi`(J$ zxC8EpJK@f_3+{@$;qJHx?umQh-uQRi2lvJOaDO}i55$A;U_1m5#l!G$JOYozqwr`v z29L$#@OV4{PsEe(WIP2=#nbR~JOj_fv+!&@2hYXx@O-=gFT{)RV!Q+|#mn#?_)okX zufQuq9lq|=;KBbp6r8hd%#c;|)p!kFi~qvw@Or!fZ^WDMX8bqag16#rcst&Kcj8@m zH{OHy;(d5OK7bG6fAArE7$3n$@iBZHpTH;aDSR6Li_hS*_#8fuFW`ɲSD;H&r= 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ɲSD;H&r= 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ɲ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

a@fq&v(_&5H8|Kfi*;5h$} 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@ zm-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~9%m3p*I4}-^gW_N~ zI1YhB;!rp=4uiwua5y}UfFt5aI5LicqvB{dI*x&3;#fE~j)UXkcsM>zfD__GI5AFw zlj3AJIZlC7;#4>_PJ`3pbT~cEfd9i8aVDG@XTe!aV1Ws@XYo0F9$&y0@g;m2U%^-L zHGCc4z&G(Nd>h}vckw-ZA3wkk@gw{gKfzD&GyELCz%TJD{2IT(Z}B_)9)G|e@hAKl zf5BhzH~by{z(4UX{2TwlfAK#YaE|}SfpB0P1P8^zaBv&~hs2?9XdDKI#o=&x905nf zk#J-j1xLlvaC964$HcL4Y#ay2#qn@_oB$`piEv_^1SiGGaB`dir^KmnYMchA#p!T* zoB{ubGvZ7*GtPpu;%qoO&Vh5{TsSw*gY)8iI6p3c3*th!FfM|N;$pZsE`dwpQn)lO zgUjM_xIC_aE8ZpJ;%2xxZh>3k zR=728gWKYExIONGJK|2bGwy=B;%>M*?ty#aUbr{zgZtusxIZ3%2jW3^Fdl-3;$e6= z9)U;VQFt^SgU8}=cs!nfC*nzXGM<8`;%Rs~o`GlLS$H;{gXiLTcs^c$7ve>DFBUM05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({F zcg5XsciaQ_#JzBD+z0o?{cwLg01w22@L)Uy55>dqa6AH!#G~+NJO+=&+pKK z0dK^c@MgRPZ^hg2cDw`c#Jlirya(^a`|y5z03XDM@L_xeAH~P;aeM-w#Ha9SdaR?j| zhr*$87#tRd!{KoR91%ytk#Q6p6-UF-aSR+2$HK9392^(N!|`zfoDe6%iE$E~6eq*U zaSEIgr^2am8k`oV!|8Db{2$JUGvUlQ3(ktO;p{jE&WUs3+&B-;i}T_9xBxDQ3*o}J z2ri0?;o`UiE{RLw(zpyRi_78ixB{+-E8)tx3a*N);p(^su8C{m+PDs`i|gU~xB+g6 z8{x*d32us;;pVsnZi!pr*0>FBi`(J$xC8EpJK@f_3+{@$;qJHx?umQh-nb9$i~HgJ zcmN)V2jRhZ2p)=u;o*1$9*IZc(Rd6Vi^t*dcmke?C*jF>3Z9Cm;pun=o{4AS*?10~ zi|66_cmZCB7vaTt30{hq;pKP*UWr%X)p!kFi`U`xcmv*uH{s2A3*L&i;q7<_-ideN z-FOe)i}&IE_y9hL58=c32tJCB;p6xOK8a7^)A$TNi_hWn_yWF&FX7Ah3ciZ3;p_MY zzKL()+xQN?i|^t4_yK;1AK}ON34V&7;pg}Teu-b<*Z2*7i{Ih*_yhikKjF{#3;v3~ z;qUkd{)vC#-}n#yi~r$(i~K(hgahLsI4BN=gX0i5Bo2i`<1jcZ4u`|z2sk2+gd^i9 zI4X{YqvIGjCXR(;<2X1jj)&vp1UMm1gcIW=I4Mqslj9UPB~FD?<1{!ePKVRu4ER5s z5of}gaTc5vXT#ZX4xAI`!ntuCoEPWA`EdbU5EsIQaS>b;7sJJI30xAF!liK;To#wZ z<#7dE5m&;MaTQz@SHsnD4O|n~!nJW7To>2F^>G8-5I4e&aTDAWH^a?w3)~X7!mV)| z+!nXP?QsX(5qH9!aTnYbcf;Lr58M;?!o6`H+!y!5{qX=i5D&tG@en)|55vRp2s{#x z!lUsRJQk0`06 z3cM1p!mIHbycVy+>+uG>5pTkq@fN%lZ^PU14!jfZ!n^Svych4o`|$yM5Ff&a@ezC! zAH&D-349Wt!l&^Wd={U>=kW!65nsZW@fCa(U&Gh&4SW;d!ng4qd>7xt_wfV#5I@3? z@e}+MKf}-Q3;Ytl!msfg{1(5%@9_ux5r4v;@fZ9Rf5YGL5BwAV!oTq!{1^Yj0hjoH z90&)-L2ytU3|uGPo=*hs)y%xFW8EE8{A-Dz1jB;~Kao zu7zvkI=C*bhwI}8xFK$Y8{;OpDQ-i3GLJ$NtPhxg+H_#i%n591^FC_aXd;}iHKK7~)?Gx#h%htJ~+ z_#(c9FXJotD!zuV;~V%UzJ+h&JNPcXhwtMD_#u9TALA$ZDSn2Z;}`fPeuZD-H~1}n zhu`B5_#^&=KjSa>EB=PR;~)4Z{)KTn?AV6>vpd30KBda8+ClSI0GQO##%*w0+zz+L9dJk733tX_a97+7cgH<&PuvUl#(i*K+zQ#%J(Zd=8(-7w|=V317xn@Kt;bU&lA_O?(UA z#&_^td=KBp5AZ|$2tUS8@KgK@KgTcdOZ*DI#&7Uj{0_gzAMi)~34g|4@K^i|f5$)Y zPy7r2#((f%{0|3Q;s0?U92f_|L2)n~9EZRmaVQ)bhrwZSI2;~Fz!7mI92rN!QE@aJ z9ml{iaV#7g$H8%NJRBb}xDYOki{PTT7%q-W;F7o$E{)6JvbY>Bk1ODc zxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS;Fh=*ZjIaEwzwT` zk2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j7#@yC;E{L~9*xJ~ zv3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4 zUX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q z;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX z8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>xxXS%k88CStoaWz~W*T6M#EnFMd z!F6#xTpu^U4RIsf7&pO9aWmW;x4}!E^CEJRdK>3-Kbn7%#y~@iM#|ufQwuD!dx6!E5n4ydH1B8}TN*8E?T` z@ix32@4!3pF1#D>!F%yOydNLH2k{|%7$3n$@iBZHpTH;aDSR5A!DsO~d>&uG7x5*0 z8DGIy@ilxM-@rHVEqoi_!FTaJd>=o+5Ah@X7(c;J@iY7!zrZi?EBqS2!Ef<9{2qV6 zAMq#r8GpfF@i+V(|G+=-FZ>(-!GG~T9B_^Q$ANHQ90Ui&!EkUK0*Az*aA+I`hsEJ= zcpL#o#F21h90fmo8o4; zIc|Yl;#RmdZiCz6cDOz6fIH$&xHIm8yW(!RJMMvd;$FBn?t}Z{ez-p#fCu71crYG< zhvH#)I39sV;!$`s9)ri?adIfG^@p_%gnNui|U?I=+E#;#>GOzJu@L zd-y(nfFI&V_%VKhpWmVe|BnOVz&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1 zW8v614vvfC;rKWKPKXoX#5f5~ij(2wI0a6LQ{mJ&4Ni;G;q*8I{tsuwnQ&&D1!u+C zaCV#n=ft^iZkz|_#rbf4TmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^GgYaNH1P{f-@Nhf=kHn+!Xgmgw#pCdJ zJONL{lkj9b1y9A(@N_%_&&0FvY&-|g#q;odyZ|r6i|}H+1TV$Q@N&EYuf(hHYP<%o z#q02Tya8{-oA7451#iXM@OHcd@5Hk@N@hE zzr?TbYy1Yk#qaQY`~iQ&pYUh=1%Jig@OS(J|HQxWZ~O=U#s6@?4gMbo!hvxR925t` z!Ep#25{JT}aTpvHhr{7<1RN1Z!jW+l92G~y(QynM6UV}_aU2{M$HVb)0-O*h!ijMb zoD?U+$#Dvt5~sqcaT=T!r^D%S2K*n+h%@2LI1A2-v*GMG2hNFe;oLY6&WrQm{I~!v zhzsGuxCkzai{aw91TKk7;nKJaE{n_I^0)%7h%4dBxC*X{tKsUn2Cj)~;o7(ku8Zs8 z`nUmZh#TR?xCw5Go8jiT1#XF3;nuhfZj0OD_P7J?h&$oVxC`!zyW#G*2kwb`;oi6p z?u+~3{&)Z$hzH@pcnBVfhvDIP1RjY;;n8>u9*f7}@puBBh$rF6cnY41r{U>%2A+v$ z;n{c&o{Q(<`FH_dh!^3-cnMyLm*M4j1zw3);njEzUW?b^^>_o`h&SQQcnjW&x8d!0 z2i}Qy;oW!--i!C){rCVrh!5ez_y|6VkKyC^1U`vR;nVmGK8w%c^Y{Y3h%e#G_zJ#? zui@+X2EK`J;oJBQzKieS`}hHVh#%p{_z8ZBpW)~D1%8QN;n(;Lev9AX_xJ<;h(F=a z_zV7uzv1ur2mXnF;otZV{)_+NfSde34uk{aAUG%vhJ)h}I3x~*L*peyF6c@wAaS2=!m%^oS z8C({Z!{u=WToG5om2nkZ6<5R6aSdD(*TS`N9b6aJ!}W0k+z>ayjd2s)6gR`oaSPlM zx5BM)8{8JR!|ibg+!1%eopBf36?enkaSz-R_rkq#AKVxB!~O99JP;4UgYghN6c5A0 z@d!K;kHVwz7(5n_!{hM;JP}XAlkpTh6;H#{@eDi@&%(3u96T4#!}IY1ybv$Ki}4b? z6feWe@d~^WufnVG8oU;-!|U+|yb*80oADOB6>r1a@eaHb@4~zB9=sRt!~5|8d=MYP zhw%}76d%LK@d!{_k@d=X#5m+=*R6<@>G@eO=?9efwx!}sw6 z{189FkMR@y6hFhy@eBMCzrwHa8~hf(!|(A2{1Jb`pYa#`6@SCu@elkH|H8lVAN&{p z!vVMWe;f!0#zAmU91I7?A#g|>3WvsFa9A7;hsP0cL>vi6#!+xo91TauF>p*A3&+NB za9kV@$Hxh9LYxRE#z}BeoD3(&DR4@h3a7?ta9W%Wr^gxae>fw~gfrtTI4jPEv*R2% zC(ea)<2*Po&WH2k0=OV9gbU*$xF{}$i{lcwBrb(Z<1)A`E{DtG3b-P!ge&7JxGJuO ztK%BDCa#5R<2tx5u7~U62Dl+^gd5`~xG8Rio8uO^C2oaV<2JZ0Zin0B4!9%kggfId zxGV04yW<|XC+>xN<36}A?uYy10eB!Dga_jxcqkr*hvN}=Bp!uF<1u(F9*4){33wu& zgeT)Ecq*QTr{fuTCZ2_7<2iUPo`>h-1$ZG|gcsu_cqv|nm*W+9C0>PB<286KUWeD? z4R|Bogg4_Ycq`t9x8ognC*Fm3<2`sU-iP<&1Nb05gb(8*_$WSxkK+^gBtC^t<1_dy zK8Mfa3-}_wgfHVO_$t1Juj3o|CccGl<2(2+zK8GQ2lyd=gdgK4_$hvdpW_$!C4Plp z<2U#%euv-V5BMYggg@gi_$&T~zvCbHC;o+h<3IQ>{)Yo@^Zz&y4vd4~pg0%~jzi#( zI1~2I2BHf)8Mo?9Zruk;Qw$&oC#;fS#VaI4QIzWa88^H=f-((UYrl-#|3afTnHD& zMQ~AE3>U{Ga7kPWm&RpqSzHd6#}#lzTnSgkRd7{Y4OhoCa7|nb*T!{lU0e^>#|>~p z+z2k4(3^&Iua7)|@x5jO7Tigz}#~pA-+zEHaU2s?24R^;qa8KL|_r`s2U)&G( z#{=*{JO~fQL-0^M3=hX6@JKugkH%y0SUe7o#}n{GJPA+6Q}9$g4Nu22@Ju`l&&G4` zTs#lY#|!X6ya+GGOYl;>3@^tk@JhT2uf}WeTD%Ug#~biQya{i{TkuxA4R6Og@J_r7 z@5X!ZUc3+Q#|Q91d z!BKHE9398NF>x#$8^^(MaXcI!C%_4DBAgf}!AWs4oE)dXDRC;C8mGZ&aXOqHXTbmA zj5rg{jI-dZI2+E6bKsmf7tW3I;Ji2=&W{V=g18VajEmr+xEL;uOW=~Y6fTX+;Igg2& z;I_COZjU?Qj<^% z_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKoafm7mCI5kd#)8ceEJr~a9JjzNaVy*!x4~_3JKP?3z#VZX+!=SlU2!+u z9rwUJaWC8(_rZN}KinS=zyt9hJQxqbL-8;?9FM>w@hChRkHKT{I6NLtz!UK#JQ+{H zQ}HxB9nZis@hm(W&%tx?JUkySzzgvrycjRROYt(i9IwDD@hZF;ufc2aI=milz#H)< zycuu7Tk$r$9q+(9@h-d@@49|WNAWRy9G}1^@hN;7pTTGGIeZ>p zz!&i)d>LQCSMfD`9pAt=@hyBC-@$kBJ$xTOzz^{w{1`vMPw_MS9KXOX@hkiqzrkpPo#ldiJ90G^Lp>Sv% z28YGraCjU6N5qkEWE=%Y#nEtd90SM1v2bi02gk+naD1EqC&YqX2B*d8aC)2p|A#Z;OgJ;ng0tdmI6KaPbK+b$H_n6e;(RziE`ST-Lbxz4f{Wr} zxHv9>OX5szJM>{OZYOrg0JFh_&UCUZ{l0{ zHok-J;(Pc$et;k1NBA**f}i4N_&I)oU*cEzHGYHN;&=Ex{(wK?Pxv$bg1_Q#_&ffA zf8t;GH~xeF;(s{cKL3vc;lMZu4vK@};5Y;hi9_MgI1CPp!{P8a0*;6y;m9}&j*6q< z=r{(BiDTi|I1Y}BHB81LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk z#FcPmTm@If)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#n zd)xtc#GP?yW#Dnl)JOmHL!|-rC0*}O_@Mt^+ zkHzEgcsv15#FOx3JOxk1)9`dW1JA^>@N7H>&&Bibe7pcJ#EbA^yaX@B%kXl%0Z@5TG@etZBQ#E0-{*YI_G1K-5A@NIkt-^KUvef$7F#E8ws@N4`Azs2wHd;9@^#GmkI`~`o--|%<*1OLRo@NfJF|Hc1szytmt2f~4I z5F8W-!@+R~91@4Zp>Y@-7Kg*(aReL@N5YYD6dV;t!_jdJ923XFv2h$67stc#aRQtW zC&Gzw5}Xt#!^v?9oD!$P|Kqxz6~Iss00`#Awr$(CZQHhO+qP}nwr$(aHCN4c^9h>@ zr^ab;TAU82#~E-&oC#;fS#VaI4QIzWa88^H=f-((UYrl-#|3afTnHD&MQ~AE3>U{G za7kPWm&RpqSzHd6#}#lzTnSgkRd7{Y4OhoCa7|nb*T!{lU0e^>#|>~p+z2k4( z3^&Iua7)|@x5jO7Tigz}#~pA-+zEHaU2s?24R^;qa8KL|_r`s2U)&G(#{=*{JO~fQ zL-0^M3=hX6@JKugkH%y0SUe7o#}n{GJPA+6Q}9$g4Nu22@Ju`l&&G4`Ts#lY#|!X6 zya+GGOYl;>3@^tk@JhT2uf}WeTD%Ug#~biQya{i{TkuxA4R6Og@J_r7@5X!ZUc3+Q z#|Q91dg2&;I_COZjU?Q zj<^%_!vHpPvDdI z6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKr~a9JjzNaVy*!x4~_3JKP?3z#VZX+!=SlU2!+u9rwUJaWC8( z_rZN}KinS=zyt9hJQxqbL-8;?9FM>w@hChRkHKT{I6NLtz!UK#JQ+{HQ}HxB9nZis z@hm(W&%tx?JUkySzzgvrycjRROYt(i9IwDD@hZF;ufc2aI=milz#H)9|WNAWRy9G}1^@hN;7pTTGGIeZ>pz!&i)d>LQC zSMfD`9pAt=@hyBC-@$kBJ$xTOzz^{w{1`vMPw_MS9KXOX@hkiqzrkOX5szJM>{OZYOrg0JFh_&UCUZ{l0{Hok-J;(Pc$ zet;k1NBA**f}i4N_&I)oU*cEzHGYHN;&=Ex{(wK?Pxv$bg1_Q#_&ffAf8t;GH~xeF z;(s{66aF6u#DQ>N90Ui&!EkUK0*Az*aA+I`hsEJ=cpL#o#F21h90fHB8 z1LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk#FcPmTm@If z)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW#Dnl)JOmHL!|-rC0*}O_@Mt^+kHzEgcsv15 z#FOx3JOxk1)9`dW1JA^>@N7H>&&Bibe7pcJ#EbA^yaX@B%kXl%0Z@5TG@etZBQ#E0-{*YI_G1K-5A@NIkt-^KUvef$7F#E8ws z@N4`Azs2wHd;9@^#GmkI`~`o--|%<*1OLRo@NfJF|Hc1sfT#RF4u}Kcz&HpFii6?c zI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWKPKXoX#5f5~ zij(2wI0a6LQ{mJ&4Ni;G;q*8I&WJPN%s30qinHPDI0w#&bK%@L56+A8;rzG&E{F@^ z!ng=7ii_dmxCAbVOX1SE3@(ey;qtfwu81q)%D4)yimT!3xCX9?YvJ0s4z7#q;rh4% zZipM<#<&S?ikso)xCL&BTjAEY4Q`9u;r6%#?ua|#&bSNiio4Mh>6X(LYaUPr(=fnAN0bCFl!i8}WTof0>#c>H-5|_fIaT#0| zm&4_81zZtV!j*9qToqTt)o~466W7AEaUEP2*TeO31Kbcd!i{kg+!Qy%&2bCd61T#w zaU0wgx5Mpm2iy^N!kuv!+!c4j-Ej}x6ZgWsaUa|l_rv}106Y*6!h`V;JQNSZ!|@0_ z5|6^8@fbW7kHh2f1UwN>!jth7JQYvF)A0;E6VJl4@fE z^Zz&?4uk{aAUG%vhJ)h}I3x~*L*p&V)1LEI2F9hO^@wI4919 zbK^WXFV2Va;{v!KE`$r?BDg3nhKu78xFjxxOXD)QEG~!3;|jPUu7oS&D!3}HhO6Tm zxF)WJYvVe&F0O~`;|91PZiE}-Cb%hXhMVISxFv3dTjMskEpCU~;|{nZ?u0wzF1Rc1 zhP&e)xF_y~d*eR1FYbr?;{kXe9)t(uA$TYrhKJ)3cqAT$N8>SgEFOo);|X{oo`fgk zDR?TLhNt5hcqX2OXX80|E}n z3WvsFa9A7;hsP0cL>vi6#!+xo91TauF>p*A3&+NBa9kV@$Hxh9LYxRE#z}BeoD3(& zDR4@h3a7?ta9W%Wr^gv^Mw|&}##wMyoDFBkIdD##3+Kjpa9*4b=f?$bL0kwI#zk;Z zTnrb-C2&bx3YW%Za9Laqm&X-wMO+D2##L}tTn$&pHE>N_3)jYVa9vyv*T)TTL)-{A z#!YZj+zdCzEpSWR3b)2>a9i9Cx5piDN8AZ_#$9k%+zoffJ#bIl3-`u-a9`XH_s0Y9 zKs*Q!#zXK>JPZ%VBk)K(3XjHP@K`(!kH-`6L_7&k##8WAJPl9BGw@723(v-L@LW6( z&&Lb!Lc9ns#!K*0ybLeLEAUFZ3a`d%@LIeMug4qkM!X4c##``KybW*1JMd1t3-88z z@Ls$R@5cx5L3{`w#z*i`d<-AQC-6yp3ZKSj@L7BgpT`&QMSKZg##iuFd<|d6H}Fk- z3*W|f@LhZl-^UN|L;MIo#!v85{0u+GFYrtJ3ctp0@LT*2zsDc&NBjwY#$WJP{0)D{ zKk!fd3;)J{@L&872YAW<2I2BHf)8Mo?9Zruk;EXsE z&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5 z;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9!BKHE9398NF>x#$8^^(MaXcI! zC%_4DBAgf}!AWs4oE)dXDRC;C8mGZ&aXOqHXTTY8CY%{(!C7%OoE_)DIdLwW8|T4! zaXy?M7r+H^AzT<2!9{T~TpX9cC2=WS8kfOkaXDNbSHKlXBitA_!A)^9+#I*SEpaQ{8n?l1aXZ`|cfcKSC)^o#!Ci4T+#UD8 zJ#jDG8~4F|aX;K255NQQAUqfk!9(#dJRFa}Bk?Fa8jrza@i;slPrwuLBs>{U!Bg=x zJRQ%#Gx01u8_&UW@jN^qFTe}&BD@$c!AtQnyd1Bo&^C-EtK8lS;u@i}}RU%(gf zC43oQ!B_D$d>!AwH}Nfe8{ffq@jZMWKfn+1Bm5XY!B6os{2af)FYzn<8o$AB@jLt; zf50E{C;Soafm7mC zI5kd#)8ceEJ*9L2K5l>;;zqbJZi1WQ zX1FGyf;!Sun-h#K{ZFoE0fp_9vcsJgI_u_qc zKR$pD;zRf_K7xa@fq&v( z_&5H8|Kfi*z#IM_2gHGJU>pPo#ldiJ90G^Lp>Sv%28YGraCjU6N5qkEWE=%Y#nEtd z90SM1v2bi02gk+naD1EqC&YqX2B*d8aC)2pXT+IsW}F3Q z#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)OTmqNGrEqCn2A9RM^b2lvJOaDO}i55$A;U_1m5#l!G$JOYozqwr`v29L$# z@OV4{PsEe(WIP2=#nbR~JOj_fv+!&@2hYXx@O-=gFT{)RV!Q+|#mn$=yaKPptMF>P z2Cv2I@Or!fZ^WDMX1oP&#oO?9yaVsVyYOzj2k*uE@P2#%AH;|7VSEH1#mDe*d;*`u zr|@Zf2A{>}@OgXzU&NR2Wqbu+#n^KL`iF4uHI1kQ?^Wprs z04|6N;lj8GE{coc;bM53iEH87xDKw1 z>*4yi0d9yJ;l{WLZi<`X=C}oJiCf{;xD9TL+u`=O1MY}B;m)`V?uxtN?zji;iF@JR zxDW1&`{Dk003L`3;lX$a9*T$I;dlfdiAUklcnltk$KmmK0-lH`;mLRko{Fd8>39a7 ziD%*2cn+S6=i&Ky0bYm~;l+3fUW%9D<#+{NiC5v(cnw~Q*WvYe1Kx-?;mvpp-io*3 z?RW>?iFe`Mcn{u-_u>8c06vHh;lua{K8law-Yw~iErWC_zu2{@8SFS0e*-d;m7z1eu|&r=lBJFiC^K@_zixG-{JT81OA9V z;m`OB{))fh@AwD)iGShW_z(Vz|KR}d_Y@-7Kg*( zaReL@N5YYD6dV;t!_jdJ923XFv2h$67stc#aRQtWC&Gzw5}Xt#!^v?9oD!$Psc{;d z7N^7MaR!_bXTq6r7MvAl!`X2ToD=85xp5wx7w5zIaRFQq7s7>c5nL1(!^Lq4ToRYU zrEwWt7MH{2aRpovSHhKX67uUn}aRb~CH^Pl^6WkOx!_9FE z+!D9Kt#KRN7PrIgaR=NHcfy@<7u*$h!`*QY+!Oc0y>TDh7x%;c@c=v!55j}-5IhtQ z!^80iJQ9z>qwyF#7LUW@@dP{(Pr{S&6g(AA!_)B$JQL5tv+*1}7th1<@dCUMFT#uQ z61)^I!^`msyb`a%tMMAV7O%tW@dmsRZ^E1L7Q7X2!`tx=yc6%jyYU{p7w^OS@d11g zAHs+65quOM!^iOnd=j6+r|}tl7N5iC@dbPlU&5F16?_$6!`JZ*d=uZoxA7f(7vID8 z@dNx2Kf;gk6Z{lE!_V;x{1U&yukjoF7Qe&q@dx}7f5M;f7yK1}!{6}_{1gAezwsaZ z7yrWn-t+%BAP$5B;~+RF4u*r{5I7_bg+t>oI4ll_!{Z1zB94S3<0v>Pj)tS-7&s=5 zg=6D5I4+Kd&OPI4{nJ^Wy@zATERp<07~yE{2Qa61XHTg-hcyxGXM*%i{{TBCdoh<0`l+ zu7<1Q8n`B|g=^zFxGt`T>*EHvA#Q{l<0iN%ZibuV7PuvDg?uNVL9=Ip&g?r;ZxG(O9`{Mz4ARdGV;~{t`9)^eG5qKmXg-7Etcq|@=$Kwfj zBA$dN<0*J5o`$F68F(h1g=gbAcrKoY=i>!cr9Ls z*W(R%Bi@8J<1KhA-iEj19e5|+g?HmUcrV_E_u~WjAU=c-<0JSeK8BCu6Zj-Pg-_!% z_$)q$&*KaDBEEz#<16?ozJ{;k8~7%^g>U0K_%6PO@8bvfA%27(<0tqjeukgp7x*Q9 zgg z$02Y?914fVVQ^R+4u{7Pa6}vlN5)ZbR2&UQ$1!kB91F+Bad2E5568y|a6+62C&o!| zQk)DY$0=}1oC>GLX>eMc4yVT%a7LU7XU17@R-6rI$2o9LoD1j1d2n8w59h}Pa6w!M z7sf?!QCtic$0cw{Tnd-QWpG(s4wuIja7A1RSH@LvRa^~M$2D+GTnpF6b#Pr=57);H za6{Y(H^xnHQ``(U$1QM6+zPkGZE#!M4!6f0a7Ww;cg9_CSKJME$31XQ+za={eQ;mg z5BJ9d@IX8W55`0AP&^C|$0P7aJPMD-WAIo!4v)tZ@I*WbPsUU5R6Gq&$20IuJPXgp zbMRa|56{O7@It%@FUCvoQoIZ=$1Ctkyb7@J74|Z^m2jR=f>w$2;&& zybJHfd+=Vo5AVkZ@IibCAI3-UQG5&^$0zVfd~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM& zxEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbeve zjoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%g zk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vO zUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L z;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h 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ɲSD;H&r= zzK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9 z;IH@_{*Hg(pZFL4jsM`k_#X}wnE&IzI0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1 zI0}x6qv7Z{29Aki;n+A1j*H{r_&5O$zzK08oERs;N%4O;8BUH<;FLHOPL0#xv^X73 zk2BzmI1|o{v*4^a8_te%;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT7%q-W;F7o$E{)6J zvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS;Fh=* zZjIaEwzwT`k2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j7#@yC z;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay% z8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sf zkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN z_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>x z6omidz&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC z;rKWK4!{Xg2&;I_COZjU?Qj<^%_!vHpPvDdI6h4j5;IsG~ zK94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zK?kaUz@;C&5Yae>fRV zj#J>2I2BHf)8Mo?9Zruk;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdiE{u!d zqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM&xEwBz zE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbevejoaY1 zxE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%gk$4my zjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAb zm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$ zK8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH z;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X}wod4s% zI0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5O$ zzzK08oERs;N%4O;8BUH<;FLHOPL0#xv^X73k2BzmI1|o{v*4^a8_te%;G8%Y&W-cn zyf`1uj|<>}xDYOki{PTT7%q-W;F7o$E{)6JvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D= zu8r&9y0{*$j~n2IxDjrQo8YFn8E%eS;Fh=*ZjIaEwzwT`k2~OwxD)P-yWp<48}5#K z;GVb_?v4B4zPKOmj|bp^cn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl0 z8lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`? zx8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh z_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}? zk3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>x6oUWbz&HpFii6?cI0O!fL*dXk3=WIK z;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWK4!{Xg2&;I_COZjU?Qj<^%_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(| z;Jf%9zK?kaUz@;C&5Yae>fRVj#J>2I2BHf)8Mo?9Zruk;EXsE&Wy9* ztT-Faj&tCgI2X>1^WeNVAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2s zu8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TGjq~8VI3LcB z3*dsd5H5_1;G(z~E{;p!lDHHujmzM&xEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjL zxE`*L8{mex5pIl|;HJ14ZjM{vmbevejoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8 zjr-ufxF7D12jGEt5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neW znRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY z-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD z;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q} z6aI|9;IH@_{*Hg(pZFL4jsM`k_#X}wn*Za#I0z1kgW=#f1P+Nq;m|k?4vWL#@HhgF zh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5O$zzK08oERs;N%4O;8BUH<;FLHOPL0#x zv^X73k2BzmI1|o{v*4^a8_te%;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT7%q-W;F7o$ zE{)6JvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS z;Fh=*ZjIaEwzwT`k2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j z7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANV zm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3 z_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cy zk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|O zzxW>x6o&ufz&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v61 z4vvfC;rKWK4!{Xg2&;I_COZjU?Qj<^%< zjJx2jxEt<{d*Gh97w(Pw;J&yY?vDrHfp`!ejECT%co-gzN8pio6dsMo;IVid9*-yB ziFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-fSKyU+6<&?k;I()i zUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5 z;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zK?kaUz@;C&5Ya ze>fRVj#J>2I2BHf)8Mo?9Zruk;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdi zE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM& zxEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbeve zjoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%g zk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vO zUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L z;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEG zAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k z_#X}wo&V#&I0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1 zj*H{r_&5O$zzK08oERs;N%4O;8BUH<;FLHOPL0#xv^X73k2BzmI1|o{v*4^a8_te% z;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT7%q-W;F7o$E{)6JvbY>Bk1ODcxDu|6tKh1* z8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS;Fh=*ZjIaEwzwT`k2~OwxD)P- zyWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=X zcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~ zk2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@d zv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtIn zevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>x6odccz&HpFii6?cI0O!f zL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWK4!{Xg2&;I_COZjU?Qj<^%_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>B zZ{VBw7QT(|;Jf%9zK?kaUz@;C&5Yae>fRVj#J>2I2BHf)8Mo?9Zruk z;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO z94?P5;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TG zjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM&xEwBzE8vQ_60VG^;HtP9u8wQq znz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbevejoaY1xE*efJK&DE6Yh+=;I6nE z?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w z;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU z6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fu zFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6 z_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X}woB!j$I0z1kgW=#f1P+Nq;m|k? z4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5O$zzK08oERs;N%4O;8BUH< z;FLHOPL0#xv^X73k2BzmI1|o{v*4^a8_te%;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT z7%q-W;F7o$E{)6JvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQ zo8YFn8E%eS;Fh=*ZjIaEwzwT`k2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^ zcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F) zj~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O* zy?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gs zzK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e z;Gg&x{*C|OzxW>x6o>!gz&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^ zI0lZ1W8v614vvfC;rKWK4!{Xg2&;I_CO zZjU?Qj<^%_!vHp zPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zK?k zaUz@;C&5Yae>fRVj#J>2I2BHf)8Mo?9Zruk;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNV zAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2su8gbTs<;}ij%(nWxE8LB z>)^V$9~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p! zlDHHujmzM&xEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14 zZjM{vmbevejoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(& z;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0> z5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)6 z58#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQa zjo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~5l>g(vI0z1kgW=#f1P+Nq z;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5Phh!f$&I0^m_C&kHd za-0IE#Hny2P|S0cXUSaAuqZXT{lYcANv}#JO;8oCoK{`EY(*02joCaA8~o z7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8aAjNtSH;zEbzB42#IBUM05`;q zaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD+z0o?{cwLg z01w0gcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_= zxp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E z-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi z;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R< z8~%=e;Gg&x{*C|OzxW>xG>re_z&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j) zilgD^I0lZ1W8v614vvfC;rKWKPKXoX#5f854=2URaB`dir^KmnYMchA#p!T*oB?OV znQ&&D1!u+CaCV#n=ft^iZkz|_#rbf4TmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^G0eBD|jECT%co-gzN8pio z6dsMo;IVid9*-yBiFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-f zSKyU+6<&?k;I()iUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@> z_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKHB81LwrKaBiFj z=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk#FcPmTm@If)o^uO1J}g0 zaBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW!~u8^9*l?Jp?DY`jz{2;coZIu$KbJe93GD+;E8w=o{Xp9 zsdyTmj%VPRcov?G=is?`9-faE;DvY*UW}LErFa=$j#uE7cokla*Wk5y9bS(&;Ei|_ z-i){4t#}*Wj(6alco*J{_u##FAKs4-;Dh)OK8%mxqxcv;j!)o|_!K^k&)~E896paP z;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+IsW}F3Q#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)O zTmqNGrEqCn2A9RM^b2lvJOaDO}i55xg@ z5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ z7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWa zcpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&K zjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg( zpZFL4jsM`k_#X~5lK2P|S0cXUSaAuqZ zXT{lYcANv}#JO;8oCoK{`EY(*02joCaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8 zaAjNtSH;zEbzB42#IBUM05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC z0e8fmaA({Fcg5XsciaQ_#JzBD+z0o?{cwLg01w0gcn}_phv1=j7#@yC;E{L~9*xJ~ zv3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4 zUX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q z;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX z8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>xG>ZS@z&HpF zii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWKPKXoX z#5f854=2URaB`dir^KmnYMchA#p!T*oB?OVnQ&&D1!u+CaCV#n=ft^iZkz|_#rbf4 zTmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^G0eBD|jECT%co-gzN8pio6dsMo;IVid9*-yBiFgv8jHlqKcp9FL zXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-fSKyU+6<&?k;I()iUXM56jd&B@jJM#e zcpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0 zjIZFU_!_>BZ{VBw7QT(|;Jf%9zKHB81LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4 zaA{lym&N69d0YWk#FcPmTm@If)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v z0=LAiaBJKKx5e#nd)xtc#GP?yW!~u8^9*l?J zp?DY`jz{2;coZIu$KbJe93GD+;E8w=o{Xp9sdyTmj%VPRcov?G=is?`9-faE;DvY* zUW}LErFa=$j#uE7cokla*Wk5y9bS(&;Ei|_-i){4t#}*Wj(6alco*J{_u##FAKs4- z;Dh)OK8%mxqxcv;j!)o|_!K^k&)~E896paP;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+IsW}F3Q#o2In zoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)OTmqNGrEqCn2A9RM^b2lvJOaDO}i55xg@5FU(&;GuXJ9*#%gk$4myjmO}zcpM&& zC*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{ zcpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-q zjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@ zm-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~5mjC0xI0z1kgW=#f z1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5Phh!f$&I0^m_ zC&kHda-0IE#Hny2P|S0cXUSaAuqZXT{lYcANv}#JO;8oCoK{`EY(*02joC zaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8aAjNtSH;zEbzB42#IBUM z05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD+z0o? z{cwLg01w0gcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9 zo{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0 z;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr z8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It& zzu>R<8~%=e;Gg&x{*C|OzxW>xG>-q{z&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S z$T$j)ilgD^I0lZ1W8v614vvfC;rKWKPKXoX#5f854=2URaB`dir^KmnYMchA#p!T* zoB?OVnQ&&D1!u+CaCV#n=ft^iZkz|_#rbf4TmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^G0eBD|jECT%co-gz zN8pio6dsMo;IVid9*-yBiFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0 zco|-fSKyU+6<&?k;I()iUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0W zjE~@>_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKHB81LwrK zaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk#FcPmTm@If)o^uO z1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW!~u8^9*l?Jp?DY`jz{2;coZIu$KbJe93GD+;E8w= zo{Xp9sdyTmj%VPRcov?G=is?`9-faE;DvY*UW}LErFa=$j#uE7cokla*Wk5y9bS(& z;Ei|_-i){4t#}*Wj(6alco*J{_u##FAKs4-;Dh)OK8%mxqxcv;j!)o|_!K^k&)~E8 z96paP;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+IsW}F3Q#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k z#l>)OTmqNGrEqCn2A9RM^b2lvJOaDO}i z55xg@5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLG zcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}C zjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C; zoA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_ z{*Hg(pZFL4jsM`k_#X~5k^kesI0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6 zqv7Z{29Aki;n+A1j*H{r_&5Phh!f$&I0^m_C&kHda-0IE#Hny2P|S0cXUS zaAuqZXT{lYcANv}#JO;8oCoK{`EY(*02joCaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi z0awJ8aAjNtSH;zEbzB42#IBUM05`;qaAVvAH^t3xbKC;A#I0~^+y=MB z?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD+z0o?{cwLg01w0gcn}_phv1=j7#@yC;E{L~ z9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8 z;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D z7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}L zpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>xG>QM? zz&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWK zPKXoX#5f854=2URaB`dir^KmnYMchA#p!T*oB?OVnQ&&D1!u+CaCV#n=ft^iZkz|_ z#rbf4TmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^G0eBD|jECT%co-gzN8pio6dsMo;IVid9*-yBiFgv8jHlqK zcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-fSKyU+6<&?k;I()iUXM56jd&B@ zjJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5;IsG~K94Wp zi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKHB81LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+4 z0++<4aA{lym&N69d0YWk#FcPmTm@If)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp z&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW!~u8^ z9*l?Jp?DY`jz{2;coZIu$KbJe93GD+;E8w=o{Xp9sdyTmj%VPRcov?G=is?`9-faE z;DvY*UW}LErFa=$j#uE7cokla*Wk5y9bS(&;Ei|_-i){4t#}*Wj(6alco*J{_u##F zAKs4-;Dh)OK8%mxqxcv;j!)o|_!K^k&)~E896paP;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+IsW}F3Q z#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)OTmqNGrEqCn2A9RM^b2lvJOaDO}i55xg@5FU(&;GuXJ9*#%gk$4myjmO}z zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3i zjo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b| zllT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT* zevV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~5mH*?wI0z1k zgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5Phh!f$& zI0^m_C&kHda-0IE#Hny2P|S0cXUSaAuqZXT{lYcANv}#JO;8oCoK{`EY(* z02joCaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8aAjNtSH;zEbzB42#IBUM05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD z+z0o?{cwLg01w0gcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4 z;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK z8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1 zui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm# z_!It&zu>R<8~%=e;Gg&x{*C|OzxW>xG>!k`z&HpFii6?cI0O!fL*dXk3=WIK;qW*D zj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWKPKXoX#5f854=2URaB`dir^KmnYMchA z#p!T*oB?OVnQ&&D1!u+CaCV#n=ft^iZkz|_#rbf4TmToug>Ye91Q*4{aB*A$m&B!T zX#r<%9JOB^G0eBD|jECT% zco-gzN8pio6dsMo;IVid9*-yBiFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHO zjF;f0co|-fSKyU+6<&?k;I()iUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9 zgZL0WjE~@>_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9 zzKHB8 z1LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk#FcPmTm@If z)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW!~u8^9*l?Jp?DY`jz{2;coZIu$KbJe93GD+ z;E8w=o{Xp9sdyTmj%VPRcov?G=is?`9-faE;DvY*UW}LErFa=$j#uE7cokla*Wk5y z9bS(&;Ei|_-i){4t#}*Wj(6alco*J{_u##FAKs4-;Dh)OK8%mxqxcv;j!)o|_!K^k z&)~E896paP;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+IsW}F3Q#o2InoCD{?xo~cr2j|84aDH3>7sQ2d zVO#_k#l>)OTmqNGrEqCn2A9RM^b2lvJO zaDO}i55xg@5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphS zjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+R zop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r= zzK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9 z;IH@_{*Hg(pZFL4jsM`k_#X~5lmFwuI0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1 zI0}x6qv7Z{29Aki;n+A1j*H{r_&5Phh!f$&I0^m_C&kHda-0IE#Hny2P|S z0cXUSaAuqZXT{lYcANv}#JO;8oCoK{`EY(*02joCaA8~o7sbVJaa;nI#HDa)Tn3lL z<#2gi0awJ8aAjNtSH;zEbzB42#IBUM05`;qaAVvAH^t3xbKC;A#I0~^ z+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_#JzBD+z0o?{cwLg01w0gcn}_phv1=j7#@yC z;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay% z8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sf zkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN z_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>x zG>iY^z&HpFii6?cI0O!fL*dXk3=WIK;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC z;rKWKPKXoX#5f854=2URaB`dir^KmnYMchA#p!T*oB?OVnQ&&D1!u+CaCV#n=ft^i zZkz|_#rbf4TmToug>Ye91Q*4{aB*A$m&B!TX#r<%9JOB^G0eBD|jECT%co-gzN8pio6dsMo;IVid9*-yBiFgv8 zjHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-fSKyU+6<&?k;I()iUXM56 zjd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5;IsG~ zK94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zKHB81LwrKaBiFj=f(MOep~<-#D#ESTm%=z z#c*+40++<4aA{lym&N69d0YWk#FcPmTm@If)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c z+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc#GP?yW z!~u8^9*l?Jp?DY`jz{2;coZIu$KbJe93GD+;E8w=o{Xp9sdyTmj%VPRcov?G=is?` z9-faE;DvY*UW}LErFa=$j#uE7cokla*Wk5y9bS(&;Ei|_-i){4t#}*Wj(6alco*J{ z_u##FAKs4-;Dh)OK8%mxqxcv;j!)o|_!K^k&)~E896paP;EVVYzKpNntN0qej&IqX2B*d8aC)2pXT+Is zW}F3Q#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)OTmqNGrEqCn2A9RM^b2lvJOaDO}i55xg@5FU(&;GuXJ9*#%gk$4my zjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAb zm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$ zK8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH z;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(pZFL4jsM`k_#X~5m;d9y zI0z1kgW=#f1P+Nq;m|k?4vWL#@HhgFh$G?1I0}x6qv7Z{29Aki;n+A1j*H{r_&5Ph zh!f$&I0^m_C&kHda-0IE#Hny2P|S0cXUSaAuqZXT{lYcANv}#JO;8oCoK{ z`EY(*02joCaA8~o7sbVJaa;nI#HDa)Tn3lL<#2gi0awJ8aAjNtSH;zEbzB42#IBUM05`;qaAVvAH^t3xbKC;A#I0~^+y=MB?QnbC0e8fmaA({Fcg5XsciaQ_ z#JzBD+z0o?{cwLg01w0gcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl0 z8lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`? zx8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh z_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}? zk3Zm#_!It&zu>R<8~%=e;Gg&x{*C|OzxW>xG>`w|z&HpFii6?cI0O!fL*dXk3=WIK z;qW*Dj))`S$T$j)ilgD^I0lZ1W8v614vvfC;rKWKPKXoX#5f854=2URaB`dir^Kmn zYMchA#p!T*oB?OVnQ&&D1!u+CaCV#n=ft^iZkz|_#rbf4TmToug>Ye91Q*4{aB*A$ zm&B!TX#r<%9JOB^G0eBD| zjECT%co-gzN8pio6dsMo;IVid9*-yBiFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tyc zg?JHOjF;f0co|-fSKyU+6<&?k;I()iUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs z-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(| z;Jf%9zKHB81LwrKaBiFj=f(MOep~<-#D#ESTm%=z#c*+40++<4aA{lym&N69d0YWk#FcPm zTm@If)o^uO1J}g0aBW-%*Twa4ecS*y#Eo!c+ypnp&2V$v0=LAiaBJKKx5e#nd)xtc z#GP?yW!~u8^9*l?Jp?DY`jz{2;coZIu$KbJe z93GD+;E8w=o{Xp9sdyTmj%VPRcov?G=is?`9-faE;DvY*UW}LErFa=$j#uE7cokla z*Wk5y9bS(&;Ei|_-i){4t#}*Wj(6alco*J{_u##FAKs4-;Dh)OK8%mxqxcv;j!)o| z_!K^k&)~E896paP;EVVYzKpNntN0qej&I#3_6Wi8{ZQHhO+qP}n_Kj`Zwrz7}UCTQC1)T&Z#mR7ToC2rBsc>qX z2B*d8aC)2pXT+IsW}F3Q#o2InoCD{?xo~cr2j|84aDH3>7sQ2dVO#_k#l>)OTmqNG zrEqCn2A9RM^b2lvJOaDO}i55$A;U_1m5 z#l!G$JOYozqwr`v29L$#@OV4{PsEe(WIP2=#nbR~JOj_fv+!&@2hYXx@O-=gFT{)R zV!Q+|#mn$=yaKPptMF>P2Cv2I@Or!f2jGo(6W)xs;H`KY-i~+Rop=}CjrZWacpu)6 z58#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h 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ɲSD z;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q} z6aI|9;IH@_{*Hg(0Q?jG!oTq!{1^Yjfu8Vx92ozHgW#Yz7!Hm@;E*^J4voX$us9qJ zk0aoSI1-MGqu{7G8jg-*;FvfTj*a8sxHuk;j}zd8I1x^ali;K{8BUH<;FLHOPL0#x zv^X73k2BzmI1|o{v*4^a8_te%;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT7%q-W;F7o$ zE{)6JvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS z;Fh=*ZjIaEwzwT`k2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j z7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANV zm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3 z_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cy zk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e-~jv+|H8lV zAN&{p!-1aie;gS9hlAjtI2aC&L*S4&6b_BU;IKFx4v!g2&;I_COZjU?Qj<^%< zjJx2jxEt<{d*Gh97w(Pw;J&yY?vDrHfp`!ejECT%co-gzN8pio6dsMo;IVid9*-yB ziFgv8jHlqKcp9FLXW*H57M_jg;JJ7no{tycg?JHOjF;f0co|-fSKyU+6<&?k;I()i zUXM56jd&B@jJM#ecpKi1ci^3P7v7Ec;JtVs-j5I9gZL0WjE~@>_!vHpPvDdI6h4j5 z;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zK2I2BHf)8Mo?9Zruk;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdi zE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM& zxEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbeve zjoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%g zk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vO zUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L z;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h 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ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEG zAK-`h5q^xH;HUT*evV(@m-rQajo;w6_#J+aKj4q}6aI|9;IH@_{*Hg(0Q?jG!oTq! z{1^Yjfj;nm92ozHgW#Yz7!Hm@;E*^J4voX$us9qJk0aoSI1-MGqu{7G8jg-*;FvfT zj*a8sxHuk;j}zd8I1x^ali;K{8BUH<;FLHOPL0#xv^X73k2BzmI1|o{v*4^a8_te% z;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT7%q-W;F7o$E{)6JvbY>Bk1ODcxDu|6tKh1* z8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQo8YFn8E%eS;Fh=*ZjIaEwzwT`k2~OwxD)P- zyWp<48}5#K;GVb_?v4B4zPKOmj|bp^cn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=X zcoLqBr{Jl08lH}4;F)+9o{i_=xp*F)j~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~ zk2m0rcoW`?x8SXK8{Uq0;GK9E-i`O*y?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@d zv-li7k1ybh_!7R1ui&fr8orKi;G6gszK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtIn zevRMYxA+}?k3Zm#_!It&zu>R<8~%=e-~jv+|H8lVAN&{p!+}2Xe;gS9hlAjtI2aC& zL*S4&6b_BU;IKFx4v!g2&;I_COZjU?Qj<^%_!vHpPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>B zZ{VBw7QT(|;Jf%9zK2I2BHf)8Mo?9Zruk z;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNVAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO z94?P5;EK2su8gbTs<;}ij%(nWxE8LB>)^V$9~7oH!TG zjq~8VI3LcB3*dsd5H5_1;G(z~E{;p!lDHHujmzM&xEwBzE8vQ_60VG^;HtP9u8wQq znz$COjqBjLxE`*L8{mex5pIl|;HJ14ZjM{vmbevejoaY1xE*efJK&DE6Yh+=;I6nE z?v8ulp12q8jr-ufxF7D12jGEt5FU(&;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w z;Hh{To{neWnRphSjpyLGcpjdQ7vP0>5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU z6W)xs;H`KY-i~+Rop=}CjrZWacpu)658#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fu zFW`ɲSD;H&r=zK(C;oA?&Kjql*Q_#VEGAK-`h5q^xH;HUT*evV(@m-rQajo;w6 z_#J+aKj4q}6aI|9;IH@_{*Hg(0Q?jG!oTq!{1^Yjfxhs692ozHgW#Yz7!Hm@;E*^J z4voX$us9qJk0aoSI1-MGqu{7G8jg-*;FvfTj*a8sxHuk;j}zd8I1x^ali;K{8BUH< z;FLHOPL0#xv^X73k2BzmI1|o{v*4^a8_te%;G8%Y&W-cnyf`1uj|<>}xDYOki{PTT z7%q-W;F7o$E{)6JvbY>Bk1ODcxDu|6tKh1*8m^9O;F`D=u8r&9y0{*$j~n2IxDjrQ zo8YFn8E%eS;Fh=*ZjIaEwzwT`k2~OwxD)P-yWp<48}5#K;GVb_?v4B4zPKOmj|bp^ zcn}_phv1=j7#@yC;E{L~9*xJ~v3MLFk0;=XcoLqBr{Jl08lH}4;F)+9o{i_=xp*F) zj~C#DcoANVm*Ay%8D5T8;FWk4UX9n_wRjy~k2m0rcoW`?x8SXK8{Uq0;GK9E-i`O* zy?7tqj}PF3_z*sfkKm*D7(R|q;FI_iK8?@dv-li7k1ybh_!7R1ui&fr8orKi;G6gs zzK!qTyZ9cyk00QN_z`}LpWvtX8Gepm;FtInevRMYxA+}?k3Zm#_!It&zu>R<8~%=e z-~jv+|H8lVAN&{p!-2l?e;gS9hlAjtI2aC&L*S4&6b_BU;IKFx4v!g2&;I_CO zZjU?Qj<^%_!vHp zPvDdI6h4j5;IsG~K94Wpi}(`0jIZFU_!_>BZ{VBw7QT(|;Jf%9zK2I2BHf)8Mo?9Zruk;EXsE&Wy9*tT-Faj&tCgI2X>1^WeNV zAI^^p;DWdiE{u!dqPQ3?j!WQ@xD+mp%iyxO94?P5;EK2su8gbTs<;}ij%(nWxE8LB z>)^V$9~7oH!TGjq~8VI3LcB3*dsd5H5_1;G(z~E{;p! zlDHHujmzM&xEwBzE8vQ_60VG^;HtP9u8wQqnz$COjqBjLxE`*L8{mex5pIl|;HJ14 zZjM{vmbevejoaY1xE*efJK&DE6Yh+=;I6nE?v8ulp12q8jr-ufxF7D12jGEt5FU(& z;GuXJ9*#%gk$4myjmO}zcpM&&C*X;A5}u5w;Hh{To{neWnRphSjpyLGcpjdQ7vP0> z5nha!;H7vOUXEAbm3S3ijo09{cpYAkH{gwU6W)xs;H`KY-i~+Rop=}CjrZWacpu)6 z58#9N5I&5L;G_5$K8{b|llT-qjnCk-_#8fuFW`ɲSD;H&r=zK(C;oA?&Kjql*Q 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 R0RD-8;otZV{)_+N{{b}3!%zSK literal 0 HcmV?d00001 diff --git a/benchmarks/models/cb_explore_adf_small.m b/benchmarks/models/cb_explore_adf_small.m new file mode 100644 index 0000000000000000000000000000000000000000..2c1f7ca02f2083e30e5e02a31977e076194c342f GIT binary patch literal 21121 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 literal 0 HcmV?d00001 diff --git a/bindings/cs/rl.net.cli.test/UnicodeTest.cs b/bindings/cs/rl.net.cli.test/UnicodeTest.cs index bbd2c853b..8527c856c 100644 --- a/bindings/cs/rl.net.cli.test/UnicodeTest.cs +++ b/bindings/cs/rl.net.cli.test/UnicodeTest.cs @@ -1,10 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Rl.Net.Native; using System.Runtime.InteropServices; @@ -33,7 +30,7 @@ public class UnicodeTest : TestBase const string PseudoLocJsonKey4 = "ℓôřú₥ ïƥƨú₥ δôℓôř"; const string PseudoLocJsonValue4 = "áβç { δèƒ: ϱλï }"; - const string PseudoLocConfigJson = + const string PseudoLocConfigJsonPdfModel = @"{ ""ApplicationID"": ""ßïϱTèƨƭÂƥƥℓïçáƭïôñNá₥è-ℓôř"", ""IsExplorationEnabled"": true, @@ -41,8 +38,24 @@ public class UnicodeTest : TestBase ""model.source"": ""NO_MODEL_DATA"", ""model.implementation"": ""PASSTHROUGH_PDF"", ""model.backgroundrefresh"": false, + ""episode.sender.implementation"": ""EPISODE_FILE_SENDER"", ""observation.sender.implementation"": ""OBSERVATION_FILE_SENDER"", - ""interaction.sender.implementation"": ""INTERACTION_FILE_SENDER"" + ""interaction.sender.implementation"": ""INTERACTION_FILE_SENDER"", + ""protocol.version"": 2 +} +"; + const string PseudoLocConfigJsonVwModel = +@"{ + ""ApplicationID"": ""ßïϱTèƨƭÂƥƥℓïçáƭïôñNá₥è-ℓôř"", + ""IsExplorationEnabled"": true, + ""InitialExplorationEpsilon"": 1.0, + ""model.source"": ""NO_MODEL_DATA"", + ""model.implementation"": ""VW"", + ""model.backgroundrefresh"": false, + ""episode.sender.implementation"": ""EPISODE_FILE_SENDER"", + ""observation.sender.implementation"": ""OBSERVATION_FILE_SENDER"", + ""interaction.sender.implementation"": ""INTERACTION_FILE_SENDER"", + ""protocol.version"": 2 } "; @@ -66,7 +79,7 @@ public class UnicodeTest : TestBase } "; - static float [] ExpectedPdf = { 0.4f, 0.6f }; + static float[] ExpectedPdf = { 0.4f, 0.6f }; const float Epsilon = float.Epsilon; const string PseudoLocMultiSlotContextWithPdf = @@ -105,6 +118,8 @@ public class UnicodeTest : TestBase ] } "; + const string ContextJson = + @"{ ""GUser"":{""id"":""mk"",""major"":""psychology"",""hobby"":""kids"",""favorite_character"":""7of9""}, ""_multi"": [ { ""TAction"":{""topic"":""SkiConditions-VT""} }, { ""TAction"":{""topic"":""HerbGarden""} }, { ""TAction"":{""topic"":""BeyBlades""} }, { ""TAction"":{""topic"":""NYCLiving""} }, { ""TAction"":{""topic"":""MachineLearning""} } ] }"; private void Run_PtrToStringUtf8_RoundtripTest(string str, string message) { @@ -281,11 +296,11 @@ public void Test_Configuration_Get() Run_ConfigurationGet_Test(PseudoLocJsonKey3, valueToReturn: String.Empty); } - private LiveModel ConfigureLiveModel() + private LiveModel ConfigureLiveModel(string configuration) { Configuration config; ApiStatus apiStatus = new ApiStatus(); - if (!Configuration.TryLoadConfigurationFromJson(PseudoLocConfigJson, out config, apiStatus)) + if (!Configuration.TryLoadConfigurationFromJson(configuration, out config, apiStatus)) { Assert.Fail("Failed to parse pseudolocalized configuration JSON: " + apiStatus.ErrorMessage); } @@ -296,6 +311,10 @@ private LiveModel ConfigureLiveModel() TempFileDisposable observationDisposable = new TempFileDisposable(); this.TestCleanup.Add(observationDisposable); + TempFileDisposable episodeDisposable = new TempFileDisposable(); + this.TestCleanup.Add(episodeDisposable); + + config["episode.file.name"] = episodeDisposable.Path; config["interaction.file.name"] = interactionDisposable.Path; config["observation.file.name"] = observationDisposable.Path; @@ -322,8 +341,7 @@ private void ValidatePdf(RankingResponse rankingResponse) [TestMethod] public void Test_LiveModel_ChooseRankE2E() { - LiveModel liveModel = this.ConfigureLiveModel(); - + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); RankingResponse rankingResponse1 = liveModel.ChooseRank(PseudoLocEventId, PseudoLocContextJsonWithPdf); ValidatePdf(rankingResponse1); @@ -331,6 +349,29 @@ public void Test_LiveModel_ChooseRankE2E() ValidatePdf(rankingResponse2); } + [TestMethod] + public void Test_LiveModel_RequestEpisodicE2E() + { + string episodeId = "episode-0"; + string eventId = "event1"; + string previousEventId = "event0"; + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonVwModel); + EpisodeState state = new EpisodeState(episodeId); + RankingResponse resp = new RankingResponse(); + + state.Update(eventId, previousEventId, ContextJson, resp); + + var response = liveModel.RequestEpisodicDecision(eventId, previousEventId, ContextJson, ActionFlags.Deferred, state); + Assert.AreEqual(0, response.ChosenAction); + + // Report reward on whole episode + liveModel.QueueOutcomeEvent(episodeId, 0.5f); + // Report reward on one step + liveModel.QueueOutcomeEvent(episodeId, eventId, 0.5f); + // Action Taken events + liveModel.QueueActionTakenEvent(episodeId, eventId); + } + private void Run_LiveModelChooseRank_Test(LiveModel liveModel, string eventId, string contextJson) { NativeMethods.LiveModelChooseRankOverride = @@ -368,7 +409,7 @@ private void Run_LiveModelChooseRankWithFlags_Test(LiveModel liveModel, string e [TestMethod] public void Test_LiveModel_ChooseRank() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelChooseRank_Test(liveModel, PseudoLocEventId, PseudoLocContextJsonWithPdf); Run_LiveModelChooseRankWithFlags_Test(liveModel, PseudoLocEventId, PseudoLocContextJsonWithPdf); @@ -405,7 +446,7 @@ private void Run_LiveModelRequestDecisionWithFlags_Test(LiveModel liveModel, str [TestMethod] public void Test_LiveModel_RequestDecision() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelRequestDecision_Test(liveModel, PseudoLocContextJsonWithPdf); Run_LiveModelRequestDecisionWithFlags_Test(liveModel, PseudoLocContextJsonWithPdf); @@ -442,7 +483,7 @@ private void Run_LiveModelRequestMultiSlotDetailedWithFlags_Test(LiveModel liveM [TestMethod] public void Test_LiveModel_RequestMultiSlotDecisionDetailed() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelRequestMultiSlotDetailed_Test(liveModel, PseudoLocMultiSlotContextWithPdf, PseudoLocEventId); Run_LiveModelRequestMultiSlotDetailedWithFlags_Test(liveModel, PseudoLocMultiSlotContextWithPdf, PseudoLocEventId); @@ -479,7 +520,7 @@ private void Run_LiveModelRequestMultiSlotWithFlags_Test(LiveModel liveModel, st [TestMethod] public void Test_LiveModel_RequestMultiSlotDecision() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelRequestMultiSlot_Test(liveModel, PseudoLocMultiSlotContextWithPdf, PseudoLocEventId); Run_LiveModelRequestMultiSlotWithFlags_Test(liveModel, PseudoLocMultiSlotContextWithPdf, PseudoLocEventId); @@ -502,11 +543,50 @@ private void Run_LiveModelRequestContinuousAction_Test(LiveModel liveModel, stri [TestMethod] public void Test_LiveModel_RequestContinuousAction() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelRequestContinuousAction_Test(liveModel, PseudoLocContextJsonWithPdf); } + private void Run_LiveModelRequestEpisodicDecisionWithFlags_Test(LiveModel liveModel, string eventId, string previousEventId, string contextJson, ActionFlags flags, EpisodeState episode) + { + NativeMethods.LiveModelRequestEpisodicDecisionWithFlagsOverride = + (IntPtr liveModelPtr, IntPtr previousEventIdPtr, IntPtr eventIdPtr, IntPtr contextJsonPtr, uint flags, IntPtr rankingResponse, IntPtr episodeHistory, IntPtr ApiStatus) => + { + string contextJsonMarshalledBack = NativeMethods.StringMarshallingFunc(contextJsonPtr); + Assert.AreEqual(contextJson, contextJsonMarshalledBack, "Marshalling contextJson does not work properly in LiveModelRequestDecisionWithFlags"); + + return NativeMethods.SuccessStatus; + }; + + liveModel.RequestEpisodicDecision(eventId, previousEventId, contextJson, flags, episode); + } + + private void Run_LiveModelRequestEpisodicDecision_Test(LiveModel liveModel, string eventId, string previousEventId, string contextJson, EpisodeState episodes) + { + NativeMethods.LiveModelRequestEpisodicDecisionWithFlagsOverride = + (IntPtr liveModelPtr, IntPtr previousEventIdPtr, IntPtr eventIdPtr, IntPtr contextJsonPtr, uint flags, IntPtr rankingResponse, IntPtr episodeHistory, IntPtr ApiStatus) => + { + string contextJsonMarshalledBack = NativeMethods.StringMarshallingFunc(contextJsonPtr); + Assert.AreEqual(contextJson, contextJsonMarshalledBack, "Marshalling contextJson does not work properly in LiveModelRequestDecisionWithFlags"); + + return NativeMethods.SuccessStatus; + }; + + liveModel.RequestEpisodicDecision(eventId, previousEventId, contextJson, ActionFlags.Default, episodes); + } + + [TestMethod] + public void Test_LiveModel_RequestEpisodicDecision() + { + string episodeId = "episode0"; + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); + EpisodeState episodes = new EpisodeState(episodeId); + + Run_LiveModelRequestEpisodicDecision_Test(liveModel, "event 0", PseudoLocEventId, PseudoLocContextJsonWithPdf, episodes); + Run_LiveModelRequestEpisodicDecisionWithFlags_Test(liveModel, "event 0", PseudoLocEventId, PseudoLocContextJsonWithPdf, ActionFlags.Deferred, episodes); + } + // TODO: Create a real CCB context json and add an E2E test (pending CCB E2E and // clarity around sample mode.) @@ -527,11 +607,38 @@ private void Run_LiveModelReportActionTaken_Test(LiveModel liveModel, string eve [TestMethod] public void Test_LiveModel_ReportActionTaken() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelReportActionTaken_Test(liveModel, PseudoLocEventId); } + private void Run_LiveModelReportActionTakenMultiId_Test(LiveModel liveModel, string episodeId, string eventId) + { + NativeMethods.LiveModelReportActionTakenMultiIdOverride = + (IntPtr liveModelPtr, IntPtr episodeIdPtr, IntPtr eventIdPtr, IntPtr apiStatus) => + { + string episodeIdPtrMarshalledBack = NativeMethods.StringMarshallingFunc(episodeIdPtr); + Assert.AreEqual(episodeId, episodeIdPtrMarshalledBack, "Marshalling episodeId does not work properly in Run_LiveModelReportActionTakenMultiId"); + + string eventIdMarshalledBack = NativeMethods.StringMarshallingFunc(eventIdPtr); + Assert.AreEqual(eventId, eventIdMarshalledBack, "Marshalling eventId does not work properly in Run_LiveModelReportActionTakenMultiId"); + + return NativeMethods.SuccessStatus; + }; + + liveModel.TryQueueActionTakenEvent(episodeId, eventId); + } + + [TestMethod] + public void Test_LiveModel_ReportActionTakenMultiId() + { + string episodeId = "episode0"; + string eventId = "event0"; + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); + + Run_LiveModelReportActionTakenMultiId_Test(liveModel, episodeId, eventId); + } + private void Run_LiveModelReportOutcomeF_Test(LiveModel liveModel, string eventId, float outcome) { NativeMethods.LiveModelReportOutcomeFOverride = @@ -634,7 +741,7 @@ private void Run_LiveModelReportOutcomeSlotStringIdJson_Test(LiveModel liveModel [TestMethod] public void Test_LiveModel_ReportOutcome() { - LiveModel liveModel = this.ConfigureLiveModel(); + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); Run_LiveModelReportOutcomeF_Test(liveModel, PseudoLocEventId, 1.0f); Run_LiveModelReportOutcomeJson_Test(liveModel, PseudoLocEventId, PseudoLocOutcomeJson); @@ -643,6 +750,29 @@ public void Test_LiveModel_ReportOutcome() Run_LiveModelReportOutcomeSlotStringIdF_Test(liveModel, PseudoLocEventId, "SlotId", 1.0f); Run_LiveModelReportOutcomeSlotStringIdJson_Test(liveModel, PseudoLocEventId, "SlotId", PseudoLocOutcomeJson); } + private void Run_LiveModelReportOutcomeMultiId_Test(LiveModel liveModel, string episodeId, string eventId, float outcome) + { + NativeMethods.LiveModelReportOutcomeSlotStringIdFOverride = + (IntPtr liveModelPtr, IntPtr episodeIdPtr, IntPtr eventIdPtr, float outcomeF, IntPtr apiStatus) => + { + string episodeIdMarshalledBack = NativeMethods.StringMarshallingFunc(episodeIdPtr); + Assert.AreEqual(episodeId, episodeIdMarshalledBack, "Marshalling episodeId does not work properly in Run_LiveModelReportOutcomeMultiId"); + + string eventIdMarshalledBack = NativeMethods.StringMarshallingFunc(eventIdPtr); + Assert.AreEqual(eventId, eventIdMarshalledBack, "Marshalling eventId does not work properly in Run_LiveModelReportOutcomeMultiId"); + Assert.AreEqual(outcome, outcomeF, 1e-6); + return NativeMethods.SuccessStatus; + }; + + liveModel.QueueOutcomeEvent(episodeId, eventId, outcome); + } + + [TestMethod] + public void Test_LiveModel_ReportOutcomeMultiId() + { + LiveModel liveModel = this.ConfigureLiveModel(PseudoLocConfigJsonPdfModel); + Run_LiveModelReportOutcomeMultiId_Test(liveModel, "episode0", "event0", 0.5f); + } private void Run_StringReturnMarshallingTest(string valueToReturn, Action> registerNativeOverride, Func targetInvocation, string targetInvocationName) where TNativeObject : NativeObject, new() diff --git a/bindings/cs/rl.net.native/CMakeLists.txt b/bindings/cs/rl.net.native/CMakeLists.txt index 0c5eec51a..243b82409 100644 --- a/bindings/cs/rl.net.native/CMakeLists.txt +++ b/bindings/cs/rl.net.native/CMakeLists.txt @@ -6,6 +6,7 @@ set(rl_net_native_SOURCES rl.net.config.cc rl.net.continuous_action_response.cc rl.net.decision_response.cc + rl.net.episode_state.cc rl.net.factory_context.cc rl.net.live_model.cc rl.net.native.cc @@ -23,6 +24,7 @@ set(rl_net_native_HEADERS rl.net.config.h rl.net.continuous_action_response.h rl.net.decision_response.h + rl.net.episode_state.h rl.net.factory_context.h rl.net.live_model.h rl.net.native.h diff --git a/bindings/cs/rl.net.native/rl.net.episode_state.cc b/bindings/cs/rl.net.native/rl.net.episode_state.cc new file mode 100644 index 000000000..99e22bcec --- /dev/null +++ b/bindings/cs/rl.net.native/rl.net.episode_state.cc @@ -0,0 +1,20 @@ +#include "rl.net.episode_state.h" + +API reinforcement_learning::episode_state* CreateEpisodeState(const char* episodeId) +{ + return new reinforcement_learning::episode_state(episodeId); +} + +API void DeleteEpisodeState(reinforcement_learning::episode_state* episode_state) { delete episode_state; } + +API const char* GetEpisodeId(reinforcement_learning::episode_state* episode_state) +{ + return episode_state->get_episode_id(); +} + +API int UpdateEpisodeHistory(reinforcement_learning::episode_state* episode_state, const char* event_id, + const char* previous_event_id, const char* context, const reinforcement_learning::ranking_response& resp, + reinforcement_learning::api_status* error) +{ + return episode_state->update(event_id, previous_event_id, context, resp, error); +} \ No newline at end of file diff --git a/bindings/cs/rl.net.native/rl.net.episode_state.h b/bindings/cs/rl.net.native/rl.net.episode_state.h new file mode 100644 index 000000000..aaafccbf2 --- /dev/null +++ b/bindings/cs/rl.net.native/rl.net.episode_state.h @@ -0,0 +1,19 @@ +#pragma once + +#include "rl.net.native.h" + +#include +#include + +// Global exports +extern "C" +{ + // NOTE: THIS IS NOT POLYMORPHISM SAFE! + API reinforcement_learning::episode_state* CreateEpisodeState(const char* episodeId); + API void DeleteEpisodeState(reinforcement_learning::episode_state* episode_state); + + API const char* GetEpisodeId(reinforcement_learning::episode_state* episode_state); + API int UpdateEpisodeHistory(reinforcement_learning::episode_state* episode_state, const char* event_id, + const char* previous_event_id, const char* context, const reinforcement_learning::ranking_response& resp, + reinforcement_learning::api_status* error = nullptr); +} \ No newline at end of file diff --git a/bindings/cs/rl.net.native/rl.net.factory_context.cc b/bindings/cs/rl.net.native/rl.net.factory_context.cc index 467fb5f52..c2cbe0027 100644 --- a/bindings/cs/rl.net.native/rl.net.factory_context.cc +++ b/bindings/cs/rl.net.native/rl.net.factory_context.cc @@ -53,11 +53,11 @@ void invoke_error_callback(error_callback_fn* error_callback, api_status* status API void SetFactoryContextBindingSenderFactory( factory_context_t* context, rl_net_native::sender_create_fn create_fn, rl_net_native::sender_vtable vtable) { - auto sender_factory_fn = [=](i_sender** retval, const utility::configuration& configuration, + auto sender_factory_fn = [=](std::unique_ptr& retval, const utility::configuration& configuration, error_callback_fn* error_callback, i_trace* trace_logger, api_status* status) { void* managed_handle = create_fn(&configuration, invoke_error_callback, error_callback); - *retval = new rl_net_native::binding_sender(managed_handle, vtable, trace_logger); + retval.reset(new rl_net_native::binding_sender(managed_handle, vtable, trace_logger)); return error_code::success; }; diff --git a/bindings/cs/rl.net.native/rl.net.live_model.cc b/bindings/cs/rl.net.native/rl.net.live_model.cc index 88e4b38e5..7525d6ff8 100644 --- a/bindings/cs/rl.net.native/rl.net.live_model.cc +++ b/bindings/cs/rl.net.native/rl.net.live_model.cc @@ -24,11 +24,12 @@ API livemodel_context_t* CreateLiveModel( context->trace_logger_factory = nullptr; // Create a trace log factory by passing in below creator. It allows LiveModel to use trace_logger provided by user. - const auto binding_tracer_create = - [context](reinforcement_learning::i_trace** retval, const reinforcement_learning::utility::configuration& cfg, - reinforcement_learning::i_trace* trace_logger, reinforcement_learning::api_status* status) + const auto binding_tracer_create = [context](std::unique_ptr& retval, + const reinforcement_learning::utility::configuration& cfg, + reinforcement_learning::i_trace* trace_logger, + reinforcement_learning::api_status* status) { - *retval = new rl_net_native::binding_tracer(*context); + retval.reset(new rl_net_native::binding_tracer(*context)); return reinforcement_learning::error_code::success; }; @@ -211,12 +212,38 @@ API int LiveModelRequestMultiSlotDecisionDetailedWithBaselineAndFlags(livemodel_ RL_IGNORE_DEPRECATED_USAGE_END } +API int LiveModelRequestEpisodicDecisionWithFlags(livemodel_context_t* context, const char* event_id, + const char* previous_id, const char* context_json, unsigned int flags, + reinforcement_learning::ranking_response& resp, reinforcement_learning::episode_state& episode, + reinforcement_learning::api_status* status) +{ + RL_IGNORE_DEPRECATED_USAGE_START + return context->livemodel->request_episodic_decision( + event_id, previous_id, context_json, flags, resp, episode, status); + RL_IGNORE_DEPRECATED_USAGE_END +} + +API int LiveModelRequestEpisodicDecision(livemodel_context_t* context, const char* event_id, const char* previous_id, + const char* context_json, reinforcement_learning::ranking_response& resp, + reinforcement_learning::episode_state& episode, reinforcement_learning::api_status* status) +{ + RL_IGNORE_DEPRECATED_USAGE_START + return context->livemodel->request_episodic_decision(event_id, previous_id, context_json, resp, episode, status); + RL_IGNORE_DEPRECATED_USAGE_END +} + API int LiveModelReportActionTaken( livemodel_context_t* context, const char* event_id, reinforcement_learning::api_status* status) { return context->livemodel->report_action_taken(event_id, status); } +API int LiveModelReportActionMultiIdTaken(livemodel_context_t* context, const char* primary_id, + const char* secondary_id, reinforcement_learning::api_status* status) +{ + return context->livemodel->report_action_taken(primary_id, secondary_id, status); +} + API int LiveModelReportOutcomeF( livemodel_context_t* context, const char* event_id, float outcome, reinforcement_learning::api_status* status) { diff --git a/bindings/cs/rl.net.native/rl.net.live_model.h b/bindings/cs/rl.net.native/rl.net.live_model.h index 51ad4ff03..ea4118a86 100644 --- a/bindings/cs/rl.net.native/rl.net.live_model.h +++ b/bindings/cs/rl.net.native/rl.net.live_model.h @@ -77,9 +77,18 @@ extern "C" const char* event_id, const char* context_json, int context_json_size, unsigned int flags, reinforcement_learning::multi_slot_response_detailed* resp, const int* baseline_actions, size_t baseline_actions_size, reinforcement_learning::api_status* status = nullptr); + API int LiveModelRequestEpisodicDecisionWithFlags(livemodel_context_t* context, const char* event_id, + const char* previous_id, const char* context_json, unsigned int flags, + reinforcement_learning::ranking_response& resp, reinforcement_learning::episode_state& episode, + reinforcement_learning::api_status* status); + API int LiveModelRequestEpisodicDecision(livemodel_context_t* context, const char* event_id, const char* previous_id, + const char* context_json, reinforcement_learning::ranking_response& resp, + reinforcement_learning::episode_state& episode, reinforcement_learning::api_status* status); API int LiveModelReportActionTaken( livemodel_context_t* livemodel, const char* event_id, reinforcement_learning::api_status* status = nullptr); + API int LiveModelReportActionMultiIdTaken(livemodel_context_t* livemodel, const char* primary_id, + const char* secondary_id, reinforcement_learning::api_status* status = nullptr); API int LiveModelReportOutcomeF(livemodel_context_t* livemodel, const char* event_id, float outcome, reinforcement_learning::api_status* status = nullptr); diff --git a/bindings/cs/rl.net.native/rl.net.native.vcxproj b/bindings/cs/rl.net.native/rl.net.native.vcxproj index 2049b192a..1a11c3f39 100644 --- a/bindings/cs/rl.net.native/rl.net.native.vcxproj +++ b/bindings/cs/rl.net.native/rl.net.native.vcxproj @@ -97,6 +97,7 @@ + @@ -114,6 +115,7 @@ + diff --git a/bindings/cs/rl.net/EpisodeState.cs b/bindings/cs/rl.net/EpisodeState.cs new file mode 100644 index 000000000..edf4caa6e --- /dev/null +++ b/bindings/cs/rl.net/EpisodeState.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.InteropServices; + +using Rl.Net.Native; + +namespace Rl.Net +{ + namespace Native + { + internal partial class NativeMethods + { + [DllImport("rlnetnative")] + public static extern IntPtr CreateEpisodeState(IntPtr episodeId); + + [DllImport("rlnetnative")] + public static extern void DeleteEpisodeState(IntPtr episodeState); + + [DllImport("rlnetnative")] + public static extern IntPtr GetEpisodeId(IntPtr episodeState); + + [DllImport("rlnetnative")] + public static extern int UpdateEpisodeHistory(IntPtr episodeState, IntPtr eventId, IntPtr previousEventId, IntPtr context, IntPtr rankingResponse, IntPtr apiStatus); + } + } + + public sealed class EpisodeState : NativeObject + { + public EpisodeState(string episodeId) : base(BindConstructorArguments(episodeId), new Delete(NativeMethods.DeleteEpisodeState)) + { + } + + internal EpisodeState(IntPtr sharedEpisodeStateHandle) : base(sharedEpisodeStateHandle, ownsHandle: false) + { + } + + private static New BindConstructorArguments(string episodeId) + { + return new New(() => + { + unsafe + { + fixed (byte* episodeIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(episodeId)) + { + IntPtr contextJsonUtf8Ptr = new IntPtr(episodeIdUtf8Bytes); + IntPtr result = NativeMethods.CreateEpisodeState(contextJsonUtf8Ptr); + GC.KeepAlive(episodeId); + return result; + } + } + }); + } + + public int Update(string eventId, string previousEventId, string context, RankingResponse resp, ApiStatus status = null) + { + if (string.IsNullOrEmpty(eventId)) + { + throw new ArgumentException("EventId cannot be null or empty", "eventId"); + } + + unsafe + { + fixed (byte* contextJsonUtf8Bytes = NativeMethods.StringEncoding.GetBytes(context)) + fixed (byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) + { + if (previousEventId == null) + { + return NativeMethods.UpdateEpisodeHistory(this.DangerousGetHandle(), new IntPtr(eventIdUtf8Bytes), IntPtr.Zero, new IntPtr(contextJsonUtf8Bytes), resp.DangerousGetHandle(), + status == null ? IntPtr.Zero : status.DangerousGetHandle()); + } + else + { + fixed (byte* previousEventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(previousEventId)) + { + return NativeMethods.UpdateEpisodeHistory(this.DangerousGetHandle(), new IntPtr(eventIdUtf8Bytes), new IntPtr(previousEventIdUtf8Bytes), new IntPtr(contextJsonUtf8Bytes), resp.DangerousGetHandle(), + status == null ? IntPtr.Zero : status.DangerousGetHandle()); + } + } + } + } + } + + public string EpisodeId + { + get + { + IntPtr episodeIdUtf8Ptr = NativeMethods.GetEpisodeId(this.DangerousGetHandle()); + GC.KeepAlive(this); + return NativeMethods.StringMarshallingFunc(episodeIdUtf8Ptr); + } + } + } +} \ No newline at end of file diff --git a/bindings/cs/rl.net/LiveModel.cs b/bindings/cs/rl.net/LiveModel.cs index 53d695952..af931b10d 100644 --- a/bindings/cs/rl.net/LiveModel.cs +++ b/bindings/cs/rl.net/LiveModel.cs @@ -170,7 +170,7 @@ public static int LiveModelRequestMultiSlotDecisionDetailed(IntPtr liveModel, In } [DllImport("rlnetnative", EntryPoint = "LiveModelRequestMultiSlotDecisionDetailedWithFlags")] - private static extern int LiveModelRequestMultiSlotDecisionDetailedWithFlagsNative(IntPtr liveModel, IntPtr eventId, IntPtr contextJson, int contextJsonSize, uint flags, IntPtr multiSlotResponseDetailed, IntPtr apiStatus); + private static extern int LiveModelRequestMultiSlotDecisionDetailedWithFlagsNative(IntPtr liveModel, IntPtr eventId, IntPtr contextJson, int contextJsonSize, uint flags, IntPtr multiSlotResponseDetailed, IntPtr apiStatus); internal static Func LiveModelRequestMultiSlotDecisionDetailedWithFlagsOverride { get; set; } @@ -199,6 +199,21 @@ public static int LiveModelRequestMultiSlotDecisionDetailedWithBaselineAndFlags( return LiveModelRequestMultiSlotDecisionDetailedWithBaselineAndFlagsNative(liveModel, eventId, contextJson, contextJsonSize, flags, multiSlotResponseDetailed, baselineActions, baselineActionsSize, apiStatus); } + [DllImport("rlnetnative", EntryPoint = "LiveModelRequestEpisodicDecisionWithFlags")] + private static extern int LiveModelRequestEpisodicDecisionWithFlagsNative(IntPtr liveModel, IntPtr eventId, IntPtr previousEventId, IntPtr contextJson, uint flags, IntPtr rankingResponse, IntPtr episodes, IntPtr apiStatus); + + internal static Func LiveModelRequestEpisodicDecisionWithFlagsOverride { get; set; } + + public static int LiveModelRequestEpisodicDecisionWithFlags(IntPtr liveModel, IntPtr eventId, IntPtr previousEventId, IntPtr contextJson, uint flags, IntPtr rankingResponse, IntPtr episodes, IntPtr apiStatus) + { + if (LiveModelRequestEpisodicDecisionWithFlagsOverride != null) + { + return LiveModelRequestEpisodicDecisionWithFlagsOverride(liveModel, eventId, previousEventId, contextJson, flags, rankingResponse, episodes, apiStatus); + } + + return LiveModelRequestEpisodicDecisionWithFlagsNative(liveModel, eventId, previousEventId, contextJson, flags, rankingResponse, episodes, apiStatus); + } + [DllImport("rlnetnative", EntryPoint = "LiveModelReportActionTaken")] private static extern int LiveModelReportActionTakenNative(IntPtr liveModel, IntPtr eventId, IntPtr apiStatus); @@ -214,6 +229,21 @@ public static int LiveModelReportActionTaken(IntPtr liveModel, IntPtr eventId, I return LiveModelReportActionTakenNative(liveModel, eventId, apiStatus); } + [DllImport("rlnetnative", EntryPoint = "LiveModelReportActionMultiIdTaken")] + private static extern int LiveModelReportActionTakenMultiIdNative(IntPtr liveModel, IntPtr primaryId, IntPtr secondaryId, IntPtr apiStatus); + + internal static Func LiveModelReportActionTakenMultiIdOverride { get; set; } + + public static int LiveModelReportActionMultiIdTaken(IntPtr liveModel, IntPtr primaryId, IntPtr secondaryId, IntPtr apiStatus) + { + if (LiveModelReportActionTakenMultiIdOverride != null) + { + return LiveModelReportActionTakenMultiIdOverride(liveModel, primaryId, secondaryId, apiStatus); + } + + return LiveModelReportActionTakenMultiIdNative(liveModel, primaryId, secondaryId, apiStatus); + } + [DllImport("rlnetnative", EntryPoint = "LiveModelReportOutcomeF")] private static extern int LiveModelReportOutcomeFNative(IntPtr liveModel, IntPtr eventId, float outcome, IntPtr apiStatus); @@ -343,7 +373,7 @@ private static New BindConstructorArguments(Configuration config, Fac } public LiveModel(Configuration config) : this(config, null) - {} + { } public LiveModel(Configuration config, FactoryContext factoryContext) : base(BindConstructorArguments(config, factoryContext), new Delete(NativeMethods.DeleteLiveModel)) { @@ -491,7 +521,7 @@ unsafe private static int LiveModelRequestMultiSlotDecision(IntPtr liveModel, st return NativeMethods.LiveModelRequestMultiSlotDecision(liveModel, IntPtr.Zero, (IntPtr)contextJsonUtf8Bytes, contextJsonSize, multiSlotResponse, apiStatus); } - fixed(byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) + fixed (byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) { return NativeMethods.LiveModelRequestMultiSlotDecision(liveModel, (IntPtr)eventIdUtf8Bytes, (IntPtr)contextJsonUtf8Bytes, contextJsonSize, multiSlotResponse, apiStatus); } @@ -510,7 +540,7 @@ unsafe private static int LiveModelRequestMultiSlotDecisionWithFlags(IntPtr live return NativeMethods.LiveModelRequestMultiSlotDecisionWithFlags(liveModel, IntPtr.Zero, (IntPtr)contextJsonUtf8Bytes, contextJsonSize, flags, multiSlotResponse, apiStatus); } - fixed(byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) + fixed (byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) { return NativeMethods.LiveModelRequestMultiSlotDecisionWithFlags(liveModel, (IntPtr)eventIdUtf8Bytes, (IntPtr)contextJsonUtf8Bytes, contextJsonSize, flags, multiSlotResponse, apiStatus); } @@ -580,7 +610,7 @@ unsafe private static int LiveModelRequestMultiSlotDecisionDetailedWithBaselineA CheckJsonString(contextJson); fixed (byte* contextJsonUtf8Bytes = NativeMethods.StringEncoding.GetBytes(contextJson)) - fixed(int* baselineActionsFixed = baselineActions) + fixed (int* baselineActionsFixed = baselineActions) { int contextJsonSize = NativeMethods.StringEncoding.GetByteCount(contextJson); if (eventId == null) @@ -595,6 +625,31 @@ unsafe private static int LiveModelRequestMultiSlotDecisionDetailedWithBaselineA } } + unsafe private static int LiveModelRequestEpisodicDecisionWithFlags(IntPtr liveModel, string eventId, string previousEventId, string contextJson, uint flags, IntPtr rankingResponse, IntPtr episodeState, IntPtr apiStatus) + { + CheckJsonString(contextJson); + if (string.IsNullOrEmpty(eventId)) + { + throw new ArgumentException("eventId cannot be null or empty", "eventId"); + } + + fixed (byte* contextJsonUtf8Bytes = NativeMethods.StringEncoding.GetBytes(contextJson)) + fixed (byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(eventId)) + { + if (previousEventId == null) + { + return NativeMethods.LiveModelRequestEpisodicDecisionWithFlags(liveModel, new IntPtr(eventIdUtf8Bytes), IntPtr.Zero, new IntPtr(contextJsonUtf8Bytes), flags, rankingResponse, episodeState, apiStatus); + } + else + { + fixed (byte* previousEventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(previousEventId)) + { + return NativeMethods.LiveModelRequestEpisodicDecisionWithFlags(liveModel, new IntPtr(eventIdUtf8Bytes), new IntPtr(previousEventIdUtf8Bytes), new IntPtr(contextJsonUtf8Bytes), flags, rankingResponse, episodeState, apiStatus); + } + } + } + } + unsafe private static int LiveModelReportActionTaken(IntPtr liveModel, string eventId, IntPtr apiStatus) { if (eventId == null) @@ -608,6 +663,25 @@ unsafe private static int LiveModelReportActionTaken(IntPtr liveModel, string ev } } + unsafe private static int LiveModelReportActionMultiIdTaken(IntPtr liveModel, string primaryId, string secondaryId, IntPtr apiStatus) + { + if (primaryId == null) + { + throw new ArgumentNullException("primaryId"); + } + + if (secondaryId == null) + { + throw new ArgumentNullException("secondaryId"); + } + + fixed (byte* episodeIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(primaryId)) + fixed (byte* eventIdUtf8Bytes = NativeMethods.StringEncoding.GetBytes(secondaryId)) + { + return NativeMethods.LiveModelReportActionMultiIdTaken(liveModel, new IntPtr(episodeIdUtf8Bytes), new IntPtr(eventIdUtf8Bytes), apiStatus); + } + } + unsafe private static int LiveModelReportOutcomeF(IntPtr liveModel, string eventId, float outcome, IntPtr apiStatus) { if (eventId == null) @@ -754,10 +828,10 @@ public bool TryInit(ApiStatus apiStatus = null) public void Init() { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryInit(apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryInit(apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryChooseRank(string eventId, string contextJson, out RankingResponse response, ApiStatus apiStatus = null) @@ -779,10 +853,10 @@ public RankingResponse ChooseRank(string eventId, string contextJson) RankingResponse result = new RankingResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryChooseRank(eventId, contextJson, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryChooseRank(eventId, contextJson, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -806,10 +880,10 @@ public RankingResponse ChooseRank(string eventId, string contextJson, ActionFlag RankingResponse result = new RankingResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryChooseRank(eventId, contextJson, flags, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryChooseRank(eventId, contextJson, flags, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -833,10 +907,10 @@ public ContinuousActionResponse RequestContinuousAction(string eventId, string c ContinuousActionResponse result = new ContinuousActionResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestContinuousAction(eventId, contextJson, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestContinuousAction(eventId, contextJson, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -860,10 +934,10 @@ public ContinuousActionResponse RequestContinuousAction(string eventId, string c ContinuousActionResponse result = new ContinuousActionResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestContinuousAction(eventId, contextJson, flags, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestContinuousAction(eventId, contextJson, flags, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -887,10 +961,10 @@ public DecisionResponse RequestDecision(string contextJson) DecisionResponse result = new DecisionResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestDecision(contextJson, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestDecision(contextJson, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -914,10 +988,10 @@ public DecisionResponse RequestDecision(string contextJson, ActionFlags flags) DecisionResponse result = new DecisionResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestDecision(contextJson, flags, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestDecision(contextJson, flags, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -941,10 +1015,10 @@ public MultiSlotResponse RequestMultiSlotDecision(string eventId, string context MultiSlotResponse result = new MultiSlotResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestMultiSlotDecision(eventId, contextJson, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestMultiSlotDecision(eventId, contextJson, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -968,10 +1042,10 @@ public MultiSlotResponse RequestMultiSlotDecision(string eventId, string context MultiSlotResponse result = new MultiSlotResponse(); using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRequestMultiSlotDecision(eventId, contextJson, flags, result, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRequestMultiSlotDecision(eventId, contextJson, flags, result, apiStatus)) + { + throw new RLException(apiStatus); + } return result; } @@ -1083,6 +1157,27 @@ public MultiSlotResponseDetailed RequestMultiSlotDecisionDetailed(string eventId return result; } + public bool TryRequestEpisodicDecision(string eventId, string previousEventId, string contextJson, ActionFlags flags, EpisodeState states, RankingResponse resp, ApiStatus apiStatus) + { + int result = LiveModelRequestEpisodicDecisionWithFlags(this.DangerousGetHandle(), eventId, previousEventId, contextJson, (uint)flags, resp.DangerousGetHandle(), states.DangerousGetHandle(), apiStatus.DangerousGetHandle()); + Console.WriteLine("TryRequestEpisodicDecision result " + result); + GC.KeepAlive(this); + return result == NativeMethods.SuccessStatus; + } + + public RankingResponse RequestEpisodicDecision(string eventId, string previousEventId, string contextJson, ActionFlags flags, EpisodeState states) + { + RankingResponse resp = new RankingResponse(); + + using (ApiStatus apiStatus = new ApiStatus()) + if (!this.TryRequestEpisodicDecision(eventId, previousEventId, contextJson, flags, states, resp, apiStatus)) + { + Console.WriteLine("api error" + apiStatus.ErrorMessage + apiStatus.ErrorCode); + throw new RLException(apiStatus); + } + return resp; + } + [Obsolete("Use TryQueueActionTakenEvent instead.")] public bool TryReportActionTaken(string eventId, ApiStatus apiStatus = null) => this.TryQueueActionTakenEvent(eventId, apiStatus); @@ -1096,6 +1191,15 @@ public bool TryQueueActionTakenEvent(string eventId, ApiStatus apiStatus = null) return result == NativeMethods.SuccessStatus; } + public bool TryQueueActionTakenEvent(string primaryId, string secondaryId, ApiStatus apiStatus = null) + { + int result = LiveModelReportActionMultiIdTaken(this.DangerousGetHandle(), primaryId, secondaryId, apiStatus.ToNativeHandleOrNullptrDangerous()); + + GC.KeepAlive(apiStatus); + GC.KeepAlive(this); + return result == NativeMethods.SuccessStatus; + } + [Obsolete("Use QueueActionTakenEvent instead.")] public void ReportActionTaken(string eventId) => this.QueueActionTakenEvent(eventId); @@ -1103,10 +1207,19 @@ public void ReportActionTaken(string eventId) public void QueueActionTakenEvent(string eventId) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueActionTakenEvent(eventId, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueActionTakenEvent(eventId, apiStatus)) + { + throw new RLException(apiStatus); + } + } + + public void QueueActionTakenEvent(string primaryId, string secondaryId) + { + using (ApiStatus apiStatus = new ApiStatus()) + if (!this.TryQueueActionTakenEvent(primaryId, secondaryId, apiStatus)) + { + throw new RLException(apiStatus); + } } [Obsolete("Use TryQueueOutcomeEvent instead.")] @@ -1129,10 +1242,10 @@ public void ReportOutcome(string eventId, float outcome) public void QueueOutcomeEvent(string eventId, float outcome) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, outcome, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(eventId, outcome, apiStatus)) + { + throw new RLException(apiStatus); + } } [Obsolete("Use TryQueueOutcomeEvent instead.")] @@ -1155,10 +1268,10 @@ public void ReportOutcome(string eventId, string outcomeJson) public void QueueOutcomeEvent(string eventId, string outcomeJson) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, outcomeJson, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(eventId, outcomeJson, apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryQueueOutcomeEvent(string eventId, uint slotIndex, float outcome, ApiStatus apiStatus = null) @@ -1173,10 +1286,10 @@ public bool TryQueueOutcomeEvent(string eventId, uint slotIndex, float outcome, public void QueueOutcomeEvent(string eventId, uint slotIndex, float outcome) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, slotIndex, outcome, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(eventId, slotIndex, outcome, apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryQueueOutcomeEvent(string eventId, uint slotIndex, string outcomeJson, ApiStatus apiStatus = null) @@ -1191,10 +1304,10 @@ public bool TryQueueOutcomeEvent(string eventId, uint slotIndex, string outcomeJ public void QueueOutcomeEvent(string eventId, uint slotIndex, string outcomeJson) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, slotIndex, outcomeJson, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(eventId, slotIndex, outcomeJson, apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryQueueOutcomeEvent(string eventId, string slotId, float outcome, ApiStatus apiStatus = null) @@ -1206,13 +1319,13 @@ public bool TryQueueOutcomeEvent(string eventId, string slotId, float outcome, A return result == NativeMethods.SuccessStatus; } - public void QueueOutcomeEvent(string eventId, string slotId, float outcome) + public void QueueOutcomeEvent(string secondaryId, string slotId, float outcome) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, slotId, outcome, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(secondaryId, slotId, outcome, apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryQueueOutcomeEvent(string eventId, string slotId, string outcomeJson, ApiStatus apiStatus = null) @@ -1227,19 +1340,19 @@ public bool TryQueueOutcomeEvent(string eventId, string slotId, string outcomeJs public void QueueOutcomeEvent(string eventId, string slotId, string outcomeJson) { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryQueueOutcomeEvent(eventId, slotId, outcomeJson, apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryQueueOutcomeEvent(eventId, slotId, outcomeJson, apiStatus)) + { + throw new RLException(apiStatus); + } } public void RefreshModel() { using (ApiStatus apiStatus = new ApiStatus()) - if (!this.TryRefreshModel(apiStatus)) - { - throw new RLException(apiStatus); - } + if (!this.TryRefreshModel(apiStatus)) + { + throw new RLException(apiStatus); + } } public bool TryRefreshModel(ApiStatus apiStatus = null) diff --git a/examples/override_interface/override_interface.cc b/examples/override_interface/override_interface.cc index 1a3331f8f..41b9f4164 100644 --- a/examples/override_interface/override_interface.cc +++ b/examples/override_interface/override_interface.cc @@ -77,10 +77,10 @@ int main() std::mutex cout_mutex; // Define a create function to be used in the factory. - auto const create_ostream_sender_fn = [&](r::i_sender** retval, const u::configuration&, + auto const create_ostream_sender_fn = [&](std::unique_ptr& retval, const u::configuration&, r::error_callback_fn* error_callback, r::i_trace* trace, r::api_status*) { - *retval = new ostream_sender(std::cout, cout_mutex); + retval.reset(new ostream_sender(std::cout, cout_mutex)); return err::success; }; diff --git a/ext_libs/string-view-lite/nonstd/string_view.h b/ext_libs/string-view-lite/nonstd/string_view.hpp similarity index 100% rename from ext_libs/string-view-lite/nonstd/string_view.h rename to ext_libs/string-view-lite/nonstd/string_view.hpp diff --git a/ext_libs/vowpal_wabbit b/ext_libs/vowpal_wabbit index 16e9114f4..dcbcd07c7 160000 --- a/ext_libs/vowpal_wabbit +++ b/ext_libs/vowpal_wabbit @@ -1 +1 @@ -Subproject commit 16e9114f41343eed0a5f3f9881b171ce4ea6774a +Subproject commit dcbcd07c7f5c8b7de2b1e19a453242deeef8c368 diff --git a/external_parser/event_processors/joined_event.h b/external_parser/event_processors/joined_event.h index 93c3f1eb8..58e81cf7b 100644 --- a/external_parser/event_processors/joined_event.h +++ b/external_parser/event_processors/joined_event.h @@ -2,27 +2,26 @@ #include "reward.h" // VW headers -// vw.h has to come before json_utils.h // clang-format off #include "vw/core/ccb_label.h" #include "vw/core/vw.h" -#include "vw/core/json_utils.h" #include "vw/core/example.h" #include "vw/io/logger.h" // clang-format on +#include "vw/json_parser/parse_example_json.h" namespace joined_event { struct MultiSlotInteraction { - std::vector interaction_data; + std::vector interaction_data; std::vector baseline_actions; bool skip_learn; float probability_of_drop{0.f}; }; -inline void calculate_multislot_interaction_metrics( - dsjson_metrics* metrics, MultiSlotInteraction multi_slot_interaction, float first_slot_original_reward_neg) +inline void calculate_multislot_interaction_metrics(VW::details::dsjson_metrics* metrics, + MultiSlotInteraction multi_slot_interaction, float first_slot_original_reward_neg) { if (metrics != nullptr) { @@ -56,13 +55,13 @@ struct typed_joined_event // we currently need it for ccb calculation std::vector& outcome_events, VW::io::logger& logger) = 0; - virtual void calculate_metrics(dsjson_metrics*) {} + virtual void calculate_metrics(VW::details::dsjson_metrics*) {} virtual float get_sum_original_reward() const = 0; }; struct cb_joined_event : public typed_joined_event { - VW::details::decision_service_interaction interaction_data; + VW::parsers::json::decision_service_interaction interaction_data; // Default Baseline Action for CB is 1 (rl client recommended actions are 1 // indexed in the CB case) static const unsigned CB_BASELINE_ACTION = 1; @@ -139,7 +138,7 @@ struct cb_joined_event : public typed_joined_event else { reward = original_reward; } } - void calculate_metrics(dsjson_metrics* metrics) override + void calculate_metrics(VW::details::dsjson_metrics* metrics) override { if (metrics != nullptr) { @@ -289,7 +288,7 @@ struct ccb_joined_event : public typed_joined_event else { rewards.assign(original_rewards.begin(), original_rewards.end()); } } - void calculate_metrics(dsjson_metrics* metrics) override + void calculate_metrics(VW::details::dsjson_metrics* metrics) override { if (metrics != nullptr) { @@ -376,7 +375,7 @@ struct slates_joined_event : public typed_joined_event else { reward = original_reward; } } - void calculate_metrics(dsjson_metrics* metrics) override + void calculate_metrics(VW::details::dsjson_metrics* metrics) override { if (metrics != nullptr) { @@ -462,7 +461,7 @@ struct ca_joined_event : public typed_joined_event else { reward = original_reward; } } - void calculate_metrics(dsjson_metrics* metrics) override + void calculate_metrics(VW::details::dsjson_metrics* metrics) override { if ((metrics != nullptr) && std::isnan(interaction_data.action)) { metrics->number_of_events_zero_actions++; } } @@ -524,7 +523,7 @@ struct joined_event typed_data->calc_cost(default_reward, reward_function, interaction_metadata, outcome_events, logger); } - void calculate_metrics(dsjson_metrics* metrics) { return typed_data->calculate_metrics(metrics); } + void calculate_metrics(VW::details::dsjson_metrics* metrics) { return typed_data->calculate_metrics(metrics); } float get_sum_original_reward() const { return typed_data->get_sum_original_reward(); } }; diff --git a/external_parser/event_processors/typed_events.h b/external_parser/event_processors/typed_events.h index eb2a1e491..f7a9b6a08 100644 --- a/external_parser/event_processors/typed_events.h +++ b/external_parser/event_processors/typed_events.h @@ -6,7 +6,6 @@ #include "generated/v2/MultiSlotEvent_generated.h" #include "joined_event.h" #include "loop.h" -#include "vw/core/json_utils.h" #include "zstd.h" namespace typed_event @@ -58,7 +57,7 @@ struct event_processorid() == nullptr ? metadata.id()->str() : slot_event->id()->str(); if (is_ccb && slot_event->id() != nullptr) diff --git a/external_parser/joiners/i_joiner.h b/external_parser/joiners/i_joiner.h index a011c3a4e..dc4910a52 100644 --- a/external_parser/joiners/i_joiner.h +++ b/external_parser/joiners/i_joiner.h @@ -19,7 +19,6 @@ // vw.h has to come before json_utils.h // clang-format off #include "vw/core/vw.h" -#include "vw/core/json_utils.h" // clang-format on class i_joiner diff --git a/external_parser/joiners/multistep_example_joiner.h b/external_parser/joiners/multistep_example_joiner.h index 9ee40d161..e109f347e 100644 --- a/external_parser/joiners/multistep_example_joiner.h +++ b/external_parser/joiners/multistep_example_joiner.h @@ -20,7 +20,6 @@ // vw.h has to come before json_utils.h // clang-format off #include "vw/core/vw.h" -#include "vw/core/json_utils.h" // clang-format on enum multistep_reward_funtion_type diff --git a/external_parser/parse_example_external.cc b/external_parser/parse_example_external.cc index 4c0a81e49..7572bd62b 100644 --- a/external_parser/parse_example_external.cc +++ b/external_parser/parse_example_external.cc @@ -115,7 +115,7 @@ std::unique_ptr parser::get_external_parser(VW::workspace* all, const pa if (all->options->was_supplied("extra_metrics")) { - all->example_parser->metrics = VW::make_unique(); + all->example_parser->metrics = VW::make_unique(); } return VW::make_unique(std::move(joiner), all->logger); @@ -175,7 +175,7 @@ std::unique_ptr initialize_with_binary_parser(std::unique_ptrmetric_output_hooks.push_back( + all->global_metrics.register_metrics_callback( [external_parser_ptr](VW::metric_sink& metric_list) { external_parser_ptr->persist_metrics(metric_list); }); all->custom_parser = std::unique_ptr(external_parser.release()); all->example_parser->reader = VW::external::parse_examples; diff --git a/include/configuration.h b/include/configuration.h index 633a8a084..b51e1315c 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -30,16 +30,16 @@ namespace utility class configuration { public: - configuration(); - ~configuration(); + configuration() = default; + ~configuration() = default; //! Copy constructor - configuration(const configuration&); + configuration(const configuration&) = default; //! Assignment operator - configuration& operator=(const configuration&); + configuration& operator=(const configuration&) = default; //! Move constructor - configuration& operator=(configuration&&) noexcept; + configuration& operator=(configuration&&) = default; //! Move assignment operator - configuration(configuration&&) noexcept; + configuration(configuration&&) = default; //! Sets the value for a given name. It overrides any existing values for that name void set(const char* name, const char* value); @@ -60,7 +60,7 @@ class configuration private: using map_type = std::unordered_map; //! Collection type that holds the (name,value) pairs - map_type* _pmap; //! Collection that holds the (name,value) pairs + map_type _pmap; //! Collection that holds the (name,value) pairs }; } // namespace utility } // namespace reinforcement_learning diff --git a/include/constants.h b/include/constants.h index 7084061a8..1b4962230 100644 --- a/include/constants.h +++ b/include/constants.h @@ -38,6 +38,10 @@ const char* const EPISODE_EH_TASKS_LIMIT = "episode.eventhub.tasks_limit"; const char* const EPISODE_EH_MAX_HTTP_RETRIES = "episode.eventhub.max_http_retries"; const char* const EPISODE_EH_MAX_HTTP_RETRY_DURATION_MS = "episode.eventhub.max_http_retry_duration_ms"; const char* const EPISODE_SENDER_IMPLEMENTATION = "episode.sender.implementation"; +const char* const EPISODE_HTTP_API_HOST = "episode.http.api.host"; +const char* const EPISODE_APIM_TASKS_LIMIT = "episode.apim.tasks_limit"; +const char* const EPISODE_APIM_MAX_HTTP_RETRIES = "episode.apim.max_http_retries"; +const char* const EPISODE_APIM_MAX_HTTP_RETRY_DURATION_MS = "episode.apim.max_http_retry_duration_ms"; // Interaction const char* const INTERACTION_EH_HOST = "interaction.eventhub.host"; @@ -130,6 +134,7 @@ const char* const INTERACTION_EH_SENDER = "INTERACTION_EH_SENDER"; const char* const EPISODE_FILE_SENDER = "EPISODE_FILE_SENDER"; const char* const OBSERVATION_FILE_SENDER = "OBSERVATION_FILE_SENDER"; const char* const INTERACTION_FILE_SENDER = "INTERACTION_FILE_SENDER"; +const char* const EPISODE_HTTP_API_SENDER = "EPISODE_HTTP_API_SENDER"; const char* const OBSERVATION_HTTP_API_SENDER = "OBSERVATION_HTTP_API_SENDER"; const char* const INTERACTION_HTTP_API_SENDER = "INTERACTION_HTTP_API_SENDER"; const char* const NULL_TRACE_LOGGER = "NULL_TRACE_LOGGER"; diff --git a/include/model_mgmt.h b/include/model_mgmt.h index 23f740ff2..4b381dba7 100644 --- a/include/model_mgmt.h +++ b/include/model_mgmt.h @@ -1,9 +1,8 @@ #pragma once #include "multistep.h" -#include - #include +#include #include #include #include @@ -23,7 +22,8 @@ class model_data { public: // Get data - char* data() const; + char* data(); + const char* data() const; size_t data_sz() const; uint32_t refresh_count() const; @@ -34,16 +34,16 @@ class model_data char* alloc(size_t desired); void free(); - model_data(); - ~model_data(); + model_data() = default; + ~model_data() = default; - model_data(model_data const& other); - model_data(model_data&& other) noexcept; - model_data& operator=(model_data other) noexcept; + model_data(const model_data& other) = default; + model_data(model_data&& other) noexcept = default; + model_data& operator=(const model_data& other) noexcept = default; + model_data& operator=(model_data&& other) noexcept = default; private: - char* _data = nullptr; - size_t _data_sz = 0; + std::vector _data; uint32_t _refresh_count = 0; }; diff --git a/include/object_factory.h b/include/object_factory.h index 7de89be2b..5493f5f8e 100644 --- a/include/object_factory.h +++ b/include/object_factory.h @@ -5,6 +5,7 @@ #include "trace_logger.h" #include +#include namespace reinforcement_learning { @@ -13,29 +14,30 @@ namespace utility template struct object_factory { - using create_fn = std::function; + using create_fn = + std::function& retval, Args&&... args, i_trace* trace_logger, api_status* status)>; void register_type(const std::string& name, create_fn fptr) { _creators[name] = fptr; } // There is a compiler bug in MSVC where a parameter pack cannot be followed by a default argument, // so an overload is used to get around this. // Both trace_logger and api_status are default - int create(I** retval, const std::string& name, Args&&... args) + int create(std::unique_ptr& retval, const std::string& name, Args&&... args) { return create(retval, name, std::forward(args)..., nullptr, nullptr); } // Only api_status is default - int create(I** retval, const std::string& name, Args&&... args, i_trace* trace) + int create(std::unique_ptr& retval, const std::string& name, Args&&... args, i_trace* trace) { return create(retval, name, std::forward(args)..., trace, nullptr); } // Only trace_logger is default - int create(I** retval, const std::string& name, Args&&... args, api_status* status) + int create(std::unique_ptr& retval, const std::string& name, Args&&... args, api_status* status) { return create(retval, name, std::forward(args)..., nullptr, status); } - int create(I** retval, const std::string& name, Args&&... args, i_trace* trace, api_status* status) + int create(std::unique_ptr& retval, const std::string& name, Args&&... args, i_trace* trace, api_status* status) { auto it = _creators.find(name); diff --git a/include/rl_string_view.h b/include/rl_string_view.h index 336140776..35143c05f 100644 --- a/include/rl_string_view.h +++ b/include/rl_string_view.h @@ -1,6 +1,6 @@ #pragma once -#include "nonstd/string_view.h" +#include "nonstd/string_view.hpp" namespace reinforcement_learning { diff --git a/nuget/CMakeLists.txt b/nuget/CMakeLists.txt index dc16feade..119969d3d 100644 --- a/nuget/CMakeLists.txt +++ b/nuget/CMakeLists.txt @@ -9,7 +9,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rlclientlib.nuspec DESTINATION ./) # Generate the .targets file from template configure_file(rlclientlib.targets.in rlclientlib.targets @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rlclientlib.targets DESTINATION ./ RENAME ${RL_NUGET_PACKAGE_NAME}-v${MSVC_TOOLSET_VERSION}-${NATIVE_NUGET_PLATFORM_TAG}.targets) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rlclientlib.targets DESTINATION ./ RENAME ${RL_NUGET_PACKAGE_NAME}-v${MSVC_TOOLSET_VERSION}-${CMAKE_BUILD_TYPE}-${NATIVE_NUGET_PLATFORM_TAG}.targets) # Build package install(SCRIPT CreateNugetPackage.cmake) \ No newline at end of file diff --git a/nuget/rlclientlib.nuspec.in b/nuget/rlclientlib.nuspec.in index 2ab527bd4..7dd646285 100644 --- a/nuget/rlclientlib.nuspec.in +++ b/nuget/rlclientlib.nuspec.in @@ -1,7 +1,7 @@ - @RL_NUGET_PACKAGE_NAME@-v@MSVC_TOOLSET_VERSION@-@NATIVE_NUGET_PLATFORM_TAG@ + @RL_NUGET_PACKAGE_NAME@-v@MSVC_TOOLSET_VERSION@-@CMAKE_BUILD_TYPE@-@NATIVE_NUGET_PLATFORM_TAG@ @RL_NUGET_PACKAGE_VERSION@ Reinforcement Learning Client Library - Static Build John Langford et al @@ -11,9 +11,9 @@ native - + - + \ No newline at end of file diff --git a/nuget/rlclientlib.targets.in b/nuget/rlclientlib.targets.in index 09603f0f7..a550fd04c 100644 --- a/nuget/rlclientlib.targets.in +++ b/nuget/rlclientlib.targets.in @@ -7,18 +7,18 @@ - rlclientlibd.lib;cpprestd.lib;libssld.lib;libcryptod.lib;zstd_staticd.lib;vw_configd.lib;vw_allreduced.lib;fmtd.lib;spdlogd.lib;vw_iod.lib;vw_cored.lib;zlibstaticd.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) + rlclientlibd.lib;cpprestd.lib;libssld.lib;libcryptod.lib;zstd_staticd.lib;vw_configd.lib;vw_cache_parserd.lib;vw_text_parserd.lib;vw_json_parserd.lib;vw_allreduced.lib;fmtd.lib;spdlogd.lib;vw_iod.lib;vw_cored.lib;zlibstaticd.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) - rlclientlibd.lib;cpprestd.lib;libssld.lib;libcryptod.lib;zstd_staticd.lib;vw_configd.lib;vw_allreduced.lib;fmtd.lib;spdlogd.lib;vw_iod.lib;vw_cored.lib;zlibstaticd.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) + rlclientlibd.lib;cpprestd.lib;libssld.lib;libcryptod.lib;zstd_staticd.lib;vw_configd.lib;vw_cache_parserd.lib;vw_text_parserd.lib;vw_json_parserd.lib;vw_allreduced.lib;fmtd.lib;spdlogd.lib;vw_iod.lib;vw_cored.lib;zlibstaticd.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) - rlclientlib.lib;cpprest.lib;libssl.lib;libcrypto.lib;zstd_static.lib;vw_config.lib;vw_allreduce.lib;fmt.lib;spdlog.lib;vw_io.lib;vw_core.lib;zlibstatic.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) + rlclientlib.lib;cpprest.lib;libssl.lib;libcrypto.lib;zstd_static.lib;vw_config.lib;vw_cache_parser.lib;vw_text_parser.lib;vw_json_parser.lib;vw_allreduce.lib;fmt.lib;spdlog.lib;vw_io.lib;vw_core.lib;zlibstatic.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) - rlclientlib.lib;cpprest.lib;libssl.lib;libcrypto.lib;zstd_static.lib;vw_config.lib;vw_allreduce.lib;fmt.lib;spdlog.lib;vw_io.lib;vw_core.lib;zlibstatic.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) + rlclientlib.lib;cpprest.lib;libssl.lib;libcrypto.lib;zstd_static.lib;vw_config.lib;vw_cache_parser.lib;vw_text_parser.lib;vw_json_parser.lib;vw_allreduce.lib;fmt.lib;spdlog.lib;vw_io.lib;vw_core.lib;zlibstatic.lib;ws2_32.lib;winhttp.lib;bcrypt.lib;crypt32.lib;%(AdditionalDependencies) diff --git a/nuget/test/test-v142.vcxproj b/nuget/test/test-v142.vcxproj deleted file mode 100644 index e4b5f5367..000000000 --- a/nuget/test/test-v142.vcxproj +++ /dev/null @@ -1,95 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {7f61ad02-50eb-4471-8e2a-77d2caa471fa} - ConsoleApplication1 - 10.0.16299.0 - - - - Application - true - v142 - Unicode - bin\x64\Debug\ - - - Application - false - v142 - true - Unicode - bin\x64\Release\ - - - - - - - - - - - - - - - true - - - false - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/nuget/test/test-v141.vcxproj b/nuget/test/test_rl_nuget.vcxproj similarity index 73% rename from nuget/test/test-v141.vcxproj rename to nuget/test/test_rl_nuget.vcxproj index 70e310a22..3def84d34 100644 --- a/nuget/test/test-v141.vcxproj +++ b/nuget/test/test_rl_nuget.vcxproj @@ -18,14 +18,14 @@ 10.0.16299.0 - + Application true v141 Unicode bin\x64\Debug\ - + Application false v141 @@ -33,6 +33,20 @@ Unicode bin\x64\Release\ + + Application + true + v142 + Unicode + bin\x64\Debug\ + + + Application + false + v142 + Unicode + bin\x64\Release\ + @@ -83,13 +97,13 @@ - + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/rlclientlib/azure_factories.cc b/rlclientlib/azure_factories.cc index ba78d8ff2..687fcd53b 100644 --- a/rlclientlib/azure_factories.cc +++ b/rlclientlib/azure_factories.cc @@ -14,22 +14,24 @@ namespace reinforcement_learning namespace m = model_management; namespace u = utility; -int restapi_data_transport_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status); -int authenticated_restapi_data_transport_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status); -int episode_sender_create(i_sender** retval, const u::configuration& /*cfg*/, error_callback_fn* /*error_cb*/, +int restapi_data_transport_create(std::unique_ptr& retval, const u::configuration& config, i_trace* trace_logger, api_status* status); -int observation_sender_create(i_sender** retval, const u::configuration& /*cfg*/, error_callback_fn* /*error_cb*/, - i_trace* trace_logger, api_status* status); -int interaction_sender_create(i_sender** retval, const u::configuration& /*cfg*/, error_callback_fn* /*error_cb*/, - i_trace* trace_logger, api_status* status); -int decision_sender_create( - i_sender** retval, const u::configuration&, error_callback_fn*, i_trace* trace_logger, api_status* status); -int observation_api_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status); -int interaction_api_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, +int authenticated_restapi_data_transport_create(std::unique_ptr& retval, + const u::configuration& config, i_trace* trace_logger, api_status* status); +int episode_sender_create(std::unique_ptr& retval, const u::configuration& /*cfg*/, + error_callback_fn* /*error_cb*/, i_trace* trace_logger, api_status* status); +int observation_sender_create(std::unique_ptr& retval, const u::configuration& /*cfg*/, + error_callback_fn* /*error_cb*/, i_trace* trace_logger, api_status* status); +int interaction_sender_create(std::unique_ptr& retval, const u::configuration& /*cfg*/, + error_callback_fn* /*error_cb*/, i_trace* trace_logger, api_status* status); +int decision_sender_create(std::unique_ptr& retval, const u::configuration&, error_callback_fn*, i_trace* trace_logger, api_status* status); +int episode_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status); +int observation_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status); +int interaction_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status); void register_azure_factories() { @@ -40,28 +42,29 @@ void register_azure_factories() sender_factory.register_type(value::EPISODE_EH_SENDER, episode_sender_create); sender_factory.register_type(value::OBSERVATION_HTTP_API_SENDER, observation_api_sender_create); sender_factory.register_type(value::INTERACTION_HTTP_API_SENDER, interaction_api_sender_create); + sender_factory.register_type(value::EPISODE_HTTP_API_SENDER, episode_api_sender_create); } -int restapi_data_transport_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int restapi_data_transport_create(std::unique_ptr& retval, const u::configuration& config, + i_trace* trace_logger, api_status* status) { const auto* const uri = config.get(name::MODEL_BLOB_URI, nullptr); if (uri == nullptr) { RETURN_ERROR(trace_logger, status, http_model_uri_not_provided); } i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(uri, config, &client, status)); - *retval = new m::restapi_data_transport(client, trace_logger); + retval.reset(new m::restapi_data_transport(client, trace_logger)); return error_code::success; } -int authenticated_restapi_data_transport_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int authenticated_restapi_data_transport_create(std::unique_ptr& retval, + const u::configuration& config, i_trace* trace_logger, api_status* status) { const auto* model_uri = config.get(name::MODEL_BLOB_URI, nullptr); if (model_uri == nullptr) { RETURN_ERROR(trace_logger, status, http_model_uri_not_provided); } i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(model_uri, config, &client, status)); - *retval = new m::restapi_data_transport( - std::unique_ptr(client), config, m::model_source::HTTP_API, trace_logger); + retval.reset(new m::restapi_data_transport( + std::unique_ptr(client), config, m::model_source::HTTP_API, trace_logger)); return error_code::success; } @@ -72,7 +75,7 @@ std::string build_eh_url(const char* eh_host, const char* eh_name) return url; } -int episode_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, +int episode_sender_create(std::unique_ptr& retval, const u::configuration& cfg, error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { const auto* const eh_host = cfg.get(name::EPISODE_EH_HOST, "localhost:8080"); @@ -80,27 +83,38 @@ int episode_sender_create(i_sender** retval, const u::configuration& cfg, error_ const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = new http_transport_client(client, + retval.reset(new http_transport_client(client, cfg.get_int(name::EPISODE_EH_TASKS_LIMIT, 16), cfg.get_int(name::EPISODE_EH_MAX_HTTP_RETRIES, 4), std::chrono::milliseconds(cfg.get_int(name::EPISODE_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, - error_cb); + error_cb)); return error_code::success; } -int create_apim_http_api_sender(i_sender** retval, const u::configuration& cfg, const char* api_host, int tasks_limit, - int max_http_retries, std::chrono::milliseconds max_http_retry_duration, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status) +int create_apim_http_api_sender(std::unique_ptr& retval, const u::configuration& cfg, const char* api_host, + int tasks_limit, int max_http_retries, std::chrono::milliseconds max_http_retry_duration, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(api_host, cfg, &client, status)); - *retval = new http_transport_client( - client, tasks_limit, max_http_retries, max_http_retry_duration, trace_logger, error_cb); + retval.reset(new http_transport_client( + client, tasks_limit, max_http_retries, max_http_retry_duration, trace_logger, error_cb)); return error_code::success; } +// Creates i_sender object for sending episode data to the apim endpoint. +int episode_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) +{ + const auto* const api_host = cfg.get(name::EPISODE_HTTP_API_HOST, "localhost:8080"); + return create_apim_http_api_sender(retval, cfg, api_host, cfg.get_int(name::EPISODE_APIM_MAX_HTTP_RETRIES, 4), + cfg.get_int(name::EPISODE_APIM_TASKS_LIMIT, 4), + std::chrono::milliseconds(cfg.get_int(name::EPISODE_APIM_MAX_HTTP_RETRY_DURATION_MS, 3600000)), error_cb, + trace_logger, status); +} + // Creates i_sender object for sending observations data to the apim endpoint. -int observation_api_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status) +int observation_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { const auto* const api_host = cfg.get(name::OBSERVATION_HTTP_API_HOST, "localhost:8080"); return create_apim_http_api_sender(retval, cfg, api_host, cfg.get_int(name::OBSERVATION_APIM_TASKS_LIMIT, 16), @@ -110,8 +124,8 @@ int observation_api_sender_create(i_sender** retval, const u::configuration& cfg } // Creates i_sender object for sending interactions data to the apim endpoint. -int interaction_api_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status) +int interaction_api_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { const auto* const api_host = cfg.get(name::INTERACTION_HTTP_API_HOST, "localhost:8080"); return create_apim_http_api_sender(retval, cfg, api_host, cfg.get_int(name::INTERACTION_APIM_TASKS_LIMIT, 16), @@ -121,34 +135,34 @@ int interaction_api_sender_create(i_sender** retval, const u::configuration& cfg } // Creates i_sender object for sending observations data to the event hub. -int observation_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status) +int observation_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { const auto* const eh_host = cfg.get(name::OBSERVATION_EH_HOST, "localhost:8080"); const auto* const eh_name = cfg.get(name::OBSERVATION_EH_NAME, "observation"); const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = new http_transport_client(client, + retval.reset(new http_transport_client(client, cfg.get_int(name::OBSERVATION_EH_TASKS_LIMIT, 16), cfg.get_int(name::OBSERVATION_EH_MAX_HTTP_RETRIES, 4), std::chrono::milliseconds(cfg.get_int(name::OBSERVATION_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, - error_cb); + error_cb)); return error_code::success; } // Creates i_sender object for sending interactions data to the event hub. -int interaction_sender_create(i_sender** retval, const u::configuration& cfg, error_callback_fn* error_cb, - i_trace* trace_logger, api_status* status) +int interaction_sender_create(std::unique_ptr& retval, const u::configuration& cfg, + error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { const auto* const eh_host = cfg.get(name::INTERACTION_EH_HOST, "localhost:8080"); const auto* const eh_name = cfg.get(name::INTERACTION_EH_NAME, "interaction"); const auto eh_url = build_eh_url(eh_host, eh_name); i_http_client* client = nullptr; RETURN_IF_FAIL(create_http_client(eh_url.c_str(), cfg, &client, status)); - *retval = new http_transport_client(client, + retval.reset(new http_transport_client(client, cfg.get_int(name::INTERACTION_EH_TASKS_LIMIT, 16), cfg.get_int(name::INTERACTION_EH_MAX_HTTP_RETRIES, 4), std::chrono::milliseconds(cfg.get_int(name::INTERACTION_EH_MAX_HTTP_RETRY_DURATION_MS, 3600000)), trace_logger, - error_cb); + error_cb)); return error_code::success; } } // namespace reinforcement_learning diff --git a/rlclientlib/dedup.cc b/rlclientlib/dedup.cc index 50587d71e..d80caaf18 100644 --- a/rlclientlib/dedup.cc +++ b/rlclientlib/dedup.cc @@ -122,10 +122,10 @@ int zstd_compressor::decompress(generic_event::payload_buffer_t& buf, api_status return error_code::success; } -dedup_state::dedup_state( - const utility::configuration& c, bool use_compression, bool use_dedup, i_time_provider* time_provider) +dedup_state::dedup_state(const utility::configuration& c, bool use_compression, bool use_dedup, + std::unique_ptr time_provider) : _compressor(c.get_int(name::ZSTD_COMPRESSION_LEVEL, zstd_compressor::ZSTD_DEFAULT_COMPRESSION_LEVEL)) - , _time_provider(time_provider) + , _time_provider(std::move(time_provider)) , _use_compression(use_compression) , _use_dedup(use_dedup) { @@ -265,28 +265,31 @@ struct dedup_collection_serializer class dedup_extensions : public logger::i_logger_extensions { public: - dedup_extensions( - const utility::configuration& c, bool use_compression, bool use_dedup, i_time_provider* time_provider) + dedup_extensions(const utility::configuration& c, bool use_compression, bool use_dedup, + std::unique_ptr time_provider) : logger::i_logger_extensions(c) - , _dedup_state(c, use_compression, use_dedup, time_provider) + , _dedup_state(c, use_compression, use_dedup, std::move(time_provider)) , _use_dedup(use_dedup) , _use_compression(use_compression) { } - logger::i_async_batcher* create_batcher(logger::i_message_sender* sender, utility::watchdog& watchdog, - error_callback_fn* perror_cb, const char* section) override + std::unique_ptr> create_batcher( + std::unique_ptr sender, utility::watchdog& watchdog, error_callback_fn* perror_cb, + const char* section) override { auto config = utility::get_batcher_config(_config, section); if (_use_dedup) { - return new logger::async_batcher( - sender, watchdog, _dedup_state, perror_cb, config); + return std::unique_ptr>( + new logger::async_batcher( + std::move(sender), watchdog, _dedup_state, perror_cb, config)); } - return new logger::async_batcher( - sender, watchdog, _dummy_state, perror_cb, config); + return std::unique_ptr>( + new logger::async_batcher( + std::move(sender), watchdog, _dummy_state, perror_cb, config)); } bool is_object_extraction_enabled() const override { return _use_dedup; } @@ -311,16 +314,28 @@ class dedup_extensions : public logger::i_logger_extensions bool _use_dedup; }; -logger::i_logger_extensions* create_dedup_logger_extension( - const utility::configuration& config, const char* section, i_time_provider* time_provider) +bool should_use_dedup_logger_extension(const utility::configuration& config, const char* section) { - if (config.get_int(name::PROTOCOL_VERSION, 1) != 2) { return nullptr; } + if (config.get_int(name::PROTOCOL_VERSION, 1) != 2) { return false; } + const bool use_compression = config.get_bool(section, name::USE_COMPRESSION, false); const bool use_dedup = config.get_bool(section, name::USE_DEDUP, false); + if (!use_compression && !use_dedup) { return false; } + + return true; +} +std::unique_ptr create_dedup_logger_extension( + const utility::configuration& config, const char* section, std::unique_ptr time_provider) +{ + if (config.get_int(name::PROTOCOL_VERSION, 1) != 2) { return nullptr; } + + const bool use_compression = config.get_bool(section, name::USE_COMPRESSION, false); + const bool use_dedup = config.get_bool(section, name::USE_DEDUP, false); if (!use_compression && !use_dedup) { return nullptr; } - return new dedup_extensions(config, use_compression, use_dedup, time_provider); + return std::unique_ptr( + new dedup_extensions(config, use_compression, use_dedup, std::move(time_provider))); } } // namespace reinforcement_learning diff --git a/rlclientlib/dedup.h b/rlclientlib/dedup.h index 5117ae313..f93803f1b 100644 --- a/rlclientlib/dedup.h +++ b/rlclientlib/dedup.h @@ -4,6 +4,8 @@ namespace reinforcement_learning { -logger::i_logger_extensions* create_dedup_logger_extension( - const utility::configuration& config, const char* section, i_time_provider* time_provider); -} +bool should_use_dedup_logger_extension(const utility::configuration& config, const char* section); + +std::unique_ptr create_dedup_logger_extension( + const utility::configuration& config, const char* section, std::unique_ptr time_provider); +} // namespace reinforcement_learning diff --git a/rlclientlib/dedup_internals.h b/rlclientlib/dedup_internals.h index 223d8754f..693ad168d 100644 --- a/rlclientlib/dedup_internals.h +++ b/rlclientlib/dedup_internals.h @@ -75,7 +75,8 @@ class zstd_compressor class dedup_state { public: - dedup_state(const utility::configuration& c, bool use_compression, bool use_dedup, i_time_provider* time_provider); + dedup_state(const utility::configuration& c, bool use_compression, bool use_dedup, + std::unique_ptr time_provider); string_view get_object(generic_event::object_id_t aid); float get_ewma_value() const; diff --git a/rlclientlib/extensions/onnx/src/onnx_extension.cc b/rlclientlib/extensions/onnx/src/onnx_extension.cc index 7abfbe3e6..878867bd0 100644 --- a/rlclientlib/extensions/onnx/src/onnx_extension.cc +++ b/rlclientlib/extensions/onnx/src/onnx_extension.cc @@ -15,7 +15,8 @@ namespace reinforcement_learning { namespace onnx { -int create_onnx_model(m::i_model** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int create_onnx_model( + std::unique_ptr& retval, const u::configuration& config, i_trace* trace_logger, api_status* status) { const char* app_id = config.get(name::APP_ID, ""); const char* output_name = config.get(name::ONNX_OUTPUT_NAME, nullptr); @@ -27,7 +28,7 @@ int create_onnx_model(m::i_model** retval, const u::configuration& config, i_tra bool use_unstructured_input = config.get_bool(name::ONNX_USE_UNSTRUCTURED_INPUT, false); - *retval = new onnx_model(trace_logger, app_id, output_name, use_unstructured_input); + retval.reset(new onnx_model(trace_logger, app_id, output_name, use_unstructured_input)); return error_code::success; }; diff --git a/rlclientlib/factory_resolver.cc b/rlclientlib/factory_resolver.cc index 5610c0c7e..c51763f03 100644 --- a/rlclientlib/factory_resolver.cc +++ b/rlclientlib/factory_resolver.cc @@ -78,75 +78,73 @@ factory_initializer::~factory_initializer() } template -int model_create(m::i_model** retval, const u::configuration& c, i_trace* trace_logger, api_status* status) +int model_create( + std::unique_ptr& retval, const u::configuration& c, i_trace* trace_logger, api_status* status) { - *retval = new model_t(trace_logger, c); + retval.reset(new model_t(trace_logger, c)); return error_code::success; } -int null_tracer_create(i_trace** retval, const u::configuration& /*cfg*/, i_trace* trace_logger, api_status* status); -int console_tracer_create(i_trace** retval, const u::configuration& /*cfg*/, i_trace* trace_logger, api_status* status); +int null_tracer_create( + std::unique_ptr& retval, const u::configuration& /*cfg*/, i_trace* trace_logger, api_status* status); +int console_tracer_create( + std::unique_ptr& retval, const u::configuration& /*cfg*/, i_trace* trace_logger, api_status* status); -int file_sender_create(i_sender** retval, const u::configuration& cfg, const char* file_name, +int file_sender_create(std::unique_ptr& retval, const u::configuration& cfg, const char* file_name, error_callback_fn* error_cb, i_trace* trace_logger, api_status* status) { - *retval = new logger::file::file_logger(file_name, trace_logger); + retval.reset(new logger::file::file_logger(file_name, trace_logger)); return error_code::success; } -int empty_data_transport_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int empty_data_transport_create(std::unique_ptr& retval, const u::configuration& config, + i_trace* trace_logger, api_status* status) { TRACE_INFO(trace_logger, "Empty data transport created."); - *retval = new model_management::empty_data_transport(); + retval.reset(new model_management::empty_data_transport()); return error_code::success; } -int file_model_loader_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int file_model_loader_create(std::unique_ptr& retval, const u::configuration& config, + i_trace* trace_logger, api_status* status) { TRACE_INFO(trace_logger, "File model loader created."); const char* file_name = config.get(name::MODEL_FILE_NAME, "current"); const bool file_must_exist = config.get_bool(name::MODEL_FILE_MUST_EXIST, false); - auto* file_loader = new model_management::file_model_loader(file_name, file_must_exist, trace_logger); + auto file_loader = VW::make_unique(file_name, file_must_exist, trace_logger); const auto success = file_loader->init(status); + if (success != error_code::success) { return success; } - if (success != error_code::success) - { - delete file_loader; - return success; - } - - *retval = file_loader; + retval = std::move(file_loader); return error_code::success; } #ifdef RL_BUILD_FEDERATION -int local_loop_controller_create( - m::i_data_transport** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) +int local_loop_controller_create(std::unique_ptr& retval, const u::configuration& config, + i_trace* trace_logger, api_status* status) { TRACE_INFO(trace_logger, "Local loop controller i_data_transport created."); std::unique_ptr output; RETURN_IF_FAIL(local_loop_controller::create(output, config, trace_logger, status)); - *retval = output.release(); + retval = std::move(output); return error_code::success; } #endif int null_time_provider_create( - i_time_provider** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) + std::unique_ptr& retval, const u::configuration& config, i_trace* trace_logger, api_status* status) { TRACE_INFO(trace_logger, "Null time provider created."); - *retval = nullptr; + retval.reset(); return error_code::success; } int clock_time_provider_create( - i_time_provider** retval, const u::configuration& config, i_trace* trace_logger, api_status* status) + std::unique_ptr& retval, const u::configuration& config, i_trace* trace_logger, api_status* status) { TRACE_INFO(trace_logger, "Clock time provider created."); - *retval = new clock_time_provider(); + retval.reset(new clock_time_provider()); return error_code::success; } @@ -163,7 +161,7 @@ void factory_initializer::register_default_factories() data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, local_loop_controller_create); #else data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, - [](m::i_data_transport**, const u::configuration&, i_trace* trace_logger, api_status* status) + [](std::unique_ptr&, const u::configuration&, i_trace* trace_logger, api_status* status) { RETURN_ERROR_ARG(trace_logger, status, create_fn_exception, "Cannot use LOCAL_LOOP_MODEL_DATA because rlclientlib was not compiled with federated learning enabled"); @@ -181,19 +179,22 @@ void factory_initializer::register_default_factories() // Register File loggers sender_factory.register_type(value::EPISODE_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + [](std::unique_ptr& retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, + api_status* status) { const char* file_name = c.get(name::EPISODE_FILE_NAME, "episode.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); }); sender_factory.register_type(value::OBSERVATION_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + [](std::unique_ptr& retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, + api_status* status) { const char* file_name = c.get(name::OBSERVATION_FILE_NAME, "observation.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); }); sender_factory.register_type(value::INTERACTION_FILE_SENDER, - [](i_sender** retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, api_status* status) + [](std::unique_ptr& retval, const u::configuration& c, error_callback_fn* cb, i_trace* trace_logger, + api_status* status) { const char* file_name = c.get(name::INTERACTION_FILE_NAME, "interaction.fb.data"); return file_sender_create(retval, c, file_name, cb, trace_logger, status); @@ -201,22 +202,25 @@ void factory_initializer::register_default_factories() // Register a default factory for LOCAL_LOOP_SENDER that returns an error sender_factory.register_type(value::LOCAL_LOOP_SENDER, - [](i_sender**, const u::configuration&, error_callback_fn*, i_trace* trace_logger, api_status* status) + [](std::unique_ptr&, const u::configuration&, error_callback_fn*, i_trace* trace_logger, + api_status* status) { RETURN_ERROR_ARG(trace_logger, status, create_fn_exception, "LOCAL_LOOP_SENDER must be used with model source set to LOCAL_LOOP_MODEL_DATA"); }); } -int null_tracer_create(i_trace** retval, const u::configuration& cfg, i_trace* trace_logger, api_status* status) +int null_tracer_create( + std::unique_ptr& retval, const u::configuration& cfg, i_trace* trace_logger, api_status* status) { - *retval = nullptr; + retval.reset(); return error_code::success; } -int console_tracer_create(i_trace** retval, const u::configuration& cfg, i_trace* trace_logger, api_status* status) +int console_tracer_create( + std::unique_ptr& retval, const u::configuration& cfg, i_trace* trace_logger, api_status* status) { - *retval = new console_tracer(); + retval.reset(new console_tracer()); return error_code::success; } } // namespace reinforcement_learning diff --git a/rlclientlib/federation/local_loop_controller.cc b/rlclientlib/federation/local_loop_controller.cc index 841241a37..bf473a831 100644 --- a/rlclientlib/federation/local_loop_controller.cc +++ b/rlclientlib/federation/local_loop_controller.cc @@ -48,19 +48,6 @@ local_loop_controller::local_loop_controller(std::string app_id, std::unique_ptr int local_loop_controller::update_global(api_status* status) { - if (_need_to_send_model_delta) - { - // get and send the model delta - auto buffer = std::make_shared>(); - auto writer = VW::io::create_vector_writer(buffer); - RETURN_IF_FAIL(_trainable_model->get_model_delta(*writer, status)); - - char* data_ptr = buffer->data(); - RETURN_IF_FAIL(_federated_client->report_result(reinterpret_cast(data_ptr), buffer->size(), status)); - - _need_to_send_model_delta = false; - } - // ask for a new global model model_management::model_data data; bool model_received = false; @@ -68,8 +55,17 @@ int local_loop_controller::update_global(api_status* status) if (model_received) { + // load the new model and immediately train it with accumulated data RETURN_IF_FAIL(_trainable_model->set_data(data, status)); - _need_to_send_model_delta = true; + update_local(); + + // get and send the model delta + auto buffer = std::make_shared>(); + auto writer = VW::io::create_vector_writer(buffer); + RETURN_IF_FAIL(_trainable_model->get_model_delta(*writer, status)); + + char* data_ptr = buffer->data(); + RETURN_IF_FAIL(_federated_client->report_result(reinterpret_cast(data_ptr), buffer->size(), status)); } return error_code::success; } @@ -84,7 +80,6 @@ int local_loop_controller::update_local(api_status* status) int local_loop_controller::get_data(model_management::model_data& data, api_status* status) { - RETURN_IF_FAIL(update_local(status)); RETURN_IF_FAIL(update_global(status)); RETURN_IF_FAIL(_trainable_model->get_data(data, status)); return error_code::success; diff --git a/rlclientlib/federation/vw_trainable_model.cc b/rlclientlib/federation/vw_trainable_model.cc index e60556cc3..512f1f276 100644 --- a/rlclientlib/federation/vw_trainable_model.cc +++ b/rlclientlib/federation/vw_trainable_model.cc @@ -262,10 +262,10 @@ int trainable_vw_model::learn(VW::workspace& example_ws, VW::multi_ex& examples, for (auto example : examples) { io_buf io_writer; - VW::details::cache_temp_buffer temp_buffer; + VW::parsers::cache::details::cache_temp_buffer temp_buffer; auto example_buffer = std::make_shared>(); io_writer.add_file(VW::io::create_vector_writer(example_buffer)); - VW::write_example_to_cache( + VW::parsers::cache::write_example_to_cache( io_writer, example, example_ws.example_parser->lbl_parser, example_ws.parse_mask, temp_buffer); io_writer.flush(); @@ -273,7 +273,7 @@ int trainable_vw_model::learn(VW::workspace& example_ws, VW::multi_ex& examples, io_reader.add_file(VW::io::create_buffer_view(example_buffer->data(), example_buffer->size())); VW::multi_ex example_out; example_out.push_back(VW::new_unused_example(*_model)); - VW::read_example_from_cache(_model.get(), io_reader, example_out); + VW::parsers::cache::read_example_from_cache(_model.get(), io_reader, example_out); examples_copied.insert(examples_copied.end(), example_out.begin(), example_out.end()); } diff --git a/rlclientlib/generic_event.cc b/rlclientlib/generic_event.cc index 8927f1d52..df5ae7778 100644 --- a/rlclientlib/generic_event.cc +++ b/rlclientlib/generic_event.cc @@ -62,7 +62,7 @@ float generic_event::prg(int drop_pass) const { const auto seed_str = _id + std::to_string(drop_pass); const auto seed = VW::uniform_hash(seed_str.c_str(), seed_str.length(), 0); - return exploration::uniform_random_merand48(seed); + return VW::details::merand48_noadvance(seed); } generic_event::payload_type_t generic_event::get_payload_type() const { return _payload_type; } diff --git a/rlclientlib/live_model_impl.cc b/rlclientlib/live_model_impl.cc index 3bbd4a4cf..f2f3c4907 100644 --- a/rlclientlib/live_model_impl.cc +++ b/rlclientlib/live_model_impl.cc @@ -133,12 +133,19 @@ int live_model_impl::choose_rank( // check arguments RETURN_IF_FAIL(check_null_or_empty(event_id, context, _trace_logger.get(), status)); - if (!_model_ready) - { - RETURN_IF_FAIL(explore_only(event_id, context, response, status)); - response.set_model_id("N/A"); - } - else { RETURN_IF_FAIL(explore_exploit(event_id, context, response, status)); } + + // The seed used is composed of uniform_hash(app_id) + uniform_hash(event_id) + const uint64_t seed = VW::uniform_hash(event_id, strlen(event_id), 0) + _seed_shift; + + std::vector action_ids; + std::vector action_pdf; + std::string model_version; + + _model->choose_rank(event_id, seed, context, action_ids, action_pdf, model_version, status); + + RETURN_IF_FAIL(sample_and_populate_response( + seed, action_ids, action_pdf, std::move(model_version), response, _trace_logger.get(), status)); + response.set_event_id(event_id); if (_learning_mode == LOGGINGONLY) @@ -517,9 +524,7 @@ live_model_impl::live_model_impl(const utility::configuration& config, std::func int live_model_impl::init_trace(api_status* status) { const auto* const trace_impl = _configuration.get(name::TRACE_LOG_IMPLEMENTATION, value::NULL_TRACE_LOGGER); - i_trace* plogger = nullptr; - RETURN_IF_FAIL(_trace_factory->create(&plogger, trace_impl, _configuration, nullptr, status)); - _trace_logger.reset(plogger); + RETURN_IF_FAIL(_trace_factory->create(_trace_logger, trace_impl, _configuration, nullptr, status)); TRACE_INFO(_trace_logger, "API Tracing initialized"); _watchdog.set_trace_log(_trace_logger.get()); return error_code::success; @@ -528,9 +533,7 @@ int live_model_impl::init_trace(api_status* status) int live_model_impl::init_model(api_status* status) { const auto* const model_impl = _configuration.get(name::MODEL_IMPLEMENTATION, value::VW); - m::i_model* pmodel = nullptr; - RETURN_IF_FAIL(_m_factory->create(&pmodel, model_impl, _configuration, _trace_logger.get(), status)); - _model.reset(pmodel); + RETURN_IF_FAIL(_m_factory->create(_model, model_impl, _configuration, _trace_logger.get(), status)); return error_code::success; } @@ -539,100 +542,103 @@ int live_model_impl::init_loggers(api_status* status) // Get the name of raw data (as opposed to message) sender for interactions. const auto* const ranking_sender_impl = _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::get_default_interaction_sender()); - i_sender* ranking_data_sender = nullptr; + std::unique_ptr ranking_data_sender; // Use the name to create an instance of raw data sender for interactions _configuration.set(config_constants::CONFIG_SECTION, config_constants::INTERACTION); RETURN_IF_FAIL(_sender_factory->create( - &ranking_data_sender, ranking_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); + ranking_data_sender, ranking_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(ranking_data_sender->init(_configuration, status)); // Get the name of raw data (as opposed to message) sender for observations. const auto* const outcome_sender_impl = _configuration.get(name::OBSERVATION_SENDER_IMPLEMENTATION, value::get_default_observation_sender()); - i_sender* outcome_sender = nullptr; + std::unique_ptr outcome_sender; // Use the name to create an instance of raw data sender for observations _configuration.set(config_constants::CONFIG_SECTION, config_constants::OBSERVATION); RETURN_IF_FAIL(_sender_factory->create( - &outcome_sender, outcome_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); + outcome_sender, outcome_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(outcome_sender->init(_configuration, status)); - RETURN_IF_FAIL(init_loggers_common(ranking_data_sender, outcome_sender, status)); + RETURN_IF_FAIL(init_loggers_common(std::move(ranking_data_sender), std::move(outcome_sender), status)); return error_code::success; } // Common part for both init_loggers and init_local_loop -int live_model_impl::init_loggers_common(i_sender* ranking_data_sender, i_sender* outcome_sender, api_status* status) +int live_model_impl::init_loggers_common( + std::unique_ptr ranking_data_sender, std::unique_ptr outcome_sender, api_status* status) { // Create a message sender that will prepend the message with a preamble and send the raw data using the // factory created raw data sender - l::i_message_sender* ranking_msg_sender = new l::preamble_message_sender(ranking_data_sender); + std::unique_ptr ranking_msg_sender( + new l::preamble_message_sender(std::move(ranking_data_sender))); RETURN_IF_FAIL(ranking_msg_sender->init(status)); // Get time provider factory and implementation const auto* const time_provider_impl = _configuration.get(name::TIME_PROVIDER_IMPLEMENTATION, value::get_default_time_provider()); - i_time_provider* logger_extensions_time_provider = nullptr; + std::unique_ptr logger_extensions_time_provider; RETURN_IF_FAIL(_time_provider_factory->create( - &logger_extensions_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); + logger_extensions_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); // Create the logger extension - _logger_extensions.reset( - logger::i_logger_extensions::get_extensions(_configuration, logger_extensions_time_provider)); + _logger_extensions = + logger::i_logger_extensions::get_extensions(_configuration, std::move(logger_extensions_time_provider)); - i_time_provider* ranking_time_provider = nullptr; + std::unique_ptr ranking_time_provider; RETURN_IF_FAIL(_time_provider_factory->create( - &ranking_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); + ranking_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); // Create a logger for interactions that will use msg sender to send interaction messages _interaction_logger.reset(new logger::interaction_logger_facade(_model->model_type(), _configuration, - ranking_msg_sender, _watchdog, ranking_time_provider, _logger_extensions.get(), &_error_cb)); + std::move(ranking_msg_sender), _watchdog, std::move(ranking_time_provider), *_logger_extensions, &_error_cb)); RETURN_IF_FAIL(_interaction_logger->init(status)); // Create a message sender that will prepend the message with a preamble and send the raw data using the // factory created raw data sender - l::i_message_sender* outcome_msg_sender = new l::preamble_message_sender(outcome_sender); + std::unique_ptr outcome_msg_sender(new l::preamble_message_sender(std::move(outcome_sender))); RETURN_IF_FAIL(outcome_msg_sender->init(status)); // Get time provider implementation - i_time_provider* observation_time_provider = nullptr; + std::unique_ptr observation_time_provider; RETURN_IF_FAIL(_time_provider_factory->create( - &observation_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); + observation_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); // Create a logger for observations that will use msg sender to send observation messages _outcome_logger.reset(new logger::observation_logger_facade( - _configuration, outcome_msg_sender, _watchdog, observation_time_provider, &_error_cb)); + _configuration, std::move(outcome_msg_sender), _watchdog, std::move(observation_time_provider), &_error_cb)); RETURN_IF_FAIL(_outcome_logger->init(status)); if (_configuration.get(name::EPISODE_EH_HOST, nullptr) != nullptr || - _configuration.get(name::EPISODE_FILE_NAME, nullptr) != nullptr) + _configuration.get(name::EPISODE_FILE_NAME, nullptr) != nullptr || + _configuration.get(name::EPISODE_HTTP_API_HOST, nullptr) != nullptr) { // Get the name of raw data (as opposed to message) sender for episodes. const auto* const episode_sender_impl = _configuration.get(name::EPISODE_SENDER_IMPLEMENTATION, value::get_default_episode_sender()); - i_sender* episode_sender = nullptr; + std::unique_ptr episode_sender; // Use the name to create an instance of raw data sender for episodes _configuration.set(config_constants::CONFIG_SECTION, config_constants::EPISODE); RETURN_IF_FAIL(_sender_factory->create( - &episode_sender, episode_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); + episode_sender, episode_sender_impl, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(episode_sender->init(_configuration, status)); // Create a message sender that will prepend the message with a preamble and send the raw data using the // factory created raw data sender - l::i_message_sender* episode_msg_sender = new l::preamble_message_sender(episode_sender); + std::unique_ptr episode_msg_sender(new l::preamble_message_sender(std::move(episode_sender))); RETURN_IF_FAIL(episode_msg_sender->init(status)); // Get time provider implementation - i_time_provider* episode_time_provider = nullptr; + std::unique_ptr episode_time_provider; RETURN_IF_FAIL(_time_provider_factory->create( - &episode_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); + episode_time_provider, time_provider_impl, _configuration, _trace_logger.get(), status)); // Create a logger for episodes that will use msg sender to send episode messages _episode_logger.reset(new logger::episode_logger_facade( - _configuration, episode_msg_sender, _watchdog, episode_time_provider, &_error_cb)); + _configuration, std::move(episode_msg_sender), _watchdog, std::move(episode_time_provider), &_error_cb)); RETURN_IF_FAIL(_episode_logger->init(status)); } @@ -664,94 +670,14 @@ void live_model_impl::handle_model_update(const model_management::model_data& da _model_ready = model_ready; } -int live_model_impl::explore_only( - const char* event_id, string_view context, ranking_response& response, api_status* status) const -{ - // Generate egreedy pdf - utility::ContextInfo context_info; - RETURN_IF_FAIL(utility::get_context_info(context, context_info, _trace_logger.get(), status)); - - size_t action_count = context_info.actions.size(); - if (action_count < 1) - { - RETURN_ERROR_LS(_trace_logger.get(), status, json_no_actions_found) << "Context must have at least one action"; - } - - vector pdf(action_count); - // Generate a pdf with epsilon distributed between all action. - // The top action gets the remaining (1 - epsilon) - // Assume that the user's top choice for action is at index 0 - const auto top_action_id = 0; - auto scode = e::generate_epsilon_greedy(_initial_epsilon, top_action_id, begin(pdf), end(pdf)); - if (S_EXPLORATION_OK != scode) - { - RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; - } - - // The seed used is composed of uniform_hash(app_id) + uniform_hash(event_id) - const uint64_t seed = VW::uniform_hash(event_id, strlen(event_id), 0) + _seed_shift; - - // Pick a slot using the pdf. NOTE: sample_after_normalizing() can change the pdf - uint32_t chosen_index = 0; - scode = e::sample_after_normalizing(seed, begin(pdf), end(pdf), chosen_index); - - if (S_EXPLORATION_OK != scode) - { - RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration error code: " << scode; - } - - // NOTE: When there is no model, the rank - // step was done by the user. i.e. Actions are already in ranked order - // If there were an action list it would be [0,1,2,3,4..]. The index - // of the list matches the action_id. There is no need to generate this - // list of actions we can use the index into this list as a proxy for the - // actual action_id. - // i.e chosen_index == action[chosen_index] - // Why is this documented? Because explore_exploit uses a model and we - // cannot make the same assumption there. (Bug was fixed) - - // Setup response with pdf from prediction and chosen action - // Chosen action goes first. First action gets swapped with chosen action - for (size_t idx = 0; idx < pdf.size(); ++idx) { response.push_back(idx, pdf[idx]); } - - // Swap values in first position with values in chosen index - scode = e::swap_chosen(begin(response), end(response), chosen_index); - - if (S_EXPLORATION_OK != scode) - { - RETURN_ERROR_LS(_trace_logger.get(), status, exploration_error) << "Exploration (Swap) error code: " << scode; - } - - RETURN_IF_FAIL(response.set_chosen_action_id(chosen_index)); - - return error_code::success; -} - -int live_model_impl::explore_exploit( - const char* event_id, string_view context, ranking_response& response, api_status* status) const -{ - // The seed used is composed of uniform_hash(app_id) + uniform_hash(event_id) - const uint64_t seed = VW::uniform_hash(event_id, strlen(event_id), 0) + _seed_shift; - - std::vector action_ids; - std::vector action_pdf; - std::string model_version; - - RETURN_IF_FAIL(_model->choose_rank(event_id, seed, context, action_ids, action_pdf, model_version, status)); - - return sample_and_populate_response( - seed, action_ids, action_pdf, std::move(model_version), response, _trace_logger.get(), status); -} - int live_model_impl::init_model_mgmt(api_status* status) { // Initialize transport for the model using transport factory const auto* const tranport_impl = _configuration.get(name::MODEL_SRC, value::get_default_data_transport()); - m::i_data_transport* ptransport = nullptr; - RETURN_IF_FAIL(_t_factory->create(&ptransport, tranport_impl, _configuration, _trace_logger.get(), status)); - + std::unique_ptr ptransport; + RETURN_IF_FAIL(_t_factory->create(ptransport, tranport_impl, _configuration, status)); // This class manages lifetime of transport - _transport.reset(ptransport); + this->_transport = std::move(ptransport); if (_bg_model_proc) { @@ -771,9 +697,9 @@ int live_model_impl::init_local_loop(api_status* status) assert(model_src == value::LOCAL_LOOP_MODEL_DATA); // Creating i_data_transport with type LOCAL_LOOP_MODEL_DATA results in local_loop_controller - m::i_data_transport* output = nullptr; - RETURN_IF_FAIL(_t_factory->create(&output, model_src, _configuration, _trace_logger.get(), status)); - std::unique_ptr llc(reinterpret_cast(output)); + std::unique_ptr output; + RETURN_IF_FAIL(_t_factory->create(output, model_src, _configuration, _trace_logger.get(), status)); + std::unique_ptr llc(reinterpret_cast(output.release())); // Create senders with default sender implementation set to LOCAL_LOOP_SENDER std::string interaction_sender_type = @@ -781,25 +707,25 @@ int live_model_impl::init_local_loop(api_status* status) std::string observation_sender_type = _configuration.get(name::INTERACTION_SENDER_IMPLEMENTATION, value::LOCAL_LOOP_SENDER); - i_sender* interaction_sender = nullptr; - if (interaction_sender_type == value::LOCAL_LOOP_SENDER) { interaction_sender = llc->get_local_sender().release(); } + std::unique_ptr interaction_sender; + if (interaction_sender_type == value::LOCAL_LOOP_SENDER) { interaction_sender = llc->get_local_sender(); } else { RETURN_IF_FAIL(_sender_factory->create( - &interaction_sender, interaction_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); + interaction_sender, interaction_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(interaction_sender->init(_configuration, status)); } - i_sender* observation_sender = nullptr; - if (observation_sender_type == value::LOCAL_LOOP_SENDER) { observation_sender = llc->get_local_sender().release(); } + std::unique_ptr observation_sender; + if (observation_sender_type == value::LOCAL_LOOP_SENDER) { observation_sender = llc->get_local_sender(); } else { RETURN_IF_FAIL(_sender_factory->create( - &observation_sender, observation_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); + observation_sender, observation_sender_type, _configuration, &_error_cb, _trace_logger.get(), status)); RETURN_IF_FAIL(observation_sender->init(_configuration, status)); } - RETURN_IF_FAIL(init_loggers_common(interaction_sender, observation_sender, status)); + RETURN_IF_FAIL(init_loggers_common(std::move(interaction_sender), std::move(observation_sender), status)); // Set live_model_impl's data transport to local loop controller _transport = std::move(llc); diff --git a/rlclientlib/live_model_impl.h b/rlclientlib/live_model_impl.h index efa97203a..2d93ff820 100644 --- a/rlclientlib/live_model_impl.h +++ b/rlclientlib/live_model_impl.h @@ -81,14 +81,13 @@ class live_model_impl int init_model(api_status* status); int init_model_mgmt(api_status* status); int init_loggers(api_status* status); - int init_loggers_common(i_sender* ranking_data_sender, i_sender* outcome_sender, api_status* status); + int init_loggers_common( + std::unique_ptr ranking_data_sender, std::unique_ptr outcome_sender, api_status* status); int init_trace(api_status* status); int init_local_loop(api_status* status); int check_if_local_loop(bool& output, api_status* status); static void _handle_model_update(const model_management::model_data& data, live_model_impl* ctxt); void handle_model_update(const model_management::model_data& data); - int explore_only(const char* event_id, string_view context, ranking_response& response, api_status* status) const; - int explore_exploit(const char* event_id, string_view context, ranking_response& response, api_status* status) const; template int report_outcome_internal(const char* event_id, D outcome, api_status* status); template diff --git a/rlclientlib/logger/async_batcher.h b/rlclientlib/logger/async_batcher.h index 7c4b8fa22..437c4b36f 100644 --- a/rlclientlib/logger/async_batcher.h +++ b/rlclientlib/logger/async_batcher.h @@ -19,7 +19,10 @@ // float comparisons #include "vw/core/vw_math.h" +#include #include +#include +#include namespace reinforcement_learning { @@ -71,7 +74,7 @@ class async_batcher : public i_async_batcher void flush(); // flush all batches public: - async_batcher(i_message_sender* sender, utility::watchdog& watchdog, shared_state_t& shared_state, + async_batcher(std::unique_ptr sender, utility::watchdog& watchdog, shared_state_t& shared_state, error_callback_fn* perror_cb, const utility::async_batcher_config& config); ~async_batcher(); @@ -205,10 +208,10 @@ void async_batcher::flush() } template class TSerializer> -async_batcher::async_batcher(i_message_sender* sender, utility::watchdog& watchdog, +async_batcher::async_batcher(std::unique_ptr sender, utility::watchdog& watchdog, typename TSerializer::shared_state_t& shared_state, error_callback_fn* perror_cb, const utility::async_batcher_config& config) - : _sender(sender) + : _sender(std::move(sender)) , _queue(config.send_queue_max_capacity, config.event_counter_status, config.subsample_rate) , _send_high_water_mark(config.send_high_water_mark) , _perror_cb(perror_cb) diff --git a/rlclientlib/logger/event_logger.h b/rlclientlib/logger/event_logger.h index 254bd8d17..4835c52bf 100644 --- a/rlclientlib/logger/event_logger.h +++ b/rlclientlib/logger/event_logger.h @@ -35,9 +35,10 @@ class event_logger using TFunc = std::function; public: - event_logger(i_time_provider* time_provider, i_async_batcher* batcher); + event_logger(std::unique_ptr time_provider, std::unique_ptr> batcher); - event_logger(i_time_provider* time_provider, i_async_batcher* batcher, const char* app_id); + event_logger(std::unique_ptr time_provider, std::unique_ptr> batcher, + const char* app_id); int init(api_status* status); @@ -56,14 +57,16 @@ class event_logger }; template -event_logger::event_logger(i_time_provider* time_provider, i_async_batcher* batcher) - : event_logger(time_provider, batcher, "") +event_logger::event_logger( + std::unique_ptr time_provider, std::unique_ptr> batcher) + : event_logger(std::move(time_provider), std::move(batcher), "") { } template -event_logger::event_logger(i_time_provider* time_provider, i_async_batcher* batcher, const char* app_id) - : _time_provider(time_provider), _batcher(batcher), _app_id(app_id) +event_logger::event_logger(std::unique_ptr time_provider, + std::unique_ptr> batcher, const char* app_id) + : _time_provider(std::move(time_provider)), _batcher(std::move(batcher)), _app_id(app_id) { } @@ -97,8 +100,9 @@ int event_logger::append(TFunc& func, TEvent* event, api_status* status) class interaction_logger : public event_logger { public: - interaction_logger(i_time_provider* time_provider, i_async_batcher* batcher) - : event_logger(time_provider, batcher) + interaction_logger( + std::unique_ptr time_provider, std::unique_ptr> batcher) + : event_logger(std::move(time_provider), std::move(batcher)) { } @@ -109,8 +113,9 @@ class interaction_logger : public event_logger class ccb_logger : public event_logger { public: - ccb_logger(i_time_provider* time_provider, i_async_batcher* batcher) - : event_logger(time_provider, batcher) + ccb_logger( + std::unique_ptr time_provider, std::unique_ptr> batcher) + : event_logger(std::move(time_provider), std::move(batcher)) { } @@ -122,8 +127,9 @@ class ccb_logger : public event_logger class multi_slot_logger : public event_logger { public: - multi_slot_logger(i_time_provider* time_provider, i_async_batcher* batcher) - : event_logger(time_provider, batcher) + multi_slot_logger(std::unique_ptr time_provider, + std::unique_ptr> batcher) + : event_logger(std::move(time_provider), std::move(batcher)) { } @@ -135,8 +141,9 @@ class multi_slot_logger : public event_logger class observation_logger : public event_logger { public: - observation_logger(i_time_provider* time_provider, i_async_batcher* batcher) - : event_logger(time_provider, batcher) + observation_logger( + std::unique_ptr time_provider, std::unique_ptr> batcher) + : event_logger(std::move(time_provider), std::move(batcher)) { } @@ -162,8 +169,9 @@ class observation_logger : public event_logger class generic_event_logger : public event_logger { public: - generic_event_logger(i_time_provider* time_provider, i_async_batcher* batcher, const char* app_id) - : event_logger(time_provider, batcher, app_id) + generic_event_logger(std::unique_ptr time_provider, + std::unique_ptr> batcher, const char* app_id) + : event_logger(std::move(time_provider), std::move(batcher), app_id) { } diff --git a/rlclientlib/logger/logger_extensions.cc b/rlclientlib/logger/logger_extensions.cc index ec846dc77..57546660f 100644 --- a/rlclientlib/logger/logger_extensions.cc +++ b/rlclientlib/logger/logger_extensions.cc @@ -8,17 +8,20 @@ namespace logger class default_extensions : public i_logger_extensions { public: - default_extensions(const utility::configuration& c, i_time_provider* provider) : i_logger_extensions(c) + default_extensions(const utility::configuration& c, std::unique_ptr /* provider */) + : i_logger_extensions(c) { - delete provider; // We don't use it + // i_time_provider is not used + // unique_ptr will delete it } - i_async_batcher* create_batcher( - i_message_sender* sender, utility::watchdog& watchdog, error_callback_fn* perror_cb, const char* section) override + std::unique_ptr> create_batcher(std::unique_ptr sender, + utility::watchdog& watchdog, error_callback_fn* perror_cb, const char* section) override { auto config = utility::get_batcher_config(_config, section); - return new async_batcher( - sender, watchdog, _dummy_state, perror_cb, config); + return std::unique_ptr>( + new async_batcher( + std::move(sender), watchdog, _dummy_state, perror_cb, config)); } bool is_object_extraction_enabled() const override { return false; } @@ -44,12 +47,15 @@ class default_extensions : public i_logger_extensions i_logger_extensions::i_logger_extensions(const utility::configuration& config) : _config(config) {} i_logger_extensions::~i_logger_extensions() = default; -i_logger_extensions* i_logger_extensions::get_extensions( - const utility::configuration& config, i_time_provider* time_provider) +std::unique_ptr i_logger_extensions::get_extensions( + const utility::configuration& config, std::unique_ptr time_provider) { const char* section = "interaction"; // fixme lift this to live_model_impl; - auto* res = create_dedup_logger_extension(config, section, time_provider); - return res != nullptr ? res : new default_extensions(config, time_provider); + if (should_use_dedup_logger_extension(config, section)) + { + return create_dedup_logger_extension(config, section, std::move(time_provider)); + } + return std::unique_ptr(new default_extensions(config, std::move(time_provider))); } } // namespace logger diff --git a/rlclientlib/logger/logger_extensions.h b/rlclientlib/logger/logger_extensions.h index 0762e5312..0d10bff9f 100644 --- a/rlclientlib/logger/logger_extensions.h +++ b/rlclientlib/logger/logger_extensions.h @@ -7,6 +7,7 @@ #include #include +#include #include namespace reinforcement_learning @@ -56,14 +57,15 @@ class i_logger_extensions virtual bool is_object_extraction_enabled() const = 0; virtual bool is_serialization_transform_enabled() const = 0; - virtual i_async_batcher* create_batcher( - i_message_sender* sender, utility::watchdog& watchdog, error_callback_fn* perror_cb, const char* section) = 0; + virtual std::unique_ptr> create_batcher(std::unique_ptr sender, + utility::watchdog& watchdog, error_callback_fn* perror_cb, const char* section) = 0; virtual int transform_payload_and_extract_objects( string_view context, std::string& edited_payload, object_list_t& objects, api_status* status) = 0; virtual int transform_serialized_payload( payload_buffer_t& input, event_content_type& content_type, api_status* status) const = 0; - static i_logger_extensions* get_extensions(const utility::configuration& config, i_time_provider* time_provider); + static std::unique_ptr get_extensions( + const utility::configuration& config, std::unique_ptr time_provider); }; } // namespace logger diff --git a/rlclientlib/logger/logger_facade.cc b/rlclientlib/logger/logger_facade.cc index 3f08a33d3..fd71fc474 100644 --- a/rlclientlib/logger/logger_facade.cc +++ b/rlclientlib/logger/logger_facade.cc @@ -16,39 +16,42 @@ int protocol_not_supported(api_status* status) } template -i_async_batcher* create_legacy_async_batcher(const utility::configuration& c, i_message_sender* sender, - utility::watchdog& watchdog, error_callback_fn* perror_cb, const char* section, - typename async_batcher::shared_state_t& shared_state) +std::unique_ptr> create_legacy_async_batcher(const utility::configuration& c, + std::unique_ptr sender, utility::watchdog& watchdog, error_callback_fn* perror_cb, + const char* section, typename async_batcher::shared_state_t& shared_state) { auto config = utility::get_batcher_config(c, section); - return new async_batcher(sender, watchdog, shared_state, perror_cb, config); + return std::unique_ptr>( + new async_batcher(std::move(sender), watchdog, shared_state, perror_cb, config)); } interaction_logger_facade::interaction_logger_facade(model_type_t model_type, const utility::configuration& c, - i_message_sender* sender, utility::watchdog& watchdog, i_time_provider* time_provider, i_logger_extensions* ext, + std::unique_ptr sender, utility::watchdog& watchdog, + std::unique_ptr time_provider, i_logger_extensions& logger_extensions, error_callback_fn* perror_cb) : _model_type(model_type) , _version(c.get_int(name::PROTOCOL_VERSION, value::DEFAULT_PROTOCOL_VERSION)) , _serializer_shared_state(0) - , _ext_p(ext) + , _logger_extensions(logger_extensions) , _v1_cb(_version == 1 && _model_type == model_type_t::CB - ? new interaction_logger(time_provider, + ? new interaction_logger(std::move(time_provider), create_legacy_async_batcher( - c, sender, watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) + c, std::move(sender), watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) : nullptr) , _v1_ccb(_version == 1 && _model_type == model_type_t::CCB - ? new ccb_logger(time_provider, + ? new ccb_logger(std::move(time_provider), create_legacy_async_batcher( - c, sender, watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) + c, std::move(sender), watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) : nullptr) , _v1_multislot(_version == 1 && _model_type == model_type_t::SLATES - ? new multi_slot_logger(time_provider, + ? new multi_slot_logger(std::move(time_provider), create_legacy_async_batcher( - c, sender, watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) + c, std::move(sender), watchdog, perror_cb, INTERACTION_SECTION, _serializer_shared_state)) : nullptr) , _v2(_version == 2 - ? new generic_event_logger(time_provider, - ext->create_batcher(sender, watchdog, perror_cb, INTERACTION_SECTION), c.get(name::APP_ID, "")) + ? new generic_event_logger(std::move(time_provider), + _logger_extensions.create_batcher(std::move(sender), watchdog, perror_cb, INTERACTION_SECTION), + c.get(name::APP_ID, "")) : nullptr) { } @@ -98,8 +101,8 @@ int interaction_logger_facade::log(string_view context, unsigned int flags, cons probabilities.push_back(r.probability); } - return _v2->log(response.get_event_id(), context, _serializer_cb.type, _ext_p, _serializer_cb, status, flags, lmt, - action_ids, probabilities, model_id); + return _v2->log(response.get_event_id(), context, _serializer_cb.type, &_logger_extensions, _serializer_cb, + status, flags, lmt, action_ids, probabilities, model_id); } default: return protocol_not_supported(status); @@ -126,8 +129,8 @@ int interaction_logger_facade::log(const char* episode_id, const char* previous_ std::string model_id(response.get_model_id()); std::string previous_id_str(previous_id ? previous_id : ""); - return _v2->log(episode_id, context, _multistep_serializer.type, _ext_p, _multistep_serializer, status, - previous_id_str, flags, action_ids, probabilities, event_id, model_id); + return _v2->log(episode_id, context, _multistep_serializer.type, &_logger_extensions, _multistep_serializer, + status, previous_id_str, flags, action_ids, probabilities, event_id, model_id); } default: return protocol_not_supported(status); @@ -193,8 +196,8 @@ int interaction_logger_facade::log_decision(const std::string& event_id, string_ generic_event::payload_type_t payload_type; RETURN_IF_FAIL(multi_slot_model_type_to_payload_type(_model_type, payload_type, status)); - return _v2->log(event_id.c_str(), context, payload_type, _ext_p, _serializer_multislot, status, flags, action_ids, - pdfs, model_version, slot_ids, baseline_actions, lmt); + return _v2->log(event_id.c_str(), context, payload_type, &_logger_extensions, _serializer_multislot, status, + flags, action_ids, pdfs, model_version, slot_ids, baseline_actions, lmt); } default: return protocol_not_supported(status); @@ -211,25 +214,26 @@ int interaction_logger_facade::log_continuous_action( // Create a string out of char* returned by get_model_id() // so that it can be copied by value and persist after char* goes out of scope auto model_id = std::string(response.get_model_id()); - return _v2->log(response.get_event_id(), context, _serializer_ca.type, _ext_p, _serializer_ca, status, flags, - response.get_chosen_action(), response.get_chosen_action_pdf_value(), model_id); + return _v2->log(response.get_event_id(), context, _serializer_ca.type, &_logger_extensions, _serializer_ca, + status, flags, response.get_chosen_action(), response.get_chosen_action_pdf_value(), model_id); } default: return protocol_not_supported(status); } } -observation_logger_facade::observation_logger_facade(const utility::configuration& c, i_message_sender* sender, - utility::watchdog& watchdog, i_time_provider* time_provider, error_callback_fn* perror_cb) +observation_logger_facade::observation_logger_facade(const utility::configuration& c, + std::unique_ptr sender, utility::watchdog& watchdog, + std::unique_ptr time_provider, error_callback_fn* perror_cb) : _version(c.get_int(name::PROTOCOL_VERSION, value::DEFAULT_PROTOCOL_VERSION)) , _serializer_shared_state(0) - , _v1(_version == 1 ? new observation_logger(time_provider, - create_legacy_async_batcher( - c, sender, watchdog, perror_cb, OBSERVATION_SECTION, _serializer_shared_state)) + , _v1(_version == 1 ? new observation_logger(std::move(time_provider), + create_legacy_async_batcher(c, std::move(sender), watchdog, perror_cb, + OBSERVATION_SECTION, _serializer_shared_state)) : nullptr) - , _v2(_version == 2 ? new generic_event_logger(time_provider, - create_legacy_async_batcher( - c, sender, watchdog, perror_cb, OBSERVATION_SECTION, _serializer_shared_state), + , _v2(_version == 2 ? new generic_event_logger(std::move(time_provider), + create_legacy_async_batcher(c, std::move(sender), watchdog, perror_cb, + OBSERVATION_SECTION, _serializer_shared_state), c.get(name::APP_ID, "")) : nullptr) { @@ -356,13 +360,13 @@ int observation_logger_facade::report_action_taken(const char* primary_id, const } // TODO: Do we need an EPISODE_SECTION for the config? Just use OBSERVATION_SECTION for now -episode_logger_facade::episode_logger_facade(const utility::configuration& c, i_message_sender* sender, - utility::watchdog& watchdog, i_time_provider* time_provider, error_callback_fn* perror_cb) +episode_logger_facade::episode_logger_facade(const utility::configuration& c, std::unique_ptr sender, + utility::watchdog& watchdog, std::unique_ptr time_provider, error_callback_fn* perror_cb) : _version(c.get_int(name::PROTOCOL_VERSION, value::DEFAULT_PROTOCOL_VERSION)) , _serializer_shared_state(0) - , _v2(_version == 2 ? new generic_event_logger(time_provider, - create_legacy_async_batcher( - c, sender, watchdog, perror_cb, OBSERVATION_SECTION, _serializer_shared_state), + , _v2(_version == 2 ? new generic_event_logger(std::move(time_provider), + create_legacy_async_batcher(c, std::move(sender), watchdog, perror_cb, + OBSERVATION_SECTION, _serializer_shared_state), c.get(name::APP_ID, "")) : nullptr) { diff --git a/rlclientlib/logger/logger_facade.h b/rlclientlib/logger/logger_facade.h index c6466153d..ee9606c09 100644 --- a/rlclientlib/logger/logger_facade.h +++ b/rlclientlib/logger/logger_facade.h @@ -15,6 +15,7 @@ #include "utility/watchdog.h" #include +#include namespace reinforcement_learning { @@ -24,8 +25,9 @@ class interaction_logger_facade { public: interaction_logger_facade(reinforcement_learning::model_management::model_type_t model_type, - const utility::configuration& c, i_message_sender* sender, utility::watchdog& watchdog, - i_time_provider* time_provider, i_logger_extensions* ext, error_callback_fn* perror_cb = nullptr); + const utility::configuration& c, std::unique_ptr sender, utility::watchdog& watchdog, + std::unique_ptr time_provider, i_logger_extensions& logger_extensions, + error_callback_fn* perror_cb = nullptr); interaction_logger_facade(const interaction_logger_facade& other) = delete; interaction_logger_facade& operator=(const interaction_logger_facade& other) = delete; @@ -62,8 +64,8 @@ class interaction_logger_facade const reinforcement_learning::model_management::model_type_t _model_type; const int _version; int _serializer_shared_state; - // _ext_p is owned by live_model_impl - i_logger_extensions* _ext_p; + // _logger_extensions is owned by live_model_impl + i_logger_extensions& _logger_extensions; const std::unique_ptr _v1_cb; const std::unique_ptr _v1_ccb; @@ -80,8 +82,9 @@ class interaction_logger_facade class observation_logger_facade { public: - observation_logger_facade(const utility::configuration& c, i_message_sender* sender, utility::watchdog& watchdog, - i_time_provider* time_provider, error_callback_fn* perror_cb = nullptr); + observation_logger_facade(const utility::configuration& c, std::unique_ptr sender, + utility::watchdog& watchdog, std::unique_ptr time_provider, + error_callback_fn* perror_cb = nullptr); observation_logger_facade(const observation_logger_facade& other) = delete; observation_logger_facade& operator=(const observation_logger_facade& other) = delete; @@ -114,8 +117,9 @@ class observation_logger_facade class episode_logger_facade { public: - episode_logger_facade(const utility::configuration& c, i_message_sender* sender, utility::watchdog& watchdog, - i_time_provider* time_provider, error_callback_fn* perror_cb = nullptr); + episode_logger_facade(const utility::configuration& c, std::unique_ptr sender, + utility::watchdog& watchdog, std::unique_ptr time_provider, + error_callback_fn* perror_cb = nullptr); episode_logger_facade(const episode_logger_facade& other) = delete; episode_logger_facade& operator=(const episode_logger_facade& other) = delete; diff --git a/rlclientlib/logger/preamble_sender.cc b/rlclientlib/logger/preamble_sender.cc index 50d611340..86bf72864 100644 --- a/rlclientlib/logger/preamble_sender.cc +++ b/rlclientlib/logger/preamble_sender.cc @@ -9,7 +9,7 @@ namespace logger { struct preamble; -preamble_message_sender::preamble_message_sender(i_sender* sender) : _sender{sender} {} +preamble_message_sender::preamble_message_sender(std::unique_ptr sender) : _sender(std::move(sender)) {} int preamble_message_sender::send(const uint16_t msg_type, const buffer& db, api_status* status) { diff --git a/rlclientlib/logger/preamble_sender.h b/rlclientlib/logger/preamble_sender.h index 6c570ded0..361e0a825 100644 --- a/rlclientlib/logger/preamble_sender.h +++ b/rlclientlib/logger/preamble_sender.h @@ -9,7 +9,7 @@ namespace logger class preamble_message_sender : public i_message_sender { public: - explicit preamble_message_sender(i_sender*); + explicit preamble_message_sender(std::unique_ptr); int send(const uint16_t msg_type, const buffer& db, api_status* status) override; int init(api_status* status) override; diff --git a/rlclientlib/model_mgmt/model_mgmt.cc b/rlclientlib/model_mgmt/model_mgmt.cc index 13bc28c6f..a4d416694 100644 --- a/rlclientlib/model_mgmt/model_mgmt.cc +++ b/rlclientlib/model_mgmt/model_mgmt.cc @@ -1,77 +1,29 @@ #include "model_mgmt.h" -#include -#include - namespace reinforcement_learning { namespace model_management { -model_data::model_data() = default; - -// copy constructor: allocate new memory and copy over other's data -model_data::model_data(model_data const& other) - : _data(new char[other._data_sz]), _data_sz(other._data_sz), _refresh_count(other._refresh_count) -{ - if (_data_sz > 0) { std::memcpy(_data, other._data, _data_sz); } -} -// move constructor: take other's data pointer and set original pointer to null -model_data::model_data(model_data&& other) noexcept - : _data(other._data), _data_sz(other._data_sz), _refresh_count(other._refresh_count) -{ - other._data = nullptr; -} - -// pass-by-value assignment operator -model_data& model_data::operator=(model_data other) noexcept -{ - std::swap(_data, other._data); - std::swap(_data_sz, other._data_sz); - std::swap(_refresh_count, other._refresh_count); - return *this; -} - -model_data::~model_data() { free(); } - -char* model_data::data() const { return _data; } +char* model_data::data() { return _data.data(); } +const char* model_data::data() const { return _data.data(); } void model_data::increment_refresh_count() { ++_refresh_count; } -size_t model_data::data_sz() const { return _data_sz; } +size_t model_data::data_sz() const { return _data.size(); } uint32_t model_data::refresh_count() const { return _refresh_count; } -void model_data::data_sz(const size_t fillsz) { _data_sz = fillsz; } +void model_data::data_sz(const size_t fillsz) { _data.resize(fillsz); } char* model_data::alloc(const size_t desired) { - // wrap the allocation in a unique_ptr for exception safety - std::unique_ptr data_new(new char[desired]); - char* data_new_ptr = data_new.get(); - std::swap(_data, data_new_ptr); - data_new.release(); - _data_sz = desired; - - // after swap, data_new_ptr now holds the original _data ptr - if (data_new_ptr != nullptr) - { - delete[] data_new_ptr; - data_new_ptr = nullptr; - } - - return _data; + _data.clear(); + _data.resize(desired); + return _data.data(); } -void model_data::free() -{ - if (_data != nullptr) - { - delete[] _data; - _data = nullptr; - } - _data_sz = 0; -} +void model_data::free() { _data.clear(); } } // namespace model_management } // namespace reinforcement_learning diff --git a/rlclientlib/ranking_event.cc b/rlclientlib/ranking_event.cc index 732cfd439..0b714995d 100644 --- a/rlclientlib/ranking_event.cc +++ b/rlclientlib/ranking_event.cc @@ -30,7 +30,7 @@ float event::prg(int drop_pass) const { const auto seed_str = _seed_id + std::to_string(drop_pass); const auto seed = VW::uniform_hash(seed_str.c_str(), seed_str.length(), 0); - return exploration::uniform_random_merand48(seed); + return VW::details::merand48_noadvance(seed); } ranking_event::ranking_event(const char* event_id, bool deferred_action, float pass_prob, string_view context, diff --git a/rlclientlib/utility/configuration.cc b/rlclientlib/utility/configuration.cc index e918cf37f..6202734c4 100644 --- a/rlclientlib/utility/configuration.cc +++ b/rlclientlib/utility/configuration.cc @@ -8,66 +8,31 @@ namespace reinforcement_learning { namespace utility { -configuration::configuration() : _pmap(new map_type()) {} -configuration::~configuration() { delete _pmap; } - -configuration::configuration(const configuration& other) { _pmap = new map_type(*(other._pmap)); } - -configuration& configuration::operator=(const configuration& rhs) -{ - if (this != &rhs) { _pmap = new map_type(*(rhs._pmap)); } - return *this; -} - -configuration& configuration::operator=(configuration&& temp) noexcept -{ - if (this != &temp) - { - auto& map = *_pmap; - temp._pmap->swap(map); - } - return *this; -} - -configuration::configuration(configuration&& temp) noexcept -{ - _pmap = temp._pmap; - temp._pmap = nullptr; -} - -void configuration::set(const char* name, const char* value) -{ - auto& map = *_pmap; - map[name] = value; -} +void configuration::set(const char* name, const char* value) { _pmap[name] = value; } const char* configuration::get(const char* name, const char* defval) const { - auto& map = *_pmap; - const auto it = map.find(name); - if (it != map.end()) { return it->second.c_str(); } + const auto it = _pmap.find(name); + if (it != _pmap.end()) { return it->second.c_str(); } return defval; } int configuration::get_int(const char* name, const int defval) const { - auto& map = *_pmap; - const auto it = map.find(name); - if (it != map.end()) { return atoi(it->second.c_str()); } + const auto it = _pmap.find(name); + if (it != _pmap.end()) { return atoi(it->second.c_str()); } return defval; } bool configuration::get_bool(const char* name, const bool defval) const { - auto& map = *_pmap; - const auto it = map.find(name); - if (it != map.end()) + const auto it = _pmap.find(name); + if (it != _pmap.end()) { auto sval = it->second; str_util::trim(str_util::to_lower(sval)); if (sval == "true") { return true; } if (sval == "false") { return false; } - return defval; // value string is neither true nor false. return default } return defval; } @@ -84,9 +49,8 @@ bool configuration::get_bool(const char* section, const char* name, bool defval) float configuration::get_float(const char* name, float defval) const { - auto& map = *_pmap; - const auto it = map.find(name); - if (it != map.end()) { return strtof(it->second.c_str(), nullptr); } + const auto it = _pmap.find(name); + if (it != _pmap.end()) { return strtof(it->second.c_str(), nullptr); } return defval; } } // namespace utility @@ -95,7 +59,7 @@ float configuration::get_float(const char* name, float defval) const std::ostream& operator<<(std::ostream& os, const reinforcement_learning::utility::configuration& cc) { os << "{" << std::endl; - for (const auto& v : *(cc._pmap)) { os << " (" << v.first << ", " << v.second << ")" << std::endl; } + for (const auto& v : cc._pmap) { os << " (" << v.first << ", " << v.second << ")" << std::endl; } os << "}" << std::endl; return os; } diff --git a/rlclientlib/utility/object_pool.h b/rlclientlib/utility/object_pool.h index 3e6e548f3..5902739c2 100644 --- a/rlclientlib/utility/object_pool.h +++ b/rlclientlib/utility/object_pool.h @@ -3,12 +3,15 @@ #include #include +#include #include namespace reinforcement_learning { namespace utility { +// Object data type must have a .reset() member function that resets +// the state of the object as if it was newly constructed template class object_pool : public std::enable_shared_from_this> { @@ -19,66 +22,63 @@ class object_pool : public std::enable_shared_from_this> return std::shared_ptr>(new object_pool(initial_size)); } - // Get an object from the pool, or allocate a new object if pool is empty - // The shared_ptr will have a custom deleter that returns the object back into pool + // Get object from pool + // Deleter of shared_ptr will return the object back into the pool std::shared_ptr acquire(); private: // Private constructor because std::enable_shared_from_this requires objects to be inside shared_ptr // Use the factory function to create object_pool - object_pool(size_t initial_size) : _pool(initial_size) {} + object_pool(size_t initial_size) : _pool(initial_size) + { + static_assert(std::is_member_function_pointer::value, + "Object type for object_pool must implement .reset() function"); + } - void return_to_pool(std::unique_ptr); + void release(std::unique_ptr); std::vector> _pool; std::mutex _mutex; - - struct return_to_pool_deleter - { - return_to_pool_deleter(std::weak_ptr> pool) : _pool(pool) {} - - void operator()(Object* pobject) - { - // wrap in unique_ptr so it's destroyed upon exception - std::unique_ptr obj(pobject); - - // if pool is still valid, return the object to pool - if (auto pool = _pool.lock()) - { - try - { - pool->return_to_pool(std::move(obj)); - } - catch (...) - { - } - } - // else pool has been deleted, obj will be destroyed by unique_ptr - } - - std::weak_ptr> _pool; - }; }; template std::shared_ptr object_pool::acquire() { std::lock_guard lock(_mutex); + std::unique_ptr ptr; - if (_pool.empty()) { return std::shared_ptr(new Object(), return_to_pool_deleter(this->shared_from_this())); } + // Get object from pool or create new object + if (_pool.empty()) { ptr.reset(new Object()); } + else + { + ptr = std::move(_pool.back()); + _pool.pop_back(); + ptr->reset(); + } - std::unique_ptr obj = std::move(_pool.back()); - _pool.pop_back(); - obj->reset(); - return std::shared_ptr(obj.release(), return_to_pool_deleter(this->shared_from_this())); + // Wrap in shared_ptr with custom deleter + std::weak_ptr> pool_weak_ptr = this->shared_from_this(); + return std::shared_ptr(ptr.release(), + [pool_weak_ptr](Object* pobject) + { + // Store raw pointer in unique_ptr for exception safety + std::unique_ptr obj(pobject); + if (auto pool_ptr = pool_weak_ptr.lock()) + { + // pool is valid, we can return the object + pool_ptr->release(std::move(obj)); + } + // else couldn't lock weak_ptr because pool has been destroyed already + // obj will be destroyed by unique_ptr + }); } template -void object_pool::return_to_pool(std::unique_ptr obj) +void object_pool::release(std::unique_ptr obj) { std::lock_guard lock(_mutex); _pool.push_back(std::move(obj)); } } // namespace utility -} // namespace reinforcement_learning \ No newline at end of file +} // namespace reinforcement_learning diff --git a/rlclientlib/utility/versioned_object_pool.h b/rlclientlib/utility/versioned_object_pool.h index c0d65ba58..aef5f5e3d 100644 --- a/rlclientlib/utility/versioned_object_pool.h +++ b/rlclientlib/utility/versioned_object_pool.h @@ -18,9 +18,9 @@ class versioned_object_pool_unsafe using TFactory = std::function; int _version; - std::vector _pool; TFactory _factory; int _objects_count; + std::vector> _pool; public: // Construct object pool given a factory function that allocates new objects when called @@ -28,35 +28,25 @@ class versioned_object_pool_unsafe versioned_object_pool_unsafe(TFactory factory, int objects_count = 0, int version = 0) : _version(version), _factory(std::move(factory)), _objects_count(objects_count) { + _pool.reserve(_objects_count); for (int i = 0; i < _objects_count; ++i) { _pool.emplace_back(_factory()); } } + ~versioned_object_pool_unsafe() = default; + versioned_object_pool_unsafe(const versioned_object_pool_unsafe&) = delete; versioned_object_pool_unsafe& operator=(const versioned_object_pool_unsafe& other) = delete; versioned_object_pool_unsafe(versioned_object_pool_unsafe&& other) = delete; - ~versioned_object_pool_unsafe() - { - // delete each pool object - for (auto&& obj : _pool) - { - delete obj; - obj = nullptr; - } - - // clear the pool vector itself - _pool.clear(); - } - // Retreive an object in the pool, or nullptr if pool is empty // The object must be returned to the pool along with its version, or else allocated memory will not be freed TObject* get() { if (_pool.empty()) { return nullptr; } - auto obj = _pool.back(); + auto obj = std::move(_pool.back()); _pool.pop_back(); - return obj; + return obj.release(); } // Create a new object with the pool's factory function @@ -71,12 +61,9 @@ class versioned_object_pool_unsafe // Otherwise, we deallocate it void return_to_pool(TObject* obj, int obj_version) { - if (_version == obj_version) { _pool.emplace_back(obj); } - else - { - delete obj; - obj = nullptr; - } + std::unique_ptr pobj(obj); + if (_version == obj_version) { _pool.push_back(std::move(pobj)); } + // else unique_ptr will delete obj } int size() const { return _objects_count; } diff --git a/rlclientlib/vw_model/safe_vw.cc b/rlclientlib/vw_model/safe_vw.cc index 1cff6dff7..93db5e62b 100644 --- a/rlclientlib/vw_model/safe_vw.cc +++ b/rlclientlib/vw_model/safe_vw.cc @@ -55,7 +55,7 @@ safe_vw::~safe_vw() for (auto&& ex : _example_pool) { VW::dealloc_examples(ex, 1); } // cleanup VW instance - reset_source(*_vw, _vw->num_bits); + VW::details::reset_source(*_vw, _vw->num_bits); VW::finish(*_vw); } @@ -85,7 +85,7 @@ VW::example& safe_vw::get_or_create_example_f(void* vw) { return *(((safe_vw*)vw void safe_vw::parse_context_with_pdf(string_view context, std::vector& actions, std::vector& scores) { - VW::details::decision_service_interaction interaction; + VW::parsers::json::decision_service_interaction interaction; VW::multi_ex examples; examples.push_back(get_or_create_example()); diff --git a/setup.py b/setup.py index cecf1721b..4070247b4 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,6 @@ def build_extension(self, ext): cmake_args += ["-GNinja"] else: - # Single config generators are handled "normally" single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) diff --git a/test_tools/example_gen/example_gen.cc b/test_tools/example_gen/example_gen.cc index f2ec1b70e..fe2c9d6e2 100644 --- a/test_tools/example_gen/example_gen.cc +++ b/test_tools/example_gen/example_gen.cc @@ -2,12 +2,107 @@ #include "config_utility.h" #include "constants.h" #include "live_model.h" +#include "vw/common/random.h" #include #include #include #include #include +#include +#include +#include +#include + +class cb_decision_gen +{ + int shared_features, action_features, actions_per_decision, ft_string_size; + std::vector actions_set; + uint64_t rand_val; + std::string temp_str; + + std::string mk_feature_vector(int count, uint32_t max_idx); + +public: + cb_decision_gen(int shared_features, int action_features, int actions_per_decision, int total_actions, + int initial_seed, int ft_string_size); + + std::string gen_example(); + + uint64_t next_uint(); +}; + +uint64_t cb_decision_gen::next_uint() +{ + VW::details::merand48(rand_val); + return rand_val; +} + +std::string cb_decision_gen::mk_feature_vector(int count, uint32_t max_idx) +{ + std::ostringstream str; + str << "{"; + std::set added_idx; + int added = 0; + while (added < count) + { + auto idx = next_uint() % max_idx; + if (added_idx.find(idx) == added_idx.end()) + { + if (added > 0) { str << ","; } + std::string ft_string(ft_string_size, 'f'); + str << "\"" << idx << "_" << ft_string << "\":1"; + + ++added; + added_idx.insert(idx); + } + } + str << "}"; + return str.str(); +} + +cb_decision_gen::cb_decision_gen(int shared_features, int action_features, int actions_per_decision, int total_actions, + int initial_seed, int ft_string_size = 1) + : shared_features(shared_features) + , action_features(action_features) + , actions_per_decision(actions_per_decision) + , ft_string_size(ft_string_size) + , rand_val(initial_seed) +{ + for (int i = 0; i < total_actions; ++i) + { + actions_set.push_back(mk_feature_vector(action_features, action_features * 3)); + } +} + +std::string cb_decision_gen::gen_example() +{ + std::ostringstream str; + str << R"({"shared":)"; + str << mk_feature_vector(shared_features, shared_features * 3) << ","; + str << R"("_multi":[)"; + std::set added_actions; + int added = 0; + while (added < actions_per_decision) + { + auto idx = next_uint() % actions_set.size(); + if (added_actions.find(idx) == added_actions.end()) + { + if (added > 0) { str << ","; } + added_actions.insert(idx); + + str << R"({"action":)"; + str << actions_set[idx]; + str << "}"; + ++added; + } + } + + str << R"(]})"; + temp_str = str.str(); + + return temp_str; +} namespace r = reinforcement_learning; namespace u = reinforcement_learning::utility; @@ -20,6 +115,9 @@ namespace po = boost::program_options; // global var, yeah ugg bool enable_dedup = false; +bool enable_compress = false; +int num_actions = 0; +int ft_string_size = 0; static const char* options[] = {"cb", "invalid-cb", "ccb", "ccb-with-slot-id", "ccb-baseline", "slates", "ca", "f-reward", "fi-reward", "fi-out-of-bound-reward", "fs-reward", "fmix-reward", "s-reward", "si-reward", "ss-reward", @@ -51,9 +149,11 @@ enum options SLATES_LOOP }; -void load_config_from_json(int action, u::configuration& config, bool enable_apprentice_mode, float epsilon = 0.0f) +void load_config_from_json( + int action, u::configuration& config, bool enable_apprentice_mode, const std::string& dir, float epsilon = 0.0f) { std::string file_name(options[action]); + if (dir != "") { file_name = dir + "/" + file_name; } config.set("ApplicationID", ""); config.set("interaction.sender.implementation", "INTERACTION_FILE_SENDER"); @@ -73,6 +173,9 @@ void load_config_from_json(int action, u::configuration& config, bool enable_app } else { + if (enable_dedup) { file_name += "_dedup"; } + if (enable_compress) { file_name += "_compress"; } + file_name += "_v2.fb"; bool is_observation = action >= F_REWARD; if (is_observation) @@ -89,11 +192,9 @@ void load_config_from_json(int action, u::configuration& config, bool enable_app config.set("protocol.version", "2"); config.set("InitialExplorationEpsilon", "1.0"); - if (enable_dedup) - { - config.set(nm::INTERACTION_USE_DEDUP, "true"); - config.set(nm::INTERACTION_USE_COMPRESSION, "true"); - } + if (enable_dedup) { config.set(nm::INTERACTION_USE_DEDUP, "true"); } + + if (enable_compress) { config.set(nm::INTERACTION_USE_COMPRESSION, "true"); } if (action == CCB_ACTION || action == CCB_BASELINE_ACTION || action == CCB_WITH_SLOT_ID_ACTION || action == CCB_LOOP || action == CCB_BASELINE_ACTION_LOOP) @@ -141,6 +242,11 @@ bool load_config_from_provided_json(const std::string& config_file, u::configura { std::cout << "enabling dedup" << std::endl; config.set(nm::INTERACTION_USE_DEDUP, "true"); + } + + if (enable_compress) + { + std::cout << "enabling compression" << std::endl; config.set(nm::INTERACTION_USE_COMPRESSION, "true"); } @@ -282,7 +388,15 @@ int take_action(r::live_model& rl, const char* event_id, int action, unsigned in case CB_ACTION: { // "cb", r::ranking_response response; - if (rl.choose_rank(event_id, JSON_CB_CONTEXT, action_flag, response, &status) != 0) + std::string example = JSON_CB_CONTEXT; + if (num_actions > 0) + { + auto fss = ft_string_size > 0 ? ft_string_size : 1; + cb_decision_gen cb_gen(50, 50, num_actions, num_actions + 1, 0, fss); + example = cb_gen.gen_example(); + } + + if (rl.choose_rank(event_id, example.c_str(), action_flag, response, &status) != 0) { std::cout << status.get_error_msg() << std::endl; } @@ -680,11 +794,12 @@ int pseudo_random(int seed) } int run_config(int action, int count, int initial_seed, bool gen_random_reward, bool enable_apprentice_mode, - int deferred_action_count, const std::string& config_file, std::mt19937& rng, float epsilon = 0.0f) + int deferred_action_count, const std::string& config_file, std::mt19937& rng, const std::string& dir, + float epsilon = 0.0f) { u::configuration config; - if (config_file.empty()) { load_config_from_json(action, config, enable_apprentice_mode, epsilon); } + if (config_file.empty()) { load_config_from_json(action, config, enable_apprentice_mode, dir, epsilon); } else { if (!load_config_from_provided_json(config_file, config)) { return -1; } @@ -725,11 +840,14 @@ int main(int argc, char* argv[]) bool gen_random_reward = false; bool enable_apprentice_mode = false; int deferred_action_count = 0; + std::string dir = ""; float epsilon = 0.f; - desc.add_options()("help", "Produce help message")("all", "use all args")("dedup", "Enable dedup/zstd")("count", - po::value(), - "Number of events to produce")("seed", po::value(), "Initial seed used to produce event ids")( + desc.add_options()("help", "Produce help message")("all", "use all args")("dedup", "Enable dedup")( + "compress", "Enable zstd")("count", po::value(), "Number of events to produce")("num_actions", + po::value(), "number of actions to use when generating a cb example")("ft_string_size", po::value(), + "to be used with num_actions, determines the size of the feature string when generating a cb example")( + "seed", po::value(), "Initial seed used to produce event ids")( "epsilon", po::value(), "epsilon to be used in command line args for VW")("kind", po::value(), "which kind of example to generate " "(cb,invalid-cb,ccb,ccb-with-slot-id,ccb-baseline,slates,ca,cb-loop,ca-loop,ccb-loop,ccb-baseline-loop,slates-" @@ -738,7 +856,8 @@ int main(int argc, char* argv[]) "json config file for rlclinetlib")("apprentice", "Enable apprentice mode")("deferred_action_count", po::value(), "Number of deferred action for interaction events. Set the deferred_action flag to true for first " - "deferred_action_count number of actions"); + "deferred_action_count number of actions")("dir", po::value(), + "Directory to store the generated examples. If not specified, examples will generated to current directory"); po::positional_options_description pd; pd.add("kind", 1); @@ -752,16 +871,24 @@ int main(int argc, char* argv[]) gen_random_reward = (vm.count("random_reward") != 0u); enable_apprentice_mode = (vm.count("apprentice") != 0u); enable_dedup = (vm.count("dedup") != 0u); + enable_compress = (vm.count("compress") != 0u); std::vector deferrable_interactions{"cb", "invalid-cb", "ccb", "ccb-baseline", "slates", "ca", "cb-loop", "ca-loop", "ccb-with-slot-id", "ccb-loop", "ccb-baseline-loop", "slates-loop"}; if (vm.count("kind") > 0) { action_name = vm["kind"].as(); } if (vm.count("count") > 0) { count = vm["count"].as(); } + if (vm.count("num_actions") > 0) { num_actions = vm["num_actions"].as(); } + if (vm.count("ft_string_size") > 0) + { + if (num_actions == 0) { throw std::runtime_error("num_actions must be set with ft_string_size"); } + ft_string_size = vm["ft_string_size"].as(); + } if (vm.count("seed") > 0) { seed = vm["seed"].as(); } if (vm.count("epsilon") > 0) { epsilon = vm["epsilon"].as(); } if (vm.count("config_file") > 0) { config_file = vm["config_file"].as(); } if (vm.count("deferred_action_count") > 0) { deferred_action_count = vm["deferred_action_count"].as(); } + if (vm.count("dir") > 0) { dir = vm["dir"].as(); } if (vm.count("deferred_action_count") > 0 && !std::any_of(deferrable_interactions.begin(), deferrable_interactions.end(), @@ -791,7 +918,7 @@ int main(int argc, char* argv[]) for (int i = 0; options[i] != nullptr; ++i) { if (run_config(i, count, seed, gen_random_reward, enable_apprentice_mode, deferred_action_count, config_file, rng, - epsilon) != 0) + dir, epsilon) != 0) { return -1; } @@ -816,6 +943,6 @@ int main(int argc, char* argv[]) return -1; } - return run_config( - action, count, seed, gen_random_reward, enable_apprentice_mode, deferred_action_count, config_file, rng, epsilon); + return run_config(action, count, seed, gen_random_reward, enable_apprentice_mode, deferred_action_count, config_file, + rng, dir, epsilon); } diff --git a/test_tools/onnx_pytorch/common/parser.py b/test_tools/onnx_pytorch/common/parser.py index 2739332e1..510364fbe 100644 --- a/test_tools/onnx_pytorch/common/parser.py +++ b/test_tools/onnx_pytorch/common/parser.py @@ -80,6 +80,8 @@ def parse(obj): MSG_TYPE_CHECKPOINT = 0x11111111 MSG_TYPE_REGULAR = 0xFFFFFFFF MSG_TYPE_EOF = 0xAAAAAAAA + + # mostly ripped from the flatbuf parser class JoinedLogStreamReader: def __init__(self, buf): diff --git a/test_tools/reproduce_model.py b/test_tools/reproduce_model.py index 54f0d277a..929668f5e 100644 --- a/test_tools/reproduce_model.py +++ b/test_tools/reproduce_model.py @@ -132,7 +132,6 @@ def replay_logs( def run_test_model_reproducibility( initial_model, final_model, log_files, vw_bins, output_dir ): - if not os.path.exists(output_dir): os.makedirs(output_dir) diff --git a/test_tools/sender_test/test_loop.cc b/test_tools/sender_test/test_loop.cc index 294cd9b30..486a43615 100644 --- a/test_tools/sender_test/test_loop.cc +++ b/test_tools/sender_test/test_loop.cc @@ -43,13 +43,11 @@ bool test_loop::init() config.set(r::name::INTERACTION_EH_TASKS_LIMIT, std::to_string(_threads).c_str()); const auto* const sender_impl = config.get(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_EH_SENDER); - r::i_sender* sender = nullptr; - if (r::sender_factory.create(&sender, sender_impl, config, &error_callback, &status) != r::error_code::success) + if (r::sender_factory.create(_sender, sender_impl, config, &error_callback, &status) != r::error_code::success) { std::cout << status.get_error_msg() << std::endl; return false; } - _sender.reset(sender); if (_sender->init(config, &status) != r::error_code::success) { diff --git a/unit_test/async_batcher_test.cc b/unit_test/async_batcher_test.cc index 3eb5894cb..310b47ad6 100644 --- a/unit_test/async_batcher_test.cc +++ b/unit_test/async_batcher_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -139,7 +138,7 @@ void expect_no_error(const api_status& s, void* cntxt) BOOST_AUTO_TEST_CASE(flush_timeout) { std::vector items; - auto s = new message_sender(items); + std::unique_ptr s(new message_sender(items)); size_t timeout_ms = 100; // set a short timeout error_callback_fn error_fn(expect_no_error, nullptr); utility::watchdog watchdog(nullptr); @@ -148,7 +147,7 @@ BOOST_AUTO_TEST_CASE(flush_timeout) config.send_batch_interval_ms = static_cast(timeout_ms); config.send_queue_max_capacity = 8192; int dummy = 0; - logger::async_batcher batcher(s, watchdog, dummy, &error_fn, config); + logger::async_batcher batcher(std::move(s), watchdog, dummy, &error_fn, config); batcher.init(nullptr); // Allow periodic_background_proc inside async_batcher to start waiting // on a timer before sending any events to it. Else we risk not // triggering the batch mechanism and might get triggered by initial @@ -187,7 +186,7 @@ BOOST_AUTO_TEST_CASE(flush_timeout) BOOST_AUTO_TEST_CASE(flush_batches) { std::vector items; - auto s = new message_sender(items); + std::unique_ptr s(new message_sender(items)); size_t send_high_water_mark = 10; // bytes error_callback_fn error_fn(expect_no_error, nullptr); utility::watchdog watchdog(nullptr); @@ -195,49 +194,58 @@ BOOST_AUTO_TEST_CASE(flush_batches) config.send_high_water_mark = static_cast(send_high_water_mark); config.send_batch_interval_ms = static_cast(100000); int dummy = 0; - auto batcher = new logger::async_batcher(s, watchdog, dummy, &error_fn, config); - batcher->init(nullptr); // Allow periodic_background_proc inside async_batcher to start waiting - // on a timer before sending any events to it. Else we risk not - // triggering the batch mechanism and might get triggered by initial - // pass in do..while loop - std::this_thread::sleep_for(std::chrono::milliseconds(20)); // add 2 items in the current batch - std::string foo("foo"); - std::string bar("bar-yyy"); + std::string expected_batch_0; + std::string expected_batch_1; { - auto foo_evt_sp = std::make_shared(foo); - auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + std::unique_ptr> batcher( + new logger::async_batcher(std::move(s), watchdog, dummy, &error_fn, config)); + + batcher->init(nullptr); // Allow periodic_background_proc inside async_batcher to start waiting + // on a timer before sending any events to it. Else we risk not + // triggering the batch mechanism and might get triggered by initial + // pass in do..while loop + std::this_thread::sleep_for(std::chrono::milliseconds(20)); // add 2 items in the current batch + std::string foo("foo"); + std::string bar("bar-yyy"); + { - out_evt = std::move(*foo_evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), foo_evt_sp.get(), nullptr); - } - { - auto bar_evt_sp = std::make_shared(bar); - auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + auto foo_evt_sp = std::make_shared(foo); + auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*foo_evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), foo_evt_sp.get(), nullptr); + } { - out_evt = std::move(*bar_evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), bar_evt_sp.get(), nullptr); - } - //'send_high_water_mark' will be triggered by previous 2 items. - // next item will be added in a new batch - std::string hello("hello"); - { - auto hello_evt_sp = std::make_shared(hello); - auto evt_fn = [hello_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + auto bar_evt_sp = std::make_shared(bar); + auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*bar_evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), bar_evt_sp.get(), nullptr); + } + //'send_high_water_mark' will be triggered by previous 2 items. + // next item will be added in a new batch + std::string hello("hello"); { - out_evt = std::move(*hello_evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), hello_evt_sp.get(), nullptr); + auto hello_evt_sp = std::make_shared(hello); + auto evt_fn = [hello_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*hello_evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), hello_evt_sp.get(), nullptr); + } + + expected_batch_0 = foo + "\n" + bar + "\n"; + expected_batch_1 = hello + "\n"; } + // unique_ptr goes out of scope + // batcher should be flushed before destruction - const std::string expected_batch_0 = foo + "\n" + bar + "\n"; - const std::string expected_batch_1 = hello + "\n"; - delete batcher; // flush force BOOST_REQUIRE_EQUAL(items.size(), 2); BOOST_CHECK_EQUAL(items[0], expected_batch_0); BOOST_CHECK_EQUAL(items[1], expected_batch_1); @@ -247,36 +255,43 @@ BOOST_AUTO_TEST_CASE(flush_batches) BOOST_AUTO_TEST_CASE(flush_after_deletion) { std::vector items; - auto s = new message_sender(items); + std::unique_ptr s(new message_sender(items)); utility::watchdog watchdog(nullptr); utility::async_batcher_config config; int dummy = 0; - auto* batcher = new logger::async_batcher(s, watchdog, dummy, nullptr, config); - batcher->init(nullptr); // Allow periodic_background_proc to start waiting - std::this_thread::sleep_for(std::chrono::milliseconds(20)); std::string foo("foo"); std::string bar("bar"); + { - auto foo_evt_sp = std::make_shared(foo); - auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + std::unique_ptr> batcher( + new logger::async_batcher(std::move(s), watchdog, dummy, nullptr, config)); + + batcher->init(nullptr); // Allow periodic_background_proc to start waiting + std::this_thread::sleep_for(std::chrono::milliseconds(20)); { - out_evt = std::move(*foo_evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), foo_evt_sp.get(), nullptr); - } - { - auto bar_evt_sp = std::make_shared(bar); - auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + auto foo_evt_sp = std::make_shared(foo); + auto evt_fn = [foo_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*foo_evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), foo_evt_sp.get(), nullptr); + } { - out_evt = std::move(*bar_evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), bar_evt_sp.get(), nullptr); + auto bar_evt_sp = std::make_shared(bar); + auto evt_fn = [bar_evt_sp](test_undroppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*bar_evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), bar_evt_sp.get(), nullptr); + } + // batch was not sent yet + BOOST_CHECK_EQUAL(items.size(), 0); } - // batch was not sent yet - BOOST_CHECK_EQUAL(items.size(), 0); // batch flush is triggered on delete - delete batcher; // check the batch was sent + // unique_ptr goes out of scope + // batch should be sent before destruction + BOOST_REQUIRE_EQUAL(items.size(), 1); std::string expected = foo + "\n" + bar + "\n"; BOOST_CHECK_EQUAL(items[0], expected); @@ -286,7 +301,7 @@ BOOST_AUTO_TEST_CASE(flush_after_deletion) BOOST_AUTO_TEST_CASE(queue_overflow_do_not_drop_event) { std::vector items; - auto s = new message_sender(items); + std::unique_ptr s(new message_sender(items)); size_t timeout_ms = 100; size_t queue_max_size = 3; queue_mode_enum queue_mode = queue_mode_enum::BLOCK; @@ -298,21 +313,26 @@ BOOST_AUTO_TEST_CASE(queue_overflow_do_not_drop_event) config.send_queue_max_capacity = static_cast(queue_max_size); config.queue_mode = queue_mode; int dummy = 0; - auto batcher = new logger::async_batcher(s, watchdog, dummy, &error_fn, config); - batcher->init(nullptr); // Allow periodic_background_proc to start waiting - std::this_thread::sleep_for(std::chrono::milliseconds(20)); int n = 10; - for (int i = 0; i < n; ++i) + { - auto evt_sp = std::make_shared(std::to_string(i)); - auto evt_fn = [evt_sp](test_droppable_event& out_evt, api_status* status) -> int + std::unique_ptr> batcher( + new logger::async_batcher(std::move(s), watchdog, dummy, &error_fn, config)); + + batcher->init(nullptr); // Allow periodic_background_proc to start waiting + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + for (int i = 0; i < n; ++i) { - out_evt = std::move(*evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), evt_sp.get(), nullptr); - } // triggers a final flush - delete batcher; + auto evt_sp = std::make_shared(std::to_string(i)); + auto evt_fn = [evt_sp](test_droppable_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), evt_sp.get(), nullptr); + } + } + // all batches were sent. Check that no event was dropped std::string expected_output = "0\n"; for (int i = 1; i < n; ++i) @@ -329,7 +349,7 @@ BOOST_AUTO_TEST_CASE(queue_overflow_do_not_drop_event) BOOST_AUTO_TEST_CASE(queue_config_drop_rate_test) { std::vector items; - auto s = new message_sender(items); + std::unique_ptr s(new message_sender(items)); size_t timeout_ms = 100; size_t queue_max_size = 10; queue_mode_enum queue_mode = queue_mode_enum::BLOCK; @@ -342,23 +362,27 @@ BOOST_AUTO_TEST_CASE(queue_config_drop_rate_test) config.queue_mode = queue_mode; config.subsample_rate = 0.7f; int dummy = 0; - auto batcher = new logger::async_batcher(s, watchdog, dummy, &error_fn, config); - batcher->init(nullptr); - // Allow periodic_background_proc to start waiting - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - std::vector vs = {"0.00", "1.00", "0.69", "0.70", "0.71"}; - for (int i = 0; i < vs.size(); ++i) { - auto evt_sp = std::make_shared(vs[i]); - auto evt_fn = [evt_sp](config_drop_event& out_evt, api_status* status) -> int + std::unique_ptr> batcher( + new logger::async_batcher(std::move(s), watchdog, dummy, &error_fn, config)); + + batcher->init(nullptr); + // Allow periodic_background_proc to start waiting + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + std::vector vs = {"0.00", "1.00", "0.69", "0.70", "0.71"}; + for (int i = 0; i < vs.size(); ++i) { - out_evt = std::move(*evt_sp); - return error_code::success; - }; - batcher->append(std::move(evt_fn), evt_sp.get(), nullptr); + auto evt_sp = std::make_shared(vs[i]); + auto evt_fn = [evt_sp](config_drop_event& out_evt, api_status* status) -> int + { + out_evt = std::move(*evt_sp); + return error_code::success; + }; + batcher->append(std::move(evt_fn), evt_sp.get(), nullptr); + } } - delete batcher; BOOST_REQUIRE(!items.empty()); BOOST_CHECK_EQUAL(items[0], "0.00\n0.69\n0.70\n"); diff --git a/unit_test/configuration_test.cc b/unit_test/configuration_test.cc index 1d998b7f8..c0ecc82ad 100644 --- a/unit_test/configuration_test.cc +++ b/unit_test/configuration_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -107,7 +106,7 @@ BOOST_AUTO_TEST_CASE(configuration_parse_eventhub_connection_string) BOOST_CHECK_EQUAL(config.get(reinforcement_learning::name::INTERACTION_EH_KEY, "DefaultValue"), ""); } -BOOST_AUTO_TEST_CASE(configuration_parse_interaction_observation_config) +BOOST_AUTO_TEST_CASE(configuration_parse_episode_interaction_observation_config) { const auto json = R"({ "http.api.key": "api_key", @@ -117,6 +116,10 @@ BOOST_AUTO_TEST_CASE(configuration_parse_interaction_observation_config) "observation.http.api.host": "http://localhost:8080", "observation.apim.tasks_limit": 6, "observation.apim.max_http_retries": 4, + "episode.http.api.host": "http://localhost:8080", + "episode.apim.tasks_limit": 7, + "episode.apim.max_http_retries": 5, + "episode.sender.implementation": "EPISODE_HTTP_API_SENDER", "interaction.sender.implementation": "INTERACTION_HTTP_API_SENDER", "observation.sender.implementation": "OBSERVATION_HTTP_API_SENDER" })"; @@ -132,6 +135,12 @@ BOOST_AUTO_TEST_CASE(configuration_parse_interaction_observation_config) config.get(reinforcement_learning::name::OBSERVATION_HTTP_API_HOST, "DefaultValue"), "http://localhost:8080"); BOOST_CHECK_EQUAL(config.get_int(reinforcement_learning::name::OBSERVATION_APIM_TASKS_LIMIT, 0), 6); BOOST_CHECK_EQUAL(config.get_int(reinforcement_learning::name::OBSERVATION_APIM_MAX_HTTP_RETRIES, 0), 4); + BOOST_CHECK_EQUAL( + config.get(reinforcement_learning::name::EPISODE_HTTP_API_HOST, "DefaultValue"), "http://localhost:8080"); + BOOST_CHECK_EQUAL(config.get_int(reinforcement_learning::name::EPISODE_APIM_TASKS_LIMIT, 0), 7); + BOOST_CHECK_EQUAL(config.get_int(reinforcement_learning::name::EPISODE_APIM_MAX_HTTP_RETRIES, 0), 5); + BOOST_CHECK_EQUAL(config.get(reinforcement_learning::name::EPISODE_SENDER_IMPLEMENTATION, "DefaultValue"), + "EPISODE_HTTP_API_SENDER"); BOOST_CHECK_EQUAL(config.get(reinforcement_learning::name::INTERACTION_SENDER_IMPLEMENTATION, "DefaultValue"), "INTERACTION_HTTP_API_SENDER"); BOOST_CHECK_EQUAL(config.get(reinforcement_learning::name::OBSERVATION_SENDER_IMPLEMENTATION, "DefaultValue"), diff --git a/unit_test/data_buffer_test.cc b/unit_test/data_buffer_test.cc index 12a9cad12..aa58236e7 100644 --- a/unit_test/data_buffer_test.cc +++ b/unit_test/data_buffer_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/data_callback_test.cc b/unit_test/data_callback_test.cc index 0022f24b2..d094d4fe1 100644 --- a/unit_test/data_callback_test.cc +++ b/unit_test/data_callback_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/dedup_test.cc b/unit_test/dedup_test.cc index 5ef831efb..057541115 100644 --- a/unit_test/dedup_test.cc +++ b/unit_test/dedup_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/err_callback_test.cc b/unit_test/err_callback_test.cc index 29e3a8be5..3caf087f9 100644 --- a/unit_test/err_callback_test.cc +++ b/unit_test/err_callback_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/event_queue_test.cc b/unit_test/event_queue_test.cc index 180c2f7cc..044577639 100644 --- a/unit_test/event_queue_test.cc +++ b/unit_test/event_queue_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/explore_test.cc b/unit_test/explore_test.cc index 82fe02681..e155861dc 100644 --- a/unit_test/explore_test.cc +++ b/unit_test/explore_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/extensions/onnx/mnist_data/data_generator.py b/unit_test/extensions/onnx/mnist_data/data_generator.py index aa0744520..982f3bd59 100644 --- a/unit_test/extensions/onnx/mnist_data/data_generator.py +++ b/unit_test/extensions/onnx/mnist_data/data_generator.py @@ -1,32 +1,32 @@ -#%% +# %% from mnist.loader import MNIST import base64 import struct -#%% +# %% mndata = MNIST("./data") images, labels = mndata.load_testing() num_images = len(images) print(num_images) -#%% +# %% def map_pixel_float(p): return float(p) / 256.0 -#%% +# %% def image_to_float(image): return map(map_pixel_float, image) -#%% +# %% def image_to_bytes(mnist_image): @@ -36,7 +36,7 @@ def image_to_bytes(mnist_image): return struct.pack("%sf" % pixel_count, *image_float) -#%% +# %% dims = [1, 1, 28, 28] prefix = '"' + base64.b64encode(struct.pack("4Q", *dims)).decode() + ";" @@ -46,7 +46,7 @@ def image_to_tensor_notation(mnist_image): return prefix + base64.b64encode(image_to_bytes(mnist_image)).decode() + '"' -#%% +# %% def encode_mnist(file, images, labels): @@ -59,7 +59,7 @@ def encode_mnist(file, images, labels): file.write("\n") -#%% +# %% tensor_notation_file = open("mnist_test_data.txt", "wt") diff --git a/unit_test/extensions/onnx/mock_helpers.cc b/unit_test/extensions/onnx/mock_helpers.cc index e5aabc2a7..24baa525e 100644 --- a/unit_test/extensions/onnx/mock_helpers.cc +++ b/unit_test/extensions/onnx/mock_helpers.cc @@ -21,17 +21,17 @@ std::unique_ptr get_mock_sender_factory( { auto factory = std::unique_ptr(new r::sender_factory_t()); factory->register_type(r::value::OBSERVATION_EH_SENDER, - [mock_observation_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) + [mock_observation_sender](std::unique_ptr& retval, const u::configuration&, + r::error_callback_fn* error_callback, r::i_trace*, r::api_status*) { - *retval = &mock_observation_sender->get(); + retval.reset(&mock_observation_sender->get()); return r::error_code::success; }); factory->register_type(r::value::INTERACTION_EH_SENDER, - [mock_interaction_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) + [mock_interaction_sender](std::unique_ptr& retval, const u::configuration&, + r::error_callback_fn* error_callback, r::i_trace*, r::api_status*) { - *retval = &mock_interaction_sender->get(); + retval.reset(&mock_interaction_sender->get()); return r::error_code::success; }); return factory; diff --git a/unit_test/factory_test.cc b/unit_test/factory_test.cc index d78c35f00..5f7e827f0 100644 --- a/unit_test/factory_test.cc +++ b/unit_test/factory_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -32,15 +31,17 @@ BOOST_AUTO_TEST_CASE(factory_tempate_usage) auto b = 5; // arbitrary variable to illustrate a point u::object_factory factory; - auto create_A_fn = [](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int + auto create_A_fn = [](std::unique_ptr& pret, const u::configuration&, r::i_trace* trace, + r::api_status*) -> int { - *pret = new impl_A(); + pret.reset(new impl_A()); return r::error_code::success; }; - auto create_B_fn = [b](an_interface** pret, const u::configuration&, r::i_trace* trace, r::api_status*) -> int + auto create_B_fn = [b](std::unique_ptr& pret, const u::configuration&, r::i_trace* trace, + r::api_status*) -> int { - *pret = new impl_B(b); + pret.reset(new impl_B(b)); return r::error_code::success; }; @@ -50,9 +51,8 @@ BOOST_AUTO_TEST_CASE(factory_tempate_usage) u::configuration cc; cc.set(r::name::MODEL_SRC, r::value::AZURE_STORAGE_BLOB); - an_interface* p_impl; - auto scode = factory.create(&p_impl, std::string("A"), cc); + std::unique_ptr p_impl; + auto scode = factory.create(p_impl, std::string("A"), cc); BOOST_CHECK_EQUAL(scode, r::error_code::success); p_impl->do_something(); - delete p_impl; } diff --git a/unit_test/fb_serializer_test.cc b/unit_test/fb_serializer_test.cc index 3eb7a9439..26eeecee1 100644 --- a/unit_test/fb_serializer_test.cc +++ b/unit_test/fb_serializer_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/file_logger_test.cc b/unit_test/file_logger_test.cc index 388384a82..1f0b117c0 100644 --- a/unit_test/file_logger_test.cc +++ b/unit_test/file_logger_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/header_auth_test.cc b/unit_test/header_auth_test.cc index f6cda231a..edaf23766 100644 --- a/unit_test/header_auth_test.cc +++ b/unit_test/header_auth_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/http_client_test.cc b/unit_test/http_client_test.cc index 0a9dff0f8..06b7ff6d8 100644 --- a/unit_test/http_client_test.cc +++ b/unit_test/http_client_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/http_transport_client_test.cc b/unit_test/http_transport_client_test.cc index 43dfa3236..3938c7d09 100644 --- a/unit_test/http_transport_client_test.cc +++ b/unit_test/http_transport_client_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/json_context_parse_test.cc b/unit_test/json_context_parse_test.cc index b159fa832..0d63eb475 100644 --- a/unit_test/json_context_parse_test.cc +++ b/unit_test/json_context_parse_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/json_serializer_test.cc b/unit_test/json_serializer_test.cc index ac462ea6f..ed9514c53 100644 --- a/unit_test/json_serializer_test.cc +++ b/unit_test/json_serializer_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/learning_mode_test.cc b/unit_test/learning_mode_test.cc index d21504649..d92f1e2f3 100644 --- a/unit_test/learning_mode_test.cc +++ b/unit_test/learning_mode_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/live_model_test.cc b/unit_test/live_model_test.cc index 2a127f2c6..83de9cd1a 100644 --- a/unit_test/live_model_test.cc +++ b/unit_test/live_model_test.cc @@ -1,5 +1,4 @@ #include "multi_slot_response.h" -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -426,6 +425,92 @@ BOOST_AUTO_TEST_CASE(live_model_request_decision) BOOST_CHECK_EQUAL(resp1.get_action_id(), 1); } +BOOST_AUTO_TEST_CASE(live_model_ranking_request_check_response_pdf_explore_only) +{ + // create a simple ds configuration + u::configuration config; + cfg::create_from_json(JSON_CFG, config); + config.set(r::name::EH_TEST, "true"); + config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); + config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); + config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); + config.set( + r::name::MODEL_VW_INITIAL_COMMAND_LINE, "--cb_explore_adf --json --quiet --epsilon 0.3 --first_only --id N/A"); + + r::api_status status; + + // create the ds live_model, and initialize it with the config + r::live_model model(config); + + BOOST_CHECK_EQUAL(model.init(&status), err::success); + const auto event_id = "event_id"; + + r::ranking_response response; + + const auto JSON_CB_CONTEXT_3ACTIONS = + R"({"GUser":{"id":"a","major":"eng","hobby":"hiking"},"_multi":[{"TAction":{"a1":"f1"} },{"TAction":{"a2":"f2"}},{"TAction":{"a3":"f3"}}]})"; + + // request ranking + BOOST_CHECK_EQUAL(model.choose_rank(event_id, JSON_CB_CONTEXT_3ACTIONS, response), err::success); + + size_t num_actions = response.size(); + BOOST_CHECK_EQUAL(num_actions, 3); + + const float EXPECTED_PROBS[3] = {0.8f, 0.1f, 0.1f}; + + // check that our PDF is what we expected + r::ranking_response::iterator it = response.begin(); + + for (uint32_t i = 0; i < num_actions; i++) + { + auto action_probability = *(it + i); + BOOST_CHECK_CLOSE(action_probability.probability, EXPECTED_PROBS[action_probability.action_id], FLOAT_TOL); + } +} + +BOOST_AUTO_TEST_CASE(live_model_ranking_w_las_request_check_response_pdf_explore_only) +{ + // create a simple ds configuration + u::configuration config; + cfg::create_from_json(JSON_CFG, config); + config.set(r::name::EH_TEST, "true"); + config.set(r::name::MODEL_SRC, r::value::NO_MODEL_DATA); + config.set(r::name::OBSERVATION_SENDER_IMPLEMENTATION, r::value::OBSERVATION_FILE_SENDER); + config.set(r::name::INTERACTION_SENDER_IMPLEMENTATION, r::value::INTERACTION_FILE_SENDER); + config.set(r::name::MODEL_VW_INITIAL_COMMAND_LINE, + "--cb_explore_adf --json --quiet --epsilon 0.3 --first_only --id N/A --large_action_space --max_actions 2"); + + r::api_status status; + + // create the ds live_model, and initialize it with the config + r::live_model model(config); + + BOOST_CHECK_EQUAL(model.init(&status), err::success); + const auto event_id = "event_id"; + + r::ranking_response response; + + const auto JSON_CB_CONTEXT_3ACTIONS = + R"({"GUser":{"id":"a","major":"eng","hobby":"hiking"},"_multi":[{"TAction":{"a1":"f1"} },{"TAction":{"a2":"f2"}},{"TAction":{"a3":"f3"}}]})"; + + // request ranking + BOOST_CHECK_EQUAL(model.choose_rank(event_id, JSON_CB_CONTEXT_3ACTIONS, response), err::success); + + size_t num_actions = response.size(); + BOOST_CHECK_EQUAL(num_actions, 3); + + const float EXPECTED_PROBS[3] = {0.85f, 0.0f, 0.15f}; + + // check that our PDF is what we expected + r::ranking_response::iterator it = response.begin(); + + for (uint32_t i = 0; i < num_actions; i++) + { + auto action_probability = *(it + i); + BOOST_CHECK_CLOSE(action_probability.probability, EXPECTED_PROBS[action_probability.action_id], FLOAT_TOL); + } +} + BOOST_AUTO_TEST_CASE(live_model_ranking_request_pdf_passthrough) { // create a simple ds configuration @@ -1859,8 +1944,6 @@ BOOST_AUTO_TEST_CASE(live_model_using_endpoint_failure_no_uri) BOOST_CHECK_EQUAL(_rl->init(&status), r::error_code::http_model_uri_not_provided); } -#ifdef _WIN32 -# ifdef USE_AZURE_FACTORIES BOOST_AUTO_TEST_CASE(live_model_using_endpoint_success) { u::configuration config; @@ -1885,5 +1968,3 @@ BOOST_AUTO_TEST_CASE(live_model_using_endpoint_failure_no_apikey) BOOST_CHECK_EQUAL(_rl->init(&status), r::error_code::http_api_key_not_provided); } -# endif -#endif diff --git a/unit_test/local_loop_end_to_end.cc b/unit_test/local_loop_end_to_end.cc index 74a1c4018..edf48afc1 100644 --- a/unit_test/local_loop_end_to_end.cc +++ b/unit_test/local_loop_end_to_end.cc @@ -100,13 +100,13 @@ BOOST_AUTO_TEST_CASE(local_loop_end_to_end_test) local_loop_controller* test_local_loop_controller = nullptr; data_transport_factory_t test_data_transport_factory; test_data_transport_factory.register_type(value::LOCAL_LOOP_MODEL_DATA, - [&](model_management::i_data_transport** retval, const utility::configuration& cfg, i_trace* trace_logger, - api_status* status) + [&](std::unique_ptr& retval, const utility::configuration& cfg, + i_trace* trace_logger, api_status* status) { std::unique_ptr output; RETURN_IF_FAIL(local_loop_controller::create(output, cfg, trace_logger, status)); - test_local_loop_controller = output.release(); - *retval = static_cast(test_local_loop_controller); + test_local_loop_controller = output.get(); + retval = std::move(output); return error_code::success; }); diff --git a/unit_test/mock_util.cc b/unit_test/mock_util.cc index 53a204a2c..5b7295455 100644 --- a/unit_test/mock_util.cc +++ b/unit_test/mock_util.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #include "mock_util.h" @@ -69,10 +68,17 @@ std::unique_ptr> get_mock_model(m::model_type_t model_t { auto mock = std::unique_ptr>(new fakeit::Mock()); - const auto choose_rank_fn = [](const char*, uint64_t, r::string_view, std::vector&, std::vector&, - std::string& model_version, r::api_status*) + const auto choose_rank_fn = [](const char*, uint64_t, r::string_view, std::vector& actions, + std::vector& scores, std::string& model_version, r::api_status*) { model_version = "model_id"; + actions.resize(2); + scores.resize(2); + for (size_t i = 0; i < 2; ++i) + { + actions[i] = i; + scores[i] = 0.5f; + } return r::error_code::success; }; @@ -127,17 +133,17 @@ std::unique_ptr get_mock_sender_factory( { auto factory = std::unique_ptr(new r::sender_factory_t()); factory->register_type(r::value::get_default_observation_sender(), - [mock_observation_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) + [mock_observation_sender](std::unique_ptr& retval, const u::configuration&, + r::error_callback_fn* error_callback, r::i_trace*, r::api_status*) { - *retval = &mock_observation_sender->get(); + retval.reset(&mock_observation_sender->get()); return r::error_code::success; }); factory->register_type(r::value::get_default_interaction_sender(), - [mock_interaction_sender](r::i_sender** retval, const u::configuration&, r::error_callback_fn* error_callback, - r::i_trace*, r::api_status*) + [mock_interaction_sender](std::unique_ptr& retval, const u::configuration&, + r::error_callback_fn* error_callback, r::i_trace*, r::api_status*) { - *retval = &mock_interaction_sender->get(); + retval.reset(&mock_interaction_sender->get()); return r::error_code::success; }); return factory; @@ -148,9 +154,10 @@ std::unique_ptr get_mock_data_transport_factory( { auto factory = std::unique_ptr(new r::data_transport_factory_t()); factory->register_type(r::value::get_default_data_transport(), - [mock_data_transport](m::i_data_transport** retval, const u::configuration&, r::i_trace* trace, r::api_status*) + [mock_data_transport]( + std::unique_ptr& retval, const u::configuration&, r::i_trace* trace, r::api_status*) { - *retval = &mock_data_transport->get(); + retval.reset(&mock_data_transport->get()); return r::error_code::success; }); return factory; @@ -160,9 +167,9 @@ std::unique_ptr get_mock_model_factory(fakeit::Mock(new r::model_factory_t()); factory->register_type(r::value::VW, - [mock_model](m::i_model** retval, const u::configuration&, r::i_trace* trace, r::api_status*) + [mock_model](std::unique_ptr& retval, const u::configuration&, r::i_trace* trace, r::api_status*) { - *retval = &mock_model->get(); + retval.reset(&mock_model->get()); return r::error_code::success; }); return factory; diff --git a/unit_test/model_mgmt_test.cc b/unit_test/model_mgmt_test.cc index 3516e792a..b581f000f 100644 --- a/unit_test/model_mgmt_test.cc +++ b/unit_test/model_mgmt_test.cc @@ -1,5 +1,4 @@ -#define BOOST_TEST_DYN_LINK -#ifdef STAND_ALONE +#ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -169,14 +168,13 @@ BOOST_AUTO_TEST_CASE(data_transport_user_extention) register_local_file_factory(); const u::configuration cc; - m::i_data_transport* data_transport; - auto scode = r::data_transport_factory.create(&data_transport, DUMMY_DATA_TRANSPORT, cc); + std::unique_ptr data_transport; + auto scode = r::data_transport_factory.create(data_transport, DUMMY_DATA_TRANSPORT, cc); BOOST_CHECK_EQUAL(scode, r::error_code::success); m::model_data md; scode = data_transport->get_data(md); BOOST_CHECK_EQUAL(scode, r::error_code::success); md.free(); - delete data_transport; } BOOST_AUTO_TEST_CASE(vw_model_factory) @@ -185,18 +183,16 @@ BOOST_AUTO_TEST_CASE(vw_model_factory) u::configuration model_cc; model_cc.set(r::name::VW_CMDLINE, "--lda 5"); - m::i_model* vw; - const auto scode = r::model_factory.create(&vw, r::value::VW, model_cc); + std::unique_ptr vw; + const auto scode = r::model_factory.create(vw, r::value::VW, model_cc); BOOST_CHECK_EQUAL(scode, r::error_code::success); - delete vw; } m::model_data get_model_data() { const u::configuration cc; - m::i_data_transport* data_transport; - r::data_transport_factory.create(&data_transport, DUMMY_DATA_TRANSPORT, cc); - std::unique_ptr pdt(data_transport); + std::unique_ptr pdt; + r::data_transport_factory.create(pdt, DUMMY_DATA_TRANSPORT, cc); m::model_data md; const auto scode = pdt->get_data(md); BOOST_CHECK_EQUAL(scode, r::error_code::success); @@ -212,10 +208,10 @@ class dummy_data_transport : public m::i_data_transport } }; -int dummy_data_tranport_create( - m::i_data_transport** retval, const u::configuration& config, r::i_trace* trace, r::api_status* status) +int dummy_data_tranport_create(std::unique_ptr& retval, const u::configuration& config, + r::i_trace* trace, r::api_status* status) { - *retval = new dummy_data_transport(); + retval.reset(new dummy_data_transport()); return r::error_code::success; } @@ -229,24 +225,21 @@ BOOST_AUTO_TEST_CASE(vw_model_type) register_local_file_factory(); u::configuration model_cc; - m::i_model* vw; + std::unique_ptr vw; model_cc.set(r::name::VW_CMDLINE, "--cb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A"); - BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(&vw, r::value::VW, model_cc)); + BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(vw, r::value::VW, model_cc)); BOOST_CHECK_EQUAL((int)m::model_type_t::CB, (int)vw->model_type()); - delete vw; model_cc.set(r::name::VW_CMDLINE, "--ccb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A"); model_cc.set( r::name::MODEL_VW_INITIAL_COMMAND_LINE, "--ccb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A"); - BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(&vw, r::value::VW, model_cc)); + BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(vw, r::value::VW, model_cc)); BOOST_CHECK_EQUAL((int)m::model_type_t::CCB, (int)vw->model_type()); - delete vw; model_cc.set(r::name::VW_CMDLINE, "--slates --ccb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A"); model_cc.set(r::name::MODEL_VW_INITIAL_COMMAND_LINE, "--slates --ccb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A"); - BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(&vw, r::value::VW, model_cc)); + BOOST_CHECK_EQUAL(r::error_code::success, r::model_factory.create(vw, r::value::VW, model_cc)); BOOST_CHECK_EQUAL((int)m::model_type_t::SLATES, (int)vw->model_type()); - delete vw; } diff --git a/unit_test/moving_queue_test.cc b/unit_test/moving_queue_test.cc index 1e1fe7cce..d4fa682e7 100644 --- a/unit_test/moving_queue_test.cc +++ b/unit_test/moving_queue_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/multi_slot_response_detailed_test.cc b/unit_test/multi_slot_response_detailed_test.cc index 002d775e7..f2420aeb3 100644 --- a/unit_test/multi_slot_response_detailed_test.cc +++ b/unit_test/multi_slot_response_detailed_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/multistep_test.cc b/unit_test/multistep_test.cc index 2b38a210e..da7020b62 100644 --- a/unit_test/multistep_test.cc +++ b/unit_test/multistep_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/object_pool_test.cc b/unit_test/object_pool_test.cc index 1bb38f416..07e7d801c 100644 --- a/unit_test/object_pool_test.cc +++ b/unit_test/object_pool_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/payload_serializer_test.cc b/unit_test/payload_serializer_test.cc index cc2e830ad..92f2d825c 100644 --- a/unit_test/payload_serializer_test.cc +++ b/unit_test/payload_serializer_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/preamble_test.cc b/unit_test/preamble_test.cc index 9fc7afe43..a63884c3c 100644 --- a/unit_test/preamble_test.cc +++ b/unit_test/preamble_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -31,8 +30,9 @@ struct dummy_sender : i_sender BOOST_AUTO_TEST_CASE(simple_preamble_usage) { std::shared_ptr db(new data_buffer()); - dummy_sender* raw_data = new dummy_sender(); - preamble_message_sender f_sender(raw_data); + dummy_sender* raw_dummy_sender = new dummy_sender(); + std::unique_ptr unique_dummy_sender(raw_dummy_sender); + preamble_message_sender f_sender(std::move(unique_dummy_sender)); i_message_sender& sender = f_sender; db->set_body_endoffset(db->preamble_size() + db->body_capacity()); const auto send_msg_sz = db->body_filled_size(); @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(simple_preamble_usage) sender.send(send_msg_type, db); preamble pre; - pre.read_from_bytes(raw_data->v_data->preamble_begin(), raw_data->v_data->preamble_size()); + pre.read_from_bytes(raw_dummy_sender->v_data->preamble_begin(), raw_dummy_sender->v_data->preamble_size()); BOOST_CHECK_EQUAL(pre.msg_size, send_msg_sz); BOOST_CHECK_EQUAL(pre.msg_type, send_msg_type); diff --git a/unit_test/ranking_response_test.cc b/unit_test/ranking_response_test.cc index 473594f43..0f8c67ceb 100644 --- a/unit_test/ranking_response_test.cc +++ b/unit_test/ranking_response_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/safe_vw_test.cc b/unit_test/safe_vw_test.cc index e13f0208e..705a3534e 100644 --- a/unit_test/safe_vw_test.cc +++ b/unit_test/safe_vw_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/serializer.cc b/unit_test/serializer.cc index 2a85f14f9..f17da06ee 100644 --- a/unit_test/serializer.cc +++ b/unit_test/serializer.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/sleeper_test.cc b/unit_test/sleeper_test.cc index 136f8389f..1a9590413 100644 --- a/unit_test/sleeper_test.cc +++ b/unit_test/sleeper_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/slot_ranking_test.cc b/unit_test/slot_ranking_test.cc index 76fea6da8..fe7eea73e 100644 --- a/unit_test/slot_ranking_test.cc +++ b/unit_test/slot_ranking_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/status_builder_test.cc b/unit_test/status_builder_test.cc index 84915429f..1c7d66edc 100644 --- a/unit_test/status_builder_test.cc +++ b/unit_test/status_builder_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/str_util_test.cc b/unit_test/str_util_test.cc index 31d247af8..0bcd29abb 100644 --- a/unit_test/str_util_test.cc +++ b/unit_test/str_util_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/time_tests.cc b/unit_test/time_tests.cc index 800b7cc47..963702ece 100644 --- a/unit_test/time_tests.cc +++ b/unit_test/time_tests.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif diff --git a/unit_test/trace_logger_test.cc b/unit_test/trace_logger_test.cc index 74b26e436..65b3923e3 100644 --- a/unit_test/trace_logger_test.cc +++ b/unit_test/trace_logger_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif @@ -44,6 +43,8 @@ const auto JSON_CFG = R"( )"; const auto JSON_CONTEXT = R"({"_multi":[{},{}]})"; +std::vector tracer_data; + struct vector_tracer : r::i_trace { void log(int log_level, const std::string& msg) override @@ -51,14 +52,14 @@ struct vector_tracer : r::i_trace std::unique_lock mlock(mutex); data.emplace_back(msg); } - std::vector data; + std::vector& data = tracer_data; std::mutex mutex; }; -vector_tracer* the_tracer = new vector_tracer(); -int vector_trace_create(r::i_trace** retval, const u::configuration&, r::i_trace* trace_logger, r::api_status* status) +int vector_trace_create( + std::unique_ptr& retval, const u::configuration&, r::i_trace* trace_logger, r::api_status* status) { - *retval = the_tracer; + retval.reset(new vector_tracer()); return err::success; } @@ -86,7 +87,7 @@ BOOST_AUTO_TEST_CASE(test_trace_logging) r::live_model ds(config, nullptr, nullptr, &r::trace_logger_factory, data_transport_factory.get(), model_factory.get(), logger_factory.get()); BOOST_CHECK_EQUAL(ds.init(&status), err::success); - BOOST_CHECK_EQUAL(the_tracer->data[0], "API Tracing initialized"); + BOOST_CHECK_EQUAL(tracer_data[0], "API Tracing initialized"); } BOOST_AUTO_TEST_CASE(test_console_logging) diff --git a/unit_test/watchdog_test.cc b/unit_test/watchdog_test.cc index 7b4a97ae2..1a6368a3e 100644 --- a/unit_test/watchdog_test.cc +++ b/unit_test/watchdog_test.cc @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #ifdef STAND_ALONE # define BOOST_TEST_MODULE Main #endif From 60d3f4e06d9e68d9f6a24e35727944986b3a23d1 Mon Sep 17 00:00:00 2001 From: Griffin Bassman Date: Wed, 5 Apr 2023 13:57:08 -0400 Subject: [PATCH 15/16] clang --- unit_test/local_client_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unit_test/local_client_test.cc b/unit_test/local_client_test.cc index 68121a478..1357bd79e 100644 --- a/unit_test/local_client_test.cc +++ b/unit_test/local_client_test.cc @@ -24,8 +24,7 @@ VW::multi_ex parse_json(VW::workspace& all, const std::string& line) VW::multi_ex examples; examples.push_back(VW::new_unused_example(all)); VW::example_factory_t ex_fac = [&all]() -> VW::example& { return *(VW::new_unused_example(all)); }; - VW::parsers::json::read_line_json( - all, examples, (char*)line.c_str(), line.length(), ex_fac); + VW::parsers::json::read_line_json(all, examples, (char*)line.c_str(), line.length(), ex_fac); VW::setup_examples(all, examples); return examples; } From f699c6349b364353e5347ab68e873be1b40a5448 Mon Sep 17 00:00:00 2001 From: Griffin Bassman Date: Mon, 10 Apr 2023 13:18:15 -0400 Subject: [PATCH 16/16] feat: add id and counter (#580) * feat: add id and counter * clang * user id field * clang * rm move * cl;ang * try catch * tidy * add id to config * fix test --- external_parser/joiners/example_joiner.cc | 12 ++++-------- rlclientlib/federation/local_client.cc | 22 ++++++++++++++++++++-- unit_test/local_client_test.cc | 2 ++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/external_parser/joiners/example_joiner.cc b/external_parser/joiners/example_joiner.cc index e19fef6b0..a9f3eef64 100644 --- a/external_parser/joiners/example_joiner.cc +++ b/external_parser/joiners/example_joiner.cc @@ -578,19 +578,15 @@ void example_joiner::persist_metrics(VW::metric_sink& metrics) if (!_joiner_metrics.first_event_id.empty()) { - metrics.set_string("first_event_id", std::move(_joiner_metrics.first_event_id), true); + metrics.set_string("first_event_id", _joiner_metrics.first_event_id, true); metrics.set_string("first_event_time", - std::move( - date::format("%FT%TZ", date::floor(_joiner_metrics.first_event_timestamp))), - true); + date::format("%FT%TZ", date::floor(_joiner_metrics.first_event_timestamp)), true); } if (!_joiner_metrics.last_event_id.empty()) { - metrics.set_string("last_event_id", std::move(_joiner_metrics.last_event_id), true); + metrics.set_string("last_event_id", _joiner_metrics.last_event_id, true); metrics.set_string("last_event_time", - std::move( - date::format("%FT%TZ", date::floor(_joiner_metrics.last_event_timestamp))), - true); + date::format("%FT%TZ", date::floor(_joiner_metrics.last_event_timestamp)), true); } } } diff --git a/rlclientlib/federation/local_client.cc b/rlclientlib/federation/local_client.cc index e3b4ae1f1..9dd26b30b 100644 --- a/rlclientlib/federation/local_client.cc +++ b/rlclientlib/federation/local_client.cc @@ -29,6 +29,8 @@ int local_client::try_get_model(const std::string& app_id, { case state_t::model_available: { + std::size_t pos = _current_model->id.find('/'); + assert(app_id == _current_model->id.substr(0, _current_model->id.find('/'))); io_buf buf; auto backing_buffer = std::make_shared>(); buf.add_file(VW::io::create_vector_writer(backing_buffer)); @@ -70,6 +72,20 @@ int local_client::report_result(const uint8_t* payload, size_t size, api_status* auto view = VW::io::create_buffer_view(reinterpret_cast(payload), size); auto delta = VW::model_delta::deserialize(*view); auto new_model = *_current_model + *delta; + + // Increment iteration id for new workspace + try + { + int iteration_id = std::stoi(_current_model->id.substr(_current_model->id.find('/') + 1, std::string::npos)); + iteration_id++; + new_model->id = _current_model->id.substr(0, _current_model->id.find('/')) + "/" + std::to_string(iteration_id); + } + catch (const std::exception& e) + { + RETURN_ERROR_ARG(_trace_logger, status, model_update_error, e.what()); + } + + // Update current model _current_model.reset(new_model.release()); _state = state_t::model_available; } @@ -83,14 +99,16 @@ int local_client::report_result(const uint8_t* payload, size_t size, api_status* int local_client::create(std::unique_ptr& output, const utility::configuration& config, i_trace* trace_logger, api_status* status) { + std::string cmd_line = "--cb_explore_adf --json --quiet --epsilon 0.0 --first_only --id "; + cmd_line += config.get("id", "default_id"); // Create empty model based on ML args on first call - std::string initial_command_line(config.get( - name::MODEL_VW_INITIAL_COMMAND_LINE, "--cb_explore_adf --json --quiet --epsilon 0.0 --first_only --id N/A")); + std::string initial_command_line(config.get(name::MODEL_VW_INITIAL_COMMAND_LINE, cmd_line.c_str())); // TODO try catch auto args = VW::make_unique(VW::split_command_line(initial_command_line)); auto logger = utility::make_vw_trace_logger(trace_logger); auto workspace = VW::initialize_experimental(std::move(args), nullptr, nullptr, nullptr, &logger); + workspace->id += "/0"; // initialize iteration id to 0 output = std::unique_ptr(new local_client(std::move(workspace), trace_logger)); return error_code::success; diff --git a/unit_test/local_client_test.cc b/unit_test/local_client_test.cc index 1357bd79e..b99a46538 100644 --- a/unit_test/local_client_test.cc +++ b/unit_test/local_client_test.cc @@ -32,6 +32,7 @@ VW::multi_ex parse_json(VW::workspace& all, const std::string& line) BOOST_AUTO_TEST_CASE(get_model_twice_fails) { utility::configuration config; + config.set("id", "test_app_id"); std::unique_ptr client; BOOST_CHECK_EQUAL(local_client::create(client, config, nullptr, nullptr), error_code::success); model_management::model_data data; @@ -45,6 +46,7 @@ BOOST_AUTO_TEST_CASE(get_model_twice_fails) BOOST_AUTO_TEST_CASE(send_delta_update) { utility::configuration config; + config.set("id", "test_app_id"); std::unique_ptr client; BOOST_CHECK_EQUAL(local_client::create(client, config, nullptr, nullptr), error_code::success); BOOST_CHECK_NE(client.get(), nullptr);