From 0ca895d694cb4b8e29410a13d1448436c1a5fe03 Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Mon, 11 Dec 2023 17:08:33 -0500 Subject: [PATCH] docs example: multilevel transmom + edits --- docs/literate/examples/multilevel_transmon.jl | 127 +++++++++++++++ docs/literate/examples/single_qubit.jl | 1 - docs/make.jl | 2 +- .../generated/examples/multilevel_transmon.md | 148 ++++++++++++++++++ docs/src/generated/examples/single_qubit.md | 10 -- src/quantum_utils.jl | 11 ++ 6 files changed, 287 insertions(+), 12 deletions(-) create mode 100644 docs/literate/examples/multilevel_transmon.jl delete mode 100644 docs/literate/examples/single_qubit.jl create mode 100644 docs/src/generated/examples/multilevel_transmon.md delete mode 100644 docs/src/generated/examples/single_qubit.md diff --git a/docs/literate/examples/multilevel_transmon.jl b/docs/literate/examples/multilevel_transmon.jl new file mode 100644 index 00000000..b7830a10 --- /dev/null +++ b/docs/literate/examples/multilevel_transmon.jl @@ -0,0 +1,127 @@ +# # Multilevel Transmon + +# In this example we will look at a multilevel transmon qubit with a Hamiltonian given by +# +# ```math +# \hat{H}(t) = \frac{\delta}{2} \hat{n}(\hat{n} - 1) + u_1(t) (\hat{a} + \hat{a}^\dagger) + u_2(t) i (\hat{a} - \hat{a}^\dagger) +# ``` +# where $\hat{n} = \hat{a}^\dagger \hat{a}$ is the number operator, $\hat{a}$ is the annihilation operator, $\delta$ is the anharmonicity, and $u_1(t)$ and $u_2(t)$ are control fields. +# +# We will use the following parameter values: +# +# ```math +# \begin{aligned} +# \delta &= 0.2 \text{ GHz}\\ +# \abs{u_i(t)} &\leq 0.2 \text{ GHz}\\ +# T_0 &= 10 \text{ ns}\\ +# \end{aligned} +# ``` + +# ## Setting up the problem + +using QuantumCollocation +using NamedTrajectories +using LinearAlgebra + +## define the time parameters + +T₀ = 10 # total time in ns +T = 50 # number of time steps +Δt = T₀ / T # time step + +## define the number of levels to model +levels = 3 + +## create operators +n̂ = number(levels) +â = annihilate(levels) +â_dag = create(levels) + +## define the Hamiltonian +δ = 0.2 +H_drift = 2π * δ * n̂ * (n̂ - I(levels)) / 2 +H_drives = [ + 2π * (â + â_dag), + 2π * im * (â - â_dag), +] + +## define the goal unitary in the computational subspace +U_init, U_goal = subspace_unitary([levels], :X, 1) + +U_goal + +# Let's get the subspace indices as well as we will need them later. + +subspace = subspace_indices([levels]) + +## check that these are the correct indices (trivial in the case of a single transmon, but a useful check for more complicated systems) +U_goal[subspace, subspace] + +# WE also can look at U_init, which is not exactly the identity + +U_init + +# Now will set up the optimization problem using the [`UnitarySmoothPulseProblem`](@ref) type. + +## set the bound on the pulse amplitude + +a_bound = 2π * 0.2 + +prob = UnitarySmoothPulseProblem( + H_drift, + H_drives, + U_goal, + T, + Δt; + U_init=U_init, + subspace=subspace, + a_bound=a_bound +) + +## and we can solve this problem + +solve!(prob; max_iter=100) + +# and we can look at the fidelity in the subspace + +f = unitary_fidelity(prob; subspace=subspace) + +println("Fidelity: $f") + +# We can also look at the pulse shapes + +transformations = OrderedDict( + :Ũ⃗ => [ + x -> populations(iso_vec_to_operator(x)[:, 1]), + x -> populations(iso_vec_to_operator(x)[:, 2]), + x -> populations(iso_vec_to_operator(x)[:, 3]), + ] +) + +transforamtion_labels = OrderedDict( + :Ũ⃗ => [ + "\\psi^g", + "\\psi^e", + "\\psi^f", + ] +) + +transformation_titles = OrderedDict( + :Ũ⃗ => [ + "Populations of evolution from |0⟩", + "Populations of evolution from |1⟩", + "Populations of evolution from |2⟩", + ] +) + +plot(prob.trajectory, [:a]; + res=(1200, 1200), + transformations=transformations, + transformation_labels=transforamtion_labels, + include_transformation_labels=true, + transformation_titles=transformation_titles +) + +# ## Leakage suppression + +# As can bee seen in the plot above, although the fidelity is high, the $f$ level of the transmon is highly populated throughout the evolution. This is suboptimal, but we can account for this by penalizing the leakage elements of the unitary, namely those elements of the form $U_{f, i}$ where $i \neq f$. We utilize an $L_1$ penalty on these elements, which is implemented in the [`UnitarySmoothPulseProblem`](@ref) type as the `leakage_penalty` keyword argument. diff --git a/docs/literate/examples/single_qubit.jl b/docs/literate/examples/single_qubit.jl deleted file mode 100644 index 2f377893..00000000 --- a/docs/literate/examples/single_qubit.jl +++ /dev/null @@ -1 +0,0 @@ -# # Single Qubit diff --git a/docs/make.jl b/docs/make.jl index 4e27b8d0..00ed59fd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -14,7 +14,7 @@ pages = [ "Utilities" => "generated/man/utils.md", ], "Examples" => [ - "Single Qubit" => "generated/examples/single_qubit.md", + "Multilevel Transmon" => "generated/examples/multilevel_transmon.md", ], "Library" => "lib.md", ] diff --git a/docs/src/generated/examples/multilevel_transmon.md b/docs/src/generated/examples/multilevel_transmon.md new file mode 100644 index 00000000..d320c36b --- /dev/null +++ b/docs/src/generated/examples/multilevel_transmon.md @@ -0,0 +1,148 @@ +```@meta +EditURL = "../../../literate/examples/multilevel_transmon.jl" +``` + +# Multilevel Transmon + +In this example we will look at a multilevel transmon qubit with a Hamiltonian given by + +```math +\hat{H}(t) = \frac{\delta}{2} \hat{n}(\hat{n} - 1) + u_1(t) (\hat{a} + \hat{a}^\dagger) + u_2(t) i (\hat{a} - \hat{a}^\dagger) +``` +where $\hat{n} = \hat{a}^\dagger \hat{a}$ is the number operator, $\hat{a}$ is the annihilation operator, $\delta$ is the anharmonicity, and $u_1(t)$ and $u_2(t)$ are control fields. + +We will use the following parameter values: + +```math +\begin{aligned} +\delta &= 0.2 \text{ GHz}\\ +\abs{u_i(t)} &\leq 0.2 \text{ GHz}\\ +T_0 &= 10 \text{ ns}\\ +\end{aligned} +``` + +## Setting up the problem + +````@example multilevel_transmon +using QuantumCollocation +using NamedTrajectories +using LinearAlgebra + +# define the time parameters + +T₀ = 10 # total time in ns +T = 50 # number of time steps +Δt = T₀ / T # time step + +# define the number of levels to model +levels = 3 + +# create operators +n̂ = number(levels) +â = annihilate(levels) +â_dag = create(levels) + +# define the Hamiltonian +δ = 0.2 +H_drift = 2π * δ * n̂ * (n̂ - I(levels)) / 2 +H_drives = [ + 2π * (â + â_dag), + 2π * im * (â - â_dag), +] + +# define the goal unitary in the computational subspace +U_init, U_goal = subspace_unitary([levels], :X, 1) + +U_goal +```` + +Let's get the subspace indices as well as we will need them later. + +````@example multilevel_transmon +subspace = subspace_indices([levels]) + +# check that these are the correct indices (trivial in the case of a single transmon, but a useful check for more complicated systems) +U_goal[subspace, subspace] +```` + +WE also can look at U_init, which is not exactly the identity + +````@example multilevel_transmon +U_init +```` + +Now will set up the optimization problem using the [`UnitarySmoothPulseProblem`](@ref) type. + +````@example multilevel_transmon +# set the bound on the pulse amplitude + +a_bound = 2π * 0.2 + +prob = UnitarySmoothPulseProblem( + H_drift, + H_drives, + U_goal, + T, + Δt; + U_init=U_init, + subspace=subspace, + a_bound=a_bound +) + +# and we can solve this problem + +solve!(prob; max_iter=100) +```` + +and we can look at the fidelity in the subspace + +````@example multilevel_transmon +f = unitary_fidelity(prob; subspace=subspace) + +println("Fidelity: $f") +```` + +We can also look at the pulse shapes + +````@example multilevel_transmon +transformations = OrderedDict( + :Ũ⃗ => [ + x -> populations(iso_vec_to_operator(x)[:, 1]), + x -> populations(iso_vec_to_operator(x)[:, 2]), + x -> populations(iso_vec_to_operator(x)[:, 3]), + ] +) + +transforamtion_labels = OrderedDict( + :Ũ⃗ => [ + "\\psi^g", + "\\psi^e", + "\\psi^f", + ] +) + +transformation_titles = OrderedDict( + :Ũ⃗ => [ + "Populations of evolution from |0⟩", + "Populations of evolution from |1⟩", + "Populations of evolution from |2⟩", + ] +) + +plot(prob.trajectory, [:a]; + res=(1200, 1200), + transformations=transformations, + transformation_labels=transforamtion_labels, + include_transformation_labels=true, + transformation_titles=transformation_titles +) +```` + +## Leakage suppression + +As can bee seen in the plot above, although the fidelity is high, the $f$ level of the transmon is highly populated throughout the evolution. This is suboptimal, but we can account for this by penalizing the leakage elements of the unitary, namely those elements of the form $U_{f, i}$ where $i \neq f$. We utilize an $L_1$ penalty on these elements, which is implemented in the [`UnitarySmoothPulseProblem`](@ref) type as the `leakage_penalty` keyword argument. + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/docs/src/generated/examples/single_qubit.md b/docs/src/generated/examples/single_qubit.md deleted file mode 100644 index a32d0ca3..00000000 --- a/docs/src/generated/examples/single_qubit.md +++ /dev/null @@ -1,10 +0,0 @@ -```@meta -EditURL = "../../../literate/examples/single_qubit.jl" -``` - -# Single Qubit - ---- - -*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* - diff --git a/src/quantum_utils.jl b/src/quantum_utils.jl index 50e2f07c..d26099ef 100644 --- a/src/quantum_utils.jl +++ b/src/quantum_utils.jl @@ -292,6 +292,17 @@ end unitary subspace utilities """ +""" + subspace_unitary( + levels::Vector{Int}, + gate_name::Symbol, + qubit::Union{Int, Vector{Int}} + ) + +Get a unitary matrix for a gate acting on a subspace of a multilevel system. + +TODO: reimplement this as `embed_operator` with more methods. +""" function subspace_unitary( levels::Vector{Int}, gate_name::Symbol,