Skip to content

Conversation

@ThreeMonth03
Copy link
Collaborator

Problem

As metioned in #601 , we currently get state value by iterating api predict() and update(). 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 in tests/test_linalg.py, if we pass the argument of observations and contorls, the member function kf.batch_filter(zs_sa, us_sa) would return (prior_state, prior_covariance, posterior_state, posterior_covariance).

def test_batchfilter_with_control(self):
        # ...
        kf = mm.KalmanFilterFp64(
            x=x_sa, f=f_sa, b=b_sa, h=h_sa,
            process_noise=sigma_w,
            measurement_noise=1.0,
        )
        bps = kf.batch_filter(zs_sa, us_sa)
        xs_pred, ps_pred, xs_upd, ps_upd = bps

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a 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.

Comment on lines 551 to 668
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);
}
Copy link
Collaborator Author

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.

Copy link
Member

@yungyuc yungyuc left a 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_type needs a helper class.

We are about to have a hammer. Let's find a nail.

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>;
Copy link
Member

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.

@yungyuc yungyuc requested a review from j8xixo12 January 20, 2026 14:53
@yungyuc yungyuc added the array Multi-dimensional array implementation label Jan 20, 2026
Comment on lines 505 to 512
if (u.shape(0) > 0)
{
predict(u);
}
else
{
predict();
}
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a 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_type needs 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.

Comment on lines 43 to 84
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 */
Copy link
Collaborator Author

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.

Comment on lines 550 to 640
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;
}
Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 22, 2026

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.

Comment on lines +97 to +111
.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"));
Copy link
Collaborator Author

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.

@ThreeMonth03 ThreeMonth03 requested a review from yungyuc January 21, 2026 10:50
Copy link
Member

@yungyuc yungyuc left a 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 BatchFilterHelper and give the class a better name.
  • Evaluate if BatchFilterHelper should have accessors removed and changed to be struct.
  • ALERT: Are you sure you want to force data copy for calling predict?

} /* end namespace detail */

template <typename T>
class BatchFilterHelper
Copy link
Member

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.

}

private:
SimpleArray<T> m_prior_xs;
Copy link
Member

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.


// 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));
Copy link
Member

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?

Comment on lines 550 to 640
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;
}
Copy link
Member

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.

}

template <typename T>
BatchFilterHelper<T> KalmanFilter<T>::batch_filter(array_type const & zs, std::unique_ptr<array_type> const & us_ptr)
Copy link
Member

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.


private:

void predict(std::unique_ptr<array_type> const & u_ptr)
Copy link
Member

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.

void predict_state();
void predict_state(array_type const & u);
// void predict_state();
void predict_state(std::unique_ptr<array_type> const & u_ptr);
Copy link
Member

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.

@ThreeMonth03 ThreeMonth03 force-pushed the batch_filter branch 2 times, most recently from 5a926a2 to c948592 Compare January 22, 2026 10:26
Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a 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 BatchFilterHelper and give the class a better name.
  • Evaluate if BatchFilterHelper should have accessors removed and changed to be struct.
  • The use of unique_ptr is not necessary and should be removed from the PR.

@yungyuc Please review this pull request again. Thanks.

Comment on lines +54 to +76
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 */
Copy link
Collaborator Author

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.

Comment on lines +243 to +252
/**
* @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>;
*/
Copy link
Collaborator Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

array Multi-dimensional array implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants