1#ifndef CONSTFILT_DISCRETIZE_HPP
2#define CONSTFILT_DISCRETIZE_HPP
4#include "vendor/consteig/consteig.hpp"
5#include "vendor/gcem_wrapper.hpp"
42template <
typename T,
typename M>
struct bind_method
47template <
typename T>
struct bind_method<T, TustinPW>
49 using type = TustinPWData<T>;
54template <
typename M>
struct is_tustinpw_tag
56 static constexpr bool value =
false;
59template <>
struct is_tustinpw_tag<TustinPW>
61 static constexpr bool value =
true;
64template <
typename M>
struct is_zoh_tag
66 static constexpr bool value =
false;
69template <>
struct is_zoh_tag<ZOH>
71 static constexpr bool value =
true;
77template <
typename T,
typename M>
constexpr M make_tustin_tag(T, M)
83constexpr TustinPWData<T> make_tustin_tag(T cutoff_hz, TustinPWData<T>)
85 return TustinPWData<T>{
static_cast<T
>(2) *
static_cast<T
>(GCEM_PI) *
93template <
typename T>
constexpr TustinPWData<T> prewarp(T warp_hz)
95 return TustinPWData<T>{
static_cast<T
>(2) *
static_cast<T
>(GCEM_PI) *
101template <
typename T, consteig::Size N>
struct StateSpace
103 consteig::Matrix<T, N, N> A{};
104 consteig::Matrix<T, N, 1> B{};
105 consteig::Matrix<T, 1, N> C{};
109template <
typename T, consteig::Size NB, consteig::Size NA>
110struct TransferFunction
122template <
typename T, consteig::Size N>
struct FactoredTF
124 consteig::Complex<T> poles[N]{};
125 consteig::Complex<T> zeros[N]{};
126 consteig::Size nz{0};
135template <
typename T, consteig::Size N>
136constexpr consteig::Matrix<T, N, N> spectral_matrix_exp(
137 const consteig::Matrix<consteig::Complex<T>, N, N> &V,
138 const consteig::Complex<T> (&exp_factors)[N])
140 using Complex = consteig::Complex<T>;
141 using ComplexMat_NN = consteig::Matrix<Complex, N, N>;
142 using ComplexMat_N1 = consteig::Matrix<Complex, N, 1>;
144 const auto lu_V = consteig::lu(V);
145 ComplexMat_NN V_inv{};
146 for (consteig::Size col = 0; col < N; ++col)
148 ComplexMat_N1 e_col{};
149 e_col(col, 0) = Complex{
static_cast<T
>(1),
static_cast<T
>(0)};
150 auto col_vec = consteig::lu_solve(lu_V, e_col);
151 for (consteig::Size row = 0; row < N; ++row)
153 V_inv(row, col) = col_vec(row, 0);
157 ComplexMat_NN result_c{};
158 for (consteig::Size i = 0; i < N; ++i)
160 for (consteig::Size r = 0; r < N; ++r)
162 for (consteig::Size c = 0; c < N; ++c)
165 result_c(r, c) + exp_factors[i] * V(r, i) * V_inv(i, c);
170 consteig::Matrix<T, N, N> result{};
171 for (consteig::Size r = 0; r < N; ++r)
173 for (consteig::Size c = 0; c < N; ++c)
175 result(r, c) = result_c(r, c).real;
185template <
typename T, consteig::Size N>
186constexpr consteig::Matrix<T, N, N> matrix_exp(
187 const consteig::Matrix<T, N, N> &A)
189 using Complex = consteig::Complex<T>;
192 const auto evals = consteig::eigenvalues(A);
193 const auto V = consteig::eigenvectors(A, evals);
196 Complex exp_factors[N]{};
197 for (consteig::Size i = 0; i < N; ++i)
199 exp_factors[i] = consteig::exp(evals(i, 0));
203 return spectral_matrix_exp<T, N>(V, exp_factors);
217template <
typename T, consteig::Size N>
218constexpr consteig::Matrix<T, N, N> matrix_exp_ccf(
219 const FactoredTF<T, N> &factored_tf, T Ts)
221 using Complex = consteig::Complex<T>;
222 using ComplexMat_NN = consteig::Matrix<Complex, N, N>;
225 for (consteig::Size i = 0; i < N; ++i)
227 Complex power{
static_cast<T
>(1),
static_cast<T
>(0)};
228 for (consteig::Size r = 0; r < N; ++r)
231 power = power * factored_tf.poles[i];
235 Complex exp_factors[N]{};
236 for (consteig::Size i = 0; i < N; ++i)
238 exp_factors[i] = consteig::exp(Complex{Ts * factored_tf.poles[i].real,
239 Ts * factored_tf.poles[i].imag});
242 return spectral_matrix_exp<T, N>(V, exp_factors);
253template <
typename T, consteig::Size N>
254constexpr StateSpace<T, N> zoh_discretize(
const StateSpace<T, N> &sys_c, T Ts,
257 const auto &Ac = sys_c.A;
258 const auto &Bc = sys_c.B;
261 const consteig::Matrix<T, N, N> Ad = matrix_exp(Ts * Ac);
264 const consteig::Matrix<T, N, N> AdmI = Ad - consteig::eye<T, N>();
267 const consteig::Matrix<T, N, 1> rhs = AdmI * Bc;
270 const auto lu_Ac = consteig::lu(Ac);
271 const auto Bd = consteig::lu_solve(lu_Ac, rhs);
273 StateSpace<T, N> sys_d{};
284template <
typename T, consteig::Size N>
285constexpr StateSpace<T, N> zoh_discretize_with_evals(
286 const StateSpace<T, N> &sys_c, T Ts,
const FactoredTF<T, N> &factored_tf)
288 const consteig::Matrix<T, N, N> Ad = matrix_exp_ccf(factored_tf, Ts);
290 const consteig::Matrix<T, N, N> AdmI = Ad - consteig::eye<T, N>();
291 const consteig::Matrix<T, N, 1> rhs = AdmI * sys_c.B;
292 const auto lu_Ac = consteig::lu(sys_c.A);
293 const auto Bd = consteig::lu_solve(lu_Ac, rhs);
295 StateSpace<T, N> sys_d{};
310template <
typename T, consteig::Size N>
311constexpr void char_poly(
const consteig::Matrix<T, N, N> &Ad,
314 using Complex = consteig::Complex<T>;
316 const auto evals = consteig::eigenvalues(Ad);
320 p[0] = Complex{
static_cast<T
>(1),
static_cast<T
>(0)};
322 for (consteig::Size k = 0; k < N; ++k)
324 const Complex lam = evals(k, 0);
326 p[k + 1u] = Complex{
static_cast<T
>(0),
static_cast<T
>(0)} - lam * p[k];
327 for (consteig::Size i = k; i > 0u; --i)
329 p[i] = p[i] - lam * p[i - 1u];
334 for (consteig::Size i = 0; i <= N; ++i)
336 coeffs[i] = p[i].real;
346template <
typename T, consteig::Size N>
347constexpr void markov_numerator(
const StateSpace<T, N> &sys_d,
348 const T (&a_coeffs)[N + 1u], T (&b)[N + 1u])
350 const auto &Ad = sys_d.A;
351 const auto &Bd = sys_d.B;
352 const auto &Cd = sys_d.C;
359 consteig::Matrix<T, N, N> A_pow = consteig::eye<T, N>();
361 for (consteig::Size k = 1u; k <= N; ++k)
364 T val =
static_cast<T
>(0);
365 for (consteig::Size c = 0; c < N; ++c)
368 T AB_c =
static_cast<T
>(0);
369 for (consteig::Size col = 0; col < N; ++col)
371 AB_c += A_pow(c, col) * Bd(col, 0);
373 val += Cd(0, c) * AB_c;
382 for (consteig::Size k = 0; k <= N; ++k)
384 T sum =
static_cast<T
>(0);
385 for (consteig::Size jj = 0; jj <= k; ++jj)
387 sum += a_coeffs[jj] * h[k - jj];
396template <
typename T, consteig::Size N>
397constexpr TransferFunction<T, N + 1u, N + 1u> ss_to_tf(
398 const StateSpace<T, N> &sys_d)
400 TransferFunction<T, N + 1u, N + 1u> tf{};
401 char_poly(sys_d.A, tf.a);
402 markov_numerator(sys_d, tf.a, tf.b);
423template <
typename T, consteig::Size N>
424constexpr StateSpace<T, N> tf_to_ss(
const T (&b)[N + 1u],
const T (&a)[N + 1u])
426 StateSpace<T, N> sys{};
428 const T inv_a0 =
static_cast<T
>(1) / a[0];
430 sys.D = b[0] * inv_a0;
434 for (consteig::Size k = 1u; k <= N; ++k)
436 e[k] = b[k] * inv_a0 - sys.D * (a[k] * inv_a0);
440 for (consteig::Size row = 0; row < N - 1u; ++row)
442 sys.A(row, row + 1u) =
static_cast<T
>(1);
446 for (consteig::Size k = 0; k < N; ++k)
448 sys.A(N - 1u, k) = -(a[N - k] * inv_a0);
452 sys.B(N - 1u, 0) =
static_cast<T
>(1);
455 for (consteig::Size k = 0; k < N; ++k)
457 sys.C(0, k) = e[N - k];
468template <
typename T, consteig::Size N>
469constexpr TransferFunction<T, N + 1u, N + 1u> matched_z_assemble(
470 const consteig::Complex<T> (&poles)[N],
471 const consteig::Complex<T> (&zeros)[N], consteig::Size nz, T k_c, T Ts)
473 using Complex = consteig::Complex<T>;
476 Complex p_d_vals[N]{};
477 Complex pole_poly[N + 1u]{};
478 pole_poly[0] = Complex{
static_cast<T
>(1),
static_cast<T
>(0)};
479 for (consteig::Size k = 0; k < N; ++k)
481 p_d_vals[k] = consteig::exp(poles[k] * Complex{Ts,
static_cast<T
>(0)});
482 const Complex zk = p_d_vals[k];
484 Complex{
static_cast<T
>(0),
static_cast<T
>(0)} - zk * pole_poly[k];
485 for (consteig::Size i = k; i > 0u; --i)
487 pole_poly[i] = pole_poly[i] - zk * pole_poly[i - 1u];
492 Complex z_d_finite[N]{};
493 Complex zero_poly[N + 1u]{};
494 zero_poly[0] = Complex{
static_cast<T
>(1),
static_cast<T
>(0)};
495 for (consteig::Size k = 0; k < nz; ++k)
498 consteig::exp(zeros[k] * Complex{Ts,
static_cast<T
>(0)});
499 const Complex zk = z_d_finite[k];
501 Complex{
static_cast<T
>(0),
static_cast<T
>(0)} - zk * zero_poly[k];
502 for (consteig::Size i = k; i > 0u; --i)
504 zero_poly[i] = zero_poly[i] - zk * zero_poly[i - 1u];
509 const consteig::Size n_extra = (nz + 1u < N) ? (N - nz - 1u) : 0u;
510 for (consteig::Size e = 0; e < n_extra; ++e)
512 const consteig::Size cur_deg = nz + e;
513 zero_poly[cur_deg + 1u] = zero_poly[cur_deg + 1u] + zero_poly[cur_deg];
514 for (consteig::Size i = cur_deg; i > 0u; --i)
516 zero_poly[i] = zero_poly[i] + zero_poly[i - 1u];
519 const consteig::Size num_deg = nz + n_extra;
522 const T tol = gcem::sqrt(consteig::epsilon<T>());
523 T w_c =
static_cast<T
>(0);
524 for (consteig::Size attempt = 0; attempt < 1000u; ++attempt)
526 bool collision =
false;
527 for (consteig::Size i = 0; i < N && !collision; ++i)
529 const T dr = w_c - poles[i].real;
530 const T di = poles[i].imag;
531 if (dr * dr + di * di < tol * tol)
536 for (consteig::Size i = 0; i < nz && !collision; ++i)
538 const T dr = w_c - zeros[i].real;
539 const T di = zeros[i].imag;
540 if (dr * dr + di * di < tol * tol)
549 w_c +=
static_cast<T
>(0.1) / Ts;
553 const Complex w_c_cx{w_c,
static_cast<T
>(0)};
554 const Complex w_d_cx =
555 consteig::exp(w_c_cx * Complex{Ts,
static_cast<T
>(0)});
557 Complex num_c_cx{
static_cast<T
>(1),
static_cast<T
>(0)};
558 for (consteig::Size i = 0; i < nz; ++i)
560 num_c_cx = num_c_cx * (w_c_cx - zeros[i]);
563 Complex den_c_cx{
static_cast<T
>(1),
static_cast<T
>(0)};
564 for (consteig::Size i = 0; i < N; ++i)
566 den_c_cx = den_c_cx * (w_c_cx - poles[i]);
569 Complex num_d_cx{
static_cast<T
>(1),
static_cast<T
>(0)};
570 for (consteig::Size i = 0; i < N; ++i)
572 num_d_cx = num_d_cx * (w_d_cx - p_d_vals[i]);
575 Complex den_d_cx{
static_cast<T
>(1),
static_cast<T
>(0)};
576 for (consteig::Size i = 0; i < nz; ++i)
578 den_d_cx = den_d_cx * (w_d_cx - z_d_finite[i]);
580 for (consteig::Size e = 0; e < n_extra; ++e)
582 den_d_cx = den_d_cx *
583 (w_d_cx - Complex{
static_cast<T
>(-1),
static_cast<T
>(0)});
586 const Complex gain_num =
587 Complex{k_c,
static_cast<T
>(0)} * num_c_cx * num_d_cx;
588 const Complex gain_den = den_c_cx * den_d_cx;
589 const T gain_den_sq =
590 gain_den.real * gain_den.real + gain_den.imag * gain_den.imag;
592 (gain_num.real * gain_den.real + gain_num.imag * gain_den.imag) /
596 TransferFunction<T, N + 1u, N + 1u> tf{};
597 for (consteig::Size i = 0; i <= N; ++i)
599 tf.a[i] = pole_poly[i].real;
601 const consteig::Size pad = N - num_deg;
602 for (consteig::Size i = 0; i <= num_deg; ++i)
604 tf.b[pad + i] = k_d * zero_poly[i].real;
616template <
typename T, consteig::Size N>
617constexpr TransferFunction<T, N + 1u, N + 1u> matched_z_discretize_tf(
618 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u], T Ts, MatchedZ )
620 using Complex = consteig::Complex<T>;
623 consteig::Size d_b = 0;
624 while (d_b <= N && b_c[d_b] ==
static_cast<T
>(0))
628 const consteig::Size nz = (d_b > N) ? 0u : N - d_b;
629 const T k_c = (d_b > N) ?
static_cast<T
>(0) : b_c[d_b] / a_c[0];
634 consteig::Matrix<T, N, N> A_pole{};
635 for (consteig::Size i = 0; i < N - 1u; ++i)
637 A_pole(i + 1u, i) =
static_cast<T
>(1);
639 for (consteig::Size i = 0; i < N; ++i)
641 A_pole(i, N - 1u) = -(a_c[N - i] / a_c[0]);
643 const auto p_c_evals = consteig::eigenvalues(A_pole);
645 for (consteig::Size i = 0; i < N; ++i)
647 poles[i] = p_c_evals(i, 0);
658 consteig::Matrix<T, N, N> A_zero{};
659 for (consteig::Size i = 0; i + 1u < nz; ++i)
661 A_zero(i + 1u, i) =
static_cast<T
>(1);
663 for (consteig::Size i = 0; i < nz; ++i)
665 A_zero(i, nz - 1u) = -(b_c[d_b + (nz - i)] / b_c[d_b]);
667 const auto z_c_evals = consteig::eigenvalues(A_zero);
671 for (consteig::Size m = 0; m < d_b; ++m)
673 consteig::Size min_idx = 0;
674 T min_mag_sq =
static_cast<T
>(-1);
675 for (consteig::Size i = 0; i < N; ++i)
680 z_c_evals(i, 0).real * z_c_evals(i, 0).real +
681 z_c_evals(i, 0).imag * z_c_evals(i, 0).imag;
682 if (min_mag_sq <
static_cast<T
>(0) || mag_sq < min_mag_sq)
689 spurious[min_idx] =
true;
692 consteig::Size nz_cnt = 0u;
693 for (consteig::Size k = 0; k < N; ++k)
697 zeros[nz_cnt++] = z_c_evals(k, 0);
702 return matched_z_assemble<T, N>(poles, zeros, nz, k_c, Ts);
707template <
typename T, consteig::Size N>
708constexpr TransferFunction<T, N + 1u, N + 1u> matched_z_discretize_factored(
709 const FactoredTF<T, N> &factored_tf, T Ts)
711 return matched_z_assemble<T, N>(factored_tf.poles, factored_tf.zeros,
712 factored_tf.nz, factored_tf.gain, Ts);
730template <
typename T, consteig::Size N>
731constexpr StateSpace<T, N> tustin_discretize_impl(
const StateSpace<T, N> &sys_c,
734 const auto &Ac = sys_c.A;
735 const auto &Bc = sys_c.B;
736 const auto &Cc = sys_c.C;
738 const T inv_alpha =
static_cast<T
>(1) / alpha;
741 const consteig::Matrix<T, N, N> M = consteig::eye<T, N>() - inv_alpha * Ac;
742 const consteig::Matrix<T, N, N> P = consteig::eye<T, N>() + inv_alpha * Ac;
744 const auto lu_M{consteig::lu(M)};
747 consteig::Matrix<T, N, N> M_inv{};
748 for (consteig::Size col = 0; col < N; ++col)
750 consteig::Matrix<T, N, 1> e_col{};
751 e_col(col, 0) =
static_cast<T
>(1);
752 const auto col_vec{consteig::lu_solve(lu_M, e_col)};
753 for (consteig::Size row = 0; row < N; ++row)
755 M_inv(row, col) = col_vec(row, 0);
760 const consteig::Matrix<T, N, N> Ad{P * M_inv};
763 const consteig::Matrix<T, N, 1> Bd{inv_alpha *
764 (Ad + consteig::eye<T, N>()) * Bc};
767 const consteig::Matrix<T, 1, N> Cd{Cc * M_inv};
770 const T Dd{sys_c.D + inv_alpha * (Cd * Bc)(0, 0)};
772 StateSpace<T, N> sys_d{};
780template <
typename T, consteig::Size N>
781constexpr StateSpace<T, N> tustin_discretize(
const StateSpace<T, N> &sys_c,
784 return tustin_discretize_impl(sys_c,
static_cast<T
>(2) / Ts);
787template <
typename T, consteig::Size N>
788constexpr StateSpace<T, N> tustin_discretize(
const StateSpace<T, N> &sys_c,
789 T Ts, TustinPWData<T> tag)
792 tag.warp_omega / gcem::tan(tag.warp_omega * Ts /
static_cast<T
>(2));
793 return tustin_discretize_impl(sys_c, alpha);
797template <
typename T, consteig::Size N>
798constexpr TransferFunction<T, N + 1u, N + 1u> matched_z_discretize(
799 const StateSpace<T, N> &sys_c, T Ts, MatchedZ )
802 char_poly(sys_c.A, a_c);
804 markov_numerator(sys_c, a_c, b_c);
805 return matched_z_discretize_tf<T, N>(b_c, a_c, Ts, MatchedZ{});
810template <
typename T, consteig::Size N>
811constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
812 const StateSpace<T, N> &sys_c, T Ts, ZOH)
814 return ss_to_tf(zoh_discretize(sys_c, Ts, ZOH{}));
817template <
typename T, consteig::Size N>
818constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
819 const StateSpace<T, N> &sys_c, T Ts, MatchedZ)
821 return matched_z_discretize(sys_c, Ts, MatchedZ{});
824template <
typename T, consteig::Size N>
825constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
826 const StateSpace<T, N> &sys_c, T Ts, TustinNW tag)
828 return ss_to_tf(tustin_discretize(sys_c, Ts, tag));
831template <
typename T, consteig::Size N>
832constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
833 const StateSpace<T, N> &sys_c, T Ts, TustinPWData<T> tag)
835 return ss_to_tf(tustin_discretize(sys_c, Ts, tag));
842template <
typename T, consteig::Size N>
843constexpr TransferFunction<T, N + 1u, N + 1u> discretize_with_factored(
844 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u],
845 const FactoredTF<T, N> &factored_tf, T Ts, ZOH)
848 zoh_discretize_with_evals(tf_to_ss<T, N>(b_c, a_c), Ts, factored_tf));
851template <
typename T, consteig::Size N>
852constexpr TransferFunction<T, N + 1u, N + 1u> discretize_with_factored(
853 const T (& )[N + 1u],
const T (& )[N + 1u],
854 const FactoredTF<T, N> &factored_tf, T Ts, MatchedZ)
856 return matched_z_discretize_factored<T, N>(factored_tf, Ts);
859template <
typename T, consteig::Size N,
typename M>
860constexpr TransferFunction<T, N + 1u, N + 1u> discretize_with_factored(
861 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u],
862 const FactoredTF<T, N> & , T Ts, M method_tag)
864 return analog_to_digital<T, N>(b_c, a_c, Ts, method_tag);
869template <
typename T, consteig::Size N>
870constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
871 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u], T Ts, ZOH)
873 return ss_to_tf(zoh_discretize(tf_to_ss<T, N>(b_c, a_c), Ts, ZOH{}));
876template <
typename T, consteig::Size N>
877constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
878 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u], T Ts, MatchedZ)
880 return matched_z_discretize_tf<T, N>(b_c, a_c, Ts, MatchedZ{});
883template <
typename T, consteig::Size N>
884constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
885 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u], T Ts, TustinNW tag)
887 return ss_to_tf(tustin_discretize(tf_to_ss<T, N>(b_c, a_c), Ts, tag));
890template <
typename T, consteig::Size N>
891constexpr TransferFunction<T, N + 1u, N + 1u> analog_to_digital(
892 const T (&b_c)[N + 1u],
const T (&a_c)[N + 1u], T Ts, TustinPWData<T> tag)
894 return ss_to_tf(tustin_discretize(tf_to_ss<T, N>(b_c, a_c), Ts, tag));