def test_squeeze_exceptions(self, fcn): if fcn == ct.frd: sys = fcn(ct.rss(2, 1, 1), [1e-2, 1e-1, 1, 1e1, 1e2]) else: sys = fcn(ct.rss(2, 1, 1)) with pytest.raises(ValueError, match="unknown squeeze value"): sys.frequency_response([1], squeeze=1) with pytest.raises(ValueError, match="unknown squeeze value"): sys([1j], squeeze='siso') with pytest.raises(ValueError, match="unknown squeeze value"): evalfr(sys, [1j], squeeze='siso') with pytest.raises(ValueError, match="must be 1D"): sys.frequency_response([[0.1, 1], [1, 10]]) with pytest.raises(ValueError, match="must be 1D"): sys([[0.1j, 1j], [1j, 10j]]) with pytest.raises(ValueError, match="must be 1D"): evalfr(sys, [[0.1j, 1j], [1j, 10j]]) with pytest.warns(DeprecationWarning, match="LTI `inputs`"): ninputs = sys.inputs assert ninputs == sys.ninputs with pytest.warns(DeprecationWarning, match="LTI `outputs`"): noutputs = sys.outputs assert noutputs == sys.noutputs if isinstance(sys, ct.StateSpace): with pytest.warns(DeprecationWarning, match="StateSpace `states`"): nstates = sys.states assert nstates == sys.nstates
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape, omega_type): """Test correct behavior of frequencey response squeeze parameter.""" # Create the system to be tested if fcn == ct.frd: sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2]) elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check(): pytest.skip("Conversion of MIMO systems to transfer functions " "requires slycot.") else: sys = fcn(ct.rss(nstate, nout, ninp)) if omega_type == "numpy": omega = np.asarray(omega) isscalar = omega.ndim == 0 # keep the ndarray type even for scalars s = np.asarray(omega * 1j) else: isscalar = not hasattr(omega, '__len__') if isscalar: s = omega * 1J else: s = [w * 1J for w in omega] # Call the transfer function directly and make sure shape is correct assert sys(s, squeeze=squeeze).shape == shape # Make sure that evalfr also works as expected assert ct.evalfr(sys, s, squeeze=squeeze).shape == shape # Check frequency response mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze) if isscalar and squeeze is not True: # sys.frequency_response() expects a list as an argument # Add the shape of the input to the expected shape assert mag.shape == shape + (1, ) assert phase.shape == shape + (1, ) else: assert mag.shape == shape assert phase.shape == shape # Make sure the default shape lines up with squeeze=None case if squeeze is None: assert sys(s).shape == shape # Changing config.default to False should return 3D frequency response ct.config.set_defaults('control', squeeze_frequency_response=False) mag, phase, _ = sys.frequency_response(omega) if isscalar: assert mag.shape == (sys.noutputs, sys.ninputs, 1) assert phase.shape == (sys.noutputs, sys.ninputs, 1) assert sys(s).shape == (sys.noutputs, sys.ninputs) assert ct.evalfr(sys, s).shape == (sys.noutputs, sys.ninputs) else: assert mag.shape == (sys.noutputs, sys.ninputs, len(omega)) assert phase.shape == (sys.noutputs, sys.ninputs, len(omega)) assert sys(s).shape == \ (sys.noutputs, sys.ninputs, len(omega)) assert ct.evalfr(sys, s).shape == \ (sys.noutputs, sys.ninputs, len(omega))
def testAcker(self, fixedseed): for states in range(1, self.maxStates): for i in range(self.maxTries): # start with a random SS system and transform to TF then # back to SS, check that the matrices are the same. sys = rss(states, 1, 1) if (self.debug): print(sys) # Make sure the system is not degenerate Cmat = ctrb(sys.A, sys.B) if np.linalg.matrix_rank(Cmat) != states: if (self.debug): print(" skipping (not reachable or ill conditioned)") continue # Place the poles at random locations des = rss(states, 1, 1) poles = pole(des) # Now place the poles using acker K = acker(sys.A, sys.B, poles) new = ss(sys.A - sys.B * K, sys.B, sys.C, sys.D) placed = pole(new) # Debugging code # diff = np.sort(poles) - np.sort(placed) # if not all(diff < 0.001): # print("Found a problem:") # print(sys) # print("desired = ", poles) np.testing.assert_array_almost_equal(np.sort(poles), np.sort(placed), decimal=4)
def test_nyquist_exceptions(): # MIMO not implemented sys = ct.rss(2, 2, 2) with pytest.raises(ct.exception.ControlMIMONotImplemented, match="only supports SISO"): ct.nyquist_plot(sys) # Legacy keywords for arrow size sys = ct.rss(2, 1, 1) with pytest.warns(FutureWarning, match="use `arrow_size` instead"): ct.nyquist_plot(sys, arrow_width=8, arrow_length=6) # Unknown arrow keyword with pytest.raises(ValueError, match="unsupported arrow location"): ct.nyquist_plot(sys, arrows='uniform') # Bad value for indent direction sys = ct.tf([1], [1, 0, 1]) with pytest.raises(ValueError, match="unknown value for indent"): ct.nyquist_plot(sys, indent_direction='up') # Discrete time system sampled above Nyquist frequency sys = ct.drss(2, 1, 1) sys.dt = 0.01 with pytest.warns(UserWarning, match="above Nyquist"): ct.nyquist_plot(sys, np.logspace(-2, 3))
def test_interconnect_docstring(): """Test the examples from the interconnect() docstring""" # MIMO interconnection (note: use [C, P] instead of [P, C] for state order) P = ct.LinearIOSystem( ct.rss(2, 2, 2, strictly_proper=True), name='P') C = ct.LinearIOSystem(ct.rss(2, 2, 2), name='C') T = ct.interconnect( [C, P], connections = [ ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'], ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']], inplist = ['C.u[0]', 'C.u[1]'], outlist = ['P.y[0]', 'P.y[1]'], ) T_ss = ct.feedback(P * C, ct.ss([], [], [], np.eye(2))) np.testing.assert_almost_equal(T.A, T_ss.A) np.testing.assert_almost_equal(T.B, T_ss.B) np.testing.assert_almost_equal(T.C, T_ss.C) np.testing.assert_almost_equal(T.D, T_ss.D) # Implicit interconnection (note: use [C, P, sumblk] for proper state order) P = ct.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y') C = ct.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u') sumblk = ct.summing_junction(inputs=['r', '-y'], output='e') T = ct.interconnect([C, P, sumblk], inplist='r', outlist='y') T_ss = ct.feedback(P * C, 1) np.testing.assert_almost_equal(T.A, T_ss.A) np.testing.assert_almost_equal(T.B, T_ss.B) np.testing.assert_almost_equal(T.C, T_ss.C) np.testing.assert_almost_equal(T.D, T_ss.D)
def test_response_copy(): # Generate some initial data to use sys_siso = ct.rss(4, 1, 1) response_siso = ct.step_response(sys_siso) siso_ntimes = response_siso.time.size sys_mimo = ct.rss(4, 2, 1) response_mimo = ct.step_response(sys_mimo) mimo_ntimes = response_mimo.time.size # Transpose response_mimo_transpose = response_mimo(transpose=True) assert response_mimo.outputs.shape == (2, 1, mimo_ntimes) assert response_mimo_transpose.outputs.shape == (mimo_ntimes, 2, 1) assert response_mimo.states.shape == (4, 1, mimo_ntimes) assert response_mimo_transpose.states.shape == (mimo_ntimes, 4, 1) # Squeeze response_siso_as_mimo = response_siso(squeeze=False) assert response_siso_as_mimo.outputs.shape == (1, 1, siso_ntimes) assert response_siso_as_mimo.states.shape == (4, 1, siso_ntimes) assert response_siso_as_mimo._legacy_states.shape == (4, siso_ntimes) response_mimo_squeezed = response_mimo(squeeze=True) assert response_mimo_squeezed.outputs.shape == (2, mimo_ntimes) assert response_mimo_squeezed.states.shape == (4, mimo_ntimes) assert response_mimo_squeezed._legacy_states.shape == (4, 1, mimo_ntimes) # Squeeze and transpose response_mimo_sqtr = response_mimo(squeeze=True, transpose=True) assert response_mimo_sqtr.outputs.shape == (mimo_ntimes, 2) assert response_mimo_sqtr.states.shape == (mimo_ntimes, 4) assert response_mimo_sqtr._legacy_states.shape == (mimo_ntimes, 4, 1) # Return_x t, y = response_mimo t, y = response_mimo() t, y, x = response_mimo(return_x=True) with pytest.raises(ValueError, match="too many"): t, y = response_mimo(return_x=True) with pytest.raises(ValueError, match="not enough"): t, y, x = response_mimo # Labels assert response_mimo.output_labels is None assert response_mimo.state_labels is None assert response_mimo.input_labels is None response = response_mimo( output_labels=['y1', 'y2'], input_labels='u', state_labels=["x[%d]" % i for i in range(4)]) assert response.output_labels == ['y1', 'y2'] assert response.state_labels == ['x[0]', 'x[1]', 'x[2]', 'x[3]'] assert response.input_labels == ['u'] # Unknown keyword with pytest.raises(ValueError, match="Unknown parameter(s)*"): response_bad_kw = response_mimo(input=0)
def test_duplicate_sysname(): # Start with an unnamed system sys = ct.rss(4, 1, 1) # No warnings should be generated if we reuse an an unnamed system with pytest.warns(None) as record: res = sys * sys assert not any([type(msg) == UserWarning for msg in record]) # Generate a warning if the system is named sys = ct.rss(4, 1, 1, name='sys') with pytest.warns(UserWarning, match="duplicate object found"): res = sys * sys
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape): # Create the system to be tested if fcn == ct.frd: sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2]) elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check(): pytest.skip("Conversion of MIMO systems to transfer functions " "requires slycot.") else: sys = fcn(ct.rss(nstate, nout, ninp)) # Convert the frequency list to an array for easy of use isscalar = not hasattr(omega, '__len__') omega = np.array(omega) # Call the transfer function directly and make sure shape is correct assert sys(omega * 1j, squeeze=squeeze).shape == shape # Make sure that evalfr also works as expected assert ct.evalfr(sys, omega * 1j, squeeze=squeeze).shape == shape # Check frequency response mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze) if isscalar and squeeze is not True: # sys.frequency_response() expects a list as an argument # Add the shape of the input to the expected shape assert mag.shape == shape + (1, ) assert phase.shape == shape + (1, ) else: assert mag.shape == shape assert phase.shape == shape # Make sure the default shape lines up with squeeze=None case if squeeze is None: assert sys(omega * 1j).shape == shape # Changing config.default to False should return 3D frequency response ct.config.set_defaults('control', squeeze_frequency_response=False) mag, phase, _ = sys.frequency_response(omega) if isscalar: assert mag.shape == (sys.noutputs, sys.ninputs, 1) assert phase.shape == (sys.noutputs, sys.ninputs, 1) assert sys(omega * 1j).shape == (sys.noutputs, sys.ninputs) assert ct.evalfr(sys, omega * 1j).shape == (sys.noutputs, sys.ninputs) else: assert mag.shape == (sys.noutputs, sys.ninputs, len(omega)) assert phase.shape == (sys.noutputs, sys.ninputs, len(omega)) assert sys(omega * 1j).shape == \ (sys.noutputs, sys.ninputs, len(omega)) assert ct.evalfr(sys, omega * 1j).shape == \ (sys.noutputs, sys.ninputs, len(omega))
def test_init_namedif(): # Set up the initial system sys = ct.rss(2, 1, 1) # Rename the system, inputs, and outouts sys_new = sys.copy() ct.StateSpace.__init__(sys_new, sys, inputs='u', outputs='y', name='new') assert sys_new.name == 'new' assert sys_new.input_labels == ['u'] assert sys_new.output_labels == ['y'] # Call constructor without re-initialization sys_keep = sys.copy() ct.StateSpace.__init__(sys_keep, sys, init_namedio=False) assert sys_keep.name == sys_keep.name assert sys_keep.input_labels == sys_keep.input_labels assert sys_keep.output_labels == sys_keep.output_labels # Make sure that passing an unrecognized keyword generates an error with pytest.raises(TypeError, match="unrecognized keyword"): ct.StateSpace.__init__(sys_keep, sys, inputs='u', outputs='y', init_namedio=False)
def test_response_transpose(self, nstate, nout, ninp, squeeze, ysh_in, ysh_no, xsh_in): sys = ct.rss(nstate, nout, ninp) T = np.linspace(0, 1, 8) # Step response - input indexed t, y, x = ct.step_response(sys, T, transpose=True, return_x=True, squeeze=squeeze) assert t.shape == (T.size, ) assert y.shape == ysh_in assert x.shape == xsh_in # Initial response - no input indexing t, y, x = ct.initial_response(sys, T, 1, transpose=True, return_x=True, squeeze=squeeze) assert t.shape == (T.size, ) assert y.shape == ysh_no assert x.shape == (T.size, sys.nstates)
def tsys(self): """Create some systems for testing""" class Tsys: pass T = Tsys() # Single input, single output continuous and discrete time systems sys = rss(3, 1, 1) T.siso_ss1 = StateSpace(sys.A, sys.B, sys.C, sys.D, None) T.siso_ss1c = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.0) T.siso_ss1d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.1) T.siso_ss2d = StateSpace(sys.A, sys.B, sys.C, sys.D, 0.2) T.siso_ss3d = StateSpace(sys.A, sys.B, sys.C, sys.D, True) # Two input, two output continuous time system A = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]] B = [[1., 4.], [-3., -3.], [-2., 1.]] C = [[4., 2., -3.], [1., 4., 3.]] D = [[-2., 4.], [0., 1.]] T.mimo_ss1 = StateSpace(A, B, C, D, None) T.mimo_ss1c = StateSpace(A, B, C, D, 0) # Two input, two output discrete time system T.mimo_ss1d = StateSpace(A, B, C, D, 0.1) # Same system, but with a different sampling time T.mimo_ss2d = StateSpace(A, B, C, D, 0.2) # Single input, single output continuus and discrete transfer function T.siso_tf1 = TransferFunction([1, 1], [1, 2, 1], None) T.siso_tf1c = TransferFunction([1, 1], [1, 2, 1], 0) T.siso_tf1d = TransferFunction([1, 1], [1, 2, 1], 0.1) T.siso_tf2d = TransferFunction([1, 1], [1, 2, 1], 0.2) T.siso_tf3d = TransferFunction([1, 1], [1, 2, 1], True) return T
def test_size_mismatch(self): sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10)) # Different number of inputs sys2 = FRD(ct.rss(3, 1, 2), np.logspace(-1, 1, 10)) self.assertRaises(ValueError, FRD.__add__, sys1, sys2) # Different number of outputs sys2 = FRD(ct.rss(3, 2, 1), np.logspace(-1, 1, 10)) self.assertRaises(ValueError, FRD.__add__, sys1, sys2) # Inputs and outputs don't match self.assertRaises(ValueError, FRD.__mul__, sys2, sys1) # Feedback mismatch self.assertRaises(ValueError, FRD.feedback, sys2, sys1)
def test_lqe_discrete(): """Test overloading of lqe operator for discrete time systems""" csys = ct.rss(2, 1, 1) dsys = ct.drss(2, 1, 1) Q = np.eye(1) R = np.eye(1) # Calling with a system versus explicit A, B should be the sam K_csys, S_csys, E_csys = ct.lqe(csys, Q, R) K_expl, S_expl, E_expl = ct.lqe(csys.A, csys.B, csys.C, Q, R) np.testing.assert_almost_equal(K_csys, K_expl) np.testing.assert_almost_equal(S_csys, S_expl) np.testing.assert_almost_equal(E_csys, E_expl) # Calling lqe() with a discrete time system should call dlqe() K_lqe, S_lqe, E_lqe = ct.lqe(dsys, Q, R) K_dlqe, S_dlqe, E_dlqe = ct.dlqe(dsys, Q, R) np.testing.assert_almost_equal(K_lqe, K_dlqe) np.testing.assert_almost_equal(S_lqe, S_dlqe) np.testing.assert_almost_equal(E_lqe, E_dlqe) # Calling lqe() with no timebase should call lqe() asys = ct.ss(csys.A, csys.B, csys.C, csys.D, dt=None) K_asys, S_asys, E_asys = ct.lqe(asys, Q, R) K_expl, S_expl, E_expl = ct.lqe(csys.A, csys.B, csys.C, Q, R) np.testing.assert_almost_equal(K_asys, K_expl) np.testing.assert_almost_equal(S_asys, S_expl) np.testing.assert_almost_equal(E_asys, E_expl) # Calling dlqe() with a continuous time system should raise an error with pytest.raises(ControlArgument, match="called with a continuous"): K, S, E = ct.dlqe(csys, Q, R)
def test_interconnect_exceptions(): # First make sure the docstring example works P = ct.tf2io(ct.tf(1, [1, 0]), input='u', output='y') C = ct.tf2io(ct.tf(10, [1, 1]), input='e', output='u') sumblk = ct.summing_junction(inputs=['r', '-y'], output='e') T = ct.interconnect((P, C, sumblk), input='r', output='y') assert (T.ninputs, T.noutputs, T.nstates) == (1, 1, 2) # Unrecognized arguments # LinearIOSystem with pytest.raises(TypeError, match="unknown parameter"): P = ct.LinearIOSystem(ct.rss(2, 1, 1), output_name='y') # Interconnect with pytest.raises(TypeError, match="unknown parameter"): T = ct.interconnect((P, C, sumblk), input_name='r', output='y') # Interconnected system with pytest.raises(TypeError, match="unknown parameter"): T = ct.InterconnectedSystem((P, C, sumblk), input_name='r', output='y') # NonlinearIOSytem with pytest.raises(TypeError, match="unknown parameter"): nlios = ct.NonlinearIOSystem( None, lambda t, x, u, params: u*u, input_count=1, output_count=1) # Summing junction with pytest.raises(TypeError, match="input specification is required"): sumblk = ct.summing_junction() with pytest.raises(TypeError, match="unknown parameter"): sumblk = ct.summing_junction(input_count=2, output_count=2)
def test_named_ss(): # Create a system to play with sys = ct.rss(2, 2, 2) assert sys.input_labels == ['u[0]', 'u[1]'] assert sys.output_labels == ['y[0]', 'y[1]'] assert sys.state_labels == ['x[0]', 'x[1]'] # Get the state matrices for later use A, B, C, D = sys.A, sys.B, sys.C, sys.D # Set up a named state space systems with default names ct.namedio.NamedIOSystem._idCounter = 0 sys = ct.ss(A, B, C, D) assert sys.name == 'sys[0]' assert sys.input_labels == ['u[0]', 'u[1]'] assert sys.output_labels == ['y[0]', 'y[1]'] assert sys.state_labels == ['x[0]', 'x[1]'] assert repr(sys) == \ "<LinearIOSystem:sys[0]:['u[0]', 'u[1]']->['y[0]', 'y[1]']>" # Pass the names as arguments sys = ct.ss(A, B, C, D, name='system', inputs=['u1', 'u2'], outputs=['y1', 'y2'], states=['x1', 'x2']) assert sys.name == 'system' assert ct.namedio.NamedIOSystem._idCounter == 1 assert sys.input_labels == ['u1', 'u2'] assert sys.output_labels == ['y1', 'y2'] assert sys.state_labels == ['x1', 'x2'] assert repr(sys) == \ "<LinearIOSystem:system:['u1', 'u2']->['y1', 'y2']>" # Do the same with rss sys = ct.rss(['x1', 'x2', 'x3'], ['y1', 'y2'], 'u1', name='random') assert sys.name == 'random' assert ct.namedio.NamedIOSystem._idCounter == 1 assert sys.input_labels == ['u1'] assert sys.output_labels == ['y1', 'y2'] assert sys.state_labels == ['x1', 'x2', 'x3'] assert repr(sys) == \ "<LinearIOSystem:random:['u1']->['y1', 'y2']>"
def test_nyquist_exceptions(): # MIMO not implemented sys = ct.rss(2, 2, 2) with pytest.raises(ct.exception.ControlMIMONotImplemented, match="only supports SISO"): ct.nyquist_plot(sys) # Legacy keywords for arrow size sys = ct.rss(2, 1, 1) with pytest.warns(FutureWarning, match="use `arrow_size` instead"): ct.nyquist_plot(sys, arrow_width=8, arrow_length=6) # Discrete time system sampled above Nyquist frequency sys = ct.drss(2, 1, 1) sys.dt = 0.01 with pytest.warns(UserWarning, match="above Nyquist"): ct.nyquist_plot(sys, np.logspace(-2, 3))
def test_string_inputoutput(): # regression test for gh-692 P1 = ct.rss(2, 1, 1) P1_iosys = ct.LinearIOSystem(P1, inputs='u1', outputs='y1') P2 = ct.rss(2, 1, 1) P2_iosys = ct.LinearIOSystem(P2, inputs='y1', outputs='y2') P_s1 = ct.interconnect([P1_iosys, P2_iosys], inputs='u1', outputs=['y2']) assert P_s1.input_index == {'u1' : 0} P_s2 = ct.interconnect([P1_iosys, P2_iosys], input='u1', outputs=['y2']) assert P_s2.input_index == {'u1' : 0} P_s1 = ct.interconnect([P1_iosys, P2_iosys], inputs=['u1'], outputs='y2') assert P_s1.output_index == {'y2' : 0} P_s2 = ct.interconnect([P1_iosys, P2_iosys], inputs=['u1'], output='y2') assert P_s2.output_index == {'y2' : 0}
def test_printing(self): """Print SISO""" sys = ss2tf(rss(4, 1, 1)) assert isinstance(str(sys), str) assert isinstance(sys._repr_latex_(), str) # SISO, discrete time sys = sample_system(sys, 1) assert isinstance(str(sys), str) assert isinstance(sys._repr_latex_(), str)
def test_frequency_response(): # Create an SISO frequence response sys = ct.rss(2, 2, 2) omega = np.logspace(-2, 2, 20) resp = ct.frequency_response(sys, omega) eval = sys(omega * 1j) # Make sure we get the right answers in various ways np.testing.assert_equal(resp.magnitude, np.abs(eval)) np.testing.assert_equal(resp.phase, np.angle(eval)) np.testing.assert_equal(resp.omega, omega) # Make sure that we can change the properties of the response sys = ct.rss(2, 1, 1) resp_default = ct.frequency_response(sys, omega) mag_default, phase_default, omega_default = resp_default assert mag_default.ndim == 1 assert phase_default.ndim == 1 assert omega_default.ndim == 1 assert mag_default.shape[0] == omega_default.shape[0] assert phase_default.shape[0] == omega_default.shape[0] resp_nosqueeze = ct.frequency_response(sys, omega, squeeze=False) mag_nosqueeze, phase_nosqueeze, omega_nosqueeze = resp_nosqueeze assert mag_nosqueeze.ndim == 3 assert phase_nosqueeze.ndim == 3 assert omega_nosqueeze.ndim == 1 assert mag_nosqueeze.shape[2] == omega_nosqueeze.shape[0] assert phase_nosqueeze.shape[2] == omega_nosqueeze.shape[0] # Try changing the response resp_def_nosq = resp_default(squeeze=False) mag_def_nosq, phase_def_nosq, omega_def_nosq = resp_def_nosq assert mag_def_nosq.shape == mag_nosqueeze.shape assert phase_def_nosq.shape == phase_nosqueeze.shape assert omega_def_nosq.shape == omega_nosqueeze.shape resp_nosq_sq = resp_nosqueeze(squeeze=True) mag_nosq_sq, phase_nosq_sq, omega_nosq_sq = resp_nosq_sq assert mag_nosq_sq.shape == mag_default.shape assert phase_nosq_sq.shape == phase_default.shape assert omega_nosq_sq.shape == omega_default.shape
def test_lqr_call_format(self, cdlqr): # Create a random state space system for testing sys = rss(2, 3, 2) sys.dt = None # treat as either continuous or discrete time # Weighting matrices Q = np.eye(sys.nstates) R = np.eye(sys.ninputs) N = np.zeros((sys.nstates, sys.ninputs)) # Standard calling format Kref, Sref, Eref = cdlqr(sys.A, sys.B, Q, R) # Call with system instead of matricees K, S, E = cdlqr(sys, Q, R) np.testing.assert_array_almost_equal(Kref, K) np.testing.assert_array_almost_equal(Sref, S) np.testing.assert_array_almost_equal(Eref, E) # Pass a cross-weighting matrix K, S, E = cdlqr(sys, Q, R, N) np.testing.assert_array_almost_equal(Kref, K) np.testing.assert_array_almost_equal(Sref, S) np.testing.assert_array_almost_equal(Eref, E) # Inconsistent system dimensions with pytest.raises(ct.ControlDimension, match="Incompatible dimen"): K, S, E = cdlqr(sys.A, sys.C, Q, R) # Incorrect covariance matrix dimensions with pytest.raises(ct.ControlDimension, match="Q must be a square"): K, S, E = cdlqr(sys.A, sys.B, sys.C, R, Q) # Too few input arguments with pytest.raises(ct.ControlArgument, match="not enough input"): K, S, E = cdlqr(sys.A, sys.B) # First argument is the wrong type (use SISO for non-slycot tests) sys_tf = tf(rss(3, 1, 1)) sys_tf.dt = None # treat as either continuous or discrete time with pytest.raises(ct.ControlArgument, match="LTI system must be"): K, S, E = cdlqr(sys_tf, Q, R)
def test_size_mismatch(self): """Test size mismacht""" sys1 = ss2tf(rss(2, 2, 2)) # Different number of inputs sys2 = ss2tf(rss(3, 1, 2)) with pytest.raises(ValueError): TransferFunction.__add__(sys1, sys2) # Different number of outputs sys2 = ss2tf(rss(3, 2, 1)) with pytest.raises(ValueError): TransferFunction.__add__(sys1, sys2) # Inputs and outputs don't match with pytest.raises(ValueError): TransferFunction.__mul__(sys2, sys1) # Feedback mismatch (MIMO not implemented) with pytest.raises(NotImplementedError): TransferFunction.feedback(sys2, sys1)
def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape): # Set defaults to match release 0.8.4 ct.config.use_legacy_defaults('0.8.4') ct.config.use_numpy_matrix(False) # Generate system, time, and input vectors sys = ct.rss(nstate, nout, ninp, strictly_proper=True) tvec = np.linspace(0, 1, 8) uvec =np.ones((sys.ninputs, 1)) @ np.reshape(np.sin(tvec), (1, 8)) _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) assert yvec.shape == shape
def test_lqe_call_format(cdlqe): # Create a random state space system for testing sys = rss(4, 3, 2) sys.dt = None # treat as either continuous or discrete time # Covariance matrices Q = np.eye(sys.ninputs) R = np.eye(sys.noutputs) N = np.zeros((sys.ninputs, sys.noutputs)) # Standard calling format Lref, Pref, Eref = cdlqe(sys.A, sys.B, sys.C, Q, R) # Call with system instead of matricees L, P, E = cdlqe(sys, Q, R) np.testing.assert_almost_equal(Lref, L) np.testing.assert_almost_equal(Pref, P) np.testing.assert_almost_equal(Eref, E) # Make sure we get an error if we specify N with pytest.raises(ct.ControlNotImplemented): L, P, E = cdlqe(sys, Q, R, N) # Inconsistent system dimensions with pytest.raises(ct.ControlDimension, match="Incompatible"): L, P, E = cdlqe(sys.A, sys.C, sys.B, Q, R) # Incorrect covariance matrix dimensions with pytest.raises(ct.ControlDimension, match="Incompatible"): L, P, E = cdlqe(sys.A, sys.B, sys.C, R, Q) # Too few input arguments with pytest.raises(ct.ControlArgument, match="not enough input"): L, P, E = cdlqe(sys.A, sys.C) # First argument is the wrong type (use SISO for non-slycot tests) sys_tf = tf(rss(3, 1, 1)) sys_tf.dt = None # treat as either continuous or discrete time with pytest.raises(ct.ControlArgument, match="LTI system must be"): L, P, E = cdlqe(sys_tf, Q, R)
def test_mpc_iosystem_continuous(): # Create a random state space system sys = ct.rss(2, 1, 1) T, _ = ct.step_response(sys) # provide penalties on the system signals Q = np.eye(sys.nstates) R = np.eye(sys.ninputs) cost = opt.quadratic_cost(sys, Q, R) # Continuous time MPC controller not implemented with pytest.raises(NotImplementedError): ctrl = opt.create_mpc_iosystem(sys, T, cost)
def test_statefbk_errors(self): sys = ct.rss(4, 4, 2, strictly_proper=True) K, _, _ = ct.lqr(sys, np.eye(sys.nstates), np.eye(sys.ninputs)) with pytest.raises(ControlArgument, match="must be I/O system"): sys_tf = ct.tf([1], [1, 1]) ctrl, clsys = ct.create_statefbk_iosystem(sys_tf, K) with pytest.raises(ControlArgument, match="output size must match"): est = ct.rss(3, 3, 2) ctrl, clsys = ct.create_statefbk_iosystem(sys, K, estimator=est) with pytest.raises(ControlArgument, match="must be the full state"): sys_nf = ct.rss(4, 3, 2, strictly_proper=True) ctrl, clsys = ct.create_statefbk_iosystem(sys_nf, K) with pytest.raises(ControlArgument, match="gain must be an array"): ctrl, clsys = ct.create_statefbk_iosystem(sys, "bad argument") with pytest.raises(ControlArgument, match="unknown type"): ctrl, clsys = ct.create_statefbk_iosystem(sys, K, type=1) # Errors involving integral action C_int = np.eye(2, 4) K_int, _, _ = ct.lqr(sys, np.eye(sys.nstates + C_int.shape[0]), np.eye(sys.ninputs), integral_action=C_int) with pytest.raises(ControlArgument, match="must pass an array"): ctrl, clsys = ct.create_statefbk_iosystem( sys, K_int, integral_action="bad argument") with pytest.raises(ControlArgument, match="must be an array of size"): ctrl, clsys = ct.create_statefbk_iosystem(sys, K, integral_action=C_int)
def test_forced_response_legacy(self): # Define a system for testing sys = ct.rss(2, 1, 1) T = np.linspace(0, 10, 10) U = np.sin(T) """Make sure that legacy version of forced_response works""" ct.config.use_legacy_defaults("0.8.4") # forced_response returns x by default t, y = ct.step_response(sys, T) t, y, x = ct.forced_response(sys, T, U) ct.config.use_legacy_defaults("0.9.0") # forced_response returns input/output by default t, y = ct.step_response(sys, T) t, y = ct.forced_response(sys, T, U) t, y, x = ct.forced_response(sys, T, U, return_x=True)
def test_convert_to_statespace(): # Set up the initial system sys = ct.tf(ct.rss(2, 1, 1)) # Make sure we can rename system name, inputs, outputs sys_new = ct.ss(sys, inputs='u', outputs='y', name='new') assert sys_new.name == 'new' assert sys_new.input_labels == ['u'] assert sys_new.output_labels == ['y'] # Try specifying the state names (via low level test) with pytest.warns(UserWarning, match="non-unique state space realization"): sys_new = ct.ss(sys, inputs='u', outputs='y', states=['x1', 'x2']) assert sys_new.input_labels == ['u'] assert sys_new.output_labels == ['y'] assert sys_new.state_labels == ['x1', 'x2']
def testMinrealBrute(self): # depending on the seed and minreal performance, a number of # reductions is produced. If random gen or minreal change, this # will be likely to fail nreductions = 0 for n, m, p in permutations(range(1, 6), 3): s = rss(n, p, m) sr = s.minreal() if s.states > sr.states: nreductions += 1 else: # Check to make sure that poles and zeros match # For poles, just look at eigenvalues of A np.testing.assert_array_almost_equal(np.sort(eigvals(s.A)), np.sort(eigvals(sr.A))) # For zeros, need to extract SISO systems for i in range(m): for j in range(p): # Extract SISO dynamixs from input i to output j s1 = ss(s.A, s.B[:, i], s.C[j, :], s.D[j, i]) s2 = ss(sr.A, sr.B[:, i], sr.C[j, :], sr.D[j, i]) # Check that the zeros match # Note: sorting doesn't work => have to do the hard way z1 = zero(s1) z2 = zero(s2) # Start by making sure we have the same # of zeros assert len(z1) == len(z2) # Make sure all zeros in s1 are in s2 for z in z1: # Find the closest zero TODO: find proper bounds assert min(abs(z2 - z)) <= 1e-7 # Make sure all zeros in s2 are in s1 for z in z2: # Find the closest zero assert min(abs(z1 - z)) <= 1e-7 # Make sure that the number of systems reduced is as expected # (Need to update this number if you change the seed at top of file) assert nreductions == 2
def test_linestyle_checks(): sys = ct.rss(2, 1, 1) # Things that should work ct.nyquist_plot(sys, primary_style=['-', '-'], mirror_style=['-', '-']) ct.nyquist_plot(sys, mirror_style=None) with pytest.raises(ValueError, match="invalid 'primary_style'"): ct.nyquist_plot(sys, primary_style=False) with pytest.raises(ValueError, match="invalid 'mirror_style'"): ct.nyquist_plot(sys, mirror_style=0.2) # If only one line style is given use, the default value for the other # TODO: for now, just make sure the signature works; no correct check yet with pytest.warns(PendingDeprecationWarning, match="single string"): ct.nyquist_plot(sys, primary_style=':', mirror_style='-.')
def test_trdata_labels(): # Create an I/O system with labels sys = ct.rss(4, 3, 2) iosys = ct.LinearIOSystem(sys) T = np.linspace(1, 10, 10) U = [np.sin(T), np.cos(T)] # Create a response response = ct.input_output_response(iosys, T, U) # Make sure the labels got created np.testing.assert_equal( response.output_labels, ["y[%d]" % i for i in range(sys.noutputs)]) np.testing.assert_equal( response.state_labels, ["x[%d]" % i for i in range(sys.nstates)]) np.testing.assert_equal( response.input_labels, ["u[%d]" % i for i in range(sys.ninputs)])
def test_operator_conversion(self): sys_tf = ct.tf([1], [1, 2, 1]) frd_tf = FRD(sys_tf, np.logspace(-1, 1, 10)) frd_2 = FRD(2 * np.ones(10), np.logspace(-1, 1, 10)) # Make sure that we can add, multiply, and feedback constants sys_add = frd_tf + 2 chk_add = frd_tf + frd_2 np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega) np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp) sys_radd = 2 + frd_tf chk_radd = frd_2 + frd_tf np.testing.assert_array_almost_equal(sys_radd.omega, chk_radd.omega) np.testing.assert_array_almost_equal(sys_radd.fresp, chk_radd.fresp) sys_sub = frd_tf - 2 chk_sub = frd_tf - frd_2 np.testing.assert_array_almost_equal(sys_sub.omega, chk_sub.omega) np.testing.assert_array_almost_equal(sys_sub.fresp, chk_sub.fresp) sys_rsub = 2 - frd_tf chk_rsub = frd_2 - frd_tf np.testing.assert_array_almost_equal(sys_rsub.omega, chk_rsub.omega) np.testing.assert_array_almost_equal(sys_rsub.fresp, chk_rsub.fresp) sys_mul = frd_tf * 2 chk_mul = frd_tf * frd_2 np.testing.assert_array_almost_equal(sys_mul.omega, chk_mul.omega) np.testing.assert_array_almost_equal(sys_mul.fresp, chk_mul.fresp) sys_rmul = 2 * frd_tf chk_rmul = frd_2 * frd_tf np.testing.assert_array_almost_equal(sys_rmul.omega, chk_rmul.omega) np.testing.assert_array_almost_equal(sys_rmul.fresp, chk_rmul.fresp) sys_rdiv = 2 / frd_tf chk_rdiv = frd_2 / frd_tf np.testing.assert_array_almost_equal(sys_rdiv.omega, chk_rdiv.omega) np.testing.assert_array_almost_equal(sys_rdiv.fresp, chk_rdiv.fresp) sys_pow = frd_tf**2 chk_pow = FRD(sys_tf**2, np.logspace(-1, 1, 10)) np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega) np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp) sys_pow = frd_tf**-2 chk_pow = FRD(sys_tf**-2, np.logspace(-1, 1, 10)) np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega) np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp) # Assertion error if we try to raise to a non-integer power self.assertRaises(ValueError, FRD.__pow__, frd_tf, 0.5) # Selected testing on transfer function conversion sys_add = frd_2 + sys_tf chk_add = frd_2 + frd_tf np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega) np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp) # Input/output mismatch size mismatch in rmul sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10)) self.assertRaises(ValueError, FRD.__rmul__, frd_2, sys1) # Make sure conversion of something random generates exception self.assertRaises(TypeError, FRD.__add__, frd_tf, 'string')