Skip to content

Commit

Permalink
Add RZX gate to the SCQubits model (qutip#245)
Browse files Browse the repository at this point in the history
- Add the RZX gate for the SCQubits. It was the building block for CNOT but is now separated to generate Hamiltonian simulation more conveniently.
- Fix a bug in the gate decomposition, if the gate is added as a native gate, the resolve function lets it pass without an error.
- Fix a bug in the compilation of the ZX Hamiltonian. The wrong hardware parameters were used for the compilation (mismatch in the qubit label.
  • Loading branch information
BoxiLi committed Sep 1, 2024
1 parent b280f7c commit 4080b81
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 24 deletions.
3 changes: 3 additions & 0 deletions src/qutip_qip/circuit/_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def _gate_SNOT(gate, temp_resolved):
)


_gate_H = _gate_SNOT


def _gate_PHASEGATE(gate, temp_resolved):
temp_resolved.append(
Gate(
Expand Down
13 changes: 7 additions & 6 deletions src/qutip_qip/circuit/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import inspect
import os
from functools import partialmethod
from typing import Optional, Union, Tuple, List, Dict, Any

import numpy as np
from copy import deepcopy
Expand Down Expand Up @@ -596,9 +597,6 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
basis_1q.append(gate)
else:
pass
raise NotImplementedError(
"%s is not a valid basis gate" % gate
)
if len(basis_1q) == 1:
raise ValueError("Not sufficient single-qubit gates in basis")
if len(basis_1q) == 0:
Expand All @@ -621,9 +619,12 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
)
try:
_resolve_to_universal(gate, temp_resolved, basis_1q, basis_2q)
except AttributeError:
exception = f"Gate {gate.name} cannot be resolved."
raise NotImplementedError(exception)
except KeyError:
if gate.name in basis:
temp_resolved.append(gate)
else:
exception = f"Gate {gate.name} cannot be resolved."
raise NotImplementedError(exception)

match = False
for basis_unit in ["CSIGN", "ISWAP", "SQRTSWAP", "SQRTISWAP"]:
Expand Down
59 changes: 51 additions & 8 deletions src/qutip_qip/compiler/circuitqedcompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self, num_qubits, params):
"RY": self.ry_compiler,
"RX": self.rx_compiler,
"CNOT": self.cnot_compiler,
"RZX": self.rzx_compiler,
}
)
self.args = { # Default configuration
Expand Down Expand Up @@ -130,10 +131,22 @@ def _rotation_compiler(self, gate, op_label, param_label, args):
maximum=self.params[param_label][targets[0]],
area=gate.arg_value / 2.0 / np.pi,
)
f = 2 * np.pi * self.params["wq"][targets[0]]
if args["DRAG"]:
pulse_info = self._drag_pulse(op_label, coeff, tlist, targets[0])
elif op_label == "sx":
pulse_info = [
("sx" + str(targets[0]), coeff),
# Add zero here just to make it easier to add the driving frequency later.
("sy" + str(targets[0]), np.zeros(len(coeff))),
]
elif op_label == "sy":
pulse_info = [
("sx" + str(targets[0]), np.zeros(len(coeff))),
("sy" + str(targets[0]), coeff),
]
else:
pulse_info = [(op_label + str(targets[0]), coeff)]
raise RuntimeError("Unknown label.")
return [Instruction(gate, tlist, pulse_info)]

