Source code for qubiter.Plotter

import pandas as pan
import sys
if 'autograd.numpy' not in sys.modules:
    import numpy as np
import matplotlib.pyplot as plt
from qubiter.StateVec import *


[docs]class Plotter: """ This class has no constructor. All its methods are static methods. It contains various utility methods for (1) creating pandas dataframes from state vectors and density matrices created by StateVec methods, and (2) plotting such dataframes. Dataframes with probability (resp., amplitude) entries are plotted as bar plots (resp., quiver plots). Casting things into dataframes is convenient because they are automatically displayed as html tables in jupyter notebooks. """
[docs] @staticmethod def get_states(num_qbits, use_bin_labels=True, ZL=True): """ Returns list of strings ['0', '1', ..] with 2^num_qbits entries if use_bin_labels=False, or the binary string equivalents if use_bin_labels=True. Parameters ---------- num_qbits : int use_bin_labels : bool ZL : bool If True, appends "(ZL)" to state labels. Else appends "(ZF)" Returns ------- list[str] """ str1 = "" if num_qbits > 1: if ZL: str1 = "ZL" else: str1 = "ZF" if not use_bin_labels: states = [str(x) for x in range(0, 1 << num_qbits)] else: states = [np.binary_repr(x, width=num_qbits) + str1 for x in range(0, 1 << num_qbits)] return states
[docs] @staticmethod def get_st_vec_df(trad_st_vec, use_bin_labels=True): """ Admits as input trad_st_vec which is a complex numpy array with shape (dim,) where dim= 2^num_qbits and ZL convention is assumed. Returns a dataframe with a single column with 2^num_bit rows. The rows are labelled by 0, 1, 2, ... or their binary equivalents, depending on the bool value of use_bin_labels. Parameters ---------- trad_st_vec : np.ndarray use_bin_labels : bool Returns ------- pan.DataFrame """ num_qbits = trad_st_vec.shape[0].bit_length() - 1 states = Plotter.get_states(num_qbits, use_bin_labels) return pan.DataFrame(trad_st_vec, index=states)
[docs] @staticmethod def get_den_mat_df(num_qbits, den_mat, use_bin_labels=True): """ Admits as input a numpy array den_mat (a density matrix) of shape ( dim, dim), where dim= 2^num_qbits and ZL convention is assumed. Returns square dataframe with entries of den_mat. The rows and columns are labelled by 0, 1, 2, ... or their binary equivalents, depending on the bool value of use_bin_labels. Parameters ---------- num_qbits : int den_mat : np.ndarray use_bin_labels : bool Returns ------- pan.DataFrame """ assert 1 << num_qbits == den_mat.shape[0] states = Plotter.get_states(num_qbits, use_bin_labels) return pan.DataFrame(den_mat, columns=states, index=states)
[docs] @staticmethod def get_pd_df(num_qbits, pd, use_bin_labels=True): """ Admits as input a 1-dim numpy array pd (probability distribution) with shape ( 2^num_qbits, ) and ZL convention is assumed. Returns a dataframe with its entries. The rows are labelled by 0, 1, 2, ... or their binary equivalents, depending on the bool value of use_bin_labels. Parameters ---------- num_qbits : int pd : np.ndarray use_bin_labels : bool Returns ------- pan.DataFrame """ # print(pd) assert 1 << num_qbits == pd.shape[0] states = Plotter.get_states(num_qbits, use_bin_labels) return pan.DataFrame(pd, index=states)
[docs] @staticmethod def get_bit_probs_df(bit_probs): """ Admits as input a list bit_probs with num_qbits items consisting of pairs of two floats. Returns a dataframe with two columns and num_qbits rows. The rows are labelled by 0, 1, 2, ... Parameters ---------- bit_probs : list[tuple(float, float)] Returns ------- pan.DataFrame """ p0_col, p1_col = zip(*bit_probs) # print('p0_col', p0_col) # print('p1_col', p1_col) return pan.DataFrame({'Prob(0)': p0_col, 'Prob(1)': p1_col})
[docs] @staticmethod def plot_probs_col(titles, probs_col_df_list): """ Admits as input a list of dataframes called 'probs_col_df_list'. The names of the dataframes are given by the list of strings 'titles'. The dataframes all have a single column, and possibly different numbers of rows. All entries of all dataframes are floats between 0 and 1. This function plots each dataframe in the list as a barplot. Parameters ---------- titles : list[str] probs_col_df_list : list[pan.DataFrame] Returns ------- None """ assert len(titles) == len(probs_col_df_list) num_titles = len(titles) def single_pd(ax, title, pd_df): y_pos = np.arange(len(pd_df.index)) + .5 plt.sca(ax) plt.yticks(y_pos, pd_df.index) ax.invert_yaxis() ax.set_xticks([0, .25, .5, .75, 1]) ax.set_xlim(0, 1) for row in range(len(y_pos)): val = pd_df.values[row] if isinstance(val, np.ndarray): val = val[0] ax.text(val, y_pos[row], '{:.3f}'.format(val)) ax.grid(True) ax.set_title(title) # new version of python/matplotlib has bug here. # The following used to work but no longer does. # ax.barh(y_pos, pd_df.values, align='center') # work around for b in range(len(y_pos)): ax.barh(y_pos[b], pd_df.values[b], align='center', color='blue') plt.close('all') fig, ax_list = plt.subplots(nrows=num_titles, ncols=1) # print("***", ax_list[0]) if num_titles == 1: ax_list = [ax_list] for k, tit in enumerate(titles): single_pd(ax_list[k], tit, probs_col_df_list[k]) plt.tight_layout() plt.show()
[docs] @staticmethod def plot_phasors(titles, st_vec_df_list=None, den_mat_df_list=None): """ Admits as input a list of dataframes called 'st_vec_df_list' or one called 'den_mat_df_list' but not both. Exactly one of these lists must be None. The names of the dataframes are given by the list of strings 'titles'. Let dim = 2^num_qbits. The dataframes in st_vec_df_list have one column with dim rows, whereas those for den_mat_df_list are square with dim rows and columns. This function plots each dataframe in the list as a quiver plot (phasor->arrow) with dim rows and either one column or dim columns. Parameters ---------- titles : list[str] st_vec_df_list : list[pan.DataFrame]|None den_mat_df_list : list[pan.DataFrame]|None Returns ------- None """ flag = 0 if st_vec_df_list is not None: flag += 1 if den_mat_df_list is not None: flag += 1 assert flag == 1 if den_mat_df_list: case = 'dm' df_list = den_mat_df_list else: case = 'vec' df_list = st_vec_df_list num_titles = len(titles) def single_ax(ax, title, df): states = df.index num_sts = len(states) y = np.linspace(0, num_sts-1, num_sts) if case == 'vec': assert len(df.columns) == 1 x = [0] x_num_sts = 1 else: assert len(df.columns) == num_sts x = y x_num_sts = num_sts xx, yy = np.meshgrid(x, y) # print(xx) # print(yy) ax.set_xlim(-1, x_num_sts) ax.set_xticks(np.arange(0, x_num_sts)) ax.xaxis.tick_top() ax.set_ylim(-1, num_sts) ax.set_yticks(np.arange(0, num_sts)) ax.invert_yaxis() ax.set_aspect('equal', adjustable='box') ax.set_title(title, y=1.2) for st, nom in enumerate(states): ax.annotate(nom, xy=(x_num_sts+.25, st), annotation_clip=False) max_mag = np.max(np.absolute(df.values)) q = ax.quiver(xx, yy, df.values.real, df.values.imag, scale=max_mag, units='x') plt.quiverkey(q, 0, -2, max_mag, '= {:.2e}'.format(max_mag), labelpos='E', coordinates='data') plt.close('all') fig, ax_list = plt.subplots(nrows=num_titles, ncols=1) if num_titles == 1: ax_list = [ax_list] for k, tit in enumerate(titles): single_ax(ax_list[k], tit, df_list[k]) plt.tight_layout(pad=2) plt.show()
[docs] @staticmethod def plot_counts(state_name_to_counts): """ Plots an OrderedDict[str, int] called state_name_to_counts as a bar graph. Divides counts by their sum before plotting. Parameters ---------- state_name_to_counts : OrderedDict[str, int] Returns ------- None """ dictio = state_name_to_counts data = np.array(list(dictio.values()), dtype=float) data /= np.sum(data) df = pan.DataFrame(data, index=dictio.keys()) Plotter.plot_probs_col(['count prob'], [df])
if __name__ == "__main__": def main(): num_qbits = 3 st_vec0 = StateVec(num_qbits, arr=StateVec.get_random_st_vec(num_qbits).arr) st_vec1 = StateVec(num_qbits, arr=StateVec.get_random_st_vec(num_qbits).arr) st_vec_dict = {'br0': st_vec0, 'br1': st_vec1, 'br3': None} trad_st_vec = st_vec0.get_traditional_st_vec den_mat = StateVec.get_den_mat(num_qbits, st_vec_dict) # print("den_mat", den_mat) st_vec_pd = st_vec0.get_pd() den_mat_pd = StateVec.get_den_mat_pd(den_mat) bit_probs_vec = StateVec.get_bit_probs(num_qbits, st_vec_pd) bit_probs_dm = StateVec.get_bit_probs(num_qbits, den_mat_pd) st_vec_df = Plotter.get_st_vec_df(st_vec0.get_traditional_st_vec) den_mat_df = Plotter.get_den_mat_df(num_qbits, den_mat) # print("den_mat_df", den_mat_df) st_vec_pd_df = Plotter.get_pd_df(num_qbits, st_vec_pd) den_mat_pd_df = Plotter.get_pd_df(num_qbits, den_mat_pd) bit_probs_df1 = Plotter.get_bit_probs_df(bit_probs_vec) bit_probs_df2 = Plotter.get_bit_probs_df(bit_probs_dm) Plotter.plot_probs_col(['st_vec_pd'], [st_vec_pd_df]) # print(bit_probs_df1) Plotter.plot_probs_col( ['bit_probs, Prob(0)'], [bit_probs_df1["Prob(0)"]]) Plotter.plot_phasors(['st_vec'], st_vec_df_list=[st_vec_df]) Plotter.plot_phasors(['den_mat'], den_mat_df_list=[den_mat_df]) main()