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)