diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index ac9b07937..56b4adb84 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.3","generation_timestamp":"2025-01-30T12:05:03","documenter_version":"1.8.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.3","generation_timestamp":"2025-01-30T12:05:26","documenter_version":"1.8.0"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index 6c8a745a5..1a0a65347 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,2 +1,2 @@ -API · ControlSystems.jl

Index

See additional API in RobustAndOptimalControl.jl: API

+API · ControlSystems.jl

Index

See additional API in RobustAndOptimalControl.jl: API

diff --git a/dev/examples/analysis/dd14e196.svg b/dev/examples/analysis/1c777ef3.svg similarity index 85% rename from dev/examples/analysis/dd14e196.svg rename to dev/examples/analysis/1c777ef3.svg index 4aa1d4c2f..b55eced13 100644 --- a/dev/examples/analysis/dd14e196.svg +++ b/dev/examples/analysis/1c777ef3.svg @@ -1,128 +1,128 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/577ded35.svg b/dev/examples/analysis/1f72c1e5.svg similarity index 86% rename from dev/examples/analysis/577ded35.svg rename to dev/examples/analysis/1f72c1e5.svg index 0dddf3320..eb2c75ef7 100644 --- a/dev/examples/analysis/577ded35.svg +++ b/dev/examples/analysis/1f72c1e5.svg @@ -1,75 +1,75 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/0d04ede3.svg b/dev/examples/analysis/462abb01.svg similarity index 92% rename from dev/examples/analysis/0d04ede3.svg rename to dev/examples/analysis/462abb01.svg index 9fd89b220..33583d206 100644 --- a/dev/examples/analysis/0d04ede3.svg +++ b/dev/examples/analysis/462abb01.svg @@ -1,51 +1,51 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/index.html b/dev/examples/analysis/index.html index fb1f6f6da..733199235 100644 --- a/dev/examples/analysis/index.html +++ b/dev/examples/analysis/index.html @@ -3,12 +3,12 @@ P = tf(1, [1, 0.2, 1]) C = pid(0.2, 1) loopgain = P*C -marginplot(loopgain)Example block output

This plot tells us that there is one gain margin of 1.27, i.e., the gain can increase by a factor of 1.27 before the system goes unstable. It also tells us that there are three different phase margins, the smallest of which is about 9°. We usually aim for a gain margin of >1.5 and a phase margin above 30-45° for a robust system. The vertical lines in the plot indicate the frequencies at which the margins have been computed.

Sensitivity analysis

More generally applicable measures of robustness include analysis of sensitivity functions, notably the peaks of the sensitivity function

\[S(s) = (I + P(s)C(s))^{-1}\]

and the complementary sensitivity function

\[T(s) = I - S(s) = (I + P(s)C(s))^{-1}P(s)C(s)\]

Examples

We can plot all four sensitivity functions referred to as the "gang of four" using gangoffourplot.

using ControlSystemsBase, Plots
+marginplot(loopgain)
Example block output

This plot tells us that there is one gain margin of 1.27, i.e., the gain can increase by a factor of 1.27 before the system goes unstable. It also tells us that there are three different phase margins, the smallest of which is about 9°. We usually aim for a gain margin of >1.5 and a phase margin above 30-45° for a robust system. The vertical lines in the plot indicate the frequencies at which the margins have been computed.

Sensitivity analysis

More generally applicable measures of robustness include analysis of sensitivity functions, notably the peaks of the sensitivity function

\[S(s) = (I + P(s)C(s))^{-1}\]

and the complementary sensitivity function

\[T(s) = I - S(s) = (I + P(s)C(s))^{-1}P(s)C(s)\]

Examples

We can plot all four sensitivity functions referred to as the "gang of four" using gangoffourplot.

using ControlSystemsBase, Plots
 P = tf(1, [1, 0.2, 1])
 C = pid(0.2, 1)
-gangoffourplot(P, C)
Example block output

The peak value of the sensitivity function, $M_S$, can be computed using hinfnorm

S = sensitivity(P, C)
+gangoffourplot(P, C)
Example block output

The peak value of the sensitivity function, $M_S$, can be computed using hinfnorm

S = sensitivity(P, C)
 Ms, ωMs = hinfnorm(S)
(8.14779356151499, 1.0941856436200372)

And we can plot a circle in the Nyquist plot corresponding to the inverse distance between the loop-transfer function and the critical point:

w = exp10.(-1:0.001:2)
-nyquistplot(P*C, w, Ms_circles=[Ms], xlims=(-1.2, 0.5), ylims=(-2, 0.3))
Example block output

$M_S$ is always $≥ 1$, but we typically want to keep it below 1.3-2 for robustness reasons. For SISO systems, $M_S$ is linked to the classical gain and phase margins through the following inequalities:

\[\begin{aligned} +nyquistplot(P*C, w, Ms_circles=[Ms], xlims=(-1.2, 0.5), ylims=(-2, 0.3))Example block output

$M_S$ is always $≥ 1$, but we typically want to keep it below 1.3-2 for robustness reasons. For SISO systems, $M_S$ is linked to the classical gain and phase margins through the following inequalities:

\[\begin{aligned} \phi_m &≥ 2 \sin^{-1}\left(\dfrac{1}{2M_S}\right) \text{rad}\\ g_m &≥ \dfrac{M_S}{M_S-1} -\end{aligned}\]

We can also obtain individual sensitivity function using the low-level function feedback directly, or using one of the higher-level functions

Further reading

A modern robustness measure is the diskmargin, that analyses the robustness of a SISO or MIMO system to simultaneous gain and phase variations.

In the presence of structured uncertainty, such as parameter uncertainty or other explicitly modeled uncertainty, the structured singular value (often referred to as $\mu$), provides a way to analyze robustness with respect to the modeled uncertainty. See the RobustAndOptimalControl.jl package for more details.

Basic usage of robustness analysis with JuliaControl are demonstrated in the two videos below:

and

+\end{aligned}\]

We can also obtain individual sensitivity function using the low-level function feedback directly, or using one of the higher-level functions

Further reading

A modern robustness measure is the diskmargin, that analyses the robustness of a SISO or MIMO system to simultaneous gain and phase variations.

In the presence of structured uncertainty, such as parameter uncertainty or other explicitly modeled uncertainty, the structured singular value (often referred to as $\mu$), provides a way to analyze robustness with respect to the modeled uncertainty. See the RobustAndOptimalControl.jl package for more details.

Basic usage of robustness analysis with JuliaControl are demonstrated in the two videos below:

and

diff --git a/dev/examples/automatic_differentiation/88a2ed4e.svg b/dev/examples/automatic_differentiation/14ee422b.svg similarity index 86% rename from dev/examples/automatic_differentiation/88a2ed4e.svg rename to dev/examples/automatic_differentiation/14ee422b.svg index 961c3e7d4..0946321f0 100644 --- a/dev/examples/automatic_differentiation/88a2ed4e.svg +++ b/dev/examples/automatic_differentiation/14ee422b.svg @@ -1,95 +1,95 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/c2e25a29.svg b/dev/examples/automatic_differentiation/1a90a23e.svg similarity index 97% rename from dev/examples/automatic_differentiation/c2e25a29.svg rename to dev/examples/automatic_differentiation/1a90a23e.svg index 0a85130f6..39068dcad 100644 --- a/dev/examples/automatic_differentiation/c2e25a29.svg +++ b/dev/examples/automatic_differentiation/1a90a23e.svg @@ -1,168 +1,168 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/15ca01f5.svg b/dev/examples/automatic_differentiation/2c2beed3.svg similarity index 88% rename from dev/examples/automatic_differentiation/15ca01f5.svg rename to dev/examples/automatic_differentiation/2c2beed3.svg index d67a838ee..ba4062363 100644 --- a/dev/examples/automatic_differentiation/15ca01f5.svg +++ b/dev/examples/automatic_differentiation/2c2beed3.svg @@ -1,124 +1,124 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/d964aed6.svg b/dev/examples/automatic_differentiation/38eb7c38.svg similarity index 88% rename from dev/examples/automatic_differentiation/d964aed6.svg rename to dev/examples/automatic_differentiation/38eb7c38.svg index d7a4d65bc..2d0b28d21 100644 --- a/dev/examples/automatic_differentiation/d964aed6.svg +++ b/dev/examples/automatic_differentiation/38eb7c38.svg @@ -1,126 +1,126 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/2ad433c5.svg b/dev/examples/automatic_differentiation/797acba2.svg similarity index 88% rename from dev/examples/automatic_differentiation/2ad433c5.svg rename to dev/examples/automatic_differentiation/797acba2.svg index 6340abb42..f4d299a94 100644 --- a/dev/examples/automatic_differentiation/2ad433c5.svg +++ b/dev/examples/automatic_differentiation/797acba2.svg @@ -1,57 +1,57 @@ - + - + - + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/9afbdea6.svg b/dev/examples/automatic_differentiation/9c058cda.svg similarity index 94% rename from dev/examples/automatic_differentiation/9afbdea6.svg rename to dev/examples/automatic_differentiation/9c058cda.svg index 4aa4243ad..7f3a90c97 100644 --- a/dev/examples/automatic_differentiation/9afbdea6.svg +++ b/dev/examples/automatic_differentiation/9c058cda.svg @@ -1,112 +1,112 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/8a390340.svg b/dev/examples/automatic_differentiation/aefde950.svg similarity index 88% rename from dev/examples/automatic_differentiation/8a390340.svg rename to dev/examples/automatic_differentiation/aefde950.svg index 8f646942a..99ebfc8c6 100644 --- a/dev/examples/automatic_differentiation/8a390340.svg +++ b/dev/examples/automatic_differentiation/aefde950.svg @@ -1,122 +1,122 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/index.html b/dev/examples/automatic_differentiation/index.html index b7f73856f..0881679a3 100644 --- a/dev/examples/automatic_differentiation/index.html +++ b/dev/examples/automatic_differentiation/index.html @@ -43,7 +43,7 @@ P = DemoSystems.double_mass_model() -bodeplot(P, title="Bode plot of Double-mass system \$P(s)\$")Example block output
Ω = exp10.(-2:0.03:3)
+bodeplot(P, title="Bode plot of Double-mass system \$P(s)\$")
Example block output
Ω = exp10.(-2:0.03:3)
 kp,ki,kd,Tf =  1, 0.1, 0.1, 0.01 # controller parameters
 
 C  = pid(kp, ki, kd; Tf, form=:parallel, state_space=true) # Construct a PID controller with filter
@@ -57,7 +57,7 @@
 plot!(Ω, mag, title="Sensitivity function", xscale=:log10, yscale=:log10, subplot=2, legend=:bottomright, ylims=(3e-2, Inf))
 Ms, _ = hinfnorm(S)
 hline!([Ms], l=(:black, :dash), subplot=2, lab="\$M_S = \$ $(round(Ms, digits=3))", sp=2)
-nyquistplot!(P*C, Ω, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), size=(1200,400))
Example block output

The initial controller $C$ achieves a maximum peak of the sensitivity function of $M_S = 1.3$ which implies a rather robust tuning, but the step response is sluggish. We will now try to optimize the controller parameters to achieve a better performance.

We start by defining a helper function plot_optimized that will evaluate the performance of the tuned controller. We then define a function systems that constructs the gang-of-four transfer functions (extended_gangoffour) and performs time-domain simulations of the transfer functions $S(s)$ and $P(s)S(s)$, i.e., the transfer functions from reference $r$ to control error $e$, and the transfer function from an input load disturbance $d$ to the control error $e$. By optimizing these step responses with respect to the PID parameters, we will get a controller that achieves good performance. To promote robustness of the closed loop as well as to limit the amplification of measurement noise in the control signal, we penalize the peak of the sensitivity function $S$ as well as the (approximate) frequency-weighted $H_2$ norm of the transfer function $CS(s)$.

The constraint function constraints enforces the peak of the sensitivity function to be below Msc. Finally, we use Optimization.jl to optimize the cost function and tell it to use ForwardDiff.jl to compute the gradient of the cost function. The optimizer we use in this example is Ipopt.

using Optimization, Statistics, LinearAlgebra
+nyquistplot!(P*C, Ω, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), size=(1200,400))
Example block output

The initial controller $C$ achieves a maximum peak of the sensitivity function of $M_S = 1.3$ which implies a rather robust tuning, but the step response is sluggish. We will now try to optimize the controller parameters to achieve a better performance.

We start by defining a helper function plot_optimized that will evaluate the performance of the tuned controller. We then define a function systems that constructs the gang-of-four transfer functions (extended_gangoffour) and performs time-domain simulations of the transfer functions $S(s)$ and $P(s)S(s)$, i.e., the transfer functions from reference $r$ to control error $e$, and the transfer function from an input load disturbance $d$ to the control error $e$. By optimizing these step responses with respect to the PID parameters, we will get a controller that achieves good performance. To promote robustness of the closed loop as well as to limit the amplification of measurement noise in the control signal, we penalize the peak of the sensitivity function $S$ as well as the (approximate) frequency-weighted $H_2$ norm of the transfer function $CS(s)$.

The constraint function constraints enforces the peak of the sensitivity function to be below Msc. Finally, we use Optimization.jl to optimize the cost function and tell it to use ForwardDiff.jl to compute the gradient of the cost function. The optimizer we use in this example is Ipopt.

using Optimization, Statistics, LinearAlgebra
 using Ipopt, OptimizationMOI; MOI = OptimizationMOI.MOI
 
 function plot_optimized(P, params, res, systems)
@@ -137,7 +137,7 @@
 )
 
 res = solve(prob, solver)
-plot_optimized(P, params, res.u, systemspid)
Example block output

The optimized controller achieves more or less the same low peak in the sensitivity function, but does this while both making the step responses significantly faster and using much less controller gain for large frequencies (the orange sensitivity function), an altogether better tuning. The only potentially negative effect of this tuning is that the overshoot in response to a reference step increased slightly, indicated also by the slightly higher peak in the complimentary sensitivity function (green). However, the response to reference steps can (and most often should) be additionally shaped by reference pre-filtering (sometimes referred to as "feedforward" or "reference shaping"), by introducing an additional filter appearing in the feedforward path only, thus allowing elimination of the overshoot without affecting the closed-loop properties.

Optimization-based tuning–LQG controller

We could attempt a similar automatic tuning of an LQG controller. This time, we choose to optimize the weight matrices of the LQR problem and the state covariance matrix of the noise. The synthesis of an LQR controller involves the solution of a Ricatti equation, which in turn involves performing a Schur decomposition. These steps hard hard to differentiate through in a conventional way, but we can make use of implicit differentiation using the implicit function theorem. To do so, we load the package ImplicitDifferentiation, and define the conditions that hold at the solution of the Ricatti equation:

\[A^TX + XA - XBR^{-1}B^T X + Q = 0\]

When ImplicitDifferentiation is loaded, differentiable versions of lqr and kalman that make use of the "implicit function" are automatically loaded.

using ImplicitDifferentiation, ComponentArrays # Both these packages are required to load the implicit differentiation rules

Since this is a SISO system, we do not need to tune the control-input matrix or the measurement covariance matrix, any non-unit weight assigned to those can be associated with the state matrices instead. Since these matrices are supposed to be positive semi-definite, we optimize Cholesky factors rather than the full matrices.

function triangular(x)
+plot_optimized(P, params, res.u, systemspid)
Example block output

The optimized controller achieves more or less the same low peak in the sensitivity function, but does this while both making the step responses significantly faster and using much less controller gain for large frequencies (the orange sensitivity function), an altogether better tuning. The only potentially negative effect of this tuning is that the overshoot in response to a reference step increased slightly, indicated also by the slightly higher peak in the complimentary sensitivity function (green). However, the response to reference steps can (and most often should) be additionally shaped by reference pre-filtering (sometimes referred to as "feedforward" or "reference shaping"), by introducing an additional filter appearing in the feedforward path only, thus allowing elimination of the overshoot without affecting the closed-loop properties.

Optimization-based tuning–LQG controller

We could attempt a similar automatic tuning of an LQG controller. This time, we choose to optimize the weight matrices of the LQR problem and the state covariance matrix of the noise. The synthesis of an LQR controller involves the solution of a Ricatti equation, which in turn involves performing a Schur decomposition. These steps hard hard to differentiate through in a conventional way, but we can make use of implicit differentiation using the implicit function theorem. To do so, we load the package ImplicitDifferentiation, and define the conditions that hold at the solution of the Ricatti equation:

\[A^TX + XA - XBR^{-1}B^T X + Q = 0\]

When ImplicitDifferentiation is loaded, differentiable versions of lqr and kalman that make use of the "implicit function" are automatically loaded.

using ImplicitDifferentiation, ComponentArrays # Both these packages are required to load the implicit differentiation rules

Since this is a SISO system, we do not need to tune the control-input matrix or the measurement covariance matrix, any non-unit weight assigned to those can be associated with the state matrices instead. Since these matrices are supposed to be positive semi-definite, we optimize Cholesky factors rather than the full matrices.

function triangular(x)
     m = length(x)
     n = round(Int, sqrt(2m-1))
     T = zeros(eltype(x), n, n)
@@ -175,7 +175,7 @@
 )
 
 res2 = solve(prob2, solver)
-plot_optimized(P, params2, res2.u, systemslqr)
Example block output

This controller should perform better than the PID controller, which is known to be incapable of properly damping the resonance in a double-mass system. However, we did not include any integral action in the LQG controller, which has implication for the disturbance response, as indicated by the steady-state error in the green step response in the simulation above.

Robustness analysis

To check the robustness of the designed LQG controller w.r.t. parametric uncertainty in the plant, we load the package MonteCarloMeasurements and recreate the plant model with 20% uncertainty in the spring coefficient.

using MonteCarloMeasurements
+plot_optimized(P, params2, res2.u, systemslqr)
Example block output

This controller should perform better than the PID controller, which is known to be incapable of properly damping the resonance in a double-mass system. However, we did not include any integral action in the LQG controller, which has implication for the disturbance response, as indicated by the steady-state error in the green step response in the simulation above.

Robustness analysis

To check the robustness of the designed LQG controller w.r.t. parametric uncertainty in the plant, we load the package MonteCarloMeasurements and recreate the plant model with 20% uncertainty in the spring coefficient.

using MonteCarloMeasurements
 Pu = DemoSystems.double_mass_model(k = Particles(32, Uniform(80, 120))) # Create a model with uncertainty in spring stiffness k ~ U(80, 120)
 unsafe_comparisons(true) # For the Bode plot to work
 
@@ -183,12 +183,12 @@
 Gu = extended_gangoffour(Pu, C)     # Form the gang-of-four with uncertainty
 w = exp10.(LinRange(-1.5, 2, 500))
 bodeplot(Gu, w, plotphase=false, ri=false, N=32, ylims=(1e-1, 30), layout=1, sp=1, c=[1 2 4 3], lab=["S" "CS" "PS" "T"])
-hline!([Msc], l=:dashdot, c=1, lab="Constraint", ylims=(9e-2, Inf))
Example block output

The uncertainty in the spring stiffness caused an uncertainty in the resonant peak in the sensitivity functions, it's a good thing that we designed a controller that was conservative with a large margin (small $M_S$) so that all the plausible variations of the plant are expected to behave reasonably well:

Gd   = c2d(Gu, 0.05)   # Discretize the system
+hline!([Msc], l=:dashdot, c=1, lab="Constraint", ylims=(9e-2, Inf))
Example block output

The uncertainty in the spring stiffness caused an uncertainty in the resonant peak in the sensitivity functions, it's a good thing that we designed a controller that was conservative with a large margin (small $M_S$) so that all the plausible variations of the plant are expected to behave reasonably well:

Gd   = c2d(Gu, 0.05)   # Discretize the system
 r1 = step(Gd[1,1], 0:0.05:15) # Simulate S
 r2 = step(Gd[1,2], 0:0.05:15) # Simulate PS
 plot([r1, r2]; title="Time response",
             lab = [" \$r → e\$" " \$d → e\$"], legend=:bottomright,
-            fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3], ri=false, N=32)
Example block output

Parameterizing the controller using feedback gains

For completeness, lets also parameterize the observer-based state-feedback controller using the gain matrices directly, that is, we search directly over $L$ and $K$. This is typically a harder problem since the search space contains non-stabilizing controllers, and the set of stabilizing gains is non-convex. (For state feedback, a nice theoretical result exists that says that there are no local minima, but the space of stabilizing gains is still non-convex.)

function systems_sf(params::AbstractVector{T}, P) where T
+            fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3], ri=false, N=32)
Example block output

Parameterizing the controller using feedback gains

For completeness, lets also parameterize the observer-based state-feedback controller using the gain matrices directly, that is, we search directly over $L$ and $K$. This is typically a harder problem since the search space contains non-stabilizing controllers, and the set of stabilizing gains is non-convex. (For state feedback, a nice theoretical result exists that says that there are no local minima, but the space of stabilizing gains is still non-convex.)

function systems_sf(params::AbstractVector{T}, P) where T
     n2 = length(params) ÷ 2
     L = params[1:n2]'
     K = params[n2+1:2n2, 1:1]
@@ -207,4 +207,4 @@
     lcons = fill(-Inf, 1),
 )
 res3 = solve(prob3, solver)
-plot_optimized(P, params3, res3.u, systems_sf)
Example block output

Known limitations

The following issues are currently known to exist when using AD through ControlSystems.jl:

ForwardDiff

ForwardDiff.jl works for a lot of workflows without any intervention required from the user. The following known limitations exist:

  • The function c2d with the default :zoh discretization method makes a call to LinearAlgebra.exp!, which is not defined for ForwardDiff.Dual numbers. A forward rule for this function exist in ChainRules, which can be enabled using ForwardDiffChainRules.jl, but this PR must be merged and released before it will work as intended. A workaround is to use the :tustin method instead, or manually defining this method.
  • The function svdvals does not have a forward rule defined. This means that the functions sigma and opnorm will not work for MIMO systems with ForwardDiff. SISO, MISO and SIMO systems will, however, work.
  • hinfnorm requires ImplicitDifferentiation.jl and ComponentArrays.jl to be manually loaded by the user, after which there are implicit differentiation rules defined for hinfnorm. The implicit rule calls opnorm, and is thus affected by the first limitation above for MIMO systems. hinfnorm has a reverse rule defined in RobustAndOptimalControl.jl, which is not affected by this limitation.
  • are, lqr and kalman all require ImplicitDifferentiation.jl and ComponentArrays.jl to be manually loaded by the user, after which there are implicit differentiation rules defined. To invoke the correct method of these functions, it is important that the second matrix (corresponding to input or measurement) has the Dual number type, i.e., the R matrix in lqr(P, Q, R) or lqr(Continuous, A, B, Q, R)
  • The schur factorization has an implicit differentiation rule defined, but the companion function ordschur does not. This is the fundamental reason for requiring ImplicitDifferentiation.jl to differentiate through the Ricatti equation solver. schur is called in several additional places, including balreal and all lyap solvers. Many of these algorithms also call givensAlgorithm which has no rule either.
  • An implicit rule is defined for continuous-time lyap and plyap solvers, but not yet for discrete-time solvers. This means that gram covar and norm ($H_2$-norm) is differentiable for continuous-time systems but not for discrete.

Reverse-mode AD

  • Zygote does not work very well at all, due to
    • Frequent use of mutation for performance
    • Try/catch blocks
+plot_optimized(P, params3, res3.u, systems_sf)Example block output

Known limitations

The following issues are currently known to exist when using AD through ControlSystems.jl:

ForwardDiff

ForwardDiff.jl works for a lot of workflows without any intervention required from the user. The following known limitations exist:

  • The function c2d with the default :zoh discretization method makes a call to LinearAlgebra.exp!, which is not defined for ForwardDiff.Dual numbers. A forward rule for this function exist in ChainRules, which can be enabled using ForwardDiffChainRules.jl, but this PR must be merged and released before it will work as intended. A workaround is to use the :tustin method instead, or manually defining this method.
  • The function svdvals does not have a forward rule defined. This means that the functions sigma and opnorm will not work for MIMO systems with ForwardDiff. SISO, MISO and SIMO systems will, however, work.
  • hinfnorm requires ImplicitDifferentiation.jl and ComponentArrays.jl to be manually loaded by the user, after which there are implicit differentiation rules defined for hinfnorm. The implicit rule calls opnorm, and is thus affected by the first limitation above for MIMO systems. hinfnorm has a reverse rule defined in RobustAndOptimalControl.jl, which is not affected by this limitation.
  • are, lqr and kalman all require ImplicitDifferentiation.jl and ComponentArrays.jl to be manually loaded by the user, after which there are implicit differentiation rules defined. To invoke the correct method of these functions, it is important that the second matrix (corresponding to input or measurement) has the Dual number type, i.e., the R matrix in lqr(P, Q, R) or lqr(Continuous, A, B, Q, R)
  • The schur factorization has an implicit differentiation rule defined, but the companion function ordschur does not. This is the fundamental reason for requiring ImplicitDifferentiation.jl to differentiate through the Ricatti equation solver. schur is called in several additional places, including balreal and all lyap solvers. Many of these algorithms also call givensAlgorithm which has no rule either.
  • An implicit rule is defined for continuous-time lyap and plyap solvers, but not yet for discrete-time solvers. This means that gram covar and norm ($H_2$-norm) is differentiable for continuous-time systems but not for discrete.

Reverse-mode AD

  • Zygote does not work very well at all, due to
    • Frequent use of mutation for performance
    • Try/catch blocks
diff --git a/dev/examples/delay_systems/b3c925ed.svg b/dev/examples/delay_systems/6c4eb62d.svg similarity index 98% rename from dev/examples/delay_systems/b3c925ed.svg rename to dev/examples/delay_systems/6c4eb62d.svg index c7272647f..12431e648 100644 --- a/dev/examples/delay_systems/b3c925ed.svg +++ b/dev/examples/delay_systems/6c4eb62d.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/302b5c81.svg b/dev/examples/delay_systems/79b5bc60.svg similarity index 90% rename from dev/examples/delay_systems/302b5c81.svg rename to dev/examples/delay_systems/79b5bc60.svg index 2b205055a..57f6db40a 100644 --- a/dev/examples/delay_systems/302b5c81.svg +++ b/dev/examples/delay_systems/79b5bc60.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/f8ff3df5.svg b/dev/examples/delay_systems/a84d2640.svg similarity index 92% rename from dev/examples/delay_systems/f8ff3df5.svg rename to dev/examples/delay_systems/a84d2640.svg index 27f46365e..09331fe52 100644 --- a/dev/examples/delay_systems/f8ff3df5.svg +++ b/dev/examples/delay_systems/a84d2640.svg @@ -1,71 +1,71 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/index.html b/dev/examples/delay_systems/index.html index 0eb6d6adb..1e0d584a3 100644 --- a/dev/examples/delay_systems/index.html +++ b/dev/examples/delay_systems/index.html @@ -2,6 +2,6 @@ Properties of delay systems · ControlSystems.jl

Properties of delay systems

Delay systems can sometimes have non-intuitive properties, in particular when the delays appear inside of the system, i.e., not directly on the inputs or outputs.

The Nyquist plot of delay systems usually spirals towards the origin for delay systems. This is due to the phase loss at high frequencies due to the delay:

using ControlSystemsBase, Plots
 w = exp10.(LinRange(-2, 2, 2000))
 P = tf(1, [1, 1]) * delay(2) # Plant with delay on the input
-nyquistplot(P, w)
Example block output

When forming a feedback interconnection, making the delay appear in the closed loop, we may get gain ripple:

bodeplot(feedback(P), w)
Example block output

If the system with delay has a direct feedthrough term, step responses may show repeated steps at integer multiples of the delay:

using ControlSystems # Load full control systems to get simulation functionality
+nyquistplot(P, w)
Example block output

When forming a feedback interconnection, making the delay appear in the closed loop, we may get gain ripple:

bodeplot(feedback(P), w)
Example block output

If the system with delay has a direct feedthrough term, step responses may show repeated steps at integer multiples of the delay:

using ControlSystems # Load full control systems to get simulation functionality
 P = tf([1, 1], [1, 0])*delay(1)
-plot(step(feedback(P, 0.5), 0:0.001:20))
Example block output

Indeed, if the system has a non-zero feedthrough, the output will contain a delayed step attenuated by the feedthrough term, in this case

ss(feedback(tf([1, 1], [1, 0]))).D[]
0.5

the steps will thus in this case decay exponentially with decay rate 0.5.

For a more advanced example using time delays, see the Smith predictor tutorial.

Simulation of time-delay systems

Time-delay systems are numerically challenging to simulate, if you run into problems, please open an issue with a reproducing example. The lsim, step and impulse functions accept keyword arguments that are passed along to the ODE integrator, this can be used to both select integration method and to tweak the integrator options. The documentation for solving delay-differential equations is available here and here.

Estimation of delay

See the companion tutorial in ControlSystemIdentification.jl on Delay estimation. This tutorial covers the both the detection of the presence of a delay, and estimation of models for systems with delays.

Approximation and discretization of delays

Delay systems may be approximated as rational functions by means of Padé approximation using the function pade. Pure continuous-time delays can also be discretized using the function thiran. Continuous-time models with internal delays can be discretized using c2d, provided that the delay is an integer multiple of the sampling time (fractional delays are not yet supported by c2d).

+plot(step(feedback(P, 0.5), 0:0.001:20))Example block output

Indeed, if the system has a non-zero feedthrough, the output will contain a delayed step attenuated by the feedthrough term, in this case

ss(feedback(tf([1, 1], [1, 0]))).D[]
0.5

the steps will thus in this case decay exponentially with decay rate 0.5.

For a more advanced example using time delays, see the Smith predictor tutorial.

Simulation of time-delay systems

Time-delay systems are numerically challenging to simulate, if you run into problems, please open an issue with a reproducing example. The lsim, step and impulse functions accept keyword arguments that are passed along to the ODE integrator, this can be used to both select integration method and to tweak the integrator options. The documentation for solving delay-differential equations is available here and here.

Estimation of delay

See the companion tutorial in ControlSystemIdentification.jl on Delay estimation. This tutorial covers the both the detection of the presence of a delay, and estimation of models for systems with delays.

Approximation and discretization of delays

Delay systems may be approximated as rational functions by means of Padé approximation using the function pade. Pure continuous-time delays can also be discretized using the function thiran. Continuous-time models with internal delays can be discretized using c2d, provided that the delay is an integer multiple of the sampling time (fractional delays are not yet supported by c2d).

diff --git a/dev/examples/example/2bf8778b.svg b/dev/examples/example/286f40c8.svg similarity index 84% rename from dev/examples/example/2bf8778b.svg rename to dev/examples/example/286f40c8.svg index a0643d46d..516b1ae00 100644 --- a/dev/examples/example/2bf8778b.svg +++ b/dev/examples/example/286f40c8.svg @@ -1,128 +1,128 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/6852497e.svg b/dev/examples/example/2acf81ad.svg similarity index 87% rename from dev/examples/example/6852497e.svg rename to dev/examples/example/2acf81ad.svg index 11c4698b2..076985ec2 100644 --- a/dev/examples/example/6852497e.svg +++ b/dev/examples/example/2acf81ad.svg @@ -1,151 +1,151 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/4f8a88b2.svg b/dev/examples/example/8d34836a.svg similarity index 89% rename from dev/examples/example/4f8a88b2.svg rename to dev/examples/example/8d34836a.svg index 24a5ab36d..ac0531821 100644 --- a/dev/examples/example/4f8a88b2.svg +++ b/dev/examples/example/8d34836a.svg @@ -1,185 +1,185 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/e949c779.svg b/dev/examples/example/bcd6a8ec.svg similarity index 87% rename from dev/examples/example/e949c779.svg rename to dev/examples/example/bcd6a8ec.svg index fc6b9b858..9817300d7 100644 --- a/dev/examples/example/e949c779.svg +++ b/dev/examples/example/bcd6a8ec.svg @@ -1,175 +1,175 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/977aedf8.svg b/dev/examples/example/eb2bad9a.svg similarity index 89% rename from dev/examples/example/977aedf8.svg rename to dev/examples/example/eb2bad9a.svg index 21c3d7377..126e0110a 100644 --- a/dev/examples/example/977aedf8.svg +++ b/dev/examples/example/eb2bad9a.svg @@ -1,185 +1,185 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/index.html b/dev/examples/example/index.html index 02a560fa3..41c6c15f8 100644 --- a/dev/examples/example/index.html +++ b/dev/examples/example/index.html @@ -24,21 +24,21 @@ save_docs_plot("lqrplot.svg"); # hide

To design an LQG controller (LQR with a Kalman filter), see the functions

See also the following tutorial video on LQR and LQG design

PID design functions

A basic PID controller can be constructed using the constructors pid, pid_2dof. In ControlSystems.jl, we often refer to three different formulations of the PID controller, which are defined as

  • Standard form: $K_p(1 + \frac{1}{T_i s} + T_ds)$
  • Series form: $K_c(1 + \frac{1}{τ_i s})(τ_d s + 1)$
  • Parallel form: $K_p + \frac{K_i}{s} + K_d s$

Most functions that construct PID controllers allow the user to select which form to use.

A tutorial on PID design is available here:

The following examples show basic workflows for designing PI/PID controllers.

PI loop shaping example

By plotting the gang of four under unit feedback for the process

\[P(s) = \dfrac{1}{(s + 1)^4}\]

using ControlSystemsBase, Plots
 P = tf(1, [1,1])^4
-gangoffourplot(P, tf(1))
Example block output

we notice that the sensitivity function is a bit too high around frequencies ω = 0.8 rad/s. Since we want to control the process using a simple PI-controller, we utilize the function loopshapingPI and tell it that we want 60 degrees phase margin at this frequency. The resulting gang of four is plotted for both the constructed controller and for unit feedback.

using ControlSystemsBase, Plots
+gangoffourplot(P, tf(1))
Example block output

we notice that the sensitivity function is a bit too high around frequencies ω = 0.8 rad/s. Since we want to control the process using a simple PI-controller, we utilize the function loopshapingPI and tell it that we want 60 degrees phase margin at this frequency. The resulting gang of four is plotted for both the constructed controller and for unit feedback.

using ControlSystemsBase, Plots
 P = tf(1, [1,1])^4
 ωp = 0.8
 C,kp,ki,fig = loopshapingPI(P,ωp,phasemargin=60,form=:parallel, doplot=true)
-fig
Example block output

We could also consider a situation where we want to create a closed-loop system with the bandwidth ω = 2 rad/s, in which case we would write something like

ωp = 2
+fig
Example block output

We could also consider a situation where we want to create a closed-loop system with the bandwidth ω = 2 rad/s, in which case we would write something like

ωp = 2
 C60,kp,ki,fig = loopshapingPI(P,ωp,rl=1,phasemargin=60,form=:standard,doplot=true)
-fig
Example block output

Here we specify that we want the Nyquist curve L(iω) = P(iω)C(iω) to pass the point |L(iω)| = rl = 1, arg(L(iω)) = -180 + phasemargin = -180 + 60 The gang of four tells us that we can indeed get a very robust and fast controller with this design method, but it will cost us significant control action to double the bandwidth of all four poles.

PID loop shaping

