Add a disturbance-control cross term Sd to the objective - #114
Open
baggepinnen wants to merge 5 commits into
Open
baggepinnen wants to merge 5 commits into
baggepinnen wants to merge 5 commits into
Conversation
set_objective!(mpc; Sd) adds ∑ₖ dₖ' Sd uₖ for the measurable disturbance d. With Sd = R the control penalty becomes (u + d)' R (u + d) up to a constant, so a direct penalty on u no longer causes a steady-state error when a persistent disturbance entering through Bd = B has to be rejected. Without disturbance preview d is part of the extended state, so Sd extends the state-control cross term S; with preview the dₖ are parameters and the term enters the linear part of the objective. evaluate_cost accepts the disturbance trajectory and Sd.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #114 +/- ##
==========================================
+ Coverage 89.57% 89.66% +0.09%
==========================================
Files 15 15
Lines 2389 2410 +21
==========================================
+ Hits 2140 2161 +21
Misses 249 249 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Julia 1.10's three-arg dot infers its accumulator type via first(x) before checking for empty input, so dot(ds[:,i],Sd,us[:,i]) threw a BoundsError for models with nd == 0. Fixed in Julia 1.11, hence the CI failure only on 1.10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With disturbance_preview the predicted state is x = Phi x0 + Gamma U + Psi D, but the x'S u condensation only added Stot'*Phi, so the S cross term silently ignored the previewed disturbance (without preview the disturbance is folded into the extended state and Phi carries it). With a completed-square cost (Q = L'L, S = L', R) the unconstrained optimum must be u = -L x for any disturbance trajectory; it deviated whenever the preview was nonzero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LNTSoj8uSmNpSqpRTor8di
…upling disturbance_preview_cost built the disturbance columns of the linear cost from the unfiltered Q_full/Qf_full, while the main condensation applies the positivity filter (non-positive diagonal weights mean an unweighted output) and the Qfx terminal. A Qf dropped entirely by the filter (a zero terminal cost) thus entered the preview coupling at full strength. The state part of the coupling now reuses CQCtot from the main condensation; the Dd feedthrough terms keep their output-level form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LNTSoj8uSmNpSqpRTor8di
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
With reference tracking and a direct penalty
u' R u, a persistent disturbance that has to be rejected causes a steady-state error: the required steady-state input is nonzero, so the optimizer trades tracking error against theupenalty and settles at an offset (mpc2mpqpalready warns about this for a nonzero operating point). This happens even when the disturbance is measured and enters the prediction exactly throughBd, and switching toRr(a penalty onΔu) is not always what one wants.The standard fix from LQ theory is a cross term between the disturbance and the control (see e.g. the
SQterm in the RobustAndOptimalControl.jl LQG disturbance example): withSd = Rand a disturbance entering like the input,Bd = B,so the control is penalized relative to the input that cancels the disturbance instead of relative to zero, and the offset disappears while
Rstays onu.Changes
MPCWeightsgets a fieldSd(nd × nu, empty = zero), settable throughset_objective!(mpc; Sd)for both the single- and multi-objective methods. A vector is interpreted as a diagonal, like the other weights.dis part of the extended state, soSdreplaces the zero padding of the disturbance rows ofSincreate_extended_cost;dₖare QP parameters, so the term entersf_thetaon the disturbance-preview columns (disturbance_preview_cross_term), honouringuₖ = u_{Nc−1}fork ≥ Nc.Sdis validated against the model's currentnd(which includes channels added by an offset-free observer) when the QP is formed, with an informativeArgumentError.Sdthrows (not supported; the no-preview case works throughcreate_extended_cost).evaluate_costaccepts the disturbance trajectorydsandSd.objective.md) and theset_objective!docstring describe the term.Tests
New testset "Disturbance-control cross term Sd" (double integrator,
Bd = B):1e-8) of theSd = Rproblem with the disturbance-free problem inũ = u + dfor{no preview, preview} × {Nc = Np, Nc < Np}, including a time-varying preview trajectory;3e-2withoutSd,< 1e-9withSd = R, both preview modes;evaluate_cost.The full suite passes locally on Julia 1.12.7.
Verified downstream as well: in a Dyad/ModelingToolkit closed loop (JuliaComputing/DiscreteComponents
LinearMPCController, reference + disturbance preview,Np = 20) the same controller goes from a−0.025offset to1e-10withSd = R.🤖 Generated with Claude Code
https://claude.ai/code/session_01LNTSoj8uSmNpSqpRTor8di