Welcome to Cadabra Q&A, where you can ask questions and receive answers from other members of the community.
+1 vote

Hi!

Thanks for Cadabra! I just started playing around. I'm using the Fedora 40 package of Version 2.5.10 (build private dated 2024-12-24).

I'm trying the following:

\partial{#}::PartialDerivative;
t::Coordinate;
O::Depends(t);
{H,O_0}::NonCommuting;

ex := O = \exp(i*H*t) O_0 \exp(-i*H*t);
ex2 := \partial_t{O};

substitute(ex2, ex);

So far so good. However

evaluate(_);

yiels 0 which should not be because H and O_0 are not commuting.

I've read that simplify should not be used (https://cadabra.science/qa/798/simple-commutator-in-quantum-mechanics?show=798#q798) but is this true for evaluate as well?

Thanks in advance,

Sebastian

in General questions by (130 points)

1 Answer

+1 vote

Evaluate calls simplify under the hood. You can turn this off by using

evaluate(_, simplify=False);

However, I think you probably want

product_rule(_);

instead (evaluate is for evaluating components of a tensor expression, but yours is a scalar, so it is already 'evaluated' for that purpose; see the docs for more detail and ask again if it's not clear).

by (84.8k points)

Thanks!

But

evaluate(_, simplify=False);

also returns 0.

And product_rule(_); does not evaluate the derivatives.

I did not find a function that just evaluates the derivatives but keeps the rest of the expression as it is.

Do you have another hint for me?

You probably did

evaluate(_);
evaluate(_, simplify=False);

But remember that Cadabra's algorithms work on the actual expression. So after the first line has run, the expression is zero, and then the second line will call evaluate on that zero.

As far as 'evaluate the derivatives' is concerned, you probably want the chain rule? There isn't a chain_rule at the moment, but you can easily do that by

substitute(_, $\partial_{t}{\exp{A??}} = \exp{A??} \partial_{t}{A??}$ );

Does that help?

Thanks. The chain rule by substitution is already helpful. Concering the evaluate, I've exported it to Python to be sure that there's no already changed expression.

This is the output from my terminal:

➜  ~ cat test-noncommuting.py 
#!/usr/bin/env cadabra2
\partial{#}::PartialDerivative;
t::Coordinate;
O::Depends(t);
{H,O_0}::NonCommuting;

ex := O = \exp(i*H*t) O_0 \exp(-i*H*t);
ex2 := \partial_t{O};

substitute(ex2, ex);
evaluate(_, simplify=False);

➜  ~ ./test-noncommuting.py
Property PartialDerivative attached to \partial{#}.
Property Coordinate attached to t.
Property Depends attached to O.
Property NonCommuting attached to {H, O_{0}}.
O = \exp(i H t) O_{0} \exp(-i H t)
\partial_{t}(O)
\partial_{t}(\exp(i H t) O_{0} \exp(-i H t))
0

However, when I change O_0 to R the output is different:

Property PartialDerivative attached to \partial{#}.
Property Coordinate attached to t.
Property Depends attached to O.
Property NonCommuting attached to {H, R}.
O = \exp(i H t) R \exp(-i H t)
\partial_{t}(O)
\partial_{t}(\exp(i H t) R \exp(-i H t))
\partial_{t}(\exp(i H t) R \exp(-i H t))

I.e. evaluate(_, simplify=False) does not yield 0.

So it seems that there is a problem with the subscript in `H_0`.

Hmm, I must have fixed that issue in the development branch already since that first script definitely leaves the expression intact for me.

In any case, be aware that anything that hits the sympy bridge will be wrong when symbols are not all commuting. The evaluate function was originally made to write out components of tensors (e.g. compute the Riemann tensor in GR from the metric), and none of those applications had non-commuting symbols in them. It's on the todo list...

Thanks. Avoiding evaluate I've come up with the following. Do you have any suggestions to improve the script? Would you say that I should always evaluate derivatives by explicit substitution?

#!/usr/bin/env cadabra2

\partial{#}::PartialDerivative.
t::Coordinate.
O::Depends(t).

# R = O(t=0)
{H,R}::NonCommuting.

# time-evolution of operator O
ex_O := O = \exp(i H t) R \exp(-i H t):
ex_swap_lhs_rhs = Ex(str(ex_O[1]) + "=" + str(ex_O[0]))

dOdt := \partial_t{O}:
print(f"{dOdt} = ", end = "")

substitute(dOdt, ex_O)
product_rule(dOdt)

evaluate_derivatives = [$\partial_{t}{R} -> 0$,
                        $\partial_{t}{\exp{A??}} -> \partial_{t}{A??} \exp{A??}$,
                        $\partial_{t}{i H t} = i H$]

for rule in evaluate_derivatives:
    substitute(_, rule)

factor_out(dOdt, $i$)
substitute(dOdt, ex_swap_lhs_rhs)
result = substitute(dOdt, $A?? B?? - B?? A?? -> \commutator{A??, B??$)

print(result)
➜  ~ ./time-evolution.py
\partial_{t}(O) = i [{H, O}]

A somewhat more cadabra-esque way of doing this is as follows (a regression in 2.5.10 prevents this from completing but 2.5.12 will roll out soon). First, setup the computation:

\partial{#}::PartialDerivative.
t::Coordinate.
O::Depends(t).

# R = O(t=0)
{H,R}::NonCommuting.

# time-evolution of operator O
exO := O = \exp{i H t} R \exp{-i H t}:

dOdt := \partial_{t}{ @(exO) };

This makes dOdt contain the full equation (not just the right-hand side); note how you can take a partial derivative of an equation and it will apply that on both sides. Then

product_rule(dOdt)
evaluate_derivatives = join($\partial_{t}{R} -> 0 $,
                            $\partial_{t}{\exp{A??}} -> \partial_{t}{A??} \exp{A??}$,
                            $\partial_{t}{i H t} = i H$)

substitute(_, evaluate_derivatives, repeat=True)
factor_out(dOdt, $i$)

Now re-insert the original identity (which you can do using builtin functionality, you don't have to do the swapping of lhs/rhs yourself):

from cdb.core.manip import *
substitute(dOdt, swap_sides(exO))
substitute(dOdt, $A?? B?? - B?? A?? -> \commutator{A??}{B??}$);

This is still not completely optimal in my opinion; more improvements to come over time.

Thanks a lot! If you like, I can work out your version into a tutorial or example. What do you say?

sbstnschmtt: Would you say that I should always evaluate derivatives by explicit substitution?

Would you be so kind to comment on this?

Best,

Sebastian

You don't have much choice at the moment: except for product_rule and distribute, there is no functionality to 'work out' derivatives. Until this is implemented you would need the route through sympy, which is fine for expressions with commuting symbols only.

Examples/tutorials are always welcome, so yes please!

The development branch now has some safeguards which will throw an exception if you attempt to use the sympy bridge with an expression containing non-commuting or anti-commuting objects. This will go into 2.5.12.

Great! Thanks a lot!

...