import networkx as nx #TODO: import only needed functions?
import matplotlib.pyplot as plt
import numpy as np
import re
from PIL import Image
from ..graph import *
from ..quantum_walk import QuantumWalk
from matplotlib.animation import FuncAnimation
from scipy.sparse import csr_array
python_range = range
# histogram is alias for bar width=1
[docs]
def plot_probability_distribution(
probabilities, plot=None, animate=False, show=True,
filename=None, interval=250, figsize=(10, 6), dpi=100,
repeat=True, repeat_delay=2500, range=None,
time_step=1, **kwargs):
"""
Plot the probability distributions of quantum walk states.
This function allows plotting the probability distributions
for multiple steps of a quantum walk evolution.
The generated figures can be displayed step-by-step,
saved as individual files, or used to create and save animations.
Parameters
----------
probabilities : :class:`numpy.ndarray`
An array representing the probabilities of the walker being
found at each vertex during the quantum walk evolution.
Each column corresponds to a vertex,
while each row represents a step in the walk.
plot : str, default=None
The plot type.
The valid options are
``{'line', 'bar', 'graph', 'histogram', 'plane'}``.
If ``None``, uses default plotting. Usually ``bar``,
but default plotting changes according to ``graph``.
show : bool, default=True
Whether or not to show plots or animation.
With ``show=True`` we have:
**Using Terminal**:
After shown, press *q* to quit.
If ``animate==False``,
the quantum walk will be shown step-by-step;
If ``animate==True``,
the entire animation is shown in a new window.
**Using Jupyter Notebook**:
If ``animate==False``,
all the quantum walk steps are shown at once.
If ``animate==True``,
the entire animation is shown as a html video.
filename : str, default=None
The filename path (with no format) where
the plot(s) will be saved.
If ``None`` no file is saved.
Otherwise, if ``animate==False``,
the j-step is saved in the ``filename-j.png`` file;
if ``animate==True``,
the entire walk is saved in the ``filename.fig`` file.
graph : optional
The structure of the graph on which the walk takes place.
The graph labels are used as plotting labels.
**Important**: check Graph Plots subsection in other parameters.
The following types are acceptable.
* :class:`hiperwalk.Graph`
Hiperwalk Graph.
It is used to generate default plotting for specific graphs.
User-specified values are not overridden.
* :class:`networkx.classes.graph`,
NetworkX Graph
* :class:`scipy.sparse.csr_matrix`
Adjacency matrix.
rescale : bool, optional
If ``False`` or omitted, the reference maximum probability
is the global one.
If ``True``, the reference maximum probability depends on
the current step, changing every image or frame.
For example, if the global maximum probability is 1,
``min_node_size, max_node_size = (300, 3000)``,
and the maximum probability of a given step is 0.5;
then for ``rescale=False``,
the step maximum node size shown is 1650
(halfway betweeen 300 and 3000),
while for ``rescale=True``,
the step maximum node size shown is 3000.
animate : bool, default=False
Whether or not to animate multiple plots.
If ``False``, each quantum walk step generates an image.
If ``True``, each quantum walk step is used as an animation frame.
interval : int, default=250
Time in milliseconds that each frame is shown if ``animate==True``.
figsize : tuple, default=(10, 6)
Figure size in inches. Must be a tuple in the format (WIDTH, HEIGHT).
dpi : float, default=100
Figure resolution in dots-per-inch.
repeat : bool, default: True
Whether or not to repeat the animation.
See :class:`matplotlib.animation.FuncAnimation`
repeat_delay : int, default=2500
Delay in milliseconds between animation repetitions.
See :class:`matplotlib.animation.FuncAnimation`
range: tuple of int, default=None
Range used for the quantum walk simulation.
See :meth:`QuantumWalk.simulate` for details.
If ``range is not None``,
each figure or frame will have a label with the corresponding time.
The values shown are
``time_step * np.arange(*range)``.
time_step: float, default=1
Time interval between simulation steps.
labels : dict, optional
Labels to be shown on graph.
If `graph` is `None`, `labels.keys()` must be integers
from 0 to `num_vert` - 1.
If `graph is not None`, `labels.keys()` must be
labels of the corresponding :class:`networkx.Graph`'s nodes.
**kwargs : dict, optional
Extra arguments to further customize plotting.
Valid arguments depend on ``plot``.
Check Other Parameters Section for details.
Other Parameters
----------------
Line Plots
See :obj:`matplotlib.pyplot.plot` for more optional keywords.
Bar Plots
See :obj:`matplotlib.pyplot.bar` for more optional keywords.
Graph Plots
See :obj:`networkx.draw <networkx.drawing.nx_pylab.draw>`
for more optional keywords.
graph :
Graph structure.
min_node_size, max_node_size : scalar, default=(300, 3000)
By default, nodes sizes depend on the probability.
``min_node_size`` and ``max_node_size`` describe the
inferior and superior limits for the node sizes, respectively.
node_size : scalar or list of scalars, optional
If ``node_size`` is a scalar,
all nodes will have the same size.
If ``node_size`` is a list of scalars,
vertices may have different sizes and
the length of ``node_size`` must match ``probabilities``.
The ``node_size`` argument is ignored if both
``min_node_size`` and ``max_node_size`` are set.
cmap : str, optional
A colormap for representing vertices probabilities.
if ``cmap='default'``, uses the ``'viridis'`` colormap.
For more colormap options, check
`Matplolib's Colormap reference
<https://matplotlib.org/stable/gallery/color/colormap_reference.html>`_.
Histogram Plots
See :obj:`matplotlib.pyplot.bar` for more optional keywords.
The ``width`` keyword is always overriden.
Plane Plots
dimensions: 2-tuple of int
plane dimensions in ``(x_dim, y_dim)`` format.
Raises
------
ValueError
If ``plot`` has an invalid value.
KeyError
If ``plot == 'graph' `` and keyword ``graph`` is not set.
Notes
-----
The core logic of the main implementation loop is more or less like follows.
>>> preconfigure() # doctest: +SKIP
>>> for prob in probabilities: # doctest: +SKIP
>>> configure() # doctest: +SKIP
>>> plot(prob) # doctest: +SKIP
``preconfigure()`` executes configurations that do
not change between plots, e.g. nodes positions in graph plots.
``configure()`` executes configurates that must be
(re)done for each iteration, e.g. setting figure size.
``plot()`` calls the appropriated plotting method with
customization parameters,
i.e. bar, line or graph plot with the respective valid kwargs.
"""
# TODO: option to use Graphviz instead of NetworkX to draw graphs.
# As noted by networkx's documentation:
# Proper graph visualization is hard, and we highly recommend
# that people visualize their graphs with tools dedicated to
# that task.
# https://networkx.org/documentation/stable/reference/drawing.html
# Figure size
if len(figsize) == 2:
fig_width, fig_height = figsize
else:
raise ValueError(
'figsize must be a tuple in the format (WIDTH, HEIGHT)'
)
# passes kwargs by reference to be updated accordingly
if 'graph' in kwargs:
plot = _default_graph_kwargs(kwargs, plot)
if plot is None:
plot = 'bar'
plot = plot.lower()
valid_plots = ['bar', 'line', 'graph', 'histogram', 'plane']
if plot not in valid_plots:
raise ValueError(
'Unexpected value for plot:' + str(plot) +
'. One of the following was expected: ' + str(valid_plots)
)
# dictionaries for function pointers
# preconfiguration: executed once before the loop starts
preconfigs = {valid_plots[0]: _preconfigure_plot,
valid_plots[1]: _preconfigure_plot,
valid_plots[2]: _preconfigure_graph_plot,
valid_plots[3]: _preconfigure_plot,
valid_plots[4]: _preconfigure_plot}
# configuration: executed every iteration before plotting
# expects return of fig, ax to be used for animations
configs = {valid_plots[0]: _configure_plot_figure,
valid_plots[1]: _configure_plot_figure,
valid_plots[2]: _configure_graph_figure,
valid_plots[3]: _configure_plot_figure,
valid_plots[4]: _configure_plane_figure}
# plot functions: code for plotting the graph accordingly
plot_funcs = {
valid_plots[0]: _plot_probability_distribution_on_bars,
valid_plots[1]: _plot_probability_distribution_on_line,
valid_plots[2]: _plot_probability_distribution_on_graph,
valid_plots[3]: _plot_probability_distribution_on_histogram,
valid_plots[4]: _plot_probability_distribution_on_plane
}
update_animation = {
valid_plots[0]: _update_animation_bars,
valid_plots[1]: _update_animation_line,
valid_plots[2]: None,
valid_plots[3]: _update_animation_bars,
valid_plots[4]: None
}
# preparing probabilities to shape requested by called functions
if len(probabilities.shape) == 1:
probabilities = np.array([probabilities])
# passes kwargs by reference to be updated accordingly
preconfigs[plot](probabilities, kwargs)
# matches a valid filename
filename_with_ext_pattern = re.compile(r'(.+)\.(.{3,4})$')
if not animate:
t = [None] * len(probabilities)
if range is not None:
t = QuantumWalk._range_to_tuple(range)
t = time_step*np.arange(*t)
for i in python_range(len(probabilities)):
# TODO: set figure size according to graph dimensions
# TODO: check for kwargs
fig, ax = configs[plot](fig_width=fig_width,
fig_height=fig_height,
dpi=dpi)
plot_funcs[plot](probabilities[i], ax, time=t[i], **kwargs)
plt.tight_layout()
# saves or shows image (or both)
if filename is not None:
filename_suffix = str(i).zfill(
len(str(len(probabilities) - 1)))
# Use the base name, suffix, and extension to assemble the
# name if filename was provided with an extension.
if m := filename_with_ext_pattern.match(filename):
name = m.group(1)
ext = m.group(2)
plt.savefig(f'{name}-{filename_suffix}.{ext}')
else:
plt.savefig(filename if len(probabilities) == 1
else filename + '-' + filename_suffix)
if not show:
plt.close()
if show:
plt.show()
else:
fig, ax = configs[plot](fig_width=fig_width,
fig_height=fig_height,
dpi=dpi)
from itertools import cycle
t = None
if range is not None:
t = QuantumWalk._range_to_tuple(range)
t[0] -= t[2]
t = time_step*np.arange(*t)
t = cycle(t)
if plot == 'plane':
from functools import partial
surf, cbar = plot_funcs[plot](probabilities[0], ax,
**kwargs)
anim = FuncAnimation(
fig,
partial(plot_funcs[plot],
ax=ax,
surf=surf,
cbar=cbar,
time=t,
**kwargs),
frames=probabilities,
interval=interval,
repeat=repeat,
repeat_delay=repeat_delay)
elif plot == 'graph':
from functools import partial
ax, cbar = plot_funcs[plot](probabilities[0], ax,
**kwargs)
anim = FuncAnimation(
fig,
partial(plot_funcs[plot], ax=ax, cbar=cbar,
time=t, **kwargs),
frames=probabilities,
interval=interval,
repeat=repeat,
repeat_delay=repeat_delay)
else:
artists = plot_funcs[plot](probabilities[0], ax, **kwargs)
anim = FuncAnimation(
fig,
update_animation[plot],
frames=probabilities,
fargs=(artists,
ax if 'min_prob' not in kwargs else None,
t),
interval=interval,
repeat=repeat,
repeat_delay=repeat_delay)
if filename is not None:
# Use .gif extension if the user didn't provide an
# extension for the animation file.
if not filename_with_ext_pattern.match(filename):
filename = filename + '.gif'
anim.save(filename)
if show:
if _is_in_notebook():
from IPython import display
# embedding animation in jupyter notebook
video = anim.to_jshtml()
html = display.HTML(video)
display.display(html)
plt.close()
else:
plt.show()
def _default_graph_kwargs(kwargs, plot):
if ((plot is None or plot == 'graph' or plot == 'plane')
and not 'cmap' in kwargs
):
kwargs['cmap'] = 'default'
if 'cmap' in kwargs:
if kwargs['cmap'] == 'default':
kwargs['cmap'] = 'viridis'
graph = kwargs['graph']
# Hiperwalk Grid
if hasattr(graph, '_euc_dim') and graph._euc_dim == 2:
if plot is None:
plot = 'plane'
if plot == 'plane' and 'dimensions' not in kwargs:
kwargs['dimensions'] = graph.dimensions()
return plot
# Hiperwalk Hypercube
plot = 'graph' if plot is None else plot
is_hypercube = False
try:
is_hypercube = graph.degree(0) == graph.dimension()
except AttributeError:
pass
if is_hypercube and (plot is None or plot == 'graph'):
plot = 'graph'
nx_graph = nx.from_scipy_sparse_array(graph.adjacency_matrix())
for v in nx_graph:
try:
nx_graph.nodes[v]["subset"] = v.bit_count()
except AttributeError:
nx_graph.nodes[v]["subset"] = bin(v).count('1')
kwargs['pos'] = nx.multipartite_layout(nx_graph)
dim = graph.dimension()
kwargs['graph'] = nx_graph
kwargs['edge_color'] = (0, 0, 0, max(0.002, 2**(-dim/2)))
kwargs['labels'] = {0: 0, 2**dim - 1 : 2**dim - 1}
kwargs['min_node_size'] = 1
kwargs['max_node_size'] = 1000
kwargs['edgecolors'] = None
return plot
# other graphs
if hasattr(graph, 'number_of_vertices'):
nx_graph = nx.Graph(graph.adjacency_matrix())
elif hasattr(graph, 'number_of_nodes'):
nx_graph = graph
else:
nx_graph = nx.Graph(graph)
kwargs['graph'] = nx_graph
return plot
def _preconfigure_plot(probabilities, kwargs):
"""
Configure static parameters for matplotlib plot.
Set parameters that need not to be changed for multiple plots
or animation of a quantum walk.
For example: minimum and maximum allowed node size.
Parameters
----------
probabilities : list of floats
Probabilities of the walker being found on each vertex.
kwargs : dict
Reference of kwargs containing all extra keywords.
"""
if ('rescale' not in kwargs or not kwargs.pop('rescale')):
kwargs['min_prob'] = 0
kwargs['max_prob'] = probabilities.max()
#kwargs passed by reference
def _preconfigure_graph_plot(probabilities, kwargs):
"""
Configure static parameters for graph plot.
Set parameters that need not to be changed for multiple plots
or animation of a quantum walk.
For example: the graph.
The graph must be sent via kwargs via the 'graph' keyword.
Parameters
----------
probabilities : list of floats
Probabilities of the walker being found on each vertex.
kwargs : dict
Reference of kwargs containing all extra keywords.
See Also
--------
_configure_nodes
"""
if ('rescale' not in kwargs or not kwargs['rescale']):
kwargs['min_prob'] = 0 #min_prob
kwargs['max_prob'] = probabilities.max() #max_prob
kwargs['rescale'] = False
if 'graph' not in kwargs:
raise KeyError("'graph' kwarg not provided.")
graph = kwargs['graph']
if isinstance(graph, csr_array):
kwargs['graph'] = nx.from_scipy_sparse_array(graph)
elif isinstance(graph, Graph):
adj_matrix = graph.adjacency_matrix()
kwargs['graph'] = nx.from_scipy_sparse_array(adj_matrix)
if 'min_node_size' not in kwargs:
kwargs['min_node_size'] = None
if 'max_node_size' not in kwargs:
kwargs['max_node_size'] = None
# setting static kwargs for plotting
# kwargs dictionary is updated by reference
# TODO: change ConfigureNodes parameters
# (remove G and use information from kwargs)
_configure_nodes(kwargs['graph'], probabilities, kwargs)
def _configure_figure(fig_width, fig_height, dpi):
"""
Set basic figure configuration.
Creates a figure with size depending on the parameters.
Parameters
----------
num_vert: int
number of vertices in the graph
fig_width, fig_height : float, optional
Custom figure width and height, respectively.
If not set, the default value is used.
Returns
-------
fig : current figure
ax : current figure axes
"""
fig = plt.figure(figsize=(fig_width, fig_height), dpi=dpi)
ax = plt.gca()
return fig, ax
def _configure_plot_figure(fig_width, fig_height, dpi):
"""
Set basic figure configuration for matplotlib plots.
"""
fig, ax = _configure_figure(fig_width, fig_height, dpi)
plt.xlabel("Vertex", size=18)
plt.ylabel("Probability", size=18)
plt.tick_params(length=7, labelsize=14)
return fig, ax
def _configure_graph_figure(fig_width,
fig_height,
dpi):
return _configure_figure(fig_width, fig_height, dpi)
def _configure_plane_figure(fig_width,
fig_height,
dpi):
#fig, ax =_configure_figure(num_vert, fig_width, fig_height)
#ax = fig.add_subplot(projection='3d')
fig, ax = plt.subplots(figsize=(fig_width, fig_height),
subplot_kw={"projection": "3d"},
dpi=dpi)
ax.tick_params(length=10, width=1, labelsize=16, pad=10)
ax.set_xlabel('Vertex X', labelpad=15, fontsize=18)
ax.set_ylabel('Vertex Y', labelpad=15, fontsize=18)
ax.set_zlabel('Probability', labelpad=30, fontsize=18)
return fig, ax
def _plot_probability_distribution_on_bars(
probabilities, ax, labels=None, graph=None,
min_prob=None, max_prob=None, time=None, **kwargs
):
"""
Plot probability distribution using matplotlib bar plot.
Parameters
----------
probabilities : list of floats
Probabilities of the walker being found on each vertex.
ax
matplotlib ax on which the figure is drawn.
{labels, graph, min_prob, max_prob} : optional
Final configuration parameters.
Refer to _posconfigure_plot_figure.
**kwargs : dict, optional
Extra parameters for plotting. Refer to matplotlib.pyplot.bar
See Also
--------
_posconfigure_plot_figure : Sets final configuraation for exhibition.
matplotlib.pyplot.bar
"""
bars = plt.bar(np.arange(len(probabilities)), probabilities, **kwargs)
_posconfigure_plot_figure(ax, len(probabilities), labels, graph,
min_prob, max_prob, time)
return [bars]
def _update_animation_bars(frame, bars, ax, time):
bars = bars[0]
for i, bar in enumerate(bars):
bar.set_height(frame[i])
if ax is not None:
_rescale_axis(ax, 0, frame.max())
if time is not None:
t = next(time)
title = plt.title('Time: ' + str(t), loc='right')
return [bars]
def _plot_probability_distribution_on_histogram(
probabilities, ax, labels=None, graph=None,
min_prob=None, max_prob=None, **kwargs
):
"""
Plot probability distribution as histogram.
Alias for no space between bars in bar plot.
Parameters
----------
Refer to _plot_probability_distribution_on_bars
See Also
--------
_plot_probability_distribution_on_bars
"""
kwargs['width'] = 1
return _plot_probability_distribution_on_bars(
probabilities, ax, labels, graph, min_prob, max_prob, **kwargs
)
def _plot_probability_distribution_on_line(
probabilities, ax, labels=None, graph=None,
min_prob=None, max_prob=None, time=None, **kwargs
):
"""
Plots probability distribution using matplotlib's line plot.
Parameters
----------
probabilities : list of floats
Probabilities of the walker being found on each vertex.
ax
matplotlib ax on which the figure is drawn.
{labels, graph, min_prob, max_prob} : optional
Final configuration parameters.
Refer to _posconfigure_plot_figure.
**kwargs : dict, optional
Extra parameters for plotting. Refer to matplotlib.pyplot.plot
See Also
--------
_posconfigure_plot_figure : Sets final configuraation for exhibition.
matplotlib.pyplot.plot
"""
line = plt.plot(np.arange(len(probabilities)),
probabilities, **kwargs)
_posconfigure_plot_figure(
ax, len(probabilities), labels, graph, min_prob, max_prob, time
)
return line
def _update_animation_line(frame, line, ax, time):
line = line[0]
line.set_ydata(frame)
if ax is not None:
_rescale_axis(ax, 0, frame.max())
if time is not None:
t = next(time)
title = plt.title('Time: ' + str(t), loc='right')
return [line]
def _posconfigure_plot_figure(ax, num_vert, labels=None, graph=None,
min_prob=None, max_prob=None, time=None):
"""
Add final touches to the plotted figure.
The labels are added to the figure and
the figure's y-axis range is configured.
Parameters
----------
ax
matplotlib ax on which the figure is drawn
num_vert : int
number of vertices in the graph
labels : dict, optional
Labels to be shown on graph.
If `graph` is `None`, `labels.keys()` must be integers
from 0 to `num_vert` - 1.
graph : networkx graph, optional
Graph on which the quantum walk occured.
if not None, the graph nodes are used to match `labels` keys.
min_prob, max_prob : int, optional
If both are set, describe the y-axis limit.
"""
if labels is not None:
if graph is None:
ax.set_xticks( list(labels.keys()), list(labels.values()) )
else:
nodes = list(graph.nodes())
nodes = {i : labels[ nodes[i] ] for i in range(num_vert)
if nodes[i] in labels}
ax.set_xticks( list(nodes.keys()), list(nodes.values()) )
else:
from matplotlib.ticker import MaxNLocator
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
if graph is not None:
ax.set_xlim((0, num_vert - 1))
loc = ax.xaxis.get_major_locator()
ind = loc().astype('int')
ind = [i for i in ind if i >=0 and i < num_vert]
nodes = list(range(0, graph.number_of_vertices()))
ax.set_xticks(ind, [nodes[i] for i in ind])
else:
ax.set_xlim((0, num_vert - 1))
loc = ax.xaxis.get_major_locator()
ind = loc().astype('int')
ind = [i for i in ind if i >=0 and i < num_vert]
ax.set_xticks(ind)
if min_prob is not None and max_prob is not None:
# plt.ylim((min_prob, max_prob*1.02))
_rescale_axis(ax, min_prob, max_prob)
if time is not None:
plt.title('Time: ' + str(time), loc='right')
def _rescale_axis(ax, min_prob, max_prob):
ax.set_ylim((min_prob, max_prob*1.02))
def _plot_probability_distribution_on_graph(probabilities, ax, time=None,
**kwargs):
"""
Draw graph and illustrates the probabilities depending on the
volatile parameters.
See Also
--------
_update_nodes : sets volatile parameters
:obj:`networkx.draw <networkx.drawing.nx_pylab.draw>`
_configure_colorbar
"""
cbar = kwargs.pop('cbar') if 'cbar' in kwargs else None
# UpdateNodes may create kwargs['node_size']
# min_node_size and max_node_size are not valid keys
# for nx.draw kwargs
_update_nodes(probabilities, kwargs.pop('min_node_size'),
kwargs.pop('max_node_size'), kwargs)
vmin = kwargs.pop('min_prob')
vmax = kwargs.pop('max_prob')
ax.clear()
nx.draw(kwargs.pop('graph'), ax=ax,
node_size=kwargs.pop('node_size'),
vmin=vmin, vmax=vmax, **kwargs)
# Note: nx.draw_networkx_labels dramatically increases plotting time.
# It is called by nx.draw
kwargs['min_prob'] = vmin
kwargs['max_prob'] = vmax
# setting and drawing colorbar
if 'cmap' in kwargs:
cbar = _configure_colorbar(ax, cbar, kwargs)
if time is not None:
try:
time = next(time)
except TypeError:
pass
ax.set_title('Time: ' + str(time), loc='right')
return [ax, cbar]
def _configure_nodes(G, probabilities, kwargs):
"""
Configure static attributes of nodes.
Configures nodes attributes that will not change
during multiple plots or an animation of a quantum walk.
Parameters
----------
kwargs: dict
Reference to the dictionary **kwargs.
Some entries may be added or altered.
See Also
--------
_update_nodes
"""
# setting colormap related attributes
if 'cmap' in kwargs:
if kwargs['cmap'] == 'default':
kwargs['cmap'] = 'viridis'
# setting node attributes
if 'edgecolors' not in kwargs:
kwargs['edgecolors'] = 'black'
if 'linewidths' not in kwargs:
kwargs['linewidths'] = 1
if 'with_labels' not in kwargs:
kwargs['with_labels'] = True
if kwargs['with_labels'] and 'font_color' not in kwargs:
kwargs['font_color'] = 'black'
# calculates vertices positions.
# needed to do beforehand in order to fix position for multiple steps
# the user may choose any networkx graph_layout function
# as long as only the graph is
# the required parameter. Check
# https://networkx.org/documentation/stable/reference/drawing.html#module-networkx.drawing.layout
# For further customisation,
# the user may call any networkx graph layout function
# BEFORE calling plot_probability_distribution and
# using its return as the 'pos' kwarg.
if 'pos' not in kwargs:
if 'graph_layout' in kwargs:
func = kwargs.pop('graph_layout')
kwargs['pos'] = func(G)
else:
kwargs['pos'] = nx.kamada_kawai_layout(G)
def _update_nodes(probabilities, min_node_size, max_node_size, kwargs):
"""
Configure probability-related attributes of nodes.
Configures attributes that may change depending on the probability
at each (saved) step of the quantum walk.
See Also
--------
_configure_nodes
Notes
-----
The separation between UpdateNodes and ConfigureNodes optimizes
plotting multiple images.
"""
if 'cmap' in kwargs:
kwargs['node_color'] = probabilities
if 'node_size' not in kwargs:
if min_node_size is None:
min_node_size = 300
if max_node_size is None:
max_node_size = 3000
if min_node_size is not None and max_node_size is not None:
if ('rescale' in kwargs and kwargs.pop('rescale')):
kwargs['min_prob'] = 0
kwargs['max_prob'] = probabilities.max()
# calculating size of each node acording to probability
# as a function f(x) = ax + b where b = min_size and
# max_size = a*(max_prob-min_prob) + min_size
a = ((max_node_size - min_node_size)
/ (kwargs['max_prob'] - kwargs['min_prob']))
kwargs['node_size'] = list(map(
lambda x: a*x + min_node_size, probabilities
))
def _configure_colorbar(ax, cbar, kwargs):
"""
Add a colorbar in the figure besides the given ax
Parameters
----------
ax : :class:`matplotlib.axes.Axes`
Ax on which the plot was drawn.
kwargs : dict
Dictionary containing the keys 'cmap', 'min_prob' and 'max_prob'.
'min_prob' and 'max_prob' describe
the inferior and superior limit for colorbar values, respectively.
'cmap' describes a valid matplotlib colormap
"""
from mpl_toolkits.axes_grid1 import make_axes_locatable
sm = plt.cm.ScalarMappable(
cmap=kwargs['cmap'],
norm=plt.Normalize(vmin=kwargs['min_prob'],
vmax=kwargs['max_prob'])
)
if cbar is None:
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='2.5%', pad=0.01)
cbar = plt.colorbar(
sm,
ticks=np.linspace(kwargs['min_prob'],
kwargs['max_prob'],
num=5),
cax=cax
)
cbar.ax.tick_params(labelsize=14, length=7)
else:
cbar.update_normal(sm)
return cbar
def _default_plane_kwargs(kwargs):
if not 'cmap' in kwargs:
kwargs['cmap'] = 'default'
if 'cmap' in kwargs:
if kwargs['cmap'] == 'default':
kwargs['cmap'] = 'viridis'
if 'linewidth' not in kwargs:
kwargs['linewidth'] = 0
if 'antialiased' not in kwargs:
kwargs['antialiased'] = False
if 'cstride' not in kwargs:
kwargs['cstride'] = 1
if 'rstride' not in kwargs:
kwargs['rstride'] = 1
if 'alpha' not in kwargs:
kwargs['alpha'] = 0.5
def _plot_probability_distribution_on_plane(
probabilities, ax, surf=None, cbar=None, labels=None, graph=None,
min_prob=None, max_prob=None, dimensions=None, time=None, **kwargs
):
"""
Plots probability distribution on the plane.
"""
x_dim, y_dim = dimensions
X = np.arange(0, x_dim, 1)
Y = np.arange(0, y_dim, 1)
Y, X = np.meshgrid(Y, X)
Z = np.reshape(probabilities, (x_dim, y_dim))
_default_plane_kwargs(kwargs)
cmap = kwargs.pop('cmap')
mappable = plt.cm.ScalarMappable(cmap=cmap)
mappable.set_array(Z)
if min_prob is not None and max_prob is not None:
vmin = min_prob
vmax = max_prob
else: #rescale
vmin = 0
vmax = Z.max()
mappable.set_clim(vmin, vmax)
# division by 4 apparently normalize the colors
if surf is None:
surf = [0]
else:
surf[0].remove()
surf[0] = ax.plot_surface(X, Y, Z, cmap=mappable.cmap,
vmin=vmin/4, vmax=vmax/4,
**kwargs)
ax.set_zlim(vmin, vmax)
if time is not None:
try:
time = next(time)
except TypeError:
pass
ax.set_title('Time: ' + str(time), loc='right')
kwargs['cmap'] = cmap # reinserts into kwargs
if cbar is None:
cbar = plt.colorbar(mappable,
shrink=0.4, aspect=20,
pad=0.15, ax=ax)
else:
cbar.update_normal(mappable)
cbar.ax.tick_params(length=10, width=1, labelsize=16)
return [[surf[0]], cbar]
def _is_in_notebook():
ipython_shells = (
'XPythonShell', # jupyterlite-xeus-python
'Interpreter', # jupyterlite-pyodide-kernel
'ZMQInteractiveShell', # ipykernel
)
try:
shell = get_ipython().__class__.__name__
if shell in ipython_shells:
return True
return False
except:
return False
##########################################################################
[docs]
def plot_success_probability(range, probabilities, figsize=(10, 6),
dpi=100, time_step=1, **kwargs):
r"""
Plot the success probability as a function of the number of steps.
Assumes that the probabilities have already been calculated.
Parameters
----------
range: tuple of int
Range used for the quantum walk simulation.
See :meth:`QuantumWalk.simulate` for details.
probabilities:
Success probabilities with respect to ``time``,
such that ``probabilities[i]`` corresponds to ``i``-th
timestamp described by ``time``.
figsize : tuple, default=(10, 6)
Figure size in inches. Must be a tuple in the format (WIDTH, HEIGHT).
dpi : float, default=100
Figure resolution in dots-per-inch.
time_step: float, default=1
Time interval between simulation steps.
**kwargs:
Additional arguments to customize plot.
See :obj:`matplotlib.pyplot.plot` for the optional keywords.
See Also
--------
QuantumWalk.simulate
QuantumWalk.success_probability
matplotlib.pyplot.plot
"""
# Figure size
if len(figsize) == 2:
fig_width, fig_height = figsize
else:
raise ValueError(
'figsize must be a tuple in the format (WIDTH, HEIGHT)'
)
_configure_figure(fig_width, fig_height, dpi)
range = QuantumWalk._range_to_tuple(range)
range = time_step*np.arange(*range)
plt.xlabel('Time', fontsize=18)
plt.ylabel('Success probability', fontsize=18)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
ax = plt.gca()
ax.set_ylim(0, 1.05*max(probabilities))
plt.plot(range, probabilities, **kwargs)
plt.show()