Processes with inertia, like double integrators, require a derivative term in the controller for good results. The function loopshapingPID allows you to specify a point in the Nyquist plane where the loop-transfer function $L(s) = P(s)C(s)$ should be tangent to the circle that denotes $|T| = |\dfrac{PC}{1 + PC}| = M_t$ The tangent point is specified by specifying $M_t$ and the angle $\phi_t$ between the real axis and the tangent point, indicated in the Nyquist plot below.

using ControlSystemsBase, Plots
+fig
Example block output

Here we specify that we want the Nyquist curve L(iω) = P(iω)C(iω) to pass the point |L(iω)| = rl = 1, arg(L(iω)) = -180 + phasemargin = -180 + 60 The gang of four tells us that we can indeed get a very robust and fast controller with this design method, but it will cost us significant control action to double the bandwidth of all four poles.

PID loop shaping

Processes with inertia, like double integrators, require a derivative term in the controller for good results. The function loopshapingPID allows you to specify a point in the Nyquist plane where the loop-transfer function $L(s) = P(s)C(s)$ should be tangent to the circle that denotes $|T| = |\dfrac{PC}{1 + PC}| = M_t$ The tangent point is specified by specifying $M_t$ and the angle $\phi_t$ between the real axis and the tangent point, indicated in the Nyquist plot below.

using ControlSystemsBase, Plots
 P  = tf(1, [1,0,0]) # A double integrator
 Mt = 1.3            # Maximum magnitude of complementary sensitivity
 ϕt = 75             # Angle of tangent point
 ω  = 1              # Frequency at which the specification holds
 C, kp, ki, kd, fig = loopshapingPID(P, ω; Mt, ϕt, doplot=true)
-fig
Example block output

To get good robustness, we typically aim for a $M_t$ less than 1.5. In general, the smaller $M_t$ we require, the larger the controller gain will be.

Since we are designing a PID controller, we expect a large controller gain for high frequencies. This is generally undesirable for both robustness and noise reasons, and is commonly solved by introducing a lowpass filter in series with the controller. The example below passes the keyword argument Tf=1/20ω to indicate that we want to add a second-order lowpass filter with a cutoff frequency 20 times faster than the design frequency.

Tf = 1/20ω
+fig
Example block output

To get good robustness, we typically aim for a $M_t$ less than 1.5. In general, the smaller $M_t$ we require, the larger the controller gain will be.

Since we are designing a PID controller, we expect a large controller gain for high frequencies. This is generally undesirable for both robustness and noise reasons, and is commonly solved by introducing a lowpass filter in series with the controller. The example below passes the keyword argument Tf=1/20ω to indicate that we want to add a second-order lowpass filter with a cutoff frequency 20 times faster than the design frequency.

Tf = 1/20ω
 C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, Tf)
-fig
Example block output

As we can see, the addition of the filter increases the high-frequency roll-off in both $T$ and $CS$, which is typically desirable.

To get better control over the filter, it can be pre-designed and supplied to loopshapingPID with the keyword argument F:

F = tf(1, [Tf^2, 2*Tf/sqrt(2), 1])
+fig
Example block output

As we can see, the addition of the filter increases the high-frequency roll-off in both $T$ and $CS$, which is typically desirable.

To get better control over the filter, it can be pre-designed and supplied to loopshapingPID with the keyword argument F:

F = tf(1, [Tf^2, 2*Tf/sqrt(2), 1])
 C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, F)

Advanced pole-zero placement

A video tutorial on pole placement is available here:

The following example illustrates how we can perform advanced pole-zero placement using the function rstc (rstd in discrete time). The task is to make the process $P$ a bit faster and damp the poorly damped poles.

Define the process

ζ = 0.2
 ω = 1
 
@@ -105,4 +105,4 @@
 pidplots(P,:nyquist,;params_p=kp,params_i=ki,ylims=(-1,1),xlims=(-1.5,1.5), form=:parallel)
 save_docs_plot("pidplotsnyquist2.svg") # hide
 pidplots(P,:gof,;params_p=kp,params_i=ki,legend=false,ylims=(0.08,8),xlims=(0.003,20), form=:parallel, legendfontsize=6, size=(1000, 1000))
-save_docs_plot("pidplotsgof2.svg"); # hide

Further examples

+save_docs_plot("pidplotsgof2.svg"); # hide

Further examples

diff --git a/dev/examples/ilc/ba40b17d.svg b/dev/examples/ilc/83c2e34d.svg similarity index 92% rename from dev/examples/ilc/ba40b17d.svg rename to dev/examples/ilc/83c2e34d.svg index 1632a044b..fe072fb78 100644 --- a/dev/examples/ilc/ba40b17d.svg +++ b/dev/examples/ilc/83c2e34d.svg @@ -1,173 +1,173 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/b8224975.svg b/dev/examples/ilc/e5945b68.svg similarity index 91% rename from dev/examples/ilc/b8224975.svg rename to dev/examples/ilc/e5945b68.svg index 337a5429a..b00702cc1 100644 --- a/dev/examples/ilc/b8224975.svg +++ b/dev/examples/ilc/e5945b68.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/723dbae9.svg b/dev/examples/ilc/e8019939.svg similarity index 89% rename from dev/examples/ilc/723dbae9.svg rename to dev/examples/ilc/e8019939.svg index 5e3ec78dd..bbf612791 100644 --- a/dev/examples/ilc/723dbae9.svg +++ b/dev/examples/ilc/e8019939.svg @@ -1,36 +1,36 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/65980d20.svg b/dev/examples/ilc/ea20713e.svg similarity index 89% rename from dev/examples/ilc/65980d20.svg rename to dev/examples/ilc/ea20713e.svg index ce61e4f77..5edb1c3af 100644 --- a/dev/examples/ilc/65980d20.svg +++ b/dev/examples/ilc/ea20713e.svg @@ -1,44 +1,44 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/9fa116ea.svg b/dev/examples/ilc/eff99d4b.svg similarity index 90% rename from dev/examples/ilc/9fa116ea.svg rename to dev/examples/ilc/eff99d4b.svg index 19d13bd41..4c7467a11 100644 --- a/dev/examples/ilc/9fa116ea.svg +++ b/dev/examples/ilc/eff99d4b.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/599d5ffe.svg b/dev/examples/ilc/fbd08396.svg similarity index 92% rename from dev/examples/ilc/599d5ffe.svg rename to dev/examples/ilc/fbd08396.svg index 8bed07e88..31d8a7b5e 100644 --- a/dev/examples/ilc/599d5ffe.svg +++ b/dev/examples/ilc/fbd08396.svg @@ -1,177 +1,177 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/ilc/index.html b/dev/examples/ilc/index.html index 100025cc0..42c8aa800 100644 --- a/dev/examples/ilc/index.html +++ b/dev/examples/ilc/index.html @@ -28,12 +28,12 @@ G = double_mass_model(Jl = 1) Gact = double_mass_model(Jl = 1.5) # 50% more load than modeled -bodeplot([G, Gact], lab=["G model" "G actual"], plotphase=false)Example block output

We will design a PID controller with a filter for the system, the controller is poorly tuned and not very good at tracking fast reference steps, in practice, one would likely design a feedforward controller as well to improve upon this, but for now we'll stick with the simple feedback controller.

C  = pid(10, 1, 1, form = :series) * tf(1, [0.02, 1])
+bodeplot([G, Gact], lab=["G model" "G actual"], plotphase=false)
Example block output

We will design a PID controller with a filter for the system, the controller is poorly tuned and not very good at tracking fast reference steps, in practice, one would likely design a feedforward controller as well to improve upon this, but for now we'll stick with the simple feedback controller.

C  = pid(10, 1, 1, form = :series) * tf(1, [0.02, 1])
 Ts = 0.02 # Sample time
 Gc = c2d(feedback(G*C), Ts)       |> tf
 Gcact = c2d(feedback(Gact*C), Ts) |> tf
 plot(step(Gc, 10), title="Closed-loop step response", lab="model")
-plot!(step(Gcact, 10), lab="actual")
Example block output

Reference trajectory

Next up we design a reference trajectory and simulate the actual closed-loop dynamics.

T = 3pi    # Duration
+plot!(step(Gcact, 10), lab="actual")
Example block output

Reference trajectory

Next up we design a reference trajectory and simulate the actual closed-loop dynamics.

T = 3pi    # Duration
 t = 0:Ts:T # Time vector
 function funnysin(x)
     x = sin(x)
@@ -43,7 +43,7 @@
 r = funnysin.(t)' |> Array # Reference signal
 
 res = lsim(Gcact, r, t)
-plot(res, plotu=true, layout=1, sp=1, title="Closed-loop simulation with actual dynamics", lab=["y" "r"])
Example block output

Performance is poor.. Enter ILC!

Non-causal filtering

For ILC to work well, we define two helper functions. One that applies a zero-phase filter by filtering both forwards and backwards (filtfilt). This is possible since ILC operates on signals offline, between iterations in the ILC scheme. We also define a special lsim that handles non-causal systems to allow "lookahead" into the future. This typically improves the performance of ILC by quite a lot, and is once again possible since ILC operates on prerecorded signals.

function lsim_zerophase(G, u, args...; kwargs...)
+plot(res, plotu=true, layout=1, sp=1, title="Closed-loop simulation with actual dynamics", lab=["y" "r"])
Example block output

Performance is poor.. Enter ILC!

Non-causal filtering

For ILC to work well, we define two helper functions. One that applies a zero-phase filter by filtering both forwards and backwards (filtfilt). This is possible since ILC operates on signals offline, between iterations in the ILC scheme. We also define a special lsim that handles non-causal systems to allow "lookahead" into the future. This typically improves the performance of ILC by quite a lot, and is once again possible since ILC operates on prerecorded signals.

function lsim_zerophase(G, u, args...; kwargs...)
     res = lsim(G, u[:, end:-1:1], args...; kwargs...)
     lsim(G, res.y[:, end:-1:1], args...; kwargs...).y
 end
@@ -63,7 +63,7 @@
 Q = c2d(tf(1, [0.05, 1]), Ts)
 # L = 0.9z^1 # A more conservative and heuristic choice
 L = 0.5inv(Gc) # Make the scaling factor smaller to take smaller steps

A theorem due to Norrlöf says that for the ILC iterations to converge, one needs to satisfy $| 1 - LG | < |Q^{-1}|$ which we can verify by looking at the Bode curves of the two sides of the inequality

bodeplot([inv(Q), (1 - L*Gc)], plotphase=false, lab=["Stability boundary \$Q^{-1}\$" "\$1 - LG\$"])
-bodeplot!((1 - L*Gcact), plotphase=false, lab="\$1 - LG\$ actual")
Example block output

Above, we plotted this curve also for the actual dynamics. This is of course not possible in a real scenario where this is unknown, but one could plot it for multiple plausible models and verify that they are all below the boundary. See Uncertainty modeling using RobustAndOptimalControl.jl for guidance on this. Looking at the stability condition, it becomes obvious how making $Q$ small where the model is uncertain is beneficial for robustness of the ILC scheme.

ILC iteration

The next step is to implement the ILC scheme and run it:

function ilc(Gc, Q, L)
+bodeplot!((1 - L*Gcact), plotphase=false, lab="\$1 - LG\$ actual")
Example block output

Above, we plotted this curve also for the actual dynamics. This is of course not possible in a real scenario where this is unknown, but one could plot it for multiple plausible models and verify that they are all below the boundary. See Uncertainty modeling using RobustAndOptimalControl.jl for guidance on this. Looking at the stability condition, it becomes obvious how making $Q$ small where the model is uncertain is beneficial for robustness of the ILC scheme.

ILC iteration

The next step is to implement the ILC scheme and run it:

function ilc(Gc, Q, L)
     a = zero(r) # ILC adjustment signal starts at 0
     fig1 = plot(t, vec(r), sp=1, layout=(3,1), l=(:black, 3), lab="Ref")
     fig2 = plot(title="Sum of squared error", xlabel="Iteration", legend=false, titlefontsize=10, framestyle=:zerolines, ylims=(0, 7.1))
@@ -82,4 +82,4 @@
     end
     plot(fig1, fig2, layout=@layout([a{0.7w} b{0.3w}]))
 end
-ilc(Gc, Q, L)
Example block output

When running on the model, the result looks very good. We see that the tracking error in the last plot decreases rapidly and is much smaller after only a couple of iterations. We also note that the adjusted reference $r+a$ has effectively been phase-advanced slightly to compensate for the lag in the system dynamics. This is an effect of the acausal filtering due to $L = G_C^{-1}$.

How does it work on the "actual" dynamics?

ilc(Gcact, Q, L)
Example block output

The result is subtly worse, but considering the rather big model error the result is still quite good.

Summary

We have seen how ILC can be used to improve tracking performance in a scenario where a repetitive task is to be executed several times. In simulation like here, ILC can be seen as an optimal-control strategy to come up with a optimal reference trajectory to minimize the control error, while if implemented on a physical process, the scheme amounts to a simple but effective reinforcement-learning or adaptive-control approach. ILC often works very well in practice and has been used in robotics and machining among other areas.

ILC does not work very well if stochastic disturbances dictate the control performance or a task is to be performed only a small number of times. In, e.g., machining applications, each ILC iteration may imply performing destructive machining on expensive material with suboptimal result before convergence. This may only be cost effective if the task is to be performed many times after an initial "tuning" by means of ILC.

  • nonlinearInverse models can be formed also for some nonlinear systems. ModelingToolkit.jl is particularily well suited for inverting models due to its acausal nature.
+ilc(Gc, Q, L)Example block output

When running on the model, the result looks very good. We see that the tracking error in the last plot decreases rapidly and is much smaller after only a couple of iterations. We also note that the adjusted reference $r+a$ has effectively been phase-advanced slightly to compensate for the lag in the system dynamics. This is an effect of the acausal filtering due to $L = G_C^{-1}$.

How does it work on the "actual" dynamics?

ilc(Gcact, Q, L)
Example block output

The result is subtly worse, but considering the rather big model error the result is still quite good.

Summary

We have seen how ILC can be used to improve tracking performance in a scenario where a repetitive task is to be executed several times. In simulation like here, ILC can be seen as an optimal-control strategy to come up with a optimal reference trajectory to minimize the control error, while if implemented on a physical process, the scheme amounts to a simple but effective reinforcement-learning or adaptive-control approach. ILC often works very well in practice and has been used in robotics and machining among other areas.

ILC does not work very well if stochastic disturbances dictate the control performance or a task is to be performed only a small number of times. In, e.g., machining applications, each ILC iteration may imply performing destructive machining on expensive material with suboptimal result before convergence. This may only be cost effective if the task is to be performed many times after an initial "tuning" by means of ILC.

  • nonlinearInverse models can be formed also for some nonlinear systems. ModelingToolkit.jl is particularily well suited for inverting models due to its acausal nature.
diff --git a/dev/examples/smith_predictor/9c18923c.svg b/dev/examples/smith_predictor/8372b96b.svg similarity index 99% rename from dev/examples/smith_predictor/9c18923c.svg rename to dev/examples/smith_predictor/8372b96b.svg index 5d3bcb74f..53694d38e 100644 --- a/dev/examples/smith_predictor/9c18923c.svg +++ b/dev/examples/smith_predictor/8372b96b.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/57e907fc.svg b/dev/examples/smith_predictor/94575276.svg similarity index 90% rename from dev/examples/smith_predictor/57e907fc.svg rename to dev/examples/smith_predictor/94575276.svg index 0e5dd33b2..ef27ae305 100644 --- a/dev/examples/smith_predictor/57e907fc.svg +++ b/dev/examples/smith_predictor/94575276.svg @@ -1,70 +1,70 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/65138677.svg b/dev/examples/smith_predictor/eac9ddbe.svg similarity index 86% rename from dev/examples/smith_predictor/65138677.svg rename to dev/examples/smith_predictor/eac9ddbe.svg index 2ba3070c0..eb6562720 100644 --- a/dev/examples/smith_predictor/65138677.svg +++ b/dev/examples/smith_predictor/eac9ddbe.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/index.html b/dev/examples/smith_predictor/index.html index fff4be357..20b617be8 100644 --- a/dev/examples/smith_predictor/index.html +++ b/dev/examples/smith_predictor/index.html @@ -51,7 +51,7 @@ Delays: [8.0]

We now plot the closed loop responses. The transfer function from $r$ to $y$ is given by $PC_r/(1+PC_r)$ = feedback(P*C,1), and from a load disturbance entering at $u$ the transfer function is $P/(1+PC_r)$ = feedback(P, C)

using ControlSystems # Load full ControlSystems for delay-system simulation
 G = [feedback(P*C, 1) feedback(P, C)] # Reference step at t = 0 and load disturbance step at t = 15
-fig_timeresp = plot(lsim(G, (_,t) -> [1; t >= 15], 0:0.1:40),  title="τ = $τ")
Example block output

Plot the frequency response of the predictor part and compare to a negative delay, which would be an ideal controller that can (typically) not be realized in practice (a negative delay implies foresight).

C_pred = feedback(1, C0*(ss(1.0) - delay(τ))*P0)
+fig_timeresp = plot(lsim(G, (_,t) -> [1; t >= 15], 0:0.1:40),  title="τ = $τ")
Example block output

Plot the frequency response of the predictor part and compare to a negative delay, which would be an ideal controller that can (typically) not be realized in practice (a negative delay implies foresight).

C_pred = feedback(1, C0*(ss(1.0) - delay(τ))*P0)
 fig_bode = bodeplot([C_pred, delay(-τ)], exp10.(-1:0.002:0.4), ls=[:solid :solid :dash :dash], title="", lab=["Smith predictor" "" "Ideal predictor" ""])
 plot!(yticks=[0.1, 1, 10], sp=1)
-plot!(yticks=0:180:1080, sp=2)
Example block output

Check the Nyquist plot. Note that the Nyquist curve encircles -1 for τ > 2.99

fig_nyquist = nyquistplot(C * P, exp10.(-1:1e-4:2), title="τ = $τ")
Example block output

A video tutorial on delay systems is available here:

Additional design methods for delay systems

Many standard control-design methods fail for delay systems, or any system not represented as a rational function. In addition to using the Smith predictor outlined above, there are however several common tricks that can be applied to make use of these methods.

  • Approximate the delay using a pade approximation, this will result in a standard rational model. The drawbacks include zeros in the right half plane and a failure to capture the extreme phase loss of the delay for high frequencies.
  • Discretize the system with a sample time that fits an integer multiple in the delay time. A delay can be represented exactly in discrete time, but if the sample time is chosen small in relation to the delay time, a large number of extra states will be introduced.
  • Neglect the delay and design the controller with large phase and delay margins. This is perhaps not a terribly sophisticated method, but nevertheless useful in practice.
  • Neglect the delay, but model it as uncertainty. See Modeling uncertain time delays in the RobustAndOptimalControl.jl extension package. This can help you get a feeling for the margin with which you must design your controller when you have neglected to model the delay.
  • Frequency-domain methods such as manual loop shaping, and some forms of optimization-based tuning, handle time delays natively.

Whatever method is used to design in the presence of delays, the robustness and performance of the design should preferably be verified using a model of the plant where the delay is included, uncertain or not.

+plot!(yticks=0:180:1080, sp=2)Example block output

Check the Nyquist plot. Note that the Nyquist curve encircles -1 for τ > 2.99

fig_nyquist = nyquistplot(C * P, exp10.(-1:1e-4:2), title="τ = $τ")
Example block output

A video tutorial on delay systems is available here:

Additional design methods for delay systems

Many standard control-design methods fail for delay systems, or any system not represented as a rational function. In addition to using the Smith predictor outlined above, there are however several common tricks that can be applied to make use of these methods.

  • Approximate the delay using a pade approximation, this will result in a standard rational model. The drawbacks include zeros in the right half plane and a failure to capture the extreme phase loss of the delay for high frequencies.
  • Discretize the system with a sample time that fits an integer multiple in the delay time. A delay can be represented exactly in discrete time, but if the sample time is chosen small in relation to the delay time, a large number of extra states will be introduced.
  • Neglect the delay and design the controller with large phase and delay margins. This is perhaps not a terribly sophisticated method, but nevertheless useful in practice.
  • Neglect the delay, but model it as uncertainty. See Modeling uncertain time delays in the RobustAndOptimalControl.jl extension package. This can help you get a feeling for the margin with which you must design your controller when you have neglected to model the delay.
  • Frequency-domain methods such as manual loop shaping, and some forms of optimization-based tuning, handle time delays natively.

Whatever method is used to design in the presence of delays, the robustness and performance of the design should preferably be verified using a model of the plant where the delay is included, uncertain or not.

diff --git a/dev/examples/tuning_from_data/536fe768.svg b/dev/examples/tuning_from_data/2a22d8bc.svg similarity index 86% rename from dev/examples/tuning_from_data/536fe768.svg rename to dev/examples/tuning_from_data/2a22d8bc.svg index eedaa77c4..58dd27143 100644 --- a/dev/examples/tuning_from_data/536fe768.svg +++ b/dev/examples/tuning_from_data/2a22d8bc.svg @@ -1,71 +1,71 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/cee7746a.svg b/dev/examples/tuning_from_data/3c9fda80.svg similarity index 99% rename from dev/examples/tuning_from_data/cee7746a.svg rename to dev/examples/tuning_from_data/3c9fda80.svg index d6f73bf9a..0bd0c0fa2 100644 --- a/dev/examples/tuning_from_data/cee7746a.svg +++ b/dev/examples/tuning_from_data/3c9fda80.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/48e259bb.svg b/dev/examples/tuning_from_data/48f91ad6.svg similarity index 99% rename from dev/examples/tuning_from_data/48e259bb.svg rename to dev/examples/tuning_from_data/48f91ad6.svg index 53caee9c2..84004982b 100644 --- a/dev/examples/tuning_from_data/48e259bb.svg +++ b/dev/examples/tuning_from_data/48f91ad6.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/6cfe3e13.svg b/dev/examples/tuning_from_data/594d69e7.svg similarity index 84% rename from dev/examples/tuning_from_data/6cfe3e13.svg rename to dev/examples/tuning_from_data/594d69e7.svg index e96ac1cba..f69aaf6ee 100644 --- a/dev/examples/tuning_from_data/6cfe3e13.svg +++ b/dev/examples/tuning_from_data/594d69e7.svg @@ -1,39 +1,39 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/2324a534.svg b/dev/examples/tuning_from_data/65b45423.svg similarity index 87% rename from dev/examples/tuning_from_data/2324a534.svg rename to dev/examples/tuning_from_data/65b45423.svg index 380fdf508..5a95627ba 100644 --- a/dev/examples/tuning_from_data/2324a534.svg +++ b/dev/examples/tuning_from_data/65b45423.svg @@ -1,112 +1,112 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/1f2b4234.svg b/dev/examples/tuning_from_data/86de2dc6.svg similarity index 92% rename from dev/examples/tuning_from_data/1f2b4234.svg rename to dev/examples/tuning_from_data/86de2dc6.svg index 350f4c33a..37c88a2e6 100644 --- a/dev/examples/tuning_from_data/1f2b4234.svg +++ b/dev/examples/tuning_from_data/86de2dc6.svg @@ -1,178 +1,178 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/8acccb09.svg b/dev/examples/tuning_from_data/8b43eb8c.svg similarity index 86% rename from dev/examples/tuning_from_data/8acccb09.svg rename to dev/examples/tuning_from_data/8b43eb8c.svg index b3efec1b7..d9759c076 100644 --- a/dev/examples/tuning_from_data/8acccb09.svg +++ b/dev/examples/tuning_from_data/8b43eb8c.svg @@ -1,110 +1,110 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/dcadbb34.svg b/dev/examples/tuning_from_data/a6b2f9cf.svg similarity index 87% rename from dev/examples/tuning_from_data/dcadbb34.svg rename to dev/examples/tuning_from_data/a6b2f9cf.svg index 9fc126854..2b85cc14e 100644 --- a/dev/examples/tuning_from_data/dcadbb34.svg +++ b/dev/examples/tuning_from_data/a6b2f9cf.svg @@ -1,62 +1,62 @@ - + - + - + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/167857c7.svg b/dev/examples/tuning_from_data/f7609f9a.svg similarity index 97% rename from dev/examples/tuning_from_data/167857c7.svg rename to dev/examples/tuning_from_data/f7609f9a.svg index 3d6b1a93b..7505d3d82 100644 --- a/dev/examples/tuning_from_data/167857c7.svg +++ b/dev/examples/tuning_from_data/f7609f9a.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/88ca9829.svg b/dev/examples/tuning_from_data/fa3a6c1d.svg similarity index 79% rename from dev/examples/tuning_from_data/88ca9829.svg rename to dev/examples/tuning_from_data/fa3a6c1d.svg index 39d367d57..8264fcda1 100644 --- a/dev/examples/tuning_from_data/88ca9829.svg +++ b/dev/examples/tuning_from_data/fa3a6c1d.svg @@ -1,64 +1,64 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/index.html b/dev/examples/tuning_from_data/index.html index 6991f2177..f14533859 100644 --- a/dev/examples/tuning_from_data/index.html +++ b/dev/examples/tuning_from_data/index.html @@ -29,13 +29,13 @@ Sample Time: 0.01 (seconds) Discrete-time state-space model

Since the data used for the system identification had acceleration rather than velocity as output, we multiply the estimated model by the transfer function $1/s$ to get a velocity model. Before we do this, we convert the estimated discrete-time model into continuous time using the function d2c. The estimated system also has a negative gain due to the mounting of the accelerometer, so we multiply the model by $-1$ to get a positive gain.

s = tf("s")
 P = 1/s * d2c(-Pacc.sys)
-bodeplot(P)
Example block output

Dealing with model uncertainty

When using a model for control design, we always have to consider how robust we are with respect to errors in the model. Classical margins like the gain and phase margins are simple measures of robustness, applicable to simple measures of uncertainty. Here, we will attempt to characterize the uncertainty in the model slightly more accurately.

When we estimate linear black-box models from data, like we did above using subspaceid, we can get a rough estimate of how well a linear model describes the input-output data by looking at the magnitude-squared coherence function $\gamma(i\omega)$:

coherenceplot(d)
Example block output

For frequencies where $\gamma$ is close to one, a linear model is expected to fit well, whereas for frequencies where $\gamma$ is close to zero, we cannot trust the model. How does this rough estimate of model certainty translate to our control analysis? In the video The benefit and Cost of Feedback, we show that for frequencies where the uncertainty in the model is large, we must have a small sensitivity. In the video, we analyzed the effects of additive uncertainty, in which case we need to make sure that the sensitivity function $CS = C/(1+PC)$ is sufficiently small. When using the rough estimate of model uncertainty provided by the coherence function, it may be more reasonable to consider a multiplicative (relative) uncertainty model, in which case we need to verify that the sensitivity function $T = PC/(1+PC)$ is small for frequencies where $\gamma$ is small.

Since our coherence drops significantly above $\omega = 130$rad/s, we will try to design a controller that yields a complementary sensitivity function $T$ that has low gain above this frequency.

In the documentation of RobustAndOptimalControl.jl, we list a number of common uncertainty models together with the criteria for robust stability. A good resource on the gang of four is available in these slides.

Controller tuning

We could take multiple different approaches to tuning the PID controller, a few alternatives are listed here

  • Trial and error in simulation or experiment.
  • Manual loop shaping
  • Automatic loop shaping
  • Step-response optimization (example)

Here, we will attempt a manual loop-shaping approach using the function loopshapingPID, and then then compare the result to a pole-placement controller.

Manual loop shaping

Since the process contains two sharp resonance peaks, visible in the Bode diagram above, we want to include a lowpass filter in the controller to suppress any frequencies above the first resonance so that the resonances do not cause excessive robustness problems. Here, we will use a second-order lowpass filer.

A PID controller is fundamentally incapable at damping the resonances in this high-order system. Indeed, for a plant model of order 4, we have a closed-loop system with a 7-dimensional state (one pole for the integrator and two for the low-pass filter), but only 3-4 parameters in the PID controller (depending on whether or not we count the filter parameter), so there is no hope for us to arbitrarily place the poles using the PID controller. Trying to use a gain high enough to dampen the resonant poles can result in poor robustness properties, as we will see below.

The function pid takes the PID parameters "standard form", but the parameter convention to use can be selected using the form keyword. We use the function marginplot to guide our tuning, the following parameters were found to give a good result

K = 10
+bodeplot(P)
Example block output

Dealing with model uncertainty

When using a model for control design, we always have to consider how robust we are with respect to errors in the model. Classical margins like the gain and phase margins are simple measures of robustness, applicable to simple measures of uncertainty. Here, we will attempt to characterize the uncertainty in the model slightly more accurately.

When we estimate linear black-box models from data, like we did above using subspaceid, we can get a rough estimate of how well a linear model describes the input-output data by looking at the magnitude-squared coherence function $\gamma(i\omega)$:

coherenceplot(d)
Example block output

For frequencies where $\gamma$ is close to one, a linear model is expected to fit well, whereas for frequencies where $\gamma$ is close to zero, we cannot trust the model. How does this rough estimate of model certainty translate to our control analysis? In the video The benefit and Cost of Feedback, we show that for frequencies where the uncertainty in the model is large, we must have a small sensitivity. In the video, we analyzed the effects of additive uncertainty, in which case we need to make sure that the sensitivity function $CS = C/(1+PC)$ is sufficiently small. When using the rough estimate of model uncertainty provided by the coherence function, it may be more reasonable to consider a multiplicative (relative) uncertainty model, in which case we need to verify that the sensitivity function $T = PC/(1+PC)$ is small for frequencies where $\gamma$ is small.

Since our coherence drops significantly above $\omega = 130$rad/s, we will try to design a controller that yields a complementary sensitivity function $T$ that has low gain above this frequency.

In the documentation of RobustAndOptimalControl.jl, we list a number of common uncertainty models together with the criteria for robust stability. A good resource on the gang of four is available in these slides.

Controller tuning

We could take multiple different approaches to tuning the PID controller, a few alternatives are listed here

  • Trial and error in simulation or experiment.
  • Manual loop shaping
  • Automatic loop shaping
  • Step-response optimization (example)

Here, we will attempt a manual loop-shaping approach using the function loopshapingPID, and then then compare the result to a pole-placement controller.

Manual loop shaping

Since the process contains two sharp resonance peaks, visible in the Bode diagram above, we want to include a lowpass filter in the controller to suppress any frequencies above the first resonance so that the resonances do not cause excessive robustness problems. Here, we will use a second-order lowpass filer.

A PID controller is fundamentally incapable at damping the resonances in this high-order system. Indeed, for a plant model of order 4, we have a closed-loop system with a 7-dimensional state (one pole for the integrator and two for the low-pass filter), but only 3-4 parameters in the PID controller (depending on whether or not we count the filter parameter), so there is no hope for us to arbitrarily place the poles using the PID controller. Trying to use a gain high enough to dampen the resonant poles can result in poor robustness properties, as we will see below.

The function pid takes the PID parameters "standard form", but the parameter convention to use can be selected using the form keyword. We use the function marginplot to guide our tuning, the following parameters were found to give a good result

K = 10
 Tf = 0.4
 Ti = 4
 Td = 0.1
 CF = pid(K, Ti, Td; Tf)
-marginplot(P*CF)
Example block output

Here, we have selected the proportional gain $K$large enough to give a crossover bandwidth of about 1rad/s, being careful not to let the resonance peaks reach too close to unit gain, destroying our robustness. The integral time constant $T_i$ is selected as low as possible without destroying the phase margin, and the derivative time constant $T_d$ is increased slowly to improve the phase margin while not letting the resonance peaks become too large.

The pid function returns the PI controller with the second-order lowpass filter already applied.

Next, we form the closed-loop system $G$ from reference to output an plot a step response

G = feedback(P*CF)
-plot(step(G, 50), label="Step response")
Example block output

This looks rather aggressive and with a large overshoot visible. The problem here is that no mechanical system can follow a perfect step in the reference, and it is thus common to generate some form of physically realizable smooth step as input reference. Below, we use the package TrajectoryLimiters.jl to filter the reference step such that it has bounded acceleration and velocity

using TrajectoryLimiters
+marginplot(P*CF)
Example block output

Here, we have selected the proportional gain $K$large enough to give a crossover bandwidth of about 1rad/s, being careful not to let the resonance peaks reach too close to unit gain, destroying our robustness. The integral time constant $T_i$ is selected as low as possible without destroying the phase margin, and the derivative time constant $T_d$ is increased slowly to improve the phase margin while not letting the resonance peaks become too large.

The pid function returns the PI controller with the second-order lowpass filter already applied.

Next, we form the closed-loop system $G$ from reference to output an plot a step response

G = feedback(P*CF)
+plot(step(G, 50), label="Step response")
Example block output

This looks rather aggressive and with a large overshoot visible. The problem here is that no mechanical system can follow a perfect step in the reference, and it is thus common to generate some form of physically realizable smooth step as input reference. Below, we use the package TrajectoryLimiters.jl to filter the reference step such that it has bounded acceleration and velocity

using TrajectoryLimiters
 ẋM = 1 # Velocity limit
 ẍM = 0.01 # Acceleration limit
 limiter = TrajectoryLimiter(d.Ts, ẋM, ẍM)
