def setUp(self): """Run repeated setup code once.""" # Define class and local parameters unittest.TestCase.setUp(self) self.K_list = [3, 10, 99.21] self.tau_list = [0.3, 1] self.delay_list = [0.03, 0.1, 0.3] self.t_step = 0.01 self.stop_time = 20.0 self.input_val = 1.0 self.t_step_ctrl = 0.1 self.zeta = 0.7054 # air MFC damping ratio self.wn = 1.3501 # air MFC natural frequency, rad/s self.td = 1.8 # air MFC time delay, sec self.y0_1 = [0] self.y0_2 = [0] * 3 # for first-order model self.y0_3 = [0] * 4 # for second-order model A, B, self.C = sim_functions.get_state_matrices( self.K_list[1], self.tau_list[-1], self.delay_list[0]) A2, B2, self.C2 = sim_functions.get_second_ord_matrices( self.K_list[-1], self.zeta, self.wn, self.td) self.Kp_list = [4.0, 5.0] self.mass_flow_des = [4.0, 2.0] self.std = 1.0 self.Ks = 2.0 # filter static gain (?) Q = 1e-5 * np.identity(A2.shape[0]) # process noise covariance R = self.std # measurement noise covariance self.P = Q # initial estimate of error covariance self.offset = 0.0 mean = 0.0 # Define objects self.test_mfc1 = lab_hardware.MFC( lambda t, y, u: test_ode(t, y, u, self.K_list[1], self.tau_list[-1] ), lambda x: x[0], self.y0_1) self.test_mfc2 = lab_hardware.MFC( lambda t, y, u: sim_functions.system_state_update(t, y, u, A, B), lambda y: sim_functions.system_output(y, self.C), self.y0_2) self.test_mfc3 = lab_hardware.MFC( lambda t, y, u: sim_functions.system_state_update(t, y, u, A2, B2), lambda y: sim_functions.system_output(y, self.C2), self.y0_3) self.mfc_list = [ lab_hardware.MFC(lambda t, y, u: test_ode(t, y, u, K, tau), lambda x: x[0], self.y0_1) for K, tau in zip(self.K_list, self.tau_list) ] self.test_KF = lab_hardware.KalmanFilter(A2, B2, self.Ks * self.C2, Q, R, self.P) self.test_sensor = lab_hardware.StaticSensor( lambda y: sim_functions.static_model(y, self.Ks, self.offset, mean, self.std), 1.0)
def run_simulation(): """Run a simulation of the controller.""" # Define system parameters p_atm = 14.0 # psi t_step = 0.01 # simulation time step, seconds t_step_ctrl = 0.1 # controller update rate, seconds K = 0.1 # static pressure sensor gain, V/psi (?) offset = 1.0 # static pressure sensor offset, V (?) K_mfcs = [99.21, 7.968, 8.007] # MFC gains: [air, pilot, outer], SLPM/V zeta_mfcs = [0.7054, 0.6, 0.6] # MFC damping ratios: [air, pilot, outer] wn_mfcs = [1.3501, 1, 1] # MFC natural frequencies: [air, pilot, outer], rad/sec td_mfcs = [1.8, 0.139, 0.306] # MFC time delays: [air, pilot, outer], sec K_mid = 7.964 # middle fuel MFC static gain, SLPM/V tau_mid = 0.516 # middle fuel MFC time constant, sec (first-order) td_mid = 0.194 # middle fuel MFC time delay, sec y0 = [0.0, 0.0, 0.0, 0.0] # initial value, 2nd order sys with 3,2 order Pade approximation of time delay mfc_list = [] control_law_list = [] Q = np.ndarray([[100, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0] [0, 0, 0, 1]]) R = np.ndarray([[1]]) sensor_locations = [43.81, 76.2, 236.728, 609.6] # dynamic P sensor locations from base, mm # Initialize sensor list sensor_list = [lab_hardware.StaticSensor(model=lambda y: sim_functions.static_model(y, K, offset), location=0.0)] for loc in sensor_locations: sensor_list.append(lab_hardware.DynamicSensor(model=tf1, location=loc)) # TODO figure out sensor transfer functions or models! # Initialize mass flow controller (MFC) list and control law list for K, zeta, wn, td in zip(K_mfcs, zeta_mfcs, wn_mfcs, td_mfcs): A, B, C = sim_functions.get_second_ord_matrices(K, zeta, wn, td) K_lqr = sim_functions.make_lqr_law(A, B, Q, R) mfc_list.append(lab_hardware.MFC(lambda t, y, u: sim_functions.system_state_update(t, y, u, A, B), lambda y: sim_functions.system_output(y, C), y0)) control_law_list.append(lambda e:-K_lqr.dot(e)) # Insert our first-order-modeled middle fuel MFC A, B, C = sim_functions.get_state_matrices(K_mid, tau_mid, td_mid) K_lqr = sim_functions.make_lqr_law(A, B, Q[:2, :2], R) # smaller Q matrix, only 3 states mfc_list.insert(2, lab_hardware.MFC(lambda t, y, u: sim_functions.system_state_update(t, y, u, A, B), lambda y: sim_functions.system_output(y, C), y0[:2])) # shorter y0, only 3 states control_law_list.insert(2, lambda e:-K_lqr.dot(e)) # mfc and control law list should be in order: [air, pilot, middle, outer] # Initialize the combustor object combustor = lab_hardware.Combustor(p_atm, t_step, t_step_ctrl, sensor_list, mfc_list, control_law_list)
def setUp(self): """Run repeated setup code once.""" # Define class and local parameters unittest.TestCase.setUp(self) self.K_list = [3, 10, 99.21] self.tau_list = [0.3, 1] self.delay_list = [0.03, 0.1, 0.3] self.t_step = 0.01 self.stop_time = 20.0 self.input_val = 1.0 self.t_step_ctrl = 0.1 self.zeta = 0.7054 # air MFC damping ratio self.wn = 1.3501 # air MFC natural frequency, rad/s self.td = 1.8 # air MFC time delay, sec self.y0_1 = [0] self.y0_2 = [0] * 3 # for first-order model self.y0_3 = [0] * 4 # for second-order model A, B, self.C = sim_functions.get_state_matrices(self.K_list[1], self.tau_list[-1], self.delay_list[0]) A2, B2, self.C2 = sim_functions.get_second_ord_matrices(self.K_list[-1], self.zeta, self.wn, self.td) self.Kp_list = [4.0, 5.0] self.mass_flow_des = [4.0, 2.0] self.std = 1.0 self.Ks = 2.0 # filter static gain (?) Q = 1e-5 * np.identity(A2.shape[0]) # process noise covariance R = self.std # measurement noise covariance self.P = Q # initial estimate of error covariance self.offset = 0.0 mean = 0.0 # Define objects self.test_mfc1 = lab_hardware.MFC(lambda t, y, u: test_ode(t, y, u, self.K_list[1], self.tau_list[-1]), lambda x: x[0], self.y0_1) self.test_mfc2 = lab_hardware.MFC(lambda t, y, u: sim_functions.system_state_update(t, y, u, A, B), lambda y: sim_functions.system_output(y, self.C), self.y0_2) self.test_mfc3 = lab_hardware.MFC(lambda t, y, u: sim_functions.system_state_update(t, y, u, A2, B2), lambda y: sim_functions.system_output(y, self.C2), self.y0_3) self.mfc_list = [lab_hardware.MFC(lambda t, y, u: test_ode(t, y, u, K, tau), lambda x: x[0], self.y0_1) for K, tau in zip(self.K_list, self.tau_list)] self.test_KF = lab_hardware.KalmanFilter(A2, B2, self.Ks * self.C2, Q, R, self.P) self.test_sensor = lab_hardware.StaticSensor(lambda y: sim_functions.static_model(y, self.Ks, self.offset, mean, self.std), 1.0)
def test_first_order_output(self): """Tests for system_output function for first order ODE.""" # Define constants y_list = itertools.combinations_with_replacement([-1, 0, 1], 3) bad_y_list = [0, 1, -1, [1, 0], [0, 0], [1, 1, 1, 1]] K_list = [1, 3, 10] delay_list = [0, 0.1, 0.3, 1] mean_list = [0, 1, -1] std_list = [0.01, 0.03, 0.1, 0.3] param_list = set(itertools.chain(*[zip(K_list, x) for x in itertools.permutations(delay_list, len(K_list))])) noise_list = set(itertools.chain(*[zip(mean_list, x) for x in itertools.permutations(std_list, len(mean_list))])) # Test response of the output function for K, delay in param_list: C = np.array([[K, -K * delay / 2, K * delay ** 2 / 12]]) for y in y_list: self.assertEqual(sim_functions.system_output(np.array(y), C)[0], y[0] * K - y[1] * K * delay / 2 + y[2] * K * delay ** 2 / 12, "Unexpected output function response for y={}, K={}, delay={}".format(y, K, delay)) for mean, std in noise_list: # test response with noise self.assertAlmostEqual(sim_functions.system_output(np.array(y), C, mean, std), y[0] * K - y[1] * K * delay / 2 + y[2] * K * delay ** 2 / 12 + mean, delta=4 * std, msg="Output function response not within 4 std of expected.\nNote this may not be an error since ~0.01% of values lie outside of this range.") # Test that function raises TypeError for inputs of wrong dimension with self.assertRaises(TypeError): for y in bad_y_list: sim_functions.system_output(np.array(y), np.array([[1]])) # Test that non-ndarray inputs raise AttributeError with self.assertRaises(AttributeError): for y in bad_y_list: sim_functions.system_output(y, np.array([[1]])) sim_functions.system_output(np.array(y), 1) sim_functions.system_output(np.array(y), [1, 1])