Skip to content

Commit 40c51cf

Browse files
committed
Add RZX gate to the SCQubits model (#245)
- 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.
1 parent 7a21a48 commit 40c51cf

File tree

7 files changed

+136
-24
lines changed

7 files changed

+136
-24
lines changed

src/qutip_qip/circuit/_decompose.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def _gate_SNOT(gate, temp_resolved):
5959
)
6060

6161

62+
_gate_H = _gate_SNOT
63+
64+
6265
def _gate_PHASEGATE(gate, temp_resolved):
6366
temp_resolved.append(
6467
Gate(

src/qutip_qip/circuit/circuit.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import inspect
88
import os
99
from functools import partialmethod
10+
from typing import Optional, Union, Tuple, List, Dict, Any
1011

1112
import numpy as np
1213
from copy import deepcopy
@@ -596,9 +597,6 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
596597
basis_1q.append(gate)
597598
else:
598599
pass
599-
raise NotImplementedError(
600-
"%s is not a valid basis gate" % gate
601-
)
602600
if len(basis_1q) == 1:
603601
raise ValueError("Not sufficient single-qubit gates in basis")
604602
if len(basis_1q) == 0:
@@ -621,9 +619,12 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
621619
)
622620
try:
623621
_resolve_to_universal(gate, temp_resolved, basis_1q, basis_2q)
624-
except AttributeError:
625-
exception = f"Gate {gate.name} cannot be resolved."
626-
raise NotImplementedError(exception)
622+
except KeyError:
623+
if gate.name in basis:
624+
temp_resolved.append(gate)
625+
else:
626+
exception = f"Gate {gate.name} cannot be resolved."
627+
raise NotImplementedError(exception)
627628

628629
match = False
629630
for basis_unit in ["CSIGN", "ISWAP", "SQRTSWAP", "SQRTISWAP"]:

src/qutip_qip/compiler/circuitqedcompiler.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def __init__(self, num_qubits, params):
9191
"RY": self.ry_compiler,
9292
"RX": self.rx_compiler,
9393
"CNOT": self.cnot_compiler,
94+
"RZX": self.rzx_compiler,
9495
}
9596
)
9697
self.args = { # Default configuration
@@ -130,10 +131,22 @@ def _rotation_compiler(self, gate, op_label, param_label, args):
130131
maximum=self.params[param_label][targets[0]],
131132
area=gate.arg_value / 2.0 / np.pi,
132133
)
134+
f = 2 * np.pi * self.params["wq"][targets[0]]
133135
if args["DRAG"]:
134136
pulse_info = self._drag_pulse(op_label, coeff, tlist, targets[0])
137+
elif op_label == "sx":
138+
pulse_info = [
139+
("sx" + str(targets[0]), coeff),
140+
# Add zero here just to make it easier to add the driving frequency later.
141+
("sy" + str(targets[0]), np.zeros(len(coeff))),
142+
]
143+
elif op_label == "sy":
144+
pulse_info = [
145+
("sx" + str(targets[0]), np.zeros(len(coeff))),
146+
("sy" + str(targets[0]), coeff),
147+
]
135148
else:
136-
pulse_info = [(op_label + str(targets[0]), coeff)]
149+
raise RuntimeError("Unknown label.")
137150
return [Instruction(gate, tlist, pulse_info)]
138151

139152
def _drag_pulse(self, op_label, coeff, tlist, target):
@@ -198,6 +211,41 @@ def rx_compiler(self, gate, args):
198211
"""
199212
return self._rotation_compiler(gate, "sx", "omega_single", args)
200213

214+
def rzx_compiler(self, gate, args):
215+
"""
216+
Cross-Resonance RZX rotation, building block for the CNOT gate.
217+
218+
Parameters
219+
----------
220+
gate : :obj:`~.operations.Gate`:
221+
The quantum gate to be compiled.
222+
args : dict
223+
The compilation configuration defined in the attributes
224+
:obj:`.GateCompiler.args` or given as a parameter in
225+
:obj:`.GateCompiler.compile`.
226+
227+
Returns
228+
-------
229+
A list of :obj:`.Instruction`, including the compiled pulse
230+
information for this gate.
231+
"""
232+
result = []
233+
q1, q2 = gate.targets
234+
if q1 < q2:
235+
zx_coeff = self.params["zx_coeff"][2 * q1]
236+
else:
237+
zx_coeff = self.params["zx_coeff"][2 * q1 - 1]
238+
area = 0.5
239+
coeff, tlist = self.generate_pulse_shape(
240+
args["shape"], args["num_samples"], maximum=zx_coeff, area=area
241+
)
242+
area_rescale_factor = np.sqrt(np.abs(gate.arg_value) / (np.pi / 2))
243+
tlist *= area_rescale_factor
244+
coeff *= area_rescale_factor
245+
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
246+
result += [Instruction(gate, tlist, pulse_info)]
247+
return result
248+
201249
def cnot_compiler(self, gate, args):
202250
"""
203251
Compiler for CNOT gate using the cross resonance iteraction.
@@ -226,13 +274,8 @@ def cnot_compiler(self, gate, args):
226274
gate1 = Gate("RX", q2, arg_value=-np.pi / 2)
227275
result += self.gate_compiler[gate1.name](gate1, args)
228276

