Repository: python-control/python-control
Pull request #1091 (2025-01-04 21:37:41)

Update I/O system repr() and str()
This PR provides updates to the string representations of I/O systems, including both the `__repr__` and `__str__` functions (used for `repr()`, `str()`, and `print()`) as well as LaTeX output (via `_repr_html_`):

* Created a set of standard `_repr_*` formats for `InputOutputSystem`'s that can be overridden in subclasses to get the most useful representations based on the system type, using method names similar to `_repr_latex_` used by Jupyter notebooks:
    - `_repr_info_`: generates a minimal description of the I/O system in the form `<SystemClass name: [input list] -> [output list][, dt=timebase]>`.  This is the default format that exists for all I/O systems.  The inclusion of the timebase is new.
    - `_repr_eval_`: generates an "evaluatable" description of the I/O system, if possible (otherwise return `_repr_info_()`). This is used in `LTI` systems to show the state space matrices, numerator/denominator, or freuquency response data.  This format is similar to what was done before this PR for `LTI` classes, but now includes the signal names (if non-generic) or the signal counts (if generic).  The inclusion of the string names make sure that you can recreate the system with the proper labels.  The inclusion of the string counts lets you see the number of inputs and outputs.
    - `_repr_html_`: generates an HTML/LaTeX description of the I/O system, if possible (otherwise return `_repr_info_()`).  This method is picked up by Jupyter notebooks to display the value of a variable and replaces `_repr_latex_` used in prior versions.  This is similar to what was present before, except that now there is a "header" that shows the system name and signal names before showing a LaTeX representation of the state space matrices and/or transfer functions (this required changing `_repr_latex_` to `_repr_html_`).  Note that since the timebase information is included in the header, it is no longer included in the LaTeX formatted code.
* Created a new function `iosys_repr` (similar to `np.array_repr`) that allows different representations of I/O systems via a `format` parameter ('info', 'eval', or 'latex').
* The transfer function `display_format` parameter is now handled in a dynamic fashion.  If it is set to `None` (default) on system creation, the display style will follow `config.defaults['xferfcn.display_format']`.  If set to `poly` or `zpk` on system creation, that overrides the default.
* Updated the `str()` (print) representation of `InputOutputSystems` to include `dt` after the state/input/output names (suppressed if it matches the default `dt`).
* Updated the `str()` (print) representation for `NonlinearIOSystems` to include list of parameter labels.
* Updated the `str()` (print) representation for `InterconnectedSystems` to include the list of subsystems (in 'info' form) as well summaries of the connections and outputs matrix (might be an eventual replacement for `connection_table`).
* All representations now apply `np.printoptions` to system arrays before generating strings, so you get consistent formatting across representations (including latex) and you can use things like `suppress` to display near-zero numbers as '0'.
* Fixed up various line spacings in `str()` (print) to make things consistent across the various I/O system classes, including adding some indentation for transfer functions and MIMO systems (see screen shots below).
* Added `examples/repr_gallery.ipynb` and `exampes/repr_gallery.py` files to show all of the various output formats and options (with dashed lines at the start and end so you can see any extraneous blank lines).
* Added `config.defaults` options 'iosys.repr_format' and 'iosys.repr_show_count' to control representation formats.
* The `combine_tf` function now accepts system and signal name keywords.  (This is not strictly related to the main subject of this PR, but I needed since I had to set the name of the system in order for doctests to work.)
* Added the ability of `config.defaults` to be called as a context manager, so that you can temporarily change default parameters (this is used in `examples/repr_gallery.py`).  (This is more general than this PR, but I used it extensively in `repr_gallery`).
* Updated docstrings and unit tests.

A detailed set of before and after views are attached as PDFs, generated by the new `examples/repr_gallery.ipynb` and `exampes/repr_gallery.py` files:

* Before: [repr_gallery-10.1-29Dec2024.pdf](https://github.com/user-attachments/files/18308150/repr_gallery-10.1-29Dec2024.pdf)
* After: [repr_gallery-10.1-04Jan2025.pdf](https://github.com/user-attachments/files/18308294/repr_gallery-10.1-04Jan2025.pdf)

Some highlights (pulled from `repr_gallery`):
* State space system, repr(), 'eval' format:
  ```
  StateSpace: sys_ss, dt=0:
  -------------------------
  StateSpace(
  array([[ 0., 1.],
  [-4., -5.]]),
  array([[0.],
  [1.]]),
  array([[-1., 1.]]),
  array([[0.]]),
  name='sys_ss', states=2, outputs=1, inputs=1)
  ----
  ```
* Discrete time, state space system, repr(), 'eval' format:
  ```
  StateSpace: sys_dss, dt=0.1:
  ----------------------------
  StateSpace(
  array([[ 0.98300988, 0.07817246],
  [-0.31268983, 0.59214759]]),
  array([[0.00424753],
  [0.07817246]]),
  array([[-1., 1.]]),
  array([[0.]]),
  dt=0.1,
  name='sys_dss', states=2, outputs=1, inputs=1)
  ----
  ```
* MIMO transfer function, repr(), 'eval' format:
  ```
  TransferFunction: sys_mtf_zpk, dt=0:
  ------------------------------------
  TransferFunction(
  [[array([ 1., -1.]), array([0.])],
  [array([1, 0]), array([1, 0])]],
  [[array([1., 5., 4.]), array([1.])],
  [array([1]), array([1, 2, 1])]],
  name='sys_mtf_zpk', outputs=2, inputs=2)
  ----
  ```
* Discrete time, state space system, repr(), ,'info' format:
  ```
  TransferFunction: sys_dss_poly, dt=0.1:
  ---------------------------------------
  <TransferFunction sys_dss_poly: ['u[0]'] -> ['y[0]'], dt=0.1>
  ----
  ```
* Discrete time, state space systems, str():
  ```
  <StateSpace>: sys_dss
  Inputs (1): ['u[0]']
  Outputs (1): ['y[0]']
  States (2): ['x[0]', 'x[1]']
  dt = 0.1

  A = [[ 0.98300988 0.07817246]
       [-0.31268983 0.59214759]]

  B = [[0.00424753]
       [0.07817246]]

  C = [[-1. 1.]]

  D = [[0.]]
  ----
  ```
* Nonlinear I/O system, str():
  ```
  NonlinearIOSystem: sys_nl, dt=0:
  --------------------------------
  <NonlinearIOSystem>: sys_nl
  Inputs (1): ['u[0]']
  Outputs (1): ['y[0]']
  States (2): ['x[0]', 'x[1]']
  Parameters: ['a', 'b']

  Update: <function nl_update at 0x1258ed800>
  Output: <function nl_output at 0x1258ed8a0>
  ```
* Linear interconnected system, str():
  ```
  LinearICSystem: sys_ic, dt=0:
  -----------------------------
  <LinearICSystem>: sys_ic
  Inputs (2): ['r[0]', 'r[1]']
  Outputs (2): ['y[0]', 'y[1]']
  States (2): ['proc_x[0]', 'proc_x[1]']

  Subsystems (2):
  * <StateSpace proc: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>
  * <StateSpace ctrl: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]'], dt=None>

  Connections:
  * proc.u[0] <- ctrl.y[0]
  * proc.u[1] <- ctrl.y[1]
  * ctrl.u[0] <- -proc.y[0] + r[0]
  * ctrl.u[1] <- -proc.y[1] + r[1]

  Outputs:
  * y[0] <- proc.y[0]
  * y[1] <- proc.y[1]

  A = [[-2. 3.]
      [-1. -5.]]

  B = [[-2. 0.]
      [ 0. -3.]]

  C = [[-1. 1.]
      [ 1. 0.]]

  D = [[0. 0.]
      [0. 0.]]
  ----
  ```

coveralls: 
[![Coverage Status](https://coveralls.io/builds/71583268/badge)](https://coveralls.io/builds/71583268)

coverage: 94.686% (-0.1%) from 94.79%
when pulling **c0f5b174393e5d11653fef7bf5655b6ce192fbe4 on murrayrm:iosys_repr-07Dec2024**
into **93c4c8d2827bad02e48234799f159f196acb2282 on python-control:main**.

murrayrm: > Is the different format for the inputs for stateless systems `u0` , `u1` vs `u[0]` , `u[1]` for other systems a bug or a feature? Maybe a bug that this PR uncovered.

The input names appear for that example because the inputs were given names when the system was created(and so they show up explicitly to preserve that naming).
murrayrm: @slivingston Thanks for the detailed review.  Let me know if additional comments are coming, otherwise I'll go head and merge.

Patch:
diff --git a/control/bdalg.py b/control/bdalg.py
index 2dbd5c8e..2be23917 100644
--- a/control/bdalg.py
+++ b/control/bdalg.py
@@ -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
@@ -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
@@ -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
@@ -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 = []
@@ -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
     ----------
@@ -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):
@@ -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)
diff --git a/control/config.py b/control/config.py
index c5a59250..721871ed 100644
--- a/control/config.py
+++ b/control/config.py
@@ -73,6 +73,28 @@ class DefaultDict(collections.UserDict):
         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)
 
@@ -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
     --------
@@ -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)))
 
diff --git a/control/frdata.py b/control/frdata.py
index 64a1e822..cb692566 100644
--- a/control/frdata.py
+++ b/control/frdata.py
@@ -285,7 +285,6 @@ class FrequencyResponseData(LTI):
         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,
@@ -401,30 +400,37 @@ class FrequencyResponseData(LTI):
 
         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."""
 
diff --git a/control/iosys.py b/control/iosys.py
index 2c1f9cea..373bc211 100644
--- a/control/iosys.py
+++ b/control/iosys.py
@@ -16,7 +16,7 @@ from . import config
 from .exception import ControlIndexError
 
 __all__ = ['InputOutputSystem', 'NamedSignal', 'issiso', 'timebase',
-           'common_timebase', 'isdtime', 'isctime']
+           'common_timebase', 'isdtime', 'isctime', 'iosys_repr']
 
 # Define module default parameter values
 _iosys_defaults = {
@@ -31,6 +31,8 @@ _iosys_defaults = {
     'iosys.indexed_system_name_suffix': '$indexed',
     'iosys.converted_system_name_prefix': '',
     'iosys.converted_system_name_suffix': '$converted',
+    'iosys.repr_format': 'eval',
+    'iosys.repr_show_count': True,
 }
 
 
@@ -162,6 +164,8 @@ class InputOutputSystem(object):
         Set the prefix for output signals.  Default = 'y'.
     state_prefix : string, optional
         Set the prefix for state signals.  Default = 'x'.
+    repr_format : str
+        String representation format.  See :func:`control.iosys_repr`.
 
     """
     # Allow NDarray * IOSystem to give IOSystem._rmul_() priority
@@ -183,6 +187,8 @@ class InputOutputSystem(object):
         # Process timebase: if not given use default, but allow None as value
         self.dt = _process_dt_keyword(kwargs)
 
+        self._repr_format = kwargs.pop('repr_format', None)
+
         # Make sure there were no other keywords
         if kwargs:
             raise TypeError("unrecognized keywords: ", str(kwargs))
@@ -237,18 +243,127 @@ class InputOutputSystem(object):
     #: :meta hide-value:
     nstates = None
 
-    def __repr__(self):
-        return f'<{self.__class__.__name__}:{self.name}:' + \
-            f'{list(self.input_labels)}->{list(self.output_labels)}>'
+    #
+    # System representation
+    #
 
     def __str__(self):
         """String representation of an input/output object"""
-        str = f"<{self.__class__.__name__}>: {self.name}\n"
-        str += f"Inputs ({self.ninputs}): {self.input_labels}\n"
-        str += f"Outputs ({self.noutputs}): {self.output_labels}\n"
+        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:
-            str += f"States ({self.nstates}): {self.state_labels}"
-        return str
+            out += f"\nStates ({self.nstates}): {self.state_labels}"
+        out += self._dt_repr(separator="\n", space=" ")
+        return out
+
+    def __repr__(self):
+        return iosys_repr(self, format=self.repr_format)
+
+    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(separator=", ", space="") + ">"
+
+        if html:
+            # Replace symbols that might be interpreted by HTML processing
+            # TODO: replace -> with right arrow (later)
+            escape_chars = {
+                '$': r'\$',
+                '<': '&lt;',
+                '>': '&gt;',
+            }
+            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):
+        """String representation format.
+
+        Format used in creating the representation for the system:
+
+          * 'info' : <IOSystemType:sysname:[inputs]->[outputs]
+          * 'eval' : system specific, loadable representation
+          * '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
+        `repr_format` parameter when the system is created or by setting
+        the `repr_format` property after system creation.  Set
+        config.defaults['iosys.repr_format'] to change for all I/O systems
+        or use the `repr_format` parameter/attribute for a single system.
+
+        """
+        return self._repr_format if self._repr_format is not None \
+            else config.defaults['iosys.repr_format']
+
+    @repr_format.setter
+    def repr_format(self, value):
+        self._repr_format = value
+
+    def _label_repr(self, show_count=None):
+        show_count = config._get_param(
+            'iosys', 'repr_show_count', show_count, True)
+        out, count = "", 0
+
+        # Include the system name if not generic
+        if not self._generic_name_check():
+            name_spec = f"name='{self.name}'"
+            count += len(name_spec)
+            out += name_spec
+
+        # Include the state, output, and input names if not generic
+        for sig_name, sig_default, sig_labels in zip(
+                ['states', 'outputs', 'inputs'],
+                ['x', 'y', 'u'],        # TODO: replace with defaults
+                [self.state_labels, self.output_labels, self.input_labels]):
+            if sig_name == 'states' and self.nstates is None:
+                continue
+
+            # Check if the signal labels are generic
+            if any([re.match(r'^' + sig_default + r'\[\d*\]$', label) is None
+                    for label in sig_labels]):
+                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:
+                count = len(spec)       # no system name => suppress comma
+            elif count + len(spec) > 72:
+                # TODO: check to make sure a single line is enough (minor)
+                out += ",\n"
+                count = len(spec)
+            elif len(spec) > 0:
+                out += ", "
+                count += len(spec) + 2
+            out += spec
+
+        return out
+
+    def _dt_repr(self, separator="\n", space=""):
+        if config.defaults['control.default_dt'] != self.dt:
+            return "{separator}dt{space}={space}{dt}".format(
+                separator=separator, space=space,
+                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):
@@ -685,6 +800,47 @@ def isctime(sys=None, dt=None, strict=False):
         return sys.isctime(strict)
 
 
+def iosys_repr(sys, format=None):
+    """Return representation of an I/O system.
+
+    Parameters
+    ----------
+    sys : InputOutputSystem
+        System for which the representation is generated.
+    format : str
+        Format to use in creating the representation:
+
+          * 'info' : <IOSystemType:sysname:[inputs]->[outputs]
+          * 'eval' : system specific, loadable representation
+          * 'latex' : HTML/LaTeX representation of the object
+
+    Returns
+    -------
+    str
+        String representing the input/output system.
+
+    Notes
+    -----
+    By default, the representation for an input/output is set to 'eval'.
+    Set config.defaults['iosys.repr_format'] to change for all I/O systems
+    or use the `repr_format` parameter for a single system.
+
+    Jupyter will automatically use the 'latex' representation for I/O
+    systems, when available.
+
+    """
+    format = config.defaults['iosys.repr_format'] if format is None else format
+    match format:
+        case 'info':
+            return sys._repr_info_()
+        case 'eval':
+            return sys._repr_eval_()
+        case 'latex':
+            return sys._repr_html_()
+        case _:
+            raise ValueError(f"format '{format}' unknown")
+
+
 # Utility function to parse iosys keywords
 def _process_iosys_keywords(
         keywords={}, defaults={}, static=False, end=False):
diff --git a/control/lti.py b/control/lti.py
index b7139f60..cb785ca5 100644
--- a/control/lti.py
+++ b/control/lti.py
@@ -615,14 +615,14 @@ def bandwidth(sys, dbdrop=-3):
     -------
     >>> G = ct.tf([1], [1, 1])
     >>> ct.bandwidth(G)
-    0.9976
+    np.float64(0.9976283451102316)
 
     >>> G1 = ct.tf(0.1, [1, 0.1])
     >>> wn2 = 1
     >>> zeta2 = 0.001
     >>> G2 = ct.tf(wn2**2, [1, 2*zeta2*wn2, wn2**2])
     >>> ct.bandwidth(G1*G2)
