Skip to content

Commit ac53148

Browse files
authored
Merge pull request #328 from Refactor Gate class (2)
The original PR #325 was split into 2, for the previous set of changes look at that PR description. - Defined `__slots__` for all the Abstract Gate classes and standard gate classes for better memory efficiency and faster lookup time. - Added boolean `is_controlled`, `is_parametric` methods to the `Gate` class. - Added the concept and implementation of namespaces for operations. This is done because at several places earlier gate.name was being used for comparison. Each operation (gate for now) must now be defined in a namespace or if it is a temporary gate, namespace can be set to None. This allows the user to define a faulty version of standard gates or follow their own convention for certain Parametric gates. The concept of namespace is much broader. Currently the parent namespace is "std", under it only "gates" is set as of now. In future we can make other namespaces under "std" like for Arithmetic Ops, Ansatz Ops etc. For the idea of Ops look at #263 (comment) Though it hasn't been implemented yet. In a way std namespace will work like a standard library for different operations and the user can also define their own implementation for operations in a different namespace. - Added caching in `get_qobj` method for standard gates. This is done for two reasons. ``` @staticmethod def get_qobj() -> Qobj: return Qobj([[1, 0], [0, -1]]) ``` If you run it multiple times and check `id()` for each obj, they will be different. This leads to higher memory consumption for large circuit simulations, even though all of them represent the Z gate's qobj in this case. Additonally different qobjs will each need to be garbage collected. Memory and lookup are an even larger bottleneck in VQA circuits where several times many gates share the same parameter. - Added `dtype` to `get_qobj(dtype="dense")` method, . This might be useful if the circuit simulation is run on a different backend like JAX, CUDA. - Made `Gate` non initialised by default i.e. `__init__` raise will raise an Error. Obviously`ParametricGate` and its controlled versions are initialisable. This makes sense because target, controls have been removed from gates and `x1=X()`, `x2=X()` now represent the same thing. Also good from a memory point of view. - Added `Sdag`, `Tdag`, `SQRTXdag`, `ISWAPdag`, `SQRTSWAPdag`, `BERKELEYdag` to the standard gates. They have been added to the documentation table and in the circuit draw (colour config for them). - Added `inverse` method for `Gate` class. Also implemented inverse for all standard gates and tests for the same. For controlled gates, the inverse is auto calculated based on the target gate. Closes #301 - Renamed `unitary_gate` to `get_unitary_gate` and added checks for all the input arguments. - The control value for a gate is now baked into the control gate itself. The user can always generate his own version of controlled gate e.g. `controlled(gates.X, num_ctrl_qubits=2, control_value=0b10)`. For more detail check out #308 (comment) - For all Controlled gates, the (target_gate, num_ctrl_qubit, ctrl_value) is used as another key in the namespace to reference that controlled gate. This is done in order to prevent regeneration of the controlled gate class for a given target gate with known (num_ctrl_qubit, ctrl_value). Since the namespace is used by default, there is no overhead. - Renamed `controlled_gate` to `controlled_gate_unitary` and resolved the bug which didn't allow for generating the correct qobj if number of controls > 1. - Added gate equivalence checking for `ParametricGate`, `ControlledGate`. And added `__eq__`, `__hash__` in Gate metaclass as well. Replaced gate.name = "X" search with gate == X now. - Corrected inconsistency between Gate and Type[Gate] from previous PRs. Gate refers to an instantiated object while type[Gate] refers to a Gate class or subclass in accordance with Python's typehinting. - Added several more checks in `__init_subclass__`, `add_gate` method. - Added tests in `test_gates.py` and `test_circuit.py` for mostly incorrect cases like num_qubits being negative etc., which should raise an Error. Since Gates are used throughout the codebase, their normal usage is already mostly covered. **Maintainence and Bug Fixes:** - `gate_product` had a clear bug that `tensor_list` was not defined but was being used, fixed that and also moved the entire `gate_product` logic to operations, which was split in circuit-simulator utils, operations but was always being imported from operations as per `__init__` even in `mat_mul_simulator.py`. - Removed deprecated arguments and functions that were deprecated 3 years back. This was checked in the git commit history. - Remove mutable default values from function arguments across the codebase, this is a common Python bug. Something like targets = [], can often lead to an error. For details check out this blog https://medium.com/@matbrizolla/beware-of-the-hidden-trap-understanding-mutable-default-arguments-in-python-d10e8b0a7b72 - Corrected several `isInstance` checks in the codebase. `Iterable` and `Sequence` were being used interchangeably at several places especially in qasm. This led to several unexpected behaviours and errors during gate refactoring. - Moved pytest.ini to the root, this is what pytest themselves recommend https://docs.pytest.org/en/stable/explanation/goodpractices.html#choosing-a-test-layout-import-rules . The reason was that placing pytest.ini in `tests/` led to certain sys path issues, for reference check https://docs.pytest.org/en/stable/explanation/pythonpath.html. This caused an error when standard gates were designated to a namespace and pytest was trying to redefine them and reassign them to the same namespace. *Edit: Some more changes* - Separated IDLE (meant for Pulse level simulation) and Identity gate (standard Identity matrix). - In Pulse compiler instead of `gate.name`, `gates` are themselves being used as keys. - Removed expand argument from `inverse()` in Parametric Gates. - Renamed `controlled` to `get_controlled_gate`.
2 parents 979f94b + 5c7ac64 commit ac53148

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+5498
-3500
lines changed

