From 5db223f92e62304690cf6d471cbc15c45974f611 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Sun, 16 Aug 2026 11:18:13 -0400 Subject: [PATCH 01/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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' From c9b37432319edecdde7926a02401d7f613ca30af Mon Sep 17 00:00:00 2001 From: franckgaga Date: Tue, 25 Aug 2026 16:35:45 -0400 Subject: [PATCH 31/40] test: removing timeout config It should default to "no timeout" right now --- .github/workflows/juliaci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index ea09a233e..f98f6dc88 100644 --- a/.github/workflows/juliaci.yml +++ b/.github/workflows/juliaci.yml @@ -15,7 +15,6 @@ jobs: julia-ci: uses: julia-testitems/testitem-workflow/.github/workflows/juliaci.yml@v2 with: - testitem-timeout: 3600 include-rc-versions: true include-windows-x86: false include-linux-x86: false From dfe7e21eff1e9642262e5b692f8ec79c65da4357 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Tue, 25 Aug 2026 22:33:52 -0400 Subject: [PATCH 32/40] test: trying memory treshold It may help for MacOS jobs --- .github/workflows/juliaci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/juliaci.yml b/.github/workflows/juliaci.yml index f98f6dc88..a1c88592b 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: + memory-threshold: 0.9 include-rc-versions: true include-windows-x86: false include-linux-x86: false From 52ee07a4559ffe357cf80a2b34713fd0288ee894 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Wed, 26 Aug 2026 15:36:40 -0400 Subject: [PATCH 33/40] doc: no `jldoctest` for `periodsleep` This is too finicky on some platforms e.g. MacOS. --- 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 ffdb9f6e7..a8a76cfaa 100644 --- a/src/sim_model.jl +++ b/src/sim_model.jl @@ -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; filter = r"(\d+\.\d)\d*" => s"\1" +```julia-repl julia> model = LinModel(tf(2, [0.3, 1]), 0.25); julia> function sim_realtime!(model) From 6d3c194b55b645c831c2008c9a521393ebcd32a7 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 10:19:00 -0400 Subject: [PATCH 34/40] doc: add signuature headers on abstract types --- src/predictive_control.jl | 2 ++ src/sim_model.jl | 2 ++ src/state_estim.jl | 2 ++ src/transcription.jl | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/predictive_control.jl b/src/predictive_control.jl index 981f6dd28..3772d4ade 100644 --- a/src/predictive_control.jl +++ b/src/predictive_control.jl @@ -1,4 +1,6 @@ @doc raw""" + abstract type PredictiveController end + Abstract supertype of all predictive controllers. --- diff --git a/src/sim_model.jl b/src/sim_model.jl index a8a76cfaa..ef70d4f9a 100644 --- a/src/sim_model.jl +++ b/src/sim_model.jl @@ -1,4 +1,6 @@ @doc raw""" + abstract type SimModel end + Abstract supertype of [`LinModel`](@ref) and [`NonLinModel`](@ref) types. --- diff --git a/src/state_estim.jl b/src/state_estim.jl index c4beb6f89..1c5943147 100644 --- a/src/state_estim.jl +++ b/src/state_estim.jl @@ -1,4 +1,6 @@ @doc raw""" + abstract type StateEstimator end + Abstract supertype of all state estimators. --- diff --git a/src/transcription.jl b/src/transcription.jl index ac82730ba..f80078830 100644 --- a/src/transcription.jl +++ b/src/transcription.jl @@ -1,6 +1,8 @@ const COLLOCATION_NODE_TYPE = Float64 """ + abstract type TranscriptionMethod end + Abstract supertype of all transcription methods for the optimization problems. The module currently supports [`SingleShooting`](@ref), [`MultipleShooting`](@ref), From 316644abe35c18fe7768e57932a32899fea220a2 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 10:20:38 -0400 Subject: [PATCH 35/40] doc: moving linear term update in misc. section --- docs/src/internals/misc.md | 8 ++++++++ docs/src/internals/predictive_control.md | 8 -------- docs/src/internals/state_estim.md | 7 ------- src/controller/transcription.jl | 2 +- src/estimator/mhe/transcription.jl | 2 +- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/docs/src/internals/misc.md b/docs/src/internals/misc.md index a1eba67e9..7a1e699e8 100644 --- a/docs/src/internals/misc.md +++ b/docs/src/internals/misc.md @@ -9,3 +9,11 @@ Pages = ["misc.md"] ```@docs ModelPredictiveControl.init_orthocolloc ``` + +## Update Optimization Linear Terms + +```@docs +ModelPredictiveControl.initpred! +ModelPredictiveControl.linconstraint! +ModelPredictiveControl.linconstrainteq! +``` diff --git a/docs/src/internals/predictive_control.md b/docs/src/internals/predictive_control.md index f5ea36706..7f843c2ff 100644 --- a/docs/src/internals/predictive_control.md +++ b/docs/src/internals/predictive_control.md @@ -30,14 +30,6 @@ ModelPredictiveControl.get_nonlinobj_op(::NonLinMPC, ::ModelPredictiveControl.Ge ModelPredictiveControl.get_nonlincon_oracle(::NonLinMPC, ::ModelPredictiveControl.GenericModel) ``` -## Update Quadratic Optimization - -```@docs -ModelPredictiveControl.initpred!(::PredictiveController, ::LinModel, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) -ModelPredictiveControl.linconstraint!(::PredictiveController, ::LinModel, ::TranscriptionMethod) -ModelPredictiveControl.linconstrainteq! -``` - ## Solve Optimization Problem ```@docs diff --git a/docs/src/internals/state_estim.md b/docs/src/internals/state_estim.md index d6269e70e..359299bfe 100644 --- a/docs/src/internals/state_estim.md +++ b/docs/src/internals/state_estim.md @@ -101,13 +101,6 @@ ModelPredictiveControl.correct_estimate! ModelPredictiveControl.update_estimate! ``` -## Update Quadratic Optimization - -```@docs -ModelPredictiveControl.initpred!(::MovingHorizonEstimator, ::LinModel) -ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel, ::TranscriptionMethod) -``` - ## Solve Optimization Problem ```@docs diff --git a/src/controller/transcription.jl b/src/controller/transcription.jl index 8e5225881..5e03f58dc 100644 --- a/src/controller/transcription.jl +++ b/src/controller/transcription.jl @@ -800,7 +800,7 @@ end deletex̂end_lincon!(i_x̂min, i_x̂max, ::SimModel, ::SingleShooting, _, _, _, _) = i_x̂min, i_x̂max @doc raw""" - linconstraint!(mpc::PredictiveController, model::LinModel) + linconstraint!(mpc::PredictiveController, model::LinModel, ::TranscriptionMethod) Set `b` vector for the linear model inequality constraints (``\mathbf{A Z̃ ≤ b}``). diff --git a/src/estimator/mhe/transcription.jl b/src/estimator/mhe/transcription.jl index 7ef21432d..baa84c38d 100644 --- a/src/estimator/mhe/transcription.jl +++ b/src/estimator/mhe/transcription.jl @@ -79,7 +79,7 @@ from ``j=N_k-1`` to ``0``, also in deviation form, are computed with: ``` The matrices ``\mathbf{E, G, J, B, E_X̂, G_X̂, J_X̂, B_X̂}`` are defined in the Extended Help section. The vectors ``\mathbf{F, F_X̂, f_x̄}`` are recalculated at each discrete time step, -see [`initpred!(::MovingHorizonEstimator, ::LinModel)`](@ref) and [`linconstraint!(::MovingHorizonEstimator, ::LinModel)`](@ref). +see [`initpred!`](@ref) and [`linconstraint!`](@ref). # Extended Help !!! details "Extended Help" From 416a544c0b63cfe91a0af52b6a8bd4f2247b7a9a Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 10:46:19 -0400 Subject: [PATCH 36/40] doc: correct method signature --- src/estimator/mhe/transcription.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/estimator/mhe/transcription.jl b/src/estimator/mhe/transcription.jl index baa84c38d..1194a2056 100644 --- a/src/estimator/mhe/transcription.jl +++ b/src/estimator/mhe/transcription.jl @@ -426,7 +426,7 @@ end @doc raw""" init_defectmat_mhe( model::LinModel, transcription::MultipleShooting, direct::Bool, - He, i_ym, Â, B̂u, Ĉm, B̂d, D̂dm, x̂op, f̂op, As, Co, λo + He, Â, B̂u, B̂d, x̂op, f̂op, As, Co, λo ) -> ES, GS, JS, BS Init the matrices for computing the defects over the predicted states. @@ -511,7 +511,7 @@ end @doc raw""" init_defectmat_mhe( model::SimModel, transcription::TranscriptionMethod, direct::Bool, - He, Â, _ , _ , _ , _ , As, _ , _ + He, Â, _ , _ , _ , _ , As, _ , _ ) -> ES, GS, JS, BS Init the matrices for computing the defects of the stochastic states only. From d6bd35847c70746db6e57346dc9c7e93461dc3c2 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 11:35:16 -0400 Subject: [PATCH 37/40] doc: using anonymous functions in `NonLinModel` as a workaround See issue https://github.com/fredrikekre/DocumenterCodeBlocks.jl/issues/28 --- src/model/nonlinmodel.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/nonlinmodel.jl b/src/model/nonlinmodel.jl index 282166199..01f3a9093 100644 --- a/src/model/nonlinmodel.jl +++ b/src/model/nonlinmodel.jl @@ -148,9 +148,9 @@ See also [`LinModel`](@ref). # Examples ```jldoctest -julia> f!(ẋ, x, u, _ , p) = (ẋ .= p*x .+ u; nothing); +julia> f! = (ẋ, x, u, _ , p) -> (ẋ .= p*x .+ u; nothing); -julia> h!(y, x, _ , _ ) = (y .= 0.1x; nothing); +julia> h! = (y, x, _ , _ ) -> (y .= 0.1x; nothing); julia> model1 = NonLinModel(f!, h!, 5.0, 1, 1, 1, p=-0.2) # continuous dynamics NonLinModel with a sample time Ts = 5.0 s: @@ -162,9 +162,9 @@ NonLinModel with a sample time Ts = 5.0 s: ├ 1 outputs y └ 0 measured disturbances d -julia> f(x, u, _ , _ ) = 0.1x + u; +julia> f = (x, u, _ , _ ) -> 0.1x + u; -julia> h(x, _ , _ ) = 2x; +julia> h = (x, _ , _ ) -> 2x; julia> model2 = NonLinModel(f, h, 2.0, 1, 1, 1, solver=nothing) # discrete dynamics NonLinModel with a sample time Ts = 2.0 s: From 2d9104f27d248db1be12018934e9dacf8bb27f87 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 11:41:00 -0400 Subject: [PATCH 38/40] doc: mistake in method signature --- Project.toml | 2 +- src/estimator/luenberger.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 32d811880..cf83496c7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ModelPredictiveControl" uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c" -version = "2.12.1" +version = "2.12.2" authors = ["Francis Gagnon"] [deps] diff --git a/src/estimator/luenberger.jl b/src/estimator/luenberger.jl index 6f3c4db36..256f0bd0f 100644 --- a/src/estimator/luenberger.jl +++ b/src/estimator/luenberger.jl @@ -116,7 +116,7 @@ end """ - correct_estimate!(estim::Luenberger, y0m, d0, _ ) + correct_estimate!(estim::Luenberger, y0m, d0) Identical to [`correct_estimate!(::SteadyKalmanFilter)`](@ref) but using [`Luenberger`](@ref). """ From b75fd02524c73111ebc9df642591bddda276be6c Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 12:26:53 -0400 Subject: [PATCH 39/40] doc: reverting anonymous function in `NonLinModel` The issue is still there anyway when calling the constructor. --- src/controller/transcription.jl | 2 +- src/estimator/mhe/execute.jl | 7 ++++++- src/estimator/mhe/transcription.jl | 4 +--- src/model/nonlinmodel.jl | 8 ++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/controller/transcription.jl b/src/controller/transcription.jl index 5e03f58dc..0bba4380c 100644 --- a/src/controller/transcription.jl +++ b/src/controller/transcription.jl @@ -802,7 +802,7 @@ deletex̂end_lincon!(i_x̂min, i_x̂max, ::SimModel, ::SingleShooting, _, _, _, @doc raw""" linconstraint!(mpc::PredictiveController, model::LinModel, ::TranscriptionMethod) -Set `b` vector for the linear model inequality constraints (``\mathbf{A Z̃ ≤ b}``). +Set `b` vector for the linear model inequality constraints (``\mathbf{A Z̃ ≤ b}``) of MPC. Also init ``\mathbf{f_x̂} = \mathbf{g_x̂ d_0}(k) + \mathbf{j_x̂ D̂_0} + \mathbf{k_x̂ x̂_0}(k) + \mathbf{v_x̂ u_0}(k-1) + \mathbf{b_x̂}`` vector for the terminal constraints, see diff --git a/src/estimator/mhe/execute.jl b/src/estimator/mhe/execute.jl index ae3a22e02..d8a052f48 100644 --- a/src/estimator/mhe/execute.jl +++ b/src/estimator/mhe/execute.jl @@ -457,7 +457,12 @@ function initpred!(estim::MovingHorizonEstimator{NT}, model::LinModel) where NT< JuMP.set_objective_function(optim, obj_quadprog(Z̃var, H̃, q̃)) return nothing end -"Does nothing if `model` is not a [`LinModel`](@ref)." + +""" + initpred!(estim::MovingHorizonEstimator, LinModel::SimModel) -> nothing + +Does nothing if `model` is not a [`LinModel`](@ref). +""" initpred!(::MovingHorizonEstimator, ::SimModel) = nothing "Get the estimated state at arrival from the decision vector `Z̃`." diff --git a/src/estimator/mhe/transcription.jl b/src/estimator/mhe/transcription.jl index 1194a2056..d2fbfe46a 100644 --- a/src/estimator/mhe/transcription.jl +++ b/src/estimator/mhe/transcription.jl @@ -816,9 +816,7 @@ function deleteŴ_lincon!(i_Ŵmin, i_Ŵmax, ::SimModel, ::TranscriptionMethod end @doc raw""" - linconstraint!( - estim::MovingHorizonEstimator, model::LinModel, transcription::TranscriptionMethod - ) + linconstraint!(estim::MovingHorizonEstimator, model::LinModel, ::TranscriptionMethod) Set `b` vector for the linear inequality constraints (``\mathbf{A Z̃ ≤ b}``) of MHE. diff --git a/src/model/nonlinmodel.jl b/src/model/nonlinmodel.jl index 01f3a9093..282166199 100644 --- a/src/model/nonlinmodel.jl +++ b/src/model/nonlinmodel.jl @@ -148,9 +148,9 @@ See also [`LinModel`](@ref). # Examples ```jldoctest -julia> f! = (ẋ, x, u, _ , p) -> (ẋ .= p*x .+ u; nothing); +julia> f!(ẋ, x, u, _ , p) = (ẋ .= p*x .+ u; nothing); -julia> h! = (y, x, _ , _ ) -> (y .= 0.1x; nothing); +julia> h!(y, x, _ , _ ) = (y .= 0.1x; nothing); julia> model1 = NonLinModel(f!, h!, 5.0, 1, 1, 1, p=-0.2) # continuous dynamics NonLinModel with a sample time Ts = 5.0 s: @@ -162,9 +162,9 @@ NonLinModel with a sample time Ts = 5.0 s: ├ 1 outputs y └ 0 measured disturbances d -julia> f = (x, u, _ , _ ) -> 0.1x + u; +julia> f(x, u, _ , _ ) = 0.1x + u; -julia> h = (x, _ , _ ) -> 2x; +julia> h(x, _ , _ ) = 2x; julia> model2 = NonLinModel(f, h, 2.0, 1, 1, 1, solver=nothing) # discrete dynamics NonLinModel with a sample time Ts = 2.0 s: From 5e83b0da1eaa34b362d78f0cc0faecace34a33e5 Mon Sep 17 00:00:00 2001 From: franckgaga Date: Thu, 27 Aug 2026 12:30:18 -0400 Subject: [PATCH 40/40] doc: more precise header The hessian is always updated in the MHE. --- docs/src/internals/misc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/internals/misc.md b/docs/src/internals/misc.md index 7a1e699e8..9dbd5b617 100644 --- a/docs/src/internals/misc.md +++ b/docs/src/internals/misc.md @@ -10,7 +10,7 @@ Pages = ["misc.md"] ModelPredictiveControl.init_orthocolloc ``` -## Update Optimization Linear Terms +## Update Linear and Quadratic Terms ```@docs ModelPredictiveControl.initpred!