dsmc: Digitalservo Motion Control Library
A Rust library for designing and analyzing motion control systems: transfer functions and state space models, discretization, frequency analysis, trajectory generation, feedforward control (perfect tracking control), signal processing and system identification.
Features
| Area | What you get |
|---|---|
| Systems | TransferFunction in s or z (the domain is a type parameter, so they cannot be mixed up), the tf! macro, arithmetic, pole-zero cancellation, poles / zeros, partial fractions; StateSpace |
| Discretization | Zero-order hold (exact), bilinear (Tustin), matched z-transform in both directions, sample-by-sample simulators |
| Frequency analysis | FrequencyTransferFunction (ω -> G(jω), including dead time), Bode diagram, Nyquist plot, FFT, Welch's method |
| Laplace transform | Inverse Laplace transform, stable (non-causal) inverse of nonminimum-phase systems, piecewise-polynomial signals |
| Trajectories | Modified trapezoid / sine / constant velocity, cycloid, harmonic, smoothstep of any smoothness |
| Feedforward | Multirate perfect tracking control (PTC), with pre-actuation for nonminimum-phase plants |
| Signal processing | Pseudo-differentiator, delay |
| System identification | ARX models and linear regressions by least squares or Kalman filter, instrumental variables (IV) for ARX models, SRIVC for continuous-time models, Levy / Sanathanan-Koerner, vector fitting, Gaussian process regression |
| Logging | CSV output of any Serialize value |
Installation
or in Cargo.toml:
[]
= "0.1"
Examples
Transfer functions
use ;
// From a string: the variable (`s` or `z`) selects continuous / discrete time at compile time.
let wn = 100.0;
let plant = tf!;
let controller = tf!;
// Arithmetic (common poles / zeros are cancelled)
let open_loop = &controller * &plant;
println!;
// Closed loop under unity negative feedback: L / (1 + L), same as `&open_loop / &(1.0 + &open_loop)`
let closed_loop = open_loop.unity_feedback;
// From coefficients (descending order), or parsed at runtime with error handling
let g = continuous;
let h: = "1 / (s + 1)^2".parse.unwrap;
// Poles, zeros, partial fractions and the inverse Laplace transform
let poles = g.pz_map.poles;
println!; // x(t) = exp(-1.000t) - exp(-2.000t)
let x = h.inverse_laplace;
assert!;
Discretization and simulation
use tf;
use ;
use DiscretizedSystem;
use ZerosAtInfinity;
let g = tf!;
let ts = 1e-3;
let g_zoh = discretize.unwrap;
let g_tustin = discretize;
let g_matched = to_discrete.unwrap;
// Step response, one sample at a time
let mut system = from_tf.unwrap;
let y: = .map.collect;
Bode diagram and Nyquist plot
use ;
use Complex;
let plant = tf!;
let controller = tf!;
// Any element can be put in series, e.g. a dead time of 1 ms
let delay = new;
let open_loop = &.frequency_transfer_function * &delay;
// Gain [dB] and phase [rad] from 0.1 Hz to 1 kHz
let bode = new.plot;
// The same in rad/s
let bode_rad = new.plot;
let nyquist = new.plot;
// Single points
let c = open_loop.;
println!;
Results can be written to CSV directly (one row per point: frequency, gain, phase or omega, re, im):
use ;
use DataStorage;
let bode = new.plot;
let mut csv = new.unwrap;
for point in &bode
csv.close.unwrap;
Trajectory and perfect tracking control
use tf;
use DiscretizedSystem;
use LiftedDiscretizedSystem;
use ;
// Normalized motion profiles: position, velocity and acceleration over x = 0..1
let profile = ModifiedSine.generate;
// Feedforward input that makes the output of a nonminimum-phase plant follow a smooth move
// exactly at every frame (with pre-actuation before the move starts)
let ts = 1e-4;
let plant = tf!;
let y_d = piecewise; // distance, duration, start, smoothness
let model = from_tf_normalized.unwrap;
let lifted: = model.try_into.unwrap;
let u = lifted.calculate_ptc_input_for_reference_output.unwrap;
System identification
use tf;
use DiscretizedSystem;
use lsm;
let ts = 1e-3;
let mut plant = from_tf.unwrap;
// ARX model y[k] = a1 y[k-1] + a2 y[k-2] + b0 u[k-1] + b1 u[k-2]
let mut arx = new.with_input_delay;
for k in 0..1000
let g_z = arx.identify.unwrap;
println!;
With measurement noise on the output, least squares is biased. The instrumental variable (IV)
method removes the bias, either for the discrete-time ARX model or, with SRIVC (simplified refined
instrumental variable method for continuous-time systems), directly for G(s) from sampled data:
use tf;
use DiscretizedSystem;
use ;
use ;
let ts = 1e-3;
let mut plant = from_tf.unwrap;
let u: =
.map
.collect;
let y: = u.iter.map.collect; // + noise
// ARX model by least squares, then 3 IV iterations (each with the previous estimate as the
// auxiliary model generating the instruments)
let model = identify.unwrap;
let g_z = model.transfer_function;
// Continuous-time B(s) / A(s) with deg B = 0, deg A = 2, started from a state-variable filter
// 1 / (s + 30)^2
let result = identify.unwrap;
let g_s = result.model;
From measured frequency responses (e.g. dsmc::fft::welch), a continuous-time model can be fitted:
use ;
use ;
let g = tf!.frequency_transfer_function;
let samples: =
.map
.collect;
let fitted = identify.unwrap;
let model: = fitted.into;
More examples are in tests/ and in the API documentation.
License
MIT