From bb2b5e1b5fc0a97cc3207552a6f41dfe28201367 Mon Sep 17 00:00:00 2001 From: Nayan Pariya <81795258+nayan2167@users.noreply.github.com> Date: Mon, 23 Oct 2023 22:11:23 +0530 Subject: [PATCH] Add Job Splitting Information Methods for Better User Experience (#1281) This PR aims to solve issue #1247 als the changes are similar to PR #1248 ### Summary: This pull request aims to enhance the user experience by providing clearer insights into the structure of experiments and their distribution across backends. It introduces two new public methods, `_max_circuits()` and `job_info(backend)`, while also refactoring the existing `_run_jobs()` method for better code organization and reuse of logic. ### Proposed Changes: Add` _max_circuits()` Method: This method will calculate the maximum number of circuits that can be included in a single job. This method will encapsulate the logic previously present in the ` _run_jobs()` method to determine the maximum circuits per job. Add `job_info(backend)` Method: This method will utilize the `_max_circuits()` method to provide users with information about the experiment's configuration for a specific backend. If the backend argument is provided, the method will return the number of circuits and the number of jobs the experiment will be split into on that backend. If the backend argument is not provided, the method will rely on the backend already specified in the experiment. Refactor `_run_jobs()` Method: The existing `_run_jobs()` method will be refactored to make use of the `_max_circuits()` method. This change will enhance code readability and maintainability by segregating the logic related to job distribution from the actual job execution logic. ### Testing Added `test_max_circuits` for testing of `_max_circuits()` and `test_job_info` for testing of `job_info(backend)` these tests ensure that methods functioning correctly --------- Co-authored-by: Helena Zhang --- .../framework/base_experiment.py | 66 +++++++++++++++-- ...circuit-count-method-a095bd74aaa1d2fb.yaml | 11 +++ test/framework/test_framework.py | 73 +++++++++++++++++++ 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/circuit-count-method-a095bd74aaa1d2fb.yaml diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index bb14cdc610..882fc20a3f 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -265,20 +265,72 @@ def _finalize(self): """ pass - def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[Job]: - """Run circuits on backend as 1 or more jobs.""" + def _max_circuits(self, backend: Backend = None): + """ + Calculate the maximum number of circuits per job for the experiment. + """ + + # set backend + if backend is None: + if self.backend is None: + raise QiskitError("A backend must be provided.") + backend = self.backend # Get max circuits for job splitting max_circuits_option = getattr(self.experiment_options, "max_circuits", None) - max_circuits_backend = self._backend_data.max_circuits + max_circuits_backend = BackendData(backend).max_circuits + if max_circuits_option and max_circuits_backend: - max_circuits = min(max_circuits_option, max_circuits_backend) + return min(max_circuits_option, max_circuits_backend) elif max_circuits_option: - max_circuits = max_circuits_option + return max_circuits_option else: - max_circuits = max_circuits_backend + return max_circuits_backend + + def job_info(self, backend: Backend = None): + """ + Get information about job distribution for the experiment on a specific + backend. + + Args: + backend: Optional, the backend for which to get job distribution + information. If not specified, the experiment must already have a + set backend. + + Returns: + dict: A dictionary containing information about job distribution. + + - "Total number of circuits in the experiment": Total number of + circuits in the experiment. + + - "Maximum number of circuits per job": Maximum number of + circuits in one job based on backend and experiment settings. + + - "Total number of jobs": Number of jobs needed to run this + experiment on the currently set backend. + + Raises: + QiskitError: if backend is not specified. + + """ + max_circuits = self._max_circuits(backend) + total_circuits = len(self.circuits()) + + if max_circuits is None: + num_jobs = 1 + else: + num_jobs = (total_circuits + max_circuits - 1) // max_circuits + return { + "Total number of circuits in the experiment": total_circuits, + "Maximum number of circuits per job": max_circuits, + "Total number of jobs": num_jobs, + } + + def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[Job]: + """Run circuits on backend as 1 or more jobs.""" + max_circuits = self._max_circuits(self.backend) # Run experiment jobs - if max_circuits and len(circuits) > max_circuits: + if max_circuits and (len(circuits) > max_circuits): # Split jobs for backends that have a maximum job size job_circuits = [ circuits[i : i + max_circuits] for i in range(0, len(circuits), max_circuits) diff --git a/releasenotes/notes/circuit-count-method-a095bd74aaa1d2fb.yaml b/releasenotes/notes/circuit-count-method-a095bd74aaa1d2fb.yaml new file mode 100644 index 0000000000..d8080ae02b --- /dev/null +++ b/releasenotes/notes/circuit-count-method-a095bd74aaa1d2fb.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + A new method :meth:`BaseExperiment.job_info` has been added that will + output the number of jobs the experiment is expected to be split into + based on the provided backend. + - | + Refer to issue + `#1247 https://github.com/Qiskit-Extensions/qiskit-experiments/issues/1247` + for more details. + \ No newline at end of file diff --git a/test/framework/test_framework.py b/test/framework/test_framework.py index 4c122f05ec..674b54f850 100644 --- a/test/framework/test_framework.py +++ b/test/framework/test_framework.py @@ -267,3 +267,76 @@ def error_message(self): res = expdata.analysis_results() self.assertEqual(len(res), 0) self.assertEqual(expdata.analysis_status(), AnalysisStatus.CANCELLED) + + @ddt.data(None, 1, 10, 100) + def test_max_circuits(self, max_experiments): + """Test running experiment with max_circuits""" + + num_circuits = 10 + + class MyExp(BaseExperiment): + """Some arbitrary experiment""" + + def __init__(self, physical_qubits): + super().__init__(physical_qubits) + + def circuits(self): + """Generate fake circuits""" + qc = QuantumCircuit(1) + qc.measure_all() + return num_circuits * [qc] + + backend = FakeBackend(max_experiments=max_experiments) + exp = MyExp([0]) + + # set backend + if backend is None: + if exp.backend is None: + self.assertRaises(QiskitError) + backend = exp.backend + exp.backend = backend + # Get max circuits for job splitting + max_circuits_option = getattr(exp.experiment_options, "max_circuits", None) + max_circuits_backend = exp._backend_data.max_circuits + if max_circuits_option and max_circuits_backend: + result = min(max_circuits_option, max_circuits_backend) + elif max_circuits_option: + result = max_circuits_option + else: + result = max_circuits_backend + + self.assertEqual(exp._max_circuits(backend=backend), result) + + @ddt.data(None, 1, 10, 100) + def test_job_info(self, max_experiments): + """Test job_info for specific backend""" + + num_circuits = 10 + + class MyExp(BaseExperiment): + """Some arbitrary experiment""" + + def __init__(self, physical_qubits): + super().__init__(physical_qubits) + + def circuits(self): + """Generate fake circuits""" + qc = QuantumCircuit(1) + qc.measure_all() + return num_circuits * [qc] + + backend = FakeBackend(max_experiments=max_experiments) + exp = MyExp([0]) + + if max_experiments is None: + num_jobs = 1 + else: + num_jobs = (num_circuits + max_experiments - 1) // max_experiments + + job_info = { + "Total number of circuits in the experiment": num_circuits, + "Maximum number of circuits per job": max_experiments, + "Total number of jobs": num_jobs, + } + + self.assertEqual(exp.job_info(backend=backend), job_info)