diff --git a/README.md b/README.md index 0e6276c..ddacbac 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ The qiskit-service provides a Swagger UI, specifying the request schemas and sho _Note_: To run the setup as a developer, see [here](./docs/dev/run-as-dev.md). +## Using IonQ QPUs +To execute quantum circuits on IonQ QPUs an IonQ account with the respective access rights is required. +The IonQ API token has to be provided in the request body to transpile or execute quantum circuits on IonQ QPUs. + ## Using AWS Braket QPUs To execute quantum circuits on AWS Braket QPUs via the qiskit service, an AWS IAM account with the Braket Group membership is required. The AWS access key and the AWS secret access key have to be provided in the request json body to transpile or execute quantum circuits for/on AWS Braket QPUs. diff --git a/app/ionq_handler.py b/app/ionq_handler.py new file mode 100644 index 0000000..3a0e287 --- /dev/null +++ b/app/ionq_handler.py @@ -0,0 +1,69 @@ +# ****************************************************************************** +# Copyright (c) 2023 University of Stuttgart +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ****************************************************************************** +from qiskit import QiskitError +from qiskit.providers.jobstatus import JOB_FINAL_STATES +from qiskit_ionq import IonQProvider + + +def get_qpu(token, qpu_name): + provider = IonQProvider(token) + return provider.get_backend(qpu_name) + + +def execute_job(transpiled_circuit, shots, backend): + """Generate qObject from transpiled circuit and execute it. Return result.""" + + try: + job = backend.run(transpiled_circuit, shots=shots) + + job_status = job.status() + while job_status not in JOB_FINAL_STATES: + print("The job is still running") + job_status = job.status() + + job_result = job.result() + print("\nJob result:") + print(job_result) + job_result_dict = job_result.to_dict() + print(job_result_dict) + + try: + statevector = job_result.get_statevector() + print("\nState vector:") + print(statevector) + except QiskitError: + statevector = None + print("No statevector available!") + try: + counts = job_result.get_counts() + print("\nCounts:") + print(counts) + except QiskitError: + counts = None + print("No counts available!") + try: + unitary = job_result.get_unitary() + print("\nUnitary:") + print(unitary) + except QiskitError: + unitary = None + print("No unitary available!") + return {'job_result_raw': job_result_dict, 'statevector': statevector, 'counts': counts, 'unitary': unitary} + except Exception: + return None diff --git a/app/routes.py b/app/routes.py index d775297..8968aa0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -19,7 +19,7 @@ from qiskit.providers.ibmq import IBMQAccountError from app import app, benchmarking, aws_handler, ibmq_handler, implementation_handler, db, parameters, circuit_analysis, \ - analysis + analysis, ionq_handler from app.benchmark_model import Benchmark from app.qpu_metrics import generate_deterministic_uuid, get_all_qpus_and_metrics_as_json_str from app.result_model import Result @@ -48,7 +48,7 @@ def transpile_circuit(): if input_params: input_params = parameters.ParameterDictionary(input_params) - if provider == 'ibmq': + if provider == 'ibmq' or provider == 'ionq': if 'token' in input_params: token = input_params['token'] elif 'token' in request.json: @@ -131,6 +131,8 @@ def transpile_circuit(): if 'project' in input_params: credentials['project'] = input_params['project'] backend = ibmq_handler.get_qpu(token, qpu_name, **credentials) + elif provider == 'ionq': + backend = ionq_handler.get_qpu(token, qpu_name) elif provider == 'aws': if 'region' in input_params: credentials['region'] = input_params['region'] @@ -292,7 +294,7 @@ def execute_circuit(): aws_access_key_id = '' aws_secret_access_key = '' - if provider == 'ibmq': + if provider == 'ibmq' or provider == 'ionq': if 'token' in input_params: token = input_params['token'] elif 'token' in request.json: @@ -328,7 +330,7 @@ def execute_circuit(): job = app.execute_queue.enqueue('app.tasks.execute', provider=provider, impl_url=impl_url, impl_data=impl_data, impl_language=impl_language, transpiled_qasm=transpiled_qasm, qpu_name=qpu_name, - token_ibmq=token, access_key_aws=aws_access_key_id, + token=token, access_key_aws=aws_access_key_id, secret_access_key_aws=aws_secret_access_key, input_params=input_params, noise_model=noise_model, only_measurement_errors=only_measurement_errors, diff --git a/app/tasks.py b/app/tasks.py index 939f4bf..3b12a41 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -18,7 +18,7 @@ # ****************************************************************************** import datetime -from app import implementation_handler, aws_handler, ibmq_handler, db, app +from app import implementation_handler, aws_handler, ibmq_handler, db, app, ionq_handler from qiskit.transpiler.exceptions import TranspilerError from rq import get_current_job from qiskit.utils.measurement_error_mitigation import get_measured_qubits @@ -33,7 +33,7 @@ import base64 -def execute(provider, impl_url, impl_data, impl_language, transpiled_qasm, input_params, token_ibmq, access_key_aws, +def execute(provider, impl_url, impl_data, impl_language, transpiled_qasm, input_params, token, access_key_aws, secret_access_key_aws, qpu_name, optimization_level, noise_model, only_measurement_errors, shots, bearer_token, qasm_string, **kwargs): """Create database entry for result. Get implementation code, prepare it, and execute it. Save result in db""" @@ -41,7 +41,9 @@ def execute(provider, impl_url, impl_data, impl_language, transpiled_qasm, input job = get_current_job() if provider == 'ibmq': - backend = ibmq_handler.get_qpu(token_ibmq, qpu_name, **kwargs) + backend = ibmq_handler.get_qpu(token, qpu_name, **kwargs) + elif provider == 'ionq': + backend = ionq_handler.get_qpu(token, qpu_name) elif provider == 'aws': backend = aws_handler.get_qpu(access_key=access_key_aws, secret_access_key=secret_access_key_aws, qpu_name=qpu_name, @@ -78,8 +80,8 @@ def execute(provider, impl_url, impl_data, impl_language, transpiled_qasm, input db.session.commit() app.logger.info('Start transpiling...') - if noise_model: - noisy_qpu = ibmq_handler.get_qpu(token_ibmq, noise_model, **kwargs) + if noise_model and provider == 'ibmq': + noisy_qpu = ibmq_handler.get_qpu(token, noise_model, **kwargs) noise_model = NoiseModel.from_backend(noisy_qpu) properties = noisy_qpu.properties() configuration = noisy_qpu.configuration() @@ -115,6 +117,8 @@ def execute(provider, impl_url, impl_data, impl_language, transpiled_qasm, input if provider == 'aws' and not noise_model: # Note: AWS cannot handle such a noise model job_result = aws_handler.execute_job(transpiled_circuits, shots, backend) + elif provider == 'ionq' and not noise_model: + job_result = ionq_handler.execute_job(transpiled_circuits, shots, backend) else: # If we need a noise model, we have to use IBM Q job_result = ibmq_handler.execute_job(transpiled_circuits, shots, backend, noise_model) diff --git a/requirements.txt b/requirements.txt index 6388ed8..f3ffb47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,6 +44,7 @@ qiskit-dynamics==0.4.0 qiskit-experiments==0.5.2 qiskit-ibm-experiment==0.3.3 qiskit-ibmq-provider==0.20.2 +qiskit-ionq==0.4.4 qiskit-terra==0.24.0 qiskit_braket_provider==0.0.4 redis==4.3.1