-    0.1018
+    np.float64(0.10184838823897456)
 
     """
     if not isinstance(sys, LTI):
diff --git a/control/nlsys.py b/control/nlsys.py
index beb2566e..7683d338 100644
--- a/control/nlsys.py
+++ b/control/nlsys.py
@@ -26,7 +26,7 @@ import scipy as sp
 
 from . import config
 from .iosys import InputOutputSystem, _parse_spec, _process_iosys_keywords, \
-    _process_signal_list, common_timebase, isctime, isdtime
+    _process_signal_list, common_timebase, iosys_repr, isctime, isdtime
 from .timeresp import _check_convert_array, _process_time_response, \
     TimeResponseData, TimeResponseList
 
@@ -154,9 +154,13 @@ class NonlinearIOSystem(InputOutputSystem):
         self._current_params = {} if params is None else params.copy()
 
     def __str__(self):
-        return f"{InputOutputSystem.__str__(self)}\n\n" + \
+        out = f"{InputOutputSystem.__str__(self)}"
+        if len(self.params) > 0:
+            out += f"\nParameters: {[p for p in self.params.keys()]}"
+        out += "\n\n" + \
             f"Update: {self.updfcn}\n" + \
             f"Output: {self.outfcn}"
+        return out
 
     # Return the value of a static nonlinear system
     def __call__(sys, u, params=None, squeeze=None):
@@ -778,6 +782,71 @@ class InterconnectedSystem(NonlinearIOSystem):
                              index + "; combining with previous entries")
                     self.output_map[index + j, ylist_index] += gain
 
+    def __str__(self):
+        import textwrap
+        out = InputOutputSystem.__str__(self)
+
+        out += f"\n\nSubsystems ({len(self.syslist)}):\n"
+        for sys in self.syslist:
+            out += "\n".join(textwrap.wrap(
+                iosys_repr(sys, format='info'), width=78,
+                initial_indent=" * ", subsequent_indent="    ")) + "\n"
+
+        # Build a list of input, output, and inpout signals
+        input_list, output_list, inpout_list = [], [], []
+        for sys in self.syslist:
+            input_list += [sys.name + "." + lbl for lbl in sys.input_labels]
+            output_list += [sys.name + "." + lbl for lbl in sys.output_labels]
+        inpout_list = input_list + output_list
+
+        # Define a utility function to generate the signal
+        def cxn_string(signal, gain, first):
+            if gain == 1:
+                return (" + " if not first else "") + f"{signal}"
+            elif gain == -1:
+                return (" - " if not first else "-") + f"{signal}"
+            elif gain > 0:
+                return (" + " if not first else "") + f"{gain} * {signal}"
+            elif gain < 0:
+                return (" - " if not first else "-") + \
+                    f"{abs(gain)} * {signal}"
+
+        out += f"\nConnections:\n"
+        for i in range(len(input_list)):
+            first = True
+            cxn = f"{input_list[i]} <- "
+            if np.any(self.connect_map[i]):
+                for j in range(len(output_list)):
+                    if self.connect_map[i, j]:
+                        cxn += cxn_string(
+                            output_list[j], self.connect_map[i,j], first)
+                        first = False
+            if np.any(self.input_map[i]):
+                for j in range(len(self.input_labels)):
+                    if self.input_map[i, j]:
+                        cxn += cxn_string(
+                            self.input_labels[j], self.input_map[i, j], first)
+                        first = False
+            out += "\n".join(textwrap.wrap(
+                cxn, width=78, initial_indent=" * ",
+                subsequent_indent="     ")) + "\n"
+
+        out += f"\nOutputs:\n"
+        for i in range(len(self.output_labels)):
+            first = True
+            cxn = f"{self.output_labels[i]} <- "
+            if np.any(self.output_map[i]):
+                for j in range(len(inpout_list)):
+                    if self.output_map[i, j]:
+                        cxn += cxn_string(
+                            output_list[j], self.output_map[i, j], first)
+                        first = False
+                out += "\n".join(textwrap.wrap(
+                    cxn, width=78, initial_indent=" * ",
+                    subsequent_indent="     ")) + "\n"
+
+        return out
+
     def _update_params(self, params, warning=False):
         for sys in self.syslist:
             local = sys.params.copy()   # start with system parameters
@@ -1018,7 +1087,7 @@ class InterconnectedSystem(NonlinearIOSystem):
     def connection_table(self, show_names=False, column_width=32):
         """Print table of connections inside an interconnected system model.
 
-        Intended primarily for :class:`InterconnectedSystems` that have been
+        Intended primarily for :class:`InterconnectedSystem`'s that have been
         connected implicitly using signal names.
 
         Parameters
@@ -1303,7 +1372,7 @@ def nlsys(updfcn, outfcn=None, **kwargs):
     Examples
     --------
     >>> def kincar_update(t, x, u, params):