@@ -43,7 +43,7 @@
 timevec = 0:d.Ts:50
 plot(step(G, 50), label="Step response")
 plot!(lsim(G, inputstep', timevec), label="Smooth step response")
-plot!(timevec, inputstep, label="Smooth reference trajectory", l=(:dash, :black))
Example block output

The result now looks much better, with some small amount of overshoot. The performance is not terrific, taking about 20 seconds to realize the step. However, attempting to make the response faster using feedback alone will further exacerbate the robustness problems due to the resonance peaks highlighted above.

To analyze the robustness of this controller, we can inspect the sensitivity functions in the gang of four. In particular, we are interested in the complementary sensitivity function $T = PC/(1+PC)$

gangoffourplot(P, CF)
Example block output

The gang of four indicates that we have a robust tuning, no uncomfortably large peaks appears in either $T$ or $S$.

Below, we attempt a pole-placement design for comparison. Contrary to the PID controller, a state-feedback controller designed using pole placement can place all poles of this system arbitrarily (the system is controllable, which can be verified using the function controllability).

Pole placement

We start by inspecting the pole locations of the open-loop plant

pzmap(P)
Example block output

As expected, we have 2 resonant pole pairs.

When dampening fast resonant poles, it is often a good idea to only dampen them, not to change the bandwidth of them. Trying to increase the bandwidth of these fast poles requires very large controller gain, and making the poles slower often causes severe robustness problems. We thus try to place the resonant poles with the same magnitude, but with perfect damping.

current_poles = poles(P)
5-element Vector{ComplexF64}:
+plot!(timevec, inputstep, label="Smooth reference trajectory", l=(:dash, :black))
Example block output

The result now looks much better, with some small amount of overshoot. The performance is not terrific, taking about 20 seconds to realize the step. However, attempting to make the response faster using feedback alone will further exacerbate the robustness problems due to the resonance peaks highlighted above.

To analyze the robustness of this controller, we can inspect the sensitivity functions in the gang of four. In particular, we are interested in the complementary sensitivity function $T = PC/(1+PC)$

gangoffourplot(P, CF)
Example block output

The gang of four indicates that we have a robust tuning, no uncomfortably large peaks appears in either $T$ or $S$.

Below, we attempt a pole-placement design for comparison. Contrary to the PID controller, a state-feedback controller designed using pole placement can place all poles of this system arbitrarily (the system is controllable, which can be verified using the function controllability).

Pole placement

We start by inspecting the pole locations of the open-loop plant

pzmap(P)
Example block output

As expected, we have 2 resonant pole pairs.

When dampening fast resonant poles, it is often a good idea to only dampen them, not to change the bandwidth of them. Trying to increase the bandwidth of these fast poles requires very large controller gain, and making the poles slower often causes severe robustness problems. We thus try to place the resonant poles with the same magnitude, but with perfect damping.

current_poles = poles(P)
5-element Vector{ComplexF64}:
                   0.0 + 0.0im
   -0.3650205987299606 + 79.62234770329262im
   -0.3650205987299606 - 79.62234770329262im
@@ -62,8 +62,8 @@
  -35335.165089836235

The resulting observer-based state-feedback controller can be constructed using the function observer_controller. We also form the closed-loop system $G_{pp}$ from reference to output an plot a step response like we did above

Cpp = observer_controller(P, L, K)
 Gpp = feedback(P*Cpp)
 plot(lsim(Gpp, inputstep', timevec), label="Smooth step response")
-plot!(timevec, inputstep, label="Smooth reference trajectory", l=(:dash, :black), legend=:bottomright)
Example block output

The pole-placement controller achieves a very nice result, but this comes at a cost of using very large controller gain. The gang-of-four plot below indicates that we have a controller with a very large noise-amplification transfer function, $CS$ has a large gain for high frequencies, implying that this controller requires a very good sensor to be practical! We also have significant gain in $T$ well above the frequency $ω = 130$rad/s above which we couldn't trust the model.

gangoffourplot(P, Cpp)
-vline!(fill(130, 1, 4), label="\$ω = 130\$", l=(:dash, :black))
Example block output

Due to the high gain of the controller we got, we redo the design, this time only dampening the resonant poles slightly. We also lower the bandwidth of the integrator pole to make the controller less aggressive

p1 = current_poles[2]
+plot!(timevec, inputstep, label="Smooth reference trajectory", l=(:dash, :black), legend=:bottomright)
Example block output

The pole-placement controller achieves a very nice result, but this comes at a cost of using very large controller gain. The gang-of-four plot below indicates that we have a controller with a very large noise-amplification transfer function, $CS$ has a large gain for high frequencies, implying that this controller requires a very good sensor to be practical! We also have significant gain in $T$ well above the frequency $ω = 130$rad/s above which we couldn't trust the model.

gangoffourplot(P, Cpp)
+vline!(fill(130, 1, 4), label="\$ω = 130\$", l=(:dash, :black))
Example block output

Due to the high gain of the controller we got, we redo the design, this time only dampening the resonant poles slightly. We also lower the bandwidth of the integrator pole to make the controller less aggressive

p1 = current_poles[2]
 p2 = current_poles[4]
 
 p1_new = abs(p1) * cis(-pi + deg2rad(65)) # Place the pole with the same magnitude, but with an angle of -pi + 65 degrees
@@ -78,7 +78,7 @@
 
 f2 = gangoffourplot(P, Cpp)
 vline!(fill(130, 1, 4), label="\$ω = 130\$", l=(:dash, :black))
-plot(f1, f2, size=(800, 600))
Example block output

We still have a nice step response using this controller, but this time, we have a rolloff in $T$ that starts around the frequency $ω = 130$rad/s.

C-Code generation

With the PID controller, we can transform the PID parameters to the desired form and enter those into an already existing PID-controller implementation. Care must be taken to incorporate also the measurement filter designed, this filter is important for robustness analysis to be valid. If no existing PID controller implementation is available, we may either make use of the package DiscretePIDs.jl, or generate C-code for the controller. Below, we generate some C code.

Using the pole-placement controller derived above, we discretize the controller using the Tustin (bilinear) method with the function c2d, and then call SymbolicControlSystems.ccode to generate the code.

using SymbolicControlSystems
+plot(f1, f2, size=(800, 600))
Example block output

We still have a nice step response using this controller, but this time, we have a rolloff in $T$ that starts around the frequency $ω = 130$rad/s.

C-Code generation

With the PID controller, we can transform the PID parameters to the desired form and enter those into an already existing PID-controller implementation. Care must be taken to incorporate also the measurement filter designed, this filter is important for robustness analysis to be valid. If no existing PID controller implementation is available, we may either make use of the package DiscretePIDs.jl, or generate C-code for the controller. Below, we generate some C code.

Using the pole-placement controller derived above, we discretize the controller using the Tustin (bilinear) method with the function c2d, and then call SymbolicControlSystems.ccode to generate the code.

using SymbolicControlSystems
 Cdiscrete = c2d(Cpp, d.Ts, :tustin)
 SymbolicControlSystems.ccode(Cdiscrete)

This produces the following C-code for filtering the error signal through the controller transfer function

#include <stdio.h>
 #include <math.h>
@@ -102,4 +102,4 @@
     for (i=0; i < 5; ++i) {
         x[i] = xp[i];
     }
-}

Summary

This tutorial has shown how to follow a workflow that consists of

  1. Estimate a process model using experimental data.
  2. Design a controller based on the estimated model. We designed a PID controller and one pole-placement controller which was able to cancel the resonances in the system which the PID controllers could not do.
  3. Simulate the closed-loop system and analyze its robustness properties. Model uncertainty was considered using the coherence function.
  4. Generate C-code for one of the controllers.

Each of these steps is covered in additional detail in the videos available in the playlist Control systems in Julia. See also the tutorial Control design for a quadruple-tank system.

+}

Summary

This tutorial has shown how to follow a workflow that consists of

  1. Estimate a process model using experimental data.
  2. Design a controller based on the estimated model. We designed a PID controller and one pole-placement controller which was able to cancel the resonances in the system which the PID controllers could not do.
  3. Simulate the closed-loop system and analyze its robustness properties. Model uncertainty was considered using the coherence function.
  4. Generate C-code for one of the controllers.

Each of these steps is covered in additional detail in the videos available in the playlist Control systems in Julia. See also the tutorial Control design for a quadruple-tank system.

diff --git a/dev/examples/zoh/7bcee2d9.svg b/dev/examples/zoh/12de4b9e.svg similarity index 89% rename from dev/examples/zoh/7bcee2d9.svg rename to dev/examples/zoh/12de4b9e.svg index e3cabf854..423820fa9 100644 --- a/dev/examples/zoh/7bcee2d9.svg +++ b/dev/examples/zoh/12de4b9e.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/5eaf9dfb.svg b/dev/examples/zoh/362dcc11.svg similarity index 98% rename from dev/examples/zoh/5eaf9dfb.svg rename to dev/examples/zoh/362dcc11.svg index 1bbc31253..f531f00a9 100644 --- a/dev/examples/zoh/5eaf9dfb.svg +++ b/dev/examples/zoh/362dcc11.svg @@ -1,66 +1,66 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/67ce0bc5.svg b/dev/examples/zoh/4411c743.svg similarity index 97% rename from dev/examples/zoh/67ce0bc5.svg rename to dev/examples/zoh/4411c743.svg index c865d910b..715792e28 100644 --- a/dev/examples/zoh/67ce0bc5.svg +++ b/dev/examples/zoh/4411c743.svg @@ -1,62 +1,62 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/b930b869.svg b/dev/examples/zoh/7a5d98fe.svg similarity index 89% rename from dev/examples/zoh/b930b869.svg rename to dev/examples/zoh/7a5d98fe.svg index 1ac67af99..b5fda04a4 100644 --- a/dev/examples/zoh/b930b869.svg +++ b/dev/examples/zoh/7a5d98fe.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/4b0c4ad0.svg b/dev/examples/zoh/7c000877.svg similarity index 97% rename from dev/examples/zoh/4b0c4ad0.svg rename to dev/examples/zoh/7c000877.svg index b1a823c80..b8d2b0d87 100644 --- a/dev/examples/zoh/4b0c4ad0.svg +++ b/dev/examples/zoh/7c000877.svg @@ -1,79 +1,79 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/7e4f508f.svg b/dev/examples/zoh/b0478100.svg similarity index 85% rename from dev/examples/zoh/7e4f508f.svg rename to dev/examples/zoh/b0478100.svg index f937d3802..5331ec87f 100644 --- a/dev/examples/zoh/7e4f508f.svg +++ b/dev/examples/zoh/b0478100.svg @@ -1,68 +1,68 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/599965aa.svg b/dev/examples/zoh/bbbe5094.svg similarity index 97% rename from dev/examples/zoh/599965aa.svg rename to dev/examples/zoh/bbbe5094.svg index e2b534236..7f9d11d1e 100644 --- a/dev/examples/zoh/599965aa.svg +++ b/dev/examples/zoh/bbbe5094.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/7e006f90.svg b/dev/examples/zoh/c7bf15fa.svg similarity index 91% rename from dev/examples/zoh/7e006f90.svg rename to dev/examples/zoh/c7bf15fa.svg index f028e7d83..ced8073bb 100644 --- a/dev/examples/zoh/7e006f90.svg +++ b/dev/examples/zoh/c7bf15fa.svg @@ -1,72 +1,72 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/6b91ed2f.svg b/dev/examples/zoh/ccfad3ef.svg similarity index 88% rename from dev/examples/zoh/6b91ed2f.svg rename to dev/examples/zoh/ccfad3ef.svg index 8157b8cd4..37b5cfe02 100644 --- a/dev/examples/zoh/6b91ed2f.svg +++ b/dev/examples/zoh/ccfad3ef.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/index.html b/dev/examples/zoh/index.html index 260bb75dd..33b4b0eae 100644 --- a/dev/examples/zoh/index.html +++ b/dev/examples/zoh/index.html @@ -15,13 +15,13 @@ bodeplot(P, wd, lab="\$P(s)\$") bodeplot!(Pz, wd, lab="\$P(s)Z(s)\$") bodeplot!(Pd, wd, lab="\$P_d(z)\$ (ZoH sampling)", l=:dash) -vline!([0.5 0.5], l=(:black, :dash), lab="Nyquist freq.", legend=:bottomleft)Example block output

The frequency response of Pz $= P(s) Z(s)$ matches that of $P_d(z)$ exactly, but these two differ from the frequency response of the original $P(s)$ due to the ZoH operator.

The step response of Pz $= P(s) Z(s)$ matches the discrete output of $P_d(z)$ delayed by half the sample time

resP = step(P, 12)
+vline!([0.5 0.5], l=(:black, :dash), lab="Nyquist freq.", legend=:bottomleft)
Example block output

The frequency response of Pz $= P(s) Z(s)$ matches that of $P_d(z)$ exactly, but these two differ from the frequency response of the original $P(s)$ due to the ZoH operator.

The step response of Pz $= P(s) Z(s)$ matches the discrete output of $P_d(z)$ delayed by half the sample time

resP = step(P, 12)
 resPz = step(Pz, 12)
 resPd = step(Pd, 12)
 plot([resP, resPz, resPd], lab=["P" "Pz" "Pd"])
 
 t_shift = resPd.t .+ Ts / 2
-plot!(t_shift, resPd.y[:], lab="Pd shifted", m=:o, l=:dash)
Example block output

With a piecewise constant input, even if it's not a step, we get the same result

Tf = 20
+plot!(t_shift, resPd.y[:], lab="Pd shifted", m=:o, l=:dash)
Example block output

With a piecewise constant input, even if it's not a step, we get the same result

Tf = 20
 ufun = (x,t)->[sin(2pi*floor(t/Ts)*Ts/5)]
 resP = lsim(P, ufun, Tf)
 resPz = lsim(Pz, ufun, 0:0.01:Tf)
@@ -29,18 +29,18 @@
 plot([resP, resPz, resPd], lab=["P" "Pz" "Pd"])
 
 t_shift = resPd.t .+ Ts / 2
-plot!(t_shift, resPd.y[:], lab="Pd shifted", m=:o)
Example block output

With a continuous input signal, the result is different, after the initial transient, the output of Pz matches that of Pd exactly (try plotting with the plotly() backend and zoom in at the end)

Tf = 100
+plot!(t_shift, resPd.y[:], lab="Pd shifted", m=:o)
Example block output

With a continuous input signal, the result is different, after the initial transient, the output of Pz matches that of Pd exactly (try plotting with the plotly() backend and zoom in at the end)

Tf = 100
 ufun = (x,t)->[sin(2pi*t/5)]
 resP = lsim(P, ufun, Tf)
 resPz = lsim(Pz, ufun, 0:0.01:Tf)
 resPd = lsim(Pd, ufun, Tf)
-plot([resP, resPz, resPd], lab=["P" "Pz" "Pd"]); lens!([Tf-10, Tf], [-0.1, 0.1], inset=(1, bbox(0.4, 0.02, 0.4, 0.3)))
Example block output

Discrete to continuous

Discrete-time systems can be converted to continuous-time systems formulated with delay terms in such a way that the frequency-response of the two systems match exactly, using the substitution $z^{-1} = e^{-sT_s}$. To this end, the function d2c_exact is used. This is useful when analyzing hybrid systems with both continuous-time and discrete-time components and accurate frequency-domain calculations are required over the entire frequency range up to the Nyquist frequency.

Below, we compare the frequency response of a discrete-time system with delays to two continuous-time systems, one translated using the exact method and one using the standard d2c method with inverse ZoH sampling. We extend the frequency axis for this example to extend beyond the Nyquist frequency.

wd = exp10.(LinRange(-2, 1, 200))
+plot([resP, resPz, resPd], lab=["P" "Pz" "Pd"]); lens!([Tf-10, Tf], [-0.1, 0.1], inset=(1, bbox(0.4, 0.02, 0.4, 0.3)))
Example block output

Discrete to continuous

Discrete-time systems can be converted to continuous-time systems formulated with delay terms in such a way that the frequency-response of the two systems match exactly, using the substitution $z^{-1} = e^{-sT_s}$. To this end, the function d2c_exact is used. This is useful when analyzing hybrid systems with both continuous-time and discrete-time components and accurate frequency-domain calculations are required over the entire frequency range up to the Nyquist frequency.

Below, we compare the frequency response of a discrete-time system with delays to two continuous-time systems, one translated using the exact method and one using the standard d2c method with inverse ZoH sampling. We extend the frequency axis for this example to extend beyond the Nyquist frequency.

wd = exp10.(LinRange(-2, 1, 200))
 Pdc = d2c_exact(ss(Pd))
 Pc = d2c(Pd)
 bodeplot(Pd, wd, lab="\$P_d(z)\$")
 bodeplot!(Pdc, wd, lab="\$P_d(s)\$ (exact translation)", l=:dash)
 bodeplot!(Pc, wd, lab="\$P_d(s)\$ (inverse ZoH sampling)")
-vline!([0.5 0.5], l=(:black, :dash), lab="Nyquist freq.", legend=:bottomleft)
Example block output

We see that the translation of the discrete-time system to continuous time using the standard inverse ZoH sampling (d2c(Pd)) is not accurate for frequencies close to and above the Nyquist frequency. The translation using exact method (d2c_exact(Pd)) matches the frequency response of the discrete-time system exactly over the entire frequency axis.

Time-domain simulation

When analyzing hybrid systems, systems with both discrete-time and continuous-time components, it is often useful to simulate the system in time-domain. To assemble the complete hybrid system, we must either

  1. Convert continuous-time components to discrete time, or
  2. Convert discrete-time components to continuous time.

If all inputs to the continuous-time components are piecewise constant, then the first option is the most natural. The ZoH discretization is exact for piece-wise constant inputs. This conversion can be performed using the function c2d with the (default) ZoH sampling method. If some of the inputs to the continuous-time components are continuously varying, then the second option may be used for maximum accuracy. This is particularly useful if the continuous-time inputs contain frequencies higher than the Nyquist frequency, or when the continuous-time plant contains significant dynamics (resonances etc.) at frequencies higher than the Nyquist frequency of the discrete-time part. This conversion can be performed using the function d2c_exact which has two modes of operation. The default method produces a causal system which can be simulated in the time domain, while the second method produces an acausal system of lower complexity, which is amenable to frequency-domain computations only. Since we are going to simulate in the time domain here, we will use the causal method (default).

In this example, we will model a continuous-time system $P(s)$ with resonances appearing at large frequencies, while the discrete-time control system is operating a significantly lower frequencies.

A = [0.0    0.0    0.0   1.0     0.0     0.0
+vline!([0.5 0.5], l=(:black, :dash), lab="Nyquist freq.", legend=:bottomleft)
Example block output

We see that the translation of the discrete-time system to continuous time using the standard inverse ZoH sampling (d2c(Pd)) is not accurate for frequencies close to and above the Nyquist frequency. The translation using exact method (d2c_exact(Pd)) matches the frequency response of the discrete-time system exactly over the entire frequency axis.

Time-domain simulation

When analyzing hybrid systems, systems with both discrete-time and continuous-time components, it is often useful to simulate the system in time-domain. To assemble the complete hybrid system, we must either

  1. Convert continuous-time components to discrete time, or
  2. Convert discrete-time components to continuous time.

If all inputs to the continuous-time components are piecewise constant, then the first option is the most natural. The ZoH discretization is exact for piece-wise constant inputs. This conversion can be performed using the function c2d with the (default) ZoH sampling method. If some of the inputs to the continuous-time components are continuously varying, then the second option may be used for maximum accuracy. This is particularly useful if the continuous-time inputs contain frequencies higher than the Nyquist frequency, or when the continuous-time plant contains significant dynamics (resonances etc.) at frequencies higher than the Nyquist frequency of the discrete-time part. This conversion can be performed using the function d2c_exact which has two modes of operation. The default method produces a causal system which can be simulated in the time domain, while the second method produces an acausal system of lower complexity, which is amenable to frequency-domain computations only. Since we are going to simulate in the time domain here, we will use the causal method (default).

In this example, we will model a continuous-time system $P(s)$ with resonances appearing at large frequencies, while the discrete-time control system is operating a significantly lower frequencies.

A = [0.0    0.0    0.0   1.0     0.0     0.0
    0.0    0.0    0.0   0.0     1.0     0.0
    0.0    0.0    0.0   0.0     0.0     1.0
  -10.0    0.0   10.0  -0.001   0.0     0.001
@@ -50,7 +50,7 @@
 C = [0.0  0.0  0.0  0.0  0.0  1.0]
 P = minreal(ss(A,B,C,0))
 w = exp10.(LinRange(-2, 2, 1000))
-bodeplot(P, w, lab="\$P(s)\$", plotphase=false)
Example block output

The discrete-time controller $C(z)$ is a basic PI controller

Ts = 1
+bodeplot(P, w, lab="\$P(s)\$", plotphase=false)
Example block output

The discrete-time controller $C(z)$ is a basic PI controller

Ts = 1
 C = pid(0.01,10; Ts, Tf = 1/100, state_space=true)
StateSpace{Discrete{Int64}, Float64}
 A = 
   1.0   0.009900019603999222   0.006273279749068797
@@ -71,14 +71,14 @@
 wd = exp10.(-3:0.001:log10(1000*0.05))
 figb = bodeplot(P, wd, lab="\$P(s)\$")
 bodeplot!(Pd, wd, lab="\$P(z)\$ ZoH")
-bodeplot!(Ld, wd, lab="\$P(z)C(z)\$", legend=:bottomleft)
Example block output

If we instead make use of the second method above, exactly translating the discrete-time controller to continuous time, we can more easily determine that the closed-loop system will be stable by inspecting the Bode plot. Here, we show the Bode plot of $P$ and that of the loop-transfer function, including the ZoH operator, $P(s)Z(s)C(s)$.

Cc = d2c_exact(C)
+bodeplot!(Ld, wd, lab="\$P(z)C(z)\$", legend=:bottomleft)
Example block output

If we instead make use of the second method above, exactly translating the discrete-time controller to continuous time, we can more easily determine that the closed-loop system will be stable by inspecting the Bode plot. Here, we show the Bode plot of $P$ and that of the loop-transfer function, including the ZoH operator, $P(s)Z(s)C(s)$.

Cc = d2c_exact(C)
 Lc = P*Z*Cc
 
 bodeplot(P, wd, lab="\$P(s)\$")
-bodeplot!(Lc, wd, lab="\$P(s)C(s)\$", legend=:bottomleft, c=3)
Example block output

If we form the closed-loop system from an input disturbance to the output

\[PS = \dfrac{P(s)}{1 + P(s)C(s)}\]

we can simulate the response to a continuous-time input disturbance that contains frequencies higher than the Nyquist frequency of the discrete-time system, we do this below. We also try doing this with the discretized plant, which yields a very poor result

PS = feedback(P, Z*Cc)  # Use the continuous-time plant and continuous translation of the controller + ZoH operator
+bodeplot!(Lc, wd, lab="\$P(s)C(s)\$", legend=:bottomleft, c=3)
Example block output

If we form the closed-loop system from an input disturbance to the output

\[PS = \dfrac{P(s)}{1 + P(s)C(s)}\]

we can simulate the response to a continuous-time input disturbance that contains frequencies higher than the Nyquist frequency of the discrete-time system, we do this below. We also try doing this with the discretized plant, which yields a very poor result

PS = feedback(P, Z*Cc)  # Use the continuous-time plant and continuous translation of the controller + ZoH operator
 PSd = feedback(Pd, C) # Use the discretized plant and discrete controller
 ω = 5.53 # Frequency of input disturbance (rad/s) (Nyquist frequency is π rad/s)
 disturbance(x, t) = sin(ω*t) # Continuous input disturbance
 plot(lsim(PS, disturbance, 0:0.22:3500), lab="Continuous disturbance response")
 plot!(lsim(PSd, disturbance, 3500), lab="Discrete disturbance response")
-hline!([abs(freqresp(PS, ω)[])], l=(:black, :dash), lab="Predicted freq. response amplitude", legend=:bottomleft, fmt=:png)
Example block output

The continuous-time analysis eventually settles at a periodic output that matches the amplitude predicted by the continuous-time frequency response. However, the discrete-time simulation yields, as expected, a very poor result. Noticeable in the simulation is the appearance of a beat frequency, which is expected due to the modulation introduced by sampling. [CCS]

Caveats

  • The exact output of the system that was translated from discrete to continuous time is not going to be accurate, so transient properties of the hybrid system cannot be accurately assessed using this kind of analysis.
  • Interpretation of frequency-responses for sampled-data systems must be done with care. The modulation introduced by sampling implies the creating of additional frequencies in the output. For an input with frequency $\omega$, the output will contain all frequencies $\omega ± \omega_s k$ where $\omega_s$ is the sampling frequency and $k$ is an integer. [CCS]

References

Learn more about sampled-data systems and zero-order hold sampling in chapter 7 of the reference below.

  • CCSÅström, K. J., & Wittenmark, B. (1997). Computer-Controlled Systems: Theory and Design.
+hline!([abs(freqresp(PS, ω)[])], l=(:black, :dash), lab="Predicted freq. response amplitude", legend=:bottomleft, fmt=:png)Example block output

The continuous-time analysis eventually settles at a periodic output that matches the amplitude predicted by the continuous-time frequency response. However, the discrete-time simulation yields, as expected, a very poor result. Noticeable in the simulation is the appearance of a beat frequency, which is expected due to the modulation introduced by sampling. [CCS]

Caveats

  • The exact output of the system that was translated from discrete to continuous time is not going to be accurate, so transient properties of the hybrid system cannot be accurately assessed using this kind of analysis.
  • Interpretation of frequency-responses for sampled-data systems must be done with care. The modulation introduced by sampling implies the creating of additional frequencies in the output. For an input with frequency $\omega$, the output will contain all frequencies $\omega ± \omega_s k$ where $\omega_s$ is the sampling frequency and $k$ is an integer. [CCS]

References

Learn more about sampled-data systems and zero-order hold sampling in chapter 7 of the reference below.

  • CCSÅström, K. J., & Wittenmark, B. (1997). Computer-Controlled Systems: Theory and Design.
diff --git a/dev/lib/analysis/index.html b/dev/lib/analysis/index.html index c670936af..eb81e11cf 100644 --- a/dev/lib/analysis/index.html +++ b/dev/lib/analysis/index.html @@ -1,24 +1,24 @@ -Analysis · ControlSystems.jl

For robust analysis, see RobustAndOptimalControl.jl.

Analysis

ControlSystemsBase.dampMethod
Wn, zeta, ps = damp(sys)

Compute the natural frequencies, Wn, and damping ratios, zeta, of the poles, ps, of sys

source
ControlSystemsBase.dcgainFunction
dcgain(sys, ϵ=0)

Compute the dcgain of system sys.

equal to G(0) for continuous-time systems and G(1) for discrete-time systems.

ϵ can be provided to evaluate the dcgain with a small perturbation into the stability region of the complex plane.

source
ControlSystemsBase.delaymarginMethod
dₘ = delaymargin(G::LTISystem)

Return the delay margin, dₘ. For discrete-time systems, the delay margin is normalized by the sample time, i.e., the value represents the margin in number of sample times. Only supports SISO systems.

source
ControlSystemsBase.gangoffourMethod
S, PS, CS, T = gangoffour(P, C; minimal=true)
-gangoffour(P::AbstractVector, C::AbstractVector; minimal=true)

Given a transfer function describing the plant P and a transfer function describing the controller C, computes the four transfer functions in the Gang-of-Four.

  • S = 1/(1+PC) Sensitivity function
  • PS = (1+PC)\P Load disturbance to measurement signal
  • CS = (1+PC)\C Measurement noise to control signal
  • T = PC/(1+PC) Complementary sensitivity function

If minimal=true, minreal will be applied to all transfer functions.

source
ControlSystemsBase.gangofsevenMethod
S, PS, CS, T, RY, RU, RE = gangofseven(P,C,F)

Given transfer functions describing the Plant P, the controller C and a feed forward block F, computes the four transfer functions in the Gang-of-Four and the transferfunctions corresponding to the feed forward.

  • S = 1/(1+PC) Sensitivity function
  • PS = P/(1+PC)
  • CS = C/(1+PC)
  • T = PC/(1+PC) Complementary sensitivity function
  • RY = PCF/(1+PC)
  • RU = CF/(1+P*C)
  • RE = F/(1+P*C)
source
ControlSystemsBase.markovparamMethod
markovparam(sys, n)

Compute the nth markov parameter of discrete-time state-space system sys. This is defined as the following:

h(0) = D

h(n) = C*A^(n-1)*B

source
ControlSystemsBase.polesMethod
poles(sys)

Compute the poles of system sys.

Note: Poles with multiplicity n > 1 may suffer numerical inaccuracies on the order eps(numeric_type(sys))^(1/n), i.e., a double pole in a system with Float64 coefficients may be computed with an error of about √(eps(Float64)) ≈ 1.5e-8.

source
ControlSystemsBase.reduce_sysMethod
reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)

Implements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed A, B, C, D matrices. These are empty if there are no zeros.

source
ControlSystemsBase.relative_gain_arrayMethod
relative_gain_array(A::AbstractMatrix; tol = 1.0e-15)

Reference: "On the Relative Gain Array (RGA) with Singular and Rectangular Matrices" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf

source
ControlSystemsBase.relative_gain_arrayMethod
relative_gain_array(G, w::AbstractVector)
-relative_gain_array(G, w::Number)

Calculate the relative gain array of G at frequencies w. G(iω) .* pinv(tranpose(G(iω)))

The RGA can be used to find input-output pairings for MIMO control using individually tuned loops. Pair the inputs and outputs such that the RGA(ωc) at the crossover frequency becomes as close to diagonal as possible. Avoid pairings such that RGA(0) contains negative diagonal elements.

  • The sum of the absolute values of the entries in the RGA is a good measure of the "true condition number" of G, the best condition number that can be achieved by input/output scaling of G, -Glad, Ljung.
  • The RGA is invariant to input/output scaling of G.
  • If the RGA contains large entries, the system may be sensitive to model errors, -Skogestad, "Multivariable Feedback Control: Analysis and Design":
    • Uncertainty in the input channels (diagonal input uncertainty). Plants with
    large RGA-elements around the crossover frequency are fundamentally difficult to control because of sensitivity to input uncertainty (e.g. caused by uncertain or neglected actuator dynamics). In particular, decouplers or other inverse-based controllers should not be used for plants with large RGAeleme
    • Element uncertainty. Large RGA-elements imply sensitivity to element-by-element uncertainty.
    However, this kind of uncertainty may not occur in practice due to physical couplings between the transfer function elements. Therefore, diagonal input uncertainty (which is always present) is usually of more concern for plants with large RGA elements.

The relative gain array is computed using the The unit-consistent (UC) generalized inverse Reference: "On the Relative Gain Array (RGA) with Singular and Rectangular Matrices" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf

source
ControlSystemsBase.tzerosMethod
tzeros(sys)

Compute the invariant zeros of the system sys. If sys is a minimal realization, these are also the transmission zeros.

source
ControlSystemsBase.zpkdataMethod
z, p, k = zpkdata(sys)

Compute the zeros, poles, and gains of system sys.

Returns

  • z : Matrix{Vector{ComplexF64}}, (ny × nu)
  • p : Matrix{Vector{ComplexF64}}, (ny × nu)
  • k : Matrix{Float64}, (ny × nu)
source
ControlSystemsBase.areMethod
are(::Continuous, A, B, Q, R)

Compute 'X', the solution to the continuous-time algebraic Riccati equation, defined as A'X + XA - (XB)R^-1(B'X) + Q = 0, where R is non-singular.

In an LQR problem, Q is associated with the state penalty $x'Qx$ while R is associated with the control penalty $u'Ru$. See lqr for more details.

Uses MatrixEquations.arec. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.arec, note that they define the input arguments in a different order.

source
ControlSystemsBase.areMethod
are(::Discrete, A, B, Q, R; kwargs...)

Compute X, the solution to the discrete-time algebraic Riccati equation, defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where Q>=0 and R>0

In an LQR problem, Q is associated with the state penalty $x'Qx$ while R is associated with the control penalty $u'Ru$. See lqr for more details.

Uses MatrixEquations.ared. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.ared, note that they define the input arguments in a different order.

source
ControlSystemsBase.balanceFunction
S, P, B = balance(A[, perm=true])

Compute a similarity transform T = S*P resulting in B = T\A*T such that the row and column norms of B are approximately equivalent. If perm=false, the transformation will only scale A using diagonal S, and not permute A (i.e., set P=I).

source
ControlSystemsBase.balrealMethod

sysr, G, T = balreal(sys::StateSpace)

Calculates a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal diagm(G). T is the similarity transform between the old state x and the new state z such that z = Tx.

See also gram, baltrunc.

Reference: Varga A., Balancing-free square-root algorithm for computing singular perturbation approximations.

source
ControlSystemsBase.baltruncMethod
sysr, G, T = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, n = nothing, residual = false)

Reduces the state dimension by calculating a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal diagm(G), and truncating it to order n. If n is not provided, it's chosen such that all states corresponding to singular values less than atol and less that rtol σmax are removed.

T is the projection matrix between the old state x and the newstate z such that z = Tx. T will in general be a non-square matrix.

If residual = true, matched static gain is achieved through "residualization", i.e., setting

\[0 = A_{21}x_{1} + A_{22}x_{2} + B_{2}u\]

where indices 1/2 correspond to the remaining/truncated states respectively.

See also gram, balreal

Glad, Ljung, Reglerteori: Flervariabla och Olinjära metoder.

For more advanced model reduction, see RobustAndOptimalControl.jl - Model Reduction.

Extended help

Note: Gramian computations are sensitive to input-output scaling. For the result of a numerical balancing, gramian computation or truncation of MIMO systems to be meaningful, the inputs and outputs of the system must thus be scaled in a meaningful way. A common (but not the only) approach is:

  • The outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.
  • The input variables are scaled to have magnitude one. This is done by dividing each variable by its maximum expected or allowed change, i.e., $u_{scaled} = u / u_{max}$

Without such scaling, the result of balancing will depend on the units used to measure the input and output signals, e.g., a change of unit for one output from meter to millimeter will make this output 1000x more important.

source
ControlSystemsBase.controllabilityMethod
controllability(A, B; atol, rtol)
-controllability(sys; atol, rtol)

Check for controllability of the pair (A, B) or sys using the PHB test.

The return value contains the field iscontrollable which is true if the rank condition is met at all eigenvalues of A, and false otherwise. The returned structure also contains the rank and smallest singular value at each individual eigenvalue of A in the fields ranks and sigma_min.

Technically, this function checks for controllability from the origin, also called reachability.

source
ControlSystemsBase.covarMethod
P = covar(sys, W)

Calculate the stationary covariance P = E[y(t)y(t)'] of the output y of a StateSpace model sys driven by white Gaussian noise w with covariance E[w(t)w(τ)]=W*δ(t-τ) (δ is the Dirac delta).

Remark: If sys is unstable then the resulting covariance is a matrix of Infs. Entries corresponding to direct feedthrough (DWD' .!= 0) will equal Inf for continuous-time systems.

source
ControlSystemsBase.ctrbMethod
ctrb(A, B)
-ctrb(sys)

Compute the controllability matrix for the system described by (A, B) or sys.

Note that checking for controllability by computing the rank from ctrb is not the most numerically accurate way, a better method is checking if gram(sys, :c) is positive definite or to call the function controllability.

source
ControlSystemsBase.gramMethod
gram(sys, opt; kwargs...)

Compute the grammian of system sys. If opt is :c, computes the controllability grammian. If opt is :o, computes the observability grammian.

See also grampd For keyword arguments, see grampd.

Extended help

Note: Gramian computations are sensitive to input-output scaling. For the result of a numerical balancing, gramian computation or truncation of MIMO systems to be meaningful, the inputs and outputs of the system must thus be scaled in a meaningful way. A common (but not the only) approach is:

  • The outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.
  • The input variables are scaled to have magnitude one. This is done by dividing each variable by its maximum expected or allowed change, i.e., $u_{scaled} = u / u_{max}$

Without such scaling, the result of balancing will depend on the units used to measure the input and output signals, e.g., a change of unit for one output from meter to millimeter will make this output 1000x more important.

source
ControlSystemsBase.grampdMethod
U = grampd(sys, opt; kwargs...)

Return a Cholesky factor U of the grammian of system sys. If opt is :c, computes the controllability grammian G = U*U'. If opt is :o, computes the observability grammian G = U'U.

Obtain a Cholesky object by Cholesky(U) for observability grammian

Uses MatrixEquations.plyapc/plyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.plyapc/plyapd

source
ControlSystemsBase.hinfnormMethod
Ninf, ω_peak = hinfnorm(sys; tol=1e-6)

Compute the H∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.

Ninf := sup_ω σ_max[sys(iω)] if G is stable (σ_max = largest singular value) := Inf' ifG` is unstable

tol is an optional keyword argument for the desired relative accuracy for the computed H∞ norm (not an absolute certificate).

sys is first converted to a state space model if needed.

The continuous-time L∞ norm computation implements the 'two-step algorithm' in:
N.A. Bruinsma and M. Steinbuch, 'A fast algorithm to compute the H∞-norm of a transfer function matrix', Systems and Control Letters (1990), pp. 287-293.

For the discrete-time version, see:
P. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.

See also linfnorm.

source
ControlSystemsBase.innovation_formMethod
sysi = innovation_form(sys, R1, R2[, R12])
+Analysis · ControlSystems.jl

For robust analysis, see RobustAndOptimalControl.jl.

Analysis

ControlSystemsBase.dampMethod
Wn, zeta, ps = damp(sys)

Compute the natural frequencies, Wn, and damping ratios, zeta, of the poles, ps, of sys

source
ControlSystemsBase.dcgainFunction
dcgain(sys, ϵ=0)

Compute the dcgain of system sys.

equal to G(0) for continuous-time systems and G(1) for discrete-time systems.

ϵ can be provided to evaluate the dcgain with a small perturbation into the stability region of the complex plane.

source
ControlSystemsBase.delaymarginMethod
dₘ = delaymargin(G::LTISystem)

Return the delay margin, dₘ. For discrete-time systems, the delay margin is normalized by the sample time, i.e., the value represents the margin in number of sample times. Only supports SISO systems.

source
ControlSystemsBase.gangoffourMethod
S, PS, CS, T = gangoffour(P, C; minimal=true)
+gangoffour(P::AbstractVector, C::AbstractVector; minimal=true)

Given a transfer function describing the plant P and a transfer function describing the controller C, computes the four transfer functions in the Gang-of-Four.

  • S = 1/(1+PC) Sensitivity function
  • PS = (1+PC)\P Load disturbance to measurement signal
  • CS = (1+PC)\C Measurement noise to control signal
  • T = PC/(1+PC) Complementary sensitivity function

If minimal=true, minreal will be applied to all transfer functions.

source
ControlSystemsBase.gangofsevenMethod
S, PS, CS, T, RY, RU, RE = gangofseven(P,C,F)

Given transfer functions describing the Plant P, the controller C and a feed forward block F, computes the four transfer functions in the Gang-of-Four and the transferfunctions corresponding to the feed forward.

  • S = 1/(1+PC) Sensitivity function
  • PS = P/(1+PC)
  • CS = C/(1+PC)
  • T = PC/(1+PC) Complementary sensitivity function
  • RY = PCF/(1+PC)
  • RU = CF/(1+P*C)
  • RE = F/(1+P*C)
source
ControlSystemsBase.markovparamMethod
markovparam(sys, n)

Compute the nth markov parameter of discrete-time state-space system sys. This is defined as the following:

h(0) = D

h(n) = C*A^(n-1)*B

source
ControlSystemsBase.polesMethod
poles(sys)

Compute the poles of system sys.

Note: Poles with multiplicity n > 1 may suffer numerical inaccuracies on the order eps(numeric_type(sys))^(1/n), i.e., a double pole in a system with Float64 coefficients may be computed with an error of about √(eps(Float64)) ≈ 1.5e-8.

source
ControlSystemsBase.reduce_sysMethod
reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)

Implements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed A, B, C, D matrices. These are empty if there are no zeros.

source
ControlSystemsBase.relative_gain_arrayMethod
relative_gain_array(A::AbstractMatrix; tol = 1.0e-15)

Reference: "On the Relative Gain Array (RGA) with Singular and Rectangular Matrices" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf

source
ControlSystemsBase.relative_gain_arrayMethod
relative_gain_array(G, w::AbstractVector)
+relative_gain_array(G, w::Number)

Calculate the relative gain array of G at frequencies w. G(iω) .* pinv(tranpose(G(iω)))

The RGA can be used to find input-output pairings for MIMO control using individually tuned loops. Pair the inputs and outputs such that the RGA(ωc) at the crossover frequency becomes as close to diagonal as possible. Avoid pairings such that RGA(0) contains negative diagonal elements.

  • The sum of the absolute values of the entries in the RGA is a good measure of the "true condition number" of G, the best condition number that can be achieved by input/output scaling of G, -Glad, Ljung.
  • The RGA is invariant to input/output scaling of G.
  • If the RGA contains large entries, the system may be sensitive to model errors, -Skogestad, "Multivariable Feedback Control: Analysis and Design":
    • Uncertainty in the input channels (diagonal input uncertainty). Plants with
    large RGA-elements around the crossover frequency are fundamentally difficult to control because of sensitivity to input uncertainty (e.g. caused by uncertain or neglected actuator dynamics). In particular, decouplers or other inverse-based controllers should not be used for plants with large RGAeleme
    • Element uncertainty. Large RGA-elements imply sensitivity to element-by-element uncertainty.
    However, this kind of uncertainty may not occur in practice due to physical couplings between the transfer function elements. Therefore, diagonal input uncertainty (which is always present) is usually of more concern for plants with large RGA elements.

The relative gain array is computed using the The unit-consistent (UC) generalized inverse Reference: "On the Relative Gain Array (RGA) with Singular and Rectangular Matrices" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf

source
ControlSystemsBase.tzerosMethod
tzeros(sys)

Compute the invariant zeros of the system sys. If sys is a minimal realization, these are also the transmission zeros.

source
ControlSystemsBase.zpkdataMethod
z, p, k = zpkdata(sys)

Compute the zeros, poles, and gains of system sys.

Returns

  • z : Matrix{Vector{ComplexF64}}, (ny × nu)
  • p : Matrix{Vector{ComplexF64}}, (ny × nu)
  • k : Matrix{Float64}, (ny × nu)
source
ControlSystemsBase.areMethod
are(::Continuous, A, B, Q, R)

Compute 'X', the solution to the continuous-time algebraic Riccati equation, defined as A'X + XA - (XB)R^-1(B'X) + Q = 0, where R is non-singular.

In an LQR problem, Q is associated with the state penalty $x'Qx$ while R is associated with the control penalty $u'Ru$. See lqr for more details.

Uses MatrixEquations.arec. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.arec, note that they define the input arguments in a different order.

source
ControlSystemsBase.areMethod
are(::Discrete, A, B, Q, R; kwargs...)

Compute X, the solution to the discrete-time algebraic Riccati equation, defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where Q>=0 and R>0

In an LQR problem, Q is associated with the state penalty $x'Qx$ while R is associated with the control penalty $u'Ru$. See lqr for more details.

Uses MatrixEquations.ared. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.ared, note that they define the input arguments in a different order.

source
ControlSystemsBase.balanceFunction
S, P, B = balance(A[, perm=true])

Compute a similarity transform T = S*P resulting in B = T\A*T such that the row and column norms of B are approximately equivalent. If perm=false, the transformation will only scale A using diagonal S, and not permute A (i.e., set P=I).

source
ControlSystemsBase.balrealMethod

sysr, G, T = balreal(sys::StateSpace)

Calculates a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal diagm(G). T is the similarity transform between the old state x and the new state z such that z = Tx.

See also gram, baltrunc.

Reference: Varga A., Balancing-free square-root algorithm for computing singular perturbation approximations.

source
ControlSystemsBase.baltruncMethod
sysr, G, T = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, n = nothing, residual = false)

