diff --git a/src/qibo/gates/abstract.py b/src/qibo/gates/abstract.py index 6476883d69..c9b53ee974 100644 --- a/src/qibo/gates/abstract.py +++ b/src/qibo/gates/abstract.py @@ -46,7 +46,6 @@ def __init__(self): self.init_args = [] self.init_kwargs = {} - self.clifford = False self.unitary = False self._target_qubits = () self._control_qubits = () @@ -59,6 +58,11 @@ def __init__(self): self.device_gates = set() self.original_gate = None + @property + def clifford(self): + """Return boolean value representing if a Gate is Clifford or not.""" + return False + @property def raw(self) -> dict: """Serialize to dictionary. diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 5b34f7faac..dfbe628a00 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -29,9 +29,12 @@ def __init__(self, q): self.draw_label = "H" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "h" @@ -58,9 +61,12 @@ def __init__(self, q): self.draw_label = "X" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "x" @@ -171,9 +177,12 @@ def __init__(self, q): self.draw_label = "Y" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "y" @@ -215,9 +224,12 @@ def __init__(self, q): self.draw_label = "Z" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "z" @@ -256,9 +268,12 @@ def __init__(self, q): self.draw_label = "SX" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "sx" @@ -303,9 +318,12 @@ def __init__(self, q): self.draw_label = "SXDG" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "sxdg" @@ -350,9 +368,12 @@ def __init__(self, q): self.draw_label = "S" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "s" @@ -382,9 +403,12 @@ def __init__(self, q): self.draw_label = "SDG" self.target_qubits = (q,) self.init_args = [q] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "sdg" @@ -468,9 +492,12 @@ def __init__(self, *q): self.draw_label = "I" self.target_qubits = tuple(q) self.init_args = q - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "id" @@ -502,6 +529,11 @@ def __init__(self, *q, delay: int = 0): self.target_qubits = tuple(q) +def _is_clifford_given_angle(angle): + """Helper function to update Clifford boolean condition according to the given angle ``angle``.""" + return isinstance(angle, (float, int)) and (angle % (np.pi / 2)).is_integer() + + class _Rn_(ParametrizedGate): """Abstract class for defining the RX, RY and RZ rotations. @@ -524,13 +556,14 @@ def __init__(self, q, theta, trainable=True): if isinstance(theta, Parameter): theta = theta() - if isinstance(theta, (float, int)) and (theta % (np.pi / 2)).is_integer(): - self.clifford = True - self.parameters = theta self.init_args = [q] self.init_kwargs = {"theta": theta, "trainable": trainable} + @property + def clifford(self): + return _is_clifford_given_angle(self.parameters[0]) + def _dagger(self) -> "Gate": """""" return self.__class__( @@ -974,9 +1007,12 @@ def __init__(self, q0, q1): self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "cx" @@ -1011,9 +1047,12 @@ def __init__(self, q0, q1): self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "cy" @@ -1057,9 +1096,12 @@ def __init__(self, q0, q1): self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "cz" @@ -1186,12 +1228,13 @@ def __init__(self, q0, q1, theta, trainable=True): self.parameters = theta self.unitary = True - if isinstance(theta, (float, int)) and (theta % np.pi).is_integer(): - self.clifford = True - self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "trainable": trainable} + @property + def clifford(self): + return _is_clifford_given_angle(self.parameters[0]) + def _dagger(self) -> "Gate": """""" q0 = self.control_qubits[0] @@ -1484,9 +1527,12 @@ def __init__(self, q0, q1): self.draw_label = "x" self.target_qubits = (q0, q1) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "swap" @@ -1516,9 +1562,12 @@ def __init__(self, q0, q1): self.draw_label = "i" self.target_qubits = (q0, q1) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "iswap" @@ -1608,9 +1657,12 @@ def __init__(self, q0, q1): self.draw_label = "fx" self.target_qubits = (q0, q1) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + @property def qasm_label(self): return "fswap" @@ -2158,9 +2210,12 @@ def __init__(self, q0, q1): self.draw_label = "ECR" self.target_qubits = (q0, q1) self.init_args = [q0, q1] - self.clifford = True self.unitary = True + @property + def clifford(self): + return True + def decompose(self, *free, use_toffolis: bool = True) -> List[Gate]: """Decomposition of :math:`\\textup{ECR}` gate up to global phase. @@ -2315,6 +2370,7 @@ def __init__( self.name = "Unitary" if name is None else name self.draw_label = "U" self.target_qubits = tuple(q) + self._clifford = False # TODO: Check that given ``unitary`` has proper shape? self.parameter_names = "u" @@ -2361,6 +2417,14 @@ def parameters(self, x): for gate in self.device_gates: # pragma: no cover gate.parameters = x + @property + def clifford(self): + return self._clifford + + @clifford.setter + def clifford(self, value): + self._clifford = value + def on_qubits(self, qubit_map): args = [self.init_args[0]] args.extend(qubit_map.get(i) for i in self.target_qubits) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 617dd6a65f..1b1252244b 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -205,7 +205,7 @@ def sample_training_circuit_cdr( gates_to_replace = [] for i, gate in enumerate(circuit.queue): if isinstance(gate, gates.RZ): - if gate.init_kwargs["theta"] % (np.pi / 2) != 0.0: + if not gate.clifford: gates_to_replace.append((i, gate)) if not gates_to_replace: diff --git a/tests/test_gates_gates.py b/tests/test_gates_gates.py index 77eda97cc0..d407e72e9e 100644 --- a/tests/test_gates_gates.py +++ b/tests/test_gates_gates.py @@ -686,7 +686,7 @@ def test_cun(backend, name, params): if name in ["CRX", "CRY", "CRZ"]: theta = params["theta"] - if (theta % np.pi).is_integer(): + if (theta % (np.pi / 2)).is_integer(): assert gate.clifford else: assert not gate.clifford @@ -1689,3 +1689,28 @@ def test_x_decomposition_execution(backend, target, controls, free, use_toffolis ############################################################################### + +####################### Test Clifford updates ################################# + + +@pytest.mark.parametrize( + "gate", + [ + gates.RX(0, 0), + gates.RY(0, 0), + gates.RZ(0, 0), + gates.CRX(0, 1, 0), + gates.CRY(0, 1, 0), + gates.CRZ(0, 1, 0), + ], +) +def test_clifford_condition_update(backend, gate): + """Test clifford condition update if setting new angle into the rotations.""" + assert gate.clifford == True + gate.parameters = 0.5 + assert gate.clifford == False + gate.parameters = np.pi + assert gate.clifford == True + + +############################################################################### diff --git a/tests/test_models_error_mitigation.py b/tests/test_models_error_mitigation.py index caaafad251..3881b2b0db 100644 --- a/tests/test_models_error_mitigation.py +++ b/tests/test_models_error_mitigation.py @@ -215,8 +215,6 @@ def test_sample_training_circuit(nqubits): c.add(gates.CNOT(q, q + 1) for q in range(1, nqubits, 2)) c.add(gates.M(q) for q in range(nqubits)) - for j in range(len(c.queue)): - c.queue[j].clifford = True with pytest.raises(ValueError): sample_training_circuit_cdr(c) with pytest.raises(ValueError):