diff --git a/src/BallAndBeam.jl b/src/BallAndBeam.jl index 2d185fd95d9151d853c354674aa26dc53e641e91..2b399863a7c30642f63804e0c06ef2897c1d8522 100644 --- a/src/BallAndBeam.jl +++ b/src/BallAndBeam.jl @@ -109,30 +109,57 @@ function sortfqs(G) G[perm,:] end -function bopl!(G; kwargs...) - plot!(G[:,1], abs.(G[:,2]); ylabel="Magnitude", title="Bode plot", xscale=:log10, yscale=:log10, subplot=1, kwargs...) +@userplot Bopl +@userplot Nypl + + +@recipe function Bopl(p::Bopl) + G = p.args[1] + link := :x + layout := (2,1) + @series begin + ylabel --> "Magnitude" + title --> "Bode plot" + xscale --> :log10 + yscale --> :log10 + subplot := 1 + G[:,1], abs.(G[:,2]) + end phase = angle.(G[:,2]).*(180/π) phase[phase .> 0] .-= 360 - plot!(G[:,1], phase; xlabel="Frequency [rad/s]", ylabel="Phase", xscale=:log10, subplot=2, kwargs...) - plot!(link=:x) + @series begin + xlabel --> "Frequency [rad/s]" + ylabel --> "Phase" + xscale --> :log10 + subplot := 2 + G[:,1], phase + end + nothing end -nypl!(G; kwargs...) = plot!(real.(G[:,2]), imag.(G[:,2]); - xlabel="\$Re(G)\$", ylabel="\$Im(G)\$", title="Nyquist plot", kwargs...) +@recipe function Nypl(p::Nypl) + G = p.args[1] + xlabel --> "\$Re(G)\$" + ylabel --> "\$Im(G)\$" + title --> "Nyquist plot" + real.(G[:,2]), imag.(G[:,2]) +end + + """ - bopl(G) + bopl(G; kwargs...) Plot a bodeplot when `G` is a matrix with frequencies in first column and complex magnitudes in the second. Add to an existing plot with `bopl!(...)` See also [`nypl`](@ref) """ -bopl(G; kwargs...) = (plot(layout=(2,1),link=:x);bopl!(G;kwargs...)) +bopl """ - nypl(G) + nypl(G; kwargs...) Plot a nyquistplot when `G` is a matrix with frequencies in first column and complex magnitudes in the second. Add to an existing plot with `nypl!(...)` See also [`bopl`](@ref) """ -nypl(G; kwargs...) = (plot();nypl!(G;kwargs...)) +nypl """ diff --git a/src/FRTN35_lab1.jl b/src/FRTN35_lab1.jl index 2d4860e3718eefe9278470ba9db62b3c652d6a08..75472ae1b56dc6cebdcbd09bcdd99fdab69188dd 100644 --- a/src/FRTN35_lab1.jl +++ b/src/FRTN35_lab1.jl @@ -1,8 +1,8 @@ -using BallAndBeam, ControlSystems, JLD, LabProcesses +using BallAndBeam, ControlSystems, JLD, LabProcesses, Plots # @load "workspace.jld" # Run this command to restore a saved workspace Bias = 0.01 # Change this if your process drifts over time -P = LabProcesses.Beamr(0.01, Bias) # Replace for BeamSimulator(0.01) to run simulations +P = LabProcesses.Beam(0.01, Bias) # Replace for BeamSimulator(0.01) to run simulations h = sampletime(P) settling_time = 1 @@ -12,7 +12,7 @@ nbr_of_periods = 3 # and run three experiments. You may modify the freqency vectors # any way you want and add/remove experiments as needed. -w1_100 = logspace(log10(1),log10(300),8) +w1_100 = logspace(log10(1),log10(300),800) G1 = fra(P, w1_100, amplitude=1, nbr_of_periods=nbr_of_periods, settling_time=settling_time) @save "workspace.jld" @@ -62,21 +62,19 @@ function prbs_experiment(;amplitude = 1, duration = 10) for (i,t) = enumerate(0:h:duration) @periodically h begin y[i] = measure(P) - u[i] = amplitude*(prbs()-0.5) + u[i] = sum(sin(ω*(t-1)) for ω in logspace(-1,3,10))#amplitude*(prbs()-0.5)# randn() control(P, u[i]) end end LabProcesses.finalize(P) - y,u,times + y,u end y,u = prbs_experiment(duration = 10, amplitude = 1) -plot([u y]) +plot([y u], lab=["y" "u"]) -na = 3 # Order of A polynomial +na = 4 # Order of A polynomial nb = 2 # Order of B polynomial -arxtf = arx(h, y, u, na, nb) # Estimate trasfer function with ARX method +arxtf, Σ = arx(h, y, u, na, nb) # Estimate trasfer function with ARX method -mag, phase, ω = bode(arxtf, logspace(-1,3,200)) bopl(G123, lab="Measured transfer function") -plot!(ω, mag[:], subplot=1, lab = "ARX estimate") -plot!(ω, phase[:], subplot=2) +bodeconfidence!(arxtf, Σ, ω = logspace(0,3,200)) diff --git a/src/arx.jl b/src/arx.jl index 5eecff1bb446b67e8cb6300696eccd17358670f8..bb6dc0eca7418666104d669345bb96be3978b2f4 100644 --- a/src/arx.jl +++ b/src/arx.jl @@ -1,4 +1,4 @@ -export toeplitz, getARXregressor, find_na, arx +export toeplitz, getARXregressor, find_na, arx, bopl_confidence, bopl_confidence! ## Helper functions @@ -60,7 +60,7 @@ end """ find_na(y::AbstractVector,n::Int) -Plots the RMSE and AIC for model orders up to `n`. Useful for model selection +Plots the RMSE and AIC For model orders up to `n`. Useful for model selection """ function find_na(y::AbstractVector,n::Int) error = zeros(n,2) @@ -75,23 +75,108 @@ function find_na(y::AbstractVector,n::Int) end """ - Gtf = arx(h,y, u, na, nb; λ = 0) -Fit a transfer function to data using an ARX model. + Gtf, Σ = arx(h,y, u, na, nb; λ = 0) +Fit a transfer Function to data using an ARX model. `nb` and `na` are the orders of the numerator and denominator polynomials. """ function arx(h,y::AbstractVector{Float64}, u::AbstractVector{Float64}, na, nb; λ = 0) na -= 1 y_train, A = getARXregressor(y,u, na, nb) - n_points = size(A,1) if λ == 0 w = A\y_train else w = (A'A + λ*eye(size(A,2)))\A'y_train end + a,b = params2poly(w,na,nb) + model = tf(b,a,h) + Σ = parameter_covariance(y_train, A, w, λ) + return model, Σ +end +""" + a,b = params2poly(params,na,nb) +""" +function params2poly(w,na,nb) a = [1; -w[1:na]] b = w[na+1:end] - model = tf(b,a,h) - return model + a,b +end + +""" + Σ = parameter_covariance(y_train, A, w, λ=0) +""" +function parameter_covariance(y_train, A, w, λ=0) + σ² = var(y_train .- A*w) + iATA = if λ == 0 + inv(A'A) + else + ATA = A'A + ATAλ = factorize(ATA + λ*I) + ATAλ\ATA/ATAλ + end + iATA = (iATA+iATA')/2 + Σ = σ²*iATA + sqrt(eps())*eye(iATA) +end + +""" + bodeconfidence(arxtf::TransferFunction, Σ::Matrix; ω = logspace(0,3,200)) +Plot a bode diagram of a transfer function estimated with [`arx`](@ref) with confidence bounds on magnitude and phase. +""" +bodeconfidence + +@userplot BodeConfidence + +@recipe function BodeConfidence(p::BodeConfidence; ω = logspace(-2,3,200)) + arxtfm = p.args[1] + Σ = p.args[2] + L = chol(Hermitian(Σ)) + am, bm = -denpoly(arxtfm)[1].a[2:end], arxtfm.matrix[1].num.a + wm = [am; bm] + na,nb = length(am), length(bm) + mc = 100 + res = map(1:mc) do _ + w = L'randn(size(L,1)) .+ wm + a,b = params2poly(w,na,nb) + arxtf = tf(b,a,arxtfm.Ts) + mag, phase, _ = bode(arxtf, ω) + mag[:], phase[:] + end + magmc = hcat(getindex.(res,1)...) + phasemc = hcat(getindex.(res,2)...) + mag = mean(magmc,2)[:] + phase = mean(phasemc,2)[:] + uppermag = getpercentile(magmc,0.95) + lowermag = getpercentile(magmc,0.05) + upperphase = getpercentile(phasemc,0.95) + lowerphase = getpercentile(phasemc,0.05) + + layout := (2,1) + + @series begin + subplot := 1 + title --> "ARX estimate" + ylabel --> "Magnitude" + ribbon := (lowermag, uppermag) + yscale --> :log10 + xscale --> :log10 + alpha --> 0.3 + ω, mag + end + @series begin + subplot := 2 + ribbon := (lowerphase, upperphase) + ylabel --> "Phase [deg]" + xlabel --> "Frequency [rad/s]" + xscale --> :log10 + alpha --> 0.3 + ω, phase + end + nothing +end + +function getpercentile(mag,p) + uppermag = mapslices(mag, 2) do magω + sort(magω)[round(Int,endof(magω)*p)] + end end