Reduces the state dimension by calculating a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal diagm(G), and truncating it to order n. If n is not provided, it's chosen such that all states corresponding to singular values less than atol and less that rtol σmax are removed.

T is the projection matrix between the old state x and the newstate z such that z = Tx. T will in general be a non-square matrix.

If residual = true, matched static gain is achieved through "residualization", i.e., setting

\[0 = A_{21}x_{1} + A_{22}x_{2} + B_{2}u\]

where indices 1/2 correspond to the remaining/truncated states respectively.

See also gram, balreal

Glad, Ljung, Reglerteori: Flervariabla och Olinjära metoder.

For more advanced model reduction, see RobustAndOptimalControl.jl - Model Reduction.

Extended help

Note: Gramian computations are sensitive to input-output scaling. For the result of a numerical balancing, gramian computation or truncation of MIMO systems to be meaningful, the inputs and outputs of the system must thus be scaled in a meaningful way. A common (but not the only) approach is:

  • The outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.
  • The input variables are scaled to have magnitude one. This is done by dividing each variable by its maximum expected or allowed change, i.e., $u_{scaled} = u / u_{max}$

Without such scaling, the result of balancing will depend on the units used to measure the input and output signals, e.g., a change of unit for one output from meter to millimeter will make this output 1000x more important.

source
ControlSystemsBase.controllabilityMethod
controllability(A, B; atol, rtol)
+controllability(sys; atol, rtol)

Check for controllability of the pair (A, B) or sys using the PHB test.

The return value contains the field iscontrollable which is true if the rank condition is met at all eigenvalues of A, and false otherwise. The returned structure also contains the rank and smallest singular value at each individual eigenvalue of A in the fields ranks and sigma_min.

Technically, this function checks for controllability from the origin, also called reachability.

source
ControlSystemsBase.covarMethod
P = covar(sys, W)

Calculate the stationary covariance P = E[y(t)y(t)'] of the output y of a StateSpace model sys driven by white Gaussian noise w with covariance E[w(t)w(τ)]=W*δ(t-τ) (δ is the Dirac delta).

Remark: If sys is unstable then the resulting covariance is a matrix of Infs. Entries corresponding to direct feedthrough (DWD' .!= 0) will equal Inf for continuous-time systems.

source
ControlSystemsBase.ctrbMethod
ctrb(A, B)
+ctrb(sys)

Compute the controllability matrix for the system described by (A, B) or sys.

Note that checking for controllability by computing the rank from ctrb is not the most numerically accurate way, a better method is checking if gram(sys, :c) is positive definite or to call the function controllability.

source
ControlSystemsBase.gramMethod
gram(sys, opt; kwargs...)

Compute the grammian of system sys. If opt is :c, computes the controllability grammian. If opt is :o, computes the observability grammian.

See also grampd For keyword arguments, see grampd.

Extended help

Note: Gramian computations are sensitive to input-output scaling. For the result of a numerical balancing, gramian computation or truncation of MIMO systems to be meaningful, the inputs and outputs of the system must thus be scaled in a meaningful way. A common (but not the only) approach is:

  • The outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.
  • The input variables are scaled to have magnitude one. This is done by dividing each variable by its maximum expected or allowed change, i.e., $u_{scaled} = u / u_{max}$

Without such scaling, the result of balancing will depend on the units used to measure the input and output signals, e.g., a change of unit for one output from meter to millimeter will make this output 1000x more important.

source
ControlSystemsBase.grampdMethod
U = grampd(sys, opt; kwargs...)

Return a Cholesky factor U of the grammian of system sys. If opt is :c, computes the controllability grammian G = U*U'. If opt is :o, computes the observability grammian G = U'U.

Obtain a Cholesky object by Cholesky(U) for observability grammian

Uses MatrixEquations.plyapc/plyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.plyapc/plyapd

source
ControlSystemsBase.hinfnormMethod
Ninf, ω_peak = hinfnorm(sys; tol=1e-6)

Compute the H∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.

Ninf := sup_ω σ_max[sys(iω)] if G is stable (σ_max = largest singular value) := Inf' ifG` is unstable

tol is an optional keyword argument for the desired relative accuracy for the computed H∞ norm (not an absolute certificate).

sys is first converted to a state space model if needed.

The continuous-time L∞ norm computation implements the 'two-step algorithm' in:
N.A. Bruinsma and M. Steinbuch, 'A fast algorithm to compute the H∞-norm of a transfer function matrix', Systems and Control Letters (1990), pp. 287-293.

For the discrete-time version, see:
P. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.

See also linfnorm.

source
ControlSystemsBase.innovation_formMethod
sysi = innovation_form(sys, R1, R2[, R12])
 sysi = innovation_form(sys; sysw=I, syse=I, R1=I, R2=I)

Takes a system

x' = Ax + Bu + w ~ R1
 y  = Cx + Du + e ~ R2

and returns the system

x' = Ax + Kv
-y  = Cx + v

where v is the innovation sequence.

If sysw (syse) is given, the covariance resulting in filtering noise with R1 (R2) through sysw (syse) is used as covariance.

See Stochastic Control, Chapter 4, Åström

source
ControlSystemsBase.innovation_formMethod
sysi = innovation_form(sys, K)

Takes a system

x' = Ax + Bu + Kv
+y  = Cx + v

where v is the innovation sequence.

If sysw (syse) is given, the covariance resulting in filtering noise with R1 (R2) through sysw (syse) is used as covariance.

See Stochastic Control, Chapter 4, Åström

source
ControlSystemsBase.innovation_formMethod
sysi = innovation_form(sys, K)

Takes a system

x' = Ax + Bu + Kv
 y  = Cx + Du + v

and returns the system

x' = Ax + Kv
-y  = Cx + v

where v is the innovation sequence.

See Stochastic Control, Chapter 4, Åström

source
ControlSystemsBase.linfnormMethod
Ninf, ω_peak = linfnorm(sys; tol=1e-6)

Compute the L∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.

Ninf := sup_ω σ_max[sys(iω)] (σ_max denotes the largest singular value)

tol is an optional keyword argument representing the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).

sys is first converted to a state space model if needed.

The continuous-time L∞ norm computation implements the 'two-step algorithm' in:
N.A. Bruinsma and M. Steinbuch, 'A fast algorithm to compute the H∞-norm of a transfer function matrix', Systems and Control Letters (1990), pp. 287-293.

For the discrete-time version, see:
P. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.

See also hinfnorm.

source
ControlSystemsBase.observabilityMethod
observability(A, C; atol, rtol)

Check for observability of the pair (A, C) or sys using the PHB test.

The return value contains the field isobservable which is true if the rank condition is met at all eigenvalues of A, and false otherwise. The returned structure also contains the rank and smallest singular value at each individual eigenvalue of A in the fields ranks and sigma_min.

source
ControlSystemsBase.observer_controllerMethod
cont = observer_controller(sys, L::AbstractMatrix, K::AbstractMatrix; direct=false)

If direct = false

Return the observer_controller cont that is given by ss(A - B*L - K*C + K*D*L, K, L, 0) such that feedback(sys, cont) produces a closed-loop system with eigenvalues given by A-KC and A-BL.

This controller does not have a direct term, and corresponds to state feedback operating on state estimated by observer_predictor. Use this form if the computed control signal is applied at the next sampling instant, or with an otherwise large delay in relation to the measurement fed into the controller.

Ref: "Computer-Controlled Systems" Eq 4.37

If direct = true

Return the observer controller cont that is given by ss((I-KC)(A-BL), (I-KC)(A-BL)K, L, LK) such that feedback(sys, cont) produces a closed-loop system with eigenvalues given by A-BL and A-BL-KC. This controller has a direct term, and corresponds to state feedback operating on state estimated by observer_filter. Use this form if the computed control signal is applied immediately after receiveing a measurement. This version typically has better performance than the one without a direct term.

Note

To use this formulation, the observer gain K should have been designed for the pair (A, CA) rather than (A, C). To do this, pass direct = true when calling place or kalman.

Ref: Ref: "Computer-Controlled Systems" pp 140 and "Computer-Controlled Systems" pp 162 prob 4.7

Arguments:

  • sys: Model of system
  • L: State-feedback gain u = -Lx
  • K: Observer gain

See also observer_predictor and innovation_form.

source

where v is the innovation sequence.

See Stochastic Control, Chapter 4, Åström

source
ControlSystemsBase.linfnormMethod
Ninf, ω_peak = linfnorm(sys; tol=1e-6)

Compute the L∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.

Ninf := sup_ω σ_max[sys(iω)] (σ_max denotes the largest singular value)

tol is an optional keyword argument representing the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).

sys is first converted to a state space model if needed.

The continuous-time L∞ norm computation implements the 'two-step algorithm' in:
N.A. Bruinsma and M. Steinbuch, 'A fast algorithm to compute the H∞-norm of a transfer function matrix', Systems and Control Letters (1990), pp. 287-293.

For the discrete-time version, see:
P. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.

See also hinfnorm.

source
ControlSystemsBase.observabilityMethod
observability(A, C; atol, rtol)

Check for observability of the pair (A, C) or sys using the PHB test.

The return value contains the field isobservable which is true if the rank condition is met at all eigenvalues of A, and false otherwise. The returned structure also contains the rank and smallest singular value at each individual eigenvalue of A in the fields ranks and sigma_min.

source
ControlSystemsBase.observer_controllerMethod
cont = observer_controller(sys, L::AbstractMatrix, K::AbstractMatrix; direct=false)

If direct = false

Return the observer_controller cont that is given by ss(A - B*L - K*C + K*D*L, K, L, 0) such that feedback(sys, cont) produces a closed-loop system with eigenvalues given by A-KC and A-BL.

This controller does not have a direct term, and corresponds to state feedback operating on state estimated by observer_predictor. Use this form if the computed control signal is applied at the next sampling instant, or with an otherwise large delay in relation to the measurement fed into the controller.

Ref: "Computer-Controlled Systems" Eq 4.37

If direct = true

Return the observer controller cont that is given by ss((I-KC)(A-BL), (I-KC)(A-BL)K, L, LK) such that feedback(sys, cont) produces a closed-loop system with eigenvalues given by A-BL and A-BL-KC. This controller has a direct term, and corresponds to state feedback operating on state estimated by observer_filter. Use this form if the computed control signal is applied immediately after receiveing a measurement. This version typically has better performance than the one without a direct term.

Note

To use this formulation, the observer gain K should have been designed for the pair (A, CA) rather than (A, C). To do this, pass direct = true when calling place or kalman.

Ref: Ref: "Computer-Controlled Systems" pp 140 and "Computer-Controlled Systems" pp 162 prob 4.7

Arguments:

  • sys: Model of system
  • L: State-feedback gain u = -Lx
  • K: Observer gain

See also observer_predictor and innovation_form.

source
ControlSystemsBase.observer_filterMethod
observer_filter(sys, K; output_state = false)

Return the observer filter

\[\begin{aligned} x̂(k|k) &= (I - KC)Ax̂(k-1|k-1) + (I - KC)Bu(k-1) + Ky(k) \\ -\end{aligned}\]

with the input equation [(I - KC)B K] * [u(k-1); y(k)].

Note the time indices in the equations, the filter assumes that the user passes the current $y(k)$, but the past $u(k-1)$, that is, this filter is used to estimate the state before the current control input has been applied. This causes a state-feedback controller acting on the estimate produced by this observer to have a direct term.

This is similar to observer_predictor, but in contrast to the predictor, the filter output depends on the current measurement, whereas the predictor output only depend on past measurements.

The observer filter is equivalent to the observer_predictor for continuous-time systems.

Note

To use this formulation, the observer gain K should have been designed for the pair (A, CA) rather than (A, C). To do this, pass direct = true when calling place or kalman.

Ref: "Computer-Controlled Systems" Eq 4.32

source
ControlSystemsBase.observer_predictorMethod
observer_predictor(sys::AbstractStateSpace, K; h::Int = 1, output_state = false)
+\end{aligned}\]

with the input equation [(I - KC)B K] * [u(k-1); y(k)].

Note the time indices in the equations, the filter assumes that the user passes the current $y(k)$, but the past $u(k-1)$, that is, this filter is used to estimate the state before the current control input has been applied. This causes a state-feedback controller acting on the estimate produced by this observer to have a direct term.

This is similar to observer_predictor, but in contrast to the predictor, the filter output depends on the current measurement, whereas the predictor output only depend on past measurements.

The observer filter is equivalent to the observer_predictor for continuous-time systems.

Note

To use this formulation, the observer gain K should have been designed for the pair (A, CA) rather than (A, C). To do this, pass direct = true when calling place or kalman.

Ref: "Computer-Controlled Systems" Eq 4.32

source
ControlSystemsBase.observer_predictorMethod
observer_predictor(sys::AbstractStateSpace, K; h::Int = 1, output_state = false)
 observer_predictor(sys::AbstractStateSpace, R1, R2[, R12]; output_state = false)

If sys is continuous, return the observer predictor system

\[\begin{aligned} x̂' &= (A - KC)x̂ + (B-KD)u + Ky \\ ŷ &= Cx + Du -\end{aligned}\]

with the input equation [B-KD K] * [u; y]

If sys is discrete, the prediction horizon h may be specified, in which case measurements up to and including time t-h and inputs up to and including time t are used to predict y(t).

If covariance matrices R1, R2 are given, the kalman gain K is calculated using kalman.

If output_state is true, the output is the state estimate instead of the output estimate .

See also innovation_form, observer_controller and observer_filter.

source
ControlSystemsBase.obsvFunction
obsv(A, C, n=size(A,1))
-obsv(sys, n=sys.nx)

Compute the observability matrix with n rows for the system described by (A, C) or sys. Providing the optional n > sys.nx returns an extended observability matrix.

Note that checking for observability by computing the rank from obsv is not the most numerically accurate way, a better method is checking if gram(sys, :o) is positive definite or to call the function observability.

source
ControlSystemsBase.plyapMethod
Xc = plyap(sys::AbstractStateSpace, Ql; kwargs...)

Lyapunov solver that takes the L Cholesky factor of Q and returns a triangular matrix Xc such that Xc*Xc' = X.

source
ControlSystemsBase.similarity_transformMethod
syst = similarity_transform(sys, T; unitary=false)

Perform a similarity transform T : Tx̃ = x on sys such that

à = T⁻¹AT
+\end{aligned}\]

with the input equation [B-KD K] * [u; y]

If sys is discrete, the prediction horizon h may be specified, in which case measurements up to and including time t-h and inputs up to and including time t are used to predict y(t).

If covariance matrices R1, R2 are given, the kalman gain K is calculated using kalman.

If output_state is true, the output is the state estimate instead of the output estimate .

See also innovation_form, observer_controller and observer_filter.

source
ControlSystemsBase.obsvFunction
obsv(A, C, n=size(A,1))
+obsv(sys, n=sys.nx)

Compute the observability matrix with n rows for the system described by (A, C) or sys. Providing the optional n > sys.nx returns an extended observability matrix.

Note that checking for observability by computing the rank from obsv is not the most numerically accurate way, a better method is checking if gram(sys, :o) is positive definite or to call the function observability.

source
ControlSystemsBase.plyapMethod
Xc = plyap(sys::AbstractStateSpace, Ql; kwargs...)

Lyapunov solver that takes the L Cholesky factor of Q and returns a triangular matrix Xc such that Xc*Xc' = X.

source
ControlSystemsBase.similarity_transformMethod
syst = similarity_transform(sys, T; unitary=false)

Perform a similarity transform T : Tx̃ = x on sys such that

à = T⁻¹AT
 B̃ = T⁻¹ B
 C̃ = CT
-D̃ = D

If unitary=true, T is assumed unitary and the matrix adjoint is used instead of the inverse. See also balance_statespace.

source
ControlSystemsBase.stab_unstabMethod
stab, unstab, sep = stab_unstab(sys; kwargs...)

Decompose sys into sys = stab + unstab where stab contains all stable poles and unstab contains unstable poles.

0 ≤ sep ≤ 1 is the estimated separation between the stable and unstable spectra.

The docstring of MatrixPencils.ssblkdiag, reproduced below, provides more information on the keyword arguments: Base.Docs.DocStr(svec(" ssblkdiag(A, B, C; smarg, disc = false, stableunstable = false, withQ = true, withZ = true) -> (At, Bt, Ct, Q, Z, blkdims, sep)\n\nReduce the regular matrix pencil A - λI to an equivalent block diagonal triangular form At - λI = Q*(A - λI)*Z \nusing the transformation matrices Q and Z, where Q = inv(Z), such that the transformed matrix At have \nseparated stable and unstable eigenvalues with respect to a stability domain Cs defined by the \nstability margin parameter smarg and the stability type parameter disc. \nIf disc = false, Cs is the set of complex numbers with real parts less than smarg, \nwhile if disc = true, Cs is the set of complex numbers with moduli less than smarg (i.e., the interior of a disc \nof radius smarg centered in the origin). If smarg = missing, the default value used is smarg = 0, if disc = false,\nand smarg = 1, if disc = true.\nThe matrix At results in the following block diagonal form\n \n At = | A1 0 |\n | 0 A2 |\n \nwhere the n1 x n1 matrix A1 and the n2 x n2 matrix A2 are in Schur form. \nThe matrix A1 has unstable eigenvalues and A2 has stable eigenvalues if `stableunstable = false,\nwhileA1has stable eigenvalues andA2has unstable eigenvalues ifstable_unstable = true.\nThe dimensions of the diagonal blocks are returned inblkdims = (n1, n2). \nIfwithQ = true,Qcontains the left transformation matrix. IfwithQ = false,Qis set tonothing. \nIfwithZ = true,Zcontains the right transformation matrix. IfwithZ = false,Zis set tonothing. \nBt = QB, unlessB = missing, in which caseBt = missingis returned, andCt = CZ, \nunlessC = missing, in which caseCt = missingis returned . \nAn estimation of the separation of the spectra of the two underlying diagonal blocks is returned insep, \nwhere0 ≤ sep ≤ 1. A valuesep ≈ 0indicates thatA1andA2` have some almost equal eigenvalues. \n"), nothing, Dict{Symbol, Any}(:typesig => Union{Tuple{T}, Tuple{AbstractMatrix{T}, Union{Missing, AbstractMatrix{T}}, Union{Missing, AbstractMatrix{T}}}} where T<:Union{Float32, Float64, ComplexF64, ComplexF32}, :module => MatrixPencils, :linenumber => 657, :binding => MatrixPencils.ssblkdiag, :path => "/home/runner/.julia/packages/MatrixPencils/DIfOZ/src/gsep.jl"))

source
ControlSystemsBase.stab_unstabMethod
stab, unstab, sep = stab_unstab(sys; kwargs...)

Decompose sys into sys = stab + unstab where stab contains all stable poles and unstab contains unstable poles.

0 ≤ sep ≤ 1 is the estimated separation between the stable and unstable spectra.

The docstring of MatrixPencils.ssblkdiag, reproduced below, provides more information on the keyword arguments: Base.Docs.DocStr(svec(" ssblkdiag(A, B, C; smarg, disc = false, stableunstable = false, withQ = true, withZ = true) -> (At, Bt, Ct, Q, Z, blkdims, sep)\n\nReduce the regular matrix pencil A - λI to an equivalent block diagonal triangular form At - λI = Q*(A - λI)*Z \nusing the transformation matrices Q and Z, where Q = inv(Z), such that the transformed matrix At have \nseparated stable and unstable eigenvalues with respect to a stability domain Cs defined by the \nstability margin parameter smarg and the stability type parameter disc. \nIf disc = false, Cs is the set of complex numbers with real parts less than smarg, \nwhile if disc = true, Cs is the set of complex numbers with moduli less than smarg (i.e., the interior of a disc \nof radius smarg centered in the origin). If smarg = missing, the default value used is smarg = 0, if disc = false,\nand smarg = 1, if disc = true.\nThe matrix At results in the following block diagonal form\n \n At = | A1 0 |\n | 0 A2 |\n \nwhere the n1 x n1 matrix A1 and the n2 x n2 matrix A2 are in Schur form. \nThe matrix A1 has unstable eigenvalues and A2 has stable eigenvalues if `stableunstable = false,\nwhileA1has stable eigenvalues andA2has unstable eigenvalues ifstable_unstable = true.\nThe dimensions of the diagonal blocks are returned inblkdims = (n1, n2). \nIfwithQ = true,Qcontains the left transformation matrix. IfwithQ = false,Qis set tonothing. \nIfwithZ = true,Zcontains the right transformation matrix. IfwithZ = false,Zis set tonothing. \nBt = QB, unlessB = missing, in which caseBt = missingis returned, andCt = CZ, \nunlessC = missing, in which caseCt = missingis returned . \nAn estimation of the separation of the spectra of the two underlying diagonal blocks is returned insep, \nwhere0 ≤ sep ≤ 1. A valuesep ≈ 0indicates thatA1andA2` have some almost equal eigenvalues. \n"), nothing, Dict{Symbol, Any}(:typesig => Union{Tuple{T}, Tuple{AbstractMatrix{T}, Union{Missing, AbstractMatrix{T}}, Union{Missing, AbstractMatrix{T}}}} where T<:Union{Float32, Float64, ComplexF64, ComplexF32}, :module => MatrixPencils, :linenumber => 657, :binding => MatrixPencils.ssblkdiag, :path => "/home/runner/.julia/packages/MatrixPencils/DIfOZ/src/gsep.jl"))

source
ControlSystemsBase.time_scaleMethod
time_scale(sys::AbstractStateSpace{Continuous}, a; balanced = false)
 time_scale(G::TransferFunction{Continuous},     a; balanced = true)

Rescale the time axis (change time unit) of sys.

For systems where the dominant time constants are very far from 1, e.g., in electronics, rescaling the time axis may be beneficial for numerical performance, in particular for continuous-time simulations.

Scaling of time for a function $f(t)$ with Laplace transform $F(s)$ can be stated as

\[f(at) \leftrightarrow \dfrac{1}{a} F\big(\dfrac{s}{a}\big)\]

The keyword argument balanced indicates whether or not to apply a balanced scaling on the B and C matrices. For statespace systems, this defaults to false since it changes the state representation, only B will be scaled. For transfer functions, it defaults to true.

Example:

The following example show how a system with a time constant on the order of one micro-second is rescaled such that the time constant becomes 1, i.e., the time unit is changed from seconds to micro-seconds.

Gs  = tf(1, [1e-6, 1])     # micro-second time scale modeled in seconds
 Gms = time_scale(Gs, 1e-6) # Change to micro-second time scale
 Gms == tf(1, [1, 1])       # Gms now has micro-seconds as time unit

The next example illustrates how the time axis of a time-domain simulation changes by time scaling

t = 0:0.1:50 # original time axis
@@ -27,5 +27,5 @@
 res1 = step(sys1, t)      # Perform original simulation
 sys2 = time_scale(sys, a) # Scale time
 res2 = step(sys2, t ./ a) # Simulate on scaled time axis, note the `1/a`
-isapprox(res1.y, res2.y, rtol=1e-3, atol=1e-3)
source
LinearAlgebra.lyapMethod
lyap(A, Q; kwargs...)

Compute the solution X to the discrete Lyapunov equation AXA' - X + Q = 0.

Uses MatrixEquations.lyapc / MatrixEquations.lyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.lyapc / ControlSystemsBase.MatrixEquations.lyapd

source
LinearAlgebra.normFunction
norm(sys, p=2; tol=1e-6)

norm(sys) or norm(sys,2) computes the H2 norm of the LTI system sys.

norm(sys, Inf) computes the H∞ norm of the LTI system sys. The H∞ norm is the same as the L∞ for stable systems, and Inf for unstable systems. If the peak gain frequency is required as well, use the function hinfnorm instead. See hinfnorm for further documentation.

tol is an optional keyword argument, used only for the computation of L∞ norms. It represents the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).

sys is first converted to a StateSpace model if needed.

source
ControlSystemsBase.balance_statespaceFunction
A, B, C, T = balance_statespace{S}(A::Matrix{S}, B::Matrix{S}, C::Matrix{S}, perm::Bool=false)
-sys, T = balance_statespace(sys::StateSpace, perm::Bool=false)

Computes a balancing transformation T that attempts to scale the system so that the row and column norms of [TA/T TB; C/T 0] are approximately equal. If perm=true, the states in A are allowed to be reordered.

The inverse of sysb, T = balance_statespace(sys) is given by similarity_transform(sysb, T)

This is not the same as finding a balanced realization with equal and diagonal observability and reachability gramians, see balreal

source

Videos

Basic usage of robustness analysis with JuliaControl

+isapprox(res1.y, res2.y, rtol=1e-3, atol=1e-3)source
LinearAlgebra.lyapMethod
lyap(A, Q; kwargs...)

Compute the solution X to the discrete Lyapunov equation AXA' - X + Q = 0.

Uses MatrixEquations.lyapc / MatrixEquations.lyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.lyapc / ControlSystemsBase.MatrixEquations.lyapd

source
LinearAlgebra.normFunction
norm(sys, p=2; tol=1e-6)

norm(sys) or norm(sys,2) computes the H2 norm of the LTI system sys.

norm(sys, Inf) computes the H∞ norm of the LTI system sys. The H∞ norm is the same as the L∞ for stable systems, and Inf for unstable systems. If the peak gain frequency is required as well, use the function hinfnorm instead. See hinfnorm for further documentation.

tol is an optional keyword argument, used only for the computation of L∞ norms. It represents the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).

sys is first converted to a StateSpace model if needed.

source
ControlSystemsBase.balance_statespaceFunction
A, B, C, T = balance_statespace{S}(A::Matrix{S}, B::Matrix{S}, C::Matrix{S}, perm::Bool=false)
+sys, T = balance_statespace(sys::StateSpace, perm::Bool=false)

Computes a balancing transformation T that attempts to scale the system so that the row and column norms of [TA/T TB; C/T 0] are approximately equal. If perm=true, the states in A are allowed to be reordered.

The inverse of sysb, T = balance_statespace(sys) is given by similarity_transform(sysb, T)

This is not the same as finding a balanced realization with equal and diagonal observability and reachability gramians, see balreal

source

Videos

Basic usage of robustness analysis with JuliaControl

diff --git a/dev/lib/constructors/index.html b/dev/lib/constructors/index.html index 5b63210b6..530df7f1b 100644 --- a/dev/lib/constructors/index.html +++ b/dev/lib/constructors/index.html @@ -1,6 +1,6 @@ -Constructors · ControlSystems.jl

See also Connecting named systems together.

Constructing systems

ControlSystemsBase.c2dFunction
sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)
-Gd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)

Convert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).

method = :tustin performs a bilinear transform with prewarp frequency w_prewarp.

  • w_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.

See also c2d_x0map

Extended help

ZoH sampling is exact for linear systems with piece-wise constant inputs (step invariant), i.e., the solution obtained using lsim is not approximative (modulu machine precision). ZoH sampling is commonly used to discretize continuous-time plant models that are to be controlled using a discrete-time controller.

FoH sampling is exact for linear systems with piece-wise linear inputs (ramp invariant), this is a good choice for simulation of systems with smooth continuous inputs.

To approximate the behavior of a continuous-time system well in the frequency domain, the :tustin (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-time controller.

The forward-Euler method generally requires the sample time to be very small relative to the time constants of the system, and its use is generally discouraged.

Classical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as $0.2 ≤ ωgc⋅Ts ≤ 0.6$ where $ωgc$ is the gain-crossover frequency (rad/s).

source
Qd     = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts;             opt=:o)
+Constructors · ControlSystems.jl

See also Connecting named systems together.

Constructing systems

ControlSystemsBase.c2dFunction
sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)
+Gd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)

Convert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).

method = :tustin performs a bilinear transform with prewarp frequency w_prewarp.

  • w_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.

See also c2d_x0map

Extended help

ZoH sampling is exact for linear systems with piece-wise constant inputs (step invariant), i.e., the solution obtained using lsim is not approximative (modulu machine precision). ZoH sampling is commonly used to discretize continuous-time plant models that are to be controlled using a discrete-time controller.

FoH sampling is exact for linear systems with piece-wise linear inputs (ramp invariant), this is a good choice for simulation of systems with smooth continuous inputs.

