Customizing the Latex printer

SymPy offers the init_printing function, which allows for basic customizations on the Latex code generated from symbolic expression.

This module offers a new function, init_latex_printing, which returns the printer that will be used to generate the Latex code (which will be then rendered on the screen). This printer is an extension of SymPy’s LatexPrinter, offering a few more customization options.

Let’s initialize a new Latex printer:

[1]:
from sympy import *
from sympy_equation import Eqn, init_latex_printing

printer = init_latex_printing()

In the following, we will look at a few common cases and learn how to apply settings to the printer.

Functions and arguments

First, let’s consider a few functions. By default, all arguments are going to be visible:

[2]:
x, y, z = symbols("x:z")
theta = Function("theta")(x, y)
f = Function("f")(x, y, z)
g = Function("g")(theta)
expr = f + g + cos(x * y)
expr
[2]:
$\displaystyle f{\left(x,y,z \right)} + g{\left(\theta{\left(x,y \right)} \right)} + \cos{\left(x y \right)}$

Consider the applied undefined function \(g(\theta(x, y))\), which is composed of \(\theta(x, y)\). As can be seen, the printer generated Latex code including all nested arguments.

For longer expressions, it might be useful to only show \(g(\theta)\). In other words, show only the arguments on the first level of the expressions tree. We can achieve it by setting the following attribute on the printer:

[3]:
printer.applied_undef_args = "first-level"
expr
[3]:
$\displaystyle f{\left(x,y,z\right)} + g{\left(\theta\right)} + \cos{\left(x y \right)}$

printer.<attribute_name> = value is a way to set the printer’s behavior. It affects all sub-expressions targeted by <attribute_name>. Take a look at the documentation of init_latex_printer, or execute printer?, for a list showing all available attributes and options.

For example, applied_undef_args is targeting all applied undefined functions, in the above example \(f(x, y, z)\) and \(g(\theta)\).

We can also hide all arguments from undefined applied functions:

[4]:
printer.applied_undef_args = None
expr
[4]:
$\displaystyle f + g + \cos{\left(x y \right)}$

What if we want to show the arguments of \(g\) and hide the arguments of \(f\)? We can keep the printer.applied_undef_args = None (as set above), and set a custom rule for the function \(g\):

[5]:
printer.add_rule(g, applied_undef_args="first-level")
expr
[5]:
$\displaystyle f + g{\left(\theta\right)} + \cos{\left(x y \right)}$

Now, everytime the printer generate Latex code for \(g\), it will write \(g(\theta)\), while \(f\) will be printed without arguments. We can list all custom rules with:

[6]:
printer.show_rules()
[0] g(theta(x, y))     {'applied_undef_args': 'first-level'}

The above output shows that there is 1 rule, with index 0, applied to \(g\). Eventually, we can remove a rule in this way:

[7]:
rule_idx = 0
printer.remove_rule(rule_idx)
printer.show_rules()
No rules yet.

Then, all undefined applied functions will be printed according to printer.applied_undef_args, without arguments for this specific case:

[8]:
expr
[8]:
$\displaystyle f + g + \cos{\left(x y \right)}$

Derivatives

Let’s consider an expression with mixed partial derivatives:

[9]:
expr = expr.diff(x).diff(y)
expr
[9]:
$\displaystyle - x y \cos{\left(x y \right)} - \sin{\left(x y \right)} + \frac{\partial g}{\partial \theta} \frac{\partial^{2} \theta}{\partial y\partial x} + \frac{\partial^{2} g}{\partial \theta^{2}} \frac{\partial \theta}{\partial x} \frac{\partial \theta}{\partial y} + \frac{\partial^{2} f}{\partial y\partial x}$

We can apply a different style for derivatives:

[10]:
printer.derivative = "subscript"
expr
[10]:
$\displaystyle - x y \cos{\left(x y \right)} - \sin{\left(x y \right)} + g_{\theta} \theta_{xy} + g_{\theta\theta} \theta_{x} \theta_{y} + f_{xy}$

We can also apply custom rules. Here, \(g\) is a function of \(\theta\) only. Let’s say we’d like to show derivatives of \(g\) using prime notation:

[11]:
printer.add_rule(g, derivative="prime-roman")
expr
[11]:
$\displaystyle - x y \cos{\left(x y \right)} - \sin{\left(x y \right)} + g^{\prime} \theta_{xy} + g^{\prime\prime} \theta_{x} \theta_{y} + f_{xy}$
[12]:
printer.show_rules()
[0] g(theta(x, y))     {'derivative': 'prime-roman'}

Note that we added a rule for \(g(\theta)\), not a specific derivative of \(g(\theta)\). The code automatically created a pattern matching function for its derivatives!

Vectors

Let’s start by creating a new printer with default settings, and two vectors defined in different coordinate systems:

[13]:
from sympy.vector import CoordSys3D, express
printer = init_latex_printing()

