diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 63b1db744440..5a8958f19500 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -43,6 +43,8 @@ Added QuantumCircuit class. (#1172) - New methods in QuantumCircuit for common circuit metrics: `size()`, `depth()`, `width()`, `count_ops()`, `num_tensor_factors()` (#1285) +- Added `backend_monitor` and `backend_overview` Jupyter magics, + as well as `plot_coupling_map` (#1231) - Added a `Layout` object (#1313) - Added a BasicMapper pass (#1270) - New `plot_bloch_multivector()` to plot Bloch vectors from a tensored state diff --git a/qiskit/backends/aer/qasm_simulator_cpp b/qiskit/backends/aer/qasm_simulator_cpp new file mode 100755 index 000000000000..6976a3dea93a Binary files /dev/null and b/qiskit/backends/aer/qasm_simulator_cpp differ diff --git a/qiskit/tools/__init__.py b/qiskit/tools/__init__.py index 70db777102d0..86f93fcbb7c3 100644 --- a/qiskit/tools/__init__.py +++ b/qiskit/tools/__init__.py @@ -16,4 +16,4 @@ """ from .compiler import (compile, execute) -from .monitor import job_monitor +from .monitor.job_monitor import job_monitor diff --git a/qiskit/tools/jupyter/__init__.py b/qiskit/tools/jupyter/__init__.py index 015a6e8f1130..63919ece0508 100644 --- a/qiskit/tools/jupyter/__init__.py +++ b/qiskit/tools/jupyter/__init__.py @@ -9,10 +9,18 @@ """ from IPython import get_ipython # pylint: disable=import-error +from qiskit.tools.visualization._matplotlib import HAS_MATPLOTLIB from .jupyter_magics import (ProgressBarMagic, StatusMagic) -from .progressbar import HTMLProgressBar, TextProgressBar +from .progressbar import HTMLProgressBar + +if HAS_MATPLOTLIB: + from .backend_overview import BackendOverview + from .backend_monitor import BackendMonitor _IP = get_ipython() if _IP is not None: _IP.register_magics(ProgressBarMagic) _IP.register_magics(StatusMagic) + if HAS_MATPLOTLIB: + _IP.register_magics(BackendOverview) + _IP.register_magics(BackendMonitor) diff --git a/qiskit/tools/jupyter/backend_monitor.py b/qiskit/tools/jupyter/backend_monitor.py new file mode 100644 index 000000000000..188bcb7d01dd --- /dev/null +++ b/qiskit/tools/jupyter/backend_monitor.py @@ -0,0 +1,569 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""A module for monitoring backends.""" + +import math +import datetime +from IPython.display import display # pylint: disable=import-error +from IPython.core.magic import (line_magic, # pylint: disable=import-error + Magics, magics_class) +import ipywidgets as widgets # pylint: disable=import-error +import matplotlib.pyplot as plt +import matplotlib.colors +import matplotlib as mpl +from matplotlib import cm +from matplotlib.patches import Circle +from qiskit.backends.ibmq import IBMQ +from qiskit.qiskiterror import QISKitError +from qiskit.backends.ibmq.ibmqbackend import IBMQBackend +from qiskit.tools.visualization._gate_map import plot_gate_map + + +@magics_class +class BackendMonitor(Magics): + """A class of status magic functions. + """ + @line_magic + def qiskit_backend_monitor(self, line='', cell=None): # pylint: disable=W0613 + """A Jupyter magic function to monitor backends. + """ + backend = self.shell.user_ns[line] + if not isinstance(backend, IBMQBackend): + raise QISKitError('Input variable is not of type IBMQBackend.') + title_style = "style='color:#ffffff;background-color:#000000;padding-top: 1%;" + title_style += "padding-bottom: 1%;padding-left: 1%; margin-top: 0px'" + title_html = "

{name}

".format( + style=title_style, name=backend.name()) + + details = [config_tab(backend)] + + tab_contents = ['Configuration'] + + if not backend.configuration().simulator: + tab_contents.extend(['Qubit Properties', 'Multi-Qubit Gates', + 'Error Map', 'Job History']) + details.extend([qubits_tab(backend), gates_tab(backend), + detailed_map(backend), job_history(backend)]) + + tabs = widgets.Tab(layout=widgets.Layout(overflow_y='scroll')) + tabs.children = details + for i in range(len(details)): + tabs.set_title(i, tab_contents[i]) + + title_widget = widgets.HTML(value=title_html, + layout=widgets.Layout(margin='0px 0px 0px 0px')) + + backend_monitor = widgets.VBox([title_widget, tabs], + layout=widgets.Layout(border='4px solid #000000', + max_height='650px', min_height='650px', + overflow_y='hidden')) + + display(backend_monitor) + + +def config_tab(backend): + """The backend configuration widget. + + Args: + backend (IBMQbackend): The backend. + + Returns: + grid: A GridBox widget. + """ + status = backend.status().to_dict() + config = backend.configuration().to_dict() + + config_dict = {**status, **config} + + upper_list = ['n_qubits', 'operational', + 'status_msg', 'pending_jobs', + 'basis_gates', 'local', 'simulator'] + + lower_list = list(set(config_dict.keys()).difference(upper_list)) + # Remove gates because they are in a different tab + lower_list.remove('gates') + upper_str = "" + upper_str += """""" + + footer = "
" + + # Upper HBox widget data + + upper_str += "PropertyValue" + for key in upper_list: + upper_str += "%s%s" % ( + key, config_dict[key]) + upper_str += footer + + upper_table = widgets.HTML( + value=upper_str, layout=widgets.Layout(width='100%', grid_area='left')) + + image_widget = widgets.Output( + layout=widgets.Layout(display='flex-inline', grid_area='right', + padding='10px 10px 10px 10px', + width='auto', max_height='300px', + align_items='center')) + + if not config['simulator']: + with image_widget: + gate_map = plot_gate_map(backend) + display(gate_map) + plt.close(gate_map) + + lower_str = "" + lower_str += """""" + lower_str += "" + for key in lower_list: + if key != 'name': + lower_str += "" % ( + key, config_dict[key]) + lower_str += footer + + lower_table = widgets.HTML(value=lower_str, + layout=widgets.Layout( + width='auto', + grid_area='bottom')) + + grid = widgets.GridBox(children=[upper_table, image_widget, lower_table], + layout=widgets.Layout( + grid_template_rows='auto auto', + grid_template_columns='25% 25% 25% 25%', + grid_template_areas=''' + "left right right right" + "bottom bottom bottom bottom" + ''', + grid_gap='0px 0px')) + + return grid + + +def qubits_tab(backend): + """The qubits properties widget + + Args: + backend (IBMQbackend): The backend. + + Returns: + VBox: A VBox widget. + """ + props = backend.properties().to_dict() + + header_html = "
{key}: {value}
" + header_html = header_html.format(key='last_update_date', + value=props['last_update_date']) + update_date_widget = widgets.HTML(value=header_html) + + qubit_html = "
%s%s
" + qubit_html += """""" + + qubit_html += "" + qubit_html += "" + qubit_html += "" + qubit_footer = "
FrequencyT1T2U1 gate errorU2 gate errorU3 gate errorReadout error
" + + for qub in range(len(props['qubits'])): + name = 'Q%s' % qub + qubit_data = props['qubits'][qub] + gate_data = props['gates'][3*qub:3*qub+3] + t1_info = qubit_data[0] + t2_info = qubit_data[1] + freq_info = qubit_data[2] + readout_info = qubit_data[3] + + freq = str(round(freq_info['value'], 5))+' '+freq_info['unit'] + T1 = str(round(t1_info['value'], # pylint: disable=invalid-name + 5))+' ' + t1_info['unit'] + T2 = str(round(t2_info['value'], # pylint: disable=invalid-name + 5))+' ' + t2_info['unit'] + # pylint: disable=invalid-name + U1 = str(round(gate_data[0]['parameters'][0]['value'], 5)) + # pylint: disable=invalid-name + U2 = str(round(gate_data[1]['parameters'][0]['value'], 5)) + # pylint: disable=invalid-name + U3 = str(round(gate_data[2]['parameters'][0]['value'], 5)) + + readout_error = round(readout_info['value'], 5) + qubit_html += "%s%s" + qubit_html += "%s%s%s%s%s%s" + qubit_html = qubit_html % (name, freq, T1, T2, U1, U2, U3, readout_error) + qubit_html += qubit_footer + + qubit_widget = widgets.HTML(value=qubit_html) + + out = widgets.VBox([update_date_widget, + qubit_widget]) + + return out + + +def gates_tab(backend): + """The multiple qubit gate error widget. + + Args: + backend (IBMQbackend): The backend. + + Returns: + VBox: A VBox widget. + """ + config = backend.configuration().to_dict() + props = backend.properties().to_dict() + + multi_qubit_gates = props['gates'][3*config['n_qubits']:] + + header_html = "
{key}: {value}
" + header_html = header_html.format(key='last_update_date', + value=props['last_update_date']) + + update_date_widget = widgets.HTML(value=header_html, + layout=widgets.Layout(grid_area='top')) + + gate_html = "" + gate_html += """""" + + gate_html += "" + gate_footer = "
TypeGate error
" + + # Split gates into two columns + left_num = math.ceil(len(multi_qubit_gates)/3) + mid_num = math.ceil((len(multi_qubit_gates)-left_num)/2) + + left_table = gate_html + + for qub in range(left_num): + gate = multi_qubit_gates[qub] + name = gate['name'] + ttype = gate['gate'] + error = round(gate['parameters'][0]['value'], 5) + + left_table += "%s" + left_table += "%s%s" + left_table = left_table % (name, ttype, error) + left_table += gate_footer + + middle_table = gate_html + + for qub in range(left_num, left_num+mid_num): + gate = multi_qubit_gates[qub] + name = gate['name'] + ttype = gate['gate'] + error = round(gate['parameters'][0]['value'], 5) + + middle_table += "%s" + middle_table += "%s%s" + middle_table = middle_table % (name, ttype, error) + middle_table += gate_footer + + right_table = gate_html + + for qub in range(left_num+mid_num, len(multi_qubit_gates)): + gate = multi_qubit_gates[qub] + name = gate['name'] + ttype = gate['gate'] + error = round(gate['parameters'][0]['value'], 5) + + right_table += "%s" + right_table += "%s%s" + right_table = right_table % (name, ttype, error) + right_table += gate_footer + + left_table_widget = widgets.HTML(value=left_table, + layout=widgets.Layout(grid_area='left')) + middle_table_widget = widgets.HTML(value=middle_table, + layout=widgets.Layout(grid_area='middle')) + right_table_widget = widgets.HTML(value=right_table, + layout=widgets.Layout(grid_area='right')) + + grid = widgets.GridBox(children=[update_date_widget, + left_table_widget, + middle_table_widget, + right_table_widget], + layout=widgets.Layout( + grid_template_rows='auto auto', + grid_template_columns='33% 33% 33%', + grid_template_areas=''' + "top top top" + "left middle right" + ''', + grid_gap='0px 0px')) + + return grid + + +def detailed_map(backend): + """Widget for displaying detailed noise map. + + Args: + backend (IBMQbackend): The backend. + + Returns: + GridBox: Widget holding noise map images. + """ + props = backend.properties().to_dict() + config = backend.configuration().to_dict() + single_gate_errors = [q['parameters'][0]['value'] + for q in props['gates'][2:3*config['n_qubits']:3]] + single_norm = matplotlib.colors.Normalize( + vmin=min(single_gate_errors), vmax=max(single_gate_errors)) + q_colors = [cm.viridis(single_norm(err)) for err in single_gate_errors] + + cmap = config['coupling_map'] + + cx_errors = [] + for line in cmap: + for item in props['gates'][3*config['n_qubits']:]: + if item['qubits'] == line: + cx_errors.append(item['parameters'][0]['value']) + break + else: + continue + + cx_norm = matplotlib.colors.Normalize( + vmin=min(cx_errors), vmax=max(cx_errors)) + line_colors = [cm.viridis(cx_norm(err)) for err in cx_errors] + + single_widget = widgets.Output(layout=widgets.Layout(display='flex-inline', grid_area='left', + align_items='center')) + + cmap_widget = widgets.Output(layout=widgets.Layout(display='flex-inline', grid_area='top', + width='auto', height='auto', + align_items='center')) + + cx_widget = widgets.Output(layout=widgets.Layout(display='flex-inline', grid_area='right', + align_items='center')) + + tick_locator = mpl.ticker.MaxNLocator(nbins=5) + with cmap_widget: + noise_map = plot_gate_map(backend, qubit_color=q_colors, + line_color=line_colors, + qubit_size=28, + plot_directed=True) + width, height = noise_map.get_size_inches() + + noise_map.set_size_inches(1.25*width, 1.25*height) + + display(noise_map) + plt.close(noise_map) + + with single_widget: + cbl_fig = plt.figure(figsize=(3, 1)) + ax1 = cbl_fig.add_axes([0.05, 0.80, 0.9, 0.15]) + single_cb = mpl.colorbar.ColorbarBase(ax1, cmap=cm.viridis, + norm=single_norm, + orientation='horizontal') + single_cb.locator = tick_locator + single_cb.update_ticks() + ax1.set_title('Single-qubit U3 error rate') + display(cbl_fig) + plt.close(cbl_fig) + + with cx_widget: + cx_fig = plt.figure(figsize=(3, 1)) + ax2 = cx_fig.add_axes([0.05, 0.80, 0.9, 0.15]) + cx_cb = mpl.colorbar.ColorbarBase(ax2, cmap=cm.viridis, + norm=cx_norm, + orientation='horizontal') + cx_cb.locator = tick_locator + cx_cb.update_ticks() + ax2.set_title('CNOT error rate') + display(cx_fig) + plt.close(cx_fig) + + out_box = widgets.GridBox([single_widget, cmap_widget, cx_widget], + layout=widgets.Layout( + grid_template_rows='auto auto', + grid_template_columns='33% 33% 33%', + grid_template_areas=''' + "top top top" + "left . right" + ''', + grid_gap='0px 0px')) + return out_box + + +def job_history(backend): + """Widget for displaying job history + + Args: + backend (IBMQbackend): The backend. + + Returns: + Tab: A tab widget for history images. + """ + year = widgets.Output(layout=widgets.Layout(display='flex-inline', + align_items='center', + min_height='400px')) + + month = widgets.Output(layout=widgets.Layout(display='flex-inline', + align_items='center', + min_height='400px')) + + week = widgets.Output(layout=widgets.Layout(display='flex-inline', + align_items='center', + min_height='400px')) + + tabs = widgets.Tab(layout=widgets.Layout(max_height='620px')) + tabs.children = [year, month, week] + tabs.set_title(0, 'Year') + tabs.set_title(1, 'Month') + tabs.set_title(2, 'Week') + tabs.selected_index = 1 + + _build_job_history(tabs, backend) + return tabs + + +def _build_job_history(tabs, backend): + + backends = IBMQ.backends(backend.name()) + past_year_date = datetime.datetime.now() - datetime.timedelta(days=365) + date_filter = {'creationDate': {'gt': past_year_date.isoformat()}} + jobs = [] + for back in backends: + jobs.extend(back.jobs(limit=None, db_filter=date_filter)) + + with tabs.children[1]: + month_plot = plot_job_history(jobs, interval='month') + display(month_plot) + plt.close(month_plot) + + with tabs.children[0]: + year_plot = plot_job_history(jobs, interval='year') + display(year_plot) + plt.close(year_plot) + + with tabs.children[2]: + week_plot = plot_job_history(jobs, interval='week') + display(week_plot) + plt.close(week_plot) + + +def plot_job_history(jobs, interval='year'): + """Plots the job history of the user from the given list of jobs. + + Args: + jobs (list): A list of jobs with type IBMQjob. + interval (str): Interval over which to examine. + + Returns: + fig: A Matplotlib figure instance. + """ + def get_date(job): + """Returns a datetime object from a IBMQJob instance. + + Args: + job (IBMQJob): A job. + + Returns: + dt: A datetime object. + """ + return datetime.datetime.strptime(job.creation_date(), + '%Y-%m-%dT%H:%M:%S.%fZ') + + current_time = datetime.datetime.now() + + if interval == 'year': + bins = [(current_time - datetime.timedelta(days=k*365/12)) + for k in range(12)] + elif interval == 'month': + bins = [(current_time - datetime.timedelta(days=k)) for k in range(30)] + elif interval == 'week': + bins = [(current_time - datetime.timedelta(days=k)) for k in range(7)] + + binned_jobs = [0]*len(bins) + + if interval == 'year': + for job in jobs: + for ind, dat in enumerate(bins): + date = get_date(job) + if date.month == dat.month: + binned_jobs[ind] += 1 + break + else: + continue + else: + for job in jobs: + for ind, dat in enumerate(bins): + date = get_date(job) + if date.day == dat.day and date.month == dat.month: + binned_jobs[ind] += 1 + break + else: + continue + + nz_bins = [] + nz_idx = [] + for ind, val in enumerate(binned_jobs): + if val != 0: + nz_idx.append(ind) + nz_bins.append(val) + + total_jobs = sum(binned_jobs) + + colors = ['#003f5c', '#ffa600', '#374c80', '#ff764a', + '#7a5195', '#ef5675', '#bc5090'] + + if interval == 'year': + labels = ['{}-{}'.format(str(bins[b].year)[2:], bins[b].month) for b in nz_idx] + else: + labels = ['{}-{}'.format(bins[b].month, bins[b].day) for b in nz_idx] + fig, ax = plt.subplots(1, 1, figsize=(5, 5)) # pylint: disable=invalid-name + ax.pie(nz_bins[::-1], labels=labels, colors=colors, textprops={'fontsize': 14}, + rotatelabels=True, counterclock=False) + ax.add_artist(Circle((0, 0), 0.7, color='white', zorder=1)) + ax.text(0, 0, total_jobs, horizontalalignment='center', + verticalalignment='center', fontsize=26) + fig.tight_layout() + return fig diff --git a/qiskit/tools/jupyter/backend_overview.py b/qiskit/tools/jupyter/backend_overview.py new file mode 100644 index 000000000000..b2f7f098dce8 --- /dev/null +++ b/qiskit/tools/jupyter/backend_overview.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""A module for monitoring backends.""" + +import time +import threading +import types +from IPython.display import display # pylint: disable=import-error +from IPython.core.magic import line_magic, Magics, magics_class # pylint: disable=import-error +from IPython.core import magic_arguments # pylint: disable=import-error +import ipywidgets as widgets # pylint: disable=import-error +import matplotlib.pyplot as plt +from qiskit.tools.monitor.backend_overview import get_unique_backends +from qiskit.tools.visualization._gate_map import plot_gate_map + + +@magics_class +class BackendOverview(Magics): + """A class of status magic functions. + """ + @line_magic + @magic_arguments.magic_arguments() + @magic_arguments.argument( + '-i', + '--interval', + type=float, + default=60, + help='Interval for status check.' + ) + def qiskit_backend_overview(self, line='', cell=None): # pylint: disable=W0613 + """A Jupyter magic function to monitor backends. + """ + args = magic_arguments.parse_argstring( + self.qiskit_backend_overview, line) + + unique_hardware_backends = get_unique_backends() + _value = "

Backend Overview

" + backend_title = widgets.HTML(value=_value, + layout=widgets.Layout(margin='0px 0px 0px 0px')) + + build_back_widgets = [backend_widget(b) + for b in unique_hardware_backends] + + _backends = [] + # Sort backends by operational or not + oper_ord_backends = [] + for n, back in enumerate(unique_hardware_backends): + if back.status().operational: + oper_ord_backends = [build_back_widgets[n]] + oper_ord_backends + _backends = [back] + _backends + else: + oper_ord_backends = oper_ord_backends + [build_back_widgets[n]] + _backends = _backends + [back] + + qubit_label = widgets.Label(value='Num. Qubits') + pend_label = widgets.Label(value='Pending Jobs') + least_label = widgets.Label(value='Least Busy') + oper_label = widgets.Label( + value='Operational', layout=widgets.Layout(margin='5px 0px 0px 0px')) + t1_label = widgets.Label( + value='Avg. T1', layout=widgets.Layout(margin='10px 0px 0px 0px')) + t2_label = widgets.Label( + value='Avg. T2', layout=widgets.Layout(margin='10px 0px 0px 0px')) + + labels_widget = widgets.VBox([qubit_label, pend_label, least_label, + oper_label, t1_label, t2_label], + layout=widgets.Layout(margin='295px 0px 0px 0px', + min_width='100px')) + + backend_grid = GridBox_with_thread(children=oper_ord_backends, + layout=widgets.Layout( + grid_template_columns='250px ' * + len(unique_hardware_backends), + grid_template_rows='auto', + grid_gap='0px 25px')) + + backend_grid._backends = _backends # pylint: disable=W0201 + backend_grid._update = types.MethodType( # pylint: disable=W0201 + update_backend_info, backend_grid) + + backend_grid._thread = threading.Thread( # pylint: disable=W0201 + target=backend_grid._update, args=(args.interval,)) + backend_grid._thread.start() + + back_box = widgets.HBox([labels_widget, backend_grid]) + + back_monitor = widgets.VBox([backend_title, back_box]) + display(back_monitor) + + +class GridBox_with_thread(widgets.GridBox): # pylint: disable=invalid-name + """A GridBox that will close an attached thread + """ + def __del__(self): + """Object disposal""" + if hasattr(self, '_thread'): + try: + self._thread.do_run = False + self._thread.join() + except Exception: # pylint: disable=W0703 + pass + self.close() + + +def backend_widget(backend): + """Creates a backend widget. + """ + config = backend.configuration().to_dict() + props = backend.properties().to_dict() + + name = widgets.HTML(value="

{name}

".format(name=backend.name()), + layout=widgets.Layout()) + + n_qubits = config['n_qubits'] + + qubit_count = widgets.HTML(value="
{qubits}
".format(qubits=n_qubits), + layout=widgets.Layout(justify_content='center')) + + cmap = widgets.Output(layout=widgets.Layout(min_width='250px', max_width='250px', + max_height='250px', + min_height='250px', + justify_content='center', + align_items='center', + margin='0px 0px 0px 0px')) + + with cmap: + _cmap_fig = plot_gate_map(backend, + plot_directed=False, + label_qubits=False) + if _cmap_fig is not None: + display(_cmap_fig) + # Prevents plot from showing up twice. + plt.close(_cmap_fig) + + pending = generate_jobs_pending_widget() + + is_oper = widgets.HTML(value="
", + layout=widgets.Layout(justify_content='center')) + + least_busy = widgets.HTML(value="
", + layout=widgets.Layout(justify_content='center')) + + t1_units = props['qubits'][0][0]['unit'] + avg_t1 = round(sum([q[0]['value'] for q in props['qubits']])/n_qubits, 1) + t1_widget = widgets.HTML(value="
{t1} {units}
".format(t1=avg_t1, units=t1_units), + layout=widgets.Layout()) + + t2_units = props['qubits'][0][1]['unit'] + avg_t2 = round(sum([q[1]['value'] for q in props['qubits']])/n_qubits, 1) + t2_widget = widgets.HTML(value="
{t2} {units}
".format(t2=avg_t2, units=t2_units), + layout=widgets.Layout()) + + out = widgets.VBox([name, cmap, qubit_count, pending, + least_busy, is_oper, t1_widget, t2_widget], + layout=widgets.Layout(display='inline-flex', + flex_flow='column', + align_items='center')) + + out._is_alive = True + return out + + +def update_backend_info(self, interval=60): + """Updates the monitor info + Called from another thread. + """ + my_thread = threading.currentThread() + current_interval = 0 + started = False + all_dead = False + stati = [None]*len(self._backends) + while getattr(my_thread, "do_run", True) and not all_dead: + if current_interval == interval or started is False: + for ind, back in enumerate(self._backends): + _value = self.children[ind].children[2].value + _head = _value.split('')[0] + try: + _status = back.status() + stati[ind] = _status + except Exception: # pylint: disable=W0703 + self.children[ind].children[2].value = _value.replace( + _head, "
") + self.children[ind]._is_alive = False + else: + self.children[ind]._is_alive = True + self.children[ind].children[2].value = _value.replace( + _head, "
") + + idx = list(range(len(self._backends))) + pending = [s.pending_jobs for s in stati] + _, least_idx = zip(*sorted(zip(pending, idx))) + + # Make sure least pending is operational + for ind in least_idx: + if stati[ind].operational: + least_pending_idx = ind + break + + for var in idx: + if var == least_pending_idx: + self.children[var].children[4].value = "
True
" + else: + self.children[var].children[4].value = "
False
" + + self.children[var].children[3].children[1].value = pending[var] + self.children[var].children[3].children[1].max = max( + self.children[var].children[3].children[1].max, pending[var]+10) + if stati[var].operational: + self.children[var].children[5].value = "
True
" + else: + self.children[var].children[5].value = "
False
" + + started = True + current_interval = 0 + time.sleep(1) + all_dead = not any([wid._is_alive for wid in self.children]) + current_interval += 1 + + +def generate_jobs_pending_widget(): + """Generates a jobs_pending progress bar widget. + """ + pbar = widgets.IntProgress( + value=0, + min=0, + max=50, + description='', + orientation='horizontal', layout=widgets.Layout(max_width='180px')) + pbar.style.bar_color = '#71cddd' + + pbar_current = widgets.Label( + value=str(pbar.value), layout=widgets.Layout(min_width='auto')) + pbar_max = widgets.Label( + value=str(pbar.max), layout=widgets.Layout(min_width='auto')) + + def _on_max_change(change): + pbar_max.value = str(change['new']) + + def _on_val_change(change): + pbar_current.value = str(change['new']) + + pbar.observe(_on_max_change, names='max') + pbar.observe(_on_val_change, names='value') + + jobs_widget = widgets.HBox([pbar_current, pbar, pbar_max], + layout=widgets.Layout(max_width='250px', + min_width='250px', + justify_content='center')) + + return jobs_widget diff --git a/qiskit/tools/jupyter/jupyter_magics.py b/qiskit/tools/jupyter/jupyter_magics.py index 61658623263c..13322952646f 100644 --- a/qiskit/tools/jupyter/jupyter_magics.py +++ b/qiskit/tools/jupyter/jupyter_magics.py @@ -14,7 +14,8 @@ from IPython.core.magic import cell_magic, Magics, magics_class # pylint: disable=import-error import ipywidgets as widgets # pylint: disable=import-error import qiskit -from .progressbar import HTMLProgressBar, TextProgressBar +from qiskit.transpiler.progressbar import TextProgressBar +from .progressbar import HTMLProgressBar def _html_checker(job_var, interval, status, header): diff --git a/qiskit/tools/monitor/__init__.py b/qiskit/tools/monitor/__init__.py new file mode 100644 index 000000000000..cb0f88081af1 --- /dev/null +++ b/qiskit/tools/monitor/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# pylint: disable=redefined-builtin + +"""A module for monitoring jobs, backends, etc. +""" + +from .job_monitor import job_monitor +from .backend_overview import backend_monitor, backend_overview diff --git a/qiskit/tools/monitor/backend_overview.py b/qiskit/tools/monitor/backend_overview.py new file mode 100644 index 000000000000..9a4dd116f894 --- /dev/null +++ b/qiskit/tools/monitor/backend_overview.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" A module for viewing the details of all available devices. +""" + +import math +from qiskit.qiskiterror import QISKitError +from qiskit.backends.ibmq import IBMQ +from qiskit.backends.ibmq.ibmqbackend import IBMQBackend + + +def get_unique_backends(): + """Gets the unique backends that are available. + + Returns: + list: Unique available backends. + + Raises: + QISKitError: No backends available. + """ + backends = IBMQ.backends() + unique_hardware_backends = [] + unique_names = [] + for back in backends: + if back.name() not in unique_names and not back.configuration().simulator: + unique_hardware_backends.append(back) + unique_names.append(back.name()) + if not unique_hardware_backends: + raise QISKitError('No backends available.') + return unique_hardware_backends + + +def backend_monitor(backend): + """Monitor a single IBMQ backend. + + Args: + backend (IBMQBackend): Backend to monitor. + Raises: + QISKitError: Input is not a IBMQ backend. + """ + if not isinstance(backend, IBMQBackend): + raise QISKitError('Input variable is not of type IBMQBackend.') + config = backend.configuration().to_dict() + status = backend.status().to_dict() + config_dict = {**status, **config} + if not config['simulator']: + props = backend.properties().to_dict() + + print(backend.name()) + print('='*len(backend.name())) + print('Configuration') + print('-'*13) + offset = ' ' + + upper_list = ['n_qubits', 'operational', + 'status_msg', 'pending_jobs', + 'basis_gates', 'local', 'simulator'] + + lower_list = list(set(config_dict.keys()).difference(upper_list)) + # Remove gates because they are in a different tab + lower_list.remove('gates') + for item in upper_list+lower_list: + print(offset+item+':', config_dict[item]) + + # Stop here if simulator + if config['simulator']: + return + + print() + qubit_header = 'Qubits [Name / Freq / T1 / T2 / U1 err / U2 err / U3 err / Readout err]' + print(qubit_header) + print('-'*len(qubit_header)) + + sep = ' / ' + for qub in range(len(props['qubits'])): + name = 'Q%s' % qub + qubit_data = props['qubits'][qub] + gate_data = props['gates'][3*qub:3*qub+3] + t1_info = qubit_data[0] + t2_info = qubit_data[1] + freq_info = qubit_data[2] + readout_info = qubit_data[3] + + freq = str(round(freq_info['value'], 5))+' '+freq_info['unit'] + T1 = str(round(t1_info['value'], # pylint: disable=invalid-name + 5))+' ' + t1_info['unit'] + T2 = str(round(t2_info['value'], # pylint: disable=invalid-name + 5))+' ' + t2_info['unit'] + # pylint: disable=invalid-name + U1 = str(round(gate_data[0]['parameters'][0]['value'], 5)) + # pylint: disable=invalid-name + U2 = str(round(gate_data[1]['parameters'][0]['value'], 5)) + # pylint: disable=invalid-name + U3 = str(round(gate_data[2]['parameters'][0]['value'], 5)) + + readout_error = str(round(readout_info['value'], 5)) + + qstr = sep.join([name, freq, T1, T2, U1, U2, U3, readout_error]) + print(offset+qstr) + + print() + multi_qubit_gates = props['gates'][3*config['n_qubits']:] + multi_header = 'Multi-Qubit Gates [Name / Type / Gate Error]' + print(multi_header) + print('-'*len(multi_header)) + + for gate in multi_qubit_gates: + name = gate['name'] + ttype = gate['gate'] + error = str(round(gate['parameters'][0]['value'], 5)) + mstr = sep.join([name, ttype, error]) + print(offset+mstr) + + +def backend_overview(): + """Gives overview information on all the IBMQ + backends that are available. + """ + unique_hardware_backends = get_unique_backends() + _backends = [] + # Sort backends by operational or not + for idx, back in enumerate(unique_hardware_backends): + if back.status().operational: + _backends = [back] + _backends + else: + _backends = _backends + [back] + + stati = [back.status() for back in _backends] + idx = list(range(len(_backends))) + pending = [s.pending_jobs for s in stati] + _, least_idx = zip(*sorted(zip(pending, idx))) + + # Make sure least pending is operational + for ind in least_idx: + if stati[ind].operational: + least_pending_idx = ind + break + + num_rows = math.ceil(len(_backends)/3) + + count = 0 + num_backends = len(_backends) + for _ in range(num_rows): + max_len = 0 + str_list = ['']*8 + for idx in range(3): + offset = ' ' * 10 if idx else '' + config = _backends[count].configuration().to_dict() + props = _backends[count].properties().to_dict() + n_qubits = config['n_qubits'] + str_list[0] += (' '*(max_len-len(str_list[0]))+offset) + str_list[0] += _backends[count].name() + + str_list[1] += (' '*(max_len-len(str_list[1]))+offset) + str_list[1] += '-'*len(_backends[count].name()) + + str_list[2] += (' '*(max_len-len(str_list[2]))+offset) + str_list[2] += 'Num. Qubits: %s' % config['n_qubits'] + + str_list[3] += (' '*(max_len-len(str_list[3]))+offset) + str_list[3] += 'Pending Jobs: %s' % stati[count].pending_jobs + + str_list[4] += (' '*(max_len-len(str_list[4]))+offset) + str_list[4] += 'Least busy: %s' % (True if count == least_pending_idx else False) + + str_list[5] += (' '*(max_len-len(str_list[5]))+offset) + str_list[5] += 'Operational: %s' % stati[count].operational + + str_list[6] += (' '*(max_len-len(str_list[6]))+offset) + str_list[6] += 'Avg. T1: %s' % round(sum([q[0]['value'] + for q in props['qubits']])/n_qubits, 1) + str_list[7] += (' '*(max_len-len(str_list[7]))+offset) + str_list[7] += 'Avg. T2: %s' % round(sum([q[1]['value'] + for q in props['qubits']])/n_qubits, 1) + count += 1 + if count == num_backends: + break + max_len = max([len(s) for s in str_list]) + + print("\n".join(str_list)) + print('\n'*2) diff --git a/qiskit/tools/monitor.py b/qiskit/tools/monitor/job_monitor.py similarity index 100% rename from qiskit/tools/monitor.py rename to qiskit/tools/monitor/job_monitor.py diff --git a/qiskit/tools/visualization/__init__.py b/qiskit/tools/visualization/__init__.py index a1460b3167ee..480f0415c271 100644 --- a/qiskit/tools/visualization/__init__.py +++ b/qiskit/tools/visualization/__init__.py @@ -34,3 +34,6 @@ iplot_state_hinton, iplot_histogram, iplot_state_paulivec) + +if HAS_MATPLOTLIB: + from ._gate_map import plot_gate_map diff --git a/qiskit/tools/visualization/_gate_map.py b/qiskit/tools/visualization/_gate_map.py new file mode 100644 index 000000000000..f70da07debb5 --- /dev/null +++ b/qiskit/tools/visualization/_gate_map.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""A module for visualizing device coupling maps""" + +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from qiskit.qiskiterror import QISKitError + + +class _GraphDist(): + """Transform the circles properly for non-square axes. + """ + def __init__(self, size, ax, x=True): + self.size = size + self.ax = ax # pylint: disable=invalid-name + self.x = x + + @property + def dist_real(self): + """Compute distance. + """ + x0, y0 = self.ax.transAxes.transform( # pylint: disable=invalid-name + (0, 0)) + x1, y1 = self.ax.transAxes.transform( # pylint: disable=invalid-name + (1, 1)) + value = x1 - x0 if self.x else y1 - y0 + return value + + @property + def dist_abs(self): + """Distance abs + """ + bounds = self.ax.get_xlim() if self.x else self.ax.get_ylim() + return bounds[0] - bounds[1] + + @property + def value(self): + """Return value. + """ + return (self.size / self.dist_real) * self.dist_abs + + def __mul__(self, obj): + return self.value * obj + + +def plot_gate_map(backend, figsize=None, + plot_directed=False, + label_qubits=True, + qubit_size=24, + line_width=4, + font_size=12, + qubit_color=None, + line_color=None, + font_color='w'): + """Plots the gate map of a device. + + Args: + backend (BaseBackend): A backend instance, + figsize (tuple): Output figure size (wxh) in inches. + plot_directed (bool): Plot directed coupling map. + label_qubits (bool): Label the qubits. + qubit_size (float): Size of qubit marker. + line_width (float): Width of lines. + font_size (int): Font size of qubit labels. + qubit_color (list): A list of colors for the qubits + line_color (list): A list of colors for each line from coupling_map. + font_color (str): The font color for the qubit labels. + + Returns: + Figure: A Matplotlib figure instance. + + Raises: + QISKitError: Tried to pass a simulator. + """ + if backend.configuration().simulator: + raise QISKitError('Requires a device backend, not simulator.') + + mpl_data = {} + + mpl_data['ibmq_20_tokyo'] = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], + [1, 0], [1, 1], [1, 2], [1, 3], [1, 4], + [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], + [3, 0], [3, 1], [3, 2], [3, 3], [3, 4]] + + mpl_data['ibmq_poughkeepsie'] = mpl_data['ibmq_20_tokyo'] + + mpl_data['ibmq_16_melbourne'] = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], + [0, 5], [0, 6], [1, 7], [1, 6], [1, 5], + [1, 4], [1, 3], [1, 2], [1, 1]] + + mpl_data['ibmq_16_rueschlikon'] = [[1, 0], [0, 0], [0, 1], [0, 2], [0, 3], + [0, 4], [0, 5], [0, 6], [0, 7], [1, 7], + [1, 6], [1, 5], [1, 4], [1, 3], [1, 2], [1, 1]] + + mpl_data['ibmq_5_tenerife'] = [[1, 0], [0, 1], [1, 1], [1, 2], [2, 1]] + + mpl_data['ibmq_5_yorktown'] = mpl_data['ibmq_5_tenerife'] + + config = backend.configuration() + name = config.backend_name + cmap = config.coupling_map + + dep_names = {'ibmqx5': 'ibmq_16_rueschlikon', + 'ibmqx4': 'ibmq_5_tenerife', + 'ibmqx2': 'ibmq_5_yorktown'} + + if name in dep_names.keys(): + name = dep_names[name] + + if name in mpl_data.keys(): + grid_data = mpl_data[name] + else: + fig, ax = plt.subplots(figsize=(5, 5)) # pylint: disable=invalid-name + ax.axis('off') + return fig + + x_max = max([d[1] for d in grid_data]) + y_max = max([d[0] for d in grid_data]) + max_dim = max(x_max, y_max) + + if figsize is None: + if x_max/max_dim > 0.33 and y_max/max_dim > 0.33: + figsize = (5, 5) + else: + figsize = (9, 3) + + fig, ax = plt.subplots(figsize=figsize) # pylint: disable=invalid-name + ax.axis('off') + fig.tight_layout() + + # set coloring + if qubit_color is None: + qubit_color = ['#648fff']*config.n_qubits + if line_color is None: + line_color = ['#648fff']*len(cmap) + + # Add lines for couplings + for ind, edge in enumerate(cmap): + is_symmetric = False + if edge[::-1] in cmap: + is_symmetric = True + y_start = grid_data[edge[0]][0] + x_start = grid_data[edge[0]][1] + y_end = grid_data[edge[1]][0] + x_end = grid_data[edge[1]][1] + + if is_symmetric: + if y_start == y_end: + x_end = (x_end - x_start)/2+x_start + + elif x_start == x_end: + y_end = (y_end - y_start)/2+y_start + + else: + x_end = (x_end - x_start)/2+x_start + y_end = (y_end - y_start)/2+y_start + ax.add_artist(plt.Line2D([x_start, x_end], [-y_start, -y_end], + color=line_color[ind], linewidth=line_width, + zorder=0)) + if plot_directed: + dx = x_end-x_start # pylint: disable=invalid-name + dy = y_end-y_start # pylint: disable=invalid-name + if is_symmetric: + x_arrow = x_start+dx*0.95 + y_arrow = -y_start-dy*0.95 + dx_arrow = dx*0.01 + dy_arrow = -dy*0.01 + head_width = 0.15 + else: + x_arrow = x_start+dx*0.5 + y_arrow = -y_start-dy*0.5 + dx_arrow = dx*0.2 + dy_arrow = -dy*0.2 + head_width = 0.2 + ax.add_patch(mpatches.FancyArrow(x_arrow, + y_arrow, + dx_arrow, + dy_arrow, + head_width=head_width, + length_includes_head=True, + edgecolor=None, + linewidth=0, + facecolor=line_color[ind], + zorder=1)) + + # Add circles for qubits + for var, idx in enumerate(grid_data): + _idx = [idx[1], -idx[0]] + width = _GraphDist(qubit_size, ax, True) + height = _GraphDist(qubit_size, ax, False) + ax.add_artist(mpatches.Ellipse( + _idx, width, height, color=qubit_color[var], zorder=1)) + if label_qubits: + ax.text(*_idx, s=str(var), + horizontalalignment='center', + verticalalignment='center', + color=font_color, size=font_size, weight='bold') + ax.set_xlim([-1, x_max+1]) + ax.set_ylim([-(y_max+1), 1]) + plt.close(fig) + return fig diff --git a/qiskit/transpiler/progressbar.py b/qiskit/transpiler/progressbar.py new file mode 100644 index 000000000000..5837c2c6661a --- /dev/null +++ b/qiskit/transpiler/progressbar.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +"""Progress bars module""" + +import time +import datetime +import sys +from qiskit._pubsub import Subscriber + + +class BaseProgressBar(Subscriber): + """An abstract progress bar with some shared functionality. + """ + def __init__(self): + super().__init__() + self.type = 'progressbar' + self.touched = False + self.iter = None + self.t_start = None + self.t_done = None + + def start(self, iterations): + """Start the progress bar. + + Parameters: + iterations (int): Number of iterations. + """ + self.touched = True + self.iter = int(iterations) + self.t_start = time.time() + + def update(self, n): + """Update status of progress bar. + """ + pass + + def time_elapsed(self): + """Return the time elapsed since start. + + Returns: + elapsed_time: Time since progress bar started. + """ + return "%6.2fs" % (time.time() - self.t_start) + + def time_remaining_est(self, completed_iter): + """Estimate the remaining time left. + + Parameters: + completed_iter (int): Number of iterations completed. + + Returns: + est_time: Estimated time remaining. + """ + if completed_iter: + t_r_est = (time.time() - self.t_start) / \ + completed_iter*(self.iter-completed_iter) + else: + t_r_est = 0 + date_time = datetime.datetime(1, 1, 1) + datetime.timedelta(seconds=t_r_est) + time_string = "%02d:%02d:%02d:%02d" % \ + (date_time.day - 1, date_time.hour, date_time.minute, date_time.second) + + return time_string + + def finished(self): + """Run when progress bar has completed. + """ + pass + + +class TextProgressBar(BaseProgressBar): + """ + A simple text-based progress bar. + """ + + def __init__(self): + super().__init__() + self._init_subscriber() + + def _init_subscriber(self): + def _initialize_progress_bar(num_tasks): + """ """ + self.start(num_tasks) + self.subscribe("terra.transpiler.transpile_dag.start", _initialize_progress_bar) + + def _update_progress_bar(progress): + """ """ + self.update(progress) + self.subscribe("terra.transpiler.transpile_dag.done", _update_progress_bar) + + def _finish_progress_bar(): + """ """ + self.unsubscribe("terra.transpiler.transpile_dag.start", _initialize_progress_bar) + self.unsubscribe("terra.transpiler.transpile_dag.done", _update_progress_bar) + self.unsubscribe("terra.transpiler.transpile_dag.finish", _finish_progress_bar) + self.finished() + self.subscribe("terra.transpiler.transpile_dag.finish", _finish_progress_bar) + + def start(self, iterations): + self.touched = True + self.iter = int(iterations) + self.t_start = time.time() + pbar = '-' * 50 + sys.stdout.write('\r|%s| %s%s%s [%s]' % + (pbar, 0, '/', self.iter, '')) + + def update(self, n): + filled_length = int(round(50 * n / self.iter)) + pbar = u'█' * filled_length + '-' * (50 - filled_length) + time_left = self.time_remaining_est(n) + sys.stdout.write('\r|%s| %s%s%s [%s]' % (pbar, n, '/', self.iter, time_left)) + if n == self.iter: + sys.stdout.write('\n') + sys.stdout.flush() diff --git a/test/python/notebooks/test_backend_tools.ipynb b/test/python/notebooks/test_backend_tools.ipynb new file mode 100644 index 000000000000..b2355a923939 --- /dev/null +++ b/test/python/notebooks/test_backend_tools.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "cwd = os.getcwd()\n", + "qiskit_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(cwd))))\n", + "sys.path.append(qiskit_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import IBMQ\n", + "from qiskit.qiskiterror import QISKitError\n", + "from qiskit.tools.jupyter import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "IBMQ.load_accounts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "backends = IBMQ.backends()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "my_backend = None\n", + "for back in backends:\n", + " if not back.configuration().simulator and back.status().operational:\n", + " my_backend = back\n", + " break\n", + "if my_backend is None:\n", + " raise QISKitError('Could not load an operational IBMQ device backend.')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%qiskit_backend_monitor my_backend" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%qiskit_backend_overview" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "hide_input": false, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/test/python/notebooks/test_pbar_status.ipynb b/test/python/notebooks/test_pbar_status.ipynb index b61200375350..daa56e4f74c4 100644 --- a/test/python/notebooks/test_pbar_status.ipynb +++ b/test/python/notebooks/test_pbar_status.ipynb @@ -13,7 +13,6 @@ "source": [ "import os\n", "import sys\n", - "import time\n", "cwd = os.getcwd()\n", "qiskit_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(cwd))))\n", "sys.path.append(qiskit_dir)" @@ -32,7 +31,8 @@ "source": [ "from qiskit import LegacySimulators, QuantumRegister, ClassicalRegister, QuantumCircuit, execute\n", "from qiskit.transpiler._parallel import parallel_map\n", - "from qiskit.tools import job_monitor\n", + "from qiskit.transpiler.progressbar import TextProgressBar\n", + "from qiskit.tools.monitor import job_monitor\n", "from qiskit.tools.jupyter import *" ] }, @@ -47,6 +47,7 @@ }, "outputs": [], "source": [ + "import time\n", "def func(_):\n", " time.sleep(0.1)\n", " return 0" @@ -188,11 +189,11 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": { "ExecuteTime": { - "end_time": "2018-11-08T10:45:49.348875Z", - "start_time": "2018-11-08T10:45:46.637049Z" + "end_time": "2018-11-19T05:09:14.423001Z", + "start_time": "2018-11-19T05:09:11.998829Z" } }, "outputs": [ @@ -213,11 +214,11 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": { "ExecuteTime": { - "end_time": "2018-11-08T10:45:51.781515Z", - "start_time": "2018-11-08T10:45:49.351455Z" + "end_time": "2018-11-19T05:09:17.109977Z", + "start_time": "2018-11-19T05:09:14.426453Z" } }, "outputs": [ @@ -262,6 +263,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" + }, + "nteract": { + "version": "0.12.3" } }, "nbformat": 4, diff --git a/test/python/tools/jupyter/test_notebooks.py b/test/python/tools/jupyter/test_notebooks.py index 8d1dbd012ab0..0bf8602139f4 100644 --- a/test/python/tools/jupyter/test_notebooks.py +++ b/test/python/tools/jupyter/test_notebooks.py @@ -14,8 +14,9 @@ import nbformat from nbconvert.preprocessors import ExecutePreprocessor - -from ...common import Path, QiskitTestCase, requires_cpp_simulator +from qiskit.tools.visualization._matplotlib import HAS_MATPLOTLIB +from ...common import (Path, QiskitTestCase, requires_qe_access, + requires_cpp_simulator) # Timeout (in seconds) for a single notebook. @@ -27,11 +28,9 @@ class TestJupyter(QiskitTestCase): """Notebooks test case.""" def setUp(self): - self.filename = self._get_resource_path( - 'notebooks/test_pbar_status.ipynb') self.execution_path = os.path.join(Path.SDK.value, '..') - def _execute_notebook(self, filename): + def _execute_notebook(self, filename, qe_token=None, qe_url=None): # Create the preprocessor. execute_preprocessor = ExecutePreprocessor(timeout=TIMEOUT, kernel_name=JUPYTER_KERNEL) @@ -40,14 +39,35 @@ def _execute_notebook(self, filename): with open(filename) as file_: notebook = nbformat.read(file_, as_version=4) + if qe_token and qe_url: + top_str = "from qiskit import IBMQ\n" + top_str += "IBMQ.enable_account('{token}', '{url}')".format(token=qe_token, + url=qe_url) + top = nbformat.notebooknode.NotebookNode({'cell_type': 'code', + 'execution_count': 0, + 'metadata': {}, + 'outputs': [], + 'source': top_str}) + notebook.cells = [top] + notebook.cells + # Run the notebook into the folder containing the `qiskit/` module. execute_preprocessor.preprocess( notebook, {'metadata': {'path': self.execution_path}}) @requires_cpp_simulator - def test_jupyter(self): - "Test Jupyter functionality" - self._execute_notebook(self.filename) + def test_jupyter_jobs_pbars(self): + "Test Jupyter progress bars and job status functionality" + self._execute_notebook(self._get_resource_path( + 'notebooks/test_pbar_status.ipynb')) + + @unittest.skipIf(not HAS_MATPLOTLIB, 'matplotlib not available.') + @requires_qe_access + def test_backend_tools(self, qe_token, qe_url): + "Test Jupyter backend tools." + self._execute_notebook(self._get_resource_path( + 'notebooks/test_backend_tools.ipynb'), + qe_token=qe_token, + qe_url=qe_url) if __name__ == '__main__': diff --git a/test/python/tools/monitor/test_backend_monitor.py b/test/python/tools/monitor/test_backend_monitor.py new file mode 100644 index 000000000000..335335f3ac97 --- /dev/null +++ b/test/python/tools/monitor/test_backend_monitor.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# pylint: disable=redefined-builtin + +"""Tests for the wrapper functionality.""" + +import unittest +from qiskit.backends.ibmq import IBMQ +from qiskit.tools.monitor import backend_overview, backend_monitor +from ...common import QiskitTestCase, requires_qe_access + + +class TestBackendOverview(QiskitTestCase): + """Tools test case.""" + @requires_qe_access + def test_backend_overview(self, qe_token, qe_url): + """Test backend_overview""" + IBMQ.enable_account(qe_token, qe_url) + backend_overview() + + def test_backend_monitor(self, qe_token, qe_url): + """Test backend_monitor""" + IBMQ.enable_account(qe_token, qe_url) + for back in IBMQ.backends(): + if not back.configuration().simulator: + backend = back + break + backend_monitor(backend) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/test/python/tools/test_job_monitor.py b/test/python/tools/monitor/test_job_monitor.py similarity index 89% rename from test/python/tools/test_job_monitor.py rename to test/python/tools/monitor/test_job_monitor.py index a9cfbbf9c32a..98d85269691e 100644 --- a/test/python/tools/test_job_monitor.py +++ b/test/python/tools/monitor/test_job_monitor.py @@ -10,12 +10,11 @@ """Tests for the wrapper functionality.""" import unittest - -import qiskit.tools from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit import BasicAer from qiskit import execute -from ..common import QiskitTestCase +from qiskit.tools.monitor import job_monitor +from ...common import QiskitTestCase class TestJobMonitor(QiskitTestCase): @@ -30,7 +29,7 @@ def test_job_monitor(self): qc.measure(qreg, creg) backend = BasicAer.get_backend('qasm_simulator') job_sim = execute([qc]*10, backend) - qiskit.tools.job_monitor(job_sim) + job_monitor(job_sim) self.assertEqual(job_sim.status().name, 'DONE')