Problem implementation#
Regardless of what kind of problem statement is considered (i.e. model predictive control, moving horizon estimation or parameter estimation), the optimization problem (1) must be implemented in C, cf. Fig. 1
For this purpose, the C file template probfct_TEMPLATE.c
is provided within the folder <grampc_root>/examples/TEMPLATE
, which allows one to describe the structure of the optimization problem (1), the cost functional \(J\) to be minimized, the system dynamics \(f\), and the constraints \(\mb{g}, \mb{g}_\text{T}, \mb{h}, \mb{h}_\text{T}\).
The number of C functions to be provided depends on the type of dynamics \(f\) of the specific problem at hand.
Problems involving explicit ODEs#
A special case of the system dynamics \(f\) and certainly the most important one concerns ordinary differential equations (ODEs) with explicit appearance of the first-order derivatives
corresponding to the identity mass matrix \(\mb{M}=\mb{I}\) in OCP (1).
In this case, the following functions of the C template file probfct_TEMPLATE.c
have to be provided:
ocp_dim
: Definition of the dimensions of the considered problem, i.e. the number of state variables \({N_{\mb{x}}}\), control variables \({N_{\mb{u}}}\), parameters \({N_{\mb{p}}}\), equality constraints \({N_{\mb{g}}}\), inequality constraints \({N_{\mb{h}}}\), terminal equality constraints \({N_{{\mb{g}}_T}}\), and terminal inequality constraints \(N_{{\mb{h}}_T}\).ffct
: Formulation of the system dynamics function \(\mb{f}\).dfdx_vec
,dfdu_vec
,dfdp_vec
: Matrix vector products \((\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\), \((\frac{\partial^{} \mb{f}}{\partial \mb{u}^{}})^\mathsf{T}\mb{v}\) and \((\frac{\partial^{} \mb{f}}{\partial \mb{p}^{}})^\mathsf{T}\mb{v}\) for the system dynamics with an arbitrary vector \(\mb{v}\) of dimension \(N_x\).Vfct
,lfct
: Terminal and integral cost functions \(V\) and \(l\) of the cost functional \(J\).dVdx
,dVdp
,dVdT
,dldx
,dldu
,dldp
: Gradients \(\frac{\partial^{} V}{\partial \mb{x}^{}}\), \(\frac{\partial^{} V}{\partial \mb{p}^{}}\), \(\frac{\partial^{} V}{\partial T^{}}\), \(\frac{\partial^{} l}{\partial \mb{x}^{}}\), \(\frac{\partial^{} l}{\partial \mb{u}^{}}\), and \(\frac{\partial^{} l}{\partial \mb{p}^{}}\) of the cost functionsVfct
andlfct
.hfct
,gfct
: Inequality and equality constraint functions \(\mb{h}\) and \(\mb{g}\) as defined in (1).hTfct
,gTfct
: Terminal inequality and equality constraint functions \(\mb{h}_T\) and \(\mb{g}_T\) as defined in (1).dhdx_vec
,dhdu_vec
,dhdp_vec
: Matrix products \((\frac{\partial^{} \mb{h}}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\), \((\frac{\partial^{} \mb{h}}{\partial \mb{u}^{}})^\mathsf{T}\mb{v}\), and \((\frac{\partial^{} \mb{h}}{\partial \mb{p}^{}})^\mathsf{T}\mb{v}\) for the inequality constraints with an arbitrary vector \(\mb{v}\) of dimension \(N_h\).dgdx_vec
,dgdu_vec
,dgdp_vec
: Matrix product functions \((\frac{\partial^{} \mb{g}}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\), \((\frac{\partial^{} \mb{g}}{\partial \mb{u}^{}})^\mathsf{T}\mb{v}\), and \((\frac{\partial^{} \mb{g}}{\partial \mb{p}^{}})^\mathsf{T}\mb{v}\) for the equality constraints with an arbitrary vector \(\mb{v}\) of dimension \(N_g\).dhTdx_vec
,dhTdu_vec
,dhTdp_vec
: Matrix product functions \((\frac{\partial^{} \mb{h}_T}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\), \((\frac{\partial^{} \mb{h}_T}{\partial \mb{p}^{}})^\mathsf{T}\mb{v}\), and \((\frac{\partial^{} \mb{h}_T}{\partial T^{}})^\mathsf{T}\mb{v}\) for the terminal inequality constraints with an arbitrary vector \(\mb{v}\) of dimension \(N_{h_T}\).dgTdx_vec
,dgTdu_vec
,dgTdp_vec
: Matrix product functions \((\frac{\partial^{} \mb{g}_T}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\), \((\frac{\partial^{} \mb{g}_T}{\partial \mb{p}^{}})^\mathsf{T}\mb{v}\), and \((\frac{\partial^{} \mb{g}_T}{\partial T^{}})^\mathsf{T}\mb{v}\) for the terminal equality constraints with an arbitrary vector \(\mb{v}\) of dimension \(N_{g_T}\).
The respective problem function templates only have to be filled in if the corresponding constraints and cost functions are defined for the problem at hand and depending on the actual choice of optimization variables (\({\mb{u}}\), \({\mb{p}}\), and/or \({T}\)). For example, if only the control \(\mb{u}\) is optimized, the partial derivatives with respect to \(\mb{p}\) and \(T\) are not required.
The gradients \(\frac{\partial^{} V}{\partial \mb{x}^{}}\), \(\frac{\partial^{} V}{\partial \mb{p}^{}}\), \(\frac{\partial^{} V}{\partial T^{}}\), \(\frac{\partial^{} l}{\partial \mb{x}^{}}\), \(\frac{\partial^{} l}{\partial \mb{u}^{}}\), and \(\frac{\partial^{} l}{\partial \mb{p}^{}}\) as well as the matrix product functions listed above appear in the partial derivatives \(H_{\mb{x}}\), \(H_{\mb{u}}\) and \(H_{\mb{p}}\) of the Hamiltonian \(H\) within the gradient method (see Projected gradient method). The matrix product formulation is chosen over the definition of Jacobians (e.g. \((\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}})^\mathsf{T}\mb{v}\) instead of \(\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}}\)) in order to avoid unnecessary zero multiplications for sparse matrices or alternatively the usage of sparse numerics.
Problems with discrete-time systems#
Added in version v2.3.
GRAMPC also allows to consider discrete-time system dynamics of the form
with \(\mb{x}_k = \mb x(t_k)\) and \(\mb{u}_k = \mb u(t_k)\).
For this case the same functions as in Problems involving explicit ODEs are used to implement the problem description but the option Integrator
is set to discrete
.
Since the discrete ffct
computes the system dynamics for a fixed sampling time \(\Delta t\) (Parameter dt
), the end time \(T\) (Parameter Thor
) of the optimization problem must satisfy
with the number of discretization points along the horizon \(N_{\text{hor}}\) (Option Nhor
).
As a consequence it is no longer possible to consider a free end time, where \(T\) is an optimization variable, since this would change the sampling time \(\Delta t\).
Note that for continuous-time system dynamics the sampling time \(\Delta t\) can be chosen independently of the end time \(T\) and the number of discretizaton points \(N_{\text{hor}}\), because it is only used for predicting the next state grampc.sol.xnext
and for the control shift.
Note
For discrete-time systems the option Integrator
is set to discrete
, the settings Nhor
, Thor
and dt
need to satisfy the relation Thor = (Nhor-1)*dt
and the option OptimTime
must be set to off
.
An MPC example that compares continuous-time and discrete-time versions of a double integrator is included in <grampc_root>/examples/Continuous_vs_Discrete
.
Furthermore, a discrete-time formulation of the helicopter example is provided in <grampc_root>/examples/Continuous_vs_Discrete
.
Problems involving semi-implicit ODEs and DAEs#
Beside explicit ODEs, GRAMPC supports semi-implicit ODEs with mass
matrix \(\mb{M} \neq \mb{I}\) and DAEs with
\(\mb{M}\) being singular. The underlying numerical
integrations of the dynamics \(f\) is
carried out using the integrator
RODAS [1][2]. In
this case, additional C functions must be provided and several
RODAS-specific options must be set, cf. Integration of semi-implicit ODEs and DAEs (RODAS) and Setting options and parameters.
Especially, the option
IMAS = 1
must be set to indicate that a mass matrix is given. The
numerical integrations performed with RODAS can be accelerated by
providing partial derivatives. In summary, the following additional C
functions are used by GRAMPC for semi-implicit ODEs and DAE systems:
Mfct
,Mtrans
: Definition of the mass matrix \(\mb{M}\) and its transpose \(\mb{M}^\mathsf{T}\), which is required for the adjoint dynamics, cf. Projected gradient method in the projected gradient algorithm. The matrices must be specified column-wise. If the mass matrix has a band structure, only the respective elements above and below the main diagonal are specified. This only applies if the optionsIMAS = 1
andMLJAC < N_x
are selected. Non-existent elements above or below the main diagonal must be filled with zeros so that the same number of elements is specified for each column.dfdx
,dfdxtrans
: The Jacobians \(\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}}\) and \((\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}})^\mathsf{T}=\frac{\partial^{2} H}{\partial \mb{x}\partial \mb{\lambda}}\) are provided by these functions if the optionIJAC = 1
is set. This allows one to evaluate the right hand sides of the canonical equations time efficiently. The Jacobians must be implemented in vector form by arranging the successive columns for \(\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}}\) and \((\frac{\partial^{} \mb{f}}{\partial \mb{x}^{}})^\mathsf{T}\). If the optionMLJAC < N_x
is set to exploit the band structure of the Jacobians, only the corresponding elements above and below the main diagonal must be specified.dfdt
,dHdxdt
: The partial derivatives \(\frac{\partial^{} \mb{f}}{\partial t^{}}\) and \(\frac{\partial^{2} H}{\partial \mb{x}\partial t}\) allow for evaluating the right hand sides of the canonical equations time efficiently, if the problem explicitly depends on time \(t\). These functions must only be provided if the optionsIFCN = 1
andIDFX = 1
are used.
An MPC example with a semi-implicit system dynamics is included in <grampc_root>/examples/Reactor_PDE
.
The problem formulation is derived from a quasi-linear
diffusion-convection-reaction system that is spatially discretized using
finite elements.
Finite differences and gradient check#
Added in version v2.3.
For the purpose of rapid prototyping, the partial derivatives can also be approximated by forward finite differences.
To this end, the header finite_diff.h
provides a helper function
void finite_diff_vec(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p, ctypeRNum *vec, const typeGRAMPCparam *param, typeUSERPARAM *userparam,
typeRNum *memory, ctypeRNum step_size, const typeFiniteTarget target, ctypeInt func_out_size, const typeFiniteDiffFctPtr func);
where the first eight arguments are the same as for typical functions in probfct.h
and the other arguments are:
memory
: Sufficient user-provided memory of size at most \(3 \cdot \max\{ N_x, N_u, N_p, N_g, N_h, N_{g_T}, N_{h_T} \}\) that is needed for storing intermediate values. If used within a probfct, this memory should be provided via theuserparam
pointer to avoid dynamic memory allocation during the optimization.step_size
: Small value used as step size for the finite differences.target
: Argument with respect to which the finite differences are computed. Options areDX
for states,DU
for inputs,DP
for parameters andDT
for time.func_out_size
: Size of the function’sout
argument, e.g.Nh
forhfct
.func
: Pointer to the function that is differentiated. ForVfct
,gTfct
andhTfct
wrappers are provided with an unused dummy argument in place of the controlu
.
The intended usage is shown in the template file probfct_TEMPLATE_finite_diff.c
.
In addition, the helper function
void finite_diff_dfdx(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p, const typeGRAMPCparam *param, typeUSERPARAM *userparam,
typeRNum *memory, ctypeRNum step_size, ctypeInt ml, ctypeInt mu, typeBoolean transpose);
approximates the partial derivatives of the ffct
for semi-implicit ODEs and DAEs with the arguments:
memory
: Sufficient allocated memory for storing intermediate values of size \(3 \cdot N_x\). If used within a probfct, this memory should be provided via theuserparam
pointer to avoid dynamic memory allocation during the optimization.step_size
: Small value used as step size for the finite differences.ml
: Number of lower non-zero diagonals if banded. Set toNx
for full matrix.mu
: Number of upper non-zero diagonals if banded. Set toNx
for full matrix.transpose
: Set to0
for approximatingdfdx
and to1
for approximatingdfdxtrans
.
Finally, it is recommended to validate the user-supplied analytic gradients by comparison to finite differences. For this task, GRAMPC provides a helper function
void grampc_check_gradients(typeGRAMPC *grampc, ctypeRNum tolerance, ctypeRNum step_size);
that checks the gradients at the initial point defined by t0
, x0
, u0
and p0
.
The tolerance is applied element-wise to the relative error
where \(\nabla \mb{f}_{fd,i}\) is the finite difference approximation and \(\nabla \mb{f}_i\) the user-supplied derivative. If the tolerance is violated, the function prints messages like
dfdx_vec: element (out_index,vec_index) exceeds supplied tolerance with 4.234546e-5
so that one is able to correct the wrong derivative. Recommended settings for the gradient check include a step size of \(\sqrt{\text{eps}}\) where eps is the floating point machine precision, and a tolerance of 1e-6. Nevertheless, the gradient checker can produce false positives, so every flagged gradient should be carefully checked.
Example: Ball-on-plate system#
The appropriate definition of the C functions of the template
probfct_TEMPLATE
is described for the example of a ball-on-plate
system [3] in the context of MPC.
The problem is also included in <grampc_root>/examples(BallOnPlate)
.
The underlying optimization problem reads as
The cost functional in (2) penalizes the state and input error \(\Delta \mb{x}=\mb{x}-\mb{x}_\text{des}\) and \(\Delta u=u-u_\text{des}\) in a quadratic manner using the weights
The system dynamics in (2) describes a simplified linear model of a single axis of a ball-on-plate system [3]. An optimal solution of the optimization problem has to satisfy the input and state constraints present in (2).
The user must provide the C functions and to describe the general
structure and the system dynamics of the optimization problem, also see Problem implementation.
As shown in Listing 1, the C function ocp_dim
is used to
define the number of states, control inputs, and number of (terminal)
inequality and equality constraints. Note that GRAMPC uses the generic
type typeInt
for integer values. The word size of this integer type can be
changed in the header file grampc_macro.h
within the folder <grampc_root>/include
.
This is particularly advantageous with regard to implementing GRAMPC on embedded hardware.
/** OCP dimensions **/
void ocp_dim(typeInt *Nx, typeInt *Nu, typeInt *Np, typeInt *Ng, typeInt *Nh,
typeInt *NgT, typeInt *NhT, typeUSERPARAM *userparam)
{
*Nx = 2;
*Nu = 1;
*Np = 0;
*Nh = 4;
*Ng = 0;
*NhT = 0;
*NgT = 0;
}
The system dynamics are described by the C function ffct
shown in
Listing 2. The example is given in explicit
ODE form, for which the functions Mfct
and Mtrans
for the mass matrix \(M\) are
not required. Similar to the generic integer type typeInt
, the data type typeRNum
is
used to adress floating point numbers of different word sizes, e.g.
float or double (cf. the header file grampc_macro.h
included in the folder <grampc_root>/include
).
/** System function f(t,x,u,p,grampc.param,userparam) **/
void ffct(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u,
ctypeRNum *p, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
out[0] = x[1]-0.04*u[0];
out[1] = -7.01*u[0];
}
The cost functions are defined via the functions lfct
and Vfct
, cf. Listing 3.
Note that the input argument userparam
is used to parametrize the cost functional in a generic way.
/** Integral cost l(t,x(t),u(t),p,grampc.param,userparam) **/
void lfct(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u,
ctypeRNum *p, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pCost = (ctypeRNum*)userparam;
ctypeRNum* xdes = param->xdes;
ctypeRNum* udes = param->udes;
out[0] = (pCost[0] * (x[0] - xdes[0]) * (x[0] - xdes[0])
+ pCost[1] * (x[1] - xdes[1]) * (x[1] - xdes[1])
+ pCost[2] * (u[0] - udes[0]) * (u[0] - udes[0])) / 2;
}
/** Terminal cost V(T,x(T),p,grampc.param,userparam) **/
void Vfct(typeRNum *out, ctypeRNum T, ctypeRNum *x, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pCost = (ctypeRNum*)userparam;
ctypeRNum* xdes = param->xdes;
out[0] = (pCost[3] * (x[0] - xdes[0]) * (x[0] - xdes[0])
+ pCost[4] * (x[1] - xdes[1]) * (x[1] - xdes[1])) / 2;
}
Listing 4 shows the formulation
of the inequality constraints
\(\mb{h}(\mb{x}(t), \mb{u}(t), \mb{p}, t) \le \mb{0}\).
For the sake of completeness,
Listing 4 also contains the
corresponding functions for equality constraints
\(\mb{g}(\mb{x}(t), \mb{u}(t), \mb{p}, t) = \mb{0}\),
terminal inequality constraints
\(\mb{h}_T(\mb{x}(T), \mb{p}, T) \le \mb{0}\)
as well as terminal equality constraints
\(\mb{g}_T(\mb{x}(T), \mb{p}, T) = \mb{0}\),
which are not defined for the ball-on-plate example. Similar to the
formulation of the cost functional, the input argument userparam
is used to
parametrize the inequality constraints.
/** Inequality constraints h(t,x(t),u(t),p,grampc.param,userparam) <= 0 **/
void hfct(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u,
ctypeRNum *p, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pSys = (ctypeRNum*)userparam;
out[0] = pSys[5] - x[0];
out[1] = -pSys[6] + x[0];
out[2] = pSys[7] - x[1];
out[3] = -pSys[8] + x[1];
}
/** Equality constraints g(t,x(t),u(t),p,grampc.param,userparam) = 0 **/
void gfct(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u,
ctypeRNum *p, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
}
/** Terminal inequality constraints hT(T,x(T),p,grampc.param,userparam) <= 0 **/
void hTfct(typeRNum *out, ctypeRNum T, ctypeRNum *x, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
}
/** Terminal equality constraints gT(T,x(T),p,grampc.param,userparam) = 0 **/
void gTfct(typeRNum *out, ctypeRNum T, ctypeRNum *x, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
}
The Jacobians of the single functions defined in
Listing 2 to Listing 4
with respect to state \(\mb{x}\) and control
\(\mb{u}\) are required for evaluating the optimality
conditions of optimization problem
(2) within the gradient
algorithm, see Projected gradient method. If applicable,
the Jacobians of the above-mentioned functions are also required with
respect to the optimization variables \(\mb{p}\) and
\(T\). Listing 5 shows the
corresponding Jacobians for the ball-on-plate example. For the matrix
product functions dfdx_vec
, dfdu_vec
, and dhdx_vec
, the pointer to a generic vector vec
is
passed as input argument that corresponds to the adjoint state,
respectively a vector that accounts for the Lagrange multiplier and
penalty term of state constraints,
cf. Optimization algorithm. Note that vec
is of
appropriate dimension for the respective matrix product function,
i.e. of dimension \(N_{\mb{x}}\) or
\(N_{\mb{h}}\). A complete C function template and further
examples concerning the problem formulation are included in the GRAMPC
software package.
/** Jacobian df/dx multiplied by vector vec, i.e. (df/dx)^T*vec or vec^T*(df/dx) **/
void dfdx_vec(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p,
ctypeRNum *vec, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
out[0] = 0;
out[1] = vec[0];
}
/** Jacobian df/du multiplied by vector vec, i.e. (df/du)^T*vec or vec^T*(df/du) **/
void dfdu_vec(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p,
ctypeRNum *vec, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
out[0] = (typeRNum)(-0.04)*vec[0] - (typeRNum)(7.01)*vec[1];
}
/** Gradient dl/dx **/
void dldx(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pCost = (ctypeRNum*)userparam;
ctypeRNum* xdes = param->xdes;
out[0] = pCost[0] * (x[0] - xdes[0]);
out[1] = pCost[1] * (x[1] - xdes[1]);
}
/** Gradient dl/du **/
void dldu(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pCost = (ctypeRNum*)userparam;
ctypeRNum* udes = param->udes;
out[0] = pCost[2] * (u[0] - udes[0]);
}
/** Gradient dV/dx **/
void dVdx(typeRNum *out, ctypeRNum T, ctypeRNum *x, ctypeRNum *p,
const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
ctypeRNum* pCost = (ctypeRNum*)userparam;
ctypeRNum* xdes = param->xdes;
out[0] = pCost[3] * (x[0] - xdes[0]);
out[1] = pCost[4] * (x[1] - xdes[1]);
}
/** Jacobian dh/dx multiplied by vector vec, i.e. (dh/dx)^T*vec or vec^T*(dg/dx) **/
void dhdx_vec(typeRNum *out, ctypeRNum t, ctypeRNum *x, ctypeRNum *u, ctypeRNum *p,
ctypeRNum *vec, const typeGRAMPCparam *param, typeUSERPARAM *userparam)
{
out[0] = -vec[0] + vec[1];
out[1] = -vec[2] + vec[3];
}
...