7. Parametric problems

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 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.

7.1. Defining parameters

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 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

Table 7.1 Possible string values for argument maps2data
Cost function Equality constraints Inequality constraints
'cost.H' 'eq.c' 'ineq.b.lb'
'cost.f' 'eq.C' 'ineq.b.ub'
  'eq.D' 'ineq.p.A'
    'ineq.p.b'
    'ineq.q.Q'
    'ineq.q.l'
    'ineq.q.r'

From FORCES Pro 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. FORCES Pro 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 Forces ‘1.8.0’.

7.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 differents in this case.

We can also have a second parameter. For instance, the right handside of the first equality constraints, which is a very common caes in MPC. In MATLAB

parameter2 = newParam('RHS_first_equality_constraint', 1, 'eq.c');

In Python

stages.newParam('RHS_first_equality_constraint', [1], 'eq.c')

7.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.

7.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 FORCES Pro 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.

7.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 FORCES Pro 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 FORCES Pro 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.

7.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 FORCES Pro 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.

7.7. 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')

The above code reshapes the array into a (2,3) Matrix stored in column major (Fortran) format.