def _drag_pulse(self, op_label, coeff, tlist, target):
Expand Down Expand Up @@ -198,6 +211,41 @@ def rx_compiler(self, gate, args):
"""
return self._rotation_compiler(gate, "sx", "omega_single", args)

def rzx_compiler(self, gate, args):
"""
Cross-Resonance RZX rotation, building block for the CNOT gate.
Parameters
----------
gate : :obj:`~.operations.Gate`:
The quantum gate to be compiled.
args : dict
The compilation configuration defined in the attributes
:obj:`.GateCompiler.args` or given as a parameter in
:obj:`.GateCompiler.compile`.
Returns
-------
A list of :obj:`.Instruction`, including the compiled pulse
information for this gate.
"""
result = []
q1, q2 = gate.targets
if q1 < q2:
zx_coeff = self.params["zx_coeff"][2 * q1]
else:
zx_coeff = self.params["zx_coeff"][2 * q1 - 1]
area = 0.5
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"], maximum=zx_coeff, area=area
)
area_rescale_factor = np.sqrt(np.abs(gate.arg_value) / (np.pi / 2))
tlist *= area_rescale_factor
coeff *= area_rescale_factor
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
result += [Instruction(gate, tlist, pulse_info)]
return result

def cnot_compiler(self, gate, args):
"""
Compiler for CNOT gate using the cross resonance iteraction.
Expand Down Expand Up @@ -226,13 +274,8 @@ def cnot_compiler(self, gate, args):
gate1 = Gate("RX", q2, arg_value=-np.pi / 2)
result += self.gate_compiler[gate1.name](gate1, args)

zx_coeff = self.params["zx_coeff"][q1]
area = 1 / 2
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"], maximum=zx_coeff, area=area
)
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
result += [Instruction(gate, tlist, pulse_info)]
gate2 = Gate("RZX", targets=[q1, q2], arg_value=np.pi / 2)
result += self.rzx_compiler(gate2, args)

gate3 = Gate("RX", q1, arg_value=-np.pi / 2)
result += self.gate_compiler[gate3.name](gate3, args)
Expand Down
11 changes: 5 additions & 6 deletions src/qutip_qip/device/circuitqed.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(self, num_qubits, dims=None, zz_crosstalk=False, **params):
**params,
)
super(SCQubits, self).__init__(model=model)
self.native_gates = ["RX", "RY", "CNOT"]
self.native_gates = ["RX", "RY", "CNOT", "RZX"]
self._default_compiler = SCQubitsCompiler
self.pulse_mode = "continuous"

Expand Down Expand Up @@ -318,13 +318,12 @@ def _compute_params(self):
)
)
zx_coeff.append(tmp)
for i in range(num_qubits - 1, 0, -1):
tmp = (
J[i - 1]
* omega_cr[i]
J[i]
* omega_cr[i + 1]
* (
1 / (wq[i] - wq[i - 1] + alpha[i])
- 1 / (wq[i] - wq[i - 1])
1 / (wq[i + 1] - wq[i] + alpha[i + 1])
- 1 / (wq[i + 1] - wq[i])
)
)
zx_coeff.append(tmp)
Expand Down
51 changes: 51 additions & 0 deletions src/qutip_qip/operations/gateclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"CS",
"CT",
"CPHASE",
"RZX",
]

