Skip to content
This repository was archived by the owner on Sep 4, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/src/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,12 @@ @phdthesis{GoerzPhd2015
Year = {2015},
}

@article{GoerzNJP2014,
Author = {Goerz, Michael H and Reich, Daniel M and Koch, Christiane P},
Title = {Optimal control theory for a unitary operation under dissipative evolution},
Journal = njp,
Year = {2014},
Doi = {10.1088/1367-2630/16/5/055012},
Pages = {055012},
Volume = {16},
}
22 changes: 11 additions & 11 deletions ext/QuantumControlBaseFiniteDifferencesExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import QuantumControlBase: make_automatic_chi, make_automatic_grad_J_a

function make_automatic_chi(
J_T,
objectives,
trajectories,
::Val{:FiniteDifferences};
via=_default_chi_via(objectives)
via=_default_chi_via(trajectories)
)

function fdm_chi_via_phi!(χ, ϕ, objectives; tau=nothing, τ=tau)
function fdm_chi_via_phi!(χ, ϕ, trajectories; tau=nothing, τ=tau)
function _J_T(Ψ...)
-J_T(Ψ, objectives)
-J_T(Ψ, trajectories)
end
fdm = FiniteDifferences.central_fdm(5, 1)
∇J = FiniteDifferences.grad(fdm, _J_T, ϕ...)
Expand All @@ -27,32 +27,32 @@ function make_automatic_chi(
end
end

function fdm_chi_via_tau!(χ, ϕ, objectives; tau=nothing, τ=tau)
function fdm_chi_via_tau!(χ, ϕ, trajectories; tau=nothing, τ=tau)
if isnothing(τ)
msg = "chi! returned by `make_chi` with `via=:tau` requires keyword argument tau/τ"
throw(ArgumentError(msg))
end
function _J_T(τ...)
-J_T(ϕ, objectives; τ=τ)
-J_T(ϕ, trajectories; τ=τ)
end
fdm = FiniteDifferences.central_fdm(5, 1)
∇J = FiniteDifferences.grad(fdm, _J_T, τ...)
for (k, ∇Jₖ) ∈ enumerate(∇J)
∂J╱∂τ̄ₖ = 0.5 * ∇Jₖ # ½ corrects for gradient vs Wirtinger deriv
# |χₖ⟩ = (∂J/∂τ̄ₖ) |ϕₖ⟩
axpby!(∂J╱∂τ̄ₖ, objectives[k].target_state, false, χ[k])
axpby!(∂J╱∂τ̄ₖ, trajectories[k].target_state, false, χ[k])
end
end

if via ≡ :phi
return fdm_chi_via_phi!
elseif via ≡ :tau
ϕ_tgt = [obj.target_state for obj in objectives]
ϕ_tgt = [traj.target_state for traj in trajectories]
if any(isnothing.(ϕ_tgt))
error("`via=:tau` requires that all objectives define a `target_state`")
error("`via=:tau` requires that all trajectories define a `target_state`")
end
τ_tgt = ComplexF64[1.0 for obj in objectives]
if abs(J_T(ϕ_tgt, objectives) - J_T(nothing, objectives; τ=τ_tgt)) > 1e-12
τ_tgt = ComplexF64[1.0 for traj in trajectories]
if abs(J_T(ϕ_tgt, trajectories) - J_T(nothing, trajectories; τ=τ_tgt)) > 1e-12
error(
"`via=:tau` in `make_chi` requires that `J_T`=$(repr(J_T)) can be evaluated solely via `τ`"
)
Expand Down
22 changes: 11 additions & 11 deletions ext/QuantumControlBaseZygoteExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import QuantumControlBase: make_automatic_chi, make_automatic_grad_J_a

function make_automatic_chi(
J_T,
objectives,
trajectories,
::Val{:Zygote};
via=_default_chi_via(objectives)
via=_default_chi_via(trajectories)
)

function zygote_chi_via_phi!(χ, ϕ, objectives; tau=nothing, τ=tau)
function zygote_chi_via_phi!(χ, ϕ, trajectories; tau=nothing, τ=tau)
function _J_T(Ψ...)
-J_T(Ψ, objectives)
-J_T(Ψ, trajectories)
end
∇J = Zygote.gradient(_J_T, ϕ...)
for (k, ∇Jₖ) ∈ enumerate(∇J)
Expand All @@ -26,31 +26,31 @@ function make_automatic_chi(
end
end

function zygote_chi_via_tau!(χ, ϕ, objectives; tau=nothing, τ=tau)
function zygote_chi_via_tau!(χ, ϕ, trajectories; tau=nothing, τ=tau)
if isnothing(τ)
msg = "chi! returned by `make_chi` with `via=:tau` requires keyword argument tau/τ"
throw(ArgumentError(msg))
end
function _J_T(τ...)
-J_T(ϕ, objectives; τ=τ)
-J_T(ϕ, trajectories; τ=τ)
end
∇J = Zygote.gradient(_J_T, τ...)
for (k, ∇Jₖ) ∈ enumerate(∇J)
∂J╱∂τ̄ₖ = 0.5 * ∇Jₖ # ½ corrects for gradient vs Wirtinger deriv
# |χₖ⟩ = (∂J/∂τ̄ₖ) |ϕₖ⟩
axpby!(∂J╱∂τ̄ₖ, objectives[k].target_state, false, χ[k])
axpby!(∂J╱∂τ̄ₖ, trajectories[k].target_state, false, χ[k])
end
end

if via ≡ :phi
return zygote_chi_via_phi!
elseif via ≡ :tau
ϕ_tgt = [obj.target_state for obj in objectives]
ϕ_tgt = [traj.target_state for traj in trajectories]
if any(isnothing.(ϕ_tgt))
error("`via=:tau` requires that all objectives define a `target_state`")
error("`via=:tau` requires that all trajectories define a `target_state`")
end
τ_tgt = ComplexF64[1.0 for obj in objectives]
if abs(J_T(ϕ_tgt, objectives) - J_T(nothing, objectives; τ=τ_tgt)) > 1e-12
τ_tgt = ComplexF64[1.0 for traj in trajectories]
if abs(J_T(ϕ_tgt, trajectories) - J_T(nothing, trajectories; τ=τ_tgt)) > 1e-12
error(
"`via=:tau` in `make_chi` requires that `J_T`=$(repr(J_T)) can be evaluated solely via `τ`"
)
Expand Down
7 changes: 4 additions & 3 deletions src/QuantumControlBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ module QuantumControlBase

# The export here is simply to indicate which symbols should be re-exported in
# QuantumControl
export ControlProblem, Objective, optimize, propagate_objective
export propagate_objectives
export ControlProblem, Trajectory, optimize, propagate_trajectory
export propagate_trajectories

include("atexit.jl")
include("conditionalthreads.jl")
include("objectives.jl")
include("trajectories.jl")
include("control_problem.jl")
include("propagate.jl")
include("derivs.jl")
include("functionals.jl")
Expand Down
4 changes: 2 additions & 2 deletions src/conditionalthreads.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Usage:
```julia
using QuantumControlBase: @threadsif

function optimize(objectives; use_threads=true)
@threadsif use_threads for k = 1:length(objectives)
function optimize(trajectories; use_threads=true)
@threadsif use_threads for k = 1:length(trajectories)
# ...
end
end
Expand Down
96 changes: 96 additions & 0 deletions src/control_problem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""A full control problem with multiple trajectories.

```julia
ControlProblem(
trajectories,
tlist;
kwargs...
)
```

The `trajectories` are a list of [`Trajectory`](@ref) instances,
each defining an initial state and a dynamical generator for the evolution of
that state. Usually, the trajectory will also include a target state (see
[`Trajectory`](@ref)) and possibly a weight. The `trajectories` may also be
given together with `tlist` as a mandatory keyword argument.

The `tlist` is the time grid on which the time evolution of the initial states
of each trajectory should be propagated. It may also be given as a (mandatory)
keyword argument.

The remaining `kwargs` are keyword arguments that are passed directly to the
optimal control method. These typically include e.g. the optimization
functional.

The control problem is solved by finding a set of controls that minimize an
optimization functional over all trajectories.
"""
struct ControlProblem
trajectories::Vector{<:Trajectory}
tlist::Vector{Float64}
kwargs::Dict{Symbol,Any}
function ControlProblem(trajectories, tlist; kwargs...)
kwargs_dict = Dict{Symbol,Any}(kwargs) # make the kwargs mutable
if :info_hook in keys(kwargs_dict)
info_hook = kwargs_dict[:info_hook]
if info_hook isa Union{Tuple,Vector}
@debug "Implicitly combining info_hooks with chain_infohooks"
kwargs_dict[:info_hook] = chain_infohooks(info_hook...)
end
end
new(trajectories, tlist, kwargs_dict)
end
end

ControlProblem(trajectories; tlist, kwargs...) =
ControlProblem(trajectories, tlist; kwargs...)

ControlProblem(; trajectories, tlist, kwargs...) =
ControlProblem(trajectories, tlist; kwargs...)


function Base.summary(io::IO, problem::ControlProblem)
N = length(problem.trajectories)
nt = length(problem.tlist)
print(io, typeof(problem), " with $N trajectories and $nt time steps")
end


function Base.show(io::IO, mime::MIME"text/plain", problem::ControlProblem)
println(io, summary(problem))
println(io, " trajectories:")
for traj in problem.trajectories
println(io, " ", summary(traj))
end
t = problem.tlist
if length(t) > 5
println(io, " tlist: [", t[begin], ", ", t[begin+1], " … ", t[end], "]")
else
print(io, " tlist: ")
show(io, t)
print(io, "\n")
end
if !isempty(problem.kwargs)
println(io, " kwargs:")
buffer = IOBuffer()
show(buffer, mime, problem.kwargs)
for line in split(String(take!(buffer)), "\n")[2:end]
println(io, " ", line)
end

end
end

function Base.copy(problem::ControlProblem)
return ControlProblem(problem.trajectories, problem.tlist; problem.kwargs...)
end


"""
```julia
controls = get_controls(problem)
```

extracts the controls from `problem.trajectories`.
"""
get_controls(problem::ControlProblem) = get_controls(problem.trajectories)
44 changes: 22 additions & 22 deletions src/functionals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ using LinearAlgebra


# default for `via` argument of `make_chi`
function _default_chi_via(objectives)
if any(isnothing(obj.target_state) for obj in objectives)
function _default_chi_via(trajectories)
if any(isnothing(traj.target_state) for traj in trajectories)
return :phi
else
return :tau
Expand All @@ -16,14 +16,14 @@ end
```julia
chi! = make_chi(
J_T,
objectives;
trajectories;
mode=:any,
automatic=:default,
via=(any(isnothing(obj.target_state) for obj in objectives) ? :phi : :tau),
via=(any(isnothing(t.target_state) for t in trajectories) ? :phi : :tau),
)
```

creates a function `chi!(χ, ϕ, objectives; τ)` that sets
creates a function `chi!(χ, ϕ, trajectories; τ)` that sets
the k'th element of `χ` to ``|χ_k⟩ = -∂J_T/∂⟨ϕ_k|``, where ``|ϕ_k⟩`` is the
k'th element of `ϕ`. These are the states used as the boundary condition for
the backward propagation propagation in Krotov's method and GRAPE. Each
Expand All @@ -36,8 +36,8 @@ the backward propagation propagation in Krotov's method and GRAPE. Each
```

The function `J_T` must take a vector of states `ϕ` and a vector of
`objectives` as positional parameters, and a vector `τ` as a keyword argument,
see e.g. `J_T_sm`). If all objectives define a `target_state`, then `τ`
`trajectories` as positional parameters, and a vector `τ` as a keyword argument,
see e.g. `J_T_sm`). If all trajectories define a `target_state`, then `τ`
will be the overlap of the states `ϕ` with those target states. The functional
`J_T` may or may not use those overlaps. Likewise, the resulting `chi!` may or
may not use the keyword parameter `τ`.
Expand Down Expand Up @@ -68,7 +68,7 @@ the states ``\{|ϕ_k(T)⟩\}``. The resulting function `chi!` ignores any passed
`τ` keyword argument.

