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 test_make_lqr_law(self): """Tests for make_lqr_law function.""" # Define constants tau_list = [0.3, 1, 3] delay_list = [0.1, 0.3] param_list = set(itertools.chain(*[zip(tau_list, x) for x in itertools.permutations(delay_list, len(tau_list))])) Q = np.array([[100, 0, 0], [0, 1, 0], [0, 0, 1]]) # state error weight R = np.array([[1]]) # control effort weight for tau, delay in param_list: den = tau * delay ** 2 A = np.array([[0, 1, 0], [0, 0, 1], [-12 / den, -(6 * delay + 12 * tau) / den, -(6 * tau + delay) / tau / delay]]) B = np.array([[0], [0], [12 / den]]) # Create control law K_lqr, _, eig_vals = sim_functions.make_lqr_law(A, B, Q, R) print(eig_vals) # Test return type self.assertIsInstance(K_lqr, np.ndarray, "LQR law not returned as ndarray.") # Test return dimensions self.assertTupleEqual(K_lqr.shape, (1, 3), "LQR law not the correct shape: {}, expected (1, 3).".format(K_lqr.shape)) # Test that controlled system is stable self.assertTrue(all([np.real(item) < 0 for item in eig_vals]), "All LQR-controlled poles not in left-half plane.")