Skip to content

Commit 3f1bcfe

Browse files
[filter] support no control
1 parent c0fdb0e commit 3f1bcfe

File tree

8 files changed

+204
-21
lines changed

8 files changed

+204
-21
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class kalman
200200
| --- | --- |
201201
| `State` | The type template parameter of the state vector x. State variables can be observed (measured), or hidden variables (inferred). This is the the mean of the multivariate Gaussian. |
202202
| `Output` | The type template parameter of the measurement vector z. |
203-
| `Input` | The type template parameter of the control u. |
203+
| `Input` | The type template parameter of the control u. A `void` input type can be used for systems with no input control to disable all of the input control features, the control transition matrix G support, and the other related computations from the filter. |
204204
| `Transpose` | The customization point object template parameter of the matrix transpose functor. |
205205
| `Symmetrize` | The customization point object template parameter of the matrix symmetrization functor. |
206206
| `Divide` | The customization point object template parameter of the matrix division functor. |

include/fcarouge/internal/eigen.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,9 @@ template <typename Type = double, std::size_t State = 1, std::size_t Output = 1,
257257
using kalman = fcarouge::kalman<
258258
std::conditional_t<State == 1, Type, Eigen::Vector<Type, State>>,
259259
std::conditional_t<Output == 1, Type, Eigen::Vector<Type, Output>>,
260-
std::conditional_t<Input == 0 || Input == 1, Type,
261-
Eigen::Vector<Type, Input>>,
260+
std::conditional_t<
261+
Input == 0, void,
262+
std::conditional_t<Input == 1, Type, Eigen::Vector<Type, Input>>>,
262263
transpose, symmetrize, divide, identity_matrix, UpdateTypes,
263264
PredictionTypes>;
264265

include/fcarouge/internal/kalman.hpp

+149
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,155 @@ struct kalman {
6767
//! @todo Support some more specializations, all, or disable others?
6868
};
6969

