Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add repr_gallery + update formatting for style and consistency
  • Loading branch information
murrayrm committed Jan 13, 2025
commit 143454067f43b62e765203492d936fd1ef11f172
18 changes: 9 additions & 9 deletions control/frdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,16 +399,19 @@ def __str__(self):
"""String representation of the transfer function."""

mimo = self.ninputs > 1 or self.noutputs > 1
outstr = [f"{InputOutputSystem.__str__(self)}", ""]
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, :]))])
Expand All @@ -421,10 +424,7 @@ def _repr_eval_(self):
d=repr(self.fresp), w=repr(self.omega),
smooth=(self._ifunc and ", smooth=True") or "")

if config.defaults['control.default_dt'] != self.dt:
out += ",\ndt={dt}".format(
dt='None' if self.dt is None else self.dt)

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

Expand Down
71 changes: 55 additions & 16 deletions control/iosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,51 @@ def _generic_name_check(self):
#: :meta hide-value:
nstates = None

#
# System representation
#

def __str__(self):
"""String representation of an input/output object"""
out = f"<{self.__class__.__name__}>: {self.name}"
out += f"\nInputs ({self.ninputs}): {self.input_labels}"
out += f"\nOutputs ({self.noutputs}): {self.output_labels}"
if self.nstates is not None:
out += f"\nStates ({self.nstates}): {self.state_labels}"
out += self._dt_repr("\n")
return out

def __repr__(self):
return iosys_repr(self, format=self.repr_format)

def _repr_info_(self):
return f'<{self.__class__.__name__} {self.name}: ' + \
f'{list(self.input_labels)} -> {list(self.output_labels)}>'
def _repr_info_(self, html=False):
out = f"<{self.__class__.__name__} {self.name}: " + \
f"{list(self.input_labels)} -> {list(self.output_labels)}"
out += self._dt_repr(", ") + ">"

if html:
# Replace symbols that might be interpreted by HTML processing
escape_chars = {
'$': r'\$',
'<': '&lt;',
'>': '&gt;',
Comment on lines +271 to +274

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might look better to change the -> in the I/O system representation into $\to$? (This would require a bit more complex checking in this function.)

@murrayrm murrayrm Jan 8, 2025 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this alone but added a comment to enhance this in a future release.

}
return "".join([c if c not in escape_chars else
escape_chars[c] for c in out])
else:
return out

def _repr_eval_(self):
# Defaults to _repr_info_; override in subclasses
return self._repr_info_()

def _repr_latex_(self):
# Defaults to using __repr__; override in subclasses
return None

def _repr_html_(self):
# Defaults to using __repr__; override in subclasses
return None

@property
def repr_format(self):
Expand All @@ -257,7 +296,7 @@ def repr_format(self):

* 'info' : <IOSystemType:sysname:[inputs]->[outputs]
* 'eval' : system specific, loadable representation
* 'latex' : latex representation of the object
* 'latex' : HTML/LaTeX representation of the object

The default representation for an input/output is set to 'info'.
This value can be changed for an individual system by setting the
Expand All @@ -274,15 +313,6 @@ def repr_format(self):
def repr_format(self, value):
self._repr_format = value

def __str__(self):
"""String representation of an input/output object"""
out = f"<{self.__class__.__name__}>: {self.name}"
out += f"\nInputs ({self.ninputs}): {self.input_labels}"
out += f"\nOutputs ({self.noutputs}): {self.output_labels}"
if self.nstates is not None:
out += f"\nStates ({self.nstates}): {self.state_labels}"
return out

def _label_repr(self, show_count=True):
out, count = "", 0

Expand All @@ -306,6 +336,8 @@ def _label_repr(self, show_count=True):
spec = f"{sig_name}={sig_labels}"
elif show_count:
spec = f"{sig_name}={len(sig_labels)}"
else:
spec = ""

# Append the specification string to the output, with wrapping
if count == 0:
Expand All @@ -314,13 +346,20 @@ def _label_repr(self, show_count=True):
# TODO: check to make sure a single line is enough (minor)
out += ",\n"
count = len(spec)
else:
elif len(spec) > 0:
out += ", "
count += len(spec) + 2
out += spec

return out

def _dt_repr(self, separator="\n"):
if config.defaults['control.default_dt'] != self.dt:
return "{separator}dt={dt}".format(
separator=separator, dt='None' if self.dt is None else self.dt)
else:
return ""

# Find a list of signals by name, index, or pattern
def _find_signals(self, name_list, sigdict):
if not isinstance(name_list, (list, tuple)):
Expand Down Expand Up @@ -768,7 +807,7 @@ def iosys_repr(sys, format=None):

* 'info' : <IOSystemType:sysname:[inputs]->[outputs]
* 'eval' : system specific, loadable representation
* 'latex' : latex representation of the object
* 'latex' : HTML/LaTeX representation of the object

Returns
-------
Expand All @@ -792,7 +831,7 @@ def iosys_repr(sys, format=None):
case 'eval':
return sys._repr_eval_()
case 'latex':
return sys._repr_latex_()
return sys._repr_html_()
case _:
raise ValueError(f"format '{format}' unknown")

Expand Down
100 changes: 39 additions & 61 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ class StateSpace(NonlinearIOSystem, LTI):
signal offsets. The subsystem is created by truncating the inputs and
outputs, but leaving the full set of system states.