-    ...     l = params.get('l', 1)  # wheelbase
+    ...     l = params['l']              # wheelbase
     ...     return np.array([
     ...         np.cos(x[2]) * u[0],     # x velocity
     ...         np.sin(x[2]) * u[0],     # y velocity
@@ -1314,7 +1383,8 @@ def nlsys(updfcn, outfcn=None, **kwargs):
     ...     return x[0:2]  # x, y position
     >>>
     >>> kincar = ct.nlsys(
-    ...     kincar_update, kincar_output, states=3, inputs=2, outputs=2)
+    ...     kincar_update, kincar_output, states=3, inputs=2, outputs=2,
+    ...     params={'l': 1})
     >>>
     >>> timepts = np.linspace(0, 10)
     >>> response = ct.input_output_response(
diff --git a/control/statesp.py b/control/statesp.py
index 070be2e1..98adc942 100644
--- a/control/statesp.py
+++ b/control/statesp.py
@@ -14,6 +14,7 @@ python-control library.
 """
 
 import math
+import sys
 from collections.abc import Iterable
 from copy import deepcopy
 from warnings import warn
@@ -21,8 +22,8 @@ from warnings import warn
 import numpy as np
 import scipy as sp
 import scipy.linalg
-from numpy import any, asarray, concatenate, cos, delete, empty, exp, eye, \
-    isinf, ones, pad, sin, squeeze, zeros
+from numpy import any, array, asarray, concatenate, cos, delete, empty, \
+    exp, eye, isinf, ones, pad, sin, squeeze, zeros
 from numpy.linalg import LinAlgError, eigvals, matrix_rank, solve
 from numpy.random import rand, randn
 from scipy.signal import StateSpace as signalStateSpace
@@ -33,7 +34,7 @@ from .exception import ControlMIMONotImplemented, ControlSlycot, slycot_check
 from .frdata import FrequencyResponseData
 from .iosys import InputOutputSystem, NamedSignal, _process_dt_keyword, \
     _process_iosys_keywords, _process_signal_list, _process_subsys_index, \
-    common_timebase, isdtime, issiso
+    common_timebase, iosys_repr, isdtime, issiso
 from .lti import LTI, _process_frequency_response
 from .nlsys import InterconnectedSystem, NonlinearIOSystem
 import control
@@ -131,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 `'{:'`
@@ -380,23 +381,56 @@ class StateSpace(NonlinearIOSystem, LTI):
     def __str__(self):
         """Return string representation of the state space system."""
         string = f"{InputOutputSystem.__str__(self)}\n\n"
-        string += "\n".join([
-            "{} = {}\n".format(Mvar,
+        string += "\n\n".join([
+            "{} = {}".format(Mvar,
                                "\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
 
-    # represent to implement a re-loadable version
-    def __repr__(self):
-        """Print state-space system in loadable form."""
-        # TODO: add input/output names (?)
-        return "StateSpace({A}, {B}, {C}, {D}{dt})".format(
+    def _repr_eval_(self):
+        # Loadable format
+        out = "StateSpace(\n{A},\n{B},\n{C},\n{D}".format(
             A=self.A.__repr__(), B=self.B.__repr__(),
-            C=self.C.__repr__(), D=self.D.__repr__(),
-            dt=(isdtime(self, strict=True) and ", {}".format(self.dt)) or '')
+            C=self.C.__repr__(), D=self.D.__repr__())
+
+        out += super()._dt_repr(separator=",\n", space="")
+        if len(labels := super()._label_repr()) > 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) + \
+                "\n" + self._latex_partitioned()
+        elif config.defaults['statesp.latex_repr_type'] == 'separate':
+            return super()._repr_info_(html=True) + \
+                "\n" + 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
@@ -407,21 +441,24 @@ class StateSpace(NonlinearIOSystem, LTI):
         -------
         s : string with LaTeX representation of model
         """
+        # Apply NumPy formatting
+        with np.printoptions(threshold=sys.maxsize):
+            D = eval(repr(self.D))
+
         lines = [
             r'$$',
-            (r'\left('
+            (r'\left['
              + r'\begin{array}'
              + r'{' + 'rll' * self.ninputs + '}')
             ]
 
-        for Di in asarray(self.D):
+        for Di in asarray(D):
             lines.append('&'.join(_f2s(Dij) for Dij in Di)
                          + '\\\\')
 
         lines.extend([
             r'\end{array}'
-            r'\right)'
-            + self._latex_dt(),
+            r'\right]',
             r'$$'])
 
         return '\n'.join(lines)
@@ -439,27 +476,31 @@ class StateSpace(NonlinearIOSystem, LTI):
         if self.nstates == 0:
             return self._latex_partitioned_stateless()
 
+        # Apply NumPy formatting
+        with np.printoptions(threshold=sys.maxsize):
+            A, B, C, D = (
+                eval(repr(getattr(self, M))) for M in ['A', 'B', 'C', 'D'])
+
         lines = [
             r'$$',
-            (r'\left('
+            (r'\left['
              + r'\begin{array}'
              + r'{' + 'rll' * self.nstates + '|' + 'rll' * self.ninputs + '}')
             ]
 
-        for Ai, Bi in zip(asarray(self.A), asarray(self.B)):
+        for Ai, Bi in zip(asarray(A), asarray(B)):
             lines.append('&'.join([_f2s(Aij) for Aij in Ai]
                                   + [_f2s(Bij) for Bij in Bi])
                          + '\\\\')
         lines.append(r'\hline')
-        for Ci, Di in zip(asarray(self.C), asarray(self.D)):
+        for Ci, Di in zip(asarray(C), asarray(D)):
             lines.append('&'.join([_f2s(Cij) for Cij in Ci]
                                   + [_f2s(Dij) for Dij in Di])
                          + '\\\\')
 
         lines.extend([
             r'\end{array}'
-            + r'\right)'
-            + self._latex_dt(),
+            + r'\right]',
             r'$$'])
 
         return '\n'.join(lines)
@@ -480,7 +521,7 @@ class StateSpace(NonlinearIOSystem, LTI):
 
         def fmt_matrix(matrix, name):
             matlines = [name
-                        + r' = \left(\begin{array}{'
+                        + r' = \left[\begin{array}{'
                         + 'rll' * matrix.shape[1]
                         + '}']
             for row in asarray(matrix):
@@ -488,7 +529,7 @@ class StateSpace(NonlinearIOSystem, LTI):
                                 + '\\\\')
             matlines.extend([
                 r'\end{array}'
-                r'\right)'])
+                r'\right]'])
             return matlines
 
         if self.nstates > 0:
@@ -502,52 +543,11 @@ class StateSpace(NonlinearIOSystem, LTI):
         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."""
@@ -1483,9 +1483,38 @@ class LinearICSystem(InterconnectedSystem, StateSpace):
             outputs=io_sys.output_labels, states=io_sys.state_labels,
             params=io_sys.params, remove_useless_states=False)
 
-        # Use StateSpace.__call__ to evaluate at a given complex value
-        def __call__(self, *args, **kwargs):
-            return StateSpace.__call__(self, *args, **kwargs)
+    # Use StateSpace.__call__ to evaluate at a given complex value
+    def __call__(self, *args, **kwargs):
+        return StateSpace.__call__(self, *args, **kwargs)
+
+    def __str__(self):
+        string = InterconnectedSystem.__str__(self) + "\n"
+        string += "\n\n".join([
+            "{} = {}".format(Mvar,
+                               "\n    ".join(str(M).splitlines()))
+            for Mvar, M in zip(["A", "B", "C", "D"],
+                               [self.A, self.B, self.C, self.D])])
+        return string
+
+    # Use InputOutputSystem repr for 'eval' since we can't recreate structure
+    # (without this, StateSpace._repr_eval_ gets used...)
+    def _repr_eval_(self):
+        return InputOutputSystem._repr_eval_(self)
+
+    def _repr_html_(self):
+        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 InterconnectedSystem._repr_info_(self, html=True) + \
+                "\n" + StateSpace._latex_partitioned(self)
+        elif config.defaults['statesp.latex_repr_type'] == 'separate':
+            return InterconnectedSystem._repr_info_(self, html=True) + \
+                "\n" + StateSpace._latex_separate(self)
+        else:
+            raise ValueError(
+                "Unknown statesp.latex_repr_type '{cfg}'".format(
+                    cfg=config.defaults['statesp.latex_repr_type']))
 
     # The following text needs to be replicated from StateSpace in order for
     # this entry to show up properly in sphinx doccumentation (not sure why,
diff --git a/control/xferfcn.py b/control/xferfcn.py
index 5e391d30..dea61f9f 100644
--- a/control/xferfcn.py
+++ b/control/xferfcn.py
@@ -13,6 +13,7 @@ python-control library.
 
 """
 
+import sys
 from collections.abc import Iterable
 from copy import deepcopy
 from itertools import chain, product
@@ -209,11 +210,8 @@ class TransferFunction(LTI):
         # get initialized when defaults are not fully initialized yet.
         # Use 'poly' in these cases.
 
-        self.display_format = kwargs.pop(
-            'display_format',
-            config.defaults.get('xferfcn.display_format', 'poly'))
-
-        if self.display_format not in ('poly', 'zpk'):
+        self.display_format = kwargs.pop('display_format', None)
+        if self.display_format not in (None, 'poly', 'zpk'):
             raise ValueError("display_format must be 'poly' or 'zpk',"
                              " got '%s'" % self.display_format)
 
@@ -441,30 +439,34 @@ class TransferFunction(LTI):
         Based on the display_format property, the output will be formatted as
         either polynomials or in zpk form.
         """
+        display_format = config.defaults['xferfcn.display_format'] if \
+            self.display_format is None else self.display_format
         mimo = not self.issiso()
         if var is None:
             var = 's' if self.isctime() else 'z'
-        outstr = f"{InputOutputSystem.__str__(self)}\n"
+        outstr = f"{InputOutputSystem.__str__(self)}"
 
         for ni in range(self.ninputs):
             for no in range(self.noutputs):
+                outstr += "\n"
                 if mimo:
-                    outstr += "\nInput %i to output %i:" % (ni + 1, no + 1)
+                    outstr += "\nInput %i to output %i:\n" % (ni + 1, no + 1)
 
                 # Convert the numerator and denominator polynomials to strings.
-                if self.display_format == 'poly':
+                if display_format == 'poly':
                     numstr = _tf_polynomial_to_string(
                         self.num_array[no, ni], var=var)
                     denstr = _tf_polynomial_to_string(
                         self.den_array[no, ni], var=var)
-                elif self.display_format == 'zpk':
+                elif display_format == 'zpk':
                     num = self.num_array[no, ni]
                     if num.size == 1 and num.item() == 0:
                         # Catch a special case that SciPy doesn't handle
                         z, p, k = tf2zpk([1.], self.den_array[no, ni])
                         k = 0
                     else:
-                        z, p, k = tf2zpk(self.num[no][ni], self.den_array[no, ni])
+                        z, p, k = tf2zpk(
+                            self.num[no][ni], self.den_array[no, ni])
                     numstr = _tf_factorized_polynomial_to_string(
                         z, gain=k, var=var)
                     denstr = _tf_factorized_polynomial_to_string(p, var=var)
@@ -479,50 +481,48 @@ class TransferFunction(LTI):
                 if len(denstr) < dashcount:
                     denstr = ' ' * ((dashcount - len(denstr)) // 2) + denstr
 
-                outstr += "\n" + numstr + "\n" + dashes + "\n" + denstr + "\n"
-
-        # If this is a strict discrete time system, print the sampling time
-        if type(self.dt) != bool and self.isdtime(strict=True):
-            outstr += "\ndt = " + str(self.dt) + "\n"
+                outstr += "\n  " + numstr + "\n  " + dashes + "\n  " + denstr
 
         return outstr
 
-    # represent to implement a re-loadable version
-    def __repr__(self):
-        """Print transfer function in loadable form."""
+    def _repr_eval_(self):
+        # Loadable format
         if self.issiso():
-            return "TransferFunction({num}, {den}{dt})".format(
+            out = "TransferFunction(\n{num},\n{den}".format(
                 num=self.num_array[0, 0].__repr__(),
-                den=self.den_array[0, 0].__repr__(),
-                dt=', {}'.format(self.dt) if isdtime(self, strict=True)
-                else '')
+                den=self.den_array[0, 0].__repr__())
         else:
-            out = "TransferFunction(["
+            out = "TransferFunction(\n["
             for entry in [self.num_array, self.den_array]:
                 for i in range(self.noutputs):
-                    out += "[" if i == 0 else " ["
+                    out += "[" if i == 0 else "\n ["
+                    linelen = 0
                     for j in range(self.ninputs):
                         out += ", " if j != 0 else ""
                         numstr = np.array_repr(entry[i, j])
+                        if linelen + len(numstr) > 72:
+                            out += "\n  "
+                            linelen = 0
                         out += numstr
+                        linelen += len(numstr)
                     out += "]," if i < self.noutputs - 1 else "]"
-                out += "], [" if entry is self.num_array else "]"
+                out += "],\n[" if entry is self.num_array else "]"
 
-            if config.defaults['control.default_dt'] != self.dt:
-                out += ", {dt}".format(
-                    dt='None' if self.dt is None else self.dt)
-            out += ")"
-            return out
+        out += super()._dt_repr(separator=",\n", space="")
+        if len(labels := self._label_repr()) > 0:
+            out += ",\n" + labels
 
-    def _repr_latex_(self, var=None):
-        """LaTeX representation of transfer function, for Jupyter notebook."""
+        out += ")"
+        return out
 
+    def _repr_html_(self, var=None):
+        """HTML/LaTeX representation of xferfcn, for Jupyter notebook."""
+        display_format = config.defaults['xferfcn.display_format'] if \
+            self.display_format is None else self.display_format
         mimo = not self.issiso()
-
         if var is None:
             var = 's' if self.isctime() else 'z'
-
-        out = ['$$']
+        out = [super()._repr_info_(html=True), '\n$$']
 
         if mimo:
             out.append(r"\begin{bmatrix}")
@@ -530,12 +530,12 @@ class TransferFunction(LTI):
         for no in range(self.noutputs):
             for ni in range(self.ninputs):
                 # Convert the numerator and denominator polynomials to strings.
-                if self.display_format == 'poly':
+                if display_format == 'poly':
                     numstr = _tf_polynomial_to_string(
                         self.num_array[no, ni], var=var)
                     denstr = _tf_polynomial_to_string(
                         self.den_array[no, ni], var=var)
-                elif self.display_format == 'zpk':
+                elif display_format == 'zpk':
                     z, p, k = tf2zpk(
                         self.num_array[no, ni], self.den_array[no, ni])
                     numstr = _tf_factorized_polynomial_to_string(
@@ -545,7 +545,7 @@ class TransferFunction(LTI):
                 numstr = _tf_string_to_latex(numstr, var=var)
                 denstr = _tf_string_to_latex(denstr, var=var)
 
-                out += [r"\frac{", numstr, "}{", denstr, "}"]
+                out += [r"\dfrac{", numstr, "}{", denstr, "}"]
 
                 if mimo and ni < self.ninputs - 1:
                     out.append("&")
@@ -556,10 +556,6 @@ class TransferFunction(LTI):
         if mimo:
             out.append(r" \end{bmatrix}")
 
-        # See if this is a discrete time system with specific sampling time
-        if not (self.dt is None) and type(self.dt) != bool and self.dt > 0:
-            out += [r"\quad dt = ", str(self.dt)]
-
         out.append("$$")
 
         return ''.join(out)
@@ -777,7 +773,7 @@ class TransferFunction(LTI):
             indices[0], self.output_labels, slice_to_list=True)
         inpdx, inputs = _process_subsys_index(
             indices[1], self.input_labels, slice_to_list=True)
-        
+
         # Construct the transfer function for the subsyste
         num = _create_poly_array((len(outputs), len(inputs)))
         den = _create_poly_array(num.shape)
@@ -1313,9 +1309,12 @@ def _c2d_matched(sysC, Ts, **kwargs):
 # Borrowed from poly1d library
 def _tf_polynomial_to_string(coeffs, var='s'):
     """Convert a transfer function polynomial to a string."""
-
     thestr = "0"
 
+    # Apply NumPy formatting
+    with np.printoptions(threshold=sys.maxsize):
+        coeffs = eval(repr(coeffs))
+
     # Compute the number of coefficients
     N = len(coeffs) - 1
 
@@ -1360,6 +1359,9 @@ def _tf_polynomial_to_string(coeffs, var='s'):
 
 def _tf_factorized_polynomial_to_string(roots, gain=1, var='s'):
     """Convert a factorized polynomial to a string."""
+    # Apply NumPy formatting
+    with np.printoptions(threshold=sys.maxsize):
+        roots = eval(repr(roots))
 
     if roots.size == 0:
         return _float2str(gain)
diff --git a/examples/repr_gallery.ipynb b/examples/repr_gallery.ipynb
new file mode 100644
index 00000000..e0d33c14
--- /dev/null
+++ b/examples/repr_gallery.ipynb
@@ -0,0 +1,1337 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "639f45ae-0ee8-426e-9d52-a7b9bb95d45a",
+   "metadata": {},
+   "source": [
+    "# System Representation Gallery\n",
+    "\n",
+    "This Jupyter notebook creates different types of systems and generates a variety of representations (`__repr__`, `__str__`) for those systems that can be used to compare different versions of python-control.  It is mainly intended for uses by developers to make sure there are no unexpected changes in representation formats, but also has some interesting examples of different choices in system representation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "c4b80abe-59e4-4d76-a81c-6979a583e82d",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'0.10.1.dev324+g2fd3802a.d20241218'"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import numpy as np\n",
+    "\n",
+    "import control as ct\n",
+    "import control.flatsys as fs\n",
+    "\n",
+    "ct.__version__"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "035ebae9-7a4b-4079-8111-31f6c493c77c",
+   "metadata": {},
+   "source": [
+    "## Text representations\n",
+    "\n",
+    "The code below shows what the output in various formats will look like in a terminal window."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "eab8cc0b-3e8a-4df8-acbd-258f006f44bb",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "============================================================================\n",
+      "                                Default repr\n",
+      "============================================================================\n",
+      "\n",
+      "StateSpace: sys_ss, dt=0:\n",
+      "-------------------------\n",
+      "StateSpace(\n",
+      "array([[ 0.,  1.],\n",
+      "       [-4., -5.]]),\n",
+      "array([[0.],\n",
+      "       [1.]]),\n",
+      "array([[-1.,  1.]]),\n",
+      "array([[0.]]),\n",
+      "name='sys_ss', states=2, outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "StateSpace: sys_dss, dt=0.1:\n",
+      "----------------------------\n",
+      "StateSpace(\n",
+      "array([[ 0.98300988,  0.07817246],\n",
+      "       [-0.31268983,  0.59214759]]),\n",
+      "array([[0.00424753],\n",
+      "       [0.07817246]]),\n",
+      "array([[-1.,  1.]]),\n",
+      "array([[0.]]),\n",
+      "dt=0.1,\n",
+      "name='sys_dss', states=2, outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "StateSpace: stateless, dt=None:\n",
+      "-------------------------------\n",
+      "StateSpace(\n",
+      "array([], shape=(0, 0), dtype=float64),\n",
+      "array([], shape=(0, 2), dtype=float64),\n",
+      "array([], shape=(2, 0), dtype=float64),\n",
+      "array([[1., 0.],\n",
+      "       [0., 1.]]),\n",
+      "dt=None,\n",
+      "name='stateless', states=0, outputs=2, inputs=['u0', 'u1'])\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_ss$converted, dt=0:\n",
+      "-----------------------------------------\n",
+      "TransferFunction(\n",
+      "array([ 1., -1.]),\n",
+      "array([1., 5., 4.]),\n",
+      "name='sys_ss$converted', outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_dss_poly, dt=0.1:\n",
+      "---------------------------------------\n",
+      "TransferFunction(\n",
+      "array([ 0.07392493, -0.08176823]),\n",
+      "array([ 1.        , -1.57515746,  0.60653066]),\n",
+      "dt=0.1,\n",
+      "name='sys_dss_poly', outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys[3], dt=0:\n",
+      "-------------------------------\n",
+      "TransferFunction(\n",
+      "array([1]),\n",
+      "array([1, 0]),\n",
+      "outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_mtf_zpk, dt=0:\n",
+      "------------------------------------\n",
+      "TransferFunction(\n",
+      "[[array([ 1., -1.]), array([0.])],\n",
+      " [array([1, 0]), array([1, 0])]],\n",
+      "[[array([1., 5., 4.]), array([1.])],\n",
+      " [array([1]), array([1, 2, 1])]],\n",
+      "name='sys_mtf_zpk', outputs=2, inputs=2)\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_ss$converted$sampled, dt=0:\n",
+      "------------------------------------------------------\n",
+      "FrequencyResponseData(\n",
+      "array([[[-0.24365959+0.05559644j, -0.19198193+0.1589174j ,\n",
+      "          0.05882353+0.23529412j,  0.1958042 -0.01105691j,\n",
+      "          0.0508706 -0.07767156j]]]),\n",
+      "array([ 0.1       ,  0.31622777,  1.        ,  3.16227766, 10.        ]),\n",
+      "name='sys_ss$converted$sampled', outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_dss_poly$sampled, dt=0.1:\n",
+      "----------------------------------------------------\n",
+      "FrequencyResponseData(\n",
+      "array([[[-0.24337799+0.05673083j, -0.18944184+0.16166381j,\n",
+      "          0.07043649+0.23113479j,  0.19038528-0.04416494j,\n",
+      "          0.00286505-0.09595906j]]]),\n",
+      "array([ 0.1       ,  0.31622777,  1.        ,  3.16227766, 10.        ])\n",
+      "dt=0.1,\n",
+      "name='sys_dss_poly$sampled', outputs=1, inputs=1)\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_mtf_zpk$sampled, dt=0:\n",
+      "-------------------------------------------------\n",
+      "FrequencyResponseData(\n",
+      "array([[[-0.24365959 +0.05559644j, -0.19198193 +0.1589174j ,\n",
+      "          0.05882353 +0.23529412j,  0.1958042  -0.01105691j,\n",
+      "          0.0508706  -0.07767156j],\n",
+      "        [ 0.         +0.j        ,  0.         +0.j        ,\n",
+      "          0.         +0.j        ,  0.         +0.j        ,\n",
+      "          0.         +0.j        ]],\n",
+      "\n",
+      "       [[ 0.         +0.1j       ,  0.         +0.31622777j,\n",
+      "          0.         +1.j        ,  0.         +3.16227766j,\n",
+      "          0.        +10.j        ],\n",
+      "        [ 0.01960592 +0.09704931j,  0.16528926 +0.23521074j,\n",
+      "          0.5        +0.j        ,  0.16528926 -0.23521074j,\n",
+      "          0.01960592 -0.09704931j]]]),\n",
+      "array([ 0.1       ,  0.31622777,  1.        ,  3.16227766, 10.        ]),\n",
+      "name='sys_mtf_zpk$sampled', outputs=2, inputs=2)\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_nl, dt=0:\n",
+      "--------------------------------\n",
+      "<NonlinearIOSystem sys_nl: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_dnl, dt=0.1:\n",
+      "-----------------------------------\n",
+      "<NonlinearIOSystem sys_dnl: ['u[0]'] -> ['y[0]'], dt=0.1>\n",
+      "----\n",
+      "\n",
+      "InterconnectedSystem: sys_ic, dt=0:\n",
+      "-----------------------------------\n",
+      "<InterconnectedSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "LinearICSystem: sys_ic, dt=0:\n",
+      "-----------------------------\n",
+      "<LinearICSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fs, dt=0:\n",
+      "-------------------------\n",
+      "<FlatSystem sys_fs: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fsnl, dt=0:\n",
+      "---------------------------\n",
+      "<FlatSystem sys_fsnl: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "============================================================================\n",
+      "                            Default str (print)\n",
+      "============================================================================\n",
+      "\n",
+      "StateSpace: sys_ss, dt=0:\n",
+      "-------------------------\n",
+      "<StateSpace>: sys_ss\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "\n",
+      "A = [[ 0.  1.]\n",
+      "     [-4. -5.]]\n",
+      "\n",
+      "B = [[0.]\n",
+      "     [1.]]\n",
+      "\n",
+      "C = [[-1.  1.]]\n",
+      "\n",
+      "D = [[0.]]\n",
+      "----\n",
+      "\n",
+      "StateSpace: sys_dss, dt=0.1:\n",
+      "----------------------------\n",
+      "<StateSpace>: sys_dss\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "dt = 0.1\n",
+      "\n",
+      "A = [[ 0.98300988  0.07817246]\n",
+      "     [-0.31268983  0.59214759]]\n",
+      "\n",
+      "B = [[0.00424753]\n",
+      "     [0.07817246]]\n",
+      "\n",
+      "C = [[-1.  1.]]\n",
+      "\n",
+      "D = [[0.]]\n",
+      "----\n",
+      "\n",
+      "StateSpace: stateless, dt=None:\n",
+      "-------------------------------\n",
+      "<StateSpace>: stateless\n",
+      "Inputs (2): ['u0', 'u1']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "States (0): []\n",
+      "dt = None\n",
+      "\n",
+      "A = []\n",
+      "\n",
+      "B = []\n",
+      "\n",
+      "C = []\n",
+      "\n",
+      "D = [[1. 0.]\n",
+      "     [0. 1.]]\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_ss$converted, dt=0:\n",
+      "-----------------------------------------\n",
+      "<TransferFunction>: sys_ss$converted\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "\n",
+      "      s - 1\n",
+      "  -------------\n",
+      "  s^2 + 5 s + 4\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_dss_poly, dt=0.1:\n",
+      "---------------------------------------\n",
+      "<TransferFunction>: sys_dss_poly\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "dt = 0.1\n",
+      "\n",
+      "   0.07392 z - 0.08177\n",
+      "  ----------------------\n",
+      "  z^2 - 1.575 z + 0.6065\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys[3], dt=0:\n",
+      "-------------------------------\n",
+      "<TransferFunction>: sys[3]\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "\n",
+      "  1\n",
+      "  -\n",
+      "  s\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_mtf_zpk, dt=0:\n",
+      "------------------------------------\n",
+      "<TransferFunction>: sys_mtf_zpk\n",
+      "Inputs (2): ['u[0]', 'u[1]']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "\n",
+      "Input 1 to output 1:\n",
+      "\n",
+      "       s - 1\n",
+      "  ---------------\n",
+      "  (s + 1) (s + 4)\n",
+      "\n",
+      "Input 1 to output 2:\n",
+      "\n",
+      "  s\n",
+      "  -\n",
+      "  1\n",
+      "\n",
+      "Input 2 to output 1:\n",
+      "\n",
+      "  0\n",
+      "  -\n",
+      "  1\n",
+      "\n",
+      "Input 2 to output 2:\n",
+      "\n",
+      "         s\n",
+      "  ---------------\n",
+      "  (s + 1) (s + 1)\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_ss$converted$sampled, dt=0:\n",
+      "------------------------------------------------------\n",
+      "<FrequencyResponseData>: sys_ss$converted$sampled\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "\n",
+      "Freq [rad/s]  Response\n",
+      "------------  ---------------------\n",
+      "       0.100     -0.2437   +0.0556j\n",
+      "       0.316      -0.192   +0.1589j\n",
+      "       1.000     0.05882   +0.2353j\n",
+      "       3.162      0.1958  -0.01106j\n",
+      "      10.000     0.05087  -0.07767j\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_dss_poly$sampled, dt=0.1:\n",
+      "----------------------------------------------------\n",
+      "<FrequencyResponseData>: sys_dss_poly$sampled\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "dt = 0.1\n",
+      "\n",
+      "Freq [rad/s]  Response\n",
+      "------------  ---------------------\n",
+      "       0.100     -0.2434  +0.05673j\n",
+      "       0.316     -0.1894   +0.1617j\n",
+      "       1.000     0.07044   +0.2311j\n",
+      "       3.162      0.1904  -0.04416j\n",
+      "      10.000    0.002865  -0.09596j\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_mtf_zpk$sampled, dt=0:\n",
+      "-------------------------------------------------\n",
+      "<FrequencyResponseData>: sys_mtf_zpk$sampled\n",
+      "Inputs (2): ['u[0]', 'u[1]']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "\n",
+      "Input 1 to output 1:\n",
+      "\n",
+      "  Freq [rad/s]  Response\n",
+      "  ------------  ---------------------\n",
+      "         0.100     -0.2437   +0.0556j\n",
+      "         0.316      -0.192   +0.1589j\n",
+      "         1.000     0.05882   +0.2353j\n",
+      "         3.162      0.1958  -0.01106j\n",
+      "        10.000     0.05087  -0.07767j\n",
+      "\n",
+      "Input 1 to output 2:\n",
+      "\n",
+      "  Freq [rad/s]  Response\n",
+      "  ------------  ---------------------\n",
+      "         0.100           0      +0.1j\n",
+      "         0.316           0   +0.3162j\n",
+      "         1.000           0        +1j\n",
+      "         3.162           0    +3.162j\n",
+      "        10.000           0       +10j\n",
+      "\n",
+      "Input 2 to output 1:\n",
+      "\n",
+      "  Freq [rad/s]  Response\n",
+      "  ------------  ---------------------\n",
+      "         0.100           0        +0j\n",
+      "         0.316           0        +0j\n",
+      "         1.000           0        +0j\n",
+      "         3.162           0        +0j\n",
+      "        10.000           0        +0j\n",
+      "\n",
+      "Input 2 to output 2:\n",
+      "\n",
+      "  Freq [rad/s]  Response\n",
+      "  ------------  ---------------------\n",
+      "         0.100     0.01961  +0.09705j\n",
+      "         0.316      0.1653   +0.2352j\n",
+      "         1.000         0.5        +0j\n",
+      "         3.162      0.1653   -0.2352j\n",
+      "        10.000     0.01961  -0.09705j\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_nl, dt=0:\n",
+      "--------------------------------\n",
+      "<NonlinearIOSystem>: sys_nl\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "Parameters: ['a', 'b']\n",
+      "\n",
+      "Update: <function nl_update at 0x1258ed800>\n",
+      "Output: <function nl_output at 0x1258ed8a0>\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_dnl, dt=0.1:\n",
+      "-----------------------------------\n",
+      "<NonlinearIOSystem>: sys_dnl\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "dt = 0.1\n",
+      "\n",
+      "Update: <function dnl_update at 0x1258ed940>\n",
+      "Output: <function dnl_output at 0x1258ed9e0>\n",
+      "----\n",
+      "\n",
+      "InterconnectedSystem: sys_ic, dt=0:\n",
+      "-----------------------------------\n",
+      "<InterconnectedSystem>: sys_ic\n",
+      "Inputs (2): ['r[0]', 'r[1]']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "States (2): ['proc_nl_x[0]', 'proc_nl_x[1]']\n",
+      "\n",
+      "Subsystems (2):\n",
+      " * <NonlinearIOSystem proc_nl: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>\n",
+      " * <NonlinearIOSystem ctrl_nl: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>\n",
+      "\n",
+      "Connections:\n",
+      " * proc_nl.u[0] <- ctrl_nl.y[0]\n",
+      " * proc_nl.u[1] <- ctrl_nl.y[1]\n",
+      " * ctrl_nl.u[0] <- -proc_nl.y[0] + r[0]\n",
+      " * ctrl_nl.u[1] <- -proc_nl.y[1] + r[1]\n",
+      "\n",
+      "Outputs:\n",
+      " * y[0] <- proc_nl.y[0]\n",
+      " * y[1] <- proc_nl.y[1]\n",
+      "\n",
+      "----\n",
+      "\n",
+      "LinearICSystem: sys_ic, dt=0:\n",
+      "-----------------------------\n",
+      "<LinearICSystem>: sys_ic\n",
+      "Inputs (2): ['r[0]', 'r[1]']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "States (2): ['proc_x[0]', 'proc_x[1]']\n",
+      "\n",
+      "Subsystems (2):\n",
+      " * <StateSpace proc: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>\n",
+      " * <StateSpace ctrl: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]'], dt=None>\n",
+      "\n",
+      "Connections:\n",
+      " * proc.u[0] <- ctrl.y[0]\n",
+      " * proc.u[1] <- ctrl.y[1]\n",
+      " * ctrl.u[0] <- -proc.y[0] + r[0]\n",
+      " * ctrl.u[1] <- -proc.y[1] + r[1]\n",
+      "\n",
+      "Outputs:\n",
+      " * y[0] <- proc.y[0]\n",
+      " * y[1] <- proc.y[1]\n",
+      "\n",
+      "A = [[-2.  3.]\n",
+      "     [-1. -5.]]\n",
+      "\n",
+      "B = [[-2.  0.]\n",
+      "     [ 0. -3.]]\n",
+      "\n",
+      "C = [[-1.  1.]\n",
+      "     [ 1.  0.]]\n",
+      "\n",
+      "D = [[0. 0.]\n",
+      "     [0. 0.]]\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fs, dt=0:\n",
+      "-------------------------\n",
+      "<FlatSystem>: sys_fs\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "\n",
+      "Update: <bound method FlatSystem._flat_updfcn of <FlatSystem sys_fs: ['u[0]'] -> ['y[0]']>>\n",
+      "Output: <bound method FlatSystem._flat_outfcn of <FlatSystem sys_fs: ['u[0]'] -> ['y[0]']>>\n",
+      "\n",
+      "Forward: <function fs_forward at 0x1258ee0c0>\n",
+      "Reverse: <function fs_reverse at 0x1258ee160>\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fsnl, dt=0:\n",
+      "---------------------------\n",
+      "<FlatSystem>: sys_fsnl\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "States (2): ['x[0]', 'x[1]']\n",
+      "\n",
+      "Update: <function nl_update at 0x1258ed800>\n",
+      "Output: <function nl_output at 0x1258ed8a0>\n",
+      "\n",
+      "Forward: <function fs_forward at 0x1258ee0c0>\n",
+      "Reverse: <function fs_reverse at 0x1258ee160>\n",
+      "----\n",
+      "\n",
+      "============================================================================\n",
+      "                             repr_format='info'\n",
+      "============================================================================\n",
+      "\n",
+      "StateSpace: sys_ss, dt=0:\n",
+      "-------------------------\n",
+      "<StateSpace sys_ss: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "StateSpace: sys_dss, dt=0.1:\n",
+      "----------------------------\n",
+      "<StateSpace sys_dss: ['u[0]'] -> ['y[0]'], dt=0.1>\n",
+      "----\n",
+      "\n",
+      "StateSpace: stateless, dt=None:\n",
+      "-------------------------------\n",
+      "<StateSpace stateless: ['u0', 'u1'] -> ['y[0]', 'y[1]'], dt=None>\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_ss$converted, dt=0:\n",
+      "-----------------------------------------\n",
+      "<TransferFunction sys_ss$converted: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_dss_poly, dt=0.1:\n",
+      "---------------------------------------\n",
+      "<TransferFunction sys_dss_poly: ['u[0]'] -> ['y[0]'], dt=0.1>\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys[3], dt=0:\n",
+      "-------------------------------\n",
+      "<TransferFunction sys[3]: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_mtf_zpk, dt=0:\n",
+      "------------------------------------\n",
+      "<TransferFunction sys_mtf_zpk: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_ss$converted$sampled, dt=0:\n",
+      "------------------------------------------------------\n",
+      "<FrequencyResponseData sys_ss$converted$sampled: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_dss_poly$sampled, dt=0.1:\n",
+      "----------------------------------------------------\n",
+      "<FrequencyResponseData sys_dss_poly$sampled: ['u[0]'] -> ['y[0]'], dt=0.1>\n",
+      "----\n",
+      "\n",
+      "FrequencyResponseData: sys_mtf_zpk$sampled, dt=0:\n",
+      "-------------------------------------------------\n",
+      "<FrequencyResponseData sys_mtf_zpk$sampled: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_nl, dt=0:\n",
+      "--------------------------------\n",
+      "<NonlinearIOSystem sys_nl: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "NonlinearIOSystem: sys_dnl, dt=0.1:\n",
+      "-----------------------------------\n",
+      "<NonlinearIOSystem sys_dnl: ['u[0]'] -> ['y[0]'], dt=0.1>\n",
+      "----\n",
+      "\n",
+      "InterconnectedSystem: sys_ic, dt=0:\n",
+      "-----------------------------------\n",
+      "<InterconnectedSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "LinearICSystem: sys_ic, dt=0:\n",
+      "-----------------------------\n",
+      "<LinearICSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fs, dt=0:\n",
+      "-------------------------\n",
+      "<FlatSystem sys_fs: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "FlatSystem: sys_fsnl, dt=0:\n",
+      "---------------------------\n",
+      "<FlatSystem sys_fsnl: ['u[0]'] -> ['y[0]']>\n",
+      "----\n",
+      "\n",
+      "============================================================================\n",
+      "                        iosys.repr_show_count=False\n",
+      "============================================================================\n",
+      "\n",
+      "StateSpace: sys_ss, dt=0:\n",
+      "-------------------------\n",
+      "StateSpace(\n",
+      "array([[ 0.,  1.],\n",
+      "       [-4., -5.]]),\n",
+      "array([[0.],\n",
+      "       [1.]]),\n",
+      "array([[-1.,  1.]]),\n",
+      "array([[0.]]),\n",
+      "name='sys_ss')\n",
+      "----\n",
+      "\n",
+      "StateSpace: sys_dss, dt=0.1:\n",
+      "----------------------------\n",
+      "StateSpace(\n",
+      "array([[ 0.98300988,  0.07817246],\n",
+      "       [-0.31268983,  0.59214759]]),\n",
+      "array([[0.00424753],\n",
+      "       [0.07817246]]),\n",
+      "array([[-1.,  1.]]),\n",
+      "array([[0.]]),\n",
+      "dt=0.1,\n",
+      "name='sys_dss')\n",
+      "----\n",
+      "\n",
+      "StateSpace: stateless, dt=None:\n",
+      "-------------------------------\n",
+      "StateSpace(\n",
+      "array([], shape=(0, 0), dtype=float64),\n",
+      "array([], shape=(0, 2), dtype=float64),\n",
+      "array([], shape=(2, 0), dtype=float64),\n",
+      "array([[1., 0.],\n",
+      "       [0., 1.]]),\n",
+      "dt=None,\n",
+      "name='stateless', inputs=['u0', 'u1'])\n",
+      "----\n",
+      "\n",
+      "LinearICSystem: sys_ic, dt=0:\n",
+      "-----------------------------\n",
+      "<LinearICSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>\n",
+      "----\n",
+      "\n",
+      "============================================================================\n",
+      "                  xferfcn.display_format=zpk, str (print)\n",
+      "============================================================================\n",
+      "\n",
+      "TransferFunction: sys_ss$converted, dt=0:\n",
+      "-----------------------------------------\n",
+      "<TransferFunction>: sys_ss$converted\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "\n",
+      "       s - 1\n",
+      "  ---------------\n",
+      "  (s + 1) (s + 4)\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_dss_poly, dt=0.1:\n",
+      "---------------------------------------\n",
+      "<TransferFunction>: sys_dss_poly\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "dt = 0.1\n",
+      "\n",
+      "   0.07392 z - 0.08177\n",
+      "  ----------------------\n",
+      "  z^2 - 1.575 z + 0.6065\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys[3], dt=0:\n",
+      "-------------------------------\n",
+      "<TransferFunction>: sys[3]\n",
+      "Inputs (1): ['u[0]']\n",
+      "Outputs (1): ['y[0]']\n",
+      "\n",
+      "  1\n",
+      "  -\n",
+      "  s\n",
+      "----\n",
+      "\n",
+      "TransferFunction: sys_mtf_zpk, dt=0:\n",
+      "------------------------------------\n",
+      "<TransferFunction>: sys_mtf_zpk\n",
+      "Inputs (2): ['u[0]', 'u[1]']\n",
+      "Outputs (2): ['y[0]', 'y[1]']\n",
+      "\n",
+      "Input 1 to output 1:\n",
+      "\n",
+      "       s - 1\n",
+      "  ---------------\n",
+      "  (s + 1) (s + 4)\n",
+      "\n",
+      "Input 1 to output 2:\n",
+      "\n",
+      "  s\n",
+      "  -\n",
+      "  1\n",
+      "\n",
+      "Input 2 to output 1:\n",
+      "\n",
+      "  0\n",
+      "  -\n",
+      "  1\n",
+      "\n",
+      "Input 2 to output 2:\n",
+      "\n",
+      "         s\n",
+      "  ---------------\n",
+      "  (s + 1) (s + 1)\n",
+      "----\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Grab system definitions (and generate various representations as text)\n",
+    "from repr_gallery import *"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19f146a3-c036-4ff6-8425-c201fba14ec7",
+   "metadata": {},
+   "source": [
+    "## Jupyter notebook (HTML/LaTeX) representations\n",
+    "\n",
+    "The following representations are generated using the `_html_repr_` method in selected types of systems.  Only those systems that have unique displays are shown."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "16ff8d11-e793-456a-bf27-ae4cc0dd1e3b",
+   "metadata": {},
+   "source": [
+    "### Continuous time state space systems"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "c1ca661d-10f3-45be-8619-c3e143bb4b4c",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace sys_ss: ['u[0]'] -&gt; ['y[0]']&gt;\n",
+       "$$\n",
+       "\\left[\\begin{array}{rllrll|rll}\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "-4\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&-5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\hline\n",
+       "-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([[ 0.,  1.],\n",
+       "       [-4., -5.]]),\n",
+       "array([[0.],\n",
+       "       [1.]]),\n",
+       "array([[-1.,  1.]]),\n",
+       "array([[0.]]),\n",
+       "name='sys_ss', states=2, outputs=1, inputs=1)"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_ss"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "b10b4db3-a8c0-4a2c-a19d-a09fef3dc25f",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace sys_ss: ['u[0]'] -&gt; ['y[0]']&gt;\n",
+       "$$\n",
+       "\\begin{array}{ll}\n",
+       "A = \\left[\\begin{array}{rllrll}\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "-4\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&-5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "&\n",
+       "B = \\left[\\begin{array}{rll}\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "\\\\\n",
+       "C = \\left[\\begin{array}{rllrll}\n",
+       "-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "&\n",
+       "D = \\left[\\begin{array}{rll}\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "\\end{array}\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([[ 0.,  1.],\n",
+       "       [-4., -5.]]),\n",
+       "array([[0.],\n",
+       "       [1.]]),\n",
+       "array([[-1.,  1.]]),\n",
+       "array([[0.]]),\n",
+       "name='sys_ss', states=2, outputs=1, inputs=1)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "with ct.config.defaults({'statesp.latex_repr_type': 'separate'}):\n",
+    "    display(sys_ss)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "0537f6fe-a155-4c49-be7c-413608a03887",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace sys_ss: ['u[0]'] -&gt; ['y[0]']&gt;\n",
+       "$$\n",
+       "\\begin{array}{ll}\n",
+       "A = \\left[\\begin{array}{rllrll}\n",
+       "  0.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}&  1.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       " -4.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}& -5.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "&\n",
+       "B = \\left[\\begin{array}{rll}\n",
+       "  0.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "  1.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "\\\\\n",
+       "C = \\left[\\begin{array}{rllrll}\n",
+       " -1.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}&  1.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "&\n",
+       "D = \\left[\\begin{array}{rll}\n",
+       "  0.&\\hspace{-1em}0000&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "\\end{array}\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([[ 0.,  1.],\n",
+       "       [-4., -5.]]),\n",
+       "array([[0.],\n",
+       "       [1.]]),\n",
+       "array([[-1.,  1.]]),\n",
+       "array([[0.]]),\n",
+       "name='sys_ss', states=2, outputs=1, inputs=1)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "with ct.config.defaults({\n",
+    "    'statesp.latex_repr_type': 'separate',\n",
+    "    'statesp.latex_num_format': '8.4f'}):\n",
+    "    display(sys_ss)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fa75f040-633d-401c-ba96-e688713d5a2d",
+   "metadata": {},
+   "source": [
+    "### Stateless state space system"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "5a71e38c-9880-4af7-82e0-49f074653e94",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace stateless: ['u0', 'u1'] -&gt; ['y[0]', 'y[1]'], dt=None&gt;\n",
+       "$$\n",
+       "\\left[\\begin{array}{rllrll}\n",
+       "1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([], shape=(0, 0), dtype=float64),\n",
+       "array([], shape=(0, 2), dtype=float64),\n",
+       "array([], shape=(2, 0), dtype=float64),\n",
+       "array([[1., 0.],\n",
+       "       [0., 1.]]),\n",
+       "dt=None,\n",
+       "name='stateless', states=0, outputs=2, inputs=['u0', 'u1'])"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_ss0"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "7ddbd638-9338-4204-99bc-792f35e14874",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace stateless: ['u0', 'u1'] -&gt; ['y[0]', 'y[1]'], dt=None&gt;\n",
+       "$$\n",
+       "\\begin{array}{ll}\n",
+       "D = \\left[\\begin{array}{rllrll}\n",
+       "1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "\\end{array}\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([], shape=(0, 0), dtype=float64),\n",
+       "array([], shape=(0, 2), dtype=float64),\n",
+       "array([], shape=(2, 0), dtype=float64),\n",
+       "array([[1., 0.],\n",
+       "       [0., 1.]]),\n",
+       "dt=None,\n",
+       "name='stateless', states=0, outputs=2, inputs=['u0', 'u1'])"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "with ct.config.defaults({'statesp.latex_repr_type': 'separate'}):\n",
+    "    display(sys_ss0)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "06c5d470-0768-4628-b2ea-d2387525ed80",
+   "metadata": {},
+   "source": [
+    "### Discrete time state space system"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "e7b9b438-28e3-453e-9860-06ff75b7af10",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace sys_dss: ['u[0]'] -&gt; ['y[0]'], dt=0.1&gt;\n",
+       "$$\n",
+       "\\left[\\begin{array}{rllrll|rll}\n",
+       "0.&\\hspace{-1em}983&\\hspace{-1em}\\phantom{\\cdot}&0.&\\hspace{-1em}0782&\\hspace{-1em}\\phantom{\\cdot}&0.&\\hspace{-1em}00425&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "-0.&\\hspace{-1em}313&\\hspace{-1em}\\phantom{\\cdot}&0.&\\hspace{-1em}592&\\hspace{-1em}\\phantom{\\cdot}&0.&\\hspace{-1em}0782&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\hline\n",
+       "-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([[ 0.98300988,  0.07817246],\n",
+       "       [-0.31268983,  0.59214759]]),\n",
+       "array([[0.00424753],\n",
+       "       [0.07817246]]),\n",
+       "array([[-1.,  1.]]),\n",
+       "array([[0.]]),\n",
+       "dt=0.1,\n",
+       "name='sys_dss', states=2, outputs=1, inputs=1)"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_dss"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7719e725-9d38-4f2a-a142-0ebc090e74e4",
+   "metadata": {},
+   "source": [
+    "### \"Stateless\" state space system"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "938fd795-f402-4491-b2c9-eb42c458e1e1",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;StateSpace stateless: ['u0', 'u1'] -&gt; ['y[0]', 'y[1]'], dt=None&gt;\n",
+       "$$\n",
+       "\\left[\\begin{array}{rllrll}\n",
+       "1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "$$"
+      ],
+      "text/plain": [
+       "StateSpace(\n",
+       "array([], shape=(0, 0), dtype=float64),\n",
+       "array([], shape=(0, 2), dtype=float64),\n",
+       "array([], shape=(2, 0), dtype=float64),\n",
+       "array([[1., 0.],\n",
+       "       [0., 1.]]),\n",
+       "dt=None,\n",
+       "name='stateless', states=0, outputs=2, inputs=['u0', 'u1'])"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_ss0"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c620f1a1-40ff-4320-9f62-21bff9ab308e",
+   "metadata": {},
+   "source": [
+    "### Continuous time transfer function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "e364e6eb-0cfa-486a-8b95-ff9c6c41a091",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;TransferFunction sys_ss\\$converted: ['u[0]'] -&gt; ['y[0]']&gt;\n",
+       "$$\\dfrac{s - 1}{s^2 + 5 s + 4}$$"
+      ],
+      "text/plain": [
+       "TransferFunction(\n",
+       "array([ 1., -1.]),\n",
+       "array([1., 5., 4.]),\n",
+       "name='sys_ss$converted', outputs=1, inputs=1)"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_tf"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "id": "af9959fd-90eb-4287-93ee-416cd13fde50",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;TransferFunction sys_ss\\$converted: ['u[0]'] -&gt; ['y[0]']&gt;\n",
+       "$$\\dfrac{s - 1}{(s + 1) (s + 4)}$$"
+      ],
+      "text/plain": [
+       "TransferFunction(\n",
+       "array([ 1., -1.]),\n",
+       "array([1., 5., 4.]),\n",
+       "name='sys_ss$converted', outputs=1, inputs=1)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "with ct.config.defaults({'xferfcn.display_format': 'zpk'}):\n",
+    "    display(sys_tf)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7bf40707-f84c-4e19-b310-5ec9811faf42",
+   "metadata": {},
+   "source": [
+    "### MIMO transfer function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "b2db2d1c-893b-43a1-aab0-a5f6d059f3f9",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/Users/murray/miniconda3/envs/python3.13-slycot/lib/python3.13/site-packages/scipy/signal/_filter_design.py:1112: BadCoefficients: Badly conditioned filter coefficients (numerator): the results may be meaningless\n",
+      "  b, a = normalize(b, a)\n",
+      "/Users/murray/miniconda3/envs/python3.13-slycot/lib/python3.13/site-packages/scipy/signal/_filter_design.py:1116: RuntimeWarning: invalid value encountered in divide\n",
+      "  b /= b[0]\n"
+     ]
+    },
+    {
+     "data": {
+      "text/html": [
+       "&lt;TransferFunction sys_mtf_zpk: ['u[0]', 'u[1]'] -&gt; ['y[0]', 'y[1]']&gt;\n",
+       "$$\\begin{bmatrix}\\dfrac{s - 1}{(s + 1) (s + 4)}&\\dfrac{0}{1}\\\\\\dfrac{s}{1}&\\dfrac{s}{(s + 1) (s + 1)}\\\\ \\end{bmatrix}$$"
+      ],
+      "text/plain": [
+       "TransferFunction(\n",
+       "[[array([ 1., -1.]), array([0.])],\n",
+       " [array([1, 0]), array([1, 0])]],\n",
+       "[[array([1., 5., 4.]), array([1.])],\n",
+       " [array([1]), array([1, 2, 1])]],\n",
+       "name='sys_mtf_zpk', outputs=2, inputs=2)"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_mtf  # SciPy generates a warning due to 0 numerator in 1, 2 entry"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ef78a05e-9a63-4e22-afae-66ac8ec457c2",
+   "metadata": {},
+   "source": [
+    "### Discrete time transfer function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "365c9b4a-2af3-42e5-ae5d-f2d7d989104b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;TransferFunction sys_dss_poly: ['u[0]'] -&gt; ['y[0]'], dt=0.1&gt;\n",
+       "$$\\dfrac{0.07392 z - 0.08177}{z^2 - 1.575 z + 0.6065}$$"
+      ],
+      "text/plain": [
+       "TransferFunction(\n",
+       "array([ 0.07392493, -0.08176823]),\n",
+       "array([ 1.        , -1.57515746,  0.60653066]),\n",
+       "dt=0.1,\n",
+       "name='sys_dss_poly', outputs=1, inputs=1)"
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_dtf"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b49fa8ab-c3af-48d1-b160-790c9f4d3c6e",
+   "metadata": {},
+   "source": [
+    "### Linear interconnected system"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "78d21969-4615-4a47-b449-7a08138dc319",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "&lt;LinearICSystem sys_ic: ['r[0]', 'r[1]'] -&gt; ['y[0]', 'y[1]']&gt;\n",
+       "$$\n",
+       "\\left[\\begin{array}{rllrll|rllrll}\n",
+       "-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&3\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&-5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&-3\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\hline\n",
+       "-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n",
+       "\\end{array}\\right]\n",
+       "$$"
+      ],
+      "text/plain": [
+       "<LinearICSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "sys_lic"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "bee65cd5-d9b5-46af-aee5-26a6a4679939",
+   "metadata": {},
+   "source": [
+    "### Non-HTML capable system representations"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "e5486349-2bd3-4015-ad17-a5b8e8ec0447",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "FrequencyResponseData(\n",
+       "array([[[-0.24365959+0.05559644j, -0.19198193+0.1589174j ,\n",
+       "          0.05882353+0.23529412j,  0.1958042 -0.01105691j,\n",
+       "          0.0508706 -0.07767156j]]]),\n",
+       "array([ 0.1       ,  0.31622777,  1.        ,  3.16227766, 10.        ]),\n",
+       "name='sys_ss$converted$sampled', outputs=1, inputs=1)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<NonlinearIOSystem sys_nl: ['u[0]'] -> ['y[0]']>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<InterconnectedSystem sys_ic: ['r[0]', 'r[1]'] -> ['y[0]', 'y[1]']>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<FlatSystem sys_fs: ['u[0]'] -> ['y[0]']>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "for sys in [sys_frd, sys_nl, sys_ic, sys_fs]:\n",
+    "    display(sys)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.13.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/examples/repr_gallery.py b/examples/repr_gallery.py
new file mode 100644
index 00000000..27755b59
--- /dev/null
+++ b/examples/repr_gallery.py
@@ -0,0 +1,156 @@
+# repr-galler.py - different system representations for comparing versions
+# RMM, 30 Dec 2024
+#
+# This file creates different types of systems and generates a variety
+# of representations (__repr__, __str__) for those systems that can be
+# used to compare different versions of python-control.  It is mainly
+# intended for uses by developers to make sure there are no unexpected
+# changes in representation formats, but also has some interesting
+# examples of different choices in system representation.
+
+import numpy as np
+
+import control as ct
+import control.flatsys as fs
+
+#
+# Create systems of different types
+#
+syslist = []
+
+# State space (continuous and discrete time)
+sys_ss = ct.ss([[0, 1], [-4, -5]], [0, 1], [-1, 1], 0, name='sys_ss')
+sys_dss = sys_ss.sample(0.1, name='sys_dss')
+sys_ss0 = ct.ss([], [], [], np.eye(2), name='stateless', inputs=['u0', 'u1'])
+syslist += [sys_ss, sys_dss, sys_ss0]
+
+# Transfer function (continuous and discrete time)
+sys_tf = ct.tf(sys_ss)
+sys_dtf = ct.tf(sys_dss, name='sys_dss_poly', display_format='poly')
+sys_gtf = ct.tf([1], [1, 0])
+syslist += [sys_tf, sys_dtf, sys_gtf]
+
+# MIMO transfer function (continuous time only)
+sys_mtf = ct.tf(
+    [[sys_tf.num[0][0].tolist(), [0]], [[1, 0], [1, 0]  ]],
+    [[sys_tf.den[0][0].tolist(), [1]], [[1],    [1, 2, 1]]],
+    name='sys_mtf_zpk', display_format='zpk')
+syslist += [sys_mtf]
+
+# Frequency response data (FRD) system (continuous and discrete time)
+sys_frd = ct.frd(sys_tf, np.logspace(-1, 1, 5))
+sys_dfrd = ct.frd(sys_dtf, np.logspace(-1, 1, 5))
+sys_mfrd = ct.frd(sys_mtf, np.logspace(-1, 1, 5))
+syslist += [sys_frd, sys_dfrd, sys_mfrd]
+
+# Nonlinear system (with linear dynamics), continuous time
+def nl_update(t, x, u, params):
+    return sys_ss.A @ x + sys_ss.B @ u
+
+def nl_output(t, x, u, params):
+    return sys_ss.C @ x + sys_ss.D @ u
+
+nl_params = {'a': 0, 'b': 1}
+
+sys_nl = ct.nlsys(
+    nl_update, nl_output, name='sys_nl', params=nl_params,
+    states=sys_ss.nstates, inputs=sys_ss.ninputs, outputs=sys_ss.noutputs)
+
+# Nonlinear system (with linear dynamics), discrete time
+def dnl_update(t, x, u, params):
+    return sys_ss.A @ x + sys_ss.B @ u
+
+def dnl_output(t, x, u, params):
+    return sys_ss.C @ x + sys_ss.D @ u
+
+sys_dnl = ct.nlsys(
+    dnl_update, dnl_output, dt=0.1, name='sys_dnl',
+    states=sys_ss.nstates, inputs=sys_ss.ninputs, outputs=sys_ss.noutputs)
+
+syslist += [sys_nl, sys_dnl]
+
+# Interconnected system
+proc = ct.ss([[0, 1], [-4, -5]], np.eye(2), [[-1, 1], [1, 0]], 0, name='proc')
+ctrl = ct.ss([], [], [], [[-2, 0], [0, -3]], name='ctrl')
+
+proc_nl = ct.nlsys(proc, name='proc_nl')
+ctrl_nl = ct.nlsys(ctrl, name='ctrl_nl')
+sys_ic = ct.interconnect(
+    [proc_nl, ctrl_nl], name='sys_ic',
+    connections=[['proc_nl.u', 'ctrl_nl.y'], ['ctrl_nl.u', '-proc_nl.y']],
+    inplist=['ctrl_nl.u'], inputs=['r[0]', 'r[1]'],
+    outlist=['proc_nl.y'], outputs=proc_nl.output_labels)
+syslist += [sys_ic]
+
+# Linear interconnected system
+sys_lic = ct.interconnect(
+    [proc, ctrl], name='sys_ic',
+    connections=[['proc.u', 'ctrl.y'], ['ctrl.u', '-proc.y']],
+    inplist=['ctrl.u'], inputs=['r[0]', 'r[1]'],
+    outlist=['proc.y'], outputs=proc.output_labels)
+syslist += [sys_lic]
+
+# Differentially flat system (with implicit dynamics), continuous time (only)
+def fs_forward(x, u):
+    return np.array([x[0], x[1], -4 * x[0] - 5 * x[1] + u[0]])
+
+def fs_reverse(zflag):
+    return (
+        np.array([zflag[0][0], zflag[0][1]]),
+        np.array([4 * zflag[0][0] + 5 * zflag[0][1] + zflag[0][2]]))
+
+sys_fs = fs.flatsys(
+    fs_forward, fs_reverse, name='sys_fs',
+    states=sys_nl.nstates, inputs=sys_nl.ninputs, outputs=sys_nl.noutputs)
+
+# Differentially flat system (with nonlinear dynamics), continuous time (only)
+sys_fsnl = fs.flatsys(
+    fs_forward, fs_reverse, nl_update, nl_output, name='sys_fsnl',
+    states=sys_nl.nstates, inputs=sys_nl.ninputs, outputs=sys_nl.noutputs)
+
+syslist += [sys_fs, sys_fsnl]
+
+# Utility function to display outputs
+def display_representations(
+        description, fcn, class_list=(ct.InputOutputSystem, )):
+    print("=" * 76)
+    print(" " * round((76 - len(description)) / 2) + f"{description}")
+    print("=" * 76 + "\n")
+    for sys in syslist:
+        if isinstance(sys, tuple(class_list)):
+            print(str := f"{type(sys).__name__}: {sys.name}, dt={sys.dt}:")
+            print("-" * len(str))
+            print(fcn(sys))
+            print("----\n")
+
+# Default formats
+display_representations("Default repr", repr)
+display_representations("Default str (print)", str)
+
+# 'info' format (if it exists and hasn't already been displayed)
+if getattr(ct.InputOutputSystem, '_repr_info_', None) and \
+   ct.config.defaults.get('iosys.repr_format', None) and \
+   ct.config.defaults['iosys.repr_format'] != 'info':
+    with ct.config.defaults({'iosys.repr_format': 'info'}):
+        display_representations("repr_format='info'", repr)
+
+# 'eval' format (if it exists and hasn't already been displayed)
+if getattr(ct.InputOutputSystem, '_repr_eval_', None) and \
+   ct.config.defaults.get('iosys.repr_format', None) and \
+   ct.config.defaults['iosys.repr_format'] != 'eval':
+    with ct.config.defaults({'iosys.repr_format': 'eval'}):
+        display_representations("repr_format='eval'", repr)
+
+# Change the way counts are displayed
+with ct.config.defaults(
+        {'iosys.repr_show_count':
+         not ct.config.defaults['iosys.repr_show_count']}):
+    display_representations(
+        f"iosys.repr_show_count={ct.config.defaults['iosys.repr_show_count']}",
+        repr, class_list=[ct.StateSpace])
+
+# ZPK format for transfer functions
+with ct.config.defaults({'xferfcn.display_format': 'zpk'}):
+    display_representations(
+        "xferfcn.display_format=zpk, str (print)", str,
+        class_list=[ct.TransferFunction])


Test patch:
diff --git a/control/tests/config_test.py b/control/tests/config_test.py
index 947dc95a..c214526c 100644
--- a/control/tests/config_test.py
+++ b/control/tests/config_test.py
@@ -319,3 +319,45 @@ class TestConfig:
             indexed_system_name_suffix='POST')
         sys2 = sys[1:, 1:]
         assert sys2.name == 'PRE' + sys.name + 'POST'
+
+    @pytest.mark.parametrize("kwargs", [
+        {},
+        {'name': 'mysys'},
+        {'inputs': 1},
+        {'inputs': 'u'},
+        {'outputs': 1},
+        {'outputs': 'y'},
+        {'states': 1},
+        {'states': 'x'},
+        {'inputs': 1, 'outputs': 'y', 'states': 'x'},
+        {'dt': 0.1}
+    ])
+    def test_repr_format(self, kwargs):
+        from ..statesp import StateSpace
+        from numpy import array
+
+        sys = ct.ss([[1]], [[1]], [[1]], [[0]], **kwargs)
+        new = eval(repr(sys))
+        for attr in ['A', 'B', 'C', 'D']:
+            assert getattr(new, attr) == getattr(sys, attr)
+        for prop in ['input_labels', 'output_labels', 'state_labels']:
+            assert getattr(new, attr) == getattr(sys, attr)
+        if 'name' in kwargs:
+            assert new.name == sys.name
+
+
+def test_config_context_manager():
+    # Make sure we can temporarily set the value of a parameter
+    default_val = ct.config.defaults['statesp.latex_repr_type']
+    with ct.config.defaults({'statesp.latex_repr_type': 'new value'}):
+        assert ct.config.defaults['statesp.latex_repr_type'] != default_val
+        assert ct.config.defaults['statesp.latex_repr_type'] == 'new value'
+    assert ct.config.defaults['statesp.latex_repr_type'] == default_val
+
+    # OK to call the context manager and not do anything with it
+    ct.config.defaults({'statesp.latex_repr_type': 'new value'})
+    assert ct.config.defaults['statesp.latex_repr_type'] == default_val
+
+    with pytest.raises(ValueError, match="unknown parameter 'unknown'"):
+        with ct.config.defaults({'unknown': 'new value'}):
+            pass
diff --git a/control/tests/docstrings_test.py b/control/tests/docstrings_test.py
index 16647895..0c0a7904 100644
--- a/control/tests/docstrings_test.py
+++ b/control/tests/docstrings_test.py
@@ -35,7 +35,7 @@ function_docstring_hash = {
     control.lqe:                        '567bf657538935173f2e50700ba87168',
     control.lqr:                        'a3e0a85f781fc9c0f69a4b7da4f0bd22',
     control.margin:                     'f02b3034f5f1d44ce26f916cc3e51600',
-    control.parallel:                   '025c5195a34c57392223374b6244a8c4',
+    control.parallel:                   'bfc470aef75dbb923f9c6fb8bf3c9b43',
     control.series:                     '9aede1459667738f05cf4fc46603a4f6',
     control.ss2tf:                      '48ff25d22d28e7b396e686dd5eb58831',
     control.tf2ss:                      '086a3692659b7321c2af126f79f4bc11',
diff --git a/control/tests/frd_test.py b/control/tests/frd_test.py
index b08cd826..c63d9e21 100644
--- a/control/tests/frd_test.py
+++ b/control/tests/frd_test.py
@@ -465,21 +465,23 @@ class TestFRD:
             [0.1, 1.0, 10.0, 100.0], name='sys0')
         sys1 = ct.frd(
             sys0.fresp, sys0.omega, smooth=True, name='sys1')
-        ref0 = "FrequencyResponseData(" \
-            "array([[[1.  +0.j , 0.9 +0.1j, 0.1 +2.j , 0.05+3.j ]]])," \
-            " array([  0.1,   1. ,  10. , 100. ]))"
-        ref1 = ref0[:-1] + ", smooth=True)"
+        ref_common = "FrequencyResponseData(\n" \
+            "array([[[1.  +0.j , 0.9 +0.1j, 0.1 +2.j , 0.05+3.j ]]]),\n" \
+            "array([  0.1,   1. ,  10. , 100. ]),"
+        ref0 = ref_common + "\nname='sys0', outputs=1, inputs=1)"
+        ref1 = ref_common + " smooth=True," + \
+            "\nname='sys1', outputs=1, inputs=1)"
         sysm = ct.frd(
             np.matmul(array([[1], [2]]), sys0.fresp), sys0.omega, name='sysm')
 
-        assert repr(sys0) == ref0
-        assert repr(sys1) == ref1
+        assert ct.iosys_repr(sys0, format='eval') == ref0
+        assert ct.iosys_repr(sys1, format='eval') == ref1
 
-        sys0r = eval(repr(sys0))
+        sys0r = eval(ct.iosys_repr(sys0, format='eval'))
         np.testing.assert_array_almost_equal(sys0r.fresp, sys0.fresp)
         np.testing.assert_array_almost_equal(sys0r.omega, sys0.omega)
 
-        sys1r = eval(repr(sys1))
+        sys1r = eval(ct.iosys_repr(sys1, format='eval'))
         np.testing.assert_array_almost_equal(sys1r.fresp, sys1.fresp)
         np.testing.assert_array_almost_equal(sys1r.omega, sys1.omega)
         assert(sys1._ifunc is not None)
@@ -503,19 +505,22 @@ Inputs (2): ['u[0]', 'u[1]']
 Outputs (1): ['y[0]']
 
 Input 1 to output 1:
-Freq [rad/s]  Response
-------------  ---------------------
-       0.100           1        +0j
-       1.000         0.9      +0.1j
-      10.000         0.1        +2j
-     100.000        0.05        +3j
+
+  Freq [rad/s]  Response
+  ------------  ---------------------
+         0.100           1        +0j
+         1.000         0.9      +0.1j
+        10.000         0.1        +2j
+       100.000        0.05        +3j
+
 Input 2 to output 1:
-Freq [rad/s]  Response
-------------  ---------------------
-       0.100           2        +0j
-       1.000         1.8      +0.2j
-      10.000         0.2        +4j
-     100.000         0.1        +6j"""
+
+  Freq [rad/s]  Response
+  ------------  ---------------------
+         0.100           2        +0j
+         1.000         1.8      +0.2j
+        10.000         0.2        +4j
+       100.000         0.1        +6j"""
         assert str(sysm) == refm
 
     def test_unrecognized_keyword(self):
diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py
index 54d6d56c..78177995 100644
--- a/control/tests/iosys_test.py
+++ b/control/tests/iosys_test.py
@@ -2287,6 +2287,89 @@ def test_signal_indexing():
     with pytest.raises(IndexError, match=r"signal name\(s\) not valid"):
         resp.outputs['y[0]', 'u[0]']
 
+
+@slycotonly
+@pytest.mark.parametrize("fcn, spec, expected, missing", [
+    (ct.ss, {}, "states=4, outputs=3, inputs=2", r"dt|name"),
+    (ct.tf, {}, "outputs=3, inputs=2", r"dt|states|name"),
+    (ct.frd, {}, "outputs=3, inputs=2", r"dt|states|name"),
+    (ct.ss, {'dt': 0.1}, ".*\ndt=0.1,\nstates=4, outputs=3, inputs=2", r"name"),
+    (ct.tf, {'dt': 0.1}, ".*\ndt=0.1,\noutputs=3, inputs=2", r"states|name"),
+    (ct.frd, {'dt': 0.1}, ".*\ndt=0.1,\noutputs=3, inputs=2", r"states|name"),
+    (ct.ss, {'dt': True}, "\ndt=True,\nstates=4, outputs=3, inputs=2", r"name"),
+    (ct.ss, {'dt': None}, "\ndt=None,\nstates=4, outputs=3, inputs=2", r"name"),
+    (ct.ss, {'dt': 0}, "states=4, outputs=3, inputs=2", r"dt|name"),
+    (ct.ss, {'name': 'mysys'}, "\nname='mysys'", r"dt"),
+    (ct.tf, {'name': 'mysys'}, "\nname='mysys'", r"dt|states"),
+    (ct.frd, {'name': 'mysys'}, "\nname='mysys'", r"dt|states"),
+    (ct.ss, {'inputs': ['u1']},
+     r"[\n]states=4, outputs=3, inputs=\['u1'\]", r"dt|name"),
+    (ct.tf, {'inputs': ['u1']},
+     r"[\n]outputs=3, inputs=\['u1'\]", r"dt|name"),
+    (ct.frd, {'inputs': ['u1'], 'name': 'sampled'},
+     r"[\n]name='sampled', outputs=3, inputs=\['u1'\]", r"dt"),
+    (ct.ss, {'outputs': ['y1']},
+     r"[\n]states=4, outputs=\['y1'\], inputs=2", r"dt|name"),
+    (ct.ss, {'name': 'mysys', 'inputs': ['u1']},
+     r"[\n]name='mysys', states=4, outputs=3, inputs=\['u1'\]", r"dt"),
+    (ct.ss, {'name': 'mysys', 'states': [
+        'long_state_1', 'long_state_2', 'long_state_3']},
+     r"[\n]name='.*', states=\[.*\],\noutputs=3, inputs=2\)", r"dt"),
+])
+@pytest.mark.parametrize("format", ['info', 'eval'])
+def test_iosys_repr(fcn, spec, expected, missing, format):
+    spec['outputs'] = spec.get('outputs', 3)
+    spec['inputs'] = spec.get('inputs', 2)
+    if fcn is ct.ss:
+        spec['states'] = spec.get('states', 4)
+
+    sys = ct.rss(**spec)
+    match fcn:
+        case ct.frd:
+            omega = np.logspace(-1, 1)
+            sys = fcn(sys, omega, name=spec.get('name'))
+        case ct.tf:
+            sys = fcn(sys, name=spec.get('name'))
+    assert sys.shape == (sys.noutputs, sys.ninputs)
+
+    # Construct the 'info' format
+    info_expected = f"<{sys.__class__.__name__} {sys.name}: " \
+        f"{sys.input_labels} -> {sys.output_labels}"
+    if sys.dt != 0:
+        info_expected += f", dt={sys.dt}>"
+    else:
+        info_expected += ">"
+
+    # Make sure the default format is OK
+    out = repr(sys)
+    if ct.config.defaults['iosys.repr_format'] == 'info':
+        assert out == info_expected
+    else:
+        assert re.search(expected, out) != None
+
+    # Now set the format to the given type and make sure things look right
+    sys.repr_format = format
+    out = repr(sys)
+    if format == 'eval':
+        assert re.search(expected, out) is not None
+
+        if missing is not None:
+            assert re.search(missing, out) is None
+
+    elif format == 'info':
+        assert out == info_expected
+
+    # Make sure we can change back to the default format
+    sys.repr_format = None
+
+    # Make sure the default format is OK
+    out = repr(sys)
+    if ct.config.defaults['iosys.repr_format'] == 'info':
+        assert out == info_expected
+    elif ct.config.defaults['iosys.repr_format'] == 'eval':
+        assert re.search(expected, out) != None
+
+
 @pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd, ct.nlsys, fs.flatsys])
 def test_relabeling(fcn):
     sys = ct.rss(1, 1, 1, name="sys")
diff --git a/control/tests/kwargs_test.py b/control/tests/kwargs_test.py
index 95450da0..d73df0bb 100644
--- a/control/tests/kwargs_test.py
+++ b/control/tests/kwargs_test.py
@@ -96,6 +96,7 @@ def test_kwarg_search(module, prefix):
 @pytest.mark.parametrize(
     "function, nsssys, ntfsys, moreargs, kwargs",
     [(control.append, 2, 0, (), {}),
+     (control.combine_tf, 0, 0, ([[1, 0], [0, 1]], ), {}),
      (control.dlqe, 1, 0, ([[1]], [[1]]), {}),
      (control.dlqr, 1, 0, ([[1, 0], [0, 1]], [[1]]), {}),
      (control.drss, 0, 0, (2, 1, 1), {}),
@@ -245,6 +246,7 @@ kwarg_unittest = {
     'bode': test_response_plot_kwargs,
     'bode_plot': test_response_plot_kwargs,
     'LTI.bode_plot': test_response_plot_kwargs, # alias for bode_plot and tested via bode_plot
+    'combine_tf': test_unrecognized_kwargs,
     'create_estimator_iosystem': stochsys_test.test_estimator_errors,
     'create_statefbk_iosystem': statefbk_test.TestStatefbk.test_statefbk_errors,
     'describing_function_plot': test_matplotlib_kwargs,
diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py
index 5359ceea..e93138af 100644
--- a/control/tests/lti_test.py
+++ b/control/tests/lti_test.py
@@ -1,15 +1,19 @@
 """lti_test.py"""
 
+import re
+
 import numpy as np
 import pytest
-from .conftest import editsdefaults
 
 import control as ct
-from control import c2d, tf, ss, tf2ss, NonlinearIOSystem
-from control.lti import LTI, evalfr, damp, dcgain, zeros, poles, bandwidth
-from control import common_timebase, isctime, isdtime, issiso
-from control.tests.conftest import slycotonly
+from control import NonlinearIOSystem, c2d, common_timebase, isctime, \
+    isdtime, issiso, ss, tf, tf2ss
 from control.exception import slycot_check
+from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros
+from control.tests.conftest import slycotonly
+
+from .conftest import editsdefaults
+
 
 class TestLTI:
     @pytest.mark.parametrize("fun, args", [
@@ -368,3 +372,41 @@ def test_scalar_algebra(op, fcn):
 
     scaled = getattr(sys, op)(2)
     np.testing.assert_almost_equal(getattr(sys(1j), op)(2), scaled(1j))
+
+
+@pytest.mark.parametrize(
+    "fcn, args, kwargs, suppress, " +
+    "repr_expected, str_expected, latex_expected", [
+    (ct.ss, (-1e-12, 1, 2, 3), {}, False,
+     r"StateSpace\([\s]*array\(\[\[-1.e-12\]\]\).*",
+     None,                      # standard Numpy formatting
+     r"10\^\{-12\}"),
+    (ct.ss, (-1e-12, 1, 3, 3), {}, True,
+     r"StateSpace\([\s]*array\(\[\[-0\.\]\]\).*",
+     None,                      # standard Numpy formatting
+     r"-0"),
+    (ct.tf, ([1, 1e-12, 1], [1, 2, 1]), {}, False,
+     r"\[1\.e\+00, 1\.e-12, 1.e\+00\]",
+     r"s\^2 \+ 1e-12 s \+ 1",
+     r"1 \\times 10\^\{-12\}"),
+    (ct.tf, ([1, 1e-12, 1], [1, 2, 1]), {}, True,
+     r"\[1\., 0., 1.\]",
+     r"s\^2 \+ 1",
+     r"\{s\^2 \+ 1\}"),
+])
+@pytest.mark.usefixtures("editsdefaults")
+def test_printoptions(
+        fcn, args, kwargs, suppress,
+        repr_expected, str_expected, latex_expected):
+    sys = fcn(*args, **kwargs)
+
+    with np.printoptions(suppress=suppress):
+        # Test loadable representation
+        assert re.search(repr_expected, ct.iosys_repr(sys, 'eval')) is not None
+
+        # Test string representation
+        if str_expected is not None:
+            assert re.search(str_expected, str(sys)) is not None
+
+        # Test LaTeX/HTML representation
+        assert re.search(latex_expected, sys._repr_html_()) is not None
diff --git a/control/tests/namedio_test.py b/control/tests/namedio_test.py
index f702e704..2fc55f7d 100644
--- a/control/tests/namedio_test.py
+++ b/control/tests/namedio_test.py
@@ -34,8 +34,8 @@ def test_named_ss():
     assert sys.input_labels == ['u[0]', 'u[1]']
     assert sys.output_labels == ['y[0]', 'y[1]']
     assert sys.state_labels == ['x[0]', 'x[1]']
-    assert ct.InputOutputSystem.__repr__(sys) == \
-        "<StateSpace:sys[0]:['u[0]', 'u[1]']->['y[0]', 'y[1]']>"
+    assert ct.iosys_repr(sys, format='info') == \
+        "<StateSpace sys[0]: ['u[0]', 'u[1]'] -> ['y[0]', 'y[1]']>"
 
     # Pass the names as arguments
     sys = ct.ss(
@@ -46,8 +46,8 @@ def test_named_ss():
     assert sys.input_labels == ['u1', 'u2']
     assert sys.output_labels == ['y1', 'y2']
     assert sys.state_labels == ['x1', 'x2']
-    assert ct.InputOutputSystem.__repr__(sys) == \
-        "<StateSpace:system:['u1', 'u2']->['y1', 'y2']>"
+    assert ct.iosys_repr(sys, format='info') == \
+        "<StateSpace system: ['u1', 'u2'] -> ['y1', 'y2']>"
 
     # Do the same with rss
     sys = ct.rss(['x1', 'x2', 'x3'], ['y1', 'y2'], 'u1', name='random')
@@ -56,8 +56,8 @@ def test_named_ss():
     assert sys.input_labels == ['u1']
     assert sys.output_labels == ['y1', 'y2']
     assert sys.state_labels == ['x1', 'x2', 'x3']
-    assert ct.InputOutputSystem.__repr__(sys) == \
-        "<StateSpace:random:['u1']->['y1', 'y2']>"
+    assert ct.iosys_repr(sys, format='info') == \
+        "<StateSpace random: ['u1'] -> ['y1', 'y2']>"
 
 
 # List of classes that are expected
diff --git a/control/tests/nlsys_test.py b/control/tests/nlsys_test.py
index 926ca436..4b1a235c 100644
--- a/control/tests/nlsys_test.py
+++ b/control/tests/nlsys_test.py
@@ -19,7 +19,7 @@ import control as ct
 # Basic test of nlsys()
 def test_nlsys_basic():
     def kincar_update(t, x, u, params):
-        l = params.get('l', 1)  # wheelbase
+        l = params['l']              # wheelbase
         return np.array([
             np.cos(x[2]) * u[0],     # x velocity
             np.sin(x[2]) * u[0],     # y velocity
@@ -33,10 +33,11 @@ def test_nlsys_basic():
         kincar_update, kincar_output,
         states=['x', 'y', 'theta'],
         inputs=2, input_prefix='U',
-        outputs=2)
+        outputs=2, params={'l': 1})
     assert kincar.input_labels == ['U[0]', 'U[1]']
     assert kincar.output_labels == ['y[0]', 'y[1]']
     assert kincar.state_labels == ['x', 'y', 'theta']
+    assert kincar.params == {'l': 1}
 
 
 # Test nonlinear initial, step, and forced response
@@ -199,3 +200,68 @@ def test_ss2io():
         with pytest.raises(ValueError, match=r"new .* doesn't match"):
             kwargs = {attr: getattr(sys, 'n' + attr) - 1}
             nlsys = ct.nlsys(sys, **kwargs)
+
+
+def test_ICsystem_str():
+    sys1 = ct.rss(2, 2, 3, name='sys1', strictly_proper=True)
+    sys2 = ct.rss(2, 3, 2, name='sys2', strictly_proper=True)
+
+    with pytest.warns(UserWarning, match="Unused") as record:
+        sys = ct.interconnect(
+            [sys1, sys2], inputs=['r1', 'r2'], outputs=['y1', 'y2'],
+            connections=[
+                ['sys1.u[0]', '-sys2.y[0]', 'sys2.y[1]'],
+                ['sys1.u[1]', 'sys2.y[0]', '-sys2.y[1]'],
+                ['sys2.u[0]', 'sys2.y[0]', (0, 0, -1)],
+                ['sys2.u[1]', (1, 1, -2), (0, 1, -2)],
+            ],
+            inplist=['sys1.u[0]', 'sys1.u[1]'],
+            outlist=['sys2.y[0]', 'sys2.y[1]'])
+    assert len(record) == 2
+    assert str(record[0].message).startswith("Unused input")
+    assert str(record[1].message).startswith("Unused output")
+
+    ref = \
+        r"<LinearICSystem>: sys\[[\d]+\]" + "\n" + \
+        r"Inputs \(2\): \['r1', 'r2'\]" + "\n" + \
+        r"Outputs \(2\): \['y1', 'y2'\]" + "\n" + \
+        r"States \(4\): \['sys1_x\[0\].*'sys2_x\[1\]'\]" + "\n" + \
+        "\n" + \
+        r"Subsystems \(2\):" + "\n" + \
+        r" \* <StateSpace sys1: \[.*\] -> \['y\[0\]', 'y\[1\]']>" + "\n" + \
+        r" \* <StateSpace sys2: \['u\[0\]', 'u\[1\]'] -> \[.*\]>" + "\n" + \
+        "\n" + \
+        r"Connections:" + "\n" + \
+        r" \* sys1.u\[0\] <- -sys2.y\[0\] \+ sys2.y\[1\] \+ r1" + "\n" + \
+        r" \* sys1.u\[1\] <- sys2.y\[0\] - sys2.y\[1\] \+ r2" + "\n" + \
+        r" \* sys1.u\[2\] <-" + "\n" + \
+        r" \* sys2.u\[0\] <- -sys1.y\[0\] \+ sys2.y\[0\]" + "\n" + \
+        r" \* sys2.u\[1\] <- -2.0 \* sys1.y\[1\] - 2.0 \* sys2.y\[1\]" + \
+        "\n\n" + \
+        r"Outputs:" + "\n" + \
+        r" \* y1 <- sys2.y\[0\]" + "\n" + \
+        r" \* y2 <- sys2.y\[1\]" + \
+        "\n\n" + \
+        r"A = \[\[.*\]\]" + "\n\n" + \
+        r"B = \[\[.*\]\]" + "\n\n" + \
+        r"C = \[\[.*\]\]" + "\n\n" + \
+        r"D = \[\[.*\]\]"
+
+    assert re.match(ref, str(sys), re.DOTALL)
+
+
+# Make sure nlsys str() works as expected
+@pytest.mark.parametrize("params, expected", [
+    ({}, r"States \(1\): \['x\[0\]'\]" + "\n\n"),
+    ({'a': 1}, r"States \(1\): \['x\[0\]'\]" + "\n" +
+     r"Parameters: \['a'\]" + "\n\n"),
+    ({'a': 1, 'b': 1}, r"States \(1\): \['x\[0\]'\]" + "\n" +
+     r"Parameters: \['a', 'b'\]" + "\n\n"),
+])
+def test_nlsys_params_str(params, expected):
+    sys = ct.nlsys(
+            lambda t, x, u, params: -x, inputs=1, outputs=1, states=1,
+            params=params)
+    out = str(sys)
+
+    assert re.search(expected, out) is not None
diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py
index 647db456..a8016864 100644
--- a/control/tests/statesp_test.py
+++ b/control/tests/statesp_test.py
@@ -735,19 +735,24 @@ class TestStateSpace:
 
     def test_repr(self, sys322):
         """Test string representation"""
-        ref322 = "\n".join(["StateSpace(array([[-3.,  4.,  2.],",
-                            "       [-1., -3.,  0.],",
-                            "       [ 2.,  5.,  3.]]), array([[ 1.,  4.],",
-                            "       [-3., -3.],",
-                            "       [-2.,  1.]]), array([[ 4.,  2., -3.],",
-                            "       [ 1.,  4.,  3.]]), array([[-2.,  4.],",
-                            "       [ 0.,  1.]]){dt})"])
-        assert repr(sys322) == ref322.format(dt='')
+        ref322 = """StateSpace(
+array([[-3.,  4.,  2.],
+       [-1., -3.,  0.],
+       [ 2.,  5.,  3.]]),
+array([[ 1.,  4.],
+       [-3., -3.],
+       [-2.,  1.]]),
+array([[ 4.,  2., -3.],
+       [ 1.,  4.,  3.]]),
+array([[-2.,  4.],
+       [ 0.,  1.]]),
+name='sys322'{dt}, states=3, outputs=2, inputs=2)"""
+        assert ct.iosys_repr(sys322, format='eval') == ref322.format(dt='')
         sysd = StateSpace(sys322.A, sys322.B,
                           sys322.C, sys322.D, 0.4)
-        assert repr(sysd), ref322.format(dt=" == 0.4")
+        assert ct.iosys_repr(sysd, format='eval'), ref322.format(dt=",\ndt=0.4")
         array = np.array  # noqa
-        sysd2 = eval(repr(sysd))
+        sysd2 = eval(ct.iosys_repr(sysd, format='eval'))
         np.testing.assert_allclose(sysd.A, sysd2.A)
         np.testing.assert_allclose(sysd.B, sysd2.B)
         np.testing.assert_allclose(sysd.C, sysd2.C)
@@ -756,31 +761,31 @@ class TestStateSpace:
     def test_str(self, sys322):
         """Test that printing the system works"""
         tsys = sys322
-        tref = ("<StateSpace>: sys322\n"
-                "Inputs (2): ['u[0]', 'u[1]']\n"
-                "Outputs (2): ['y[0]', 'y[1]']\n"
-                "States (3): ['x[0]', 'x[1]', 'x[2]']\n"
-                "\n"
-                "A = [[-3.  4.  2.]\n"
-                "     [-1. -3.  0.]\n"
-                "     [ 2.  5.  3.]]\n"
-                "\n"
-                "B = [[ 1.  4.]\n"
-                "     [-3. -3.]\n"
-                "     [-2.  1.]]\n"
-                "\n"
-                "C = [[ 4.  2. -3.]\n"
-                "     [ 1.  4.  3.]]\n"
-                "\n"
-                "D = [[-2.  4.]\n"
-                "     [ 0.  1.]]\n")
-        assert str(tsys) == tref
+        tref = """<StateSpace>: sys322
+Inputs (2): ['u[0]', 'u[1]']
+Outputs (2): ['y[0]', 'y[1]']
+States (3): ['x[0]', 'x[1]', 'x[2]']{dt}
+
+A = [[-3.  4.  2.]
+     [-1. -3.  0.]
+     [ 2.  5.  3.]]
+
+B = [[ 1.  4.]
+     [-3. -3.]
+     [-2.  1.]]
+
+C = [[ 4.  2. -3.]
+     [ 1.  4.  3.]]
+
+D = [[-2.  4.]
+     [ 0.  1.]]"""
+        assert str(tsys) == tref.format(dt='')
         tsysdtunspec = StateSpace(
             tsys.A, tsys.B, tsys.C, tsys.D, True, name=tsys.name)
-        assert str(tsysdtunspec) == tref + "\ndt = True\n"
+        assert str(tsysdtunspec) == tref.format(dt="\ndt = True")
         sysdt1 = StateSpace(
             tsys.A, tsys.B, tsys.C, tsys.D, 1., name=tsys.name)
-        assert str(sysdt1) == tref + "\ndt = {}\n".format(1.)
+        assert str(sysdt1) == tref.format(dt="\ndt = 1.0")
 
     def test_pole_static(self):
         """Regression: poles() of static gain is empty array."""
@@ -1049,7 +1054,7 @@ class TestStateSpaceConfig:
                 "{} is {} but expected {}".format(k, defaults[k], v)
 
 
-# test data for test_latex_repr below
+# test data for test_html_repr below
 LTX_G1 = ([[np.pi, 1e100], [-1.23456789, 5e-23]],
           [[0], [1]],
           [[987654321, 0.001234]],
@@ -1061,23 +1066,23 @@ LTX_G2 = ([],
           [[1.2345, -2e-200], [-1, 0]])
 
 LTX_G1_REF = {
-    'p3_p' : '$$\n\\left(\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}14&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}88&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}00123&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n$$',
+    'p3_p': "&lt;StateSpace sys: ['u[0]'] -&gt; ['y[0]']{dt}&gt;\n$$\n\\left[\\begin{{array}}{{rllrll|rll}}\n3.&\\hspace{{-1em}}14&\\hspace{{-1em}}\\phantom{{\\cdot}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{100}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n-1.&\\hspace{{-1em}}23&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-23}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\hline\n9.&\\hspace{{-1em}}88&\\hspace{{-1em}}\\cdot10^{{8}}&0.&\\hspace{{-1em}}00123&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n$$",
 
-    'p5_p' : '$$\n\\left(\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}1416&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}2346&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}8765&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}001234&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n$$',
+    'p5_p': "&lt;StateSpace sys: ['u[0]'] -&gt; ['y[0]']{dt}&gt;\n$$\n\\left[\\begin{{array}}{{rllrll|rll}}\n3.&\\hspace{{-1em}}1416&\\hspace{{-1em}}\\phantom{{\\cdot}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{100}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n-1.&\\hspace{{-1em}}2346&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-23}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\hline\n9.&\\hspace{{-1em}}8765&\\hspace{{-1em}}\\cdot10^{{8}}&0.&\\hspace{{-1em}}001234&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n$$",
 
-    'p3_s' : '$$\n\\begin{array}{ll}\nA = \\left(\\begin{array}{rllrll}\n3.&\\hspace{-1em}14&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}\\\\\n-1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}\\\\\n\\end{array}\\right)\n&\nB = \\left(\\begin{array}{rll}\n0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\\\\nC = \\left(\\begin{array}{rllrll}\n9.&\\hspace{-1em}88&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}00123&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n&\nD = \\left(\\begin{array}{rll}\n5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n$$',
+    'p3_s': "&lt;StateSpace sys: ['u[0]'] -&gt; ['y[0]']{dt}&gt;\n$$\n\\begin{{array}}{{ll}}\nA = \\left[\\begin{{array}}{{rllrll}}\n3.&\\hspace{{-1em}}14&\\hspace{{-1em}}\\phantom{{\\cdot}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{100}}\\\\\n-1.&\\hspace{{-1em}}23&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-23}}\\\\\n\\end{{array}}\\right]\n&\nB = \\left[\\begin{{array}}{{rll}}\n0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\\\\nC = \\left[\\begin{{array}}{{rllrll}}\n9.&\\hspace{{-1em}}88&\\hspace{{-1em}}\\cdot10^{{8}}&0.&\\hspace{{-1em}}00123&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n&\nD = \\left[\\begin{{array}}{{rll}}\n5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\end{{array}}\n$$",
 
-    'p5_s' : '$$\n\\begin{array}{ll}\nA = \\left(\\begin{array}{rllrll}\n3.&\\hspace{-1em}1416&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}\\\\\n-1.&\\hspace{-1em}2346&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}\\\\\n\\end{array}\\right)\n&\nB = \\left(\\begin{array}{rll}\n0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\\\\nC = \\left(\\begin{array}{rllrll}\n9.&\\hspace{-1em}8765&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}001234&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n&\nD = \\left(\\begin{array}{rll}\n5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n$$',
+    'p5_s': "&lt;StateSpace sys: ['u[0]'] -&gt; ['y[0]']{dt}&gt;\n$$\n\\begin{{array}}{{ll}}\nA = \\left[\\begin{{array}}{{rllrll}}\n3.&\\hspace{{-1em}}1416&\\hspace{{-1em}}\\phantom{{\\cdot}}&1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{100}}\\\\\n-1.&\\hspace{{-1em}}2346&\\hspace{{-1em}}\\phantom{{\\cdot}}&5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-23}}\\\\\n\\end{{array}}\\right]\n&\nB = \\left[\\begin{{array}}{{rll}}\n0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\\\\nC = \\left[\\begin{{array}}{{rllrll}}\n9.&\\hspace{{-1em}}8765&\\hspace{{-1em}}\\cdot10^{{8}}&0.&\\hspace{{-1em}}001234&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n&\nD = \\left[\\begin{{array}}{{rll}}\n5\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\end{{array}}\n$$",
 }
 
 LTX_G2_REF = {
-    'p3_p' : '$$\n\\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n$$',
+    'p3_p': "&lt;StateSpace sys: ['u[0]', 'u[1]'] -&gt; ['y[0]', 'y[1]']{dt}&gt;\n$$\n\\left[\\begin{{array}}{{rllrll}}\n1.&\\hspace{{-1em}}23&\\hspace{{-1em}}\\phantom{{\\cdot}}&-2\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-200}}\\\\\n-1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n$$",
 
-    'p5_p' : '$$\n\\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}2345&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n$$',
+    'p5_p': "&lt;StateSpace sys: ['u[0]', 'u[1]'] -&gt; ['y[0]', 'y[1]']{dt}&gt;\n$$\n\\left[\\begin{{array}}{{rllrll}}\n1.&\\hspace{{-1em}}2345&\\hspace{{-1em}}\\phantom{{\\cdot}}&-2\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-200}}\\\\\n-1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n$$",
 
-    'p3_s' : '$$\n\\begin{array}{ll}\nD = \\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n$$',
+    'p3_s': "&lt;StateSpace sys: ['u[0]', 'u[1]'] -&gt; ['y[0]', 'y[1]']{dt}&gt;\n$$\n\\begin{{array}}{{ll}}\nD = \\left[\\begin{{array}}{{rllrll}}\n1.&\\hspace{{-1em}}23&\\hspace{{-1em}}\\phantom{{\\cdot}}&-2\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-200}}\\\\\n-1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\end{{array}}\n$$",
 
-    'p5_s' : '$$\n\\begin{array}{ll}\nD = \\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}2345&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n$$',
+    'p5_s': "&lt;StateSpace sys: ['u[0]', 'u[1]'] -&gt; ['y[0]', 'y[1]']{dt}&gt;\n$$\n\\begin{{array}}{{ll}}\nD = \\left[\\begin{{array}}{{rllrll}}\n1.&\\hspace{{-1em}}2345&\\hspace{{-1em}}\\phantom{{\\cdot}}&-2\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\cdot10^{{-200}}\\\\\n-1\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}&0\\phantom{{.}}&\\hspace{{-1em}}&\\hspace{{-1em}}\\phantom{{\\cdot}}\\\\\n\\end{{array}}\\right]\n\\end{{array}}\n$$",
 }
 
 refkey_n = {None: 'p3', '.3g': 'p3', '.5g': 'p5'}
@@ -1088,19 +1093,19 @@ refkey_r = {None: 'p', 'partitioned': 'p', 'separate': 's'}
                           (LTX_G2, LTX_G2_REF)])
 @pytest.mark.parametrize("dt, dtref",
                          [(0, ""),
-                          (None, ""),
-                          (True, r"~,~dt=~\mathrm{{True}}"),
-                          (0.1, r"~,~dt={dt:{fmt}}")])
+                          (None, ", dt=None"),
+                          (True, ", dt=True"),
+                          (0.1, ", dt={dt:{fmt}}")])
 @pytest.mark.parametrize("repr_type", [None, "partitioned", "separate"])
 @pytest.mark.parametrize("num_format", [None, ".3g", ".5g"])
-def test_latex_repr(gmats, ref, dt, dtref, repr_type, num_format, editsdefaults):
-    """Test `._latex_repr_` with different config values
+def test_html_repr(gmats, ref, dt, dtref, repr_type, num_format, editsdefaults):
+    """Test `._html_repr_` with different config values
 
     This is a 'gold image' test, so if you change behaviour,
     you'll need to regenerate the reference results.
     Try something like:
         control.reset_defaults()
-        print(f'p3_p : {g1._repr_latex_()!r}')
+        print(f'p3_p : {g1._repr_html_()!r}')
     """
     from control import set_defaults
     if num_format is not None:
@@ -1109,11 +1114,11 @@ def test_latex_repr(gmats, ref, dt, dtref, repr_type, num_format, editsdefaults)
     if repr_type is not None:
         set_defaults('statesp', latex_repr_type=repr_type)
 
-    g = StateSpace(*(gmats+(dt,)))
+    g = StateSpace(*(gmats + (dt,)), name='sys')
     refkey = "{}_{}".format(refkey_n[num_format], refkey_r[repr_type])
-    dt_latex = dtref.format(dt=dt, fmt=defaults['statesp.latex_num_format'])
-    ref_latex = ref[refkey][:-3] + dt_latex + ref[refkey][-3:]
-    assert g._repr_latex_() == ref_latex
+    dt_html = dtref.format(dt=dt, fmt=defaults['statesp.latex_num_format'])
+    ref_html = ref[refkey].format(dt=dt_html)
+    assert g._repr_html_() == ref_html
 
 
 @pytest.mark.parametrize(
@@ -1136,8 +1141,8 @@ def test_xferfcn_ndarray_precedence(op, tf, arr):
     assert isinstance(result, ct.StateSpace)
 
 
-def test_latex_repr_testsize(editsdefaults):
-    # _repr_latex_ returns None when size > maxsize
+def test_html_repr_testsize(editsdefaults):
+    # _repr_html_ returns None when size > maxsize
     from control import set_defaults
 
     maxsize = defaults['statesp.latex_maxsize']
@@ -1149,16 +1154,16 @@ def test_latex_repr_testsize(editsdefaults):
     assert ninputs > 0
 
     g = rss(nstates, ninputs, noutputs)
-    assert isinstance(g._repr_latex_(), str)
+    assert isinstance(g._repr_html_(), str)
 
     set_defaults('statesp', latex_maxsize=maxsize - 1)
-    assert g._repr_latex_() is None
+    assert g._repr_html_() is None
 
     set_defaults('statesp', latex_maxsize=-1)
-    assert g._repr_latex_() is None
+    assert g._repr_html_() is None
 
     gstatic = ss([], [], [], 1)
-    assert gstatic._repr_latex_() is None
+    assert gstatic._repr_html_() is None
 
 
 class TestLinfnorm:
@@ -1308,4 +1313,18 @@ def test_convenience_aliases():
     assert isinstance(sys.forced_response([0, 1], [1, 1]), (ct.TimeResponseData, ct.TimeResponseList))
     assert isinstance(sys.impulse_response(), (ct.TimeResponseData, ct.TimeResponseList))
     assert isinstance(sys.step_response(), (ct.TimeResponseData, ct.TimeResponseList))
-    assert isinstance(sys.initial_response(X0=1), (ct.TimeResponseData, ct.TimeResponseList))
\ No newline at end of file
+    assert isinstance(sys.initial_response(X0=1), (ct.TimeResponseData, ct.TimeResponseList))
+
+# Test LinearICSystem __call__
+def test_linearic_call():
+    import cmath
+
+    sys1 = ct.rss(2, 1, 1, strictly_proper=True, name='sys1')
+    sys2 = ct.rss(2, 1, 1, strictly_proper=True, name='sys2')
+
+    sys_ic = ct.interconnect(
+        [sys1, sys2], connections=['sys1.u', 'sys2.y'],
+        inplist='sys2.u', outlist='sys1.y')
+
+    for s in [0, 1, 1j]:
+        assert cmath.isclose(sys_ic(s), (sys1 * sys2)(s))
diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py
index 3f87ef1d..c7f92379 100644
--- a/control/tests/xferfcn_test.py
+++ b/control/tests/xferfcn_test.py
@@ -877,18 +877,18 @@ class TestXferFcn:
         """Print SISO"""
         sys = ss2tf(rss(4, 1, 1))
         assert isinstance(str(sys), str)
-        assert isinstance(sys._repr_latex_(), str)
+        assert isinstance(sys._repr_html_(), str)
 
         # SISO, discrete time
         sys = sample_system(sys, 1)
         assert isinstance(str(sys), str)
-        assert isinstance(sys._repr_latex_(), str)
+        assert isinstance(sys._repr_html_(), str)
 
     @pytest.mark.parametrize(
         "args, output",
-        [(([0], [1]), "\n0\n-\n1\n"),
-         (([1.0001], [-1.1111]), "\n  1\n------\n-1.111\n"),
-         (([0, 1], [0, 1.]), "\n1\n-\n1\n"),
+        [(([0], [1]), "  0\n  -\n  1"),
+         (([1.0001], [-1.1111]), "    1\n  ------\n  -1.111"),
+         (([0, 1], [0, 1.]), "  1\n  -\n  1"),
          ])
     def test_printing_polynomial_const(self, args, output):
         """Test _tf_polynomial_to_string for constant systems"""
@@ -897,82 +897,77 @@ class TestXferFcn:
     @pytest.mark.parametrize(
         "args, outputfmt",
         [(([1, 0], [2, 1]),
-          "\n   {var}\n-------\n2 {var} + 1\n{dtstring}"),
+          "     {var}\n  -------\n  2 {var} + 1"),
          (([2, 0, -1], [1, 0, 0, 1.2]),
-          "\n2 {var}^2 - 1\n---------\n{var}^3 + 1.2\n{dtstring}")])
+          "  2 {var}^2 - 1\n  ---------\n  {var}^3 + 1.2")])
     @pytest.mark.parametrize("var, dt, dtstring",
                              [("s", None, ''),
                               ("z", True, ''),
-                              ("z", 1, '\ndt = 1\n')])
+                              ("z", 1, 'dt = 1')])
     def test_printing_polynomial(self, args, outputfmt, var, dt, dtstring):
         """Test _tf_polynomial_to_string for all other code branches"""
