{ "cells": [ { "cell_type": "markdown", "id": "6512a60e-dd1a-4a69-a18f-23321fd927fe", "metadata": {}, "source": [ "# Customizing the Latex printer\n", "\n", "SymPy offers the `init_printing` function, which allows for basic customizations on the Latex code generated from symbolic expression.\n", "\n", "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.\n", "\n", "Let's initialize a new Latex printer:" ] }, { "cell_type": "code", "execution_count": 1, "id": "6531b5f8-52c2-4792-9d1a-4bdf1529abd6", "metadata": {}, "outputs": [], "source": [ "from sympy import *\n", "from sympy_equation import Eqn, init_latex_printing\n", "\n", "printer = init_latex_printing()" ] }, { "cell_type": "markdown", "id": "e9bc8abc-6589-4ae7-b4e2-1e4dcd5e958f", "metadata": {}, "source": [ "In the following, we will look at a few common cases and learn how to apply settings to the printer." ] }, { "cell_type": "markdown", "id": "9206f92d-c784-4a76-8a55-aeb86a83cccf", "metadata": {}, "source": [ "## Functions and arguments" ] }, { "cell_type": "markdown", "id": "d9ffe4c7-f432-4d7e-b322-63effb3ce187", "metadata": {}, "source": [ "First, let's consider a few functions. By default, all arguments are going to be visible:" ] }, { "cell_type": "code", "execution_count": 2, "id": "94439525-efc9-45dd-a7ef-11b1678bf540", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f{\\left(x,y,z \\right)} + g{\\left(\\theta{\\left(x,y \\right)} \\right)} + \\cos{\\left(x y \\right)}$" ], "text/plain": [ "f(x, y, z) + g(theta(x, y)) + cos(x*y)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x, y, z = symbols(\"x:z\")\n", "theta = Function(\"theta\")(x, y)\n", "f = Function(\"f\")(x, y, z)\n", "g = Function(\"g\")(theta)\n", "expr = f + g + cos(x * y)\n", "expr" ] }, { "cell_type": "markdown", "id": "5170538b-961c-460e-a0e3-6e42ce8ae439", "metadata": {}, "source": [ "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.\n", " \n", "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:" ] }, { "cell_type": "code", "execution_count": 3, "id": "578128d2-340c-477d-8afa-08c2c219c441", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f{\\left(x,y,z\\right)} + g{\\left(\\theta\\right)} + \\cos{\\left(x y \\right)}$" ], "text/plain": [ "f(x, y, z) + g(theta(x, y)) + cos(x*y)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.applied_undef_args = \"first-level\"\n", "expr" ] }, { "cell_type": "markdown", "id": "3b9c189c-55d5-4b21-90cb-7288d5368766", "metadata": {}, "source": [ "`printer. = value` is a way to set the printer's behavior. It affects all sub-expressions targeted by ``. Take a look at the documentation of `init_latex_printer`, or execute `printer?`, for a list showing all available attributes and options.\n", "\n", "For example, `applied_undef_args` is targeting all applied undefined functions, in the above example $f(x, y, z)$ and $g(\\theta)$.\n", "\n", "We can also hide all arguments from undefined applied functions:" ] }, { "cell_type": "code", "execution_count": 4, "id": "a5a84aac-7836-4998-8ce1-4c5d052fbc72", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f + g + \\cos{\\left(x y \\right)}$" ], "text/plain": [ "f(x, y, z) + g(theta(x, y)) + cos(x*y)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.applied_undef_args = None\n", "expr" ] }, { "cell_type": "markdown", "id": "0cec2e84-366b-40ee-ad79-63a212f4bec9", "metadata": {}, "source": [ "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$:" ] }, { "cell_type": "code", "execution_count": 5, "id": "7959c56c-ecbc-4f88-a34d-1ad9b5e17298", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f + g{\\left(\\theta\\right)} + \\cos{\\left(x y \\right)}$" ], "text/plain": [ "f(x, y, z) + g(theta(x, y)) + cos(x*y)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.add_rule(g, applied_undef_args=\"first-level\")\n", "expr" ] }, { "cell_type": "markdown", "id": "197f08e6-a678-4f66-a4d8-baa454f4fd38", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 6, "id": "6deaeb35-b850-496a-9639-591017a4e9cb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0] g(theta(x, y)) {'applied_undef_args': 'first-level'}\n" ] } ], "source": [ "printer.show_rules()" ] }, { "cell_type": "markdown", "id": "353040a5-e93f-42d8-a59d-39036255b657", "metadata": {}, "source": [ "The above output shows that there is 1 rule, with index 0, applied to $g$. Eventually, we can remove a rule in this way:" ] }, { "cell_type": "code", "execution_count": 7, "id": "c637fc2a-5c46-46d5-b217-1fe2c10b6368", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "No rules yet.\n" ] } ], "source": [ "rule_idx = 0\n", "printer.remove_rule(rule_idx)\n", "printer.show_rules()" ] }, { "cell_type": "markdown", "id": "46a1ade5-0376-4ce6-9b1a-4e01937f5e3b", "metadata": {}, "source": [ "Then, all undefined applied functions will be printed according to `printer.applied_undef_args`, without arguments for this specific case:" ] }, { "cell_type": "code", "execution_count": 8, "id": "7bd4ed72-840f-4412-85e3-778fa4248aa5", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f + g + \\cos{\\left(x y \\right)}$" ], "text/plain": [ "f(x, y, z) + g(theta(x, y)) + cos(x*y)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr" ] }, { "cell_type": "markdown", "id": "16218d6b-06b5-4d0b-b2b1-d0ad04dd93da", "metadata": {}, "source": [ "## Derivatives" ] }, { "cell_type": "markdown", "id": "577dc7c0-400d-4a2a-9082-e058dbd30786", "metadata": {}, "source": [ "Let's consider an expression with mixed partial derivatives:" ] }, { "cell_type": "code", "execution_count": 9, "id": "f8fb560d-a83f-4b49-9cb3-7515c7238cb9", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\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}$" ], "text/plain": [ "-x*y*cos(x*y) - sin(x*y) + Derivative(g(theta(x, y)), theta(x, y))*Derivative(theta(x, y), x, y) + Derivative(g(theta(x, y)), (theta(x, y), 2))*Derivative(theta(x, y), x)*Derivative(theta(x, y), y) + Derivative(f(x, y, z), x, y)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "expr = expr.diff(x).diff(y)\n", "expr" ] }, { "cell_type": "markdown", "id": "5c5f77d9-2bad-44fa-8efe-1b7f25a7d808", "metadata": {}, "source": [ "We can apply a different style for derivatives:" ] }, { "cell_type": "code", "execution_count": 10, "id": "1017272c-428c-4e0e-b514-67be75a9e9ae", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\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}$" ], "text/plain": [ "-x*y*cos(x*y) - sin(x*y) + Derivative(g(theta(x, y)), theta(x, y))*Derivative(theta(x, y), x, y) + Derivative(g(theta(x, y)), (theta(x, y), 2))*Derivative(theta(x, y), x)*Derivative(theta(x, y), y) + Derivative(f(x, y, z), x, y)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.derivative = \"subscript\"\n", "expr" ] }, { "cell_type": "markdown", "id": "b536cba9-6bcc-4aa7-b94c-0bd6ac073d49", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 11, "id": "f7e2d24d-9e41-4864-b441-d6f871036f85", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\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}$" ], "text/plain": [ "-x*y*cos(x*y) - sin(x*y) + Derivative(g(theta(x, y)), theta(x, y))*Derivative(theta(x, y), x, y) + Derivative(g(theta(x, y)), (theta(x, y), 2))*Derivative(theta(x, y), x)*Derivative(theta(x, y), y) + Derivative(f(x, y, z), x, y)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.add_rule(g, derivative=\"prime-roman\")\n", "expr" ] }, { "cell_type": "code", "execution_count": 12, "id": "450ecfe3-4228-4fc9-a4ed-017c52bade90", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0] g(theta(x, y)) {'derivative': 'prime-roman'}\n" ] } ], "source": [ "printer.show_rules()" ] }, { "cell_type": "markdown", "id": "a9b29aa6-f63b-44fe-ab58-7c28dc120482", "metadata": {}, "source": [ "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! " ] }, { "cell_type": "markdown", "id": "0a3b1216-4c86-4009-917b-ae6c4ede5303", "metadata": {}, "source": [ "## Vectors" ] }, { "cell_type": "markdown", "id": "4716bcc5-756c-44e0-85f1-d41d823d3a88", "metadata": {}, "source": [ "Let's start by creating a new printer with default settings, and two vectors defined in different coordinate systems:" ] }, { "cell_type": "code", "execution_count": 13, "id": "f8f308fe-38fc-4b35-9dd1-f7bed4559e61", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\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}}$" ], "text/plain": [ "5*S.i + 6*S.j + 7*S.k" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\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}}$" ], "text/plain": [ "(-7*sin(S.phi) + 5*sin(S.theta)*cos(S.phi) + 6*cos(S.phi)*cos(S.theta))*C.i + (5*sin(S.phi)*sin(S.theta) + 6*sin(S.phi)*cos(S.theta) + 7*cos(S.phi))*C.j + (-6*sin(S.theta) + 5*cos(S.theta))*C.k" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from sympy.vector import CoordSys3D, express\n", "printer = init_latex_printing()\n", "\n", "C = CoordSys3D(\"C\")\n", "S = C.create_new(\"S\", transformation=\"spherical\")\n", "e_r, e_theta, e_phi = S.base_vectors()\n", "vs = 5 * e_r + 6 * e_theta + 7 * e_phi\n", "vc = express(vs, C)\n", "display(vs, vc)" ] }, { "cell_type": "markdown", "id": "627c94fe-829d-4306-b002-e9a74e490f4e", "metadata": {}, "source": [ "The output above shows that, by default, base vectors will be rendered with:\n", "\n", "* *ijk-notation* for Cartesian systems.\n", "* *e-notation* for curvilinear systems.\n", "\n", "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.\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 14, "id": "a4164cb6-25b7-41da-8595-e173ba2f790d", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\begin{bmatrix}5 \\\\ 6 \\\\ 7\\end{bmatrix}_{\\text{S}}$" ], "text/plain": [ "5*S.i + 6*S.j + 7*S.k" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\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}}$" ], "text/plain": [ "(-7*sin(S.phi) + 5*sin(S.theta)*cos(S.phi) + 6*cos(S.phi)*cos(S.theta))*C.i + (5*sin(S.phi)*sin(S.theta) + 6*sin(S.phi)*cos(S.theta) + 7*cos(S.phi))*C.j + (-6*sin(S.theta) + 5*cos(S.theta))*C.k" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "printer.vector = \"matrix\"\n", "display(vs, vc)" ] }, { "cell_type": "markdown", "id": "f92ae055-0361-496a-8e5d-3049840909cf", "metadata": {}, "source": [ "The subscripts $S$ and $C$ indicate the coordinate systems of the vector." ] }, { "cell_type": "markdown", "id": "9d52a296-4a60-4b99-b9cf-b1d91ff41226", "metadata": {}, "source": [ "### Working on a single Cartesian system\n", "\n", "Suppose we are working on a single Cartesian system:" ] }, { "cell_type": "code", "execution_count": 15, "id": "be20aabc-c91d-4b42-aba5-4690bdc59e3e", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle x_{\\text{C}}\\,\\mathbf{\\hat{i}_{C}} + y_{\\text{C}}\\,\\mathbf{\\hat{j}_{C}} + z_{\\text{C}}\\,\\mathbf{\\hat{k}_{C}}$" ], "text/plain": [ "C.x*C.i + C.y*C.j + C.z*C.k" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer = init_latex_printing()\n", "C = CoordSys3D(\"C\")\n", "x, y, z = C.base_scalars()\n", "i, j, k = C.base_vectors()\n", "vc = x*i + y*j + z*k\n", "vc" ] }, { "cell_type": "markdown", "id": "0315e446-0a27-4a83-ae3c-3037dd72985a", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 16, "id": "244cb6cd-788c-4bcc-8222-c9a89b698cfa", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle x\\,\\mathbf{\\hat{i}} + y\\,\\mathbf{\\hat{j}} + z\\,\\mathbf{\\hat{k}}$" ], "text/plain": [ "C.x*C.i + C.y*C.j + C.z*C.k" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.base_scalar_style = \"normal-ns\"\n", "printer.base_vector_style = \"ijk-ns\"\n", "vc" ] }, { "cell_type": "markdown", "id": "275b6265-7125-415f-9c89-71abd4a1e1c6", "metadata": {}, "source": [ "Where the '-ns' stands for *no system*." ] }, { "cell_type": "markdown", "id": "203bce08-5b56-4352-b1b9-bc180a0e6f72", "metadata": {}, "source": [ "### Working on two coordinate systems\n", "\n", "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:\n", "\n", "* `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.\n", "* `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." ] }, { "cell_type": "code", "execution_count": 17, "id": "923735c1-0ccc-4621-a1d6-aee30a35d267", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle 2\\,\\mathbf{\\hat{e}}^{\\left(\\text{S}\\right)}_{\\boldsymbol{r}}$" ], "text/plain": [ "2*S.i" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.base_vector_style = \"auto\"\n", "printer.add_rule(C, base_vector_style=\"ijk-ns\")\n", "\n", "S = C.create_new(\"S\", transformation=\"spherical\")\n", "r, theta, phi = S.base_scalars()\n", "e_r, e_theta, e_phi = S.base_vectors()\n", "vs = 2 * e_r\n", "vs" ] }, { "cell_type": "code", "execution_count": 18, "id": "1b151bb7-90ce-4fc8-a0ad-e3e64dfc5a2d", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle x\\,\\mathbf{\\hat{i}} + y\\,\\mathbf{\\hat{j}} + z\\,\\mathbf{\\hat{k}}$" ], "text/plain": [ "C.x*C.i + C.y*C.j + C.z*C.k" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vc" ] }, { "cell_type": "markdown", "id": "19cf226b-44fc-4a15-8e0f-8dcd67d9acc1", "metadata": {}, "source": [ "## Coloring sub-expressions\n", "\n", "Suppose we'd like to highlight some sub-expression, then we can apply a dictionary mapping symbolic expressions to latex colors:" ] }, { "cell_type": "code", "execution_count": 21, "id": "e0e3c9e1-4f23-4fcd-b707-01f7fdde74cb", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle f{\\left(x,y \\right)} \\sin{\\left(y \\right)} + g{\\left(x,y \\right)} \\cos{\\left(x \\right)}$" ], "text/plain": [ "f(x, y)*sin(y) + g(x, y)*cos(x)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer = init_latex_printing()\n", "\n", "x, y = symbols(\"x y\")\n", "f = Function(\"f\")(x, y)\n", "g = Function(\"g\")(x, y)\n", "expr = cos(x) * g + sin(y) * f\n", "expr" ] }, { "cell_type": "code", "execution_count": 22, "id": "596e2971-deea-4b6e-ae20-7ace32f5d0b5", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\textcolor{red}{f{\\left(x,y \\right)}} \\sin{\\left(y \\right)} + g{\\left(x,y \\right)} \\textcolor{green}{\\cos{\\left(x \\right)}}$" ], "text/plain": [ "f(x, y)*sin(y) + g(x, y)*cos(x)" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "printer.colorize = {\n", " f: \"red\",\n", " cos(x): \"green\"\n", "}\n", "expr" ] } ], "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.12.7" } }, "nbformat": 4, "nbformat_minor": 5 }