70+
template <typename State, typename Output, typename Transpose,
71+
typename Symmetrize, typename Divide, typename Identity,
72+
typename... UpdateTypes, typename... PredictionTypes>
73+
struct kalman<State, Output, void, Transpose, Symmetrize, Divide, Identity,
74+
pack<UpdateTypes...>, pack<PredictionTypes...>> {
75+
struct empty {
76+
};
77+
using state = State;
78+
using output = Output;
79+
using input = empty;
80+
using estimate_uncertainty =
81+
std::decay_t<std::invoke_result_t<Divide, State, State>>;
82+
using process_uncertainty =
83+
std::decay_t<std::invoke_result_t<Divide, State, State>>;
84+
using output_uncertainty =
85+
std::decay_t<std::invoke_result_t<Divide, Output, Output>>;
86+
using state_transition =
87+
std::decay_t<std::invoke_result_t<Divide, State, State>>;
88+
using output_model =
89+
std::decay_t<std::invoke_result_t<Divide, Output, State>>;
90+
using input_control = empty;
91+
using gain = std::decay_t<std::invoke_result_t<Transpose, output_model>>;
92+
using innovation = output;
93+
using innovation_uncertainty = output_uncertainty;
94+
using observation_state_function =
95+
std::function<output_model(const state &, const UpdateTypes &...)>;
96+
using noise_observation_function = std::function<output_uncertainty(
97+
const state &, const output &, const UpdateTypes &...)>;
98+
using transition_state_function = std::function<state_transition(
99+
const state &, const PredictionTypes &...)>;
100+
using noise_process_function = std::function<process_uncertainty(
101+
const state &, const PredictionTypes &...)>;
102+
using transition_control_function = empty;
103+
using transition_function =
104+
std::function<state(const state &, const PredictionTypes &...)>;
105+
using observation_function =
106+
std::function<output(const state &, const UpdateTypes &...arguments)>;
107+
108+
//! @todo Is there a simpler way to initialize to the zero matrix?
109+
state x{ 0 * Identity().template operator()<state>() };
110+
estimate_uncertainty p{
111+
Identity().template operator()<estimate_uncertainty>()
112+
};
113+
process_uncertainty q{
114+
0 * Identity().template operator()<process_uncertainty>()
115+
};
116+
output_uncertainty r{ 0 *
117+
Identity().template operator()<output_uncertainty>() };
118+
output_model h{ Identity().template operator()<output_model>() };
119+
state_transition f{ Identity().template operator()<state_transition>() };
120+
gain k{ Identity().template operator()<gain>() };
121+
innovation y{ 0 * Identity().template operator()<innovation>() };
122+
innovation_uncertainty s{
123+
Identity().template operator()<innovation_uncertainty>()
124+
};
125+
output z{ 0 * Identity().template operator()<output>() };
126+
127+
//! @todo Should we pass through the reference to the state x or have the user
128+
//! access it through k.x() when needed? Where does the practical/performance
129+
//! tradeoff leans toward? For the general case? For the specialized cases?
130+
//! Same question applies to other parameters.
131+
//! @todo Pass the arguments by universal reference?
132+
observation_state_function observation_state_h{
133+
[this](const state &x, const UpdateTypes &...arguments) -> output_model {
134+
static_cast<void>(x);
135+
(static_cast<void>(arguments), ...);
136+
return h;
137+
}
138+
};
139+
noise_observation_function noise_observation_r{
140+
[this](const state &x, const output &z,
141+
const UpdateTypes &...arguments) -> output_uncertainty {
142+
static_cast<void>(x);
143+
static_cast<void>(z);
144+
(static_cast<void>(arguments), ...);
145+
return r;
146+
}
147+
};
148+
transition_state_function transition_state_f{
149+
[this](const state &x,
150+
const PredictionTypes &...arguments) -> state_transition {
151+
static_cast<void>(x);
152+
(static_cast<void>(arguments), ...);
153+
return f;
154+
}
155+
};
156+
noise_process_function noise_process_q{
157+
[this](const state &x,
158+
const PredictionTypes &...arguments) -> process_uncertainty {
159+
static_cast<void>(x);
160+
(static_cast<void>(arguments), ...);
161+
return q;
162+
}
163+
};
164+
transition_function transition{
165+
[this](const state &x, const PredictionTypes &...arguments) -> state {
166+
(static_cast<void>(arguments), ...);
167+
return f * x;
168+
}
169+
};
170+
observation_function observation{
171+
[this](const state &x, const UpdateTypes &...arguments) -> output {
172+
(static_cast<void>(arguments), ...);
173+
return h * x;
174+
}
175+
};
176+
177+
Transpose transpose;
178+
Divide divide;
179+
Symmetrize symmetrize;
180+
Identity identity;
181+
182+
//! @todo Do we want to store i - k * h in a temporary result for reuse? Or
183+
//! does the compiler/linker do it for us?
184+
//! @todo Do we want to support extended custom y = output_difference(z,
185+
//! observation(x))?
186+
inline constexpr void update(const UpdateTypes &...arguments,
187+
const auto &...output_z)
188+
{
189+
const auto i{ identity.template operator()<estimate_uncertainty>() };
190+
191+
z = output{ output_z... };
192+
h = observation_state_h(x, arguments...); // x, z, args?
193+
r = noise_observation_r(x, z, arguments...);
194+
s = h * p * transpose(h) + r;
195+
k = divide(p * transpose(h), s);
196+
y = z - observation(x, arguments...);
197+
x = x + k * y;
198+
p = symmetrize(estimate_uncertainty{
199+
(i - k * h) * p * transpose(i - k * h) + k * r * transpose(k) });
200+
}
201+
202+
inline constexpr void predict(const PredictionTypes &...arguments)
203+
{
204+
f = transition_state_f(x, arguments...);
205+
q = noise_process_q(x, arguments...);
206+
x = transition(x, arguments...);
207+
p = symmetrize(estimate_uncertainty{ f * p * transpose(f) + q });
208+
}
209+
210+
inline constexpr void
211+
operator()(const PredictionTypes &...prediction_arguments,
212+
const UpdateTypes &...update_arguments, const auto &...output_z)
213+
{
214+
update(update_arguments..., output_z...);
215+
predict(prediction_arguments...);
216+
}
217+
};
218+
70219
template <typename State, typename Output, typename Input, typename Transpose,
71220
typename Symmetrize, typename Divide, typename Identity,
72221
typename... UpdateTypes, typename... PredictionTypes>

