python实现对布洛赫球(Bloch Sphere)动画可视化的方法

鲁炳
2023-12-01

布洛赫球可以表示任意一个量子力学中的二能级系统,任何一个二能级量子态都能在布洛赫球上找到对应的点。

静态可视化布洛赫球:

当我们只需要在布洛赫球上绘制一个静态的量子态或者一个测量基矢时,只需要import qutip即可:参见Plotting on the Bloch Sphere — QuTiP 4.6 Documentation

需要注意的是,qutip提供3d布洛赫球和2d布洛赫球的选项,2d布洛赫球使用matplotlib库画三维图,图上的箭头和遮挡关系都不正确。

而3d布洛赫球使用mayavi库进行绘图,除了3d的箭头,正确的遮挡关系,甚至可以选择透视关系。但是mayavi库的安装真是一言难尽。。。,具体可以参见(6条消息) python3.7-3.9安装mayavi教程_Dodo·D·Caster的博客-CSDN博客_python安装mayavi

动态可视化布洛赫球:

如果要在布洛赫球上绘制3d动画,qutip就不支持了,但是mayavi是支持3d动画的,可以修改一下qutip的源代码中的bloch3d,让它可以支持3d动画:


__all__ = ['new_Bloch3d']

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


class new_Bloch3d():
    """Class for plotting data on a 3D Bloch sphere using mayavi.
    Valid data can be either points, vectors, or qobj objects
    corresponding to state vectors or density matrices. for
    a two-state system (or subsystem).

    Attributes
    ----------
    fig : instance {None}
        User supplied Matplotlib Figure instance for plotting Bloch sphere.
    font_color : str {'black'}
        Color of font used for Bloch sphere labels.
    font_scale : float {0.08}
        Scale for font used for Bloch sphere labels.
    frame : bool {True}
        Draw frame for Bloch sphere
    frame_alpha : float {0.05}
        Sets transparency of Bloch sphere frame.
    frame_color : str {'gray'}
        Color of sphere wireframe.
    frame_num : int {8}
        Number of frame elements to draw.
    frame_radius : floats {0.005}
        Width of wireframe.
    point_color : list {['r', 'g', 'b', 'y']}
        List of colors for Bloch sphere point markers to cycle through.
        i.e. By default, points 0 and 4 will both be blue ('r').
    point_mode : string {'sphere','cone','cube','cylinder','point'}
        Point marker shapes.
    point_size : float {0.075}
        Size of points on Bloch sphere.
    sphere_alpha : float {0.1}
        Transparency of Bloch sphere itself.
    sphere_color : str {'#808080'}
        Color of Bloch sphere.
    size : list {[500,500]}
        Size of Bloch sphere plot in pixels. Best to have both numbers the same
        otherwise you will have a Bloch sphere that looks like a football.
    vector_color : list {['r', 'g', 'b', 'y']}
        List of vector colors to cycle through.
    vector_width : int {3}
        Width of displayed vectors.
    view : list {[45,65]}
        Azimuthal and Elevation viewing angles.
    xlabel : list {['|x>', '']}
        List of strings corresponding to +x and -x axes labels, respectively.
    xlpos : list {[1.07,-1.07]}
        Positions of +x and -x labels respectively.
    ylabel : list {['|y>', '']}
        List of strings corresponding to +y and -y axes labels, respectively.
    ylpos : list {[1.07,-1.07]}
        Positions of +y and -y labels respectively.
    zlabel : list {['|0>', '|1>']}
        List of strings corresponding to +z and -z axes labels, respectively.
    zlpos : list {[1.07,-1.07]}
        Positions of +z and -z labels respectively.

    Notes
    -----
    The use of mayavi for 3D rendering of the Bloch sphere comes with
    a few limitations: I) You can not embed a Bloch3d figure into a
    matplotlib window. II) The use of LaTex is not supported by the
    mayavi rendering engine. Therefore all labels must be defined using
    standard text. Of course you can post-process the generated figures
    later to add LaTeX using other software if needed.


    """
    def __init__(self, fig):
        # ----check for mayavi-----
        try:
            from mayavi import mlab
        except:
            raise Exception("This function requires the mayavi module.")

        # ---Image options---
        self.fig = None
        self.user_fig = None
        # check if user specified figure or axes.
        if fig:
            self.user_fig = fig
        # The size of the figure in inches, default = [500,500].
        self.size = [500, 500]
        # Azimuthal and Elvation viewing angles, default = [45,65].
        self.view = [45, 65]
        # Image background color
        self.bgcolor = 'white'
        # Image foreground color. Other options can override.
        self.fgcolor = 'black'

        # ---Sphere options---
        # Color of Bloch sphere, default = #808080
        self.sphere_color = '#808080'
        # Transparency of Bloch sphere, default = 0.1
        self.sphere_alpha = 0.1

        # ---Frame options---
        # Draw frame?
        self.frame = True
        # number of lines to draw for frame
        self.frame_num = 8
        # Color of wireframe, default = 'gray'
        self.frame_color = 'black'
        # Transparency of wireframe, default = 0.2
        self.frame_alpha = 0.05
        # Radius of frame lines
        self.frame_radius = 0.005

        # --Axes---
        # Axes color
        self.axes_color = 'black'
        # Transparency of axes
        self.axes_alpha = 0.4
        # Radius of axes lines
        self.axes_radius = 0.005

        # ---Labels---
        # Labels for x-axis (in LaTex), default = ['$x$','']
        self.xlabel = ['|+>', '|->']
        # Position of x-axis labels, default = [1.2,-1.2]
        self.xlpos = [1.07, -1.07]
        # Labels for y-axis (in LaTex), default = ['$y$','']
        self.ylabel = ['|L>', '|R>']
        # Position of y-axis labels, default = [1.1,-1.1]
        self.ylpos = [1.07, -1.07]
        # Labels for z-axis
        self.zlabel = ['|H>', '|V>']
        # Position of z-axis labels, default = [1.05,-1.05]
        self.zlpos = [1.07, -1.07]

        # ---Font options---
        # Color of fonts, default = 'black'
        self.font_color = 'black'
        # Size of fonts, default = 20
        self.font_scale = 0.08

        # ---Vector options---
        # Object used for representing vectors on Bloch sphere.
        # List of colors for Bloch vectors, default = ['b','g','r','y']
        self.vector_color = ['r', 'g', 'b', 'y']
        self.v_color = []
        # Transparency of vectors
        self.vector_alpha = 1.0
        # Width of Bloch vectors, default = 2
        self.vector_width = 2.0
        # Height of vector head
        self.vector_head_height = 0.15
        # Radius of vector head
        self.vector_head_radius = 0.075

        # ---Point options---
        # List of colors for Bloch point markers, default = ['b','g','r','y']
        self.point_color = ['r', 'g', 'b', 'y']
        self.p_color = []
        # Size of point markers
        self.point_size = 0.02
        # Shape of point markers
        # Options: 'cone' or 'cube' or 'cylinder' or 'point' or 'sphere'.
        # Default = 'sphere'
        self.point_mode = 'sphere'

        # ---Data lists---
        # Data for point markers
        self.points = []
        # Data for Bloch vectors
        self.vectors = []
        # Number of times sphere has been saved
        self.savenum = 0
        # Style of points, 'm' for multiple colors, 's' for single color
        self.point_style = []
        
        # clear cone
        self.ccs = []

    def __str__(self):
        s = ""
        s += "Bloch3D 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 += "Bloch3D sphere properties:\n"
        s += "--------------------------\n"
        s += "axes_alpha:         " + str(self.axes_alpha) + "\n"
        s += "axes_color:         " + str(self.axes_color) + "\n"
        s += "axes_radius:        " + str(self.axes_radius) + "\n"
        s += "bgcolor:            " + str(self.bgcolor) + "\n"
        s += "fgcolor:            " + str(self.fgcolor) + "\n"
        s += "font_color:         " + str(self.font_color) + "\n"
        s += "font_scale:         " + str(self.font_scale) + "\n"
        s += "frame:              " + str(self.frame) + "\n"
        s += "frame_alpha:        " + str(self.frame_alpha) + "\n"
        s += "frame_color:        " + str(self.frame_color) + "\n"
        s += "frame_num:          " + str(self.frame_num) + "\n"
        s += "frame_radius:       " + str(self.frame_radius) + "\n"
        s += "point_color:        " + str(self.point_color) + "\n"
        s += "point_mode:         " + str(self.point_mode) + "\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 += "size:               " + str(self.size) + "\n"
        s += "vector_alpha:       " + str(self.vector_alpha) + "\n"
        s += "vector_color:       " + str(self.vector_color) + "\n"
        s += "vector_width:       " + str(self.vector_width) + "\n"
        s += "vector_head_height: " + str(self.vector_head_height) + "\n"
        s += "vector_head_radius: " + str(self.vector_head_radius) + "\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 add_points(self, points, p_color, meth='s'):
        """Add a list of data points to bloch sphere.

        Parameters
        ----------
        points : array/list
            Collection of data points.
        
        p_color : 元组(RGB)
            指定点的颜色是什么,例如(1.0,0,0)

        meth : str {'s','m'}
            Type of points to plot, use 'm' for multicolored.

        """
        self.p_color.append(p_color)
        if not isinstance(points[0], (list, np.ndarray)):
            points = [[points[0]], [points[1]], [points[2]]]
        points = np.array(points)
        if meth == 's':
            if len(points[0]) == 1:
                pnts = np.array(
                    [[points[0][0]], [points[1][0]], [points[2][0]]])
                pnts = np.append(pnts, points, axis=1)
            else:
                pnts = points
            self.points.append(pnts)
            self.point_style.append('s')
        else:
            self.points.append(points)
            self.point_style.append('m')

    def add_states(self, state, s_color, kind='vector'):
        """Add a state vector Qobj to Bloch sphere.

        Parameters
        ----------
        state : qobj
            Input state vector.

        kind : str {'vector','point'}
            Type of object to plot.

        """
        # self.v_color.append(s_color)
        if isinstance(state, Qobj):
            state = [state]
        for st in state:
            if kind == 'vector':
                vec = [expect(sigmax(), st), expect(sigmay(), st),
                       expect(sigmaz(), st)]
                self.add_vectors(vec, s_color)
            elif kind == 'point':
                pnt = [expect(sigmax(), st), expect(sigmay(), st),
                       expect(sigmaz(), st)]
                self.add_points(pnt, s_color)

    def add_vectors(self, vectors, v_color):
        """Add a list of vectors to Bloch sphere.

        Parameters
        ----------
        vectors : array/list
            Array with vectors of unit length or smaller.
            
        v_color : 元组(RGB)
            指定箭头的颜色是什么,例如(1.0,0,0)

        """
        self.v_color.append(v_color)
        if isinstance(vectors[0], (list, np.ndarray)):
            for vec in vectors:
                self.vectors.append(vec)
        else:
            self.vectors.append(vectors)

    def plot_vectors(self):
        """
        Plots vectors on the Bloch sphere.
        """
        from mayavi import mlab
        from tvtk.api import tvtk
        import matplotlib.colors as colors
        ii = 0
        # print(self.vectors, self.v_color)
        for k in range(len(self.vectors)):
            vec = np.array(self.vectors[k])
            norm = np.linalg.norm(vec)
            theta = np.arccos(vec[2] / norm)
            phi = np.arctan2(vec[1], vec[0])
            vec -= 0.5 * self.vector_head_height * \
                np.array([np.sin(theta) * np.cos(phi),
                          np.sin(theta) * np.sin(phi), np.cos(theta)])

            # color = colors.colorConverter.to_rgb(
            #     self.vector_color[np.mod(k, len(self.vector_color))])
            color = self.v_color

            mlab.plot3d([0, vec[0]], [0, vec[1]], [0, vec[2]],
                        name='vector' + str(ii), tube_sides=100,
                        line_width=self.vector_width,
                        opacity=self.vector_alpha,
                        color=color[k])

            cone = tvtk.ConeSource(height=self.vector_head_height,
                                   radius=self.vector_head_radius,
                                   resolution=100)
            cone_mapper = tvtk.PolyDataMapper(
                input_connection=cone.output_port)
            prop = tvtk.Property(opacity=self.vector_alpha, color=color[k])
            cc = tvtk.Actor(mapper=cone_mapper, property=prop)
            cc.rotate_z(np.degrees(phi))
            cc.rotate_y(-90 + np.degrees(theta))
            cc.position = vec
            
            self.ccs.append(cc)
            # print(self.fig)
            self.fig.scene.add_actor(self.ccs[k])
            
            # print(self.ccs)
            # self.fig.scene.remove_actors()
            
            
    def clear(self):
        """Resets the Bloch sphere data sets to empty.
        """
        from mayavi import mlab
        from tvtk.api import tvtk
        self.points = []
        self.vectors = []
        self.point_style = []
        
        if self.fig != None:
            self.fig.scene.remove_actors(self.ccs)
        self.ccs = []
        # return(self.fig)
        
        # mlab.clf()


    def plot_points(self):
        """
        Plots points on the Bloch sphere.
        """
        from mayavi import mlab
        import matplotlib.colors as colors
        for k in range(len(self.points)):
            num = len(self.points[k][0])
            dist = [np.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
                # and sort rates from lowest to highest
                zipped = sorted(zip(dist, range(num)))
                dist, indperm = zip(*zipped)
                indperm = np.array(indperm)
            else:
                indperm = range(num)
            if self.point_style[k] == 's':
                # color = colors.colorConverter.to_rgb(
                #     self.point_color[np.mod(k, len(self.point_color))])
                # ##############
                # color = (1.0,1.0,0)
                # print(color)
                ##############
                mlab.points3d(
                    self.points[k][0][indperm], self.points[k][1][indperm],
                    self.points[k][2][indperm], figure=self.fig,
                    resolution=100, scale_factor=self.point_size,
                    mode=self.point_mode, color=self.p_color[k])

            elif self.point_style[k] == 'm':
                # pnt_colors = np.array(self.point_color * np.ceil(
                #     num / float(len(self.point_color))))
                # pnt_colors = pnt_colors[0:num]
                # pnt_colors = list(pnt_colors[indperm])
                print('ok')
                pnt_colors = [(1.0,1.0,0),(1.0,0,1.0)]
                for kk in range(num):
                    mlab.points3d(
                        self.points[k][0][
                            indperm[kk]], self.points[k][1][indperm[kk]],
                        self.points[k][2][
                            indperm[kk]], figure=self.fig, resolution=100,
                        scale_factor=self.point_size, mode=self.point_mode,
                        color=pnt_colors[kk])

    def make_sphere(self):
        """
        Plots Bloch sphere and data sets.
        """
        # setup plot
        # Figure instance for Bloch sphere plot
        from mayavi import mlab
        import matplotlib.colors as colors
        if self.user_fig:
            self.fig = self.user_fig
        else:
            self.fig = mlab.figure(
                1, size=self.size,
                bgcolor=colors.colorConverter.to_rgb(self.bgcolor),
                fgcolor=colors.colorConverter.to_rgb(self.fgcolor))

        sphere = mlab.points3d(
            0, 0, 0, figure=self.fig, scale_mode='none', scale_factor=2,
            color=colors.colorConverter.to_rgb(self.sphere_color),
            resolution=100, opacity=self.sphere_alpha, name='bloch_sphere')

        # Thse commands make the sphere look better
        sphere.actor.property.specular = 0.45
        sphere.actor.property.specular_power = 5
        sphere.actor.property.backface_culling = True

        # make frame for sphere surface
        if self.frame:
            theta = np.linspace(0, 2 * np.pi, 100)
            for angle in np.linspace(-np.pi, np.pi, self.frame_num):
                xlat = np.cos(theta) * np.cos(angle)
                ylat = np.sin(theta) * np.cos(angle)
                zlat = np.ones_like(theta) * np.sin(angle)
                xlon = np.sin(angle) * np.sin(theta)
                ylon = np.cos(angle) * np.sin(theta)
                zlon = np.cos(theta)
                mlab.plot3d(
                    xlat, ylat, zlat,
                    color=colors.colorConverter.to_rgb(self.frame_color),
                    opacity=self.frame_alpha, tube_radius=self.frame_radius)
                mlab.plot3d(
                    xlon, ylon, zlon,
                    color=colors.colorConverter.to_rgb(self.frame_color),
                    opacity=self.frame_alpha, tube_radius=self.frame_radius)

        # add axes
        axis = np.linspace(-1.0, 1.0, 10)
        other = np.zeros_like(axis)
        mlab.plot3d(
            axis, other, other,
            color=colors.colorConverter.to_rgb(self.axes_color),
            tube_radius=self.axes_radius, opacity=self.axes_alpha)
        mlab.plot3d(
            other, axis, other,
            color=colors.colorConverter.to_rgb(self.axes_color),
            tube_radius=self.axes_radius, opacity=self.axes_alpha)
        mlab.plot3d(
            other, other, axis,
            color=colors.colorConverter.to_rgb(self.axes_color),
            tube_radius=self.axes_radius, opacity=self.axes_alpha)

        # add data to sphere
        self.plot_points()
        self.plot_vectors()

        # #add labels
        mlab.text3d(0, 0, self.zlpos[0], self.zlabel[0],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        mlab.text3d(0, 0, self.zlpos[1], self.zlabel[1],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        mlab.text3d(self.xlpos[0], 0, 0, self.xlabel[0],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        mlab.text3d(self.xlpos[1], 0, 0, self.xlabel[1],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        mlab.text3d(0, self.ylpos[0], 0, self.ylabel[0],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        mlab.text3d(0, self.ylpos[1], 0, self.ylabel[1],
                    color=colors.colorConverter.to_rgb(self.font_color),
                    scale=self.font_scale)
        
        return(self.fig)

    def show(self):
        """
        Display the Bloch sphere and corresponding data sets.
        """
        from mayavi import mlab
        self.make_sphere()
        mlab.view(azimuth=self.view[0], elevation=self.view[1], distance=5)
        if self.fig:
            mlab.show()

    def save(self, name=None, format='png', dirc=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. Default is 'png'.
        dirc : str
            Directory for output images. Defaults to current working directory.

        Returns
        -------
        File containing plot of Bloch sphere.

        """
        from mayavi import mlab
        import os
        self.make_sphere()
        mlab.view(azimuth=self.view[0], elevation=self.view[1], distance=5)
        if dirc:
            if not os.path.isdir(os.getcwd() + "/" + str(dirc)):
                os.makedirs(os.getcwd() + "/" + str(dirc))
        if name is None:
            if dirc:
                mlab.savefig(os.getcwd() + "/" + str(dirc) + '/bloch_' +
                             str(self.savenum) + '.' + format)
            else:
                mlab.savefig(os.getcwd() + '/bloch_' + str(self.savenum) +
                             '.' + format)
        else:
            mlab.savefig(name)
        self.savenum += 1
        if self.fig:
            mlab.close(self.fig)

然后我们调用这个函数的new_Bloch3d这个类

import mayavi.mlab as mlab
import matplotlib.colors as colors
import moviepy.editor as mpy
from new_bloch3d import new_Bloch3d

duration= 4
fps = 25

fig_myv = mlab.figure(
                1, size=[800, 800],
                bgcolor=colors.colorConverter.to_rgb('white'),
                fgcolor=colors.colorConverter.to_rgb('black'))

b3d = new_Bloch3d(fig=fig_myv)

具体示例可以参见下一篇文章。

 类似资料: