Code Generation with SymPy¶
I am often working with density functionals where equations of motion follow by varying an energy functional with respect to some densities. In this post I explore using SymPy as a tool for expressing these relationships and for generating the relevant code.
import sympy
sympy.init_printing(use_latex=True)
from sympy import S, var
Differentiating a non-linear expression is straightforward, the complication is dealing with gradient terms. For example, consider a Brachistochrone problem to find the curve $y(x)$ that minimizes the time for a particle to slide from $y(0) = 0$ to $y(1) = y_1$. From energy conservation starting with $v(0) = 0$ we have $v^2/2 = gy$ where $g \approx -10$m/s$^2$. We can formulate the problem in terms of the horizontal velocity
$$ v_x = \dot{x} = \frac{v}{\sqrt{1+(y')^2}} $$where $y'(x)$ is the slope to find the total time $T$ which is to be minimized:
$$ \DeclareMathOperator{\d}{d} T = \int_0^T \d{t} = \int_0^1 \d{x}\;\diff{}{t}{x} = \int_0^1 \d{x}\;\frac{1}{v_x} = \int_0^1 \d{x}\;\sqrt{\frac{1+(y')^2}{2gy}} $$x, y, z, g = sympy.symbols('x, y, z, g')
y_x = sympy.Function('y')(x)
dy_x = sympy.Derivative(y(x), x)
f = sympy.sqrt((1+dy_x**2)/(2*g*y_x))
F = sympy.Integral(f, (x, 0, 1))
F, f
The Euler-Lagrange equations can be obtained by differentiating with respect to the variable and derivative, but sympy can't do the latter without help:
f.diff(dy_x)
We can do this with substitutions:
def ddiff(f, dy_x):
return f.subs(dy_x, '_dy_x').diff('_dy_x').subs('_dy_x', dy_x)
ddiff(f, dy_x)
(f.diff(y_x) - ddiff(f, dy_x).diff(x)).simplify()
[sympy.simplify(_s.subs(y(x), y)) for _s in sympy.calculus.euler.euler_equations(f.subs(y, y(x)))]
from sympy.tensor import IndexedBase, Idx
from sympy import symbols
M = IndexedBase('M')
i, j = symbols('i, j', cls=Idx)
M[i]
M[i].diff(M[j])
f = sympy.Function('f')
i = sympy.Symbol('i')
f(i).diff(i)
from sympy.physics.vector import ReferenceFrame
R = ReferenceFrame('R')