GeezerVison is showing github.com — Create latex output for Jupyter notebooks · Issue #278 · python-control/python-control
Create latex output for Jupyter notebooks #278DescriptionPR #276 adds latex output for transfer functions. It would be nice to add this for other LTI types as well (particularly StateSpace objects). Having the output match the text approximation in __repr__ seems like a good first cut at format to use. Activityrepagh commented on Jun 21, 2019 I got this representation in a Spyder console

I think we need to get the numbers printed in exponent form a bit nicer still. roryyorke commented on Aug 2, 2020 Here's a proof of concept for SS, presenting the system in "partitioned matrix" form.
The format is loosely based on Sympy's LaTeX output for matrices with floating point entries, which centre-aligns and uses a \cdot to separate the 10^x part.
Some points:
- MathJax doesn't support
@ in \array preamble [1], so one can't align columns on the decimal point. One could left-align, and use \llap{-} for the minus signs; then the leading digits are aligned in each column. MathJax limitations only apply for notebook output.
- If one uses a "tabular" output like this, perhaps
_repr_html_ would be better than _repr_latex_; I haven't investigated that. That might be better if one, e.g., labelled rows with state and output names, and columns with state and input names.
- this doesn't match the current
repr output (A = [[ ... ]] B = [[ ... ]], etc.)
- a complete display doesn't make sense for big (nx, ny, nu > 10?) systems; we could have something like numpy's or panda's display limits. I think something similar applies to TFs, i.e., we should omit intermediate polynomial terms for high-degree denominators and numerators. One trick would be still showing the partitions, so this could get pretty untidy-looking.
[1] mathjax/MathJax-docs#205
import numpy as np
from IPython.display import Latex
def _f2s(f):
s = f'{f:.3g}'
if 'e' in s:
n, e = s.split('e')
e = int(e)
s = f'{n}\\cdot10^{{{e}}}'
return s
def format_ss(A, B, C, D):
nx = A.shape[0]
nu = B.shape[1]
ny = C.shape[0]
lines = [
r'\[',
r'\left(',
r'\begin{array}{' + 'c' * nx + '|' + 'c' * nu + '}'
]
for Ai, Bi in zip(A, 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(C, D):
lines.append('&'.join([_f2s(Cij) for Cij in Ci]
+ [_f2s(Dij) for Dij in Di])
+ '\\\\')
lines.extend([
r'\end{array}'
r'\right)',
r'\]'])
return '\n'.join(lines)
nx = np.random.randint(low=1,high=5)
nu = np.random.randint(low=1,high=5)
ny = np.random.randint(low=1,high=5)
A = 10.0 ** np.random.randint(low=-20, high=20, size=(nx,nx)) * np.random.randn(nx,nx)
B = 10.0 ** np.random.randint(low=-20, high=20, size=(nx,nu)) * np.random.randn(nx,nu)
C = 10.0 ** np.random.randint(low=-20, high=20, size=(ny,nx)) * np.random.randn(ny,nx)
D = 10.0 ** np.random.randint(low=-20, high=20, size=(ny,nu)) * np.random.randn(ny,nu)
Latex(format_ss(A,B,C,D))

bnavigator commented on Aug 2, 2020
one can't align columns on the decimal point.
How about \hspace{-1em}? The MathJax columnspacing is 1em by default and I could not find any place, that overrides the default:
\begin{array} -> https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/base/BaseMappings.ts#L659
-> https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/base/BaseMethods.ts#L1433
-> https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/base/BaseMethods.ts#L1377
import numpy as np
from IPython.display import Latex
def _f2s(f):
s = '{:-.3g}'.format(f)
ne = s.split('e')
n = ne[0].split('.')
s = n[0]
s += r'.&\hspace{{-1em}}{:0<2s}'.format(n[1]) if n[1:] else r'\phantom{.}&'
if ne[1:]:
if not n[1:]:
s += r'\hspace{-1em}\phantom{00}'
s += r'\cdot10^{{{:d}}}'.format(int(ne[1]))
return s
def format_ss(A, B, C, D):
nx = A.shape[0]
nu = B.shape[1]
ny = C.shape[0]
lines = [
r'\[',
r'\left(',
r'\begin{array}{' + 'rl' * nx + '|' + 'rl' * nu + '}'
]
for Ai, Bi in zip(A, 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(C, D):
lines.append('&'.join([_f2s(Cij) for Cij in Ci]
+ [_f2s(Dij) for Dij in Di])
+ '\\\\')
lines.extend([
r'\end{array}'
r'\right)',
r'\]'])
return '\n'.join(lines)
#nx = np.random.randint(low=1,high=5)
#nu = np.random.randint(low=1,high=5)
#ny = np.random.randint(low=1,high=5)
nx = 3
nu = 2
ny = 2
A = 10.0 ** np.random.randint(low=-20, high=20, size=(nx,nx)) * np.random.randn(nx,nx)
B = 10.0 ** np.random.randint(low=-20, high=20, size=(nx,nu)) * np.random.randn(nx,nu)
C = 10.0 ** np.random.randint(low=-20, high=20, size=(ny,nx)) * np.random.randn(ny,nx)
D = 10.0 ** np.random.randint(low=-20, high=20, size=(ny,nu)) * np.random.randn(ny,nu)
B[1,1] = 3e4
B[2,1] = -9e4
C[0,1] = 123
lstr = format_ss(A,B,C,D)
#print(lstr)
Latex(lstr)
Jupyter Notebook:

Jupyter console:

One could argue how much space to leave after the decimal separator and if the phantom ".00" for integer mantissae when there is an exponent part should be visible. And whether the \phantom entries or the whole column will need no be removed for systems where the whole column for the fractional part is empty:
A2 = np.full((5,5),-99)
B2 = np.full((5,1),2)
C2 = np.full((1,5),3)
D2 = [[99]]
Latex(format_ss(A2, B2, C2, D2))

roryyorke commented on Aug 4, 2020
How about \hspace{-1em}?
Ha! Ingenious. This does tie us to MathJax, but we could add features for "real" LaTeX (for exporting notebooks to LaTeX) if anyone ever asks for it.
As for the extra spacing introduced by the extra column: it would be ideal to remove it, but that is a finesse.
Mathematica uses a similar output format for its state-space objects [1].
@murrayrm, do you think this is worth pursuing?
[1] https://reference.wolfram.com/language/ref/StateSpaceModel.html bnavigator commented on Aug 4, 2020 "real" LaTeX would have a set length \arraycolsep. Maybe that could be included into the \hspace overriding the 1em with some clever LaTeX hack? murrayrm commented on Aug 5, 2020 Nice to have something in place. I would probably got for something that is as "plain latex" (or MathJax) as possible. It won't look as pretty, but it can be updated later as more functionality is added to the formatting packages.
Another possibility would be to allow the user to pass a formatting string for the table style, item entries, etc with some sort of reasonable default value. roryyorke commented on Aug 8, 2020 WIP rendered https://nbviewer.jupyter.org/gist/roryyorke/6879e0b20735f533410dad883eb687e5 from code at roryyorke/python-control/tree/rory/ss-repr-latex
I've taken @bnavigator 's idea a little further by adding a third set of columns for the exponents; this makes easy to scan down the column for orders of magnitude, and it also Does The Right Thing when there are no exponents to display.
One special case is a gain-only system, which shows the partitions even though the A/B/C entries are empty. This makes it clear this is a "D-only" system, but it doesn't look that good. I think I'll change this.
It's probably not too diffiicult to make the formatting somewhat customizable, though we would need to be able to parse the resulting string to insert the column separators and so on. I'm not familiar with the python-control control config system (I think there is one?), will take a look. roryyorke commented on Aug 8, 2020 Oh, one thing I'm not sure about is how to write tests for this. I don't see a lot tests for this sort of thing in, e.g., sympy. bnavigator commented on Aug 8, 2020
It's probably not too diffiicult to make the formatting somewhat customizable, though we would need to be able to parse the resulting string to insert the column separators and so on. I'm not familiar with the python-control control config system (I think there is one?), will take a look.
It's called config.defaults and IMHO should be renamed anyway.
I would like to have the option to display the matrices (possibly still arranged in a table) without the partitioned matrix like this.
[ ... ... ] [ ... ]
A = [ ... ... ] B = [ ... ]
[ ... ... ] [ ... ]
C = [ ... ... ] D = [ ... ]
roryyorke commented on Aug 9, 2020 Branch and gist updated. User can select from 'partitioned' and 'separate' types, and specify format string.
Static-gain ("stateless") systems are special-cased.
How should configuration options be documented?
I think this should have some minimal testing, though I'll probably use a "gold image" approach, i.e., generate reference strings using the implementation.


sawyerbfuller commented on Feb 12, 2021 bnavigator commented on Feb 12, 2021 Yes. #450 did not close this. It added the functionality for StateSpace, but not for other system objects. (TransferFunction._repr_latex_ already exists, FRD and iosys don't have it yet`) danielbrown2 commented on Apr 1, 2021 Having Jupyter display the Mathjax is useful for small state spaces, but I've had some problems with larger ones causing Jupyter Notebook to hang. I then have to forcibly stop my browser and then reopening, hoping that I can clear all outputs before it starts rendering.
Using display(ss) to show the Mathjax version might be more appropriate, or maybe have some size limit where it falls back to just showing a string, or a flag to switch this feature off. I had this issue when I just want to check what a variable in a notebook is quickly or run some function that returns a statespace, then I end up printing a 100x100 StateSpace I'm working with and crashing everything. murrayrm commented on Apr 1, 2021 I like the idea of requiring display() to get mathjax. Then, as suggested, we could use the config module to set defaults for when display is automatically called. roryyorke commented on Apr 11, 2021
Having Jupyter display the Mathjax is useful for small state spaces, but I've had some problems with larger ones causing Jupyter Notebook to hang. I then have to forcibly stop my browser and then reopening, hoping that I can clear all outputs before it starts rendering.
A short-term workaround is to remove the related method after importing python-control:
import control as ct
del ct.statesp.StateSpace._repr_latex_
Using display(ss) to show the Mathjax version might be more appropriate,
My understanding is that display just a way to force output of a variable, so IPython cells
a = ss(1,2,3,4);
display(a)
and
are equivalent.
A switch back to "plain old repr" for bigger systems seems like a reasonable idea. We could add a config.defaults['statesp.latex_maxsize'] and if either (nstates + ninputs) or (nstate + noutputs) > latex_maxsize, have _repr_latex_ return None. One could then set latex_maxsize to -1 to disable the LaTeX display altogether. See https://ipython.readthedocs.io/en/stable/config/integrating.html
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|