import numpy as np
from qutip import qeye, tensor, destroy, basis
from .modelprocessor import ModelProcessor
from ..transpiler import to_chain_structure
from ..compiler import SCQubitsCompiler
from ..noise import ZZCrossTalk
__all__ = ['SCQubits']
[docs]class SCQubits(ModelProcessor):
"""
A chain of superconducting qubits with fixed frequency.
Single-qubit control is realized by rotation around the X and Y axis
while two-qubit gates are implemented with Cross Resonance gates.
A 3-level system is used to simulate the superconducting qubit system,
in order to simulation leakage.
Various types of interaction can be realized on a superconducting
system, as a demonstration and
for simplicity, we only use a ZX Hamiltonian for
the two-qubit interaction.
For details see https://arxiv.org/abs/2005.12667 and
https://journals.aps.org/pra/abstract/10.1103/PhysRevA.101.052308.
Parameters
----------
num_qubits: int
Number of qubits
t1, t2: float or list, optional
Coherence time for all qubit or each qubit
zz_crosstalk: bool, optional
if zz cross talk is included.
**params:
Keyword argument for hardware parameters, in the unit of GHz.
Each can should be given as list:
- ``wq``: qubit bare frequency, default 5.15 and 5.09
for each pair of superconducting qubits,
e.g. ``[5.15, 5.09, 5.15, ...]``
- ``wr``: resonator bare frequency, default ``[5.96]*num_qubits``
- ``g``: The coupling strength between the resonator and the qubits,
default ``[0.1]*(num_qubits - 1)``
- ``alpha``: anharmonicity for each superconducting qubit,
default ``[-0.3]*num_qubits``
- ``omega_single``: control strength for single-qubit gate,
default ``[0.01]*num_qubits``
- ``omega_cr``: control strength for cross resonance gate,
default ``[0.01]*num_qubits``
Attributes
----------
dims: list
Dimension of the subsystem, e.g. ``[3,3,3]``.
pulse_mode: "discrete" or "continuous"
Given pulse is treated as continuous pulses or discrete step functions.
native_gates: list of str
The native gate sets
"""
def __init__(
self, num_qubits, t1=None, t2=None, zz_crosstalk=False, **params):
super(SCQubits, self).__init__(
num_qubits, t1=t1, t2=t2)
self.num_qubits = num_qubits
self.dims = [3] * num_qubits
self.pulse_mode = "continuous"
self.params = {
"wq": np.array(
(
(5.15, 5.09) * int(np.ceil(self.num_qubits / 2))
)[: self.num_qubits]
),
"wr": self.to_array(5.96, num_qubits - 1),
"alpha": self.to_array(-0.3, num_qubits),
"g": self.to_array(0.1, 2 * (num_qubits - 1)),
"omega_single": self.to_array(0.01, num_qubits),
"omega_cr": self.to_array(0.01, num_qubits)
}
if params is not None:
self.params.update(params)
self.set_up_ops()
self.set_up_params()
self.native_gates = ["RX", "RY", "CNOT"]
self._default_compiler = SCQubitsCompiler
if zz_crosstalk:
self.add_noise(ZZCrossTalk(self.params))
[docs] def set_up_ops(self):
"""
Setup the operators.
We use 2π σ/2 as the single-qubit control Hamiltonian and
-2πZX/4 as the two-qubit Hamiltonian.
"""
for m in range(self.num_qubits):
destroy_op = destroy(self.dims[m])
coeff = 2 * np.pi * self.params["alpha"][m] / 2.
self.add_drift(
coeff * destroy_op.dag() ** 2 * destroy_op ** 2, targets=[m])
for m in range(self.num_qubits):
destroy_op = destroy(self.dims[m])
op = destroy_op + destroy_op.dag()
self.add_control(2 * np.pi / 2 * op, [m], label="sx" + str(m))
for m in range(self.num_qubits):
destroy_op = destroy(self.dims[m])
op = destroy_op * (-1.j) + destroy_op.dag() * 1.j
self.add_control(2 * np.pi / 2 * op, [m], label="sy" + str(m))
for m in range(self.num_qubits - 1):
# For simplicity, we neglect leakage in two-qubit gates.
d1 = self.dims[m]
d2 = self.dims[m+1]
# projector to the 0 and 1 subspace
projector1 = basis(d1, 0) * basis(d1, 0).dag() + \
basis(d1, 1) * basis(d2, 1).dag()
projector2 = basis(d2, 0) * basis(d2, 0).dag() + \
basis(d2, 1) * basis(d2, 1).dag()
destroy_op1 = destroy(d1)
# Notice that this is actually 2πZX/4
z = projector1 * \
(- destroy_op1.dag()*destroy_op1 * 2 + qeye(d1)) / 2 \
* projector1
destroy_op2 = destroy(d2)
x = projector2 * (destroy_op2.dag() + destroy_op2) / 2 * projector2
self.add_control(
2 * np.pi * tensor([z, x]), [m, m+1],
label="zx" + str(m) + str(m + 1)
)
self.add_control(
2 * np.pi * tensor([x, z]), [m, m+1],
label="zx" + str(m + 1) + str(m)
)
[docs] def set_up_params(self):
"""
Compute the dressed frequency and the interaction strength.
"""
g = self.params["g"]
wq = self.params["wq"]
wr = self.params["wr"]
alpha = self.params["alpha"]
# Dressed qubit frequency
wq_dr = []
for i in range(self.num_qubits):
tmp = wq[i]
if i != 0:
tmp += g[2*i - 1]**2/(wq[i] - wr[i - 1])
if i != (self.num_qubits - 1):
tmp += g[2*i]**2/(wq[i] - wr[i])
wq_dr.append(tmp)
self.params["wq_dressed"] = wq_dr
# Dressed resonator frequency
wr_dr = []
for i in range(self.num_qubits - 1):
tmp = wr[i]
tmp -= g[2*i]**2/(wq[i] - wr[i] + alpha[i])
tmp -= g[2*i + 1]**2/(wq[i+1] - wr[i] + alpha[i])
wr_dr.append(tmp)
self.params["wr_dressed"] = wr_dr
# Effective qubit coupling strength
J = []
for i in range(self.num_qubits - 1):
tmp = g[2*i] * g[2*i+1] * \
(wq[i] + wq[i+1] - 2*wr[i]) / \
2 / (wq[i] - wr[i]) / (wq[i+1] - wr[i])
J.append(tmp)
self.params["J"] = J
# Effective ZX strength
zx_coeff = []
omega_cr = self.params["omega_cr"]
for i in range(self.num_qubits - 1):
tmp = J[i] * omega_cr[i] * (
1/(wq[i] - wq[i+1] + alpha[i]) -
1/(wq[i] - wq[i+1])
)
zx_coeff.append(tmp)
for i in range(self.num_qubits - 1, 0, -1):
tmp = J[i-1] * omega_cr[i] * (
1/(wq[i] - wq[i-1] + alpha[i]) -
1/(wq[i] - wq[i-1])
)
zx_coeff.append(tmp)
# Times 2 because we use -2πZX/4 as operators
self.params["zx_coeff"] = np.asarray(zx_coeff) * 2
[docs] def get_operators_labels(self):
"""
Get the labels for each Hamiltonian.
It is used in the method method :meth:`.Processor.plot_pulses`.
It is a 2-d nested list, in the plot,
a different color will be used for each sublist.
"""
labels = [[r"$\sigma_x^%d$" % n for n in range(self.num_qubits)],
[r"$\sigma_y^%d$" % n for n in range(self.num_qubits)]]
label_zx = []
for m in range(self.num_qubits - 1):
label_zx.append(r"$ZX^{%d%d}$"% (m, m + 1))
label_zx.append(r"$ZX^{%d%d}$"% (m + 1, m))
labels.append(label_zx)
return labels
[docs] def topology_map(self, qc):
return to_chain_structure(qc)