bitrl & cuberl Documentation
Simulation engine for reinforcement learning agents
Loading...
Searching...
No Matches
ekf_sensor_fusion.h
Go to the documentation of this file.
1//
2// Created by alex on 11/23/25.
3//
4
5#ifndef EKF_SENSOR_FUSION_H
6#define EKF_SENSOR_FUSION_H
7
8#include "Eigen/Dense"
9#include "bitrl/bitrl_types.h"
12
13#include <chrono>
14#include <iostream>
15#include <map>
16#include <memory>
17#include <string>
18#include <unordered_map>
19
20namespace bitrl
21{
22namespace sensors
23{
24template <typename MotionModelType, typename ObservationModelType> class EKFSensorFusion
25{
26 public:
27 typedef MotionModelType motion_model_type;
28 typedef ObservationModelType observation_model_type;
30 typedef std::string topic_type;
31
34 EKFSensorFusion(motion_model_type &motion_model, observation_model_type &observation_model);
36
37 void step();
38
41 void predict(const std::string &input_message, const std::string &sensor_message,
42 const topic_type &topic);
43
44 void update(const std::string &sensor_message, const topic_type &topic);
45
46 EKFSensorFusion &with_matrix(const std::string &name, const matrix_type &mat);
47
48 std::optional<std::string>
49 read_from_topic(const topic_type &topic,
50 std::chrono::milliseconds timeout = std::chrono::milliseconds(1000));
51
52 void add_input_topic(const std::string &mqtt_url, const topic_type &topic);
53
54 void add_sensor_topic(const std::string &mqtt_url, const topic_type &topic,
55 SensorTypeEnum sensor_type);
56
58 const matrix_type &operator[](const std::string &name) const;
59
61 matrix_type &operator[](const std::string &name);
62
64 bool has_matrix(const std::string &name) const;
65
66 private:
67 motion_model_type *motion_model_;
68 const observation_model_type *observation_model_;
69
71 std::map<std::string, matrix_type> matrices_;
72 std::unique_ptr<bitrl::network::MqttSubscriber> input_subscriber_;
73 std::unordered_map<topic_type, std::unique_ptr<bitrl::network::MqttSubscriber>> sensors_;
74 std::unordered_map<topic_type, SensorTypeEnum> topic_to_sensor_type_;
75};
76
77template <typename MotionModelType, typename ObservationModelType>
79 motion_model_type &motion_model, observation_model_type &observation_model)
80 : motion_model_(&motion_model), observation_model_(&observation_model), sensors_(),
81 topic_to_sensor_type_()
82{
83}
84
85template <typename MotionModelType, typename ObservationModelType>
89
90template <typename MotionModelType, typename ObservationModelType>
92 const std::string &name) const
93{
94 auto itr = matrices_.find(name);
95 return itr != matrices_.end();
96}
97
98template <typename MotionModelType, typename ObservationModelType>
101{
102
103 auto itr = matrices_.find(name);
104 if (itr == matrices_.end())
105 {
106 throw std::invalid_argument("Matrix: " + name + " does not exist");
107 }
108 return itr->second;
109}
110
111template <typename MotionModelType, typename ObservationModelType>
114{
115
116 auto itr = matrices_.find(name);
117 if (itr == matrices_.end())
118 {
119 throw std::invalid_argument("Matrix: " + name + " does not exist");
120 }
121 return itr->second;
122}
123
124template <typename MotionModelType, typename ObservationModelType>
127 const matrix_type &mat)
128{
129 if (name != "Q" && name != "K" && name != "R" && name != "P")
130 {
131 throw std::logic_error("Invalid matrix name. Name: " + name + " not in [Q, K, R, P]");
132 }
133 matrices_.insert_or_assign(name, mat);
134 return *this;
135}
136
137template <typename MotionModelType, typename ObservationModelType>
139 const std::string &mqtt_url, const topic_type &topic)
140{
141 input_subscriber_ = std::make_unique<bitrl::network::MqttSubscriber>(mqtt_url, topic);
142 input_subscriber_->connect();
143}
144
145template <typename MotionModelType, typename ObservationModelType>
147 const std::string &mqtt_url, const std::string &topic, SensorTypeEnum sensor_type)
148{
149 auto subscriber = std::make_unique<bitrl::network::MqttSubscriber>(mqtt_url, topic);
150 subscriber->connect(); // connect to MQTT broker
151 sensors_[topic] = std::move(subscriber);
152 topic_to_sensor_type_.insert({topic, sensor_type});
153}
154
155template <typename MotionModelType, typename ObservationModelType>
157 const std::string &topic, std::chrono::milliseconds timeout)
158{
159
160 auto topic_itr = sensors_.find(topic);
161 if (topic_itr == sensors_.end())
162 {
163 return std::nullopt;
164 }
165
166 auto topic_message = topic_itr->second->poll(timeout);
167
168 if (!topic_message.has_value())
169 {
170 return std::nullopt;
171 }
172
173 return topic_message;
174}
175
176template <typename MotionModelType, typename ObservationModelType>
178{
179
180 // loop over the sensor topics and perform and update
181 for (auto &sensor : sensors_)
182 {
183 auto sensor_message = read_from_topic(sensor.first, std::chrono::milliseconds(200));
184
185 // if we have a message, estimate and update
186 // the state
187 if (sensor_message.has_value())
188 {
189 // read the input assocoate with the sensor read
190 auto input_message = input_subscriber_->poll(std::chrono::milliseconds(1000));
191
192 if (input_message.has_value())
193 {
194 predict(input_message.value(), sensor_message.value(), sensor.first);
195 update(sensor_message.value(), sensor.first);
196 }
197 else
198 {
199 std::cout << "No input message received: " << std::endl;
200 }
201 }
202 }
203}
204
205template <typename MotionModelType, typename ObservationModelType>
207 const std::string &input_message, const std::string &sensor_message, const topic_type &topic)
208{
209
210 // make a state prediction using the motion model
211 motion_model_->evaluate(input_message, sensor_message, topic);
212
213 std::cout << "Model evaluation finished...: " << std::endl;
214
215 auto &P = (*this)["P"];
216 auto &Q = (*this)["Q"];
217
218 // get the Jacobian description for the
219 // motion model
220 auto &F = motion_model_->get_matrix("F");
221 auto F_T = F.transpose();
222
223 P = F * P * F_T;
224
225 if (motion_model_->has_matrix("L"))
226 {
227 auto &L = motion_model_->get_matrix("L");
228 auto L_T = L.transpose();
229 P += L * Q * L_T;
230 }
231 else
232 {
233 // Most EKFs simply add Q
234 P += Q;
235 }
236}
237
238template <typename MotionModelType, typename ObservationModelType>
240 const std::string &sensor_message, const topic_type &topic)
241{
242 std::cout << "Update sensor message: " << sensor_message << std::endl;
243 auto &state = motion_model_->get_state();
244 auto &P = (*this)["P"];
245 auto &R = (*this)["R"];
246
247 auto z = observation_model_->convert_sensor_message(sensor_message, topic);
248 auto zpred = observation_model_->evaluate(state.as_vector());
249
250 // get the Jacobian of the observation
251 // model
252 auto &H = observation_model_->get_matrix("H");
253 auto H_T = H.transpose();
254
255 // compute \partial{h}/\partial{v} the jacobian of the observation model
256 // w.r.t the error vector
257 try
258 {
259
260 DynMat<real_t> S = H * P * H_T;
261 if (observation_model_->has_matrix("M"))
262 {
263 auto &M = observation_model_->get_matrix("M");
264 auto M_T = M.transpose();
265 S += M * R * M_T;
266 }
267 else
268 {
269 S += R;
270 }
271
272 auto S_inv = S.inverse();
273
274 if (has_matrix("K"))
275 {
276 auto &K = (*this)["K"];
277 K = P * H_T * S_inv;
278 }
279 else
280 {
281 auto K = P * H_T * S_inv;
282 with_matrix("K", K);
283 }
284
285 auto &K = (*this)["K"];
286 auto innovation = z - zpred;
287
288 // we need to take the transpose
289 auto innovation_t = innovation.transpose();
290
291 if (K.cols() != innovation_t.rows())
292 {
293 throw std::runtime_error(
294 "Matrix columns: " + std::to_string(K.cols()) +
295 " not equal to vector size: " + std::to_string(innovation_t.rows()));
296 }
297
298 // auto vec = K * innovation_t;
299 state += K * innovation_t;
300
301 // IdentityMatrix<real_t> I(state.size());
302 auto I = matrix_type::Identity(state.size(), state.size());
304 P = (I - K * H) * P;
305 }
306 catch (...)
307 {
308
309 // this is a singular matrix what
310 // should we do? Simply use the predicted
311 // values and log the fact that there was a singular matrix
312 throw;
313 }
314}
315
316} // namespace sensors
317} // namespace bitrl
318
319#endif // SENSOR_FUSION_H
Definition ekf_sensor_fusion.h:25
void step()
Definition ekf_sensor_fusion.h:177
std::optional< std::string > read_from_topic(const topic_type &topic, std::chrono::milliseconds timeout=std::chrono::milliseconds(1000))
Definition ekf_sensor_fusion.h:156
~EKFSensorFusion()
Definition ekf_sensor_fusion.h:86
EKFSensorFusion & with_matrix(const std::string &name, const matrix_type &mat)
Definition ekf_sensor_fusion.h:126
EKFSensorFusion(motion_model_type &motion_model, observation_model_type &observation_model)
Definition ekf_sensor_fusion.h:78
void update(const std::string &sensor_message, const topic_type &topic)
Definition ekf_sensor_fusion.h:239
void add_sensor_topic(const std::string &mqtt_url, const topic_type &topic, SensorTypeEnum sensor_type)
Definition ekf_sensor_fusion.h:146
MotionModelType motion_model_type
Definition ekf_sensor_fusion.h:27
const matrix_type & operator[](const std::string &name) const
Returns the name-th matrix.
Definition ekf_sensor_fusion.h:100
DynMat< real_t > matrix_type
Definition ekf_sensor_fusion.h:29
ObservationModelType observation_model_type
Definition ekf_sensor_fusion.h:28
bool has_matrix(const std::string &name) const
Returns true if the matrix with the given name exists.
Definition ekf_sensor_fusion.h:91
void predict(const std::string &input_message, const std::string &sensor_message, const topic_type &topic)
Definition ekf_sensor_fusion.h:206
std::string topic_type
Definition ekf_sensor_fusion.h:30
void add_input_topic(const std::string &mqtt_url, const topic_type &topic)
Definition ekf_sensor_fusion.h:138
SensorTypeEnum
Definition sensor_type_enum.h:14
Definition bitrl_consts.h:14
Eigen::MatrixX< T > DynMat
Dynamically sized matrix to use around the library.
Definition bitrl_types.h:49