-
Notifications
You must be signed in to change notification settings - Fork 32
Closed
Description
Describe the Issue!
Hi, I have been trying to translate my QuTiP script into QuantumToolbox.jl to see whether I could benefit from the expected speedup. However, I have been stuck at some weird issue where simple mesolve (time-independent Hamiltonian) is very slow (or fails). I was wondering if you have any insights on what is causing this?
Python Script (QuTiP):
from qutip import *
import numpy as np
from tqdm.notebook import tqdm
from matplotlib import pyplot as plt
import matplotlib.cm as cm
plt.style.use('default')
class TransmonROParams:
"""Abstract class to calculate theoretically predicted parameters for dispersive readout of transmon qubits """
def __init__(self, omega_q, alpha, omega_r, g, kappa):
self.omega_q = omega_q
self.omega_r = omega_r
self.g = g
self.kappa = kappa
self.alpha = alpha
@property
def delta_qr(self):
"""Qubit-Resonator Detuning"""
return self.omega_q - self.omega_r
@property
def resonator_ringdown_time(self):
"""Resonator Ringdown Time"""
return 1 / self.kappa
@property
def omega_r_dressed(self):
"""Dressed Resonator Frequency"""
return self.omega_r - self.g ** 2 / (self.delta_qr + self.alpha)
@property
def omega_q_dressed(self):
"""Dressed Qubit Frequency"""
return self.omega_q + self.g ** 2 / self.delta_qr
@property
def chi(self):
"""Dispersive shift"""
return self.g ** 2 * self.alpha / (self.delta_qr * (self.delta_qr + self.alpha))
@property
def n_crit(self):
"""Critical Photon Number"""
return self.delta_qr ** 2 / (4 * self.g ** 2)
@property
def gamma1_purcell(self):
"""Purcell Decay Rate"""
return (self.g / self.delta_qr) ** 2 * self.kappa
@property
def t1_purcell(self):
"""Purcell-limited Lifetime"""
return 1 / self.gamma1_purcell
def alpha0_ss(self, omega_d, epsilon):
"""
Steady-state resonator field amplitude corresponding to the qubit ground state,
given an external drive with frequency `omega_d` and constant amplitude `epsilon`.
"""
delta_r = self.omega_r_dressed - omega_d
return - epsilon / (delta_r - self.chi - 1j * self.kappa / 2)
def alpha1_ss(self, omega_d, epsilon):
"""
Steady-state resonator field amplitude corresponding to the qubit excited state,
given an external drive with frequency `omega_d` and constant amplitude `epsilon`.
"""
delta_r = self.omega_r_dressed - omega_d
return - epsilon / (delta_r + self.chi - 1j * self.kappa / 2)
# parameters
omega_q = 5 * (2 *np.pi) # qubit transition frequency
omega_r = 7 * (2 * np.pi) # readout resonator frequency
g = 0.15 * (2 * np.pi) # qubit-resonator coupling
alpha = -0.2 * (2 * np.pi) # anharmonicity
kappa = 2.046e-3 * (2 * np.pi) # resonator decay rate
Delta = omega_q - omega_r # qubit resonator detuning
# Create a transmon ro params instance
ro_params = TransmonROParams(omega_q, alpha, omega_r, g, kappa)
print(f'Resonator ringdown time: {ro_params.resonator_ringdown_time:.2f} ns')
# H space dimension
N_q, N_r = 4, 10 # Hilbert space dimensions (qubit, resonator)
# operators
a = tensor(qeye(N_q), destroy(N_r)) # resonator annihilation operator
b = tensor(destroy(N_q), qeye(N_r)) # transmon annihilation operator
# Hamiltonians (time-independent)
H_q = omega_q * b.dag() * b + 0.5 * alpha * b.dag() * b.dag() * b * b # bare transmon Hamiltonian
H_r = omega_r * a.dag() * a # bare resonator Hamiltonian
#H_c = g * (a + a.dag()) * (b + b.dag()) # quantum Rabi model Hamiltonian (takes longer time to simulate)
H_c = g * (a * b.dag() + a.dag() * b) # Jaynes-Cummings model Hamiltonian (faster to simulate)
# Total H
H = H_q + H_r + H_c
print(f"Dressed Qubit Freq. (Analytical): {ro_params.omega_q_dressed / (2 * np.pi):.4f} GHz")
print(f"Dressed Resonator Freq. (Analytical): {ro_params.omega_r_dressed / (2 * np.pi):.4f} GHz")
print(f'Dispersive shift (Analytical): {ro_params.chi / (2 * np.pi) / 0.001:.3f} MHz')
def dressed_state(s):
"""
Return the eigenenergy and eigenstate of the Hamiltonian that has the largest overlap with the state `s`.
"""
eig_energies, eig_states = H.eigenstates()
overlaps = np.array([np.abs(s.overlap(ds)) for ds in eig_states])
s_ind = np.argmax(overlaps)
return eig_energies[s_ind], eig_states[s_ind]
E_g0, _ = dressed_state(tensor(basis(N_q, 0), tensor(basis(N_r, 0))))
E_g1, _ = dressed_state(tensor(basis(N_q, 0), tensor(basis(N_r, 1))))
E_e0, _ = dressed_state(tensor(basis(N_q, 1), tensor(basis(N_r, 0))))
E_e1, _ = dressed_state(tensor(basis(N_q, 1), tensor(basis(N_r, 1))))
omega_q_dressed_numerical = (E_e0 - E_g0) # qubit frequency when the resonator is not occupied
omega_r_g_numerical = (E_g1 - E_g0) # readout resonator frequency when qubit is in the ground state
omega_r_e_numerical = (E_e1 - E_e0) # readout resonator frequency when qubit is in the excited state
print(f'Dressed Qubit Freq. (Numerical): {omega_q_dressed_numerical / (2 * np.pi):.4f} GHz"')
print(f'Resonator Freq. w/ Qubit in |g> (Numerical) = {omega_r_g_numerical/ (2 * np.pi):.4f} GHz')
print(f'Resonator Freq. w/ Qubit in |e> (Numerical) = {omega_r_e_numerical/ (2 * np.pi):.4f} GHz')
print(f'Dressed Resonator Freq. (Numerical): {0.5 * (omega_r_g_numerical + omega_r_e_numerical) / (2 * np.pi):.4f} GHz')
print(f'Dispersive Shift (Numerical) = {(omega_r_e_numerical - omega_r_g_numerical) / 2e-3 / (2 * np.pi):.4f} MHz')
print(f'Purcell-limited Lifetime (Analytical) = {ro_params.t1_purcell * 1e-3:.3f} us')
print(f'Purcell Decay Rate (Analytical) = {ro_params.gamma1_purcell / (2 * np.pi * 1e-6) :.3f} kHz' )
# Find dressed qubit excited state and the ground state
_, psi_e0_dressed = dressed_state(tensor(basis(N_q, 1), basis(N_r, 0)))
_, psi_g0_dressed = dressed_state(tensor(basis(N_q, 0), basis(N_r, 0)))
# Projectors onto the dressed qubit excited state and the ground state
proj_e0_dressed = psi_e0_dressed * psi_e0_dressed.dag()
proj_g0_dressed = psi_g0_dressed * psi_g0_dressed.dag()
tlist0 = np.arange(0, 50000, 50) # list of times for time evolution, in units of ns
result0 = mesolve(
[H], psi_e0_dressed, tlist0, c_ops=[np.sqrt(kappa) * a], # no collapse operator for qubit
e_ops=[proj_g0_dressed, proj_e0_dressed], options = {"progress_bar": 'tqdm', 'nsteps': 100000}
)
This QuTiP script takes about 1 min to execute on my desktop.
Julia Script (QuantumToolbox.jl):
using QuantumToolbox, CUDA
using CairoMakie, Printf
import DifferentialEquations: AutoTsit5, Rosenbrock23, AutoVern7, Rodas5, QNDF, CVODE_BDF, JVODE_Adams
import QuantumToolbox:mesolve
using LSODA
# struct to
struct TransmonROParams{T}
omega_q::T
anharmonicity::T
omega_r::T
g::T
kappa::T
end
delta_qr(p::TransmonROParams) = p.omega_q - p.omega_r
resonator_ringdown_time(p::TransmonROParams) = 1 / p.kappa
omega_r_dressed(p::TransmonROParams) = p.omega_r - p.g ^ 2 / (delta_qr(p) + p.anharmonicity)
omega_q_dressed(p::TransmonROParams) = p.omega_q + p.g ^ 2 / delta_qr(p)
chi(p::TransmonROParams) = p.g ^ 2 * p.anharmonicity / (delta_qr(p) * (delta_qr(p) + p.anharmonicity))
n_crit(p::TransmonROParams) = delta_qr(p) ^ 2 / (4 * p.g ^ 2)
gamma1_purcell(p::TransmonROParams) = (p.g / delta_qr(p)) ^ 2 * p.kappa
t1_purcell(p::TransmonROParams) = 1 / gamma1_purcell(p)
alpha0_ss(p::TransmonROParams, omega_d::Float64, epsilon::Float64) = begin
delta_r = omega_q_dressed(p) - omega_d
- epsilon / (delta_r - chi(p) - im * p.kappa / 2)
end
alpha1_ss(p::TransmonROParams, omega_d::Float64, epsilon::Float64) = begin
delta_r = omega_q_dressed(p) - omega_d
- epsilon / (delta_r + chi(p) - im * p.kappa / 2)
end
# parameters
omega_q = 5 * 2π # qubit transition frequency
omega_r = 7 * 2π # readout resonator frequency
g = 0.15 * 2π # qubit-resonator coupling
alpha = -0.2 * 2π # anharmonicity
kappa = 2.046e-3 * 2π # resonator decay rate
# Create a transmon ro params instance
ro_params = TransmonROParams(omega_q, alpha, omega_r, g, kappa)
@printf "Resonator ringdown time: %.2f ns" resonator_ringdown_time(ro_params)
# H space dimension
N_q, N_r = 4, 10 # Hilbert space dimensions (qubit, resonator)
# operators
a = tensor(qeye(N_q), destroy(N_r)) # resonator annihilation operator
b = tensor(destroy(N_q), qeye(N_r)) # transmon annihilation operator
# Hamiltonians (time-independent)
H_q = omega_q * b' * b + 0.5 * alpha * b' * b' * b * b # bare transmon Hamiltonian
H_r = omega_r * a' * a # bare resonator Hamiltonian
#H_c = g * (a + a.dag()) * (b + b.dag()) # quantum Rabi model Hamiltonian (takes longer time to simulate)
H_c = g * (a * b' + a' * b) # Jaynes-Cummings model Hamiltonian (faster to simulate)
# Total H
H = H_q + H_r + H_c
@printf("Dressed Qubit Freq. (Analytical): %.4f GHz\n", omega_q_dressed(ro_params) / 2π)
@printf("Dressed Resonator Freq. (Analytical): %.4f GHz\n", omega_r_dressed(ro_params) / 2π)
@printf("Dispersive shift (Analytical): %.3f MHz\n", chi(ro_params) / 2π / 0.001)
r = eigenstates(H)
function dressed_state(s::QuantumObject)
"""
Return the eigenenergy and eigenstate of the Hamiltonian that has the largest overlap with the state `s`.
"""
eig_energies, eig_states = eigenstates(H)
overlaps = [abs(fidelity(s, ds)) for ds in eig_states]
s_ind = argmax(overlaps)
eig_energies[s_ind], eig_states[s_ind]
end
E_g0, _ = dressed_state(tensor(basis(N_q, 0), tensor(basis(N_r, 0))))
E_g1, _ = dressed_state(tensor(basis(N_q, 0), tensor(basis(N_r, 1))))
E_e0, _ = dressed_state(tensor(basis(N_q, 1), tensor(basis(N_r, 0))))
E_e1, _ = dressed_state(tensor(basis(N_q, 1), tensor(basis(N_r, 1))))
omega_q_dressed_numerical = (E_e0 - E_g0) # qubit frequency when the resonator is not occupied
omega_r_g_numerical = (E_g1 - E_g0) # readout resonator frequency when qubit is in the ground state
omega_r_e_numerical = (E_e1 - E_e0) # readout resonator frequency when qubit is in the excited state
@printf("Dressed Qubit Freq. (Numerical): %.4f GHz\n", omega_q_dressed_numerical / 2π)
@printf("Resonator Freq. w/ Qubit in |g> (Numerical): %.4f GHz\n", omega_r_g_numerical / 2π)
@printf("Resonator Freq. w/ Qubit in |e> (Numerical): %.4f GHz\n", omega_r_e_numerical / 2π)
@printf("Dressed Resonator Freq. (Numerical): %.4f GHz\n", 0.5 * (omega_r_g_numerical + omega_r_e_numerical) / 2π)
@printf("Dispersive Shift (Numerical): %.4f MHz\n", (omega_r_e_numerical - omega_r_g_numerical) / 2e-3 / 2π)
@printf("Purcell-limited Lifetime (Analytical): %.3f μs\n", t1_purcell(ro_params) * 1e-3)
@printf("Purcell Decay Rate (Analytical): %.3f kHz\n", gamma1_purcell(ro_params) / (2π * 1e-6))
# Find dressed qubit excited state and the ground state
_, psi_e0_dressed = dressed_state(tensor(basis(N_q, 1), basis(N_r, 0)))
_, psi_g0_dressed = dressed_state(tensor(basis(N_q, 0), basis(N_r, 0)))
# Projectors onto the dressed qubit excited state and the ground state
proj_e0_dressed = psi_e0_dressed * psi_e0_dressed'
proj_g0_dressed = psi_g0_dressed * psi_g0_dressed'
tlist0 = collect(StepRange(0, 50, 50000)) # list of times for time evolution, in units of ns
# cannot run
result0 = mesolve(
H, psi_e0_dressed, tlist0, [sqrt(kappa) * a], # no collapse operator for qubit
e_ops=[proj_g0_dressed, proj_e0_dressed]
)
- My observation is that increasing
maxitersin the solver option does help in reducing the failure, but still the execution takes infinite amount of time. Also, reducing the range of sweep to sth like StepRange(0, 50, 5000) runs ok. This seems to be related to ODE integration error building up. - Changing the
algoption didn't seem to help much. Also, I noticed that options likelsodaimported fromLSODA.jlcannot be handled by the mesolver due to type mismatch. In the case of QuTiP where things worked fine, I was using the defaultadamsintegrator with order 12.
Metadata
Metadata
Assignees
Labels
No labels