Skip to content

Commit 6eea2f1

Browse files
committed
Move _tf_close_coeff back to testing realm and make better use of assertion messages
1 parent ebff125 commit 6eea2f1

5 files changed

Lines changed: 91 additions & 102 deletions

File tree

control/tests/bdalg_test.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
1-
"""bdalg_test.py - test suite for block diagram algebra
1+
"""bdalg_test.py - test suite for block diagram algebra.
22
33
RMM, 30 Mar 2011 (based on TestBDAlg from v0.4a)
44
"""
55

6+
import control as ctrl
67
import numpy as np
7-
from numpy import sort
88
import pytest
9-
10-
import control as ctrl
11-
from control.xferfcn import TransferFunction, _tf_close_coeff
9+
from control.bdalg import _ensure_tf, append, connect, feedback
10+
from control.lti import poles, zeros
1211
from control.statesp import StateSpace
13-
from control.bdalg import feedback, append, connect
14-
from control.lti import zeros, poles
15-
from control.bdalg import _ensure_tf
12+
from control.tests.conftest import assert_tf_close_coeff
13+
from control.xferfcn import TransferFunction
14+
from numpy import sort
1615

1716

1817
class TestFeedback:
19-
"""These are tests for the feedback function in bdalg.py. Currently, some
20-
of the tests are not implemented, or are not working properly. TODO: these
21-
need to be fixed."""
18+
"""Tests for the feedback function in bdalg.py."""
2219

2320
@pytest.fixture
2421
def tsys(self):
@@ -180,7 +177,7 @@ def testTFTF(self, tsys):
180177
[[[1., 4., 9., 8., 5.]]])
181178

182179
def testLists(self, tsys):
183-
"""Make sure that lists of various lengths work for operations"""
180+
"""Make sure that lists of various lengths work for operations."""
184181
sys1 = ctrl.tf([1, 1], [1, 2])
185182
sys2 = ctrl.tf([1, 3], [1, 4])
186183
sys3 = ctrl.tf([1, 5], [1, 6])
@@ -237,7 +234,7 @@ def testLists(self, tsys):
237234
sort(zeros(sys1 + sys2 + sys3 + sys4 + sys5)))
238235

239236
def testMimoSeries(self, tsys):
240-
"""regression: bdalg.series reverses order of arguments"""
237+
"""regression: bdalg.series reverses order of arguments."""
241238
g1 = ctrl.ss([], [], [], [[1, 2], [0, 3]])
242239
g2 = ctrl.ss([], [], [], [[1, 0], [2, 3]])
243240
ref = g2 * g1
@@ -430,9 +427,9 @@ class TestEnsureTf:
430427
],
431428
)
432429
def test_ensure(self, arraylike_or_tf, dt, tf):
433-
"""Test nominal cases"""
430+
"""Test nominal cases."""
434431
ensured_tf = _ensure_tf(arraylike_or_tf, dt)
435-
assert _tf_close_coeff(tf, ensured_tf)
432+
assert_tf_close_coeff(tf, ensured_tf)
436433

437434
@pytest.mark.parametrize(
438435
"arraylike_or_tf, dt, exception",
@@ -460,7 +457,7 @@ def test_ensure(self, arraylike_or_tf, dt, tf):
460457
],
461458
)
462459
def test_error_ensure(self, arraylike_or_tf, dt, exception):
463-
"""Test error cases"""
460+
"""Test error cases."""
464461
with pytest.raises(exception):
465462
_ensure_tf(arraylike_or_tf, dt)
466463

@@ -624,7 +621,7 @@ class TestTfCombineSplit:
624621
def test_combine_tf(self, tf_array, tf):
625622
"""Test combining transfer functions."""
626623
tf_combined = ctrl.combine_tf(tf_array)
627-
assert _tf_close_coeff(tf_combined, tf)
624+
assert_tf_close_coeff(tf_combined, tf)
628625

629626
@pytest.mark.parametrize(
630627
"tf_array, tf",
@@ -712,12 +709,12 @@ def test_split_tf(self, tf_array, tf):
712709
# Test entry-by-entry
713710
for i in range(tf_split.shape[0]):
714711
for j in range(tf_split.shape[1]):
715-
assert _tf_close_coeff(
712+
assert_tf_close_coeff(
716713
tf_split[i, j],
717714
tf_array[i, j],
718715
)
719716
# Test combined
720-
assert _tf_close_coeff(
717+
assert_tf_close_coeff(
721718
ctrl.combine_tf(tf_split),
722719
ctrl.combine_tf(tf_array),
723720
)

control/tests/conftest.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""conftest.py - pytest local plugins and fixtures"""
1+
"""conftest.py - pytest local plugins, fixtures, marks and functions."""
22

