Source code for soft4pes.control.mpc.solvers.mpc_bnb
"""Branch-and-bound solver for model predictive control (MPC)."""
from itertools import product
import numpy as np
from soft4pes.control.mpc.solvers.utils import (switching_constraint_violated,
squared_weighted_second_norm)
[docs]class MpcBnB:
"""
Branch-and-bound (BnB) solver for model predictive control (MPC).
Parameters
----------
conv : converter object
Converter model.
Attributes
----------
J_min : float
Minimum cost.
U_seq : 1 x 3*Np ndarray of ints
Sequence of three-phase switch positions (switching sequence) with the lowest cost.
U_temp : 1 x 3*Np ndarray of ints
Temporary array for incumbent swithing sequence.
SW_COMB : 1 x conv.nl^3 ndarray of ints
All possible three-phase switch positions.
"""
def __init__(self, conv):
if conv.nl == 2:
sw_pos_3ph = np.array([-1, 1])
elif conv.nl == 3:
sw_pos_3ph = np.array([-1, 0, 1])
else:
raise ValueError(
'Only two- and three-level converters are supported.')
# Create all possible three-phase switch positions
[docs] self.SW_COMB = np.array(list(product(sw_pos_3ph, repeat=3)))
[docs] def __call__(self, sys, ctr, y_ref):
"""
Solve MPC problem by using a simple BnB method.
Parameters
----------
sys : system object
System model.
ctr : controller object
Controller object.
y_ref : ndarray of floats
Reference vector [p.u.].
Returns
-------
u_abc : 1 x 3 ndarray of ints
The three-phase switch position.
"""
self.J_min = np.inf
self.U_seq = np.zeros(3 * ctr.Np)
self.U_temp = np.zeros(3 * ctr.Np)
self.solve(sys, ctr, sys.x, y_ref, ctr.u_km1_abc)
u_abc = self.U_seq[0:3]
return u_abc
[docs] def solve(self, sys, ctr, x_ell, y_ref, u_ell_abc_prev, ell=0, J_prev=0):
"""
Recursively compute the cost for different switching sequences.
Parameters
----------
sys : object
System model.
ctr : object
Controller object.
x_ell : ndarray of floats
State vector [p.u.].
y_ref : ndarray of floats
Reference vector [p.u.].
u_ell_abc_prev : 1 x 3 ndarray of ints
Previous three-phase switch position.
ell : int
Prediction step. The default is 0.
J_prev : float
Previous cost. The default is 0.
"""
# Iterate over all possible three-phase switch positions
for u_ell_abc in self.SW_COMB:
# Check if switching constraint is violated or cost is infinite
if not switching_constraint_violated(sys.conv.nl, u_ell_abc,
u_ell_abc_prev):
# Compute the next state
x_ell_next = ctr.get_next_state(sys, x_ell, u_ell_abc, ell)
# Calculate the cost of reference tracking and control effort
y_ell_next = np.dot(ctr.C, x_ell_next)
Q = np.eye(np.size(y_ref[ell + 1]))
y_error = squared_weighted_second_norm(
y_ref[ell + 1] - y_ell_next, Q)
delta_u = np.linalg.norm(u_ell_abc - u_ell_abc_prev, ord=1)
J_temp = J_prev + y_error + ctr.lambda_u * delta_u
# if the cost is smaller than the current minimum cost, continue
if J_temp < self.J_min:
# If not at the last prediction step, move to the next prediction step
if ell < ctr.Np - 1:
self.U_temp[3 * ell:3 * (ell + 1)] = u_ell_abc
self.solve(sys, ctr, x_ell_next, y_ref, u_ell_abc,
ell + 1, J_temp)
else:
# If at the last prediction step, store the three-phase switch position and
# update the minimum cost
self.U_temp[3 * ell:3 * (ell + 1)] = u_ell_abc
self.U_seq = np.copy(self.U_temp)
self.J_min = J_temp