doc/pulse-paper/qft.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def get_control_latex(model):
4242
num_qubits = model.num_qubits
4343
num_coupling = model._get_num_coupling()
4444
return [
45-
{f"sx{m}": r"$\sigma_x^{}$".format(m) for m in range(num_qubits)},
46-
{f"sz{m}": r"$\sigma_z^{}$".format(m) for m in range(num_qubits)},
47-
{f"g{m}": r"$g_{}$".format(m) for m in range(num_coupling)},
45+
{f"sx{m}": rf"$\sigma_x^{m}$" for m in range(num_qubits)},
46+
{f"sz{m}": rf"$\sigma_z^{m}$" for m in range(num_qubits)},
47+
{f"g{m}": rf"$g_{m}$" for m in range(num_coupling)},
4848
]
4949

5050

doc/source/apidoc/qutip_qip.operations.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ qutip\_qip.operations
5050

5151
.. autosummary::
5252

53-
controlled_gate
53+
controlled_gate_unitary
5454
expand_operator
5555
gate_sequence_product

doc/source/qip-basics.rst

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,38 +139,51 @@ The pre-defined gates for the class :class:`~.operations.Gate` are shown in the
139139
==================== ========================================
140140
Gate name Description
141141
==================== ========================================
142-
"RX" Rotation around x axis
143-
"RY" Rotation around y axis
144-
"RZ" Rotation around z axis
145-
"R" Arbitrary single qubit rotation
146142
"X" Pauli-X gate
147143
"Y" Pauli-Y gate
148144
"Z" Pauli-Z gate
145+
"H" Hadamard gate
149146
"S" Single-qubit rotation or Z90
147+
"Sdag" Inverse of S gate
150148
"T" Square root of S gate
149+
"Tdag" Inverse of T gate
151150
"SQRTX" Square root of X gate
152-
"H" Hadamard gate
153-
"PHASEGATE" Add a phase one the state 1
154-
"CRX" Controlled rotation around x axis
155-
"CRY" Controlled rotation around y axis
156-
"CRZ" Controlled rotation around z axis
157-
"CX" Controlled X gate (also called CNOT)
151+
"SQRTXdag" Inverse of SQRTX gate
152+
"RX" Rotation around x axis
153+
"RY" Rotation around y axis
154+
"RZ" Rotation around z axis
155+
"PHASE" Adds a relative phase to ket 1
156+
"R" Arbitrary single qubit rotation
157+
"QASMU" U rotation gate used as a primitive in the QASM standard
158+
"CX" (CNOT) Controlled X gate
158159
"CY" Controlled Y gate
159160
"CZ" Controlled Z gate
161+
"CH" Controlled H gate
160162
"CS" Controlled S gate
163+
"CSdag" Controlled Sdag gate
161164
"CT" Controlled T gate
162-
"CPHASE" Controlled phase gate
163-
"QASMU" U rotation gate used as a primitive in the QASM standard
164-
"BERKELEY" Berkeley gate
165-
"SWAPalpha" SWAPalpha gate
165+
"CTdag" Controlled Tdag gate
166+
"CRX" Controlled rotation around x axis
167+
"CRY" Controlled rotation around y axis
168+
"CRZ" Controlled rotation around z axis
169+
"CPHASE" Controlled Phase gate
170+
"CQASMU" Controlled QASMU gate
166171
"SWAP" Swap the states of two qubits
167172
"ISWAP" Swap gate with additional phase for 01 and 10 states
173+
"ISWAPdag" Inverse of ISWAP gate
168174
"SQRTSWAP" Square root of the SWAP gate
175+
"SQRTSWAPdag" Inverse of SQRTSWAP gate
169176
"SQRTISWAP" Square root of the ISWAP gate
177+
"SQRTISWAPdag" Inverse of SQRTISWAP gate
178+
"BERKELEY" Berkeley gate
179+
"BERKELEYdag" Inverse of BERKELEY gate
180+
"SWAPALPHA" SWAPALPHA gate
170181
"MS" Mølmer-Sørensen gate
182+
"RZX" RZX gate
183+
"TOFFOLI" (CCX) Toffoli gate
171184
"FREDKIN" Fredkin gate
172-
"TOFFOLI" Toffoli gate
173-
"GLOBALPHASE" Global phase
185+
"GLOBALPHASE" Global phase gate
186+
"IDENTITY" Identity gate
174187
==================== ========================================
175188

