Fang-Robotics-MCB
Fang Robotics Team Codebase
Loading...
Searching...
No Matches
butterworth.hpp
Go to the documentation of this file.
1/*****************************************************************************/
2/********** !!! WARNING: CODE GENERATED BY TAPROOT. DO NOT EDIT !!! **********/
3/*****************************************************************************/
4
5/*
6 * Copyright (c) 2024-2025 Advanced Robotics at the University of Washington <robomstr@uw.edu>
7 *
8 * This file is part of Taproot.
9 *
10 * Taproot is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * Taproot is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Taproot. If not, see <https://www.gnu.org/licenses/>.
22 */
23
24#ifndef TAPROOT_BUTTERWORTH_HPP_
25#define TAPROOT_BUTTERWORTH_HPP_
26#include <array>
27#include <cmath>
28#include <complex>
29#include <cstdint>
30
31#include "modm/math/geometry/angle.hpp"
32
104namespace tap
105{
106namespace algorithms
107{
108enum FilterType : uint8_t
109{
110 LOWPASS = 0b00,
111 HIGHPASS = 0b01,
112 BANDPASS = 0b10,
113 BANDSTOP = 0b11,
114};
115
124constexpr std::complex<double> s2z(std::complex<double> s, double Ts)
125{
126 return (1.0 + (Ts / 2) * s) / (1.0 - (Ts / 2) * s);
127}
128
141template <uint8_t ORDER>
142constexpr std::array<double, ORDER + 1> expandPolynomial(
143 std::array<std::complex<double>, ORDER> zeros)
144{
145 std::array<std::complex<double>, ORDER + 1> coefficients{
146 std::complex<double>(1.0, 0.0)}; // Start with 1
147
148 for (size_t i = 0; i < ORDER; i++)
149 {
150 // Multiply current polynomial by (x - zero)
151 std::array<std::complex<double>, ORDER + 1> newCoefficients{std::complex<double>(0.0, 0.0)};
152 for (size_t j = 0; j < i + 1; j++)
153 {
154 newCoefficients[j] -=
155 coefficients[j] * zeros[i]; // Multiply current coefficient by zero
156 newCoefficients[j + 1] += coefficients[j]; // Shift current coefficient
157 }
158 coefficients.swap(newCoefficients);
159 }
160 std::array<double, ORDER + 1> stripped_coefficients{};
161 // Extract the real part of each coefficient, the imaginary appears to be an
162 // artifact of double
163 for (size_t i = 0; i < ORDER + 1; i++)
164 {
165 stripped_coefficients[i] = coefficients[i].real();
166 }
167
168 return stripped_coefficients;
169}
170
181template <uint8_t ORDER>
182constexpr std::complex<double> evaluateFrequencyResponse(
183 const std::array<double, ORDER + 1> &b,
184 const std::array<double, ORDER + 1> &a,
185 double w,
186 double Ts)
187{
188 std::complex<double> j(0, 1); // imaginary unit i = sqrt(-1)
189 std::complex<double> numerator(0, 0);
190 std::complex<double> denominator(0, 0);
191
192 double omega = w * Ts; // omega is in terms of rad/sample
193
194 // Evaluate numerator: b0 + b1 * e^{-jω} + b2 * e^{-j2ω} + ...
195 for (size_t k = 0; k < b.size(); ++k)
196 {
197 numerator += b[k] * (std::cos(omega * static_cast<double>(k)) -
198 j * std::sin(omega * static_cast<double>(k)));
199 }
200
201 // Evaluate denominator: a0 + a1 * e^{-jω} + a2 * e^{-j2ω} + ...
202 for (size_t k = 0; k < a.size(); ++k)
203 {
204 denominator += a[k] * (std::cos(omega * static_cast<double>(k)) -
205 j * std::sin(omega * static_cast<double>(k)));
206 }
207
208 return numerator / denominator;
209}
215constexpr double complex_abs(std::complex<double> z)
216{
217 return std::sqrt(z.real() * z.real() + z.imag() * z.imag());
218}
219
226constexpr std::complex<double> complex_sqrt(std::complex<double> z)
227{
228 double r = std::sqrt(complex_abs(z));
229 double theta = std::atan2(z.imag(), z.real()) * 0.5;
230 return {r * std::cos(theta), r * std::sin(theta)};
231}
232
233template <uint8_t ORDER, FilterType Type = LOWPASS, typename T = float>
235{
236public:
245 constexpr Butterworth(double wc, double Ts, double wh = 0.0)
246 : naturalResponseCoefficients(),
247 forcedResponseCoefficients()
248 {
249 const int n = ORDER;
250
251 // For band filters we treat wc as ωl
252 double wl = wc;
253 double whp = wh;
254 std::array<std::complex<double>, 2 * ORDER> bandpass_stop_poles;
255
256 // pre-warp all edges for bilinear transform
257 wl = (2.0 / Ts) * std::tan(wl * (Ts / 2.0));
258 whp = (2.0 / Ts) * std::tan(whp * (Ts / 2.0));
259
260 // generate N prototype poles on unit circle
261 std::array<std::complex<double>, COEFFICIENTS - 1> poles;
262 for (int k = 0; k < n; ++k)
263 {
264 double theta = M_PI * (2.0 * k + 1) / (2.0 * n) + M_PI / 2.0;
265 poles[k] = std::complex<double>(std::cos(theta), std::sin(theta));
266 }
267
268 std::array<std::complex<double>, COEFFICIENTS - 1> zPoles;
269
270 // apply the appropriate s-domain transform to each pole
271 switch (Type)
272 {
273 case LOWPASS:
274 {
275 // poles are multiplied by wl to scale them to the desired cutoff frequency
276 for (int j = 0; j < n; ++j) poles[j] = poles[j] * wl;
277
278 // now map each analog pole into the z-plane
279 for (int i = 0; i < COEFFICIENTS - 1; ++i) zPoles[i] = s2z(poles[i], Ts);
280 break;
281 }
282 case HIGHPASS:
283 {
284 // applys the inverse transform of the butterworth lowpass filter
285 for (int j = 0; j < n; ++j)
286 {
287 poles[j] = wl / poles[j];
288 }
289 // now map each analog pole into the z-plane
290 for (int i = 0; i < COEFFICIENTS - 1; ++i) zPoles[i] = s2z(poles[i], Ts);
291 break;
292 }
293 // In the case of a bandpass or a bandstop the amount of poles doubles.
294 case BANDPASS:
295 {
296 // check for validity of the filter edges
297 if (whp <= wl)
298 {
299 throw std::invalid_argument("wh must be > wl for BANDPASS");
300 }
301 /*
302 * transform in the form of s → (s² + Ω₀²) / (B · s)
303 * where:
304 * Ω₀ = √(Ω_low * Ω_high) // Center frequency (rad/sec)
305 * B = Ω_high - Ω_low // Bandwidth (rad/sec)
306 */
307 double B = whp - wl;
308 double W0sq = whp * wl;
309
310 for (int j = 0; j < ORDER; ++j)
311 {
312 std::complex<double> p = poles[j];
313
314 std::complex<double> discriminant = (p * B) * (p * B) - 4.0 * W0sq;
315 std::complex<double> root = complex_sqrt(discriminant);
316
317 bandpass_stop_poles[2 * j] = (p * B + root) * 0.5;
318 bandpass_stop_poles[2 * j + 1] = (p * B - root) * 0.5;
319 }
320
321 // now map each analog pole into the z-plane
322 for (int i = 0; i < COEFFICIENTS - 1; ++i)
323 zPoles[i] = s2z(bandpass_stop_poles[i], Ts);
324
325 break;
326 }
327
328 case BANDSTOP:
329 {
330 // check for validity of the filter edges
331 if (whp <= wl)
332 {
333 throw std::invalid_argument("wh must be > wl for BANDSTOP");
334 }
335 /*
336 * transform in the form of s → B · s / (s² + Ω₀²)
337 * where:
338 * Ω₀ = √(Ω_low * Ω_high) // Center frequency (rad/sec)
339 * B = Ω_high - Ω_low // Bandwidth (rad/sec)
340 */
341 double B = whp - wl;
342 double W0sq = whp * wl;
343 for (int j = 0; j < n; ++j)
344 {
345 std::complex<double> p = poles[j];
346
347 std::complex<double> discriminant = B * B - (4.0 * -p * W0sq);
348 std::complex<double> root = complex_sqrt(discriminant);
349
350 bandpass_stop_poles[2 * j] = (B + root) / (2.0 * p);
351 bandpass_stop_poles[2 * j + 1] = (B - root) / (2.0 * p);
352 }
353
354 // now map each analog pole into the z-plane
355 for (int i = 0; i < COEFFICIENTS - 1; ++i)
356 zPoles[i] = s2z(bandpass_stop_poles[i], Ts);
357
358 break;
359 }
360 default:
361 throw std::invalid_argument("Unknown filter type");
362 }
363
364 std::array<std::complex<double>, COEFFICIENTS - 1> zZeros;
365
366 switch (Type)
367 {
368 case LOWPASS:
369 // zeros: for Butterworth lowpass all z-zeros at z = –1
370 zZeros.fill(std::complex<double>(-1, 0));
371 break;
372 case HIGHPASS:
373 // zeros: for butterworth highpass all z-zeros are at z = 1
374 zZeros.fill(std::complex<double>(1, 0));
375 break;
376 case BANDPASS:
377 // zeros: for butterworth bandpass all z-zeros are at z = ±1
378 for (int i = 0; i < COEFFICIENTS - 1; ++i)
379 {
380 zZeros[i] =
381 (i % 2 == 0) ? std::complex<double>(1, 0) : std::complex<double>(-1, 0);
382 }
383 break;
384 case BANDSTOP:
385 {
386 /* zeros: for butterworth bandstop are in a circle with radius 1 at the
387 * center of the z-domain with an angle in radians per sample of the
388 * center frequency. The zeros are complex conjugates of each other.
389 */
390
391 /* the notch (center) frequency in radians/sample */
392 double omega0 = std::sqrt(wl * whp) * Ts;
393
394 double realPart = std::cos(omega0);
395 double imagPart = std::sin(omega0);
396
397 std::complex<double> zeroPlus(realPart, imagPart); // e^(+jω0)
398 std::complex<double> zeroMinus(realPart, -imagPart); // e^(-jω0)
399
400 for (int i = 0; i < COEFFICIENTS - 1; i += 2)
401 {
402 zZeros[i] = zeroPlus; // first of pair
403 if (i + 1 < COEFFICIENTS) zZeros[i + 1] = zeroMinus; // second of pair
404 }
405 }
406 break;
407 }
408
409 // get expanded polynomials
410 auto b = expandPolynomial<COEFFICIENTS - 1>(zZeros);
411 auto a = expandPolynomial<COEFFICIENTS - 1>(zPoles);
412
413 // scale the gain properly
414 switch (Type)
415 {
416 case LOWPASS:
417 {
418 // Eval at DC
419 auto freqResp = evaluateFrequencyResponse<COEFFICIENTS - 1>(b, a, 0, Ts);
420 auto mag = complex_abs(freqResp);
421 double scale = 1 / mag;
422 for (auto &coef : b) coef *= scale;
423 break;
424 }
425 case HIGHPASS:
426 {
427 // Eval at niquest
428 auto freqResp = evaluateFrequencyResponse<COEFFICIENTS - 1>(b, a, M_PI / Ts, Ts);
429 auto mag = complex_abs(freqResp);
430 double scale = 1 / mag;
431 for (auto &coef : b) coef *= scale;
432 break;
433 }
434 case BANDPASS:
435 {
436 // Eval at center frequency
437 auto freqResp =
438 evaluateFrequencyResponse<COEFFICIENTS - 1>(b, a, std::sqrt(wl * whp), Ts);
439 auto mag = complex_abs(freqResp);
440 double scale = 1 / mag;
441 for (auto &coef : b) coef *= scale;
442 break;
443 }
444 case BANDSTOP:
445 {
446 // Eval at dc gain
447 auto freqResp = evaluateFrequencyResponse<COEFFICIENTS - 1>(b, a, 0, Ts);
448 auto mag = complex_abs(freqResp);
449 double scale = 1 / mag;
450 for (auto &coef : b) coef *= scale;
451 break;
452 }
453 }
454
455 // store in member arrays (reverse order)
456 for (size_t i = 0; i < COEFFICIENTS; ++i)
457 {
458 naturalResponseCoefficients[COEFFICIENTS - i - 1] = a[i];
459 forcedResponseCoefficients[COEFFICIENTS - i - 1] = b[i];
460 }
461 }
462
463 static constexpr int COEFFICIENTS = (1 + ((Type & 0b10) != 0)) * ORDER + 1;
464
465 std::array<T, COEFFICIENTS> getNaturalResponseCoefficients() const
466 {
467 return naturalResponseCoefficients;
468 }
469
470 std::array<T, COEFFICIENTS> getForcedResponseCoefficients() const
471 {
472 return forcedResponseCoefficients;
473 }
474
475private:
476 std::array<T, COEFFICIENTS> naturalResponseCoefficients;
477 std::array<T, COEFFICIENTS> forcedResponseCoefficients;
478};
479
480} // namespace algorithms
481
482} // namespace tap
483
484#endif // TAPROOT_BUTTERWORTH_HPP_
Definition butterworth.hpp:235
std::array< T, COEFFICIENTS > getForcedResponseCoefficients() const
Definition butterworth.hpp:470
static constexpr int COEFFICIENTS
Definition butterworth.hpp:463
std::array< T, COEFFICIENTS > getNaturalResponseCoefficients() const
Definition butterworth.hpp:465
constexpr Butterworth(double wc, double Ts, double wh=0.0)
Definition butterworth.hpp:245
IUnit i
Definition dimensional_smooth_pid.hpp:2
PUnit p
Definition dimensional_smooth_pid.hpp:1
constexpr std::complex< double > evaluateFrequencyResponse(const std::array< double, ORDER+1 > &b, const std::array< double, ORDER+1 > &a, double w, double Ts)
Definition butterworth.hpp:182
constexpr std::array< double, ORDER+1 > expandPolynomial(std::array< std::complex< double >, ORDER > zeros)
Definition butterworth.hpp:142
constexpr std::complex< double > complex_sqrt(std::complex< double > z)
Definition butterworth.hpp:226
FilterType
Definition butterworth.hpp:109
@ HIGHPASS
Definition butterworth.hpp:111
@ BANDSTOP
Definition butterworth.hpp:113
@ LOWPASS
Definition butterworth.hpp:110
@ BANDPASS
Definition butterworth.hpp:112
constexpr std::complex< double > s2z(std::complex< double > s, double Ts)
Definition butterworth.hpp:124
constexpr double complex_abs(std::complex< double > z)
Definition butterworth.hpp:215
Definition ballistics.cpp:29