12. Parametric problems¶
Parameters (or real-time data) are a key concept in FORCESPRO. Usually at least one vector in an embedded optimization problem will change between two calls to the solver. In MPC, the initial state changes usually between two sampling times. But other data can change too, for example because you are working with linearizations of nonlinear dynamics, or because the cost matrices of a quadratic objective function are tuned online. The following API is available when using the low-level interface only and cannot be used with the high-level interface.
12.1. Defining parameters¶
FORCESPRO gives you full control over the parametrization of the optimization problem: You can define all data matrices and vectors to be parametric. To define a parameter in MATLAB, use the function
parameter = newParam(name, maps2stage, maps2data);
and in Python, use
stages.newParam(name, maps2stage, maps2data)
where name
is the parameter name, which you need to be set before calling the solver. The vector of indices maps2stage
defines
to which stages the parameters maps. The last argument maps2data
has to be one of the following strings
Cost function |
Equality constraints |
Inequality constraints |
---|---|---|
|
|
|
|
|
|
|
|
|
|
||
|
||
|
||
|
From FORCESPRO 1.8.0
, the user is allowed to provide a parameter for all problem stages at once.
All stage parameters are then stacked into one vector or matrix before getting passed to the solver at runtime. FORCESPRO is notified
about this by having
maps2stage = [];
For instance, in order to provide a parametric linear cost across all stages, one should use the following code at codegen.
parameter = newParam('linear_stage_cost', [], 'cost.f');
At runtime, the user is expected to provide the linear stage cost as follows.
problem.linear_stage_cost = repmat(rand(problem.nvar, 1), problem.horzLength, 1);
where problem.horzLength
is the horizon length and problem.nvar
is the number of stage variables.
Note
The stacked parameters feature is only available in MATLAB from FORCESPRO ‘1.8.0’.
12.2. Example¶
To define the linear term of the cost of stages \(1\) to \(5\) as a parameter, use the following command in MATLAB
parameter1 = newParam('linear_cost', 1:5, 'cost.f');
and in Python, use
stages.newParam('linear_cost', range(1, 6), 'cost.f')
Note that this will generate only one parameter and the same runtime data will be mapped to stages \(1\) to \(5\). If the runtime data should be different for each stage one would have to generate five different ones in this case.
We can also have a second parameter. For instance, the right-hand side of the first equality constraints, which is a very common case in MPC. In MATLAB
parameter2 = newParam('RHS_first_equality_constraint', 1, 'eq.c');
In Python
stages.newParam('RHS_first_equality_constraint', [1], 'eq.c')
12.3. Parametric Quadratic Constraints¶
As there may be multiple quadratic constraints for every stage, one needs to specify which ones are to be parametric. One can use a fourth argument
in the newParam
call, as shown below.
In MATLAB
parameter = newParam(name, maps2stage, maps2data, idxWithinStage);
In Python
stages.newParam(name, maps2stage, maps2data, idxWithinStage)
where idxWithinStage denotes the index of the quadratic constraints to which this parameters applies.
12.4. Diagonal Hessians¶
In case your parametric Hessian is diagonal, you should use the fourth argument of newParam
as shown below.
In MATLAB
parameter1 = newParam('Hessians', 1:5, 'cost.H', 'diag');
In Python
stages.newParam('Hessians', range(1,6), 'cost.H', 'diag')
The FORCESPRO solver will then only expect a vector as a parameter. The 'diag'
keyword is currently only valid for hessian matrices related
to the objective function.
12.5. Sparse Parameters¶
If your parameters are not diagonal but they have a sparse structure that can be exploited for performance, you can use the fourth and fifth arguments of newParam to let FORCESPRO know about the sparsity pattern. In MATLAB
parameter2 = newParam('Ai', 1:5, 'ineq.p.A', 'sparse', [zeros(5, 6) rand(5, 2)]);
In Python
stages.newParam('Ai',range(1,6),'ineq.p.A','sparse',numpy.hstack((numpy.zeros(5,6),random.random((5,2)))))
The fifth argument is used to let FORCESPRO know about the location of the non-zero elements. When a solver is generated using sparse parameters it is the responsibility of the user to pass on parameters with the correct sparsity pattern to the solver. There will be no warnings thrown at runtime.
Sparse parameter values have to be passed as a column vector of nonzero elements, i.e. to assign the values of matrix B to sparse parameter Ci one should use the following: In MATLAB
problem.Ci = nonzeros(sparse(B));
In Python
problem.Ci = B[numpy.nonzeros(B)]
Note that parameters with a general sparsity structure defined by the fifth argument are currently only supported for polytopic constraints.
For the equality constraint matrices, only the structure [0 A]
, where A
is assumed to be dense, is currently supported.
12.6. Special Parameters¶
To prevent having to transfer entire matrices for parameters with few changing elements at runtime, one can specify a sixth argument to let FORCESPRO know about the location of the elements that will be supplied at runtime. In MATLAB
parameter2 = newParam('Ci', 1:5,'eq.C','sparse',Cstruc,Cvar)
In Python
stages.newParam('Ci',range(1,6),'eq.C','sparse',Cstruc,Cvar)
Note that in this case the constant values will be taken from the data supplied in the field Cstruc
. At runtime the user only has to supply a column vector including the time-varying elements marked in the field Cvar
.
The ordering should be column major.
12.7. Python: Column vs Row Major Storage Format¶
Unlike MATLAB, numpy stores arrays by default in row-major format internally. Since FORCESPRO expects the parameters in column major storage format, a conversion is necessary. This conversion is automatically performed by the Python interface when the solver is called. To avoid the conversion every time the solver is called, you should use the following way of creating the arrays storing parameters:
a = array([1, 2, 3, 4, 5, 6])
b = a.reshape(2,3,order='F')
The above code reshapes the array into a (2,3) Matrix stored in column major (Fortran) format.