Skip to content

Commit cca4d13

Browse files
committed
add string-based indexing of FRD systems
1 parent e52fca6 commit cca4d13

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

control/frdata.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
FRD data.
1111
"""
1212

13+
from collections.abc import Iterable
1314
from copy import copy
1415
from warnings import warn
1516

@@ -21,7 +22,7 @@
2122
from . import config
2223
from .exception import pandas_check
2324
from .iosys import InputOutputSystem, NamedSignal, _process_iosys_keywords, \
24-
common_timebase
25+
_process_subsys_index, common_timebase
2526
from .lti import LTI, _process_frequency_response
2627

2728
__all__ = ['FrequencyResponseData', 'FRD', 'frd']
@@ -597,9 +598,25 @@ def __iter__(self):
597598
return iter((self.omega, fresp))
598599
return iter((np.abs(fresp), np.angle(fresp), self.omega))
599600

600-
# Implement (thin) getitem to allow access via legacy indexing
601-
def __getitem__(self, index):
602-
return list(self.__iter__())[index]
601+
def __getitem__(self, key):
602+
if not isinstance(key, Iterable) or len(key) != 2:
603+
# Implement (thin) getitem to allow access via legacy indexing
604+
return list(self.__iter__())[key]
605+
606+
# Convert signal names to integer offsets (via NamedSignal object)
607+
iomap = NamedSignal(
608+
self.fresp[:, :, 0], self.output_labels, self.input_labels)
609+
indices = iomap._parse_key(key)
610+
outdx, outputs = _process_subsys_index(indices[0], self.output_labels)
611+
inpdx, inputs = _process_subsys_index(indices[1], self.input_labels)
612+
613+
# Create the system name
614+
sysname = config.defaults['iosys.indexed_system_name_prefix'] + \
615+
self.name + config.defaults['iosys.indexed_system_name_suffix']
616+
617+
return FrequencyResponseData(
618+
self.fresp[outdx, :][:, inpdx], self.omega, self.dt,
619+
inputs=inputs, outputs=outputs, name=sysname)
603620

604621
# Implement (thin) len to emulate legacy testing interface
605622
def __len__(self):

control/tests/lti_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,49 @@ def test_squeeze_exceptions(self, fcn):
303303
sys([[0.1j, 1j], [1j, 10j]])
304304
with pytest.raises(ValueError, match="must be 1D"):
305305
evalfr(sys, [[0.1j, 1j], [1j, 10j]])
306+
307+
308+
@pytest.mark.parametrize(
309+
"outdx, inpdx, key",
310+
[('y[0]', 'u[1]', (0, 1)),
311+
(['y[0]'], ['u[1]'], (0, 1)),
312+
(slice(0, 1, 1), slice(1, 2, 1), (0, 1)),
313+
(['y[0]', 'y[1]'], ['u[1]', 'u[2]'], ([0, 1], [1, 2])),
314+
([0, 'y[1]'], ['u[1]', 2], ([0, 1], [1, 2])),
315+
(slice(0, 2, 1), slice(1, 3, 1), ([0, 1], [1, 2])),
316+
(['y[2]', 'y[1]'], ['u[2]', 'u[0]'], ([2, 1], [2, 0])),
317+
])
318+
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd])
319+
def test_subsys_indexing(fcn, outdx, inpdx, key):
320+
# Construct the base system and subsystem
321+
sys = ct.rss(4, 3, 3)
322+
subsys = sys[key]
323+
324+
# Construct the system to be test
325+
match fcn:
326+
case ct.frd:
327+
omega = np.logspace(-1, 1)
328+
sys = fcn(sys, omega)
329+
subsys_chk = fcn(subsys, omega)
330+
case _:
331+
sys = fcn(sys)
332+
subsys_chk = fcn(subsys)
333+
334+
# Construct the subsystem
335+
subsys_fcn = sys[outdx, inpdx]
336+
337+
# Check to make sure everythng matches up
338+
match fcn:
339+
case ct.frd:
340+
np.testing.assert_almost_equal(
341+
subsys_fcn.response, subsys_chk.response)
342+
case ct.ss:
343+
np.testing.assert_almost_equal(subsys_fcn.A, subsys_chk.A)
344+
np.testing.assert_almost_equal(subsys_fcn.B, subsys_chk.B)
345+
np.testing.assert_almost_equal(subsys_fcn.C, subsys_chk.C)
346+
np.testing.assert_almost_equal(subsys_fcn.D, subsys_chk.D)
347+
case ct.tf:
348+
omega = np.logspace(-1, 1)
349+
np.testing.assert_almost_equal(
350+
subsys_fcn.frequency_response(omega).response,
351+
subsys_chk.frequency_response(omega).response)

0 commit comments

Comments
 (0)