Engee documentation

Functions: Predictive Controllers

All the predictive controllers in this module rely on a state estimator to compute the predictions. The default LinMPC estimator is a SteadyKalmanFilter, and NonLinMPC with nonlinear models, an UnscentedKalmanFilter. For simpler and more classical designs, an InternalModel structure is also available, that assumes by default that the current model mismatch estimation is constant in the future (same approach as dynamic matrix control, DMC).

The nomenclature use capital letters for time series (and matrices) and hats for the predictions (and estimations, for state estimators).

To be precise, at the th control period, the vectors that encompass the future measured disturbances , model outputs and setpoints over the prediction horizon are defined as:

The vectors for the manipulated input are shifted by one time step:

Defining the manipulated input increment as , the control horizon enforces that for . For this reason, the vector that collects them is truncated up to :

PredictiveController

Abstract supertype of all predictive controllers.


(mpc::PredictiveController)(ry, d=[]; kwargs...) -> u

Functor allowing callable PredictiveController object as an alias for moveinput!.

Examples

julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1);

julia> u = mpc([5]); round.(u, digits=3)
1-element Vector{Float64}:
 1.0

LinMPC

LinMPC(model::LinModel; <keyword arguments>)

Construct a linear predictive controller based on LinModel model.

The controller minimizes the following objective function at each discrete time :

in which the weight matrices are repeated or times by default:

Time-varying weights over the horizons are also supported. The includes the input increments from to , the vector, the output predictions from to , and the vector, the manipulated inputs from to . The slack variable relaxes the constraints, as described in setconstraint! documentation. See Extended Help for a detailed nomenclature.

This method uses the default state estimator, a SteadyKalmanFilter with default arguments.

Arguments

  • model::LinModel : model used for controller predictions and state estimations.

  • Hp=10+nk: prediction horizon , nk is the number of delays in model.

  • Hc=2 : control horizon .

  • Mwt=fill(1.0,model.ny) : main diagonal of weight matrix (vector).

  • Nwt=fill(0.1,model.nu) : main diagonal of weight matrix (vector).

  • Lwt=fill(0.0,model.nu) : main diagonal of weight matrix (vector).

  • M_Hp=Diagonal(repeat(Mwt),Hp) : diagonal weight matrix .

  • N_Hc=Diagonal(repeat(Nwt),Hc) : diagonal weight matrix .

  • L_Hp=Diagonal(repeat(Lwt),Hp) : diagonal weight matrix .

  • Cwt=1e5 : slack variable weight (scalar), use Cwt=Inf for hard constraints only.

  • optim=JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer) : quadratic optimizer used in the predictive controller, provided as a JuMP.Model (default to OSQP optimizer).

  • additional keyword arguments are passed to SteadyKalmanFilter constructor.

Examples

julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4);

julia> mpc = LinMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFilter estimator and:
 30 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  4 estimated states x̂
  2 measured outputs ym (2 integrating states)
  0 unmeasured outputs yu
  0 measured disturbances d

Extended Help

Manipulated inputs setpoints are not common but they can be interesting for over-actuated systems, when nu > ny (e.g. prioritize solutions with lower economical costs). The default Lwt value implies that this feature is disabled by default.

The objective function follows this nomenclature:

VARIABLE DESCRIPTION SIZE

prediction horizon (integer)

()

control horizon (integer)

()

manipulated input increments over

(nu*Hc,)

predicted outputs over

(ny*Hp,)

manipulated inputs over

(nu*Hp,)

predicted output setpoints over

(ny*Hp,)

predicted manipulated input setpoints over

(nu*Hp,)

output setpoint tracking weights over

(ny*Hp, ny*Hp)

manipulated input increment weights over

(nu*Hc, nu*Hc)

manipulated input setpoint tracking weights over

(nu*Hp, nu*Hp)

slack variable weight

()

slack variable for constraint softening

()

LinMPC(estim::StateEstimator; <keyword arguments>)

Use custom state estimator estim to construct LinMPC.

estim.model must be a LinModel. Else, a NonLinMPC is required.

Examples

julia> estim = KalmanFilter(LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4), i_ym=[2]);

julia> mpc = LinMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, KalmanFilter estimator and:
 30 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  3 estimated states x̂
  1 measured outputs ym (1 integrating states)
  1 unmeasured outputs yu
  0 measured disturbances d

ExplicitMPC

ExplicitMPC(model::LinModel; <keyword arguments>)

Construct an explicit linear predictive controller based on LinModel model.

The controller minimizes the following objective function at each discrete time :

See LinMPC for the variable definitions. This controller does not support constraints but the computational costs are extremely low (array division), therefore suitable for applications that require small sample times. The keyword arguments are identical to LinMPC, except for Cwt and optim which are not supported.

This method uses the default state estimator, a SteadyKalmanFilter with default arguments.

Examples

julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4);

julia> mpc = ExplicitMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
ExplicitMPC controller with a sample time Ts = 4.0 s, SteadyKalmanFilter estimator and:
 30 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  4 estimated states x̂
  2 measured outputs ym (2 integrating states)
  0 unmeasured outputs yu
  0 measured disturbances d
ExplicitMPC(estim::StateEstimator; <keyword arguments>)

Use custom state estimator estim to construct ExplicitMPC.

estim.model must be a LinModel. Else, a NonLinMPC is required.

Examples

julia> estim = KalmanFilter(LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4), i_ym=[2]);

