diff --git a/src/BallAndBeam.jl b/src/BallAndBeam.jl index ab87053654472b0bd9eafe1d50d205e140ba8905..7c7da9c41e9fdd5e469c7287d7be21d5bce5527f 100644 --- a/src/BallAndBeam.jl +++ b/src/BallAndBeam.jl @@ -134,12 +134,13 @@ nypl(G; kwargs...) = (plot();nypl!(G;kwargs...)) """ - sysFB,L,T,C = fbdesign(G, polevect, zerovect, gain) + sysFB,L,T,C = fbdesign(G::AbstractMatrix, polevect, zerovect, gain) Frequency Compensation to determine polynominals in compensator C=S/R. Frequency responses for loop transfer, closed loop and controller are calculated. `sys` is of type `StateSpace`, to be used together with [`sysfilter!`](@ref) +`G` is a frequency response matrix with ω in the first column and G(iω) in the second. """ -function fbdesign(G, polevect, zerovect, gain) +function fbdesign(G::AbstractMatrix, polevect, zerovect, gain) ω = Float64.(G[:,1]) pzv = isempty(zerovect) ? 1 : prod(abs.(zerovect)) sysFB = ss(zpk(zerovect,polevect,gain*prod(abs.(polevect))/pzv)) diff --git a/src/FRTN35_lab1.jl b/src/FRTN35_lab1.jl index 16c073f75f41ab6d67800f680b8fdb0c177722fc..7db7989e9b65bb821884ef1c6c46c2453c6fe0c9 100644 --- a/src/FRTN35_lab1.jl +++ b/src/FRTN35_lab1.jl @@ -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" @@ -31,6 +31,8 @@ bopl(G123, m=:star) nypl(G123, m=:star) ## Control ================================================================================== +# Specify poles, zeros and gain for your feedback controller and feedforward compensator +# The input `G` to `fbdesign` is the data you have measured, e.g, `G123` polevect = [-10] zerovect = [] gain = 1 @@ -49,3 +51,26 @@ bopl!(YR, lab="Closed-loop system r->y") sysFB,sysFF = c2d(sysFBc,h)[1],c2d(sysFFc,h)[1] y,u,r = run_control_2DOF(P, sysFB, sysFF, duration=5, reference = t->2sign(sin(2π/3*t))) plot([y u r], lab = ["y" "u" "r"]) + + +# If you have time, estimate ARX model +prbs = PRBSGenerator() +duration = 5 +y = zeros(0:h:duration) +u = zeros(0:h:duration) +LabProcesses.initialize(P) +for (i,t) = enumerate(0:h:duration) + y[i] = measure(P) + u[i] = prbs()-0.5 + 0.5*u[max(1,i-1)] + control(P, u[i]) +end +LabProcesses.finalize(P) + +na = 6 +nb = 5 +arxtf = arx(h, y[:], u, na, nb; λ = 0) + +bodeplot([P.sys, arxtf], logspace(-1,3,200)) + +plot(result.error[1:end-1]) +plot([u y]) diff --git a/src/arx.jl b/src/arx.jl new file mode 100644 index 0000000000000000000000000000000000000000..1d0569e0a5fbf32487150f2a66908625718c30c3 --- /dev/null +++ b/src/arx.jl @@ -0,0 +1,127 @@ +abstract type Model end +abstract type LinearModel <: Model end +abstract type NonLinearModel <: Model end +abstract type Network <: NonLinearModel end +const Polynom{T<:Real} = Union{Array{T,1} , T} +const PolynomMatrix{T} = Union{Array{Polynom{T},1},Polynom{T}, T} + + + +mutable struct FitStatistics + RMS::Float64 + FIT::Float64 + AIC::Float64 + FitStatistics(y::Vector{Float64},yh::Vector{Float64},d) = new(rms(y-yh),fit(y,yh),aic(y-yh,d)) +end + +mutable struct FitResult + error::VecOrMat{Float64} + fit::FitStatistics + method::Symbol + λ::Float64 + function FitResult(y,yh,d::Int,method::Symbol, λ = 0) + error = y-yh + new(error, FitStatistics(y,yh, d),method, λ) + end +end + +mutable struct IdData + y::VecOrMat{Float64} + u::VecOrMat{Float64} + Ts::Float64 + IdData(y::VecOrMat{Float64}) = new(y,[],0) + IdData(y::VecOrMat{Float64} ,Ts::Float64) = new(y,[],Ts) + IdData(y::VecOrMat{Float64}, u::VecOrMat{Float64}) = new(y,u,0) +end + +## Helper functions +rms(x) = sqrt(mean(x.^2)) +sse(x) = sum(x.^2) +fit(y,yh) = 100 * (1-rms(y-yh)./rms(y-mean(y))); +aic(x,d) = log(sse(x)) + 2d/length(x) + +## +Base.show(fit::FitStatistics) = println("Fit RMS:$(fit.RMS), FIT:$(fit.FIT), AIC:$(fit.AIC)") + +function toeplitz{T}(c::AbstractVector{T},r::AbstractVector{T}) + nc = length(c) + nr = length(r) + A = zeros(T, nc, nr) + A[:,1] = c + A[1,:] = r + for i in 2:nr + A[2:end,i] = A[1:end-1,i-1] + end + A +end + +""" +`a::Vector{Float64}`: The polynomial coeffs A(z) (not including the first 1)\n +`b::VecOrMat{Float64}`: The polynomial coeffs B(z)\n +`na::Int`\n +`nb::Vector{Int}`\n +""" +type ARX <: LinearModel + a::Polynom + b::PolynomMatrix + na::Int + nb::Polynom{Int} +end + + +function getARXregressor(y::AbstractVector{Float64},u::AbstractVecOrMat{Float64}, na, nb) + assert(length(nb) == size(u,2)) + m = max(na+1,maximum(nb)) + n = length(y) - m+1 + offs = m-na-1 + A = toeplitz(y[offs+na+1:n+na+offs],y[offs+na+1:-1:1]) + y = copy(A[:,1]) + A = A[:,2:end] + for i = 1:length(nb) + offs = m-nb[i] + A = [A toeplitz(u[nb[i]+offs:n+nb[i]+offs-1,i],u[nb[i]+offs:-1:1+offs,i])] + end + return y,A +end + +getARXregressor(iddata::IdData, na, nb) = getARXregressor(iddata.y,iddata.u, na, nb) + +""" + find_na(y::Vector,n::Int) +Plots the RMSE and AIC for model orders up to `n`. Useful for model selection +""" +function find_na(y::Vector,n::Int) + error = zeros(n,2) + for i = 1:n + w,e = ar(y,i) + error[i,1] = rms(e) + error[i,2] = aic(e,i) + print(i,", ") + end + println("Done") + scatter(error, show=true) +end + +""" + 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 + method = :LS + else + w = (A'A + λ*eye(size(A,2)))\A'y_train + method = :LS_reg + end + + a = [1; -w[1:na]] + b = w[na+1:end] + model = tf(b,a,h) + return model +end diff --git a/test/runtests.jl b/test/runtests.jl index b4f2b660f65094ca00ea4518a7c2dbb6c4b69f49..ccd72776ab913fc6e4f28a588cc660168588281c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -52,7 +52,6 @@ sysFB,sysFF = c2d(sysFBc,h),c2d(sysFFc,h) # Test BeamSimulator -bias = 0.01 # Change this if your process drifts over time P = LabProcesses.BeamSimulator(0.001) # Replace for BeamSimulator to run simulations h = sampletime(P) @@ -68,3 +67,34 @@ phase_id = angle.(G1[:,2]) |> ControlSystems.unwrap phase_true = angle.(true_resp[1][:]) |> ControlSystems.unwrap @test sum(abs, (phase_id-phase_true)[1:400]) < 0.065 @test sum(abs, (phase_id-phase_true)[400:end]) < 5.3 # Some numerical errors for high freqs + + +P = LabProcesses.BeamSimulator(0.01) +h = sampletime(P) +prbs = PRBSGenerator() +duration = 5 +y = zeros(0:h:duration) +u = zeros(0:h:duration) +LabProcesses.initialize(P) +for (i,t) = enumerate(0:h:duration) + y[i] = measure(P) + u[i] = prbs()-0.5 + 0.5*u[max(1,i-1)] + control(P, u[i]) +end +LabProcesses.finalize(P) +na = 6 +nb = 5 +arxtf = arx(h, y[:], u, na, nb; λ = 0) + +w1_100 = logspace(-1,log10(100),500) +true_resp = freqresp(P.sys, w1_100) +phase_true = angle.(true_resp[1][:]) |> ControlSystems.unwrap +arx_resp = freqresp(arxtf, w1_100) +@test sum(abs, log.(abs.(arx_resp[1][:])) - log.(abs.(true_resp[1][:]))) < 1.25 + +phase_id = angle.(arx_resp[1][:]) |> ControlSystems.unwrap +@test sum(abs, (phase_id-phase_true)[1:400]) < 21 + + +# plot([log.(abs.(arx_resp[1][:])) log.(abs.(true_resp[1][:]))]) +# bodeplot([P.sys, arxtf])