From 060d56bbf07e214e3f51554d08fb9219be5eda0e Mon Sep 17 00:00:00 2001 From: MozammilQ Date: Tue, 6 Jun 2023 15:31:41 +0000 Subject: [PATCH 01/56] Added a FakeGeneric BackendV2 backend, this is just a bare minimum working code --- .../fake_provider/backends/__init__.py | 1 + .../backends/generic/__init__.py | 15 ++ .../backends/generic/fake_generic.py | 251 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 qiskit/providers/fake_provider/backends/generic/__init__.py create mode 100644 qiskit/providers/fake_provider/backends/generic/fake_generic.py diff --git a/qiskit/providers/fake_provider/backends/__init__.py b/qiskit/providers/fake_provider/backends/__init__.py index ecde62a637c1..9f580e3ef9f3 100644 --- a/qiskit/providers/fake_provider/backends/__init__.py +++ b/qiskit/providers/fake_provider/backends/__init__.py @@ -62,6 +62,7 @@ from .vigo import FakeVigoV2 from .washington import FakeWashingtonV2 from .yorktown import FakeYorktownV2 +from .generic import FakeGeneric # BackendV1 Backends from .almaden import FakeAlmaden diff --git a/qiskit/providers/fake_provider/backends/generic/__init__.py b/qiskit/providers/fake_provider/backends/generic/__init__.py new file mode 100644 index 000000000000..3890efd788a6 --- /dev/null +++ b/qiskit/providers/fake_provider/backends/generic/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""A fake generic device""" + +from .fake_generic import FakeGeneric diff --git a/qiskit/providers/fake_provider/backends/generic/fake_generic.py b/qiskit/providers/fake_provider/backends/generic/fake_generic.py new file mode 100644 index 000000000000..f2dee8ef4ff2 --- /dev/null +++ b/qiskit/providers/fake_provider/backends/generic/fake_generic.py @@ -0,0 +1,251 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +import numpy as np +from qiskit.transpiler import CouplingMap +from typing import Optional, List, Tuple + +from qiskit.transpiler import Target, InstructionProperties, QubitProperties +from qiskit.providers.backend import BackendV2 +from qiskit.providers.options import Options +from qiskit.exceptions import QiskitError +from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate +from qiskit.circuit import Measure, Parameter, Delay +from qiskit.circuit.controlflow import ( + IfElseOp, + WhileLoopOp, + ForLoopOp, + SwitchCaseOp, + BreakLoopOp, + ContinueLoopOp, +) + + +class FakeGeneric(BackendV2): + """ + Generate a generic fake backend, this backend will have properties and configuration according to the settings passed in the argument. + + Argumets: + num_qubits: + Pass in the integer which is the number of qubits of the backend. + Example: num_qubits = 19 + + coupling_map: + Pass in the coupling Map of the backend as a list of tuples. + Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. + + If None passed then the coupling map will be generated. + This map will be in accordance with the Heavy Hex lattice. + + Heavy Hex Lattice Reference: + https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 + + + basis_gates: + Pass in the basis gates of the backend as list of strings. + Example: ['cx', 'id', 'rz', 'sx', 'x'] --> This is the default basis gates of the backend. + + dynamic: + Enable/Disable dynamic circuits on this backend. + True: Enable + False: Disable (Default) + + bidirectional_cp_mp: + Enable/Disable bi-directional coupling map. + True: Enable + False: Disable (Default) + replace_cx_with_ecr: + Enable/Disable bi-directional coupling map. + True: Enable (Default) + False: Disable + + + + + Returns: + None + + Raises: + QiskitError: If argument basis_gates has a gate which is not a valid basis gate. + + + """ + + def __init__( + self, + num_qubits: int, + coupling_map: Optional[List[Tuple[str, str]]] = None, + basis_gates: List[str] = ["cx", "id", "rz", "sx", "x"], + dynamic: bool = False, + bidirectional_cp_mp: bool = False, + replace_cx_with_ecr: bool = True, + ): + + super().__init__( + provider=None, + name="fake_generic", + description=f"This {num_qubits} qubit fake generic device, with generic settings has been generated right now!", + backend_version="", + ) + + self.basis_gates = basis_gates + + if "delay" not in basis_gates: + self.basis_gates.append("delay") + if "measure" not in basis_gates: + self.basis_gates.append("measure") + if "barrier" not in basis_gates: + self.basis_gates.append("barrier") + + if not coupling_map: + distance: int = self._get_distance(num_qubits) + coupling_map = CouplingMap().from_heavy_hex( + distance=distance, bidirectional=bidirectional_cp_mp + ) + + rng = np.random.default_rng(seed=123456789123456) + + self._target = Target( + description="Fake Generic Backend", + num_qubits=num_qubits, + qubit_properties=[ + QubitProperties( + t1=rng.uniform(100e-6, 200e-6), + t2=rng.uniform(100e-6, 200e-6), + frequency=rng.uniform(5e9, 5.5e9), + ) + for _ in range(num_qubits) + ], + ) + + for gate in basis_gates: + if gate == "cx": + if replace_cx_with_ecr: + self._target.add_instruction( + ECRGate(), + { + edge: InstructionProperties( + error=rng.uniform(1e-5, 5e-3), duration=rng.uniform(1e-8, 9e-7) + ) + for edge in coupling_map + }, + ) + else: + self._target.add_instruction( + CXGate(), + { + edge: InstructionProperties( + error=rng.uniform(1e-5, 5e-3), duration=rng.uniform(1e-8, 9e-7) + ) + for edge in coupling_map + }, + ) + + elif gate == "id": + self._target.add_instruction( + IGate(), + { + (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) + for qubit_idx in range(num_qubits) + }, + ) + elif gate == "rz": + self._target.add_instruction( + RZGate(Parameter("theta")), + { + (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) + for qubit_idx in range(num_qubits) + }, + ) + elif gate == "sx": + self._target.add_instruction( + SXGate(), + { + (qubit_idx,): InstructionProperties( + error=rng.uniform(1e-6, 1e-4), duration=rng.uniform(1e-8, 9e-7) + ) + for qubit_idx in range(num_qubits) + }, + ) + elif gate == "x": + self._target.add_instruction( + XGate(), + { + (qubit_idx,): InstructionProperties( + error=rng.uniform(1e-6, 1e-4), duration=rng.uniform(1e-8, 9e-7) + ) + for qubit_idx in range(num_qubits) + }, + ) + elif gate == "measure": + self._target.add_instruction( + Measure(), + { + (qubit_idx,): InstructionProperties( + error=rng.uniform(1e-3, 1e-1), duration=rng.uniform(1e-8, 9e-7) + ) + for qubit_idx in range(num_qubits) + }, + ) + + elif gate == "delay": + self._target.add_instruction( + Delay(Parameter("Time")), + {(qubit_idx,): None for qubit_idx in range(num_qubits)}, + ) + + elif gate == "abc_gate": + self._target.add_instruction( + ABC_Gate(), + { + (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) + for qubit_idx in range(num_qubits) + }, + ) + else: + QiskitError(f"{gate} is not a basis gate") + + if dynamic: + self._target.add_instruction(IfElseOp, name="if_else") + self._target.add_instruction(WhileLoopOp, name="while") + self._target.add_instruction(ForLoopOp, name="for") + self._target.add_instruction(SwitchCaseOp, name="switch_case") + self._target.add_instruction(BreakLoopOp, name="break") + self._target.add_instruction(ContinueLoopOp, name="continue") + + @property + def target(self): + return self._target + + @property + def max_circuits(self): + return None + + def _get_distance(self, num_qubits: int) -> int: + for d in range(3, 20, 2): + # The description of the formula: 5*d**2 - 2*d -1 is explained in https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 + n = (5 * (d**2) - (2 * d) - 1) / 2 + if n >= num_qubits: + return d + + @classmethod + def _default_options(cls): + return Options(shots=1024) + + def run(self, circuit, **kwargs): + from qiskit_aer import AerSimulator + from qiskit_aer.noise import NoiseModel + + noise_model = NoiseModel.from_backend(self) + simulator = AerSimulator(noise_model=None) + return simulator.run(circuit, **kwargs) From ae26ee5a9f8d870cf663e6a4b95b436380cf1d05 Mon Sep 17 00:00:00 2001 From: MozammilQ Date: Thu, 8 Jun 2023 19:08:54 +0000 Subject: [PATCH 02/56] All FakeBackends (like FakeMelbourne, FakeBoeblingen ,etc) is replaced with FakeGeneric --- .../backends/generic/fake_generic.py | 37 ++-- test/python/compiler/test_transpiler.py | 182 +++++++++--------- 2 files changed, 113 insertions(+), 106 deletions(-) diff --git a/qiskit/providers/fake_provider/backends/generic/fake_generic.py b/qiskit/providers/fake_provider/backends/generic/fake_generic.py index f2dee8ef4ff2..fbc10cae35ac 100644 --- a/qiskit/providers/fake_provider/backends/generic/fake_generic.py +++ b/qiskit/providers/fake_provider/backends/generic/fake_generic.py @@ -12,15 +12,16 @@ import numpy as np -from qiskit.transpiler import CouplingMap from typing import Optional, List, Tuple +from qiskit.transpiler import CouplingMap +from qiskit import Aer from qiskit.transpiler import Target, InstructionProperties, QubitProperties from qiskit.providers.backend import BackendV2 from qiskit.providers.options import Options from qiskit.exceptions import QiskitError from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate -from qiskit.circuit import Measure, Parameter, Delay +from qiskit.circuit import Measure, Parameter, Delay, Reset from qiskit.circuit.controlflow import ( IfElseOp, WhileLoopOp, @@ -65,10 +66,16 @@ class FakeGeneric(BackendV2): True: Enable False: Disable (Default) replace_cx_with_ecr: - Enable/Disable bi-directional coupling map. - True: Enable (Default) - False: Disable + True: (Default) Replace every occurance of 'cx' with 'ecr' + False: Do not replace 'cx' with 'ecr' + + enable_reset: + True: (Default) this enables the reset on the backend + False: This disables the reset on the backend + dt: + The system time resolution of input signals in seconds. + Default is 0.22ns @@ -89,6 +96,8 @@ def __init__( dynamic: bool = False, bidirectional_cp_mp: bool = False, replace_cx_with_ecr: bool = True, + enable_reset: bool = True, + dt: float = 0.222e-9, ): super().__init__( @@ -118,6 +127,7 @@ def __init__( self._target = Target( description="Fake Generic Backend", num_qubits=num_qubits, + dt=dt, qubit_properties=[ QubitProperties( t1=rng.uniform(100e-6, 200e-6), @@ -217,12 +227,17 @@ def __init__( if dynamic: self._target.add_instruction(IfElseOp, name="if_else") - self._target.add_instruction(WhileLoopOp, name="while") - self._target.add_instruction(ForLoopOp, name="for") + self._target.add_instruction(WhileLoopOp, name="while_loop") + self._target.add_instruction(ForLoopOp, name="for_loop") self._target.add_instruction(SwitchCaseOp, name="switch_case") self._target.add_instruction(BreakLoopOp, name="break") self._target.add_instruction(ContinueLoopOp, name="continue") + if enable_reset: + self._target.add_instruction( + Reset(), {(qubit_idx,): None for qubit_idx in range(num_qubits)} + ) + @property def target(self): return self._target @@ -243,9 +258,5 @@ def _default_options(cls): return Options(shots=1024) def run(self, circuit, **kwargs): - from qiskit_aer import AerSimulator - from qiskit_aer.noise import NoiseModel - - noise_model = NoiseModel.from_backend(self) - simulator = AerSimulator(noise_model=None) - return simulator.run(circuit, **kwargs) + noise_model = None + return Aer.get_backend("aer_simulator").run(circuit, noise_model=noise_model, **kwargs) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index c25425d051f1..3320236c1fab 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -60,6 +60,9 @@ from qiskit.dagcircuit import DAGOpNode, DAGOutNode from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 + +from qiskit.providers.fake_provider import FakeGeneric + from qiskit.providers.fake_provider import ( FakeBoeblingen, FakeMelbourne, @@ -179,14 +182,13 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - coupling_map = FakeMelbourne().configuration().coupling_map - basis_gates = FakeMelbourne().configuration().basis_gates + backend=FakeGeneric(num_qubits=19) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] new_circuit = transpile( circuit, - basis_gates=basis_gates, - coupling_map=coupling_map, + basis_gates=backend.operation_names, + coupling_map=backend.coupling_map, initial_layout=initial_layout, ) @@ -194,7 +196,7 @@ def test_transpile_non_adjacent_layout(self): for instruction in new_circuit.data: if isinstance(instruction.operation, CXGate): - self.assertIn([qubit_indices[x] for x in instruction.qubits], coupling_map) + self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) def test_transpile_qft_grid(self): """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" @@ -205,36 +207,35 @@ def test_transpile_qft_grid(self): circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) circuit.h(qr[i]) - coupling_map = FakeMelbourne().configuration().coupling_map - basis_gates = FakeMelbourne().configuration().basis_gates - new_circuit = transpile(circuit, basis_gates=basis_gates, coupling_map=coupling_map) + backend = FakeGeneric(num_qubits=19) + new_circuit = transpile(circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map) qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} for instruction in new_circuit.data: if isinstance(instruction.operation, CXGate): - self.assertIn([qubit_indices[x] for x in instruction.qubits], coupling_map) + self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) def test_already_mapped_1(self): """Circuit not remapped if matches topology. See: https://github.com/Qiskit/qiskit-terra/issues/342 """ - backend = FakeRueschlikon() - coupling_map = backend.configuration().coupling_map - basis_gates = backend.configuration().basis_gates + backend=FakeGeneric(num_qubits=19, replace_cx_with_ecr=False) + coupling_map = backend.coupling_map + basis_gates = backend.operation_names - qr = QuantumRegister(16, "qr") - cr = ClassicalRegister(16, "cr") + qr = QuantumRegister(19, "qr") + cr = ClassicalRegister(19, "cr") qc = QuantumCircuit(qr, cr) - qc.cx(qr[3], qr[14]) - qc.cx(qr[5], qr[4]) - qc.h(qr[9]) - qc.cx(qr[9], qr[8]) + qc.cx(qr[0], qr[9]) + qc.cx(qr[1], qr[14]) + qc.h(qr[3]) + qc.cx(qr[10], qr[16]) qc.x(qr[11]) - qc.cx(qr[3], qr[4]) - qc.cx(qr[12], qr[11]) - qc.cx(qr[13], qr[4]) + qc.cx(qr[5], qr[12]) + qc.cx(qr[7], qr[18]) + qc.cx(qr[6], qr[17]) qc.measure(qr, cr) new_qc = transpile( @@ -249,7 +250,7 @@ def test_already_mapped_1(self): [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits ] self.assertEqual( - sorted(cx_qubits_physical), [[3, 4], [3, 14], [5, 4], [9, 8], [12, 11], [13, 4]] + sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] ) def test_already_mapped_via_layout(self): @@ -464,7 +465,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = FakeRueschlikon() + backend = FakeGeneric(num_qubits=19) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -579,7 +580,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = FakeRueschlikon() + backend = FakeGeneric(num_qubits=19) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -596,7 +597,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = FakeRueschlikon() + backend = FakeGeneric(num_qubits=19) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -612,7 +613,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = FakeMelbourne() + backend = FakeGeneric(num_qubits=19) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -650,15 +651,17 @@ def test_parameterized_circuit_for_device(self): qc = QuantumCircuit(qr) theta = Parameter("theta") - qc.rz(theta, qr[0]) + qc.p(theta, qr[0]) transpiled_qc = transpile( - qc, backend=FakeMelbourne(), initial_layout=Layout.generate_trivial_layout(qr) + qc, + backend=FakeGeneric(num_qubits=19), + initial_layout=Layout.generate_trivial_layout(qr), ) - qr = QuantumRegister(14, "q") - expected_qc = QuantumCircuit(qr, global_phase=-1 * theta / 2.0) - expected_qc.append(U1Gate(theta), [qr[0]]) + qr = QuantumRegister(19, "q") + expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) + expected_qc.append(RZGate(theta), [qr[0]]) self.assertEqual(expected_qc, transpiled_qc) @@ -689,12 +692,14 @@ def test_parameter_expression_circuit_for_device(self): qc.rz(square, qr[0]) transpiled_qc = transpile( - qc, backend=FakeMelbourne(), initial_layout=Layout.generate_trivial_layout(qr) + qc, + backend=FakeGeneric(num_qubits=19), + initial_layout=Layout.generate_trivial_layout(qr), ) - qr = QuantumRegister(14, "q") - expected_qc = QuantumCircuit(qr, global_phase=-1 * square / 2.0) - expected_qc.append(U1Gate(square), [qr[0]]) + qr = QuantumRegister(19, "q") + expected_qc = QuantumCircuit(qr) + expected_qc.append(RZGate(square), [qr[0]]) self.assertEqual(expected_qc, transpiled_qc) def test_final_measurement_barrier_for_devices(self): @@ -776,8 +781,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - backend = FakeRueschlikon() - cmap = backend.configuration().coupling_map + cmap = FakeGeneric(num_qubits=19).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -814,7 +818,7 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile(qc, backend=FakeMelbourne()) + out = transpile(qc, backend=FakeGeneric(num_qubits=19)) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1113,10 +1117,9 @@ def test_transpiled_custom_gates_calibration(self): circ.add_calibration(custom_180, [0], q0_x180) circ.add_calibration(custom_90, [1], q1_y90) - backend = FakeBoeblingen() transpiled_circuit = transpile( circ, - backend=backend, + backend=FakeGeneric(num_qubits=19), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1134,10 +1137,9 @@ def test_transpiled_basis_gates_calibrations(self): # Add calibration circ.add_calibration("h", [0], q0_x180) - backend = FakeBoeblingen() transpiled_circuit = transpile( circ, - backend=backend, + backend=FakeGeneric(num_qubits=19), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1154,9 +1156,8 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): # Add calibration circ.add_calibration(custom_180, [1], q0_x180) - backend = FakeBoeblingen() with self.assertRaises(QiskitError): - transpile(circ, backend=backend, layout_method="trivial") + transpile(circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial") def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): """Test if the non-basis gates are transpiled if they are on different qubit that @@ -1171,13 +1172,12 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): # Add calibration circ.add_calibration("h", [1], q0_x180) - backend = FakeBoeblingen() transpiled_circuit = transpile( circ, - backend=backend, + backend=FakeGeneric(num_qubits=19), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"u2", "h"}) + self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) def test_transpile_subset_of_calibrated_gates(self): """Test transpiling a circuit with both basis gate (not-calibrated) and @@ -1195,8 +1195,10 @@ def test_transpile_subset_of_calibrated_gates(self): circ.add_calibration(x_180, [0], q0_x180) circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 - transpiled_circ = transpile(circ, FakeBoeblingen(), layout_method="trivial") - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"u2", "mycustom", "h"}) + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + ) + self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) def test_parameterized_calibrations_transpile(self): """Check that gates can be matched to their calibrations before and after parameter @@ -1212,10 +1214,14 @@ def q0_rxt(tau): circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) - transpiled_circ = transpile(circ, FakeBoeblingen(), layout_method="trivial") + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) - transpiled_circ = transpile(circ, FakeBoeblingen(), layout_method="trivial") + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) def test_inst_durations_from_calibrations(self): @@ -1242,22 +1248,23 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = FakeBoeblingen() - with pulse.build(backend, name="custom") as my_schedule: + backend = FakeGeneric(num_qubits=19) + + with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(1) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(2) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(3) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(4) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) ) pulse.play( pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) @@ -1272,7 +1279,9 @@ def test_multiqubit_gates_calibrations(self, opt_level): pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) ) circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) - trans_circ = transpile(circ, backend, optimization_level=opt_level, layout_method="trivial") + trans_circ = transpile( + circ, backend=backend, optimization_level=opt_level, layout_method="trivial" + ) self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) @data(0, 1, 2, 3) @@ -1299,8 +1308,7 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = FakeRueschlikon() - backend.configuration().dt = 0.5e-6 + backend = FakeGeneric(num_qubits=19, dt=0.5e-6) out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") self.assertEqual(out[1].data[0].operation.unit, "dt") @@ -1315,8 +1323,7 @@ def test_scheduling_backend_v2(self): qc.cx(0, 1) qc.measure_all() - backend = FakeMumbaiV2() - out = transpile([qc, qc], backend, scheduling_method="alap") + out = transpile([qc, qc], backend=FakeGeneric(num_qubits=19), scheduling_method="alap") self.assertIn("delay", out[0].count_ops()) self.assertIn("delay", out[1].count_ops()) @@ -1396,10 +1403,11 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) + backend = FakeGeneric(num_qubits=19) - out = transpile(qc, FakeBoeblingen(), optimization_level=optimization_level) + out = transpile(qc, backend=backend, optimization_level=optimization_level) - self.assertEqual(len(out.qubits), FakeBoeblingen().configuration().num_qubits) + self.assertEqual(len(out.qubits), backend.num_qubits) self.assertEqual(len(out.clbits), len(clbits)) @data(0, 1, 2, 3) @@ -1706,7 +1714,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeMelbourne(), + backend=FakeGeneric(num_qubits=19), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1723,7 +1731,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeMumbaiV2(), + backend=FakeGeneric(num_qubits=19), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1742,12 +1750,11 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" - backend = FakeMelbourne() + backend = FakeGeneric(num_qubits=19, dynamic=True) transpiled = transpile( self._control_flow_circuit(), backend=backend, - basis_gates=backend.configuration().basis_gates - + ["if_else", "for_loop", "while_loop", "switch_case"], + basis_gates=backend.operation_names, optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1763,14 +1770,9 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" - backend = FakeMumbaiV2() - backend.target.add_instruction(IfElseOp, name="if_else") - backend.target.add_instruction(ForLoopOp, name="for_loop") - backend.target.add_instruction(WhileLoopOp, name="while_loop") - backend.target.add_instruction(SwitchCaseOp, name="switch_case") transpiled = transpile( self._control_flow_circuit(), - backend=backend, + backend=FakeGeneric(num_qubits=19, dynamic=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1787,7 +1789,7 @@ def test_qasm3_output(self, optimization_level): """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" transpiled = transpile( self._regular_circuit(), - backend=FakeMelbourne(), + backend=FakeGeneric(num_qubits=19), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1800,14 +1802,9 @@ def test_qasm3_output(self, optimization_level): def test_qasm3_output_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be dumped into OpenQASM 3.""" - backend = FakeMumbaiV2() - backend.target.add_instruction(IfElseOp, name="if_else") - backend.target.add_instruction(ForLoopOp, name="for_loop") - backend.target.add_instruction(WhileLoopOp, name="while_loop") - backend.target.add_instruction(SwitchCaseOp, name="switch_case") transpiled = transpile( self._control_flow_circuit(), - backend=backend, + backend=FakeGeneric(num_qubits=19, dynamic=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1921,8 +1918,7 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - backend = FakeMumbaiV2() - pm = generate_preset_pass_manager(opt_level, backend) + pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=19)) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -1943,7 +1939,7 @@ def test_parallel_with_target(self, opt_level): @data(0, 1, 2, 3) def test_parallel_dispatch(self, opt_level): """Test that transpile in parallel works for all optimization levels.""" - backend = FakeRueschlikon() + backend = FakeGeneric(num_qubits=19) qr = QuantumRegister(16) cr = ClassicalRegister(16) qc = QuantumCircuit(qr, cr) @@ -1981,7 +1977,7 @@ def run(self, dag): ) return dag - backend = FakeMumbaiV2() + backend = FakeGeneric(num_qubits=19) # This target has PulseQobj entries that provides a serialized schedule data pass_ = TestAddCalibration(backend.target) @@ -2001,7 +1997,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = FakeNairobiV2() + backend = FakeGeneric(num_qubits=19) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2027,7 +2023,7 @@ def test_backendv2_and_basis_gates(self, opt_level): @data(0, 1, 2, 3) def test_backendv2_and_coupling_map(self, opt_level): """Test transpile() with custom coupling map.""" - backend = FakeNairobiV2() + qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2038,7 +2034,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=backend, + backend=FakeGeneric(num_qubits=19), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2053,7 +2049,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = FakeNairobiV2() + backend = FakeGeneric(num_qubits=19) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2084,7 +2080,7 @@ def test_transpile_with_multiple_coupling_maps(self): @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = FakeNairobiV2() + backend = FakeGeneric(num_qubits=19) inst_map = InstructionScheduleMap() inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) newgate = Gate("newgate", 2, []) @@ -2943,7 +2939,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = FakeSherbrooke() + backend = FakeGeneric(num_qubits=19) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) From c15254e0068e0cfcfc29694497ca59679f8f7de5 Mon Sep 17 00:00:00 2001 From: MozammilQ Date: Sun, 11 Jun 2023 18:41:51 +0000 Subject: [PATCH 03/56] relocated FakeGeneric in fake_provider directory; replaced if-elif with instruction_dict in fake_generic.py; added support of grid type of coupling map; using lower number of qubits, passed all tests of test_transpiler except one method ( test_parallel_dispatch_lazy_cal_loading ) --- qiskit/providers/fake_provider/__init__.py | 1 + .../fake_provider/backends/__init__.py | 1 - .../backends/generic/__init__.py | 15 - .../backends/generic/fake_generic.py | 262 ---------------- .../providers/fake_provider/fake_generic.py | 280 ++++++++++++++++++ test/python/compiler/test_transpiler.py | 116 +++----- 6 files changed, 329 insertions(+), 346 deletions(-) delete mode 100644 qiskit/providers/fake_provider/backends/generic/__init__.py delete mode 100644 qiskit/providers/fake_provider/backends/generic/fake_generic.py create mode 100644 qiskit/providers/fake_provider/fake_generic.py diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index ed099fdd24c7..d3061fc62061 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -251,3 +251,4 @@ # Configurable fake backend from .utils.configurable_backend import ConfigurableFakeBackend +from .fake_generic import FakeGeneric diff --git a/qiskit/providers/fake_provider/backends/__init__.py b/qiskit/providers/fake_provider/backends/__init__.py index 9f580e3ef9f3..ecde62a637c1 100644 --- a/qiskit/providers/fake_provider/backends/__init__.py +++ b/qiskit/providers/fake_provider/backends/__init__.py @@ -62,7 +62,6 @@ from .vigo import FakeVigoV2 from .washington import FakeWashingtonV2 from .yorktown import FakeYorktownV2 -from .generic import FakeGeneric # BackendV1 Backends from .almaden import FakeAlmaden diff --git a/qiskit/providers/fake_provider/backends/generic/__init__.py b/qiskit/providers/fake_provider/backends/generic/__init__.py deleted file mode 100644 index 3890efd788a6..000000000000 --- a/qiskit/providers/fake_provider/backends/generic/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""A fake generic device""" - -from .fake_generic import FakeGeneric diff --git a/qiskit/providers/fake_provider/backends/generic/fake_generic.py b/qiskit/providers/fake_provider/backends/generic/fake_generic.py deleted file mode 100644 index fbc10cae35ac..000000000000 --- a/qiskit/providers/fake_provider/backends/generic/fake_generic.py +++ /dev/null @@ -1,262 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -import numpy as np -from typing import Optional, List, Tuple - -from qiskit.transpiler import CouplingMap -from qiskit import Aer -from qiskit.transpiler import Target, InstructionProperties, QubitProperties -from qiskit.providers.backend import BackendV2 -from qiskit.providers.options import Options -from qiskit.exceptions import QiskitError -from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate -from qiskit.circuit import Measure, Parameter, Delay, Reset -from qiskit.circuit.controlflow import ( - IfElseOp, - WhileLoopOp, - ForLoopOp, - SwitchCaseOp, - BreakLoopOp, - ContinueLoopOp, -) - - -class FakeGeneric(BackendV2): - """ - Generate a generic fake backend, this backend will have properties and configuration according to the settings passed in the argument. - - Argumets: - num_qubits: - Pass in the integer which is the number of qubits of the backend. - Example: num_qubits = 19 - - coupling_map: - Pass in the coupling Map of the backend as a list of tuples. - Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. - - If None passed then the coupling map will be generated. - This map will be in accordance with the Heavy Hex lattice. - - Heavy Hex Lattice Reference: - https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 - - - basis_gates: - Pass in the basis gates of the backend as list of strings. - Example: ['cx', 'id', 'rz', 'sx', 'x'] --> This is the default basis gates of the backend. - - dynamic: - Enable/Disable dynamic circuits on this backend. - True: Enable - False: Disable (Default) - - bidirectional_cp_mp: - Enable/Disable bi-directional coupling map. - True: Enable - False: Disable (Default) - replace_cx_with_ecr: - True: (Default) Replace every occurance of 'cx' with 'ecr' - False: Do not replace 'cx' with 'ecr' - - enable_reset: - True: (Default) this enables the reset on the backend - False: This disables the reset on the backend - - dt: - The system time resolution of input signals in seconds. - Default is 0.22ns - - - - Returns: - None - - Raises: - QiskitError: If argument basis_gates has a gate which is not a valid basis gate. - - - """ - - def __init__( - self, - num_qubits: int, - coupling_map: Optional[List[Tuple[str, str]]] = None, - basis_gates: List[str] = ["cx", "id", "rz", "sx", "x"], - dynamic: bool = False, - bidirectional_cp_mp: bool = False, - replace_cx_with_ecr: bool = True, - enable_reset: bool = True, - dt: float = 0.222e-9, - ): - - super().__init__( - provider=None, - name="fake_generic", - description=f"This {num_qubits} qubit fake generic device, with generic settings has been generated right now!", - backend_version="", - ) - - self.basis_gates = basis_gates - - if "delay" not in basis_gates: - self.basis_gates.append("delay") - if "measure" not in basis_gates: - self.basis_gates.append("measure") - if "barrier" not in basis_gates: - self.basis_gates.append("barrier") - - if not coupling_map: - distance: int = self._get_distance(num_qubits) - coupling_map = CouplingMap().from_heavy_hex( - distance=distance, bidirectional=bidirectional_cp_mp - ) - - rng = np.random.default_rng(seed=123456789123456) - - self._target = Target( - description="Fake Generic Backend", - num_qubits=num_qubits, - dt=dt, - qubit_properties=[ - QubitProperties( - t1=rng.uniform(100e-6, 200e-6), - t2=rng.uniform(100e-6, 200e-6), - frequency=rng.uniform(5e9, 5.5e9), - ) - for _ in range(num_qubits) - ], - ) - - for gate in basis_gates: - if gate == "cx": - if replace_cx_with_ecr: - self._target.add_instruction( - ECRGate(), - { - edge: InstructionProperties( - error=rng.uniform(1e-5, 5e-3), duration=rng.uniform(1e-8, 9e-7) - ) - for edge in coupling_map - }, - ) - else: - self._target.add_instruction( - CXGate(), - { - edge: InstructionProperties( - error=rng.uniform(1e-5, 5e-3), duration=rng.uniform(1e-8, 9e-7) - ) - for edge in coupling_map - }, - ) - - elif gate == "id": - self._target.add_instruction( - IGate(), - { - (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(num_qubits) - }, - ) - elif gate == "rz": - self._target.add_instruction( - RZGate(Parameter("theta")), - { - (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(num_qubits) - }, - ) - elif gate == "sx": - self._target.add_instruction( - SXGate(), - { - (qubit_idx,): InstructionProperties( - error=rng.uniform(1e-6, 1e-4), duration=rng.uniform(1e-8, 9e-7) - ) - for qubit_idx in range(num_qubits) - }, - ) - elif gate == "x": - self._target.add_instruction( - XGate(), - { - (qubit_idx,): InstructionProperties( - error=rng.uniform(1e-6, 1e-4), duration=rng.uniform(1e-8, 9e-7) - ) - for qubit_idx in range(num_qubits) - }, - ) - elif gate == "measure": - self._target.add_instruction( - Measure(), - { - (qubit_idx,): InstructionProperties( - error=rng.uniform(1e-3, 1e-1), duration=rng.uniform(1e-8, 9e-7) - ) - for qubit_idx in range(num_qubits) - }, - ) - - elif gate == "delay": - self._target.add_instruction( - Delay(Parameter("Time")), - {(qubit_idx,): None for qubit_idx in range(num_qubits)}, - ) - - elif gate == "abc_gate": - self._target.add_instruction( - ABC_Gate(), - { - (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(num_qubits) - }, - ) - else: - QiskitError(f"{gate} is not a basis gate") - - if dynamic: - self._target.add_instruction(IfElseOp, name="if_else") - self._target.add_instruction(WhileLoopOp, name="while_loop") - self._target.add_instruction(ForLoopOp, name="for_loop") - self._target.add_instruction(SwitchCaseOp, name="switch_case") - self._target.add_instruction(BreakLoopOp, name="break") - self._target.add_instruction(ContinueLoopOp, name="continue") - - if enable_reset: - self._target.add_instruction( - Reset(), {(qubit_idx,): None for qubit_idx in range(num_qubits)} - ) - - @property - def target(self): - return self._target - - @property - def max_circuits(self): - return None - - def _get_distance(self, num_qubits: int) -> int: - for d in range(3, 20, 2): - # The description of the formula: 5*d**2 - 2*d -1 is explained in https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 - n = (5 * (d**2) - (2 * d) - 1) / 2 - if n >= num_qubits: - return d - - @classmethod - def _default_options(cls): - return Options(shots=1024) - - def run(self, circuit, **kwargs): - noise_model = None - return Aer.get_backend("aer_simulator").run(circuit, noise_model=noise_model, **kwargs) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py new file mode 100644 index 000000000000..afb3ef9ec634 --- /dev/null +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -0,0 +1,280 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +import numpy as np +import statistics +from typing import Optional, List, Tuple + +from qiskit.transpiler import CouplingMap +from qiskit.providers.basicaer import BasicAer +from qiskit.transpiler import Target, InstructionProperties, QubitProperties +from qiskit.providers.backend import BackendV2 +from qiskit.providers.options import Options +from qiskit.exceptions import QiskitError +from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate +from qiskit.circuit import Measure, Parameter, Delay, Reset +from qiskit.circuit.controlflow import ( + IfElseOp, + WhileLoopOp, + ForLoopOp, + SwitchCaseOp, + BreakLoopOp, + ContinueLoopOp, +) + + +class FakeGeneric(BackendV2): + """ + Generate a generic fake backend, this backend will have properties and configuration according to the settings passed in the argument. + + Argumets: + num_qubits: + Pass in the integer which is the number of qubits of the backend. + Example: num_qubits = 19 + + coupling_map: + Pass in the coupling Map of the backend as a list of tuples. + Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. + + If None passed then the coupling map will be generated. + This map will be in accordance with the argument coupling_map_type. + + coupling_map_type: + Pass in the type of coupling map to be generated. If coupling map is passed then this option will be overriden. + Valid types of coupling map: 'grid', 'heavy_hex' + + Heavy Hex Lattice Reference: + https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 + + + basis_gates: + Pass in the basis gates of the backend as list of strings. + Example: ['cx', 'id', 'rz', 'sx', 'x'] --> This is the default basis gates of the backend. + + dynamic: + Enable/Disable dynamic circuits on this backend. + True: Enable + False: Disable (Default) + + bidirectional_cp_mp: + Enable/Disable bi-directional coupling map. + True: Enable + False: Disable (Default) + replace_cx_with_ecr: + True: (Default) Replace every occurance of 'cx' with 'ecr' + False: Do not replace 'cx' with 'ecr' + + enable_reset: + True: (Default) this enables the reset on the backend + False: This disables the reset on the backend + + dt: + The system time resolution of input signals in seconds. + Default is 0.2222ns + + + + Returns: + None + + Raises: + QiskitError: If argument basis_gates has a gate which is not a valid basis gate. + + + """ + + def __init__( + self, + num_qubits: int, + coupling_map: Optional[List[Tuple[str, str]]] = None, + coupling_map_type: Optional[str] = "grid", + basis_gates: List[str] = ["cx", "id", "rz", "sx", "x"], + dynamic: bool = False, + bidirectional_cp_mp: bool = False, + replace_cx_with_ecr: bool = True, + enable_reset: bool = True, + dt: float = 0.222e-9, + ): + + super().__init__( + provider=None, + name="fake_generic", + description=f"This {num_qubits} qubit fake generic device, with generic settings has been generated right now!", + backend_version="", + ) + + self.basis_gates = basis_gates + self.__rng = np.random.default_rng(seed=123456789123456) + self.__coupling_map_type = coupling_map_type + if replace_cx_with_ecr: + self.basis_gates = list(map(lambda gate: gate.replace("cx", "ecr"), basis_gates)) + + if "delay" not in basis_gates: + self.basis_gates.append("delay") + if "measure" not in basis_gates: + self.basis_gates.append("measure") + + if not coupling_map: + if self.__coupling_map_type == "heavy_hex": + distance = self._get_cmap_args(num_qubits=num_qubits) + coupling_map = CouplingMap().from_heavy_hex( + distance=distance, bidirectional=bidirectional_cp_mp + ) + + elif self.__coupling_map_type == "grid": + num_rows, num_columns = self._get_cmap_args(num_qubits=num_qubits) + coupling_map = CouplingMap().from_grid( + num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cp_mp + ) + else: + coupling_map = CouplingMap(coupling_map) + + num_qubits = coupling_map.size() + + self._target = Target( + description="Fake Generic Backend", + num_qubits=num_qubits, + dt=dt, + qubit_properties=[ + QubitProperties( + t1=self.__rng.uniform(100e-6, 200e-6), + t2=self.__rng.uniform(100e-6, 200e-6), + frequency=self.__rng.uniform(5e9, 5.5e9), + ) + for _ in range(num_qubits) + ], + ) + + instruction_dict = self._get_instruction_dict(num_qubits, coupling_map) + for gate in self.basis_gates: + try: + self._target.add_instruction(*instruction_dict[gate]) + except: + raise QiskitError(f"{gate} is not a valid basis gate") + + if dynamic: + self._target.add_instruction(IfElseOp, name="if_else") + self._target.add_instruction(WhileLoopOp, name="while_loop") + self._target.add_instruction(ForLoopOp, name="for_loop") + self._target.add_instruction(SwitchCaseOp, name="switch_case") + self._target.add_instruction(BreakLoopOp, name="break") + self._target.add_instruction(ContinueLoopOp, name="continue") + + if enable_reset: + self._target.add_instruction( + Reset(), {(qubit_idx,): None for qubit_idx in range(num_qubits)} + ) + + @property + def target(self): + return self._target + + @property + def max_circuits(self): + return None + + def _get_cmap_args(self, num_qubits): + if self.__coupling_map_type == "heavy_hex": + for d in range(3, 20, 2): + # The description of the formula: 5*d**2 - 2*d -1 is explained in + # https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 + n = (5 * (d**2) - (2 * d) - 1) / 2 + if n >= num_qubits: + return int(d) + + elif self.__coupling_map_type == "grid": + factors = [x for x in range(2, num_qubits + 1) if num_qubits % x == 0] + first_factor = statistics.median_high(factors) + second_factor = int(num_qubits / first_factor) + return (first_factor, second_factor) + + def _get_instruction_dict(self, num_qubits, coupling_map): + instruction_dict = { + "ecr": ( + ECRGate(), + { + edge: InstructionProperties( + error=self.__rng.uniform(1e-5, 5e-3), + duration=self.__rng.uniform(1e-8, 9e-7), + ) + for edge in coupling_map + }, + ), + "cx": ( + CXGate(), + { + edge: InstructionProperties( + error=self.__rng.uniform(1e-5, 5e-3), + duration=self.__rng.uniform(1e-8, 9e-7), + ) + for edge in coupling_map + }, + ), + "id": ( + IGate(), + { + (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) + for qubit_idx in range(num_qubits) + }, + ), + "rz": ( + RZGate(Parameter("theta")), + { + (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) + for qubit_idx in range(num_qubits) + }, + ), + "sx": ( + SXGate(), + { + (qubit_idx,): InstructionProperties( + error=self.__rng.uniform(1e-6, 1e-4), + duration=self.__rng.uniform(1e-8, 9e-7), + ) + for qubit_idx in range(num_qubits) + }, + ), + "x": ( + XGate(), + { + (qubit_idx,): InstructionProperties( + error=self.__rng.uniform(1e-6, 1e-4), + duration=self.__rng.uniform(1e-8, 9e-7), + ) + for qubit_idx in range(num_qubits) + }, + ), + "measure": ( + Measure(), + { + (qubit_idx,): InstructionProperties( + error=self.__rng.uniform(1e-3, 1e-1), + duration=self.__rng.uniform(1e-8, 9e-7), + ) + for qubit_idx in range(num_qubits) + }, + ), + "delay": ( + Delay(Parameter("Time")), + {(qubit_idx,): None for qubit_idx in range(num_qubits)}, + ), + } + return instruction_dict + + @classmethod + def _default_options(cls): + return Options(shots=1024) + + def run(self, circuit, **kwargs): + noise_model = None + return BasicAer.get_backend('qasm_simulator').run(circuit, **kwargs) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 3320236c1fab..4757c71bbca1 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -27,19 +27,10 @@ from ddt import data, ddt, unpack from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister, pulse, qasm3, qpy -from qiskit.circuit import ( - Clbit, - ControlFlowOp, - ForLoopOp, - Gate, - IfElseOp, - Parameter, - Qubit, - Reset, - SwitchCaseOp, - WhileLoopOp, -) +from qiskit.circuit import Gate, Qubit, Clbit, ControlFlowOp, Parameter, IfElseOp from qiskit.circuit.delay import Delay +from qiskit.circuit.measure import Measure +from qiskit.circuit.reset import Reset from qiskit.circuit.library import ( CXGate, CZGate, @@ -54,7 +45,6 @@ UGate, XGate, ) -from qiskit.circuit.measure import Measure from qiskit.compiler import transpile from qiskit.converters import circuit_to_dag from qiskit.dagcircuit import DAGOpNode, DAGOutNode @@ -63,14 +53,6 @@ from qiskit.providers.fake_provider import FakeGeneric -from qiskit.providers.fake_provider import ( - FakeBoeblingen, - FakeMelbourne, - FakeMumbaiV2, - FakeNairobiV2, - FakeRueschlikon, - FakeSherbrooke, -) from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap from qiskit.quantum_info import Operator, random_unitary @@ -182,7 +164,7 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - backend=FakeGeneric(num_qubits=19) + backend=FakeGeneric(num_qubits=6) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] new_circuit = transpile( @@ -207,7 +189,7 @@ def test_transpile_qft_grid(self): circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) circuit.h(qr[i]) - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=14) new_circuit = transpile(circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map) qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} @@ -221,7 +203,7 @@ def test_already_mapped_1(self): See: https://github.com/Qiskit/qiskit-terra/issues/342 """ - backend=FakeGeneric(num_qubits=19, replace_cx_with_ecr=False) + backend=FakeGeneric(num_qubits=19, replace_cx_with_ecr=False, coupling_map_type='heavy_hex') coupling_map = backend.coupling_map basis_gates = backend.operation_names @@ -465,7 +447,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=12) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -580,7 +562,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=8) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -597,7 +579,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -613,7 +595,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -652,14 +634,15 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) + backend=FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, - backend=FakeGeneric(num_qubits=19), + backend=backend, initial_layout=Layout.generate_trivial_layout(qr), ) - qr = QuantumRegister(19, "q") + qr = QuantumRegister(backend.num_qubits, "q") expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) expected_qc.append(RZGate(theta), [qr[0]]) @@ -690,14 +673,15 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) + backend=FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, - backend=FakeGeneric(num_qubits=19), + backend=backend, initial_layout=Layout.generate_trivial_layout(qr), ) - qr = QuantumRegister(19, "q") + qr = QuantumRegister(backend.num_qubits, "q") expected_qc = QuantumCircuit(qr) expected_qc.append(RZGate(square), [qr[0]]) self.assertEqual(expected_qc, transpiled_qc) @@ -711,7 +695,7 @@ def test_final_measurement_barrier_for_devices(self): with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: transpile( circ, - coupling_map=FakeRueschlikon().configuration().coupling_map, + coupling_map=FakeGeneric(num_qubits=6).coupling_map, initial_layout=layout, ) self.assertTrue(mock_pass.called) @@ -722,7 +706,7 @@ def test_do_not_run_gatedirection_with_symmetric_cm(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) coupling_map = [] - for node1, node2 in FakeRueschlikon().configuration().coupling_map: + for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: coupling_map.append([node1, node2]) coupling_map.append([node2, node1]) @@ -781,7 +765,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - cmap = FakeGeneric(num_qubits=19).coupling_map + cmap = FakeGeneric(num_qubits=16).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -818,7 +802,7 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile(qc, backend=FakeGeneric(num_qubits=19)) + out = transpile(qc, backend=FakeGeneric(num_qubits=4)) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1119,7 +1103,7 @@ def test_transpiled_custom_gates_calibration(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1139,7 +1123,7 @@ def test_transpiled_basis_gates_calibrations(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1157,7 +1141,7 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): circ.add_calibration(custom_180, [1], q0_x180) with self.assertRaises(QiskitError): - transpile(circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial") + transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): """Test if the non-basis gates are transpiled if they are on different qubit that @@ -1174,7 +1158,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1196,7 +1180,7 @@ def test_transpile_subset_of_calibrated_gates(self): circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) @@ -1215,12 +1199,12 @@ def q0_rxt(tau): circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=19), layout_method="trivial" + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) @@ -1248,7 +1232,7 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=6) with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( @@ -1308,7 +1292,7 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = FakeGeneric(num_qubits=19, dt=0.5e-6) + backend = FakeGeneric(num_qubits=4, dt=0.5e-6) out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") self.assertEqual(out[1].data[0].operation.unit, "dt") @@ -1323,7 +1307,7 @@ def test_scheduling_backend_v2(self): qc.cx(0, 1) qc.measure_all() - out = transpile([qc, qc], backend=FakeGeneric(num_qubits=19), scheduling_method="alap") + out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") self.assertIn("delay", out[0].count_ops()) self.assertIn("delay", out[1].count_ops()) @@ -1403,7 +1387,7 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4) out = transpile(qc, backend=backend, optimization_level=optimization_level) @@ -1530,11 +1514,7 @@ def test_target_ideal_gates(self, opt_level): @data(0, 1) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" - target = FakeMumbaiV2().target - target.add_instruction(ForLoopOp, name="for_loop") - target.add_instruction(WhileLoopOp, name="while_loop") - target.add_instruction(IfElseOp, name="if_else") - target.add_instruction(SwitchCaseOp, name="switch_case") + target = FakeGeneric(num_qubits=8, dynamic=True).target circuit = QuantumCircuit(6, 1) circuit.h(0) @@ -1714,7 +1694,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1731,7 +1711,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1750,7 +1730,7 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" - backend = FakeGeneric(num_qubits=19, dynamic=True) + backend = FakeGeneric(num_qubits=8, dynamic=True) transpiled = transpile( self._control_flow_circuit(), backend=backend, @@ -1772,7 +1752,7 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): through QPY.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=19, dynamic=True), + backend=FakeGeneric(num_qubits=8, dynamic=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1789,7 +1769,7 @@ def test_qasm3_output(self, optimization_level): """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1804,7 +1784,7 @@ def test_qasm3_output_control_flow(self, optimization_level): OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=19, dynamic=True), + backend=FakeGeneric(num_qubits=8, dynamic=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1918,7 +1898,7 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=19)) + pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=4)) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -1930,7 +1910,7 @@ def test_parallel_with_target(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeMumbaiV2().target + target = FakeGeneric(num_qubits=4).target res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: @@ -1939,7 +1919,7 @@ def test_parallel_with_target(self, opt_level): @data(0, 1, 2, 3) def test_parallel_dispatch(self, opt_level): """Test that transpile in parallel works for all optimization levels.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=19, replace_cx_with_ecr=False) qr = QuantumRegister(16) cr = ClassicalRegister(16) qc = QuantumCircuit(qr, cr) @@ -1977,12 +1957,12 @@ def run(self, dag): ) return dag - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4) # This target has PulseQobj entries that provides a serialized schedule data pass_ = TestAddCalibration(backend.target) pm = PassManager(passes=[pass_]) - self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) + #self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) qc = QuantumCircuit(1) qc.sx(0) @@ -1997,7 +1977,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=6) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2034,7 +2014,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=FakeGeneric(num_qubits=19), + backend=FakeGeneric(num_qubits=6, replace_cx_with_ecr=False), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2049,7 +2029,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4, replace_cx_with_ecr=False) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2080,7 +2060,7 @@ def test_transpile_with_multiple_coupling_maps(self): @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=4) inst_map = InstructionScheduleMap() inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) newgate = Gate("newgate", 2, []) @@ -2939,7 +2919,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = FakeGeneric(num_qubits=19) + backend = FakeGeneric(num_qubits=130) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) From 7f544d47ba3eab9e8cceeabf856a1bf5605d824f Mon Sep 17 00:00:00 2001 From: MozammilQ Date: Mon, 12 Jun 2023 17:25:57 +0000 Subject: [PATCH 04/56] added tests for FakeGeneric --- .../fake_provider/test_fake_generic.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/python/providers/fake_provider/test_fake_generic.py diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py new file mode 100644 index 000000000000..eee6aee2d737 --- /dev/null +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -0,0 +1,51 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test of FakeGeneric backend""" +from qiskit.providers.fake_provider import FakeGeneric +from qiskit.test import QiskitTestCase + +class TestFakeGeneric(QiskitTestCase): + def test_heavy_hex_num_qubits(self): + """ Test if num_qubits=5 and coupling_map_type is heavy_hex the number of qubits generated is 19""" + self.assertEqual(FakeGeneric(num_qubits=5, coupling_map_type='heavy_hex').num_qubits, 19) + + def test_heavy_hex_coupling_map(self): + """ Test if coupling_map of heavy_hex is generated right """ + cp_mp=[(0, 13), (1, 13), (1, 14), (2, 14), (3, 15), (4, 15), (4, 16), (5, 16), (6, 17), (7, 17), (7, 18), (8, 18), (0, 9), (3, 9), (5, 12), (8, 12), (10, 14), (10, 16), (11, 15), (11, 17)] + self.assertEqual(list(FakeGeneric(num_qubits=19, coupling_map_type='heavy_hex').coupling_map.get_edges()), cp_mp) + + def test_grid_coupling_map(self): + """ Test if grid coupling map is generated correct. + In this test num_qubits=8, so a grid of 2x4 qubits need to be constructed""" + cp_mp=[(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] + self.assertEqual(list(FakeGeneric(num_qubits=8, coupling_map_type='grid').coupling_map.get_edges()), cp_mp) + + def test_basis_gates(self): + """ Test if the backend has a default basis gates, that includes dealy and measure""" + self.assertEqual(FakeGeneric(num_qubits=8).operation_names, ['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'reset']) + + def test_if_cx_replaced_with_ecr(self): + """ Test if cx is not reaplced with ecr""" + self.assertEqual(FakeGeneric(num_qubits=8, replace_cx_with_ecr=False).operation_names, ['cx', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'reset']) + + def test_dynamic_true_basis_gates(self): + """ Test if basis_gates includes ControlFlowOps when dynamic is set to True""" + self.assertEqual(FakeGeneric(num_qubits=9, dynamic=True).operation_names,['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'if_else', 'while_loop', 'for_loop', 'switch_case', 'break', 'continue', 'reset']) + + def test_if_excludes_reset(self): + """ Test if reset is excluded from the operation names when set enable_reset False""" + self.assertEqual(FakeGeneric(num_qubits=9, enable_reset=False).operation_names, ['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure']) + + def test_if_dt_is_set_correctly(self): + """ Test if dt is set correctly""" + self.assertEqual(FakeGeneric(num_qubits=4, dt=0.5e-9).dt, 0.5e-9) From c8646457c83f289679c5c59e048d04ad99666b06 Mon Sep 17 00:00:00 2001 From: MozammilQ Date: Mon, 12 Jun 2023 17:40:23 +0000 Subject: [PATCH 05/56] This commit just reformats test_transpiler.py fake_generic.py and test_fake_generic.py --- .../providers/fake_provider/fake_generic.py | 2 +- test/python/compiler/test_transpiler.py | 16 ++-- .../fake_provider/test_fake_generic.py | 91 +++++++++++++++---- 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index afb3ef9ec634..e3d3dfbe5fec 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -277,4 +277,4 @@ def _default_options(cls): def run(self, circuit, **kwargs): noise_model = None - return BasicAer.get_backend('qasm_simulator').run(circuit, **kwargs) + return BasicAer.get_backend("qasm_simulator").run(circuit, **kwargs) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 4757c71bbca1..f9ebb24101e4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -164,7 +164,7 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - backend=FakeGeneric(num_qubits=6) + backend = FakeGeneric(num_qubits=6) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] new_circuit = transpile( @@ -190,7 +190,9 @@ def test_transpile_qft_grid(self): circuit.h(qr[i]) backend = FakeGeneric(num_qubits=14) - new_circuit = transpile(circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map) + new_circuit = transpile( + circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map + ) qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} @@ -203,7 +205,9 @@ def test_already_mapped_1(self): See: https://github.com/Qiskit/qiskit-terra/issues/342 """ - backend=FakeGeneric(num_qubits=19, replace_cx_with_ecr=False, coupling_map_type='heavy_hex') + backend = FakeGeneric( + num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" + ) coupling_map = backend.coupling_map basis_gates = backend.operation_names @@ -634,7 +638,7 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) - backend=FakeGeneric(num_qubits=4) + backend = FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, @@ -673,7 +677,7 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) - backend=FakeGeneric(num_qubits=4) + backend = FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, @@ -1962,7 +1966,7 @@ def run(self, dag): # This target has PulseQobj entries that provides a serialized schedule data pass_ = TestAddCalibration(backend.target) pm = PassManager(passes=[pass_]) - #self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) + # self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) qc = QuantumCircuit(1) qc.sx(0) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index eee6aee2d737..9215705567c7 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -14,38 +14,95 @@ from qiskit.providers.fake_provider import FakeGeneric from qiskit.test import QiskitTestCase + class TestFakeGeneric(QiskitTestCase): def test_heavy_hex_num_qubits(self): - """ Test if num_qubits=5 and coupling_map_type is heavy_hex the number of qubits generated is 19""" - self.assertEqual(FakeGeneric(num_qubits=5, coupling_map_type='heavy_hex').num_qubits, 19) + """Test if num_qubits=5 and coupling_map_type is heavy_hex the number of qubits generated is 19""" + self.assertEqual(FakeGeneric(num_qubits=5, coupling_map_type="heavy_hex").num_qubits, 19) def test_heavy_hex_coupling_map(self): - """ Test if coupling_map of heavy_hex is generated right """ - cp_mp=[(0, 13), (1, 13), (1, 14), (2, 14), (3, 15), (4, 15), (4, 16), (5, 16), (6, 17), (7, 17), (7, 18), (8, 18), (0, 9), (3, 9), (5, 12), (8, 12), (10, 14), (10, 16), (11, 15), (11, 17)] - self.assertEqual(list(FakeGeneric(num_qubits=19, coupling_map_type='heavy_hex').coupling_map.get_edges()), cp_mp) + """Test if coupling_map of heavy_hex is generated right""" + cp_mp = [ + (0, 13), + (1, 13), + (1, 14), + (2, 14), + (3, 15), + (4, 15), + (4, 16), + (5, 16), + (6, 17), + (7, 17), + (7, 18), + (8, 18), + (0, 9), + (3, 9), + (5, 12), + (8, 12), + (10, 14), + (10, 16), + (11, 15), + (11, 17), + ] + self.assertEqual( + list( + FakeGeneric(num_qubits=19, coupling_map_type="heavy_hex").coupling_map.get_edges() + ), + cp_mp, + ) def test_grid_coupling_map(self): - """ Test if grid coupling map is generated correct. + """Test if grid coupling map is generated correct. In this test num_qubits=8, so a grid of 2x4 qubits need to be constructed""" - cp_mp=[(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] - self.assertEqual(list(FakeGeneric(num_qubits=8, coupling_map_type='grid').coupling_map.get_edges()), cp_mp) + cp_mp = [(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] + self.assertEqual( + list(FakeGeneric(num_qubits=8, coupling_map_type="grid").coupling_map.get_edges()), + cp_mp, + ) def test_basis_gates(self): - """ Test if the backend has a default basis gates, that includes dealy and measure""" - self.assertEqual(FakeGeneric(num_qubits=8).operation_names, ['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'reset']) + """Test if the backend has a default basis gates, that includes dealy and measure""" + self.assertEqual( + FakeGeneric(num_qubits=8).operation_names, + ["ecr", "id", "rz", "sx", "x", "delay", "measure", "reset"], + ) def test_if_cx_replaced_with_ecr(self): - """ Test if cx is not reaplced with ecr""" - self.assertEqual(FakeGeneric(num_qubits=8, replace_cx_with_ecr=False).operation_names, ['cx', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'reset']) + """Test if cx is not reaplced with ecr""" + self.assertEqual( + FakeGeneric(num_qubits=8, replace_cx_with_ecr=False).operation_names, + ["cx", "id", "rz", "sx", "x", "delay", "measure", "reset"], + ) def test_dynamic_true_basis_gates(self): - """ Test if basis_gates includes ControlFlowOps when dynamic is set to True""" - self.assertEqual(FakeGeneric(num_qubits=9, dynamic=True).operation_names,['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure', 'if_else', 'while_loop', 'for_loop', 'switch_case', 'break', 'continue', 'reset']) + """Test if basis_gates includes ControlFlowOps when dynamic is set to True""" + self.assertEqual( + FakeGeneric(num_qubits=9, dynamic=True).operation_names, + [ + "ecr", + "id", + "rz", + "sx", + "x", + "delay", + "measure", + "if_else", + "while_loop", + "for_loop", + "switch_case", + "break", + "continue", + "reset", + ], + ) def test_if_excludes_reset(self): - """ Test if reset is excluded from the operation names when set enable_reset False""" - self.assertEqual(FakeGeneric(num_qubits=9, enable_reset=False).operation_names, ['ecr', 'id', 'rz', 'sx', 'x', 'delay', 'measure']) + """Test if reset is excluded from the operation names when set enable_reset False""" + self.assertEqual( + FakeGeneric(num_qubits=9, enable_reset=False).operation_names, + ["ecr", "id", "rz", "sx", "x", "delay", "measure"], + ) def test_if_dt_is_set_correctly(self): - """ Test if dt is set correctly""" + """Test if dt is set correctly""" self.assertEqual(FakeGeneric(num_qubits=4, dt=0.5e-9).dt, 0.5e-9) From 1531e84f6603a836e3a2c728c86ca96d82f97ed7 Mon Sep 17 00:00:00 2001 From: MozammilQ <54737489+MozammilQ@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:19:19 +0530 Subject: [PATCH 06/56] Update qiskit/providers/fake_provider/fake_generic.py Co-authored-by: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com> --- qiskit/providers/fake_provider/fake_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index e3d3dfbe5fec..8e7408080e1c 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -37,7 +37,7 @@ class FakeGeneric(BackendV2): """ Generate a generic fake backend, this backend will have properties and configuration according to the settings passed in the argument. - Argumets: + Arguments: num_qubits: Pass in the integer which is the number of qubits of the backend. Example: num_qubits = 19 From bf91f3585c5c5cfe3ba91efa20a4e9744243059d Mon Sep 17 00:00:00 2001 From: MozammilQ <54737489+MozammilQ@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:19:29 +0530 Subject: [PATCH 07/56] Update qiskit/providers/fake_provider/fake_generic.py Co-authored-by: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com> --- qiskit/providers/fake_provider/fake_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 8e7408080e1c..a14d4e693112 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -71,7 +71,7 @@ class FakeGeneric(BackendV2): True: Enable False: Disable (Default) replace_cx_with_ecr: - True: (Default) Replace every occurance of 'cx' with 'ecr' + True: (Default) Replace every occurrence of 'cx' with 'ecr' False: Do not replace 'cx' with 'ecr' enable_reset: From c34316ae165be9c8625537c8502eabda63f8a39b Mon Sep 17 00:00:00 2001 From: MozammilQ <54737489+MozammilQ@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:19:43 +0530 Subject: [PATCH 08/56] Update test/python/providers/fake_provider/test_fake_generic.py Co-authored-by: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com> --- test/python/providers/fake_provider/test_fake_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 9215705567c7..929f7c44719e 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -61,7 +61,7 @@ def test_grid_coupling_map(self): ) def test_basis_gates(self): - """Test if the backend has a default basis gates, that includes dealy and measure""" + """Test if the backend has a default basis gates, that includes delay and measure""" self.assertEqual( FakeGeneric(num_qubits=8).operation_names, ["ecr", "id", "rz", "sx", "x", "delay", "measure", "reset"], From dc2461832aa31e0f72baa64c209920981bc12345 Mon Sep 17 00:00:00 2001 From: MozammilQ <54737489+MozammilQ@users.noreply.github.com> Date: Sat, 1 Jul 2023 17:19:53 +0530 Subject: [PATCH 09/56] Update test/python/providers/fake_provider/test_fake_generic.py Co-authored-by: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com> --- test/python/providers/fake_provider/test_fake_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 929f7c44719e..e26b9a14e8d9 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -68,7 +68,7 @@ def test_basis_gates(self): ) def test_if_cx_replaced_with_ecr(self): - """Test if cx is not reaplced with ecr""" + """Test if cx is not replaced with ecr""" self.assertEqual( FakeGeneric(num_qubits=8, replace_cx_with_ecr=False).operation_names, ["cx", "id", "rz", "sx", "x", "delay", "measure", "reset"], From 02099a0468cb5b388e4c2515a3afedfc1b01ba01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 2 Oct 2023 17:16:37 +0200 Subject: [PATCH 10/56] Fix some tests --- test/python/compiler/test_transpiler.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 5b304d02fa9c..3623eb204a59 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -51,7 +51,6 @@ RYGate, RZGate, SXGate, - U1Gate, U2Gate, UGate, XGate, @@ -1857,7 +1856,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): transpiled = transpile( self._control_flow_expr_circuit(), backend=backend, - basis_gates=backend.configuration().basis_gates + basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], optimization_level=optimization_level, seed_transpiler=2023_07_26, @@ -1925,11 +1924,6 @@ def test_qasm3_output_control_flow(self, optimization_level): def test_qasm3_output_control_flow_expr(self, optimization_level): """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be dumped into OpenQASM 3.""" -# backend = FakeMumbaiV2() -# backend.target.add_instruction(IfElseOp, name="if_else") -# backend.target.add_instruction(ForLoopOp, name="for_loop") -# backend.target.add_instruction(WhileLoopOp, name="while_loop") -# backend.target.add_instruction(SwitchCaseOp, name="switch_case") transpiled = transpile( self._control_flow_circuit(), backend=FakeGeneric(num_qubits=27, dynamic=True), @@ -1986,7 +1980,7 @@ def callback(**kwargs): vf2_post_layout_called = True self.assertIsNotNone(kwargs["property_set"]["post_layout"]) - backend = FakeGeneric(num_qubits=5, coupling_map=[[1, 0], [2, 0], [2, 1], [3, 2], [3, 4], [4, 2]]) + backend = FakeGeneric(num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]]) qubits = 3 qc = QuantumCircuit(qubits) for i in range(5): @@ -2146,10 +2140,10 @@ def run(self, dag): backend = FakeGeneric(num_qubits=4) - # This target has PulseQobj entries that provides a serialized schedule data + # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) pm = PassManager(passes=[pass_]) - # self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) + self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) qc = QuantumCircuit(1) qc.sx(0) @@ -2764,7 +2758,7 @@ def _visit_block(circuit, qubit_mapping=None): )[0] # The first node should be a measurement self.assertIsInstance(first_meas_node.op, Measure) - # This shoulde be in the first ocmponent + # This should be in the first component self.assertIn(qubit_map[first_meas_node.qargs[0]], components[0]) op_node = tqc_dag._multi_graph.find_successors_by_edge( first_meas_node._node_id, lambda edge_data: isinstance(edge_data, Clbit) From 680c3ce1c4ca72a549b774b96d6fcab1cc7d4d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 2 Oct 2023 19:30:52 +0200 Subject: [PATCH 11/56] Add calibration schedules, fix transpiler tests --- .../providers/fake_provider/fake_generic.py | 162 +++++++- test/python/compiler/test_transpiler.py | 393 +++++++++--------- 2 files changed, 340 insertions(+), 215 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index a14d4e693112..45f2fe08d329 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -31,11 +31,17 @@ BreakLoopOp, ContinueLoopOp, ) +from qiskit.providers.models import ( + PulseDefaults, + Command, +) +from qiskit.qobj import PulseQobjInstruction class FakeGeneric(BackendV2): """ - Generate a generic fake backend, this backend will have properties and configuration according to the settings passed in the argument. + Generate a generic fake backend, this backend will have properties and configuration according + to the settings passed in the argument. Arguments: num_qubits: @@ -50,7 +56,8 @@ class FakeGeneric(BackendV2): This map will be in accordance with the argument coupling_map_type. coupling_map_type: - Pass in the type of coupling map to be generated. If coupling map is passed then this option will be overriden. + Pass in the type of coupling map to be generated. If coupling map is passed + then this option will be overriden. Valid types of coupling map: 'grid', 'heavy_hex' Heavy Hex Lattice Reference: @@ -82,6 +89,9 @@ class FakeGeneric(BackendV2): The system time resolution of input signals in seconds. Default is 0.2222ns + skip_calibration_gates: + Optional list of gates where we do not wish to append a calibration schedule. + Returns: @@ -104,18 +114,20 @@ def __init__( replace_cx_with_ecr: bool = True, enable_reset: bool = True, dt: float = 0.222e-9, + skip_calibration_gates: Optional[List[str]] = [], ): super().__init__( provider=None, name="fake_generic", - description=f"This {num_qubits} qubit fake generic device, with generic settings has been generated right now!", + description=f"This {num_qubits} qubit fake generic device, " + f"with generic settings has been generated right now!", backend_version="", ) self.basis_gates = basis_gates - self.__rng = np.random.default_rng(seed=123456789123456) - self.__coupling_map_type = coupling_map_type + self._rng = np.random.default_rng(seed=123456789123456) + self._coupling_map_type = coupling_map_type if replace_cx_with_ecr: self.basis_gates = list(map(lambda gate: gate.replace("cx", "ecr"), basis_gates)) @@ -125,13 +137,13 @@ def __init__( self.basis_gates.append("measure") if not coupling_map: - if self.__coupling_map_type == "heavy_hex": + if self._coupling_map_type == "heavy_hex": distance = self._get_cmap_args(num_qubits=num_qubits) coupling_map = CouplingMap().from_heavy_hex( distance=distance, bidirectional=bidirectional_cp_mp ) - elif self.__coupling_map_type == "grid": + elif self._coupling_map_type == "grid": num_rows, num_columns = self._get_cmap_args(num_qubits=num_qubits) coupling_map = CouplingMap().from_grid( num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cp_mp @@ -147,9 +159,9 @@ def __init__( dt=dt, qubit_properties=[ QubitProperties( - t1=self.__rng.uniform(100e-6, 200e-6), - t2=self.__rng.uniform(100e-6, 200e-6), - frequency=self.__rng.uniform(5e9, 5.5e9), + t1=self._rng.uniform(100e-6, 200e-6), + t2=self._rng.uniform(100e-6, 200e-6), + frequency=self._rng.uniform(5e9, 5.5e9), ) for _ in range(num_qubits) ], @@ -175,6 +187,25 @@ def __init__( Reset(), {(qubit_idx,): None for qubit_idx in range(num_qubits)} ) + defaults = self._build_calibration_defaults(skip_calibration_gates) + + inst_map = defaults.instruction_schedule_map + for inst in inst_map.instructions: + for qarg in inst_map.qubits_with_instruction(inst): + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + # Do NOT call .get method. This parses Qpbj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = inst_map._get_calibration_entry(inst, qargs) + if inst in self._target._gate_map: + if inst == "measure": + for qubit in qargs: + self._target._gate_map[inst][(qubit,)].calibration = calibration_entry + elif qargs in self._target._gate_map[inst] and inst != "delay": + self._target._gate_map[inst][qargs].calibration = calibration_entry + @property def target(self): return self._target @@ -184,7 +215,7 @@ def max_circuits(self): return None def _get_cmap_args(self, num_qubits): - if self.__coupling_map_type == "heavy_hex": + if self._coupling_map_type == "heavy_hex": for d in range(3, 20, 2): # The description of the formula: 5*d**2 - 2*d -1 is explained in # https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 @@ -192,20 +223,21 @@ def _get_cmap_args(self, num_qubits): if n >= num_qubits: return int(d) - elif self.__coupling_map_type == "grid": + elif self._coupling_map_type == "grid": factors = [x for x in range(2, num_qubits + 1) if num_qubits % x == 0] first_factor = statistics.median_high(factors) second_factor = int(num_qubits / first_factor) return (first_factor, second_factor) def _get_instruction_dict(self, num_qubits, coupling_map): + instruction_dict = { "ecr": ( ECRGate(), { edge: InstructionProperties( - error=self.__rng.uniform(1e-5, 5e-3), - duration=self.__rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-5, 5e-3), + duration=self._rng.uniform(1e-8, 9e-7), ) for edge in coupling_map }, @@ -214,8 +246,8 @@ def _get_instruction_dict(self, num_qubits, coupling_map): CXGate(), { edge: InstructionProperties( - error=self.__rng.uniform(1e-5, 5e-3), - duration=self.__rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-5, 5e-3), + duration=self._rng.uniform(1e-8, 9e-7), ) for edge in coupling_map }, @@ -238,8 +270,8 @@ def _get_instruction_dict(self, num_qubits, coupling_map): SXGate(), { (qubit_idx,): InstructionProperties( - error=self.__rng.uniform(1e-6, 1e-4), - duration=self.__rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-6, 1e-4), + duration=self._rng.uniform(1e-8, 9e-7), ) for qubit_idx in range(num_qubits) }, @@ -248,8 +280,8 @@ def _get_instruction_dict(self, num_qubits, coupling_map): XGate(), { (qubit_idx,): InstructionProperties( - error=self.__rng.uniform(1e-6, 1e-4), - duration=self.__rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-6, 1e-4), + duration=self._rng.uniform(1e-8, 9e-7), ) for qubit_idx in range(num_qubits) }, @@ -258,8 +290,8 @@ def _get_instruction_dict(self, num_qubits, coupling_map): Measure(), { (qubit_idx,): InstructionProperties( - error=self.__rng.uniform(1e-3, 1e-1), - duration=self.__rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-3, 1e-1), + duration=self._rng.uniform(1e-8, 9e-7), ) for qubit_idx in range(num_qubits) }, @@ -275,6 +307,92 @@ def _get_instruction_dict(self, num_qubits, coupling_map): def _default_options(cls): return Options(shots=1024) + def _build_calibration_defaults(self, skip_calibration_gates) -> PulseDefaults: + """Build calibration defaults.""" + + qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() + meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() + pulse_library = [ + {"name": "test_pulse_1", "samples": [[0.0, 0.0], [0.0, 0.1]]}, + {"name": "test_pulse_2", "samples": [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]}, + {"name": "test_pulse_3", "samples": [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]]}, + { + "name": "test_pulse_4", + "samples": 7 * [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]], + }, + ] + + measure_command_sequence = [ + PulseQobjInstruction( + name="acquire", + duration=10, + t0=0, + qubits=list(range(self.num_qubits)), + memory_slot=list(range(self.num_qubits)), + ).to_dict() + ] + measure_command_sequence += [ + PulseQobjInstruction(name="test_pulse_1", ch=f"m{i}", t0=0).to_dict() + for i in range(self.num_qubits) + ] + + measure_command = Command.from_dict( + { + "name": "measure", + "qubits": list(range(self.num_qubits)), + "sequence": measure_command_sequence, + } + ).to_dict() + + cmd_def = [measure_command] + + for gate in self.basis_gates: + for i in range(self.num_qubits): + sequence = [] + if gate not in skip_calibration_gates: + sequence = [ + PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0").to_dict(), + PulseQobjInstruction(name="test_pulse_3", ch=f"d{i}", t0=0).to_dict(), + ] + cmd_def.append( + Command.from_dict( + { + "name": gate, + "qubits": [i], + "sequence": sequence, + } + ).to_dict() + ) + + for qubit1, qubit2 in self.coupling_map: + sequence = [] + if gate not in skip_calibration_gates: + sequence = [ + PulseQobjInstruction(name="test_pulse_1", ch=f"d{qubit1}", t0=0).to_dict(), + PulseQobjInstruction(name="test_pulse_2", ch=f"u{qubit1}", t0=10).to_dict(), + PulseQobjInstruction(name="test_pulse_1", ch=f"d{qubit2}", t0=20).to_dict(), + PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1).to_dict(), + ] + cmd_def += [ + Command.from_dict( + { + "name": "cx", + "qubits": [qubit1, qubit2], + "sequence": sequence, + } + ).to_dict() + ] + + return PulseDefaults.from_dict( + { + "qubit_freq_est": qubit_freq_est, + "meas_freq_est": meas_freq_est, + "buffer": 0, + "pulse_library": pulse_library, + "cmd_def": cmd_def, + } + ) + def run(self, circuit, **kwargs): noise_model = None return BasicAer.get_backend("qasm_simulator").run(circuit, **kwargs) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 3623eb204a59..1a9fdfa55b64 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -35,7 +35,6 @@ IfElseOp, Parameter, Qubit, - Reset, SwitchCaseOp, WhileLoopOp, ) @@ -1763,193 +1762,193 @@ def _control_flow_expr_circuit(self): base.append(CustomCX(), [3, 4]) return base - @data(0, 1, 2, 3) - def test_qpy_roundtrip(self, optimization_level): - """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be round-tripped - through QPY.""" - if optimization_level == 3 and sys.platform == "win32": - self.skipTest( - "This test case triggers a bug in the eigensolver routine on windows. " - "See #10345 for more details." - ) - - backend = FakeGeneric(num_qubits=8, dynamic=True) - transpiled = transpile( - self._control_flow_circuit(), - backend=backend, - basis_gates=backend.operation_names, - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be round-tripped - through QPY.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_expr(self, optimization_level): - """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - be round-tripped through QPY.""" - if optimization_level == 3 and sys.platform == "win32": - self.skipTest( - "This test case triggers a bug in the eigensolver routine on windows. " - "See #10345 for more details." - ) - backend = FakeGeneric(num_qubits=16) - transpiled = transpile( - self._control_flow_expr_circuit(), - backend=backend, - basis_gates=backend.basis_gates - + ["if_else", "for_loop", "while_loop", "switch_case"], - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - be round-tripped through QPY.""" - backend = FakeGeneric(num_qubits=27) - backend.target.add_instruction(IfElseOp, name="if_else") - backend.target.add_instruction(ForLoopOp, name="for_loop") - backend.target.add_instruction(WhileLoopOp, name="while_loop") - backend.target.add_instruction(SwitchCaseOp, name="switch_case") - transpiled = transpile( - self._control_flow_circuit(), - backend=backend, - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qasm3_output(self, optimization_level): - """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) - - @data(0, 1, 2, 3) - def test_qasm3_output_control_flow(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be dumped into - OpenQASM 3.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance( - qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - str, - ) - - @data(0, 1, 2, 3) - def test_qasm3_output_control_flow_expr(self, optimization_level): - """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be - dumped into OpenQASM 3.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=27, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance( - qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - str, - ) - - @data(0, 1, 2, 3) - def test_transpile_target_no_measurement_error(self, opt_level): - """Test that transpile with a target which contains ideal measurement works - - Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 - """ - target = Target() - target.add_instruction(Measure(), {(0,): None}) - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - res = transpile(qc, target=target, optimization_level=opt_level) - self.assertEqual(qc, res) + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip(self, optimization_level): + # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be round-tripped + # through QPY.""" + # if optimization_level == 3 and sys.platform == "win32": + # self.skipTest( + # "This test case triggers a bug in the eigensolver routine on windows. " + # "See #10345 for more details." + # ) + # + # backend = FakeGeneric(num_qubits=8, dynamic=True) + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=backend, + # basis_gates=backend.operation_names, + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be round-tripped + # through QPY.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=8, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_expr(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + # be round-tripped through QPY.""" + # if optimization_level == 3 and sys.platform == "win32": + # self.skipTest( + # "This test case triggers a bug in the eigensolver routine on windows. " + # "See #10345 for more details." + # ) + # backend = FakeGeneric(num_qubits=16) + # transpiled = transpile( + # self._control_flow_expr_circuit(), + # backend=backend, + # basis_gates=backend.basis_gates + # + ["if_else", "for_loop", "while_loop", "switch_case"], + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + # be round-tripped through QPY.""" + # backend = FakeGeneric(num_qubits=27) + # backend.target.add_instruction(IfElseOp, name="if_else") + # backend.target.add_instruction(ForLoopOp, name="for_loop") + # backend.target.add_instruction(WhileLoopOp, name="while_loop") + # backend.target.add_instruction(SwitchCaseOp, name="switch_case") + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=backend, + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output(self, optimization_level): + # """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output_control_flow(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be dumped into + # OpenQASM 3.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=8, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance( + # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + # str, + # ) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output_control_flow_expr(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be + # dumped into OpenQASM 3.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=27, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance( + # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + # str, + # ) + # + # @data(0, 1, 2, 3) + # def test_transpile_target_no_measurement_error(self, opt_level): + # """Test that transpile with a target which contains ideal measurement works + # + # Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 + # """ + # target = Target() + # target.add_instruction(Measure(), {(0,): None}) + # qc = QuantumCircuit(1, 1) + # qc.measure(0, 0) + # res = transpile(qc, target=target, optimization_level=opt_level) + # self.assertEqual(qc, res) def test_transpile_final_layout_updated_with_post_layout(self): """Test that the final layout is correctly set when vf2postlayout runs. @@ -1980,7 +1979,10 @@ def callback(**kwargs): vf2_post_layout_called = True self.assertIsNotNone(kwargs["property_set"]["post_layout"]) - backend = FakeGeneric(num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]]) + backend = FakeGeneric( + num_qubits=5, + coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], + ) qubits = 3 qc = QuantumCircuit(qubits) for i in range(5): @@ -1988,7 +1990,7 @@ def callback(**kwargs): tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) self.assertTrue(vf2_post_layout_called) - self.assertEqual([3, 2, 1], _get_index_layout(tqc, qubits)) + self.assertEqual([1, 4, 3], _get_index_layout(tqc, qubits)) class StreamHandlerRaiseException(StreamHandler): @@ -2100,7 +2102,7 @@ def test_parallel_with_target(self, opt_level): @data(0, 1, 2, 3) def test_parallel_dispatch(self, opt_level): """Test that transpile in parallel works for all optimization levels.""" - backend = FakeGeneric(num_qubits=19, replace_cx_with_ecr=False) + backend = FakeGeneric(num_qubits=16, replace_cx_with_ecr=False) qr = QuantumRegister(16) cr = ClassicalRegister(16) qc = QuantumCircuit(qr, cr) @@ -2138,11 +2140,13 @@ def run(self, dag): ) return dag - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(num_qubits=4, skip_calibration_gates=["sx"]) # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) pm = PassManager(passes=[pass_]) + print(backend.target["sx"][(0,)]) + print(backend.target["sx"][(0,)]._calibration._definition) self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) qc = QuantumCircuit(1) @@ -2247,7 +2251,10 @@ def test_transpile_with_multiple_coupling_maps(self): @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric( + num_qubits=4, + coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], + ) inst_map = InstructionScheduleMap() inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) newgate = Gate("newgate", 2, []) From 6131e31c92236897bd8f3fee092baa20eb9d2fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 3 Oct 2023 09:42:41 +0200 Subject: [PATCH 12/56] Fix lint --- qiskit/providers/fake_provider/fake_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 45f2fe08d329..308c180ead97 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -129,7 +129,7 @@ def __init__( self._rng = np.random.default_rng(seed=123456789123456) self._coupling_map_type = coupling_map_type if replace_cx_with_ecr: - self.basis_gates = list(map(lambda gate: gate.replace("cx", "ecr"), basis_gates)) + self.basis_gates = [gate.replace("cx", "ecr") for gate in basis_gates] if "delay" not in basis_gates: self.basis_gates.append("delay") From 04914f2f19c5b67f489100abc39e18a24c210440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 5 Oct 2023 14:21:17 +0200 Subject: [PATCH 13/56] Refactor, add pulse functionality --- .../providers/fake_provider/fake_generic.py | 501 +++++++++++------- 1 file changed, 299 insertions(+), 202 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 308c180ead97..2ff708849ff9 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -10,18 +10,16 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Generic FakeBackendV2 class""" -import numpy as np +from __future__ import annotations import statistics -from typing import Optional, List, Tuple -from qiskit.transpiler import CouplingMap -from qiskit.providers.basicaer import BasicAer -from qiskit.transpiler import Target, InstructionProperties, QubitProperties -from qiskit.providers.backend import BackendV2 -from qiskit.providers.options import Options -from qiskit.exceptions import QiskitError -from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate +from collections.abc import Iterable + +import numpy as np + +from qiskit import pulse from qiskit.circuit import Measure, Parameter, Delay, Reset from qiskit.circuit.controlflow import ( IfElseOp, @@ -31,11 +29,19 @@ BreakLoopOp, ContinueLoopOp, ) +from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate + +from qiskit.exceptions import QiskitError +from qiskit.transpiler import CouplingMap, Target, InstructionProperties, QubitProperties +from qiskit.providers.basicaer import BasicAer +from qiskit.providers.backend import BackendV2 from qiskit.providers.models import ( PulseDefaults, Command, ) -from qiskit.qobj import PulseQobjInstruction +from qiskit.providers.options import Options +from qiskit.pulse import InstructionScheduleMap +from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem class FakeGeneric(BackendV2): @@ -44,118 +50,80 @@ class FakeGeneric(BackendV2): to the settings passed in the argument. Arguments: - num_qubits: - Pass in the integer which is the number of qubits of the backend. + num_qubits: Pass in the integer which is the number of qubits of the backend. Example: num_qubits = 19 - coupling_map: - Pass in the coupling Map of the backend as a list of tuples. - Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. + coupling_map: Pass in the coupling Map of the backend as a list of tuples. + Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. If None passed then the + coupling map will be generated randomly + This map will be in accordance with the argument coupling_map_type. - If None passed then the coupling map will be generated. - This map will be in accordance with the argument coupling_map_type. + coupling_map_type: Pass in the type of coupling map to be generated. If coupling map + is passed, then this option will be overriden. Valid types of coupling + map: 'grid', 'heavy_hex'. Heavy Hex Lattice Reference: + https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 - coupling_map_type: - Pass in the type of coupling map to be generated. If coupling map is passed - then this option will be overriden. - Valid types of coupling map: 'grid', 'heavy_hex' + basis_gates: Pass in the basis gates of the backend as list of strings. + Example: ['cx', 'id', 'rz', 'sx', 'x'] --> + This is the default basis gates of the backend. - Heavy Hex Lattice Reference: - https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 + dynamic: Enable/Disable dynamic circuits on this backend. True: Enable, + False: Disable (Default) - - basis_gates: - Pass in the basis gates of the backend as list of strings. - Example: ['cx', 'id', 'rz', 'sx', 'x'] --> This is the default basis gates of the backend. - - dynamic: - Enable/Disable dynamic circuits on this backend. - True: Enable - False: Disable (Default) - - bidirectional_cp_mp: - Enable/Disable bi-directional coupling map. + bidirectional_cp_mp: Enable/Disable bi-directional coupling map. True: Enable False: Disable (Default) - replace_cx_with_ecr: - True: (Default) Replace every occurrence of 'cx' with 'ecr' + replace_cx_with_ecr: True: (Default) Replace every occurrence of 'cx' with 'ecr' False: Do not replace 'cx' with 'ecr' - enable_reset: - True: (Default) this enables the reset on the backend + enable_reset: True: (Default) this enables the reset on the backend False: This disables the reset on the backend - dt: - The system time resolution of input signals in seconds. + dt: The system time resolution of input signals in seconds. Default is 0.2222ns - skip_calibration_gates: - Optional list of gates where we do not wish to append a calibration schedule. - - - + skip_calibration_gates: Optional list of gates where we do not wish to + append a calibration schedule. Returns: None Raises: QiskitError: If argument basis_gates has a gate which is not a valid basis gate. - - """ def __init__( self, - num_qubits: int, - coupling_map: Optional[List[Tuple[str, str]]] = None, - coupling_map_type: Optional[str] = "grid", - basis_gates: List[str] = ["cx", "id", "rz", "sx", "x"], + num_qubits: int = None, + *, + coupling_map: list | tuple[str, str] = None, + coupling_map_type: str = "grid", + basis_gates: list[str] = None, dynamic: bool = False, - bidirectional_cp_mp: bool = False, + bidirectional_cmap: bool = False, replace_cx_with_ecr: bool = True, enable_reset: bool = True, dt: float = 0.222e-9, - skip_calibration_gates: Optional[List[str]] = [], + skip_calibration_gates: list[str] = None, + instruction_schedule_map: InstructionScheduleMap = None, ): super().__init__( provider=None, name="fake_generic", - description=f"This {num_qubits} qubit fake generic device, " - f"with generic settings has been generated right now!", + description=f"This is a {num_qubits} qubit fake device, " + f"with generic settings. It has been generated right now!", backend_version="", ) - self.basis_gates = basis_gates - self._rng = np.random.default_rng(seed=123456789123456) - self._coupling_map_type = coupling_map_type - if replace_cx_with_ecr: - self.basis_gates = [gate.replace("cx", "ecr") for gate in basis_gates] - - if "delay" not in basis_gates: - self.basis_gates.append("delay") - if "measure" not in basis_gates: - self.basis_gates.append("measure") + self._rng = np.random.default_rng(seed=42) + self._num_qubits = num_qubits - if not coupling_map: - if self._coupling_map_type == "heavy_hex": - distance = self._get_cmap_args(num_qubits=num_qubits) - coupling_map = CouplingMap().from_heavy_hex( - distance=distance, bidirectional=bidirectional_cp_mp - ) - - elif self._coupling_map_type == "grid": - num_rows, num_columns = self._get_cmap_args(num_qubits=num_qubits) - coupling_map = CouplingMap().from_grid( - num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cp_mp - ) - else: - coupling_map = CouplingMap(coupling_map) - - num_qubits = coupling_map.size() + self._set_basis_gates(basis_gates, replace_cx_with_ecr) + self._set_coupling_map(coupling_map, coupling_map_type, bidirectional_cmap) self._target = Target( description="Fake Generic Backend", - num_qubits=num_qubits, + num_qubits=self._num_qubits, dt=dt, qubit_properties=[ QubitProperties( @@ -165,46 +133,12 @@ def __init__( ) for _ in range(num_qubits) ], + concurrent_measurements=[list(range(num_qubits))], ) - instruction_dict = self._get_instruction_dict(num_qubits, coupling_map) - for gate in self.basis_gates: - try: - self._target.add_instruction(*instruction_dict[gate]) - except: - raise QiskitError(f"{gate} is not a valid basis gate") - - if dynamic: - self._target.add_instruction(IfElseOp, name="if_else") - self._target.add_instruction(WhileLoopOp, name="while_loop") - self._target.add_instruction(ForLoopOp, name="for_loop") - self._target.add_instruction(SwitchCaseOp, name="switch_case") - self._target.add_instruction(BreakLoopOp, name="break") - self._target.add_instruction(ContinueLoopOp, name="continue") - - if enable_reset: - self._target.add_instruction( - Reset(), {(qubit_idx,): None for qubit_idx in range(num_qubits)} - ) - - defaults = self._build_calibration_defaults(skip_calibration_gates) - - inst_map = defaults.instruction_schedule_map - for inst in inst_map.instructions: - for qarg in inst_map.qubits_with_instruction(inst): - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - # Do NOT call .get method. This parses Qpbj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = inst_map._get_calibration_entry(inst, qargs) - if inst in self._target._gate_map: - if inst == "measure": - for qubit in qargs: - self._target._gate_map[inst][(qubit,)].calibration = calibration_entry - elif qargs in self._target._gate_map[inst] and inst != "delay": - self._target._gate_map[inst][qargs].calibration = calibration_entry + self._add_gate_instructions_to_target(dynamic, enable_reset) + self._add_calibration_defaults_to_target(instruction_schedule_map, skip_calibration_gates) + self._build_default_channels() @property def target(self): @@ -214,23 +148,155 @@ def target(self): def max_circuits(self): return None - def _get_cmap_args(self, num_qubits): - if self._coupling_map_type == "heavy_hex": + @property + def meas_map(self) -> list[list[int]]: + return self._target.concurrent_measurements + + def drive_channel(self, qubit: int): + """Return the drive channel for the given qubit. + + Returns: + DriveChannel: The Qubit drive channel + """ + drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) + qubits = (qubit,) + if qubits in drive_channels_map: + return drive_channels_map[qubits][0] + return None + + def measure_channel(self, qubit: int): + """Return the measure stimulus channel for the given qubit. + + Returns: + MeasureChannel: The Qubit measurement stimulus line + """ + measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) + qubits = (qubit,) + if qubits in measure_channels_map: + return measure_channels_map[qubits][0] + return None + + def acquire_channel(self, qubit: int): + """Return the acquisition channel for the given qubit. + + Returns: + AcquireChannel: The Qubit measurement acquisition line. + """ + acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) + qubits = (qubit,) + if qubits in acquire_channels_map: + return acquire_channels_map[qubits][0] + return None + + def control_channel(self, qubits: Iterable[int]): + """Return the secondary drive channel for the given qubit + + This is typically utilized for controlling multiqubit interactions. + This channel is derived from other channels. + + Args: + qubits: Tuple or list of qubits of the form + ``(control_qubit, target_qubit)``. + + Returns: + List[ControlChannel]: The multi qubit control line. + """ + control_channels_map = getattr(self, "channels_map", {}).get("control", {}) + qubits = tuple(qubits) + if qubits in control_channels_map: + return control_channels_map[qubits] + return [] + + def run(self, circuit, **kwargs): + return BasicAer.get_backend("qasm_simulator").run(circuit, **kwargs) + + @classmethod + def _default_options(cls): + return Options(shots=1024) + + def _set_basis_gates(self, basis_gates: list[str] = None, replace_cx_with_ecr=None): + + default_gates = ["cx", "id", "rz", "sx", "x"] + + if basis_gates is not None: + self._basis_gates = basis_gates + else: + self._basis_gates = default_gates + + if replace_cx_with_ecr: + self._basis_gates = [gate.replace("cx", "ecr") for gate in self._basis_gates] + + if "delay" not in self._basis_gates: + self._basis_gates.append("delay") + if "measure" not in self._basis_gates: + self._basis_gates.append("measure") + + def _set_coupling_map(self, coupling_map, coupling_map_type, bidirectional_cmap): + + if not coupling_map: + if not self._num_qubits: + raise QiskitError( + "Please provide either `num_qubits` or `coupling_map` " + "to generate a new fake backend." + ) + + if coupling_map_type == "heavy_hex": + distance = self._get_cmap_args(coupling_map_type) + self._coupling_map = CouplingMap().from_heavy_hex( + distance=distance, bidirectional=bidirectional_cmap + ) + elif coupling_map_type == "grid": + num_rows, num_columns = self._get_cmap_args(coupling_map_type) + self._coupling_map = CouplingMap().from_grid( + num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cmap + ) + else: + raise QiskitError("Provided coupling map type not recognized") + else: + self._coupling_map = CouplingMap(coupling_map) + self._num_qubits = self._coupling_map.size() + + def _get_cmap_args(self, coupling_map_type): + if coupling_map_type == "heavy_hex": for d in range(3, 20, 2): # The description of the formula: 5*d**2 - 2*d -1 is explained in # https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 n = (5 * (d**2) - (2 * d) - 1) / 2 - if n >= num_qubits: - return int(d) + if n >= self._num_qubits: + return d - elif self._coupling_map_type == "grid": - factors = [x for x in range(2, num_qubits + 1) if num_qubits % x == 0] + elif coupling_map_type == "grid": + factors = [x for x in range(2, self._num_qubits + 1) if self._num_qubits % x == 0] first_factor = statistics.median_high(factors) - second_factor = int(num_qubits / first_factor) + second_factor = int(self._num_qubits / first_factor) return (first_factor, second_factor) - def _get_instruction_dict(self, num_qubits, coupling_map): + return None + + def _add_gate_instructions_to_target(self, dynamic, enable_reset): + + instruction_dict = self._get_instruction_dict() + for gate in self._basis_gates: + try: + self._target.add_instruction(*instruction_dict[gate]) + except Exception as exc: + raise QiskitError(f"{gate} is not a valid basis gate") from exc + + if dynamic: + self._target.add_instruction(IfElseOp, name="if_else") + self._target.add_instruction(WhileLoopOp, name="while_loop") + self._target.add_instruction(ForLoopOp, name="for_loop") + self._target.add_instruction(SwitchCaseOp, name="switch_case") + self._target.add_instruction(BreakLoopOp, name="break") + self._target.add_instruction(ContinueLoopOp, name="continue") + + if enable_reset: + self._target.add_instruction( + Reset(), {(qubit_idx,): None for qubit_idx in range(self._num_qubits)} + ) + + def _get_instruction_dict(self): instruction_dict = { "ecr": ( ECRGate(), @@ -239,7 +305,7 @@ def _get_instruction_dict(self, num_qubits, coupling_map): error=self._rng.uniform(1e-5, 5e-3), duration=self._rng.uniform(1e-8, 9e-7), ) - for edge in coupling_map + for edge in self.coupling_map }, ), "cx": ( @@ -249,21 +315,21 @@ def _get_instruction_dict(self, num_qubits, coupling_map): error=self._rng.uniform(1e-5, 5e-3), duration=self._rng.uniform(1e-8, 9e-7), ) - for edge in coupling_map + for edge in self.coupling_map }, ), "id": ( IGate(), { (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(num_qubits) + for qubit_idx in range(self._num_qubits) }, ), "rz": ( RZGate(Parameter("theta")), { (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(num_qubits) + for qubit_idx in range(self._num_qubits) }, ), "sx": ( @@ -273,7 +339,7 @@ def _get_instruction_dict(self, num_qubits, coupling_map): error=self._rng.uniform(1e-6, 1e-4), duration=self._rng.uniform(1e-8, 9e-7), ) - for qubit_idx in range(num_qubits) + for qubit_idx in range(self._num_qubits) }, ), "x": ( @@ -283,7 +349,7 @@ def _get_instruction_dict(self, num_qubits, coupling_map): error=self._rng.uniform(1e-6, 1e-4), duration=self._rng.uniform(1e-8, 9e-7), ) - for qubit_idx in range(num_qubits) + for qubit_idx in range(self._num_qubits) }, ), "measure": ( @@ -293,106 +359,137 @@ def _get_instruction_dict(self, num_qubits, coupling_map): error=self._rng.uniform(1e-3, 1e-1), duration=self._rng.uniform(1e-8, 9e-7), ) - for qubit_idx in range(num_qubits) + for qubit_idx in range(self._num_qubits) }, ), "delay": ( Delay(Parameter("Time")), - {(qubit_idx,): None for qubit_idx in range(num_qubits)}, + {(qubit_idx,): None for qubit_idx in range(self._num_qubits)}, ), } return instruction_dict - @classmethod - def _default_options(cls): - return Options(shots=1024) + def _add_calibration_defaults_to_target(self, instruction_schedule_map, skip_calibration_gates): + + if skip_calibration_gates is None: + skip_calibration_gates = [] + + if not instruction_schedule_map: + defaults = self._build_calibration_defaults(skip_calibration_gates) + inst_map = defaults.instruction_schedule_map + else: + inst_map = instruction_schedule_map + + for inst in inst_map.instructions: + for qarg in inst_map.qubits_with_instruction(inst): + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + # Do NOT call .get method. This parses Qpbj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = inst_map._get_calibration_entry(inst, qargs) + if inst in self._target: + if inst == "measure": + for qubit in qargs: + self._target[inst][(qubit,)].calibration = calibration_entry + elif qargs in self._target[inst] and inst != "delay": + self._target[inst][qargs].calibration = calibration_entry def _build_calibration_defaults(self, skip_calibration_gates) -> PulseDefaults: """Build calibration defaults.""" - qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() - meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() - pulse_library = [ - {"name": "test_pulse_1", "samples": [[0.0, 0.0], [0.0, 0.1]]}, - {"name": "test_pulse_2", "samples": [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]}, - {"name": "test_pulse_3", "samples": [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]]}, - { - "name": "test_pulse_4", - "samples": 7 * [[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]], - }, - ] - measure_command_sequence = [ PulseQobjInstruction( name="acquire", - duration=10, + duration=1792, t0=0, qubits=list(range(self.num_qubits)), memory_slot=list(range(self.num_qubits)), - ).to_dict() + ) ] + measure_command_sequence += [ - PulseQobjInstruction(name="test_pulse_1", ch=f"m{i}", t0=0).to_dict() + PulseQobjInstruction(name="pulse_1", ch=f"m{i}", duration=1792, t0=0) for i in range(self.num_qubits) ] - measure_command = Command.from_dict( - { - "name": "measure", - "qubits": list(range(self.num_qubits)), - "sequence": measure_command_sequence, - } - ).to_dict() + measure_command = Command( + name="measure", + qubits=list(range(self.num_qubits)), + sequence=measure_command_sequence, + ) cmd_def = [measure_command] - for gate in self.basis_gates: + for gate in self._basis_gates: for i in range(self.num_qubits): sequence = [] if gate not in skip_calibration_gates: sequence = [ - PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0").to_dict(), - PulseQobjInstruction(name="test_pulse_3", ch=f"d{i}", t0=0).to_dict(), + PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), ] cmd_def.append( - Command.from_dict( - { - "name": gate, - "qubits": [i], - "sequence": sequence, - } - ).to_dict() + Command( + name=gate, + qubits=[i], + sequence=sequence, + ) ) - for qubit1, qubit2 in self.coupling_map: - sequence = [] - if gate not in skip_calibration_gates: - sequence = [ - PulseQobjInstruction(name="test_pulse_1", ch=f"d{qubit1}", t0=0).to_dict(), - PulseQobjInstruction(name="test_pulse_2", ch=f"u{qubit1}", t0=10).to_dict(), - PulseQobjInstruction(name="test_pulse_1", ch=f"d{qubit2}", t0=20).to_dict(), - PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1).to_dict(), - ] - cmd_def += [ - Command.from_dict( - { - "name": "cx", - "qubits": [qubit1, qubit2], - "sequence": sequence, - } - ).to_dict() - ] - - return PulseDefaults.from_dict( - { - "qubit_freq_est": qubit_freq_est, - "meas_freq_est": meas_freq_est, - "buffer": 0, - "pulse_library": pulse_library, - "cmd_def": cmd_def, - } + for qubit1, qubit2 in self.coupling_map: + sequence = [] + if gate not in skip_calibration_gates: + sequence = [ + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), + PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), + ] + if "cx" in self._basis_gates: + cmd_def += [ + Command( + name="cx", + qubits=[qubit1, qubit2], + sequence=sequence, + ) + ] + if "ecr" in self._basis_gates: + cmd_def += [ + Command( + name="ecr", + qubits=[qubit1, qubit2], + sequence=sequence, + ) + ] + + qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() + meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() + pulse_library = [ + PulseLibraryItem(name="pulse_1", samples=[[0.0, 0.0], [0.0, 0.1]]), + PulseLibraryItem(name="pulse_2", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]), + PulseLibraryItem( + name="pulse_3", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]] + ), + ] + + return PulseDefaults( + qubit_freq_est=qubit_freq_est, + meas_freq_est=meas_freq_est, + buffer=0, + pulse_library=pulse_library, + cmd_def=cmd_def, ) - def run(self, circuit, **kwargs): - noise_model = None - return BasicAer.get_backend("qasm_simulator").run(circuit, **kwargs) + def _build_default_channels(self): + """Create default channel map and set "channels_map" attribute""" + channels_map = { + "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, + "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, + "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, + "control": { + (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self.coupling_map) + }, + } + setattr(self, "channels_map", channels_map) From 2523707f4bfab5ad7b65b7dd95c6a01332737867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 10 Oct 2023 16:23:59 +0200 Subject: [PATCH 14/56] Latest updates to FakeGeneric --- .../providers/fake_provider/fake_generic.py | 131 +++++++++++++++--- 1 file changed, 115 insertions(+), 16 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 2ff708849ff9..b12e996f5f77 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -14,13 +14,14 @@ from __future__ import annotations import statistics +import warnings from collections.abc import Iterable import numpy as np from qiskit import pulse -from qiskit.circuit import Measure, Parameter, Delay, Reset +from qiskit.circuit import Measure, Parameter, Delay, Reset, QuantumCircuit from qiskit.circuit.controlflow import ( IfElseOp, WhileLoopOp, @@ -39,9 +40,9 @@ PulseDefaults, Command, ) -from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem +from qiskit.utils import optionals as _optionals class FakeGeneric(BackendV2): @@ -84,6 +85,8 @@ class FakeGeneric(BackendV2): skip_calibration_gates: Optional list of gates where we do not wish to append a calibration schedule. + seed: )ptional seed for error and duration value generation. + Returns: None @@ -105,6 +108,7 @@ def __init__( dt: float = 0.222e-9, skip_calibration_gates: list[str] = None, instruction_schedule_map: InstructionScheduleMap = None, + seed: int = 42, ): super().__init__( @@ -114,8 +118,9 @@ def __init__( f"with generic settings. It has been generated right now!", backend_version="", ) + self.sim = None - self._rng = np.random.default_rng(seed=42) + self._rng = np.random.default_rng(seed=seed) self._num_qubits = num_qubits self._set_basis_gates(basis_gates, replace_cx_with_ecr) @@ -144,6 +149,10 @@ def __init__( def target(self): return self._target + @property + def coupling_map(self): + return self._coupling_map + @property def max_circuits(self): return None @@ -207,12 +216,101 @@ def control_channel(self, qubits: Iterable[int]): return control_channels_map[qubits] return [] - def run(self, circuit, **kwargs): - return BasicAer.get_backend("qasm_simulator").run(circuit, **kwargs) + def run(self, run_input, **options): + """Run on the fake backend using a simulator. + + This method runs circuit jobs (an individual or a list of QuantumCircuit + ) and pulse jobs (an individual or a list of Schedule or ScheduleBlock) + using BasicAer or Aer simulator and returns a + :class:`~qiskit.providers.Job` object. + + If qiskit-aer is installed, jobs will be run using AerSimulator with + noise model of the fake backend. Otherwise, jobs will be run using + BasicAer simulator without noise. + + Currently noisy simulation of a pulse job is not supported yet in + FakeBackendV2. + + Args: + run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An + individual or a list of + :class:`~qiskit.circuit.QuantumCircuit`, + :class:`~qiskit.pulse.ScheduleBlock`, or + :class:`~qiskit.pulse.Schedule` objects to run on the backend. + options: Any kwarg options to pass to the backend for running the + config. If a key is also present in the options + attribute/object then the expectation is that the value + specified will be used instead of what's set in the options + object. + + Returns: + Job: The job object for the run + + Raises: + QiskitError: If a pulse job is supplied and qiskit-aer is not installed. + """ + circuits = run_input + pulse_job = None + if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): + pulse_job = True + elif isinstance(circuits, QuantumCircuit): + pulse_job = False + elif isinstance(circuits, list): + if circuits: + if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits): + pulse_job = True + elif all(isinstance(x, QuantumCircuit) for x in circuits): + pulse_job = False + if pulse_job is None: # submitted job is invalid + raise QiskitError( + "Invalid input object %s, must be either a " + "QuantumCircuit, Schedule, or a list of either" % circuits + ) + if pulse_job: # pulse job + raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.") + # circuit job + if not _optionals.HAS_AER: + warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) + if self.sim is None: + self._setup_sim() + self.sim._options = self._options + job = self.sim.run(circuits, **options) + return job + + def _setup_sim(self): + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator + from qiskit_aer.noise import NoiseModel + + self.sim = AerSimulator() + noise_model = NoiseModel.from_backend(self) + self.sim.set_options(noise_model=noise_model) + # Update fake backend default too to avoid overwriting + # it when run() is called + self.set_options(noise_model=noise_model) + + else: + self.sim = BasicAer.get_backend("qasm_simulator") @classmethod def _default_options(cls): - return Options(shots=1024) + """Return the default options + + This method will return a :class:`qiskit.providers.Options` + subclass object that will be used for the default options. These + should be the default parameters to use for the options of the + backend. + + Returns: + qiskit.providers.Options: A options object with + default values set + """ + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator + + return AerSimulator._default_options() + else: + return BasicAer.get_backend("qasm_simulator")._default_options() def _set_basis_gates(self, basis_gates: list[str] = None, replace_cx_with_ecr=None): @@ -275,7 +373,7 @@ def _get_cmap_args(self, coupling_map_type): def _add_gate_instructions_to_target(self, dynamic, enable_reset): - instruction_dict = self._get_instruction_dict() + instruction_dict = self._get_default_instruction_dict() for gate in self._basis_gates: try: @@ -296,7 +394,8 @@ def _add_gate_instructions_to_target(self, dynamic, enable_reset): Reset(), {(qubit_idx,): None for qubit_idx in range(self._num_qubits)} ) - def _get_instruction_dict(self): + def _get_default_instruction_dict(self): + instruction_dict = { "ecr": ( ECRGate(), @@ -312,8 +411,8 @@ def _get_instruction_dict(self): CXGate(), { edge: InstructionProperties( - error=self._rng.uniform(1e-5, 5e-3), - duration=self._rng.uniform(1e-8, 9e-7), + error=self._rng.uniform(1e-3, 5e-2), + duration=self._rng.uniform(2e-7, 8e-7), ) for edge in self.coupling_map }, @@ -332,22 +431,22 @@ def _get_instruction_dict(self): for qubit_idx in range(self._num_qubits) }, ), - "sx": ( - SXGate(), + "x": ( + XGate(), { (qubit_idx,): InstructionProperties( error=self._rng.uniform(1e-6, 1e-4), - duration=self._rng.uniform(1e-8, 9e-7), + duration=self._rng.uniform(2e-8, 4e-8), ) for qubit_idx in range(self._num_qubits) }, ), - "x": ( - XGate(), + "sx": ( + SXGate(), { (qubit_idx,): InstructionProperties( error=self._rng.uniform(1e-6, 1e-4), - duration=self._rng.uniform(1e-8, 9e-7), + duration=self._rng.uniform(1e-8, 2e-8), ) for qubit_idx in range(self._num_qubits) }, From 7ec720dcbe1d6fc91f181a0a93a55e78a92918d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 10 Oct 2023 18:25:26 +0200 Subject: [PATCH 15/56] Fix some unit tests --- .../providers/fake_provider/fake_generic.py | 4 + test/python/compiler/test_transpiler.py | 376 +++++++++--------- 2 files changed, 192 insertions(+), 188 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index b12e996f5f77..0238f99ea709 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -153,6 +153,10 @@ def target(self): def coupling_map(self): return self._coupling_map + @property + def basis_gates(self): + return self._basis_gates + @property def max_circuits(self): return None diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 1a9fdfa55b64..c299176a2835 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1762,193 +1762,192 @@ def _control_flow_expr_circuit(self): base.append(CustomCX(), [3, 4]) return base - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip(self, optimization_level): - # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be round-tripped - # through QPY.""" - # if optimization_level == 3 and sys.platform == "win32": - # self.skipTest( - # "This test case triggers a bug in the eigensolver routine on windows. " - # "See #10345 for more details." - # ) - # - # backend = FakeGeneric(num_qubits=8, dynamic=True) - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=backend, - # basis_gates=backend.operation_names, - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be round-tripped - # through QPY.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=8, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_expr(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - # be round-tripped through QPY.""" - # if optimization_level == 3 and sys.platform == "win32": - # self.skipTest( - # "This test case triggers a bug in the eigensolver routine on windows. " - # "See #10345 for more details." - # ) - # backend = FakeGeneric(num_qubits=16) - # transpiled = transpile( - # self._control_flow_expr_circuit(), - # backend=backend, - # basis_gates=backend.basis_gates - # + ["if_else", "for_loop", "while_loop", "switch_case"], - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - # be round-tripped through QPY.""" - # backend = FakeGeneric(num_qubits=27) - # backend.target.add_instruction(IfElseOp, name="if_else") - # backend.target.add_instruction(ForLoopOp, name="for_loop") - # backend.target.add_instruction(WhileLoopOp, name="while_loop") - # backend.target.add_instruction(SwitchCaseOp, name="switch_case") - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=backend, - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output(self, optimization_level): - # """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output_control_flow(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be dumped into - # OpenQASM 3.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=8, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance( - # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - # str, - # ) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output_control_flow_expr(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be - # dumped into OpenQASM 3.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=27, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance( - # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - # str, - # ) - # - # @data(0, 1, 2, 3) - # def test_transpile_target_no_measurement_error(self, opt_level): - # """Test that transpile with a target which contains ideal measurement works - # - # Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 - # """ - # target = Target() - # target.add_instruction(Measure(), {(0,): None}) - # qc = QuantumCircuit(1, 1) - # qc.measure(0, 0) - # res = transpile(qc, target=target, optimization_level=opt_level) - # self.assertEqual(qc, res) + @data(0, 1, 2, 3) + def test_qpy_roundtrip(self, optimization_level): + """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be round-tripped + through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) + + backend = FakeGeneric(num_qubits=8, dynamic=True) + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + basis_gates=backend.operation_names, + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be round-tripped + through QPY.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=8, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) + backend = FakeGeneric(num_qubits=16) + transpiled = transpile( + self._control_flow_expr_circuit(), + backend=backend, + basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + backend = FakeGeneric(num_qubits=27) + backend.target.add_instruction(IfElseOp, name="if_else") + backend.target.add_instruction(ForLoopOp, name="for_loop") + backend.target.add_instruction(WhileLoopOp, name="while_loop") + backend.target.add_instruction(SwitchCaseOp, name="switch_case") + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qasm3_output(self, optimization_level): + """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) + + @data(0, 1, 2, 3) + def test_qasm3_output_control_flow(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be dumped into + OpenQASM 3.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=8, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance( + qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + str, + ) + + @data(0, 1, 2, 3) + def test_qasm3_output_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be + dumped into OpenQASM 3.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=27, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance( + qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + str, + ) + + @data(0, 1, 2, 3) + def test_transpile_target_no_measurement_error(self, opt_level): + """Test that transpile with a target which contains ideal measurement works + + Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 + """ + target = Target() + target.add_instruction(Measure(), {(0,): None}) + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + res = transpile(qc, target=target, optimization_level=opt_level) + self.assertEqual(qc, res) def test_transpile_final_layout_updated_with_post_layout(self): """Test that the final layout is correctly set when vf2postlayout runs. @@ -1990,6 +1989,7 @@ def callback(**kwargs): tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) self.assertTrue(vf2_post_layout_called) + print("AHA", _get_index_layout(tqc, qubits)) self.assertEqual([1, 4, 3], _get_index_layout(tqc, qubits)) @@ -2252,7 +2252,7 @@ def test_transpile_with_multiple_coupling_maps(self): def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" backend = FakeGeneric( - num_qubits=4, + num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], ) inst_map = InstructionScheduleMap() From 8ce51ee7abfab455d43bfee2c2779bb3201fbfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 11 Oct 2023 08:47:46 +0200 Subject: [PATCH 16/56] Fix some unit tests --- .../providers/fake_provider/fake_generic.py | 9 +- test/python/compiler/test_transpiler.py | 3508 ++++++++--------- 2 files changed, 1759 insertions(+), 1758 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 0238f99ea709..a569ea6ceaed 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -136,9 +136,9 @@ def __init__( t2=self._rng.uniform(100e-6, 200e-6), frequency=self._rng.uniform(5e9, 5.5e9), ) - for _ in range(num_qubits) + for _ in range(self._num_qubits) ], - concurrent_measurements=[list(range(num_qubits))], + concurrent_measurements=[list(range(self._num_qubits))], ) self._add_gate_instructions_to_target(dynamic, enable_reset) @@ -156,7 +156,7 @@ def coupling_map(self): @property def basis_gates(self): return self._basis_gates - + @property def max_circuits(self): return None @@ -356,7 +356,8 @@ def _set_coupling_map(self, coupling_map, coupling_map_type, bidirectional_cmap) raise QiskitError("Provided coupling map type not recognized") else: self._coupling_map = CouplingMap(coupling_map) - self._num_qubits = self._coupling_map.size() + + self._num_qubits = self._coupling_map.size() def _get_cmap_args(self, coupling_map_type): if coupling_map_type == "heavy_hex": diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index c299176a2835..10b4bdb46ace 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -92,1574 +92,1574 @@ def connected_qubits(physical: int, coupling_map: CouplingMap) -> set: raise ValueError(f"physical qubit {physical} is not in the coupling map") -@ddt -class TestTranspile(QiskitTestCase): - """Test transpile function.""" - - def test_empty_transpilation(self): - """Test that transpiling an empty list is a no-op. Regression test of gh-7287.""" - self.assertEqual(transpile([]), []) - - def test_pass_manager_none(self): - """Test passing the default (None) pass manager to the transpiler. - - It should perform the default qiskit flow: - unroll, swap_mapper, cx_direction, cx_cancellation, optimize_1q_gates - and should be equivalent to using tools.compile - """ - qr = QuantumRegister(2, "qr") - circuit = QuantumCircuit(qr) - circuit.h(qr[0]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[1], qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[1], qr[0]) - - coupling_map = [[1, 0]] - basis_gates = ["u1", "u2", "u3", "cx", "id"] - - backend = BasicAer.get_backend("qasm_simulator") - circuit2 = transpile( - circuit, - backend=backend, - coupling_map=coupling_map, - basis_gates=basis_gates, - ) - - circuit3 = transpile( - circuit, backend=backend, coupling_map=coupling_map, basis_gates=basis_gates - ) - self.assertEqual(circuit2, circuit3) - - def test_transpile_basis_gates_no_backend_no_coupling_map(self): - """Verify transpile() works with no coupling_map or backend.""" - qr = QuantumRegister(2, "qr") - circuit = QuantumCircuit(qr) - circuit.h(qr[0]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - - basis_gates = ["u1", "u2", "u3", "cx", "id"] - circuit2 = transpile(circuit, basis_gates=basis_gates, optimization_level=0) - resources_after = circuit2.count_ops() - self.assertEqual({"u2": 2, "cx": 4}, resources_after) - - def test_transpile_non_adjacent_layout(self): - """Transpile pipeline can handle manual layout on non-adjacent qubits. - - circuit: - - .. parsed-literal:: - - ┌───┐ - qr_0: ┤ H ├──■──────────── -> 1 - └───┘┌─┴─┐ - qr_1: ─────┤ X ├──■─────── -> 2 - └───┘┌─┴─┐ - qr_2: ──────────┤ X ├──■── -> 3 - └───┘┌─┴─┐ - qr_3: ───────────────┤ X ├ -> 5 - └───┘ - - device: - 0 - 1 - 2 - 3 - 4 - 5 - 6 - - | | | | | | - - 13 - 12 - 11 - 10 - 9 - 8 - 7 - """ - qr = QuantumRegister(4, "qr") - circuit = QuantumCircuit(qr) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[1], qr[2]) - circuit.cx(qr[2], qr[3]) - - backend = FakeGeneric(num_qubits=6) - initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] - - new_circuit = transpile( - circuit, - basis_gates=backend.operation_names, - coupling_map=backend.coupling_map, - initial_layout=initial_layout, - ) - - qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} - - for instruction in new_circuit.data: - if isinstance(instruction.operation, CXGate): - self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) - - def test_transpile_qft_grid(self): - """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" - qr = QuantumRegister(8) - circuit = QuantumCircuit(qr) - for i, _ in enumerate(qr): - for j in range(i): - circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) - circuit.h(qr[i]) - - backend = FakeGeneric(num_qubits=14) - new_circuit = transpile( - circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map - ) - - qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} - - for instruction in new_circuit.data: - if isinstance(instruction.operation, CXGate): - self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) - - def test_already_mapped_1(self): - """Circuit not remapped if matches topology. - - See: https://github.com/Qiskit/qiskit-terra/issues/342 - """ - backend = FakeGeneric( - num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" - ) - coupling_map = backend.coupling_map - basis_gates = backend.operation_names - - qr = QuantumRegister(19, "qr") - cr = ClassicalRegister(19, "cr") - qc = QuantumCircuit(qr, cr) - qc.cx(qr[0], qr[9]) - qc.cx(qr[1], qr[14]) - qc.h(qr[3]) - qc.cx(qr[10], qr[16]) - qc.x(qr[11]) - qc.cx(qr[5], qr[12]) - qc.cx(qr[7], qr[18]) - qc.cx(qr[6], qr[17]) - qc.measure(qr, cr) - - new_qc = transpile( - qc, - coupling_map=coupling_map, - basis_gates=basis_gates, - initial_layout=Layout.generate_trivial_layout(qr), - ) - qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} - cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] - cx_qubits_physical = [ - [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits - ] - self.assertEqual( - sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] - ) - - def test_already_mapped_via_layout(self): - """Test that a manual layout that satisfies a coupling map does not get altered. - - See: https://github.com/Qiskit/qiskit-terra/issues/2036 - - circuit: - - .. parsed-literal:: - - ┌───┐ ┌───┐ ░ ┌─┐ - qn_0: ┤ H ├──■────────────■──┤ H ├─░─┤M├─── -> 9 - └───┘ │ │ └───┘ ░ └╥┘ - qn_1: ───────┼────────────┼────────░──╫──── -> 6 - │ │ ░ ║ - qn_2: ───────┼────────────┼────────░──╫──── -> 5 - │ │ ░ ║ - qn_3: ───────┼────────────┼────────░──╫──── -> 0 - │ │ ░ ║ - qn_4: ───────┼────────────┼────────░──╫──── -> 1 - ┌───┐┌─┴─┐┌──────┐┌─┴─┐┌───┐ ░ ║ ┌─┐ - qn_5: ┤ H ├┤ X ├┤ P(2) ├┤ X ├┤ H ├─░──╫─┤M├ -> 4 - └───┘└───┘└──────┘└───┘└───┘ ░ ║ └╥┘ - cn: 2/════════════════════════════════╩══╩═ - 0 1 - - device: - 0 -- 1 -- 2 -- 3 -- 4 - | | - 5 -- 6 -- 7 -- 8 -- 9 - | | - 10 - 11 - 12 - 13 - 14 - | | - 15 - 16 - 17 - 18 - 19 - """ - basis_gates = ["u1", "u2", "u3", "cx", "id"] - coupling_map = [ - [0, 1], - [0, 5], - [1, 0], - [1, 2], - [2, 1], - [2, 3], - [3, 2], - [3, 4], - [4, 3], - [4, 9], - [5, 0], - [5, 6], - [5, 10], - [6, 5], - [6, 7], - [7, 6], - [7, 8], - [7, 12], - [8, 7], - [8, 9], - [9, 4], - [9, 8], - [9, 14], - [10, 5], - [10, 11], - [10, 15], - [11, 10], - [11, 12], - [12, 7], - [12, 11], - [12, 13], - [13, 12], - [13, 14], - [14, 9], - [14, 13], - [14, 19], - [15, 10], - [15, 16], - [16, 15], - [16, 17], - [17, 16], - [17, 18], - [18, 17], - [18, 19], - [19, 14], - [19, 18], - ] - - q = QuantumRegister(6, name="qn") - c = ClassicalRegister(2, name="cn") - qc = QuantumCircuit(q, c) - qc.h(q[0]) - qc.h(q[5]) - qc.cx(q[0], q[5]) - qc.p(2, q[5]) - qc.cx(q[0], q[5]) - qc.h(q[0]) - qc.h(q[5]) - qc.barrier(q) - qc.measure(q[0], c[0]) - qc.measure(q[5], c[1]) - - initial_layout = [ - q[3], - q[4], - None, - None, - q[5], - q[2], - q[1], - None, - None, - q[0], - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ] - - new_qc = transpile( - qc, coupling_map=coupling_map, basis_gates=basis_gates, initial_layout=initial_layout - ) - qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} - cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] - cx_qubits_physical = [ - [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits - ] - self.assertEqual(sorted(cx_qubits_physical), [[9, 4], [9, 4]]) - - def test_transpile_bell(self): - """Test Transpile Bell. - - If all correct some should exists. - """ - backend = BasicAer.get_backend("qasm_simulator") - - qubit_reg = QuantumRegister(2, name="q") - clbit_reg = ClassicalRegister(2, name="c") - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) - - circuits = transpile(qc, backend) - self.assertIsInstance(circuits, QuantumCircuit) - - def test_transpile_one(self): - """Test transpile a single circuit. - - Check that the top-level `transpile` function returns - a single circuit.""" - backend = BasicAer.get_backend("qasm_simulator") - - qubit_reg = QuantumRegister(2) - clbit_reg = ClassicalRegister(2) - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) - - circuit = transpile(qc, backend) - self.assertIsInstance(circuit, QuantumCircuit) - - def test_transpile_two(self): - """Test transpile two circuits. - - Check that the transpiler returns a list of two circuits. - """ - backend = BasicAer.get_backend("qasm_simulator") - - qubit_reg = QuantumRegister(2) - clbit_reg = ClassicalRegister(2) - qubit_reg2 = QuantumRegister(2) - clbit_reg2 = ClassicalRegister(2) - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) - qc_extra = QuantumCircuit(qubit_reg, qubit_reg2, clbit_reg, clbit_reg2, name="extra") - qc_extra.measure(qubit_reg, clbit_reg) - circuits = transpile([qc, qc_extra], backend) - self.assertIsInstance(circuits, list) - self.assertEqual(len(circuits), 2) - - for circuit in circuits: - self.assertIsInstance(circuit, QuantumCircuit) - - def test_transpile_singleton(self): - """Test transpile a single-element list with a circuit. - - Check that `transpile` returns a single-element list. - - See https://github.com/Qiskit/qiskit-terra/issues/5260 - """ - backend = BasicAer.get_backend("qasm_simulator") - - qubit_reg = QuantumRegister(2) - clbit_reg = ClassicalRegister(2) - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) - - circuits = transpile([qc], backend) - self.assertIsInstance(circuits, list) - self.assertEqual(len(circuits), 1) - self.assertIsInstance(circuits[0], QuantumCircuit) - - def test_mapping_correction(self): - """Test mapping works in previous failed case.""" - backend = FakeGeneric(num_qubits=12) - qr = QuantumRegister(name="qr", size=11) - cr = ClassicalRegister(name="qc", size=11) - circuit = QuantumCircuit(qr, cr) - circuit.u(1.564784764685993, -1.2378965763410095, 2.9746763177861713, qr[3]) - circuit.u(1.2269835563676523, 1.1932982847014162, -1.5597357740824318, qr[5]) - circuit.cx(qr[5], qr[3]) - circuit.p(0.856768317675967, qr[3]) - circuit.u(-3.3911273825190915, 0.0, 0.0, qr[5]) - circuit.cx(qr[3], qr[5]) - circuit.u(2.159209321625547, 0.0, 0.0, qr[5]) - circuit.cx(qr[5], qr[3]) - circuit.u(0.30949966910232335, 1.1706201763833217, 1.738408691990081, qr[3]) - circuit.u(1.9630571407274755, -0.6818742967975088, 1.8336534616728195, qr[5]) - circuit.u(1.330181833806101, 0.6003162754946363, -3.181264980452862, qr[7]) - circuit.u(0.4885914820775024, 3.133297443244865, -2.794457469189904, qr[8]) - circuit.cx(qr[8], qr[7]) - circuit.p(2.2196187596178616, qr[7]) - circuit.u(-3.152367609631023, 0.0, 0.0, qr[8]) - circuit.cx(qr[7], qr[8]) - circuit.u(1.2646005789809263, 0.0, 0.0, qr[8]) - circuit.cx(qr[8], qr[7]) - circuit.u(0.7517780502091939, 1.2828514296564781, 1.6781179605443775, qr[7]) - circuit.u(0.9267400575390405, 2.0526277839695153, 2.034202361069533, qr[8]) - circuit.u(2.550304293455634, 3.8250017126569698, -2.1351609599720054, qr[1]) - circuit.u(0.9566260876600556, -1.1147561503064538, 2.0571590492298797, qr[4]) - circuit.cx(qr[4], qr[1]) - circuit.p(2.1899329069137394, qr[1]) - circuit.u(-1.8371715243173294, 0.0, 0.0, qr[4]) - circuit.cx(qr[1], qr[4]) - circuit.u(0.4717053496327104, 0.0, 0.0, qr[4]) - circuit.cx(qr[4], qr[1]) - circuit.u(2.3167620677708145, -1.2337330260253256, -0.5671322899563955, qr[1]) - circuit.u(1.0468499525240678, 0.8680750644809365, -1.4083720073192485, qr[4]) - circuit.u(2.4204244021892807, -2.211701932616922, 3.8297006565735883, qr[10]) - circuit.u(0.36660280497727255, 3.273119149343493, -1.8003362351299388, qr[6]) - circuit.cx(qr[6], qr[10]) - circuit.p(1.067395863586385, qr[10]) - circuit.u(-0.7044917541291232, 0.0, 0.0, qr[6]) - circuit.cx(qr[10], qr[6]) - circuit.u(2.1830003849921527, 0.0, 0.0, qr[6]) - circuit.cx(qr[6], qr[10]) - circuit.u(2.1538343756723917, 2.2653381826084606, -3.550087952059485, qr[10]) - circuit.u(1.307627685019188, -0.44686656993522567, -2.3238098554327418, qr[6]) - circuit.u(2.2046797998462906, 0.9732961754855436, 1.8527865921467421, qr[9]) - circuit.u(2.1665254613904126, -1.281337664694577, -1.2424905413631209, qr[0]) - circuit.cx(qr[0], qr[9]) - circuit.p(2.6209599970201007, qr[9]) - circuit.u(0.04680566321901303, 0.0, 0.0, qr[0]) - circuit.cx(qr[9], qr[0]) - circuit.u(1.7728411151289603, 0.0, 0.0, qr[0]) - circuit.cx(qr[0], qr[9]) - circuit.u(2.4866395967434443, 0.48684511243566697, -3.0069186877854728, qr[9]) - circuit.u(1.7369112924273789, -4.239660866163805, 1.0623389015296005, qr[0]) - circuit.barrier(qr) - circuit.measure(qr, cr) - - circuits = transpile(circuit, backend) - - self.assertIsInstance(circuits, QuantumCircuit) - - def test_transpiler_layout_from_intlist(self): - """A list of ints gives layout to correctly map circuit. - virtual physical - q1_0 - 4 ---[H]--- - q2_0 - 5 - q2_1 - 6 ---[H]--- - q3_0 - 8 - q3_1 - 9 - q3_2 - 10 ---[H]--- - - """ - qr1 = QuantumRegister(1, "qr1") - qr2 = QuantumRegister(2, "qr2") - qr3 = QuantumRegister(3, "qr3") - qc = QuantumCircuit(qr1, qr2, qr3) - qc.h(qr1[0]) - qc.h(qr2[1]) - qc.h(qr3[2]) - layout = [4, 5, 6, 8, 9, 10] - - cmap = [ - [1, 0], - [1, 2], - [2, 3], - [4, 3], - [4, 10], - [5, 4], - [5, 6], - [5, 9], - [6, 8], - [7, 8], - [9, 8], - [9, 10], - [11, 3], - [11, 10], - [11, 12], - [12, 2], - [13, 1], - [13, 12], - ] - - new_circ = transpile( - qc, backend=None, coupling_map=cmap, basis_gates=["u2"], initial_layout=layout - ) - qubit_indices = {bit: idx for idx, bit in enumerate(new_circ.qubits)} - mapped_qubits = [] - - for instruction in new_circ.data: - mapped_qubits.append(qubit_indices[instruction.qubits[0]]) - - self.assertEqual(mapped_qubits, [4, 6, 10]) - - def test_mapping_multi_qreg(self): - """Test mapping works for multiple qregs.""" - backend = FakeGeneric(num_qubits=8) - qr = QuantumRegister(3, name="qr") - qr2 = QuantumRegister(1, name="qr2") - qr3 = QuantumRegister(4, name="qr3") - cr = ClassicalRegister(3, name="cr") - qc = QuantumCircuit(qr, qr2, qr3, cr) - qc.h(qr[0]) - qc.cx(qr[0], qr2[0]) - qc.cx(qr[1], qr3[2]) - qc.measure(qr, cr) - - circuits = transpile(qc, backend) - - self.assertIsInstance(circuits, QuantumCircuit) - - def test_transpile_circuits_diff_registers(self): - """Transpile list of circuits with different qreg names.""" - backend = FakeGeneric(num_qubits=4) - circuits = [] - for _ in range(2): - qr = QuantumRegister(2) - cr = ClassicalRegister(2) - circuit = QuantumCircuit(qr, cr) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.measure(qr, cr) - circuits.append(circuit) - - circuits = transpile(circuits, backend) - self.assertIsInstance(circuits[0], QuantumCircuit) - - def test_wrong_initial_layout(self): - """Test transpile with a bad initial layout.""" - backend = FakeGeneric(num_qubits=4) - - qubit_reg = QuantumRegister(2, name="q") - clbit_reg = ClassicalRegister(2, name="c") - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - qc.measure(qubit_reg, clbit_reg) - - bad_initial_layout = [ - QuantumRegister(3, "q")[0], - QuantumRegister(3, "q")[1], - QuantumRegister(3, "q")[2], - ] - - with self.assertRaises(TranspilerError): - transpile(qc, backend, initial_layout=bad_initial_layout) - - def test_parameterized_circuit_for_simulator(self): - """Verify that a parameterized circuit can be transpiled for a simulator backend.""" - qr = QuantumRegister(2, name="qr") - qc = QuantumCircuit(qr) - - theta = Parameter("theta") - qc.rz(theta, qr[0]) - - transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) - - expected_qc = QuantumCircuit(qr) - expected_qc.append(RZGate(theta), [qr[0]]) - self.assertEqual(expected_qc, transpiled_qc) - - def test_parameterized_circuit_for_device(self): - """Verify that a parameterized circuit can be transpiled for a device backend.""" - qr = QuantumRegister(2, name="qr") - qc = QuantumCircuit(qr) - - theta = Parameter("theta") - qc.p(theta, qr[0]) - backend = FakeGeneric(num_qubits=4) - - transpiled_qc = transpile( - qc, - backend=backend, - initial_layout=Layout.generate_trivial_layout(qr), - ) - - qr = QuantumRegister(backend.num_qubits, "q") - expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) - expected_qc.append(RZGate(theta), [qr[0]]) - - self.assertEqual(expected_qc, transpiled_qc) - - def test_parameter_expression_circuit_for_simulator(self): - """Verify that a circuit including expressions of parameters can be - transpiled for a simulator backend.""" - qr = QuantumRegister(2, name="qr") - qc = QuantumCircuit(qr) - - theta = Parameter("theta") - square = theta * theta - qc.rz(square, qr[0]) - - transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) - - expected_qc = QuantumCircuit(qr) - expected_qc.append(RZGate(square), [qr[0]]) - self.assertEqual(expected_qc, transpiled_qc) - - def test_parameter_expression_circuit_for_device(self): - """Verify that a circuit including expressions of parameters can be - transpiled for a device backend.""" - qr = QuantumRegister(2, name="qr") - qc = QuantumCircuit(qr) - - theta = Parameter("theta") - square = theta * theta - qc.rz(square, qr[0]) - backend = FakeGeneric(num_qubits=4) - - transpiled_qc = transpile( - qc, - backend=backend, - initial_layout=Layout.generate_trivial_layout(qr), - ) - - qr = QuantumRegister(backend.num_qubits, "q") - expected_qc = QuantumCircuit(qr) - expected_qc.append(RZGate(square), [qr[0]]) - self.assertEqual(expected_qc, transpiled_qc) - - def test_final_measurement_barrier_for_devices(self): - """Verify BarrierBeforeFinalMeasurements pass is called in default pipeline for devices.""" - qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") - circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) - layout = Layout.generate_trivial_layout(*circ.qregs) - orig_pass = BarrierBeforeFinalMeasurements() - with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: - transpile( - circ, - coupling_map=FakeGeneric(num_qubits=6).coupling_map, - initial_layout=layout, - ) - self.assertTrue(mock_pass.called) - - def test_do_not_run_gatedirection_with_symmetric_cm(self): - """When the coupling map is symmetric, do not run GateDirection.""" - qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") - circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) - layout = Layout.generate_trivial_layout(*circ.qregs) - coupling_map = [] - for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: - coupling_map.append([node1, node2]) - coupling_map.append([node2, node1]) - - orig_pass = GateDirection(CouplingMap(coupling_map)) - with patch.object(GateDirection, "run", wraps=orig_pass.run) as mock_pass: - transpile(circ, coupling_map=coupling_map, initial_layout=layout) - self.assertFalse(mock_pass.called) - - def test_optimize_to_nothing(self): - """Optimize gates up to fixed point in the default pipeline - See https://github.com/Qiskit/qiskit-terra/issues/2035 - """ - # ┌───┐ ┌───┐┌───┐┌───┐ ┌───┐ - # q0_0: ┤ H ├──■──┤ X ├┤ Y ├┤ Z ├──■──┤ H ├──■────■── - # └───┘┌─┴─┐└───┘└───┘└───┘┌─┴─┐└───┘┌─┴─┐┌─┴─┐ - # q0_1: ─────┤ X ├───────────────┤ X ├─────┤ X ├┤ X ├ - # └───┘ └───┘ └───┘└───┘ - qr = QuantumRegister(2) - circ = QuantumCircuit(qr) - circ.h(qr[0]) - circ.cx(qr[0], qr[1]) - circ.x(qr[0]) - circ.y(qr[0]) - circ.z(qr[0]) - circ.cx(qr[0], qr[1]) - circ.h(qr[0]) - circ.cx(qr[0], qr[1]) - circ.cx(qr[0], qr[1]) - - after = transpile(circ, coupling_map=[[0, 1], [1, 0]], basis_gates=["u3", "u2", "u1", "cx"]) - - expected = QuantumCircuit(QuantumRegister(2, "q"), global_phase=-np.pi / 2) - msg = f"after:\n{after}\nexpected:\n{expected}" - self.assertEqual(after, expected, msg=msg) - - def test_pass_manager_empty(self): - """Test passing an empty PassManager() to the transpiler. - - It should perform no transformations on the circuit. - """ - qr = QuantumRegister(2) - circuit = QuantumCircuit(qr) - circuit.h(qr[0]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[0], qr[1]) - resources_before = circuit.count_ops() - - pass_manager = PassManager() - out_circuit = pass_manager.run(circuit) - resources_after = out_circuit.count_ops() - - self.assertDictEqual(resources_before, resources_after) - - def test_move_measurements(self): - """Measurements applied AFTER swap mapping.""" - cmap = FakeGeneric(num_qubits=16).coupling_map - qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") - circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) - - lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] - out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic") - out_dag = circuit_to_dag(out) - meas_nodes = out_dag.named_nodes("measure") - for meas_node in meas_nodes: - is_last_measure = all( - isinstance(after_measure, DAGOutNode) - for after_measure in out_dag.quantum_successors(meas_node) - ) - self.assertTrue(is_last_measure) - - @data(0, 1, 2, 3) - def test_init_resets_kept_preset_passmanagers(self, optimization_level): - """Test initial resets kept at all preset transpilation levels""" - num_qubits = 5 - qc = QuantumCircuit(num_qubits) - qc.reset(range(num_qubits)) - - num_resets = transpile(qc, optimization_level=optimization_level).count_ops()["reset"] - self.assertEqual(num_resets, num_qubits) - - @data(0, 1, 2, 3) - def test_initialize_reset_is_not_removed(self, optimization_level): - """The reset in front of initializer should NOT be removed at beginning""" - qr = QuantumRegister(1, "qr") - qc = QuantumCircuit(qr) - qc.initialize([1.0 / math.sqrt(2), 1.0 / math.sqrt(2)], [qr[0]]) - qc.initialize([1.0 / math.sqrt(2), -1.0 / math.sqrt(2)], [qr[0]]) - - after = transpile(qc, basis_gates=["reset", "u3"], optimization_level=optimization_level) - self.assertEqual(after.count_ops()["reset"], 2, msg=f"{after}\n does not have 2 resets.") - - def test_initialize_FakeMelbourne(self): - """Test that the zero-state resets are remove in a device not supporting them.""" - desired_vector = [1 / math.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / math.sqrt(2)] - qr = QuantumRegister(3, "qr") - qc = QuantumCircuit(qr) - qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - - out = transpile(qc, backend=FakeGeneric(num_qubits=4)) - out_dag = circuit_to_dag(out) - reset_nodes = out_dag.named_nodes("reset") - - self.assertEqual(len(reset_nodes), 3) - - def test_non_standard_basis(self): - """Test a transpilation with a non-standard basis""" - qr1 = QuantumRegister(1, "q1") - qr2 = QuantumRegister(2, "q2") - qr3 = QuantumRegister(3, "q3") - qc = QuantumCircuit(qr1, qr2, qr3) - qc.h(qr1[0]) - qc.h(qr2[1]) - qc.h(qr3[2]) - layout = [4, 5, 6, 8, 9, 10] - - cmap = [ - [1, 0], - [1, 2], - [2, 3], - [4, 3], - [4, 10], - [5, 4], - [5, 6], - [5, 9], - [6, 8], - [7, 8], - [9, 8], - [9, 10], - [11, 3], - [11, 10], - [11, 12], - [12, 2], - [13, 1], - [13, 12], - ] - - circuit = transpile( - qc, backend=None, coupling_map=cmap, basis_gates=["h"], initial_layout=layout - ) - - dag_circuit = circuit_to_dag(circuit) - resources_after = dag_circuit.count_ops() - self.assertEqual({"h": 3}, resources_after) - - def test_hadamard_to_rot_gates(self): - """Test a transpilation from H to Rx, Ry gates""" - qr = QuantumRegister(1) - qc = QuantumCircuit(qr) - qc.h(0) - - expected = QuantumCircuit(qr, global_phase=np.pi / 2) - expected.append(RYGate(theta=np.pi / 2), [0]) - expected.append(RXGate(theta=np.pi), [0]) - - circuit = transpile(qc, basis_gates=["rx", "ry"], optimization_level=0) - self.assertEqual(circuit, expected) - - def test_basis_subset(self): - """Test a transpilation with a basis subset of the standard basis""" - qr = QuantumRegister(1, "q1") - qc = QuantumCircuit(qr) - qc.h(qr[0]) - qc.x(qr[0]) - qc.t(qr[0]) - - layout = [4] - - cmap = [ - [1, 0], - [1, 2], - [2, 3], - [4, 3], - [4, 10], - [5, 4], - [5, 6], - [5, 9], - [6, 8], - [7, 8], - [9, 8], - [9, 10], - [11, 3], - [11, 10], - [11, 12], - [12, 2], - [13, 1], - [13, 12], - ] - - circuit = transpile( - qc, backend=None, coupling_map=cmap, basis_gates=["u3"], initial_layout=layout - ) - - dag_circuit = circuit_to_dag(circuit) - resources_after = dag_circuit.count_ops() - self.assertEqual({"u3": 1}, resources_after) - - def test_check_circuit_width(self): - """Verify transpilation of circuit with virtual qubits greater than - physical qubits raises error""" - cmap = [ - [1, 0], - [1, 2], - [2, 3], - [4, 3], - [4, 10], - [5, 4], - [5, 6], - [5, 9], - [6, 8], - [7, 8], - [9, 8], - [9, 10], - [11, 3], - [11, 10], - [11, 12], - [12, 2], - [13, 1], - [13, 12], - ] - - qc = QuantumCircuit(15, 15) - - with self.assertRaises(TranspilerError): - transpile(qc, coupling_map=cmap) - - @data(0, 1, 2, 3) - def test_ccx_routing_method_none(self, optimization_level): - """CCX without routing method.""" - - qc = QuantumCircuit(3) - qc.cx(0, 1) - qc.cx(1, 2) - - out = transpile( - qc, - routing_method="none", - basis_gates=["u", "cx"], - initial_layout=[0, 1, 2], - seed_transpiler=0, - coupling_map=[[0, 1], [1, 2]], - optimization_level=optimization_level, - ) - - self.assertTrue(Operator(qc).equiv(out)) - - @data(0, 1, 2, 3) - def test_ccx_routing_method_none_failed(self, optimization_level): - """CCX without routing method cannot be routed.""" - - qc = QuantumCircuit(3) - qc.ccx(0, 1, 2) - - with self.assertRaises(TranspilerError): - transpile( - qc, - routing_method="none", - basis_gates=["u", "cx"], - initial_layout=[0, 1, 2], - seed_transpiler=0, - coupling_map=[[0, 1], [1, 2]], - optimization_level=optimization_level, - ) - - @data(0, 1, 2, 3) - def test_ms_unrolls_to_cx(self, optimization_level): - """Verify a Rx,Ry,Rxx circuit transpile to a U3,CX target.""" - - qc = QuantumCircuit(2) - qc.rx(math.pi / 2, 0) - qc.ry(math.pi / 4, 1) - qc.rxx(math.pi / 4, 0, 1) - - out = transpile(qc, basis_gates=["u3", "cx"], optimization_level=optimization_level) - - self.assertTrue(Operator(qc).equiv(out)) - - @data(0, 1, 2, 3) - def test_ms_can_target_ms(self, optimization_level): - """Verify a Rx,Ry,Rxx circuit can transpile to an Rx,Ry,Rxx target.""" - - qc = QuantumCircuit(2) - qc.rx(math.pi / 2, 0) - qc.ry(math.pi / 4, 1) - qc.rxx(math.pi / 4, 0, 1) - - out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) - - self.assertTrue(Operator(qc).equiv(out)) - - @data(0, 1, 2, 3) - def test_cx_can_target_ms(self, optimization_level): - """Verify a U3,CX circuit can transpiler to a Rx,Ry,Rxx target.""" - - qc = QuantumCircuit(2) - qc.h(0) - qc.cx(0, 1) - qc.rz(math.pi / 4, [0, 1]) - - out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) - - self.assertTrue(Operator(qc).equiv(out)) - - @data(0, 1, 2, 3) - def test_measure_doesnt_unroll_ms(self, optimization_level): - """Verify a measure doesn't cause an Rx,Ry,Rxx circuit to unroll to U3,CX.""" - - qc = QuantumCircuit(2, 2) - qc.rx(math.pi / 2, 0) - qc.ry(math.pi / 4, 1) - qc.rxx(math.pi / 4, 0, 1) - qc.measure([0, 1], [0, 1]) - out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) - - self.assertEqual(qc, out) - - @data( - ["cx", "u3"], - ["cz", "u3"], - ["cz", "rx", "rz"], - ["rxx", "rx", "ry"], - ["iswap", "rx", "rz"], - ) - def test_block_collection_runs_for_non_cx_bases(self, basis_gates): - """Verify block collection is run when a single two qubit gate is in the basis.""" - twoq_gate, *_ = basis_gates - - qc = QuantumCircuit(2) - qc.cx(0, 1) - qc.cx(1, 0) - qc.cx(0, 1) - qc.cx(0, 1) - - out = transpile(qc, basis_gates=basis_gates, optimization_level=3) - - self.assertLessEqual(out.count_ops()[twoq_gate], 2) - - @unpack - @data( - (["u3", "cx"], {"u3": 1, "cx": 1}), - (["rx", "rz", "iswap"], {"rx": 6, "rz": 12, "iswap": 2}), - (["rx", "ry", "rxx"], {"rx": 6, "ry": 5, "rxx": 1}), - ) - def test_block_collection_reduces_1q_gate(self, basis_gates, gate_counts): - """For synthesis to non-U3 bases, verify we minimize 1q gates.""" - qc = QuantumCircuit(2) - qc.h(0) - qc.cx(0, 1) - - out = transpile(qc, basis_gates=basis_gates, optimization_level=3) - - self.assertTrue(Operator(out).equiv(qc)) - self.assertTrue(set(out.count_ops()).issubset(basis_gates)) - for basis_gate in basis_gates: - self.assertLessEqual(out.count_ops()[basis_gate], gate_counts[basis_gate]) - - @combine( - optimization_level=[0, 1, 2, 3], - basis_gates=[ - ["u3", "cx"], - ["rx", "rz", "iswap"], - ["rx", "ry", "rxx"], - ], - ) - def test_translation_method_synthesis(self, optimization_level, basis_gates): - """Verify translation_method='synthesis' gets to the basis.""" - qc = QuantumCircuit(2) - qc.h(0) - qc.cx(0, 1) - - out = transpile( - qc, - translation_method="synthesis", - basis_gates=basis_gates, - optimization_level=optimization_level, - ) - - self.assertTrue(Operator(out).equiv(qc)) - self.assertTrue(set(out.count_ops()).issubset(basis_gates)) - - def test_transpiled_custom_gates_calibration(self): - """Test if transpiled calibrations is equal to custom gates circuit calibrations.""" - custom_180 = Gate("mycustom", 1, [3.14]) - custom_90 = Gate("mycustom", 1, [1.57]) - - circ = QuantumCircuit(2) - circ.append(custom_180, [0]) - circ.append(custom_90, [1]) - - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - with pulse.build() as q1_y90: - pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) - - # Add calibration - circ.add_calibration(custom_180, [0], q0_x180) - circ.add_calibration(custom_90, [1], q1_y90) - - transpiled_circuit = transpile( - circ, - backend=FakeGeneric(num_qubits=4), - layout_method="trivial", - ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) - self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) - - def test_transpiled_basis_gates_calibrations(self): - """Test if the transpiled calibrations is equal to basis gates circuit calibrations.""" - circ = QuantumCircuit(2) - circ.h(0) - - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration("h", [0], q0_x180) - - transpiled_circuit = transpile( - circ, - backend=FakeGeneric(num_qubits=4), - ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - - def test_transpile_calibrated_custom_gate_on_diff_qubit(self): - """Test if the custom, non calibrated gate raises QiskitError.""" - custom_180 = Gate("mycustom", 1, [3.14]) - - circ = QuantumCircuit(2) - circ.append(custom_180, [0]) - - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration(custom_180, [1], q0_x180) - - with self.assertRaises(QiskitError): - transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") - - def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): - """Test if the non-basis gates are transpiled if they are on different qubit that - is not calibrated.""" - circ = QuantumCircuit(2) - circ.h(0) - circ.h(1) - - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration("h", [1], q0_x180) - - transpiled_circuit = transpile( - circ, - backend=FakeGeneric(num_qubits=4), - ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) - - def test_transpile_subset_of_calibrated_gates(self): - """Test transpiling a circuit with both basis gate (not-calibrated) and - a calibrated gate on different qubits.""" - x_180 = Gate("mycustom", 1, [3.14]) - - circ = QuantumCircuit(2) - circ.h(0) - circ.append(x_180, [0]) - circ.h(1) - - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - circ.add_calibration(x_180, [0], q0_x180) - circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 - - transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) - - def test_parameterized_calibrations_transpile(self): - """Check that gates can be matched to their calibrations before and after parameter - assignment.""" - tau = Parameter("tau") - circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) - - def q0_rxt(tau): - with pulse.build() as q0_rxt: - pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) - return q0_rxt - - circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) - - transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) - circ = circ.assign_parameters({tau: 1}) - transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) - - def test_inst_durations_from_calibrations(self): - """Test that circuit calibrations can be used instead of explicitly - supplying inst_durations. - """ - qc = QuantumCircuit(2) - qc.append(Gate("custom", 1, []), [0]) - - with pulse.build() as cal: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("custom", [0], cal) - - out = transpile(qc, scheduling_method="alap") - self.assertEqual(out.duration, cal.duration) - - @data(0, 1, 2, 3) - def test_multiqubit_gates_calibrations(self, opt_level): - """Test multiqubit gate > 2q with calibrations works - - Adapted from issue description in https://github.com/Qiskit/qiskit-terra/issues/6572 - """ - circ = QuantumCircuit(5) - custom_gate = Gate("my_custom_gate", 5, []) - circ.append(custom_gate, [0, 1, 2, 3, 4]) - circ.measure_all() - backend = FakeGeneric(num_qubits=6) - - with pulse.build(backend=backend, name="custom") as my_schedule: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) - ) - circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) - trans_circ = transpile( - circ, backend=backend, optimization_level=opt_level, layout_method="trivial" - ) - self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) - - @data(0, 1, 2, 3) - def test_circuit_with_delay(self, optimization_level): - """Verify a circuit with delay can transpile to a scheduled circuit.""" - - qc = QuantumCircuit(2) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - - out = transpile( - qc, - scheduling_method="alap", - basis_gates=["h", "cx"], - instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], - optimization_level=optimization_level, - ) - - self.assertEqual(out.duration, 1200) - - def test_delay_converts_to_dt(self): - """Test that a delay instruction is converted to units of dt given a backend.""" - qc = QuantumCircuit(2) - qc.delay(1000, [0], unit="us") - - backend = FakeGeneric(num_qubits=4, dt=0.5e-6) - out = transpile([qc, qc], backend) - self.assertEqual(out[0].data[0].operation.unit, "dt") - self.assertEqual(out[1].data[0].operation.unit, "dt") - - out = transpile(qc, dt=1e-9) - self.assertEqual(out.data[0].operation.unit, "dt") - - def test_scheduling_backend_v2(self): - """Test that scheduling method works with Backendv2.""" - qc = QuantumCircuit(2) - qc.h(0) - qc.cx(0, 1) - qc.measure_all() - - out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") - self.assertIn("delay", out[0].count_ops()) - self.assertIn("delay", out[1].count_ops()) - - @data(1, 2, 3) - def test_no_infinite_loop(self, optimization_level): - """Verify circuit cost always descends and optimization does not flip flop indefinitely.""" - qc = QuantumCircuit(1) - qc.ry(0.2, 0) - - out = transpile( - qc, basis_gates=["id", "p", "sx", "cx"], optimization_level=optimization_level - ) - - # Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and - # a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi - # for the second and third RZ gates in the U3 decomposition. - expected = QuantumCircuit( - 1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi - ) - expected.p(-np.pi, 0) - expected.sx(0) - expected.p(np.pi - 0.2, 0) - expected.sx(0) - - error_message = ( - f"\nOutput circuit:\n{out!s}\n{Operator(out).data}\n" - f"Expected circuit:\n{expected!s}\n{Operator(expected).data}" - ) - self.assertEqual(out, expected, error_message) - - @data(0, 1, 2, 3) - def test_transpile_preserves_circuit_metadata(self, optimization_level): - """Verify that transpile preserves circuit metadata in the output.""" - circuit = QuantumCircuit(2, metadata={"experiment_id": "1234", "execution_number": 4}) - circuit.h(0) - circuit.cx(0, 1) - - cmap = [ - [1, 0], - [1, 2], - [2, 3], - [4, 3], - [4, 10], - [5, 4], - [5, 6], - [5, 9], - [6, 8], - [7, 8], - [9, 8], - [9, 10], - [11, 3], - [11, 10], - [11, 12], - [12, 2], - [13, 1], - [13, 12], - ] - - res = transpile( - circuit, - basis_gates=["id", "p", "sx", "cx"], - coupling_map=cmap, - optimization_level=optimization_level, - ) - self.assertEqual(circuit.metadata, res.metadata) - - @data(0, 1, 2, 3) - def test_transpile_optional_registers(self, optimization_level): - """Verify transpile accepts circuits without registers end-to-end.""" - - qubits = [Qubit() for _ in range(3)] - clbits = [Clbit() for _ in range(3)] - - qc = QuantumCircuit(qubits, clbits) - qc.h(0) - qc.cx(0, 1) - qc.cx(1, 2) - - qc.measure(qubits, clbits) - backend = FakeGeneric(num_qubits=4) - - out = transpile(qc, backend=backend, optimization_level=optimization_level) - - self.assertEqual(len(out.qubits), backend.num_qubits) - self.assertEqual(len(out.clbits), len(clbits)) - - @data(0, 1, 2, 3) - def test_translate_ecr_basis(self, optimization_level): - """Verify that rewriting in ECR basis is efficient.""" - circuit = QuantumCircuit(2) - circuit.append(random_unitary(4, seed=1), [0, 1]) - circuit.barrier() - circuit.cx(0, 1) - circuit.barrier() - circuit.swap(0, 1) - circuit.barrier() - circuit.iswap(0, 1) - - res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=optimization_level) - self.assertEqual(res.count_ops()["ecr"], 9) - self.assertTrue(Operator(res).equiv(circuit)) - - def test_optimize_ecr_basis(self): - """Test highest optimization level can optimize over ECR.""" - circuit = QuantumCircuit(2) - circuit.swap(1, 0) - circuit.iswap(0, 1) - - res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=3) - self.assertEqual(res.count_ops()["ecr"], 1) - self.assertTrue(Operator(res).equiv(circuit)) - - def test_approximation_degree_invalid(self): - """Test invalid approximation degree raises.""" - circuit = QuantumCircuit(2) - circuit.swap(0, 1) - with self.assertRaises(QiskitError): - transpile(circuit, basis_gates=["u", "cz"], approximation_degree=1.1) - - def test_approximation_degree(self): - """Test more approximation gives lower-cost circuit.""" - circuit = QuantumCircuit(2) - circuit.swap(0, 1) - circuit.h(0) - circ_10 = transpile( - circuit, - basis_gates=["u", "cx"], - translation_method="synthesis", - approximation_degree=0.1, - ) - circ_90 = transpile( - circuit, - basis_gates=["u", "cx"], - translation_method="synthesis", - approximation_degree=0.9, - ) - self.assertLess(circ_10.depth(), circ_90.depth()) - - @data(0, 1, 2, 3) - def test_synthesis_translation_method_with_single_qubit_gates(self, optimization_level): - """Test that synthesis basis translation works for solely 1q circuit""" - qc = QuantumCircuit(3) - qc.h(0) - qc.h(1) - qc.h(2) - res = transpile( - qc, - basis_gates=["id", "rz", "x", "sx", "cx"], - translation_method="synthesis", - optimization_level=optimization_level, - ) - expected = QuantumCircuit(3, global_phase=3 * np.pi / 4) - expected.rz(np.pi / 2, 0) - expected.rz(np.pi / 2, 1) - expected.rz(np.pi / 2, 2) - expected.sx(0) - expected.sx(1) - expected.sx(2) - expected.rz(np.pi / 2, 0) - expected.rz(np.pi / 2, 1) - expected.rz(np.pi / 2, 2) - self.assertEqual(res, expected) - - @data(0, 1, 2, 3) - def test_synthesis_translation_method_with_gates_outside_basis(self, optimization_level): - """Test that synthesis translation works for circuits with single gates outside bassis""" - qc = QuantumCircuit(2) - qc.swap(0, 1) - res = transpile( - qc, - basis_gates=["id", "rz", "x", "sx", "cx"], - translation_method="synthesis", - optimization_level=optimization_level, - ) - if optimization_level != 3: - self.assertTrue(Operator(qc).equiv(res)) - self.assertNotIn("swap", res.count_ops()) - else: - # Optimization level 3 eliminates the pointless swap - self.assertEqual(res, QuantumCircuit(2)) - - @data(0, 1, 2, 3) - def test_target_ideal_gates(self, opt_level): - """Test that transpile() with a custom ideal sim target works.""" - theta = Parameter("θ") - phi = Parameter("ϕ") - lam = Parameter("λ") - target = Target(num_qubits=2) - target.add_instruction(UGate(theta, phi, lam), {(0,): None, (1,): None}) - target.add_instruction(CXGate(), {(0, 1): None}) - target.add_instruction(Measure(), {(0,): None, (1,): None}) - qubit_reg = QuantumRegister(2, name="q") - clbit_reg = ClassicalRegister(2, name="c") - qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") - qc.h(qubit_reg[0]) - qc.cx(qubit_reg[0], qubit_reg[1]) - - result = transpile(qc, target=target, optimization_level=opt_level) - - self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc)) - - @data(0, 1, 2, 3) - def test_transpile_with_custom_control_flow_target(self, opt_level): - """Test transpile() with a target and constrol flow ops.""" - target = FakeGeneric(num_qubits=8, dynamic=True).target - - circuit = QuantumCircuit(6, 1) - circuit.h(0) - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.cz(0, 2) - circuit.append(CustomCX(), [1, 2], []) - with circuit.for_loop((1,)): - circuit.cx(0, 1) - circuit.cz(0, 2) - circuit.append(CustomCX(), [1, 2], []) - with circuit.if_test((circuit.clbits[0], True)) as else_: - circuit.cx(0, 1) - circuit.cz(0, 2) - circuit.append(CustomCX(), [1, 2], []) - with else_: - circuit.cx(3, 4) - circuit.cz(3, 5) - circuit.append(CustomCX(), [4, 5], []) - with circuit.while_loop((circuit.clbits[0], True)): - circuit.cx(3, 4) - circuit.cz(3, 5) - circuit.append(CustomCX(), [4, 5], []) - with circuit.switch(circuit.cregs[0]) as case_: - with case_(0): - circuit.cx(0, 1) - circuit.cz(0, 2) - circuit.append(CustomCX(), [1, 2], []) - with case_(1): - circuit.cx(1, 2) - circuit.cz(1, 3) - circuit.append(CustomCX(), [2, 3], []) - transpiled = transpile( - circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 - ) - # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; - # here we're just checking that various passes do appear to have run. - self.assertIsInstance(transpiled, QuantumCircuit) - # Assert layout ran. - self.assertIsNot(getattr(transpiled, "_layout", None), None) - - def _visit_block(circuit, qubit_mapping=None): - for instruction in circuit: - qargs = tuple(qubit_mapping[x] for x in instruction.qubits) - self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) - if isinstance(instruction.operation, ControlFlowOp): - for block in instruction.operation.blocks: - new_mapping = { - inner: qubit_mapping[outer] - for outer, inner in zip(instruction.qubits, block.qubits) - } - _visit_block(block, new_mapping) - # Assert unrolling ran. - self.assertNotIsInstance(instruction.operation, CustomCX) - # Assert translation ran. - self.assertNotIsInstance(instruction.operation, CZGate) - - # Assert routing ran. - _visit_block( - transpiled, - qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, - ) - - @data(1, 2, 3) - def test_transpile_identity_circuit_no_target(self, opt_level): - """Test circuit equivalent to identity is optimized away for all optimization levels >0. - - Reproduce taken from https://github.com/Qiskit/qiskit-terra/issues/9217 - """ - qr1 = QuantumRegister(3, "state") - qr2 = QuantumRegister(2, "ancilla") - cr = ClassicalRegister(2, "c") - qc = QuantumCircuit(qr1, qr2, cr) - qc.h(qr1[0]) - qc.cx(qr1[0], qr1[1]) - qc.cx(qr1[1], qr1[2]) - qc.cx(qr1[1], qr1[2]) - qc.cx(qr1[0], qr1[1]) - qc.h(qr1[0]) - - empty_qc = QuantumCircuit(qr1, qr2, cr) - result = transpile(qc, optimization_level=opt_level) - self.assertEqual(empty_qc, result) - - @data(0, 1, 2, 3) - def test_initial_layout_with_loose_qubits(self, opt_level): - """Regression test of gh-10125.""" - qc = QuantumCircuit([Qubit(), Qubit()]) - qc.cx(0, 1) - transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) - self.assertIsNotNone(transpiled.layout) - self.assertEqual( - transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) - ) - - @data(0, 1, 2, 3) - def test_initial_layout_with_overlapping_qubits(self, opt_level): - """Regression test of gh-10125.""" - qr1 = QuantumRegister(2, "qr1") - qr2 = QuantumRegister(bits=qr1[:]) - qc = QuantumCircuit(qr1, qr2) - qc.cx(0, 1) - transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) - self.assertIsNotNone(transpiled.layout) - self.assertEqual( - transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) - ) - - @combine(opt_level=[0, 1, 2, 3], basis=[["rz", "x"], ["rx", "z"], ["rz", "y"], ["ry", "x"]]) - def test_paulis_to_constrained_1q_basis(self, opt_level, basis): - """Test that Pauli-gate circuits can be transpiled to constrained 1q bases that do not - contain any root-Pauli gates.""" - qc = QuantumCircuit(1) - qc.x(0) - qc.barrier() - qc.y(0) - qc.barrier() - qc.z(0) - transpiled = transpile(qc, basis_gates=basis, optimization_level=opt_level) - self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys()) - self.assertEqual(Operator(qc), Operator(transpiled)) - +# @ddt +# class TestTranspile(QiskitTestCase): +# """Test transpile function.""" +# +# def test_empty_transpilation(self): +# """Test that transpiling an empty list is a no-op. Regression test of gh-7287.""" +# self.assertEqual(transpile([]), []) +# +# def test_pass_manager_none(self): +# """Test passing the default (None) pass manager to the transpiler. +# +# It should perform the default qiskit flow: +# unroll, swap_mapper, cx_direction, cx_cancellation, optimize_1q_gates +# and should be equivalent to using tools.compile +# """ +# qr = QuantumRegister(2, "qr") +# circuit = QuantumCircuit(qr) +# circuit.h(qr[0]) +# circuit.h(qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[1], qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[1], qr[0]) +# +# coupling_map = [[1, 0]] +# basis_gates = ["u1", "u2", "u3", "cx", "id"] +# +# backend = BasicAer.get_backend("qasm_simulator") +# circuit2 = transpile( +# circuit, +# backend=backend, +# coupling_map=coupling_map, +# basis_gates=basis_gates, +# ) +# +# circuit3 = transpile( +# circuit, backend=backend, coupling_map=coupling_map, basis_gates=basis_gates +# ) +# self.assertEqual(circuit2, circuit3) +# +# def test_transpile_basis_gates_no_backend_no_coupling_map(self): +# """Verify transpile() works with no coupling_map or backend.""" +# qr = QuantumRegister(2, "qr") +# circuit = QuantumCircuit(qr) +# circuit.h(qr[0]) +# circuit.h(qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# +# basis_gates = ["u1", "u2", "u3", "cx", "id"] +# circuit2 = transpile(circuit, basis_gates=basis_gates, optimization_level=0) +# resources_after = circuit2.count_ops() +# self.assertEqual({"u2": 2, "cx": 4}, resources_after) +# +# def test_transpile_non_adjacent_layout(self): +# """Transpile pipeline can handle manual layout on non-adjacent qubits. +# +# circuit: +# +# .. parsed-literal:: +# +# ┌───┐ +# qr_0: ┤ H ├──■──────────── -> 1 +# └───┘┌─┴─┐ +# qr_1: ─────┤ X ├──■─────── -> 2 +# └───┘┌─┴─┐ +# qr_2: ──────────┤ X ├──■── -> 3 +# └───┘┌─┴─┐ +# qr_3: ───────────────┤ X ├ -> 5 +# └───┘ +# +# device: +# 0 - 1 - 2 - 3 - 4 - 5 - 6 +# +# | | | | | | +# +# 13 - 12 - 11 - 10 - 9 - 8 - 7 +# """ +# qr = QuantumRegister(4, "qr") +# circuit = QuantumCircuit(qr) +# circuit.h(qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[1], qr[2]) +# circuit.cx(qr[2], qr[3]) +# +# backend = FakeGeneric(num_qubits=6) +# initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] +# +# new_circuit = transpile( +# circuit, +# basis_gates=backend.operation_names, +# coupling_map=backend.coupling_map, +# initial_layout=initial_layout, +# ) +# +# qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} +# +# for instruction in new_circuit.data: +# if isinstance(instruction.operation, CXGate): +# self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) +# +# def test_transpile_qft_grid(self): +# """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" +# qr = QuantumRegister(8) +# circuit = QuantumCircuit(qr) +# for i, _ in enumerate(qr): +# for j in range(i): +# circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) +# circuit.h(qr[i]) +# +# backend = FakeGeneric(num_qubits=14) +# new_circuit = transpile( +# circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map +# ) +# +# qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} +# +# for instruction in new_circuit.data: +# if isinstance(instruction.operation, CXGate): +# self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) +# +# def test_already_mapped_1(self): +# """Circuit not remapped if matches topology. +# +# See: https://github.com/Qiskit/qiskit-terra/issues/342 +# """ +# backend = FakeGeneric( +# num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" +# ) +# coupling_map = backend.coupling_map +# basis_gates = backend.operation_names +# +# qr = QuantumRegister(19, "qr") +# cr = ClassicalRegister(19, "cr") +# qc = QuantumCircuit(qr, cr) +# qc.cx(qr[0], qr[9]) +# qc.cx(qr[1], qr[14]) +# qc.h(qr[3]) +# qc.cx(qr[10], qr[16]) +# qc.x(qr[11]) +# qc.cx(qr[5], qr[12]) +# qc.cx(qr[7], qr[18]) +# qc.cx(qr[6], qr[17]) +# qc.measure(qr, cr) +# +# new_qc = transpile( +# qc, +# coupling_map=coupling_map, +# basis_gates=basis_gates, +# initial_layout=Layout.generate_trivial_layout(qr), +# ) +# qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} +# cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] +# cx_qubits_physical = [ +# [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits +# ] +# self.assertEqual( +# sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] +# ) +# +# def test_already_mapped_via_layout(self): +# """Test that a manual layout that satisfies a coupling map does not get altered. +# +# See: https://github.com/Qiskit/qiskit-terra/issues/2036 +# +# circuit: +# +# .. parsed-literal:: +# +# ┌───┐ ┌───┐ ░ ┌─┐ +# qn_0: ┤ H ├──■────────────■──┤ H ├─░─┤M├─── -> 9 +# └───┘ │ │ └───┘ ░ └╥┘ +# qn_1: ───────┼────────────┼────────░──╫──── -> 6 +# │ │ ░ ║ +# qn_2: ───────┼────────────┼────────░──╫──── -> 5 +# │ │ ░ ║ +# qn_3: ───────┼────────────┼────────░──╫──── -> 0 +# │ │ ░ ║ +# qn_4: ───────┼────────────┼────────░──╫──── -> 1 +# ┌───┐┌─┴─┐┌──────┐┌─┴─┐┌───┐ ░ ║ ┌─┐ +# qn_5: ┤ H ├┤ X ├┤ P(2) ├┤ X ├┤ H ├─░──╫─┤M├ -> 4 +# └───┘└───┘└──────┘└───┘└───┘ ░ ║ └╥┘ +# cn: 2/════════════════════════════════╩══╩═ +# 0 1 +# +# device: +# 0 -- 1 -- 2 -- 3 -- 4 +# | | +# 5 -- 6 -- 7 -- 8 -- 9 +# | | +# 10 - 11 - 12 - 13 - 14 +# | | +# 15 - 16 - 17 - 18 - 19 +# """ +# basis_gates = ["u1", "u2", "u3", "cx", "id"] +# coupling_map = [ +# [0, 1], +# [0, 5], +# [1, 0], +# [1, 2], +# [2, 1], +# [2, 3], +# [3, 2], +# [3, 4], +# [4, 3], +# [4, 9], +# [5, 0], +# [5, 6], +# [5, 10], +# [6, 5], +# [6, 7], +# [7, 6], +# [7, 8], +# [7, 12], +# [8, 7], +# [8, 9], +# [9, 4], +# [9, 8], +# [9, 14], +# [10, 5], +# [10, 11], +# [10, 15], +# [11, 10], +# [11, 12], +# [12, 7], +# [12, 11], +# [12, 13], +# [13, 12], +# [13, 14], +# [14, 9], +# [14, 13], +# [14, 19], +# [15, 10], +# [15, 16], +# [16, 15], +# [16, 17], +# [17, 16], +# [17, 18], +# [18, 17], +# [18, 19], +# [19, 14], +# [19, 18], +# ] +# +# q = QuantumRegister(6, name="qn") +# c = ClassicalRegister(2, name="cn") +# qc = QuantumCircuit(q, c) +# qc.h(q[0]) +# qc.h(q[5]) +# qc.cx(q[0], q[5]) +# qc.p(2, q[5]) +# qc.cx(q[0], q[5]) +# qc.h(q[0]) +# qc.h(q[5]) +# qc.barrier(q) +# qc.measure(q[0], c[0]) +# qc.measure(q[5], c[1]) +# +# initial_layout = [ +# q[3], +# q[4], +# None, +# None, +# q[5], +# q[2], +# q[1], +# None, +# None, +# q[0], +# None, +# None, +# None, +# None, +# None, +# None, +# None, +# None, +# None, +# None, +# ] +# +# new_qc = transpile( +# qc, coupling_map=coupling_map, basis_gates=basis_gates, initial_layout=initial_layout +# ) +# qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} +# cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] +# cx_qubits_physical = [ +# [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits +# ] +# self.assertEqual(sorted(cx_qubits_physical), [[9, 4], [9, 4]]) +# +# def test_transpile_bell(self): +# """Test Transpile Bell. +# +# If all correct some should exists. +# """ +# backend = BasicAer.get_backend("qasm_simulator") +# +# qubit_reg = QuantumRegister(2, name="q") +# clbit_reg = ClassicalRegister(2, name="c") +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# qc.measure(qubit_reg, clbit_reg) +# +# circuits = transpile(qc, backend) +# self.assertIsInstance(circuits, QuantumCircuit) +# +# def test_transpile_one(self): +# """Test transpile a single circuit. +# +# Check that the top-level `transpile` function returns +# a single circuit.""" +# backend = BasicAer.get_backend("qasm_simulator") +# +# qubit_reg = QuantumRegister(2) +# clbit_reg = ClassicalRegister(2) +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# qc.measure(qubit_reg, clbit_reg) +# +# circuit = transpile(qc, backend) +# self.assertIsInstance(circuit, QuantumCircuit) +# +# def test_transpile_two(self): +# """Test transpile two circuits. +# +# Check that the transpiler returns a list of two circuits. +# """ +# backend = BasicAer.get_backend("qasm_simulator") +# +# qubit_reg = QuantumRegister(2) +# clbit_reg = ClassicalRegister(2) +# qubit_reg2 = QuantumRegister(2) +# clbit_reg2 = ClassicalRegister(2) +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# qc.measure(qubit_reg, clbit_reg) +# qc_extra = QuantumCircuit(qubit_reg, qubit_reg2, clbit_reg, clbit_reg2, name="extra") +# qc_extra.measure(qubit_reg, clbit_reg) +# circuits = transpile([qc, qc_extra], backend) +# self.assertIsInstance(circuits, list) +# self.assertEqual(len(circuits), 2) +# +# for circuit in circuits: +# self.assertIsInstance(circuit, QuantumCircuit) +# +# def test_transpile_singleton(self): +# """Test transpile a single-element list with a circuit. +# +# Check that `transpile` returns a single-element list. +# +# See https://github.com/Qiskit/qiskit-terra/issues/5260 +# """ +# backend = BasicAer.get_backend("qasm_simulator") +# +# qubit_reg = QuantumRegister(2) +# clbit_reg = ClassicalRegister(2) +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# qc.measure(qubit_reg, clbit_reg) +# +# circuits = transpile([qc], backend) +# self.assertIsInstance(circuits, list) +# self.assertEqual(len(circuits), 1) +# self.assertIsInstance(circuits[0], QuantumCircuit) +# +# def test_mapping_correction(self): +# """Test mapping works in previous failed case.""" +# backend = FakeGeneric(num_qubits=12) +# qr = QuantumRegister(name="qr", size=11) +# cr = ClassicalRegister(name="qc", size=11) +# circuit = QuantumCircuit(qr, cr) +# circuit.u(1.564784764685993, -1.2378965763410095, 2.9746763177861713, qr[3]) +# circuit.u(1.2269835563676523, 1.1932982847014162, -1.5597357740824318, qr[5]) +# circuit.cx(qr[5], qr[3]) +# circuit.p(0.856768317675967, qr[3]) +# circuit.u(-3.3911273825190915, 0.0, 0.0, qr[5]) +# circuit.cx(qr[3], qr[5]) +# circuit.u(2.159209321625547, 0.0, 0.0, qr[5]) +# circuit.cx(qr[5], qr[3]) +# circuit.u(0.30949966910232335, 1.1706201763833217, 1.738408691990081, qr[3]) +# circuit.u(1.9630571407274755, -0.6818742967975088, 1.8336534616728195, qr[5]) +# circuit.u(1.330181833806101, 0.6003162754946363, -3.181264980452862, qr[7]) +# circuit.u(0.4885914820775024, 3.133297443244865, -2.794457469189904, qr[8]) +# circuit.cx(qr[8], qr[7]) +# circuit.p(2.2196187596178616, qr[7]) +# circuit.u(-3.152367609631023, 0.0, 0.0, qr[8]) +# circuit.cx(qr[7], qr[8]) +# circuit.u(1.2646005789809263, 0.0, 0.0, qr[8]) +# circuit.cx(qr[8], qr[7]) +# circuit.u(0.7517780502091939, 1.2828514296564781, 1.6781179605443775, qr[7]) +# circuit.u(0.9267400575390405, 2.0526277839695153, 2.034202361069533, qr[8]) +# circuit.u(2.550304293455634, 3.8250017126569698, -2.1351609599720054, qr[1]) +# circuit.u(0.9566260876600556, -1.1147561503064538, 2.0571590492298797, qr[4]) +# circuit.cx(qr[4], qr[1]) +# circuit.p(2.1899329069137394, qr[1]) +# circuit.u(-1.8371715243173294, 0.0, 0.0, qr[4]) +# circuit.cx(qr[1], qr[4]) +# circuit.u(0.4717053496327104, 0.0, 0.0, qr[4]) +# circuit.cx(qr[4], qr[1]) +# circuit.u(2.3167620677708145, -1.2337330260253256, -0.5671322899563955, qr[1]) +# circuit.u(1.0468499525240678, 0.8680750644809365, -1.4083720073192485, qr[4]) +# circuit.u(2.4204244021892807, -2.211701932616922, 3.8297006565735883, qr[10]) +# circuit.u(0.36660280497727255, 3.273119149343493, -1.8003362351299388, qr[6]) +# circuit.cx(qr[6], qr[10]) +# circuit.p(1.067395863586385, qr[10]) +# circuit.u(-0.7044917541291232, 0.0, 0.0, qr[6]) +# circuit.cx(qr[10], qr[6]) +# circuit.u(2.1830003849921527, 0.0, 0.0, qr[6]) +# circuit.cx(qr[6], qr[10]) +# circuit.u(2.1538343756723917, 2.2653381826084606, -3.550087952059485, qr[10]) +# circuit.u(1.307627685019188, -0.44686656993522567, -2.3238098554327418, qr[6]) +# circuit.u(2.2046797998462906, 0.9732961754855436, 1.8527865921467421, qr[9]) +# circuit.u(2.1665254613904126, -1.281337664694577, -1.2424905413631209, qr[0]) +# circuit.cx(qr[0], qr[9]) +# circuit.p(2.6209599970201007, qr[9]) +# circuit.u(0.04680566321901303, 0.0, 0.0, qr[0]) +# circuit.cx(qr[9], qr[0]) +# circuit.u(1.7728411151289603, 0.0, 0.0, qr[0]) +# circuit.cx(qr[0], qr[9]) +# circuit.u(2.4866395967434443, 0.48684511243566697, -3.0069186877854728, qr[9]) +# circuit.u(1.7369112924273789, -4.239660866163805, 1.0623389015296005, qr[0]) +# circuit.barrier(qr) +# circuit.measure(qr, cr) +# +# circuits = transpile(circuit, backend) +# +# self.assertIsInstance(circuits, QuantumCircuit) +# +# def test_transpiler_layout_from_intlist(self): +# """A list of ints gives layout to correctly map circuit. +# virtual physical +# q1_0 - 4 ---[H]--- +# q2_0 - 5 +# q2_1 - 6 ---[H]--- +# q3_0 - 8 +# q3_1 - 9 +# q3_2 - 10 ---[H]--- +# +# """ +# qr1 = QuantumRegister(1, "qr1") +# qr2 = QuantumRegister(2, "qr2") +# qr3 = QuantumRegister(3, "qr3") +# qc = QuantumCircuit(qr1, qr2, qr3) +# qc.h(qr1[0]) +# qc.h(qr2[1]) +# qc.h(qr3[2]) +# layout = [4, 5, 6, 8, 9, 10] +# +# cmap = [ +# [1, 0], +# [1, 2], +# [2, 3], +# [4, 3], +# [4, 10], +# [5, 4], +# [5, 6], +# [5, 9], +# [6, 8], +# [7, 8], +# [9, 8], +# [9, 10], +# [11, 3], +# [11, 10], +# [11, 12], +# [12, 2], +# [13, 1], +# [13, 12], +# ] +# +# new_circ = transpile( +# qc, backend=None, coupling_map=cmap, basis_gates=["u2"], initial_layout=layout +# ) +# qubit_indices = {bit: idx for idx, bit in enumerate(new_circ.qubits)} +# mapped_qubits = [] +# +# for instruction in new_circ.data: +# mapped_qubits.append(qubit_indices[instruction.qubits[0]]) +# +# self.assertEqual(mapped_qubits, [4, 6, 10]) +# +# def test_mapping_multi_qreg(self): +# """Test mapping works for multiple qregs.""" +# backend = FakeGeneric(num_qubits=8) +# qr = QuantumRegister(3, name="qr") +# qr2 = QuantumRegister(1, name="qr2") +# qr3 = QuantumRegister(4, name="qr3") +# cr = ClassicalRegister(3, name="cr") +# qc = QuantumCircuit(qr, qr2, qr3, cr) +# qc.h(qr[0]) +# qc.cx(qr[0], qr2[0]) +# qc.cx(qr[1], qr3[2]) +# qc.measure(qr, cr) +# +# circuits = transpile(qc, backend) +# +# self.assertIsInstance(circuits, QuantumCircuit) +# +# def test_transpile_circuits_diff_registers(self): +# """Transpile list of circuits with different qreg names.""" +# backend = FakeGeneric(num_qubits=4) +# circuits = [] +# for _ in range(2): +# qr = QuantumRegister(2) +# cr = ClassicalRegister(2) +# circuit = QuantumCircuit(qr, cr) +# circuit.h(qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.measure(qr, cr) +# circuits.append(circuit) +# +# circuits = transpile(circuits, backend) +# self.assertIsInstance(circuits[0], QuantumCircuit) +# +# def test_wrong_initial_layout(self): +# """Test transpile with a bad initial layout.""" +# backend = FakeGeneric(num_qubits=4) +# +# qubit_reg = QuantumRegister(2, name="q") +# clbit_reg = ClassicalRegister(2, name="c") +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# qc.measure(qubit_reg, clbit_reg) +# +# bad_initial_layout = [ +# QuantumRegister(3, "q")[0], +# QuantumRegister(3, "q")[1], +# QuantumRegister(3, "q")[2], +# ] +# +# with self.assertRaises(TranspilerError): +# transpile(qc, backend, initial_layout=bad_initial_layout) +# +# def test_parameterized_circuit_for_simulator(self): +# """Verify that a parameterized circuit can be transpiled for a simulator backend.""" +# qr = QuantumRegister(2, name="qr") +# qc = QuantumCircuit(qr) +# +# theta = Parameter("theta") +# qc.rz(theta, qr[0]) +# +# transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) +# +# expected_qc = QuantumCircuit(qr) +# expected_qc.append(RZGate(theta), [qr[0]]) +# self.assertEqual(expected_qc, transpiled_qc) +# +# def test_parameterized_circuit_for_device(self): +# """Verify that a parameterized circuit can be transpiled for a device backend.""" +# qr = QuantumRegister(2, name="qr") +# qc = QuantumCircuit(qr) +# +# theta = Parameter("theta") +# qc.p(theta, qr[0]) +# backend = FakeGeneric(num_qubits=4) +# +# transpiled_qc = transpile( +# qc, +# backend=backend, +# initial_layout=Layout.generate_trivial_layout(qr), +# ) +# +# qr = QuantumRegister(backend.num_qubits, "q") +# expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) +# expected_qc.append(RZGate(theta), [qr[0]]) +# +# self.assertEqual(expected_qc, transpiled_qc) +# +# def test_parameter_expression_circuit_for_simulator(self): +# """Verify that a circuit including expressions of parameters can be +# transpiled for a simulator backend.""" +# qr = QuantumRegister(2, name="qr") +# qc = QuantumCircuit(qr) +# +# theta = Parameter("theta") +# square = theta * theta +# qc.rz(square, qr[0]) +# +# transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) +# +# expected_qc = QuantumCircuit(qr) +# expected_qc.append(RZGate(square), [qr[0]]) +# self.assertEqual(expected_qc, transpiled_qc) +# +# def test_parameter_expression_circuit_for_device(self): +# """Verify that a circuit including expressions of parameters can be +# transpiled for a device backend.""" +# qr = QuantumRegister(2, name="qr") +# qc = QuantumCircuit(qr) +# +# theta = Parameter("theta") +# square = theta * theta +# qc.rz(square, qr[0]) +# backend = FakeGeneric(num_qubits=4) +# +# transpiled_qc = transpile( +# qc, +# backend=backend, +# initial_layout=Layout.generate_trivial_layout(qr), +# ) +# +# qr = QuantumRegister(backend.num_qubits, "q") +# expected_qc = QuantumCircuit(qr) +# expected_qc.append(RZGate(square), [qr[0]]) +# self.assertEqual(expected_qc, transpiled_qc) +# +# def test_final_measurement_barrier_for_devices(self): +# """Verify BarrierBeforeFinalMeasurements pass is called in default pipeline for devices.""" +# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") +# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) +# layout = Layout.generate_trivial_layout(*circ.qregs) +# orig_pass = BarrierBeforeFinalMeasurements() +# with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: +# transpile( +# circ, +# coupling_map=FakeGeneric(num_qubits=6).coupling_map, +# initial_layout=layout, +# ) +# self.assertTrue(mock_pass.called) +# +# def test_do_not_run_gatedirection_with_symmetric_cm(self): +# """When the coupling map is symmetric, do not run GateDirection.""" +# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") +# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) +# layout = Layout.generate_trivial_layout(*circ.qregs) +# coupling_map = [] +# for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: +# coupling_map.append([node1, node2]) +# coupling_map.append([node2, node1]) +# +# orig_pass = GateDirection(CouplingMap(coupling_map)) +# with patch.object(GateDirection, "run", wraps=orig_pass.run) as mock_pass: +# transpile(circ, coupling_map=coupling_map, initial_layout=layout) +# self.assertFalse(mock_pass.called) +# +# def test_optimize_to_nothing(self): +# """Optimize gates up to fixed point in the default pipeline +# See https://github.com/Qiskit/qiskit-terra/issues/2035 +# """ +# # ┌───┐ ┌───┐┌───┐┌───┐ ┌───┐ +# # q0_0: ┤ H ├──■──┤ X ├┤ Y ├┤ Z ├──■──┤ H ├──■────■── +# # └───┘┌─┴─┐└───┘└───┘└───┘┌─┴─┐└───┘┌─┴─┐┌─┴─┐ +# # q0_1: ─────┤ X ├───────────────┤ X ├─────┤ X ├┤ X ├ +# # └───┘ └───┘ └───┘└───┘ +# qr = QuantumRegister(2) +# circ = QuantumCircuit(qr) +# circ.h(qr[0]) +# circ.cx(qr[0], qr[1]) +# circ.x(qr[0]) +# circ.y(qr[0]) +# circ.z(qr[0]) +# circ.cx(qr[0], qr[1]) +# circ.h(qr[0]) +# circ.cx(qr[0], qr[1]) +# circ.cx(qr[0], qr[1]) +# +# after = transpile(circ, coupling_map=[[0, 1], [1, 0]], basis_gates=["u3", "u2", "u1", "cx"]) +# +# expected = QuantumCircuit(QuantumRegister(2, "q"), global_phase=-np.pi / 2) +# msg = f"after:\n{after}\nexpected:\n{expected}" +# self.assertEqual(after, expected, msg=msg) +# +# def test_pass_manager_empty(self): +# """Test passing an empty PassManager() to the transpiler. +# +# It should perform no transformations on the circuit. +# """ +# qr = QuantumRegister(2) +# circuit = QuantumCircuit(qr) +# circuit.h(qr[0]) +# circuit.h(qr[0]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# circuit.cx(qr[0], qr[1]) +# resources_before = circuit.count_ops() +# +# pass_manager = PassManager() +# out_circuit = pass_manager.run(circuit) +# resources_after = out_circuit.count_ops() +# +# self.assertDictEqual(resources_before, resources_after) +# +# def test_move_measurements(self): +# """Measurements applied AFTER swap mapping.""" +# cmap = FakeGeneric(num_qubits=16).coupling_map +# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") +# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) +# +# lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] +# out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic") +# out_dag = circuit_to_dag(out) +# meas_nodes = out_dag.named_nodes("measure") +# for meas_node in meas_nodes: +# is_last_measure = all( +# isinstance(after_measure, DAGOutNode) +# for after_measure in out_dag.quantum_successors(meas_node) +# ) +# self.assertTrue(is_last_measure) +# +# @data(0, 1, 2, 3) +# def test_init_resets_kept_preset_passmanagers(self, optimization_level): +# """Test initial resets kept at all preset transpilation levels""" +# num_qubits = 5 +# qc = QuantumCircuit(num_qubits) +# qc.reset(range(num_qubits)) +# +# num_resets = transpile(qc, optimization_level=optimization_level).count_ops()["reset"] +# self.assertEqual(num_resets, num_qubits) +# +# @data(0, 1, 2, 3) +# def test_initialize_reset_is_not_removed(self, optimization_level): +# """The reset in front of initializer should NOT be removed at beginning""" +# qr = QuantumRegister(1, "qr") +# qc = QuantumCircuit(qr) +# qc.initialize([1.0 / math.sqrt(2), 1.0 / math.sqrt(2)], [qr[0]]) +# qc.initialize([1.0 / math.sqrt(2), -1.0 / math.sqrt(2)], [qr[0]]) +# +# after = transpile(qc, basis_gates=["reset", "u3"], optimization_level=optimization_level) +# self.assertEqual(after.count_ops()["reset"], 2, msg=f"{after}\n does not have 2 resets.") +# +# def test_initialize_FakeMelbourne(self): +# """Test that the zero-state resets are remove in a device not supporting them.""" +# desired_vector = [1 / math.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / math.sqrt(2)] +# qr = QuantumRegister(3, "qr") +# qc = QuantumCircuit(qr) +# qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) +# +# out = transpile(qc, backend=FakeGeneric(num_qubits=4)) +# out_dag = circuit_to_dag(out) +# reset_nodes = out_dag.named_nodes("reset") +# +# self.assertEqual(len(reset_nodes), 3) +# +# def test_non_standard_basis(self): +# """Test a transpilation with a non-standard basis""" +# qr1 = QuantumRegister(1, "q1") +# qr2 = QuantumRegister(2, "q2") +# qr3 = QuantumRegister(3, "q3") +# qc = QuantumCircuit(qr1, qr2, qr3) +# qc.h(qr1[0]) +# qc.h(qr2[1]) +# qc.h(qr3[2]) +# layout = [4, 5, 6, 8, 9, 10] +# +# cmap = [ +# [1, 0], +# [1, 2], +# [2, 3], +# [4, 3], +# [4, 10], +# [5, 4], +# [5, 6], +# [5, 9], +# [6, 8], +# [7, 8], +# [9, 8], +# [9, 10], +# [11, 3], +# [11, 10], +# [11, 12], +# [12, 2], +# [13, 1], +# [13, 12], +# ] +# +# circuit = transpile( +# qc, backend=None, coupling_map=cmap, basis_gates=["h"], initial_layout=layout +# ) +# +# dag_circuit = circuit_to_dag(circuit) +# resources_after = dag_circuit.count_ops() +# self.assertEqual({"h": 3}, resources_after) +# +# def test_hadamard_to_rot_gates(self): +# """Test a transpilation from H to Rx, Ry gates""" +# qr = QuantumRegister(1) +# qc = QuantumCircuit(qr) +# qc.h(0) +# +# expected = QuantumCircuit(qr, global_phase=np.pi / 2) +# expected.append(RYGate(theta=np.pi / 2), [0]) +# expected.append(RXGate(theta=np.pi), [0]) +# +# circuit = transpile(qc, basis_gates=["rx", "ry"], optimization_level=0) +# self.assertEqual(circuit, expected) +# +# def test_basis_subset(self): +# """Test a transpilation with a basis subset of the standard basis""" +# qr = QuantumRegister(1, "q1") +# qc = QuantumCircuit(qr) +# qc.h(qr[0]) +# qc.x(qr[0]) +# qc.t(qr[0]) +# +# layout = [4] +# +# cmap = [ +# [1, 0], +# [1, 2], +# [2, 3], +# [4, 3], +# [4, 10], +# [5, 4], +# [5, 6], +# [5, 9], +# [6, 8], +# [7, 8], +# [9, 8], +# [9, 10], +# [11, 3], +# [11, 10], +# [11, 12], +# [12, 2], +# [13, 1], +# [13, 12], +# ] +# +# circuit = transpile( +# qc, backend=None, coupling_map=cmap, basis_gates=["u3"], initial_layout=layout +# ) +# +# dag_circuit = circuit_to_dag(circuit) +# resources_after = dag_circuit.count_ops() +# self.assertEqual({"u3": 1}, resources_after) +# +# def test_check_circuit_width(self): +# """Verify transpilation of circuit with virtual qubits greater than +# physical qubits raises error""" +# cmap = [ +# [1, 0], +# [1, 2], +# [2, 3], +# [4, 3], +# [4, 10], +# [5, 4], +# [5, 6], +# [5, 9], +# [6, 8], +# [7, 8], +# [9, 8], +# [9, 10], +# [11, 3], +# [11, 10], +# [11, 12], +# [12, 2], +# [13, 1], +# [13, 12], +# ] +# +# qc = QuantumCircuit(15, 15) +# +# with self.assertRaises(TranspilerError): +# transpile(qc, coupling_map=cmap) +# +# @data(0, 1, 2, 3) +# def test_ccx_routing_method_none(self, optimization_level): +# """CCX without routing method.""" +# +# qc = QuantumCircuit(3) +# qc.cx(0, 1) +# qc.cx(1, 2) +# +# out = transpile( +# qc, +# routing_method="none", +# basis_gates=["u", "cx"], +# initial_layout=[0, 1, 2], +# seed_transpiler=0, +# coupling_map=[[0, 1], [1, 2]], +# optimization_level=optimization_level, +# ) +# +# self.assertTrue(Operator(qc).equiv(out)) +# +# @data(0, 1, 2, 3) +# def test_ccx_routing_method_none_failed(self, optimization_level): +# """CCX without routing method cannot be routed.""" +# +# qc = QuantumCircuit(3) +# qc.ccx(0, 1, 2) +# +# with self.assertRaises(TranspilerError): +# transpile( +# qc, +# routing_method="none", +# basis_gates=["u", "cx"], +# initial_layout=[0, 1, 2], +# seed_transpiler=0, +# coupling_map=[[0, 1], [1, 2]], +# optimization_level=optimization_level, +# ) +# +# @data(0, 1, 2, 3) +# def test_ms_unrolls_to_cx(self, optimization_level): +# """Verify a Rx,Ry,Rxx circuit transpile to a U3,CX target.""" +# +# qc = QuantumCircuit(2) +# qc.rx(math.pi / 2, 0) +# qc.ry(math.pi / 4, 1) +# qc.rxx(math.pi / 4, 0, 1) +# +# out = transpile(qc, basis_gates=["u3", "cx"], optimization_level=optimization_level) +# +# self.assertTrue(Operator(qc).equiv(out)) +# +# @data(0, 1, 2, 3) +# def test_ms_can_target_ms(self, optimization_level): +# """Verify a Rx,Ry,Rxx circuit can transpile to an Rx,Ry,Rxx target.""" +# +# qc = QuantumCircuit(2) +# qc.rx(math.pi / 2, 0) +# qc.ry(math.pi / 4, 1) +# qc.rxx(math.pi / 4, 0, 1) +# +# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) +# +# self.assertTrue(Operator(qc).equiv(out)) +# +# @data(0, 1, 2, 3) +# def test_cx_can_target_ms(self, optimization_level): +# """Verify a U3,CX circuit can transpiler to a Rx,Ry,Rxx target.""" +# +# qc = QuantumCircuit(2) +# qc.h(0) +# qc.cx(0, 1) +# qc.rz(math.pi / 4, [0, 1]) +# +# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) +# +# self.assertTrue(Operator(qc).equiv(out)) +# +# @data(0, 1, 2, 3) +# def test_measure_doesnt_unroll_ms(self, optimization_level): +# """Verify a measure doesn't cause an Rx,Ry,Rxx circuit to unroll to U3,CX.""" +# +# qc = QuantumCircuit(2, 2) +# qc.rx(math.pi / 2, 0) +# qc.ry(math.pi / 4, 1) +# qc.rxx(math.pi / 4, 0, 1) +# qc.measure([0, 1], [0, 1]) +# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) +# +# self.assertEqual(qc, out) +# +# @data( +# ["cx", "u3"], +# ["cz", "u3"], +# ["cz", "rx", "rz"], +# ["rxx", "rx", "ry"], +# ["iswap", "rx", "rz"], +# ) +# def test_block_collection_runs_for_non_cx_bases(self, basis_gates): +# """Verify block collection is run when a single two qubit gate is in the basis.""" +# twoq_gate, *_ = basis_gates +# +# qc = QuantumCircuit(2) +# qc.cx(0, 1) +# qc.cx(1, 0) +# qc.cx(0, 1) +# qc.cx(0, 1) +# +# out = transpile(qc, basis_gates=basis_gates, optimization_level=3) +# +# self.assertLessEqual(out.count_ops()[twoq_gate], 2) +# +# @unpack +# @data( +# (["u3", "cx"], {"u3": 1, "cx": 1}), +# (["rx", "rz", "iswap"], {"rx": 6, "rz": 12, "iswap": 2}), +# (["rx", "ry", "rxx"], {"rx": 6, "ry": 5, "rxx": 1}), +# ) +# def test_block_collection_reduces_1q_gate(self, basis_gates, gate_counts): +# """For synthesis to non-U3 bases, verify we minimize 1q gates.""" +# qc = QuantumCircuit(2) +# qc.h(0) +# qc.cx(0, 1) +# +# out = transpile(qc, basis_gates=basis_gates, optimization_level=3) +# +# self.assertTrue(Operator(out).equiv(qc)) +# self.assertTrue(set(out.count_ops()).issubset(basis_gates)) +# for basis_gate in basis_gates: +# self.assertLessEqual(out.count_ops()[basis_gate], gate_counts[basis_gate]) +# +# @combine( +# optimization_level=[0, 1, 2, 3], +# basis_gates=[ +# ["u3", "cx"], +# ["rx", "rz", "iswap"], +# ["rx", "ry", "rxx"], +# ], +# ) +# def test_translation_method_synthesis(self, optimization_level, basis_gates): +# """Verify translation_method='synthesis' gets to the basis.""" +# qc = QuantumCircuit(2) +# qc.h(0) +# qc.cx(0, 1) +# +# out = transpile( +# qc, +# translation_method="synthesis", +# basis_gates=basis_gates, +# optimization_level=optimization_level, +# ) +# +# self.assertTrue(Operator(out).equiv(qc)) +# self.assertTrue(set(out.count_ops()).issubset(basis_gates)) +# +# def test_transpiled_custom_gates_calibration(self): +# """Test if transpiled calibrations is equal to custom gates circuit calibrations.""" +# custom_180 = Gate("mycustom", 1, [3.14]) +# custom_90 = Gate("mycustom", 1, [1.57]) +# +# circ = QuantumCircuit(2) +# circ.append(custom_180, [0]) +# circ.append(custom_90, [1]) +# +# with pulse.build() as q0_x180: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# with pulse.build() as q1_y90: +# pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) +# +# # Add calibration +# circ.add_calibration(custom_180, [0], q0_x180) +# circ.add_calibration(custom_90, [1], q1_y90) +# +# transpiled_circuit = transpile( +# circ, +# backend=FakeGeneric(num_qubits=4), +# layout_method="trivial", +# ) +# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) +# self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) +# self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) +# +# def test_transpiled_basis_gates_calibrations(self): +# """Test if the transpiled calibrations is equal to basis gates circuit calibrations.""" +# circ = QuantumCircuit(2) +# circ.h(0) +# +# with pulse.build() as q0_x180: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# +# # Add calibration +# circ.add_calibration("h", [0], q0_x180) +# +# transpiled_circuit = transpile( +# circ, +# backend=FakeGeneric(num_qubits=4), +# ) +# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) +# +# def test_transpile_calibrated_custom_gate_on_diff_qubit(self): +# """Test if the custom, non calibrated gate raises QiskitError.""" +# custom_180 = Gate("mycustom", 1, [3.14]) +# +# circ = QuantumCircuit(2) +# circ.append(custom_180, [0]) +# +# with pulse.build() as q0_x180: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# +# # Add calibration +# circ.add_calibration(custom_180, [1], q0_x180) +# +# with self.assertRaises(QiskitError): +# transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") +# +# def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): +# """Test if the non-basis gates are transpiled if they are on different qubit that +# is not calibrated.""" +# circ = QuantumCircuit(2) +# circ.h(0) +# circ.h(1) +# +# with pulse.build() as q0_x180: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# +# # Add calibration +# circ.add_calibration("h", [1], q0_x180) +# +# transpiled_circuit = transpile( +# circ, +# backend=FakeGeneric(num_qubits=4), +# ) +# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) +# self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) +# +# def test_transpile_subset_of_calibrated_gates(self): +# """Test transpiling a circuit with both basis gate (not-calibrated) and +# a calibrated gate on different qubits.""" +# x_180 = Gate("mycustom", 1, [3.14]) +# +# circ = QuantumCircuit(2) +# circ.h(0) +# circ.append(x_180, [0]) +# circ.h(1) +# +# with pulse.build() as q0_x180: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# +# circ.add_calibration(x_180, [0], q0_x180) +# circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 +# +# transpiled_circ = transpile( +# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" +# ) +# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) +# +# def test_parameterized_calibrations_transpile(self): +# """Check that gates can be matched to their calibrations before and after parameter +# assignment.""" +# tau = Parameter("tau") +# circ = QuantumCircuit(3, 3) +# circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) +# +# def q0_rxt(tau): +# with pulse.build() as q0_rxt: +# pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) +# return q0_rxt +# +# circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) +# +# transpiled_circ = transpile( +# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" +# ) +# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) +# circ = circ.assign_parameters({tau: 1}) +# transpiled_circ = transpile( +# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" +# ) +# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) +# +# def test_inst_durations_from_calibrations(self): +# """Test that circuit calibrations can be used instead of explicitly +# supplying inst_durations. +# """ +# qc = QuantumCircuit(2) +# qc.append(Gate("custom", 1, []), [0]) +# +# with pulse.build() as cal: +# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) +# qc.add_calibration("custom", [0], cal) +# +# out = transpile(qc, scheduling_method="alap") +# self.assertEqual(out.duration, cal.duration) +# +# @data(0, 1, 2, 3) +# def test_multiqubit_gates_calibrations(self, opt_level): +# """Test multiqubit gate > 2q with calibrations works +# +# Adapted from issue description in https://github.com/Qiskit/qiskit-terra/issues/6572 +# """ +# circ = QuantumCircuit(5) +# custom_gate = Gate("my_custom_gate", 5, []) +# circ.append(custom_gate, [0, 1, 2, 3, 4]) +# circ.measure_all() +# backend = FakeGeneric(num_qubits=6) +# +# with pulse.build(backend=backend, name="custom") as my_schedule: +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) +# ) +# pulse.play( +# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) +# ) +# circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) +# trans_circ = transpile( +# circ, backend=backend, optimization_level=opt_level, layout_method="trivial" +# ) +# self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) +# +# @data(0, 1, 2, 3) +# def test_circuit_with_delay(self, optimization_level): +# """Verify a circuit with delay can transpile to a scheduled circuit.""" +# +# qc = QuantumCircuit(2) +# qc.h(0) +# qc.delay(500, 1) +# qc.cx(0, 1) +# +# out = transpile( +# qc, +# scheduling_method="alap", +# basis_gates=["h", "cx"], +# instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], +# optimization_level=optimization_level, +# ) +# +# self.assertEqual(out.duration, 1200) +# +# def test_delay_converts_to_dt(self): +# """Test that a delay instruction is converted to units of dt given a backend.""" +# qc = QuantumCircuit(2) +# qc.delay(1000, [0], unit="us") +# +# backend = FakeGeneric(num_qubits=4, dt=0.5e-6) +# out = transpile([qc, qc], backend) +# self.assertEqual(out[0].data[0].operation.unit, "dt") +# self.assertEqual(out[1].data[0].operation.unit, "dt") +# +# out = transpile(qc, dt=1e-9) +# self.assertEqual(out.data[0].operation.unit, "dt") +# +# def test_scheduling_backend_v2(self): +# """Test that scheduling method works with Backendv2.""" +# qc = QuantumCircuit(2) +# qc.h(0) +# qc.cx(0, 1) +# qc.measure_all() +# +# out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") +# self.assertIn("delay", out[0].count_ops()) +# self.assertIn("delay", out[1].count_ops()) +# +# @data(1, 2, 3) +# def test_no_infinite_loop(self, optimization_level): +# """Verify circuit cost always descends and optimization does not flip flop indefinitely.""" +# qc = QuantumCircuit(1) +# qc.ry(0.2, 0) +# +# out = transpile( +# qc, basis_gates=["id", "p", "sx", "cx"], optimization_level=optimization_level +# ) +# +# # Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and +# # a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi +# # for the second and third RZ gates in the U3 decomposition. +# expected = QuantumCircuit( +# 1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi +# ) +# expected.p(-np.pi, 0) +# expected.sx(0) +# expected.p(np.pi - 0.2, 0) +# expected.sx(0) +# +# error_message = ( +# f"\nOutput circuit:\n{out!s}\n{Operator(out).data}\n" +# f"Expected circuit:\n{expected!s}\n{Operator(expected).data}" +# ) +# self.assertEqual(out, expected, error_message) +# +# @data(0, 1, 2, 3) +# def test_transpile_preserves_circuit_metadata(self, optimization_level): +# """Verify that transpile preserves circuit metadata in the output.""" +# circuit = QuantumCircuit(2, metadata={"experiment_id": "1234", "execution_number": 4}) +# circuit.h(0) +# circuit.cx(0, 1) +# +# cmap = [ +# [1, 0], +# [1, 2], +# [2, 3], +# [4, 3], +# [4, 10], +# [5, 4], +# [5, 6], +# [5, 9], +# [6, 8], +# [7, 8], +# [9, 8], +# [9, 10], +# [11, 3], +# [11, 10], +# [11, 12], +# [12, 2], +# [13, 1], +# [13, 12], +# ] +# +# res = transpile( +# circuit, +# basis_gates=["id", "p", "sx", "cx"], +# coupling_map=cmap, +# optimization_level=optimization_level, +# ) +# self.assertEqual(circuit.metadata, res.metadata) +# +# @data(0, 1, 2, 3) +# def test_transpile_optional_registers(self, optimization_level): +# """Verify transpile accepts circuits without registers end-to-end.""" +# +# qubits = [Qubit() for _ in range(3)] +# clbits = [Clbit() for _ in range(3)] +# +# qc = QuantumCircuit(qubits, clbits) +# qc.h(0) +# qc.cx(0, 1) +# qc.cx(1, 2) +# +# qc.measure(qubits, clbits) +# backend = FakeGeneric(num_qubits=4) +# +# out = transpile(qc, backend=backend, optimization_level=optimization_level) +# +# self.assertEqual(len(out.qubits), backend.num_qubits) +# self.assertEqual(len(out.clbits), len(clbits)) +# +# @data(0, 1, 2, 3) +# def test_translate_ecr_basis(self, optimization_level): +# """Verify that rewriting in ECR basis is efficient.""" +# circuit = QuantumCircuit(2) +# circuit.append(random_unitary(4, seed=1), [0, 1]) +# circuit.barrier() +# circuit.cx(0, 1) +# circuit.barrier() +# circuit.swap(0, 1) +# circuit.barrier() +# circuit.iswap(0, 1) +# +# res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=optimization_level) +# self.assertEqual(res.count_ops()["ecr"], 9) +# self.assertTrue(Operator(res).equiv(circuit)) +# +# def test_optimize_ecr_basis(self): +# """Test highest optimization level can optimize over ECR.""" +# circuit = QuantumCircuit(2) +# circuit.swap(1, 0) +# circuit.iswap(0, 1) +# +# res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=3) +# self.assertEqual(res.count_ops()["ecr"], 1) +# self.assertTrue(Operator(res).equiv(circuit)) +# +# def test_approximation_degree_invalid(self): +# """Test invalid approximation degree raises.""" +# circuit = QuantumCircuit(2) +# circuit.swap(0, 1) +# with self.assertRaises(QiskitError): +# transpile(circuit, basis_gates=["u", "cz"], approximation_degree=1.1) +# +# def test_approximation_degree(self): +# """Test more approximation gives lower-cost circuit.""" +# circuit = QuantumCircuit(2) +# circuit.swap(0, 1) +# circuit.h(0) +# circ_10 = transpile( +# circuit, +# basis_gates=["u", "cx"], +# translation_method="synthesis", +# approximation_degree=0.1, +# ) +# circ_90 = transpile( +# circuit, +# basis_gates=["u", "cx"], +# translation_method="synthesis", +# approximation_degree=0.9, +# ) +# self.assertLess(circ_10.depth(), circ_90.depth()) +# +# @data(0, 1, 2, 3) +# def test_synthesis_translation_method_with_single_qubit_gates(self, optimization_level): +# """Test that synthesis basis translation works for solely 1q circuit""" +# qc = QuantumCircuit(3) +# qc.h(0) +# qc.h(1) +# qc.h(2) +# res = transpile( +# qc, +# basis_gates=["id", "rz", "x", "sx", "cx"], +# translation_method="synthesis", +# optimization_level=optimization_level, +# ) +# expected = QuantumCircuit(3, global_phase=3 * np.pi / 4) +# expected.rz(np.pi / 2, 0) +# expected.rz(np.pi / 2, 1) +# expected.rz(np.pi / 2, 2) +# expected.sx(0) +# expected.sx(1) +# expected.sx(2) +# expected.rz(np.pi / 2, 0) +# expected.rz(np.pi / 2, 1) +# expected.rz(np.pi / 2, 2) +# self.assertEqual(res, expected) +# +# @data(0, 1, 2, 3) +# def test_synthesis_translation_method_with_gates_outside_basis(self, optimization_level): +# """Test that synthesis translation works for circuits with single gates outside bassis""" +# qc = QuantumCircuit(2) +# qc.swap(0, 1) +# res = transpile( +# qc, +# basis_gates=["id", "rz", "x", "sx", "cx"], +# translation_method="synthesis", +# optimization_level=optimization_level, +# ) +# if optimization_level != 3: +# self.assertTrue(Operator(qc).equiv(res)) +# self.assertNotIn("swap", res.count_ops()) +# else: +# # Optimization level 3 eliminates the pointless swap +# self.assertEqual(res, QuantumCircuit(2)) +# +# @data(0, 1, 2, 3) +# def test_target_ideal_gates(self, opt_level): +# """Test that transpile() with a custom ideal sim target works.""" +# theta = Parameter("θ") +# phi = Parameter("ϕ") +# lam = Parameter("λ") +# target = Target(num_qubits=2) +# target.add_instruction(UGate(theta, phi, lam), {(0,): None, (1,): None}) +# target.add_instruction(CXGate(), {(0, 1): None}) +# target.add_instruction(Measure(), {(0,): None, (1,): None}) +# qubit_reg = QuantumRegister(2, name="q") +# clbit_reg = ClassicalRegister(2, name="c") +# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") +# qc.h(qubit_reg[0]) +# qc.cx(qubit_reg[0], qubit_reg[1]) +# +# result = transpile(qc, target=target, optimization_level=opt_level) +# +# self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc)) +# +# @data(0, 1, 2, 3) +# def test_transpile_with_custom_control_flow_target(self, opt_level): +# """Test transpile() with a target and constrol flow ops.""" +# target = FakeGeneric(num_qubits=8, dynamic=True).target +# +# circuit = QuantumCircuit(6, 1) +# circuit.h(0) +# circuit.measure(0, 0) +# circuit.cx(0, 1) +# circuit.cz(0, 2) +# circuit.append(CustomCX(), [1, 2], []) +# with circuit.for_loop((1,)): +# circuit.cx(0, 1) +# circuit.cz(0, 2) +# circuit.append(CustomCX(), [1, 2], []) +# with circuit.if_test((circuit.clbits[0], True)) as else_: +# circuit.cx(0, 1) +# circuit.cz(0, 2) +# circuit.append(CustomCX(), [1, 2], []) +# with else_: +# circuit.cx(3, 4) +# circuit.cz(3, 5) +# circuit.append(CustomCX(), [4, 5], []) +# with circuit.while_loop((circuit.clbits[0], True)): +# circuit.cx(3, 4) +# circuit.cz(3, 5) +# circuit.append(CustomCX(), [4, 5], []) +# with circuit.switch(circuit.cregs[0]) as case_: +# with case_(0): +# circuit.cx(0, 1) +# circuit.cz(0, 2) +# circuit.append(CustomCX(), [1, 2], []) +# with case_(1): +# circuit.cx(1, 2) +# circuit.cz(1, 3) +# circuit.append(CustomCX(), [2, 3], []) +# transpiled = transpile( +# circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 +# ) +# # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; +# # here we're just checking that various passes do appear to have run. +# self.assertIsInstance(transpiled, QuantumCircuit) +# # Assert layout ran. +# self.assertIsNot(getattr(transpiled, "_layout", None), None) +# +# def _visit_block(circuit, qubit_mapping=None): +# for instruction in circuit: +# qargs = tuple(qubit_mapping[x] for x in instruction.qubits) +# self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) +# if isinstance(instruction.operation, ControlFlowOp): +# for block in instruction.operation.blocks: +# new_mapping = { +# inner: qubit_mapping[outer] +# for outer, inner in zip(instruction.qubits, block.qubits) +# } +# _visit_block(block, new_mapping) +# # Assert unrolling ran. +# self.assertNotIsInstance(instruction.operation, CustomCX) +# # Assert translation ran. +# self.assertNotIsInstance(instruction.operation, CZGate) +# +# # Assert routing ran. +# _visit_block( +# transpiled, +# qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, +# ) +# +# @data(1, 2, 3) +# def test_transpile_identity_circuit_no_target(self, opt_level): +# """Test circuit equivalent to identity is optimized away for all optimization levels >0. +# +# Reproduce taken from https://github.com/Qiskit/qiskit-terra/issues/9217 +# """ +# qr1 = QuantumRegister(3, "state") +# qr2 = QuantumRegister(2, "ancilla") +# cr = ClassicalRegister(2, "c") +# qc = QuantumCircuit(qr1, qr2, cr) +# qc.h(qr1[0]) +# qc.cx(qr1[0], qr1[1]) +# qc.cx(qr1[1], qr1[2]) +# qc.cx(qr1[1], qr1[2]) +# qc.cx(qr1[0], qr1[1]) +# qc.h(qr1[0]) +# +# empty_qc = QuantumCircuit(qr1, qr2, cr) +# result = transpile(qc, optimization_level=opt_level) +# self.assertEqual(empty_qc, result) +# +# @data(0, 1, 2, 3) +# def test_initial_layout_with_loose_qubits(self, opt_level): +# """Regression test of gh-10125.""" +# qc = QuantumCircuit([Qubit(), Qubit()]) +# qc.cx(0, 1) +# transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) +# self.assertIsNotNone(transpiled.layout) +# self.assertEqual( +# transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) +# ) +# +# @data(0, 1, 2, 3) +# def test_initial_layout_with_overlapping_qubits(self, opt_level): +# """Regression test of gh-10125.""" +# qr1 = QuantumRegister(2, "qr1") +# qr2 = QuantumRegister(bits=qr1[:]) +# qc = QuantumCircuit(qr1, qr2) +# qc.cx(0, 1) +# transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) +# self.assertIsNotNone(transpiled.layout) +# self.assertEqual( +# transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) +# ) +# +# @combine(opt_level=[0, 1, 2, 3], basis=[["rz", "x"], ["rx", "z"], ["rz", "y"], ["ry", "x"]]) +# def test_paulis_to_constrained_1q_basis(self, opt_level, basis): +# """Test that Pauli-gate circuits can be transpiled to constrained 1q bases that do not +# contain any root-Pauli gates.""" +# qc = QuantumCircuit(1) +# qc.x(0) +# qc.barrier() +# qc.y(0) +# qc.barrier() +# qc.z(0) +# transpiled = transpile(qc, basis_gates=basis, optimization_level=opt_level) +# self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys()) +# self.assertEqual(Operator(qc), Operator(transpiled)) +# @ddt class TestPostTranspileIntegration(QiskitTestCase): @@ -1762,192 +1762,192 @@ def _control_flow_expr_circuit(self): base.append(CustomCX(), [3, 4]) return base - @data(0, 1, 2, 3) - def test_qpy_roundtrip(self, optimization_level): - """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be round-tripped - through QPY.""" - if optimization_level == 3 and sys.platform == "win32": - self.skipTest( - "This test case triggers a bug in the eigensolver routine on windows. " - "See #10345 for more details." - ) - - backend = FakeGeneric(num_qubits=8, dynamic=True) - transpiled = transpile( - self._control_flow_circuit(), - backend=backend, - basis_gates=backend.operation_names, - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be round-tripped - through QPY.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - transpiled._layout = None - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_expr(self, optimization_level): - """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - be round-tripped through QPY.""" - if optimization_level == 3 and sys.platform == "win32": - self.skipTest( - "This test case triggers a bug in the eigensolver routine on windows. " - "See #10345 for more details." - ) - backend = FakeGeneric(num_qubits=16) - transpiled = transpile( - self._control_flow_expr_circuit(), - backend=backend, - basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): - """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - be round-tripped through QPY.""" - backend = FakeGeneric(num_qubits=27) - backend.target.add_instruction(IfElseOp, name="if_else") - backend.target.add_instruction(ForLoopOp, name="for_loop") - backend.target.add_instruction(WhileLoopOp, name="while_loop") - backend.target.add_instruction(SwitchCaseOp, name="switch_case") - transpiled = transpile( - self._control_flow_circuit(), - backend=backend, - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - buffer = io.BytesIO() - qpy.dump(transpiled, buffer) - buffer.seek(0) - round_tripped = qpy.load(buffer)[0] - self.assertEqual(round_tripped, transpiled) - - @data(0, 1, 2, 3) - def test_qasm3_output(self, optimization_level): - """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" - transpiled = transpile( - self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) - - @data(0, 1, 2, 3) - def test_qasm3_output_control_flow(self, optimization_level): - """Test that the output of a transpiled circuit with control flow can be dumped into - OpenQASM 3.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2022_10_17, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance( - qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - str, - ) - - @data(0, 1, 2, 3) - def test_qasm3_output_control_flow_expr(self, optimization_level): - """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be - dumped into OpenQASM 3.""" - transpiled = transpile( - self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=27, dynamic=True), - optimization_level=optimization_level, - seed_transpiler=2023_07_26, - ) - # TODO: There's not a huge amount we can sensibly test for the output here until we can - # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # itself doesn't throw an error, though. - self.assertIsInstance( - qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - str, - ) - - @data(0, 1, 2, 3) - def test_transpile_target_no_measurement_error(self, opt_level): - """Test that transpile with a target which contains ideal measurement works - - Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 - """ - target = Target() - target.add_instruction(Measure(), {(0,): None}) - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - res = transpile(qc, target=target, optimization_level=opt_level) - self.assertEqual(qc, res) + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip(self, optimization_level): + # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be round-tripped + # through QPY.""" + # if optimization_level == 3 and sys.platform == "win32": + # self.skipTest( + # "This test case triggers a bug in the eigensolver routine on windows. " + # "See #10345 for more details." + # ) + # + # backend = FakeGeneric(num_qubits=8, dynamic=True) + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=backend, + # basis_gates=backend.operation_names, + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be round-tripped + # through QPY.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=8, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + # transpiled._layout = None + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_expr(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + # be round-tripped through QPY.""" + # if optimization_level == 3 and sys.platform == "win32": + # self.skipTest( + # "This test case triggers a bug in the eigensolver routine on windows. " + # "See #10345 for more details." + # ) + # backend = FakeGeneric(num_qubits=16) + # transpiled = transpile( + # self._control_flow_expr_circuit(), + # backend=backend, + # basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + # be round-tripped through QPY.""" + # backend = FakeGeneric(num_qubits=27) + # backend.target.add_instruction(IfElseOp, name="if_else") + # backend.target.add_instruction(ForLoopOp, name="for_loop") + # backend.target.add_instruction(WhileLoopOp, name="while_loop") + # backend.target.add_instruction(SwitchCaseOp, name="switch_case") + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=backend, + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # buffer = io.BytesIO() + # qpy.dump(transpiled, buffer) + # buffer.seek(0) + # round_tripped = qpy.load(buffer)[0] + # self.assertEqual(round_tripped, transpiled) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output(self, optimization_level): + # """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" + # transpiled = transpile( + # self._regular_circuit(), + # backend=FakeGeneric(num_qubits=8), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output_control_flow(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow can be dumped into + # OpenQASM 3.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=8, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2022_10_17, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance( + # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + # str, + # ) + # + # @data(0, 1, 2, 3) + # def test_qasm3_output_control_flow_expr(self, optimization_level): + # """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be + # dumped into OpenQASM 3.""" + # transpiled = transpile( + # self._control_flow_circuit(), + # backend=FakeGeneric(num_qubits=27, dynamic=True), + # optimization_level=optimization_level, + # seed_transpiler=2023_07_26, + # ) + # # TODO: There's not a huge amount we can sensibly test for the output here until we can + # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # # itself doesn't throw an error, though. + # self.assertIsInstance( + # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + # str, + # ) + # + # @data(0, 1, 2, 3) + # def test_transpile_target_no_measurement_error(self, opt_level): + # """Test that transpile with a target which contains ideal measurement works + # + # Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 + # """ + # target = Target() + # target.add_instruction(Measure(), {(0,): None}) + # qc = QuantumCircuit(1, 1) + # qc.measure(0, 0) + # res = transpile(qc, target=target, optimization_level=opt_level) + # self.assertEqual(qc, res) def test_transpile_final_layout_updated_with_post_layout(self): """Test that the final layout is correctly set when vf2postlayout runs. From 5592167d62db61c31dfb89788ca992f0efc8cafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 11 Oct 2023 11:56:12 +0200 Subject: [PATCH 17/56] Fix tests, lint, refactor --- .../providers/fake_provider/fake_generic.py | 164 +- test/python/compiler/test_transpiler.py | 3516 ++++++++--------- .../fake_provider/test_fake_generic.py | 11 +- 3 files changed, 1865 insertions(+), 1826 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index a569ea6ceaed..fca806749262 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -10,14 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Generic FakeBackendV2 class""" +"""Generic fake BackendV2 class""" from __future__ import annotations import statistics import warnings from collections.abc import Iterable - import numpy as np from qiskit import pulse @@ -31,61 +30,69 @@ ContinueLoopOp, ) from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate - from qiskit.exceptions import QiskitError from qiskit.transpiler import CouplingMap, Target, InstructionProperties, QubitProperties +from qiskit.providers import Options from qiskit.providers.basicaer import BasicAer from qiskit.providers.backend import BackendV2 from qiskit.providers.models import ( PulseDefaults, Command, ) -from qiskit.pulse import InstructionScheduleMap +from qiskit.pulse import ( + InstructionScheduleMap, + AcquireChannel, + ControlChannel, + DriveChannel, + MeasureChannel, +) from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem from qiskit.utils import optionals as _optionals class FakeGeneric(BackendV2): """ - Generate a generic fake backend, this backend will have properties and configuration according - to the settings passed in the argument. + Configurable fake BackendV2 generator for unit testing purposes. This class will + generate a fake backend from a combination of random defaults + (with a fixable `seed`) and properties and configuration driven from a series of + optional input arguments. Arguments: - num_qubits: Pass in the integer which is the number of qubits of the backend. - Example: num_qubits = 19 + num_qubits (int): Minimum number of qubits in the fake backend. + The final number of qubits will be updated to be coherent + with the specified coupling map/ coupling map type. - coupling_map: Pass in the coupling Map of the backend as a list of tuples. - Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. If None passed then the - coupling map will be generated randomly - This map will be in accordance with the argument coupling_map_type. + coupling_map (list[tuple[int]]): Backend's coupling map as a list of tuples. + Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. If None is passed then the + coupling map will be generated randomly, in + accordance with the coupling_map_type argument. - coupling_map_type: Pass in the type of coupling map to be generated. If coupling map + coupling_map_type (str): Type of coupling map to be generated. If coupling map is passed, then this option will be overriden. Valid types of coupling - map: 'grid', 'heavy_hex'. Heavy Hex Lattice Reference: - https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 + map: 'grid' (default), 'heavy_hex'. - basis_gates: Pass in the basis gates of the backend as list of strings. - Example: ['cx', 'id', 'rz', 'sx', 'x'] --> - This is the default basis gates of the backend. + basis_gates (list[str]): Basis gates supported by the backend as list of strings. + If None is passed, this parameter will be set internally + to ['cx', 'id', 'rz', 'sx', 'x']. - dynamic: Enable/Disable dynamic circuits on this backend. True: Enable, - False: Disable (Default) + dynamic (bool): If True, enable dynamic circuits on this backend. + Defaults to False. - bidirectional_cp_mp: Enable/Disable bi-directional coupling map. - True: Enable - False: Disable (Default) - replace_cx_with_ecr: True: (Default) Replace every occurrence of 'cx' with 'ecr' - False: Do not replace 'cx' with 'ecr' + bidirectional_cp_mp (bool): If True, enable bi-directional coupling map. + Defaults to False. + replace_cx_with_ecr (bool): If True, replace every occurrence of 'cx' with 'ecr'. + Defaults to True. - enable_reset: True: (Default) this enables the reset on the backend - False: This disables the reset on the backend + enable_reset (bool): If True, enable reset on the backend. + Defaults to True. - dt: The system time resolution of input signals in seconds. - Default is 0.2222ns + dt (float): The system time resolution of input signals in seconds. + Default is 0.2222ns - skip_calibration_gates: Optional list of gates where we do not wish to - append a calibration schedule. - seed: )ptional seed for error and duration value generation. + skip_calibration_gates (list[str]): Optional list of gates where you do not wish to + append a calibration schedule. + + seed (int): Optional seed for error and duration value generation. Returns: None @@ -98,7 +105,7 @@ def __init__( self, num_qubits: int = None, *, - coupling_map: list | tuple[str, str] = None, + coupling_map: list[tuple[int]] = None, coupling_map_type: str = "grid", basis_gates: list[str] = None, dynamic: bool = False, @@ -114,17 +121,16 @@ def __init__( super().__init__( provider=None, name="fake_generic", - description=f"This is a {num_qubits} qubit fake device, " - f"with generic settings. It has been generated right now!", + description=f"This is a fake device with at least {num_qubits} " + f"and generic settings. It has been generated right now!", backend_version="", ) self.sim = None self._rng = np.random.default_rng(seed=seed) - self._num_qubits = num_qubits self._set_basis_gates(basis_gates, replace_cx_with_ecr) - self._set_coupling_map(coupling_map, coupling_map_type, bidirectional_cmap) + self._set_coupling_map(num_qubits, coupling_map, coupling_map_type, bidirectional_cmap) self._target = Target( description="Fake Generic Backend", @@ -146,26 +152,50 @@ def __init__( self._build_default_channels() @property - def target(self): + def target(self) -> Target: + """Return the target. + + Returns: + Target: The backend's target. + """ return self._target @property - def coupling_map(self): + def coupling_map(self) -> CouplingMap: + """Return the coupling map. + + Returns: + CouplingMap: The backend's coupling map. + """ return self._coupling_map @property - def basis_gates(self): + def basis_gates(self) -> list[str]: + """Return the basis gates. + + Returns: + list[str]: The list of accepted basis gates + """ return self._basis_gates @property - def max_circuits(self): + def max_circuits(self) -> None: + """Placeholder to be able to query max_circuits. Set to None. + + Returns: None + """ return None @property def meas_map(self) -> list[list[int]]: + """Return the measurement map in a V1-compatible way. + + Returns: + list[list[int]]: The list of concurrent measurements/measurement map + """ return self._target.concurrent_measurements - def drive_channel(self, qubit: int): + def drive_channel(self, qubit: int) -> DriveChannel | None: """Return the drive channel for the given qubit. Returns: @@ -177,7 +207,7 @@ def drive_channel(self, qubit: int): return drive_channels_map[qubits][0] return None - def measure_channel(self, qubit: int): + def measure_channel(self, qubit: int) -> MeasureChannel | None: """Return the measure stimulus channel for the given qubit. Returns: @@ -189,7 +219,7 @@ def measure_channel(self, qubit: int): return measure_channels_map[qubits][0] return None - def acquire_channel(self, qubit: int): + def acquire_channel(self, qubit: int) -> AcquireChannel | None: """Return the acquisition channel for the given qubit. Returns: @@ -201,7 +231,7 @@ def acquire_channel(self, qubit: int): return acquire_channels_map[qubits][0] return None - def control_channel(self, qubits: Iterable[int]): + def control_channel(self, qubits: Iterable[int]) -> list[ControlChannel]: """Return the secondary drive channel for the given qubit This is typically utilized for controlling multiqubit interactions. @@ -212,7 +242,7 @@ def control_channel(self, qubits: Iterable[int]): ``(control_qubit, target_qubit)``. Returns: - List[ControlChannel]: The multi qubit control line. + list[ControlChannel]: The multi qubit control line. """ control_channels_map = getattr(self, "channels_map", {}).get("control", {}) qubits = tuple(qubits) @@ -297,7 +327,7 @@ def _setup_sim(self): self.sim = BasicAer.get_backend("qasm_simulator") @classmethod - def _default_options(cls): + def _default_options(cls) -> Options: """Return the default options This method will return a :class:`qiskit.providers.Options` @@ -306,7 +336,7 @@ def _default_options(cls): backend. Returns: - qiskit.providers.Options: A options object with + qiskit.providers.Options: An options object with default values set """ if _optionals.HAS_AER: @@ -316,7 +346,7 @@ def _default_options(cls): else: return BasicAer.get_backend("qasm_simulator")._default_options() - def _set_basis_gates(self, basis_gates: list[str] = None, replace_cx_with_ecr=None): + def _set_basis_gates(self, basis_gates: list[str], replace_cx_with_ecr: bool) -> None: default_gates = ["cx", "id", "rz", "sx", "x"] @@ -333,22 +363,28 @@ def _set_basis_gates(self, basis_gates: list[str] = None, replace_cx_with_ecr=No if "measure" not in self._basis_gates: self._basis_gates.append("measure") - def _set_coupling_map(self, coupling_map, coupling_map_type, bidirectional_cmap): + def _set_coupling_map( + self, + num_qubits: int | None, + coupling_map: list[tuple[int]], + coupling_map_type: str, + bidirectional_cmap: bool, + ) -> None: if not coupling_map: - if not self._num_qubits: + if not num_qubits: raise QiskitError( - "Please provide either `num_qubits` or `coupling_map` " + "Please provide either ``num_qubits`` or ``coupling_map`` " "to generate a new fake backend." ) if coupling_map_type == "heavy_hex": - distance = self._get_cmap_args(coupling_map_type) + distance = self._get_cmap_args(coupling_map_type, num_qubits) self._coupling_map = CouplingMap().from_heavy_hex( distance=distance, bidirectional=bidirectional_cmap ) elif coupling_map_type == "grid": - num_rows, num_columns = self._get_cmap_args(coupling_map_type) + num_rows, num_columns = self._get_cmap_args(coupling_map_type, num_qubits) self._coupling_map = CouplingMap().from_grid( num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cmap ) @@ -359,24 +395,24 @@ def _set_coupling_map(self, coupling_map, coupling_map_type, bidirectional_cmap) self._num_qubits = self._coupling_map.size() - def _get_cmap_args(self, coupling_map_type): + def _get_cmap_args(self, coupling_map_type: str, num_qubits: int) -> int | tuple[int] | None: if coupling_map_type == "heavy_hex": for d in range(3, 20, 2): # The description of the formula: 5*d**2 - 2*d -1 is explained in # https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 n = (5 * (d**2) - (2 * d) - 1) / 2 - if n >= self._num_qubits: + if n >= num_qubits: return d elif coupling_map_type == "grid": - factors = [x for x in range(2, self._num_qubits + 1) if self._num_qubits % x == 0] + factors = [x for x in range(2, num_qubits + 1) if num_qubits % x == 0] first_factor = statistics.median_high(factors) - second_factor = int(self._num_qubits / first_factor) + second_factor = int(num_qubits / first_factor) return (first_factor, second_factor) return None - def _add_gate_instructions_to_target(self, dynamic, enable_reset): + def _add_gate_instructions_to_target(self, dynamic: bool, enable_reset: bool) -> None: instruction_dict = self._get_default_instruction_dict() @@ -399,7 +435,7 @@ def _add_gate_instructions_to_target(self, dynamic, enable_reset): Reset(), {(qubit_idx,): None for qubit_idx in range(self._num_qubits)} ) - def _get_default_instruction_dict(self): + def _get_default_instruction_dict(self) -> dict[str, tuple]: instruction_dict = { "ecr": ( @@ -473,7 +509,9 @@ def _get_default_instruction_dict(self): } return instruction_dict - def _add_calibration_defaults_to_target(self, instruction_schedule_map, skip_calibration_gates): + def _add_calibration_defaults_to_target( + self, instruction_schedule_map: InstructionScheduleMap, skip_calibration_gates: bool + ) -> None: if skip_calibration_gates is None: skip_calibration_gates = [] @@ -500,7 +538,7 @@ def _add_calibration_defaults_to_target(self, instruction_schedule_map, skip_cal elif qargs in self._target[inst] and inst != "delay": self._target[inst][qargs].calibration = calibration_entry - def _build_calibration_defaults(self, skip_calibration_gates) -> PulseDefaults: + def _build_calibration_defaults(self, skip_calibration_gates: list[str]) -> PulseDefaults: """Build calibration defaults.""" measure_command_sequence = [ @@ -586,7 +624,7 @@ def _build_calibration_defaults(self, skip_calibration_gates) -> PulseDefaults: cmd_def=cmd_def, ) - def _build_default_channels(self): + def _build_default_channels(self) -> None: """Create default channel map and set "channels_map" attribute""" channels_map = { "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 10b4bdb46ace..5e7dafdd6b11 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2019. +# (C) Copyright IBM 2017, 2023. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -92,1574 +92,1574 @@ def connected_qubits(physical: int, coupling_map: CouplingMap) -> set: raise ValueError(f"physical qubit {physical} is not in the coupling map") -# @ddt -# class TestTranspile(QiskitTestCase): -# """Test transpile function.""" -# -# def test_empty_transpilation(self): -# """Test that transpiling an empty list is a no-op. Regression test of gh-7287.""" -# self.assertEqual(transpile([]), []) -# -# def test_pass_manager_none(self): -# """Test passing the default (None) pass manager to the transpiler. -# -# It should perform the default qiskit flow: -# unroll, swap_mapper, cx_direction, cx_cancellation, optimize_1q_gates -# and should be equivalent to using tools.compile -# """ -# qr = QuantumRegister(2, "qr") -# circuit = QuantumCircuit(qr) -# circuit.h(qr[0]) -# circuit.h(qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[1], qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[1], qr[0]) -# -# coupling_map = [[1, 0]] -# basis_gates = ["u1", "u2", "u3", "cx", "id"] -# -# backend = BasicAer.get_backend("qasm_simulator") -# circuit2 = transpile( -# circuit, -# backend=backend, -# coupling_map=coupling_map, -# basis_gates=basis_gates, -# ) -# -# circuit3 = transpile( -# circuit, backend=backend, coupling_map=coupling_map, basis_gates=basis_gates -# ) -# self.assertEqual(circuit2, circuit3) -# -# def test_transpile_basis_gates_no_backend_no_coupling_map(self): -# """Verify transpile() works with no coupling_map or backend.""" -# qr = QuantumRegister(2, "qr") -# circuit = QuantumCircuit(qr) -# circuit.h(qr[0]) -# circuit.h(qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# -# basis_gates = ["u1", "u2", "u3", "cx", "id"] -# circuit2 = transpile(circuit, basis_gates=basis_gates, optimization_level=0) -# resources_after = circuit2.count_ops() -# self.assertEqual({"u2": 2, "cx": 4}, resources_after) -# -# def test_transpile_non_adjacent_layout(self): -# """Transpile pipeline can handle manual layout on non-adjacent qubits. -# -# circuit: -# -# .. parsed-literal:: -# -# ┌───┐ -# qr_0: ┤ H ├──■──────────── -> 1 -# └───┘┌─┴─┐ -# qr_1: ─────┤ X ├──■─────── -> 2 -# └───┘┌─┴─┐ -# qr_2: ──────────┤ X ├──■── -> 3 -# └───┘┌─┴─┐ -# qr_3: ───────────────┤ X ├ -> 5 -# └───┘ -# -# device: -# 0 - 1 - 2 - 3 - 4 - 5 - 6 -# -# | | | | | | -# -# 13 - 12 - 11 - 10 - 9 - 8 - 7 -# """ -# qr = QuantumRegister(4, "qr") -# circuit = QuantumCircuit(qr) -# circuit.h(qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[1], qr[2]) -# circuit.cx(qr[2], qr[3]) -# -# backend = FakeGeneric(num_qubits=6) -# initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] -# -# new_circuit = transpile( -# circuit, -# basis_gates=backend.operation_names, -# coupling_map=backend.coupling_map, -# initial_layout=initial_layout, -# ) -# -# qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} -# -# for instruction in new_circuit.data: -# if isinstance(instruction.operation, CXGate): -# self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) -# -# def test_transpile_qft_grid(self): -# """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" -# qr = QuantumRegister(8) -# circuit = QuantumCircuit(qr) -# for i, _ in enumerate(qr): -# for j in range(i): -# circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) -# circuit.h(qr[i]) -# -# backend = FakeGeneric(num_qubits=14) -# new_circuit = transpile( -# circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map -# ) -# -# qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} -# -# for instruction in new_circuit.data: -# if isinstance(instruction.operation, CXGate): -# self.assertIn((qubit_indices[x] for x in instruction.qubits), coupling_map) -# -# def test_already_mapped_1(self): -# """Circuit not remapped if matches topology. -# -# See: https://github.com/Qiskit/qiskit-terra/issues/342 -# """ -# backend = FakeGeneric( -# num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" -# ) -# coupling_map = backend.coupling_map -# basis_gates = backend.operation_names -# -# qr = QuantumRegister(19, "qr") -# cr = ClassicalRegister(19, "cr") -# qc = QuantumCircuit(qr, cr) -# qc.cx(qr[0], qr[9]) -# qc.cx(qr[1], qr[14]) -# qc.h(qr[3]) -# qc.cx(qr[10], qr[16]) -# qc.x(qr[11]) -# qc.cx(qr[5], qr[12]) -# qc.cx(qr[7], qr[18]) -# qc.cx(qr[6], qr[17]) -# qc.measure(qr, cr) -# -# new_qc = transpile( -# qc, -# coupling_map=coupling_map, -# basis_gates=basis_gates, -# initial_layout=Layout.generate_trivial_layout(qr), -# ) -# qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} -# cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] -# cx_qubits_physical = [ -# [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits -# ] -# self.assertEqual( -# sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] -# ) -# -# def test_already_mapped_via_layout(self): -# """Test that a manual layout that satisfies a coupling map does not get altered. -# -# See: https://github.com/Qiskit/qiskit-terra/issues/2036 -# -# circuit: -# -# .. parsed-literal:: -# -# ┌───┐ ┌───┐ ░ ┌─┐ -# qn_0: ┤ H ├──■────────────■──┤ H ├─░─┤M├─── -> 9 -# └───┘ │ │ └───┘ ░ └╥┘ -# qn_1: ───────┼────────────┼────────░──╫──── -> 6 -# │ │ ░ ║ -# qn_2: ───────┼────────────┼────────░──╫──── -> 5 -# │ │ ░ ║ -# qn_3: ───────┼────────────┼────────░──╫──── -> 0 -# │ │ ░ ║ -# qn_4: ───────┼────────────┼────────░──╫──── -> 1 -# ┌───┐┌─┴─┐┌──────┐┌─┴─┐┌───┐ ░ ║ ┌─┐ -# qn_5: ┤ H ├┤ X ├┤ P(2) ├┤ X ├┤ H ├─░──╫─┤M├ -> 4 -# └───┘└───┘└──────┘└───┘└───┘ ░ ║ └╥┘ -# cn: 2/════════════════════════════════╩══╩═ -# 0 1 -# -# device: -# 0 -- 1 -- 2 -- 3 -- 4 -# | | -# 5 -- 6 -- 7 -- 8 -- 9 -# | | -# 10 - 11 - 12 - 13 - 14 -# | | -# 15 - 16 - 17 - 18 - 19 -# """ -# basis_gates = ["u1", "u2", "u3", "cx", "id"] -# coupling_map = [ -# [0, 1], -# [0, 5], -# [1, 0], -# [1, 2], -# [2, 1], -# [2, 3], -# [3, 2], -# [3, 4], -# [4, 3], -# [4, 9], -# [5, 0], -# [5, 6], -# [5, 10], -# [6, 5], -# [6, 7], -# [7, 6], -# [7, 8], -# [7, 12], -# [8, 7], -# [8, 9], -# [9, 4], -# [9, 8], -# [9, 14], -# [10, 5], -# [10, 11], -# [10, 15], -# [11, 10], -# [11, 12], -# [12, 7], -# [12, 11], -# [12, 13], -# [13, 12], -# [13, 14], -# [14, 9], -# [14, 13], -# [14, 19], -# [15, 10], -# [15, 16], -# [16, 15], -# [16, 17], -# [17, 16], -# [17, 18], -# [18, 17], -# [18, 19], -# [19, 14], -# [19, 18], -# ] -# -# q = QuantumRegister(6, name="qn") -# c = ClassicalRegister(2, name="cn") -# qc = QuantumCircuit(q, c) -# qc.h(q[0]) -# qc.h(q[5]) -# qc.cx(q[0], q[5]) -# qc.p(2, q[5]) -# qc.cx(q[0], q[5]) -# qc.h(q[0]) -# qc.h(q[5]) -# qc.barrier(q) -# qc.measure(q[0], c[0]) -# qc.measure(q[5], c[1]) -# -# initial_layout = [ -# q[3], -# q[4], -# None, -# None, -# q[5], -# q[2], -# q[1], -# None, -# None, -# q[0], -# None, -# None, -# None, -# None, -# None, -# None, -# None, -# None, -# None, -# None, -# ] -# -# new_qc = transpile( -# qc, coupling_map=coupling_map, basis_gates=basis_gates, initial_layout=initial_layout -# ) -# qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} -# cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] -# cx_qubits_physical = [ -# [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits -# ] -# self.assertEqual(sorted(cx_qubits_physical), [[9, 4], [9, 4]]) -# -# def test_transpile_bell(self): -# """Test Transpile Bell. -# -# If all correct some should exists. -# """ -# backend = BasicAer.get_backend("qasm_simulator") -# -# qubit_reg = QuantumRegister(2, name="q") -# clbit_reg = ClassicalRegister(2, name="c") -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# qc.measure(qubit_reg, clbit_reg) -# -# circuits = transpile(qc, backend) -# self.assertIsInstance(circuits, QuantumCircuit) -# -# def test_transpile_one(self): -# """Test transpile a single circuit. -# -# Check that the top-level `transpile` function returns -# a single circuit.""" -# backend = BasicAer.get_backend("qasm_simulator") -# -# qubit_reg = QuantumRegister(2) -# clbit_reg = ClassicalRegister(2) -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# qc.measure(qubit_reg, clbit_reg) -# -# circuit = transpile(qc, backend) -# self.assertIsInstance(circuit, QuantumCircuit) -# -# def test_transpile_two(self): -# """Test transpile two circuits. -# -# Check that the transpiler returns a list of two circuits. -# """ -# backend = BasicAer.get_backend("qasm_simulator") -# -# qubit_reg = QuantumRegister(2) -# clbit_reg = ClassicalRegister(2) -# qubit_reg2 = QuantumRegister(2) -# clbit_reg2 = ClassicalRegister(2) -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# qc.measure(qubit_reg, clbit_reg) -# qc_extra = QuantumCircuit(qubit_reg, qubit_reg2, clbit_reg, clbit_reg2, name="extra") -# qc_extra.measure(qubit_reg, clbit_reg) -# circuits = transpile([qc, qc_extra], backend) -# self.assertIsInstance(circuits, list) -# self.assertEqual(len(circuits), 2) -# -# for circuit in circuits: -# self.assertIsInstance(circuit, QuantumCircuit) -# -# def test_transpile_singleton(self): -# """Test transpile a single-element list with a circuit. -# -# Check that `transpile` returns a single-element list. -# -# See https://github.com/Qiskit/qiskit-terra/issues/5260 -# """ -# backend = BasicAer.get_backend("qasm_simulator") -# -# qubit_reg = QuantumRegister(2) -# clbit_reg = ClassicalRegister(2) -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# qc.measure(qubit_reg, clbit_reg) -# -# circuits = transpile([qc], backend) -# self.assertIsInstance(circuits, list) -# self.assertEqual(len(circuits), 1) -# self.assertIsInstance(circuits[0], QuantumCircuit) -# -# def test_mapping_correction(self): -# """Test mapping works in previous failed case.""" -# backend = FakeGeneric(num_qubits=12) -# qr = QuantumRegister(name="qr", size=11) -# cr = ClassicalRegister(name="qc", size=11) -# circuit = QuantumCircuit(qr, cr) -# circuit.u(1.564784764685993, -1.2378965763410095, 2.9746763177861713, qr[3]) -# circuit.u(1.2269835563676523, 1.1932982847014162, -1.5597357740824318, qr[5]) -# circuit.cx(qr[5], qr[3]) -# circuit.p(0.856768317675967, qr[3]) -# circuit.u(-3.3911273825190915, 0.0, 0.0, qr[5]) -# circuit.cx(qr[3], qr[5]) -# circuit.u(2.159209321625547, 0.0, 0.0, qr[5]) -# circuit.cx(qr[5], qr[3]) -# circuit.u(0.30949966910232335, 1.1706201763833217, 1.738408691990081, qr[3]) -# circuit.u(1.9630571407274755, -0.6818742967975088, 1.8336534616728195, qr[5]) -# circuit.u(1.330181833806101, 0.6003162754946363, -3.181264980452862, qr[7]) -# circuit.u(0.4885914820775024, 3.133297443244865, -2.794457469189904, qr[8]) -# circuit.cx(qr[8], qr[7]) -# circuit.p(2.2196187596178616, qr[7]) -# circuit.u(-3.152367609631023, 0.0, 0.0, qr[8]) -# circuit.cx(qr[7], qr[8]) -# circuit.u(1.2646005789809263, 0.0, 0.0, qr[8]) -# circuit.cx(qr[8], qr[7]) -# circuit.u(0.7517780502091939, 1.2828514296564781, 1.6781179605443775, qr[7]) -# circuit.u(0.9267400575390405, 2.0526277839695153, 2.034202361069533, qr[8]) -# circuit.u(2.550304293455634, 3.8250017126569698, -2.1351609599720054, qr[1]) -# circuit.u(0.9566260876600556, -1.1147561503064538, 2.0571590492298797, qr[4]) -# circuit.cx(qr[4], qr[1]) -# circuit.p(2.1899329069137394, qr[1]) -# circuit.u(-1.8371715243173294, 0.0, 0.0, qr[4]) -# circuit.cx(qr[1], qr[4]) -# circuit.u(0.4717053496327104, 0.0, 0.0, qr[4]) -# circuit.cx(qr[4], qr[1]) -# circuit.u(2.3167620677708145, -1.2337330260253256, -0.5671322899563955, qr[1]) -# circuit.u(1.0468499525240678, 0.8680750644809365, -1.4083720073192485, qr[4]) -# circuit.u(2.4204244021892807, -2.211701932616922, 3.8297006565735883, qr[10]) -# circuit.u(0.36660280497727255, 3.273119149343493, -1.8003362351299388, qr[6]) -# circuit.cx(qr[6], qr[10]) -# circuit.p(1.067395863586385, qr[10]) -# circuit.u(-0.7044917541291232, 0.0, 0.0, qr[6]) -# circuit.cx(qr[10], qr[6]) -# circuit.u(2.1830003849921527, 0.0, 0.0, qr[6]) -# circuit.cx(qr[6], qr[10]) -# circuit.u(2.1538343756723917, 2.2653381826084606, -3.550087952059485, qr[10]) -# circuit.u(1.307627685019188, -0.44686656993522567, -2.3238098554327418, qr[6]) -# circuit.u(2.2046797998462906, 0.9732961754855436, 1.8527865921467421, qr[9]) -# circuit.u(2.1665254613904126, -1.281337664694577, -1.2424905413631209, qr[0]) -# circuit.cx(qr[0], qr[9]) -# circuit.p(2.6209599970201007, qr[9]) -# circuit.u(0.04680566321901303, 0.0, 0.0, qr[0]) -# circuit.cx(qr[9], qr[0]) -# circuit.u(1.7728411151289603, 0.0, 0.0, qr[0]) -# circuit.cx(qr[0], qr[9]) -# circuit.u(2.4866395967434443, 0.48684511243566697, -3.0069186877854728, qr[9]) -# circuit.u(1.7369112924273789, -4.239660866163805, 1.0623389015296005, qr[0]) -# circuit.barrier(qr) -# circuit.measure(qr, cr) -# -# circuits = transpile(circuit, backend) -# -# self.assertIsInstance(circuits, QuantumCircuit) -# -# def test_transpiler_layout_from_intlist(self): -# """A list of ints gives layout to correctly map circuit. -# virtual physical -# q1_0 - 4 ---[H]--- -# q2_0 - 5 -# q2_1 - 6 ---[H]--- -# q3_0 - 8 -# q3_1 - 9 -# q3_2 - 10 ---[H]--- -# -# """ -# qr1 = QuantumRegister(1, "qr1") -# qr2 = QuantumRegister(2, "qr2") -# qr3 = QuantumRegister(3, "qr3") -# qc = QuantumCircuit(qr1, qr2, qr3) -# qc.h(qr1[0]) -# qc.h(qr2[1]) -# qc.h(qr3[2]) -# layout = [4, 5, 6, 8, 9, 10] -# -# cmap = [ -# [1, 0], -# [1, 2], -# [2, 3], -# [4, 3], -# [4, 10], -# [5, 4], -# [5, 6], -# [5, 9], -# [6, 8], -# [7, 8], -# [9, 8], -# [9, 10], -# [11, 3], -# [11, 10], -# [11, 12], -# [12, 2], -# [13, 1], -# [13, 12], -# ] -# -# new_circ = transpile( -# qc, backend=None, coupling_map=cmap, basis_gates=["u2"], initial_layout=layout -# ) -# qubit_indices = {bit: idx for idx, bit in enumerate(new_circ.qubits)} -# mapped_qubits = [] -# -# for instruction in new_circ.data: -# mapped_qubits.append(qubit_indices[instruction.qubits[0]]) -# -# self.assertEqual(mapped_qubits, [4, 6, 10]) -# -# def test_mapping_multi_qreg(self): -# """Test mapping works for multiple qregs.""" -# backend = FakeGeneric(num_qubits=8) -# qr = QuantumRegister(3, name="qr") -# qr2 = QuantumRegister(1, name="qr2") -# qr3 = QuantumRegister(4, name="qr3") -# cr = ClassicalRegister(3, name="cr") -# qc = QuantumCircuit(qr, qr2, qr3, cr) -# qc.h(qr[0]) -# qc.cx(qr[0], qr2[0]) -# qc.cx(qr[1], qr3[2]) -# qc.measure(qr, cr) -# -# circuits = transpile(qc, backend) -# -# self.assertIsInstance(circuits, QuantumCircuit) -# -# def test_transpile_circuits_diff_registers(self): -# """Transpile list of circuits with different qreg names.""" -# backend = FakeGeneric(num_qubits=4) -# circuits = [] -# for _ in range(2): -# qr = QuantumRegister(2) -# cr = ClassicalRegister(2) -# circuit = QuantumCircuit(qr, cr) -# circuit.h(qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.measure(qr, cr) -# circuits.append(circuit) -# -# circuits = transpile(circuits, backend) -# self.assertIsInstance(circuits[0], QuantumCircuit) -# -# def test_wrong_initial_layout(self): -# """Test transpile with a bad initial layout.""" -# backend = FakeGeneric(num_qubits=4) -# -# qubit_reg = QuantumRegister(2, name="q") -# clbit_reg = ClassicalRegister(2, name="c") -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# qc.measure(qubit_reg, clbit_reg) -# -# bad_initial_layout = [ -# QuantumRegister(3, "q")[0], -# QuantumRegister(3, "q")[1], -# QuantumRegister(3, "q")[2], -# ] -# -# with self.assertRaises(TranspilerError): -# transpile(qc, backend, initial_layout=bad_initial_layout) -# -# def test_parameterized_circuit_for_simulator(self): -# """Verify that a parameterized circuit can be transpiled for a simulator backend.""" -# qr = QuantumRegister(2, name="qr") -# qc = QuantumCircuit(qr) -# -# theta = Parameter("theta") -# qc.rz(theta, qr[0]) -# -# transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) -# -# expected_qc = QuantumCircuit(qr) -# expected_qc.append(RZGate(theta), [qr[0]]) -# self.assertEqual(expected_qc, transpiled_qc) -# -# def test_parameterized_circuit_for_device(self): -# """Verify that a parameterized circuit can be transpiled for a device backend.""" -# qr = QuantumRegister(2, name="qr") -# qc = QuantumCircuit(qr) -# -# theta = Parameter("theta") -# qc.p(theta, qr[0]) -# backend = FakeGeneric(num_qubits=4) -# -# transpiled_qc = transpile( -# qc, -# backend=backend, -# initial_layout=Layout.generate_trivial_layout(qr), -# ) -# -# qr = QuantumRegister(backend.num_qubits, "q") -# expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) -# expected_qc.append(RZGate(theta), [qr[0]]) -# -# self.assertEqual(expected_qc, transpiled_qc) -# -# def test_parameter_expression_circuit_for_simulator(self): -# """Verify that a circuit including expressions of parameters can be -# transpiled for a simulator backend.""" -# qr = QuantumRegister(2, name="qr") -# qc = QuantumCircuit(qr) -# -# theta = Parameter("theta") -# square = theta * theta -# qc.rz(square, qr[0]) -# -# transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) -# -# expected_qc = QuantumCircuit(qr) -# expected_qc.append(RZGate(square), [qr[0]]) -# self.assertEqual(expected_qc, transpiled_qc) -# -# def test_parameter_expression_circuit_for_device(self): -# """Verify that a circuit including expressions of parameters can be -# transpiled for a device backend.""" -# qr = QuantumRegister(2, name="qr") -# qc = QuantumCircuit(qr) -# -# theta = Parameter("theta") -# square = theta * theta -# qc.rz(square, qr[0]) -# backend = FakeGeneric(num_qubits=4) -# -# transpiled_qc = transpile( -# qc, -# backend=backend, -# initial_layout=Layout.generate_trivial_layout(qr), -# ) -# -# qr = QuantumRegister(backend.num_qubits, "q") -# expected_qc = QuantumCircuit(qr) -# expected_qc.append(RZGate(square), [qr[0]]) -# self.assertEqual(expected_qc, transpiled_qc) -# -# def test_final_measurement_barrier_for_devices(self): -# """Verify BarrierBeforeFinalMeasurements pass is called in default pipeline for devices.""" -# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") -# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) -# layout = Layout.generate_trivial_layout(*circ.qregs) -# orig_pass = BarrierBeforeFinalMeasurements() -# with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: -# transpile( -# circ, -# coupling_map=FakeGeneric(num_qubits=6).coupling_map, -# initial_layout=layout, -# ) -# self.assertTrue(mock_pass.called) -# -# def test_do_not_run_gatedirection_with_symmetric_cm(self): -# """When the coupling map is symmetric, do not run GateDirection.""" -# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") -# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) -# layout = Layout.generate_trivial_layout(*circ.qregs) -# coupling_map = [] -# for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: -# coupling_map.append([node1, node2]) -# coupling_map.append([node2, node1]) -# -# orig_pass = GateDirection(CouplingMap(coupling_map)) -# with patch.object(GateDirection, "run", wraps=orig_pass.run) as mock_pass: -# transpile(circ, coupling_map=coupling_map, initial_layout=layout) -# self.assertFalse(mock_pass.called) -# -# def test_optimize_to_nothing(self): -# """Optimize gates up to fixed point in the default pipeline -# See https://github.com/Qiskit/qiskit-terra/issues/2035 -# """ -# # ┌───┐ ┌───┐┌───┐┌───┐ ┌───┐ -# # q0_0: ┤ H ├──■──┤ X ├┤ Y ├┤ Z ├──■──┤ H ├──■────■── -# # └───┘┌─┴─┐└───┘└───┘└───┘┌─┴─┐└───┘┌─┴─┐┌─┴─┐ -# # q0_1: ─────┤ X ├───────────────┤ X ├─────┤ X ├┤ X ├ -# # └───┘ └───┘ └───┘└───┘ -# qr = QuantumRegister(2) -# circ = QuantumCircuit(qr) -# circ.h(qr[0]) -# circ.cx(qr[0], qr[1]) -# circ.x(qr[0]) -# circ.y(qr[0]) -# circ.z(qr[0]) -# circ.cx(qr[0], qr[1]) -# circ.h(qr[0]) -# circ.cx(qr[0], qr[1]) -# circ.cx(qr[0], qr[1]) -# -# after = transpile(circ, coupling_map=[[0, 1], [1, 0]], basis_gates=["u3", "u2", "u1", "cx"]) -# -# expected = QuantumCircuit(QuantumRegister(2, "q"), global_phase=-np.pi / 2) -# msg = f"after:\n{after}\nexpected:\n{expected}" -# self.assertEqual(after, expected, msg=msg) -# -# def test_pass_manager_empty(self): -# """Test passing an empty PassManager() to the transpiler. -# -# It should perform no transformations on the circuit. -# """ -# qr = QuantumRegister(2) -# circuit = QuantumCircuit(qr) -# circuit.h(qr[0]) -# circuit.h(qr[0]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# circuit.cx(qr[0], qr[1]) -# resources_before = circuit.count_ops() -# -# pass_manager = PassManager() -# out_circuit = pass_manager.run(circuit) -# resources_after = out_circuit.count_ops() -# -# self.assertDictEqual(resources_before, resources_after) -# -# def test_move_measurements(self): -# """Measurements applied AFTER swap mapping.""" -# cmap = FakeGeneric(num_qubits=16).coupling_map -# qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") -# circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) -# -# lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] -# out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic") -# out_dag = circuit_to_dag(out) -# meas_nodes = out_dag.named_nodes("measure") -# for meas_node in meas_nodes: -# is_last_measure = all( -# isinstance(after_measure, DAGOutNode) -# for after_measure in out_dag.quantum_successors(meas_node) -# ) -# self.assertTrue(is_last_measure) -# -# @data(0, 1, 2, 3) -# def test_init_resets_kept_preset_passmanagers(self, optimization_level): -# """Test initial resets kept at all preset transpilation levels""" -# num_qubits = 5 -# qc = QuantumCircuit(num_qubits) -# qc.reset(range(num_qubits)) -# -# num_resets = transpile(qc, optimization_level=optimization_level).count_ops()["reset"] -# self.assertEqual(num_resets, num_qubits) -# -# @data(0, 1, 2, 3) -# def test_initialize_reset_is_not_removed(self, optimization_level): -# """The reset in front of initializer should NOT be removed at beginning""" -# qr = QuantumRegister(1, "qr") -# qc = QuantumCircuit(qr) -# qc.initialize([1.0 / math.sqrt(2), 1.0 / math.sqrt(2)], [qr[0]]) -# qc.initialize([1.0 / math.sqrt(2), -1.0 / math.sqrt(2)], [qr[0]]) -# -# after = transpile(qc, basis_gates=["reset", "u3"], optimization_level=optimization_level) -# self.assertEqual(after.count_ops()["reset"], 2, msg=f"{after}\n does not have 2 resets.") -# -# def test_initialize_FakeMelbourne(self): -# """Test that the zero-state resets are remove in a device not supporting them.""" -# desired_vector = [1 / math.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / math.sqrt(2)] -# qr = QuantumRegister(3, "qr") -# qc = QuantumCircuit(qr) -# qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) -# -# out = transpile(qc, backend=FakeGeneric(num_qubits=4)) -# out_dag = circuit_to_dag(out) -# reset_nodes = out_dag.named_nodes("reset") -# -# self.assertEqual(len(reset_nodes), 3) -# -# def test_non_standard_basis(self): -# """Test a transpilation with a non-standard basis""" -# qr1 = QuantumRegister(1, "q1") -# qr2 = QuantumRegister(2, "q2") -# qr3 = QuantumRegister(3, "q3") -# qc = QuantumCircuit(qr1, qr2, qr3) -# qc.h(qr1[0]) -# qc.h(qr2[1]) -# qc.h(qr3[2]) -# layout = [4, 5, 6, 8, 9, 10] -# -# cmap = [ -# [1, 0], -# [1, 2], -# [2, 3], -# [4, 3], -# [4, 10], -# [5, 4], -# [5, 6], -# [5, 9], -# [6, 8], -# [7, 8], -# [9, 8], -# [9, 10], -# [11, 3], -# [11, 10], -# [11, 12], -# [12, 2], -# [13, 1], -# [13, 12], -# ] -# -# circuit = transpile( -# qc, backend=None, coupling_map=cmap, basis_gates=["h"], initial_layout=layout -# ) -# -# dag_circuit = circuit_to_dag(circuit) -# resources_after = dag_circuit.count_ops() -# self.assertEqual({"h": 3}, resources_after) -# -# def test_hadamard_to_rot_gates(self): -# """Test a transpilation from H to Rx, Ry gates""" -# qr = QuantumRegister(1) -# qc = QuantumCircuit(qr) -# qc.h(0) -# -# expected = QuantumCircuit(qr, global_phase=np.pi / 2) -# expected.append(RYGate(theta=np.pi / 2), [0]) -# expected.append(RXGate(theta=np.pi), [0]) -# -# circuit = transpile(qc, basis_gates=["rx", "ry"], optimization_level=0) -# self.assertEqual(circuit, expected) -# -# def test_basis_subset(self): -# """Test a transpilation with a basis subset of the standard basis""" -# qr = QuantumRegister(1, "q1") -# qc = QuantumCircuit(qr) -# qc.h(qr[0]) -# qc.x(qr[0]) -# qc.t(qr[0]) -# -# layout = [4] -# -# cmap = [ -# [1, 0], -# [1, 2], -# [2, 3], -# [4, 3], -# [4, 10], -# [5, 4], -# [5, 6], -# [5, 9], -# [6, 8], -# [7, 8], -# [9, 8], -# [9, 10], -# [11, 3], -# [11, 10], -# [11, 12], -# [12, 2], -# [13, 1], -# [13, 12], -# ] -# -# circuit = transpile( -# qc, backend=None, coupling_map=cmap, basis_gates=["u3"], initial_layout=layout -# ) -# -# dag_circuit = circuit_to_dag(circuit) -# resources_after = dag_circuit.count_ops() -# self.assertEqual({"u3": 1}, resources_after) -# -# def test_check_circuit_width(self): -# """Verify transpilation of circuit with virtual qubits greater than -# physical qubits raises error""" -# cmap = [ -# [1, 0], -# [1, 2], -# [2, 3], -# [4, 3], -# [4, 10], -# [5, 4], -# [5, 6], -# [5, 9], -# [6, 8], -# [7, 8], -# [9, 8], -# [9, 10], -# [11, 3], -# [11, 10], -# [11, 12], -# [12, 2], -# [13, 1], -# [13, 12], -# ] -# -# qc = QuantumCircuit(15, 15) -# -# with self.assertRaises(TranspilerError): -# transpile(qc, coupling_map=cmap) -# -# @data(0, 1, 2, 3) -# def test_ccx_routing_method_none(self, optimization_level): -# """CCX without routing method.""" -# -# qc = QuantumCircuit(3) -# qc.cx(0, 1) -# qc.cx(1, 2) -# -# out = transpile( -# qc, -# routing_method="none", -# basis_gates=["u", "cx"], -# initial_layout=[0, 1, 2], -# seed_transpiler=0, -# coupling_map=[[0, 1], [1, 2]], -# optimization_level=optimization_level, -# ) -# -# self.assertTrue(Operator(qc).equiv(out)) -# -# @data(0, 1, 2, 3) -# def test_ccx_routing_method_none_failed(self, optimization_level): -# """CCX without routing method cannot be routed.""" -# -# qc = QuantumCircuit(3) -# qc.ccx(0, 1, 2) -# -# with self.assertRaises(TranspilerError): -# transpile( -# qc, -# routing_method="none", -# basis_gates=["u", "cx"], -# initial_layout=[0, 1, 2], -# seed_transpiler=0, -# coupling_map=[[0, 1], [1, 2]], -# optimization_level=optimization_level, -# ) -# -# @data(0, 1, 2, 3) -# def test_ms_unrolls_to_cx(self, optimization_level): -# """Verify a Rx,Ry,Rxx circuit transpile to a U3,CX target.""" -# -# qc = QuantumCircuit(2) -# qc.rx(math.pi / 2, 0) -# qc.ry(math.pi / 4, 1) -# qc.rxx(math.pi / 4, 0, 1) -# -# out = transpile(qc, basis_gates=["u3", "cx"], optimization_level=optimization_level) -# -# self.assertTrue(Operator(qc).equiv(out)) -# -# @data(0, 1, 2, 3) -# def test_ms_can_target_ms(self, optimization_level): -# """Verify a Rx,Ry,Rxx circuit can transpile to an Rx,Ry,Rxx target.""" -# -# qc = QuantumCircuit(2) -# qc.rx(math.pi / 2, 0) -# qc.ry(math.pi / 4, 1) -# qc.rxx(math.pi / 4, 0, 1) -# -# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) -# -# self.assertTrue(Operator(qc).equiv(out)) -# -# @data(0, 1, 2, 3) -# def test_cx_can_target_ms(self, optimization_level): -# """Verify a U3,CX circuit can transpiler to a Rx,Ry,Rxx target.""" -# -# qc = QuantumCircuit(2) -# qc.h(0) -# qc.cx(0, 1) -# qc.rz(math.pi / 4, [0, 1]) -# -# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) -# -# self.assertTrue(Operator(qc).equiv(out)) -# -# @data(0, 1, 2, 3) -# def test_measure_doesnt_unroll_ms(self, optimization_level): -# """Verify a measure doesn't cause an Rx,Ry,Rxx circuit to unroll to U3,CX.""" -# -# qc = QuantumCircuit(2, 2) -# qc.rx(math.pi / 2, 0) -# qc.ry(math.pi / 4, 1) -# qc.rxx(math.pi / 4, 0, 1) -# qc.measure([0, 1], [0, 1]) -# out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) -# -# self.assertEqual(qc, out) -# -# @data( -# ["cx", "u3"], -# ["cz", "u3"], -# ["cz", "rx", "rz"], -# ["rxx", "rx", "ry"], -# ["iswap", "rx", "rz"], -# ) -# def test_block_collection_runs_for_non_cx_bases(self, basis_gates): -# """Verify block collection is run when a single two qubit gate is in the basis.""" -# twoq_gate, *_ = basis_gates -# -# qc = QuantumCircuit(2) -# qc.cx(0, 1) -# qc.cx(1, 0) -# qc.cx(0, 1) -# qc.cx(0, 1) -# -# out = transpile(qc, basis_gates=basis_gates, optimization_level=3) -# -# self.assertLessEqual(out.count_ops()[twoq_gate], 2) -# -# @unpack -# @data( -# (["u3", "cx"], {"u3": 1, "cx": 1}), -# (["rx", "rz", "iswap"], {"rx": 6, "rz": 12, "iswap": 2}), -# (["rx", "ry", "rxx"], {"rx": 6, "ry": 5, "rxx": 1}), -# ) -# def test_block_collection_reduces_1q_gate(self, basis_gates, gate_counts): -# """For synthesis to non-U3 bases, verify we minimize 1q gates.""" -# qc = QuantumCircuit(2) -# qc.h(0) -# qc.cx(0, 1) -# -# out = transpile(qc, basis_gates=basis_gates, optimization_level=3) -# -# self.assertTrue(Operator(out).equiv(qc)) -# self.assertTrue(set(out.count_ops()).issubset(basis_gates)) -# for basis_gate in basis_gates: -# self.assertLessEqual(out.count_ops()[basis_gate], gate_counts[basis_gate]) -# -# @combine( -# optimization_level=[0, 1, 2, 3], -# basis_gates=[ -# ["u3", "cx"], -# ["rx", "rz", "iswap"], -# ["rx", "ry", "rxx"], -# ], -# ) -# def test_translation_method_synthesis(self, optimization_level, basis_gates): -# """Verify translation_method='synthesis' gets to the basis.""" -# qc = QuantumCircuit(2) -# qc.h(0) -# qc.cx(0, 1) -# -# out = transpile( -# qc, -# translation_method="synthesis", -# basis_gates=basis_gates, -# optimization_level=optimization_level, -# ) -# -# self.assertTrue(Operator(out).equiv(qc)) -# self.assertTrue(set(out.count_ops()).issubset(basis_gates)) -# -# def test_transpiled_custom_gates_calibration(self): -# """Test if transpiled calibrations is equal to custom gates circuit calibrations.""" -# custom_180 = Gate("mycustom", 1, [3.14]) -# custom_90 = Gate("mycustom", 1, [1.57]) -# -# circ = QuantumCircuit(2) -# circ.append(custom_180, [0]) -# circ.append(custom_90, [1]) -# -# with pulse.build() as q0_x180: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# with pulse.build() as q1_y90: -# pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) -# -# # Add calibration -# circ.add_calibration(custom_180, [0], q0_x180) -# circ.add_calibration(custom_90, [1], q1_y90) -# -# transpiled_circuit = transpile( -# circ, -# backend=FakeGeneric(num_qubits=4), -# layout_method="trivial", -# ) -# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) -# self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) -# self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) -# -# def test_transpiled_basis_gates_calibrations(self): -# """Test if the transpiled calibrations is equal to basis gates circuit calibrations.""" -# circ = QuantumCircuit(2) -# circ.h(0) -# -# with pulse.build() as q0_x180: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# -# # Add calibration -# circ.add_calibration("h", [0], q0_x180) -# -# transpiled_circuit = transpile( -# circ, -# backend=FakeGeneric(num_qubits=4), -# ) -# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) -# -# def test_transpile_calibrated_custom_gate_on_diff_qubit(self): -# """Test if the custom, non calibrated gate raises QiskitError.""" -# custom_180 = Gate("mycustom", 1, [3.14]) -# -# circ = QuantumCircuit(2) -# circ.append(custom_180, [0]) -# -# with pulse.build() as q0_x180: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# -# # Add calibration -# circ.add_calibration(custom_180, [1], q0_x180) -# -# with self.assertRaises(QiskitError): -# transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") -# -# def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): -# """Test if the non-basis gates are transpiled if they are on different qubit that -# is not calibrated.""" -# circ = QuantumCircuit(2) -# circ.h(0) -# circ.h(1) -# -# with pulse.build() as q0_x180: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# -# # Add calibration -# circ.add_calibration("h", [1], q0_x180) -# -# transpiled_circuit = transpile( -# circ, -# backend=FakeGeneric(num_qubits=4), -# ) -# self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) -# self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) -# -# def test_transpile_subset_of_calibrated_gates(self): -# """Test transpiling a circuit with both basis gate (not-calibrated) and -# a calibrated gate on different qubits.""" -# x_180 = Gate("mycustom", 1, [3.14]) -# -# circ = QuantumCircuit(2) -# circ.h(0) -# circ.append(x_180, [0]) -# circ.h(1) -# -# with pulse.build() as q0_x180: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# -# circ.add_calibration(x_180, [0], q0_x180) -# circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 -# -# transpiled_circ = transpile( -# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" -# ) -# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) -# -# def test_parameterized_calibrations_transpile(self): -# """Check that gates can be matched to their calibrations before and after parameter -# assignment.""" -# tau = Parameter("tau") -# circ = QuantumCircuit(3, 3) -# circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) -# -# def q0_rxt(tau): -# with pulse.build() as q0_rxt: -# pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) -# return q0_rxt -# -# circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) -# -# transpiled_circ = transpile( -# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" -# ) -# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) -# circ = circ.assign_parameters({tau: 1}) -# transpiled_circ = transpile( -# circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" -# ) -# self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) -# -# def test_inst_durations_from_calibrations(self): -# """Test that circuit calibrations can be used instead of explicitly -# supplying inst_durations. -# """ -# qc = QuantumCircuit(2) -# qc.append(Gate("custom", 1, []), [0]) -# -# with pulse.build() as cal: -# pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) -# qc.add_calibration("custom", [0], cal) -# -# out = transpile(qc, scheduling_method="alap") -# self.assertEqual(out.duration, cal.duration) -# -# @data(0, 1, 2, 3) -# def test_multiqubit_gates_calibrations(self, opt_level): -# """Test multiqubit gate > 2q with calibrations works -# -# Adapted from issue description in https://github.com/Qiskit/qiskit-terra/issues/6572 -# """ -# circ = QuantumCircuit(5) -# custom_gate = Gate("my_custom_gate", 5, []) -# circ.append(custom_gate, [0, 1, 2, 3, 4]) -# circ.measure_all() -# backend = FakeGeneric(num_qubits=6) -# -# with pulse.build(backend=backend, name="custom") as my_schedule: -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) -# ) -# pulse.play( -# pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) -# ) -# circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) -# trans_circ = transpile( -# circ, backend=backend, optimization_level=opt_level, layout_method="trivial" -# ) -# self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) -# -# @data(0, 1, 2, 3) -# def test_circuit_with_delay(self, optimization_level): -# """Verify a circuit with delay can transpile to a scheduled circuit.""" -# -# qc = QuantumCircuit(2) -# qc.h(0) -# qc.delay(500, 1) -# qc.cx(0, 1) -# -# out = transpile( -# qc, -# scheduling_method="alap", -# basis_gates=["h", "cx"], -# instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], -# optimization_level=optimization_level, -# ) -# -# self.assertEqual(out.duration, 1200) -# -# def test_delay_converts_to_dt(self): -# """Test that a delay instruction is converted to units of dt given a backend.""" -# qc = QuantumCircuit(2) -# qc.delay(1000, [0], unit="us") -# -# backend = FakeGeneric(num_qubits=4, dt=0.5e-6) -# out = transpile([qc, qc], backend) -# self.assertEqual(out[0].data[0].operation.unit, "dt") -# self.assertEqual(out[1].data[0].operation.unit, "dt") -# -# out = transpile(qc, dt=1e-9) -# self.assertEqual(out.data[0].operation.unit, "dt") -# -# def test_scheduling_backend_v2(self): -# """Test that scheduling method works with Backendv2.""" -# qc = QuantumCircuit(2) -# qc.h(0) -# qc.cx(0, 1) -# qc.measure_all() -# -# out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") -# self.assertIn("delay", out[0].count_ops()) -# self.assertIn("delay", out[1].count_ops()) -# -# @data(1, 2, 3) -# def test_no_infinite_loop(self, optimization_level): -# """Verify circuit cost always descends and optimization does not flip flop indefinitely.""" -# qc = QuantumCircuit(1) -# qc.ry(0.2, 0) -# -# out = transpile( -# qc, basis_gates=["id", "p", "sx", "cx"], optimization_level=optimization_level -# ) -# -# # Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and -# # a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi -# # for the second and third RZ gates in the U3 decomposition. -# expected = QuantumCircuit( -# 1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi -# ) -# expected.p(-np.pi, 0) -# expected.sx(0) -# expected.p(np.pi - 0.2, 0) -# expected.sx(0) -# -# error_message = ( -# f"\nOutput circuit:\n{out!s}\n{Operator(out).data}\n" -# f"Expected circuit:\n{expected!s}\n{Operator(expected).data}" -# ) -# self.assertEqual(out, expected, error_message) -# -# @data(0, 1, 2, 3) -# def test_transpile_preserves_circuit_metadata(self, optimization_level): -# """Verify that transpile preserves circuit metadata in the output.""" -# circuit = QuantumCircuit(2, metadata={"experiment_id": "1234", "execution_number": 4}) -# circuit.h(0) -# circuit.cx(0, 1) -# -# cmap = [ -# [1, 0], -# [1, 2], -# [2, 3], -# [4, 3], -# [4, 10], -# [5, 4], -# [5, 6], -# [5, 9], -# [6, 8], -# [7, 8], -# [9, 8], -# [9, 10], -# [11, 3], -# [11, 10], -# [11, 12], -# [12, 2], -# [13, 1], -# [13, 12], -# ] -# -# res = transpile( -# circuit, -# basis_gates=["id", "p", "sx", "cx"], -# coupling_map=cmap, -# optimization_level=optimization_level, -# ) -# self.assertEqual(circuit.metadata, res.metadata) -# -# @data(0, 1, 2, 3) -# def test_transpile_optional_registers(self, optimization_level): -# """Verify transpile accepts circuits without registers end-to-end.""" -# -# qubits = [Qubit() for _ in range(3)] -# clbits = [Clbit() for _ in range(3)] -# -# qc = QuantumCircuit(qubits, clbits) -# qc.h(0) -# qc.cx(0, 1) -# qc.cx(1, 2) -# -# qc.measure(qubits, clbits) -# backend = FakeGeneric(num_qubits=4) -# -# out = transpile(qc, backend=backend, optimization_level=optimization_level) -# -# self.assertEqual(len(out.qubits), backend.num_qubits) -# self.assertEqual(len(out.clbits), len(clbits)) -# -# @data(0, 1, 2, 3) -# def test_translate_ecr_basis(self, optimization_level): -# """Verify that rewriting in ECR basis is efficient.""" -# circuit = QuantumCircuit(2) -# circuit.append(random_unitary(4, seed=1), [0, 1]) -# circuit.barrier() -# circuit.cx(0, 1) -# circuit.barrier() -# circuit.swap(0, 1) -# circuit.barrier() -# circuit.iswap(0, 1) -# -# res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=optimization_level) -# self.assertEqual(res.count_ops()["ecr"], 9) -# self.assertTrue(Operator(res).equiv(circuit)) -# -# def test_optimize_ecr_basis(self): -# """Test highest optimization level can optimize over ECR.""" -# circuit = QuantumCircuit(2) -# circuit.swap(1, 0) -# circuit.iswap(0, 1) -# -# res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=3) -# self.assertEqual(res.count_ops()["ecr"], 1) -# self.assertTrue(Operator(res).equiv(circuit)) -# -# def test_approximation_degree_invalid(self): -# """Test invalid approximation degree raises.""" -# circuit = QuantumCircuit(2) -# circuit.swap(0, 1) -# with self.assertRaises(QiskitError): -# transpile(circuit, basis_gates=["u", "cz"], approximation_degree=1.1) -# -# def test_approximation_degree(self): -# """Test more approximation gives lower-cost circuit.""" -# circuit = QuantumCircuit(2) -# circuit.swap(0, 1) -# circuit.h(0) -# circ_10 = transpile( -# circuit, -# basis_gates=["u", "cx"], -# translation_method="synthesis", -# approximation_degree=0.1, -# ) -# circ_90 = transpile( -# circuit, -# basis_gates=["u", "cx"], -# translation_method="synthesis", -# approximation_degree=0.9, -# ) -# self.assertLess(circ_10.depth(), circ_90.depth()) -# -# @data(0, 1, 2, 3) -# def test_synthesis_translation_method_with_single_qubit_gates(self, optimization_level): -# """Test that synthesis basis translation works for solely 1q circuit""" -# qc = QuantumCircuit(3) -# qc.h(0) -# qc.h(1) -# qc.h(2) -# res = transpile( -# qc, -# basis_gates=["id", "rz", "x", "sx", "cx"], -# translation_method="synthesis", -# optimization_level=optimization_level, -# ) -# expected = QuantumCircuit(3, global_phase=3 * np.pi / 4) -# expected.rz(np.pi / 2, 0) -# expected.rz(np.pi / 2, 1) -# expected.rz(np.pi / 2, 2) -# expected.sx(0) -# expected.sx(1) -# expected.sx(2) -# expected.rz(np.pi / 2, 0) -# expected.rz(np.pi / 2, 1) -# expected.rz(np.pi / 2, 2) -# self.assertEqual(res, expected) -# -# @data(0, 1, 2, 3) -# def test_synthesis_translation_method_with_gates_outside_basis(self, optimization_level): -# """Test that synthesis translation works for circuits with single gates outside bassis""" -# qc = QuantumCircuit(2) -# qc.swap(0, 1) -# res = transpile( -# qc, -# basis_gates=["id", "rz", "x", "sx", "cx"], -# translation_method="synthesis", -# optimization_level=optimization_level, -# ) -# if optimization_level != 3: -# self.assertTrue(Operator(qc).equiv(res)) -# self.assertNotIn("swap", res.count_ops()) -# else: -# # Optimization level 3 eliminates the pointless swap -# self.assertEqual(res, QuantumCircuit(2)) -# -# @data(0, 1, 2, 3) -# def test_target_ideal_gates(self, opt_level): -# """Test that transpile() with a custom ideal sim target works.""" -# theta = Parameter("θ") -# phi = Parameter("ϕ") -# lam = Parameter("λ") -# target = Target(num_qubits=2) -# target.add_instruction(UGate(theta, phi, lam), {(0,): None, (1,): None}) -# target.add_instruction(CXGate(), {(0, 1): None}) -# target.add_instruction(Measure(), {(0,): None, (1,): None}) -# qubit_reg = QuantumRegister(2, name="q") -# clbit_reg = ClassicalRegister(2, name="c") -# qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") -# qc.h(qubit_reg[0]) -# qc.cx(qubit_reg[0], qubit_reg[1]) -# -# result = transpile(qc, target=target, optimization_level=opt_level) -# -# self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc)) -# -# @data(0, 1, 2, 3) -# def test_transpile_with_custom_control_flow_target(self, opt_level): -# """Test transpile() with a target and constrol flow ops.""" -# target = FakeGeneric(num_qubits=8, dynamic=True).target -# -# circuit = QuantumCircuit(6, 1) -# circuit.h(0) -# circuit.measure(0, 0) -# circuit.cx(0, 1) -# circuit.cz(0, 2) -# circuit.append(CustomCX(), [1, 2], []) -# with circuit.for_loop((1,)): -# circuit.cx(0, 1) -# circuit.cz(0, 2) -# circuit.append(CustomCX(), [1, 2], []) -# with circuit.if_test((circuit.clbits[0], True)) as else_: -# circuit.cx(0, 1) -# circuit.cz(0, 2) -# circuit.append(CustomCX(), [1, 2], []) -# with else_: -# circuit.cx(3, 4) -# circuit.cz(3, 5) -# circuit.append(CustomCX(), [4, 5], []) -# with circuit.while_loop((circuit.clbits[0], True)): -# circuit.cx(3, 4) -# circuit.cz(3, 5) -# circuit.append(CustomCX(), [4, 5], []) -# with circuit.switch(circuit.cregs[0]) as case_: -# with case_(0): -# circuit.cx(0, 1) -# circuit.cz(0, 2) -# circuit.append(CustomCX(), [1, 2], []) -# with case_(1): -# circuit.cx(1, 2) -# circuit.cz(1, 3) -# circuit.append(CustomCX(), [2, 3], []) -# transpiled = transpile( -# circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 -# ) -# # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; -# # here we're just checking that various passes do appear to have run. -# self.assertIsInstance(transpiled, QuantumCircuit) -# # Assert layout ran. -# self.assertIsNot(getattr(transpiled, "_layout", None), None) -# -# def _visit_block(circuit, qubit_mapping=None): -# for instruction in circuit: -# qargs = tuple(qubit_mapping[x] for x in instruction.qubits) -# self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) -# if isinstance(instruction.operation, ControlFlowOp): -# for block in instruction.operation.blocks: -# new_mapping = { -# inner: qubit_mapping[outer] -# for outer, inner in zip(instruction.qubits, block.qubits) -# } -# _visit_block(block, new_mapping) -# # Assert unrolling ran. -# self.assertNotIsInstance(instruction.operation, CustomCX) -# # Assert translation ran. -# self.assertNotIsInstance(instruction.operation, CZGate) -# -# # Assert routing ran. -# _visit_block( -# transpiled, -# qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, -# ) -# -# @data(1, 2, 3) -# def test_transpile_identity_circuit_no_target(self, opt_level): -# """Test circuit equivalent to identity is optimized away for all optimization levels >0. -# -# Reproduce taken from https://github.com/Qiskit/qiskit-terra/issues/9217 -# """ -# qr1 = QuantumRegister(3, "state") -# qr2 = QuantumRegister(2, "ancilla") -# cr = ClassicalRegister(2, "c") -# qc = QuantumCircuit(qr1, qr2, cr) -# qc.h(qr1[0]) -# qc.cx(qr1[0], qr1[1]) -# qc.cx(qr1[1], qr1[2]) -# qc.cx(qr1[1], qr1[2]) -# qc.cx(qr1[0], qr1[1]) -# qc.h(qr1[0]) -# -# empty_qc = QuantumCircuit(qr1, qr2, cr) -# result = transpile(qc, optimization_level=opt_level) -# self.assertEqual(empty_qc, result) -# -# @data(0, 1, 2, 3) -# def test_initial_layout_with_loose_qubits(self, opt_level): -# """Regression test of gh-10125.""" -# qc = QuantumCircuit([Qubit(), Qubit()]) -# qc.cx(0, 1) -# transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) -# self.assertIsNotNone(transpiled.layout) -# self.assertEqual( -# transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) -# ) -# -# @data(0, 1, 2, 3) -# def test_initial_layout_with_overlapping_qubits(self, opt_level): -# """Regression test of gh-10125.""" -# qr1 = QuantumRegister(2, "qr1") -# qr2 = QuantumRegister(bits=qr1[:]) -# qc = QuantumCircuit(qr1, qr2) -# qc.cx(0, 1) -# transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) -# self.assertIsNotNone(transpiled.layout) -# self.assertEqual( -# transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) -# ) -# -# @combine(opt_level=[0, 1, 2, 3], basis=[["rz", "x"], ["rx", "z"], ["rz", "y"], ["ry", "x"]]) -# def test_paulis_to_constrained_1q_basis(self, opt_level, basis): -# """Test that Pauli-gate circuits can be transpiled to constrained 1q bases that do not -# contain any root-Pauli gates.""" -# qc = QuantumCircuit(1) -# qc.x(0) -# qc.barrier() -# qc.y(0) -# qc.barrier() -# qc.z(0) -# transpiled = transpile(qc, basis_gates=basis, optimization_level=opt_level) -# self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys()) -# self.assertEqual(Operator(qc), Operator(transpiled)) -# +@ddt +class TestTranspile(QiskitTestCase): + """Test transpile function.""" + + def test_empty_transpilation(self): + """Test that transpiling an empty list is a no-op. Regression test of gh-7287.""" + self.assertEqual(transpile([]), []) + + def test_pass_manager_none(self): + """Test passing the default (None) pass manager to the transpiler. + + It should perform the default qiskit flow: + unroll, swap_mapper, cx_direction, cx_cancellation, optimize_1q_gates + and should be equivalent to using tools.compile + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) + + coupling_map = [[1, 0]] + basis_gates = ["u1", "u2", "u3", "cx", "id"] + + backend = BasicAer.get_backend("qasm_simulator") + circuit2 = transpile( + circuit, + backend=backend, + coupling_map=coupling_map, + basis_gates=basis_gates, + ) + + circuit3 = transpile( + circuit, backend=backend, coupling_map=coupling_map, basis_gates=basis_gates + ) + self.assertEqual(circuit2, circuit3) + + def test_transpile_basis_gates_no_backend_no_coupling_map(self): + """Verify transpile() works with no coupling_map or backend.""" + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + + basis_gates = ["u1", "u2", "u3", "cx", "id"] + circuit2 = transpile(circuit, basis_gates=basis_gates, optimization_level=0) + resources_after = circuit2.count_ops() + self.assertEqual({"u2": 2, "cx": 4}, resources_after) + + def test_transpile_non_adjacent_layout(self): + """Transpile pipeline can handle manual layout on non-adjacent qubits. + + circuit: + + .. parsed-literal:: + + ┌───┐ + qr_0: ┤ H ├──■──────────── -> 1 + └───┘┌─┴─┐ + qr_1: ─────┤ X ├──■─────── -> 2 + └───┘┌─┴─┐ + qr_2: ──────────┤ X ├──■── -> 3 + └───┘┌─┴─┐ + qr_3: ───────────────┤ X ├ -> 5 + └───┘ + + device: + 0 - 1 - 2 - 3 - 4 - 5 - 6 + + | | | | | | + + 13 - 12 - 11 - 10 - 9 - 8 - 7 + """ + qr = QuantumRegister(4, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[2]) + circuit.cx(qr[2], qr[3]) + + backend = FakeGeneric(num_qubits=6) + initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] + + new_circuit = transpile( + circuit, + basis_gates=backend.operation_names, + coupling_map=backend.coupling_map, + initial_layout=initial_layout, + ) + + qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} + + for instruction in new_circuit.data: + if isinstance(instruction.operation, CXGate): + self.assertIn((qubit_indices[x] for x in instruction.qubits), backend.coupling_map) + + def test_transpile_qft_grid(self): + """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" + qr = QuantumRegister(8) + circuit = QuantumCircuit(qr) + for i, _ in enumerate(qr): + for j in range(i): + circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) + circuit.h(qr[i]) + + backend = FakeGeneric(num_qubits=14) + new_circuit = transpile( + circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map + ) + + qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} + + for instruction in new_circuit.data: + if isinstance(instruction.operation, CXGate): + self.assertIn((qubit_indices[x] for x in instruction.qubits), backend.coupling_map) + + def test_already_mapped_1(self): + """Circuit not remapped if matches topology. + + See: https://github.com/Qiskit/qiskit-terra/issues/342 + """ + backend = FakeGeneric( + num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" + ) + coupling_map = backend.coupling_map + basis_gates = backend.operation_names + + qr = QuantumRegister(19, "qr") + cr = ClassicalRegister(19, "cr") + qc = QuantumCircuit(qr, cr) + qc.cx(qr[0], qr[9]) + qc.cx(qr[1], qr[14]) + qc.h(qr[3]) + qc.cx(qr[10], qr[16]) + qc.x(qr[11]) + qc.cx(qr[5], qr[12]) + qc.cx(qr[7], qr[18]) + qc.cx(qr[6], qr[17]) + qc.measure(qr, cr) + + new_qc = transpile( + qc, + coupling_map=coupling_map, + basis_gates=basis_gates, + initial_layout=Layout.generate_trivial_layout(qr), + ) + qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} + cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] + cx_qubits_physical = [ + [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits + ] + self.assertEqual( + sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] + ) + + def test_already_mapped_via_layout(self): + """Test that a manual layout that satisfies a coupling map does not get altered. + + See: https://github.com/Qiskit/qiskit-terra/issues/2036 + + circuit: + + .. parsed-literal:: + + ┌───┐ ┌───┐ ░ ┌─┐ + qn_0: ┤ H ├──■────────────■──┤ H ├─░─┤M├─── -> 9 + └───┘ │ │ └───┘ ░ └╥┘ + qn_1: ───────┼────────────┼────────░──╫──── -> 6 + │ │ ░ ║ + qn_2: ───────┼────────────┼────────░──╫──── -> 5 + │ │ ░ ║ + qn_3: ───────┼────────────┼────────░──╫──── -> 0 + │ │ ░ ║ + qn_4: ───────┼────────────┼────────░──╫──── -> 1 + ┌───┐┌─┴─┐┌──────┐┌─┴─┐┌───┐ ░ ║ ┌─┐ + qn_5: ┤ H ├┤ X ├┤ P(2) ├┤ X ├┤ H ├─░──╫─┤M├ -> 4 + └───┘└───┘└──────┘└───┘└───┘ ░ ║ └╥┘ + cn: 2/════════════════════════════════╩══╩═ + 0 1 + + device: + 0 -- 1 -- 2 -- 3 -- 4 + | | + 5 -- 6 -- 7 -- 8 -- 9 + | | + 10 - 11 - 12 - 13 - 14 + | | + 15 - 16 - 17 - 18 - 19 + """ + basis_gates = ["u1", "u2", "u3", "cx", "id"] + coupling_map = [ + [0, 1], + [0, 5], + [1, 0], + [1, 2], + [2, 1], + [2, 3], + [3, 2], + [3, 4], + [4, 3], + [4, 9], + [5, 0], + [5, 6], + [5, 10], + [6, 5], + [6, 7], + [7, 6], + [7, 8], + [7, 12], + [8, 7], + [8, 9], + [9, 4], + [9, 8], + [9, 14], + [10, 5], + [10, 11], + [10, 15], + [11, 10], + [11, 12], + [12, 7], + [12, 11], + [12, 13], + [13, 12], + [13, 14], + [14, 9], + [14, 13], + [14, 19], + [15, 10], + [15, 16], + [16, 15], + [16, 17], + [17, 16], + [17, 18], + [18, 17], + [18, 19], + [19, 14], + [19, 18], + ] + + q = QuantumRegister(6, name="qn") + c = ClassicalRegister(2, name="cn") + qc = QuantumCircuit(q, c) + qc.h(q[0]) + qc.h(q[5]) + qc.cx(q[0], q[5]) + qc.p(2, q[5]) + qc.cx(q[0], q[5]) + qc.h(q[0]) + qc.h(q[5]) + qc.barrier(q) + qc.measure(q[0], c[0]) + qc.measure(q[5], c[1]) + + initial_layout = [ + q[3], + q[4], + None, + None, + q[5], + q[2], + q[1], + None, + None, + q[0], + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ] + + new_qc = transpile( + qc, coupling_map=coupling_map, basis_gates=basis_gates, initial_layout=initial_layout + ) + qubit_indices = {bit: idx for idx, bit in enumerate(new_qc.qubits)} + cx_qubits = [instr.qubits for instr in new_qc.data if instr.operation.name == "cx"] + cx_qubits_physical = [ + [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits + ] + self.assertEqual(sorted(cx_qubits_physical), [[9, 4], [9, 4]]) + + def test_transpile_bell(self): + """Test Transpile Bell. + + If all correct some should exists. + """ + backend = BasicAer.get_backend("qasm_simulator") + + qubit_reg = QuantumRegister(2, name="q") + clbit_reg = ClassicalRegister(2, name="c") + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + qc.measure(qubit_reg, clbit_reg) + + circuits = transpile(qc, backend) + self.assertIsInstance(circuits, QuantumCircuit) + + def test_transpile_one(self): + """Test transpile a single circuit. + + Check that the top-level `transpile` function returns + a single circuit.""" + backend = BasicAer.get_backend("qasm_simulator") + + qubit_reg = QuantumRegister(2) + clbit_reg = ClassicalRegister(2) + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + qc.measure(qubit_reg, clbit_reg) + + circuit = transpile(qc, backend) + self.assertIsInstance(circuit, QuantumCircuit) + + def test_transpile_two(self): + """Test transpile two circuits. + + Check that the transpiler returns a list of two circuits. + """ + backend = BasicAer.get_backend("qasm_simulator") + + qubit_reg = QuantumRegister(2) + clbit_reg = ClassicalRegister(2) + qubit_reg2 = QuantumRegister(2) + clbit_reg2 = ClassicalRegister(2) + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + qc.measure(qubit_reg, clbit_reg) + qc_extra = QuantumCircuit(qubit_reg, qubit_reg2, clbit_reg, clbit_reg2, name="extra") + qc_extra.measure(qubit_reg, clbit_reg) + circuits = transpile([qc, qc_extra], backend) + self.assertIsInstance(circuits, list) + self.assertEqual(len(circuits), 2) + + for circuit in circuits: + self.assertIsInstance(circuit, QuantumCircuit) + + def test_transpile_singleton(self): + """Test transpile a single-element list with a circuit. + + Check that `transpile` returns a single-element list. + + See https://github.com/Qiskit/qiskit-terra/issues/5260 + """ + backend = BasicAer.get_backend("qasm_simulator") + + qubit_reg = QuantumRegister(2) + clbit_reg = ClassicalRegister(2) + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + qc.measure(qubit_reg, clbit_reg) + + circuits = transpile([qc], backend) + self.assertIsInstance(circuits, list) + self.assertEqual(len(circuits), 1) + self.assertIsInstance(circuits[0], QuantumCircuit) + + def test_mapping_correction(self): + """Test mapping works in previous failed case.""" + backend = FakeGeneric(num_qubits=12) + qr = QuantumRegister(name="qr", size=11) + cr = ClassicalRegister(name="qc", size=11) + circuit = QuantumCircuit(qr, cr) + circuit.u(1.564784764685993, -1.2378965763410095, 2.9746763177861713, qr[3]) + circuit.u(1.2269835563676523, 1.1932982847014162, -1.5597357740824318, qr[5]) + circuit.cx(qr[5], qr[3]) + circuit.p(0.856768317675967, qr[3]) + circuit.u(-3.3911273825190915, 0.0, 0.0, qr[5]) + circuit.cx(qr[3], qr[5]) + circuit.u(2.159209321625547, 0.0, 0.0, qr[5]) + circuit.cx(qr[5], qr[3]) + circuit.u(0.30949966910232335, 1.1706201763833217, 1.738408691990081, qr[3]) + circuit.u(1.9630571407274755, -0.6818742967975088, 1.8336534616728195, qr[5]) + circuit.u(1.330181833806101, 0.6003162754946363, -3.181264980452862, qr[7]) + circuit.u(0.4885914820775024, 3.133297443244865, -2.794457469189904, qr[8]) + circuit.cx(qr[8], qr[7]) + circuit.p(2.2196187596178616, qr[7]) + circuit.u(-3.152367609631023, 0.0, 0.0, qr[8]) + circuit.cx(qr[7], qr[8]) + circuit.u(1.2646005789809263, 0.0, 0.0, qr[8]) + circuit.cx(qr[8], qr[7]) + circuit.u(0.7517780502091939, 1.2828514296564781, 1.6781179605443775, qr[7]) + circuit.u(0.9267400575390405, 2.0526277839695153, 2.034202361069533, qr[8]) + circuit.u(2.550304293455634, 3.8250017126569698, -2.1351609599720054, qr[1]) + circuit.u(0.9566260876600556, -1.1147561503064538, 2.0571590492298797, qr[4]) + circuit.cx(qr[4], qr[1]) + circuit.p(2.1899329069137394, qr[1]) + circuit.u(-1.8371715243173294, 0.0, 0.0, qr[4]) + circuit.cx(qr[1], qr[4]) + circuit.u(0.4717053496327104, 0.0, 0.0, qr[4]) + circuit.cx(qr[4], qr[1]) + circuit.u(2.3167620677708145, -1.2337330260253256, -0.5671322899563955, qr[1]) + circuit.u(1.0468499525240678, 0.8680750644809365, -1.4083720073192485, qr[4]) + circuit.u(2.4204244021892807, -2.211701932616922, 3.8297006565735883, qr[10]) + circuit.u(0.36660280497727255, 3.273119149343493, -1.8003362351299388, qr[6]) + circuit.cx(qr[6], qr[10]) + circuit.p(1.067395863586385, qr[10]) + circuit.u(-0.7044917541291232, 0.0, 0.0, qr[6]) + circuit.cx(qr[10], qr[6]) + circuit.u(2.1830003849921527, 0.0, 0.0, qr[6]) + circuit.cx(qr[6], qr[10]) + circuit.u(2.1538343756723917, 2.2653381826084606, -3.550087952059485, qr[10]) + circuit.u(1.307627685019188, -0.44686656993522567, -2.3238098554327418, qr[6]) + circuit.u(2.2046797998462906, 0.9732961754855436, 1.8527865921467421, qr[9]) + circuit.u(2.1665254613904126, -1.281337664694577, -1.2424905413631209, qr[0]) + circuit.cx(qr[0], qr[9]) + circuit.p(2.6209599970201007, qr[9]) + circuit.u(0.04680566321901303, 0.0, 0.0, qr[0]) + circuit.cx(qr[9], qr[0]) + circuit.u(1.7728411151289603, 0.0, 0.0, qr[0]) + circuit.cx(qr[0], qr[9]) + circuit.u(2.4866395967434443, 0.48684511243566697, -3.0069186877854728, qr[9]) + circuit.u(1.7369112924273789, -4.239660866163805, 1.0623389015296005, qr[0]) + circuit.barrier(qr) + circuit.measure(qr, cr) + + circuits = transpile(circuit, backend) + + self.assertIsInstance(circuits, QuantumCircuit) + + def test_transpiler_layout_from_intlist(self): + """A list of ints gives layout to correctly map circuit. + virtual physical + q1_0 - 4 ---[H]--- + q2_0 - 5 + q2_1 - 6 ---[H]--- + q3_0 - 8 + q3_1 - 9 + q3_2 - 10 ---[H]--- + + """ + qr1 = QuantumRegister(1, "qr1") + qr2 = QuantumRegister(2, "qr2") + qr3 = QuantumRegister(3, "qr3") + qc = QuantumCircuit(qr1, qr2, qr3) + qc.h(qr1[0]) + qc.h(qr2[1]) + qc.h(qr3[2]) + layout = [4, 5, 6, 8, 9, 10] + + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + + new_circ = transpile( + qc, backend=None, coupling_map=cmap, basis_gates=["u2"], initial_layout=layout + ) + qubit_indices = {bit: idx for idx, bit in enumerate(new_circ.qubits)} + mapped_qubits = [] + + for instruction in new_circ.data: + mapped_qubits.append(qubit_indices[instruction.qubits[0]]) + + self.assertEqual(mapped_qubits, [4, 6, 10]) + + def test_mapping_multi_qreg(self): + """Test mapping works for multiple qregs.""" + backend = FakeGeneric(num_qubits=8) + qr = QuantumRegister(3, name="qr") + qr2 = QuantumRegister(1, name="qr2") + qr3 = QuantumRegister(4, name="qr3") + cr = ClassicalRegister(3, name="cr") + qc = QuantumCircuit(qr, qr2, qr3, cr) + qc.h(qr[0]) + qc.cx(qr[0], qr2[0]) + qc.cx(qr[1], qr3[2]) + qc.measure(qr, cr) + + circuits = transpile(qc, backend) + + self.assertIsInstance(circuits, QuantumCircuit) + + def test_transpile_circuits_diff_registers(self): + """Transpile list of circuits with different qreg names.""" + backend = FakeGeneric(num_qubits=4) + circuits = [] + for _ in range(2): + qr = QuantumRegister(2) + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.measure(qr, cr) + circuits.append(circuit) + + circuits = transpile(circuits, backend) + self.assertIsInstance(circuits[0], QuantumCircuit) + + def test_wrong_initial_layout(self): + """Test transpile with a bad initial layout.""" + backend = FakeGeneric(num_qubits=4) + + qubit_reg = QuantumRegister(2, name="q") + clbit_reg = ClassicalRegister(2, name="c") + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + qc.measure(qubit_reg, clbit_reg) + + bad_initial_layout = [ + QuantumRegister(3, "q")[0], + QuantumRegister(3, "q")[1], + QuantumRegister(3, "q")[2], + ] + + with self.assertRaises(TranspilerError): + transpile(qc, backend, initial_layout=bad_initial_layout) + + def test_parameterized_circuit_for_simulator(self): + """Verify that a parameterized circuit can be transpiled for a simulator backend.""" + qr = QuantumRegister(2, name="qr") + qc = QuantumCircuit(qr) + + theta = Parameter("theta") + qc.rz(theta, qr[0]) + + transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) + + expected_qc = QuantumCircuit(qr) + expected_qc.append(RZGate(theta), [qr[0]]) + self.assertEqual(expected_qc, transpiled_qc) + + def test_parameterized_circuit_for_device(self): + """Verify that a parameterized circuit can be transpiled for a device backend.""" + qr = QuantumRegister(2, name="qr") + qc = QuantumCircuit(qr) + + theta = Parameter("theta") + qc.p(theta, qr[0]) + backend = FakeGeneric(num_qubits=4) + + transpiled_qc = transpile( + qc, + backend=backend, + initial_layout=Layout.generate_trivial_layout(qr), + ) + + qr = QuantumRegister(backend.num_qubits, "q") + expected_qc = QuantumCircuit(qr, global_phase=theta / 2.0) + expected_qc.append(RZGate(theta), [qr[0]]) + + self.assertEqual(expected_qc, transpiled_qc) + + def test_parameter_expression_circuit_for_simulator(self): + """Verify that a circuit including expressions of parameters can be + transpiled for a simulator backend.""" + qr = QuantumRegister(2, name="qr") + qc = QuantumCircuit(qr) + + theta = Parameter("theta") + square = theta * theta + qc.rz(square, qr[0]) + + transpiled_qc = transpile(qc, backend=BasicAer.get_backend("qasm_simulator")) + + expected_qc = QuantumCircuit(qr) + expected_qc.append(RZGate(square), [qr[0]]) + self.assertEqual(expected_qc, transpiled_qc) + + def test_parameter_expression_circuit_for_device(self): + """Verify that a circuit including expressions of parameters can be + transpiled for a device backend.""" + qr = QuantumRegister(2, name="qr") + qc = QuantumCircuit(qr) + + theta = Parameter("theta") + square = theta * theta + qc.rz(square, qr[0]) + backend = FakeGeneric(num_qubits=4) + + transpiled_qc = transpile( + qc, + backend=backend, + initial_layout=Layout.generate_trivial_layout(qr), + ) + + qr = QuantumRegister(backend.num_qubits, "q") + expected_qc = QuantumCircuit(qr) + expected_qc.append(RZGate(square), [qr[0]]) + self.assertEqual(expected_qc, transpiled_qc) + + def test_final_measurement_barrier_for_devices(self): + """Verify BarrierBeforeFinalMeasurements pass is called in default pipeline for devices.""" + qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") + circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) + layout = Layout.generate_trivial_layout(*circ.qregs) + orig_pass = BarrierBeforeFinalMeasurements() + with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: + transpile( + circ, + coupling_map=FakeGeneric(num_qubits=6).coupling_map, + initial_layout=layout, + ) + self.assertTrue(mock_pass.called) + + def test_do_not_run_gatedirection_with_symmetric_cm(self): + """When the coupling map is symmetric, do not run GateDirection.""" + qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") + circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) + layout = Layout.generate_trivial_layout(*circ.qregs) + coupling_map = [] + for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: + coupling_map.append([node1, node2]) + coupling_map.append([node2, node1]) + + orig_pass = GateDirection(CouplingMap(coupling_map)) + with patch.object(GateDirection, "run", wraps=orig_pass.run) as mock_pass: + transpile(circ, coupling_map=coupling_map, initial_layout=layout) + self.assertFalse(mock_pass.called) + + def test_optimize_to_nothing(self): + """Optimize gates up to fixed point in the default pipeline + See https://github.com/Qiskit/qiskit-terra/issues/2035 + """ + # ┌───┐ ┌───┐┌───┐┌───┐ ┌───┐ + # q0_0: ┤ H ├──■──┤ X ├┤ Y ├┤ Z ├──■──┤ H ├──■────■── + # └───┘┌─┴─┐└───┘└───┘└───┘┌─┴─┐└───┘┌─┴─┐┌─┴─┐ + # q0_1: ─────┤ X ├───────────────┤ X ├─────┤ X ├┤ X ├ + # └───┘ └───┘ └───┘└───┘ + qr = QuantumRegister(2) + circ = QuantumCircuit(qr) + circ.h(qr[0]) + circ.cx(qr[0], qr[1]) + circ.x(qr[0]) + circ.y(qr[0]) + circ.z(qr[0]) + circ.cx(qr[0], qr[1]) + circ.h(qr[0]) + circ.cx(qr[0], qr[1]) + circ.cx(qr[0], qr[1]) + + after = transpile(circ, coupling_map=[[0, 1], [1, 0]], basis_gates=["u3", "u2", "u1", "cx"]) + + expected = QuantumCircuit(QuantumRegister(2, "q"), global_phase=-np.pi / 2) + msg = f"after:\n{after}\nexpected:\n{expected}" + self.assertEqual(after, expected, msg=msg) + + def test_pass_manager_empty(self): + """Test passing an empty PassManager() to the transpiler. + + It should perform no transformations on the circuit. + """ + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[1]) + resources_before = circuit.count_ops() + + pass_manager = PassManager() + out_circuit = pass_manager.run(circuit) + resources_after = out_circuit.count_ops() + + self.assertDictEqual(resources_before, resources_after) + + def test_move_measurements(self): + """Measurements applied AFTER swap mapping.""" + cmap = FakeGeneric(num_qubits=16).coupling_map + qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") + circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) + + lay = [0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6] + out = transpile(circ, initial_layout=lay, coupling_map=cmap, routing_method="stochastic") + out_dag = circuit_to_dag(out) + meas_nodes = out_dag.named_nodes("measure") + for meas_node in meas_nodes: + is_last_measure = all( + isinstance(after_measure, DAGOutNode) + for after_measure in out_dag.quantum_successors(meas_node) + ) + self.assertTrue(is_last_measure) + + @data(0, 1, 2, 3) + def test_init_resets_kept_preset_passmanagers(self, optimization_level): + """Test initial resets kept at all preset transpilation levels""" + num_qubits = 5 + qc = QuantumCircuit(num_qubits) + qc.reset(range(num_qubits)) + + num_resets = transpile(qc, optimization_level=optimization_level).count_ops()["reset"] + self.assertEqual(num_resets, num_qubits) + + @data(0, 1, 2, 3) + def test_initialize_reset_is_not_removed(self, optimization_level): + """The reset in front of initializer should NOT be removed at beginning""" + qr = QuantumRegister(1, "qr") + qc = QuantumCircuit(qr) + qc.initialize([1.0 / math.sqrt(2), 1.0 / math.sqrt(2)], [qr[0]]) + qc.initialize([1.0 / math.sqrt(2), -1.0 / math.sqrt(2)], [qr[0]]) + + after = transpile(qc, basis_gates=["reset", "u3"], optimization_level=optimization_level) + self.assertEqual(after.count_ops()["reset"], 2, msg=f"{after}\n does not have 2 resets.") + + def test_initialize_FakeMelbourne(self): + """Test that the zero-state resets are remove in a device not supporting them.""" + desired_vector = [1 / math.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / math.sqrt(2)] + qr = QuantumRegister(3, "qr") + qc = QuantumCircuit(qr) + qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) + + out = transpile(qc, backend=FakeGeneric(num_qubits=4)) + out_dag = circuit_to_dag(out) + reset_nodes = out_dag.named_nodes("reset") + + self.assertEqual(len(reset_nodes), 3) + + def test_non_standard_basis(self): + """Test a transpilation with a non-standard basis""" + qr1 = QuantumRegister(1, "q1") + qr2 = QuantumRegister(2, "q2") + qr3 = QuantumRegister(3, "q3") + qc = QuantumCircuit(qr1, qr2, qr3) + qc.h(qr1[0]) + qc.h(qr2[1]) + qc.h(qr3[2]) + layout = [4, 5, 6, 8, 9, 10] + + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + + circuit = transpile( + qc, backend=None, coupling_map=cmap, basis_gates=["h"], initial_layout=layout + ) + + dag_circuit = circuit_to_dag(circuit) + resources_after = dag_circuit.count_ops() + self.assertEqual({"h": 3}, resources_after) + + def test_hadamard_to_rot_gates(self): + """Test a transpilation from H to Rx, Ry gates""" + qr = QuantumRegister(1) + qc = QuantumCircuit(qr) + qc.h(0) + + expected = QuantumCircuit(qr, global_phase=np.pi / 2) + expected.append(RYGate(theta=np.pi / 2), [0]) + expected.append(RXGate(theta=np.pi), [0]) + + circuit = transpile(qc, basis_gates=["rx", "ry"], optimization_level=0) + self.assertEqual(circuit, expected) + + def test_basis_subset(self): + """Test a transpilation with a basis subset of the standard basis""" + qr = QuantumRegister(1, "q1") + qc = QuantumCircuit(qr) + qc.h(qr[0]) + qc.x(qr[0]) + qc.t(qr[0]) + + layout = [4] + + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + + circuit = transpile( + qc, backend=None, coupling_map=cmap, basis_gates=["u3"], initial_layout=layout + ) + + dag_circuit = circuit_to_dag(circuit) + resources_after = dag_circuit.count_ops() + self.assertEqual({"u3": 1}, resources_after) + + def test_check_circuit_width(self): + """Verify transpilation of circuit with virtual qubits greater than + physical qubits raises error""" + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + + qc = QuantumCircuit(15, 15) + + with self.assertRaises(TranspilerError): + transpile(qc, coupling_map=cmap) + + @data(0, 1, 2, 3) + def test_ccx_routing_method_none(self, optimization_level): + """CCX without routing method.""" + + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.cx(1, 2) + + out = transpile( + qc, + routing_method="none", + basis_gates=["u", "cx"], + initial_layout=[0, 1, 2], + seed_transpiler=0, + coupling_map=[[0, 1], [1, 2]], + optimization_level=optimization_level, + ) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_ccx_routing_method_none_failed(self, optimization_level): + """CCX without routing method cannot be routed.""" + + qc = QuantumCircuit(3) + qc.ccx(0, 1, 2) + + with self.assertRaises(TranspilerError): + transpile( + qc, + routing_method="none", + basis_gates=["u", "cx"], + initial_layout=[0, 1, 2], + seed_transpiler=0, + coupling_map=[[0, 1], [1, 2]], + optimization_level=optimization_level, + ) + + @data(0, 1, 2, 3) + def test_ms_unrolls_to_cx(self, optimization_level): + """Verify a Rx,Ry,Rxx circuit transpile to a U3,CX target.""" + + qc = QuantumCircuit(2) + qc.rx(math.pi / 2, 0) + qc.ry(math.pi / 4, 1) + qc.rxx(math.pi / 4, 0, 1) + + out = transpile(qc, basis_gates=["u3", "cx"], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_ms_can_target_ms(self, optimization_level): + """Verify a Rx,Ry,Rxx circuit can transpile to an Rx,Ry,Rxx target.""" + + qc = QuantumCircuit(2) + qc.rx(math.pi / 2, 0) + qc.ry(math.pi / 4, 1) + qc.rxx(math.pi / 4, 0, 1) + + out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_cx_can_target_ms(self, optimization_level): + """Verify a U3,CX circuit can transpiler to a Rx,Ry,Rxx target.""" + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.rz(math.pi / 4, [0, 1]) + + out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) + + self.assertTrue(Operator(qc).equiv(out)) + + @data(0, 1, 2, 3) + def test_measure_doesnt_unroll_ms(self, optimization_level): + """Verify a measure doesn't cause an Rx,Ry,Rxx circuit to unroll to U3,CX.""" + + qc = QuantumCircuit(2, 2) + qc.rx(math.pi / 2, 0) + qc.ry(math.pi / 4, 1) + qc.rxx(math.pi / 4, 0, 1) + qc.measure([0, 1], [0, 1]) + out = transpile(qc, basis_gates=["rx", "ry", "rxx"], optimization_level=optimization_level) + + self.assertEqual(qc, out) + + @data( + ["cx", "u3"], + ["cz", "u3"], + ["cz", "rx", "rz"], + ["rxx", "rx", "ry"], + ["iswap", "rx", "rz"], + ) + def test_block_collection_runs_for_non_cx_bases(self, basis_gates): + """Verify block collection is run when a single two qubit gate is in the basis.""" + twoq_gate, *_ = basis_gates + + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(1, 0) + qc.cx(0, 1) + qc.cx(0, 1) + + out = transpile(qc, basis_gates=basis_gates, optimization_level=3) + + self.assertLessEqual(out.count_ops()[twoq_gate], 2) + + @unpack + @data( + (["u3", "cx"], {"u3": 1, "cx": 1}), + (["rx", "rz", "iswap"], {"rx": 6, "rz": 12, "iswap": 2}), + (["rx", "ry", "rxx"], {"rx": 6, "ry": 5, "rxx": 1}), + ) + def test_block_collection_reduces_1q_gate(self, basis_gates, gate_counts): + """For synthesis to non-U3 bases, verify we minimize 1q gates.""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + + out = transpile(qc, basis_gates=basis_gates, optimization_level=3) + + self.assertTrue(Operator(out).equiv(qc)) + self.assertTrue(set(out.count_ops()).issubset(basis_gates)) + for basis_gate in basis_gates: + self.assertLessEqual(out.count_ops()[basis_gate], gate_counts[basis_gate]) + + @combine( + optimization_level=[0, 1, 2, 3], + basis_gates=[ + ["u3", "cx"], + ["rx", "rz", "iswap"], + ["rx", "ry", "rxx"], + ], + ) + def test_translation_method_synthesis(self, optimization_level, basis_gates): + """Verify translation_method='synthesis' gets to the basis.""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + + out = transpile( + qc, + translation_method="synthesis", + basis_gates=basis_gates, + optimization_level=optimization_level, + ) + + self.assertTrue(Operator(out).equiv(qc)) + self.assertTrue(set(out.count_ops()).issubset(basis_gates)) + + def test_transpiled_custom_gates_calibration(self): + """Test if transpiled calibrations is equal to custom gates circuit calibrations.""" + custom_180 = Gate("mycustom", 1, [3.14]) + custom_90 = Gate("mycustom", 1, [1.57]) + + circ = QuantumCircuit(2) + circ.append(custom_180, [0]) + circ.append(custom_90, [1]) + + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with pulse.build() as q1_y90: + pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) + + # Add calibration + circ.add_calibration(custom_180, [0], q0_x180) + circ.add_calibration(custom_90, [1], q1_y90) + + transpiled_circuit = transpile( + circ, + backend=FakeGeneric(num_qubits=4), + layout_method="trivial", + ) + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) + self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) + + def test_transpiled_basis_gates_calibrations(self): + """Test if the transpiled calibrations is equal to basis gates circuit calibrations.""" + circ = QuantumCircuit(2) + circ.h(0) + + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + + # Add calibration + circ.add_calibration("h", [0], q0_x180) + + transpiled_circuit = transpile( + circ, + backend=FakeGeneric(num_qubits=4), + ) + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + + def test_transpile_calibrated_custom_gate_on_diff_qubit(self): + """Test if the custom, non calibrated gate raises QiskitError.""" + custom_180 = Gate("mycustom", 1, [3.14]) + + circ = QuantumCircuit(2) + circ.append(custom_180, [0]) + + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + + # Add calibration + circ.add_calibration(custom_180, [1], q0_x180) + + with self.assertRaises(QiskitError): + transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") + + def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): + """Test if the non-basis gates are transpiled if they are on different qubit that + is not calibrated.""" + circ = QuantumCircuit(2) + circ.h(0) + circ.h(1) + + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + + # Add calibration + circ.add_calibration("h", [1], q0_x180) + + transpiled_circuit = transpile( + circ, + backend=FakeGeneric(num_qubits=4), + ) + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) + + def test_transpile_subset_of_calibrated_gates(self): + """Test transpiling a circuit with both basis gate (not-calibrated) and + a calibrated gate on different qubits.""" + x_180 = Gate("mycustom", 1, [3.14]) + + circ = QuantumCircuit(2) + circ.h(0) + circ.append(x_180, [0]) + circ.h(1) + + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + + circ.add_calibration(x_180, [0], q0_x180) + circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 + + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + ) + self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) + + def test_parameterized_calibrations_transpile(self): + """Check that gates can be matched to their calibrations before and after parameter + assignment.""" + tau = Parameter("tau") + circ = QuantumCircuit(3, 3) + circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) + + def q0_rxt(tau): + with pulse.build() as q0_rxt: + pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) + return q0_rxt + + circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) + + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + ) + self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) + circ = circ.assign_parameters({tau: 1}) + transpiled_circ = transpile( + circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + ) + self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) + + def test_inst_durations_from_calibrations(self): + """Test that circuit calibrations can be used instead of explicitly + supplying inst_durations. + """ + qc = QuantumCircuit(2) + qc.append(Gate("custom", 1, []), [0]) + + with pulse.build() as cal: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + qc.add_calibration("custom", [0], cal) + + out = transpile(qc, scheduling_method="alap") + self.assertEqual(out.duration, cal.duration) + + @data(0, 1, 2, 3) + def test_multiqubit_gates_calibrations(self, opt_level): + """Test multiqubit gate > 2q with calibrations works + + Adapted from issue description in https://github.com/Qiskit/qiskit-terra/issues/6572 + """ + circ = QuantumCircuit(5) + custom_gate = Gate("my_custom_gate", 5, []) + circ.append(custom_gate, [0, 1, 2, 3, 4]) + circ.measure_all() + backend = FakeGeneric(num_qubits=6) + + with pulse.build(backend=backend, name="custom") as my_schedule: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) + ) + circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) + trans_circ = transpile( + circ, backend=backend, optimization_level=opt_level, layout_method="trivial" + ) + self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) + + @data(0, 1, 2, 3) + def test_circuit_with_delay(self, optimization_level): + """Verify a circuit with delay can transpile to a scheduled circuit.""" + + qc = QuantumCircuit(2) + qc.h(0) + qc.delay(500, 1) + qc.cx(0, 1) + + out = transpile( + qc, + scheduling_method="alap", + basis_gates=["h", "cx"], + instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], + optimization_level=optimization_level, + ) + + self.assertEqual(out.duration, 1200) + + def test_delay_converts_to_dt(self): + """Test that a delay instruction is converted to units of dt given a backend.""" + qc = QuantumCircuit(2) + qc.delay(1000, [0], unit="us") + + backend = FakeGeneric(num_qubits=4, dt=0.5e-6) + out = transpile([qc, qc], backend) + self.assertEqual(out[0].data[0].operation.unit, "dt") + self.assertEqual(out[1].data[0].operation.unit, "dt") + + out = transpile(qc, dt=1e-9) + self.assertEqual(out.data[0].operation.unit, "dt") + + def test_scheduling_backend_v2(self): + """Test that scheduling method works with Backendv2.""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") + self.assertIn("delay", out[0].count_ops()) + self.assertIn("delay", out[1].count_ops()) + + @data(1, 2, 3) + def test_no_infinite_loop(self, optimization_level): + """Verify circuit cost always descends and optimization does not flip flop indefinitely.""" + qc = QuantumCircuit(1) + qc.ry(0.2, 0) + + out = transpile( + qc, basis_gates=["id", "p", "sx", "cx"], optimization_level=optimization_level + ) + + # Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and + # a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi + # for the second and third RZ gates in the U3 decomposition. + expected = QuantumCircuit( + 1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi + ) + expected.p(-np.pi, 0) + expected.sx(0) + expected.p(np.pi - 0.2, 0) + expected.sx(0) + + error_message = ( + f"\nOutput circuit:\n{out!s}\n{Operator(out).data}\n" + f"Expected circuit:\n{expected!s}\n{Operator(expected).data}" + ) + self.assertEqual(out, expected, error_message) + + @data(0, 1, 2, 3) + def test_transpile_preserves_circuit_metadata(self, optimization_level): + """Verify that transpile preserves circuit metadata in the output.""" + circuit = QuantumCircuit(2, metadata={"experiment_id": "1234", "execution_number": 4}) + circuit.h(0) + circuit.cx(0, 1) + + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + + res = transpile( + circuit, + basis_gates=["id", "p", "sx", "cx"], + coupling_map=cmap, + optimization_level=optimization_level, + ) + self.assertEqual(circuit.metadata, res.metadata) + + @data(0, 1, 2, 3) + def test_transpile_optional_registers(self, optimization_level): + """Verify transpile accepts circuits without registers end-to-end.""" + + qubits = [Qubit() for _ in range(3)] + clbits = [Clbit() for _ in range(3)] + + qc = QuantumCircuit(qubits, clbits) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + + qc.measure(qubits, clbits) + backend = FakeGeneric(num_qubits=4) + + out = transpile(qc, backend=backend, optimization_level=optimization_level) + + self.assertEqual(len(out.qubits), backend.num_qubits) + self.assertEqual(len(out.clbits), len(clbits)) + + @data(0, 1, 2, 3) + def test_translate_ecr_basis(self, optimization_level): + """Verify that rewriting in ECR basis is efficient.""" + circuit = QuantumCircuit(2) + circuit.append(random_unitary(4, seed=1), [0, 1]) + circuit.barrier() + circuit.cx(0, 1) + circuit.barrier() + circuit.swap(0, 1) + circuit.barrier() + circuit.iswap(0, 1) + + res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=optimization_level) + self.assertEqual(res.count_ops()["ecr"], 9) + self.assertTrue(Operator(res).equiv(circuit)) + + def test_optimize_ecr_basis(self): + """Test highest optimization level can optimize over ECR.""" + circuit = QuantumCircuit(2) + circuit.swap(1, 0) + circuit.iswap(0, 1) + + res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=3) + self.assertEqual(res.count_ops()["ecr"], 1) + self.assertTrue(Operator(res).equiv(circuit)) + + def test_approximation_degree_invalid(self): + """Test invalid approximation degree raises.""" + circuit = QuantumCircuit(2) + circuit.swap(0, 1) + with self.assertRaises(QiskitError): + transpile(circuit, basis_gates=["u", "cz"], approximation_degree=1.1) + + def test_approximation_degree(self): + """Test more approximation gives lower-cost circuit.""" + circuit = QuantumCircuit(2) + circuit.swap(0, 1) + circuit.h(0) + circ_10 = transpile( + circuit, + basis_gates=["u", "cx"], + translation_method="synthesis", + approximation_degree=0.1, + ) + circ_90 = transpile( + circuit, + basis_gates=["u", "cx"], + translation_method="synthesis", + approximation_degree=0.9, + ) + self.assertLess(circ_10.depth(), circ_90.depth()) + + @data(0, 1, 2, 3) + def test_synthesis_translation_method_with_single_qubit_gates(self, optimization_level): + """Test that synthesis basis translation works for solely 1q circuit""" + qc = QuantumCircuit(3) + qc.h(0) + qc.h(1) + qc.h(2) + res = transpile( + qc, + basis_gates=["id", "rz", "x", "sx", "cx"], + translation_method="synthesis", + optimization_level=optimization_level, + ) + expected = QuantumCircuit(3, global_phase=3 * np.pi / 4) + expected.rz(np.pi / 2, 0) + expected.rz(np.pi / 2, 1) + expected.rz(np.pi / 2, 2) + expected.sx(0) + expected.sx(1) + expected.sx(2) + expected.rz(np.pi / 2, 0) + expected.rz(np.pi / 2, 1) + expected.rz(np.pi / 2, 2) + self.assertEqual(res, expected) + + @data(0, 1, 2, 3) + def test_synthesis_translation_method_with_gates_outside_basis(self, optimization_level): + """Test that synthesis translation works for circuits with single gates outside bassis""" + qc = QuantumCircuit(2) + qc.swap(0, 1) + res = transpile( + qc, + basis_gates=["id", "rz", "x", "sx", "cx"], + translation_method="synthesis", + optimization_level=optimization_level, + ) + if optimization_level != 3: + self.assertTrue(Operator(qc).equiv(res)) + self.assertNotIn("swap", res.count_ops()) + else: + # Optimization level 3 eliminates the pointless swap + self.assertEqual(res, QuantumCircuit(2)) + + @data(0, 1, 2, 3) + def test_target_ideal_gates(self, opt_level): + """Test that transpile() with a custom ideal sim target works.""" + theta = Parameter("θ") + phi = Parameter("ϕ") + lam = Parameter("λ") + target = Target(num_qubits=2) + target.add_instruction(UGate(theta, phi, lam), {(0,): None, (1,): None}) + target.add_instruction(CXGate(), {(0, 1): None}) + target.add_instruction(Measure(), {(0,): None, (1,): None}) + qubit_reg = QuantumRegister(2, name="q") + clbit_reg = ClassicalRegister(2, name="c") + qc = QuantumCircuit(qubit_reg, clbit_reg, name="bell") + qc.h(qubit_reg[0]) + qc.cx(qubit_reg[0], qubit_reg[1]) + + result = transpile(qc, target=target, optimization_level=opt_level) + + self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc)) + + @data(0, 1, 2, 3) + def test_transpile_with_custom_control_flow_target(self, opt_level): + """Test transpile() with a target and constrol flow ops.""" + target = FakeGeneric(num_qubits=8, dynamic=True).target + + circuit = QuantumCircuit(6, 1) + circuit.h(0) + circuit.measure(0, 0) + circuit.cx(0, 1) + circuit.cz(0, 2) + circuit.append(CustomCX(), [1, 2], []) + with circuit.for_loop((1,)): + circuit.cx(0, 1) + circuit.cz(0, 2) + circuit.append(CustomCX(), [1, 2], []) + with circuit.if_test((circuit.clbits[0], True)) as else_: + circuit.cx(0, 1) + circuit.cz(0, 2) + circuit.append(CustomCX(), [1, 2], []) + with else_: + circuit.cx(3, 4) + circuit.cz(3, 5) + circuit.append(CustomCX(), [4, 5], []) + with circuit.while_loop((circuit.clbits[0], True)): + circuit.cx(3, 4) + circuit.cz(3, 5) + circuit.append(CustomCX(), [4, 5], []) + with circuit.switch(circuit.cregs[0]) as case_: + with case_(0): + circuit.cx(0, 1) + circuit.cz(0, 2) + circuit.append(CustomCX(), [1, 2], []) + with case_(1): + circuit.cx(1, 2) + circuit.cz(1, 3) + circuit.append(CustomCX(), [2, 3], []) + transpiled = transpile( + circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 + ) + # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; + # here we're just checking that various passes do appear to have run. + self.assertIsInstance(transpiled, QuantumCircuit) + # Assert layout ran. + self.assertIsNot(getattr(transpiled, "_layout", None), None) + + def _visit_block(circuit, qubit_mapping=None): + for instruction in circuit: + qargs = tuple(qubit_mapping[x] for x in instruction.qubits) + self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) + if isinstance(instruction.operation, ControlFlowOp): + for block in instruction.operation.blocks: + new_mapping = { + inner: qubit_mapping[outer] + for outer, inner in zip(instruction.qubits, block.qubits) + } + _visit_block(block, new_mapping) + # Assert unrolling ran. + self.assertNotIsInstance(instruction.operation, CustomCX) + # Assert translation ran. + self.assertNotIsInstance(instruction.operation, CZGate) + + # Assert routing ran. + _visit_block( + transpiled, + qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, + ) + + @data(1, 2, 3) + def test_transpile_identity_circuit_no_target(self, opt_level): + """Test circuit equivalent to identity is optimized away for all optimization levels >0. + + Reproduce taken from https://github.com/Qiskit/qiskit-terra/issues/9217 + """ + qr1 = QuantumRegister(3, "state") + qr2 = QuantumRegister(2, "ancilla") + cr = ClassicalRegister(2, "c") + qc = QuantumCircuit(qr1, qr2, cr) + qc.h(qr1[0]) + qc.cx(qr1[0], qr1[1]) + qc.cx(qr1[1], qr1[2]) + qc.cx(qr1[1], qr1[2]) + qc.cx(qr1[0], qr1[1]) + qc.h(qr1[0]) + + empty_qc = QuantumCircuit(qr1, qr2, cr) + result = transpile(qc, optimization_level=opt_level) + self.assertEqual(empty_qc, result) + + @data(0, 1, 2, 3) + def test_initial_layout_with_loose_qubits(self, opt_level): + """Regression test of gh-10125.""" + qc = QuantumCircuit([Qubit(), Qubit()]) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + + @data(0, 1, 2, 3) + def test_initial_layout_with_overlapping_qubits(self, opt_level): + """Regression test of gh-10125.""" + qr1 = QuantumRegister(2, "qr1") + qr2 = QuantumRegister(bits=qr1[:]) + qc = QuantumCircuit(qr1, qr2) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + + @combine(opt_level=[0, 1, 2, 3], basis=[["rz", "x"], ["rx", "z"], ["rz", "y"], ["ry", "x"]]) + def test_paulis_to_constrained_1q_basis(self, opt_level, basis): + """Test that Pauli-gate circuits can be transpiled to constrained 1q bases that do not + contain any root-Pauli gates.""" + qc = QuantumCircuit(1) + qc.x(0) + qc.barrier() + qc.y(0) + qc.barrier() + qc.z(0) + transpiled = transpile(qc, basis_gates=basis, optimization_level=opt_level) + self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys()) + self.assertEqual(Operator(qc), Operator(transpiled)) + @ddt class TestPostTranspileIntegration(QiskitTestCase): @@ -1762,192 +1762,192 @@ def _control_flow_expr_circuit(self): base.append(CustomCX(), [3, 4]) return base - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip(self, optimization_level): - # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit can be round-tripped through QPY.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be round-tripped - # through QPY.""" - # if optimization_level == 3 and sys.platform == "win32": - # self.skipTest( - # "This test case triggers a bug in the eigensolver routine on windows. " - # "See #10345 for more details." - # ) - # - # backend = FakeGeneric(num_qubits=8, dynamic=True) - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=backend, - # basis_gates=backend.operation_names, - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be round-tripped - # through QPY.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=8, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. - # transpiled._layout = None - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_expr(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - # be round-tripped through QPY.""" - # if optimization_level == 3 and sys.platform == "win32": - # self.skipTest( - # "This test case triggers a bug in the eigensolver routine on windows. " - # "See #10345 for more details." - # ) - # backend = FakeGeneric(num_qubits=16) - # transpiled = transpile( - # self._control_flow_expr_circuit(), - # backend=backend, - # basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow including `Expr` nodes can - # be round-tripped through QPY.""" - # backend = FakeGeneric(num_qubits=27) - # backend.target.add_instruction(IfElseOp, name="if_else") - # backend.target.add_instruction(ForLoopOp, name="for_loop") - # backend.target.add_instruction(WhileLoopOp, name="while_loop") - # backend.target.add_instruction(SwitchCaseOp, name="switch_case") - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=backend, - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # buffer = io.BytesIO() - # qpy.dump(transpiled, buffer) - # buffer.seek(0) - # round_tripped = qpy.load(buffer)[0] - # self.assertEqual(round_tripped, transpiled) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output(self, optimization_level): - # """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" - # transpiled = transpile( - # self._regular_circuit(), - # backend=FakeGeneric(num_qubits=8), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output_control_flow(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow can be dumped into - # OpenQASM 3.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=8, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2022_10_17, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance( - # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - # str, - # ) - # - # @data(0, 1, 2, 3) - # def test_qasm3_output_control_flow_expr(self, optimization_level): - # """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be - # dumped into OpenQASM 3.""" - # transpiled = transpile( - # self._control_flow_circuit(), - # backend=FakeGeneric(num_qubits=27, dynamic=True), - # optimization_level=optimization_level, - # seed_transpiler=2023_07_26, - # ) - # # TODO: There's not a huge amount we can sensibly test for the output here until we can - # # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump - # # itself doesn't throw an error, though. - # self.assertIsInstance( - # qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), - # str, - # ) - # - # @data(0, 1, 2, 3) - # def test_transpile_target_no_measurement_error(self, opt_level): - # """Test that transpile with a target which contains ideal measurement works - # - # Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 - # """ - # target = Target() - # target.add_instruction(Measure(), {(0,): None}) - # qc = QuantumCircuit(1, 1) - # qc.measure(0, 0) - # res = transpile(qc, target=target, optimization_level=opt_level) - # self.assertEqual(qc, res) + @data(0, 1, 2, 3) + def test_qpy_roundtrip(self, optimization_level): + """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit can be round-tripped through QPY.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be round-tripped + through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) + + backend = FakeGeneric(num_qubits=8, dynamic=True) + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + basis_gates=backend.operation_names, + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be round-tripped + through QPY.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=8, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # Round-tripping the layout is out-of-scope for QPY while it's a private attribute. + transpiled._layout = None + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) + backend = FakeGeneric(num_qubits=16) + transpiled = transpile( + self._control_flow_expr_circuit(), + backend=backend, + basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): + """Test that the output of a transpiled circuit with control flow including `Expr` nodes can + be round-tripped through QPY.""" + backend = FakeGeneric(num_qubits=27) + backend.target.add_instruction(IfElseOp, name="if_else") + backend.target.add_instruction(ForLoopOp, name="for_loop") + backend.target.add_instruction(WhileLoopOp, name="while_loop") + backend.target.add_instruction(SwitchCaseOp, name="switch_case") + transpiled = transpile( + self._control_flow_circuit(), + backend=backend, + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + buffer = io.BytesIO() + qpy.dump(transpiled, buffer) + buffer.seek(0) + round_tripped = qpy.load(buffer)[0] + self.assertEqual(round_tripped, transpiled) + + @data(0, 1, 2, 3) + def test_qasm3_output(self, optimization_level): + """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" + transpiled = transpile( + self._regular_circuit(), + backend=FakeGeneric(num_qubits=8), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) + + @data(0, 1, 2, 3) + def test_qasm3_output_control_flow(self, optimization_level): + """Test that the output of a transpiled circuit with control flow can be dumped into + OpenQASM 3.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=8, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2022_10_17, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance( + qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + str, + ) + + @data(0, 1, 2, 3) + def test_qasm3_output_control_flow_expr(self, optimization_level): + """Test that the output of a transpiled circuit with control flow and `Expr` nodes can be + dumped into OpenQASM 3.""" + transpiled = transpile( + self._control_flow_circuit(), + backend=FakeGeneric(num_qubits=27, dynamic=True), + optimization_level=optimization_level, + seed_transpiler=2023_07_26, + ) + # TODO: There's not a huge amount we can sensibly test for the output here until we can + # round-trip the OpenQASM 3 back into a Terra circuit. Mostly we're concerned that the dump + # itself doesn't throw an error, though. + self.assertIsInstance( + qasm3.dumps(transpiled, experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1).strip(), + str, + ) + + @data(0, 1, 2, 3) + def test_transpile_target_no_measurement_error(self, opt_level): + """Test that transpile with a target which contains ideal measurement works + + Reproduce from https://github.com/Qiskit/qiskit-terra/issues/8969 + """ + target = Target() + target.add_instruction(Measure(), {(0,): None}) + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + res = transpile(qc, target=target, optimization_level=opt_level) + self.assertEqual(qc, res) def test_transpile_final_layout_updated_with_post_layout(self): """Test that the final layout is correctly set when vf2postlayout runs. @@ -1981,6 +1981,7 @@ def callback(**kwargs): backend = FakeGeneric( num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], + seed=1234, ) qubits = 3 qc = QuantumCircuit(qubits) @@ -1989,8 +1990,7 @@ def callback(**kwargs): tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) self.assertTrue(vf2_post_layout_called) - print("AHA", _get_index_layout(tqc, qubits)) - self.assertEqual([1, 4, 3], _get_index_layout(tqc, qubits)) + self.assertEqual([0, 2, 1], _get_index_layout(tqc, qubits)) class StreamHandlerRaiseException(StreamHandler): @@ -2145,8 +2145,6 @@ def run(self, dag): # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) pm = PassManager(passes=[pass_]) - print(backend.target["sx"][(0,)]) - print(backend.target["sx"][(0,)]._calibration._definition) self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) qc = QuantumCircuit(1) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index e26b9a14e8d9..2a17d4b5c32a 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -16,12 +16,15 @@ class TestFakeGeneric(QiskitTestCase): + """Test class for FakeGeneric backend""" + def test_heavy_hex_num_qubits(self): - """Test if num_qubits=5 and coupling_map_type is heavy_hex the number of qubits generated is 19""" + """Test if num_qubits=5 and coupling_map_type + is heavy_hex the number of qubits generated is 19.""" self.assertEqual(FakeGeneric(num_qubits=5, coupling_map_type="heavy_hex").num_qubits, 19) def test_heavy_hex_coupling_map(self): - """Test if coupling_map of heavy_hex is generated right""" + """Test if coupling_map of heavy_hex is generated correctly.""" cp_mp = [ (0, 13), (1, 13), @@ -52,7 +55,7 @@ def test_heavy_hex_coupling_map(self): ) def test_grid_coupling_map(self): - """Test if grid coupling map is generated correct. + """Test if grid coupling map is generated correctly. In this test num_qubits=8, so a grid of 2x4 qubits need to be constructed""" cp_mp = [(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] self.assertEqual( @@ -61,7 +64,7 @@ def test_grid_coupling_map(self): ) def test_basis_gates(self): - """Test if the backend has a default basis gates, that includes delay and measure""" + """Test if the backend has default basis gates that include delay and measure""" self.assertEqual( FakeGeneric(num_qubits=8).operation_names, ["ecr", "id", "rz", "sx", "x", "delay", "measure", "reset"], From f2f3320bd2353f96378e1a3f3c5baf9f41da5603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 11 Oct 2023 12:08:32 +0200 Subject: [PATCH 18/56] Remove unused inputs --- .../providers/fake_provider/fake_generic.py | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index fca806749262..996dad16f225 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -89,9 +89,6 @@ class FakeGeneric(BackendV2): dt (float): The system time resolution of input signals in seconds. Default is 0.2222ns - skip_calibration_gates (list[str]): Optional list of gates where you do not wish to - append a calibration schedule. - seed (int): Optional seed for error and duration value generation. Returns: @@ -113,7 +110,6 @@ def __init__( replace_cx_with_ecr: bool = True, enable_reset: bool = True, dt: float = 0.222e-9, - skip_calibration_gates: list[str] = None, instruction_schedule_map: InstructionScheduleMap = None, seed: int = 42, ): @@ -148,7 +144,7 @@ def __init__( ) self._add_gate_instructions_to_target(dynamic, enable_reset) - self._add_calibration_defaults_to_target(instruction_schedule_map, skip_calibration_gates) + self._add_calibration_defaults_to_target(instruction_schedule_map) self._build_default_channels() @property @@ -510,14 +506,11 @@ def _get_default_instruction_dict(self) -> dict[str, tuple]: return instruction_dict def _add_calibration_defaults_to_target( - self, instruction_schedule_map: InstructionScheduleMap, skip_calibration_gates: bool + self, instruction_schedule_map: InstructionScheduleMap ) -> None: - if skip_calibration_gates is None: - skip_calibration_gates = [] - if not instruction_schedule_map: - defaults = self._build_calibration_defaults(skip_calibration_gates) + defaults = self._build_calibration_defaults() inst_map = defaults.instruction_schedule_map else: inst_map = instruction_schedule_map @@ -538,7 +531,7 @@ def _add_calibration_defaults_to_target( elif qargs in self._target[inst] and inst != "delay": self._target[inst][qargs].calibration = calibration_entry - def _build_calibration_defaults(self, skip_calibration_gates: list[str]) -> PulseDefaults: + def _build_calibration_defaults(self) -> PulseDefaults: """Build calibration defaults.""" measure_command_sequence = [ @@ -566,12 +559,10 @@ def _build_calibration_defaults(self, skip_calibration_gates: list[str]) -> Puls for gate in self._basis_gates: for i in range(self.num_qubits): - sequence = [] - if gate not in skip_calibration_gates: - sequence = [ - PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), - ] + sequence = [ + PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), + ] cmd_def.append( Command( name=gate, @@ -581,14 +572,12 @@ def _build_calibration_defaults(self, skip_calibration_gates: list[str]) -> Puls ) for qubit1, qubit2 in self.coupling_map: - sequence = [] - if gate not in skip_calibration_gates: - sequence = [ - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), - PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), - ] + sequence = [ + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), + PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), + ] if "cx" in self._basis_gates: cmd_def += [ Command( From 22de9372401ebc009b8d29ad33887134332d5c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 11 Oct 2023 14:07:42 +0200 Subject: [PATCH 19/56] Make calibrations optional --- .../providers/fake_provider/fake_generic.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 996dad16f225..0b9489eb77f9 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -78,7 +78,7 @@ class FakeGeneric(BackendV2): dynamic (bool): If True, enable dynamic circuits on this backend. Defaults to False. - bidirectional_cp_mp (bool): If True, enable bi-directional coupling map. + bidirectional_cmap (bool): If True, enable bi-directional coupling map. Defaults to False. replace_cx_with_ecr (bool): If True, replace every occurrence of 'cx' with 'ecr'. Defaults to True. @@ -86,6 +86,12 @@ class FakeGeneric(BackendV2): enable_reset (bool): If True, enable reset on the backend. Defaults to True. + add_calibration_entries (bool): If True, pulse calibration defaults will be added to + the target. Defaults to False. + + instruction_schedule_map (InstructionScheduleMap): An optional instruction schedule map + to replace the randomly generated pulse defaults. + dt (float): The system time resolution of input signals in seconds. Default is 0.2222ns @@ -109,8 +115,9 @@ def __init__( bidirectional_cmap: bool = False, replace_cx_with_ecr: bool = True, enable_reset: bool = True, - dt: float = 0.222e-9, + add_calibration_entries: bool = False, instruction_schedule_map: InstructionScheduleMap = None, + dt: float = 0.222e-9, seed: int = 42, ): @@ -144,8 +151,10 @@ def __init__( ) self._add_gate_instructions_to_target(dynamic, enable_reset) - self._add_calibration_defaults_to_target(instruction_schedule_map) - self._build_default_channels() + + if add_calibration_entries: + self._add_calibration_defaults_to_target(instruction_schedule_map) + self._build_default_channels() @property def target(self) -> Target: @@ -307,7 +316,7 @@ def run(self, run_input, **options): job = self.sim.run(circuits, **options) return job - def _setup_sim(self): + def _setup_sim(self) -> None: if _optionals.HAS_AER: from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel From 550234b86e66fd8318e1bbb979b982e2a41b9500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 11 Oct 2023 14:52:13 +0200 Subject: [PATCH 20/56] Attempt to speed up test: only add calibrations if option enabled --- .../providers/fake_provider/fake_generic.py | 83 +++++++++++-------- test/python/compiler/test_transpiler.py | 3 +- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 0b9489eb77f9..913b7c0d84bd 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -86,8 +86,10 @@ class FakeGeneric(BackendV2): enable_reset (bool): If True, enable reset on the backend. Defaults to True. - add_calibration_entries (bool): If True, pulse calibration defaults will be added to + add_cal_entries (bool): If True, pulse calibration defaults will be added to the target. Defaults to False. + empty_cal_gates (list[str): List of gates defined a strings which should have + calibration entries with an empty schedule. instruction_schedule_map (InstructionScheduleMap): An optional instruction schedule map to replace the randomly generated pulse defaults. @@ -108,14 +110,15 @@ def __init__( self, num_qubits: int = None, *, - coupling_map: list[tuple[int]] = None, + coupling_map: list[tuple[int]] | None = None, coupling_map_type: str = "grid", - basis_gates: list[str] = None, + basis_gates: list[str] | None = None, dynamic: bool = False, bidirectional_cmap: bool = False, replace_cx_with_ecr: bool = True, enable_reset: bool = True, - add_calibration_entries: bool = False, + add_cal_entries: bool = False, + empty_cal_gates: list[str] | None = None, instruction_schedule_map: InstructionScheduleMap = None, dt: float = 0.222e-9, seed: int = 42, @@ -152,8 +155,8 @@ def __init__( self._add_gate_instructions_to_target(dynamic, enable_reset) - if add_calibration_entries: - self._add_calibration_defaults_to_target(instruction_schedule_map) + if add_cal_entries: + self._add_calibration_defaults_to_target(instruction_schedule_map, empty_cal_gates) self._build_default_channels() @property @@ -515,11 +518,11 @@ def _get_default_instruction_dict(self) -> dict[str, tuple]: return instruction_dict def _add_calibration_defaults_to_target( - self, instruction_schedule_map: InstructionScheduleMap + self, instruction_schedule_map: InstructionScheduleMap, empty_cal_gates: list[str] | None ) -> None: if not instruction_schedule_map: - defaults = self._build_calibration_defaults() + defaults = self._build_calibration_defaults(empty_cal_gates) inst_map = defaults.instruction_schedule_map else: inst_map = instruction_schedule_map @@ -540,9 +543,12 @@ def _add_calibration_defaults_to_target( elif qargs in self._target[inst] and inst != "delay": self._target[inst][qargs].calibration = calibration_entry - def _build_calibration_defaults(self) -> PulseDefaults: + def _build_calibration_defaults(self, empty_cal_gates: list[str] | None) -> PulseDefaults: """Build calibration defaults.""" + if empty_cal_gates is None: + empty_cal_gates = [] + measure_command_sequence = [ PulseQobjInstruction( name="acquire", @@ -568,10 +574,12 @@ def _build_calibration_defaults(self) -> PulseDefaults: for gate in self._basis_gates: for i in range(self.num_qubits): - sequence = [ - PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), - ] + sequence = [] + if gate not in empty_cal_gates: + sequence = [ + PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), + ] cmd_def.append( Command( name=gate, @@ -580,29 +588,34 @@ def _build_calibration_defaults(self) -> PulseDefaults: ) ) - for qubit1, qubit2 in self.coupling_map: - sequence = [ - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), - PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), + for qubit1, qubit2 in self.coupling_map: + sequence = [ + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), + PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), + ] + + if "cx" in self._basis_gates: + if "cx" in empty_cal_gates: + sequence = [] + cmd_def += [ + Command( + name="cx", + qubits=[qubit1, qubit2], + sequence=sequence, + ) + ] + if "ecr" in self._basis_gates: + if "ecr" in empty_cal_gates: + sequence = [] + cmd_def += [ + Command( + name="ecr", + qubits=[qubit1, qubit2], + sequence=sequence, + ) ] - if "cx" in self._basis_gates: - cmd_def += [ - Command( - name="cx", - qubits=[qubit1, qubit2], - sequence=sequence, - ) - ] - if "ecr" in self._basis_gates: - cmd_def += [ - Command( - name="ecr", - qubits=[qubit1, qubit2], - sequence=sequence, - ) - ] qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 5e7dafdd6b11..4f246d3c4a04 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2140,7 +2140,8 @@ def run(self, dag): ) return dag - backend = FakeGeneric(num_qubits=4, skip_calibration_gates=["sx"]) + # Remove calibration for sx gate + backend = FakeGeneric(num_qubits=4, add_cal_entries=True, empty_cal_gates=["sx"]) # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) From 4fdf7802fa53a2241c54a49ea852a49717ff873d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 16 Oct 2023 10:14:08 +0200 Subject: [PATCH 21/56] Revert some tests to V1 --- test/python/compiler/test_transpiler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 4f246d3c4a04..81d603251940 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -60,6 +60,10 @@ from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 from qiskit.providers.fake_provider import FakeGeneric +from qiskit.providers.fake_provider import ( + FakeMelbourne, + FakeRueschlikon, +) from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap from qiskit.quantum_info import Operator, random_unitary @@ -1891,7 +1895,7 @@ def test_qasm3_output(self, optimization_level): """Test that the output of a transpiled circuit can be dumped into OpenQASM 3.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), + backend=FakeMelbourne(), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2102,7 +2106,7 @@ def test_parallel_with_target(self, opt_level): @data(0, 1, 2, 3) def test_parallel_dispatch(self, opt_level): """Test that transpile in parallel works for all optimization levels.""" - backend = FakeGeneric(num_qubits=16, replace_cx_with_ecr=False) + backend = FakeRueschlikon() qr = QuantumRegister(16) cr = ClassicalRegister(16) qc = QuantumCircuit(qr, cr) From 177953b9c74df1c6db3b133f61dcd0d953624e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 18 Oct 2023 18:06:08 +0200 Subject: [PATCH 22/56] Refactor FakeGeneric --- .../providers/fake_provider/fake_generic.py | 808 ++++++++---------- test/python/compiler/test_transpiler.py | 276 ++++-- 2 files changed, 570 insertions(+), 514 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 913b7c0d84bd..c1d93092146b 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -13,14 +13,13 @@ """Generic fake BackendV2 class""" from __future__ import annotations -import statistics import warnings from collections.abc import Iterable import numpy as np from qiskit import pulse -from qiskit.circuit import Measure, Parameter, Delay, Reset, QuantumCircuit +from qiskit.circuit import Measure, Parameter, Delay, Reset, QuantumCircuit, Instruction from qiskit.circuit.controlflow import ( IfElseOp, WhileLoopOp, @@ -39,219 +38,414 @@ PulseDefaults, Command, ) -from qiskit.pulse import ( - InstructionScheduleMap, - AcquireChannel, - ControlChannel, - DriveChannel, - MeasureChannel, -) from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem from qiskit.utils import optionals as _optionals -class FakeGeneric(BackendV2): +class GenericTarget(Target): """ - Configurable fake BackendV2 generator for unit testing purposes. This class will - generate a fake backend from a combination of random defaults - (with a fixable `seed`) and properties and configuration driven from a series of - optional input arguments. - - Arguments: - num_qubits (int): Minimum number of qubits in the fake backend. - The final number of qubits will be updated to be coherent - with the specified coupling map/ coupling map type. - - coupling_map (list[tuple[int]]): Backend's coupling map as a list of tuples. - Example: [(1, 2), (2, 3), (3, 4), (4, 5)]. If None is passed then the - coupling map will be generated randomly, in - accordance with the coupling_map_type argument. - - coupling_map_type (str): Type of coupling map to be generated. If coupling map - is passed, then this option will be overriden. Valid types of coupling - map: 'grid' (default), 'heavy_hex'. - - basis_gates (list[str]): Basis gates supported by the backend as list of strings. - If None is passed, this parameter will be set internally - to ['cx', 'id', 'rz', 'sx', 'x']. - - dynamic (bool): If True, enable dynamic circuits on this backend. - Defaults to False. - - bidirectional_cmap (bool): If True, enable bi-directional coupling map. - Defaults to False. - replace_cx_with_ecr (bool): If True, replace every occurrence of 'cx' with 'ecr'. - Defaults to True. - - enable_reset (bool): If True, enable reset on the backend. - Defaults to True. - - add_cal_entries (bool): If True, pulse calibration defaults will be added to - the target. Defaults to False. - empty_cal_gates (list[str): List of gates defined a strings which should have - calibration entries with an empty schedule. - - instruction_schedule_map (InstructionScheduleMap): An optional instruction schedule map - to replace the randomly generated pulse defaults. - - dt (float): The system time resolution of input signals in seconds. - Default is 0.2222ns - - seed (int): Optional seed for error and duration value generation. - - Returns: - None - - Raises: - QiskitError: If argument basis_gates has a gate which is not a valid basis gate. + This class will generate a :class:`~.Target` instance with + default qubit, instruction and calibration properties. """ def __init__( self, - num_qubits: int = None, - *, - coupling_map: list[tuple[int]] | None = None, - coupling_map_type: str = "grid", - basis_gates: list[str] | None = None, - dynamic: bool = False, - bidirectional_cmap: bool = False, - replace_cx_with_ecr: bool = True, - enable_reset: bool = True, - add_cal_entries: bool = False, - empty_cal_gates: list[str] | None = None, - instruction_schedule_map: InstructionScheduleMap = None, - dt: float = 0.222e-9, - seed: int = 42, + num_qubits: int, + basis_gates: list[str], + coupling_map: CouplingMap, + control_flow: bool = False, + calibrate_gates: list[str] | None = None, + rng: np.random.Generator = None, ): + """ + Args: + num_qubits (int): Number of qubits. - super().__init__( - provider=None, - name="fake_generic", - description=f"This is a fake device with at least {num_qubits} " - f"and generic settings. It has been generated right now!", - backend_version="", - ) - self.sim = None + basis_gates (list[str]): List of basis gate names to be supported by + the target. The currently supported instructions can be consulted via + the ``supported_instructions`` property. + Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` + and ``{"ecr", "id", "rz", "sx", "x"}``. - self._rng = np.random.default_rng(seed=seed) + coupling_map (CouplingMap): Target's coupling map as an instance of + :class:`~.CouplingMap`. + + control_flow (bool): Flag to enable control flow directives on the backend + (defaults to False). - self._set_basis_gates(basis_gates, replace_cx_with_ecr) - self._set_coupling_map(num_qubits, coupling_map, coupling_map_type, bidirectional_cmap) + calibrate_gates (list[str] | None): List of gate names which should contain + default calibration entries (overriden if an ``instruction_schedule_map`` is + provided). These must be a subset of ``basis_gates``. - self._target = Target( - description="Fake Generic Backend", - num_qubits=self._num_qubits, - dt=dt, + rng (np.random.Generator): Optional fixed-seed generator for default random values. + """ + self._rng = rng if rng else np.random.default_rng(seed=42) + self._num_qubits = num_qubits + self._coupling_map = coupling_map + + # hardcode default target attributes. To modify, + # access corresponding properties through the public Target API + super().__init__( + description="Fake Target", + num_qubits=num_qubits, + dt=0.222e-9, qubit_properties=[ QubitProperties( t1=self._rng.uniform(100e-6, 200e-6), t2=self._rng.uniform(100e-6, 200e-6), frequency=self._rng.uniform(5e9, 5.5e9), ) - for _ in range(self._num_qubits) + for _ in range(num_qubits) ], - concurrent_measurements=[list(range(self._num_qubits))], + concurrent_measurements=[list(range(num_qubits))], ) - self._add_gate_instructions_to_target(dynamic, enable_reset) + # ensure that reset, delay and measure are in basis_gates + self._basis_gates = set(basis_gates) + for name in {"reset", "delay", "measure"}: + self._basis_gates.add(name) - if add_cal_entries: - self._add_calibration_defaults_to_target(instruction_schedule_map, empty_cal_gates) - self._build_default_channels() + # iterate over gates, generate noise params from defaults + # and add instructions to target + for name in self._basis_gates: + if name not in self.supported_instructions: + raise QiskitError( + f"Provided base gate {name} is not a supported " + f"instruction ({self.supported_instructions})." + ) + gate = self.supported_instructions[name] + noise_params = self.noise_defaults[name] + self.add_noisy_instruction(gate, noise_params) + + if control_flow: + self.add_instruction(IfElseOp, name="if_else") + self.add_instruction(WhileLoopOp, name="while_loop") + self.add_instruction(ForLoopOp, name="for_loop") + self.add_instruction(SwitchCaseOp, name="switch_case") + self.add_instruction(BreakLoopOp, name="break") + self.add_instruction(ContinueLoopOp, name="continue") + + # generate block of calibration defaults and add to target + if calibrate_gates is not None: + defaults = self._generate_calibration_defaults(calibrate_gates) + self.add_calibration_defaults(defaults) @property - def target(self) -> Target: - """Return the target. + def supported_instructions(self) -> dict[str, Instruction]: + """Mapping of names to class instances for instructions supported + in ``basis_gates``. Returns: - Target: The backend's target. + Dictionary mapping instruction names to instruction instances """ - return self._target + return { + "cx": CXGate(), + "ecr": ECRGate(), + "id": IGate(), + "rz": RZGate(Parameter("theta")), + "sx": SXGate(), + "x": XGate(), + "measure": Measure(), + "delay": Delay(Parameter("Time")), + "reset": Reset(), + } @property - def coupling_map(self) -> CouplingMap: - """Return the coupling map. + def noise_defaults(self) -> dict[str, tuple | None]: + """Noise default values/ranges for duration and error of supported + instructions. There are three possible formats: + + #. (min_duration, max_duration, min_error, max_error), + if the defaults are ranges + #. (duration, error), if the defaults are fixed values + #. None Returns: - CouplingMap: The backend's coupling map. + Dictionary mapping instruction names to noise defaults """ - return self._coupling_map + return { + "cx": (1e-5, 5e-3, 1e-8, 9e-7), + "ecr": (1e-5, 5e-3, 1e-8, 9e-7), + "id": (0.0, 0.0), + "rz": (0.0, 0.0), + "sx": (1e-5, 5e-3, 1e-8, 9e-7), + "x": (1e-5, 5e-3, 1e-8, 9e-7), + "measure": (1e-5, 5e-3, 1e-8, 9e-7), + "delay": None, + "reset": None, + } - @property - def basis_gates(self) -> list[str]: - """Return the basis gates. + def add_noisy_instruction( + self, instruction: Instruction, noise_params: tuple[float, ...] | None + ) -> None: + """Add instruction properties to target for specified instruction. + + Args: + instruction (Instruction): Instance of instruction to be added to the target + noise_params (tuple[float, ...] | None): error and duration noise values/ranges to + include in instruction properties. Returns: - list[str]: The list of accepted basis gates + None """ - return self._basis_gates - @property - def max_circuits(self) -> None: - """Placeholder to be able to query max_circuits. Set to None. + qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) + props = {} - Returns: None - """ - return None + for qarg in qarg_set: + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + + duration, error = ( + (None, None) + if noise_params is None + else noise_params + if len(noise_params) == 2 + else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) + ) - @property - def meas_map(self) -> list[list[int]]: - """Return the measurement map in a V1-compatible way. + props.update({qargs: InstructionProperties(duration, error)}) + + self.add_instruction(instruction, props) + + def add_calibration_defaults(self, defaults: PulseDefaults) -> None: + """Add calibration entries from provided pulse defaults to target. + + Args: + defaults (PulseDefaults): pulse defaults with instruction schedule map Returns: - list[list[int]]: The list of concurrent measurements/measurement map + None """ - return self._target.concurrent_measurements + inst_map = defaults.instruction_schedule_map + for inst in inst_map.instructions: + for qarg in inst_map.qubits_with_instruction(inst): + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + # Do NOT call .get method. This parses Qpbj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = inst_map._get_calibration_entry(inst, qargs) + if inst in self._gate_map: + if inst == "measure": + for qubit in qargs: + self._gate_map[inst][(qubit,)].calibration = calibration_entry + elif qargs in self._gate_map[inst] and inst not in ["delay", "reset"]: + self._gate_map[inst][qargs].calibration = calibration_entry - def drive_channel(self, qubit: int) -> DriveChannel | None: - """Return the drive channel for the given qubit. + def _generate_calibration_defaults(self, calibrate_gates: list[str] | None) -> PulseDefaults: + """Generate calibration defaults for specified gates. + + Args: + calibrate_gates (list[str]): list of gates to be calibrated. Returns: - DriveChannel: The Qubit drive channel + Corresponding PulseDefaults """ + measure_command_sequence = [ + PulseQobjInstruction( + name="acquire", + duration=1792, + t0=0, + qubits=list(range(self.num_qubits)), + memory_slot=list(range(self.num_qubits)), + ) + ] + + measure_command_sequence += [ + PulseQobjInstruction(name="pulse_1", ch=f"m{i}", duration=1792, t0=0) + for i in range(self.num_qubits) + ] + + measure_command = Command( + name="measure", + qubits=list(range(self.num_qubits)), + sequence=measure_command_sequence, + ) + + cmd_def = [measure_command] + + for gate in self._basis_gates: + for i in range(self.num_qubits): + sequence = [] + if gate in calibrate_gates: + sequence = [ + PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), + ] + cmd_def.append( + Command( + name=gate, + qubits=[i], + sequence=sequence, + ) + ) + + for qubit1, qubit2 in self._coupling_map: + sequence = [ + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), + PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), + PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), + ] + + if "cx" in self._basis_gates: + if "cx" in calibrate_gates: + sequence = [] + cmd_def += [ + Command( + name="cx", + qubits=[qubit1, qubit2], + sequence=sequence, + ) + ] + if "ecr" in self._basis_gates: + if "ecr" in calibrate_gates: + sequence = [] + cmd_def += [ + Command( + name="ecr", + qubits=[qubit1, qubit2], + sequence=sequence, + ) + ] + + qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() + meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() + pulse_library = [ + PulseLibraryItem(name="pulse_1", samples=[[0.0, 0.0], [0.0, 0.1]]), + PulseLibraryItem(name="pulse_2", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]), + PulseLibraryItem( + name="pulse_3", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]] + ), + ] + + return PulseDefaults( + qubit_freq_est=qubit_freq_est, + meas_freq_est=meas_freq_est, + buffer=0, + pulse_library=pulse_library, + cmd_def=cmd_def, + ) + + +class FakeGeneric(BackendV2): + """ + Configurable fake :class:`~.BackendV2` generator. This class will + generate a fake backend from a combination of random defaults + (with a fixable ``seed``) driven from a series of optional input arguments. + """ + + def __init__( + self, + num_qubits: int, + basis_gates: list[str], + *, + coupling_map: list[list[int]] | CouplingMap | None = None, + control_flow: bool = False, + calibrate_gates: list[str] | None = None, + seed: int = 42, + ): + """ + Args: + basis_gates (list[str]): List of basis gate names to be supported by + the backend. The currently supported gate names are: + ``"cx"``, ``"ecr"``, ``"id"``, ``"rz"``, ``"sx"``, and ``"x"`` + Common sets of basis gates are ``["cx", "id", "rz", "sx", "x"]`` + and ``["ecr", "id", "rz", "sx", "x"]``. + + num_qubits (int): Number of qubits for the fake backend. + + coupling_map (list[list[int]] | CouplingMap | None): Optional coupling map + for the fake backend. Multiple formats are supported: + + #. :class:`~.CouplingMap` instance + #. List, must be given as an adjacency matrix, where each entry + specifies all directed two-qubit interactions supported by the backend, + e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` + + If ``coupling_map`` is specified, it must match the number of qubits + specified in ``num_qubits``. If ``coupling_map`` is not specified, + a fully connected coupling map will be generated with ``num_qubits`` + qubits. + + control_flow (bool): Flag to enable control flow directives on the backend + (defaults to False). + + calibrate_gates (list[str] | None): List of gate names which should contain + default calibration entries (overriden if an ``instruction_schedule_map`` is + provided). These must be a subset of ``basis_gates``. + + seed (int): Optional seed for generation of default values. + """ + + super().__init__( + provider=None, + name="fake_generic", + description=f"This is a fake device with {num_qubits} " f"and generic settings.", + backend_version="", + ) + self._rng = np.random.default_rng(seed=seed) + + # the coupling map is necessary to build the default channels + # (duplicating a bit of logic) + if coupling_map is None: + self._coupling_map = CouplingMap().from_full(num_qubits) + else: + if isinstance(coupling_map, CouplingMap): + self._coupling_map = coupling_map + else: + self._coupling_map = CouplingMap(coupling_map) + + self._target = GenericTarget( + num_qubits, + basis_gates, + self._coupling_map, + control_flow, + calibrate_gates, + self._rng, + ) + + self._build_default_channels() + self.sim = None + + @property + def target(self): + return self._target + + @property + def max_circuits(self): + return None + + @property + def meas_map(self) -> list[list[int]]: + return self._target.concurrent_measurements + + def drive_channel(self, qubit: int): drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) qubits = (qubit,) if qubits in drive_channels_map: return drive_channels_map[qubits][0] return None - def measure_channel(self, qubit: int) -> MeasureChannel | None: - """Return the measure stimulus channel for the given qubit. - - Returns: - MeasureChannel: The Qubit measurement stimulus line - """ + def measure_channel(self, qubit: int): measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) qubits = (qubit,) if qubits in measure_channels_map: return measure_channels_map[qubits][0] return None - def acquire_channel(self, qubit: int) -> AcquireChannel | None: - """Return the acquisition channel for the given qubit. - - Returns: - AcquireChannel: The Qubit measurement acquisition line. - """ + def acquire_channel(self, qubit: int): acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) qubits = (qubit,) if qubits in acquire_channels_map: return acquire_channels_map[qubits][0] return None - def control_channel(self, qubits: Iterable[int]) -> list[ControlChannel]: - """Return the secondary drive channel for the given qubit - - This is typically utilized for controlling multiqubit interactions. - This channel is derived from other channels. - - Args: - qubits: Tuple or list of qubits of the form - ``(control_qubit, target_qubit)``. - - Returns: - list[ControlChannel]: The multi qubit control line. - """ + def control_channel(self, qubits: Iterable[int]): control_channels_map = getattr(self, "channels_map", {}).get("control", {}) qubits = tuple(qubits) if qubits in control_channels_map: @@ -261,17 +455,16 @@ def control_channel(self, qubits: Iterable[int]) -> list[ControlChannel]: def run(self, run_input, **options): """Run on the fake backend using a simulator. - This method runs circuit jobs (an individual or a list of QuantumCircuit - ) and pulse jobs (an individual or a list of Schedule or ScheduleBlock) - using BasicAer or Aer simulator and returns a + This method runs circuit jobs (an individual or a list of :class:`~.QuantumCircuit` + ) and pulse jobs (an individual or a list of :class:`~.Schedule` or + :class:`~.ScheduleBlock`) using :class:`~.BasicAer` or Aer simulator and returns a :class:`~qiskit.providers.Job` object. - If qiskit-aer is installed, jobs will be run using AerSimulator with - noise model of the fake backend. Otherwise, jobs will be run using - BasicAer simulator without noise. + If qiskit-aer is installed, jobs will be run using the ``AerSimulator`` with + noise model of the fake backend. Otherwise, jobs will be run using the + ``BasicAer`` simulator without noise. - Currently noisy simulation of a pulse job is not supported yet in - FakeBackendV2. + Noisy simulations of pulse jobs are not yet supported in :class:`~.FakeGeneric`. Args: run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An @@ -281,7 +474,7 @@ def run(self, run_input, **options): :class:`~qiskit.pulse.Schedule` objects to run on the backend. options: Any kwarg options to pass to the backend for running the config. If a key is also present in the options - attribute/object then the expectation is that the value + attribute/object, then the expectation is that the value specified will be used instead of what's set in the options object. @@ -289,8 +482,9 @@ def run(self, run_input, **options): Job: The job object for the run Raises: - QiskitError: If a pulse job is supplied and qiskit-aer is not installed. + QiskitError: If a pulse job is supplied and qiskit_aer is not installed. """ + circuits = run_input pulse_job = None if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): @@ -320,6 +514,7 @@ def run(self, run_input, **options): return job def _setup_sim(self) -> None: + if _optionals.HAS_AER: from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel @@ -336,17 +531,7 @@ def _setup_sim(self) -> None: @classmethod def _default_options(cls) -> Options: - """Return the default options - - This method will return a :class:`qiskit.providers.Options` - subclass object that will be used for the default options. These - should be the default parameters to use for the options of the - backend. - Returns: - qiskit.providers.Options: An options object with - default values set - """ if _optionals.HAS_AER: from qiskit_aer import AerSimulator @@ -354,295 +539,14 @@ def _default_options(cls) -> Options: else: return BasicAer.get_backend("qasm_simulator")._default_options() - def _set_basis_gates(self, basis_gates: list[str], replace_cx_with_ecr: bool) -> None: - - default_gates = ["cx", "id", "rz", "sx", "x"] - - if basis_gates is not None: - self._basis_gates = basis_gates - else: - self._basis_gates = default_gates - - if replace_cx_with_ecr: - self._basis_gates = [gate.replace("cx", "ecr") for gate in self._basis_gates] - - if "delay" not in self._basis_gates: - self._basis_gates.append("delay") - if "measure" not in self._basis_gates: - self._basis_gates.append("measure") - - def _set_coupling_map( - self, - num_qubits: int | None, - coupling_map: list[tuple[int]], - coupling_map_type: str, - bidirectional_cmap: bool, - ) -> None: - - if not coupling_map: - if not num_qubits: - raise QiskitError( - "Please provide either ``num_qubits`` or ``coupling_map`` " - "to generate a new fake backend." - ) - - if coupling_map_type == "heavy_hex": - distance = self._get_cmap_args(coupling_map_type, num_qubits) - self._coupling_map = CouplingMap().from_heavy_hex( - distance=distance, bidirectional=bidirectional_cmap - ) - elif coupling_map_type == "grid": - num_rows, num_columns = self._get_cmap_args(coupling_map_type, num_qubits) - self._coupling_map = CouplingMap().from_grid( - num_rows=num_rows, num_columns=num_columns, bidirectional=bidirectional_cmap - ) - else: - raise QiskitError("Provided coupling map type not recognized") - else: - self._coupling_map = CouplingMap(coupling_map) - - self._num_qubits = self._coupling_map.size() - - def _get_cmap_args(self, coupling_map_type: str, num_qubits: int) -> int | tuple[int] | None: - if coupling_map_type == "heavy_hex": - for d in range(3, 20, 2): - # The description of the formula: 5*d**2 - 2*d -1 is explained in - # https://journals.aps.org/prx/pdf/10.1103/PhysRevX.10.011022 Page 011022-4 - n = (5 * (d**2) - (2 * d) - 1) / 2 - if n >= num_qubits: - return d - - elif coupling_map_type == "grid": - factors = [x for x in range(2, num_qubits + 1) if num_qubits % x == 0] - first_factor = statistics.median_high(factors) - second_factor = int(num_qubits / first_factor) - return (first_factor, second_factor) - - return None - - def _add_gate_instructions_to_target(self, dynamic: bool, enable_reset: bool) -> None: - - instruction_dict = self._get_default_instruction_dict() - - for gate in self._basis_gates: - try: - self._target.add_instruction(*instruction_dict[gate]) - except Exception as exc: - raise QiskitError(f"{gate} is not a valid basis gate") from exc - - if dynamic: - self._target.add_instruction(IfElseOp, name="if_else") - self._target.add_instruction(WhileLoopOp, name="while_loop") - self._target.add_instruction(ForLoopOp, name="for_loop") - self._target.add_instruction(SwitchCaseOp, name="switch_case") - self._target.add_instruction(BreakLoopOp, name="break") - self._target.add_instruction(ContinueLoopOp, name="continue") - - if enable_reset: - self._target.add_instruction( - Reset(), {(qubit_idx,): None for qubit_idx in range(self._num_qubits)} - ) - - def _get_default_instruction_dict(self) -> dict[str, tuple]: - - instruction_dict = { - "ecr": ( - ECRGate(), - { - edge: InstructionProperties( - error=self._rng.uniform(1e-5, 5e-3), - duration=self._rng.uniform(1e-8, 9e-7), - ) - for edge in self.coupling_map - }, - ), - "cx": ( - CXGate(), - { - edge: InstructionProperties( - error=self._rng.uniform(1e-3, 5e-2), - duration=self._rng.uniform(2e-7, 8e-7), - ) - for edge in self.coupling_map - }, - ), - "id": ( - IGate(), - { - (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(self._num_qubits) - }, - ), - "rz": ( - RZGate(Parameter("theta")), - { - (qubit_idx,): InstructionProperties(error=0.0, duration=0.0) - for qubit_idx in range(self._num_qubits) - }, - ), - "x": ( - XGate(), - { - (qubit_idx,): InstructionProperties( - error=self._rng.uniform(1e-6, 1e-4), - duration=self._rng.uniform(2e-8, 4e-8), - ) - for qubit_idx in range(self._num_qubits) - }, - ), - "sx": ( - SXGate(), - { - (qubit_idx,): InstructionProperties( - error=self._rng.uniform(1e-6, 1e-4), - duration=self._rng.uniform(1e-8, 2e-8), - ) - for qubit_idx in range(self._num_qubits) - }, - ), - "measure": ( - Measure(), - { - (qubit_idx,): InstructionProperties( - error=self._rng.uniform(1e-3, 1e-1), - duration=self._rng.uniform(1e-8, 9e-7), - ) - for qubit_idx in range(self._num_qubits) - }, - ), - "delay": ( - Delay(Parameter("Time")), - {(qubit_idx,): None for qubit_idx in range(self._num_qubits)}, - ), - } - return instruction_dict - - def _add_calibration_defaults_to_target( - self, instruction_schedule_map: InstructionScheduleMap, empty_cal_gates: list[str] | None - ) -> None: - - if not instruction_schedule_map: - defaults = self._build_calibration_defaults(empty_cal_gates) - inst_map = defaults.instruction_schedule_map - else: - inst_map = instruction_schedule_map - - for inst in inst_map.instructions: - for qarg in inst_map.qubits_with_instruction(inst): - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - # Do NOT call .get method. This parses Qpbj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = inst_map._get_calibration_entry(inst, qargs) - if inst in self._target: - if inst == "measure": - for qubit in qargs: - self._target[inst][(qubit,)].calibration = calibration_entry - elif qargs in self._target[inst] and inst != "delay": - self._target[inst][qargs].calibration = calibration_entry - - def _build_calibration_defaults(self, empty_cal_gates: list[str] | None) -> PulseDefaults: - """Build calibration defaults.""" - - if empty_cal_gates is None: - empty_cal_gates = [] - - measure_command_sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=list(range(self.num_qubits)), - memory_slot=list(range(self.num_qubits)), - ) - ] - - measure_command_sequence += [ - PulseQobjInstruction(name="pulse_1", ch=f"m{i}", duration=1792, t0=0) - for i in range(self.num_qubits) - ] - - measure_command = Command( - name="measure", - qubits=list(range(self.num_qubits)), - sequence=measure_command_sequence, - ) - - cmd_def = [measure_command] - - for gate in self._basis_gates: - for i in range(self.num_qubits): - sequence = [] - if gate not in empty_cal_gates: - sequence = [ - PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), - ] - cmd_def.append( - Command( - name=gate, - qubits=[i], - sequence=sequence, - ) - ) - - for qubit1, qubit2 in self.coupling_map: - sequence = [ - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), - PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), - ] - - if "cx" in self._basis_gates: - if "cx" in empty_cal_gates: - sequence = [] - cmd_def += [ - Command( - name="cx", - qubits=[qubit1, qubit2], - sequence=sequence, - ) - ] - if "ecr" in self._basis_gates: - if "ecr" in empty_cal_gates: - sequence = [] - cmd_def += [ - Command( - name="ecr", - qubits=[qubit1, qubit2], - sequence=sequence, - ) - ] - - qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() - meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() - pulse_library = [ - PulseLibraryItem(name="pulse_1", samples=[[0.0, 0.0], [0.0, 0.1]]), - PulseLibraryItem(name="pulse_2", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]), - PulseLibraryItem( - name="pulse_3", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]] - ), - ] - - return PulseDefaults( - qubit_freq_est=qubit_freq_est, - meas_freq_est=meas_freq_est, - buffer=0, - pulse_library=pulse_library, - cmd_def=cmd_def, - ) - def _build_default_channels(self) -> None: - """Create default channel map and set "channels_map" attribute""" + channels_map = { "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, "control": { - (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self.coupling_map) + (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) }, } setattr(self, "channels_map", channels_map) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index e73b94b32673..b73482ee75b3 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -188,6 +188,49 @@ def test_transpile_non_adjacent_layout(self): 13 - 12 - 11 - 10 - 9 - 8 - 7 """ + cmap = [ + [0, 1], + [0, 14], + [1, 0], + [1, 2], + [1, 13], + [2, 1], + [2, 3], + [2, 12], + [3, 2], + [3, 4], + [3, 11], + [4, 3], + [4, 5], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 5], + [6, 8], + [7, 8], + [8, 6], + [8, 7], + [8, 9], + [9, 5], + [9, 8], + [9, 10], + [10, 4], + [10, 9], + [10, 11], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [12, 11], + [12, 13], + [13, 1], + [13, 12], + [13, 14], + [14, 0], + [14, 13], + ] + qr = QuantumRegister(4, "qr") circuit = QuantumCircuit(qr) circuit.h(qr[0]) @@ -195,7 +238,9 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - backend = FakeGeneric(num_qubits=6) + backend = FakeGeneric( + num_qubits=15, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=cmap + ) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] new_circuit = transpile( @@ -209,10 +254,33 @@ def test_transpile_non_adjacent_layout(self): for instruction in new_circuit.data: if isinstance(instruction.operation, CXGate): - self.assertIn((qubit_indices[x] for x in instruction.qubits), backend.coupling_map) + self.assertIn([qubit_indices[x] for x in instruction.qubits], backend.coupling_map) def test_transpile_qft_grid(self): """Transpile pipeline can handle 8-qubit QFT on 14-qubit grid.""" + + coupling_map = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + basis_gates = ["cx", "id", "rz", "sx", "x"] + qr = QuantumRegister(8) circuit = QuantumCircuit(qr) for i, _ in enumerate(qr): @@ -220,39 +288,61 @@ def test_transpile_qft_grid(self): circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) circuit.h(qr[i]) - backend = FakeGeneric(num_qubits=14) - new_circuit = transpile( - circuit, basis_gates=backend.operation_names, coupling_map=backend.coupling_map - ) + new_circuit = transpile(circuit, basis_gates=basis_gates, coupling_map=coupling_map) qubit_indices = {bit: idx for idx, bit in enumerate(new_circuit.qubits)} for instruction in new_circuit.data: if isinstance(instruction.operation, CXGate): - self.assertIn((qubit_indices[x] for x in instruction.qubits), backend.coupling_map) + self.assertIn([qubit_indices[x] for x in instruction.qubits], coupling_map) def test_already_mapped_1(self): """Circuit not remapped if matches topology. See: https://github.com/Qiskit/qiskit-terra/issues/342 """ + cmap = [ + [1, 0], + [1, 2], + [2, 3], + [3, 4], + [3, 14], + [5, 4], + [6, 5], + [6, 7], + [6, 11], + [7, 10], + [8, 7], + [9, 8], + [9, 10], + [11, 10], + [12, 5], + [12, 11], + [12, 13], + [13, 4], + [13, 14], + [15, 0], + [15, 2], + [15, 14], + ] + backend = FakeGeneric( - num_qubits=19, replace_cx_with_ecr=False, coupling_map_type="heavy_hex" + num_qubits=16, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=cmap ) coupling_map = backend.coupling_map basis_gates = backend.operation_names - qr = QuantumRegister(19, "qr") - cr = ClassicalRegister(19, "cr") + qr = QuantumRegister(16, "qr") + cr = ClassicalRegister(16, "cr") qc = QuantumCircuit(qr, cr) - qc.cx(qr[0], qr[9]) - qc.cx(qr[1], qr[14]) - qc.h(qr[3]) - qc.cx(qr[10], qr[16]) + qc.cx(qr[3], qr[14]) + qc.cx(qr[5], qr[4]) + qc.h(qr[9]) + qc.cx(qr[9], qr[8]) qc.x(qr[11]) - qc.cx(qr[5], qr[12]) - qc.cx(qr[7], qr[18]) - qc.cx(qr[6], qr[17]) + qc.cx(qr[3], qr[4]) + qc.cx(qr[12], qr[11]) + qc.cx(qr[13], qr[4]) qc.measure(qr, cr) new_qc = transpile( @@ -267,7 +357,7 @@ def test_already_mapped_1(self): [qubit_indices[ctrl], qubit_indices[tgt]] for [ctrl, tgt] in cx_qubits ] self.assertEqual( - sorted(cx_qubits_physical), [[0, 9], [1, 14], [5, 12], [6, 17], [7, 18], [10, 16]] + sorted(cx_qubits_physical), [[3, 4], [3, 14], [5, 4], [9, 8], [12, 11], [13, 4]] ) def test_already_mapped_via_layout(self): @@ -504,7 +594,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = FakeGeneric(num_qubits=12) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=12) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -619,7 +709,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = FakeGeneric(num_qubits=8) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -636,7 +726,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -652,7 +742,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -691,7 +781,7 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) transpiled_qc = transpile( qc, @@ -730,7 +820,7 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) transpiled_qc = transpile( qc, @@ -749,10 +839,34 @@ def test_final_measurement_barrier_for_devices(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) orig_pass = BarrierBeforeFinalMeasurements() + coupling_map = [ + [1, 0], + [1, 2], + [2, 3], + [3, 4], + [3, 14], + [5, 4], + [6, 5], + [6, 7], + [6, 11], + [7, 10], + [8, 7], + [9, 8], + [9, 10], + [11, 10], + [12, 5], + [12, 11], + [12, 13], + [13, 4], + [13, 14], + [15, 0], + [15, 2], + [15, 14], + ] with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: transpile( circ, - coupling_map=FakeGeneric(num_qubits=6).coupling_map, + coupling_map=coupling_map, initial_layout=layout, ) self.assertTrue(mock_pass.called) @@ -763,7 +877,9 @@ def test_do_not_run_gatedirection_with_symmetric_cm(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) coupling_map = [] - for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: + for node1, node2 in FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16 + ).coupling_map: coupling_map.append([node1, node2]) coupling_map.append([node2, node1]) @@ -822,7 +938,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - cmap = FakeGeneric(num_qubits=16).coupling_map + cmap = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -865,7 +981,9 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile(qc, backend=FakeGeneric(num_qubits=4)) + out = transpile( + qc, backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + ) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1166,7 +1284,7 @@ def test_transpiled_custom_gates_calibration(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1186,7 +1304,7 @@ def test_transpiled_basis_gates_calibrations(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1204,7 +1322,11 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): circ.add_calibration(custom_180, [1], q0_x180) with self.assertRaises(QiskitError): - transpile(circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial") + transpile( + circ, + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + layout_method="trivial", + ) def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): """Test if the non-basis gates are transpiled if they are on different qubit that @@ -1221,7 +1343,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1243,7 +1365,9 @@ def test_transpile_subset_of_calibrated_gates(self): circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + circ, + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) @@ -1262,12 +1386,16 @@ def q0_rxt(tau): circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + circ, + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) transpiled_circ = transpile( - circ, backend=FakeGeneric(num_qubits=4), layout_method="trivial" + circ, + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) @@ -1295,23 +1423,23 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = FakeGeneric(num_qubits=6) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6) with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(1) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(2) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(2) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(3) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(3) ) pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(4) + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(4) ) pulse.play( pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) @@ -1355,7 +1483,8 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = FakeGeneric(num_qubits=4, dt=0.5e-6) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend.target.dt = 0.5e-6 out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") self.assertEqual(out[1].data[0].operation.unit, "dt") @@ -1370,7 +1499,11 @@ def test_scheduling_backend_v2(self): qc.cx(0, 1) qc.measure_all() - out = transpile([qc, qc], backend=FakeGeneric(num_qubits=4), scheduling_method="alap") + out = transpile( + [qc, qc], + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + scheduling_method="alap", + ) self.assertIn("delay", out[0].count_ops()) self.assertIn("delay", out[1].count_ops()) @@ -1450,7 +1583,7 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) - backend = FakeGeneric(num_qubits=4) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) out = transpile(qc, backend=backend, optimization_level=optimization_level) @@ -1575,7 +1708,9 @@ def test_target_ideal_gates(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" - target = FakeGeneric(num_qubits=8, dynamic=True).target + target = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True + ).target circuit = QuantumCircuit(6, 1) circuit.h(0) @@ -1834,7 +1969,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1851,7 +1986,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1875,7 +2010,9 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): "See #10345 for more details." ) - backend = FakeGeneric(num_qubits=8, dynamic=True) + backend = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True + ) transpiled = transpile( self._control_flow_circuit(), backend=backend, @@ -1897,7 +2034,9 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): through QPY.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), + backend=FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True + ), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1918,11 +2057,12 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): "This test case triggers a bug in the eigensolver routine on windows. " "See #10345 for more details." ) - backend = FakeGeneric(num_qubits=16) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16) transpiled = transpile( self._control_flow_expr_circuit(), backend=backend, - basis_gates=backend.basis_gates + ["if_else", "for_loop", "while_loop", "switch_case"], + basis_gates=backend.operation_names + + ["if_else", "for_loop", "while_loop", "switch_case"], optimization_level=optimization_level, seed_transpiler=2023_07_26, ) @@ -1936,7 +2076,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow including `Expr` nodes can be round-tripped through QPY.""" - backend = FakeGeneric(num_qubits=27) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27) backend.target.add_instruction(IfElseOp, name="if_else") backend.target.add_instruction(ForLoopOp, name="for_loop") backend.target.add_instruction(WhileLoopOp, name="while_loop") @@ -1973,7 +2113,9 @@ def test_qasm3_output_control_flow(self, optimization_level): OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, dynamic=True), + backend=FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True + ), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -1991,7 +2133,9 @@ def test_qasm3_output_control_flow_expr(self, optimization_level): dumped into OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=27, dynamic=True), + backend=FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, control_flow=True + ), optimization_level=optimization_level, seed_transpiler=2023_07_26, ) @@ -2047,6 +2191,7 @@ def callback(**kwargs): backend = FakeGeneric( num_qubits=5, + basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], seed=1234, ) @@ -2148,7 +2293,9 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=4)) + pm = generate_preset_pass_manager( + opt_level, backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + ) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -2160,7 +2307,7 @@ def test_parallel_with_target(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeGeneric(num_qubits=4).target + target = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4).target res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: @@ -2208,7 +2355,11 @@ def run(self, dag): return dag # Remove calibration for sx gate - backend = FakeGeneric(num_qubits=4, add_cal_entries=True, empty_cal_gates=["sx"]) + backend = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], + num_qubits=4, + calibrate_gates=["cx", "id", "rz", "x"], + ) # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) @@ -2228,7 +2379,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): """Test that singleton mutable instance doesn't lose state in parallel.""" - backend = FakeGeneric(num_qubits=27) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27) circ = QuantumCircuit(2, 1) circ.h(0) circ.measure(0, circ.clbits[0]) @@ -2242,7 +2393,7 @@ def test_parallel_singleton_conditional_gate(self, opt_level): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = FakeGeneric(num_qubits=6) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2279,7 +2430,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=FakeGeneric(num_qubits=6, replace_cx_with_ecr=False), + backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2294,7 +2445,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = FakeGeneric(num_qubits=4, replace_cx_with_ecr=False) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2318,6 +2469,7 @@ def test_transpile_with_multiple_coupling_maps(self): def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" backend = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], ) @@ -3203,7 +3355,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = FakeGeneric(num_qubits=130) + backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=130) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) From 96c46e1f511b9ded691e034f0101e352c910783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 19 Oct 2023 13:27:14 +0200 Subject: [PATCH 23/56] Update unit test --- .../providers/fake_provider/fake_generic.py | 26 ++- .../fake_provider/test_fake_generic.py | 157 +++++++++--------- 2 files changed, 91 insertions(+), 92 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index c1d93092146b..9b495a9448fb 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -63,7 +63,7 @@ def __init__( basis_gates (list[str]): List of basis gate names to be supported by the target. The currently supported instructions can be consulted via - the ``supported_instructions`` property. + the ``supported_names`` property. Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` and ``{"ecr", "id", "rz", "sx", "x"}``. @@ -81,6 +81,13 @@ def __init__( """ self._rng = rng if rng else np.random.default_rng(seed=42) self._num_qubits = num_qubits + + if coupling_map.size() != num_qubits: + raise ValueError( + f"The number of qubits in the coupling map " + f"({coupling_map.size()}) does not match the " + f"number of qubits defined in the target ({num_qubits})." + ) self._coupling_map = coupling_map # hardcode default target attributes. To modify, @@ -101,19 +108,20 @@ def __init__( ) # ensure that reset, delay and measure are in basis_gates - self._basis_gates = set(basis_gates) - for name in {"reset", "delay", "measure"}: - self._basis_gates.add(name) + basis_gates = set(basis_gates) + for name in ["delay", "measure", "reset"]: + basis_gates.add(name) + self._basis_gates = basis_gates # iterate over gates, generate noise params from defaults # and add instructions to target for name in self._basis_gates: - if name not in self.supported_instructions: - raise QiskitError( + if name not in self.supported_names: + raise ValueError( f"Provided base gate {name} is not a supported " - f"instruction ({self.supported_instructions})." + f"instruction ({self.supported_names})." ) - gate = self.supported_instructions[name] + gate = self.supported_names[name] noise_params = self.noise_defaults[name] self.add_noisy_instruction(gate, noise_params) @@ -131,7 +139,7 @@ def __init__( self.add_calibration_defaults(defaults) @property - def supported_instructions(self) -> dict[str, Instruction]: + def supported_names(self) -> dict[str, Instruction]: """Mapping of names to class instances for instructions supported in ``basis_gates``. diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 2a17d4b5c32a..178c6ff2e094 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -12,100 +12,91 @@ """ Test of FakeGeneric backend""" from qiskit.providers.fake_provider import FakeGeneric -from qiskit.test import QiskitTestCase +from qiskit.providers.fake_provider.fake_generic import GenericTarget +from qiskit.transpiler import CouplingMap +from qiskit.test import QiskitTestCase -class TestFakeGeneric(QiskitTestCase): - """Test class for FakeGeneric backend""" - def test_heavy_hex_num_qubits(self): - """Test if num_qubits=5 and coupling_map_type - is heavy_hex the number of qubits generated is 19.""" - self.assertEqual(FakeGeneric(num_qubits=5, coupling_map_type="heavy_hex").num_qubits, 19) +class TestGenericTarget(QiskitTestCase): + """Test class for GenericTarget""" - def test_heavy_hex_coupling_map(self): - """Test if coupling_map of heavy_hex is generated correctly.""" - cp_mp = [ - (0, 13), - (1, 13), - (1, 14), - (2, 14), - (3, 15), - (4, 15), - (4, 16), - (5, 16), - (6, 17), - (7, 17), - (7, 18), - (8, 18), - (0, 9), - (3, 9), - (5, 12), - (8, 12), - (10, 14), - (10, 16), - (11, 15), - (11, 17), - ] - self.assertEqual( - list( - FakeGeneric(num_qubits=19, coupling_map_type="heavy_hex").coupling_map.get_edges() - ), - cp_mp, + def setUp(self): + super().setUp() + self.cmap = CouplingMap( + [(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] ) + self.basis_gates = ["cx", "id", "rz", "sx", "x"] - def test_grid_coupling_map(self): - """Test if grid coupling map is generated correctly. - In this test num_qubits=8, so a grid of 2x4 qubits need to be constructed""" - cp_mp = [(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] - self.assertEqual( - list(FakeGeneric(num_qubits=8, coupling_map_type="grid").coupling_map.get_edges()), - cp_mp, - ) + def test_supported_basis_gates(self): + """Test that target raises error if basis_gate not in ``supported_names``.""" + with self.assertRaises(ValueError): + GenericTarget( + num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"], coupling_map=self.cmap + ) - def test_basis_gates(self): - """Test if the backend has default basis gates that include delay and measure""" - self.assertEqual( - FakeGeneric(num_qubits=8).operation_names, - ["ecr", "id", "rz", "sx", "x", "delay", "measure", "reset"], + def test_operation_names(self): + """Test that target basis gates include "delay", "measure" and "reset" even + if not provided by user.""" + target = GenericTarget( + num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=self.cmap ) + op_names = list(target.operation_names) + op_names.sort() + self.assertEqual(op_names, ["delay", "ecr", "id", "measure", "reset", "rz", "sx", "x"]) - def test_if_cx_replaced_with_ecr(self): - """Test if cx is not replaced with ecr""" - self.assertEqual( - FakeGeneric(num_qubits=8, replace_cx_with_ecr=False).operation_names, - ["cx", "id", "rz", "sx", "x", "delay", "measure", "reset"], - ) + def test_incompatible_coupling_map(self): + """Test that the size of the coupling map must match num_qubits.""" + with self.assertRaises(ValueError): + FakeGeneric( + num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=self.cmap + ) - def test_dynamic_true_basis_gates(self): - """Test if basis_gates includes ControlFlowOps when dynamic is set to True""" - self.assertEqual( - FakeGeneric(num_qubits=9, dynamic=True).operation_names, - [ - "ecr", - "id", - "rz", - "sx", - "x", - "delay", - "measure", - "if_else", - "while_loop", - "for_loop", - "switch_case", - "break", - "continue", - "reset", - ], + def test_control_flow_operation_names(self): + """Test that control flow instructions are added to the target if control_flow is True.""" + target = GenericTarget( + num_qubits=8, + basis_gates=["ecr", "id", "rz", "sx", "x"], + coupling_map=self.cmap, + control_flow=True, ) + op_names = list(target.operation_names) + op_names.sort() + reference = [ + "break", + "continue", + "delay", + "ecr", + "for_loop", + "id", + "if_else", + "measure", + "reset", + "rz", + "switch_case", + "sx", + "while_loop", + "x", + ] + self.assertEqual(op_names, reference) + + +class TestFakeGeneric(QiskitTestCase): + """Test class for FakeGeneric backend""" + + def test_default_coupling_map(self): + """Test that fully-connected coupling map is generated correctly.""" + + # fmt: off + reference_cmap = [(0, 1), (1, 0), (0, 2), (2, 0), (0, 3), (3, 0), (0, 4), (4, 0), (1, 2), (2, 1), + (1, 3), (3, 1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3)] + # fmt: on - def test_if_excludes_reset(self): - """Test if reset is excluded from the operation names when set enable_reset False""" self.assertEqual( - FakeGeneric(num_qubits=9, enable_reset=False).operation_names, - ["ecr", "id", "rz", "sx", "x", "delay", "measure"], + list( + FakeGeneric( + num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"] + ).coupling_map.get_edges() + ), + reference_cmap, ) - - def test_if_dt_is_set_correctly(self): - """Test if dt is set correctly""" - self.assertEqual(FakeGeneric(num_qubits=4, dt=0.5e-9).dt, 0.5e-9) From 242d5fb7c3bb81d4b54b6abff437a1a33f7f34ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 13 Nov 2023 11:19:05 +0100 Subject: [PATCH 24/56] Rename supported instructions to operations, fix tests Fix test seed Fix unit test, lint --- .../providers/fake_provider/fake_generic.py | 60 ++++++++++--------- test/python/compiler/test_transpiler.py | 4 +- .../fake_provider/test_fake_generic.py | 6 +- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 9b495a9448fb..c06289ce7b26 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -46,6 +46,8 @@ class GenericTarget(Target): """ This class will generate a :class:`~.Target` instance with default qubit, instruction and calibration properties. + A target object represents the minimum set of information + the transpiler needs from a backend. """ def __init__( @@ -59,11 +61,12 @@ def __init__( ): """ Args: - num_qubits (int): Number of qubits. + num_qubits (int): Number of qubits in the target. basis_gates (list[str]): List of basis gate names to be supported by - the target. The currently supported instructions can be consulted via - the ``supported_names`` property. + the target. These must be within the supported operations of this + target generator, which can be consulted through the + ``supported_operations`` property. Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` and ``{"ecr", "id", "rz", "sx", "x"}``. @@ -80,20 +83,20 @@ def __init__( rng (np.random.Generator): Optional fixed-seed generator for default random values. """ self._rng = rng if rng else np.random.default_rng(seed=42) - self._num_qubits = num_qubits - if coupling_map.size() != num_qubits: - raise ValueError( - f"The number of qubits in the coupling map " - f"({coupling_map.size()}) does not match the " - f"number of qubits defined in the target ({num_qubits})." + if num_qubits != coupling_map.size(): + raise QiskitError( + f"The number of qubits (got {num_qubits}) must match " + f"the size of the provided coupling map (got {coupling_map.size()})." ) + + self._num_qubits = num_qubits self._coupling_map = coupling_map - # hardcode default target attributes. To modify, - # access corresponding properties through the public Target API + # hardcoded default target attributes. To modify, + # access corresponding properties through the public `Target` API super().__init__( - description="Fake Target", + description="Generic Target", num_qubits=num_qubits, dt=0.222e-9, qubit_properties=[ @@ -107,21 +110,20 @@ def __init__( concurrent_measurements=[list(range(num_qubits))], ) - # ensure that reset, delay and measure are in basis_gates - basis_gates = set(basis_gates) - for name in ["delay", "measure", "reset"]: - basis_gates.add(name) - self._basis_gates = basis_gates + # ensure that Reset, Delay and Measure are in basis_gates + self._basis_gates = set(basis_gates) + for name in ["reset", "delay", "measure"]: + self._basis_gates.add(name) - # iterate over gates, generate noise params from defaults + # iterate over gates, generate noise params. from defaults # and add instructions to target for name in self._basis_gates: - if name not in self.supported_names: - raise ValueError( + if name not in self.supported_operations: + raise QiskitError( f"Provided base gate {name} is not a supported " - f"instruction ({self.supported_names})." + f"operation ({self.supported_operations})." ) - gate = self.supported_names[name] + gate = self.supported_operations[name] noise_params = self.noise_defaults[name] self.add_noisy_instruction(gate, noise_params) @@ -134,17 +136,19 @@ def __init__( self.add_instruction(ContinueLoopOp, name="continue") # generate block of calibration defaults and add to target + # Note: this could be improved if we could generate and add + # calibration defaults per-gate, and not as a block. if calibrate_gates is not None: defaults = self._generate_calibration_defaults(calibrate_gates) self.add_calibration_defaults(defaults) @property - def supported_names(self) -> dict[str, Instruction]: - """Mapping of names to class instances for instructions supported + def supported_operations(self) -> dict[str, Instruction]: + """Mapping of names to class instances for operations supported in ``basis_gates``. Returns: - Dictionary mapping instruction names to instruction instances + Dictionary mapping operation names to class instances. """ return { "cx": CXGate(), @@ -343,7 +347,7 @@ def _generate_calibration_defaults(self, calibrate_gates: list[str] | None) -> P class FakeGeneric(BackendV2): """ Configurable fake :class:`~.BackendV2` generator. This class will - generate a fake backend from a combination of random defaults + generate a fake backend from a combination of generated defaults (with a fixable ``seed``) driven from a series of optional input arguments. """ @@ -365,7 +369,8 @@ def __init__( Common sets of basis gates are ``["cx", "id", "rz", "sx", "x"]`` and ``["ecr", "id", "rz", "sx", "x"]``. - num_qubits (int): Number of qubits for the fake backend. + num_qubits (int): Number of qubits of the fake backend. Note that this + fake backend runs on a local noisy simulator, coupling_map (list[list[int]] | CouplingMap | None): Optional coupling map for the fake backend. Multiple formats are supported: @@ -399,7 +404,6 @@ def __init__( self._rng = np.random.default_rng(seed=seed) # the coupling map is necessary to build the default channels - # (duplicating a bit of logic) if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) else: diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index b73482ee75b3..8c2ab2586c90 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2193,7 +2193,7 @@ def callback(**kwargs): num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], - seed=1234, + seed=5, ) qubits = 3 qc = QuantumCircuit(qubits) @@ -2202,7 +2202,7 @@ def callback(**kwargs): tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) self.assertTrue(vf2_post_layout_called) - self.assertEqual([0, 2, 1], _get_index_layout(tqc, qubits)) + self.assertEqual([2, 3, 1], _get_index_layout(tqc, qubits)) class StreamHandlerRaiseException(StreamHandler): diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 178c6ff2e094..354d9dfff457 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -14,7 +14,7 @@ from qiskit.providers.fake_provider import FakeGeneric from qiskit.providers.fake_provider.fake_generic import GenericTarget from qiskit.transpiler import CouplingMap - +from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase @@ -30,7 +30,7 @@ def setUp(self): def test_supported_basis_gates(self): """Test that target raises error if basis_gate not in ``supported_names``.""" - with self.assertRaises(ValueError): + with self.assertRaises(QiskitError): GenericTarget( num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"], coupling_map=self.cmap ) @@ -47,7 +47,7 @@ def test_operation_names(self): def test_incompatible_coupling_map(self): """Test that the size of the coupling map must match num_qubits.""" - with self.assertRaises(ValueError): + with self.assertRaises(QiskitError): FakeGeneric( num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=self.cmap ) From e8305e3c9f1847cdbd647747cad2f3be6d05d243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 23 Nov 2023 11:01:23 +0100 Subject: [PATCH 25/56] Restore vf2postlayout test to V1 Fx vf2 test --- test/python/compiler/test_transpiler.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 8c2ab2586c90..337c95eba2cb 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -75,6 +75,7 @@ from qiskit.providers.fake_provider import ( FakeMelbourne, FakeRueschlikon, + FakeVigo, ) from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap @@ -2189,12 +2190,7 @@ def callback(**kwargs): vf2_post_layout_called = True self.assertIsNotNone(kwargs["property_set"]["post_layout"]) - backend = FakeGeneric( - num_qubits=5, - basis_gates=["cx", "id", "rz", "sx", "x"], - coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], - seed=5, - ) + backend = FakeVigo() qubits = 3 qc = QuantumCircuit(qubits) for i in range(5): @@ -2202,7 +2198,7 @@ def callback(**kwargs): tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) self.assertTrue(vf2_post_layout_called) - self.assertEqual([2, 3, 1], _get_index_layout(tqc, qubits)) + self.assertEqual([3, 2, 1], _get_index_layout(tqc, qubits)) class StreamHandlerRaiseException(StreamHandler): From be2393b8a5458faef6f336f7f5521782067e91a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 23 Nov 2023 13:44:57 +0100 Subject: [PATCH 26/56] Add pulse test to validate pulse capabilities of new fake backend Update fake generic --- .../providers/fake_provider/fake_generic.py | 16 +++--- test/python/pulse/test_macros.py | 50 ++++++++++++------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index c06289ce7b26..e8f3a0f07907 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -19,6 +19,7 @@ import numpy as np from qiskit import pulse +from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.circuit import Measure, Parameter, Delay, Reset, QuantumCircuit, Instruction from qiskit.circuit.controlflow import ( IfElseOp, @@ -77,8 +78,7 @@ def __init__( (defaults to False). calibrate_gates (list[str] | None): List of gate names which should contain - default calibration entries (overriden if an ``instruction_schedule_map`` is - provided). These must be a subset of ``basis_gates``. + default calibration entries. These must be a subset of ``basis_gates``. rng (np.random.Generator): Optional fixed-seed generator for default random values. """ @@ -138,9 +138,9 @@ def __init__( # generate block of calibration defaults and add to target # Note: this could be improved if we could generate and add # calibration defaults per-gate, and not as a block. - if calibrate_gates is not None: - defaults = self._generate_calibration_defaults(calibrate_gates) - self.add_calibration_defaults(defaults) + defaults = self._generate_calibration_defaults(calibrate_gates) + inst_map = defaults.instruction_schedule_map + self.add_calibrations_from_instruction_schedule_map(inst_map) @property def supported_operations(self) -> dict[str, Instruction]: @@ -222,7 +222,9 @@ def add_noisy_instruction( self.add_instruction(instruction, props) - def add_calibration_defaults(self, defaults: PulseDefaults) -> None: + def add_calibrations_from_instruction_schedule_map( + self, inst_map: InstructionScheduleMap + ) -> None: """Add calibration entries from provided pulse defaults to target. Args: @@ -231,7 +233,6 @@ def add_calibration_defaults(self, defaults: PulseDefaults) -> None: Returns: None """ - inst_map = defaults.instruction_schedule_map for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): try: @@ -257,6 +258,7 @@ def _generate_calibration_defaults(self, calibrate_gates: list[str] | None) -> P Returns: Corresponding PulseDefaults """ + calibrate_gates = calibrate_gates or [] measure_command_sequence = [ PulseQobjInstruction( name="acquire", diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 8c82e0c569be..da6cdd2a2335 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2019, 2023 # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -24,7 +24,7 @@ ) from qiskit.pulse import macros from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, FakeHanoiV2 +from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, FakeGeneric from qiskit.test import QiskitTestCase @@ -34,9 +34,17 @@ class TestMeasure(QiskitTestCase): def setUp(self): super().setUp() self.backend = FakeOpenPulse2Q() - self.backend_v2 = FakeHanoiV2() + self.backend_v1 = FakeHanoi() self.inst_map = self.backend.defaults().instruction_schedule_map + self.backend_v2 = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], + num_qubits=27, + ) + self.backend_v2.target.add_calibrations_from_instruction_schedule_map( + self.backend_v1.defaults().instruction_schedule_map + ) + def test_measure(self): """Test macro - measure.""" sched = macros.measure(qubits=[0], backend=self.backend) @@ -149,13 +157,16 @@ def test_multiple_measure_v2(self): def test_output_with_measure_v1_and_measure_v2(self): """Test make outputs of measure_v1 and measure_v2 consistent.""" - sched_measure_v1 = macros.measure(qubits=[0, 1], backend=FakeHanoi()) + sched_measure_v1 = macros.measure(qubits=[0, 1], backend=self.backend_v1) sched_measure_v2 = macros.measure(qubits=[0, 1], backend=self.backend_v2) + self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) def test_output_with_measure_v1_and_measure_v2_sched_with_qubit_mem_slots(self): """Test make outputs of measure_v1 and measure_v2 with custom qubit_mem_slots consistent.""" - sched_measure_v1 = macros.measure(qubits=[0], backend=FakeHanoi(), qubit_mem_slots={0: 2}) + sched_measure_v1 = macros.measure( + qubits=[0], backend=self.backend_v1, qubit_mem_slots={0: 2} + ) sched_measure_v2 = macros.measure( qubits=[0], backend=self.backend_v2, qubit_mem_slots={0: 2} ) @@ -167,11 +178,11 @@ def test_output_with_measure_v1_and_measure_v2_sched_with_meas_map(self): num_qubits_list_measure_v1 = list(range(FakeHanoi().configuration().num_qubits)) num_qubits_list_measure_v2 = list(range(self.backend_v2.num_qubits)) sched_with_meas_map_list_v1 = macros.measure( - qubits=[0], backend=FakeHanoi(), meas_map=[num_qubits_list_measure_v1] + qubits=[0], backend=self.backend_v1, meas_map=[num_qubits_list_measure_v1] ) sched_with_meas_map_dict_v1 = macros.measure( qubits=[0], - backend=FakeHanoi(), + backend=self.backend_v1, meas_map={0: num_qubits_list_measure_v1, 1: num_qubits_list_measure_v1}, ) sched_with_meas_map_list_v2 = macros.measure( @@ -193,7 +204,7 @@ def test_output_with_measure_v1_and_measure_v2_sched_with_meas_map(self): def test_output_with_multiple_measure_v1_and_measure_v2(self): """Test macro - consistent output of multiple qubit measure with backendV1 and backendV2.""" - sched_measure_v1 = macros.measure(qubits=[0, 1], backend=FakeHanoi()) + sched_measure_v1 = macros.measure(qubits=[0, 1], backend=self.backend_v1) sched_measure_v2 = macros.measure(qubits=[0, 1], backend=self.backend_v2) self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) @@ -203,29 +214,32 @@ class TestMeasureAll(QiskitTestCase): def setUp(self): super().setUp() - self.backend = FakeOpenPulse2Q() - self.backend_v2 = FakeHanoiV2() - self.inst_map = self.backend.defaults().instruction_schedule_map + self.backend_v1 = FakeOpenPulse2Q() + self.backend_v2 = FakeGeneric( + basis_gates=["cx", "id", "rz", "sx", "x"], + num_qubits=2, + ) + self.inst_map = self.backend_v1.defaults().instruction_schedule_map + self.backend_v2.target.add_calibrations_from_instruction_schedule_map( + self.backend_v1.defaults().instruction_schedule_map + ) def test_measure_all(self): """Test measure_all function.""" - sched = macros.measure_all(self.backend) + sched = macros.measure_all(self.backend_v1) expected = Schedule(self.inst_map.get("measure", [0, 1])) self.assertEqual(sched.instructions, expected.instructions) def test_measure_all_v2(self): """Test measure_all function with backendV2.""" - backend_v1 = FakeHanoi() - sched = macros.measure_all(self.backend_v2) + sched = macros.measure_all(self.backend_v1) expected = Schedule( - backend_v1.defaults().instruction_schedule_map.get( - "measure", list(range(backend_v1.configuration().num_qubits)) - ) + self.inst_map.get("measure", list(range(self.backend_v1.configuration().num_qubits))) ) self.assertEqual(sched.instructions, expected.instructions) def test_output_of_measure_all_with_backend_v1_and_v2(self): """Test make outputs of measure_all with backendV1 and backendV2 consistent.""" - sched_measure_v1 = macros.measure_all(backend=FakeHanoi()) + sched_measure_v1 = macros.measure_all(backend=self.backend_v1) sched_measure_v2 = macros.measure_all(backend=self.backend_v2) self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) From a6b3fe6a0ec0cb4a8063090fe344a20489ccf73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 23 Nov 2023 17:26:12 +0100 Subject: [PATCH 27/56] Update docs, fix lint --- qiskit/providers/fake_provider/__init__.py | 3 ++- qiskit/providers/fake_provider/fake_generic.py | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index cb8268451957..6075ec84be03 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -224,6 +224,7 @@ FakeBackend5QV2 FakeMumbaiFractionalCX ConfigurableFakeBackend + FakeGeneric Fake Backend Base Classes ========================= @@ -265,4 +266,4 @@ # Configurable fake backend from .utils.configurable_backend import ConfigurableFakeBackend -from .fake_generic import FakeGeneric +from .fake_generic import FakeGeneric, GenericTarget diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index e8f3a0f07907..2c25dfe1e8fd 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -77,8 +77,8 @@ def __init__( control_flow (bool): Flag to enable control flow directives on the backend (defaults to False). - calibrate_gates (list[str] | None): List of gate names which should contain - default calibration entries. These must be a subset of ``basis_gates``. + calibrate_gates (list[str] | None): List of gate names (subset of basis_gates) + to add default calibration entries to. rng (np.random.Generator): Optional fixed-seed generator for default random values. """ @@ -121,7 +121,7 @@ def __init__( if name not in self.supported_operations: raise QiskitError( f"Provided base gate {name} is not a supported " - f"operation ({self.supported_operations})." + f"operation ({self.supported_operations.keys()})." ) gate = self.supported_operations[name] noise_params = self.noise_defaults[name] @@ -228,7 +228,7 @@ def add_calibrations_from_instruction_schedule_map( """Add calibration entries from provided pulse defaults to target. Args: - defaults (PulseDefaults): pulse defaults with instruction schedule map + inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map Returns: None @@ -390,9 +390,8 @@ def __init__( control_flow (bool): Flag to enable control flow directives on the backend (defaults to False). - calibrate_gates (list[str] | None): List of gate names which should contain - default calibration entries (overriden if an ``instruction_schedule_map`` is - provided). These must be a subset of ``basis_gates``. + calibrate_gates (list[str] | None): List of gate names (subset of basis_gates) + to add default calibration entries to. seed (int): Optional seed for generation of default values. """ From fbd1aadd4d218c0313d82bf8dfd5363898e314fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 24 Nov 2023 15:06:57 +0100 Subject: [PATCH 28/56] Avoid set for basis gates to allow reproducibility in the error/duration value generation --- qiskit/providers/fake_provider/fake_generic.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 2c25dfe1e8fd..6a4b10cc0753 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -111,9 +111,10 @@ def __init__( ) # ensure that Reset, Delay and Measure are in basis_gates - self._basis_gates = set(basis_gates) + self._basis_gates = basis_gates for name in ["reset", "delay", "measure"]: - self._basis_gates.add(name) + if name not in self._basis_gates: + self._basis_gates.append(name) # iterate over gates, generate noise params. from defaults # and add instructions to target From 6f3bb4bccd996f53dd145c83d8adc8edd7e27756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 30 Nov 2023 15:27:29 +0100 Subject: [PATCH 29/56] Update calibrations --- .../providers/fake_provider/fake_generic.py | 205 ++++++++++-------- 1 file changed, 115 insertions(+), 90 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 6a4b10cc0753..e4f25749492e 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -57,7 +57,7 @@ def __init__( basis_gates: list[str], coupling_map: CouplingMap, control_flow: bool = False, - calibrate_gates: list[str] | None = None, + calibrate_instructions: list[str] | None = None, rng: np.random.Generator = None, ): """ @@ -69,16 +69,22 @@ def __init__( target generator, which can be consulted through the ``supported_operations`` property. Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` - and ``{"ecr", "id", "rz", "sx", "x"}``. + and ``{"ecr", "id", "rz", "sx", "x"}``. The ``"reset"``, ``"`delay"`` + and ``"measure"`` instructions are supported by default, even if + not specified via ``basis_gates``. coupling_map (CouplingMap): Target's coupling map as an instance of :class:`~.CouplingMap`. - control_flow (bool): Flag to enable control flow directives on the backend + control_flow (bool): Flag to enable control flow directives on the target (defaults to False). - calibrate_gates (list[str] | None): List of gate names (subset of basis_gates) - to add default calibration entries to. + calibrate_instructions (list[str] | None): List of instruction names + to add default calibration entries to. These must be within the + supported operations of this target generator, which can be + consulted through the ``supported_operations`` property. If no + instructions are provided, the target generator will append + empty calibration schedules by default. rng (np.random.Generator): Optional fixed-seed generator for default random values. """ @@ -93,7 +99,7 @@ def __init__( self._num_qubits = num_qubits self._coupling_map = coupling_map - # hardcoded default target attributes. To modify, + # Hardcoded default target attributes. To modify, # access corresponding properties through the public `Target` API super().__init__( description="Generic Target", @@ -110,14 +116,16 @@ def __init__( concurrent_measurements=[list(range(num_qubits))], ) - # ensure that Reset, Delay and Measure are in basis_gates + # Ensure that Reset, Delay and Measure are in + # self._basis_gates + # so that their instructions are added to the target. self._basis_gates = basis_gates for name in ["reset", "delay", "measure"]: if name not in self._basis_gates: self._basis_gates.append(name) - # iterate over gates, generate noise params. from defaults - # and add instructions to target + # Iterate over gates, generate noise params from defaults, + # and add instructions to target. for name in self._basis_gates: if name not in self.supported_operations: raise QiskitError( @@ -136,10 +144,10 @@ def __init__( self.add_instruction(BreakLoopOp, name="break") self.add_instruction(ContinueLoopOp, name="continue") - # generate block of calibration defaults and add to target + # Generate block of calibration defaults and add to target. # Note: this could be improved if we could generate and add # calibration defaults per-gate, and not as a block. - defaults = self._generate_calibration_defaults(calibrate_gates) + defaults = self._generate_calibration_defaults(calibrate_instructions) inst_map = defaults.instruction_schedule_map self.add_calibrations_from_instruction_schedule_map(inst_map) @@ -234,13 +242,16 @@ def add_calibrations_from_instruction_schedule_map( Returns: None """ + + # The calibration entries are directly injected into the gate map to + # avoid then being labeled as "user_provided". for inst in inst_map.instructions: for qarg in inst_map.qubits_with_instruction(inst): try: qargs = tuple(qarg) except TypeError: qargs = (qarg,) - # Do NOT call .get method. This parses Qpbj immediately. + # Do NOT call .get method. This parses Qobj immediately. # This operation is computationally expensive and should be bypassed. calibration_entry = inst_map._get_calibration_entry(inst, qargs) if inst in self._gate_map: @@ -250,94 +261,97 @@ def add_calibrations_from_instruction_schedule_map( elif qargs in self._gate_map[inst] and inst not in ["delay", "reset"]: self._gate_map[inst][qargs].calibration = calibration_entry - def _generate_calibration_defaults(self, calibrate_gates: list[str] | None) -> PulseDefaults: - """Generate calibration defaults for specified gates. + def _generate_calibration_defaults( + self, calibrate_instructions: list[str] | None + ) -> PulseDefaults: + """Generate calibration defaults for instructions specified via ``calibrate_instructions``. + By default, this method generates empty calibration schedules. Args: - calibrate_gates (list[str]): list of gates to be calibrated. + calibrate_instructions (list[str]): list of instructions to be calibrated. Returns: Corresponding PulseDefaults """ - calibrate_gates = calibrate_gates or [] - measure_command_sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=list(range(self.num_qubits)), - memory_slot=list(range(self.num_qubits)), - ) - ] - measure_command_sequence += [ - PulseQobjInstruction(name="pulse_1", ch=f"m{i}", duration=1792, t0=0) - for i in range(self.num_qubits) - ] + # The number of samples determines the pulse durations of the corresponding + # instructions. This class generates pulses with durations in multiples of + # 16 for consistency with the pulse granularity of real IBM devices, but + # keeps the number smaller than what would be realistic for + # manageability. If needed, more realistic durations could be added in the + # future (order of 160 dt for 1q gates, 1760 for 2q gates and measure). - measure_command = Command( - name="measure", - qubits=list(range(self.num_qubits)), - sequence=measure_command_sequence, - ) + samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt + samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt + samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt - cmd_def = [measure_command] + pulse_library = [ + PulseLibraryItem(name="pulse_1", samples=samples_1), + PulseLibraryItem(name="pulse_2", samples=samples_2), + PulseLibraryItem(name="pulse_3", samples=samples_3), + ] - for gate in self._basis_gates: - for i in range(self.num_qubits): + # Unless explicitly given a series of gates to calibrate, this method + # will generate empty pulse schedules for all gates in self._basis_gates. + calibrate_instructions = calibrate_instructions or [] + calibration_buffer = self._basis_gates.copy() + for inst in ["delay", "reset"]: + calibration_buffer.remove(inst) + + # List of calibration commands (generated from sequences of PulseQobjInstructions) + # corresponding to each calibrated instruction. Note that the calibration pulses + # are different for 1q gates vs 2q gates vs measurement instructions. + cmd_def = [] + for inst in calibration_buffer: + num_qubits = self.supported_operations[inst].num_qubits + qarg_set = self._coupling_map if num_qubits > 1 else list(range(self._num_qubits)) + if inst == "measure": sequence = [] - if gate in calibrate_gates: + qubits = qarg_set + if inst in calibrate_instructions: sequence = [ - PulseQobjInstruction(name="fc", ch=f"d{i}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_3", ch=f"d{i}", t0=0), - ] + PulseQobjInstruction( + name="acquire", + duration=1792, + t0=0, + qubits=list(range(self.num_qubits)), + memory_slot=list(range(self.num_qubits)), + ) + ] + [PulseQobjInstruction(name="pulse_2", ch=f"m{i}", t0=0) for i in qarg_set] cmd_def.append( Command( - name=gate, - qubits=[i], + name=inst, + qubits=qubits, sequence=sequence, ) ) - - for qubit1, qubit2 in self._coupling_map: - sequence = [ - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit1}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"u{qubit1}", t0=10), - PulseQobjInstruction(name="pulse_1", ch=f"d{qubit2}", t0=20), - PulseQobjInstruction(name="fc", ch=f"d{qubit2}", t0=20, phase=2.1), - ] - - if "cx" in self._basis_gates: - if "cx" in calibrate_gates: - sequence = [] - cmd_def += [ - Command( - name="cx", - qubits=[qubit1, qubit2], - sequence=sequence, - ) - ] - if "ecr" in self._basis_gates: - if "ecr" in calibrate_gates: + else: + for qarg in qarg_set: sequence = [] - cmd_def += [ - Command( - name="ecr", - qubits=[qubit1, qubit2], - sequence=sequence, + qubits = [qarg] if num_qubits == 1 else qarg + if inst in calibrate_instructions: + if num_qubits == 1: + sequence = [ + PulseQobjInstruction(name="fc", ch=f"u{qarg}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_1", ch=f"d{qarg}", t0=0), + ] + else: + sequence = [ + PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[0]}", t0=0), + PulseQobjInstruction(name="pulse_3", ch=f"u{qarg[0]}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[1]}", t0=0), + PulseQobjInstruction(name="fc", ch=f"d{qarg[1]}", t0=0, phase=2.1), + ] + cmd_def.append( + Command( + name=inst, + qubits=qubits, + sequence=sequence, + ) ) - ] qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() - pulse_library = [ - PulseLibraryItem(name="pulse_1", samples=[[0.0, 0.0], [0.0, 0.1]]), - PulseLibraryItem(name="pulse_2", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0]]), - PulseLibraryItem( - name="pulse_3", samples=[[0.0, 0.0], [0.0, 0.1], [0.0, 1.0], [0.5, 0.0]] - ), - ] - return PulseDefaults( qubit_freq_est=qubit_freq_est, meas_freq_est=meas_freq_est, @@ -361,19 +375,26 @@ def __init__( *, coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, - calibrate_gates: list[str] | None = None, + calibrate_instructions: list[str] | None = None, seed: int = 42, ): """ Args: - basis_gates (list[str]): List of basis gate names to be supported by - the backend. The currently supported gate names are: - ``"cx"``, ``"ecr"``, ``"id"``, ``"rz"``, ``"sx"``, and ``"x"`` - Common sets of basis gates are ``["cx", "id", "rz", "sx", "x"]`` - and ``["ecr", "id", "rz", "sx", "x"]``. + num_qubits (int): Number of qubits that will + be used to construct the backend's target. Note that, while + there is no limit in the size of the target that can be + constructed, fake backends run on local noisy simulators, + and these might show limitations in the number of qubits that + can be simulated. - num_qubits (int): Number of qubits of the fake backend. Note that this - fake backend runs on a local noisy simulator, + basis_gates (list[str]): List of basis gate names to be supported by + the target. These must be within the supported operations of the + target generator, which can be consulted through its + ``supported_operations`` property. + Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` + and ``{"ecr", "id", "rz", "sx", "x"}``. The ``"reset"``, ``"`delay"``, + and ``"measure"`` instructions are supported by default, even if + not specified via ``basis_gates``. coupling_map (list[list[int]] | CouplingMap | None): Optional coupling map for the fake backend. Multiple formats are supported: @@ -388,11 +409,15 @@ def __init__( a fully connected coupling map will be generated with ``num_qubits`` qubits. - control_flow (bool): Flag to enable control flow directives on the backend + control_flow (bool): Flag to enable control flow directives on the target (defaults to False). - calibrate_gates (list[str] | None): List of gate names (subset of basis_gates) - to add default calibration entries to. + calibrate_instructions (list[str] | None): List of instruction names + to add default calibration entries to. These must be within the + supported operations of the target generator, which can be + consulted through the ``supported_operations`` property. If no + instructions are provided, the target generator will append + empty calibration schedules by default. seed (int): Optional seed for generation of default values. """ @@ -419,7 +444,7 @@ def __init__( basis_gates, self._coupling_map, control_flow, - calibrate_gates, + calibrate_instructions, self._rng, ) From ba14dc1b7b42ca88f59a7d30ee9e33b78a7635a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 30 Nov 2023 16:27:36 +0100 Subject: [PATCH 30/56] Fix transpiler test --- test/python/compiler/test_transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 662abe0d0be7..e7e24fdc3ee8 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2386,7 +2386,7 @@ def run(self, dag): backend = FakeGeneric( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4, - calibrate_gates=["cx", "id", "rz", "x"], + calibrate_instructions=["cx", "id", "rz", "x", "measure"], ) # This target has PulseQobj entries that provide a serialized schedule data From 5b87345550fc767f3fb7e6473ac279962064e428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 4 Dec 2023 19:01:05 +0100 Subject: [PATCH 31/56] Add run test to confirm noise defaults --- .../providers/fake_provider/fake_generic.py | 23 ++++++++---------- .../fake_provider/test_fake_generic.py | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index e4f25749492e..33baaad4fc37 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -174,26 +174,25 @@ def supported_operations(self) -> dict[str, Instruction]: @property def noise_defaults(self) -> dict[str, tuple | None]: """Noise default values/ranges for duration and error of supported - instructions. There are three possible formats: + instructions. There are two possible formats: #. (min_duration, max_duration, min_error, max_error), if the defaults are ranges #. (duration, error), if the defaults are fixed values - #. None Returns: Dictionary mapping instruction names to noise defaults """ return { - "cx": (1e-5, 5e-3, 1e-8, 9e-7), - "ecr": (1e-5, 5e-3, 1e-8, 9e-7), - "id": (0.0, 0.0), + "cx": (1e-8, 9e-7, 1e-5, 5e-3), + "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "id": (3e-8, 4e-8, 9e-5, 1e-4), "rz": (0.0, 0.0), - "sx": (1e-5, 5e-3, 1e-8, 9e-7), - "x": (1e-5, 5e-3, 1e-8, 9e-7), - "measure": (1e-5, 5e-3, 1e-8, 9e-7), - "delay": None, - "reset": None, + "sx": (1e-8, 9e-7, 1e-5, 5e-3), + "x": (1e-8, 9e-7, 1e-5, 5e-3), + "measure": (1e-8, 9e-7, 1e-5, 5e-3), + "delay": (None, None), + "reset": (None, None), } def add_noisy_instruction( @@ -220,9 +219,7 @@ def add_noisy_instruction( qargs = (qarg,) duration, error = ( - (None, None) - if noise_params is None - else noise_params + noise_params if len(noise_params) == 2 else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) ) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 354d9dfff457..58318715aa98 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -11,6 +11,11 @@ # that they have been altered from the originals. """ Test of FakeGeneric backend""" + +import math + +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit import transpile from qiskit.providers.fake_provider import FakeGeneric from qiskit.providers.fake_provider.fake_generic import GenericTarget from qiskit.transpiler import CouplingMap @@ -100,3 +105,22 @@ def test_default_coupling_map(self): ), reference_cmap, ) + + def test_run(self): + """Test run method, confirm correct noisy simulation if Aer is installed.""" + + qr = QuantumRegister(5) + cr = ClassicalRegister(5) + qc = QuantumCircuit(qr, cr) + qc.h(qr[0]) + for k in range(1, 4): + qc.cx(qr[0], qr[k]) + qc.measure(qr, cr) + + backend = FakeGeneric(num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"]) + tqc = transpile(qc, backend=backend, optimization_level=3, seed_transpiler=42) + result = backend.run(tqc, seed_simulator=42, shots=1000).result() + counts = result.get_counts() + + self.assertTrue(math.isclose(counts["00000"], 500, rel_tol=0.1)) + self.assertTrue(math.isclose(counts["01111"], 500, rel_tol=0.1)) From 01343ada2a05f85457037c393b30541a3a2769f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 7 Dec 2023 11:52:46 +0100 Subject: [PATCH 32/56] Add CZ to basis gates, dtm, update backend names, fix docs --- .../providers/fake_provider/fake_generic.py | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 33baaad4fc37..f66bde99031a 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -29,7 +29,7 @@ BreakLoopOp, ContinueLoopOp, ) -from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate +from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate, CZGate from qiskit.exceptions import QiskitError from qiskit.transpiler import CouplingMap, Target, InstructionProperties, QubitProperties from qiskit.providers import Options @@ -102,7 +102,7 @@ def __init__( # Hardcoded default target attributes. To modify, # access corresponding properties through the public `Target` API super().__init__( - description="Generic Target", + description=f"Generic Target with {num_qubits} qubits", num_qubits=num_qubits, dt=0.222e-9, qubit_properties=[ @@ -162,6 +162,7 @@ def supported_operations(self) -> dict[str, Instruction]: return { "cx": CXGate(), "ecr": ECRGate(), + "cz": CZGate(), "id": IGate(), "rz": RZGate(Parameter("theta")), "sx": SXGate(), @@ -186,6 +187,7 @@ def noise_defaults(self) -> dict[str, tuple | None]: return { "cx": (1e-8, 9e-7, 1e-5, 5e-3), "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "cz": (1e-8, 9e-7, 1e-5, 5e-3), "id": (3e-8, 4e-8, 9e-5, 1e-4), "rz": (0.0, 0.0), "sx": (1e-8, 9e-7, 1e-5, 5e-3), @@ -201,7 +203,7 @@ def add_noisy_instruction( """Add instruction properties to target for specified instruction. Args: - instruction (Instruction): Instance of instruction to be added to the target + instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target noise_params (tuple[float, ...] | None): error and duration noise values/ranges to include in instruction properties. @@ -365,6 +367,10 @@ class FakeGeneric(BackendV2): (with a fixable ``seed``) driven from a series of optional input arguments. """ + # Added backend_name for compatibility with + # snapshot-based fake backends. + backend_name = "FakeGeneric" + def __init__( self, num_qubits: int, @@ -373,6 +379,7 @@ def __init__( coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: list[str] | None = None, + dtm: float = None, seed: int = 42, ): """ @@ -416,16 +423,20 @@ def __init__( instructions are provided, the target generator will append empty calibration schedules by default. + dtm (float): System time resolution of output signals in nanoseconds. + None by default. + seed (int): Optional seed for generation of default values. """ super().__init__( provider=None, - name="fake_generic", + name=f"fake_generic_{num_qubits}q", description=f"This is a fake device with {num_qubits} " f"and generic settings.", backend_version="", ) self._rng = np.random.default_rng(seed=seed) + self._dtm = dtm # the coupling map is necessary to build the default channels if coupling_map is None: @@ -456,6 +467,19 @@ def target(self): def max_circuits(self): return None + @property + def dtm(self) -> float: + """Return the system time resolution of output signals + + Returns: + The output signal timestep in seconds. + """ + if self._dtm is not None: + # converting `dtm` in nanoseconds in configuration file to seconds + return self._dtm * 1e-9 + else: + return None + @property def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements From 982deb763795f9d2e0e8af26868fb2105318f32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 17 Jan 2024 19:31:44 +0100 Subject: [PATCH 33/56] Remove GenericTarget, add default for basis_gates, remove supported_operations. --- qiskit/providers/fake_provider/__init__.py | 2 +- .../providers/fake_provider/fake_generic.py | 511 ++++++++---------- test/python/compiler/test_transpiler.py | 93 ++-- .../fake_provider/test_fake_generic.py | 38 +- test/python/pulse/test_macros.py | 4 +- 5 files changed, 265 insertions(+), 383 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 6075ec84be03..ff37b8084b81 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -266,4 +266,4 @@ # Configurable fake backend from .utils.configurable_backend import ConfigurableFakeBackend -from .fake_generic import FakeGeneric, GenericTarget +from .fake_generic import FakeGeneric diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index f66bde99031a..0c3b2290ab9f 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -29,7 +29,7 @@ BreakLoopOp, ContinueLoopOp, ) -from qiskit.circuit.library import XGate, RZGate, SXGate, CXGate, ECRGate, IGate, CZGate +from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from qiskit.exceptions import QiskitError from qiskit.transpiler import CouplingMap, Target, InstructionProperties, QubitProperties from qiskit.providers import Options @@ -43,134 +43,133 @@ from qiskit.utils import optionals as _optionals -class GenericTarget(Target): +class FakeGeneric(BackendV2): """ - This class will generate a :class:`~.Target` instance with - default qubit, instruction and calibration properties. - A target object represents the minimum set of information - the transpiler needs from a backend. + Configurable fake :class:`~.BackendV2` generator. This class will + generate a fake backend from a combination of generated defaults + (with a fixable ``seed``) driven from a series of optional input arguments. """ + # Added backend_name for compatibility with + # snapshot-based fake backends. + backend_name = "FakeGeneric" + def __init__( self, num_qubits: int, - basis_gates: list[str], - coupling_map: CouplingMap, + basis_gates: list[str] | None = None, + *, + coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: list[str] | None = None, - rng: np.random.Generator = None, + dtm: float = None, + seed: int = 42, ): """ Args: - num_qubits (int): Number of qubits in the target. + num_qubits: Number of qubits that will + be used to construct the backend's target. Note that, while + there is no limit in the size of the target that can be + constructed, fake backends run on local noisy simulators, + and these might show limitations in the number of qubits that + can be simulated. - basis_gates (list[str]): List of basis gate names to be supported by - the target. These must be within the supported operations of this - target generator, which can be consulted through the - ``supported_operations`` property. - Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` - and ``{"ecr", "id", "rz", "sx", "x"}``. The ``"reset"``, ``"`delay"`` - and ``"measure"`` instructions are supported by default, even if - not specified via ``basis_gates``. + basis_gates: List of basis gate names to be supported by + the target. These must be part of the standard qiskit circuit library. + The default set of basis gates is ``["id", "rz", "sx", "x", "cx"]`` + The ``"reset"``, ``"delay"``, and ``"measure"`` instructions are + always supported by default, even if not specified via ``basis_gates``. - coupling_map (CouplingMap): Target's coupling map as an instance of - :class:`~.CouplingMap`. + coupling_map: Optional coupling map + for the fake backend. Multiple formats are supported: - control_flow (bool): Flag to enable control flow directives on the target + #. :class:`~.CouplingMap` instance + #. List, must be given as an adjacency matrix, where each entry + specifies all directed two-qubit interactions supported by the backend, + e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` + + If ``coupling_map`` is specified, it must match the number of qubits + specified in ``num_qubits``. If ``coupling_map`` is not specified, + a fully connected coupling map will be generated with ``num_qubits`` + qubits. + + control_flow: Flag to enable control flow directives on the target (defaults to False). - calibrate_instructions (list[str] | None): List of instruction names - to add default calibration entries to. These must be within the - supported operations of this target generator, which can be - consulted through the ``supported_operations`` property. If no + calibrate_instructions: List of instruction names + to add default calibration entries to. These must be part of the + standard qiskit circuit library. If no instructions are provided, the target generator will append empty calibration schedules by default. - rng (np.random.Generator): Optional fixed-seed generator for default random values. + dtm: System time resolution of output signals in nanoseconds. + None by default. + + seed: Optional seed for generation of default values. """ - self._rng = rng if rng else np.random.default_rng(seed=42) - if num_qubits != coupling_map.size(): - raise QiskitError( - f"The number of qubits (got {num_qubits}) must match " - f"the size of the provided coupling map (got {coupling_map.size()})." - ) + super().__init__( + provider=None, + name=f"fake_generic_{num_qubits}q", + description=f"This is a fake device with {num_qubits} " f"and generic settings.", + backend_version="", + ) + self.sim = None + self._rng = np.random.default_rng(seed=seed) + self._dtm = dtm self._num_qubits = num_qubits - self._coupling_map = coupling_map + self._control_flow = control_flow + self._calibrate_instructions = calibrate_instructions + self._noise_defaults = None + self._supported_gates = get_standard_gate_name_mapping() - # Hardcoded default target attributes. To modify, - # access corresponding properties through the public `Target` API - super().__init__( - description=f"Generic Target with {num_qubits} qubits", - num_qubits=num_qubits, - dt=0.222e-9, - qubit_properties=[ - QubitProperties( - t1=self._rng.uniform(100e-6, 200e-6), - t2=self._rng.uniform(100e-6, 200e-6), - frequency=self._rng.uniform(5e9, 5.5e9), + if coupling_map is None: + self._coupling_map = CouplingMap().from_full(num_qubits) + else: + if isinstance(coupling_map, CouplingMap): + self._coupling_map = coupling_map + else: + self._coupling_map = CouplingMap(coupling_map) + + if num_qubits != self._coupling_map.size(): + raise QiskitError( + f"The number of qubits (got {num_qubits}) must match " + f"the size of the provided coupling map (got {coupling_map.size()})." ) - for _ in range(num_qubits) - ], - concurrent_measurements=[list(range(num_qubits))], - ) - # Ensure that Reset, Delay and Measure are in - # self._basis_gates - # so that their instructions are added to the target. - self._basis_gates = basis_gates + self._basis_gates = basis_gates if basis_gates else ["cx", "id", "rz", "sx", "x"] for name in ["reset", "delay", "measure"]: if name not in self._basis_gates: self._basis_gates.append(name) - # Iterate over gates, generate noise params from defaults, - # and add instructions to target. - for name in self._basis_gates: - if name not in self.supported_operations: - raise QiskitError( - f"Provided base gate {name} is not a supported " - f"operation ({self.supported_operations.keys()})." - ) - gate = self.supported_operations[name] - noise_params = self.noise_defaults[name] - self.add_noisy_instruction(gate, noise_params) + self._build_generic_target() + self._build_default_channels() - if control_flow: - self.add_instruction(IfElseOp, name="if_else") - self.add_instruction(WhileLoopOp, name="while_loop") - self.add_instruction(ForLoopOp, name="for_loop") - self.add_instruction(SwitchCaseOp, name="switch_case") - self.add_instruction(BreakLoopOp, name="break") - self.add_instruction(ContinueLoopOp, name="continue") + @property + def target(self): + return self._target - # Generate block of calibration defaults and add to target. - # Note: this could be improved if we could generate and add - # calibration defaults per-gate, and not as a block. - defaults = self._generate_calibration_defaults(calibrate_instructions) - inst_map = defaults.instruction_schedule_map - self.add_calibrations_from_instruction_schedule_map(inst_map) + @property + def max_circuits(self): + return None @property - def supported_operations(self) -> dict[str, Instruction]: - """Mapping of names to class instances for operations supported - in ``basis_gates``. + def dtm(self) -> float: + """Return the system time resolution of output signals Returns: - Dictionary mapping operation names to class instances. + The output signal timestep in seconds. """ - return { - "cx": CXGate(), - "ecr": ECRGate(), - "cz": CZGate(), - "id": IGate(), - "rz": RZGate(Parameter("theta")), - "sx": SXGate(), - "x": XGate(), - "measure": Measure(), - "delay": Delay(Parameter("Time")), - "reset": Reset(), - } + if self._dtm is not None: + # converting `dtm` in nanoseconds in configuration file to seconds + return self._dtm * 1e-9 + else: + return None + + @property + def meas_map(self) -> list[list[int]]: + return self._target.concurrent_measurements @property def noise_defaults(self) -> dict[str, tuple | None]: @@ -184,20 +183,30 @@ def noise_defaults(self) -> dict[str, tuple | None]: Returns: Dictionary mapping instruction names to noise defaults """ - return { - "cx": (1e-8, 9e-7, 1e-5, 5e-3), - "ecr": (1e-8, 9e-7, 1e-5, 5e-3), - "cz": (1e-8, 9e-7, 1e-5, 5e-3), - "id": (3e-8, 4e-8, 9e-5, 1e-4), - "rz": (0.0, 0.0), - "sx": (1e-8, 9e-7, 1e-5, 5e-3), - "x": (1e-8, 9e-7, 1e-5, 5e-3), - "measure": (1e-8, 9e-7, 1e-5, 5e-3), - "delay": (None, None), - "reset": (None, None), - } - def add_noisy_instruction( + if self._noise_defaults is None: + # gates with known noise approximations + default_dict = { + "cx": (1e-8, 9e-7, 1e-5, 5e-3), + "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "cz": (1e-8, 9e-7, 1e-5, 5e-3), + "id": (3e-8, 4e-8, 9e-5, 1e-4), + "rz": (0.0, 0.0), + "sx": (1e-8, 9e-7, 1e-5, 5e-3), + "x": (1e-8, 9e-7, 1e-5, 5e-3), + "measure": (1e-8, 9e-7, 1e-5, 5e-3), + "delay": (None, None), + "reset": (None, None), + } + # add values for rest of the gates (untested) + for gate in self._supported_gates: + if gate not in default_dict: + default_dict[gate] = (1e-8, 9e-7, 1e-5, 5e-3) + self._noise_defaults = default_dict + + return self._noise_defaults + + def add_noisy_instruction_to_target( self, instruction: Instruction, noise_params: tuple[float, ...] | None ) -> None: """Add instruction properties to target for specified instruction. @@ -210,67 +219,73 @@ def add_noisy_instruction( Returns: None """ - qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) props = {} - for qarg in qarg_set: try: qargs = tuple(qarg) except TypeError: qargs = (qarg,) - duration, error = ( noise_params if len(noise_params) == 2 else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) ) - props.update({qargs: InstructionProperties(duration, error)}) - self.add_instruction(instruction, props) + self._target.add_instruction(instruction, props) - def add_calibrations_from_instruction_schedule_map( - self, inst_map: InstructionScheduleMap - ) -> None: - """Add calibration entries from provided pulse defaults to target. + def _build_generic_target(self): + """ + This method generates a :class:`~.Target` instance with + default qubit, instruction and calibration properties. + """ + # Hardcoded default target attributes. + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=0.222e-9, + qubit_properties=[ + QubitProperties( + t1=self._rng.uniform(100e-6, 200e-6), + t2=self._rng.uniform(100e-6, 200e-6), + frequency=self._rng.uniform(5e9, 5.5e9), + ) + for _ in range(self._num_qubits) + ], + concurrent_measurements=[list(range(self._num_qubits))], + ) - Args: - inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map + # Iterate over gates, generate noise params from defaults, + # and add instructions to target. + for name in self._basis_gates: + if name not in self._supported_gates: + raise QiskitError( + f"Provided basis gate {name} is not an instruction " + f"in the standard qiskit circuit library." + ) + gate = self._supported_gates[name] + noise_params = self.noise_defaults[name] + self.add_noisy_instruction_to_target(gate, noise_params) - Returns: - None - """ + if self._control_flow: + self._target.add_instruction(IfElseOp, name="if_else") + self._target.add_instruction(WhileLoopOp, name="while_loop") + self._target.add_instruction(ForLoopOp, name="for_loop") + self._target.add_instruction(SwitchCaseOp, name="switch_case") + self._target.add_instruction(BreakLoopOp, name="break") + self._target.add_instruction(ContinueLoopOp, name="continue") - # The calibration entries are directly injected into the gate map to - # avoid then being labeled as "user_provided". - for inst in inst_map.instructions: - for qarg in inst_map.qubits_with_instruction(inst): - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - # Do NOT call .get method. This parses Qobj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = inst_map._get_calibration_entry(inst, qargs) - if inst in self._gate_map: - if inst == "measure": - for qubit in qargs: - self._gate_map[inst][(qubit,)].calibration = calibration_entry - elif qargs in self._gate_map[inst] and inst not in ["delay", "reset"]: - self._gate_map[inst][qargs].calibration = calibration_entry + # Generate block of calibration defaults and add to target. + # Note: this could be improved if we could generate and add + # calibration defaults per-gate, and not as a block. + defaults = self._generate_calibration_defaults() + inst_map = defaults.instruction_schedule_map + self.add_calibrations_to_target(inst_map) - def _generate_calibration_defaults( - self, calibrate_instructions: list[str] | None - ) -> PulseDefaults: + def _generate_calibration_defaults(self) -> PulseDefaults: """Generate calibration defaults for instructions specified via ``calibrate_instructions``. By default, this method generates empty calibration schedules. - - Args: - calibrate_instructions (list[str]): list of instructions to be calibrated. - - Returns: - Corresponding PulseDefaults """ # The number of samples determines the pulse durations of the corresponding @@ -292,7 +307,7 @@ def _generate_calibration_defaults( # Unless explicitly given a series of gates to calibrate, this method # will generate empty pulse schedules for all gates in self._basis_gates. - calibrate_instructions = calibrate_instructions or [] + calibrate_instructions = self._calibrate_instructions or [] calibration_buffer = self._basis_gates.copy() for inst in ["delay", "reset"]: calibration_buffer.remove(inst) @@ -302,8 +317,8 @@ def _generate_calibration_defaults( # are different for 1q gates vs 2q gates vs measurement instructions. cmd_def = [] for inst in calibration_buffer: - num_qubits = self.supported_operations[inst].num_qubits - qarg_set = self._coupling_map if num_qubits > 1 else list(range(self._num_qubits)) + num_qubits = self._supported_gates[inst].num_qubits + qarg_set = self._coupling_map if num_qubits > 1 else list(range(self.num_qubits)) if inst == "measure": sequence = [] qubits = qarg_set @@ -359,158 +374,33 @@ def _generate_calibration_defaults( cmd_def=cmd_def, ) + def add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: + """Add calibration entries from provided pulse defaults to target. -class FakeGeneric(BackendV2): - """ - Configurable fake :class:`~.BackendV2` generator. This class will - generate a fake backend from a combination of generated defaults - (with a fixable ``seed``) driven from a series of optional input arguments. - """ - - # Added backend_name for compatibility with - # snapshot-based fake backends. - backend_name = "FakeGeneric" - - def __init__( - self, - num_qubits: int, - basis_gates: list[str], - *, - coupling_map: list[list[int]] | CouplingMap | None = None, - control_flow: bool = False, - calibrate_instructions: list[str] | None = None, - dtm: float = None, - seed: int = 42, - ): - """ Args: - num_qubits (int): Number of qubits that will - be used to construct the backend's target. Note that, while - there is no limit in the size of the target that can be - constructed, fake backends run on local noisy simulators, - and these might show limitations in the number of qubits that - can be simulated. - - basis_gates (list[str]): List of basis gate names to be supported by - the target. These must be within the supported operations of the - target generator, which can be consulted through its - ``supported_operations`` property. - Common sets of basis gates are ``{"cx", "id", "rz", "sx", "x"}`` - and ``{"ecr", "id", "rz", "sx", "x"}``. The ``"reset"``, ``"`delay"``, - and ``"measure"`` instructions are supported by default, even if - not specified via ``basis_gates``. - - coupling_map (list[list[int]] | CouplingMap | None): Optional coupling map - for the fake backend. Multiple formats are supported: - - #. :class:`~.CouplingMap` instance - #. List, must be given as an adjacency matrix, where each entry - specifies all directed two-qubit interactions supported by the backend, - e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` - - If ``coupling_map`` is specified, it must match the number of qubits - specified in ``num_qubits``. If ``coupling_map`` is not specified, - a fully connected coupling map will be generated with ``num_qubits`` - qubits. - - control_flow (bool): Flag to enable control flow directives on the target - (defaults to False). - - calibrate_instructions (list[str] | None): List of instruction names - to add default calibration entries to. These must be within the - supported operations of the target generator, which can be - consulted through the ``supported_operations`` property. If no - instructions are provided, the target generator will append - empty calibration schedules by default. - - dtm (float): System time resolution of output signals in nanoseconds. - None by default. - - seed (int): Optional seed for generation of default values. - """ - - super().__init__( - provider=None, - name=f"fake_generic_{num_qubits}q", - description=f"This is a fake device with {num_qubits} " f"and generic settings.", - backend_version="", - ) - self._rng = np.random.default_rng(seed=seed) - self._dtm = dtm - - # the coupling map is necessary to build the default channels - if coupling_map is None: - self._coupling_map = CouplingMap().from_full(num_qubits) - else: - if isinstance(coupling_map, CouplingMap): - self._coupling_map = coupling_map - else: - self._coupling_map = CouplingMap(coupling_map) - - self._target = GenericTarget( - num_qubits, - basis_gates, - self._coupling_map, - control_flow, - calibrate_instructions, - self._rng, - ) - - self._build_default_channels() - self.sim = None - - @property - def target(self): - return self._target - - @property - def max_circuits(self): - return None - - @property - def dtm(self) -> float: - """Return the system time resolution of output signals + inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map Returns: - The output signal timestep in seconds. + None """ - if self._dtm is not None: - # converting `dtm` in nanoseconds in configuration file to seconds - return self._dtm * 1e-9 - else: - return None - @property - def meas_map(self) -> list[list[int]]: - return self._target.concurrent_measurements - - def drive_channel(self, qubit: int): - drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) - qubits = (qubit,) - if qubits in drive_channels_map: - return drive_channels_map[qubits][0] - return None - - def measure_channel(self, qubit: int): - measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) - qubits = (qubit,) - if qubits in measure_channels_map: - return measure_channels_map[qubits][0] - return None - - def acquire_channel(self, qubit: int): - acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) - qubits = (qubit,) - if qubits in acquire_channels_map: - return acquire_channels_map[qubits][0] - return None - - def control_channel(self, qubits: Iterable[int]): - control_channels_map = getattr(self, "channels_map", {}).get("control", {}) - qubits = tuple(qubits) - if qubits in control_channels_map: - return control_channels_map[qubits] - return [] + # The calibration entries are directly injected into the gate map to + # avoid then being labeled as "user_provided". + for inst in inst_map.instructions: + for qarg in inst_map.qubits_with_instruction(inst): + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + # Do NOT call .get method. This parses Qobj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = inst_map._get_calibration_entry(inst, qargs) + if inst in self._target._gate_map: + if inst == "measure": + for qubit in qargs: + self._target._gate_map[inst][(qubit,)].calibration = calibration_entry + elif qargs in self._target._gate_map[inst] and inst not in ["delay", "reset"]: + self._target._gate_map[inst][qargs].calibration = calibration_entry def run(self, run_input, **options): """Run on the fake backend using a simulator. @@ -544,7 +434,6 @@ def run(self, run_input, **options): Raises: QiskitError: If a pulse job is supplied and qiskit_aer is not installed. """ - circuits = run_input pulse_job = None if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): @@ -574,7 +463,6 @@ def run(self, run_input, **options): return job def _setup_sim(self) -> None: - if _optionals.HAS_AER: from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel @@ -585,13 +473,11 @@ def _setup_sim(self) -> None: # Update fake backend default too to avoid overwriting # it when run() is called self.set_options(noise_model=noise_model) - else: self.sim = BasicAer.get_backend("qasm_simulator") @classmethod def _default_options(cls) -> Options: - if _optionals.HAS_AER: from qiskit_aer import AerSimulator @@ -600,7 +486,6 @@ def _default_options(cls) -> Options: return BasicAer.get_backend("qasm_simulator")._default_options() def _build_default_channels(self) -> None: - channels_map = { "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, @@ -610,3 +495,31 @@ def _build_default_channels(self) -> None: }, } setattr(self, "channels_map", channels_map) + + def drive_channel(self, qubit: int): + drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) + qubits = (qubit,) + if qubits in drive_channels_map: + return drive_channels_map[qubits][0] + return None + + def measure_channel(self, qubit: int): + measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) + qubits = (qubit,) + if qubits in measure_channels_map: + return measure_channels_map[qubits][0] + return None + + def acquire_channel(self, qubit: int): + acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) + qubits = (qubit,) + if qubits in acquire_channels_map: + return acquire_channels_map[qubits][0] + return None + + def control_channel(self, qubits: Iterable[int]): + control_channels_map = getattr(self, "channels_map", {}).get("control", {}) + qubits = tuple(qubits) + if qubits in control_channels_map: + return control_channels_map[qubits] + return [] diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index e7e24fdc3ee8..8a93060a7dd5 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -327,9 +327,7 @@ def test_already_mapped_1(self): [15, 14], ] - backend = FakeGeneric( - num_qubits=16, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=cmap - ) + backend = FakeGeneric(num_qubits=16, coupling_map=cmap) coupling_map = backend.coupling_map basis_gates = backend.operation_names @@ -595,7 +593,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=12) + backend = FakeGeneric(num_qubits=12) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -710,7 +708,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8) + backend = FakeGeneric(num_qubits=8) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -727,7 +725,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -743,7 +741,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -782,7 +780,7 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, @@ -821,7 +819,7 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) transpiled_qc = transpile( qc, @@ -878,9 +876,7 @@ def test_do_not_run_gatedirection_with_symmetric_cm(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) coupling_map = [] - for node1, node2 in FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16 - ).coupling_map: + for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: coupling_map.append([node1, node2]) coupling_map.append([node2, node1]) @@ -939,7 +935,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - cmap = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16).coupling_map + cmap = FakeGeneric(num_qubits=16).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -982,9 +978,7 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile( - qc, backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) - ) + out = transpile(qc, backend=FakeGeneric(num_qubits=4)) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1285,7 +1279,7 @@ def test_transpiled_custom_gates_calibration(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1305,7 +1299,7 @@ def test_transpiled_basis_gates_calibrations(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1325,7 +1319,7 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): with self.assertRaises(QiskitError): transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) @@ -1344,7 +1338,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1367,7 +1361,7 @@ def test_transpile_subset_of_calibrated_gates(self): transpiled_circ = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) @@ -1388,14 +1382,14 @@ def q0_rxt(tau): transpiled_circ = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) transpiled_circ = transpile( circ, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) @@ -1424,7 +1418,7 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6) + backend = FakeGeneric(num_qubits=6) with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( @@ -1484,7 +1478,7 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) backend.target.dt = 0.5e-6 out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") @@ -1502,7 +1496,7 @@ def test_scheduling_backend_v2(self): out = transpile( [qc, qc], - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4), + backend=FakeGeneric(num_qubits=4), scheduling_method="alap", ) self.assertIn("delay", out[0].count_ops()) @@ -1584,7 +1578,7 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) out = transpile(qc, backend=backend, optimization_level=optimization_level) @@ -1709,9 +1703,7 @@ def test_target_ideal_gates(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" - target = FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True - ).target + target = FakeGeneric(num_qubits=8, control_flow=True).target circuit = QuantumCircuit(6, 1) circuit.h(0) @@ -2002,7 +1994,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8), + backend=FakeGeneric(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2019,7 +2011,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8), + backend=FakeGeneric(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2043,9 +2035,7 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): "See #10345 for more details." ) - backend = FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True - ) + backend = FakeGeneric(num_qubits=8, control_flow=True) transpiled = transpile( self._control_flow_circuit(), backend=backend, @@ -2067,9 +2057,7 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): through QPY.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True - ), + backend=FakeGeneric(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2090,7 +2078,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): "This test case triggers a bug in the eigensolver routine on windows. " "See #10345 for more details." ) - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=16) + backend = FakeGeneric(num_qubits=16) transpiled = transpile( self._control_flow_expr_circuit(), backend=backend, @@ -2109,7 +2097,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow including `Expr` nodes can be round-tripped through QPY.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27) + backend = FakeGeneric(num_qubits=27) backend.target.add_instruction(IfElseOp, name="if_else") backend.target.add_instruction(ForLoopOp, name="for_loop") backend.target.add_instruction(WhileLoopOp, name="while_loop") @@ -2146,9 +2134,7 @@ def test_qasm3_output_control_flow(self, optimization_level): OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=8, control_flow=True - ), + backend=FakeGeneric(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2166,9 +2152,7 @@ def test_qasm3_output_control_flow_expr(self, optimization_level): dumped into OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, control_flow=True - ), + backend=FakeGeneric(num_qubits=27, control_flow=True), optimization_level=optimization_level, seed_transpiler=2023_07_26, ) @@ -2321,9 +2305,7 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - pm = generate_preset_pass_manager( - opt_level, backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) - ) + pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=4)) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -2335,7 +2317,7 @@ def test_parallel_with_target(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4).target + target = FakeGeneric(num_qubits=4).target res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: @@ -2407,7 +2389,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): """Test that singleton mutable instance doesn't lose state in parallel.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27) + backend = FakeGeneric(num_qubits=27) circ = QuantumCircuit(2, 1) circ.h(0) circ.measure(0, circ.clbits[0]) @@ -2421,7 +2403,7 @@ def test_parallel_singleton_conditional_gate(self, opt_level): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6) + backend = FakeGeneric(num_qubits=6) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2458,7 +2440,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=6), + backend=FakeGeneric(num_qubits=6), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2473,7 +2455,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4) + backend = FakeGeneric(num_qubits=4) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2497,7 +2479,6 @@ def test_transpile_with_multiple_coupling_maps(self): def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" backend = FakeGeneric( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], ) @@ -3383,7 +3364,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = FakeGeneric(basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=130) + backend = FakeGeneric(num_qubits=130) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 58318715aa98..4d050531a8ec 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -17,35 +17,34 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit import transpile from qiskit.providers.fake_provider import FakeGeneric -from qiskit.providers.fake_provider.fake_generic import GenericTarget from qiskit.transpiler import CouplingMap from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase -class TestGenericTarget(QiskitTestCase): - """Test class for GenericTarget""" +class TestFakeGeneric(QiskitTestCase): + """Test class for FakeGeneric backend""" def setUp(self): super().setUp() self.cmap = CouplingMap( [(0, 2), (0, 1), (1, 3), (2, 4), (2, 3), (3, 5), (4, 6), (4, 5), (5, 7), (6, 7)] ) - self.basis_gates = ["cx", "id", "rz", "sx", "x"] def test_supported_basis_gates(self): """Test that target raises error if basis_gate not in ``supported_names``.""" with self.assertRaises(QiskitError): - GenericTarget( - num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"], coupling_map=self.cmap - ) + FakeGeneric(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" - target = GenericTarget( - num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=self.cmap - ) + target = FakeGeneric(num_qubits=8) + op_names = list(target.operation_names) + op_names.sort() + self.assertEqual(op_names, ["cx", "delay", "id", "measure", "reset", "rz", "sx", "x"]) + + target = FakeGeneric(num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"]) op_names = list(target.operation_names) op_names.sort() self.assertEqual(op_names, ["delay", "ecr", "id", "measure", "reset", "rz", "sx", "x"]) @@ -53,18 +52,16 @@ def test_operation_names(self): def test_incompatible_coupling_map(self): """Test that the size of the coupling map must match num_qubits.""" with self.assertRaises(QiskitError): - FakeGeneric( - num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=self.cmap - ) + FakeGeneric(num_qubits=5, coupling_map=self.cmap) def test_control_flow_operation_names(self): """Test that control flow instructions are added to the target if control_flow is True.""" - target = GenericTarget( + target = FakeGeneric( num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=self.cmap, control_flow=True, - ) + ).target op_names = list(target.operation_names) op_names.sort() reference = [ @@ -85,10 +82,6 @@ def test_control_flow_operation_names(self): ] self.assertEqual(op_names, reference) - -class TestFakeGeneric(QiskitTestCase): - """Test class for FakeGeneric backend""" - def test_default_coupling_map(self): """Test that fully-connected coupling map is generated correctly.""" @@ -96,13 +89,8 @@ def test_default_coupling_map(self): reference_cmap = [(0, 1), (1, 0), (0, 2), (2, 0), (0, 3), (3, 0), (0, 4), (4, 0), (1, 2), (2, 1), (1, 3), (3, 1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3)] # fmt: on - self.assertEqual( - list( - FakeGeneric( - num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"] - ).coupling_map.get_edges() - ), + list(FakeGeneric(num_qubits=5).coupling_map.get_edges()), reference_cmap, ) diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index da6cdd2a2335..91e36197478d 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -41,7 +41,7 @@ def setUp(self): basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, ) - self.backend_v2.target.add_calibrations_from_instruction_schedule_map( + self.backend_v2.add_calibrations_to_target( self.backend_v1.defaults().instruction_schedule_map ) @@ -220,7 +220,7 @@ def setUp(self): num_qubits=2, ) self.inst_map = self.backend_v1.defaults().instruction_schedule_map - self.backend_v2.target.add_calibrations_from_instruction_schedule_map( + self.backend_v2.add_calibrations_to_target( self.backend_v1.defaults().instruction_schedule_map ) From 09bc015f6b97618b5a8e438e5167f7e4862bbfbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 22 Jan 2024 15:43:18 +0100 Subject: [PATCH 34/56] Apply review comments, modify calibrate_instructions to avoid public calibration method. --- .../providers/fake_provider/fake_generic.py | 115 ++++++++++-------- test/python/pulse/test_macros.py | 10 +- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 0c3b2290ab9f..e2a46bf1c1c5 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -20,7 +20,7 @@ from qiskit import pulse from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.circuit import Measure, Parameter, Delay, Reset, QuantumCircuit, Instruction +from qiskit.circuit import QuantumCircuit, Instruction from qiskit.circuit.controlflow import ( IfElseOp, WhileLoopOp, @@ -45,9 +45,9 @@ class FakeGeneric(BackendV2): """ - Configurable fake :class:`~.BackendV2` generator. This class will - generate a fake backend from a combination of generated defaults - (with a fixable ``seed``) driven from a series of optional input arguments. + Configurable :class:`~.BackendV2` generator. This class will + generate a local (fake) backend from a combination of generated defaults + (with a fixable ``seed``) and a series of input arguments. """ # Added backend_name for compatibility with @@ -61,7 +61,7 @@ def __init__( *, coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, - calibrate_instructions: list[str] | None = None, + calibrate_instructions: bool | InstructionScheduleMap | None = None, dtm: float = None, seed: int = 42, ): @@ -96,11 +96,19 @@ def __init__( control_flow: Flag to enable control flow directives on the target (defaults to False). - calibrate_instructions: List of instruction names - to add default calibration entries to. These must be part of the - standard qiskit circuit library. If no - instructions are provided, the target generator will append - empty calibration schedules by default. + calibrate_instructions: Instruction calibration settings, this argument + supports both boolean and :class:`.InstructionScheduleMap` as + input types, and is ``None`` by default: + + #. If ``calibrate_instructions==None``, no calibrations will be added to the + target. + #. If ``calibrate_instructions==True``, all gates will be calibrated for all + qubits using the default pulse schedules generated internally. + #. If ``calibrate_instructions==False``, all gates will be "calibrated" for + all qubits with an empty pulse schedule. + #. If an :class:`.InstructionScheduleMap` instance is given, this calibrations + present in this instruction schedule map will be appended to the target + instead of the default pulse schedules (this allows for custom calibrations). dtm: System time resolution of output signals in nanoseconds. None by default. @@ -111,7 +119,7 @@ def __init__( super().__init__( provider=None, name=f"fake_generic_{num_qubits}q", - description=f"This is a fake device with {num_qubits} " f"and generic settings.", + description=f"This is a fake device with {num_qubits} qubits and generic settings.", backend_version="", ) @@ -138,7 +146,9 @@ def __init__( f"the size of the provided coupling map (got {coupling_map.size()})." ) - self._basis_gates = basis_gates if basis_gates else ["cx", "id", "rz", "sx", "x"] + self._basis_gates = ( + basis_gates if basis_gates is not None else ["cx", "id", "rz", "sx", "x"] + ) for name in ["reset", "delay", "measure"]: if name not in self._basis_gates: self._basis_gates.append(name) @@ -162,7 +172,7 @@ def dtm(self) -> float: The output signal timestep in seconds. """ if self._dtm is not None: - # converting `dtm` in nanoseconds in configuration file to seconds + # converting `dtm` from nanoseconds to seconds return self._dtm * 1e-9 else: return None @@ -206,41 +216,12 @@ def noise_defaults(self) -> dict[str, tuple | None]: return self._noise_defaults - def add_noisy_instruction_to_target( - self, instruction: Instruction, noise_params: tuple[float, ...] | None - ) -> None: - """Add instruction properties to target for specified instruction. - - Args: - instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target - noise_params (tuple[float, ...] | None): error and duration noise values/ranges to - include in instruction properties. - - Returns: - None - """ - qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) - props = {} - for qarg in qarg_set: - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - duration, error = ( - noise_params - if len(noise_params) == 2 - else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) - ) - props.update({qargs: InstructionProperties(duration, error)}) - - self._target.add_instruction(instruction, props) - def _build_generic_target(self): """ This method generates a :class:`~.Target` instance with default qubit, instruction and calibration properties. """ - # Hardcoded default target attributes. + # the qubit properties are currently not configurable. self._target = Target( description=f"Generic Target with {self._num_qubits} qubits", num_qubits=self._num_qubits, @@ -266,7 +247,7 @@ def _build_generic_target(self): ) gate = self._supported_gates[name] noise_params = self.noise_defaults[name] - self.add_noisy_instruction_to_target(gate, noise_params) + self._add_noisy_instruction_to_target(gate, noise_params) if self._control_flow: self._target.add_instruction(IfElseOp, name="if_else") @@ -279,9 +260,42 @@ def _build_generic_target(self): # Generate block of calibration defaults and add to target. # Note: this could be improved if we could generate and add # calibration defaults per-gate, and not as a block. - defaults = self._generate_calibration_defaults() - inst_map = defaults.instruction_schedule_map - self.add_calibrations_to_target(inst_map) + if self._calibrate_instructions is not None: + if isinstance(self._calibrate_instructions, InstructionScheduleMap): + inst_map = self._calibrate_instructions + else: + defaults = self._generate_calibration_defaults() + inst_map = defaults.instruction_schedule_map + self._add_calibrations_to_target(inst_map) + + def _add_noisy_instruction_to_target( + self, instruction: Instruction, noise_params: tuple[float, ...] | None + ) -> None: + """Add instruction properties to target for specified instruction. + + Args: + instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target + noise_params (tuple[float, ...] | None): error and duration noise values/ranges to + include in instruction properties. + + Returns: + None + """ + qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) + props = {} + for qarg in qarg_set: + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + duration, error = ( + noise_params + if len(noise_params) == 2 + else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) + ) + props.update({qargs: InstructionProperties(duration, error)}) + + self._target.add_instruction(instruction, props) def _generate_calibration_defaults(self) -> PulseDefaults: """Generate calibration defaults for instructions specified via ``calibrate_instructions``. @@ -307,7 +321,6 @@ def _generate_calibration_defaults(self) -> PulseDefaults: # Unless explicitly given a series of gates to calibrate, this method # will generate empty pulse schedules for all gates in self._basis_gates. - calibrate_instructions = self._calibrate_instructions or [] calibration_buffer = self._basis_gates.copy() for inst in ["delay", "reset"]: calibration_buffer.remove(inst) @@ -322,7 +335,7 @@ def _generate_calibration_defaults(self) -> PulseDefaults: if inst == "measure": sequence = [] qubits = qarg_set - if inst in calibrate_instructions: + if self._calibrate_instructions: sequence = [ PulseQobjInstruction( name="acquire", @@ -343,7 +356,7 @@ def _generate_calibration_defaults(self) -> PulseDefaults: for qarg in qarg_set: sequence = [] qubits = [qarg] if num_qubits == 1 else qarg - if inst in calibrate_instructions: + if self._calibrate_instructions: if num_qubits == 1: sequence = [ PulseQobjInstruction(name="fc", ch=f"u{qarg}", t0=0, phase="-P0"), @@ -374,7 +387,7 @@ def _generate_calibration_defaults(self) -> PulseDefaults: cmd_def=cmd_def, ) - def add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: + def _add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: """Add calibration entries from provided pulse defaults to target. Args: diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 91e36197478d..a8b19e6d4004 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -40,9 +40,7 @@ def setUp(self): self.backend_v2 = FakeGeneric( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, - ) - self.backend_v2.add_calibrations_to_target( - self.backend_v1.defaults().instruction_schedule_map + calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, ) def test_measure(self): @@ -215,13 +213,11 @@ class TestMeasureAll(QiskitTestCase): def setUp(self): super().setUp() self.backend_v1 = FakeOpenPulse2Q() + self.inst_map = self.backend_v1.defaults().instruction_schedule_map self.backend_v2 = FakeGeneric( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=2, - ) - self.inst_map = self.backend_v1.defaults().instruction_schedule_map - self.backend_v2.add_calibrations_to_target( - self.backend_v1.defaults().instruction_schedule_map + calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, ) def test_measure_all(self): From e0c5aa710fc1c55d8ef78f0892b9984ace69f807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 22 Jan 2024 16:34:43 +0100 Subject: [PATCH 35/56] New name proposal --- qiskit/providers/fake_provider/__init__.py | 4 +- .../providers/fake_provider/fake_generic.py | 28 +++---- test/python/compiler/test_transpiler.py | 82 +++++++++---------- .../fake_provider/test_fake_generic.py | 22 ++--- test/python/pulse/test_macros.py | 6 +- 5 files changed, 71 insertions(+), 71 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index ff37b8084b81..9d5136ce02fe 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -224,7 +224,7 @@ FakeBackend5QV2 FakeMumbaiFractionalCX ConfigurableFakeBackend - FakeGeneric + GenericFakeBackend Fake Backend Base Classes ========================= @@ -266,4 +266,4 @@ # Configurable fake backend from .utils.configurable_backend import ConfigurableFakeBackend -from .fake_generic import FakeGeneric +from .fake_generic import GenericFakeBackend diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index e2a46bf1c1c5..36a035908c36 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -43,16 +43,19 @@ from qiskit.utils import optionals as _optionals -class FakeGeneric(BackendV2): +class GenericFakeBackend(BackendV2): """ - Configurable :class:`~.BackendV2` generator. This class will - generate a local (fake) backend from a combination of generated defaults - (with a fixable ``seed``) and a series of input arguments. + Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, + locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, + ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. + The remainder of the backend properties are generated by randomly sampling from default ranges + extracted from historical IBM backend data. The seed for this random generation is always fixed + to ensure the reproducibility of the backend output, and can also be configured by the user. """ # Added backend_name for compatibility with # snapshot-based fake backends. - backend_name = "FakeGeneric" + backend_name = "GenericFakeBackend" def __init__( self, @@ -67,12 +70,10 @@ def __init__( ): """ Args: - num_qubits: Number of qubits that will - be used to construct the backend's target. Note that, while - there is no limit in the size of the target that can be - constructed, fake backends run on local noisy simulators, - and these might show limitations in the number of qubits that - can be simulated. + num_qubits: Number of qubits that will be used to construct the backend's target. + Note that, while there is no limit in the size of the target that can be + constructed, fake backends run on local noisy simulators, and these might + present limitations in the number of qubits that can be simulated. basis_gates: List of basis gate names to be supported by the target. These must be part of the standard qiskit circuit library. @@ -100,8 +101,7 @@ def __init__( supports both boolean and :class:`.InstructionScheduleMap` as input types, and is ``None`` by default: - #. If ``calibrate_instructions==None``, no calibrations will be added to the - target. + #. If ``calibrate_instructions==None``, no calibrations will be added to the target. #. If ``calibrate_instructions==True``, all gates will be calibrated for all qubits using the default pulse schedules generated internally. #. If ``calibrate_instructions==False``, all gates will be "calibrated" for @@ -427,7 +427,7 @@ def run(self, run_input, **options): noise model of the fake backend. Otherwise, jobs will be run using the ``BasicAer`` simulator without noise. - Noisy simulations of pulse jobs are not yet supported in :class:`~.FakeGeneric`. + Noisy simulations of pulse jobs are not yet supported in :class:`~.GenericFakeBackend`. Args: run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 8a93060a7dd5..240c2a8b730b 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -71,7 +71,7 @@ from qiskit.dagcircuit import DAGOpNode, DAGOutNode from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 -from qiskit.providers.fake_provider import FakeGeneric +from qiskit.providers.fake_provider import GenericFakeBackend from qiskit.providers.fake_provider import ( FakeMelbourne, FakeRueschlikon, @@ -239,7 +239,7 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - backend = FakeGeneric( + backend = GenericFakeBackend( num_qubits=15, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=cmap ) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] @@ -327,7 +327,7 @@ def test_already_mapped_1(self): [15, 14], ] - backend = FakeGeneric(num_qubits=16, coupling_map=cmap) + backend = GenericFakeBackend(num_qubits=16, coupling_map=cmap) coupling_map = backend.coupling_map basis_gates = backend.operation_names @@ -593,7 +593,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = FakeGeneric(num_qubits=12) + backend = GenericFakeBackend(num_qubits=12) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -708,7 +708,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = FakeGeneric(num_qubits=8) + backend = GenericFakeBackend(num_qubits=8) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -725,7 +725,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -741,7 +741,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -780,7 +780,7 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) transpiled_qc = transpile( qc, @@ -819,7 +819,7 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) transpiled_qc = transpile( qc, @@ -876,7 +876,7 @@ def test_do_not_run_gatedirection_with_symmetric_cm(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) coupling_map = [] - for node1, node2 in FakeGeneric(num_qubits=16).coupling_map: + for node1, node2 in GenericFakeBackend(num_qubits=16).coupling_map: coupling_map.append([node1, node2]) coupling_map.append([node2, node1]) @@ -935,7 +935,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - cmap = FakeGeneric(num_qubits=16).coupling_map + cmap = GenericFakeBackend(num_qubits=16).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -978,7 +978,7 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile(qc, backend=FakeGeneric(num_qubits=4)) + out = transpile(qc, backend=GenericFakeBackend(num_qubits=4)) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1279,7 +1279,7 @@ def test_transpiled_custom_gates_calibration(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1299,7 +1299,7 @@ def test_transpiled_basis_gates_calibrations(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1319,7 +1319,7 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): with self.assertRaises(QiskitError): transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), layout_method="trivial", ) @@ -1338,7 +1338,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): transpiled_circuit = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1361,7 +1361,7 @@ def test_transpile_subset_of_calibrated_gates(self): transpiled_circ = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) @@ -1382,14 +1382,14 @@ def q0_rxt(tau): transpiled_circ = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) transpiled_circ = transpile( circ, - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) @@ -1418,7 +1418,7 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = FakeGeneric(num_qubits=6) + backend = GenericFakeBackend(num_qubits=6) with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( @@ -1478,7 +1478,7 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) backend.target.dt = 0.5e-6 out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") @@ -1496,7 +1496,7 @@ def test_scheduling_backend_v2(self): out = transpile( [qc, qc], - backend=FakeGeneric(num_qubits=4), + backend=GenericFakeBackend(num_qubits=4), scheduling_method="alap", ) self.assertIn("delay", out[0].count_ops()) @@ -1578,7 +1578,7 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) out = transpile(qc, backend=backend, optimization_level=optimization_level) @@ -1703,7 +1703,7 @@ def test_target_ideal_gates(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" - target = FakeGeneric(num_qubits=8, control_flow=True).target + target = GenericFakeBackend(num_qubits=8, control_flow=True).target circuit = QuantumCircuit(6, 1) circuit.h(0) @@ -1994,7 +1994,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), + backend=GenericFakeBackend(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2011,7 +2011,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=FakeGeneric(num_qubits=8), + backend=GenericFakeBackend(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2035,7 +2035,7 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): "See #10345 for more details." ) - backend = FakeGeneric(num_qubits=8, control_flow=True) + backend = GenericFakeBackend(num_qubits=8, control_flow=True) transpiled = transpile( self._control_flow_circuit(), backend=backend, @@ -2057,7 +2057,7 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): through QPY.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, control_flow=True), + backend=GenericFakeBackend(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2078,7 +2078,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): "This test case triggers a bug in the eigensolver routine on windows. " "See #10345 for more details." ) - backend = FakeGeneric(num_qubits=16) + backend = GenericFakeBackend(num_qubits=16) transpiled = transpile( self._control_flow_expr_circuit(), backend=backend, @@ -2097,7 +2097,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow including `Expr` nodes can be round-tripped through QPY.""" - backend = FakeGeneric(num_qubits=27) + backend = GenericFakeBackend(num_qubits=27) backend.target.add_instruction(IfElseOp, name="if_else") backend.target.add_instruction(ForLoopOp, name="for_loop") backend.target.add_instruction(WhileLoopOp, name="while_loop") @@ -2134,7 +2134,7 @@ def test_qasm3_output_control_flow(self, optimization_level): OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=8, control_flow=True), + backend=GenericFakeBackend(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2152,7 +2152,7 @@ def test_qasm3_output_control_flow_expr(self, optimization_level): dumped into OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=FakeGeneric(num_qubits=27, control_flow=True), + backend=GenericFakeBackend(num_qubits=27, control_flow=True), optimization_level=optimization_level, seed_transpiler=2023_07_26, ) @@ -2305,7 +2305,7 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - pm = generate_preset_pass_manager(opt_level, backend=FakeGeneric(num_qubits=4)) + pm = generate_preset_pass_manager(opt_level, backend=GenericFakeBackend(num_qubits=4)) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -2317,7 +2317,7 @@ def test_parallel_with_target(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeGeneric(num_qubits=4).target + target = GenericFakeBackend(num_qubits=4).target res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: @@ -2365,7 +2365,7 @@ def run(self, dag): return dag # Remove calibration for sx gate - backend = FakeGeneric( + backend = GenericFakeBackend( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4, calibrate_instructions=["cx", "id", "rz", "x", "measure"], @@ -2389,7 +2389,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): """Test that singleton mutable instance doesn't lose state in parallel.""" - backend = FakeGeneric(num_qubits=27) + backend = GenericFakeBackend(num_qubits=27) circ = QuantumCircuit(2, 1) circ.h(0) circ.measure(0, circ.clbits[0]) @@ -2403,7 +2403,7 @@ def test_parallel_singleton_conditional_gate(self, opt_level): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = FakeGeneric(num_qubits=6) + backend = GenericFakeBackend(num_qubits=6) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2440,7 +2440,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=FakeGeneric(num_qubits=6), + backend=GenericFakeBackend(num_qubits=6), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2455,7 +2455,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = FakeGeneric(num_qubits=4) + backend = GenericFakeBackend(num_qubits=4) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2478,7 +2478,7 @@ def test_transpile_with_multiple_coupling_maps(self): @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = FakeGeneric( + backend = GenericFakeBackend( num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], ) @@ -3364,7 +3364,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = FakeGeneric(num_qubits=130) + backend = GenericFakeBackend(num_qubits=130) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 4d050531a8ec..61b5bd463c07 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -10,20 +10,20 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test of FakeGeneric backend""" +""" Test of GenericFakeBackend backend""" import math from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit import transpile -from qiskit.providers.fake_provider import FakeGeneric +from qiskit.providers.fake_provider import GenericFakeBackend from qiskit.transpiler import CouplingMap from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase -class TestFakeGeneric(QiskitTestCase): - """Test class for FakeGeneric backend""" +class TestGenericFakeBackend(QiskitTestCase): + """Test class for GenericFakeBackend backend""" def setUp(self): super().setUp() @@ -34,17 +34,17 @@ def setUp(self): def test_supported_basis_gates(self): """Test that target raises error if basis_gate not in ``supported_names``.""" with self.assertRaises(QiskitError): - FakeGeneric(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) + GenericFakeBackend(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" - target = FakeGeneric(num_qubits=8) + target = GenericFakeBackend(num_qubits=8) op_names = list(target.operation_names) op_names.sort() self.assertEqual(op_names, ["cx", "delay", "id", "measure", "reset", "rz", "sx", "x"]) - target = FakeGeneric(num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"]) + target = GenericFakeBackend(num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"]) op_names = list(target.operation_names) op_names.sort() self.assertEqual(op_names, ["delay", "ecr", "id", "measure", "reset", "rz", "sx", "x"]) @@ -52,11 +52,11 @@ def test_operation_names(self): def test_incompatible_coupling_map(self): """Test that the size of the coupling map must match num_qubits.""" with self.assertRaises(QiskitError): - FakeGeneric(num_qubits=5, coupling_map=self.cmap) + GenericFakeBackend(num_qubits=5, coupling_map=self.cmap) def test_control_flow_operation_names(self): """Test that control flow instructions are added to the target if control_flow is True.""" - target = FakeGeneric( + target = GenericFakeBackend( num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=self.cmap, @@ -90,7 +90,7 @@ def test_default_coupling_map(self): (1, 3), (3, 1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3)] # fmt: on self.assertEqual( - list(FakeGeneric(num_qubits=5).coupling_map.get_edges()), + list(GenericFakeBackend(num_qubits=5).coupling_map.get_edges()), reference_cmap, ) @@ -105,7 +105,7 @@ def test_run(self): qc.cx(qr[0], qr[k]) qc.measure(qr, cr) - backend = FakeGeneric(num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"]) + backend = GenericFakeBackend(num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"]) tqc = transpile(qc, backend=backend, optimization_level=3, seed_transpiler=42) result = backend.run(tqc, seed_simulator=42, shots=1000).result() counts = result.get_counts() diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index a8b19e6d4004..1d2a1aac77bf 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -24,7 +24,7 @@ ) from qiskit.pulse import macros from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, FakeGeneric +from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, GenericFakeBackend from qiskit.test import QiskitTestCase @@ -37,7 +37,7 @@ def setUp(self): self.backend_v1 = FakeHanoi() self.inst_map = self.backend.defaults().instruction_schedule_map - self.backend_v2 = FakeGeneric( + self.backend_v2 = GenericFakeBackend( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, @@ -214,7 +214,7 @@ def setUp(self): super().setUp() self.backend_v1 = FakeOpenPulse2Q() self.inst_map = self.backend_v1.defaults().instruction_schedule_map - self.backend_v2 = FakeGeneric( + self.backend_v2 = GenericFakeBackend( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=2, calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, From 12e253dbaffc7b38b81b55dd94d57bcad9b4a082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 22 Jan 2024 18:42:09 +0100 Subject: [PATCH 36/56] Fix lint and test --- qiskit/providers/fake_provider/__init__.py | 1 - test/python/compiler/test_transpiler.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 70272de3b85e..c90990575fee 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -210,7 +210,6 @@ FakeBackendV2 FakeBackend5QV2 FakeMumbaiFractionalCX - ConfigurableFakeBackend GenericFakeBackend Fake Backend Base Classes diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 12c65ef4fd59..495761eef11d 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2368,7 +2368,7 @@ def run(self, dag): backend = GenericFakeBackend( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4, - calibrate_instructions=["cx", "id", "rz", "x", "measure"], + calibrate_instructions=False, ) # This target has PulseQobj entries that provide a serialized schedule data From 0db8035b526453a867d0d7f827701d925519ca20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 22 Jan 2024 18:45:05 +0100 Subject: [PATCH 37/56] Fix comment --- test/python/compiler/test_transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 495761eef11d..444b2f94dcca 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2364,7 +2364,7 @@ def run(self, dag): ) return dag - # Remove calibration for sx gate + # Create backend with empty calibrations (PulseQobjEntries) backend = GenericFakeBackend( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4, From abef2280fbf6b6cbb10d8e493f9e9fcd671295ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 23 Jan 2024 11:50:13 +0100 Subject: [PATCH 38/56] Apply Kevin's suggestions to extract defaults from class --- .../providers/fake_provider/fake_generic.py | 296 +++++++++++------- 1 file changed, 177 insertions(+), 119 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 36a035908c36..1bef2359cf44 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -42,12 +42,123 @@ from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem from qiskit.utils import optionals as _optionals +from enum import Enum +from collections.abc import Callable + + +def _get_noise_nisq_2024(inst: str) -> tuple: + default_dict = { + "cx": (1e-8, 9e-7, 1e-5, 5e-3), + "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "cz": (1e-8, 9e-7, 1e-5, 5e-3), + "id": (3e-8, 4e-8, 9e-5, 1e-4), + "rz": (0.0, 0.0), + "sx": (1e-8, 9e-7, 1e-5, 5e-3), + "x": (1e-8, 9e-7, 1e-5, 5e-3), + "measure": (1e-8, 9e-7, 1e-5, 5e-3), + "delay": (None, None), + "reset": (None, None), + } + return default_dict.get(inst, (1e-8, 9e-7, 1e-5, 5e-3)) + +class NoiseDefaults(Enum): + NISQ_2024_1 = (_get_noise_nisq_2024,) + + def __call__(self, *args, **kwargs): + return self.value[0](*args, **kwargs) + +def _get_sequence_2024( + inst: str, num_qubits: int, qargs: tuple[int], pulse_library: list[PulseLibraryItem] +) -> list[PulseQobjInstruction]: + if len(pulse_library) < 3: + raise ValueError( + f"This calibration sequence requires at least 3 pulses in " + f"the pulse library, found {len(pulse_library)}." + ) + # Note that the calibration pulses are different for + # 1q gates vs 2q gates vs measurement instructions. + if inst == "measure": + sequence = [ + PulseQobjInstruction( + name="acquire", + duration=1792, + t0=0, + qubits=list(range(num_qubits)), + memory_slot=list(range(num_qubits)), + ) + ] + [PulseQobjInstruction(name=pulse_library[1], ch=f"m{i}", t0=0) for i in qargs] + return sequence + if num_qubits == 1: + return [ + PulseQobjInstruction(name="fc", ch=f"u{qargs}", t0=0, phase="-P0"), + PulseQobjInstruction(name=pulse_library[0].name, ch=f"d{qargs}", t0=0), + ] + return [ + PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[0]}", t0=0), + PulseQobjInstruction(name=pulse_library[2].name, ch=f"u{qargs[0]}", t0=0), + PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[1]}", t0=0), + PulseQobjInstruction(name="fc", ch=f"d{qargs[1]}", t0=0, phase=2.1), + ] + +def _get_pulse_config_2024(num_qubits: int) -> dict: + # The number of samples determines the pulse durations of the corresponding + # instructions. This function defines pulses with durations in multiples of + # 16 for consistency with the pulse granularity of real IBM devices, but + # keeps the number smaller than what would be realistic for + # manageability. If needed, more realistic durations could be added in the + # future (order of 160dt for 1q gates, 1760dt for 2q gates and measure). + samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt + samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt + samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt + pulse_library = [ + PulseLibraryItem(name="pulse_1", samples=samples_1), + PulseLibraryItem(name="pulse_2", samples=samples_2), + PulseLibraryItem(name="pulse_3", samples=samples_3), + ] + qubit_freq_est = np.random.normal(4.8, scale=0.01, size=num_qubits).tolist() + meas_freq_est = np.linspace(6.4, 6.6, num_qubits).tolist() + config = { + "pulse_library": pulse_library, + "qubit_freq_est": qubit_freq_est, + "meas_feq_est": meas_freq_est, + } + return config + +class PulseCalibrationDefaults(Enum): + PULSE_CONFIG_2024_1 = (_get_pulse_config_2024,) + SEQUENCE_2024_1 = (_get_sequence_2024,) + + def __call__(self, *args, **kwargs): + return self.value[0](*args, **kwargs) + +class BasisDefaults(Enum): + CX = ["cx", "id", "rz", "sx", "x"] + CZ = ["cz", "id", "rz", "sx", "x"] + ECR = ["ecr", "id", "rz", "sx", "x"] + + def __get__(self, *args, **kwargs): + return self.value + +class QubitDefaults(Enum): + NISQ_2024_1 = { + "dt": 0.222e-9, + "t1": (100e-6, 200e-6), + "t2": (100e-6, 200e-6), + "frequency": (5e9, 5.5e9), + } + + def __get__(self, *args, **kwargs): + return self.value class GenericFakeBackend(BackendV2): """ - Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, - locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, - ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. + Configurable :class:`~.BackendV2` fake backend. Users can directly configure the number + of qubits, basis gates, coupling map, ability to run dynamic circuits (control flow + instructions), pulse calibration sequences and dtm of the backend instance without having + to manually build a target or deal with backend properties. Qubit, instruction and pulse + properties are randomly generated from a series of default ranges that can also be configured + through the ``noise_defaults``, ``calibration_sequence``, ``pulse_config`` and ``qubit_defaults`` + inputs. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random generation is always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. @@ -65,6 +176,10 @@ def __init__( coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: bool | InstructionScheduleMap | None = None, + noise_defaults: Callable | None = None, + calibration_sequence: Callable | None = None, + pulse_config: dict | None = None, + qubit_defaults: Callable | None = None, dtm: float = None, seed: int = 42, ): @@ -110,6 +225,15 @@ def __init__( present in this instruction schedule map will be appended to the target instead of the default pulse schedules (this allows for custom calibrations). + noise_defaults: Callable that returns noise default ranges for every instruction + in ``basis_gates``. See implementation of :class:`.NoiseDefaults` for reference. + + pulse_config: Callable that returns a pulse configuration dictionary for a given number + of qubits. See implementation of :class:`.PulseCalibrationDefaults` for reference. + + qubit_defaults: Dictionary with qubit property configuration ranges. See implementation + of :class:`.QubitDefaults` for reference. + dtm: System time resolution of output signals in nanoseconds. None by default. @@ -129,9 +253,30 @@ def __init__( self._num_qubits = num_qubits self._control_flow = control_flow self._calibrate_instructions = calibrate_instructions - self._noise_defaults = None self._supported_gates = get_standard_gate_name_mapping() + self._qubit_defaults = ( + qubit_defaults if qubit_defaults is not None else QubitDefaults.NISQ_2024_1 + ) + self._noise_defaults = ( + noise_defaults if noise_defaults is not None else NoiseDefaults.NISQ_2024_1 + ) + self._calibration_sequence = ( + calibration_sequence + if calibration_sequence is not None + else PulseCalibrationDefaults.SEQUENCE_2024_1 + ) + self._pulse_config = ( + pulse_config + if pulse_config is not None + else PulseCalibrationDefaults.PULSE_CONFIG_2024_1 + ) + + self._basis_gates = basis_gates if basis_gates is not None else BasisDefaults.CX + for name in ["reset", "delay", "measure"]: + if name not in self._basis_gates: + self._basis_gates.append(name) + if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) else: @@ -146,13 +291,6 @@ def __init__( f"the size of the provided coupling map (got {coupling_map.size()})." ) - self._basis_gates = ( - basis_gates if basis_gates is not None else ["cx", "id", "rz", "sx", "x"] - ) - for name in ["reset", "delay", "measure"]: - if name not in self._basis_gates: - self._basis_gates.append(name) - self._build_generic_target() self._build_default_channels() @@ -181,56 +319,21 @@ def dtm(self) -> float: def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements - @property - def noise_defaults(self) -> dict[str, tuple | None]: - """Noise default values/ranges for duration and error of supported - instructions. There are two possible formats: - - #. (min_duration, max_duration, min_error, max_error), - if the defaults are ranges - #. (duration, error), if the defaults are fixed values - - Returns: - Dictionary mapping instruction names to noise defaults - """ - - if self._noise_defaults is None: - # gates with known noise approximations - default_dict = { - "cx": (1e-8, 9e-7, 1e-5, 5e-3), - "ecr": (1e-8, 9e-7, 1e-5, 5e-3), - "cz": (1e-8, 9e-7, 1e-5, 5e-3), - "id": (3e-8, 4e-8, 9e-5, 1e-4), - "rz": (0.0, 0.0), - "sx": (1e-8, 9e-7, 1e-5, 5e-3), - "x": (1e-8, 9e-7, 1e-5, 5e-3), - "measure": (1e-8, 9e-7, 1e-5, 5e-3), - "delay": (None, None), - "reset": (None, None), - } - # add values for rest of the gates (untested) - for gate in self._supported_gates: - if gate not in default_dict: - default_dict[gate] = (1e-8, 9e-7, 1e-5, 5e-3) - self._noise_defaults = default_dict - - return self._noise_defaults - def _build_generic_target(self): """ This method generates a :class:`~.Target` instance with default qubit, instruction and calibration properties. """ - # the qubit properties are currently not configurable. + # The qubit properties are currently not configurable self._target = Target( description=f"Generic Target with {self._num_qubits} qubits", num_qubits=self._num_qubits, - dt=0.222e-9, + dt=self._qubit_defaults["dt"], qubit_properties=[ QubitProperties( - t1=self._rng.uniform(100e-6, 200e-6), - t2=self._rng.uniform(100e-6, 200e-6), - frequency=self._rng.uniform(5e9, 5.5e9), + t1=self._qubit_defaults["t1"], + t2=self._qubit_defaults["t2"], + frequency=self._qubit_defaults["frequency"], ) for _ in range(self._num_qubits) ], @@ -246,7 +349,7 @@ def _build_generic_target(self): f"in the standard qiskit circuit library." ) gate = self._supported_gates[name] - noise_params = self.noise_defaults[name] + noise_params = self._noise_defaults(name) self._add_noisy_instruction_to_target(gate, noise_params) if self._control_flow: @@ -259,7 +362,9 @@ def _build_generic_target(self): # Generate block of calibration defaults and add to target. # Note: this could be improved if we could generate and add - # calibration defaults per-gate, and not as a block. + # calibration defaults per-gate, and not as a block, but we + # currently rely on the `PulseDefaults` class + # from qiskit.providers.models if self._calibrate_instructions is not None: if isinstance(self._calibrate_instructions, InstructionScheduleMap): inst_map = self._calibrate_instructions @@ -277,9 +382,6 @@ def _add_noisy_instruction_to_target( instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target noise_params (tuple[float, ...] | None): error and duration noise values/ranges to include in instruction properties. - - Returns: - None """ qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) props = {} @@ -298,90 +400,50 @@ def _add_noisy_instruction_to_target( self._target.add_instruction(instruction, props) def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate calibration defaults for instructions specified via ``calibrate_instructions``. - By default, this method generates empty calibration schedules. - """ - - # The number of samples determines the pulse durations of the corresponding - # instructions. This class generates pulses with durations in multiples of - # 16 for consistency with the pulse granularity of real IBM devices, but - # keeps the number smaller than what would be realistic for - # manageability. If needed, more realistic durations could be added in the - # future (order of 160 dt for 1q gates, 1760 for 2q gates and measure). - - samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt - samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt - samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt - - pulse_library = [ - PulseLibraryItem(name="pulse_1", samples=samples_1), - PulseLibraryItem(name="pulse_2", samples=samples_2), - PulseLibraryItem(name="pulse_3", samples=samples_3), - ] - - # Unless explicitly given a series of gates to calibrate, this method - # will generate empty pulse schedules for all gates in self._basis_gates. + """Generate pulse calibration defaults if specified via ``calibrate_instructions``.""" + pulse_library = self._pulse_config("pulse_library") + # If self.calibrate_instructions==True, this method + # will generate default pulse schedules for all gates in self._basis_gates, + # except for `delay` and `reset`. calibration_buffer = self._basis_gates.copy() for inst in ["delay", "reset"]: calibration_buffer.remove(inst) - # List of calibration commands (generated from sequences of PulseQobjInstructions) - # corresponding to each calibrated instruction. Note that the calibration pulses - # are different for 1q gates vs 2q gates vs measurement instructions. + # corresponding to each calibrated instruction. cmd_def = [] for inst in calibration_buffer: num_qubits = self._supported_gates[inst].num_qubits qarg_set = self._coupling_map if num_qubits > 1 else list(range(self.num_qubits)) if inst == "measure": - sequence = [] - qubits = qarg_set - if self._calibrate_instructions: - sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=list(range(self.num_qubits)), - memory_slot=list(range(self.num_qubits)), - ) - ] + [PulseQobjInstruction(name="pulse_2", ch=f"m{i}", t0=0) for i in qarg_set] cmd_def.append( Command( name=inst, - qubits=qubits, - sequence=sequence, + qubits=qarg_set, + sequence=( + self._calibration_sequence(inst, num_qubits, qarg_set, pulse_library) + if self._calibrate_instructions + else [] + ), ) ) else: for qarg in qarg_set: - sequence = [] qubits = [qarg] if num_qubits == 1 else qarg - if self._calibrate_instructions: - if num_qubits == 1: - sequence = [ - PulseQobjInstruction(name="fc", ch=f"u{qarg}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_1", ch=f"d{qarg}", t0=0), - ] - else: - sequence = [ - PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[0]}", t0=0), - PulseQobjInstruction(name="pulse_3", ch=f"u{qarg[0]}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[1]}", t0=0), - PulseQobjInstruction(name="fc", ch=f"d{qarg[1]}", t0=0, phase=2.1), - ] cmd_def.append( Command( name=inst, qubits=qubits, - sequence=sequence, + sequence=( + self._calibration_sequence(inst, num_qubits, qubits, pulse_library) + if self._calibrate_instructions + else [] + ), ) ) - qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() - meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() return PulseDefaults( - qubit_freq_est=qubit_freq_est, - meas_freq_est=meas_freq_est, + qubit_freq_est=self._pulse_config("qubit_freq_est"), + meas_freq_est=self._pulse_config("meas_freq_est"), buffer=0, pulse_library=pulse_library, cmd_def=cmd_def, @@ -392,11 +454,7 @@ def _add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: Args: inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map - - Returns: - None """ - # The calibration entries are directly injected into the gate map to # avoid then being labeled as "user_provided". for inst in inst_map.instructions: From 0d303bb94f2484529f5b44615006e021d856dcae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 23 Jan 2024 13:26:46 +0100 Subject: [PATCH 39/56] Make defaults private --- .../providers/fake_provider/fake_generic.py | 74 ++++++++----------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 1bef2359cf44..b2562e5dc07d 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -43,7 +43,6 @@ from qiskit.utils import optionals as _optionals from enum import Enum -from collections.abc import Callable def _get_noise_nisq_2024(inst: str) -> tuple: @@ -61,12 +60,14 @@ def _get_noise_nisq_2024(inst: str) -> tuple: } return default_dict.get(inst, (1e-8, 9e-7, 1e-5, 5e-3)) -class NoiseDefaults(Enum): + +class _NoiseDefaults(Enum): NISQ_2024_1 = (_get_noise_nisq_2024,) def __call__(self, *args, **kwargs): return self.value[0](*args, **kwargs) + def _get_sequence_2024( inst: str, num_qubits: int, qargs: tuple[int], pulse_library: list[PulseLibraryItem] ) -> list[PulseQobjInstruction]: @@ -100,6 +101,7 @@ def _get_sequence_2024( PulseQobjInstruction(name="fc", ch=f"d{qargs[1]}", t0=0, phase=2.1), ] + def _get_pulse_config_2024(num_qubits: int) -> dict: # The number of samples determines the pulse durations of the corresponding # instructions. This function defines pulses with durations in multiples of @@ -120,18 +122,20 @@ def _get_pulse_config_2024(num_qubits: int) -> dict: config = { "pulse_library": pulse_library, "qubit_freq_est": qubit_freq_est, - "meas_feq_est": meas_freq_est, + "meas_freq_est": meas_freq_est, } return config -class PulseCalibrationDefaults(Enum): + +class _PulseCalibrationDefaults(Enum): PULSE_CONFIG_2024_1 = (_get_pulse_config_2024,) SEQUENCE_2024_1 = (_get_sequence_2024,) def __call__(self, *args, **kwargs): return self.value[0](*args, **kwargs) -class BasisDefaults(Enum): + +class _BasisDefaults(Enum): CX = ["cx", "id", "rz", "sx", "x"] CZ = ["cz", "id", "rz", "sx", "x"] ECR = ["ecr", "id", "rz", "sx", "x"] @@ -139,7 +143,8 @@ class BasisDefaults(Enum): def __get__(self, *args, **kwargs): return self.value -class QubitDefaults(Enum): + +class _QubitDefaults(Enum): NISQ_2024_1 = { "dt": 0.222e-9, "t1": (100e-6, 200e-6), @@ -150,6 +155,7 @@ class QubitDefaults(Enum): def __get__(self, *args, **kwargs): return self.value + class GenericFakeBackend(BackendV2): """ Configurable :class:`~.BackendV2` fake backend. Users can directly configure the number @@ -171,16 +177,12 @@ class GenericFakeBackend(BackendV2): def __init__( self, num_qubits: int, - basis_gates: list[str] | None = None, *, + basis_gates: list[str] | None = None, coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: bool | InstructionScheduleMap | None = None, - noise_defaults: Callable | None = None, - calibration_sequence: Callable | None = None, - pulse_config: dict | None = None, - qubit_defaults: Callable | None = None, - dtm: float = None, + dtm: float | None = None, seed: int = 42, ): """ @@ -225,15 +227,6 @@ def __init__( present in this instruction schedule map will be appended to the target instead of the default pulse schedules (this allows for custom calibrations). - noise_defaults: Callable that returns noise default ranges for every instruction - in ``basis_gates``. See implementation of :class:`.NoiseDefaults` for reference. - - pulse_config: Callable that returns a pulse configuration dictionary for a given number - of qubits. See implementation of :class:`.PulseCalibrationDefaults` for reference. - - qubit_defaults: Dictionary with qubit property configuration ranges. See implementation - of :class:`.QubitDefaults` for reference. - dtm: System time resolution of output signals in nanoseconds. None by default. @@ -255,24 +248,7 @@ def __init__( self._calibrate_instructions = calibrate_instructions self._supported_gates = get_standard_gate_name_mapping() - self._qubit_defaults = ( - qubit_defaults if qubit_defaults is not None else QubitDefaults.NISQ_2024_1 - ) - self._noise_defaults = ( - noise_defaults if noise_defaults is not None else NoiseDefaults.NISQ_2024_1 - ) - self._calibration_sequence = ( - calibration_sequence - if calibration_sequence is not None - else PulseCalibrationDefaults.SEQUENCE_2024_1 - ) - self._pulse_config = ( - pulse_config - if pulse_config is not None - else PulseCalibrationDefaults.PULSE_CONFIG_2024_1 - ) - - self._basis_gates = basis_gates if basis_gates is not None else BasisDefaults.CX + self._basis_gates = basis_gates if basis_gates is not None else _BasisDefaults.CX for name in ["reset", "delay", "measure"]: if name not in self._basis_gates: self._basis_gates.append(name) @@ -291,6 +267,11 @@ def __init__( f"the size of the provided coupling map (got {coupling_map.size()})." ) + self._qubit_defaults = _QubitDefaults.NISQ_2024_1 + self._noise_defaults = _NoiseDefaults.NISQ_2024_1 + self._calibration_sequence = _PulseCalibrationDefaults.SEQUENCE_2024_1 + self._pulse_config = _PulseCalibrationDefaults.PULSE_CONFIG_2024_1 + self._build_generic_target() self._build_default_channels() @@ -331,8 +312,12 @@ def _build_generic_target(self): dt=self._qubit_defaults["dt"], qubit_properties=[ QubitProperties( - t1=self._qubit_defaults["t1"], - t2=self._qubit_defaults["t2"], + t1=self._rng.uniform( + self._qubit_defaults["t1"][0], self._qubit_defaults["t1"][1] + ), + t2=self._rng.uniform( + self._qubit_defaults["t2"][0], self._qubit_defaults["t2"][1] + ), frequency=self._qubit_defaults["frequency"], ) for _ in range(self._num_qubits) @@ -401,7 +386,8 @@ def _add_noisy_instruction_to_target( def _generate_calibration_defaults(self) -> PulseDefaults: """Generate pulse calibration defaults if specified via ``calibrate_instructions``.""" - pulse_library = self._pulse_config("pulse_library") + pulse_config = self._pulse_config(self._num_qubits) + pulse_library = pulse_config["pulse_library"] # If self.calibrate_instructions==True, this method # will generate default pulse schedules for all gates in self._basis_gates, # except for `delay` and `reset`. @@ -442,8 +428,8 @@ def _generate_calibration_defaults(self) -> PulseDefaults: ) return PulseDefaults( - qubit_freq_est=self._pulse_config("qubit_freq_est"), - meas_freq_est=self._pulse_config("meas_freq_est"), + qubit_freq_est=pulse_config["qubit_freq_est"], + meas_freq_est=pulse_config["meas_freq_est"], buffer=0, pulse_library=pulse_library, cmd_def=cmd_def, From 9e276e98ab51e66f2eceb2e9f2f54029dd215a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 23 Jan 2024 14:08:21 +0100 Subject: [PATCH 40/56] Fix lint --- qiskit/providers/fake_provider/fake_generic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index b2562e5dc07d..23cabd2445d5 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -15,6 +15,7 @@ from __future__ import annotations import warnings +from enum import Enum from collections.abc import Iterable import numpy as np @@ -42,8 +43,6 @@ from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem from qiskit.utils import optionals as _optionals -from enum import Enum - def _get_noise_nisq_2024(inst: str) -> tuple: default_dict = { From e98f059193b62f44f4a9afdb318dd278b52ac61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 24 Jan 2024 18:07:53 +0100 Subject: [PATCH 41/56] Revert previous 3 comments. --- .../providers/fake_provider/fake_generic.py | 283 ++++++++---------- 1 file changed, 120 insertions(+), 163 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 23cabd2445d5..36a035908c36 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -15,7 +15,6 @@ from __future__ import annotations import warnings -from enum import Enum from collections.abc import Iterable import numpy as np @@ -44,126 +43,11 @@ from qiskit.utils import optionals as _optionals -def _get_noise_nisq_2024(inst: str) -> tuple: - default_dict = { - "cx": (1e-8, 9e-7, 1e-5, 5e-3), - "ecr": (1e-8, 9e-7, 1e-5, 5e-3), - "cz": (1e-8, 9e-7, 1e-5, 5e-3), - "id": (3e-8, 4e-8, 9e-5, 1e-4), - "rz": (0.0, 0.0), - "sx": (1e-8, 9e-7, 1e-5, 5e-3), - "x": (1e-8, 9e-7, 1e-5, 5e-3), - "measure": (1e-8, 9e-7, 1e-5, 5e-3), - "delay": (None, None), - "reset": (None, None), - } - return default_dict.get(inst, (1e-8, 9e-7, 1e-5, 5e-3)) - - -class _NoiseDefaults(Enum): - NISQ_2024_1 = (_get_noise_nisq_2024,) - - def __call__(self, *args, **kwargs): - return self.value[0](*args, **kwargs) - - -def _get_sequence_2024( - inst: str, num_qubits: int, qargs: tuple[int], pulse_library: list[PulseLibraryItem] -) -> list[PulseQobjInstruction]: - if len(pulse_library) < 3: - raise ValueError( - f"This calibration sequence requires at least 3 pulses in " - f"the pulse library, found {len(pulse_library)}." - ) - # Note that the calibration pulses are different for - # 1q gates vs 2q gates vs measurement instructions. - if inst == "measure": - sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=list(range(num_qubits)), - memory_slot=list(range(num_qubits)), - ) - ] + [PulseQobjInstruction(name=pulse_library[1], ch=f"m{i}", t0=0) for i in qargs] - return sequence - if num_qubits == 1: - return [ - PulseQobjInstruction(name="fc", ch=f"u{qargs}", t0=0, phase="-P0"), - PulseQobjInstruction(name=pulse_library[0].name, ch=f"d{qargs}", t0=0), - ] - return [ - PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[0]}", t0=0), - PulseQobjInstruction(name=pulse_library[2].name, ch=f"u{qargs[0]}", t0=0), - PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[1]}", t0=0), - PulseQobjInstruction(name="fc", ch=f"d{qargs[1]}", t0=0, phase=2.1), - ] - - -def _get_pulse_config_2024(num_qubits: int) -> dict: - # The number of samples determines the pulse durations of the corresponding - # instructions. This function defines pulses with durations in multiples of - # 16 for consistency with the pulse granularity of real IBM devices, but - # keeps the number smaller than what would be realistic for - # manageability. If needed, more realistic durations could be added in the - # future (order of 160dt for 1q gates, 1760dt for 2q gates and measure). - samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt - samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt - samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt - pulse_library = [ - PulseLibraryItem(name="pulse_1", samples=samples_1), - PulseLibraryItem(name="pulse_2", samples=samples_2), - PulseLibraryItem(name="pulse_3", samples=samples_3), - ] - qubit_freq_est = np.random.normal(4.8, scale=0.01, size=num_qubits).tolist() - meas_freq_est = np.linspace(6.4, 6.6, num_qubits).tolist() - config = { - "pulse_library": pulse_library, - "qubit_freq_est": qubit_freq_est, - "meas_freq_est": meas_freq_est, - } - return config - - -class _PulseCalibrationDefaults(Enum): - PULSE_CONFIG_2024_1 = (_get_pulse_config_2024,) - SEQUENCE_2024_1 = (_get_sequence_2024,) - - def __call__(self, *args, **kwargs): - return self.value[0](*args, **kwargs) - - -class _BasisDefaults(Enum): - CX = ["cx", "id", "rz", "sx", "x"] - CZ = ["cz", "id", "rz", "sx", "x"] - ECR = ["ecr", "id", "rz", "sx", "x"] - - def __get__(self, *args, **kwargs): - return self.value - - -class _QubitDefaults(Enum): - NISQ_2024_1 = { - "dt": 0.222e-9, - "t1": (100e-6, 200e-6), - "t2": (100e-6, 200e-6), - "frequency": (5e9, 5.5e9), - } - - def __get__(self, *args, **kwargs): - return self.value - - class GenericFakeBackend(BackendV2): """ - Configurable :class:`~.BackendV2` fake backend. Users can directly configure the number - of qubits, basis gates, coupling map, ability to run dynamic circuits (control flow - instructions), pulse calibration sequences and dtm of the backend instance without having - to manually build a target or deal with backend properties. Qubit, instruction and pulse - properties are randomly generated from a series of default ranges that can also be configured - through the ``noise_defaults``, ``calibration_sequence``, ``pulse_config`` and ``qubit_defaults`` - inputs. + Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, + locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, + ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random generation is always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. @@ -176,12 +60,12 @@ class GenericFakeBackend(BackendV2): def __init__( self, num_qubits: int, - *, basis_gates: list[str] | None = None, + *, coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: bool | InstructionScheduleMap | None = None, - dtm: float | None = None, + dtm: float = None, seed: int = 42, ): """ @@ -245,13 +129,9 @@ def __init__( self._num_qubits = num_qubits self._control_flow = control_flow self._calibrate_instructions = calibrate_instructions + self._noise_defaults = None self._supported_gates = get_standard_gate_name_mapping() - self._basis_gates = basis_gates if basis_gates is not None else _BasisDefaults.CX - for name in ["reset", "delay", "measure"]: - if name not in self._basis_gates: - self._basis_gates.append(name) - if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) else: @@ -266,10 +146,12 @@ def __init__( f"the size of the provided coupling map (got {coupling_map.size()})." ) - self._qubit_defaults = _QubitDefaults.NISQ_2024_1 - self._noise_defaults = _NoiseDefaults.NISQ_2024_1 - self._calibration_sequence = _PulseCalibrationDefaults.SEQUENCE_2024_1 - self._pulse_config = _PulseCalibrationDefaults.PULSE_CONFIG_2024_1 + self._basis_gates = ( + basis_gates if basis_gates is not None else ["cx", "id", "rz", "sx", "x"] + ) + for name in ["reset", "delay", "measure"]: + if name not in self._basis_gates: + self._basis_gates.append(name) self._build_generic_target() self._build_default_channels() @@ -299,25 +181,56 @@ def dtm(self) -> float: def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements + @property + def noise_defaults(self) -> dict[str, tuple | None]: + """Noise default values/ranges for duration and error of supported + instructions. There are two possible formats: + + #. (min_duration, max_duration, min_error, max_error), + if the defaults are ranges + #. (duration, error), if the defaults are fixed values + + Returns: + Dictionary mapping instruction names to noise defaults + """ + + if self._noise_defaults is None: + # gates with known noise approximations + default_dict = { + "cx": (1e-8, 9e-7, 1e-5, 5e-3), + "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "cz": (1e-8, 9e-7, 1e-5, 5e-3), + "id": (3e-8, 4e-8, 9e-5, 1e-4), + "rz": (0.0, 0.0), + "sx": (1e-8, 9e-7, 1e-5, 5e-3), + "x": (1e-8, 9e-7, 1e-5, 5e-3), + "measure": (1e-8, 9e-7, 1e-5, 5e-3), + "delay": (None, None), + "reset": (None, None), + } + # add values for rest of the gates (untested) + for gate in self._supported_gates: + if gate not in default_dict: + default_dict[gate] = (1e-8, 9e-7, 1e-5, 5e-3) + self._noise_defaults = default_dict + + return self._noise_defaults + def _build_generic_target(self): """ This method generates a :class:`~.Target` instance with default qubit, instruction and calibration properties. """ - # The qubit properties are currently not configurable + # the qubit properties are currently not configurable. self._target = Target( description=f"Generic Target with {self._num_qubits} qubits", num_qubits=self._num_qubits, - dt=self._qubit_defaults["dt"], + dt=0.222e-9, qubit_properties=[ QubitProperties( - t1=self._rng.uniform( - self._qubit_defaults["t1"][0], self._qubit_defaults["t1"][1] - ), - t2=self._rng.uniform( - self._qubit_defaults["t2"][0], self._qubit_defaults["t2"][1] - ), - frequency=self._qubit_defaults["frequency"], + t1=self._rng.uniform(100e-6, 200e-6), + t2=self._rng.uniform(100e-6, 200e-6), + frequency=self._rng.uniform(5e9, 5.5e9), ) for _ in range(self._num_qubits) ], @@ -333,7 +246,7 @@ def _build_generic_target(self): f"in the standard qiskit circuit library." ) gate = self._supported_gates[name] - noise_params = self._noise_defaults(name) + noise_params = self.noise_defaults[name] self._add_noisy_instruction_to_target(gate, noise_params) if self._control_flow: @@ -346,9 +259,7 @@ def _build_generic_target(self): # Generate block of calibration defaults and add to target. # Note: this could be improved if we could generate and add - # calibration defaults per-gate, and not as a block, but we - # currently rely on the `PulseDefaults` class - # from qiskit.providers.models + # calibration defaults per-gate, and not as a block. if self._calibrate_instructions is not None: if isinstance(self._calibrate_instructions, InstructionScheduleMap): inst_map = self._calibrate_instructions @@ -366,6 +277,9 @@ def _add_noisy_instruction_to_target( instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target noise_params (tuple[float, ...] | None): error and duration noise values/ranges to include in instruction properties. + + Returns: + None """ qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) props = {} @@ -384,51 +298,90 @@ def _add_noisy_instruction_to_target( self._target.add_instruction(instruction, props) def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate pulse calibration defaults if specified via ``calibrate_instructions``.""" - pulse_config = self._pulse_config(self._num_qubits) - pulse_library = pulse_config["pulse_library"] - # If self.calibrate_instructions==True, this method - # will generate default pulse schedules for all gates in self._basis_gates, - # except for `delay` and `reset`. + """Generate calibration defaults for instructions specified via ``calibrate_instructions``. + By default, this method generates empty calibration schedules. + """ + + # The number of samples determines the pulse durations of the corresponding + # instructions. This class generates pulses with durations in multiples of + # 16 for consistency with the pulse granularity of real IBM devices, but + # keeps the number smaller than what would be realistic for + # manageability. If needed, more realistic durations could be added in the + # future (order of 160 dt for 1q gates, 1760 for 2q gates and measure). + + samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt + samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt + samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt + + pulse_library = [ + PulseLibraryItem(name="pulse_1", samples=samples_1), + PulseLibraryItem(name="pulse_2", samples=samples_2), + PulseLibraryItem(name="pulse_3", samples=samples_3), + ] + + # Unless explicitly given a series of gates to calibrate, this method + # will generate empty pulse schedules for all gates in self._basis_gates. calibration_buffer = self._basis_gates.copy() for inst in ["delay", "reset"]: calibration_buffer.remove(inst) + # List of calibration commands (generated from sequences of PulseQobjInstructions) - # corresponding to each calibrated instruction. + # corresponding to each calibrated instruction. Note that the calibration pulses + # are different for 1q gates vs 2q gates vs measurement instructions. cmd_def = [] for inst in calibration_buffer: num_qubits = self._supported_gates[inst].num_qubits qarg_set = self._coupling_map if num_qubits > 1 else list(range(self.num_qubits)) if inst == "measure": + sequence = [] + qubits = qarg_set + if self._calibrate_instructions: + sequence = [ + PulseQobjInstruction( + name="acquire", + duration=1792, + t0=0, + qubits=list(range(self.num_qubits)), + memory_slot=list(range(self.num_qubits)), + ) + ] + [PulseQobjInstruction(name="pulse_2", ch=f"m{i}", t0=0) for i in qarg_set] cmd_def.append( Command( name=inst, - qubits=qarg_set, - sequence=( - self._calibration_sequence(inst, num_qubits, qarg_set, pulse_library) - if self._calibrate_instructions - else [] - ), + qubits=qubits, + sequence=sequence, ) ) else: for qarg in qarg_set: + sequence = [] qubits = [qarg] if num_qubits == 1 else qarg + if self._calibrate_instructions: + if num_qubits == 1: + sequence = [ + PulseQobjInstruction(name="fc", ch=f"u{qarg}", t0=0, phase="-P0"), + PulseQobjInstruction(name="pulse_1", ch=f"d{qarg}", t0=0), + ] + else: + sequence = [ + PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[0]}", t0=0), + PulseQobjInstruction(name="pulse_3", ch=f"u{qarg[0]}", t0=0), + PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[1]}", t0=0), + PulseQobjInstruction(name="fc", ch=f"d{qarg[1]}", t0=0, phase=2.1), + ] cmd_def.append( Command( name=inst, qubits=qubits, - sequence=( - self._calibration_sequence(inst, num_qubits, qubits, pulse_library) - if self._calibrate_instructions - else [] - ), + sequence=sequence, ) ) + qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() + meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() return PulseDefaults( - qubit_freq_est=pulse_config["qubit_freq_est"], - meas_freq_est=pulse_config["meas_freq_est"], + qubit_freq_est=qubit_freq_est, + meas_freq_est=meas_freq_est, buffer=0, pulse_library=pulse_library, cmd_def=cmd_def, @@ -439,7 +392,11 @@ def _add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: Args: inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map + + Returns: + None """ + # The calibration entries are directly injected into the gate map to # avoid then being labeled as "user_provided". for inst in inst_map.instructions: From d44a5177606ae54d95272c4e64b0fef2309793c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 24 Jan 2024 18:14:08 +0100 Subject: [PATCH 42/56] Privatize GenericFakeBackend --- qiskit/providers/fake_provider/__init__.py | 1 - qiskit/providers/fake_provider/fake_generic.py | 2 +- test/python/compiler/test_transpiler.py | 2 +- test/python/providers/fake_provider/test_fake_generic.py | 2 +- test/python/pulse/test_macros.py | 3 ++- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index c90990575fee..615531f07d94 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -249,4 +249,3 @@ from .fake_1q import Fake1Q from .fake_backend_v2 import FakeBackendV2, FakeBackend5QV2 from .fake_mumbai_v2 import FakeMumbaiFractionalCX -from .fake_generic import GenericFakeBackend diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 36a035908c36..59aa691d456a 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -43,7 +43,7 @@ from qiskit.utils import optionals as _optionals -class GenericFakeBackend(BackendV2): +class _GenericFakeBackend(BackendV2): """ Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 444b2f94dcca..4d3105d813e0 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -71,7 +71,7 @@ from qiskit.dagcircuit import DAGOpNode, DAGOutNode from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 -from qiskit.providers.fake_provider import GenericFakeBackend +from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend from qiskit.providers.fake_provider import ( FakeMelbourne, FakeRueschlikon, diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 61b5bd463c07..77b1309e47ed 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -16,7 +16,7 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit import transpile -from qiskit.providers.fake_provider import GenericFakeBackend +from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend from qiskit.transpiler import CouplingMap from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 1d2a1aac77bf..9dfa562e7c7d 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -24,7 +24,8 @@ ) from qiskit.pulse import macros from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, GenericFakeBackend +from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi +from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend from qiskit.test import QiskitTestCase From a54fb64f003feab179c6a595976f6a21248f911d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 24 Jan 2024 18:15:11 +0100 Subject: [PATCH 43/56] Remove from docs --- qiskit/providers/fake_provider/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 615531f07d94..8d8e3f38608c 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -210,7 +210,6 @@ FakeBackendV2 FakeBackend5QV2 FakeMumbaiFractionalCX - GenericFakeBackend Fake Backend Base Classes ========================= From 99c643fe55c0b29da683b36bf315affe95bd36d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 25 Jan 2024 16:43:35 +0100 Subject: [PATCH 44/56] Revert privatization --- qiskit/providers/fake_provider/__init__.py | 2 ++ qiskit/providers/fake_provider/fake_generic.py | 2 +- test/python/compiler/test_transpiler.py | 2 +- test/python/providers/fake_provider/test_fake_generic.py | 2 +- test/python/pulse/test_macros.py | 3 +-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 8d8e3f38608c..c90990575fee 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -210,6 +210,7 @@ FakeBackendV2 FakeBackend5QV2 FakeMumbaiFractionalCX + GenericFakeBackend Fake Backend Base Classes ========================= @@ -248,3 +249,4 @@ from .fake_1q import Fake1Q from .fake_backend_v2 import FakeBackendV2, FakeBackend5QV2 from .fake_mumbai_v2 import FakeMumbaiFractionalCX +from .fake_generic import GenericFakeBackend diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 59aa691d456a..36a035908c36 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -43,7 +43,7 @@ from qiskit.utils import optionals as _optionals -class _GenericFakeBackend(BackendV2): +class GenericFakeBackend(BackendV2): """ Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 4d3105d813e0..444b2f94dcca 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -71,7 +71,7 @@ from qiskit.dagcircuit import DAGOpNode, DAGOutNode from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 -from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend +from qiskit.providers.fake_provider import GenericFakeBackend from qiskit.providers.fake_provider import ( FakeMelbourne, FakeRueschlikon, diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 77b1309e47ed..61b5bd463c07 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -16,7 +16,7 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit import transpile -from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend +from qiskit.providers.fake_provider import GenericFakeBackend from qiskit.transpiler import CouplingMap from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 9dfa562e7c7d..1d2a1aac77bf 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -24,8 +24,7 @@ ) from qiskit.pulse import macros from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi -from qiskit.providers.fake_provider.fake_generic import _GenericFakeBackend as GenericFakeBackend +from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, GenericFakeBackend from qiskit.test import QiskitTestCase From de63a9d19d29b1d21533ee0fb547433abb797e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 25 Jan 2024 18:04:18 +0100 Subject: [PATCH 45/56] Apply review comments --- .../providers/fake_provider/fake_generic.py | 189 +++++++++--------- test/python/compiler/test_transpiler.py | 2 +- 2 files changed, 97 insertions(+), 94 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 36a035908c36..8804f43b73dd 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -42,6 +42,44 @@ from qiskit.qobj import PulseQobjInstruction, PulseLibraryItem from qiskit.utils import optionals as _optionals +# Noise default values/ranges for duration and error of supported +# instructions. There are two possible formats: +# - (min_duration, max_duration, min_error, max_error), +# if the defaults are ranges. +# - (duration, error), if the defaults are fixed values. +_NOISE_DEFAULTS = { + "cx": (1e-8, 9e-7, 1e-5, 5e-3), + "ecr": (1e-8, 9e-7, 1e-5, 5e-3), + "cz": (1e-8, 9e-7, 1e-5, 5e-3), + "id": (3e-8, 4e-8, 9e-5, 1e-4), + "rz": (0.0, 0.0), + "sx": (1e-8, 9e-7, 1e-5, 5e-3), + "x": (1e-8, 9e-7, 1e-5, 5e-3), + "measure": (1e-8, 9e-7, 1e-5, 5e-3), + "delay": (None, None), + "reset": (None, None), +} + +# Ranges to sample qubit properties from. +_QUBIT_PROPERTIES = { + "dt": 0.222e-9, + "t1": (100e-6, 200e-6), + "t2": (100e-6, 200e-6), + "frequency": (5e9, 5.5e9), +} + +# The number of samples determines the pulse durations of the corresponding +# instructions. This default defines pulses with durations in multiples of +# 16 for consistency with the pulse granularity of real IBM devices, but +# keeps the number smaller than what would be realistic for +# manageability. If needed, more realistic durations could be added in the +# future (order of 160dt for 1q gates, 1760dt for 2q gates and measure). +_PULSE_LIBRARY = [ + PulseLibraryItem(name="pulse_1", samples=np.linspace(0, 1.0, 16, dtype=np.complex128)), # 16dt + PulseLibraryItem(name="pulse_2", samples=np.linspace(0, 1.0, 32, dtype=np.complex128)), # 32dt + PulseLibraryItem(name="pulse_3", samples=np.linspace(0, 1.0, 64, dtype=np.complex128)), # 64dt +] + class GenericFakeBackend(BackendV2): """ @@ -129,7 +167,6 @@ def __init__( self._num_qubits = num_qubits self._control_flow = control_flow self._calibrate_instructions = calibrate_instructions - self._noise_defaults = None self._supported_gates = get_standard_gate_name_mapping() if coupling_map is None: @@ -181,56 +218,27 @@ def dtm(self) -> float: def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements - @property - def noise_defaults(self) -> dict[str, tuple | None]: - """Noise default values/ranges for duration and error of supported - instructions. There are two possible formats: - - #. (min_duration, max_duration, min_error, max_error), - if the defaults are ranges - #. (duration, error), if the defaults are fixed values - - Returns: - Dictionary mapping instruction names to noise defaults - """ - - if self._noise_defaults is None: - # gates with known noise approximations - default_dict = { - "cx": (1e-8, 9e-7, 1e-5, 5e-3), - "ecr": (1e-8, 9e-7, 1e-5, 5e-3), - "cz": (1e-8, 9e-7, 1e-5, 5e-3), - "id": (3e-8, 4e-8, 9e-5, 1e-4), - "rz": (0.0, 0.0), - "sx": (1e-8, 9e-7, 1e-5, 5e-3), - "x": (1e-8, 9e-7, 1e-5, 5e-3), - "measure": (1e-8, 9e-7, 1e-5, 5e-3), - "delay": (None, None), - "reset": (None, None), - } - # add values for rest of the gates (untested) - for gate in self._supported_gates: - if gate not in default_dict: - default_dict[gate] = (1e-8, 9e-7, 1e-5, 5e-3) - self._noise_defaults = default_dict - - return self._noise_defaults + def _get_noise_defaults(self, name: str) -> tuple: + return _NOISE_DEFAULTS.get(name, (1e-8, 9e-7, 1e-5, 5e-3)) def _build_generic_target(self): """ This method generates a :class:`~.Target` instance with default qubit, instruction and calibration properties. """ - # the qubit properties are currently not configurable. + # the qubit properties are sampled from default ranges + properties = _QUBIT_PROPERTIES self._target = Target( description=f"Generic Target with {self._num_qubits} qubits", num_qubits=self._num_qubits, - dt=0.222e-9, + dt=properties["dt"], qubit_properties=[ QubitProperties( - t1=self._rng.uniform(100e-6, 200e-6), - t2=self._rng.uniform(100e-6, 200e-6), - frequency=self._rng.uniform(5e9, 5.5e9), + t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), + t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), + frequency=self._rng.uniform( + properties["frequency"][0], properties["frequency"][1] + ), ) for _ in range(self._num_qubits) ], @@ -246,7 +254,7 @@ def _build_generic_target(self): f"in the standard qiskit circuit library." ) gate = self._supported_gates[name] - noise_params = self.noise_defaults[name] + noise_params = self._get_noise_defaults(name) self._add_noisy_instruction_to_target(gate, noise_params) if self._control_flow: @@ -297,30 +305,43 @@ def _add_noisy_instruction_to_target( self._target.add_instruction(instruction, props) - def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate calibration defaults for instructions specified via ``calibrate_instructions``. - By default, this method generates empty calibration schedules. - """ - - # The number of samples determines the pulse durations of the corresponding - # instructions. This class generates pulses with durations in multiples of - # 16 for consistency with the pulse granularity of real IBM devices, but - # keeps the number smaller than what would be realistic for - # manageability. If needed, more realistic durations could be added in the - # future (order of 160 dt for 1q gates, 1760 for 2q gates and measure). - - samples_1 = np.linspace(0, 1.0, 16, dtype=np.complex128) # 16dt - samples_2 = np.linspace(0, 1.0, 32, dtype=np.complex128) # 32dt - samples_3 = np.linspace(0, 1.0, 64, dtype=np.complex128) # 64dt - - pulse_library = [ - PulseLibraryItem(name="pulse_1", samples=samples_1), - PulseLibraryItem(name="pulse_2", samples=samples_2), - PulseLibraryItem(name="pulse_3", samples=samples_3), + def _get_calibration_sequence( + self, inst: str, num_qubits: int, qargs: tuple[int] + ) -> list[PulseQobjInstruction]: + """Return calibration sequence for given instruction (defined by name and num_qubits) acting on qargs.""" + + pulse_library = _PULSE_LIBRARY + # Note that the calibration pulses are different for + # 1q gates vs 2q gates vs measurement instructions. + if inst == "measure": + sequence = [ + PulseQobjInstruction( + name="acquire", + duration=1792, + t0=0, + qubits=list(range(num_qubits)), + memory_slot=list(range(num_qubits)), + ) + ] + [PulseQobjInstruction(name=pulse_library[1], ch=f"m{i}", t0=0) for i in qargs] + return sequence + if num_qubits == 1: + return [ + PulseQobjInstruction(name="fc", ch=f"u{qargs}", t0=0, phase="-P0"), + PulseQobjInstruction(name=pulse_library[0].name, ch=f"d{qargs}", t0=0), + ] + return [ + PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[0]}", t0=0), + PulseQobjInstruction(name=pulse_library[2].name, ch=f"u{qargs[0]}", t0=0), + PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[1]}", t0=0), + PulseQobjInstruction(name="fc", ch=f"d{qargs[1]}", t0=0, phase=2.1), ] - # Unless explicitly given a series of gates to calibrate, this method - # will generate empty pulse schedules for all gates in self._basis_gates. + def _generate_calibration_defaults(self) -> PulseDefaults: + """Generate pulse calibration defaults if specified via ``calibrate_instructions``.""" + + # If self._calibrate_instructions==True, this method + # will generate default pulse schedules for all gates in self._basis_gates, + # except for `delay` and `reset`. calibration_buffer = self._basis_gates.copy() for inst in ["delay", "reset"]: calibration_buffer.remove(inst) @@ -333,47 +354,29 @@ def _generate_calibration_defaults(self) -> PulseDefaults: num_qubits = self._supported_gates[inst].num_qubits qarg_set = self._coupling_map if num_qubits > 1 else list(range(self.num_qubits)) if inst == "measure": - sequence = [] - qubits = qarg_set - if self._calibrate_instructions: - sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=list(range(self.num_qubits)), - memory_slot=list(range(self.num_qubits)), - ) - ] + [PulseQobjInstruction(name="pulse_2", ch=f"m{i}", t0=0) for i in qarg_set] cmd_def.append( Command( name=inst, - qubits=qubits, - sequence=sequence, + qubits=qarg_set, + sequence=( + self._get_calibration_sequence(inst, num_qubits, qarg_set) + if self._calibrate_instructions + else [] + ), ) ) else: for qarg in qarg_set: - sequence = [] qubits = [qarg] if num_qubits == 1 else qarg - if self._calibrate_instructions: - if num_qubits == 1: - sequence = [ - PulseQobjInstruction(name="fc", ch=f"u{qarg}", t0=0, phase="-P0"), - PulseQobjInstruction(name="pulse_1", ch=f"d{qarg}", t0=0), - ] - else: - sequence = [ - PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[0]}", t0=0), - PulseQobjInstruction(name="pulse_3", ch=f"u{qarg[0]}", t0=0), - PulseQobjInstruction(name="pulse_2", ch=f"d{qarg[1]}", t0=0), - PulseQobjInstruction(name="fc", ch=f"d{qarg[1]}", t0=0, phase=2.1), - ] cmd_def.append( Command( name=inst, qubits=qubits, - sequence=sequence, + sequence=( + self._get_calibration_sequence(inst, num_qubits, qubits) + if self._calibrate_instructions + else [] + ), ) ) @@ -383,7 +386,7 @@ def _generate_calibration_defaults(self) -> PulseDefaults: qubit_freq_est=qubit_freq_est, meas_freq_est=meas_freq_est, buffer=0, - pulse_library=pulse_library, + pulse_library=_PULSE_LIBRARY, cmd_def=cmd_def, ) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 444b2f94dcca..5ed9e280b138 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -862,6 +862,7 @@ def test_final_measurement_barrier_for_devices(self): [15, 2], [15, 14], ] + with patch.object(BarrierBeforeFinalMeasurements, "run", wraps=orig_pass.run) as mock_pass: transpile( circ, @@ -2366,7 +2367,6 @@ def run(self, dag): # Create backend with empty calibrations (PulseQobjEntries) backend = GenericFakeBackend( - basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=4, calibrate_instructions=False, ) From fca016a77d11925cef66bbd178a519f35cf9d250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 25 Jan 2024 18:50:11 +0100 Subject: [PATCH 46/56] Add reno --- .../providers/fake_provider/fake_generic.py | 37 +++++------- ...generic-fake-backend-c1434e0c5c413935.yaml | 60 +++++++++++++++++++ 2 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 8804f43b73dd..f4603fabb2b3 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -203,21 +203,25 @@ def max_circuits(self): @property def dtm(self) -> float: - """Return the system time resolution of output signals - - Returns: - The output signal timestep in seconds. - """ - if self._dtm is not None: - # converting `dtm` from nanoseconds to seconds - return self._dtm * 1e-9 - else: - return None + """Return the system time resolution of output signals""" + # converting `dtm` from nanoseconds to seconds + return self._dtm * 1e-9 if self._dtm is not None else None @property def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements + def _build_default_channels(self) -> None: + channels_map = { + "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, + "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, + "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, + "control": { + (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) + }, + } + setattr(self, "channels_map", channels_map) + def _get_noise_defaults(self, name: str) -> tuple: return _NOISE_DEFAULTS.get(name, (1e-8, 9e-7, 1e-5, 5e-3)) @@ -337,7 +341,7 @@ def _get_calibration_sequence( ] def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate pulse calibration defaults if specified via ``calibrate_instructions``.""" + """Generate pulse calibration defaults if specified via `calibrate_instructions`.""" # If self._calibrate_instructions==True, this method # will generate default pulse schedules for all gates in self._basis_gates, @@ -501,17 +505,6 @@ def _default_options(cls) -> Options: else: return BasicAer.get_backend("qasm_simulator")._default_options() - def _build_default_channels(self) -> None: - channels_map = { - "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, - "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, - "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, - "control": { - (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) - }, - } - setattr(self, "channels_map", channels_map) - def drive_channel(self, qubit: int): drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) qubits = (qubit,) diff --git a/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml new file mode 100644 index 000000000000..5ed3d79457e8 --- /dev/null +++ b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml @@ -0,0 +1,60 @@ +--- +features: + - | + A new class, :class:`.GenericFakeBackend` has been added to the :mod:`qiskit.providers.fake_provider` + module. This class allows to build a configurable :class:`~.BackendV2` fake backend instance that can + be ran locally. Users can configure the number of qubits, basis gates, coupling map, + ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm of the + backend without having to deal with manual target construction. Qubit and gate properties are generated + by randomly sampling from default ranges. The seed for this random generation is always fixed + to ensure the reproducibility of the backend output, and can also be configured by the user. + + Example usage 1:: + + from qiskit import QuantumCircuit, transpile + from qiskit.providers.fake_provider import GenericFakeBackend + + # Create a simple circuit + circuit = QuantumCircuit(3) + circuit.h(0) + circuit.cx(0,1) + circuit.cx(0,2) + circuit.measure_all() + circuit.draw('mpl') + + # Define backend with 3 qubits + backend = GenericFakeBackend(num_qubits=3) + + # Transpile and run + transpiled_circuit = transpile(circuit, backend) + result = backend.run(transpiled_circuit).result() + + Example usage 2:: + + from qiskit import QuantumCircuit, ClassicalRegister, transpile + from qiskit.providers.fake_provider import GenericFakeBackend + + # Create a circuit with classical control + creg = ClassicalRegister(19) + qc = QuantumCircuit(25) + qc.add_register(creg) + qc.h(0) + for i in range(18): + qc.cx(0, i + 1) + for i in range(18): + qc.measure(i, creg[i]) + qc.ecr(20, 21).c_if(creg, 0) + + # Define backend with custom basis gates and control flow instructions + backend = GenericFakeBackend(num_qubits=25, basis_gates = ["ecr","id","rz","sx","x"], control_flow=True) + + #Transpile + transpiled_qc = transpile(qc, backend) + + .. note:: + + The noise properties generated by these class do not mimic any concrete quantum device, and should + not be used to measure any concrete behaviors. They are "reasonable defaults" that can be used to + test backend-interfacing functionality not tied specific noise values of real quantum systems. + For a more accurate simulation of existing devices, you can manually build a noise model from the + real backend using the functionality offered in ``qiskit-aer``. From de5bc0e1a9e2bbca3c3567aa1e6204cc85545fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 25 Jan 2024 19:29:55 +0100 Subject: [PATCH 47/56] Fix lint --- qiskit/providers/fake_provider/fake_generic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index f4603fabb2b3..04dbd522d1f3 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -312,7 +312,8 @@ def _add_noisy_instruction_to_target( def _get_calibration_sequence( self, inst: str, num_qubits: int, qargs: tuple[int] ) -> list[PulseQobjInstruction]: - """Return calibration sequence for given instruction (defined by name and num_qubits) acting on qargs.""" + """Return calibration sequence for given instruction (defined by name and num_qubits) + acting on qargs.""" pulse_library = _PULSE_LIBRARY # Note that the calibration pulses are different for From 099143f055264061063f062e5b3ff9c2d1f685aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:47:29 +0100 Subject: [PATCH 48/56] Apply suggestions from Matt's code review Co-authored-by: Matthew Treinish --- qiskit/providers/fake_provider/fake_generic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 04dbd522d1f3..20a4901ad736 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -123,9 +123,9 @@ def __init__( for the fake backend. Multiple formats are supported: #. :class:`~.CouplingMap` instance - #. List, must be given as an adjacency matrix, where each entry - specifies all directed two-qubit interactions supported by the backend, - e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` + #. List, must be given as an edge list representing the two qubit interactions + supported by the backend, for example: + ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` If ``coupling_map`` is specified, it must match the number of qubits specified in ``num_qubits``. If ``coupling_map`` is not specified, @@ -233,7 +233,7 @@ def _build_generic_target(self): # the qubit properties are sampled from default ranges properties = _QUBIT_PROPERTIES self._target = Target( - description=f"Generic Target with {self._num_qubits} qubits", + description=f"Target with {self._num_qubits} qubits", num_qubits=self._num_qubits, dt=properties["dt"], qubit_properties=[ From 54496db3127ac6a754004a5f77d55770375ad016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Sat, 27 Jan 2024 14:41:14 +0100 Subject: [PATCH 49/56] Apply comments from code review --- .../providers/fake_provider/fake_generic.py | 246 +++++++++--------- 1 file changed, 126 insertions(+), 120 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 04dbd522d1f3..e5c2b7eec92c 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -82,19 +82,16 @@ class GenericFakeBackend(BackendV2): - """ - Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, - locally runnable, fake backend. Users can configure the number of qubits, basis gates, coupling map, + """Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, + locally runnable, fake backend. One can configure the number of qubits, basis gates, coupling map, ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random generation is always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. + This backend only supports gates in the standard library, if you need a more flexible backend, + there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. """ - # Added backend_name for compatibility with - # snapshot-based fake backends. - backend_name = "GenericFakeBackend" - def __init__( self, num_qubits: int, @@ -103,8 +100,8 @@ def __init__( coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, calibrate_instructions: bool | InstructionScheduleMap | None = None, - dtm: float = None, - seed: int = 42, + dtm: float | None = None, + seed: int | None = None, ): """ Args: @@ -161,7 +158,7 @@ def __init__( backend_version="", ) - self.sim = None + self._sim = None self._rng = np.random.default_rng(seed=seed) self._dtm = dtm self._num_qubits = num_qubits @@ -223,97 +220,20 @@ def _build_default_channels(self) -> None: setattr(self, "channels_map", channels_map) def _get_noise_defaults(self, name: str) -> tuple: - return _NOISE_DEFAULTS.get(name, (1e-8, 9e-7, 1e-5, 5e-3)) - - def _build_generic_target(self): - """ - This method generates a :class:`~.Target` instance with - default qubit, instruction and calibration properties. - """ - # the qubit properties are sampled from default ranges - properties = _QUBIT_PROPERTIES - self._target = Target( - description=f"Generic Target with {self._num_qubits} qubits", - num_qubits=self._num_qubits, - dt=properties["dt"], - qubit_properties=[ - QubitProperties( - t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), - t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), - frequency=self._rng.uniform( - properties["frequency"][0], properties["frequency"][1] - ), - ) - for _ in range(self._num_qubits) - ], - concurrent_measurements=[list(range(self._num_qubits))], - ) - - # Iterate over gates, generate noise params from defaults, - # and add instructions to target. - for name in self._basis_gates: - if name not in self._supported_gates: - raise QiskitError( - f"Provided basis gate {name} is not an instruction " - f"in the standard qiskit circuit library." - ) - gate = self._supported_gates[name] - noise_params = self._get_noise_defaults(name) - self._add_noisy_instruction_to_target(gate, noise_params) - - if self._control_flow: - self._target.add_instruction(IfElseOp, name="if_else") - self._target.add_instruction(WhileLoopOp, name="while_loop") - self._target.add_instruction(ForLoopOp, name="for_loop") - self._target.add_instruction(SwitchCaseOp, name="switch_case") - self._target.add_instruction(BreakLoopOp, name="break") - self._target.add_instruction(ContinueLoopOp, name="continue") - - # Generate block of calibration defaults and add to target. - # Note: this could be improved if we could generate and add - # calibration defaults per-gate, and not as a block. - if self._calibrate_instructions is not None: - if isinstance(self._calibrate_instructions, InstructionScheduleMap): - inst_map = self._calibrate_instructions - else: - defaults = self._generate_calibration_defaults() - inst_map = defaults.instruction_schedule_map - self._add_calibrations_to_target(inst_map) - - def _add_noisy_instruction_to_target( - self, instruction: Instruction, noise_params: tuple[float, ...] | None - ) -> None: - """Add instruction properties to target for specified instruction. - - Args: - instruction (qiskit.circuit.Instruction): Instance of instruction to be added to the target - noise_params (tuple[float, ...] | None): error and duration noise values/ranges to - include in instruction properties. - - Returns: - None + """Return noise default values/ranges for duration and error of supported + instructions. There are two possible formats: + - (min_duration, max_duration, min_error, max_error), + if the defaults are ranges. + - (duration, error), if the defaults are fixed values. """ - qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self._num_qubits) - props = {} - for qarg in qarg_set: - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - duration, error = ( - noise_params - if len(noise_params) == 2 - else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) - ) - props.update({qargs: InstructionProperties(duration, error)}) - - self._target.add_instruction(instruction, props) + return _NOISE_DEFAULTS.get(name, (1e-8, 9e-7, 1e-5, 5e-3)) def _get_calibration_sequence( self, inst: str, num_qubits: int, qargs: tuple[int] ) -> list[PulseQobjInstruction]: - """Return calibration sequence for given instruction (defined by name and num_qubits) - acting on qargs.""" + """Return calibration pulse sequence for given instruction (defined by name and num_qubits) + acting on qargs. + """ pulse_library = _PULSE_LIBRARY # Note that the calibration pulses are different for @@ -342,7 +262,12 @@ def _get_calibration_sequence( ] def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate pulse calibration defaults if specified via `calibrate_instructions`.""" + """Generate pulse calibration defaults as specified with `self._calibrate_instructions`. + If `self._calibrate_instructions` is True, the pulse schedules will be generated from + a series of default calibration sequences. If `self._calibrate_instructions` is False, + the pulse schedules will contain empty calibration sequences, but still be generated and + added to the target. + """ # If self._calibrate_instructions==True, this method # will generate default pulse schedules for all gates in self._basis_gates, @@ -395,33 +320,114 @@ def _generate_calibration_defaults(self) -> PulseDefaults: cmd_def=cmd_def, ) - def _add_calibrations_to_target(self, inst_map: InstructionScheduleMap) -> None: - """Add calibration entries from provided pulse defaults to target. + def _build_generic_target(self): + """This method generates a :class:`~.Target` instance with + default qubit, instruction and calibration properties. + """ + # the qubit properties are sampled from default ranges + properties = _QUBIT_PROPERTIES + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=properties["dt"], + qubit_properties=[ + QubitProperties( + t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), + t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), + frequency=self._rng.uniform( + properties["frequency"][0], properties["frequency"][1] + ), + ) + for _ in range(self._num_qubits) + ], + concurrent_measurements=[list(range(self._num_qubits))], + ) - Args: - inst_map (InstructionScheduleMap): pulse defaults with instruction schedule map + # Generate instruction schedule map with calibrations to add to target. + calibration_inst_map = None + if self._calibrate_instructions is not None: + if isinstance(self._calibrate_instructions, InstructionScheduleMap): + calibration_inst_map = self._calibrate_instructions + else: + defaults = self._generate_calibration_defaults() + calibration_inst_map = defaults.instruction_schedule_map - Returns: - None + # Iterate over gates, generate noise params from defaults, + # and add instructions, noise and calibrations to target. + for name in self._basis_gates: + if name not in self._supported_gates: + raise QiskitError( + f"Provided basis gate {name} is not an instruction " + f"in the standard qiskit circuit library." + ) + gate = self._supported_gates[name] + noise_params = self._get_noise_defaults(name) + self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + + if self._control_flow: + self._target.add_instruction(IfElseOp, name="if_else") + self._target.add_instruction(WhileLoopOp, name="while_loop") + self._target.add_instruction(ForLoopOp, name="for_loop") + self._target.add_instruction(SwitchCaseOp, name="switch_case") + self._target.add_instruction(BreakLoopOp, name="break") + self._target.add_instruction(ContinueLoopOp, name="continue") + + def _add_noisy_instruction_to_target( + self, + instruction: Instruction, + noise_params: tuple[float, ...] | None, + calibration_inst_map: InstructionScheduleMap | None, + ) -> None: + """Add instruction properties to target for specified instruction. + + Args: + instruction: Instance of instruction to be added to the target + noise_params: Error and duration noise values/ranges to + include in instruction properties. + calibration_inst_map: Instruction schedule map with calibration defaults """ + qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self.num_qubits) + props = {} + for qarg in qarg_set: + try: + qargs = tuple(qarg) + except TypeError: + qargs = (qarg,) + duration, error = ( + noise_params + if len(noise_params) == 2 + else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) + ) + if ( + calibration_inst_map is not None + and instruction.name not in ["reset", "delay"] + and qarg in calibration_inst_map.qubits_with_instruction(instruction.name) + ): + # Do NOT call .get method. This parses Qobj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = calibration_inst_map._get_calibration_entry( + instruction.name, qargs + ) + else: + calibration_entry = None + props.update({qargs: InstructionProperties(duration, error, calibration_entry)}) + self._target.add_instruction(instruction, props) - # The calibration entries are directly injected into the gate map to - # avoid then being labeled as "user_provided". - for inst in inst_map.instructions: - for qarg in inst_map.qubits_with_instruction(inst): + # The "measure" instruction calibrations need to be added qubit by qubit, once the + # instruction has been added to the target. + if calibration_inst_map is not None and instruction.name == "measure": + for qarg in calibration_inst_map.qubits_with_instruction(instruction.name): try: qargs = tuple(qarg) except TypeError: qargs = (qarg,) # Do NOT call .get method. This parses Qobj immediately. # This operation is computationally expensive and should be bypassed. - calibration_entry = inst_map._get_calibration_entry(inst, qargs) - if inst in self._target._gate_map: - if inst == "measure": - for qubit in qargs: - self._target._gate_map[inst][(qubit,)].calibration = calibration_entry - elif qargs in self._target._gate_map[inst] and inst not in ["delay", "reset"]: - self._target._gate_map[inst][qargs].calibration = calibration_entry + calibration_entry = calibration_inst_map._get_calibration_entry( + instruction.name, qargs + ) + for qubit in qargs: + self._target[instruction.name][(qubit,)].calibration = calibration_entry def run(self, run_input, **options): """Run on the fake backend using a simulator. @@ -477,10 +483,10 @@ def run(self, run_input, **options): # circuit job if not _optionals.HAS_AER: warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) - if self.sim is None: + if self._sim is None: self._setup_sim() - self.sim._options = self._options - job = self.sim.run(circuits, **options) + self._sim._options = self._options + job = self._sim.run(circuits, **options) return job def _setup_sim(self) -> None: @@ -488,14 +494,14 @@ def _setup_sim(self) -> None: from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel - self.sim = AerSimulator() + self._sim = AerSimulator() noise_model = NoiseModel.from_backend(self) - self.sim.set_options(noise_model=noise_model) + self._sim.set_options(noise_model=noise_model) # Update fake backend default too to avoid overwriting # it when run() is called self.set_options(noise_model=noise_model) else: - self.sim = BasicAer.get_backend("qasm_simulator") + self._sim = BasicAer.get_backend("qasm_simulator") @classmethod def _default_options(cls) -> Options: From fe373936fb5e688cf8b15f096ccb7ad3d446378d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Sat, 27 Jan 2024 15:08:19 +0100 Subject: [PATCH 50/56] Fix lint --- qiskit/providers/fake_provider/fake_generic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index dd0a072fa851..2a5dea52ee30 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -82,14 +82,16 @@ class GenericFakeBackend(BackendV2): - """Configurable :class:`~.BackendV2` fake backend. This class will instantiate a partially configurable, - locally runnable, fake backend. One can configure the number of qubits, basis gates, coupling map, - ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. + """Generic :class:`~.BackendV2` fake backend. This class will instantiate a partially + configurable, locally runnable, fake backend. One can configure the number of qubits, + basis gates, coupling map, ability to run dynamic circuits (control flow instructions), + instruction calibrations and dtm. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random generation is always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. This backend only supports gates in the standard library, if you need a more flexible backend, - there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. + there is always the option to directly instantiate a :class:`.Target` object to use for + transpilation. """ def __init__( From a36acaffb253d20e057aee49603f39fd625db3c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 29 Jan 2024 11:21:46 +0100 Subject: [PATCH 51/56] Rename to GenericBackendV2 and adjust docs. --- qiskit/providers/fake_provider/__init__.py | 13 +-- .../providers/fake_provider/fake_generic.py | 40 ++++----- ...generic-fake-backend-c1434e0c5c413935.yaml | 25 +++--- test/python/compiler/test_transpiler.py | 82 +++++++++---------- .../fake_provider/test_fake_generic.py | 22 ++--- test/python/pulse/test_macros.py | 6 +- 6 files changed, 97 insertions(+), 91 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index c90990575fee..70a8c3fa2a0f 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -20,10 +20,11 @@ Overview ======== -The fake provider module contains fake providers and fake backends classes. The fake backends are -built to mimic the behaviors of IBM Quantum systems using system snapshots. The system snapshots -contain important information about the quantum system such as coupling map, basis gates, qubit -properties (T1, T2, error rate, etc.) which are useful for testing the transpiler and performing +The fake provider module contains fake providers, fake backends and other simulated backend +implementations. The fake backends are built to mimic the behaviors of IBM Quantum systems +using system snapshots. The system snapshots contain important information about the quantum +system such as coupling map, basis gates, qubit properties (T1, T2, error rate, etc.) which +are useful for testing the transpiler and performing noisy simulation of the system. Example Usage @@ -210,7 +211,7 @@ FakeBackendV2 FakeBackend5QV2 FakeMumbaiFractionalCX - GenericFakeBackend + GenericBackendV2 Fake Backend Base Classes ========================= @@ -249,4 +250,4 @@ from .fake_1q import Fake1Q from .fake_backend_v2 import FakeBackendV2, FakeBackend5QV2 from .fake_mumbai_v2 import FakeMumbaiFractionalCX -from .fake_generic import GenericFakeBackend +from .fake_generic import GenericBackendV2 diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 2a5dea52ee30..2d5dd6b4875d 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Generic fake BackendV2 class""" +"""Generic BackendV2 class that with a simulated ``run``.""" from __future__ import annotations import warnings @@ -70,7 +70,7 @@ # The number of samples determines the pulse durations of the corresponding # instructions. This default defines pulses with durations in multiples of -# 16 for consistency with the pulse granularity of real IBM devices, but +# 16 dt for consistency with the pulse granularity of real IBM devices, but # keeps the number smaller than what would be realistic for # manageability. If needed, more realistic durations could be added in the # future (order of 160dt for 1q gates, 1760dt for 2q gates and measure). @@ -81,14 +81,16 @@ ] -class GenericFakeBackend(BackendV2): - """Generic :class:`~.BackendV2` fake backend. This class will instantiate a partially - configurable, locally runnable, fake backend. One can configure the number of qubits, - basis gates, coupling map, ability to run dynamic circuits (control flow instructions), - instruction calibrations and dtm. - The remainder of the backend properties are generated by randomly sampling from default ranges - extracted from historical IBM backend data. The seed for this random generation is always fixed - to ensure the reproducibility of the backend output, and can also be configured by the user. +class GenericBackendV2(BackendV2): + """Generic :class:`~.BackendV2` implementation with a configurable constructor. This class will + return a :class:`~.BackendV2` instance that runs on a local simulator (in the spirit of fake + backends) and contains all the necessary information to test backend-interfacing components, such + as the transpiler. A :class:`.GenericBackendV2` instance can be constructed from as little as a + specified ``num_qubits``, but users can additionally configure the basis gates, coupling map, + ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. + The remainder of the backend properties are generated by randomly sampling + from default ranges extracted from historical IBM backend data. The seed for this random generation is + always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. This backend only supports gates in the standard library, if you need a more flexible backend, there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. @@ -109,7 +111,7 @@ def __init__( Args: num_qubits: Number of qubits that will be used to construct the backend's target. Note that, while there is no limit in the size of the target that can be - constructed, fake backends run on local noisy simulators, and these might + constructed, this backend runs on local noisy simulators, and these might present limitations in the number of qubits that can be simulated. basis_gates: List of basis gate names to be supported by @@ -119,7 +121,7 @@ def __init__( always supported by default, even if not specified via ``basis_gates``. coupling_map: Optional coupling map - for the fake backend. Multiple formats are supported: + for the backend. Multiple formats are supported: #. :class:`~.CouplingMap` instance #. List, must be given as an edge list representing the two qubit interactions @@ -155,8 +157,8 @@ def __init__( super().__init__( provider=None, - name=f"fake_generic_{num_qubits}q", - description=f"This is a fake device with {num_qubits} qubits and generic settings.", + name=f"generic_backend_{num_qubits}q", + description=f"This is a device with {num_qubits} qubits and generic settings.", backend_version="", ) @@ -432,7 +434,7 @@ def _add_noisy_instruction_to_target( self._target[instruction.name][(qubit,)].calibration = calibration_entry def run(self, run_input, **options): - """Run on the fake backend using a simulator. + """Run on the backend using a simulator. This method runs circuit jobs (an individual or a list of :class:`~.QuantumCircuit` ) and pulse jobs (an individual or a list of :class:`~.Schedule` or @@ -440,10 +442,10 @@ def run(self, run_input, **options): :class:`~qiskit.providers.Job` object. If qiskit-aer is installed, jobs will be run using the ``AerSimulator`` with - noise model of the fake backend. Otherwise, jobs will be run using the + noise model of the backend. Otherwise, jobs will be run using the ``BasicAer`` simulator without noise. - Noisy simulations of pulse jobs are not yet supported in :class:`~.GenericFakeBackend`. + Noisy simulations of pulse jobs are not yet supported in :class:`~.GenericBackendV2`. Args: run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An @@ -481,7 +483,7 @@ def run(self, run_input, **options): "QuantumCircuit, Schedule, or a list of either" % circuits ) if pulse_job: # pulse job - raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.") + raise QiskitError("Pulse simulation is currently not supported for V2 backends.") # circuit job if not _optionals.HAS_AER: warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) @@ -499,7 +501,7 @@ def _setup_sim(self) -> None: self._sim = AerSimulator() noise_model = NoiseModel.from_backend(self) self._sim.set_options(noise_model=noise_model) - # Update fake backend default too to avoid overwriting + # Update backend default too to avoid overwriting # it when run() is called self.set_options(noise_model=noise_model) else: diff --git a/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml index 5ed3d79457e8..a271ce3c498c 100644 --- a/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml +++ b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml @@ -1,18 +1,21 @@ --- features: - | - A new class, :class:`.GenericFakeBackend` has been added to the :mod:`qiskit.providers.fake_provider` - module. This class allows to build a configurable :class:`~.BackendV2` fake backend instance that can - be ran locally. Users can configure the number of qubits, basis gates, coupling map, - ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm of the - backend without having to deal with manual target construction. Qubit and gate properties are generated - by randomly sampling from default ranges. The seed for this random generation is always fixed - to ensure the reproducibility of the backend output, and can also be configured by the user. + A new class, :class:`.GenericBackendV2` has been added to the :mod:`qiskit.providers.fake_provider` + module. This class is configurable, and builds a :class:`~.BackendV2` backend instance that can + be ran locally (in the spirit of fake backends). Users can configure the number of qubits, basis gates, + coupling map, ability to run dynamic circuits (control flow instructions), instruction calibrations and + dtm of the backend without having to deal with manual target construction. + Qubit and gate properties are generated by randomly sampling from default ranges. The seed for this + random generation is always fixed to ensure the reproducibility of the backend output, and can also be + configured by the user. It's important to note that this backend only supports gates in the standard + library. If you need a more flexible backend, there is always the option to directly instantiate a + :class:`.Target` object to use for transpilation. Example usage 1:: from qiskit import QuantumCircuit, transpile - from qiskit.providers.fake_provider import GenericFakeBackend + from qiskit.providers.fake_provider import GenericBackendV2 # Create a simple circuit circuit = QuantumCircuit(3) @@ -23,7 +26,7 @@ features: circuit.draw('mpl') # Define backend with 3 qubits - backend = GenericFakeBackend(num_qubits=3) + backend = GenericBackendV2(num_qubits=3) # Transpile and run transpiled_circuit = transpile(circuit, backend) @@ -32,7 +35,7 @@ features: Example usage 2:: from qiskit import QuantumCircuit, ClassicalRegister, transpile - from qiskit.providers.fake_provider import GenericFakeBackend + from qiskit.providers.fake_provider import GenericBackendV2 # Create a circuit with classical control creg = ClassicalRegister(19) @@ -46,7 +49,7 @@ features: qc.ecr(20, 21).c_if(creg, 0) # Define backend with custom basis gates and control flow instructions - backend = GenericFakeBackend(num_qubits=25, basis_gates = ["ecr","id","rz","sx","x"], control_flow=True) + backend = GenericBackendV2(num_qubits=25, basis_gates = ["ecr","id","rz","sx","x"], control_flow=True) #Transpile transpiled_qc = transpile(qc, backend) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 5ed9e280b138..bdd51efb122f 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -71,7 +71,7 @@ from qiskit.dagcircuit import DAGOpNode, DAGOutNode from qiskit.exceptions import QiskitError from qiskit.providers.backend import BackendV2 -from qiskit.providers.fake_provider import GenericFakeBackend +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.providers.fake_provider import ( FakeMelbourne, FakeRueschlikon, @@ -239,7 +239,7 @@ def test_transpile_non_adjacent_layout(self): circuit.cx(qr[1], qr[2]) circuit.cx(qr[2], qr[3]) - backend = GenericFakeBackend( + backend = GenericBackendV2( num_qubits=15, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=cmap ) initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] @@ -327,7 +327,7 @@ def test_already_mapped_1(self): [15, 14], ] - backend = GenericFakeBackend(num_qubits=16, coupling_map=cmap) + backend = GenericBackendV2(num_qubits=16, coupling_map=cmap) coupling_map = backend.coupling_map basis_gates = backend.operation_names @@ -593,7 +593,7 @@ def test_transpile_singleton(self): def test_mapping_correction(self): """Test mapping works in previous failed case.""" - backend = GenericFakeBackend(num_qubits=12) + backend = GenericBackendV2(num_qubits=12) qr = QuantumRegister(name="qr", size=11) cr = ClassicalRegister(name="qc", size=11) circuit = QuantumCircuit(qr, cr) @@ -708,7 +708,7 @@ def test_transpiler_layout_from_intlist(self): def test_mapping_multi_qreg(self): """Test mapping works for multiple qregs.""" - backend = GenericFakeBackend(num_qubits=8) + backend = GenericBackendV2(num_qubits=8) qr = QuantumRegister(3, name="qr") qr2 = QuantumRegister(1, name="qr2") qr3 = QuantumRegister(4, name="qr3") @@ -725,7 +725,7 @@ def test_mapping_multi_qreg(self): def test_transpile_circuits_diff_registers(self): """Transpile list of circuits with different qreg names.""" - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) circuits = [] for _ in range(2): qr = QuantumRegister(2) @@ -741,7 +741,7 @@ def test_transpile_circuits_diff_registers(self): def test_wrong_initial_layout(self): """Test transpile with a bad initial layout.""" - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) qubit_reg = QuantumRegister(2, name="q") clbit_reg = ClassicalRegister(2, name="c") @@ -780,7 +780,7 @@ def test_parameterized_circuit_for_device(self): theta = Parameter("theta") qc.p(theta, qr[0]) - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) transpiled_qc = transpile( qc, @@ -819,7 +819,7 @@ def test_parameter_expression_circuit_for_device(self): theta = Parameter("theta") square = theta * theta qc.rz(square, qr[0]) - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) transpiled_qc = transpile( qc, @@ -877,7 +877,7 @@ def test_do_not_run_gatedirection_with_symmetric_cm(self): circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "example.qasm")) layout = Layout.generate_trivial_layout(*circ.qregs) coupling_map = [] - for node1, node2 in GenericFakeBackend(num_qubits=16).coupling_map: + for node1, node2 in GenericBackendV2(num_qubits=16).coupling_map: coupling_map.append([node1, node2]) coupling_map.append([node2, node1]) @@ -936,7 +936,7 @@ def test_pass_manager_empty(self): def test_move_measurements(self): """Measurements applied AFTER swap mapping.""" - cmap = GenericFakeBackend(num_qubits=16).coupling_map + cmap = GenericBackendV2(num_qubits=16).coupling_map qasm_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm") circ = QuantumCircuit.from_qasm_file(os.path.join(qasm_dir, "move_measurements.qasm")) @@ -979,7 +979,7 @@ def test_initialize_FakeMelbourne(self): qc = QuantumCircuit(qr) qc.initialize(desired_vector, [qr[0], qr[1], qr[2]]) - out = transpile(qc, backend=GenericFakeBackend(num_qubits=4)) + out = transpile(qc, backend=GenericBackendV2(num_qubits=4)) out_dag = circuit_to_dag(out) reset_nodes = out_dag.named_nodes("reset") @@ -1280,7 +1280,7 @@ def test_transpiled_custom_gates_calibration(self): transpiled_circuit = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), layout_method="trivial", ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1300,7 +1300,7 @@ def test_transpiled_basis_gates_calibrations(self): transpiled_circuit = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) @@ -1320,7 +1320,7 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): with self.assertRaises(QiskitError): transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), layout_method="trivial", ) @@ -1339,7 +1339,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): transpiled_circuit = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1362,7 +1362,7 @@ def test_transpile_subset_of_calibrated_gates(self): transpiled_circ = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) @@ -1383,14 +1383,14 @@ def q0_rxt(tau): transpiled_circ = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) circ = circ.assign_parameters({tau: 1}) transpiled_circ = transpile( circ, - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), layout_method="trivial", ) self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) @@ -1419,7 +1419,7 @@ def test_multiqubit_gates_calibrations(self, opt_level): custom_gate = Gate("my_custom_gate", 5, []) circ.append(custom_gate, [0, 1, 2, 3, 4]) circ.measure_all() - backend = GenericFakeBackend(num_qubits=6) + backend = GenericBackendV2(num_qubits=6) with pulse.build(backend=backend, name="custom") as my_schedule: pulse.play( @@ -1479,7 +1479,7 @@ def test_delay_converts_to_dt(self): qc = QuantumCircuit(2) qc.delay(1000, [0], unit="us") - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) backend.target.dt = 0.5e-6 out = transpile([qc, qc], backend) self.assertEqual(out[0].data[0].operation.unit, "dt") @@ -1497,7 +1497,7 @@ def test_scheduling_backend_v2(self): out = transpile( [qc, qc], - backend=GenericFakeBackend(num_qubits=4), + backend=GenericBackendV2(num_qubits=4), scheduling_method="alap", ) self.assertIn("delay", out[0].count_ops()) @@ -1579,7 +1579,7 @@ def test_transpile_optional_registers(self, optimization_level): qc.cx(1, 2) qc.measure(qubits, clbits) - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) out = transpile(qc, backend=backend, optimization_level=optimization_level) @@ -1704,7 +1704,7 @@ def test_target_ideal_gates(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" - target = GenericFakeBackend(num_qubits=8, control_flow=True).target + target = GenericBackendV2(num_qubits=8, control_flow=True).target circuit = QuantumCircuit(6, 1) circuit.h(0) @@ -1995,7 +1995,7 @@ def test_qpy_roundtrip(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=GenericFakeBackend(num_qubits=8), + backend=GenericBackendV2(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2012,7 +2012,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): """Test that the output of a transpiled circuit can be round-tripped through QPY.""" transpiled = transpile( self._regular_circuit(), - backend=GenericFakeBackend(num_qubits=8), + backend=GenericBackendV2(num_qubits=8), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2036,7 +2036,7 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): "See #10345 for more details." ) - backend = GenericFakeBackend(num_qubits=8, control_flow=True) + backend = GenericBackendV2(num_qubits=8, control_flow=True) transpiled = transpile( self._control_flow_circuit(), backend=backend, @@ -2058,7 +2058,7 @@ def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): through QPY.""" transpiled = transpile( self._control_flow_circuit(), - backend=GenericFakeBackend(num_qubits=8, control_flow=True), + backend=GenericBackendV2(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2079,7 +2079,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): "This test case triggers a bug in the eigensolver routine on windows. " "See #10345 for more details." ) - backend = GenericFakeBackend(num_qubits=16) + backend = GenericBackendV2(num_qubits=16) transpiled = transpile( self._control_flow_expr_circuit(), backend=backend, @@ -2098,7 +2098,7 @@ def test_qpy_roundtrip_control_flow_expr(self, optimization_level): def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow including `Expr` nodes can be round-tripped through QPY.""" - backend = GenericFakeBackend(num_qubits=27) + backend = GenericBackendV2(num_qubits=27) backend.target.add_instruction(IfElseOp, name="if_else") backend.target.add_instruction(ForLoopOp, name="for_loop") backend.target.add_instruction(WhileLoopOp, name="while_loop") @@ -2135,7 +2135,7 @@ def test_qasm3_output_control_flow(self, optimization_level): OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=GenericFakeBackend(num_qubits=8, control_flow=True), + backend=GenericBackendV2(num_qubits=8, control_flow=True), optimization_level=optimization_level, seed_transpiler=2022_10_17, ) @@ -2153,7 +2153,7 @@ def test_qasm3_output_control_flow_expr(self, optimization_level): dumped into OpenQASM 3.""" transpiled = transpile( self._control_flow_circuit(), - backend=GenericFakeBackend(num_qubits=27, control_flow=True), + backend=GenericBackendV2(num_qubits=27, control_flow=True), optimization_level=optimization_level, seed_transpiler=2023_07_26, ) @@ -2306,7 +2306,7 @@ def test_parallel_multiprocessing(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - pm = generate_preset_pass_manager(opt_level, backend=GenericFakeBackend(num_qubits=4)) + pm = generate_preset_pass_manager(opt_level, backend=GenericBackendV2(num_qubits=4)) res = pm.run([qc, qc]) for circ in res: self.assertIsInstance(circ, QuantumCircuit) @@ -2318,7 +2318,7 @@ def test_parallel_with_target(self, opt_level): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = GenericFakeBackend(num_qubits=4).target + target = GenericBackendV2(num_qubits=4).target res = transpile([qc] * 3, target=target, optimization_level=opt_level) self.assertIsInstance(res, list) for circ in res: @@ -2366,7 +2366,7 @@ def run(self, dag): return dag # Create backend with empty calibrations (PulseQobjEntries) - backend = GenericFakeBackend( + backend = GenericBackendV2( num_qubits=4, calibrate_instructions=False, ) @@ -2389,7 +2389,7 @@ def run(self, dag): @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): """Test that singleton mutable instance doesn't lose state in parallel.""" - backend = GenericFakeBackend(num_qubits=27) + backend = GenericBackendV2(num_qubits=27) circ = QuantumCircuit(2, 1) circ.h(0) circ.measure(0, circ.clbits[0]) @@ -2403,7 +2403,7 @@ def test_parallel_singleton_conditional_gate(self, opt_level): @data(0, 1, 2, 3) def test_backendv2_and_basis_gates(self, opt_level): """Test transpile() with BackendV2 and basis_gates set.""" - backend = GenericFakeBackend(num_qubits=6) + backend = GenericBackendV2(num_qubits=6) qc = QuantumCircuit(5) qc.h(0) qc.cz(0, 1) @@ -2440,7 +2440,7 @@ def test_backendv2_and_coupling_map(self, opt_level): cmap = CouplingMap.from_line(5, bidirectional=False) tqc = transpile( qc, - backend=GenericFakeBackend(num_qubits=6), + backend=GenericBackendV2(num_qubits=6), coupling_map=cmap, optimization_level=opt_level, seed_transpiler=12345678942, @@ -2455,7 +2455,7 @@ def test_backendv2_and_coupling_map(self, opt_level): def test_transpile_with_multiple_coupling_maps(self): """Test passing a different coupling map for every circuit""" - backend = GenericFakeBackend(num_qubits=4) + backend = GenericBackendV2(num_qubits=4) qc = QuantumCircuit(3) qc.cx(0, 2) @@ -2478,7 +2478,7 @@ def test_transpile_with_multiple_coupling_maps(self): @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = GenericFakeBackend( + backend = GenericBackendV2( num_qubits=5, coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], ) @@ -3364,7 +3364,7 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): qc = QuantumCircuit(127) for i in range(1, 127): qc.ecr(0, i) - backend = GenericFakeBackend(num_qubits=130) + backend = GenericBackendV2(num_qubits=130) original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 61b5bd463c07..8a6d6132689c 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -10,20 +10,20 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" Test of GenericFakeBackend backend""" +""" Test of GenericBackendV2 backend""" import math from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit import transpile -from qiskit.providers.fake_provider import GenericFakeBackend +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler import CouplingMap from qiskit.exceptions import QiskitError from qiskit.test import QiskitTestCase -class TestGenericFakeBackend(QiskitTestCase): - """Test class for GenericFakeBackend backend""" +class TestGenericBackendV2(QiskitTestCase): + """Test class for GenericBackendV2 backend""" def setUp(self): super().setUp() @@ -34,17 +34,17 @@ def setUp(self): def test_supported_basis_gates(self): """Test that target raises error if basis_gate not in ``supported_names``.""" with self.assertRaises(QiskitError): - GenericFakeBackend(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) + GenericBackendV2(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" - target = GenericFakeBackend(num_qubits=8) + target = GenericBackendV2(num_qubits=8) op_names = list(target.operation_names) op_names.sort() self.assertEqual(op_names, ["cx", "delay", "id", "measure", "reset", "rz", "sx", "x"]) - target = GenericFakeBackend(num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"]) + target = GenericBackendV2(num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"]) op_names = list(target.operation_names) op_names.sort() self.assertEqual(op_names, ["delay", "ecr", "id", "measure", "reset", "rz", "sx", "x"]) @@ -52,11 +52,11 @@ def test_operation_names(self): def test_incompatible_coupling_map(self): """Test that the size of the coupling map must match num_qubits.""" with self.assertRaises(QiskitError): - GenericFakeBackend(num_qubits=5, coupling_map=self.cmap) + GenericBackendV2(num_qubits=5, coupling_map=self.cmap) def test_control_flow_operation_names(self): """Test that control flow instructions are added to the target if control_flow is True.""" - target = GenericFakeBackend( + target = GenericBackendV2( num_qubits=8, basis_gates=["ecr", "id", "rz", "sx", "x"], coupling_map=self.cmap, @@ -90,7 +90,7 @@ def test_default_coupling_map(self): (1, 3), (3, 1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3)] # fmt: on self.assertEqual( - list(GenericFakeBackend(num_qubits=5).coupling_map.get_edges()), + list(GenericBackendV2(num_qubits=5).coupling_map.get_edges()), reference_cmap, ) @@ -105,7 +105,7 @@ def test_run(self): qc.cx(qr[0], qr[k]) qc.measure(qr, cr) - backend = GenericFakeBackend(num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"]) + backend = GenericBackendV2(num_qubits=5, basis_gates=["cx", "id", "rz", "sx", "x"]) tqc = transpile(qc, backend=backend, optimization_level=3, seed_transpiler=42) result = backend.run(tqc, seed_simulator=42, shots=1000).result() counts = result.get_counts() diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 1d2a1aac77bf..47dc51909fac 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -24,7 +24,7 @@ ) from qiskit.pulse import macros from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, GenericFakeBackend +from qiskit.providers.fake_provider import FakeOpenPulse2Q, FakeHanoi, GenericBackendV2 from qiskit.test import QiskitTestCase @@ -37,7 +37,7 @@ def setUp(self): self.backend_v1 = FakeHanoi() self.inst_map = self.backend.defaults().instruction_schedule_map - self.backend_v2 = GenericFakeBackend( + self.backend_v2 = GenericBackendV2( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=27, calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, @@ -214,7 +214,7 @@ def setUp(self): super().setUp() self.backend_v1 = FakeOpenPulse2Q() self.inst_map = self.backend_v1.defaults().instruction_schedule_map - self.backend_v2 = GenericFakeBackend( + self.backend_v2 = GenericBackendV2( basis_gates=["cx", "id", "rz", "sx", "x"], num_qubits=2, calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, From f1816ea983711138d46c2776c246a17b8d5e6b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 29 Jan 2024 13:41:21 +0100 Subject: [PATCH 52/56] Fix lint --- qiskit/providers/fake_provider/fake_generic.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/fake_generic.py index 2d5dd6b4875d..505bb9337f91 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/fake_generic.py @@ -89,8 +89,9 @@ class GenericBackendV2(BackendV2): specified ``num_qubits``, but users can additionally configure the basis gates, coupling map, ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. The remainder of the backend properties are generated by randomly sampling - from default ranges extracted from historical IBM backend data. The seed for this random generation is - always fixed to ensure the reproducibility of the backend output, and can also be configured by the user. + from default ranges extracted from historical IBM backend data. The seed for this random + generation is always fixed to ensure the reproducibility of the backend output, and can also be + configured by the user. This backend only supports gates in the standard library, if you need a more flexible backend, there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. From 741831ef6817a96b8d2f7037c294f3848112c0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 29 Jan 2024 18:49:13 +0100 Subject: [PATCH 53/56] Apply comments from code review --- qiskit/providers/fake_provider/__init__.py | 2 +- .../{fake_generic.py => generic_backend_v2.py} | 12 +++++++----- .../add-generic-fake-backend-c1434e0c5c413935.yaml | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) rename qiskit/providers/fake_provider/{fake_generic.py => generic_backend_v2.py} (98%) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 70a8c3fa2a0f..d553379a75ce 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -250,4 +250,4 @@ from .fake_1q import Fake1Q from .fake_backend_v2 import FakeBackendV2, FakeBackend5QV2 from .fake_mumbai_v2 import FakeMumbaiFractionalCX -from .fake_generic import GenericBackendV2 +from .generic_backend_v2 import GenericBackendV2 diff --git a/qiskit/providers/fake_provider/fake_generic.py b/qiskit/providers/fake_provider/generic_backend_v2.py similarity index 98% rename from qiskit/providers/fake_provider/fake_generic.py rename to qiskit/providers/fake_provider/generic_backend_v2.py index 505bb9337f91..33f69a2ddaba 100644 --- a/qiskit/providers/fake_provider/fake_generic.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2023. +# (C) Copyright IBM 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -60,6 +60,9 @@ "reset": (None, None), } +# Fallback values for gates with unknown noise default ranges. +_NOISE_DEFAULTS_FALLBACK = (1e-8, 9e-7, 1e-5, 5e-3) + # Ranges to sample qubit properties from. _QUBIT_PROPERTIES = { "dt": 0.222e-9, @@ -90,8 +93,7 @@ class GenericBackendV2(BackendV2): ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random - generation is always fixed to ensure the reproducibility of the backend output, and can also be - configured by the user. + generation can be fixed to ensure the reproducibility of the backend output. This backend only supports gates in the standard library, if you need a more flexible backend, there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. @@ -146,8 +148,8 @@ def __init__( qubits using the default pulse schedules generated internally. #. If ``calibrate_instructions==False``, all gates will be "calibrated" for all qubits with an empty pulse schedule. - #. If an :class:`.InstructionScheduleMap` instance is given, this calibrations - present in this instruction schedule map will be appended to the target + #. If an :class:`.InstructionScheduleMap` instance is given, the calibrations + in this instruction schedule map will be appended to the target instead of the default pulse schedules (this allows for custom calibrations). dtm: System time resolution of output signals in nanoseconds. diff --git a/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml index a271ce3c498c..00f7eeea6800 100644 --- a/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml +++ b/releasenotes/notes/add-generic-fake-backend-c1434e0c5c413935.yaml @@ -3,12 +3,12 @@ features: - | A new class, :class:`.GenericBackendV2` has been added to the :mod:`qiskit.providers.fake_provider` module. This class is configurable, and builds a :class:`~.BackendV2` backend instance that can - be ran locally (in the spirit of fake backends). Users can configure the number of qubits, basis gates, + be run locally (in the spirit of fake backends). Users can configure the number of qubits, basis gates, coupling map, ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm of the backend without having to deal with manual target construction. Qubit and gate properties are generated by randomly sampling from default ranges. The seed for this - random generation is always fixed to ensure the reproducibility of the backend output, and can also be - configured by the user. It's important to note that this backend only supports gates in the standard + random generation can be fixed to ensure the reproducibility of the backend output. + It's important to note that this backend only supports gates in the standard library. If you need a more flexible backend, there is always the option to directly instantiate a :class:`.Target` object to use for transpilation. From ee588ccd7d642a45043d12229760f215d3162811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 29 Jan 2024 18:51:52 +0100 Subject: [PATCH 54/56] It's not 2023 anymore --- qiskit/providers/fake_provider/__init__.py | 2 +- test/python/compiler/test_transpiler.py | 2 +- test/python/providers/fake_provider/test_fake_generic.py | 2 +- test/python/pulse/test_macros.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index d553379a75ce..7738c7ff244a 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022. +# (C) Copyright IBM 2022, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index bdd51efb122f..4304d0794c7c 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2023. +# (C) Copyright IBM 2017, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory diff --git a/test/python/providers/fake_provider/test_fake_generic.py b/test/python/providers/fake_provider/test_fake_generic.py index 8a6d6132689c..0c9740f3a285 100644 --- a/test/python/providers/fake_provider/test_fake_generic.py +++ b/test/python/providers/fake_provider/test_fake_generic.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2023. +# (C) Copyright IBM 2023, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 47dc51909fac..99cb873c67e2 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2023 +# (C) Copyright IBM 2019, 2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory From cba3eba8e97c2b03d8862a63b4c0c6245baa6067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 29 Jan 2024 20:26:52 +0100 Subject: [PATCH 55/56] Fix conflict --- test/python/compiler/test_transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index b98021283c56..bbe68d83acf2 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -156,7 +156,7 @@ def test_num_processes_kwarg_concurrent_default(self, num_processes): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeMumbaiV2().target + target = GenericBackendV2(num_qubits=27).target res = transpile([qc] * 3, target=target, num_processes=num_processes) self.assertIsInstance(res, list) for circ in res: From 72d1dee5d8af8534014aa89c64b9557b90554903 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Mon, 29 Jan 2024 15:29:07 -0500 Subject: [PATCH 56/56] Fix test conflict. --- test/python/compiler/test_transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index bbe68d83acf2..1aa34edbb50c 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2344,7 +2344,7 @@ def test_parallel_num_processes_kwarg(self, num_processes): qc.h(0) qc.cx(0, 1) qc.measure_all() - target = FakeMumbaiV2().target + target = GenericBackendV2(num_qubits=27).target res = transpile([qc] * 3, target=target, num_processes=num_processes) self.assertIsInstance(res, list) for circ in res: