From 91f72a138a2378422d0451372dc06782f00dc7a5 Mon Sep 17 00:00:00 2001 From: pockerman Date: Sun, 1 Dec 2024 11:59:03 +0000 Subject: [PATCH 1/5] Add example for monte carlo tree search (#150) --- README.md | 2 +- examples/rl/CMakeLists.txt | 3 +- examples/rl/example_16/CMakeLists.txt | 16 -- examples/rl/example_16/example_16.cpp | 252 -------------------------- 4 files changed, 3 insertions(+), 270 deletions(-) delete mode 100644 examples/rl/example_16/CMakeLists.txt delete mode 100755 examples/rl/example_16/example_16.cpp diff --git a/README.md b/README.md index ff36554..5acfe0f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The following is an indicative list of examples. - REINFORCE algorithm on ```CartPole-v0``` - Expected SARSA on ```CliffWalking-v0``` - Approximate Monte Carlo on ```MountainCar-v0``` -- Monte Carlo tree search on ```Taxi-v3``` +- Monte Carlo tree search on ```Taxi-v3``` - Double Q-learning on ```CartPole-v0``` - First visit Monte Carlo on ```FrozenLake-v0``` diff --git a/examples/rl/CMakeLists.txt b/examples/rl/CMakeLists.txt index c14c231..84d0391 100644 --- a/examples/rl/CMakeLists.txt +++ b/examples/rl/CMakeLists.txt @@ -12,6 +12,7 @@ ADD_SUBDIRECTORY(rl_example_12) ADD_SUBDIRECTORY(rl_example_13) ADD_SUBDIRECTORY(rl_example_14) ADD_SUBDIRECTORY(rl_example_15) +ADD_SUBDIRECTORY(rl_example_16) ADD_SUBDIRECTORY(rl_example_19) @@ -20,7 +21,7 @@ ADD_SUBDIRECTORY(rl_example_19) #ADD_SUBDIRECTORY(rl_example_18) #ADD_SUBDIRECTORY(example_15) -#ADD_SUBDIRECTORY(example_16) + #ADD_SUBDIRECTORY(example_17) # #ADD_SUBDIRECTORY(example_21) diff --git a/examples/rl/example_16/CMakeLists.txt b/examples/rl/example_16/CMakeLists.txt deleted file mode 100644 index 010eccb..0000000 --- a/examples/rl/example_16/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 3.6 FATAL_ERROR) - -SET(EXECUTABLE example_16) -SET(SOURCE ${EXECUTABLE}.cpp) - -ADD_EXECUTABLE(${EXECUTABLE} ${SOURCE}) - -TARGET_LINK_LIBRARIES(${EXECUTABLE} cubeailib) -TARGET_LINK_LIBRARIES(${EXECUTABLE} ${TORCH_LIBRARIES}) -TARGET_LINK_LIBRARIES(${EXECUTABLE} pthread) -TARGET_LINK_LIBRARIES(${EXECUTABLE} openblas) -TARGET_LINK_LIBRARIES(${EXECUTABLE} gymfcpplib) -TARGET_LINK_LIBRARIES(${EXECUTABLE} python3.8) -TARGET_LINK_LIBRARIES(${EXECUTABLE} boost_python38) -TARGET_LINK_LIBRARIES(${EXECUTABLE} boost_system) -TARGET_LINK_LIBRARIES(${EXECUTABLE} boost_numpy38) diff --git a/examples/rl/example_16/example_16.cpp b/examples/rl/example_16/example_16.cpp deleted file mode 100755 index e1abc35..0000000 --- a/examples/rl/example_16/example_16.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Simple implementation of Monte Carlo tree serach - * algorithm on the Taxi environment. The code below is a translation of - * the Python code in - * https://github.com/ashishrana160796/prototyping-self-driving-agents/blob/master/milestone-four/monte_carlo_tree_search_taxi_v3.ipynb - * - */ - - -#include "cubeai/base/cubeai_types.h" -#include "cubeai/rl/algorithms/mc/mc_tree_search_base.h" -#include "cubeai/utils/array_utils.h" - -#include "gymfcpp/gymfcpp_types.h" -#include "gymfcpp/taxi_env.h" -#include "gymfcpp/time_step.h" -#include "gymfcpp/gymfcpp_consts.h" - -#include - -#include -#include -#include -#include -#include -#include - - -namespace example{ - -using cubeai::real_t; -using cubeai::uint_t; -using cubeai::rl::algos::MCTreeSearchBase; -using cubeai::rl::algos::MCTreeSearchConfig; - -using gymfcpp::Taxi; - -typedef Taxi::time_step_type time_step_type; - - -const real_t GAMMA = 1.0; -const uint_t N_EPISODES = 20000; -const uint_t N_ITRS_PER_EPISODE = 2000; -const real_t TOL = 1.0e-8; - -/** - * Implementation of Monte Carlo tree serach for the OpenAI-Gym - * environment. - */ -template -class TaxiMCTreeSearch: public MCTreeSearchBase -{ -public: - - typedef typename Env::time_step_type time_step_type; - typedef typename MCTreeSearchBase::env_type env_type; - typedef typename MCTreeSearchBase::node_type node_type; - - // - TaxiMCTreeSearch(MCTreeSearchConfig config, Env& env); - - - time_step_type simulate_node(std::shared_ptr node, env_type& env) override final; - void on_episode() override final; - void expand_node(std::shared_ptr node, env_type& env) override final; - void backprop(std::shared_ptr node) override final; - -private: - - // the reward the agent receives for every - // training iteration within an episode - real_t sum_reward_; - - // - std::vector best_actions_; - std::vector best_rewards_; - std::vector actions_; - - -}; - -template -TaxiMCTreeSearch::TaxiMCTreeSearch(MCTreeSearchConfig config, Env& env) - : - MCTreeSearchBase(config, env), - sum_reward_(0.0), - best_actions_(), - best_rewards_() -{} - - -template -typename TaxiMCTreeSearch::time_step_type -TaxiMCTreeSearch::simulate_node(std::shared_ptr node, env_type& env){ - - auto time_step = TaxiMCTreeSearch::time_step_type(); - while(node -> has_children()){ - - if(node -> n_explored_children() < node -> n_children() ){ - - auto child = node->get_child(node->n_explored_children()); - node ->update_explored_children(); - node = child; - } - else{ - - node = node -> max_ucb_child(this->temperature_); - - } - - time_step = env.step(node ->get_action()); - sum_reward_ += time_step.reward(); - actions_.push_back(node ->get_action()); - } - - return time_step; -} - -template -void -TaxiMCTreeSearch::on_episode(){ - - auto best_reward = std::numeric_limits::min(); - - std::map names; - - names[gymfcpp::PY_ENV_NAME] = this->env_.py_env_name + "_" + std::to_string(this->current_episode_idx()); - names[gymfcpp::PY_RESET_ENV_RESULT_NAME] = this->env_.py_reset_result_name + "_" + std::to_string(this->current_episode_idx()); - names[gymfcpp::PY_STEP_ENV_RESULT_NAME] = this->env_.py_step_result_name + "_" + std::to_string(this->current_episode_idx()); - names[gymfcpp::PY_STATE_NAME] = this->env_.py_state_name + "_" + std::to_string(this->current_episode_idx()); - - // simulate the environment - auto env_copy = this->env_.copy(std::move(names)); - - while(this->itr_mix_.continue_iterations()){ - - sum_reward_ = 0.0; - auto terminal = false; - actions_.clear(); - - - auto node = this->root_; - - auto time_step = this->simulate_node(node, env_copy); - - terminal = time_step.done(); - - if(!terminal){ - // expand the node if this is not - // terminal - this->expand_node(node, env_copy); - } - - // creating exhaustive list of actions - while(!terminal){ - auto action = env_copy.sample_action(); - auto new_time_step = env_copy.step(action); - sum_reward_ += new_time_step.reward(); - actions_.push_back(action); - - if(actions_.size() > this->max_depth_tree()){ - sum_reward_ -= 100; - break; - } - } - - // do some book keeping retaining the best reward value and actions - if( best_reward < sum_reward_){ - best_reward = sum_reward_; - best_actions_ = actions_; - } - - // backpropagating in MCTS for assigning reward value to a node. - this->backprop(node); - } - - // step in the best actions - sum_reward_ = 0.; - for(auto action : best_actions_){ - auto time_step = this->env_.step(action); - - sum_reward_ += time_step.reward(); - if(time_step.done()){ - break; - } - } - - - best_rewards_.push_back(sum_reward_); -} - -template -void -TaxiMCTreeSearch::expand_node(std::shared_ptr node, env_type& env){ - - for(uint_t a=0; a < env.n_actions(); ++a ){ - node -> add_child(node, a); - } - - node->shuffle_children(); - -} - -template -void -TaxiMCTreeSearch::backprop(std::shared_ptr node){ - - while(node){ - - node -> update_visits(); - node -> update_total_score(sum_reward_); - node = node ->parent(); - } - -} - -} - - -int main(){ - - using namespace example; - - try{ - - Py_Initialize(); - auto main_module = boost::python::import("__main__"); - auto main_namespace = main_module.attr("__dict__"); - - Taxi env("v0", main_namespace, false); - env.make(); - - MCTreeSearchConfig config; - config.max_tree_depth = 512; - config.temperature = 1.0; - TaxiMCTreeSearch agent(config, env); - agent.train(); - - } - catch(const boost::python::error_already_set&) - { - PyErr_Print(); - } - catch(std::exception& e){ - std::cout< Date: Sun, 1 Dec 2024 11:59:24 +0000 Subject: [PATCH 2/5] Add example for monte carlo tree search (#150) --- examples/rl/rl_example_16/CMakeLists.txt | 19 ++ examples/rl/rl_example_16/rl_example_16.cpp | 252 ++++++++++++++++++++ examples/rl/rl_example_16/rl_example_16.md | 10 + 3 files changed, 281 insertions(+) create mode 100644 examples/rl/rl_example_16/CMakeLists.txt create mode 100755 examples/rl/rl_example_16/rl_example_16.cpp create mode 100644 examples/rl/rl_example_16/rl_example_16.md diff --git a/examples/rl/rl_example_16/CMakeLists.txt b/examples/rl/rl_example_16/CMakeLists.txt new file mode 100644 index 0000000..ee2e4ca --- /dev/null +++ b/examples/rl/rl_example_16/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.6 FATAL_ERROR) + +SET(EXECUTABLE rl_example_16) +SET(SOURCE ${EXECUTABLE}.cpp) + +ADD_EXECUTABLE(${EXECUTABLE} ${SOURCE}) + +TARGET_LINK_LIBRARIES(${EXECUTABLE} cubeailib) + +IF( USE_PYTORCH ) + TARGET_LINK_LIBRARIES(${EXECUTABLE} ${TORCH_LIBRARIES}) +ENDIF() + +TARGET_LINK_LIBRARIES(${EXECUTABLE} pthread) +TARGET_LINK_LIBRARIES(${EXECUTABLE} openblas) +TARGET_LINK_LIBRARIES(${EXECUTABLE} rlenvscpplib) +target_link_libraries(${EXECUTABLE} tbb) +target_link_libraries(${EXECUTABLE} boost_log) + diff --git a/examples/rl/rl_example_16/rl_example_16.cpp b/examples/rl/rl_example_16/rl_example_16.cpp new file mode 100755 index 0000000..e1abc35 --- /dev/null +++ b/examples/rl/rl_example_16/rl_example_16.cpp @@ -0,0 +1,252 @@ +/** + * Simple implementation of Monte Carlo tree serach + * algorithm on the Taxi environment. The code below is a translation of + * the Python code in + * https://github.com/ashishrana160796/prototyping-self-driving-agents/blob/master/milestone-four/monte_carlo_tree_search_taxi_v3.ipynb + * + */ + + +#include "cubeai/base/cubeai_types.h" +#include "cubeai/rl/algorithms/mc/mc_tree_search_base.h" +#include "cubeai/utils/array_utils.h" + +#include "gymfcpp/gymfcpp_types.h" +#include "gymfcpp/taxi_env.h" +#include "gymfcpp/time_step.h" +#include "gymfcpp/gymfcpp_consts.h" + +#include + +#include +#include +#include +#include +#include +#include + + +namespace example{ + +using cubeai::real_t; +using cubeai::uint_t; +using cubeai::rl::algos::MCTreeSearchBase; +using cubeai::rl::algos::MCTreeSearchConfig; + +using gymfcpp::Taxi; + +typedef Taxi::time_step_type time_step_type; + + +const real_t GAMMA = 1.0; +const uint_t N_EPISODES = 20000; +const uint_t N_ITRS_PER_EPISODE = 2000; +const real_t TOL = 1.0e-8; + +/** + * Implementation of Monte Carlo tree serach for the OpenAI-Gym + * environment. + */ +template +class TaxiMCTreeSearch: public MCTreeSearchBase +{ +public: + + typedef typename Env::time_step_type time_step_type; + typedef typename MCTreeSearchBase::env_type env_type; + typedef typename MCTreeSearchBase::node_type node_type; + + // + TaxiMCTreeSearch(MCTreeSearchConfig config, Env& env); + + + time_step_type simulate_node(std::shared_ptr node, env_type& env) override final; + void on_episode() override final; + void expand_node(std::shared_ptr node, env_type& env) override final; + void backprop(std::shared_ptr node) override final; + +private: + + // the reward the agent receives for every + // training iteration within an episode + real_t sum_reward_; + + // + std::vector best_actions_; + std::vector best_rewards_; + std::vector actions_; + + +}; + +template +TaxiMCTreeSearch::TaxiMCTreeSearch(MCTreeSearchConfig config, Env& env) + : + MCTreeSearchBase(config, env), + sum_reward_(0.0), + best_actions_(), + best_rewards_() +{} + + +template +typename TaxiMCTreeSearch::time_step_type +TaxiMCTreeSearch::simulate_node(std::shared_ptr node, env_type& env){ + + auto time_step = TaxiMCTreeSearch::time_step_type(); + while(node -> has_children()){ + + if(node -> n_explored_children() < node -> n_children() ){ + + auto child = node->get_child(node->n_explored_children()); + node ->update_explored_children(); + node = child; + } + else{ + + node = node -> max_ucb_child(this->temperature_); + + } + + time_step = env.step(node ->get_action()); + sum_reward_ += time_step.reward(); + actions_.push_back(node ->get_action()); + } + + return time_step; +} + +template +void +TaxiMCTreeSearch::on_episode(){ + + auto best_reward = std::numeric_limits::min(); + + std::map names; + + names[gymfcpp::PY_ENV_NAME] = this->env_.py_env_name + "_" + std::to_string(this->current_episode_idx()); + names[gymfcpp::PY_RESET_ENV_RESULT_NAME] = this->env_.py_reset_result_name + "_" + std::to_string(this->current_episode_idx()); + names[gymfcpp::PY_STEP_ENV_RESULT_NAME] = this->env_.py_step_result_name + "_" + std::to_string(this->current_episode_idx()); + names[gymfcpp::PY_STATE_NAME] = this->env_.py_state_name + "_" + std::to_string(this->current_episode_idx()); + + // simulate the environment + auto env_copy = this->env_.copy(std::move(names)); + + while(this->itr_mix_.continue_iterations()){ + + sum_reward_ = 0.0; + auto terminal = false; + actions_.clear(); + + + auto node = this->root_; + + auto time_step = this->simulate_node(node, env_copy); + + terminal = time_step.done(); + + if(!terminal){ + // expand the node if this is not + // terminal + this->expand_node(node, env_copy); + } + + // creating exhaustive list of actions + while(!terminal){ + auto action = env_copy.sample_action(); + auto new_time_step = env_copy.step(action); + sum_reward_ += new_time_step.reward(); + actions_.push_back(action); + + if(actions_.size() > this->max_depth_tree()){ + sum_reward_ -= 100; + break; + } + } + + // do some book keeping retaining the best reward value and actions + if( best_reward < sum_reward_){ + best_reward = sum_reward_; + best_actions_ = actions_; + } + + // backpropagating in MCTS for assigning reward value to a node. + this->backprop(node); + } + + // step in the best actions + sum_reward_ = 0.; + for(auto action : best_actions_){ + auto time_step = this->env_.step(action); + + sum_reward_ += time_step.reward(); + if(time_step.done()){ + break; + } + } + + + best_rewards_.push_back(sum_reward_); +} + +template +void +TaxiMCTreeSearch::expand_node(std::shared_ptr node, env_type& env){ + + for(uint_t a=0; a < env.n_actions(); ++a ){ + node -> add_child(node, a); + } + + node->shuffle_children(); + +} + +template +void +TaxiMCTreeSearch::backprop(std::shared_ptr node){ + + while(node){ + + node -> update_visits(); + node -> update_total_score(sum_reward_); + node = node ->parent(); + } + +} + +} + + +int main(){ + + using namespace example; + + try{ + + Py_Initialize(); + auto main_module = boost::python::import("__main__"); + auto main_namespace = main_module.attr("__dict__"); + + Taxi env("v0", main_namespace, false); + env.make(); + + MCTreeSearchConfig config; + config.max_tree_depth = 512; + config.temperature = 1.0; + TaxiMCTreeSearch agent(config, env); + agent.train(); + + } + catch(const boost::python::error_already_set&) + { + PyErr_Print(); + } + catch(std::exception& e){ + std::cout< Date: Wed, 25 Dec 2024 21:02:37 +0000 Subject: [PATCH 3/5] Refactor examples for new rlenvscpp namespace (#150) --- examples/rl/rl_example_10/rl_example_10.cpp | 4 +- examples/rl/rl_example_11/rl_example_11.cpp | 3 +- examples/rl/rl_example_12/rl_example_12.cpp | 2 +- examples/rl/rl_example_13/rl_example_13.cpp | 2 +- examples/rl/rl_example_15/rl_example_15.cpp | 2 +- examples/rl/rl_example_16/rl_example_16.cpp | 29 +- examples/rl/rl_example_16/rl_example_16.md | 74 +++- examples/rl/rl_example_19/rl_example_19.cpp | 4 +- examples/rl/rl_example_6/rl_example_6.cpp | 2 +- examples/rl/rl_example_7/rl_example_7.cpp | 2 +- examples/rl/rl_example_8/rl_example_8.cpp | 2 +- examples/rl/rl_example_9/rl_example_9.cpp | 4 +- .../rl/algorithms/mc/mc_tree_search_base.h | 398 ------------------ 13 files changed, 92 insertions(+), 436 deletions(-) delete mode 100644 include/cubeai/rl/algorithms/mc/mc_tree_search_base.h diff --git a/examples/rl/rl_example_10/rl_example_10.cpp b/examples/rl/rl_example_10/rl_example_10.cpp index de91511..a19f5ba 100755 --- a/examples/rl/rl_example_10/rl_example_10.cpp +++ b/examples/rl/rl_example_10/rl_example_10.cpp @@ -25,8 +25,8 @@ using cubeai::rl::algos::td::QLearningConfig; using cubeai::rl::policies::EpsilonDecayOption; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::CliffWorldActionsEnum; -typedef rlenvs_cpp::envs::gymnasium::CliffWorld env_type; + +typedef rlenvscpp::envs::gymnasium::CliffWorld env_type; } diff --git a/examples/rl/rl_example_11/rl_example_11.cpp b/examples/rl/rl_example_11/rl_example_11.cpp index 329f86a..37d39d6 100755 --- a/examples/rl/rl_example_11/rl_example_11.cpp +++ b/examples/rl/rl_example_11/rl_example_11.cpp @@ -28,7 +28,6 @@ using cubeai::rl::algos::ac::A2CConfig; using cubeai::rl::algos::ac::A2CSolver; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::CartPoleActionsEnum; // create the Action and the Critic networks @@ -132,7 +131,7 @@ CriticNetImpl::forward(torch_tensor_t state){ TORCH_MODULE(ActorNet); TORCH_MODULE(CriticNet); -typedef rlenvs_cpp::envs::gymnasium::CartPole env_type; +typedef rlenvscpp::envs::gymnasium::CartPole env_type; } diff --git a/examples/rl/rl_example_12/rl_example_12.cpp b/examples/rl/rl_example_12/rl_example_12.cpp index 63e1986..ad2e530 100755 --- a/examples/rl/rl_example_12/rl_example_12.cpp +++ b/examples/rl/rl_example_12/rl_example_12.cpp @@ -41,7 +41,7 @@ using cubeai::torch_tensor_t; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; using cubeai::rl::policies::EpsilonGreedyPolicy; -using rlenvs_cpp::envs::grid_world::Gridworld; +using rlenvscpp::envs::grid_world::Gridworld; const uint_t l1 = 64; const uint_t l2 = 150; diff --git a/examples/rl/rl_example_13/rl_example_13.cpp b/examples/rl/rl_example_13/rl_example_13.cpp index 76f4ca7..38ed6b6 100755 --- a/examples/rl/rl_example_13/rl_example_13.cpp +++ b/examples/rl/rl_example_13/rl_example_13.cpp @@ -43,7 +43,7 @@ using cubeai::rl::algos::pg::ReinforceConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; using cubeai::maths::stats::TorchCategorical; -using rlenvs_cpp::envs::gymnasium::CartPole; +using rlenvs cpp::envs::gymnasium::CartPole; const uint_t L1 = 4; diff --git a/examples/rl/rl_example_15/rl_example_15.cpp b/examples/rl/rl_example_15/rl_example_15.cpp index ea7360e..39bcb7e 100755 --- a/examples/rl/rl_example_15/rl_example_15.cpp +++ b/examples/rl/rl_example_15/rl_example_15.cpp @@ -42,7 +42,7 @@ using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; using cubeai::rl::policies::EpsilonGreedyPolicy; using cubeai::containers::ExperienceBuffer; -using rlenvs_cpp::envs::grid_world::Gridworld; +using rlenvscpp::envs::grid_world::Gridworld; const std::string EXPERIMENT_ID = "1"; diff --git a/examples/rl/rl_example_16/rl_example_16.cpp b/examples/rl/rl_example_16/rl_example_16.cpp index e1abc35..4c47412 100755 --- a/examples/rl/rl_example_16/rl_example_16.cpp +++ b/examples/rl/rl_example_16/rl_example_16.cpp @@ -11,12 +11,7 @@ #include "cubeai/rl/algorithms/mc/mc_tree_search_base.h" #include "cubeai/utils/array_utils.h" -#include "gymfcpp/gymfcpp_types.h" -#include "gymfcpp/taxi_env.h" -#include "gymfcpp/time_step.h" -#include "gymfcpp/gymfcpp_consts.h" - -#include +#include "rlenvs/envs/connect2/connect2_env.h" #include #include @@ -33,7 +28,7 @@ using cubeai::uint_t; using cubeai::rl::algos::MCTreeSearchBase; using cubeai::rl::algos::MCTreeSearchConfig; -using gymfcpp::Taxi; +using rlenvs_cpp::envs::connect2::Connect2; typedef Taxi::time_step_type time_step_type; @@ -116,6 +111,7 @@ TaxiMCTreeSearch::simulate_node(std::shared_ptr node, env_type& return time_step; } +/* template void TaxiMCTreeSearch::on_episode(){ @@ -188,6 +184,8 @@ TaxiMCTreeSearch::on_episode(){ best_rewards_.push_back(sum_reward_); } +*/ + template void @@ -223,23 +221,8 @@ int main(){ try{ - Py_Initialize(); - auto main_module = boost::python::import("__main__"); - auto main_namespace = main_module.attr("__dict__"); - - Taxi env("v0", main_namespace, false); - env.make(); + - MCTreeSearchConfig config; - config.max_tree_depth = 512; - config.temperature = 1.0; - TaxiMCTreeSearch agent(config, env); - agent.train(); - - } - catch(const boost::python::error_already_set&) - { - PyErr_Print(); } catch(std::exception& e){ std::cout<Monte Carlo tree search algorithm +or MCTS for short. +---- +**Remark** + +A concise presentation of the algorithm can be found in the following video: https://www.youtube.com/watch?v=UXW2yZndl7U + +The following videos from Udacity's Reinforcement Learning also explain +the MCTS algorithm + +1. Monte Carlo Tree Search p1 +2. Monte Carlo Tree Search p2 +3. Monte Carlo Tree Search p3 +4. Monte Carlo Tree Search p4 + + +In this tutorial we will be applying MCTS in a toy environment namely Connect2. The following video is a nice discussion +on the application of MCTS on Connect2: Alpha Zero and Monte Carlo Tree Search. + +---- + +Overall, the vanilla MCTS algorithm has four steps + +1. Tree tranversal +2. Node expansion +3. Rollout or random simulation +4. Backward propagation + +Let's go over each one of these steps. + +### Tree traversal + +At this step the agent select the root node from which it will recursively select the best paths. +This is done until a leaf node is reached. The selction of a node is typically done using the +UCB (or upper confidence bound) criterion +but you can use any criterion that is suitable for your application but make sure that the criterion you are using +balances exploration and exploitation. The UCB criterion is as follows + + + +Where $N$ is the number of node visits +The UCB criterion limits the extent that each each branch will be explored. +This is done because as $N$ increases beacuse we visit the node more and more, +the overall factor continuously decreases thus the expected reward from the current branch will also decrease. + +A new node is created when we execute the rollout step. + + + +### Rollout step + +The MCTS starts with an empty tree data structure and iteratively will build a portion of the tree by running a number of simulations +Each simulation will add a single node to the tree. Since a simulation is the exploration step, the more simualtions we run, the beter +we expect the model to perform. However, simulating indefinitetly is not possible for obvious reasons. + +The MCTS algorithm implies that somehow we have a way of doing the simulation step. One way is if we know the transition dynamics of the +environment or being able to sample from the environment. + +### Backward propagation + +During the backward step we update the statistics for every node in the search path. These statistics affect the UCB score and will +guide the next MCTS simulation. + +Now that we have gone over the detaisl of the MCTS algorithm let's try to answer some practical questions. Fo example when does the algorithm stop? +There are various ways to address this question and the most common one is to simulate up until a specific number of iterations. + +All in all, the MCTS algorithm is summarised below. + +In this ## Driver code +## References + + diff --git a/examples/rl/rl_example_19/rl_example_19.cpp b/examples/rl/rl_example_19/rl_example_19.cpp index 08ffdcd..20dbd6d 100755 --- a/examples/rl/rl_example_19/rl_example_19.cpp +++ b/examples/rl/rl_example_19/rl_example_19.cpp @@ -29,8 +29,8 @@ using cubeai::rl::algos::mc::FirstVisitMCSolver; using cubeai::rl::algos::mc::FirstVisitMCSolverConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::FrozenLake; -using rlenvs_cpp::envs::gymnasium::FrozenLakeActionsEnum; +using rlenvscpp::envs::gymnasium::FrozenLake; +using rlenvscpp::envs::gymnasium::FrozenLakeActionsEnum; const std::string SERVER_URL = "http://0.0.0.0:8001/api"; const uint_t SEED = 42; diff --git a/examples/rl/rl_example_6/rl_example_6.cpp b/examples/rl/rl_example_6/rl_example_6.cpp index a8a0263..8ba542e 100755 --- a/examples/rl/rl_example_6/rl_example_6.cpp +++ b/examples/rl/rl_example_6/rl_example_6.cpp @@ -25,7 +25,7 @@ using cubeai::rl::algos::dp::IterativePolicyEvalutationSolver; using cubeai::rl::algos::dp::IterativePolicyEvalConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::FrozenLake; +using rlenvscpp::envs::gymnasium::FrozenLake; typedef FrozenLake<4> env_type; } diff --git a/examples/rl/rl_example_7/rl_example_7.cpp b/examples/rl/rl_example_7/rl_example_7.cpp index a084c01..1c45b77 100755 --- a/examples/rl/rl_example_7/rl_example_7.cpp +++ b/examples/rl/rl_example_7/rl_example_7.cpp @@ -30,7 +30,7 @@ using cubeai::rl::algos::dp::PolicyIterationSolver; using cubeai::rl::algos::dp::PolicyIterationConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::FrozenLake; +using rlenvscpp::envs::gymnasium::FrozenLake; typedef FrozenLake<4> env_type; } diff --git a/examples/rl/rl_example_8/rl_example_8.cpp b/examples/rl/rl_example_8/rl_example_8.cpp index d58b983..3657825 100755 --- a/examples/rl/rl_example_8/rl_example_8.cpp +++ b/examples/rl/rl_example_8/rl_example_8.cpp @@ -23,7 +23,7 @@ using cubeai::rl::algos::dp::ValueIterationConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; -using rlenvs_cpp::envs::gymnasium::FrozenLake; +using rlenvscpp::envs::gymnasium::FrozenLake; typedef FrozenLake<4> env_type; typedef ValueIteration -#endif - -#include -#include -#include -#include - -namespace cubeai{ -namespace rl{ -namespace algos{ - - -/// -/// \brief The MCTreeSearchConfig struct -/// -struct MCTreeSearchConfig: public RLAlgoConfig -{ - uint_t max_tree_depth = 1000; - real_t temperature = 1.0; - -}; - -template -class MCTreeNodeBase -{ -public: - - typedef ActionTp action_type; - typedef StateTp state_type; - - /// - /// \brief MCTreeNodeBase - /// \param parent - /// \param action - /// - MCTreeNodeBase(std::shared_ptr parent, action_type action); - - /// - /// - /// - virtual ~MCTreeNodeBase()=default; - - /// - /// \brief add_child - /// \param child - /// - void add_child(std::shared_ptr> child); - - /// - /// \brief add_child - /// \param parent - /// \param action - /// - void add_child(std::shared_ptr> parent, action_type action); - - /// - /// \brief has_children - /// \return - /// - bool has_children()const noexcept{return children_.empty() != true;} - - /// - /// \brief get_child - /// \param cidx - /// \return - /// - std::shared_ptr get_child(uint_t cidx){return children_[cidx];} - - /// - /// \brief n_children - /// \return - /// - uint_t n_children()const noexcept{return children_.size();} - - /// - /// \brief shuffle_children - /// - void shuffle_children()noexcept; - - /// - /// \brief explored_children - /// \return - /// - uint_t n_explored_children()const noexcept{return explored_children_;} - - /// - /// \brief update_visits - /// - void update_visits()noexcept{total_visits_ += 1;} - - /// - /// - /// - void update_explored_children()noexcept{explored_children_ += 1;} - - /// - /// \brief update_total_score - /// \param score - /// - void update_total_score(real_t score)noexcept {total_score_ += score;} - - /// - /// \brief ucb - /// \param temperature - /// \return - /// - real_t ucb(real_t temperature )const; - - /// - /// \brief max_ucb_child - /// \return - /// - std::shared_ptr max_ucb_child(real_t temperature)const; - - /// - /// \brief win_pct - /// \return - /// - real_t win_pct()const{return total_score_ / total_visits_ ;} - - /// - /// \brief total_visits - /// \return - /// - uint_t total_visits()const noexcept{return total_visits_;} - - /// - /// \brief get_action - /// \return - /// - uint_t get_action()const noexcept{return action_;} - - /// - /// \brief parent - /// \return - /// - std::shared_ptr parent(){return parent_;} - -protected: - - - - /// - /// \brief total_score_ - /// - real_t total_score_; - - /// - /// \brief total_visits_ - /// - uint_t total_visits_; - - /// - /// \brief explored_children_ - /// - uint_t explored_children_; - - /// - /// \brief action_ - /// - action_type action_; - - /// - /// \brief parent_ - /// - std::shared_ptr parent_; - - /// - /// \brief children_ - /// - std::vector> children_; - -}; - -template -MCTreeNodeBase::MCTreeNodeBase(std::shared_ptr parent, action_type action) - : - total_score_(0.0), - total_visits_(0), - explored_children_(0), - action_(action), - parent_(parent), - children_() -{} - - -template -void -MCTreeNodeBase::add_child(std::shared_ptr> child){ - -#ifdef CUBEAI_DEBUG - assert(child != nullptr && "Cannot add null children"); -#endif - - children_.push_back(child); - -} - -template -void -MCTreeNodeBase::shuffle_children()noexcept{ - - if(children_.empty()){ - return; - } - - std::random_shuffle(children_.begin(), children_.end()); -} - -template -void -MCTreeNodeBase::add_child(std::shared_ptr> parent, action_type action){ - - add_child(std::make_shared>(parent, action)); -} - -template -std::shared_ptr> -MCTreeNodeBase::max_ucb_child(real_t temperature)const{ - - if(children_.empty()){ - return std::shared_ptr(); - } - - auto current_ucb = children_[0]->ucb(temperature); - auto current_idx = 0; - auto dummy_idx = 0; - for(auto node : children_){ - - auto node_ucb = node->ucb(temperature); - - if(node_ucb > current_ucb){ - - current_idx = dummy_idx; - current_ucb = node_ucb; - } - - dummy_idx += 1; - } - - return children_[current_idx]; -} - -template -real_t -MCTreeNodeBase::ucb(real_t temperature )const{ - - return win_pct() + temperature * std::sqrt(std::log(parent_ -> total_visits()) / total_visits_); -} - -/// -/// \brief MCTreeSearchBase. -/// -template> -class MCTreeSearchBase: public AlgorithmBase -{ -public: - - static_assert (std::is_default_constructible::value, "Action type is not default constructible"); - static_assert (std::is_default_constructible::value, "State type is not default constructible"); - - /// - /// \brief env_type - /// - typedef EnvTp env_type; - - /// - /// \brief node_type - /// - typedef NodeTp node_type; - - /// - /// \brief actions_before_training_episodes. Execute any actions the - /// algorithm needs before starting the iterations - /// - virtual void actions_before_training_episodes() override; - - /// - /// \brief actions_after_training_episodes. Actions to execute after - /// the training iterations have finisehd - /// - virtual void actions_after_training_episodes() override; - - /// - /// \brief step Do one step of the algorithm - /// - virtual void on_episode() = 0; - - /// - /// \brief simulate_node - /// \param node - /// - virtual typename env_type::time_step_type simulate_node(std::shared_ptr node, env_type& env)=0; - - /// - /// \brief expand_node - /// \param node - /// - virtual void expand_node(std::shared_ptr node, env_type& env)=0; - - /// - /// \brief backprop - /// - virtual void backprop(std::shared_ptr node)=0; - - /// - /// \brief max_depth_tree - /// \return - /// - uint_t max_depth_tree()const noexcept{return max_depth_tree_;} - -protected: - - /// - /// \brief MCTreeSearchBase - /// \param config - /// - MCTreeSearchBase(MCTreeSearchConfig config, env_type& env); - - /// - /// \brief itr_mix_ - /// - IterationMixin itr_mix_; - - /// - /// \brief env_ - /// - env_type& env_; - - /// - /// \brief root_ - /// - std::shared_ptr root_; - - /// - /// \brief max_depth_tree_ - /// - uint_t max_depth_tree_; - - /// - /// \brief temperature_ - /// - real_t temperature_; - -}; - -template -MCTreeSearchBase::MCTreeSearchBase(MCTreeSearchConfig config, env_type& env) - : - AlgorithmBase(config.n_episodes, config.tolerance), - itr_mix_(config.n_itrs_per_episode, config.render_episode), - env_(env), - root_(nullptr), - max_depth_tree_(config.max_tree_depth), - temperature_(config.temperature) -{} - -template -void -MCTreeSearchBase::actions_after_training_episodes(){ - - //this->AlgorithmBase::actions_after_training_episodes(); -} - -template -void -MCTreeSearchBase::actions_before_training_episodes(){ - //this->AlgorithmBase::actions_before_training_episodes(); -} - -template -void -MCTreeSearchBase::on_episode(){ - - while(itr_mix_.continue_iterations()){ - - - } -} - - -} -} -} - -#endif // MC_TREE_SEARCH_BASE_H From 8e93b58c08ac505ea24fd670e02cf653f45af4ea Mon Sep 17 00:00:00 2001 From: pockerman Date: Thu, 26 Dec 2024 11:18:40 +0000 Subject: [PATCH 4/5] Add extended Kalman filter example (#150) --- .../filtering_example_2/CMakeLists.txt | 18 + .../filtering_example_2.cpp | 222 ++++++++++++ .../filtering_example_2.md | 328 ++++++++++++++++++ .../filtering_example_2/orientation.png | Bin 0 -> 33890 bytes .../filtering/filtering_example_2/plot.py | 65 ++++ .../filtering/filtering_example_2/state.csv | 302 ++++++++++++++++ .../filtering_example_2/x_coordinate.png | Bin 0 -> 48682 bytes .../filtering_example_2/y_coordinate.png | Bin 0 -> 47423 bytes .../estimation/extended_kalman_filter.h | 324 +++++++++++++++++ 9 files changed, 1259 insertions(+) create mode 100644 examples/filtering/filtering_example_2/CMakeLists.txt create mode 100755 examples/filtering/filtering_example_2/filtering_example_2.cpp create mode 100644 examples/filtering/filtering_example_2/filtering_example_2.md create mode 100644 examples/filtering/filtering_example_2/orientation.png create mode 100755 examples/filtering/filtering_example_2/plot.py create mode 100644 examples/filtering/filtering_example_2/state.csv create mode 100644 examples/filtering/filtering_example_2/x_coordinate.png create mode 100644 examples/filtering/filtering_example_2/y_coordinate.png create mode 100644 include/cubeai/estimation/extended_kalman_filter.h diff --git a/examples/filtering/filtering_example_2/CMakeLists.txt b/examples/filtering/filtering_example_2/CMakeLists.txt new file mode 100644 index 0000000..c09580f --- /dev/null +++ b/examples/filtering/filtering_example_2/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.6 FATAL_ERROR) + +SET(EXECUTABLE filtering_example_2) +SET(SOURCE ${EXECUTABLE}.cpp) + +ADD_EXECUTABLE(${EXECUTABLE} ${SOURCE}) + +TARGET_LINK_LIBRARIES(${EXECUTABLE} cubeailib) + +IF( USE_PYTORCH ) + TARGET_LINK_LIBRARIES(${EXECUTABLE} ${TORCH_LIBRARIES}) +ENDIF() + +TARGET_LINK_LIBRARIES(${EXECUTABLE} pthread) +TARGET_LINK_LIBRARIES(${EXECUTABLE} openblas) +TARGET_LINK_LIBRARIES(${EXECUTABLE} rlenvscpplib) +target_link_libraries(${EXECUTABLE} tbb) +target_link_libraries(${EXECUTABLE} boost_log) diff --git a/examples/filtering/filtering_example_2/filtering_example_2.cpp b/examples/filtering/filtering_example_2/filtering_example_2.cpp new file mode 100755 index 0000000..eee917d --- /dev/null +++ b/examples/filtering/filtering_example_2/filtering_example_2.cpp @@ -0,0 +1,222 @@ +#include "cubeai/base/cubeai_types.h" +#include "cubeai/estimation/extended_kalman_filter.h" +#include "cubeai/utils/iteration_counter.h" +#include "cubeai/io/csv_file_writer.h" +#include "rlenvs/dynamics/diff_drive_dynamics.h" +#include "rlenvs/dynamics/system_state.h" +#include "rlenvs/utils/unit_converter.h" + +#include +#include +#include +#include +#include +#include + +namespace example_2 +{ + +using cubeai::real_t; +using cubeai::uint_t; +using cubeai::DynMat; +using cubeai::DynVec; +using cubeai::estimation::ExtendedKalmanFilter; +using cubeai::utils::IterationCounter; +using cubeai::io::CSVWriter; +using rlenvscpp::dynamics::DiffDriveDynamics; +using rlenvscpp::dynamics::SysState; + + +class ObservationModel +{ + +public: + + // the ExtendedKalmanFilter expects an exposed + // input_type + typedef DynVec input_type; + + ObservationModel(); + + // simply return the state + const DynVec evaluate(const DynVec& input)const; + + // get the H or M matrix + const DynMat& get_matrix(const std::string& name)const; + +private: + + DynMat H; + DynMat M; +}; + +ObservationModel::ObservationModel() + : + H(2, 3), + M(2, 2) +{ + H = DynMat::Zero(2,3); + M = DynMat::Zero(2,2); + H(0, 0) = 1.0; + H(1,1) = 1.0; + M(0,0) = 1.0; + M(1, 1) = 1.0; + +} + +const DynVec +ObservationModel::evaluate(const DynVec& input)const{ + return input; +} + +const DynMat& +ObservationModel::get_matrix(const std::string& name)const{ + if(name == "H"){ + return H; + } + else if(name == "M"){ + return M; + } + + throw std::logic_error("Invalid matrix name. Name "+name+ " not found"); +} + +const DynVec get_measurement(const SysState<3>& state){ + + DynVec result(2); + result[0] = state.get("X"); + result[1] = state.get("Y"); + return result; +} + + +} + +int main() { + + using namespace example_2; + + BOOST_LOG_TRIVIAL(info)<<"Starting example..."; + + uint_t n_steps = 300; + auto time = 0.0; + auto dt = 0.5; + + /// angular velocity + auto w = 0.0; + + /// linear velocity + auto vt = 1.0; + + std::array motion_control_error; + motion_control_error[0] = 0.0; + motion_control_error[1] = 0.0; + + DiffDriveDynamics exact_motion_model; + exact_motion_model.set_matrix_update_flag(false); + exact_motion_model.set_time_step(dt); + + DiffDriveDynamics motion_model; + motion_model.set_time_step(dt); + + std::map input; + input["v"] = 1.0; + input["w"] = 0.0; + input["errors"] = motion_control_error; + motion_model.initialize_matrices(input); + + ObservationModel observation; + + ExtendedKalmanFilter ekf(motion_model, observation); + + DynMat R = DynMat::Zero(2, 2); + R(0,0) = 1.0; + R(1, 1) = 1.0; + + DynMat Q = DynMat::Zero(2, 2); + Q(0,0) = 0.001; + Q(1, 1) = 0.001; + + DynMat P = DynMat::Zero(3, 3); + P(0, 0) = 1.0; + P(1, 1) = 1.0; + P(2, 2) = 1.0; + + ekf.with_matrix("P", P) + .with_matrix("R", R) + .with_matrix("Q", Q); + + CSVWriter writer("state"); + writer.open(); + std::vector names{"Time", "X_true", "Y_true", "Theta_true", "X", "Y", "Theta"}; + writer.write_column_names(names); + + try{ + + + BOOST_LOG_TRIVIAL(info)<<"Starting simulation... "< motion_input; + motion_input["v"] = vt; // we keep the velocity constant + motion_input["errors"] = motion_control_error; + + for(uint_t step=0; step < n_steps; ++step){ + + BOOST_LOG_TRIVIAL(info)<<"Simulation time: "< row(7, 0.0); + row[0] = time; + row[1] = exact_state.get("X"); + row[2] = exact_state.get("Y"); + row[3] = exact_state.get("Theta"); + row[4] = state.get("X"); + row[5] = state.get("Y"); + row[6] = state.get("Theta"); + writer.write_row(row); + + time += dt; + counter++; + } + + BOOST_LOG_TRIVIAL(info)<<"Finished example..."; + } + catch(std::runtime_error& e){ + std::cerr< +#include +#include +#include +#include +#include + +namespace example_2 +{ + +using cubeai::real_t; +using cubeai::uint_t; +using cubeai::DynMat; +using cubeai::DynVec; +using cubeai::estimation::ExtendedKalmanFilter; +using cubeai::utils::IterationCounter; +using cubeai::io::CSVWriter; +using rlenvscpp::dynamics::DiffDriveDynamics; +using rlenvscpp::dynamics::SysState; + + +class ObservationModel +{ + +public: + + // the ExtendedKalmanFilter expects an exposed + // input_type + typedef DynVec input_type; + + ObservationModel(); + + // simply return the state + const DynVec evaluate(const DynVec& input)const; + + // get the H or M matrix + const DynMat& get_matrix(const std::string& name)const; + +private: + + DynMat H; + DynMat M; +}; + +ObservationModel::ObservationModel() + : + H(2, 3), + M(2, 2) +{ + H = DynMat::Zero(2,3); + M = DynMat::Zero(2,2); + H(0, 0) = 1.0; + H(1,1) = 1.0; + M(0,0) = 1.0; + M(1, 1) = 1.0; + +} + +const DynVec +ObservationModel::evaluate(const DynVec& input)const{ + return input; +} + +const DynMat& +ObservationModel::get_matrix(const std::string& name)const{ + if(name == "H"){ + return H; + } + else if(name == "M"){ + return M; + } + + throw std::logic_error("Invalid matrix name. Name "+name+ " not found"); +} + +const DynVec get_measurement(const SysState<3>& state){ + + DynVec result(2); + result[0] = state.get("X"); + result[1] = state.get("Y"); + return result; +} + + +} + +int main() { + + using namespace example_2; + + BOOST_LOG_TRIVIAL(info)<<"Starting example..."; + + uint_t n_steps = 300; + auto time = 0.0; + auto dt = 0.5; + + /// angular velocity + auto w = 0.0; + + /// linear velocity + auto vt = 1.0; + + std::array motion_control_error; + motion_control_error[0] = 0.0; + motion_control_error[1] = 0.0; + + DiffDriveDynamics exact_motion_model; + exact_motion_model.set_matrix_update_flag(false); + exact_motion_model.set_time_step(dt); + + DiffDriveDynamics motion_model; + motion_model.set_time_step(dt); + + std::map input; + input["v"] = 1.0; + input["w"] = 0.0; + input["errors"] = motion_control_error; + motion_model.initialize_matrices(input); + + ObservationModel observation; + + ExtendedKalmanFilter ekf(motion_model, observation); + + DynMat R = DynMat::Zero(2, 2); + R(0,0) = 1.0; + R(1, 1) = 1.0; + + DynMat Q = DynMat::Zero(2, 2); + Q(0,0) = 0.001; + Q(1, 1) = 0.001; + + DynMat P = DynMat::Zero(3, 3); + P(0, 0) = 1.0; + P(1, 1) = 1.0; + P(2, 2) = 1.0; + + ekf.with_matrix("P", P) + .with_matrix("R", R) + .with_matrix("Q", Q); + + CSVWriter writer("state"); + writer.open(); + std::vector names{"Time", "X_true", "Y_true", "Theta_true", "X", "Y", "Theta"}; + writer.write_column_names(names); + + try{ + + + BOOST_LOG_TRIVIAL(info)<<"Starting simulation... "< motion_input; + motion_input["v"] = vt; // we keep the velocity constant + motion_input["errors"] = motion_control_error; + + for(uint_t step=0; step < n_steps; ++step){ + + BOOST_LOG_TRIVIAL(info)<<"Simulation time: "< row(7, 0.0); + row[0] = time; + row[1] = exact_state.get("X"); + row[2] = exact_state.get("Y"); + row[3] = exact_state.get("Theta"); + row[4] = state.get("X"); + row[5] = state.get("Y"); + row[6] = state.get("Theta"); + writer.write_row(row); + + time += dt; + counter++; + } + + BOOST_LOG_TRIVIAL(info)<<"Finished example..."; + } + catch(std::runtime_error& e){ + std::cerr<!nu+M8-4w1`j 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μ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 Nc2WE9xj$Ik`CmYUuh{?q literal 0 HcmV?d00001 diff --git a/examples/filtering/filtering_example_2/y_coordinate.png b/examples/filtering/filtering_example_2/y_coordinate.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4b4fecc69430340c63f23ce910bdeb982bb7be GIT binary patch literal 47423 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 literal 0 HcmV?d00001 diff --git a/include/cubeai/estimation/extended_kalman_filter.h b/include/cubeai/estimation/extended_kalman_filter.h new file mode 100644 index 0000000..032eb85 --- /dev/null +++ b/include/cubeai/estimation/extended_kalman_filter.h @@ -0,0 +1,324 @@ +#ifndef EXTENDED_KALMAN_FILTER_H +#define EXTENDED_KALMAN_FILTER_H + +#include "cubeai/base/cubeai_types.h" + +#include +#include +#include +#include + +namespace cubeai{ +namespace estimation{ + +/// +/// Implements the Extended Kalman filter algorithm. +/// The class expects a number of inputs in order to be useful. Concretely +/// the application must specity +/// +/// MotionModelTp a motion model +/// ObservationModelTp observation model +/// +/// The MotionModelTp should expose the following functions +/// +/// evaluate(MotionModelTp input)-->State& +/// get_matrix(const std::string)--->DynMat +/// +/// In particular, the class expects the L, F matrices to be supplied via the +/// get_matix function of the motion model. +/// +/// Similarly, the ObservationModelTp should expose the following functions +/// +/// evaluate(ObservationModelTp& input)--->DynVec +/// get_matrix(const std::string)--->DynMat +/// +/// In particular, the class expects the M, H matrices to be supplied via the +/// get_matix function of the observation model. +/// +/// Finally, the application should supply the P, Q, R matrices associated +/// with the process +/// + +template +class ExtendedKalmanFilter: private boost::noncopyable +{ +public: + + typedef MotionModelTp motion_model_type; + typedef ObservationModelTp observation_model_type; + typedef typename motion_model_type::input_type motion_model_input_type; + typedef typename motion_model_type::matrix_type matrix_type; + typedef typename motion_model_type::state_type state_type; + typedef typename observation_model_type::input_type observation_model_input_type; + + /// \brief Constructor + ExtendedKalmanFilter(); + + /// + /// \brief Constructor. Initialize with a writable reference + /// of the motion model and a read only reference of the observation model + /// + ExtendedKalmanFilter(motion_model_type& motion_model, + const observation_model_type& observation_model); + + /// + /// \brief Destructor + /// + ~ExtendedKalmanFilter(); + + /// \brief Estimate the state. This function simply + /// wraps the predict and update steps described by the + /// functions below + void estimate(const std::tuple& input ); + + /// \brief Predicts the state vector x and the process covariance matrix P using + /// the given input control u accroding to the following equations + /// + /// \hat{x}_{k = f(x_{k-1}, u_{k}, w_k) + /// \hat{P}_{k} = F_{k-1} * P_{k-1} * F_{k-1}^T + L_{k-1} * Q_{k-1} * L_{k-1}^T + /// + /// where x_{k-1} is the state at the previous step, u_{k} + /// is the control signal and w_k is the error associated with the + /// control signal. In input argument passed to the function is meant + /// to model in a tuple all the arguments needed. F, L are Jacobian matrices + /// and Q is the covariance matrix associate with the control signal + void predict(const motion_model_input_type& input); + + /// \brief Updates the gain matrix K, the state vector x and covariance matrix P + /// using the given measurement z_k according to the following equations + /// + /// K_k = \hat{P}_{k} * H_{k}^T * (H_k * \hat{P}_{k} * H_{k}^T + M_k * R_k * M_k^T)^{-1} + /// x_k = \hat{x}_{k} + K_k * (z_k - h( \hat{x}_{k}, 0)) + /// P_k = (I - K_k * H_k) * \hat{P}_{k} + void update(const observation_model_input_type& z); + + /// \brief Set the motion model + void set_motion_model(motion_model_type& motion_model) + {motion_model_ptr_ = &motion_model;} + + /// \brief Set the observation model + void set_observation_model(const observation_model_type& observation_model) + {observation_model_ptr_ = &observation_model;} + + /// \brief Set the matrix used by the filter + void set_matrix(const std::string& name, const matrix_type& mat); + + /// \brief Returns true if the matrix with the given name exists + bool has_matrix(const std::string& name)const; + + /// \brief Returns the state + const state_type& get_state()const{return motion_model_ptr_->get_state();} + + /// \brief Returns the state + state_type& get_state(){return motion_model_ptr_->get_state();} + + /// \brief Returns the state property with the given name + real_t get(const std::string& name)const{return motion_model_ptr_->get_state_property(name);} + + /// \brief Returns the name-th matrix + const DynMat& operator[](const std::string& name)const; + + /// \brief Returns the name-th matrix + DynMat& operator[](const std::string& name); + + /// + /// \brief Set the matrix and return *this + /// + ExtendedKalmanFilter& with_matrix(const std::string& name, const matrix_type& mat); + +protected: + + /// \brief pointer to the function that computes f + motion_model_type* motion_model_ptr_; + + /// \brief pointer to the function that computes h + const observation_model_type* observation_model_ptr_; + + /// \brief Matrices used by the filter internally + std::map matrices_; + +}; + +template +ExtendedKalmanFilter::ExtendedKalmanFilter() + : + motion_model_ptr_(nullptr), + observation_model_ptr_(nullptr) +{} + +template +ExtendedKalmanFilter::ExtendedKalmanFilter(motion_model_type& motion_model, + const observation_model_type& observation_model) + : + motion_model_ptr_(&motion_model), + observation_model_ptr_(&observation_model) +{} + +template +ExtendedKalmanFilter::~ExtendedKalmanFilter() +{} + +template +void +ExtendedKalmanFilter::set_matrix(const std::string& name, + const matrix_type& mat){ + + if(name != "Q" && name != "K" && name != "R" && name != "P"){ + throw std::logic_error("Invalid matrix name. Name: "+ + name+ + " not in [Q, K, R, P]"); + } + + matrices_.insert_or_assign(name, mat); +} + +template +ExtendedKalmanFilter& +ExtendedKalmanFilter::with_matrix(const std::string& name, + const matrix_type& mat){ + set_matrix(name, mat); + return *this; +} + +template +bool +ExtendedKalmanFilter::has_matrix(const std::string& name)const{ + + auto itr = matrices_.find(name); + return itr != matrices_.end(); +} + +template +const DynMat& +ExtendedKalmanFilter::operator[](const std::string& name)const{ + + auto itr = matrices_.find(name); + + if(itr == matrices_.end()){ + throw std::invalid_argument("Matrix: "+name+" does not exist"); + } + + return itr->second; +} + +template +DynMat& +ExtendedKalmanFilter::operator[](const std::string& name){ + + auto itr = matrices_.find(name); + + if(itr == matrices_.end()){ + throw std::invalid_argument("Matrix: "+name+" does not exist"); + } + + return itr->second; +} + + +template +void +ExtendedKalmanFilter::estimate(const std::tuple& input ){ + + predict(input.template get<0>()); + update(input.template get<1>()); +} + +template +void +ExtendedKalmanFilter::predict(const motion_model_input_type& u){ + + /// make a state prediction using the + /// motion model + motion_model_ptr_->evaluate(u); + + auto& P = (*this)["P"]; + auto& Q = (*this)["Q"]; + + auto& L = motion_model_ptr_->get_matrix("L"); + auto L_T = L.transpose(); //trans(L); + + auto& F = motion_model_ptr_->get_matrix("F"); + auto F_T = F.transpose(); //trans(F); + + P = F * P * F_T + L*Q*L_T; +} + +template +void +ExtendedKalmanFilter::update(const observation_model_input_type& z){ + + auto& state = motion_model_ptr_->get_state(); + auto& P = (*this)["P"]; + auto& R = (*this)["R"]; + + auto zpred = observation_model_ptr_->evaluate(z); + + auto& H = observation_model_ptr_->get_matrix("H"); + auto H_T = H.transpose(); //trans(H); + + // compute \partial{h}/\partial{v} the jacobian of the observation model + // w.r.t the error vector + auto& M = observation_model_ptr_->get_matrix("M"); + auto M_T = M.transpose(); //trans(M); + + try{ + + // S = H*P*H^T + M*R*M^T + auto S = H*P*H_T + M*R*M_T; + + auto S_inv = S.inverse(); //inv(S); + + if(has_matrix("K")){ + auto& K = (*this)["K"]; + K = P*H_T*S_inv; + } + else{ + auto K = P*H_T*S_inv; + set_matrix("K", K); + } + + auto& K = (*this)["K"]; + + auto innovation = z - zpred; + + // we need to take the transpose + auto innovation_t = innovation.transpose(); + + if(K.cols() != innovation_t.rows()){ + throw std::runtime_error("Matrix columns: "+ + std::to_string(K.cols())+ + " not equal to vector size: "+ + std::to_string(innovation_t.rows())); + } + + //auto vec = K * innovation_t; + state += K * innovation_t; //.add(K*innovation); + + //IdentityMatrix I(state.size()); + auto I = matrix_type::Identity(state.size(), state.size()); + /// update the covariance matrix + P = (I - K*H)*P; + } + catch(...){ + + // this is a singular matrix what + // should we do? Simply use the predicted + // values and log the fact that there was a singular matrix + + throw; + } +} + + +} +} + + +#endif \ No newline at end of file From 66c8fac20a867792e42ddeddba7a300ceaa5a798 Mon Sep 17 00:00:00 2001 From: pockerman Date: Thu, 26 Dec 2024 11:19:14 +0000 Subject: [PATCH 5/5] Fix compilation errors from rlenvscpp refactoring (#150) --- README.md | 1 + examples/filtering/CMakeLists.txt | 1 + examples/intro/CMakeLists.txt | 6 +++++- examples/rl/rl_example_0/rl_example_0.cpp | 2 +- examples/rl/rl_example_13/rl_example_13.cpp | 2 +- examples/rl/rl_example_15/rl_example_15.cpp | 2 +- examples/rl/rl_example_16/rl_example_16.cpp | 16 +++++++--------- examples/rl/rl_example_19/rl_example_19.cpp | 2 +- include/cubeai/base/cubeai_types.h | 9 +++++++-- include/cubeai/io/csv_file_writer.h | 7 +++---- 10 files changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d8beea2..cd7b010 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The following is an indicative list of examples. ### Filtering - Kalman filtering +- Extended Kalman filter for diff-drive system ### Path planning diff --git a/examples/filtering/CMakeLists.txt b/examples/filtering/CMakeLists.txt index 07a51d4..130e5d0 100644 --- a/examples/filtering/CMakeLists.txt +++ b/examples/filtering/CMakeLists.txt @@ -1,2 +1,3 @@ ADD_SUBDIRECTORY(filtering_example_1) +ADD_SUBDIRECTORY(filtering_example_2) diff --git a/examples/intro/CMakeLists.txt b/examples/intro/CMakeLists.txt index 038505c..74d7159 100644 --- a/examples/intro/CMakeLists.txt +++ b/examples/intro/CMakeLists.txt @@ -5,4 +5,8 @@ ADD_SUBDIRECTORY(intro_example_4) ADD_SUBDIRECTORY(intro_example_5) ADD_SUBDIRECTORY(intro_example_6) ADD_SUBDIRECTORY(intro_example_7) -ADD_SUBDIRECTORY(intro_example_8) + +# we need CUDA for this to work +IF(USE_CUDA) + ADD_SUBDIRECTORY(intro_example_8) +ENDIF() diff --git a/examples/rl/rl_example_0/rl_example_0.cpp b/examples/rl/rl_example_0/rl_example_0.cpp index 8bff3f3..3adfc24 100755 --- a/examples/rl/rl_example_0/rl_example_0.cpp +++ b/examples/rl/rl_example_0/rl_example_0.cpp @@ -40,7 +40,7 @@ using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; using cubeai::rl::agents::DummyAgent; using cubeai::utils::IterationCounter; -using rlenvs_cpp::envs::gymnasium::MountainCar; +using rlenvscpp::envs::gymnasium::MountainCar; template diff --git a/examples/rl/rl_example_13/rl_example_13.cpp b/examples/rl/rl_example_13/rl_example_13.cpp index 38ed6b6..887dd59 100755 --- a/examples/rl/rl_example_13/rl_example_13.cpp +++ b/examples/rl/rl_example_13/rl_example_13.cpp @@ -43,7 +43,7 @@ using cubeai::rl::algos::pg::ReinforceConfig; using cubeai::rl::RLSerialAgentTrainer; using cubeai::rl::RLSerialTrainerConfig; using cubeai::maths::stats::TorchCategorical; -using rlenvs cpp::envs::gymnasium::CartPole; +using rlenvscpp::envs::gymnasium::CartPole; const uint_t L1 = 4; diff --git a/examples/rl/rl_example_15/rl_example_15.cpp b/examples/rl/rl_example_15/rl_example_15.cpp index 39bcb7e..2940880 100755 --- a/examples/rl/rl_example_15/rl_example_15.cpp +++ b/examples/rl/rl_example_15/rl_example_15.cpp @@ -176,7 +176,7 @@ int main(){ // initialize the environment using random mode std::unordered_map options; - options["mode"] = std::any(rlenvs_cpp::envs::grid_world::to_string(rlenvs_cpp::envs::grid_world::GridWorldInitType::RANDOM)); + options["mode"] = std::any(rlenvscpp::envs::grid_world::to_string(rlenvscpp::envs::grid_world::GridWorldInitType::RANDOM)); env.make("v0", options); diff --git a/examples/rl/rl_example_16/rl_example_16.cpp b/examples/rl/rl_example_16/rl_example_16.cpp index 4c47412..95ad42d 100755 --- a/examples/rl/rl_example_16/rl_example_16.cpp +++ b/examples/rl/rl_example_16/rl_example_16.cpp @@ -8,8 +8,7 @@ #include "cubeai/base/cubeai_types.h" -#include "cubeai/rl/algorithms/mc/mc_tree_search_base.h" -#include "cubeai/utils/array_utils.h" +//#include "cubeai/rl/algorithms/mc/mc_tree_search_solver.h" #include "rlenvs/envs/connect2/connect2_env.h" @@ -23,12 +22,13 @@ namespace example{ + /* using cubeai::real_t; using cubeai::uint_t; using cubeai::rl::algos::MCTreeSearchBase; using cubeai::rl::algos::MCTreeSearchConfig; -using rlenvs_cpp::envs::connect2::Connect2; +using rlenvscpp::envs::connect2::Connect2; typedef Taxi::time_step_type time_step_type; @@ -38,10 +38,7 @@ const uint_t N_EPISODES = 20000; const uint_t N_ITRS_PER_EPISODE = 2000; const real_t TOL = 1.0e-8; -/** - * Implementation of Monte Carlo tree serach for the OpenAI-Gym - * environment. - */ + template class TaxiMCTreeSearch: public MCTreeSearchBase { @@ -110,7 +107,7 @@ TaxiMCTreeSearch::simulate_node(std::shared_ptr node, env_type& return time_step; } - +*/ /* template void @@ -186,7 +183,7 @@ TaxiMCTreeSearch::on_episode(){ } */ - +/* template void TaxiMCTreeSearch::expand_node(std::shared_ptr node, env_type& env){ @@ -212,6 +209,7 @@ TaxiMCTreeSearch::backprop(std::shared_ptr node){ } + */ } diff --git a/examples/rl/rl_example_19/rl_example_19.cpp b/examples/rl/rl_example_19/rl_example_19.cpp index 20dbd6d..7b2724d 100755 --- a/examples/rl/rl_example_19/rl_example_19.cpp +++ b/examples/rl/rl_example_19/rl_example_19.cpp @@ -78,7 +78,7 @@ std::vector::time_step_type> TrajectoryGenerator::operator()(FrozenLake<4>& env, uint_t max_steps){ RandomActionSelector action_selector; - return rlenvs_cpp::envs::create_trajectory(env, action_selector, max_steps ); + return rlenvscpp::envs::create_trajectory(env, action_selector, max_steps ); } diff --git a/include/cubeai/base/cubeai_types.h b/include/cubeai/base/cubeai_types.h index 785a189..f791757 100755 --- a/include/cubeai/base/cubeai_types.h +++ b/include/cubeai/base/cubeai_types.h @@ -54,9 +54,14 @@ namespace cubeai using DynVec = Eigen::RowVectorX; /// + /// Float type vector /// - /// - using FloatVec = DynVec; + using FloatVec = DynVec; + + /// + /// Real type vector + /// + using RealVec = DynVec; /// /// \brief Null type. Simple placeholder diff --git a/include/cubeai/io/csv_file_writer.h b/include/cubeai/io/csv_file_writer.h index 7ba8de4..6d08af5 100755 --- a/include/cubeai/io/csv_file_writer.h +++ b/include/cubeai/io/csv_file_writer.h @@ -60,10 +60,9 @@ class CSVWriter: public FileWriterBase template void write_row(const DynVec& vals); - /** - * - * @brief Write the given vector as a column - */ + /// + /// \brief Write the given vector as a column + /// template void write_column_vector(const std::vector& vals);