ros2_control - rolling
Loading...
Searching...
No Matches
filter_traits.hpp
1// Copyright (c) 2025, ros2_control development team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef CONTROL_TOOLBOX__FILTER_TRAITS_HPP_
16#define CONTROL_TOOLBOX__FILTER_TRAITS_HPP_
17
18#define EIGEN_INITIALIZE_MATRICES_BY_NAN
19
20#include <fmt/core.h>
21#include <Eigen/Dense>
22#include <algorithm>
23#include <cmath>
24#include <limits>
25#include <stdexcept>
26#include <vector>
27
28#include "geometry_msgs/msg/wrench_stamped.hpp"
29
30namespace control_toolbox
31{
32
33template <typename T>
34struct FilterTraits;
35
36// Wrapper around std::vector<U> to be used as
37// the std::vector<U> StorageType specialization.
38// This is a workaround for the fact that
39// std::vector<U>'s operator* and operator+ cannot be overloaded.
40template <typename U>
42{
43 std::vector<U> data;
44
45 FilterVector() = default;
46
47 explicit FilterVector(const std::vector<U> & vec) : data(vec) {}
48
49 explicit FilterVector(size_t size, const U & initial_value = U{}) : data(size, initial_value) {}
50
51 FilterVector operator*(const U & scalar) const
52 {
53 FilterVector result = *this;
54 for (auto & val : result.data)
55 {
56 val *= scalar;
57 }
58 return result;
59 }
60
61 FilterVector operator+(const FilterVector & other) const
62 {
63 if (data.size() != other.data.size())
64 {
65 throw std::runtime_error(
66 fmt::format(
67 "Vectors must be of the same size for addition ({} vs {}).", data.size(),
68 other.data.size()));
69 }
70 FilterVector result = *this;
71 for (size_t i = 0; i < data.size(); ++i)
72 {
73 result.data[i] += other.data[i];
74 }
75 return result;
76 }
77
78 size_t size() const { return data.size(); }
79};
80
81// Enable scalar * FilterVector
82template <typename U>
83inline FilterVector<U> operator*(const U & scalar, const FilterVector<U> & vec)
84{
85 return vec * scalar;
86}
87
88template <typename T>
90{
91 using StorageType = T;
92
93 static void initialize(StorageType & storage)
94 {
95 storage = T{std::numeric_limits<T>::quiet_NaN()};
96 }
97
98 static bool is_nan(const StorageType & storage) { return std::isnan(storage); }
99
100 static bool is_finite(const StorageType & storage) { return std::isfinite(storage); }
101
102 static bool is_empty(const StorageType & storage)
103 {
104 (void)storage;
105 return false;
106 }
107
108 static void assign(StorageType & storage, const StorageType & data_in) { storage = data_in; }
109
110 static void validate_input(const T & data_in, const StorageType & filtered_value, T & data_out)
111 {
112 (void)data_in;
114 (void)data_out; // Suppress unused warnings
115 }
116
117 static void add_metadata(StorageType & storage, const StorageType & data_in)
118 {
119 (void)storage;
120 (void)data_in;
121 }
122};
123
124template <>
125struct FilterTraits<geometry_msgs::msg::WrenchStamped>
126{
127 using StorageType = Eigen::Matrix<double, 6, 1>;
128 using DataType = geometry_msgs::msg::WrenchStamped;
129
130 static void initialize(StorageType & storage)
131 {
132 // Evocation of the default constructor through EIGEN_INITIALIZE_MATRICES_BY_NAN
133 storage = StorageType();
134 }
135
136 static bool is_nan(const StorageType & storage) { return storage.hasNaN(); }
137
138 static bool is_finite(const DataType & data)
139 {
140 return std::isfinite(data.wrench.force.x) && std::isfinite(data.wrench.force.y) &&
141 std::isfinite(data.wrench.force.z) && std::isfinite(data.wrench.torque.x) &&
142 std::isfinite(data.wrench.torque.y) && std::isfinite(data.wrench.torque.z);
143 }
144
145 static bool is_empty(const StorageType & storage)
146 {
147 (void)storage;
148 return false;
149 }
150
151 static void assign(DataType & data_in, const StorageType & storage)
152 {
153 data_in.wrench.force.x = storage[0];
154 data_in.wrench.force.y = storage[1];
155 data_in.wrench.force.z = storage[2];
156 data_in.wrench.torque.x = storage[3];
157 data_in.wrench.torque.y = storage[4];
158 data_in.wrench.torque.z = storage[5];
159 }
160
161 static void assign(StorageType & storage, const DataType & data_in)
162 {
163 storage[0] = data_in.wrench.force.x;
164 storage[1] = data_in.wrench.force.y;
165 storage[2] = data_in.wrench.force.z;
166 storage[3] = data_in.wrench.torque.x;
167 storage[4] = data_in.wrench.torque.y;
168 storage[5] = data_in.wrench.torque.z;
169 }
170
171 static void assign(StorageType & storage, const StorageType & data_in) { storage = data_in; }
172
173 static void validate_input(
174 const DataType & data_in, const StorageType & filtered_value, DataType & data_out)
175 {
176 (void)filtered_value; // filtered_value has no header
177
178 // Compare new input's frame_id with previous output's frame_id,
179 // but only if it existed (filter_chains does not initialize output)
180 if (!data_out.header.frame_id.empty() && data_in.header.frame_id != data_out.header.frame_id)
181 {
182 throw std::runtime_error(
183 "Frame ID changed between filter updates! Out: " + data_out.header.frame_id +
184 ", In: " + data_in.header.frame_id);
185 }
186 }
187
188 static void add_metadata(DataType & data_out, const DataType & data_in)
189 {
190 data_out.header = data_in.header;
191 }
192};
193
194template <typename U>
195struct FilterTraits<std::vector<U>>
196{
198 using DataType = std::vector<U>;
199
200 static void initialize(StorageType & storage) { (void)storage; }
201
202 static bool is_finite(const StorageType & storage)
203 {
204 return std::all_of(
205 storage.data.begin(), storage.data.end(), [](U val) { return std::isfinite(val); });
206 }
207
208 static bool is_finite(const DataType & storage)
209 {
210 return std::all_of(storage.begin(), storage.end(), [](U val) { return std::isfinite(val); });
211 }
212
213 static bool is_empty(const StorageType & storage) { return storage.data.empty(); }
214
215 static bool is_nan(const StorageType & storage)
216 {
217 for (const auto & val : storage.data)
218 {
219 if (std::isnan(val))
220 {
221 return true;
222 }
223 }
224
225 return false;
226 }
227
228 static void assign(StorageType & storage, const DataType & data_in) { storage.data = data_in; }
229
230 static void assign(DataType & storage, const StorageType & data_in) { storage = data_in.data; }
231
232 static void assign(StorageType & storage, const StorageType & data_in)
233 {
234 storage.data = data_in.data;
235 }
236
237 static void validate_input(
238 const DataType & data_in, const StorageType & filtered_value, DataType & data_out)
239 {
240 if (data_in.size() != filtered_value.size())
241 {
242 throw std::runtime_error(
243 fmt::format(
244 "Input vector size ({}) does not match internal state size ({}).", data_in.size(),
245 filtered_value.size()));
246 }
247 // Compare new input's size with output's size,
248 // but only if it existed (filter_chains does not initialize output)
249 if (!data_out.empty() && data_out.size() != data_in.size())
250 {
251 throw std::runtime_error(
252 fmt::format(
253 "Input and output vectors must be the same size, {} vs {}.", data_out.size(),
254 data_in.size()));
255 }
256 }
257
258 static void add_metadata(DataType & storage, const DataType & data_in)
259 {
260 (void)storage;
261 (void)data_in;
262 }
263};
264
265} // namespace control_toolbox
266
267#endif // CONTROL_TOOLBOX__FILTER_TRAITS_HPP_
A Low-pass filter class.
Definition low_pass_filter.hpp:79
Definition dither.hpp:46
Definition filter_traits.hpp:90
Definition filter_traits.hpp:42