def test_reverse_sign_mimo(self): """Negate a MIMO system.""" num1 = [[[1., 2.], [0., 3.], [2., -1.]], [[1.], [4., 0.], [1., -4., 3.]]] num3 = [[[-1., -2.], [0., -3.], [-2., 1.]], [[-1.], [-4., 0.], [-1., 4., -3.]]] den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]], [[3., 0., .0], [2., -1., -1.], [1.]]] sys1 = TransferFunction(num1, den1) sys2 = -sys1 sys3 = TransferFunction(num3, den1) for i in range(sys3.outputs): for j in range(sys3.inputs): np.testing.assert_array_equal(sys2.num[i][j], sys3.num[i][j]) np.testing.assert_array_equal(sys2.den[i][j], sys3.den[i][j])
def test_phase_crossover_frequencies_mimo(): """Test MIMO exception""" tf = TransferFunction([[[1], [2]], [[3], [4]]], [[[1, 2, 3, 4], [1, 1]], [[1, 1], [1, 1]]]) with pytest.raises(ControlMIMONotImplemented): omega, gain = phase_crossover_frequencies(tf)
def setUp(self): """This contains some random LTI systems and scalars for testing.""" # Two random SISO systems. sys1 = TransferFunction([1, 2], [1, 2, 3]) sys2 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]], [[1., 0.]], [[0.]]) self.systems = (sys1, sys2)
def test_pole_mimo(self): """Test for correct MIMO poles.""" sys = TransferFunction( [[[1.], [1.]], [[1.], [1.]]], [[[1., 2.], [1., 3.]], [[1., 4., 4.], [1., 9., 14.]]]) p = sys.pole() np.testing.assert_array_almost_equal(p, [-2., -2., -7., -3., -2.]) # non proper transfer function sys2 = TransferFunction( [[[1., 2., 3., 4.], [1.]], [[1.], [1.]]], [[[1., 2.], [1., 3.]], [[1., 4., 4.], [1., 9., 14.]]]) p2 = sys2.pole() np.testing.assert_array_almost_equal(p2, [-2., -2., -7., -3., -2.])
def testNyquist(self): h1 = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 40) f1 = FRD(h1, omega, smooth=True) freqplot.nyquist(f1, np.logspace(-1, 2, 100)) # plt.savefig('/dev/null', format='svg') plt.figure(2) freqplot.nyquist(f1, f1.omega)
def testPoleMIMO(self): """Test for correct MIMO poles.""" sys = TransferFunction([[[1.], [1.]], [[1.], [1.]]], [[[1., 2.], [1., 3.]], [[1., 4., 4.], [1., 9., 14.]]]) p = sys.pole() np.testing.assert_array_almost_equal(p, [-7., -3., -2., -2.])
def testNegSISO(self): """Negate a SISO system.""" sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1.]) sys2 = -sys1 np.testing.assert_array_equal(sys2.num, [[[-1., -3., -5.]]]) np.testing.assert_array_equal(sys2.den, [[[1., 6., 2., -1.]]])
def testNegScalar(self): """Negate a direct feedthrough system.""" sys1 = TransferFunction(2., np.array([-3.])) sys2 = -sys1 np.testing.assert_array_equal(sys2.num, [[[-2.]]]) np.testing.assert_array_equal(sys2.den, [[[-3.]]])
def test_phase_crossover_frequencies(): """Test phase_crossover_frequencies() function""" omega, gain = phase_crossover_frequencies(tsys[1][0]) assert_allclose(omega, [1.73205, 0.], atol=1.5e-3) assert_allclose(gain, [-0.5, 0.25], atol=1.5e-3) tf = TransferFunction([1], [1, 1]) omega, gain = phase_crossover_frequencies(tf) assert_allclose(omega, [0.], atol=1.5e-3) assert_allclose(gain, [1.], atol=1.5e-3) # testing MIMO, only (0,0) element is considered tf = TransferFunction([[[1], [2]], [[3], [4]]], [[[1, 2, 3, 4], [1, 1]], [[1, 1], [1, 1]]]) omega, gain = phase_crossover_frequencies(tf) assert_allclose(omega, [1.73205, 0.], atol=1.5e-3) assert_allclose(gain, [-0.5, 0.25], atol=1.5e-3)
def test_latex_repr(self): """ Test latex printout for TransferFunction """ Hc = TransferFunction([1e-5, 2e5, 3e-4], [1.2e34, 2.3e-4, 2.3e-45]) Hd = TransferFunction([1e-5, 2e5, 3e-4], [1.2e34, 2.3e-4, 2.3e-45], .1) # TODO: make the multiplication sign configurable expmul = r'\times' for var, H, suffix in zip(['s', 'z'], [Hc, Hd], ['', r'\quad dt = 0.1']): ref = (r'$$\frac{' r'1 ' + expmul + ' 10^{-5} ' + var + '^2 ' r'+ 2 ' + expmul + ' 10^{5} ' + var + ' + 0.0003' r'}{' r'1.2 ' + expmul + ' 10^{34} ' + var + '^2 ' r'+ 0.00023 ' + var + ' ' r'+ 2.3 ' + expmul + ' 10^{-45}' r'}' + suffix + '$$') self.assertEqual(H._repr_latex_(), ref)
def test_div(self): # Make sure that sampling times work correctly sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1]) sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], True) sys3 = sys1 / sys2 self.assertEqual(sys3.dt, True) sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], 0.5) sys3 = sys1 / sys2 self.assertEqual(sys3.dt, 0.5) sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1], 0.1) self.assertRaises(ValueError, TransferFunction.__truediv__, sys1, sys2) sys1 = sample_system(rss(4, 1, 1), 0.5) sys3 = TransferFunction.__rtruediv__(sys2, sys1) self.assertEqual(sys3.dt, 0.5)
def test_constructor_bad_input_type(self): """Give the constructor invalid input types.""" # MIMO requires lists of lists of vectors (not lists of vectors) with pytest.raises(TypeError): TransferFunction([[0., 1.], [2., 3.]], [[5., 2.], [3., 0.]]) # good input TransferFunction([[[0., 1.], [2., 3.]]], [[[5., 2.], [3., 0.]]]) # Single argument of the wrong type with pytest.raises(TypeError): TransferFunction([1]) # Too many arguments with pytest.raises(ValueError): TransferFunction(1, 2, 3, 4) # Different numbers of elements in numerator rows with pytest.raises(ValueError): TransferFunction([[[0, 1], [2, 3]], [[4, 5]]], [[[6, 7], [4, 5]], [[2, 3], [0, 1]]]) with pytest.raises(ValueError): TransferFunction([[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[6, 7], [4, 5]], [[2, 3]]]) # good input TransferFunction([[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[6, 7], [4, 5]], [[2, 3], [0, 1]]])
def testSISOtf(self): # get a SISO transfer function h = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 10) frd = FRD(h, omega) assert isinstance(frd, FRD) np.testing.assert_array_almost_equal(frd.freqresp([1.0]), h.freqresp([1.0]))
def test_printing_polynomial(self): """Cover all _tf_polynomial_to_string code branches""" # Note: the assertions below use plain assert statements instead of # unittest methods so that debugging with pytest is easier assert str(TransferFunction([0], [1])) == "\n0\n-\n1\n" assert str(TransferFunction([1.0001], [-1.1111])) == \ "\n 1\n------\n-1.111\n" assert str(TransferFunction([0, 1], [0, 1.])) == "\n1\n-\n1\n" for var, dt, dtstring in zip(["s", "z", "z"], [None, True, 1], ['', '', '\ndt = 1\n']): assert str(TransferFunction([1, 0], [2, 1], dt)) == \ "\n {var}\n-------\n2 {var} + 1\n{dtstring}".format( var=var, dtstring=dtstring) assert str(TransferFunction([2, 0, -1], [1, 0, 0, 1.2], dt)) == \ "\n2 {var}^2 - 1\n---------\n{var}^3 + 1.2\n{dtstring}".format( var=var, dtstring=dtstring)
def test_ss2tf(self): A = np.array([[-4, -1], [-1, -4]]) B = np.array([[1], [3]]) C = np.array([[3, 1]]) D = 0 sys = ss2tf(A, B, C, D) true_sys = TransferFunction([6., 14.], [1., 8., 15.]) np.testing.assert_almost_equal(sys.num, true_sys.num) np.testing.assert_almost_equal(sys.den, true_sys.den)
def test_discrete_bode(self): # Create a simple discrete time system and check the calculation sys = TransferFunction([1], [1, 0.5], 1) omega = [1, 2, 3] mag_out, phase_out, omega_out = bode(sys, omega) H_z = list(map(lambda w: 1. / (np.exp(1.j * w) + 0.5), omega)) np.testing.assert_array_almost_equal(omega, omega_out) np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z)) np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
def test_dcgain_discr(self): """Test DC gain for discrete-time transfer functions""" # static gain sys = TransferFunction(6, 3, True) np.testing.assert_equal(sys.dcgain(), 2) # averaging filter sys = TransferFunction(0.5, [1, -0.5], True) np.testing.assert_almost_equal(sys.dcgain(), 1) # differencer sys = TransferFunction(1, [1, -1], True) with pytest.warns(RuntimeWarning, match="divide by zero"): np.testing.assert_equal(sys.dcgain(), np.inf) # summer sys = TransferFunction([1, -1], [1], True) np.testing.assert_equal(sys.dcgain(), 0)
def testMinreal(self): """Try the minreal function, and also test easy entry by creation of a Laplace variable s""" s = TransferFunction([1, 0], [1]) h = (s + 1) * (s + 2.00000000001) / (s + 2) / (s**2 + s + 1) hm = h.minreal() hr = (s + 1) / (s**2 + s + 1) np.testing.assert_array_almost_equal(hm.num[0][0], hr.num[0][0]) np.testing.assert_array_almost_equal(hm.den[0][0], hr.den[0][0])
def test_div(self): # Make sure that sampling times work correctly sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1], None) sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], True) sys3 = sys1 / sys2 assert sys3.dt is True sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], 0.5) sys3 = sys1 / sys2 assert sys3.dt == 0.5 sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1], 0.1) with pytest.raises(ValueError): TransferFunction.__truediv__(sys1, sys2) sys1 = sample_system(rss(4, 1, 1), 0.5) sys3 = TransferFunction.__rtruediv__(sys2, sys1) assert sys3.dt == 0.5
def test_common_den_nonproper(self): """ Test _common_den with order(num)>order(den) """ tf1 = TransferFunction( [[[1., 2., 3.]], [[1., 2.]]], [[[1., -2.]], [[1., -3.]]]) tf2 = TransferFunction( [[[1., 2.]], [[1., 2., 3.]]], [[[1., -2.]], [[1., -3.]]]) common_den_ref = np.array([[1., -5., 6.]]) np.testing.assert_raises(ValueError, tf1._common_den) np.testing.assert_raises(ValueError, tf2._common_den) _, den1, _ = tf1._common_den(allow_nonproper=True) np.testing.assert_array_almost_equal(den1, common_den_ref) _, den2, _ = tf2._common_den(allow_nonproper=True) np.testing.assert_array_almost_equal(den2, common_den_ref)
def testOperators(self): # get two SISO transfer functions h1 = TransferFunction([1], [1, 2, 2]) h2 = TransferFunction([1], [0.1, 1]) omega = np.logspace(-1, 2, 10) f1 = FRD(h1, omega) f2 = FRD(h2, omega) np.testing.assert_array_almost_equal( (f1 + f2).frequency_response([0.1, 1.0, 10])[0], (h1 + h2).frequency_response([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( (f1 + f2).frequency_response([0.1, 1.0, 10])[1], (h1 + h2).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (f1 - f2).frequency_response([0.1, 1.0, 10])[0], (h1 - h2).frequency_response([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( (f1 - f2).frequency_response([0.1, 1.0, 10])[1], (h1 - h2).frequency_response([0.1, 1.0, 10])[1]) # multiplication and division np.testing.assert_array_almost_equal( (f1 * f2).frequency_response([0.1, 1.0, 10])[1], (h1 * h2).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (f1 / f2).frequency_response([0.1, 1.0, 10])[1], (h1 / h2).frequency_response([0.1, 1.0, 10])[1]) # with default conversion from scalar np.testing.assert_array_almost_equal( (f1 * 1.5).frequency_response([0.1, 1.0, 10])[1], (h1 * 1.5).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (f1 / 1.7).frequency_response([0.1, 1.0, 10])[1], (h1 / 1.7).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (2.2 * f2).frequency_response([0.1, 1.0, 10])[1], (2.2 * h2).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (1.3 / f2).frequency_response([0.1, 1.0, 10])[1], (1.3 / h2).frequency_response([0.1, 1.0, 10])[1])
def testMatrixMult(self): """MIMO transfer functions should be multiplyable by constant matrices""" s = TransferFunction([1, 0], [1]) b0 = 0.2 b1 = 0.1 b2 = 0.5 a0 = 2.3 a1 = 6.3 a2 = 3.6 a3 = 1.0 h = (b0 + b1 * s + b2 * s**2) / (a0 + a1 * s + a2 * s**2 + a3 * s**3) H = TransferFunction([[h.num[0][0]], [(h * s).num[0][0]]], [[h.den[0][0]], [h.den[0][0]]]) H1 = (np.matrix([[1.0, 0]]) * H).minreal() H2 = (np.matrix([[0, 1.0]]) * H).minreal() np.testing.assert_array_almost_equal(H.num[0][0], H1.num[0][0]) np.testing.assert_array_almost_equal(H.den[0][0], H1.den[0][0]) np.testing.assert_array_almost_equal(H.num[1][0], H2.num[0][0]) np.testing.assert_array_almost_equal(H.den[1][0], H2.den[0][0])
def testMIMO(self): """Test conversion of a single input, two-output state-space system against the same TF""" s = TransferFunction([1, 0], [1]) b0 = 0.2 b1 = 0.1 b2 = 0.5 a0 = 2.3 a1 = 6.3 a2 = 3.6 a3 = 1.0 h = (b0 + b1 * s + b2 * s**2) / (a0 + a1 * s + a2 * s**2 + a3 * s**3) H = TransferFunction([[h.num[0][0]], [(h * s).num[0][0]]], [[h.den[0][0]], [h.den[0][0]]]) sys = _convertToStateSpace(H) H2 = _convertToTransferFunction(sys) np.testing.assert_array_almost_equal(H.num[0][0], H2.num[0][0]) np.testing.assert_array_almost_equal(H.den[0][0], H2.den[0][0]) np.testing.assert_array_almost_equal(H.num[1][0], H2.num[1][0]) np.testing.assert_array_almost_equal(H.den[1][0], H2.den[1][0])
def test_mag_phase_omega(self): # test for bug reported in gh-58 sys = TransferFunction(15, [1, 6, 11, 6]) out = stability_margins(sys) omega = np.logspace(-1, 1, 100) mag, phase, omega = sys.freqresp(omega) out2 = stability_margins((mag, phase * 180 / np.pi, omega)) ind = [0, 1, 3, 4] # indices of gm, pm, wg, wp -- ignore sm marg1 = np.array(out)[ind] marg2 = np.array(out2)[ind] np.testing.assert_array_almost_equal(marg1, marg2, 4)
def test_mag_phase_omega(): """Test for bug reported in gh-58""" sys = TransferFunction(15, [1, 6, 11, 6]) out = stability_margins(sys) omega = np.logspace(-2, 2, 1000) mag, phase, omega = sys.frequency_response(omega) out2 = stability_margins((mag, phase * 180 / np.pi, omega)) ind = [0, 1, 3, 4] # indices of gm, pm, wg, wp -- ignore sm marg1 = np.array(out)[ind] marg2 = np.array(out2)[ind] assert_allclose(marg1, marg2, atol=1.5e-3)
def test_evalfr_deprecated(self): sys = TransferFunction([1., 3., 5], [1., 6., 2., -1]) # Deprecated version of the call (should generate warning) import warnings with warnings.catch_warnings(): # Make warnings generate an exception warnings.simplefilter('error') # Make sure that we get a pending deprecation warning self.assertRaises(PendingDeprecationWarning, sys.evalfr, 1.)
def testMinreal2(self): """This one gave a problem, due to poly([]) giving simply 1 instead of numpy.array([1])""" s = TransferFunction([1, 0], [1]) G = 6205 / (s * (s**2 + 13 * s + 1281)) Heq = G.feedback(1) H1 = 1 / (s + 5) H2a = Heq / H1 H2b = H2a.minreal() hr = 6205 / (s**2 + 8 * s + 1241) np.testing.assert_array_almost_equal(H2b.num[0][0], hr.num[0][0]) np.testing.assert_array_almost_equal(H2b.den[0][0], hr.den[0][0])
def test_to_pandas(): # Create a SISO frequency response h1 = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 10) resp = FRD(h1, omega) # Convert to pandas df = resp.to_pandas() # Check to make sure the data make senses np.testing.assert_equal(df['omega'], resp.omega) np.testing.assert_equal(df['H_{y[0], u[0]}'], resp.fresp[0, 0])
def testSISOtf(self): # get a SISO transfer function h = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 10) frd = FRD(h, omega) assert isinstance(frd, FRD) mag1, phase1, omega1 = frd.frequency_response([1.0]) mag2, phase2, omega2 = h.frequency_response([1.0]) np.testing.assert_array_almost_equal(mag1, mag2) np.testing.assert_array_almost_equal(phase1, phase2) np.testing.assert_array_almost_equal(omega1, omega2)
def testFeedback(self): h1 = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 10) f1 = FRD(h1, omega) np.testing.assert_array_almost_equal( f1.feedback(1).frequency_response([0.1, 1.0, 10])[0], h1.feedback(1).frequency_response([0.1, 1.0, 10])[0]) # Make sure default argument also works np.testing.assert_array_almost_equal( f1.feedback().frequency_response([0.1, 1.0, 10])[0], h1.feedback().frequency_response([0.1, 1.0, 10])[0])