Schrödinger Equation Solver
Unitary evolution
The dynamics of a closed (pure) quantum system is governed by the Schrödinger equation
where
where
The Schrödinger equation, which governs the time-evolution of closed quantum systems, is defined by its Hamiltonian and state vector. In the previous sections, Manipulating States and Operators and Tensor Products and Partial Traces, we showed how Hamiltonians and state vectors are constructed in QuantumToolbox.jl. Given a Hamiltonian, we can calculate the unitary (non-dissipative) time-evolution of an arbitrary initial state vector QuantumToolbox time evolution problem sesolveProblem or directly call the function sesolve. It evolves the state vector e_ops at each given time points, using an ordinary differential equation solver provided by the powerful julia package DifferentialEquation.jl.
Example: Spin dynamics
For example, the time evolution of a quantum spin-
H = 2 * π * 0.1 * sigmax()
ψ0 = basis(2, 0) # spin-up
tlist = LinRange(0.0, 10.0, 20)
prob = sesolveProblem(H, ψ0, tlist, e_ops = [sigmaz()], progress_bar = Val(false))
sol = sesolve(prob)Solution of time evolution
(return code: Success)
--------------------------
num_states = 1
num_expect = 1
ODE alg.: OrdinaryDiffEqTsit5.Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), Static.False}(OrdinaryDiffEqCore.trivial_limiter!, OrdinaryDiffEqCore.trivial_limiter!, static(false))
abstol = 1.0e-8
reltol = 1.0e-6Note
Here, we generate the time evolution problem by sesolveProblem first and then put it into the function sesolve. One can also directly call sesolve, which we also demonstrates in below.
The function returns TimeEvolutionSol, as described in the previous section Time Evolution Solutions. The attribute expect in solution is a list of expectation values for the operator(s) that are passed to the e_ops keyword argument.
sol.expect1×20 Matrix{ComplexF64}:
1.0+0.0im 0.789141+0.0im 0.245485+0.0im … 0.789141+0.0im 1.0+0.0imPassing multiple operators to e_ops as a Vector results in the expectation values for each operators at each time points.
tlist = LinRange(0.0, 10.0, 100)
sol = sesolve(H, ψ0, tlist, e_ops = [sigmaz(), sigmay()])Solution of time evolution
(return code: Success)
--------------------------
num_states = 1
num_expect = 2
ODE alg.: OrdinaryDiffEqTsit5.Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), Static.False}(OrdinaryDiffEqCore.trivial_limiter!, OrdinaryDiffEqCore.trivial_limiter!, static(false))
abstol = 1.0e-8
reltol = 1.0e-6Note
Here, we call sesolve directly instead of pre-defining sesolveProblem first (as shown previously).
println(size(sol.times)) # time points corresponds to stored expectation values
println(size(sol.times_states)) # time points corresponds to stored states(100,)
(1,)expt = sol.expect
print(size(expt))(2, 100)We can therefore plot the expectation values:
expt_z = real(expt[1,:])
expt_y = real(expt[2,:])
# plot by CairoMakie.jl
fig = Figure(size = (500, 350))
ax = Axis(fig[1, 1], xlabel = "Time", ylabel = "Expectation values")
lines!(ax, tlist, expt_z, label = L"\langle\hat{\sigma}_z\rangle", linestyle = :solid)
lines!(ax, tlist, expt_y, label = L"\langle\hat{\sigma}_y\rangle", linestyle = :dash)
axislegend(ax, position = :rb)
figIf the keyword argument e_ops is not specified (or given as an empty Vector), the sesolve and functions return a TimeEvolutionSol that contains a list of state vectors which corresponds to the time points specified in tlist:
tlist = [0, 10]
sol = sesolve(H, ψ0, tlist) # or specify: e_ops = []
println(size(sol.times))
println(size(sol.times_states))
[sesolve] 100%|███████████████████████████| Time: 0:00:00 (15.97 μs/it)[K
(2,)
(2,)sol.states2-element Vector{QuantumObject{Ket, Dimensions{1, Tuple{Space}}, Vector{ComplexF64}}}:
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{ComplexF64}:
1.0 + 0.0im
0.0 + 0.0im
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{ComplexF64}:
0.9999999543220187 + 0.0im
0.0 - 5.6377384923229734e-8imThis is because the states will be saved depend on the keyword argument saveat in kwargs. If e_ops is empty, the default value of saveat=tlist (saving the states corresponding to tlist), otherwise, saveat=[tlist[end]] (only save the final state).
One can also specify e_ops and saveat separately:
tlist = [0, 5, 10]
sol = sesolve(H, ψ0, tlist, e_ops = [sigmay()], saveat = tlist)Solution of time evolution
(return code: Success)
--------------------------
num_states = 3
num_expect = 1
ODE alg.: OrdinaryDiffEqTsit5.Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), Static.False}(OrdinaryDiffEqCore.trivial_limiter!, OrdinaryDiffEqCore.trivial_limiter!, static(false))
abstol = 1.0e-8
reltol = 1.0e-6print(size(sol.times))(3,)sol.expect1×3 Matrix{ComplexF64}:
0.0+0.0im -7.18972e-8+0.0im -1.12755e-7+0.0imprint(size(sol.times_states))(3,)sol.states3-element Vector{QuantumObject{Ket, Dimensions{1, Tuple{Space}}, Vector{ComplexF64}}}:
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{ComplexF64}:
1.0 + 0.0im
0.0 + 0.0im
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{ComplexF64}:
-0.9999999984043708 + 0.0im
0.0 + 3.594862168420766e-8im
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{ComplexF64}:
0.9999999543220187 + 0.0im
0.0 - 5.6377384923229734e-8im