StateSpace instances have support for IPython LaTeX output, intended
for pretty-printing in Jupyter notebooks. The LaTeX output can be
StateSpace instances have support for IPython HTML/LaTeX output, intended
for pretty-printing in Jupyter notebooks. The HTML/LaTeX output can be
configured using `control.config.defaults['statesp.latex_num_format']`
and `control.config.defaults['statesp.latex_repr_type']`. The LaTeX
output is tailored for MathJax, as used in Jupyter, and may look odd
when typeset by non-MathJax LaTeX systems.
and `control.config.defaults['statesp.latex_repr_type']`. The
HTML/LaTeX output is tailored for MathJax, as used in Jupyter, and
may look odd when typeset by non-MathJax LaTeX systems.

`control.config.defaults['statesp.latex_num_format']` is a format string
fragment, specifically the part of the format string after `'{:'`
Expand Down Expand Up @@ -386,8 +386,6 @@ def __str__(self):
"\n ".join(str(M).splitlines()))
for Mvar, M in zip(["A", "B", "C", "D"],
[self.A, self.B, self.C, self.D])])
if self.isdtime(strict=True):
string += f"\ndt = {self.dt}\n"
return string

def _repr_eval_(self):
Expand All @@ -396,16 +394,42 @@ def _repr_eval_(self):
A=self.A.__repr__(), B=self.B.__repr__(),
C=self.C.__repr__(), D=self.D.__repr__())

if config.defaults['control.default_dt'] != self.dt:
out += ",\ndt={dt}".format(
dt='None' if self.dt is None else self.dt)

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

out += ")"
return out

def _repr_html_(self):
"""HTML representation of state-space model.

Output is controlled by config options statesp.latex_repr_type,
statesp.latex_num_format, and statesp.latex_maxsize.

The output is primarily intended for Jupyter notebooks, which
use MathJax to render the LaTeX, and the results may look odd
when processed by a 'conventional' LaTeX system.

Returns
-------
s : string
HTML/LaTeX representation of model, or None if either matrix
dimension is greater than statesp.latex_maxsize.

"""
syssize = self.nstates + max(self.noutputs, self.ninputs)
if syssize > config.defaults['statesp.latex_maxsize']:
return None
elif config.defaults['statesp.latex_repr_type'] == 'partitioned':
return super()._repr_info(html=True) + self._latex_partitioned()
elif config.defaults['statesp.latex_repr_type'] == 'separate':
return super()._repr_info(html=True) + self._latex_separate()
else:
raise ValueError(
"Unknown statesp.latex_repr_type '{cfg}'".format(
cfg=config.defaults['statesp.latex_repr_type']))

def _latex_partitioned_stateless(self):
"""`Partitioned` matrix LaTeX representation for stateless systems

Expand All @@ -420,7 +444,6 @@ def _latex_partitioned_stateless(self):
D = eval(repr(self.D))

lines = [
self._repr_info_(),
r'$$',
(r'\left('
+ r'\begin{array}'
Expand All @@ -433,8 +456,7 @@ def _latex_partitioned_stateless(self):

lines.extend([
r'\end{array}'
r'\right)'
+ self._latex_dt(),
r'\right)',
r'$$'])

return '\n'.join(lines)
Expand All @@ -458,7 +480,6 @@ def _latex_partitioned(self):
eval(repr(getattr(self, M))) for M in ['A', 'B', 'C', 'D'])

lines = [
self._repr_info_(),
r'$$',
(r'\left('
+ r'\begin{array}'
Expand All @@ -477,8 +498,7 @@ def _latex_partitioned(self):

lines.extend([
r'\end{array}'
+ r'\right)'
+ self._latex_dt(),
+ r'\right)',
r'$$'])

return '\n'.join(lines)
Expand All @@ -493,7 +513,6 @@ def _latex_separate(self):
s : string with LaTeX representation of model
"""
lines = [
self._repr_info_(),
r'$$',
r'\begin{array}{ll}',
]
Expand Down Expand Up @@ -522,52 +541,11 @@ def fmt_matrix(matrix, name):
lines.extend(fmt_matrix(self.D, 'D'))

lines.extend([
r'\end{array}'
+ self._latex_dt(),
r'\end{array}',
r'$$'])

return '\n'.join(lines)

def _latex_dt(self):
if self.isdtime(strict=True):
if self.dt is True:
return r"~,~dt=~\mathrm{True}"
else:
fmt = config.defaults['statesp.latex_num_format']
return f"~,~dt={self.dt:{fmt}}"
return ""

def _repr_latex_(self):
"""LaTeX representation of state-space model

Output is controlled by config options statesp.latex_repr_type,
statesp.latex_num_format, and statesp.latex_maxsize.

The output is primarily intended for Jupyter notebooks, which
use MathJax to render the LaTeX, and the results may look odd
when processed by a 'conventional' LaTeX system.


Returns
-------

s : string with LaTeX representation of model, or None if
either matrix dimension is greater than
statesp.latex_maxsize

"""
syssize = self.nstates + max(self.noutputs, self.ninputs)
if syssize > config.defaults['statesp.latex_maxsize']:
return None
elif config.defaults['statesp.latex_repr_type'] == 'partitioned':
return self._latex_partitioned()
elif config.defaults['statesp.latex_repr_type'] == 'separate':
return self._latex_separate()
else:
raise ValueError(
"Unknown statesp.latex_repr_type '{cfg}'".format(
cfg=config.defaults['statesp.latex_repr_type']))

# Negation of a system
def __neg__(self):
"""Negate a state space system."""
Expand Down
Loading