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

Lines changed: 68 additions & 35 deletions
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

Lines changed: 28 additions & 6 deletions
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

Lines changed: 17 additions & 11 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy