def test_nonlinear_iosys(self): # Create a simple nonlinear I/O system nlsys = ios.NonlinearIOSystem(predprey) T = self.T # Start by simulating from an equilibrium point X0 = [0, 0] ios_t, ios_y = ios.input_output_response(nlsys, T, 0, X0) np.testing.assert_array_almost_equal(ios_y, np.zeros(np.shape(ios_y))) # Now simulate from a nonzero point X0 = [0.5, 0.5] ios_t, ios_y = ios.input_output_response(nlsys, T, 0, X0) # # Simulate a linear function as a nonlinear function and compare # # Create a single input/single output linear system linsys = self.siso_linsys # Create a nonlinear system with the same dynamics nlupd = lambda t, x, u, params: \ np.reshape(linsys.A * np.reshape(x, (-1, 1)) + linsys.B * u, (-1,)) nlout = lambda t, x, u, params: \ np.reshape(linsys.C * np.reshape(x, (-1, 1)) + linsys.D * u, (-1,)) nlsys = ios.NonlinearIOSystem(nlupd, nlout) # Make sure that simulations also line up T, U, X0 = self.T, self.U, self.X0 lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0) ios_t, ios_y = ios.input_output_response(nlsys, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
def test_discrete(self): """Test discrete time functionality""" # Create some linear and nonlinear systems to play with linsys = ct.StateSpace( [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[0]], True) lnios = ios.LinearIOSystem(linsys) # Set up parameters for simulation T, U, X0 = self.T, self.U, self.X0 # Simulate and compare to LTI output ios_t, ios_y = ios.input_output_response(lnios, T, U, X0) lin_t, lin_y, lin_x = ct.forced_response(linsys, T, U, X0) np.testing.assert_array_almost_equal(ios_t, lin_t, decimal=3) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Test MIMO system, converted to discrete time linsys = ct.StateSpace(self.mimo_linsys1) linsys.dt = self.T[1] - self.T[0] lnios = ios.LinearIOSystem(linsys) # Set up parameters for simulation T = self.T U = [np.sin(T), np.cos(T)] X0 = 0 # Simulate and compare to LTI output ios_t, ios_y = ios.input_output_response(lnios, T, U, X0) lin_t, lin_y, lin_x = ct.forced_response(linsys, T, U, X0) np.testing.assert_array_almost_equal(ios_t, lin_t, decimal=3) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3)
def test_connect(self): # Define a couple of (linear) systems to interconnection linsys1 = self.siso_linsys iosys1 = ios.LinearIOSystem(linsys1) linsys2 = self.siso_linsys iosys2 = ios.LinearIOSystem(linsys2) # Connect systems in different ways and compare to StateSpace linsys_series = linsys2 * linsys1 iosys_series = ios.InterconnectedSystem( (iosys1, iosys2), # systems ((1, 0),), # interconnection (series) 0, # input = first system 1 # output = second system ) # Run a simulation and compare to linear response T, U = self.T, self.U X0 = np.concatenate((self.X0, self.X0)) ios_t, ios_y, ios_x = ios.input_output_response( iosys_series, T, U, X0, return_x=True) lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3) # Connect systems with different timebases linsys2c = self.siso_linsys linsys2c.dt = 0 # Reset the timebase iosys2c = ios.LinearIOSystem(linsys2c) iosys_series = ios.InterconnectedSystem( (iosys1, iosys2c), # systems ((1, 0),), # interconnection (series) 0, # input = first system 1 # output = second system ) self.assertTrue(ct.isctime(iosys_series, strict=True)) ios_t, ios_y, ios_x = ios.input_output_response( iosys_series, T, U, X0, return_x=True) lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3) # Feedback interconnection linsys_feedback = ct.feedback(linsys1, linsys2) iosys_feedback = ios.InterconnectedSystem( (iosys1, iosys2), # systems ((1, 0), # input of sys2 = output of sys1 (0, (1, 0, -1))), # input of sys1 = -output of sys2 0, # input = first system 0 # output = first system ) ios_t, ios_y, ios_x = ios.input_output_response( iosys_feedback, T, U, X0, return_x=True) lti_t, lti_y, lti_x = ct.forced_response(linsys_feedback, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
def test_interconnected_system(): control_input = ControlInput() control_input.throttle = 0.5 wind_model = no_wind() state = State() state.vx = 20 prop = IcedSkywalkerX8Properties(control_input) outputs = [ "x", "y", "z", "roll", "pitch", "yaw", "vx", "vy", "vz", "ang_rate_x", "ang_rate_y", "ang_rate_z" ] aircraft_model = build_nonlin_sys(prop, wind_model, outputs) initial_control_input_state = ControlInput() initial_control_input_state.throttle = 0.4 motor_time_constant = 0.001 elevon_time_constant = 0.001 actuator_model = build_flying_wing_actuator_system(elevon_time_constant, motor_time_constant) x0 = np.concatenate( (initial_control_input_state.control_input, state.state)) connected_system = add_actuator(actuator_model, aircraft_model) t = np.linspace(0.0, 0.5, 10, endpoint=True) u = np.array([ control_input.control_input, ] * len(t)).transpose() T, yout_without_actuator = input_output_response(aircraft_model, t, U=u, X0=state.state) T, yout_with_actuator = input_output_response(connected_system, t, U=u, X0=x0) assert np.allclose(yout_with_actuator[6, :], yout_without_actuator[6, :], atol=5.e-3) assert np.allclose(yout_with_actuator[7, :], yout_without_actuator[7, :], atol=5.e-3) assert np.allclose(yout_with_actuator[8, :], yout_without_actuator[8, :], atol=5.e-3) assert np.allclose(yout_with_actuator[9, :], yout_without_actuator[9, :], atol=5.e-3) assert np.allclose(yout_with_actuator[10, :], yout_without_actuator[10, :], atol=5.e-3) assert np.allclose(yout_with_actuator[11, :], yout_without_actuator[11, :], atol=5.e-3)
def test_nonsquare_bdalg(self): # Set up parameters for simulation T = self.T U2 = [np.sin(T), np.cos(T)] U3 = [np.sin(T), np.cos(T), T] X0 = 0 # Set up systems to be composed linsys_2i3o = ct.StateSpace( [[-1, 1, 0], [0, -2, 0], [0, 0, -3]], [[1, 0], [0, 1], [1, 1]], [[1, 0, 0], [0, 1, 0], [0, 0, 1]], np.zeros((3, 2))) iosys_2i3o = ios.LinearIOSystem(linsys_2i3o) linsys_3i2o = ct.StateSpace( [[-1, 1, 0], [0, -2, 0], [0, 0, -3]], [[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[1, 0, 1], [0, 1, -1]], np.zeros((2, 3))) iosys_3i2o = ios.LinearIOSystem(linsys_3i2o) # Multiplication linsys_multiply = linsys_3i2o * linsys_2i3o iosys_multiply = iosys_3i2o * iosys_2i3o lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U2, X0) ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U2, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) linsys_multiply = linsys_2i3o * linsys_3i2o iosys_multiply = iosys_2i3o * iosys_3i2o lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U3, X0) ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U3, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Right multiplication # TODO: add real tests once conversion from other types is supported iosys_multiply = ios.InputOutputSystem.__rmul__(iosys_3i2o, iosys_2i3o) ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U3, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Feedback linsys_multiply = ct.feedback(linsys_3i2o, linsys_2i3o) iosys_multiply = iosys_3i2o.feedback(iosys_2i3o) lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U3, X0) ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U3, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Mismatch should generate exception args = (iosys_3i2o, iosys_3i2o) self.assertRaises(ValueError, ct.series, *args)
def test_bdalg_functions(self): """Test block diagram functions algebra on I/O systems""" # Set up parameters for simulation T = self.T U = [np.sin(T), np.cos(T)] X0 = 0 # Set up systems to be composed linsys1 = self.mimo_linsys1 linio1 = ios.LinearIOSystem(linsys1) linsys2 = self.mimo_linsys2 linio2 = ios.LinearIOSystem(linsys2) # Series interconnection linsys_series = ct.series(linsys1, linsys2) iosys_series = ct.series(linio1, linio2) lin_t, lin_y, lin_x = ct.forced_response(linsys_series, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys_series, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Make sure that systems don't commute linsys_series = ct.series(linsys2, linsys1) lin_t, lin_y, lin_x = ct.forced_response(linsys_series, T, U, X0) self.assertFalse((np.abs(lin_y - ios_y) < 1e-3).all()) # Parallel interconnection linsys_parallel = ct.parallel(linsys1, linsys2) iosys_parallel = ct.parallel(linio1, linio2) lin_t, lin_y, lin_x = ct.forced_response(linsys_parallel, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys_parallel, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Negation linsys_negate = ct.negate(linsys1) iosys_negate = ct.negate(linio1) lin_t, lin_y, lin_x = ct.forced_response(linsys_negate, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys_negate, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3) # Feedback interconnection linsys_feedback = ct.feedback(linsys1, linsys2) iosys_feedback = ct.feedback(linio1, linio2) lin_t, lin_y, lin_x = ct.forced_response(linsys_feedback, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys_feedback, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3)
def test_tf2io(self): # Create a transfer function from the state space system linsys = self.siso_linsys tfsys = ct.ss2tf(linsys) iosys = ct.tf2io(tfsys) # Verify correctness via simulation T, U, X0 = self.T, self.U, self.X0 lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
def test_neg(self): """Test negation of a system""" # Set up parameters for simulation T, U, X0 = self.T, self.U, self.X0 # Static nonlinear system nlios = ios.NonlinearIOSystem(None, \ lambda t, x, u, params: u*u, inputs=1, outputs=1) ios_t, ios_y = ios.input_output_response(-nlios, T, U, X0) np.testing.assert_array_almost_equal(ios_y, -U*U, decimal=3) # Linear system with input nonlinearity # Also creates a nested interconnected system ioslin = ios.LinearIOSystem(self.siso_linsys) sys = (ioslin) * (-nlios) # Make sure we got the right thing (via simulation comparison) ios_t, ios_y = ios.input_output_response(sys, T, U, X0) lti_t, lti_y, lti_x = ct.forced_response(ioslin, T, U*U, X0) np.testing.assert_array_almost_equal(ios_y, -lti_y, decimal=3)
def test_feedback(self): # Set up parameters for simulation T, U, X0 = self.T, self.U, self.X0 # Linear system with constant feedback (via "nonlinear" mapping) ioslin = ios.LinearIOSystem(self.siso_linsys) nlios = ios.NonlinearIOSystem(None, \ lambda t, x, u, params: u, inputs=1, outputs=1) iosys = ct.feedback(ioslin, nlios) linsys = ct.feedback(self.siso_linsys, 1) ios_t, ios_y = ios.input_output_response(iosys, T, U, X0) lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lti_y, decimal=3)
def test_summer(self): # Construct a MIMO system for testing linsys = self.mimo_linsys1 linio = ios.LinearIOSystem(linsys) linsys_parallel = linsys + linsys iosys_parallel = linio + linio # Set up parameters for simulation T = self.T U = [np.sin(T), np.cos(T)] X0 = 0 lin_t, lin_y, lin_x = ct.forced_response(linsys_parallel, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys_parallel, T, U, X0) np.testing.assert_array_almost_equal(ios_y, lin_y, decimal=3)
def test_linear_iosys(self): # Create an input/output system from the linear system linsys = self.siso_linsys iosys = ios.LinearIOSystem(linsys) # Make sure that the right hand side matches linear system for x, u in (([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([0, 0], 1)): np.testing.assert_array_almost_equal( np.reshape(iosys._rhs(0, x, u), (-1,1)), linsys.A * np.reshape(x, (-1, 1)) + linsys.B * u) # Make sure that simulations also line up T, U, X0 = self.T, self.U, self.X0 lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0) ios_t, ios_y = ios.input_output_response(iosys, T, U, X0) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
def test_rmul(self): # Test right multiplication # TODO: replace with better tests when conversions are implemented # Set up parameters for simulation T, U, X0 = self.T, self.U, self.X0 # Linear system with input and output nonlinearities # Also creates a nested interconnected system ioslin = ios.LinearIOSystem(self.siso_linsys) nlios = ios.NonlinearIOSystem(None, \ lambda t, x, u, params: u*u, inputs=1, outputs=1) sys1 = nlios * ioslin sys2 = ios.InputOutputSystem.__rmul__(nlios, sys1) # Make sure we got the right thing (via simulation comparison) ios_t, ios_y = ios.input_output_response(sys2, T, U, X0) lti_t, lti_y, lti_x = ct.forced_response(ioslin, T, U*U, X0) np.testing.assert_array_almost_equal(ios_y, lti_y*lti_y, decimal=3)
def test_static_nonlinearity(self): # Linear dynamical system linsys = self.siso_linsys ioslin = ios.LinearIOSystem(linsys) # Nonlinear saturation sat = lambda u: u if abs(u) < 1 else np.sign(u) sat_output = lambda t, x, u, params: sat(u) nlsat = ios.NonlinearIOSystem(None, sat_output, inputs=1, outputs=1) # Set up parameters for simulation T, U, X0 = self.T, 2 * self.U, self.X0 Usat = np.vectorize(sat)(U) # Make sure saturation works properly by comparing linear system with # saturated input to nonlinear system with saturation composition lti_t, lti_y, lti_x = ct.forced_response(linsys, T, Usat, X0) ios_t, ios_y, ios_x = ios.input_output_response( ioslin * nlsat, T, U, X0, return_x=True) np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=2)
def test_params(self): # Start with the default set of parameters ios_secord_default = ios.NonlinearIOSystem( secord_update, secord_output, inputs=1, outputs=1, states=2) lin_secord_default = ios.linearize(ios_secord_default, [0, 0], [0]) w_default, v_default = np.linalg.eig(lin_secord_default.A) # New copy, with modified parameters ios_secord_update = ios.NonlinearIOSystem( secord_update, secord_output, inputs=1, outputs=1, states=2, params={'omega0':2, 'zeta':0}) # Make sure the default parameters haven't changed lin_secord_check = ios.linearize(ios_secord_default, [0, 0], [0]) w, v = np.linalg.eig(lin_secord_check.A) np.testing.assert_array_almost_equal(np.sort(w), np.sort(w_default)) # Make sure updated system parameters got set correctly lin_secord_update = ios.linearize(ios_secord_update, [0, 0], [0]) w, v = np.linalg.eig(lin_secord_update.A) np.testing.assert_array_almost_equal(np.sort(w), np.sort([2j, -2j])) # Change the parameters of the default sys just for the linearization lin_secord_local = ios.linearize(ios_secord_default, [0, 0], [0], params={'zeta':0}) w, v = np.linalg.eig(lin_secord_local.A) np.testing.assert_array_almost_equal(np.sort(w), np.sort([1j, -1j])) # Change the parameters of the updated sys just for the linearization lin_secord_local = ios.linearize(ios_secord_update, [0, 0], [0], params={'zeta':0, 'omega0':3}) w, v = np.linalg.eig(lin_secord_local.A) np.testing.assert_array_almost_equal(np.sort(w), np.sort([3j, -3j])) # Make sure that changes propagate through interconnections ios_series_default_local = ios_secord_default * ios_secord_update lin_series_default_local = ios.linearize( ios_series_default_local, [0, 0, 0, 0], [0]) w, v = np.linalg.eig(lin_series_default_local.A) np.testing.assert_array_almost_equal( np.sort(w), np.sort(np.concatenate((w_default, [2j, -2j])))) # Show that we can change the parameters at linearization lin_series_override = ios.linearize( ios_series_default_local, [0, 0, 0, 0], [0], params={'zeta':0, 'omega0':4}) w, v = np.linalg.eig(lin_series_override.A) np.testing.assert_array_almost_equal(w, [4j, -4j, 4j, -4j]) # Check for warning if we try to set params for LinearIOSystem linsys = self.siso_linsys iosys = ios.LinearIOSystem(linsys) T, U, X0 = self.T, self.U, self.X0 lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0) with warnings.catch_warnings(record=True) as warnval: # Turn off deprecation warnings warnings.simplefilter("ignore", category=DeprecationWarning) warnings.simplefilter("ignore", category=PendingDeprecationWarning) # Trigger a warning ios_t, ios_y = ios.input_output_response( iosys, T, U, X0, params={'something':0}) # Verify that we got a warning self.assertEqual(len(warnval), 1) self.assertTrue(issubclass(warnval[-1].category, UserWarning)) self.assertTrue("LinearIOSystem" in str(warnval[-1].message)) self.assertTrue("ignored" in str(warnval[-1].message)) # Check to make sure results are OK np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_array_almost_equal(lti_y, ios_y, decimal=3)
def test_algebraic_loop(self): # Create some linear and nonlinear systems to play with linsys = self.siso_linsys lnios = ios.LinearIOSystem(linsys) nlios = ios.NonlinearIOSystem(None, \ lambda t, x, u, params: u*u, inputs=1, outputs=1) nlios1 = nlios.copy() nlios2 = nlios.copy() # Set up parameters for simulation T, U, X0 = self.T, self.U, self.X0 # Single nonlinear system - no states ios_t, ios_y = ios.input_output_response(nlios, T, U, X0) np.testing.assert_array_almost_equal(ios_y, U*U, decimal=3) # Composed nonlinear system (series) ios_t, ios_y = ios.input_output_response(nlios1 * nlios2, T, U, X0) np.testing.assert_array_almost_equal(ios_y, U**4, decimal=3) # Composed nonlinear system (parallel) ios_t, ios_y = ios.input_output_response(nlios1 + nlios2, T, U, X0) np.testing.assert_array_almost_equal(ios_y, 2*U**2, decimal=3) # Nonlinear system composed with LTI system (series) ios_t, ios_y = ios.input_output_response( nlios * lnios * nlios, T, U, X0) lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U*U, X0) np.testing.assert_array_almost_equal(ios_y, lti_y*lti_y, decimal=3) # Nonlinear system in feeback loop with LTI system iosys = ios.InterconnectedSystem( (lnios, nlios), # linear system w/ nonlinear feedback ((1,), # feedback interconnection (sig to 0) (0, (1, 0, -1))), 0, # input to linear system 0 # output from linear system ) ios_t, ios_y = ios.input_output_response(iosys, T, U, X0) # No easy way to test the result # Algebraic loop from static nonlinear system in feedback # (error will be due to no states) iosys = ios.InterconnectedSystem( (nlios1, nlios2), # two copies of a static nonlinear system ((0, 1), # feedback interconnection (1, (0, 0, -1))), 0, 0 ) args = (iosys, T, U, X0) self.assertRaises(RuntimeError, ios.input_output_response, *args) # Algebraic loop due to feedthrough term linsys = ct.StateSpace( [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[1]]) lnios = ios.LinearIOSystem(linsys) iosys = ios.InterconnectedSystem( (nlios, lnios), # linear system w/ nonlinear feedback ((0, 1), # feedback interconnection (1, (0, 0, -1))), 0, 0 ) args = (iosys, T, U, X0) # ios_t, ios_y = ios.input_output_response(iosys, T, U, X0) self.assertRaises(RuntimeError, ios.input_output_response, *args)