-        assert str(TransferFunction(*(args + (dt,)))).partition('\n\n')[2] == \
-            outputfmt.format(var=var, dtstring=dtstring)
+        polystr = str(TransferFunction(*(args + (dt,)))).partition('\n\n')
+        if dtstring != '':
+            # Make sure the last line of the header has proper dt
+            assert polystr[0].split('\n')[3] == dtstring
+        else:
+            # Make sure there are only three header lines (sys, in, out)
+            assert len(polystr[0].split('\n')) == 4
+        assert polystr[2] == outputfmt.format(var=var)
 
     @slycotonly
     def test_printing_mimo(self):
         """Print MIMO, continuous time"""
         sys = ss2tf(rss(4, 2, 3))
         assert isinstance(str(sys), str)
-        assert isinstance(sys._repr_latex_(), str)
+        assert isinstance(sys._repr_html_(), str)
 
     @pytest.mark.parametrize(
         "zeros, poles, gain, output",
         [([0], [-1], 1,
-          '\n'
-          '  s\n'
-          '-----\n'
-          's + 1\n'),
+          '    s\n'
+          '  -----\n'
+          '  s + 1'),
          ([-1], [-1], 1,
-          '\n'
-          's + 1\n'
-          '-----\n'
-          's + 1\n'),
+          '  s + 1\n'
+          '  -----\n'
+          '  s + 1'),
          ([-1], [1], 1,
-          '\n'
-          's + 1\n'
-          '-----\n'
-          's - 1\n'),
+          '  s + 1\n'
+          '  -----\n'
+          '  s - 1'),
          ([1], [-1], 1,
-          '\n'
-          's - 1\n'
-          '-----\n'
-          's + 1\n'),
+          '  s - 1\n'
+          '  -----\n'
+          '  s + 1'),
          ([-1], [-1], 2,
-          '\n'
-          '2 (s + 1)\n'
-          '---------\n'
-          '  s + 1\n'),
+          '  2 (s + 1)\n'
+          '  ---------\n'
+          '    s + 1'),
          ([-1], [-1], 0,
-          '\n'
-          '0\n'
-          '-\n'
-          '1\n'),
+          '  0\n'
+          '  -\n'
+          '  1'),
          ([-1], [1j, -1j], 1,
-          '\n'
-          '      s + 1\n'
-          '-----------------\n'
-          '(s - 1j) (s + 1j)\n'),
+          '        s + 1\n'
+          '  -----------------\n'
+          '  (s - 1j) (s + 1j)'),
          ([4j, -4j], [2j, -2j], 2,
-          '\n'
-          '2 (s - 4j) (s + 4j)\n'
-          '-------------------\n'
-          ' (s - 2j) (s + 2j)\n'),
+          '  2 (s - 4j) (s + 4j)\n'
+          '  -------------------\n'
+          '   (s - 2j) (s + 2j)'),
          ([1j, -1j], [-1, -4], 2,
-          '\n'
-          '2 (s - 1j) (s + 1j)\n'
-          '-------------------\n'
-          '  (s + 1) (s + 4)\n'),
+          '  2 (s - 1j) (s + 1j)\n'
+          '  -------------------\n'
+          '    (s + 1) (s + 4)'),
          ([1], [-1 + 1j, -1 - 1j], 1,
-          '\n'
-          '          s - 1\n'
-          '-------------------------\n'
-          '(s + (1-1j)) (s + (1+1j))\n'),
+          '            s - 1\n'
+          '  -------------------------\n'
+          '  (s + (1-1j)) (s + (1+1j))'),
          ([1], [1 + 1j, 1 - 1j], 1,
-          '\n'
-          '          s - 1\n'
-          '-------------------------\n'
-          '(s - (1+1j)) (s - (1-1j))\n'),
+          '            s - 1\n'
+          '  -------------------------\n'
+          '  (s - (1+1j)) (s - (1-1j))'),
          ])
     def test_printing_zpk(self, zeros, poles, gain, output):
         """Test _tf_polynomial_to_string for constant systems"""
