diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index e71e745df..6e3e8f58f 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-11-28T07:19:45","documenter_version":"1.8.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-11-28T07:20:06","documenter_version":"1.8.0"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index e1c5dcdae..de98c085f 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/8f942d21.svg b/dev/examples/analysis/1405488c.svg similarity index 86% rename from dev/examples/analysis/8f942d21.svg rename to dev/examples/analysis/1405488c.svg index fcc8c928a..558f96af4 100644 --- a/dev/examples/analysis/8f942d21.svg +++ b/dev/examples/analysis/1405488c.svg @@ -1,75 +1,75 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/40bed48e.svg b/dev/examples/analysis/3e9c04e8.svg similarity index 92% rename from dev/examples/analysis/40bed48e.svg rename to dev/examples/analysis/3e9c04e8.svg index 2afe2412f..08abd8600 100644 --- a/dev/examples/analysis/40bed48e.svg +++ b/dev/examples/analysis/3e9c04e8.svg @@ -1,51 +1,51 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/4b6b3337.svg b/dev/examples/analysis/be33a4e9.svg similarity index 85% rename from dev/examples/analysis/4b6b3337.svg rename to dev/examples/analysis/be33a4e9.svg index 8bf9217d9..2720469f7 100644 --- a/dev/examples/analysis/4b6b3337.svg +++ b/dev/examples/analysis/be33a4e9.svg @@ -1,128 +1,128 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/analysis/index.html b/dev/examples/analysis/index.html index 2acae2525..8f695a853 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

diff --git a/dev/examples/automatic_differentiation/e89b85a9.svg b/dev/examples/automatic_differentiation/136ecbd4.svg similarity index 88% rename from dev/examples/automatic_differentiation/e89b85a9.svg rename to dev/examples/automatic_differentiation/136ecbd4.svg index 92526e497..7d5d3bd1e 100644 --- a/dev/examples/automatic_differentiation/e89b85a9.svg +++ b/dev/examples/automatic_differentiation/136ecbd4.svg @@ -1,124 +1,124 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/fdd96a22.svg b/dev/examples/automatic_differentiation/2b89d348.svg similarity index 86% rename from dev/examples/automatic_differentiation/fdd96a22.svg rename to dev/examples/automatic_differentiation/2b89d348.svg index 4348962e6..09b0144e2 100644 --- a/dev/examples/automatic_differentiation/fdd96a22.svg +++ b/dev/examples/automatic_differentiation/2b89d348.svg @@ -1,95 +1,95 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/848fe4c8.svg b/dev/examples/automatic_differentiation/80c51636.svg similarity index 88% rename from dev/examples/automatic_differentiation/848fe4c8.svg rename to dev/examples/automatic_differentiation/80c51636.svg index aba24b637..feb42cc91 100644 --- a/dev/examples/automatic_differentiation/848fe4c8.svg +++ b/dev/examples/automatic_differentiation/80c51636.svg @@ -1,57 +1,57 @@ - + - + - + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/fb2724b3.svg b/dev/examples/automatic_differentiation/87e13371.svg similarity index 88% rename from dev/examples/automatic_differentiation/fb2724b3.svg rename to dev/examples/automatic_differentiation/87e13371.svg index 487617038..edbfdec1c 100644 --- a/dev/examples/automatic_differentiation/fb2724b3.svg +++ b/dev/examples/automatic_differentiation/87e13371.svg @@ -1,122 +1,122 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/0f7b9fda.svg b/dev/examples/automatic_differentiation/ade664d0.svg similarity index 88% rename from dev/examples/automatic_differentiation/0f7b9fda.svg rename to dev/examples/automatic_differentiation/ade664d0.svg index 47fe91af1..a8d8f1607 100644 --- a/dev/examples/automatic_differentiation/0f7b9fda.svg +++ b/dev/examples/automatic_differentiation/ade664d0.svg @@ -1,126 +1,126 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/a4b33382.svg b/dev/examples/automatic_differentiation/f38da721.svg similarity index 94% rename from dev/examples/automatic_differentiation/a4b33382.svg rename to dev/examples/automatic_differentiation/f38da721.svg index ba0b9b5bf..b46d880b4 100644 --- a/dev/examples/automatic_differentiation/a4b33382.svg +++ b/dev/examples/automatic_differentiation/f38da721.svg @@ -1,112 +1,112 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/7a8ca3a8.svg b/dev/examples/automatic_differentiation/fc9f3171.svg similarity index 97% rename from dev/examples/automatic_differentiation/7a8ca3a8.svg rename to dev/examples/automatic_differentiation/fc9f3171.svg index b2bf781a0..990298593 100644 --- a/dev/examples/automatic_differentiation/7a8ca3a8.svg +++ b/dev/examples/automatic_differentiation/fc9f3171.svg @@ -1,168 +1,168 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/automatic_differentiation/index.html b/dev/examples/automatic_differentiation/index.html index b102d3ced..52993f87b 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/5bfc6278.svg b/dev/examples/delay_systems/3be229a2.svg similarity index 98% rename from dev/examples/delay_systems/5bfc6278.svg rename to dev/examples/delay_systems/3be229a2.svg index d9e5a5562..d23ec5a72 100644 --- a/dev/examples/delay_systems/5bfc6278.svg +++ b/dev/examples/delay_systems/3be229a2.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/c95953e4.svg b/dev/examples/delay_systems/6e7885f1.svg similarity index 92% rename from dev/examples/delay_systems/c95953e4.svg rename to dev/examples/delay_systems/6e7885f1.svg index 65c00c9b9..058d8e7e5 100644 --- a/dev/examples/delay_systems/c95953e4.svg +++ b/dev/examples/delay_systems/6e7885f1.svg @@ -1,71 +1,71 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/0a7d4ce3.svg b/dev/examples/delay_systems/7e23708f.svg similarity index 90% rename from dev/examples/delay_systems/0a7d4ce3.svg rename to dev/examples/delay_systems/7e23708f.svg index 97e558ba8..cb5f0e14e 100644 --- a/dev/examples/delay_systems/0a7d4ce3.svg +++ b/dev/examples/delay_systems/7e23708f.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/delay_systems/index.html b/dev/examples/delay_systems/index.html index c80d3f085..8073908d0 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/4a7c2c33.svg b/dev/examples/example/2d511947.svg similarity index 89% rename from dev/examples/example/4a7c2c33.svg rename to dev/examples/example/2d511947.svg index 06a131380..3a42c22df 100644 --- a/dev/examples/example/4a7c2c33.svg +++ b/dev/examples/example/2d511947.svg @@ -1,185 +1,185 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/1df8b698.svg b/dev/examples/example/60cae0a8.svg similarity index 87% rename from dev/examples/example/1df8b698.svg rename to dev/examples/example/60cae0a8.svg index ee40b45c3..62e191a8d 100644 --- a/dev/examples/example/1df8b698.svg +++ b/dev/examples/example/60cae0a8.svg @@ -1,175 +1,175 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/cdc0aa71.svg b/dev/examples/example/937a3956.svg similarity index 84% rename from dev/examples/example/cdc0aa71.svg rename to dev/examples/example/937a3956.svg index 61c7c0e4c..51407727f 100644 --- a/dev/examples/example/cdc0aa71.svg +++ b/dev/examples/example/937a3956.svg @@ -1,128 +1,128 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/767bdcc8.svg b/dev/examples/example/a191245d.svg similarity index 87% rename from dev/examples/example/767bdcc8.svg rename to dev/examples/example/a191245d.svg index 4752f41d2..7c214ea09 100644 --- a/dev/examples/example/767bdcc8.svg +++ b/dev/examples/example/a191245d.svg @@ -1,151 +1,151 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/f5f2a755.svg b/dev/examples/example/fe69dfb4.svg similarity index 89% rename from dev/examples/example/f5f2a755.svg rename to dev/examples/example/fe69dfb4.svg index fc688c40c..1545e47ac 100644 --- a/dev/examples/example/f5f2a755.svg +++ b/dev/examples/example/fe69dfb4.svg @@ -1,185 +1,185 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/example/index.html b/dev/examples/example/index.html index 2e574e321..cdf9e70dc 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 constructor pid. 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
 
diff --git a/dev/examples/ilc/4025ee6b.svg b/dev/examples/ilc/2f3fbb32.svg
similarity index 90%
rename from dev/examples/ilc/4025ee6b.svg
rename to dev/examples/ilc/2f3fbb32.svg
index 727ed5e72..fb5feb138 100644
--- a/dev/examples/ilc/4025ee6b.svg
+++ b/dev/examples/ilc/2f3fbb32.svg
@@ -1,52 +1,52 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/examples/ilc/7ab4157f.svg b/dev/examples/ilc/4414989a.svg
similarity index 92%
rename from dev/examples/ilc/7ab4157f.svg
rename to dev/examples/ilc/4414989a.svg
index bdef5d6b5..57c533b61 100644
--- a/dev/examples/ilc/7ab4157f.svg
+++ b/dev/examples/ilc/4414989a.svg
@@ -1,173 +1,173 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
diff --git a/dev/examples/ilc/b209dbb2.svg b/dev/examples/ilc/7a5c2c35.svg
similarity index 89%
rename from dev/examples/ilc/b209dbb2.svg
rename to dev/examples/ilc/7a5c2c35.svg
index 2c5877093..023e640d8 100644
--- a/dev/examples/ilc/b209dbb2.svg
+++ b/dev/examples/ilc/7a5c2c35.svg
@@ -1,36 +1,36 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/examples/ilc/eaf7ec82.svg b/dev/examples/ilc/89f0f205.svg
similarity index 92%
rename from dev/examples/ilc/eaf7ec82.svg
rename to dev/examples/ilc/89f0f205.svg
index 6c8b97a86..0998d1fc2 100644
--- a/dev/examples/ilc/eaf7ec82.svg
+++ b/dev/examples/ilc/89f0f205.svg
@@ -1,177 +1,177 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
diff --git a/dev/examples/ilc/c3f2fda3.svg b/dev/examples/ilc/969ff00c.svg
similarity index 89%
rename from dev/examples/ilc/c3f2fda3.svg
rename to dev/examples/ilc/969ff00c.svg
index b7b51b2e3..da4b2dd67 100644
--- a/dev/examples/ilc/c3f2fda3.svg
+++ b/dev/examples/ilc/969ff00c.svg
@@ -1,44 +1,44 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/examples/ilc/1198ded3.svg b/dev/examples/ilc/d4d379aa.svg
similarity index 91%
rename from dev/examples/ilc/1198ded3.svg
rename to dev/examples/ilc/d4d379aa.svg
index d13385bb9..690aae0e8 100644
--- a/dev/examples/ilc/1198ded3.svg
+++ b/dev/examples/ilc/d4d379aa.svg
@@ -1,48 +1,48 @@
 
 
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
+
 
-  
+  
     
   
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/examples/ilc/index.html b/dev/examples/ilc/index.html
index 74f44972c..a7bbecffa 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/cf5a4e6c.svg b/dev/examples/smith_predictor/780782b8.svg similarity index 86% rename from dev/examples/smith_predictor/cf5a4e6c.svg rename to dev/examples/smith_predictor/780782b8.svg index cce119229..ba973e513 100644 --- a/dev/examples/smith_predictor/cf5a4e6c.svg +++ b/dev/examples/smith_predictor/780782b8.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/2f876e16.svg b/dev/examples/smith_predictor/a59c7d94.svg similarity index 99% rename from dev/examples/smith_predictor/2f876e16.svg rename to dev/examples/smith_predictor/a59c7d94.svg index 2b95ce129..41c31b0fb 100644 --- a/dev/examples/smith_predictor/2f876e16.svg +++ b/dev/examples/smith_predictor/a59c7d94.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/4177350d.svg b/dev/examples/smith_predictor/bc16dede.svg similarity index 90% rename from dev/examples/smith_predictor/4177350d.svg rename to dev/examples/smith_predictor/bc16dede.svg index 1299bc52e..96a52059f 100644 --- a/dev/examples/smith_predictor/4177350d.svg +++ b/dev/examples/smith_predictor/bc16dede.svg @@ -1,70 +1,70 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/smith_predictor/index.html b/dev/examples/smith_predictor/index.html index 373cbc4f4..c0cf38cfb 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/704ccfea.svg b/dev/examples/tuning_from_data/52d4a7a5.svg similarity index 97% rename from dev/examples/tuning_from_data/704ccfea.svg rename to dev/examples/tuning_from_data/52d4a7a5.svg index ff3b80425..eb237279c 100644 --- a/dev/examples/tuning_from_data/704ccfea.svg +++ b/dev/examples/tuning_from_data/52d4a7a5.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/8428ad4f.svg b/dev/examples/tuning_from_data/69e1f035.svg similarity index 99% rename from dev/examples/tuning_from_data/8428ad4f.svg rename to dev/examples/tuning_from_data/69e1f035.svg index f2d2b3a0e..e5ba794ba 100644 --- a/dev/examples/tuning_from_data/8428ad4f.svg +++ b/dev/examples/tuning_from_data/69e1f035.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/0f60dc3b.svg b/dev/examples/tuning_from_data/7bfae977.svg similarity index 84% rename from dev/examples/tuning_from_data/0f60dc3b.svg rename to dev/examples/tuning_from_data/7bfae977.svg index c98250582..bf9eaf690 100644 --- a/dev/examples/tuning_from_data/0f60dc3b.svg +++ b/dev/examples/tuning_from_data/7bfae977.svg @@ -1,39 +1,39 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/b5581928.svg b/dev/examples/tuning_from_data/90d3cf08.svg similarity index 86% rename from dev/examples/tuning_from_data/b5581928.svg rename to dev/examples/tuning_from_data/90d3cf08.svg index 9b7256e20..fcc53756a 100644 --- a/dev/examples/tuning_from_data/b5581928.svg +++ b/dev/examples/tuning_from_data/90d3cf08.svg @@ -1,71 +1,71 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/75f1bd0a.svg b/dev/examples/tuning_from_data/a4350313.svg similarity index 99% rename from dev/examples/tuning_from_data/75f1bd0a.svg rename to dev/examples/tuning_from_data/a4350313.svg index 710bf89ce..0c6e9c582 100644 --- a/dev/examples/tuning_from_data/75f1bd0a.svg +++ b/dev/examples/tuning_from_data/a4350313.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/eddcadbe.svg b/dev/examples/tuning_from_data/afdacf12.svg similarity index 87% rename from dev/examples/tuning_from_data/eddcadbe.svg rename to dev/examples/tuning_from_data/afdacf12.svg index a3b3a9fc2..e75b248e8 100644 --- a/dev/examples/tuning_from_data/eddcadbe.svg +++ b/dev/examples/tuning_from_data/afdacf12.svg @@ -1,62 +1,62 @@ - + - + - + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/c844b45a.svg b/dev/examples/tuning_from_data/c55b11cd.svg similarity index 93% rename from dev/examples/tuning_from_data/c844b45a.svg rename to dev/examples/tuning_from_data/c55b11cd.svg index f98e787d3..64170253a 100644 --- a/dev/examples/tuning_from_data/c844b45a.svg +++ b/dev/examples/tuning_from_data/c55b11cd.svg @@ -1,178 +1,178 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/405395ce.svg b/dev/examples/tuning_from_data/c692f4fe.svg similarity index 79% rename from dev/examples/tuning_from_data/405395ce.svg rename to dev/examples/tuning_from_data/c692f4fe.svg index 18e35789a..fe774a3b5 100644 --- a/dev/examples/tuning_from_data/405395ce.svg +++ b/dev/examples/tuning_from_data/c692f4fe.svg @@ -1,64 +1,64 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/b3734c7a.svg b/dev/examples/tuning_from_data/dcae8f43.svg similarity index 86% rename from dev/examples/tuning_from_data/b3734c7a.svg rename to dev/examples/tuning_from_data/dcae8f43.svg index 389e967e4..78f4b8c2a 100644 --- a/dev/examples/tuning_from_data/b3734c7a.svg +++ b/dev/examples/tuning_from_data/dcae8f43.svg @@ -1,110 +1,110 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/78743339.svg b/dev/examples/tuning_from_data/ffaec7d7.svg similarity index 87% rename from dev/examples/tuning_from_data/78743339.svg rename to dev/examples/tuning_from_data/ffaec7d7.svg index c7ec15a69..e9d658232 100644 --- a/dev/examples/tuning_from_data/78743339.svg +++ b/dev/examples/tuning_from_data/ffaec7d7.svg @@ -1,112 +1,112 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/tuning_from_data/index.html b/dev/examples/tuning_from_data/index.html index f6775c885..0c55bfb8b 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/cc9b0e6b.svg b/dev/examples/zoh/0107be60.svg similarity index 88% rename from dev/examples/zoh/cc9b0e6b.svg rename to dev/examples/zoh/0107be60.svg index 2ecc2d611..7b8bca29e 100644 --- a/dev/examples/zoh/cc9b0e6b.svg +++ b/dev/examples/zoh/0107be60.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/29e5b404.svg b/dev/examples/zoh/12408985.svg similarity index 97% rename from dev/examples/zoh/29e5b404.svg rename to dev/examples/zoh/12408985.svg index ba1f22b86..483ce1d1e 100644 --- a/dev/examples/zoh/29e5b404.svg +++ b/dev/examples/zoh/12408985.svg @@ -1,62 +1,62 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/34b66198.svg b/dev/examples/zoh/34312a89.svg similarity index 85% rename from dev/examples/zoh/34b66198.svg rename to dev/examples/zoh/34312a89.svg index 47bdc4d17..135b91a3c 100644 --- a/dev/examples/zoh/34b66198.svg +++ b/dev/examples/zoh/34312a89.svg @@ -1,68 +1,68 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/f2f6933c.svg b/dev/examples/zoh/4c25a2dd.svg similarity index 97% rename from dev/examples/zoh/f2f6933c.svg rename to dev/examples/zoh/4c25a2dd.svg index 8e71af147..213f66206 100644 --- a/dev/examples/zoh/f2f6933c.svg +++ b/dev/examples/zoh/4c25a2dd.svg @@ -1,79 +1,79 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/39d0a40e.svg b/dev/examples/zoh/baddd454.svg similarity index 89% rename from dev/examples/zoh/39d0a40e.svg rename to dev/examples/zoh/baddd454.svg index ccb8576cc..5a24659c0 100644 --- a/dev/examples/zoh/39d0a40e.svg +++ b/dev/examples/zoh/baddd454.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/b937b966.svg b/dev/examples/zoh/bd989074.svg similarity index 89% rename from dev/examples/zoh/b937b966.svg rename to dev/examples/zoh/bd989074.svg index 3ed81aa5a..cf8e1e2ff 100644 --- a/dev/examples/zoh/b937b966.svg +++ b/dev/examples/zoh/bd989074.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/5346fbd8.svg b/dev/examples/zoh/dbc1303e.svg similarity index 97% rename from dev/examples/zoh/5346fbd8.svg rename to dev/examples/zoh/dbc1303e.svg index e1d21e5a2..9e3ffc14f 100644 --- a/dev/examples/zoh/5346fbd8.svg +++ b/dev/examples/zoh/dbc1303e.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/a9d6cbd4.svg b/dev/examples/zoh/e792559e.svg similarity index 98% rename from dev/examples/zoh/a9d6cbd4.svg rename to dev/examples/zoh/e792559e.svg index e37963f18..7c9e65d9d 100644 --- a/dev/examples/zoh/a9d6cbd4.svg +++ b/dev/examples/zoh/e792559e.svg @@ -1,66 +1,66 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/a2b48c7e.svg b/dev/examples/zoh/ea1d323f.svg similarity index 91% rename from dev/examples/zoh/a2b48c7e.svg rename to dev/examples/zoh/ea1d323f.svg index ec5af1ea4..58170ed37 100644 --- a/dev/examples/zoh/a2b48c7e.svg +++ b/dev/examples/zoh/ea1d323f.svg @@ -1,72 +1,72 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/zoh/index.html b/dev/examples/zoh/index.html index 141013f87..d2aaf0c9a 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/index.html b/dev/index.html index ad1f52da3..c1e7dfafe 100644 --- a/dev/index.html +++ b/dev/index.html @@ -8,4 +8,4 @@ Star -

ControlSystems.jl Manual

ControlSystems.jl and the rest of the packages in the JuliaControl organization implement solutions for analysis and design of (primarily linear) control systems. If you are new to the Julia programming language, learn more here. If you are familiar with Julia but unfamiliar with the ecosystem for control, learn more under Ecosystem.

This documentation is structured into an introductory section labeled Introductory guide, a section with Examples and a reference section sorted into topics, labeled Functions.

Introductory guide

Examples

Functions

Ecosystem

JuliaControl

The JuliaControl and surrounding ecosystem contains a few additional packages that may be of interest

  • RobustAndOptimalControl.jl contains more advanced features for LQG design, robust analysis and synthesis, uncertainty modeling, named systems and an interface to DescriptorSystems.jl.
  • SymbolicControlSystems.jl contains basic C-code generation for linear systems.
  • ControlSystemIdentification.jl is a system-identification toolbox for identification of LTI systems using either time or frequency-domain data. This package can use data to estimate statespace models, transfer-function models and Kalman filters that can be used for control design.
  • ControlSystemsMTK.jl is an interface between ControlSystems.jl and ModelingToolkit.jl.
  • DiscretePIDs.jl contains a reference implementation in Julia of a discrete-time PID controller including set-point weighting, integrator anti-windup, derivative filtering and bumpless transfer.

See also the paper describing the toolbox

Bagge Carlson, F., Fält, M., Heimerson, A., & Troeng, O. (2021). ControlSystems.jl: A Control Toolbox in Julia. In 2021 60th IEEE Conference on Decision and Control (CDC) IEEE Press. https://doi.org/10.1109/CDC45484.2021.9683403

and the introductory Youtube video below, as well as the following Youtube playlist with videos about using Julia for control.

The wider Julia ecosystem for control

The following is a list of packages from the wider Julia ecosystem that may be of interest.

  • DescriptorSystems.jl contains types that represent statespace systems on descriptor form, i.e., with a mass matrix. These systems can represent linear DAE systems and non-proper systems.
  • TrajectoryOptimization.jl is one of the more developed packages for open-loop optimal control and trajectory optimization in Julia.
  • LowLevelParticleFilters.jl is a library for state estimation using particle filters and Kalman filters of different flavors.
  • ModelingToolkit.jl is an acausal modeling tool, similar in spirit to Modelica. A video showing ControlSystems and ModelingToolkit together is available here. ControlSystemsMTK.jl exists to ease the use of these two packages together.
  • JuliaSimControl.jl is a product that builds upon the JuliaControl ecosystem and ModelingToolkit, providing additional nonlinear and robust control methods.
  • FaultDetectionTools.jl contains utilities and observers for online fault detection.
  • ReachabilityAnalysis.jl is a package for reachability analysis. This can be used to verify stability and safety properties of linear and nonlinear systems.
  • MatrixEquations.jl contains solvers for many different matrix equations common in control. ControlSystems.jl makes use of this package for solving Riccati and Lyapunov equations.
  • JuMP.jl is a modeling language for optimization, similar to YALMIP. JuMP is suitable for solving LMI/SDP problems as well as advanced linear MPC problems.
  • SumOfSquares.jl is a package for sum-of-squares programming that builds on top of JuMP. Their documentation contains examples of Lyapunov-function search and nonlinear synthesis.
  • MonteCarloMeasurements.jl is a library for working with parametric uncertainty. An example using ControlSystems is available here.
  • DifferentialEquations.jl is the home of the SciML ecosystem that provides solvers for scientific problems. ControlSystems.jl uses these solvers for continuous-time simulations.
  • Dojo.jl is a differentiable robot simulator.
  • StaticCompiler.jl contains tools for compiling small binaries of Julia programs.
  • JuliaPOMDP is a Julia ecosystem for reinforcement learning.
  • JuliaReinforcementLearning is another Julia ecosystem for reinforcement learning.
+

ControlSystems.jl Manual

ControlSystems.jl and the rest of the packages in the JuliaControl organization implement solutions for analysis and design of (primarily linear) control systems. If you are new to the Julia programming language, learn more here. If you are familiar with Julia but unfamiliar with the ecosystem for control, learn more under Ecosystem.

This documentation is structured into an introductory section labeled Introductory guide, a section with Examples and a reference section sorted into topics, labeled Functions.

Introductory guide

Examples

Functions

Ecosystem

JuliaControl

The JuliaControl and surrounding ecosystem contains a few additional packages that may be of interest

  • RobustAndOptimalControl.jl contains more advanced features for LQG design, robust analysis and synthesis, uncertainty modeling, named systems and an interface to DescriptorSystems.jl.
  • SymbolicControlSystems.jl contains basic C-code generation for linear systems.
  • ControlSystemIdentification.jl is a system-identification toolbox for identification of LTI systems using either time or frequency-domain data. This package can use data to estimate statespace models, transfer-function models and Kalman filters that can be used for control design.
  • ControlSystemsMTK.jl is an interface between ControlSystems.jl and ModelingToolkit.jl.
  • DiscretePIDs.jl contains a reference implementation in Julia of a discrete-time PID controller including set-point weighting, integrator anti-windup, derivative filtering and bumpless transfer.

See also the paper describing the toolbox

Bagge Carlson, F., Fält, M., Heimerson, A., & Troeng, O. (2021). ControlSystems.jl: A Control Toolbox in Julia. In 2021 60th IEEE Conference on Decision and Control (CDC) IEEE Press. https://doi.org/10.1109/CDC45484.2021.9683403

and the introductory Youtube video below, as well as the following Youtube playlist with videos about using Julia for control.

The wider Julia ecosystem for control

The following is a list of packages from the wider Julia ecosystem that may be of interest.

  • DescriptorSystems.jl contains types that represent statespace systems on descriptor form, i.e., with a mass matrix. These systems can represent linear DAE systems and non-proper systems.
  • TrajectoryOptimization.jl is one of the more developed packages for open-loop optimal control and trajectory optimization in Julia.
  • LowLevelParticleFilters.jl is a library for state estimation using particle filters and Kalman filters of different flavors.
  • ModelingToolkit.jl is an acausal modeling tool, similar in spirit to Modelica. A video showing ControlSystems and ModelingToolkit together is available here. ControlSystemsMTK.jl exists to ease the use of these two packages together.
  • JuliaSimControl.jl is a product that builds upon the JuliaControl ecosystem and ModelingToolkit, providing additional nonlinear and robust control methods.
  • FaultDetectionTools.jl contains utilities and observers for online fault detection.
  • ReachabilityAnalysis.jl is a package for reachability analysis. This can be used to verify stability and safety properties of linear and nonlinear systems.
  • MatrixEquations.jl contains solvers for many different matrix equations common in control. ControlSystems.jl makes use of this package for solving Riccati and Lyapunov equations.
  • JuMP.jl is a modeling language for optimization, similar to YALMIP. JuMP is suitable for solving LMI/SDP problems as well as advanced linear MPC problems.
  • SumOfSquares.jl is a package for sum-of-squares programming that builds on top of JuMP. Their documentation contains examples of Lyapunov-function search and nonlinear synthesis.
  • MonteCarloMeasurements.jl is a library for working with parametric uncertainty. An example using ControlSystems is available here.
  • DifferentialEquations.jl is the home of the SciML ecosystem that provides solvers for scientific problems. ControlSystems.jl uses these solvers for continuous-time simulations.
  • Dojo.jl is a differentiable robot simulator.
  • StaticCompiler.jl contains tools for compiling small binaries of Julia programs.
  • JuliaPOMDP is a Julia ecosystem for reinforcement learning.
  • JuliaReinforcementLearning is another Julia ecosystem for reinforcement learning.
diff --git a/dev/lib/analysis/index.html b/dev/lib/analysis/index.html index 59fdbf643..e9e05dea2 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.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.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.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 24f5ec2eb..4687e7abb 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::TransferFunction, C::TransferFunction, F::TransferFunction)

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::TransferFunction, C::TransferFunction, F::TransferFunction)

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.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.

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.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.

source
diff --git a/dev/lib/nonlinear/0d818ce5.svg b/dev/lib/nonlinear/1a8293cf.svg similarity index 97% rename from dev/lib/nonlinear/0d818ce5.svg rename to dev/lib/nonlinear/1a8293cf.svg index 3cd268112..7a9ed178b 100644 --- a/dev/lib/nonlinear/0d818ce5.svg +++ b/dev/lib/nonlinear/1a8293cf.svg @@ -1,208 +1,208 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - + diff --git a/dev/lib/nonlinear/9efc8bb0.svg b/dev/lib/nonlinear/20746fdd.svg similarity index 96% rename from dev/lib/nonlinear/9efc8bb0.svg rename to dev/lib/nonlinear/20746fdd.svg index 30d0fba84..eb28be174 100644 --- a/dev/lib/nonlinear/9efc8bb0.svg +++ b/dev/lib/nonlinear/20746fdd.svg @@ -1,76 +1,76 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/5748c27f.svg b/dev/lib/nonlinear/378594e1.svg similarity index 94% rename from dev/lib/nonlinear/5748c27f.svg rename to dev/lib/nonlinear/378594e1.svg index e3bf5c12a..c239bc0c1 100644 --- a/dev/lib/nonlinear/5748c27f.svg +++ b/dev/lib/nonlinear/378594e1.svg @@ -1,249 +1,249 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - + diff --git a/dev/lib/nonlinear/e78f32d3.svg b/dev/lib/nonlinear/5fc9767a.svg similarity index 86% rename from dev/lib/nonlinear/e78f32d3.svg rename to dev/lib/nonlinear/5fc9767a.svg index 72f508a32..efc804259 100644 --- a/dev/lib/nonlinear/e78f32d3.svg +++ b/dev/lib/nonlinear/5fc9767a.svg @@ -1,90 +1,90 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/a1badc2c.svg b/dev/lib/nonlinear/9fa1213d.svg similarity index 88% rename from dev/lib/nonlinear/a1badc2c.svg rename to dev/lib/nonlinear/9fa1213d.svg index b9c342829..9cf26b96a 100644 --- a/dev/lib/nonlinear/a1badc2c.svg +++ b/dev/lib/nonlinear/9fa1213d.svg @@ -1,47 +1,47 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/nonlinear/index.html b/dev/lib/nonlinear/index.html index 7f245ed84..5c53c8973 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 20be562ed..499cd36e0 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.

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.

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))
diff --git a/dev/lib/synthesis/index.html b/dev/lib/synthesis/index.html
index a0c9730dc..2e0ffd2e2 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,26 +66,26 @@
     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
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.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.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.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.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])

Calculates and returns a PID controller.

The form can be chosen as one of the following

  • :standard - Kp*(1 + 1/(Ti*s) + Td*s)
  • :series - Kc*(1 + 1/(τi*s))*(τd*s + 1)
  • :parallel - Kp + Ki/s + Kd*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. The filter used is 1 / (1 + s*Tf + (s*Tf)^2/2), where Tf can typically be chosen as Ti/N for a PI controller and Td/N for a PID controller, and N is commonly in the range 2 to 20. The state space will be returned on controllable canonical form.

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])

Calculates and returns a PID controller.

The form can be chosen as one of the following

  • :standard - Kp*(1 + 1/(Ti*s) + Td*s)
  • :series - Kc*(1 + 1/(τi*s))*(τd*s + 1)
  • :parallel - Kp + Ki/s + Kd*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. The filter used is 1 / (1 + s*Tf + (s*Tf)^2/2), where Tf can typically be chosen as Ti/N for a PI controller and Td/N for a PID controller, and N is commonly in the range 2 to 20. The state space will be returned on controllable canonical form.

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.

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 ⟹ \\ +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.

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     │◄──── Σ ◄──────
@@ -103,11 +103,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::TransferFunction, C::TransferFunction, F::TransferFunction)

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::TransferFunction, C::TransferFunction, F::TransferFunction)

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   +----+
    |     |       |    |
@@ -118,7 +118,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₄
@@ -128,7 +128,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₄
@@ -138,7 +138,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₄
@@ -148,7 +148,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  ├─┐
@@ -170,7 +170,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" "CS" "PS" "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" "CS" "PS" "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₄
@@ -180,7 +180,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₄
@@ -190,7 +190,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₄
@@ -200,7 +200,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₄
@@ -210,7 +210,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₄
@@ -220,4 +220,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/6bd5e9cf.svg b/dev/lib/timefreqresponse/56042c84.svg similarity index 85% rename from dev/lib/timefreqresponse/6bd5e9cf.svg rename to dev/lib/timefreqresponse/56042c84.svg index a2f1c61c0..b3af95285 100644 --- a/dev/lib/timefreqresponse/6bd5e9cf.svg +++ b/dev/lib/timefreqresponse/56042c84.svg @@ -1,43 +1,43 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/2d63a5d7.svg b/dev/lib/timefreqresponse/9202b49c.svg similarity index 85% rename from dev/lib/timefreqresponse/2d63a5d7.svg rename to dev/lib/timefreqresponse/9202b49c.svg index cb6d8e529..382dc2fa7 100644 --- a/dev/lib/timefreqresponse/2d63a5d7.svg +++ b/dev/lib/timefreqresponse/9202b49c.svg @@ -1,52 +1,52 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/c860ca80.svg b/dev/lib/timefreqresponse/ba7329a7.svg similarity index 86% rename from dev/lib/timefreqresponse/c860ca80.svg rename to dev/lib/timefreqresponse/ba7329a7.svg index bdb029400..27bcfccaa 100644 --- a/dev/lib/timefreqresponse/c860ca80.svg +++ b/dev/lib/timefreqresponse/ba7329a7.svg @@ -1,65 +1,65 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/lib/timefreqresponse/index.html b/dev/lib/timefreqresponse/index.html index 4e17f7916..9aa3c57f4 100644 --- a/dev/lib/timefreqresponse/index.html +++ b/dev/lib/timefreqresponse/index.html @@ -2,7 +2,7 @@ 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/6446fc97.svg b/dev/man/creating_systems/94675c01.svg similarity index 86% rename from dev/man/creating_systems/6446fc97.svg rename to dev/man/creating_systems/94675c01.svg index d0f3898b4..e5af87e6d 100644 --- a/dev/man/creating_systems/6446fc97.svg +++ b/dev/man/creating_systems/94675c01.svg @@ -1,83 +1,83 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/man/creating_systems/index.html b/dev/man/creating_systems/index.html index 9b0c2f23c..2d792d421 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.9730071096773746  -2.298287675084933
-  0.0  -1.0   0.4933456944891067  -1.8551420729705308
-  0.0   0.0  -2.042805810770514   -2.523221164112835
-  0.0   0.0  -1.819804527023511   -2.9953694744594244
+ -1.0   0.0   0.006305300052374094   1.3139369855738614
+  0.0  -1.0   1.1549598374597116    -0.9983823883353837
+  0.0   0.0  -1.3374648942601268    -0.7712299225271819
+  0.0   0.0   0.85316202925009       0.4803903613190623
 B = 
-  0.3139226336055105  -0.40958593871834703
- -0.0750107272388175  -0.1011629685987092
-  0.7177216189305121  -2.0038122389644806
-  0.6713647849542677   0.5397370264658145
+ -0.201975835188191   -0.414432987047086
+ -1.0706949732849842   1.5809641843114859
+ -0.904445021183683    1.700776662307409
+ -0.404606576268416   -0.01933504671628342
 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.9730071096773746  -2.298287675084933
-  0.0  -1.0   0.4933456944891067  -1.8551420729705308
-  0.0   0.0  -2.042805810770514   -2.523221164112835
-  0.0   0.0  -1.819804527023511   -2.9953694744594244
+ -1.0   0.0   0.006305300052374094   1.3139369855738614
+  0.0  -1.0   1.1549598374597116    -0.9983823883353837
+  0.0   0.0  -1.3374648942601268    -0.7712299225271819
+  0.0   0.0   0.85316202925009       0.4803903613190623
 B = 
-  0.3139226336055105  -0.40958593871834703
- -0.0750107272388175  -0.1011629685987092
-  0.7177216189305121  -2.0038122389644806
-  0.6713647849542677   0.5397370264658145
+ -0.201975835188191   -0.414432987047086
+ -1.0706949732849842   1.5809641843114859
+ -0.904445021183683    1.700776662307409
+ -0.404606576268416   -0.01933504671628342
 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.2068014354825678   0.0
-  0.0                 -1.1723344721366311
+ -0.048301005435514965   0.0
+  0.0                   -0.05805502231194015
 B = 
- 0.42275797775344315   0.0
- 0.0                  -0.6087999702806123
+ -0.5667213577087555  0.0
+  0.0                 1.5559493939944422
 C = 
- -1.7681337042950172   0.0
-  0.0                 -1.1801764467721507
+ -1.1206222378437896   0.0
+  0.0                 -1.7461691205487961
 D = 
- 1.134398722341151  0.0
- 0.0                0.20090063399124075
+ 0.6601171611678346  0.0
+ 0.0                 1.3710850041559872
 
 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 = 
- -0.09193645374910964
+ -0.005191290506354911
 B = 
- -0.4292483474928767
+ 0.02610656632766166
 C = 
- -0.6826626498669832
+ 0.9537471147056108
 D = 
- 0.18174197127444716
+ 0.463571854144401
 
-Continuous-time state-space model  …  StateSpace{Continuous, Float64}
+Continuous-time state-space model     …  StateSpace{Continuous, Float64}
 A = 
- -0.09193645374910964
+ -0.005191290506354911
 B = 
- 2.0874011852657395
+ 0.16514967443936893
 C = 
- -0.6826626498669832
+ 0.9537471147056108
 D = 
- 0.4696933049047494
+ 1.3452118122647354
 
 Continuous-time state-space model
  StateSpace{Continuous, Float64}
 A = 
- -0.09193645374910964
+ -0.005191290506354911
 B = 
- -0.4292483474928767
+ 0.02610656632766166
 C = 
- -0.0670304389177525
+ -0.7676157941950167
 D = 
- 0.4975367494242059
+ -1.1941312638144796
 
-Continuous-time state-space model      StateSpace{Continuous, Float64}
+Continuous-time state-space model     StateSpace{Continuous, Float64}
 A = 
- -0.09193645374910964
+ -0.005191290506354911
 B = 
- 2.0874011852657395
+ 0.16514967443936893
 C = 
- -0.0670304389177525
+ -0.7676157941950167
 D = 
- 0.6737634856754471
+ -1.1328050651468182
 
 Continuous-time state-space model

Creating arrays with different types of systems

When calling hcat/vcat, Julia automatically tries to promote the types to the smallest common supertype, this means that creating an array with one continuous and one discrete-time system fails

P_cont = ssrand(2,3,1)
 P_disc = ssrand(2,3,1, Ts=1)
@@ -303,28 +303,28 @@
       Thrown: ErrorException

You can explicitly tell Julia that you want a particular supertype, e.g,

StateSpace[P_cont, P_disc]
2-element Vector{StateSpace}:
  StateSpace{Continuous, Float64}
 A = 
- -0.005854337787121293
+ -0.015674221803348737
 B = 
- -0.7855499184705256  0.03837463456487948  -0.5352287964729737
+ -1.3526022341347577  -0.705534388005141  -0.8142966937114064
 C = 
- 0.4695903577722625
- 1.8189898880178907
+ 1.9332563628339654
+ 0.3774829318446776
 D = 
- -0.618784498384777    1.3461842081505588  -0.04372967522122136
- -0.5043589026485487  -0.5229622960012005  -0.3029981682903917
+ -0.24344806178461076  0.8190759281024835   0.02536712624546477
+ -0.9938952136319724   0.23308572555983725  0.926280328396496
 
 Continuous-time state-space model
  StateSpace{Discrete{Int64}, Float64}
 A = 
- -0.9
+ 0.9
 B = 
- -1.0740436309810928  0.02493533222271873  0.09023861344483787
+ -0.12918359948640698  0.4912268758757623  1.438403995028269
 C = 
-  2.342982493160079
- -2.1536995305356035
+ -0.8917590082840476
+ -0.48001299190051766
 D = 
- -0.09885233157880048  -0.36640064615844853  0.4499398723976746
-  1.022735088603068     0.3126340277939648   0.19165483844834366
+  0.9869076681777165   0.9323035518547163  0.8013037799675026
+ -0.8242352425994235  -1.3373479846522187  0.011864173583359781
 
 Sample Time: 1 (seconds)
 Discrete-time state-space model

The type StateSpace is abstract, since the type parameters are not specified.

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   ┌─────┐     ┌─────┐
@@ -419,4 +419,4 @@
 fs = 100
 df = digitalfilter(Bandpass(5, 10; fs), Butterworth(2))
 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)
Example block output

See also

+bodeplot(G, xscale=:identity, yscale=:identity, hz=true)Example block output

See also

diff --git a/dev/man/introduction/b205c514.svg b/dev/man/introduction/3299b973.svg similarity index 85% rename from dev/man/introduction/b205c514.svg rename to dev/man/introduction/3299b973.svg index a2ed5b7c5..ecd394f45 100644 --- a/dev/man/introduction/b205c514.svg +++ b/dev/man/introduction/3299b973.svg @@ -1,73 +1,73 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/man/introduction/index.html b/dev/man/introduction/index.html index 81a9e5d6b..c4f0e7771 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/plots/lqrplot.svg b/dev/plots/lqrplot.svg index 522b71fea..629466dbb 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 e35a6fe9d..92ad0ea41 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 f3fd50bc7..319e86ea5 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 2692bb1cc..70998740a 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 e4f76a3a9..31c96b100 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 37d059bc6..294d3158d 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 adb377410..760ac3b4e 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 06e12feaf..047f3a8a7 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 0041b0736..3c5610b22 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 dfa454d28..13362260e 100644 --- a/dev/plots/stab3.svg +++ b/dev/plots/stab3.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/search_index.js b/dev/search_index.js index 5cce146c9..60076c523 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"#using ChainRules, ForwardDiff, ChainRulesCore, LinearAlgebra\n# @ForwardDiff_frule LinearAlgebra.exp!(x1::AbstractMatrix{<:ForwardDiff.Dual})\n\n# Replace by ForwardDiffChainRules when https://github.com/ThummeTo/ForwardDiffChainRules.jl/pull/16 is merged\n#function LinearAlgebra.exp!(A::AbstractMatrix{<:ForwardDiff.Dual})\n# Av = ForwardDiff.value.(A)\n# J = reduce(vcat, transpose.(ForwardDiff.partials.(A)))\n# CS = length(ForwardDiff.partials(A[1,1]))\n# dself = NoTangent();\n# cAv = copy(Av)\n# eA, newJ1 = ChainRules.frule((dself, reshape(J[:,1], size(A))), LinearAlgebra.exp!, cAv)\n#\n# newJt = ntuple(Val(CS - 1)) do i\n# xpartialsi = reshape(J[:, i+1], size(A))\n# cAv .= Av\n# _, ypartialsi = ChainRulesCore.frule((dself, xpartialsi), LinearAlgebra.exp!, cAv)\n# ypartialsi\n# end\n# newJ = hcat(vec(newJ1), vec.(newJt)...)\n# T = ForwardDiff.tagtype(eltype(A))\n# flaty = ForwardDiff.Dual{T}.(\n# eA, reshape(ForwardDiff.Partials.(NTuple{CS}.(eachrow(newJ))), size(A)),\n# )\n#end","category":"page"},{"location":"examples/automatic_differentiation/#Automatic-Differentiation","page":"Automatic differentiation","title":"Automatic Differentiation","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"In Julia, it is often possible to automatically compute derivatives, gradients, Jacobians and Hessians of arbitrary Julia functions with precision matching the machine precision, that is, without the numerical inaccuracies incurred by finite-difference approximations.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Two general methods for automatic differentiation are available: forward and reverse mode. Forward mode is algorithmically more favorable for functions with few inputs but many outputs, while reverse mode is more efficient for functions with many parameters but few outputs (like in deep learning). In Julia, forward-mode AD is provided by the package ForwardDiff.jl, while reverse-mode AD is provided by several different packages, such as Zygote.jl and ReverseDiff.jl. Forward-mode AD generally has a lower overhead than reverse-mode AD, so for functions of a small number of parameters, say, less than about 10 or 100, forward-mode is usually most efficient. ForwardDiff.jl also has support for differentiating most of the Julia language, making the probability of success higher than for other packages, why we generally recommend trying ForwardDiff.jl first.","category":"page"},{"location":"examples/automatic_differentiation/#Linearizing-nonlinear-dynamics","page":"Automatic differentiation","title":"Linearizing nonlinear dynamics","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Nonlinear dynamics on the form","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"beginaligned\ndot x = f(x u) \ny = g(x u)\nendaligned","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"is easily linearized in the point x_0 u_0 using ForwardDiff.jl:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ControlSystemsBase, ForwardDiff\n\n\"An example of nonlinear dynamics\"\nfunction f(x, u)\n x1, x2 = x\n u1, u2 = u\n [x2; u1*x1 + u2*x2]\nend\n\nx0 = [1.0, 0.0] # Operating point to linearize around\nu0 = [0.0, 1.0]\n\nA = ForwardDiff.jacobian(x -> f(x, u0), x0)\nB = ForwardDiff.jacobian(u -> f(x0, u), u0)\n\n\"An example of a nonlinear output (measurement) function\"\nfunction g(x, u)\n y = [x[1] + 0.1x[1]*u[2]; x[2]]\nend\n\nC = ForwardDiff.jacobian(x -> g(x, u0), x0)\nD = ForwardDiff.jacobian(u -> g(x0, u), u0)\n\nlinear_sys = ss(A, B, C, D)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The example above linearizes f in the point x_0 u_0 to obtain the linear statespace matrices A and B, and linearizes g to obtain the linear output matrices C and D. Instead of manually calling ForwardDiff.jl to linearize the dynamics, the user may call the function ControlSystemsBase.linearize which includes the necessary calls to ForwardDiff.jl.","category":"page"},{"location":"examples/automatic_differentiation/#Optimization-based-tuning–PID-controller","page":"Automatic differentiation","title":"Optimization-based tuning–PID controller","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"This example will demonstrate simple usage of AD using ForwardDiff.jl for optimization-based auto tuning of a PID controller.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The system we will control is a double-mass system, in which two masses (or inertias) are connected by a flexible transmission. ","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"We start by defining the system model and an initial guess for the PID controller parameters","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ControlSystemsBase, ForwardDiff, Plots\n\nP = DemoSystems.double_mass_model()\n\nbodeplot(P, title=\"Bode plot of Double-mass system \\$P(s)\\$\")","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Ω = exp10.(-2:0.03:3)\nkp,ki,kd,Tf = 1, 0.1, 0.1, 0.01 # controller parameters\n\nC = pid(kp, ki, kd; Tf, form=:parallel, state_space=true) # Construct a PID controller with filter\nG = feedback(P*C) # Closed-loop system\nS = 1/(1 + P*C) # Sensitivity function\nGd = c2d(G, 0.1) # Discretize the system\nres = step(Gd,15) # Step-response\n\nmag = bodev(S, Ω)[1]\nplot(res, title=\"Time response\", layout = (1,3), legend=:bottomright)\nplot!(Ω, mag, title=\"Sensitivity function\", xscale=:log10, yscale=:log10, subplot=2, legend=:bottomright, ylims=(3e-2, Inf))\nMs, _ = hinfnorm(S)\nhline!([Ms], l=(:black, :dash), subplot=2, lab=\"\\$M_S = \\$ $(round(Ms, digits=3))\", sp=2)\nnyquistplot!(P*C, Ω, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), size=(1200,400))","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The initial controller C achieves a maximum peak of the sensitivity function of M_S = 13 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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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).","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using Optimization, Statistics, LinearAlgebra\nusing Ipopt, OptimizationMOI; MOI = OptimizationMOI.MOI\n\nfunction plot_optimized(P, params, res, systems)\n fig = plot(layout=(1,3), size=(1200,400), bottommargin=2Plots.mm)\n for (i,params) = enumerate((params, res))\n ls = (i == 1 ? :dash : :solid)\n lab = (i==1 ? \"Initial\" : \"Optimized\")\n C, G = systems(params, P)\n\t\tr1, r2 = sim(G)\n mag = reshape(bode(G, Ω)[1], 4, :)'[:, [1, 2, 4]]\n plot!([r1, r2]; title=\"Time response\", subplot=1,\n lab = lab .* [\" \\$r → e\\$\" \" \\$d → e\\$\"], legend=:bottomright, ls,\n fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3])\n plot!(Ω, mag; title=\"Sensitivity functions \\$S(s), CS(s), T(s)\\$\",\n xscale=:log10, yscale=:log10, subplot=2, lab, ls,\n legend=:bottomright, fillalpha=0.05, linealpha=0.8, c=[1 2 3], linewidth=i)\n nyquistplot!(P*C, Ω; Ms_circles=Msc, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), lab, seriescolor=i, ls)\n end\n hline!([Msc], l=:dashdot, c=1, subplot=2, lab=\"Constraint\", ylims=(9e-2, Inf))\n fig\nend\n\n\"A helper function that creates a PID controller and closed-loop transfer functions\"\nfunction systemspid(params, P)\n kp,ki,kd,Tf = params # We optimize parameters in\n C = pid(kp, ki, kd; form=:parallel, Tf, state_space=true)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\n\"A helper function that simulates the closed-loop system\"\nfunction sim(G)\n Gd = c2d(G, 0.1, :tustin) # Discretize the system\n res1 = step(Gd[1, 1], 0:0.1:15) # Simulate S\n res2 = step(Gd[1, 2], 0:0.1:15) # Simulate PS\n res1, res2\nend\n\n\"The cost function to optimize\"\nfunction cost(params::AbstractVector{T}, (P, systems)) where T\n CSweight = 0.001 # Noise amplification penalty\n C, G = systems(params, P)\n res1, res2 = sim(G)\n R, _ = bodev(G[2, 1], Ω; unwrap=false)\n CS = sum(R .*= Ω) # frequency-weighted noise sensitivity\n perf = mean(abs2, res1.y .*= res1.t') + mean(abs2, res2.y .*= res2.t')\n return perf + CSweight * CS # Blend all objectives together\nend\n\n\"The sensitivity constraint to enforce robustness\"\nfunction constraints(res, params::AbstractVector{T}, (P, systems)) where T\n C, G = systems(params, P)\n S, _ = bodev(G[1, 1], Ω; unwrap=false)\n res .= maximum(S) # max sensitivity\n nothing\nend\n\nMsc = 1.3 # Constraint on Ms\n\nparams = [kp, ki, kd, 0.01] # Initial guess for parameters\n\nsolver = Ipopt.Optimizer()\nMOI.set(solver, MOI.RawOptimizerAttribute(\"print_level\"), 0)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"max_iter\"), 200)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_tol\"), 1e-1)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_constr_viol_tol\"), 1e-2)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_iter\"), 5)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"hessian_approximation\"), \"limited-memory\")\n\nfopt = OptimizationFunction(cost, Optimization.AutoForwardDiff(); cons=constraints)\n\nprob = OptimizationProblem(fopt, params, (P, systemspid);\n lb = fill(-10.0, length(params)),\n ub = fill(10.0, length(params)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\n\nres = solve(prob, solver)\nplot_optimized(P, params, res.u, systemspid)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/#Optimization-based-tuning–LQG-controller","page":"Automatic differentiation","title":"Optimization-based tuning–LQG controller","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"A^TX + XA - XBR^-1B^T X + Q = 0","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"When ImplicitDifferentiation is loaded, differentiable versions of lqr and kalman that make use of the \"implicit function\" are automatically loaded.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ImplicitDifferentiation, ComponentArrays # Both these packages are required to load the implicit differentiation rules","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"function triangular(x)\n m = length(x)\n n = round(Int, sqrt(2m-1))\n T = zeros(eltype(x), n, n)\n k = 1\n for i = 1:n, j = i:n\n T[i,j] = x[k]\n k += 1\n end\n T\nend\ninvtriangular(T) = [T[i,j] for i = 1:size(T,1) for j = i:size(T,1)]\n\nfunction systemslqr(params::AbstractVector{T}, P) where T\n n2 = length(params) ÷ 2\n Qchol = triangular(params[1:n2])\n Rchol = triangular(params[n2+1:2n2])\n Q = Qchol'Qchol\n R = Rchol'Rchol\n L = lqr(P, Q, one(T)*I(1)) # It's important that the last matrix has the correct type\n K = kalman(P, R, one(T)*I(1))\n C = observer_controller(P, L, K)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\nQ0 = diagm([1.0, 1, 1, 1]) # Initial guess LQR state penalty\nR0 = diagm([1.0, 1, 1, 1]) # Initial guess Kalman state covariance\nparams2 = [invtriangular(cholesky(Q0).U); invtriangular(cholesky(R0).U)]\n\nprob2 = OptimizationProblem(fopt, params2, (P, systemslqr);\n lb = fill(-10.0, length(params2)),\n ub = fill(10.0, length(params2)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\n\nres2 = solve(prob2, solver)\nplot_optimized(P, params2, res2.u, systemslqr)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/#Robustness-analysis","page":"Automatic differentiation","title":"Robustness analysis","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using MonteCarloMeasurements\nPu = DemoSystems.double_mass_model(k = Particles(32, Uniform(80, 120))) # Create a model with uncertainty in spring stiffness k ~ U(80, 120)\nunsafe_comparisons(true) # For the Bode plot to work\n\nC,_ = systemslqr(res2.u, P) # Get the controller assuming P without uncertainty\nGu = extended_gangoffour(Pu, C) # Form the gang-of-four with uncertainty\nw = exp10.(LinRange(-1.5, 2, 500))\nbodeplot(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\"])\nhline!([Msc], l=:dashdot, c=1, lab=\"Constraint\", ylims=(9e-2, Inf))","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Gd = c2d(Gu, 0.05) # Discretize the system\nr1 = step(Gd[1,1], 0:0.05:15) # Simulate S\nr2 = step(Gd[1,2], 0:0.05:15) # Simulate PS\nplot([r1, r2]; title=\"Time response\",\n lab = [\" \\$r → e\\$\" \" \\$d → e\\$\"], legend=:bottomright,\n fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3], ri=false, N=32)","category":"page"},{"location":"examples/automatic_differentiation/#Parameterizing-the-controller-using-feedback-gains","page":"Automatic differentiation","title":"Parameterizing the controller using feedback gains","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"function systems_sf(params::AbstractVector{T}, P) where T\n n2 = length(params) ÷ 2\n L = params[1:n2]'\n K = params[n2+1:2n2, 1:1]\n C = observer_controller(P, L, K)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\nL0 = lqr(P, Q0, I) # Initial guess\nK0 = kalman(P, R0, I)\nparams3 = [vec(L0); vec(K0)]\nprob3 = OptimizationProblem(fopt, params3, (P, systems_sf);\n lb = fill(-15.0, length(params3)),\n ub = fill(15.0, length(params3)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\nres3 = solve(prob3, solver)\nplot_optimized(P, params3, res3.u, systems_sf)","category":"page"},{"location":"examples/automatic_differentiation/#Known-limitations","page":"Automatic differentiation","title":"Known limitations","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The following issues are currently known to exist when using AD through ControlSystems.jl:","category":"page"},{"location":"examples/automatic_differentiation/#ForwardDiff","page":"Automatic differentiation","title":"ForwardDiff","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"ForwardDiff.jl works for a lot of workflows without any intervention required from the user. The following known limitations exist:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.\nThe 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.\nhinfnorm 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.\nare, 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)\nThe 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.\nAn 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.","category":"page"},{"location":"examples/automatic_differentiation/#Reverse-mode-AD","page":"Automatic differentiation","title":"Reverse-mode AD","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Zygote does not work very well at all, due to\nFrequent use of mutation for performance\nTry/catch blocks","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"DocTestSetup = quote\n using ControlSystems, Plots\n plotsDir = joinpath(dirname(pathof(ControlSystems)), \"..\", \"docs\", \"build\", \"plots\")\n mkpath(plotsDir)\n nyquistplot(ssrand(1,1,1)) # to get the warning for hover already here\n save_docs_plot(name) = (Plots.savefig(joinpath(plotsDir,name)); nothing)\n save_docs_plot(p, name) = (Plots.savefig(p, joinpath(plotsDir,name)); nothing)\nend","category":"page"},{"location":"examples/example/#Examples","page":"Design","title":"Examples","text":"","category":"section"},{"location":"examples/example/#LQR-design","page":"Design","title":"LQR design","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"The infinite-horizon LQR controller is derived as the linear state-feedback u = -Lx that minimizes the following quadratic cost function","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"L = textargmin_L int_0^infty x^T Q x + u^T R u dt","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"where x is the state vector and u is the input vector.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"The example below performs a simple LQR design for a double integrator in discrete time using the function lqr. In this example, we will use the method of lsim that accepts a function u(x t) as input. This allows us to easily simulate the system both control input and a disturbance input. For more advanced LQR and LQG design, see the LQGProblem type in RobustAndOptimalControl.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase\nusing LinearAlgebra # For identity matrix I\nusing Plots\n\n# Create system\nTs = 0.1\nA = [1 Ts; 0 1]\nB = [0; 1]\nC = [1 0]\nsys = ss(A,B,C,0,Ts)\n\n# Design controller\nQ = I # Weighting matrix for state\nR = I # Weighting matrix for input\nL = lqr(Discrete,A,B,Q,R) # lqr(sys,Q,R) can also be used\n\n# Simulation\nu(x,t) = -L*x .+ 1.5(t>=2.5) # Form control law (u is a function of t and x), a constant input disturbance is affecting the system from t≧2.5\nt = 0:Ts:5 # Time vector\nx0 = [1,0] # Initial condition\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nsave_docs_plot(\"lqrplot.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: )","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"To design an LQG controller (LQR with a Kalman filter), see the functions","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"kalman\nobserver_controller\nLQGProblem type in RobustAndOptimalControl.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"See also the following tutorial video on LQR and LQG design","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/#PID-design-functions","page":"Design","title":"PID design functions","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"A basic PID controller can be constructed using the constructor pid. In ControlSystems.jl, we often refer to three different formulations of the PID controller, which are defined as","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Standard form: K_p(1 + frac1T_i s + T_ds)\nSeries form: K_c(1 + frac1τ_i s)(τ_d s + 1)\nParallel form: K_p + fracK_is + K_d s","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Most functions that construct PID controllers allow the user to select which form to use.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"A tutorial on PID design is available here:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"The following examples show basic workflows for designing PI/PID controllers. ","category":"page"},{"location":"examples/example/#PI-loop-shaping-example","page":"Design","title":"PI loop shaping example","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"By plotting the gang of four under unit feedback for the process","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"P(s) = dfrac1(s + 1)^4","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,1])^4\ngangoffourplot(P, tf(1))","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,1])^4\nωp = 0.8\nC,kp,ki,fig = loopshapingPI(P,ωp,phasemargin=60,form=:parallel, doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"ωp = 2\nC60,kp,ki,fig = loopshapingPI(P,ωp,rl=1,phasemargin=60,form=:standard,doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/#PID-loop-shaping","page":"Design","title":"PID loop shaping","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"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 = dfracPC1 + 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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,0,0]) # A double integrator\nMt = 1.3 # Maximum magnitude of complementary sensitivity\nϕt = 75 # Angle of tangent point\nω = 1 # Frequency at which the specification holds\nC, kp, ki, kd, fig = loopshapingPID(P, ω; Mt, ϕt, doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Tf = 1/20ω\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, Tf)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"As we can see, the addition of the filter increases the high-frequency roll-off in both T and CS, which is typically desirable.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"To get better control over the filter, it can be pre-designed and supplied to loopshapingPID with the keyword argument F:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"F = tf(1, [Tf^2, 2*Tf/sqrt(2), 1])\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, F)","category":"page"},{"location":"examples/example/#Advanced-pole-zero-placement","page":"Design","title":"Advanced pole-zero placement","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"A video tutorial on pole placement is available here:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Define the process","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"ζ = 0.2\nω = 1\n\nB = [1]\nA = [1, 2ζ*ω, ω^2]\nP = tf(B,A)","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Define the desired closed-loop response, calculate the controller polynomials and simulate the closed-loop system. The design utilizes an observer poles twice as fast as the closed-loop poles. An additional observer pole is added in order to get a casual controller when an integrator is added to the controller.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystems\nimport DSP: conv\n# Control design\nζ0 = 0.7\nω0 = 2\nAm = [1, 2ζ0*ω0, ω0^2]\nAo = conv(2Am, [1/2, 1]) # Observer polynomial, add extra pole due to the integrator\nAR = [1,0] # Force the controller to contain an integrator ( 1/(s+0) )\n\nB⁺ = [1] # The process numerator polynomial can be facored as B = B⁺B⁻ where B⁻ contains the zeros we do not want to cancel (non-minimum phase and poorly damped zeros)\nB⁻ = [1]\nBm = conv(B⁺, B⁻) # In this case, keep the entire numerator polynomial of the process\n\nR,S,T = rstc(B⁺,B⁻,A,Bm,Am,Ao,AR) # Calculate the 2-DOF controller polynomials\n\nGcl = tf(conv(B,T),zpconv(A,R,B,S)) # Form the closed loop polynomial from reference to output, the closed-loop characteristic polynomial is AR + BS, the function zpconv takes care of the polynomial multiplication and makes sure the coefficient vectors are of equal length\n\nplot(step(P, 20))\nplot!(step(Gcl, 20)) # Visualize the open and closed loop responses.\nsave_docs_plot(\"ppstepplot.svg\") # hide\ngangoffourplot(P, tf(-S,R)) # Plot the gang of four to check that all transfer functions are OK\nsave_docs_plot(\"ppgofplot.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/#Stability-boundary-for-PID-controllers","page":"Design","title":"Stability boundary for PID controllers","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"The stability boundary, i.e., the surface of PID parameters where the transfer function P(s)C(s) equals -1, can be plotted with the command stabregionPID. The process can be given in function form or as a regular LTIsystem.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"P1 = s -> exp(-sqrt(s))\ndoplot = true\nform = :parallel\nkp, ki, f1 = stabregionPID(P1,exp10.(range(-5, stop=1, length=1000)); doplot, form); f1\nP2 = s -> 100*(s+6).^2. /(s.*(s+1).^2. *(s+50).^2)\nkp, ki, f2 = stabregionPID(P2,exp10.(range(-5, stop=2, length=1000)); doplot, form); f2\nP3 = tf(1,[1,1])^4\nkp, ki, f3 = stabregionPID(P3,exp10.(range(-5, stop=0, length=1000)); doplot, form); f3\n\nsave_docs_plot(f1, \"stab1.svg\") # hide\nsave_docs_plot(f2, \"stab2.svg\") # hide\nsave_docs_plot(f3, \"stab3.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: ) (Image: )","category":"page"},{"location":"examples/example/#PID-plots","page":"Design","title":"PID plots","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"This example utilizes the function pidplots, which accepts vectors of PID-parameters and produces relevant plots. The task is to take a system with bandwidth 1 rad/s and produce a closed-loop system with bandwidth 0.1 rad/s. If one is not careful and proceed with pole placement, one easily get a system with very poor robustness.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase\nP = tf([1.], [1., 1])\n\nζ = 0.5 # Desired damping\nws = exp10.(range(-1, stop=2, length=8)) # A vector of closed-loop bandwidths\nkp = 2*ζ*ws .- 1 # Simple pole placement with PI given the closed-loop bandwidth, the poles are placed in a butterworth pattern\nki = ws.^2\n\nω = exp10.(range(-3, stop = 2, length = 500))\npidplots(\n P,\n :nyquist;\n params_p = kp,\n params_i = ki,\n ω = ω,\n ylims = (-2, 2),\n xlims = (-3, 3),\n form = :parallel,\n)\nsave_docs_plot(\"pidplotsnyquist1.svg\") # hide\npidplots(P, :gof; params_p = kp, params_i = ki, ω = ω, legend = false, form=:parallel, legendfontsize=6, size=(1000, 1000))\n# You can also request both Nyquist and Gang-of-four plots (more plots are available, see ?pidplots ):\n# pidplots(P,:nyquist,:gof;kps=kp,kis=ki,ω=ω);\nsave_docs_plot(\"pidplotsgof1.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Now try a different strategy, where we have specified a gain crossover frequency of 0.1 rad/s","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"kp = range(-1, stop=1, length=8) #\nki = sqrt.(1 .- kp.^2)/10\n\npidplots(P,:nyquist,;params_p=kp,params_i=ki,ylims=(-1,1),xlims=(-1.5,1.5), form=:parallel)\nsave_docs_plot(\"pidplotsnyquist2.svg\") # hide\npidplots(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))\nsave_docs_plot(\"pidplotsgof2.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/#Further-examples","page":"Design","title":"Further examples","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"See the examples folder as well as the notebooks in ControlExamples.jl.\nSee also the paper introducing the toolbox with supplementary material.\nSee the docs for RobustAndOptimalControl.jl for additional examples.","category":"page"},{"location":"man/creating_systems/#Creating-Systems","page":"Creating Systems","title":"Creating Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This page illustrates how to create system models such as transfer functions and statespace models. This topic is also treated in the introductory video below:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/#Transfer-Functions","page":"Creating Systems","title":"Transfer Functions","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"DocTestSetup = quote\n using ControlSystems\nend","category":"page"},{"location":"man/creating_systems/#tf-Rational-Representation","page":"Creating Systems","title":"tf - Rational Representation","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The basic syntax for creating a transfer function is tf","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf(num, den) # Continuous-time system\ntf(num, den, Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"where num and den are the polynomial coefficients of the numerator and denominator of the polynomial and Ts, if provided, is the sample time for a discrete-time system.","category":"page"},{"location":"man/creating_systems/#Example:","page":"Creating Systems","title":"Example:","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf([1.0],[1,2,1])\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0\n-------------------\n1.0s^2 + 2.0s + 1.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer functions created using this method will be of type TransferFunction{SisoRational}. For more general expressions, it is often more convenient to define s = tf(\"s\"):","category":"page"},{"location":"man/creating_systems/#Example:-2","page":"Creating Systems","title":"Example:","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"julia> s = tf(\"s\")\n\nTransferFunction{Continuous,ControlSystems.SisoRational{Int64}}\ns\n-\n1\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This allows us to use s to define transfer-functions:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"julia> (s-1)*(s^2 + s + 1)/(s^2 + 3s + 2)/(s+1)\n\nTransferFunction{Continuous,ControlSystems.SisoRational{Int64}}\n s^3 - 1\n---------------------\ns^3 + 4*s^2 + 5*s + 2\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/#zpk-Pole-Zero-Gain-Representation","page":"Creating Systems","title":"zpk - Pole-Zero-Gain Representation","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sometimes it's better to represent the transfer function by its poles, zeros and gain, this can be done using the function zpk","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"zpk(zeros, poles, gain) # Continuous-time system\nzpk(zeros, poles, gain, Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"where zeros and poles are Vectors of the zeros and poles for the system and gain is a gain coefficient.","category":"page"},{"location":"man/creating_systems/#Example","page":"Creating Systems","title":"Example","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"zpk([-1.0,1], [-5, -10], 2)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoZpk{Float64, Float64}}\n (1.0s + 1.0)(1.0s - 1.0)\n2.0-------------------------\n (1.0s + 5.0)(1.0s + 10.0)\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer functions created using this method will be of type TransferFunction{SisoZpk}.","category":"page"},{"location":"man/creating_systems/#State-Space-Systems","page":"Creating Systems","title":"State-Space Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A state-space system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginaligned\ndotx = Ax + Bu \ny = Cx + Du\nendaligned","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"in continuous time, or","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginaligned\nx_t+T_s = Ax_t + Bu_t \ny_t = Cx_t + Du_t\nendaligned","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"in discrete time, is created using","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"ss(A,B,C,D) # Continuous-time system\nss(A,B,C,D,Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"and they behave similarly to transfer functions.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The ss constructor allows you to","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Pass 0 instead of a D matrix, and an appropriately sized zero matrix is created automatically.\nPass I instead of a C matrix, and an appropriately sized identity matrix is created automatically. The UniformScaling operator I lives in the LinearAlgebra standard library which must be loaded first.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"State-space systems with heterogeneous matrix types are also available, which can be used to create systems with static or sized matrices, e.g.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase, StaticArrays\nsys = ss([-5 0; 0 -5],[2; 2],[3 3],[0])\nHeteroStateSpace(sys, to_sized)\nHeteroStateSpace(sys, to_static)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Notice the different matrix types used.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"To associate names with states, inputs and outputs, see named_ss from RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/#Converting-between-types","page":"Creating Systems","title":"Converting between types","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"It is sometime useful to convert one representation to another. This is possible using the constructors tf, zpk, ss, for example","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf(zpk([-1], [1], 2, 0.1))\n\n# output\n\nTransferFunction{Discrete{Float64}, ControlSystemsBase.SisoRational{Int64}}\n2z + 2\n------\nz - 1\n\nSample Time: 0.1 (seconds)\nDiscrete-time transfer function model","category":"page"},{"location":"man/creating_systems/#Converting-between-continuous-and-discrete-time","page":"Creating Systems","title":"Converting between continuous and discrete time","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A continuous-time system represents differential equations or a transfer function in the Laplace domain, while a discrete-time system represents difference equations or a transfer function in the Z-domain.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The functions c2d and d2c implement sampling/discretization of continuous-time systems and the inverse mapping from discrete-time to continuous-time systems. ","category":"page"},{"location":"man/creating_systems/#Delay-Systems","page":"Creating Systems","title":"Delay Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The constructor delay creates a pure delay, which may be connected to a system by multiplication:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"delay(1.2) # Pure delay or 1.2s\ntf(1, [1, 1])*delay(1.2) # Input delay\ndelay(1.2)*tf(1, [1, 1]) # Output delay","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Delayed systems can also be created using","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"s = tf(\"s\")\nL = 1.2 # Delay time\ntf(1, [1, 1]) * exp(-L*s)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Padé approximations of delays can be created using pade. Models with delays can be discretized using c2d, currently, only delays that are integer multiples of the sample time are supported. Pure fractional delays can be approximately discretized using the function thiran.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A tutorial on delay systems is available here:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/#Nonlinear-Systems","page":"Creating Systems","title":"Nonlinear Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See Nonlinear functionality.","category":"page"},{"location":"man/creating_systems/#Simplifying-systems","page":"Creating Systems","title":"Simplifying systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A statespace system with a non-minimal realization, or a transfer function with overlapping zeros and poles, may be simplified using the function minreal. Systems that are structurally singular, i.e., that contains outputs that can not be reached from the inputs based on analysis of the structure of the zeros in the system matrices only, can be simplified with the function sminreal.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Examples:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nG = tf([1, 1], [1, 1])\nminreal(G) # Performs pole-zero cancellation\n\nP = tf(1, [1, 1]) |> ss\nG = P / (1 + P) # this creates a non-minimal realization, use feedback(P) instead\nfeedback(P) # Creates a minimal realization directly\nGmin = minreal(G) # this simplifies the realization to a minimal realization\nnorm(Gmin - feedback(P), Inf) # No difference\nbodeplot([G, Gmin, feedback(P)]) # They are all identical","category":"page"},{"location":"man/creating_systems/#Multiplying-systems","page":"Creating Systems","title":"Multiplying systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Two systems can be connected in series by multiplication","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nP1 = ss(-1,1,1,0)\nP2 = ss(-2,1,1,0)\nP2*P1","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P2 above).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"If the input dimension of P2 does not match the output dimension of P1, an error is thrown. If one of the systems is SISO and the other is MIMO, broadcasted multiplication will expand the SISO system to match the input or output dimension of the MIMO system, e.g.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Pmimo = ssrand(2,2,1)\nPsiso = ss(-2,1,1,0)\n# Psiso * Pmimo # error\nPsiso .* Pmimo ≈ [Psiso 0; 0 Psiso] * Pmimo # Broadcasted multiplication expands SISO into diagonal system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Broadcasted multiplication between a system and an array is only allowed for diagonal arrays","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using LinearAlgebra\nPsiso .* I(2)","category":"page"},{"location":"man/creating_systems/#Adding-systems","page":"Creating Systems","title":"Adding systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Two systems can be connected in parallel by addition","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P12 = P1 + P2","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P1 above).","category":"page"},{"location":"man/creating_systems/#MIMO-systems-and-arrays-of-systems","page":"Creating Systems","title":"MIMO systems and arrays of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Concatenation of systems creates MIMO systems, which is different from an array of systems. For example","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nP = ss(-1,1,1,0)\nP_MIMO = [P 2P]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"is a 1×2 MISO system, not a 1×2 array.","category":"page"},{"location":"man/creating_systems/#From-SISO-to-MIMO","page":"Creating Systems","title":"From SISO to MIMO","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"SISO systems do not multiply MIMO systems directly, i.e.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using Test\nsiso = ss(-1,1,1,0)\nmimo = ssrand(2,2,2)\n@test_throws DimensionMismatch siso * mimo","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"To multiply siso with each output channel of mimo in the example above, use broadcasting:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"siso .* mimo","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This is equivalent to first expanding the SISO system into a diagonal system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using LinearAlgebra\n(siso .* I(2)) * mimo","category":"page"},{"location":"man/creating_systems/#Converting-an-array-of-systems-to-a-MIMO-system","page":"Creating Systems","title":"Converting an array of systems to a MIMO system","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Diagonal MIMO systems can be created from a vector of systems using append","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P1 = ssrand(1,1,1)\nP2 = ssrand(1,1,1)\nappend(P1, P2)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"More general arrays of systems can be converted to a MIMO system using array2mimo.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"sys_array = fill(P, 2, 2) # Creates an array of systems\nmimo_sys = array2mimo(sys_array)","category":"page"},{"location":"man/creating_systems/#Converting-MIMO-system-to-an-array-of-systems","page":"Creating Systems","title":"Converting MIMO system to an array of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This conversion is not explicitly supported, but is easy enough to accomplish with standard Julia code, for example:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P = ssrand(2,3,1) # A random 2×3 MIMO system\nsys_array = getindex.(Ref(P), 1:P.ny, (1:P.nu)')","category":"page"},{"location":"man/creating_systems/#Creating-arrays-with-different-types-of-systems","page":"Creating Systems","title":"Creating arrays with different types of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"When calling hcat/vcat, Julia automatically tries to promote the types to the smallest common supertype, this means that creating an array with one continuous and one discrete-time system fails","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P_cont = ssrand(2,3,1) \nP_disc = ssrand(2,3,1, Ts=1)\n@test_throws ErrorException [P_cont, P_disc] # ERROR: Sampling time mismatch","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"You can explicitly tell Julia that you want a particular supertype, e.g,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"StateSpace[P_cont, P_disc]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The type StateSpace is abstract, since the type parameters are not specified.","category":"page"},{"location":"man/creating_systems/#Demo-systems","page":"Creating Systems","title":"Demo systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The module ControlSystemsBase.DemoSystems contains a number of demo systems demonstrating different kinds of dynamics.","category":"page"},{"location":"man/creating_systems/#From-block-diagrams-to-code","page":"Creating Systems","title":"From block diagrams to code","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This section lists a number of block diagrams, and indicates the corresponding transfer functions and how they are built in code.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"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.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The returned closed-loop system will have a state vector comprised of the state of G1 followed by the state of G2.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Closed-loop system from reference to output","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"r ┌─────┐ ┌─────┐\n───►│ │ u │ │ y\n │ C ├────►│ P ├─┬─►\n -┌►│ │ │ │ │\n │ └─────┘ └─────┘ │\n │ │\n └─────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfracPCI+PCR","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(P*C) or equivalently comp_sensitivity(P, C). Here, the system PC appears directly between the input r and the output y, and the feedback loop is negative identity. We thus call feedback(P*C) = feedback(P*C, 1)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"d ┌───┐ y\n───+─►│ P ├─┬───►\n -▲ └───┘ │\n │ │\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfracPI+PCD = PSD","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(P, C) or equivalently G_PS(P, C). Here, only P appears directly between d and y, while C appears first in the feedback loop. We thus call feedback(P, C)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sensitivity function at plant input","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"d e┌───┐ \n───+─►│ P ├─┬───►\n -▲ └───┘ │\n │ │\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"E = dfrac1I+CPD = SD","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(1, C*P) or equivalently input_sensitivity(P, C). Here, there are no systems directly between the input and the output, we thus call feedback(1, C*P). Note the order in C*P, which is important for MIMO systems. This computes the sensitivity function at the plant input. It's more common to analyze the sensitivity function at the plant output, illustrated below (for SISO systems they are equivalent).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sensitivity function at plant output","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" ┌───┐ \n───+─►│ P ├─+◄── e\n -▲ └───┘ │\n │ │y\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfrac1I+PCE = SE","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(1, P*C) or equivalently output_sensitivity(P, C). Note the reverse order in PC compared to the input sensitivity function above.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Reference r and input disturbance d to output y and control signal u. This example forms the transfer function matrix with r and d as inputs, and y and u as outputs.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" d\n ┌─────┐ │ ┌─────┐\nr │ │u ▼ │ │ y\n──+─►│ C ├──+─►│ P ├─┬─►\n ▲ │ │ │ │ │\n -│ └─────┘ └─────┘ │\n │ │\n └──────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginbmatrix\ny u\nendbmatrix = \nbeginbmatrix\ndfracPCI + PC dfracCI + PC \ndfracPI + PC dfrac-PCI + PC\nendbmatrix\nbeginbmatrix\nr d\nendbmatrix","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(C, P, W2=:, Z2=:, Zperm=[(1:P.ny).+P.nu; 1:P.nu]) # y,u from r,d. Here, we have reversed the order of P and C to get the correct sign of the control signal. We also make use of the keyword arguments W2 and Z2 to specify that we want to include the inputs and outputs of P as external inputs and outputs, and Zperm to specify the order of the outputs (y before u).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Linear fractional transformation","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" ┌─────────┐\nz◄───┤ │◄────w\n │ P │\ny┌───┤ │◄───┐u\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n │ │ │ │\n └─────►│ K ├───────┘\n │ │\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Z = operatornamelft(P K) W","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: lft(P, K)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" z1 z2\n ▲ ┌─────┐ ▲ ┌─────┐\n │ │ │ │ │ │\nw1──+─┴─►│ C ├──┴───+─►│ P ├─┐\n │ │ │ │ │ │ │\n │ └─────┘ │ └─────┘ │\n │ w2 │\n └────────────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer function from w_1 w_2 to z_1 z_2 contains all the transfer functions that are commonly called \"gang of four\" (see also gangoffour).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginbmatrix\nz_1 z_2\nendbmatrix = \nbeginbmatrix\nI C\nendbmatrix (I + PC)^-1 beginbmatrix\nI P\nendbmatrix\nbeginbmatrix\nw_1 w_2\nendbmatrix","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: This function requires the package RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"RobustAndOptimalControl.extended_gangoffour(P, C, pos=true)\n# For SISO P\nS = G[1, 1]\nPS = G[1, 2]\nCS = G[2, 1]\nT = G[2, 2]\n\n# For MIMO P\nS = G[1:P.ny, 1:P.nu]\nPS = G[1:P.ny, P.nu+1:end]\nCS = G[P.ny+1:end, 1:P.nu]\nT = G[P.ny+1:end, P.nu+1:end]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See also","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"output_sensitivity\ninput_sensitivity\noutput_comp_sensitivity\ninput_comp_sensitivity\nG_PS\nG_CS\ngangoffour)\ngangoffourplot)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This diagram is more complicated and forms several connections, including both feedforward and feedback connections. A code file that goes through how to form such complicated connections using named signals is linked below. This example uses the package RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" yF\n ┌────────────────────────────────┐\n │ │\n ┌───────┐ │ ┌───────┐ yR ┌─────────┐ │ ┌───────┐\nuF │ │ │ │ ├──────► │ yC │ uP│ │ yP\n────► F ├─┴──► R │ │ C ├────+────► P ├────┬────►\n │ │ │ │ ┌──► │ │ │ │\n └───────┘ └───────┘ │- └─────────┘ └───────┘ │\n │ │\n └───────────────────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See code example complicated_feedback.jl.","category":"page"},{"location":"man/creating_systems/#Filter-design","page":"Creating Systems","title":"Filter design","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Filters can be designed using DSP.jl. This results in filter objects with types from the DSP package, which can be converted to transfer functions using tf from ControlSystemsBase.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using DSP, ControlSystemsBase, Plots\n\nfs = 100\ndf = digitalfilter(Bandpass(5, 10; fs), Butterworth(2))\nG = tf(df, 1/fs) # Sample time must be provided in the conversion to get the correct frequency scale in the Bode plot\nbodeplot(G, xscale=:identity, yscale=:identity, hz=true)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See also","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"ControlSystemsBase.seriesform","category":"page"},{"location":"man/numerical/#Performance-considerations","page":"Performance considerations","title":"Performance considerations","text":"","category":"section"},{"location":"man/numerical/#Numerical-accuracy","page":"Performance considerations","title":"Numerical accuracy","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Transfer functions, and indeed polynomials in general, are infamous for having poor numerical properties and for this reason, it's ill-advised to use high-order transfer functions. Orders as low as 6 may already be considered high. When a transfer function is converted to a state-space representation using ss(G), balancing is automatically performed in an attempt at making the numerical properties of the model better.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"This problem is illustrated below, where we first create a statespace system G and convert this to a transfer function G_1. We then perturb a single element of the dynamics matrix A by adding the machine epsilon for Float64 (eps() = 2.22044e-16), and convert this perturbed statespace system to a transfer function G_2. The difference between the two transfer functions is enormous, the norm of the difference in their denominator coefficient vectors is on the order of 10^96.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"sys = ssrand(1,1,100);\nG1 = tf(sys);\nsys.A[1,1] += eps();\nG2 = tf(sys);\nnorm(denvec(G1)[] - denvec(G2)[])\n6.270683106765845e96","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If we plot the poles of the two systems, they are also very different","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"scatter(poles(G1)); scatter!(poles(G2))","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"(Image: Noisy poles)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If we instead compute the poles of the statespace model before and after the perturbation, they are almost indistinguishable.","category":"page"},{"location":"man/numerical/#State-space-balancing","page":"Performance considerations","title":"State-space balancing","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"The function balance_statespace can be used to compute a balancing transformation T that attempts to scale the system so that the row and column norms of","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"beginbmatrix\nTAT^-1 TB\nCT^-1 0\nendbmatrix","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"are approximately equal. This typically improves the numerical performance of several algorithms, including frequency-response calculations and continuous-time simulations. When frequency-responses are plotted using any of the built-in functions, such as bodeplot or nyquistplot, this balancing is performed automatically. However, when calling bode and nyquist directly, the user is responsible for performing the balancing. The balancing is a relatively cheap operation, but it","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Changes the state representations of the system (but not the input-output mapping). If balancing is performed before simulation, the output will correspond to the output of the original system, but the state trajectory will not.\nAllocates some memory.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Balancing is also automatically performed when a transfer function is converted to a statespace system using ss(G), to convert without balancing, call convert(StateSpace, G, balance=false).","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Intuitively (and simplified), balancing may be beneficial when the magnitude of the elements of the B matrix are vastly different from the magnitudes of the element of the C matrix, or when the A matrix contains very large coefficients. An example that exhibits all of these traits is the following","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"using ControlSystemsBase, LinearAlgebra\nA = [-6.537773175952662 0.0 0.0 0.0 -9.892378564622923e-9 0.0; 0.0 -6.537773175952662 0.0 0.0 0.0 -9.892378564622923e-9; 2.0163803998106024e8 2.0163803998106024e8 -0.006223894167415392 -1.551620418759878e8 0.002358202548321148 0.002358202548321148; 0.0 0.0 5.063545034365582e-9 -0.4479539754649166 0.0 0.0; -2.824060629317756e8 2.0198389074625736e8 -0.006234569427701143 -1.5542817673286995e8 -0.7305736722226711 0.0023622473513548576; 2.0198389074625736e8 -2.824060629317756e8 -0.006234569427701143 -1.5542817673286995e8 0.0023622473513548576 -0.7305736722226711]\nB = [0.004019511633336128; 0.004019511633336128; 0.0; 0.0; 297809.51426114445; 297809.51426114445]\nC = [0.0 0.0 0.0 1.0 0.0 0.0]\nD = [0.0]\nlinsys = ss(A,B,C,D)\nnorm(linsys.A, Inf), norm(linsys.B, Inf), norm(linsys.C, Inf)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"which after balancing becomes","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"bsys, T = balance_statespace(linsys)\nnorm(bsys.A, Inf), norm(bsys.B, Inf), norm(bsys.C, Inf)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If you plot the frequency-response of the two systems using bodeplot, you'll see that they differ significantly (the balanced one is correct).","category":"page"},{"location":"man/numerical/#Frequency-response-calculation","page":"Performance considerations","title":"Frequency-response calculation","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For small systems (small number of inputs, outputs and states), evaluating the frequency-response of a transfer function is reasonably accurate and very fast.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"G = tf(1, [1, 1])\nw = exp10.(LinRange(-2, 2, 200));\n@btime freqresp($G, $w);\n# 4.351 μs (2 allocations: 3.31 KiB)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Evaluating the frequency-response for the equivalent state-space system incurs some additional allocations due to a Hessenberg matrix factorization","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"sys = ss(G);\n@btime freqresp($sys, $w);\n# 20.820 μs (16 allocations: 37.20 KiB)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For larger systems, the state-space calculations are considerably more accurate, provided that the realization is well balanced.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For optimal performance, one may preallocate the return array","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"ny,nu = size(G)\nR = zeros(ComplexF64, ny, nu, length(w));\n\n@btime freqresp!($R, $G, $w);\n# 4.214 μs (1 allocation: 64 bytes)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Other functions that accept preallocated workspaces are","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"bodemag!\nfreqresp!\nlsim!","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"an example using bodemag! follows:","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"using ControlSystemsBase\nG = tf(ssrand(2,2,5))\nw = exp10.(LinRange(-2, 2, 20000))\n@btime bode($G, $w);\n# 55.120 ms (517957 allocations: 24.42 MiB)\n@btime bode($G, $w, unwrap=false); # phase unwrapping is slow\n# 3.624 ms (7 allocations: 2.44 MiB)\nws = ControlSystemsBase.BodemagWorkspace(G, w)\n@btime bodemag!($ws, $G, $w);\n# 2.991 ms (1 allocation: 64 bytes)","category":"page"},{"location":"man/numerical/#Time-domain-simulation","page":"Performance considerations","title":"Time-domain simulation","text":"","category":"section"},{"location":"man/numerical/#Time-scale","page":"Performance considerations","title":"Time scale","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/#Transfer-functions","page":"Performance considerations","title":"Transfer functions","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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. ","category":"page"},{"location":"man/numerical/#Discrete-time-simulation","page":"Performance considerations","title":"Discrete-time simulation","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For discrete-time systems, the function lsim! accepts a pre-allocated workspace objects that can be used to avoid allocations for repeated simulations.","category":"page"},{"location":"man/numerical/#Numerical-balancing","page":"Performance considerations","title":"Numerical balancing","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/#Static-arrays-in-StateSpace-systems","page":"Performance considerations","title":"Static arrays in StateSpace systems","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"examples/ilc/#Iterative-Learning-Control","page":"Iterative Learning Control (ILC)","title":"Iterative-Learning Control","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"In this example, we will design an Iterative-Learning Control (ILC) iteration scheme. ILC can be thought of as a simple reinforcement-learning strategy that is suitable in situations where a repetitive task is to be performed multiple times, and disturbances acting on the system are also repetitive and predictable but unknown. Multiple versions of ILC exists, in this tutorial we will consider a heuristic scheme as well as a model-based scheme. ","category":"page"},{"location":"examples/ilc/#Algorithm","page":"Iterative Learning Control (ILC)","title":"Algorithm","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The ILC iteration scheme typically looks something like this (many variants exists), at ILC iteration k:","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"beginaligned\ny_k(t) = G(q) big(r(t) + a_k(t) big) \ne_k(t) = r(t) - y_k(t) \na_k(t) = Q(q) big( a_k-1(t) + L(q) e_k-1(t) big)\nendaligned","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"where q is the time-shift operator, G(q) is the transfer function from the reference r to the output y, i.e, typically a closed-loop transfer function, e_k is the control error and a_k is the ILC adjustment signal, an additive correction to the reference that is learned throughout the ILC iterations in order to minimize the control error. Q(q) and L(q) are stable filters that control the learning dynamics. Interestingly, these filters does not have to be causal since they operate on the signals e and a between ILC iterations, when the whole signals are available at once for acausal filtering. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"In simulation (the rollout y_k = G(q) (r + a_k) is simulated), this scheme is nothing other than an open-loop optimal-control strategy, while if y_k = G(q) (r + a_k) amounts to performing an actual experiment on a process, ILC turns into episode-based reinforcement learning or adaptive control.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The system to control in this example is a double-mass system with a spring and damper in between. This system is a common model of a servo system where one mass represents the motor and the other represents the load. The spring and damper represents a flexible transmission between them. We will create two instances of the system model. G represents the nominal model, whereas G_act represents the actual (unknown) dynamics. This simulates a model-based approach where there is a slight error in the model. The error will lie in the mass of the load, simulating, e.g., that the motor is driving a heavier load than specified. ","category":"page"},{"location":"examples/ilc/#System-model-and-controller","page":"Iterative Learning Control (ILC)","title":"System model and controller","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"using ControlSystemsBase, Plots\n\nfunction double_mass_model(; \n Jm = 1, # motor inertia\n Jl = 1, # load inertia\n k = 100, # stiffness\n c0 = 1, # motor damping\n c1 = 1, # transmission damping\n c2 = 1, # load damping\n)\n\n A = [\n 0.0 1 0 0\n -k/Jm -(c1 + c0)/Jm k/Jm c1/Jm\n 0 0 0 1\n k/Jl c1/Jl -k/Jl -(c1 + c2)/Jl\n ]\n B = [0, 1/Jm, 0, 0]\n C = [1 0 0 0]\n ss(A, B, C, 0)\nend\n\nG = double_mass_model(Jl = 1)\nGact = double_mass_model(Jl = 1.5) # 50% more load than modeled\n\nbodeplot([G, Gact], lab=[\"G model\" \"G actual\"], plotphase=false)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"C = pid(10, 1, 1, form = :series) * tf(1, [0.02, 1])\nTs = 0.02 # Sample time\nGc = c2d(feedback(G*C), Ts) |> tf\nGcact = c2d(feedback(Gact*C), Ts) |> tf\nplot(step(Gc, 10), title=\"Closed-loop step response\", lab=\"model\")\nplot!(step(Gcact, 10), lab=\"actual\")","category":"page"},{"location":"examples/ilc/#Reference-trajectory","page":"Iterative Learning Control (ILC)","title":"Reference trajectory","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"Next up we design a reference trajectory and simulate the actual closed-loop dynamics.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"T = 3pi # Duration\nt = 0:Ts:T # Time vector\nfunction funnysin(x)\n x = sin(x)\n s,a = sign(x), abs(x)\n s*((a + 0.01)^0.2 - 0.01^0.2)\nend\nr = funnysin.(t)' |> Array # Reference signal\n\nres = lsim(Gcact, r, t)\nplot(res, plotu=true, layout=1, sp=1, title=\"Closed-loop simulation with actual dynamics\", lab=[\"y\" \"r\"])","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"Performance is poor.. Enter ILC!","category":"page"},{"location":"examples/ilc/#Non-causal-filtering","page":"Iterative Learning Control (ILC)","title":"Non-causal filtering","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"function lsim_zerophase(G, u, args...; kwargs...)\n res = lsim(G, u[:, end:-1:1], args...; kwargs...)\n lsim(G, res.y[:, end:-1:1], args...; kwargs...).y\nend\n\nfunction lsim_noncausal(L::LTISystem{<:Discrete}, u, args...; kwargs...)\n np = length(denpoly(L)[])\n nz = length(numpoly(L)[])\n zeroexcess = nz-np\n if zeroexcess <= 0\n return lsim(L, u, args...; kwargs...)\n end\n integrators = tf(1, [1, 0], L.Ts)^zeroexcess\n res = lsim(L*integrators, u, args...; kwargs...)\n res.y[1:end-zeroexcess] .= res.y[1+zeroexcess:end]\n res.y\nend\nnothing # hide","category":"page"},{"location":"examples/ilc/#Choosing-filters","page":"Iterative Learning Control (ILC)","title":"Choosing filters","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The next step is to define the ILC filters Q(q) and L(q).","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The filter L(q) acts as a frequency-dependent step size. To make the procedure take smaller steps, simply scale L by a constant < 1. Scaling down L makes the learning process slower but more robust. A heuristic choice of L is some form of scaled lookahead, such as 05z^l where l geq 0 is the number of samples lookahead. A model-based approach may use some form of inverse of the system model, which is what we will use here. [nonlinear]","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"[nonlinear]: Inverse models can be formed also for some nonlinear systems. ModelingToolkit.jl is particularily well suited for inverting models due to its acausal nature.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The filter Q(q) acts to make the procedure robust w.r.t. noise and modeling errors. Q has a final say over what frequencies appear in a and it's good to choose Q with low-pass properties. Q will here be applied in zero-phase mode, so the effective transfer function will be Q(z)Q(z).","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"z = tf(\"z\", Ts)\nQ = c2d(tf(1, [0.05, 1]), Ts)\n# L = 0.9z^1 # A more conservative and heuristic choice\nL = 0.5inv(Gc) # Make the scaling factor smaller to take smaller steps\nnothing # hide","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"bodeplot([inv(Q), (1 - L*Gc)], plotphase=false, lab=[\"Stability boundary \\$Q^{-1}\\$\" \"\\$1 - LG\\$\"])\nbodeplot!((1 - L*Gcact), plotphase=false, lab=\"\\$1 - LG\\$ actual\")","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/#ILC-iteration","page":"Iterative Learning Control (ILC)","title":"ILC iteration","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The next step is to implement the ILC scheme and run it:","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"function ilc(Gc, Q, L)\n a = zero(r) # ILC adjustment signal starts at 0\n fig1 = plot(t, vec(r), sp=1, layout=(3,1), l=(:black, 3), lab=\"Ref\")\n fig2 = plot(title=\"Sum of squared error\", xlabel=\"Iteration\", legend=false, titlefontsize=10, framestyle=:zerolines, ylims=(0, 7.1))\n for iter = 1:5\n ra = r .+ a\n res = lsim(Gc, ra, t) # Simulate system, replaced by an actual experiment if running on real process\n y = res.y # System response\n e = r .- y # Error\n Le = lsim_noncausal(L, e, t)\n a = lsim_zerophase(Q, a + Le, t) # Update ILC adjustment\n\n err = sum(abs2, e)\n plot!(fig1, res, plotu=true, sp=[1 2], title=[\"Output \\$y(t)\\$\" \"Adjusted reference \\$r + a\\$\"], lab=\"Iter $iter\", c=iter)\n plot!(fig1, e[:], sp=3, title=\"Tracking error \\$e(t)\\$\", lab=\"err: $(round(err, digits=2))\", c=iter)\n scatter!(fig2, [iter], [err])\n end\n plot(fig1, fig2, layout=@layout([a{0.7w} b{0.3w}]))\nend\nilc(Gc, Q, L)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"How does it work on the \"actual\" dynamics?","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"ilc(Gcact, Q, L)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The result is subtly worse, but considering the rather big model error the result is still quite good. ","category":"page"},{"location":"examples/ilc/#Summary","page":"Iterative Learning Control (ILC)","title":"Summary","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"man/differences/#Noteworthy-Differences-from-other-Languages","page":"Noteworthy differences from other languages","title":"Noteworthy Differences from other Languages","text":"","category":"section"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"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.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"The rest of this page will list noteworthy differences between ControlSystems.jl and other pieces of control-systems software.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"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.\nSimulation 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.\nFunctions are, lqr and kalman have slightly different signatures in julia compared to in other languages. More advanced LQG functionalities are located in RobustAndOptimalControl.jl.\nSimulation using lsim, step, impulse etc. return a structure that can be plotted. These functions never plot anything themselves.\nFunctions bode, nyquist etc. never produce a plot. Instead, see bodeplot, nyquistplot etc.\nIn 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.\nIn 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.\nJulia treats matrices and vectors as different types, in particular, column vectors and row vectors are not interchangeable. \nIn 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.\nIn 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.\nIf 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!\nJulia compiles code just before it is called the first time. This introduces a noticeable lag, and can make packages take a long time to load. If you want to speed up the loading of ControlSystems.jl, consider building a system image that includes ControlSystems.jl using PackageCompiler.jl. More info about this is available below under Precompilation for faster load times","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"If you find other noteworthy differences between ControlSystems.jl and other pieces of control-related software, please consider submitting a pull request (PR) to add to the list above. You can submit a PR by clicking on \"Edit on GitHub\" at the top of this page and then clicking on the icon that looks like a pen above the file viewer. A two-minute video on this process is available below","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"","category":"page"},{"location":"man/differences/#Precompilation-for-faster-load-times","page":"Noteworthy differences from other languages","title":"Precompilation for faster load times","text":"","category":"section"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"In order to make it faster to load the ControlSystems.jl package, you may make use of PackageCompiler.jl. ","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"warning: For developers\nIf you intend to develop ControlSystem.jl, i.e., modify the source code, it's not recommended to build the package into the system image. We then recommend to build OrdinaryDiffEq into the system image since this package contributes the largest part of the loading time.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Building a custom system image can reduce the time to get started in a new Julia session, as an example:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Without system image:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"julia> @time using ControlSystems\n 1.646961 seconds (2.70 M allocations: 173.558 MiB, 1.08% gc time, 2.06% compilation time)","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"With OrdinaryDiffEq and Plots in the system image:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"julia> @time using ControlSystems\n 0.120975 seconds (413.37 k allocations: 27.672 MiB, 1.66% compilation time)","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"To build a system image with ControlSystems, save the following script in a file, e.g., precompile_controlsystems.jl (feel free to add any additional packages you may want to load).","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"using OrdinaryDiffEq # Include this if you want to develop ControlSystems.jl\nusing ControlSystems # Include this if you only want to use ControlSystems.jl\nusing Plots # In case you also want to use plotting functions\n\n# Run some statements to make sure these are precompiled. Do not include this if you want to develop ControlSystems.jl\nfor P = StateSpace[ssrand(2,2,2), ssrand(2,2,2, Ts=0.1)]\n bodeplot(P)\n nyquistplot(P)\n plot(step(P, 10))\nend","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Then run the following","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"using PackageCompiler\nPackageCompiler.create_sysimage(\n [\n :OrdinaryDiffEq,\n :Plots,\n :ControlSystems,\n ];\n precompile_execution_file = \"precompile_execution_file\",\n sysimage_path = \"sys_ControlSystems_$(VERSION).so\",\n)\nexit()","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"When you have created a system image, start Julia with the -J flag pointing to the system image that was created, named sys_ControlSystems_.so, more details here. After this, loading the package should be very fast.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"warning: Updating packages\nWhen you update installed julia packages, the update will not be reflected in the system image until the image is rebuilt. ","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"You can make vscode load this system image as well by adding","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"\"julia.additionalArgs\": [\n \"-J/path_to_sysimage/sys_ControlSystems_.so\"\n],","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"to settings.json.","category":"page"},{"location":"examples/analysis/#Analysis-of-linear-control-systems","page":"Analysis","title":"Analysis of linear control systems","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"From classical control, we get robustness measures such as gain and phase margins. These provide a quick and intuitive way to assess robustness of single-input, single-output systems, but also have a number of downsides, such as optimism in the presence of simultaneous gain and phase variations as well as limited applicability for MIMO systems.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"Gain and phase margins can be computed using the functions margin and marginplot","category":"page"},{"location":"examples/analysis/#Example:-Gain-and-phase-margins","page":"Analysis","title":"Example: Gain and phase margins","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"using ControlSystemsBase, Plots\nP = tf(1, [1, 0.2, 1])\nC = pid(0.2, 1)\nloopgain = P*C\nmarginplot(loopgain)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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.","category":"page"},{"location":"examples/analysis/#Sensitivity-analysis","page":"Analysis","title":"Sensitivity analysis","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"More generally applicable measures of robustness include analysis of sensitivity functions, notably the peaks of the sensitivity function","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"S(s) = (I + P(s)C(s))^-1","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"and the complementary sensitivity function","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"T(s) = I - S(s) = (I + P(s)C(s))^-1P(s)C(s)","category":"page"},{"location":"examples/analysis/#Examples","page":"Analysis","title":"Examples","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"We can plot all four sensitivity functions referred to as the \"gang of four\" using gangoffourplot.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"using ControlSystemsBase, Plots\nP = tf(1, [1, 0.2, 1])\nC = pid(0.2, 1)\ngangoffourplot(P, C)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"The peak value of the sensitivity function, M_S, can be computed using hinfnorm","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"S = sensitivity(P, C)\nMs, ωMs = hinfnorm(S)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"And we can plot a circle in the Nyquist plot corresponding to the inverse distance between the loop-transfer function and the critical point:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"w = exp10.(-1:0.001:2)\nnyquistplot(P*C, w, Ms_circles=[Ms], xlims=(-1.2, 0.5), ylims=(-2, 0.3))","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"beginaligned\nphi_m 2 sin^-1left(dfrac12M_Sright) textrad\ng_m dfracM_SM_S-1\nendaligned","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"We can also obtain individual sensitivity function using the low-level function feedback directly, or using one of the higher-level functions","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"sensitivity\ncomp_sensitivity\nG_PS\nG_CS\ngangoffour\nextended_gangoffour\nRobustAndOptimalControl.feedback_control","category":"page"},{"location":"examples/analysis/#Further-reading","page":"Analysis","title":"Further reading","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"A modern robustness measure is the diskmargin, that analyses the robustness of a SISO or MIMO system to simultaneous gain and phase variations.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"Basic usage of robustness analysis with JuliaControl are demonstrated in the two videos below:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"and ","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"lib/nonlinear/#Nonlinear-functionality","page":"Nonlinear","title":"Nonlinear functionality","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"danger: Experimental\nThe nonlinear interface is currently experimental and at any time subject to breaking changes not respecting semantic versioning. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ControlSystems.jl can represent nonlinear feedback systems that can be written on the form","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":" ┌─────────┐\n y◄───┤ │◄────u\n │ P │\nΔy┌───┤ │◄───┐Δu\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n └─────►│ f ├───────┘\n └───┘","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"i.e., as a linear-fractional transform (LFT) between a linear system P and a diagonal matrix with scalar non-linear functions f. This representation is identical to that used for delay systems, and is exposed to the user in a similar way as well. The main entry point is the function nonlinearity which takes a nonlinear function f like so, nonlinearity(f). This creates a primitive system containing only the nonlinearity, but which behaves like a standard LTISystem during algebraic operations. We illustrate its usage through a number of examples.","category":"page"},{"location":"lib/nonlinear/#Examples","page":"Nonlinear","title":"Examples","text":"","category":"section"},{"location":"lib/nonlinear/#Control-signal-saturation","page":"Nonlinear","title":"Control-signal saturation","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"To create a controller that saturates the output at pm 07, we call","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystems, Plots\nusing ControlSystemsBase: nonlinearity # This functionality is not exported due to the beta status\n\nC = pid(1, 0.1, form=:parallel) # A standard PI controller\nnl = nonlinearity(x->clamp(x, -0.7, 0.7)) # a saturating nonlinearity\nsatC = nl*C # Connect the saturation at the output of C","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"we may now use this controller like we would normally do in ControlSystems, e.g.,","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"P = tf(1, [1, 1]) # a plant\nG = feedback(P*C) # closed loop without nonlinearity\nGnl = feedback(P*satC) # closed loop with saturation\n\nGu = feedback(C, P) # closed loop from reference to control signal without nonlinearity\nGunl = feedback(satC, P) # closed loop from reference to control signal with saturation\n\nplot(step([G; Gu], 5), lab = [\"Linear y\" \"Linear u\"])\nplot!(step([Gnl; Gunl], 5), lab = [\"Nonlinear y\" \"Nonlinear u\"])","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystemsBase: saturation\nsaturation(0.7)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"See also ControlSystemsBase.ratelimit that saturates the derivative of a signal.","category":"page"},{"location":"lib/nonlinear/#Non-zero-operating-point","page":"Nonlinear","title":"Non-zero operating point","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"It's common to linearize nonlinear systems around some operating point. We may make use of the helper constructor ControlSystemsBase.offset to create affine functions at the inputs and outputs of the linearized system to, e.g.,","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Make sure that simulations result are given in the original coordinates rather than in the coordinates of the linearization point.\nAllow nonlinearities that are added back after the linearization (such as saturations) to operate with their original parameters.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We will demonstrate a composite usage of offset and saturation below. The system we'll consider is a linearized model of a quadruple-tank process;","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"The system is linearized around the operating point","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"xr = [10, 10, 4.9, 4.9] # reference state\nur = [0.263, 0.263] # control input at the operating point\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"and is given by","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using LinearAlgebra\nkc, k1, k2, g = 0.5, 1.6, 1.6, 9.81\nA1 = A3 = A2 = A4 = 4.9\na1, a3, a2, a4 = 0.03, 0.03, 0.03, 0.03\nh01, h02, h03, h04 = xr\nT1, T2 = (A1/a1)sqrt(2*h01/g), (A2/a2)sqrt(2*h02/g)\nT3, T4 = (A3/a3)sqrt(2*h03/g), (A4/a4)sqrt(2*h04/g)\nc1, c2 = (T1*k1*kc/A1), (T2*k2*kc/A2)\nγ1, γ2 = 0.3, 0.3\n\n# Define the process dynamics\nA = [-1/T1 0 A3/(A1*T3) 0\n 0 -1/T2 0 A4/(A2*T4)\n 0 0 -1/T3 0\n 0 0 0 -1/T4]\nB = [γ1*k1/A1 0\n 0 γ2*k2/A2\n 0 (1-γ2)k2/A3\n (1-γ1)k1/A4 0 ]\n\nC = kc*[I(2) 0*I(2)] # Measure the first two tank levels\nD = 0\nG = ss(A,B,C,D)\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"A PID controller with a filter is given by","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"F = tf(1, [0.63, 1.12, 1])\nCpid = pid(0.26, 0.001, 15.9, form=:parallel)*F |> ss\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"and to make the controller MIMO, we add a static pre-compensator that decouples the system at the the zero frequency.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"iG0 = dcgain(G)\niG0 ./= maximum(abs, iG0)\nC = (Cpid .* I(2)) * iG0 \nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"The pumps (there are two of them) that service the tanks can only add liquid to the tanks, not remove liquid. The pump is thus saturated from below at 0, and from above at the maximum pump capacity 0.4. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystemsBase: offset\numin = [0.0, 0.0]\numax = [0.4, 0.4]\n\nyr = G.C*xr # Reference output\nGop = offset(yr) * G * offset(-ur) # Make the plant operate in Δ-coordinates \nC_sat = saturation(umin, umax) * C # while the controller and the saturation operate in the original coordinates","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We now simulate the closed-loop system, the initial state of the plant is adjusted with the operating point x0-xr since the plant operates in Δ-coordinates","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"x0 = [2, 1, 8, 3] # Initial tank levels\nplot(\n plot(lsim(feedback(Gop*C_sat), yr, 0:1:3000, x0=[x0-xr; zeros(C.nx)]), layout=1, sp=1, title=\"Outputs\", ylabel=\"\"),\n plot(lsim(feedback(C_sat, Gop), yr, 0:1:3000, x0=[zeros(C.nx); x0-xr]), layout=1, sp=1, title=\"Control signals\", ylabel=\"\")\n)\nhline!([yr[1]], label=\"Reference\", l=:dash, sp=1, c=1)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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].","category":"page"},{"location":"lib/nonlinear/#Duffing-oscillator","page":"Nonlinear","title":"Duffing oscillator","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"In this example, we'll model and control the nonlinear system","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ddot x = -kx - k_3 x^3 - c dotx + 10u","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"To do this, we first draw the block diagram","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"10u ┌───┐\n──────►│+ │ ┌───┐ ┌───┐\n ┌────►│- │ ẍ │ 1 │ ẋ │ 1 │ x\n │ ┌──►│- ├──►│ - ├┬─►│ - ├─┬──►\n │ │ ┌►│- │ │ s ││ │ s │ │\n │ │ │ └───┘ └───┘│ └───┘ │\n │ │ │ │ │\n │ │ │ ┌───┐ │ │\n │ │ └───┤ c │◄─────┘ │\n │ │ └───┘ │\n │ │ │\n │ │ ┌───┐ │\n │ └─────┤ k │◄──────────────┤\n │ └───┘ │\n │ │\n │ ┌───┐ ┌───┐ │\n └───────┤ k³│◄──┤ x³│◄──────┘\n └───┘ └───┘","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We see that the input u passes through the inner velocity loop before reaching the output x, we can form this inner closed-loop transfer function using feedback(1/s, c), i.e., close the loop over an integrator by -c. This inner loop is then connected in series with another integrator an feedback loop is closed with k_3 x^3 + kx = pos_loop_feedback in the feedback path. Notice how we multiply the final system with 10 from the right to get the input gain correct, for nonlinear systems, 10*sys and sys*10 are not always equivalent!","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystems, Plots\nusing ControlSystemsBase: nonlinearity\nk = 10\nk3 = 2\nc = 1\n\ns = tf(\"s\")\n\ncube = nonlinearity(x->x^3)\nvel_loop = feedback(1/s, c)\npos_loop_feedback = (k3*cube + k)\nduffing = feedback(vel_loop/s, pos_loop_feedback)*10\n\nplot(step(duffing, 20), title=\"Duffing oscillator open-loop step response\")","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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:","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"function circle_criterion(L::ControlSystemsBase.HammersteinWienerSystem, domain::Tuple; N=10000)\n fun = x->L.f[](x)/x\n x = range(domain[1], stop=domain[2], length=N)\n 0 ∈ x && (x = filter(!=(0), x)) # We cannot divide by zero\n k1, k2 = extrema(fun, x)\n\n f1 = plot(L.f[], domain[1], domain[2], title=\"Nonlinearity\", lab=\"f(x)\", xlab=\"x\")\n plot!(x, [k1.*x k2.*x], lab=[\"k1 = $(round(k1, sigdigits=2))\" \"k2 = $(round(k2, sigdigits=2))\"], l=(:dash), legend=:bottomright)\n\n p1 = -1/k2 # Close to origin\n p2 = -1/k1 # Far from origin\n\n c = (p1 + p2)/2\n r = (p2 - p1)/2\n\n Lnominal = sminreal(ss(L.A, L.B1, L.C1, L.D11, L.P.timeevol))\n f2 = nyquistplot(Lnominal)\n if p2 < -1000 # Due to bug in plots\n vspan!([-1000, p1], fillalpha=0.7, c=:red, primary=false)\n else\n th = 0:0.01:2pi\n Cs,Ss = cos.(th), sin.(th)\n plot!(r.*Cs .+ c, r.*Ss, fill=true, fillalpha=0.7, c=:red, primary=false)\n end\n\n plot(f1,f2)\nend\n\n\nC = pid(2, 0, 1, form=:parallel)*tf(1, [0.01,1])\nf1 = circle_criterion(duffing*C, (-1, 1))\nplot!(sp=2, ylims=(-10, 3), xlims=(-5, 11))\nf2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title=\"Controlled oscillator disturbance step response\", layout=4)\nplot(f1,f2, size=(1300,800))","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Since we evaluated the nonlinearity over a small domain, we should convince ourselves that we indeed never risk leaving this domain. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"In the example above, the circle turns into a half plane since the lower sector bound is 0. The example below chooses another nonlinearity","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"f(x) = x + sin(x)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"to get an actual circle in the Nyquist plane.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"wiggly = nonlinearity(x->x+sin(x)) # This function is a bit wiggly\nvel_loop = feedback(1/s, c)\npos_loop_feedback = (k3*wiggly + k)\nduffing = feedback(vel_loop/s, pos_loop_feedback)*10\n\nC = pid(2, 5, 1, form=:parallel)*tf(1,[0.1, 1]) \nf1 = circle_criterion(duffing*C, (-2pi, 2pi))\nplot!(sp=2, ylims=(-5, 2), xlims=(-2.1, 0.1))\nf2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title=\"Controlled wiggly oscillator disturbance step response\", layout=5)\nplot(f1,f2, size=(1300,800))","category":"page"},{"location":"lib/nonlinear/#Limitations","page":"Nonlinear","title":"Limitations","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Remember, this functionality is experimental and subject to breakage.\nCurrently only Continuous systems supported.\nNo 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. \nA lot of functions that expect linear systems will not work for nonlinear systems (naturally).","category":"page"},{"location":"lib/nonlinear/#Possible-future-work","page":"Nonlinear","title":"Possible future work","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Discrete-time support.\nBasic 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.\nAdditional nonlinear components, such as \nIntegrator anti-windup\nFriction models","category":"page"},{"location":"lib/nonlinear/#See-also","page":"Nonlinear","title":"See also","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"More advanced nonlinear modeling is facilitated by ModelingToolkit.jl (MTK) and ModelingToolkitStandardLibrary.jl. The tutorials ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Modeling for control using ModelingToolkit\nDisturbance modeling in ModelingToolkit\nModal analysis of a series of masses and springs using MTK","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"show how to use these packages to model and simulate control systems.","category":"page"},{"location":"lib/nonlinear/#Docstrings","page":"Nonlinear","title":"Docstrings","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ControlSystemsBase.nonlinearity\nControlSystemsBase.offset\nControlSystemsBase.saturation\nControlSystemsBase.ratelimit\nControlSystemsBase.deadzone\nControlSystemsBase.linearize","category":"page"},{"location":"lib/nonlinear/#ControlSystemsBase.nonlinearity","page":"Nonlinear","title":"ControlSystemsBase.nonlinearity","text":"nonlinearity(f)\nnonlinearity(T, f)\n\nCreate a pure nonlinearity. f is assumed to be a static (no memory) nonlinear function from f R - R.\n\nThe type T defaults to Float64.\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nExample:\n\nCreate a LTI system with a static input nonlinearity that saturates the input to [-1,1].\n\ntf(1, [1, 1])*nonlinearity(x->clamp(x, -1, 1))\n\nSee also predefined nonlinearities saturation, offset.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.offset","page":"Nonlinear","title":"ControlSystemsBase.offset","text":"offset(val)\n\nCreate a constant-offset nonlinearity x -> x + val.\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nExample:\n\nTo create a linear system that operates around operating point y₀, u₀, use\n\noffset_sys = offset(y₀) * sys * offset(-u₀)\n\nnote 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\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.saturation","page":"Nonlinear","title":"ControlSystemsBase.saturation","text":"saturation(val)\nsaturation(lower, upper)\n\nCreate a saturating nonlinearity. Connect it to the output of a controller C using\n\nCsat = saturation(val) * C\n\n y▲ ────── upper\n │ /\n │ /\n │/\n ──────────┼────────► u\n /│ \n / │\n / │\nlower──── \n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.ratelimit","page":"Nonlinear","title":"ControlSystemsBase.ratelimit","text":"ratelimit(val; Tf)\nratelimit(lower, upper; Tf)\n\nCreate a nonlinearity that limits the rate of change of a signal, roughly equivalent to 1s 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.deadzone","page":"Nonlinear","title":"ControlSystemsBase.deadzone","text":"deadzone(val)\ndeadzone(lower, upper)\n\nCreate a dead-zone nonlinearity.\n\n y▲\n │ /\n │ /\n lower │ /\n─────|──┼──|───────► u\n / │ upper\n / │\n / │\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.linearize","page":"Nonlinear","title":"ControlSystemsBase.linearize","text":"linearize(sys::HammersteinWienerSystem, Δy)\n\nLinearize the nonlinear system sys around the operating point implied by the specified Δy\n\n ┌─────────┐\n y◄───┤ │◄────u\n │ P │\nΔy┌───┤ │◄───┐Δu\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n │ │ │ │\n └─────►│ f ├───────┘\n │ │\n └───┘\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\n\n\n\n\nA, B = linearize(f, x, u, args...)\n\nLinearize dynamics x = f(x u args) around operating point (xuargs) 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...).\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"Pages = [\"plotting.md\"]","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"note: Using Plots\nAll plotting requires the user to manually load the Plots.jl library, e.g., by calling using Plots.","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"note: Time-domain responses\nThere 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.","category":"page"},{"location":"lib/plotting/#Plotting-functions","page":"Plotting","title":"Plotting functions","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [libpath*\"/plotting.jl\"]\nOrder = [:function]\nPrivate = false","category":"page"},{"location":"lib/plotting/#ControlSystemsBase.bodeplot","page":"Plotting","title":"ControlSystemsBase.bodeplot","text":"fig = bodeplot(sys, args...)\nbodeplot(LTISystem[sys1, sys2...], args...; plotphase=true, balance = true, kwargs...)\n\nCreate 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).\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.gangoffourplot-Tuple{Union{LTISystem, Vector}, Vector, Vararg{Any}}","page":"Plotting","title":"ControlSystemsBase.gangoffourplot","text":"fig = gangoffourplot(P::LTISystem, C::LTISystem; minimal=true, plotphase=false, Ms_lines = [1.0, 1.25, 1.5], Mt_lines = [], sigma = true, kwargs...)\n\nGang-of-Four plot.\n\nsigma determines whether a sigmaplot is used instead of a bodeplot for MIMO S and T. kwargs are sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"method"},{"location":"lib/plotting/#ControlSystemsBase.marginplot","page":"Plotting","title":"ControlSystemsBase.marginplot","text":"fig = marginplot(sys::LTISystem [,w::AbstractVector]; balance=true, kwargs...)\nmarginplot(sys::Vector{LTISystem}, w::AbstractVector; balance=true, kwargs...)\n\nPlot all the amplitude and phase margins of the system(s) sys.\n\nA frequency vector w can be optionally provided.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.nicholsplot","page":"Plotting","title":"ControlSystemsBase.nicholsplot","text":"fig = nicholsplot{T<:LTISystem}(systems::Vector{T}, w::AbstractVector; kwargs...)\n\nCreate a Nichols plot of the LTISystem(s). A frequency vector w can be optionally provided.\n\nKeyword arguments:\n\ntext = true\nGains = [12, 6, 3, 1, 0.5, -0.5, -1, -3, -6, -10, -20, -40, -60]\npInc = 30\nsat = 0.4\nval = 0.85\nfontsize = 10\n\npInc determines the increment in degrees between phase lines.\n\nsat ∈ [0,1] determines the saturation of the gain lines\n\nval ∈ [0,1] determines the brightness of the gain lines\n\nAdditional 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\n\nThis function is based on code subject to the two-clause BSD licence Copyright 2011 Will Robertson Copyright 2011 Philipp Allgeuer\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.nyquistplot","page":"Plotting","title":"ControlSystemsBase.nyquistplot","text":"fig = nyquistplot(sys; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)\nnyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)\n\nCreate a Nyquist plot of the LTISystem(s). A frequency vector w can be optionally provided.\n\nunit_circle: if the unit circle should be displayed. The Nyquist curve crosses the unit circle at the gain crossover frequency.\nMs_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).\nMt_circles: draw circles corresponding to given levels of complementary sensitivity. Mt_circles can be supplied as a number or a vector of numbers.\ncritical_point: point on real axis to mark as critical for encirclements\nIf hz=true, the hover information will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.pzmap","page":"Plotting","title":"ControlSystemsBase.pzmap","text":"fig = pzmap(fig, system, args...; hz = false, kwargs...)\n\nCreate a pole-zero map of the LTISystem(s) in figure fig, args and kwargs will be sent to the scatter plot command.\n\nTo customize the unit-circle drawn for discrete systems, modify the line attributes, e.g., linecolor=:red.\n\nIf hz is true, all poles and zeros are scaled by 1/2π.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.rgaplot","page":"Plotting","title":"ControlSystemsBase.rgaplot","text":"rgaplot(sys, args...; hz=false)\nrgaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true)\n\nPlot the relative-gain array entries of the LTISystem(s). A frequency vector w can be optionally provided.\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to Plots.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.setPlotScale-Tuple{AbstractString}","page":"Plotting","title":"ControlSystemsBase.setPlotScale","text":"setPlotScale(str)\n\nSet 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/plotting/#ControlSystemsBase.sigmaplot","page":"Plotting","title":"ControlSystemsBase.sigmaplot","text":"sigmaplot(sys, args...; hz=false balance=true, extrema)\nsigmaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true, extrema)\n\nPlot the singular values of the frequency response of the LTISystem(s). A frequency vector w can be optionally provided.\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\nextrema: Only plot the largest and smallest singular values.\n\nkwargs is sent as argument to Plots.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#Examples","page":"Plotting","title":"Examples","text":"","category":"section"},{"location":"lib/plotting/#Bode-plot","page":"Plotting","title":"Bode plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: bode)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nws = exp10.(range(-2,stop=2,length=200))\nbodeplot(sys, ws)","category":"page"},{"location":"lib/plotting/#Sigma-plot","page":"Plotting","title":"Sigma plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: sigma)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nsigmaplot(sys)","category":"page"},{"location":"lib/plotting/#Margin","page":"Plotting","title":"Margin","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: margin)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nws = exp10.(range(-2,stop=2,length=200))\nmarginplot([tf1, tf2], ws)","category":"page"},{"location":"lib/plotting/#Gangoffour-plot","page":"Plotting","title":"Gangoffour plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: gangoffour)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1.0],[1,1])\ngangoffourplot(tf1, [tf(1), tf(5)])","category":"page"},{"location":"lib/plotting/#Nyquist-plot","page":"Plotting","title":"Nyquist plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: nyquist)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nws = exp10.(range(-2,stop=2,length=200))\nnyquistplot(sys, ws, Ms_circles=1.2, Mt_circles=1.2)","category":"page"},{"location":"lib/plotting/#Nichols-plot","page":"Plotting","title":"Nichols plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: nichols)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\nws = exp10.(range(-2,stop=2,length=200))\nnicholsplot(tf1,ws)","category":"page"},{"location":"lib/plotting/#Pole-zero-plot","page":"Plotting","title":"Pole-zero plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: pzmap)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf2 = tf([1/5,2],[1,1,1])\npzmap(c2d(tf2, 0.1))","category":"page"},{"location":"lib/plotting/#Rlocus-plot","page":"Plotting","title":"Rlocus plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: rlocus)","category":"page"},{"location":"lib/plotting/#Lsim-response-plot","page":"Plotting","title":"Lsim response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: lsim)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nsysd = c2d(sys, 0.01)\nL = lqr(sysd, [1 0; 0 1], [1 0; 0 1])\nts = 0:0.01:5\nplot(lsim(sysd, (x,i)->-L*x, ts; x0=[1;2]), plotu=true)","category":"page"},{"location":"lib/plotting/#Impulse-response-plot","page":"Plotting","title":"Impulse response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: impulse)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nsysd = c2d(ss(sys), 0.01)\nplot(impulse(sysd, 5), l=:blue)","category":"page"},{"location":"lib/plotting/#Step-response-plot","page":"Plotting","title":"Step response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: step)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nsysd = c2d(ss(sys), 0.01)\nres = step(sysd, 5)\nplot(res, l=(:dash, 4))\n# plot!(stepinfo(step(sysd[1,1], 5))) # adds extra info to the plot","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Pages = [\"analysis.md\"]","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"For robust analysis, see RobustAndOptimalControl.jl.","category":"page"},{"location":"lib/analysis/#Analysis","page":"Analysis","title":"Analysis","text":"","category":"section"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [\n libpath*\"/analysis.jl\", \n libpath*\"/matrix_comps.jl\", \n libpath*\"/types/conversion.jl\"\n ]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/analysis/#ControlSystemsBase.damp-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.damp","text":"Wn, zeta, ps = damp(sys)\n\nCompute the natural frequencies, Wn, and damping ratios, zeta, of the poles, ps, of sys\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.dampreport-Tuple{IO, LTISystem}","page":"Analysis","title":"ControlSystemsBase.dampreport","text":"dampreport(sys)\n\nDisplay a report of the poles, damping ratio, natural frequency, and time constant of the system sys\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.dcgain","page":"Analysis","title":"ControlSystemsBase.dcgain","text":"dcgain(sys, ϵ=0)\n\nCompute the dcgain of system sys.\n\nequal to G(0) for continuous-time systems and G(1) for discrete-time systems.\n\nϵ can be provided to evaluate the dcgain with a small perturbation into the stability region of the complex plane.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.delaymargin-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.delaymargin","text":"dₘ = delaymargin(G::LTISystem)\n\nReturn 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gangoffour-Tuple{LTISystem, LTISystem}","page":"Analysis","title":"ControlSystemsBase.gangoffour","text":"S, PS, CS, T = gangoffour(P, C; minimal=true)\ngangoffour(P::AbstractVector, C::AbstractVector; minimal=true)\n\nGiven 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.\n\nS = 1/(1+PC) Sensitivity function\nPS = (1+PC)\\P Load disturbance to measurement signal\nCS = (1+PC)\\C Measurement noise to control signal\nT = PC/(1+PC) Complementary sensitivity function\n\nIf minimal=true, minreal will be applied to all transfer functions.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gangofseven-Tuple{LTISystem, LTISystem, LTISystem}","page":"Analysis","title":"ControlSystemsBase.gangofseven","text":"S, PS, CS, T, RY, RU, RE = gangofseven(P,C,F)\n\nGiven 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.\n\nS = 1/(1+PC) Sensitivity function\nPS = P/(1+PC)\nCS = C/(1+PC)\nT = PC/(1+PC) Complementary sensitivity function\nRY = PCF/(1+PC)\nRU = CF/(1+P*C)\nRE = F/(1+P*C)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.margin-Tuple{LTISystem, AbstractVector{<:Real}}","page":"Analysis","title":"ControlSystemsBase.margin","text":"wgm, gm, wpm, pm = margin(sys::LTISystem, w::Vector; full=false, allMargins=false)\n\nreturns frequencies for gain margins, gain margins, frequencies for phase margins, phase margins\n\nIf !allMargins, return only the smallest margin\n\nIf full return also fullPhase See also delaymargin and RobustAndOptimalControl.diskmargin\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.markovparam-Tuple{AbstractStateSpace{<:Discrete}, Integer}","page":"Analysis","title":"ControlSystemsBase.markovparam","text":"markovparam(sys, n)\n\nCompute the nth markov parameter of discrete-time state-space system sys. This is defined as the following:\n\nh(0) = D\n\nh(n) = C*A^(n-1)*B\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.poles-Tuple{AbstractStateSpace}","page":"Analysis","title":"ControlSystemsBase.poles","text":"poles(sys)\n\nCompute the poles of system sys.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.reduce_sys-Tuple{AbstractMatrix, AbstractMatrix, AbstractMatrix, AbstractMatrix, AbstractFloat}","page":"Analysis","title":"ControlSystemsBase.reduce_sys","text":"reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)\n\nImplements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed A, B, C, D matrices. These are empty if there are no zeros.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.relative_gain_array-Tuple{AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.relative_gain_array","text":"relative_gain_array(A::AbstractMatrix; tol = 1.0e-15)\n\nReference: \"On the Relative Gain Array (RGA) with Singular and Rectangular Matrices\" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.relative_gain_array-Tuple{Any, AbstractVector}","page":"Analysis","title":"ControlSystemsBase.relative_gain_array","text":"relative_gain_array(G, w::AbstractVector)\nrelative_gain_array(G, w::Number)\n\nCalculate the relative gain array of G at frequencies w. G(iω) .* pinv(tranpose(G(iω)))\n\nThe 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. \n\nThe 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.\nThe RGA is invariant to input/output scaling of G.\nIf the RGA contains large entries, the system may be sensitive to model errors, -Skogestad, \"Multivariable Feedback Control: Analysis and Design\":\nUncertainty in the input channels (diagonal input uncertainty). Plants with\nlarge 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\nElement uncertainty. Large RGA-elements imply sensitivity to element-by-element uncertainty.\nHowever, 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.\n\nThe 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\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.tzeros-Tuple{TransferFunction}","page":"Analysis","title":"ControlSystemsBase.tzeros","text":"tzeros(sys)\n\nCompute the invariant zeros of the system sys. If sys is a minimal realization, these are also the transmission zeros.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.zpkdata-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.zpkdata","text":"z, p, k = zpkdata(sys)\n\nCompute the zeros, poles, and gains of system sys.\n\nReturns\n\nz : Matrix{Vector{ComplexF64}}, (ny × nu)\np : Matrix{Vector{ComplexF64}}, (ny × nu)\nk : Matrix{Float64}, (ny × nu)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.are-Tuple{Union{Continuous, Type{Continuous}}, AbstractMatrix, Any, Any, Any}","page":"Analysis","title":"ControlSystemsBase.are","text":"are(::Continuous, A, B, Q, R)\n\nCompute '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.\n\nIn an LQR problem, Q is associated with the state penalty xQx while R is associated with the control penalty uRu. See lqr for more details.\n\nUses MatrixEquations.arec. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.arec, note that they define the input arguments in a different order.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.are-Tuple{Union{Type{Discrete}, Discrete}, AbstractMatrix, Any, Any, Any}","page":"Analysis","title":"ControlSystemsBase.are","text":"are(::Discrete, A, B, Q, R; kwargs...)\n\nCompute 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\n\nIn an LQR problem, Q is associated with the state penalty xQx while R is associated with the control penalty uRu. See lqr for more details.\n\nUses MatrixEquations.ared. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.ared, note that they define the input arguments in a different order.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.balance","page":"Analysis","title":"ControlSystemsBase.balance","text":"S, P, B = balance(A[, perm=true])\n\nCompute 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).\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.balreal-Tuple{ST} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.balreal","text":"sysr, G, T = balreal(sys::StateSpace)\n\nCalculates 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.\n\nSee also gram, baltrunc.\n\nReference: Varga A., Balancing-free square-root algorithm for computing singular perturbation approximations.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.baltrunc-Tuple{ST} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.baltrunc","text":"sysr, G, T = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, n = nothing, residual = false)\n\nReduces 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.\n\nT 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.\n\nIf residual = true, matched static gain is achieved through \"residualization\", i.e., setting\n\n0 = A_21x_1 + A_22x_2 + B_2u\n\nwhere indices 1/2 correspond to the remaining/truncated states respectively.\n\nSee also gram, balreal\n\nGlad, Ljung, Reglerteori: Flervariabla och Olinjära metoder.\n\nFor more advanced model reduction, see RobustAndOptimalControl.jl - Model Reduction.\n\nExtended help\n\nNote: 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:\n\nThe outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.\nThe 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\n\nWithout 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.controllability-Union{Tuple{T}, Tuple{AbstractMatrix{T}, Any}} where T","page":"Analysis","title":"ControlSystemsBase.controllability","text":"controllability(A, B; atol, rtol)\ncontrollability(sys; atol, rtol)\n\nCheck for controllability of the pair (A, B) or sys using the PHB test.\n\nThe 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.\n\nTechnically, this function checks for controllability from the origin, also called reachability.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.covar-Tuple{AbstractStateSpace, Any}","page":"Analysis","title":"ControlSystemsBase.covar","text":"P = covar(sys, W)\n\nCalculate 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).\n\nRemark: 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.ctrb-Tuple{AbstractMatrix, AbstractVecOrMat}","page":"Analysis","title":"ControlSystemsBase.ctrb","text":"ctrb(A, B)\nctrb(sys)\n\nCompute the controllability matrix for the system described by (A, B) or sys.\n\nNote 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gram-Tuple{AbstractStateSpace, Symbol}","page":"Analysis","title":"ControlSystemsBase.gram","text":"gram(sys, opt; kwargs...)\n\nCompute the grammian of system sys. If opt is :c, computes the controllability grammian. If opt is :o, computes the observability grammian.\n\nSee also grampd For keyword arguments, see grampd.\n\nExtended help\n\nNote: 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:\n\nThe outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.\nThe 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\n\nWithout 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.grampd-Tuple{AbstractStateSpace, Symbol}","page":"Analysis","title":"ControlSystemsBase.grampd","text":"U = grampd(sys, opt; kwargs...)\n\nReturn 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.\n\nObtain a Cholesky object by Cholesky(U) for observability grammian\n\nUses MatrixEquations.plyapc/plyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.plyapc/plyapd\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.hinfnorm-Tuple{AbstractStateSpace{<:Continuous}}","page":"Analysis","title":"ControlSystemsBase.hinfnorm","text":"Ninf, ω_peak = hinfnorm(sys; tol=1e-6)\n\nCompute the H∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.\n\nNinf := sup_ω σ_max[sys(iω)] if G is stable (σ_max = largest singular value) := Inf' ifG` is unstable\n\ntol is an optional keyword argument for the desired relative accuracy for the computed H∞ norm (not an absolute certificate).\n\nsys is first converted to a state space model if needed.\n\nThe continuous-time L∞ norm computation implements the 'two-step algorithm' in:\nN.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.\n\nFor the discrete-time version, see:\nP. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.\n\nSee also linfnorm.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.innovation_form-Union{Tuple{ST}, Tuple{ST, Any, Any, Vararg{Any}}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.innovation_form","text":"sysi = innovation_form(sys, R1, R2[, R12])\nsysi = innovation_form(sys; sysw=I, syse=I, R1=I, R2=I)\n\nTakes a system\n\nx' = Ax + Bu + w ~ R1\ny = Cx + Du + e ~ R2\n\nand returns the system\n\nx' = Ax + Kv\ny = Cx + v\n\nwhere v is the innovation sequence.\n\nIf sysw (syse) is given, the covariance resulting in filtering noise with R1 (R2) through sysw (syse) is used as covariance.\n\nSee Stochastic Control, Chapter 4, Åström\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.innovation_form-Union{Tuple{ST}, Tuple{ST, Any}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.innovation_form","text":"sysi = innovation_form(sys, K)\n\nTakes a system\n\nx' = Ax + Bu + Kv\ny = Cx + Du + v\n\nand returns the system\n\nx' = Ax + Kv\ny = Cx + v\n\nwhere v is the innovation sequence.\n\nSee Stochastic Control, Chapter 4, Åström\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.linfnorm-Tuple{AbstractStateSpace}","page":"Analysis","title":"ControlSystemsBase.linfnorm","text":"Ninf, ω_peak = linfnorm(sys; tol=1e-6)\n\nCompute the L∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.\n\nNinf := sup_ω σ_max[sys(iω)] (σ_max denotes the largest singular value)\n\ntol is an optional keyword argument representing the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).\n\nsys is first converted to a state space model if needed.\n\nThe continuous-time L∞ norm computation implements the 'two-step algorithm' in:\nN.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.\n\nFor the discrete-time version, see:\nP. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.\n\nSee also hinfnorm.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observability-Union{Tuple{T}, Tuple{AbstractMatrix{T}, Any}} where T","page":"Analysis","title":"ControlSystemsBase.observability","text":"observability(A, C; atol, rtol)\n\nCheck for observability of the pair (A, C) or sys using the PHB test.\n\nThe 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_controller-Tuple{Any, AbstractMatrix, AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.observer_controller","text":"cont = observer_controller(sys, L::AbstractMatrix, K::AbstractMatrix; direct=false)\n\nIf direct = false\n\nReturn 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.\n\nThis 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.\n\nRef: \"Computer-Controlled Systems\" Eq 4.37\n\nIf direct = true\n\nReturn 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.\n\nnote: Note\nTo 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.\n\nRef: Ref: \"Computer-Controlled Systems\" pp 140 and \"Computer-Controlled Systems\" pp 162 prob 4.7\n\nArguments:\n\nsys: Model of system\nL: State-feedback gain u = -Lx\nK: Observer gain\n\nSee also observer_predictor and innovation_form.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_filter-Tuple{AbstractStateSpace{<:Discrete}, AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.observer_filter","text":"observer_filter(sys, K; output_state = false)\n\nReturn the observer filter \n\nbeginaligned\nx(kk) = (I - KC)Ax(k-1k-1) + (I - KC)Bu(k-1) + Ky(k) \nendaligned\n\nwith the input equation [(I - KC)B K] * [u(k-1); y(k)].\n\nNote 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.\n\nThis 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.\n\nThe observer filter is equivalent to the observer_predictor for continuous-time systems.\n\nnote: Note\nTo 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.\n\nRef: \"Computer-Controlled Systems\" Eq 4.32\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_predictor-Tuple{AbstractStateSpace, Any, Union{AbstractArray, UniformScaling}, Vararg{Any}}","page":"Analysis","title":"ControlSystemsBase.observer_predictor","text":"observer_predictor(sys::AbstractStateSpace, K; h::Int = 1, output_state = false)\nobserver_predictor(sys::AbstractStateSpace, R1, R2[, R12]; output_state = false)\n\nIf sys is continuous, return the observer predictor system\n\nbeginaligned\nx = (A - KC)x + (B-KD)u + Ky \ny = Cx + Du\nendaligned\n\nwith the input equation [B-KD K] * [u; y]\n\nIf 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).\n\nIf covariance matrices R1, R2 are given, the kalman gain K is calculated using kalman.\n\nIf output_state is true, the output is the state estimate x̂ instead of the output estimate ŷ.\n\nSee also innovation_form, observer_controller and observer_filter.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.obsv","page":"Analysis","title":"ControlSystemsBase.obsv","text":"obsv(A, C, n=size(A,1))\nobsv(sys, n=sys.nx)\n\nCompute 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.\n\nNote 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.plyap-Tuple{AbstractStateSpace, Vararg{Any}}","page":"Analysis","title":"ControlSystemsBase.plyap","text":"Xc = plyap(sys::AbstractStateSpace, Ql; kwargs...)\n\nLyapunov solver that takes the L Cholesky factor of Q and returns a triangular matrix Xc such that Xc*Xc' = X.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.similarity_transform-Union{Tuple{ST}, Tuple{ST, Any}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.similarity_transform","text":"syst = similarity_transform(sys, T; unitary=false)\n\nPerform a similarity transform T : Tx̃ = x on sys such that\n\nà = T⁻¹AT\nB̃ = T⁻¹ B\nC̃ = CT\nD̃ = D\n\nIf unitary=true, T is assumed unitary and the matrix adjoint is used instead of the inverse. See also balance_statespace.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.time_scale-Tuple{AbstractStateSpace{Continuous}, Any}","page":"Analysis","title":"ControlSystemsBase.time_scale","text":"time_scale(sys::AbstractStateSpace{Continuous}, a; balanced = false)\ntime_scale(G::TransferFunction{Continuous}, a; balanced = true)\n\nRescale the time axis (change time unit) of sys.\n\nFor 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.\n\nScaling of time for a function f(t) with Laplace transform F(s) can be stated as\n\nf(at) leftrightarrow dfrac1a Fbig(dfracsabig)\n\nThe 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.\n\nExample:\n\nThe 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. \n\nGs = tf(1, [1e-6, 1]) # micro-second time scale modeled in seconds\nGms = time_scale(Gs, 1e-6) # Change to micro-second time scale\nGms == tf(1, [1, 1]) # Gms now has micro-seconds as time unit\n\nThe next example illustrates how the time axis of a time-domain simulation changes by time scaling \n\nt = 0:0.1:50 # original time axis\na = 10 # Scaling factor\nsys1 = ssrand(1,1,5)\nres1 = step(sys1, t) # Perform original simulation\nsys2 = time_scale(sys, a) # Scale time\nres2 = step(sys2, t ./ a) # Simulate on scaled time axis, note the `1/a`\nisapprox(res1.y, res2.y, rtol=1e-3, atol=1e-3)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#LinearAlgebra.lyap-Tuple{Union{Type{Discrete}, Discrete}, AbstractMatrix, Any}","page":"Analysis","title":"LinearAlgebra.lyap","text":"lyap(A, Q; kwargs...)\n\nCompute the solution X to the discrete Lyapunov equation AXA' - X + Q = 0.\n\nUses MatrixEquations.lyapc / MatrixEquations.lyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.lyapc / ControlSystemsBase.MatrixEquations.lyapd\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#LinearAlgebra.norm","page":"Analysis","title":"LinearAlgebra.norm","text":"norm(sys, p=2; tol=1e-6)\n\nnorm(sys) or norm(sys,2) computes the H2 norm of the LTI system sys.\n\nnorm(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.\n\ntol 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).\n\nsys is first converted to a StateSpace model if needed.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.balance_statespace","page":"Analysis","title":"ControlSystemsBase.balance_statespace","text":"A, B, C, T = balance_statespace{S}(A::Matrix{S}, B::Matrix{S}, C::Matrix{S}, perm::Bool=false)\nsys, T = balance_statespace(sys::StateSpace, perm::Bool=false)\n\nComputes 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.\n\nThe inverse of sysb, T = balance_statespace(sys) is given by similarity_transform(sysb, T)\n\nThis is not the same as finding a balanced realization with equal and diagonal observability and reachability gramians, see balreal\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#Videos","page":"Analysis","title":"Videos","text":"","category":"section"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Basic usage of robustness analysis with JuliaControl","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"api/#Index","page":"API","title":"Index","text":"","category":"section"},{"location":"api/","page":"API","title":"API","text":"","category":"page"},{"location":"api/","page":"API","title":"API","text":"See additional API in RobustAndOptimalControl.jl: API","category":"page"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"Pages = [\"constructors.md\"]","category":"page"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"See also Connecting named systems together.","category":"page"},{"location":"lib/constructors/#Constructing-systems","page":"Constructors","title":"Constructing systems","text":"","category":"section"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"append\nc2d\nfeedback\nfeedback2dof\nminreal\nparallel\nseries\nsminreal\nss\ntf\nzpk\ndelay\npade\nthiran\nssdata\nControlSystemsBase.seriesform","category":"page"},{"location":"lib/constructors/#ControlSystemsBase.append","page":"Constructors","title":"ControlSystemsBase.append","text":"append(systems::StateSpace...), append(systems::TransferFunction...)\n\nAppend systems in block diagonal form\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.c2d","page":"Constructors","title":"ControlSystemsBase.c2d","text":"sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\nGd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)\n\nConvert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).\n\nmethod = :tustin performs a bilinear transform with prewarp frequency w_prewarp.\n\nw_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also c2d_x0map\n\nExtended help\n\nZoH 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.\n\nFoH 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.\n\nTo 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.\n\nThe 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.\n\nClassical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as 02 ωgcTs 06 where ωgc is the gain-crossover frequency (rad/s).\n\n\n\n\n\nQd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)\nQd = c2d(sys::StateSpace{Discrete}, Qc::Matrix; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Discrete}, Qc::Matrix, Rc::Matrix; opt=:o)\n\nSample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.\n\nIf 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.\n\nnote: Note\nMeasurement 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.\n\nThe method used comes from theorem 5 in the reference below.\n\nRef: \"Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering\", Patrik Axelsson and Fredrik Gustafsson\n\nOn 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'. \n\nExample:\n\nThe 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.\n\nusing ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test\nsysc = DemoSystems.resonant()\nx0 = ones(sysc.nx)\nQc = [1 0.01; 0.01 2] # Continuous-time cost matrix for the state\nRc = I(1) # Continuous-time cost matrix for the input\n\nL = lqr(sysc, Qc, Rc)\ndynamics = function (xc, p, t)\n x = xc[1:sysc.nx]\n u = -L*x\n dx = sysc.A*x + sysc.B*u\n dc = dot(x, Qc, x) + dot(u, Rc, u)\n return [dx; dc]\nend\nprob = ODEProblem(dynamics, [x0; 0], (0.0, 10.0))\nsol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)\ncc = sol.u[end][end] # Continuous-time cost\n\n# Discrete-time version\nTs = 0.01 \nsysd = c2d(sysc, Ts)\nQd, Rd = c2d(sysd, Qc, Rc, opt=:c)\nLd = lqr(sysd, Qd, Rd)\nsold = lsim(sysd, (x, t) -> -Ld*x, 0:Ts:10, x0 = x0)\nfunction cost(x, u, Q, R)\n dot(x, Q, x) + dot(u, R, u)\nend\ncd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost\n@test cc ≈ cd rtol=0.01 # These should be similar\n\n\n\n\n\nc2d(G::DelayLtiSystem, Ts, method=:zoh)\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.feedback","page":"Constructors","title":"ControlSystemsBase.feedback","text":"feedback(sys)\nfeedback(sys1, sys2)\n\nFor a general LTI-system, feedback forms the negative feedback interconnection\n\n>-+ sys1 +-->\n | |\n (-)sys2 +\n\nIf no second system is given, negative identity feedback is assumed\n\n\n\n\n\nfeedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;\n U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],\n Wperm=:, Zperm=:, pos_feedback::Bool=false)\n\nBasic use feedback(sys1, sys2) forms the (negative) feedback interconnection\n\n ┌──────────────┐\n◄──────────┤ sys1 │◄──── Σ ◄──────\n │ │ │ │\n │ └──────────────┘ -1\n │ |\n │ ┌──────────────┐ │\n └─────►│ sys2 ├──────┘\n │ │\n └──────────────┘\n\nIf no second system sys2 is given, negative identity feedback (sys2 = 1) is assumed. The returned closed-loop system will have a state vector comprised of the state of sys1 followed by the state of sys2.\n\nAdvanced use feedback also supports more flexible use according to the figure below\n\n ┌──────────────┐\n z1◄─────┤ sys1 │◄──────w1\n ┌─── y1◄─────┤ │◄──────u1 ◄─┐\n │ └──────────────┘ │\n │ α\n │ ┌──────────────┐ │\n └──► u2─────►│ sys2 ├───────►y2──┘\n w2─────►│ ├───────►z2\n └──────────────┘\n\nU1, 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.\nY1, 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.\nU2, 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.\n\nSpecify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.\n\nNegative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).\n\nSee also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.\n\nThe 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.\n\nSee Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.feedback2dof","page":"Constructors","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P,R,S,T)\nfeedback2dof(B,A,R,S,T)\n\nReturn BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively\nReturn BT/(AR+ST)\n\n\n\n\n\nfeedback2dof(P::TransferFunction, C::TransferFunction, F::TransferFunction)\n\nReturn 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).\n\n +-------+\n | |\n +-----> F +----+\n | | | |\n | +-------+ |\n | +-------+ | +-------+\nr | - | | | | | y\n+--+-----> C +----+----> P +---+-->\n | | | | | |\n | +-------+ +-------+ |\n | |\n +--------------------------------+\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.minreal","page":"Constructors","title":"ControlSystemsBase.minreal","text":"minreal(tf::TransferFunction, eps=sqrt(eps()))\n\nCreate a minimal representation of each transfer function in tf by cancelling poles and zeros will promote system to an appropriate numeric type\n\n\n\n\n\nminreal(sys::StateSpace; fast=false, kwargs...)\n\nMinimal realisation algorithm from P. Van Dooreen, The generalized eigenstructure problem in linear system theory, IEEE Transactions on Automatic Control\n\nFor information about the options, see ?ControlSystemsBase.MatrixPencils.lsminreal\n\nSee also sminreal, which is both numerically exact and substantially faster than minreal, but with a much more limited potential in removing non-minimal dynamics.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.parallel","page":"Constructors","title":"ControlSystemsBase.parallel","text":"parallel(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in parallel, equivalent to sys2+sys1\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.series","page":"Constructors","title":"ControlSystemsBase.series","text":"series(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in series, equivalent to sys2*sys1\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.sminreal","page":"Constructors","title":"ControlSystemsBase.sminreal","text":"sminreal(sys)\n\nCompute 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.\n\nSystems 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.,\n\ntrunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0\ntrunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)\nsminreal(sys)\n\nIn 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.\n\nSee also minreal.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.ss","page":"Constructors","title":"ControlSystemsBase.ss","text":"sys = ss(A, B, C, D) # Continuous\nsys = ss(A, B, C, D, Ts) # Discrete\n\nCreate a state-space model sys::StateSpace{TE, T} with matrix element type T and TE is Continuous or <:Discrete.\n\nThis is a continuous-time model if Ts is omitted. Otherwise, this is a discrete-time model with sampling period Ts.\n\nD 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.\n\nTo associate names with states, inputs and outputs, see named_ss.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.tf","page":"Constructors","title":"ControlSystemsBase.tf","text":"sys = tf(num, den[, Ts])\nsys = tf(gain[, Ts])\n\nCreate as a fraction of polynomials:\n\nsys::TransferFunction{SisoRational{T,TR}} = numerator/denominator\n\nwhere T is the type of the coefficients in the polynomial.\n\nnum: the coefficients of the numerator polynomial. Either scalar or vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nden: the coefficients of the denominator polynomial. Either vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nTs: Sample time if discrete time system.\n\nThe polynomial coefficients are ordered starting from the highest order term. \n\nOther uses:\n\ntf(sys): Convert sys to tf form.\ntf(\"s\"), tf(\"z\"): Create the continuous-time transfer function s, or the discrete-time transfer function z.\nnumpoly(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.\n\nSee also: zpk, ss.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.zpk","page":"Constructors","title":"ControlSystemsBase.zpk","text":"zpk(gain[, Ts])\nzpk(num, den, k[, Ts])\nzpk(sys)\n\nCreate transfer function on zero pole gain form. The numerator and denominator are represented by their poles and zeros.\n\nsys::TransferFunction{SisoZpk{T,TR}} = k*numerator/denominator\n\nwhere T is the type of k and TR the type of the zeros/poles, usually Float64 and Complex{Float64}.\n\nnum: the roots of the numerator polynomial. Either scalar or vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nden: the roots of the denominator polynomial. Either vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nk: The gain of the system. Obs, this is not the same as dcgain.\nTs: Sample time if discrete time system.\n\nOther uses:\n\nzpk(sys): Convert sys to zpk form.\nzpk(\"s\"): Create the transferfunction s.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.delay","page":"Constructors","title":"ControlSystemsBase.delay","text":"delay(tau)\ndelay(tau, Ts)\ndelay(T::Type{<:Number}, tau)\ndelay(T::Type{<:Number}, tau, Ts)\n\nCreate a pure time delay of length τ of type T.\n\nThe type T defaults to promote_type(Float64, typeof(tau)).\n\nIf Ts is given, the delay is discretized with sampling time Ts and a discrete-time StateSpace object is returned.\n\nExample:\n\nCreate a LTI system with an input delay of L\n\nL = 1\ntf(1, [1, 1])*delay(L)\ns = tf(\"s\")\ntf(1, [1, 1])*exp(-s*L) # Equivalent to the version above\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.pade","page":"Constructors","title":"ControlSystemsBase.pade","text":"pade(τ::Real, N::Int)\n\nCompute the Nth order Padé approximation of a time-delay of length τ.\n\nSee also thiran for discretization of delays.\n\n\n\n\n\npade(G::DelayLtiSystem, N)\n\nApproximate all time-delays in G by Padé approximations of degree N.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.thiran","page":"Constructors","title":"ControlSystemsBase.thiran","text":"thiran(τ::Real, Ts)\n\nDiscretize a potentially fractional delay τ as a Thiran all-pass filter with sample time Ts. \n\nThe Thiran all-pass filter gives an a maximally flat group delay.\n\nIf τ is an integer multiple of Ts, the Thiran all-pass filter reduces to z^-τTs.\n\nRef: 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.ssdata","page":"Constructors","title":"ControlSystemsBase.ssdata","text":"A, B, C, D = ssdata(sys)\n\nA destructor that outputs the statespace matrices.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.seriesform","page":"Constructors","title":"ControlSystemsBase.seriesform","text":"Gs, k = seriesform(G::TransferFunction{Discrete})\n\nConvert a transfer function G to a vector of second-order transfer functions and a scalar gain k, the product of which equals G.\n\n\n\n\n\n","category":"function"},{"location":"examples/smith_predictor/#Smith-predictor","page":"Smith predictor","title":"Smith predictor","text":"","category":"section"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"This example designs a controller for a plant with a time delay using the internal-model principle, which in this case implies the use of a Smith predictor. The plant is given by $ \\dfrac{1}{s + 1}e^{-s\\tau} = P_0 e^{-s\\tau}$","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"and the control architecture looks like this","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":" ┌──────┐ ┌─────────────┐\nr │ │ u │ │\n───+──+────────►│ C0 ├───────────┬─►│ P0*exp(-st) ├─┐y\n ▲ ▲ │ │ │ │ │ │\n -│ │- └──────┘ │ └─────────────┘ │\n │ │ │ │\n │ │ ┌──────────┐ ┌──────┐ │ │\n │ │ │ │ │ │ │ │\n │ └─┤1-exp(-st)│◄───┤ P0 │◄──┘ │\n │ │ │ │ │ │\n │ └──────────┘ └──────┘ │\n │ │\n └──────────────────────────────────────────────────┘","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"The benefit of this approach is that the controller C_0 can be designed for the nominal plant P_0 without time delay, and still behave well in the presence of the delay. We also see why we refer to such a controller as using an \"internal model\", due to the presence of a model of P_0 in the inner feedback path.","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We now set up the nominal system and PI controller","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"using ControlSystemsBase, Plots\nP0 = ss(-1, 1, 1, 0) # Nominal system","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We design a PI controller for nominal system using placePI. To verify the pole placement, use, e.g., dampreport(feedback(P0, C0))","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"ω0 = 2\nζ = 0.7\nC0, _ = placePI(P0, ω0, ζ)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We then setup delayed plant + Smith predictor-based controller","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"τ = 8\nP = delay(τ) * P0\nC = feedback(C0, (1.0 - delay(τ))*P0) # form the inner feedback connection in the diagram above","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"using ControlSystems # Load full ControlSystems for delay-system simulation\nG = [feedback(P*C, 1) feedback(P, C)] # Reference step at t = 0 and load disturbance step at t = 15\nfig_timeresp = plot(lsim(G, (_,t) -> [1; t >= 15], 0:0.1:40), title=\"τ = $τ\")","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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). ","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"C_pred = feedback(1, C0*(ss(1.0) - delay(τ))*P0)\nfig_bode = bodeplot([C_pred, delay(-τ)], exp10.(-1:0.002:0.4), ls=[:solid :solid :dash :dash], title=\"\", lab=[\"Smith predictor\" \"\" \"Ideal predictor\" \"\"])\nplot!(yticks=[0.1, 1, 10], sp=1)\nplot!(yticks=0:180:1080, sp=2)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"Check the Nyquist plot. Note that the Nyquist curve encircles -1 for τ > 2.99","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"fig_nyquist = nyquistplot(C * P, exp10.(-1:1e-4:2), title=\"τ = $τ\")","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"A video tutorial on delay systems is available here:","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"","category":"page"},{"location":"examples/smith_predictor/#Additional-design-methods-for-delay-systems","page":"Smith predictor","title":"Additional design methods for delay systems","text":"","category":"section"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.\nDiscretize 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.\nNeglect 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.\nNeglect 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.\nFrequency-domain methods such as manual loop shaping, and some forms of optimization-based tuning, handle time delays natively. ","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.","category":"page"},{"location":"lib/timefreqresponse/#Time-and-Frequency-response-analysis","page":"Time and Frequency response","title":"Time and Frequency response analysis","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Pages = [\"timefreqresponse.md\"]","category":"page"},{"location":"lib/timefreqresponse/#Frequency-response","page":"Time and Frequency response","title":"Frequency response","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Frequency responses are calculated using freqresp, bode, sigma and nyquist. Frequency-response plots are obtained using bodeplot, sigmaplot, nyquistplot, marginplot and nicholsplot.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Any TransferFunction can be evaluated at a point using F(s), F(omega, true), F(z, false)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"F(s) evaluates the continuous-time transfer function F at s.\nF(omega,true) evaluates the discrete-time transfer function F at exp(i*Ts*omega)\nF(z,false) evaluates the discrete-time transfer function F at z","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"A video demonstrating frequency-response analysis in ControlSystems.jl is available below.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"","category":"page"},{"location":"lib/timefreqresponse/#Time-response-(simulation)","page":"Time and Frequency response","title":"Time response (simulation)","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"For more extensive nonlinear simulation capabilities, see the notes on ModelingToolkit and DifferentialEquations under The wider Julia ecosystem for control.","category":"page"},{"location":"lib/timefreqresponse/#Example-step-response:","page":"Time and Frequency response","title":"Example step response:","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"The following simulates a step response of a second-order system and plots the result.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"using ControlSystemsBase, Plots\nG = tf(1, [1, 1, 1])\nres = step(G, 20) # Simulate 20 seconds step response\nplot(res)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Using the function stepinfo, we can compute characteristics of a step response:","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"si = stepinfo(res)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"We can also plot the StepInfo object","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"plot(si)","category":"page"},{"location":"lib/timefreqresponse/#Example-lsim:","page":"Time and Frequency response","title":"Example lsim:","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"The function lsim can take the control input as either","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"An array of equidistantly sampled values, in this case the argument u is expected to have the shape nu × n_time\nA 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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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:","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"using ControlSystems\nusing LinearAlgebra: I\nusing Plots\n\nA = [0 1; 0 0]\nB = [0;1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R)\n\nu(x,t) = -L*x .+ (t > 4) # State feedback + step disturbance\nt = 0:0.1:12\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\"); vline!([4], lab=\"Step disturbance\", l=(:black, :dash, 0.5))","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"A video demonstrating time-domain simulation in ControlSystems.jl is available below.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"","category":"page"},{"location":"lib/timefreqresponse/#Docstrings","page":"Time and Frequency response","title":"Docstrings","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [libpath*\"/timeresp.jl\", libpath*\"/result_types.jl\", libpath*\"/freqresp.jl\", \"simulators.jl\"]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/timefreqresponse/#ControlSystems.Simulator","page":"Time and Frequency response","title":"ControlSystems.Simulator","text":"Simulator\n\nFields:\n\nP::StateSpace\nf = (x,p,t) -> x\ny = (x,t) -> y\n\n\n\n\n\n","category":"type"},{"location":"lib/timefreqresponse/#ControlSystems.Simulator-Union{Tuple{AbstractStateSpace}, Tuple{F}, Tuple{AbstractStateSpace, F}} where F","page":"Time and Frequency response","title":"ControlSystems.Simulator","text":"Simulator(P::StateSpace, u = (x,t) -> 0)\n\nUsed to simulate continuous-time systems. See function ?solve for additional info.\n\nUsage:\n\nusing OrdinaryDiffEq, Plots\ndt = 0.1\ntfinal = 20\nt = 0:dt:tfinal\nP = ss(tf(1,[2,1])^2)\nK = 5\nreference(x,t) = [1.]\ns = Simulator(P, reference)\nx0 = [0.,0]\ntspan  = (0.0,tfinal)\nsol = solve(s, x0, tspan, Tsit5())\nplot(t, s.y(sol, t)[:], lab=\"Open loop step response\")\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#Base.step-Tuple{AbstractStateSpace, AbstractVector}","page":"Time and Frequency response","title":"Base.step","text":"y, t, x = step(sys[, tfinal])\ny, t, x = step(sys[, t])\n\nCalculate 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. \n\nThe return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result. \n\ny has size (ny, length(t), nu), x has size (nx, length(t), nu)\n\nSee also stepinfo and lsim.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.impulse-Tuple{AbstractStateSpace, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.impulse","text":"y, t, x = impulse(sys[, tfinal])\ny, t, x = impulse(sys[, t])\n\nCalculate 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. \n\nThe return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.\n\ny has size (ny, length(t), nu), x has size (nx, length(t), nu)\n\nSee also lsim.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.lsim!-Union{Tuple{T}, Tuple{LsimWorkspace{T}, AbstractStateSpace{<:Discrete}, Any, AbstractVector}} where T","page":"Time and Frequency response","title":"ControlSystemsBase.lsim!","text":"res = lsim!(ws::LsimWorkspace, sys::AbstractStateSpace{<:Discrete}, u, [t]; x0)\n\nIn-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.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.lsim-Tuple{AbstractStateSpace, AbstractVecOrMat, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.lsim","text":"result = lsim(sys, u[, t]; x0, method])\nresult = lsim(sys, u::Function, t; x0, method)\n\nCalculate the time response of system sys to input u. If x0 is omitted, a zero vector is used.\n\nThe result structure contains the fields y, t, x, u and can be destructured automatically by iteration, e.g.,\n\ny, t, x, u = result\n\nresult::SimResult can also be plotted directly:\n\nplot(result, plotu=true, plotx=false)\n\ny, x, u have time in the second dimension. Initial state x0 defaults to zero.\n\nContinuous-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.\n\nu 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.\n\nNote: 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.\n\nFor maximum performance, see function lsim!, available for discrete-time systems only.\n\nUsage example:\n\nusing ControlSystems\nusing LinearAlgebra: I\nusing Plots\n\nA = [0 1; 0 0]\nB = [0;1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R)\n\nu(x,t) = -L*x # Form control law\nt = 0:0.1:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\n# Alternative way of plotting\nres = lsim(sys,u,t,x0=x0)\nplot(res)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.stepinfo-Tuple{ControlSystemsBase.SimResult}","page":"Time and Frequency response","title":"ControlSystemsBase.stepinfo","text":"stepinfo(res::SimResult; y0 = nothing, yf = nothing, settling_th = 0.02, risetime_th = (0.1, 0.9))\n\nCompute the step response characteristics for a simulation result. The following information is computed and stored in a StepInfo struct:\n\ny0: The initial value of the response\nyf: The final value of the response\nstepsize: The size of the step\npeak: The peak value of the response\npeaktime: The time at which the peak occurs\novershoot: The percentage overshoot of the response\nundershoot: The percentage undershoot of the response. If the step response never reaches below the initial value, the undershoot is zero.\nsettlingtime: The time at which the response settles within settling_th of the final value\nsettlingtimeind: The index at which the response settles within settling_th of the final value\nrisetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value\n\nArguments:\n\nres: The result from a simulation using step (or lsim)\ny0: The initial value, if not provided, the first value of the response is used.\nyf: 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.\nsettling_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.\nrisetime_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.\n\nExample:\n\nG = tf([1], [1, 1, 1])\nres = step(G, 15)\nsi = stepinfo(res)\nplot(si)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.LsimWorkspace-Tuple{AbstractStateSpace, Int64}","page":"Time and Frequency response","title":"ControlSystemsBase.LsimWorkspace","text":"LsimWorkspace(sys::AbstractStateSpace, N::Int)\nLsimWorkspace(sys::AbstractStateSpace, u::AbstractMatrix)\nLsimWorkspace{T}(ny, nu, nx, N)\n\nGenerate 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. \n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.StepInfo","page":"Time and Frequency response","title":"ControlSystemsBase.StepInfo","text":"StepInfo\n\nComputed using stepinfo\n\nFields:\n\ny0: The initial value of the step response.\nyf: The final value of the step response.\nstepsize: The size of the step.\npeak: The peak value of the step response.\npeaktime: The time at which the peak occurs.\novershoot: The overshoot of the step response.\nsettlingtime: The time at which the step response has settled to within settling_th of the final value.\nsettlingtimeind::Int: The index at which the step response has settled to within settling_th of the final value.\nrisetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value \ni10::Int: The index at which the response reaches risetime_th[1]\ni90::Int: The index at which the response reaches risetime_th[2]\nres::SimResult{SR}: The simulation result used to compute the step response characteristics.\nsettling_th: The threshold used to compute settlingtime and settlingtimeind.\nrisetime_th: The thresholds used to compute risetime, i10, and i90.\n\n\n\n\n\n","category":"type"},{"location":"lib/timefreqresponse/#ControlSystemsBase.bode-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.bode","text":"mag, phase, w = bode(sys[, w]; unwrap=true)\n\nCompute the magnitude and phase parts of the frequency response of system sys at frequencies w. See also bodeplot\n\nmag 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.\n\nFor higher performance, see the function bodemag! that computes the magnitude only.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.bodemag!-Tuple{BodemagWorkspace, LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.bodemag!","text":"mag = bodemag!(ws::BodemagWorkspace, sys::LTISystem, w::AbstractVector)\n\nCompute 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ω).\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.evalfr-Tuple{AbstractStateSpace, Number}","page":"Time and Frequency response","title":"ControlSystemsBase.evalfr","text":"evalfr(sys, x)\n\nEvaluate the transfer function of the LTI system sys at the complex number s=x (continuous-time) or z=x (discrete-time).\n\nFor many values of x, use freqresp instead.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.freqresp!-Union{Tuple{T}, Tuple{Array{T, 3}, LTISystem, AbstractVector{<:Real}}} where T","page":"Time and Frequency response","title":"ControlSystemsBase.freqresp!","text":"freqresp!(R::Array{T, 3}, sys::LTISystem, w_vec::AbstractVector{<:Real})\n\nIn-place version of freqresp that takes a pre-allocated array R of size (ny, nu, nw)`\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.freqresp-Union{Tuple{W}, Tuple{LTISystem, AbstractVector{W}}} where W<:Real","page":"Time and Frequency response","title":"ControlSystemsBase.freqresp","text":"sys_fr = freqresp(sys, w)\n\nEvaluate the frequency response of a linear system\n\nw -> C*((iw*im*I - A)^-1)*B + D\n\nof system sys over the frequency vector w.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.nyquist-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.nyquist","text":"re, im, w = nyquist(sys[, w])\n\nCompute the real and imaginary parts of the frequency response of system sys at frequencies w. See also nyquistplot\n\nre and im has size (ny, nu, length(w))\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.sigma-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.sigma","text":"sv, w = sigma(sys[, w])\n\nCompute the singular values sv of the frequency response of system sys at frequencies w. See also sigmaplot\n\nsv has size (min(ny, nu), length(w))\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.BodemagWorkspace-Tuple{LTISystem, Int64}","page":"Time and Frequency response","title":"ControlSystemsBase.BodemagWorkspace","text":"BodemagWorkspace(sys::LTISystem, N::Int)\nBodemagWorkspace(sys::LTISystem, ω::AbstractVector)\nBodemagWorkspace(R::Array{Complex{T}, 3}, mag::Array{T, 3})\nBodemagWorkspace{T}(ny, nu, N)\n\nGenerate 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. \n\nArguments:\n\nmag: The output array ∈ 𝐑(ny, nu, nω)\nR: Frequency-response array ∈ 𝐂(ny, nu, nω)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.TransferFunction-Tuple{Any}","page":"Time and Frequency response","title":"ControlSystemsBase.TransferFunction","text":"F(s), F(omega, true), F(z, false)\n\nNotation for frequency response evaluation.\n\nF(s) evaluates the continuous-time transfer function F at s.\nF(omega,true) evaluates the discrete-time transfer function F at exp(imTsomega)\nF(z,false) evaluates the discrete-time transfer function F at z\n\n\n\n\n\n","category":"method"},{"location":"man/introduction/#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"man/introduction/#Installation","page":"Introduction","title":"Installation","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"ControlSystems.jl is written in the Julia programming language and is available through the Julia package manager. To install Julia, follow the instructions at julialang.org.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"To install the full set of features of ControlSystems.jl, simply run the following command in the Julia REPL:","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Pkg; Pkg.add(\"ControlSystems\")","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"For workflows that do not require continuous-time simulation, you may instead opt to install the much lighter package ControlSystemsBase.jl","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Pkg; Pkg.add(\"ControlSystemsBase\")","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"ControlSystemsBase contains all functionality of ControlSystems except continuous-time simulation and root locus, and is considerably faster to load and precompile. To enjoy the faster pre-compilation, do not even install ControlSystems since this will cause pre-compilation of OrdinaryDiffEq, which can take several minutes.","category":"page"},{"location":"man/introduction/#Basic-functions","page":"Introduction","title":"Basic functions","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"DocTestSetup = quote\n using ControlSystems\n P = tf([1],[1,1])\n T = P/(1+P)\n plotsDir = joinpath(dirname(pathof(ControlSystems)), \"..\", \"docs\", \"build\", \"plots\")\n mkpath(plotsDir)\n save_docs_plot(name) = Plots.savefig(joinpath(plotsDir,name))\n save_docs_plot(p, name) = Plots.savefig(p, joinpath(plotsDir,name))\nend","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"State-space systems can be created using the function ss and transfer functions can be created using the function tf(num, den) or tf(num, den, Ts), where num and den are vectors representing the numerator and denominator of a rational function and Ts is the sample time for a discrete-time system. See tf or the section [Creating Systems] for more info. These functions can then be connected and modified using the operators +,-,*,/ and functions like append.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"Example:","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"P = tf([1.0],[1,1])\nT = P/(1+P)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0s + 1.0\n-------------------\n1.0s^2 + 3.0s + 2.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"Notice that the poles are not canceled automatically, to do this, the function minreal is available","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"minreal(T)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0\n----------\n1.0s + 2.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"or use feedback(P) to get a minimal realization directly (recommended):","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using ControlSystems # hide\nP = tf([1.0],[1,1]) # hide\nfeedback(P) # Equivalent to P/(1+P)","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"note: Numerical accuracy\nTransfer 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.","category":"page"},{"location":"man/introduction/#Plotting","page":"Introduction","title":"Plotting","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"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.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"More examples of plots are provided in Plotting functions.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Plots\nbodeplot(tf(1,[1,2,1]))","category":"page"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"Pages = [\"synthesis.md\"]","category":"page"},{"location":"lib/synthesis/#Synthesis","page":"Synthesis","title":"Synthesis","text":"","category":"section"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"For H_infty and H_2 synthesis as well as more advanced LQG design, see RobustAndOptimalControl.","category":"page"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [\n libpath*\"/synthesis.jl\",\n libpath*\"/discrete.jl\",\n libpath*\"/types/lqg.jl\",\n libpath*\"/pid_design.jl\",\n libpath*\"/simplification.jl\",\n libpath*\"/connections.jl\",\n libpath*\"/sensitivity_functions.jl\",\n libpath*\"/utilities.jl\"\n ]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/synthesis/#ControlSystemsBase.kalman-Tuple{Any, Any, Any, Any, Any, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.kalman","text":"kalman(Continuous, A, C, R1, R2)\nkalman(Discrete, A, C, R1, R2; direct = false)\nkalman(sys, R1, R2; direct = false)\n\nCalculate the optimal asymptotic Kalman gain.\n\nIf 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.\n\nTo obtain a discrete-time approximation to a continuous-time LQG problem, the function c2d can be used to obtain corresponding discrete-time covariance matrices.\n\nTo 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.\n\nThe args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec/ared for more help.\n\nFAQ\n\nThis function requires\n\nR1 must be positive semi-definite\nR2 must be positive definite\nThe 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.lqr-Tuple{Union{Continuous, Type{Continuous}}, Any, Any, Any, Any, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.lqr","text":"lqr(sys, Q, R)\nlqr(Continuous, A, B, Q, R, args...; kwargs...)\nlqr(Discrete, A, B, Q, R, args...; kwargs...)\n\nCalculate the optimal gain matrix K for the state-feedback law u = -K*x that minimizes the cost function:\n\nJ = 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].\n\nSolve the LQR problem for state-space system sys. Works for both discrete and continuous time systems.\n\nThe args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec / ared for more help.\n\nTo 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).\n\nTo obtain a discrete-time approximation to a continuous-time LQR problem, the function c2d can be used to obtain corresponding discrete-time cost matrices.\n\nExamples\n\nContinuous time\n\nusing LinearAlgebra # For identity matrix I\nusing Plots\nA = [0 1; 0 0]\nB = [0; 1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R) # lqr(Continuous,A,B,Q,R) can also be used\n\nu(x,t) = -L*x # Form control law,\nt=0:0.1:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nDiscrete time\n\nusing LinearAlgebra # For identity matrix I\nusing Plots\nTs = 0.1\nA = [1 Ts; 0 1]\nB = [0;1]\nC = [1 0]\nsys = ss(A, B, C, 0, Ts)\nQ = I\nR = I\nL = lqr(Discrete, A,B,Q,R) # lqr(sys,Q,R) can also be used\n\nu(x,t) = -L*x # Form control law,\nt=0:Ts:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nFAQ\n\nThis function requires\n\nQ must be positive semi-definite\nR must be positive definite\nThe 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.place","page":"Synthesis","title":"ControlSystemsBase.place","text":"place(A, B, p, opt=:c; direct = false)\nplace(sys::StateSpace, p, opt=:c; direct = false)\n\nCalculate the gain matrix K such that A - BK has eigenvalues p.\n\nplace(A, C, p, opt=:o)\nplace(sys::StateSpace, p, opt=:o)\n\nCalculate the observer gain matrix L such that A - LC has eigenvalues p.\n\nIf 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. \n\nNote: only apply direct = true to discrete-time systems.\n\nRef: \"Computer-Controlled Systems\" pp 140.\n\nUses Ackermann's formula for SISO systems and place_knvd for MIMO systems. \n\nPlease note that this function can be numerically sensitive, solving the placement problem in extended precision might be beneficial.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.place_knvd-Tuple{AbstractMatrix, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.place_knvd","text":"place_knvd(A::AbstractMatrix, B, λ; verbose = false, init = :s)\n\nRobust pole placement using the algorithm from\n\n\"Robust Pole Assignment in Linear State Feedback\", Kautsky, Nichols, Van Dooren\n\nThis implementation uses \"method 0\" for the X-step and the QR factorization for all factorizations.\n\nThis function will be called automatically when place is called with a MIMO system.\n\nArguments:\n\ninit: Determines the initialization strategy for the iterations for find the X matrix. Possible choices are :id (default), :rand, :s. \n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d","page":"Synthesis","title":"ControlSystemsBase.c2d","text":"sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\nGd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)\n\nConvert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).\n\nmethod = :tustin performs a bilinear transform with prewarp frequency w_prewarp.\n\nw_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also c2d_x0map\n\nExtended help\n\nZoH 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.\n\nFoH 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.\n\nTo 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.\n\nThe 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.\n\nClassical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as 02 ωgcTs 06 where ωgc is the gain-crossover frequency (rad/s).\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.c2d-Tuple{AbstractStateSpace{<:Continuous}, AbstractMatrix, Real}","page":"Synthesis","title":"ControlSystemsBase.c2d","text":"Qd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)\nQd = c2d(sys::StateSpace{Discrete}, Qc::Matrix; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Discrete}, Qc::Matrix, Rc::Matrix; opt=:o)\n\nSample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.\n\nIf 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.\n\nnote: Note\nMeasurement 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.\n\nThe method used comes from theorem 5 in the reference below.\n\nRef: \"Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering\", Patrik Axelsson and Fredrik Gustafsson\n\nOn 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'. \n\nExample:\n\nThe 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.\n\nusing ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test\nsysc = DemoSystems.resonant()\nx0 = ones(sysc.nx)\nQc = [1 0.01; 0.01 2] # Continuous-time cost matrix for the state\nRc = I(1) # Continuous-time cost matrix for the input\n\nL = lqr(sysc, Qc, Rc)\ndynamics = function (xc, p, t)\n x = xc[1:sysc.nx]\n u = -L*x\n dx = sysc.A*x + sysc.B*u\n dc = dot(x, Qc, x) + dot(u, Rc, u)\n return [dx; dc]\nend\nprob = ODEProblem(dynamics, [x0; 0], (0.0, 10.0))\nsol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)\ncc = sol.u[end][end] # Continuous-time cost\n\n# Discrete-time version\nTs = 0.01 \nsysd = c2d(sysc, Ts)\nQd, Rd = c2d(sysd, Qc, Rc, opt=:c)\nLd = lqr(sysd, Qd, Rd)\nsold = lsim(sysd, (x, t) -> -Ld*x, 0:Ts:10, x0 = x0)\nfunction cost(x, u, Q, R)\n dot(x, Q, x) + dot(u, R, u)\nend\ncd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost\n@test cc ≈ cd rtol=0.01 # These should be similar\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_poly2poly-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.c2d_poly2poly","text":"c2d_poly2poly(ro, Ts)\n\nreturns the polynomial coefficients in discrete time given polynomial coefficients in continuous time\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_roots2poly-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.c2d_roots2poly","text":"c2d_roots2poly(ro, Ts)\n\nreturns the polynomial coefficients in discrete time given a vector of roots in continuous time\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_x0map","page":"Synthesis","title":"ControlSystemsBase.c2d_x0map","text":"sysd, x0map = c2d_x0map(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\n\nReturns 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]\n\nSee c2d for further details.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c","page":"Synthesis","title":"ControlSystemsBase.d2c","text":"Qc = d2c(sys::AbstractStateSpace{<:Discrete}, Qd::AbstractMatrix; opt=:o)\n\nResample discrete-time covariance matrix belonging to sys to the equivalent continuous-time matrix.\n\nThe method used comes from theorem 5 in the reference below.\n\nIf opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.\n\nRef: Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering Patrik Axelsson and Fredrik Gustafsson\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c-2","page":"Synthesis","title":"ControlSystemsBase.d2c","text":"d2c(sys::AbstractStateSpace{<:Discrete}, method::Symbol = :zoh; w_prewarp=0)\n\nConvert discrete-time system to a continuous time system, assuming that the discrete-time system was discretized using method. Available methods are `:zoh, :fwdeuler´.\n\nw_prewarp: Frequency for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also d2c_exact.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c_exact","page":"Synthesis","title":"ControlSystemsBase.d2c_exact","text":"d2c_exact(sys::AbstractStateSpace{<:Discrete}, method = :causal)\n\nTranslate a discrete-time system to a continuous-time system by one of the substitutions\n\nz^-1 = e^-sT_s if method = :causal (default)\nz = e^sT_s if method = :acausal\n\nThe 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.\n\nThis method of translation is useful when analyzing hybrid continuous/discrete systems in the frequency domain and high accuracy is required.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.dab-Tuple{Any, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.dab","text":"X,Y = dab(A,B,C)\n\nSolves the Diophantine-Aryabhatta-Bezout identity\n\nAX + BY = C, where A B C X and Y are polynomials and deg Y = deg A - 1.\n\nSee Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.rstc-Tuple","page":"Synthesis","title":"ControlSystemsBase.rstc","text":"See ?rstd for the discrete case\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.rstd-Tuple","page":"Synthesis","title":"ControlSystemsBase.rstd","text":"R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR,AS)\nR,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR)\nR,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO)\n\nPolynomial synthesis in discrete time.\n\nPolynomial synthesis according to \"Computer-Controlled Systems\" ch 10 to design a controller R(q) u(k) = T(q) r(k) - S(q) y(k)\n\nInputs:\n\nBPLUS : Part of open loop numerator\nBMINUS : Part of open loop numerator\nA : Open loop denominator\nBM1 : Additional zeros\nAM : Closed loop denominator\nAO : Observer polynomial\nAR : Pre-specified factor of R,\n\ne.g integral part [1, -1]^k\n\nAS : Pre-specified factor of S,\n\ne.g notch filter [1, 0, w^2]\n\nOutputs: R,S,T : Polynomials in controller\n\nSee function dab how the solution to the Diophantine- Aryabhatta-Bezout identity is chosen.\n\nSee Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.zpconv-NTuple{4, Any}","page":"Synthesis","title":"ControlSystemsBase.zpconv","text":"zpc(a,r,b,s)\n\nform 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\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.laglink-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.laglink","text":"laglink(a, M; [Ts])\n\nReturns 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\n\ndfracs + as + aM = M dfrac1 + sa1 + sMa\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.leadlink","page":"Synthesis","title":"ControlSystemsBase.leadlink","text":"leadlink(b, N, K=1; [Ts])\n\nReturns 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\n\nThe phase advance at ω = b√(N) can be plotted as a function of N with leadlinkcurve()\n\nValues of N < 1 will give a phase retarding link.\n\nKN dfracs + bs + bN = K dfrac1 + sb1 + s(bN)\n\nSee also leadlinkat laglink\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.leadlinkat","page":"Synthesis","title":"ControlSystemsBase.leadlinkat","text":"leadlinkat(ω, N, K=1; [Ts])\n\nReturns 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)\n\nThe phase advance at ω can be plotted as a function of N with leadlinkcurve()\n\nValues of N < 1 will give a phase retarding link.\n\nSee also leadlink laglink\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.leadlinkcurve","page":"Synthesis","title":"ControlSystemsBase.leadlinkcurve","text":"leadlinkcurve(start=1)\n\nPlot 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.\n\nSee also leadlink, leadlinkat\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.loopshapingPI-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.loopshapingPI","text":"C, kp, ki, fig, CF = loopshapingPI(P, ωp; ϕl, rl, phasemargin, form=:standard, doplot=false, Tf, F)\n\nSelects 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)\n\nThe parameters can be returned as one of several common representations chosen by form, the options are\n\n:standard - K_p(1 + 1(T_i s) + T_d s)\n:series - K_c(1 + 1(τ_i s))(τ_d s + 1)\n:parallel - K_p + K_is + K_d s\n\nIf phasemargin is supplied (in degrees), ϕl is selected such that the curve is moved to an angle of phasemargin - 180 degrees\n\nIf 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.\n\nTf: 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.\nF: A pre-designed filter to use instead of the default second-order filter that is used if Tf is given.\ndoplot plot the gangoffourplot and nyquistplot of the system.\n\nSee also loopshapingPID, pidplots, stabregionPID and placePI.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.loopshapingPID-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.loopshapingPID","text":"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)\n\nSelects 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.\n\nThe default values for Mt and ϕt are chosen to give a good design for processes with inertia, and may need tuning for simpler processes.\n\nThe gain of the resulting controller is generally increasing with increasing ω and Mt.\n\nArguments:\n\nP: A SISO plant.\nω: The specification frequency.\nMt: The magnitude of the complementary sensitivity function at the specification frequency, T(iω).\nϕt: The positive angle in degrees between the real axis and the tangent point.\ndoplot: If true, gang of four and Nyquist plots will be returned in fig.\nlb: log10 of lower bound for kd.\nub: log10 of upper bound for kd.\nTf: 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ω\nF: A pre-designed filter to use instead of the default second-order filter.\n\nThe parameters can be returned as one of several common representations chosen by form, the options are\n\n:standard - K_p(1 + 1(T_i s) + T_ds)\n:series - K_c(1 + 1(τ_i s))(τ_d s + 1)\n:parallel - K_p + K_is + K_d s\n\nSee also loopshapingPI, pidplots, stabregionPID and placePI.\n\nExample:\n\nP = tf(1, [1,0,0]) # A double integrator\nMt = 1.3 # Maximum magnitude of complementary sensitivity\nω = 1 # Frequency at which the specification holds\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt = 75, doplot=true)\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.pid","page":"Synthesis","title":"ControlSystemsBase.pid","text":"C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts])\n\nCalculates and returns a PID controller. \n\nThe form can be chosen as one of the following\n\n:standard - Kp*(1 + 1/(Ti*s) + Td*s) \n:series - Kc*(1 + 1/(τi*s))*(τd*s + 1)\n:parallel - Kp + Ki/s + Kd*s\n\nIf 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. The filter used is 1 / (1 + s*Tf + (s*Tf)^2/2), where Tf can typically be chosen as Ti/N for a PI controller and Td/N for a PID controller, and N is commonly in the range 2 to 20. The state space will be returned on controllable canonical form.\n\nFor a discrete controller a positive Ts can be supplied. In this case, the continuous-time controller is discretized using the Tustin method.\n\nExamples\n\nC1 = pid(3.3, 1, 2) # Kd≠0 works without filter in tf form\nC2 = pid(3.3, 1, 2; Tf=0.3, state_space=true) # In statespace a filter is needed\nC3 = pid(2., 3, 0; Ts=0.4, state_space=true) # Discrete\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.pidplots-Tuple{LTISystem, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.pidplots","text":"pidplots(P, args...; params_p, params_i, params_d=0, form=:standard, ω=0, grid=false, kwargs...)\n\nDisplay the relevant plots related to closing the loop around process P with a PID controller supplied in params on one of the following forms:\n\n:standard - Kp*(1 + 1/(Ti*s) + Td*s) \n:series - Kc*(1 + 1/(τi*s))*(τd*s + 1)\n:parallel - Kp + Ki/s + Kd*s\n\nThe 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.\n\nAvailable 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.\n\nOne can also supply a frequency vector ω to be used in Bode and Nyquist plots.\n\nSee also loopshapingPI, stabregionPID\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.placePI-Union{Tuple{T}, Tuple{TransferFunction{<:Continuous, <:ControlSystemsBase.SisoRational{T}}, Any, Any}} where T","page":"Synthesis","title":"ControlSystemsBase.placePI","text":"C, kp, ki = placePI(P, ω₀, ζ; form=:standard)\n\nSelects 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.\n\nThe parameters can be returned as one of several common representations chose by form, the options are\n\n:standard - K_p(1 + 1(T_i s))\n:series - K_c(1 + 1(τ_i s)) (equivalent to above for PI controllers)\n:parallel - K_p + K_is\n\nC 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).\n\nSee also loopshapingPI\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.stabregionPID","page":"Synthesis","title":"ControlSystemsBase.stabregionPID","text":"kp, ki, fig = stabregionPID(P, [ω]; kd=0, doplot=false, form=:standard)\n\nSegments 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\n\nP(s)C(s) = -1 \nPC = P C = 1 \narg(P) + arg(C) = -π\n\nIf 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\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.sminreal-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.sminreal","text":"sminreal(sys)\n\nCompute 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.\n\nSystems 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.,\n\ntrunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0\ntrunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)\nsminreal(sys)\n\nIn 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.\n\nSee also minreal.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.add_input","page":"Synthesis","title":"ControlSystemsBase.add_input","text":"add_input(sys::AbstractStateSpace, B2::AbstractArray, D2 = 0)\n\nAdd inputs to sys by forming\n\nbeginaligned\nx = Ax + B B_2u \ny = Cx + D D_2u \nendaligned\n\nIf 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.\n\nExample: The following example forms an innovation model that takes innovations as inputs\n\nG = ssrand(2,2,3, Ts=1)\nK = kalman(G, I(G.nx), I(G.ny))\nsys = add_input(G, K)\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.add_output","page":"Synthesis","title":"ControlSystemsBase.add_output","text":"add_output(sys::AbstractStateSpace, C2::AbstractArray, D2 = 0)\n\nAdd outputs to sys by forming\n\nbeginaligned\nx = Ax + Bu \ny = C C_2x + D D_2u \nendaligned\n\nIf 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.\n\nWhen called with C2 = I(sys.nx), this function is in some settings known to as augstate.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.append-Tuple{Vararg{AbstractStateSpace}}","page":"Synthesis","title":"ControlSystemsBase.append","text":"append(systems::StateSpace...), append(systems::TransferFunction...)\n\nAppend systems in block diagonal form\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.array2mimo-Tuple{AbstractArray{<:LTISystem}}","page":"Synthesis","title":"ControlSystemsBase.array2mimo","text":"array2mimo(M::AbstractArray{<:LTISystem})\n\nTake an array of LTISystems and create a single MIMO system.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback-Tuple{AbstractStateSpace, AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.feedback","text":"feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;\n U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],\n Wperm=:, Zperm=:, pos_feedback::Bool=false)\n\nBasic use feedback(sys1, sys2) forms the (negative) feedback interconnection\n\n ┌──────────────┐\n◄──────────┤ sys1 │◄──── Σ ◄──────\n │ │ │ │\n │ └──────────────┘ -1\n │ |\n │ ┌──────────────┐ │\n └─────►│ sys2 ├──────┘\n │ │\n └──────────────┘\n\nIf no second system sys2 is given, negative identity feedback (sys2 = 1) is assumed. The returned closed-loop system will have a state vector comprised of the state of sys1 followed by the state of sys2.\n\nAdvanced use feedback also supports more flexible use according to the figure below\n\n ┌──────────────┐\n z1◄─────┤ sys1 │◄──────w1\n ┌─── y1◄─────┤ │◄──────u1 ◄─┐\n │ └──────────────┘ │\n │ α\n │ ┌──────────────┐ │\n └──► u2─────►│ sys2 ├───────►y2──┘\n w2─────►│ ├───────►z2\n └──────────────┘\n\nU1, 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.\nY1, 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.\nU2, 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.\n\nSpecify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.\n\nNegative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).\n\nSee also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.\n\nThe 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.\n\nSee Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback-Tuple{TransferFunction}","page":"Synthesis","title":"ControlSystemsBase.feedback","text":"feedback(sys)\nfeedback(sys1, sys2)\n\nFor a general LTI-system, feedback forms the negative feedback interconnection\n\n>-+ sys1 +-->\n | |\n (-)sys2 +\n\nIf no second system is given, negative identity feedback is assumed\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback2dof-Tuple{TransferFunction, Any, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P,R,S,T)\nfeedback2dof(B,A,R,S,T)\n\nReturn BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively\nReturn BT/(AR+ST)\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback2dof-Union{Tuple{TE}, Tuple{TransferFunction{TE}, TransferFunction{TE}, TransferFunction{TE}}} where TE","page":"Synthesis","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P::TransferFunction, C::TransferFunction, F::TransferFunction)\n\nReturn 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).\n\n +-------+\n | |\n +-----> F +----+\n | | | |\n | +-------+ |\n | +-------+ | +-------+\nr | - | | | | | y\n+--+-----> C +----+----> P +---+-->\n | | | | | |\n | +-------+ +-------+ |\n | |\n +--------------------------------+\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.lft","page":"Synthesis","title":"ControlSystemsBase.lft","text":"lft(G, Δ, type=:l)\n\nLower and upper linear fractional transformation between systems G and Δ.\n\nSpecify :l lor lower LFT, and :u for upper LFT.\n\nG must have more inputs and outputs than Δ has outputs and inputs.\n\nFor details, see Chapter 9.1 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.parallel-Tuple{LTISystem, LTISystem}","page":"Synthesis","title":"ControlSystemsBase.parallel","text":"parallel(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in parallel, equivalent to sys2+sys1\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.series-Tuple{LTISystem, LTISystem}","page":"Synthesis","title":"ControlSystemsBase.series","text":"series(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in series, equivalent to sys2*sys1\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.starprod-Tuple{Any, Any, Int64, Int64}","page":"Synthesis","title":"ControlSystemsBase.starprod","text":"starprod(sys1, sys2, dimu, dimy)\n\nCompute the Redheffer star product.\n\nlength(U1) = length(Y2) = dimu and length(Y1) = length(U2) = dimy\n\nFor details, see Chapter 9.3 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.G_CS-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.G_CS","text":"G_CS(P, C)\n\nThe 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.G_PS-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.G_PS","text":"G_PS(P, C)\n\nThe 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.comp_sensitivity-Tuple","page":"Synthesis","title":"ControlSystemsBase.comp_sensitivity","text":"See output_comp_sensitivity\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.extended_gangoffour","page":"Synthesis","title":"ControlSystemsBase.extended_gangoffour","text":"extended_gangoffour(P, C, pos=true)\n\nReturns a single statespace system that maps \n\nw1 reference or measurement noise\nw2 load disturbance\n\nto\n\nz1 control error\nz2 control input\n\n z1 z2\n ▲ ┌─────┐ ▲ ┌─────┐\n │ │ │ │ │ │\nw1──+─┴─►│ C ├──┴───+─►│ P ├─┐\n │ │ │ │ │ │ │\n │ └─────┘ │ └─────┘ │\n │ w2 │\n └────────────────────────────┘\n\nThe returned system has the transfer-function matrix\n\nbeginbmatrix\nI C\nendbmatrix (I + PC)^-1 beginbmatrix\nI P\nendbmatrix\n\nor in code\n\n# For SISO P\nS = G[1, 1]\nPS = G[1, 2]\nCS = G[2, 1]\nT = G[2, 2]\n\n# For MIMO P\nS = G[1:P.ny, 1:P.nu]\nPS = G[1:P.ny, P.ny+1:end]\nCS = G[P.ny+1:end, 1:P.ny]\nT = G[P.ny+1:end, P.ny+1:end] # Input complimentary sensitivity function\n\nThe gang of four can be plotted like so\n\nGcl = extended_gangoffour(G, C) # Form closed-loop system\nbodeplot(Gcl, lab=[\"S\" \"CS\" \"PS\" \"T\"], plotphase=false) |> display # Plot gang of four\n\nNote, 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.input_comp_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.input_comp_sensitivity","text":"input_comp_sensitivity(P,C)\n\nTransfer function from load disturbance to control signal.\n\n\"Input\" signifies that the transfer function is from the input of the plant.\n\"Complimentary\" signifies that the transfer function is to an output (in this case controller output)\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.input_resolvent-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.input_resolvent","text":"input_resolvent(sys::AbstractStateSpace)\n\nReturn the input-mapped resolvent of sys\n\n(sI - A)^-1B\n\ni.e., the system ss(A, B, I, 0).\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.input_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.input_sensitivity","text":"input_sensitivity(P, C)\n\nTransfer function from load disturbance to total plant input.\n\n\"Input\" signifies that the transfer function is from the input of the plant.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.output_comp_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.output_comp_sensitivity","text":"output_comp_sensitivity(P,C)\n\nTransfer function from measurement noise / reference to plant output.\n\n\"output\" signifies that the transfer function is from the output of the plant.\n\"Complimentary\" signifies that the transfer function is to an output (in this case plant output)\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.output_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.output_sensitivity","text":"output_sensitivity(P, C)\n\nTransfer function from measurement noise / reference to control error.\n\n\"output\" signifies that the transfer function is from the output of the plant.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.resolvent-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.resolvent","text":"resolvent(sys::AbstractStateSpace)\n\nReturn the resolvent of sys\n\n(sI - A)^-1\n\ni.e., the system ss(A, I, I, 0).\n\nSee also input_resolvent.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sensitivity-Tuple","page":"Synthesis","title":"ControlSystemsBase.sensitivity","text":"See output_sensitivity\n\nThe 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\n\nϕ_m 2textsin^-1(frac12M_S) g_m fracM_SM_S-1\n\nGenerally, 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.bodev-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.bodev","text":"bodev(sys::LTISystem, w::AbstractVector; $(Expr(:kw, :unwrap, true)))\n\nFor use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.bodev-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.bodev","text":"bodev(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Tuple{AbstractMatrix, AbstractVector{<:Real}}","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(G::AbstractMatrix, w_vec::AbstractVector{<:Real}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Tuple{Number, AbstractVector{<:Real}}","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(G::Number, w_vec::AbstractVector{<:Real}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Union{Tuple{var\"#450#W\"}, Tuple{LTISystem, AbstractVector{var\"#450#W\"}}} where var\"#450#W\"<:Real","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(sys::LTISystem, w_vec::AbstractVector{W}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.nyquistv-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.nyquistv","text":"nyquistv(sys::LTISystem, w::AbstractVector; )\n\nFor use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.nyquistv-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.nyquistv","text":"nyquistv(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sigmav-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.sigmav","text":"sigmav(sys::LTISystem, w::AbstractVector; )\n\nFor use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sigmav-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.sigmav","text":"sigmav(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"examples/tuning_from_data/#Tuning-a-PID-controller-from-data","page":"Tune a controller using experimental data","title":"Tuning a PID controller from data","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"In this example, we will consider a very commonly occurring workflow: using process data to tune a PID controller.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The two main steps involved in this workflow are:","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Estimate a process model from data\nCharacterize the uncertainty in the estimated model\nDesign a controller based on the estimated model\nVerify that the controller is robust with respect to the estimated model uncertainty","category":"page"},{"location":"examples/tuning_from_data/#Estimation-of-a-model","page":"Tune a controller using experimental data","title":"Estimation of a model","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"In this example, which is split into two parts, we will consider tuning a velocity controller for a flexible robot arm. Part 1 is available here: Flexible Robot Arm Part 1: Estimation of a model.. The system identification uses the package ControlSystemIdentification.jl.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The rest of this example makes up part 2, tuning of the controller. We simply replicate the relevant code from part 1 to get the estimated model, and then use the estimated model to tune controllers.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using DelimitedFiles, Plots\nusing ControlSystemIdentification, ControlSystems\n\nurl = \"https://ftp.esat.kuleuven.be/pub/SISTA/data/mechanical/robot_arm.dat.gz\"\nzipfilename = \"/tmp/flex.dat.gz\"\npath = Base.download(url, zipfilename)\nrun(`gunzip -f $path`)\ndata = readdlm(path[1:end-3])\nu = data[:, 1]' # torque\ny = data[:, 2]' # acceleration\nd = iddata(y, u, 0.01) # sample time not specified for data, 0.01 is a guess\nPacc = subspaceid(d, 4, focus=:prediction) # Estimate the process model using subspace-based identification","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Since the data used for the system identification had acceleration rather than velocity as output, we multiply the estimated model by the transfer function 1s 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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"s = tf(\"s\")\nP = 1/s * d2c(-Pacc.sys)\nbodeplot(P)","category":"page"},{"location":"examples/tuning_from_data/#Dealing-with-model-uncertainty","page":"Tune a controller using experimental data","title":"Dealing with model uncertainty","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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(iomega):","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"coherenceplot(d)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Since our coherence drops significantly above omega = 130rad/s, we will try to design a controller that yields a complementary sensitivity function T that has low gain above this frequency.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/#Controller-tuning","page":"Tune a controller using experimental data","title":"Controller tuning","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We could take multiple different approaches to tuning the PID controller, a few alternatives are listed here","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Trial and error in simulation or experiment.\nManual loop shaping\nAutomatic loop shaping\nStep-response optimization (example)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Here, we will attempt a manual loop-shaping approach using the function loopshapingPID, and then then compare the result to a pole-placement controller.","category":"page"},{"location":"examples/tuning_from_data/#Manual-loop-shaping","page":"Tune a controller using experimental data","title":"Manual loop shaping","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"K = 10\nTf = 0.4\nTi = 4\nTd = 0.1\nCF = pid(K, Ti, Td; Tf)\nmarginplot(P*CF)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Here, we have selected the proportional gain Klarge 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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The pid function returns the PI controller with the second-order lowpass filter already applied.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Next, we form the closed-loop system G from reference to output an plot a step response","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"G = feedback(P*CF)\nplot(step(G, 50), label=\"Step response\")","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using TrajectoryLimiters\nẋM = 1 # Velocity limit\nẍM = 0.01 # Acceleration limit\nlimiter = TrajectoryLimiter(d.Ts, ẋM, ẍM)\ninputstep, vel, acc = limiter([0; ones(5000)])\ntimevec = 0:d.Ts:50\nplot(step(G, 50), label=\"Step response\")\nplot!(lsim(G, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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) ","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"gangoffourplot(P, CF)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The gang of four indicates that we have a robust tuning, no uncomfortably large peaks appears in either T or S.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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).","category":"page"},{"location":"examples/tuning_from_data/#Pole-placement","page":"Tune a controller using experimental data","title":"Pole placement","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We start by inspecting the pole locations of the open-loop plant","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"pzmap(P)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"As expected, we have 2 resonant pole pairs.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"current_poles = poles(P)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The integrator pole can be placed to achieve a desired bandwidth. Here, we place it in -30rad/s to achieve a faster response than the PID controller achieved.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"desired_poles = -[80, 80, 37, 37, 30];","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We compute the state-feedback gain L using the function place, and also compute an observer gain K using the rule of thumb that the observer poles should be approximately twice as fast as the system poles.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"L = place(P, desired_poles, :c)\nK = place(P, 2*desired_poles, :o)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Cpp = observer_controller(P, L, K)\nGpp = feedback(P*Cpp)\nplot(lsim(Gpp, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black), legend=:bottomright)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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 ω = 130rad/s above which we couldn't trust the model.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"gangoffourplot(P, Cpp)\nvline!(fill(130, 1, 4), label=\"\\$ω = 130\\$\", l=(:dash, :black))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"p1 = current_poles[2]\np2 = current_poles[4]\n\np1_new = abs(p1) * cis(-pi + deg2rad(65)) # Place the pole with the same magnitude, but with an angle of -pi + 65 degrees\np2_new = abs(p2) * cis(-pi + deg2rad(65))\ndesired_poles = [-20, p1_new, conj(p1_new), p2_new, conj(p2_new)]\nL = place(P, desired_poles, :c) |> real\nK = place(P, 2*desired_poles, :o) |> real\nCpp = observer_controller(P, L, K)\nGpp = feedback(P*Cpp)\nf1 = plot(lsim(Gpp, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black), legend=:bottomright)\n\nf2 = gangoffourplot(P, Cpp)\nvline!(fill(130, 1, 4), label=\"\\$ω = 130\\$\", l=(:dash, :black))\nplot(f1, f2, size=(800, 600))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We still have a nice step response using this controller, but this time, we have a rolloff in T that starts around the frequency ω = 130rad/s.","category":"page"},{"location":"examples/tuning_from_data/#C-Code-generation","page":"Tune a controller using experimental data","title":"C-Code generation","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using SymbolicControlSystems\nCdiscrete = c2d(Cpp, d.Ts, :tustin)\nSymbolicControlSystems.ccode(Cdiscrete)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"This produces the following C-code for filtering the error signal through the controller transfer function","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"#include \n#include \n\nvoid transfer_function(double *y, double u) {\n static double x[5] = {0}; // Current state\n double xp[5] = {0}; // Next state\n int i;\n\n // Advance the state xp = Ax + Bu\n xp[0] = (1.2608412916795442*u - 0.35051915762703334*x[0] + 0.0018847792079810998*x[1] - 0.0035104037080211504*x[2] + 0.0022503125347378308*x[3] + 0.00019318421187795658*x[4]);\n xp[1] = (45.346976964169166*u - 49.856146529754966*x[0] + 0.19058339536496746*x[1] + 0.58214123400704609*x[2] - 0.068048140252114517*x[3] - 0.03667586076286556*x[4]);\n xp[2] = (18.14135831274827*u - 19.16237014106056*x[0] - 0.84117137404200237*x[1] + 0.7024229589860792*x[2] + 0.018736385625077446*x[3] - 0.008392059099094502*x[4]);\n xp[3] = (190.59457176680613*u - 161.57645282794124*x[0] - 0.23872534677018914*x[1] + 1.0884789050298469*x[2] + 0.32394494701618637*x[3] + 0.32518305451736074*x[4]);\n xp[4] = (18.392870361917002*u - 0.43306059549357445*x[0] + 0.60377162139631557*x[1] + 0.62662564832184231*x[2] - 0.48738482327867771*x[3] + 0.98218650191968704*x[4]);\n\n // Accumulate the output y = C*x + D*u\n y[0] = (182.81664929547824*u - 63.477219815374006*x[0] + 3.5715419988427302*x[1] + 4.1831558072019464*x[2] - 1.0447833362501759*x[3] + 0.27420732436215378*x[4]);\n\n // Make the predicted state the current state\n for (i=0; i < 5; ++i) {\n x[i] = xp[i];\n }\n}","category":"page"},{"location":"examples/tuning_from_data/#Summary","page":"Tune a controller using experimental data","title":"Summary","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"This tutorial has shown how to follow a workflow that consists of","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Estimate a process model using experimental data.\nDesign 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.\nSimulate the closed-loop system and analyze its robustness properties. Model uncertainty was considered using the coherence function.\nGenerate C-code for one of the controllers.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/zoh/#Analysis-of-sampled-data-systems","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data systems","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"A sampled-data system contains both continuous-time and discrete-time components, such as a continuous-time plant and a discrete-time controller. In this example, we will look at how to analyze such systems using the ControlSystems.jl package. To learn more about the theory of sampled-data systems, consult the reference mentioned at the end of this page.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"First, we analyze the effect of ZoH sampling in continuous time and compare it to the equivalent discrete-time system","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"We will consider a simple second-order system P, which we define in continuous time:","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"using ControlSystems, Plots\ns = tf('s')\nP = tf(0.1, [1, 0.1, 0.1])","category":"page"},{"location":"examples/zoh/#Continuous-to-discrete","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Continuous to discrete","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Next, we discretize this system using the standard c2d function, which uses ZoH sampling by default. We compare the frequency response of the discretized system with the frequency response of the original continuous-time system multiplied by the transfer function of the ZoH operator [CCS]","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Z(s) = dfrac1 - e^-T_s sT_s s","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"[CCS]: Åström, K. J., & Wittenmark, B. (1997). Computer-Controlled Systems: Theory and Design.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Ts = 1 # Sample interval\nZ = (1 - delay(Ts))/(Ts*s) # The transfer function of the ZoH operator\nPd = c2d(P, Ts) # Discrete-time system obtained by ZoH sampling\nPz = P*Z # The continuous-time version of the discrete-time system\n\nwd = exp10.(-2:0.01:log10(2*0.5))\nbodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Pz, wd, lab=\"\\$P(s)Z(s)\\$\")\nbodeplot!(Pd, wd, lab=\"\\$P_d(z)\\$ (ZoH sampling)\", l=:dash)\nvline!([0.5 0.5], l=(:black, :dash), lab=\"Nyquist freq.\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"The step response of Pz = P(s) Z(s) matches the discrete output of P_d(z) delayed by half the sample time","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"resP = step(P, 12)\nresPz = step(Pz, 12)\nresPd = step(Pd, 12)\nplot([resP, resPz, resPd], lab=[\"P\" \"Pz\" \"Pd\"])\n\nt_shift = resPd.t .+ Ts / 2\nplot!(t_shift, resPd.y[:], lab=\"Pd shifted\", m=:o, l=:dash)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"With a piecewise constant input, even if it's not a step, we get the same result","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Tf = 20\nufun = (x,t)->[sin(2pi*floor(t/Ts)*Ts/5)]\nresP = lsim(P, ufun, Tf)\nresPz = lsim(Pz, ufun, 0:0.01:Tf)\nresPd = lsim(Pd, ufun, Tf)\nplot([resP, resPz, resPd], lab=[\"P\" \"Pz\" \"Pd\"])\n\nt_shift = resPd.t .+ Ts / 2\nplot!(t_shift, resPd.y[:], lab=\"Pd shifted\", m=:o)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Tf = 100\nufun = (x,t)->[sin(2pi*t/5)]\nresP = lsim(P, ufun, Tf)\nresPz = lsim(Pz, ufun, 0:0.01:Tf)\nresPd = lsim(Pd, ufun, Tf)\nplot([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)))","category":"page"},{"location":"examples/zoh/#Discrete-to-continuous","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Discrete to continuous","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"wd = exp10.(LinRange(-2, 1, 200))\nPdc = d2c_exact(ss(Pd))\nPc = d2c(Pd)\nbodeplot(Pd, wd, lab=\"\\$P_d(z)\\$\")\nbodeplot!(Pdc, wd, lab=\"\\$P_d(s)\\$ (exact translation)\", l=:dash)\nbodeplot!(Pc, wd, lab=\"\\$P_d(s)\\$ (inverse ZoH sampling)\")\nvline!([0.5 0.5], l=(:black, :dash), lab=\"Nyquist freq.\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/#Time-domain-simulation","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Time-domain simulation","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Convert continuous-time components to discrete time, or\nConvert discrete-time components to continuous time.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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).","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"A = [0.0 0.0 0.0 1.0 0.0 0.0\n 0.0 0.0 0.0 0.0 1.0 0.0\n 0.0 0.0 0.0 0.0 0.0 1.0\n -10.0 0.0 10.0 -0.001 0.0 0.001\n -0.0 -10.0 10.0 -0.0 -0.001 0.001\n 10.0 10.0 -20.0 0.001 0.001 -0.002]\nB = [0, 0, 0, 0.1, 0, 0]\nC = [0.0 0.0 0.0 0.0 0.0 1.0]\nP = minreal(ss(A,B,C,0))\nw = exp10.(LinRange(-2, 2, 1000))\nbodeplot(P, w, lab=\"\\$P(s)\\$\", plotphase=false)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"The discrete-time controller C(z) is a basic PI controller","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Ts = 1\nC = pid(0.01,10; Ts, Tf = 1/100, state_space=true)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"If we choose option 1, we get a discretized plant that has a very poor frequency-response fit to the original continuous-time plant, making frequency-domain analysis difficult","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Pd = c2d(P, Ts)\nLd = Pd*C\nwd = exp10.(-3:0.001:log10(1000*0.05))\nfigb = bodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Pd, wd, lab=\"\\$P(z)\\$ ZoH\")\nbodeplot!(Ld, wd, lab=\"\\$P(z)C(z)\\$\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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).","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Cc = d2c_exact(C)\nLc = P*Z*Cc\n\nbodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Lc, wd, lab=\"\\$P(s)C(s)\\$\", legend=:bottomleft, c=3)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"If we form the closed-loop system from an input disturbance to the output","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"PS = dfracP(s)1 + P(s)C(s)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"PS = feedback(P, Z*Cc) # Use the continuous-time plant and continuous translation of the controller + ZoH operator\nPSd = feedback(Pd, C) # Use the discretized plant and discrete controller\nω = 5.53 # Frequency of input disturbance (rad/s) (Nyquist frequency is π rad/s)\ndisturbance(x, t) = sin(ω*t) # Continuous input disturbance\nplot(lsim(PS, disturbance, 0:0.22:3500), lab=\"Continuous disturbance response\")\nplot!(lsim(PSd, disturbance, 3500), lab=\"Discrete disturbance response\")\nhline!([abs(freqresp(PS, ω)[])], l=(:black, :dash), lab=\"Predicted freq. response amplitude\", legend=:bottomleft, fmt=:png)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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]","category":"page"},{"location":"examples/zoh/#Caveats","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Caveats","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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. \nInterpretation 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]","category":"page"},{"location":"examples/zoh/#References","page":"Analysis of sampled-data (continuous/discrete) systems","title":"References","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Learn more about sampled-data systems and zero-order hold sampling in chapter 7 of the reference below.","category":"page"},{"location":"","page":"Home","title":"Home","text":"

\n\n\"JuliaControl\n\n
\n\nStar\n\n\n

","category":"page"},{"location":"#ControlSystems.jl-Manual","page":"Home","title":"ControlSystems.jl Manual","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = ControlSystems\nconst libpath = haskey(ENV, \"CI\") ? dirname(pathof(ControlSystemsBase)) : \"lib/src\"\nDocTestFilters = [\n r\"StateSpace.+?\\n\"\n r\"HeteroStateSpace.+?\\n\"\n r\"TransferFunction.+?\\n\"\n r\"DelayLtiSystem.+?\\n\"\n r\"┌ Warning: Keyword argument hover.+\\n*.+\\n*\" # remove next line as well\n r\"\\[ Info: Precompiling.+\\n*\"\n]\nnyquistplot(ssrand(1,1,1))","category":"page"},{"location":"","page":"Home","title":"Home","text":"ControlSystems.jl and the rest of the packages in the JuliaControl organization implement solutions for analysis and design of (primarily linear) control systems. If you are new to the Julia programming language, learn more here. If you are familiar with Julia but unfamiliar with the ecosystem for control, learn more under Ecosystem.","category":"page"},{"location":"","page":"Home","title":"Home","text":"This documentation is structured into an introductory section labeled Introductory guide, a section with Examples and a reference section sorted into topics, labeled Functions.","category":"page"},{"location":"#Introductory-guide","page":"Home","title":"Introductory guide","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"man/introduction.md\", \"man/creating_systems.md\", \"man/numerical.md\", \"man/differences.md\"]\nDepth = 1","category":"page"},{"location":"#index_examples","page":"Home","title":"Examples","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"examples/example.md\", \"examples/ilc.md\", \"examples/smith_predictor.md\"]\nDepth = 2","category":"page"},{"location":"#Functions","page":"Home","title":"Functions","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"lib/constructors.md\", \"lib/analysis.md\", \"lib/synthesis.md\", \"lib/timefreqresponse.md\", \"lib/plotting.md\", \"lib/nonlinear.md\"]\nDepth = 1","category":"page"},{"location":"#Ecosystem","page":"Home","title":"Ecosystem","text":"","category":"section"},{"location":"#JuliaControl","page":"Home","title":"JuliaControl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The JuliaControl and surrounding ecosystem contains a few additional packages that may be of interest","category":"page"},{"location":"","page":"Home","title":"Home","text":"RobustAndOptimalControl.jl contains more advanced features for LQG design, robust analysis and synthesis, uncertainty modeling, named systems and an interface to DescriptorSystems.jl.\nSymbolicControlSystems.jl contains basic C-code generation for linear systems.\nControlSystemIdentification.jl is a system-identification toolbox for identification of LTI systems using either time or frequency-domain data. This package can use data to estimate statespace models, transfer-function models and Kalman filters that can be used for control design.\nControlSystemsMTK.jl is an interface between ControlSystems.jl and ModelingToolkit.jl.\nDiscretePIDs.jl contains a reference implementation in Julia of a discrete-time PID controller including set-point weighting, integrator anti-windup, derivative filtering and bumpless transfer.","category":"page"},{"location":"","page":"Home","title":"Home","text":"See also the paper describing the toolbox","category":"page"},{"location":"","page":"Home","title":"Home","text":"Bagge Carlson, F., Fält, M., Heimerson, A., & Troeng, O. (2021). ControlSystems.jl: A Control Toolbox in Julia. In 2021 60th IEEE Conference on Decision and Control (CDC) IEEE Press. https://doi.org/10.1109/CDC45484.2021.9683403","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the introductory Youtube video below, as well as the following Youtube playlist with videos about using Julia for control.","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"#The-wider-Julia-ecosystem-for-control","page":"Home","title":"The wider Julia ecosystem for control","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The following is a list of packages from the wider Julia ecosystem that may be of interest.","category":"page"},{"location":"","page":"Home","title":"Home","text":"DescriptorSystems.jl contains types that represent statespace systems on descriptor form, i.e., with a mass matrix. These systems can represent linear DAE systems and non-proper systems.\nTrajectoryOptimization.jl is one of the more developed packages for open-loop optimal control and trajectory optimization in Julia.\nLowLevelParticleFilters.jl is a library for state estimation using particle filters and Kalman filters of different flavors.\nModelingToolkit.jl is an acausal modeling tool, similar in spirit to Modelica. A video showing ControlSystems and ModelingToolkit together is available here. ControlSystemsMTK.jl exists to ease the use of these two packages together.\nJuliaSimControl.jl is a product that builds upon the JuliaControl ecosystem and ModelingToolkit, providing additional nonlinear and robust control methods.\nFaultDetectionTools.jl contains utilities and observers for online fault detection.\nReachabilityAnalysis.jl is a package for reachability analysis. This can be used to verify stability and safety properties of linear and nonlinear systems.\nMatrixEquations.jl contains solvers for many different matrix equations common in control. ControlSystems.jl makes use of this package for solving Riccati and Lyapunov equations.\nJuMP.jl is a modeling language for optimization, similar to YALMIP. JuMP is suitable for solving LMI/SDP problems as well as advanced linear MPC problems. \nSumOfSquares.jl is a package for sum-of-squares programming that builds on top of JuMP. Their documentation contains examples of Lyapunov-function search and nonlinear synthesis.\nMonteCarloMeasurements.jl is a library for working with parametric uncertainty. An example using ControlSystems is available here.\nDifferentialEquations.jl is the home of the SciML ecosystem that provides solvers for scientific problems. ControlSystems.jl uses these solvers for continuous-time simulations.\nDojo.jl is a differentiable robot simulator.\nStaticCompiler.jl contains tools for compiling small binaries of Julia programs.\nJuliaPOMDP is a Julia ecosystem for reinforcement learning. \nJuliaReinforcementLearning is another Julia ecosystem for reinforcement learning. ","category":"page"},{"location":"examples/delay_systems/#Properties-of-delay-systems","page":"Properties of delay systems","title":"Properties of delay systems","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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. ","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"using ControlSystemsBase, Plots\nw = exp10.(LinRange(-2, 2, 2000))\nP = tf(1, [1, 1]) * delay(2) # Plant with delay on the input\nnyquistplot(P, w)","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"When forming a feedback interconnection, making the delay appear in the closed loop, we may get gain ripple:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"bodeplot(feedback(P), w)","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"If the system with delay has a direct feedthrough term, step responses may show repeated steps at integer multiples of the delay:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"using ControlSystems # Load full control systems to get simulation functionality\nP = tf([1, 1], [1, 0])*delay(1)\nplot(step(feedback(P, 0.5), 0:0.001:20))","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"Indeed, if the system has a non-zero feedthrough, the output will contain a delayed step attenuated by the feedthrough term, in this case","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"ss(feedback(tf([1, 1], [1, 0]))).D[]","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"the steps will thus in this case decay exponentially with decay rate 0.5. ","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"For a more advanced example using time delays, see the Smith predictor tutorial.","category":"page"},{"location":"examples/delay_systems/#Simulation-of-time-delay-systems","page":"Properties of delay systems","title":"Simulation of time-delay systems","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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.","category":"page"},{"location":"examples/delay_systems/#Estimation-of-delay","page":"Properties of delay systems","title":"Estimation of delay","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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.","category":"page"},{"location":"examples/delay_systems/#Approximation-and-discretization-of-delays","page":"Properties of delay systems","title":"Approximation and discretization of delays","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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).","category":"page"}] +[{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"#using ChainRules, ForwardDiff, ChainRulesCore, LinearAlgebra\n# @ForwardDiff_frule LinearAlgebra.exp!(x1::AbstractMatrix{<:ForwardDiff.Dual})\n\n# Replace by ForwardDiffChainRules when https://github.com/ThummeTo/ForwardDiffChainRules.jl/pull/16 is merged\n#function LinearAlgebra.exp!(A::AbstractMatrix{<:ForwardDiff.Dual})\n# Av = ForwardDiff.value.(A)\n# J = reduce(vcat, transpose.(ForwardDiff.partials.(A)))\n# CS = length(ForwardDiff.partials(A[1,1]))\n# dself = NoTangent();\n# cAv = copy(Av)\n# eA, newJ1 = ChainRules.frule((dself, reshape(J[:,1], size(A))), LinearAlgebra.exp!, cAv)\n#\n# newJt = ntuple(Val(CS - 1)) do i\n# xpartialsi = reshape(J[:, i+1], size(A))\n# cAv .= Av\n# _, ypartialsi = ChainRulesCore.frule((dself, xpartialsi), LinearAlgebra.exp!, cAv)\n# ypartialsi\n# end\n# newJ = hcat(vec(newJ1), vec.(newJt)...)\n# T = ForwardDiff.tagtype(eltype(A))\n# flaty = ForwardDiff.Dual{T}.(\n# eA, reshape(ForwardDiff.Partials.(NTuple{CS}.(eachrow(newJ))), size(A)),\n# )\n#end","category":"page"},{"location":"examples/automatic_differentiation/#Automatic-Differentiation","page":"Automatic differentiation","title":"Automatic Differentiation","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"In Julia, it is often possible to automatically compute derivatives, gradients, Jacobians and Hessians of arbitrary Julia functions with precision matching the machine precision, that is, without the numerical inaccuracies incurred by finite-difference approximations.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Two general methods for automatic differentiation are available: forward and reverse mode. Forward mode is algorithmically more favorable for functions with few inputs but many outputs, while reverse mode is more efficient for functions with many parameters but few outputs (like in deep learning). In Julia, forward-mode AD is provided by the package ForwardDiff.jl, while reverse-mode AD is provided by several different packages, such as Zygote.jl and ReverseDiff.jl. Forward-mode AD generally has a lower overhead than reverse-mode AD, so for functions of a small number of parameters, say, less than about 10 or 100, forward-mode is usually most efficient. ForwardDiff.jl also has support for differentiating most of the Julia language, making the probability of success higher than for other packages, why we generally recommend trying ForwardDiff.jl first.","category":"page"},{"location":"examples/automatic_differentiation/#Linearizing-nonlinear-dynamics","page":"Automatic differentiation","title":"Linearizing nonlinear dynamics","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Nonlinear dynamics on the form","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"beginaligned\ndot x = f(x u) \ny = g(x u)\nendaligned","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"is easily linearized in the point x_0 u_0 using ForwardDiff.jl:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ControlSystemsBase, ForwardDiff\n\n\"An example of nonlinear dynamics\"\nfunction f(x, u)\n x1, x2 = x\n u1, u2 = u\n [x2; u1*x1 + u2*x2]\nend\n\nx0 = [1.0, 0.0] # Operating point to linearize around\nu0 = [0.0, 1.0]\n\nA = ForwardDiff.jacobian(x -> f(x, u0), x0)\nB = ForwardDiff.jacobian(u -> f(x0, u), u0)\n\n\"An example of a nonlinear output (measurement) function\"\nfunction g(x, u)\n y = [x[1] + 0.1x[1]*u[2]; x[2]]\nend\n\nC = ForwardDiff.jacobian(x -> g(x, u0), x0)\nD = ForwardDiff.jacobian(u -> g(x0, u), u0)\n\nlinear_sys = ss(A, B, C, D)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The example above linearizes f in the point x_0 u_0 to obtain the linear statespace matrices A and B, and linearizes g to obtain the linear output matrices C and D. Instead of manually calling ForwardDiff.jl to linearize the dynamics, the user may call the function ControlSystemsBase.linearize which includes the necessary calls to ForwardDiff.jl.","category":"page"},{"location":"examples/automatic_differentiation/#Optimization-based-tuning–PID-controller","page":"Automatic differentiation","title":"Optimization-based tuning–PID controller","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"This example will demonstrate simple usage of AD using ForwardDiff.jl for optimization-based auto tuning of a PID controller.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The system we will control is a double-mass system, in which two masses (or inertias) are connected by a flexible transmission. ","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"We start by defining the system model and an initial guess for the PID controller parameters","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ControlSystemsBase, ForwardDiff, Plots\n\nP = DemoSystems.double_mass_model()\n\nbodeplot(P, title=\"Bode plot of Double-mass system \\$P(s)\\$\")","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Ω = exp10.(-2:0.03:3)\nkp,ki,kd,Tf = 1, 0.1, 0.1, 0.01 # controller parameters\n\nC = pid(kp, ki, kd; Tf, form=:parallel, state_space=true) # Construct a PID controller with filter\nG = feedback(P*C) # Closed-loop system\nS = 1/(1 + P*C) # Sensitivity function\nGd = c2d(G, 0.1) # Discretize the system\nres = step(Gd,15) # Step-response\n\nmag = bodev(S, Ω)[1]\nplot(res, title=\"Time response\", layout = (1,3), legend=:bottomright)\nplot!(Ω, mag, title=\"Sensitivity function\", xscale=:log10, yscale=:log10, subplot=2, legend=:bottomright, ylims=(3e-2, Inf))\nMs, _ = hinfnorm(S)\nhline!([Ms], l=(:black, :dash), subplot=2, lab=\"\\$M_S = \\$ $(round(Ms, digits=3))\", sp=2)\nnyquistplot!(P*C, Ω, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), size=(1200,400))","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The initial controller C achieves a maximum peak of the sensitivity function of M_S = 13 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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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).","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using Optimization, Statistics, LinearAlgebra\nusing Ipopt, OptimizationMOI; MOI = OptimizationMOI.MOI\n\nfunction plot_optimized(P, params, res, systems)\n fig = plot(layout=(1,3), size=(1200,400), bottommargin=2Plots.mm)\n for (i,params) = enumerate((params, res))\n ls = (i == 1 ? :dash : :solid)\n lab = (i==1 ? \"Initial\" : \"Optimized\")\n C, G = systems(params, P)\n\t\tr1, r2 = sim(G)\n mag = reshape(bode(G, Ω)[1], 4, :)'[:, [1, 2, 4]]\n plot!([r1, r2]; title=\"Time response\", subplot=1,\n lab = lab .* [\" \\$r → e\\$\" \" \\$d → e\\$\"], legend=:bottomright, ls,\n fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3])\n plot!(Ω, mag; title=\"Sensitivity functions \\$S(s), CS(s), T(s)\\$\",\n xscale=:log10, yscale=:log10, subplot=2, lab, ls,\n legend=:bottomright, fillalpha=0.05, linealpha=0.8, c=[1 2 3], linewidth=i)\n nyquistplot!(P*C, Ω; Ms_circles=Msc, sp=3, ylims=(-2.1,1.1), xlims=(-2.1,1.2), lab, seriescolor=i, ls)\n end\n hline!([Msc], l=:dashdot, c=1, subplot=2, lab=\"Constraint\", ylims=(9e-2, Inf))\n fig\nend\n\n\"A helper function that creates a PID controller and closed-loop transfer functions\"\nfunction systemspid(params, P)\n kp,ki,kd,Tf = params # We optimize parameters in\n C = pid(kp, ki, kd; form=:parallel, Tf, state_space=true)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\n\"A helper function that simulates the closed-loop system\"\nfunction sim(G)\n Gd = c2d(G, 0.1, :tustin) # Discretize the system\n res1 = step(Gd[1, 1], 0:0.1:15) # Simulate S\n res2 = step(Gd[1, 2], 0:0.1:15) # Simulate PS\n res1, res2\nend\n\n\"The cost function to optimize\"\nfunction cost(params::AbstractVector{T}, (P, systems)) where T\n CSweight = 0.001 # Noise amplification penalty\n C, G = systems(params, P)\n res1, res2 = sim(G)\n R, _ = bodev(G[2, 1], Ω; unwrap=false)\n CS = sum(R .*= Ω) # frequency-weighted noise sensitivity\n perf = mean(abs2, res1.y .*= res1.t') + mean(abs2, res2.y .*= res2.t')\n return perf + CSweight * CS # Blend all objectives together\nend\n\n\"The sensitivity constraint to enforce robustness\"\nfunction constraints(res, params::AbstractVector{T}, (P, systems)) where T\n C, G = systems(params, P)\n S, _ = bodev(G[1, 1], Ω; unwrap=false)\n res .= maximum(S) # max sensitivity\n nothing\nend\n\nMsc = 1.3 # Constraint on Ms\n\nparams = [kp, ki, kd, 0.01] # Initial guess for parameters\n\nsolver = Ipopt.Optimizer()\nMOI.set(solver, MOI.RawOptimizerAttribute(\"print_level\"), 0)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"max_iter\"), 200)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_tol\"), 1e-1)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_constr_viol_tol\"), 1e-2)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"acceptable_iter\"), 5)\nMOI.set(solver, MOI.RawOptimizerAttribute(\"hessian_approximation\"), \"limited-memory\")\n\nfopt = OptimizationFunction(cost, Optimization.AutoForwardDiff(); cons=constraints)\n\nprob = OptimizationProblem(fopt, params, (P, systemspid);\n lb = fill(-10.0, length(params)),\n ub = fill(10.0, length(params)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\n\nres = solve(prob, solver)\nplot_optimized(P, params, res.u, systemspid)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/#Optimization-based-tuning–LQG-controller","page":"Automatic differentiation","title":"Optimization-based tuning–LQG controller","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"A^TX + XA - XBR^-1B^T X + Q = 0","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"When ImplicitDifferentiation is loaded, differentiable versions of lqr and kalman that make use of the \"implicit function\" are automatically loaded.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using ImplicitDifferentiation, ComponentArrays # Both these packages are required to load the implicit differentiation rules","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"function triangular(x)\n m = length(x)\n n = round(Int, sqrt(2m-1))\n T = zeros(eltype(x), n, n)\n k = 1\n for i = 1:n, j = i:n\n T[i,j] = x[k]\n k += 1\n end\n T\nend\ninvtriangular(T) = [T[i,j] for i = 1:size(T,1) for j = i:size(T,1)]\n\nfunction systemslqr(params::AbstractVector{T}, P) where T\n n2 = length(params) ÷ 2\n Qchol = triangular(params[1:n2])\n Rchol = triangular(params[n2+1:2n2])\n Q = Qchol'Qchol\n R = Rchol'Rchol\n L = lqr(P, Q, one(T)*I(1)) # It's important that the last matrix has the correct type\n K = kalman(P, R, one(T)*I(1))\n C = observer_controller(P, L, K)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\nQ0 = diagm([1.0, 1, 1, 1]) # Initial guess LQR state penalty\nR0 = diagm([1.0, 1, 1, 1]) # Initial guess Kalman state covariance\nparams2 = [invtriangular(cholesky(Q0).U); invtriangular(cholesky(R0).U)]\n\nprob2 = OptimizationProblem(fopt, params2, (P, systemslqr);\n lb = fill(-10.0, length(params2)),\n ub = fill(10.0, length(params2)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\n\nres2 = solve(prob2, solver)\nplot_optimized(P, params2, res2.u, systemslqr)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/#Robustness-analysis","page":"Automatic differentiation","title":"Robustness analysis","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"using MonteCarloMeasurements\nPu = DemoSystems.double_mass_model(k = Particles(32, Uniform(80, 120))) # Create a model with uncertainty in spring stiffness k ~ U(80, 120)\nunsafe_comparisons(true) # For the Bode plot to work\n\nC,_ = systemslqr(res2.u, P) # Get the controller assuming P without uncertainty\nGu = extended_gangoffour(Pu, C) # Form the gang-of-four with uncertainty\nw = exp10.(LinRange(-1.5, 2, 500))\nbodeplot(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\"])\nhline!([Msc], l=:dashdot, c=1, lab=\"Constraint\", ylims=(9e-2, Inf))","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Gd = c2d(Gu, 0.05) # Discretize the system\nr1 = step(Gd[1,1], 0:0.05:15) # Simulate S\nr2 = step(Gd[1,2], 0:0.05:15) # Simulate PS\nplot([r1, r2]; title=\"Time response\",\n lab = [\" \\$r → e\\$\" \" \\$d → e\\$\"], legend=:bottomright,\n fillalpha=0.05, linealpha=0.8, seriestype=:path, c=[1 3], ri=false, N=32)","category":"page"},{"location":"examples/automatic_differentiation/#Parameterizing-the-controller-using-feedback-gains","page":"Automatic differentiation","title":"Parameterizing the controller using feedback gains","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.)","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"function systems_sf(params::AbstractVector{T}, P) where T\n n2 = length(params) ÷ 2\n L = params[1:n2]'\n K = params[n2+1:2n2, 1:1]\n C = observer_controller(P, L, K)\n G = extended_gangoffour(P, C) # [S PS; CS T]\n C, G\nend\n\nL0 = lqr(P, Q0, I) # Initial guess\nK0 = kalman(P, R0, I)\nparams3 = [vec(L0); vec(K0)]\nprob3 = OptimizationProblem(fopt, params3, (P, systems_sf);\n lb = fill(-15.0, length(params3)),\n ub = fill(15.0, length(params3)),\n ucons = fill(Msc, 1),\n lcons = fill(-Inf, 1),\n)\nres3 = solve(prob3, solver)\nplot_optimized(P, params3, res3.u, systems_sf)","category":"page"},{"location":"examples/automatic_differentiation/#Known-limitations","page":"Automatic differentiation","title":"Known limitations","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"The following issues are currently known to exist when using AD through ControlSystems.jl:","category":"page"},{"location":"examples/automatic_differentiation/#ForwardDiff","page":"Automatic differentiation","title":"ForwardDiff","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"ForwardDiff.jl works for a lot of workflows without any intervention required from the user. The following known limitations exist:","category":"page"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"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.\nThe 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.\nhinfnorm 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.\nare, 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)\nThe 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.\nAn 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.","category":"page"},{"location":"examples/automatic_differentiation/#Reverse-mode-AD","page":"Automatic differentiation","title":"Reverse-mode AD","text":"","category":"section"},{"location":"examples/automatic_differentiation/","page":"Automatic differentiation","title":"Automatic differentiation","text":"Zygote does not work very well at all, due to\nFrequent use of mutation for performance\nTry/catch blocks","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"DocTestSetup = quote\n using ControlSystems, Plots\n plotsDir = joinpath(dirname(pathof(ControlSystems)), \"..\", \"docs\", \"build\", \"plots\")\n mkpath(plotsDir)\n nyquistplot(ssrand(1,1,1)) # to get the warning for hover already here\n save_docs_plot(name) = (Plots.savefig(joinpath(plotsDir,name)); nothing)\n save_docs_plot(p, name) = (Plots.savefig(p, joinpath(plotsDir,name)); nothing)\nend","category":"page"},{"location":"examples/example/#Examples","page":"Design","title":"Examples","text":"","category":"section"},{"location":"examples/example/#LQR-design","page":"Design","title":"LQR design","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"The infinite-horizon LQR controller is derived as the linear state-feedback u = -Lx that minimizes the following quadratic cost function","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"L = textargmin_L int_0^infty x^T Q x + u^T R u dt","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"where x is the state vector and u is the input vector.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"The example below performs a simple LQR design for a double integrator in discrete time using the function lqr. In this example, we will use the method of lsim that accepts a function u(x t) as input. This allows us to easily simulate the system both control input and a disturbance input. For more advanced LQR and LQG design, see the LQGProblem type in RobustAndOptimalControl.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase\nusing LinearAlgebra # For identity matrix I\nusing Plots\n\n# Create system\nTs = 0.1\nA = [1 Ts; 0 1]\nB = [0; 1]\nC = [1 0]\nsys = ss(A,B,C,0,Ts)\n\n# Design controller\nQ = I # Weighting matrix for state\nR = I # Weighting matrix for input\nL = lqr(Discrete,A,B,Q,R) # lqr(sys,Q,R) can also be used\n\n# Simulation\nu(x,t) = -L*x .+ 1.5(t>=2.5) # Form control law (u is a function of t and x), a constant input disturbance is affecting the system from t≧2.5\nt = 0:Ts:5 # Time vector\nx0 = [1,0] # Initial condition\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nsave_docs_plot(\"lqrplot.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: )","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"To design an LQG controller (LQR with a Kalman filter), see the functions","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"kalman\nobserver_controller\nLQGProblem type in RobustAndOptimalControl.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"See also the following tutorial video on LQR and LQG design","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/#PID-design-functions","page":"Design","title":"PID design functions","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"A basic PID controller can be constructed using the constructor pid. In ControlSystems.jl, we often refer to three different formulations of the PID controller, which are defined as","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Standard form: K_p(1 + frac1T_i s + T_ds)\nSeries form: K_c(1 + frac1τ_i s)(τ_d s + 1)\nParallel form: K_p + fracK_is + K_d s","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Most functions that construct PID controllers allow the user to select which form to use.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"A tutorial on PID design is available here:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"The following examples show basic workflows for designing PI/PID controllers. ","category":"page"},{"location":"examples/example/#PI-loop-shaping-example","page":"Design","title":"PI loop shaping example","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"By plotting the gang of four under unit feedback for the process","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"P(s) = dfrac1(s + 1)^4","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,1])^4\ngangoffourplot(P, tf(1))","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,1])^4\nωp = 0.8\nC,kp,ki,fig = loopshapingPI(P,ωp,phasemargin=60,form=:parallel, doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"ωp = 2\nC60,kp,ki,fig = loopshapingPI(P,ωp,rl=1,phasemargin=60,form=:standard,doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/#PID-loop-shaping","page":"Design","title":"PID loop shaping","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"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 = dfracPC1 + 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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase, Plots\nP = tf(1, [1,0,0]) # A double integrator\nMt = 1.3 # Maximum magnitude of complementary sensitivity\nϕt = 75 # Angle of tangent point\nω = 1 # Frequency at which the specification holds\nC, kp, ki, kd, fig = loopshapingPID(P, ω; Mt, ϕt, doplot=true)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Tf = 1/20ω\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, Tf)\nfig","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"As we can see, the addition of the filter increases the high-frequency roll-off in both T and CS, which is typically desirable.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"To get better control over the filter, it can be pre-designed and supplied to loopshapingPID with the keyword argument F:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"F = tf(1, [Tf^2, 2*Tf/sqrt(2), 1])\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt, doplot=true, F)","category":"page"},{"location":"examples/example/#Advanced-pole-zero-placement","page":"Design","title":"Advanced pole-zero placement","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"A video tutorial on pole placement is available here:","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"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.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Define the process","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"ζ = 0.2\nω = 1\n\nB = [1]\nA = [1, 2ζ*ω, ω^2]\nP = tf(B,A)","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Define the desired closed-loop response, calculate the controller polynomials and simulate the closed-loop system. The design utilizes an observer poles twice as fast as the closed-loop poles. An additional observer pole is added in order to get a casual controller when an integrator is added to the controller.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystems\nimport DSP: conv\n# Control design\nζ0 = 0.7\nω0 = 2\nAm = [1, 2ζ0*ω0, ω0^2]\nAo = conv(2Am, [1/2, 1]) # Observer polynomial, add extra pole due to the integrator\nAR = [1,0] # Force the controller to contain an integrator ( 1/(s+0) )\n\nB⁺ = [1] # The process numerator polynomial can be facored as B = B⁺B⁻ where B⁻ contains the zeros we do not want to cancel (non-minimum phase and poorly damped zeros)\nB⁻ = [1]\nBm = conv(B⁺, B⁻) # In this case, keep the entire numerator polynomial of the process\n\nR,S,T = rstc(B⁺,B⁻,A,Bm,Am,Ao,AR) # Calculate the 2-DOF controller polynomials\n\nGcl = tf(conv(B,T),zpconv(A,R,B,S)) # Form the closed loop polynomial from reference to output, the closed-loop characteristic polynomial is AR + BS, the function zpconv takes care of the polynomial multiplication and makes sure the coefficient vectors are of equal length\n\nplot(step(P, 20))\nplot!(step(Gcl, 20)) # Visualize the open and closed loop responses.\nsave_docs_plot(\"ppstepplot.svg\") # hide\ngangoffourplot(P, tf(-S,R)) # Plot the gang of four to check that all transfer functions are OK\nsave_docs_plot(\"ppgofplot.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/#Stability-boundary-for-PID-controllers","page":"Design","title":"Stability boundary for PID controllers","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"The stability boundary, i.e., the surface of PID parameters where the transfer function P(s)C(s) equals -1, can be plotted with the command stabregionPID. The process can be given in function form or as a regular LTIsystem.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"P1 = s -> exp(-sqrt(s))\ndoplot = true\nform = :parallel\nkp, ki, f1 = stabregionPID(P1,exp10.(range(-5, stop=1, length=1000)); doplot, form); f1\nP2 = s -> 100*(s+6).^2. /(s.*(s+1).^2. *(s+50).^2)\nkp, ki, f2 = stabregionPID(P2,exp10.(range(-5, stop=2, length=1000)); doplot, form); f2\nP3 = tf(1,[1,1])^4\nkp, ki, f3 = stabregionPID(P3,exp10.(range(-5, stop=0, length=1000)); doplot, form); f3\n\nsave_docs_plot(f1, \"stab1.svg\") # hide\nsave_docs_plot(f2, \"stab2.svg\") # hide\nsave_docs_plot(f3, \"stab3.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: ) (Image: )","category":"page"},{"location":"examples/example/#PID-plots","page":"Design","title":"PID plots","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"This example utilizes the function pidplots, which accepts vectors of PID-parameters and produces relevant plots. The task is to take a system with bandwidth 1 rad/s and produce a closed-loop system with bandwidth 0.1 rad/s. If one is not careful and proceed with pole placement, one easily get a system with very poor robustness.","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"using ControlSystemsBase\nP = tf([1.], [1., 1])\n\nζ = 0.5 # Desired damping\nws = exp10.(range(-1, stop=2, length=8)) # A vector of closed-loop bandwidths\nkp = 2*ζ*ws .- 1 # Simple pole placement with PI given the closed-loop bandwidth, the poles are placed in a butterworth pattern\nki = ws.^2\n\nω = exp10.(range(-3, stop = 2, length = 500))\npidplots(\n P,\n :nyquist;\n params_p = kp,\n params_i = ki,\n ω = ω,\n ylims = (-2, 2),\n xlims = (-3, 3),\n form = :parallel,\n)\nsave_docs_plot(\"pidplotsnyquist1.svg\") # hide\npidplots(P, :gof; params_p = kp, params_i = ki, ω = ω, legend = false, form=:parallel, legendfontsize=6, size=(1000, 1000))\n# You can also request both Nyquist and Gang-of-four plots (more plots are available, see ?pidplots ):\n# pidplots(P,:nyquist,:gof;kps=kp,kis=ki,ω=ω);\nsave_docs_plot(\"pidplotsgof1.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"Now try a different strategy, where we have specified a gain crossover frequency of 0.1 rad/s","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"kp = range(-1, stop=1, length=8) #\nki = sqrt.(1 .- kp.^2)/10\n\npidplots(P,:nyquist,;params_p=kp,params_i=ki,ylims=(-1,1),xlims=(-1.5,1.5), form=:parallel)\nsave_docs_plot(\"pidplotsnyquist2.svg\") # hide\npidplots(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))\nsave_docs_plot(\"pidplotsgof2.svg\"); # hide","category":"page"},{"location":"examples/example/","page":"Design","title":"Design","text":"(Image: ) (Image: )","category":"page"},{"location":"examples/example/#Further-examples","page":"Design","title":"Further examples","text":"","category":"section"},{"location":"examples/example/","page":"Design","title":"Design","text":"See the examples folder as well as the notebooks in ControlExamples.jl.\nSee also the paper introducing the toolbox with supplementary material.\nSee the docs for RobustAndOptimalControl.jl for additional examples.","category":"page"},{"location":"man/creating_systems/#Creating-Systems","page":"Creating Systems","title":"Creating Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This page illustrates how to create system models such as transfer functions and statespace models. This topic is also treated in the introductory video below:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/#Transfer-Functions","page":"Creating Systems","title":"Transfer Functions","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"DocTestSetup = quote\n using ControlSystems\nend","category":"page"},{"location":"man/creating_systems/#tf-Rational-Representation","page":"Creating Systems","title":"tf - Rational Representation","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The basic syntax for creating a transfer function is tf","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf(num, den) # Continuous-time system\ntf(num, den, Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"where num and den are the polynomial coefficients of the numerator and denominator of the polynomial and Ts, if provided, is the sample time for a discrete-time system.","category":"page"},{"location":"man/creating_systems/#Example:","page":"Creating Systems","title":"Example:","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf([1.0],[1,2,1])\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0\n-------------------\n1.0s^2 + 2.0s + 1.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer functions created using this method will be of type TransferFunction{SisoRational}. For more general expressions, it is often more convenient to define s = tf(\"s\"):","category":"page"},{"location":"man/creating_systems/#Example:-2","page":"Creating Systems","title":"Example:","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"julia> s = tf(\"s\")\n\nTransferFunction{Continuous,ControlSystems.SisoRational{Int64}}\ns\n-\n1\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This allows us to use s to define transfer-functions:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"julia> (s-1)*(s^2 + s + 1)/(s^2 + 3s + 2)/(s+1)\n\nTransferFunction{Continuous,ControlSystems.SisoRational{Int64}}\n s^3 - 1\n---------------------\ns^3 + 4*s^2 + 5*s + 2\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/#zpk-Pole-Zero-Gain-Representation","page":"Creating Systems","title":"zpk - Pole-Zero-Gain Representation","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sometimes it's better to represent the transfer function by its poles, zeros and gain, this can be done using the function zpk","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"zpk(zeros, poles, gain) # Continuous-time system\nzpk(zeros, poles, gain, Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"where zeros and poles are Vectors of the zeros and poles for the system and gain is a gain coefficient.","category":"page"},{"location":"man/creating_systems/#Example","page":"Creating Systems","title":"Example","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"zpk([-1.0,1], [-5, -10], 2)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoZpk{Float64, Float64}}\n (1.0s + 1.0)(1.0s - 1.0)\n2.0-------------------------\n (1.0s + 5.0)(1.0s + 10.0)\n\nContinuous-time transfer function model","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer functions created using this method will be of type TransferFunction{SisoZpk}.","category":"page"},{"location":"man/creating_systems/#State-Space-Systems","page":"Creating Systems","title":"State-Space Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A state-space system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginaligned\ndotx = Ax + Bu \ny = Cx + Du\nendaligned","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"in continuous time, or","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginaligned\nx_t+T_s = Ax_t + Bu_t \ny_t = Cx_t + Du_t\nendaligned","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"in discrete time, is created using","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"ss(A,B,C,D) # Continuous-time system\nss(A,B,C,D,Ts) # Discrete-time system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"and they behave similarly to transfer functions.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The ss constructor allows you to","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Pass 0 instead of a D matrix, and an appropriately sized zero matrix is created automatically.\nPass I instead of a C matrix, and an appropriately sized identity matrix is created automatically. The UniformScaling operator I lives in the LinearAlgebra standard library which must be loaded first.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"State-space systems with heterogeneous matrix types are also available, which can be used to create systems with static or sized matrices, e.g.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase, StaticArrays\nsys = ss([-5 0; 0 -5],[2; 2],[3 3],[0])\nHeteroStateSpace(sys, to_sized)\nHeteroStateSpace(sys, to_static)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Notice the different matrix types used.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"To associate names with states, inputs and outputs, see named_ss from RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/#Converting-between-types","page":"Creating Systems","title":"Converting between types","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"It is sometime useful to convert one representation to another. This is possible using the constructors tf, zpk, ss, for example","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"tf(zpk([-1], [1], 2, 0.1))\n\n# output\n\nTransferFunction{Discrete{Float64}, ControlSystemsBase.SisoRational{Int64}}\n2z + 2\n------\nz - 1\n\nSample Time: 0.1 (seconds)\nDiscrete-time transfer function model","category":"page"},{"location":"man/creating_systems/#Converting-between-continuous-and-discrete-time","page":"Creating Systems","title":"Converting between continuous and discrete time","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A continuous-time system represents differential equations or a transfer function in the Laplace domain, while a discrete-time system represents difference equations or a transfer function in the Z-domain.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The functions c2d and d2c implement sampling/discretization of continuous-time systems and the inverse mapping from discrete-time to continuous-time systems. ","category":"page"},{"location":"man/creating_systems/#Delay-Systems","page":"Creating Systems","title":"Delay Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The constructor delay creates a pure delay, which may be connected to a system by multiplication:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"delay(1.2) # Pure delay or 1.2s\ntf(1, [1, 1])*delay(1.2) # Input delay\ndelay(1.2)*tf(1, [1, 1]) # Output delay","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Delayed systems can also be created using","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"s = tf(\"s\")\nL = 1.2 # Delay time\ntf(1, [1, 1]) * exp(-L*s)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Padé approximations of delays can be created using pade. Models with delays can be discretized using c2d, currently, only delays that are integer multiples of the sample time are supported. Pure fractional delays can be approximately discretized using the function thiran.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A tutorial on delay systems is available here:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/#Nonlinear-Systems","page":"Creating Systems","title":"Nonlinear Systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See Nonlinear functionality.","category":"page"},{"location":"man/creating_systems/#Simplifying-systems","page":"Creating Systems","title":"Simplifying systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"A statespace system with a non-minimal realization, or a transfer function with overlapping zeros and poles, may be simplified using the function minreal. Systems that are structurally singular, i.e., that contains outputs that can not be reached from the inputs based on analysis of the structure of the zeros in the system matrices only, can be simplified with the function sminreal.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Examples:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nG = tf([1, 1], [1, 1])\nminreal(G) # Performs pole-zero cancellation\n\nP = tf(1, [1, 1]) |> ss\nG = P / (1 + P) # this creates a non-minimal realization, use feedback(P) instead\nfeedback(P) # Creates a minimal realization directly\nGmin = minreal(G) # this simplifies the realization to a minimal realization\nnorm(Gmin - feedback(P), Inf) # No difference\nbodeplot([G, Gmin, feedback(P)]) # They are all identical","category":"page"},{"location":"man/creating_systems/#Multiplying-systems","page":"Creating Systems","title":"Multiplying systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Two systems can be connected in series by multiplication","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nP1 = ss(-1,1,1,0)\nP2 = ss(-2,1,1,0)\nP2*P1","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P2 above).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"If the input dimension of P2 does not match the output dimension of P1, an error is thrown. If one of the systems is SISO and the other is MIMO, broadcasted multiplication will expand the SISO system to match the input or output dimension of the MIMO system, e.g.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Pmimo = ssrand(2,2,1)\nPsiso = ss(-2,1,1,0)\n# Psiso * Pmimo # error\nPsiso .* Pmimo ≈ [Psiso 0; 0 Psiso] * Pmimo # Broadcasted multiplication expands SISO into diagonal system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Broadcasted multiplication between a system and an array is only allowed for diagonal arrays","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using LinearAlgebra\nPsiso .* I(2)","category":"page"},{"location":"man/creating_systems/#Adding-systems","page":"Creating Systems","title":"Adding systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Two systems can be connected in parallel by addition","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P12 = P1 + P2","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The state of the resulting system is the concatenation of the states of the two systems, starting with the left/first operand (P1 above).","category":"page"},{"location":"man/creating_systems/#MIMO-systems-and-arrays-of-systems","page":"Creating Systems","title":"MIMO systems and arrays of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Concatenation of systems creates MIMO systems, which is different from an array of systems. For example","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using ControlSystemsBase\nP = ss(-1,1,1,0)\nP_MIMO = [P 2P]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"is a 1×2 MISO system, not a 1×2 array.","category":"page"},{"location":"man/creating_systems/#From-SISO-to-MIMO","page":"Creating Systems","title":"From SISO to MIMO","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"SISO systems do not multiply MIMO systems directly, i.e.,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using Test\nsiso = ss(-1,1,1,0)\nmimo = ssrand(2,2,2)\n@test_throws DimensionMismatch siso * mimo","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"To multiply siso with each output channel of mimo in the example above, use broadcasting:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"siso .* mimo","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This is equivalent to first expanding the SISO system into a diagonal system","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using LinearAlgebra\n(siso .* I(2)) * mimo","category":"page"},{"location":"man/creating_systems/#Converting-an-array-of-systems-to-a-MIMO-system","page":"Creating Systems","title":"Converting an array of systems to a MIMO system","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Diagonal MIMO systems can be created from a vector of systems using append","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P1 = ssrand(1,1,1)\nP2 = ssrand(1,1,1)\nappend(P1, P2)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"More general arrays of systems can be converted to a MIMO system using array2mimo.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"sys_array = fill(P, 2, 2) # Creates an array of systems\nmimo_sys = array2mimo(sys_array)","category":"page"},{"location":"man/creating_systems/#Converting-MIMO-system-to-an-array-of-systems","page":"Creating Systems","title":"Converting MIMO system to an array of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This conversion is not explicitly supported, but is easy enough to accomplish with standard Julia code, for example:","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P = ssrand(2,3,1) # A random 2×3 MIMO system\nsys_array = getindex.(Ref(P), 1:P.ny, (1:P.nu)')","category":"page"},{"location":"man/creating_systems/#Creating-arrays-with-different-types-of-systems","page":"Creating Systems","title":"Creating arrays with different types of systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"When calling hcat/vcat, Julia automatically tries to promote the types to the smallest common supertype, this means that creating an array with one continuous and one discrete-time system fails","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"P_cont = ssrand(2,3,1) \nP_disc = ssrand(2,3,1, Ts=1)\n@test_throws ErrorException [P_cont, P_disc] # ERROR: Sampling time mismatch","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"You can explicitly tell Julia that you want a particular supertype, e.g,","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"StateSpace[P_cont, P_disc]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The type StateSpace is abstract, since the type parameters are not specified.","category":"page"},{"location":"man/creating_systems/#Demo-systems","page":"Creating Systems","title":"Demo systems","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The module ControlSystemsBase.DemoSystems contains a number of demo systems demonstrating different kinds of dynamics.","category":"page"},{"location":"man/creating_systems/#From-block-diagrams-to-code","page":"Creating Systems","title":"From block diagrams to code","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This section lists a number of block diagrams, and indicates the corresponding transfer functions and how they are built in code.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"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.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The returned closed-loop system will have a state vector comprised of the state of G1 followed by the state of G2.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Closed-loop system from reference to output","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"r ┌─────┐ ┌─────┐\n───►│ │ u │ │ y\n │ C ├────►│ P ├─┬─►\n -┌►│ │ │ │ │\n │ └─────┘ └─────┘ │\n │ │\n └─────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfracPCI+PCR","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(P*C) or equivalently comp_sensitivity(P, C). Here, the system PC appears directly between the input r and the output y, and the feedback loop is negative identity. We thus call feedback(P*C) = feedback(P*C, 1)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"d ┌───┐ y\n───+─►│ P ├─┬───►\n -▲ └───┘ │\n │ │\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfracPI+PCD = PSD","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(P, C) or equivalently G_PS(P, C). Here, only P appears directly between d and y, while C appears first in the feedback loop. We thus call feedback(P, C)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sensitivity function at plant input","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"d e┌───┐ \n───+─►│ P ├─┬───►\n -▲ └───┘ │\n │ │\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"E = dfrac1I+CPD = SD","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(1, C*P) or equivalently input_sensitivity(P, C). Here, there are no systems directly between the input and the output, we thus call feedback(1, C*P). Note the order in C*P, which is important for MIMO systems. This computes the sensitivity function at the plant input. It's more common to analyze the sensitivity function at the plant output, illustrated below (for SISO systems they are equivalent).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Sensitivity function at plant output","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" ┌───┐ \n───+─►│ P ├─+◄── e\n -▲ └───┘ │\n │ │y\n │ ┌───┐ │\n └──┤ C │◄┘\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Y = dfrac1I+PCE = SE","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(1, P*C) or equivalently output_sensitivity(P, C). Note the reverse order in PC compared to the input sensitivity function above.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Reference r and input disturbance d to output y and control signal u. This example forms the transfer function matrix with r and d as inputs, and y and u as outputs.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" d\n ┌─────┐ │ ┌─────┐\nr │ │u ▼ │ │ y\n──+─►│ C ├──+─►│ P ├─┬─►\n ▲ │ │ │ │ │\n -│ └─────┘ └─────┘ │\n │ │\n └──────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginbmatrix\ny u\nendbmatrix = \nbeginbmatrix\ndfracPCI + PC dfracCI + PC \ndfracPI + PC dfrac-PCI + PC\nendbmatrix\nbeginbmatrix\nr d\nendbmatrix","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: feedback(C, P, W2=:, Z2=:, Zperm=[(1:P.ny).+P.nu; 1:P.nu]) # y,u from r,d. Here, we have reversed the order of P and C to get the correct sign of the control signal. We also make use of the keyword arguments W2 and Z2 to specify that we want to include the inputs and outputs of P as external inputs and outputs, and Zperm to specify the order of the outputs (y before u).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Linear fractional transformation","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" ┌─────────┐\nz◄───┤ │◄────w\n │ P │\ny┌───┤ │◄───┐u\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n │ │ │ │\n └─────►│ K ├───────┘\n │ │\n └───┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Z = operatornamelft(P K) W","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: lft(P, K)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" z1 z2\n ▲ ┌─────┐ ▲ ┌─────┐\n │ │ │ │ │ │\nw1──+─┴─►│ C ├──┴───+─►│ P ├─┐\n │ │ │ │ │ │ │\n │ └─────┘ │ └─────┘ │\n │ w2 │\n └────────────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"The transfer function from w_1 w_2 to z_1 z_2 contains all the transfer functions that are commonly called \"gang of four\" (see also gangoffour).","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"beginbmatrix\nz_1 z_2\nendbmatrix = \nbeginbmatrix\nI C\nendbmatrix (I + PC)^-1 beginbmatrix\nI P\nendbmatrix\nbeginbmatrix\nw_1 w_2\nendbmatrix","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Code: This function requires the package RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"RobustAndOptimalControl.extended_gangoffour(P, C, pos=true)\n# For SISO P\nS = G[1, 1]\nPS = G[1, 2]\nCS = G[2, 1]\nT = G[2, 2]\n\n# For MIMO P\nS = G[1:P.ny, 1:P.nu]\nPS = G[1:P.ny, P.nu+1:end]\nCS = G[P.ny+1:end, 1:P.nu]\nT = G[P.ny+1:end, P.nu+1:end]","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See also","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"output_sensitivity\ninput_sensitivity\noutput_comp_sensitivity\ninput_comp_sensitivity\nG_PS\nG_CS\ngangoffour)\ngangoffourplot)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"This diagram is more complicated and forms several connections, including both feedforward and feedback connections. A code file that goes through how to form such complicated connections using named signals is linked below. This example uses the package RobustAndOptimalControl.jl.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":" yF\n ┌────────────────────────────────┐\n │ │\n ┌───────┐ │ ┌───────┐ yR ┌─────────┐ │ ┌───────┐\nuF │ │ │ │ ├──────► │ yC │ uP│ │ yP\n────► F ├─┴──► R │ │ C ├────+────► P ├────┬────►\n │ │ │ │ ┌──► │ │ │ │\n └───────┘ └───────┘ │- └─────────┘ └───────┘ │\n │ │\n └───────────────────────────────────┘","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See code example complicated_feedback.jl.","category":"page"},{"location":"man/creating_systems/#Filter-design","page":"Creating Systems","title":"Filter design","text":"","category":"section"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"Filters can be designed using DSP.jl. This results in filter objects with types from the DSP package, which can be converted to transfer functions using tf from ControlSystemsBase.","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"using DSP, ControlSystemsBase, Plots\n\nfs = 100\ndf = digitalfilter(Bandpass(5, 10; fs), Butterworth(2))\nG = tf(df, 1/fs) # Sample time must be provided in the conversion to get the correct frequency scale in the Bode plot\nbodeplot(G, xscale=:identity, yscale=:identity, hz=true)","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"See also","category":"page"},{"location":"man/creating_systems/","page":"Creating Systems","title":"Creating Systems","text":"ControlSystemsBase.seriesform","category":"page"},{"location":"man/numerical/#Performance-considerations","page":"Performance considerations","title":"Performance considerations","text":"","category":"section"},{"location":"man/numerical/#Numerical-accuracy","page":"Performance considerations","title":"Numerical accuracy","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Transfer functions, and indeed polynomials in general, are infamous for having poor numerical properties and for this reason, it's ill-advised to use high-order transfer functions. Orders as low as 6 may already be considered high. When a transfer function is converted to a state-space representation using ss(G), balancing is automatically performed in an attempt at making the numerical properties of the model better.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"This problem is illustrated below, where we first create a statespace system G and convert this to a transfer function G_1. We then perturb a single element of the dynamics matrix A by adding the machine epsilon for Float64 (eps() = 2.22044e-16), and convert this perturbed statespace system to a transfer function G_2. The difference between the two transfer functions is enormous, the norm of the difference in their denominator coefficient vectors is on the order of 10^96.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"sys = ssrand(1,1,100);\nG1 = tf(sys);\nsys.A[1,1] += eps();\nG2 = tf(sys);\nnorm(denvec(G1)[] - denvec(G2)[])\n6.270683106765845e96","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If we plot the poles of the two systems, they are also very different","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"scatter(poles(G1)); scatter!(poles(G2))","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"(Image: Noisy poles)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If we instead compute the poles of the statespace model before and after the perturbation, they are almost indistinguishable.","category":"page"},{"location":"man/numerical/#State-space-balancing","page":"Performance considerations","title":"State-space balancing","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"The function balance_statespace can be used to compute a balancing transformation T that attempts to scale the system so that the row and column norms of","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"beginbmatrix\nTAT^-1 TB\nCT^-1 0\nendbmatrix","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"are approximately equal. This typically improves the numerical performance of several algorithms, including frequency-response calculations and continuous-time simulations. When frequency-responses are plotted using any of the built-in functions, such as bodeplot or nyquistplot, this balancing is performed automatically. However, when calling bode and nyquist directly, the user is responsible for performing the balancing. The balancing is a relatively cheap operation, but it","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Changes the state representations of the system (but not the input-output mapping). If balancing is performed before simulation, the output will correspond to the output of the original system, but the state trajectory will not.\nAllocates some memory.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Balancing is also automatically performed when a transfer function is converted to a statespace system using ss(G), to convert without balancing, call convert(StateSpace, G, balance=false).","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Intuitively (and simplified), balancing may be beneficial when the magnitude of the elements of the B matrix are vastly different from the magnitudes of the element of the C matrix, or when the A matrix contains very large coefficients. An example that exhibits all of these traits is the following","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"using ControlSystemsBase, LinearAlgebra\nA = [-6.537773175952662 0.0 0.0 0.0 -9.892378564622923e-9 0.0; 0.0 -6.537773175952662 0.0 0.0 0.0 -9.892378564622923e-9; 2.0163803998106024e8 2.0163803998106024e8 -0.006223894167415392 -1.551620418759878e8 0.002358202548321148 0.002358202548321148; 0.0 0.0 5.063545034365582e-9 -0.4479539754649166 0.0 0.0; -2.824060629317756e8 2.0198389074625736e8 -0.006234569427701143 -1.5542817673286995e8 -0.7305736722226711 0.0023622473513548576; 2.0198389074625736e8 -2.824060629317756e8 -0.006234569427701143 -1.5542817673286995e8 0.0023622473513548576 -0.7305736722226711]\nB = [0.004019511633336128; 0.004019511633336128; 0.0; 0.0; 297809.51426114445; 297809.51426114445]\nC = [0.0 0.0 0.0 1.0 0.0 0.0]\nD = [0.0]\nlinsys = ss(A,B,C,D)\nnorm(linsys.A, Inf), norm(linsys.B, Inf), norm(linsys.C, Inf)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"which after balancing becomes","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"bsys, T = balance_statespace(linsys)\nnorm(bsys.A, Inf), norm(bsys.B, Inf), norm(bsys.C, Inf)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"If you plot the frequency-response of the two systems using bodeplot, you'll see that they differ significantly (the balanced one is correct).","category":"page"},{"location":"man/numerical/#Frequency-response-calculation","page":"Performance considerations","title":"Frequency-response calculation","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For small systems (small number of inputs, outputs and states), evaluating the frequency-response of a transfer function is reasonably accurate and very fast.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"G = tf(1, [1, 1])\nw = exp10.(LinRange(-2, 2, 200));\n@btime freqresp($G, $w);\n# 4.351 μs (2 allocations: 3.31 KiB)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Evaluating the frequency-response for the equivalent state-space system incurs some additional allocations due to a Hessenberg matrix factorization","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"sys = ss(G);\n@btime freqresp($sys, $w);\n# 20.820 μs (16 allocations: 37.20 KiB)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For larger systems, the state-space calculations are considerably more accurate, provided that the realization is well balanced.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For optimal performance, one may preallocate the return array","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"ny,nu = size(G)\nR = zeros(ComplexF64, ny, nu, length(w));\n\n@btime freqresp!($R, $G, $w);\n# 4.214 μs (1 allocation: 64 bytes)","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"Other functions that accept preallocated workspaces are","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"bodemag!\nfreqresp!\nlsim!","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"an example using bodemag! follows:","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"using ControlSystemsBase\nG = tf(ssrand(2,2,5))\nw = exp10.(LinRange(-2, 2, 20000))\n@btime bode($G, $w);\n# 55.120 ms (517957 allocations: 24.42 MiB)\n@btime bode($G, $w, unwrap=false); # phase unwrapping is slow\n# 3.624 ms (7 allocations: 2.44 MiB)\nws = ControlSystemsBase.BodemagWorkspace(G, w)\n@btime bodemag!($ws, $G, $w);\n# 2.991 ms (1 allocation: 64 bytes)","category":"page"},{"location":"man/numerical/#Time-domain-simulation","page":"Performance considerations","title":"Time-domain simulation","text":"","category":"section"},{"location":"man/numerical/#Time-scale","page":"Performance considerations","title":"Time scale","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/#Transfer-functions","page":"Performance considerations","title":"Transfer functions","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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. ","category":"page"},{"location":"man/numerical/#Discrete-time-simulation","page":"Performance considerations","title":"Discrete-time simulation","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"For discrete-time systems, the function lsim! accepts a pre-allocated workspace objects that can be used to avoid allocations for repeated simulations.","category":"page"},{"location":"man/numerical/#Numerical-balancing","page":"Performance considerations","title":"Numerical balancing","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"man/numerical/#Static-arrays-in-StateSpace-systems","page":"Performance considerations","title":"Static arrays in StateSpace systems","text":"","category":"section"},{"location":"man/numerical/","page":"Performance considerations","title":"Performance considerations","text":"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.","category":"page"},{"location":"examples/ilc/#Iterative-Learning-Control","page":"Iterative Learning Control (ILC)","title":"Iterative-Learning Control","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"In this example, we will design an Iterative-Learning Control (ILC) iteration scheme. ILC can be thought of as a simple reinforcement-learning strategy that is suitable in situations where a repetitive task is to be performed multiple times, and disturbances acting on the system are also repetitive and predictable but unknown. Multiple versions of ILC exists, in this tutorial we will consider a heuristic scheme as well as a model-based scheme. ","category":"page"},{"location":"examples/ilc/#Algorithm","page":"Iterative Learning Control (ILC)","title":"Algorithm","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The ILC iteration scheme typically looks something like this (many variants exists), at ILC iteration k:","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"beginaligned\ny_k(t) = G(q) big(r(t) + a_k(t) big) \ne_k(t) = r(t) - y_k(t) \na_k(t) = Q(q) big( a_k-1(t) + L(q) e_k-1(t) big)\nendaligned","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"where q is the time-shift operator, G(q) is the transfer function from the reference r to the output y, i.e, typically a closed-loop transfer function, e_k is the control error and a_k is the ILC adjustment signal, an additive correction to the reference that is learned throughout the ILC iterations in order to minimize the control error. Q(q) and L(q) are stable filters that control the learning dynamics. Interestingly, these filters does not have to be causal since they operate on the signals e and a between ILC iterations, when the whole signals are available at once for acausal filtering. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"In simulation (the rollout y_k = G(q) (r + a_k) is simulated), this scheme is nothing other than an open-loop optimal-control strategy, while if y_k = G(q) (r + a_k) amounts to performing an actual experiment on a process, ILC turns into episode-based reinforcement learning or adaptive control.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The system to control in this example is a double-mass system with a spring and damper in between. This system is a common model of a servo system where one mass represents the motor and the other represents the load. The spring and damper represents a flexible transmission between them. We will create two instances of the system model. G represents the nominal model, whereas G_act represents the actual (unknown) dynamics. This simulates a model-based approach where there is a slight error in the model. The error will lie in the mass of the load, simulating, e.g., that the motor is driving a heavier load than specified. ","category":"page"},{"location":"examples/ilc/#System-model-and-controller","page":"Iterative Learning Control (ILC)","title":"System model and controller","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"using ControlSystemsBase, Plots\n\nfunction double_mass_model(; \n Jm = 1, # motor inertia\n Jl = 1, # load inertia\n k = 100, # stiffness\n c0 = 1, # motor damping\n c1 = 1, # transmission damping\n c2 = 1, # load damping\n)\n\n A = [\n 0.0 1 0 0\n -k/Jm -(c1 + c0)/Jm k/Jm c1/Jm\n 0 0 0 1\n k/Jl c1/Jl -k/Jl -(c1 + c2)/Jl\n ]\n B = [0, 1/Jm, 0, 0]\n C = [1 0 0 0]\n ss(A, B, C, 0)\nend\n\nG = double_mass_model(Jl = 1)\nGact = double_mass_model(Jl = 1.5) # 50% more load than modeled\n\nbodeplot([G, Gact], lab=[\"G model\" \"G actual\"], plotphase=false)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"C = pid(10, 1, 1, form = :series) * tf(1, [0.02, 1])\nTs = 0.02 # Sample time\nGc = c2d(feedback(G*C), Ts) |> tf\nGcact = c2d(feedback(Gact*C), Ts) |> tf\nplot(step(Gc, 10), title=\"Closed-loop step response\", lab=\"model\")\nplot!(step(Gcact, 10), lab=\"actual\")","category":"page"},{"location":"examples/ilc/#Reference-trajectory","page":"Iterative Learning Control (ILC)","title":"Reference trajectory","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"Next up we design a reference trajectory and simulate the actual closed-loop dynamics.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"T = 3pi # Duration\nt = 0:Ts:T # Time vector\nfunction funnysin(x)\n x = sin(x)\n s,a = sign(x), abs(x)\n s*((a + 0.01)^0.2 - 0.01^0.2)\nend\nr = funnysin.(t)' |> Array # Reference signal\n\nres = lsim(Gcact, r, t)\nplot(res, plotu=true, layout=1, sp=1, title=\"Closed-loop simulation with actual dynamics\", lab=[\"y\" \"r\"])","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"Performance is poor.. Enter ILC!","category":"page"},{"location":"examples/ilc/#Non-causal-filtering","page":"Iterative Learning Control (ILC)","title":"Non-causal filtering","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"function lsim_zerophase(G, u, args...; kwargs...)\n res = lsim(G, u[:, end:-1:1], args...; kwargs...)\n lsim(G, res.y[:, end:-1:1], args...; kwargs...).y\nend\n\nfunction lsim_noncausal(L::LTISystem{<:Discrete}, u, args...; kwargs...)\n np = length(denpoly(L)[])\n nz = length(numpoly(L)[])\n zeroexcess = nz-np\n if zeroexcess <= 0\n return lsim(L, u, args...; kwargs...)\n end\n integrators = tf(1, [1, 0], L.Ts)^zeroexcess\n res = lsim(L*integrators, u, args...; kwargs...)\n res.y[1:end-zeroexcess] .= res.y[1+zeroexcess:end]\n res.y\nend\nnothing # hide","category":"page"},{"location":"examples/ilc/#Choosing-filters","page":"Iterative Learning Control (ILC)","title":"Choosing filters","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The next step is to define the ILC filters Q(q) and L(q).","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The filter L(q) acts as a frequency-dependent step size. To make the procedure take smaller steps, simply scale L by a constant < 1. Scaling down L makes the learning process slower but more robust. A heuristic choice of L is some form of scaled lookahead, such as 05z^l where l geq 0 is the number of samples lookahead. A model-based approach may use some form of inverse of the system model, which is what we will use here. [nonlinear]","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"[nonlinear]: Inverse models can be formed also for some nonlinear systems. ModelingToolkit.jl is particularily well suited for inverting models due to its acausal nature.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The filter Q(q) acts to make the procedure robust w.r.t. noise and modeling errors. Q has a final say over what frequencies appear in a and it's good to choose Q with low-pass properties. Q will here be applied in zero-phase mode, so the effective transfer function will be Q(z)Q(z).","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"z = tf(\"z\", Ts)\nQ = c2d(tf(1, [0.05, 1]), Ts)\n# L = 0.9z^1 # A more conservative and heuristic choice\nL = 0.5inv(Gc) # Make the scaling factor smaller to take smaller steps\nnothing # hide","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"bodeplot([inv(Q), (1 - L*Gc)], plotphase=false, lab=[\"Stability boundary \\$Q^{-1}\\$\" \"\\$1 - LG\\$\"])\nbodeplot!((1 - L*Gcact), plotphase=false, lab=\"\\$1 - LG\\$ actual\")","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/#ILC-iteration","page":"Iterative Learning Control (ILC)","title":"ILC iteration","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The next step is to implement the ILC scheme and run it:","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"function ilc(Gc, Q, L)\n a = zero(r) # ILC adjustment signal starts at 0\n fig1 = plot(t, vec(r), sp=1, layout=(3,1), l=(:black, 3), lab=\"Ref\")\n fig2 = plot(title=\"Sum of squared error\", xlabel=\"Iteration\", legend=false, titlefontsize=10, framestyle=:zerolines, ylims=(0, 7.1))\n for iter = 1:5\n ra = r .+ a\n res = lsim(Gc, ra, t) # Simulate system, replaced by an actual experiment if running on real process\n y = res.y # System response\n e = r .- y # Error\n Le = lsim_noncausal(L, e, t)\n a = lsim_zerophase(Q, a + Le, t) # Update ILC adjustment\n\n err = sum(abs2, e)\n plot!(fig1, res, plotu=true, sp=[1 2], title=[\"Output \\$y(t)\\$\" \"Adjusted reference \\$r + a\\$\"], lab=\"Iter $iter\", c=iter)\n plot!(fig1, e[:], sp=3, title=\"Tracking error \\$e(t)\\$\", lab=\"err: $(round(err, digits=2))\", c=iter)\n scatter!(fig2, [iter], [err])\n end\n plot(fig1, fig2, layout=@layout([a{0.7w} b{0.3w}]))\nend\nilc(Gc, Q, L)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"How does it work on the \"actual\" dynamics?","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"ilc(Gcact, Q, L)","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"The result is subtly worse, but considering the rather big model error the result is still quite good. ","category":"page"},{"location":"examples/ilc/#Summary","page":"Iterative Learning Control (ILC)","title":"Summary","text":"","category":"section"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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. ","category":"page"},{"location":"examples/ilc/","page":"Iterative Learning Control (ILC)","title":"Iterative Learning Control (ILC)","text":"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.","category":"page"},{"location":"man/differences/#Noteworthy-Differences-from-other-Languages","page":"Noteworthy differences from other languages","title":"Noteworthy Differences from other Languages","text":"","category":"section"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"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.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"The rest of this page will list noteworthy differences between ControlSystems.jl and other pieces of control-systems software.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"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.\nSimulation 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.\nFunctions are, lqr and kalman have slightly different signatures in julia compared to in other languages. More advanced LQG functionalities are located in RobustAndOptimalControl.jl.\nSimulation using lsim, step, impulse etc. return a structure that can be plotted. These functions never plot anything themselves.\nFunctions bode, nyquist etc. never produce a plot. Instead, see bodeplot, nyquistplot etc.\nIn 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.\nIn 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.\nJulia treats matrices and vectors as different types, in particular, column vectors and row vectors are not interchangeable. \nIn 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.\nIn 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.\nIf 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!\nJulia compiles code just before it is called the first time. This introduces a noticeable lag, and can make packages take a long time to load. If you want to speed up the loading of ControlSystems.jl, consider building a system image that includes ControlSystems.jl using PackageCompiler.jl. More info about this is available below under Precompilation for faster load times","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"If you find other noteworthy differences between ControlSystems.jl and other pieces of control-related software, please consider submitting a pull request (PR) to add to the list above. You can submit a PR by clicking on \"Edit on GitHub\" at the top of this page and then clicking on the icon that looks like a pen above the file viewer. A two-minute video on this process is available below","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"","category":"page"},{"location":"man/differences/#Precompilation-for-faster-load-times","page":"Noteworthy differences from other languages","title":"Precompilation for faster load times","text":"","category":"section"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"In order to make it faster to load the ControlSystems.jl package, you may make use of PackageCompiler.jl. ","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"warning: For developers\nIf you intend to develop ControlSystem.jl, i.e., modify the source code, it's not recommended to build the package into the system image. We then recommend to build OrdinaryDiffEq into the system image since this package contributes the largest part of the loading time.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Building a custom system image can reduce the time to get started in a new Julia session, as an example:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Without system image:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"julia> @time using ControlSystems\n 1.646961 seconds (2.70 M allocations: 173.558 MiB, 1.08% gc time, 2.06% compilation time)","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"With OrdinaryDiffEq and Plots in the system image:","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"julia> @time using ControlSystems\n 0.120975 seconds (413.37 k allocations: 27.672 MiB, 1.66% compilation time)","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"To build a system image with ControlSystems, save the following script in a file, e.g., precompile_controlsystems.jl (feel free to add any additional packages you may want to load).","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"using OrdinaryDiffEq # Include this if you want to develop ControlSystems.jl\nusing ControlSystems # Include this if you only want to use ControlSystems.jl\nusing Plots # In case you also want to use plotting functions\n\n# Run some statements to make sure these are precompiled. Do not include this if you want to develop ControlSystems.jl\nfor P = StateSpace[ssrand(2,2,2), ssrand(2,2,2, Ts=0.1)]\n bodeplot(P)\n nyquistplot(P)\n plot(step(P, 10))\nend","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"Then run the following","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"using PackageCompiler\nPackageCompiler.create_sysimage(\n [\n :OrdinaryDiffEq,\n :Plots,\n :ControlSystems,\n ];\n precompile_execution_file = \"precompile_execution_file\",\n sysimage_path = \"sys_ControlSystems_$(VERSION).so\",\n)\nexit()","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"When you have created a system image, start Julia with the -J flag pointing to the system image that was created, named sys_ControlSystems_.so, more details here. After this, loading the package should be very fast.","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"warning: Updating packages\nWhen you update installed julia packages, the update will not be reflected in the system image until the image is rebuilt. ","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"You can make vscode load this system image as well by adding","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"\"julia.additionalArgs\": [\n \"-J/path_to_sysimage/sys_ControlSystems_.so\"\n],","category":"page"},{"location":"man/differences/","page":"Noteworthy differences from other languages","title":"Noteworthy differences from other languages","text":"to settings.json.","category":"page"},{"location":"examples/analysis/#Analysis-of-linear-control-systems","page":"Analysis","title":"Analysis of linear control systems","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"From classical control, we get robustness measures such as gain and phase margins. These provide a quick and intuitive way to assess robustness of single-input, single-output systems, but also have a number of downsides, such as optimism in the presence of simultaneous gain and phase variations as well as limited applicability for MIMO systems.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"Gain and phase margins can be computed using the functions margin and marginplot","category":"page"},{"location":"examples/analysis/#Example:-Gain-and-phase-margins","page":"Analysis","title":"Example: Gain and phase margins","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"using ControlSystemsBase, Plots\nP = tf(1, [1, 0.2, 1])\nC = pid(0.2, 1)\nloopgain = P*C\nmarginplot(loopgain)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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.","category":"page"},{"location":"examples/analysis/#Sensitivity-analysis","page":"Analysis","title":"Sensitivity analysis","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"More generally applicable measures of robustness include analysis of sensitivity functions, notably the peaks of the sensitivity function","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"S(s) = (I + P(s)C(s))^-1","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"and the complementary sensitivity function","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"T(s) = I - S(s) = (I + P(s)C(s))^-1P(s)C(s)","category":"page"},{"location":"examples/analysis/#Examples","page":"Analysis","title":"Examples","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"We can plot all four sensitivity functions referred to as the \"gang of four\" using gangoffourplot.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"using ControlSystemsBase, Plots\nP = tf(1, [1, 0.2, 1])\nC = pid(0.2, 1)\ngangoffourplot(P, C)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"The peak value of the sensitivity function, M_S, can be computed using hinfnorm","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"S = sensitivity(P, C)\nMs, ωMs = hinfnorm(S)","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"And we can plot a circle in the Nyquist plot corresponding to the inverse distance between the loop-transfer function and the critical point:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"w = exp10.(-1:0.001:2)\nnyquistplot(P*C, w, Ms_circles=[Ms], xlims=(-1.2, 0.5), ylims=(-2, 0.3))","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"beginaligned\nphi_m 2 sin^-1left(dfrac12M_Sright) textrad\ng_m dfracM_SM_S-1\nendaligned","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"We can also obtain individual sensitivity function using the low-level function feedback directly, or using one of the higher-level functions","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"sensitivity\ncomp_sensitivity\nG_PS\nG_CS\ngangoffour\nextended_gangoffour\nRobustAndOptimalControl.feedback_control","category":"page"},{"location":"examples/analysis/#Further-reading","page":"Analysis","title":"Further reading","text":"","category":"section"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"A modern robustness measure is the diskmargin, that analyses the robustness of a SISO or MIMO system to simultaneous gain and phase variations.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"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.","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"Basic usage of robustness analysis with JuliaControl are demonstrated in the two videos below:","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"and ","category":"page"},{"location":"examples/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"lib/nonlinear/#Nonlinear-functionality","page":"Nonlinear","title":"Nonlinear functionality","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"danger: Experimental\nThe nonlinear interface is currently experimental and at any time subject to breaking changes not respecting semantic versioning. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ControlSystems.jl can represent nonlinear feedback systems that can be written on the form","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":" ┌─────────┐\n y◄───┤ │◄────u\n │ P │\nΔy┌───┤ │◄───┐Δu\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n └─────►│ f ├───────┘\n └───┘","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"i.e., as a linear-fractional transform (LFT) between a linear system P and a diagonal matrix with scalar non-linear functions f. This representation is identical to that used for delay systems, and is exposed to the user in a similar way as well. The main entry point is the function nonlinearity which takes a nonlinear function f like so, nonlinearity(f). This creates a primitive system containing only the nonlinearity, but which behaves like a standard LTISystem during algebraic operations. We illustrate its usage through a number of examples.","category":"page"},{"location":"lib/nonlinear/#Examples","page":"Nonlinear","title":"Examples","text":"","category":"section"},{"location":"lib/nonlinear/#Control-signal-saturation","page":"Nonlinear","title":"Control-signal saturation","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"To create a controller that saturates the output at pm 07, we call","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystems, Plots\nusing ControlSystemsBase: nonlinearity # This functionality is not exported due to the beta status\n\nC = pid(1, 0.1, form=:parallel) # A standard PI controller\nnl = nonlinearity(x->clamp(x, -0.7, 0.7)) # a saturating nonlinearity\nsatC = nl*C # Connect the saturation at the output of C","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"we may now use this controller like we would normally do in ControlSystems, e.g.,","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"P = tf(1, [1, 1]) # a plant\nG = feedback(P*C) # closed loop without nonlinearity\nGnl = feedback(P*satC) # closed loop with saturation\n\nGu = feedback(C, P) # closed loop from reference to control signal without nonlinearity\nGunl = feedback(satC, P) # closed loop from reference to control signal with saturation\n\nplot(step([G; Gu], 5), lab = [\"Linear y\" \"Linear u\"])\nplot!(step([Gnl; Gunl], 5), lab = [\"Nonlinear y\" \"Nonlinear u\"])","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystemsBase: saturation\nsaturation(0.7)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"See also ControlSystemsBase.ratelimit that saturates the derivative of a signal.","category":"page"},{"location":"lib/nonlinear/#Non-zero-operating-point","page":"Nonlinear","title":"Non-zero operating point","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"It's common to linearize nonlinear systems around some operating point. We may make use of the helper constructor ControlSystemsBase.offset to create affine functions at the inputs and outputs of the linearized system to, e.g.,","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Make sure that simulations result are given in the original coordinates rather than in the coordinates of the linearization point.\nAllow nonlinearities that are added back after the linearization (such as saturations) to operate with their original parameters.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We will demonstrate a composite usage of offset and saturation below. The system we'll consider is a linearized model of a quadruple-tank process;","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"The system is linearized around the operating point","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"xr = [10, 10, 4.9, 4.9] # reference state\nur = [0.263, 0.263] # control input at the operating point\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"and is given by","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using LinearAlgebra\nkc, k1, k2, g = 0.5, 1.6, 1.6, 9.81\nA1 = A3 = A2 = A4 = 4.9\na1, a3, a2, a4 = 0.03, 0.03, 0.03, 0.03\nh01, h02, h03, h04 = xr\nT1, T2 = (A1/a1)sqrt(2*h01/g), (A2/a2)sqrt(2*h02/g)\nT3, T4 = (A3/a3)sqrt(2*h03/g), (A4/a4)sqrt(2*h04/g)\nc1, c2 = (T1*k1*kc/A1), (T2*k2*kc/A2)\nγ1, γ2 = 0.3, 0.3\n\n# Define the process dynamics\nA = [-1/T1 0 A3/(A1*T3) 0\n 0 -1/T2 0 A4/(A2*T4)\n 0 0 -1/T3 0\n 0 0 0 -1/T4]\nB = [γ1*k1/A1 0\n 0 γ2*k2/A2\n 0 (1-γ2)k2/A3\n (1-γ1)k1/A4 0 ]\n\nC = kc*[I(2) 0*I(2)] # Measure the first two tank levels\nD = 0\nG = ss(A,B,C,D)\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"A PID controller with a filter is given by","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"F = tf(1, [0.63, 1.12, 1])\nCpid = pid(0.26, 0.001, 15.9, form=:parallel)*F |> ss\nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"and to make the controller MIMO, we add a static pre-compensator that decouples the system at the the zero frequency.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"iG0 = dcgain(G)\niG0 ./= maximum(abs, iG0)\nC = (Cpid .* I(2)) * iG0 \nnothing # hide","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"The pumps (there are two of them) that service the tanks can only add liquid to the tanks, not remove liquid. The pump is thus saturated from below at 0, and from above at the maximum pump capacity 0.4. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystemsBase: offset\numin = [0.0, 0.0]\numax = [0.4, 0.4]\n\nyr = G.C*xr # Reference output\nGop = offset(yr) * G * offset(-ur) # Make the plant operate in Δ-coordinates \nC_sat = saturation(umin, umax) * C # while the controller and the saturation operate in the original coordinates","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We now simulate the closed-loop system, the initial state of the plant is adjusted with the operating point x0-xr since the plant operates in Δ-coordinates","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"x0 = [2, 1, 8, 3] # Initial tank levels\nplot(\n plot(lsim(feedback(Gop*C_sat), yr, 0:1:3000, x0=[x0-xr; zeros(C.nx)]), layout=1, sp=1, title=\"Outputs\", ylabel=\"\"),\n plot(lsim(feedback(C_sat, Gop), yr, 0:1:3000, x0=[zeros(C.nx); x0-xr]), layout=1, sp=1, title=\"Control signals\", ylabel=\"\")\n)\nhline!([yr[1]], label=\"Reference\", l=:dash, sp=1, c=1)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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].","category":"page"},{"location":"lib/nonlinear/#Duffing-oscillator","page":"Nonlinear","title":"Duffing oscillator","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"In this example, we'll model and control the nonlinear system","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ddot x = -kx - k_3 x^3 - c dotx + 10u","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"To do this, we first draw the block diagram","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"10u ┌───┐\n──────►│+ │ ┌───┐ ┌───┐\n ┌────►│- │ ẍ │ 1 │ ẋ │ 1 │ x\n │ ┌──►│- ├──►│ - ├┬─►│ - ├─┬──►\n │ │ ┌►│- │ │ s ││ │ s │ │\n │ │ │ └───┘ └───┘│ └───┘ │\n │ │ │ │ │\n │ │ │ ┌───┐ │ │\n │ │ └───┤ c │◄─────┘ │\n │ │ └───┘ │\n │ │ │\n │ │ ┌───┐ │\n │ └─────┤ k │◄──────────────┤\n │ └───┘ │\n │ │\n │ ┌───┐ ┌───┐ │\n └───────┤ k³│◄──┤ x³│◄──────┘\n └───┘ └───┘","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"We see that the input u passes through the inner velocity loop before reaching the output x, we can form this inner closed-loop transfer function using feedback(1/s, c), i.e., close the loop over an integrator by -c. This inner loop is then connected in series with another integrator an feedback loop is closed with k_3 x^3 + kx = pos_loop_feedback in the feedback path. Notice how we multiply the final system with 10 from the right to get the input gain correct, for nonlinear systems, 10*sys and sys*10 are not always equivalent!","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"using ControlSystems, Plots\nusing ControlSystemsBase: nonlinearity\nk = 10\nk3 = 2\nc = 1\n\ns = tf(\"s\")\n\ncube = nonlinearity(x->x^3)\nvel_loop = feedback(1/s, c)\npos_loop_feedback = (k3*cube + k)\nduffing = feedback(vel_loop/s, pos_loop_feedback)*10\n\nplot(step(duffing, 20), title=\"Duffing oscillator open-loop step response\")","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"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:","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"function circle_criterion(L::ControlSystemsBase.HammersteinWienerSystem, domain::Tuple; N=10000)\n fun = x->L.f[](x)/x\n x = range(domain[1], stop=domain[2], length=N)\n 0 ∈ x && (x = filter(!=(0), x)) # We cannot divide by zero\n k1, k2 = extrema(fun, x)\n\n f1 = plot(L.f[], domain[1], domain[2], title=\"Nonlinearity\", lab=\"f(x)\", xlab=\"x\")\n plot!(x, [k1.*x k2.*x], lab=[\"k1 = $(round(k1, sigdigits=2))\" \"k2 = $(round(k2, sigdigits=2))\"], l=(:dash), legend=:bottomright)\n\n p1 = -1/k2 # Close to origin\n p2 = -1/k1 # Far from origin\n\n c = (p1 + p2)/2\n r = (p2 - p1)/2\n\n Lnominal = sminreal(ss(L.A, L.B1, L.C1, L.D11, L.P.timeevol))\n f2 = nyquistplot(Lnominal)\n if p2 < -1000 # Due to bug in plots\n vspan!([-1000, p1], fillalpha=0.7, c=:red, primary=false)\n else\n th = 0:0.01:2pi\n Cs,Ss = cos.(th), sin.(th)\n plot!(r.*Cs .+ c, r.*Ss, fill=true, fillalpha=0.7, c=:red, primary=false)\n end\n\n plot(f1,f2)\nend\n\n\nC = pid(2, 0, 1, form=:parallel)*tf(1, [0.01,1])\nf1 = circle_criterion(duffing*C, (-1, 1))\nplot!(sp=2, ylims=(-10, 3), xlims=(-5, 11))\nf2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title=\"Controlled oscillator disturbance step response\", layout=4)\nplot(f1,f2, size=(1300,800))","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Since we evaluated the nonlinearity over a small domain, we should convince ourselves that we indeed never risk leaving this domain. ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"In the example above, the circle turns into a half plane since the lower sector bound is 0. The example below chooses another nonlinearity","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"f(x) = x + sin(x)","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"to get an actual circle in the Nyquist plane.","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"wiggly = nonlinearity(x->x+sin(x)) # This function is a bit wiggly\nvel_loop = feedback(1/s, c)\npos_loop_feedback = (k3*wiggly + k)\nduffing = feedback(vel_loop/s, pos_loop_feedback)*10\n\nC = pid(2, 5, 1, form=:parallel)*tf(1,[0.1, 1]) \nf1 = circle_criterion(duffing*C, (-2pi, 2pi))\nplot!(sp=2, ylims=(-5, 2), xlims=(-2.1, 0.1))\nf2 = plot(step(feedback(duffing, C), 8), plotx=true, plot_title=\"Controlled wiggly oscillator disturbance step response\", layout=5)\nplot(f1,f2, size=(1300,800))","category":"page"},{"location":"lib/nonlinear/#Limitations","page":"Nonlinear","title":"Limitations","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Remember, this functionality is experimental and subject to breakage.\nCurrently only Continuous systems supported.\nNo 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. \nA lot of functions that expect linear systems will not work for nonlinear systems (naturally).","category":"page"},{"location":"lib/nonlinear/#Possible-future-work","page":"Nonlinear","title":"Possible future work","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Discrete-time support.\nBasic 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.\nAdditional nonlinear components, such as \nIntegrator anti-windup\nFriction models","category":"page"},{"location":"lib/nonlinear/#See-also","page":"Nonlinear","title":"See also","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"More advanced nonlinear modeling is facilitated by ModelingToolkit.jl (MTK) and ModelingToolkitStandardLibrary.jl. The tutorials ","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"Modeling for control using ModelingToolkit\nDisturbance modeling in ModelingToolkit\nModal analysis of a series of masses and springs using MTK","category":"page"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"show how to use these packages to model and simulate control systems.","category":"page"},{"location":"lib/nonlinear/#Docstrings","page":"Nonlinear","title":"Docstrings","text":"","category":"section"},{"location":"lib/nonlinear/","page":"Nonlinear","title":"Nonlinear","text":"ControlSystemsBase.nonlinearity\nControlSystemsBase.offset\nControlSystemsBase.saturation\nControlSystemsBase.ratelimit\nControlSystemsBase.deadzone\nControlSystemsBase.linearize","category":"page"},{"location":"lib/nonlinear/#ControlSystemsBase.nonlinearity","page":"Nonlinear","title":"ControlSystemsBase.nonlinearity","text":"nonlinearity(f)\nnonlinearity(T, f)\n\nCreate a pure nonlinearity. f is assumed to be a static (no memory) nonlinear function from f R - R.\n\nThe type T defaults to Float64.\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nExample:\n\nCreate a LTI system with a static input nonlinearity that saturates the input to [-1,1].\n\ntf(1, [1, 1])*nonlinearity(x->clamp(x, -1, 1))\n\nSee also predefined nonlinearities saturation, offset.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.offset","page":"Nonlinear","title":"ControlSystemsBase.offset","text":"offset(val)\n\nCreate a constant-offset nonlinearity x -> x + val.\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nExample:\n\nTo create a linear system that operates around operating point y₀, u₀, use\n\noffset_sys = offset(y₀) * sys * offset(-u₀)\n\nnote 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\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.saturation","page":"Nonlinear","title":"ControlSystemsBase.saturation","text":"saturation(val)\nsaturation(lower, upper)\n\nCreate a saturating nonlinearity. Connect it to the output of a controller C using\n\nCsat = saturation(val) * C\n\n y▲ ────── upper\n │ /\n │ /\n │/\n ──────────┼────────► u\n /│ \n / │\n / │\nlower──── \n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.ratelimit","page":"Nonlinear","title":"ControlSystemsBase.ratelimit","text":"ratelimit(val; Tf)\nratelimit(lower, upper; Tf)\n\nCreate a nonlinearity that limits the rate of change of a signal, roughly equivalent to 1s 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.deadzone","page":"Nonlinear","title":"ControlSystemsBase.deadzone","text":"deadzone(val)\ndeadzone(lower, upper)\n\nCreate a dead-zone nonlinearity.\n\n y▲\n │ /\n │ /\n lower │ /\n─────|──┼──|───────► u\n / │ upper\n / │\n / │\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\nNote: when composing linear systems with nonlinearities, it's often important to handle operating points correctly. See ControlSystemsBase.offset for handling operating points.\n\n\n\n\n\n","category":"function"},{"location":"lib/nonlinear/#ControlSystemsBase.linearize","page":"Nonlinear","title":"ControlSystemsBase.linearize","text":"linearize(sys::HammersteinWienerSystem, Δy)\n\nLinearize the nonlinear system sys around the operating point implied by the specified Δy\n\n ┌─────────┐\n y◄───┤ │◄────u\n │ P │\nΔy┌───┤ │◄───┐Δu\n │ └─────────┘ │\n │ │\n │ ┌───┐ │\n │ │ │ │\n └─────►│ f ├───────┘\n │ │\n └───┘\n\nNOTE: The nonlinear functionality in ControlSystemsBase.jl is currently experimental and subject to breaking changes not respecting semantic versioning. Use at your own risk.\n\n\n\n\n\nA, B = linearize(f, x, u, args...)\n\nLinearize dynamics x = f(x u args) around operating point (xuargs) 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...).\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"Pages = [\"plotting.md\"]","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"note: Using Plots\nAll plotting requires the user to manually load the Plots.jl library, e.g., by calling using Plots.","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"note: Time-domain responses\nThere 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.","category":"page"},{"location":"lib/plotting/#Plotting-functions","page":"Plotting","title":"Plotting functions","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [libpath*\"/plotting.jl\"]\nOrder = [:function]\nPrivate = false","category":"page"},{"location":"lib/plotting/#ControlSystemsBase.bodeplot","page":"Plotting","title":"ControlSystemsBase.bodeplot","text":"fig = bodeplot(sys, args...)\nbodeplot(LTISystem[sys1, sys2...], args...; plotphase=true, balance = true, kwargs...)\n\nCreate 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).\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.gangoffourplot-Tuple{Union{LTISystem, Vector}, Vector, Vararg{Any}}","page":"Plotting","title":"ControlSystemsBase.gangoffourplot","text":"fig = gangoffourplot(P::LTISystem, C::LTISystem; minimal=true, plotphase=false, Ms_lines = [1.0, 1.25, 1.5], Mt_lines = [], sigma = true, kwargs...)\n\nGang-of-Four plot.\n\nsigma determines whether a sigmaplot is used instead of a bodeplot for MIMO S and T. kwargs are sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"method"},{"location":"lib/plotting/#ControlSystemsBase.marginplot","page":"Plotting","title":"ControlSystemsBase.marginplot","text":"fig = marginplot(sys::LTISystem [,w::AbstractVector]; balance=true, kwargs...)\nmarginplot(sys::Vector{LTISystem}, w::AbstractVector; balance=true, kwargs...)\n\nPlot all the amplitude and phase margins of the system(s) sys.\n\nA frequency vector w can be optionally provided.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to RecipesBase.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.nicholsplot","page":"Plotting","title":"ControlSystemsBase.nicholsplot","text":"fig = nicholsplot{T<:LTISystem}(systems::Vector{T}, w::AbstractVector; kwargs...)\n\nCreate a Nichols plot of the LTISystem(s). A frequency vector w can be optionally provided.\n\nKeyword arguments:\n\ntext = true\nGains = [12, 6, 3, 1, 0.5, -0.5, -1, -3, -6, -10, -20, -40, -60]\npInc = 30\nsat = 0.4\nval = 0.85\nfontsize = 10\n\npInc determines the increment in degrees between phase lines.\n\nsat ∈ [0,1] determines the saturation of the gain lines\n\nval ∈ [0,1] determines the brightness of the gain lines\n\nAdditional 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\n\nThis function is based on code subject to the two-clause BSD licence Copyright 2011 Will Robertson Copyright 2011 Philipp Allgeuer\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.nyquistplot","page":"Plotting","title":"ControlSystemsBase.nyquistplot","text":"fig = nyquistplot(sys; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)\nnyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)\n\nCreate a Nyquist plot of the LTISystem(s). A frequency vector w can be optionally provided.\n\nunit_circle: if the unit circle should be displayed. The Nyquist curve crosses the unit circle at the gain crossover frequency.\nMs_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).\nMt_circles: draw circles corresponding to given levels of complementary sensitivity. Mt_circles can be supplied as a number or a vector of numbers.\ncritical_point: point on real axis to mark as critical for encirclements\nIf hz=true, the hover information will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.pzmap","page":"Plotting","title":"ControlSystemsBase.pzmap","text":"fig = pzmap(fig, system, args...; hz = false, kwargs...)\n\nCreate a pole-zero map of the LTISystem(s) in figure fig, args and kwargs will be sent to the scatter plot command.\n\nTo customize the unit-circle drawn for discrete systems, modify the line attributes, e.g., linecolor=:red.\n\nIf hz is true, all poles and zeros are scaled by 1/2π.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.rgaplot","page":"Plotting","title":"ControlSystemsBase.rgaplot","text":"rgaplot(sys, args...; hz=false)\nrgaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true)\n\nPlot the relative-gain array entries of the LTISystem(s). A frequency vector w can be optionally provided.\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\n\nkwargs is sent as argument to Plots.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#ControlSystemsBase.setPlotScale-Tuple{AbstractString}","page":"Plotting","title":"ControlSystemsBase.setPlotScale","text":"setPlotScale(str)\n\nSet 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/plotting/#ControlSystemsBase.sigmaplot","page":"Plotting","title":"ControlSystemsBase.sigmaplot","text":"sigmaplot(sys, args...; hz=false balance=true, extrema)\nsigmaplot(LTISystem[sys1, sys2...], args...; hz=false, balance=true, extrema)\n\nPlot the singular values of the frequency response of the LTISystem(s). A frequency vector w can be optionally provided.\n\nIf hz=true, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.\nbalance: Call balance_statespace on the system before plotting.\nextrema: Only plot the largest and smallest singular values.\n\nkwargs is sent as argument to Plots.plot.\n\n\n\n\n\n","category":"function"},{"location":"lib/plotting/#Examples","page":"Plotting","title":"Examples","text":"","category":"section"},{"location":"lib/plotting/#Bode-plot","page":"Plotting","title":"Bode plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: bode)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nws = exp10.(range(-2,stop=2,length=200))\nbodeplot(sys, ws)","category":"page"},{"location":"lib/plotting/#Sigma-plot","page":"Plotting","title":"Sigma plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: sigma)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nsigmaplot(sys)","category":"page"},{"location":"lib/plotting/#Margin","page":"Plotting","title":"Margin","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: margin)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nws = exp10.(range(-2,stop=2,length=200))\nmarginplot([tf1, tf2], ws)","category":"page"},{"location":"lib/plotting/#Gangoffour-plot","page":"Plotting","title":"Gangoffour plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: gangoffour)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1.0],[1,1])\ngangoffourplot(tf1, [tf(1), tf(5)])","category":"page"},{"location":"lib/plotting/#Nyquist-plot","page":"Plotting","title":"Nyquist plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: nyquist)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nws = exp10.(range(-2,stop=2,length=200))\nnyquistplot(sys, ws, Ms_circles=1.2, Mt_circles=1.2)","category":"page"},{"location":"lib/plotting/#Nichols-plot","page":"Plotting","title":"Nichols plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: nichols)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\nws = exp10.(range(-2,stop=2,length=200))\nnicholsplot(tf1,ws)","category":"page"},{"location":"lib/plotting/#Pole-zero-plot","page":"Plotting","title":"Pole-zero plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: pzmap)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf2 = tf([1/5,2],[1,1,1])\npzmap(c2d(tf2, 0.1))","category":"page"},{"location":"lib/plotting/#Rlocus-plot","page":"Plotting","title":"Rlocus plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: rlocus)","category":"page"},{"location":"lib/plotting/#Lsim-response-plot","page":"Plotting","title":"Lsim response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: lsim)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"sys = ss([-1 2; 0 1], [1 0; 1 1], [1 0; 0 1], [0.1 0; 0 -0.2])\nsysd = c2d(sys, 0.01)\nL = lqr(sysd, [1 0; 0 1], [1 0; 0 1])\nts = 0:0.01:5\nplot(lsim(sysd, (x,i)->-L*x, ts; x0=[1;2]), plotu=true)","category":"page"},{"location":"lib/plotting/#Impulse-response-plot","page":"Plotting","title":"Impulse response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: impulse)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nsysd = c2d(ss(sys), 0.01)\nplot(impulse(sysd, 5), l=:blue)","category":"page"},{"location":"lib/plotting/#Step-response-plot","page":"Plotting","title":"Step response plot","text":"","category":"section"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"(Image: step)","category":"page"},{"location":"lib/plotting/","page":"Plotting","title":"Plotting","text":"tf1 = tf([1],[1,1])\ntf2 = tf([1/5,2],[1,1,1])\nsys = [tf1 tf2]\nsysd = c2d(ss(sys), 0.01)\nres = step(sysd, 5)\nplot(res, l=(:dash, 4))\n# plot!(stepinfo(step(sysd[1,1], 5))) # adds extra info to the plot","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Pages = [\"analysis.md\"]","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"For robust analysis, see RobustAndOptimalControl.jl.","category":"page"},{"location":"lib/analysis/#Analysis","page":"Analysis","title":"Analysis","text":"","category":"section"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [\n libpath*\"/analysis.jl\", \n libpath*\"/matrix_comps.jl\", \n libpath*\"/types/conversion.jl\"\n ]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/analysis/#ControlSystemsBase.damp-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.damp","text":"Wn, zeta, ps = damp(sys)\n\nCompute the natural frequencies, Wn, and damping ratios, zeta, of the poles, ps, of sys\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.dampreport-Tuple{IO, LTISystem}","page":"Analysis","title":"ControlSystemsBase.dampreport","text":"dampreport(sys)\n\nDisplay a report of the poles, damping ratio, natural frequency, and time constant of the system sys\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.dcgain","page":"Analysis","title":"ControlSystemsBase.dcgain","text":"dcgain(sys, ϵ=0)\n\nCompute the dcgain of system sys.\n\nequal to G(0) for continuous-time systems and G(1) for discrete-time systems.\n\nϵ can be provided to evaluate the dcgain with a small perturbation into the stability region of the complex plane.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.delaymargin-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.delaymargin","text":"dₘ = delaymargin(G::LTISystem)\n\nReturn 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gangoffour-Tuple{LTISystem, LTISystem}","page":"Analysis","title":"ControlSystemsBase.gangoffour","text":"S, PS, CS, T = gangoffour(P, C; minimal=true)\ngangoffour(P::AbstractVector, C::AbstractVector; minimal=true)\n\nGiven 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.\n\nS = 1/(1+PC) Sensitivity function\nPS = (1+PC)\\P Load disturbance to measurement signal\nCS = (1+PC)\\C Measurement noise to control signal\nT = PC/(1+PC) Complementary sensitivity function\n\nIf minimal=true, minreal will be applied to all transfer functions.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gangofseven-Tuple{LTISystem, LTISystem, LTISystem}","page":"Analysis","title":"ControlSystemsBase.gangofseven","text":"S, PS, CS, T, RY, RU, RE = gangofseven(P,C,F)\n\nGiven 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.\n\nS = 1/(1+PC) Sensitivity function\nPS = P/(1+PC)\nCS = C/(1+PC)\nT = PC/(1+PC) Complementary sensitivity function\nRY = PCF/(1+PC)\nRU = CF/(1+P*C)\nRE = F/(1+P*C)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.margin-Tuple{LTISystem, AbstractVector{<:Real}}","page":"Analysis","title":"ControlSystemsBase.margin","text":"wgm, gm, wpm, pm = margin(sys::LTISystem, w::Vector; full=false, allMargins=false)\n\nreturns frequencies for gain margins, gain margins, frequencies for phase margins, phase margins\n\nIf !allMargins, return only the smallest margin\n\nIf full return also fullPhase See also delaymargin and RobustAndOptimalControl.diskmargin\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.markovparam-Tuple{AbstractStateSpace{<:Discrete}, Integer}","page":"Analysis","title":"ControlSystemsBase.markovparam","text":"markovparam(sys, n)\n\nCompute the nth markov parameter of discrete-time state-space system sys. This is defined as the following:\n\nh(0) = D\n\nh(n) = C*A^(n-1)*B\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.poles-Tuple{AbstractStateSpace}","page":"Analysis","title":"ControlSystemsBase.poles","text":"poles(sys)\n\nCompute the poles of system sys.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.reduce_sys-Tuple{AbstractMatrix, AbstractMatrix, AbstractMatrix, AbstractMatrix, AbstractFloat}","page":"Analysis","title":"ControlSystemsBase.reduce_sys","text":"reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)\n\nImplements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed A, B, C, D matrices. These are empty if there are no zeros.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.relative_gain_array-Tuple{AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.relative_gain_array","text":"relative_gain_array(A::AbstractMatrix; tol = 1.0e-15)\n\nReference: \"On the Relative Gain Array (RGA) with Singular and Rectangular Matrices\" Jeffrey Uhlmann https://arxiv.org/pdf/1805.10312.pdf\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.relative_gain_array-Tuple{Any, AbstractVector}","page":"Analysis","title":"ControlSystemsBase.relative_gain_array","text":"relative_gain_array(G, w::AbstractVector)\nrelative_gain_array(G, w::Number)\n\nCalculate the relative gain array of G at frequencies w. G(iω) .* pinv(tranpose(G(iω)))\n\nThe 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. \n\nThe 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.\nThe RGA is invariant to input/output scaling of G.\nIf the RGA contains large entries, the system may be sensitive to model errors, -Skogestad, \"Multivariable Feedback Control: Analysis and Design\":\nUncertainty in the input channels (diagonal input uncertainty). Plants with\nlarge 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\nElement uncertainty. Large RGA-elements imply sensitivity to element-by-element uncertainty.\nHowever, 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.\n\nThe 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\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.tzeros-Tuple{TransferFunction}","page":"Analysis","title":"ControlSystemsBase.tzeros","text":"tzeros(sys)\n\nCompute the invariant zeros of the system sys. If sys is a minimal realization, these are also the transmission zeros.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.zpkdata-Tuple{LTISystem}","page":"Analysis","title":"ControlSystemsBase.zpkdata","text":"z, p, k = zpkdata(sys)\n\nCompute the zeros, poles, and gains of system sys.\n\nReturns\n\nz : Matrix{Vector{ComplexF64}}, (ny × nu)\np : Matrix{Vector{ComplexF64}}, (ny × nu)\nk : Matrix{Float64}, (ny × nu)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.are-Tuple{Union{Continuous, Type{Continuous}}, AbstractMatrix, Any, Any, Any}","page":"Analysis","title":"ControlSystemsBase.are","text":"are(::Continuous, A, B, Q, R)\n\nCompute '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.\n\nIn an LQR problem, Q is associated with the state penalty xQx while R is associated with the control penalty uRu. See lqr for more details.\n\nUses MatrixEquations.arec. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.arec, note that they define the input arguments in a different order.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.are-Tuple{Union{Type{Discrete}, Discrete}, AbstractMatrix, Any, Any, Any}","page":"Analysis","title":"ControlSystemsBase.are","text":"are(::Discrete, A, B, Q, R; kwargs...)\n\nCompute 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\n\nIn an LQR problem, Q is associated with the state penalty xQx while R is associated with the control penalty uRu. See lqr for more details.\n\nUses MatrixEquations.ared. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.ared, note that they define the input arguments in a different order.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.balance","page":"Analysis","title":"ControlSystemsBase.balance","text":"S, P, B = balance(A[, perm=true])\n\nCompute 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).\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.balreal-Tuple{ST} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.balreal","text":"sysr, G, T = balreal(sys::StateSpace)\n\nCalculates 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.\n\nSee also gram, baltrunc.\n\nReference: Varga A., Balancing-free square-root algorithm for computing singular perturbation approximations.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.baltrunc-Tuple{ST} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.baltrunc","text":"sysr, G, T = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, n = nothing, residual = false)\n\nReduces 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.\n\nT 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.\n\nIf residual = true, matched static gain is achieved through \"residualization\", i.e., setting\n\n0 = A_21x_1 + A_22x_2 + B_2u\n\nwhere indices 1/2 correspond to the remaining/truncated states respectively.\n\nSee also gram, balreal\n\nGlad, Ljung, Reglerteori: Flervariabla och Olinjära metoder.\n\nFor more advanced model reduction, see RobustAndOptimalControl.jl - Model Reduction.\n\nExtended help\n\nNote: 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:\n\nThe outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.\nThe 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\n\nWithout 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.controllability-Union{Tuple{T}, Tuple{AbstractMatrix{T}, Any}} where T","page":"Analysis","title":"ControlSystemsBase.controllability","text":"controllability(A, B; atol, rtol)\ncontrollability(sys; atol, rtol)\n\nCheck for controllability of the pair (A, B) or sys using the PHB test.\n\nThe 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.\n\nTechnically, this function checks for controllability from the origin, also called reachability.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.covar-Tuple{AbstractStateSpace, Any}","page":"Analysis","title":"ControlSystemsBase.covar","text":"P = covar(sys, W)\n\nCalculate 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).\n\nRemark: 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.ctrb-Tuple{AbstractMatrix, AbstractVecOrMat}","page":"Analysis","title":"ControlSystemsBase.ctrb","text":"ctrb(A, B)\nctrb(sys)\n\nCompute the controllability matrix for the system described by (A, B) or sys.\n\nNote 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.gram-Tuple{AbstractStateSpace, Symbol}","page":"Analysis","title":"ControlSystemsBase.gram","text":"gram(sys, opt; kwargs...)\n\nCompute the grammian of system sys. If opt is :c, computes the controllability grammian. If opt is :o, computes the observability grammian.\n\nSee also grampd For keyword arguments, see grampd.\n\nExtended help\n\nNote: 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:\n\nThe outputs are scaled such that the maximum allowed control error, the maximum expected reference variation, or the maximum expected variation, is unity.\nThe 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\n\nWithout 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.grampd-Tuple{AbstractStateSpace, Symbol}","page":"Analysis","title":"ControlSystemsBase.grampd","text":"U = grampd(sys, opt; kwargs...)\n\nReturn 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.\n\nObtain a Cholesky object by Cholesky(U) for observability grammian\n\nUses MatrixEquations.plyapc/plyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.plyapc/plyapd\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.hinfnorm-Tuple{AbstractStateSpace{<:Continuous}}","page":"Analysis","title":"ControlSystemsBase.hinfnorm","text":"Ninf, ω_peak = hinfnorm(sys; tol=1e-6)\n\nCompute the H∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.\n\nNinf := sup_ω σ_max[sys(iω)] if G is stable (σ_max = largest singular value) := Inf' ifG` is unstable\n\ntol is an optional keyword argument for the desired relative accuracy for the computed H∞ norm (not an absolute certificate).\n\nsys is first converted to a state space model if needed.\n\nThe continuous-time L∞ norm computation implements the 'two-step algorithm' in:\nN.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.\n\nFor the discrete-time version, see:\nP. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.\n\nSee also linfnorm.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.innovation_form-Union{Tuple{ST}, Tuple{ST, Any, Any, Vararg{Any}}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.innovation_form","text":"sysi = innovation_form(sys, R1, R2[, R12])\nsysi = innovation_form(sys; sysw=I, syse=I, R1=I, R2=I)\n\nTakes a system\n\nx' = Ax + Bu + w ~ R1\ny = Cx + Du + e ~ R2\n\nand returns the system\n\nx' = Ax + Kv\ny = Cx + v\n\nwhere v is the innovation sequence.\n\nIf sysw (syse) is given, the covariance resulting in filtering noise with R1 (R2) through sysw (syse) is used as covariance.\n\nSee Stochastic Control, Chapter 4, Åström\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.innovation_form-Union{Tuple{ST}, Tuple{ST, Any}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.innovation_form","text":"sysi = innovation_form(sys, K)\n\nTakes a system\n\nx' = Ax + Bu + Kv\ny = Cx + Du + v\n\nand returns the system\n\nx' = Ax + Kv\ny = Cx + v\n\nwhere v is the innovation sequence.\n\nSee Stochastic Control, Chapter 4, Åström\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.linfnorm-Tuple{AbstractStateSpace}","page":"Analysis","title":"ControlSystemsBase.linfnorm","text":"Ninf, ω_peak = linfnorm(sys; tol=1e-6)\n\nCompute the L∞ norm Ninf of the LTI system sys, together with a frequency ω_peak at which the gain Ninf is achieved.\n\nNinf := sup_ω σ_max[sys(iω)] (σ_max denotes the largest singular value)\n\ntol is an optional keyword argument representing the desired relative accuracy for the computed L∞ norm (this is not an absolute certificate however).\n\nsys is first converted to a state space model if needed.\n\nThe continuous-time L∞ norm computation implements the 'two-step algorithm' in:\nN.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.\n\nFor the discrete-time version, see:\nP. Bongers, O. Bosgra, M. Steinbuch, 'L∞-norm calculation for generalized state space systems in continuous and discrete time', American Control Conference, 1991.\n\nSee also hinfnorm.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observability-Union{Tuple{T}, Tuple{AbstractMatrix{T}, Any}} where T","page":"Analysis","title":"ControlSystemsBase.observability","text":"observability(A, C; atol, rtol)\n\nCheck for observability of the pair (A, C) or sys using the PHB test.\n\nThe 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.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_controller-Tuple{Any, AbstractMatrix, AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.observer_controller","text":"cont = observer_controller(sys, L::AbstractMatrix, K::AbstractMatrix; direct=false)\n\nIf direct = false\n\nReturn 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.\n\nThis 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.\n\nRef: \"Computer-Controlled Systems\" Eq 4.37\n\nIf direct = true\n\nReturn 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.\n\nnote: Note\nTo 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.\n\nRef: Ref: \"Computer-Controlled Systems\" pp 140 and \"Computer-Controlled Systems\" pp 162 prob 4.7\n\nArguments:\n\nsys: Model of system\nL: State-feedback gain u = -Lx\nK: Observer gain\n\nSee also observer_predictor and innovation_form.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_filter-Tuple{AbstractStateSpace{<:Discrete}, AbstractMatrix}","page":"Analysis","title":"ControlSystemsBase.observer_filter","text":"observer_filter(sys, K; output_state = false)\n\nReturn the observer filter \n\nbeginaligned\nx(kk) = (I - KC)Ax(k-1k-1) + (I - KC)Bu(k-1) + Ky(k) \nendaligned\n\nwith the input equation [(I - KC)B K] * [u(k-1); y(k)].\n\nNote 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.\n\nThis 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.\n\nThe observer filter is equivalent to the observer_predictor for continuous-time systems.\n\nnote: Note\nTo 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.\n\nRef: \"Computer-Controlled Systems\" Eq 4.32\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.observer_predictor-Tuple{AbstractStateSpace, Any, Union{AbstractArray, UniformScaling}, Vararg{Any}}","page":"Analysis","title":"ControlSystemsBase.observer_predictor","text":"observer_predictor(sys::AbstractStateSpace, K; h::Int = 1, output_state = false)\nobserver_predictor(sys::AbstractStateSpace, R1, R2[, R12]; output_state = false)\n\nIf sys is continuous, return the observer predictor system\n\nbeginaligned\nx = (A - KC)x + (B-KD)u + Ky \ny = Cx + Du\nendaligned\n\nwith the input equation [B-KD K] * [u; y]\n\nIf 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).\n\nIf covariance matrices R1, R2 are given, the kalman gain K is calculated using kalman.\n\nIf output_state is true, the output is the state estimate x̂ instead of the output estimate ŷ.\n\nSee also innovation_form, observer_controller and observer_filter.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.obsv","page":"Analysis","title":"ControlSystemsBase.obsv","text":"obsv(A, C, n=size(A,1))\nobsv(sys, n=sys.nx)\n\nCompute 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.\n\nNote 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.plyap-Tuple{AbstractStateSpace, Vararg{Any}}","page":"Analysis","title":"ControlSystemsBase.plyap","text":"Xc = plyap(sys::AbstractStateSpace, Ql; kwargs...)\n\nLyapunov solver that takes the L Cholesky factor of Q and returns a triangular matrix Xc such that Xc*Xc' = X.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.similarity_transform-Union{Tuple{ST}, Tuple{ST, Any}} where ST<:AbstractStateSpace","page":"Analysis","title":"ControlSystemsBase.similarity_transform","text":"syst = similarity_transform(sys, T; unitary=false)\n\nPerform a similarity transform T : Tx̃ = x on sys such that\n\nà = T⁻¹AT\nB̃ = T⁻¹ B\nC̃ = CT\nD̃ = D\n\nIf unitary=true, T is assumed unitary and the matrix adjoint is used instead of the inverse. See also balance_statespace.\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#ControlSystemsBase.time_scale-Tuple{AbstractStateSpace{Continuous}, Any}","page":"Analysis","title":"ControlSystemsBase.time_scale","text":"time_scale(sys::AbstractStateSpace{Continuous}, a; balanced = false)\ntime_scale(G::TransferFunction{Continuous}, a; balanced = true)\n\nRescale the time axis (change time unit) of sys.\n\nFor 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.\n\nScaling of time for a function f(t) with Laplace transform F(s) can be stated as\n\nf(at) leftrightarrow dfrac1a Fbig(dfracsabig)\n\nThe 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.\n\nExample:\n\nThe 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. \n\nGs = tf(1, [1e-6, 1]) # micro-second time scale modeled in seconds\nGms = time_scale(Gs, 1e-6) # Change to micro-second time scale\nGms == tf(1, [1, 1]) # Gms now has micro-seconds as time unit\n\nThe next example illustrates how the time axis of a time-domain simulation changes by time scaling \n\nt = 0:0.1:50 # original time axis\na = 10 # Scaling factor\nsys1 = ssrand(1,1,5)\nres1 = step(sys1, t) # Perform original simulation\nsys2 = time_scale(sys, a) # Scale time\nres2 = step(sys2, t ./ a) # Simulate on scaled time axis, note the `1/a`\nisapprox(res1.y, res2.y, rtol=1e-3, atol=1e-3)\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#LinearAlgebra.lyap-Tuple{Union{Type{Discrete}, Discrete}, AbstractMatrix, Any}","page":"Analysis","title":"LinearAlgebra.lyap","text":"lyap(A, Q; kwargs...)\n\nCompute the solution X to the discrete Lyapunov equation AXA' - X + Q = 0.\n\nUses MatrixEquations.lyapc / MatrixEquations.lyapd. For keyword arguments, see the docstring of ControlSystemsBase.MatrixEquations.lyapc / ControlSystemsBase.MatrixEquations.lyapd\n\n\n\n\n\n","category":"method"},{"location":"lib/analysis/#LinearAlgebra.norm","page":"Analysis","title":"LinearAlgebra.norm","text":"norm(sys, p=2; tol=1e-6)\n\nnorm(sys) or norm(sys,2) computes the H2 norm of the LTI system sys.\n\nnorm(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.\n\ntol 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).\n\nsys is first converted to a StateSpace model if needed.\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#ControlSystemsBase.balance_statespace","page":"Analysis","title":"ControlSystemsBase.balance_statespace","text":"A, B, C, T = balance_statespace{S}(A::Matrix{S}, B::Matrix{S}, C::Matrix{S}, perm::Bool=false)\nsys, T = balance_statespace(sys::StateSpace, perm::Bool=false)\n\nComputes 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.\n\nThe inverse of sysb, T = balance_statespace(sys) is given by similarity_transform(sysb, T)\n\nThis is not the same as finding a balanced realization with equal and diagonal observability and reachability gramians, see balreal\n\n\n\n\n\n","category":"function"},{"location":"lib/analysis/#Videos","page":"Analysis","title":"Videos","text":"","category":"section"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"Basic usage of robustness analysis with JuliaControl","category":"page"},{"location":"lib/analysis/","page":"Analysis","title":"Analysis","text":"","category":"page"},{"location":"api/#Index","page":"API","title":"Index","text":"","category":"section"},{"location":"api/","page":"API","title":"API","text":"","category":"page"},{"location":"api/","page":"API","title":"API","text":"See additional API in RobustAndOptimalControl.jl: API","category":"page"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"Pages = [\"constructors.md\"]","category":"page"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"See also Connecting named systems together.","category":"page"},{"location":"lib/constructors/#Constructing-systems","page":"Constructors","title":"Constructing systems","text":"","category":"section"},{"location":"lib/constructors/","page":"Constructors","title":"Constructors","text":"append\nc2d\nfeedback\nfeedback2dof\nminreal\nparallel\nseries\nsminreal\nss\ntf\nzpk\ndelay\npade\nthiran\nssdata\nControlSystemsBase.seriesform","category":"page"},{"location":"lib/constructors/#ControlSystemsBase.append","page":"Constructors","title":"ControlSystemsBase.append","text":"append(systems::StateSpace...), append(systems::TransferFunction...)\n\nAppend systems in block diagonal form\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.c2d","page":"Constructors","title":"ControlSystemsBase.c2d","text":"sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\nGd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)\n\nConvert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).\n\nmethod = :tustin performs a bilinear transform with prewarp frequency w_prewarp.\n\nw_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also c2d_x0map\n\nExtended help\n\nZoH 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.\n\nFoH 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.\n\nTo 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.\n\nThe 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.\n\nClassical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as 02 ωgcTs 06 where ωgc is the gain-crossover frequency (rad/s).\n\n\n\n\n\nQd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)\nQd = c2d(sys::StateSpace{Discrete}, Qc::Matrix; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Discrete}, Qc::Matrix, Rc::Matrix; opt=:o)\n\nSample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.\n\nIf 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.\n\nnote: Note\nMeasurement 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.\n\nThe method used comes from theorem 5 in the reference below.\n\nRef: \"Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering\", Patrik Axelsson and Fredrik Gustafsson\n\nOn 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'. \n\nExample:\n\nThe 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.\n\nusing ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test\nsysc = DemoSystems.resonant()\nx0 = ones(sysc.nx)\nQc = [1 0.01; 0.01 2] # Continuous-time cost matrix for the state\nRc = I(1) # Continuous-time cost matrix for the input\n\nL = lqr(sysc, Qc, Rc)\ndynamics = function (xc, p, t)\n x = xc[1:sysc.nx]\n u = -L*x\n dx = sysc.A*x + sysc.B*u\n dc = dot(x, Qc, x) + dot(u, Rc, u)\n return [dx; dc]\nend\nprob = ODEProblem(dynamics, [x0; 0], (0.0, 10.0))\nsol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)\ncc = sol.u[end][end] # Continuous-time cost\n\n# Discrete-time version\nTs = 0.01 \nsysd = c2d(sysc, Ts)\nQd, Rd = c2d(sysd, Qc, Rc, opt=:c)\nLd = lqr(sysd, Qd, Rd)\nsold = lsim(sysd, (x, t) -> -Ld*x, 0:Ts:10, x0 = x0)\nfunction cost(x, u, Q, R)\n dot(x, Q, x) + dot(u, R, u)\nend\ncd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost\n@test cc ≈ cd rtol=0.01 # These should be similar\n\n\n\n\n\nc2d(G::DelayLtiSystem, Ts, method=:zoh)\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.feedback","page":"Constructors","title":"ControlSystemsBase.feedback","text":"feedback(sys)\nfeedback(sys1, sys2)\n\nFor a general LTI-system, feedback forms the negative feedback interconnection\n\n>-+ sys1 +-->\n | |\n (-)sys2 +\n\nIf no second system is given, negative identity feedback is assumed\n\n\n\n\n\nfeedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;\n U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],\n Wperm=:, Zperm=:, pos_feedback::Bool=false)\n\nBasic use feedback(sys1, sys2) forms the (negative) feedback interconnection\n\n ┌──────────────┐\n◄──────────┤ sys1 │◄──── Σ ◄──────\n │ │ │ │\n │ └──────────────┘ -1\n │ |\n │ ┌──────────────┐ │\n └─────►│ sys2 ├──────┘\n │ │\n └──────────────┘\n\nIf no second system sys2 is given, negative identity feedback (sys2 = 1) is assumed. The returned closed-loop system will have a state vector comprised of the state of sys1 followed by the state of sys2.\n\nAdvanced use feedback also supports more flexible use according to the figure below\n\n ┌──────────────┐\n z1◄─────┤ sys1 │◄──────w1\n ┌─── y1◄─────┤ │◄──────u1 ◄─┐\n │ └──────────────┘ │\n │ α\n │ ┌──────────────┐ │\n └──► u2─────►│ sys2 ├───────►y2──┘\n w2─────►│ ├───────►z2\n └──────────────┘\n\nU1, 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.\nY1, 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.\nU2, 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.\n\nSpecify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.\n\nNegative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).\n\nSee also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.\n\nThe 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.\n\nSee Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.feedback2dof","page":"Constructors","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P,R,S,T)\nfeedback2dof(B,A,R,S,T)\n\nReturn BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively\nReturn BT/(AR+ST)\n\n\n\n\n\nfeedback2dof(P::TransferFunction, C::TransferFunction, F::TransferFunction)\n\nReturn 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).\n\n +-------+\n | |\n +-----> F +----+\n | | | |\n | +-------+ |\n | +-------+ | +-------+\nr | - | | | | | y\n+--+-----> C +----+----> P +---+-->\n | | | | | |\n | +-------+ +-------+ |\n | |\n +--------------------------------+\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.minreal","page":"Constructors","title":"ControlSystemsBase.minreal","text":"minreal(tf::TransferFunction, eps=sqrt(eps()))\n\nCreate a minimal representation of each transfer function in tf by cancelling poles and zeros will promote system to an appropriate numeric type\n\n\n\n\n\nminreal(sys::StateSpace; fast=false, kwargs...)\n\nMinimal realisation algorithm from P. Van Dooreen, The generalized eigenstructure problem in linear system theory, IEEE Transactions on Automatic Control\n\nFor information about the options, see ?ControlSystemsBase.MatrixPencils.lsminreal\n\nSee also sminreal, which is both numerically exact and substantially faster than minreal, but with a much more limited potential in removing non-minimal dynamics.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.parallel","page":"Constructors","title":"ControlSystemsBase.parallel","text":"parallel(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in parallel, equivalent to sys2+sys1\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.series","page":"Constructors","title":"ControlSystemsBase.series","text":"series(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in series, equivalent to sys2*sys1\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.sminreal","page":"Constructors","title":"ControlSystemsBase.sminreal","text":"sminreal(sys)\n\nCompute 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.\n\nSystems 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.,\n\ntrunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0\ntrunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)\nsminreal(sys)\n\nIn 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.\n\nSee also minreal.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.ss","page":"Constructors","title":"ControlSystemsBase.ss","text":"sys = ss(A, B, C, D) # Continuous\nsys = ss(A, B, C, D, Ts) # Discrete\n\nCreate a state-space model sys::StateSpace{TE, T} with matrix element type T and TE is Continuous or <:Discrete.\n\nThis is a continuous-time model if Ts is omitted. Otherwise, this is a discrete-time model with sampling period Ts.\n\nD 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.\n\nTo associate names with states, inputs and outputs, see named_ss.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.tf","page":"Constructors","title":"ControlSystemsBase.tf","text":"sys = tf(num, den[, Ts])\nsys = tf(gain[, Ts])\n\nCreate as a fraction of polynomials:\n\nsys::TransferFunction{SisoRational{T,TR}} = numerator/denominator\n\nwhere T is the type of the coefficients in the polynomial.\n\nnum: the coefficients of the numerator polynomial. Either scalar or vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nden: the coefficients of the denominator polynomial. Either vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nTs: Sample time if discrete time system.\n\nThe polynomial coefficients are ordered starting from the highest order term. \n\nOther uses:\n\ntf(sys): Convert sys to tf form.\ntf(\"s\"), tf(\"z\"): Create the continuous-time transfer function s, or the discrete-time transfer function z.\nnumpoly(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.\n\nSee also: zpk, ss.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.zpk","page":"Constructors","title":"ControlSystemsBase.zpk","text":"zpk(gain[, Ts])\nzpk(num, den, k[, Ts])\nzpk(sys)\n\nCreate transfer function on zero pole gain form. The numerator and denominator are represented by their poles and zeros.\n\nsys::TransferFunction{SisoZpk{T,TR}} = k*numerator/denominator\n\nwhere T is the type of k and TR the type of the zeros/poles, usually Float64 and Complex{Float64}.\n\nnum: the roots of the numerator polynomial. Either scalar or vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nden: the roots of the denominator polynomial. Either vector to create SISO systems\n\nor an array of vectors to create MIMO system.\n\nk: The gain of the system. Obs, this is not the same as dcgain.\nTs: Sample time if discrete time system.\n\nOther uses:\n\nzpk(sys): Convert sys to zpk form.\nzpk(\"s\"): Create the transferfunction s.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.delay","page":"Constructors","title":"ControlSystemsBase.delay","text":"delay(tau)\ndelay(tau, Ts)\ndelay(T::Type{<:Number}, tau)\ndelay(T::Type{<:Number}, tau, Ts)\n\nCreate a pure time delay of length τ of type T.\n\nThe type T defaults to promote_type(Float64, typeof(tau)).\n\nIf Ts is given, the delay is discretized with sampling time Ts and a discrete-time StateSpace object is returned.\n\nExample:\n\nCreate a LTI system with an input delay of L\n\nL = 1\ntf(1, [1, 1])*delay(L)\ns = tf(\"s\")\ntf(1, [1, 1])*exp(-s*L) # Equivalent to the version above\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.pade","page":"Constructors","title":"ControlSystemsBase.pade","text":"pade(τ::Real, N::Int)\n\nCompute the Nth order Padé approximation of a time-delay of length τ.\n\nSee also thiran for discretization of delays.\n\n\n\n\n\npade(G::DelayLtiSystem, N)\n\nApproximate all time-delays in G by Padé approximations of degree N.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.thiran","page":"Constructors","title":"ControlSystemsBase.thiran","text":"thiran(τ::Real, Ts)\n\nDiscretize a potentially fractional delay τ as a Thiran all-pass filter with sample time Ts. \n\nThe Thiran all-pass filter gives an a maximally flat group delay.\n\nIf τ is an integer multiple of Ts, the Thiran all-pass filter reduces to z^-τTs.\n\nRef: 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.ssdata","page":"Constructors","title":"ControlSystemsBase.ssdata","text":"A, B, C, D = ssdata(sys)\n\nA destructor that outputs the statespace matrices.\n\n\n\n\n\n","category":"function"},{"location":"lib/constructors/#ControlSystemsBase.seriesform","page":"Constructors","title":"ControlSystemsBase.seriesform","text":"Gs, k = seriesform(G::TransferFunction{Discrete})\n\nConvert a transfer function G to a vector of second-order transfer functions and a scalar gain k, the product of which equals G.\n\n\n\n\n\n","category":"function"},{"location":"examples/smith_predictor/#Smith-predictor","page":"Smith predictor","title":"Smith predictor","text":"","category":"section"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"This example designs a controller for a plant with a time delay using the internal-model principle, which in this case implies the use of a Smith predictor. The plant is given by $ \\dfrac{1}{s + 1}e^{-s\\tau} = P_0 e^{-s\\tau}$","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"and the control architecture looks like this","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":" ┌──────┐ ┌─────────────┐\nr │ │ u │ │\n───+──+────────►│ C0 ├───────────┬─►│ P0*exp(-st) ├─┐y\n ▲ ▲ │ │ │ │ │ │\n -│ │- └──────┘ │ └─────────────┘ │\n │ │ │ │\n │ │ ┌──────────┐ ┌──────┐ │ │\n │ │ │ │ │ │ │ │\n │ └─┤1-exp(-st)│◄───┤ P0 │◄──┘ │\n │ │ │ │ │ │\n │ └──────────┘ └──────┘ │\n │ │\n └──────────────────────────────────────────────────┘","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"The benefit of this approach is that the controller C_0 can be designed for the nominal plant P_0 without time delay, and still behave well in the presence of the delay. We also see why we refer to such a controller as using an \"internal model\", due to the presence of a model of P_0 in the inner feedback path.","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We now set up the nominal system and PI controller","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"using ControlSystemsBase, Plots\nP0 = ss(-1, 1, 1, 0) # Nominal system","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We design a PI controller for nominal system using placePI. To verify the pole placement, use, e.g., dampreport(feedback(P0, C0))","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"ω0 = 2\nζ = 0.7\nC0, _ = placePI(P0, ω0, ζ)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"We then setup delayed plant + Smith predictor-based controller","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"τ = 8\nP = delay(τ) * P0\nC = feedback(C0, (1.0 - delay(τ))*P0) # form the inner feedback connection in the diagram above","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"using ControlSystems # Load full ControlSystems for delay-system simulation\nG = [feedback(P*C, 1) feedback(P, C)] # Reference step at t = 0 and load disturbance step at t = 15\nfig_timeresp = plot(lsim(G, (_,t) -> [1; t >= 15], 0:0.1:40), title=\"τ = $τ\")","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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). ","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"C_pred = feedback(1, C0*(ss(1.0) - delay(τ))*P0)\nfig_bode = bodeplot([C_pred, delay(-τ)], exp10.(-1:0.002:0.4), ls=[:solid :solid :dash :dash], title=\"\", lab=[\"Smith predictor\" \"\" \"Ideal predictor\" \"\"])\nplot!(yticks=[0.1, 1, 10], sp=1)\nplot!(yticks=0:180:1080, sp=2)","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"Check the Nyquist plot. Note that the Nyquist curve encircles -1 for τ > 2.99","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"fig_nyquist = nyquistplot(C * P, exp10.(-1:1e-4:2), title=\"τ = $τ\")","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"A video tutorial on delay systems is available here:","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"","category":"page"},{"location":"examples/smith_predictor/#Additional-design-methods-for-delay-systems","page":"Smith predictor","title":"Additional design methods for delay systems","text":"","category":"section"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.\nDiscretize 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.\nNeglect 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.\nNeglect 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.\nFrequency-domain methods such as manual loop shaping, and some forms of optimization-based tuning, handle time delays natively. ","category":"page"},{"location":"examples/smith_predictor/","page":"Smith predictor","title":"Smith predictor","text":"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.","category":"page"},{"location":"lib/timefreqresponse/#Time-and-Frequency-response-analysis","page":"Time and Frequency response","title":"Time and Frequency response analysis","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Pages = [\"timefreqresponse.md\"]","category":"page"},{"location":"lib/timefreqresponse/#Frequency-response","page":"Time and Frequency response","title":"Frequency response","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Frequency responses are calculated using freqresp, bode, sigma and nyquist. Frequency-response plots are obtained using bodeplot, sigmaplot, nyquistplot, marginplot and nicholsplot.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Any TransferFunction can be evaluated at a point using F(s), F(omega, true), F(z, false)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"F(s) evaluates the continuous-time transfer function F at s.\nF(omega,true) evaluates the discrete-time transfer function F at exp(i*Ts*omega)\nF(z,false) evaluates the discrete-time transfer function F at z","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"A video demonstrating frequency-response analysis in ControlSystems.jl is available below.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"","category":"page"},{"location":"lib/timefreqresponse/#Time-response-(simulation)","page":"Time and Frequency response","title":"Time response (simulation)","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"For more extensive nonlinear simulation capabilities, see the notes on ModelingToolkit and DifferentialEquations under The wider Julia ecosystem for control.","category":"page"},{"location":"lib/timefreqresponse/#Example-step-response:","page":"Time and Frequency response","title":"Example step response:","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"The following simulates a step response of a second-order system and plots the result.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"using ControlSystemsBase, Plots\nG = tf(1, [1, 1, 1])\nres = step(G, 20) # Simulate 20 seconds step response\nplot(res)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Using the function stepinfo, we can compute characteristics of a step response:","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"si = stepinfo(res)","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"We can also plot the StepInfo object","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"plot(si)","category":"page"},{"location":"lib/timefreqresponse/#Example-lsim:","page":"Time and Frequency response","title":"Example lsim:","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"The function lsim can take the control input as either","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"An array of equidistantly sampled values, in this case the argument u is expected to have the shape nu × n_time\nA 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.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"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:","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"using ControlSystems\nusing LinearAlgebra: I\nusing Plots\n\nA = [0 1; 0 0]\nB = [0;1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R)\n\nu(x,t) = -L*x .+ (t > 4) # State feedback + step disturbance\nt = 0:0.1:12\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\"); vline!([4], lab=\"Step disturbance\", l=(:black, :dash, 0.5))","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"A video demonstrating time-domain simulation in ControlSystems.jl is available below.","category":"page"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"","category":"page"},{"location":"lib/timefreqresponse/#Docstrings","page":"Time and Frequency response","title":"Docstrings","text":"","category":"section"},{"location":"lib/timefreqresponse/","page":"Time and Frequency response","title":"Time and Frequency response","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [libpath*\"/timeresp.jl\", libpath*\"/result_types.jl\", libpath*\"/freqresp.jl\", \"simulators.jl\"]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/timefreqresponse/#ControlSystems.Simulator","page":"Time and Frequency response","title":"ControlSystems.Simulator","text":"Simulator\n\nFields:\n\nP::StateSpace\nf = (x,p,t) -> x\ny = (x,t) -> y\n\n\n\n\n\n","category":"type"},{"location":"lib/timefreqresponse/#ControlSystems.Simulator-Union{Tuple{AbstractStateSpace}, Tuple{F}, Tuple{AbstractStateSpace, F}} where F","page":"Time and Frequency response","title":"ControlSystems.Simulator","text":"Simulator(P::StateSpace, u = (x,t) -> 0)\n\nUsed to simulate continuous-time systems. See function ?solve for additional info.\n\nUsage:\n\nusing OrdinaryDiffEq, Plots\ndt = 0.1\ntfinal = 20\nt = 0:dt:tfinal\nP = ss(tf(1,[2,1])^2)\nK = 5\nreference(x,t) = [1.]\ns = Simulator(P, reference)\nx0 = [0.,0]\ntspan  = (0.0,tfinal)\nsol = solve(s, x0, tspan, Tsit5())\nplot(t, s.y(sol, t)[:], lab=\"Open loop step response\")\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#Base.step-Tuple{AbstractStateSpace, AbstractVector}","page":"Time and Frequency response","title":"Base.step","text":"y, t, x = step(sys[, tfinal])\ny, t, x = step(sys[, t])\n\nCalculate 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. \n\nThe return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result. \n\ny has size (ny, length(t), nu), x has size (nx, length(t), nu)\n\nSee also stepinfo and lsim.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.impulse-Tuple{AbstractStateSpace, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.impulse","text":"y, t, x = impulse(sys[, tfinal])\ny, t, x = impulse(sys[, t])\n\nCalculate 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. \n\nThe return value is a structure of type SimResult. A SimResul can be plotted by plot(result), or destructured as y, t, x = result.\n\ny has size (ny, length(t), nu), x has size (nx, length(t), nu)\n\nSee also lsim.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.lsim!-Union{Tuple{T}, Tuple{LsimWorkspace{T}, AbstractStateSpace{<:Discrete}, Any, AbstractVector}} where T","page":"Time and Frequency response","title":"ControlSystemsBase.lsim!","text":"res = lsim!(ws::LsimWorkspace, sys::AbstractStateSpace{<:Discrete}, u, [t]; x0)\n\nIn-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.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.lsim-Tuple{AbstractStateSpace, AbstractVecOrMat, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.lsim","text":"result = lsim(sys, u[, t]; x0, method])\nresult = lsim(sys, u::Function, t; x0, method)\n\nCalculate the time response of system sys to input u. If x0 is omitted, a zero vector is used.\n\nThe result structure contains the fields y, t, x, u and can be destructured automatically by iteration, e.g.,\n\ny, t, x, u = result\n\nresult::SimResult can also be plotted directly:\n\nplot(result, plotu=true, plotx=false)\n\ny, x, u have time in the second dimension. Initial state x0 defaults to zero.\n\nContinuous-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.\n\nu 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.\n\nNote: 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.\n\nFor maximum performance, see function lsim!, available for discrete-time systems only.\n\nUsage example:\n\nusing ControlSystems\nusing LinearAlgebra: I\nusing Plots\n\nA = [0 1; 0 0]\nB = [0;1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R)\n\nu(x,t) = -L*x # Form control law\nt = 0:0.1:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\n# Alternative way of plotting\nres = lsim(sys,u,t,x0=x0)\nplot(res)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.stepinfo-Tuple{ControlSystemsBase.SimResult}","page":"Time and Frequency response","title":"ControlSystemsBase.stepinfo","text":"stepinfo(res::SimResult; y0 = nothing, yf = nothing, settling_th = 0.02, risetime_th = (0.1, 0.9))\n\nCompute the step response characteristics for a simulation result. The following information is computed and stored in a StepInfo struct:\n\ny0: The initial value of the response\nyf: The final value of the response\nstepsize: The size of the step\npeak: The peak value of the response\npeaktime: The time at which the peak occurs\novershoot: The percentage overshoot of the response\nundershoot: The percentage undershoot of the response. If the step response never reaches below the initial value, the undershoot is zero.\nsettlingtime: The time at which the response settles within settling_th of the final value\nsettlingtimeind: The index at which the response settles within settling_th of the final value\nrisetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value\n\nArguments:\n\nres: The result from a simulation using step (or lsim)\ny0: The initial value, if not provided, the first value of the response is used.\nyf: 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.\nsettling_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.\nrisetime_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.\n\nExample:\n\nG = tf([1], [1, 1, 1])\nres = step(G, 15)\nsi = stepinfo(res)\nplot(si)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.LsimWorkspace-Tuple{AbstractStateSpace, Int64}","page":"Time and Frequency response","title":"ControlSystemsBase.LsimWorkspace","text":"LsimWorkspace(sys::AbstractStateSpace, N::Int)\nLsimWorkspace(sys::AbstractStateSpace, u::AbstractMatrix)\nLsimWorkspace{T}(ny, nu, nx, N)\n\nGenerate 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. \n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.StepInfo","page":"Time and Frequency response","title":"ControlSystemsBase.StepInfo","text":"StepInfo\n\nComputed using stepinfo\n\nFields:\n\ny0: The initial value of the step response.\nyf: The final value of the step response.\nstepsize: The size of the step.\npeak: The peak value of the step response.\npeaktime: The time at which the peak occurs.\novershoot: The overshoot of the step response.\nsettlingtime: The time at which the step response has settled to within settling_th of the final value.\nsettlingtimeind::Int: The index at which the step response has settled to within settling_th of the final value.\nrisetime: The time at which the response rises from risetime_th[1] to risetime_th[2] of the final value \ni10::Int: The index at which the response reaches risetime_th[1]\ni90::Int: The index at which the response reaches risetime_th[2]\nres::SimResult{SR}: The simulation result used to compute the step response characteristics.\nsettling_th: The threshold used to compute settlingtime and settlingtimeind.\nrisetime_th: The thresholds used to compute risetime, i10, and i90.\n\n\n\n\n\n","category":"type"},{"location":"lib/timefreqresponse/#ControlSystemsBase.bode-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.bode","text":"mag, phase, w = bode(sys[, w]; unwrap=true)\n\nCompute the magnitude and phase parts of the frequency response of system sys at frequencies w. See also bodeplot\n\nmag 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.\n\nFor higher performance, see the function bodemag! that computes the magnitude only.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.bodemag!-Tuple{BodemagWorkspace, LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.bodemag!","text":"mag = bodemag!(ws::BodemagWorkspace, sys::LTISystem, w::AbstractVector)\n\nCompute 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ω).\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.evalfr-Tuple{AbstractStateSpace, Number}","page":"Time and Frequency response","title":"ControlSystemsBase.evalfr","text":"evalfr(sys, x)\n\nEvaluate the transfer function of the LTI system sys at the complex number s=x (continuous-time) or z=x (discrete-time).\n\nFor many values of x, use freqresp instead.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.freqresp!-Union{Tuple{T}, Tuple{Array{T, 3}, LTISystem, AbstractVector{<:Real}}} where T","page":"Time and Frequency response","title":"ControlSystemsBase.freqresp!","text":"freqresp!(R::Array{T, 3}, sys::LTISystem, w_vec::AbstractVector{<:Real})\n\nIn-place version of freqresp that takes a pre-allocated array R of size (ny, nu, nw)`\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.freqresp-Union{Tuple{W}, Tuple{LTISystem, AbstractVector{W}}} where W<:Real","page":"Time and Frequency response","title":"ControlSystemsBase.freqresp","text":"sys_fr = freqresp(sys, w)\n\nEvaluate the frequency response of a linear system\n\nw -> C*((iw*im*I - A)^-1)*B + D\n\nof system sys over the frequency vector w.\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.nyquist-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.nyquist","text":"re, im, w = nyquist(sys[, w])\n\nCompute the real and imaginary parts of the frequency response of system sys at frequencies w. See also nyquistplot\n\nre and im has size (ny, nu, length(w))\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.sigma-Tuple{LTISystem, AbstractVector}","page":"Time and Frequency response","title":"ControlSystemsBase.sigma","text":"sv, w = sigma(sys[, w])\n\nCompute the singular values sv of the frequency response of system sys at frequencies w. See also sigmaplot\n\nsv has size (min(ny, nu), length(w))\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.BodemagWorkspace-Tuple{LTISystem, Int64}","page":"Time and Frequency response","title":"ControlSystemsBase.BodemagWorkspace","text":"BodemagWorkspace(sys::LTISystem, N::Int)\nBodemagWorkspace(sys::LTISystem, ω::AbstractVector)\nBodemagWorkspace(R::Array{Complex{T}, 3}, mag::Array{T, 3})\nBodemagWorkspace{T}(ny, nu, N)\n\nGenerate 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. \n\nArguments:\n\nmag: The output array ∈ 𝐑(ny, nu, nω)\nR: Frequency-response array ∈ 𝐂(ny, nu, nω)\n\n\n\n\n\n","category":"method"},{"location":"lib/timefreqresponse/#ControlSystemsBase.TransferFunction-Tuple{Any}","page":"Time and Frequency response","title":"ControlSystemsBase.TransferFunction","text":"F(s), F(omega, true), F(z, false)\n\nNotation for frequency response evaluation.\n\nF(s) evaluates the continuous-time transfer function F at s.\nF(omega,true) evaluates the discrete-time transfer function F at exp(imTsomega)\nF(z,false) evaluates the discrete-time transfer function F at z\n\n\n\n\n\n","category":"method"},{"location":"man/introduction/#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"man/introduction/#Installation","page":"Introduction","title":"Installation","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"ControlSystems.jl is written in the Julia programming language and is available through the Julia package manager. To install Julia, follow the instructions at julialang.org.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"To install the full set of features of ControlSystems.jl, simply run the following command in the Julia REPL:","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Pkg; Pkg.add(\"ControlSystems\")","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"For workflows that do not require continuous-time simulation, you may instead opt to install the much lighter package ControlSystemsBase.jl","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Pkg; Pkg.add(\"ControlSystemsBase\")","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"ControlSystemsBase contains all functionality of ControlSystems except continuous-time simulation and root locus, and is considerably faster to load and precompile. To enjoy the faster pre-compilation, do not even install ControlSystems since this will cause pre-compilation of OrdinaryDiffEq, which can take several minutes.","category":"page"},{"location":"man/introduction/#Basic-functions","page":"Introduction","title":"Basic functions","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"DocTestSetup = quote\n using ControlSystems\n P = tf([1],[1,1])\n T = P/(1+P)\n plotsDir = joinpath(dirname(pathof(ControlSystems)), \"..\", \"docs\", \"build\", \"plots\")\n mkpath(plotsDir)\n save_docs_plot(name) = Plots.savefig(joinpath(plotsDir,name))\n save_docs_plot(p, name) = Plots.savefig(p, joinpath(plotsDir,name))\nend","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"State-space systems can be created using the function ss and transfer functions can be created using the function tf(num, den) or tf(num, den, Ts), where num and den are vectors representing the numerator and denominator of a rational function and Ts is the sample time for a discrete-time system. See tf or the section [Creating Systems] for more info. These functions can then be connected and modified using the operators +,-,*,/ and functions like append.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"Example:","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"P = tf([1.0],[1,1])\nT = P/(1+P)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0s + 1.0\n-------------------\n1.0s^2 + 3.0s + 2.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"Notice that the poles are not canceled automatically, to do this, the function minreal is available","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"minreal(T)\n\n# output\n\nTransferFunction{Continuous, ControlSystemsBase.SisoRational{Float64}}\n 1.0\n----------\n1.0s + 2.0\n\nContinuous-time transfer function model","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"or use feedback(P) to get a minimal realization directly (recommended):","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using ControlSystems # hide\nP = tf([1.0],[1,1]) # hide\nfeedback(P) # Equivalent to P/(1+P)","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"note: Numerical accuracy\nTransfer 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.","category":"page"},{"location":"man/introduction/#Plotting","page":"Introduction","title":"Plotting","text":"","category":"section"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"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.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"More examples of plots are provided in Plotting functions.","category":"page"},{"location":"man/introduction/","page":"Introduction","title":"Introduction","text":"using Plots\nbodeplot(tf(1,[1,2,1]))","category":"page"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"Pages = [\"synthesis.md\"]","category":"page"},{"location":"lib/synthesis/#Synthesis","page":"Synthesis","title":"Synthesis","text":"","category":"section"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"For H_infty and H_2 synthesis as well as more advanced LQG design, see RobustAndOptimalControl.","category":"page"},{"location":"lib/synthesis/","page":"Synthesis","title":"Synthesis","text":"Modules = [ControlSystems, ControlSystemsBase]\nPages = [\n libpath*\"/synthesis.jl\",\n libpath*\"/discrete.jl\",\n libpath*\"/types/lqg.jl\",\n libpath*\"/pid_design.jl\",\n libpath*\"/simplification.jl\",\n libpath*\"/connections.jl\",\n libpath*\"/sensitivity_functions.jl\",\n libpath*\"/utilities.jl\"\n ]\nOrder = [:function, :type]\nPrivate = false","category":"page"},{"location":"lib/synthesis/#ControlSystemsBase.kalman-Tuple{Any, Any, Any, Any, Any, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.kalman","text":"kalman(Continuous, A, C, R1, R2)\nkalman(Discrete, A, C, R1, R2; direct = false)\nkalman(sys, R1, R2; direct = false)\n\nCalculate the optimal asymptotic Kalman gain.\n\nIf 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.\n\nTo obtain a discrete-time approximation to a continuous-time LQG problem, the function c2d can be used to obtain corresponding discrete-time covariance matrices.\n\nTo 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.\n\nThe args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec/ared for more help.\n\nFAQ\n\nThis function requires\n\nR1 must be positive semi-definite\nR2 must be positive definite\nThe 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.lqr-Tuple{Union{Continuous, Type{Continuous}}, Any, Any, Any, Any, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.lqr","text":"lqr(sys, Q, R)\nlqr(Continuous, A, B, Q, R, args...; kwargs...)\nlqr(Discrete, A, B, Q, R, args...; kwargs...)\n\nCalculate the optimal gain matrix K for the state-feedback law u = -K*x that minimizes the cost function:\n\nJ = 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].\n\nSolve the LQR problem for state-space system sys. Works for both discrete and continuous time systems.\n\nThe args...; kwargs... are sent to the Riccati solver, allowing specification of cross-covariance etc. See ?MatrixEquations.arec / ared for more help.\n\nTo 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).\n\nTo obtain a discrete-time approximation to a continuous-time LQR problem, the function c2d can be used to obtain corresponding discrete-time cost matrices.\n\nExamples\n\nContinuous time\n\nusing LinearAlgebra # For identity matrix I\nusing Plots\nA = [0 1; 0 0]\nB = [0; 1]\nC = [1 0]\nsys = ss(A,B,C,0)\nQ = I\nR = I\nL = lqr(sys,Q,R) # lqr(Continuous,A,B,Q,R) can also be used\n\nu(x,t) = -L*x # Form control law,\nt=0:0.1:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nDiscrete time\n\nusing LinearAlgebra # For identity matrix I\nusing Plots\nTs = 0.1\nA = [1 Ts; 0 1]\nB = [0;1]\nC = [1 0]\nsys = ss(A, B, C, 0, Ts)\nQ = I\nR = I\nL = lqr(Discrete, A,B,Q,R) # lqr(sys,Q,R) can also be used\n\nu(x,t) = -L*x # Form control law,\nt=0:Ts:5\nx0 = [1,0]\ny, t, x, uout = lsim(sys,u,t,x0=x0)\nplot(t,x', lab=[\"Position\" \"Velocity\"], xlabel=\"Time [s]\")\n\nFAQ\n\nThis function requires\n\nQ must be positive semi-definite\nR must be positive definite\nThe 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\".\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.place","page":"Synthesis","title":"ControlSystemsBase.place","text":"place(A, B, p, opt=:c; direct = false)\nplace(sys::StateSpace, p, opt=:c; direct = false)\n\nCalculate the gain matrix K such that A - BK has eigenvalues p.\n\nplace(A, C, p, opt=:o)\nplace(sys::StateSpace, p, opt=:o)\n\nCalculate the observer gain matrix L such that A - LC has eigenvalues p.\n\nIf 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. \n\nNote: only apply direct = true to discrete-time systems.\n\nRef: \"Computer-Controlled Systems\" pp 140.\n\nUses Ackermann's formula for SISO systems and place_knvd for MIMO systems. \n\nPlease note that this function can be numerically sensitive, solving the placement problem in extended precision might be beneficial.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.place_knvd-Tuple{AbstractMatrix, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.place_knvd","text":"place_knvd(A::AbstractMatrix, B, λ; verbose = false, init = :s)\n\nRobust pole placement using the algorithm from\n\n\"Robust Pole Assignment in Linear State Feedback\", Kautsky, Nichols, Van Dooren\n\nThis implementation uses \"method 0\" for the X-step and the QR factorization for all factorizations.\n\nThis function will be called automatically when place is called with a MIMO system.\n\nArguments:\n\ninit: Determines the initialization strategy for the iterations for find the X matrix. Possible choices are :id (default), :rand, :s. \n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d","page":"Synthesis","title":"ControlSystemsBase.c2d","text":"sysd = c2d(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\nGd = c2d(G::TransferFunction{<:Continuous}, Ts, method=:zoh)\n\nConvert the continuous-time system sys into a discrete-time system with sample time Ts, using the specified method (:zoh, :foh, :fwdeuler or :tustin).\n\nmethod = :tustin performs a bilinear transform with prewarp frequency w_prewarp.\n\nw_prewarp: Frequency (rad/s) for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also c2d_x0map\n\nExtended help\n\nZoH 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.\n\nFoH 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.\n\nTo 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.\n\nThe 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.\n\nClassical rules-of-thumb for selecting the sample time for control design dictate that Ts should be chosen as 02 ωgcTs 06 where ωgc is the gain-crossover frequency (rad/s).\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.c2d-Tuple{AbstractStateSpace{<:Continuous}, AbstractMatrix, Real}","page":"Synthesis","title":"ControlSystemsBase.c2d","text":"Qd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Ts; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Continuous}, Qc::Matrix, Rc::Matrix, Ts; opt=:o)\nQd = c2d(sys::StateSpace{Discrete}, Qc::Matrix; opt=:o)\nQd, Rd = c2d(sys::StateSpace{Discrete}, Qc::Matrix, Rc::Matrix; opt=:o)\n\nSample a continuous-time covariance or LQR cost matrix to fit the provided discrete-time system.\n\nIf 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.\n\nnote: Note\nMeasurement 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.\n\nThe method used comes from theorem 5 in the reference below.\n\nRef: \"Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering\", Patrik Axelsson and Fredrik Gustafsson\n\nOn 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'. \n\nExample:\n\nThe 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.\n\nusing ControlSystemsBase, LinearAlgebra, OrdinaryDiffEq, Test\nsysc = DemoSystems.resonant()\nx0 = ones(sysc.nx)\nQc = [1 0.01; 0.01 2] # Continuous-time cost matrix for the state\nRc = I(1) # Continuous-time cost matrix for the input\n\nL = lqr(sysc, Qc, Rc)\ndynamics = function (xc, p, t)\n x = xc[1:sysc.nx]\n u = -L*x\n dx = sysc.A*x + sysc.B*u\n dc = dot(x, Qc, x) + dot(u, Rc, u)\n return [dx; dc]\nend\nprob = ODEProblem(dynamics, [x0; 0], (0.0, 10.0))\nsol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)\ncc = sol.u[end][end] # Continuous-time cost\n\n# Discrete-time version\nTs = 0.01 \nsysd = c2d(sysc, Ts)\nQd, Rd = c2d(sysd, Qc, Rc, opt=:c)\nLd = lqr(sysd, Qd, Rd)\nsold = lsim(sysd, (x, t) -> -Ld*x, 0:Ts:10, x0 = x0)\nfunction cost(x, u, Q, R)\n dot(x, Q, x) + dot(u, R, u)\nend\ncd = cost(sold.x, sold.u, Qd, Rd) # Discrete-time cost\n@test cc ≈ cd rtol=0.01 # These should be similar\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_poly2poly-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.c2d_poly2poly","text":"c2d_poly2poly(ro, Ts)\n\nreturns the polynomial coefficients in discrete time given polynomial coefficients in continuous time\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_roots2poly-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.c2d_roots2poly","text":"c2d_roots2poly(ro, Ts)\n\nreturns the polynomial coefficients in discrete time given a vector of roots in continuous time\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.c2d_x0map","page":"Synthesis","title":"ControlSystemsBase.c2d_x0map","text":"sysd, x0map = c2d_x0map(sys::AbstractStateSpace{<:Continuous}, Ts, method=:zoh; w_prewarp=0)\n\nReturns 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]\n\nSee c2d for further details.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c","page":"Synthesis","title":"ControlSystemsBase.d2c","text":"d2c(sys::AbstractStateSpace{<:Discrete}, method::Symbol = :zoh; w_prewarp=0)\n\nConvert discrete-time system to a continuous time system, assuming that the discrete-time system was discretized using method. Available methods are `:zoh, :fwdeuler´.\n\nw_prewarp: Frequency for pre-warping when using the Tustin method, has no effect for other methods.\n\nSee also d2c_exact.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c-2","page":"Synthesis","title":"ControlSystemsBase.d2c","text":"Qc = d2c(sys::AbstractStateSpace{<:Discrete}, Qd::AbstractMatrix; opt=:o)\n\nResample discrete-time covariance matrix belonging to sys to the equivalent continuous-time matrix.\n\nThe method used comes from theorem 5 in the reference below.\n\nIf opt = :c, the matrix is instead assumed to be a cost matrix for an LQR problem.\n\nRef: Discrete-time Solutions to the Continuous-time Differential Lyapunov Equation With Applications to Kalman Filtering Patrik Axelsson and Fredrik Gustafsson\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.d2c_exact","page":"Synthesis","title":"ControlSystemsBase.d2c_exact","text":"d2c_exact(sys::AbstractStateSpace{<:Discrete}, method = :causal)\n\nTranslate a discrete-time system to a continuous-time system by one of the substitutions\n\nz^-1 = e^-sT_s if method = :causal (default)\nz = e^sT_s if method = :acausal\n\nThe 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.\n\nThis method of translation is useful when analyzing hybrid continuous/discrete systems in the frequency domain and high accuracy is required.\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.dab-Tuple{Any, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.dab","text":"X,Y = dab(A,B,C)\n\nSolves the Diophantine-Aryabhatta-Bezout identity\n\nAX + BY = C, where A B C X and Y are polynomials and deg Y = deg A - 1.\n\nSee Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.rstc-Tuple","page":"Synthesis","title":"ControlSystemsBase.rstc","text":"See ?rstd for the discrete case\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.rstd-Tuple","page":"Synthesis","title":"ControlSystemsBase.rstd","text":"R,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR,AS)\nR,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO,AR)\nR,S,T = rstd(BPLUS,BMINUS,A,BM1,AM,AO)\n\nPolynomial synthesis in discrete time.\n\nPolynomial synthesis according to \"Computer-Controlled Systems\" ch 10 to design a controller R(q) u(k) = T(q) r(k) - S(q) y(k)\n\nInputs:\n\nBPLUS : Part of open loop numerator\nBMINUS : Part of open loop numerator\nA : Open loop denominator\nBM1 : Additional zeros\nAM : Closed loop denominator\nAO : Observer polynomial\nAR : Pre-specified factor of R,\n\ne.g integral part [1, -1]^k\n\nAS : Pre-specified factor of S,\n\ne.g notch filter [1, 0, w^2]\n\nOutputs: R,S,T : Polynomials in controller\n\nSee function dab how the solution to the Diophantine- Aryabhatta-Bezout identity is chosen.\n\nSee Computer-Controlled Systems: Theory and Design, Third Edition Karl Johan Åström, Björn Wittenmark\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.zpconv-NTuple{4, Any}","page":"Synthesis","title":"ControlSystemsBase.zpconv","text":"zpc(a,r,b,s)\n\nform 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\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.laglink-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.laglink","text":"laglink(a, M; [Ts])\n\nReturns 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\n\ndfracs + as + aM = M dfrac1 + sa1 + sMa\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.leadlink","page":"Synthesis","title":"ControlSystemsBase.leadlink","text":"leadlink(b, N, K=1; [Ts])\n\nReturns 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\n\nThe phase advance at ω = b√(N) can be plotted as a function of N with leadlinkcurve()\n\nValues of N < 1 will give a phase retarding link.\n\nKN dfracs + bs + bN = K dfrac1 + sb1 + s(bN)\n\nSee also leadlinkat laglink\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.leadlinkat","page":"Synthesis","title":"ControlSystemsBase.leadlinkat","text":"leadlinkat(ω, N, K=1; [Ts])\n\nReturns 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)\n\nThe phase advance at ω can be plotted as a function of N with leadlinkcurve()\n\nValues of N < 1 will give a phase retarding link.\n\nSee also leadlink laglink\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.leadlinkcurve","page":"Synthesis","title":"ControlSystemsBase.leadlinkcurve","text":"leadlinkcurve(start=1)\n\nPlot 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.\n\nSee also leadlink, leadlinkat\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.loopshapingPI-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.loopshapingPI","text":"C, kp, ki, fig, CF = loopshapingPI(P, ωp; ϕl, rl, phasemargin, form=:standard, doplot=false, Tf, F)\n\nSelects 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)\n\nThe parameters can be returned as one of several common representations chosen by form, the options are\n\n:standard - K_p(1 + 1(T_i s) + T_d s)\n:series - K_c(1 + 1(τ_i s))(τ_d s + 1)\n:parallel - K_p + K_is + K_d s\n\nIf phasemargin is supplied (in degrees), ϕl is selected such that the curve is moved to an angle of phasemargin - 180 degrees\n\nIf 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.\n\nTf: 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.\nF: A pre-designed filter to use instead of the default second-order filter that is used if Tf is given.\ndoplot plot the gangoffourplot and nyquistplot of the system.\n\nSee also loopshapingPID, pidplots, stabregionPID and placePI.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.loopshapingPID-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.loopshapingPID","text":"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)\n\nSelects 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.\n\nThe default values for Mt and ϕt are chosen to give a good design for processes with inertia, and may need tuning for simpler processes.\n\nThe gain of the resulting controller is generally increasing with increasing ω and Mt.\n\nArguments:\n\nP: A SISO plant.\nω: The specification frequency.\nMt: The magnitude of the complementary sensitivity function at the specification frequency, T(iω).\nϕt: The positive angle in degrees between the real axis and the tangent point.\ndoplot: If true, gang of four and Nyquist plots will be returned in fig.\nlb: log10 of lower bound for kd.\nub: log10 of upper bound for kd.\nTf: 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ω\nF: A pre-designed filter to use instead of the default second-order filter.\n\nThe parameters can be returned as one of several common representations chosen by form, the options are\n\n:standard - K_p(1 + 1(T_i s) + T_ds)\n:series - K_c(1 + 1(τ_i s))(τ_d s + 1)\n:parallel - K_p + K_is + K_d s\n\nSee also loopshapingPI, pidplots, stabregionPID and placePI.\n\nExample:\n\nP = tf(1, [1,0,0]) # A double integrator\nMt = 1.3 # Maximum magnitude of complementary sensitivity\nω = 1 # Frequency at which the specification holds\nC, kp, ki, kd, fig, CF = loopshapingPID(P, ω; Mt, ϕt = 75, doplot=true)\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.pid","page":"Synthesis","title":"ControlSystemsBase.pid","text":"C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts])\n\nCalculates and returns a PID controller. \n\nThe form can be chosen as one of the following\n\n:standard - Kp*(1 + 1/(Ti*s) + Td*s) \n:series - Kc*(1 + 1/(τi*s))*(τd*s + 1)\n:parallel - Kp + Ki/s + Kd*s\n\nIf 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. The filter used is 1 / (1 + s*Tf + (s*Tf)^2/2), where Tf can typically be chosen as Ti/N for a PI controller and Td/N for a PID controller, and N is commonly in the range 2 to 20. The state space will be returned on controllable canonical form.\n\nFor a discrete controller a positive Ts can be supplied. In this case, the continuous-time controller is discretized using the Tustin method.\n\nExamples\n\nC1 = pid(3.3, 1, 2) # Kd≠0 works without filter in tf form\nC2 = pid(3.3, 1, 2; Tf=0.3, state_space=true) # In statespace a filter is needed\nC3 = pid(2., 3, 0; Ts=0.4, state_space=true) # Discrete\n\nThe 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.pidplots-Tuple{LTISystem, Vararg{Any}}","page":"Synthesis","title":"ControlSystemsBase.pidplots","text":"pidplots(P, args...; params_p, params_i, params_d=0, form=:standard, ω=0, grid=false, kwargs...)\n\nDisplay the relevant plots related to closing the loop around process P with a PID controller supplied in params on one of the following forms:\n\n:standard - Kp*(1 + 1/(Ti*s) + Td*s) \n:series - Kc*(1 + 1/(τi*s))*(τd*s + 1)\n:parallel - Kp + Ki/s + Kd*s\n\nThe 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.\n\nAvailable 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.\n\nOne can also supply a frequency vector ω to be used in Bode and Nyquist plots.\n\nSee also loopshapingPI, stabregionPID\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.placePI-Union{Tuple{T}, Tuple{TransferFunction{<:Continuous, <:ControlSystemsBase.SisoRational{T}}, Any, Any}} where T","page":"Synthesis","title":"ControlSystemsBase.placePI","text":"C, kp, ki = placePI(P, ω₀, ζ; form=:standard)\n\nSelects 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.\n\nThe parameters can be returned as one of several common representations chose by form, the options are\n\n:standard - K_p(1 + 1(T_i s))\n:series - K_c(1 + 1(τ_i s)) (equivalent to above for PI controllers)\n:parallel - K_p + K_is\n\nC 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).\n\nSee also loopshapingPI\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.stabregionPID","page":"Synthesis","title":"ControlSystemsBase.stabregionPID","text":"kp, ki, fig = stabregionPID(P, [ω]; kd=0, doplot=false, form=:standard)\n\nSegments 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\n\nP(s)C(s) = -1 \nPC = P C = 1 \narg(P) + arg(C) = -π\n\nIf 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\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.sminreal-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.sminreal","text":"sminreal(sys)\n\nCompute 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.\n\nSystems 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.,\n\ntrunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0\ntrunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)\nsminreal(sys)\n\nIn 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.\n\nSee also minreal.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.add_input","page":"Synthesis","title":"ControlSystemsBase.add_input","text":"add_input(sys::AbstractStateSpace, B2::AbstractArray, D2 = 0)\n\nAdd inputs to sys by forming\n\nbeginaligned\nx = Ax + B B_2u \ny = Cx + D D_2u \nendaligned\n\nIf 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.\n\nExample: The following example forms an innovation model that takes innovations as inputs\n\nG = ssrand(2,2,3, Ts=1)\nK = kalman(G, I(G.nx), I(G.ny))\nsys = add_input(G, K)\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.add_output","page":"Synthesis","title":"ControlSystemsBase.add_output","text":"add_output(sys::AbstractStateSpace, C2::AbstractArray, D2 = 0)\n\nAdd outputs to sys by forming\n\nbeginaligned\nx = Ax + Bu \ny = C C_2x + D D_2u \nendaligned\n\nIf 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.\n\nWhen called with C2 = I(sys.nx), this function is in some settings known to as augstate.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.append-Tuple{Vararg{AbstractStateSpace}}","page":"Synthesis","title":"ControlSystemsBase.append","text":"append(systems::StateSpace...), append(systems::TransferFunction...)\n\nAppend systems in block diagonal form\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.array2mimo-Tuple{AbstractArray{<:LTISystem}}","page":"Synthesis","title":"ControlSystemsBase.array2mimo","text":"array2mimo(M::AbstractArray{<:LTISystem})\n\nTake an array of LTISystems and create a single MIMO system.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback-Tuple{AbstractStateSpace, AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.feedback","text":"feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;\n U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],\n Wperm=:, Zperm=:, pos_feedback::Bool=false)\n\nBasic use feedback(sys1, sys2) forms the (negative) feedback interconnection\n\n ┌──────────────┐\n◄──────────┤ sys1 │◄──── Σ ◄──────\n │ │ │ │\n │ └──────────────┘ -1\n │ |\n │ ┌──────────────┐ │\n └─────►│ sys2 ├──────┘\n │ │\n └──────────────┘\n\nIf no second system sys2 is given, negative identity feedback (sys2 = 1) is assumed. The returned closed-loop system will have a state vector comprised of the state of sys1 followed by the state of sys2.\n\nAdvanced use feedback also supports more flexible use according to the figure below\n\n ┌──────────────┐\n z1◄─────┤ sys1 │◄──────w1\n ┌─── y1◄─────┤ │◄──────u1 ◄─┐\n │ └──────────────┘ │\n │ α\n │ ┌──────────────┐ │\n └──► u2─────►│ sys2 ├───────►y2──┘\n w2─────►│ ├───────►z2\n └──────────────┘\n\nU1, 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.\nY1, 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.\nU2, 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.\n\nSpecify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to [z1; z2]) in the resulting statespace model.\n\nNegative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).\n\nSee also lft, starprod, sensitivity, input_sensitivity, output_sensitivity, comp_sensitivity, input_comp_sensitivity, output_comp_sensitivity, G_PS, G_CS.\n\nThe 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.\n\nSee Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback-Tuple{TransferFunction}","page":"Synthesis","title":"ControlSystemsBase.feedback","text":"feedback(sys)\nfeedback(sys1, sys2)\n\nFor a general LTI-system, feedback forms the negative feedback interconnection\n\n>-+ sys1 +-->\n | |\n (-)sys2 +\n\nIf no second system is given, negative identity feedback is assumed\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback2dof-Tuple{TransferFunction, Any, Any, Any}","page":"Synthesis","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P,R,S,T)\nfeedback2dof(B,A,R,S,T)\n\nReturn BT/(AR+ST) where B and A are the numerator and denominator polynomials of P respectively\nReturn BT/(AR+ST)\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.feedback2dof-Union{Tuple{TE}, Tuple{TransferFunction{TE}, TransferFunction{TE}, TransferFunction{TE}}} where TE","page":"Synthesis","title":"ControlSystemsBase.feedback2dof","text":"feedback2dof(P::TransferFunction, C::TransferFunction, F::TransferFunction)\n\nReturn 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).\n\n +-------+\n | |\n +-----> F +----+\n | | | |\n | +-------+ |\n | +-------+ | +-------+\nr | - | | | | | y\n+--+-----> C +----+----> P +---+-->\n | | | | | |\n | +-------+ +-------+ |\n | |\n +--------------------------------+\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.lft","page":"Synthesis","title":"ControlSystemsBase.lft","text":"lft(G, Δ, type=:l)\n\nLower and upper linear fractional transformation between systems G and Δ.\n\nSpecify :l lor lower LFT, and :u for upper LFT.\n\nG must have more inputs and outputs than Δ has outputs and inputs.\n\nFor details, see Chapter 9.1 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.parallel-Tuple{LTISystem, LTISystem}","page":"Synthesis","title":"ControlSystemsBase.parallel","text":"parallel(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in parallel, equivalent to sys2+sys1\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.series-Tuple{LTISystem, LTISystem}","page":"Synthesis","title":"ControlSystemsBase.series","text":"series(sys1::LTISystem, sys2::LTISystem)\n\nConnect systems in series, equivalent to sys2*sys1\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.starprod-Tuple{Any, Any, Int64, Int64}","page":"Synthesis","title":"ControlSystemsBase.starprod","text":"starprod(sys1, sys2, dimu, dimy)\n\nCompute the Redheffer star product.\n\nlength(U1) = length(Y2) = dimu and length(Y1) = length(U2) = dimy\n\nFor details, see Chapter 9.3 in Zhou, K. and JC Doyle. Essentials of robust control, Prentice hall (NJ), 1998\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.G_CS-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.G_CS","text":"G_CS(P, C)\n\nThe 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.G_PS-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.G_PS","text":"G_PS(P, C)\n\nThe 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.comp_sensitivity-Tuple","page":"Synthesis","title":"ControlSystemsBase.comp_sensitivity","text":"See output_comp_sensitivity\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.extended_gangoffour","page":"Synthesis","title":"ControlSystemsBase.extended_gangoffour","text":"extended_gangoffour(P, C, pos=true)\n\nReturns a single statespace system that maps \n\nw1 reference or measurement noise\nw2 load disturbance\n\nto\n\nz1 control error\nz2 control input\n\n z1 z2\n ▲ ┌─────┐ ▲ ┌─────┐\n │ │ │ │ │ │\nw1──+─┴─►│ C ├──┴───+─►│ P ├─┐\n │ │ │ │ │ │ │\n │ └─────┘ │ └─────┘ │\n │ w2 │\n └────────────────────────────┘\n\nThe returned system has the transfer-function matrix\n\nbeginbmatrix\nI C\nendbmatrix (I + PC)^-1 beginbmatrix\nI P\nendbmatrix\n\nor in code\n\n# For SISO P\nS = G[1, 1]\nPS = G[1, 2]\nCS = G[2, 1]\nT = G[2, 2]\n\n# For MIMO P\nS = G[1:P.ny, 1:P.nu]\nPS = G[1:P.ny, P.ny+1:end]\nCS = G[P.ny+1:end, 1:P.ny]\nT = G[P.ny+1:end, P.ny+1:end] # Input complimentary sensitivity function\n\nThe gang of four can be plotted like so\n\nGcl = extended_gangoffour(G, C) # Form closed-loop system\nbodeplot(Gcl, lab=[\"S\" \"CS\" \"PS\" \"T\"], plotphase=false) |> display # Plot gang of four\n\nNote, 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.\n\n\n\n\n\n","category":"function"},{"location":"lib/synthesis/#ControlSystemsBase.input_comp_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.input_comp_sensitivity","text":"input_comp_sensitivity(P,C)\n\nTransfer function from load disturbance to control signal.\n\n\"Input\" signifies that the transfer function is from the input of the plant.\n\"Complimentary\" signifies that the transfer function is to an output (in this case controller output)\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.input_resolvent-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.input_resolvent","text":"input_resolvent(sys::AbstractStateSpace)\n\nReturn the input-mapped resolvent of sys\n\n(sI - A)^-1B\n\ni.e., the system ss(A, B, I, 0).\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.input_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.input_sensitivity","text":"input_sensitivity(P, C)\n\nTransfer function from load disturbance to total plant input.\n\n\"Input\" signifies that the transfer function is from the input of the plant.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.output_comp_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.output_comp_sensitivity","text":"output_comp_sensitivity(P,C)\n\nTransfer function from measurement noise / reference to plant output.\n\n\"output\" signifies that the transfer function is from the output of the plant.\n\"Complimentary\" signifies that the transfer function is to an output (in this case plant output)\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.output_sensitivity-Tuple{Any, Any}","page":"Synthesis","title":"ControlSystemsBase.output_sensitivity","text":"output_sensitivity(P, C)\n\nTransfer function from measurement noise / reference to control error.\n\n\"output\" signifies that the transfer function is from the output of the plant.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.resolvent-Tuple{AbstractStateSpace}","page":"Synthesis","title":"ControlSystemsBase.resolvent","text":"resolvent(sys::AbstractStateSpace)\n\nReturn the resolvent of sys\n\n(sI - A)^-1\n\ni.e., the system ss(A, I, I, 0).\n\nSee also input_resolvent.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sensitivity-Tuple","page":"Synthesis","title":"ControlSystemsBase.sensitivity","text":"See output_sensitivity\n\nThe 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\n\nϕ_m 2textsin^-1(frac12M_S) g_m fracM_SM_S-1\n\nGenerally, 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.\n\n ▲\n │e₁\n │ ┌─────┐\nd₁────+──┴──► P ├─────┬──►e₄\n │ └─────┘ │\n │ │\n │ ┌─────┐ -│\n e₂◄──┴─────┤ C ◄──┬──+───d₂\n └─────┘ │\n │e₃\n ▼\n\ninput_sensitivity is the transfer function from d₁ to e₁, (I + CP)⁻¹\noutput_sensitivity is the transfer function from d₂ to e₃, (I + PC)⁻¹\ninput_comp_sensitivity is the transfer function from d₁ to e₂, (I + CP)⁻¹CP\noutput_comp_sensitivity is the transfer function from d₂ to e₄, (I + PC)⁻¹PC\nG_PS is the transfer function from d₁ to e₄, (1 + PC)⁻¹P\nG_CS is the transfer function from d₂ to e₂, (1 + CP)⁻¹C\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.bodev-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.bodev","text":"bodev(sys::LTISystem, w::AbstractVector; $(Expr(:kw, :unwrap, true)))\n\nFor use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.bodev-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.bodev","text":"bodev(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as bode but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Tuple{AbstractMatrix, AbstractVector{<:Real}}","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(G::AbstractMatrix, w_vec::AbstractVector{<:Real}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Tuple{Number, AbstractVector{<:Real}}","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(G::Number, w_vec::AbstractVector{<:Real}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.freqrespv-Union{Tuple{var\"#450#W\"}, Tuple{LTISystem, AbstractVector{var\"#450#W\"}}} where var\"#450#W\"<:Real","page":"Synthesis","title":"ControlSystemsBase.freqrespv","text":"freqrespv(sys::LTISystem, w_vec::AbstractVector{W}; )\n\nFor use with SISO systems where it acts the same as freqresp but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.nyquistv-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.nyquistv","text":"nyquistv(sys::LTISystem, w::AbstractVector; )\n\nFor use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.nyquistv-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.nyquistv","text":"nyquistv(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as nyquist but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sigmav-Tuple{LTISystem, AbstractVector}","page":"Synthesis","title":"ControlSystemsBase.sigmav","text":"sigmav(sys::LTISystem, w::AbstractVector; )\n\nFor use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"lib/synthesis/#ControlSystemsBase.sigmav-Tuple{LTISystem}","page":"Synthesis","title":"ControlSystemsBase.sigmav","text":"sigmav(sys::LTISystem; )\n\nFor use with SISO systems where it acts the same as sigma but with the extra dimensions removed in the returned values.\n\n\n\n\n\n","category":"method"},{"location":"examples/tuning_from_data/#Tuning-a-PID-controller-from-data","page":"Tune a controller using experimental data","title":"Tuning a PID controller from data","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"In this example, we will consider a very commonly occurring workflow: using process data to tune a PID controller.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The two main steps involved in this workflow are:","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Estimate a process model from data\nCharacterize the uncertainty in the estimated model\nDesign a controller based on the estimated model\nVerify that the controller is robust with respect to the estimated model uncertainty","category":"page"},{"location":"examples/tuning_from_data/#Estimation-of-a-model","page":"Tune a controller using experimental data","title":"Estimation of a model","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"In this example, which is split into two parts, we will consider tuning a velocity controller for a flexible robot arm. Part 1 is available here: Flexible Robot Arm Part 1: Estimation of a model.. The system identification uses the package ControlSystemIdentification.jl.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The rest of this example makes up part 2, tuning of the controller. We simply replicate the relevant code from part 1 to get the estimated model, and then use the estimated model to tune controllers.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using DelimitedFiles, Plots\nusing ControlSystemIdentification, ControlSystems\n\nurl = \"https://ftp.esat.kuleuven.be/pub/SISTA/data/mechanical/robot_arm.dat.gz\"\nzipfilename = \"/tmp/flex.dat.gz\"\npath = Base.download(url, zipfilename)\nrun(`gunzip -f $path`)\ndata = readdlm(path[1:end-3])\nu = data[:, 1]' # torque\ny = data[:, 2]' # acceleration\nd = iddata(y, u, 0.01) # sample time not specified for data, 0.01 is a guess\nPacc = subspaceid(d, 4, focus=:prediction) # Estimate the process model using subspace-based identification","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Since the data used for the system identification had acceleration rather than velocity as output, we multiply the estimated model by the transfer function 1s 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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"s = tf(\"s\")\nP = 1/s * d2c(-Pacc.sys)\nbodeplot(P)","category":"page"},{"location":"examples/tuning_from_data/#Dealing-with-model-uncertainty","page":"Tune a controller using experimental data","title":"Dealing with model uncertainty","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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(iomega):","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"coherenceplot(d)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Since our coherence drops significantly above omega = 130rad/s, we will try to design a controller that yields a complementary sensitivity function T that has low gain above this frequency.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/#Controller-tuning","page":"Tune a controller using experimental data","title":"Controller tuning","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We could take multiple different approaches to tuning the PID controller, a few alternatives are listed here","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Trial and error in simulation or experiment.\nManual loop shaping\nAutomatic loop shaping\nStep-response optimization (example)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Here, we will attempt a manual loop-shaping approach using the function loopshapingPID, and then then compare the result to a pole-placement controller.","category":"page"},{"location":"examples/tuning_from_data/#Manual-loop-shaping","page":"Tune a controller using experimental data","title":"Manual loop shaping","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"K = 10\nTf = 0.4\nTi = 4\nTd = 0.1\nCF = pid(K, Ti, Td; Tf)\nmarginplot(P*CF)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Here, we have selected the proportional gain Klarge 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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The pid function returns the PI controller with the second-order lowpass filter already applied.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Next, we form the closed-loop system G from reference to output an plot a step response","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"G = feedback(P*CF)\nplot(step(G, 50), label=\"Step response\")","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using TrajectoryLimiters\nẋM = 1 # Velocity limit\nẍM = 0.01 # Acceleration limit\nlimiter = TrajectoryLimiter(d.Ts, ẋM, ẍM)\ninputstep, vel, acc = limiter([0; ones(5000)])\ntimevec = 0:d.Ts:50\nplot(step(G, 50), label=\"Step response\")\nplot!(lsim(G, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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) ","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"gangoffourplot(P, CF)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The gang of four indicates that we have a robust tuning, no uncomfortably large peaks appears in either T or S.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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).","category":"page"},{"location":"examples/tuning_from_data/#Pole-placement","page":"Tune a controller using experimental data","title":"Pole placement","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We start by inspecting the pole locations of the open-loop plant","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"pzmap(P)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"As expected, we have 2 resonant pole pairs.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"current_poles = poles(P)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"The integrator pole can be placed to achieve a desired bandwidth. Here, we place it in -30rad/s to achieve a faster response than the PID controller achieved.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"desired_poles = -[80, 80, 37, 37, 30];","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We compute the state-feedback gain L using the function place, and also compute an observer gain K using the rule of thumb that the observer poles should be approximately twice as fast as the system poles.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"L = place(P, desired_poles, :c)\nK = place(P, 2*desired_poles, :o)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Cpp = observer_controller(P, L, K)\nGpp = feedback(P*Cpp)\nplot(lsim(Gpp, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black), legend=:bottomright)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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 ω = 130rad/s above which we couldn't trust the model.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"gangoffourplot(P, Cpp)\nvline!(fill(130, 1, 4), label=\"\\$ω = 130\\$\", l=(:dash, :black))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"p1 = current_poles[2]\np2 = current_poles[4]\n\np1_new = abs(p1) * cis(-pi + deg2rad(65)) # Place the pole with the same magnitude, but with an angle of -pi + 65 degrees\np2_new = abs(p2) * cis(-pi + deg2rad(65))\ndesired_poles = [-20, p1_new, conj(p1_new), p2_new, conj(p2_new)]\nL = place(P, desired_poles, :c) |> real\nK = place(P, 2*desired_poles, :o) |> real\nCpp = observer_controller(P, L, K)\nGpp = feedback(P*Cpp)\nf1 = plot(lsim(Gpp, inputstep', timevec), label=\"Smooth step response\")\nplot!(timevec, inputstep, label=\"Smooth reference trajectory\", l=(:dash, :black), legend=:bottomright)\n\nf2 = gangoffourplot(P, Cpp)\nvline!(fill(130, 1, 4), label=\"\\$ω = 130\\$\", l=(:dash, :black))\nplot(f1, f2, size=(800, 600))","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"We still have a nice step response using this controller, but this time, we have a rolloff in T that starts around the frequency ω = 130rad/s.","category":"page"},{"location":"examples/tuning_from_data/#C-Code-generation","page":"Tune a controller using experimental data","title":"C-Code generation","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"using SymbolicControlSystems\nCdiscrete = c2d(Cpp, d.Ts, :tustin)\nSymbolicControlSystems.ccode(Cdiscrete)","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"This produces the following C-code for filtering the error signal through the controller transfer function","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"#include \n#include \n\nvoid transfer_function(double *y, double u) {\n static double x[5] = {0}; // Current state\n double xp[5] = {0}; // Next state\n int i;\n\n // Advance the state xp = Ax + Bu\n xp[0] = (1.2608412916795442*u - 0.35051915762703334*x[0] + 0.0018847792079810998*x[1] - 0.0035104037080211504*x[2] + 0.0022503125347378308*x[3] + 0.00019318421187795658*x[4]);\n xp[1] = (45.346976964169166*u - 49.856146529754966*x[0] + 0.19058339536496746*x[1] + 0.58214123400704609*x[2] - 0.068048140252114517*x[3] - 0.03667586076286556*x[4]);\n xp[2] = (18.14135831274827*u - 19.16237014106056*x[0] - 0.84117137404200237*x[1] + 0.7024229589860792*x[2] + 0.018736385625077446*x[3] - 0.008392059099094502*x[4]);\n xp[3] = (190.59457176680613*u - 161.57645282794124*x[0] - 0.23872534677018914*x[1] + 1.0884789050298469*x[2] + 0.32394494701618637*x[3] + 0.32518305451736074*x[4]);\n xp[4] = (18.392870361917002*u - 0.43306059549357445*x[0] + 0.60377162139631557*x[1] + 0.62662564832184231*x[2] - 0.48738482327867771*x[3] + 0.98218650191968704*x[4]);\n\n // Accumulate the output y = C*x + D*u\n y[0] = (182.81664929547824*u - 63.477219815374006*x[0] + 3.5715419988427302*x[1] + 4.1831558072019464*x[2] - 1.0447833362501759*x[3] + 0.27420732436215378*x[4]);\n\n // Make the predicted state the current state\n for (i=0; i < 5; ++i) {\n x[i] = xp[i];\n }\n}","category":"page"},{"location":"examples/tuning_from_data/#Summary","page":"Tune a controller using experimental data","title":"Summary","text":"","category":"section"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"This tutorial has shown how to follow a workflow that consists of","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"Estimate a process model using experimental data.\nDesign 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.\nSimulate the closed-loop system and analyze its robustness properties. Model uncertainty was considered using the coherence function.\nGenerate C-code for one of the controllers.","category":"page"},{"location":"examples/tuning_from_data/","page":"Tune a controller using experimental data","title":"Tune a controller using experimental data","text":"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.","category":"page"},{"location":"examples/zoh/#Analysis-of-sampled-data-systems","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data systems","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"A sampled-data system contains both continuous-time and discrete-time components, such as a continuous-time plant and a discrete-time controller. In this example, we will look at how to analyze such systems using the ControlSystems.jl package. To learn more about the theory of sampled-data systems, consult the reference mentioned at the end of this page.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"First, we analyze the effect of ZoH sampling in continuous time and compare it to the equivalent discrete-time system","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"We will consider a simple second-order system P, which we define in continuous time:","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"using ControlSystems, Plots\ns = tf('s')\nP = tf(0.1, [1, 0.1, 0.1])","category":"page"},{"location":"examples/zoh/#Continuous-to-discrete","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Continuous to discrete","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Next, we discretize this system using the standard c2d function, which uses ZoH sampling by default. We compare the frequency response of the discretized system with the frequency response of the original continuous-time system multiplied by the transfer function of the ZoH operator [CCS]","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Z(s) = dfrac1 - e^-T_s sT_s s","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"[CCS]: Åström, K. J., & Wittenmark, B. (1997). Computer-Controlled Systems: Theory and Design.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Ts = 1 # Sample interval\nZ = (1 - delay(Ts))/(Ts*s) # The transfer function of the ZoH operator\nPd = c2d(P, Ts) # Discrete-time system obtained by ZoH sampling\nPz = P*Z # The continuous-time version of the discrete-time system\n\nwd = exp10.(-2:0.01:log10(2*0.5))\nbodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Pz, wd, lab=\"\\$P(s)Z(s)\\$\")\nbodeplot!(Pd, wd, lab=\"\\$P_d(z)\\$ (ZoH sampling)\", l=:dash)\nvline!([0.5 0.5], l=(:black, :dash), lab=\"Nyquist freq.\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"The step response of Pz = P(s) Z(s) matches the discrete output of P_d(z) delayed by half the sample time","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"resP = step(P, 12)\nresPz = step(Pz, 12)\nresPd = step(Pd, 12)\nplot([resP, resPz, resPd], lab=[\"P\" \"Pz\" \"Pd\"])\n\nt_shift = resPd.t .+ Ts / 2\nplot!(t_shift, resPd.y[:], lab=\"Pd shifted\", m=:o, l=:dash)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"With a piecewise constant input, even if it's not a step, we get the same result","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Tf = 20\nufun = (x,t)->[sin(2pi*floor(t/Ts)*Ts/5)]\nresP = lsim(P, ufun, Tf)\nresPz = lsim(Pz, ufun, 0:0.01:Tf)\nresPd = lsim(Pd, ufun, Tf)\nplot([resP, resPz, resPd], lab=[\"P\" \"Pz\" \"Pd\"])\n\nt_shift = resPd.t .+ Ts / 2\nplot!(t_shift, resPd.y[:], lab=\"Pd shifted\", m=:o)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Tf = 100\nufun = (x,t)->[sin(2pi*t/5)]\nresP = lsim(P, ufun, Tf)\nresPz = lsim(Pz, ufun, 0:0.01:Tf)\nresPd = lsim(Pd, ufun, Tf)\nplot([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)))","category":"page"},{"location":"examples/zoh/#Discrete-to-continuous","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Discrete to continuous","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"wd = exp10.(LinRange(-2, 1, 200))\nPdc = d2c_exact(ss(Pd))\nPc = d2c(Pd)\nbodeplot(Pd, wd, lab=\"\\$P_d(z)\\$\")\nbodeplot!(Pdc, wd, lab=\"\\$P_d(s)\\$ (exact translation)\", l=:dash)\nbodeplot!(Pc, wd, lab=\"\\$P_d(s)\\$ (inverse ZoH sampling)\")\nvline!([0.5 0.5], l=(:black, :dash), lab=\"Nyquist freq.\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/#Time-domain-simulation","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Time-domain simulation","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Convert continuous-time components to discrete time, or\nConvert discrete-time components to continuous time.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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).","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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.","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"A = [0.0 0.0 0.0 1.0 0.0 0.0\n 0.0 0.0 0.0 0.0 1.0 0.0\n 0.0 0.0 0.0 0.0 0.0 1.0\n -10.0 0.0 10.0 -0.001 0.0 0.001\n -0.0 -10.0 10.0 -0.0 -0.001 0.001\n 10.0 10.0 -20.0 0.001 0.001 -0.002]\nB = [0, 0, 0, 0.1, 0, 0]\nC = [0.0 0.0 0.0 0.0 0.0 1.0]\nP = minreal(ss(A,B,C,0))\nw = exp10.(LinRange(-2, 2, 1000))\nbodeplot(P, w, lab=\"\\$P(s)\\$\", plotphase=false)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"The discrete-time controller C(z) is a basic PI controller","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Ts = 1\nC = pid(0.01,10; Ts, Tf = 1/100, state_space=true)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"If we choose option 1, we get a discretized plant that has a very poor frequency-response fit to the original continuous-time plant, making frequency-domain analysis difficult","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Pd = c2d(P, Ts)\nLd = Pd*C\nwd = exp10.(-3:0.001:log10(1000*0.05))\nfigb = bodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Pd, wd, lab=\"\\$P(z)\\$ ZoH\")\nbodeplot!(Ld, wd, lab=\"\\$P(z)C(z)\\$\", legend=:bottomleft)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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).","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Cc = d2c_exact(C)\nLc = P*Z*Cc\n\nbodeplot(P, wd, lab=\"\\$P(s)\\$\")\nbodeplot!(Lc, wd, lab=\"\\$P(s)C(s)\\$\", legend=:bottomleft, c=3)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"If we form the closed-loop system from an input disturbance to the output","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"PS = dfracP(s)1 + P(s)C(s)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"PS = feedback(P, Z*Cc) # Use the continuous-time plant and continuous translation of the controller + ZoH operator\nPSd = feedback(Pd, C) # Use the discretized plant and discrete controller\nω = 5.53 # Frequency of input disturbance (rad/s) (Nyquist frequency is π rad/s)\ndisturbance(x, t) = sin(ω*t) # Continuous input disturbance\nplot(lsim(PS, disturbance, 0:0.22:3500), lab=\"Continuous disturbance response\")\nplot!(lsim(PSd, disturbance, 3500), lab=\"Discrete disturbance response\")\nhline!([abs(freqresp(PS, ω)[])], l=(:black, :dash), lab=\"Predicted freq. response amplitude\", legend=:bottomleft, fmt=:png)","category":"page"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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]","category":"page"},{"location":"examples/zoh/#Caveats","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Caveats","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"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. \nInterpretation 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]","category":"page"},{"location":"examples/zoh/#References","page":"Analysis of sampled-data (continuous/discrete) systems","title":"References","text":"","category":"section"},{"location":"examples/zoh/","page":"Analysis of sampled-data (continuous/discrete) systems","title":"Analysis of sampled-data (continuous/discrete) systems","text":"Learn more about sampled-data systems and zero-order hold sampling in chapter 7 of the reference below.","category":"page"},{"location":"","page":"Home","title":"Home","text":"

\n\n\"JuliaControl\n\n
\n\nStar\n\n\n

","category":"page"},{"location":"#ControlSystems.jl-Manual","page":"Home","title":"ControlSystems.jl Manual","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = ControlSystems\nconst libpath = haskey(ENV, \"CI\") ? dirname(pathof(ControlSystemsBase)) : \"lib/src\"\nDocTestFilters = [\n r\"StateSpace.+?\\n\"\n r\"HeteroStateSpace.+?\\n\"\n r\"TransferFunction.+?\\n\"\n r\"DelayLtiSystem.+?\\n\"\n r\"┌ Warning: Keyword argument hover.+\\n*.+\\n*\" # remove next line as well\n r\"\\[ Info: Precompiling.+\\n*\"\n]\nnyquistplot(ssrand(1,1,1))","category":"page"},{"location":"","page":"Home","title":"Home","text":"ControlSystems.jl and the rest of the packages in the JuliaControl organization implement solutions for analysis and design of (primarily linear) control systems. If you are new to the Julia programming language, learn more here. If you are familiar with Julia but unfamiliar with the ecosystem for control, learn more under Ecosystem.","category":"page"},{"location":"","page":"Home","title":"Home","text":"This documentation is structured into an introductory section labeled Introductory guide, a section with Examples and a reference section sorted into topics, labeled Functions.","category":"page"},{"location":"#Introductory-guide","page":"Home","title":"Introductory guide","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"man/introduction.md\", \"man/creating_systems.md\", \"man/numerical.md\", \"man/differences.md\"]\nDepth = 1","category":"page"},{"location":"#index_examples","page":"Home","title":"Examples","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"examples/example.md\", \"examples/ilc.md\", \"examples/smith_predictor.md\"]\nDepth = 2","category":"page"},{"location":"#Functions","page":"Home","title":"Functions","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Pages = [\"lib/constructors.md\", \"lib/analysis.md\", \"lib/synthesis.md\", \"lib/timefreqresponse.md\", \"lib/plotting.md\", \"lib/nonlinear.md\"]\nDepth = 1","category":"page"},{"location":"#Ecosystem","page":"Home","title":"Ecosystem","text":"","category":"section"},{"location":"#JuliaControl","page":"Home","title":"JuliaControl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The JuliaControl and surrounding ecosystem contains a few additional packages that may be of interest","category":"page"},{"location":"","page":"Home","title":"Home","text":"RobustAndOptimalControl.jl contains more advanced features for LQG design, robust analysis and synthesis, uncertainty modeling, named systems and an interface to DescriptorSystems.jl.\nSymbolicControlSystems.jl contains basic C-code generation for linear systems.\nControlSystemIdentification.jl is a system-identification toolbox for identification of LTI systems using either time or frequency-domain data. This package can use data to estimate statespace models, transfer-function models and Kalman filters that can be used for control design.\nControlSystemsMTK.jl is an interface between ControlSystems.jl and ModelingToolkit.jl.\nDiscretePIDs.jl contains a reference implementation in Julia of a discrete-time PID controller including set-point weighting, integrator anti-windup, derivative filtering and bumpless transfer.","category":"page"},{"location":"","page":"Home","title":"Home","text":"See also the paper describing the toolbox","category":"page"},{"location":"","page":"Home","title":"Home","text":"Bagge Carlson, F., Fält, M., Heimerson, A., & Troeng, O. (2021). ControlSystems.jl: A Control Toolbox in Julia. In 2021 60th IEEE Conference on Decision and Control (CDC) IEEE Press. https://doi.org/10.1109/CDC45484.2021.9683403","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the introductory Youtube video below, as well as the following Youtube playlist with videos about using Julia for control.","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"#The-wider-Julia-ecosystem-for-control","page":"Home","title":"The wider Julia ecosystem for control","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The following is a list of packages from the wider Julia ecosystem that may be of interest.","category":"page"},{"location":"","page":"Home","title":"Home","text":"DescriptorSystems.jl contains types that represent statespace systems on descriptor form, i.e., with a mass matrix. These systems can represent linear DAE systems and non-proper systems.\nTrajectoryOptimization.jl is one of the more developed packages for open-loop optimal control and trajectory optimization in Julia.\nLowLevelParticleFilters.jl is a library for state estimation using particle filters and Kalman filters of different flavors.\nModelingToolkit.jl is an acausal modeling tool, similar in spirit to Modelica. A video showing ControlSystems and ModelingToolkit together is available here. ControlSystemsMTK.jl exists to ease the use of these two packages together.\nJuliaSimControl.jl is a product that builds upon the JuliaControl ecosystem and ModelingToolkit, providing additional nonlinear and robust control methods.\nFaultDetectionTools.jl contains utilities and observers for online fault detection.\nReachabilityAnalysis.jl is a package for reachability analysis. This can be used to verify stability and safety properties of linear and nonlinear systems.\nMatrixEquations.jl contains solvers for many different matrix equations common in control. ControlSystems.jl makes use of this package for solving Riccati and Lyapunov equations.\nJuMP.jl is a modeling language for optimization, similar to YALMIP. JuMP is suitable for solving LMI/SDP problems as well as advanced linear MPC problems. \nSumOfSquares.jl is a package for sum-of-squares programming that builds on top of JuMP. Their documentation contains examples of Lyapunov-function search and nonlinear synthesis.\nMonteCarloMeasurements.jl is a library for working with parametric uncertainty. An example using ControlSystems is available here.\nDifferentialEquations.jl is the home of the SciML ecosystem that provides solvers for scientific problems. ControlSystems.jl uses these solvers for continuous-time simulations.\nDojo.jl is a differentiable robot simulator.\nStaticCompiler.jl contains tools for compiling small binaries of Julia programs.\nJuliaPOMDP is a Julia ecosystem for reinforcement learning. \nJuliaReinforcementLearning is another Julia ecosystem for reinforcement learning. ","category":"page"},{"location":"examples/delay_systems/#Properties-of-delay-systems","page":"Properties of delay systems","title":"Properties of delay systems","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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. ","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"using ControlSystemsBase, Plots\nw = exp10.(LinRange(-2, 2, 2000))\nP = tf(1, [1, 1]) * delay(2) # Plant with delay on the input\nnyquistplot(P, w)","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"When forming a feedback interconnection, making the delay appear in the closed loop, we may get gain ripple:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"bodeplot(feedback(P), w)","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"If the system with delay has a direct feedthrough term, step responses may show repeated steps at integer multiples of the delay:","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"using ControlSystems # Load full control systems to get simulation functionality\nP = tf([1, 1], [1, 0])*delay(1)\nplot(step(feedback(P, 0.5), 0:0.001:20))","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"Indeed, if the system has a non-zero feedthrough, the output will contain a delayed step attenuated by the feedthrough term, in this case","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"ss(feedback(tf([1, 1], [1, 0]))).D[]","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"the steps will thus in this case decay exponentially with decay rate 0.5. ","category":"page"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"For a more advanced example using time delays, see the Smith predictor tutorial.","category":"page"},{"location":"examples/delay_systems/#Simulation-of-time-delay-systems","page":"Properties of delay systems","title":"Simulation of time-delay systems","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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.","category":"page"},{"location":"examples/delay_systems/#Estimation-of-delay","page":"Properties of delay systems","title":"Estimation of delay","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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.","category":"page"},{"location":"examples/delay_systems/#Approximation-and-discretization-of-delays","page":"Properties of delay systems","title":"Approximation and discretization of delays","text":"","category":"section"},{"location":"examples/delay_systems/","page":"Properties of delay systems","title":"Properties of delay systems","text":"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).","category":"page"}] }