From eebbae2c06212216c2d9f8cf1024313964825486 Mon Sep 17 00:00:00 2001 From: wederbn Date: Tue, 17 Nov 2020 10:34:04 +0100 Subject: [PATCH 1/4] Add route to calculate calibration matrix --- app/ibmq_handler.py | 38 +++++++++++++++++++++++++++++++++++++- app/routes.py | 23 +++++++++++++++++++++++ app/tasks.py | 19 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/app/ibmq_handler.py b/app/ibmq_handler.py index a148e8f..3e02207 100644 --- a/app/ibmq_handler.py +++ b/app/ibmq_handler.py @@ -16,8 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ****************************************************************************** - +import time +import qiskit from qiskit import IBMQ, assemble, QiskitError +from qiskit.ignis.mitigation import CompleteMeasFitter, complete_meas_cal from qiskit.providers.jobstatus import JOB_FINAL_STATES from qiskit.providers.exceptions import JobError, JobTimeoutError from qiskit.providers.exceptions import QiskitBackendNotFoundError @@ -82,3 +84,37 @@ def execute_job(transpiled_circuit, shots, backend): return {'job_result_raw': job_result_dict, 'statevector': statevector, 'counts': counts, 'unitary': unitary} except (JobError, JobTimeoutError): return None + + +def calculate_calibration_matrix(token, qpu_name, shots): + """Execute the calibration circuits on the given backend and calculate resulting matrix.""" + print("Starting calculation of calibration matrix for QPU: ", qpu_name) + + backend = get_qpu(token, qpu_name) + + # Generate a calibration circuit for each state + qr = qiskit.QuantumRegister(len(backend.properties().qubits)) + meas_calibs, state_labels = complete_meas_cal(qr=qr, circlabel='mcal') + + # Execute each calibration circuit and store results + print('Executing ' + str(len(meas_calibs)) + ' circuits to create calibration matrix...') + cal_results = [] + for circuit in meas_calibs: + print('Executing circuit ' + circuit.name) + cal_results.append(execute_calibration_circuit(circuit, shots, backend)) + + # Generate calibration matrix out of measurement results + meas_fitter = CompleteMeasFitter(cal_results, state_labels, circlabel='mcal') + return meas_fitter.filter + + +def execute_calibration_circuit(circuit, shots, backend): + """Execute a calibration circuit on the specified backend""" + job = qiskit.execute(circuit, backend=backend, shots=shots) + + job_status = job.status() + while job_status not in JOB_FINAL_STATES: + print('The execution is still running') + time.sleep(20) + job_status = job.status() + return job.result() diff --git a/app/routes.py b/app/routes.py index aab96e7..4cf6598 100644 --- a/app/routes.py +++ b/app/routes.py @@ -108,6 +108,29 @@ def execute_circuit(): return response +@app.route('/qiskit-service/api/v1.0/calculate-calibration-matrix', methods=['POST']) +def calculate_calibration_matrix(): + """Put calibration matrix calculation job in queue. Return location of the later result.""" + if not request.json or not 'qpu-name' in request.json or not 'token' in request.json: + abort(400) + qpu_name = request.json['qpu-name'] + token = request.json['token'] + shots = request.json.get('shots', 8192) + + job = app.execute_queue.enqueue('app.tasks.calculate_calibration_matrix', qpu_name=qpu_name, token=token, + shots=shots) + result = Result(id=job.get_id()) + db.session.add(result) + db.session.commit() + + logging.info('Returning HTTP response to client...') + content_location = '/qiskit-service/api/v1.0/results/' + result.id + response = jsonify({'Location': content_location}) + response.status_code = 202 + response.headers['Location'] = content_location + return response + + @app.route('/qiskit-service/api/v1.0/results/', methods=['GET']) def get_result(result_id): """Return result when it is available.""" diff --git a/app/tasks.py b/app/tasks.py index 586e211..452e922 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -71,3 +71,22 @@ def execute(impl_url, input_params, token, qpu_name, shots): db.session.commit() # ibmq_handler.delete_token() + + +def calculate_calibration_matrix(token, qpu_name, shots): + """Calculate the current calibration matrix for the given QPU and save the result in db""" + job = get_current_job() + + backend = ibmq_handler.get_qpu(token, qpu_name) + if backend: + job_result = ibmq_handler.calculate_calibration_matrix(token, qpu_name, shots) + if job_result: + result = Result.query.get(job.get_id()) + result.result = json.dumps(job_result) + result.complete = True + db.session.commit() + else: + result = Result.query.get(job.get_id()) + result.result = json.dumps({'error': 'qpu-name or token wrong'}) + result.complete = True + db.session.commit() From b35717ba3d013328e7988ea5515631d54ee49ca8 Mon Sep 17 00:00:00 2001 From: wederbn Date: Tue, 17 Nov 2020 12:35:54 +0100 Subject: [PATCH 2/4] Return matrix instead of meas_filter --- app/NumpyEncoder.py | 27 +++++++++++++++++++++++++++ app/tasks.py | 9 ++++++++- requirements.txt | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/NumpyEncoder.py diff --git a/app/NumpyEncoder.py b/app/NumpyEncoder.py new file mode 100644 index 0000000..549ec21 --- /dev/null +++ b/app/NumpyEncoder.py @@ -0,0 +1,27 @@ +# ****************************************************************************** +# Copyright (c) 2020 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. +# ****************************************************************************** +import json +import numpy as np + + +class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + return json.JSONEncoder.default(self, obj) diff --git a/app/tasks.py b/app/tasks.py index 452e922..1924213 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -21,6 +21,8 @@ from qiskit import transpile from qiskit.transpiler.exceptions import TranspilerError from rq import get_current_job + +from app.NumpyEncoder import NumpyEncoder from app.result_model import Result import logging import json @@ -82,7 +84,12 @@ def calculate_calibration_matrix(token, qpu_name, shots): job_result = ibmq_handler.calculate_calibration_matrix(token, qpu_name, shots) if job_result: result = Result.query.get(job.get_id()) - result.result = json.dumps(job_result) + result.result = json.dumps({'matrix': job_result.cal_matrix}, cls=NumpyEncoder) + result.complete = True + db.session.commit() + else: + result = Result.query.get(job.get_id()) + result.result = json.dumps({'error': 'matrix calculation failed'}) result.complete = True db.session.commit() else: diff --git a/requirements.txt b/requirements.txt index a76bf32..21bb948 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ rq==1.4.1 SQLAlchemy==1.3.16 urllib3==1.25.9 Werkzeug==1.0.1 +numpy~=1.19.0 gunicorn \ No newline at end of file From a8f3b2181629f03275a9aa4ef087a8f566fad74a Mon Sep 17 00:00:00 2001 From: wederbn Date: Tue, 17 Nov 2020 14:00:06 +0100 Subject: [PATCH 3/4] Rename method and improve imports --- app/ibmq_handler.py | 14 +++++++------- app/tasks.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/ibmq_handler.py b/app/ibmq_handler.py index 3e02207..3a9cb4e 100644 --- a/app/ibmq_handler.py +++ b/app/ibmq_handler.py @@ -16,9 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # ****************************************************************************** -import time -import qiskit -from qiskit import IBMQ, assemble, QiskitError +from time import sleep + +from qiskit import IBMQ, assemble, QiskitError, QuantumRegister, execute from qiskit.ignis.mitigation import CompleteMeasFitter, complete_meas_cal from qiskit.providers.jobstatus import JOB_FINAL_STATES from qiskit.providers.exceptions import JobError, JobTimeoutError @@ -86,14 +86,14 @@ def execute_job(transpiled_circuit, shots, backend): return None -def calculate_calibration_matrix(token, qpu_name, shots): +def get_meas_fitter(token, qpu_name, shots): """Execute the calibration circuits on the given backend and calculate resulting matrix.""" print("Starting calculation of calibration matrix for QPU: ", qpu_name) backend = get_qpu(token, qpu_name) # Generate a calibration circuit for each state - qr = qiskit.QuantumRegister(len(backend.properties().qubits)) + qr = QuantumRegister(len(backend.properties().qubits)) meas_calibs, state_labels = complete_meas_cal(qr=qr, circlabel='mcal') # Execute each calibration circuit and store results @@ -110,11 +110,11 @@ def calculate_calibration_matrix(token, qpu_name, shots): def execute_calibration_circuit(circuit, shots, backend): """Execute a calibration circuit on the specified backend""" - job = qiskit.execute(circuit, backend=backend, shots=shots) + job = execute(circuit, backend=backend, shots=shots) job_status = job.status() while job_status not in JOB_FINAL_STATES: print('The execution is still running') - time.sleep(20) + sleep(20) job_status = job.status() return job.result() diff --git a/app/tasks.py b/app/tasks.py index 1924213..91d636d 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -81,7 +81,7 @@ def calculate_calibration_matrix(token, qpu_name, shots): backend = ibmq_handler.get_qpu(token, qpu_name) if backend: - job_result = ibmq_handler.calculate_calibration_matrix(token, qpu_name, shots) + job_result = ibmq_handler.get_meas_fitter(token, qpu_name, shots) if job_result: result = Result.query.get(job.get_id()) result.result = json.dumps({'matrix': job_result.cal_matrix}, cls=NumpyEncoder) From e993bf2eff766b2b19ad622a426ac0a1b41f34aa Mon Sep 17 00:00:00 2001 From: wederbn Date: Tue, 17 Nov 2020 14:04:03 +0100 Subject: [PATCH 4/4] Add documentation for the calibration matrix calculation --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 5164475..d5ede65 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,20 @@ Send implementation, input, QPU information, and your IBM Quantum Experience tok Returns a content location for the result. Access it via `GET`. +## Calibration Matrix Calculation Request +Send QPU information, optional shots, and your IBM Quantum Experience token to the API to calculate the calibration matrix for the given QPU. + +`POST /qiskit-service/api/v1.0/calculate-calibration-matrix` +``` +{ + "qpu-name": "NAME-OF-QPU", + "token": "YOUR-IBMQ-TOKEN", + "shots": "NUMBER-OF-SHOTS" +} +``` + +Returns a content location for the result. Access it via `GET`. + ## Sample Implementations for Transpilation and Execution Sample implementations can be found [here](https://github.com/UST-QuAntiL/nisq-analyzer-content/tree/master/example-implementations). Please use the raw GitHub URL as `impl-url` value (see [example](https://raw.githubusercontent.com/UST-QuAntiL/nisq-analyzer-content/master/example-implementations/Shor/shor-general-qiskit.py)).