user6764549
user6764549

Reputation:

Modify Jupyter Notebook's (ipython kernel) LaTeX rendering of Sympy objects

I recently started using Jupyter notebook for writing my homework assignments for a Fluid Dynamics class. I use an IPython kernel with Sympy for symbolic computations. Often, I need to print the result of my symbolic calculations as LaTeX output. As an example, see the following code

import sympy as s
s.init_printing()
from IPython.display import display
r,t = s.symbols('r,theta')
u_x = r*s.sin(t)
display(u_x)

This prints the LaTeX rendering of $r\sin\theta$ in the cell below the code. Current Output Is there someway I could add $u_x = $ in front of the output? In this example, the desired output would be

My purpose is that the output of my display(u_x) statement in Jupyter notebook should be like a regular equation in a LaTeX document. The reader should not have to read the code part to understand what quantity that I am displaying in the equation.

Upvotes: 2

Views: 2535

Answers (5)

Jonathan Gutow
Jonathan Gutow

Reputation: 305

I've been puttering with a somewhat different approach that generates this kind of output directly from assignment statements. I call this display math operation or dmo for short. You can make calls such as dmo(p=a*b/c**2) and get the typeset output p=... displayed in Jupyter notebooks.

Along with this I am also working on how to extend sympy functions more generally to show what operations are done. I have working examples for differentiation and integration. I hope to have time to look into how to embed this in sympy, rather than bolt it on.

You can try the examples in mybinder: Binder The git repository is https://github.com/gutow/Easy_Pretty_Math. Please put suggestions and issues in the git repository issues.

Upvotes: 0

Matthias
Matthias

Reputation: 4914

I just came up with one (admittedly quite ugly) way to do it, by creating a custom class:

import sympy as sp
from sympy.printing.pretty.stringpict import prettyForm

class NamedExpression(sp.Expr):

    def __init__(self, name, expr=None, **kwargs):
        self._symbol = sp.Symbol(name, **kwargs)
        self._expr = expr

    def assign(self, expr):
        self._expr = expr

    def as_symbol(self):
        return self._symbol

    def as_eq(self):
        if self._expr is None:
            raise RuntimeError('No expression available')
        return sp.Eq(self._symbol, self._expr)

    def _print_myself(self, printer):
        return _print_function(self, printer)

    _sympystr = _sympyrepr = _latex = _print_myself

    def _pretty(self, printer):
        return prettyForm(_print_function(self, printer))

    def __getattribute__(self, name):
        if name in ['_symbol', '_expr', 'assign', 'as_symbol', 'as_eq',
                    '_pretty', '_sympystr', '_sympyrepr', '_latex']:
            return super().__getattribute__(name)
        expr = self._expr
        if expr is not None:
            return expr.__getattribute__(name)
        return self._symbol.__getattribute__(name)


def _print_function(obj, printer):
    expr = obj._expr
    what = obj._symbol if expr is None else expr
    return printer.doprint(what)

With this class, you can create your symbol like this:

u_x = NamedExpression('u_x')

You can use this in SymPy expressions and it will be displayed like a normal symbol.

At some point, you can assign something to this symbol:

u_x.assign(r * sp.sin(t))

After that, you can again use it in SymPy expressions and it will behave like the assigned expression.

If you want to display the definition, just do

u_x.as_eq()

Upvotes: 2

Matthias
Matthias

Reputation: 4914

I've created an arguably slightly less ugly variation of my previous answer, which avoids all the complicated printing stuff. However, I'm losing the ability to assign an expression at a later time; the expression has to be specified at construction time. But that's probably not a big deal.

Instead of defining a single class, I'm defining a custom class on demand:

import sympy as sp

def named_expression(symbol, expr):

    class NamedExpression(type(expr)):

        def __new__(cls, sym, ex):
            # NB: we don't call the base class' __new__()!
            self = object.__new__(cls)
            if isinstance(sym, sp.Symbol):
                self._symbol = sym
            else:
                self._symbol = sp.Symbol(sym)
            self._expr = ex
            return self

        def __getattribute__(self, name):
            if name in ['_symbol', '_expr', 'as_symbol', 'as_eq']:
                return super().__getattribute__(name)
            return self._expr.__getattribute__(name)

        def as_symbol(self):
            return self._symbol

        def as_eq(self):
            return sp.Eq(self._symbol, self._expr)

    return NamedExpression(symbol, expr)

It can be used like this:

u_x = named_expression('u_x', r * sp.sin(t))

Or, if you already have a symbol named u_x, you can overwrite it like this:

u_x = named_expression(u_x, r * sp.sin(t))

As before, the equality can be shown like this:

u_x.as_eq()

Note that this is not at all specific to IPython, it should work wherever SymPy is available.

Upvotes: 1

user6764549
user6764549

Reputation:

After scanning through IPython's display module some more, it turns out I can use a function like below

from sympy import latex
from IPython.display import display_latex
def disp(idx, symObj):
    eqn = '\\[' + idx + ' = ' + latex(symObj) + '\\]'
    display_latex(eqn,raw=True)
    return

Now, disp('u_x',u_x) gives the desired output.

Correct output

Upvotes: 3

Pierre de Buyl
Pierre de Buyl

Reputation: 7293

Add

s.init_session()

right after the import of sympy.

Then, simply putting

u_x

in a cell will render properly, without the need to call display.

Upvotes: 0

Related Questions