176189
For some of the gates listed above, :class:`.QubitCircuit` also has a primitive :func:`.QubitCircuit.resolve_gates()` method that decomposes them into elementary gate sets such as CX or SWAP with single-qubit gates (RX, RY and RZ). However, this method is not fully optimized. It is very likely that the depth of the circuit can be further reduced by merging quantum gates. It is required that the gate resolution be carried out before the measurements to the circuit are added.
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
[pytest]
22
markers =
33
slow: Mark a test as taking a long time to run, and so can be skipped with `pytest -m "not slow"`.
4-
repeat(n): Repeat the given test 'n' times.
54
requires_cython: Mark that the given test requires Cython to be installed. Such tests will be skipped if Cython is not available.
5+
66
filterwarnings =
77
error
88
; ImportWarning: PyxImporter.find_spec() not found
99
ignore:PyxImporter:ImportWarning
10-
; DeprecationWarning: Please use `upcast` from the `scipy.sparse` namespace
11-
ignore::DeprecationWarning:qutip.fastsparse*:
10+
ignore::UserWarning:
1211
ignore:matplotlib not found:UserWarning
1312
ignore:the imp module is deprecated in favour of importlib:DeprecationWarning
1413
ignore:Dedicated options class are no longer needed, options should be passed as dict to solvers.:FutureWarning
1514
ignore::DeprecationWarning:qiskit.utils.algorithm_globals:
16-
# Deprecation warning for python = 3.9 with matplotlib 3.9.4
17-
ignore:'mode' parameter is deprecated
1815
# Deprecation warning for scipy disp interface, will be removed in scipy 1.18
1916
ignore:.*`disp` and `iprint` options.*L-BFGS-B.*deprecated.*:DeprecationWarning
2017

21-
22-
18+
pythonpath = src
19+
testpaths = tests

src/qutip_qip/algorithms/bit_flip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from qutip_qip.circuit import QubitCircuit
2-
from qutip_qip.operations import CX, TOFFOLI, X
2+
from qutip_qip.operations.gates import CX, TOFFOLI, X
33

44

55
class BitFlipCode:

src/qutip_qip/algorithms/phase_flip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from qutip_qip.circuit import QubitCircuit
2-
from qutip_qip.operations import CX, H
2+
from qutip_qip.operations.gates import CX, H
33

44

55
class PhaseFlipCode:

src/qutip_qip/algorithms/qft.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44

55
import numpy as np
6-
from qutip_qip.operations import H, RZ, CX, CPHASE, SWAP, expand_operator
7-
from qutip_qip.circuit import QubitCircuit
86
from qutip import Qobj
7+
from qutip_qip.circuit import QubitCircuit
98
from qutip_qip.decompose import decompose_one_qubit_gate
9+
from qutip_qip.operations import expand_operator
10+
from qutip_qip.operations.gates import H, RZ, CX, CPHASE, SWAP
1011

1112

