-
Notifications
You must be signed in to change notification settings - Fork 55
Feat: Implement batch filter #660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
ThreeMonth03
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yungyuc Please review this pull request. Thanks.
| for (size_t iter = 0; iter < z_m; ++iter) | ||
| { | ||
| for (size_t j = 0; j < z_n; ++j) | ||
| { | ||
| z(j) = zs(iter, j); | ||
| } | ||
|
|
||
| if (iter < u_m) | ||
| { | ||
| for (size_t j = 0; j < u_n; ++j) | ||
| { | ||
| u(j) = us(iter, j); | ||
| } | ||
| } | ||
| else if (iter == u_m) | ||
| { | ||
| u = array_type(small_vector<size_t>{0}); | ||
| } | ||
|
|
||
| predict_and_update(z, u, bfs, iter); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterate the data points according to the first dimension.
yungyuc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do this for now. But to really test the implementation, we need an application for the Kalman filter.
-
KalmanFilter::bf_typeneeds a helper class.
We are about to have a hammer. Let's find a nail.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| using value_type = T; | ||
| using real_type = typename detail::select_real_t<value_type>::type; | ||
| using array_type = SimpleArray<T>; | ||
| using bf_type = std::tuple<array_type, array_type, array_type, array_type>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bf_type needs a helper class. std::tuple should not be used because it is too difficult to document for each of the field.
The alias should probably be avoided once the helper class is made and named carefully.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| if (u.shape(0) > 0) | ||
| { | ||
| predict(u); | ||
| } | ||
| else | ||
| { | ||
| predict(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is possible to separate with / without control input into 2 functions to avoid this branch ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I thought that the row of control input may be missing value, but it can be deal with at a different module.
a6d6674 to
2f37b62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do this for now. But to really test the implementation, we need an application for the Kalman filter.
KalmanFilter::bf_typeneeds a helper class.We are about to have a hammer. Let's find a nail.
@yungyuc. I fix it. Please review this pull request. Thanks.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| template <typename T> | ||
| class BatchFilterHelper | ||
| { | ||
| using tuple_type = std::tuple<SimpleArray<T>, SimpleArray<T>, SimpleArray<T>, SimpleArray<T>>; | ||
|
|
||
| public: | ||
| BatchFilterHelper(size_t observation_size, size_t state_size) | ||
| { | ||
| small_vector<size_t> xs_shape{observation_size, state_size}; | ||
| small_vector<size_t> ps_shape{observation_size, state_size, state_size}; | ||
| m_prior_xs = SimpleArray<T>(xs_shape); | ||
| m_prior_ps = SimpleArray<T>(ps_shape); | ||
| m_posterior_xs = SimpleArray<T>(xs_shape); | ||
| m_posterior_ps = SimpleArray<T>(ps_shape); | ||
| } | ||
| SimpleArray<T> & prior_xs() | ||
| { | ||
| return m_prior_xs; | ||
| } | ||
| SimpleArray<T> & prior_ps() | ||
| { | ||
| return m_prior_ps; | ||
| } | ||
| SimpleArray<T> & posterior_xs() | ||
| { | ||
| return m_posterior_xs; | ||
| } | ||
| SimpleArray<T> & posterior_ps() | ||
| { | ||
| return m_posterior_ps; | ||
| } | ||
| tuple_type to_tuple() const | ||
| { | ||
| return std::make_tuple(m_prior_xs, m_prior_ps, m_posterior_xs, m_posterior_ps); | ||
| } | ||
|
|
||
| private: | ||
| SimpleArray<T> m_prior_xs; | ||
| SimpleArray<T> m_prior_ps; | ||
| SimpleArray<T> m_posterior_xs; | ||
| SimpleArray<T> m_posterior_ps; | ||
| }; /* end class BatchFilterHelper */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create the batch filter helper.
| template <typename T> | ||
| BatchFilterHelper<T> KalmanFilter<T>::batch_filter(array_type const & zs, std::unique_ptr<array_type> const & us_ptr) | ||
| { | ||
| size_t z_m = zs.shape(0); | ||
| size_t z_n = zs.shape(1); | ||
| array_type z(small_vector<size_t>{z_n}); | ||
| BatchFilterHelper<T> bfs(z_m, m_state_size); | ||
|
|
||
| std::unique_ptr<array_type> u_ptr = nullptr; | ||
| size_t u_n = 0; | ||
| if (us_ptr) | ||
| { | ||
| size_t u_m = us_ptr->shape(0); | ||
| if (u_m != z_m) | ||
| { | ||
| throw std::invalid_argument("KalmanFilter::batch_filter: The number of control inputs must match the number of measurements."); | ||
| } | ||
| u_n = us_ptr->shape(1); | ||
| u_ptr = std::make_unique<array_type>(small_vector<size_t>{u_n}); | ||
| } | ||
|
|
||
| for (size_t iter = 0; iter < z_m; ++iter) | ||
| { | ||
| for (size_t j = 0; j < z_n; ++j) | ||
| { | ||
| z(j) = zs(iter, j); | ||
| } | ||
| if (us_ptr) | ||
| { | ||
| for (size_t j = 0; j < u_n; ++j) | ||
| { | ||
| (*u_ptr)(j) = (*us_ptr)(iter, j); | ||
| } | ||
| } | ||
| predict_and_update(z, u_ptr, bfs, iter); | ||
| } | ||
| return bfs; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether it is a good idea to deal with the similar pattern by std::unique_ptr or std::optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the necessary why you need to use unique_ptr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the necessary why you need to use
unique_ptr.
The argument of control input u is optional, I'm not sure which design is the best choice:
1. Overloading: It the contol input is 0 dimensional SimpleArray, it should be seen as the null input.
2. std::optional
3. std:unique_ptr
4. Coding the similar logic twice: It looks quite long.
| .def( | ||
| "batch_filter", | ||
| [](wrapped_type & self, array_type const & zs, array_type const & us) | ||
| { | ||
| return self.batch_filter(zs, us).to_tuple(); | ||
| }, | ||
| py::arg("zs"), | ||
| py::arg("us")) | ||
| .def( | ||
| "batch_filter", | ||
| [](wrapped_type & self, array_type const & zs) | ||
| { | ||
| return self.batch_filter(zs).to_tuple(); | ||
| }, | ||
| py::arg("zs")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To pass a few return variables, the helper class should be casted to std::tuple.
yungyuc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of unique_ptr is not necessary and should be removed from the PR.
More points to address:
- Add a comment block to describe
BatchFilterHelperand give the class a better name. - Evaluate if
BatchFilterHelpershould have accessors removed and changed to bestruct. - ALERT: Are you sure you want to force data copy for calling
predict?
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| } /* end namespace detail */ | ||
|
|
||
| template <typename T> | ||
| class BatchFilterHelper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide a description block of comment for the class. I don't know what does "filter" mean and please explain that at least.
Rename it to be meaningful (and as concise as BatchFilterHelper).
BatchFilterHelper does not carry much information. It does not help readers.
Also consider to make it a struct with all member data public and remove the accessors. All the member data now are arrays. In this scenario, accessors are not very useful.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| } | ||
|
|
||
| private: | ||
| SimpleArray<T> m_prior_xs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do the member data array mean? It's hard to tell from the variable names.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
|
|
||
| // P <- 0.5(P + P^H) (m_p <- 0.5(m_p + m_p^H)) | ||
| m_p = m_p.symmetrize(); | ||
| predict(std::make_unique<array_type>(u)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make_unique constructs a new array and copy the data. It will be slow. Are you sure you want to force data copy for calling predict?
| template <typename T> | ||
| BatchFilterHelper<T> KalmanFilter<T>::batch_filter(array_type const & zs, std::unique_ptr<array_type> const & us_ptr) | ||
| { | ||
| size_t z_m = zs.shape(0); | ||
| size_t z_n = zs.shape(1); | ||
| array_type z(small_vector<size_t>{z_n}); | ||
| BatchFilterHelper<T> bfs(z_m, m_state_size); | ||
|
|
||
| std::unique_ptr<array_type> u_ptr = nullptr; | ||
| size_t u_n = 0; | ||
| if (us_ptr) | ||
| { | ||
| size_t u_m = us_ptr->shape(0); | ||
| if (u_m != z_m) | ||
| { | ||
| throw std::invalid_argument("KalmanFilter::batch_filter: The number of control inputs must match the number of measurements."); | ||
| } | ||
| u_n = us_ptr->shape(1); | ||
| u_ptr = std::make_unique<array_type>(small_vector<size_t>{u_n}); | ||
| } | ||
|
|
||
| for (size_t iter = 0; iter < z_m; ++iter) | ||
| { | ||
| for (size_t j = 0; j < z_n; ++j) | ||
| { | ||
| z(j) = zs(iter, j); | ||
| } | ||
| if (us_ptr) | ||
| { | ||
| for (size_t j = 0; j < u_n; ++j) | ||
| { | ||
| (*u_ptr)(j) = (*us_ptr)(iter, j); | ||
| } | ||
| } | ||
| predict_and_update(z, u_ptr, bfs, iter); | ||
| } | ||
| return bfs; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the necessary why you need to use unique_ptr.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| } | ||
|
|
||
| template <typename T> | ||
| BatchFilterHelper<T> KalmanFilter<T>::batch_filter(array_type const & zs, std::unique_ptr<array_type> const & us_ptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bad idea to be needing to include _ptr in the variable name to signify it's a pointer.
One of the argument is array_type and the other is unique_ptr<array_type>. I do not see why you need different argument types for the two arrays. It smells bad.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
|
|
||
| private: | ||
|
|
||
| void predict(std::unique_ptr<array_type> const & u_ptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see why you need to use unique_ptr here.
cpp/modmesh/linalg/kalman_filter.hpp
Outdated
| void predict_state(); | ||
| void predict_state(array_type const & u); | ||
| // void predict_state(); | ||
| void predict_state(std::unique_ptr<array_type> const & u_ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the reason you change many places to use unique_ptr and it does not make sense to me.
5a926a2 to
c948592
Compare
c948592 to
9d141e7
Compare
ThreeMonth03
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Add a comment block to describe
BatchFilterHelperand give the class a better name.- Evaluate if
BatchFilterHelpershould have accessors removed and changed to bestruct.- The use of
unique_ptris not necessary and should be removed from the PR.
@yungyuc Please review this pull request again. Thanks.
| struct BFType | ||
| { | ||
| using tuple_type = std::tuple<SimpleArray<T>, SimpleArray<T>, SimpleArray<T>, SimpleArray<T>>; | ||
|
|
||
| BFType(size_t observation_size, size_t state_size) | ||
| { | ||
| small_vector<size_t> xs_shape{observation_size, state_size}; | ||
| small_vector<size_t> ps_shape{observation_size, state_size, state_size}; | ||
| prior_state = SimpleArray<T>(xs_shape); | ||
| prior_state_covariance = SimpleArray<T>(ps_shape); | ||
| posterior_state = SimpleArray<T>(xs_shape); | ||
| posterior_state_covariance = SimpleArray<T>(ps_shape); | ||
| } | ||
| tuple_type to_tuple() const | ||
| { | ||
| return std::make_tuple(prior_state, prior_state_covariance, posterior_state, posterior_state_covariance); | ||
| } | ||
|
|
||
| SimpleArray<T> prior_state; | ||
| SimpleArray<T> prior_state_covariance; | ||
| SimpleArray<T> posterior_state; | ||
| SimpleArray<T> posterior_state_covariance; | ||
| }; /* end struct BFType */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to POD design, and name it as BFtype.
| /** | ||
| * @brief Predict and update in batch mode without a batch of control input us. | ||
| * | ||
| * @ref https://filterpy.readthedocs.io/en/latest/_modules/filterpy/kalman/kalman_filter.html#KalmanFilter.batch_filter | ||
| * | ||
| * @param zs A batch of measurement inputs. | ||
| * | ||
| * @see BFType<T> KalmanFilter<T>::batch_filter(array_type const & zs, array_type const & us) | ||
| * @see struct BFType<T>; | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The api design is refered from filter.py, and it is the origin of the api naming.
Problem
As metioned in #601 , we currently get state value by iterating api
predict()andupdate(). If there are many time series data-points, these api cannot deal with the data in one step.Solution
To use handy, the new api
batch_filter()is implemented in this pull request. There is a simple example intests/test_linalg.py, if we pass the argument of observations and contorls, the member functionkf.batch_filter(zs_sa, us_sa)would return(prior_state, prior_covariance, posterior_state, posterior_covariance).