diff --git a/docs/_ext/custom_styles/example/example_experiment.py b/docs/_ext/custom_styles/example/example_experiment.py index 6c42e7e593..7c459bf6b7 100644 --- a/docs/_ext/custom_styles/example/example_experiment.py +++ b/docs/_ext/custom_styles/example/example_experiment.py @@ -212,5 +212,5 @@ def __init__(self, qubit: int): """ super().__init__(qubits=[qubit]) - def circuits(self, backend=None): + def _circuits(self, backend=None): pass diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 2799cdef2a..730fc465f7 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -22,7 +22,6 @@ from qiskit.providers import BaseJob from qiskit.providers.backend import Backend from qiskit.providers.basebackend import BaseBackend as LegacyBackend -from qiskit.test.mock import FakeBackend from qiskit.exceptions import QiskitError from qiskit.qobj.utils import MeasLevel from qiskit_experiments.framework import Options @@ -96,10 +95,6 @@ def run( Returns: The experiment data object. - - Raises: - QiskitError: if experiment is run with an incompatible existing - ExperimentData container. """ # Create experiment data container experiment_data = self._initialize_experiment_data(backend, experiment_data) @@ -109,24 +104,8 @@ def run( run_opts.update_options(**run_options) run_opts = run_opts.__dict__ - # Scheduling parameters - if backend.configuration().simulator is False and isinstance(backend, FakeBackend) is False: - timing_constraints = getattr(self.transpile_options.__dict__, "timing_constraints", {}) - timing_constraints["acquire_alignment"] = getattr( - timing_constraints, "acquire_alignment", 16 - ) - scheduling_method = getattr( - self.transpile_options.__dict__, "scheduling_method", "alap" - ) - self.set_transpile_options( - timing_constraints=timing_constraints, scheduling_method=scheduling_method - ) - # Generate and transpile circuits - transpile_opts = copy.copy(self.transpile_options.__dict__) - transpile_opts["initial_layout"] = list(self._physical_qubits) - circuits = transpile(self.circuits(backend), backend, **transpile_opts) - self._postprocess_transpiled_circuits(circuits, backend, **run_options) + circuits = self.circuits(backend, run_transpile=True) # Run experiment jobs max_experiments = getattr(backend.configuration(), "max_experiments", None) @@ -158,6 +137,7 @@ def run( # Optionally run analysis if analysis and self.__analysis_class__: experiment_data.add_analysis_callback(self.run_analysis) + experiment_data.add_analysis_callback(self._post_analysis_action) # Return the ExperimentData future return experiment_data @@ -181,6 +161,106 @@ def _initialize_experiment_data( return experiment_data._copy_metadata() + def _pre_transpile_action(self, backend: Backend): + """An extra subroutine executed before transpilation. + + Note: + This method may be implemented by a subclass that requires to update the + transpiler configuration based on the given backend instance, + otherwise the transpiler configuration should be updated with the + :py:meth:`_default_transpile_options` method. + + For example, some specific transpiler options might change depending on the real + hardware execution or circuit simulator execution. + By default, this method does nothing. + + Args: + backend: Target backend. + """ + pass + + # pylint: disable = unused-argument + def _post_transpile_action( + self, circuits: List[QuantumCircuit], backend: Backend + ) -> List[QuantumCircuit]: + """An extra subroutine executed after transpilation. + + Note: + This method may be implemented by a subclass that requires to update the + circuit or its metadata after transpilation. + Without this method, the transpiled circuit will be immediately executed on the backend. + This method enables the experiment to modify the circuit with pulse gates, + or some extra metadata regarding the transpiled sequence of instructions. + + By default, this method just passes transpiled circuits to the execution chain. + + Args: + circuits: List of transpiled circuits. + backend: Target backend. + + Returns: + List of circuits to execute. + """ + return circuits + + def circuits( + self, backend: Optional[Backend] = None, run_transpile: bool = False, + ) -> List[QuantumCircuit]: + """Run transpile and return transpiled circuits. + + Args: + backend: Target backend. + run_transpile: Set ``True`` to run transpile, otherwise this returns logical circuits. + The logical circuits are convenient to grasp overview of the experiment, + while transpiled circuits can be used to scrutinize the program actually + executed on the backend. This defaults to ``False``. + + Returns: + Experiment circuits. + + Raises: + QiskitError: When transpile is performed without backend instance. + """ + logical_circs = self._circuits(backend) + + if not run_transpile: + return logical_circs + + # Run pre transpile if implemented by subclasses. + self._pre_transpile_action(backend) + + # Get transpile options + circuits = transpile( + circuits=logical_circs, backend=backend, **self.transpile_options.__dict__ + ) + + # Run post transpile. This is implemented by each experiment subclass. + circuits = self._post_transpile_action(circuits, backend) + + return circuits + + def _post_analysis_action(self, experiment_data: ExperimentData): + """An extra subroutine executed after analysis. + + Note: + This method may be implemented by a subclass that requires to perform + extra data processing based on the analyzed experimental result. + + Note that the analysis routine will not complete until the backend job + is executed, and this method will be called after the analysis routine + is completed though a handler of the experiment result will be immediately + returned to users (a future object). This method is automatically triggered + when the analysis is finished, and will be processed in background. + + If this method updates some other (mutable) objects, you may need manage + synchronization of update of the object data. + By default, this method does nothing. + + Args: + experiment_data: A future object of the experimental result. + """ + pass + def run_analysis(self, experiment_data: ExperimentData, **options) -> ExperimentData: """Run analysis and update ExperimentData with analysis result. @@ -194,7 +274,7 @@ def run_analysis(self, experiment_data: ExperimentData, **options) -> Experiment An experiment data object containing the analysis results and figures. Raises: - QiskitError: if experiment_data container is not valid for analysis. + QiskitError: Method is called with an empty experiment result. """ # Get analysis options analysis_options = copy.copy(self.analysis_options) @@ -230,8 +310,8 @@ def analysis(cls): return cls.__analysis_class__() @abstractmethod - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: - """Return a list of experiment circuits. + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + """Return a list of logical circuits with logical qubit index. Args: backend: Optional, a backend object. @@ -349,10 +429,6 @@ def set_analysis_options(self, **fields): """ self._analysis_options.update_options(**fields) - def _postprocess_transpiled_circuits(self, circuits, backend, **run_options): - """Additional post-processing of transpiled circuits before running on backend""" - pass - def _metadata(self) -> Dict[str, any]: """Return experiment metadata for ExperimentData. @@ -379,7 +455,12 @@ def _additional_metadata(self) -> Dict[str, any]: """ return {} - def _add_job_metadata(self, experiment_data: ExperimentData, jobs: BaseJob, **run_options): + def _add_job_metadata( + self, + experiment_data: ExperimentData, + jobs: List[BaseJob], + **run_options, + ): """Add runtime job metadata to ExperimentData. Args: diff --git a/qiskit_experiments/framework/composite/batch_experiment.py b/qiskit_experiments/framework/composite/batch_experiment.py index c6d0a71ada..e18d0d99f8 100644 --- a/qiskit_experiments/framework/composite/batch_experiment.py +++ b/qiskit_experiments/framework/composite/batch_experiment.py @@ -41,7 +41,7 @@ def __init__(self, experiments): qubits = tuple(self._qubit_map.keys()) super().__init__(experiments, qubits) - def circuits(self, backend=None): + def _circuits(self, backend=None): batch_circuits = [] @@ -51,7 +51,7 @@ def circuits(self, backend=None): qubit_mapping = None else: qubit_mapping = [self._qubit_map[qubit] for qubit in expr.physical_qubits] - for circuit in expr.circuits(backend): + for circuit in expr._circuits(backend): # Update metadata circuit.metadata = { "experiment_type": self._type, diff --git a/qiskit_experiments/framework/composite/composite_experiment.py b/qiskit_experiments/framework/composite/composite_experiment.py index 5c41d0e0f2..7b32df81b6 100644 --- a/qiskit_experiments/framework/composite/composite_experiment.py +++ b/qiskit_experiments/framework/composite/composite_experiment.py @@ -41,7 +41,7 @@ def __init__(self, experiments, qubits, experiment_type=None): super().__init__(qubits, experiment_type=experiment_type) @abstractmethod - def circuits(self, backend=None): + def _circuits(self, backend=None): pass @property diff --git a/qiskit_experiments/framework/composite/parallel_experiment.py b/qiskit_experiments/framework/composite/parallel_experiment.py index 286b6c8efc..9b989ab7eb 100644 --- a/qiskit_experiments/framework/composite/parallel_experiment.py +++ b/qiskit_experiments/framework/composite/parallel_experiment.py @@ -32,7 +32,7 @@ def __init__(self, experiments): qubits += exp.physical_qubits super().__init__(experiments, qubits) - def circuits(self, backend=None): + def _circuits(self, backend=None): sub_circuits = [] sub_qubits = [] @@ -42,7 +42,7 @@ def circuits(self, backend=None): # Generate data for combination for expr in self._experiments: # Add subcircuits - circs = expr.circuits(backend) + circs = expr._circuits(backend) sub_circuits.append(circs) sub_size.append(len(circs)) diff --git a/qiskit_experiments/library/calibration/drag.py b/qiskit_experiments/library/calibration/drag.py index f939abef74..bcca76c6d7 100644 --- a/qiskit_experiments/library/calibration/drag.py +++ b/qiskit_experiments/library/calibration/drag.py @@ -140,7 +140,7 @@ def __init__(self, qubit: int): super().__init__([qubit]) - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Create the circuits for the Drag calibration. Args: diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index a96413aecd..f60f69be43 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -183,7 +183,7 @@ def _pre_circuit(self) -> QuantumCircuit: return circuit - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Create the circuits for the fine amplitude calibration experiment. Args: diff --git a/qiskit_experiments/library/calibration/fine_drag.py b/qiskit_experiments/library/calibration/fine_drag.py index 318b54acf2..ad620d1c21 100644 --- a/qiskit_experiments/library/calibration/fine_drag.py +++ b/qiskit_experiments/library/calibration/fine_drag.py @@ -185,7 +185,7 @@ def _post_circuit() -> QuantumCircuit: circ.ry(np.pi / 2, 0) # Maps unwanted Z rotations to qubit population. return circ - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Create the circuits for the fine DRAG calibration experiment. Args: diff --git a/qiskit_experiments/library/calibration/rabi.py b/qiskit_experiments/library/calibration/rabi.py index a85998ae1c..c1f31b3faa 100644 --- a/qiskit_experiments/library/calibration/rabi.py +++ b/qiskit_experiments/library/calibration/rabi.py @@ -144,7 +144,7 @@ def _default_gate_schedule(self, backend: Optional[Backend] = None): return default_schedule - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Create the circuits for the Rabi experiment. Args: diff --git a/qiskit_experiments/library/calibration/ramsey_xy.py b/qiskit_experiments/library/calibration/ramsey_xy.py index 7fa7d5d5ec..17ff542287 100644 --- a/qiskit_experiments/library/calibration/ramsey_xy.py +++ b/qiskit_experiments/library/calibration/ramsey_xy.py @@ -132,7 +132,7 @@ def _pre_circuit(self) -> QuantumCircuit: """ return QuantumCircuit(1) - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Create the circuits for the Ramsey XY calibration experiment. Args: diff --git a/qiskit_experiments/library/characterization/cr_hamiltonian.py b/qiskit_experiments/library/characterization/cr_hamiltonian.py index 9d44ac925d..84d9953788 100644 --- a/qiskit_experiments/library/characterization/cr_hamiltonian.py +++ b/qiskit_experiments/library/characterization/cr_hamiltonian.py @@ -252,7 +252,7 @@ def _build_cr_schedule( return cross_resonance - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Return a list of experiment circuits. Args: diff --git a/qiskit_experiments/library/characterization/qubit_spectroscopy.py b/qiskit_experiments/library/characterization/qubit_spectroscopy.py index a1224d2aba..5907cebbef 100644 --- a/qiskit_experiments/library/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/library/characterization/qubit_spectroscopy.py @@ -158,7 +158,7 @@ def _template_circuit(self, freq_param) -> QuantumCircuit: return circuit - def circuits(self, backend: Optional[Backend] = None): + def _circuits(self, backend: Optional[Backend] = None): """Create the circuit for the spectroscopy experiment. The circuits are based on a GaussianSquare pulse and a frequency_shift instruction diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index d03b9761c1..2e597b15f8 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -14,10 +14,11 @@ """ from typing import List, Optional, Union -import numpy as np -from qiskit.providers import Backend +import numpy as np from qiskit.circuit import QuantumCircuit +from qiskit.providers import Backend +from qiskit.test.mock import FakeBackend from qiskit_experiments.framework import BaseExperiment, Options from qiskit_experiments.library.characterization.t1_analysis import T1Analysis @@ -89,7 +90,7 @@ def __init__( # Set experiment options self.set_experiment_options(delays=delays, unit=unit) - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """ Return a list of experiment circuits @@ -131,3 +132,11 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: circuits.append(circ) return circuits + + def _pre_transpile_action(self, backend: Backend): + """Set schedule method if not simulator.""" + is_simulator = getattr(backend.configuration(), "simulator", False) + + if not is_simulator and not isinstance(backend, FakeBackend): + if "scheduling_method" not in self.transpile_options.__dict__: + self.set_transpile_options(scheduling_method="alap") diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index abb93d486f..8a1af6b9cc 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -15,12 +15,14 @@ """ from typing import List, Optional, Union -import numpy as np +import numpy as np import qiskit -from qiskit.utils import apply_prefix -from qiskit.providers import Backend from qiskit.circuit import QuantumCircuit +from qiskit.providers import Backend +from qiskit.test.mock import FakeBackend +from qiskit.utils import apply_prefix + from qiskit_experiments.framework import BaseExperiment, Options from .t2ramsey_analysis import T2RamseyAnalysis @@ -102,7 +104,7 @@ def __init__( super().__init__([qubit]) self.set_experiment_options(delays=delays, unit=unit, osc_freq=osc_freq) - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Return a list of experiment circuits. Each circuit consists of a Hadamard gate, followed by a fixed delay, @@ -154,3 +156,11 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: circuits.append(circ) return circuits + + def _pre_transpile_action(self, backend: Backend): + """Set schedule method if not simulator.""" + is_simulator = getattr(backend.configuration(), "simulator", False) + + if not is_simulator and not isinstance(backend, FakeBackend): + if "scheduling_method" not in self.transpile_options.__dict__: + self.set_transpile_options(scheduling_method="alap") diff --git a/qiskit_experiments/library/quantum_volume/qv_experiment.py b/qiskit_experiments/library/quantum_volume/qv_experiment.py index acfea48c74..7d9af886ed 100644 --- a/qiskit_experiments/library/quantum_volume/qv_experiment.py +++ b/qiskit_experiments/library/quantum_volume/qv_experiment.py @@ -150,7 +150,7 @@ def _get_ideal_data(self, circuit: QuantumCircuit, **run_options) -> List[float] probabilities = state_vector.probabilities() return probabilities - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Return a list of Quantum Volume circuits. Args: diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py index d917dd47fd..d9059a5d8a 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py @@ -129,7 +129,7 @@ def _default_experiment_options(cls) -> Options: return options # pylint: disable = arguments-differ - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + def _circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: """Return a list of RB circuits. Args: @@ -226,12 +226,16 @@ def _get_circuit_metadata(self, circuit): return meta return None - def _postprocess_transpiled_circuits(self, circuits, backend, **run_options): - """Additional post-processing of transpiled circuits before running on backend""" - for c in circuits: - meta = self._get_circuit_metadata(c) + def _post_transpile_action( + self, circuits: List[QuantumCircuit], backend: Backend + ) -> List[QuantumCircuit]: + """Count gate operations in each circuit and update metadata.""" + for circuit in circuits: + meta = self._get_circuit_metadata(circuit) if meta is not None: - c_count_ops = RBUtils.count_ops(c, self.physical_qubits) + c_count_ops = RBUtils.count_ops(circuit, self.physical_qubits) circuit_length = meta["xval"] count_ops = [(key, (value, circuit_length)) for key, value in c_count_ops.items()] meta.update({"count_ops": count_ops}) + + return circuits diff --git a/qiskit_experiments/library/tomography/tomography_experiment.py b/qiskit_experiments/library/tomography/tomography_experiment.py index 74f2a11363..69b1dc307e 100644 --- a/qiskit_experiments/library/tomography/tomography_experiment.py +++ b/qiskit_experiments/library/tomography/tomography_experiment.py @@ -140,7 +140,7 @@ def _metadata(self): metadata["target"] = copy.copy(self._target) return metadata - def circuits(self, backend=None): + def _circuits(self, backend=None): # Get qubits and clbits meas_qubits = self._meas_qubits or range(self.num_qubits) diff --git a/qiskit_experiments/test/t1_backend.py b/qiskit_experiments/test/t1_backend.py index b87c763475..63684ef54e 100644 --- a/qiskit_experiments/test/t1_backend.py +++ b/qiskit_experiments/test/t1_backend.py @@ -36,7 +36,7 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, d configuration = QasmBackendConfiguration( backend_name="t1_simulator", backend_version="0", - n_qubits=int(1e6), + n_qubits=100, basis_gates=["barrier", "x", "delay", "measure"], gates=[], local=True, diff --git a/qiskit_experiments/test/t2ramsey_backend.py b/qiskit_experiments/test/t2ramsey_backend.py index ca8999eaa3..8eb8d4d00b 100644 --- a/qiskit_experiments/test/t2ramsey_backend.py +++ b/qiskit_experiments/test/t2ramsey_backend.py @@ -46,7 +46,7 @@ def __init__( configuration = QasmBackendConfiguration( backend_name="T2Ramsey_simulator", backend_version="0", - n_qubits=int(1e6), + n_qubits=100, basis_gates=["barrier", "h", "p", "delay", "measure"], gates=[], local=True, diff --git a/releasenotes/notes/update-base-experiment-execution-chain-9ed60922e2f1b33f.yaml b/releasenotes/notes/update-base-experiment-execution-chain-9ed60922e2f1b33f.yaml new file mode 100644 index 0000000000..389d418fa7 --- /dev/null +++ b/releasenotes/notes/update-base-experiment-execution-chain-9ed60922e2f1b33f.yaml @@ -0,0 +1,19 @@ +--- +developer: + - | + The execution chain of :py:class:`~qiskit_experiments.framework.base_experiment.BaseExperiment` + has been updated with some flexibility. The new feature will benefit experiment developers + who need to modify the standard job execution and analysis workflow. + + With this change, following three methods are newly introduced. + + - :py:meth:`~qiskit_experiments.framework.base_experiment.BaseExperiment#_pre_transpile_action` + - :py:meth:`~qiskit_experiments.framework.base_experiment.BaseExperiment#_post_transpile_action` + - :py:meth:`~qiskit_experiments.framework.base_experiment.BaseExperiment#_post_analysis_action` + + These methods allow a developer to insert extra data processing routine (somewhat of hooks) + in between circuit generation and result data generation. + This feature increases the flexibility of job execution. + + In addition, :py:meth:`run_transpile` method is added to all experiment classes. + This returns a list of quantum circuits to execute on the given backend. diff --git a/test/calibration/experiments/test_drag.py b/test/calibration/experiments/test_drag.py index e2232a41af..f88066b21b 100644 --- a/test/calibration/experiments/test_drag.py +++ b/test/calibration/experiments/test_drag.py @@ -110,7 +110,7 @@ def test_default_circuits(self): drag = DragCal(0) drag.set_experiment_options(reps=[2, 4, 8], schedule=self.x_plus) - circuits = drag.circuits(DragBackend(gate_name="xp")) + circuits = drag._circuits(DragBackend(gate_name="xp")) for idx, expected in enumerate([4, 8, 16]): ops = transpile(circuits[idx * 51], backend).count_ops() diff --git a/test/calibration/experiments/test_fine_amplitude.py b/test/calibration/experiments/test_fine_amplitude.py index c5e1b47704..ca5959254f 100644 --- a/test/calibration/experiments/test_fine_amplitude.py +++ b/test/calibration/experiments/test_fine_amplitude.py @@ -112,7 +112,7 @@ def test_xp(self): schedule=self.x_plus, angle_per_gate=np.pi, add_xp_circuit=False, add_sx=True ) - for idx, circ in enumerate(amp_cal.circuits()): + for idx, circ in enumerate(amp_cal._circuits()): self.assertTrue(circ.data[0][0].name == "sx") self.assertEqual(circ.count_ops().get("xp", 0), idx) @@ -124,7 +124,7 @@ def test_x90p(self): schedule=self.x_90_plus, angle_per_gate=np.pi, add_xp_circuit=False, add_sx=False ) - for idx, circ in enumerate(amp_cal.circuits()): + for idx, circ in enumerate(amp_cal._circuits()): self.assertTrue(circ.data[0][0].name != "sx") self.assertEqual(circ.count_ops().get("x90p", 0), idx) diff --git a/test/calibration/experiments/test_fine_drag.py b/test/calibration/experiments/test_fine_drag.py index 5eb7aa0ae6..673c29abcd 100644 --- a/test/calibration/experiments/test_fine_drag.py +++ b/test/calibration/experiments/test_fine_drag.py @@ -51,7 +51,7 @@ def test_circuits(self): drag = FineDrag(0) drag.set_experiment_options(schedule=self.schedule) - for circuit in drag.circuits(FakeArmonk())[1:]: + for circuit in drag._circuits(FakeArmonk())[1:]: for idx, name in enumerate(["Drag", "rz", "Drag", "rz"]): self.assertEqual(circuit.data[idx][0].name, name) diff --git a/test/calibration/experiments/test_rabi.py b/test/calibration/experiments/test_rabi.py index 0e1d6ad51e..2d5a82f639 100644 --- a/test/calibration/experiments/test_rabi.py +++ b/test/calibration/experiments/test_rabi.py @@ -141,7 +141,7 @@ def test_ef_rabi_circuit(self): anharm = -330e6 rabi12 = EFRabi(2) rabi12.set_experiment_options(amplitudes=[0.5], frequency_shift=anharm) - circ = rabi12.circuits(RabiBackend())[0] + circ = rabi12._circuits(RabiBackend())[0] with pulse.build() as expected: pulse.shift_frequency(anharm, pulse.DriveChannel(2)) @@ -161,7 +161,7 @@ def test_default_schedule(self): rabi = Rabi(2) rabi.set_experiment_options(amplitudes=[0.5]) - circs = rabi.circuits(RabiBackend()) + circs = rabi._circuits(RabiBackend()) with pulse.build() as expected: pulse.play(pulse.Gaussian(160, 0.5, 40), pulse.DriveChannel(2)) @@ -179,7 +179,7 @@ def test_user_schedule(self): rabi = Rabi(2) rabi.set_experiment_options(schedule=my_schedule, amplitudes=[0.5]) - circs = rabi.circuits(RabiBackend()) + circs = rabi._circuits(RabiBackend()) assigned_sched = my_schedule.assign_parameters({amp: 0.5}, inplace=False) self.assertEqual(circs[0].calibrations["Rabi"][((2,), (0.5,))], assigned_sched) @@ -274,7 +274,7 @@ def test_calibrations(self): experiments.append(rabi) par_exp = ParallelExperiment(experiments) - par_circ = par_exp.circuits()[0] + par_circ = par_exp._circuits()[0] # If the calibrations are not there we will not be able to transpile try: @@ -284,7 +284,7 @@ def test_calibrations(self): # Assert that the calibration keys are in the calibrations of the composite circuit. for qubit in range(3): - rabi_circuit = experiments[qubit].circuits()[0] + rabi_circuit = experiments[qubit]._circuits()[0] cal_key = next(iter(rabi_circuit.calibrations["Rabi"].keys())) self.assertEqual(cal_key[0], (qubit,)) diff --git a/test/fake_backend.py b/test/fake_backend.py index 53dfb32be3..f8225c9e86 100644 --- a/test/fake_backend.py +++ b/test/fake_backend.py @@ -26,12 +26,12 @@ class FakeBackend(BackendV1): Fake backend for test purposes only. """ - def __init__(self, max_experiments=None): + def __init__(self, n_qubits: int = 2, max_experiments: int = None): configuration = QasmBackendConfiguration( backend_name="dummy_backend", backend_version="0", - n_qubits=int(1e6), - basis_gates=["barrier", "x", "delay", "measure"], + n_qubits=n_qubits, + basis_gates=["barrier", "sx", "x", "cx", "rz", "delay", "measure"], gates=[], local=True, simulator=True, diff --git a/test/fake_experiment.py b/test/fake_experiment.py index 5c6b1fa46f..9a6297207f 100644 --- a/test/fake_experiment.py +++ b/test/fake_experiment.py @@ -38,6 +38,6 @@ def __init__(self, qubit=0): self._type = None super().__init__((qubit,), "fake_test_experiment") - def circuits(self, backend=None): + def _circuits(self, backend=None): """Fake circuits.""" return [] diff --git a/test/quantum_volume/qv_generate_data.py b/test/quantum_volume/qv_generate_data.py index a2eb2ae3dd..647c1e0d59 100644 --- a/test/quantum_volume/qv_generate_data.py +++ b/test/quantum_volume/qv_generate_data.py @@ -38,7 +38,7 @@ def create_qv_ideal_probabilities(dir_path: str): num_of_qubits = 3 qv_exp = QuantumVolume(num_of_qubits, seed=SEED) qv_exp.set_experiment_options(trials=20) - qv_circs = qv_exp.circuits() + qv_circs = qv_exp._circuits() simulation_probabilities = [ list(qv_circ.metadata["ideal_probabilities"]) for qv_circ in qv_circs ] diff --git a/test/quantum_volume/test_qv.py b/test/quantum_volume/test_qv.py index 20a3c4bb72..f5182ed261 100644 --- a/test/quantum_volume/test_qv.py +++ b/test/quantum_volume/test_qv.py @@ -42,7 +42,7 @@ def test_qv_circuits_length(self): for trials in ntrials: qv_exp = QuantumVolume(qubits) qv_exp.set_experiment_options(trials=trials) - qv_circs = qv_exp.circuits() + qv_circs = qv_exp._circuits() self.assertEqual( len(qv_circs), @@ -67,14 +67,14 @@ def test_qv_ideal_probabilities(self): qv_exp = QuantumVolume(num_of_qubits, seed=SEED) # set number of trials to a low number to make the test faster qv_exp.set_experiment_options(trials=20) - qv_circs = qv_exp.circuits() + qv_circs = qv_exp._circuits() simulation_probabilities = [qv_circ.metadata["ideal_probabilities"] for qv_circ in qv_circs] # create the circuits again, but this time disable simulation so the # ideal probabilities will be calculated using statevector qv_exp = QuantumVolume(num_of_qubits, seed=SEED) qv_exp.set_experiment_options(trials=20) qv_exp._simulation_backend = None - qv_circs = qv_exp.circuits() + qv_circs = qv_exp._circuits() statevector_probabilities = [ qv_circ.metadata["ideal_probabilities"] for qv_circ in qv_circs ] diff --git a/test/randomized_benchmarking/test_rb.py b/test/randomized_benchmarking/test_rb.py index af593d2bc4..07dd24f5d3 100644 --- a/test/randomized_benchmarking/test_rb.py +++ b/test/randomized_benchmarking/test_rb.py @@ -60,7 +60,7 @@ def test_rb_experiment(self, qubits: list): ) exp_data = rb_exp.run(backend) exp = exp_data.experiment - exp_circuits = rb_exp.circuits() + exp_circuits = rb_exp._circuits() self.validate_metadata(exp_circuits, exp_attributes) self.validate_circuit_data(exp, exp_attributes) self.is_identity(exp_circuits) @@ -193,7 +193,7 @@ def test_interleaved_rb_experiment(self, interleaved_element: "Gate", qubits: li ) experiment_obj = rb_exp.run(backend) exp_data = experiment_obj.experiment - exp_circuits = rb_exp.circuits() + exp_circuits = rb_exp._circuits() self.validate_metadata(exp_circuits, exp_attributes) self.validate_circuit_data(exp_data, exp_attributes) self.is_identity(exp_circuits) @@ -214,7 +214,7 @@ def test_interleaved_structure(self, interleaved_element: "Gate", qubits: list, qubits=qubits, interleaved_element=interleaved_element, lengths=[length], num_samples=1 ) - circuits = exp.circuits() + circuits = exp._circuits() c_std = circuits[0] c_int = circuits[1] if c_std.metadata["interleaved"]: diff --git a/test/test_cross_resonance_hamiltonian.py b/test/test_cross_resonance_hamiltonian.py index 02a948ee21..cb49fc5ebd 100644 --- a/test/test_cross_resonance_hamiltonian.py +++ b/test/test_cross_resonance_hamiltonian.py @@ -183,7 +183,7 @@ def test_circuit_generation(self): pulse.delay(nearlest_16, pulse.DriveChannel(1)) cr_gate = circuit.Gate("cr_gate", num_qubits=2, params=[1000]) - expr_circs = expr.circuits(backend) + expr_circs = expr._circuits(backend) x0_circ = QuantumCircuit(2, 1) x0_circ.append(cr_gate, [0, 1]) @@ -254,7 +254,7 @@ def test_circuit_generation_from_sec(self): pulse.delay(nearlest_16, pulse.DriveChannel(1)) cr_gate = circuit.Gate("cr_gate", num_qubits=2, params=[500]) - expr_circs = expr.circuits(backend) + expr_circs = expr._circuits(backend) x0_circ = QuantumCircuit(2, 1) x0_circ.append(cr_gate, [0, 1]) diff --git a/test/test_framework.py b/test/test_framework.py index 51085ecbd4..30beaf92d6 100644 --- a/test/test_framework.py +++ b/test/test_framework.py @@ -35,7 +35,7 @@ def test_job_splitting(self, max_experiments): class Experiment(FakeExperiment): """Fake Experiment to test job splitting""" - def circuits(self, backend=None): + def _circuits(self, backend=None): """Generate fake circuits""" qc = QuantumCircuit(1) qc.measure_all() @@ -53,3 +53,174 @@ def circuits(self, backend=None): if num_circuits % max_experiments: num_jobs += 1 self.assertEqual(len(job_ids), num_jobs) + + def test_logical_circuits(self): + """Test if user can get logical circuits.""" + backend = FakeBackend(n_qubits=3) + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + exp = Experiment(1) + test_circ = exp.circuits(backend)[0] + + ref_circ = QuantumCircuit(1, 1) + ref_circ.x(0) + ref_circ.measure(0, 0) + + self.assertEqual(test_circ, ref_circ) + + def test_physical_circuits(self): + """Test if user can get physical circuits.""" + backend = FakeBackend(n_qubits=3) + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + exp = Experiment(2) + test_circ = exp.circuits(backend, run_transpile=True)[0] + + ref_circ = QuantumCircuit(3, 1) + ref_circ.x(2) + ref_circ.measure(2, 0) + + self.assertEqual(test_circ, ref_circ) + + def test_pre_transpile_action(self): + """Test pre transpile.""" + backend = FakeBackend(n_qubits=1) + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1, 1) + qc.y(0) + qc.measure(0, 0) + + return [qc] + + def _pre_transpile_action(self, backend): + """Update basis gates with y gate.""" + basis_gates = backend.configuration().basis_gates + + if "y" not in basis_gates: + basis_gates.append("y") + + self.set_transpile_options(basis_gates=basis_gates) + + exp = Experiment(0) + test_circ = exp.circuits(backend, run_transpile=True)[0] + + ref_circ = QuantumCircuit(1, 1) + ref_circ.y(0) + ref_circ.measure(0, 0) + + self.assertEqual(test_circ, ref_circ) + + def test_post_transpile_action(self): + """Test post transpile.""" + backend = FakeBackend(n_qubits=1) + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + return [qc] + + def _post_transpile_action(self, circuits, backend): + """Add test metadata.""" + for circ in circuits: + circ.metadata = {"test": "test"} + + return circuits + + exp = Experiment(0) + test_circ = exp.circuits(backend, run_transpile=True)[0] + + ref_metadata = {"test": "test"} + + self.assertDictEqual(test_circ.metadata, ref_metadata) + + def test_post_analysis(self): + """Test post analysis.""" + backend = FakeBackend(n_qubits=1) + mutable_target_obj = {"type": "dummy_data"} + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def __init__(self, qubit=0, target_obj=None): + """Initialise the fake experiment.""" + super().__init__(qubit) + self.target = target_obj + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1) + qc.measure_all() + + return [qc] + + def _post_analysis_action(self, experiment_data): + """Update target object.""" + self.target["type"] = experiment_data.metadata["experiment_type"] + + exp = Experiment(0, target_obj=mutable_target_obj) + exp.run(backend).block_for_results() + + self.assertEqual(mutable_target_obj["type"], "fake_test_experiment") + + def test_post_analysis_from_run_analysis(self): + """Test post analysis called from run analysis.""" + backend = FakeBackend(n_qubits=1) + mutable_target_obj = {"type": "dummy_data"} + + class Experiment(FakeExperiment): + """Fake Experiment to test transpile.""" + + def __init__(self, qubit=0, target_obj=None): + """Initialise the fake experiment.""" + super().__init__(qubit) + self.target = target_obj + + def _circuits(self, backend=None): + """Generate fake circuits""" + qc = QuantumCircuit(1) + qc.measure_all() + + return [qc] + + def _post_analysis_action(self, experiment_data): + """Update target object.""" + self.target["type"] = experiment_data.metadata["experiment_type"] + + exp = Experiment(0, target_obj=mutable_target_obj) + exp_data = exp.run(backend, analysis=False).block_for_results() + self.assertEqual(mutable_target_obj["type"], "dummy_data") + + # post analysis should be called + exp.run_analysis(experiment_data=exp_data) + self.assertEqual(mutable_target_obj["type"], "fake_test_experiment") diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index 81ef75cb8b..9dae7be94b 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -144,6 +144,6 @@ def test_spectroscopy12_end2end_classified(self): self.assertEqual(result.quality, "good") # Test the circuits - circ = spec.circuits(backend)[0] + circ = spec._circuits(backend)[0] self.assertEqual(circ.data[0][0].name, "x") self.assertEqual(circ.data[1][0].name, "Spec") diff --git a/test/test_t1.py b/test/test_t1.py index 649dac95f6..df350afcf0 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -137,7 +137,7 @@ def test_t1_metadata(self): delays = list(range(1, 40, 3)) exp = T1(0, delays, unit="ms") - circs = exp.circuits() + circs = exp._circuits() self.assertEqual(len(circs), len(delays)) diff --git a/test/test_tomography.py b/test/test_tomography.py index 17ee833d74..34a6b75318 100644 --- a/test/test_tomography.py +++ b/test/test_tomography.py @@ -128,7 +128,7 @@ def test_exp_circuits_measurement_qubits(self, meas_qubits): num_meas = len(meas_qubits) exp = StateTomography(circ, measurement_qubits=meas_qubits) - tomo_circuits = exp.circuits() + tomo_circuits = exp._circuits() # Check correct number of circuits are generated self.assertEqual(len(tomo_circuits), 3 ** num_meas) @@ -321,7 +321,7 @@ def test_exp_measurement_preparation_qubits(self, qubits): num_meas = len(qubits) exp = ProcessTomography(circ, measurement_qubits=qubits, preparation_qubits=qubits) - tomo_circuits = exp.circuits() + tomo_circuits = exp._circuits() # Check correct number of circuits are generated size = 3 ** num_meas * 4 ** num_meas