From 239dd818dca6abdc34c3ec253c29802b13e46c84 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Wed, 6 Nov 2024 08:27:49 +0100 Subject: [PATCH] PNG output in docs --- docs/Project.toml | 1 + docs/make.jl | 4 +++- docs/src/freq.md | 2 ++ docs/src/impulse.md | 2 ++ docs/src/nonlinear.md | 6 ++++++ docs/src/ss.md | 2 ++ docs/src/validation.md | 2 ++ 7 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index 05b15bdb..319b8e1b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,6 +3,7 @@ ControlSystemIdentification = "3abffc1c-5106-53b7-b354-a47bfc086282" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" +DisplayAs = "0b91fe84-8a4c-11e9-3e1d-67c38462b6d6" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" LeastSquaresOptim = "0fc2ff8b-aaa3-5acd-a817-1944a5e08891" LowLevelParticleFilters = "d9d29d28-c116-5dba-9239-57a5fe23875b" diff --git a/docs/make.jl b/docs/make.jl index 6d64a70e..fa3ca66c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,4 +1,4 @@ -ENV["GKSwstype"] = 322 # workaround for gr segfault on GH actions +ENV["GKSwstype"] = 100 # workaround for gr segfault on GH actions using Documenter, ControlSystemIdentification, ControlSystemsBase, DelimitedFiles # ENV["JULIA_DEBUG"]=Documenter # Enable this for debugging @@ -56,3 +56,5 @@ makedocs( deploydocs( repo = "github.com/baggepinnen/ControlSystemIdentification.jl.git", ) + +cd(@__DIR__) \ No newline at end of file diff --git a/docs/src/freq.md b/docs/src/freq.md index e70c03d0..08f2f532 100644 --- a/docs/src/freq.md +++ b/docs/src/freq.md @@ -6,6 +6,7 @@ Frequency-domain estimation refers to estimation of linear systems using frequen Non-parametric estimation refers to the estimation of a model without a fixed number of parameters. Instead, the number of estimated parameters typically grows with the size of the data. This form of estimation can be useful to gain an initial understanding of the system, before selecting model orders etc. for a more standard parametric model. We provide non-parametric estimation of transfer functions through spectral estimation. To illustrate, we once again simulate some data: ```@example npfreq using ControlSystemIdentification, ControlSystemsBase, Plots +using DisplayAs # hide gr(fmt=:png) # hide T = 100000 h = 1 @@ -34,6 +35,7 @@ bodeplot([sys,sysn], exp10.(range(-3, stop=log10(pi), length=200)), layout=(1,3) coherenceplot!(dn, subplot=3) plot!(G, subplot=1, lab="G Est", alpha=0.3, title="Process model") plot!(√N, subplot=2, lab="N Est", alpha=0.3, title="Noise model") +DisplayAs.PNG(current()) # hide ``` The left figure displays the Bode magnitude of the true system, together with the estimate (noisy), and the middle figure illustrates the estimated noise model. The right figure displays the coherence function ([`coherenceplot`](@ref)), which is close to 1 everywhere except for at the resonance peak of the noise `log10(sqrt(0.3)) = -0.26`. diff --git a/docs/src/impulse.md b/docs/src/impulse.md index ffa14e53..b6836be9 100644 --- a/docs/src/impulse.md +++ b/docs/src/impulse.md @@ -21,6 +21,7 @@ plot!(impulse(sys,50), lab="True system", l=:dash) MIMO example ```@example using ControlSystemIdentification, ControlSystemsBase, Plots +using DisplayAs # hide T = 200 h = 1 t = 0:h:T-h @@ -33,6 +34,7 @@ d = iddata(res) H = okid(d, sys.nx) plot(impulse(sys,50), lab="True system", layout=sys.ny+sys.nu, sp=(1:4)') plot!(reshape(H, sys.nu+sys.ny, :)', lab="OKID Estiamte", seriestype=:steppre, l=:dash) +DisplayAs.PNG(current()) # hide ``` See the [example notebooks](https://github.com/JuliaControl/ControlExamples.jl) for more details. diff --git a/docs/src/nonlinear.md b/docs/src/nonlinear.md index fbd90955..52096cec 100644 --- a/docs/src/nonlinear.md +++ b/docs/src/nonlinear.md @@ -1,6 +1,7 @@ ```@setup HW using ControlSystemIdentification using LeastSquaresOptim +using DisplayAs ``` @@ -111,6 +112,7 @@ plot( plot(U', title="Inputs", lab=["u1" "u2"]), plot(Y', title="Outputs", lab=["y1" "y2"]), ) +DisplayAs.PNG(current()) # hide ``` We package the experimental data into an [`iddata`](@ref) object as usual. Finally, we specify the covariance matrices for the dynamics noise and measurement noise as well as a guess for the initial condition. Since we can measure the level in the first two tanks, we use the true initial condition for those tanks, but we pretend that we are quite off when guessing the initial condition for the last two tanks. @@ -136,11 +138,13 @@ simplot(model, d, layout=2) x_guess = LowLevelParticleFilters.rollout(discrete_dynamics, x0_guess, u, p_guess)[1:end-1] y_guess = measurement.(x_guess, u, 0, 0) plot!(reduce(hcat, y_guess)', lab="Initial guess") +DisplayAs.PNG(current()) # hide ``` We can also perform a residual analysis to see if the model is able to capture the dynamics of the system ```@example HW residualplot(model, d) +DisplayAs.PNG(current()) # hide ``` since we are using simulated data here, the residuals are white and there's nothing to worry about. In practice, one should always inspect the residuals to see if there are any systematic errors in the model. @@ -268,6 +272,7 @@ The example below identifies a model of a resonant system where the sign of the using ControlSystemIdentification, ControlSystemsBase using ControlSystemsBase.DemoSystems: resonant using Random, Plots +using DisplayAs # hide # Generate some data from the system Random.seed!(1) @@ -307,6 +312,7 @@ output_nonlinearity(yh, nothing) # We need to manually apply the output nonlinea plot(d.t, [abs.(y); u]', lab=["True nonlinear output" "Input"], seriestype = [:line :steps], layout=(2,1), xlabel="Time") scatter!(d.t, ynn', lab="Measured nonlinear output", sp=1) plot!(d.t, yh', lab="Simulation", sp=1, l=:dash) +DisplayAs.PNG(current()) # hide ``` diff --git a/docs/src/ss.md b/docs/src/ss.md index a753b543..9a70001c 100644 --- a/docs/src/ss.md +++ b/docs/src/ss.md @@ -16,6 +16,7 @@ There exist several methods for identification of statespace models, [`subspacei In this example we will estimate a statespace model using the [`subspaceid`](@ref) method. This function returns an object of type [`N4SIDStateSpace`](@ref) where the model is accessed as `sys.sys`. ```@example ss using ControlSystemIdentification, ControlSystemsBase, Plots +using DisplayAs # hide gr(fmt=:png) # hide Ts = 0.1 G = c2d(DemoSystems.resonant(), Ts) @@ -91,6 +92,7 @@ f2 = bodeplot([G, meanmodel], lab=["True" "" "Combined estimate" ""], l=2) bodeplot!(models, lab="Individual estimates", c=:black, alpha=0.5, legend=:bottomleft) plot(f1, f2) +DisplayAs.PNG(current()) # hide ``` The procedure shown above is equivalent to calling [`era`](@ref) directly with a vector of data sets, in which case the averaging of the impulse responses is done internally. diff --git a/docs/src/validation.md b/docs/src/validation.md index d57d0989..c984a153 100644 --- a/docs/src/validation.md +++ b/docs/src/validation.md @@ -7,6 +7,7 @@ Generate some test data: ```@example validation using ControlSystemIdentification, ControlSystemsBase, Random using ControlSystemIdentification: newpem +using DisplayAs # hide Random.seed!(1) T = 200 nx = 2 @@ -50,6 +51,7 @@ bodeplot!((getindex.(res,1)), ω, link = :none, balance=fals bodeplot!(innovation_form.(getindex.(res,1)), ω, link = :none, balance=false, plotphase=false, subplot=4, linewidth=2*[4 3 2 1]) bodeplot!(sys, ω, link = :none, balance=false, plotphase=false, subplot=3, lab="True", l=(:black, :dash), legend = :bottomleft, title="System model") bodeplot!(innovation_form(ss(sys),syse=ss(sysn)), ω, link = :none, balance=false, plotphase=false, subplot=4, lab="True", l=(:black, :dash), ylims=(0.1, 100), legend = :bottomleft, title="Noise model") +DisplayAs.PNG(current()) # hide ``` In the figure, simulation output is compared to the true model on the top left and prediction on top right. The system models and noise models are visualized in the bottom plots. All models capture the system dynamics reasonably well, but struggle slightly with capturing the gain of the noise dynamics.