Source code for qutip.bloch

# 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.
###############################################################################

__all__ = ['Bloch']

import os

from numpy import (ndarray, array, linspace, pi, outer, cos, sin, ones, size,
                   sqrt, real, mod, append, ceil, arange)

from packaging.version import parse as parse_version

from qutip.qobj import Qobj
from qutip.expect import expect
from qutip.operators import sigmax, sigmay, sigmaz

try:
    import matplotlib
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.patches import FancyArrowPatch
    from mpl_toolkits.mplot3d import proj3d

    # Define a custom _axes3D function based on the matplotlib version.
    # The auto_add_to_figure keyword is new for matplotlib>=3.4.
    if parse_version(matplotlib.__version__) >= parse_version('3.4'):
        def _axes3D(fig, *args, **kwargs):
            ax = Axes3D(fig, *args, auto_add_to_figure=False, **kwargs)
            return fig.add_axes(ax)
    else:
        def _axes3D(*args, **kwargs):
            return Axes3D(*args, **kwargs)

    class Arrow3D(FancyArrowPatch):
        def __init__(self, xs, ys, zs, *args, **kwargs):
            FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)

            self._verts3d = xs, ys, zs

        def draw(self, renderer):
            xs3d, ys3d, zs3d = self._verts3d
            xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)

            self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
            FancyArrowPatch.draw(self, renderer)
except:
    pass

try:
    from IPython.display import display
except:
    pass


