using NamedTrajectories
using PiccoloQuantumObjects
using QuantumCollocation
Problem Templates
We provide a number of problem templates for making it simple and easy to set up and solve certain types of quantum optimal control problems. These templates all construct a DirectTrajOptProblem
object, which stores all the parts of the optimal control problem.
This page provides a brief overview of each problem template, broken down by the state of the problem being solved.
Ket Problem Templates:
Unitary Problem Templates:
Ket Problem Templates
Quantum State Smooth Pulse Problem
QuantumCollocation.ProblemTemplates.QuantumStateSmoothPulseProblem
— FunctionQuantumStateSmoothPulseProblem(system, ψ_inits, ψ_goals, T, Δt; kwargs...)
QuantumStateSmoothPulseProblem(system, ψ_init, ψ_goal, T, Δt; kwargs...)
QuantumStateSmoothPulseProblem(H_drift, H_drives, args...; kwargs...)
Create a quantum state smooth pulse problem. The goal is to find a control pulse a(t)
that drives all of the initial states ψ_inits
to the corresponding target states ψ_goals
using T
timesteps of size Δt
. This problem also controls the first and second derivatives of the control pulse, da(t)
and dda(t)
, to ensure smoothness.
Arguments
system::AbstractQuantumSystem
: The quantum system.
or
H_drift::AbstractMatrix{<:Number}
: The drift Hamiltonian.H_drives::Vector{<:AbstractMatrix{<:Number}}
: The control Hamiltonians.
with
ψ_inits::Vector{<:AbstractVector{<:ComplexF64}}
: The initial states.ψ_goals::Vector{<:AbstractVector{<:ComplexF64}}
: The target states.
or
ψ_init::AbstractVector{<:ComplexF64}
: The initial state.ψ_goal::AbstractVector{<:ComplexF64}
: The target state.
with
T::Int
: The number of timesteps.Δt::Float64
: The timestep size.
Keyword Arguments
state_name::Symbol=:ψ̃
: The name of the state variable.control_name::Symbol=:a
: The name of the control variable.timestep_name::Symbol=:Δt
: The name of the timestep variable.init_trajectory::Union{NamedTrajectory, Nothing}=nothing
: The initial trajectory.a_bound::Float64=1.0
: The bound on the control pulse.a_bounds::Vector{Float64}=fill(a_bound, length(system.G_drives))
: The bounds on the control pulse.a_guess::Union{Matrix{Float64}, Nothing}=nothing
: The initial guess for the control pulse.da_bound::Float64=Inf
: The bound on the first derivative of the control pulse.da_bounds::Vector{Float64}=fill(da_bound, length(system.G_drives))
: The bounds on the first derivative of the control pulse.dda_bound::Float64=1.0
: The bound on the second derivative of the control pulse.dda_bounds::Vector{Float64}=fill(dda_bound, length(system.G_drives))
: The bounds on the second derivative of the control pulse.Δt_min::Float64=0.5 * Δt
: The minimum timestep size.Δt_max::Float64=1.5 * Δt
: The maximum timestep size.drive_derivative_σ::Float64=0.01
: The standard deviation of the drive derivative random initialization.Q::Float64=100.0
: The weight on the state objective.R=1e-2
: The weight on the control pulse and its derivatives.R_a::Union{Float64, Vector{Float64}}=R
: The weight on the control pulse.R_da::Union{Float64, Vector{Float64}}=R
: The weight on the first derivative of the control pulse.R_dda::Union{Float64, Vector{Float64}}=R
: The weight on the second derivative of the control pulse.constraints::Vector{<:AbstractConstraint}=AbstractConstraint[]
: The constraints.piccolo_options::PiccoloOptions=PiccoloOptions()
: The Piccolo options.
Each problem starts with a QuantumSystem
object, which is used to define the system's Hamiltonian and control operators. The goal is to find a control pulse that drives the intial state, ψ_init
, to a target state, ψ_goal
.
define the quantum system
system = QuantumSystem(0.1 * PAULIS.Z, [PAULIS.X, PAULIS.Y])
ψ_init = Vector{ComplexF64}([1.0, 0.0])
ψ_goal = Vector{ComplexF64}([0.0, 1.0])
T = 51
Δt = 0.2
0.2
create the smooth pulse problem
state_prob = QuantumStateSmoothPulseProblem(system, ψ_init, ψ_goal, T, Δt);
constructing QuantumStateSmoothPulseProblem...
using integrator: typeof(KetIntegrator)
using 1 initial state(s)
applying timesteps_all_equal constraint: Δt
check the fidelity before solving
println("Before: ", rollout_fidelity(state_prob.trajectory, system))
Before: 0.2702650322149119
solve the problem
solve!(state_prob, max_iter=100, verbose=true, print_level=1);
initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of ψ̃
applying constraint: initial value of a
applying constraint: initial value of da
applying constraint: final value of a
applying constraint: final value of da
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit https://github.com/coin-or/Ipopt
******************************************************************************
check the fidelity after solving
println("After: ", rollout_fidelity(state_prob.trajectory, system))
After: 0.9999999999792313
extract the control pulses
state_prob.trajectory.a |> size
(2, 51)
Quantum State Minimum Time Problem
QuantumCollocation.ProblemTemplates.QuantumStateMinimumTimeProblem
— FunctionQuantumStateMinimumTimeProblem(traj, sys, obj, integrators, constraints; kwargs...)
QuantumStateMinimumTimeProblem(prob; kwargs...)
Construct a DirectTrajOptProblem
for the minimum time problem of reaching a target state.
Keyword Arguments
state_name::Symbol=:ψ̃
: The symbol for the state variables.final_fidelity::Union{Real, Nothing}=nothing
: The final fidelity.D=1.0
: The cost weight on the time.piccolo_options::PiccoloOptions=PiccoloOptions()
: The Piccolo options.
create the minimum time problem
min_state_prob = QuantumStateMinimumTimeProblem(state_prob, ψ_goal);
constructing QuantumStateMinimumTimeProblem...
final fidelity: 1.0
check the previous duration
println("Duration before: ", get_duration(state_prob.trajectory))
Duration before: 10.46338258283023
solve the minimum time problem
solve!(min_state_prob, max_iter=100, verbose=true, print_level=1);
initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of ψ̃
applying constraint: initial value of a
applying constraint: initial value of da
applying constraint: final value of a
applying constraint: final value of da
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
check the new duration
println("Duration after: ", get_duration(min_state_prob.trajectory))
Duration after: 3.681381318546689
the fidelity is preserved by a constraint
println("Fidelity after: ", rollout_fidelity(min_state_prob.trajectory, system))
Fidelity after: 0.999985409162291
Quantum State Sampling Problem
QuantumCollocation.ProblemTemplates.QuantumStateSamplingProblem
— Functioncreate a sampling problem
driftless_system = QuantumSystem([PAULIS.X, PAULIS.Y])
sampling_state_prob = QuantumStateSamplingProblem([system, driftless_system], ψ_init, ψ_goal, T, Δt);
constructing QuantumStateSamplingProblem...
using integrator: typeof(KetIntegrator)
using 2 initial state(s)
applying timesteps_all_equal constraint: Δt
new keys are added to the trajectory for the new states
println(sampling_state_prob.trajectory.state_names)
(:ψ̃1_system_1, :a, :da, :ψ̃1_system_2)
solve the sampling problem for a few iterations
solve!(sampling_state_prob, max_iter=25, verbose=true, print_level=1);
initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of ψ̃1_system_1
applying constraint: initial value of a
applying constraint: initial value of da
applying constraint: initial value of ψ̃1_system_2
applying constraint: final value of a
applying constraint: final value of da
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
check the fidelity of the sampling problem (use the updated key to get the initial and goal)
println("After (original system): ", rollout_fidelity(sampling_state_prob.trajectory, system, state_name=:ψ̃1_system_1))
println("After (new system): ", rollout_fidelity(sampling_state_prob.trajectory, driftless_system, state_name=:ψ̃1_system_1))
After (original system): 0.9877387017241955
After (new system): 0.9885756196665351
compare this to using the original problem on the new system
println("After (new system, original `prob`): ", rollout_fidelity(state_prob.trajectory, driftless_system))
After (new system, original `prob`): 0.7760663640031801
Unitary Problem Templates
Unitary Smooth Pulse Problem
QuantumCollocation.ProblemTemplates.UnitarySmoothPulseProblem
— FunctionUnitarySmoothPulseProblem(system::AbstractQuantumSystem, operator, T, Δt; kwargs...)
UnitarySmoothPulseProblem(H_drift, H_drives, operator, T, Δt; kwargs...)
Construct a DirectTrajOptProblem
for a free-time unitary gate problem with smooth control pulses enforced by constraining the second derivative of the pulse trajectory, i.e.,
\[\begin{aligned} \underset{\vec{\tilde{U}}, a, \dot{a}, \ddot{a}, \Delta t}{\text{minimize}} & \quad Q \cdot \ell\qty(\vec{\tilde{U}}_T, \vec{\tilde{U}}_{\text{goal}}) + \frac{1}{2} \sum_t \qty(R_a a_t^2 + R_{\dot{a}} \dot{a}_t^2 + R_{\ddot{a}} \ddot{a}_t^2) \\ \text{ subject to } & \quad \vb{P}^{(n)}\qty(\vec{\tilde{U}}_{t+1}, \vec{\tilde{U}}_t, a_t, \Delta t_t) = 0 \\ & \quad a_{t+1} - a_t - \dot{a}_t \Delta t_t = 0 \\ & \quad \dot{a}_{t+1} - \dot{a}_t - \ddot{a}_t \Delta t_t = 0 \\ & \quad |a_t| \leq a_{\text{bound}} \\ & \quad |\ddot{a}_t| \leq \ddot{a}_{\text{bound}} \\ & \quad \Delta t_{\text{min}} \leq \Delta t_t \leq \Delta t_{\text{max}} \\ \end{aligned}\]
where, for $U \in SU(N)$,
\[\ell\qty(\vec{\tilde{U}}_T, \vec{\tilde{U}}_{\text{goal}}) = \abs{1 - \frac{1}{N} \abs{ \tr \qty(U_{\text{goal}}, U_T)} }\]
is the infidelity objective function, $Q$ is a weight, $R_a$, $R_{\dot{a}}$, and $R_{\ddot{a}}$ are weights on the regularization terms, and $\vb{P}^{(n)}$ is the $n$th-order Pade integrator.
Arguments
system::AbstractQuantumSystem
: the system to be controlled
or
H_drift::AbstractMatrix{<:Number}
: the drift hamiltonianH_drives::Vector{<:AbstractMatrix{<:Number}}
: the control hamiltonians
with
goal::AbstractPiccoloOperator
: the target unitary, either in the form of anEmbeddedOperator
or a `Matrix{ComplexF64}T::Int
: the number of timestepsΔt::Float64
: the (initial) time step size
Keyword Arguments
piccolo_options::PiccoloOptions=PiccoloOptions()
: the options for the Piccolo solverstate_name::Symbol = :Ũ⃗
: the name of the statecontrol_name::Symbol = :a
: the name of the controltimestep_name::Symbol = :Δt
: the name of the timestepinit_trajectory::Union{NamedTrajectory, Nothing}=nothing
: an initial trajectory to usea_guess::Union{Matrix{Float64}, Nothing}=nothing
: an initial guess for the control pulsesa_bound::Float64=1.0
: the bound on the control pulsea_bounds::Vector{Float64}=fill(a_bound, length(system.G_drives))
: the bounds on the control pulses, one for each driveda_bound::Float64=Inf
: the bound on the control pulse derivativeda_bounds::Vector{Float64}=fill(da_bound, length(system.G_drives))
: the bounds on the control pulse derivatives, one for each drivedda_bound::Float64=1.0
: the bound on the control pulse second derivativedda_bounds::Vector{Float64}=fill(dda_bound, length(system.G_drives))
: the bounds on the control pulse second derivatives, one for each driveΔt_min::Float64=Δt isa Float64 ? 0.5 * Δt : 0.5 * mean(Δt)
: the minimum time step sizeΔt_max::Float64=Δt isa Float64 ? 1.5 * Δt : 1.5 * mean(Δt)
: the maximum time step sizeQ::Float64=100.0
: the weight on the infidelity objectiveR=1e-2
: the weight on the regularization termsR_a::Union{Float64, Vector{Float64}}=R
: the weight on the regularization term for the control pulsesR_da::Union{Float64, Vector{Float64}}=R
: the weight on the regularization term for the control pulse derivativesR_dda::Union{Float64, Vector{Float64}}=R
: the weight on the regularization term for the control pulse second derivativesconstraints::Vector{<:AbstractConstraint}=AbstractConstraint[]
: the constraints to enforce
The UnitarySmoothPulseProblem
is similar to the QuantumStateSmoothPulseProblem
, but instead of driving the system to a target state, the goal is to drive the system to a target unitary operator, U_goal
.
system = QuantumSystem(0.1 * PAULIS.Z, [PAULIS.X, PAULIS.Y])
U_goal = GATES.H
T = 51
Δt = 0.2
prob = UnitarySmoothPulseProblem(system, U_goal, T, Δt);
constructing UnitarySmoothPulseProblem...
using integrator: typeof(UnitaryIntegrator)
control derivative names: [:da, :dda]
applying timesteps_all_equal constraint: Δt
check the fidelity before solving
println("Before: ", unitary_rollout_fidelity(prob.trajectory, system))
Before: 0.22043303242736734
finding an optimal control is as simple as calling solve!
solve!(prob, max_iter=100, verbose=true, print_level=1);
initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of Ũ⃗
applying constraint: initial value of a
applying constraint: initial value of da
applying constraint: final value of a
applying constraint: final value of da
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
check the fidelity after solving
println("After: ", unitary_rollout_fidelity(prob.trajectory, system))
After: 0.9996060497974855
The NamedTrajectory
object stores the control pulse, state variables, and the time grid.
extract the control pulses
prob.trajectory.a |> size
(2, 51)
Unitary Minimum Time Problem
QuantumCollocation.ProblemTemplates.UnitaryMinimumTimeProblem
— FunctionUnitaryMinimumTimeProblem(
goal::AbstractPiccoloOperator,
trajectory::NamedTrajectory,
objective::Objective,
dynamics::TrajectoryDynamics,
constraints::AbstractVector{<:AbstractConstraint};
kwargs...
)
UnitaryMinimumTimeProblem(
goal::AbstractPiccoloOperator,
prob::DirectTrajOptProblem;
kwargs...
)
Create a minimum-time problem for unitary control.
\[\begin{aligned} \underset{\vec{\tilde{U}}, a, \dot{a}, \ddot{a}, \Delta t}{\text{minimize}} & \quad J(\vec{\tilde{U}}, a, \dot{a}, \ddot{a}) + D \sum_t \Delta t_t \\ \text{ subject to } & \quad \vb{P}^{(n)}\qty(\vec{\tilde{U}}_{t+1}, \vec{\tilde{U}}_t, a_t, \Delta t_t) = 0 \\ & c(\vec{\tilde{U}}, a, \dot{a}, \ddot{a}) = 0 \\ & \quad \Delta t_{\text{min}} \leq \Delta t_t \leq \Delta t_{\text{max}} \\ \end{aligned}\]
Keyword Arguments
piccolo_options::PiccoloOptions=PiccoloOptions()
: The Piccolo options.unitary_name::Symbol=:Ũ⃗
: The name of the unitary for the goal.final_fidelity::Float64=1.0
: The final fidelity constraint.D::Float64=1.0
: The scaling factor for the minimum-time objective.
The goal of this problem is to find the shortest time it takes to drive the system to a target unitary operator, U_goal
. The problem is solved by minimizing the sum of all of the time steps. It is constructed from prob
in the previous example.
min_prob = UnitaryMinimumTimeProblem(prob, U_goal);
constructing UnitaryMinimumTimeProblem...
final fidelity: 1.0
check the previous duration
println("Duration before: ", get_duration(prob.trajectory))
Duration before: 8.369711303490133
solve the minimum time problem
solve!(min_prob, max_iter=100, verbose=true, print_level=1);
initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of Ũ⃗
applying constraint: initial value of a
applying constraint: initial value of da
applying constraint: final value of a
applying constraint: final value of da
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
check the new duration
println("Duration after: ", get_duration(min_prob.trajectory))
Duration after: 5.775410534511182
the fidelity is preserved by a constraint
println("Fidelity after: ", unitary_rollout_fidelity(min_prob.trajectory, system))
Fidelity after: 0.999999996816126
Unitary Sampling Problem
QuantumCollocation.ProblemTemplates.UnitarySamplingProblem
— FunctionUnitarySamplingProblem(systemns, operator, T, Δt; kwargs...)
A UnitarySamplingProblem
is a quantum control problem where the goal is to find a control pulse that generates a target unitary operator for a set of quantum systems. The controls are shared among all systems, and the optimization seeks to find the control pulse that achieves the operator for each system. The idea is to enforce a robust solution by including multiple systems reflecting the problem uncertainty.
Arguments
systems::AbstractVector{<:AbstractQuantumSystem}
: A vector of quantum systems.operators::AbstractVector{<:AbstractPiccoloOperator}
: A vector of target operators.T::Int
: The number of time steps.Δt::Union{Float64, Vector{Float64}}
: The time step value or vector of time steps.
Keyword Arguments
system_labels::Vector{String} = string.(1:length(systems))
: The labels for each system.system_weights::Vector{Float64} = fill(1.0, length(systems))
: The weights for each system.init_trajectory::Union{NamedTrajectory, Nothing} = nothing
: The initial trajectory.state_name::Symbol = :Ũ⃗
: The name of the state variable.control_name::Symbol = :a
: The name of the control variable.timestep_name::Symbol = :Δt
: The name of the timestep variable.constraints::Vector{<:AbstractConstraint} = AbstractConstraint[]
: The constraints.a_bound::Float64 = 1.0
: The bound for the control amplitudes.a_bounds::Vector{Float64} = fill(a_bound, length(systems[1].G_drives))
: The bounds for the control amplitudes.a_guess::Union{Matrix{Float64}, Nothing} = nothing
: The initial guess for the control amplitudes.da_bound::Float64 = Inf
: The bound for the control first derivatives.da_bounds::Vector{Float64} = fill(da_bound, length(systems[1].G_drives))
: The bounds for the control first derivatives.dda_bound::Float64 = 1.0
: The bound for the control second derivatives.dda_bounds::Vector{Float64} = fill(dda_bound, length(systems[1].G_drives))
: The bounds for the control second derivatives.Δt_min::Float64 = 0.5 * Δt
: The minimum time step size.Δt_max::Float64 = 1.5 * Δt
: The maximum time step size.Q::Float64 = 100.0
: The fidelity weight.R::Float64 = 1e-2
: The regularization weight.R_a::Union{Float64, Vector{Float64}} = R
: The regularization weight for the control amplitudes.R_da::Union{Float64, Vector{Float64}} = R
: The regularization weight for the control first derivatives.R_dda::Union{Float64, Vector{Float64}} = R
: The regularization weight for the control second derivatives.piccolo_options::PiccoloOptions = PiccoloOptions()
: The Piccolo options.
A sampling problem is used to solve over multiple quantum systems with the same control. This can be useful for exploring robustness, for example.
create a sampling problem
driftless_system = QuantumSystem([PAULIS.X, PAULIS.Y])
sampling_prob = UnitarySamplingProblem([system, driftless_system], U_goal, T, Δt);
constructing UnitarySamplingProblem...
using integrator: typeof(UnitaryIntegrator)
using 2 systems
applying timesteps_all_equal constraint: Δt
new keys are addded to the trajectory for the new states
println(sampling_prob.trajectory.state_names)
(:Ũ⃗_system_1, :a, :da, :Ũ⃗_system_2)
the solve!
proceeds as in the Quantum State Sampling Problem]
This page was generated using Literate.jl.