├── DRO_model.py ├── DRO_optproblem.py ├── DRO_simulation.py ├── README.md ├── fig ├── .DS_Store ├── fig1.png ├── fig2.png ├── fig3.png ├── fig4.png └── fig5.png ├── inverted_pendulum.py ├── inverted_pendulum_experiment_N=var_eps=1.py ├── inverted_pendulum_experiment_eps=var_N_sample=1.py ├── mass_spring.py ├── mass_spring_collect_data.py ├── mass_spring_experiment_N=var_eps=1.py └── mass_spring_experiment_eps=var_N_sample=1.py /DRO_model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.linalg import matrix_power 3 | class Model: 4 | 5 | def __init__(self, A, B, C, D, E, x_init, N, delta_t): 6 | ''' 7 | x_next = Ak x_k + Bk u_k + Ck w_k 8 | y_k = D x_k + E w_k 9 | 10 | Args: 11 | A: Continuous time 12 | B: Continuous time 13 | C: Discrete time 14 | D: Discrete time 15 | E: Discrete time 16 | 17 | ''' 18 | 19 | self.A = A 20 | self.B = B 21 | 22 | self.delta_t = delta_t 23 | self.N = N 24 | 25 | Ak, Bk = self.disc_linear_system(A, B, delta_t) 26 | self.Ak = Ak 27 | self.Bk = Bk 28 | self.C = C 29 | self.D = D 30 | self.E = E 31 | 32 | n, m, d, r, Nx, Nu, Ny, Nw, Ax, Bx, Cx, Ay, By, Cy, Ey, Cx_tilde, Cy_tilde, Ey_tilde, D_tilde = self.stack_system(Ak, Bk, C, D, E, x_init, N) 33 | # Define relevant dimension 34 | self.n = n 35 | self.m = m 36 | self.d = d 37 | self.r = r 38 | self.Nx = Nx 39 | self.Nu = Nu 40 | self.Ny = Ny 41 | self.Nw = Nw 42 | # Stack system matrices 43 | self.Ax = Ax 44 | self.Bx = Bx 45 | self.Cx = Cx 46 | self.Ay = Ay 47 | self.By = By 48 | self.Cy = Cy 49 | self.Ey = Ey 50 | self.Cx_tilde = Cx_tilde 51 | self.Cy_tilde = Cy_tilde 52 | self.Ey_tilde = Ey_tilde 53 | self.D_tilde = D_tilde 54 | 55 | 56 | def disc_linear_system(self, A, B, delta_t): 57 | ''' 58 | Discrete a linear system with implicit Euler 59 | x[k+1] = (I - delta_t * A)^{-1} @ x[k] + (I - delta_t * A)^{-1} @ (delta_t * B) @ u[k] 60 | 61 | Returns: 62 | Ak 63 | Bk 64 | 65 | ''' 66 | Nx = np.shape(A)[0] 67 | Ix = np.identity(Nx) 68 | 69 | Ak = np.linalg.inv(Ix - delta_t * A) 70 | Bk = np.linalg.inv(Ix - delta_t * A) @ (delta_t * B) 71 | 72 | return Ak, Bk 73 | 74 | 75 | def stack_system(self, A, B, C, D, E, x_init, N): 76 | ''' 77 | Stack system matrix for N prediction horizon 78 | 79 | x_next = A x_k + B u_k + C w_k 80 | y_k = D x_k + E w_k 81 | 82 | ''' 83 | 84 | n = np.shape(A)[1] # Dimension of state 85 | m = np.shape(B)[1] # Dimension of input 86 | d = np.shape(C)[1] # Dimension of disturbance 87 | r = np.shape(D)[0] # Dimension of output 88 | 89 | # print(Nx,Nu,Nw,Ny) 90 | Nx = (N + 1) * n 91 | Nu = N * m 92 | Ny = N * r 93 | Nw = N * d 94 | 95 | Ax = np.zeros([Nx, n]) 96 | Bx = np.zeros([Nx, Nu]) 97 | Cx = np.zeros([Nx, Nw]) 98 | Ay = np.zeros([Ny, n]) 99 | By = np.zeros([Ny, Nu]) 100 | Cy = np.zeros([Ny, Nw]) 101 | Ey = np.zeros([Ny, Nw]) 102 | Cx_tilde = np.zeros([Nx, Nw + 1]) 103 | Cy_tilde = np.zeros([Ny + 1, Nw + 1]) 104 | Ey_tilde = np.zeros([Ny + 1, Nw + 1]) 105 | D_tilde = np.zeros([Ny + 1, Nx]) 106 | # H 107 | 108 | # Ax 109 | for i in range(N + 1): 110 | Ax[i * n:(i + 1) * n, :] = matrix_power(A, i) 111 | # Bx 112 | for i in range(N): 113 | mat_temp = B 114 | for j in range(i + 1): 115 | Bx[(i + 1) * n: (i + 2) * n, (i - j) * m: (i - j + 1) * m] = mat_temp 116 | mat_temp = A @ mat_temp 117 | # Cx 118 | for i in range(N): 119 | mat_temp = C 120 | for j in range(i + 1): 121 | Cx[(i + 1) * n: (i + 2) * n, (i - j) * d: (i - j + 1) * d] = mat_temp 122 | mat_temp = A @ mat_temp 123 | # Ay 124 | for i in range(N): 125 | Ay[i * r:(i + 1) * r, :] = D @ matrix_power(A, i) 126 | # By 127 | for i in range(N): 128 | mat_temp = B 129 | for j in range(i + 1): 130 | By[(i + 1) * r: (i + 2) * r, (i - j) * m: (i - j + 1) * m] = D @ mat_temp 131 | mat_temp = A @ mat_temp 132 | # Cy 133 | for i in range(N): 134 | mat_temp = C 135 | for j in range(i + 1): 136 | Cy[(i + 1) * r: (i + 2) * r, (i - j) * d: (i - j + 1) * d] = D @ mat_temp 137 | mat_temp = A @ mat_temp 138 | # Ey 139 | for i in range(N): 140 | Ey[i * r: (i + 1) * r, i * d: (i + 1) * d] = E 141 | # Cx_tilde 142 | Cx_tilde[:, [0]] = Ax @ x_init 143 | Cx_tilde[:, 1:] = Cx 144 | # Cy_tilde 145 | Cy_tilde[r:, [0]] = Ay @ x_init 146 | Cy_tilde[r:, 1:] = Cy 147 | # Ey_tilde 148 | Ey_tilde[0, 0] = 1 149 | Ey_tilde[1:, 1:] = Ey 150 | # D_tilde 151 | for i in range(N): 152 | D_tilde[1 + i * r: 1 + (i + 1) * r, i * n: (i + 1) * n] = D 153 | 154 | return n, m, d, r, Nx, Nu, Ny, Nw, Ax, Bx, Cx, Ay, By, Cy, Ey, Cx_tilde, Cy_tilde, Ey_tilde, D_tilde 155 | 156 | 157 | 158 | def change_xinit(self, x_k): 159 | ''' 160 | x_next = A x_k + B u_k + C w_k 161 | y_k = D x_k + E w_k 162 | ''' 163 | 164 | n = self.n 165 | m = self.m 166 | d = self.d 167 | r = self.r 168 | 169 | # print(Nx,Nu,Nw,Ny) 170 | Nx = self.Nx 171 | Nu = self.Nu 172 | Ny = self.Ny 173 | Nw = self.Nw 174 | 175 | N = self.N 176 | 177 | A = self.Ak 178 | B = self.Bk 179 | C = self.C 180 | D = self.D 181 | E = self.E 182 | 183 | 184 | Ax = self.Ax 185 | Bx = self.Bx 186 | Cx = self.Cx 187 | Ay = self.Ay 188 | By = self.By 189 | Cy = self.Cy 190 | Ey = self.Ey 191 | 192 | Cx_tilde = self.Cx_tilde 193 | Cy_tilde = self.Cy_tilde 194 | Ey_tilde = self.Ey_tilde 195 | D_tilde = self.D_tilde 196 | 197 | # Cx_tilde 198 | Cx_tilde[:, [0]] = Ax @ x_k 199 | # Cx_tilde[:, 1:] = Cx 200 | # Cy_tilde 201 | Cy_tilde[r:, [0]] = Ay @ x_k 202 | # Cy_tilde[r:, 1:] = Cy 203 | 204 | # Stack system matrices 205 | 206 | self.Cx_tilde = Cx_tilde 207 | self.Cy_tilde = Cy_tilde 208 | 209 | # return n, m, d, r, Nx, Nu, Ny, Nw, Ax, Bx, Cx, Ay, By, Cy, Ey, Cx_tilde, Cy_tilde, Ey_tilde, D_tilde -------------------------------------------------------------------------------- /DRO_optproblem.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cvxpy as cp 3 | import mosek 4 | 5 | 6 | class Opt_problem: 7 | def __init__(self, model, Q, Qf, R, mu, beta = 0.95, N_sample = 5, i_th_state = 1, i_state_ub = 0.05, epsilon = 1, sin_const = 1, collect = None, data_set = None): 8 | 9 | N = model.N 10 | 11 | n = model.n 12 | m = model.m 13 | d = model.d 14 | r = model.r 15 | Nx = model.Nx 16 | Nu = model.Nu 17 | Ny = model.Ny 18 | Nw = model.Nw 19 | # Stack system matrices 20 | Ax = model.Ax 21 | Bx = model.Bx 22 | Cx = model.Cx 23 | Ay = model.Ay 24 | By = model.By 25 | Cy = model.Cy 26 | Ey = model.Ey 27 | Cx_tilde = model.Cx_tilde 28 | Cy_tilde = model.Cy_tilde 29 | Ey_tilde = model.Ey_tilde 30 | D_tilde = model.D_tilde 31 | 32 | self.Q = Q 33 | self.Qf = Qf 34 | self.R = R 35 | 36 | if not collect: 37 | Jx, Ju, eigval,eigvec, H_cal_dec, H, H_new_matrix, H_new, loss_func = self.define_loss_func(n, m, d, r, Nu, Nw, Bx, Cx_tilde, N, Q, Qf, R, mu, beta, sin_const) 38 | # W_sample_matrix, W_sample_matrix_ext = self.disturbance_para(N, d, N_sample, sin_const) 39 | W_sample_matrix, W_sample_matrix_ext = self.gene_disturbance(N, d, N_sample, sin_const) 40 | 41 | lambda_var, gamma_matrix, si_var, constraint = self.define_constraint(W_sample_matrix, W_sample_matrix_ext, eigvec, H_cal_dec, H, H_new, n, d, Bx, Cx_tilde, Cy_tilde, 42 | Ey_tilde, N, N_sample, sin_const, i_th_state, i_state_ub, epsilon) 43 | if collect: 44 | Jx, Ju, eigval, eigvec, H_cal_dec, H, H_new_matrix, H_new, loss_func = self.define_loss_func(n, m, d, r, Nu, 45 | Nw, Bx, 46 | Cx_tilde, N, Q, 47 | Qf, R, mu, 48 | beta, 49 | sin_const) 50 | W_sample_matrix, W_sample_matrix_ext = self.select_disturbance(N, d, N_sample, sin_const,data_set) 51 | lambda_var, gamma_matrix, si_var, constraint = self.define_constraint(W_sample_matrix, W_sample_matrix_ext, eigvec, H_cal_dec, H, H_new, n, d, Bx, Cx_tilde, Cy_tilde, 52 | Ey_tilde, N, N_sample, sin_const, i_th_state, i_state_ub, epsilon) 53 | 54 | 55 | 56 | self.lambda_var = lambda_var 57 | self.si_var = si_var 58 | 59 | self.H_cal_dec = H_cal_dec 60 | self.W_sample_matrix = W_sample_matrix 61 | self.W_sample_matrix_ext = W_sample_matrix_ext 62 | 63 | self.obj = cp.Minimize(loss_func) 64 | self.prob = cp.Problem(self.obj, constraint) 65 | 66 | def select_disturbance(self, N, d, N_sample, sin_const,data_set): 67 | W_sample_matrix = np.vstack(data_set[- N * N_sample:]) 68 | W_sample_matrix = W_sample_matrix.T.reshape(d * N, -1, order='F') 69 | W_sample_matrix_ext = np.vstack([np.ones([1, N_sample]), W_sample_matrix]) 70 | 71 | return W_sample_matrix, W_sample_matrix_ext 72 | def gene_disturbance(self, N, d, N_sample, sin_const): 73 | # Generate data: const * sinx 74 | 75 | w_sample = [] 76 | for i in range(N_sample): 77 | w_temp = sin_const * np.sin(np.random.randn(N * d)) 78 | w_sample += [w_temp] 79 | W_sample_matrix = np.array(w_sample).T 80 | 81 | W_sample_matrix_ext = np.vstack([np.ones([1, N_sample]), W_sample_matrix]) 82 | return W_sample_matrix, W_sample_matrix_ext 83 | 84 | # def disturbance_para(self, N, d, N_sample, sin_const): 85 | # W_sample_matrix = cp.Parameter((N * d, N_sample)) 86 | # W_sample_matrix_ext = cp.vstack([np.ones([1, N_sample]),W_sample_matrix]) 87 | # return W_sample_matrix, W_sample_matrix_ext 88 | 89 | def define_loss_func(self, n, m, d, r, Nu, Nw, Bx, Cx_tilde, N, Q, Qf, R, mu, beta, sin_const): 90 | # Define decision variables for POB affine constrol law 91 | 92 | sigma = 1 # var of normal distribution 93 | 94 | H_cal_dec = cp.Variable((Nu, 1)) 95 | # print(H_cal_dec) 96 | # \mathcal{H} matrix 97 | for i in range(N): 98 | H_col = cp.Variable((Nu - (i * m), r)) 99 | if i > 0: 100 | H_col = cp.vstack([np.zeros([i * m, r]), H_col]) 101 | # print(H_col) 102 | H_cal_dec = cp.hstack([H_cal_dec, H_col]) 103 | # print(H_cal_dec) 104 | # print(np.shape(H_cal_dec)) 105 | # Define intermediate decision variables for objective function 106 | H = cp.Variable((Nu, Nw + 1)) 107 | 108 | # Define loss function 109 | # beta = 0.95 110 | Jx = np.zeros([(N + 1) * n, (N + 1) * n]) 111 | for i in range(N): 112 | Jx[i * n: (i + 1) * n, i * n: (i + 1) * n] = beta ** i * Q 113 | Jx[N * n:, N * n:] = beta ** N * Qf 114 | 115 | Ju = np.zeros([N * m, N * m]) 116 | for i in range(N): 117 | Ju[i * m: (i + 1) * m, i * m: (i + 1) * m] = beta ** i * R 118 | 119 | # This is only for var = 1 and mean = 0. Should be modified. 120 | mu_w = np.vstack([1] + [mu] * N) 121 | # M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 122 | M_w = np.diag([1] + [sin_const ** 2 * (1 - np.exp(-2 * sigma ** 2)) / 2] * N * d) 123 | 124 | # Intermediate decision variables. Since CVXPY does not support quadratic obj of decision variable matrix. 125 | H_new_matrix = [] 126 | # for i in range(Nw+1): 127 | # H_new_matrix += [cp.Variable([Nu,1])] 128 | # H_new = cp.hstack(H_new_matrix) 129 | for i in range(Nu): 130 | H_new_matrix += [cp.Variable((1, Nw + 1))] 131 | H_new = cp.vstack(H_new_matrix) 132 | 133 | # print(H_new.shape) 134 | # Reformulate the quadratic term 135 | 136 | eigval, eigvec = np.linalg.eig(Ju + Bx.T @ Jx @ Bx) 137 | eigval_mat = np.diag(eigval) 138 | # print(Ju + Bx.T @ Jx @ Bx - eigvec @ eigval_mat @ np.linalg.inv(eigvec)) 139 | # Loss function 140 | loss_func = 0 141 | 142 | N_eig = np.shape(eigval)[0] 143 | I = np.diag([1] * (Nw + 1)) 144 | for i in range(N_eig): 145 | # Reformulate Tr(H.T @ (Ju + Bx.T @ Jx @ Bx)@ H ) @ M_w 146 | # print(np.shape(H_new_matrix[i].T)) 147 | loss_func += eigval[i] * M_w[i, i] * cp.quad_form(H_new_matrix[i].T, 148 | I) # When M_w is identity matrix. Otherwise reformulate system matrix or this line 149 | # loss_func += cp.trace(2 * Cx_tilde.T @ Jx @ Bx @ eigvec @ H_new @ M_w) 150 | loss_func += cp.trace(2 * Cx_tilde.T @ Jx @ Bx @ H @ M_w) 151 | # loss_func += cp.trace(2 * Cx_tilde.T @ Jx @ Bx @ H_cal_dec @ (Cy_tilde + Ey_tilde) @ M_w) 152 | loss_func += cp.trace(Cx_tilde.T @ Jx @ Cx_tilde @ M_w) 153 | # Reformulate mu_w.T @ (H.T @ (Ju + Bx.T @ Jx @ Bx)@ H ) @ mu_w 154 | # loss_func += eigval[0] * cp.quad_form(H_new_matrix[0].T, I) + 2 * mu_w.T @ Cx_tilde.T @ Jx @ Bx @ eigvec @ H_new @ mu_w 155 | loss_func += eigval[0] * cp.quad_form(H_new_matrix[0].T, I) + 2 * mu_w.T @ Cx_tilde.T @ Jx @ Bx @ H @ mu_w 156 | # loss_func += eigval[0] * cp.quad_form(H_new_matrix[0].T, I) + 2 * mu_w.T @ Cx_tilde.T @ Jx @ Bx @ H_cal_dec @ (Cy_tilde + Ey_tilde) @ mu_w 157 | loss_func += mu_w.T @ Cx_tilde.T @ Jx @ Cx_tilde @ mu_w 158 | 159 | return Jx, Ju, eigval, eigvec, H_cal_dec, H, H_new_matrix, H_new, loss_func 160 | 161 | 162 | def define_constraint(self, W_sample_matrix, W_sample_matrix_ext, eigvec, H_cal_dec, H, H_new, n, d, Bx, Cx_tilde, Cy_tilde, 163 | Ey_tilde, N, N_sample, sin_const, i_th_state, i_state_ub, epsilon): 164 | constraint = [] 165 | constraint += [H_new == np.linalg.inv(eigvec) @ H] 166 | # constraint += [H_new == eigvec.T @ H ] 167 | constraint += [H == H_cal_dec @ (Cy_tilde + Ey_tilde)] 168 | # constraint += [H_new == np.linalg.inv(eigvec) @ H_cal_dec @ (Cy_tilde + Ey_tilde) ] 169 | 170 | # i_th_state = 1 # 0 for first element, 1 for second element 171 | # i_state_ub = 0.05 172 | 173 | d_supp = np.vstack((sin_const * np.ones([N * d, 1]), sin_const * np.ones([N * d, 1]))) 174 | C_supp = np.vstack((np.diag([1] * N * d), np.diag([-1] * N * d))) 175 | # d_supp = np.vstack( ( 0 * np.ones([N*d, 1]), 0 * np.ones([N*d, 1]))) 176 | # C_supp = np.vstack( (np.diag([0]*N*d), np.diag([0]*N*d) )) 177 | # lambda_var = cp.Variable() 178 | lambda_var = cp.Variable(nonneg=True) 179 | 180 | gamma_shape = np.shape(d_supp)[0] 181 | gamma_matrix = [] 182 | for i in range(N_sample): 183 | for j in range(N): 184 | gamma_var = cp.Variable((gamma_shape, 1), nonneg=True) 185 | # gamma_var = cp.Variable([gamma_shape,1]) 186 | gamma_matrix += [gamma_var] 187 | # k in N, i in N_sample 188 | # bk + 189 | X_constraint = (Bx @ H_cal_dec @ (Cy_tilde + Ey_tilde) + Cx_tilde) @ W_sample_matrix_ext 190 | # si_var = cp.Variable((N_sample,1)) 191 | si_var = cp.Variable(N_sample) 192 | 193 | for i in range(N_sample): 194 | for j in range(N): 195 | # print(N_sample) 196 | constraint_temp = X_constraint[n * (j + 1) + i_th_state, i] + gamma_matrix[i * N + j].T @ ( 197 | d_supp - C_supp @ W_sample_matrix[:, [i]]) 198 | # constraint += [constraint_temp <= si_var[i,0]] 199 | constraint += [constraint_temp <= si_var[i]] 200 | 201 | ak_matrix = (Bx @ H_cal_dec @ (Cy_tilde + Ey_tilde) + Cx_tilde)[:, 1:] 202 | for i in range(N_sample): 203 | for j in range(N): 204 | # constraint_temp = C_supp.T @ gamma_matrix[i * N + j] - ak_matrix[n * (j+1) + i_th_state:n * (j+1)+i_th_state + 1,:].T 205 | constraint_temp = C_supp.T @ gamma_matrix[i * N + j] - ak_matrix[[n * (j + 1) + i_th_state], :].T 206 | # constraint += [cp.norm_inf(constraint_temp) <= lambda_var] 207 | constraint += [cp.norm(constraint_temp, p=np.inf) <= lambda_var] 208 | 209 | # for i in range(N_sample): 210 | # for j in range(N): 211 | # constraint += [gamma_matrix[i * N + j] >= 0] 212 | # constraint += [lambda_var * epsilon + 1/N_sample * cp.sum(si_var) <= i_state_ub] 213 | constraint += [lambda_var * epsilon + 1 / N_sample * cp.sum(si_var) <= i_state_ub] 214 | return lambda_var, gamma_matrix, si_var, constraint 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /DRO_simulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_optproblem import Opt_problem 3 | from DRO_model import Model 4 | import mosek 5 | import cvxpy as cp 6 | import matplotlib.pyplot as plt 7 | 8 | class Simulation: 9 | ''' 10 | @Arg 11 | 12 | mode: "collect" data and incorporate the collected data into constraint 13 | "gene" data at each time instant and use fixed number of data to solve opt problem 14 | 15 | 16 | ''' 17 | 18 | def __init__(self, model, Q, Qf, R, mu, x_init, beta = 0.95, N_sample = 10, i_th_state = 1, i_state_ub = 0.05, epsilon = 1, 19 | sin_const = 1, N_sim=80, mode = "collect",data_set = None, N_sample_max = None): 20 | 21 | self.Q = Q 22 | self.Qf = Qf 23 | self.R = R 24 | self.mu = mu 25 | self.beta = beta 26 | self.N_sample = N_sample 27 | self.i_th_state = i_th_state 28 | self.i_state_ub = i_state_ub 29 | self.epsilon = epsilon 30 | self.sin_const = sin_const 31 | self.model = model 32 | 33 | if mode is "collect": 34 | self.x_sim, self.u_sim = self.simulation_collect(x_init,N_sim, data_set, N_sample_max) 35 | elif mode is "gene": 36 | self.x_sim, self.u_sim = self.simulation_gene(x_init,N_sim) 37 | 38 | else: 39 | print("Error Mode") 40 | 41 | def simulation_collect(self,x_init,N_sim, data_set, N_sample_max): 42 | Ak = self.model.Ak 43 | Bk = self.model.Bk 44 | C = self.model.C 45 | D = self.model.D 46 | E = self.model.E 47 | N = self.model.N 48 | 49 | Q = self.Q 50 | Qf = self.Qf 51 | R = self.R 52 | mu = self.mu 53 | beta = self.beta 54 | 55 | i_th_state = self.i_th_state 56 | i_state_ub = self.i_state_ub 57 | epsilon = self.epsilon 58 | sin_const = self.sin_const 59 | 60 | delta_t = self.model.delta_t 61 | d = self.model.d 62 | 63 | t0 = 0 64 | xk = x_init 65 | uk = 0 66 | t = t0 67 | h = delta_t 68 | 69 | x_list = [] 70 | x_list += xk.flatten().tolist() 71 | u_list = [] 72 | 73 | N_sample = self.N_sample 74 | # N_sample_max 75 | for i in range(N_sim): 76 | if i % N == 0 and i > 0 and N_sample <= N_sample_max: 77 | N_sample += 1 78 | 79 | self.model.change_xinit(xk) 80 | opt_problem = Opt_problem(self.model, Q, Qf, R, mu, beta=beta, N_sample=N_sample, i_th_state=i_th_state, 81 | i_state_ub=i_state_ub, epsilon=epsilon, sin_const=sin_const, collect = True, data_set = data_set) 82 | # W_sample, W_sample_ext = self.gene_disturbance(N, d, N_sample, sin_const) 83 | # opt_problem.W_sample_matrix.value = W_sample 84 | prob = opt_problem.prob 85 | # print(W_sample_matrix) 86 | # print( prob.solve(verbose=True)) 87 | 88 | prob.solve(solver=cp.MOSEK) 89 | 90 | wk = sin_const * np.sin(np.random.randn(d, 1)) 91 | data_set += [wk] 92 | uk = opt_problem.H_cal_dec.value[0, 0] + opt_problem.H_cal_dec.value[0, 1] * (D @ xk + E @ wk) 93 | u_list += uk.flatten().tolist() 94 | # print("current state and input", xk, uk) 95 | x_kp1 = self.simulation_Euler(Ak, Bk, xk, uk) 96 | x_list += x_kp1.flatten().tolist() 97 | xk = x_kp1 98 | xk += C @ wk 99 | return x_list, u_list 100 | 101 | 102 | def simulation_gene(self, x_init, N_sim): 103 | Ak = self.model.Ak 104 | Bk = self.model.Bk 105 | C = self.model.C 106 | D = self.model.D 107 | E = self.model.E 108 | N = self.model.N 109 | 110 | Q = self.Q 111 | Qf = self.Qf 112 | R = self.R 113 | mu = self.mu 114 | beta = self.beta 115 | N_sample = self.N_sample 116 | i_th_state = self.i_th_state 117 | i_state_ub = self.i_state_ub 118 | epsilon = self.epsilon 119 | sin_const = self.sin_const 120 | 121 | delta_t = self.model.delta_t 122 | d = self.model.d 123 | 124 | t0 = 0 125 | xk = x_init 126 | uk = 0 127 | t = t0 128 | h = delta_t 129 | 130 | x_list = [] 131 | x_list += xk.flatten().tolist() 132 | u_list = [] 133 | 134 | for i in range(N_sim): 135 | # if i % N == 0: 136 | self.model.change_xinit(xk) 137 | 138 | 139 | 140 | opt_problem = Opt_problem(self.model, Q, Qf, R, mu, beta=beta, N_sample=N_sample, i_th_state=i_th_state, 141 | i_state_ub=i_state_ub, epsilon=epsilon, sin_const=sin_const) 142 | # W_sample, W_sample_ext = self.gene_disturbance(N, d, N_sample, sin_const) 143 | # opt_problem.W_sample_matrix.value = W_sample 144 | prob = opt_problem.prob 145 | # print(W_sample_matrix) 146 | # print( prob.solve(verbose=True)) 147 | 148 | prob.solve(solver=cp.MOSEK) 149 | # print("opt value:", prob.value) 150 | # print( prob.solve(verbose=True)) 151 | # prob.solve(solver = cp.MOSEK,verbose = True, mosek_params = {mosek.dparam.basis_tol_s:1e-9, mosek.dparam.ana_sol_infeas_tol:0}) 152 | # print(Ax @ x_init + Bx @ H.value @ W_sample_matrix_ext[:,0:1] + Cx @ W_sample_matrix[:,0:1]) 153 | # print("status:", prob.status) 154 | # print("Controller", opt_problem.H_cal_dec.value[0,0], opt_problem.H_cal_dec.value[0,1]) 155 | # print("dual:", constraint[0].dual_value) 156 | # print("gamma", gamma_matrix[0].value, gamma_matrix[1].value, gamma_matrix[2].value, gamma_matrix[3].value) 157 | # print("lambda",opt_problem.lambda_var.value) 158 | # print("lambda time epsilon",lambda_var.value * epsilon) 159 | # print("si",opt_problem.si_var.value) 160 | # print("si average",np.sum(opt_problem.si_var.value)/N_sample) 161 | # print("state_constraint", np.mean(opt_problem.si_var.value) + opt_problem.lambda_var.value * epsilon) 162 | # print("state",(self.model.Bx @ opt_problem.H_cal_dec.value @ (self.model.Cy_tilde + self.model.Ey_tilde) + self.model.Cx_tilde) @ opt_problem.W_sample_matrix_ext) 163 | # print("disturbance data", W_sample_matrix) 164 | wk = sin_const * np.sin(np.random.randn(d, 1)) 165 | uk = opt_problem.H_cal_dec.value[0, 0] + opt_problem.H_cal_dec.value[0, 1] * (D @ xk + E @ wk) 166 | u_list += uk.flatten().tolist() 167 | # print("current state and input", xk, uk) 168 | x_kp1 = self.simulation_Euler(Ak, Bk, xk, uk) 169 | x_list += x_kp1.flatten().tolist() 170 | xk = x_kp1 171 | xk += C @ wk 172 | return x_list, u_list 173 | 174 | 175 | 176 | def gene_disturbance(self, N, d, N_sample, sin_const): 177 | # Generate data: const * sinx 178 | 179 | w_sample = [] 180 | for i in range(N_sample): 181 | w_temp = sin_const * np.sin(np.random.randn(N*d)) 182 | w_sample += [w_temp] 183 | W_sample_matrix = np.array(w_sample).T 184 | 185 | W_sample_matrix_ext = np.vstack( [np.ones([1, N_sample]),W_sample_matrix]) 186 | return W_sample_matrix, W_sample_matrix_ext 187 | 188 | def simulation_Euler(self, Ak, Bk, x, u): 189 | ''' 190 | Simulate with implicit Euler 191 | x[k+1] = (I - delta_t * A)^{-1} @ x[k] + (I - delta_t * A)^{-1} @ (delta_t * B) @ u[k] 192 | ''' 193 | x_next = Ak @ x + Bk @ u 194 | 195 | return x_next 196 | 197 | def RK4_np(self, f, x, u, t, h): 198 | """ 199 | Runge-Kutta 4th order solver using numpy array data type. 200 | 201 | Args: 202 | f: A function returning first order ODE in 2D numpy array (Nx x 1). 203 | x: Current value (list or numpy array). 204 | t: Current time. 205 | h: Step length. 206 | Returns: 207 | x_next: Vector of next value in 2D numpy array (Nx x 1) 208 | """ 209 | x = np.reshape(x, (np.shape(x)[0], -1)) # Reshape x to col vector in np 2D array 210 | k1 = f(t, x, u) 211 | k2 = f(t + h / 2, x + h / 2 * k1, u) 212 | k3 = f(t + h / 2, x + h / 2 * k2, u) 213 | k4 = f(t + h, x + h * k3, u) 214 | x_next = x + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4) 215 | return x_next 216 | 217 | def plot_state(self): 218 | delta_t = self.model.delta_t 219 | n = self.model.n 220 | 221 | x_traj = self.x_sim 222 | 223 | Nt = np.shape(x_traj[::n])[0] 224 | t_plot = [delta_t * i for i in range(Nt)] 225 | 226 | plt.figure(1, figsize=(10, 20)) 227 | plt.clf() 228 | for i in range (n): 229 | plt.subplot( str(n) + str(1) + str(i + 1) ) 230 | plt.grid() 231 | x_traj_temp = x_traj[i::n] 232 | plt.plot(t_plot, x_traj_temp) 233 | plt.ylabel('x' + str(i + 1)) 234 | 235 | plt.xlabel('t') 236 | plt.show() 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data-driven distributionally robust MPC using the Wasserstein metric 2 | 3 | This project contains the experimental framework for distributionally robust MPC and two case studies, per "Data-driven distributionally robust MPC using the Wasserstein metric" by Zhengang Zhong, Ehecatl Antonio del Rio-Chanona and Panagiotis Petsagkourakis, submitted to IEEE CDC 2021. 4 | 5 | ## Abstract 6 | 7 | Distributionally robust optimization is a technique for decision making under uncertainty where the probability distribution of the uncertain problem is itself subject to uncertainty. A novel data-driven MPC scheme is proposed to control constrained stochastic linear systems using distributionally robust optimization. Distributionally robust constraints based on Wasserstein ball are imposed to bound the expected state constraint violation in the presence of process disturbance. A feedback control law is solved to guarantee that the predicted states comply with constraints with regard to the worst-case distribution within the Wasserstein ball centered at the discrete empirical probability distribution. The resulting distributionally robust MPC framework is tractable and efficient. The effectiveness of the proposed scheme is demonstrated through two numerical case studies. 8 | 9 | ## How to Run Experiments 10 | 11 | ``python inverted_pendulum.py `` or ``python mass_spring.py`` to run the simulation for one realization. 12 | 13 | ``python`` other file_name to execute simulations for multiple realizations. 14 | 15 | 16 | 17 | ## Simulation results 18 | 19 | ### Simulation 1: 50 realizations of the data collection algorithm for a mass spring system initialized with different numbers of samples 20 | 21 |

