Skip to content

Update I/O system repr() and str() #1091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f363e7b
updated iosys class/factory function documentation + docstring unit t…
murrayrm Dec 16, 2024
5076298
update state space system repr() to include signal/system names
murrayrm Dec 7, 2024
6f7cbfa
update transfer function repr() + signal/system names
murrayrm Dec 7, 2024
4cedb6c
update FRD repr() + copy signal/system names on sampling
murrayrm Dec 7, 2024
b0397e0
add unit tests for consistent systems repr() processing
murrayrm Dec 7, 2024
85772f5
add iosys_repr and set default __repr__ to short form
murrayrm Dec 15, 2024
9897ab2
use NumPy printoptions in LaTeX representations
murrayrm Dec 17, 2024
376c28e
refactor I/O system repr() processing via iosys_repr and _repr_<fmt>
murrayrm Dec 20, 2024
42eaff9
small docstring and comment updates
murrayrm Dec 20, 2024
964c3aa
expanded __str__ for IC systems (subsys list + connection map)
murrayrm Dec 22, 2024
9ca6833
add Parameters to NonlinearIOSystem.__str__() + avoid double empty lines
murrayrm Dec 22, 2024
ea7d3ed
Update split_tf and combine_tf docstrings + combine_tf kwargs
murrayrm Dec 28, 2024
1434540
add repr_gallery + update formatting for style and consistency
murrayrm Dec 31, 2024
b083212
add context manager functionality to config.defaults
murrayrm Dec 31, 2024
8402605
update latex processing + repr_gallery.ipynb
murrayrm Dec 31, 2024
7863690
update unit tests + tweak formatting for consistency
murrayrm Dec 31, 2024
e0a612f
change default repr to 'eval' + adjust HTML formatting
murrayrm Dec 31, 2024
5ae33b0
add iosys.repr_show_count and defualt to True
murrayrm Jan 4, 2025
32f65d6
add params to sys_nl in repr_gallery; fix combine/split_tf outputs
murrayrm Jan 4, 2025
41d92d3
address @murrayrm and preliminary @slivingston review comments
murrayrm Jan 8, 2025
0f0fad0
address final (?) @slivingston comments
murrayrm Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 68 additions & 35 deletions control/bdalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def parallel(sys1, *sysn, **kwargs):
or `y`). See :class:`InputOutputSystem` for more information.
states : str, or list of str, optional
List of names for system states. If not given, state names will be
of of the form `x[i]` for interconnections of linear systems or
of the form `x[i]` for interconnections of linear systems or
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
name : string, optional
System name (used for specifying signals). If unspecified, a generic
Expand Down Expand Up @@ -511,7 +511,7 @@ def connect(sys, Q, inputv, outputv):

return Ytrim * sys * Utrim

def combine_tf(tf_array):
def combine_tf(tf_array, **kwargs):
"""Combine array-like of transfer functions into MIMO transfer function.

Parameters
Expand All @@ -527,6 +527,16 @@ def combine_tf(tf_array):
TransferFunction
Transfer matrix represented as a single MIMO TransferFunction object.

Other Parameters
----------------
inputs, outputs : str, or list of str, optional
List of strings that name the individual signals. If not given,
signal names will be of the form `s[i]` (where `s` is one of `u`,
or `y`). See :class:`InputOutputSystem` for more information.
name : string, optional
System name (used for specifying signals). If unspecified, a generic
name <sys[id]> is generated with a unique integer id.

Raises
------
ValueError
Expand All @@ -541,26 +551,34 @@ def combine_tf(tf_array):
--------
Combine two transfer functions

>>> s = control.TransferFunction.s
>>> control.combine_tf([
... [1 / (s + 1)],
... [s / (s + 2)],
... ])
TransferFunction([[array([1])], [array([1, 0])]],
[[array([1, 1])], [array([1, 2])]])
>>> s = ct.tf('s')
>>> ct.combine_tf(
... [[1 / (s + 1)],
... [s / (s + 2)]],
... name='G'
... )
TransferFunction(
[[array([1])],
[array([1, 0])]],
[[array([1, 1])],
[array([1, 2])]],
name='G', outputs=2, inputs=1)

Combine NumPy arrays with transfer functions

>>> control.combine_tf([
... [np.eye(2), np.zeros((2, 1))],
... [np.zeros((1, 2)), control.TransferFunction([1], [1, 0])],
... ])
TransferFunction([[array([1.]), array([0.]), array([0.])],
[array([0.]), array([1.]), array([0.])],
[array([0.]), array([0.]), array([1])]],
[[array([1.]), array([1.]), array([1.])],
[array([1.]), array([1.]), array([1.])],
[array([1.]), array([1.]), array([1, 0])]])
>>> ct.combine_tf(
... [[np.eye(2), np.zeros((2, 1))],
... [np.zeros((1, 2)), ct.tf([1], [1, 0])]],
... name='G'
... )
TransferFunction(
[[array([1.]), array([0.]), array([0.])],
[array([0.]), array([1.]), array([0.])],
[array([0.]), array([0.]), array([1])]],
[[array([1.]), array([1.]), array([1.])],
[array([1.]), array([1.]), array([1.])],
[array([1.]), array([1.]), array([1, 0])]],
name='G', outputs=3, inputs=3)
"""
# Find common timebase or raise error
dt_list = []
Expand Down Expand Up @@ -616,10 +634,14 @@ def combine_tf(tf_array):
"Mismatched number transfer function inputs in row "
f"{row_index} of denominator."
)
return tf.TransferFunction(num, den, dt=dt)
return tf.TransferFunction(num, den, dt=dt, **kwargs)


