From 8ba92dca054c719484f21881a69233a6c6bef321 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 17 May 2024 16:33:42 +0800 Subject: [PATCH 01/13] Added global unitary folding for get_noisy_circuits --- src/qibo/models/error_mitigation.py | 89 +++++++++++++++++------------ 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 606e822bc4..75808f1120 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -51,51 +51,70 @@ def get_gammas(noise_levels, analytical: bool = True): return zne_coefficients -def get_noisy_circuit(circuit, num_insertions: int, insertion_gate: str = "CNOT"): +def get_noisy_circuit(circuit, num_insertions: int, insertion_gate=None): """Standalone function to generate the noisy circuit with the inverse gate pairs insertions. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to modify. - num_insertions (int): number of insertion gate pairs to add. + num_insertions (int): number of insertion gate pairs / global unitary folds to add. insertion_gate (str, optional): gate to be used in the insertion. If ``"RX"``, the gate used is :math:``RX(\\pi / 2)``. - Default is ``"CNOT"``. + Otherwise, the other gate is ``"CNOT"``. + If None, global unitary folding is carried out. Returns: - :class:`qibo.models.Circuit`: circuit with the inserted gate pairs. + :class:`qibo.models.Circuit`: circuit with the inserted gate pairs or with global folding. """ - if insertion_gate not in ("CNOT", "RX"): # pragma: no cover - raise_error( - ValueError, - "Invalid insertion gate specification. Please select between 'CNOT' and 'RX'.", - ) - if insertion_gate == "CNOT" and circuit.nqubits < 2: # pragma: no cover - raise_error( - ValueError, - "Provide a circuit with at least 2 qubits when using the 'CNOT' insertion gate. " - + "Alternatively, try with the 'RX' insertion gate instead.", - ) - i_gate = gates.CNOT if insertion_gate == "CNOT" else gates.RX - - theta = np.pi / 2 - noisy_circuit = circuit.__class__(**circuit.init_kwargs) - - for gate in circuit.queue: - noisy_circuit.add(gate) - - if isinstance(gate, i_gate): - if insertion_gate == "CNOT": - control = gate.control_qubits[0] - target = gate.target_qubits[0] - for _ in range(num_insertions): - noisy_circuit.add(gates.CNOT(control, target)) - noisy_circuit.add(gates.CNOT(control, target)) - elif gate.init_kwargs["theta"] == theta: - qubit = gate.qubits[0] - for _ in range(num_insertions): - noisy_circuit.add(gates.RX(qubit, theta=theta)) - noisy_circuit.add(gates.RX(qubit, theta=-theta)) + if insertion_gate is None: # pragma: no cover + circuit_no_meas = circuit.__class__(**circuit.init_kwargs) + circuit_meas = circuit.__class__(**circuit.init_kwargs) + for gate in circuit.queue: + if gate.name != "measure": + circuit_no_meas.add(gate) + else: + circuit_meas.add(gate) + + noisy_circuit = circuit.__class__(**circuit.init_kwargs) + noisy_circuit = circuit_no_meas + for _ in range(num_insertions): + noisy_circuit += circuit_no_meas.invert() + circuit_no_meas + + noisy_circuit += circuit_meas + + else: + if insertion_gate not in ("CNOT", "RX"): # pragma: no cover + raise_error( + ValueError, + "Invalid insertion gate specification. Please select between 'CNOT' and 'RX'.", + ) + if insertion_gate == "CNOT" and circuit.nqubits < 2: # pragma: no cover + raise_error( + ValueError, + "Provide a circuit with at least 2 qubits when using the 'CNOT' insertion gate. " + + "Alternatively, try with the 'RX' insertion gate instead.", + ) + + i_gate = gates.CNOT if insertion_gate == "CNOT" else gates.RX + + theta = np.pi / 2 + noisy_circuit = circuit.__class__(**circuit.init_kwargs) + + for gate in circuit.queue: + noisy_circuit.add(gate) + + if isinstance(gate, i_gate): + if insertion_gate == "CNOT": + control = gate.control_qubits[0] + target = gate.target_qubits[0] + for _ in range(num_insertions): + noisy_circuit.add(gates.CNOT(control, target)) + noisy_circuit.add(gates.CNOT(control, target)) + elif gate.init_kwargs["theta"] == theta: + qubit = gate.qubits[0] + for _ in range(num_insertions): + noisy_circuit.add(gates.RX(qubit, theta=theta)) + noisy_circuit.add(gates.RX(qubit, theta=-theta)) return noisy_circuit From a59e62cb666c8bde9986e2683d18ab5c2277523f Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 23 May 2024 10:56:59 +0800 Subject: [PATCH 02/13] Changed flag for global unitary folding --- src/qibo/models/error_mitigation.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 75808f1120..19e286ec4e 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -51,22 +51,25 @@ def get_gammas(noise_levels, analytical: bool = True): return zne_coefficients -def get_noisy_circuit(circuit, num_insertions: int, insertion_gate=None): +def get_noisy_circuit( + circuit, num_insertions: int, global_unitary_folding=True, insertion_gate=None +): """Standalone function to generate the noisy circuit with the inverse gate pairs insertions. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to modify. num_insertions (int): number of insertion gate pairs / global unitary folds to add. + global_unitary_folding (bool): Default True. Global unitary folding is carried out by default. + Global unitary folding takes precedence over insertion_gate. insertion_gate (str, optional): gate to be used in the insertion. If ``"RX"``, the gate used is :math:``RX(\\pi / 2)``. Otherwise, the other gate is ``"CNOT"``. - If None, global unitary folding is carried out. Returns: :class:`qibo.models.Circuit`: circuit with the inserted gate pairs or with global folding. """ - if insertion_gate is None: # pragma: no cover + if global_unitary_folding: # pragma: no cover circuit_no_meas = circuit.__class__(**circuit.init_kwargs) circuit_meas = circuit.__class__(**circuit.init_kwargs) for gate in circuit.queue: @@ -83,7 +86,10 @@ def get_noisy_circuit(circuit, num_insertions: int, insertion_gate=None): noisy_circuit += circuit_meas else: - if insertion_gate not in ("CNOT", "RX"): # pragma: no cover + if insertion_gate is None or insertion_gate not in ( + "CNOT", + "RX", + ): # pragma: no cover raise_error( ValueError, "Invalid insertion gate specification. Please select between 'CNOT' and 'RX'.", From 68d17b5a2624842a95635ae13c94033f0e054181 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 30 May 2024 11:39:32 +0800 Subject: [PATCH 03/13] Included flag for global_unitary_folding in ZNE --- src/qibo/models/error_mitigation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 19e286ec4e..d310e8c8c8 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -132,6 +132,7 @@ def ZNE( noise_model=None, nshots=10000, solve_for_gammas=False, + global_unitary_folding=True, insertion_gate="CNOT", readout=None, qubit_map=None, @@ -186,7 +187,10 @@ def ZNE( expected_values = [] for num_insertions in noise_levels: noisy_circuit = get_noisy_circuit( - circuit, num_insertions, insertion_gate=insertion_gate + circuit, + num_insertions, + global_unitary_folding, + insertion_gate=insertion_gate, ) val = get_expectation_val_with_readout_mitigation( noisy_circuit, From ba59c978f92fb4035017fc20ffe723666bd9497d Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 30 May 2024 11:51:48 +0800 Subject: [PATCH 04/13] Added pytest parametrize for global unitary folding --- tests/test_models_error_mitigation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_models_error_mitigation.py b/tests/test_models_error_mitigation.py index 3881b2b0db..1b2aeb8acc 100644 --- a/tests/test_models_error_mitigation.py +++ b/tests/test_models_error_mitigation.py @@ -95,7 +95,8 @@ def get_circuit(nqubits, nmeas=None): ], ) @pytest.mark.parametrize("solve", [False, True]) -def test_zne(backend, nqubits, noise, solve, insertion_gate, readout): +@pytest.mark.parametrize("GUF", [False, True]) +def test_zne(backend, nqubits, noise, solve, GUF, insertion_gate, readout): """Test that ZNE reduces the noise.""" if backend.name == "tensorflow": import tensorflow as tf @@ -128,6 +129,7 @@ def test_zne(backend, nqubits, noise, solve, insertion_gate, readout): noise_model=noise, nshots=10000, solve_for_gammas=solve, + global_unitary_folding=GUF, insertion_gate=insertion_gate, readout=readout, backend=backend, From ba8e3bbfc4e2679a01568a75a8363c41bac47b58 Mon Sep 17 00:00:00 2001 From: Matthew <46650770+mho291@users.noreply.github.com> Date: Thu, 30 May 2024 11:55:39 +0800 Subject: [PATCH 05/13] Change RX gate folding, added #pragma no cover in else statement --- src/qibo/models/error_mitigation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index d310e8c8c8..6ef3212d4c 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -85,7 +85,7 @@ def get_noisy_circuit( noisy_circuit += circuit_meas - else: + else: # pragma: no cover if insertion_gate is None or insertion_gate not in ( "CNOT", "RX", @@ -116,7 +116,8 @@ def get_noisy_circuit( for _ in range(num_insertions): noisy_circuit.add(gates.CNOT(control, target)) noisy_circuit.add(gates.CNOT(control, target)) - elif gate.init_kwargs["theta"] == theta: + # elif gate.init_kwargs["theta"] == theta: + elif insertion_gate == "RX": qubit = gate.qubits[0] for _ in range(num_insertions): noisy_circuit.add(gates.RX(qubit, theta=theta)) From e7c86a5a7c70d7e6a84976383aeaeb8e665f025f Mon Sep 17 00:00:00 2001 From: Matthew <46650770+mho291@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:00:42 +0800 Subject: [PATCH 06/13] Simplified the way to do global unitary folding --- src/qibo/models/error_mitigation.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 6a7220767f..18f265f457 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -70,21 +70,25 @@ def get_noisy_circuit( """ if global_unitary_folding: # pragma: no cover - circuit_no_meas = circuit.__class__(**circuit.init_kwargs) - circuit_meas = circuit.__class__(**circuit.init_kwargs) - for gate in circuit.queue: - if gate.name != "measure": - circuit_no_meas.add(gate) - else: - circuit_meas.add(gate) + from qibo import Circuit, gates - noisy_circuit = circuit.__class__(**circuit.init_kwargs) - noisy_circuit = circuit_no_meas - for _ in range(num_insertions): - noisy_circuit += circuit_no_meas.invert() + circuit_no_meas + # Create a copy of input circuit without measurements + copy_c = Circuit(**circuit.init_kwargs) + for g in circuit.queue: + if not isinstance(g, gates.M): + copy_c.add(g) - noisy_circuit += circuit_meas + # Initialize noisy circuit + noisy_circuit = copy_c + + # Add (U^+ U)s + for _ in range(num_insertions): + noisy_circuit += copy_c.invert() + copy_c + # Add measurements according to the original circuit + for m in circuit.measurements: + noisy_circuit.add(m) + else: # pragma: no cover if insertion_gate is None or insertion_gate not in ( "CNOT", From 4b626145563170e9424bc37baaaef9d2bd1dfe5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 08:01:03 +0000 Subject: [PATCH 07/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/models/error_mitigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 18f265f457..cf13a99c4b 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -88,7 +88,7 @@ def get_noisy_circuit( # Add measurements according to the original circuit for m in circuit.measurements: noisy_circuit.add(m) - + else: # pragma: no cover if insertion_gate is None or insertion_gate not in ( "CNOT", From 79578e2e43b58ba86d2987222603bb4fc4dd6ab4 Mon Sep 17 00:00:00 2001 From: Matthew <46650770+mho291@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:04:02 +0800 Subject: [PATCH 08/13] Update global_unitary_folding --- src/qibo/models/error_mitigation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index cf13a99c4b..c9325134c6 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -70,7 +70,6 @@ def get_noisy_circuit( """ if global_unitary_folding: # pragma: no cover - from qibo import Circuit, gates # Create a copy of input circuit without measurements copy_c = Circuit(**circuit.init_kwargs) From 0a2188ed541295511c9bfa75be061ecc4b0205fb Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 18 Jul 2024 16:51:25 +0800 Subject: [PATCH 09/13] Add import for Circuit --- src/qibo/models/error_mitigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index c9325134c6..6da47ea019 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -5,7 +5,7 @@ import numpy as np from scipy.optimize import curve_fit -from qibo import gates +from qibo import Circuit, gates from qibo.backends import GlobalBackend, _check_backend, _check_backend_and_local_state from qibo.config import raise_error From 2b0fe76e99040cd138f38c2284853c0ff545cc04 Mon Sep 17 00:00:00 2001 From: Matthew <46650770+mho291@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:07:38 +0800 Subject: [PATCH 10/13] Move "from qibo import Circuit" to get_noisy_circuit function --- src/qibo/models/error_mitigation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 6da47ea019..ff3dbd9c7b 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -5,7 +5,7 @@ import numpy as np from scipy.optimize import curve_fit -from qibo import Circuit, gates +from qibo import gates from qibo.backends import GlobalBackend, _check_backend, _check_backend_and_local_state from qibo.config import raise_error @@ -69,6 +69,8 @@ def get_noisy_circuit( :class:`qibo.models.Circuit`: circuit with the inserted gate pairs or with global folding. """ + from qibo import Circuit # pylint: disable=import-outside-toplevel + if global_unitary_folding: # pragma: no cover # Create a copy of input circuit without measurements From 8df401e9e65c5d2e9e38fef6641c42a8c4426111 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:08:36 +0000 Subject: [PATCH 11/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/models/error_mitigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index ff3dbd9c7b..f9fa712763 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -70,7 +70,7 @@ def get_noisy_circuit( """ from qibo import Circuit # pylint: disable=import-outside-toplevel - + if global_unitary_folding: # pragma: no cover # Create a copy of input circuit without measurements From a63cc4d7c0bae530e3bfdd4de78f41b62ddff307 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 9 Aug 2024 15:11:06 +0800 Subject: [PATCH 12/13] Updated docstrings for get_noisy_circuits and ZNE --- src/qibo/models/error_mitigation.py | 30 +++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index f9fa712763..94cd05c6be 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -59,11 +59,10 @@ def get_noisy_circuit( Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to modify. num_insertions (int): number of insertion gate pairs / global unitary folds to add. - global_unitary_folding (bool): Default True. Global unitary folding is carried out by default. - Global unitary folding takes precedence over insertion_gate. - insertion_gate (str, optional): gate to be used in the insertion. - If ``"RX"``, the gate used is :math:``RX(\\pi / 2)``. - Otherwise, the other gate is ``"CNOT"``. + global_unitary_folding (bool): If ``True``, noise is increased by global unitary folding. + If ``False``, local unitary folding is used. Defaults to ``True``. + insertion_gate (str, optional): gate to be folded in the local unitary folding. + If ``RX``, the gate used is :math:``RX(\\pi / 2)``. Otherwise, it is the ``CNOT`` gate. Returns: :class:`qibo.models.Circuit`: circuit with the inserted gate pairs or with global folding. @@ -71,26 +70,22 @@ def get_noisy_circuit( from qibo import Circuit # pylint: disable=import-outside-toplevel - if global_unitary_folding: # pragma: no cover + if global_unitary_folding: - # Create a copy of input circuit without measurements copy_c = Circuit(**circuit.init_kwargs) for g in circuit.queue: if not isinstance(g, gates.M): copy_c.add(g) - # Initialize noisy circuit noisy_circuit = copy_c - # Add (U^+ U)s for _ in range(num_insertions): noisy_circuit += copy_c.invert() + copy_c - # Add measurements according to the original circuit for m in circuit.measurements: noisy_circuit.add(m) - else: # pragma: no cover + else: if insertion_gate is None or insertion_gate not in ( "CNOT", "RX", @@ -109,7 +104,7 @@ def get_noisy_circuit( i_gate = gates.CNOT if insertion_gate == "CNOT" else gates.RX theta = np.pi / 2 - noisy_circuit = circuit.__class__(**circuit.init_kwargs) + noisy_circuit = Circuit(**circuit.init_kwargs) for gate in circuit.queue: noisy_circuit.add(gate) @@ -121,9 +116,9 @@ def get_noisy_circuit( for _ in range(num_insertions): noisy_circuit.add(gates.CNOT(control, target)) noisy_circuit.add(gates.CNOT(control, target)) - # elif gate.init_kwargs["theta"] == theta: elif insertion_gate == "RX": qubit = gate.qubits[0] + theta = gate.init_kwargs["theta"] for _ in range(num_insertions): noisy_circuit.add(gates.RX(qubit, theta=theta)) noisy_circuit.add(gates.RX(qubit, theta=-theta)) @@ -160,9 +155,10 @@ def ZNE( nshots (int, optional): Number of shots. Defaults to :math:`10000`. solve_for_gammas (bool, optional): If ``True``, explicitly solve the equations to obtain the ``gamma`` coefficients. Default is ``False``. - insertion_gate (str, optional): gate to be used in the insertion. - If ``"RX"``, the gate used is :math:``RX(\\pi / 2)``. - Defaults to ``"CNOT"``. + global_unitary_folding (bool, optional): If ``True``, noise is increased by global unitary folding. + If ``False``, local unitary folding is used. Defaults to ``True``. + insertion_gate (str, optional): gate to be folded in the local unitary folding. + If ``RX``, the gate used is :math:``RX(\\pi / 2)``. Otherwise, it is the ``CNOT`` gate. readout (dict, optional): a dictionary that may contain the following keys: * ncircuits: int, specifies the number of random circuits to use for the randomized method of readout error mitigation. @@ -212,7 +208,7 @@ def ZNE( gamma = get_gammas(noise_levels, analytical=solve_for_gammas) - return np.sum(gamma * expected_values) + return expected_values, gamma, np.sum(gamma * expected_values) def sample_training_circuit_cdr( From 4455a70b1a5c74acc87a9b23f33ebffa777567a9 Mon Sep 17 00:00:00 2001 From: Matthew <46650770+mho291@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:09:57 +0800 Subject: [PATCH 13/13] Update error_mitigation.py --- src/qibo/models/error_mitigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 6097e37e16..eea1071399 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -209,7 +209,7 @@ def ZNE( gamma = get_gammas(noise_levels, analytical=solve_for_gammas) - return expected_values, gamma, np.sum(gamma * expected_values) + return np.sum(gamma * expected_values) def sample_training_circuit_cdr(