33
import os
44
from contextlib import contextmanager
@@ -9,6 +9,7 @@
99

1010
import control
1111

12+
1213
# some common pytest marks. These can be used as test decorators or in
1314
# pytest.param(marks=)
1415
slycotonly = pytest.mark.skipif(
@@ -61,7 +62,7 @@ def mplcleanup():
6162

6263
@pytest.fixture(scope="function")
6364
def legacy_plot_signature():
64-
"""Turn off warnings for calls to plotting functions with old signatures"""
65+
"""Turn off warnings for calls to plotting functions with old signatures."""
6566
import warnings
6667
warnings.filterwarnings(
6768
'ignore', message='passing systems .* is deprecated',
@@ -75,14 +76,53 @@ def legacy_plot_signature():
7576

7677
@pytest.fixture(scope="function")
7778
def ignore_future_warning():
78-
"""Turn off warnings for functions that generate FutureWarning"""
79+
"""Turn off warnings for functions that generate FutureWarning."""
7980
import warnings
8081
warnings.filterwarnings(
8182
'ignore', message='.*deprecated', category=FutureWarning)
8283
yield
8384
warnings.resetwarnings()
84-
8585

86-
# Allow pytest.mark.slow to mark slow tests (skip with pytest -m "not slow")
86+
8787
def pytest_configure(config):
88+
"""Allow pytest.mark.slow to mark slow tests.
89+
90+
skip with pytest -m "not slow"
91+
"""
8892
config.addinivalue_line("markers", "slow: mark test as slow to run")
93+
94+
95+
def assert_tf_close_coeff(tf_a, tf_b, rtol=1e-5, atol=1e-8):
96+
"""Check if two transfer functions have close coefficients.
97+
98+
Parameters
99+
----------
100+
tf_a : TransferFunction
101+
First transfer function.
102+
tf_b : TransferFunction
103+
Second transfer function.
104+
rtol : float
105+
Relative tolerance for ``np.testing.assert_allclose``.
106+
atol : float
107+
Absolute tolerance for ``np.testing.assert_allclose``.
108+
109+
Raises
110+
------
111+
AssertionError
112+
"""
113+
# Check number of outputs and inputs
114+
assert tf_a.noutputs == tf_b.noutputs
115+
assert tf_a.ninputs == tf_b.ninputs
116+
# Check timestep
117+
assert tf_a.dt == tf_b.dt
118+
# Check coefficient arrays
119+
for i in range(tf_a.noutputs):
120+
for j in range(tf_a.ninputs):
121+
np.testing.assert_allclose(
122+
tf_a.num[i][j],
123+
tf_b.num[i][j],
124+
rtol=rtol, atol=atol)
125+
np.testing.assert_allclose(
126+
tf_a.den[i][j],
127+
tf_b.den[i][j],
128+
rtol=rtol, atol=atol)

control/tests/statesp_test.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@
77
convert to pytest
88
"""
99

10-
import numpy as np
11-
from numpy.testing import assert_array_almost_equal
12-
import pytest
1310
import operator
14-
from numpy.linalg import solve
15-
from scipy.linalg import block_diag, eigvals
1611

1712
import control as ct
13+
import numpy as np
14+
import pytest
1815
from control.config import defaults
1916
from control.dtime import sample_system
2017
from control.lti import evalfr
21-
from control.statesp import StateSpace, _convert_to_statespace, tf2ss, \
22-
_statesp_defaults, _rss_generate, linfnorm, ss, rss, drss
23-
from control.xferfcn import TransferFunction, ss2tf, _tf_close_coeff
18+
from control.statesp import (StateSpace, _convert_to_statespace, _rss_generate,
19+
_statesp_defaults, drss, linfnorm, rss, ss, tf2ss)
20+
from control.xferfcn import TransferFunction, ss2tf
21+
from numpy.linalg import solve
22+
from numpy.testing import assert_array_almost_equal
23+
from scipy.linalg import block_diag, eigvals
2424

25-
from .conftest import editsdefaults, slycotonly
25+
from .conftest import assert_tf_close_coeff, editsdefaults, slycotonly
2626

2727

2828
class TestStateSpace:
@@ -387,7 +387,7 @@ def test_add_sub_mimo_siso(self):
387387
(StateSpace.__rsub__, -expected_sub),
388388
]:
389389
result = op(ss_mimo, ss_siso)
390-
assert _tf_close_coeff(
390+
assert_tf_close_coeff(
391391
expected.minreal(),
392392
ss2tf(result).minreal(),
393393
)
@@ -407,7 +407,7 @@ def test_add_sub_mimo_siso(self):
407407
(StateSpace.__rsub__, -expected_sub),
408408
]:
409409
result = op(ss_siso, np.eye(2))
410-
assert _tf_close_coeff(
410+
assert_tf_close_coeff(
411411
expected.minreal(),
412412
ss2tf(result).minreal(),
413413
)
@@ -482,7 +482,7 @@ def test_add_sub_mimo_siso(self):
482482
)
483483
def test_mul_mimo_siso(self, left, right, expected):
484484
result = tf2ss(left).__mul__(right)
485-
assert _tf_close_coeff(
485+
assert_tf_close_coeff(
486486
expected.minreal(),
487487
ss2tf(result).minreal(),
488488
)
@@ -557,7 +557,7 @@ def test_mul_mimo_siso(self, left, right, expected):
557557
)
558558
def test_rmul_mimo_siso(self, left, right, expected):
559559
result = tf2ss(right).__rmul__(left)
560-
assert _tf_close_coeff(
560+
assert_tf_close_coeff(
561561
expected.minreal(),
562562
ss2tf(result).minreal(),
563563
)
@@ -587,13 +587,13 @@ def test_pow(self, sys222, sys322):
587587
# matrices instead.
588588
result = (sys * sys**-1).minreal()
589589
expected = StateSpace([], [], [], np.eye(2), dt=0)
590-
assert _tf_close_coeff(
590+
assert_tf_close_coeff(
591591
ss2tf(expected).minreal(),
592592
ss2tf(result).minreal(),
593593
)
594594
result = (sys**-1 * sys).minreal()
595595
expected = StateSpace([], [], [], np.eye(2), dt=0)
596-
assert _tf_close_coeff(
596+
assert_tf_close_coeff(
597597
ss2tf(expected).minreal(),
598598
ss2tf(result).minreal(),
599599
)
@@ -619,14 +619,14 @@ def test_truediv(self, sys222, sys322):
619619
# Divide by self
620620
result = (sys.__truediv__(sys)).minreal()
621621
expected = StateSpace([], [], [], np.eye(2), dt=0)
622-
assert _tf_close_coeff(
622+
assert_tf_close_coeff(
623623
ss2tf(expected).minreal(),
624624
ss2tf(result).minreal(),
625625
)
626626
# Divide by TF
627627
result = sys.__truediv__(TransferFunction.s)
628628
expected = ss2tf(sys) / TransferFunction.s
629-
assert _tf_close_coeff(
629+
assert_tf_close_coeff(
630630
expected.minreal(),
631631
ss2tf(result).minreal(),
632632
)
@@ -637,22 +637,22 @@ def test_rtruediv(self, sys222, sys322):
637637
for sys in [sys222, sys322]:
638638
result = (sys.__rtruediv__(sys)).minreal()
639639
expected = StateSpace([], [], [], np.eye(2), dt=0)
640-
assert _tf_close_coeff(
640+
assert_tf_close_coeff(
641641
ss2tf(expected).minreal(),
642642
ss2tf(result).minreal(),
643643
)
644644
# Divide TF by SS
645645
result = sys.__rtruediv__(TransferFunction.s)
646646
expected = TransferFunction.s / sys
647-
assert _tf_close_coeff(
647+
assert_tf_close_coeff(
648648
expected.minreal(),
649649
result.minreal(),
650650
)
651651
# Divide array by SS
652652
sys = tf2ss(TransferFunction([1, 2], [2, 1]))
653653
result = sys.__rtruediv__(np.eye(2))
654654
expected = TransferFunction([2, 1], [1, 2]) * np.eye(2)
655-
assert _tf_close_coeff(
655+
assert_tf_close_coeff(
656656
expected.minreal(),
657657
ss2tf(result).minreal(),
658658
)

control/tests/xferfcn_test.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
import operator
77
import re
88

9+
import control as ct
910
import numpy as np
1011
import pytest
11-
12-
import control as ct
1312
from control import (StateSpace, TransferFunction, defaults, evalfr, isctime,
1413
isdtime, reset_defaults, rss, sample_system, set_defaults,
1514
ss, ss2tf, tf, tf2ss, zpk)
1615
from control.statesp import _convert_to_statespace
17-
from control.tests.conftest import slycotonly
18-
from control.xferfcn import _convert_to_transfer_function, _tf_close_coeff
16+
from control.tests.conftest import assert_tf_close_coeff, slycotonly
17+
from control.xferfcn import _convert_to_transfer_function
1918

2019

2120
class TestXferFcn:
@@ -428,7 +427,7 @@ def test_add_sub_mimo_siso(self):
428427
[op(tf_arr[1, 0], tf_siso), op(tf_arr[1, 1], tf_siso)],
429428
])
430429
result = op(tf_mimo, tf_siso)
431-
assert _tf_close_coeff(expected.minreal(), result.minreal())
430+
assert_tf_close_coeff(expected.minreal(), result.minreal())
432431

433432
@pytest.mark.parametrize(
434433
"left, right, expected",
@@ -500,7 +499,7 @@ def test_add_sub_mimo_siso(self):
500499
def test_mul_mimo_siso(self, left, right, expected):
501500
"""Test multiplication of a MIMO and a SISO system."""
502501
result = left.__mul__(right)
503-
assert _tf_close_coeff(expected.minreal(), result.minreal())
502+
assert_tf_close_coeff(expected.minreal(), result.minreal())
504503

505504
@pytest.mark.parametrize(
506505
"left, right, expected",
@@ -572,7 +571,7 @@ def test_mul_mimo_siso(self, left, right, expected):
572571
def test_rmul_mimo_siso(self, left, right, expected):
573572
"""Test right multiplication of a MIMO and a SISO system."""
574573
result = right.__rmul__(left)
575-
assert _tf_close_coeff(expected.minreal(), result.minreal())
574+
assert_tf_close_coeff(expected.minreal(), result.minreal())
576575

577576
@pytest.mark.parametrize(
578577
"left, right, expected",
@@ -609,7 +608,7 @@ def test_rmul_mimo_siso(self, left, right, expected):
609608
def test_truediv_mimo_siso(self, left, right, expected):
610609
"""Test true division of a MIMO and a SISO system."""
611610
result = left.__truediv__(right)
612-
assert _tf_close_coeff(expected.minreal(), result.minreal())
611+
assert_tf_close_coeff(expected.minreal(), result.minreal())
613612

614613
@pytest.mark.parametrize(
615614
"left, right, expected",
@@ -635,7 +634,7 @@ def test_truediv_mimo_siso(self, left, right, expected):
635634
def test_rtruediv_mimo_siso(self, left, right, expected):
636635
"""Test right true division of a MIMO and a SISO system."""
637636
result = right.__rtruediv__(left)
638-
assert _tf_close_coeff(expected.minreal(), result.minreal())
637+
assert_tf_close_coeff(expected.minreal(), result.minreal())
639638

640639
@pytest.mark.parametrize("named", [False, True])
641640
def test_slice(self, named):
@@ -932,9 +931,9 @@ def test_append(self):
932931
],
933932
)
934933
tf_appended_1 = tf1.append(tf2)
935-
assert _tf_close_coeff(tf_exp_1, tf_appended_1)
934+
assert_tf_close_coeff(tf_exp_1, tf_appended_1)
936935
tf_appended_2 = tf1.append(tf2).append(tf3)
937-
assert _tf_close_coeff(tf_exp_2, tf_appended_2)
936+
assert_tf_close_coeff(tf_exp_2, tf_appended_2)
938937

939938
@slycotonly
940939
def test_convert_to_transfer_function(self):

0 commit comments

Comments
 (0)