Skip to content

Commit ec7dc8a

Browse files
authored
Merge pull request #1091 from murrayrm/iosys_repr-07Dec2024
Update I/O system repr() and str()
2 parents a1791c9 + 0f0fad0 commit ec7dc8a

20 files changed

+2468
-388
lines changed

control/bdalg.py

+68-35
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def parallel(sys1, *sysn, **kwargs):
164164
or `y`). See :class:`InputOutputSystem` for more information.
165165
states : str, or list of str, optional
166166
List of names for system states. If not given, state names will be
167-
of of the form `x[i]` for interconnections of linear systems or
167+
of the form `x[i]` for interconnections of linear systems or
168168
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
169169
name : string, optional
170170
System name (used for specifying signals). If unspecified, a generic
@@ -511,7 +511,7 @@ def connect(sys, Q, inputv, outputv):
511511

512512
return Ytrim * sys * Utrim
513513

514-
def combine_tf(tf_array):
514+
def combine_tf(tf_array, **kwargs):
515515
"""Combine array-like of transfer functions into MIMO transfer function.
516516
517517
Parameters
@@ -527,6 +527,16 @@ def combine_tf(tf_array):
527527
TransferFunction
528528
Transfer matrix represented as a single MIMO TransferFunction object.
529529
530+
Other Parameters
531+
----------------
532+
inputs, outputs : str, or list of str, optional
533+
List of strings that name the individual signals. If not given,
534+
signal names will be of the form `s[i]` (where `s` is one of `u`,
535+
or `y`). See :class:`InputOutputSystem` for more information.
536+
name : string, optional
537+
System name (used for specifying signals). If unspecified, a generic
538+
name <sys[id]> is generated with a unique integer id.
539+
530540
Raises
531541
------
532542
ValueError
@@ -541,26 +551,34 @@ def combine_tf(tf_array):
541551
--------
542552
Combine two transfer functions
543553
544-
>>> s = control.TransferFunction.s
545-
>>> control.combine_tf([
546-
... [1 / (s + 1)],
547-
... [s / (s + 2)],
548-
... ])
549-
TransferFunction([[array([1])], [array([1, 0])]],
550-
[[array([1, 1])], [array([1, 2])]])
554+
>>> s = ct.tf('s')
555+
>>> ct.combine_tf(
556+
... [[1 / (s + 1)],
557+
... [s / (s + 2)]],
558+
... name='G'
559+
... )
560+
TransferFunction(
561+
[[array([1])],
562+
[array([1, 0])]],
563+
[[array([1, 1])],
564+
[array([1, 2])]],
565+
name='G', outputs=2, inputs=1)
551566
552567
Combine NumPy arrays with transfer functions
553568
554-
>>> control.combine_tf([
555-
... [np.eye(2), np.zeros((2, 1))],
556-
... [np.zeros((1, 2)), control.TransferFunction([1], [1, 0])],
557-
... ])
558-
TransferFunction([[array([1.]), array([0.]), array([0.])],
559-
[array([0.]), array([1.]), array([0.])],
560-
[array([0.]), array([0.]), array([1])]],
561-
[[array([1.]), array([1.]), array([1.])],
562-
[array([1.]), array([1.]), array([1.])],
563-
[array([1.]), array([1.]), array([1, 0])]])
569+
>>> ct.combine_tf(
570+
... [[np.eye(2), np.zeros((2, 1))],
571+
... [np.zeros((1, 2)), ct.tf([1], [1, 0])]],
572+
... name='G'
573+
... )
574+
TransferFunction(
575+
[[array([1.]), array([0.]), array([0.])],
576+
[array([0.]), array([1.]), array([0.])],
577+
[array([0.]), array([0.]), array([1])]],
578+
[[array([1.]), array([1.]), array([1.])],
579+
[array([1.]), array([1.]), array([1.])],
580+
[array([1.]), array([1.]), array([1, 0])]],
581+
name='G', outputs=3, inputs=3)
564582
"""
565583
# Find common timebase or raise error
566584
dt_list = []
@@ -616,10 +634,14 @@ def combine_tf(tf_array):
616634
"Mismatched number transfer function inputs in row "
617635
f"{row_index} of denominator."
618636
)
619-
return tf.TransferFunction(num, den, dt=dt)
637+
return tf.TransferFunction(num, den, dt=dt, **kwargs)
638+
620639

