On this page:
<require>
<provide>
3.14.1 Objective and dynamics
<P>
3.14.2 The box cone for input bounds
<G>
<rhs>
3.14.3 Receding horizon
<run>
<*>

3.14 Model predictive control🔗ℹ

Model predictive control (MPC) repeatedly solves a finite-horizon optimal-control problem as the system state evolves, applying the first control and re-solving. It mirrors the SCS MPC example and showcases two features: the box cone for input bounds, and receding-horizon warm starting when only b changes between solves.

For a scalar system x_{t+1} = x_t + u_t over horizon T = 3 with x₀ given:

minimize Σ_{t=1}^{3} x_t² + 0.1·Σ_{t=0}^{2} u_t²
subject to x_{t+1} = x_t + u_t, −1 ≤ u_t ≤ 1

Variables are w = (u₀, u₁, u₂, x₁, x₂, x₃).

(require scs)

(provide run-example)

3.14.1 Objective and dynamics🔗ℹ

The quadratic cost gives P entries 0.2 on each u_t (from 0.1·u² = ½·0.2·u²) and 2 on each x_t:

<P> ::=
(define P
  (scs:sparse-matrix 6 6
                     '(0 0 0.2) '(1 1 0.2) '(2 2 0.2)
                     '(3 3 2) '(4 4 2) '(5 5 2)))

3.14.2 The box cone for input bounds🔗ℹ

The bounds −1 ≤ u_t ≤ 1 use SCS’s box cone {(s₀, r) : s₀·l ≤ r ≤ s₀·u}. Its first row is the scale s₀, which we pin to 1 with a constant row (all-zero A coefficients and right-hand side 1); the remaining rows carry u₀..u₂. The three dynamics equations come first as the zero cone:

<G> ::=
(define G
  (scs:matrix 7 6
 
              -1  0  0  1  0  0    ; x1 - u0 = x0 (dynamics)
               0 -1  0 -1  1  0    ; x2 - x1 - u1 = 0
               0  0 -1  0 -1  1    ; x3 - x2 - u2 = 0
               0  0  0  0  0  0    ; box scale row -> s0 = 1
              -1  0  0  0  0  0    ; s = u0
               0 -1  0  0  0  0    ; s = u1
               0  0 -1  0  0  0))
(code:comment "s = u2")

The right-hand side depends only on the initial state x₀ (its first entry), and the box scale row contributes the 1:

<rhs> ::=
(define (rhs x0)
  (vector x0 0.0 0.0 1.0 0.0 0.0 0.0))

3.14.3 Receding horizon🔗ℹ

We build the solver for x₀ = 2, solve it, then solver-update! the right-hand side and warm-solve for x₀ = 1.5. #:box-lower and #:box-upper declare the bounds; the cone is three zero rows then the box block.

<run> ::=
(define (run-example [x0s '(2.0 1.5)])
  (define s
    (make-solver #:A G
                 #:b (rhs (car x0s))
                 #:c #(0.0 0.0 0.0 0.0 0.0 0.0)
                 #:P P
                 #:cone (make-cone #:zero 3
                                   #:box-lower '(-1.0 -1.0 -1.0)
                                   #:box-upper '(1.0 1.0 1.0))
                 #:settings (make-settings #:eps-abs 1e-9 #:eps-rel 1e-9)))
  (for/list ([x0 (in-list x0s)]
             [step (in-naturals)])
    (define r
      (cond
        [(zero? step) (solver-solve! s)]
        [else
         (solver-update! s #:b (rhs x0))
         (solver-solve! s)]))
 
    (list x0 (scs-result-exit-flag r) (vector-ref (scs-result-x r) 0))))

Running it.

Both initial states drive the first input to its lower bound:

(run-example)   ; ((2.0 1 -1.0) (1.5 1 -1.0))

<*> ::=