Parameters (or real-time data) are a key concept in FORCES Pro. 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 non-linear dynamics, or because the cost matrices of the objective function are tuned online.
FORCES Pro 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, use the function
parameter = newParam(name, maps2stage, maps2data)
stages.newParam(name, maps2stage, maps2data)
where name is the name you give to a parameter (you will need this to set it before calling the solver). The index vector (or integer) maps2stage defines to which stage this parameter maps. The last argument, maps2data has to be one of the following strings:
Possible string values for argument maps2data
* Please read the section below on parametric quadratic constraints for more information.
To define the linear term of the cost of stages 1 to 5 as a parameter, use the following command:
parameter1 = newParam('linear_cost', 1:5, 'cost.f')
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 all stages. If the runtime data should be different for each stage one would have to generate five different parameters in this case.
We can also have a second parameter, for example the RHS of the first equality constraint, which is very common in MPC:
parameter2 = newParam('RHS_first_equality_constraint', 1, 'eq.c')
stages.newParam('RHS_first_equality_constraint', , 'eq.c')
Parametric Quadratic Constraints
Since there can be multiple quadratic constraints per stage, FORCES Pro needs to know for which of them to create a parameter. This can be told FORCES by the fourth argument:
parameter = newParam(name, maps2stage, maps2data, idxWithinStage)
stages.newParam(name, maps2stage, maps2data, idxWithinStage)
denotes the index of the quadratic constraint to which this parameter should apply.
If your parametric Hessian is diagonal, you can use the fourth argument of newParam to let FORCES Pro know:
parameter1 = newParam('Hessians', 1:5,'cost.H','diag')
stages.newParam('Hessians', range(1,6), 'cost.H', 'diag')
It will then only expect a vector as a parameter. The 'diag'
keyword is currently only valid for Hessian matrices related to the objective function.
If your parameters are not diagonal but they have structure that can be exploited, you can use the fourth and fifth arguments of newParam to let FORCES Pro know about the sparsity pattern:
parameter2 = newParam('Ai', 1:5,'ineq.p.A','sparse',[zeros(5,6) rand(5,2)])
The fifth argument is used to let FORCES Pro know about the location of the non-zeros. 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 during 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:
problem.Ci = nonzeros(sparse(B));
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.
To prevent having to transfer entire matrices for parameters with few changing elements during runtime, one can specify a sixth argument to let FORCES Pro know about the location of the elements that will be supplied during runtime:
parameter2 = newParam('Ci', 1:5,'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.
Python: Column vs Row Major Storage Format
Unlike Matlab, numpy stores arrays by default in row-major format internally. Since FORCES 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')
This will reshape the array into a (2,3) Matrix stored in column major (Fortran) format.