1213
def qft(N=1):
@@ -66,7 +67,7 @@ def qft_steps(N=1, swapping=True):
6667
for j in range(i):
6768
U_step_list.append(
6869
expand_operator(
69-
CPHASE(np.pi / (2 ** (i - j))).get_qobj(),
70+
CPHASE(theta=np.pi / (2 ** (i - j))).get_qobj(),
7071
dims=[2] * N,
7172
targets=[i, j],
7273
)
@@ -113,7 +114,10 @@ def qft_gate_sequence(N=1, swapping=True, to_cnot=False):
113114
for j in range(i):
114115
if not to_cnot:
115116
qc.add_gate(
116-
CPHASE(np.pi / (2 ** (i - j)), arg_label=r"{\pi/2^{%d}}" % (i - j)),
117+
CPHASE(
118+
theta=np.pi / (2 ** (i - j)),
119+
arg_label=r"{\pi/2^{%d}}" % (i - j),
120+
),
117121
targets=[j],
118122
controls=[i],
119123
)
@@ -136,6 +140,6 @@ def _cphase_to_cnot(targets, controls, arg_value, qc: QubitCircuit):
136140
qc.add_gate(decomposed_gates[4], targets=targets)
137141
qc.add_gate(CX, targets=targets, controls=controls)
138142
qc.add_gate(RZ(arg_value / 2), targets=controls)
139-
gate = decomposed_gates[7]
140-
gate.arg_value[0] += arg_value / 4
143+
gate = decomposed_gates[7] # This is a GLOBALPHASE Gate
144+
gate.arg_value = gate.arg_value[0] + arg_value / 4
141145
qc.add_gate(gate, targets=targets)

src/qutip_qip/algorithms/qpe.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import numpy as np
2-
from qutip_qip.circuit import QubitCircuit
32
from qutip_qip.algorithms import qft_gate_sequence
4-
from qutip_qip.operations import custom_gate_factory, controlled_gate_factory, H
3+
from qutip_qip.circuit import QubitCircuit
4+
from qutip_qip.operations import get_unitary_gate, get_controlled_gate
5+
from qutip_qip.operations.gates import H
56

67

78
def qpe(U, num_counting_qubits, target_qubits=None, to_cnot=False):
@@ -41,7 +42,7 @@ def qpe(U, num_counting_qubits, target_qubits=None, to_cnot=False):
4142
target_qubits = list(
4243
range(num_counting_qubits, num_counting_qubits + num_target_qubits)
4344
)
44-
elif isinstance(target_qubits, int):
45+
elif type(target_qubits) is int:
4546
target_qubits = [target_qubits]
4647
num_target_qubits = 1
4748
else:
@@ -61,11 +62,11 @@ def qpe(U, num_counting_qubits, target_qubits=None, to_cnot=False):
6162
U_power = U if power == 1 else U**power
6263

6364
# Add controlled-U^power gate
64-
controlled_u = controlled_gate_factory(
65-
gate=custom_gate_factory(
66-
gate_name="U^power gate",
65+
controlled_u = get_controlled_gate(
66+
gate=get_unitary_gate(
67+
gate_name=f"U^{power}",
6768
U=U_power,
68-
)(),
69+
),
6970
)
7071
qc.add_gate(controlled_u, targets=target_qubits, controls=[i])
7172

src/qutip_qip/circuit/__init__.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,12 @@
11
"""Circuit representation and simulation at the gate level."""
22

3-
import warnings
4-
53
from .instruction import (
64
CircuitInstruction,
75
GateInstruction,
86
MeasurementInstruction,
97
)
108
from .simulator import CircuitResult, CircuitSimulator
119
from .circuit import QubitCircuit
12-
from qutip_qip.operations import Gate, Measurement
13-
14-
15-
def _add_deprecation(fun, msg):
16-
def newfun(*args, **kwargs):
17-
warnings.warn(
18-
msg,
19-
DeprecationWarning,
20-
stacklevel=2,
21-
)
22-
return fun(*args, **kwargs)
23-
24-
return newfun
25-
26-
27-
Gate = _add_deprecation(
28-
Gate,
29-
"The class Gate has been moved to qutip_qip.operations."
30-
"Please use update the import statement.\n",
31-
)
32-
Measurement = _add_deprecation(
33-
Measurement,
34-
"The class Measurement has been moved to qutip_qip.operations."
35-
"Please use update the import statement.\n",
36-
)
37-
3810

3911
__all__ = [
4012
"CircuitSimulator",

0 commit comments

Comments
 (0)