Move _tf_close_coeff back to testing realm and make better use of assertion messages by bnavigator · Pull Request #1109 · python-control/python-control

Before:

___________________________ TestStateSpace.test_pow ____________________________

self = <control.tests.statesp_test.TestStateSpace object at 0x108ea1580>
sys222 = StateSpace(
array([[ 4.,  1.],
       [ 2., -3.]]),
array([[ 5.,  2.],
       [-3., -3.]]),
array([[ 2., -4.],
       [ 0.,  1.]]),
array([[ 3.,  2.],
       [ 1., -1.]]),
states=2, outputs=2, inputs=2)
sys322 = StateSpace(
array([[-3.,  4.,  2.],
       [-1., -3.,  0.],
       [ 2.,  5.,  3.]]),
array([[ 1.,  4.],
       [-3., ..., -3.],
       [ 1.,  4.,  3.]]),
array([[-2.,  4.],
       [ 0.,  1.]]),
name='sys322', states=3, outputs=2, inputs=2)

    @slycotonly
    def test_pow(self, sys222, sys322):
        """Test state space powers."""
        for sys in [sys222, sys322]:
            # Power of 0
            result = sys**0
            expected = StateSpace([], [], [], np.eye(2), dt=0)
            np.testing.assert_allclose(expected.A, result.A)
            np.testing.assert_allclose(expected.B, result.B)
            np.testing.assert_allclose(expected.C, result.C)
            np.testing.assert_allclose(expected.D, result.D)
            # Power of 1
            result = sys**1
            expected = sys
            np.testing.assert_allclose(expected.A, result.A)
            np.testing.assert_allclose(expected.B, result.B)
            np.testing.assert_allclose(expected.C, result.C)
            np.testing.assert_allclose(expected.D, result.D)
            # Power of -1 (inverse of biproper system)
            # Testing transfer function representations to avoid the
            # non-uniqueness of the state-space representation. Once MIMO
            # canonical forms are supported, can check canonical state-space
            # matrices instead.
            result = (sys * sys**-1).minreal()
            expected = StateSpace([], [], [], np.eye(2), dt=0)
            assert _tf_close_coeff(
                ss2tf(expected).minreal(),
                ss2tf(result).minreal(),
            )
            result = (sys**-1 * sys).minreal()
            expected = StateSpace([], [], [], np.eye(2), dt=0)
>           assert _tf_close_coeff(
                ss2tf(expected).minreal(),
                ss2tf(result).minreal(),
            )
