From a961c907dbc80c4c8639de42cb1da8f1d10dc560 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 10 May 2021 14:56:10 -0400 Subject: [PATCH 1/6] Respect max_experiments in QuantumInstance BackendV1 path In #6299 support was fixed for strict BackendV1 backends that only take QuantumCircuit objects (instead of qobj) for the input. That was fixed by adding a parallel path when running with a new backend. However that parallel path wasn't identical and was missing the support the qobj path had for splitting an algorithm run into multiple jobs if the backend if the number of circuits was greater than the max_experiments set in the backend. This would result on some providers' backends, such as ionq and aqt, both of which have max_experiments set to 1. This commit fixes this issue by splitting the circuits list into smaller sublists when the len(circuits) > max_experiments (or the old env var, which we should change the name of at some point). --- qiskit/utils/run_circuits.py | 161 ++++++++++++++--------- test/python/algorithms/test_backendv1.py | 14 ++ 2 files changed, 114 insertions(+), 61 deletions(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 66a96ab9521e..f7716af5603d 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -457,79 +457,118 @@ def run_circuits( run_config = run_config or {} with_autorecover = not is_simulator_backend(backend) - job, job_id = _safe_submit_circuits( - circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - ) + if MAX_CIRCUITS_PER_JOB is not None: + max_circuits_per_job = int(MAX_CIRCUITS_PER_JOB) + else: + if is_local_backend(backend): + max_circuits_per_job = sys.maxsize + else: + max_circuits_per_job = backend.configuration().max_experiments + + if len(circuits) > max_circuits_per_job: + jobs = [] + job_ids = [] + split_circuits = [] + count = 0 + while count < len(circuits): + some_circuits = circuits[count : count + max_circuits_per_job] + circuits.append(some_circuits) + job, job_id = _safe_submit_circuits( + circuits, + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config, + ) + jobs.append(job) + job_ids.append(job_id) + count += max_circuits_per_job + else: + job, job_id = _safe_submit_circuits( + circuits, + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config, + ) + jobs = [job] + job_ids = [job_id] + split_circuits = [circuits] result = None if with_autorecover: logger.info("Backend status: %s", backend.status()) - logger.info("There is one jobs are submitted: id: %s", job_id) - while True: - logger.info("Running job id: %s", job_id) - # try to get result if possible + logger.info("There are %s jobs are submitted.", len(jobs)) + logger.info("All job ids:\n%s", job_ids) + for idx, _ in enumerate(jobs): + logger.info("Backend status: %s", backend.status()) + logger.info("There is one jobs are submitted: id: %s", job_id) + job = jobs[idx] + job_id = job_ids[idx] while True: - job_status = _safe_get_job_status(job, job_id) - queue_position = 0 - if job_status in JOB_FINAL_STATES: - # do callback again after the job is in the final states + logger.info("Running job id: %s", job_id) + # try to get result if possible + while True: + job_status = _safe_get_job_status(job, job_id) + queue_position = 0 + if job_status in JOB_FINAL_STATES: + # do callback again after the job is in the final states + if job_callback is not None: + job_callback(job_id, job_status, queue_position, job) + break + if job_status == JobStatus.QUEUED and hasattr(job, queue_position): + queue_position = job.queue_position() + logger.info("Job id: %s is queued at position %s", job_id, queue_position) + else: + logger.info("Job id: %s, status: %s", job_id, job_status) if job_callback is not None: job_callback(job_id, job_status, queue_position, job) - break - if job_status == JobStatus.QUEUED and hasattr(job, queue_position): - queue_position = job.queue_position() - logger.info("Job id: %s is queued at position %s", job_id, queue_position) - else: - logger.info("Job id: %s, status: %s", job_id, job_status) - if job_callback is not None: - job_callback(job_id, job_status, queue_position, job) - time.sleep(qjob_config["wait"]) + time.sleep(qjob_config["wait"]) - # get result after the status is DONE - if job_status == JobStatus.DONE: - while True: - result = job.result() - if result.success: - logger.info("COMPLETED: job id: %s", job_id) - break + # get result after the status is DONE + if job_status == JobStatus.DONE: + while True: + result = job.result() + if result.success: + logger.info("COMPLETED: job id: %s", job_id) + break - logger.warning("FAILURE: Job id: %s", job_id) + logger.warning("FAILURE: Job id: %s", job_id) + logger.warning( + "Job (%s) is completed anyway, retrieve result " "from backend again.", + job_id, + ) + job = backend.retrieve_job(job_id) + break + # for other cases, resubmit the circuit until the result is available. + # since if there is no result returned, there is no way algorithm can do any process + if job_status == JobStatus.CANCELLED: + logger.warning( + "FAILURE: Job id: %s is cancelled. Re-submit the circuits.", job_id + ) + elif job_status == JobStatus.ERROR: logger.warning( - "Job (%s) is completed anyway, retrieve result " "from backend again.", + "FAILURE: Job id: %s encounters the error. " + "Error is : %s. Re-submit the circuits.", job_id, + job.error_message(), + ) + else: + logging.warning( + "FAILURE: Job id: %s. Unknown status: %s. " "Re-submit the circuits.", + job_id, + job_status, ) - job = backend.retrieve_job(job_id) - break - # for other cases, resubmit the circuit until the result is available. - # since if there is no result returned, there is no way algorithm can do any process - if job_status == JobStatus.CANCELLED: - logger.warning("FAILURE: Job id: %s is cancelled. Re-submit the circuits.", job_id) - elif job_status == JobStatus.ERROR: - logger.warning( - "FAILURE: Job id: %s encounters the error. " - "Error is : %s. Re-submit the circuits.", - job_id, - job.error_message(), - ) - else: - logging.warning( - "FAILURE: Job id: %s. Unknown status: %s. " "Re-submit the circuits.", - job_id, - job_status, - ) - job, job_id = _safe_submit_circuits( - circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config, - ) + job, job_id = _safe_submit_circuits( + split_circuits[idx], + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config, + ) else: result = job.result() diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py index d684789aa750..6cfd4824b364 100644 --- a/test/python/algorithms/test_backendv1.py +++ b/test/python/algorithms/test_backendv1.py @@ -80,6 +80,20 @@ def test_run_circuit_oracle(self): result = grover.amplify(problem) self.assertIn(result.top_measurement, ["11"]) + def test_run_circuit_oracle_single_experiment_backend(self): + """Test execution with a quantum circuit oracle""" + oracle = QuantumCircuit(2) + oracle.cz(0, 1) + problem = AmplificationProblem(oracle, is_good_state=["11"]) + backend = self._provider.get_backend("fake_yorktown") + backend._configuration.max_experiments = 1 + qi = QuantumInstance( + self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 + ) + grover = Grover(quantum_instance=qi) + result = grover.amplify(problem) + self.assertIn(result.top_measurement, ["11"]) + def test_measurement_error_mitigation_with_vqe(self): """measurement error mitigation test with vqe""" try: From 164fd67c3ea19205923f245b76926a1e8a02fd79 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 10 May 2021 15:51:00 -0400 Subject: [PATCH 2/6] Fix issues with results and split circuits path --- qiskit/utils/run_circuits.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index f7716af5603d..7cbcc26f3c17 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -472,9 +472,9 @@ def run_circuits( count = 0 while count < len(circuits): some_circuits = circuits[count : count + max_circuits_per_job] - circuits.append(some_circuits) + split_circuits.append(some_circuits) job, job_id = _safe_submit_circuits( - circuits, + some_circuits, backend, qjob_config=qjob_config, backend_options=backend_options, @@ -496,12 +496,13 @@ def run_circuits( jobs = [job] job_ids = [job_id] split_circuits = [circuits] - result = None + results = [] if with_autorecover: logger.info("Backend status: %s", backend.status()) logger.info("There are %s jobs are submitted.", len(jobs)) logger.info("All job ids:\n%s", job_ids) for idx, _ in enumerate(jobs): + result = None logger.info("Backend status: %s", backend.status()) logger.info("There is one jobs are submitted: id: %s", job_id) job = jobs[idx] @@ -531,7 +532,8 @@ def run_circuits( while True: result = job.result() if result.success: - logger.info("COMPLETED: job id: %s", job_id) + results.append(result) + logger.info("COMPLETED the %s-th job, job id: %s", idx, job_id) break logger.warning("FAILURE: Job id: %s", job_id) @@ -570,7 +572,11 @@ def run_circuits( run_config=run_config, ) else: - result = job.result() + results = [] + for job in jobs: + results.append(job.result(**qjob_config)) + + result = _combine_result_objects(results) if results else None # If result was not successful then raise an exception with either the status msg or # extra information if this was an Aer partial result return From e1b6c287e005065902b53279845645e09b7ca418 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 10 May 2021 15:58:25 -0400 Subject: [PATCH 3/6] Fix copy paste issue --- qiskit/utils/run_circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 7cbcc26f3c17..16c336bf0ab2 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -574,7 +574,7 @@ def run_circuits( else: results = [] for job in jobs: - results.append(job.result(**qjob_config)) + results.append(job.result()) result = _combine_result_objects(results) if results else None From 9895a87d1caa418dff569ff9e4cd09c33fc6ae63 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 11 May 2021 14:53:49 -0400 Subject: [PATCH 4/6] Update qiskit/utils/run_circuits.py --- qiskit/utils/run_circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 16c336bf0ab2..1f24e63cbf32 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -518,7 +518,7 @@ def run_circuits( if job_callback is not None: job_callback(job_id, job_status, queue_position, job) break - if job_status == JobStatus.QUEUED and hasattr(job, queue_position): + if job_status == JobStatus.QUEUED and hasattr(job, "queue_position"): queue_position = job.queue_position() logger.info("Job id: %s is queued at position %s", job_id, queue_position) else: From 1fad480f6fcdef6448ada11db3834841f21321de Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 11 May 2021 15:02:40 -0400 Subject: [PATCH 5/6] Add release notes --- ...ax-experiments-quantum-instance-aae99429034aab52.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml diff --git a/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml b/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml new file mode 100644 index 000000000000..d123a4bf3c19 --- /dev/null +++ b/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue with the :class:`~qiskit.utils.QuantumInstance` with + :class:`~qiskit.providers.BackendV1` backends with the + :attr:`~qiskit.providers.models.BackendConfiguration.`max_experiments` + attribute set to a value less than the number of circuits to run. Previously + the :class:`~qiskit.utils.QuantumInstance` would not correctly split the + circuits to run into separate jobs, which has been corrected. From 9af296ce3a8d5b9d5d3c00a72691de3f5767cf0c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 12 May 2021 13:54:37 -0400 Subject: [PATCH 6/6] Fix whitespace --- ...spect-max-experiments-quantum-instance-aae99429034aab52.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml b/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml index d123a4bf3c19..3c098eacd00f 100644 --- a/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml +++ b/releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml @@ -2,7 +2,7 @@ fixes: - | Fixed an issue with the :class:`~qiskit.utils.QuantumInstance` with - :class:`~qiskit.providers.BackendV1` backends with the + :class:`~qiskit.providers.BackendV1` backends with the :attr:`~qiskit.providers.models.BackendConfiguration.`max_experiments` attribute set to a value less than the number of circuits to run. Previously the :class:`~qiskit.utils.QuantumInstance` would not correctly split the