229-
zx_coeff = self.params["zx_coeff"][q1]
230-
area = 1 / 2
231-
coeff, tlist = self.generate_pulse_shape(
232-
args["shape"], args["num_samples"], maximum=zx_coeff, area=area
233-
)
234-
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
235-
result += [Instruction(gate, tlist, pulse_info)]
277+
gate2 = Gate("RZX", targets=[q1, q2], arg_value=np.pi / 2)
278+
result += self.rzx_compiler(gate2, args)
236279

237280
gate3 = Gate("RX", q1, arg_value=-np.pi / 2)
238281
result += self.gate_compiler[gate3.name](gate3, args)

src/qutip_qip/device/circuitqed.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(self, num_qubits, dims=None, zz_crosstalk=False, **params):
7373
**params,
7474
)
7575
super(SCQubits, self).__init__(model=model)
76-
self.native_gates = ["RX", "RY", "CNOT"]
76+
self.native_gates = ["RX", "RY", "CNOT", "RZX"]
7777
self._default_compiler = SCQubitsCompiler
7878
self.pulse_mode = "continuous"
7979

@@ -318,13 +318,12 @@ def _compute_params(self):
318318
)
319319
)
320320
zx_coeff.append(tmp)
321-
for i in range(num_qubits - 1, 0, -1):
322321
tmp = (
323-
J[i - 1]
324-
* omega_cr[i]
322+
J[i]
323+
* omega_cr[i + 1]
325324
* (
326-
1 / (wq[i] - wq[i - 1] + alpha[i])
327-
- 1 / (wq[i] - wq[i - 1])
325+
1 / (wq[i + 1] - wq[i] + alpha[i + 1])
326+
- 1 / (wq[i + 1] - wq[i])
328327
)
329328
)
330329
zx_coeff.append(tmp)

src/qutip_qip/operations/gateclass.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"CS",
9494
"CT",
9595
"CPHASE",
96+
"RZX",
9697
]
9798

