From 080ad6e6abe2ae4a77e43aee6c36582fc580bd31 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 13 May 2021 20:23:28 -0400 Subject: [PATCH 1/3] Respect max_experiments in QuantumInstance BackendV1 path (#6391) * 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). * Fix issues with results and split circuits path * Fix copy paste issue * Update qiskit/utils/run_circuits.py * Add release notes * Fix whitespace Co-authored-by: Kevin Krsulich Co-authored-by: Manoel Marques (cherry picked from commit 9d4bb91b8751ce12f6299cf8cabd220221754ec6) # Conflicts: # qiskit/utils/run_circuits.py # test/python/algorithms/test_backendv1.py --- qiskit/utils/run_circuits.py | 137 ++++++++++++++++-- ...nts-quantum-instance-aae99429034aab52.yaml | 9 ++ test/python/algorithms/test_backendv1.py | 61 ++++++++ 3 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/respect-max-experiments-quantum-instance-aae99429034aab52.yaml diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index c9895be31361..4e443313b974 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -418,6 +418,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], run_config = run_config or {} with_autorecover = not is_simulator_backend(backend) +<<<<<<< HEAD job, job_id = _safe_submit_circuits(circuits, backend, qjob_config=qjob_config, @@ -425,19 +426,77 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], noise_config=noise_config, run_config=run_config) result = None +======= + 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] + split_circuits.append(some_circuits) + job, job_id = _safe_submit_circuits( + some_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] + results = [] +>>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) 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): + result = None + 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) +<<<<<<< HEAD break if job_status == JobStatus.QUEUED and hasattr(job, queue_position): queue_position = job.queue_position() @@ -447,15 +506,20 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], 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"]) +>>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) - # 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: + results.append(result) + logger.info("COMPLETED the %s-th job, job id: %s", idx, job_id) + break +<<<<<<< HEAD logger.warning("FAILURE: Job id: %s", job_id) logger.warning("Job (%s) is completed anyway, retrieve result " "from backend again.", job_id) @@ -480,8 +544,49 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend_options=backend_options, noise_config=noise_config, run_config=run_config) +======= + 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( + "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( + split_circuits[idx], + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config, + ) +>>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) else: - result = job.result() + results = [] + for job in jobs: + results.append(job.result()) + + 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 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..3c098eacd00f --- /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. diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py index 7249af0dfa9d..742959762caf 100644 --- a/test/python/algorithms/test_backendv1.py +++ b/test/python/algorithms/test_backendv1.py @@ -66,5 +66,66 @@ def test_vqe_qasm(self): self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) +<<<<<<< HEAD if __name__ == '__main__': +======= + 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 qiskit.ignis.mitigation.measurement import CompleteMeasFitter + from qiskit.providers.aer import noise + except ImportError as ex: + self.skipTest("Package doesn't appear to be installed. Error: '{}'".format(str(ex))) + return + + algorithm_globals.random_seed = 0 + + # build noise model + noise_model = noise.NoiseModel() + read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) + noise_model.add_all_qubit_readout_error(read_err) + + backend = self._qasm + + quantum_instance = QuantumInstance( + backend=backend, + seed_simulator=167, + seed_transpiler=167, + noise_model=noise_model, + measurement_error_mitigation_cls=CompleteMeasFitter, + ) + + h2_hamiltonian = ( + -1.052373245772859 * (I ^ I) + + 0.39793742484318045 * (I ^ Z) + - 0.39793742484318045 * (Z ^ I) + - 0.01128010425623538 * (Z ^ Z) + + 0.18093119978423156 * (X ^ X) + ) + optimizer = SPSA(maxiter=200) + ansatz = EfficientSU2(2, reps=1) + + vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) + result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) + self.assertGreater(quantum_instance.time_taken, 0.0) + quantum_instance.reset_execution_results() + self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) + + +if __name__ == "__main__": +>>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) unittest.main() From 48d2918e9622560122ed1290eed2262f87ab2fd8 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 14 May 2021 12:46:47 -0400 Subject: [PATCH 2/3] Fix merge conflicts This commit fixes the merge conflicts from backporting #6391 to stable/0.17. The tests added in #6391 are just removed because they can't actually run without #6286 which isn't backportable. --- qiskit/utils/run_circuits.py | 49 ------------------- test/python/algorithms/test_backendv1.py | 61 ------------------------ 2 files changed, 110 deletions(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 4e443313b974..7f5b1600daf8 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -418,15 +418,6 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], run_config = run_config or {} with_autorecover = not is_simulator_backend(backend) -<<<<<<< HEAD - job, job_id = _safe_submit_circuits(circuits, - backend, - qjob_config=qjob_config, - backend_options=backend_options, - noise_config=noise_config, - run_config=run_config) - result = None -======= if MAX_CIRCUITS_PER_JOB is not None: max_circuits_per_job = int(MAX_CIRCUITS_PER_JOB) else: @@ -467,7 +458,6 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], job_ids = [job_id] split_circuits = [circuits] results = [] ->>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) if with_autorecover: logger.info("Backend status: %s", backend.status()) logger.info("There are %s jobs are submitted.", len(jobs)) @@ -496,19 +486,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], 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) -<<<<<<< HEAD - 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"]) ->>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) # get result after the status is DONE if job_status == JobStatus.DONE: @@ -519,32 +497,6 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], logger.info("COMPLETED the %s-th job, job id: %s", idx, job_id) break -<<<<<<< HEAD - 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("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) -======= logger.warning("FAILURE: Job id: %s", job_id) logger.warning( "Job (%s) is completed anyway, retrieve result " "from backend again.", @@ -580,7 +532,6 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], noise_config=noise_config, run_config=run_config, ) ->>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) else: results = [] for job in jobs: diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py index 742959762caf..7249af0dfa9d 100644 --- a/test/python/algorithms/test_backendv1.py +++ b/test/python/algorithms/test_backendv1.py @@ -66,66 +66,5 @@ def test_vqe_qasm(self): self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) -<<<<<<< HEAD if __name__ == '__main__': -======= - 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 qiskit.ignis.mitigation.measurement import CompleteMeasFitter - from qiskit.providers.aer import noise - except ImportError as ex: - self.skipTest("Package doesn't appear to be installed. Error: '{}'".format(str(ex))) - return - - algorithm_globals.random_seed = 0 - - # build noise model - noise_model = noise.NoiseModel() - read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]]) - noise_model.add_all_qubit_readout_error(read_err) - - backend = self._qasm - - quantum_instance = QuantumInstance( - backend=backend, - seed_simulator=167, - seed_transpiler=167, - noise_model=noise_model, - measurement_error_mitigation_cls=CompleteMeasFitter, - ) - - h2_hamiltonian = ( - -1.052373245772859 * (I ^ I) - + 0.39793742484318045 * (I ^ Z) - - 0.39793742484318045 * (Z ^ I) - - 0.01128010425623538 * (Z ^ Z) - + 0.18093119978423156 * (X ^ X) - ) - optimizer = SPSA(maxiter=200) - ansatz = EfficientSU2(2, reps=1) - - vqe = VQE(ansatz=ansatz, optimizer=optimizer, quantum_instance=quantum_instance) - result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) - self.assertGreater(quantum_instance.time_taken, 0.0) - quantum_instance.reset_execution_results() - self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) - - -if __name__ == "__main__": ->>>>>>> 9d4bb91b8... Respect max_experiments in QuantumInstance BackendV1 path (#6391) unittest.main() From 5f51903325fcf8cdbc1fe71a1fee0afa28202557 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 14 May 2021 16:38:00 -0400 Subject: [PATCH 3/3] Fix lint --- 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 7f5b1600daf8..d8bbf1684c80 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -432,7 +432,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], split_circuits = [] count = 0 while count < len(circuits): - some_circuits = circuits[count : count + max_circuits_per_job] + some_circuits = circuits[count: count + max_circuits_per_job] split_circuits.append(some_circuits) job, job_id = _safe_submit_circuits( some_circuits,