E           assert False
E            +  where False = _tf_close_coeff(TransferFunction(\n[[array([1.]), array([0.])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([1.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2), TransferFunction(\n[[array([1.]), array([-2.76395297e-15, -1.35541257e-13,  9.01576541e-13])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([  1.,   3.,  -9., -41.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2))
E            +    where TransferFunction(\n[[array([1.]), array([0.])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([1.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2) = minreal()
E            +      where minreal = TransferFunction(\n[[array([1.]), array([0.])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([1.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2).minreal
E            +        where TransferFunction(\n[[array([1.]), array([0.])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([1.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2) = ss2tf(StateSpace(\narray([], shape=(0, 0), dtype=float64),\narray([], shape=(0, 2), dtype=float64),\narray([], shape=(2, 0), dtype=float64),\narray([[1., 0.],\n       [0., 1.]]),\nstates=0, outputs=2, inputs=2))
E            +    and   TransferFunction(\n[[array([1.]), array([-2.76395297e-15, -1.35541257e-13,  9.01576541e-13])],\n [array([0.]), array([1.])]],\n[[array([1.]), array([  1.,   3.,  -9., -41.])],\n [array([1.]), array([1.])]],\noutputs=2, inputs=2) = minreal()
E            +      where minreal = TransferFunction(\n[[array([  1.,   3.,  -9., -41.]), \n  array([-2.76395297e-15, -1.35541257e-13,  9.01576541e-13])],\n ....,   3.,  -9., -41.]), array([  1.,   3.,  -9., -41.])],\n [array([1.]), array([1., 0., 0., 0.])]],\noutputs=2, inputs=2).minreal
E            +        where TransferFunction(\n[[array([  1.,   3.,  -9., -41.]), \n  array([-2.76395297e-15, -1.35541257e-13,  9.01576541e-13])],\n ....,   3.,  -9., -41.]), array([  1.,   3.,  -9., -41.])],\n [array([1.]), array([1., 0., 0., 0.])]],\noutputs=2, inputs=2) = ss2tf(StateSpace(\narray([[  4.86563613,  -0.22114886, -14.38268526],\n       [  3.86388385,  -1.64824725, -16.2652334 ],\n    ... [0.00000000e+00, 2.59786829e-16, 3.85014366e-15]]),\narray([[1., 0.],\n       [0., 1.]]),\nstates=3, outputs=2, inputs=2))

control/tests/statesp_test.py:596: AssertionError

After:

________________ TestStateSpace.test_pow_inv[sys322-inv*sys] __________________

self = <control.tests.statesp_test.TestStateSpace object at 0x10b398260>
request = <FixtureRequest for <Function test_pow_inv[sys322-inv*sys]>>
sysname = 'sys322', order = 'inv*sys'

    @slycotonly
    @pytest.mark.parametrize("order", ["inv*sys", "sys*inv"])
    @pytest.mark.parametrize("sysname", ["sys222", "sys322"])
    def test_pow_inv(self, request, sysname, order):
        """Power of -1 (inverse of biproper system).
    
        Testing transfer function representations to avoid the
        non-uniqueness of the state-space representation. Once MIMO
        canonical forms are supported, can check canonical state-space
        matrices instead.
        """
        sys = request.getfixturevalue(sysname)
        if order == "inv*sys":
            result = (sys**-1 * sys).minreal()
        else:
            result = (sys * sys**-1).minreal()
        expected = StateSpace([], [], [], np.eye(2), dt=0)
>       assert_tf_close_coeff(
            ss2tf(expected).minreal(),
            ss2tf(result).minreal())

control/tests/statesp_test.py:601: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tf_a = TransferFunction(
[[array([1.]), array([0.])],
 [array([0.]), array([1.])]],
[[array([1.]), array([1.])],
 [array([1.]), array([1.])]],
outputs=2, inputs=2)
tf_b = TransferFunction(
[[array([1.]), array([-2.76395297e-15, -1.35541257e-13,  9.01576541e-13])],
 [array([0.]), array([1.])]],
[[array([1.]), array([  1.,   3.,  -9., -41.])],
 [array([1.]), array([1.])]],
outputs=2, inputs=2)
rtol = 1e-05, atol = 1e-08

    def assert_tf_close_coeff(tf_a, tf_b, rtol=1e-5, atol=1e-8):
        """Check if two transfer functions have close coefficients.
    
        Parameters
        ----------
        tf_a : TransferFunction
            First transfer function.
        tf_b : TransferFunction
            Second transfer function.
        rtol : float
            Relative tolerance for ``np.testing.assert_allclose``.
        atol : float
            Absolute tolerance for ``np.testing.assert_allclose``.
    
        Raises
        ------
        AssertionError
        """
        # Check number of outputs and inputs
        assert tf_a.noutputs == tf_b.noutputs
        assert tf_a.ninputs == tf_b.ninputs
        # Check timestep
        assert  tf_a.dt == tf_b.dt
        # Check coefficient arrays
        for i in range(tf_a.noutputs):
            for j in range(tf_a.ninputs):
>               np.testing.assert_allclose(
                    tf_a.num[i][j],
                    tf_b.num[i][j],
                    rtol=rtol, atol=atol)
E               AssertionError: 
E               Not equal to tolerance rtol=1e-05, atol=1e-08
E               
E               (shapes (1,), (3,) mismatch)
E                ACTUAL: array([0.])
E                DESIRED: array([-2.763953e-15, -1.355413e-13,  9.015765e-13])