Parametric Driving

scqubits can simplify simulations of dynamics using qutip, by automating the building of the dynamical Hamiltonians that can be directly subsituted to qutip’s solver routines such as mesolve, mcsolve, etc.

[1]:
import matplotlib.pyplot as plt
import numpy as np
import qutip as q
import scqubits as scq

Let us define a circuit and look at its spectrum

[2]:
# Let's start by defining a fluxonium
inp_yaml = """
branches:
- [JJ, 1, 2, 4, 0.5]
- [L, 1, 2, 1.3]
- [C, 1, 2, 2]
"""
circ = scq.Circuit(inp_yaml, from_file=False, use_dynamic_flux_grouping=True, ext_basis="discretized")
circ.cutoff_ext_1 = 100
circ.Φ1 = 0.5
[3]:
eigs = circ.eigenvals()
eigs - eigs[0]
[3]:
array([0.        , 0.1734474 , 2.36189081, 3.65328642, 5.59769016,
       7.63584593])
[12]:
circ.plot_evals_vs_paramvals("Φ1", np.linspace(0, 1, 81), subtract_ground=True);
../../../_images/guide_circuit_ipynb_custom_circuit_parametric_driving_5_1.svg

Rabi oscillations

And study Rabi oscillations generated by modulating the flux

[13]:
# defining Hierarchical diagonalization to limit to the lowest two states
circ.configure(system_hierarchy=[[1]], subsystem_trunc_dims=[10])

# specific form of the flux modulation
def flux_ac(t, args):
        freq = args["freq"]
        return 0.001*np.sin(2*np.pi*freq*t)

# asking the method to return Hamiltonian when Φ1 is varied around the bias point
# H_mesolve is the Hamiltonian to be used in the Qutip method mesolve
H_mesolve, *H_sym_ref = circ.hamiltonian_for_qutip_dynamics(free_var_func_dict={"Φ1": flux_ac}, prefactor=np.pi*2)

eigs, evecs = circ.eigensys(evals_count=5)
wf0 = q.Qobj(evecs[:, 0]) # ground state as initial state

initial_state_proj = wf0 * wf0.dag() # to see the overlap
tf = 100 # final time in nanoseconds
freq = eigs[1] - eigs[0] # transition frequency between the first two states

# evolve the system
result = q.mesolve(H_mesolve, wf0, np.linspace(0, tf, 500), args={"freq": freq}, e_ops=[initial_state_proj])
[14]:
fig, ax=plt.subplots()
ax.plot(result.times, result.expect[0])
ax.set_xlabel('Time (ns)')
ax.set_ylabel('Ground state population')
[14]:
Text(0, 0.5, 'Ground state population')
../../../_images/guide_circuit_ipynb_custom_circuit_parametric_driving_8_1.svg

Note that the steps like features over the sinusiod in the above plot, is because we have not used RWA in the drive Hamiltonian.

Example Floquet Simulation

We can also explore a Floquet simulation

[4]:
# defining Hierarchical diagonalization to limit to the lowest two states
circ.configure(system_hierarchy=[[1]], subsystem_trunc_dims=[2])

all_eigs = []
flux_vals = np.linspace(0.48, 0.58, 61)
for flux in flux_vals:

    circ.Φ1 = flux
    eigs, evecs = circ.eigensys(evals_count=2)
    psi0 = q.Qobj(evecs[:, 0])
    freq = 510e-3

    def flux_ac(t, args):
        freq = args["freq"]
        return 0.028*np.cos(2*np.pi*freq*t)

    t = np.linspace(0, 1/freq, 300)
    H_mesolve, *b = circ.hamiltonian_for_qutip_dynamics(free_var_func_dict={"Φ1": flux_ac}, prefactor=np.pi*2)
    # calculating the floquet energies
    res = q.propagator(H_mesolve, t=t, args={"freq": freq})
    u_eigs = res[-1].eigenenergies()
    floquet_eigs = np.arctan2(np.real(u_eigs), np.imag(u_eigs))
    floquet_eigs.sort()
    floquet_eigs = floquet_eigs * freq / (2*np.pi)
    all_eigs.append(floquet_eigs)

[5]:
fig, ax=plt.subplots()
plt.plot(flux_vals, all_eigs)
ax.set_xlabel(r'External flux ($\Phi_0$)')
ax.set_ylabel('Floquet energies (GHz)')
[5]:
Text(0, 0.5, 'Floquet energies (GHz)')
../../../_images/guide_circuit_ipynb_custom_circuit_parametric_driving_12_1.svg