Skip to content
This repository

Use LaTeX to print various built-in types with the SymPy printing extension #1399

Merged
merged 2 commits into from about 2 years ago

3 participants

Aaron Meurer Min RK Fernando Perez
Aaron Meurer

SymPy's latex() function supports printing lists, tuples, and dicts using
latex notation (it uses bmatrix, pmatrix, and Bmatrix, respectively). This
provides a more unified experience with SymPy functions that return these
types (such as solve()).

Also print ints, longs, and floats using LaTeX, to get a more unified printing
experience (so that, e.g., x/x will print the same as just 1). The string
form can always be obtained by using print, or 2d unicode printing using
pprint().

SymPy's latex() function doesn't treat set() or frosenset() correctly
presently (see http://code.google.com/p/sympy/issues/detail?id=3062), so for
the present, we leave those alone.

Currently, the printing of lists, tuples, and dicts does not work correctly in
the qtconsole. Any help fixing this would be appreciated.

Aaron Meurer Use LaTeX to print various built-in types with the SymPy printing ext…
…ension

SymPy's latex() function supports printing lists, tuples, and dicts using
latex notation (it uses bmatrix, pmatrix, and Bmatrix, respectively).  This
provides a more unified experience with SymPy functions that return these
types (such as solve()).

Also print ints, longs, and floats using LaTeX, to get a more unified printing
experience (so that, e.g., `x/x` will print the same as just `1`).  The string
form can always be obtained by using `print`, or 2d unicode printing using
pprint().

SymPy's latex() function doesn't treat set() or frosenset() correctly
presently (see http://code.google.com/p/sympy/issues/detail?id=3062), so for
the present, we leave those alone.

Currently, the printing of lists, tuples, and dicts does not work correctly in
the qtconsole.
e21221c
Min RK
Owner

This looks pretty good.

One thing that does get weird in the notebook is if you have a list containing a type (or anything whose repr includes <...>), because markdown is still treating those as HTML tags.

For instance, [type] transforms to \begin{bmatrix}<type 'type'>\end{bmatrix} which displays as $$\begin{bmatrix}\end{bmatrix}$$ (not rendered), presumably because it contains an unterminated open-tag, confusing the MathJax parser.

I'm less confident in the png for qtconsole choice, just because they aren't particularly nice, they are slow, and matplotlib.mathtext doesn't provide full LaTeX.

The reason your container renderers are not drawing in the qtconsole is that the png render is failing, I think due to the use of \begin{bmatrix}, which mathtext doesn't seem to support. I believe mathtext is only meant for single expressions.

Exceptions raised in display formatters are ignored, and treated as "we can't format this object, move on to other mime-types". To see the actual result of the display format call, you can do:

# get the formatter object for PNG
png_formatter = get_ipython().display_formatter.formatters['image/png']
# get the callable for lists
f = png_formatter.type_printers[list]
# call it on a list
f([1,2])

which will raise:

ValueError: 
\begin{bmatrix}1, & 2\end{bmatrix}
^
Unknown symbol: \begin (at char 0), (line:1, col:1)
Aaron Meurer

So the mathjax formatter should replace all instances of < and > with \< and \>?

I didn't realize that the png formatter just falls back to the text formatter when it fails. Does it support \left ( and so on (I'm actually not entirely sure why we don't just use that instead of the matrix stuff)? I'll play with it.

I agree that mathtext doesn't look very nice (in general). I would be better if we could render mathjax in the qtconsole. Or, we could do something like http://research.scios.ch/inet/doku.php?id=ipy_tex, which uses latex itself to render the math (if it's installed).

Aaron Meurer

Escaping the < and > doesn't seem to work. I made it so that [type(Symbol('x')] gives $$\begin{bmatrix}\<class 'sympy.core.symbol.Symbol'\>\end{bmatrix}$$, but this just renders as $$\begin{bmatrix}\\end{bmatrix}$$ (plaintext).

Aaron Meurer

I'm not convinced that <...> is the problem, actually. And(x < y, y > x) renders just fine in master for example (you have to use the SymPy master too). That's rendered as $$x < y \wedge x > y$$.

Min RK
Owner

backslash is not used for escaping HTML, it would be &lt;, &gt; etc. Also, this escape should not happen at the code level, it should be in javascript in the frontend. The brackets are valid in LaTeX, but the mixed TeX/HTML environment of mathjax should make a pass at escaping apparent HTML in clearly non-HTML expressions.

As for the PNG - All available formatters are always called. So if the PNG formatter fails, it simply returns nothing, acting as if there was no PNG formatter registered for the given object. At that point, plaintext format is the only remaining one that the QtConsole can display. As for what it supports, I would check out the docs for matplotlib.mathtext, as that is the code that actually does the rendering. If you can render the TeX as a png yourself, then that's fine as well.

Min RK
Owner

I'm not convinced that <...> is the problem, actually...

It requires that it be identifiable as a tag, which is sensitive to space between open-bracket and tag symbol (hence <type... being mishandled, while x < y is not). This has nothing to do with sympy, and applies to latex rendering in MathJax in general. A simple example:

from IPython.core.display import *
m = Math("x < y")
display(m._repr_latex_())
display(m)
m = Math("x <y")
display(m._repr_latex_())
display(m)
Aaron Meurer

So should I consider it a separate problem from this pull request? Regardless, I don't know javascript well enough to do the parsing there, so someone else will have to fix it.

Min RK
Owner

Yes, I suppose I would consider it a separate issue. But it does create a real problem when using sympy to draw basic types that are not at all related to sympy, such as lists, etc. For instance, I wouldn't want sympy to draw any of my lists or dicts unless they actually contained sympy objects (less objectionable when they contain only simple scalars).

This changes what %load_ext sympyprinting means,

from: "Display sympy objects with latex"
to: "Draw everything with latex"

Both of which are reasonable, and have advantages and disadvantages.

Is it possible for the sympyprinting callable to skip containers that contain anything sympy doesn't handle (e.g. str, object, etc.)? All it would have to do is return None in those cases.

Aaron Meurer

Yeah, that should be straightforward. If there are mixed types, I guess it will have to draw the whole thing using the string printer (unless mathjax can be mixed with it nicely).

Min RK
Owner

Okay, let's go with that. I wouldn't bother trying to mix latex and reprs, just abort latex-printing if there's anything in the container not on the whitelist of latexable types.

Aaron Meurer

What's the best way to do that, given that we use string forms of the SymPy types instead of the types themselves?

Aaron Meurer

In other words, I want to do something like

def can_print_latex(o):
    """                                                                       
    Return True of every element of a container type o can be printed with    
    LaTeX.                                                                    
    """
    if type(o) in (list, tuple):
        return all(can_print_latex(i) for i in o)
    if type(o) is dict:
        return all(can_print_latex(i) and can_print_latex(o[i]) for i in o)
    if type(o) in (sympy.Basic, sympy.matrices.Matrix):
        return True
    return False

But that requires having sympy imported, which I gather from the rest of the file is not necessarily true (though I'm not entirely sure why).

Aaron Meurer

Actually, I want to use isinstance, not type. But the question is the same.

Min RK
Owner

Why is the sympy import a problem? You can just import sympy, either in the module, or in the function itself, if that's necessary for some reason:

Something like this should work:

def can_print_latex(o):
    """                                                                       
    Return True of every element of a container type o can be printed with    
    LaTeX.                                                                    
    """
    import sympy
    if isinstance(o, (list, tuple)):
        return all(can_print_latex(i) for i in o)
    elif isinstance(o, dict):
        return all((isinstance(i, basestring) or can_print_latex(i)) and can_print_latex(o[i]) for i in o)
    elif isinstance(o,(sympy.Basic, sympy.matrices.Matrix, int, long, float)):
        return True
    return False
Aaron Meurer

I don't know. I assumed it was because of the try block at the top of the file.

Min RK
Owner

Oh, I wouldn't worry about that. I think it might have to do with testing or some such that the file must be importable. It should be fine to rely on that import having succeeded in your functions

Fernando Perez
Owner

@asmeurer, just checking in... I finally have some time to start catching up with our accumulated PR backlog. This looks like a good PR, once the discussion above is taken into account in the changes.

Aaron Meurer

I'm actually backlogged as well, but I'm am trying to get through stuff. I'm going through my emails in order, so your comment ironically made it take longer for me to get to :)

Aaron Meurer Only print container types with LaTeX in the notebook if all the elem…
…ents can

be printed with LaTeX in the SymPy printing extension.

Otherwise, fallback to the string printer.
1186cac
Aaron Meurer

OK, I finally finished this. Please verify that I did everything correctly, as I may have forgotten something in the time I've left this.

Aaron Meurer

I don't have time to look into the qtconsole now, so I opened #1523 for it. I'd be interested in hearing your opinions on using MathJax or something like http://research.scios.ch/inet/doku.php?id=ipy_tex in the qtconsole.

Min RK
Owner
minrk commented April 14, 2012

This one looks good to me, now.

Fernando Perez
Owner

Looks good to me too, merging now. @asmeurer, thanks for the work! We'll take up the rest of the discussion in #1523.

Fernando Perez fperez merged commit ba882bf into from April 14, 2012
Fernando Perez fperez closed this April 14, 2012
Aaron Meurer asmeurer referenced this pull request from a commit in asmeurer/ipython May 28, 2012
Aaron Meurer Update SymPy profile: SymPy's latex() can now print set and frozenset
I've included a version check against the latest version of SymPy that
supports it (the current development version, 0.7.1-git), so that users of old
versions of SymPy where this doesn't work will still have a good user
experience.  This is a continuation of pull request #1399.
9fe341d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Feb 11, 2012
Aaron Meurer Use LaTeX to print various built-in types with the SymPy printing ext…
…ension

SymPy's latex() function supports printing lists, tuples, and dicts using
latex notation (it uses bmatrix, pmatrix, and Bmatrix, respectively).  This
provides a more unified experience with SymPy functions that return these
types (such as solve()).

Also print ints, longs, and floats using LaTeX, to get a more unified printing
experience (so that, e.g., `x/x` will print the same as just `1`).  The string
form can always be obtained by using `print`, or 2d unicode printing using
pprint().

SymPy's latex() function doesn't treat set() or frosenset() correctly
presently (see http://code.google.com/p/sympy/issues/detail?id=3062), so for
the present, we leave those alone.

Currently, the printing of lists, tuples, and dicts does not work correctly in
the qtconsole.
e21221c
Mar 24, 2012
Aaron Meurer Only print container types with LaTeX in the notebook if all the elem…
…ents can

be printed with LaTeX in the SymPy printing extension.

Otherwise, fallback to the string printer.
1186cac
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 34 additions and 9 deletions. Show diff stats Hide diff stats

  1. 43  IPython/extensions/sympyprinting.py
43  IPython/extensions/sympyprinting.py
@@ -30,7 +30,6 @@
30 30
 except ImportError:
31 31
     pass
32 32
 
33  
-
34 33
 #-----------------------------------------------------------------------------
35 34
 # Definitions of magic functions for use with IPython
36 35
 #-----------------------------------------------------------------------------
@@ -55,14 +54,32 @@ def print_png(o):
55 54
     png = latex_to_png(s)
56 55
     return png
57 56
 
  57
+def can_print_latex(o):
  58
+    """
  59
+    Return True if type o can be printed with LaTeX.
  60
+
  61
+    If o is a container type, this is True if and only if every element of o
  62
+    can be printed with LaTeX.
  63
+    """
  64
+    import sympy
  65
+    if isinstance(o, (list, tuple)):
  66
+        return all(can_print_latex(i) for i in o)
  67
+    elif isinstance(o, dict):
  68
+        return all((isinstance(i, basestring) or can_print_latex(i)) and can_print_latex(o[i]) for i in o)
  69
+    elif isinstance(o,(sympy.Basic, sympy.matrices.Matrix, int, long, float)):
  70
+        return True
  71
+    return False
58 72
 
59 73
 def print_latex(o):
60  
-    """A function to generate the latex representation of sympy expressions."""
61  
-    s = latex(o, mode='plain')
62  
-    s = s.replace('\\dag','\\dagger')
63  
-    s = s.strip('$')
64  
-    return '$$%s$$' % s
65  
-
  74
+    """A function to generate the latex representation of sympy
  75
+    expressions."""
  76
+    if can_print_latex(o):
  77
+        s = latex(o, mode='plain')
  78
+        s = s.replace('\\dag','\\dagger')
  79
+        s = s.strip('$')
  80
+        return '$$%s$$' % s
  81
+    # Fallback to the string printer
  82
+    return None
66 83
 
67 84
 _loaded = False
68 85
 
@@ -72,7 +89,9 @@ def load_ipython_extension(ip):
72 89
     if not _loaded:
73 90
         plaintext_formatter = ip.display_formatter.formatters['text/plain']
74 91
 
75  
-        for cls in (object, tuple, list, set, frozenset, dict, str):
  92
+        for cls in (object, set, frozenset, str):
  93
+            # set and frozen set are currently broken with SymPy's latex()
  94
+            # function. See http://code.google.com/p/sympy/issues/detail?id=3062.
76 95
             plaintext_formatter.for_type(cls, print_basic_unicode)
77 96
 
78 97
         plaintext_formatter.for_type_by_name(
@@ -87,6 +106,8 @@ def load_ipython_extension(ip):
87 106
         png_formatter.for_type_by_name(
88 107
             'sympy.core.basic', 'Basic', print_png
89 108
         )
  109
+        for cls in (list, tuple, dict, int, long, float):
  110
+            png_formatter.for_type(cls, print_png)
90 111
 
91 112
         latex_formatter = ip.display_formatter.formatters['text/latex']
92 113
         latex_formatter.for_type_by_name(
@@ -95,5 +116,9 @@ def load_ipython_extension(ip):
95 116
         latex_formatter.for_type_by_name(
96 117
             'sympy.matrices.matrices', 'Matrix', print_latex
97 118
         )
98  
-        _loaded = True
99 119
 
  120
+        for cls in (list, tuple):
  121
+            # Use LaTeX only if every element is printable by latex
  122
+            latex_formatter.for_type(cls, print_latex)
  123
+
  124
+        _loaded = True
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.