To approximate the behavior of a continuous-time system well in the frequency domain, the :tustin (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-time controller.

The forward-Euler method generally requires the sample time to be very small relative to the time constants of the system, and its use is generally discouraged.

Classical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as $0.2 ≤ ωgc⋅Ts ≤ 0.6$ where $ωgc$ is the gain-crossover frequency (rad/s).

source
Qd     = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts;             opt=:o)
 Qd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)
 Qd     = c2d(sys::StateSpace{Discrete},   Qc::Matrix;                 opt=:o)
 Qd, Rd = c2d(sys::StateSpace{Discrete},   Qc::Matrix, Rc::Matrix;     opt=:o)

Sample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.

If opt = :o (default), the matrix is assumed to be a covariance matrix. The measurement covariance R may also be provided. If opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.

Note

Measurement covariance (here called Rc) is usually estimated in discrete time, and is in this case not dependent on the sample rate. Discretization of the measurement covariance only makes sense when a continuous-time controller has been designed and the closest corresponding discrete-time controller is desired.

The method used comes from theorem 5 in the reference below.

Ref: "Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering", Patrik Axelsson and Fredrik Gustafsson

On singular covariance matrices: The traditional double integrator with covariance matrix Q = diagm([0,σ²]) can not be sampled with this method. Instead, the input matrix ("Cholesky factor") of Q must be manually kept track of, e.g., the noise of variance σ² enters like N = [0, 1] which is sampled using ZoH and becomes Nd = [1/2 Ts^2; Ts] which results in the covariance matrix σ² * Nd * Nd'.

Example:

The following example designs a continuous-time LQR controller for a resonant system. This is simulated with OrdinaryDiffEq to allow the ODE integrator to also integrate the continuous-time LQR cost (the cost is added as an additional state variable). We then discretize both the system and the cost matrices and simulate the same thing. The discretization of an LQR contorller in this way is sometimes refered to as lqrd.

using ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test
@@ -31,10 +31,10 @@
     dot(x, Q, x) + dot(u, R, u)
 end
 cd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost
-@test cc ≈ cd rtol=0.01           # These should be similar
source
c2d(G::DelayLtiSystem, Ts, method=:zoh)
source
ControlSystemsBase.feedbackFunction
feedback(sys)
 feedback(sys1, sys2)

For a general LTI-system, feedback forms the negative feedback interconnection

>-+ sys1 +-->
   |      |
- (-)sys2 +

If no second system is given, negative identity feedback is assumed

source
feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
+ (-)sys2 +

If no second system is given, negative identity feedback is assumed

source
feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
          U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],
          Wperm=:, Zperm=:, pos_feedback::Bool=false)

Basic use feedback(sys1, sys2) forms the (negative) feedback interconnection

           ┌──────────────┐
 ◄──────────┤     sys1     │◄──── Σ ◄──────
@@ -52,8 +52,8 @@
  │            ┌──────────────┐            │
  └──► u2─────►│     sys2     ├───────►y2──┘
       w2─────►│              ├───────►z2
-              └──────────────┘
  • U1, W1 specify the indices of the input signals of sys1 corresponding to u1 and w1. W1 contains the indices of the inputs of sys1 that are included among the inputs to the returned system, i.e., external inputs.
  • Y1, Z1 specify the indices of the output signals of sys1 corresponding to y1 and z1. Z1 contains the indices of the outputs ofsys1` that are included among the outputs of the returned system, i.e., external outputs.
  • U2, W2, Y2, Z2 specify the corresponding signals of sys2. W2 contains the indices of the inputs ofsys2that are included among the inputs to the returned system, i.e., external inputs.Z2contains the indices of the outputs ofsys2` that are included among the outputs of the returned system, i.e., external outputs.

Specify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.

Negative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).

See also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.

The manual section From block diagrams to code contains higher-level instructions on how to use this function. See also RobustAndOptimalControl.jl: Connections using named signals for a higher-level interface.

See Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.

source
ControlSystemsBase.feedback2dofFunction
feedback2dof(P,R,S,T)
-feedback2dof(B,A,R,S,T)
  • Return BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively
  • Return BT/(AR+ST)
source
feedback2dof(P, C, F)

Return the transfer function P(F+C)/(1+PC) which is the closed-loop system with process P, controller C and feedforward filter F from reference to control signal (by-passing C).

         +-------+
+              └──────────────┘
  • U1, W1 specify the indices of the input signals of sys1 corresponding to u1 and w1. W1 contains the indices of the inputs of sys1 that are included among the inputs to the returned system, i.e., external inputs.
  • Y1, Z1 specify the indices of the output signals of sys1 corresponding to y1 and z1. Z1 contains the indices of the outputs ofsys1` that are included among the outputs of the returned system, i.e., external outputs.
  • U2, W2, Y2, Z2 specify the corresponding signals of sys2. W2 contains the indices of the inputs ofsys2that are included among the inputs to the returned system, i.e., external inputs.Z2contains the indices of the outputs ofsys2` that are included among the outputs of the returned system, i.e., external outputs.

Specify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.

Negative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).

See also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.

The manual section From block diagrams to code contains higher-level instructions on how to use this function. See also RobustAndOptimalControl.jl: Connections using named signals for a higher-level interface.

See Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.

source
ControlSystemsBase.feedback2dofFunction
feedback2dof(P,R,S,T)
+feedback2dof(B,A,R,S,T)
  • Return BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively
  • Return BT/(AR+ST)
source
feedback2dof(P, C, F)

Return the transfer function P(F+C)/(1+PC) which is the closed-loop system with process P, controller C and feedforward filter F from reference to control signal (by-passing C).

         +-------+
          |       |
    +----->   F   +----+
    |     |       |    |
@@ -64,16 +64,16 @@
       |  |       |         |       |   |
       |  +-------+         +-------+   |
       |                                |
-      +--------------------------------+
source
ControlSystemsBase.minrealFunction
minreal(tf::TransferFunction, eps=sqrt(eps()))

Create a minimal representation of each transfer function in tf by cancelling poles and zeros will promote system to an appropriate numeric type

source
minreal(sys::StateSpace; fast=false, kwargs...)

Minimal realisation algorithm from P. Van Dooreen, The generalized eigenstructure problem in linear system theory, IEEE Transactions on Automatic Control

For information about the options, see ?ControlSystemsBase.MatrixPencils.lsminreal

See also sminreal, which is both numerically exact and substantially faster than minreal, but with a much more limited potential in removing non-minimal dynamics.

source
ControlSystemsBase.sminrealFunction
sminreal(sys)

Compute the structurally minimal realization of the state-space system sys. A structurally minimal realization is one where only states that can be determined to be uncontrollable and unobservable based on the location of 0s in sys are removed.

Systems with numerical noise in the coefficients, e.g., noise on the order of eps require truncation to zero to be affected by structural simplification, e.g.,

trunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0
+      +--------------------------------+
source
ControlSystemsBase.minrealFunction
minreal(tf::TransferFunction, eps=sqrt(eps()))

Create a minimal representation of each transfer function in tf by cancelling poles and zeros will promote system to an appropriate numeric type

source
minreal(sys::StateSpace; fast=false, kwargs...)

Minimal realisation algorithm from P. Van Dooreen, The generalized eigenstructure problem in linear system theory, IEEE Transactions on Automatic Control

For information about the options, see ?ControlSystemsBase.MatrixPencils.lsminreal

See also sminreal, which is both numerically exact and substantially faster than minreal, but with a much more limited potential in removing non-minimal dynamics.

source
ControlSystemsBase.sminrealFunction
sminreal(sys)

Compute the structurally minimal realization of the state-space system sys. A structurally minimal realization is one where only states that can be determined to be uncontrollable and unobservable based on the location of 0s in sys are removed.

Systems with numerical noise in the coefficients, e.g., noise on the order of eps require truncation to zero to be affected by structural simplification, e.g.,

trunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0
 trunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)
-sminreal(sys)

In contrast to minreal, which performs pole-zero cancellation using linear-algebra operations, has an 𝑂(nₓ^3) complexity and is subject to numerical tolerances, sminreal is computationally very cheap and numerically exact (operates on integers). However, the ability of sminreal to reduce the order of the model is much less powerful.

See also minreal.

source
ControlSystemsBase.ssFunction
sys = ss(A, B, C, D)      # Continuous
-sys = ss(A, B, C, D, Ts)  # Discrete

Create a state-space model sys::StateSpace{TE, T} with matrix element type T and TE is Continuous or <:Discrete.

This is a continuous-time model if Ts is omitted. Otherwise, this is a discrete-time model with sampling period Ts.

D may be specified as 0 in which case a zero matrix of appropriate size is constructed automatically. sys = ss(D [, Ts]) specifies a static gain matrix D.

To associate names with states, inputs and outputs, see named_ss.

source
ControlSystemsBase.tfFunction
sys = tf(num, den[, Ts])
-sys = tf(gain[, Ts])

Create as a fraction of polynomials:

  • sys::TransferFunction{SisoRational{T,TR}} = numerator/denominator

where T is the type of the coefficients in the polynomial.

  • num: the coefficients of the numerator polynomial. Either scalar or vector to create SISO systems

or an array of vectors to create MIMO system.

  • den: the coefficients of the denominator polynomial. Either vector to create SISO systems

or an array of vectors to create MIMO system.

  • Ts: Sample time if discrete time system.

The polynomial coefficients are ordered starting from the highest order term.

Other uses:

  • tf(sys): Convert sys to tf form.
  • tf("s"), tf("z"): Create the continuous-time transfer function s, or the discrete-time transfer function z.
  • numpoly(sys), denpoly(sys): Get the numerator and denominator polynomials of sys as a matrix of vectors, where the outer matrix is of size n_output × n_inputs.

See also: zpk, ss.

source
ControlSystemsBase.zpkFunction
zpk(gain[, Ts])
+sminreal(sys)

In contrast to minreal, which performs pole-zero cancellation using linear-algebra operations, has an 𝑂(nₓ^3) complexity and is subject to numerical tolerances, sminreal is computationally very cheap and numerically exact (operates on integers). However, the ability of sminreal to reduce the order of the model is much less powerful.

See also minreal.

source
ControlSystemsBase.ssFunction
sys = ss(A, B, C, D)      # Continuous
+sys = ss(A, B, C, D, Ts)  # Discrete

Create a state-space model sys::StateSpace{TE, T} with matrix element type T and TE is Continuous or <:Discrete.

This is a continuous-time model if Ts is omitted. Otherwise, this is a discrete-time model with sampling period Ts.

D may be specified as 0 in which case a zero matrix of appropriate size is constructed automatically. sys = ss(D [, Ts]) specifies a static gain matrix D.

To associate names with states, inputs and outputs, see named_ss.

source
ControlSystemsBase.tfFunction
sys = tf(num, den[, Ts])
+sys = tf(gain[, Ts])

Create as a fraction of polynomials:

  • sys::TransferFunction{SisoRational{T,TR}} = numerator/denominator

where T is the type of the coefficients in the polynomial.

  • num: the coefficients of the numerator polynomial. Either scalar or vector to create SISO systems

or an array of vectors to create MIMO system.

  • den: the coefficients of the denominator polynomial. Either vector to create SISO systems

or an array of vectors to create MIMO system.

  • Ts: Sample time if discrete time system.

The polynomial coefficients are ordered starting from the highest order term.

Other uses:

  • tf(sys): Convert sys to tf form.
  • tf("s"), tf("z"): Create the continuous-time transfer function s, or the discrete-time transfer function z.
  • numpoly(sys), denpoly(sys): Get the numerator and denominator polynomials of sys as a matrix of vectors, where the outer matrix is of size n_output × n_inputs.

See also: zpk, ss.

source
ControlSystemsBase.zpkFunction
zpk(gain[, Ts])
 zpk(num, den, k[, Ts])
-zpk(sys)

Create transfer function on zero pole gain form. The numerator and denominator are represented by their poles and zeros.

  • sys::TransferFunction{SisoZpk{T,TR}} = k*numerator/denominator

where T is the type of k and TR the type of the zeros/poles, usually Float64 and Complex{Float64}.

  • num: the roots of the numerator polynomial. Either scalar or vector to create SISO systems

or an array of vectors to create MIMO system.

  • den: the roots of the denominator polynomial. Either vector to create SISO systems

or an array of vectors to create MIMO system.

  • k: The gain of the system. Obs, this is not the same as dcgain.
  • Ts: Sample time if discrete time system.

Other uses:

  • zpk(sys): Convert sys to zpk form.
  • zpk("s"): Create the transferfunction s.
source
ControlSystemsBase.delayFunction
delay(tau)
+zpk(sys)

Create transfer function on zero pole gain form. The numerator and denominator are represented by their poles and zeros.

  • sys::TransferFunction{SisoZpk{T,TR}} = k*numerator/denominator

where T is the type of k and TR the type of the zeros/poles, usually Float64 and Complex{Float64}.

  • num: the roots of the numerator polynomial. Either scalar or vector to create SISO systems

or an array of vectors to create MIMO system.

  • den: the roots of the denominator polynomial. Either vector to create SISO systems

or an array of vectors to create MIMO system.

  • k: The gain of the system. Obs, this is not the same as dcgain.
  • Ts: Sample time if discrete time system.

Other uses:

  • zpk(sys): Convert sys to zpk form.
  • zpk("s"): Create the transferfunction s.
source
ControlSystemsBase.delayFunction
delay(tau)
 delay(tau, Ts)
 delay(T::Type{<:Number}, tau)
 delay(T::Type{<:Number}, tau, Ts)

Create a pure time delay of length τ of type T.

The type T defaults to promote_type(Float64, typeof(tau)).

If Ts is given, the delay is discretized with sampling time Ts and a discrete-time StateSpace object is returned.

Example:

Create a LTI system with an input delay of L

L = 1
 tf(1, [1, 1])*delay(L)
 s = tf("s")
-tf(1, [1, 1])*exp(-s*L) # Equivalent to the version above
source
ControlSystemsBase.padeFunction
pade(τ::Real, N::Int)

Compute the Nth order Padé approximation of a time-delay of length τ.

See also thiran for discretization of delays.

source
pade(G::DelayLtiSystem, N)

Approximate all time-delays in G by Padé approximations of degree N.

source
ControlSystemsBase.thiranFunction
thiran(τ::Real, Ts)

Discretize a potentially fractional delay $τ$ as a Thiran all-pass filter with sample time Ts.

The Thiran all-pass filter gives an a maximally flat group delay.

If $τ$ is an integer multiple of $Ts$, the Thiran all-pass filter reduces to $z^{-τ/Ts}$.

Ref: T. I. Laakso, V. Valimaki, M. Karjalainen and U. K. Laine, "Splitting the unit delay [FIR/all pass filters design]," in IEEE Signal Processing Magazine, vol. 13, no. 1, 1996.

source
ControlSystemsBase.ssdataFunction
A, B, C, D = ssdata(sys)

Outputs the statespace matrices of sys. The matrices are not copies: no new memory is allocated, but modifying the matrices in-place will change the behavior of sys.

source
ControlSystemsBase.seriesformFunction
Gs, k = seriesform(G::TransferFunction{Discrete})

Convert a transfer function G to a vector of second-order transfer functions and a scalar gain k, the product of which equals G.

Note

This function requires the user to load the package DSP.jl.

source
+tf(1, [1, 1])*exp(-s*L) # Equivalent to the version above
source
ControlSystemsBase.padeFunction
pade(τ::Real, N::Int)

Compute the Nth order Padé approximation of a time-delay of length τ.

See also thiran for discretization of delays.

source
pade(G::DelayLtiSystem, N)

Approximate all time-delays in G by Padé approximations of degree N.

source
ControlSystemsBase.thiranFunction
thiran(τ::Real, Ts)

Discretize a potentially fractional delay $τ$ as a Thiran all-pass filter with sample time Ts.

The Thiran all-pass filter gives an a maximally flat group delay.

If $τ$ is an integer multiple of $Ts$, the Thiran all-pass filter reduces to $z^{-τ/Ts}$.

Ref: T. I. Laakso, V. Valimaki, M. Karjalainen and U. K. Laine, "Splitting the unit delay [FIR/all pass filters design]," in IEEE Signal Processing Magazine, vol. 13, no. 1, 1996.

source
ControlSystemsBase.ssdataFunction
A, B, C, D = ssdata(sys)

Outputs the statespace matrices of sys. The matrices are not copies: no new memory is allocated, but modifying the matrices in-place will change the behavior of sys.

source
ControlSystemsBase.seriesformFunction
Gs, k = seriesform(G::TransferFunction{Discrete})

Convert a transfer function G to a vector of second-order transfer functions and a scalar gain k, the product of which equals G.

Note

This function requires the user to load the package DSP.jl.

source
diff --git a/dev/lib/nonlinear/c980479c.svg b/dev/lib/nonlinear/0158eff5.svg similarity index 88% rename from dev/lib/nonlinear/c980479c.svg rename to dev/lib/nonlinear/0158eff5.svg index 92fc38d9d..e1cd23867 100644 --- a/dev/lib/nonlinear/c980479c.svg +++ b/dev/lib/nonlinear/0158eff5.svg @@ -1,47 +1,47 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/6c093758.svg b/dev/lib/nonlinear/25642c20.svg similarity index 97% rename from dev/lib/nonlinear/6c093758.svg rename to dev/lib/nonlinear/25642c20.svg index 102d36343..44da26cfb 100644 --- a/dev/lib/nonlinear/6c093758.svg +++ b/dev/lib/nonlinear/25642c20.svg @@ -1,208 +1,208 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - + diff --git a/dev/lib/nonlinear/e149e5a7.svg b/dev/lib/nonlinear/25c1fc58.svg similarity index 94% rename from dev/lib/nonlinear/e149e5a7.svg rename to dev/lib/nonlinear/25c1fc58.svg index f4b5aab38..4dac16bb9 100644 --- a/dev/lib/nonlinear/e149e5a7.svg +++ b/dev/lib/nonlinear/25c1fc58.svg @@ -1,249 +1,249 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - + diff --git a/dev/lib/nonlinear/09e34d73.svg b/dev/lib/nonlinear/e24bb897.svg similarity index 96% rename from dev/lib/nonlinear/09e34d73.svg rename to dev/lib/nonlinear/e24bb897.svg index 5664f90ba..8949bc54c 100644 --- a/dev/lib/nonlinear/09e34d73.svg +++ b/dev/lib/nonlinear/e24bb897.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/473fae98.svg b/dev/lib/nonlinear/ee3ea568.svg similarity index 86% rename from dev/lib/nonlinear/473fae98.svg rename to dev/lib/nonlinear/ee3ea568.svg index c7c846935..c0cff20e4 100644 --- a/dev/lib/nonlinear/473fae98.svg +++ b/dev/lib/nonlinear/ee3ea568.svg @@ -1,90 +1,90 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/index.html b/dev/lib/nonlinear/index.html index af3af4262..4a7dd7366 100644 --- a/dev/lib/nonlinear/index.html +++ b/dev/lib/nonlinear/index.html @@ -36,7 +36,7 @@ Gunl = feedback(satC, P) # closed loop from reference to control signal with saturation plot(step([G; Gu], 5), lab = ["Linear y" "Linear u"]) -plot!(step([Gnl; Gunl], 5), lab = ["Nonlinear y" "Nonlinear u"])
Example block output

Since the saturating nonlinearity is common, we provide the constructor ControlSystemsBase.saturation that automatically forms the equivalent to nonlinearity(x->clamp(x, -0.7, 0.7)) while at the same time making sure the function has a recognizable name when the system is printed

using ControlSystemsBase: saturation
+plot!(step([Gnl; Gunl], 5), lab = ["Nonlinear y" "Nonlinear u"])
Example block output

Since the saturating nonlinearity is common, we provide the constructor ControlSystemsBase.saturation that automatically forms the equivalent to nonlinearity(x->clamp(x, -0.7, 0.7)) while at the same time making sure the function has a recognizable name when the system is printed

using ControlSystemsBase: saturation
 saturation(0.7)
ControlSystemsBase.HammersteinWienerSystem{Float64}
 
 P: StateSpace{Continuous, Float64}
@@ -113,7 +113,7 @@
     plot(lsim(feedback(Gop*C_sat), yr, 0:1:3000, x0=[x0-xr; zeros(C.nx)]), layout=1, sp=1, title="Outputs", ylabel=""),
     plot(lsim(feedback(C_sat, Gop), yr, 0:1:3000, x0=[zeros(C.nx); x0-xr]), layout=1, sp=1, title="Control signals", ylabel="")
 )
-hline!([yr[1]], label="Reference", l=:dash, sp=1, c=1)
Example block output

The state vector resulting from the call to feedback is comprised of the concatenated states of the first and second arguments, i.e., feedback(C_sat, Gop) has the state vector [C_sat.x; Gop.x] while feedback(Gop*C_sat) has the state vector of Gop*C_sat which is starting with the first operand, [Gop.x; C_sat.x].

Duffing oscillator

In this example, we'll model and control the nonlinear system

\[\ddot x = -kx - k_3 x^3 - c \dot{x} + 10u\]

To do this, we first draw the block diagram

10u    ┌───┐
+hline!([yr[1]], label="Reference", l=:dash, sp=1, c=1)
Example block output

The state vector resulting from the call to feedback is comprised of the concatenated states of the first and second arguments, i.e., feedback(C_sat, Gop) has the state vector [C_sat.x; Gop.x] while feedback(Gop*C_sat) has the state vector of Gop*C_sat which is starting with the first operand, [Gop.x; C_sat.x].

Duffing oscillator

In this example, we'll model and control the nonlinear system

\[\ddot x = -kx - k_3 x^3 - c \dot{x} + 10u\]

To do this, we first draw the block diagram

10u    ┌───┐
 ──────►│+  │   ┌───┐   ┌───┐
  ┌────►│-  │ ẍ │ 1 │ ẋ │ 1 │ x
  │ ┌──►│-  ├──►│ - ├┬─►│ - ├─┬──►
@@ -143,7 +143,7 @@
 pos_loop_feedback = (k3*cube + k)
 duffing = feedback(vel_loop/s, pos_loop_feedback)*10
 
-plot(step(duffing, 20), title="Duffing oscillator open-loop step response")
Example block output

We now show how we can make use of the circle criterion to prove stability of the closed loop. The function circle_criterion below plots the Nyquist curve of the loop-transfer function and figures out the circle to avoid by finding sector bounds for the static nonlinearity $f(x) = x^3$. We then choose a controller and check that it stays outside of the circle. To find the sector bounds, we choose a domain to evaluate the nonlinearity over. The function $f(x) = x^3$ goes to infinity faster than any linear function, and the upper sector bound is thus ∞, but if we restrict the nonlinearity to a smaller domain, we get a finite sector bound:

function circle_criterion(L::ControlSystemsBase.HammersteinWienerSystem, domain::Tuple; N=10000)
+plot(step(duffing, 20), title="Duffing oscillator open-loop step response")
Example block output

We now show how we can make use of the circle criterion to prove stability of the closed loop. The function circle_criterion below plots the Nyquist curve of the loop-transfer function and figures out the circle to avoid by finding sector bounds for the static nonlinearity $f(x) = x^3$. We then choose a controller and check that it stays outside of the circle. To find the sector bounds, we choose a domain to evaluate the nonlinearity over. The function $f(x) = x^3$ goes to infinity faster than any linear function, and the upper sector bound is thus ∞, but if we restrict the nonlinearity to a smaller domain, we get a finite sector bound:

function circle_criterion(L::ControlSystemsBase.HammersteinWienerSystem, domain::Tuple; N=10000)
     fun = x->L.f[](x)/x
     x = range(domain[1], stop=domain[2], length=N)
     0 ∈ x && (x = filter(!=(0), x)) # We cannot divide by zero
@@ -176,7 +176,7 @@
 f1 = circle_criterion(duffing*C, (-1, 1))
 plot!(sp=2, ylims=(-10, 3), xlims=(-5, 11))
 f2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title="Controlled oscillator disturbance step response", layout=4)
-plot(f1,f2, size=(1300,800))
Example block output

Since we evaluated the nonlinearity over a small domain, we should convince ourselves that we indeed never risk leaving this domain.

In the example above, the circle turns into a half plane since the lower sector bound is 0. The example below chooses another nonlinearity

\[f(x) = x + \sin(x)\]

to get an actual circle in the Nyquist plane.

wiggly = nonlinearity(x->x+sin(x)) # This function is a bit wiggly
+plot(f1,f2, size=(1300,800))
Example block output

Since we evaluated the nonlinearity over a small domain, we should convince ourselves that we indeed never risk leaving this domain.

In the example above, the circle turns into a half plane since the lower sector bound is 0. The example below chooses another nonlinearity

\[f(x) = x + \sin(x)\]

to get an actual circle in the Nyquist plane.

wiggly = nonlinearity(x->x+sin(x)) # This function is a bit wiggly
 vel_loop = feedback(1/s, c)
 pos_loop_feedback = (k3*wiggly + k)
 duffing = feedback(vel_loop/s, pos_loop_feedback)*10
@@ -185,8 +185,8 @@
 f1 = circle_criterion(duffing*C, (-2pi, 2pi))
 plot!(sp=2, ylims=(-5, 2), xlims=(-2.1, 0.1))
 f2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title="Controlled wiggly oscillator disturbance step response", layout=5)
-plot(f1,f2, size=(1300,800))
Example block output

Limitations

  • Remember, this functionality is experimental and subject to breakage.
  • Currently only Continuous systems supported.
  • No nonlinear root-finding is performed during simulation. This limits the kinds of systems that can be simulated somewhat, in particular, no algebraic loops are allowed.
  • A lot of functions that expect linear systems will not work for nonlinear systems (naturally).

Possible future work

  • Discrete-time support.
  • Basic support for nonlinear analysis such as stability proof through the circle criterion etc. In particular, predefined nonlinear functions may specify sector bounds for the gain, required by the circle-criterion calculations.
  • Additional nonlinear components, such as
    • Integrator anti-windup
    • Friction models

See also

More advanced nonlinear modeling is facilitated by ModelingToolkit.jl (MTK) and ModelingToolkitStandardLibrary.jl. The tutorials

show how to use these packages to model and simulate control systems.

Docstrings

ControlSystemsBase.nonlinearityFunction
nonlinearity(f)
-nonlinearity(T, f)

Create a pure nonlinearity. f is assumed to be a static (no memory) nonlinear function from $f : R -> R$.

The type T defaults to Float64.

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Example:

Create a LTI system with a static input nonlinearity that saturates the input to [-1,1].

tf(1, [1, 1])*nonlinearity(x->clamp(x, -1, 1))

See also predefined nonlinearities saturation, offset.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.offsetFunction
offset(val)

Create a constant-offset nonlinearity x -> x + val.

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Example:

To create a linear system that operates around operating point y₀, u₀, use

offset_sys = offset(y₀) * sys * offset(-u₀)

note the sign on the offset u₀. This ensures that sys operates in the coordinates Δu = u-u₀, Δy = y-y₀ and the inputs and outputs to the offset system are in their non-offset coordinate system. If the system is linearized around x₀, y₀ is given by C*x₀. Additional information and an example is available here https://juliacontrol.github.io/ControlSystemsBase.jl/latest/lib/nonlinear/#Non-zero-operating-point

source
ControlSystemsBase.saturationFunction
saturation(val)
+plot(f1,f2, size=(1300,800))
Example block output

Limitations

  • Remember, this functionality is experimental and subject to breakage.
  • Currently only Continuous systems supported.
  • No nonlinear root-finding is performed during simulation. This limits the kinds of systems that can be simulated somewhat, in particular, no algebraic loops are allowed.
  • A lot of functions that expect linear systems will not work for nonlinear systems (naturally).

Possible future work

  • Discrete-time support.
  • Basic support for nonlinear analysis such as stability proof through the circle criterion etc. In particular, predefined nonlinear functions may specify sector bounds for the gain, required by the circle-criterion calculations.
  • Additional nonlinear components, such as
    • Integrator anti-windup
    • Friction models

See also

More advanced nonlinear modeling is facilitated by ModelingToolkit.jl (MTK) and ModelingToolkitStandardLibrary.jl. The tutorials

show how to use these packages to model and simulate control systems.

Docstrings

ControlSystemsBase.nonlinearityFunction
nonlinearity(f)
+nonlinearity(T, f)

Create a pure nonlinearity. f is assumed to be a static (no memory) nonlinear function from $f : R -> R$.

The type T defaults to Float64.

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Example:

Create a LTI system with a static input nonlinearity that saturates the input to [-1,1].

tf(1, [1, 1])*nonlinearity(x->clamp(x, -1, 1))

See also predefined nonlinearities saturation, offset.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.offsetFunction
offset(val)

Create a constant-offset nonlinearity x -> x + val.

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Example:

To create a linear system that operates around operating point y₀, u₀, use

offset_sys = offset(y₀) * sys * offset(-u₀)

note the sign on the offset u₀. This ensures that sys operates in the coordinates Δu = u-u₀, Δy = y-y₀ and the inputs and outputs to the offset system are in their non-offset coordinate system. If the system is linearized around x₀, y₀ is given by C*x₀. Additional information and an example is available here https://juliacontrol.github.io/ControlSystemsBase.jl/latest/lib/nonlinear/#Non-zero-operating-point

source
ControlSystemsBase.saturationFunction
saturation(val)
 saturation(lower, upper)

Create a saturating nonlinearity. Connect it to the output of a controller C using

Csat = saturation(val) * C
           y▲   ────── upper
             │  /
             │ /
@@ -195,8 +195,8 @@
            /│   
           / │
          /  │
-lower──── 

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.ratelimitFunction
ratelimit(val; Tf)
-ratelimit(lower, upper; Tf)

Create a nonlinearity that limits the rate of change of a signal, roughly equivalent to $1/s ∘ sat ∘ s$. Tf controls the filter time constant on the derivative used to calculate the rate. NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

source
ControlSystemsBase.deadzoneFunction
deadzone(val)
+lower──── 

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.ratelimitFunction
ratelimit(val; Tf)
+ratelimit(lower, upper; Tf)

Create a nonlinearity that limits the rate of change of a signal, roughly equivalent to $1/s ∘ sat ∘ s$. Tf controls the filter time constant on the derivative used to calculate the rate. NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

source
ControlSystemsBase.deadzoneFunction
deadzone(val)
 deadzone(lower, upper)

Create a dead-zone nonlinearity.

       y▲
         │     /
         │    /
@@ -204,7 +204,7 @@
 ─────|──┼──|───────► u
     /   │   upper
    /    │
-  /     │

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.linearizeFunction
linearize(sys::HammersteinWienerSystem, Δy)

Linearize the nonlinear system sys around the operating point implied by the specified Δy

      ┌─────────┐
+  /     │

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

Note: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.

source
ControlSystemsBase.linearizeFunction
linearize(sys::HammersteinWienerSystem, Δy)

Linearize the nonlinear system sys around the operating point implied by the specified Δy

      ┌─────────┐
  y◄───┤         │◄────u
       │    P    │
 Δy┌───┤         │◄───┐Δu
@@ -214,4 +214,4 @@
   │      │   │       │
   └─────►│ f ├───────┘
          │   │
-         └───┘

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

source
A, B = linearize(f, x, u, args...)

Linearize dynamics $ẋ = f(x, u, args...)$ around operating point $(x,u,args...)$ using ForwardDiff. args can be empty, or contain, e.g., parameters and time (p, t) like in the SciML interface. This function can also be used to linearize an output equation C, D = linearize(h, x, u, args...).

source
+ └───┘

NOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.

source
A, B = linearize(f, x, u, args...)

Linearize dynamics $ẋ = f(x, u, args...)$ around operating point $(x,u,args...)$ using ForwardDiff. args can be empty, or contain, e.g., parameters and time (p, t) like in the SciML interface. This function can also be used to linearize an output equation C, D = linearize(h, x, u, args...).

source
diff --git a/dev/lib/plotting/index.html b/dev/lib/plotting/index.html index 24f25c1c6..8b12a693a 100644 --- a/dev/lib/plotting/index.html +++ b/dev/lib/plotting/index.html @@ -1,15 +1,15 @@ Plotting · ControlSystems.jl
Using Plots

All plotting requires the user to manually load the Plots.jl library, e.g., by calling using Plots.

Time-domain responses

There are no special functions to plot time-domain results, such as step and impulse responses, instead, simply call plot on the result structure (ControlSystemsBase.SimResult) returned by lsim, step, impulse etc.

Plotting functions

ControlSystemsBase.bodeplotFunction
fig = bodeplot(sys, args...)
-bodeplot(LTISystem[sys1, sys2...], args...; plotphase=true, balance = true, kwargs...)

Create a Bode plot of the LTISystem(s). A frequency vector w can be optionally provided. To change the Magnitude scale see setPlotScale. The default magnitude scale is "log10" (absolute scale).

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.
  • adjust_phase_start: If true, the phase will be adjusted so that it starts at -90*intexcess degrees, where intexcess is the integrator excess of the system.

kwargs is sent as argument to RecipesBase.plot.

source
ControlSystemsBase.gangoffourplotMethod
fig = gangoffourplot(P::LTISystem, C::LTISystem; minimal=true, plotphase=false, Ms_lines = [1.0, 1.25, 1.5], Mt_lines = [], sigma = true, kwargs...)

Gang-of-Four plot.

sigma determines whether a sigmaplot is used instead of a bodeplot for MIMO S and T. kwargs are sent as argument to RecipesBase.plot.

source
ControlSystemsBase.marginplotFunction
fig = marginplot(sys::LTISystem [,w::AbstractVector];  balance=true, kwargs...)
-marginplot(sys::Vector{LTISystem}, w::AbstractVector;  balance=true, kwargs...)

Plot all the amplitude and phase margins of the system(s) sys.

  • A frequency vector w can be optionally provided.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to RecipesBase.plot.

source
ControlSystemsBase.nicholsplotFunction
fig = nicholsplot{T<:LTISystem}(systems::Vector{T}, w::AbstractVector; kwargs...)

Create a Nichols plot of the LTISystem(s). A frequency vector w can be optionally provided.

Keyword arguments:

text = true
+bodeplot(LTISystem[sys1, sys2...], args...; plotphase=true, balance = true, kwargs...)

Create a Bode plot of the LTISystem(s). A frequency vector w can be optionally provided. To change the Magnitude scale see setPlotScale. The default magnitude scale is "log10" (absolute scale).

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.
  • adjust_phase_start: If true, the phase will be adjusted so that it starts at -90*intexcess degrees, where intexcess is the integrator excess of the system.

kwargs is sent as argument to RecipesBase.plot.

source
ControlSystemsBase.gangoffourplotMethod
fig = gangoffourplot(P::LTISystem, C::LTISystem; minimal=true, plotphase=false, Ms_lines = [1.0, 1.25, 1.5], Mt_lines = [], sigma = true, kwargs...)

Gang-of-Four plot.

sigma determines whether a sigmaplot is used instead of a bodeplot for MIMO S and T. kwargs are sent as argument to RecipesBase.plot.