9899
"""
@@ -1153,6 +1154,55 @@ def get_compact_qobj(self):
11531154
return cphase(self.arg_value).tidyup()
11541155

11551156

1157+
class RZX(TwoQubitGate):
1158+
r"""
1159+
RZX gate.
1160+
1161+
.. math::
1162+
1163+
\begin{pmatrix}
1164+
\cos{\theta/2} & -i\sin{\theta/2} & 0 & 0 \\
1165+
-i\sin{\theta/2} & \cos{\theta/2} & 0 & 0 \\
1166+
0 & 0 & \cos{\theta/2} & i\sin{\theta/2} \\
1167+
0 & 0 & i\sin{\theta/2} & \cos{\theta/2} \\
1168+
\end{pmatrix}
1169+
1170+
Examples
1171+
--------
1172+
>>> from qutip_qip.operations import RZX
1173+
>>> RZX([0, 1], np.pi).get_compact_qobj().tidyup() # doctest: +NORMALIZE_WHITESPACE
1174+
Quantum object: dims=[[2, 2], [2, 2]], shape=(4, 4), type='oper', dtype=Dense, isherm=False
1175+
Qobj data =
1176+
[[0.+0.j 0.-1.j 0.+0.j 0.+0.j]
1177+
[0.-1.j 0.+0.j 0.+0.j 0.+0.j]
1178+
[0.+0.j 0.+0.j 0.+0.j 0.+1.j]
1179+
[0.+0.j 0.+0.j 0.+1.j 0.+0.j]]
1180+
"""
1181+
1182+
def __init__(self, targets, arg_value, **kwargs):
1183+
self.target_gate = RZ
1184+
super().__init__(
1185+
targets=targets,
1186+
arg_value=arg_value,
1187+
target_gate=self.target_gate,
1188+
**kwargs,
1189+
)
1190+
1191+
def get_compact_qobj(self):
1192+
theta = self.arg_value
1193+
return Qobj(
1194+
np.array(
1195+
[
1196+
[np.cos(theta / 2), -1.0j * np.sin(theta / 2), 0.0, 0.0],
1197+
[-1.0j * np.sin(theta / 2), np.cos(theta / 2), 0.0, 0.0],
1198+
[0.0, 0.0, np.cos(theta / 2), 1.0j * np.sin(theta / 2)],
1199+
[0.0, 0.0, 1.0j * np.sin(theta / 2), np.cos(theta / 2)],
1200+
]
1201+
),
1202+
dims=[[2, 2], [2, 2]],
1203+
)
1204+
1205+
11561206
CRY = partial(_OneControlledGate, target_gate=RY)
11571207
CRY.__doc__ = "Controlled Y rotation."
11581208
CRX = partial(_OneControlledGate, target_gate=RX)
@@ -1205,4 +1255,5 @@ def get_compact_qobj(self):
12051255
"CS": CS,
12061256
"CT": CT,
12071257
"CPHASE": CPHASE,
1258+
"RZX": RZX,
12081259
}

tests/test_device.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import qutip
1010
from qutip_qip.circuit import QubitCircuit
11-
from qutip_qip.operations import Gate, gate_sequence_product
11+
from qutip_qip.operations import Gate, gate_sequence_product, RZX
1212
from qutip_qip.device import (DispersiveCavityQED, LinearSpinChain,
1313
CircularSpinChain, SCQubits)
1414

@@ -101,8 +101,21 @@ def test_analytical_evolution(num_qubits, gates, device_class, kwargs):
101101
@pytest.mark.filterwarnings("ignore:Not in the dispersive regime")
102102
@pytest.mark.parametrize(("num_qubits", "gates"), single_gate_tests)
103103
@pytest.mark.parametrize(("device_class", "kwargs"), device_lists_numeric)
104-
def test_numerical_evolution(
105-
num_qubits, gates, device_class, kwargs):
104+
def test_numerical_evolution(num_qubits, gates, device_class, kwargs):
105+
_test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)
106+
107+
108+
# Test for RZX gate, only available on SCQubits.
109+
_rzx = RZX([0, 1], arg_value=np.pi/2)
110+
@pytest.mark.parametrize(
111+
("num_qubits", "gates", "device_class", "kwargs"),
112+
[pytest.param(2, [_rzx], SCQubits, {}, id="RZX-SCQubits")]
113+
)
114+
def test_numerical_evolution_zx(num_qubits, gates, device_class, kwargs):
115+
_test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs)
116+
117+
118+
def _test_numerical_evolution_helper(num_qubits, gates, device_class, kwargs):
106119
num_qubits = 2
107120
circuit = QubitCircuit(num_qubits)
108121
for gate in gates:

tests/test_gates.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from qutip_qip.operations import (
1111
X, Y, Z, RX, RY, RZ, H, SQRTNOT, S, T, QASMU, CNOT, CPHASE, ISWAP, SWAP,
1212
CZ, SQRTSWAP, SQRTISWAP, SWAPALPHA, SWAPALPHA, MS, TOFFOLI, FREDKIN,
13-
BERKELEY, R, expand_operator)
13+
BERKELEY, R, RZX, expand_operator)
1414

1515

1616
def _permutation_id(permutation):
@@ -382,6 +382,7 @@ def test_gates_class():
382382
circuit1.add_gate("TOFFOLI", [2, 0, 1])
383383
circuit1.add_gate("FREDKIN", [0, 1, 2])
384384
circuit1.add_gate("BERKELEY", [1, 0])
385+
circuit1.add_gate("RZX", [1, 0], arg_value=1.)
385386
result1 = circuit1.run(init_state)
386387

387388
circuit2 = QubitCircuit(3)
@@ -409,6 +410,7 @@ def test_gates_class():
409410
circuit2.add_gate(TOFFOLI([2, 0, 1]))
410411
circuit2.add_gate(FREDKIN([0, 1, 2]))
411412
circuit2.add_gate(BERKELEY([1, 0]))
413+
circuit2.add_gate(RZX([1, 0], 1.))
412414
result2 = circuit2.run(init_state)
413415

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

0 commit comments

Comments
 (0)