If `via=:tau` is given instead, the functional ``J_T`` is considered a function
of overlaps ``τ_k = ⟨ϕ_k^\tgt|ϕ_k(T)⟩``. This requires that all `objectives`
of overlaps ``τ_k = ⟨ϕ_k^\tgt|ϕ_k(T)⟩``. This requires that all `trajectories`
define a `target_state` and that `J_T` calculates the value of the functional
solely based on the values of `τ` passed as a keyword argument. With only the
complex conjugate ``τ̄_k = ⟨ϕ_k(T)|ϕ_k^\tgt⟩`` having an explicit dependency on
Expand Down Expand Up @@ -116,7 +116,7 @@ and the definition of the Zygote gradient with respect to a complex scalar,
`J_T` function, define a new method `make_analytic_chi` like so:

```julia
QuantumControlBase.make_analytic_chi(::typeof(J_T_sm), objectives) = chi_sm!
QuantumControlBase.make_analytic_chi(::typeof(J_T_sm), trajectories) = chi_sm!
```

which links `make_chi` for `J_T_sm` to `chi_sm!`.
Expand All @@ -130,22 +130,22 @@ and the definition of the Zygote gradient with respect to a complex scalar,
"""
function make_chi(
J_T,
objectives;
trajectories;
mode=:any,
automatic=:default,
via=_default_chi_via(objectives),
via=_default_chi_via(trajectories),
)
if mode == :any
try
chi = make_analytic_chi(J_T, objectives)
chi = make_analytic_chi(J_T, trajectories)
@debug "make_chi for J_T=$(J_T) -> analytic"
# TODO: call chi to compile it and ensure required properties
return chi
catch exception
if exception isa MethodError
@info "make_chi for J_T=$(J_T): fallback to mode=:automatic"
try
chi = make_automatic_chi(J_T, objectives, automatic; via)
chi = make_automatic_chi(J_T, trajectories, automatic; via)
# TODO: call chi to compile it and ensure required properties
return chi
catch exception
Expand All @@ -162,20 +162,20 @@ function make_chi(
end
elseif mode == :analytic
try
chi = make_analytic_chi(J_T, objectives)
chi = make_analytic_chi(J_T, trajectories)
# TODO: call chi to compile it and ensure required properties
return chi
catch exception
if exception isa MethodError
msg = "make_chi for J_T=$(J_T): no analytic gradient. Implement `QuantumControlBase.make_analytic_chi(::typeof(J_T), objectives)`"
msg = "make_chi for J_T=$(J_T): no analytic gradient. Implement `QuantumControlBase.make_analytic_chi(::typeof(J_T), trajectories)`"
error(msg)
else
rethrow()
end
end
elseif mode == :automatic
try
chi = make_automatic_chi(J_T, objectives, automatic; via)
chi = make_automatic_chi(J_T, trajectories, automatic; via)
# TODO: call chi to compile it and ensure required properties
return chi
catch exception
Expand All @@ -198,25 +198,25 @@ function make_analytic_chi end


# Module to Symbol-Val
function make_automatic_chi(J_T, objectives, automatic::Module; via)
return make_automatic_chi(J_T, objectives, Val(nameof(automatic)); via)
function make_automatic_chi(J_T, trajectories, automatic::Module; via)
return make_automatic_chi(J_T, trajectories, Val(nameof(automatic)); via)
end

# Symbol to Symbol-Val
function make_automatic_chi(J_T, objectives, automatic::Symbol; via)
return make_automatic_chi(J_T, objectives, Val(automatic); via)
function make_automatic_chi(J_T, trajectories, automatic::Symbol; via)
return make_automatic_chi(J_T, trajectories, Val(automatic); via)
end


DEFAULT_AD_FRAMEWORK = :nothing

function make_automatic_chi(J_T, objectives, ::Val{:default}; via)
function make_automatic_chi(J_T, trajectories, ::Val{:default}; via)
if DEFAULT_AD_FRAMEWORK == :nothing
msg = "make_chi: no default `automatic`. You must run `QuantumControl.set_default_ad_framework` first, e.g. `import Zygote; QuantumControl.set_default_ad_framework(Zygote)`."
error(msg)
else
automatic = DEFAULT_AD_FRAMEWORK
chi = make_automatic_chi(J_T, objectives, DEFAULT_AD_FRAMEWORK; via)
chi = make_automatic_chi(J_T, trajectories, DEFAULT_AD_FRAMEWORK; via)
(string(automatic) == "default") && error("automatic fallback") # DEBUG
@info "make_chi for J_T=$(J_T): automatic with $automatic"
return chi
Expand Down
Loading