22 | 23 |

24 | 25 | The shaded area denotes the 25-th to 75-th trajectory distribution. 26 | 27 | ### Simulation 2: Mass spring system averaged from 50 realizations with sample number ranging from 1 to 10 28 | 29 |

30 | 31 |

32 | 33 | ### Simulation 3: Relation between sample number and constraint violations within first four seconds in simulation 2 34 | 35 |

36 | 37 |

38 | 39 | ### Simulation 4: Inverted pendulum system averaged from 10 realizations with Wasserstein ball radius ranging from 0.01 to 100 40 | 41 |

42 | 43 |

44 | 45 | ### Simulation 5: Relation between Wasserstein ball radius and constraint violations within first two seconds in simulation 4 46 | 47 |

48 | 49 |

50 | 51 | -------------------------------------------------------------------------------- /fig/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/.DS_Store -------------------------------------------------------------------------------- /fig/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/fig1.png -------------------------------------------------------------------------------- /fig/fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/fig2.png -------------------------------------------------------------------------------- /fig/fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/fig3.png -------------------------------------------------------------------------------- /fig/fig4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/fig4.png -------------------------------------------------------------------------------- /fig/fig5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OptiMaL-PSE-Lab/Data-driven-distributionally-robust-MPC/1d1d3ff909dd79748ce5f1488662ab76fc8d442c/fig/fig5.png -------------------------------------------------------------------------------- /inverted_pendulum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_optproblem import Opt_problem 4 | from DRO_simulation import Simulation 5 | import mosek 6 | import cvxpy as cp 7 | 8 | 9 | def inverted_pendulum_ode(t, x, u): 10 | M = 1.096 11 | m = 0.109 12 | l = 0.25 13 | b = 0.1 14 | g = 9.81 15 | I = 0.0034 16 | 17 | p = I * (M + m) + M * m * l ** 2 18 | 19 | A = np.array([[0, 1, 0, 0], 20 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 21 | [0, 0, 0, 1], 22 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 23 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 24 | 25 | dot_x = A @ x + B @ u 26 | 27 | return dot_x 28 | 29 | 30 | if __name__ == "__main__": 31 | M = 1.096 32 | m = 0.109 33 | l = 0.25 34 | b = 0.1 35 | g = 9.81 36 | I = 0.0034 37 | 38 | p = I * (M + m) + M * m * l ** 2 39 | 40 | A = np.array([[0, 1, 0, 0], 41 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 42 | [0, 0, 0, 1], 43 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 44 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 45 | 46 | delta_t = 0.1 47 | 48 | # x_init = np.array([[0], [0], [-np.pi/2], [0]]) 49 | x_init = np.array([[0], [0], [-0.5], [0]]) 50 | # C = np.diag([0, 0, 0, 1e-2]) 51 | # C = np.array([[0, 0],[0, 0],[1e-2, 0],[0, 0]]) 52 | C = np.array([[0, 0],[0, 0],[0, 0],[1e-2, 0]]) 53 | D = np.array([[0, 0, 1, 0]]) 54 | # D = np.array([[1, 0, 0, 0], [0,0,1,0]]) 55 | E = np.array([[0, 1e-2]]) 56 | # E = np.array([[0, 0, 1e-2, 0]]) 57 | N = 5 58 | model = Model(A, B, C, D, E, x_init, N, delta_t) 59 | 60 | Q = np.diag([1000, 1, 1500, 1]) 61 | Qf = np.diag([1000, 1, 1500, 1]) 62 | R = np.diag([1]) 63 | 64 | d = model.d 65 | mu = np.zeros([d, 1]) 66 | beta = 0.95 67 | N_sample = 3 68 | i_th_state = 3 69 | i_state_ub = 0.5 70 | epsilon = 1 71 | 72 | sin_const = 3 73 | N_sim = 100 74 | 75 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, 76 | sin_const = sin_const, N_sim=N_sim, mode = "gene") 77 | print(sim.x_sim) 78 | sim.plot_state() -------------------------------------------------------------------------------- /inverted_pendulum_experiment_N=var_eps=1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_simulation import Simulation 4 | import sys 5 | import os 6 | 7 | 8 | 9 | def inverted_pendulum_ode(t, x, u): 10 | M = 1.096 11 | m = 0.109 12 | l = 0.25 13 | b = 0.1 14 | g = 9.81 15 | I = 0.0034 16 | 17 | p = I * (M + m) + M * m * l ** 2 18 | 19 | A = np.array([[0, 1, 0, 0], 20 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 21 | [0, 0, 0, 1], 22 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 23 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 24 | 25 | dot_x = A @ x + B @ u 26 | 27 | return dot_x 28 | 29 | 30 | if __name__ == "__main__": 31 | file_name =(os.path.splitext(sys.argv[0])[0]).split("/")[-1] 32 | 33 | M = 1.096 34 | m = 0.109 35 | l = 0.25 36 | b = 0.1 37 | g = 9.81 38 | I = 0.0034 39 | 40 | p = I * (M + m) + M * m * l ** 2 41 | 42 | A = np.array([[0, 1, 0, 0], 43 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 44 | [0, 0, 0, 1], 45 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 46 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 47 | 48 | delta_t = 0.1 49 | 50 | # x_init = np.array([[0], [0], [-np.pi/2], [0]]) 51 | x_init = np.array([[0], [0], [-0.5], [0]]) 52 | # C = np.diag([0, 0, 0, 1e-2]) 53 | C = np.array([[0, 0],[0, 0],[0, 0],[1e-2, 0]]) 54 | D = np.array([[0, 0, 1, 0]]) 55 | # D = np.array([[1, 0, 0, 0], [0,0,1,0]]) 56 | E = np.array([[0, 1e-2]]) 57 | # E = np.array([[0, 0, 1e-2, 0]]) 58 | N = 5 59 | model = Model(A, B, C, D, E, x_init, N, delta_t) 60 | 61 | Q = np.diag([1000, 1, 1500, 1]) 62 | Qf = np.diag([1000, 1, 1500, 1]) 63 | R = np.diag([1]) 64 | 65 | d = model.d 66 | mu = np.zeros([d, 1]) 67 | beta = 0.95 68 | N_sample = 1 69 | i_th_state = 3 70 | i_state_ub = 0.5 71 | epsilon = 1 72 | sin_const = 3 73 | N_sim = 100 74 | N_loop = 10 75 | 76 | # Test2: fix esp = 1, sin_const = 3, beta = 0.95, i_ub = 0.5, N_loop = 1. 77 | # change N_sample 78 | # N_sample_test = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 79 | N_sample_test = [3, 4, 5, 6, 7, 8, 9, 10] 80 | for N_sample_temp in N_sample_test: 81 | result_x = [] 82 | result_u = [] 83 | N_sample = N_sample_temp 84 | for i in range(N_loop): 85 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, 86 | i_state_ub = i_state_ub, epsilon = epsilon,sin_const = sin_const, N_sim=N_sim, mode = "gene") 87 | result_x += [sim.x_sim] 88 | result_u += [sim.u_sim] 89 | print("#" +str(i) + " sim of " + str(N_sample) + " is done") 90 | 91 | N_sample_str = str(N_sample) 92 | 93 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + N_sample_str+ "_" + "x_tra" + ".txt" 94 | with open(write_path, 'w') as f: 95 | for listitem in result_x: 96 | f.write('%s\n' % listitem) 97 | f.close() 98 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + N_sample_str + "_" + "u_tra" +".txt" 99 | with open(write_path, 'w') as f: 100 | for listitem in result_u: 101 | f.write('%s\n' % listitem) 102 | f.close() -------------------------------------------------------------------------------- /inverted_pendulum_experiment_eps=var_N_sample=1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_simulation import Simulation 4 | import sys 5 | import os 6 | 7 | 8 | 9 | def inverted_pendulum_ode(t, x, u): 10 | M = 1.096 11 | m = 0.109 12 | l = 0.25 13 | b = 0.1 14 | g = 9.81 15 | I = 0.0034 16 | 17 | p = I * (M + m) + M * m * l ** 2 18 | 19 | A = np.array([[0, 1, 0, 0], 20 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 21 | [0, 0, 0, 1], 22 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 23 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 24 | 25 | dot_x = A @ x + B @ u 26 | 27 | return dot_x 28 | 29 | 30 | if __name__ == "__main__": 31 | file_name =(os.path.splitext(sys.argv[0])[0]).split("/")[-1] 32 | 33 | M = 1.096 34 | m = 0.109 35 | l = 0.25 36 | b = 0.1 37 | g = 9.81 38 | I = 0.0034 39 | 40 | p = I * (M + m) + M * m * l ** 2 41 | 42 | A = np.array([[0, 1, 0, 0], 43 | [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0], 44 | [0, 0, 0, 1], 45 | [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]]) 46 | B = np.array([[0], [(I+m*l**2)/p], [0], [m*l/p]]) 47 | 48 | delta_t = 0.1 49 | 50 | # x_init = np.array([[0], [0], [-np.pi/2], [0]]) 51 | x_init = np.array([[0], [0], [-0.5], [0]]) 52 | # C = np.diag([0, 0, 0, 1e-2]) 53 | C = np.array([[0, 0],[0, 0],[0, 0],[1e-2, 0]]) 54 | D = np.array([[0, 0, 1, 0]]) 55 | # D = np.array([[1, 0, 0, 0], [0,0,1,0]]) 56 | E = np.array([[0, 1e-2]]) 57 | # E = np.array([[0, 0, 1e-2, 0]]) 58 | N = 5 59 | model = Model(A, B, C, D, E, x_init, N, delta_t) 60 | 61 | Q = np.diag([1000, 1, 1500, 1]) 62 | Qf = np.diag([1000, 1, 1500, 1]) 63 | R = np.diag([1]) 64 | 65 | d = model.d 66 | mu = np.zeros([d, 1]) 67 | beta = 0.95 68 | N_sample = 1 69 | i_th_state = 3 70 | i_state_ub = 0.5 71 | epsilon = 1 72 | sin_const = 3 73 | N_sim = 100 74 | N_loop = 10 75 | # Test1: fix N_sample = 1, sin_const = 3, beta = 0.95, i_ub = 0.4, N_loop = 1. 76 | # change epsilon 77 | eps_test = [1e-2, 1e-1, 1, 3, 5, 10, 100] 78 | for eps_temp in eps_test: 79 | result_x = [] 80 | result_u = [] 81 | epsilon = eps_temp 82 | for i in range(N_loop): 83 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, 84 | i_state_ub = i_state_ub, epsilon = epsilon,sin_const = sin_const, N_sim=N_sim, mode = "gene") 85 | result_x += [sim.x_sim] 86 | result_u += [sim.u_sim] 87 | print("#" +str(i) + " sim of " + str(epsilon) + " is done") 88 | 89 | eps_str = str(epsilon) 90 | if epsilon < 1: 91 | eps_str = str(epsilon).split(".")[0] + str(epsilon).split(".")[1] # 0.01 -> "001" 92 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + eps_str+ "_" + "x_tra" + ".txt" 93 | with open(write_path, 'w') as f: 94 | for listitem in result_x: 95 | f.write('%s\n' % listitem) 96 | f.close() 97 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + eps_str+ "_" + "u_tra" + ".txt" 98 | with open(write_path, 'w') as f: 99 | for listitem in result_u: 100 | f.write('%s\n' % listitem) 101 | f.close() -------------------------------------------------------------------------------- /mass_spring.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_optproblem import Opt_problem 4 | from DRO_simulation import Simulation 5 | import mosek 6 | import cvxpy as cp 7 | 8 | 9 | def mass_string_ode(t, x, u): 10 | m = 2 # [kg] 11 | k1 = 3 # [N/m] 12 | k2 = 2 # [N/m] 13 | 14 | A = np.array([[0, 1], [-k2 / m, -k1 / m]]) 15 | B = np.array([[0], [1 / m]]) 16 | 17 | dot_x = A @ x + B @ u 18 | 19 | return dot_x 20 | 21 | def gene_sample(N, d, N_sample, sin_const): 22 | # Generate data: const * sinx 23 | 24 | w_sample = [] 25 | for i in range(N_sample * N): 26 | w_temp = sin_const * np.sin(np.random.randn(d, 1)) 27 | w_sample += [w_temp] 28 | return w_sample 29 | 30 | if __name__ == "__main__": 31 | 32 | m = 2 #[kg] 33 | k1 = 3 # [N/m] 34 | k2 = 2 # [N/m] 35 | 36 | A = np.array([[0,1],[-k2/m, -k1/m]]) 37 | B = np.array([[0],[1/m]]) 38 | delta_t = 0.1 39 | 40 | x_init = np.array([[-2],[0]]) 41 | 42 | 43 | Ck = np.array([[1e-3, 0],[0, 0]]) 44 | D = np.array([[1, 0]]) 45 | E = np.array([[0,1e-3]]) 46 | N = 5 47 | 48 | model = Model(A, B, Ck, D, E, x_init, N, delta_t) 49 | 50 | 51 | Q = np.diag([10, 1]) 52 | Qf = np.diag([15, 1]) 53 | R = np.diag([1]) 54 | 55 | d = model.d 56 | mu = np.zeros([d, 1]) 57 | mu_w = np.vstack([1] + [mu] * N) 58 | M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 59 | beta = 0.95 60 | N_sample = 1 61 | i_th_state = 1 62 | i_state_ub = 0.5 63 | epsilon = 1 64 | sin_const = 2 65 | N_sim = 60 66 | 67 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, 68 | sin_const = sin_const, N_sim=N_sim, mode = "gene") 69 | print(sim.x_sim) 70 | 71 | 72 | # data_set = gene_sample(N, d, N_sample, sin_const) 73 | # N_sample_max = 10 74 | # sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, 75 | # sin_const = sin_const, N_sim=N_sim, mode = "collect", data_set = data_set, N_sample_max = N_sample_max) 76 | # print(sim.x_sim) 77 | # sim.plot_state() 78 | 79 | # opt_problem = Opt_problem(model, Q, Qf, R, mu, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, sin_const = sin_const) 80 | # W_sample, W_sample_ext =gene_disturbance(N, d, N_sample, sin_const) 81 | # opt_problem.W_sample_matrix.value = W_sample 82 | # prob = opt_problem.prob 83 | # prob.solve(solver = cp.MOSEK) 84 | # H_cal_dec = opt_problem.H_cal_dec 85 | # print(H_cal_dec.value) -------------------------------------------------------------------------------- /mass_spring_collect_data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_simulation import Simulation 4 | import sys 5 | import os 6 | 7 | 8 | def mass_string_ode(t, x, u): 9 | m = 2 # [kg] 10 | k1 = 3 # [N/m] 11 | k2 = 2 # [N/m] 12 | 13 | A = np.array([[0, 1], [-k2 / m, -k1 / m]]) 14 | B = np.array([[0], [1 / m]]) 15 | 16 | dot_x = A @ x + B @ u 17 | 18 | return dot_x 19 | 20 | def gene_sample(N, d, N_sample, sin_const): 21 | # Generate data: const * sinx 22 | 23 | w_sample = [] 24 | for i in range(N_sample * N): 25 | w_temp = sin_const * np.sin(np.random.randn(d, 1)) 26 | w_sample += [w_temp] 27 | return w_sample 28 | 29 | if __name__ == "__main__": 30 | file_name =(os.path.splitext(sys.argv[0])[0]).split("/")[-1] 31 | 32 | m = 2 #[kg] 33 | k1 = 3 # [N/m] 34 | k2 = 2 # [N/m] 35 | 36 | A = np.array([[0,1],[-k2/m, -k1/m]]) 37 | B = np.array([[0],[1/m]]) 38 | delta_t = 0.1 39 | 40 | x_init = np.array([[-2],[0]]) 41 | 42 | 43 | Ck = np.array([[1e-3, 0],[0, 0]]) 44 | D = np.array([[1, 0]]) 45 | E = np.array([[0,1e-3]]) 46 | N = 5 47 | 48 | model = Model(A, B, Ck, D, E, x_init, N, delta_t) 49 | 50 | 51 | Q = np.diag([10, 1]) 52 | Qf = np.diag([15, 1]) 53 | R = np.diag([1]) 54 | 55 | d = model.d 56 | mu = np.zeros([d, 1]) 57 | mu_w = np.vstack([1] + [mu] * N) 58 | M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 59 | beta = 0.95 60 | N_sample = 5 61 | i_th_state = 1 62 | i_state_ub = 0.4 63 | epsilon = 1 64 | sin_const = 3 65 | N_sim = 70 66 | N_loop = 50 # 50 67 | N_sample_max = 10 68 | 69 | 70 | data_set_init = gene_sample(N, d, N_sample, sin_const) 71 | 72 | 73 | 74 | # Test3: fix N_sample = 1, sin_const = 3, beta = 0.95, i_ub = 0.4, N_loop = 10, eps = 1 75 | # sample 100 trajectories with collected data 76 | result_x = [] 77 | result_u = [] 78 | for i in range(N_loop): 79 | data_set = data_set_init 80 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta=beta, N_sample=N_sample, i_th_state=i_th_state, 81 | i_state_ub=i_state_ub, epsilon=epsilon, 82 | sin_const=sin_const, N_sim=N_sim, mode="collect", data_set=data_set, N_sample_max=N_sample_max) 83 | result_x += [sim.x_sim] 84 | result_u += [sim.u_sim] 85 | print("#" +str(i) + " sim " + "is done") 86 | 87 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "_" + "x_tra" + "_N_sample=" + str(N_sample) + ".txt" 88 | with open(write_path, 'w') as f: 89 | for listitem in result_x: 90 | f.write('%s\n' % listitem) 91 | f.close() 92 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "_" + "u_tra" + "_N_sample=" + str(N_sample) + ".txt" 93 | with open(write_path, 'w') as f: 94 | for listitem in result_u: 95 | f.write('%s\n' % listitem) 96 | f.close() 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /mass_spring_experiment_N=var_eps=1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_simulation import Simulation 4 | import sys 5 | import os 6 | 7 | def mass_string_ode(t, x, u): 8 | m = 2 # [kg] 9 | k1 = 3 # [N/m] 10 | k2 = 2 # [N/m] 11 | 12 | A = np.array([[0, 1], [-k2 / m, -k1 / m]]) 13 | B = np.array([[0], [1 / m]]) 14 | 15 | dot_x = A @ x + B @ u 16 | 17 | return dot_x 18 | 19 | 20 | if __name__ == "__main__": 21 | 22 | file_name =(os.path.splitext(sys.argv[0])[0]).split("/")[-1] 23 | m = 2 #[kg] 24 | k1 = 3 # [N/m] 25 | k2 = 2 # [N/m] 26 | 27 | A = np.array([[0,1],[-k2/m, -k1/m]]) 28 | B = np.array([[0],[1/m]]) 29 | delta_t = 0.1 30 | 31 | x_init = np.array([[-2],[0]]) 32 | 33 | 34 | Ck = np.array([[1e-3, 0],[0, 0]]) 35 | D = np.array([[1, 0]]) 36 | E = np.array([[0,1e-3]]) 37 | N = 5 38 | 39 | model = Model(A, B, Ck, D, E, x_init, N, delta_t) 40 | 41 | 42 | # Q = np.diag([10, 1]) 43 | # Qf = np.diag([15, 1]) 44 | # R = np.diag([1]) 45 | # 46 | # d = model.d 47 | # mu = np.zeros([d, 1]) 48 | # mu_w = np.vstack([1] + [mu] * N) 49 | # M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 50 | # beta = 0.95 51 | # N_sample = 3 52 | # i_th_state = 1 53 | # i_state_ub = 0.4 54 | # epsilon = 1 55 | # sin_const = 3 56 | # N_sim = 60 57 | 58 | # sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, 59 | # sin_const = sin_const, N_sim=N_sim, mode = "gene") 60 | # print(sim.x_sim) 61 | 62 | Q = np.diag([10, 1]) 63 | Qf = np.diag([15, 1]) 64 | R = np.diag([1]) 65 | 66 | d = model.d 67 | mu = np.zeros([d, 1]) 68 | mu_w = np.vstack([1] + [mu] * N) 69 | M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 70 | beta = 0.95 71 | N_sample = 1 72 | i_th_state = 1 73 | i_state_ub = 0.4 74 | epsilon = 1 75 | sin_const = 3 76 | N_sim = 75 77 | N_loop = 100 78 | # Test2: fix esp = 1, sin_const = 3, beta = 0.95, i_ub = 0.4, N_loop = 1. 79 | # change N_sample 80 | N_sample_test = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 81 | for N_sample_temp in N_sample_test: 82 | result_x = [] 83 | result_u = [] 84 | N_sample = N_sample_temp 85 | for i in range(N_loop): 86 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, 87 | i_state_ub = i_state_ub, epsilon = epsilon,sin_const = sin_const, N_sim=N_sim, mode = "gene") 88 | result_x += [sim.x_sim] 89 | result_u += [sim.u_sim] 90 | print("#" +str(i) + " sim of " + str(N_sample) + " is done") 91 | 92 | N_sample_str = str(N_sample) 93 | 94 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + N_sample_str+ "_" + "x_tra" + ".txt" 95 | with open(write_path, 'w') as f: 96 | for listitem in result_x: 97 | f.write('%s\n' % listitem) 98 | f.close() 99 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "/" + N_sample_str + "_" + "u_tra" +".txt" 100 | with open(write_path, 'w') as f: 101 | for listitem in result_u: 102 | f.write('%s\n' % listitem) 103 | f.close() -------------------------------------------------------------------------------- /mass_spring_experiment_eps=var_N_sample=1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DRO_model import Model 3 | from DRO_simulation import Simulation 4 | import sys 5 | import os 6 | 7 | def mass_string_ode(t, x, u): 8 | m = 2 # [kg] 9 | k1 = 3 # [N/m] 10 | k2 = 2 # [N/m] 11 | 12 | A = np.array([[0, 1], [-k2 / m, -k1 / m]]) 13 | B = np.array([[0], [1 / m]]) 14 | 15 | dot_x = A @ x + B @ u 16 | 17 | return dot_x 18 | 19 | 20 | if __name__ == "__main__": 21 | 22 | file_name =(os.path.splitext(sys.argv[0])[0]).split("/")[-1] 23 | m = 2 #[kg] 24 | k1 = 3 # [N/m] 25 | k2 = 2 # [N/m] 26 | 27 | A = np.array([[0,1],[-k2/m, -k1/m]]) 28 | B = np.array([[0],[1/m]]) 29 | delta_t = 0.1 30 | 31 | x_init = np.array([[-2],[0]]) 32 | 33 | 34 | Ck = np.array([[1e-3, 0],[0, 0]]) 35 | D = np.array([[1, 0]]) 36 | E = np.array([[0,1e-3]]) 37 | N = 5 38 | 39 | model = Model(A, B, Ck, D, E, x_init, N, delta_t) 40 | 41 | 42 | # Q = np.diag([10, 1]) 43 | # Qf = np.diag([15, 1]) 44 | # R = np.diag([1]) 45 | # 46 | # d = model.d 47 | # mu = np.zeros([d, 1]) 48 | # mu_w = np.vstack([1] + [mu] * N) 49 | # M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 50 | # beta = 0.95 51 | # N_sample = 3 52 | # i_th_state = 1 53 | # i_state_ub = 0.4 54 | # epsilon = 1 55 | # sin_const = 3 56 | # N_sim = 60 57 | 58 | # sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, i_state_ub = i_state_ub, epsilon = epsilon, 59 | # sin_const = sin_const, N_sim=N_sim, mode = "gene") 60 | # print(sim.x_sim) 61 | 62 | Q = np.diag([10, 1]) 63 | Qf = np.diag([15, 1]) 64 | R = np.diag([1]) 65 | 66 | d = model.d 67 | mu = np.zeros([d, 1]) 68 | mu_w = np.vstack([1] + [mu] * N) 69 | M_w = mu_w @ mu_w.T + np.diag([0] + [1] * N * d) 70 | beta = 0.95 71 | N_sample = 1 72 | i_th_state = 1 73 | i_state_ub = 0.4 74 | epsilon = 1 75 | sin_const = 3 76 | N_sim = 70 77 | N_loop = 10 78 | # Test1: fix N_sample = 1, sin_const = 3, beta = 0.95, i_ub = 0.4, N_loop = 10. 79 | # change epsilon 80 | eps_test = [1e-2, 1e-1, 1, 3, 5, 10, 100] 81 | for eps_temp in eps_test: 82 | result_x = [] 83 | result_u = [] 84 | epsilon = eps_temp 85 | for i in range(N_loop): 86 | sim = Simulation(model, Q, Qf, R, mu, x_init, beta = beta, N_sample = N_sample, i_th_state = i_th_state, 87 | i_state_ub = i_state_ub, epsilon = epsilon,sin_const = sin_const, N_sim=N_sim, mode = "gene") 88 | result_x += [sim.x_sim] 89 | result_u += [sim.u_sim] 90 | print("#" +str(i) + " sim of " + str(epsilon) + " is done") 91 | 92 | eps_str = str(epsilon) 93 | if epsilon < 1: 94 | eps_str = str(epsilon).split(".")[0] + str(epsilon).split(".")[1] # 0.01 -> "001" 95 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "_" + eps_str+ "_" + "x_tra" + ".txt" 96 | with open(write_path, 'w') as f: 97 | for listitem in result_x: 98 | f.write('%s\n' % listitem) 99 | f.close() 100 | write_path = "/Users/zhengangzhong/Dropbox/PhD/documents/paper_writing/CDC2021/result/" + file_name + "_" + eps_str + "_" + "u_tra" +".txt" 101 | with open(write_path, 'w') as f: 102 | for listitem in result_u: 103 | f.write('%s\n' % listitem) 104 | f.close() --------------------------------------------------------------------------------