From 034d165ce47a468132d634c1d79c01bdc3baf3fd Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 28 Sep 2021 18:07:30 +0900 Subject: [PATCH 1/9] move change of base framework from #380 --- qiskit_experiments/framework/base_analysis.py | 4 + .../framework/base_experiment.py | 139 +++++++++++++----- .../library/characterization/t1.py | 8 + .../library/characterization/t2ramsey.py | 8 + .../randomized_benchmarking/rb_experiment.py | 14 +- qiskit_experiments/test/t1_backend.py | 2 +- qiskit_experiments/test/t2ramsey_backend.py | 2 +- 7 files changed, 134 insertions(+), 43 deletions(-) diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index adfe2cf831..7d6123351a 100644 --- a/qiskit_experiments/framework/base_analysis.py +++ b/qiskit_experiments/framework/base_analysis.py @@ -101,6 +101,10 @@ def run( if figures: experiment_data.add_figures(figures) + # Run post analysis + if experiment_data.experiment is not None: + experiment_data.experiment._post_analysis_action(experiment_data) + return experiment_data def _format_analysis_result(self, data, experiment_id, experiment_components=None): diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index e6161ba1f3..9db9170d8a 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,43 +104,24 @@ 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.run_transpile(backend) + # Execute experiment if isinstance(backend, LegacyBackend): qobj = assemble(circuits, backend=backend, **run_opts) job = backend.run(qobj) else: job = backend.run(circuits, **run_opts) - # Add Job to ExperimentData and add analysis for post processing. - run_analysis = None - # Add experiment option metadata self._add_job_metadata(experiment_data, job, **run_opts) if analysis and self.__analysis_class__ is not None: - run_analysis = self.run_analysis - - experiment_data.add_data(job, post_processing_callback=run_analysis) + experiment_data.add_data(job, post_processing_callback=self.run_analysis) + else: + experiment_data.add_data(job) - # Return the ExperimentData future return experiment_data def _initialize_experiment_data( @@ -167,11 +143,106 @@ def _initialize_experiment_data( return experiment_data._copy_metadata() - def run_analysis(self, experiment_data, **options) -> ExperimentData: + 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 run_transpile(self, backend: Backend, **options) -> List[QuantumCircuit]: + """Run transpile and return transpiled circuits. + + Args: + backend: Target backend. + options: User provided runtime options. + + Returns: + Transpiled circuit to execute. + """ + # Run pre transpile if implemented by subclasses. + self._pre_transpile_action(backend) + + # Get transpile options + transpile_options = copy.copy(self.transpile_options) + transpile_options.update_options( + initial_layout=list(self._physical_qubits), + **options, + ) + transpile_options = transpile_options.__dict__ + + circuits = transpile(circuits=self.circuits(backend), backend=backend, **transpile_options) + + # 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. Otherwise you may want to + call :meth:`block_for_results` method of the ``experiment_data`` here + to freeze processing chain until the job result is returned. + + 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. Args: - experiment_data (ExperimentData): the experiment data to analyze. + experiment_data: The experiment data to analyze. options: additional analysis options. Any values set here will override the value from :meth:`analysis_options` for the current run. @@ -180,7 +251,7 @@ def run_analysis(self, experiment_data, **options) -> ExperimentData: 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) @@ -335,10 +406,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. diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index d03b9761c1..de5e3eb149 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -62,6 +62,14 @@ def _default_experiment_options(cls) -> Options: return options + @classmethod + def _default_transpile_options(cls) -> Options: + """Default transpile options.""" + options = super()._default_transpile_options() + options.scheduling_method = "alap" + + return options + def __init__( self, qubit: int, diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index abb93d486f..13bc06655d 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -76,6 +76,14 @@ def _default_experiment_options(cls) -> Options: return options + @classmethod + def _default_transpile_options(cls) -> Options: + """Default transpile options.""" + options = super()._default_transpile_options() + options.scheduling_method = "alap" + + return options + def __init__( self, qubit: int, diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py index bf9f3b29e1..05137236bf 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py @@ -220,12 +220,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/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, From ee31bf3da7fb14b9ae41eb48f0fec8b54f2b20d4 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 28 Sep 2021 18:07:43 +0900 Subject: [PATCH 2/9] reno --- ...ment-execution-chain-9ed60922e2f1b33f.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 releasenotes/notes/update-base-experiment-execution-chain-9ed60922e2f1b33f.yaml 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. From 48bb939721f6f171c082f68dcd649467f5ec9c2a Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 28 Sep 2021 18:53:45 +0900 Subject: [PATCH 3/9] add pre transpile --- .../library/characterization/t1.py | 21 ++++++++-------- .../library/characterization/t2ramsey.py | 24 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index de5e3eb149..d303db223d 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 @@ -62,14 +63,6 @@ def _default_experiment_options(cls) -> Options: return options - @classmethod - def _default_transpile_options(cls) -> Options: - """Default transpile options.""" - options = super()._default_transpile_options() - options.scheduling_method = "alap" - - return options - def __init__( self, qubit: int, @@ -139,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 13bc06655d..bf61d9a6af 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 @@ -76,14 +78,6 @@ def _default_experiment_options(cls) -> Options: return options - @classmethod - def _default_transpile_options(cls) -> Options: - """Default transpile options.""" - options = super()._default_transpile_options() - options.scheduling_method = "alap" - - return options - def __init__( self, qubit: int, @@ -162,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") From e362adf959163f1f6acde9bcfe4099ab94879ea0 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 13 Oct 2021 16:12:18 +0900 Subject: [PATCH 4/9] use add_analysis_callback --- qiskit_experiments/framework/base_analysis.py | 4 ---- qiskit_experiments/framework/base_experiment.py | 10 ++++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index 7d6123351a..adfe2cf831 100644 --- a/qiskit_experiments/framework/base_analysis.py +++ b/qiskit_experiments/framework/base_analysis.py @@ -101,10 +101,6 @@ def run( if figures: experiment_data.add_figures(figures) - # Run post analysis - if experiment_data.experiment is not None: - experiment_data.experiment._post_analysis_action(experiment_data) - return experiment_data def _format_analysis_result(self, data, experiment_id, experiment_components=None): diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 73582c1022..6a27365bf1 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -112,7 +112,7 @@ def run( if max_experiments and len(circuits) > max_experiments: # Split jobs for backends that have a maximum job size job_circuits = [ - circuits[i : i + max_experiments] for i in range(0, len(circuits), max_experiments) + circuits[i:i + max_experiments] for i in range(0, len(circuits), max_experiments) ] else: # Run as single job @@ -137,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 @@ -449,7 +450,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: From 3b6b372dab23688993460497a7ef32f5f4d19c90 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 13 Oct 2021 17:12:33 +0900 Subject: [PATCH 5/9] merge run_transpile into circuits - circuits is renamed to _circuits - run_transpile options is added to circuits --- .../example/example_experiment.py | 2 +- .../framework/base_experiment.py | 36 +++++++++++++------ .../framework/composite/batch_experiment.py | 4 +-- .../composite/composite_experiment.py | 2 +- .../composite/parallel_experiment.py | 4 +-- .../library/calibration/drag.py | 2 +- .../library/calibration/fine_amplitude.py | 2 +- .../library/calibration/fine_drag.py | 2 +- .../library/calibration/rabi.py | 2 +- .../library/calibration/ramsey_xy.py | 2 +- .../characterization/cr_hamiltonian.py | 2 +- .../characterization/qubit_spectroscopy.py | 2 +- .../library/characterization/t1.py | 2 +- .../library/characterization/t2ramsey.py | 2 +- .../library/quantum_volume/qv_experiment.py | 2 +- .../randomized_benchmarking/rb_experiment.py | 2 +- .../tomography/tomography_experiment.py | 2 +- test/calibration/experiments/test_drag.py | 2 +- .../experiments/test_fine_amplitude.py | 4 +-- .../calibration/experiments/test_fine_drag.py | 2 +- test/calibration/experiments/test_rabi.py | 10 +++--- test/fake_experiment.py | 2 +- test/quantum_volume/qv_generate_data.py | 2 +- test/quantum_volume/test_qv.py | 6 ++-- test/randomized_benchmarking/test_rb.py | 6 ++-- test/test_cross_resonance_hamiltonian.py | 4 +-- test/test_framework.py | 2 +- test/test_qubit_spectroscopy.py | 2 +- test/test_t1.py | 2 +- test/test_tomography.py | 4 +-- 30 files changed, 67 insertions(+), 53 deletions(-) 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 6a27365bf1..9c68d94715 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -105,7 +105,7 @@ def run( run_opts = run_opts.__dict__ # Generate and transpile circuits - circuits = self.run_transpile(backend) + circuits = self.circuits(backend, run_transpile=True) # Run experiment jobs max_experiments = getattr(backend.configuration(), "max_experiments", None) @@ -203,16 +203,33 @@ def _post_transpile_action( """ return circuits - def run_transpile(self, backend: Backend, **options) -> List[QuantumCircuit]: + def circuits( + self, + backend: Optional[Backend] = None, + run_transpile: bool = False, + **options + ) -> List[QuantumCircuit]: """Run transpile and return transpiled circuits. Args: backend: Target backend. - options: User provided runtime options. + 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``. + options: User provided transpile options. Returns: - Transpiled circuit to execute. + Experiment circuits. """ + logical_circs = self._circuits(backend) + + if not run_transpile: + return logical_circs + + if backend is None: + raise QiskitError("Transpile cannot be performed without backend instance.") + # Run pre transpile if implemented by subclasses. self._pre_transpile_action(backend) @@ -224,7 +241,7 @@ def run_transpile(self, backend: Backend, **options) -> List[QuantumCircuit]: ) transpile_options = transpile_options.__dict__ - circuits = transpile(circuits=self.circuits(backend), backend=backend, **transpile_options) + circuits = transpile(circuits=logical_circs, backend=backend, **transpile_options) # Run post transpile. This is implemented by each experiment subclass. circuits = self._post_transpile_action(circuits, backend) @@ -245,10 +262,7 @@ def _post_analysis_action(self, experiment_data: ExperimentData): 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. Otherwise you may want to - call :meth:`block_for_results` method of the ``experiment_data`` here - to freeze processing chain until the job result is returned. - + synchronization of update of the object data. By default, this method does nothing. Args: @@ -305,8 +319,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. 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 d303db223d..2e597b15f8 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -90,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 diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index bf61d9a6af..8a1af6b9cc 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -104,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, 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 c11b06c769..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: 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/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_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..9c924afba3 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() 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 From b0290a0d0cb727519c062d76fb2403a6cb2d0cab Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 13 Oct 2021 17:16:15 +0900 Subject: [PATCH 6/9] lint --- qiskit_experiments/framework/base_experiment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 9c68d94715..d403ded0f1 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -112,7 +112,7 @@ def run( if max_experiments and len(circuits) > max_experiments: # Split jobs for backends that have a maximum job size job_circuits = [ - circuits[i:i + max_experiments] for i in range(0, len(circuits), max_experiments) + circuits[i : i + max_experiments] for i in range(0, len(circuits), max_experiments) ] else: # Run as single job @@ -204,10 +204,7 @@ def _post_transpile_action( return circuits def circuits( - self, - backend: Optional[Backend] = None, - run_transpile: bool = False, - **options + self, backend: Optional[Backend] = None, run_transpile: bool = False, **options ) -> List[QuantumCircuit]: """Run transpile and return transpiled circuits. @@ -221,6 +218,9 @@ def circuits( Returns: Experiment circuits. + + Raises: + QiskitError: When transpile is performed without backend instance. """ logical_circs = self._circuits(backend) From ee6556ba13703a4281191aa868aa679cab0118e4 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 13 Oct 2021 20:02:44 +0900 Subject: [PATCH 7/9] add framework unittest --- test/fake_backend.py | 6 +- test/test_framework.py | 139 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) 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/test_framework.py b/test/test_framework.py index 9c924afba3..5096ed2032 100644 --- a/test/test_framework.py +++ b/test/test_framework.py @@ -53,3 +53,142 @@ 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": ""} + + 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") From 0c8b915eebfa8682edd0480bd39e4e16a718f53a Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 13 Oct 2021 20:18:20 +0900 Subject: [PATCH 8/9] add unittest that post analysis fails --- test/test_framework.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/test_framework.py b/test/test_framework.py index 5096ed2032..30beaf92d6 100644 --- a/test/test_framework.py +++ b/test/test_framework.py @@ -167,7 +167,7 @@ def _post_transpile_action(self, circuits, backend): def test_post_analysis(self): """Test post analysis.""" backend = FakeBackend(n_qubits=1) - mutable_target_obj = {"type": ""} + mutable_target_obj = {"type": "dummy_data"} class Experiment(FakeExperiment): """Fake Experiment to test transpile.""" @@ -192,3 +192,35 @@ def _post_analysis_action(self, experiment_data): 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") From e9c6aa16ed98c669cd49b229c43d433dc1678fea Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 19 Oct 2021 01:25:33 +0900 Subject: [PATCH 9/9] minor update --- qiskit_experiments/framework/base_experiment.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index d403ded0f1..730fc465f7 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -204,7 +204,7 @@ def _post_transpile_action( return circuits def circuits( - self, backend: Optional[Backend] = None, run_transpile: bool = False, **options + self, backend: Optional[Backend] = None, run_transpile: bool = False, ) -> List[QuantumCircuit]: """Run transpile and return transpiled circuits. @@ -214,7 +214,6 @@ def 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``. - options: User provided transpile options. Returns: Experiment circuits. @@ -227,21 +226,13 @@ def circuits( if not run_transpile: return logical_circs - if backend is None: - raise QiskitError("Transpile cannot be performed without backend instance.") - # Run pre transpile if implemented by subclasses. self._pre_transpile_action(backend) # Get transpile options - transpile_options = copy.copy(self.transpile_options) - transpile_options.update_options( - initial_layout=list(self._physical_qubits), - **options, + circuits = transpile( + circuits=logical_circs, backend=backend, **self.transpile_options.__dict__ ) - transpile_options = transpile_options.__dict__ - - circuits = transpile(circuits=logical_circs, backend=backend, **transpile_options) # Run post transpile. This is implemented by each experiment subclass. circuits = self._post_transpile_action(circuits, backend)