summaryrefslogtreecommitdiffstats
path: root/src/control.cpp
diff options
context:
space:
mode:
authorNao Pross <np@0hm.ch>2024-02-12 14:52:43 +0100
committerNao Pross <np@0hm.ch>2024-02-12 14:52:43 +0100
commiteda5bc26f44ee9a6f83dcf8c91f17296d7fc509d (patch)
treebc2efa38ff4e350f9a111ac87065cd7ae9a911c7 /src/control.cpp
downloadfsisotool-eda5bc26f44ee9a6f83dcf8c91f17296d7fc509d.tar.gz
fsisotool-eda5bc26f44ee9a6f83dcf8c91f17296d7fc509d.zip
Move into version control
Diffstat (limited to 'src/control.cpp')
-rw-r--r--src/control.cpp130
1 files changed, 130 insertions, 0 deletions
diff --git a/src/control.cpp b/src/control.cpp
new file mode 100644
index 0000000..f57baec
--- /dev/null
+++ b/src/control.cpp
@@ -0,0 +1,130 @@
+#include "control.h"
+#include <armadillo>
+
+
+namespace ct
+{
+ TransferFn::TransferFn(complex gain)
+ : gain(gain)
+ , num(math::Poly<complex>{1})
+ , den(math::Poly<complex>{1})
+ {
+ num(0) = 1.;
+ den(0) = 1.;
+ }
+
+ bool TransferFn::is_proper() const
+ {
+ return den.degree() >= num.degree();
+ }
+
+ bool TransferFn::is_strictly_proper() const
+ {
+ return den.degree() >= num.degree();
+ }
+
+ TransferFn feedback(const TransferFn& tf, double k)
+ {
+ // TransferFn tf_cl;
+ }
+
+ LocusSeries::LocusSeries(double start, double end, size_t nsamples)
+ : n_samples(nsamples)
+ , start(start)
+ , end(end)
+ , in(arma::logspace(log10(start), log10(end), n_samples))
+ // FIXME: do not hardcode one root locus
+ , out(1, n_samples, arma::fill::zeros)
+ {}
+
+ void rlocus(const TransferFn& tf, LocusSeries& ls)
+ {
+ using namespace arma;
+
+ CT_ASSERT(tf.is_proper());
+ CT_ASSERT(tf.den.degree() > 0);
+
+ // prepare output
+ ls.out = cx_mat(tf.den.degree(), ls.n_samples, fill::zeros);
+
+ // compute roots
+ for (int i = 0; i < ls.n_samples; i++)
+ ls.out.col(i) = (tf.den + ls.in(i) * tf.num).roots();
+
+ // sort the roots
+ }
+
+ SSModel::SSModel(size_t n_in, size_t n_out, size_t n_states)
+ : n_in(n_in)
+ , n_out(n_out)
+ , n_states(n_states)
+ , A(n_states, n_states)
+ , B(n_states, n_in)
+ , C(n_out, n_states)
+ , D(n_out, n_in)
+ {}
+
+ SSModel ctrb_form(const TransferFn& tf)
+ {
+ // TODO: change to proper by implementing D
+ CT_ASSERT(tf.is_strictly_proper());
+
+ int ord = tf.den.degree();
+ SSModel ss(1, 1, ord);
+
+ ss.B(ord - 1) = 1;
+ for (int i = 0; i < ord; i++)
+ {
+ ss.A(ord - 1, i) = tf.den(ord - i);
+ if (i < tf.num.coeffs.n_elem)
+ ss.C(i) = tf.num(i) * tf.gain;
+ }
+
+ return ss;
+ }
+
+ TimeSeries::TimeSeries(double start, double end, size_t n_samples)
+ : n_samples(n_samples)
+ , start(start)
+ , end(end)
+ , dt((start - end) / (n_samples - 1))
+ , time(arma::linspace(start, end, n_samples))
+ // FIXME: do not hardcode SISO
+ , in(1, n_samples, arma::fill::zeros)
+ , out(1, n_samples, arma::fill::zeros)
+ {}
+
+ void response(const SSModel& ss, TimeSeries& ts)
+ {
+ using namespace arma;
+
+ CT_ASSERT(ts.in.n_rows == ss.n_in);
+ CT_ASSERT(ts.in.n_cols == ts.n_samples);
+
+ ts.out = cx_mat(ss.n_out, ts.n_samples, fill::zeros);
+ ts.state = cx_mat(ss.n_states, ts.n_samples, fill::zeros);
+ // FIXME: non-homogeneous initial condition
+
+ // For the current application we want this to be faster rather than
+ // accurate. Hence we use a simulation with ZOH input is cheap and probably
+ // good enough.
+ // FIXME: do not invert matrix like that
+ const cx_mat Ad = expmat(ss.A * ts.dt);
+ const cx_mat Bd = (Ad - eye(size(Ad))) * pinv(ss.A) * ss.B;
+
+ for (int k = 0; k < ts.n_samples - 1; k++)
+ ts.state.col(k + 1) = Ad * ts.state.col(k) + Bd * ts.in.col(k);
+
+ ts.out = ss.C * ts.state + ss.D * ts.in;
+ }
+
+
+ void step(const SSModel& ss, TimeSeries& ts)
+ {
+ ts.in = arma::cx_mat(ss.n_in, ts.n_samples);
+ ts.in.fill(1.);
+ response(ss, ts);
+ }
+}
+
+// vim: ts=2 sw=2 noet: