Quick Overview
QSpin relies on OrdinaryDiffEq to time-evolve equations of motion. The QSpin package itself provides some helper methods for constructing frequently-used terms (or functions, or sub-functions) that appear in the equations of motion, as well as some thin wrappers around the functionality we need from OrdinaryDiffEq.
One important thing to keep in mind is that the functions QSpin provides need to be written in a format that OrdinaryDiffEq.ODEProblem is expecting. The full documentation for these problems is available OrdinaryDiffEq's website, but the main impact on how we write functions in QSpin is the functional form of the equation of motion $f$.
The function $f$ that defines the equation of motion should have one of two signatures:
- The simplest signature is
f(ψ, parameters, t).- These functions should explicitly
returnan array that corresponds to the evaluation of the equation of motion $f$. ψandtare the field value and current time respectively.parametersis a container that includes all constant parameter values that are needed to evaluate the equation of motion.
- These functions should explicitly
- A more memory-efficient format for the functions is
f!(dψ, ψ, parameters, t).dψshould be overwritten with the result of evaluating $f$, rather than having the result of the evaluation explicitly returned viareturn.ψ,t, andparametershave the same interpretations as above.
QSpin Conventions
Helper Functions for the Equations of Motion
There are several functions within QSpin that can generate an appropriate function f from the component "pieces" of the equation of motion. The hamiltonian! function, for example, constructs a function H!(dψ, ψ, parameters, t) from two other functions that compute the kinetic and potential energy.
QSpin uses the convention that the names of these "constructor" functions have an ! at the end if the function they return has the f!(dψ, ψ, parameters, t) signature - that is, when the returned function is expected to mutate its input in-place. Conversely, constructor functions without the ! at the end of their name return functions with the f(ψ, parameters, t) signature.
The ParameterType used for parameters Arguments
The ParameterType type provided by QSpin can be used for static typing of the parameters argument.
OrdinaryDiffEq recommends using immutable containers for the parameters variable. QSpin elects to use NamedTuples for this purpose, however will not complain if you write equations of motion that also accept Tuple values. However, the functions provided by QSpin all assume that either:
parametersis provided as aNamedTuple, if the function requires access to certain named parameters. Parameter values are then accessed by name within the function, e.g. viaparameters.theta.parametersis provided as an emptyNamedTupleor emptyTuple, if it is not accessed by the function. EmptyTuples andNamedTuples can be created with the()and(;)syntax, respectively.
You are free to write your own equations of motion that assume the parameters argument is provided as a Tuple. QSpin prefers NamedTuples, since it makes reading the source code of the problem being defined easier; parameters.theta is much more legible than simply accessing parameters[2] which you have decided will correspond to theta. This is of particular importance to QSpin, since we anticipate exploring a variety of problems, each with different parameter names and values, and thus cannot feasibly decide on an absolute-indexing convention for parameters that spans all the systems we want to explore.
Time-Evolving Equations of Motion
QSpin.OdeSolve provides the evolve method which is a thin wrapper that creates an appropriate OrdinaryDiffEq.ODEProblem and then passes it to OrdinaryDiffEq.solve. Solver options, usually taken by OrdinaryDiffEq.solve, can be passed via keyword arguments to solver_options. The positional arguments to QSpin.OdeSolve.evolve are essentially analogues of the arguments that are passed to OrdinaryDiffEq.ODEProblem to create the problem.
Example: Coupled System of ODEs
As a simple example, let's solve the system of equations
\[\begin{aligned} \frac{\mathrm{d}\psi}{\mathrm{d}t} = M \psi, &\qquad \psi_0 = \begin{pmatrix} 0.1 \\ 0.2 \end{pmatrix}, \end{aligned}\]
over the time interval $t\in[0,1]$, with
\[M = \begin{pmatrix} -2 & 1 \\ 1 & -2 \end{pmatrix}.\]
Naively, we could just define our equation of motion function $f$ as
function f(ψ)
return [-2 1; 1 -2] * ψ
endhowever, this is not the format that evolve is expecting. It also returns the evaluation explicitly, rather than updating an argument in place. To use this equation of motion with evolve, we need to give it the appropriate signature:
function f!(dψ, ψ, parameters, t)
# Perform an in-place overwrite of the input array,
# replacing the current values with the result of evaluation.
dψ .= [-2 1; 1 -2] * ψ
endNotice that:
- Even though the equation of motion does not use the
parametersortarguments, they must be present in the function signature. - The final line performs an in-place update (
.=) to one of the input arrays. - The exclamation mark at the end of the function name (
f!) is consistent with Julia's naming practices for functions that edit their input arguments.
We could then time-evolve $f$ as follows:
ψ0 = [0.1; 0.2]
timestep = 1e-3
save_interval = 1e-1
# Note that the default time-range for evolve is [0, 1].
ψ = QSpin.OdeSolve.evolve(
f!, ψ0;
alg=OrdinaryDiffEq.Tsit5(),
dt=timestep,
saveat=save_interval,
)Example: Parametrised Coupling Matrix
In the example above, we hard-coded the value we wanted the matrix $M$ to take into our equation of motion. This is inefficient, as every time we want to solve an equation of motion of the same form as $f$, we'd need to re-write (or define a new function) f!. We can instead make use of OrdinaryDiffEq's parametrisation functionality, and the parameters argument to evolve, to avoid having multiple "versions" of the same function floating around.
function f_parametrised!(dψ, ψ, parameters, t)
dψ .= parameters.M * ψ
end
ψ0 = [0.1; 0.2]
timestep = 1e-3
save_interval = 1e-1
original_parameters = (
M = [-2 1; 1 -2],
)
alternative_parameters = (
M = [-4 1; 1 -4],
)
ψ_original = QSpin.OdeSolve.evolve(
f_parametrised!, ψ0, p=original_parameters;
alg=OrdinaryDiffEq.Tsit5(),
dt=timestep,
saveat=save_interval,
)
ψ_alternative = QSpin.OdeSolve.evolve(
f_parametrised!, ψ0, p=alternative_parameters;
alg=OrdinaryDiffEq.Tsit5(),
dt=timestep,
saveat=save_interval,
)