Controlpp
Loading...
Searching...
No Matches
Bode.hpp
Go to the documentation of this file.
1#pragma once
2
3//std
4#include <numbers>
5#include <cassert>
6#include <concepts>
7#include <complex>
8#include <algorithm>
9#include <variant>
10
11// eigen
12#include <Eigen/Dense>
13
14// csvd a csv reader
15#include <csvd/csvd.hpp>
16
17// controlpp
22
28namespace controlpp{
29
42 template<class T = double>
43 class Bode{
44 private:
45 Eigen::Vector<T, Eigen::Dynamic> omegas_; // frequencies in rad
46 Eigen::Vector<std::complex<T>, Eigen::Dynamic> values_; // complex magnitudes
47
48 public:
49 Bode() = default;
50
56 Bode(const Eigen::Vector<T, Eigen::Dynamic>& freqs_rad, const Eigen::Vector<std::complex<T>, Eigen::Dynamic>& values)
57 : omegas_(freqs_rad)
58 , values_(values)
59 {
60 // assert same size
61 assert(this->omegas_.size() == this->values_.size());
62
63 // assert only positive frequencies
64 for(size_t i = 0; i < static_cast<size_t>(omegas_.size()); ++i){
65 assert(omegas_(i) >= T(0));
66 }
67
68 // assert frequencies are ascending
69 for(size_t i = 1; i < static_cast<size_t>(omegas_.size()); ++i){
70 assert(omegas_(i) > omegas_(i-1));
71 }
72 }
73
74 Bode(Eigen::Vector<T, Eigen::Dynamic>&& freqs_rad, Eigen::Vector<std::complex<T>, Eigen::Dynamic>&& values)
75 : omegas_(std::move(freqs_rad))
76 , values_(std::move(values))
77 {
78 // assert same size
79 assert(this->omegas_.size() == this->values_.size());
80
81 // assert only positive frequencies
82 for(size_t i = 0; i < static_cast<size_t>(omegas_.size()); ++i){
83 assert(omegas_(i) >= T(0));
84 }
85
86 // assert frequencies are ascending
87 for(size_t i = 1; i < static_cast<size_t>(omegas_.size()); ++i){
88 assert(omegas_(i) > omegas_(i-1));
89 }
90 }
91
92 Bode(const Eigen::Vector<T, Eigen::Dynamic>& freqs_rad, Eigen::Vector<std::complex<T>, Eigen::Dynamic>&& values)
93 : omegas_(freqs_rad)
94 , values_(std::move(values))
95 {
96 // assert same size
97 assert(this->omegas_.size() == this->values_.size());
98
99 // assert only positive frequencies
100 for(size_t i = 0; i < static_cast<size_t>(omegas_.size()); ++i){
101 assert(omegas_(i) >= T(0));
102 }
103
104 // assert frequencies are ascending
105 for(size_t i = 1; i < static_cast<size_t>(omegas_.size()); ++i){
106 assert(omegas_(i) > omegas_(i-1));
107 }
108 }
109
110 Bode(Eigen::Vector<T, Eigen::Dynamic>&& freqs_rad, const Eigen::Vector<std::complex<T>, Eigen::Dynamic>& values)
111 : omegas_(std::move(freqs_rad))
112 , values_(values)
113 {
114 // assert same size
115 assert(this->omegas_.size() == this->values_.size());
116
117 // assert only positive frequencies
118 for(size_t i = 0; i < static_cast<size_t>(omegas_.size()); ++i){
119 assert(omegas_(i) >= T(0));
120 }
121
122 // assert frequencies are ascending
123 for(size_t i = 1; i < static_cast<size_t>(omegas_.size()); ++i){
124 assert(omegas_(i) > omegas_(i-1));
125 }
126 }
127
133 const Eigen::Vector<T, Eigen::Dynamic>& frequencies() const {
134 return this->omegas_;
135 }
136
142 Eigen::Vector<T, Eigen::Dynamic>& frequencies() {
143 return this->omegas_;
144 }
145
151 T& frequency(std::size_t n) {
152 return this->omegas_(n);
153 }
154
160 const T& frequency(std::size_t n) const {
161 return this->omegas_(n);
162 }
163
243 void prewarp_tustin(const T& Ts){
244 this->omegas_ = controlpp::prewarp_tustin(this->omegas_, Ts);
245 }
246
260 void unwarp_tustin(const T& Ts){
261 this->omegas_ = controlpp::unwarp_tustin(this->omegas_, Ts);
262 }
263
268 const Eigen::Vector<std::complex<T>, Eigen::Dynamic>& values() const {
269 return this->values_;
270 }
271
276 Eigen::Vector<std::complex<T>, Eigen::Dynamic>& values() {
277 return this->values_;
278 }
279
285 const std::complex<T>& value(std::size_t n) const {
286 return this->values_(n);
287 }
288
294 std::complex<T>& value(std::size_t n) {
295 return this->values_(n);
296 }
297
298 size_t size() const {return this->omegas_.size();}
299
300 bool empty() const {return this->size() == 0;}
301
302
315 std::complex<T> value_at(const T& frequency) const {
316 std::optional<std::pair<const T*, const T*>> result = find_enclosing(this->omegas_, frequency);
317 if(result.has_value()){
318 // extract result values (iterators)
319 const T* low = result.value().first;
320 const T* high = result.value().second;
321
322 // calculate integral ierators
323 const size_t i_low = low - this->omegas_.data();
324 const size_t i_high = high - this->omegas_.data();
325
326 // get frequencies and values
327 const T f_low = *low;
328 const T f_high = *high;
329 const std::complex<T> v_low = this->values_(i_low);
330 const std::complex<T> v_high = this->values_(i_high);
331
332 // calculate percentage for interpolation
333 const T p = (frequency - f_low) / (f_high - f_low);
334
335 // linear magnitude interpolation
336 const T mag_interp = std::abs(v_low) * (static_cast<T>(1) - p) + (p) * std::abs(v_high);
337
338 // linear phase interpolation
339 const T phase_interp = std::arg(v_low) * (static_cast<T>(1) - p) + (p) * std::arg(v_high);
340
341 // calculate resulting complex value
342 const std::complex<T> result = std::polar(mag_interp, phase_interp);
343 return result;
344 }else{
345 // assume same value for out of bound values | do not extrapolate
346 if(frequency <= this->omegas_(0)){
347 return this->values_(0);
348 }else{
349 return this->values_(this->values_.size()-1);
350 }
351 }
352 }
353
365 T phase_at(const T& frequency) const {
366 const std::complex<T> value = this->value_at(frequency);
367 const T phase = std::arg(value);
368 return phase;
369 }
370
381 T phase_deg_at(const T& frequency) const {
382 const T phase_rad = this->phase_at(frequency);
383 const T phase_deg = phase_rad * static_cast<T>(180) / std::numbers::pi_v<T>;
384 return phase_deg;
385 }
386
394 T magnitude_at(const T& frequency) const {
395 const std::complex<T> value = this->value_at(frequency);
396 const T mag = std::abs(value);
397 return mag;
398 }
399
407 T magnitude_dB_at(const T& frequency) const {
408 const T mag = this->magnitude_at(frequency);
409 const T mag_dB = static_cast<T>(20) * std::log10(mag);
410 return mag_dB;
411 }
412 };
413
426 template<class T>
427 Bode<T> prewarp_tustin(const Bode<T>& bode, const T& Ts){
428 Bode<T> result(controlpp::prewarp_tustin(bode.frequencies(), Ts), bode.values());
429 return result;
430 }
431
444 template<class T>
445 Bode<T> unwarp_tustin(const Bode<T>& bode, const T& Ts){
446 Bode<T> result(controlpp::unwarp_tustin(bode.frequencies(), Ts), bode.values());
447 return result;
448 }
449
460 template<class T>
461 const Eigen::Vector<T, Eigen::Dynamic>& frequencies(const Bode<T>& bode) {
462 return bode.frequencies();
463 }
464
475 template<class T>
476 Eigen::Vector<T, Eigen::Dynamic> frequencies_hz(const Bode<T>& bode) {
477 return bode.frequencies() * std::numbers::inv_pi_v<T> / 2;
478 }
479
490 template<class T>
491 Eigen::Vector<T, Eigen::Dynamic> real(const Bode<T>& bode){
492 return bode.values().real();
493 }
494
505 template<class T>
506 Eigen::Vector<T, Eigen::Dynamic> imag(const Bode<T>& bode){
507 return bode.values().imag();
508 }
509
520 template<class T>
521 const Eigen::Vector<T, Eigen::Dynamic>& values(const Bode<T>& bode) {
522 return bode.values();
523 }
524
537 template<class T>
538 Eigen::Vector<T, Eigen::Dynamic> magnitudes(const Bode<T>& bode) {
539 return bode.values().array().abs();
540 }
541
554 template<class T>
555 Eigen::Vector<T, Eigen::Dynamic> magnitudes_dB(const Bode<T>& bode) {
556 return static_cast<T>(20) * bode.values().array().abs().log10();
557 }
558
571 template<class T>
572 Eigen::Vector<T, Eigen::Dynamic> phases(const Bode<T>& bode) {
573 Eigen::Vector<T, Eigen::Dynamic> result = bode.values().array().arg();
574 return unwrap_rad(result);
575 }
576
590 template<class T>
591 Eigen::Vector<T, Eigen::Dynamic> phases_deg(const Bode<T>& bode) {
592 Eigen::Vector<T, Eigen::Dynamic> result = bode.values().array().arg() * static_cast<T>(180) / std::numbers::pi_v<T>;
593 return unwrap_deg(result);
594 }
595
613 template<class T>
614 TimeSeries<T> impulse(const Bode<T>& bode, const T& time_step, const T& simulation_time){
615 assert(bode.empty() == false);
616 assert(simulation_time > T(0));
617 assert(time_step > T(0));
618
619 const T pi = std::numbers::pi_v<T>;
620 const std::complex<T> j(0, 1);
621
622 const T start_time = 0.0;
623 const size_t number_of_samples = static_cast<size_t>(simulation_time / time_step + T(0.5));
624 Eigen::Vector<T, Eigen::Dynamic> times = Eigen::Vector<T, Eigen::Dynamic>::LinSpaced(number_of_samples, start_time, simulation_time);
625 Eigen::Vector<T, Eigen::Dynamic> values(number_of_samples);
626 values.setZero();
627
628 const auto f1 = bode.frequencies().head(bode.frequencies().size() - 1);
629 const auto f2 = bode.frequencies().tail(bode.frequencies().size() - 1);
630
631 Eigen::Vector<T, Eigen::Dynamic> delta_f = f2.array() - f1.array();
632 T df_max = delta_f.maxCoeff();
633
634 const auto X1 = bode.values().head(bode.values().size() - 1);
635 const auto X2 = bode.values().tail(bode.values().size() - 1);
636
637 // estimate where to switch from the small t or t=0 solution to the large t solution
638 const T t_switch = 0.001 / (2 * pi * df_max);
639
640 // complex phase of the current iteration
641 Eigen::Vector<std::complex<T>, Eigen::Dynamic> e_j_2_pi_f_ti(bode.frequencies().size());
642 e_j_2_pi_f_ti.setOnes();
643
644 // complex phase turner (is also the value of the first iteration)
645 Eigen::Vector<std::complex<T>, Eigen::Dynamic> e_j_2_pi_f_t1 = (j * T(2) * pi * time_step * bode.frequencies().array()).exp();
646
647 const bool includes_dc = bode.frequencies()[0] == 0;
648 const std::complex<T> dc_gain = bode.values()[0];
649 const T delta_f_dc = bode.frequencies()[0];
650
651 // special case t=0
652 std::complex<T> v0_one_sided = 0;
653 {
654 // the one sided result for positive frequencies
655 v0_one_sided = ((X1.array() + X2.array()) * delta_f.array()).sum() * T(0.5);
656
657 // add artificial sample at 0Hz
658 if(includes_dc == false){
659 v0_one_sided += ((dc_gain + X1(0)) * delta_f_dc) * T(0.5);
660 }
661
662 values(0) = std::real(v0_one_sided) * 2; // assume mirrored conjugated negative frequencies
663 }
664
665
666 // linear approximation (for numerical stability)
667 size_t t_itr = 1;
668 std::complex<T> LinF = j * (pi/T(3)) * (delta_f.array() * (X1.array() * (T(2) * f1.array() + f2.array()) + X2.array() * (T(2) * f2.array() + f1.array()))).sum();
669
670 // add artificial sample at 0Hz
671 if(includes_dc == false){
672 LinF += j * (pi/T(3)) * (delta_f_dc * (dc_gain * f1(0) + X1(0) * (T(2) * f1(0))));
673 }
674
675 for(; (t_itr < static_cast<size_t>(number_of_samples)) && (times(t_itr) <= t_switch); ++ t_itr){
676 const T t = times(t_itr);
677 std::complex<T> v = v0_one_sided + LinF * t;
678 values(t_itr) = std::real(v) * 2; // assume mirrored conjugated negative frequencies (imaginary part cancels, real part adds twice)
679 e_j_2_pi_f_ti.array() *= e_j_2_pi_f_t1.array();
680 }
681
682 // exact integration
683 for(; t_itr < static_cast<size_t>(number_of_samples); ++ t_itr){
684 const T t = times(t_itr);
685 const T w = 2 * pi * t;
686
687 e_j_2_pi_f_ti.array() *= e_j_2_pi_f_t1.array();
688
689 const auto E_f1 = e_j_2_pi_f_ti.head(e_j_2_pi_f_ti.size()-1);
690 const auto E_f2 = e_j_2_pi_f_ti.tail(e_j_2_pi_f_ti.size()-1);
691
692 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> Ia = E_f1.array() * (T(1) + j * w * delta_f.array()) - E_f2.array();
693 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> Ib = E_f2.array() * (T(1) - j * w * delta_f.array()) - E_f1.array();
694 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> I = (X1.array() * Ia.array() + X2.array() * Ib.array()) / delta_f.array();
695
696 std::complex<T> sum = I.sum();
697
698 // add artificial dc sample
699 if(includes_dc == false){
700 const std::complex<T> Ia_ = (T(1) + j * w * delta_f_dc) - E_f1(0);
701 const std::complex<T> Ib_ = E_f1(0) * (T(1) - j * w * delta_f_dc) - T(1);
702 const std::complex<T> I = (dc_gain * Ia_ + X1(0) * Ib_) / delta_f_dc;
703 sum += I;
704 }
705
706 const std::complex<T> v = sum / (w * w);
707
708 values(t_itr) = std::real(v) * 2; // assume mirrored conjugated negative frequencies
709 }
710
711 return TimeSeries<T>(std::move(times), std::move(values));
712 }
713
738 template<class T>
740 const T max_freq = bode.frequencies()[bode.frequencies().size()-1];
741 const T min_freq = bode.frequencies()[0];
742
743 const T time_step = static_cast<T>(1) / (T(2 * 4) * max_freq); // oversample 4 times
744 const T total_time = static_cast<T>(1) / (T(4) * min_freq); // only use a quarter of the slowest frequency
745
746 return impulse(bode, time_step, total_time);
747 }
748
764 template<class T>
765 void integrate(TimeSeries<T>& out, const TimeSeries<T>& in, const T& v0 = T(0)){
766 T prev_value = in.values(0);
767 T sum = 0;
768 out.resize(in.size());
769 out.values(0) = v0;
770 for(size_t i = 1; i < in.size(); ++i){
771 // first order integration
772 const T sum_i = (in.values(i) + prev_value) * (in.times(i) - in.times(i-1)) * T(0.5);
773 prev_value = in.values(i);
774 sum += sum_i;
775 out.values(i) = sum;
776 }
777 }
778
787 template<class T>
788 TimeSeries<T> integrate(const TimeSeries<T>& in, const T& v0 = T(0)){
789 TimeSeries<T> out;
790 return integrate(out, in, v0);
791 }
792
809 template<class T>
812 integrate<T>(imp, imp);
813 return imp;
814 }
815
827 template<class T>
828 TimeSeries<T> step(const Bode<T>& bode, const T& time_step, const T& simulation_time){
829 TimeSeries<T> imp = impulse(bode, time_step, simulation_time);
830 integrate<T>(imp, imp);
831 return imp;
832 }
833
834 // operator +
835 //-----------------
836
845 template<class T>
846 Bode<T> operator+ (const Bode<T>& l, const Bode<T>& r){
847 assert(l.size() == r.size());
848 assert(l.frequencies() == r.frequencies());
849 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() + r.values().array();
850 return Bode<T>(l.frequencies(), result);
851 }
852
862 template<class T, std::convertible_to<T> T2>
863 Bode<T> operator+ (const Bode<T>& l, const T2& r){
864 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() + static_cast<std::complex<T>>(l)(r);
865 return Bode<T>(l.frequencies(), result);
866 }
867
877 template<class T, std::convertible_to<T> T2>
878 Bode<T> operator+ (const T2& l, const Bode<T>& r){
879 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = static_cast<std::complex<T>>(l) + r.values().array();
880 return Bode<T>(r.frequencies(), result);
881 }
882
890 template<class T>
892 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = +b.values().array();
893 return Bode<T>(b.frequencies(), result);
894 }
895
907 template<class T, int NumOrder, int DenOrder>
909 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> r_mags = r.eval_frequencies(l.frequencies());
910 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() + r_mags.array();
911 return Bode<T>(l.frequencies(), result);
912 }
913
925 template<class T, int NumOrder, int DenOrder>
927 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> l_mags = l.eval_frequencies(r.frequencies());
928 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l_mags.array() + l.values().array();
929 return Bode<T>(r.frequencies(), result);
930 }
931
932 // operator -
933 //-----------------
934
935 template<class T>
936 Bode<T> operator- (const Bode<T>& l, const Bode<T>& r){
937 assert(l.size() == r.size());
938 assert(l.frequencies() == r.frequencies());
939 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() - r.values().array();
940 return Bode<T>(l.frequencies(), result);
941 }
942
943 template<class T, std::convertible_to<T> T2>
944 Bode<T> operator- (const Bode<T>& l, const T2& r){
945 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() - static_cast<std::complex<T>>(r);
946 return Bode<T>(l.frequencies(), result);
947 }
948
949 template<class T, std::convertible_to<T> T2>
950 Bode<T> operator- (const T2& l, const Bode<T>& r){
951 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = static_cast<std::complex<T>>(l) - r.values().array();
952 return Bode<T>(r.frequencies(), result);
953 }
954
955 template<class T>
957 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = -b.values().array();
958 return Bode<T>(b.frequencies(), result);
959 }
960
961 template<class T, int NumOrder, int DenOrder>
963 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> r_mags = r.eval_frequencies(l.frequencies());
964 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() - r_mags.array();
965 return Bode<T>(l.frequencies(), result);
966 }
967
968 template<class T, int NumOrder, int DenOrder>
970 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> l_mags = l.eval_frequencies(r.frequencies());
971 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l_mags.array() - l.values().array();
972 return Bode<T>(r.frequencies(), result);
973 }
974
975 // operator *
976 //-----------------
977
978 template<class T>
979 Bode<T> operator* (const Bode<T>& l, const Bode<T>& r){
980 assert(l.size() == r.size());
981 assert(l.frequencies() == r.frequencies());
982 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() * r.values().array();
983 return Bode<T>(l.frequencies(), result);
984 }
985
986 template<class T, std::convertible_to<T> T2>
987 Bode<T> operator* (const Bode<T>& l, const T2& r){
988 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() * static_cast<std::complex<T>>(r);
989 return Bode<T>(l.frequencies(), result);
990 }
991
992 template<class T, std::convertible_to<T> T2>
993 Bode<T> operator* (const T2& l, const Bode<T>& r){
994 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = static_cast<std::complex<T>>(l) * r.values().array();
995 return Bode<T>(r.frequencies(), result);
996 }
997
998 template<class T, int NumOrder, int DenOrder>
1000 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> r_mags = r.eval_frequencies(l.frequencies());
1001 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() * r_mags.array();
1002 return Bode<T>(l.frequencies(), result);
1003 }
1004
1005 template<class T, int NumOrder, int DenOrder>
1007 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> l_mags = l.eval_frequencies(r.frequencies());
1008 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l_mags.array() * r.values().array();
1009 return Bode<T>(r.frequencies(), result);
1010 }
1011
1012 // operator /
1013 //-----------------
1014
1025 template<class T>
1026 Bode<T> operator/ (const Bode<T>& l, const Bode<T>& r){
1027 assert(l.size() == r.size());
1028 assert(l.frequencies() == r.frequencies());
1029 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() / r.values().array();
1030 return Bode<T>(l.frequencies(), result);
1031 }
1032
1033 template<class T, std::convertible_to<T> T2>
1034 Bode<T> operator/ (const Bode<T>& l, const T2& r){
1035 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() / std::complex<T>(r);
1036 return Bode<T>(l.frequencies(), result);
1037 }
1038
1039 template<class T, std::convertible_to<T> T2>
1040 Bode<T> operator/ (const T2& l, const Bode<T>& r){
1041 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = std::complex<T>(l) / r.values().array();
1042 return Bode<T>(r.frequencies(), result);
1043 }
1044
1045 template<class T, int NumOrder, int DenOrder>
1047 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> r_mags = r.eval_frequencies(l.frequencies().array());
1048 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.values().array() / r_mags.array();
1049 return Bode<T>(l.frequencies(), result);
1050 }
1051
1052 template<class T, int NumOrder, int DenOrder>
1054 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> result = l.eval_frequencies(r.frequencies().array()) / r.values().array();
1055 return Bode<T>(r.frequencies(), result);
1056 }
1057
1070 template<class T, int NumOrder, int DenOrder>
1073 const Eigen::Vector<T, Eigen::Dynamic>& freqs
1074 ){
1075 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> complex_magnitudes = tf.eval_frequencies(freqs);
1076 const Bode<T> result(freqs, complex_magnitudes);
1077 return result;
1078 }
1079
1083 template<class T, int NumOrder, int DenOrder>
1086 Eigen::Vector<T, Eigen::Dynamic>&& freqs
1087 ){
1088 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> complex_magnitudes = tf.eval_frequencies(freqs);
1089 const Bode<T> result(std::move(freqs), complex_magnitudes);
1090 return result;
1091 }
1092
1105 template<class T, int NumOrder, int DenOrder>
1108 const Eigen::Vector<T, Eigen::Dynamic>& freqs_Hz
1109 ){
1110 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> complex_magnitudes = tf.eval_frequencies_hz(freqs_Hz);
1111 Eigen::Vector<T, Eigen::Dynamic> freqs_rad = freqs_Hz.array() * 2 * std::numbers::pi_v<T>;
1112 const Bode<T> result(std::move(freqs_rad), complex_magnitudes);
1113 return result;
1114 }
1115
1119 template<class T, int NumOrder, int DenOrder>
1122 Eigen::Vector<T, Eigen::Dynamic>&& freqs_Hz
1123 ){
1124 const Eigen::Vector<std::complex<T>, Eigen::Dynamic> complex_magnitudes = tf.eval_frequencies_hz(freqs_Hz);
1125 freqs_Hz *= 2 * std::numbers::pi_v<T>;
1126 const Bode<T> result(std::move(freqs_Hz), complex_magnitudes);
1127 return result;
1128 }
1129
1140 template<class T, int NumOrder, int DenOrder, std::convertible_to<T> T1, std::convertible_to<T> T2>
1143 const T1& slowest_freq_Hz,
1144 const T2& fastest_freq_Hz,
1145 const int samples_per_decade=100
1146 ){
1147 const T decades = std::log10(static_cast<T>(fastest_freq_Hz)) - std::log10(static_cast<T>(slowest_freq_Hz));
1148 const T samples = samples_per_decade * decades;
1149 const Eigen::Vector<T, Eigen::Dynamic> freqs_Hz = Eigen::Vector<T, Eigen::Dynamic>::LinSpaced(samples, std::log(slowest_freq_Hz), std::log(fastest_freq_Hz)).array().exp();
1150 return bode_hz<T, NumOrder, DenOrder>(tf, freqs_Hz);
1151 }
1152
1163 template<class T, int NumOrder, int DenOrder, std::convertible_to<T> T1, std::convertible_to<T> T2>
1166 const T1& slowest_freq_rad,
1167 const T2& fastest_freq_rad,
1168 const int samples_per_decade=100
1169 ){
1170 const T decades = std::log10(static_cast<T>(fastest_freq_rad)) - std::log10(static_cast<T>(slowest_freq_rad));
1171 const T samples = samples_per_decade * decades;
1172
1173 const Eigen::Vector<T, Eigen::Dynamic> freqs_rad = Eigen::Vector<T, Eigen::Dynamic>::LinSpaced(samples, std::log(slowest_freq_rad), std::log(fastest_freq_rad)).array().exp();
1174 return bode<T, NumOrder, DenOrder>(tf, freqs_rad);
1175 }
1176
1189 template<class T, int NumOrder, int DenOrder>
1192 const int samples_per_decade=100
1193 ){
1194 const auto [slowest_freq_rad, fastest_freq_rad] = slowest_fastest_frequencies(tf);
1195 const T frequency_from_Hz = slowest_freq_rad / (static_cast<T>(10 * 2) * std::numbers::pi_v<T>);
1196 const T frequency_to_Hz = fastest_freq_rad * static_cast<T>(10 / 2) / std::numbers::pi_v<T>;
1197 return bode_hz(tf, frequency_from_Hz, frequency_to_Hz, samples_per_decade);
1198 }
1199
1212 template<class T, int NumOrder, int DenOrder>
1215 const int samples_per_decade=100
1216 ){
1217 const auto [slowest_freq_rad, fastest_freq_rad] = slowest_fastest_frequencies(tf);
1218 const T frequency_from_rad = slowest_freq_rad / static_cast<T>(10);
1219 const T frequency_to_rad = fastest_freq_rad * static_cast<T>(10);
1220 return bode(tf, frequency_from_rad, frequency_to_rad, samples_per_decade);
1221 }
1222
1230 template<class T>
1231 void write_csv (std::ostream& stream, const Bode<T>& bode){
1232 stream << "Frequencies (Hz), Magnitudes (dB), Phases (deg), Real, Imag" << std::endl;
1233
1234 const int n = bode.size();
1235 for(int i = 0; i < n; ++i){
1236 const T omega = bode.frequency(i);
1237 const T f = to_hz(omega);
1238
1239 const std::complex<T> value = bode.value(i);
1240 const T mag = to_dB(std::abs(value));
1241 const T phase = to_deg(std::arg(value));
1242 const T real = std::real(value);
1243 const T imag = std::imag(value);
1244
1245 stream << f << ", " << mag << ", " << phase << ", " << real << ", " << imag;
1246 if(i < n-1) stream << "\n";
1247 }
1248 }
1249
1257
1258 std::ostream& operator<<(std::ostream& stream, EBodeCsvReadError val);
1259
1264 AutoHz,
1265 AutoRad,
1266 ForceHz,
1267 ForceRad,
1268 };
1269
1274 Auto,
1275 ForceAbs,
1276 ForceDB,
1277 };
1278
1283 AutoRad,
1284 AutoDeg,
1285 ForceRad,
1286 ForceDeg
1287 };
1288
1320 tl::expected<
1321 Bode<double>, // success
1322 std::variant<EBodeCsvReadError, csvd::ReadError>> //error
1324 std::istream& stream,
1325 const csvd::Settings& csv_settings = csvd::Settings(),
1329 );
1330
1331}
General algorithms that are used in multiple places in the library.
Frequency response data.
Definition Bode.hpp:43
const T & frequency(std::size_t n) const
Returns a reference to the frequency in rad at the index n.
Definition Bode.hpp:160
bool empty() const
Definition Bode.hpp:300
Eigen::Vector< T, Eigen::Dynamic > & frequencies()
Returns a reference to the frequency vector in rad.
Definition Bode.hpp:142
T phase_at(const T &frequency) const
Returns the phase at the passed frequency.
Definition Bode.hpp:365
size_t size() const
Definition Bode.hpp:298
void prewarp_tustin(const T &Ts)
Applies tustin pre-warping to the frequency axis.
Definition Bode.hpp:243
void unwarp_tustin(const T &Ts)
Unwarps the pre-warping. Or calculates how system frequencies will shift after tustin discretisation.
Definition Bode.hpp:260
const Eigen::Vector< T, Eigen::Dynamic > & frequencies() const
Returns a const-reference to the frequency vector in rad.
Definition Bode.hpp:133
Bode(const Eigen::Vector< T, Eigen::Dynamic > &freqs_rad, const Eigen::Vector< std::complex< T >, Eigen::Dynamic > &values)
Constructs a bode from frequencies and complex magnitudes.
Definition Bode.hpp:56
std::complex< T > & value(std::size_t n)
Returns a reference to the complex magnitue at the n-th position.
Definition Bode.hpp:294
std::complex< T > value_at(const T &frequency) const
returns the complex value at the given frequency using interpolation
Definition Bode.hpp:315
const std::complex< T > & value(std::size_t n) const
Returns a reference to the complex magnitue at the n-th position.
Definition Bode.hpp:285
T magnitude_dB_at(const T &frequency) const
Returns the magnitude at the passed frequency.
Definition Bode.hpp:407
Bode(Eigen::Vector< T, Eigen::Dynamic > &&freqs_rad, const Eigen::Vector< std::complex< T >, Eigen::Dynamic > &values)
Definition Bode.hpp:110
Bode(Eigen::Vector< T, Eigen::Dynamic > &&freqs_rad, Eigen::Vector< std::complex< T >, Eigen::Dynamic > &&values)
Definition Bode.hpp:74
Bode()=default
const Eigen::Vector< std::complex< T >, Eigen::Dynamic > & values() const
Returns a const-reference to the complex magnitued vector.
Definition Bode.hpp:268
T phase_deg_at(const T &frequency) const
return the phase at the given frequency in degree
Definition Bode.hpp:381
T & frequency(std::size_t n)
Returns a reference to the frequency in rad at the index n.
Definition Bode.hpp:151
Eigen::Vector< std::complex< T >, Eigen::Dynamic > & values()
Returns a reference to the complex magnitued vector.
Definition Bode.hpp:276
Bode(const Eigen::Vector< T, Eigen::Dynamic > &freqs_rad, Eigen::Vector< std::complex< T >, Eigen::Dynamic > &&values)
Definition Bode.hpp:92
T magnitude_at(const T &frequency) const
Returns the magnitude at the passed frequency.
Definition Bode.hpp:394
Continuous transfer functions in the s lapace plain.
Definition ContinuousTransferFunction.hpp:28
Eigen::Vector< std::complex< T >, M > eval_frequencies_hz(const Eigen::Vector< T, M > &frequencies) const
Evaluates the transfer function (Hz) at the given frequencies.
Definition ContinuousTransferFunction.hpp:142
Eigen::Vector< std::complex< T >, M > eval_frequencies(const Eigen::Vector< T, M > &frequencies) const
Evaluates the transfer function (rad/s) at the given frequencies.
Definition ContinuousTransferFunction.hpp:132
Contiains time and values pairs.
Definition TimeSeries.hpp:26
Eigen::Vector< T, Eigen::Dynamic > & times()
Definition TimeSeries.hpp:79
void resize(int n)
Definition TimeSeries.hpp:100
size_t size() const
Definition TimeSeries.hpp:105
Eigen::Vector< T, Eigen::Dynamic > & values()
Definition TimeSeries.hpp:85
void integrate(TimeSeries< T > &out, const TimeSeries< T > &in, const T &v0=T(0))
Integrates the time series and writes it to out.
Definition Bode.hpp:765
const Eigen::Vector< T, Eigen::Dynamic > & values(const Bode< T > &bode)
Returns the complex values of the bode data.
Definition Bode.hpp:521
const Eigen::Vector< T, Eigen::Dynamic > & frequencies(const Bode< T > &bode)
Converts and returns the frequency vector in rad.
Definition Bode.hpp:461
TimeSeries< T > impulse(const Bode< T > &bode, const T &time_step, const T &simulation_time)
Calculates the impulse-response of frequency data.
Definition Bode.hpp:614
Eigen::Vector< T, Eigen::Dynamic > phases(const Bode< T > &bode)
Creates a vector of phases in rad.
Definition Bode.hpp:572
Bode< T > operator+(const Bode< T > &l, const Bode< T > &r)
Adds two bode plots together.
Definition Bode.hpp:846
Eigen::Vector< T, Eigen::Dynamic > frequencies_hz(const Bode< T > &bode)
Converts and returns the frequency vector in Hz.
Definition Bode.hpp:476
Eigen::Vector< T, Eigen::Dynamic > magnitudes_dB(const Bode< T > &bode)
Creates a vector of magnitudes in dB.
Definition Bode.hpp:555
Eigen::Vector< T, Eigen::Dynamic > phases_deg(const Bode< T > &bode)
Creates a vector of phases in degree.
Definition Bode.hpp:591
Eigen::Vector< T, Eigen::Dynamic > magnitudes(const Bode< T > &bode)
Creates a vector containing the absolute magnitudes.
Definition Bode.hpp:538
Eigen::Vector< T, Eigen::Dynamic > imag(const Bode< T > &bode)
Converts and returns the frequency vector in rad/s.
Definition Bode.hpp:506
Bode< T > prewarp_tustin(const Bode< T > &bode, const T &Ts)
Prewarps the frequency axis of a bode plot for tustin discretisation.
Definition Bode.hpp:427
Bode< T > unwarp_tustin(const Bode< T > &bode, const T &Ts)
Unwarps the frequency axis of a bode plot for tustin discretisation.
Definition Bode.hpp:445
Eigen::Vector< T, Eigen::Dynamic > real(const Bode< T > &bode)
Converts and returns the frequency vector in rad/s.
Definition Bode.hpp:491
The main namespace for the Control++ library.
Definition Bode.cpp:3
EPhaseInterpretation
Enum that determines how phase data will be interpreted when reading CSV data.
Definition Bode.hpp:1282
@ ForceDeg
Forces the magnitude data to be interpreted in deg regardless of the header name.
@ AutoDeg
Automatically infers the unit from the header or interprets the data as deg if no unit is found in th...
EBodeCsvReadError
Error cases for reading/parsing bode data from CSV formated data.
Definition Bode.hpp:1253
@ CouldNotFindFrequencyVector
Could not find the frequency axis in the csv data. Searched for a header that starts with "f" (case-i...
@ CouldNotFindAmplitudeVectors
Could not find the amplitude data. Needed either: 1) Two columns that start with "re" and "im" (case-...
std::ostream & operator<<(std::ostream &stream, EBodeCsvReadError val)
Definition Bode.cpp:5
Bode< T > bode_hz(const ContinuousTransferFunction< T, NumOrder, DenOrder > &tf, const Eigen::Vector< T, Eigen::Dynamic > &freqs_Hz)
Calculates the bode response for a pre defined frequency (Hz) vector.
Definition Bode.hpp:1106
T to_hz(const T &radps)
Converts a number from radiants per second to herz.
Definition conversion.hpp:16
void write_csv(std::ostream &stream, const Bode< T > &bode)
Prints a bode plot to an output stream as a .csv file.
Definition Bode.hpp:1231
Eigen::Vector< T, N > unwrap_deg(const Eigen::Vector< T, N > &phases)
Unwinds phase jumps of in degrees.
Definition math.hpp:93
Bode< T > bode(const ContinuousTransferFunction< T, NumOrder, DenOrder > &tf, const Eigen::Vector< T, Eigen::Dynamic > &freqs)
Calculates the bode response for a pre defined frequency (rad/s) vector.
Definition Bode.hpp:1071
std::optional< std::pair< Itr, Itr > > find_enclosing(Itr first, Itr last, const T &v)
Finds elements in a range that enclose v.
Definition algorithm.hpp:29
Bode< T > operator-(const Bode< T > &l, const Bode< T > &r)
Definition Bode.hpp:936
Bode< T > operator*(const Bode< T > &l, const Bode< T > &r)
Definition Bode.hpp:979
TimeSeries< T > step(const DiscreteStateSpace< T, NStates, 1, 1 > &dss, double Ts, double simulation_time)
calculates the step response of a system
Definition analysis.hpp:34
EFrequencyInterpretation
Enum that determines how frequency data will be interpreted when reading CSV data.
Definition Bode.hpp:1263
@ ForceHz
When reading frequency data the data is always interpreted in Hz regardless of the header name.
@ AutoHz
Automatically infers the unit from the header or interprets the data in Hz if no unit is found in the...
@ AutoRad
Automatically infers the unit from the header or interprets the data in rad if no unit is found in th...
@ ForceRad
When reading frequency data the data is always interpreted in rad regardless of the header name.
std::tuple< T, T > slowest_fastest_frequencies(const ContinuousTransferFunction< T, NumOrder, DenOrder > &tf, T alternative=static_cast< T >(1))
Calculates the slowest (lowest) and fastest (highest) frequencies of a continuous transfer function.
Definition analysis.hpp:66
EMagnitudeInterpretation
Enum that determines how frequency data will be interpreted when reading CSV data.
Definition Bode.hpp:1273
@ Auto
Automatically infers the unit from the header and interprets as deci-Bell if "dB" has been found or i...
@ ForceAbs
Forces the magnitude data to be interpreted in absolute values regardless of the header name.
@ ForceDB
Forces the magnitude data to be interpreted in dB regardless of the header name.
T to_dB(const T &value)
Definition conversion.hpp:70
Bode< T > operator/(const Bode< T > &l, const Bode< T > &r)
TODO: make it also work for bode that have different frequency vectors.
Definition Bode.hpp:1026
tl::expected< Bode< double >, std::variant< EBodeCsvReadError, csvd::ReadError > > read_bode_from_csv(std::istream &stream, const csvd::Settings &csv_settings, EFrequencyInterpretation freq_interp, EMagnitudeInterpretation mag_interp, EPhaseInterpretation phase_interp)
Loads bode data from csv data.
Definition Bode.cpp:61
T to_deg(const T &rad)
Definition conversion.hpp:46
Eigen::Vector< T, N > unwrap_rad(const Eigen::Vector< T, N > &phases)
Unwinds phase jumps of in radiants.
Definition math.hpp:80