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:
(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:
(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))
The right-hand side depends only on the initial state x₀ (its first entry), and the box scale row contributes the 1:
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.
(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))