From 5db223f92e62304690cf6d471cbc15c45974f611 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Sun, 16 Aug 2026 11:18:13 -0400 Subject: [PATCH 01/30] changed: using the new `juliaci.yml` workflow --- .github/workflows/CI.yml | 49 ----------------------------- .github/workflows/TagBot.yml | 33 ------------------- .github/workflows/documentation.yml | 26 --------------- .github/workflows/juliaci.yml | 14 +++++++++ 4 files changed, 14 insertions(+), 108 deletions(-) delete mode 100644 .github/workflows/CI.yml delete mode 100644 .github/workflows/TagBot.yml delete mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/juliaci.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml deleted file mode 100644 index 144c91b1f..000000000 --- a/.github/workflows/CI.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: CI -on: - push: - branches: - - main - tags: ['*'] - pull_request: -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} -jobs: - test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} - runs-on: ${{ matrix.os }} - permissions: # needed for julia-actions/cache delete old caches that it has created - actions: write - contents: read - continue-on-error: ${{ matrix.version == 'pre' }} - strategy: - fail-fast: false - matrix: - version: - - 'lts' # long-term support release - - '1' # latest stable 1.x release - - 'pre' # latest stable prerelease - # - 'nightly' # commented since noisy + 'pre' allows testing upcoming versions - os: - - ubuntu-latest - arch: - - x64 - steps: - - name: Set JULIA_DEBUG environment variable if applicable - if: ${{ runner.debug == '1' }} - run: echo "JULIA_DEBUG=ModelPredictiveControl" >> $GITHUB_ENV - - uses: actions/checkout@v7 - - uses: julia-actions/setup-julia@v3 - with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - - uses: julia-actions/cache@v3 - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v7 - with: - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false \ No newline at end of file diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml deleted file mode 100644 index 3042569cb..000000000 --- a/.github/workflows/TagBot.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: TagBot -on: - issue_comment: - types: - - created - workflow_dispatch: - inputs: - lookback: - default: 3 -permissions: - actions: read - checks: read - contents: write - deployments: read - issues: read - discussions: read - packages: read - pages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: read -jobs: - TagBot: - if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' - runs-on: ubuntu-latest - steps: - - uses: JuliaRegistries/TagBot@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - # Edit the following line to reflect the actual name of the GitHub Secret containing your private key - ssh: ${{ secrets.DOCUMENTER_KEY }} - # ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }} \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 70a059074..000000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Documentation - -on: - push: - branches: - - main # update to match your development branch (master, main, dev, trunk, ...) - tags: '*' - pull_request: - -jobs: - build: - permissions: - contents: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: julia-actions/setup-julia@v3 - with: - version: '1' - - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - name: Build and deploy - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key - run: julia --project=docs/ docs/make.jl \ No newline at end of file diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml new file mode 100644 index 000000000..1ddc402c7 --- /dev/null +++ b/.github/workflows/juliaci.yml @@ -0,0 +1,14 @@ +name: Julia CI + +on: + push: {branches: [main,master]} + pull_request: {types: [opened,synchronize,reopened,ready_for_review,converted_to_draft]} + issue_comment: {types: [created]} + workflow_dispatch: {inputs: {feature: {type: choice, description: What to run, options: [DocDeploy,LintAndTest,TagBot]}}} + +jobs: + julia-ci: + uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 + permissions: write-all + secrets: + codecov_token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From e3f24f65ebf45630c07576e8d8c8073d9e93561f Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 09:51:18 -0400 Subject: [PATCH 02/30] removed: x64 MacOS and x86 windows on CI Not worth it, too niche. --- .github/workflows/juliaci.yml | 11 ++++++++++- test/runtests.jl | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index 1ddc402c7..c98617be6 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -4,11 +4,20 @@ on: push: {branches: [main,master]} pull_request: {types: [opened,synchronize,reopened,ready_for_review,converted_to_draft]} issue_comment: {types: [created]} - workflow_dispatch: {inputs: {feature: {type: choice, description: What to run, options: [DocDeploy,LintAndTest,TagBot]}}} + workflow_dispatch: + inputs: + feature: + type: choice + description: What to run + options: [DocDeploy, LintAndTest, TagBot] jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 + with: + include-rc-versions: true + include-windows-x86: false + include-macos-x64: false permissions: write-all secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 5a707242e..ca1e49c84 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,10 @@ using ModelPredictiveControl using Test, TestItems, TestItemRunner -@run_package_tests(verbose=true) +@run_package_tests +# Not needed for TestItems discovery, but including the files means `Pkg.test` parses them +# and catches syntax errors early: include("0_test_module.jl") include("1_test_sim_model.jl") include("2_test_state_estim.jl") From ef38567e72d680316ed95e16c8bc8ded2e4943b3 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 09:52:53 -0400 Subject: [PATCH 03/30] removed: Linux x86, same reasons --- .github/workflows/juliaci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index c98617be6..f98f6dc88 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -17,6 +17,7 @@ jobs: with: include-rc-versions: true include-windows-x86: false + include-linux-x86: false include-macos-x64: false permissions: write-all secrets: From 6334bc0f51af71efff2da0cbc4fd7b25ff892285 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:01:52 -0400 Subject: [PATCH 04/30] test: smaller chunks for nonlinear MPC and MHE tests To avoid 1200 s timouts --- test/2_test_state_estim.jl | 125 ++++++++++++++++++------------ test/3_test_predictive_control.jl | 123 +++++++++++++++++++---------- 2 files changed, 159 insertions(+), 89 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 3c98ebc2c..1342b61be 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1141,7 +1141,7 @@ end end -@testitem "MHE estimation and getinfo (NonLinModel)" setup=[SetupMPCtests] begin +@testitem "MHE estimation and getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer import ForwardDiff @@ -1229,6 +1229,65 @@ end preparestate!(mhe2, [50, 30], [5]) @test mhe2([5]) ≈ [50, 30] atol=5e-3 + Q̂ = diagm([1/4, 1/4, 1/4, 1/4].^2) + R̂ = diagm([1, 1].^2) + optim = Model(Ipopt.Optimizer) + covestim = ExtendedKalmanFilter(nonlinmodel, 1:2, 0, 0, Q̂, Q̂, R̂) + mhe5 = MovingHorizonEstimator(nonlinmodel, 1, 1:2, 0, 0, Q̂, Q̂, R̂; optim, covestim) + preparestate!(mhe5, [50, 30], [5]) + x̂ = updatestate!(mhe5, [10, 50], [50, 30], [5]) + @test x̂ ≈ zeros(4) atol=1e-9 + @test mhe5.x̂0 ≈ zeros(4) atol=1e-9 + preparestate!(mhe5, [50, 30], [5]) + @test evaloutput(mhe5, [5]) ≈ mhe5([5]) ≈ [50, 30] + info = getinfo(mhe5) + @test info[:x̂] ≈ x̂ atol=1e-9 + @test info[:Ŷ][end-1:end] ≈ [50, 30] atol=1e-9 + + # coverage of the branch with error termination status (with an infeasible problem): + mhe_infeas = MovingHorizonEstimator(nonlinmodel, He=1, Cwt=Inf) + mhe_infeas = setconstraint!(mhe_infeas, v̂min=[1, 1], v̂max=[-1, -1]) + @test_logs( + (:error, "MHE terminated without solution: estimation in open-loop "* + "(more info in debug log)"), + preparestate!(mhe_infeas, [0, 0], [0]) + ) + + # for coverage of NLP functions, the univariate syntax of JuMP.@operator + mhe7 = MovingHorizonEstimator(nonlinmodel, He=1, Cwt=Inf) + setconstraint!(mhe7, v̂min=[-51,-52], v̂max=[53,54]) + x̂ = preparestate!(mhe7, [50, 30], [5]) + @test x̂ ≈ zeros(6) atol=1e-9 + @test_nowarn ModelPredictiveControl.info2debugstr(info) + @test_throws ErrorException setstate!(mhe1, [1,2,3,4,5,6], diagm(.1:.1:.6)) + + hessian = AutoSparse( + AutoForwardDiff(); + sparsity_detector=TracerSparsityDetector(), + coloring_algorithm=GreedyColoringAlgorithm(), + ) + + mhe8 = MovingHorizonEstimator(nonlinmodel; He=2, hessian) + @test_logs( + (:warn, "NaN values in the MHE measurements ym: ignoring them in the objective"), + preparestate!(mhe8, [50, NaN], [5]) + ) + @test mhe8.x̂0 ≈ zeros(6) atol=1e-9 + +end + +@testitem "MHE estimation and getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff + using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer + import ForwardDiff + + linmodel = LinModel(sys,Ts,i_u=[1,2], i_d=[3]) + linmodel = setop!(linmodel, uop=[10,50], yop=[50,30], dop=[5]) + f(x,u,d,model) = model.A*x + model.Bu*u + model.Bd*d + h(x,d,model) = model.C*x + model.Dd*d + nonlinmodel = NonLinModel(f, h, Ts, 2, 4, 2, 1, solver=nothing, p=linmodel) + nonlinmodel = setop!(nonlinmodel, uop=[10,50], yop=[50,30], dop=[5]) + mhe3 = MovingHorizonEstimator( nonlinmodel, He=3, direct=false, transcription=MultipleShooting(f_threads=true) ) @@ -1244,22 +1303,13 @@ end updatestate!(mhe3, [10, 50], [50, 30], [5]) info = getinfo(mhe3) # test getinfo when Nk ẋ .= -0.001x .+ u h! = (y,x,_,_) -> y .= x @@ -1292,6 +1342,16 @@ end info = getinfo(mhe7) # test getinfo when Nk ẋ .= -0.001x .+ u + h! = (y,x,_,_) -> y .= x + nonlinmodel_c = NonLinModel(f!, h!, 500, 1, 1, 1) transcription = OrthogonalCollocation(f_threads=true, h_threads=true) mhe10 = MovingHorizonEstimator( @@ -1313,38 +1373,7 @@ end updatestate!(mhe11, [-6], [13]) end preparestate!(mhe11, [13]) - @test mhe11() ≈ [13] atol=5e-3 - - # coverage of the branch with error termination status (with an infeasible problem): - mhe_infeas = MovingHorizonEstimator(nonlinmodel, He=1, Cwt=Inf) - mhe_infeas = setconstraint!(mhe_infeas, v̂min=[1, 1], v̂max=[-1, -1]) - @test_logs( - (:error, "MHE terminated without solution: estimation in open-loop "* - "(more info in debug log)"), - preparestate!(mhe_infeas, [0, 0], [0]) - ) - - # for coverage of NLP functions, the univariate syntax of JuMP.@operator - mhe7 = MovingHorizonEstimator(nonlinmodel, He=1, Cwt=Inf) - setconstraint!(mhe7, v̂min=[-51,-52], v̂max=[53,54]) - x̂ = preparestate!(mhe7, [50, 30], [5]) - @test x̂ ≈ zeros(6) atol=1e-9 - @test_nowarn ModelPredictiveControl.info2debugstr(info) - @test_throws ErrorException setstate!(mhe1, [1,2,3,4,5,6], diagm(.1:.1:.6)) - - hessian = AutoSparse( - AutoForwardDiff(); - sparsity_detector=TracerSparsityDetector(), - coloring_algorithm=GreedyColoringAlgorithm(), - ) - - mhe8 = MovingHorizonEstimator(nonlinmodel; He=2, hessian) - @test_logs( - (:warn, "NaN values in the MHE measurements ym: ignoring them in the objective"), - preparestate!(mhe8, [50, NaN], [5]) - ) - @test mhe8.x̂0 ≈ zeros(6) atol=1e-9 - + @test mhe11() ≈ [13] atol=5e-3 end @testitem "MHE estimation with unfilled window" setup=[SetupMPCtests] begin diff --git a/test/3_test_predictive_control.jl b/test/3_test_predictive_control.jl index 6f4f636e9..73d3a12d8 100644 --- a/test/3_test_predictive_control.jl +++ b/test/3_test_predictive_control.jl @@ -905,7 +905,7 @@ end @test_throws ArgumentError OrthogonalCollocation(2) end -@testitem "NonLinMPC moves and getinfo (LinModel)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves and getinfo (LinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -948,14 +948,6 @@ end preparestate!(nmpc6, [0]) @test moveinput!(nmpc6, [0]) ≈ [0.0] 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 - # coverage of the branch with error termination status (with an infeasible problem): nmpc_infeas = NonLinMPC(linmodel, Hp=1, Hc=1, Cwt=Inf) nmpc_infeas = setconstraint!(nmpc_infeas, umin=[+1], umax=[-1]) @@ -969,7 +961,22 @@ end @test_nowarn ModelPredictiveControl.info2debugstr(info) end -@testitem "NonLinMPC moves and getinfo (NonLinModel)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves and getinfo (LinModel, MS)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra + using DifferentiationInterface + import FiniteDiff + linmodel = setop!(LinModel(tf(5, [2000, 1]), 3000.0), yop=[10]) + + 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 +end + +@testitem "NonLinMPC moves and getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -977,10 +984,6 @@ end f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d h = (x,d,model) -> model.C*x + model.Dd*d nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) - - f! = (ẋ,x,u,_,_) -> ẋ .= -0.001x .+ u - h! = (y,x,_,_) -> y .= x - nonlinmodel_c = NonLinModel(f!, h!, 500, 1, 1, 1) nmpc1 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=100, Hc=1) preparestate!(nmpc1, [0], [0]) @@ -1004,6 +1007,43 @@ end u = moveinput!(nmpc3, [0], d, R̂u=fill(12, nmpc3.Hp)) @test u ≈ [12] atol=5e-2 + nonlinmodel2 = NonLinModel{Float32}(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) + nmpc7 = NonLinMPC(nonlinmodel2, Hp=10) + y = similar(nonlinmodel2.yop) + ModelPredictiveControl.h!(y, nonlinmodel2, Float32[0,0], Float32[0], nonlinmodel2.p) + preparestate!(nmpc7, [0], [0]) + @test moveinput!(nmpc7, [0], [0]) ≈ [0.0] atol=5e-2 + + nmpc10 = setconstraint!(NonLinMPC( + nonlinmodel, Nwt=[0], Hp=100, Hc=1, + gradient=AutoFiniteDiff(), + jacobian=AutoFiniteDiff(), + hessian=AutoFiniteDiff() + ), ymax=[100], ymin=[-100] + ) + preparestate!(nmpc10, [0], [0]) + u = moveinput!(nmpc10, [10], [0]) + @test u ≈ [2] atol=5e-2 + info = getinfo(nmpc10) + @test info[:u] ≈ u + @test info[:Ŷ][end] ≈ 10 atol=5e-2 + + nmpc11 = NonLinMPC(nonlinmodel, Hp=10, Hc=[1, 2, 3, 4], Nwt=[10]) + preparestate!(nmpc11, y, [0]) + moveinput!(nmpc11, [10], [0]) + ΔU_diff = diff(getinfo(nmpc11)[:U]) + @test ΔU_diff[[2, 4, 5, 7, 8, 9]] ≈ zeros(6) atol=1e-9 +end + +@testitem "NonLinMPC moves and getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra + using DifferentiationInterface + import FiniteDiff + linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2]) + f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d + h = (x,d,model) -> model.C*x + model.Dd*d + nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) + transcription = MultipleShooting() nmpc4 = NonLinMPC(nonlinmodel; Nwt=[0], Hp=100, Hc=1, transcription) preparestate!(nmpc4, [0], [0]) @@ -1022,6 +1062,20 @@ end info = getinfo(nmpc4t) @test info[:u] ≈ u @test info[:Ŷ][end] ≈ 10 atol=5e-2 +end + +@testitem "NonLinMPC moves and getinfo (NonLinModel, TC)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra + using DifferentiationInterface + import FiniteDiff + linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2]) + f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d + h = (x,d,model) -> model.C*x + model.Dd*d + nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) + + f! = (ẋ,x,u,_,_) -> ẋ .= -0.001x .+ u + h! = (y,x,_,_) -> y .= x + nonlinmodel_c = NonLinModel(f!, h!, 500, 1, 1, 1) transcription = TrapezoidalCollocation(0, f_threads=true, h_threads=true) nmpc5 = NonLinMPC(nonlinmodel_c; Nwt=[0], Hp=100, Hc=1, transcription) @@ -1034,6 +1088,20 @@ end preparestate!(nmpc5_1, [0.0]) u = moveinput!(nmpc5_1, [1/0.001]) @test u ≈ [1.0] atol=5e-2 +end + +@testitem "NonLinMPC moves and getinfo (NonLinModel, OC)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra + using DifferentiationInterface + import FiniteDiff + linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2]) + f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d + h = (x,d,model) -> model.C*x + model.Dd*d + nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) + + f! = (ẋ,x,u,_,_) -> ẋ .= -0.001x .+ u + h! = (y,x,_,_) -> y .= x + nonlinmodel_c = NonLinModel(f!, h!, 500, 1, 1, 1) transcription = OrthogonalCollocation(0, 4) nmpc6 = NonLinMPC(nonlinmodel_c; Nwt=[0], Hp=100, Hc=1, transcription) @@ -1049,33 +1117,6 @@ end setstate!(nmpc6_1, [5.0]) moveinput!(nmpc6_1) @test nmpc6_1.con.FS ≈ nmpc6_1.con.KS*nmpc6_1.estim.x̂0 # OC + IMC: special lin. eq. method - - nonlinmodel2 = NonLinModel{Float32}(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2) - nmpc7 = NonLinMPC(nonlinmodel2, Hp=10) - y = similar(nonlinmodel2.yop) - ModelPredictiveControl.h!(y, nonlinmodel2, Float32[0,0], Float32[0], nonlinmodel2.p) - preparestate!(nmpc7, [0], [0]) - @test moveinput!(nmpc7, [0], [0]) ≈ [0.0] atol=5e-2 - - nmpc10 = setconstraint!(NonLinMPC( - nonlinmodel, Nwt=[0], Hp=100, Hc=1, - gradient=AutoFiniteDiff(), - jacobian=AutoFiniteDiff(), - hessian=AutoFiniteDiff() - ), ymax=[100], ymin=[-100] - ) - preparestate!(nmpc10, [0], [0]) - u = moveinput!(nmpc10, [10], [0]) - @test u ≈ [2] atol=5e-2 - info = getinfo(nmpc10) - @test info[:u] ≈ u - @test info[:Ŷ][end] ≈ 10 atol=5e-2 - - nmpc11 = NonLinMPC(nonlinmodel, Hp=10, Hc=[1, 2, 3, 4], Nwt=[10]) - preparestate!(nmpc11, y, [0]) - moveinput!(nmpc11, [10], [0]) - ΔU_diff = diff(getinfo(nmpc11)[:U]) - @test ΔU_diff[[2, 4, 5, 7, 8, 9]] ≈ zeros(6) atol=1e-9 end @testitem "NonLinMPC step disturbance rejection" setup=[SetupMPCtests] begin From cb53222c636b3f19fef98da90e567e725bc40527 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:02:39 -0400 Subject: [PATCH 05/30] test: remove useless test for MHE The univariate syntax of `@operator` is impossible for MHE --- test/2_test_state_estim.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 1342b61be..e2c8d06a0 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1253,11 +1253,6 @@ end preparestate!(mhe_infeas, [0, 0], [0]) ) - # for coverage of NLP functions, the univariate syntax of JuMP.@operator - mhe7 = MovingHorizonEstimator(nonlinmodel, He=1, Cwt=Inf) - setconstraint!(mhe7, v̂min=[-51,-52], v̂max=[53,54]) - x̂ = preparestate!(mhe7, [50, 30], [5]) - @test x̂ ≈ zeros(6) atol=1e-9 @test_nowarn ModelPredictiveControl.info2debugstr(info) @test_throws ErrorException setstate!(mhe1, [1,2,3,4,5,6], diagm(.1:.1:.6)) From 500995230445ab03e0648ce04498400110c95e03 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:16:45 -0400 Subject: [PATCH 06/30] test: shorter titles --- test/2_test_state_estim.jl | 18 +++++++++++++----- test/3_test_predictive_control.jl | 16 ++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index e2c8d06a0..28eddb991 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1031,7 +1031,7 @@ end ) end -@testitem "MHE estimation and getinfo (LinModel)" setup=[SetupMPCtests] begin +@testitem "MHE estim. & getinfo (LinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, DAQP linmodel = LinModel(sys,Ts,i_u=[1,2], i_d=[3]) @@ -1123,6 +1123,14 @@ end updatestate!(mhe5, [10, 50], [50, NaN], [5]) ) +end + +@testitem "MHE estim. & getinfo (LinModel, MS)" setup=[SetupMPCtests] begin + using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff + using JuMP, DAQP + linmodel = LinModel(sys,Ts,i_u=[1,2], i_d=[3]) + linmodel = setop!(linmodel, uop=[10,50], yop=[50,30], dop=[5]) + mhe5 = MovingHorizonEstimator(linmodel, He=2, direct=true, transcription=MultipleShooting()) for i in 1:40 preparestate!(mhe5, [51, 32], [5]) @@ -1141,7 +1149,7 @@ end end -@testitem "MHE estimation and getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin +@testitem "MHE estim. & getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer import ForwardDiff @@ -1271,7 +1279,7 @@ end end -@testitem "MHE estimation and getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin +@testitem "MHE estim. & getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer import ForwardDiff @@ -1301,7 +1309,7 @@ end @test info[:V̂] ≈ [0.0, 0.0] end -@testitem "MHE estimation and getinfo (NonLinModel, TC)" setup=[SetupMPCtests] begin +@testitem "MHE estim. & getinfo (NonLinModel, TC)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer import ForwardDiff @@ -1339,7 +1347,7 @@ end @test info[:V̂] ≈ [0.0] end -@testitem "MHE estimation and getinfo (NonLinModel, OC)" setup=[SetupMPCtests] begin +@testitem "MHE estim. & getinfo (NonLinModel, OC)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, ForwardDiff using JuMP, Ipopt, DifferentiationInterface, SparseMatrixColorings, SparseConnectivityTracer import ForwardDiff diff --git a/test/3_test_predictive_control.jl b/test/3_test_predictive_control.jl index 73d3a12d8..37d73e0eb 100644 --- a/test/3_test_predictive_control.jl +++ b/test/3_test_predictive_control.jl @@ -89,7 +89,7 @@ @test_throws DimensionMismatch LinMPC(model, Wy=ones(2, model.ny), Wu=ones(3, model.nu)) end -@testitem "LinMPC moves and getinfo" setup=[SetupMPCtests] begin +@testitem "LinMPC moves & getinfo" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using .SetupMPCtests, ControlSystemsBase, LinearAlgebra linmodel = setop!(LinModel(tf(5, [2, 1]), 3), yop=[10]) @@ -637,7 +637,7 @@ end end -@testitem "ExplicitMPC moves and getinfo" setup=[SetupMPCtests] begin +@testitem "ExplicitMPC moves & getinfo" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra model = LinModel(tf(5, [2, 1]), 3) mpc1 = ExplicitMPC(model, Nwt=[0], Hp=1000, Hc=1) @@ -905,7 +905,7 @@ end @test_throws ArgumentError OrthogonalCollocation(2) end -@testitem "NonLinMPC moves and getinfo (LinModel, SS)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (LinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -961,7 +961,7 @@ end @test_nowarn ModelPredictiveControl.info2debugstr(info) end -@testitem "NonLinMPC moves and getinfo (LinModel, MS)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (LinModel, MS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -976,7 +976,7 @@ end @test info[:Ŷ][end] ≈ 20 atol=5e-2 end -@testitem "NonLinMPC moves and getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (NonLinModel, SS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -1035,7 +1035,7 @@ end @test ΔU_diff[[2, 4, 5, 7, 8, 9]] ≈ zeros(6) atol=1e-9 end -@testitem "NonLinMPC moves and getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (NonLinModel, MS)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -1064,7 +1064,7 @@ end @test info[:Ŷ][end] ≈ 10 atol=5e-2 end -@testitem "NonLinMPC moves and getinfo (NonLinModel, TC)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (NonLinModel, TC)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff @@ -1090,7 +1090,7 @@ end @test u ≈ [1.0] atol=5e-2 end -@testitem "NonLinMPC moves and getinfo (NonLinModel, OC)" setup=[SetupMPCtests] begin +@testitem "NonLinMPC moves & getinfo (NonLinModel, OC)" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra using DifferentiationInterface import FiniteDiff From 6a25b0b92947f0fb7625826e410bc572928f6615 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:18:34 -0400 Subject: [PATCH 07/30] doc: less finicky `jldoctest` --- src/sim_model.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sim_model.jl b/src/sim_model.jl index 15ed1595e..9a63db123 100644 --- a/src/sim_model.jl +++ b/src/sim_model.jl @@ -312,7 +312,7 @@ julia> function sim_realtime!(model) t_0 = time() for i=1:3 t = savetime!(model) # first function called - println(round(t - t_0, digits=3)) + println(round(t - t_0, digits=2)) updatestate!(model, [1]) periodsleep(model, true) # last function called end From 308c47bab33628b301671ed771b2e3411b7a837c Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:38:08 -0400 Subject: [PATCH 08/30] test: small `Aqua` tests --- test/7_test_aqua.jl | 55 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/test/7_test_aqua.jl b/test/7_test_aqua.jl index 2eca61e33..754ca3317 100644 --- a/test/7_test_aqua.jl +++ b/test/7_test_aqua.jl @@ -1,9 +1,48 @@ -@testitem "Aqua" begin - using Aqua - # All the functions defined inside `ModelPredictiveControl.get_nonlinobj_op` with the - # `Vararg`s have unbound type parameters. This is necessary for the splatting syntax of - # `JuMP.@operator`, and JuMP will never call these functions with 0 argument, so - # defining zero-argument methods would be useless, so I disable this check here. - unbound_args = false - Aqua.test_all(ModelPredictiveControl; unbound_args) +@testitem "Aqua ambiguities" begin + using Aqua + Aqua.test_ambiguities(ModelPredictiveControl) +end + +### All the functions defined inside `ModelPredictiveControl.get_nonlinobj_op` with the +### `Vararg`s have unbound type parameters. This is necessary for the splatting syntax of +### `JuMP.@operator`, and JuMP will never call these functions with 0 argument, so +### defining zero-argument methods would be useless, so I disable this check here. +# @testitem "Aqua unbound args" begin +# using Aqua +# Aqua.test_unbound_args(ModelPredictiveControl) +# end + +@testitem "Aqua undefined exports" begin + using Aqua + Aqua.test_undefined_exports(ModelPredictiveControl) +end + +@testitem "Aqua project extras" begin + using Aqua + Aqua.test_project_extras(ModelPredictiveControl) +end + +@testitem "Aqua stale deps" begin + using Aqua + Aqua.test_stale_deps(ModelPredictiveControl) +end + +@testitem "Aqua deps compat" begin + using Aqua + Aqua.test_deps_compat(ModelPredictiveControl) +end + +@testitem "Aqua piracies" begin + using Aqua + Aqua.test_piracies(ModelPredictiveControl) +end + +@testitem "Aqua persistent tasks" begin + using Aqua + Aqua.test_persistent_tasks(ModelPredictiveControl) +end + +@testitem "Aqua undocumented names" begin + using Aqua + Aqua.test_undocumented_names(ModelPredictiveControl) end \ No newline at end of file From aac8d565dab965b5767c396ae89c967e139e1c0d Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 11:44:12 -0400 Subject: [PATCH 09/30] test: less finicky soft real-time measurements --- test/1_test_sim_model.jl | 4 ++-- test/2_test_state_estim.jl | 2 +- test/3_test_predictive_control.jl | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/1_test_sim_model.jl b/test/1_test_sim_model.jl index e2eabb750..bd480f8c8 100644 --- a/test/1_test_sim_model.jl +++ b/test/1_test_sim_model.jl @@ -152,7 +152,7 @@ end updatestate!(linmodel2, [1]) periodsleep(linmodel2, true) end - @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.0001)) + @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.025)) end @testitem "NonLinModel construction" setup=[SetupMPCtests] begin @@ -419,5 +419,5 @@ end updatestate!(nonlinmodel2, [1]) periodsleep(nonlinmodel2, true) end - @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.0001)) + @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.025)) end \ No newline at end of file diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 28eddb991..06e3b6267 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -149,7 +149,7 @@ end updatestate!(kalmanfilter1, [1], [1]) periodsleep(kalmanfilter1, true) end - @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.01)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) end @testitem "KF construction" setup=[SetupMPCtests] begin diff --git a/test/3_test_predictive_control.jl b/test/3_test_predictive_control.jl index 37d73e0eb..134466046 100644 --- a/test/3_test_predictive_control.jl +++ b/test/3_test_predictive_control.jl @@ -581,7 +581,7 @@ end @testitem "LinMPC real-time simulations" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra - linmodel1 = LinModel(tf(2, [10, 1]), 0.1) + linmodel1 = LinModel(tf(2, [10, 1]), 0.25) mpc1 = LinMPC(linmodel1) times1 = zeros(5) for i=1:5 @@ -590,7 +590,7 @@ end updatestate!(mpc1, [1], [1]) periodsleep(mpc1, true) end - @test all(isapprox.(diff(times1[2:end]), 0.1, atol=0.01)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) end @testitem "ExplicitMPC construction" setup=[SetupMPCtests] begin From 4bb82fce0ae19126626a41f51ee0aefca7a62538 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 12:14:32 -0400 Subject: [PATCH 10/30] test: shorter names --- test/2_test_state_estim.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 06e3b6267..0c63d8016 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1379,7 +1379,7 @@ end @test mhe11() ≈ [13] atol=5e-3 end -@testitem "MHE estimation with unfilled window" setup=[SetupMPCtests] begin +@testitem "MHE estim. with unfilled window" setup=[SetupMPCtests] begin f(x,u,_,_) = 0.5x + u h(x,_,_) = x model = NonLinModel(f, h, 10.0, 1, 1, 1, solver=nothing) @@ -1405,7 +1405,7 @@ end end -@testitem "MHE fallbacks for arrival covariance estimation" setup=[SetupMPCtests] begin +@testitem "MHE fallbacks for arrival covariance" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra linmodel = setop!(LinModel(sys,Ts,i_u=[1,2], i_d=[3]), uop=[10,50], yop=[50,30], dop=[5]) f(x,u,d,model) = model.A*x + model.Bu*u + model.Bd*d From 667a7b23506bbbe351538bd4265864b91f734a25 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 13:12:31 -0400 Subject: [PATCH 11/30] test: timeout from 1200 -> 3600 --- .github/workflows/juliaci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index f98f6dc88..a32b422a3 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,6 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: + testitem-timeout: 3600 # some Aqua tests are long (default 1200 is nor enough) include-rc-versions: true include-windows-x86: false include-linux-x86: false From b6eb1e0b303471e5d7d0cf17cfe7aad95cf0fb22 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 13:12:53 -0400 Subject: [PATCH 12/30] test: include Linux x86 --- .github/workflows/juliaci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index a32b422a3..9d569befb 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -18,7 +18,6 @@ jobs: testitem-timeout: 3600 # some Aqua tests are long (default 1200 is nor enough) include-rc-versions: true include-windows-x86: false - include-linux-x86: false include-macos-x64: false permissions: write-all secrets: From 933d70dc3db483b77964dfead4b67396f02a7eec Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 14:19:27 -0400 Subject: [PATCH 13/30] test: debug `jldoctest` for `x86` platforms --- src/estimator/construct.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/estimator/construct.jl b/src/estimator/construct.jl index 328593021..c7f187787 100644 --- a/src/estimator/construct.jl +++ b/src/estimator/construct.jl @@ -359,11 +359,8 @@ and unstable `model` (see Examples). ```jldoctest julia> model = LinModel(append(tf(3, [10, 1]), tf(2, [1, 0]), tf(4,[-5, 1])), 1.0); -julia> nint_ym = default_nint(model) -3-element Vector{Int64}: - 1 - 0 - 1 +julia> default_nint(model) |> println +[1, 0, 1] ``` """ function default_nint(model::LinModel, i_ym=1:model.ny, nint_u=0) From c7dab4e66afe2c347b8a8f64b1e38147e8439462 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 14:32:25 -0400 Subject: [PATCH 14/30] test: skipping all x86 jobs for now There is a bug in DAQP.jl on x86. I can add a `skip` option on x86, but the skip option is not released yet. --- .github/workflows/juliaci.yml | 1 + test/5_test_extensions.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index 9d569befb..a32b422a3 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -18,6 +18,7 @@ jobs: testitem-timeout: 3600 # some Aqua tests are long (default 1200 is nor enough) include-rc-versions: true include-windows-x86: false + include-linux-x86: false include-macos-x64: false permissions: write-all secrets: diff --git a/test/5_test_extensions.jl b/test/5_test_extensions.jl index 1384c0bd5..ee4ec83be 100644 --- a/test/5_test_extensions.jl +++ b/test/5_test_extensions.jl @@ -1,3 +1,4 @@ +# TODO: add `skip=(Sys.ARCH==:x86)` because bug in DAQP.jl, and activate Linux x86 CI job in juliaci.yml @testitem "LinearMPCext general" setup=[SetupMPCtests] begin using .SetupMPCtests, ControlSystemsBase, LinearAlgebra, JuMP, DAQP import LinearMPC From 8d12b73c16ff7fa737e7a7b25de301834f001fe6 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 17:13:54 -0400 Subject: [PATCH 15/30] test: loosen tolerances for MHE v.s. KF tests --- test/2_test_state_estim.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 0c63d8016..8e590673e 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1832,7 +1832,7 @@ end updatestate!(mhe, [11, 50], y, [25]) updatestate!(kf, [11, 50], y, [25]) end - @test X̂_mhe ≈ X̂_kf atol=1e-6 rtol=1e-6 + @test X̂_mhe ≈ X̂_kf atol=1e-3 rtol=1e-3 kf = KalmanFilter(linmodel, nint_ym=0, direct=true) # recuperate P̂(-1|-1) exact value using the Kalman filter: preparestate!(kf, [50, 30], [20]) @@ -1850,7 +1850,7 @@ end updatestate!(mhe, [11, 50], y, [25]) updatestate!(kf, [11, 50], y, [25]) end - @test X̂_mhe ≈ X̂_kf atol=1e-6 rtol=1e-6 + @test X̂_mhe ≈ X̂_kf atol=1e-3 rtol=1e-3 f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d h = (x,d,model) -> model.C*x + model.Dd*d @@ -1874,8 +1874,8 @@ end updatestate!(ukf, [11, 50], y, [25]) updatestate!(ekf, [11, 50], y, [25]) end - @test X̂_mhe ≈ X̂_ukf atol=1e-6 rtol=1e-6 - @test X̂_mhe ≈ X̂_ekf atol=1e-6 rtol=1e-6 + @test X̂_mhe ≈ X̂_ukf atol=1e-3 rtol=1e-3 + @test X̂_mhe ≈ X̂_ekf atol=1e-3 rtol=1e-3 ukf = UnscentedKalmanFilter(nonlinmodel, nint_ym=0, direct=true) ekf = ExtendedKalmanFilter(nonlinmodel, nint_ym=0, direct=true) @@ -1901,8 +1901,8 @@ end updatestate!(ukf, [11, 50], y, [25]) updatestate!(ekf, [11, 50], y, [25]) end - @test X̂_mhe ≈ X̂_ukf atol=1e-6 rtol=1e-6 - @test X̂_mhe ≈ X̂_ekf atol=1e-6 rtol=1e-6 + @test X̂_mhe ≈ X̂_ukf atol=1e-3 rtol=1e-3 + @test X̂_mhe ≈ X̂_ekf atol=1e-3 rtol=1e-3 end @testitem "MHE LinModel v.s. NonLinModel" setup=[SetupMPCtests] begin From ac4f22e5b7334ce6a04b648c0e4a79ba740e01aa Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 17:31:08 -0400 Subject: [PATCH 16/30] test : wip debugging last test on MacOS lts --- test/2_test_state_estim.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 8e590673e..fb93fa0ad 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1624,8 +1624,9 @@ end linmodel2 = LinModel(sys, Ts, i_u=[1,2], i_d=[3]) linmodel2 = setop!(linmodel2, uop=[10,50], yop=[50,30], dop=[5]) function gclv!(LHS, X̂e, _, _, _, _, _, _, _, nx̂, _ ) - for i in 1:div(length(X̂e), nx̂) - LHS[(i-1)+1] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 + nc_Nk = length(X̂e) ÷ nx̂ + for i in 1:nc_Nk + LHS[i] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 end return nothing end @@ -1710,8 +1711,9 @@ end nonlinmodel2 = NonLinModel(f, h, Ts, 2, 4, 2, 1, solver=nothing, p=linmodel2) nonlinmodel2 = setop!(nonlinmodel2, uop=[10,50], yop=[50,30], dop=[5]) function gcnlv!(LHS, X̂e, _, _, _, _, _, _, _, nx̂, _) - for i in 1:div(length(X̂e), nx̂) - LHS[(i-1)+1] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 + nc_Nk = length(X̂e) ÷ nx̂ + for i in 1:nc_Nk + LHS[i] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 end return nothing end From 7255d9f1e43fef08b6b28f784d8b3ff2acac1d05 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 17:34:12 -0400 Subject: [PATCH 17/30] test: debug `gc` tests on MHE 90% sure that it was reading un-initialized values in the `gc` vector when `Nk < He`. --- test/2_test_state_estim.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index fb93fa0ad..7bf2c7885 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -1625,6 +1625,7 @@ end linmodel2 = setop!(linmodel2, uop=[10,50], yop=[50,30], dop=[5]) function gclv!(LHS, X̂e, _, _, _, _, _, _, _, nx̂, _ ) nc_Nk = length(X̂e) ÷ nx̂ + LHS .= 0 # for when Nk < He, fill unused values with 0s for i in 1:nc_Nk LHS[i] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 end @@ -1712,6 +1713,7 @@ end nonlinmodel2 = setop!(nonlinmodel2, uop=[10,50], yop=[50,30], dop=[5]) function gcnlv!(LHS, X̂e, _, _, _, _, _, _, _, nx̂, _) nc_Nk = length(X̂e) ÷ nx̂ + LHS .= 0 # for when Nk < He, fill unused values with 0s for i in 1:nc_Nk LHS[i] = 0.5 - X̂e[(i-1)*nx̂ + 1] # First state >= 0.5 end From 04cd48cd2a545410f0952bd05d2dd9cd6967466d Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 20:15:38 -0400 Subject: [PATCH 18/30] test: disabling timeout altogether It seems, one of my test timeout even if it all passes. --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index a32b422a3..8f1aca89a 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: 3600 # some Aqua tests are long (default 1200 is nor enough) + testitem-timeout: 0 include-rc-versions: true include-windows-x86: false include-linux-x86: false From ac7193939abfb8cf58c0855109884675f6e34b0b Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 20:27:25 -0400 Subject: [PATCH 19/30] test: idem, trying `null` --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index 8f1aca89a..a638e76d1 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: 0 + testitem-timeout: null include-rc-versions: true include-windows-x86: false include-linux-x86: false From 33f15b418916f2c6ab41919a8977dc4adb60e735 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 20:29:17 -0400 Subject: [PATCH 20/30] test: same, trying empty string --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index a638e76d1..ef6677fae 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: null + testitem-timeout: include-rc-versions: true include-windows-x86: false include-linux-x86: false From 3bfc76c8dd11568e3d15cf4ec1ec37e0209f3a1b Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 20:30:28 -0400 Subject: [PATCH 21/30] test: trying back 3600 --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index ef6677fae..ea09a233e 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: + testitem-timeout: 3600 include-rc-versions: true include-windows-x86: false include-linux-x86: false From 6cbd01b4031f5495b0e6e0416fd4f93617597435 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 20:30:54 -0400 Subject: [PATCH 22/30] test: trying Inf --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index ea09a233e..8cecdd35a 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: 3600 + testitem-timeout: Inf include-rc-versions: true include-windows-x86: false include-linux-x86: false From 07b9fcd5f502ab935baf8946ded5490f8e2a0b12 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 22:38:17 -0400 Subject: [PATCH 23/30] test: trying -1 --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index 8cecdd35a..6dcc7fa19 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: Inf + testitem-timeout: -1 include-rc-versions: true include-windows-x86: false include-linux-x86: false From f680aa0578115eca15e2957d7140584073b71fb0 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 20 Aug 2026 22:53:31 -0400 Subject: [PATCH 24/30] test: going back to 3600 --- .github/workflows/juliaci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index 6dcc7fa19..ea09a233e 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,7 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: -1 + testitem-timeout: 3600 include-rc-versions: true include-windows-x86: false include-linux-x86: false From 61398a60d4279386fb0f7ba838c1cda0d59b1697 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Fri, 21 Aug 2026 09:05:07 -0400 Subject: [PATCH 25/30] bench: result on summary Since the CI is now also there. --- .github/workflows/benchmark.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 49c57a06e..0cee71d87 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,8 +3,6 @@ on: pull_request_target: branches: [ main ] types: [labeled, opened, synchronize, reopened] -permissions: - pull-requests: write # needed to post comments jobs: bench: runs-on: ubuntu-latest @@ -12,4 +10,5 @@ jobs: steps: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: - julia-version: '1' \ No newline at end of file + julia-version: '1' + job-summary: 'true' \ No newline at end of file From a248ea8e4a3d17a3c005a92cf48d59a1d8336561 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Fri, 21 Aug 2026 15:39:22 -0400 Subject: [PATCH 26/30] test: looser tolerance for soft real-time --- test/1_test_sim_model.jl | 8 ++++---- test/2_test_state_estim.jl | 2 +- test/3_test_predictive_control.jl | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/1_test_sim_model.jl b/test/1_test_sim_model.jl index bd480f8c8..8a618b864 100644 --- a/test/1_test_sim_model.jl +++ b/test/1_test_sim_model.jl @@ -144,7 +144,7 @@ end updatestate!(linmodel1, [1]) periodsleep(linmodel1) end - @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.05)) linmodel2 = LinModel(tf(2, [0.1, 1]), 0.25) times2 = zeros(5) for i=1:5 @@ -152,7 +152,7 @@ end updatestate!(linmodel2, [1]) periodsleep(linmodel2, true) end - @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.05)) end @testitem "NonLinModel construction" setup=[SetupMPCtests] begin @@ -406,7 +406,7 @@ end updatestate!(nonlinmodel1, [1]) periodsleep(nonlinmodel1) end - @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.05)) linmodel2 = LinModel(tf(2, [0.1, 1]), 0.25) nonlinmodel2 = NonLinModel( (x,u,_,_)->linmodel2.A*x + linmodel2.Bu*u, @@ -419,5 +419,5 @@ end updatestate!(nonlinmodel2, [1]) periodsleep(nonlinmodel2, true) end - @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times2[2:end]), 0.25, atol=0.05)) end \ No newline at end of file diff --git a/test/2_test_state_estim.jl b/test/2_test_state_estim.jl index 7bf2c7885..b920e0d06 100644 --- a/test/2_test_state_estim.jl +++ b/test/2_test_state_estim.jl @@ -149,7 +149,7 @@ end updatestate!(kalmanfilter1, [1], [1]) periodsleep(kalmanfilter1, true) end - @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.05)) end @testitem "KF construction" setup=[SetupMPCtests] begin diff --git a/test/3_test_predictive_control.jl b/test/3_test_predictive_control.jl index 134466046..83ba626e7 100644 --- a/test/3_test_predictive_control.jl +++ b/test/3_test_predictive_control.jl @@ -590,7 +590,7 @@ end updatestate!(mpc1, [1], [1]) periodsleep(mpc1, true) end - @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.025)) + @test all(isapprox.(diff(times1[2:end]), 0.25, atol=0.05)) end @testitem "ExplicitMPC construction" setup=[SetupMPCtests] begin From 64c8ea3de484e9a2684f8e854cb8dfcf2706eeb4 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Fri, 21 Aug 2026 15:39:50 -0400 Subject: [PATCH 27/30] test: removing Aqua persistent tasks Not necessary for this package --- test/7_test_aqua.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/7_test_aqua.jl b/test/7_test_aqua.jl index 754ca3317..fb907c80b 100644 --- a/test/7_test_aqua.jl +++ b/test/7_test_aqua.jl @@ -37,10 +37,12 @@ end Aqua.test_piracies(ModelPredictiveControl) end -@testitem "Aqua persistent tasks" begin - using Aqua - Aqua.test_persistent_tasks(ModelPredictiveControl) -end +### This package does not use a __init__ function, and will very likely never use one, +### so the persistent task test is useless (and slow, because of the long precompilation) +# @testitem "Aqua persistent tasks" begin +# using Aqua +# Aqua.test_persistent_tasks(ModelPredictiveControl) +# end @testitem "Aqua undocumented names" begin using Aqua From b0faa5197d6630af1f11aa435e333b80601ef058 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Fri, 21 Aug 2026 20:21:23 -0400 Subject: [PATCH 28/30] doc: regex to improve `jldoctest` robustness --- src/sim_model.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sim_model.jl b/src/sim_model.jl index 9a63db123..ffdb9f6e7 100644 --- a/src/sim_model.jl +++ b/src/sim_model.jl @@ -289,7 +289,7 @@ function savetime!(model::SimModel) return model.t[] end -""" +@doc raw""" periodsleep(model::SimModel, busywait=false) -> nothing Sleep for `model.Ts` s minus the time elapsed since the last call to [`savetime!`](@ref). @@ -305,7 +305,7 @@ simple soft real-time simulations, see the example below. restart it at a convenient time e.g.: just before calling `periodsleep`. # Examples -```jldoctest +```jldoctest; filter = r"(\d+\.\d)\d*" => s"\1" julia> model = LinModel(tf(2, [0.3, 1]), 0.25); julia> function sim_realtime!(model) From 78a6878e825f5bb171b755cdeaaeb8347cb8b952 Mon Sep 17 00:00:00 2001 From: Francis Gagnon <34136215+franckgaga@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:47:31 -0400 Subject: [PATCH 29/30] bench: add permissions for pull request comments in benchmark.yml --- .github/workflows/benchmark.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0cee71d87..821c0aad6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,6 +3,8 @@ on: pull_request_target: branches: [ main ] types: [labeled, opened, synchronize, reopened] +permissions: + pull-requests: write # needed to post comments jobs: bench: runs-on: ubuntu-latest @@ -11,4 +13,4 @@ jobs: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: julia-version: '1' - job-summary: 'true' \ No newline at end of file + job-summary: 'true' From a33dfc46bd55df21f5d100aad4d15c9767d0a26e Mon Sep 17 00:00:00 2001 From: Francis Gagnon <34136215+franckgaga@users.noreply.github.com> Date: Mon, 24 Aug 2026 08:00:29 -0400 Subject: [PATCH 30/30] bench: revert changes in the files Results on summary instead of comments does not seems to work --- .github/workflows/benchmark.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 821c0aad6..788c19ffa 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -13,4 +13,3 @@ jobs: - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 with: julia-version: '1' - job-summary: 'true'