the_plot.show()
Today let’s look into some pretty neat SymPy functionality. I was in a fluid dynamics lecture, practicing taking notes with LaTeX on the go and stumbled upon this monstrosity:
\[ \Delta(k) = \frac{\rho_1-\rho_2}{\rho_1 + \rho_2} gk + \frac{\gamma k^3}{\rho_1 + \rho_2} - \frac{\rho_1 \rho_2}{(\rho_1 + \rho_2)^2} U^2 k^2 \]
(bonus points for whoever recognizes this!)
We were supposed to draw this for a few example sets of values. All right! I opened up pinta
and scribbled a few squiggly lines with my small touchpad, following the blackboard drawings. It looked darn ugly, but that got me thinking. SymPy
has parsers, right? Can’t I just parse that LaTeX equation into Python and make that plot pretty with matplotlib?
Well, as it turns out, sure…
But it takes some tinkering.
All right, let the tinkering commence! Let’s get straight to the point. For this to run, you’ll need antlr4 (in current Jupyter, you can simply do %conda install antlr-python-runtime
from within the Notebook).
We’re going to simply dump the LaTeX string into sympy.parsing.latex.parse_latex
, with the important caveat - this needs to be a r"raw string"
. Otherwise, LaTeX is going to go wild put a carriage return into every \rho
.
import sympy
from sympy.parsing.latex import parse_latex
= r"\Delta(k) = \frac{\rho_1-\rho_2}{\rho_1 + \rho_2} gk + \frac{\gamma k^3}{\rho_1 + \rho_2} - \frac{\rho_1 \rho_2}{(\rho_1 + \rho_2)^2} U^2 k^2"
latex_string = parse_latex(latex_string)
equation equation
Eq(Delta(k), -U**2*k**2*rho_{1}*rho_{2}/(rho_{1} + rho_{2})**2 + (g*k)*((rho_{1} - rho_{2})/(rho_{1} + rho_{2})) + (gamma*k**3)/(rho_{1} + rho_{2}))
We can access the variables we’d like to substitute (as SymPy symbols) using equation.free_symbols
:
equation.free_symbols
{U, g, gamma, k, rho_{1}, rho_{2}}
Ideally what I’d like to do is use .subs
on the equation
to plug in numerical values. To achieve this, it would probably be easiest to turn the symbols into Python variables. However…
= equation.free_symbols
U, g, gamma, k, rho_1, rho_2 U, gamma, rho_1
(g, U, gamma)
… the unordered nature of Python’s set
comes back with a vengeance! It’s not too trivial to get these out in the right order. You could try sorted
, but one does not simply compare Symbol
s:
sorted(equation.free_symbols)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-f67f3a401c32> in <module> ----> 1 sorted(equation.free_symbols) /progs/miniconda3/lib/python3.7/site-packages/sympy/core/relational.py in __nonzero__(self) 227 228 def __nonzero__(self): --> 229 raise TypeError("cannot determine truth value of Relational") 230 231 __bool__ = __nonzero__ TypeError: cannot determine truth value of Relational
What I ended up doing here is:
= sorted(equation.free_symbols,
U, g, gamma, k, rho_1, rho_2 = lambda x: str(x) # the literal key part here - just sort them alphabetically!
key
) U, g, gamma, k, rho_1, rho_2
(U, g, gamma, k, rho_{1}, rho_{2})
And now we can simply use subs
with a dictionary:
dict(rho_1=1,
equation.subs(=2,
rho_2=1,
gamma=1,
g
) )
Eq(Delta(k), -U**2*k**2*rho_{1}*rho_{2}/(rho_{1} + rho_{2})**2 + k**3/(rho_{1} + rho_{2}) + k*(rho_{1} - rho_{2})/(rho_{1} + rho_{2}))
… or can we? This does not work on rho_{1}
and rho_{2}
. Here’s why:
dict(rho_1=1,
=2,
rho_2=1,
gamma=1,
g )
{'rho_1': 1, 'rho_2': 2, 'gamma': 1, 'g': 1}
Well duh, those are string values when input this way, and "rho_1" != "rho_{1}"
!
We could instead do the following:
= {rho_1: 1,
better_dict 2,
rho_2: 1,
gamma: 1,
g:
} better_dict
{rho_{1}: 1, rho_{2}: 2, gamma: 1, g: 1}
Will that work?
equation.subs(better_dict)
Eq(Delta(k), -2*U**2*k**2/9 + k**3/3 - k/3)
Finally! However, along the way you may have noticed a simpler way to do this:
= equation.subs({
simpler_equation "rho_{1}": 1,
"rho_{2}": 2,
"gamma": 1,
"g": 1,
}) simpler_equation
Eq(Delta(k), -2*U**2*k**2/9 + k**3/3 - k/3)
Note how this did not need us to even touch equation.free_symbols
or mess around with sorted
at all! I’m leaving the exploratory part here though - it might help someone looking to access variables in a parse_latex
expression.
We may now plot it:
= simpler_equation.rhs
DeltaK DeltaK
-2*U**2*k**2/9 + k**3/3 - k/3
import sympy.plotting
import matplotlib.pyplot as plt
'figure.figsize'] = 12, 8
plt.rcParams[= (k, 0, 300)
k_range = ["blue", "green", "red"]
colors = [1, 20, 50]
U_values = []
plots for u, color in zip(U_values, colors):
= sympy.plot(DeltaK.subs(U, u), k_range,
plot =False,
show=color,
line_color=True,
legend=r"$\Delta(k)$",
ylabel= (-1e6, 1e6),
ylim = (0, 300),
xlim = f"${latex_string}$",
title
)
plots.append(plot)
0].extend(plots[1])
plots[0].extend(plots[2])
plots[= plots[0]
the_plot the_plot.show()
And, while not beautiful, it’s much more pretty than what I got together with pinta
!