include/fcarouge/kalman.hpp

+23-8
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ struct identity_matrix {
8383
//! the measurement (Z, R), the measurement function H, and if the system has
8484
//! control inputs (U, B). Designing a filter is as much art as science.
8585
//!
86-
//! @tparam State The type template parameter of the state vector X. State
86+
//! @tparam State The type template parameter of the state vector x. State
8787
//! variables can be observed (measured), or hidden variables (inferred). This
8888
//! is the the mean of the multivariate Gaussian.
89-
//! @tparam Output The type template parameter of the measurement vector Z.
90-
//! @tparam Input The type template parameter of the control U.
89+
//! @tparam Output The type template parameter of the measurement vector z.
90+
//! @tparam Input The type template parameter of the control u. A `void` input
91+
//! type can be used for systems with no input control to disable all of the
92+
//! input control features, the control transition matrix G support, and the
93+
//! other related computations from the filter.
9194
//! @tparam Transpose The customization point object template parameter of the
9295
//! matrix transpose functor.
9396
//! @tparam Symmetrize The customization point object template parameter of the
@@ -150,7 +153,7 @@ struct identity_matrix {
150153
//! re-initializations but to what default?
151154
//! @todo Could the Input be void by default? Or empty?
152155
template <
153-
typename State = double, typename Output = State, typename Input = State,
156+
typename State = double, typename Output = State, typename Input = void,
154157
typename Transpose = std::identity, typename Symmetrize = std::identity,
155158
typename Divide = std::divides<void>, typename Identity = identity_matrix,
156159
typename UpdateTypes = internal::empty_pack_t,
@@ -185,6 +188,8 @@ class kalman
185188
using output = typename implementation::output;
186189

187190
//! @brief Type of the control vector U.
191+
//!
192+
//! @todo Conditionally remove this member type when no input is present.
188193
using input = typename implementation::input;
189194

190195
//! @brief Type of the estimated correlated variance matrix P.
@@ -211,6 +216,8 @@ class kalman
211216
//! @brief Type of the control transition matrix G.
212217
//!
213218
//! @details Also known as B.
219+
//!
220+
//! @todo Conditionally remove this member type when no input is present.
214221
using input_control = typename implementation::input_control;
215222

216223
//! @brief Type of the gain matrix K.
@@ -370,12 +377,14 @@ class kalman
370377

371378
//! @brief Returns the last control vector U.
372379
//!
380+
//! @details Not present when the filter has no input.
381+
//!
373382
//! @return The last control vector U.
374383
//!
375384
//! @complexity Constant.
376385
[[nodiscard("The returned control vector U is unexpectedly "
377386
"discarded.")]] inline constexpr auto
378-
u() const -> input
387+
u() const -> input requires(!std::is_void_v<Input>)
379388
{
380389
return filter.u;
381390
}
@@ -860,7 +869,7 @@ class kalman
860869
//! @complexity Constant.
861870
[[nodiscard("The returned control transition matrix G is unexpectedly "
862871
"discarded.")]] inline constexpr auto
863-
g() const -> input_control
872+
g() const -> input_control requires(!std::is_void_v<Input>)
864873
{
865874
return filter.g;
866875
}
@@ -870,7 +879,8 @@ class kalman
870879
//! @param value The copied control transition matrix G.
871880
//!
872881
//! @complexity Constant.
873-
inline constexpr void g(const input_control &value)
882+
inline constexpr void
883+
g(const input_control &value) requires(!std::is_void_v<Input>)
874884
{
875885
filter.g = value;
876886
}
@@ -880,7 +890,8 @@ class kalman
880890
//! @param value The moved control transition matrix G.
881891
//!
882892
//! @complexity Constant.
883-
inline constexpr void g(input_control &&value)
893+
inline constexpr void
894+
g(input_control &&value) requires(!std::is_void_v<Input>)
884895
{
885896
filter.g = std::move(value);
886897
}
@@ -894,6 +905,7 @@ class kalman
894905
//!
895906
//! @complexity Constant.
896907
inline constexpr void g(const auto &value, const auto &...values) requires(
908+
!std::is_void_v<Input> &&
897909
!std::is_assignable_v<
898910
typename implementation::transition_control_function,
899911
std::decay_t<decltype(value)>>)
@@ -910,6 +922,7 @@ class kalman
910922
//!
911923
//! @complexity Constant.
912924
inline constexpr void g(auto &&value, auto &&...values) requires(
925+
!std::is_void_v<Input> &&
913926
!std::is_assignable_v<
914927
typename implementation::transition_control_function,
915928
std::decay_t<decltype(value)>>)
@@ -929,6 +942,7 @@ class kalman
929942
//!
930943
//! @complexity Constant.
931944
inline constexpr void g(const auto &callable) requires(
945+
!std::is_void_v<Input> &&
932946
std::is_assignable_v<typename implementation::transition_control_function,
933947
std::decay_t<decltype(callable)>>)
934948
{
@@ -945,6 +959,7 @@ class kalman
945959
//!
946960
//! @complexity Constant.
947961
inline constexpr void g(auto &&callable) requires(
962+
!std::is_void_v<Input> &&
948963
std::is_assignable_v<typename implementation::transition_control_function,
949964
std::decay_t<decltype(callable)>>)
950965
{

include/fcarouge/kalman_eigen.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ using identity_matrix = internal::identity_matrix;
8585
//! matrices. The parameters are also propagated to the state transition
8686
//! function object f.
8787
template <typename Type = double, std::size_t State = 1, std::size_t Output = 1,
88-
std::size_t Input = 1,
88+
std::size_t Input = 0,
8989
typename UpdateTypes = fcarouge::internal::empty_pack_t,
9090
typename PredictionTypes = fcarouge::internal::empty_pack_t>
9191
using kalman =

sample/dog_position.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace
3232
//!
3333
//! @example dog_position.cpp
3434
[[maybe_unused]] auto dog_position{ [] {
35+
using kalman = fcarouge::kalman<double, double, double>;
3536
kalman k;
3637

3738
// Initialization

test/f.cpp

+4-8
Original file line numberDiff line numberDiff line change
@@ -77,28 +77,24 @@ namespace
7777
}
7878

7979
{
80-
const auto f{ [](const kalman::state &x,
81-
const kalman::input &u) -> kalman::state_transition {
80+
const auto f{ [](const kalman::state &x) -> kalman::state_transition {
8281
static_cast<void>(x);
83-
static_cast<void>(u);
8482
return 6.;
8583
} };
8684
k.f(f);
8785
assert(k.f() == 5);
88-
k.predict(0.);
86+
k.predict();
8987
assert(k.f() == 6);
9088
}
9189

9290
{
93-
const auto f{ [](const kalman::state &x,
94-
const kalman::input &u) -> kalman::state_transition {
91+
const auto f{ [](const kalman::state &x) -> kalman::state_transition {
9592
static_cast<void>(x);
96-
static_cast<void>(u);
9793
return 7.;
9894
} };
9995
k.f(std::move(f));
10096
assert(k.f() == 6);
101-
k.predict(0.);
97+
k.predict();
10298
assert(k.f() == 7);
10399
}
104100

test/initialization.cpp

+22-1
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,29 @@ namespace fcarouge::test
4545
{
4646
namespace
4747
{
48-
//! @test Verifies default values are initialized for single-dimension filters.
48+
//! @test Verifies default values are initialized for single-dimension filters
49+
//! without input control.
50+
[[maybe_unused]] auto defaults110{ [] {
51+
kalman k;
52+
53+
assert(k.f() == 1);
54+
assert(k.h() == 1);
55+
assert(k.k() == 1);
56+
assert(k.p() == 1);
57+
assert(k.q() == 0 && "No process noise by default.");
58+
assert(k.r() == 0 && "No observation noise by default.");
59+
assert(k.s() == 1);
60+
assert(k.x() == 0 && "Origin state.");
61+
assert(k.y() == 0);
62+
assert(k.z() == 0);
63+
64+
return 0;
65+
}() };
66+
67+
//! @test Verifies default values are initialized for single-dimension filters
68+
//! with input control.
4969
[[maybe_unused]] auto defaults111{ [] {
70+
using kalman = fcarouge::kalman<double, double, double>;
5071
kalman k;
5172

5273
assert(k.f() == 1);

0 commit comments

Comments
 (0)