Howtos
\[\gdef\op#1{\hat{#1}} \gdef\Liouvillian{\mathcal{L}} \gdef\Re{\operatorname{Re}} \gdef\Im{\operatorname{Im}}\]
How to implement a new propagation method
Define a new sub-type of
AbstractPropagator
type that is unique to the propagation method, e.g.MyNewMethodPropagator
. If appropriate, sub-typePiecewisePropagator
orPWCPropagator
.The high-level
propagate
andinit_prop
functions have a mandatorymethod
keyword argument. That argument should receive aModule
object for themodule
implementing a propagation method (e.g.,using QuantumPropagators: Cheby; method=Cheby
). This ensures that the module or package implementing the method is fully loaded. Internally,init_prop
delegatesmethod=module
to a positional argumentmethod = Val(nameof(module))
Thus, if
MyNewMethodPropagator
is implemented in a moduleMyNewMethod
, or if it wraps a registered packageMyNewMethod
, implement a newinit_prop
method with the following signature:function init_prop( state, generator, tlist, method::Val{:MyNewMethod}; inplace=true, backward=false, verbose=false, parameters=nothing, # ... method-specific keyword arguments _... # ignore other keyword arguments )
Note the
method::Val{:MyNewMethod}
as the fourth positional (!) parameter. While the public interface forinit_prop
takesmethod
as a keyword argument, privatelyinit_prop
dispatches for different methods as above.If the propagation method is not associated with a module or package, it is also possible to implement a method
init_prop
with a fourth positional argument with a type of, e.g.,::Val{:my_new_method}
. This would allow to call the high-levelpropagate
/init_prop
with the keyword argumentmethod=:my_new_method
, i.e., passing a name (Symbol
) instead of aModule
.Implement the remaining methods in The Propagator interface.
Test the implementation by instantiating a
propagator
and callingQuantumPropagators.Interfaces.check_propagator
on it.
How to specify the spectral range for a Chebychev propagation
A propagation with method=Cheby
requires that the dynamic generator $\op{H}(t)$ be normalized to a spectral range of [-1, 1]. That is, the method needs a (pessimistic) estimate of the "spectral envelope": the minimum and maximum eigenvalue of $\op{H}(t)$ for any point t
on the interval of the propagation time grid tlist
.
By default, the Chebychev propagator uses heuristics to estimate this spectral envelope. If the spectral envelope is known (either analytically of via a separate numerical exploration of the eigenvalues over the full range of possible controls), the minimum and maximum eigenvalues of $\op{H}(t)$ can be passed as keyword arguments E_min
and E_max
to propagate
or init_prop
. Since the Chebychev method is only defined for Hermitian generators, E_min
and E_max
must be real values. Both values must be given.
Manually specifying E_min
and E_max
works with the default specrange_method=:auto
as well as with the explicit specrange_method=:manual
. When calculating the Chebychev coefficients, the given values may still be enlarged by the default specrange_buffer
keyword argument in init_prop
. If E_min
and E_max
should be used exactly, pass specrange_buffer=0
.
How to define a parameterized control
Parameterized controls are function-like objects with an associated vector of parameter values that must be accessible via QuantumPropagators.Controls.get_parameters
.
It is recommended to define a parameterized control as a subtype of QuantumPropagators.Controls.ParameterizedFunction
. The packages ComponentArrays
and UnPack
might be useful in the implementing of a suitable type . For example,
using ComponentArrays
using UnPack: @unpack
using QuantumPropagators.Controls: ParameterizedFunction, get_parameters
struct GaussianControl <: ParameterizedFunction
parameters::ComponentVector{Float64,Vector{Float64},Tuple{Axis{(A=1, t0=2, sigma=3)}}}
end
function GaussianControl(; A=1.0, t0=0.0, sigma=1.0)
return GaussianControl(ComponentVector(; A, t0, sigma))
end
function (control::GaussianControl)(t)
@unpack A, t0, sigma = control.parameters
return A * exp(- (t - t0)^2 / (2 * sigma^2))
end
# usage
gaussian = GaussianControl(A=2.0, sigma=0.5)
gaussian.parameters.t0 = 5 # shift center from original 0.0
@show get_parameters(gaussian)
println("gaussian(4.5) = $(round(gaussian(4.5); digits=3))")
# output
get_parameters(gaussian) = (A = 2.0, t0 = 5.0, sigma = 0.5)
gaussian(4.5) = 1.213
We could put some extra effort into giving direct property access to all parameters and to provide unicode-aliases for all parameters:
using ComponentArrays
using QuantumPropagators.Controls: ParameterizedFunction, get_parameters
struct GaussianControl <: ParameterizedFunction
parameters::ComponentVector{Float64,Vector{Float64},Tuple{Axis{(A=1, t0=2, sigma=3)}}}
end
function GaussianControl(; A=1.0, t0=0.0, t₀=t0, sigma=1.0, σ=sigma)
return GaussianControl(ComponentVector(; A, t0=t₀, sigma=σ))
end
function Base.propertynames(g::GaussianControl, private::Bool=false)
names = (:A, :t0, :t₀, :sigma, :σ)
return private ? Tuple(union(names, fieldnames(GaussianControl))) : names
end
function Base.getproperty(g::GaussianControl, name::Symbol)
unicode_aliases = Dict(:σ => :sigma, :t₀ => :t0)
getproperty(get_parameters(g), get(unicode_aliases, name, name))
end
function Base.setproperty!(g::GaussianControl, name::Symbol, value)
unicode_aliases = Dict(:σ => :sigma, :t₀ => :t0)
setproperty!(
get_parameters(g),
get(unicode_aliases, name, name),
value
)
end
function (control::GaussianControl)(t)
A, t₀, σ = get_parameters(control)
return A * exp(- (t - t₀)^2 / (2 * σ^2))
end
# usage
gaussian = GaussianControl(A=2.0, σ=0.5)
gaussian.t₀ = 5 # shift center from original 0.0
@show get_parameters(gaussian)
println("gaussian(4.5) = $(round(gaussian(4.5); digits=3))")
# output
get_parameters(gaussian) = (A = 2.0, t0 = 5.0, sigma = 0.5)
gaussian(4.5) = 1.213
The QuantumPropagators.Interfaces.check_parameterized_function
can be used to verify the implementation of a ParameterizedFunction
.