Constraints

The constraints that are supported in LinearMPC.jl are of the form

\[\underline{b} \leq A_x x_k + A_u u_k \leq \overline{b}.\]

Such constraints can be added with the function add_constraint!.

Bounds on controls ($\underline{u} \leq u \leq \overline{u}$) and on outputs ($\underline{y} \leq y \leq \overline{y}$) can be set in a special way with the function set_bounds!.

Illustrative example

As an illustrative example, consider the case with two controls $u_1$ and $u_2$, two outputs $y_1$ and $y_2$, and three states $x_1$, $x_2$, and $x_3$, where we want to impose the following constraints

\[\begin{aligned} -2 &\leq u_1 &\leq 1 \\ 0 &\leq u_2 &\leq 3 \\ -1 &\leq y_1 &\leq 1 \\ -3 &\leq y_2 &\leq 0 \\ -1 &\leq x_2 - 3 x_3 &\leq 5 \\ -1 &\leq x_1 - 2 x_2 + u_2 &\leq 1 \end{aligned}\]

This can be added to an MPC controller mpc with the following commands

# control + output bounds
set_bounds!(mpc;umin=[-2,0], umax = [1,3], ymin = [-1,-3], ymax = [1,0])

# -1 ≤ x2 - 3 x3 ≤  5 
add_constraint!(mpc; Ax = [1 0 3], lb = -2, ub = 5)

# -1 ≤ x1 - 2 x2 + u2 ≤  1
add_constraint!(mpc; Ax = [1 -2 0], Au = [0 1], lb = -1, ub = 1)
import numpy as np

# control + output bounds
mpc.set_bounds(umin=[-2, 0], umax=[1, 3], ymin=[-1, -3], ymax=[1, 0])

# -1 <= x2 - 3 x3 <=  5
mpc.add_constraint(Ax=np.array([[1, 0, 3]]), lb=[-2], ub=[5])

# -1 <= x1 - 2 x2 + u2 <=  1
mpc.add_constraint(Ax=np.array([[1, -2, 0]]), Au=np.array([[0, 1]]), lb=[-1], ub=[1])

Constraint horizon

The constraints from above will be enforced for all time steps of the prediction horizon. It is, however, possible to specify a limited subset of time steps when a constraint should be satisfied. In other words, constraints are of the form

\[\underline{b} \leq A_x x_{k-1} + A_u u_{k-1} \leq \overline{b},\qquad \text{for all } k\in \mathcal{K},\]

where the index set ${\mathcal{K}} \subseteq \{1,\dots,N_p+1\}$ determines for which time steps the constraint should be enforced. The index $k=1$ refers to the current time step, with the state $x_0$ and the control $u_0$, and $k = N_p+1$ refers to the terminal state $x_{N_p}$ (for which only constraints on the state are formed). By default, $\mathcal{K} = \{1,\dots, N_p\}$ for constraints that involve the control, and $\mathcal{K} = \{2,\dots, N_p+1\}$ for constraints on the state only, since $x_0$ cannot be influenced. The indices for which the constraints should be enforced are specified with the optional argument ks to add_constraint!.

For example, say that we want to add the constraint

\[-1 \leq x_1 + x_2 + 3 x_3 \leq 1, \qquad k \in \{2,4,6\}\]

to the MPC controller mpc. This can be done with

add_constraint!(mpc;Ax=[1 1 3], lb = -1, ub = 1, ks = [2,4,6])
mpc.add_constraint(Ax=np.array([[1, 1, 3]]), lb=[-1], ub=[1], ks=[2, 4, 6])

Soft constraints

To ensure that there is a solution to the resulting optimization problem, it can be good to soften some constraint. A soft constraints means that the constraints is allowed to be violated, but such violations are penalized. A constraint can be marked to be soft by setting the optional argument soft to true. For example, if the constraint from above should be soft, one could run the command

add_constraint!(mpc;Ax=[1 1 3], lb = -1, ub = 1, ks = [2,4,6], soft=true)
mpc.add_constraint(Ax=np.array([[1, 1, 3]]), lb=[-1], ub=[1], ks=[2, 4, 6], soft=True)

By default, bound constraints on inputs $u$ are hard, and bound constraint outputs $y$ are soft.

To be more specific, a constraint $\underline{b} \leq A_x x_k + A_u u_k \leq \overline{b}$ is softened by introducing a slack variable $\epsilon$, and replace the constraint with the constraints

\[\begin{aligned} \underline{b}-\epsilon \leq &A_x x_k + A_u u_k & \\ & A_x x_k + A_u u_k &\leq \overline{b} + \epsilon. \end{aligned}\]

To penalize violation of the constraint, a term $\rho \|\epsilon\|$ is added to the objective function, where the weight $\rho$ determines how hard the soft constraints should be penalized. By default, $\rho$ is set to 1e6.