package
Control.Mpc
Online receding-horizon optimal control as a plain Modelica library
Model-predictive control defined without any language extension. The
optimal control problem is a record (OcpSpec), the
transcription of that problem into an online nonlinear least-squares
step is a replaceable block (PartialTranscription,
implemented first by MultipleShooting), the plant
prediction model is a function interface
(PartialDynamics), and the per-iteration step computation
is a replaceable function (PartialNlpSolver, implemented
first by gaussNewtonSqpStep, which needs only first-order
information and a dense Cholesky solve via
LinearAlgebra.solveSPD). Selecting a transcription is one
redeclare line in the consuming model, the same pattern as
Estimation.StrapdownINS.PartialEstimator.
Execution is online and clocked: the transcription re-solves inside
a when sample(0.0, ocp.samplePeriod) clause each control
tick as a real-time iteration, warm-started by shifting the previous
solution, and applies the first control of the horizon. The whole solve
is discrete so an MPC controller composes with sampled estimators and
plants and remains eligible for embedded code generation.
Scope of this first milestone (M-A) and deliberate compromises, each forced by a current compiler limitation unless noted:
- Costs are quadratic tracking costs with diagonal running, input,
and terminal weights (design choice); the weights are the diagonals
of Q, R, and the terminal Q and must be nonnegative (negative entries
are clamped to zero before the square root). The weight parameters
live on
PartialTranscriptioninstead of insideOcpSpecbecause record components cannot yet be sized by sibling record members. - The problem is unconstrained. Input and state constraint handling is the M-B milestone (a constrained QP inner solve); it is not emulated here by penalties or clipping (design choice).
- The compiler silently ignores
redeclareof functions and packages: the constraint is checked, then every call resolves to the declared default. The prediction model therefore cannot be a replaceable function inside the transcription yet; it enters through the linearization ports ofPartialTranscription, fed by one consumer-side equation calling a per-application finite-difference linearization function (seeTest.doubleIntegratorLinearization).NlpSolverkeeps the intended replaceable-function form; the solver that executes under this compiler is always the declared default. - One linearize-condense-step iteration per tick (the classical RTI scheme). In-tick multi-iteration SQP needs fresh model evaluations at the stepped iterate, which requires the replaceable prediction function above.
- The multiple-shooting normal equations are formed by dense
condensing, so each Gauss-Newton step solves a dense
N*nusystem. Structure-exploiting banded or Riccati factorizations are the M-C milestone (design choice). - All per-tick work happens inside one Modelica function call because
the compiler rejects general for-loops inside
whenclauses (event tensor loops must cover every target axis exactly once). - Inside functions, nested loops survive only as reductions: pure elementwise-copy inner loops are miscompiled (an internal compaction error), loops inside conditional branches are rejected, and every variable a loop reads needs a definition before the loop. The solver therefore uses whole-array matrix products for the condensation recurrences, assembles by accumulation into zeroed arrays, and applies a rejected step with scale zero instead of branching (mathematically identical in all three cases).
- Redeclared functions must nominally extend their partial interface
(structural signature equality is not accepted), so every
implementation extends its
Partial*function.
Members
| Name | Kind | Description |
|---|---|---|
| OcpSpec | record | Fixed-horizon quadratic tracking optimal control problem |
| PartialDynamics | function | Interface for the discrete-time prediction model x+ = f(x, u) |
| PartialNlpSolver | function | Interface for the per-iteration step solver of the transcribed problem |
| gaussNewtonSqpStep | function | Damped Gauss-Newton step from the dense normal equations via Cholesky |
| PartialTranscription | block | Common replaceable interface for online receding-horizon transcriptions |
| MultipleShooting | block | Condensed multiple-shooting transcription, one warm-started step per tick |
| Test | package | Mpc library test models |
Modelica declarations and implementation
Show parameters, interfaces and source
Parameters, inputs, outputs, defaults and equations from the source file.
package Mpc "Online receding-horizon optimal control as a plain Modelica library"
annotation(Documentation(info="<html>
<p>Model-predictive control defined without any language extension. The
optimal control problem is a record (<code>OcpSpec</code>), the
transcription of that problem into an online nonlinear least-squares
step is a replaceable block (<code>PartialTranscription</code>,
implemented first by <code>MultipleShooting</code>), the plant
prediction model is a function interface
(<code>PartialDynamics</code>), and the per-iteration step computation
is a replaceable function (<code>PartialNlpSolver</code>, implemented
first by <code>gaussNewtonSqpStep</code>, which needs only first-order
information and a dense Cholesky solve via
<code>LinearAlgebra.solveSPD</code>). Selecting a transcription is one
<code>redeclare</code> line in the consuming model, the same pattern as
<code>Estimation.StrapdownINS.PartialEstimator</code>.</p>
<p>Execution is online and clocked: the transcription re-solves inside
a <code>when sample(0.0, ocp.samplePeriod)</code> clause each control
tick as a real-time iteration, warm-started by shifting the previous
solution, and applies the first control of the horizon. The whole solve
is discrete so an MPC controller composes with sampled estimators and
plants and remains eligible for embedded code generation.</p>
<p>Scope of this first milestone (M-A) and deliberate compromises,
each forced by a current compiler limitation unless noted:</p>
<ul>
<li>Costs are quadratic tracking costs with diagonal running, input,
and terminal weights (design choice); the weights are the diagonals
of Q, R, and the terminal Q and must be nonnegative (negative entries
are clamped to zero before the square root). The weight parameters
live on <code>PartialTranscription</code> instead of inside
<code>OcpSpec</code> because record components cannot yet be sized by
sibling record members.</li>
<li>The problem is unconstrained. Input and state constraint handling
is the M-B milestone (a constrained QP inner solve); it is not
emulated here by penalties or clipping (design choice).</li>
<li>The compiler silently ignores <code>redeclare</code> of functions
and packages: the constraint is checked, then every call resolves to
the declared default. The prediction model therefore cannot be a
replaceable function inside the transcription yet; it enters through
the linearization ports of <code>PartialTranscription</code>, fed by
one consumer-side equation calling a per-application
finite-difference linearization function (see
<code>Test.doubleIntegratorLinearization</code>).
<code>NlpSolver</code> keeps the intended replaceable-function form;
the solver that executes under this compiler is always the declared
default.</li>
<li>One linearize-condense-step iteration per tick (the classical RTI
scheme). In-tick multi-iteration SQP needs fresh model evaluations at
the stepped iterate, which requires the replaceable prediction
function above.</li>
<li>The multiple-shooting normal equations are formed by dense
condensing, so each Gauss-Newton step solves a dense
<code>N*nu</code> system. Structure-exploiting banded or Riccati
factorizations are the M-C milestone (design choice).</li>
<li>All per-tick work happens inside one Modelica function call because
the compiler rejects general for-loops inside <code>when</code>
clauses (event tensor loops must cover every target axis exactly
once).</li>
<li>Inside functions, nested loops survive only as reductions: pure
elementwise-copy inner loops are miscompiled (an internal
compaction error), loops inside conditional branches are rejected,
and every variable a loop reads needs a definition before the loop.
The solver therefore uses whole-array matrix products for the
condensation recurrences, assembles by accumulation into zeroed
arrays, and applies a rejected step with scale zero instead of
branching (mathematically identical in all three cases).</li>
<li>Redeclared functions must nominally extend their partial interface
(structural signature equality is not accepted), so every
implementation extends its <code>Partial*</code> function.</li>
</ul>
</html>"));
end Mpc;