[docs]class Bloch: r""" Class for plotting data on the Bloch sphere. Valid data can be either points, vectors, or Qobj objects. Attributes ---------- axes : matplotlib.axes.Axes User supplied Matplotlib axes for Bloch sphere animation. fig : matplotlib.figure.Figure User supplied Matplotlib Figure instance for plotting Bloch sphere. font_color : str, default 'black' Color of font used for Bloch sphere labels. font_size : int, default 20 Size of font used for Bloch sphere labels. frame_alpha : float, default 0.1 Sets transparency of Bloch sphere frame. frame_color : str, default 'gray' Color of sphere wireframe. frame_width : int, default 1 Width of wireframe. point_color : list, default ["b", "r", "g", "#CC6600"] List of colors for Bloch sphere point markers to cycle through, i.e. by default, points 0 and 4 will both be blue ('b'). point_marker : list, default ["o", "s", "d", "^"] List of point marker shapes to cycle through. point_size : list, default [25, 32, 35, 45] List of point marker sizes. Note, not all point markers look the same size when plotted! sphere_alpha : float, default 0.2 Transparency of Bloch sphere itself. sphere_color : str, default '#FFDDDD' Color of Bloch sphere. figsize : list, default [7, 7] Figure size of Bloch sphere plot. Best to have both numbers the same; otherwise you will have a Bloch sphere that looks like a football. vector_color : list, ["g", "#CC6600", "b", "r"] List of vector colors to cycle through. vector_width : int, default 5 Width of displayed vectors. vector_style : str, default '-\|>' Vector arrowhead style (from matplotlib's arrow style). vector_mutation : int, default 20 Width of vectors arrowhead. view : list, default [-60, 30] Azimuthal and Elevation viewing angles. xlabel : list, default ["$x$", ""] List of strings corresponding to +x and -x axes labels, respectively. xlpos : list, default [1.1, -1.1] Positions of +x and -x labels respectively. ylabel : list, default ["$y$", ""] List of strings corresponding to +y and -y axes labels, respectively. ylpos : list, default [1.2, -1.2] Positions of +y and -y labels respectively. zlabel : list, default ['$\\left\|0\\right>$', '$\\left\|1\\right>$'] List of strings corresponding to +z and -z axes labels, respectively. zlpos : list, default [1.2, -1.2] Positions of +z and -z labels respectively. """ def __init__(self, fig=None, axes=None, view=None, figsize=None, background=False): # Figure and axes self.fig = fig self.axes = axes # Background axes, default = False self.background = background # The size of the figure in inches, default = [5,5]. self.figsize = figsize if figsize else [5, 5] # Azimuthal and Elvation viewing angles, default = [-60,30]. self.view = view if view else [-60, 30] # Color of Bloch sphere, default = #FFDDDD self.sphere_color = '#FFDDDD' # Transparency of Bloch sphere, default = 0.2 self.sphere_alpha = 0.2 # Color of wireframe, default = 'gray' self.frame_color = 'gray' # Width of wireframe, default = 1 self.frame_width = 1 # Transparency of wireframe, default = 0.2 self.frame_alpha = 0.2 # Labels for x-axis (in LaTex), default = ['$x$', ''] self.xlabel = ['$x$', ''] # Position of x-axis labels, default = [1.2, -1.2] self.xlpos = [1.2, -1.2] # Labels for y-axis (in LaTex), default = ['$y$', ''] self.ylabel = ['$y$', ''] # Position of y-axis labels, default = [1.1, -1.1] self.ylpos = [1.2, -1.2] # Labels for z-axis (in LaTex), # default = [r'$\left\|0\right>$', r'$\left|1\right>$'] self.zlabel = [r'$\left|0\right>$', r'$\left|1\right>$'] # Position of z-axis labels, default = [1.2, -1.2] self.zlpos = [1.2, -1.2] # ---font options--- # Color of fonts, default = 'black' self.font_color = 'black' # Size of fonts, default = 20 self.font_size = 20 # ---vector options--- # List of colors for Bloch vectors, default = ['b','g','r','y'] self.vector_color = ['g', '#CC6600', 'b', 'r'] #: Width of Bloch vectors, default = 5 self.vector_width = 3 #: Style of Bloch vectors, default = '-\|>' (or 'simple') self.vector_style = '-|>' #: Sets the width of the vectors arrowhead self.vector_mutation = 20 # ---point options--- # List of colors for Bloch point markers, default = ['b','g','r','y'] self.point_color = ['b', 'r', 'g', '#CC6600'] # Size of point markers, default = 25 self.point_size = [25, 32, 35, 45] # Shape of point markers, default = ['o','^','d','s'] self.point_marker = ['o', 's', 'd', '^'] # ---data lists--- # Data for point markers self.points = [] # Data for Bloch vectors self.vectors = [] # Data for annotations self.annotations = [] # Number of times sphere has been saved self.savenum = 0 # Style of points, 'm' for multiple colors, 's' for single color self.point_style = [] # status of rendering self._rendered = False # status of showing if fig is None: self._shown = False else: self._shown = True
[docs] def set_label_convention(self, convention): """Set x, y and z labels according to one of conventions. Parameters ---------- convention : string One of the following: - "original" - "xyz" - "sx sy sz" - "01" - "polarization jones" - "polarization jones letters" see also: https://en.wikipedia.org/wiki/Jones_calculus - "polarization stokes" see also: https://en.wikipedia.org/wiki/Stokes_parameters """ ketex = "$\\left.|%s\\right\\rangle$" # \left.| is on purpose, so that every ket has the same size if convention == "original": self.xlabel = ['$x$', ''] self.ylabel = ['$y$', ''] self.zlabel = ['$\\left|0\\right>$', '$\\left|1\\right>$'] elif convention == "xyz": self.xlabel = ['$x$', ''] self.ylabel = ['$y$', ''] self.zlabel = ['$z$', ''] elif convention == "sx sy sz": self.xlabel = ['$s_x$', ''] self.ylabel = ['$s_y$', ''] self.zlabel = ['$s_z$', ''] elif convention == "01": self.xlabel = ['', ''] self.ylabel = ['', ''] self.zlabel = ['$\\left|0\\right>$', '$\\left|1\\right>$'] elif convention == "polarization jones": self.xlabel = [ketex % "\\nearrow\\hspace{-1.46}\\swarrow", ketex % "\\nwarrow\\hspace{-1.46}\\searrow"] self.ylabel = [ketex % "\\circlearrowleft", ketex % "\\circlearrowright"] self.zlabel = [ketex % "\\leftrightarrow", ketex % "\\updownarrow"] elif convention == "polarization jones letters": self.xlabel = [ketex % "D", ketex % "A"] self.ylabel = [ketex % "L", ketex % "R"] self.zlabel = [ketex % "H", ketex % "V"] elif convention == "polarization stokes": self.ylabel = ["$\\nearrow\\hspace{-1.46}\\swarrow$", "$\\nwarrow\\hspace{-1.46}\\searrow$"] self.zlabel = ["$\\circlearrowleft$", "$\\circlearrowright$"] self.xlabel = ["$\\leftrightarrow$", "$\\updownarrow$"] else: raise Exception("No such convention.")
def __str__(self): s = "" s += "Bloch data:\n" s += "-----------\n" s += "Number of points: " + str(len(self.points)) + "\n" s += "Number of vectors: " + str(len(self.vectors)) + "\n" s += "\n" s += "Bloch sphere properties:\n" s += "------------------------\n" s += "font_color: " + str(self.font_color) + "\n" s += "font_size: " + str(self.font_size) + "\n" s += "frame_alpha: " + str(self.frame_alpha) + "\n" s += "frame_color: " + str(self.frame_color) + "\n" s += "frame_width: " + str(self.frame_width) + "\n" s += "point_color: " + str(self.point_color) + "\n" s += "point_marker: " + str(self.point_marker) + "\n" s += "point_size: " + str(self.point_size) + "\n" s += "sphere_alpha: " + str(self.sphere_alpha) + "\n" s += "sphere_color: " + str(self.sphere_color) + "\n" s += "figsize: " + str(self.figsize) + "\n" s += "vector_color: " + str(self.vector_color) + "\n" s += "vector_width: " + str(self.vector_width) + "\n" s += "vector_style: " + str(self.vector_style) + "\n" s += "vector_mutation: " + str(self.vector_mutation) + "\n" s += "view: " + str(self.view) + "\n" s += "xlabel: " + str(self.xlabel) + "\n" s += "xlpos: " + str(self.xlpos) + "\n" s += "ylabel: " + str(self.ylabel) + "\n" s += "ylpos: " + str(self.ylpos) + "\n" s += "zlabel: " + str(self.zlabel) + "\n" s += "zlpos: " + str(self.zlpos) + "\n" return s def _repr_png_(self): from IPython.core.pylabtools import print_figure self.render() fig_data = print_figure(self.fig, 'png') plt.close(self.fig) return fig_data def _repr_svg_(self): from IPython.core.pylabtools import print_figure self.render() fig_data = print_figure(self.fig, 'svg').decode('utf-8') plt.close(self.fig) return fig_data
[docs] def clear(self): """Resets Bloch sphere data sets to empty. """ self.points = [] self.vectors = [] self.point_style = [] self.annotations = []
[docs] def add_points(self, points, meth='s'): """Add a list of data points to bloch sphere. Parameters ---------- points : array_like Collection of data points. meth : {'s', 'm', 'l'} Type of points to plot, use 'm' for multicolored, 'l' for points connected with a line. """ if not isinstance(points[0], (list, ndarray)): points = [[points[0]], [points[1]], [points[2]]] points = array(points) if meth == 's': if len(points[0]) == 1: pnts = array([[points[0][0]], [points[1][0]], [points[2][0]]]) pnts = append(pnts, points, axis=1) else: pnts = points self.points.append(pnts) self.point_style.append('s') elif meth == 'l': self.points.append(points) self.point_style.append('l') else: self.points.append(points) self.point_style.append('m')
[docs] def add_states(self, state, kind='vector'): """Add a state vector Qobj to Bloch sphere. Parameters ---------- state : Qobj Input state vector. kind : {'vector', 'point'} Type of object to plot. """ if isinstance(state, Qobj): state = [state] for st in state: vec = [expect(sigmax(), st), expect(sigmay(), st), expect(sigmaz(), st)] if kind == 'vector': self.add_vectors(vec) elif kind == 'point': self.add_points(vec)
[docs] def add_vectors(self, vectors): """Add a list of vectors to Bloch sphere. Parameters ---------- vectors : array_like Array with vectors of unit length or smaller. """ if isinstance(vectors[0], (list, ndarray)): for vec in vectors: self.vectors.append(vec) else: self.vectors.append(vectors)
[docs] def add_annotation(self, state_or_vector, text, **kwargs): """ Add a text or LaTeX annotation to Bloch sphere, parametrized by a qubit state or a vector. Parameters ---------- state_or_vector : Qobj/array/list/tuple Position for the annotaion. Qobj of a qubit or a vector of 3 elements. text : str Annotation text. You can use LaTeX, but remember to use raw string e.g. r"$\\langle x \\rangle$" or escape backslashes e.g. "$\\\\langle x \\\\rangle$". kwargs : Options as for mplot3d.axes3d.text, including: fontsize, color, horizontalalignment, verticalalignment. """ if isinstance(state_or_vector, Qobj): vec = [expect(sigmax(), state_or_vector), expect(sigmay(), state_or_vector), expect(sigmaz(), state_or_vector)] elif isinstance(state_or_vector, (list, ndarray, tuple)) \ and len(state_or_vector) == 3: vec = state_or_vector else: raise Exception("Position needs to be specified by a qubit " + "state or a 3D vector.") self.annotations.append({'position': vec, 'text': text, 'opts': kwargs})
[docs] def make_sphere(self): """ Plots Bloch sphere and data sets. """ self.render(self.fig, self.axes)
def run_from_ipython(self): try: __IPYTHON__ return True except NameError: return False
[docs] def render(self, fig=None, axes=None): """ Render the Bloch sphere and its data sets in on given figure and axes. """ if self._rendered: self.axes.clear() self._rendered = True # Figure instance for Bloch sphere plot if not fig: self.fig = plt.figure(figsize=self.figsize) if not axes: self.axes = _axes3D(self.fig, azim=self.view[0], elev=self.view[1]) if self.background: self.axes.clear() self.axes.set_xlim3d(-1.3, 1.3) self.axes.set_ylim3d(-1.3, 1.3) self.axes.set_zlim3d(-1.3, 1.3) else: self.plot_axes() self.axes.set_axis_off() self.axes.set_xlim3d(-0.7, 0.7) self.axes.set_ylim3d(-0.7, 0.7) self.axes.set_zlim3d(-0.7, 0.7) # Manually set aspect ratio to fit a square bounding box. # Matplotlib did this stretching for < 3.3.0, but not above. if parse_version(matplotlib.__version__) >= parse_version('3.3'): self.axes.set_box_aspect((1, 1, 1)) self.axes.grid(False) self.plot_back() self.plot_points() self.plot_vectors() self.plot_front() self.plot_axes_labels() self.plot_annotations()
def plot_back(self): # back half of sphere u = linspace(0, pi, 25) v = linspace(0, pi, 25) x = outer(cos(u), sin(v)) y = outer(sin(u), sin(v)) z = outer(ones(size(u)), cos(v)) self.axes.plot_surface(x, y, z, rstride=2, cstride=2, color=self.sphere_color, linewidth=0, alpha=self.sphere_alpha) # wireframe self.axes.plot_wireframe(x, y, z, rstride=5, cstride=5, color=self.frame_color, alpha=self.frame_alpha) # equator self.axes.plot(1.0 * cos(u), 1.0 * sin(u), zs=0, zdir='z', lw=self.frame_width, color=self.frame_color) self.axes.plot(1.0 * cos(u), 1.0 * sin(u), zs=0, zdir='x', lw=self.frame_width, color=self.frame_color) def plot_front(self): # front half of sphere u = linspace(-pi, 0, 25) v = linspace(0, pi, 25) x = outer(cos(u), sin(v)) y = outer(sin(u), sin(v)) z = outer(ones(size(u)), cos(v)) self.axes.plot_surface(x, y, z, rstride=2, cstride=2, color=self.sphere_color, linewidth=0, alpha=self.sphere_alpha) # wireframe self.axes.plot_wireframe(x, y, z, rstride=5, cstride=5, color=self.frame_color, alpha=self.frame_alpha) # equator self.axes.plot(1.0 * cos(u), 1.0 * sin(u), zs=0, zdir='z', lw=self.frame_width, color=self.frame_color) self.axes.plot(1.0 * cos(u), 1.0 * sin(u), zs=0, zdir='x', lw=self.frame_width, color=self.frame_color) def plot_axes(self): # axes span = linspace(-1.0, 1.0, 2) self.axes.plot(span, 0 * span, zs=0, zdir='z', label='X', lw=self.frame_width, color=self.frame_color) self.axes.plot(0 * span, span, zs=0, zdir='z', label='Y', lw=self.frame_width, color=self.frame_color) self.axes.plot(0 * span, span, zs=0, zdir='y', label='Z', lw=self.frame_width, color=self.frame_color) def plot_axes_labels(self): # axes labels opts = {'fontsize': self.font_size, 'color': self.font_color, 'horizontalalignment': 'center', 'verticalalignment': 'center'} self.axes.text(0, -self.xlpos[0], 0, self.xlabel[0], **opts) self.axes.text(0, -self.xlpos[1], 0, self.xlabel[1], **opts) self.axes.text(self.ylpos[0], 0, 0, self.ylabel[0], **opts) self.axes.text(self.ylpos[1], 0, 0, self.ylabel[1], **opts) self.axes.text(0, 0, self.zlpos[0], self.zlabel[0], **opts) self.axes.text(0, 0, self.zlpos[1], self.zlabel[1], **opts) for a in (self.axes.w_xaxis.get_ticklines() + self.axes.w_xaxis.get_ticklabels()): a.set_visible(False) for a in (self.axes.w_yaxis.get_ticklines() + self.axes.w_yaxis.get_ticklabels()): a.set_visible(False) for a in (self.axes.w_zaxis.get_ticklines() + self.axes.w_zaxis.get_ticklabels()): a.set_visible(False) def plot_vectors(self): # -X and Y data are switched for plotting purposes for k in range(len(self.vectors)): xs3d = self.vectors[k][1] * array([0, 1]) ys3d = -self.vectors[k][0] * array([0, 1]) zs3d = self.vectors[k][2] * array([0, 1]) color = self.vector_color[mod(k, len(self.vector_color))] if self.vector_style == '': # simple line style self.axes.plot(xs3d, ys3d, zs3d, zs=0, zdir='z', label='Z', lw=self.vector_width, color=color) else: # decorated style, with arrow heads a = Arrow3D(xs3d, ys3d, zs3d, mutation_scale=self.vector_mutation, lw=self.vector_width, arrowstyle=self.vector_style, color=color) self.axes.add_artist(a) def plot_points(self): # -X and Y data are switched for plotting purposes for k in range(len(self.points)): num = len(self.points[k][0]) dist = [sqrt(self.points[k][0][j] ** 2 + self.points[k][1][j] ** 2 + self.points[k][2][j] ** 2) for j in range(num)] if any(abs(dist - dist[0]) / dist[0] > 1e-12): # combine arrays so that they can be sorted together zipped = list(zip(dist, range(num))) zipped.sort() # sort rates from lowest to highest dist, indperm = zip(*zipped) indperm = array(indperm) else: indperm = arange(num) if self.point_style[k] == 's': self.axes.scatter( real(self.points[k][1][indperm]), - real(self.points[k][0][indperm]), real(self.points[k][2][indperm]), s=self.point_size[mod(k, len(self.point_size))], alpha=1, edgecolor=None, zdir='z', color=self.point_color[mod(k, len(self.point_color))], marker=self.point_marker[mod(k, len(self.point_marker))]) elif self.point_style[k] == 'm': pnt_colors = array(self.point_color * int(ceil(num / float(len(self.point_color))))) pnt_colors = pnt_colors[0:num] pnt_colors = list(pnt_colors[indperm]) marker = self.point_marker[mod(k, len(self.point_marker))] s = self.point_size[mod(k, len(self.point_size))] self.axes.scatter(real(self.points[k][1][indperm]), -real(self.points[k][0][indperm]), real(self.points[k][2][indperm]), s=s, alpha=1, edgecolor=None, zdir='z', color=pnt_colors, marker=marker) elif self.point_style[k] == 'l': color = self.point_color[mod(k, len(self.point_color))] self.axes.plot(real(self.points[k][1]), -real(self.points[k][0]), real(self.points[k][2]), alpha=0.75, zdir='z', color=color) def plot_annotations(self): # -X and Y data are switched for plotting purposes for annotation in self.annotations: vec = annotation['position'] opts = {'fontsize': self.font_size, 'color': self.font_color, 'horizontalalignment': 'center', 'verticalalignment': 'center'} opts.update(annotation['opts']) self.axes.text(vec[1], -vec[0], vec[2], annotation['text'], **opts)
[docs] def show(self): """ Display Bloch sphere and corresponding data sets. """ self.render(self.fig, self.axes) if self.run_from_ipython(): if self._shown: display(self.fig) else: self.fig.show() self._shown = True
[docs] def save(self, name=None, format='png', dirc=None, dpin=None): """Saves Bloch sphere to file of type ``format`` in directory ``dirc``. Parameters ---------- name : str Name of saved image. Must include path and format as well. i.e. '/Users/Paul/Desktop/bloch.png' This overrides the 'format' and 'dirc' arguments. format : str Format of output image. dirc : str Directory for output images. Defaults to current working directory. dpin : int Resolution in dots per inch. Returns ------- File containing plot of Bloch sphere. """ self.render(self.fig, self.axes) # Conditional variable for first argument to savefig # that is set in subsequent if-elses complete_path = "" if dirc: if not os.path.isdir(os.getcwd() + "/" + str(dirc)): os.makedirs(os.getcwd() + "/" + str(dirc)) if name is None: if dirc: complete_path = os.getcwd() + "/" + str(dirc) + '/bloch_' \ + str(self.savenum) + '.' + format else: complete_path = os.getcwd() + '/bloch_' + \ str(self.savenum) + '.' + format else: complete_path = name if dpin: self.fig.savefig(complete_path, dpi=dpin) else: self.fig.savefig(complete_path) self.savenum += 1 if self.fig: plt.close(self.fig)
def _hide_tick_lines_and_labels(axis): ''' Set visible property of ticklines and ticklabels of an axis to False ''' for a in axis.get_ticklines() + axis.get_ticklabels(): a.set_visible(False)