julia> mpc = ExplicitMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
ExplicitMPC controller with a sample time Ts = 4.0 s, KalmanFilter estimator and:
 30 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  3 estimated states x̂
  1 measured outputs ym (1 integrating states)
  1 unmeasured outputs yu
  0 measured disturbances d

NonLinMPC

NonLinMPC(model::SimModel; <keyword arguments>)

Construct a nonlinear predictive controller based on SimModel model.

Both NonLinModel and LinModel are supported (see Extended Help). The controller minimizes the following objective function at each discrete time :

See LinMPC for the variable definitions. The custom economic function can penalizes solutions with high economic costs. Setting all the weights to 0 except creates a pure economic model predictive controller (EMPC). The arguments of are the manipulated inputs, the predicted outputs and measured disturbances from to inclusively:

since implies that or . The vector includes the predicted measured disturbance over .

Replace any of the 3 arguments with _ if not needed (see JE default value below).

This method uses the default state estimator :

See Extended Help if you get an error like: MethodError: no method matching Float64(::ForwardDiff.Dual).

Arguments

  • model::SimModel : model used for controller predictions and state estimations.

  • Hp=nothing: prediction horizon , must be specified for NonLinModel.

  • Hc=2 : control horizon .

  • Mwt=fill(1.0,model.ny) : main diagonal of weight matrix (vector).

  • Nwt=fill(0.1,model.nu) : main diagonal of weight matrix (vector).

  • Lwt=fill(0.0,model.nu) : main diagonal of weight matrix (vector).

  • M_Hp=Diagonal(repeat(Mwt),Hp) : diagonal weight matrix .

  • N_Hc=Diagonal(repeat(Nwt),Hc) : diagonal weight matrix .

  • L_Hp=Diagonal(repeat(Lwt),Hp) : diagonal weight matrix .

  • Cwt=1e5 : slack variable weight (scalar), use Cwt=Inf for hard constraints only.

  • Ewt=0.0 : economic costs weight (scalar).

  • JE=(_,_,_)->0.0 : economic function .

  • optim=JuMP.Model(Ipopt.Optimizer) : nonlinear optimizer used in the predictive controller, provided as a JuMP.Model (default to Ipopt optimizer).

  • additional keyword arguments are passed to UnscentedKalmanFilter constructor (or SteadyKalmanFilter, for LinModel).

Examples

julia> model = NonLinModel((x,u,_)->0.5x+u, (x,_)->2x, 10.0, 1, 1, 1);

julia> mpc = NonLinMPC(model, Hp=20, Hc=1, Cwt=1e6)
NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedKalmanFilter estimator and:
 20 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  2 estimated states x̂
  1 measured outputs ym (1 integrating states)
  0 unmeasured outputs yu
  0 measured disturbances d

Extended Help

NonLinMPC controllers based on LinModel compute the predictions with matrix algebra instead of a for loop. This feature can accelerate the optimization, especially for the constraint handling, and is not available in any other package, to my knowledge.

The optimization relies on JuMP automatic differentiation (AD) to compute the objective and constraint derivatives. Optimizers generally benefit from exact derivatives like AD. However, the NonLinModel f and h functions must be compatible with this feature. See Automatic differentiation for common mistakes when writing these functions.

Note that if Cwt≠Inf, the attribute nlp_scaling_max_gradient of Ipopt is set to 10/Cwt (if not already set), to scale the small values of .

NonLinMPC(estim::StateEstimator; <keyword arguments>)

Use custom state estimator estim to construct NonLinMPC.

Examples

julia> model = NonLinModel((x,u,_)->0.5x+u, (x,_)->2x, 10.0, 1, 1, 1);

julia> estim = UnscentedKalmanFilter(model, σQint_ym=[0.05]);

julia> mpc = NonLinMPC(estim, Hp=20, Hc=1, Cwt=1e6)
NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedKalmanFilter estimator and:
 20 prediction steps Hp
  1 control steps Hc
  1 manipulated inputs u (0 integrating states)
  2 estimated states x̂
  1 measured outputs ym (1 integrating states)
  0 unmeasured outputs yu
  0 measured disturbances d

Move Manipulated Input u

moveinput!(mpc::PredictiveController, ry=mpc.estim.model.yop, d=[]; <keyword args>) -> u

Compute the optimal manipulated input value u for the current control period.

Solve the optimization problem of mpc PredictiveController and return the results . Following the receding horizon principle, the algorithm discards the optimal future manipulated inputs Note that the method mutates mpc internal data but it does not modifies mpc.estim states. Call updatestate!(mpc, u, ym, d) to update mpc state estimates.

Calling a PredictiveController object calls this method.

Arguments

  • mpc::PredictiveController : solve optimization problem of mpc.

  • ry=mpc.estim.model.yop : current output setpoints .

  • d=[] : current measured disturbances .

  • D̂=repeat(d, mpc.Hp) : predicted measured disturbances , constant in the future by default or for to .

  • R̂y=repeat(ry, mpc.Hp) : predicted output setpoints , constant in the future by default or for to .

  • R̂u=repeat(mpc.estim.model.uop, mpc.Hp) : predicted manipulated input setpoints, constant in the future by default or for to .

  • ym=nothing : current measured outputs , only required if mpc.estim is an InternalModel.

Examples

julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1);

julia> ry = [5]; u = moveinput!(mpc, ry); round.(u, digits=3)
1-element Vector{Float64}:
 1.0