Support for bra ket notation

In quantum theory, operators play a central role to obtain physical quantities such as momentum or energy. A widely used nomenclature to simplify the use of operators is the bra-ket notation. Its terms are easy to read, compact and provide a direct way to express and compute various physical quantities. For more details on the mathematical background, refer to Bra-ket notation.

SigSpace supports simple bra-ket expressions, allowing you to quickly evaluate wave functions. The following terms can be used directly in python:

No. Syntax Description
1. B = O\|A> ket Applies the operator O to the state A and stores the result in a new state B.
2. C = bra <A\|B> ket Represents the inner product. The states A and B are multiplied and the integrated value is returned.
3. C = bra <A\|O\|B> ket Returns the expectation value calculated by the operator O. Often A and B are identical. For example to determine the energy of a state

The keywords "bra" and "ket" are needed to fulfill the python syntax and must be imported from SigSpace.

The following operators are defined by default. These expressions are particularly useful for computing the expectation value (expression 3 in table above).

Symbol Name Description
X Position operator Is used to calculate the expected position of a particle.
I Identity operator This is the "1" element of the operator algebra. Applied to a state it will return a copy of the state.
U Time operator The time operator is used to obtain the time evolution of the state. It takes a value as parameter. See example below.
P Momentum operator Computes the impulse of a wave function.
H Hamiltonian The hamiltonian of course is itself an operator. Its definition is explained in the first tutorials.
grad Gradient operator Returns the first derivative of the wave function. The returned state will (e.g. in term No. 1) will have as many columns as the problem has dimensions.
laplace Laplace operator The Laplace operator calculates the sum of the second derivative in all dimensions.

Examples

Now that we've covered the theory, let's explore some examples. To perform real calculations, we’ll first create two states (psi1 and psi2) to be used with the operators:

In [1]:
import sigspace as sg
from sigspace.H.braket import *
import numpy as np

H = sg.Hamiltonian(sdims=1) + (lambda x: -1/(1+(x-2)**2))
solver = sg.Solver(H)
solver.grid(-20, 20, 200)

sol = solver.run()
psi1 = sol.state
sg.quickplot(sol)

solver.nodes.add(sg.PointNode())
sol = solver.run()
psi2 = sol.state

sg.quickplot(sol)

The solver returns a solution object, which, in addition to the solution itself, contains various details such as the number of iterations, computation time, residual, and more. In the following example, we are primarily interested in the state, which is stored in sol.state.

State objects can be used in bra-ket notation. The most straightforward application is:

In [2]:
bra <psi1|psi1> ket
Out[2]:
np.float64(0.999999999999999)

This returns 1, indicating that the state is normalized. However, it can also be combined with another state, for example:

In [3]:
bra <psi1|psi2> ket
Out[3]:
np.float64(0.00015643258544048577)

This value is typically interpreted as the probability of one state collapsing into the other. In this case, it is zero because the two states are orthogonal to each other.

As noted earlier, bra-ket notation can also be used to calculate expectation values. Here's an example of how to compute the expected position of a particle:

In [4]:
bra <psi1|X|psi1> ket
Out[4]:
np.float64(1.9999999060195168)

In this case, the result is 2, which corresponds to the location where the potential reaches its maximum. A similar result is obtained for psi2.

In [5]:
bra <psi2|X|psi2> ket
Out[5]:
np.float64(1.9532684010488088)

The value isn't exactly 2, and this discrepancy is due less to grid resolution and more to the grid size. By extending the limits (e.g., to ±40), the result will be much closer to 2. Higher-energy states are more spatially extended, and a limited simulation space can introduce systematic errors.

In addition to position, the expectation value of the energy can also be calculated using the Hamiltonian operator, which was initially defined as input to the solver:

In [6]:
bra <psi1|H|psi1> ket
Out[6]:
np.float64(-0.5418670175631956)
In [7]:
bra <psi2|H|psi2> ket
Out[7]:
np.float64(-0.06565334713837415)

Although the energies align well with the graphs shown above, the accuracy is slightly lower. In general, it's preferable to use the energy directly calculated by the solver, which is stored in sol.energy.

Another important physical quantity to consider is momentum:

In [8]:
bra <psi1|P|psi1> ket
Out[8]:
np.complex128(-1.8041124150158794e-16j)

Momentum is defined by $ P = -i \hbar \frac{\partial}{\partial x} $. Since the wave function of the stationary Schrödinger equation is real, the resulting momentum must be imaginary. On the other hand, the eigenvalue of an operator must also be a real value to be physically measurable (see postulates of quantum mechanics). To satisfy both conditions, the momentum for bound, stationary solutions will always be zero. This differs for unbounded states, such as scattering states or Bloch waves (see other tutorials).

For demonstration purpose, let's consider a complex wave function of the form $ \psi = \frac{1}{\sqrt{10}} e^{ikx} $, where the prefactor ensures normalization. We'll choose $ k = -1.5 $ and get:

In [9]:
from sigspace.solver.Solver1D import State1D, Wave1D

x = np.linspace(-5, 5, 1000)
psi = State1D(Wave1D(x, np.exp(-1.5j*x)/np.sqrt(10)))

impulse = bra <psi|P|psi> ket
impulse
Out[9]:
np.complex128(-1.4977472374670588-2.5412574466019615e-09j)

As expected the operator returns the $ k $ value from the exponential term.

Finally, we use the time operator defined as $ U(t) = e^{\frac{-i Et}{\hbar}} $. The parameter t can be given as argument:

In [10]:
new_state = U(1)|psi1> ket
sg.quickplot(new_state)

This plots the real and imaginary part of the wave function.