source
ControlSystemsBase.marginplotFunction
fig = marginplot(sys::LTISystem [,w::AbstractVector];  balance=true, kwargs...)
+marginplot(sys::Vector{LTISystem}, w::AbstractVector;  balance=true, kwargs...)

Plot all the amplitude and phase margins of the system(s) sys.

  • A frequency vector w can be optionally provided.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to RecipesBase.plot.

source
ControlSystemsBase.nicholsplotFunction
fig = nicholsplot{T<:LTISystem}(systems::Vector{T}, w::AbstractVector; kwargs...)

Create a Nichols plot of the LTISystem(s). A frequency vector w can be optionally provided.

Keyword arguments:

text = true
 Gains = [12, 6, 3, 1, 0.5, -0.5, -1, -3, -6, -10, -20, -40, -60]
 pInc = 30
 sat = 0.4
 val = 0.85
-fontsize = 10

pInc determines the increment in degrees between phase lines.

sat ∈ [0,1] determines the saturation of the gain lines

val ∈ [0,1] determines the brightness of the gain lines

Additional keyword arguments are sent to the function plotting the systems and can be used to specify colors, line styles etc. using regular RecipesBase.jl syntax

This function is based on code subject to the two-clause BSD licence Copyright 2011 Will Robertson Copyright 2011 Philipp Allgeuer

source
ControlSystemsBase.nyquistplotFunction
fig = nyquistplot(sys;                Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
-nyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)

Create a Nyquist plot of the LTISystem(s). A frequency vector w can be optionally provided.

  • unit_circle: if the unit circle should be displayed. The Nyquist curve crosses the unit circle at the gain crossover frequency.
  • Ms_circles: draw circles corresponding to given levels of sensitivity (circles around -1 with radii 1/Ms). Ms_circles can be supplied as a number or a vector of numbers. A design staying outside such a circle has a phase margin of at least 2asin(1/(2Ms)) rad and a gain margin of at least Ms/(Ms-1).
  • Mt_circles: draw circles corresponding to given levels of complementary sensitivity. Mt_circles can be supplied as a number or a vector of numbers.
  • critical_point: point on real axis to mark as critical for encirclements
  • If hz=true, the hover information will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to plot.

source
ControlSystemsBase.pzmapFunction
fig = pzmap(fig, system, args...; hz = false, kwargs...)

Create a pole-zero map of the LTISystem(s) in figure fig, args and kwargs will be sent to the scatter plot command.

To customize the unit-circle drawn for discrete systems, modify the line attributes, e.g., linecolor=:red.

If hz is true, all poles and zeros are scaled by 1/2π.

source
ControlSystemsBase.rgaplotFunction
rgaplot(sys, args...; hz=false)
-rgaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true)

Plot the relative-gain array entries of the LTISystem(s). A frequency vector w can be optionally provided.

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to Plots.plot.

source
ControlSystemsBase.setPlotScaleMethod
setPlotScale(str)

Set the default scale of magnitude in bodeplot and sigmaplot. str should be either "dB" or "log10". The default scale if none is chosen is "log10".

source
ControlSystemsBase.sigmaplotFunction
sigmaplot(sys, args...; hz=false balance=true, extrema)
-sigmaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true, extrema)

Plot the singular values of the frequency response of the LTISystem(s). A frequency vector w can be optionally provided.

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.
  • extrema: Only plot the largest and smallest singular values.

kwargs is sent as argument to Plots.plot.

source

Examples

Bode plot

bode

tf1 = tf([1],[1,1])
+fontsize = 10

pInc determines the increment in degrees between phase lines.

sat ∈ [0,1] determines the saturation of the gain lines

val ∈ [0,1] determines the brightness of the gain lines

Additional keyword arguments are sent to the function plotting the systems and can be used to specify colors, line styles etc. using regular RecipesBase.jl syntax

This function is based on code subject to the two-clause BSD licence Copyright 2011 Will Robertson Copyright 2011 Philipp Allgeuer

source
ControlSystemsBase.nyquistplotFunction
fig = nyquistplot(sys;                Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
+nyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)

Create a Nyquist plot of the LTISystem(s). A frequency vector w can be optionally provided.

  • unit_circle: if the unit circle should be displayed. The Nyquist curve crosses the unit circle at the gain crossover frequency.
  • Ms_circles: draw circles corresponding to given levels of sensitivity (circles around -1 with radii 1/Ms). Ms_circles can be supplied as a number or a vector of numbers. A design staying outside such a circle has a phase margin of at least 2asin(1/(2Ms)) rad and a gain margin of at least Ms/(Ms-1).
  • Mt_circles: draw circles corresponding to given levels of complementary sensitivity. Mt_circles can be supplied as a number or a vector of numbers.
  • critical_point: point on real axis to mark as critical for encirclements
  • If hz=true, the hover information will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to plot.

source
ControlSystemsBase.pzmapFunction
fig = pzmap(fig, system, args...; hz = false, kwargs...)

Create a pole-zero map of the LTISystem(s) in figure fig, args and kwargs will be sent to the scatter plot command.

To customize the unit-circle drawn for discrete systems, modify the line attributes, e.g., linecolor=:red.

If hz is true, all poles and zeros are scaled by 1/2π.

source
ControlSystemsBase.rgaplotFunction
rgaplot(sys, args...; hz=false)
+rgaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true)

Plot the relative-gain array entries of the LTISystem(s). A frequency vector w can be optionally provided.

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.

kwargs is sent as argument to Plots.plot.

source
ControlSystemsBase.setPlotScaleMethod
setPlotScale(str)

Set the default scale of magnitude in bodeplot and sigmaplot. str should be either "dB" or "log10". The default scale if none is chosen is "log10".

source
ControlSystemsBase.sigmaplotFunction
sigmaplot(sys, args...; hz=false balance=true, extrema)
+sigmaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true, extrema)

Plot the singular values of the frequency response of the LTISystem(s). A frequency vector w can be optionally provided.

  • If hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
  • balance: Call balance_statespace on the system before plotting.
  • extrema: Only plot the largest and smallest singular values.

kwargs is sent as argument to Plots.plot.

source

Examples

Bode plot

bode

tf1 = tf([1],[1,1])
 tf2 = tf([1/5,2],[1,1,1])
 sys = [tf1 tf2]
 ws = exp10.(range(-2,stop=2,length=200))
@@ -37,4 +37,4 @@
 sysd = c2d(ss(sys), 0.01)
 res = step(sysd, 5)
 plot(res, l=(:dash, 4))
-# plot!(stepinfo(step(sysd[1,1], 5))) # adds extra info to the plot
+# plot!(stepinfo(step(sysd[1,1], 5))) # adds extra info to the plot diff --git a/dev/lib/synthesis/index.html b/dev/lib/synthesis/index.html index 2b6c2d5e3..053489693 100644 --- a/dev/lib/synthesis/index.html +++ b/dev/lib/synthesis/index.html @@ -1,7 +1,7 @@ -Synthesis · ControlSystems.jl

Synthesis

For $H_\infty$ and $H_2$ synthesis as well as more advanced LQG design, see RobustAndOptimalControl.

ControlSystemsBase.kalmanMethod
kalman(Continuous, A, C, R1, R2)
+Synthesis · ControlSystems.jl

Synthesis

For $H_\infty$ and $H_2$ synthesis as well as more advanced LQG design, see RobustAndOptimalControl.

ControlSystemsBase.kalmanMethod
kalman(Continuous, A, C, R1, R2)
 kalman(Discrete, A, C, R1, R2; direct = false)
-kalman(sys, R1, R2; direct = false)

Calculate the optimal asymptotic Kalman gain.

If direct = true, the observer gain is computed for the pair (A, CA) instead of (A,C). This option is intended to be used together with the option direct = true to observer_controller. Ref: "Computer-Controlled Systems" pp 140. direct = false is sometimes referred to as a "delayed" estimator, while direct = true is a "current" estimator.

To obtain a discrete-time approximation to a continuous-time LQG problem, the function c2d can be used to obtain corresponding discrete-time covariance matrices.

To obtain an LTISystem that represents the Kalman filter, pass the obtained Kalman feedback gain into observer_filter. To obtain an LQG controller, pass the obtained Kalman feedback gain as well as a state-feedback gain computed using lqr into observer_controller.

The args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec/ared for more help.

FAQ

This function requires

  • R1 must be positive semi-definite
  • R2 must be positive definite
  • The pair (A,R1) must not have any uncontrollable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not affected through R1. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
source
ControlSystemsBase.lqrMethod
lqr(sys, Q, R)
+kalman(sys, R1, R2; direct = false)

Calculate the optimal asymptotic Kalman gain.

If direct = true, the observer gain is computed for the pair (A, CA) instead of (A,C). This option is intended to be used together with the option direct = true to observer_controller. Ref: "Computer-Controlled Systems" pp 140. direct = false is sometimes referred to as a "delayed" estimator, while direct = true is a "current" estimator.

To obtain a discrete-time approximation to a continuous-time LQG problem, the function c2d can be used to obtain corresponding discrete-time covariance matrices.

To obtain an LTISystem that represents the Kalman filter, pass the obtained Kalman feedback gain into observer_filter. To obtain an LQG controller, pass the obtained Kalman feedback gain as well as a state-feedback gain computed using lqr into observer_controller.

The args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec/ared for more help.

FAQ

This function requires

  • R1 must be positive semi-definite
  • R2 must be positive definite
  • The pair (A,R1) must not have any uncontrollable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not affected through R1. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
source
ControlSystemsBase.lqrMethod
lqr(sys, Q, R)
 lqr(Continuous, A, B, Q, R, args...; kwargs...)
 lqr(Discrete, A, B, Q, R, args...; kwargs...)

Calculate the optimal gain matrix K for the state-feedback law u = -K*x that minimizes the cost function:

J = integral(x'Qx + u'Ru, 0, inf) for the continuous-time model dx = Ax + Bu. J = sum(x'Qx + u'Ru, 0, inf) for the discrete-time model x[k+1] = Ax[k] + Bu[k].

Solve the LQR problem for state-space system sys. Works for both discrete and continuous time systems.

The args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec / ared for more help.

To obtain also the solution to the Riccati equation and the eigenvalues of the closed-loop system as well, call ControlSystemsBase.MatrixEquations.arec / ared instead (note the different order of the arguments to these functions).

To obtain a discrete-time approximation to a continuous-time LQR problem, the function c2d can be used to obtain corresponding discrete-time cost matrices.

Examples

Continuous time

using LinearAlgebra # For identity matrix I
 using Plots
@@ -32,10 +32,10 @@
 t=0:Ts:5
 x0 = [1,0]
 y, t, x, uout = lsim(sys,u,t,x0=x0)
-plot(t,x', lab=["Position"  "Velocity"], xlabel="Time [s]")

FAQ

This function requires

  • Q must be positive semi-definite
  • R must be positive definite
  • The pair (Q,A) must not have any unobservable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not penalized by Q. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
source
ControlSystemsBase.placeFunction
place(A, B, p, opt=:c; direct = false)
+plot(t,x', lab=["Position"  "Velocity"], xlabel="Time [s]")

FAQ

This function requires

  • Q must be positive semi-definite
  • R must be positive definite
  • The pair (Q,A) must not have any unobservable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not penalized by Q. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
source
ControlSystemsBase.placeFunction
place(A, B, p, opt=:c; direct = false)
 place(sys::StateSpace, p, opt=:c; direct = false)

Calculate the gain matrix K such that A - BK has eigenvalues p.

place(A, C, p, opt=:o)
-place(sys::StateSpace, p, opt=:o)

Calculate the observer gain matrix L such that A - LC has eigenvalues p.

If direct = true and opt = :o, the the observer gain K is calculated such that A - KCA has eigenvalues p, this option is to be used together with direct = true in observer_controller.

Note: only apply direct = true to discrete-time systems.

Ref: "Computer-Controlled Systems" pp 140.

Uses Ackermann's formula for SISO systems and place_knvd for MIMO systems.

Please note that this function can be numerically sensitive, solving the placement problem in extended precision might be beneficial.

source
ControlSystemsBase.place_knvdMethod
place_knvd(A::AbstractMatrix, B, λ; verbose = false, init = :s)

Robust pole placement using the algorithm from

"Robust Pole Assignment in Linear State Feedback", Kautsky, Nichols, Van Dooren

This implementation uses "method 0" for the X-step and the QR factorization for all factorizations.

This function will be called automatically when place is called with a MIMO system.

Arguments:

  • init: Determines the initialization strategy for the iterations for find the X matrix. Possible choices are :id (default), :rand, :s.
source
ControlSystemsBase.c2dFunction
sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)
-Gd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)

Convert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).

method = :tustin performs a bilinear transform with prewarp frequency w_prewarp.

  • w_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.

See also c2d_x0map

Extended help

ZoH sampling is exact for linear systems with piece-wise constant inputs (step invariant), i.e., the solution obtained using lsim is not approximative (modulu machine precision). ZoH sampling is commonly used to discretize continuous-time plant models that are to be controlled using a discrete-time controller.

FoH sampling is exact for linear systems with piece-wise linear inputs (ramp invariant), this is a good choice for simulation of systems with smooth continuous inputs.

To approximate the behavior of a continuous-time system well in the frequency domain, the :tustin (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-time controller.

The forward-Euler method generally requires the sample time to be very small relative to the time constants of the system, and its use is generally discouraged.

Classical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as $0.2 ≤ ωgc⋅Ts ≤ 0.6$ where $ωgc$ is the gain-crossover frequency (rad/s).

source
ControlSystemsBase.c2dMethod
Qd     = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts;             opt=:o)
+place(sys::StateSpace, p, opt=:o)

Calculate the observer gain matrix L such that A - LC has eigenvalues p.

If direct = true and opt = :o, the the observer gain K is calculated such that A - KCA has eigenvalues p, this option is to be used together with direct = true in observer_controller.

Note: only apply direct = true to discrete-time systems.

Ref: "Computer-Controlled Systems" pp 140.

Uses Ackermann's formula for SISO systems and place_knvd for MIMO systems.

Please note that this function can be numerically sensitive, solving the placement problem in extended precision might be beneficial.

source
ControlSystemsBase.place_knvdMethod
place_knvd(A::AbstractMatrix, B, λ; verbose = false, init = :s)

Robust pole placement using the algorithm from

"Robust Pole Assignment in Linear State Feedback", Kautsky, Nichols, Van Dooren

This implementation uses "method 0" for the X-step and the QR factorization for all factorizations.

This function will be called automatically when place is called with a MIMO system.

Arguments:

  • init: Determines the initialization strategy for the iterations for find the X matrix. Possible choices are :id (default), :rand, :s.
source
ControlSystemsBase.c2dFunction
sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)
+Gd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)

Convert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).

method = :tustin performs a bilinear transform with prewarp frequency w_prewarp.

  • w_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.

See also c2d_x0map

Extended help

ZoH sampling is exact for linear systems with piece-wise constant inputs (step invariant), i.e., the solution obtained using lsim is not approximative (modulu machine precision). ZoH sampling is commonly used to discretize continuous-time plant models that are to be controlled using a discrete-time controller.

FoH sampling is exact for linear systems with piece-wise linear inputs (ramp invariant), this is a good choice for simulation of systems with smooth continuous inputs.

To approximate the behavior of a continuous-time system well in the frequency domain, the :tustin (trapezoidal / bilinear) method may be most appropriate. In this case, the pre-warping argument can be used to ensure that the frequency response of the discrete-time system matches the continuous-time system at a given frequency. The tustin transformation alters the meaning of the state components, while ZoH and FoH preserve the meaning of the state components. The Tustin method is commonly used to discretize a continuous-time controller.

The forward-Euler method generally requires the sample time to be very small relative to the time constants of the system, and its use is generally discouraged.

Classical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as $0.2 ≤ ωgc⋅Ts ≤ 0.6$ where $ωgc$ is the gain-crossover frequency (rad/s).

source
ControlSystemsBase.c2dMethod
Qd     = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts;             opt=:o)
 Qd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)
 Qd     = c2d(sys::StateSpace{Discrete},   Qc::Matrix;                 opt=:o)
 Qd, Rd = c2d(sys::StateSpace{Discrete},   Qc::Matrix, Rc::Matrix;     opt=:o)

Sample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.

If opt = :o (default), the matrix is assumed to be a covariance matrix. The measurement covariance R may also be provided. If opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.

Note

Measurement covariance (here called Rc) is usually estimated in discrete time, and is in this case not dependent on the sample rate. Discretization of the measurement covariance only makes sense when a continuous-time controller has been designed and the closest corresponding discrete-time controller is desired.

The method used comes from theorem 5 in the reference below.

Ref: "Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering", Patrik Axelsson and Fredrik Gustafsson

On singular covariance matrices: The traditional double integrator with covariance matrix Q = diagm([0,σ²]) can not be sampled with this method. Instead, the input matrix ("Cholesky factor") of Q must be manually kept track of, e.g., the noise of variance σ² enters like N = [0, 1] which is sampled using ZoH and becomes Nd = [1/2 Ts^2; Ts] which results in the covariance matrix σ² * Nd * Nd'.

Example:

The following example designs a continuous-time LQR controller for a resonant system. This is simulated with OrdinaryDiffEq to allow the ODE integrator to also integrate the continuous-time LQR cost (the cost is added as an additional state variable). We then discretize both the system and the cost matrices and simulate the same thing. The discretization of an LQR contorller in this way is sometimes refered to as lqrd.

using ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test
@@ -66,14 +66,14 @@
     dot(x, Q, x) + dot(u, R, u)
 end
 cd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost
-@test cc ≈ cd rtol=0.01           # These should be similar
source
ControlSystemsBase.c2d_x0mapFunction
sysd, x0map = c2d_x0map(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)

Returns the discretization sysd of the system sys and a matrix x0map that transforms the initial conditions to the discrete domain by x0_discrete = x0map*[x0; u0]

See c2d for further details.

source
ControlSystemsBase.d2cFunction
d2c(sys::AbstractStateSpace{<:Discrete}, method::Symbol = :zoh; w_prewarp=0)

Convert discrete-time system to a continuous time system, assuming that the discrete-time system was discretized using method. Available methods are `:zoh, :fwdeuler´.

  • w_prewarp: Frequency for pre-warping when using the Tustin method, has no effect for other methods.

See also d2c_exact.

source
ControlSystemsBase.d2cFunction
Qc = d2c(sys::AbstractStateSpace{<:Discrete}, Qd::AbstractMatrix; opt=:o)

Resample discrete-time covariance matrix belonging to sys to the equivalent continuous-time matrix.

The method used comes from theorem 5 in the reference below.

If opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.

Ref: Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering Patrik Axelsson and Fredrik Gustafsson

source
ControlSystemsBase.d2c_exactFunction
d2c_exact(sys::AbstractStateSpace{<:Discrete}, method = :causal)

Translate a discrete-time system to a continuous-time system by one of the substitutions

  • $z^{-1} = e^{-sT_s}$ if method = :causal (default)
  • $z = e^{sT_s}$ if method = :acausal

The translation is exact in the frequency domain, i.e., the frequency response of the resulting continuous-time system is identical to the frequency response of the discrete-time system.

This method of translation is useful when analyzing hybrid continuous/discrete systems in the frequency domain and high accuracy is required.

The resulting system will be be a static system in feedback with pure delays. When method = :causal, the delays will be positive, resulting in a causal system that can be simulated in the time domain. When method = :acausal, the delays will be negative, resulting in an acausal system that can not be simulated in the time domain. The acausal translation results in a smaller system with half as many delay elements in the feedback path.

source
ControlSystemsBase.dabMethod
X,Y = dab(A,B,C)

Solves the Diophantine-Aryabhatta-Bezout identity

$AX + BY = C$, where $A, B, C, X$ and $Y$ are polynomials and $deg Y = deg A - 1$.

See Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark

source
ControlSystemsBase.c2d_x0mapFunction
sysd, x0map = c2d_x0map(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)

Returns the discretization sysd of the system sys and a matrix x0map that transforms the initial conditions to the discrete domain by x0_discrete = x0map*[x0; u0]

See c2d for further details.

source
ControlSystemsBase.d2cFunction
d2c(sys::AbstractStateSpace{<:Discrete}, method::Symbol = :zoh; w_prewarp=0)

Convert discrete-time system to a continuous time system, assuming that the discrete-time system was discretized using method. Available methods are `:zoh, :fwdeuler´.

  • w_prewarp: Frequency for pre-warping when using the Tustin method, has no effect for other methods.

See also d2c_exact.

source
ControlSystemsBase.d2cFunction
Qc = d2c(sys::AbstractStateSpace{<:Discrete}, Qd::AbstractMatrix; opt=:o)

Resample discrete-time covariance matrix belonging to sys to the equivalent continuous-time matrix.

The method used comes from theorem 5 in the reference below.

If opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.

Ref: Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering Patrik Axelsson and Fredrik Gustafsson

source
ControlSystemsBase.d2c_exactFunction
d2c_exact(sys::AbstractStateSpace{<:Discrete}, method = :causal)

Translate a discrete-time system to a continuous-time system by one of the substitutions

  • $z^{-1} = e^{-sT_s}$ if method = :causal (default)
  • $z = e^{sT_s}$ if method = :acausal

The translation is exact in the frequency domain, i.e., the frequency response of the resulting continuous-time system is identical to the frequency response of the discrete-time system.

This method of translation is useful when analyzing hybrid continuous/discrete systems in the frequency domain and high accuracy is required.

The resulting system will be be a static system in feedback with pure delays. When method = :causal, the delays will be positive, resulting in a causal system that can be simulated in the time domain. When method = :acausal, the delays will be negative, resulting in an acausal system that can not be simulated in the time domain. The acausal translation results in a smaller system with half as many delay elements in the feedback path.

source
ControlSystemsBase.dabMethod
X,Y = dab(A,B,C)

Solves the Diophantine-Aryabhatta-Bezout identity

$AX + BY = C$, where $A, B, C, X$ and $Y$ are polynomials and $deg Y = deg A - 1$.

See Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark

source
ControlSystemsBase.rstdMethod
R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR,AS)
 R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR)
-R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO)

Polynomial synthesis in discrete time.

Polynomial synthesis according to "Computer-Controlled Systems" ch 10 to design a controller $R(q) u(k) = T(q) r(k) - S(q) y(k)$

Inputs:

  • BPLUS : Part of open loop numerator
  • BMINUS : Part of open loop numerator
  • A : Open loop denominator
  • BM1 : Additional zeros
  • AM : Closed loop denominator
  • AO : Observer polynomial
  • AR : Pre-specified factor of R,

e.g integral part [1, -1]^k

  • AS : Pre-specified factor of S,

e.g notch filter [1, 0, w^2]

Outputs: R,S,T : Polynomials in controller

See function dab how the solution to the Diophantine- Aryabhatta-Bezout identity is chosen.

See Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark

source
ControlSystemsBase.zpconvMethod
zpc(a,r,b,s)

form conv(a,r) + conv(b,s) where the lengths of the polynomials are equalized by zero-padding such that the addition can be carried out

source
ControlSystemsBase.convert_pidparams_from_parallelMethod
Kp, Ti, Td = convert_pidparams_from_parallel(Kp, Ki, Kd, to_form)

Convert parameters from form :parallel to form to_form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_from_standardMethod
param_p, param_i, param_d = convert_pidparams_from_standard(Kp, Ti, Td, form)

Convert parameters to form form from :standard form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_from_toMethod
convert_pidparams_from_to(kp, ki, kd, from::Symbol, to::Symbol)

Convert PID parameters from from form to to form.

The from and to forms can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_to_parallelMethod
Kp, Ti, Td = convert_pidparams_to_parallel(param_p, param_i, param_d, form)

Convert parameters from form form to :parallel form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_to_standardMethod
Kp, Ti, Td = convert_pidparams_to_standard(param_p, param_i, param_d, form)

Convert parameters from form form to :standard form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_ds)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.laglinkMethod
laglink(a, M; [Ts])

Returns a phase retarding link, the rule of thumb a = 0.1ωc guarantees less than 6 degrees phase margin loss. The bode curve will go from M, bend down at a/M and level out at 1 for frequencies > a

\[\dfrac{s + a}{s + a/M} = M \dfrac{1 + s/a}{1 + sM/a}\]

source
ControlSystemsBase.leadlinkFunction
leadlink(b, N, K=1; [Ts])

Returns a phase advancing link, the top of the phase curve is located at ω = b√(N) where the link amplification is K√(N) The bode curve will go from K, bend up at b and level out at KN for frequencies > bN

The phase advance at ω = b√(N) can be plotted as a function of N with leadlinkcurve()

Values of N < 1 will give a phase retarding link.

\[KN \dfrac{s + b}{s + bN} = K \dfrac{1 + s/b}{1 + s/(bN)}\]

See also leadlinkat laglink

source
ControlSystemsBase.leadlinkatFunction
leadlinkat(ω, N, K=1; [Ts])

Returns a phase advancing link, the top of the phase curve is located at ω where the link amplification is K√(N) The bode curve will go from K, bend up at ω/√(N) and level out at KN for frequencies > ω√(N)

The phase advance at ω can be plotted as a function of N with leadlinkcurve()

Values of N < 1 will give a phase retarding link.

See also leadlink laglink

source
ControlSystemsBase.leadlinkcurveFunction
leadlinkcurve(start=1)

Plot the phase advance as a function of N for a lead link (phase advance link) If an input argument start is given, the curve is plotted from start to 10, else from 1 to 10.

See also leadlink, leadlinkat

source
ControlSystemsBase.loopshapingPIMethod
C, kp, ki, fig, CF = loopshapingPI(P, ωp; ϕl, rl, phasemargin, form=:standard, doplot=false, Tf, F)

Selects the parameters of a PI-controller (on parallel form) such that the Nyquist curve of P at the frequency ωp is moved to rl exp(i ϕl)

The parameters can be returned as one of several common representations chosen by form, the options are

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

If phasemargin is supplied (in degrees), ϕl is selected such that the curve is moved to an angle of phasemargin - 180 degrees

If no rl is given, the magnitude of the curve at ωp is kept the same and only the phase is affected, the same goes for ϕl if no phasemargin is given.

  • Tf: An optional time constant for second-order measurement noise filter on the form tf(1, [Tf^2, 2*Tf/sqrt(2), 1]) to make the controller strictly proper.
  • F: A pre-designed filter to use instead of the default second-order filter that is used if Tf is given.
  • doplot plot the gangoffourplot and nyquistplot of the system.

See also loopshapingPID, pidplots, stabregionPID and placePI.

source
ControlSystemsBase.loopshapingPIDMethod
C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt = 1.3, ϕt=75, form=:standard, doplot=false, lb=-10, ub=10, Tf = 1/1000ω, F = nothing)

Selects the parameters of a PID-controller such that the Nyquist curve of the loop-transfer function $L = PC$ at the frequency ω is tangent to the circle where the magnitude of $T = PC / (1+PC)$ equals Mt. ϕt denotes the positive angle in degrees between the real axis and the tangent point.

The default values for Mt and ϕt are chosen to give a good design for processes with inertia, and may need tuning for simpler processes.

The gain of the resulting controller is generally increasing with increasing ω and Mt.

Arguments:

  • P: A SISO plant.
  • ω: The specification frequency.
  • Mt: The magnitude of the complementary sensitivity function at the specification frequency, $|T(iω)|$.
  • ϕt: The positive angle in degrees between the real axis and the tangent point.
  • doplot: If true, gang of four and Nyquist plots will be returned in fig.
  • lb: log10 of lower bound for kd.
  • ub: log10 of upper bound for kd.
  • Tf: Time constant for second-order measurement noise filter on the form tf(1, [Tf^2, 2*Tf/sqrt(2), 1]) to make the controller strictly proper. A practical controller typically sets this time constant slower than the default, e.g., Tf = 1/100ω or Tf = 1/10ω
  • F: A pre-designed filter to use instead of the default second-order filter.

The parameters can be returned as one of several common representations chosen by form, the options are

  • :standard - $K_p(1 + 1/(T_i s) + T_ds)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

See also loopshapingPI, pidplots, stabregionPID and placePI.

Example:

P  = tf(1, [1,0,0]) # A double integrator
+R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO)

Polynomial synthesis in discrete time.

Polynomial synthesis according to "Computer-Controlled Systems" ch 10 to design a controller $R(q) u(k) = T(q) r(k) - S(q) y(k)$

Inputs:

  • BPLUS : Part of open loop numerator
  • BMINUS : Part of open loop numerator
  • A : Open loop denominator
  • BM1 : Additional zeros
  • AM : Closed loop denominator
  • AO : Observer polynomial
  • AR : Pre-specified factor of R,

e.g integral part [1, -1]^k

  • AS : Pre-specified factor of S,

e.g notch filter [1, 0, w^2]

Outputs: R,S,T : Polynomials in controller

See function dab how the solution to the Diophantine- Aryabhatta-Bezout identity is chosen.

See Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark

source
ControlSystemsBase.zpconvMethod
zpc(a,r,b,s)

form conv(a,r) + conv(b,s) where the lengths of the polynomials are equalized by zero-padding such that the addition can be carried out

source
ControlSystemsBase.convert_pidparams_from_parallelMethod
Kp, Ti, Td = convert_pidparams_from_parallel(Kp, Ki, Kd, to_form)

Convert parameters from form :parallel to form to_form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_from_standardMethod
param_p, param_i, param_d = convert_pidparams_from_standard(Kp, Ti, Td, form)

Convert parameters to form form from :standard form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_from_toMethod
convert_pidparams_from_to(kp, ki, kd, from::Symbol, to::Symbol)

Convert PID parameters from from form to to form.

The from and to forms can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_to_parallelMethod
Kp, Ti, Td = convert_pidparams_to_parallel(param_p, param_i, param_d, form)

Convert parameters from form form to :parallel form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.convert_pidparams_to_standardMethod
Kp, Ti, Td = convert_pidparams_to_standard(param_p, param_i, param_d, form)

Convert parameters from form form to :standard form.

The form can be chosen as one of the following

  • :standard - $K_p(1 + 1/(T_i s) + T_ds)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$
source
ControlSystemsBase.laglinkMethod
laglink(a, M; [Ts])

Returns a phase retarding link, the rule of thumb a = 0.1ωc guarantees less than 6 degrees phase margin loss. The bode curve will go from M, bend down at a/M and level out at 1 for frequencies > a

\[\dfrac{s + a}{s + a/M} = M \dfrac{1 + s/a}{1 + sM/a}\]

source
ControlSystemsBase.leadlinkFunction
leadlink(b, N, K=1; [Ts])

Returns a phase advancing link, the top of the phase curve is located at ω = b√(N) where the link amplification is K√(N) The bode curve will go from K, bend up at b and level out at KN for frequencies > bN

The phase advance at ω = b√(N) can be plotted as a function of N with leadlinkcurve()

Values of N < 1 will give a phase retarding link.

\[KN \dfrac{s + b}{s + bN} = K \dfrac{1 + s/b}{1 + s/(bN)}\]

See also leadlinkat laglink

source
ControlSystemsBase.leadlinkatFunction
leadlinkat(ω, N, K=1; [Ts])

Returns a phase advancing link, the top of the phase curve is located at ω where the link amplification is K√(N) The bode curve will go from K, bend up at ω/√(N) and level out at KN for frequencies > ω√(N)

The phase advance at ω can be plotted as a function of N with leadlinkcurve()

Values of N < 1 will give a phase retarding link.

See also leadlink laglink

source
ControlSystemsBase.leadlinkcurveFunction
leadlinkcurve(start=1)

Plot the phase advance as a function of N for a lead link (phase advance link) If an input argument start is given, the curve is plotted from start to 10, else from 1 to 10.

See also leadlink, leadlinkat

source
ControlSystemsBase.loopshapingPIMethod
C, kp, ki, fig, CF = loopshapingPI(P, ωp; ϕl, rl, phasemargin, form=:standard, doplot=false, Tf, F)

Selects the parameters of a PI-controller (on parallel form) such that the Nyquist curve of P at the frequency ωp is moved to rl exp(i ϕl)

The parameters can be returned as one of several common representations chosen by form, the options are

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

If phasemargin is supplied (in degrees), ϕl is selected such that the curve is moved to an angle of phasemargin - 180 degrees

If no rl is given, the magnitude of the curve at ωp is kept the same and only the phase is affected, the same goes for ϕl if no phasemargin is given.

  • Tf: An optional time constant for second-order measurement noise filter on the form tf(1, [Tf^2, 2*Tf/sqrt(2), 1]) to make the controller strictly proper.
  • F: A pre-designed filter to use instead of the default second-order filter that is used if Tf is given.
  • doplot plot the gangoffourplot and nyquistplot of the system.

See also loopshapingPID, pidplots, stabregionPID and placePI.

source
ControlSystemsBase.loopshapingPIDMethod
C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt = 1.3, ϕt=75, form=:standard, doplot=false, lb=-10, ub=10, Tf = 1/1000ω, F = nothing)

Selects the parameters of a PID-controller such that the Nyquist curve of the loop-transfer function $L = PC$ at the frequency ω is tangent to the circle where the magnitude of $T = PC / (1+PC)$ equals Mt. ϕt denotes the positive angle in degrees between the real axis and the tangent point.

The default values for Mt and ϕt are chosen to give a good design for processes with inertia, and may need tuning for simpler processes.

The gain of the resulting controller is generally increasing with increasing ω and Mt.

Arguments:

  • P: A SISO plant.
  • ω: The specification frequency.
  • Mt: The magnitude of the complementary sensitivity function at the specification frequency, $|T(iω)|$.
  • ϕt: The positive angle in degrees between the real axis and the tangent point.
  • doplot: If true, gang of four and Nyquist plots will be returned in fig.
  • lb: log10 of lower bound for kd.
  • ub: log10 of upper bound for kd.
  • Tf: Time constant for second-order measurement noise filter on the form tf(1, [Tf^2, 2*Tf/sqrt(2), 1]) to make the controller strictly proper. A practical controller typically sets this time constant slower than the default, e.g., Tf = 1/100ω or Tf = 1/10ω
  • F: A pre-designed filter to use instead of the default second-order filter.

The parameters can be returned as one of several common representations chosen by form, the options are

  • :standard - $K_p(1 + 1/(T_i s) + T_ds)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

See also loopshapingPI, pidplots, stabregionPID and placePI.

Example:

P  = tf(1, [1,0,0]) # A double integrator
 Mt = 1.3  # Maximum magnitude of complementary sensitivity
 ω  = 1    # Frequency at which the specification holds
-C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt = 75, doplot=true)
source
ControlSystemsBase.pidFunction
C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts], filter_order=2, d=1/√(2))

Calculates and returns a PID controller.

The form can be chosen as one of the following (determines how the arguments param_p, param_i, param_d are interpreted)

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

If state_space is set to true, either Kd has to be zero or a positive Tf has to be provided for creating a filter on the input to allow for a state-space realization. A balanced state-space realization is returned, unless balance = false.

Filter