C = CoordSys3D("C")
S = C.create_new("S", transformation="spherical")
e_r, e_theta, e_phi = S.base_vectors()
vs = 5 * e_r + 6 * e_theta + 7 * e_phi
vc = express(vs, C)
display(vs, vc)
$\displaystyle 5\,\mathbf{\hat{e}}^{\left(\text{S}\right)}_{\boldsymbol{r}} + 6\,\mathbf{\hat{e}}^{\left(\text{S}\right)}_{\boldsymbol{\theta}} + 7\,\mathbf{\hat{e}}^{\left(\text{S}\right)}_{\boldsymbol{\phi}}$
$\displaystyle \left(- 7 \sin{\left(\phi_{\text{S}} \right)} + 5 \sin{\left(\theta_{\text{S}} \right)} \cos{\left(\phi_{\text{S}} \right)} + 6 \cos{\left(\phi_{\text{S}} \right)} \cos{\left(\theta_{\text{S}} \right)}\right)\,\mathbf{\hat{i}_{C}} + \left(5 \sin{\left(\phi_{\text{S}} \right)} \sin{\left(\theta_{\text{S}} \right)} + 6 \sin{\left(\phi_{\text{S}} \right)} \cos{\left(\theta_{\text{S}} \right)} + 7 \cos{\left(\phi_{\text{S}} \right)}\right)\,\mathbf{\hat{j}_{C}} + \left(- 6 \sin{\left(\theta_{\text{S}} \right)} + 5 \cos{\left(\theta_{\text{S}} \right)}\right)\,\mathbf{\hat{k}_{C}}$

The output above shows that, by default, base vectors will be rendered with:

  • ijk-notation for Cartesian systems.

  • e-notation for curvilinear systems.

The vector module represents vectors as an addition of components multiplying base vectors. Often, this produces very long one-line expressions that are difficult to read, requiring us to use scroll bars to see all the components.

In these occasions, it might be useful to represent a vector in a matrix form. Instead of continuosly type vs.to_matrix(S) or vc.to_matrix(C), we can set the printer to do it for us:

[14]:
printer.vector = "matrix"
display(vs, vc)
$\displaystyle \begin{bmatrix}5 \\ 6 \\ 7\end{bmatrix}_{\text{S}}$
$\displaystyle \begin{bmatrix}- 7 \sin{\left(\phi_{\text{S}} \right)} + 5 \sin{\left(\theta_{\text{S}} \right)} \cos{\left(\phi_{\text{S}} \right)} + 6 \cos{\left(\phi_{\text{S}} \right)} \cos{\left(\theta_{\text{S}} \right)} \\ 5 \sin{\left(\phi_{\text{S}} \right)} \sin{\left(\theta_{\text{S}} \right)} + 6 \sin{\left(\phi_{\text{S}} \right)} \cos{\left(\theta_{\text{S}} \right)} + 7 \cos{\left(\phi_{\text{S}} \right)} \\ - 6 \sin{\left(\theta_{\text{S}} \right)} + 5 \cos{\left(\theta_{\text{S}} \right)}\end{bmatrix}_{\text{C}}$

The subscripts \(S\) and \(C\) indicate the coordinate systems of the vector.

Working on a single Cartesian system

Suppose we are working on a single Cartesian system:

[15]:
printer = init_latex_printing()
C = CoordSys3D("C")
x, y, z = C.base_scalars()
i, j, k = C.base_vectors()
vc = x*i + y*j + z*k
vc
[15]:
$\displaystyle x_{\text{C}}\,\mathbf{\hat{i}_{C}} + y_{\text{C}}\,\mathbf{\hat{j}_{C}} + z_{\text{C}}\,\mathbf{\hat{k}_{C}}$

The above output shows a subscript indicating the system both on base scalars and base vectors. If we are working only on a single coordinate system, then we might want to hide all those subscripts, because they quickly create longer expressions. In order to save some space we can set:

[16]:
printer.base_scalar_style = "normal-ns"
printer.base_vector_style = "ijk-ns"
vc
[16]:
$\displaystyle x\,\mathbf{\hat{i}} + y\,\mathbf{\hat{j}} + z\,\mathbf{\hat{k}}$

Where the ‘-ns’ stands for no system.

Working on two coordinate systems

Suppose we have one Cartesian system and one curvilinear system and we’d like to use ijk-notation without subscripts for the Cartesian system, and the e-notation for the curvilinear system. Then we can set:

  • printer.base_vector_style = "auto": this is the default behavior. It uses ijk-notation with subscripts for the Cartesian systems, and e-notation for the curvilinear systems.

  • printer.add_rule(C, base_vector_style="ijk-ns"): add a customization rule, requesting the base vectors of C to be rendererd with ijk-notation without subscripts.

[17]:
printer.base_vector_style = "auto"
printer.add_rule(C, base_vector_style="ijk-ns")

S = C.create_new("S", transformation="spherical")
r, theta, phi = S.base_scalars()
e_r, e_theta, e_phi = S.base_vectors()
vs = 2 * e_r
vs
[17]:
$\displaystyle 2\,\mathbf{\hat{e}}^{\left(\text{S}\right)}_{\boldsymbol{r}}$
[18]:
vc
[18]:
$\displaystyle x\,\mathbf{\hat{i}} + y\,\mathbf{\hat{j}} + z\,\mathbf{\hat{k}}$

Coloring sub-expressions

Suppose we’d like to highlight some sub-expression, then we can apply a dictionary mapping symbolic expressions to latex colors:

[21]:
printer = init_latex_printing()

x, y = symbols("x y")
f = Function("f")(x, y)
g = Function("g")(x, y)
expr = cos(x) * g + sin(y) * f
expr
[21]:
$\displaystyle f{\left(x,y \right)} \sin{\left(y \right)} + g{\left(x,y \right)} \cos{\left(x \right)}$
[22]:
printer.colorize = {
    f: "red",
    cos(x): "green"
}
expr
[22]:
$\displaystyle \textcolor{red}{f{\left(x,y \right)}} \sin{\left(y \right)} + g{\left(x,y \right)} \textcolor{green}{\cos{\left(x \right)}}$