# Q: simple Cadabra coordinate computation

+1 vote

As a Cadabra 2 neophyte, with experience in symbolic computation using Sympy,
I'm attempting my first component computation after reading the reference manuals.
The application is to geometric-classical Hamiltonian dynamics.
However, I've run into an issue and it's not clear to me what aspect(s) of Cadabra 2 I'm misunderstanding.

Running the Cadabra 2 kernel in Jupyter Notebook using Firefox 75.0.
Ran the 2-sphere coordinate computation example and it worked as expected.

The code, so far, is as follows:

# Component computations

# Geometric Hamiltonian dynamics
# Ref. "Geometric approach to Lyapunov analysis in Hamiltonian dynamics"
# Published version: https://journals.aps.org/pre/abstract/10.1103/PhysRevE.64.066206

{x y}::Coordinate;
{i, j, k, l, m, n, h#}::Integer(0..1);
{i, j, k, l, m, n, h#}::Indices(values={x, y}, position=fixed);

\partial{#}::PartialDerivative;
g_{i j}::Metric.
g^{i j}::InverseMetric.

# Hénon-Heiles potential
V := (x**2 + y**2)/2 + (x**2)*y - (y**3)/3;

# Ho is a constant: conservation of energy
metric := { g_{x x} = 2*(Ho - V), g_{y y} = 2*(Ho - V) };
complete(metric, $g^{i j}$);

Christoffel := \Gamma^{i}_{j k} = (1/2)*g^{i l}*( \partial_{k}{g_{l j}}
+ \partial_{j}{g_{l k}}
- \partial_{l}{g_{j k}} );

evaluate(Christoffel, metric, rhsonly=True);


After calling "evaluate", all the components of the Christoffel symbol are returned as being identically zero,
whereas a calculation by hand yields, for example,

Christoffel^{0}_{0 0} = -[1 / (Ho - V)] * (x + 2*x*y)

Once V is substituted into the metric, is it being treated a constant in the above code?

Any insight into what I have misunderstood with regards to Cadabra Coordinates and Indices would be appreciated.

System configuration:

OS. Ubuntu 20.04 LTS WSL
Python. Anaconda 3.7.7
jupyter 1.0.0
jupyterclient 6.1.2
jupyter
console 6.1.0
jupytercore 4.6.3
jupyterlab 1.2.6
jupyterlab
server 1.1.0

You need that first line to read

{x, y}::Coordinate;


(mind the comma), and you also have to add

V::Depends(x,y);


if you do not substitute $V$ into your metric before evaluating. Will post an working example tomorrow with some other comments.

You need that first line to read

{x, y}::Coordinate;


(mind the comma),

Thank you for pointing out this error.

and you also have to add

V::Depends(x,y);


Ah.

With the correction and addition the code is

{x, y}::Coordinate;
{i, j, k, l, m, n, h#}::Integer(0..1);
{i, j, k, l, m, n, h#}::Indices(values={x, y}, position=fixed);

\partial{#}::PartialDerivative;
g_{i j}::Metric.
g^{i j}::InverseMetric.

# Hénon-Heiles Hamiltonian potential: V
V::Depends(x, y);
V := (x**2 + y**2)/2 + (x**2)*y - (y**3)/3;

# Ho is a constant of the motion: conservation of energy
metric := { g_{x x} = 2*(Ho - V),
g_{y y} = 2*(Ho - V) };
complete(metric, $g^{i j}$);

Christoffel := \Gamma^{i}_{j k} = (1/2)*g^{i l}*( \partial_{k}{g_{l j}}
+ \partial_{j}{g_{l k}}
- \partial_{l}{g_{j k}} );

evaluate(Christoffel, metric, rhsonly=True);


The modified code now runs fine.
However,

evaluate(Christoffel, metric, rhsonly=True)


returns the partial derivatives of V in symbolic form,
i.e, the partial derivatives of V are not explicitly calculated. Spent some time experimenting, but no success.

If I insert the explicit expression for V(x, y) into the metric, the expected result is returned.

{x, y}::Coordinate;
{i, j, k, l, m, n, h#}::Integer(0..1);
{i, j, k, l, m, n, h#}::Indices(values={x, y}, position=fixed);

\partial{#}::PartialDerivative;
g_{i j}::Metric.
g^{i j}::InverseMetric.

# Hénon-Heiles Hamiltonian potential: V
# V::Depends(x, y);
# V := ((x**2 + y**2)/2 + (x**2)*y - (y**3)/3);

# Ho is a constant of the motion: conservation of energy
metric := { g_{x x} = 2*(Ho - ((x**2 + y**2)/2 + (x**2)*y - (y**3)/3)),
g_{y y} = 2*(Ho - ((x**2 + y**2)/2 + (x**2)*y - (y**3)/3)) };
complete(metric, $g^{i j}$);

Christoffel := \Gamma^{i}_{j k} = (1/2)*g^{i l}*( \partial_{k}{g_{l j}}
+ \partial_{j}{g_{l k}}
- \partial_{l}{g_{j k}} );

evaluate(Christoffel, metric, rhsonly=True);


If possible, however, I'd prefer to use the general form approach and force evaluation of the derivative.

Will post an working example tomorrow with some other comments.

+1 vote

So as you already discovered, the following gives the form with $V$ unevaluated:

{x, y}::Coordinate;
{i, j, k, l, m, n, h#}::Indices(values={x, y}, position=fixed);
\partial{#}::PartialDerivative;
g_{i j}::Metric.
g^{i j}::InverseMetric.

# Hénon-Heiles potential
V::Depends(x,y);
V := (x**2 + y**2)/2 + (x**2)*y - (y**3)/3;

# Ho is a constant: conservation of energy
metric := { g_{x x} = 2*(Ho - V), g_{y y} = 2*(Ho - V) };
complete(metric, $g^{i j}$);

Christoffel := \Gamma^{i}_{j k} = (1/2)*g^{i l}*( \partial_{k}{g_{l j}}
+ \partial_{j}{g_{l k}}
- \partial_{l}{g_{j k}} );


followed by

evaluate(Christoffel, metric, rhsonly=True);


This produces e.g. $\Gamma^{x}{}_{x x}=-2\partial_{y}V (2Ho - 2V)^{-1}$.

If you want that written out in terms of $x$ and $y$, you probably want that only for the derivative part. I would then do something like

substitute(Christoffel, $\partial_{a?}{V} = \partial_{a?}{@(V)}$ )
map_sympy(Christoffel, 'simplify');


which produces precisely the result you quoted, $\Gamma^{x}{}_{x x} = -x(2y+1) (2Ho-2V)^{-1}$. In the first line above, note how we 'pull in' the value of the expression V using @(V). In Cadabra, there is a difference between 'python objects' and 'maths objects'. The things on the left-hand side of a := symbol are python objects, and everything appearing on the right-hand side is built from maths objects. You have to tell Cadabra when to make such substitutions, it won't automatically do that for you (with the advantage that you can keep $V$ in the overall factor $(2Ho-2V)^{-1}$ while writing out the derivative).

The last line above just uses 'sympy' to simplify the expression (cadabra does not have a lot of scalar computer algebra logic built in, but instead passes that on to 'sympy' or 'mathematica').

Last comment: if you wanted to eliminate $V$ immediately, you can of course substitute

substitute(metric, $V = @(V)$);


just before you evaluate.

In the first line above, note how we 'pull in' the value of the expression V using @(V).
In Cadabra, there is a difference between 'python objects' and 'maths
objects'. The things on the left-hand side of a := symbol are python
objects, and everything appearing on the right-hand side is built from
maths objects.

This information helps to clarify my understanding of Cadabra.

The last line above just uses 'sympy' to simplify the expression
(cadabra does not have a lot of scalar computer algebra logic built
in, but instead passes that on to 'sympy' or 'mathematica')

I tried the various @(V) combinations you suggested.
They and the following code fragment

evaluate(Christoffel, metric, rhsonly=True);
substitute(Christoffel, $V = @(V)$);
map_sympy(Christoffel, 'simplify');


helped me to understand how this aspect of Cadabra operates and interacts with Sympy.