diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md index 1cc239e..9133e62 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ # qiskit-bot + Bots that use Qiskit + +# QuantumComputingBot + +Monitoring the load of IBM Q processors from [IBM Quantum Experience](https://quantumexperience.ng.bluemix.net) (data for 24 hours). + +| Calibration for [IBM QX 4](https://github.com/QISKit/ibmqx-backend-information/tree/master/backends/ibmqx4/V1) | Pending jobs for [IBM QX 5](https://github.com/QISKit/ibmqx-backend-information/blob/master/backends/ibmqx5/README.md) | +| :------------- | :------------- | +| | | + +### Bot for Slack + +Bot is already available inside [QISKit](https://qiskit.org) workspace in [Slack](https://slack.com). + +Bot understands the following commands: + +* `/full [backend]` - calibration and pending jobs info for backend +* `/gate_errors [backend]` - send gate errors +* `/jobs [backend]` - diagram of pending jobs +* `/readout_errors [backend]` - send readout errors + +`backend` is the name for quantum processors like [ibmqx4](https://github.com/QISKit/ibmqx-backend-information/tree/master/backends/ibmqx4/V1) or [ibmqx5](https://github.com/QISKit/ibmqx-backend-information/blob/master/backends/ibmqx5/README.md). + +| Step 1 | Step 2 | Step 3 | +| :-------------: | :-------------: | :-------------: | +| | | | + +| Pass backend as argument - 1 | Pass backend as argument - 2 | +| :-------------: | :-------------: | +| | | + + +## Description + +Structure of the project: +``` +... +├── main_slack.py <- Slack bot. +├── main_dumper.py <- Crawler that dumps statistics +| into pickled data. +├── utils.py <- File with methods for plotting. +├── data <- Folder with pickled data. +│   ├── real_data_1.pkl +│   └── ... +├── img <- Images for this README. +│   ├── jobs_demo.jpg +│   └── ... +├── res <- Folder with different resource files +| | including tokens for APIs. +│   ├── rqc.jpg +│   ├── token_q.json +│   └── ... +└── tmp <- Folder with generated plots. +    ├── ibmqx4_full.png +    └── ... +``` + + +## Authors + +🇦🇺🇷🇺[Yaroslav Kharkov](https://www.physics.unsw.edu.au/staff/yaroslav-kharkov) <[y.kharkov@gmail.com](mailto:y.kharkov@gmail.com)>, [Anton Karazeev](https://akarazeev.github.io) <[anton.karazeev@gmail.com](mailto:anton.karazeev@gmail.com)> diff --git a/data/real_data_1.pkl b/data/real_data_1.pkl new file mode 100644 index 0000000..b4c164a Binary files /dev/null and b/data/real_data_1.pkl differ diff --git a/data/real_data_2.pkl b/data/real_data_2.pkl new file mode 100644 index 0000000..73445cf Binary files /dev/null and b/data/real_data_2.pkl differ diff --git a/img/errors_demo.png b/img/errors_demo.png new file mode 100644 index 0000000..c796365 Binary files /dev/null and b/img/errors_demo.png differ diff --git a/img/jobs_demo.png b/img/jobs_demo.png new file mode 100644 index 0000000..c0a4442 Binary files /dev/null and b/img/jobs_demo.png differ diff --git a/img/qiskit-logo-black.png b/img/qiskit-logo-black.png new file mode 100644 index 0000000..31cfb96 Binary files /dev/null and b/img/qiskit-logo-black.png differ diff --git a/img/rqc.png b/img/rqc.png new file mode 100644 index 0000000..0ab1f95 Binary files /dev/null and b/img/rqc.png differ diff --git a/img/slack.png b/img/slack.png new file mode 100644 index 0000000..c753eeb Binary files /dev/null and b/img/slack.png differ diff --git a/img/slack_1.jpg b/img/slack_1.jpg new file mode 100644 index 0000000..e3ea006 Binary files /dev/null and b/img/slack_1.jpg differ diff --git a/img/slack_2.jpg b/img/slack_2.jpg new file mode 100644 index 0000000..6fe625d Binary files /dev/null and b/img/slack_2.jpg differ diff --git a/img/slack_3.jpg b/img/slack_3.jpg new file mode 100644 index 0000000..52ec761 Binary files /dev/null and b/img/slack_3.jpg differ diff --git a/img/slack_jobs_1.jpg b/img/slack_jobs_1.jpg new file mode 100644 index 0000000..a1ba9a1 Binary files /dev/null and b/img/slack_jobs_1.jpg differ diff --git a/img/slack_jobs_2.jpg b/img/slack_jobs_2.jpg new file mode 100644 index 0000000..81b28c9 Binary files /dev/null and b/img/slack_jobs_2.jpg differ diff --git a/main_dumper.py b/main_dumper.py new file mode 100644 index 0000000..3b9534a --- /dev/null +++ b/main_dumper.py @@ -0,0 +1,82 @@ +from IBMQuantumExperience import IBMQuantumExperience +from slackclient import SlackClient +import json +import time +import pickle +import logging +import requests +import os +import utils + +# Enable logging +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - \ + %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +with open('res/token_q.json') as jsn: + qx_config = json.load(jsn) +api = IBMQuantumExperience(token=qx_config['APItoken'], config={'url': qx_config['url']}) + + +def dumper(delay): + if os.path.exists(utils.PKL1) is False: + data = list() + with open(utils.PKL1, 'wb') as f: + pickle.dump(data, f) + if os.path.exists(utils.PKL2) is False: + data = list() + with open(utils.PKL2, 'wb') as f: + pickle.dump(data, f) + + step = 0 + + logger.info("Dumper is running!") + while True: + # Load. + data = utils.load_data() + + if len(data) > 0: + # Store data for 24 hours only. + day_back = max([x[0] for x in data]) - 86400 + data = list(filter(lambda x: x[0] >= day_back, data)) + + # Append. + # remote_backends = discover_remote_backends(api) + remote_backends = utils.backends + try: + device_status = [api.backend_status(backend) for backend in remote_backends] + data.append((time.time(), device_status)) + except requests.exceptions.ConnectionError as e: + print(e) + except Exception as e: + pass + + # Store. + if step == 0: + with open(utils.PKL1, 'wb') as f: + pickle.dump(data, f) + elif step == 1: + with open(utils.PKL2, 'wb') as f: + pickle.dump(data, f) + + step += 1 + step %= 2 + + ############# + # Make Plots. + for backend in utils.backends: + utils.plot_full(backend, api) + ### + + # Sleep. + time.sleep(delay) + + +def main(): + delay = 30 # Seconds. + dumper(delay) + + +if __name__ == "__main__": + main() diff --git a/main_slack.py b/main_slack.py new file mode 100644 index 0000000..26b0776 --- /dev/null +++ b/main_slack.py @@ -0,0 +1,171 @@ +from flask import abort, Flask, jsonify, request +import concurrent.futures as cf +import requests +import logging +import utils +import json +import os + +# Enable logging +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - \ + %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +app = Flask(__name__) + +slack_token = utils.get_token('res/token_slack.json') +pool = cf.ThreadPoolExecutor(4) + +extension_gateerrors = '_gateerrors_full.png' +extension_readouterrors = '_readouterrors_full.png' +extension_jobs = '_jobs_full.png' +extension_full = '_full.png' + + +### +# Slack API +### +@app.route('/confirm', methods=['POST']) +def confirm(): + req = request.form.to_dict() + data = json.loads(req["payload"]) + backend = data["actions"][0]["name"] + value = data["actions"][0]["value"] + + reply = None + name = None + if value.endswith(extension_jobs): + name = 'Pending jobs for {}'.format(backend) + reply = '*Pending jobs* for {} will be sent soon ...'.format(backend) + elif value.endswith(extension_gateerrors): + name = 'Gate errors for {}'.format(backend) + reply = '*Gate errors* for {} will be sent soon ...'.format(backend) + elif value.endswith(extension_readouterrors): + name = 'Readout errors for {}'.format(backend) + reply = '*Readout errors* for {} will be sent soon ...'.format(backend) + elif value.endswith(extension_full): + name = 'Full statistics for {}'.format(backend) + reply = '*Full statistics* for {} will be sent soon ...'.format(backend) + + if name is not None: + pool.submit(send_image, 'tmp/{}'.format(value), name, data['channel']['id']) + + return reply + + +### +# Bot Commands +### +@app.route('/gateerrors', methods=['POST']) +def gateerrors(): + data = request.form.to_dict() + backend = data['text'].lower() + + if backend in utils.backends: + name = 'Gate errors for {}'.format(backend) + pool.submit(send_image, 'tmp/{}{}'.format(backend, extension_gateerrors), + name, data['channel_id']) + return "Wait a sec ..." + else: + send_buttons(data["response_url"], extension_gateerrors) + return '' + + +@app.route('/readouterrors', methods=['POST']) +def readouterrors(): + data = request.form.to_dict() + backend = data['text'].lower() + + if backend in utils.backends: + name = 'Gate errors for {}'.format(backend) + pool.submit(send_image, 'tmp/{}{}'.format(backend, extension_readouterrors), + name, data['channel_id']) + return "Wait a sec ..." + else: + send_buttons(data["response_url"], extension_readouterrors) + return '' + + +@app.route('/jobs', methods=['GET', 'POST']) +def jobs(): + data = request.form.to_dict() + backend = data['text'].lower() + return "kek" + if backend in utils.backends: + name = 'Pending jobs for {}'.format(backend) + pool.submit(send_image, 'tmp/{}{}'.format(backend, extension_jobs), + name, data['channel_id']) + return "Wait a sec ..." + else: + send_buttons(data["response_url"], extension_jobs) + return '' + + +@app.route('/full', methods=['POST']) +def full(): + data = request.form.to_dict() + backend = data['text'].lower() + + if backend in utils.backends: + name = 'Full statistics for {}'.format(backend) + pool.submit(send_image, 'tmp/{}{}'.format(backend, extension_full), + name, data['channel_id']) + return "Wait a sec ..." + else: + send_buttons(data["response_url"], extension_full) + return '' + + +### +# Helper Functions +### +def send_image(path, name, channel): + my_file = { + 'file': (path, open(path, 'rb'), 'png') + } + payload = { + "filename": '{}.png'.format(name), + 'token': slack_token, + "channels": [channel] + } + + response = requests.post("https://slack.com/api/files.upload", params=payload, files=my_file) + + +def send_buttons(response_url, extension): + payload = { + "text": "What backend you are interested in?", + "attachments": [ + { + "text": "Choose a backend :qiskit:", + "callback_id": 42, + "color": "#3AA3E3", + "attachment_type": "default", + "actions": [ + { + "name": "ibmqx4", + "text": "ibmqx4", + "type": "button", + "value": "ibmqx4{}".format(extension) + }, + { + "name": "ibmqx5", + "text": "ibmqx5", + "type": "button", + "value": "ibmqx5{}".format(extension) + }, + ] + } + ] + } + + headers = { + 'content-type': "application/json", + } + + response = requests.request("POST", response_url, data=json.dumps(payload), headers=headers) + print(response.text) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/res/qiskit-logo.png b/res/qiskit-logo.png new file mode 100644 index 0000000..575c927 Binary files /dev/null and b/res/qiskit-logo.png differ diff --git a/res/rqc.jpg b/res/rqc.jpg new file mode 100644 index 0000000..e7d5a02 Binary files /dev/null and b/res/rqc.jpg differ diff --git a/res/token_q.json b/res/token_q.json new file mode 100644 index 0000000..02918bc --- /dev/null +++ b/res/token_q.json @@ -0,0 +1 @@ +{"url": "https://quantumexperience.ng.bluemix.net/api", "APItoken": ""} diff --git a/res/token_slack.json b/res/token_slack.json new file mode 100644 index 0000000..cc028f4 --- /dev/null +++ b/res/token_slack.json @@ -0,0 +1 @@ +{"token": ""} diff --git a/tmp/ibmqx4_full.png b/tmp/ibmqx4_full.png new file mode 100644 index 0000000..0902799 Binary files /dev/null and b/tmp/ibmqx4_full.png differ diff --git a/tmp/ibmqx4_gateerrors_full.png b/tmp/ibmqx4_gateerrors_full.png new file mode 100644 index 0000000..e151e9d Binary files /dev/null and b/tmp/ibmqx4_gateerrors_full.png differ diff --git a/tmp/ibmqx4_gateerrors_part.png b/tmp/ibmqx4_gateerrors_part.png new file mode 100644 index 0000000..cee0584 Binary files /dev/null and b/tmp/ibmqx4_gateerrors_part.png differ diff --git a/tmp/ibmqx4_jobs_full.png b/tmp/ibmqx4_jobs_full.png new file mode 100644 index 0000000..31bc180 Binary files /dev/null and b/tmp/ibmqx4_jobs_full.png differ diff --git a/tmp/ibmqx4_jobs_part.png b/tmp/ibmqx4_jobs_part.png new file mode 100644 index 0000000..c55fbe2 Binary files /dev/null and b/tmp/ibmqx4_jobs_part.png differ diff --git a/tmp/ibmqx4_readouterrors_full.png b/tmp/ibmqx4_readouterrors_full.png new file mode 100644 index 0000000..b014f5b Binary files /dev/null and b/tmp/ibmqx4_readouterrors_full.png differ diff --git a/tmp/ibmqx4_readouterrors_part.png b/tmp/ibmqx4_readouterrors_part.png new file mode 100644 index 0000000..51dfa82 Binary files /dev/null and b/tmp/ibmqx4_readouterrors_part.png differ diff --git a/tmp/ibmqx5_full.png b/tmp/ibmqx5_full.png new file mode 100644 index 0000000..ab6e562 Binary files /dev/null and b/tmp/ibmqx5_full.png differ diff --git a/tmp/ibmqx5_gateerrors_full.png b/tmp/ibmqx5_gateerrors_full.png new file mode 100644 index 0000000..3657b9b Binary files /dev/null and b/tmp/ibmqx5_gateerrors_full.png differ diff --git a/tmp/ibmqx5_gateerrors_part.png b/tmp/ibmqx5_gateerrors_part.png new file mode 100644 index 0000000..51cc643 Binary files /dev/null and b/tmp/ibmqx5_gateerrors_part.png differ diff --git a/tmp/ibmqx5_jobs_full.png b/tmp/ibmqx5_jobs_full.png new file mode 100644 index 0000000..8d7d520 Binary files /dev/null and b/tmp/ibmqx5_jobs_full.png differ diff --git a/tmp/ibmqx5_jobs_part.png b/tmp/ibmqx5_jobs_part.png new file mode 100644 index 0000000..df8f66f Binary files /dev/null and b/tmp/ibmqx5_jobs_part.png differ diff --git a/tmp/ibmqx5_readouterrors_full.png b/tmp/ibmqx5_readouterrors_full.png new file mode 100644 index 0000000..da42974 Binary files /dev/null and b/tmp/ibmqx5_readouterrors_full.png differ diff --git a/tmp/ibmqx5_readouterrors_part.png b/tmp/ibmqx5_readouterrors_part.png new file mode 100644 index 0000000..afd318c Binary files /dev/null and b/tmp/ibmqx5_readouterrors_part.png differ diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..2c024af --- /dev/null +++ b/utils.py @@ -0,0 +1,408 @@ +from datetime import date, timedelta +from datetime import datetime as dt +from matplotlib.font_manager import findfont, FontProperties +from PIL import Image +from PIL import ImageFont +from PIL import ImageDraw +import numpy as np +import requests +import threading +import pickle +import time +import json +import math +import sys +import os +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt + + +PKL1 = 'data/real_data_1.pkl' +PKL2 = 'data/real_data_2.pkl' + +backends = ['ibmqx4', 'ibmqx5'] + + +def get_token(path): + with open(path) as jsn: + data = json.load(jsn) + return data['token'] + + +def load_data(): + data = None + try: + with open(PKL1, 'rb') as f: + data = pickle.load(f) + except (pickle.UnpicklingError, EOFError) as e: + with open(PKL2, 'rb') as f: + data = pickle.load(f) + return data + + +def plot_pending_jobs(backend): + data = load_data() + + times = sorted([x[0] for x in data]) + pending_jobs = [[y for y in x[1] if y['backend'] == backend][0]['pending_jobs'] + for x in sorted(data, key=lambda x: x[0])] + + ### + # Jobs Full. + plt.close() + plt.figure(figsize=(11, 5)) + plt.grid(True, zorder=5) + plt.fill_between(times, pending_jobs, color='brown') + plt.locator_params(axis='x', nbins=11) + # New xticks. + locs, labels = plt.xticks() + new_ticks = [dt.fromtimestamp(x).strftime('%H:%M') for x in locs] + plt.xticks(locs[1:-1], new_ticks[1:-1], rotation=0, fontsize=15) + plt.yticks(fontsize=15) + plt.ylim(0, math.ceil(max(pending_jobs)) + 1) + plt.title('IBMQ Backend: {},\nLocal time of bot: {}'.format(backend, + dt.fromtimestamp(time.time()).strftime('%Y, %b %d, %H:%M')), fontsize=15) + plt.xlabel('Time', fontsize=15) + plt.ylabel('# of pending jobs', fontsize=15) + plt.savefig('tmp/{}_jobs_full.png'.format(backend), bbox_inches='tight') + plt.close() + + img_jobs = Image.open('tmp/{}_jobs_full.png'.format(backend), 'r') + img_jobs_w, img_jobs_h = img_jobs.size + img_background = Image.new('RGBA', (img_jobs_w, img_jobs_h), + (255, 255, 255, 255)) + img_background_w, img_background_h = img_background.size + + ############# + # Load Logos. + img_qiskit = Image.open('res/qiskit-logo.png', 'r') + factor = 7 + img_qiskit = img_qiskit.resize((img_qiskit.size[0] // factor, img_qiskit.size[1] // factor)) + img_qiskit_w, img_qiskit_h = img_qiskit.size + + img_rqc = Image.open('res/rqc.jpg', 'r') + factor = 13 + img_rqc = img_rqc.resize((img_rqc.size[0] // factor, img_rqc.size[1] // factor)) + img_rqc_w, img_rqc_h = img_rqc.size + #### + + ######## + # Paste. + offset = (0, 0) + img_background.paste(img_jobs, offset) + + displacement_h = 8 + displacement_w = 10 + offset = (img_background_w - img_qiskit_w - displacement_w, displacement_h - 4) + img_background.paste(img_qiskit, offset) + + offset = (img_background_w - img_rqc_w - img_qiskit_w - displacement_w - 7, displacement_h) + img_background.paste(img_rqc, offset) + img_background.save('tmp/{}_jobs_full.png'.format(backend)) + img_background.close() + img_rqc.close() + img_jobs.close() + + ### + # Jobs Part. + plt.close() + plt.figure(figsize=(11, 5)) + plt.grid(True, zorder=5) + plt.fill_between(times, pending_jobs, color='brown') + plt.locator_params(axis='x', nbins=11) + # New xticks. + locs, labels = plt.xticks() + new_ticks = [dt.fromtimestamp(x).strftime('%H:%M') for x in locs] + plt.xticks(locs[1:-1], new_ticks[1:-1], rotation=0, fontsize=15) + plt.yticks(fontsize=15) + plt.ylim(0, math.ceil(max(pending_jobs)) + 1) + plt.title('Local time of bot: {}'.format(dt.fromtimestamp(time.time()).strftime('%Y, %b %d, %H:%M')), + fontsize=15) + plt.xlabel('Time', fontsize=15) + plt.ylabel('# of pending jobs', fontsize=15) + plt.savefig('tmp/{}_jobs_part.png'.format(backend), bbox_inches='tight') + plt.close() + return + + +def plot_readout_errors(backend, api): + try: + full_info = api.backend_calibration(backend=backend) + except requests.exceptions.ConnectionError as e: + print(e) + return + except Exception as e: + print(e) + return + + N_qubits = len(full_info['qubits']) + qubits = [full_info['qubits'][qub]['name'] for qub in range(N_qubits)] + readout_error = [full_info['qubits'][qub]['readoutError']['value'] for qub in range(N_qubits)] + readout_error = np.array([readout_error]) + + readout_error *= 100 + + last_update = full_info['lastUpdateDate'] + last_update = dt.strptime(last_update, "%Y-%m-%dT%H:%M:%S.000Z").timestamp() + last_update = dt.fromtimestamp(last_update).strftime('%Y, %b %d, %H:%M') + + ### + # Readout Errors Full. + plt.close() + plt.figure(figsize=(15, 15)) + plt.matshow(readout_error, cmap='Reds', fignum=1) + # Placing actual values in the matshow plot + for (i,), value in np.ndenumerate(readout_error[0]): + plt.text(i, 0, '{:0.2f}'.format(value), ha='center', va='center') + + # Formatting axes. + plt.xticks(np.arange(N_qubits), qubits) + plt.yticks([], []) + plt.autoscale(axis='both', tight=True) + + plt.title('IBMQ Backend: {}, Readout errors,\nLast calibration: {}\n'.format(backend, last_update), fontsize=15) + plt.margins(tight=True) + plt.savefig('tmp/{}_readouterrors_full.png'.format(backend), bbox_inches='tight') + plt.close() + + img_rerr = Image.open('tmp/{}_readouterrors_full.png'.format(backend), 'r') + img_rerr_w, img_rerr_h = img_rerr.size + img_background = Image.new('RGBA', (img_rerr_w, img_rerr_h), + (255, 255, 255, 255)) + img_background_w, img_background_h = img_background.size + + ############# + # Load Logos. + img_qiskit = Image.open('res/qiskit-logo.png', 'r') + factor = 7 + img_qiskit = img_qiskit.resize((img_qiskit.size[0] // factor, img_qiskit.size[1] // factor)) + img_qiskit_w, img_qiskit_h = img_qiskit.size + + img_rqc = Image.open('res/rqc.jpg', 'r') + factor = 14 + img_rqc = img_rqc.resize((img_rqc.size[0] // factor, img_rqc.size[1] // factor)) + img_rqc_w, img_rqc_h = img_rqc.size + #### + + ####### + # Paste. + offset = (0, 0) + img_background.paste(img_rerr, offset) + + displacement_h = 8 + displacement_w = 10 + offset = (img_background_w - img_qiskit_w - displacement_w, displacement_h - 4) + img_background.paste(img_qiskit, offset) + + offset = (img_background_w - img_rqc_w - img_qiskit_w - displacement_w - 7, displacement_h) + img_background.paste(img_rqc, offset) + + img_background.save('tmp/{}_readouterrors_full.png'.format(backend)) + img_background.close() + img_rqc.close() + img_rerr.close() + + ### + # Readout Errors Part. + plt.close() + plt.figure(figsize=(15, 15)) + plt.matshow(readout_error, cmap='Reds', fignum=1) + # Placing actual values in the matshow plot + for (i,), value in np.ndenumerate(readout_error[0]): + plt.text(i, 0, '{:0.2f}'.format(value), ha='center', va='center', fontsize=16) + + # Formatting axes. + plt.xticks(np.arange(N_qubits), qubits, fontsize=16) + plt.yticks([], []) + plt.autoscale(axis='both', tight=True) + + plt.title('IBMQ Backend: {}\nLast calibration: {}\n\nReadout errors\n'.format(backend, last_update), fontsize=20) + plt.margins(tight=True) + plt.savefig('tmp/{}_readouterrors_part.png'.format(backend), bbox_inches='tight') + plt.close() + return + + +def plot_gate_errors(backend, api): + try: + full_info = api.backend_calibration(backend=backend) + except requests.exceptions.ConnectionError as e: + print(e) + return + except Exception as e: + print(e) + return + + # Info. + N_qubits = len(full_info['qubits']) + qubits = [full_info['qubits'][qub]['name'] for qub in range(N_qubits)] + gate_error = [full_info['qubits'][qub]['gateError']['value'] for qub in range(N_qubits)] + gate_error = np.array([gate_error]) + + last_update = full_info['lastUpdateDate'] + last_update = dt.strptime(last_update, "%Y-%m-%dT%H:%M:%S.000Z").timestamp() + last_update = dt.fromtimestamp(last_update).strftime('%Y, %b %d, %H:%M') + + ##### + # Multiqubit error. + multi_qubit_gates = [full_info['multiQubitGates'][qub]['qubits'] for qub in range(N_qubits)] + multi_qubit_error = np.array([full_info['multiQubitGates'][qub]['gateError']['value'] for qub in range(N_qubits)]) + + multi_qubit_error *= 100 + gate_error *= 100 + + # creating gate error matrix. + error_matrix = np.zeros((N_qubits, N_qubits)) + + error_matrix[:, :] = None + for i in range(len(multi_qubit_gates)): + gate = multi_qubit_gates[i] + qub1, qub2 = gate[0], gate[1] + error_matrix[qub1][qub2] = multi_qubit_error[i] + error_matrix[qub2][qub1] = multi_qubit_error[i] + error_matrix[i][i] = gate_error[0][i] + + fontsize = None + if backend == 'ibmqx4': + fontsize = 13 + elif backend == 'ibmqx5': + fontsize = 8 + + ### + # Gate Errors Full. + plt.close() + plt.figure(figsize=(7, 7)) + plt.matshow(error_matrix, cmap='Reds', fignum=1) + plt.title('IBMQ Backend: {},\nQubit gate errors,\nLast calibration: {}'.format(backend, last_update), fontsize=15) + # Placing actual values in the matshow plot + for (i, j), value in np.ndenumerate(error_matrix): + if not np.isnan(value): + plt.text(j, i, '{:0.2f}'.format(value), ha='center', va='center', fontsize=fontsize) + # Formatting axes + plt.yticks(np.arange(N_qubits), qubits) + plt.xticks(np.arange(N_qubits), qubits) + plt.autoscale(axis='both', tight=True) + plt.savefig('tmp/{}_gateerrors_full.png'.format(backend), bbox_inches='tight') + plt.close() + + img_gerr = Image.open('tmp/{}_gateerrors_full.png'.format(backend), 'r') + img_gerr_w, img_gerr_h = img_gerr.size + img_background = Image.new('RGBA', (img_gerr_w, img_gerr_h), + (255, 255, 255, 255)) + img_background_w, img_background_h = img_background.size + + ############# + # Load Logos. + img_qiskit = Image.open('res/qiskit-logo.png', 'r') + factor = 7 + img_qiskit = img_qiskit.resize((img_qiskit.size[0] // factor, img_qiskit.size[1] // factor)) + img_qiskit_w, img_qiskit_h = img_qiskit.size + + img_rqc = Image.open('res/rqc.jpg', 'r') + factor = 14 + img_rqc = img_rqc.resize((img_rqc.size[0] // factor, img_rqc.size[1] // factor)) + img_rqc_w, img_rqc_h = img_rqc.size + #### + + ####### + # Paste. + offset = (0, 0) + img_background.paste(img_gerr, offset) + + displacement_h = 5 + displacement_w = 5 + offset = (displacement_w, img_rqc_h + displacement_h) + img_background.paste(img_qiskit, offset) + + offset = (displacement_w, displacement_h) + img_background.paste(img_rqc, offset) + img_background.save('tmp/{}_gateerrors_full.png'.format(backend)) + img_background.close() + img_rqc.close() + img_gerr.close() + ### + + ### + # Gate Errors Part. + plt.close() + plt.figure(figsize=(6, 6)) + plt.matshow(error_matrix, cmap='Reds', fignum=1) + plt.title('Qubit gate errors', fontsize=15) + # Placing actual values in the matshow plot + for (i, j), value in np.ndenumerate(error_matrix): + if not np.isnan(value): + plt.text(j, i, '{:0.2f}'.format(value), ha='center', va='center', fontsize=fontsize) + # Formatting axes + plt.yticks(np.arange(N_qubits), qubits) + plt.xticks(np.arange(N_qubits), qubits) + plt.autoscale(axis='both', tight=True) + plt.savefig('tmp/{}_gateerrors_part.png'.format(backend), bbox_inches='tight') + plt.close() + + +def plot_full(backend, api): + plot_gate_errors(backend, api) + plot_readout_errors(backend, api) + plot_pending_jobs(backend) + + img_gerr = Image.open('tmp/{}_gateerrors_part.png'.format(backend), 'r') + img_gerr_w, img_gerr_h = img_gerr.size + + img_rerr = Image.open('tmp/{}_readouterrors_part.png'.format(backend), 'r') + img_rerr_w, img_rerr_h = img_rerr.size + + img_jobs = Image.open('tmp/{}_jobs_part.png'.format(backend), 'r') + img_jobs_w, img_jobs_h = img_jobs.size + + img_background = Image.new('RGBA', (img_gerr_w + img_jobs_w, img_rerr_h + img_gerr_h), + (255, 255, 255, 255)) + img_background_w, img_background_h = img_background.size + + ############# + # Load Logos. + img_qiskit = Image.open('res/qiskit-logo.png', 'r') + factor = 4 + img_qiskit = img_qiskit.resize((img_qiskit.size[0] // factor, img_qiskit.size[1] // factor)) + img_qiskit_w, img_qiskit_h = img_qiskit.size + + img_rqc = Image.open('res/rqc.jpg', 'r') + factor = 7 + img_rqc = img_rqc.resize((img_rqc.size[0] // factor, img_rqc.size[1] // factor)) + img_rqc_w, img_rqc_h = img_rqc.size + #### + + ############## + # Paste Plots. + offset = ((img_background_w - img_rerr_w) // 2, 0) + img_background.paste(img_rerr, offset) + + offset = (0, img_rerr_h) + img_background.paste(img_gerr, offset) + + offset = (img_gerr_w, (img_rerr_h + img_background_h - img_jobs_h) // 2) + img_background.paste(img_jobs, offset) + #### + + ############## + # Paste Logos. + displacement_h = 8 + displacement_w = 10 + offset = (img_background_w - img_qiskit_w - displacement_w, displacement_h - 6) + img_background.paste(img_qiskit, offset) + + offset = (img_background_w - img_rqc_w - img_qiskit_w - displacement_w - 7, displacement_h) + img_background.paste(img_rqc, offset) + ### + + img_background.save('tmp/{}_full.png'.format(backend)) + + img_background.close() + img_rqc.close() + img_gerr.close() + img_rerr.close() + img_jobs.close() + + +if __name__ == '__main__': + raise RuntimeError