Source code for scqubits.core.transmon

# transmon.py
#
# This file is part of scqubits.
#
#    Copyright (c) 2019, Jens Koch and Peter Groszkowski
#    All rights reserved.
#
#    This source code is licensed under the BSD-style license found in the
#    LICENSE file in the root directory of this source tree.
############################################################################

import math
import os

import numpy as np

import scqubits.core.constants as constants
import scqubits.core.descriptors as descriptors
import scqubits.core.discretization as discretization
from scqubits.core.noise import NoisySystem
import scqubits.core.qubit_base as base
import scqubits.core.storage as storage
import scqubits.io_utils.fileio_serializers as serializers
import scqubits.utils.plot_defaults as defaults
import scqubits.utils.plotting as plot


# —Cooper pair box / transmon——————————————————————————————————————————————

[docs]class Transmon(base.QubitBaseClass1d, serializers.Serializable, NoisySystem): r"""Class for the Cooper-pair-box and transmon qubit. The Hamiltonian is represented in dense form in the number basis, :math:`H_\text{CPB}=4E_\text{C}(\hat{n}-n_g)^2+\frac{E_\text{J}}{2}(|n\rangle\langle n+1|+\text{h.c.})`. Initialize with, for example:: Transmon(EJ=1.0, EC=2.0, ng=0.2, ncut=30) Parameters ---------- EJ: float Josephson energy EC: float charging energy ng: float offset charge ncut: int charge basis cutoff, `n = -ncut, ..., ncut` truncated_dim: int, optional desired dimension of the truncated quantum system; expected: truncated_dim > 1 """ EJ = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') EC = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') ng = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') ncut = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') def __init__(self, EJ, EC, ng, ncut, truncated_dim=None): self.EJ = EJ self.EC = EC self.ng = ng self.ncut = ncut self.truncated_dim = truncated_dim self._sys_type = type(self).__name__ self._evec_dtype = np.float_ self._default_grid = discretization.Grid1d(-np.pi, np.pi, 151) self._default_n_range = (-5, 6) self._image_filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qubit_img/fixed-transmon.jpg')
[docs] @staticmethod def default_params(): return { 'EJ': 15.0, 'EC': 0.3, 'ng': 0.0, 'ncut': 30, 'truncated_dim': 10 }
[docs] def supported_noise_channels(self): """Return a list of supported noise channels""" return ['tphi_1_over_f_cc', 'tphi_1_over_f_ng', 't1_capacitive', 't1_charge_impedance', ]
[docs] def effective_noise_channels(self): """Return a default list of channels used when calculating effective t1 and t2 nosie.""" noise_channels=self.supported_noise_channels() noise_channels.remove('t1_charge_impedance') return noise_channels
[docs] def n_operator(self): """Returns charge operator `n` in the charge basis""" diag_elements = np.arange(-self.ncut, self.ncut + 1, 1) return np.diag(diag_elements)
[docs] def exp_i_phi_operator(self): """Returns operator :math:`e^{i\\varphi}` in the charge basis""" dimension = self.hilbertdim() entries = np.repeat(1.0, dimension - 1) exp_op = np.diag(entries, -1) return exp_op
[docs] def cos_phi_operator(self): """Returns operator :math:`\\cos \\varphi` in the charge basis""" cos_op = 0.5 * self.exp_i_phi_operator() cos_op += cos_op.T return cos_op
[docs] def sin_phi_operator(self): """Returns operator :math:`\\sin \\varphi` in the charge basis""" sin_op = -1j * 0.5 * self.exp_i_phi_operator() sin_op += sin_op.conjugate().T return sin_op
[docs] def hamiltonian(self): """Returns Hamiltonian in charge basis""" dimension = self.hilbertdim() hamiltonian_mat = np.diag([4.0 * self.EC * (ind - self.ncut - self.ng) ** 2 for ind in range(dimension)]) ind = np.arange(dimension - 1) hamiltonian_mat[ind, ind+1] = -self.EJ / 2.0 hamiltonian_mat[ind+1, ind] = -self.EJ / 2.0 return hamiltonian_mat
[docs] def d_hamiltonian_d_ng(self): """Returns operator representing a derivittive of the Hamiltonian with respect to charge offset `ng`.""" return -8*self.EC*self.n_operator()
[docs] def d_hamiltonian_d_EJ(self): """Returns operator representing a derivittive of the Hamiltonian with respect to EJ.""" return -self.cos_phi_operator()
[docs] def hilbertdim(self): """Returns Hilbert space dimension""" return 2 * self.ncut + 1
[docs] def potential(self, phi): """Transmon phase-basis potential evaluated at `phi`. Parameters ---------- phi: float phase variable value Returns ------- float """ return -self.EJ * np.cos(phi)
[docs] def plot_n_wavefunction(self, esys=None, mode='real', which=0, nrange=None, **kwargs): """Plots transmon wave function in charge basis Parameters ---------- esys: tuple(ndarray, ndarray), optional eigenvalues, eigenvectors mode: str from MODE_FUNC_DICT, optional `'abs_sqr', 'abs', 'real', 'imag'` which: int or tuple of ints, optional index or indices of wave functions to plot (default value = 0) nrange: tuple of two ints, optional range of `n` to be included on the x-axis (default value = (-5,6)) **kwargs: plotting parameters Returns ------- Figure, Axes """ if nrange is None: nrange = self._default_n_range n_wavefunc = self.numberbasis_wavefunction(esys, which=which) amplitude_modifier = constants.MODE_FUNC_DICT[mode] n_wavefunc.amplitudes = amplitude_modifier(n_wavefunc.amplitudes) kwargs = {**defaults.wavefunction1d_discrete(mode), **kwargs} # if any duplicates, later ones survive return plot.wavefunction1d_discrete(n_wavefunc, xlim=nrange, **kwargs)
[docs] def wavefunction1d_defaults(self, mode, evals, wavefunc_count): """Plot defaults for plotting.wavefunction1d. Parameters ---------- mode: str amplitude modifier, needed to give the correct default y label evals: ndarray eigenvalues to include in plot wavefunc_count: int """ ylabel = r'$\psi_j(\varphi)$' ylabel = constants.MODE_STR_DICT[mode](ylabel) options = { 'xlabel': r'$\varphi$', 'ylabel': ylabel } if wavefunc_count > 1: ymin = -1.05 * self.EJ ymax = max(1.1 * self.EJ, evals[-1] + 0.05 * (evals[-1] - evals[0])) options['ylim'] = (ymin, ymax) return options
[docs] def plot_phi_wavefunction(self, esys=None, which=0, phi_grid=None, mode='abs_sqr', scaling=None, **kwargs): """Alias for plot_wavefunction""" return self.plot_wavefunction(esys=esys, which=which, phi_grid=phi_grid, mode=mode, scaling=scaling, **kwargs)
[docs] def numberbasis_wavefunction(self, esys=None, which=0): """Return the transmon wave function in number basis. The specific index of the wave function to be returned is `which`. Parameters ---------- esys: ndarray, ndarray, optional if `None`, the eigensystem is calculated on the fly; otherwise, the provided eigenvalue, eigenvector arrays as obtained from `.eigensystem()`, are used (default value = None) which: int, optional eigenfunction index (default value = 0) Returns ------- WaveFunction object """ if esys is None: evals_count = max(which + 1, 3) esys = self.eigensys(evals_count) evals, evecs = esys n_vals = np.arange(-self.ncut, self.ncut + 1) return storage.WaveFunction(n_vals, evecs[:, which], evals[which])
[docs] def wavefunction(self, esys=None, which=0, phi_grid=None): """Return the transmon wave function in phase basis. The specific index of the wavefunction is `which`. `esys` can be provided, but if set to `None` then it is calculated on the fly. Parameters ---------- esys: tuple(ndarray, ndarray), optional if None, the eigensystem is calculated on the fly; otherwise, the provided eigenvalue, eigenvector arrays as obtained from `.eigensystem()` are used which: int, optional eigenfunction index (default value = 0) phi_grid: Grid1d, optional used for setting a custom grid for phi; if None use self._default_grid Returns ------- WaveFunction object """ if esys is None: evals_count = max(which + 1, 3) esys = self.eigensys(evals_count) evals, _ = esys n_wavefunc = self.numberbasis_wavefunction(esys, which=which) phi_grid = phi_grid or self._default_grid phi_basis_labels = phi_grid.make_linspace() phi_wavefunc_amplitudes = np.empty(phi_grid.pt_count, dtype=np.complex_) for k in range(phi_grid.pt_count): phi_wavefunc_amplitudes[k] = ((1j**which / math.sqrt(2 * np.pi)) * np.sum(n_wavefunc.amplitudes * np.exp(1j * phi_basis_labels[k] * n_wavefunc.basis_labels))) return storage.WaveFunction(basis_labels=phi_basis_labels, amplitudes=phi_wavefunc_amplitudes, energy=evals[which])
# — Flux-tunable Cooper pair box / transmon———————————————————————————————————————————
[docs]class TunableTransmon(Transmon, serializers.Serializable, NoisySystem): r"""Class for the flux-tunable transmon qubit. The Hamiltonian is represented in dense form in the number basis, :math:`H_\text{CPB}=4E_\text{C}(\hat{n}-n_g)^2+\frac{\mathcal{E}_\text{J}(\Phi)}{2}(|n\rangle\langle n+1|+\text{h.c.})`, Here, the effective Josephson energy is flux-tunable: :math:`\mathcal{E}_J(\Phi) = E_{J,\text{max}} \sqrt{\cos^2(\pi\Phi/\Phi_0) + d^2 \sin^2(\pi\Phi/\Phi_0)}` and :math:`d=(E_{J2}-E_{J1})(E_{J1}+E_{J2})` parametrizes th junction asymmetry. Initialize with, for example:: TunableTransmon(EJmax=1.0, d=0.1, EC=2.0, flux=0.3, ng=0.2, ncut=30) Parameters ---------- EJmax: float maximum effective Josephson energy (sum of the Josephson energies of the two junctions) d: float junction asymmetry parameter EC: float charging energy flux: float flux threading the SQUID loop, in units of the flux quantum ng: float offset charge ncut: int charge basis cutoff, `n = -ncut, ..., ncut` truncated_dim: int, optional desired dimension of the truncated quantum system; expected: truncated_dim > 1 """ EJmax = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') d = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') flux = descriptors.WatchedProperty('QUANTUMSYSTEM_UPDATE') def __init__(self, EJmax, EC, d, flux, ng, ncut, truncated_dim=None): self.EJmax = EJmax self.EC = EC self.d = d self.flux = flux self.ng = ng self.ncut = ncut self.truncated_dim = truncated_dim self._sys_type = type(self).__name__ self._evec_dtype = np.float_ self._default_grid = discretization.Grid1d(-np.pi, np.pi, 151) self._default_n_range = (-5, 6) self._image_filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'qubit_img/tunable-transmon.jpg') @property def EJ(self): """This is the effective, flux dependent Josephson energy, playing the role of EJ in the parent class `Transmon`""" return self.EJmax * np.sqrt(np.cos(np.pi * self.flux)**2 + self.d**2 * np.sin(np.pi * self.flux)**2)
[docs] @staticmethod def default_params(): return { 'EJmax': 20.0, 'EC': 0.3, 'd': 0.01, 'flux': 0.0, 'ng': 0.0, 'ncut': 30, 'truncated_dim': 10 }
[docs] def supported_noise_channels(self): """Return a list of supported noise channels""" return ['tphi_1_over_f_flux', 'tphi_1_over_f_cc', 'tphi_1_over_f_ng', 't1_capacitive', 't1_flux_bias_line', 't1_charge_impedance', ]
[docs] def d_hamiltonian_d_flux(self): """Returns operator representing a derivittive of the Hamiltonian with respect to `flux`.""" return np.pi * self.EJmax * np.cos(np.pi * self.flux) * np.sin(np.pi * self.flux) * (self.d**2 - 1) \ / np.sqrt(np.cos(np.pi * self.flux)**2 + self.d**2 * np.sin(np.pi * self.flux)**2) \ * self.cos_phi_operator()