From ee58512534ec539980d362ed4632d5b83cfc2232 Mon Sep 17 00:00:00 2001 From: Maria Marchwicka Date: Mon, 9 Nov 2020 08:15:43 +0100 Subject: [PATCH] SignaturePlote, import instead of attach, jupyter friendly plots --- cable_signature.sage | 60 ++++++++++------ signature.sage | 164 ++++++++++++++++++++----------------------- 2 files changed, 118 insertions(+), 106 deletions(-) diff --git a/cable_signature.sage b/cable_signature.sage index 3612ed5..04cc92a 100644 --- a/cable_signature.sage +++ b/cable_signature.sage @@ -1,16 +1,31 @@ #!/usr/bin/env sage -python + import numpy as np import itertools as it -import matplotlib.pyplot as plt - import warnings import re -import inspect - from typing import Iterable from collections import Counter from sage.arith.functions import LCM_list +import importlib +def import_sage(module_name): + + importlib.invalidate_caches() + sage_name = module_name + ".sage" + python_name = module_name + ".sage.py" + + if os.path.isfile(sage_name): + os.system('sage --preparse {}'.format(sage_name)); + os.system('mv {} {}.py'.format(python_name, module_name)) + + if module_name in sys.modules: + return importlib.reload(sys.modules[module_name]) + return importlib.import_module(module_name, package=None) +sig = import_sage('signature') + + +# ############################################################################# # 9.11 (9.8) # 9.15 (9.9) PLOTS_DIR = "plots" @@ -18,8 +33,8 @@ PLOTS_DIR = "plots" class CableSummand(): def __init__(self, knot_as_k_values): - self.knot_as_k_values = knot_as_k_values + self.knot_as_k_values = knot_as_k_values self.knot_description = self.get_summand_descrption(knot_as_k_values) self.signature_as_function_of_theta = \ self.get_summand_signature_as_theta_function() @@ -90,14 +105,14 @@ class CableSummand(): results.append((e * ksi, -1 * sgn(k_n))) results.append((1 - e * ksi, 1 * sgn(k_n))) - return SignatureFunction(values=results) + return sig.SignatureFunction(values=results) @classmethod def get_satellite_part(cls, *knot_as_k_values, theta=0): patt_k = knot_as_k_values[-1] ksi = 1/(2 * abs(patt_k) + 1) - satellite_part = SignatureFunction() + satellite_part = sig.SignatureFunction() # For each knot summand consider k values in reversed order, # ommit k value for pattern. for layer_num, k in enumerate(knot_as_k_values[:-1][::-1]): @@ -120,7 +135,7 @@ class CableSummand(): for a in range(k)}) counter.update(Counter({(2 * a + 1)/(2 * q) : sgn(j) for a in range(k + 1, q)})) - return SignatureFunction(counter=counter) + return sig.SignatureFunction(counter=counter) def get_summand_signature_as_theta_function(self): knot_as_k_values = self.knot_as_k_values @@ -168,7 +183,8 @@ class CableSummand(): if save_path is not None: file_name = self.get_file_name_for_summand_plot(theta) save_path = os.path.join(save_path, file_name) - pp.plot_sum_with_other(sp, title=title, save_path=save_path) + sig.SignaturePloter.plot_sum_of_two(pp, sp, title=title, + save_path=save_path) def plot_summand(self): range_limit = min(self.knot_as_k_values[-1] + 1, 3) @@ -178,10 +194,12 @@ class CableSummand(): class CableSum(): def __init__(self, knot_sum): + self.knot_sum_as_k_valus = knot_sum self.knot_description = self.get_knot_descrption(knot_sum) self.patt_k_list = [abs(i[-1]) for i in knot_sum] self.patt_q_list = [2 * i + 1 for i in self.patt_k_list] + if any(n not in Primes() for n in self.patt_q_list): msg = "Incorrect k- or q-vector. This implementation assumes that"\ + " all last q values are prime numbers.\n" + \ @@ -234,7 +252,8 @@ class CableSum(): file_name = re.sub(r', ', '_', str(thetas)) file_name = re.sub(r'[\[\]]', '', str(file_name)) file_path = os.path.join(save_path, file_name) - pp.plot_sum_with_other(sp, title=title, save_path=file_path) + sig.SignaturePloter.plot_sum_of_two(pp, sp, title=title, + save_path=file_path) if save_path is not None: @@ -249,11 +268,12 @@ class CableSum(): sf_list.append(sf_list[-1]) # sf_list.append(sf_list[-1]) - SignatureFunction.plot_many(*sf_list) + sig.SignaturePloter.plot_many(*sf_list) # pp, sp, sf = knot.signature_as_function_of_theta(thetas[i]) # (pp + sp) = sp.plot # - # pp.plot_sum_with_other(sp, title=title, save_path=file_path) + # sig.SignatureFunction.plot_sum_of_two(pp, sp, title=title, + # save_path=file_path) @@ -311,8 +331,8 @@ class CableSum(): verbose = kwargs['verbose'] thetas = self.parse_thetas(*thetas) - satellite_part = SignatureFunction() - pattern_part = SignatureFunction() + satellite_part = sig.SignatureFunction() + pattern_part = sig.SignatureFunction() # for each cable knot (summand) in cable sum apply theta for i, knot in enumerate(self.knot_summands): @@ -357,7 +377,7 @@ class CableSum(): shifted_thetas = [shift * th for th in thetas] pp, sp, sf= self.signature_as_function_of_theta(*shifted_thetas) limit = 5 + np.count_nonzero(shifted_thetas) - extremum = abs(sf.extremum(limit=limit)) + extremum = abs(sf.extremum(limit=limit)[1]) if shift > 1: print(shifted_thetas, end=" ") print(extremum) @@ -509,7 +529,7 @@ CableSum.get_signature_as_function_of_theta.__doc__ = \ k - parameters for a cable: parameters k_i (i=1,.., n-1) for satelit knots T(2, 2k_i + 1) and - the last one - k_n for a pattern knot T(2, 2k_n + 1). Returns a function that will take theta vector as an argument and return - an object SignatureFunction. + an object sig.SignatureFunction. To calculate signature function for a cable sum and a theta values vector, use as below. @@ -555,7 +575,7 @@ CableSum.get_signature_as_function_of_theta.__doc__ = \ get_summand_signture_function_docsting = \ """ - This function returns SignatureFunction for previously defined single + This function returns sig.SignatureFunction for previously defined single cable T_(2, q) and a theta given as an argument. The cable was defined by calling function get_summand_signature_as_theta_function(*arg) @@ -570,7 +590,7 @@ signature_as_function_of_theta_docstring = \ """ Arguments: - Returns object of SignatureFunction class for a previously defined + Returns object of sig.SignatureFunction class for a previously defined connected sum of len(arg) cables. Accept len(arg) arguments: for each cable one theta parameter. If call with no arguments, all theta parameters are set to be 0. @@ -583,7 +603,7 @@ signature_as_function_of_theta_docstring = \ # T(2, q_n) is a pattern knot for a single cable from a cable sum # theta: twist/character for the cable (value form v vector) # Return: -# SignatureFunction created for pattern signature function +# sig.SignatureFunction created for pattern signature function # for a given cable and theta/character # Based on: # Proposition 9.8. in Twisted Blanchfield Pairing @@ -596,6 +616,6 @@ signature_as_function_of_theta_docstring = \ # n integers that encode a single cable, i.e. # values of q_i for T(2,q_0; 2,q_1; ... 2, q_n) # Return: -# a function that returns SignatureFunction for this single cable +# a function that returns sig.SignatureFunction for this single cable # and a theta given as an argument # """ diff --git a/signature.sage b/signature.sage index 2fc788a..261bec4 100644 --- a/signature.sage +++ b/signature.sage @@ -1,20 +1,31 @@ #!/usr/bin/env sage -python -import numpy as np -import itertools as it -from typing import Iterable + from collections import Counter -from sage.arith.functions import LCM_list -import warnings -import re import matplotlib.pyplot as plt import inspect from PIL import Image from pathlib import Path +import warnings + # 9.11 (9.8) # 9.15 (9.9) -class SignatureFunction(): +JUPYTER = 'ipykernel' +IPy_TERMINAL = 'IPython' + +def get_ipython_info(): + if JUPYTER in sys.modules: + return JUPYTER + elif IPy_TERMINAL in sys.modules: + return IPy_TERMINAL + return False + +global ipython_info +ipython_info = get_ipython_info() + + +class SignatureFunction: def __init__(self, values=None, counter=None, plot_title=''): @@ -104,35 +115,43 @@ class SignatureFunction(): counter = Counter({mod_one(2 * k) : v for k, v in items if k >= 1/2}) return SignatureFunction(counter=counter) - def is_zero_everywhere(self): return not any(self.jumps_counter.values()) - - def extremum(self, limit=None): - max = 0 + def extremum(self, limit=math.inf): + max_point = (0, 0) current = 0 items = sorted(self.jumps_counter.items()) for arg, jump in items: current += 2 * jump assert current == self(arg) + jump - if abs(current) > abs(max): - max = current - if limit is not None: - if abs(max) > limit: - break - return max + if abs(current) > abs(max_point[1]): + max_point = (arg, current) + if abs(current) > limit: + break + return max_point def total_sign_jump(self): # Total signature jump is the sum of all jumps. return sum([j[1] for j in sorted(self.jumps_counter.items())]) - @staticmethod - def plot_many(*sf_list, save_path=None, title='',): + def plot(self, *args, **kargs): + SignaturePloter.plot(self, *args, **kargs) + + +class SignaturePloter: + + @classmethod + def plot_many(cls, *sf_list, save_path=None, title='',): + axes_num = len(sf_list) if axes_num > 36: sf_list = sf_list[36] axes_num = 36 + msg = "To many functions for the plot were given. " + msg += "Only 36 can be plotted " + warnings.warn(msg) + # print war, set val in conf rows = ceil(sqrt(axes_num)) cols = ceil(axes_num/rows) @@ -146,55 +165,21 @@ class SignatureFunction(): ax=axes_matrix[row][col], title=sf.plot_title) + fig.suptitle(title) plt.tight_layout() - save_path = save_path or os.path.join(os.getcwd(),"tmp.png") - save_path = Path(save_path).with_suffix('.png') - plt.savefig(save_path) - plt.close() - image = Image.open(save_path) - image.show() + cls.show_and_save(save_path) - return + @classmethod + def plot_sum_of_two(cls, sf1, sf2, save_path=None, title=''): + sf = sf1 + sf2 + fig, axes_matrix = plt.subplots(2, 2, sharey=True, figsize=(10,5)) sf1.plot(subplot=True, - ax=axes_matrix[1][0], - color='red', - linestyle='dotted') - - sf2.plot(subplot=True, - ax=axes_matrix[0][0], - color='black') - - sf3.plot(subplot=True, - ax=axes_matrix[1][1], - alpha=0.3) - - fig.suptitle(title) - - plt.tight_layout() - save_path = save_path or os.path.join(os.getcwd(),"tmp.png") - save_path = Path(save_path).with_suffix('.png') - - plt.savefig(save_path) - plt.close() - image = Image.open(save_path) - image.show() - - def plot_sum_with_other(self, other, - save_path=None, title=''): - tp = self - up = other - sf = tp + up - - fig, axes_matrix = plt.subplots(2, 2, sharey=True, - figsize=(10,5)) - - tp.plot(subplot=True, ax=axes_matrix[0][1]) - up.plot(subplot=True, + sf2.plot(subplot=True, ax=axes_matrix[1][0], color='red', linestyle='dotted') @@ -203,11 +188,11 @@ class SignatureFunction(): ax=axes_matrix[0][0], color='black') - tp.plot(subplot=True, + sf1.plot(subplot=True, ax=axes_matrix[1][1], alpha=0.3) - up.plot(subplot=True, + sf2.plot(subplot=True, ax=axes_matrix[1][1], color='red', alpha=0.3, linestyle='dotted') @@ -218,22 +203,12 @@ class SignatureFunction(): alpha=0.7,) fig.suptitle(title) - plt.tight_layout() - save_path = save_path or os.path.join(os.getcwd(),"tmp.png") - save_path = Path(save_path) - save_path = save_path.with_suffix('.png') + cls.show_and_save(save_path) - - # print(save_as) - - plt.savefig(save_path) - plt.close() - image = Image.open(save_path) - image.show() - - def plot(self, subplot=False, ax=None, save_as='sf', + @classmethod + def plot(cls, sf, subplot=False, ax=None, save_path=None, title="", alpha=1, color='blue', @@ -243,8 +218,8 @@ class SignatureFunction(): if ax is None: fig, ax = plt.subplots(1, 1) - keys = sorted(self.jumps_counter.keys()) - y = [self(k) + self.jumps_counter[k] for k in keys] + keys = sorted(sf.jumps_counter.keys()) + y = [sf(k) + sf.jumps_counter[k] for k in keys] xmax = keys[1:] xmin = keys[:-1] @@ -255,20 +230,37 @@ class SignatureFunction(): if subplot: return ax - save_as += ".png" - plt.savefig(save_as) - plt.close() - image = Image.open(save_as) - image.show() + cls.show_and_save(save_path) - def step_function_data(self): + @staticmethod + def show_and_save(save_path): + + if save_path is not None: + save_path = Path(save_path) + save_path = save_path.with_suffix('.png') + plt.savefig(save_path) + + if ipython_info == JUPYTER: + plt.show() + + elif True: # save_path is None: + plt.savefig('tmp.png') + plt.close() + image = Image.open('tmp.png') + image.show() + # msg = "For interactive shell set save_path." + # warnings.warn(msg) + + @staticmethod + def step_function_data(sf): # Transform the signature jump data to a format understandable # by the plot function. - result = [(k, self.sf(k) + self.jumps_counter[k]) - for k in sorted(self.jumps_counter.keys())] + result = [(k, sf.sf(k) + sf.jumps_counter[k]) + for k in sorted(sf.jumps_counter.keys())] return result - def tikz_plot(self, save_as): + @staticmethod + def tikz_plot(sf, save_as): plt_sin = plot(sin(x), (x, 0, 2*pi)) # plt_sin.show() plt_sin.save("MyPic.pdf")