@@ -983,20 +978,17 @@ class TestXferFcn:
     @pytest.mark.parametrize(
         "zeros, poles, gain, format, output",
         [([1], [1 + 1j, 1 - 1j], 1, ".2f",
-          '\n'
-          '                1.00\n'
-          '-------------------------------------\n'
-          '(s + (1.00-1.41j)) (s + (1.00+1.41j))\n'),
+          '                  1.00\n'
+          '  -------------------------------------\n'
+          '  (s + (1.00-1.41j)) (s + (1.00+1.41j))'),
          ([1], [1 + 1j, 1 - 1j], 1, ".3f",
-          '\n'
-           '                  1.000\n'
-           '-----------------------------------------\n'
-           '(s + (1.000-1.414j)) (s + (1.000+1.414j))\n'),
+           '                    1.000\n'
+           '  -----------------------------------------\n'
+           '  (s + (1.000-1.414j)) (s + (1.000+1.414j))'),
          ([1], [1 + 1j, 1 - 1j], 1, ".6g",
-          '\n'
-          '                  1\n'
-          '-------------------------------------\n'
-          '(s + (1-1.41421j)) (s + (1+1.41421j))\n')
+          '                    1\n'
+          '  -------------------------------------\n'
+          '  (s + (1-1.41421j)) (s + (1+1.41421j))')
          ])
     def test_printing_zpk_format(self, zeros, poles, gain, format, output):
         """Test _tf_polynomial_to_string for constant systems"""