def split_tf(transfer_function):
"""Split MIMO transfer function into NumPy array of SISO tranfer functions.
"""Split MIMO transfer function into NumPy array of SISO transfer functions.

System and signal names for the array of SISO transfer functions are
copied from the MIMO system.

Parameters
----------
Expand All @@ -635,21 +657,29 @@ def split_tf(transfer_function):
--------
Split a MIMO transfer function

>>> G = control.TransferFunction(
... [
... [[87.8], [-86.4]],
... [[108.2], [-109.6]],
... ],
... [
... [[1, 1], [1, 1]],
... [[1, 1], [1, 1]],
... ],
>>> G = ct.tf(
... [ [[87.8], [-86.4]],
... [[108.2], [-109.6]] ],
... [ [[1, 1], [1, 1]],
... [[1, 1], [1, 1]], ],
... name='G'
... )
>>> control.split_tf(G)
array([[TransferFunction(array([87.8]), array([1, 1])),
TransferFunction(array([-86.4]), array([1, 1]))],
[TransferFunction(array([108.2]), array([1, 1])),
TransferFunction(array([-109.6]), array([1, 1]))]], dtype=object)
>>> ct.split_tf(G)
array([[TransferFunction(
array([87.8]),
array([1, 1]),
name='G', outputs=1, inputs=1), TransferFunction(
array([-86.4]),
array([1, 1]),
name='G', outputs=1, inputs=1)],
[TransferFunction(
array([108.2]),
array([1, 1]),
name='G', outputs=1, inputs=1), TransferFunction(
array([-109.6]),
array([1, 1]),
name='G', outputs=1, inputs=1)]],
dtype=object)
"""
tf_split_lst = []
for i_out in range(transfer_function.noutputs):
Expand All @@ -660,6 +690,9 @@ def split_tf(transfer_function):
transfer_function.num_array[i_out, i_in],
transfer_function.den_array[i_out, i_in],
dt=transfer_function.dt,
inputs=transfer_function.input_labels[i_in],
outputs=transfer_function.output_labels[i_out],
name=transfer_function.name
)
)
tf_split_lst.append(row)
Expand Down
34 changes: 28 additions & 6 deletions control/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ def _check_deprecation(self, key):
else:
return key

#
# Context manager functionality
#

def __call__(self, mapping):
self.saved_mapping = dict()
self.temp_mapping = mapping.copy()
return self

def __enter__(self):
for key, val in self.temp_mapping.items():
if not key in self:
raise ValueError(f"unknown parameter '{key}'")
self.saved_mapping[key] = self[key]
self[key] = val
return self

def __exit__(self, exc_type, exc_val, exc_tb):
for key, val in self.saved_mapping.items():
self[key] = val
del self.saved_mapping, self.temp_mapping
return None

defaults = DefaultDict(_control_defaults)

Expand Down Expand Up @@ -266,7 +288,7 @@ def use_legacy_defaults(version):
Parameters
----------
version : string
Version number of the defaults desired. Ranges from '0.1' to '0.8.4'.
Version number of the defaults desired. Ranges from '0.1' to '0.10.1'.
Examples
--------
Expand All @@ -279,26 +301,26 @@ def use_legacy_defaults(version):
(major, minor, patch) = (None, None, None) # default values

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

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

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

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

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

Expand Down
28 changes: 17 additions & 11 deletions control/frdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ def __init__(self, *args, **kwargs):
if self.squeeze not in (None, True, False):
raise ValueError("unknown squeeze value")

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

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

for i in range(self.ninputs):
for j in range(self.noutputs):
if mimo:
outstr.append("Input %i to output %i:" % (i + 1, j + 1))
outstr.append('Freq [rad/s] Response')
outstr.append('------------ ---------------------')
outstr.append(
"\nInput %i to output %i:" % (i + 1, j + 1))
outstr.append(nl + 'Freq [rad/s] Response')
outstr.append(sp + '------------ ---------------------')
outstr.extend(
['%12.3f %10.4g%+10.4gj' % (w, re, im)
[sp + '%12.3f %10.4g%+10.4gj' % (w, re, im)
for w, re, im in zip(self.omega,
real(self.fresp[j, i, :]),
imag(self.fresp[j, i, :]))])

return '\n'.join(outstr)

def __repr__(self):
"""Loadable string representation,

limited for number of data points.
"""
return "FrequencyResponseData({d}, {w}{smooth})".format(
def _repr_eval_(self):
# Loadable format
out = "FrequencyResponseData(\n{d},\n{w}{smooth}".format(
d=repr(self.fresp), w=repr(self.omega),
smooth=(self._ifunc and ", smooth=True") or "")

out += self._dt_repr()
if len(labels := self._label_repr()) > 0:
out += ",\n" + labels

out += ")"
return out

def __neg__(self):
"""Negate a transfer function."""

Expand Down
Loading
Loading