621640
def split_tf(transfer_function):
622-
"""Split MIMO transfer function into NumPy array of SISO tranfer functions.
641+
"""Split MIMO transfer function into NumPy array of SISO transfer functions.
642+
643+
System and signal names for the array of SISO transfer functions are
644+
copied from the MIMO system.
623645
624646
Parameters
625647
----------
@@ -635,21 +657,29 @@ def split_tf(transfer_function):
635657
--------
636658
Split a MIMO transfer function
637659
638-
>>> G = control.TransferFunction(
639-
... [
640-
... [[87.8], [-86.4]],
641-
... [[108.2], [-109.6]],
642-
... ],
643-
... [
644-
... [[1, 1], [1, 1]],
645-
... [[1, 1], [1, 1]],
646-
... ],
660+
>>> G = ct.tf(
661+
... [ [[87.8], [-86.4]],
662+
... [[108.2], [-109.6]] ],
663+
... [ [[1, 1], [1, 1]],
664+
... [[1, 1], [1, 1]], ],
665+
... name='G'
647666
... )
648-
>>> control.split_tf(G)
649-
array([[TransferFunction(array([87.8]), array([1, 1])),
650-
TransferFunction(array([-86.4]), array([1, 1]))],
651-
[TransferFunction(array([108.2]), array([1, 1])),
652-
TransferFunction(array([-109.6]), array([1, 1]))]], dtype=object)
667+
>>> ct.split_tf(G)
668+
array([[TransferFunction(
669+
array([87.8]),
670+
array([1, 1]),
671+
name='G', outputs=1, inputs=1), TransferFunction(
672+
array([-86.4]),
673+
array([1, 1]),
674+
name='G', outputs=1, inputs=1)],
675+
[TransferFunction(
676+
array([108.2]),
677+
array([1, 1]),
678+
name='G', outputs=1, inputs=1), TransferFunction(
679+
array([-109.6]),
680+
array([1, 1]),
681+
name='G', outputs=1, inputs=1)]],
682+
dtype=object)
653683
"""
654684
tf_split_lst = []
655685
for i_out in range(transfer_function.noutputs):
@@ -660,6 +690,9 @@ def split_tf(transfer_function):
660690
transfer_function.num_array[i_out, i_in],
661691
transfer_function.den_array[i_out, i_in],
662692
dt=transfer_function.dt,
693+
inputs=transfer_function.input_labels[i_in],
694+
outputs=transfer_function.output_labels[i_out],
695+
name=transfer_function.name
663696
)
664697
)
665698
tf_split_lst.append(row)

control/config.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ def _check_deprecation(self, key):
7373
else:
7474
return key
7575

76+
#
77+
# Context manager functionality
78+
#
79+
80+
def __call__(self, mapping):
81+
self.saved_mapping = dict()
82+
self.temp_mapping = mapping.copy()
83+
return self
84+
85+
def __enter__(self):
86+
for key, val in self.temp_mapping.items():
87+
if not key in self:
88+
raise ValueError(f"unknown parameter '{key}'")
89+
self.saved_mapping[key] = self[key]
90+
self[key] = val
91+
return self
92+
93+
def __exit__(self, exc_type, exc_val, exc_tb):
94+
for key, val in self.saved_mapping.items():
95+
self[key] = val
96+
del self.saved_mapping, self.temp_mapping
97+
return None
7698

7799
defaults = DefaultDict(_control_defaults)
78100

@@ -266,7 +288,7 @@ def use_legacy_defaults(version):
266288
Parameters
267289
----------
268290
version : string
269-
Version number of the defaults desired. Ranges from '0.1' to '0.8.4'.
291+
Version number of the defaults desired. Ranges from '0.1' to '0.10.1'.
270292
271293
Examples
272294
--------
@@ -279,26 +301,26 @@ def use_legacy_defaults(version):
279301
(major, minor, patch) = (None, None, None) # default values
280302

281303
# Early release tag format: REL-0.N
282-
match = re.match("REL-0.([12])", version)
304+
match = re.match(r"^REL-0.([12])$", version)
283305
if match: (major, minor, patch) = (0, int(match.group(1)), 0)
284306

285307
# Early release tag format: control-0.Np
286-
match = re.match("control-0.([3-6])([a-d])", version)
308+
match = re.match(r"^control-0.([3-6])([a-d])$", version)
287309
if match: (major, minor, patch) = \
288310
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
289311

290312
# Early release tag format: v0.Np
291-
match = re.match("[vV]?0.([3-6])([a-d])", version)
313+
match = re.match(r"^[vV]?0\.([3-6])([a-d])$", version)
292314
if match: (major, minor, patch) = \
293315
(0, int(match.group(1)), ord(match.group(2)) - ord('a') + 1)
294316

295317
# Abbreviated version format: vM.N or M.N
296-
match = re.match("([vV]?[0-9]).([0-9])", version)
318+
match = re.match(r"^[vV]?([0-9]*)\.([0-9]*)$", version)
297319
if match: (major, minor, patch) = \
298320
(int(match.group(1)), int(match.group(2)), 0)
299321

300322
# Standard version format: vM.N.P or M.N.P
301-
match = re.match("[vV]?([0-9]).([0-9]).([0-9])", version)
323+
match = re.match(r"^[vV]?([0-9]*)\.([0-9]*)\.([0-9]*)$", version)
302324
if match: (major, minor, patch) = \
303325
(int(match.group(1)), int(match.group(2)), int(match.group(3)))
304326

control/frdata.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ def __init__(self, *args, **kwargs):
285285
if self.squeeze not in (None, True, False):
286286
raise ValueError("unknown squeeze value")
287287

288-
# Process iosys keywords
289288
defaults = {
290289
'inputs': self.fresp.shape[1] if not getattr(
291290
self, 'input_index', None) else self.input_labels,
@@ -401,30 +400,37 @@ def __str__(self):
401400

402401
mimo = self.ninputs > 1 or self.noutputs > 1
403402
outstr = [f"{InputOutputSystem.__str__(self)}"]
403+
nl = "\n " if mimo else "\n"
404+
sp = " " if mimo else ""
404405

405406
for i in range(self.ninputs):
406407
for j in range(self.noutputs):
407408
if mimo:
408-
outstr.append("Input %i to output %i:" % (i + 1, j + 1))
409-
outstr.append('Freq [rad/s] Response')
410-
outstr.append('------------ ---------------------')
409+
outstr.append(
410+
"\nInput %i to output %i:" % (i + 1, j + 1))
411+
outstr.append(nl + 'Freq [rad/s] Response')
412+
outstr.append(sp + '------------ ---------------------')
411413
outstr.extend(
412-
['%12.3f %10.4g%+10.4gj' % (w, re, im)
414+
[sp + '%12.3f %10.4g%+10.4gj' % (w, re, im)
413415
for w, re, im in zip(self.omega,
414416
real(self.fresp[j, i, :]),
415417
imag(self.fresp[j, i, :]))])
416418

417419
return '\n'.join(outstr)
418420

419-
def __repr__(self):
420-
"""Loadable string representation,
421-
422-
limited for number of data points.
423-
"""
424-
return "FrequencyResponseData({d}, {w}{smooth})".format(
421+
def _repr_eval_(self):
422+
# Loadable format
423+
out = "FrequencyResponseData(\n{d},\n{w}{smooth}".format(
425424
d=repr(self.fresp), w=repr(self.omega),
426425
smooth=(self._ifunc and ", smooth=True") or "")
427426

427+
out += self._dt_repr()
428+
if len(labels := self._label_repr()) > 0:
429+
out += ",\n" + labels
430+
431+
out += ")"
432+
return out
433+
428434
def __neg__(self):
429435
"""Negate a transfer function."""
430436

0 commit comments

Comments
 (0)