Two Qubit Gates
In this example we will solve for a selection of two-qubit gates using a simple two-qubit system. We will use the UnitarySmoothPulseProblem template to solve for the optimal control fields.
Defining our Hamiltonian
In quantum optimal control we work with Hamiltonians of the form
\[H(t) = H_{\text{drift}} + \sum_{j} u^j(t) H_{\text{drive}}^j,\]
Specifically, for a simple two-qubit system in a rotating frame, we have
\[H = J_{12} \sigma_1^x \sigma_2^x + \sum_{i \in {1,2}} a_i^R(t) {\sigma^x_i \over 2} + a_i^I(t) {\sigma^y_i \over 2}.\]
where
\[\begin{align*} J_{12} &= 0.001 \text{ GHz}, \\ |a_i^R(t)| &\leq 0.1 \text{ GHz} \\ \end{align*}\]
And the duration of the gate will be capped at $400 \ \mu s$.
Let's now set this up using some of the convenience functions available in QuantumCollocation.jl.
using QuantumCollocation
using PiccoloQuantumObjects
using NamedTrajectories
using LinearAlgebra
using PiccoloPlots
using CairoMakie
⊗(a, b) = kron(a, b)
# Define our operators
σx = GATES[:X]
σy = GATES[:Y]
Id = GATES[:I]
# Lift the operators to the two-qubit Hilbert space
σx_1 = σx ⊗ Id
σx_2 = Id ⊗ σx
σy_1 = σy ⊗ Id
σy_2 = Id ⊗ σy
# Define the parameters of the Hamiltonian
J_12 = 0.001 # GHz
a_bound = 0.100 # GHz
# Define the drift (coupling) Hamiltonian
H_drift = J_12 * (σx ⊗ σx)
# Define the control Hamiltonians
H_drives = [σx_1 / 2, σy_1 / 2, σx_2 / 2, σy_2 / 2]
# Define control (and higher derivative) bounds
a_bound = 0.1
da_bound = 0.0005
dda_bound = 0.0025
# Scale the Hamiltonians by 2π
H_drift *= 2π
H_drives .*= 2π
# Define the time parameters
T = 100 # timesteps
T_max = 1.0 # max evolution time
u_bounds = [
(-1.0, 1.0),
(-1.0, 1.0),
(-1.0, 1.0),
(-1.0, 1.0)
]
duration = 100 # μs
Δt = duration / T
Δt_max = 400 / T
# Define the system
sys = QuantumSystem(H_drift, H_drives, T_max, u_bounds)QuantumSystem: levels = 4, n_drives = 4SWAP gate
# Define the goal operation
U_goal = [
1 0 0 0;
0 0 1 0;
0 1 0 0;
0 0 0 1
] |> Matrix{ComplexF64}
# Set up the problem
prob = UnitarySmoothPulseProblem(
sys,
U_goal,
T,
Δt;
a_bound=a_bound,
da_bound=da_bound,
dda_bound=dda_bound,
R_da=0.01,
R_dda=0.01,
Δt_max=Δt_max,
piccolo_options=PiccoloOptions(bound_state=true),
)
fid_init = unitary_rollout_fidelity(prob.trajectory, sys, drive_name=:a)
println(fid_init) constructing UnitarySmoothPulseProblem...
using integrator: typeof(UnitaryIntegrator)
control derivative names: [:da, :dda]
applying timesteps_all_equal constraint: Δt
0.00039542607543285754Solve the problem
solve!(prob; max_iter=100) initializing optimizer...
applying constraint: timesteps all equal constraint
applying constraint: initial value of Ũ⃗
applying constraint: initial value of a
applying constraint: final value of a
applying constraint: bounds on a
applying constraint: bounds on da
applying constraint: bounds on dda
applying constraint: bounds on Δt
applying constraint: bounds on Ũ⃗
******************************************************************************
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
******************************************************************************
This is Ipopt version 3.14.19, running with linear solver MUMPS 5.8.1.
Number of nonzeros in equality constraint Jacobian...: 122590
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 22843
Total number of variables............................: 4460
variables with only lower bounds: 0
variables with lower and upper bounds: 4460
variables with only upper bounds: 0
Total number of equality constraints.................: 4059
Total number of inequality constraints...............: 0
inequality constraints with only lower bounds: 0
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 0
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 1.9966690e+00 4.09e-01 1.50e+01 0.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 1.8868533e+00 4.08e-01 1.49e+01 -2.2 7.79e-01 - 2.56e-03 2.67e-03f 1
2 1.5589426e+00 4.05e-01 1.49e+01 -2.2 7.02e-01 - 2.93e-03 7.80e-03f 1
3 1.2080227e+00 4.00e-01 1.48e+01 -2.2 6.76e-01 - 6.93e-03 1.10e-02f 1
4 1.0979090e+00 3.98e-01 1.90e+01 -2.0 6.91e-01 - 3.70e-02 5.96e-03f 1
<...snip...>
95 5.0246578e-04 8.45e-07 1.60e-03 -5.4 3.00e-03 -0.5 1.00e+00 1.00e+00h 1
96 4.1531633e-04 6.13e-07 7.07e+01 -5.4 3.03e-03 -0.9 1.00e+00 1.00e+00h 1
97 3.3533437e-04 5.91e-06 4.93e+01 -4.0 9.44e-03 -1.4 1.00e+00 3.03e-01h 2
98 3.9456259e-04 5.21e-06 2.76e+01 -4.0 9.13e-03 -1.9 1.00e+00 1.25e-01h 4
99 3.3591342e-04 4.60e-06 4.66e+01 -4.0 1.34e-02 -2.4 1.00e+00 1.25e-01h 4
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
100 3.3633429e-04 4.46e-06 2.56e+01 -4.1 2.62e-02 -2.8 1.00e+00 3.12e-02h 6
Number of Iterations....: 100
(scaled) (unscaled)
Objective...............: 3.3633429041403043e-04 3.3633429041403043e-04
Dual infeasibility......: 2.5590761688032366e+01 2.5590761688032366e+01
Constraint violation....: 4.4603128525290609e-06 4.4603128525290609e-06
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 1.1727909811733182e-04 1.1727909811733182e-04
Overall NLP error.......: 2.5590761688032366e+01 2.5590761688032366e+01
Number of objective function evaluations = 216
Number of objective gradient evaluations = 101
Number of equality constraint evaluations = 216
Number of inequality constraint evaluations = 0
Number of equality constraint Jacobian evaluations = 101
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations = 100
Total seconds in IPOPT = 470.209
EXIT: Maximum Number of Iterations Exceeded.
# Let's take a look at the final fidelity
fid_final = unitary_rollout_fidelity(prob.trajectory, sys, drive_name=:a)
println(fid_final)
@assert fid_final > 0.990.9999986984382394Looks good!
Now let's plot the pulse and the population trajectories for the first two columns of the unitary, i.e. initial state of $\ket{00}$ and $\ket{01}$. For this we provide the function plot_unitary_populations.
plot_unitary_populations(prob.trajectory, control_name=:a)
For fun, let's look at a minimum time pulse for this problem
min_time_prob = UnitaryMinimumTimeProblem(prob, U_goal; final_fidelity=.995)DirectTrajOptProblem
timesteps = 100
duration = 376.46706725712096
variable names = (:Ũ⃗, :a, :da, :dda, :Δt)
knot point dimension = 45
solve!(min_time_prob; max_iter=300)fid_final_min_time = unitary_rollout_fidelity(min_time_prob.trajectory, sys, drive_name=:a)
println(fid_final_min_time)
@assert fid_final_min_time > 0.990.9949999904950233And let's plot this solution
plot_unitary_populations(min_time_prob.trajectory, control_name=:a)
It looks like our pulse derivative bounds are holding back the solution, but regardless, the duration has decreased:
duration = get_duration(prob.trajectory)
min_time_duration = get_duration(min_time_prob.trajectory)
println(duration, " - ", min_time_duration, " = ", duration - min_time_duration)376.46706725712096 - 355.4858483001799 = 20.981218956941063Mølmer–Sørensen gate
Here we will solve for a Mølmer–Sørensen gate between two. The gate is generally described, for N qubits, by the unitary matrix
\[U_{\text{MS}}(\vec\theta) = \exp\left(i\sum_{j=1}^{N-1}\sum_{k=j+1}^{N}\theta_{jk}\sigma_j^x\sigma_k^x\right),\]
where $\sigma_j^x$ is the Pauli-X operator acting on the $j$-th qubit, and $\vec\theta$ is a vector of real parameters. The Mølmer–Sørensen gate is a two-qubit gate that is particularly well-suited for trapped-ion qubits, where the interaction between qubits is mediated.
Here we will focus on the simplest case of a Mølmer–Sørensen gate between two qubits. The gate is described by the unitary matrix
\[U_{\text{MS}}\left({\pi \over 4}\right) = \exp\left(i\frac{\pi}{4}\sigma_1^x\sigma_2^x\right).\]
Let's set up the problem.
# Define the goal operation
U_goal = exp(im * π/4 * σx_1 * σx_2)
# Set up and solve the problem
prob = UnitarySmoothPulseProblem(
sys,
U_goal,
T,
Δt;
a_bound=a_bound,
da_bound=da_bound,
dda_bound=dda_bound,
R_da=0.01,
R_dda=0.01,
Δt_max=Δt_max,
piccolo_options=PiccoloOptions(bound_state=true),
)
fid_init = unitary_rollout_fidelity(prob.trajectory, sys, drive_name=:a)
println(fid_init) constructing UnitarySmoothPulseProblem...
using integrator: typeof(UnitaryIntegrator)
control derivative names: [:da, :dda]
applying timesteps_all_equal constraint: Δt
0.12249963901581312solve!(prob; max_iter=300)# Let's take a look at the final fidelity
fid_final = unitary_rollout_fidelity(prob.trajectory, sys, drive_name=:a)
println(fid_final)
@assert fid_final > 0.9990.9999494300031536Again, looks good!
Now let's plot the pulse and the population trajectories for the first two columns of the unitary, i.e. initial state of $\ket{00}$ and $\ket{01}$.
plot_unitary_populations(prob.trajectory, control_name=:a)
For fun, let's look at a minimum time pulse for this problem
min_time_prob = UnitaryMinimumTimeProblem(prob, U_goal; final_fidelity=.9995)DirectTrajOptProblem
timesteps = 100
duration = 312.9535256034742
variable names = (:Ũ⃗, :a, :da, :dda, :Δt)
knot point dimension = 45
solve!(min_time_prob; max_iter=300)fid_final_min_time = unitary_rollout_fidelity(min_time_prob.trajectory, sys, drive_name=:a)
println(fid_final_min_time)
@assert fid_final_min_time > 0.9990.9994999899999919And let's plot this solution
plot_unitary_populations(min_time_prob.trajectory, control_name=:a)
It looks like our pulse derivative bounds are holding back the solution, but regardless, the duration has decreased:
duration = get_duration(prob.trajectory)
min_time_duration = get_duration(min_time_prob.trajectory)
println(duration, " - ", min_time_duration, " = ", duration - min_time_duration)312.9535256034742 - 121.44085505155924 = 191.51267055191494This page was generated using Literate.jl.