From 471b479f5357c12966ff0dc47d92d5763b59c24a Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Tue, 15 Jun 2021 05:01:27 +0430 Subject: [PATCH 001/112] matrix_histogram updated - imporved the styling options --- qutip/visualization.py | 165 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 19 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index f8c979803a..f87e189be0 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -67,6 +67,7 @@ import matplotlib as mpl from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D + from mpl_toolkits.mplot3d.axis3d import Axis # Define a custom _axes3D function based on the matplotlib version. # The auto_add_to_figure keyword is new for matplotlib>=3.4. @@ -414,8 +415,11 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): return fig, ax -def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, - colorbar=True, fig=None, ax=None): +def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, title=None, limits=None, + fig=None, ax=None, figsize=None, + colorbar=True, cmap='jet', cmap_min=0., cmap_max=1., + bars_spacing=0.1, bars_alpha=1., bars_lw=0.5, bars_edgecolor='k', shade=False, + azim=65, elev=30, proj_type='ortho', stick=False, cbar_pad=0.04): """ Draw a histogram for the matrix M, with the given x and y labels and title. @@ -429,6 +433,9 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ylabels : list of strings list of y labels + + zticks : list of numbers + list of z-axis ticks location title : string title of the plot (optional) @@ -439,7 +446,50 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax : a matplotlib axes instance The axes context in which the plot will be drawn. - Returns + cmap : string (default: 'jet') + colormap name + + cmap_min : float (default: 0.0) + colormap truncation minimum, a value in range 0-1 + + cmap_max : float (default: 1.0) + colormap truncation maximum, a value in range 0-1 + + bars_spacing : float (default: 0.1) + spacing between bars + + bars_alpha : float (default: 1.) + transparency of bars, should be in range 0-1 + + bars_lw : float (default: 0.5) + linewidth of bars' edges + + bars_edgecolor : color (default: 'k') + color of bars' edges, examples: 'k', '1', (0.1, 0.2, 0.5), '#0f0f0f80',... + + shade : bool (default: True) + when True, this shades the dark sides of the bars (relative + to the plot's source of light). + + azim : float + Azimuthal viewing angle. + + elev : float + Elevation viewing angle. + + proj_type : string (default: 'ortho') + type of projection ('ortho' or 'persp') + + stick : bool (default: False) + works for Azimuthal viewing angles between -360 and +360 + + cbar_pad : float (default: 0.04) + fraction of original axes between colorbar and new image axes (padding between 3D figure and colorbar). + + figsize : tuple of two numbers + size of the figure + + Returns : ------- fig, ax : tuple A tuple of the matplotlib figure and axes instances used to produce @@ -452,68 +502,145 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, """ + # limit finder function + def lim_finder(z): + if z>0: + if int(z+0.5)z>int(z)-0.5 or z%1==0.5: + return int(z)-0.5 + else: + return int(z-0.5) + else: + return 0 + + # colormap truncation function + def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): + if isinstance(cmap, str): + cmap = plt.get_cmap(cmap) + new_cmap = mpl.colors.LinearSegmentedColormap.from_list( + 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval), + cmap(np.linspace(minval, maxval, n))) + return new_cmap + + # patch + if not hasattr(Axis, "_get_coord_info_old"): + def _get_coord_info_new(self, renderer): + mins, maxs, centers, deltas, tc, highs = self._get_coord_info_old(renderer) + mins += deltas / 4 + maxs -= deltas / 4 + return mins, maxs, centers, deltas, tc, highs + Axis._get_coord_info_old = Axis._get_coord_info + Axis._get_coord_info = _get_coord_info_new + + + if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() n = np.size(M) xpos, ypos = np.meshgrid(range(M.shape[0]), range(M.shape[1])) - xpos = xpos.T.flatten() - 0.5 - ypos = ypos.T.flatten() - 0.5 + xpos = xpos.T.flatten() + 0.5 + ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = 0.8 * np.ones(n) + dx = dy = (1-bars_spacing) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: z_min = limits[0] z_max = limits[1] else: - z_min = min(dz) - z_max = max(dz) - if z_min == z_max: - z_min -= 0.1 - z_max += 0.1 + limits = [lim_finder(min(dz)),lim_finder(max(dz))] + z_min = limits[0] + z_max = limits[1] norm = mpl.colors.Normalize(z_min, z_max) - cmap = cm.get_cmap('jet') # Spectral + cmap = truncate_colormap(cmap, cmap_min, cmap_max) # Spectral colors = cmap(norm(dz)) if ax is None: - fig = plt.figure() - ax = _axes3D(fig, azim=-35, elev=35) + if figsize: + fig = plt.figure(figsize=figsize) + else: + fig = plt.figure() + ax = _axes3D(fig, azim=azim, elev=elev) + ax.set_proj_type(proj_type) - ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors) + ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, edgecolors=bars_edgecolor, linewidths=bars_lw, alpha=bars_alpha, shade=shade) + # remove vertical lines on xz and yz plane + ax.yaxis._axinfo["grid"]['linewidth'] = 0 + ax.xaxis._axinfo["grid"]['linewidth'] = 0 if title and fig: ax.set_title(title) # x axis - xtics = -0.5 + np.arange(M.shape[0]) + xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) if nxlabels != len(xtics): raise ValueError(f"got {nxlabels} xlabels but needed {len(xtics)}") ax.set_xticklabels(xlabels) + else: + ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) + ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) + ax.set_xticklabels([str(i) for i in range(M.shape[0])]) # y axis - ytics = -0.5 + np.arange(M.shape[1]) + ytics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) if nylabels != len(ytics): raise ValueError(f"got {nylabels} ylabels but needed {len(ytics)}") ax.set_yticklabels(ylabels) + else: + ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) + ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) + ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + # z axis ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) - ax.set_zlim3d([min(z_min, 0), z_max]) + # ax.set_zlim3d([min(z_min, 0), z_max]) + if z_min>0 and z_max>0: + ax.set_zlim3d([0, z_max]) + elif z_min<0 and z_max<0: + ax.set_zlim3d([0, z_min]) + else: + ax.set_zlim3d([z_min, z_max]) + + + if zticks: + ax.set_zticks(zticks) + else: + ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) + + # stick to xz and yz plane + if stick== True: + if 0 Date: Tue, 15 Jun 2021 22:24:31 +0430 Subject: [PATCH 002/112] resolved code-climate issues --- qutip/visualization.py | 103 +++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index f87e189be0..227dd5b0ee 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -415,11 +415,12 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): return fig, ax -def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, title=None, limits=None, - fig=None, ax=None, figsize=None, - colorbar=True, cmap='jet', cmap_min=0., cmap_max=1., - bars_spacing=0.1, bars_alpha=1., bars_lw=0.5, bars_edgecolor='k', shade=False, - azim=65, elev=30, proj_type='ortho', stick=False, cbar_pad=0.04): +def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, + title=None, limits=None, fig=None, ax=None, figsize=None, + colorbar=True, cmap='jet', cmap_min=0., cmap_max=1., + bars_spacing=0.1, bars_alpha=1., bars_lw=0.5, + bars_edgecolor='k', shade=False, azim=65, elev=30, + proj_type='ortho', stick=False, cbar_pad=0.04): """ Draw a histogram for the matrix M, with the given x and y labels and title. @@ -447,25 +448,25 @@ def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, title=None, lim The axes context in which the plot will be drawn. cmap : string (default: 'jet') - colormap name - + colormap name + cmap_min : float (default: 0.0) colormap truncation minimum, a value in range 0-1 cmap_max : float (default: 1.0) colormap truncation maximum, a value in range 0-1 - + bars_spacing : float (default: 0.1) spacing between bars - + bars_alpha : float (default: 1.) transparency of bars, should be in range 0-1 bars_lw : float (default: 0.5) linewidth of bars' edges - + bars_edgecolor : color (default: 'k') - color of bars' edges, examples: 'k', '1', (0.1, 0.2, 0.5), '#0f0f0f80',... + color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' shade : bool (default: True) when True, this shades the dark sides of the bars (relative @@ -473,23 +474,24 @@ def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, title=None, lim azim : float Azimuthal viewing angle. - + elev : float Elevation viewing angle. - + proj_type : string (default: 'ortho') type of projection ('ortho' or 'persp') - + stick : bool (default: False) - works for Azimuthal viewing angles between -360 and +360 - + works for Azimuthal viewing angles between -360 and +360 + cbar_pad : float (default: 0.04) - fraction of original axes between colorbar and new image axes (padding between 3D figure and colorbar). + fraction of original axes between colorbar and new image axes + (padding between 3D figure and colorbar). figsize : tuple of two numbers size of the figure - Returns : + Returns : ------- fig, ax : tuple A tuple of the matplotlib figure and axes instances used to produce @@ -504,40 +506,41 @@ def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, title=None, lim # limit finder function def lim_finder(z): - if z>0: - if int(z+0.5) 0: + if int(z+0.5) < z < int(z)+0.5 or z % 1 == 0.5: + limit = int(z)+0.5 else: - return int(z+0.5) - elif z<0: - if int(z-0.5)>z>int(z)-0.5 or z%1==0.5: - return int(z)-0.5 + limit = int(z+0.5) + elif z < 0: + if int(z-0.5) > z > int(z)-0.5 or z % 1 == 0.5: + limit = int(z)-0.5 else: - return int(z-0.5) + limit = int(z-0.5) else: - return 0 + limit = 0 + return limit # colormap truncation function def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): if isinstance(cmap, str): cmap = plt.get_cmap(cmap) new_cmap = mpl.colors.LinearSegmentedColormap.from_list( - 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval), - cmap(np.linspace(minval, maxval, n))) + 'trunc({n},{a:.2f},{b:.2f})'.format( + n=cmap.name, a=minval, b=maxval), + cmap(np.linspace(minval, maxval, n))) return new_cmap # patch if not hasattr(Axis, "_get_coord_info_old"): def _get_coord_info_new(self, renderer): - mins, maxs, centers, deltas, tc, highs = self._get_coord_info_old(renderer) - mins += deltas / 4 - maxs -= deltas / 4 + mins, maxs, centers, deltas, tc, highs = \ + self._get_coord_info_old(renderer) + mins += deltas/4 + maxs -= deltas/4 return mins, maxs, centers, deltas, tc, highs - Axis._get_coord_info_old = Axis._get_coord_info + Axis._get_coord_info_old = Axis._get_coord_info Axis._get_coord_info = _get_coord_info_new - - if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() @@ -554,7 +557,7 @@ def _get_coord_info_new(self, renderer): z_min = limits[0] z_max = limits[1] else: - limits = [lim_finder(min(dz)),lim_finder(max(dz))] + limits = [lim_finder(min(dz)), lim_finder(max(dz))] z_min = limits[0] z_max = limits[1] @@ -570,7 +573,9 @@ def _get_coord_info_new(self, renderer): ax = _axes3D(fig, azim=azim, elev=elev) ax.set_proj_type(proj_type) - ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, edgecolors=bars_edgecolor, linewidths=bars_lw, alpha=bars_alpha, shade=shade) + ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, + edgecolors=bars_edgecolor, linewidths=bars_lw, + alpha=bars_alpha, shade=shade) # remove vertical lines on xz and yz plane ax.yaxis._axinfo["grid"]['linewidth'] = 0 ax.xaxis._axinfo["grid"]['linewidth'] = 0 @@ -606,17 +611,15 @@ def _get_coord_info_new(self, renderer): ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) ax.set_yticklabels([str(i) for i in range(M.shape[1])]) - # z axis ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) # ax.set_zlim3d([min(z_min, 0), z_max]) - if z_min>0 and z_max>0: + if z_min > 0 and z_max > 0: ax.set_zlim3d([0, z_max]) - elif z_min<0 and z_max<0: + elif z_min < 0 and z_max < 0: ax.set_zlim3d([0, z_min]) else: ax.set_zlim3d([z_min, z_max]) - if zticks: ax.set_zticks(zticks) @@ -624,18 +627,18 @@ def _get_coord_info_new(self, renderer): ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) # stick to xz and yz plane - if stick== True: - if 0 Date: Wed, 16 Jun 2021 01:14:26 +0430 Subject: [PATCH 003/112] resolved code-climate issues - 2 --- qutip/visualization.py | 134 +++++++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 227dd5b0ee..f285de5c5b 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -415,12 +415,8 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): return fig, ax -def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, - title=None, limits=None, fig=None, ax=None, figsize=None, - colorbar=True, cmap='jet', cmap_min=0., cmap_max=1., - bars_spacing=0.1, bars_alpha=1., bars_lw=0.5, - bars_edgecolor='k', shade=False, azim=65, elev=30, - proj_type='ortho', stick=False, cbar_pad=0.04): +def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, + colorbar=True, fig=None, ax=None, options=None): """ Draw a histogram for the matrix M, with the given x and y labels and title. @@ -434,9 +430,6 @@ def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, ylabels : list of strings list of y labels - - zticks : list of numbers - list of z-axis ticks location title : string title of the plot (optional) @@ -447,49 +440,63 @@ def matrix_histogram(M, xlabels=None, ylabels=None, zticks=None, ax : a matplotlib axes instance The axes context in which the plot will be drawn. - cmap : string (default: 'jet') - colormap name + colorbar : bool (default: True) + show colorbar - cmap_min : float (default: 0.0) - colormap truncation minimum, a value in range 0-1 + options : dict + dictionary containing extra options + all keys are of type `str` and values have different types + key:value pairs should be as follows: - cmap_max : float (default: 1.0) - colormap truncation maximum, a value in range 0-1 + 'zticks' : list of numbers + list of z-axis ticks location - bars_spacing : float (default: 0.1) - spacing between bars + 'cmap' : string (default: 'jet') + colormap name - bars_alpha : float (default: 1.) - transparency of bars, should be in range 0-1 + 'cmap_min' : float (default: 0.0) + colormap truncation minimum, a value in range 0-1 - bars_lw : float (default: 0.5) - linewidth of bars' edges + 'cmap_max' : float (default: 1.0) + colormap truncation maximum, a value in range 0-1 - bars_edgecolor : color (default: 'k') - color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' - - shade : bool (default: True) - when True, this shades the dark sides of the bars (relative - to the plot's source of light). + 'bars_spacing' : float (default: 0.1) + spacing between bars - azim : float - Azimuthal viewing angle. + 'bars_alpha' : float (default: 1.) + transparency of bars, should be in range 0-1 - elev : float - Elevation viewing angle. + 'bars_lw' : float (default: 0.5) + linewidth of bars' edges - proj_type : string (default: 'ortho') - type of projection ('ortho' or 'persp') + 'bars_edgecolor' : color (default: 'k') + color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' - stick : bool (default: False) - works for Azimuthal viewing angles between -360 and +360 + 'shade' : bool (default: True) + when True, this shades the dark sides of the bars (relative + to the plot's source of light). - cbar_pad : float (default: 0.04) - fraction of original axes between colorbar and new image axes - (padding between 3D figure and colorbar). + 'azim' : float + Azimuthal viewing angle. - figsize : tuple of two numbers - size of the figure + 'elev' : float + Elevation viewing angle. + + 'proj_type' : string (default: 'ortho') + type of projection ('ortho' or 'persp') + + 'stick' : bool (default: False) + works for Azimuthal viewing angles between -360 and +360 + + 'cbar_pad' : float (default: 0.04) + fraction of original axes between colorbar and new image axes + (padding between 3D figure and colorbar). + + 'cbarmax_to_zmax' : bool (default: False) + set color of maximum z-value to maximum color of colorbar + + 'figsize' : tuple of two numbers + size of the figure Returns : ------- @@ -541,6 +548,39 @@ def _get_coord_info_new(self, renderer): Axis._get_coord_info_old = Axis._get_coord_info Axis._get_coord_info = _get_coord_info_new + # default options + default_opts = {'figsize':None, 'cmap':'jet', 'cmap_min':0., 'cmap_max':1., + 'zticks':None, 'bars_spacing':0.1, 'bars_alpha':1., 'bars_lw':0.5, + 'bars_edgecolor':'k', 'shade':False, 'azim':65, 'elev':30, + 'proj_type':'ortho', 'stick':False, + 'cbar_pad':0.04, 'cbarmax_to_zmax':False} + + if options: + # check if keys in option dict are valid + for key in options: + if key not in default_opts: + raise ValueError(f"{key} is not a valid option") + + # updating default options + default_opts.update(options) + + figsize = default_opts['figsize'] + cmap = default_opts['cmap'] + cmap_min = default_opts['cmap_min'] + cmap_max = default_opts['cmap_max'] + zticks = default_opts['zticks'] + bars_spacing = default_opts['bars_spacing'] + bars_alpha = default_opts['bars_alpha'] + bars_lw = default_opts['bars_lw'] + bars_edgecolor = default_opts['bars_edgecolor'] + shade = default_opts['shade'] + azim = default_opts['azim'] + elev = default_opts['elev'] + proj_type = default_opts['proj_type'] + stick = default_opts['stick'] + cbar_pad = default_opts['cbar_pad'] + cbarmax_to_zmax = default_opts['cbarmax_to_zmax'] + if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() @@ -561,8 +601,12 @@ def _get_coord_info_new(self, renderer): z_min = limits[0] z_max = limits[1] - norm = mpl.colors.Normalize(z_min, z_max) - cmap = truncate_colormap(cmap, cmap_min, cmap_max) # Spectral + if cbarmax_to_zmax: + norm = mpl.colors.Normalize(min(dz), max(dz)) + else: + norm = mpl.colors.Normalize(z_min, z_max) + cmap = truncate_colormap(cmap, cmap_min, cmap_max) + # Spectral colors = cmap(norm(dz)) if ax is None: @@ -628,13 +672,13 @@ def _get_coord_info_new(self, renderer): # stick to xz and yz plane if stick is True: - if 0 < azim <= 90 or -360 <= azim <- 270 or azim == 0: + if 0 < azim <= 90 or -360 <= azim < -270 or azim == 0: ax.set_ylim(1-0.55,) ax.set_xlim(1-0.55,) - elif 90 < azim <= 180 or -270 <= azim <- 180: + elif 90 < azim <= 180 or -270 <= azim < -180: ax.set_ylim(1-0.55,) ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 180 < azim <= 270 or -180 <= azim <- 90: + elif 180 < azim <= 270 or -180 <= azim < -90: ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) elif 270 < azim <= 360 or -90 <= azim < 0: From 1fa93610e04e502534dc3a6c7cb2d7e4d0757691 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 01:21:38 +0430 Subject: [PATCH 004/112] resolved code-climate issues - 3 --- qutip/visualization.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index f285de5c5b..cb44fa1997 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -549,11 +549,12 @@ def _get_coord_info_new(self, renderer): Axis._get_coord_info = _get_coord_info_new # default options - default_opts = {'figsize':None, 'cmap':'jet', 'cmap_min':0., 'cmap_max':1., - 'zticks':None, 'bars_spacing':0.1, 'bars_alpha':1., 'bars_lw':0.5, - 'bars_edgecolor':'k', 'shade':False, 'azim':65, 'elev':30, - 'proj_type':'ortho', 'stick':False, - 'cbar_pad':0.04, 'cbarmax_to_zmax':False} + default_opts = {'figsize': None, 'cmap': 'jet', 'cmap_min': 0., + 'cmap_max': 1., 'zticks': None, 'bars_spacing': 0.1, + 'bars_alpha': 1., 'bars_lw': 0.5, 'bars_edgecolor': 'k', + 'shade': False, 'azim': 65, 'elev': 30, + 'proj_type': 'ortho', 'stick': False, + 'cbar_pad': 0.04, 'cbarmax_to_zmax': False} if options: # check if keys in option dict are valid From 745a6abf5a512e8e4e6951f569085b10ec58a213 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 01:24:15 +0430 Subject: [PATCH 005/112] resolved code-climate issues - 4 --- qutip/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index cb44fa1997..d903e4e539 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -416,7 +416,7 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, - colorbar=True, fig=None, ax=None, options=None): + colorbar=True, fig=None, ax=None, options=None): """ Draw a histogram for the matrix M, with the given x and y labels and title. From 5d76d9857d820fde57239a16715ab4f0303976d6 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 07:12:00 +0430 Subject: [PATCH 006/112] resolved code-climate issues - 5 --- qutip/visualization.py | 138 ++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index d903e4e539..020a6805e2 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -415,6 +415,88 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): return fig, ax +def _limit_finder(z): + """Finds nearest proper 0.5 value + This funtion is used when limits is not passed to matrix_histogtam + examples: + if z=0.1 returns 0.0 + if z=-0.1 returns -0.5 + if z=-2.4 returns -2.5 + if z=3.8 returns 4 + if z=-5.0 returns -5.0 + + Parameters + ---------- + z : float or int + + Returns + ------- + limit : float + nearest proper 0.5 value. + """ + if z > 0: + if int(z+0.5) < z < int(z)+0.5 or z % 1 == 0.5: + limit = int(z)+0.5 + else: + limit = int(z+0.5) + elif z < 0: + if int(z-0.5) > z > int(z)-0.5 or z % 1 == 0.5: + limit = int(z)-0.5 + else: + limit = int(z-0.5) + else: + limit = 0 + return limit + + +def _remove_margins(): + """ + Removes margins about z=0 and improves the style + """ + if not hasattr(Axis, "_get_coord_info_old"): + def _get_coord_info_new(self, renderer): + mins, maxs, centers, deltas, tc, highs = \ + self._get_coord_info_old(renderer) + mins += deltas/4 + maxs -= deltas/4 + return mins, maxs, centers, deltas, tc, highs + Axis._get_coord_info_old = Axis._get_coord_info + Axis._get_coord_info = _get_coord_info_new + + +def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): + """ + Truncates portion of a colormap and returns the new one + """ + if isinstance(cmap, str): + cmap = plt.get_cmap(cmap) + new_cmap = mpl.colors.LinearSegmentedColormap.from_list( + 'trunc({n},{a:.2f},{b:.2f})'.format( + n=cmap.name, a=minval, b=maxval), + cmap(np.linspace(minval, maxval, n))) + return new_cmap + + +def _stick_to_planes(stick, azim, ax, M, bars_spacing): + """Adjusts xlim and ylim in way that bars will + Stick to xz and yz planes + """ + if stick is True: + if 0 < azim <= 90 or -360 <= azim < -270 or azim == 0: + ax.set_ylim(1-0.55,) + ax.set_xlim(1-0.55,) + elif 90 < azim <= 180 or -270 <= azim < -180: + ax.set_ylim(1-0.55,) + ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) + elif 180 < azim <= 270 or -180 <= azim < -90: + ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) + ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) + elif 270 < azim <= 360 or -90 <= azim < 0: + ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) + ax.set_xlim(1-0.55,) + + + def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, colorbar=True, fig=None, ax=None, options=None): """ @@ -511,42 +593,8 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, """ - # limit finder function - def lim_finder(z): - if z > 0: - if int(z+0.5) < z < int(z)+0.5 or z % 1 == 0.5: - limit = int(z)+0.5 - else: - limit = int(z+0.5) - elif z < 0: - if int(z-0.5) > z > int(z)-0.5 or z % 1 == 0.5: - limit = int(z)-0.5 - else: - limit = int(z-0.5) - else: - limit = 0 - return limit - - # colormap truncation function - def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): - if isinstance(cmap, str): - cmap = plt.get_cmap(cmap) - new_cmap = mpl.colors.LinearSegmentedColormap.from_list( - 'trunc({n},{a:.2f},{b:.2f})'.format( - n=cmap.name, a=minval, b=maxval), - cmap(np.linspace(minval, maxval, n))) - return new_cmap - # patch - if not hasattr(Axis, "_get_coord_info_old"): - def _get_coord_info_new(self, renderer): - mins, maxs, centers, deltas, tc, highs = \ - self._get_coord_info_old(renderer) - mins += deltas/4 - maxs -= deltas/4 - return mins, maxs, centers, deltas, tc, highs - Axis._get_coord_info_old = Axis._get_coord_info - Axis._get_coord_info = _get_coord_info_new + _remove_margins() # default options default_opts = {'figsize': None, 'cmap': 'jet', 'cmap_min': 0., @@ -556,6 +604,7 @@ def _get_coord_info_new(self, renderer): 'proj_type': 'ortho', 'stick': False, 'cbar_pad': 0.04, 'cbarmax_to_zmax': False} + # update default_opts from input options if options: # check if keys in option dict are valid for key in options: @@ -598,7 +647,7 @@ def _get_coord_info_new(self, renderer): z_min = limits[0] z_max = limits[1] else: - limits = [lim_finder(min(dz)), lim_finder(max(dz))] + limits = [_limit_finder(min(dz)), _limit_finder(max(dz))] z_min = limits[0] z_max = limits[1] @@ -606,7 +655,7 @@ def _get_coord_info_new(self, renderer): norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) - cmap = truncate_colormap(cmap, cmap_min, cmap_max) + cmap = _truncate_colormap(cmap, cmap_min, cmap_max) # Spectral colors = cmap(norm(dz)) @@ -638,6 +687,7 @@ def _get_coord_info_new(self, renderer): ax.set_xticklabels(xlabels) else: ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) + ax.tick_params(axis='x', labelsize=14) ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) ax.set_xticklabels([str(i) for i in range(M.shape[0])]) @@ -672,19 +722,7 @@ def _get_coord_info_new(self, renderer): ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) # stick to xz and yz plane - if stick is True: - if 0 < azim <= 90 or -360 <= azim < -270 or azim == 0: - ax.set_ylim(1-0.55,) - ax.set_xlim(1-0.55,) - elif 90 < azim <= 180 or -270 <= azim < -180: - ax.set_ylim(1-0.55,) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 180 < azim <= 270 or -180 <= azim < -90: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 270 < azim <= 360 or -90 <= azim < 0: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) - ax.set_xlim(1-0.55,) + _stick_to_planes(stick, azim, ax, M, bars_spacing) # color axis if colorbar: From 413c4f50a44c777936ec1621ef81bac9e04cd36f Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 07:30:10 +0430 Subject: [PATCH 007/112] resolved code-climate issues - 6 --- qutip/visualization.py | 62 ++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 020a6805e2..1cdbd2d728 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -496,7 +496,6 @@ def _stick_to_planes(stick, azim, ax, M, bars_spacing): ax.set_xlim(1-0.55,) - def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, colorbar=True, fig=None, ax=None, options=None): """ @@ -614,23 +613,6 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # updating default options default_opts.update(options) - figsize = default_opts['figsize'] - cmap = default_opts['cmap'] - cmap_min = default_opts['cmap_min'] - cmap_max = default_opts['cmap_max'] - zticks = default_opts['zticks'] - bars_spacing = default_opts['bars_spacing'] - bars_alpha = default_opts['bars_alpha'] - bars_lw = default_opts['bars_lw'] - bars_edgecolor = default_opts['bars_edgecolor'] - shade = default_opts['shade'] - azim = default_opts['azim'] - elev = default_opts['elev'] - proj_type = default_opts['proj_type'] - stick = default_opts['stick'] - cbar_pad = default_opts['cbar_pad'] - cbarmax_to_zmax = default_opts['cbarmax_to_zmax'] - if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() @@ -640,7 +622,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, xpos = xpos.T.flatten() + 0.5 ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = (1-bars_spacing) * np.ones(n) + dx = dy = (1-default_opts['bars_spacing']) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: @@ -651,25 +633,31 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, z_min = limits[0] z_max = limits[1] - if cbarmax_to_zmax: + if default_opts['cbarmax_to_zmax']: norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) - cmap = _truncate_colormap(cmap, cmap_min, cmap_max) + cmap = _truncate_colormap(default_opts['cmap'], + default_opts['cmap_min'], + default_opts['cmap_max']) # Spectral colors = cmap(norm(dz)) if ax is None: - if figsize: - fig = plt.figure(figsize=figsize) + if default_opts['figsize']: + fig = plt.figure(figsize=default_opts['figsize']) else: fig = plt.figure() - ax = _axes3D(fig, azim=azim, elev=elev) - ax.set_proj_type(proj_type) + ax = _axes3D(fig, + azim=default_opts['azim'], + elev=default_opts['elev']) + ax.set_proj_type(default_opts['proj_type']) ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, - edgecolors=bars_edgecolor, linewidths=bars_lw, - alpha=bars_alpha, shade=shade) + edgecolors=default_opts['bars_edgecolor'], + linewidths=default_opts['bars_lw'], + alpha=default_opts['bars_alpha'], + shade=default_opts['shade']) # remove vertical lines on xz and yz plane ax.yaxis._axinfo["grid"]['linewidth'] = 0 ax.xaxis._axinfo["grid"]['linewidth'] = 0 @@ -678,7 +666,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax.set_title(title) # x axis - xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] + xtics = [x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) @@ -689,11 +677,11 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) + ax.set_xticks([x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[0])]) ax.set_xticklabels([str(i) for i in range(M.shape[0])]) # y axis - ytics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] + ytics = [x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) @@ -703,12 +691,12 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, else: ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) + ax.set_yticks([y+(1-(default_opts['bars_spacing']/2)) for y in range(M.shape[1])]) ax.set_yticklabels([str(i) for i in range(M.shape[1])]) # z axis ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) - # ax.set_zlim3d([min(z_min, 0), z_max]) + if z_min > 0 and z_max > 0: ax.set_zlim3d([0, z_max]) elif z_min < 0 and z_max < 0: @@ -716,17 +704,19 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, else: ax.set_zlim3d([z_min, z_max]) - if zticks: - ax.set_zticks(zticks) + if default_opts['zticks']: + ax.set_zticks(default_opts['zticks']) else: ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) # stick to xz and yz plane - _stick_to_planes(stick, azim, ax, M, bars_spacing) + _stick_to_planes(default_opts['stick'], default_opts['azim'], + ax, M, default_opts['bars_spacing']) # color axis if colorbar: - cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, pad=cbar_pad) + cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, + pad=default_opts['cbar_pad']) mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm) return fig, ax From 58751a86b3481c7450e2e7b9969dd6d1e2e20182 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 07:39:53 +0430 Subject: [PATCH 008/112] resolved code-climate issues - 7 --- qutip/visualization.py | 110 +++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 1cdbd2d728..e55326fcd0 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -496,6 +496,36 @@ def _stick_to_planes(stick, azim, ax, M, bars_spacing): ax.set_xlim(1-0.55,) +def _update_yaxis(bars_spacing, M, ax, ylabels): + ytics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] + ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) + if ylabels: + nylabels = len(ylabels) + if nylabels != len(ytics): + raise ValueError(f"got {nylabels} ylabels but needed {len(ytics)}") + ax.set_yticklabels(ylabels) + else: + ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) + ax.tick_params(axis='y', labelsize=14) + ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) + ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + +def _update_xaxis(bars_spacing, M, ax, xlabels): + xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] + ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) + if xlabels: + nxlabels = len(xlabels) + if nxlabels != len(xtics): + raise ValueError(f"got {nxlabels} xlabels but needed {len(xtics)}") + ax.set_xticklabels(xlabels) + else: + ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) + + ax.tick_params(axis='x', labelsize=14) + ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) + ax.set_xticklabels([str(i) for i in range(M.shape[0])]) + + def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, colorbar=True, fig=None, ax=None, options=None): """ @@ -613,6 +643,23 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # updating default options default_opts.update(options) + figsize = default_opts['figsize'] + cmap = default_opts['cmap'] + cmap_min = default_opts['cmap_min'] + cmap_max = default_opts['cmap_max'] + zticks = default_opts['zticks'] + bars_spacing = default_opts['bars_spacing'] + bars_alpha = default_opts['bars_alpha'] + bars_lw = default_opts['bars_lw'] + bars_edgecolor = default_opts['bars_edgecolor'] + shade = default_opts['shade'] + azim = default_opts['azim'] + elev = default_opts['elev'] + proj_type = default_opts['proj_type'] + stick = default_opts['stick'] + cbar_pad = default_opts['cbar_pad'] + cbarmax_to_zmax = default_opts['cbarmax_to_zmax'] + if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() @@ -622,7 +669,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, xpos = xpos.T.flatten() + 0.5 ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = (1-default_opts['bars_spacing']) * np.ones(n) + dx = dy = (1-bars_spacing) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: @@ -633,31 +680,25 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, z_min = limits[0] z_max = limits[1] - if default_opts['cbarmax_to_zmax']: + if cbarmax_to_zmax: norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) - cmap = _truncate_colormap(default_opts['cmap'], - default_opts['cmap_min'], - default_opts['cmap_max']) + cmap = _truncate_colormap(cmap, cmap_min, cmap_max) # Spectral colors = cmap(norm(dz)) if ax is None: - if default_opts['figsize']: - fig = plt.figure(figsize=default_opts['figsize']) + if figsize: + fig = plt.figure(figsize=figsize) else: fig = plt.figure() - ax = _axes3D(fig, - azim=default_opts['azim'], - elev=default_opts['elev']) - ax.set_proj_type(default_opts['proj_type']) + ax = _axes3D(fig, azim=azim, elev=elev) + ax.set_proj_type(proj_type) ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, - edgecolors=default_opts['bars_edgecolor'], - linewidths=default_opts['bars_lw'], - alpha=default_opts['bars_alpha'], - shade=default_opts['shade']) + edgecolors=bars_edgecolor, linewidths=bars_lw, + alpha=bars_alpha, shade=shade) # remove vertical lines on xz and yz plane ax.yaxis._axinfo["grid"]['linewidth'] = 0 ax.xaxis._axinfo["grid"]['linewidth'] = 0 @@ -666,37 +707,14 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax.set_title(title) # x axis - xtics = [x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[1])] - ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) - if xlabels: - nxlabels = len(xlabels) - if nxlabels != len(xtics): - raise ValueError(f"got {nxlabels} xlabels but needed {len(xtics)}") - ax.set_xticklabels(xlabels) - else: - ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) - - ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[0])]) - ax.set_xticklabels([str(i) for i in range(M.shape[0])]) + _update_xaxis(bars_spacing, M, ax, xlabels) # y axis - ytics = [x+(1-(default_opts['bars_spacing']/2)) for x in range(M.shape[1])] - ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) - if ylabels: - nylabels = len(ylabels) - if nylabels != len(ytics): - raise ValueError(f"got {nylabels} ylabels but needed {len(ytics)}") - ax.set_yticklabels(ylabels) - else: - ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) - ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1-(default_opts['bars_spacing']/2)) for y in range(M.shape[1])]) - ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + _update_yaxis(bars_spacing, M, ax, ylabels) # z axis ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) - + # ax.set_zlim3d([min(z_min, 0), z_max]) if z_min > 0 and z_max > 0: ax.set_zlim3d([0, z_max]) elif z_min < 0 and z_max < 0: @@ -704,19 +722,17 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, else: ax.set_zlim3d([z_min, z_max]) - if default_opts['zticks']: - ax.set_zticks(default_opts['zticks']) + if zticks: + ax.set_zticks(zticks) else: ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) # stick to xz and yz plane - _stick_to_planes(default_opts['stick'], default_opts['azim'], - ax, M, default_opts['bars_spacing']) + _stick_to_planes(stick, azim, ax, M, bars_spacing) # color axis if colorbar: - cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, - pad=default_opts['cbar_pad']) + cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, pad=cbar_pad) mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm) return fig, ax From 7b129925682d021c67cb5c30591971e921a1baae Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Wed, 16 Jun 2021 07:43:34 +0430 Subject: [PATCH 009/112] resolved code-climate issues - 8 --- qutip/visualization.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index e55326fcd0..4358f812e2 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -510,6 +510,7 @@ def _update_yaxis(bars_spacing, M, ax, ylabels): ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + def _update_xaxis(bars_spacing, M, ax, xlabels): xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) @@ -520,12 +521,27 @@ def _update_xaxis(bars_spacing, M, ax, xlabels): ax.set_xticklabels(xlabels) else: ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) - ax.tick_params(axis='x', labelsize=14) ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) ax.set_xticklabels([str(i) for i in range(M.shape[0])]) +def _update_zaxis(ax, z_min, z_max, zticks): + ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) + # ax.set_zlim3d([min(z_min, 0), z_max]) + if z_min > 0 and z_max > 0: + ax.set_zlim3d([0, z_max]) + elif z_min < 0 and z_max < 0: + ax.set_zlim3d([0, z_min]) + else: + ax.set_zlim3d([z_min, z_max]) + + if zticks: + ax.set_zticks(zticks) + else: + ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) + + def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, colorbar=True, fig=None, ax=None, options=None): """ @@ -713,19 +729,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, _update_yaxis(bars_spacing, M, ax, ylabels) # z axis - ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) - # ax.set_zlim3d([min(z_min, 0), z_max]) - if z_min > 0 and z_max > 0: - ax.set_zlim3d([0, z_max]) - elif z_min < 0 and z_max < 0: - ax.set_zlim3d([0, z_min]) - else: - ax.set_zlim3d([z_min, z_max]) - - if zticks: - ax.set_zticks(zticks) - else: - ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) + _update_zaxis(ax, z_min, z_max, zticks) # stick to xz and yz plane _stick_to_planes(stick, azim, ax, M, bars_spacing) From c7969efe713dcf0cb203ca2766913d4980521ae6 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Sun, 20 Jun 2021 00:42:09 +0430 Subject: [PATCH 010/112] fixed some issues and applied suggestions by Erric - #1 --- qutip/visualization.py | 134 ++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 70 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 4358f812e2..556ae45a94 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -418,11 +418,13 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): def _limit_finder(z): """Finds nearest proper 0.5 value This funtion is used when limits is not passed to matrix_histogtam + If z > 0 it returns the higher half value + If z < 0 it returns the lower half value examples: - if z=0.1 returns 0.0 + if z=+0.1 returns 0.5 if z=-0.1 returns -0.5 if z=-2.4 returns -2.5 - if z=3.8 returns 4 + if z=+3.8 returns 4.0 if z=-5.0 returns -5.0 Parameters @@ -435,17 +437,9 @@ def _limit_finder(z): nearest proper 0.5 value. """ if z > 0: - if int(z+0.5) < z < int(z)+0.5 or z % 1 == 0.5: - limit = int(z)+0.5 - else: - limit = int(z+0.5) - elif z < 0: - if int(z-0.5) > z > int(z)-0.5 or z % 1 == 0.5: - limit = int(z)-0.5 - else: - limit = int(z-0.5) + limit = np.ceil(z * 2) / 2 else: - limit = 0 + limit = np.floor(z * 2) / 2 return limit @@ -477,27 +471,31 @@ def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): return new_cmap -def _stick_to_planes(stick, azim, ax, M, bars_spacing): +def _stick_to_planes(stick, azim, ax, M, spacing): """Adjusts xlim and ylim in way that bars will Stick to xz and yz planes """ if stick is True: - if 0 < azim <= 90 or -360 <= azim < -270 or azim == 0: + azim = azim % 360 + if 0 <= azim <= 90: ax.set_ylim(1-0.55,) ax.set_xlim(1-0.55,) - elif 90 < azim <= 180 or -270 <= azim < -180: + elif 90 < azim <= 180: ax.set_ylim(1-0.55,) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 180 < azim <= 270 or -180 <= azim < -90: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 270 < azim <= 360 or -90 <= azim < 0: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) + ax.set_xlim(0, M.shape[0]+(.5-spacing)) + elif 180 < azim <= 270: + ax.set_ylim(0, M.shape[1]+(.5-spacing)) + ax.set_xlim(0, M.shape[0]+(.5-spacing)) + elif 270 < azim < 360: + ax.set_ylim(0, M.shape[1]+(.5-spacing)) ax.set_xlim(1-0.55,) -def _update_yaxis(bars_spacing, M, ax, ylabels): - ytics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] +def _update_yaxis(spacing, M, ax, ylabels): + """ + updates the y-axis + """ + ytics = [x+(1-(spacing/2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) @@ -506,13 +504,16 @@ def _update_yaxis(bars_spacing, M, ax, ylabels): ax.set_yticklabels(ylabels) else: ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) + ax.set_yticklabels([str(i) for i in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) - ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + ax.set_yticks([y+(1-(spacing/2)) for y in range(M.shape[1])]) -def _update_xaxis(bars_spacing, M, ax, xlabels): - xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] +def _update_xaxis(spacing, M, ax, xlabels): + """ + updates the x-axis + """ + xtics = [x+(1-(spacing/2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) @@ -521,12 +522,15 @@ def _update_xaxis(bars_spacing, M, ax, xlabels): ax.set_xticklabels(xlabels) else: ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) + ax.set_xticklabels([str(i) for i in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) - ax.set_xticklabels([str(i) for i in range(M.shape[0])]) + ax.set_xticks([x+(1-(spacing/2)) for x in range(M.shape[0])]) def _update_zaxis(ax, z_min, z_max, zticks): + """ + updates the z-axis + """ ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) # ax.set_zlim3d([min(z_min, 0), z_max]) if z_min > 0 and z_max > 0: @@ -613,7 +617,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, type of projection ('ortho' or 'persp') 'stick' : bool (default: False) - works for Azimuthal viewing angles between -360 and +360 + works for Azimuthal viewing angles 'cbar_pad' : float (default: 0.04) fraction of original axes between colorbar and new image axes @@ -651,30 +655,14 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # update default_opts from input options if options: - # check if keys in option dict are valid - for key in options: - if key not in default_opts: - raise ValueError(f"{key} is not a valid option") - - # updating default options - default_opts.update(options) - - figsize = default_opts['figsize'] - cmap = default_opts['cmap'] - cmap_min = default_opts['cmap_min'] - cmap_max = default_opts['cmap_max'] - zticks = default_opts['zticks'] - bars_spacing = default_opts['bars_spacing'] - bars_alpha = default_opts['bars_alpha'] - bars_lw = default_opts['bars_lw'] - bars_edgecolor = default_opts['bars_edgecolor'] - shade = default_opts['shade'] - azim = default_opts['azim'] - elev = default_opts['elev'] - proj_type = default_opts['proj_type'] - stick = default_opts['stick'] - cbar_pad = default_opts['cbar_pad'] - cbarmax_to_zmax = default_opts['cbarmax_to_zmax'] + # check if keys in options dict are valid + if set(options) - set(default_opts): + raise ValueError("invalid key(s) found in options: "\ + f"{', '.join(set(options) - set(default_opts))}") + else: + # updating default options + default_opts.update(options) + if isinstance(M, Qobj): # extract matrix data from Qobj @@ -685,7 +673,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, xpos = xpos.T.flatten() + 0.5 ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = (1-bars_spacing) * np.ones(n) + dx = dy = (1-default_opts['bars_spacing']) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: @@ -696,25 +684,28 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, z_min = limits[0] z_max = limits[1] - if cbarmax_to_zmax: + if default_opts['cbarmax_to_zmax']: norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) - cmap = _truncate_colormap(cmap, cmap_min, cmap_max) + cmap = _truncate_colormap(default_opts['cmap'], + default_opts['cmap_min'], + default_opts['cmap_max']) # Spectral colors = cmap(norm(dz)) if ax is None: - if figsize: - fig = plt.figure(figsize=figsize) - else: - fig = plt.figure() - ax = _axes3D(fig, azim=azim, elev=elev) - ax.set_proj_type(proj_type) + fig = plt.figure(figsize=default_opts['figsize']) + ax = _axes3D(fig, + azim=default_opts['azim'] % 360, + elev=default_opts['elev'] % 360) + ax.set_proj_type(default_opts['proj_type']) ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, - edgecolors=bars_edgecolor, linewidths=bars_lw, - alpha=bars_alpha, shade=shade) + edgecolors=default_opts['bars_edgecolor'], + linewidths=default_opts['bars_lw'], + alpha=default_opts['bars_alpha'], + shade=default_opts['shade']) # remove vertical lines on xz and yz plane ax.yaxis._axinfo["grid"]['linewidth'] = 0 ax.xaxis._axinfo["grid"]['linewidth'] = 0 @@ -723,20 +714,23 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax.set_title(title) # x axis - _update_xaxis(bars_spacing, M, ax, xlabels) + _update_xaxis(default_opts['bars_spacing'], M, ax, xlabels) # y axis - _update_yaxis(bars_spacing, M, ax, ylabels) + _update_yaxis(default_opts['bars_spacing'], M, ax, ylabels) # z axis - _update_zaxis(ax, z_min, z_max, zticks) + _update_zaxis(ax, z_min, z_max, default_opts['zticks']) # stick to xz and yz plane - _stick_to_planes(stick, azim, ax, M, bars_spacing) + _stick_to_planes(default_opts['stick'], + default_opts['azim'],ax, M, + default_opts['bars_spacing']) # color axis if colorbar: - cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, pad=cbar_pad) + cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, + pad=default_opts['cbar_pad']) mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm) return fig, ax From 60902164d59446bf33aac544e650564860ea9f03 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Sun, 20 Jun 2021 00:42:09 +0430 Subject: [PATCH 011/112] fixed some issues and applied suggestions by Erric - #1 --- qutip/visualization.py | 134 ++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 70 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 4358f812e2..556ae45a94 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -418,11 +418,13 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): def _limit_finder(z): """Finds nearest proper 0.5 value This funtion is used when limits is not passed to matrix_histogtam + If z > 0 it returns the higher half value + If z < 0 it returns the lower half value examples: - if z=0.1 returns 0.0 + if z=+0.1 returns 0.5 if z=-0.1 returns -0.5 if z=-2.4 returns -2.5 - if z=3.8 returns 4 + if z=+3.8 returns 4.0 if z=-5.0 returns -5.0 Parameters @@ -435,17 +437,9 @@ def _limit_finder(z): nearest proper 0.5 value. """ if z > 0: - if int(z+0.5) < z < int(z)+0.5 or z % 1 == 0.5: - limit = int(z)+0.5 - else: - limit = int(z+0.5) - elif z < 0: - if int(z-0.5) > z > int(z)-0.5 or z % 1 == 0.5: - limit = int(z)-0.5 - else: - limit = int(z-0.5) + limit = np.ceil(z * 2) / 2 else: - limit = 0 + limit = np.floor(z * 2) / 2 return limit @@ -477,27 +471,31 @@ def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): return new_cmap -def _stick_to_planes(stick, azim, ax, M, bars_spacing): +def _stick_to_planes(stick, azim, ax, M, spacing): """Adjusts xlim and ylim in way that bars will Stick to xz and yz planes """ if stick is True: - if 0 < azim <= 90 or -360 <= azim < -270 or azim == 0: + azim = azim % 360 + if 0 <= azim <= 90: ax.set_ylim(1-0.55,) ax.set_xlim(1-0.55,) - elif 90 < azim <= 180 or -270 <= azim < -180: + elif 90 < azim <= 180: ax.set_ylim(1-0.55,) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 180 < azim <= 270 or -180 <= azim < -90: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) - ax.set_xlim(0, M.shape[0]+(.5-bars_spacing)) - elif 270 < azim <= 360 or -90 <= azim < 0: - ax.set_ylim(0, M.shape[1]+(.5-bars_spacing)) + ax.set_xlim(0, M.shape[0]+(.5-spacing)) + elif 180 < azim <= 270: + ax.set_ylim(0, M.shape[1]+(.5-spacing)) + ax.set_xlim(0, M.shape[0]+(.5-spacing)) + elif 270 < azim < 360: + ax.set_ylim(0, M.shape[1]+(.5-spacing)) ax.set_xlim(1-0.55,) -def _update_yaxis(bars_spacing, M, ax, ylabels): - ytics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] +def _update_yaxis(spacing, M, ax, ylabels): + """ + updates the y-axis + """ + ytics = [x+(1-(spacing/2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) @@ -506,13 +504,16 @@ def _update_yaxis(bars_spacing, M, ax, ylabels): ax.set_yticklabels(ylabels) else: ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) + ax.set_yticklabels([str(i) for i in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1-(bars_spacing/2)) for y in range(M.shape[1])]) - ax.set_yticklabels([str(i) for i in range(M.shape[1])]) + ax.set_yticks([y+(1-(spacing/2)) for y in range(M.shape[1])]) -def _update_xaxis(bars_spacing, M, ax, xlabels): - xtics = [x+(1-(bars_spacing/2)) for x in range(M.shape[1])] +def _update_xaxis(spacing, M, ax, xlabels): + """ + updates the x-axis + """ + xtics = [x+(1-(spacing/2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) @@ -521,12 +522,15 @@ def _update_xaxis(bars_spacing, M, ax, xlabels): ax.set_xticklabels(xlabels) else: ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) + ax.set_xticklabels([str(i) for i in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1-(bars_spacing/2)) for x in range(M.shape[0])]) - ax.set_xticklabels([str(i) for i in range(M.shape[0])]) + ax.set_xticks([x+(1-(spacing/2)) for x in range(M.shape[0])]) def _update_zaxis(ax, z_min, z_max, zticks): + """ + updates the z-axis + """ ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) # ax.set_zlim3d([min(z_min, 0), z_max]) if z_min > 0 and z_max > 0: @@ -613,7 +617,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, type of projection ('ortho' or 'persp') 'stick' : bool (default: False) - works for Azimuthal viewing angles between -360 and +360 + works for Azimuthal viewing angles 'cbar_pad' : float (default: 0.04) fraction of original axes between colorbar and new image axes @@ -651,30 +655,14 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # update default_opts from input options if options: - # check if keys in option dict are valid - for key in options: - if key not in default_opts: - raise ValueError(f"{key} is not a valid option") - - # updating default options - default_opts.update(options) - - figsize = default_opts['figsize'] - cmap = default_opts['cmap'] - cmap_min = default_opts['cmap_min'] - cmap_max = default_opts['cmap_max'] - zticks = default_opts['zticks'] - bars_spacing = default_opts['bars_spacing'] - bars_alpha = default_opts['bars_alpha'] - bars_lw = default_opts['bars_lw'] - bars_edgecolor = default_opts['bars_edgecolor'] - shade = default_opts['shade'] - azim = default_opts['azim'] - elev = default_opts['elev'] - proj_type = default_opts['proj_type'] - stick = default_opts['stick'] - cbar_pad = default_opts['cbar_pad'] - cbarmax_to_zmax = default_opts['cbarmax_to_zmax'] + # check if keys in options dict are valid + if set(options) - set(default_opts): + raise ValueError("invalid key(s) found in options: "\ + f"{', '.join(set(options) - set(default_opts))}") + else: + # updating default options + default_opts.update(options) + if isinstance(M, Qobj): # extract matrix data from Qobj @@ -685,7 +673,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, xpos = xpos.T.flatten() + 0.5 ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = (1-bars_spacing) * np.ones(n) + dx = dy = (1-default_opts['bars_spacing']) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: @@ -696,25 +684,28 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, z_min = limits[0] z_max = limits[1] - if cbarmax_to_zmax: + if default_opts['cbarmax_to_zmax']: norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) - cmap = _truncate_colormap(cmap, cmap_min, cmap_max) + cmap = _truncate_colormap(default_opts['cmap'], + default_opts['cmap_min'], + default_opts['cmap_max']) # Spectral colors = cmap(norm(dz)) if ax is None: - if figsize: - fig = plt.figure(figsize=figsize) - else: - fig = plt.figure() - ax = _axes3D(fig, azim=azim, elev=elev) - ax.set_proj_type(proj_type) + fig = plt.figure(figsize=default_opts['figsize']) + ax = _axes3D(fig, + azim=default_opts['azim'] % 360, + elev=default_opts['elev'] % 360) + ax.set_proj_type(default_opts['proj_type']) ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, - edgecolors=bars_edgecolor, linewidths=bars_lw, - alpha=bars_alpha, shade=shade) + edgecolors=default_opts['bars_edgecolor'], + linewidths=default_opts['bars_lw'], + alpha=default_opts['bars_alpha'], + shade=default_opts['shade']) # remove vertical lines on xz and yz plane ax.yaxis._axinfo["grid"]['linewidth'] = 0 ax.xaxis._axinfo["grid"]['linewidth'] = 0 @@ -723,20 +714,23 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax.set_title(title) # x axis - _update_xaxis(bars_spacing, M, ax, xlabels) + _update_xaxis(default_opts['bars_spacing'], M, ax, xlabels) # y axis - _update_yaxis(bars_spacing, M, ax, ylabels) + _update_yaxis(default_opts['bars_spacing'], M, ax, ylabels) # z axis - _update_zaxis(ax, z_min, z_max, zticks) + _update_zaxis(ax, z_min, z_max, default_opts['zticks']) # stick to xz and yz plane - _stick_to_planes(stick, azim, ax, M, bars_spacing) + _stick_to_planes(default_opts['stick'], + default_opts['azim'],ax, M, + default_opts['bars_spacing']) # color axis if colorbar: - cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, pad=cbar_pad) + cax, kw = mpl.colorbar.make_axes(ax, shrink=.75, + pad=default_opts['cbar_pad']) mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm) return fig, ax From 90702a8090fa0e9e593e5a3040e73a7c01dd05d1 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Sun, 20 Jun 2021 00:56:26 +0430 Subject: [PATCH 012/112] resolved code-climate issues - 9 --- qutip/visualization.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 556ae45a94..7f591426a8 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -657,13 +657,12 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, if options: # check if keys in options dict are valid if set(options) - set(default_opts): - raise ValueError("invalid key(s) found in options: "\ + raise ValueError("invalid key(s) found in options: "+ f"{', '.join(set(options) - set(default_opts))}") else: # updating default options default_opts.update(options) - if isinstance(M, Qobj): # extract matrix data from Qobj M = M.full() @@ -724,7 +723,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # stick to xz and yz plane _stick_to_planes(default_opts['stick'], - default_opts['azim'],ax, M, + default_opts['azim'], ax, M, default_opts['bars_spacing']) # color axis From 173348f2128356ab3b6e265f6ba38d4c984f2c0e Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Sun, 20 Jun 2021 21:50:43 +0430 Subject: [PATCH 013/112] monkey patching safely to remove margins --- qutip/visualization.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 7f591426a8..05de4515ca 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -443,19 +443,20 @@ def _limit_finder(z): return limit -def _remove_margins(): +def _remove_margins(axis): """ Removes margins about z=0 and improves the style + by monkey patching """ - if not hasattr(Axis, "_get_coord_info_old"): - def _get_coord_info_new(self, renderer): - mins, maxs, centers, deltas, tc, highs = \ - self._get_coord_info_old(renderer) - mins += deltas/4 - maxs -= deltas/4 - return mins, maxs, centers, deltas, tc, highs - Axis._get_coord_info_old = Axis._get_coord_info - Axis._get_coord_info = _get_coord_info_new + def _get_coord_info_new(renderer): + mins, maxs, centers, deltas, tc, highs = \ + _get_coord_info_old(renderer) + mins += deltas / 4 + maxs -= deltas / 4 + return mins, maxs, centers, deltas, tc, highs + + _get_coord_info_old = axis._get_coord_info + axis._get_coord_info = _get_coord_info_new def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): @@ -642,9 +643,6 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, """ - # patch - _remove_margins() - # default options default_opts = {'figsize': None, 'cmap': 'jet', 'cmap_min': 0., 'cmap_max': 1., 'zticks': None, 'bars_spacing': 0.1, @@ -657,7 +655,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, if options: # check if keys in options dict are valid if set(options) - set(default_opts): - raise ValueError("invalid key(s) found in options: "+ + raise ValueError("invalid key(s) found in options: " + \ f"{', '.join(set(options) - set(default_opts))}") else: # updating default options @@ -732,6 +730,11 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, pad=default_opts['cbar_pad']) mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm) + # removing margins + _remove_margins(ax.xaxis) + _remove_margins(ax.yaxis) + _remove_margins(ax.zaxis) + return fig, ax From 21d53fc0e4e73fb2b9c5d0b904200e55e045d591 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Sun, 20 Jun 2021 21:58:38 +0430 Subject: [PATCH 014/112] resolved code-climate issues - 10 --- qutip/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 05de4515ca..7d4e27549c 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -655,7 +655,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, if options: # check if keys in options dict are valid if set(options) - set(default_opts): - raise ValueError("invalid key(s) found in options: " + \ + raise ValueError("invalid key(s) found in options: " f"{', '.join(set(options) - set(default_opts))}") else: # updating default options From da35d1df722d75f9a519b4bf9512ec774ec3b813 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Tue, 22 Jun 2021 17:09:31 +0430 Subject: [PATCH 015/112] improved readability - fixed some issues --- qutip/visualization.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 7d4e27549c..0d84fd5fb6 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -479,24 +479,24 @@ def _stick_to_planes(stick, azim, ax, M, spacing): if stick is True: azim = azim % 360 if 0 <= azim <= 90: - ax.set_ylim(1-0.55,) - ax.set_xlim(1-0.55,) + ax.set_ylim(1 - .5,) + ax.set_xlim(1 - .5,) elif 90 < azim <= 180: - ax.set_ylim(1-0.55,) - ax.set_xlim(0, M.shape[0]+(.5-spacing)) + ax.set_ylim(1 - .5,) + ax.set_xlim(0, M.shape[0]+(.5 - spacing)) elif 180 < azim <= 270: - ax.set_ylim(0, M.shape[1]+(.5-spacing)) - ax.set_xlim(0, M.shape[0]+(.5-spacing)) + ax.set_ylim(0, M.shape[1]+(.5 - spacing)) + ax.set_xlim(0, M.shape[0]+(.5 - spacing)) elif 270 < azim < 360: - ax.set_ylim(0, M.shape[1]+(.5-spacing)) - ax.set_xlim(1-0.55,) + ax.set_ylim(0, M.shape[1]+(.5 - spacing)) + ax.set_xlim(1 - .5,) def _update_yaxis(spacing, M, ax, ylabels): """ updates the y-axis """ - ytics = [x+(1-(spacing/2)) for x in range(M.shape[1])] + ytics = [x+(1 - (spacing / 2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) @@ -504,17 +504,17 @@ def _update_yaxis(spacing, M, ax, ylabels): raise ValueError(f"got {nylabels} ylabels but needed {len(ytics)}") ax.set_yticklabels(ylabels) else: - ax.set_yticklabels([str(y+1) for y in range(M.shape[1])]) + ax.set_yticklabels([str(y + 1) for y in range(M.shape[1])]) ax.set_yticklabels([str(i) for i in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1-(spacing/2)) for y in range(M.shape[1])]) + ax.set_yticks([y+(1 - (spacing / 2)) for y in range(M.shape[1])]) def _update_xaxis(spacing, M, ax, xlabels): """ updates the x-axis """ - xtics = [x+(1-(spacing/2)) for x in range(M.shape[1])] + xtics = [x+(1 - (spacing / 2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) @@ -522,10 +522,10 @@ def _update_xaxis(spacing, M, ax, xlabels): raise ValueError(f"got {nxlabels} xlabels but needed {len(xtics)}") ax.set_xticklabels(xlabels) else: - ax.set_xticklabels([str(x+1) for x in range(M.shape[0])]) + ax.set_xticklabels([str(x + 1) for x in range(M.shape[0])]) ax.set_xticklabels([str(i) for i in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1-(spacing/2)) for x in range(M.shape[0])]) + ax.set_xticks([x+(1 - (spacing / 2)) for x in range(M.shape[0])]) def _update_zaxis(ax, z_min, z_max, zticks): @@ -544,7 +544,8 @@ def _update_zaxis(ax, z_min, z_max, zticks): if zticks: ax.set_zticks(zticks) else: - ax.set_zticks([z_min+0.5*i for i in range(int((z_max-z_min)/0.5)+1)]) + ax.set_zticks([z_min + 0.5 * i for i in + range(int((z_max - z_min) / 0.5) + 1)]) def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, @@ -647,7 +648,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, default_opts = {'figsize': None, 'cmap': 'jet', 'cmap_min': 0., 'cmap_max': 1., 'zticks': None, 'bars_spacing': 0.1, 'bars_alpha': 1., 'bars_lw': 0.5, 'bars_edgecolor': 'k', - 'shade': False, 'azim': 65, 'elev': 30, + 'shade': False, 'azim': -35, 'elev': 35, 'proj_type': 'ortho', 'stick': False, 'cbar_pad': 0.04, 'cbarmax_to_zmax': False} @@ -696,7 +697,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, ax = _axes3D(fig, azim=default_opts['azim'] % 360, elev=default_opts['elev'] % 360) - ax.set_proj_type(default_opts['proj_type']) + ax.set_proj_type(default_opts['proj_type']) ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, edgecolors=default_opts['bars_edgecolor'], From 35918be60037ebb7ee6e7751c128fd50236b9727 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Tue, 22 Jun 2021 17:27:32 +0430 Subject: [PATCH 016/112] resolved code-climate issues - 11 --- qutip/visualization.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 0d84fd5fb6..521aeee9dc 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -544,7 +544,7 @@ def _update_zaxis(ax, z_min, z_max, zticks): if zticks: ax.set_zticks(zticks) else: - ax.set_zticks([z_min + 0.5 * i for i in + ax.set_zticks([z_min + 0.5 * i for i in range(int((z_max - z_min) / 0.5) + 1)]) @@ -619,7 +619,9 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, type of projection ('ortho' or 'persp') 'stick' : bool (default: False) - works for Azimuthal viewing angles + changes xlim and ylim in a way that bars next to the + xz and yz planes will stick to those planes + works if ax is not passed to the function 'cbar_pad' : float (default: 0.04) fraction of original axes between colorbar and new image axes From 0ab427ed8bb6f2e8127842a11e5440a2ab4f49c3 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Thu, 24 Jun 2021 02:06:00 +0430 Subject: [PATCH 017/112] imporoved readability - changed some doc-strings --- qutip/visualization.py | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 521aeee9dc..aafb70b932 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -416,16 +416,16 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): def _limit_finder(z): - """Finds nearest proper 0.5 value - This funtion is used when limits is not passed to matrix_histogtam + """finds nearest proper 0.5 value + funtion used when limits is not passed to matrix_histogtam If z > 0 it returns the higher half value If z < 0 it returns the lower half value examples: - if z=+0.1 returns 0.5 - if z=-0.1 returns -0.5 - if z=-2.4 returns -2.5 - if z=+3.8 returns 4.0 - if z=-5.0 returns -5.0 + if z = +0.1 returns +0.5 + if z = -0.1 returns -0.5 + if z = -2.4 returns -2.5 + if z = +3.8 returns +4.0 + if z = -5.0 returns -5.0 Parameters ---------- @@ -445,7 +445,7 @@ def _limit_finder(z): def _remove_margins(axis): """ - Removes margins about z=0 and improves the style + removes margins about z = 0 and improves the style by monkey patching """ def _get_coord_info_new(renderer): @@ -461,7 +461,7 @@ def _get_coord_info_new(renderer): def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): """ - Truncates portion of a colormap and returns the new one + truncates portion of a colormap and returns the new one """ if isinstance(cmap, str): cmap = plt.get_cmap(cmap) @@ -473,7 +473,7 @@ def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): def _stick_to_planes(stick, azim, ax, M, spacing): - """Adjusts xlim and ylim in way that bars will + """adjusts xlim and ylim in way that bars will Stick to xz and yz planes """ if stick is True: @@ -483,12 +483,12 @@ def _stick_to_planes(stick, azim, ax, M, spacing): ax.set_xlim(1 - .5,) elif 90 < azim <= 180: ax.set_ylim(1 - .5,) - ax.set_xlim(0, M.shape[0]+(.5 - spacing)) + ax.set_xlim(0, M.shape[0] + (.5 - spacing)) elif 180 < azim <= 270: - ax.set_ylim(0, M.shape[1]+(.5 - spacing)) - ax.set_xlim(0, M.shape[0]+(.5 - spacing)) + ax.set_ylim(0, M.shape[1] + (.5 - spacing)) + ax.set_xlim(0, M.shape[0] + (.5 - spacing)) elif 270 < azim < 360: - ax.set_ylim(0, M.shape[1]+(.5 - spacing)) + ax.set_ylim(0, M.shape[1] + (.5 - spacing)) ax.set_xlim(1 - .5,) @@ -496,7 +496,7 @@ def _update_yaxis(spacing, M, ax, ylabels): """ updates the y-axis """ - ytics = [x+(1 - (spacing / 2)) for x in range(M.shape[1])] + ytics = [x + (1 - (spacing / 2)) for x in range(M.shape[1])] ax.axes.w_yaxis.set_major_locator(plt.FixedLocator(ytics)) if ylabels: nylabels = len(ylabels) @@ -507,14 +507,14 @@ def _update_yaxis(spacing, M, ax, ylabels): ax.set_yticklabels([str(y + 1) for y in range(M.shape[1])]) ax.set_yticklabels([str(i) for i in range(M.shape[1])]) ax.tick_params(axis='y', labelsize=14) - ax.set_yticks([y+(1 - (spacing / 2)) for y in range(M.shape[1])]) + ax.set_yticks([y + (1 - (spacing / 2)) for y in range(M.shape[1])]) def _update_xaxis(spacing, M, ax, xlabels): """ updates the x-axis """ - xtics = [x+(1 - (spacing / 2)) for x in range(M.shape[1])] + xtics = [x + (1 - (spacing / 2)) for x in range(M.shape[1])] ax.axes.w_xaxis.set_major_locator(plt.FixedLocator(xtics)) if xlabels: nxlabels = len(xlabels) @@ -525,7 +525,7 @@ def _update_xaxis(spacing, M, ax, xlabels): ax.set_xticklabels([str(x + 1) for x in range(M.shape[0])]) ax.set_xticklabels([str(i) for i in range(M.shape[0])]) ax.tick_params(axis='x', labelsize=14) - ax.set_xticks([x+(1 - (spacing / 2)) for x in range(M.shape[0])]) + ax.set_xticks([x + (1 - (spacing / 2)) for x in range(M.shape[0])]) def _update_zaxis(ax, z_min, z_max, zticks): @@ -606,20 +606,20 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' 'shade' : bool (default: True) - when True, this shades the dark sides of the bars (relative - to the plot's source of light). + when True, this shades dark sides of the bars (relative + to plot's source of light). 'azim' : float - Azimuthal viewing angle. + azimuthal viewing angle. 'elev' : float - Elevation viewing angle. + elevation viewing angle. - 'proj_type' : string (default: 'ortho') + 'proj_type' : string (default: 'ortho' if ax is not passed) type of projection ('ortho' or 'persp') 'stick' : bool (default: False) - changes xlim and ylim in a way that bars next to the + changes xlim and ylim in a way that bars next to xz and yz planes will stick to those planes works if ax is not passed to the function @@ -648,7 +648,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, # default options default_opts = {'figsize': None, 'cmap': 'jet', 'cmap_min': 0., - 'cmap_max': 1., 'zticks': None, 'bars_spacing': 0.1, + 'cmap_max': 1., 'zticks': None, 'bars_spacing': 0.2, 'bars_alpha': 1., 'bars_lw': 0.5, 'bars_edgecolor': 'k', 'shade': False, 'azim': -35, 'elev': 35, 'proj_type': 'ortho', 'stick': False, @@ -673,7 +673,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, xpos = xpos.T.flatten() + 0.5 ypos = ypos.T.flatten() + 0.5 zpos = np.zeros(n) - dx = dy = (1-default_opts['bars_spacing']) * np.ones(n) + dx = dy = (1 - default_opts['bars_spacing']) * np.ones(n) dz = np.real(M.flatten()) if isinstance(limits, list) and len(limits) == 2: From 2383e5e76a587da5a805f410fd0bcc80295b6534 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Tue, 29 Jun 2021 22:01:59 +0430 Subject: [PATCH 018/112] doc-strings updated (suggested by hodgestar) --- qutip/visualization.py | 43 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index aafb70b932..9686f0a6b4 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -67,7 +67,6 @@ import matplotlib as mpl from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D - from mpl_toolkits.mplot3d.axis3d import Axis # Define a custom _axes3D function based on the matplotlib version. # The auto_add_to_figure keyword is new for matplotlib>=3.4. @@ -577,30 +576,34 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, show colorbar options : dict - dictionary containing extra options - all keys are of type `str` and values have different types - key:value pairs should be as follows: + A dictionary containing extra options for the plot. + The names (keys) and values of the options are + described below: 'zticks' : list of numbers - list of z-axis ticks location + A list of z-axis tick locations. 'cmap' : string (default: 'jet') - colormap name + The name of the color map to use. 'cmap_min' : float (default: 0.0) - colormap truncation minimum, a value in range 0-1 + The lower bound to truncate the color map at. + A value in range 0 - 1. The default, 0, leaves the lower + bound of the map unchanged. 'cmap_max' : float (default: 1.0) - colormap truncation maximum, a value in range 0-1 + The upper bound to truncate the color map at. + A value in range 0 - 1. The default, 1, leaves the upper + bound of the map unchanged. 'bars_spacing' : float (default: 0.1) - spacing between bars + spacing between bars. 'bars_alpha' : float (default: 1.) - transparency of bars, should be in range 0-1 + transparency of bars, should be in range 0 - 1 'bars_lw' : float (default: 0.5) - linewidth of bars' edges + linewidth of bars' edges. 'bars_edgecolor' : color (default: 'k') color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' @@ -616,22 +619,24 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, elevation viewing angle. 'proj_type' : string (default: 'ortho' if ax is not passed) - type of projection ('ortho' or 'persp') + The type of projection ('ortho' or 'persp') 'stick' : bool (default: False) - changes xlim and ylim in a way that bars next to - xz and yz planes will stick to those planes - works if ax is not passed to the function + Changes xlim and ylim in such a way that bars next to + XZ and YZ planes will stick to those planes. + This option has no effect if ``ax`` is passed as a parameter. 'cbar_pad' : float (default: 0.04) - fraction of original axes between colorbar and new image axes - (padding between 3D figure and colorbar). + The fraction of the original axes between the colorbar + and the new image axes. + (i.e. the padding between the 3D figure and the colorbar). 'cbarmax_to_zmax' : bool (default: False) - set color of maximum z-value to maximum color of colorbar + Whether to set the color of maximum z-value to the maximum color + in the colorbar (True) or not (False). 'figsize' : tuple of two numbers - size of the figure + The size of the figure. Returns : ------- From 3059786a56061682c444dc3dc5ce5516f2562f9e Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Tue, 29 Jun 2021 22:06:57 +0430 Subject: [PATCH 019/112] removed a trailing whitespace --- qutip/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 9686f0a6b4..83b9dd6e4c 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -577,7 +577,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, options : dict A dictionary containing extra options for the plot. - The names (keys) and values of the options are + The names (keys) and values of the options are described below: 'zticks' : list of numbers From 30c7f9d53ba80a82d95f303bfb839d743c6cbfbf Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Thu, 1 Jul 2021 12:08:39 +0430 Subject: [PATCH 020/112] Apply suggestions from code review Co-authored-by: Simon Cross --- qutip/visualization.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 83b9dd6e4c..b597e66da8 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -606,17 +606,17 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, linewidth of bars' edges. 'bars_edgecolor' : color (default: 'k') - color of bars' edges, examples: 'k', (0.1, 0.2, 0.5), '#0f0f0f80' + The colors of the bars' edges. For example: 'k', (0.1, 0.2, 0.5) or '#0f0f0f80'. 'shade' : bool (default: True) - when True, this shades dark sides of the bars (relative - to plot's source of light). + Whether to shade the dark sides of the bars (True) or not (False). The shading is relative + to plot's source of light. 'azim' : float - azimuthal viewing angle. + The azimuthal viewing angle. 'elev' : float - elevation viewing angle. + The elevation viewing angle. 'proj_type' : string (default: 'ortho' if ax is not passed) The type of projection ('ortho' or 'persp') From 4dcdf609a80e8e27c8875b0e8c05e19beb98914f Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Thu, 1 Jul 2021 12:19:17 +0430 Subject: [PATCH 021/112] resolved code-climate issues --- qutip/visualization.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index b597e66da8..37407bee0e 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -606,11 +606,12 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, linewidth of bars' edges. 'bars_edgecolor' : color (default: 'k') - The colors of the bars' edges. For example: 'k', (0.1, 0.2, 0.5) or '#0f0f0f80'. + The colors of the bars' edges. + Examples: 'k', (0.1, 0.2, 0.5) or '#0f0f0f80'. 'shade' : bool (default: True) - Whether to shade the dark sides of the bars (True) or not (False). The shading is relative - to plot's source of light. + Whether to shade the dark sides of the bars (True) or not (False). + The shading is relative to plot's source of light. 'azim' : float The azimuthal viewing angle. From 3bf0afb12c2de9d366edab469ef5ac6589ec5072 Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Fri, 2 Jul 2021 02:13:36 +0430 Subject: [PATCH 022/112] removed _limit_finder - updated some doc-strings --- qutip/visualization.py | 49 +++++++++--------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 37407bee0e..81c018cc60 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -414,34 +414,6 @@ def sphereplot(theta, phi, values, fig=None, ax=None, save=False): return fig, ax -def _limit_finder(z): - """finds nearest proper 0.5 value - funtion used when limits is not passed to matrix_histogtam - If z > 0 it returns the higher half value - If z < 0 it returns the lower half value - examples: - if z = +0.1 returns +0.5 - if z = -0.1 returns -0.5 - if z = -2.4 returns -2.5 - if z = +3.8 returns +4.0 - if z = -5.0 returns -5.0 - - Parameters - ---------- - z : float or int - - Returns - ------- - limit : float - nearest proper 0.5 value. - """ - if z > 0: - limit = np.ceil(z * 2) / 2 - else: - limit = np.floor(z * 2) / 2 - return limit - - def _remove_margins(axis): """ removes margins about z = 0 and improves the style @@ -542,9 +514,6 @@ def _update_zaxis(ax, z_min, z_max, zticks): if zticks: ax.set_zticks(zticks) - else: - ax.set_zticks([z_min + 0.5 * i for i in - range(int((z_max - z_min) / 0.5) + 1)]) def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, @@ -632,9 +601,9 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, and the new image axes. (i.e. the padding between the 3D figure and the colorbar). - 'cbarmax_to_zmax' : bool (default: False) - Whether to set the color of maximum z-value to the maximum color - in the colorbar (True) or not (False). + 'cbar_to_z' : bool (default: False) + Whether to set the color of maximum and minimum z-values to the + maximum and minimum colors in the colorbar (True) or not (False). 'figsize' : tuple of two numbers The size of the figure. @@ -658,7 +627,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, 'bars_alpha': 1., 'bars_lw': 0.5, 'bars_edgecolor': 'k', 'shade': False, 'azim': -35, 'elev': 35, 'proj_type': 'ortho', 'stick': False, - 'cbar_pad': 0.04, 'cbarmax_to_zmax': False} + 'cbar_pad': 0.04, 'cbar_to_z': False} # update default_opts from input options if options: @@ -686,11 +655,13 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, z_min = limits[0] z_max = limits[1] else: - limits = [_limit_finder(min(dz)), _limit_finder(max(dz))] - z_min = limits[0] - z_max = limits[1] + z_min = min(dz) + z_max = max(dz) + if z_min == z_max: + z_min -= 0.1 + z_max += 0.1 - if default_opts['cbarmax_to_zmax']: + if default_opts['cbar_to_z']: norm = mpl.colors.Normalize(min(dz), max(dz)) else: norm = mpl.colors.Normalize(z_min, z_max) From 1a877bc1d05c56ac5e6187173cb81116976ea89c Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Fri, 2 Jul 2021 03:15:52 +0430 Subject: [PATCH 023/112] updated _update_zaxis --- qutip/visualization.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 81c018cc60..34686e6b5f 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -504,16 +504,9 @@ def _update_zaxis(ax, z_min, z_max, zticks): updates the z-axis """ ax.axes.w_zaxis.set_major_locator(plt.IndexLocator(1, 0.5)) - # ax.set_zlim3d([min(z_min, 0), z_max]) - if z_min > 0 and z_max > 0: - ax.set_zlim3d([0, z_max]) - elif z_min < 0 and z_max < 0: - ax.set_zlim3d([0, z_min]) - else: - ax.set_zlim3d([z_min, z_max]) - - if zticks: + if isinstance(zticks, list): ax.set_zticks(zticks) + ax.set_zlim3d([min(z_min, 0), z_max]) def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, From 6a2a63c5a306ebe7341c2aee6398d72eba428f5d Mon Sep 17 00:00:00 2001 From: Mehdi Aslani Date: Fri, 2 Jul 2021 11:21:24 +0430 Subject: [PATCH 024/112] added error message for options instance checking --- qutip/visualization.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index 34686e6b5f..3ba1b2422c 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -623,7 +623,7 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, 'cbar_pad': 0.04, 'cbar_to_z': False} # update default_opts from input options - if options: + if isinstance(options, dict): # check if keys in options dict are valid if set(options) - set(default_opts): raise ValueError("invalid key(s) found in options: " @@ -631,6 +631,8 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, else: # updating default options default_opts.update(options) + else: + raise ValueError("options must be a dictionary") if isinstance(M, Qobj): # extract matrix data from Qobj @@ -661,7 +663,6 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, cmap = _truncate_colormap(default_opts['cmap'], default_opts['cmap_min'], default_opts['cmap_max']) - # Spectral colors = cmap(norm(dz)) if ax is None: From b24b48854866b18f5bd59e1471c86ffd790b0b54 Mon Sep 17 00:00:00 2001 From: Johannes Feist Date: Tue, 13 Jul 2021 12:19:56 +0200 Subject: [PATCH 025/112] speed up and simplify state_number_enumerate, state_number_index, state_index_number, state_number_qobj, and enr_state_dictionaries Note that there are some interface changes: - state_number_enumerate and state_index_number now always return tuples (before, state_number_enumerate returned arrays for excitations==None and tuples otherwise, and state_index_number returned a list) - enr_state_dictionaries now returns a dictionary and a list instead of two dictionaries (before, the idx2state was a dictionary with keys 0 to nstates-1, which behaves essentially like a list) --- qutip/operators.py | 2 +- qutip/states.py | 108 ++++++++++++++++++--------------------------- 2 files changed, 45 insertions(+), 65 deletions(-) diff --git a/qutip/operators.py b/qutip/operators.py index 7d787a176c..7d5e011ce6 100644 --- a/qutip/operators.py +++ b/qutip/operators.py @@ -859,7 +859,7 @@ def enr_destroy(dims, excitations): a_ops = [sp.lil_matrix((nstates, nstates), dtype=np.complex128) for _ in range(len(dims))] - for n1, state1 in idx2state.items(): + for n1, state1 in enumerate(idx2state): for idx, s in enumerate(state1): # if s > 0, the annihilation operator of mode idx has a non-zero # entry with one less excitation in mode idx in the final state diff --git a/qutip/states.py b/qutip/states.py index 5cada3b1f1..e0cd0f1809 100644 --- a/qutip/states.py +++ b/qutip/states.py @@ -11,6 +11,7 @@ import numpy as np from numpy import arange, conj, prod import scipy.sparse as sp +import itertools from qutip.qobj import Qobj from qutip.operators import destroy, jmat @@ -715,67 +716,58 @@ def bra(seq, dim=2): # # quantum state number helper functions # -def state_number_enumerate(dims, excitations=None, state=None, idx=0, nexc=0): +def state_number_enumerate(dims, excitations=None, prevstate=()): """ - An iterator that enumerate all the state number arrays (quantum numbers on - the form [n1, n2, n3, ...]) for a system with dimensions given by dims. + An iterator that enumerates all the state number tuples (quantum numbers of + the form (n1, n2, n3, ...)) for a system with dimensions given by dims. Example: >>> for state in state_number_enumerate([2,2]): # doctest: +SKIP >>> print(state) # doctest: +SKIP - [ 0 0 ] - [ 0 1 ] - [ 1 0 ] - [ 1 1 ] + ( 0 0 ) + ( 0 1 ) + ( 1 0 ) + ( 1 1 ) Parameters ---------- dims : list or array The quantum state dimensions array, as it would appear in a Qobj. - state : list - Current state in the iteration. Used internally. - excitations : integer (None) Restrict state space to states with excitation numbers below or equal to this value. - idx : integer - Current index in the iteration. Used internally. - - nexc : integer - Number of excitations in modes [0..idx-1]. Used internally. + prevstate : tuple + Previous state in the iteration. Used internally. Returns ------- - state_number : list - Successive state number arrays that can be used in loops and other + state_number : tuple + Successive state number tuples that can be used in loops and other iterations, using standard state enumeration *by definition*. """ - if state is None: - state = np.zeros(len(dims), dtype=int) - - if idx == len(dims): - if excitations is None: - yield np.array(state) - else: - yield tuple(state) - else: - if excitations is None: - nlim = dims[idx] + if excitations is None: + # in this case, state numbers are a direct product + yield from itertools.product(*(range(d) for d in dims)) + return + + # excitations is not None + nlim = min(dims[0], 1 + excitations) + for n in range(nlim): + # prevstate is the state for all previous dimensions + state = prevstate + (n,) + if len(dims) == 1: + # this is the last dimension, so the state tuple is finished + yield state else: - # modes [0..idx-1] have nexc excitations, - # so mode idx can have at most excitations-nexc excitations - nlim = min(dims[idx], 1 + excitations - nexc) - - for n in range(nlim): - state[idx] = n - for s in state_number_enumerate(dims, excitations, - state, idx + 1, nexc + n): - yield s + # add the state numbers for the remaining dimensions. since we used + # n excitations in this mode, the remaining states can have only + # (excitations - n) total excitations + yield from state_number_enumerate(dims[1:], excitations - n, state) def state_number_index(dims, state): @@ -803,8 +795,7 @@ def state_number_index(dims, state): ordering. """ - return int( - sum([state[i] * prod(dims[i + 1:]) for i, d in enumerate(dims)])) + return np.ravel_multi_index(state, dims) def state_index_number(dims, index): @@ -827,20 +818,12 @@ def state_index_number(dims, index): Returns ------- - state : list - The state number array corresponding to index `index` in standard + state : tuple + The state number tuple corresponding to index `index` in standard enumeration ordering. """ - state = np.empty_like(dims) - - D = np.concatenate([np.flipud(np.cumprod(np.flipud(dims[1:]))), [1]]) - - for n in range(len(dims)): - state[n] = index / D[n] - index -= state[n] * D[n] - - return list(state) + return np.unravel_index(index, dims) def state_number_qobj(dims, state): @@ -878,7 +861,8 @@ def state_number_qobj(dims, state): """ - return tensor([fock(dims[i], s) for i, s in enumerate(state)]) + assert len(state) == len(dims) + return tensor([fock(d, s) for d, s in zip(dims, state)]) # @@ -900,19 +884,16 @@ def enr_state_dictionaries(dims, excitations): Returns ------- - nstates, state2idx, idx2state: integer, dict, dict + nstates, state2idx, idx2state: integer, dict, list The number of states `nstates`, a dictionary for looking up state - indices from a state tuple, and a dictionary for looking up state - state tuples from state indices. + indices from a state tuple, and a list containing the state tuples + ordered by state indices. state2idx and idx2state are reverses of + each other, i.e., state2idx[idx2state[idx]] = idx and + idx2state[state2idx[state]] = state. """ - nstates = 0 - state2idx = {} - idx2state = {} - - for state in state_number_enumerate(dims, excitations): - state2idx[state] = nstates - idx2state[nstates] = state - nstates += 1 + idx2state = list(state_number_enumerate(dims, excitations)) + state2idx = {state: idx for idx, state in enumerate(idx2state)} + nstates = len(idx2state) return nstates, state2idx, idx2state @@ -996,8 +977,7 @@ def enr_thermal_dm(dims, excitations, n): else: n = np.asarray(n) - diags = [np.prod((n / (n + 1)) ** np.array(state)) - for idx, state in idx2state.items()] + diags = [np.prod((n / (n + 1)) ** np.array(state)) for state in idx2state] diags /= np.sum(diags) data = sp.spdiags(diags, 0, nstates, nstates, format='csr') From da8ff3f1fb530dbbbba2cfd5f707f5b3367c3b81 Mon Sep 17 00:00:00 2001 From: Johannes Feist Date: Tue, 13 Jul 2021 13:55:26 +0200 Subject: [PATCH 026/112] non-recursive algorithm for state_number_enumerate, another ~50% speedup --- qutip/states.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/qutip/states.py b/qutip/states.py index e0cd0f1809..80b5a231d4 100644 --- a/qutip/states.py +++ b/qutip/states.py @@ -716,7 +716,7 @@ def bra(seq, dim=2): # # quantum state number helper functions # -def state_number_enumerate(dims, excitations=None, prevstate=()): +def state_number_enumerate(dims, excitations=None): """ An iterator that enumerates all the state number tuples (quantum numbers of the form (n1, n2, n3, ...)) for a system with dimensions given by dims. @@ -739,9 +739,6 @@ def state_number_enumerate(dims, excitations=None, prevstate=()): Restrict state space to states with excitation numbers below or equal to this value. - prevstate : tuple - Previous state in the iteration. Used internally. - Returns ------- state_number : tuple @@ -755,19 +752,26 @@ def state_number_enumerate(dims, excitations=None, prevstate=()): yield from itertools.product(*(range(d) for d in dims)) return - # excitations is not None - nlim = min(dims[0], 1 + excitations) - for n in range(nlim): - # prevstate is the state for all previous dimensions - state = prevstate + (n,) - if len(dims) == 1: - # this is the last dimension, so the state tuple is finished - yield state - else: - # add the state numbers for the remaining dimensions. since we used - # n excitations in this mode, the remaining states can have only - # (excitations - n) total excitations - yield from state_number_enumerate(dims[1:], excitations - n, state) + # From here on, excitations is not None + + # General idea of algorithm: add excitations one by one in last mode (idx = + # len(dims)-1), and carry over to the next index when the limit is reached. + # Keep track of the number of excitations while doing so to avoid having to + # do explicit sums over the states. + state = (0,)*len(dims) + nexc = 0 + while True: + yield state + idx = len(dims) - 1 + state = state[:idx] + (state[idx]+1,) + state[idx+1:] + nexc += 1 + while nexc > excitations or state[idx] >= dims[idx]: + # remove all excitations in mode idx, add one in idx-1 + idx -= 1 + if idx < 0: + return + nexc -= state[idx+1] - 1 + state = state[:idx] + (state[idx]+1, 0) + state[idx+2:] def state_number_index(dims, state): From e6abef4d5a788382e9a7012b2c2b1ccdd2fd5e45 Mon Sep 17 00:00:00 2001 From: Johannes Feist Date: Wed, 21 Jul 2021 20:18:21 +0200 Subject: [PATCH 027/112] remove one extraneous term in state_number_enumerate --- qutip/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/states.py b/qutip/states.py index 80b5a231d4..10ac9e3296 100644 --- a/qutip/states.py +++ b/qutip/states.py @@ -763,7 +763,7 @@ def state_number_enumerate(dims, excitations=None): while True: yield state idx = len(dims) - 1 - state = state[:idx] + (state[idx]+1,) + state[idx+1:] + state = state[:idx] + (state[idx]+1,) nexc += 1 while nexc > excitations or state[idx] >= dims[idx]: # remove all excitations in mode idx, add one in idx-1 From a1c809ab44d3b0ae48cb4833725ea41f780d6b3e Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:00:05 +0200 Subject: [PATCH 028/112] Add tests for optimize_pulse with sparse eigenvector decomposition. --- qutip/tests/test_control_pulseoptim.py | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/qutip/tests/test_control_pulseoptim.py b/qutip/tests/test_control_pulseoptim.py index 69cb4a789f..d71a513429 100644 --- a/qutip/tests/test_control_pulseoptim.py +++ b/qutip/tests/test_control_pulseoptim.py @@ -13,6 +13,7 @@ import tempfile import numpy as np import scipy.optimize +import scipy.sparse as sp import qutip from qutip.control import pulseoptim as cpo @@ -116,6 +117,7 @@ @pytest.fixture(params=[ pytest.param(None, id="default propagation"), pytest.param({'oper_dtype': qutip.Qobj}, id="Qobj propagation"), + pytest.param({'oper_dtype': np.ndarray}, id="ndarray propagation"), ]) def propagation(request): return {'dyn_params': request.param} @@ -165,6 +167,38 @@ def test_basic_optimization(self, system, propagation): result = _optimize_pulse(system) assert result.fid_err < system.kwargs['fid_err_targ'] + def test_sparse_eigen_optimization(self, system, propagation): + system = _merge_kwargs(system, { + **propagation, + "dyn_params": { + **(propagation.get("dyn_params") or {}), + "sparse_eigen_decomp": True, + } + }) + if system.system.dims == [[2], [2]]: + with pytest.raises(ValueError, match=( + r"^Single qubit pulse optimization dynamics cannot use" + r" sparse eigenvector decomposition")): + _optimize_pulse(system) + else: + result = _optimize_pulse(system) + assert result.fid_err < system.kwargs['fid_err_targ'] + + @pytest.mark.parametrize("oper_dtype", [ + pytest.param(sp.csr_matrix, id="csr_matrix"), + pytest.param(list, id="list"), + ]) + def test_invalid_oper_dtype(self, system, oper_dtype): + system = _merge_kwargs(system, {"dyn_params": { + "oper_dtype": oper_dtype, + }}) + with pytest.raises(ValueError) as err: + _optimize_pulse(system) + assert str(err.value) == ( + f"Unknown oper_dtype {oper_dtype!r}. The oper_dtype may be" + " qutip.Qobj or numpy.ndarray." + ) + def test_object_oriented_approach_and_gradient(self, system, propagation): """ Test the object-oriented version of the optimiser, and ensure that the From 346b2e7fa7a7165683657c4319fe83a0b10796c1 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:04:29 +0200 Subject: [PATCH 029/112] Remove support for oper_dtypes that aren't Qobj or np.ndarray. --- qutip/control/propcomp.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/qutip/control/propcomp.py b/qutip/control/propcomp.py index 90a9e305ea..a7e4c8d9f9 100644 --- a/qutip/control/propcomp.py +++ b/qutip/control/propcomp.py @@ -306,16 +306,11 @@ def _get_aug_mat(self, k, j): E = dyn._get_phased_ctrl_dyn_gen(k, j).data*dyn.tau[k] Z = sp.csr_matrix(dg.data.shape) aug = Qobj(sp.vstack([sp.hstack([A, E]), sp.hstack([Z, A])])) - elif dyn.oper_dtype == np.ndarray: + else: A = dg*dyn.tau[k] E = dyn._get_phased_ctrl_dyn_gen(k, j)*dyn.tau[k] Z = np.zeros(dg.shape) aug = np.vstack([np.hstack([A, E]), np.hstack([Z, A])]) - else: - A = dg*dyn.tau[k] - E = dyn._get_phased_ctrl_dyn_gen(k, j)*dyn.tau[k] - Z = dg*0.0 - aug = sp.vstack([sp.hstack([A, E]), sp.hstack([Z, A])]) return aug def _compute_prop_grad(self, k, j, compute_prop=True): @@ -388,7 +383,7 @@ def _compute_prop_grad(self, k, j, compute_prop=True): prop_grad_dense = la.expm_frechet(A, E, compute_expm=False) prop_grad = Qobj(prop_grad_dense, dims=dyn.dyn_dims) - elif dyn.oper_dtype == np.ndarray: + else: A = dyn._get_phased_dyn_gen(k)*dyn.tau[k] E = dyn._get_phased_ctrl_dyn_gen(k, j)*dyn.tau[k] if compute_prop: @@ -396,19 +391,6 @@ def _compute_prop_grad(self, k, j, compute_prop=True): else: prop_grad = la.expm_frechet(A, E, compute_expm=False) - else: - # Assuming some sparse matrix - spcls = dyn._dyn_gen[k].__class__ - A = (dyn._get_phased_dyn_gen(k)*dyn.tau[k]).toarray() - E = (dyn._get_phased_ctrl_dyn_gen(k, j)*dyn.tau[k]).toarray() - if compute_prop: - prop_dense, prop_grad_dense = la.expm_frechet(A, E) - prop = spcls(prop_dense) - prop_grad = spcls(prop_grad_dense) - else: - prop_grad_dense = la.expm_frechet(A, E, compute_expm=False) - prop_grad = spcls(prop_grad_dense) - if compute_prop: return prop, prop_grad else: From 03b4cb75008579df5e236fed8624357488ef1799 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:10:44 +0200 Subject: [PATCH 030/112] Remove support for oper_dtypes other than Qobj or np.ndarray. --- qutip/control/dynamics.py | 58 +++++++++++---------------------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/qutip/control/dynamics.py b/qutip/control/dynamics.py index 0efe66e606..ce0befbb44 100644 --- a/qutip/control/dynamics.py +++ b/qutip/control/dynamics.py @@ -185,9 +185,7 @@ class Dynamics(object): oper_dtype : type Data type for internal dynamics generators, propagators and time - evolution operators. This can be ndarray or Qobj, or (in theory) any - other representaion that supports typical matrix methods (e.g. dot) - ndarray performs best for smaller quantum systems. + evolution operators. This can be ndarray or Qobj. Qobj may perform better for larger systems, and will also perform better when (custom) fidelity measures use Qobj methods such as partial trace. @@ -386,7 +384,7 @@ def reset(self): self.time_depend_ctrl_dyn_gen = False # These internal attributes will be of the internal operator data type # used to compute the evolution - # Note this maybe ndarray, Qobj or some other depending on oper_dtype + # This will be either ndarray or Qobj self._drift_dyn_gen = None self._ctrl_dyn_gen = None self._phased_ctrl_dyn_gen = None @@ -684,7 +682,7 @@ def _choose_oper_dtype(self): else: ctrls = self.ctrl_dyn_gen for c in ctrls: - dg = dg + c + dg = dg + c N = dg.data.shape[0] n = dg.data.nnz @@ -724,7 +722,9 @@ def _init_evo(self): self.sys_shape = self.initial.shape # Set the phase application method self._init_phase() + self._set_memory_optimizations() + n_ts = self.num_tslots n_ctrls = self.num_ctrls if self.oper_dtype == Qobj: @@ -748,26 +748,10 @@ def _init_evo(self): else: self._ctrl_dyn_gen = [ctrl.full() for ctrl in self.ctrl_dyn_gen] - elif self.oper_dtype == sp.csr_matrix: - self._initial = self.initial.data - self._target = self.target.data - if self.time_depend_drift: - self._drift_dyn_gen = [d.data for d in self.drift_dyn_gen] - else: - self._drift_dyn_gen = self.drift_dyn_gen.data - - if self.time_depend_ctrl_dyn_gen: - self._ctrl_dyn_gen = np.empty([n_ts, n_ctrls], dtype=object) - for k in range(n_ts): - for j in range(n_ctrls): - self._ctrl_dyn_gen[k, j] = \ - self.ctrl_dyn_gen[k, j].data - else: - self._ctrl_dyn_gen = [ctrl.data for ctrl in self.ctrl_dyn_gen] else: - logger.warn("Unknown option '{}' for oper_dtype. " - "Assuming that internal drift, ctrls, initial and target " - "have been set correctly".format(self.oper_dtype)) + raise ValueError( + "Unknown oper_dtype {!r}. The oper_dtype may be qutip.Qobj or" + " numpy.ndarray.".format(self.oper_dtype)) if self.cache_phased_dyn_gen: if self.time_depend_ctrl_dyn_gen: @@ -1152,21 +1136,15 @@ def _get_onto_evo_target(self): self._onto_evo_target = Qobj(targ) elif self.oper_dtype == np.ndarray: self._onto_evo_target = targ - elif self.oper_dtype == sp.csr_matrix: - self._onto_evo_target = sp.csr_matrix(targ) else: - targ_cls = self._target.__class__ - self._onto_evo_target = targ_cls(targ) + assert False, f"Unknown oper_dtype {self.oper_dtype!r}" else: if self.oper_dtype == Qobj: self._onto_evo_target = self.target.dag() elif self.oper_dtype == np.ndarray: self._onto_evo_target = self.target.dag().full() - elif self.oper_dtype == sp.csr_matrix: - self._onto_evo_target = self.target.dag().data else: - targ_cls = self._target.__class__ - self._onto_evo_target = targ_cls(self.target.dag().full()) + assert False, f"Unknown oper_dtype {self.oper_dtype!r}" return self._onto_evo_target @@ -1575,13 +1553,9 @@ def _spectral_decomp(self, k): H = self._dyn_gen[k] # returns row vector of eigenvals, columns with the eigenvecs eig_val, eig_vec = eigh(H) + else: - if sparse: - H = self._dyn_gen[k].toarray() - else: - H = self._dyn_gen[k] - # returns row vector of eigenvals, columns with the eigenvecs - eig_val, eig_vec = eigh(H) + assert False, f"Unknown oper_dtype {self.oper_dtype!r}" # assuming H is an nxn matrix, find n n = self.get_drift_dim() @@ -1630,7 +1604,7 @@ def _spectral_decomp(self, k): if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].dag() - else: + elif self.oper_dtype == np.ndarray: self._prop_eigen[k] = np.diagflat(prop_eig) self._dyn_gen_eigenvectors[k] = eig_vec # The _dyn_gen_eigenvectors_adj list is not used in @@ -1638,6 +1612,8 @@ def _spectral_decomp(self, k): if self._dyn_gen_eigenvectors_adj is not None: self._dyn_gen_eigenvectors_adj[k] = \ self._dyn_gen_eigenvectors[k].conj().T + else: + assert False, f"Unknown oper_dtype {self.oper_dtype!r}" def _get_dyn_gen_eigenvectors_adj(self, k): # The _dyn_gen_eigenvectors_adj list is not used in @@ -1736,10 +1712,8 @@ def _get_omega(self): if self.oper_dtype == Qobj: self._omega = Qobj(omg, dims=self.dyn_dims) self._omega_qobj = self._omega - elif self.oper_dtype == sp.csr_matrix: - self._omega = sp.csr_matrix(omg) else: - self._omega = omg + self._omega = omg return self._omega def _set_phase_application(self, value): From 8ce77574eddd3c352e10315e8e65ed9977e11cd8 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:15:25 +0200 Subject: [PATCH 031/112] Raise a ValueError if sparse eigenvector decomposition is used with single qubit systems. SciPy's sparse eigenvector decomposition only supports finding N - 2 or fewer eigenvectors for an N dimension system, but for a single qubit system N - 2 == 0 -- i.e. the sparse decomposer can find no eigenvectors at all. --- qutip/control/dynamics.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qutip/control/dynamics.py b/qutip/control/dynamics.py index ce0befbb44..88505db679 100644 --- a/qutip/control/dynamics.py +++ b/qutip/control/dynamics.py @@ -724,6 +724,12 @@ def _init_evo(self): self._init_phase() self._set_memory_optimizations() + if self.sparse_eigen_decomp and self.sys_shape[0] <= 2: + raise ValueError( + "Single qubit pulse optimization dynamics cannot use sparse" + " eigenvector decomposition because of limitations in" + " scipy.linalg.eigsh. Pleae set sparse_eigen_decomp to False" + " or increase the size of the system.") n_ts = self.num_tslots n_ctrls = self.num_ctrls From 3e908bbba52f4268a142c2c22833c85eff33349c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:18:02 +0200 Subject: [PATCH 032/112] Add missing dims to construction of onto_evo Qobj. --- qutip/control/dynamics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutip/control/dynamics.py b/qutip/control/dynamics.py index 88505db679..59dd0677d5 100644 --- a/qutip/control/dynamics.py +++ b/qutip/control/dynamics.py @@ -1139,7 +1139,8 @@ def _get_onto_evo_target(self): #Target is operator targ = la.inv(self.target.full()) if self.oper_dtype == Qobj: - self._onto_evo_target = Qobj(targ) + rev_dims = [self.target.dims[1], self.target.dims[0]] + self._onto_evo_target = Qobj(targ, dims=rev_dims) elif self.oper_dtype == np.ndarray: self._onto_evo_target = targ else: From ecca45c8046b49c978224f51662520304aa4e6b7 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 22 Jul 2021 18:18:13 +0200 Subject: [PATCH 033/112] Convert the results of the sparse eigensolver to a sparse matrix. The sparse eigensolver returns the eigenvectors as an ndarray of sparse matrixes, while the Qobj constructor expects them as as single sparse matrix. --- qutip/control/dynamics.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qutip/control/dynamics.py b/qutip/control/dynamics.py index 59dd0677d5..d5602aef16 100644 --- a/qutip/control/dynamics.py +++ b/qutip/control/dynamics.py @@ -1555,6 +1555,11 @@ def _spectral_decomp(self, k): eig_val, eig_vec = sp_eigs(H.data, H.isherm, sparse=self.sparse_eigen_decomp) eig_vec = eig_vec.T + if self.sparse_eigen_decomp: + # when sparse=True, sp_eigs returns an ndarray where each + # element is a sparse matrix so we convert it into a sparse + # matrix we can later pass to Qobj(...) + eig_vec = sp.hstack(eig_vec) elif self.oper_dtype == np.ndarray: H = self._dyn_gen[k] From 02c2900e59d85b9edd16a43979df8eb2a5fc3cdb Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Wed, 4 Aug 2021 14:43:31 +0200 Subject: [PATCH 034/112] Fix handling of options=None in matrix_histogram. --- qutip/visualization.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index d6052216ce..47f40fc101 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -591,7 +591,9 @@ def matrix_histogram(M, xlabels=None, ylabels=None, title=None, limits=None, 'cbar_pad': 0.04, 'cbar_to_z': False} # update default_opts from input options - if isinstance(options, dict): + if options is None: + pass + elif isinstance(options, dict): # check if keys in options dict are valid if set(options) - set(default_opts): raise ValueError("invalid key(s) found in options: " From 0cff88682309e422bb1a445b486f3b74b885c9af Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 5 Aug 2021 14:42:26 +1000 Subject: [PATCH 035/112] Fix transcription errors in spin husimi Q function. --- qutip/wigner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 6b85a47708..5116ccc949 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -884,15 +884,15 @@ def spin_q_function(rho, theta, phi): for m1 in arange(-j, j+1): - Q += binom(2*j, j+m1) * cos(THETA/2) ** (2*(j-m1)) * sin(THETA/2) ** (2*(j+m1)) * \ + Q += binom(2*j, j+m1) * cos(THETA/2) ** (2*(j+m1)) * sin(THETA/2) ** (2*(j-m1)) * \ rho.data[int(j-m1), int(j-m1)] for m2 in arange(m1+1, j+1): Q += (sqrt(binom(2*j, j+m1)) * sqrt(binom(2*j, j+m2)) * - cos(THETA/2) ** (2*j-m1-m2) * sin(THETA/2) ** (2*j+m1+m2)) * \ - (exp(1j * (m2-m1) * PHI) * rho.data[int(j-m1), int(j-m2)] + - exp(1j * (m1-m2) * PHI) * rho.data[int(j-m2), int(j-m1)]) + cos(THETA/2) ** (2*j+m1+m2) * sin(THETA/2) ** (2*j-m1-m2)) * \ + (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) return Q.real/pi, THETA, PHI From 8e7da8e967abe0b66be0c56dd6418872acbe277a Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 5 Aug 2021 14:47:38 +1000 Subject: [PATCH 036/112] Add definition of husimi Q function for clarity, adjust formatting. --- qutip/wigner.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 5116ccc949..2587dab6b0 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -852,6 +852,12 @@ def qfunc( def spin_q_function(rho, theta, phi): """Husimi Q-function for spins. + The Husimi Q function for spins is defined as: + Q(theta, phi) = /pi + where |s,theta,phi> is the spin coherent state for a spin with length s and pointing + along (theta,phi). The Q function can be calculated using the elements of rho using + the definition of spin coherent states (e.g. Loh and Kim 2015 doi: 10.1119/1.4898595) + Parameters ---------- state : qobj @@ -882,19 +888,19 @@ def spin_q_function(rho, theta, phi): Q = np.zeros_like(THETA, dtype=complex) - for m1 in arange(-j, j+1): - - Q += binom(2*j, j+m1) * cos(THETA/2) ** (2*(j+m1)) * sin(THETA/2) ** (2*(j-m1)) * \ - rho.data[int(j-m1), int(j-m1)] - - for m2 in arange(m1+1, j+1): + for m1 in arange(-j, j + 1): + Q += binom(2 * j, j + m1) * cos(THETA / 2) ** (2 * (j + m1)) * sin(THETA / 2) ** ( + 2 * (j - m1)) * \ + rho.data[int(j - m1), int(j - m1)] - Q += (sqrt(binom(2*j, j+m1)) * sqrt(binom(2*j, j+m2)) * - cos(THETA/2) ** (2*j+m1+m2) * sin(THETA/2) ** (2*j-m1-m2)) * \ - (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + - exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) + for m2 in arange(m1 + 1, j + 1): + Q += (sqrt(binom(2 * j, j + m1)) * sqrt(binom(2 * j, j + m2)) * + cos(THETA / 2) ** (2 * j + m1 + m2) * sin(THETA / 2) ** ( + 2 * j - m1 - m2)) * \ + (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) - return Q.real/pi, THETA, PHI + return Q.real / pi, THETA, PHI def _rho_kq(rho, j, k, q): v = 0j From ff309c83d42acac7a4751261da87167e50eda8c6 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 5 Aug 2021 15:12:59 +1000 Subject: [PATCH 037/112] PEP8 --- qutip/wigner.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 2587dab6b0..6a927e5072 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -854,9 +854,10 @@ def spin_q_function(rho, theta, phi): The Husimi Q function for spins is defined as: Q(theta, phi) = /pi - where |s,theta,phi> is the spin coherent state for a spin with length s and pointing - along (theta,phi). The Q function can be calculated using the elements of rho using - the definition of spin coherent states (e.g. Loh and Kim 2015 doi: 10.1119/1.4898595) + where |s,theta,phi> is the spin coherent state for a spin with length s + and pointing along (theta,phi). The Q function can be calculated using + the elements of rho using the definition of spin coherent states (e.g. + Loh and Kim 2015 doi: 10.1119/1.4898595) Parameters ---------- @@ -889,14 +890,13 @@ def spin_q_function(rho, theta, phi): Q = np.zeros_like(THETA, dtype=complex) for m1 in arange(-j, j + 1): - Q += binom(2 * j, j + m1) * cos(THETA / 2) ** (2 * (j + m1)) * sin(THETA / 2) ** ( - 2 * (j - m1)) * \ - rho.data[int(j - m1), int(j - m1)] + Q += binom(2 * j, j + m1) * cos(THETA / 2) ** (2 * (j + m1)) * sin(THETA / 2) ** \ + (2 * (j - m1)) * rho.data[int(j - m1), int(j - m1)] for m2 in arange(m1 + 1, j + 1): Q += (sqrt(binom(2 * j, j + m1)) * sqrt(binom(2 * j, j + m2)) * - cos(THETA / 2) ** (2 * j + m1 + m2) * sin(THETA / 2) ** ( - 2 * j - m1 - m2)) * \ + cos(THETA / 2) ** (2 * j + m1 + m2) * sin(THETA / 2) ** + (2 * j - m1 - m2)) * \ (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) From 6e78d5810f7d6e1c793076480dc503bf5aaec0f1 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 5 Aug 2021 15:23:27 +1000 Subject: [PATCH 038/112] PEP8 --- qutip/wigner.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 6a927e5072..190055fdd6 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -890,15 +890,16 @@ def spin_q_function(rho, theta, phi): Q = np.zeros_like(THETA, dtype=complex) for m1 in arange(-j, j + 1): - Q += binom(2 * j, j + m1) * cos(THETA / 2) ** (2 * (j + m1)) * sin(THETA / 2) ** \ - (2 * (j - m1)) * rho.data[int(j - m1), int(j - m1)] + Q += binom(2 * j, j + m1) * cos(THETA / 2) ** (2 * (j + m1)) * \ + sin(THETA / 2) ** (2 * (j - m1)) * \ + rho.data[int(j - m1), int(j - m1)] for m2 in arange(m1 + 1, j + 1): Q += (sqrt(binom(2 * j, j + m1)) * sqrt(binom(2 * j, j + m2)) * - cos(THETA / 2) ** (2 * j + m1 + m2) * sin(THETA / 2) ** - (2 * j - m1 - m2)) * \ - (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + - exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) + cos(THETA / 2) ** (2 * j + m1 + m2) * + sin(THETA / 2) ** (2 * j - m1 - m2)) * \ + (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) return Q.real / pi, THETA, PHI From 035c17c3dceb54971335ef05210b73bf17e957f0 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Fri, 6 Aug 2021 12:24:07 +1000 Subject: [PATCH 039/112] Add tests for spin_q_function --- qutip/tests/test_wigner.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 7c1c63bc5c..f02973ae23 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -1,8 +1,10 @@ import pytest import numpy as np +import itertools from scipy.special import laguerre from numpy.random import rand -from numpy.testing import assert_, run_module_suite, assert_equal +from numpy.testing import assert_, run_module_suite, assert_equal, \ + assert_almost_equal import qutip from qutip.states import coherent, fock, ket, bell_state @@ -593,6 +595,21 @@ def test_wigner_clenshaw_sp_iter_dm(): Wdiff = abs(W - Wclen) assert_equal(np.sum(abs(Wdiff)) < 1e-7, True) - +@pytest.mark.slow +def test_spin_q_function(): + for spin in [1/2, 3, 11/2, 21]: + d = int(2*spin + 1) + rho = rand_dm(d) + + # Points at which to evaluate the spin Q function + theta_prime = np.linspace(0, np.pi, 32, endpoint=True) + phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) + Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) + + Q = Q.transpose() + for k, (t, p) in enumerate(itertools.product(theta_prime, phi_prime)): + state = qutip.spin_coherent(spin, t, p) + direct_Q = (state.dag() * rho * state).norm() / np.pi + assert_almost_equal(direct_Q, Q.flat[k], decimal=9) if __name__ == "__main__": run_module_suite() From 2e01ab08a240bf36a3520fd29b13862c8d3a39f9 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:07:16 +1000 Subject: [PATCH 040/112] Fix indexing issue in spin wigner and renormalize W function. --- qutip/wigner.py | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 190055fdd6..9225451617 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -904,13 +904,35 @@ def spin_q_function(rho, theta, phi): return Q.real / pi, THETA, PHI def _rho_kq(rho, j, k, q): + """ + This calculates the trace of the multipole operator T_kq and the density + matrix rho for use in the spin Wigner quasiprobability distribution. + + Parameters + ---------- + rho : qobj + A density matrix for a spin-j quantum system. + j : float + The spin length of the system. + k : int + Spherical harmonic degree + q : int + Spherical harmonic order + + Returns + ------- + v : float + Overlap of state with multipole operator T_kq + """ + v = 0j for m1 in arange(-j, j+1): for m2 in arange(-j, j+1): v += ( - (-1)**(j - m1 - q) - * qutip.clebsch(j, j, k, m1, -m2, q) - * rho.data[m1 + j, m2 + j] + (-1)**(2*j - k - m1 - m2) + * np.sqrt((2*k+1)/(2*j+1)) + * qutip.clebsch(j, k, j, -m1, q, -m2) + * rho.data[j - m1, j - m2] ) return v @@ -934,10 +956,13 @@ def spin_wigner(rho, theta, phi): Values representing the spin Wigner function at the values specified by THETA and PHI. - Notes + References ----- - Experimental. - + [1] Agarwal, G. S. (1981). Phys. Rev. A, 24(6), 2889–2896. https://doi.org/10.1103/PhysRevA.24.2889 + [2] Dowling, J. P., Agarwal, G. S., & Schleich, W. P. (1994). + Phys. Rev. A, 49(5), 4101–4109. https://doi.org/10.1103/PhysRevA.49.4101 + [3] Conversion between Wigner 3-j symbol and Clebsch-Gordan coefficients + taken from Wikipedia (https://en.wikipedia.org/wiki/3-j_symbol) """ if rho.type == 'bra': @@ -958,4 +983,4 @@ def spin_wigner(rho, theta, phi): # sph_harm takes azimuthal angle then polar angle as arguments W += _rho_kq(rho, j, k, q) * sph_harm(q, k, PHI, THETA) - return W, THETA, PHI + return W*np.sqrt((2*j + 1)/(4*pi)), THETA, PHI From 9b3f962584bcd7587b35df13123a07ea93021f4a Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:08:19 +1000 Subject: [PATCH 041/112] Parametrize test for spin_q_function. Now tests both pure and non-pure states. --- qutip/tests/test_wigner.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index f02973ae23..9396ac40cf 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -595,14 +595,22 @@ def test_wigner_clenshaw_sp_iter_dm(): Wdiff = abs(W - Wclen) assert_equal(np.sum(abs(Wdiff)) < 1e-7, True) -@pytest.mark.slow -def test_spin_q_function(): - for spin in [1/2, 3, 11/2, 21]: - d = int(2*spin + 1) - rho = rand_dm(d) - - # Points at which to evaluate the spin Q function - theta_prime = np.linspace(0, np.pi, 32, endpoint=True) +@pytest.mark.parametrize(['spin'], [ + pytest.param(1/2, id="spin-one-half"), + pytest.param(3, id="spin-three"), + pytest.param(13/2, id="spin-thirteen-half"), + pytest.param(7, id="spin-seven") +]) +@pytest.mark.parametrize("pure", [ + pytest.param(True, id="pure"), + pytest.param(False, id="mixed") +]) +def test_spin_q_function(spin, pure): + d = int(2*spin + 1) + rho = rand_dm(d, pure=pure) + + # Points at which to evaluate the spin Q function + theta_prime = np.linspace(0, np.pi, 32, endpoint=True) phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) From f3efdf76792bb29c62a72eb79fb2d0a0ba95989f Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:10:50 +1000 Subject: [PATCH 042/112] Add normalized and real test for spin_wigner function. Note the normalization is not very precise (using 3 decimal places) and may fail for some states. --- qutip/tests/test_wigner.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 9396ac40cf..320d86709f 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -619,5 +619,32 @@ def test_spin_q_function(spin, pure): state = qutip.spin_coherent(spin, t, p) direct_Q = (state.dag() * rho * state).norm() / np.pi assert_almost_equal(direct_Q, Q.flat[k], decimal=9) +@pytest.mark.parametrize(["spin"], [ + pytest.param(1/2, id="spin-one-half"), + pytest.param(3, id="spin-three"), + pytest.param(13/2, id="spin-thirteen-half"), + pytest.param(7, id="spin-seven") +]) +@pytest.mark.parametrize("pure", [ + pytest.param(True, id="pure"), + pytest.param(False, id="mixed") +]) +def test_spin_wigner_normalized_and_real(spin, pure): + d = int(2*spin + 1) + rho = rand_dm(d, pure=pure) + + # Points at which to evaluate the spin Wigner function + theta_prime = np.linspace(0, np.pi, 128, endpoint=True) + phi_prime = np.linspace(-np.pi, np.pi, 256, endpoint=True) + W, THETA, PHI = qutip.spin_wigner(rho, theta_prime, phi_prime) + + assert_allclose(W.imag, 0, atol=1e-9, + err_msg=f"Wigner function is non-real with maximum " + f"imaginary value {np.max(W.imag)}") + + norm = np.trapz(np.trapz(W*np.sin(THETA), theta_prime), phi_prime) + assert_almost_equal(norm.real, 1, decimal=3, + err_msg=f"Wigner function is not normalized.") + if __name__ == "__main__": run_module_suite() From 779b39cd75abe1de85dedf61f6825db6dfd92800 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:28:50 +1000 Subject: [PATCH 043/112] Use the correct normalization factor for spin Q function. --- qutip/tests/test_wigner.py | 12 ++++++------ qutip/wigner.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 320d86709f..71f906d6a9 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -4,7 +4,7 @@ from scipy.special import laguerre from numpy.random import rand from numpy.testing import assert_, run_module_suite, assert_equal, \ - assert_almost_equal + assert_almost_equal, assert_allclose import qutip from qutip.states import coherent, fock, ket, bell_state @@ -614,11 +614,11 @@ def test_spin_q_function(spin, pure): phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) - Q = Q.transpose() - for k, (t, p) in enumerate(itertools.product(theta_prime, phi_prime)): - state = qutip.spin_coherent(spin, t, p) - direct_Q = (state.dag() * rho * state).norm() / np.pi - assert_almost_equal(direct_Q, Q.flat[k], decimal=9) + for k, (p, t) in enumerate(itertools.product(phi_prime, theta_prime)): + state = qutip.spin_coherent(spin, t, p) + direct_Q = (state.dag() * rho * state).norm() * (2 * j + 1) / (4*pi) + assert_almost_equal(Q.flat[k], direct_Q, decimal=9) + @pytest.mark.parametrize(["spin"], [ pytest.param(1/2, id="spin-one-half"), pytest.param(3, id="spin-three"), diff --git a/qutip/wigner.py b/qutip/wigner.py index 9225451617..437267d8b1 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -901,7 +901,8 @@ def spin_q_function(rho, theta, phi): (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) - return Q.real / pi, THETA, PHI + return Q.real * (2 * j + 1) / (4*pi), THETA, PHI + def _rho_kq(rho, j, k, q): """ From 952910f25b168d20770b499936e5eb46cbd9900e Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:38:49 +1000 Subject: [PATCH 044/112] Whole wigner.py PEP8 --- qutip/tests/test_wigner.py | 8 +- qutip/wigner.py | 145 +++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 73 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 71f906d6a9..9f23a9ab40 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -606,7 +606,7 @@ def test_wigner_clenshaw_sp_iter_dm(): pytest.param(False, id="mixed") ]) def test_spin_q_function(spin, pure): - d = int(2*spin + 1) + d = int(2 * spin + 1) rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Q function @@ -616,7 +616,7 @@ def test_spin_q_function(spin, pure): for k, (p, t) in enumerate(itertools.product(phi_prime, theta_prime)): state = qutip.spin_coherent(spin, t, p) - direct_Q = (state.dag() * rho * state).norm() * (2 * j + 1) / (4*pi) + direct_Q = (state.dag() * rho * state).norm() * (2 * j + 1) / (4 * pi) assert_almost_equal(Q.flat[k], direct_Q, decimal=9) @pytest.mark.parametrize(["spin"], [ @@ -630,7 +630,7 @@ def test_spin_q_function(spin, pure): pytest.param(False, id="mixed") ]) def test_spin_wigner_normalized_and_real(spin, pure): - d = int(2*spin + 1) + d = int(2 * spin + 1) rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Wigner function @@ -642,7 +642,7 @@ def test_spin_wigner_normalized_and_real(spin, pure): err_msg=f"Wigner function is non-real with maximum " f"imaginary value {np.max(W.imag)}") - norm = np.trapz(np.trapz(W*np.sin(THETA), theta_prime), phi_prime) + norm = np.trapz(np.trapz(W * np.sin(THETA), theta_prime), phi_prime) assert_almost_equal(norm.real, 1, decimal=3, err_msg=f"Wigner function is not normalized.") diff --git a/qutip/wigner.py b/qutip/wigner.py index 437267d8b1..f391dc10d7 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -72,7 +72,7 @@ def wigner_transform(psi, j, fullparity, steps, slicearray): else: rho = psi - sun = 2 # The order of the SU group + sun = 2 # The order of the SU group # calculate total number of particles in quantum state: N = np.int32(np.log(np.shape(rho)[0]) / np.log(2 * j + 1)) @@ -88,7 +88,7 @@ def wigner_transform(psi, j, fullparity, steps, slicearray): wigner = np.zeros((steps, steps)) if fullparity: - pari = _parity(sun**N, j) + pari = _parity(sun ** N, j) else: pari = _parity(sun, j) for t in range(steps): @@ -131,7 +131,7 @@ def _kernelsu2(theta, phi, N, j, parity, fullparity): for i in range(0, N): U = np.kron(U, _rotation_matrix(theta[i], phi[i], j)) if not fullparity: - op_parity = parity # The parity for a one particle system + op_parity = parity # The parity for a one particle system for i in range(1, N): parity = np.kron(parity, op_parity) matrix = U @ parity @ U.conj().T @@ -184,16 +184,17 @@ def wigner(psi, xvec, yvec, method='clenshaw', g=sqrt(2), value `hbar=1`. method : string {'clenshaw', 'iterative', 'laguerre', 'fft'} - Select method 'clenshaw' 'iterative', 'laguerre', or 'fft', where 'clenshaw' - and 'iterative' use an iterative method to evaluate the Wigner functions for density - matrices :math:`|m>~50). 'clenshaw' is a fast and numerically stable method. + Select method 'clenshaw' 'iterative', 'laguerre', or 'fft', + where 'clenshaw' and 'iterative' use an iterative method to evaluate + the Wigner functions for density matrices :math:`|m>~50). 'clenshaw' is a fast and numerically stable method. sparse : bool {False, True} Tells the default solver whether or not to keep the input density @@ -407,18 +408,19 @@ def _wigner_fft(psi, xvec): Evaluates the Fourier transformation of a given state vector. Returns the corresponding density matrix and range """ - n = 2*len(psi.T) + n = 2 * len(psi.T) r1 = np.concatenate((np.array([[0]]), - np.fliplr(psi.conj()), - np.zeros((1, n//2 - 1))), axis=1) + np.fliplr(psi.conj()), + np.zeros((1, n // 2 - 1))), axis=1) r2 = np.concatenate((np.array([[0]]), psi, - np.zeros((1, n//2 - 1))), axis=1) - w = la.toeplitz(np.zeros((n//2, 1)), r1) * \ - np.flipud(la.toeplitz(np.zeros((n//2, 1)), r2)) - w = np.concatenate((w[:, n//2:n], w[:, 0:n//2]), axis=1) + np.zeros((1, n // 2 - 1))), axis=1) + w = la.toeplitz(np.zeros((n // 2, 1)), r1) * \ + np.flipud(la.toeplitz(np.zeros((n // 2, 1)), r2)) + w = np.concatenate((w[:, n // 2:n], w[:, 0:n // 2]), axis=1) w = ft.fft(w) - w = np.real(np.concatenate((w[:, 3*n//4:n+1], w[:, 0:n//4]), axis=1)) - p = np.arange(-n/4, n/4)*np.pi / (n*(xvec[1] - xvec[0])) + w = np.real( + np.concatenate((w[:, 3 * n // 4:n + 1], w[:, 0:n // 4]), axis=1)) + p = np.arange(-n / 4, n / 4) * np.pi / (n * (xvec[1] - xvec[0])) w = w / (p[1] - p[0]) / n return w, p @@ -438,7 +440,7 @@ def _osc_eigen(N, pnts): A[1, :] = np.sqrt(2) * pnts * A[0, :] for k in range(2, N): A[k, :] = np.sqrt(2.0 / k) * pnts * A[k - 1, :] - \ - np.sqrt((k - 1.0) / k) * A[k - 2, :] + np.sqrt((k - 1.0) / k) * A[k - 2, :] return A @@ -454,33 +456,34 @@ def _wigner_clenshaw(rho, xvec, yvec, g=sqrt(2), sparse=False): """ M = np.prod(rho.shape[0]) - X,Y = np.meshgrid(xvec, yvec) - #A = 0.5 * g * (X + 1.0j * Y) - A2 = g * (X + 1.0j * Y) #this is A2 = 2*A + X, Y = np.meshgrid(xvec, yvec) + # A = 0.5 * g * (X + 1.0j * Y) + A2 = g * (X + 1.0j * Y) # this is A2 = 2*A B = np.abs(A2) B *= B - w0 = (2*rho.data[0,-1])*np.ones_like(A2) - L = M-1 - #calculation of \sum_{L} c_L (2x)^L / \sqrt(L!) - #using Horner's method + w0 = (2 * rho.data[0, -1]) * np.ones_like(A2) + L = M - 1 + # calculation of \sum_{L} c_L (2x)^L / \sqrt(L!) + # using Horner's method if not sparse: - rho = rho.full() * (2*np.ones((M,M)) - np.diag(np.ones(M))) + rho = rho.full() * (2 * np.ones((M, M)) - np.diag(np.ones(M))) while L > 0: L -= 1 - #here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) - w0 = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 * A2 * (L+1)**-0.5 + # here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 * A2 * ( + L + 1) ** -0.5 else: while L > 0: L -= 1 - diag = _csr_get_diag(rho.data.data,rho.data.indices, - rho.data.indptr,L) + diag = _csr_get_diag(rho.data.data, rho.data.indices, + rho.data.indptr, L) if L != 0: diag *= 2 - #here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) - w0 = _wig_laguerre_val(L, B, diag) + w0 * A2 * (L+1)**-0.5 + # here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 = _wig_laguerre_val(L, B, diag) + w0 * A2 * (L + 1) ** -0.5 - return w0.real * np.exp(-B*0.5) * (g*g*0.5 / pi) + return w0.real * np.exp(-B * 0.5) * (g * g * 0.5 / pi) def _wig_laguerre_val(L, x, c): @@ -511,10 +514,11 @@ def _wig_laguerre_val(L, x, c): y1 = c[-1] for i in range(3, len(c) + 1): k -= 1 - y0, y1 = c[-i] - y1 * (float((k - 1)*(L + k - 1))/((L+k)*k))**0.5, \ - y0 - y1 * ((L + 2*k -1) - x) * ((L+k)*k)**-0.5 + y0, y1 = c[-i] - y1 * ( + float((k - 1) * (L + k - 1)) / ((L + k) * k)) ** 0.5, \ + y0 - y1 * ((L + 2 * k - 1) - x) * ((L + k) * k) ** -0.5 - return y0 - y1 * ((L + 1) - x) * (L + 1)**-0.5 + return y0 - y1 * ((L + 1) - x) * (L + 1) ** -0.5 # ----------------------------------------------------------------------------- @@ -526,10 +530,10 @@ def _qfunc_check_state(state: Qobj): # This is only approximate, but it's enough for our purposes; doing more # than this would take computational effort we don't _need_ to do. isdm = ( - state.isoper - and state.dims[0] == state.dims[1] - and state.isherm - and abs(state.tr() - 1) < qutip.settings.atol + state.isoper + and state.dims[0] == state.dims[1] + and state.isherm + and abs(state.tr() - 1) < qutip.settings.atol ) if not (state.isket or isdm): raise ValueError( @@ -552,7 +556,8 @@ def _qfunc_check_coordinates(xvec, yvec): yvec = np.asarray(yvec, dtype=np.float64) if xvec.ndim != 1 or yvec.ndim != 1: raise ValueError( - f"xvec and yvec must be 1D, but have shapes {xvec.shape} and {yvec.shape}." + f"xvec and yvec must be 1D, but have shapes {xvec.shape} and " + f"{yvec.shape}." ) return xvec, yvec @@ -595,6 +600,7 @@ class _QFuncCoherentGrid: >>> np.allclose(naive[:, :, 4:7], grid(4, 7)) True """ + def __init__(self, xvec, yvec, g: float): self.xvec, self.yvec = _qfunc_check_coordinates(xvec, yvec) x, y = np.meshgrid(0.5 * g * self.xvec, 0.5 * g * self.yvec) @@ -635,7 +641,7 @@ def __call__(self, first: int, last: int = None): out = np.empty(self.grid.shape + (ns.size,), dtype=np.complex128) out[:, :, 0] = self._start(ns.flat[0]) for i in range(ns.size - 1): - out[:, :, i+1] = out[:, :, i] * self.grid + out[:, :, i + 1] = out[:, :, i] * self.grid out /= np.sqrt(scipy.special.factorial(ns)) return out @@ -689,7 +695,7 @@ class QFunc: """ def __init__( - self, xvec, yvec, g: float = np.sqrt(2), memory: float = 1024 + self, xvec, yvec, g: float = np.sqrt(2), memory: float = 1024 ): self._g = g self._coherent_grid = _QFuncCoherentGrid(xvec, yvec, g) @@ -752,7 +758,7 @@ def __call__(self, state: Qobj): def _qfunc_iterative_single( - vector: np.ndarray, alpha_grid: _QFuncCoherentGrid, g: float, + vector: np.ndarray, alpha_grid: _QFuncCoherentGrid, g: float, ): r""" Get the Q function (without the :math:`\pi` scaling factor) of a single @@ -761,19 +767,19 @@ def _qfunc_iterative_single( """ ns = np.arange(vector.shape[0]) out = np.polyval( - (0.5*g * vector / np.sqrt(scipy.special.factorial(ns)))[::-1], + (0.5 * g * vector / np.sqrt(scipy.special.factorial(ns)))[::-1], alpha_grid.grid, ) out *= alpha_grid.prefactor - return np.abs(out)**2 + return np.abs(out) ** 2 def qfunc( - state: Qobj, - xvec, - yvec, - g: float = sqrt(2), - precompute_memory: float = 1024, + state: Qobj, + xvec, + yvec, + g: float = sqrt(2), + precompute_memory: float = 1024, ): r""" Husimi-Q function of a given state vector or density matrix at phase-space @@ -818,8 +824,8 @@ def qfunc( xvec, yvec = _qfunc_check_coordinates(xvec, yvec) required_memory = state.shape[0] * xvec.size * yvec.size * 16 / (1024 ** 2) enough_memory = ( - precompute_memory is not None - and precompute_memory > required_memory + precompute_memory is not None + and precompute_memory > required_memory ) if state.isoper and enough_memory: return QFunc(xvec, yvec, g)(state) @@ -901,7 +907,7 @@ def spin_q_function(rho, theta, phi): (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) - return Q.real * (2 * j + 1) / (4*pi), THETA, PHI + return Q.real * (2 * j + 1) / (4 * pi), THETA, PHI def _rho_kq(rho, j, k, q): @@ -927,13 +933,13 @@ def _rho_kq(rho, j, k, q): """ v = 0j - for m1 in arange(-j, j+1): - for m2 in arange(-j, j+1): + for m1 in arange(-j, j + 1): + for m2 in arange(-j, j + 1): v += ( - (-1)**(2*j - k - m1 - m2) - * np.sqrt((2*k+1)/(2*j+1)) - * qutip.clebsch(j, k, j, -m1, q, -m2) - * rho.data[j - m1, j - m2] + (-1) ** (2 * j - k - m1 - m2) + * np.sqrt((2 * k + 1) / (2 * j + 1)) + * qutip.clebsch(j, k, j, -m1, q, -m2) + * rho.data[j - m1, j - m2] ) return v @@ -959,7 +965,8 @@ def spin_wigner(rho, theta, phi): References ----- - [1] Agarwal, G. S. (1981). Phys. Rev. A, 24(6), 2889–2896. https://doi.org/10.1103/PhysRevA.24.2889 + [1] Agarwal, G. S. (1981). Phys. Rev. A, 24(6), 2889–2896. + https://doi.org/10.1103/PhysRevA.24.2889 [2] Dowling, J. P., Agarwal, G. S., & Schleich, W. P. (1994). Phys. Rev. A, 49(5), 4101–4109. https://doi.org/10.1103/PhysRevA.49.4101 [3] Conversion between Wigner 3-j symbol and Clebsch-Gordan coefficients @@ -979,9 +986,9 @@ def spin_wigner(rho, theta, phi): W = np.zeros_like(THETA, dtype=complex) - for k in range(int(2 * j)+1): - for q in arange(-k, k+1): + for k in range(int(2 * j) + 1): + for q in arange(-k, k + 1): # sph_harm takes azimuthal angle then polar angle as arguments W += _rho_kq(rho, j, k, q) * sph_harm(q, k, PHI, THETA) - return W*np.sqrt((2*j + 1)/(4*pi)), THETA, PHI + return W * np.sqrt((2 * j + 1) / (4 * pi)), THETA, PHI From 48bfc6d5a30414eac186a3752289c170e7010cb5 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 14:48:24 +1000 Subject: [PATCH 045/112] PEP8 --- qutip/wigner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index f391dc10d7..9305e02638 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -514,9 +514,9 @@ def _wig_laguerre_val(L, x, c): y1 = c[-1] for i in range(3, len(c) + 1): k -= 1 - y0, y1 = c[-i] - y1 * ( - float((k - 1) * (L + k - 1)) / ((L + k) * k)) ** 0.5, \ - y0 - y1 * ((L + 2 * k - 1) - x) * ((L + k) * k) ** -0.5 + y0, y1 = c[-i] - y1 * (float((k - 1) * (L + k - 1)) / ( + (L + k) * k)) ** 0.5, y0 - y1 * ( + (L + 2 * k - 1) - x) * ((L + k) * k) ** -0.5 return y0 - y1 * ((L + 1) - x) * (L + 1) ** -0.5 From 6bdebfa7e0c4c4f88c179d40596b1716d8b94999 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Mon, 9 Aug 2021 15:29:08 +1000 Subject: [PATCH 046/112] Fix bug in test_spin_q_function --- qutip/tests/test_wigner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 9f23a9ab40..f4bbbc7ce6 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -616,7 +616,7 @@ def test_spin_q_function(spin, pure): for k, (p, t) in enumerate(itertools.product(phi_prime, theta_prime)): state = qutip.spin_coherent(spin, t, p) - direct_Q = (state.dag() * rho * state).norm() * (2 * j + 1) / (4 * pi) + direct_Q = (state.dag() * rho * state).norm() * (2 * spin + 1) / (4 * np.pi) assert_almost_equal(Q.flat[k], direct_Q, decimal=9) @pytest.mark.parametrize(["spin"], [ From c0203b7455e13747a15ec460dab0f3ca9df45dc4 Mon Sep 17 00:00:00 2001 From: maij Date: Tue, 10 Aug 2021 08:47:40 +1000 Subject: [PATCH 047/112] Fix broken indent in test_wigner.py --- qutip/tests/test_wigner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index f4bbbc7ce6..7c91dd00ce 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -611,8 +611,8 @@ def test_spin_q_function(spin, pure): # Points at which to evaluate the spin Q function theta_prime = np.linspace(0, np.pi, 32, endpoint=True) - phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) - Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) + phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) + Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) for k, (p, t) in enumerate(itertools.product(phi_prime, theta_prime)): state = qutip.spin_coherent(spin, t, p) From 1ec37a46755477907c2a1f92a7cec20c94d2f31f Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Tue, 10 Aug 2021 09:26:08 +1000 Subject: [PATCH 048/112] Ensure integer indexing into array --- qutip/wigner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 9305e02638..c1432427c2 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -939,7 +939,7 @@ def _rho_kq(rho, j, k, q): (-1) ** (2 * j - k - m1 - m2) * np.sqrt((2 * k + 1) / (2 * j + 1)) * qutip.clebsch(j, k, j, -m1, q, -m2) - * rho.data[j - m1, j - m2] + * rho.data[int(j - m1), int(j - m2)] ) return v From 85da35b67c995345d22bce9d3e2031da487e8330 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Tue, 10 Aug 2021 10:47:44 +1000 Subject: [PATCH 049/112] Fix docstrings for sphinx --- qutip/wigner.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index c1432427c2..2ad91aab82 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -856,21 +856,17 @@ def qfunc( # PSEUDO DISTRIBUTION FUNCTIONS FOR SPINS # def spin_q_function(rho, theta, phi): - """Husimi Q-function for spins. - - The Husimi Q function for spins is defined as: - Q(theta, phi) = /pi - where |s,theta,phi> is the spin coherent state for a spin with length s - and pointing along (theta,phi). The Q function can be calculated using - the elements of rho using the definition of spin coherent states (e.g. - Loh and Kim 2015 doi: 10.1119/1.4898595) + r"""The Husimi Q function for spins is defined as + ``Q(theta, phi) = SCS.dag()* rho * SCS*(2*j+1)/(4*pi)`` for the spin + coherent state ``SCS = spin_coherent(j, theta, phi)``. + The implementation here is more efficient (see references). Parameters ---------- state : qobj A state vector or density matrix for a spin-j quantum system. theta : array_like - Polar angle at which to calculate the Husimi-Q function. + Polar (colatitude) angle at which to calculate the Husimi-Q function. phi : array_like Azimuthal angle at which to calculate the Husimi-Q function. @@ -880,6 +876,11 @@ def spin_q_function(rho, theta, phi): Values representing the spin Husimi Q function at the values specified by THETA and PHI. + References + ---------- + [1] Lee Loh, Y., & Kim, M. (2015). American J. of Phys., 83(1), 30–35. + https://doi.org/10.1119/1.4898595 + """ if rho.type == 'bra': @@ -945,15 +946,14 @@ def _rho_kq(rho, j, k, q): def spin_wigner(rho, theta, phi): - """Wigner function for a spin-j system on the 2-sphere of radius j - (for j = 1/2 this is the Bloch sphere). + r"""Wigner function for a spin-j system. Parameters ---------- state : qobj A state vector or density matrix for a spin-j quantum system. theta : array_like - Polar angle at which to calculate the W function. + Polar (colatitude) angle at which to calculate the W function. phi : array_like Azimuthal angle at which to calculate the W function. @@ -964,13 +964,16 @@ def spin_wigner(rho, theta, phi): by THETA and PHI. References - ----- + ---------- [1] Agarwal, G. S. (1981). Phys. Rev. A, 24(6), 2889–2896. https://doi.org/10.1103/PhysRevA.24.2889 + [2] Dowling, J. P., Agarwal, G. S., & Schleich, W. P. (1994). Phys. Rev. A, 49(5), 4101–4109. https://doi.org/10.1103/PhysRevA.49.4101 + [3] Conversion between Wigner 3-j symbol and Clebsch-Gordan coefficients taken from Wikipedia (https://en.wikipedia.org/wiki/3-j_symbol) + """ if rho.type == 'bra': From 3a4cdd1a615d49d47d229f27aa3675cb27dc7840 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Tue, 10 Aug 2021 19:26:43 +1000 Subject: [PATCH 050/112] Tidy up spin quasiprobability tests, add normalization check for spin Q --- qutip/tests/test_wigner.py | 78 ++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 7c91dd00ce..16c27e686f 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -610,15 +610,38 @@ def test_spin_q_function(spin, pure): rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Q function - theta_prime = np.linspace(0, np.pi, 32, endpoint=True) - phi_prime = np.linspace(-np.pi, np.pi, 64, endpoint=True) - Q, _, _ = qutip.spin_q_function(rho, theta_prime, phi_prime) + theta = np.linspace(0, np.pi, 32, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 64, endpoint=True) + Q, _, _ = qutip.spin_q_function(rho, theta, phi) - for k, (p, t) in enumerate(itertools.product(phi_prime, theta_prime)): - state = qutip.spin_coherent(spin, t, p) - direct_Q = (state.dag() * rho * state).norm() * (2 * spin + 1) / (4 * np.pi) + for k, (phi_prime, theta_prime) in enumerate(itertools.product(phi, theta)): + state = qutip.spin_coherent(spin, theta_prime, phi_prime) + direct_Q = (2 * spin + 1) / (4 * np.pi) * (state.dag() * rho * state).norm() assert_almost_equal(Q.flat[k], direct_Q, decimal=9) +@pytest.mark.parametrize(['spin'], [ + pytest.param(1/2, id="spin-one-half"), + pytest.param(3, id="spin-three"), + pytest.param(13/2, id="spin-thirteen-half"), + pytest.param(7, id="spin-seven") +]) +@pytest.mark.parametrize("pure", [ + pytest.param(True, id="pure"), + pytest.param(False, id="mixed") +]) +def test_spin_q_function_normalized(spin, pure): + d = int(2 * spin + 1) + rho = rand_dm(d, pure=pure) + + # Points at which to evaluate the spin Q function + theta = np.linspace(0, np.pi, 512, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 1024, endpoint=True) + Q, THETA, _ = qutip.spin_q_function(rho, theta, phi) + + norm = np.trapz(np.trapz(Q * np.sin(THETA), theta), phi) + assert_almost_equal(norm, 1, decimal=5) + + @pytest.mark.parametrize(["spin"], [ pytest.param(1/2, id="spin-one-half"), pytest.param(3, id="spin-three"), @@ -634,17 +657,42 @@ def test_spin_wigner_normalized_and_real(spin, pure): rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Wigner function - theta_prime = np.linspace(0, np.pi, 128, endpoint=True) - phi_prime = np.linspace(-np.pi, np.pi, 256, endpoint=True) - W, THETA, PHI = qutip.spin_wigner(rho, theta_prime, phi_prime) + theta = np.linspace(0, np.pi, 256, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 512, endpoint=True) + W, THETA, PHI = qutip.spin_wigner(rho, theta, phi) + + assert_allclose(W.imag, 0, atol=1e-9) - assert_allclose(W.imag, 0, atol=1e-9, - err_msg=f"Wigner function is non-real with maximum " - f"imaginary value {np.max(W.imag)}") + norm = np.trapz(np.trapz(W * np.sin(THETA), theta), phi) + assert_almost_equal(norm, 1, decimal=4) - norm = np.trapz(np.trapz(W * np.sin(THETA), theta_prime), phi_prime) - assert_almost_equal(norm.real, 1, decimal=3, - err_msg=f"Wigner function is not normalized.") +@pytest.mark.parametrize(['spin'], [ + pytest.param(1/2, id="spin-one-half"), + pytest.param(3, id="spin-three"), + pytest.param(13/2, id="spin-thirteen-half"), + pytest.param(7, id="spin-seven") +]) +@pytest.mark.parametrize("pure", [ + pytest.param(True, id="pure"), + pytest.param(False, id="mixed") +]) +def test_spin_wigner_overlap(spin, pure, n=10): + d = int(2*spin + 1) + rho = rand_dm(d, pure=pure) + + # Points at which to evaluate the spin Wigner function + theta = np.linspace(0, np.pi, 256, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 512, endpoint=True) + W, THETA, _ = qutip.spin_wigner(rho, theta, phi) + + for k in range(n): + test_state = rand_dm(d) + state_overlap = (test_state*rho).tr().real + + W_state, _, _ = qutip.spin_wigner(test_state, theta, phi) + W_overlap = (4 * np.pi / (2 * spin + 1)) * np.trapz( + np.trapz(W_state * W * np.sin(THETA), theta), phi).real + assert_almost_equal(W_overlap, state_overlap, decimal=4) if __name__ == "__main__": run_module_suite() From eb604a165260f4c897a11a1a7955e1e6fed1e17d Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Fri, 20 Aug 2021 17:47:23 +0800 Subject: [PATCH 051/112] drafting the implementation of passing multiple callable functions for performing measurements --- qutip/solver.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/qutip/solver.py b/qutip/solver.py index 67634d4b7b..25e818e93b 100644 --- a/qutip/solver.py +++ b/qutip/solver.py @@ -43,14 +43,25 @@ def __init__(self, e_ops=[], super_=False): self.e_ops = e_ops if isinstance(e_ops, list): self.e_num = len(e_ops) - self.e_ops_isherm = [e.isherm for e in e_ops] - if not super_: - self.e_ops_qoevo = np.array([QobjEvo(e) for e in e_ops], - dtype=object) - else: - self.e_ops_qoevo = np.array([QobjEvo(spre(e)) for e in e_ops], - dtype=object) - [op.compile() for op in self.e_ops_qoevo] + e_ops_qoevo = [] + e_ops_isherm = [] + for e in e_ops: + if isinstance(e, (Qobj, QobjEvo)): + e_ops_isherm.append(e.isherm) + e_ops_qoevo_entry = None + if not super_: + e_ops_qoevo_entry = QobjEvo(e) + else: + e_ops_qoevo_entry = QobjEvo(spre(e)) + e_ops_qoevo_entry.compile() + e_ops_qoevo.append(e_ops_qoevo_entry) + elif callable(e): + e_ops_isherm.append(None) + e_ops_qoevo.append(e) + else: + raise TypeError("Expectation value list entry needs to be either a function either an operator") + self.e_ops_isherm = e_ops_isherm + self.e_ops_qoevo = np.array(e_ops_qoevo, dtype=object) elif callable(e_ops): self.isfunc = True self.e_num = 1 @@ -79,9 +90,12 @@ def step(self, iter_, state): else: t = self.tlist[iter_] for ii in range(self.e_num): - self.raw_out[ii, iter_] = \ - self.e_ops_qoevo[ii].compiled_qobjevo.expect(t, state) - + if isinstance(ii, (Qobj, QobjEvo)): + self.raw_out[ii, iter_] = \ + self.e_ops_qoevo[ii].compiled_qobjevo.expect(t, state) + elif callable(ii): + self.raw_out[ii, iter_] = \ + self.e_ops_qoevo[ii](t, state) def finish(self): if self.isfunc: result = self.raw_out From 57d859846a81750895543dd9c012fd29bacbdeb4 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Wed, 25 Aug 2021 14:28:49 +1000 Subject: [PATCH 052/112] Revert "Whole wigner.py PEP8" This reverts commit 952910f25b168d20770b499936e5eb46cbd9900e. --- qutip/tests/test_wigner.py | 4 +- qutip/wigner.py | 134 ++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 72 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 16c27e686f..a317791c52 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -606,7 +606,7 @@ def test_wigner_clenshaw_sp_iter_dm(): pytest.param(False, id="mixed") ]) def test_spin_q_function(spin, pure): - d = int(2 * spin + 1) + d = int(2*spin + 1) rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Q function @@ -653,7 +653,7 @@ def test_spin_q_function_normalized(spin, pure): pytest.param(False, id="mixed") ]) def test_spin_wigner_normalized_and_real(spin, pure): - d = int(2 * spin + 1) + d = int(2*spin + 1) rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Wigner function diff --git a/qutip/wigner.py b/qutip/wigner.py index 2ad91aab82..7f17c96e48 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -72,7 +72,7 @@ def wigner_transform(psi, j, fullparity, steps, slicearray): else: rho = psi - sun = 2 # The order of the SU group + sun = 2 # The order of the SU group # calculate total number of particles in quantum state: N = np.int32(np.log(np.shape(rho)[0]) / np.log(2 * j + 1)) @@ -88,7 +88,7 @@ def wigner_transform(psi, j, fullparity, steps, slicearray): wigner = np.zeros((steps, steps)) if fullparity: - pari = _parity(sun ** N, j) + pari = _parity(sun**N, j) else: pari = _parity(sun, j) for t in range(steps): @@ -131,7 +131,7 @@ def _kernelsu2(theta, phi, N, j, parity, fullparity): for i in range(0, N): U = np.kron(U, _rotation_matrix(theta[i], phi[i], j)) if not fullparity: - op_parity = parity # The parity for a one particle system + op_parity = parity # The parity for a one particle system for i in range(1, N): parity = np.kron(parity, op_parity) matrix = U @ parity @ U.conj().T @@ -184,17 +184,16 @@ def wigner(psi, xvec, yvec, method='clenshaw', g=sqrt(2), value `hbar=1`. method : string {'clenshaw', 'iterative', 'laguerre', 'fft'} - Select method 'clenshaw' 'iterative', 'laguerre', or 'fft', - where 'clenshaw' and 'iterative' use an iterative method to evaluate - the Wigner functions for density matrices :math:`|m>~50). 'clenshaw' is a fast and numerically stable method. + Select method 'clenshaw' 'iterative', 'laguerre', or 'fft', where 'clenshaw' + and 'iterative' use an iterative method to evaluate the Wigner functions for density + matrices :math:`|m>~50). 'clenshaw' is a fast and numerically stable method. sparse : bool {False, True} Tells the default solver whether or not to keep the input density @@ -408,19 +407,18 @@ def _wigner_fft(psi, xvec): Evaluates the Fourier transformation of a given state vector. Returns the corresponding density matrix and range """ - n = 2 * len(psi.T) + n = 2*len(psi.T) r1 = np.concatenate((np.array([[0]]), - np.fliplr(psi.conj()), - np.zeros((1, n // 2 - 1))), axis=1) + np.fliplr(psi.conj()), + np.zeros((1, n//2 - 1))), axis=1) r2 = np.concatenate((np.array([[0]]), psi, - np.zeros((1, n // 2 - 1))), axis=1) - w = la.toeplitz(np.zeros((n // 2, 1)), r1) * \ - np.flipud(la.toeplitz(np.zeros((n // 2, 1)), r2)) - w = np.concatenate((w[:, n // 2:n], w[:, 0:n // 2]), axis=1) + np.zeros((1, n//2 - 1))), axis=1) + w = la.toeplitz(np.zeros((n//2, 1)), r1) * \ + np.flipud(la.toeplitz(np.zeros((n//2, 1)), r2)) + w = np.concatenate((w[:, n//2:n], w[:, 0:n//2]), axis=1) w = ft.fft(w) - w = np.real( - np.concatenate((w[:, 3 * n // 4:n + 1], w[:, 0:n // 4]), axis=1)) - p = np.arange(-n / 4, n / 4) * np.pi / (n * (xvec[1] - xvec[0])) + w = np.real(np.concatenate((w[:, 3*n//4:n+1], w[:, 0:n//4]), axis=1)) + p = np.arange(-n/4, n/4)*np.pi / (n*(xvec[1] - xvec[0])) w = w / (p[1] - p[0]) / n return w, p @@ -440,7 +438,7 @@ def _osc_eigen(N, pnts): A[1, :] = np.sqrt(2) * pnts * A[0, :] for k in range(2, N): A[k, :] = np.sqrt(2.0 / k) * pnts * A[k - 1, :] - \ - np.sqrt((k - 1.0) / k) * A[k - 2, :] + np.sqrt((k - 1.0) / k) * A[k - 2, :] return A @@ -456,34 +454,33 @@ def _wigner_clenshaw(rho, xvec, yvec, g=sqrt(2), sparse=False): """ M = np.prod(rho.shape[0]) - X, Y = np.meshgrid(xvec, yvec) - # A = 0.5 * g * (X + 1.0j * Y) - A2 = g * (X + 1.0j * Y) # this is A2 = 2*A + X,Y = np.meshgrid(xvec, yvec) + #A = 0.5 * g * (X + 1.0j * Y) + A2 = g * (X + 1.0j * Y) #this is A2 = 2*A B = np.abs(A2) B *= B - w0 = (2 * rho.data[0, -1]) * np.ones_like(A2) - L = M - 1 - # calculation of \sum_{L} c_L (2x)^L / \sqrt(L!) - # using Horner's method + w0 = (2*rho.data[0,-1])*np.ones_like(A2) + L = M-1 + #calculation of \sum_{L} c_L (2x)^L / \sqrt(L!) + #using Horner's method if not sparse: - rho = rho.full() * (2 * np.ones((M, M)) - np.diag(np.ones(M))) + rho = rho.full() * (2*np.ones((M,M)) - np.diag(np.ones(M))) while L > 0: L -= 1 - # here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) - w0 = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 * A2 * ( - L + 1) ** -0.5 + #here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 * A2 * (L+1)**-0.5 else: while L > 0: L -= 1 - diag = _csr_get_diag(rho.data.data, rho.data.indices, - rho.data.indptr, L) + diag = _csr_get_diag(rho.data.data,rho.data.indices, + rho.data.indptr,L) if L != 0: diag *= 2 - # here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) - w0 = _wig_laguerre_val(L, B, diag) + w0 * A2 * (L + 1) ** -0.5 + #here c_L = _wig_laguerre_val(L, B, np.diag(rho, L)) + w0 = _wig_laguerre_val(L, B, diag) + w0 * A2 * (L+1)**-0.5 - return w0.real * np.exp(-B * 0.5) * (g * g * 0.5 / pi) + return w0.real * np.exp(-B*0.5) * (g*g*0.5 / pi) def _wig_laguerre_val(L, x, c): @@ -514,11 +511,10 @@ def _wig_laguerre_val(L, x, c): y1 = c[-1] for i in range(3, len(c) + 1): k -= 1 - y0, y1 = c[-i] - y1 * (float((k - 1) * (L + k - 1)) / ( - (L + k) * k)) ** 0.5, y0 - y1 * ( - (L + 2 * k - 1) - x) * ((L + k) * k) ** -0.5 + y0, y1 = c[-i] - y1 * (float((k - 1)*(L + k - 1))/((L+k)*k))**0.5, \ + y0 - y1 * ((L + 2*k -1) - x) * ((L+k)*k)**-0.5 - return y0 - y1 * ((L + 1) - x) * (L + 1) ** -0.5 + return y0 - y1 * ((L + 1) - x) * (L + 1)**-0.5 # ----------------------------------------------------------------------------- @@ -530,10 +526,10 @@ def _qfunc_check_state(state: Qobj): # This is only approximate, but it's enough for our purposes; doing more # than this would take computational effort we don't _need_ to do. isdm = ( - state.isoper - and state.dims[0] == state.dims[1] - and state.isherm - and abs(state.tr() - 1) < qutip.settings.atol + state.isoper + and state.dims[0] == state.dims[1] + and state.isherm + and abs(state.tr() - 1) < qutip.settings.atol ) if not (state.isket or isdm): raise ValueError( @@ -556,8 +552,7 @@ def _qfunc_check_coordinates(xvec, yvec): yvec = np.asarray(yvec, dtype=np.float64) if xvec.ndim != 1 or yvec.ndim != 1: raise ValueError( - f"xvec and yvec must be 1D, but have shapes {xvec.shape} and " - f"{yvec.shape}." + f"xvec and yvec must be 1D, but have shapes {xvec.shape} and {yvec.shape}." ) return xvec, yvec @@ -600,7 +595,6 @@ class _QFuncCoherentGrid: >>> np.allclose(naive[:, :, 4:7], grid(4, 7)) True """ - def __init__(self, xvec, yvec, g: float): self.xvec, self.yvec = _qfunc_check_coordinates(xvec, yvec) x, y = np.meshgrid(0.5 * g * self.xvec, 0.5 * g * self.yvec) @@ -641,7 +635,7 @@ def __call__(self, first: int, last: int = None): out = np.empty(self.grid.shape + (ns.size,), dtype=np.complex128) out[:, :, 0] = self._start(ns.flat[0]) for i in range(ns.size - 1): - out[:, :, i + 1] = out[:, :, i] * self.grid + out[:, :, i+1] = out[:, :, i] * self.grid out /= np.sqrt(scipy.special.factorial(ns)) return out @@ -695,7 +689,7 @@ class QFunc: """ def __init__( - self, xvec, yvec, g: float = np.sqrt(2), memory: float = 1024 + self, xvec, yvec, g: float = np.sqrt(2), memory: float = 1024 ): self._g = g self._coherent_grid = _QFuncCoherentGrid(xvec, yvec, g) @@ -758,7 +752,7 @@ def __call__(self, state: Qobj): def _qfunc_iterative_single( - vector: np.ndarray, alpha_grid: _QFuncCoherentGrid, g: float, + vector: np.ndarray, alpha_grid: _QFuncCoherentGrid, g: float, ): r""" Get the Q function (without the :math:`\pi` scaling factor) of a single @@ -767,19 +761,19 @@ def _qfunc_iterative_single( """ ns = np.arange(vector.shape[0]) out = np.polyval( - (0.5 * g * vector / np.sqrt(scipy.special.factorial(ns)))[::-1], + (0.5*g * vector / np.sqrt(scipy.special.factorial(ns)))[::-1], alpha_grid.grid, ) out *= alpha_grid.prefactor - return np.abs(out) ** 2 + return np.abs(out)**2 def qfunc( - state: Qobj, - xvec, - yvec, - g: float = sqrt(2), - precompute_memory: float = 1024, + state: Qobj, + xvec, + yvec, + g: float = sqrt(2), + precompute_memory: float = 1024, ): r""" Husimi-Q function of a given state vector or density matrix at phase-space @@ -824,8 +818,8 @@ def qfunc( xvec, yvec = _qfunc_check_coordinates(xvec, yvec) required_memory = state.shape[0] * xvec.size * yvec.size * 16 / (1024 ** 2) enough_memory = ( - precompute_memory is not None - and precompute_memory > required_memory + precompute_memory is not None + and precompute_memory > required_memory ) if state.isoper and enough_memory: return QFunc(xvec, yvec, g)(state) @@ -908,7 +902,7 @@ def spin_q_function(rho, theta, phi): (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) - return Q.real * (2 * j + 1) / (4 * pi), THETA, PHI + return Q.real * (2 * j + 1) / (4*pi), THETA, PHI def _rho_kq(rho, j, k, q): @@ -934,8 +928,8 @@ def _rho_kq(rho, j, k, q): """ v = 0j - for m1 in arange(-j, j + 1): - for m2 in arange(-j, j + 1): + for m1 in arange(-j, j+1): + for m2 in arange(-j, j+1): v += ( (-1) ** (2 * j - k - m1 - m2) * np.sqrt((2 * k + 1) / (2 * j + 1)) @@ -989,9 +983,9 @@ def spin_wigner(rho, theta, phi): W = np.zeros_like(THETA, dtype=complex) - for k in range(int(2 * j) + 1): - for q in arange(-k, k + 1): + for k in range(int(2 * j)+1): + for q in arange(-k, k+1): # sph_harm takes azimuthal angle then polar angle as arguments W += _rho_kq(rho, j, k, q) * sph_harm(q, k, PHI, THETA) - return W * np.sqrt((2 * j + 1) / (4 * pi)), THETA, PHI + return W*np.sqrt((2*j + 1)/(4*pi)), THETA, PHI From 1144ecc5578c2c0d7919874d372a037d58614314 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 27 Aug 2021 14:06:57 -0400 Subject: [PATCH 053/112] fix test using qzero as mcsolve's c_op --- qutip/tests/test_interpolate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/test_interpolate.py b/qutip/tests/test_interpolate.py index 81f4e5caa3..1028f1abd2 100644 --- a/qutip/tests/test_interpolate.py +++ b/qutip/tests/test_interpolate.py @@ -78,7 +78,7 @@ def _parametrize_solver_coefficients(metafunc): """ size = 10 times = np.linspace(0, 5, 50) - c_ops = [qutip.qzero(size)] + c_ops = [qutip.qeye(size)] solvers = [ (qutip.sesolve, 'sesolve'), (functools.partial(qutip.mesolve, c_ops=c_ops), 'mesolve'), From d08058b2cc134947d1a3838d960a5af26fa49ab7 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 31 Aug 2021 14:36:40 +0200 Subject: [PATCH 054/112] Close matplotlib figure windows cleanly so that pytest-xvfb does not produce an obscure error at the end of the test. --- qutip/tests/test_processor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qutip/tests/test_processor.py b/qutip/tests/test_processor.py index 0661b20384..730a165147 100644 --- a/qutip/tests/test_processor.py +++ b/qutip/tests/test_processor.py @@ -142,8 +142,10 @@ def testPlot(self): processor.add_control(sigmaz()) processor.pulses[0].tlist = tlist processor.pulses[0].coeff = np.array([np.sin(t) for t in tlist]) - processor.plot_pulses() - plt.clf() + fig, _ = processor.plot_pulses() + # testing under Xvfb with pytest-xvfb complains if figure windows are + # left open, so we politely close it: + plt.close(fig) # cubic spline tlist = np.linspace(0., 2*np.pi, 20) @@ -151,8 +153,10 @@ def testPlot(self): processor.add_control(sigmaz()) processor.pulses[0].tlist = tlist processor.pulses[0].coeff = np.array([np.sin(t) for t in tlist]) - processor.plot_pulses() - plt.clf() + fig, _ = processor.plot_pulses() + # testing under Xvfb with pytest-xvfb complains if figure windows are + # left open, so we politely close it: + plt.close(fig) def testSpline(self): """ From 4ba6c3a1b16bfbaf85065191f2a03e58d62e55af Mon Sep 17 00:00:00 2001 From: Jon Crall Date: Tue, 7 Sep 2021 00:02:01 -0400 Subject: [PATCH 055/112] Minor doc fix in QubitCircuit The `num_cbits` wasn't documented in the QubitCircuit docstring. This adds it. Took me a second to figure out what it was (as a novice in quantum computing), so I figured I'd save some other novice that second. FWIW: `reverse_states` isn't documented either, but I'm not confident I can write an accurate description of what that is, otherwise I would have put it here too. --- qutip/qip/circuit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutip/qip/circuit.py b/qutip/qip/circuit.py index e92127e713..b3be05a0ea 100644 --- a/qutip/qip/circuit.py +++ b/qutip/qip/circuit.py @@ -364,6 +364,8 @@ class QubitCircuit: A list of integer for the dimension of each composite system. e.g [2,2,2,2,2] for 5 qubits system. If None, qubits system will be the default option. + num_cbits : int + Number of classical bits in the system. Examples -------- From f3580f770b5bc3f67b54ce90cfc7dbd1551b5960 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Sep 2021 03:14:22 +0000 Subject: [PATCH 056/112] Bump pillow from 8.2.0 to 8.3.2 in /doc Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.2.0 to 8.3.2. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/8.2.0...8.3.2) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index f6da069ab6..d8dc1296e5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -23,7 +23,7 @@ packaging==20.9 parso==0.8.2 pexpect==4.8.0 pickleshare==0.7.5 -Pillow==8.2.0 +Pillow==8.3.2 prompt-toolkit==3.0.18 ptyprocess==0.7.0 Pygments==2.8.1 From f994be6e6d8783ca63bb563fd762ce3747082d81 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Wed, 8 Sep 2021 16:59:16 +0800 Subject: [PATCH 057/112] measurement operators can be integers, tests fixed --- qutip/solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/solver.py b/qutip/solver.py index 25e818e93b..d905fc9c81 100644 --- a/qutip/solver.py +++ b/qutip/solver.py @@ -90,7 +90,7 @@ def step(self, iter_, state): else: t = self.tlist[iter_] for ii in range(self.e_num): - if isinstance(ii, (Qobj, QobjEvo)): + if isinstance(ii, (Qobj, QobjEvo, int)): self.raw_out[ii, iter_] = \ self.e_ops_qoevo[ii].compiled_qobjevo.expect(t, state) elif callable(ii): From 644f2d1f15b7a2d366c2530eb8f92c5cd2b9bf77 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Thu, 9 Sep 2021 17:22:21 +0800 Subject: [PATCH 058/112] debugged and developped a test --- qutip/mesolve.py | 6 ++++++ qutip/sesolve.py | 19 +++++++++++++++++-- qutip/tests/test_expect.py | 16 +++++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index 180ac569a3..a3184db8c2 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -458,6 +458,9 @@ def _generic_ode_solve(func, ode_args, rho0, tlist, e_ops, opt, opt.store_states = True else: for op in e_ops: + if not isinstance(op, (Qobj, QobjEvo)) and callable(op): + output.expect.append(np.zeros(n_tsteps, dtype=complex)) + continue e_ops_data.append(spre(op).data) if op.isherm and rho0.isherm: output.expect.append(np.zeros(n_tsteps)) @@ -504,6 +507,9 @@ def get_curr_state_data(r): output.expect.append(e_ops(t, rho_t)) for m in range(n_expt_op): + if not isinstance(e_ops[m], (Qobj, QobjEvo)) and callable(e_ops[m]): + output.expect[m][t_idx] = e_ops[m](t, rho_t) + continue output.expect[m][t_idx] = expect_rho_vec(e_ops_data[m], r.y, e_ops[m].isherm and rho0.isherm) diff --git a/qutip/sesolve.py b/qutip/sesolve.py index 75522d36c5..9f420a392a 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -275,16 +275,25 @@ def _generic_ode_solve(func, ode_args, psi0, tlist, e_ops, opt, opt.store_states = True else: for op in e_ops: + if not isinstance(op, (Qobj, QobjEvo)) and callable(op): + output.expect.append(np.zeros(n_tsteps, dtype=complex)) + continue if op.isherm: output.expect.append(np.zeros(n_tsteps)) else: output.expect.append(np.zeros(n_tsteps, dtype=complex)) if oper_evo: for e in e_ops: - e_ops_data.append(e.dag().data) + if isinstance(e, (Qobj, QobjEvo)): + e_ops_data.append(e.dag().data) + continue + e_ops_data.append(e) else: for e in e_ops: - e_ops_data.append(e.data) + if isinstance(e, (Qobj, QobjEvo)): + e_ops_data.append(e.data) + continue + e_ops_data.append(e) else: raise TypeError("Expectation parameter must be a list or a function") @@ -343,9 +352,15 @@ def get_curr_state_data(r): if oper_evo: for m in range(n_expt_op): + if callable(e_ops_data[m]): + output.expect[m][t_idx] = e_ops_data[m](t, Qobj(cdata, dims=dims)) + continue output.expect[m][t_idx] = (e_ops_data[m] * cdata).trace() else: for m in range(n_expt_op): + if callable(e_ops_data[m]): + output.expect[m][t_idx] = e_ops_data[m](t, Qobj(cdata, dims=dims)) + continue output.expect[m][t_idx] = cy_expect_psi(e_ops_data[m], cdata, e_ops[m].isherm) diff --git a/qutip/tests/test_expect.py b/qutip/tests/test_expect.py index b262f08186..678402a37f 100644 --- a/qutip/tests/test_expect.py +++ b/qutip/tests/test_expect.py @@ -132,17 +132,27 @@ def test_equivalent_to_matrix_element(hermitian): ]) def test_compatibility_with_solver(solve): e_ops = [getattr(qutip, 'sigma'+x)() for x in 'xyzmp'] + e_ops += [lambda t, psi: qutip.basis(2, 0).overlap(psi)] h = qutip.sigmax() state = qutip.basis(2, 0) times = np.linspace(0, 10, 101) options = qutip.Options(store_states=True) result = solve(h, state, times, e_ops=e_ops, options=options) direct, states = result.expect, result.states - indirect = qutip.expect(e_ops, states) - assert len(direct) == len(indirect) + indirect = qutip.expect(e_ops[:-1], states) + # check measurement operators based on quantum objects + assert len(direct)-1 == len(indirect) for direct_, indirect_ in zip(direct, indirect): assert len(direct_) == len(indirect_) assert isinstance(direct_, np.ndarray) assert isinstance(indirect_, np.ndarray) assert direct_.dtype == indirect_.dtype - np.testing.assert_allclose(direct_, indirect_, atol=1e-12) + # test measurement operators based on lambda functions + direct_ = direct[-1] + indirect_ = np.sin(times) + assert len(direct_) == len(indirect_) + assert isinstance(direct_, np.ndarray) + assert isinstance(indirect_, np.ndarray) + # by design, lambda measurements are of complex type + assert direct_.dtype != indirect_.dtype + From 56d954cc77de90fa72c363f968c54183feb5a24b Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Thu, 9 Sep 2021 17:33:07 +0800 Subject: [PATCH 059/112] documenting list of callbacks for measurements --- qutip/mesolve.py | 6 ++++-- qutip/sesolve.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index a3184db8c2..759d8b882c 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -127,8 +127,10 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. - e_ops : None / list of :class:`qutip.Qobj` / callback function single - single operator or list of operators for which to evaluate + e_ops : None / list of :class:`qutip.Qobj` / list of callback functions + / callback function + single operator, list of operators or list of callable functions + for which to evaluate expectation values. args : None / *dictionary* diff --git a/qutip/sesolve.py b/qutip/sesolve.py index 9f420a392a..df1f5e768a 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -54,8 +54,10 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, tlist : array_like of float List of times for :math:`t`. - e_ops : list of :class:`~Qobj` or callback function, optional - Single operator or list of operators for which to evaluate expectation + e_ops : list of :class:`~Qobj` or list of callback functions or + a single callback function, optional + Single operator, list of operators or list of callable functions + for which to evaluate expectation values. For operator evolution, the overlap is computed: :: (e_ops[i].dag() * op(t)).tr() From eecea5fc2b922e63919274ced90d72a15bfa24e0 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Thu, 9 Sep 2021 17:33:20 +0800 Subject: [PATCH 060/112] adding my name as the contributor --- doc/contributors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributors.rst b/doc/contributors.rst index 3a6e2f3494..1a2eba701a 100644 --- a/doc/contributors.rst +++ b/doc/contributors.rst @@ -79,7 +79,7 @@ Contributors - Louis Tessler - Lucas Verney - Marco David -- Marek +- Marek Narozniak - Markus Baden - Martín Sande - Mateo Laguna From 42aff8e1ceeeb773f912301b48889cd01d703c62 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Fri, 10 Sep 2021 14:29:21 +0800 Subject: [PATCH 061/112] implemented changes requested during code review --- qutip/mesolve.py | 2 +- qutip/solver.py | 2 +- qutip/tests/test_expect.py | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index 759d8b882c..ade73408ab 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -460,7 +460,7 @@ def _generic_ode_solve(func, ode_args, rho0, tlist, e_ops, opt, opt.store_states = True else: for op in e_ops: - if not isinstance(op, (Qobj, QobjEvo)) and callable(op): + if not isinstance(op, Qobj) and callable(op): output.expect.append(np.zeros(n_tsteps, dtype=complex)) continue e_ops_data.append(spre(op).data) diff --git a/qutip/solver.py b/qutip/solver.py index d905fc9c81..786e90039f 100644 --- a/qutip/solver.py +++ b/qutip/solver.py @@ -90,7 +90,7 @@ def step(self, iter_, state): else: t = self.tlist[iter_] for ii in range(self.e_num): - if isinstance(ii, (Qobj, QobjEvo, int)): + if isinstance(self.e_ops_qoevo[ii], QobjEvo): self.raw_out[ii, iter_] = \ self.e_ops_qoevo[ii].compiled_qobjevo.expect(t, state) elif callable(ii): diff --git a/qutip/tests/test_expect.py b/qutip/tests/test_expect.py index 678402a37f..4556811071 100644 --- a/qutip/tests/test_expect.py +++ b/qutip/tests/test_expect.py @@ -132,7 +132,7 @@ def test_equivalent_to_matrix_element(hermitian): ]) def test_compatibility_with_solver(solve): e_ops = [getattr(qutip, 'sigma'+x)() for x in 'xyzmp'] - e_ops += [lambda t, psi: qutip.basis(2, 0).overlap(psi)] + e_ops += [lambda t, psi: np.sin(t)] h = qutip.sigmax() state = qutip.basis(2, 0) times = np.linspace(0, 10, 101) @@ -147,12 +147,14 @@ def test_compatibility_with_solver(solve): assert isinstance(direct_, np.ndarray) assert isinstance(indirect_, np.ndarray) assert direct_.dtype == indirect_.dtype + np.testing.assert_allclose(direct_, indirect_, atol=1e-12) # test measurement operators based on lambda functions direct_ = direct[-1] - indirect_ = np.sin(times) + # by design, lambda measurements are of complex type + indirect_ = np.sin(times, dtype=complex) assert len(direct_) == len(indirect_) assert isinstance(direct_, np.ndarray) assert isinstance(indirect_, np.ndarray) - # by design, lambda measurements are of complex type - assert direct_.dtype != indirect_.dtype + assert direct_.dtype == indirect_.dtype + np.testing.assert_allclose(direct_, indirect_, atol=1e-12) From 45344ed3165f7a91701f066ef354fde77ca33079 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Fri, 10 Sep 2021 14:37:37 +0800 Subject: [PATCH 062/112] pep8 compliance of my contribution --- qutip/mesolve.py | 3 ++- qutip/sesolve.py | 9 ++++++--- qutip/solver.py | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index ade73408ab..54f4613263 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -509,7 +509,8 @@ def get_curr_state_data(r): output.expect.append(e_ops(t, rho_t)) for m in range(n_expt_op): - if not isinstance(e_ops[m], (Qobj, QobjEvo)) and callable(e_ops[m]): + if not isinstance(e_ops[m], (Qobj, QobjEvo)) \ + and callable(e_ops[m]): output.expect[m][t_idx] = e_ops[m](t, rho_t) continue output.expect[m][t_idx] = expect_rho_vec(e_ops_data[m], r.y, diff --git a/qutip/sesolve.py b/qutip/sesolve.py index df1f5e768a..febf73dd48 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -322,7 +322,8 @@ def get_curr_state_data(r): "the allowed number of substeps by increasing " "the nsteps parameter in the Options class.") # get the current state / oper data if needed - if opt.store_states or opt.normalize_output or n_expt_op > 0 or expt_callback: + if opt.store_states or opt.normalize_output \ + or n_expt_op > 0 or expt_callback: cdata = get_curr_state_data(r) if opt.normalize_output: @@ -355,13 +356,15 @@ def get_curr_state_data(r): if oper_evo: for m in range(n_expt_op): if callable(e_ops_data[m]): - output.expect[m][t_idx] = e_ops_data[m](t, Qobj(cdata, dims=dims)) + func = e_ops_data[m] + output.expect[m][t_idx] = func(t, Qobj(cdata, dims=dims)) continue output.expect[m][t_idx] = (e_ops_data[m] * cdata).trace() else: for m in range(n_expt_op): if callable(e_ops_data[m]): - output.expect[m][t_idx] = e_ops_data[m](t, Qobj(cdata, dims=dims)) + func = e_ops_data[m] + output.expect[m][t_idx] = func(t, Qobj(cdata, dims=dims)) continue output.expect[m][t_idx] = cy_expect_psi(e_ops_data[m], cdata, e_ops[m].isherm) diff --git a/qutip/solver.py b/qutip/solver.py index 786e90039f..4a71a5a541 100644 --- a/qutip/solver.py +++ b/qutip/solver.py @@ -59,7 +59,8 @@ def __init__(self, e_ops=[], super_=False): e_ops_isherm.append(None) e_ops_qoevo.append(e) else: - raise TypeError("Expectation value list entry needs to be either a function either an operator") + raise TypeError("Expectation value list entry needs to be " + "either a function either an operator") self.e_ops_isherm = e_ops_isherm self.e_ops_qoevo = np.array(e_ops_qoevo, dtype=object) elif callable(e_ops): @@ -96,6 +97,7 @@ def step(self, iter_, state): elif callable(ii): self.raw_out[ii, iter_] = \ self.e_ops_qoevo[ii](t, state) + def finish(self): if self.isfunc: result = self.raw_out From 5a8cb354459d72c90c8e996ce6b8551f2209f140 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sat, 11 Sep 2021 00:54:50 +0200 Subject: [PATCH 063/112] Added steadystate_floquet function --- qutip/steadystate.py | 83 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 3b055c21d9..00e9cdf063 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -4,7 +4,7 @@ collapse operators. """ -__all__ = ['steadystate', 'steady', 'build_preconditioner', +__all__ = ['steadystate', 'steady', 'steadystate_floquet', 'build_preconditioner', 'pseudo_inverse'] import functools @@ -912,6 +912,87 @@ def _iter_count(r): return rhoss, ss_args['info'] else: return rhoss + + +def steadystate_floquet(H_0, c_ops, Op_t, w_d = 1.0, nmax = 3, sparse = False): + """ + Calculates the effective steady state for a driven system with a time-dependent cosinusoidal term: + + .. math:: + + \\mathcal{\\hat{H}}(t) = \\hat{H}_0 + \\mathcal{\\hat{O}} \\cos(\\omega_d t) + + Parameters + ---------- + H_0 : :obj:`~Qobj` + A Hamiltonian or Liouvillian operator. + + c_ops : list + A list of collapse operators. + + Op_t : :obj:`~Qobj` + The the interaction operator which is multiplied by the cosine + + w_d : float, default 1.0 + The frequency of the drive + + nmax : int, default 3 + The maximum number of iteration for the solver + + sparse : bool, default False + Solve for the steady state using sparse algorithms. Actually, dense seems to be faster. + + Returns + ------- + dm : qobj + Steady state density matrix. + """ + if sparse: + N = H_0.shape[0] + + L_0 = liouvillian(H_0, c_ops).data.tocsc() + L_t = liouvillian(Op_t) + L_p = (0.5 * L_t).data.tocsc() + L_m = L_p #(0.5 * A_l * L_t).data + L_p_array = L_p.todense() + L_m_array = L_m.todense() + + Id = sp.eye(N ** 2, format = "csc", dtype = np.complex128) + S, T = sp.csc_matrix((N ** 2, N ** 2), dtype = np.complex128), sp.csc_matrix((N ** 2, N ** 2), dtype = np.complex128) + + for n_i in np.arange(nmax, 0, -1): + L = sp.csc_matrix( L_0 - 1j * n_i * w_d * Id + L_m.dot(S) ) + L.sort_indices() + LU = splu( L ) + S = - LU.solve(L_p_array) + + L = sp.csc_matrix( L_0 + 1j * n_i * w_d * Id + L_p.dot(T) ) + L.sort_indices() + LU = splu( L ) + T = - LU.solve(L_m_array) + + M_subs = L_0 + L_m.dot(S) + L_p.dot(T) + else: + N = H_0.shape[0] + + L_0 = liouvillian(H_0, c_ops).full() + L_t = liouvillian(Op_t) + L_p = (0.5 * L_t).full() + L_m = (0.5 * L_t).full() + + Id = np.eye(N ** 2) #qeye(N ** 2).full() + S, T = np.zeros((N ** 2, N ** 2)), np.zeros((N ** 2, N ** 2)) + + for n_i in np.arange(nmax, 0, -1): + lu, piv = la.lu_factor(L_0 - 1j * n_i * w_d * Id + np.matmul(L_m, S)) + S = - la.lu_solve((lu, piv), L_p) + + lu, piv = la.lu_factor(L_0 + 1j * n_i * w_d * Id + np.matmul(L_p, T)) + T = - la.lu_solve((lu, piv), L_m) + + M_subs = L_0 + np.matmul(L_m, S) + np.matmul(L_p, T) + + return steadystate(Qobj(M_subs, type = "super", dims = L_t.dims)) def build_preconditioner(A, c_op_list=[], **kwargs): From 0998361c7942242769d1891768f303b5fef213b0 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sat, 11 Sep 2021 02:34:55 +0200 Subject: [PATCH 064/112] Fixed the PEP-8 errors --- qutip/steadystate.py | 57 ++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 00e9cdf063..fd38ec2dc7 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -4,8 +4,8 @@ collapse operators. """ -__all__ = ['steadystate', 'steady', 'steadystate_floquet', 'build_preconditioner', - 'pseudo_inverse'] +__all__ = ['steadystate', 'steady', 'steadystate_floquet', + 'build_preconditioner', 'pseudo_inverse'] import functools import time @@ -912,16 +912,18 @@ def _iter_count(r): return rhoss, ss_args['info'] else: return rhoss - -def steadystate_floquet(H_0, c_ops, Op_t, w_d = 1.0, nmax = 3, sparse = False): + +def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, nmax=3, sparse=False): """ - Calculates the effective steady state for a driven system with a time-dependent cosinusoidal term: - + Calculates the effective steady state for a driven + system with a time-dependent cosinusoidal term: + .. math:: - \\mathcal{\\hat{H}}(t) = \\hat{H}_0 + \\mathcal{\\hat{O}} \\cos(\\omega_d t) - + \\mathcal{\\hat{H}}(t) = \\hat{H}_0 + + \\mathcal{\\hat{O}} \\cos(\\omega_d t) + Parameters ---------- H_0 : :obj:`~Qobj` @@ -929,19 +931,20 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d = 1.0, nmax = 3, sparse = False): c_ops : list A list of collapse operators. - + Op_t : :obj:`~Qobj` The the interaction operator which is multiplied by the cosine - + w_d : float, default 1.0 The frequency of the drive - + nmax : int, default 3 The maximum number of iteration for the solver - + sparse : bool, default False - Solve for the steady state using sparse algorithms. Actually, dense seems to be faster. - + Solve for the steady state using sparse algorithms. + Actually, dense seems to be faster. + Returns ------- dm : qobj @@ -953,22 +956,22 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d = 1.0, nmax = 3, sparse = False): L_0 = liouvillian(H_0, c_ops).data.tocsc() L_t = liouvillian(Op_t) L_p = (0.5 * L_t).data.tocsc() - L_m = L_p #(0.5 * A_l * L_t).data + L_m = L_p # (0.5 * A_l * L_t).data L_p_array = L_p.todense() L_m_array = L_m.todense() - Id = sp.eye(N ** 2, format = "csc", dtype = np.complex128) - S, T = sp.csc_matrix((N ** 2, N ** 2), dtype = np.complex128), sp.csc_matrix((N ** 2, N ** 2), dtype = np.complex128) + Id = sp.eye(N ** 2, format="csc", dtype=np.complex128) + S = T = sp.csc_matrix((N ** 2, N ** 2), dtype=np.complex128) for n_i in np.arange(nmax, 0, -1): - L = sp.csc_matrix( L_0 - 1j * n_i * w_d * Id + L_m.dot(S) ) + L = sp.csc_matrix(L_0 - 1j * n_i * w_d * Id + L_m.dot(S)) L.sort_indices() - LU = splu( L ) + LU = splu(L) S = - LU.solve(L_p_array) - L = sp.csc_matrix( L_0 + 1j * n_i * w_d * Id + L_p.dot(T) ) + L = sp.csc_matrix(L_0 + 1j * n_i * w_d * Id + L_p.dot(T)) L.sort_indices() - LU = splu( L ) + LU = splu(L) T = - LU.solve(L_m_array) M_subs = L_0 + L_m.dot(S) + L_p.dot(T) @@ -980,19 +983,21 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d = 1.0, nmax = 3, sparse = False): L_p = (0.5 * L_t).full() L_m = (0.5 * L_t).full() - Id = np.eye(N ** 2) #qeye(N ** 2).full() + Id = np.eye(N ** 2) S, T = np.zeros((N ** 2, N ** 2)), np.zeros((N ** 2, N ** 2)) for n_i in np.arange(nmax, 0, -1): - lu, piv = la.lu_factor(L_0 - 1j * n_i * w_d * Id + np.matmul(L_m, S)) + L = L_0 - 1j * n_i * w_d * Id + np.matmul(L_m, S) + lu, piv = la.lu_factor(L) S = - la.lu_solve((lu, piv), L_p) - lu, piv = la.lu_factor(L_0 + 1j * n_i * w_d * Id + np.matmul(L_p, T)) + L = L_0 + 1j * n_i * w_d * Id + np.matmul(L_p, T) + lu, piv = la.lu_factor(L) T = - la.lu_solve((lu, piv), L_m) M_subs = L_0 + np.matmul(L_m, S) + np.matmul(L_p, T) - - return steadystate(Qobj(M_subs, type = "super", dims = L_t.dims)) + + return steadystate(Qobj(M_subs, type="super", dims=L_t.dims)) def build_preconditioner(A, c_op_list=[], **kwargs): From 50948f9c9451773c1e0719d26549d056a80e6041 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Sat, 11 Sep 2021 10:44:30 +1000 Subject: [PATCH 065/112] Remove normalization from spin functions, make spin_wigner real --- qutip/tests/test_wigner.py | 12 +++++------- qutip/wigner.py | 24 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index a317791c52..8a060209cc 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -616,7 +616,7 @@ def test_spin_q_function(spin, pure): for k, (phi_prime, theta_prime) in enumerate(itertools.product(phi, theta)): state = qutip.spin_coherent(spin, theta_prime, phi_prime) - direct_Q = (2 * spin + 1) / (4 * np.pi) * (state.dag() * rho * state).norm() + direct_Q = (state.dag() * rho * state).norm() assert_almost_equal(Q.flat[k], direct_Q, decimal=9) @pytest.mark.parametrize(['spin'], [ @@ -638,7 +638,7 @@ def test_spin_q_function_normalized(spin, pure): phi = np.linspace(-np.pi, np.pi, 1024, endpoint=True) Q, THETA, _ = qutip.spin_q_function(rho, theta, phi) - norm = np.trapz(np.trapz(Q * np.sin(THETA), theta), phi) + norm = d / (4 * np.pi) * np.trapz(np.trapz(Q * np.sin(THETA), theta), phi) assert_almost_equal(norm, 1, decimal=5) @@ -652,7 +652,7 @@ def test_spin_q_function_normalized(spin, pure): pytest.param(True, id="pure"), pytest.param(False, id="mixed") ]) -def test_spin_wigner_normalized_and_real(spin, pure): +def test_spin_wigner_normalized(spin, pure): d = int(2*spin + 1) rho = rand_dm(d, pure=pure) @@ -661,9 +661,7 @@ def test_spin_wigner_normalized_and_real(spin, pure): phi = np.linspace(-np.pi, np.pi, 512, endpoint=True) W, THETA, PHI = qutip.spin_wigner(rho, theta, phi) - assert_allclose(W.imag, 0, atol=1e-9) - - norm = np.trapz(np.trapz(W * np.sin(THETA), theta), phi) + norm = np.trapz(np.trapz(W * np.sin(THETA) * np.sqrt(d / (4*np.pi)), theta), phi) assert_almost_equal(norm, 1, decimal=4) @pytest.mark.parametrize(['spin'], [ @@ -690,7 +688,7 @@ def test_spin_wigner_overlap(spin, pure, n=10): state_overlap = (test_state*rho).tr().real W_state, _, _ = qutip.spin_wigner(test_state, theta, phi) - W_overlap = (4 * np.pi / (2 * spin + 1)) * np.trapz( + W_overlap = np.trapz( np.trapz(W_state * W * np.sin(THETA), theta), phi).real assert_almost_equal(W_overlap, state_overlap, decimal=4) diff --git a/qutip/wigner.py b/qutip/wigner.py index 7f17c96e48..4ccdebadf2 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -850,10 +850,16 @@ def qfunc( # PSEUDO DISTRIBUTION FUNCTIONS FOR SPINS # def spin_q_function(rho, theta, phi): - r"""The Husimi Q function for spins is defined as - ``Q(theta, phi) = SCS.dag()* rho * SCS*(2*j+1)/(4*pi)`` for the spin - coherent state ``SCS = spin_coherent(j, theta, phi)``. - The implementation here is more efficient (see references). + r"""The Husimi Q function for spins is defined as ``Q(theta, phi) = + SCS.dag() * rho * SCS`` for the spin coherent state ``SCS = spin_coherent( + j, theta, phi)`` where j is the spin length. + The implementation here is more efficient as it doesn't + generate all of the SCS at theta and phi (see references). + + The spin Q function is normal when integrated over the surface of the sphere + + .. math:: \frac{4 \pi}{2j + 1}\int_\phi \int_\theta + Q(\theta, \phi) \sin(\theta) d\theta d\phi = 1 Parameters ---------- @@ -902,7 +908,7 @@ def spin_q_function(rho, theta, phi): (exp(1j * (m1 - m2) * PHI) * rho.data[int(j - m1), int(j - m2)] + exp(1j * (m2 - m1) * PHI) * rho.data[int(j - m2), int(j - m1)]) - return Q.real * (2 * j + 1) / (4*pi), THETA, PHI + return Q.real, THETA, PHI def _rho_kq(rho, j, k, q): @@ -942,6 +948,12 @@ def _rho_kq(rho, j, k, q): def spin_wigner(rho, theta, phi): r"""Wigner function for a spin-j system. + The spin W function is normal when integrated over the surface of the sphere + + .. math:: \sqrt{\frac{4 \pi}{2j + 1}}\int_\phi \int_\theta + W(\theta,\phi) \sin(\theta) d\theta d\phi = 1 + + Parameters ---------- state : qobj @@ -988,4 +1000,4 @@ def spin_wigner(rho, theta, phi): # sph_harm takes azimuthal angle then polar angle as arguments W += _rho_kq(rho, j, k, q) * sph_harm(q, k, PHI, THETA) - return W*np.sqrt((2*j + 1)/(4*pi)), THETA, PHI + return W.real, THETA, PHI From 11a52f933d49a8d730061f1d7bf6ea08dfb4677a Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Sat, 11 Sep 2021 11:39:41 +1000 Subject: [PATCH 066/112] Reduce precision in spin Q and wigner tests for a speed-up. --- qutip/tests/test_wigner.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/qutip/tests/test_wigner.py b/qutip/tests/test_wigner.py index 8a060209cc..26c2030358 100644 --- a/qutip/tests/test_wigner.py +++ b/qutip/tests/test_wigner.py @@ -610,8 +610,8 @@ def test_spin_q_function(spin, pure): rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Q function - theta = np.linspace(0, np.pi, 32, endpoint=True) - phi = np.linspace(-np.pi, np.pi, 64, endpoint=True) + theta = np.linspace(0, np.pi, 16, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 32, endpoint=True) Q, _, _ = qutip.spin_q_function(rho, theta, phi) for k, (phi_prime, theta_prime) in enumerate(itertools.product(phi, theta)): @@ -634,19 +634,19 @@ def test_spin_q_function_normalized(spin, pure): rho = rand_dm(d, pure=pure) # Points at which to evaluate the spin Q function - theta = np.linspace(0, np.pi, 512, endpoint=True) - phi = np.linspace(-np.pi, np.pi, 1024, endpoint=True) + theta = np.linspace(0, np.pi, 128, endpoint=True) + phi = np.linspace(-np.pi, np.pi, 256, endpoint=True) Q, THETA, _ = qutip.spin_q_function(rho, theta, phi) norm = d / (4 * np.pi) * np.trapz(np.trapz(Q * np.sin(THETA), theta), phi) - assert_almost_equal(norm, 1, decimal=5) + assert_almost_equal(norm, 1, decimal=4) @pytest.mark.parametrize(["spin"], [ pytest.param(1/2, id="spin-one-half"), - pytest.param(3, id="spin-three"), - pytest.param(13/2, id="spin-thirteen-half"), - pytest.param(7, id="spin-seven") + pytest.param(1, id="spin-one"), + pytest.param(3/2, id="spin-three-half"), + pytest.param(2, id="spin-two") ]) @pytest.mark.parametrize("pure", [ pytest.param(True, id="pure"), @@ -665,16 +665,16 @@ def test_spin_wigner_normalized(spin, pure): assert_almost_equal(norm, 1, decimal=4) @pytest.mark.parametrize(['spin'], [ - pytest.param(1/2, id="spin-one-half"), - pytest.param(3, id="spin-three"), - pytest.param(13/2, id="spin-thirteen-half"), - pytest.param(7, id="spin-seven") + pytest.param(1 / 2, id="spin-one-half"), + pytest.param(1, id="spin-one"), + pytest.param(3 / 2, id="spin-three-half"), + pytest.param(2, id="spin-two") ]) @pytest.mark.parametrize("pure", [ pytest.param(True, id="pure"), pytest.param(False, id="mixed") ]) -def test_spin_wigner_overlap(spin, pure, n=10): +def test_spin_wigner_overlap(spin, pure, n=5): d = int(2*spin + 1) rho = rand_dm(d, pure=pure) From 218f9f225b2c09596b0630bbb03dcd29b3ea125e Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Sat, 11 Sep 2021 11:44:34 +1000 Subject: [PATCH 067/112] PEP8 --- qutip/wigner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qutip/wigner.py b/qutip/wigner.py index 4ccdebadf2..abe987fd3a 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -856,7 +856,8 @@ def spin_q_function(rho, theta, phi): The implementation here is more efficient as it doesn't generate all of the SCS at theta and phi (see references). - The spin Q function is normal when integrated over the surface of the sphere + The spin Q function is normal when integrated over the surface of the + sphere .. math:: \frac{4 \pi}{2j + 1}\int_\phi \int_\theta Q(\theta, \phi) \sin(\theta) d\theta d\phi = 1 @@ -948,7 +949,8 @@ def _rho_kq(rho, j, k, q): def spin_wigner(rho, theta, phi): r"""Wigner function for a spin-j system. - The spin W function is normal when integrated over the surface of the sphere + The spin W function is normal when integrated over the surface of the + sphere .. math:: \sqrt{\frac{4 \pi}{2j + 1}}\int_\phi \int_\theta W(\theta,\phi) \sin(\theta) d\theta d\phi = 1 From bdf5e1a1df6fe558571aba3339670cd173e52e99 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sun, 12 Sep 2021 10:59:25 +0200 Subject: [PATCH 068/112] Added the test function in the qutip directory --- qutip/tests/test_correlation.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/qutip/tests/test_correlation.py b/qutip/tests/test_correlation.py index bb2bfe5659..c919e11016 100644 --- a/qutip/tests/test_correlation.py +++ b/qutip/tests/test_correlation.py @@ -271,3 +271,42 @@ def test_correlation_2op_1t_known_cases(solver, cmp = qutip.correlation_2op_1t(H, state, times, c_ops, a_op, b_op, solver=solver) np.testing.assert_allclose(base, cmp, atol=0.25 if solver == 'mc' else 2e-5) + + +@pytest.mark.parametrize("sparse", [False, True]) +def test_steadystate_floquet(sparse): + """ + Test the steadystate solution for a periodically + driven system. + """ + N_c = _equivalence_dimension + + a = qutip.destroy(N_c) + a_d = a.dag() + X_c = a + a_d + + w_c = 1 + + A_l = 0.001 + w_l = w_c + gam = 0.01 + + H = w_c * a_d * a + + H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] + + psi0 = qutip.fock(N_c, 0); + + args = {"A_l": A_l, "w_l": w_l} + + c_ops = [] + c_ops.append(np.sqrt(gam) * a) + + t_l = np.linspace(0, 20 / gam, 2000) + + expect_me = qutip.mesolve(H_t, psi0, t_l, c_ops, [a_d * a], args = args).expect[0] + + rho_ss = qutip.steadystate_floquet(H, c_ops, A_l * X_c, w_l, nmax = 3, sparse = sparse) + expect_ss = qutip.expect(a_d * a, rho_ss) + + np.testing.assert_allclose( expect_me[-20:], expect_ss, atol=1e-3 ) From 675374fdda6273f571b6c3458a7f05bdeabe75fb Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sun, 12 Sep 2021 21:46:30 +0200 Subject: [PATCH 069/112] Added the test function in the test_steadystate file --- qutip/tests/test_correlation.py | 39 ---------------- qutip/tests/test_steadystate.py | 83 ++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/qutip/tests/test_correlation.py b/qutip/tests/test_correlation.py index c919e11016..bb2bfe5659 100644 --- a/qutip/tests/test_correlation.py +++ b/qutip/tests/test_correlation.py @@ -271,42 +271,3 @@ def test_correlation_2op_1t_known_cases(solver, cmp = qutip.correlation_2op_1t(H, state, times, c_ops, a_op, b_op, solver=solver) np.testing.assert_allclose(base, cmp, atol=0.25 if solver == 'mc' else 2e-5) - - -@pytest.mark.parametrize("sparse", [False, True]) -def test_steadystate_floquet(sparse): - """ - Test the steadystate solution for a periodically - driven system. - """ - N_c = _equivalence_dimension - - a = qutip.destroy(N_c) - a_d = a.dag() - X_c = a + a_d - - w_c = 1 - - A_l = 0.001 - w_l = w_c - gam = 0.01 - - H = w_c * a_d * a - - H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] - - psi0 = qutip.fock(N_c, 0); - - args = {"A_l": A_l, "w_l": w_l} - - c_ops = [] - c_ops.append(np.sqrt(gam) * a) - - t_l = np.linspace(0, 20 / gam, 2000) - - expect_me = qutip.mesolve(H_t, psi0, t_l, c_ops, [a_d * a], args = args).expect[0] - - rho_ss = qutip.steadystate_floquet(H, c_ops, A_l * X_c, w_l, nmax = 3, sparse = sparse) - expect_ss = qutip.expect(a_d * a, rho_ss) - - np.testing.assert_allclose( expect_me[-20:], expect_ss, atol=1e-3 ) diff --git a/qutip/tests/test_steadystate.py b/qutip/tests/test_steadystate.py index 941d65f917..8554970480 100644 --- a/qutip/tests/test_steadystate.py +++ b/qutip/tests/test_steadystate.py @@ -1,8 +1,8 @@ import numpy as np from numpy.testing import assert_, assert_equal, run_module_suite -from qutip import (sigmaz, destroy, steadystate, expect, coherent_dm, - build_preconditioner) +from qutip import (sigmaz, destroy, steadystate, steadystate_floquet, + expect, coherent_dm, fock, mesolve, build_preconditioner) def test_qubit_direct(): @@ -510,6 +510,85 @@ def test_driven_cavity_bicgstab(): assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) +def test_steadystate_floquet_sparse(): + """ + Test the steadystate solution for a periodically + driven system. + """ + N_c = 20 + + a = destroy(N_c) + a_d = a.dag() + X_c = a + a_d + + w_c = 1 + + A_l = 0.001 + w_l = w_c + gam = 0.01 + + H = w_c * a_d * a + + H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] + + psi0 = fock(N_c, 0) + + args = {"A_l": A_l, "w_l": w_l} + + c_ops = [] + c_ops.append(np.sqrt(gam) * a) + + t_l = np.linspace(0, 20 / gam, 2000) + + expect_me = mesolve(H_t, psi0, t_l, + c_ops, [a_d * a], args=args).expect[0] + + rho_ss = steadystate_floquet(H, c_ops, + A_l * X_c, w_l, nmax=3, sparse=True) + expect_ss = expect(a_d * a, rho_ss) + + np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) + + +def test_steadystate_floquet_dense(): + """ + Test the steadystate solution for a periodically + driven system. + """ + N_c = 20 + + a = destroy(N_c) + a_d = a.dag() + X_c = a + a_d + + w_c = 1 + + A_l = 0.001 + w_l = w_c + gam = 0.01 + + H = w_c * a_d * a + + H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] + + psi0 = fock(N_c, 0) + + args = {"A_l": A_l, "w_l": w_l} + + c_ops = [] + c_ops.append(np.sqrt(gam) * a) + + t_l = np.linspace(0, 20 / gam, 2000) + + expect_me = mesolve(H_t, psi0, t_l, + c_ops, [a_d * a], args=args).expect[0] + + rho_ss = steadystate_floquet(H, c_ops, + A_l * X_c, w_l, nmax=3, sparse=False) + expect_ss = expect(a_d * a, rho_ss) + + np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) + if __name__ == "__main__": run_module_suite() From 065fdde35f393c3eb4e36a529585456b4933cc88 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Wed, 15 Sep 2021 16:08:00 +0800 Subject: [PATCH 070/112] fixed incorrect test of a callable --- qutip/solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/solver.py b/qutip/solver.py index 4a71a5a541..307db3911e 100644 --- a/qutip/solver.py +++ b/qutip/solver.py @@ -94,7 +94,7 @@ def step(self, iter_, state): if isinstance(self.e_ops_qoevo[ii], QobjEvo): self.raw_out[ii, iter_] = \ self.e_ops_qoevo[ii].compiled_qobjevo.expect(t, state) - elif callable(ii): + elif callable(self.e_ops_qoevo[ii]): self.raw_out[ii, iter_] = \ self.e_ops_qoevo[ii](t, state) From a00292f8bc774486a2f88227479ad38db239fe79 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Wed, 15 Sep 2021 16:08:26 +0800 Subject: [PATCH 071/112] rewording the parameter description --- qutip/mesolve.py | 8 +++----- qutip/sesolve.py | 9 ++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index 54f4613263..4984f04d1e 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -127,11 +127,9 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. - e_ops : None / list of :class:`qutip.Qobj` / list of callback functions - / callback function - single operator, list of operators or list of callable functions - for which to evaluate - expectation values. + e_ops : None / list of :class:`qutip.Qobj` and callback functions + single operator, list of operators and callable functions + for which to evaluate expectation values. args : None / *dictionary* dictionary of parameters for time-dependent Hamiltonians and diff --git a/qutip/sesolve.py b/qutip/sesolve.py index febf73dd48..74da12b363 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -54,11 +54,10 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, tlist : array_like of float List of times for :math:`t`. - e_ops : list of :class:`~Qobj` or list of callback functions or - a single callback function, optional - Single operator, list of operators or list of callable functions - for which to evaluate expectation - values. For operator evolution, the overlap is computed: :: + e_ops : None / list of :class:`qutip.Qobj` and callback functions, optional + single operator, list of operators and callable functions + for which to evaluate expectation values. For operator evolution, + the overlap is computed: :: (e_ops[i].dag() * op(t)).tr() From eb9202b554b7eb88c9c562ce7686746197ca54a9 Mon Sep 17 00:00:00 2001 From: Ashish Panigrahi Date: Wed, 15 Sep 2021 17:48:15 +0530 Subject: [PATCH 072/112] changed filename for github citation support --- qutip.bib => CITATION.bib | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename qutip.bib => CITATION.bib (100%) diff --git a/qutip.bib b/CITATION.bib similarity index 100% rename from qutip.bib rename to CITATION.bib From 3946a3b1985444834dabda071e499ff060896c2d Mon Sep 17 00:00:00 2001 From: Ericgig Date: Mon, 20 Sep 2021 16:34:44 -0400 Subject: [PATCH 073/112] Make mcsolve more memory efficient mcsolve would always save the final state of all trajectories, needed or not, Now only save them when needed. --- qutip/cy/mcsolve.pyx | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/qutip/cy/mcsolve.pyx b/qutip/cy/mcsolve.pyx index 2e3e1a5db5..f50ec250b8 100644 --- a/qutip/cy/mcsolve.pyx +++ b/qutip/cy/mcsolve.pyx @@ -32,15 +32,15 @@ cdef np.ndarray[complex, ndim=1] normalize(complex[::1] psi): cdef class CyMcOde: cdef: - int steady_state, store_states, col_args + int steady_state, store_states, col_args, store_final_state int norm_steps, l_vec, num_ops double norm_t_tol, norm_tol list collapses list collapses_args list c_ops list n_ops - complex[:,::1] states_out - complex[:,::1] ss_out + object states_out + object ss_out double[::1] n_dp def __init__(self, ss, opt): @@ -51,6 +51,7 @@ cdef class CyMcOde: self.norm_tol = opt.norm_tol self.steady_state = opt.steady_state_average self.store_states = opt.store_states or opt.average_states + self.store_final_state = opt.store_final_state self.collapses = [] self.l_vec = self.c_ops[0].cte.shape[0] self.num_ops = len(ss.td_n_ops) @@ -77,10 +78,11 @@ cdef class CyMcOde: @cython.wraparound(False) cdef void sumsteadystate(self, complex[::1] state): cdef int ii, jj, l_vec + cdef complex [:, ::1] _ss_out = self.ss_out l_vec = state.shape[0] for ii in range(l_vec): - for jj in range(l_vec): - self.ss_out[ii,jj] += state[ii]*conj(state[jj]) + for jj in range(l_vec): + _ss_out[ii,jj] += state[ii] * conj(state[jj]) @cython.boundscheck(False) @@ -94,6 +96,7 @@ cdef class CyMcOde: cdef int ii, which, k cdef double norm2_prev, norm2_psi cdef double t_prev + cdef complex [:, ::1] _states_out if self.steady_state: self.sumsteadystate(out_psi) @@ -102,8 +105,9 @@ cdef class CyMcOde: self.states_out = np.zeros((num_times, self.l_vec), dtype=complex) for ii in range(self.l_vec): self.states_out[0, ii] = out_psi[ii] - else: + elif self.store_final_state: self.states_out = np.zeros((1, self.l_vec), dtype=complex) + _states_out = self.states_out e_call.step(0, out_psi) rand_vals = prng.rand(2) @@ -152,11 +156,11 @@ cdef class CyMcOde: self.sumsteadystate(out_psi) if self.store_states: for ii in range(self.l_vec): - self.states_out[k, ii] = out_psi[ii] - if not self.store_states: + _states_out[k, ii] = out_psi[ii] + if not self.store_states and self.store_final_state: for ii in range(self.l_vec): - self.states_out[0, ii] = out_psi[ii] - return np.array(self.states_out), np.array(self.ss_out), self.collapses + _states_out[0, ii] = out_psi[ii] + return self.states_out, self.ss_out, self.collapses @cython.cdivision(True) @@ -341,7 +345,7 @@ cdef class CyMcOdeDiag(CyMcOde): self.states_out = np.zeros((num_times, self.l_vec), dtype=complex) for ii in range(self.l_vec): self.states_out[0, ii] = out_psi[ii] - else: + elif self.store_final_state: self.states_out = np.zeros((1, self.l_vec), dtype=complex) e_call.step(0, out_psi) @@ -367,10 +371,10 @@ cdef class CyMcOdeDiag(CyMcOde): if self.store_states: for ii in range(self.l_vec): self.states_out[k, ii] = out_psi[ii] - if not self.store_states: + if not self.store_states and self.store_final_state: for ii in range(self.l_vec): self.states_out[0, ii] = out_psi[ii] - return np.array(self.states_out), np.array(self.ss_out), self.collapses + return self.states_out, self.ss_out, self.collapses @cython.cdivision(True) @cython.boundscheck(False) From af80846df9221c54c46b361a48ae26992ee26165 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 21 Sep 2021 11:55:22 -0400 Subject: [PATCH 074/112] self.states_out set to None when unused --- qutip/cy/mcsolve.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutip/cy/mcsolve.pyx b/qutip/cy/mcsolve.pyx index f50ec250b8..5b11f81d98 100644 --- a/qutip/cy/mcsolve.pyx +++ b/qutip/cy/mcsolve.pyx @@ -107,6 +107,8 @@ cdef class CyMcOde: self.states_out[0, ii] = out_psi[ii] elif self.store_final_state: self.states_out = np.zeros((1, self.l_vec), dtype=complex) + else: + self.states_out = None _states_out = self.states_out e_call.step(0, out_psi) @@ -347,6 +349,8 @@ cdef class CyMcOdeDiag(CyMcOde): self.states_out[0, ii] = out_psi[ii] elif self.store_final_state: self.states_out = np.zeros((1, self.l_vec), dtype=complex) + else: + self.states_out = None e_call.step(0, out_psi) rand_vals = prng.rand(2) From 870bb55a208bc23142479743a981a44e71b25bc3 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Wed, 22 Sep 2021 11:22:39 +0200 Subject: [PATCH 075/112] Minor changes about notation --- qutip/steadystate.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index fd38ec2dc7..9b470f8045 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -914,7 +914,7 @@ def _iter_count(r): return rhoss -def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, nmax=3, sparse=False): +def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): """ Calculates the effective steady state for a driven system with a time-dependent cosinusoidal term: @@ -938,8 +938,8 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, nmax=3, sparse=False): w_d : float, default 1.0 The frequency of the drive - nmax : int, default 3 - The maximum number of iteration for the solver + n_it : int, default 3 + The number of iterations for the solver sparse : bool, default False Solve for the steady state using sparse algorithms. @@ -956,14 +956,14 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, nmax=3, sparse=False): L_0 = liouvillian(H_0, c_ops).data.tocsc() L_t = liouvillian(Op_t) L_p = (0.5 * L_t).data.tocsc() - L_m = L_p # (0.5 * A_l * L_t).data + L_m = L_p L_p_array = L_p.todense() L_m_array = L_m.todense() Id = sp.eye(N ** 2, format="csc", dtype=np.complex128) S = T = sp.csc_matrix((N ** 2, N ** 2), dtype=np.complex128) - for n_i in np.arange(nmax, 0, -1): + for n_i in np.arange(n_it, 0, -1): L = sp.csc_matrix(L_0 - 1j * n_i * w_d * Id + L_m.dot(S)) L.sort_indices() LU = splu(L) @@ -986,7 +986,7 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, nmax=3, sparse=False): Id = np.eye(N ** 2) S, T = np.zeros((N ** 2, N ** 2)), np.zeros((N ** 2, N ** 2)) - for n_i in np.arange(nmax, 0, -1): + for n_i in np.arange(n_it, 0, -1): L = L_0 - 1j * n_i * w_d * Id + np.matmul(L_m, S) lu, piv = la.lu_factor(L) S = - la.lu_solve((lu, piv), L_p) From d5e7c5903ba1045c69d56dbe5e73d6a4102344d7 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Wed, 22 Sep 2021 14:59:47 +0200 Subject: [PATCH 076/112] Added the reference of the algorithm --- qutip/steadystate.py | 4 ++++ qutip/tests/test_steadystate.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 9b470f8045..c46dc27873 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -949,6 +949,10 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): ------- dm : qobj Steady state density matrix. + + .. note:: + See: Sze Meng Tan, https://copilot.caltech.edu/documents/16743/qousersguide.pdf, Section (10.16) + """ if sparse: N = H_0.shape[0] diff --git a/qutip/tests/test_steadystate.py b/qutip/tests/test_steadystate.py index 8554970480..1ff64b1040 100644 --- a/qutip/tests/test_steadystate.py +++ b/qutip/tests/test_steadystate.py @@ -544,7 +544,7 @@ def test_steadystate_floquet_sparse(): c_ops, [a_d * a], args=args).expect[0] rho_ss = steadystate_floquet(H, c_ops, - A_l * X_c, w_l, nmax=3, sparse=True) + A_l * X_c, w_l, n_it=3, sparse=True) expect_ss = expect(a_d * a, rho_ss) np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) @@ -584,7 +584,7 @@ def test_steadystate_floquet_dense(): c_ops, [a_d * a], args=args).expect[0] rho_ss = steadystate_floquet(H, c_ops, - A_l * X_c, w_l, nmax=3, sparse=False) + A_l * X_c, w_l, n_it=3, sparse=False) expect_ss = expect(a_d * a, rho_ss) np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) From bfe3631dd6c4d75dd40a9996327253f3bdbfb052 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 23 Sep 2021 15:05:24 +0800 Subject: [PATCH 077/112] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eric Giguère --- qutip/mesolve.py | 12 +++++++----- qutip/sesolve.py | 17 ++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index 4984f04d1e..bab131e2d6 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -127,9 +127,12 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. - e_ops : None / list of :class:`qutip.Qobj` and callback functions - single operator, list of operators and callable functions - for which to evaluate expectation values. + e_ops : None / list / callback function, optional + A list of operators as `Qobj` and/or callable functions (can be mixed) + or a single callable function. For operators, the result's expect will be computed by :func:`qutip.expect`. + For callable functions, they are called as ``f(t, state)`` and return the expectation value. + A single callback's expectation value can be any type, but a callback part of a list must return + a number as the expectation value. args : None / *dictionary* dictionary of parameters for time-dependent Hamiltonians and @@ -507,8 +510,7 @@ def get_curr_state_data(r): output.expect.append(e_ops(t, rho_t)) for m in range(n_expt_op): - if not isinstance(e_ops[m], (Qobj, QobjEvo)) \ - and callable(e_ops[m]): + if not isinstance(e_ops[m], Qobj) and callable(e_ops[m]): output.expect[m][t_idx] = e_ops[m](t, rho_t) continue output.expect[m][t_idx] = expect_rho_vec(e_ops_data[m], r.y, diff --git a/qutip/sesolve.py b/qutip/sesolve.py index 74da12b363..9ab4f1161a 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -54,10 +54,13 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, tlist : array_like of float List of times for :math:`t`. - e_ops : None / list of :class:`qutip.Qobj` and callback functions, optional - single operator, list of operators and callable functions - for which to evaluate expectation values. For operator evolution, - the overlap is computed: :: + e_ops : None / list / callback function, optional + A list of operators as `Qobj` and/or callable functions (can be mixed) + or a single callable function. For callable functions, they are called as ``f(t, state)`` + and return the expectation value. A single callback's expectation value can be any type, + but a callback part of a list must return a number as the expectation value. For operators, + the result's expect will be computed by :func:`qutip.expect` when the state is a ``ket``. + For operator evolution, the overlap is computed by: :: (e_ops[i].dag() * op(t)).tr() @@ -276,7 +279,7 @@ def _generic_ode_solve(func, ode_args, psi0, tlist, e_ops, opt, opt.store_states = True else: for op in e_ops: - if not isinstance(op, (Qobj, QobjEvo)) and callable(op): + if not isinstance(op, Qobj) and callable(op): output.expect.append(np.zeros(n_tsteps, dtype=complex)) continue if op.isherm: @@ -285,13 +288,13 @@ def _generic_ode_solve(func, ode_args, psi0, tlist, e_ops, opt, output.expect.append(np.zeros(n_tsteps, dtype=complex)) if oper_evo: for e in e_ops: - if isinstance(e, (Qobj, QobjEvo)): + if isinstance(e, Qobj): e_ops_data.append(e.dag().data) continue e_ops_data.append(e) else: for e in e_ops: - if isinstance(e, (Qobj, QobjEvo)): + if isinstance(e, Qobj): e_ops_data.append(e.data) continue e_ops_data.append(e) From 6387799145c1df7d6243e9b3387455fc3c08d959 Mon Sep 17 00:00:00 2001 From: marekyggdrasil Date: Fri, 24 Sep 2021 21:43:48 +0800 Subject: [PATCH 078/112] fixed the linting errors --- qutip/mesolve.py | 9 +++++---- qutip/sesolve.py | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/qutip/mesolve.py b/qutip/mesolve.py index bab131e2d6..84b7389ded 100644 --- a/qutip/mesolve.py +++ b/qutip/mesolve.py @@ -129,10 +129,11 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, e_ops : None / list / callback function, optional A list of operators as `Qobj` and/or callable functions (can be mixed) - or a single callable function. For operators, the result's expect will be computed by :func:`qutip.expect`. - For callable functions, they are called as ``f(t, state)`` and return the expectation value. - A single callback's expectation value can be any type, but a callback part of a list must return - a number as the expectation value. + or a single callable function. For operators, the result's expect will + be computed by :func:`qutip.expect`. For callable functions, they are + called as ``f(t, state)`` and return the expectation value. + A single callback's expectation value can be any type, but a callback + part of a list must return a number as the expectation value. args : None / *dictionary* dictionary of parameters for time-dependent Hamiltonians and diff --git a/qutip/sesolve.py b/qutip/sesolve.py index 9ab4f1161a..2374f2cc91 100644 --- a/qutip/sesolve.py +++ b/qutip/sesolve.py @@ -56,11 +56,12 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, e_ops : None / list / callback function, optional A list of operators as `Qobj` and/or callable functions (can be mixed) - or a single callable function. For callable functions, they are called as ``f(t, state)`` - and return the expectation value. A single callback's expectation value can be any type, - but a callback part of a list must return a number as the expectation value. For operators, - the result's expect will be computed by :func:`qutip.expect` when the state is a ``ket``. - For operator evolution, the overlap is computed by: :: + or a single callable function. For callable functions, they are called + as ``f(t, state)`` and return the expectation value. A single + callback's expectation value can be any type, but a callback part of a + list must return a number as the expectation value. For operators, the + result's expect will be computed by :func:`qutip.expect` when the state + is a ``ket``. For operator evolution, the overlap is computed by: :: (e_ops[i].dag() * op(t)).tr() From bf133e23a3d8ce1aeff9ce5ffaa317cdf0e3fec1 Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 25 Sep 2021 15:45:29 +0200 Subject: [PATCH 079/112] Update qutip/steadystate.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eric Giguère --- qutip/steadystate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index c46dc27873..006103a8a5 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -960,9 +960,11 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): L_0 = liouvillian(H_0, c_ops).data.tocsc() L_t = liouvillian(Op_t) L_p = (0.5 * L_t).data.tocsc() + # L_p and L_m correspond to the positive and negative frequency terms respectively. + # They are independent in the model, so we keep both names. L_m = L_p L_p_array = L_p.todense() - L_m_array = L_m.todense() + L_m_array = L_p_array Id = sp.eye(N ** 2, format="csc", dtype=np.complex128) S = T = sp.csc_matrix((N ** 2, N ** 2), dtype=np.complex128) From cb99936c54da81f17f6d873c813a2d565dc9408f Mon Sep 17 00:00:00 2001 From: Alberto Mercurio <61953577+albertomercurio@users.noreply.github.com> Date: Sat, 25 Sep 2021 15:45:36 +0200 Subject: [PATCH 080/112] Update qutip/steadystate.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eric Giguère --- qutip/steadystate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 006103a8a5..28a9b02841 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -987,7 +987,7 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): L_0 = liouvillian(H_0, c_ops).full() L_t = liouvillian(Op_t) L_p = (0.5 * L_t).full() - L_m = (0.5 * L_t).full() + L_m = L_p Id = np.eye(N ** 2) S, T = np.zeros((N ** 2, N ** 2)), np.zeros((N ** 2, N ** 2)) From 0ab03860862d011436642908ffe760645fa32396 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sun, 26 Sep 2021 19:05:27 +0200 Subject: [PATCH 081/112] solve some pep8 issues --- qutip/steadystate.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 28a9b02841..b95f5900d6 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -951,8 +951,9 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): Steady state density matrix. .. note:: - See: Sze Meng Tan, https://copilot.caltech.edu/documents/16743/qousersguide.pdf, Section (10.16) - + See: Sze Meng Tan, + https://copilot.caltech.edu/documents/16743/qousersguide.pdf, + Section (10.16) """ if sparse: N = H_0.shape[0] @@ -960,7 +961,8 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): L_0 = liouvillian(H_0, c_ops).data.tocsc() L_t = liouvillian(Op_t) L_p = (0.5 * L_t).data.tocsc() - # L_p and L_m correspond to the positive and negative frequency terms respectively. + # L_p and L_m correspond to the positive and negative + # frequency terms respectively. # They are independent in the model, so we keep both names. L_m = L_p L_p_array = L_p.todense() From 68aece3aee3a15ae5fd4baefec10681f6edcb256 Mon Sep 17 00:00:00 2001 From: albertomercurio Date: Sun, 26 Sep 2021 19:09:18 +0200 Subject: [PATCH 082/112] solve other pep8 issues --- qutip/steadystate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index b95f5900d6..5db73fc2db 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -951,9 +951,9 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False): Steady state density matrix. .. note:: - See: Sze Meng Tan, - https://copilot.caltech.edu/documents/16743/qousersguide.pdf, - Section (10.16) + See: Sze Meng Tan, + https://copilot.caltech.edu/documents/16743/qousersguide.pdf, + Section (10.16) """ if sparse: N = H_0.shape[0] From a377949b2d459876aebac0e60f494924566f7453 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 27 Sep 2021 09:33:08 +0200 Subject: [PATCH 083/112] Fix parameters in call to fsesolve. --- doc/guide/dynamics/dynamics-floquet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guide/dynamics/dynamics-floquet.rst b/doc/guide/dynamics/dynamics-floquet.rst index cc9b7420e0..95976817d4 100644 --- a/doc/guide/dynamics/dynamics-floquet.rst +++ b/doc/guide/dynamics/dynamics-floquet.rst @@ -206,7 +206,7 @@ For convenience, all the steps described above for calculating the evolution of .. code-block:: python - output = fsesolve(H, psi0, times, [num(2)], args) + output = fsesolve(H, psi0=psi0, tlist=tlist, e_ops=[qutip.num(2)], args=args) p_ex = output.expect[0] .. _floquet-dissipative: From 24a9fe2df162129f2fde68fd6624dec21cecba6f Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 28 Sep 2021 11:55:55 +0200 Subject: [PATCH 084/112] Improve description of random number generation in mcsolve guide. --- doc/guide/dynamics/dynamics-monte.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index d98e420797..db3e4779ed 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -39,16 +39,18 @@ If more than a single collapse operator is present in Eq. :eq:`heff`, the probab Evaluating the MC evolution to first-order in time is quite tedious. Instead, QuTiP uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state :math:`\left|\psi(0)\right>`: -- **I:** Choose a random number :math:`r` between zero and one, representing the probability that a quantum jump occurs. +- **Ia:** Choose a random number :math:`r_1` between zero and one, representing the probability that a quantum jump occurs. -- **II:** Integrate the Schrödinger equation, using the effective Hamiltonian :eq:`heff` until a time :math:`\tau` such that the norm of the wave function satisfies :math:`\left<\psi(\tau)\right.\left|\psi(\tau)\right>=r`, at which point a jump occurs. +- **Ib:** Choose a random number :math:`r_2` between zero and one, used to select which collapse operator was responsible for the jump. + +- **II:** Integrate the Schrödinger equation, using the effective Hamiltonian :eq:`heff` until a time :math:`\tau` such that the norm of the wave function satisfies :math:`\left<\psi(\tau)\right.\left|\psi(\tau)\right> = r_1`, at which point a jump occurs. - **III:** The resultant jump projects the system at time :math:`\tau` into one of the renormalized states given by Eq. :eq:`project`. The corresponding collapse operator :math:`C_{n}` is chosen such that :math:`n` is the smallest integer satisfying: .. math:: :label: mc3 - \sum_{i=1}^{n} P_{n}(\tau) \ge r + \sum_{i=1}^{n} P_{n}(\tau) \ge r_2 where the individual :math:`P_{n}` are given by Eq. :eq:`pcn`. Note that the left hand side of Eq. :eq:`mc3` is, by definition, normalized to unity. From 59cc57a682a0d43f38cc2f1251ebed7678070761 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 28 Sep 2021 12:56:40 +0200 Subject: [PATCH 085/112] Include the site.css in the list of HTML CSS files. --- doc/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 00b846a2b5..dd6fb9c896 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -227,6 +227,10 @@ def qutip_version(): # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None +html_css_files = [ + 'site.css', +] + # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. From af4c83afaf2a6e9fa1187619eadd3d723356fd09 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 28 Sep 2021 12:57:14 +0200 Subject: [PATCH 086/112] Fix a bug in the RTD theme CSS for rendering equation numbers. --- doc/static/site.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/static/site.css b/doc/static/site.css index 8c6988ea63..e52df8762b 100644 --- a/doc/static/site.css +++ b/doc/static/site.css @@ -62,3 +62,23 @@ pre { background-color: #F7A7AA; border-color: #F1595F; } + +/* Fix for: https://github.com/readthedocs/sphinx_rtd_theme/issues/301 */ +/* Fix taken from: https://github.com/readthedocs/sphinx_rtd_theme/pull/383/ */ +span.eqno { + margin-left: 5px; + float: right; + /* position the number above the equation so that :hover is activated */ + z-index: 1; + position: relative; +} + +span.eqno .headerlink { + display: none; + visibility: hidden; +} + +span.eqno:hover .headerlink { + display: inline-block; + visibility: visible; +} From 7f3023148e63e9494575b2ab42fe37af4d02c720 Mon Sep 17 00:00:00 2001 From: ericgig Date: Thu, 30 Sep 2021 11:09:03 -0400 Subject: [PATCH 087/112] Update steadystates tests to pytest --- qutip/steadystate.py | 22 +- qutip/tests/test_steadystate.py | 693 ++++++++------------------------ 2 files changed, 177 insertions(+), 538 deletions(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index 5db73fc2db..d79241b130 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -242,10 +242,10 @@ def steadystate(A, c_op_list=[], method='direct', solver=None, **kwargs): solver = 'mkl' elif solver == 'mkl' and \ (method not in ['direct', 'power']): - raise Exception('MKL solver only for direct or power methods.') + raise ValueError('MKL solver only for direct or power methods.') elif solver not in ['scipy', 'mkl']: - raise Exception('Invalid solver kwarg.') + raise ValueError('Invalid solver kwarg.') ss_args = _default_steadystate_args() ss_args['method'] = method @@ -258,7 +258,7 @@ def steadystate(A, c_op_list=[], method='direct', solver=None, **kwargs): if key in ss_args.keys(): ss_args[key] = kwargs[key] else: - raise Exception( + raise TypeError( "Invalid keyword argument '"+key+"' passed to steadystate.") # Set column perm to NATURAL if using RCM and not specified by user @@ -1085,7 +1085,7 @@ def build_preconditioner(A, c_op_list=[], **kwargs): if key in ss_args.keys(): ss_args[key] = kwargs[key] else: - raise Exception("Invalid keyword argument '" + key + + raise TypeError("Invalid keyword argument '" + key + "' passed to steadystate.") # Set column perm to NATURAL if using RCM and not specified by user @@ -1106,7 +1106,7 @@ def build_preconditioner(A, c_op_list=[], **kwargs): ss_list = _steadystate_power_liouvillian(L, ss_args) L, perm, perm2, rev_perm, ss_args = ss_list else: - raise Exception("Invalid preconditioning method.") + raise ValueError("Invalid preconditioning method.") M, ss_args = _iterative_precondition(L, n, ss_args) @@ -1194,7 +1194,7 @@ def _pseudo_inverse_sparse(L, rhoss, w=None, **pseudo_args): A = sp_permute(L.data, perm, perm) Q = sp_permute(Q, perm, perm) else: - if ss_args['solver'] == 'scipy': + if pseudo_args['solver'] == 'scipy': A = L.data.tocsc() A.sort_indices() @@ -1206,10 +1206,9 @@ def _pseudo_inverse_sparse(L, rhoss, w=None, **pseudo_args): else: pspec = pseudo_args['permc_spec'] diag_p_thresh = pseudo_args['diag_pivot_thresh'] - pseudo_args = pseudo_args['ILU_MILU'] lu = sp.linalg.splu(A, permc_spec=pspec, diag_pivot_thresh=diag_p_thresh, - options=dict(ILU_MILU=pseudo_args)) + options=dict(ILU_MILU=pseudo_args['ILU_MILU'])) LIQ = lu.solve(Q.toarray()) elif pseudo_args['method'] == 'spilu': @@ -1230,7 +1229,7 @@ def _pseudo_inverse_sparse(L, rhoss, w=None, **pseudo_args): return Qobj(R, dims=L.dims) -def pseudo_inverse(L, rhoss=None, w=None, sparse=True, **kwargs): +def pseudo_inverse(L, rhoss=None, w=None, sparse=True, method='splu', **kwargs): """ Compute the pseudo inverse for a Liouvillian superoperator, optionally given its steady state density matrix (which will be computed if not @@ -1288,10 +1287,9 @@ def pseudo_inverse(L, rhoss=None, w=None, sparse=True, **kwargs): if key in pseudo_args.keys(): pseudo_args[key] = kwargs[key] else: - raise Exception( + raise TypeError( "Invalid keyword argument '"+key+"' passed to pseudo_inverse.") - if 'method' not in kwargs.keys(): - pseudo_args['method'] = 'splu' + pseudo_args['method'] = method # Set column perm to NATURAL if using RCM and not specified by user if pseudo_args['use_rcm'] and ('permc_spec' not in kwargs.keys()): diff --git a/qutip/tests/test_steadystate.py b/qutip/tests/test_steadystate.py index 1ff64b1040..549b76d47f 100644 --- a/qutip/tests/test_steadystate.py +++ b/qutip/tests/test_steadystate.py @@ -1,99 +1,39 @@ import numpy as np -from numpy.testing import assert_, assert_equal, run_module_suite - -from qutip import (sigmaz, destroy, steadystate, steadystate_floquet, - expect, coherent_dm, fock, mesolve, build_preconditioner) - - -def test_qubit_direct(): - "Steady state: Thermal qubit - direct solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='direct') - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_qubit_eigen(): - "Steady state: Thermal qubit - eigen solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='eigen') - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_qubit_power(): - "Steady state: Thermal qubit - power solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='power', mtol=1e-5) - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_qubit_power_gmres(): - "Steady state: Thermal qubit - power-gmres solver" +import pytest +import qutip +import warnings + + +@pytest.mark.parametrize(['method', 'kwargs'], [ + pytest.param('direct', {}, id="direct"), + pytest.param('direct', {'solver':'mkl'}, id="direct_mkl", + marks=pytest.mark.skipif(not qutip.settings.has_mkl, + reason='MKL extensions not found.')), + pytest.param('direct', {'return_info':True}, id="direct_info"), + pytest.param('direct', {'sparse':False}, id="direct_dense"), + pytest.param('direct', {'use_rcm':True}, id="direct_rcm"), + pytest.param('direct', {'use_wbm':True}, id="direct_wbm"), + pytest.param('eigen', {}, id="eigen"), + pytest.param('eigen', {'use_rcm':True}, id="eigen_rcm"), + pytest.param('svd', {}, id="svd"), + pytest.param('power', {'mtol':1e-5}, id="power"), + pytest.param('power', {'mtol':1e-5, 'solver':'mkl'}, id="power_mkl", + marks=pytest.mark.skipif(not qutip.settings.has_mkl, + reason='MKL extensions not found.')), + pytest.param('power-gmres', {'mtol':1e-1}, id="power-gmres"), + pytest.param('power-gmres', {'mtol':1e-1, 'use_rcm':True, 'use_wbm':True}, + id="power-gmres_perm"), + pytest.param('power-bicgstab', {'use_precond':1}, id="power-bicgstab"), + pytest.param('iterative-gmres', {}, id="iterative-gmres"), + pytest.param('iterative-gmres', {'use_rcm':True, 'use_wbm':True}, + id="iterative-gmres_perm"), + pytest.param('iterative-bicgstab', {'return_info':True}, + id="iterative-bicgstab"), +]) +def test_qubit(method, kwargs): # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) + sz = qutip.sigmaz() + sm = qutip.destroy(2) H = 0.5 * 2 * np.pi * sz gamma1 = 0.05 @@ -101,246 +41,43 @@ def test_qubit_power_gmres(): wth_vec = np.linspace(0.1, 3, 20) p_ss = np.zeros(np.shape(wth_vec)) - for idx, wth in enumerate(wth_vec): - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='power-gmres', mtol=1e-1) - p_ss[idx] = expect(sm.dag() * sm, rho_ss) + with warnings.catch_warnings(): + if 'use_wbm' in kwargs: + # The deprecation has been fixed in dev.major + warnings.simplefilter("ignore", category=DeprecationWarning) + + for idx, wth in enumerate(wth_vec): + n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature + c_op_list = [] + rate = gamma1 * (1 + n_th) + c_op_list.append(np.sqrt(rate) * sm) + rate = gamma1 * n_th + c_op_list.append(np.sqrt(rate) * sm.dag()) + rho_ss = qutip.steadystate(H, c_op_list, method=method, **kwargs) + if 'return_info' in kwargs: + rho_ss, info = rho_ss + assert isinstance(info, dict) + p_ss[idx] = qutip.expect(sm.dag() * sm, rho_ss) p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_qubit_power_bicgstab(): - "Steady state: Thermal qubit - power-bicgstab solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='power-bicgstab', use_precond=1) - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - - -def test_qubit_gmres(): - "Steady state: Thermal qubit - iterative-gmres solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='iterative-gmres') - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_qubit_bicgstab(): - "Steady state: Thermal qubit - iterative-bicgstab solver" - # thermal steadystate of a qubit: compare numerics with analytical formula - sz = sigmaz() - sm = destroy(2) - - H = 0.5 * 2 * np.pi * sz - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * sm) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * sm.dag()) - rho_ss = steadystate(H, c_op_list, method='iterative-bicgstab') - p_ss[idx] = expect(sm.dag() * sm, rho_ss) - - p_ss_analytic = np.exp(-1.0 / wth_vec) / (1 + np.exp(-1.0 / wth_vec)) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-5, True) - - -def test_ho_direct(): - "Steady state: Thermal HO - direct solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='direct') - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - -def test_ho_eigen(): - "Steady state: Thermal HO - eigen solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='eigen') - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - -def test_ho_power(): - "Steady state: Thermal HO - power solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='power', mtol=1e-5) - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - -def test_ho_power_gmres(): - "Steady state: Thermal HO - power-gmres solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='power-gmres', mtol=1e-1, - use_precond=1) - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - -def test_ho_power_bicgstab(): - "Steady state: Thermal HO - power-bicgstab solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='power-bicgstab',use_precond=1) - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - -def test_ho_gmres(): - "Steady state: Thermal HO - iterative-gmres solver" + np.testing.assert_allclose(p_ss_analytic, p_ss, atol=1e-5) + + +@pytest.mark.parametrize(['method', 'kwargs'], [ + pytest.param('direct', {}, id="direct"), + pytest.param('direct', {'sparse':False}, id="direct_dense"), + pytest.param('eigen', {}, id="eigen"), + pytest.param('power', {'mtol':1e-5}, id="power"), + pytest.param('power-gmres', {'mtol':1e-1, 'use_precond':1}, id="power-gmres"), + pytest.param('power-bicgstab', {'use_precond':1}, id="power-bicgstab"), + pytest.param('iterative-lgmres', {'use_precond':1}, id="iterative-lgmres"), + pytest.param('iterative-gmres', {}, id="iterative-gmres"), + pytest.param('iterative-bicgstab', {}, id="iterative-bicgstab"), +]) +def test_ho(method, kwargs): # thermal steadystate of an oscillator: compare numerics with analytical # formula - a = destroy(40) + a = qutip.destroy(35) H = 0.5 * 2 * np.pi * a.dag() * a gamma1 = 0.05 @@ -355,209 +92,69 @@ def test_ho_gmres(): c_op_list.append(np.sqrt(rate) * a) rate = gamma1 * n_th c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='iterative-gmres') - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) + rho_ss = qutip.steadystate(H, c_op_list, method=method, **kwargs) + p_ss[idx] = np.real(qutip.expect(a.dag() * a, rho_ss)) p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - -def test_ho_bicgstab(): - "Steady state: Thermal HO - iterative-bicgstab solver" - # thermal steadystate of an oscillator: compare numerics with analytical - # formula - a = destroy(40) - H = 0.5 * 2 * np.pi * a.dag() * a - gamma1 = 0.05 - - wth_vec = np.linspace(0.1, 3, 20) - p_ss = np.zeros(np.shape(wth_vec)) - - for idx, wth in enumerate(wth_vec): - - n_th = 1.0 / (np.exp(1.0 / wth) - 1) # bath temperature - c_op_list = [] - rate = gamma1 * (1 + n_th) - c_op_list.append(np.sqrt(rate) * a) - rate = gamma1 * n_th - c_op_list.append(np.sqrt(rate) * a.dag()) - rho_ss = steadystate(H, c_op_list, method='iterative-bicgstab') - p_ss[idx] = np.real(expect(a.dag() * a, rho_ss)) - - p_ss_analytic = 1.0 / (np.exp(1.0 / wth_vec) - 1) - delta = sum(abs(p_ss_analytic - p_ss)) - assert_equal(delta < 1e-3, True) - - - -def test_driven_cavity_direct(): - "Steady state: Driven cavity - direct solver" - + np.testing.assert_allclose(p_ss_analytic, p_ss, atol=1e-3) + + +@pytest.mark.parametrize(['method', 'kwargs'], [ + pytest.param('direct', {}, id="direct"), + pytest.param('direct', {'sparse':False}, id="direct_dense"), + pytest.param('eigen', {}, id="eigen"), + pytest.param('svd', {}, id="svd"), + pytest.param('power', {'mtol':1e-5}, id="power"), + pytest.param('power-gmres', {'mtol':1e-1, 'use_precond':1, 'M':'iterative'}, + id="power-gmres"), + pytest.param('power-bicgstab', {'use_precond':1, 'M':'power'}, + id="power-bicgstab"), + pytest.param('iterative-gmres', {}, id="iterative-gmres"), + pytest.param('iterative-bicgstab', {}, id="iterative-bicgstab"), +]) +def test_driven_cavity(method, kwargs): N = 30 Omega = 0.01 * 2 * np.pi Gamma = 0.05 - a = destroy(N) + a = qutip.destroy(N) H = Omega * (a.dag() + a) c_ops = [np.sqrt(Gamma) * a] - - rho_ss = steadystate(H, c_ops, method='direct') - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_driven_cavity_eigen(): - "Steady state: Driven cavity - eigen solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - - rho_ss = steadystate(H, c_ops, method='eigen') - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_driven_cavity_power(): - "Steady state: Driven cavity - power solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - - rho_ss = steadystate(H, c_ops, method='power', mtol=1e-5,) - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_driven_cavity_power_gmres(): - "Steady state: Driven cavity - power-gmres solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - M = build_preconditioner(H, c_ops, method='power') - rho_ss = steadystate(H, c_ops, method='power-gmres', M=M, mtol=1e-1, - use_precond=1) - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - - -def test_driven_cavity_power_bicgstab(): - "Steady state: Driven cavity - power-bicgstab solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - M = build_preconditioner(H, c_ops, method='power') - rho_ss = steadystate(H, c_ops, method='power-bicgstab', M=M, use_precond=1) - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_driven_cavity_gmres(): - "Steady state: Driven cavity - iterative-gmres solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - - rho_ss = steadystate(H, c_ops, method='iterative-gmres') - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_driven_cavity_bicgstab(): - "Steady state: Driven cavity - iterative-bicgstab solver" - - N = 30 - Omega = 0.01 * 2 * np.pi - Gamma = 0.05 - - a = destroy(N) - H = Omega * (a.dag() + a) - c_ops = [np.sqrt(Gamma) * a] - - rho_ss = steadystate(H, c_ops, method='iterative-bicgstab') - rho_ss_analytic = coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) - - assert_((rho_ss - rho_ss_analytic).norm() < 1e-4) - - -def test_steadystate_floquet_sparse(): - """ - Test the steadystate solution for a periodically - driven system. - """ - N_c = 20 - - a = destroy(N_c) - a_d = a.dag() - X_c = a + a_d - - w_c = 1 - - A_l = 0.001 - w_l = w_c - gam = 0.01 - - H = w_c * a_d * a - - H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] - - psi0 = fock(N_c, 0) - - args = {"A_l": A_l, "w_l": w_l} - - c_ops = [] - c_ops.append(np.sqrt(gam) * a) - - t_l = np.linspace(0, 20 / gam, 2000) - - expect_me = mesolve(H_t, psi0, t_l, - c_ops, [a_d * a], args=args).expect[0] - - rho_ss = steadystate_floquet(H, c_ops, - A_l * X_c, w_l, n_it=3, sparse=True) - expect_ss = expect(a_d * a, rho_ss) - - np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) - - -def test_steadystate_floquet_dense(): + if 'use_precond' in kwargs: + kwargs['M'] = qutip.build_preconditioner(H, c_ops, method=kwargs['M']) + rho_ss = qutip.steadystate(H, c_ops, method=method, **kwargs) + rho_ss_analytic = qutip.coherent_dm(N, -1.0j * (Omega)/(Gamma/2)) + + np.testing.assert_allclose(rho_ss, rho_ss_analytic, atol=1e-4) + + +@pytest.mark.parametrize(['method', 'kwargs'], [ + pytest.param('splu', {'sparse':False}, id="dense_direct"), + pytest.param('numpy', {'sparse':False}, id="dense_numpy"), + pytest.param('scipy', {'sparse':False}, id="dense_scipy"), + pytest.param('splu', {}, id="splu"), + pytest.param('spilu', {}, id="spilu"), +]) +def test_pseudo_inverse(method, kwargs): + N = 4 + a = qutip.destroy(N) + H = (a.dag() + a) + L = qutip.liouvillian(H, [a]) + rho = qutip.steadystate(L) + Lpinv = qutip.pseudo_inverse(L, rho, method=method, **kwargs) + np.testing.assert_allclose((L * Lpinv * L).full(), L.full()) + np.testing.assert_allclose((Lpinv * L * Lpinv).full(), Lpinv.full()) + + +@pytest.mark.parametrize('sparse', [True, False]) +def test_steadystate_floquet(sparse): """ Test the steadystate solution for a periodically driven system. """ N_c = 20 - a = destroy(N_c) + a = qutip.destroy(N_c) a_d = a.dag() X_c = a + a_d @@ -571,7 +168,7 @@ def test_steadystate_floquet_dense(): H_t = [H, [X_c, lambda t, args: args["A_l"] * np.cos(args["w_l"] * t)]] - psi0 = fock(N_c, 0) + psi0 = qutip.fock(N_c, 0) args = {"A_l": A_l, "w_l": w_l} @@ -580,15 +177,59 @@ def test_steadystate_floquet_dense(): t_l = np.linspace(0, 20 / gam, 2000) - expect_me = mesolve(H_t, psi0, t_l, + expect_me = qutip.mesolve(H_t, psi0, t_l, c_ops, [a_d * a], args=args).expect[0] - rho_ss = steadystate_floquet(H, c_ops, - A_l * X_c, w_l, n_it=3, sparse=False) - expect_ss = expect(a_d * a, rho_ss) + rho_ss = qutip.steadystate_floquet(H, c_ops, + A_l * X_c, w_l, n_it=3, sparse=sparse) + expect_ss = qutip.expect(a_d * a, rho_ss) np.testing.assert_allclose(expect_me[-20:], expect_ss, atol=1e-3) -if __name__ == "__main__": - run_module_suite() +def test_bad_options_steadystate(): + N = 4 + a = qutip.destroy(N) + H = (a.dag() + a) + c_ops = [a] + with pytest.raises(ValueError): + rho_ss = qutip.steadystate(H, c_ops, method='not a method') + with pytest.raises(TypeError): + rho_ss = qutip.steadystate(H, c_ops, method='direct', bad_opt=True) + with pytest.raises(ValueError): + rho_ss = qutip.steadystate(H, c_ops, method='direct', solver='Error') + + +def test_bad_options_pseudo_inverse(): + N = 4 + a = qutip.destroy(N) + H = (a.dag() + a) + L = qutip.liouvillian(H, [a]) + with pytest.raises(TypeError): + qutip.pseudo_inverse(L, method='splu', bad_opt=True) + with pytest.raises(ValueError): + qutip.pseudo_inverse(L, method='not a method', sparse=False) + with pytest.raises(ValueError): + qutip.pseudo_inverse(L, method='not a method') + + +def test_bad_options_build_preconditioner(): + N = 4 + a = qutip.destroy(N) + H = (a.dag() + a) + c_ops = [a] + with pytest.raises(TypeError): + qutip.build_preconditioner(H, c_ops, method='power', bad_opt=True) + with pytest.raises(ValueError): + qutip.build_preconditioner(H, c_ops, method='not a method') + + +def test_bad_system(): + N = 4 + a = qutip.destroy(N) + H = (a.dag() + a) + c_ops = [a] + with pytest.raises(TypeError) as err: + rho_ss = qutip.steadystate(H, [], method='direct') + with pytest.raises(TypeError) as err: + rho_ss = qutip.steadystate(qutip.basis(N, N-1), [], method='direct') From 04ea09a4d6d0a9e6a1266acc98df452848d6a985 Mon Sep 17 00:00:00 2001 From: ericgig Date: Thu, 30 Sep 2021 16:28:11 -0400 Subject: [PATCH 088/112] Change test_utilities to pytest --- doc/apidoc/functions.rst | 2 +- qutip/tests/test_utilities.py | 181 +++++++++++++++++----------------- qutip/utilities.py | 62 +----------- 3 files changed, 94 insertions(+), 151 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index d13de7f3a6..801e89b010 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -334,7 +334,7 @@ Utility Functions ----------------- .. automodule:: qutip.utilities - :members: n_thermal, linspace_with, clebsch, convert_unit + :members: n_thermal, clebsch, convert_unit .. _functions-fileio: diff --git a/qutip/tests/test_utilities.py b/qutip/tests/test_utilities.py index 9961f2018c..e40c3b6800 100644 --- a/qutip/tests/test_utilities.py +++ b/qutip/tests/test_utilities.py @@ -1,104 +1,107 @@ import numpy as np -from numpy.testing import assert_, run_module_suite +from qutip import convert_unit, clebsch, n_thermal +import qutip.utilities as utils +from functools import partial +import pytest -from qutip import convert_unit, clebsch +@pytest.mark.parametrize(['w', 'w_th', 'expected'], [ + pytest.param(np.log(2), 1, 1, id='log(2)'), + pytest.param(np.log(2)*5, 5, 1, id='5*log(2)'), + pytest.param(0, 1, 0, id="0_energy"), + pytest.param(1, -1, 0, id="neg_temp"), + pytest.param(np.array([np.log(2), np.log(3), np.log(4)]), 1, + np.array([1, 1/2, 1/3]), id="array"), +]) +def test_n_thermal(w, w_th, expected): + np.testing.assert_allclose(n_thermal(w, w_th), expected) -def test_unit_conversions(): - "utilities: energy unit conversions" +def _get_converter(orig, target): + """get funtion 'convert_{}_to_{}' when available for coverage """ + try: + func = getattr(utils, f'convert_{orig}_to_{target}') + except AttributeError: + func = partial(convert_unit, orig=orig, to=target) + return func + + +@pytest.mark.parametrize('orig', ["J", "eV", "meV", "GHz", "mK"]) +@pytest.mark.parametrize('target', ["J", "eV", "meV", "GHz", "mK"]) +def test_unit_conversions(orig, target): + T = np.random.rand() * 100.0 + T_converted = convert_unit(T, orig=orig, to=target) + T_back = convert_unit(T_converted, orig=target, to=orig) + + assert T == pytest.approx(T_back) + + T_converted = _get_converter(orig=orig, target=target)(T) + T_back = _get_converter(orig=target, target=orig)(T_converted) + + assert T == pytest.approx(T_back) + + +@pytest.mark.parametrize('orig', ["J", "eV", "meV", "GHz", "mK"]) +@pytest.mark.parametrize('middle', ["J", "eV", "meV", "GHz", "mK"]) +@pytest.mark.parametrize('target', ["J", "eV", "meV", "GHz", "mK"]) +def test_unit_conversions_loop(orig, middle, target): T = np.random.rand() * 100.0 + T_middle = convert_unit(T, orig=orig, to=middle) + T_converted = convert_unit(T_middle, orig=middle, to=target) + T_back = convert_unit(T_converted, orig=target, to=orig) + + assert T == pytest.approx(T_back) + - diff = convert_unit(convert_unit(T, orig="mK", to="GHz"), - orig="GHz", to="mK") - T - assert_(abs(diff) < 1e-6) - diff = convert_unit(convert_unit(T, orig="mK", to="meV"), - orig="meV", to="mK") - T - assert_(abs(diff) < 1e-6) - - diff = convert_unit(convert_unit(convert_unit(T, orig="mK", to="GHz"), - orig="GHz", to="meV"), - orig="meV", to="mK") - T - assert_(abs(diff) < 1e-6) - - w = np.random.rand() * 100.0 - - diff = convert_unit(convert_unit(w, orig="GHz", to="meV"), - orig="meV", to="GHz") - w - assert_(abs(diff) < 1e-6) - - diff = convert_unit(convert_unit(w, orig="GHz", to="mK"), - orig="mK", to="GHz") - w - assert_(abs(diff) < 1e-6) - - diff = convert_unit(convert_unit(convert_unit(w, orig="GHz", to="mK"), - orig="mK", to="meV"), - orig="meV", to="GHz") - w - assert_(abs(diff) < 1e-6) - -def test_unit_clebsch(): - "utilities: Clebsch–Gordan coefficients " - N = 15 - for _ in range(100): - "sum_m1 sum_m2 C(j1,j2,j3,m1,m2,m3)*C(j1,j2,j3',m1,m2,m3') =" - "delta j3,j3' delta m3,m3'" - j1 = np.random.randint(0, N+1) - j2 = np.random.randint(0, N+1) - j3 = np.random.randint(abs(j1-j2), j1+j2+1) - j3p = np.random.randint(abs(j1-j2), j1+j2+1) - m3 = np.random.randint(-j3, j3+1) - m3p = np.random.randint(-j3p, j3p+1) - if np.random.rand() < 0.25: - j1 += 0.5 - j3 += 0.5 - j3p += 0.5 - m3 += np.random.choice([-0.5, 0.5]) - m3p += np.random.choice([-0.5, 0.5]) - if np.random.rand() < 0.25: - j2 += 0.5 - j3 += 0.5 - j3p += 0.5 - m3 += np.random.choice([-0.5, 0.5]) - m3p += np.random.choice([-0.5, 0.5]) - sum_match = -1 - sum_differ = -int(j3 == j3p and m3 == m3p) - for m1 in np.arange(-j1,j1+1): - for m2 in np.arange(-j2,j2+1): +def test_unit_conversions_bad_unit(): + with pytest.raises(TypeError): + convert_unit(10, orig="bad", to="J") + with pytest.raises(TypeError): + convert_unit(10, orig="J", to="bad") + + + +@pytest.mark.parametrize('j1', [0.5, 1.0, 1.5, 2.0, 5, 7.5, 10, 12.5]) +@pytest.mark.parametrize('j2', [0.5, 1.0, 1.5, 2.0, 5, 7.5, 10, 12.5]) +def test_unit_clebsch_delta_j(j1, j2): + """sum_m1 sum_m2 C(j1,j2,j3,m1,m2,m3) * C(j1,j2,j3',m1,m2,m3') = + delta j3,j3' delta m3,m3'""" + for _ in range(10): + j3 = np.random.choice(np.arange(abs(j1-j2), j1+j2+1)) + j3p = np.random.choice(np.arange(abs(j1-j2), j1+j2+1)) + m3 = np.random.choice(np.arange(-j3, j3+1)) + m3p = np.random.choice(np.arange(-j3p, j3p+1)) + + sum_match = 0 + sum_differ = 0 + for m1 in np.arange(-j1, j1+1): + for m2 in np.arange(-j2, j2+1): c1 = clebsch(j1, j2, j3, m1, m2, m3) c2 = clebsch(j1, j2, j3p, m1, m2, m3p) sum_match += c1**2 sum_differ += c1*c2 - assert_(abs(sum_match) < 1e-6) - assert_(abs(sum_differ) < 1e-6) - - for _ in range(100): - "sum_j3 sum_m3 C(j1,j2,j3,m1,m2,m3)*C(j1,j2,j3,m1',m2',m3) =" - "delta m1,m1' delta m2,m2'" - j1 = np.random.randint(0,N+1) - j2 = np.random.randint(0,N+1) - m1 = np.random.randint(-j1,j1+1) - m1p = np.random.randint(-j1,j1+1) - m2 = np.random.randint(-j2,j2+1) - m2p = np.random.randint(-j2,j2+1) - if np.random.rand() < 0.25: - j1 += 0.5 - m1 += np.random.choice([-0.5, 0.5]) - m1p += np.random.choice([-0.5, 0.5]) - if np.random.rand() < 0.25: - j2 += 0.5 - m2 += np.random.choice([-0.5, 0.5]) - m2p += np.random.choice([-0.5, 0.5]) - sum_match = -1 - sum_differ = -int(m1 == m1p and m2 == m2p) - for j3 in np.arange(abs(j1-j2),j1+j2+1): - for m3 in np.arange(-j3,j3+1): + assert sum_match == pytest.approx(1) + assert sum_differ == pytest.approx(int(j3 == j3p and m3 == m3p)) + + +@pytest.mark.parametrize('j1', [0.5, 1.0, 1.5, 2.0, 5, 7.5, 10, 12.5]) +@pytest.mark.parametrize('j2', [0.5, 1.0, 1.5, 2.0, 5, 7.5, 10, 12.5]) +def test_unit_clebsch_delta_m(j1, j2): + """sum_j3 sum_m3 C(j1,j2,j3,m1,m2,m3)*C(j1,j2,j3,m1',m2',m3) = + delta m1,m1' delta m2,m2'""" + for _ in range(10): + m1 = np.random.choice(np.arange(-j1, j1+1)) + m1p = np.random.choice(np.arange(-j1, j1+1)) + m2 = np.random.choice(np.arange(-j2, j2+1)) + m2p = np.random.choice(np.arange(-j2, j2+1)) + + sum_match = 0 + sum_differ = 0 + for j3 in np.arange(abs(j1-j2), j1+j2+1): + for m3 in np.arange(-j3, j3+1): c1 = clebsch(j1, j2, j3, m1, m2, m3) c2 = clebsch(j1, j2, j3, m1p, m2p, m3) sum_match += c1**2 sum_differ += c1*c2 - assert_(abs(sum_match) < 1e-6) - assert_(abs(sum_differ) < 1e-6) - - -if __name__ == "__main__": - run_module_suite() + assert sum_match == pytest.approx(1) + assert sum_differ == pytest.approx(int(m1 == m1p and m2 == m2p)) diff --git a/qutip/utilities.py b/qutip/utilities.py index 5740961fd9..ba7f5c8d69 100644 --- a/qutip/utilities.py +++ b/qutip/utilities.py @@ -3,8 +3,7 @@ qutip modules. """ -__all__ = ['n_thermal', 'linspace_with', 'clebsch', 'convert_unit', - 'view_methods'] +__all__ = ['n_thermal', 'clebsch', 'convert_unit'] import numpy as np @@ -45,39 +44,6 @@ def n_thermal(w, w_th): return 0.0 -def linspace_with(start, stop, num=50, elems=[]): - """ - Return an array of numbers sampled over specified interval - with additional elements added. - - Returns `num` spaced array with elements from `elems` inserted - if not already included in set. - - Returned sample array is not evenly spaced if addtional elements - are added. - - Parameters - ---------- - start : int - The starting value of the sequence. - stop : int - The stoping values of the sequence. - num : int, optional - Number of samples to generate. - elems : list/ndarray, optional - Requested elements to include in array - - Returns - ------- - samples : ndadrray - Original equally spaced sample array with additional - elements added. - """ - elems = np.array(elems) - lspace = np.linspace(start, stop, num) - return np.union1d(lspace, elems) - - def _factorial_prod(N, arr): arr[:int(N)] += 1 @@ -358,32 +324,6 @@ def convert_mK_to_GHz(w): return w_GHz -def view_methods(Q): - """ - View the methods and corresponding doc strings - for a Qobj class. - - Parameters - ---------- - Q : Qobj - Input Quantum object. - - """ - meth = dir(Q) - qobj_props = ['data', 'dims', 'isherm', 'shape', 'type'] - pub_meth = [x for x in meth if x.find('_') and x not in qobj_props] - ml = max([len(x) for x in pub_meth]) - nl = len(Q.__class__.__name__ + 'Class Methods:') - print(Q.__class__.__name__ + ' Class Methods:') - print('-' * nl) - for ii in range(len(pub_meth)): - m = getattr(Q, pub_meth[ii]) - meth_str = m.__doc__ - ind = meth_str.find('\n') - pub_len = len(pub_meth[ii] + ': ') - print(pub_meth[ii] + ':' + ' ' * (ml+3-pub_len) + meth_str[:ind]) - - def _version2int(version_string): str_list = version_string.split( "-dev")[0].split("rc")[0].split("a")[0].split("b")[0].split( From 5df591d49aae03d4d44d7cb3db79a7a214bd8494 Mon Sep 17 00:00:00 2001 From: ericgig Date: Thu, 30 Sep 2021 16:47:28 -0400 Subject: [PATCH 089/112] pep8 --- qutip/steadystate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutip/steadystate.py b/qutip/steadystate.py index d79241b130..1f60f942b2 100644 --- a/qutip/steadystate.py +++ b/qutip/steadystate.py @@ -1229,7 +1229,8 @@ def _pseudo_inverse_sparse(L, rhoss, w=None, **pseudo_args): return Qobj(R, dims=L.dims) -def pseudo_inverse(L, rhoss=None, w=None, sparse=True, method='splu', **kwargs): +def pseudo_inverse(L, rhoss=None, w=None, sparse=True, + method='splu', **kwargs): """ Compute the pseudo inverse for a Liouvillian superoperator, optionally given its steady state density matrix (which will be computed if not From e2557cdf1afc5235e9fe76c2ff20bb4c8dca71cf Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Oct 2021 10:54:23 -0400 Subject: [PATCH 090/112] add tests for simdiags --- qutip/tests/test_simdiag.py | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 qutip/tests/test_simdiag.py diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py new file mode 100644 index 0000000000..373423c10b --- /dev/null +++ b/qutip/tests/test_simdiag.py @@ -0,0 +1,50 @@ +import pytest +import numpy as np +import qutip + + +@pytest.mark.parametrize('num_mat', [1, 2, 3, 5]) +def test_simdiag(num_mat): + N = 10 + + U = qutip.rand_unitary(N) + cummuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() + for _ in range(num_mat)] + all_evals, evecs = qutip.simdiag(cummuting_matrices) + + for matrix, evals in zip(cummuting_matrices, all_evals): + for eval, evec in zip(evals, evecs): + assert matrix * evec == evec * eval + + +@pytest.mark.parametrize('num_mat', [1, 2, 3, 5]) +def test_simdiag_no_evals(num_mat): + N = 10 + + U = qutip.rand_unitary(N) + cummuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() + for _ in range(num_mat)] + evecs = qutip.simdiag(cummuting_matrices, evals=False) + + for matrix in cummuting_matrices: + for evec in evecs: + Mvec = matrix * evec + eval = Mvec.full()[0,0] / evec.full()[0,0] + assert matrix * evec == evec * eval + + +def test_simdiag_no_input(): + with pytest.raises(ValueError): + qutip.simdiag([]) + + +@pytest.mark.parametrize(['ops', 'error'], [ + pytest.param([qutip.basis(5,0)], 'square', id="Not square"), + pytest.param([qutip.qeye(5), qutip.qeye(3)], 'shape', id="shape mismatch"), + pytest.param([qutip.destroy(5)], 'Hermitian', id="Non Hermitian"), + pytest.param([qutip.sigmax(), qutip.sigmay()], 'commut', id="Not commuting"), +]) +def test_simdiag_errors(ops, error): + with pytest.raises(TypeError) as err: + qutip.simdiag(ops) + assert error in str(err.value) From 9271f4ca4fbc2eb13720ea8c90605a870e09b6d0 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Oct 2021 12:41:27 -0400 Subject: [PATCH 091/112] add test for degen --- qutip/tests/test_simdiag.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index 373423c10b..a3c44d4aba 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -33,6 +33,16 @@ def test_simdiag_no_evals(num_mat): assert matrix * evec == evec * eval +def test_simdiag_degen(): + N = 10 + U = qutip.rand_unitary(N) + matrix = U * qutip.qdiags([0,0,0,1,1,1,2,2,3,4], 0) * U.dag() + evals, evecs = qutip.simdiag([matrix]) + + for eval, evec in zip(evals[0], evecs): + assert matrix * evec == evec * eval + + def test_simdiag_no_input(): with pytest.raises(ValueError): qutip.simdiag([]) From 67ad0585257519e07ddfaa6415c036e0716a5f14 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Oct 2021 12:41:27 -0400 Subject: [PATCH 092/112] Add a test for degen --- qutip/tests/test_simdiag.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index 373423c10b..70c0007e69 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -33,6 +33,19 @@ def test_simdiag_no_evals(num_mat): assert matrix * evec == evec * eval +def test_simdiag_degen(): + N = 10 + U = qutip.rand_unitary(N) + matrices = [ + U * qutip.qdiags([0,0,0,1,1,1,2,2,3,4], 0) * U.dag(), + U * qutip.qdiags([1,3,4,2,4,0,0,3,0,1], 0) * U.dag(), + ] + evals, evecs = qutip.simdiag(matrices) + + for eval, evec in zip(evals[0], evecs): + assert matrix * evec == evec * eval + + def test_simdiag_no_input(): with pytest.raises(ValueError): qutip.simdiag([]) From d5a2e799a6bb86070bc7b867a3642bc8111505e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 4 Oct 2021 07:52:17 -0400 Subject: [PATCH 093/112] Correct spelling Co-authored-by: Simon Cross --- qutip/tests/test_simdiag.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index a3c44d4aba..4463344862 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -8,11 +8,11 @@ def test_simdiag(num_mat): N = 10 U = qutip.rand_unitary(N) - cummuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() + commuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() for _ in range(num_mat)] - all_evals, evecs = qutip.simdiag(cummuting_matrices) + all_evals, evecs = qutip.simdiag(commuting_matrices) - for matrix, evals in zip(cummuting_matrices, all_evals): + for matrix, evals in zip(commuting_matrices, all_evals): for eval, evec in zip(evals, evecs): assert matrix * evec == evec * eval @@ -22,11 +22,11 @@ def test_simdiag_no_evals(num_mat): N = 10 U = qutip.rand_unitary(N) - cummuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() + commuting_matrices = [U * qutip.qdiags(np.random.rand(N), 0) * U.dag() for _ in range(num_mat)] - evecs = qutip.simdiag(cummuting_matrices, evals=False) + evecs = qutip.simdiag(commuting_matrices, evals=False) - for matrix in cummuting_matrices: + for matrix in commuting_matrices: for evec in evecs: Mvec = matrix * evec eval = Mvec.full()[0,0] / evec.full()[0,0] @@ -52,7 +52,7 @@ def test_simdiag_no_input(): pytest.param([qutip.basis(5,0)], 'square', id="Not square"), pytest.param([qutip.qeye(5), qutip.qeye(3)], 'shape', id="shape mismatch"), pytest.param([qutip.destroy(5)], 'Hermitian', id="Non Hermitian"), - pytest.param([qutip.sigmax(), qutip.sigmay()], 'commut', id="Not commuting"), + pytest.param([qutip.sigmax(), qutip.sigmay()], 'commute', id="Not commuting"), ]) def test_simdiag_errors(ops, error): with pytest.raises(TypeError) as err: From a9e5c88d5cb6df945be5e3efa8bea2696c039f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 4 Oct 2021 07:52:59 -0400 Subject: [PATCH 094/112] Update qutip/tests/test_simdiag.py Co-authored-by: Simon Cross --- qutip/tests/test_simdiag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index 4463344862..4d8fb92234 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -29,7 +29,7 @@ def test_simdiag_no_evals(num_mat): for matrix in commuting_matrices: for evec in evecs: Mvec = matrix * evec - eval = Mvec.full()[0,0] / evec.full()[0,0] + eval = Mvec.norm() / evec.norm() assert matrix * evec == evec * eval From 6657295fdfecb47c19c7f887e3afa0890119832c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 5 Oct 2021 13:05:53 +0200 Subject: [PATCH 095/112] Remove unused site-specific styling (most styling is now handled by the readthedocs Sphinx theme). --- doc/static/site.css | 65 --------------------------------------------- 1 file changed, 65 deletions(-) diff --git a/doc/static/site.css b/doc/static/site.css index e52df8762b..2a0a81fca1 100644 --- a/doc/static/site.css +++ b/doc/static/site.css @@ -1,68 +1,3 @@ -@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro'); - - -.navbar-text { - color: #e8e8e8 !important; -} - -a { - color: #599AD3; - text-decoration: none; -} - -a:hover, -a:focus { - color: #8C0028; - text-decoration: underline; -} - -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -code, -pre { - padding: 0 3px 2px; - font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; - font-size: 12px; - color: #333333; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.alert { - border-width: 2px; - color: #09224F; - font-weight: bold; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - } - -.alert-success { - background-color: #B9E0B0; - border-color: #79C36A; -} - - -.alert-info { - background-color: #A6CBE9; - border-color: #599AD3; -} - -.alert-warning { - background-color: #FBD1A7; - border-color: #F9A65A; -} - -.alert-danger { -background-color: #F7A7AA; - border-color: #F1595F; -} - /* Fix for: https://github.com/readthedocs/sphinx_rtd_theme/issues/301 */ /* Fix taken from: https://github.com/readthedocs/sphinx_rtd_theme/pull/383/ */ span.eqno { From 8671d15ec519c415debc624abde7a096f85be587 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 12 Oct 2021 15:46:27 -0400 Subject: [PATCH 096/112] fix degen bug for not properly orthogonal vectors --- qutip/simdiag.py | 150 ++++++++++++++++++------------------ qutip/tests/test_simdiag.py | 39 ++++++++-- 2 files changed, 109 insertions(+), 80 deletions(-) diff --git a/qutip/simdiag.py b/qutip/simdiag.py index 505738cc27..6b57f82cf4 100644 --- a/qutip/simdiag.py +++ b/qutip/simdiag.py @@ -5,7 +5,43 @@ from qutip.qobj import Qobj -def simdiag(ops, evals=True): +def _degen(tol, vecs, ops, i=0): + """ + Private function that finds eigen vals and vecs for degenerate matrices.. + """ + if len(ops) == i: + return vecs + + # New eigenvectors are sometime not orthogonal. + for j in range(1, vecs.shape[1]): + for k in range(j): + dot = vecs[:, j].dot(vecs[:, k].conj()) + if np.abs(dot) > tol: + vecs[:, j] = ((vecs[:, j] - dot * vecs[:, k]) + / (1 - np.abs(dot)**2)**0.5) + + subspace = vecs.conj().T @ ops[i].data @ vecs + eigvals, eigvecs = la.eig(subspace) + + perm = np.argsort(eigvals) + eigvals = eigvals[perm] + + vecs_new = vecs @ eigvecs[:, perm] + for k in range(len(eigvals)): + vecs_new[:, k] = vecs_new[:, k] / la.norm(vecs_new[:, k]) + + k = 0 + while k < len(eigvals): + ttol = max(tol, tol * abs(eigvals[k])) + inds, = np.where(abs(eigvals - eigvals[k]) < ttol) + if len(inds) > 1: # if at least 2 eigvals are degenerate + vecs_new[:, inds] = _degen(tol, vecs_new[:, inds], ops, i+1) + k = inds[-1] + 1 + return vecs_new + + +def simdiag(ops, evals: bool = True, *, + tol: float = 1e-14, safe_mode: bool = True): """Simultaneous diagonalization of commuting Hermitian matrices. Parameters @@ -14,6 +50,18 @@ def simdiag(ops, evals=True): ``list`` or ``array`` of qobjs representing commuting Hermitian operators. + evals : bool [True] + Whether to return the eigenvalues for each ops and eigenvectors or just + the eigenvectors. + + tol : float [1e-14] + Tolerance for detecting degenerate eigenstates. + + safe_mode : bool [True] + Whether to check that all ops are Hermitian and commuting. If set to + ``False`` and operators are not commuting, the eigenvectors returned + will often be eigenvectors of only the first operator. + Returns -------- eigs : tuple @@ -22,21 +70,16 @@ def simdiag(ops, evals=True): operator. """ - tol = 1e-14 - start_flag = 0 - if not any(ops): - raise ValueError('Need at least one input operator.') - if not isinstance(ops, (list, np.ndarray)): - ops = np.array([ops]) - num_ops = len(ops) + if not ops: + raise ValueError("No input matrices.") + N = ops[0].shape[0] + num_ops = len(ops) if safe_mode else 0 for jj in range(num_ops): A = ops[jj] shape = A.shape if shape[0] != shape[1]: raise TypeError('Matricies must be square.') - if start_flag == 0: - s = shape[0] - if s != shape[0]: + if shape[0] != N: raise TypeError('All matrices. must be the same shape') if not A.isherm: raise TypeError('Matricies must be Hermitian') @@ -45,75 +88,34 @@ def simdiag(ops, evals=True): if (A * B - B * A).norm() / (A * B).norm() > tol: raise TypeError('Matricies must commute.') - A = ops[0] - eigvals, eigvecs = la.eig(A.full()) - zipped = list(zip(-eigvals, range(len(eigvals)))) - zipped.sort() - ds, perm = zip(*zipped) - ds = -np.real(np.array(ds)) - perm = np.array(perm) - eigvecs_array = np.array( - [np.zeros((A.shape[0], 1), dtype=complex) for k in range(A.shape[0])]) - - for kk in range(len(perm)): # matrix with sorted eigvecs in columns - eigvecs_array[kk][:, 0] = eigvecs[:, perm[kk]] + eigvals, eigvecs = la.eigh(ops[0].full()) + perm = np.argsort(eigvals) + eigvecs = eigvecs[:, perm] + eigvals = eigvals[perm] + k = 0 - rng = np.arange(len(eigvals)) - while k < len(ds): + while k < N: # find degenerate eigenvalues, get indicies of degenerate eigvals - inds = np.array(abs(ds - ds[k]) < max(tol, tol * abs(ds[k]))) - inds = rng[inds] + ttol = max(tol, tol * abs(eigvals[k])) + inds, = np.where(abs(eigvals - eigvals[k]) < ttol) if len(inds) > 1: # if at least 2 eigvals are degenerate - eigvecs_array[inds] = degen(tol, eigvecs_array[inds], ops[1:]) - k = max(inds) + 1 - eigvals_out = np.zeros((num_ops, len(ds)), dtype=np.float64) - kets_out = np.empty((len(ds),), dtype=object) - kets_out[:] = [ - Qobj(eigvecs_array[j] / la.norm(eigvecs_array[j]), + eigvecs[:, inds] = _degen(tol, eigvecs[:, inds], ops, 1) + k = inds[-1] + 1 + + for k in range(N): + eigvecs[:, k] = eigvecs[:, k] / la.norm(eigvecs[:, k]) + + kets_out = [ + Qobj(eigvecs[:, j], dims=[ops[0].dims[0], [1]], shape=[ops[0].shape[0], 1]) - for j in range(len(ds)) + for j in range(N) ] + eigvals_out = np.zeros((len(ops), N), dtype=np.float64) if not evals: return kets_out else: - for kk in range(num_ops): - for j in range(len(ds)): - eigvals_out[kk, j] = np.real(np.dot( - eigvecs_array[j].conj().T, - ops[kk].data * eigvecs_array[j])) + for kk in range(len(ops)): + for j in range(N): + eigvals_out[kk, j] = ops[kk].matrix_element(kets_out[j], + kets_out[j]).real return eigvals_out, kets_out - - -def degen(tol, in_vecs, ops): - """ - Private function that finds eigen vals and vecs for degenerate matrices.. - """ - n = len(ops) - if n == 0: - return in_vecs - A = ops[0] - vecs = np.column_stack(in_vecs) - eigvals, eigvecs = la.eig(np.dot(vecs.conj().T, A.data.dot(vecs))) - zipped = list(zip(-eigvals, range(len(eigvals)))) - zipped.sort() - ds, perm = zip(*zipped) - ds = -np.real(np.array(ds)) - perm = np.array(perm) - vecsperm = np.zeros(eigvecs.shape, dtype=complex) - for kk in range(len(perm)): # matrix with sorted eigvecs in columns - vecsperm[:, kk] = eigvecs[:, perm[kk]] - vecs_new = np.dot(vecs, vecsperm) - vecs_out = np.array( - [np.zeros((A.shape[0], 1), dtype=complex) for k in range(len(ds))]) - for kk in range(len(perm)): # matrix with sorted eigvecs in columns - vecs_out[kk][:, 0] = vecs_new[:, kk] - k = 0 - rng = np.arange(len(ds)) - while k < len(ds): - inds = np.array(abs(ds - ds[k]) < max( - tol, tol * abs(ds[k]))) # get indicies of degenerate eigvals - inds = rng[inds] - if len(inds) > 1: # if at least 2 eigvals are degenerate - vecs_out[inds] = degen(tol, vecs_out[inds], ops[1:n]) - k = max(inds) + 1 - return vecs_out diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index 4d8fb92234..5ece1a9285 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -36,11 +36,37 @@ def test_simdiag_no_evals(num_mat): def test_simdiag_degen(): N = 10 U = qutip.rand_unitary(N) - matrix = U * qutip.qdiags([0,0,0,1,1,1,2,2,3,4], 0) * U.dag() - evals, evecs = qutip.simdiag([matrix]) + commuting_matrices = [ + U * qutip.qdiags([0, 0, 0, 1, 1, 1, 2, 2, 3, 4], 0) * U.dag(), + U * qutip.qdiags([0, 0, 0, 1, 2, 2, 2, 2, 2, 2], 0) * U.dag(), + U * qutip.qdiags([0, 0, 2, 1, 1, 2, 2, 3, 3, 4], 0) * U.dag(), + ] + all_evals, evecs = qutip.simdiag(commuting_matrices) + + for matrix, evals in zip(commuting_matrices, all_evals): + for eval, evec in zip(evals, evecs): + np.testing.assert_allclose( + (matrix * evec).full(), + (evec * eval).full() + ) + - for eval, evec in zip(evals[0], evecs): - assert matrix * evec == evec * eval +@pytest.mark.repeat(10) +def test_simdiag_degen_large(): + N = 20 + U = qutip.rand_unitary(N) + commuting_matrices = [ + U * qutip.qdiags(np.random.randint(0, 3, N), 0) * U.dag() + for _ in range(5) + ] + all_evals, evecs = qutip.simdiag(commuting_matrices, tol=1e-12) + + for matrix, evals in zip(commuting_matrices, all_evals): + for eval, evec in zip(evals, evecs): + np.testing.assert_allclose( + (matrix * evec).full(), + (evec * eval).full() + ) def test_simdiag_no_input(): @@ -49,10 +75,11 @@ def test_simdiag_no_input(): @pytest.mark.parametrize(['ops', 'error'], [ - pytest.param([qutip.basis(5,0)], 'square', id="Not square"), + pytest.param([qutip.basis(5, 0)], 'square', id="Not square"), pytest.param([qutip.qeye(5), qutip.qeye(3)], 'shape', id="shape mismatch"), pytest.param([qutip.destroy(5)], 'Hermitian', id="Non Hermitian"), - pytest.param([qutip.sigmax(), qutip.sigmay()], 'commute', id="Not commuting"), + pytest.param([qutip.sigmax(), qutip.sigmay()], 'commute', + id="Not commuting"), ]) def test_simdiag_errors(ops, error): with pytest.raises(TypeError) as err: From 4456bea4a81097baccd46d621d3d552c707222a8 Mon Sep 17 00:00:00 2001 From: Asier Galicia Date: Wed, 13 Oct 2021 12:05:47 +0200 Subject: [PATCH 097/112] More efficient creation of the hadamard matrix. For loops are slow. --- qutip/qip/operations/gates.py | 7 +++---- qutip/tests/test_gates.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/qutip/qip/operations/gates.py b/qutip/qip/operations/gates.py index 7c72cf66dd..53bd0675ef 100644 --- a/qutip/qip/operations/gates.py +++ b/qutip/qip/operations/gates.py @@ -892,11 +892,10 @@ def hadamard_transform(N=1): Quantum object representation of the N-qubit Hadamard gate. """ - data = 2 ** (-N / 2) * np.array([[(-1) ** _hamming_distance(i & j) - for i in range(2 ** N)] - for j in range(2 ** N)]) + data = [[1, 1], [1, -1]] + H = Qobj(data)/np.sqrt(2) - return Qobj(data, dims=[[2] * N, [2] * N]) + return tensor([H]*N) def _flatten(lst): diff --git a/qutip/tests/test_gates.py b/qutip/tests/test_gates.py index 588fa27b36..d42ba373e9 100644 --- a/qutip/tests/test_gates.py +++ b/qutip/tests/test_gates.py @@ -299,6 +299,20 @@ def test_cnot_explicit(self): [0, 0, 0, 1, 0, 0, 0, 0]]) np.testing.assert_allclose(test, expected, atol=1e-15) + + def test_hadamard_explicit(self): + test = gates.hadamard_transform(3).full() + expected = np.array([[ 1, 1, 1, 1, 1, 1, 1, 1], + [ 1, -1, 1, -1, 1, -1, 1, -1], + [ 1, 1, -1, -1, 1, 1, -1, -1], + [ 1, -1, -1, 1, 1, -1, -1, 1], + [ 1, 1, 1, 1, -1, -1, -1, -1], + [ 1, -1, 1, -1, -1, 1, -1, 1], + [ 1, 1, -1, -1, -1, -1, 1, 1], + [ 1, -1, -1, 1, -1, 1, 1, -1]]) + expected = expected/np.sqrt(8) + np.testing.assert_allclose(test, expected) + def test_cyclic_permutation(self): operators = [qutip.sigmax(), qutip.sigmaz()] test = gates.expand_operator(qutip.tensor(*operators), N=3, From ede4a5b2caf05bd524f3b90b83a3cd370f0f82d5 Mon Sep 17 00:00:00 2001 From: Asier Galicia <57414022+AGaliciaMartinez@users.noreply.github.com> Date: Wed, 13 Oct 2021 12:33:11 +0200 Subject: [PATCH 098/112] Update qutip/qip/operations/gates.py Co-authored-by: Simon Cross --- qutip/qip/operations/gates.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutip/qip/operations/gates.py b/qutip/qip/operations/gates.py index 53bd0675ef..67278318fb 100644 --- a/qutip/qip/operations/gates.py +++ b/qutip/qip/operations/gates.py @@ -892,8 +892,7 @@ def hadamard_transform(N=1): Quantum object representation of the N-qubit Hadamard gate. """ - data = [[1, 1], [1, -1]] - H = Qobj(data)/np.sqrt(2) + H = Qobj([[1, 1], [1, -1]]) / np.sqrt(2) return tensor([H]*N) From 311ba241a12c6f140093c169dbe31c2d753c2d1a Mon Sep 17 00:00:00 2001 From: Asier Galicia <57414022+AGaliciaMartinez@users.noreply.github.com> Date: Wed, 13 Oct 2021 12:38:26 +0200 Subject: [PATCH 099/112] Update qutip/qip/operations/gates.py Co-authored-by: Simon Cross --- qutip/qip/operations/gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/qip/operations/gates.py b/qutip/qip/operations/gates.py index 67278318fb..7b58b9bdf9 100644 --- a/qutip/qip/operations/gates.py +++ b/qutip/qip/operations/gates.py @@ -894,7 +894,7 @@ def hadamard_transform(N=1): """ H = Qobj([[1, 1], [1, -1]]) / np.sqrt(2) - return tensor([H]*N) + return tensor([H] * N) def _flatten(lst): From 9a8a2e8b65d121a1748da0bb6c51d28a9f8dfada Mon Sep 17 00:00:00 2001 From: Asier Galicia Date: Wed, 20 Oct 2021 11:47:54 +0200 Subject: [PATCH 100/112] Fix #1691 --- qutip/qobj.py | 2 +- qutip/tests/test_qobj.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qutip/qobj.py b/qutip/qobj.py index 8d280d6b18..bf82a4a665 100644 --- a/qutip/qobj.py +++ b/qutip/qobj.py @@ -693,7 +693,7 @@ def __pow__(self, n, m=None): # calculates powers of Qobj """ POWER operation. """ - if self.type not in ['oper', 'super']: + if self.shape[0] != self.shape[1]: raise Exception("Raising a qobj to some power works only for " + "operators and super-operators (square matrices).") diff --git a/qutip/tests/test_qobj.py b/qutip/tests/test_qobj.py index de20908a96..11caa048bc 100644 --- a/qutip/tests/test_qobj.py +++ b/qutip/tests/test_qobj.py @@ -313,6 +313,11 @@ def test_QobjPower(): q3 = q ** 3 assert (q3.full() - np.linalg.matrix_power(data, 3) < 1e-12).all() +def test_QobjPowerScalar(): + """Check that scalars obtained from bra*ket can be exponentiated. (#1691) + """ + ket = basis(2, 0) + assert (ket.dag()*ket)**2 == Qobj(1) def test_QobjNeg(): "Qobj negation" From 8c64f52b93369f584b2c304b43afa32620211300 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 18:49:20 +0000 Subject: [PATCH 101/112] Bump babel from 2.9.0 to 2.9.1 in /doc Bumps [babel](https://github.com/python-babel/babel) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/python-babel/babel/releases) - [Changelog](https://github.com/python-babel/babel/blob/master/CHANGES) - [Commits](https://github.com/python-babel/babel/compare/v2.9.0...v2.9.1) --- updated-dependencies: - dependency-name: babel dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index d8dc1296e5..58c3f66db5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,6 +1,6 @@ alabaster==0.7.12 appnope==0.1.2 -Babel==2.9.0 +Babel==2.9.1 backcall==0.2.0 certifi==2020.12.5 chardet==4.0.0 From a2f788c5a8e97420caccd38869078e4c35bd35ad Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 11:46:40 +0200 Subject: [PATCH 102/112] Add support for setting the numpy version in test runs. --- .github/workflows/tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f89efa2a4..cc9dcd08bb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,6 +60,12 @@ jobs: python-version: 3.8 nocython: 1 + # Numpy 1.21.0 run. Builds currently fail with Python 3.9 and + # numpy 1.21.X on the Intel 8171 and 8272 Azure VMs. + - case-name: numpy-1.20 + os: ubuntu-latest + numpy-requirement: ">=1.20,<1.21" + steps: - uses: actions/checkout@v2 @@ -79,7 +85,7 @@ jobs: fi export CI_QUTIP_WITH_OPENMP=${{ matrix.openmp }} if [[ -z "${{ matrix.nomkl }}" ]]; then - conda install blas=*=mkl numpy "scipy${{ matrix.scipy-requirement }}" + conda install blas=*=mkl "numpy${{ matrix.numpy-requirement }}" "scipy${{ matrix.scipy-requirement }}" elif [[ "${{ matrix.os }}" =~ ^windows.*$ ]]; then # Conda doesn't supply forced nomkl builds on Windows, so we rely on # pip not automatically linking to MKL. From d7bf159fb4cd77bf66a694fee4e2f2b5f9253f6d Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 11:57:34 +0200 Subject: [PATCH 103/112] Explicitly set Python version. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc9dcd08bb..5861516b64 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,6 +64,7 @@ jobs: # numpy 1.21.X on the Intel 8171 and 8272 Azure VMs. - case-name: numpy-1.20 os: ubuntu-latest + python-version: 3.9 numpy-requirement: ">=1.20,<1.21" steps: From fa90e5dd7d5ff5737f07d52bc3f9810e8850dd24 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 14:56:02 +0200 Subject: [PATCH 104/112] Pin base numpy to 1.21.0 in test runs. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5861516b64..d9dca5f5f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,6 +27,7 @@ jobs: # matrix size; make sure to test all supported versions in some form. python-version: [3.9] case-name: [defaults] + numpy-requirement: ["==1.21.0"] # Extra special cases. In these, the new variable defined should always # be a truth-y value (hence 'nomkl: 1' rather than 'mkl: 0'), because # the lack of a variable is _always_ false-y, and the defaults lack all From 51c29af203fc435f423759a333442719b7e98203 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 15:45:55 +0200 Subject: [PATCH 105/112] Pin numpy to 1.20.X by default. --- .github/workflows/tests.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d9dca5f5f1..9c1c88f67e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: # matrix size; make sure to test all supported versions in some form. python-version: [3.9] case-name: [defaults] - numpy-requirement: ["==1.21.0"] + numpy-requirement: [">=1.20,<1.21"] # Extra special cases. In these, the new variable defined should always # be a truth-y value (hence 'nomkl: 1' rather than 'mkl: 0'), because # the lack of a variable is _always_ false-y, and the defaults lack all @@ -52,6 +52,7 @@ jobs: - case-name: OpenMP os: ubuntu-latest python-version: 3.9 + numpy-requirement: ">=1.20,<1.21" openmp: 1 # Builds without Cython at runtime. This is a core feature; @@ -61,13 +62,6 @@ jobs: python-version: 3.8 nocython: 1 - # Numpy 1.21.0 run. Builds currently fail with Python 3.9 and - # numpy 1.21.X on the Intel 8171 and 8272 Azure VMs. - - case-name: numpy-1.20 - os: ubuntu-latest - python-version: 3.9 - numpy-requirement: ">=1.20,<1.21" - steps: - uses: actions/checkout@v2 From 166ffc87eb0ae33ee6ef269440e08de2a76524fa Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 16:22:22 +0200 Subject: [PATCH 106/112] Add host environment information. --- .github/workflows/tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c1c88f67e..11e03fbe03 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,6 +97,13 @@ jobs: conda list python -c "import qutip; qutip.about()" + - name: Environment information + run: | + uname -r + hostnamectl + free -h + lscpu + - name: Run tests # If our tests are running for longer than an hour, _something_ is wrong # somewhere. The GitHub default is 6 hours, which is a bit long to wait From 6c28e8274fe26e589ba940362f75b6d6f238b4bc Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 16:51:27 +0200 Subject: [PATCH 107/112] Don't call hostnamectl or lscpu on OS X. --- .github/workflows/tests.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 11e03fbe03..7c06e61407 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -99,10 +99,12 @@ jobs: - name: Environment information run: | - uname -r - hostnamectl + uname -a + if [[ "ubuntu-latest" == "${{ matrix.os }}" ]]; then + hostnamectl + lscpu + fi free -h - lscpu - name: Run tests # If our tests are running for longer than an hour, _something_ is wrong From cd4a90c3d90c08801118e7599675d64f989c7e7c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 16:52:48 +0200 Subject: [PATCH 108/112] Remove redundant setting of numpy-requirement in OpenMP tests run. --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c06e61407..da72c8f50f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,6 @@ jobs: - case-name: OpenMP os: ubuntu-latest python-version: 3.9 - numpy-requirement: ">=1.20,<1.21" openmp: 1 # Builds without Cython at runtime. This is a core feature; From 9922162a9b10b66e63f57ab422a239baca12d4fc Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 16:57:25 +0200 Subject: [PATCH 109/112] Use the selected version of numpy in all cases. --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index da72c8f50f..6fab25b674 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,6 +39,8 @@ jobs: os: ubuntu-latest python-version: 3.6 scipy-requirement: ">=1.4,<1.5" + # let the old SciPy version select an appropriate numpy version: + numpy-requirement: "" # No MKL runs. MKL is now the default for conda installations, but # not necessarily for pip. @@ -84,9 +86,9 @@ jobs: elif [[ "${{ matrix.os }}" =~ ^windows.*$ ]]; then # Conda doesn't supply forced nomkl builds on Windows, so we rely on # pip not automatically linking to MKL. - pip install numpy "scipy${{ matrix.scipy-requirement }}" + pip install "numpy${{ matrix.numpy-requirement }}" "scipy${{ matrix.scipy-requirement }}" else - conda install nomkl numpy "scipy${{ matrix.scipy-requirement }}" + conda install nomkl "numpy${{ matrix.numpy-requirement }}" "scipy${{ matrix.scipy-requirement }}" fi python -m pip install -e .[$QUTIP_TARGET] python -m pip install pytest-cov coveralls From 028b2623798eb1c9524941d06b93ab145b7f28cc Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 17:00:46 +0200 Subject: [PATCH 110/112] Don't call 'free' on OS X either. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6fab25b674..138d3b9751 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -104,8 +104,8 @@ jobs: if [[ "ubuntu-latest" == "${{ matrix.os }}" ]]; then hostnamectl lscpu + free -h fi - free -h - name: Run tests # If our tests are running for longer than an hour, _something_ is wrong From bc1b28535848da97d8cde7e69e2e68ec113af007 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 17:36:26 +0200 Subject: [PATCH 111/112] Pin numpy requirement for OpenMP test case on Python 3.9. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 138d3b9751..40154a92d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,6 +54,7 @@ jobs: - case-name: OpenMP os: ubuntu-latest python-version: 3.9 + numpy-requirement: [">=1.20,<1.21"] openmp: 1 # Builds without Cython at runtime. This is a core feature; From 818214c8e58d17c2f264d28eb8dc361eb0f32c5c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 22 Oct 2021 17:44:57 +0200 Subject: [PATCH 112/112] Remove brackets around numpy-requirement. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40154a92d1..eceee8cc60 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,7 +54,7 @@ jobs: - case-name: OpenMP os: ubuntu-latest python-version: 3.9 - numpy-requirement: [">=1.20,<1.21"] + numpy-requirement: ">=1.20,<1.21" openmp: 1 # Builds without Cython at runtime. This is a core feature;