"""
Expand Down Expand Up @@ -1153,6 +1154,55 @@ def get_compact_qobj(self):
return cphase(self.arg_value).tidyup()


class RZX(TwoQubitGate):
r"""
RZX gate.
.. math::
\begin{pmatrix}
\cos{\theta/2} & -i\sin{\theta/2} & 0 & 0 \\
-i\sin{\theta/2} & \cos{\theta/2} & 0 & 0 \\
0 & 0 & \cos{\theta/2} & i\sin{\theta/2} \\
0 & 0 & i\sin{\theta/2} & \cos{\theta/2} \\
\end{pmatrix}
Examples
--------
>>> from qutip_qip.operations import RZX
>>> RZX([0, 1], np.pi).get_compact_qobj().tidyup() # doctest: +NORMALIZE_WHITESPACE
Quantum object: dims=[[2, 2], [2, 2]], shape=(4, 4), type='oper', dtype=Dense, isherm=False
Qobj data =
[[0.+0.j 0.-1.j 0.+0.j 0.+0.j]
[0.-1.j 0.+0.j 0.+0.j 0.+0.j]
[0.+0.j 0.+0.j 0.+0.j 0.+1.j]
[0.+0.j 0.+0.j 0.+1.j 0.+0.j]]
"""

def __init__(self, targets, arg_value, **kwargs):
self.target_gate = RZ
super().__init__(
targets=targets,
arg_value=arg_value,
target_gate=self.target_gate,
**kwargs,
)

def get_compact_qobj(self):
theta = self.arg_value
return Qobj(
np.array(
[
[np.cos(theta / 2), -1.0j * np.sin(theta / 2), 0.0, 0.0],
[-1.0j * np.sin(theta / 2), np.cos(theta / 2), 0.0, 0.0],
[0.0, 0.0, np.cos(theta / 2), 1.0j * np.sin(theta / 2)],
[0.0, 0.0, 1.0j * np.sin(theta / 2), np.cos(theta / 2)],
]
),
dims=[[2, 2], [2, 2]],
)


CRY = partial(_OneControlledGate, target_gate=RY)
CRY.__doc__ = "Controlled Y rotation."
CRX = partial(_OneControlledGate, target_gate=RX)
Expand Down Expand Up @@ -1205,4 +1255,5 @@ def get_compact_qobj(self):
"CS": CS,
"CT": CT,
"CPHASE": CPHASE,
"RZX": RZX,
}
19 changes: 16 additions & 3 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import qutip
from qutip_qip.circuit import QubitCircuit
from qutip_qip.operations import Gate, gate_sequence_product
from qutip_qip.operations import Gate, gate_sequence_product, RZX
from qutip_qip.device import (DispersiveCavityQED, LinearSpinChain,
CircularSpinChain, SCQubits)

Expand Down Expand Up @@ -101,8 +101,21 @@ def test_analytical_evolution(num_qubits, gates, device_class, kwargs):
@pytest.mark.filterwarnings("ignore:Not in the dispersive regime")
@pytest.mark.parametrize(("num_qubits", "gates"), single_gate_tests)
@pytest.mark.parametrize(("device_class", "kwargs"), device_lists_numeric)
def test_numerical_evolution(
num_qubits, gates, device_class, kwargs):
def test_numerical_evolution(num_qubits, gates, device_class, kwargs):
_test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)


# Test for RZX gate, only available on SCQubits.
_rzx = RZX([0, 1], arg_value=np.pi/2)
@pytest.mark.parametrize(
("num_qubits", "gates", "device_class", "kwargs"),
[pytest.param(2, [_rzx], SCQubits, {}, id="RZX-SCQubits")]
)
def test_numerical_evolution_zx(num_qubits, gates, device_class, kwargs):
_test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)


def _test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs):
num_qubits = 2
circuit = QubitCircuit(num_qubits)
for gate in gates:
Expand Down
4 changes: 3 additions & 1 deletion tests/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from qutip_qip.operations import (
X, Y, Z, RX, RY, RZ, H, SQRTNOT, S, T, QASMU, CNOT, CPHASE, ISWAP, SWAP,
CZ, SQRTSWAP, SQRTISWAP, SWAPALPHA, SWAPALPHA, MS, TOFFOLI, FREDKIN,
BERKELEY, R, expand_operator)
BERKELEY, R, RZX, expand_operator)


def _permutation_id(permutation):
Expand Down Expand Up @@ -382,6 +382,7 @@ def test_gates_class():
circuit1.add_gate("TOFFOLI", [2, 0, 1])
circuit1.add_gate("FREDKIN", [0, 1, 2])
circuit1.add_gate("BERKELEY", [1, 0])
circuit1.add_gate("RZX", [1, 0], arg_value=1.)
result1 = circuit1.run(init_state)

circuit2 = QubitCircuit(3)
Expand Down Expand Up @@ -409,6 +410,7 @@ def test_gates_class():
circuit2.add_gate(TOFFOLI([2, 0, 1]))
circuit2.add_gate(FREDKIN([0, 1, 2]))
circuit2.add_gate(BERKELEY([1, 0]))
circuit2.add_gate(RZX([1, 0], 1.))
result2 = circuit2.run(init_state)

assert pytest.approx(qutip.fidelity(result1, result2), 1.0e-6) == 1

0 comments on commit 4080b81

Please sign in to comment.