@@ -1012,26 +1004,30 @@ class TestXferFcn:
         "num, den, output",
         [([[[11], [21]], [[12], [22]]],
          [[[1, -3, 2], [1, 1, -6]], [[1, 0, 1], [1, -1, -20]]],
-         ('\n'
-          'Input 1 to output 1:\n'
-          '      11\n'
-          '---------------\n'
-          '(s - 2) (s - 1)\n'
-          '\n'
-          'Input 1 to output 2:\n'
-          '       12\n'
-          '-----------------\n'
-          '(s - 1j) (s + 1j)\n'
-          '\n'
-          'Input 2 to output 1:\n'
-          '      21\n'
-          '---------------\n'
-          '(s - 2) (s + 3)\n'
-          '\n'
-          'Input 2 to output 2:\n'
-          '      22\n'
-          '---------------\n'
-          '(s - 5) (s + 4)\n'))])
+         ("""Input 1 to output 1:
+
+        11
+  ---------------
+  (s - 2) (s - 1)
+
+Input 1 to output 2:
+
+         12
+  -----------------
+  (s - 1j) (s + 1j)
+
+Input 2 to output 1:
+
+        21
+  ---------------
+  (s - 2) (s + 3)
+
+Input 2 to output 2:
+
+        22
+  ---------------
+  (s - 5) (s + 4)"""))],
+    )
     def test_printing_zpk_mimo(self, num, den, output):
         """Test _tf_polynomial_to_string for constant systems"""
         G = tf(num, den, display_format='zpk')
