diff --git a/Project.toml b/Project.toml index 14e9d5006..475722bc0 100644 --- a/Project.toml +++ b/Project.toml @@ -31,14 +31,21 @@ PreallocationTools = "0.4.14" PrecompileTools = "1" ProgressLogging = "0.1" RecipesBase = "1" -TestItemRunner = "1.1" [extras] DAQP = "c47d62df-3981-49c8-9651-128b1cd08617" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" [targets] -test = ["Test", "TestItemRunner", "Documenter", "Plots", "DAQP"] +test = [ + "Test", + "TestItems", + "TestItemRunner", + "Documenter", + "Plots", + "DAQP" + ] diff --git a/src/controller/transcription.jl b/src/controller/transcription.jl index fdad0879f..2ced9fedc 100644 --- a/src/controller/transcription.jl +++ b/src/controller/transcription.jl @@ -18,7 +18,7 @@ The decision variable in the optimization problem is (excluding the slack ``ϵ`` \vdots \\ \mathbf{Δu}(k+H_c-1) \end{bmatrix} ``` -This method is generally more efficient for small control horizon ``H_c``, stable or mildly +This method is generally more efficient for small control horizon ``H_c``, stable and mildly nonlinear plant model/constraints. """ struct SingleShooting <: TranscriptionMethod end @@ -42,9 +42,10 @@ operating point ``\mathbf{x̂_{op}}`` (see [`augment_model`](@ref)): \mathbf{x̂}_i(k+H_p) - \mathbf{x̂_{op}} \end{bmatrix} ``` where ``\mathbf{x̂}_i(k+j)`` is the state prediction for time ``k+j``, estimated by the -observer at time ``i=k`` or ``i=k-1`` depending on its `direct` flag. This transcription -method is generally more efficient for large control horizon ``H_c``, unstable or highly -nonlinear plant models/constraints. +observer at time ``i=k`` or ``i=k-1`` depending on its `direct` flag. Note that +``\mathbf{X̂_0 = X̂}`` if the operating points is zero, which is typically the case in +practice for [`NonLinModel`](@ref). This transcription method is generally more efficient +for large control horizon ``H_c``, unstable or highly nonlinear plant models/constraints. Sparse optimizers like `OSQP` or `Ipopt` are recommended for this method. """ @@ -182,7 +183,7 @@ contribution for non-zero state ``\mathbf{x̂_{op}}`` and state update ``\mathbf operating points (for linearization at non-equilibrium point, see [`linearize`](@ref)). The stochastic predictions ``\mathbf{Ŷ_s=0}`` if `estim` is not a [`InternalModel`](@ref), see [`init_stochpred`](@ref). The method also computes similar matrices for the predicted -terminal states at ``k+H_p``: +terminal state at ``k+H_p``: ```math \begin{aligned} \mathbf{x̂_0}(k+H_p) &= \mathbf{e_x̂ Z} + \mathbf{g_x̂ d_0}(k) + \mathbf{j_x̂ D̂_0} diff --git a/test/1_test_sim_model.jl b/test/1_test_sim_model.jl index 343102f97..7a181ddc6 100644 --- a/test/1_test_sim_model.jl +++ b/test/1_test_sim_model.jl @@ -155,9 +155,9 @@ end @test y ≈ zeros(2,) linmodel2 = LinModel(sys,Ts,i_d=[3]) - f2(x,u,d,_) = linmodel2.A*x + linmodel2.Bu*u + linmodel2.Bd*d - h2(x,d,_) = linmodel2.C*x + linmodel2.Dd*d - nonlinmodel2 = NonLinModel(f2,h2,Ts,2,4,2,1,solver=nothing) + f2(x,u,d,model) = model.A*x + model.Bu*u + model.Bd*d + h2(x,d,model) = model.C*x + model.Dd*d + nonlinmodel2 = NonLinModel(f2,h2,Ts,2,4,2,1,solver=nothing,p=linmodel2) @test nonlinmodel2.nx == 4 @test nonlinmodel2.nu == 2 @@ -172,18 +172,18 @@ end nonlinmodel3 = NonLinModel{Float32}(f2,h2,Ts,2,4,2,1,solver=nothing) @test isa(nonlinmodel3, NonLinModel{Float32}) - function f1!(xnext, x, u, d,_) - mul!(xnext, linmodel2.A, x) - mul!(xnext, linmodel2.Bu, u, 1, 1) - mul!(xnext, linmodel2.Bd, d, 1, 1) + function f1!(xnext, x, u, d, model) + mul!(xnext, model.A, x) + mul!(xnext, model.Bu, u, 1, 1) + mul!(xnext, model.Bd, d, 1, 1) return nothing end - function h1!(y, x, d,_) - mul!(y, linmodel2.C, x) - mul!(y, linmodel2.Dd, d, 1, 1) + function h1!(y, x, d, model) + mul!(y, model.C, x) + mul!(y, model.Dd, d, 1, 1) return nothing end - nonlinmodel4 = NonLinModel(f1!, h1!, Ts, 2, 4, 2, 1, solver=nothing) + nonlinmodel4 = NonLinModel(f1!, h1!, Ts, 2, 4, 2, 1, solver=nothing, p=linmodel2) xnext, y = similar(nonlinmodel4.x0), similar(nonlinmodel4.yop) nonlinmodel4.f!(xnext,[0,0,0,0],[0,0],[0],nonlinmodel4.p) @test xnext ≈ zeros(4) @@ -195,36 +195,37 @@ end Bd = reshape([0; 0.5], 2, 1) C = [0.4 0] Dd = reshape([0], 1, 1) - f3(x, u, d, _) = A*x + Bu*u+ Bd*d - h3(x, d, _) = C*x + Dd*d + p=(; A, Bu, Bd, C, Dd) + f3(x, u, d, p) = p.A*x + p.Bu*u+ p.Bd*d + h3(x, d, p) = p.C*x + p.Dd*d solver=RungeKutta(4) @test string(solver) == "4th order Runge-Kutta differential equation solver with 1 supersamples." - nonlinmodel5 = NonLinModel(f3, h3, 1.0, 1, 2, 1, 1, solver=solver) + nonlinmodel5 = NonLinModel(f3, h3, 1.0, 1, 2, 1, 1, solver=solver, p=p) xnext, y = similar(nonlinmodel5.x0), similar(nonlinmodel5.yop) nonlinmodel5.f!(xnext, [0; 0], [0], [0], nonlinmodel5.p) @test xnext ≈ zeros(2) nonlinmodel5.h!(y, [0; 0], [0], nonlinmodel5.p) @test y ≈ zeros(1) - function f2!(ẋ, x, u , d, _) - mul!(ẋ, A, x) - mul!(ẋ, Bu, u, 1, 1) - mul!(ẋ, Bd, d, 1, 1) + function f2!(ẋ, x, u , d, p) + mul!(ẋ, p.A, x) + mul!(ẋ, p.Bu, u, 1, 1) + mul!(ẋ, p.Bd, d, 1, 1) return nothing end - function h2!(y, x, d, _) - mul!(y, C, x) - mul!(y, Dd, d, 1, 1) + function h2!(y, x, d, p) + mul!(y, p.C, x) + mul!(y, p.Dd, d, 1, 1) return nothing end - nonlinmodel6 = NonLinModel(f2!, h2!, 1.0, 1, 2, 1, 1, solver=RungeKutta()) + nonlinmodel6 = NonLinModel(f2!, h2!, 1.0, 1, 2, 1, 1, solver=RungeKutta(), p=p) xnext, y = similar(nonlinmodel6.x0), similar(nonlinmodel6.yop) nonlinmodel6.f!(xnext, [0; 0], [0], [0], nonlinmodel6.p) @test xnext ≈ zeros(2) nonlinmodel6.h!(y, [0; 0], [0], nonlinmodel6.p) @test y ≈ zeros(1) - nonlinemodel7 = NonLinModel(f2!, h2!, 1.0, 1, 2, 1, 1, solver=ForwardEuler()) + nonlinemodel7 = NonLinModel(f2!, h2!, 1.0, 1, 2, 1, 1, solver=ForwardEuler(), p=p) xnext, y = similar(nonlinemodel7.x0), similar(nonlinemodel7.yop) nonlinemodel7.f!(xnext, [0; 0], [0], [0], nonlinemodel7.p) @test xnext ≈ zeros(2) @@ -269,8 +270,8 @@ end @testitem "NonLinModel linearization" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff Ts = 1.0 - f1(x,u,d,_) = x.^5 + u.^4 + d.^3 - h1(x,d,_) = x.^2 + d + f1(x,u,d,_) = x.^5 .+ u.^4 .+ d.^3 + h1(x,d,_) = x.^2 .+ d nonlinmodel1 = NonLinModel(f1,h1,Ts,1,1,1,1,solver=nothing) x, u, d = [2.0], [3.0], [4.0] linmodel1 = linearize(nonlinmodel1; x, u, d) @@ -288,8 +289,8 @@ end @test repr(nonlinmodel1.linbuffer) == "LinearizationBuffer object" @test repr(nonlinmodel1.linbuffer.buffer_f_at_u_d) == "DifferentiationBuffer with a JacobianConfig" - f1!(ẋ, x, u, d, _) = (ẋ .= x.^5 + u.^4 + d.^3; nothing) - h1!(y, x, d, _) = (y .= x.^2 + d; nothing) + f1!(ẋ, x, u, d, _) = (ẋ .= x.^5 .+ u.^4 .+ d.^3; nothing) + h1!(y, x, d, _) = (y .= x.^2 .+ d; nothing) nonlinmodel3 = NonLinModel(f1!,h1!,Ts,1,1,1,1,solver=RungeKutta()) linmodel3 = linearize(nonlinmodel3; x, u, d) u0, d0 = u - nonlinmodel3.uop, d - nonlinmodel3.dop @@ -306,20 +307,23 @@ end @test linmodel3.Dd ≈ Dd # test `linearize` at a non-equilibrium point: - N = 5 - x, u, d = [0.2], [0.0], [0.0] - Ynl = zeros(N) - Yl = zeros(N) - setstate!(nonlinmodel3, x) - linmodel3 = linearize(nonlinmodel3; x, u, d) - for i=1:N - ynl = nonlinmodel3(d) - global yl = linmodel3(d) - Ynl[i] = ynl[1] - Yl[i] = yl[1] - global linmodel3 = linearize(nonlinmodel3; u, d) - updatestate!(nonlinmodel3, u, d) - updatestate!(linmodel3, u, d) + Ynl, Yl = let nonlinmodel3=nonlinmodel3 + N = 5 + Ynl = zeros(N) + Yl = zeros(N) + x, u, d = [0.2], [0.0], [0.0] + setstate!(nonlinmodel3, x) + linmodel3 = linearize(nonlinmodel3; x, u, d) + for i=1:N + ynl = nonlinmodel3(d) + yl = linmodel3(d) + Ynl[i] = ynl[1] + Yl[i] = yl[1] + linmodel3 = linearize(nonlinmodel3; u, d) + updatestate!(nonlinmodel3, u, d) + updatestate!(linmodel3, u, d) + end + Ynl, Yl end @test all(isapprox.(Ynl, Yl, atol=1e-6)) end diff --git a/test/3_test_predictive_control.jl b/test/3_test_predictive_control.jl index 52e07bdde..38983a835 100644 --- a/test/3_test_predictive_control.jl +++ b/test/3_test_predictive_control.jl @@ -82,11 +82,25 @@ end u = moveinput!(mpc3, [0], R̂u=fill(12, mpc3.Hp)) @test u ≈ [12] atol=1e-2 model2 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0) - mpc4 = LinMPC(model2) + mpc4 = LinMPC(model2) preparestate!(mpc4, [0]) moveinput!(mpc4, [0]) ≈ [0.0] @test_nowarn ModelPredictiveControl.info2debugstr(info) - + mpc5 = LinMPC(linmodel, Hp=1000, Hc=1, transcription=MultipleShooting()) + preparestate!(mpc5, [10]) + r = [15] + u = moveinput!(mpc5, r) + @test u ≈ [1] atol=1e-2 + info = getinfo(mpc5) + @test info[:u] ≈ [1] atol=1e-2 + @test info[:Ŷ][end] ≈ 15 atol=1e-2 + linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2]) + mpc6 = LinMPC(linmodel2, Nwt=[0], Hp=1000, Hc=1) + preparestate!(mpc6, [0], [0]) + # if d=[0.1], the output will eventually reach 7*0.1=0.7, no action needed (u=0): + d = [0.1] + u = moveinput!(mpc6, 7d, d) + @test u ≈ [0] atol=1e-2 @test_throws DimensionMismatch moveinput!(mpc1, [0,0,0]) @test_throws DimensionMismatch moveinput!(mpc1, [0], [0,0]) @test_throws DimensionMismatch moveinput!(mpc1; D̂ = fill(0, mpc1.Hp+1)) @@ -99,39 +113,48 @@ end linmodel = setop!(LinModel(tf(5, [2, 1]), 3.0), yop=[10]) r = [15] outdist = [5] - mpc_im = LinMPC(InternalModel(linmodel)) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_im, ym) - global u = moveinput!(mpc_im, r) - updatestate!(mpc_im, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_im = LinMPC(InternalModel(linmodel)) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_im, ym) + u = moveinput!(mpc_im, r) + updatestate!(mpc_im, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - mpc_nint_u = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_nint_u, ym) - global u = moveinput!(mpc_nint_u, r) - updatestate!(mpc_nint_u, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_nint_u = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_nint_u, ym) + u = moveinput!(mpc_nint_u, r) + updatestate!(mpc_nint_u, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - mpc_nint_ym = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_nint_ym, ym) - global u = moveinput!(mpc_nint_ym, r) - updatestate!(mpc_nint_ym, u, ym) - updatestate!(linmodel, u) + u,ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_nint_ym = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_nint_ym, ym) + u = moveinput!(mpc_nint_ym, r) + updatestate!(mpc_nint_ym, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 @@ -286,27 +309,30 @@ end @testitem "LinMPC terminal cost" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra model = LinModel(ss([0.5 -0.4;0.6 0.5], [1 0;0 1], [1 0; 0 1], 0, 1)) - K = lqr(Discrete, model.A, model.Bu, I, 0.5I) - M_end = ControlSystemsBase.are(Discrete, model.A, model.Bu, I, 0.5I) - M_Hp = [I(4) zeros(4,2); zeros(2,4) M_end] - mpc = LinMPC(model; Hp=3, Hc=3, M_Hp, Nwt=[0; 0], Lwt=[0.5, 0.5], nint_ym=0) - X_mpc = zeros(2,20) - setstate!(mpc,[1,1]) - setstate!(model, [1,1]) - for i=1:20 - y = model() - preparestate!(mpc, y) - u = moveinput!(mpc, [0, 0]) - X_mpc[:,i] = model.x0 - updatestate!(mpc, u, y) - updatestate!(model, u) - end - X_lqr = zeros(2,20) - x=[1,1] - for i=1:20 - global u = -K*x - X_lqr[:,i] = x - global x = model.A*x + model.Bu*u + X_mpc, X_lqr = let model=model + K = lqr(Discrete, model.A, model.Bu, I, 0.5I) + M_end = ControlSystemsBase.are(Discrete, model.A, model.Bu, I, 0.5I) + M_Hp = [I(4) zeros(4,2); zeros(2,4) M_end] + mpc = LinMPC(model; Hp=3, Hc=3, M_Hp, Nwt=[0; 0], Lwt=[0.5, 0.5], nint_ym=0) + X_mpc = zeros(2,20) + setstate!(mpc,[1,1]) + setstate!(model, [1,1]) + for i=1:20 + y = model() + preparestate!(mpc, y) + u = moveinput!(mpc, [0, 0]) + X_mpc[:,i] = model.x0 + updatestate!(mpc, u, y) + updatestate!(model, u) + end + X_lqr = zeros(2,20) + x=[1,1] + for i=1:20 + u = -K*x + X_lqr[:,i] = x + x = model.A*x + model.Bu*u + end + X_mpc, X_lqr end @test all(isapprox.(X_mpc, X_lqr, atol=1e-5)) end @@ -429,39 +455,48 @@ end linmodel = setop!(LinModel(tf(5, [2, 1]), 3.0), yop=[10]) r = [15] outdist = [5] - mpc_im = ExplicitMPC(InternalModel(linmodel)) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_im, ym) - global u = moveinput!(mpc_im, r) - updatestate!(mpc_im, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_im = ExplicitMPC(InternalModel(linmodel)) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_im, ym) + u = moveinput!(mpc_im, r) + updatestate!(mpc_im, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - mpc_nint_u = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_nint_u, ym) - global u = moveinput!(mpc_nint_u, r) - updatestate!(mpc_nint_u, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_nint_u = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_nint_u, ym) + u = moveinput!(mpc_nint_u, r) + updatestate!(mpc_nint_u, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - mpc_nint_ym = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(mpc_nint_ym, ym) - global u = moveinput!(mpc_nint_ym, r) - updatestate!(mpc_nint_ym, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + mpc_nint_ym = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(mpc_nint_ym, ym) + u = moveinput!(mpc_nint_ym, r) + updatestate!(mpc_nint_ym, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 @@ -567,6 +602,14 @@ end LHS = zeros(1) nmpc15.con.gc!(LHS,[1,2],[3,4],[4,6],10,0.1) @test LHS ≈ [10*dot([1,2],[3,4])+sum([4,6])+0.1] + nmpc16 = NonLinMPC(nonlinmodel, Hp=10, transcription=MultipleShooting()) + @test nmpc16.transcription == MultipleShooting() + @test length(nmpc16.Z̃) == nonlinmodel.nu*nmpc16.Hc + nmpc16.estim.nx̂*nmpc16.Hp + nmpc16.nϵ + @test nmpc16.con.neq == nmpc16.estim.nx̂*nmpc16.Hp + nmpc17 = NonLinMPC(linmodel1, Hp=10, transcription=MultipleShooting()) + @test nmpc17.transcription == MultipleShooting() + @test length(nmpc17.Z̃) == linmodel1.nu*nmpc17.Hc + nmpc17.estim.nx̂*nmpc17.Hp + nmpc17.nϵ + @test size(nmpc17.con.Aeq, 1) == nmpc17.estim.nx̂*nmpc17.Hp nonlinmodel2 = NonLinModel{Float32}(f, h, Ts, 2, 4, 2, 1, solver=nothing) nmpc15 = NonLinMPC(nonlinmodel2, Hp=15) @@ -586,7 +629,7 @@ end @testitem "NonLinMPC moves and getinfo" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff linmodel = setop!(LinModel(tf(5, [2000, 1]), 3000.0), yop=[10]) - Hp = 1000 + Hp = 100 nmpc_lin = NonLinMPC(linmodel, Nwt=[0], Hp=Hp, Hc=1) ry, ru = [15], [4] preparestate!(nmpc_lin, [10]) @@ -619,10 +662,10 @@ end f = (x,u,d,_) -> linmodel2.A*x + linmodel2.Bu*u + linmodel2.Bd*d h = (x,d,_) -> linmodel2.C*x + linmodel2.Dd*d nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing) - nmpc2 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=1000, Hc=1) + nmpc2 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=100, Hc=1) + preparestate!(nmpc2, [0], [0]) # if d=[0.1], the output will eventually reach 7*0.1=0.7, no action needed (u=0): d = [0.1] - preparestate!(nmpc2, [0], d) u = moveinput!(nmpc2, 7d, d) @test u ≈ [0] atol=5e-2 u = nmpc2(7d, d) @@ -630,7 +673,7 @@ end info = getinfo(nmpc2) @test info[:u] ≈ u @test info[:Ŷ][end] ≈ 7d[1] atol=5e-2 - nmpc3 = NonLinMPC(nonlinmodel, Nwt=[0], Cwt=Inf, Hp=1000, Hc=1) + nmpc3 = NonLinMPC(nonlinmodel, Nwt=[0], Cwt=Inf, Hp=100, Hc=1) preparestate!(nmpc3, [0], [0]) u = moveinput!(nmpc3, 7d, d) @test u ≈ [0] atol=5e-2 @@ -654,6 +697,20 @@ end nonlinmodel2.h!(y, Float32[0,0], Float32[0], Float32[]) preparestate!(nmpc7, [0], [0]) @test moveinput!(nmpc7, [0], [0]) ≈ [0.0] + nmpc8 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=100, Hc=1, transcription=MultipleShooting()) + preparestate!(nmpc8, [0], [0]) + u = moveinput!(nmpc8, [10], [0]) + @test u ≈ [2] atol=5e-2 + info = getinfo(nmpc8) + @test info[:u] ≈ u + @test info[:Ŷ][end] ≈ 10 atol=5e-2 + nmpc9 = NonLinMPC(linmodel, Nwt=[0], Hp=100, Hc=1, transcription=MultipleShooting()) + preparestate!(nmpc9, [10]) + u = moveinput!(nmpc9, [20]) + @test u ≈ [2] atol=5e-2 + info = getinfo(nmpc9) + @test info[:u] ≈ u + @test info[:Ŷ][end] ≈ 20 atol=5e-2 @test_nowarn ModelPredictiveControl.info2debugstr(info) end @@ -662,39 +719,48 @@ end linmodel = setop!(LinModel(tf(5, [2000, 1]), 3000.0), yop=[10]) r = [15] outdist = [5] - nmpc_im = NonLinMPC(InternalModel(linmodel)) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(nmpc_im, ym) - global u = moveinput!(nmpc_im, r) - updatestate!(nmpc_im, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + nmpc_im = NonLinMPC(InternalModel(linmodel)) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(nmpc_im, ym) + u = moveinput!(nmpc_im, r) + updatestate!(nmpc_im, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - nmpc_nint_u = NonLinMPC(SteadyKalmanFilter(linmodel, nint_u=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(nmpc_nint_u, ym) - global u = moveinput!(nmpc_nint_u, r) - updatestate!(nmpc_nint_u, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + nmpc_nint_u = NonLinMPC(SteadyKalmanFilter(linmodel, nint_u=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(nmpc_nint_u, ym) + u = moveinput!(nmpc_nint_u, r) + updatestate!(nmpc_nint_u, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 - nmpc_nint_ym = NonLinMPC(SteadyKalmanFilter(linmodel, nint_ym=[1])) - linmodel.x0 .= 0 - ym, u = linmodel() - outdist, [0.0] - for i=1:25 - global ym = linmodel() - outdist - preparestate!(nmpc_nint_ym, ym) - global u = moveinput!(nmpc_nint_ym, r) - updatestate!(nmpc_nint_ym, u, ym) - updatestate!(linmodel, u) + u, ym = let linmodel=linmodel, r=r, outdist=outdist + nmpc_nint_ym = NonLinMPC(SteadyKalmanFilter(linmodel, nint_ym=[1])) + linmodel.x0 .= 0 + ym, u = linmodel() - outdist, [0.0] + for i=1:25 + ym = linmodel() - outdist + preparestate!(nmpc_nint_ym, ym) + u = moveinput!(nmpc_nint_ym, r) + updatestate!(nmpc_nint_ym, u, ym) + updatestate!(linmodel, u) + end + u, ym end @test u ≈ [2] atol=1e-2 @test ym ≈ r atol=1e-2 diff --git a/test/5_test_doctest.jl b/test/5_test_doctest.jl new file mode 100644 index 000000000..1295c9c59 --- /dev/null +++ b/test/5_test_doctest.jl @@ -0,0 +1,16 @@ +@testitem "DocTest" begin + using Documenter + old_debug_level = get(ENV, "JULIA_DEBUG", "") + DocMeta.setdocmeta!( + ModelPredictiveControl, + :DocTestSetup, + :( + using ModelPredictiveControl, ControlSystemsBase; + ENV["JULIA_DEBUG"] = ""; # temporarily disable @debug logging for the doctests + ); + recursive=true, + warn=false + ) + doctest(ModelPredictiveControl, testset="DocTest") + ENV["JULIA_DEBUG"] = old_debug_level +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 17fa3df6a..9be719de0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,5 @@ -# spell-checker: disable - using ModelPredictiveControl -using Documenter -using Test, TestItemRunner +using Test, TestItems, TestItemRunner @run_package_tests(verbose=true) @@ -11,19 +8,6 @@ include("1_test_sim_model.jl") include("2_test_state_estim.jl") include("3_test_predictive_control.jl") include("4_test_plot_sim.jl") - -old_debug_level = get(ENV, "JULIA_DEBUG", "") -DocMeta.setdocmeta!( - ModelPredictiveControl, - :DocTestSetup, - :( - using ModelPredictiveControl, ControlSystemsBase; - ENV["JULIA_DEBUG"] = ""; # temporarily disable @debug logging for the doctests - ); - recursive=true, - warn=false -) -doctest(ModelPredictiveControl, testset="DocTest") -ENV["JULIA_DEBUG"] = old_debug_level +include("5_test_doctest.jl") nothing \ No newline at end of file