If Tf is supplied, a filter is added, the filter used is either

  • filter_order = 2 (default): $1 / ((sT_f)^2/(4d^2) + sT_f + 1)$ in series with the controller. Note: this parametrization of the filter differs in behavior from the common parameterizaiton $1/(s^2 + 2dws + w^2)$ as the parameters vary, the former maintains an almost fixed bandwidth while d varies, while the latter maintains a fixed distance of the poles from the origin.
  • filter_order = 1: $1 / (1 + sT_f)$ applied to the derivative term only

$T_f$ can typically be chosen as $T_i/N$ for a PI controller and $T_d/N$ for a PID controller, and N is commonly in the range 2 to 20. With a second-order filter, d controls the damping. d = 1/√(2) gives a Butterworth configuration of the poles, and d=1 gives a critically damped filter (no overshoot). d above one may be used, although d > 1 yields an increasingly over-damped filter (this parametrization does not send one pole to the origin $d → ∞$ like the $(ω,ζ)$ parametrization does).

Discrete-time

For a discrete controller a positive Ts can be supplied. In this case, the continuous-time controller is discretized using the Tustin method.

Examples

C1 = pid(3.3, 1, 2)                             # Kd≠0 works without filter in tf form
+C, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt = 75, doplot=true)
source
ControlSystemsBase.pidFunction
C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts], filter_order=2, d=1/√(2))

Calculates and returns a PID controller.

The form can be chosen as one of the following (determines how the arguments param_p, param_i, param_d are interpreted)

  • :standard - $K_p(1 + 1/(T_i s) + T_d s)$
  • :series - $K_c(1 + 1/(τ_i s))(τ_d s + 1)$
  • :parallel - $K_p + K_i/s + K_d s$

If state_space is set to true, either Kd has to be zero or a positive Tf has to be provided for creating a filter on the input to allow for a state-space realization. A balanced state-space realization is returned, unless balance = false.

Filter

If Tf is supplied, a filter is added, the filter used is either

  • filter_order = 2 (default): $1 / ((sT_f)^2/(4d^2) + sT_f + 1)$ in series with the controller. Note: this parametrization of the filter differs in behavior from the common parameterizaiton $1/(s^2 + 2dws + w^2)$ as the parameters vary, the former maintains an almost fixed bandwidth while d varies, while the latter maintains a fixed distance of the poles from the origin.
  • filter_order = 1: $1 / (1 + sT_f)$ applied to the derivative term only

$T_f$ can typically be chosen as $T_i/N$ for a PI controller and $T_d/N$ for a PID controller, and N is commonly in the range 2 to 20. With a second-order filter, d controls the damping. d = 1/√(2) gives a Butterworth configuration of the poles, and d=1 gives a critically damped filter (no overshoot). d above one may be used, although d > 1 yields an increasingly over-damped filter (this parametrization does not send one pole to the origin $d → ∞$ like the $(ω,ζ)$ parametrization does).

Discrete-time

For a discrete controller a positive Ts can be supplied. In this case, the continuous-time controller is discretized using the Tustin method.

Examples

C1 = pid(3.3, 1, 2)                             # Kd≠0 works without filter in tf form
 C2 = pid(3.3, 1, 2; Tf=0.3, state_space=true)   # In statespace a filter is needed
-C3 = pid(2.,  3, 0; Ts=0.4, state_space=true)   # Discrete

The functions pid_tf and pid_ss are also exported. They take the same parameters and is what is actually called in pid based on the state_space parameter. See also pid_2dof for a 2DOF controller with inputs [r; y] and outputs u.

source
ControlSystemsBase.pid_2dofMethod
C = pid_2dof(param_p, param_i, [param_d]; form=:standard, state_space=true, N = 10, [Ts], b=1, c=0, disc=:tustin)

Calculates and returns a PID controller on 2DOF form with inputs [r; y] and outputs u where r is the reference signal, y is the measured output and u is the control signal.

Belowm we show two different depections of the contorller, one as a 2-input system (left) and one where the tw internal SISO systems of the controller are shown (right).

                                ┌──────┐                      
+C3 = pid(2.,  3, 0; Ts=0.4, state_space=true)   # Discrete

The functions pid_tf and pid_ss are also exported. They take the same parameters and is what is actually called in pid based on the state_space parameter. See also pid_2dof for a 2DOF controller with inputs [r; y] and outputs u.

source
ControlSystemsBase.pid_2dofMethod
C = pid_2dof(param_p, param_i, [param_d]; form=:standard, state_space=true, N = 10, [Ts], b=1, c=0, disc=:tustin)

Calculates and returns a PID controller on 2DOF form with inputs [r; y] and outputs u where r is the reference signal, y is the measured output and u is the control signal.

Belowm we show two different depections of the contorller, one as a 2-input system (left) and one where the tw internal SISO systems of the controller are shown (right).

                                ┌──────┐                      
                              r  │      │                      
                             ───►│  Cr  ├────┐                 
 r  ┌─────┐     ┌─────┐          │      │    │    ┌─────┐      
@@ -88,19 +88,19 @@
                              └───────────────────────────┘     

The form can be chosen as one of the following (determines how the arguments param_p, param_i, param_d are interpreted)

  • :standard - $K_p*(br-y + (r-y)/(T_i s) + T_d s (cr-y)/(T_f s + 1))$

  • :parallel - $K_p*(br-y) + K_i (r-y)/s + K_d s (cr-y)/(Tf s + 1)$

  • b is a set-point weighting for the proportional term

  • c is a set-point weighting for the derivative term, this defaults to 0.

  • If both b and c are set to zero, the feedforward path of the controller will be strictly proper.

  • Tf is a time constant for a filter on the derivative term, this defaults to Td/N where N is set to 10. Instead of passing Tf one can also pass N directly. The proportional term is not affected by this filter. Please note: this derivative filter is not the same as the one used in the pid function, where the filter is of second order and applied in series with the contorller, i.e., it affects all three PID terms.

  • A PD controller is constructed by setting param_i to zero.

  • A balanced state-space realization is returned, unless balance = false

  • If Ts is supplied, the controller is discretized using the method disc (defaults to :tustin).

This controller has negative feedback built in, and the closed-loop system from r to y is thus formed as

Cr, Cy = C[1, 1], C[1, 2]
 feedback(P, Cy, pos_feedback=true)*Cr                    # Alternative 1
 feedback(P, -Cy)*Cr                                      # Alternative 2
-feedback(P, C, U2=2, W2=1, W1=[], pos_feedback=true) # Alternative 3, less pretty but more efficient, returns smaller realization
source
ControlSystemsBase.pidplotsMethod
pidplots(P, args...; params_p, params_i, params_d=0, form=:standard, ω=0, grid=false, kwargs...)

Display the relevant plots related to closing the loop around process P with a PID controller supplied in params on one of the following forms:

  • :standard - Kp*(1 + 1/(Ti*s) + Td*s)
  • :series - Kc*(1 + 1/(τi*s))*(τd*s + 1)
  • :parallel - Kp + Ki/s + Kd*s

The sent in values can be arrays to evaluate multiple different controllers, and if grid=true it will be a grid search over all possible combinations of the values.

Available plots are :gof for Gang of four, :nyquist, :controller for a bode plot of the controller TF and :pz for pole-zero maps and should be supplied as additional arguments to the function.

One can also supply a frequency vector ω to be used in Bode and Nyquist plots.

See also loopshapingPI, stabregionPID

source
ControlSystemsBase.placePIMethod
C, kp, ki = placePI(P, ω₀, ζ; form=:standard)

Selects the parameters of a PI-controller such that the poles of closed loop between P and C are placed to match the poles of s^2 + 2ζω₀s + ω₀^2.

The parameters can be returned as one of several common representations chose by form, the options are

  • :standard - $K_p(1 + 1/(T_i s))$
  • :series - $K_c(1 + 1/(τ_i s))$ (equivalent to above for PI controllers)
  • :parallel - $K_p + K_i/s$

C is the returned transfer function of the controller and params is a named tuple containing the parameters. The parameters can be accessed as params.Kp or params["Kp"] from the named tuple, or they can be unpacked using Kp, Ti, Td = values(params).

See also loopshapingPI

source
ControlSystemsBase.stabregionPIDFunction
kp, ki, fig = stabregionPID(P, [ω]; kd=0, doplot=false, form=:standard)

Segments of the curve generated by this program is the boundary of the stability region for a process with transfer function P(s) The provided derivative gain is expected on parallel form, i.e., the form kp + ki/s + kd s, but the result can be transformed to any form given by the form keyword. The curve is found by analyzing

\[P(s)C(s) = -1 ⟹ \\ +feedback(P, C, U2=2, W2=1, W1=[], pos_feedback=true) # Alternative 3, less pretty but more efficient, returns smaller realization

source
ControlSystemsBase.pidplotsMethod
pidplots(P, args...; params_p, params_i, params_d=0, form=:standard, ω=0, grid=false, kwargs...)

Display the relevant plots related to closing the loop around process P with a PID controller supplied in params on one of the following forms:

  • :standard - Kp*(1 + 1/(Ti*s) + Td*s)
  • :series - Kc*(1 + 1/(τi*s))*(τd*s + 1)
  • :parallel - Kp + Ki/s + Kd*s

The sent in values can be arrays to evaluate multiple different controllers, and if grid=true it will be a grid search over all possible combinations of the values.

Available plots are :gof for Gang of four, :nyquist, :controller for a bode plot of the controller TF and :pz for pole-zero maps and should be supplied as additional arguments to the function.

One can also supply a frequency vector ω to be used in Bode and Nyquist plots.

See also loopshapingPI, stabregionPID

source
ControlSystemsBase.placePIMethod
C, kp, ki = placePI(P, ω₀, ζ; form=:standard)

Selects the parameters of a PI-controller such that the poles of closed loop between P and C are placed to match the poles of s^2 + 2ζω₀s + ω₀^2.

The parameters can be returned as one of several common representations chose by form, the options are

  • :standard - $K_p(1 + 1/(T_i s))$
  • :series - $K_c(1 + 1/(τ_i s))$ (equivalent to above for PI controllers)
  • :parallel - $K_p + K_i/s$

C is the returned transfer function of the controller and params is a named tuple containing the parameters. The parameters can be accessed as params.Kp or params["Kp"] from the named tuple, or they can be unpacked using Kp, Ti, Td = values(params).

See also loopshapingPI

source
ControlSystemsBase.stabregionPIDFunction
kp, ki, fig = stabregionPID(P, [ω]; kd=0, doplot=false, form=:standard)

Segments of the curve generated by this program is the boundary of the stability region for a process with transfer function P(s) The provided derivative gain is expected on parallel form, i.e., the form kp + ki/s + kd s, but the result can be transformed to any form given by the form keyword. The curve is found by analyzing

\[P(s)C(s) = -1 ⟹ \\ |PC| = |P| |C| = 1 \\ -arg(P) + arg(C) = -π\]

If P is a function (e.g. s -> exp(-sqrt(s)) ), the stability of feedback loops using PI-controllers can be analyzed for processes with models with arbitrary analytic functions See also loopshapingPI, loopshapingPID, pidplots

source
ControlSystemsBase.sminrealMethod
sminreal(sys)

Compute the structurally minimal realization of the state-space system sys. A structurally minimal realization is one where only states that can be determined to be uncontrollable and unobservable based on the location of 0s in sys are removed.

Systems with numerical noise in the coefficients, e.g., noise on the order of eps require truncation to zero to be affected by structural simplification, e.g.,

trunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0
+arg(P) + arg(C) = -π\]

If P is a function (e.g. s -> exp(-sqrt(s)) ), the stability of feedback loops using PI-controllers can be analyzed for processes with models with arbitrary analytic functions See also loopshapingPI, loopshapingPID, pidplots

source
ControlSystemsBase.sminrealMethod
sminreal(sys)

Compute the structurally minimal realization of the state-space system sys. A structurally minimal realization is one where only states that can be determined to be uncontrollable and unobservable based on the location of 0s in sys are removed.

Systems with numerical noise in the coefficients, e.g., noise on the order of eps require truncation to zero to be affected by structural simplification, e.g.,

trunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0
 trunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)
-sminreal(sys)

In contrast to minreal, which performs pole-zero cancellation using linear-algebra operations, has an 𝑂(nₓ^3) complexity and is subject to numerical tolerances, sminreal is computationally very cheap and numerically exact (operates on integers). However, the ability of sminreal to reduce the order of the model is much less powerful.

See also minreal.

source
ControlSystemsBase.add_inputFunction
add_input(sys::AbstractStateSpace, B2::AbstractArray, D2 = 0)

Add inputs to sys by forming

\[\begin{aligned} +sminreal(sys)

In contrast to minreal, which performs pole-zero cancellation using linear-algebra operations, has an 𝑂(nₓ^3) complexity and is subject to numerical tolerances, sminreal is computationally very cheap and numerically exact (operates on integers). However, the ability of sminreal to reduce the order of the model is much less powerful.

See also minreal.

source
ControlSystemsBase.add_inputFunction
add_input(sys::AbstractStateSpace, B2::AbstractArray, D2 = 0)

Add inputs to sys by forming

\[\begin{aligned} x' &= Ax + [B \; B_2]u \\ y &= Cx + [D \; D_2]u \\ \end{aligned}\]

If B2 is an integer it will be interpreted as an index and an input matrix containing a single 1 at the specified index will be used.

Example: The following example forms an innovation model that takes innovations as inputs

G   = ssrand(2,2,3, Ts=1)
 K   = kalman(G, I(G.nx), I(G.ny))
-sys = add_input(G, K)
source
ControlSystemsBase.add_outputFunction
add_output(sys::AbstractStateSpace, C2::AbstractArray, D2 = 0)

Add outputs to sys by forming

\[\begin{aligned} +sys = add_input(G, K)

source
ControlSystemsBase.add_outputFunction
add_output(sys::AbstractStateSpace, C2::AbstractArray, D2 = 0)

Add outputs to sys by forming

\[\begin{aligned} x' &= Ax + Bu \\ y &= [C; C_2]x + [D; D_2]u \\ -\end{aligned}\]

If C2 is an integer it will be interpreted as an index and an output matrix containing a single 1 at the specified index will be used.

When called with C2 = I(sys.nx), this function is in some settings known to as augstate.

source
ControlSystemsBase.feedbackMethod
feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
+\end{aligned}\]

If C2 is an integer it will be interpreted as an index and an output matrix containing a single 1 at the specified index will be used.

When called with C2 = I(sys.nx), this function is in some settings known to as augstate.

source
ControlSystemsBase.feedbackMethod
feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
          U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],
          Wperm=:, Zperm=:, pos_feedback::Bool=false)

Basic use feedback(sys1, sys2) forms the (negative) feedback interconnection

           ┌──────────────┐
 ◄──────────┤     sys1     │◄──── Σ ◄──────
@@ -118,11 +118,11 @@
  │            ┌──────────────┐            │
  └──► u2─────►│     sys2     ├───────►y2──┘
       w2─────►│              ├───────►z2
-              └──────────────┘
  • U1, W1 specify the indices of the input signals of sys1 corresponding to u1 and w1. W1 contains the indices of the inputs of sys1 that are included among the inputs to the returned system, i.e., external inputs.
  • Y1, Z1 specify the indices of the output signals of sys1 corresponding to y1 and z1. Z1 contains the indices of the outputs ofsys1` that are included among the outputs of the returned system, i.e., external outputs.
  • U2, W2, Y2, Z2 specify the corresponding signals of sys2. W2 contains the indices of the inputs ofsys2that are included among the inputs to the returned system, i.e., external inputs.Z2contains the indices of the outputs ofsys2` that are included among the outputs of the returned system, i.e., external outputs.

Specify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.

Negative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).

See also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.

The manual section From block diagrams to code contains higher-level instructions on how to use this function. See also RobustAndOptimalControl.jl: Connections using named signals for a higher-level interface.

See Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.

source
ControlSystemsBase.feedbackMethod
feedback(sys)
+              └──────────────┘
  • U1, W1 specify the indices of the input signals of sys1 corresponding to u1 and w1. W1 contains the indices of the inputs of sys1 that are included among the inputs to the returned system, i.e., external inputs.
  • Y1, Z1 specify the indices of the output signals of sys1 corresponding to y1 and z1. Z1 contains the indices of the outputs ofsys1` that are included among the outputs of the returned system, i.e., external outputs.
  • U2, W2, Y2, Z2 specify the corresponding signals of sys2. W2 contains the indices of the inputs ofsys2that are included among the inputs to the returned system, i.e., external inputs.Z2contains the indices of the outputs ofsys2` that are included among the outputs of the returned system, i.e., external outputs.

Specify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.

Negative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).

See also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.

The manual section From block diagrams to code contains higher-level instructions on how to use this function. See also RobustAndOptimalControl.jl: Connections using named signals for a higher-level interface.

See Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.

source
ControlSystemsBase.feedbackMethod
feedback(sys)
 feedback(sys1, sys2)

For a general LTI-system, feedback forms the negative feedback interconnection

>-+ sys1 +-->
   |      |
- (-)sys2 +

If no second system is given, negative identity feedback is assumed

source
ControlSystemsBase.feedback2dofMethod
feedback2dof(P,R,S,T)
-feedback2dof(B,A,R,S,T)
  • Return BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively
  • Return BT/(AR+ST)
source
ControlSystemsBase.feedback2dofMethod
feedback2dof(P, C, F)

Return the transfer function P(F+C)/(1+PC) which is the closed-loop system with process P, controller C and feedforward filter F from reference to control signal (by-passing C).

         +-------+
+ (-)sys2 +

If no second system is given, negative identity feedback is assumed

source
ControlSystemsBase.feedback2dofMethod
feedback2dof(P,R,S,T)
+feedback2dof(B,A,R,S,T)
  • Return BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively
  • Return BT/(AR+ST)
source
ControlSystemsBase.feedback2dofMethod
feedback2dof(P, C, F)

Return the transfer function P(F+C)/(1+PC) which is the closed-loop system with process P, controller C and feedforward filter F from reference to control signal (by-passing C).

         +-------+
          |       |
    +----->   F   +----+
    |     |       |    |
@@ -133,7 +133,7 @@
       |  |       |         |       |   |
       |  +-------+         +-------+   |
       |                                |
-      +--------------------------------+
source
ControlSystemsBase.lftFunction
lft(G, Δ, type=:l)

Lower and upper linear fractional transformation between systems G and Δ.

Specify :l lor lower LFT, and :u for upper LFT.

G must have more inputs and outputs than Δ has outputs and inputs.

For details, see Chapter 9.1 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998

source
ControlSystemsBase.starprodMethod
starprod(sys1, sys2, dimu, dimy)

Compute the Redheffer star product.

length(U1) = length(Y2) = dimu and length(Y1) = length(U2) = dimy

For details, see Chapter 9.3 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998

source
ControlSystemsBase.G_CSMethod
G_CS(P, C)

The closed-loop transfer function from (-) measurement noise or (+) reference to control signal. Technically, the transfer function is given by (1 + CP)⁻¹C so SC would be a better, but nonstandard name.

         ▲
+      +--------------------------------+
source
ControlSystemsBase.lftFunction
lft(G, Δ, type=:l)

Lower and upper linear fractional transformation between systems G and Δ.

Specify :l lor lower LFT, and :u for upper LFT.

G must have more inputs and outputs than Δ has outputs and inputs.

For details, see Chapter 9.1 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998

source
ControlSystemsBase.starprodMethod
starprod(sys1, sys2, dimu, dimy)

Compute the Redheffer star product.

length(U1) = length(Y2) = dimu and length(Y1) = length(U2) = dimy

For details, see Chapter 9.3 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998

source
ControlSystemsBase.G_CSMethod
G_CS(P, C)

The closed-loop transfer function from (-) measurement noise or (+) reference to control signal. Technically, the transfer function is given by (1 + CP)⁻¹C so SC would be a better, but nonstandard name.

         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -143,7 +143,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.G_PSMethod
G_PS(P, C)

The closed-loop transfer function from load disturbance to plant output. Technically, the transfer function is given by (1 + PC)⁻¹P so SP would be a better, but nonstandard name.

         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.G_PSMethod
G_PS(P, C)

The closed-loop transfer function from load disturbance to plant output. Technically, the transfer function is given by (1 + PC)⁻¹P so SP would be a better, but nonstandard name.

         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -153,7 +153,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.comp_sensitivityMethod

See output_comp_sensitivity

         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.comp_sensitivityMethod

See output_comp_sensitivity

         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -163,7 +163,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.extended_gangoffourFunction
extended_gangoffour(P, C, pos=true)

Returns a single statespace system that maps

  • w1 reference or measurement noise
  • w2 load disturbance

to

  • z1 control error
  • z2 control input
      z1          z2
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.extended_gangoffourFunction
extended_gangoffour(P, C, pos=true)

Returns a single statespace system that maps

  • w1 reference or measurement noise
  • w2 load disturbance

to

  • z1 control error
  • z2 control input
      z1          z2
       ▲  ┌─────┐  ▲      ┌─────┐
       │  │     │  │      │     │
 w1──+─┴─►│  C  ├──┴───+─►│  P  ├─┐
@@ -185,7 +185,7 @@
 PS = G[1:P.ny,     P.ny+1:end]
 CS = G[P.ny+1:end, 1:P.ny]
 T  = G[P.ny+1:end, P.ny+1:end] # Input complimentary sensitivity function

The gang of four can be plotted like so

Gcl = extended_gangoffour(G, C) # Form closed-loop system
-bodeplot(Gcl, lab=["S" "PS" "CS" "T"], plotphase=false) |> display # Plot gang of four

Note, the last input of Gcl is the negative of the PS and T transfer functions from gangoffour2. To get a transfer matrix with the same sign as G_PS and input_comp_sensitivity, call extended_gangoffour(P, C, pos=false). See glover_mcfarlane from RobustAndOptimalControl.jl for an extended example. See also ncfmargin and feedback_control from RobustAndOptimalControl.jl.

source
ControlSystemsBase.input_comp_sensitivityMethod
input_comp_sensitivity(P,C)

Transfer function from load disturbance to control signal.

  • "Input" signifies that the transfer function is from the input of the plant.
  • "Complimentary" signifies that the transfer function is to an output (in this case controller output)
         ▲
+bodeplot(Gcl, lab=["S" "PS" "CS" "T"], plotphase=false) |> display # Plot gang of four

Note, the last input of Gcl is the negative of the PS and T transfer functions from gangoffour2. To get a transfer matrix with the same sign as G_PS and input_comp_sensitivity, call extended_gangoffour(P, C, pos=false). See glover_mcfarlane from RobustAndOptimalControl.jl for an extended example. See also ncfmargin and feedback_control from RobustAndOptimalControl.jl.

source
ControlSystemsBase.input_comp_sensitivityMethod
input_comp_sensitivity(P,C)

Transfer function from load disturbance to control signal.

  • "Input" signifies that the transfer function is from the input of the plant.
  • "Complimentary" signifies that the transfer function is to an output (in this case controller output)
         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -195,7 +195,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.input_sensitivityMethod
input_sensitivity(P, C)

Transfer function from load disturbance to total plant input.

  • "Input" signifies that the transfer function is from the input of the plant.
         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.input_sensitivityMethod
input_sensitivity(P, C)

Transfer function from load disturbance to total plant input.

  • "Input" signifies that the transfer function is from the input of the plant.
         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -205,7 +205,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.output_comp_sensitivityMethod
output_comp_sensitivity(P,C)

Transfer function from measurement noise / reference to plant output.

  • "output" signifies that the transfer function is from the output of the plant.
  • "Complimentary" signifies that the transfer function is to an output (in this case plant output)
         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.output_comp_sensitivityMethod
output_comp_sensitivity(P,C)

Transfer function from measurement noise / reference to plant output.

  • "output" signifies that the transfer function is from the output of the plant.
  • "Complimentary" signifies that the transfer function is to an output (in this case plant output)
         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -215,7 +215,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.output_sensitivityMethod
output_sensitivity(P, C)

Transfer function from measurement noise / reference to control error.

  • "output" signifies that the transfer function is from the output of the plant.
         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.output_sensitivityMethod
output_sensitivity(P, C)

Transfer function from measurement noise / reference to control error.

  • "output" signifies that the transfer function is from the output of the plant.
         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -225,7 +225,7 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.sensitivityMethod

See output_sensitivity

The output sensitivity function $S_o = (I + PC)^{-1}$ is the transfer function from a reference input to control error, while the input sensitivity function $S_i = (I + CP)^{-1}$ is the transfer function from a disturbance at the plant input to the total plant input. For SISO systems, input and output sensitivity functions are equal. In general, we want to minimize the sensitivity function to improve robustness and performance, but practical constraints always cause the sensitivity function to tend to 1 for high frequencies. A robust design minimizes the peak of the sensitivity function, $M_S$. The peak magnitude of $S$ is the inverse of the distance between the open-loop Nyquist curve and the critical point -1. Upper bounding the sensitivity peak $M_S$ gives lower-bounds on phase and gain margins according to

\[ϕ_m ≥ 2\text{sin}^{-1}(\frac{1}{2M_S}), g_m ≥ \frac{M_S}{M_S-1}\]

Generally, bounding $M_S$ is a better objective than looking at gain and phase margins due to the possibility of combined gain and pahse variations, which may lead to poor robustness despite large gain and pahse margins.

         ▲
+                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.sensitivityMethod

See output_sensitivity

The output sensitivity function $S_o = (I + PC)^{-1}$ is the transfer function from a reference input to control error, while the input sensitivity function $S_i = (I + CP)^{-1}$ is the transfer function from a disturbance at the plant input to the total plant input. For SISO systems, input and output sensitivity functions are equal. In general, we want to minimize the sensitivity function to improve robustness and performance, but practical constraints always cause the sensitivity function to tend to 1 for high frequencies. A robust design minimizes the peak of the sensitivity function, $M_S$. The peak magnitude of $S$ is the inverse of the distance between the open-loop Nyquist curve and the critical point -1. Upper bounding the sensitivity peak $M_S$ gives lower-bounds on phase and gain margins according to

\[ϕ_m ≥ 2\text{sin}^{-1}(\frac{1}{2M_S}), g_m ≥ \frac{M_S}{M_S-1}\]

Generally, bounding $M_S$ is a better objective than looking at gain and phase margins due to the possibility of combined gain and pahse variations, which may lead to poor robustness despite large gain and pahse margins.

         ▲
          │e₁
          │  ┌─────┐
 d₁────+──┴──►  P  ├─────┬──►e₄
@@ -235,4 +235,4 @@
  e₂◄──┴─────┤  C  ◄──┬──+───d₂
             └─────┘  │
                      │e₃
-                     ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.bodevMethod

bodev(sys::LTISystem, w::AbstractVector; $(Expr(:kw, :unwrap, true)))

For use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.bodevMethod

bodev(sys::LTISystem; )