@@ -1061,59 +1057,73 @@ class TestXferFcn:
         with pytest.raises(NotImplementedError):
             TransferFunction.feedback(sys2, sys1)
 
-    def test_latex_repr(self):
+    def test_html_repr(self):
         """Test latex printout for TransferFunction"""
         Hc = TransferFunction([1e-5, 2e5, 3e-4],
-                              [1.2e34, 2.3e-4, 2.3e-45])
+                              [1.2e34, 2.3e-4, 2.3e-45], name='sys')
         Hd = TransferFunction([1e-5, 2e5, 3e-4],
                               [1.2e34, 2.3e-4, 2.3e-45],
-                              .1)
+                              .1, name='sys')
         # TODO: make the multiplication sign configurable
         expmul = r'\times'
-        for var, H, suffix in zip(['s', 'z'],
+        for var, H, dtstr in zip(['s', 'z'],
                                   [Hc, Hd],
-                                  ['', r'\quad dt = 0.1']):
-            ref = (r'$$\frac{'
+                                  ['', ', dt=0.1']):
+            ref = (r"&lt;TransferFunction sys: ['u[0]'] -&gt; ['y[0]']" +
+                   dtstr + r"&gt;"
+                   "\n"
+                   r'$$\dfrac{'
                    r'1 ' + expmul + ' 10^{-5} ' + var + '^2 '
                    r'+ 2 ' + expmul + ' 10^{5} ' + var + ' + 0.0003'
                    r'}{'
                    r'1.2 ' + expmul + ' 10^{34} ' + var + '^2 '
                    r'+ 0.00023 ' + var + ' '
                    r'+ 2.3 ' + expmul + ' 10^{-45}'
-                   r'}' + suffix + '$$')
-            assert H._repr_latex_() == ref
+                   r'}' + '$$')
+            assert H._repr_html_() == ref
 
     @pytest.mark.parametrize(
         "Hargs, ref",
         [(([-1., 4.], [1., 3., 5.]),
-          "TransferFunction(array([-1.,  4.]), array([1., 3., 5.]))"),
+          "TransferFunction(\n"
+          "array([-1.,  4.]),\n"
+          "array([1., 3., 5.]),\n"
+          "outputs=1, inputs=1)"),
          (([2., 3., 0.], [1., -3., 4., 0], 2.0),
-          "TransferFunction(array([2., 3., 0.]),"
-          " array([ 1., -3.,  4.,  0.]), 2.0)"),
-
+          "TransferFunction(\n"
+          "array([2., 3., 0.]),\n"
+          "array([ 1., -3.,  4.,  0.]),\n"
+          "dt=2.0,\n"
+          "outputs=1, inputs=1)"),
          (([[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
            [[[6, 7], [4, 5]], [[2, 3], [0, 1]]]),
-          "TransferFunction([[array([1]), array([2, 3])],"
-          " [array([4, 5]), array([6, 7])]],"
-          " [[array([6, 7]), array([4, 5])],"
-          " [array([2, 3]), array([1])]])"),
+          "TransferFunction(\n"
+          "[[array([1]), array([2, 3])],\n"
+          " [array([4, 5]), array([6, 7])]],\n"
+          "[[array([6, 7]), array([4, 5])],\n"
+          " [array([2, 3]), array([1])]],\n"
+          "outputs=2, inputs=2)"),
          (([[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
            [[[6, 7], [4, 5]], [[2, 3], [0, 1]]],
            0.5),
-          "TransferFunction([[array([1]), array([2, 3])],"
-          " [array([4, 5]), array([6, 7])]],"
-          " [[array([6, 7]), array([4, 5])],"
-          " [array([2, 3]), array([1])]], 0.5)")
+          "TransferFunction(\n"
+          "[[array([1]), array([2, 3])],\n"
+          " [array([4, 5]), array([6, 7])]],\n"
+          "[[array([6, 7]), array([4, 5])],\n"
+          " [array([2, 3]), array([1])]],\n"
+          "dt=0.5,\n"
+          "outputs=2, inputs=2)"),
          ])
-    def test_repr(self, Hargs, ref):
+    def test_loadable_repr(self, Hargs, ref):
         """Test __repr__ printout."""
         H = TransferFunction(*Hargs)
 
-        assert repr(H) == ref
+        rep = ct.iosys_repr(H, format='eval')
+        assert rep == ref
 
         # and reading back
         array = np.array  # noqa
-        H2 = eval(H.__repr__())
+        H2 = eval(rep)
         for p in range(len(H.num)):
             for m in range(len(H.num[0])):
                 np.testing.assert_array_almost_equal(