diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ea6fba..69392ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,8 @@ FILE(GLOB SRCS src/bitrl/*.cpp src/bitrl/envs/webots_envs/*.cpp src/bitrl/envs/gymnasium/box2d/*.cpp src/bitrl/boards/arduino/*.cpp + src/bitrl/control/*.cpp + src/bitrl/planning/*.cpp src/bitrl/rigid_bodies/*.cpp src/bitrl/rigid_bodies/chrono_robots/*.cpp src/bitrl/rigid_bodies/webots_robots/*.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 44129de..e4b146e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -38,6 +38,8 @@ ADD_SUBDIRECTORY(example_12) ADD_SUBDIRECTORY(example_14) ADD_SUBDIRECTORY(example_15) ADD_SUBDIRECTORY(example_16) +ADD_SUBDIRECTORY(example_21) +ADD_SUBDIRECTORY(example_22) IF(BITRL_MQTT) ADD_SUBDIRECTORY(example_17) diff --git a/examples/example_1/example_1.md b/examples/example_1/example_1.md index 2d10c5b..f49c39e 100644 --- a/examples/example_1/example_1.md +++ b/examples/example_1/example_1.md @@ -5,10 +5,10 @@ specifically how to create an interact with bitrl-envs-api. bitrl itself implements classes that hide this interaction from the client code. -In general, environment classes in bitrl, have to implement the \ref bitrl::envs::EnvBase "`bitrl::envs::EnvBase`" API. +In general, environment classes in bitrl, have to implement the \ref bitrl::envs::EnvBase "bitrl::envs::EnvBase" API. -In this example we will use the \ref bitrl::envs::gymnasium::FrozenLake "`bitrl::envs::gymnasium::FrozenLake`" -class. This is a template class, see the example below, that itself inherits from \ref bitrl::envs::gymnasium::GymnasiumEnvBase "`bitrl::envs::gymnasium::GymnasiumEnvBase`" +In this example we will use the \ref bitrl::envs::gymnasium::FrozenLake "bitrl::envs::gymnasium::FrozenLake" +class. This is a template class, see the example below, that itself inherits from \ref bitrl::envs::gymnasium::GymnasiumEnvBase "bitrl::envs::gymnasium::GymnasiumEnvBase" class. Below is the driver code. @@ -132,6 +132,6 @@ int main() @endcode In order to run the example you will need an instance of the bitrl-envs-api server running -on your machine listening at por 8001. Note the actual example also shows how to use \ref bitrl::envs::gymnasium::Taxi "`bitrl::envs::gymnasium::Taxi`", -\ref bitrl::envs::gymnasium::CliffWorld "`bitrl::envs::gymnasium::CliffWorld`" and \ref bitrl::envs::gymnasium::BlackJack "`bitrl::envs::gymnasium::BlackJack`" +on your machine listening at por 8001. Note the actual example also shows how to use \ref bitrl::envs::gymnasium::Taxi "bitrl::envs::gymnasium::Taxi", +\ref bitrl::envs::gymnasium::CliffWorld "bitrl::envs::gymnasium::CliffWorld" and \ref bitrl::envs::gymnasium::BlackJack "bitrl::envs::gymnasium::BlackJack" environments. diff --git a/examples/example_21/CMakeLists.txt b/examples/example_21/CMakeLists.txt new file mode 100644 index 0000000..ebc023f --- /dev/null +++ b/examples/example_21/CMakeLists.txt @@ -0,0 +1,24 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.20 FATAL_ERROR) + +SET(EXECUTABLE example_21) +SET(SOURCE ${EXECUTABLE}.cpp) + +ADD_EXECUTABLE(${EXECUTABLE} ${SOURCE}) + +TARGET_LINK_LIBRARIES(${EXECUTABLE} PRIVATE bitrllib pthread boost_log) + +IF(ENABLE_CHRONO) + TARGET_LINK_LIBRARIES(${EXECUTABLE} PRIVATE Chrono::Chrono_core) +ENDIF () + +IF(BITRL_WEBOTS) + TARGET_LINK_LIBRARIES(${EXECUTABLE} CppController) +ENDIF() + +IF(BITRL_MQTT) + TARGET_LINK_LIBRARIES(${EXECUTABLE} PRIVATE paho-mqttpp3 paho-mqtt3as) +ENDIF () + +IF(BITRL_OPENCV) + TARGET_LINK_LIBRARIES(${EXECUTABLE} PRIVATE ${OpenCV_LIBS}) +ENDIF () diff --git a/examples/example_21/example_21.cpp b/examples/example_21/example_21.cpp new file mode 100755 index 0000000..d0c68e4 --- /dev/null +++ b/examples/example_21/example_21.cpp @@ -0,0 +1,195 @@ +/** + * KalmanFilter example. The example is taken from the + * paper An Introduction to the Kalman Filter by + * Greg Welch and Gary Bishop + * */ + +#include "bitrl/bitrl_types.h" +#include "bitrl/estimation/kalman_filter.h" +#include "bitrl/estimation/kf_model_base.h" +#include "bitrl/utils/iteration_counter.h" +#include "bitrl/utils/io/csv_file_writer.h" + +#define BOOST_LOG_DYN_LINK +#include +#include +#include +#include +#include +#include + +namespace example_1 +{ + +using bitrl::real_t; +using bitrl::uint_t; +using bitrl::DynMat; +using bitrl::DynVec; +using bitrl::estimation::KalmanFilterConfig; +using bitrl::estimation::KalmanFilter; +using bitrl::estimation::KFMotionModelBase; +using bitrl::estimation::KFModelBase; +using bitrl::utils::IterationCounter; +using bitrl::utils::io::CSVWriter; + + +real_t DT = 1.0; +real_t SIM_TIME = 50.0; + +struct Cmd +{ + // v: [m/s] omega: [rad/s] + static DynVec cmd(); +}; + +DynVec +Cmd::cmd(){ + + DynVec u(1); + u << 0.0; + return u; +} + +// simple struct that describes the Motion of the robots +struct MotionModel: public KFMotionModelBase, DynVec> +{ + const static uint_t VEC_SIZE; + +}; + +const uint_t MotionModel::VEC_SIZE = 1; + +// simple struct that describes the Observation model +struct ObservationModel: public KFModelBase> +{ + const static uint_t VEC_SIZE; + static DynVec sensors(); +}; + +const uint_t ObservationModel::VEC_SIZE = 1; + +const real_t STD = 0.1; +const real_t MU = -0.37727; + +DynVec +ObservationModel::sensors(){ + DynVec all_sensors(1); + + std::normal_distribution d{MU , STD}; + std::mt19937 generator; //(42); + all_sensors << d(generator); + return all_sensors; +} + + +typedef MotionModel motion_model_type; +typedef ObservationModel obs_motion_type; +} + +int main() { + + using namespace example_1; + + try{ + + typedef motion_model_type::state_type state_type; + obs_motion_type obs; + DynMat H(ObservationModel::VEC_SIZE, MotionModel::VEC_SIZE); + H << 1.0; + + obs.add_matrix("H", H); + + // the motion model + motion_model_type motion; + + // set the initial state + DynVec x_init(MotionModel::VEC_SIZE); + x_init << 0; + motion.set_state(x_init); + + //...and the matrix describing the motion dynamics + DynMat F(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + F << 1.0; + + motion.add_matrix("F", F); + + KalmanFilterConfig kf_config; + kf_config.motion_model = &motion; + kf_config.observation_model = &obs; + + // the KalmanFilter to use + KalmanFilter kf(kf_config); + + // set up the matrices + DynMat P(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + P << 1.0; + + DynMat Q(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + Q << 1.0e-5; + + DynMat R(ObservationModel::VEC_SIZE, ObservationModel::VEC_SIZE); + R << STD*STD; + + DynMat B(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + B << 0.0; + + kf.with_matrix("P", P) + .with_matrix("Q", Q) + .with_matrix("R", R) + .with_matrix("B", B); + + + // the input to the filter + std::map kf_input; + + auto n_steps = static_cast(SIM_TIME / DT); + + BOOST_LOG_TRIVIAL(info)<<"Expected number of time steps: "< rows; + rows.reserve(n_steps); + + auto current_time = 0.0; + while(current_time <= SIM_TIME){ + + auto u = Cmd::cmd(); + auto w = DynVec(u.size()); + w << 0.0; + auto z = ObservationModel::sensors(); + + kf_input["u"] = u; + kf_input["w"] = w; + kf_input["z"] = z; + + auto& state_vec = kf.estimate(kf_input); + + BOOST_LOG_TRIVIAL(info)<<"Time: "<(state_vec.size()); ++i){ + row[i + 1] = state_vec[i]; + } + + rows.push_back(row); + current_time += DT; + } + + CSVWriter csv_writer("state.csv"); + csv_writer.open(); + + for(auto& r:rows){ + csv_writer.write_row(r); + } + + } + catch(std::exception& e){ + std::cout<An Introduction to the Kalman Filter by +Greg Welch and Gary Bishop. In fact the implementation of the ```KalmaFilter``` follows this paper. + + + +## The driver code + +``` +/** + * KalmanFilter example. + * */ + +#include "cubeai/base/cubeai_types.h" +#include "cubeai/estimation/kalman_filter.h" +#include "cubeai/estimation/kf_model_base.h" +#include "cubeai/utils/iteration_counter.h" +#include "cubeai/io/csv_file_writer.h" + +#include +#include +#include +#include +#include +#include + +namespace example_1 +{ + +using cubeai::real_t; +using cubeai::uint_t; +using cubeai::DynMat; +using cubeai::DynVec; +using cubeai::estimation::KalmanFilterConfig; +using cubeai::estimation::KalmanFilter; +using cubeai::estimation::KFMotionModelBase; +using cubeai::estimation::KFModelBase; +using cubeai::utils::IterationCounter; +using cubeai::io::CSVWriter; + + +real_t DT = 1.0; +real_t SIM_TIME = 50.0; + +struct Cmd +{ + // v: [m/s] omega: [rad/s] + static DynVec cmd(); +}; + +DynVec +Cmd::cmd(){ + + DynVec u(1); + u << 0.0; + return u; +} + +// simple struct that describes the Motion of the robots +struct MotionModel: public KFMotionModelBase, DynVec> +{ + const static uint_t VEC_SIZE; + +}; + +const uint_t MotionModel::VEC_SIZE = 1; + +// simple struct that describes the Observation model +struct ObservationModel: public KFModelBase> +{ + const static uint_t VEC_SIZE; + static DynVec sensors(); +}; + +const uint_t ObservationModel::VEC_SIZE = 1; + +const real_t STD = 0.1; +const real_t MU = -0.37727; + +DynVec +ObservationModel::sensors(){ + DynVec all_sensors(1); + + std::normal_distribution d{MU , STD}; + std::mt19937 generator; //(42); + all_sensors << d(generator); + return all_sensors; +} + + +typedef MotionModel motion_model_type; +typedef ObservationModel obs_motion_type; +} + +int main() { + + using namespace example_1; + + try{ + + typedef motion_model_type::state_type state_type; + obs_motion_type obs; + DynMat H(ObservationModel::VEC_SIZE, MotionModel::VEC_SIZE); + H << 1.0; + + obs.add_matrix("H", H); + + // the motion model + motion_model_type motion; + + // set the initial state + DynVec x_init(MotionModel::VEC_SIZE); + x_init << 0; + motion.set_state(x_init); + + //...and the matrix describing the motion dynamics + DynMat F(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + F << 1.0; + + motion.add_matrix("F", F); + + KalmanFilterConfig kf_config; + kf_config.motion_model = &motion; + kf_config.observation_model = &obs; + + // the KalmanFilter to use + KalmanFilter kf(kf_config); + + // set up the matrices + DynMat P(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + P << 1.0; + + DynMat Q(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + Q << 1.0e-5; + + DynMat R(ObservationModel::VEC_SIZE, ObservationModel::VEC_SIZE); + R << STD*STD; + + DynMat B(MotionModel::VEC_SIZE, MotionModel::VEC_SIZE); + B << 0.0; + + kf.with_matrix("P", P) + .with_matrix("Q", Q) + .with_matrix("R", R) + .with_matrix("B", B); + + + // the input to the filter + std::map kf_input; + + auto n_steps = static_cast(SIM_TIME / DT); + + BOOST_LOG_TRIVIAL(info)<<"Expected number of time steps: "< rows; + rows.reserve(n_steps); + + auto current_time = 0.0; + while(current_time <= SIM_TIME){ + + auto u = Cmd::cmd(); + auto w = DynVec(u.size()); + w << 0.0; + auto z = ObservationModel::sensors(); + + kf_input["u"] = u; + kf_input["w"] = w; + kf_input["z"] = z; + + auto& state_vec = kf.estimate(kf_input); + + BOOST_LOG_TRIVIAL(info)<<"Time: "<(state_vec.size()); ++i){ + row[i + 1] = state_vec[i]; + } + + rows.push_back(row); + current_time += DT; + } + + CSVWriter csv_writer("state.csv"); + csv_writer.open(); + + for(auto& r:rows){ + csv_writer.write_row(r); + } + + } + catch(std::exception& e){ + std::cout< +#include +#include +#include +#include +#include + +namespace example_2 +{ + +using bitrl::real_t; +using bitrl::uint_t; +using bitrl::DynMat; +using bitrl::DynVec; +using bitrl::estimation::ExtendedKalmanFilter; + +using bitrl::utils::io::CSVWriter; +using bitrl::utils::IterationCounter; +using bitrl::dynamics::DiffDriveDynamics; +using bitrl::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< + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace example{ + +typedef boost::python::api::object obj_t; +using cubeai::real_t; +using cubeai::uint_t; + +template +std::vector +reconstruct_a_star_path(const std::multimap& map, const IdTp& start){ + + if(map.empty()){ + return std::vector(); + } + + std::vector path; + path.push_back(start); + + auto next_itr = map.find(start); + + if(next_itr == map.end()){ + + //such a key does not exist + throw std::logic_error("Key: "+std::to_string(start)+" does not exist"); + } + + IdTp next = next_itr->second; + path.push_back(next); + + while(next_itr!=map.end()){ + + next_itr = map.find(next); + if(next_itr != map.end()){ + next = next_itr->second; + path.push_back(next); + } + } + + //let's reverse the path + std::vector the_path; + the_path.reserve(path.size()); + auto itrb = path.rbegin(); + auto itre = path.rend(); + + while(itrb != itre){ + the_path.push_back(*itrb++); + } + + return the_path; +} + + +template< + class T, + class Container = std::vector, + class Compare = std::less > +class searchable_priority_queue: public std::priority_queue +{ + +public: + + using std::priority_queue::priority_queue; + + /// \brief Returns true if the given value is contained + /// internally it calls std::find. + bool contains(const T& val)const; +}; + +template +bool +searchable_priority_queue::contains(const T& val)const{ + + auto itr = std::find_if(this->c.cbegin(), this->c.cend(), [&](const auto& node){ + if(node.id == val.id){ + return true; + } + + return false; + }); + + if(itr == this->c.end()) + { + return false; + } + + return true; +} + + +struct fcost_comparison +{ + template + bool operator()(const NodeTp& n1,const NodeTp& n2)const; +}; + +template +bool +fcost_comparison::operator()(const NodeTp& n1,const NodeTp& n2)const{ + + if(n1.f_cost > n2.f_cost){ + return true; + } + + return false; +} + +struct id_comparison +{ + template + bool operator()(const NodeTp& n1,const NodeTp& n2)const; +}; + +template +bool +id_comparison::operator()(const NodeTp& n1,const NodeTp& n2)const{ + + if(n1.id > n2.id){ + return true; + } + + return false; +} + +struct Node +{ + + uint_t id; + real_t x; + real_t y; + uint_t street_count; + + // holds the heuristic cost for the node + real_t f_cost{0.0}; + real_t g_cost{0.0}; +}; + +bool operator==(const Node& n1, const Node& n2){ + return n1.id == n2.id; +} + +struct Edge +{ + uint_t in_vertex; + uint_t out_vertex; + real_t length; +}; + +// the heuristic to use for A* +struct DistanceHeuristic +{ + + // + real_t operator()(const Node& n1, const Node& n2)const; + +}; + +real_t +DistanceHeuristic::operator()(const Node& n1, const Node& n2)const{ + + auto long1 = n1.x*cubeai::MathConsts::PI/180.0; + auto lat1 = n1.y*cubeai::MathConsts::PI/180.0; + auto long2 = n2.x*cubeai::MathConsts::PI/180.0; + auto lat2 = n2.y*cubeai::MathConsts::PI/180.0; + + // Use a spherical approximation of the earth for + // estimating the distance between two points. + auto r = 6371000; + auto x1 = r*std::cos(lat1)*std::cos(long1); + auto y1 = r*std::cos(lat1)*std::sin(long1); + auto z1 = r*std::sin(lat1); + + auto x2 = r*std::cos(lat2)*std::cos(long2); + auto y2 = r*std::cos(lat2)*std::sin(long2); + auto z2 = r*std::sin(lat2); + + auto d = std::pow(x2-x1, 2) + std::pow(y2-y1, 2) + std::pow(z2-z1, 2); + return std::sqrt(d); +} + +// wrapper to facilitate the Python API +class Graph +{ +public: + + typedef Node vertex_type; + + // constructor + Graph(std::string start, std::string end, obj_t py_namespace); + + // build the OSNX graph + void build(); + + // number of nodes + uint_t n_nodes()const noexcept{return nodes_.size();} + + uint_t start_node()const{return start_node_.id;} + + // returns the edge associated with the given node + std::vector get_node_edges(const Node node)const; + + // get the node with the given idx + Node& get_node(uint_t idx); + + // build the A* path + std::multimap get_a_star_path(); + +private: + + std::string start_; + std::string end_; + obj_t py_namespace_; + obj_t map_graph_; + + Node start_node_; + Node end_node_; + + std::vector nodes_; + + // build the nodes list + void build_nodes_list_(); + +}; + +Graph::Graph(std::string start, std::string end, obj_t py_namespace) + : + start_(start), + end_(end), + py_namespace_(py_namespace), + start_node_(), + end_node_() +{} + +void +Graph::build_nodes_list_(){ + + std::string nodes_list_str = "node_data = map_graph.nodes(True)\n"; + nodes_list_str += "node_data = list(node_data)\n"; + boost::python::exec(nodes_list_str.c_str(), py_namespace_); + + auto node_data = boost::python::extract(py_namespace_["node_data"]); + + nodes_.reserve(boost::python::len(node_data)); + + for(auto i=0; i(node_data()[i]); + + n.id = boost::python::extract(node_tuple()[0]); + n.x = boost::python::extract(node_tuple()[1]["x"]); + n.y = boost::python::extract(node_tuple()[1]["y"]); + n.street_count = n.x = boost::python::extract(node_tuple()[1]["street_count"]); + nodes_.push_back(n); + } +} + +// get the node with the given idx +Node& +Graph::get_node(uint_t idx){ + + for(auto& node : nodes_){ + if(node.id == idx){ + return node; + } + } + + + throw std::logic_error("Invalid node id"); +} + +std::vector +Graph::get_node_edges(const Node node)const{ + + std::string edges_list_str = "edges = map_graph.out_edges([" + std::to_string(node.id) + "], data=True)\n"; + edges_list_str += "edges = list(edges))\n"; + boost::python::exec(edges_list_str.c_str(), py_namespace_); + + std::vector edges; + auto edges_list = boost::python::extract(py_namespace_["edges"]); + + edges.reserve(boost::python::len(edges_list)); + + for(auto i=0; i(edges_list()[i]); + + e.in_vertex = boost::python::extract(edge_tuple()[0]); + e.out_vertex = boost::python::extract(edge_tuple()[1]); + e.length = boost::python::extract(edge_tuple()[2]["length"]); + + edges.push_back(e); + } + + return edges; + +} + +void +Graph::build(){ + + std::string imports = "import osmnx as ox\n"; + auto ignore = boost::python::exec(imports.c_str(), py_namespace_); + + std::string map_graph_str = "map_graph = ox.graph_from_place('" + start_ + "," + end_ + "'," + "network_type='drive')\n"; + + // fill in the map graph + boost::python::exec(map_graph_str.c_str(), py_namespace_); + + std::string origin_str = "origin = ox.get_nearest_node(map_graph, (37.8743, -122.277))\n"; + boost::python::exec(origin_str.c_str(), py_namespace_); + + uint_t start_node_id = boost::python::extract(py_namespace_["origin"]); + std::string destination_str = "destination = list(map_graph.nodes())[-1]"; + + boost::python::exec(destination_str.c_str(), py_namespace_); + uint_t end_node_id = boost::python::extract(py_namespace_["destination"]); + + start_node_.id = start_node_id; + end_node_.id = end_node_id; + + build_nodes_list_(); + + auto& start_node = get_node(start_node_id); + start_node_ = start_node; + + auto& end_node = get_node(end_node_id); + end_node_ = end_node; +} + +std::multimap +Graph::get_a_star_path(){ + + // for each node hodls where it + // came form + std::multimap came_from; + + // start and goal node are the same + if(start_node_ == end_node_){ + came_from.insert({start_node_.id, start_node_.id}); + return came_from; + } + + // the object to use for the + DistanceHeuristic dist_h; + + searchable_priority_queue, fcost_comparison> open; + + // set of explored vertices + std::set explored; + + //the cost of the path so far leading to this + //node is obviously zero at the start node + start_node_.g_cost = 0.0; + + //calculate the fCost from start node to the goal + //at the moment this can be done only heuristically + //start.data.fcost = h(start.data.position, end.data.position); + start_node_.f_cost = dist_h(start_node_, end_node_); + open.push(start_node_); + + bool goal_found = true; + while(!open.empty()){ + + //the vertex currently examined + const vertex_type cv = open.top(); + open.pop(); + + //check if this is the goal + if(cv == end_node_){ + goal_found = true; + break; + } + + // the cost for the cv + auto cv_g_cost = cv.g_cost; + + //get the adjacent neighbors + auto edges = get_node_edges(cv); + auto edge_itr_begin = edges.begin(); + auto edge_itr_end = edges.end(); + + // loop over the neighbors + for(; edge_itr_begin != edge_itr_end; ++edge_itr_begin){ + + auto& edge = *edge_itr_begin; + + auto out_vertex = edge.out_vertex; + + if(explored.contains(out_vertex)){ + continue; + } + + // get the node associated with the + // out vertex + auto& node = get_node(out_vertex); + + // what is the length of the edge + auto edge_length = edge.length; + + // if the out vertex is not in the + // open set + if(!open.contains(node)){ + + // the cost for the out vertex + node.g_cost = cv_g_cost + edge_length; + node.f_cost = cv_g_cost + edge_length + dist_h( node, end_node_); + open.push(node); + came_from.insert({out_vertex, cv.id}); + } + else{ + + auto v_cost = node.g_cost; + + if(cv_g_cost + edge_length < v_cost ){ + node.g_cost = cv_g_cost + edge_length; + node.f_cost = cv_g_cost + edge_length + dist_h( node, end_node_); + came_from.insert({out_vertex, cv.id}); + } + } + } + + //current node is not the goal so proceed + //add it to the explored (or else called closed) set + explored.insert(cv.id); + + } + + if(!goal_found){ + throw std::logic_error("Goal not found"); + } + + return came_from; + +} + +} + + +int main(){ + + using namespace example; + + try{ + + Py_Initialize(); + auto main_module = boost::python::import("__main__"); + auto main_namespace = main_module.attr("__dict__"); + + // create the graph + Graph graph("Berkeley", "California", main_namespace); + graph.build(); + + std::cout<<"Number of nodes="< + +namespace bitrl +{ + namespace control + { + /** + * @class PIDController + * @ingroup bitrl_control + * @brief PID controller implementation. + * + */ + class PIDController + { + public: + + typedef real_t ctrl_signal_type; + + /// Constructor + /// @param Kp + /// @param Ki + /// @param Kd + PIDController(real_t Kp, real_t Ki, real_t Kd); + + real_t get_kp()const noexcept{return kp_;} + real_t get_ki()const noexcept{return ki_;} + real_t get_kd()const noexcept{return kd_;} + + /// Test the is_limited_ flag + /// @return True is the set_control_limits has been called + bool is_limited()const noexcept{return is_limited_;} + + /// Set the min/max threshold for the action + /// @param limits + void set_control_limits(std::pair limits); + + /// Compute the control signal to return + /// @param current + /// @param target + /// @param dt + /// @return + ctrl_signal_type compute_ctrl_signal(real_t current, real_t target, real_t dt); + + private: + + bool is_limited_{false}; + real_t kp_; + real_t ki_; + real_t kd_; + + real_t integral_{0.0}; + real_t err_old_{0.0}; + // min = limits.first, max = limits.second + std::pair limits_; + + }; + + inline + PIDController::PIDController(real_t kp, real_t ki, real_t kd) + : + kp_(kp), + ki_(ki), + kd_(kd) + {} + + inline + void + PIDController::set_control_limits(std::pair limits) + { + is_limited_ = true; + limits_ = limits; + } + } + +} + +#endif //PID_CONTROLLER_H diff --git a/src/bitrl/estimation/extended_kalman_filter.h b/src/bitrl/estimation/extended_kalman_filter.h new file mode 100644 index 0000000..7ad8a1a --- /dev/null +++ b/src/bitrl/estimation/extended_kalman_filter.h @@ -0,0 +1,324 @@ +#ifndef EXTENDED_KALMAN_FILTER_H +#define EXTENDED_KALMAN_FILTER_H + +#include "bitrl/bitrl_types.h" +#include "Eigen/Dense" +#include +#include +#include +#include + +namespace bitrl{ +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 diff --git a/src/bitrl/estimation/kalman_filter.h b/src/bitrl/estimation/kalman_filter.h new file mode 100644 index 0000000..5921289 --- /dev/null +++ b/src/bitrl/estimation/kalman_filter.h @@ -0,0 +1,399 @@ +#include "bitrl/bitrl_types.h" +#include "bitrl/utils/std_map_utils.h" +#include "Eigen/Dense" +#include + +#include +#include +#include +#include //for std::invalid_argument + + +namespace bitrl{ +namespace estimation{ + + +/// +/// \brief Configuration class for the Kalman filter +/// + +template +struct KalmanFilterConfig +{ + + + typedef MotionModelType motion_model_type; + typedef ObservationModelType observation_model_type; + + /// + /// \brief Pointer to the motion model + /// + motion_model_type* motion_model; + + /// + /// \brief Pointer to the observation model + /// + const observation_model_type* observation_model; + + DynMat B; + DynMat P; + DynMat Q; + DynMat K; + DynMat R; +}; + +/// +/// \brief Linear Kalman Filter implementation. +/// See: An Introduction to the Kalman Filter, TR 95-041 +/// by Greg Welch1 and Gary Bishop +/// https://www.cs.unc.edu/~welch/media/pdf/kalman_intro.pdf +/// +/// The algorithm is implemented as follows: +/// +/// prediction step: +/// +/// \f[\hat{\mathbf{x}}_{k} = F_k \mathbf{x}_{k-1} + B_k \mathbf{u}_k + \mathbf{w}_k\f] +/// +/// \f[\hat{P}_{k} = F_{k-1} P_{k-1} F_{k-1}^T + Q_{k-1}\f] +/// +/// update step: +/// +/// \f[K_k = \hat{P}_{k} H_{k}^T * (H_k \hat{P}_{k} * H_{k}^T + R_k )^{-1}\f] +/// +/// \f[\mathbf{x}_k = \hat{\mathbf{x}}_{k} + K_k (z_k - h( \hat{x}_{k}, 0))\f] +/// +/// \f[P_k = (I - K_k H_k) \hat{P}_{k}\f] +/// +/// where \f$w_k\f$ and \f$v_k\f$ represent process and measurement noise respectively. +/// They are assumed independent and normally distributed: +/// +/// \f[p(w) \sim N(0,Q)\f] +/// +/// \f[p(v) \sim N(0,R)\f] +/// +/// The gain matrix K says how much the predictions should be corrected +/// The following matrices dimensions are assumed: +/// +/// state vector: x n x 1 +/// control vector: u l x 1 +/// meas. vector: y m x 1 +/// +/// \f[ \mathbf{F} n \times n \f] +/// \f[ \mathfb{P} n \times n \f] +/// \f[ \mathbf{B} n \times l \f] +/// \f[ \mathbf{H} m \times n \f] +/// \f[ \mathbf{K} n \times m \f] +/// \f[ \mathbf{Q} n \times n \f] +/// \f[ \mathbf{R} m \times m \f] +/// +template +class KalmanFilter: private boost::noncopyable +{ + +public: + + typedef KalmanFilterConfig config_type; + typedef DynMat matrix_type; + + typedef MotionModelType motion_model_type; + typedef ObservationModelType observation_model_type; + typedef typename config_type::motion_model_type::state_type state_type; + typedef std::map input_type; + + /// + /// \brief KalmanFilter Constructor + /// + KalmanFilter(const KalmanFilterConfig& config); + + /// + /// \brief Estimate the state. This function simply + /// wraps the predict and update steps described by the + /// functions below + /// + state_type& estimate(const input_type& input ); + + /// + /// \brief Predicts the state vector x and the process covariance matrix P using + /// the given input control u accroding to the following equations + /// + /// \f[\hat{x}_{k = F_k* x_{k-1} + B_k * u_k + w_k\f] + /// + /// \f[\hat{P}_{k} = F_{k-1} * P_{k-1} * F_{k-1}^T + Q_{k-1}\f] + /// + /// where \f$x_{k-1}\f$ is the state at the previous step, \f$u_{k}\f$ + /// 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, is the dynamics matrix + /// and Q is the covariance matrix associate with the control signal + /// + /// The control input argument should supply both + /// \f$u_k\f$ and \f$w_k\f$ vectors + /// + /// + void predict(const input_type& input); + + /// + /// \brief Updates the gain matrix \f$K\f$, the state vector \f$x\f$ 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 + R_k )^{-1} + /// x_k = \hat{x}_{k} + K_k * (z_k - H * \hat{x}_{k} + /// P_k = (I - K_k * H_k) * \hat{P}_{k} + void update(const input_type& input); + + /// + /// \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 and return *this + /// + KalmanFilter& with_matrix(const std::string& name, const matrix_type& mat); + + /// + /// \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(name);} + + /// + /// \brief Returns the name-th matrix + /// + const matrix_type& operator[](const std::string& name)const; + + /// + /// \brief Returns the name-th matrix + /// + matrix_type& operator[](const std::string& name); + +private: + + /// + /// \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 +KalmanFilter::KalmanFilter(const KalmanFilterConfig& config) + : + motion_model_ptr_(config.motion_model), + observation_model_ptr_(config.observation_model) +{ + // set the matrices + set_matrix("B", config.B); + set_matrix("P", config.P); + set_matrix("Q", config.Q); + set_matrix("K", config.K); + set_matrix("R", config.R); +} + + +template +const DynMat& +KalmanFilter::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& +KalmanFilter::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 +KalmanFilter& +KalmanFilter::with_matrix(const std::string& name, const matrix_type& mat){ + + set_matrix(name, mat); + return *this; +} + +template +void +KalmanFilter::set_matrix(const std::string& name, + const matrix_type& mat){ + + if(name != "Q" && + name != "K" && + name != "R" && + name != "P" && + name != "B"){ + throw std::logic_error("Invalid matrix name. Name: "+ + name+ + " not in [Q, K, R, P, B]"); + } + + matrices_.insert_or_assign(name, mat); +} + +template +bool +KalmanFilter::has_matrix(const std::string& name)const{ + + auto itr = matrices_.find(name); + return itr != matrices_.end(); +} + +template +typename KalmanFilter::state_type& +KalmanFilter::estimate(const input_type& input ){ + predict(input); + update(input); + + return get_state(); +} + +template +void +KalmanFilter::predict(const input_type& input ){ + + if(!motion_model_ptr_){ + throw std::runtime_error("Motion model has not been set"); + } + + auto u = bitrl::utils::template resolve>("u", input); + auto w = bitrl::utils::template resolve>("w", input); + + // make a state predicion using the + // motion model + auto& x = motion_model_ptr_->get_state(); + + // get the matrix that describes the dynamics + // of the system + auto& F = motion_model_ptr_->get_matrix("F"); + auto& B = (*this)["B"]; + + // update the state vector + x = F*x + B*u + w; + //state.set(x); + + // predict the covariance matrix + auto& P = (*this)["P"]; + auto& Q = (*this)["Q"]; + auto F_T = F.transpose(); + + P = (F*P*F_T) + Q; +} + +template +void +KalmanFilter::update(const input_type& input){ + + if(!motion_model_ptr_){ + throw std::runtime_error("Motion model has not been set"); + } + + if(!observation_model_ptr_){ + throw std::runtime_error("Observation model has not been set"); + } + + auto& x = motion_model_ptr_->get_state(); + auto& P = (*this)["P"]; + auto& R = (*this)["R"]; + + auto& H = observation_model_ptr_->get_matrix("H"); + auto H_T = H.transpose(); + + try{ + + auto S = H*P*H_T + R; + auto S_inv = S.inverse(); //inv(S); + + // compute the gain matrix + if(has_matrix("K")){ + auto& K = matrices_["K"]; + K = P*H_T*S_inv; + } + else{ + auto K = P*H_T*S_inv; + set_matrix("K", K); + } + + auto& K = (*this)["K"]; + auto z = bitrl::utils::template resolve>("z", input); + auto innovation = z - H*x; + + if(K.cols() != innovation.size()){ + throw std::runtime_error("Matrix columns: "+ + std::to_string(K.cols())+ + " not equal to vector size: "+ + std::to_string(innovation.size())); + } + + x += K*innovation; + //state.set(x); + + auto I = matrix_type::Identity(x.size(), x.size()); + + // update the error 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; + } +} + +} +} diff --git a/src/bitrl/estimation/kf_model_base.h b/src/bitrl/estimation/kf_model_base.h new file mode 100644 index 0000000..53ac3e8 --- /dev/null +++ b/src/bitrl/estimation/kf_model_base.h @@ -0,0 +1,80 @@ +#ifndef KF_MODEL_BASE_H +#define KF_MODEL_BASE_H + +#include +#include +#include //for std::invalid_argument + +namespace bitrl{ +namespace estimation{ + +template +class KFModelBase +{ +public: + + typedef MatrixType matrix_type; + + virtual void add_matrix(const std::string& name, matrix_type& mat); + virtual matrix_type& get_matrix(const std::string& mat); + virtual const matrix_type& get_matrix(const std::string& mat)const; + +protected: + + std::map matrices_; + +}; + +template +void +KFModelBase::add_matrix(const std::string& name, matrix_type& mat){ + matrices_.insert_or_assign(name, mat); +} + +template +typename KFModelBase::matrix_type& +KFModelBase::get_matrix(const std::string& name){ + + auto itr = matrices_.find(name); + if(itr != matrices_.end()){ + return itr->second; + } + + throw std::invalid_argument("Matrix not found"); +} + +template +const typename KFModelBase::matrix_type& +KFModelBase::get_matrix(const std::string& name)const{ + + auto itr = matrices_.find(name); + if(itr != matrices_.end()){ + return itr->second; + } + + throw std::invalid_argument("Matrix not found"); +} + +template +class KFMotionModelBase: public KFModelBase +{ +public: + + typedef MatrixType matrix_type; + typedef StateType state_type; + + virtual state_type& get_state(){return state_;} + virtual const state_type& get_state()const{return state_;} + virtual void set_state(const state_type& state){state_ = state;} + +protected: + + // the state + state_type state_; +}; + + +} +} + +#endif \ No newline at end of file diff --git a/src/bitrl/planning/a_star_search.h b/src/bitrl/planning/a_star_search.h new file mode 100755 index 0000000..763c31f --- /dev/null +++ b/src/bitrl/planning/a_star_search.h @@ -0,0 +1,248 @@ +#ifndef A_STAR_SEARCH_H +#define A_STAR_SEARCH_H + +#include "cubeai/base/cubeai_types.h" + +//#include "cubeai/data_structs/searchable_priority_queue.h" +//#include "kernel/utilities/map_utilities.h" + +#include +#include +#include +#include + +namespace cubeai{ +namespace astar_impl{ + + + +/// +/// \brief See Scott Meyers Effective STL Item 24 +/// for this function. If k isn't in the map +/// efficiently add pair (k,v) otherwise efficiently update +/// to v the value associated with k. Return an interator +/// to the added or modified pair +/// +template +typename MapType::iterator +add_or_update_map(MapType& map, const KeyArgType& k, const ValueArgType& v){ + +//find where k is or should be +typename MapType::iterator lb = map.lower_bound(k); + +//if lb points to a pair whose key is equivalent to k +//update the pair's value and return the iterator +if(lb != map.end() && + !(map.key_comp()(k,lb->first))){ + + lb->second = v; + return lb; + } + else{ //add pair (k,v) to map and return an iterator + //to the new map element + + typedef typename MapType::value_type MVT; + return map.insert(lb,MVT(k,v)); + + } +} + +struct fcost_astar_node_compare +{ + template + bool operator()(const NodeTp& n1,const NodeTp& n2)const; +}; + +template +bool +fcost_astar_node_compare::operator()(const NodeTp& n1,const NodeTp& n2)const{ + + if(n1.data.f_cost > n2.data.f_cost){ + return true; + } + + return false; +} + +struct id_astar_node_compare +{ + template + bool operator()(const NodeTp& n1,const NodeTp& n2)const; +}; + +template +bool +id_astar_node_compare::operator()(const NodeTp& n1,const NodeTp& n2)const{ + + if(n1.id > n2.id){ + return true; + } + + return false; +} +} //astar_impl + +/// +/// \brief Simple implementation of A* algorithm +/// at the moment the algorithm is only usable with a +/// boost_unidirected_serial_graph graph +/// +template +std::multimap +a_star_search(GraphTp& g, typename GraphTp::vertex_type& start, typename GraphTp::vertex_type& end, const H& h){ + + // for each node hodls where it + // came form + std::multimap came_from; + + if(start == end){ + + //we don't have to search for anything + came_from.insert(start.id, start.id); + return came_from; + } + + typedef typename H::cost_type cost_t; + typedef typename GraphTp::vertex_type vertex_t; + typedef typename GraphTp::adjacency_iterator adjacency_iterator; + typedef typename GraphTp::vertex_type node_t; + typedef std::priority_queue, astar_impl::fcost_astar_node_compare> searchable_priority_queue; + + std::set explored; + searchable_priority_queue open; + + //the cost of the path so far leading to this + //node is obviously zero at the start node + start.data.g_cost = 0.0; + + //calculate the fCost from start node to the goal + //at the moment this can be done only heuristically + //start.data.fcost = h(start.data.position, end.data.position); + start.data.f_cost = h(start, end); + open.push(start); + + while(!open.empty()){ + + //the vertex currently examined + const node_t cv = open.top(); + open.pop(); + + //check if this is the goal + if(cv == end){ + break; + } + + //current node is not the goal so proceed + //add it to the explored (or else called closed) set + explored.insert(cv); + + //get the adjacent neighbors + std::pair neighbors = g.get_vertex_neighbors(cv); + auto itr = neighbors.first; + + // loop over the neighbors + for(; itr != neighbors.second; itr++){ + + node_t& nv = g.get_vertex(itr); + + if(open.contains(nv)){ + continue; + } + else{ + + // we cannot move to the neighbor + // so no reason checking + if(!nv.data.can_move()){ + explored.insert(nv); + continue; + } + } + + // node id + uint_t nid = nv.id; + + //search explored set by id + auto itr = std::find_if(explored.begin(), explored.end(), + [=](const node_t& n){return (n.id == nid);}); + + //the node has been explored + if(itr != explored.end()){ + continue; + } + + //this actually the cost of the path from the current node + //to reach its neighbor + cost_t tg_cost = cv.data.g_cost + h(cv, nv);//h(cv.data.position, nv.data.position); + + if (tg_cost >= nv.data.g_cost) { + continue; //this is not a better path + } + + // This path is the best until now. Record it! + astar_impl::add_or_update_map(came_from,nv.id,cv.id); + + //came_from.put(nv.id,cv.id); + nv.data.g_cost = tg_cost; + + //acutally calculate f(nn) = g(nn)+h(nn) + nv.data.f_cost = nv.data.g_cost + h(nv, end);//h(nv.data.position, end.data.position); + + //if the neighbor not in open set add it + open.push(nv); + + } + } + + return came_from; +} + +template +std::vector +reconstruct_a_star_path(const std::multimap& map, const IdTp& start){ + + if(map.empty()){ + return std::vector(); + } + + std::vector path; + path.push_back(start); + + auto next_itr = map.find(start); + + if(next_itr == map.end()){ + + //such a key does not exist + throw std::logic_error("Key: "+std::to_string(start)+" does not exist"); + } + + IdTp next = next_itr->second; + path.push_back(next); + + while(next_itr!=map.end()){ + + next_itr = map.find(next); + if(next_itr != map.end()){ + next = next_itr->second; + path.push_back(next); + } + } + + //let's reverse the path + std::vector the_path; + the_path.reserve(path.size()); + auto itrb = path.rbegin(); + auto itre = path.rend(); + + while(itrb != itre){ + the_path.push_back(*itrb++); + } + + return the_path; +} + + +} + +#endif // A_STAR_SEARCH_H diff --git a/src/bitrl/planning/rapidly_exploring_random_tree.h b/src/bitrl/planning/rapidly_exploring_random_tree.h new file mode 100755 index 0000000..6a0fbc1 --- /dev/null +++ b/src/bitrl/planning/rapidly_exploring_random_tree.h @@ -0,0 +1,431 @@ +#ifndef RAPIDLY_EXPLORING_RANDOM_TREE_H +#define RAPIDLY_EXPLORING_RANDOM_TREE_H + +#include "cubic_engine/base/cubic_engine_types.h" +#include "kernel/data_structs/boost_serial_graph.h" +#include "kernel/base/kernel_consts.h" + +#include"boost/noncopyable.hpp" +#include +#include + + +namespace cengine { +namespace search { + +/// +/// \brief The RRT class models a Rapidly-Exploring Random Tree +/// see: http://msl.cs.uiuc.edu/~lavalle/papers/Lav98c.pdf +/// The NodeData most frequently will represent the +/// state type modeled. It corresponds to the types +/// of \f$x_{rand}\f$ and \f$x_{new}\f$ from the paper cited above. +/// The EdgeData type corresponds to the type of \f$u\f$ in the paper. +/// It is the input that should subsequently be applied to reach from +/// one state to another and this is what the applications most often will use +/// +template +class RRT: private boost::noncopyable +{ +public: + + /// + /// \brief vertex_data_t The type of node data + /// + typedef NodeData vertex_data_t; + + /// + /// \brief edge_data_t The type of the edge data + /// + typedef EdgeData edge_data_t; + + /// + /// \brief vertex_t the vertex type + /// + typedef typename kernel::BoostSerialGraph::vertex_t vertex_t; + + /// + /// \brief edge_t The edge type + /// + typedef typename kernel::BoostSerialGraph::edge_t edge_t; + + /// + /// \brief edge_iterator Edge iterator + /// + typedef typename kernel::BoostSerialGraph::edge_iterator edge_iterator; + + /// + /// \brief adjacency_iterator Adjacency iterator + /// + typedef typename kernel::BoostSerialGraph::adjacency_iterator adjacency_iterator; + + /// + /// \brief RRT Default constructor. Creates an empty tree + /// + RRT(); + + /// + /// \brief get_vertex Returns the v-th vertex + /// + vertex_t& get_vertex(uint_t v){ return tree_.get_vertex(v);} + + /// + /// \brief get_vertex Returns the v-th vertex + /// + const vertex_t& get_vertex(uint_t v)const{return tree_.get_vertex(v);} + + /// + /// \brief Access the vertex given the vertex descriptor + /// This is needed when accessing the vertices using the adjacency_iterator + /// + vertex_t& get_vertex(adjacency_iterator itr){return tree_.get_vertex(itr);} + + /// + /// \brief Access the vertex given the vertex descriptor + /// This is needed when accessing the vertices using the adjacency_iterator + /// + const vertex_t& get_vertex(adjacency_iterator itr)const{return tree_.get_vertex(itr);} + + /// + /// \brief Returns the neighboring vertices for the given vertex id + /// + std::pair + get_vertex_neighbors(uint_t id)const{ + return tree_.get_vertex_neighbors(id); + } + + /// + /// \brief Returns the neighboring vertices for the given vertex id + /// + std::pair + get_vertex_neighbors(const vertex_t& v)const{ + return tree_.get_vertex_neighbors(v); + } + + /// + /// \brief add_vertex Add a new vertex to the tree + /// \param node The new vertex to add + /// + vertex_t& add_vertex(const vertex_t& node){ return tree_.add_vertex(node.data);} + + /// + /// \brief Add a new vertex in the tree that has the given data + /// + vertex_t& add_vertex(const vertex_data_t& data); + + /// + /// \brief get_edge Returns the edge between the vertices v1 and v2 + /// \param v1 Vertex 1 + /// \param v2 Vertex 2 + /// + edge_t& get_edge(uint_t v1, uint_t v2){ + return tree_.get_edge(v1, v2); + } + + /// + /// \brief get_edge Returns the edge between the vertices v1 and v2 + /// \param v1 Vertex 1 + /// \param v2 Vertex 2 + /// + const edge_t& get_edge(uint_t v1, uint_t v2)const{ + return tree_.get_edge(v1, v2); + } + + /// + /// \brief edges Access the edges of the tree + /// + std::pair edges()const{ + return tree_.edges(); + } + + /// + /// \brief add_edge Add a new edge between the vertices v1 and v2 + /// \param v1 Vertex 1 + /// \param v2 Vertex 2 + /// + edge_t& add_edge(uint_t v1, uint_t v2){ + return tree_.add_edge(v1, v2); + } + + /// + /// \brief find_nearest_neighbor find the nearest neighbor of other node in this tree + /// \param other The node for which to find the nearest neighbor + /// \return + /// + template + const vertex_t& find_nearest_neighbor(const vertex_t& other, + const MetricTp& metric)const; + + /// + /// \brief find_nearest_neighbor find the nearest neighbor of other node in this tree + /// \param other The node for which to find the nearest neighbor + /// \return + /// + template + const vertex_t& find_nearest_neighbor(const vertex_data_t& other, + const MetricTp& metric)const; + + /// + /// \brief clear Clear the underlying tree + /// + void clear(){tree_.clear();} + + /// + /// \brief n_vertices. Returns the number of vertices of the tree + /// \return + /// + uint_t n_vertices()const{return tree_.n_vertices();} + + /// + /// \brief n_edges. Returns the number of edges of the tree + /// \return + /// + uint_t n_edges()const{return tree_.n_edges();} + + /// + /// \brief set_show_iterations_flag Set the show_iterations_ flag + /// + void set_show_iterations_flag(bool val){show_iterations_ = val;} + + /// + /// \brief Build the tree + /// + template + void build(uint_t nitrs, const vertex_t& xinit, + const StateSelector& state_selector, + const MetricTp& metric, + DynamicsTp& dynamics); + + /// + /// \brief Build the tree by using the given goal. + /// The tree expands as long as the specified number + /// of nodes has not been reached or the goal is not found yet. + /// The algorithm terminates when either the number of nodes + /// specified is built or the goal is found + /// + template + std::tuple + build(uint_t nitrs, const vertex_t& xinit, + const vertex_t& goal, const StateSelector& state_selector, + const MetricTp& metric, DynamicsTp& dynamics, real_t goal_radius); + +private: + + /// + /// \brief tree_ The underlying tree data structure + /// + kernel::BoostSerialGraph tree_; + + /// + /// \brief show_iterations_ Flag indicating if information + /// on the iterations should be displayed + /// + bool show_iterations_; + +}; + +template +RRT::RRT() + : + tree_(), + show_iterations_(false) +{} + +template +typename RRT::vertex_t& +RRT::add_vertex(const vertex_data_t& data){ + return tree_.add_vertex(data); +} + +template +template +void +RRT::build(uint_t nitrs, const vertex_t& xinit, + const StateSelector& state_selector, + const MetricTp& metric, + DynamicsTp& dynamics){ + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + // just in case clear what the tree has + clear(); + + // initialize the tree. This is the root node + tree_.add_vertex(xinit.data); + + // loop over the states and create + // the tree + for(uint_t itr=0; itr dur = end - start; + std::cout<<"Total build time: "< +template +std::tuple +RRT::build(uint_t nitrs, const vertex_t& xinit, + const vertex_t& goal, const StateSelector& state_selector, + const MetricTp& metric, DynamicsTp& dynamics, real_t goal_radius){ + + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + // just in case clear what the tree has + clear(); + + // start and goal are the same + // then there is nothing to do + if( metric(xinit, goal) < goal_radius ){ + return std::make_tuple(true, 0, 0); + } + + // initialize the tree. This is the root node + auto& root = tree_.add_vertex(xinit.data); + + // flag indicating that the goal is found + bool goal_found = false; + + // the uid of the last vertex + uint_t last_v_id = kernel::KernelConsts::invalid_size_type(); + + // loop over the states and create + // the tree + for(uint_t itr=0; (itr dur = end - start; + std::cout<<"Total build time: "< +template +const typename RRT::vertex_t& +RRT::find_nearest_neighbor(const vertex_t& other, + const MetricTp& metric)const{ + + auto dist = metric(tree_.get_vertex(0), other); + auto result = 0; + + for(uint_t v=1; v< tree_.n_vertices(); ++v){ + auto vertex_data = tree_.get_vertex(v); + + auto new_dist = metric(vertex_data, other); + + if(new_dist < dist){ + dist = new_dist; + result = v; + } + } + + return tree_.get_vertex(result); +} + +template +template +const typename RRT::vertex_t& +RRT::find_nearest_neighbor(const vertex_data_t& other, + const MetricTp& metric)const{ + + typedef typename RRT::vertex_t vertex_t; + vertex_t dummy; + dummy.data = other; + + auto dist = metric(tree_.get_vertex(0), dummy); + auto result = 0; + + for(uint_t v=1; v< tree_.n_vertices(); ++v){ + auto vertex_data = tree_.get_vertex(v); + + auto new_dist = metric(vertex_data, dummy); + + if(new_dist < dist){ + dist = new_dist; + result = v; + } + } + + return tree_.get_vertex(result); + +} + + + +} + +} + +#endif // RAPIDLY_EXPLORING_RANDOM_TREE_H diff --git a/src/bitrl/rigid_bodies/chrono_robots/rb_chrono_module.h b/src/bitrl/rigid_bodies/chrono_robots/rb_chrono_module.h index 088d3fa..e1426e8 100644 --- a/src/bitrl/rigid_bodies/chrono_robots/rb_chrono_module.h +++ b/src/bitrl/rigid_bodies/chrono_robots/rb_chrono_module.h @@ -1,7 +1,3 @@ -// -// Created by alex on 1/11/26. -// - #ifndef RB_CHRONO_MODULE_H #define RB_CHRONO_MODULE_H @@ -13,5 +9,4 @@ * and physics-based components built on Project Chrono. */ - #endif //RB_CHRONO_MODULE_H diff --git a/src/bitrl/utils/boost_graph_utils.h b/src/bitrl/utils/boost_graph_utils.h new file mode 100755 index 0000000..178708f --- /dev/null +++ b/src/bitrl/utils/boost_graph_utils.h @@ -0,0 +1,27 @@ +#ifndef BOOST_GRAPH_UTILS_H +#define BOOST_GRAPH_UTILS_H + +#include "bitrl/bitrl_types.h" +#include "bitrl/bitrl_consts.h" + +namespace bitrl +{ +namespace utils { + +template +uint_t +find_vertex(const GraphType& graph, const Predicate& pred){ + + for(uint_t v=0; v +#include +#include + +#include +#include +#include +#include + +namespace bitrl +{ +namespace utils{ + +/// +/// \brief. Representation of a graph using adjacency lists. +/// The underlying implementation uses Boost Graph library. +/// This wrapper is introduced to simplify the creation +/// and handling of the graph. +/// +template +class BoostSerialGraph +{ + +public: + + /// + /// \brief vertex_data_t The type of the vertex data + /// + typedef VertexData vertex_data_t; + + /// + /// \brief edge_data_t The type of the edge data + /// + typedef EdgeData edge_data_t; + + /// + /// \brief Class that represents the Node of a graph + /// + struct SerialGraphNode + { + vertex_data_t data; + uint_t id{bitrl::consts::INVALID_ID}; + + SerialGraphNode()=default; + SerialGraphNode(const vertex_data_t& data); + SerialGraphNode(vertex_data_t&& data); + SerialGraphNode(const SerialGraphNode& o); + + SerialGraphNode& operator=(const SerialGraphNode& o); + bool operator==(const SerialGraphNode& o)const{return this->id==o.id;} + bool operator!=(const SerialGraphNode& o)const{return !(*this==o);} + }; + + /// + /// \brief vertex_t The vertex type + /// + typedef SerialGraphNode vertex_t; + + /// + /// \brief edge_t The edge type + /// + typedef GenericLine edge_t; + +private: + + typedef boost::undirected_graph graph_type; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor_t; + typedef typename boost::graph_traits::edge_descriptor edge_descriptor_t; + +public: + + /// + /// \brief edge_iterator Edge iterator + /// + typedef typename graph_type::edge_iterator edge_iterator; + + /// + /// \brief adjacency_iterator Adjacency iterator + /// + typedef typename graph_type::adjacency_iterator adjacency_iterator; + + /// + /// \brief Constructor + /// + explicit BoostSerialGraph(uint_t nvs=0); + + /// + /// \brief Add a vertex to the graph by providing the data + /// + vertex_t& add_vertex(const VertexData& data); + + /// + /// \brief Add an edge formed by the two given vertices + /// + edge_t& add_edge(uint_t v1, uint_t v2); + + /// + /// \brief Access the i-th vertex of the graph + /// + const vertex_t& get_vertex(uint_t i)const; + + /// + /// \brief Access the i-th vertex of the graph + /// + vertex_t& get_vertex(uint_t i); + + /// + /// \brief Access the vertex given the vertex descriptor + /// This is needed when accessing the vertices using the adjacency_iterator + /// + vertex_t& get_vertex(adjacency_iterator itr); + + /// + /// \brief Access the vertex given the vertex descriptor + /// This is needed when accessing the vertices using the adjacency_iterator + /// + const vertex_t& get_vertex(adjacency_iterator itr)const; + + /// + /// \brief Access the i-th edge of the graph with endpoints + /// the given vertices + /// + const edge_t& get_edge(uint_t v1, uint_t v2)const; + + /// + /// \brief Access the i-th edge of the graph with endpoints + /// the given vertices + /// + edge_t& get_edge(uint_t v1, uint_t v2); + + /// + /// \brief edges Access the edges of the tree + /// + std::pair edges()const; + + /// + /// \brief Returns the neighboring vertices for the given vertex id + /// + std::pair get_vertex_neighbors(uint_t id)const; + + /// + /// \brief Returns the neighboring vertices for the given vertex id + /// + std::pair get_vertex_neighbors(const vertex_t& v)const; + + /// + /// \brief get_vertex_neighbors_ids Returns the ids of the vertices + /// connectected with this vertex + /// + std::vector get_vertex_neighbors_ids(uint_t id)const; + + /// + /// \brief Returns the number of vertices + /// + uint_t n_vertices()const{return g_.num_vertices();} + + /// + /// \brief Returns the maximum vertex index + /// + uint_t max_vertex_index()const noexcept{return g_.max_vertex_index();} + + /// + /// \brief Returns the number of edges + /// + uint_t n_edges()const{return g_.num_edges();} + + /// + /// \brief Returns the maximum edge index + /// + uint_t max_edge_index() const noexcept{return g_.max_edge_index();} + + /// + /// \brief Clear the graph + /// + void clear(){g_.clear();} + +private: + + /// + /// \brief The actual graph + /// + graph_type g_; +}; + +template +BoostSerialGraph::BoostSerialGraph(uint_t nv) +: +g_(nv) +{} + +template +typename BoostSerialGraph::vertex_t& +BoostSerialGraph::add_vertex(const VertexData& data){ + + typedef typename BoostSerialGraph::vertex_t vertex_t; + typedef typename BoostSerialGraph::vertex_descriptor_t vertex_descriptor_t; + uint_t idx = n_vertices(); + + //add a new vertex + vertex_descriptor_t a = boost::add_vertex(g_); + vertex_t& v = g_[a]; + v.data = data; + v.id = idx; + return v; +} + +template +typename BoostSerialGraph::edge_t& +BoostSerialGraph::add_edge(uint_t v1, uint_t v2){ + + //TODO what happens when v1 and v2 are not valid vertex indices? + //at the moment we throw an assertion + if(v1>=n_vertices() || v2 >=n_vertices()) + throw std::logic_error("Invalid vertex index v1/v2: "+ + std::to_string(v1)+ + "/"+ + std::to_string(v2)+ + " not in [0,"+ + std::to_string(n_vertices())+ + ")"); + typedef typename BoostSerialGraph::vertex_descriptor_t vertex_descriptor_t; + typedef typename BoostSerialGraph::edge_descriptor_t edge_descriptor_t; + typedef typename BoostSerialGraph::edge_t edge_t; + edge_descriptor_t et; + bool condition; + + // get the vertices that correspond to the indices + vertex_descriptor_t a = boost::vertex(v1, g_); + vertex_descriptor_t b = boost::vertex(v2, g_); + uint_t idx = n_edges(); + + // create an edge + boost::tie(et, condition) = boost::add_edge(a,b,g_); + edge_t& edge = g_[et]; + edge.set_id(idx); + return edge; +} + + +template +const typename BoostSerialGraph::vertex_t& +BoostSerialGraph::get_vertex(uint_t i)const{ + + if(i>=n_vertices()){ + throw std::logic_error("Invalid vertex index. Index "+ + std::to_string(i)+ + " not in [0,"+ + std::to_string(n_vertices())+ + ")"); + } + + typedef typename BoostSerialGraph::vertex_descriptor_t vertex_descriptor_t; + vertex_descriptor_t a = boost::vertex(i,g_); + return g_[a]; +} + +template +typename BoostSerialGraph::vertex_t& +BoostSerialGraph::get_vertex(adjacency_iterator itr){ + + return g_[*itr]; +} + +template +const typename BoostSerialGraph::vertex_t& +BoostSerialGraph::get_vertex(adjacency_iterator itr)const{ + + return g_[*itr]; +} + +template +typename BoostSerialGraph::vertex_t& +BoostSerialGraph::get_vertex(uint_t i){ + + return const_cast::vertex_t&>( + + static_cast&>(*this).get_vertex(i) + ); +} + +template +std::vector +BoostSerialGraph::get_vertex_neighbors_ids(uint_t id)const{ + + if(id >=n_vertices()){ + throw std::logic_error("Invalid vertex index. Index "+ + std::to_string(id)+ + " not in [0,"+ + std::to_string(n_vertices())+ + ")"); + } + + + auto vneighs = get_vertex_neighbors(id); + std::vector neighbors; + neighbors.reserve(std::distance(vneighs.first, vneighs.second)); + + auto start = vneighs.first; + auto end = vneighs.second; + while(start != end){ + auto& vertex = get_vertex(start); + neighbors.push_back(vertex.id); + ++start; + } + + neighbors; +} + + +template +const typename BoostSerialGraph::edge_t& +BoostSerialGraph::get_edge(uint_t v1, uint_t v2)const{ + + //TODO what happens when v1 and v2 are not valid vertex indices? + //at the moment we throw an assertion + if(v1>=n_vertices() || v2 >=n_vertices()) + throw std::logic_error("Invalid vertex index v1/v2. Indeces "+ + std::to_string(v1)+ + "/"+ + std::to_string(v2)+ + " not in [0,"+ + std::to_string(n_vertices())+ + ")"); + + typedef typename BoostSerialGraph::vertex_descriptor_t vertex_descriptor_t; + typedef typename BoostSerialGraph::edge_descriptor_t edge_descriptor_t; + vertex_descriptor_t a = boost::vertex(v1,g_); + vertex_descriptor_t b = boost::vertex(v2,g_); + + std::pair rslt = boost::edge(a,b,g_); + + return g_[rslt.first]; +} + + +template +typename BoostSerialGraph::edge_t& +BoostSerialGraph::get_edge(uint_t v1, uint_t v2){ + + return const_cast::edge_t&>( + static_cast&>(*this).get_edge(v1,v2) + ); + +} + +template +std::pair::adjacency_iterator, + typename BoostSerialGraph::adjacency_iterator> +BoostSerialGraph::get_vertex_neighbors(uint_t i)const{ + + if(i>=n_vertices()){ + throw std::logic_error("Invalid vertex index. Index "+ + std::to_string(i)+ + " not in [0,"+ + std::to_string(n_vertices())+ + ")"); + } + + typedef typename BoostSerialGraph::vertex_descriptor_t vertex_descriptor_t; + vertex_descriptor_t a = boost::vertex(i,g_); + return boost::adjacent_vertices(a, g_); +} + +template +std::pair::adjacency_iterator, + typename BoostSerialGraph::adjacency_iterator> +BoostSerialGraph::get_vertex_neighbors(const typename BoostSerialGraph::vertex_t& v)const{ + return get_vertex_neighbors(v.id); +} + +template +std::pair::edge_iterator, + typename BoostSerialGraph::edge_iterator> +BoostSerialGraph::edges()const{ + return boost::edges(g_); +} + + +template +BoostSerialGraph::SerialGraphNode::SerialGraphNode(const typename BoostSerialGraph::vertex_data_t& data_) +: +data(data_), +id(bitrl::consts::INVALID_ID) +{} + +template +BoostSerialGraph::SerialGraphNode::SerialGraphNode(typename BoostSerialGraph::vertex_data_t&& data_) +: +data(data_), +id(bitrl::consts::INVALID_ID) +{ + data_.clear(); +} + +template +BoostSerialGraph::SerialGraphNode::SerialGraphNode(const typename BoostSerialGraph::SerialGraphNode& o) +: +data(o.data), +id(o.id) +{} + + +template +typename BoostSerialGraph::SerialGraphNode& +BoostSerialGraph::SerialGraphNode::operator=(const typename BoostSerialGraph::SerialGraphNode& o){ + + if(this == &o) + return *this; + + this->data = o.data; + this->id = o.id; + return *this; + +} + +/*template +bool operator==(const typename boost_unidirected_serial_graph::vertex_t& v1, + const typename boost_unidirected_serial_graph::vertex_t& v2){ + + return (v2.id == v1.id); +} + +template +bool operator!=(const typename boost_unidirected_serial_graph::vertex_t& v1, + const typename boost_unidirected_serial_graph::vertex_t& v2){ + + return !(v2 == v1); +}*/ + +/*template +inline +bool operator==(const typename boost_unidirected_serial_graph::vertex_t& v1, + const typename boost_unidirected_serial_graph::vertex_t& v2){ + + return (v2.id == v1.id); +} + +template +inline +bool operator!=(const typename boost_unidirected_serial_graph::vertex_t& v1, + const typename boost_unidirected_serial_graph::vertex_t& v2){ + + return !(v2 == v1); +}*/ + + +} +} +#endif // BOOST_SERIAL_GRAPH_H diff --git a/src/bitrl/utils/experience_buffer.h b/src/bitrl/utils/experience_buffer.h new file mode 100644 index 0000000..d7f2d87 --- /dev/null +++ b/src/bitrl/utils/experience_buffer.h @@ -0,0 +1,223 @@ +#ifndef EXPERIENCE_BUFFER_H +#define EXPERIENCE_BUFFER_H + +#include "bitrl/bitrl_config.h" +#include "bitrl/bitrl_types.h" + +#ifdef BITRL_DEBUG +#include +#endif + +#include +#include + +#include +#include + +namespace bitrl{ +namespace utils { + +/** + * @brief The ExperienceBuffer class. A buffer based on + * boost::circular_buffer to accumulate items of type ExperienceType. + * see for example the A2C algorithm in A2C.h and rl_example_15 + */ +template +class ExperienceBuffer: private boost::noncopyable{ + +public: + + static const uint_t DEFAULT_CAPACITY = 100; + + typedef ExperienceType value_type ; + typedef ExperienceType experience_type; + + + typedef typename boost::circular_buffer::iterator iterator; + typedef typename boost::circular_buffer::const_iterator const_iterator; + typedef typename boost::circular_buffer::reverse_iterator reverse_iterator; + typedef typename boost::circular_buffer::const_reverse_iterator const_reverse_iterator; + + /// + /// \brief ExperienceBuffer + /// + ExperienceBuffer(); + + /// + /// \brief ExperienceBuffer + /// + explicit ExperienceBuffer(uint_t capacity); + + /// + /// \brief append Add the experience item in the buffer + /// + void append(const experience_type& experience); + + /// + /// \brief size + /// \return + /// + uint_t size()const noexcept{return static_cast(buffer_.size());} + + /// + /// \brief capacity + /// \return + /// + uint_t capacity()const noexcept{return static_cast(buffer_.capacity());} + + /// + /// \brief empty. Returns true if the buffer is empty + /// \return + /// + bool empty()const noexcept{return buffer_.empty();} + + /// + /// \brief clear + /// + void clear()noexcept{buffer_.clear();} + + /// + /// \brief operator [] + /// \param idx + /// \return + /// + value_type& operator[](uint_t idx){return buffer_[idx];} + + + /// + /// \brief Resize the buffer + /// + void resize(uint_t new_size, const experience_type& item = experience_type()); + + /// + /// \brief operator [] + /// \param idx + /// \return + /// + const value_type& operator[](uint_t idx)const{return buffer_[idx];} + + /// + /// \brief sample. Sample batch_size experiences from the + /// buffer and transfer them in the BatchTp container. + /// + template + void sample(uint_t batch_size, BatchTp& batch, uint_t seed=42)const; + + /// + /// \brief Copy the contents of the buffer to the given container + /// + template + void get(ContainerType& container)const; + + /// + /// \brief Copy the contents of the buffer to the given container + /// + template + ContainerType get()const; + + iterator begin(){return buffer_.begin();} + iterator end(){return buffer_.end();} + + const_iterator begin()const{return buffer_.begin();} + const_iterator end()const{return buffer_.end();} + + reverse_iterator rbegin(){return buffer_.rbegin();} + reverse_iterator rend(){return buffer_.rend();} + + const_reverse_iterator rbegin()const{return buffer_.rbegin();} + const_reverse_iterator rend()const{return buffer_.rend();} + +private: + + /// + /// \brief buffer_ + /// + boost::circular_buffer buffer_; + +}; + +template +ExperienceBuffer::ExperienceBuffer() + : + buffer_(ExperienceBuffer::DEFAULT_CAPACITY) +{} + +template +ExperienceBuffer::ExperienceBuffer(uint_t max_size) + : + buffer_(max_size) +{} + +template +void +ExperienceBuffer::append(const experience_type& experience){ + + buffer_.push_back(experience); +} + +template +void +ExperienceBuffer::resize(uint_t new_size, const experience_type& item){ + buffer_.resize(new_size, item); +} + +template +template +void +ExperienceBuffer::sample(uint_t batch_size, + BatchTp& batch, uint_t seed)const{ + +#ifdef BITRL_DEBUG + assert(!empty() && "Cannot sample from an empty buffer"); +#endif + + if(batch_size == 0){ +#ifdef BITRL_DEBUG + assert(false && "Cannot sample with zero batch_size"); +#endif + + return; + + } + + // uniformly initialize weights + std::vector weights(size(), 1.0/static_cast(size())); + std::discrete_distribution distribution(weights.begin(), weights.end()); + + std::mt19937 generator(seed); + for(uint_t c=0; c < batch_size; ++c){ + + auto idx = distribution(generator); + batch.push_back(buffer_[idx]); + } + +} + +template +template +void +ExperienceBuffer::get(ContainerType& container)const{ + + for(auto val: buffer_){ + container.push_back(val); + } +} + +template +template +ContainerType +ExperienceBuffer::get()const{ + + ContainerType container_; + container_.reserve(size()); + for(auto val: buffer_){ + container_.push_back(val); + } + + return container_; +} + +} +} + +#endif // EXPERIENCE_BUFFER_H diff --git a/src/bitrl/utils/fixed_size_priority_queue.h b/src/bitrl/utils/fixed_size_priority_queue.h new file mode 100644 index 0000000..8fc68c5 --- /dev/null +++ b/src/bitrl/utils/fixed_size_priority_queue.h @@ -0,0 +1,309 @@ +#ifndef FIXED_SIZE_PRIORITY_QUEUE_H +#define FIXED_SIZE_PRIORITY_QUEUE_H + +#include "cubeai/base/cubeai_types.h" +#include "cubeai/utils/cubeai_concepts.h" +#include +#include + +namespace cubeai { +namespace containers { + +namespace detail +{ + + +/// +/// +/// +template> +class priority_queue_common +{ +public: + + typedef T value_type; + typedef Container container_type; + + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + /// + /// \brief Constructor + /// + explicit priority_queue_common(uint_t max_size) noexcept; + + /// + /// \brief size + /// \return + /// + uint_t size()const noexcept{return pq_.size();} + + /// + /// \brief capacity + /// \return + /// + uint_t capacity()const noexcept{return capacity_;} + + /// + /// \brief empty + /// \return + /// + bool empty()const noexcept{return pq_.empty();} + + /// + /// \brief top + /// \return + /// + const value_type& top()const{return pq_.front();} + + /// + /// \brief top + /// \return + /// + value_type top(){return pq_.front();} + + + + + iterator begin(){return pq_.begin();} + iterator end(){return pq_.end();} + + const_iterator begin()const{return pq_.begin();} + const_iterator end()const{return pq_.end();} + +protected: + + /// + /// \brief push_back + /// \param value + /// + void push_back(const T& value){pq_.push_back(value);} + + /// + /// \brief pop_back + /// + void pop_back(){pq_.pop_back();} + + /// + /// \brief capacity_ + /// + uint_t capacity_; + + /// + /// \brief pq_ + /// + container_type pq_; + +}; + +template +priority_queue_common::priority_queue_common(uint_t max_size) noexcept + : + capacity_(max_size), + pq_() +{} + +} + +/// +/// \brief FixedSizeMaxPriorityQueue +/// +template, class Container = std::vector> +class FixedSizeMaxPriorityQueue: public detail::priority_queue_common +{ + +public: + + typedef T value_type; + typedef Container container_type; + typedef Compare value_compare; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + /// + /// \brief Constructor + /// + explicit FixedSizeMaxPriorityQueue(uint_t max_size) noexcept; + + /// + /// \brief push + /// \param value + /// + void push(const value_type& value); + + /// + /// \brief pop + /// + void pop()noexcept; + + /// + /// \brief top_and_pop + /// \return + /// + value_type top_and_pop(); + +private: + + /// + /// \brief value_cp_ + /// + value_compare value_cp_; +}; + + + +template +FixedSizeMaxPriorityQueue::FixedSizeMaxPriorityQueue(uint_t max_size) noexcept + : + detail::priority_queue_common(max_size), + value_cp_() +{} + + +template +void +FixedSizeMaxPriorityQueue::push(const value_type& value){ + + + if(this->size() >= this->capacity()){ + + // we need to differentiate if we + // implement a max_heap or a min_heap + auto min = std::min_element(this->begin(), this->end()); + + // only if this is better + if(*min < value){ + *min = value; + } + } + else{ + + this->push_back(value); + } + std::make_heap(this->begin(), this->end(), value_cp_); +} + +template +void +FixedSizeMaxPriorityQueue::pop()noexcept{ + + if(this->empty()){ + return; + } + + std::pop_heap(this->begin(), this->end(), value_cp_); + this->pop_back(); +} + +template +typename FixedSizeMaxPriorityQueue::value_type +FixedSizeMaxPriorityQueue::top_and_pop(){ + auto item = this->top(); + pop(); + return item; +} + + +/// +/// \brief FixedSizeMaxPriorityQueue +/// +template, class Container = std::vector> +class FixedSizeMinPriorityQueue: public detail::priority_queue_common +{ + +public: + + typedef T value_type; + typedef Container container_type; + typedef Compare value_compare; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + /// + /// \brief Constructor + /// + explicit FixedSizeMinPriorityQueue(uint_t max_size) noexcept; + + /// + /// \brief push + /// \param value + /// + void push(const value_type& value); + + /// + /// \brief pop + /// + void pop()noexcept; + + /// + /// \brief top_and_pop + /// \return + /// + value_type top_and_pop(); + +private: + + /// + /// \brief value_cp_ + /// + value_compare value_cp_; +}; + + + +template +FixedSizeMinPriorityQueue::FixedSizeMinPriorityQueue(uint_t max_size) noexcept + : + detail::priority_queue_common(max_size), + value_cp_() +{} + + +template +void +FixedSizeMinPriorityQueue::push(const value_type& value){ + + + if(this->size() >= this->capacity()){ + + // get the max element + auto max = std::max_element(this->begin(), this->end()); + + // evoke the max element if the given + // value is smaller than it + if(*max > value){ + *max = value; + } + } + else{ + + this->push_back(value); + } + std::make_heap(this->begin(), this->end(), value_cp_); +} + +template +void +FixedSizeMinPriorityQueue::pop()noexcept{ + + if(this->empty()){ + return; + } + + std::pop_heap(this->begin(), this->end(), value_cp_); + this->pop_back(); +} + +template +typename FixedSizeMinPriorityQueue::value_type +FixedSizeMinPriorityQueue::top_and_pop(){ + auto item = this->top(); + pop(); + return item; +} + +} + +} + +#endif // FIXED_SIZE_PRIORITY_QUEUE_H