For use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(G::AbstractMatrix, w_vec::AbstractVector{<:Real}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(G::Number, w_vec::AbstractVector{<:Real}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(sys::LTISystem, w_vec::AbstractVector{W}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.nyquistvMethod

nyquistv(sys::LTISystem, w::AbstractVector; )

For use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.nyquistvMethod

nyquistv(sys::LTISystem; )

For use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.sigmavMethod

sigmav(sys::LTISystem, w::AbstractVector; )

For use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.sigmavMethod

sigmav(sys::LTISystem; )

For use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.

source
+ ▼
  • input_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹
  • output_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹
  • input_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP
  • output_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC
  • G_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P
  • G_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C
source
ControlSystemsBase.bodevMethod

bodev(sys::LTISystem, w::AbstractVector; $(Expr(:kw, :unwrap, true)))

For use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.bodevMethod

bodev(sys::LTISystem; )

For use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(G::AbstractMatrix, w_vec::AbstractVector{<:Real}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(G::Number, w_vec::AbstractVector{<:Real}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.freqrespvMethod

freqrespv(sys::LTISystem, w_vec::AbstractVector{W}; )

For use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.nyquistvMethod

nyquistv(sys::LTISystem, w::AbstractVector; )

For use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.nyquistvMethod

nyquistv(sys::LTISystem; )

For use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.sigmavMethod

sigmav(sys::LTISystem, w::AbstractVector; )

For use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.

source
ControlSystemsBase.sigmavMethod

sigmav(sys::LTISystem; )

For use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.

source
diff --git a/dev/lib/timefreqresponse/ff5115e8.svg b/dev/lib/timefreqresponse/8803d6c9.svg similarity index 86% rename from dev/lib/timefreqresponse/ff5115e8.svg rename to dev/lib/timefreqresponse/8803d6c9.svg index f928b398f..9ee288100 100644 --- a/dev/lib/timefreqresponse/ff5115e8.svg +++ b/dev/lib/timefreqresponse/8803d6c9.svg @@ -1,65 +1,65 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/4dbdc282.svg b/dev/lib/timefreqresponse/c8f7a1b1.svg similarity index 85% rename from dev/lib/timefreqresponse/4dbdc282.svg rename to dev/lib/timefreqresponse/c8f7a1b1.svg index d662def3a..3ebfc71ea 100644 --- a/dev/lib/timefreqresponse/4dbdc282.svg +++ b/dev/lib/timefreqresponse/c8f7a1b1.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/07a069bd.svg b/dev/lib/timefreqresponse/eaff4075.svg similarity index 85% rename from dev/lib/timefreqresponse/07a069bd.svg rename to dev/lib/timefreqresponse/eaff4075.svg index feb0f2ebb..428c6fc87 100644 --- a/dev/lib/timefreqresponse/07a069bd.svg +++ b/dev/lib/timefreqresponse/eaff4075.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/index.html b/dev/lib/timefreqresponse/index.html index 049112158..f1b6b3dd5 100644 --- a/dev/lib/timefreqresponse/index.html +++ b/dev/lib/timefreqresponse/index.html @@ -1,8 +1,8 @@ -Time and Frequency response · ControlSystems.jl

Time and Frequency response analysis

Frequency response

Frequency responses are calculated using freqresp, bode, sigma and nyquist. Frequency-response plots are obtained using bodeplot, sigmaplot, nyquistplot, marginplot and nicholsplot.

Any TransferFunction can be evaluated at a point using F(s), F(omega, true), F(z, false)

  • F(s) evaluates the continuous-time transfer function F at s.
  • F(omega,true) evaluates the discrete-time transfer function F at exp(i*Ts*omega)
  • F(z,false) evaluates the discrete-time transfer function F at z

A video demonstrating frequency-response analysis in ControlSystems.jl is available below.

Time response (simulation)

Simulation with arbitrary inputs is primarily handled by the function lsim, with step and impulse serving as convenience functions to simulate responses to particular inputs.

The function lsim can take an input vector u containing a sampled input trajectory, or an input function taking the state and time as arguments, u(x,t). This function can be used to easily simulate, e.g., ramp responses or saturated state-feedback control etc. See the docstring of lsim for more details.

For more extensive nonlinear simulation capabilities, see the notes on ModelingToolkit and DifferentialEquations under The wider Julia ecosystem for control.

Example step response:

The following simulates a step response of a second-order system and plots the result.

using ControlSystemsBase, Plots
+Time and Frequency response · ControlSystems.jl

Time and Frequency response analysis

Frequency response

Frequency responses are calculated using freqresp, bode, sigma and nyquist. Frequency-response plots are obtained using bodeplot, sigmaplot, nyquistplot, marginplot and nicholsplot.

Any TransferFunction can be evaluated at a point using F(s), F(omega, true), F(z, false)

  • F(s) evaluates the continuous-time transfer function F at s.
  • F(omega,true) evaluates the discrete-time transfer function F at exp(i*Ts*omega)
  • F(z,false) evaluates the discrete-time transfer function F at z

A video demonstrating frequency-response analysis in ControlSystems.jl is available below.

Time response (simulation)

Simulation with arbitrary inputs is primarily handled by the function lsim, with step and impulse serving as convenience functions to simulate responses to particular inputs.

The function lsim can take an input vector u containing a sampled input trajectory, or an input function taking the state and time as arguments, u(x,t). This function can be used to easily simulate, e.g., ramp responses or saturated state-feedback control etc. See the docstring of lsim for more details.

For more extensive nonlinear simulation capabilities, see the notes on ModelingToolkit and DifferentialEquations under The wider Julia ecosystem for control.

Example step response:

The following simulates a step response of a second-order system and plots the result.

using ControlSystemsBase, Plots
 G = tf(1, [1, 1, 1])
 res = step(G, 20) # Simulate 20 seconds step response
-plot(res)
Example block output

Using the function stepinfo, we can compute characteristics of a step response:

si = stepinfo(res)
StepInfo:
+plot(res)
Example block output

Using the function stepinfo, we can compute characteristics of a step response:

si = stepinfo(res)
StepInfo:
 Initial value:     0.000
 Final value:       1.000
 Step size:         1.000
@@ -12,7 +12,7 @@
 Undershoot:         0.00 %
 Settling time:     8.134 s
 Rise time:         1.660 s
-

We can also plot the StepInfo object

plot(si)
Example block output

Example lsim:

The function lsim can take the control input as either

  1. An array of equidistantly sampled values, in this case the argument u is expected to have the shape nu × n_time
  2. A function of the state and time u(x,t). This form allows simulation of state feedback, a step response at time $t_0$: u(x, t) = amplitude * (t > t0), or a ramp response: u(x, t) = t etc.

The example below simulates state feedback with a step disturbance at $t=4$ by providing the function u(x,t) = -L*x .+ (t > 4) to lsim:

using ControlSystems
+

We can also plot the StepInfo object

plot(si)
Example block output

Example lsim:

The function lsim can take the control input as either

  1. An array of equidistantly sampled values, in this case the argument u is expected to have the shape nu × n_time
  2. A function of the state and time u(x,t). This form allows simulation of state feedback, a step response at time $t_0$: u(x, t) = amplitude * (t > t0), or a ramp response: u(x, t) = t etc.

The example below simulates state feedback with a step disturbance at $t=4$ by providing the function u(x,t) = -L*x .+ (t > 4) to lsim:

using ControlSystems
 using LinearAlgebra: I
 using Plots
 
@@ -28,9 +28,9 @@
 t  = 0:0.1:12
 x0 = [1,0]
 y, t, x, uout = lsim(sys,u,t,x0=x0)
-plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]"); vline!([4], lab="Step disturbance", l=(:black, :dash, 0.5))
Example block output

A video demonstrating time-domain simulation in ControlSystems.jl is available below.

Docstrings

ControlSystems.SimulatorType
Simulator

Fields:

P::StateSpace
+plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]"); vline!([4], lab="Step disturbance", l=(:black, :dash, 0.5))
Example block output

A video demonstrating time-domain simulation in ControlSystems.jl is available below.

Docstrings

ControlSystems.SimulatorMethod
Simulator(P::StateSpace, u = (x,t) -> 0)

Used to simulate continuous-time systems. See function ?solve for additional info.

Usage:

using OrdinaryDiffEq, Plots
+y = (x,t)   -> y
source
ControlSystems.SimulatorMethod
Simulator(P::StateSpace, u = (x,t) -> 0)

Used to simulate continuous-time systems. See function ?solve for additional info.

Usage:

using OrdinaryDiffEq, Plots
 dt             = 0.1
 tfinal         = 20
 t              = 0:dt:tfinal
@@ -41,9 +41,9 @@
 x0             = [0.,0]
 tspan          = (0.0,tfinal)
 sol            = solve(s, x0, tspan, Tsit5())
-plot(t, s.y(sol, t)[:], lab="Open loop step response")
source
Base.stepMethod
y, t, x = step(sys[, tfinal])
-y, t, x = step(sys[, t])

Calculate the response of the system sys to a unit step at time t = 0. If the final time tfinal or time vector t is not provided, one is calculated based on the system pole locations.

The return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.

y has size (ny, length(t), nu), x has size (nx, length(t), nu)

See also stepinfo and lsim.

source
ControlSystemsBase.impulseMethod
y, t, x = impulse(sys[, tfinal])
-y, t, x = impulse(sys[, t])

Calculate the response of the system sys to an impulse at time t = 0. For continous-time systems, the impulse is a unit Dirac impulse. For discrete-time systems, the impulse lasts one sample and has magnitude 1/Ts. If the final time tfinal or time vector t is not provided, one is calculated based on the system pole locations.

The return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.

y has size (ny, length(t), nu), x has size (nx, length(t), nu)

See also lsim.

source
ControlSystemsBase.lsim!Method
res = lsim!(ws::LsimWorkspace, sys::AbstractStateSpace{<:Discrete}, u, [t]; x0)

In-place version of lsim that takes a workspace object created by calling LsimWorkspace. Notice, if u is a function, res.u === ws.u. If u is an array, res.u === u.

source
Base.stepMethod
y, t, x = step(sys[, tfinal])
+y, t, x = step(sys[, t])

Calculate the response of the system sys to a unit step at time t = 0. If the final time tfinal or time vector t is not provided, one is calculated based on the system pole locations.

The return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.

y has size (ny, length(t), nu), x has size (nx, length(t), nu)

See also stepinfo and lsim.

source
ControlSystemsBase.impulseMethod
y, t, x = impulse(sys[, tfinal])
+y, t, x = impulse(sys[, t])

Calculate the response of the system sys to an impulse at time t = 0. For continous-time systems, the impulse is a unit Dirac impulse. For discrete-time systems, the impulse lasts one sample and has magnitude 1/Ts. If the final time tfinal or time vector t is not provided, one is calculated based on the system pole locations.

The return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.

y has size (ny, length(t), nu), x has size (nx, length(t), nu)

See also lsim.

source
ControlSystemsBase.lsim!Method
res = lsim!(ws::LsimWorkspace, sys::AbstractStateSpace{<:Discrete}, u, [t]; x0)

In-place version of lsim that takes a workspace object created by calling LsimWorkspace. Notice, if u is a function, res.u === ws.u. If u is an array, res.u === u.

source
ControlSystemsBase.lsimMethod
result = lsim(sys, u[, t]; x0, method])
 result = lsim(sys, u::Function, t; x0, method)

Calculate the time response of system sys to input u. If x0 is omitted, a zero vector is used.

The result structure contains the fields y, t, x, u and can be destructured automatically by iteration, e.g.,

y, t, x, u = result

result::SimResult can also be plotted directly:

plot(result, plotu=true, plotx=false)

y, x, u have time in the second dimension. Initial state x0 defaults to zero.

Continuous-time systems are simulated using an ODE solver if u is a function (requires using ControlSystems). If u is an array, the system is discretized (with method=:zoh by default) before simulation. For a lower-level interface, see ?Simulator and ?solve. For continuous-time systems, keyword arguments are forwarded to the ODE solver. By default, the option dtmax = t[2]-t[1] is used to prevent the solver from stepping over discontinuities in u(x, t). This prevents the solver from taking too large steps, but may also slow down the simulation when u is smooth. To disable this behavior, set dtmax = Inf.

u can be a function or a matrix of precalculated control signals and must have dimensions (nu, length(t)). If u is a function, then u(x,i) (for discrete systems) or u(x,t) (for continuous ones) is called to calculate the control signal at every iteration (time instance used by solver). This can be used to provide a control law such as state feedback u(x,t) = -L*x calculated by lqr. To simulate a unit step at t=t₀, use (x,t)-> t ≥ t₀, for a ramp, use (x,t)-> t, for a step at t=5, use (x,t)-> (t >= 5) etc.

Note: The function u will be called once before simulating to verify that it returns an array of the correct dimensions. This can cause problems if u is stateful or has other side effects. You can disable this check by passing check_u = false.

For maximum performance, see function lsim!, available for discrete-time systems only.

Usage example:

using ControlSystems
 using LinearAlgebra: I
 using Plots
@@ -64,12 +64,12 @@
 
 # Alternative way of plotting
 res = lsim(sys,u,t,x0=x0)
-plot(res)
source
ControlSystemsBase.stepinfoMethod
stepinfo(res::SimResult; y0 = nothing, yf = nothing, settling_th = 0.02, risetime_th = (0.1, 0.9))

Compute the step response characteristics for a simulation result. The following information is computed and stored in a StepInfo struct:

  • y0: The initial value of the response
  • yf: The final value of the response
  • stepsize: The size of the step
  • peak: The peak value of the response
  • peaktime: The time at which the peak occurs
  • overshoot: The percentage overshoot of the response
  • undershoot: The percentage undershoot of the response. If the step response never reaches below the initial value, the undershoot is zero.
  • settlingtime: The time at which the response settles within settling_th of the final value
  • settlingtimeind: The index at which the response settles within settling_th of the final value
  • risetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value

Arguments:

  • res: The result from a simulation using step (or lsim)
  • y0: The initial value, if not provided, the first value of the response is used.
  • yf: The final value, if not provided, the last value of the response is used. The simulation must have reached steady-state for an automatically computed value to make sense. If the simulation has not reached steady state, you may provide the final value manually.
  • settling_th: The threshold for computing the settling time. The settling time is the time at which the response settles within settling_th of the final value.
  • risetime_th: The lower and upper threshold for computing the rise time. The rise time is the time at which the response rises from risetime_th[1] to risetime_th[2] of the final value.

Example:

G = tf([1], [1, 1, 1])
+plot(res)
source
ControlSystemsBase.stepinfoMethod
stepinfo(res::SimResult; y0 = nothing, yf = nothing, settling_th = 0.02, risetime_th = (0.1, 0.9))

Compute the step response characteristics for a simulation result. The following information is computed and stored in a StepInfo struct:

  • y0: The initial value of the response
  • yf: The final value of the response
  • stepsize: The size of the step
  • peak: The peak value of the response
  • peaktime: The time at which the peak occurs
  • overshoot: The percentage overshoot of the response
  • undershoot: The percentage undershoot of the response. If the step response never reaches below the initial value, the undershoot is zero.
  • settlingtime: The time at which the response settles within settling_th of the final value
  • settlingtimeind: The index at which the response settles within settling_th of the final value
  • risetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value

Arguments:

  • res: The result from a simulation using step (or lsim)
  • y0: The initial value, if not provided, the first value of the response is used.
  • yf: The final value, if not provided, the last value of the response is used. The simulation must have reached steady-state for an automatically computed value to make sense. If the simulation has not reached steady state, you may provide the final value manually.
  • settling_th: The threshold for computing the settling time. The settling time is the time at which the response settles within settling_th of the final value.
  • risetime_th: The lower and upper threshold for computing the rise time. The rise time is the time at which the response rises from risetime_th[1] to risetime_th[2] of the final value.

Example:

G = tf([1], [1, 1, 1])
 res = step(G, 15)
 si = stepinfo(res)
-plot(si)
source
ControlSystemsBase.LsimWorkspaceMethod
LsimWorkspace(sys::AbstractStateSpace, N::Int)
 LsimWorkspace(sys::AbstractStateSpace, u::AbstractMatrix)
-LsimWorkspace{T}(ny, nu, nx, N)

Generate a workspace object for use with the in-place function lsim!. sys is the discrete-time system to be simulated and N is the number of time steps, alternatively, the input u can be provided instead of N. Note: for threaded applications, create one workspace object per thread.

source
ControlSystemsBase.StepInfoType
StepInfo

Computed using stepinfo

Fields:

  • y0: The initial value of the step response.
  • yf: The final value of the step response.
  • stepsize: The size of the step.
  • peak: The peak value of the step response.
  • peaktime: The time at which the peak occurs.
  • overshoot: The overshoot of the step response.
  • settlingtime: The time at which the step response has settled to within settling_th of the final value.
  • settlingtimeind::Int: The index at which the step response has settled to within settling_th of the final value.
  • risetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value
  • i10::Int: The index at which the response reaches risetime_th[1]
  • i90::Int: The index at which the response reaches risetime_th[2]
  • res::SimResult{SR}: The simulation result used to compute the step response characteristics.
  • settling_th: The threshold used to compute settlingtime and settlingtimeind.
  • risetime_th: The thresholds used to compute risetime, i10, and i90.
source
ControlSystemsBase.bodeMethod
mag, phase, w = bode(sys[, w]; unwrap=true)

Compute the magnitude and phase parts of the frequency response of system sys at frequencies w. See also bodeplot

mag and phase has size (ny, nu, length(w)). If unwrap is true (default), the function unwrap! will be applied to the phase angles. This procedure is costly and can be avoided if the unwrapping is not required.

For higher performance, see the function bodemag! that computes the magnitude only.

source
ControlSystemsBase.bodemag!Method
mag = bodemag!(ws::BodemagWorkspace, sys::LTISystem, w::AbstractVector)

Compute the Bode magnitude operating in-place on an instance of BodemagWorkspace. Note that the returned magnitude array is aliased with ws.mag. The output array mag is ∈ 𝐑(ny, nu, nω).

source
ControlSystemsBase.evalfrMethod
evalfr(sys, x)

Evaluate the transfer function of the LTI system sys at the complex number s=x (continuous-time) or z=x (discrete-time).

For many values of x, use freqresp instead.

source
ControlSystemsBase.freqrespMethod
sys_fr = freqresp(sys, w)

Evaluate the frequency response of a linear system

w -> C*((iw*im*I - A)^-1)*B + D

of system sys over the frequency vector w.

source
ControlSystemsBase.nyquistMethod
re, im, w = nyquist(sys[, w])

Compute the real and imaginary parts of the frequency response of system sys at frequencies w. See also nyquistplot

re and im has size (ny, nu, length(w))

source
ControlSystemsBase.sigmaMethod
sv, w = sigma(sys[, w])

Compute the singular values sv of the frequency response of system sys at frequencies w. See also sigmaplot

sv has size (min(ny, nu), length(w))

source
ControlSystemsBase.BodemagWorkspaceMethod
BodemagWorkspace(sys::LTISystem, N::Int)
+LsimWorkspace{T}(ny, nu, nx, N)

Generate a workspace object for use with the in-place function lsim!. sys is the discrete-time system to be simulated and N is the number of time steps, alternatively, the input u can be provided instead of N. Note: for threaded applications, create one workspace object per thread.

source
ControlSystemsBase.StepInfoType
StepInfo

Computed using stepinfo

Fields:

  • y0: The initial value of the step response.
  • yf: The final value of the step response.
  • stepsize: The size of the step.
  • peak: The peak value of the step response.
  • peaktime: The time at which the peak occurs.
  • overshoot: The overshoot of the step response.
  • settlingtime: The time at which the step response has settled to within settling_th of the final value.
  • settlingtimeind::Int: The index at which the step response has settled to within settling_th of the final value.
  • risetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value
  • i10::Int: The index at which the response reaches risetime_th[1]
  • i90::Int: The index at which the response reaches risetime_th[2]
  • res::SimResult{SR}: The simulation result used to compute the step response characteristics.
  • settling_th: The threshold used to compute settlingtime and settlingtimeind.
  • risetime_th: The thresholds used to compute risetime, i10, and i90.
source
ControlSystemsBase.bodeMethod
mag, phase, w = bode(sys[, w]; unwrap=true)

Compute the magnitude and phase parts of the frequency response of system sys at frequencies w. See also bodeplot

mag and phase has size (ny, nu, length(w)). If unwrap is true (default), the function unwrap! will be applied to the phase angles. This procedure is costly and can be avoided if the unwrapping is not required.

For higher performance, see the function bodemag! that computes the magnitude only.

source
ControlSystemsBase.bodemag!Method
mag = bodemag!(ws::BodemagWorkspace, sys::LTISystem, w::AbstractVector)

Compute the Bode magnitude operating in-place on an instance of BodemagWorkspace. Note that the returned magnitude array is aliased with ws.mag. The output array mag is ∈ 𝐑(ny, nu, nω).

source
ControlSystemsBase.evalfrMethod
evalfr(sys, x)

Evaluate the transfer function of the LTI system sys at the complex number s=x (continuous-time) or z=x (discrete-time).

For many values of x, use freqresp instead.

source
ControlSystemsBase.freqrespMethod
sys_fr = freqresp(sys, w)

Evaluate the frequency response of a linear system

w -> C*((iw*im*I - A)^-1)*B + D

of system sys over the frequency vector w.

source
ControlSystemsBase.nyquistMethod
re, im, w = nyquist(sys[, w])

Compute the real and imaginary parts of the frequency response of system sys at frequencies w. See also nyquistplot

re and im has size (ny, nu, length(w))

source
ControlSystemsBase.sigmaMethod
sv, w = sigma(sys[, w])

Compute the singular values sv of the frequency response of system sys at frequencies w. See also sigmaplot

sv has size (min(ny, nu), length(w))

source
ControlSystemsBase.BodemagWorkspaceMethod
BodemagWorkspace(sys::LTISystem, N::Int)
 BodemagWorkspace(sys::LTISystem, ω::AbstractVector)
 BodemagWorkspace(R::Array{Complex{T}, 3}, mag::Array{T, 3})
-BodemagWorkspace{T}(ny, nu, N)

Generate a workspace object for use with the in-place function bodemag!. N is the number of frequency points, alternatively, the input ω can be provided instead of N. Note: for threaded applications, create one workspace object per thread.

Arguments:

  • mag: The output array ∈ 𝐑(ny, nu, nω)
  • R: Frequency-response array ∈ 𝐂(ny, nu, nω)
source
ControlSystemsBase.TransferFunctionMethod

F(s), F(omega, true), F(z, false)

Notation for frequency response evaluation.

  • F(s) evaluates the continuous-time transfer function F at s.
  • F(omega,true) evaluates the discrete-time transfer function F at exp(imTsomega)
  • F(z,false) evaluates the discrete-time transfer function F at z
source
+BodemagWorkspace{T}(ny, nu, N)

Generate a workspace object for use with the in-place function bodemag!. N is the number of frequency points, alternatively, the input ω can be provided instead of N. Note: for threaded applications, create one workspace object per thread.

Arguments:

  • mag: The output array ∈ 𝐑(ny, nu, nω)
  • R: Frequency-response array ∈ 𝐂(ny, nu, nω)
source
ControlSystemsBase.TransferFunctionMethod

F(s), F(omega, true), F(z, false)

Notation for frequency response evaluation.

  • F(s) evaluates the continuous-time transfer function F at s.
  • F(omega,true) evaluates the discrete-time transfer function F at exp(imTsomega)
  • F(z,false) evaluates the discrete-time transfer function F at z
source
diff --git a/dev/man/creating_systems/bf1b5120.svg b/dev/man/creating_systems/e1ed9aa9.svg similarity index 86% rename from dev/man/creating_systems/bf1b5120.svg rename to dev/man/creating_systems/e1ed9aa9.svg index c0eb8ce42..77aa2f4de 100644 --- a/dev/man/creating_systems/bf1b5120.svg +++ b/dev/man/creating_systems/e1ed9aa9.svg @@ -1,87 +1,87 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/man/creating_systems/index.html b/dev/man/creating_systems/index.html index 9f0929d14..9372ad284 100644 --- a/dev/man/creating_systems/index.html +++ b/dev/man/creating_systems/index.html @@ -184,56 +184,56 @@ @test_throws DimensionMismatch siso * mimo
Test Passed
       Thrown: DimensionMismatch

To multiply siso with each output channel of mimo in the example above, use broadcasting:

siso .* mimo
StateSpace{Continuous, Float64}
 A = 
- -1.0   0.0   0.5381442059866932   1.1139651945389593
-  0.0  -1.0  -0.8098171558522519  -0.8861152716904239
-  0.0   0.0   0.1505931416002615   1.175176975808013
-  0.0   0.0  -1.4130005438635362  -0.38254478260432956
+ -1.0   0.0  -0.4279670499477654  -0.832462361309289
+  0.0  -1.0  -0.6986231414572817  -1.2663399263477806
+  0.0   0.0  -1.6559954213914583  -1.4967315965975834
+  0.0   0.0  -1.1678841904202915  -1.5790027826822126
 B = 
- -1.1516454096833264    0.7353703118133802
-  0.07946682678079557   0.9289293705251618
- -1.3081320305789996    0.0022426087637038147
- -0.6957761633070271   -0.279206727302284
+ -1.1423288273126784  -1.1540551790108882
+  0.9776511173537141  -0.8246795973176549
+ -0.7585696886762945  -0.06853944388187941
+ -0.8726218694242683   0.18975230875443405
 C = 
- 1.0  0.0  0.0  0.0
- 0.0  1.0  0.0  0.0
+ 1.0  0.0  -0.0  -0.0
+ 0.0  1.0  -0.0  -0.0
 D = 
- 0.0  0.0
- 0.0  0.0
+ 0.0  -0.0
+ 0.0  -0.0
 
 Continuous-time state-space model

This is equivalent to first expanding the SISO system into a diagonal system

using LinearAlgebra
 (siso .* I(2)) * mimo
StateSpace{Continuous, Float64}
 A = 
- -1.0   0.0   0.5381442059866932   1.1139651945389593
-  0.0  -1.0  -0.8098171558522519  -0.8861152716904239
-  0.0   0.0   0.1505931416002615   1.175176975808013
-  0.0   0.0  -1.4130005438635362  -0.38254478260432956
+ -1.0   0.0  -0.4279670499477654  -0.832462361309289
+  0.0  -1.0  -0.6986231414572817  -1.2663399263477806
+  0.0   0.0  -1.6559954213914583  -1.4967315965975834
+  0.0   0.0  -1.1678841904202915  -1.5790027826822126
 B = 
- -1.1516454096833264    0.7353703118133802
-  0.07946682678079557   0.9289293705251618
- -1.3081320305789996    0.0022426087637038147
- -0.6957761633070271   -0.279206727302284
+ -1.1423288273126784  -1.1540551790108882
+  0.9776511173537141  -0.8246795973176549
+ -0.7585696886762945  -0.06853944388187941
+ -0.8726218694242683   0.18975230875443405
 C = 
- 1.0  0.0  0.0  0.0
- 0.0  1.0  0.0  0.0
+ 1.0  0.0  -0.0  -0.0
+ 0.0  1.0  -0.0  -0.0
 D = 
- 0.0  0.0
- 0.0  0.0
+ 0.0  -0.0
+ 0.0  -0.0
 
 Continuous-time state-space model

Converting an array of systems to a MIMO system

Diagonal MIMO systems can be created from a vector of systems using append

P1 = ssrand(1,1,1)
 P2 = ssrand(1,1,1)
 append(P1, P2)
StateSpace{Continuous, Float64}
 A = 
- -0.8243919646500245   0.0
-  0.0                 -1.5582838776756835
+ -0.14002659504912143   0.0
+  0.0                  -0.10913176097373567
 B = 
- 2.4525797229133857   0.0
- 0.0                 -0.8896946871634013
+ 1.024110716767843  0.0
+ 0.0                1.4398127792567013
 C = 
- 0.8077995260433491   0.0
- 0.0                 -0.6667486360265784
+ -1.4992207627938183  0.0
+  0.0                 1.7894330229122624
 D = 
- 2.189063393356488  0.0
- 0.0                1.1354723032245535
+ 0.327280224121082   0.0
+ 0.0                -1.2247163245853923
 
 Continuous-time state-space model

More general arrays of systems can be converted to a MIMO system using array2mimo.

sys_array = fill(P, 2, 2) # Creates an array of systems
 mimo_sys = array2mimo(sys_array)
StateSpace{Continuous, Int64}
@@ -258,44 +258,44 @@
 sys_array = getindex.(Ref(P), 1:P.ny, (1:P.nu)')
2×3 Matrix{StateSpace{Continuous, Float64}}:
  StateSpace{Continuous, Float64}
 A = 
- -1.1275797622125385
+ -0.46439032678464515
 B = 
- -1.617514372532446
+ 0.3149923527039399
 C = 
- -1.196952417292882
+ 0.5668967993520505
 D = 
- -0.7109454689227043
+ 1.1398514406712201
 
-Continuous-time state-space model  …  StateSpace{Continuous, Float64}
+Continuous-time state-space model      …  StateSpace{Continuous, Float64}
 A = 
- -1.1275797622125385
+ -0.46439032678464515
 B = 
- -0.47381336888065384
+ 0.8661903187867119
 C = 
- -1.196952417292882
+ 0.5668967993520505
 D = 
- 0.048322068838791345
+ 3.7211799973892097
 
 Continuous-time state-space model
  StateSpace{Continuous, Float64}
 A = 
- -1.1275797622125385
+ -0.46439032678464515
 B = 
- -1.617514372532446
+ 0.3149923527039399
 C = 
- 0.3481905476804239
+ -0.15301810116769693
 D = 
- 0.6863841337903974
+ -0.10589719012310667
 
-Continuous-time state-space model      StateSpace{Continuous, Float64}
+Continuous-time state-space model     StateSpace{Continuous, Float64}
 A = 
- -1.1275797622125385
+ -0.46439032678464515
 B = 
- -0.47381336888065384
+ 0.8661903187867119
 C = 
- 0.3481905476804239
+ -0.15301810116769693
 D = 
- 0.6585433110630615
+ -1.235947970223202
 
 Continuous-time state-space model

Demo systems

The module ControlSystemsBase.DemoSystems contains a number of demo systems demonstrating different kinds of dynamics.

From block diagrams to code

This section lists a number of block diagrams, and indicates the corresponding transfer functions and how they are built in code.

The function feedback(G1, G2) can be thought of like this: the first argument G1 is the system that appears directly between the input and the output (the forward path), while the second argument G2 (defaults to 1 if omitted) contains all other systems that appear in the closed loop (the feedback path). The feedback is assumed to be negative, unless the argument pos_feedback = true is passed (lft is an exception, which due to convention defaults to positive feedback). This means that feedback(G, 1) results in unit negative feedback, while feedback(G, -1) or feedback(G, 1, pos_feedback = true) results in unit positive feedback.

The returned closed-loop system will have a state vector comprised of the state of G1 followed by the state of G2.


Closed-loop system from reference to output

    ┌─────┐     ┌─────┐
 r   │     │  u  │     │ y
@@ -401,4 +401,4 @@
 df = digitalfilter(Bandpass(5, 10), Butterworth(2); fs)
 G = tf(df, 1/fs) # Sample time must be provided in the conversion to get the correct frequency scale in the Bode plot
 bodeplot(G, xscale=:identity, yscale=:identity, hz=true)
-vline!([5 10], l=(:black, :dash), label="Band-pass limits", sp=1)
Example block output

See also

+vline!([5 10], l=(:black, :dash), label="Band-pass limits", sp=1)Example block output

See also

diff --git a/dev/man/differences/index.html b/dev/man/differences/index.html index f90637636..340c47daf 100644 --- a/dev/man/differences/index.html +++ b/dev/man/differences/index.html @@ -1,2 +1,2 @@ -Noteworthy differences from other languages · ControlSystems.jl

Noteworthy Differences from other Languages

If you are new to the Julia programming language, you are encouraged to visit the documentation page on noteworthy differences between Julia and other programming languages.

The rest of this page will list noteworthy differences between ControlSystems.jl and other pieces of control-systems software.

  • Functions to calculate poles and zeros of systems are named using their plural forms, i.e., poles instead of pole, and tzeros instead of tzero.
  • Simulation using lsim, step, impulse returns arrays where time is in the second dimension rather than in the first dimension (applies also to freqresp, bode, nyquist etc.). Julia uses a column major memory layout, and this choice is made for performance reasons.
  • Functions are, lqr and kalman have slightly different signatures in julia compared to in other languages. More advanced LQG functionalities are located in RobustAndOptimalControl.jl.
  • Simulation using lsim, step, impulse etc. return a structure that can be plotted. These functions never plot anything themselves.
  • Functions bode, nyquist etc. never produce a plot. Instead, see bodeplot, nyquistplot etc.
  • In Julia, functionality is often split up into several different packages. You may therefore have to install and use additional packages in order to cover all your needs. See Ecosystem for a collection of control-related packages.
  • In Julia, 1 has a different type than 1.0, and the types in ControlSystemsBase.jl respect the types chosen by the user. As an example, tf(1, [1, 1]) is a transfer function with integer coefficients, while tf(1.0, [1, 1]) will promote all coefficients to Float64.
  • Julia treats matrices and vectors as different types, in particular, column vectors and row vectors are not interchangeable.
  • In Julia, code can often be differentiated using automatic differentiation. When using ControlSystems.jl, we recommend trying ForwardDiff.jl for AD. An example making use of this is available in Automatic Differentiation.
  • In Julia, the source code is often very readable. If you want to learn how a function is implemented, you may find the macros @edit or @less useful to locate the source code.
  • If you run into an issue (bug) with a Julia package, you can share this issue (bug report) on the package's github page and it will often be fixed promptly. To open an issue with ControlSystems.jl, click here. Thank you for helping out improving open-source software!
  • Julia compiles code just before it is called the first time. This introduces a noticeable lag the first time a function is called, and can make packages take a while to load.

If you find other noteworthy differences between ControlSystems.jl and other pieces of control-related software, please consider opening an issue on the github repository.

+Noteworthy differences from other languages · ControlSystems.jl

Noteworthy Differences from other Languages

If you are new to the Julia programming language, you are encouraged to visit the documentation page on noteworthy differences between Julia and other programming languages.

The rest of this page will list noteworthy differences between ControlSystems.jl and other pieces of control-systems software.

  • Functions to calculate poles and zeros of systems are named using their plural forms, i.e., poles instead of pole, and tzeros instead of tzero.
  • Simulation using lsim, step, impulse returns arrays where time is in the second dimension rather than in the first dimension (applies also to freqresp, bode, nyquist etc.). Julia uses a column major memory layout, and this choice is made for performance reasons.
  • Functions are, lqr and kalman have slightly different signatures in julia compared to in other languages. More advanced LQG functionalities are located in RobustAndOptimalControl.jl.
  • Simulation using lsim, step, impulse etc. return a structure that can be plotted. These functions never plot anything themselves.
  • Functions bode, nyquist etc. never produce a plot. Instead, see bodeplot, nyquistplot etc.
  • In Julia, functionality is often split up into several different packages. You may therefore have to install and use additional packages in order to cover all your needs. See Ecosystem for a collection of control-related packages.
  • In Julia, 1 has a different type than 1.0, and the types in ControlSystemsBase.jl respect the types chosen by the user. As an example, tf(1, [1, 1]) is a transfer function with integer coefficients, while tf(1.0, [1, 1]) will promote all coefficients to Float64.
  • Julia treats matrices and vectors as different types, in particular, column vectors and row vectors are not interchangeable.
  • In Julia, code can often be differentiated using automatic differentiation. When using ControlSystems.jl, we recommend trying ForwardDiff.jl for AD. An example making use of this is available in Automatic Differentiation.
  • In Julia, the source code is often very readable. If you want to learn how a function is implemented, you may find the macros @edit or @less useful to locate the source code.
  • If you run into an issue (bug) with a Julia package, you can share this issue (bug report) on the package's github page and it will often be fixed promptly. To open an issue with ControlSystems.jl, click here. Thank you for helping out improving open-source software!
  • Julia compiles code just before it is called the first time. This introduces a noticeable lag the first time a function is called, and can make packages take a while to load.

If you find other noteworthy differences between ControlSystems.jl and other pieces of control-related software, please consider opening an issue on the github repository.

diff --git a/dev/man/introduction/0561999a.svg b/dev/man/introduction/fdfbd958.svg similarity index 85% rename from dev/man/introduction/0561999a.svg rename to dev/man/introduction/fdfbd958.svg index f464bff25..c4bb0239d 100644 --- a/dev/man/introduction/0561999a.svg +++ b/dev/man/introduction/fdfbd958.svg @@ -1,73 +1,73 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/man/introduction/index.html b/dev/man/introduction/index.html index 5e94cc8a4..2a8a91df4 100644 --- a/dev/man/introduction/index.html +++ b/dev/man/introduction/index.html @@ -24,4 +24,4 @@ 1.0s + 2.0 Continuous-time transfer function model
Numerical accuracy

Transfer functions represent systems using polynomials and may have poor numerical properties for high-order systems. Well-balanced state-space representations are often better behaved. See Performance considerations for more details.

Plotting

The ControlSystems package is using RecipesBase.jl (link) as interface to generate all the plots. This means that it is up to the user to choose a plotting library that supports RecipesBase.jl, a suggestion would be Plots.jl with which the user is also able to freely choose a back-end. The plots in this manual are generated using Plots.jl with the GR backend. If you have several back-ends for plotting then you can select the one you want to use with the corresponding Plots call (for GR this is Plots.gr(), some alternatives are pyplot(), plotly(), pgfplots()). A simple example where we generate a plot and save it to a file is shown below.

More examples of plots are provided in Plotting functions.

using Plots
-bodeplot(tf(1,[1,2,1]))
Example block output +bodeplot(tf(1,[1,2,1]))Example block output diff --git a/dev/man/numerical/index.html b/dev/man/numerical/index.html index 32a9dd991..6a83abaf9 100644 --- a/dev/man/numerical/index.html +++ b/dev/man/numerical/index.html @@ -32,4 +32,4 @@ # 3.624 ms (7 allocations: 2.44 MiB) ws = ControlSystemsBase.BodemagWorkspace(G, w) @btime bodemag!($ws, $G, $w); -# 2.991 ms (1 allocation: 64 bytes)

Time-domain simulation

Time scale

When simulating a dynamical system in continuous time, a differential-equation integrator is used. These integrators are sensitive to the scaling of the equations, and may perform poorly for stiff problems or problems with a poorly chosen time scale. In, e.g., electronics, it's common to simulate systems where the dominant dynamics have time constants on the order of microseconds. To simulate such systems accurately, it's often a good idea to model the system in microseconds rather than in seconds. The function time_scale can be used to automatically change the time scale of a system.

Transfer functions

Transfer functions are automatically converted to state-space form before time-domain simulation. If you want control over the exact internal representation used, consider modeling the system as a state-space system already from start.

Discrete-time simulation

Linear systems with zero-order-hold inputs can be exactly simulated in discrete time. You may specify ZoH-discretization in the call to lsim using method=:zoh or manually perform the discretization using c2d. Discrete-time simulation is often much faster than continuous-time integration.

For discrete-time systems, the function lsim! accepts a pre-allocated workspace objects that can be used to avoid allocations for repeated simulations.

Numerical balancing

If you are only interested in the simulated outputs, not the state trajectories, you may consider applying balancing to the statespace model using balance_statespace before simulating, see the section on State-space balancing above. If the state trajectories are of interest, balancing can still be performed before simulation, and the inverse transformation applied to the state trajectories after simulation.

Static arrays in StateSpace systems

The special statespace system type HeteroStateSapce can be used to store statespace models with static arrays rather than the default matrix type Matrix. See State-Space Systems for more details.

+# 2.991 ms (1 allocation: 64 bytes)

Time-domain simulation

Time scale

When simulating a dynamical system in continuous time, a differential-equation integrator is used. These integrators are sensitive to the scaling of the equations, and may perform poorly for stiff problems or problems with a poorly chosen time scale. In, e.g., electronics, it's common to simulate systems where the dominant dynamics have time constants on the order of microseconds. To simulate such systems accurately, it's often a good idea to model the system in microseconds rather than in seconds. The function time_scale can be used to automatically change the time scale of a system.

Transfer functions

Transfer functions are automatically converted to state-space form before time-domain simulation. If you want control over the exact internal representation used, consider modeling the system as a state-space system already from start.

Discrete-time simulation

Linear systems with zero-order-hold inputs can be exactly simulated in discrete time. You may specify ZoH-discretization in the call to lsim using method=:zoh or manually perform the discretization using c2d. Discrete-time simulation is often much faster than continuous-time integration.

For discrete-time systems, the function lsim! accepts a pre-allocated workspace objects that can be used to avoid allocations for repeated simulations.

Numerical balancing

If you are only interested in the simulated outputs, not the state trajectories, you may consider applying balancing to the statespace model using balance_statespace before simulating, see the section on State-space balancing above. If the state trajectories are of interest, balancing can still be performed before simulation, and the inverse transformation applied to the state trajectories after simulation.

Static arrays in StateSpace systems

The special statespace system type HeteroStateSapce can be used to store statespace models with static arrays rather than the default matrix type Matrix. See State-Space Systems for more details.

diff --git a/dev/plots/lqrplot.svg b/dev/plots/lqrplot.svg index cf3709992..ca91323f4 100644 --- a/dev/plots/lqrplot.svg +++ b/dev/plots/lqrplot.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/pidplotsgof1.svg b/dev/plots/pidplotsgof1.svg index b03d79499..ba4167ed8 100644 --- a/dev/plots/pidplotsgof1.svg +++ b/dev/plots/pidplotsgof1.svg @@ -1,162 +1,162 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/pidplotsgof2.svg b/dev/plots/pidplotsgof2.svg index fb32d440f..daa5db1f6 100644 --- a/dev/plots/pidplotsgof2.svg +++ b/dev/plots/pidplotsgof2.svg @@ -1,160 +1,160 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/pidplotsnyquist1.svg b/dev/plots/pidplotsnyquist1.svg index 9e888a70c..6fade9211 100644 --- a/dev/plots/pidplotsnyquist1.svg +++ b/dev/plots/pidplotsnyquist1.svg @@ -1,68 +1,68 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/pidplotsnyquist2.svg b/dev/plots/pidplotsnyquist2.svg index 58f2a55e5..00a973a87 100644 --- a/dev/plots/pidplotsnyquist2.svg +++ b/dev/plots/pidplotsnyquist2.svg @@ -1,68 +1,68 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/ppgofplot.svg b/dev/plots/ppgofplot.svg index c55845249..aaab8ca5c 100644 --- a/dev/plots/ppgofplot.svg +++ b/dev/plots/ppgofplot.svg @@ -1,134 +1,134 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/ppstepplot.svg b/dev/plots/ppstepplot.svg index 86da99e00..049f2dd3b 100644 --- a/dev/plots/ppstepplot.svg +++ b/dev/plots/ppstepplot.svg @@ -1,42 +1,42 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/stab1.svg b/dev/plots/stab1.svg index 5fd175173..b88a9e908 100644 --- a/dev/plots/stab1.svg +++ b/dev/plots/stab1.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/stab2.svg b/dev/plots/stab2.svg index 0ec8643c9..f91c51af7 100644 --- a/dev/plots/stab2.svg +++ b/dev/plots/stab2.svg @@ -1,46 +1,46 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/plots/stab3.svg b/dev/plots/stab3.svg index 56a4c9e7f..60319ff4d 100644 --- a/dev/plots/stab3.svg +++ b/dev/plots/stab3.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +