Source code for sboxUv2.display.pollock

from collections import defaultdict

import matplotlib.pyplot as plt
from matplotlib.backend_bases import MouseEvent

from sboxUv2.core import Sb
from sboxUv2.statistics import \
    ddt, differential_spectrum, ddt_coeff_probability, \
    lat, walsh_spectrum, absolute_walsh_spectrum, lat_coeff_probability_permutation, lat_coeff_probability_function, \
    bct, boomerang_spectrum, bct_coeff_probability, \
    fbct, fbct_spectrum \
    


# !SECTION! Interactive Tables


# !SUBSECTION! General function to generate interacting views

[docs] def table_interactive_view( table, title="Table", desc="v", cmap="coolwarm", vmin=0, vmax=20 ): """Stops the execution flow and displays a new window containing a Pollock-style (in the sense of [C:BirPer15]) representation of a table. The table is assumed to be a 2-dimensional array containing integers. We associate a color to each value using the colormap `cmap`. Then, a 2D picture is generated representing the content of the table. A cross-hair cursor is displayed where the mouse, and the values of the coordinates are displayed under the picture. To describe the value at a given position, you need to specify the `desc` input. The cursor is handled using the TableCursor class. """ fig, ax = plt.subplots() ax.set_title(title) ax.imshow( table, cmap=cmap, vmin=vmin, vmax=vmax ) cursor = TableCursor(ax, lambda a,b : table[a][b], desc=desc) fig.canvas.mpl_connect('motion_notify_event', cursor.on_mouse_move) plt.show()
# !SUBSECTION! Specific instanciations
[docs] def lat_interactive_view( s, cmap="coolwarm", vmin=None, vmax=40, absolute=True, show_only=None): sb = Sb(s) table = lat(sb) if absolute: t = [[abs(table[a][b]) for b in range(0, len(table[a]))] for a in range(0, len(table))] if vmin == None: vmin = 0 else: t = table if vmin == None: vmin = -vmax if show_only != None: t = [[t[a][b] if t[a][b] in show_only else vmax+2 for b in range(0, len(t[a]))] for a in range(0, len(t))] table_interactive_view(t, title="LAT of {}".format(sb.name().decode("UTF-8")), desc="$\\sum_x (-1)^{ax +bS(x)}$", cmap=cmap, vmin=vmin, vmax=vmax )
[docs] def ddt_interactive_view( s, cmap="coolwarm", vmin=0, vmax=10, show_only=None ): sb = Sb(s) t = ddt(sb) if show_only != None: t = [[t[a][b] if t[a][b] in show_only else vmax+2 for b in range(0, len(t[a]))] for a in range(0, len(t))] table_interactive_view(t, title="DDT of {}".format(sb.name().decode("UTF-8")), desc="$\\#\\{x, S(x+a)+S(x)=b\\}$", cmap=cmap, vmin=vmin, vmax=vmax )
[docs] def bct_interactive_view( s, cmap="coolwarm", vmin=0, vmax=32, absolute=True, show_only=None ): sb = Sb(s) t = bct(sb) if show_only != None: t = [[t[a][b] if t[a][b] in show_only else vmax+2 for b in range(0, len(t[a]))] for a in range(0, len(t))] table_interactive_view(t, title="BCT of {}".format(sb.name().decode("UTF-8")), desc="$\\#\\{x, S^{-1}(S(x)+b) + S^{-1}(S(x+a)+b)=a\\}$", cmap=cmap, vmin=vmin, vmax=vmax )
[docs] def fbct_interactive_view( s, cmap="coolwarm", vmin=0, vmax=16, absolute=True, show_only=None ): sb = Sb(s) t = fbct(sb) if show_only != None: t = [[t[a][b] if t[a][b] in show_only else vmax+2 for b in range(0, len(t[a]))] for a in range(0, len(t))] table_interactive_view(t, title="F-BCT of {}".format(sb.name().decode("UTF-8")), desc="$\\#\\{x, \\sum_{y \\in x+<a, b>}S(y)\\}$", cmap=cmap, vmin=vmin, vmax=vmax )
# !SUBSECTION! The TableCursor class
[docs] class TableCursor: """ A cross hair cursor. Its implementation is largely taken from the matplotlib tutorial (see https://matplotlib.org/stable/gallery/event_handling/cursor_demo.html#sphx-glr-gallery-event-handling-cursor-demo-py). """ def __init__(self, ax, values_function, desc): self.ax = ax self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--') self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--') # text location in axes coordinates self.text = ax.text(0.1, -0.1, '', transform=ax.transAxes) self.values_function = values_function self.desc = desc
[docs] def set_cross_hair_visible(self, visible): need_redraw = self.horizontal_line.get_visible() != visible self.horizontal_line.set_visible(visible) self.vertical_line.set_visible(visible) self.text.set_visible(visible) return need_redraw
[docs] def on_mouse_move(self, event): if not event.inaxes: need_redraw = self.set_cross_hair_visible(False) if need_redraw: self.ax.figure.canvas.draw() else: self.set_cross_hair_visible(True) x, y = event.xdata, event.ydata # update the line positions self.horizontal_line.set_ydata([y]) self.vertical_line.set_xdata([x]) a, b = int(round(y)), int(round(x)) self.text.set_text('a=0x{:02x} b=0x{:02x} {}={:3d}'.format( a, b, self.desc, self.values_function(a, b) )) self.text.set_fontfamily("monospace") self.ax.figure.canvas.draw()
# !SECTION! Studying distributions of coefficients # !SUBSECTION! General function
[docs] def interactive_distribution_comparison( spec, in_length, out_length, expected_distrib, title="T", name="S", l_min=None, l_max=None, y_log_scale=True, ): # preprocessing arguments spectra = {} spectra[name] = defaultdict(float) absolute_spec = defaultdict(int) for k in spec.keys(): spectra[name][abs(k)] += spec[k] if l_min == None: l_min = 0 # computing expected probabilities spectra["Expected"] = defaultdict(float) finished = False c = l_min while not finished: p = expected_distrib(in_length, out_length, c) expected_card = p*2**out_length*(2**in_length-1) if expected_card > 2**-4: spectra["Expected"][c] = expected_card elif p != 0: finished = True c += 2 l_max = max(c, max(spectra[name].keys()) + 2) # plotting data itself fig, p = plt.subplots() p.set_xlabel("c") p.set_ylabel("$\\# \\{(a, b), |" + title + "[a,b]| = c\\}$") overall_max = 9 for w in spectra.keys(): abscissa = [] ordenna = [] for c in sorted(spectra[w].keys()): if c >= l_min and c <= l_max: abscissa.append(c) v = float(spectra[w][c]) ordenna.append(v) overall_max = max(overall_max, v) p.plot(abscissa, ordenna, marker="x" if w == "Expected" else "o", linestyle="-" if w == "Expected" else "", markersize=4, label=w) # plotting the difference abscissa = [] diff_min = [] diff_max = [] for c in range(l_min, l_max+1): if spectra[name][c] != 0 or spectra["Expected"][c] != 0: abscissa.append(float(c)) diff_min.append(float(min(spectra[name][c], spectra["Expected"][c]))) diff_max.append(float(max(spectra[name][c], spectra["Expected"][c]))) print(abscissa) print(diff_min) print(diff_max) p.fill_between(abscissa, diff_min, diff_max, alpha=0.5, linewidth=0) # adding the metadata p.legend(shadow=True, fontsize=18) p.set_xlim([l_min, l_max]) p.set_ylim([0.5, overall_max*1.2]) p.yaxis.get_label().set_fontsize(18) p.xaxis.get_label().set_fontsize(18) p.tick_params(labelsize=12) p.grid(color="0.8") if y_log_scale: # if require_version(9): # p.set_yscale("log", basey=2, nonposy="clip") # else: p.set_yscale("log", base=2, nonpositive="clip") plt.show()
# !SUBSECTION! Specific instanciations
[docs] def interactive_distribution_comparison_lat(s, y_log_scale=True): sb = Sb(s) if sb.is_invertible(): interactive_distribution_comparison( absolute_walsh_spectrum(sb), sb.get_input_length(), sb.get_output_length(), lat_coeff_probability_permutation, title="LAT", name=sb.name().decode("UTF-8"), y_log_scale=y_log_scale ) else: interactive_distribution_comparison( absolute_walsh_spectrum(sb), sb.get_input_length(), sb.get_output_length(), lat_coeff_probability_function, title="LAT", name=sb.name().decode("UTF-8"), y_log_scale=y_log_scale )
[docs] def interactive_distribution_comparison_ddt(s, y_log_scale=True): sb = Sb(s) interactive_distribution_comparison( differential_spectrum(sb), sb.get_input_length(), sb.get_output_length(), ddt_coeff_probability, title="DDT", name=sb.name().decode("UTF-8"), y_log_scale=y_log_scale )
[docs] def interactive_distribution_comparison_bct(s, y_log_scale=True): sb = Sb(s) interactive_distribution_comparison( boomerang_spectrum(sb), sb.get_input_length(), sb.get_output_length(), bct_coeff_probability, title="BCT", name=sb.name().decode("UTF-8"), y_log_scale=y_log_scale )