From a71888558e81e681c5ea304574b79fa5d98db273 Mon Sep 17 00:00:00 2001 From: Maria Marchwicka Date: Thu, 5 Nov 2020 15:10:12 +0100 Subject: [PATCH] plot matrices --- cable_signature.sage | 795 ++++++++++++++++++------------------------- main.sage | 62 ++-- signature.sage | 433 ++++++++++++++++------- 3 files changed, 677 insertions(+), 613 deletions(-) diff --git a/cable_signature.sage b/cable_signature.sage index 410f456..05d9705 100644 --- a/cable_signature.sage +++ b/cable_signature.sage @@ -11,389 +11,47 @@ import inspect # 9.11 (9.8) # 9.15 (9.9) - - -class SignatureFunction(): - - def __init__(self, values=None, counter=None): - - # counter of signature jumps - if counter is None: - counter = Counter() - if values is None: - values = [] - for k, v in values: - counter[k] += v - - counter = Counter({k : v for k, v in counter.items() if v != 0}) - if any(k >= 1 for k in counter.keys()): - msg = "Signature function is defined on the interval [0, 1)." - raise ValueError(msg) - - counter[0] += 0 - counter[1] += 0 - self.jumps_counter = counter - - def __rshift__(self, shift): - # A shift of the signature functions corresponds to the rotation. - counter = Counter({mod_one(k + shift) : v \ - for k, v in self.jumps_counter.items()}) - return SignatureFunction(counter=counter) - - def __lshift__(self, shift): - return self.__rshift__(-shift) - - def __neg__(self): - counter = Counter() - counter.subtract(self.jumps_counter) - return SignatureFunction(counter=counter) - - def __add__(self, other): - counter = copy(self.jumps_counter) - counter.update(other.jumps_counter) - return SignatureFunction(counter=counter) - - def __sub__(self, other): - counter = copy(self.jumps_counter) - counter.subtract(other.jumps_counter) - return SignatureFunction(counter=counter) - - def __eq__(self, other): - return self.jumps_counter == other.jumps_counter - - def __str__(self): - result = ''.join([str(jump_arg) + ": " + str(jump) + "\n" - for jump_arg, jump in sorted(self.jumps_counter.items())]) - return result - - def __repr__(self): - result = ''.join([str(jump_arg) + ": " + str(jump) + ", " - for jump_arg, jump in sorted(self.jumps_counter.items())]) - return result[:-2] + "." - - def __call__(self, arg): - # return the value of the signature function at the point arg, i.e. - # sum of all signature jumps that occur before arg - items = self.jumps_counter.items() - result = [jump for jump_arg, jump in items if jump_arg < mod_one(arg)] - return 2 * sum(result) + self.jumps_counter[arg] - - def is_zero_everywhere(self): - return not any(self.jumps_counter.values()) - - def double_cover(self): - # to read values for t^2 - items = self.jumps_counter.items() - counter = Counter({(1 + k) / 2 : v for k, v in items}) - counter.update(Counter({k / 2 : v for k, v in items})) - return SignatureFunction(counter=counter) - - def square_root(self): - # to read values for t^(1/2) - counter = Counter() - for jump_arg, jump in self.jumps_counter.items(): - if jump_arg < 1/2: - counter[2 * jump_arg] = jump - return SignatureFunction(counter=counter) - - def minus_square_root(self): - # to read values for t^(1/2) - items = self.jumps_counter.items() - counter = Counter({mod_one(2 * k) : v for k, v in items if k >= 1/2}) - return SignatureFunction(counter=counter) - - def extremum(self, limit=None): - max = 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 - - 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())]) - -class SignatureWriter(): - - def __init__(self, signature_function): - self.sf = signature_function - - def plot(self, title=None, subplot=False): - - keys = sorted(self.sf.jumps_counter.keys()) - y = [self.sf(k) + self.sf.jumps_counter[k] for k in keys] - xmax = [k for k in keys if k != 0] - xmin = [k for k in keys if k != 1] - fig, ax = plt.subplots(1, 1) - ax.set(ylabel='signature function') - if title is not None: - ax.set(title=title) - ax.hlines(y, xmin, xmax, color='blue') - plt.savefig('sf.png') - plt.close() - - - from PIL import Image - image = Image.open('sf.png') - image.show() - - def step_function_data(self): - # Transform the signature jump data to a format understandable - # by the plot function. - result = [(k, self.sf(k) + self.sf.jumps_counter[k]) - for k in sorted(self.sf.jumps_counter.keys())] - return result - - def tikz_plot(self, file_name): - plt_sin = plot(sin(x), (x, 0, 2*pi)) - # plt_sin.show() - plt_sin.save("MyPic.pdf") - - return - # Draw the graph of the signature and transform it into TiKz. - # header of the LaTeX file - head = inspect.cleandoc( - r""" - \documentclass{standalone} - \usepackage{tikz} - \usetikzlibrary{calc} - \begin{document} - \begin{tikzpicture} - """) - - body = \ - r""" - %A piecewise linear function is drawn over the interval. - \draw (5,0) -- (6,-4); - %The axes are drawn. - \draw[latex-latex] ($(0,{-4*(2/5)}) +(0pt,-12.5pt)$) -- - ($(0,{4*(2/5)}) +(0pt,12.5pt)$) node[above right]{$y$}; - \draw[latex-latex] ($({-4*(2/5)},0) +(-12.5pt,0pt)$) -- - ($({12*(2/5)},0) +(12.5pt,0pt)$) node[below right]{$x$}; - """ - tail = \ - r""" - \end{tikzpicture} - \end{document} - """ - tikzpicture = re.sub(r' +', ' ', ''.join([head, body, tail])) - tikzpicture = re.sub(r'\n ', '\n', tikzpicture) - - with open("tmp.tex", "w") as f: - f.write(tikzpicture) - - data = self.step_function_data() - with open(file_name, "w") as f: - head = \ - r""" - \documentclass[tikz]{{standalone}} - %\usepackage{{tikz}} - \usetikzlibrary{{datavisualization}} - \usetikzlibrary{{datavisualization.formats.functions}} - %\usetikzlibrary{{calc}} - \begin{{document}} - \begin{{tikzpicture}} - \datavisualization[scientific axes, visualize as smooth line, - x axis={{ticks={{none,major={{at={{, {arg0} " as \\( {val0} \\ - %] - """.format(arg0=str(N(data[0][0] ,digits=4)), val0=str(data[0][0])) - f.write(head) - - - # f.write(", " + str(N(data[0][0],digits=4)) + " as \\(" + \ - # str(data[0][0]) + "\\)") - for jump_arg, jump in data[1:3]: - f.write(", " + str(N(jump_arg,digits=4)) + - " as \\(" + str(jump_arg) + "\\)") - f.write("}}}}\n") - f.write(" ]\n") - f.write("data [format=function]{\n") - f.write("var x : interval [0:1];\n") - f.write("func y = \\value x;\n") - f.write("};\n") - # close LaTeX enviroments - tail = \ - r""" - %}; - \end{tikzpicture} - \end{document} - """ - f.write(tail) - +PLOTS_DIR = "plots" class CableSummand(): - pass + def __init__(self, knot_as_k_values): + self.knot_as_k_values = knot_as_k_values - - - -class CableSum(): - def __init__(self, knot_formula, k_vector=None, q_vector=None): - - self._knot_formula = knot_formula - # q_i = 2 * k_i + 1 - if k_vector is not None: - self.k_vector = k_vector - elif q_vector is not None: - self.q_vector = q_vector - else: - self.q_vector = self.get_q_vector_alg_slice(self.knot_formula) - + self._knot_description = self.get_summand_descrption(knot_as_k_values) self._signature_as_function_of_theta = None - @property - def signature_as_function_of_theta(self): - if self._signature_as_function_of_theta is None: - self._signature_as_function_of_theta = \ - self.get_signature_as_function_of_theta() - return self._signature_as_function_of_theta - - # knot encoding - @property - def knot_formula(self): - return self._knot_formula + @staticmethod + def get_summand_descrption(knot_as_k_values): + description = "" + if knot_as_k_values[0] < 0: + description += "-" + description += "T(" + for k in knot_as_k_values: + description += "2, " + str(2 * abs(k) + 1) + "; " + return description[:-2] + ")" @property def knot_description(self): return self._knot_description - # knot encoding @property - def knot_sum(self): - return self._knot_sum - @knot_sum.setter - def knot_sum(self, knot_sum): - self._knot_sum = 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 q-vector. This implementation assumes that" + \ - " all last q values are prime numbers.\n" + \ - str(self._patt_q_list) - raise ValueError(msg) - - self.q_order = LCM_list(self._patt_q_list) - - @property - def patt_k_list(self): - return self._patt_k_list - - @property - def patt_q_list(self): - return self._patt_q_list - - # q_order is LCM of all q values for pattern knots - @property - def q_order(self): - return self._q_order - @q_order.setter - def q_order(self, val): - self._q_order = val - - @property - def k_vector(self): - return self._k_vector - @k_vector.setter - def k_vector(self, k): - self._k_vector = k - if self.extract_max(self.knot_formula) > len(k) - 1: - msg = "The vector for knot_formula evaluation is to short!" - msg += "\nk_vector " + str(k) + " \nknot_formula " \ - + str(self.knot_formula) - raise IndexError(msg) - self.knot_sum = eval(self.knot_formula) - self._q_vector = [2 * k_val + 1 for k_val in k] - - @property - def q_vector(self): - return self._q_vector - @q_vector.setter - def q_vector(self, new_q_vector): - self.k_vector = [(q - 1)/2 for q in new_q_vector] + def signature_as_function_of_theta(self): + if self._signature_as_function_of_theta is None: + self._signature_as_function_of_theta = \ + self.get_summand_signature_as_theta_function() + return self._signature_as_function_of_theta - def __add__(self, other): - s_formula = self.knot_formula - o_formula = other.knot_formula - k_vector = self.k_vector - if self.k_vector != other.k_vector: - msg = "k_vectors are different. k-vector preserving addition is " +\ - "impossible." - warnings.warn(msg) - shift = len(self.k_vector) - o_formula = re.sub(r'\d+', lambda x: str(int(x.group()) + shift), - o_formula) - k_vector += other.k_vector - - knot_formula = s_formula[:-1] + ",\n" + o_formula[1:] - cable = CableSum(knot_formula, k_vector=k_vector) - s_sig = self.signature_as_function_of_theta - o_sig = other.signature_as_function_of_theta - shift = len(self.knot_sum) - - def signature_as_function_of_theta(*thetas, **kwargs): - thetas = cable.parse_thetas(*thetas) - result = s_sig(*thetas[shift:]) + o_sig(*thetas[0:shift]) - return result - - cable._signature_as_function_of_theta = signature_as_function_of_theta - return cable - - def parse_thetas(self, *thetas): - summands_num = len(self.knot_sum) - if not thetas: - return summands_num * (0,) - if len(thetas) == 1 and summands_num > 1: - if isinstance(thetas[0], Iterable): - if len(thetas[0]) >= summands_num: - return tuple(thetas[0]) - elif not thetas[0]: - return summands_num * (0,) - elif thetas[0] == 0: - return summands_num * (0,) - else: - msg = "This function takes at least " + str(summands_num) + \ - " arguments or no argument at all (" + str(len(thetas)) \ - + " given)." - raise TypeError(msg) - return tuple(thetas) - - @staticmethod - def get_q_vector_alg_slice(knot_formula): - lowest_number = 2 - q_vector = [0] * (CableSum.extract_max(knot_formula) + 1) - P = Primes() - for layer in CableSum.get_layers_from_formula(knot_formula)[::-1]: - for el in layer: - q_vector[el] = P.next(lowest_number) - lowest_number = q_vector[el] - lowest_number *= 4 - return q_vector - - @staticmethod - def extract_max(string): - numbers = re.findall(r'\d+', string) - numbers = map(int, numbers) - return max(numbers) - - @staticmethod - def get_blanchfield_for_pattern(k_n, theta=0): + @classmethod + def get_blanchfield_for_pattern(cls, k_n, theta=0): msg = "Theorem on which this function is based, assumes " +\ "theta < k, where q = 2*k + 1 for pattern knot T(p, q)." if theta == 0: - sf = CableSum.get_untwisted_signature_function(k_n) + sf = cls.get_untwisted_signature_function(k_n) return sf.square_root() + sf.minus_square_root() k = abs(k_n) @@ -449,6 +107,25 @@ class CableSum(): return SignatureFunction(values=results) + @classmethod + def get_untwisted_part(cls, *knot_as_k_values, theta=0): + patt_k = knot_as_k_values[-1] + ksi = 1/(2 * abs(patt_k) + 1) + + untwisted_part = 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]): + sf = cls.get_untwisted_signature_function(k) + shift = theta * ksi * 2^layer_num + right_shift = sf >> shift + left__shift = sf << shift + for _ in range(layer_num): + right_shift = right_shift.double_cover() + left__shift = left__shift.double_cover() + untwisted_part += right_shift + left__shift + return untwisted_part + @staticmethod def get_untwisted_signature_function(j): # return the signature function of the T_{2, 2k+1} torus knot @@ -461,6 +138,161 @@ class CableSum(): return SignatureFunction(counter=counter) + def get_summand_signature_as_theta_function(self): + knot_as_k_values = self.knot_as_k_values + def get_summand_signture_function(theta, plot=False): + + patt_k = knot_as_k_values[-1] + + # theta should not be larger than k for the pattern. + theta %= (2 * abs(patt_k) + 1) + theta = min(theta, 2 * abs(patt_k) + 1 - theta) + + twisted_part = self.get_blanchfield_for_pattern(patt_k, theta) + untwisted_part = self.get_untwisted_part(*knot_as_k_values, + theta=theta) + if plot: + + twisted_part.plot() + untwisted_part.plot() + return twisted_part, untwisted_part + get_summand_signture_function.__doc__ = \ + get_summand_signture_function_docsting + + return get_summand_signture_function + + def get_file_name_for_summand_plot(self, theta=0): + if self.knot_as_k_values[0] < 0: + name = "inv_T_" + else: + name = "T_" + for k in self.knot_as_k_values: + name += str(abs(k)) + "_" + name += "_theta_" + str(theta) + return name + + + def plot_summand_for_theta(self, theta, save_path=None): + tp, up = self.signature_as_function_of_theta(theta) + title = self.knot_description + ", theta = " + str(theta) + 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) + tp.plot_sum_with_other(up, title=title, save_path=save_path) + + + def plot_summand(self): + sf_theta = self.signature_as_function_of_theta + range_limit = min(self.knot_as_k_values[-1] + 1, 3) + for theta in range(range_limit): + self.plot_summand_for_theta(theta) + +class CableSum(): + def __init__(self, knot_sum): + self.knot_sum_as_k_valus = knot_sum + self.knot_summands = [CableSummand(k) for k in knot_sum] + self.signature_as_function_of_theta = \ + self.get_signature_as_function_of_theta() + + def __call__(self, *thetas): + return self.signature_as_function_of_theta(*thetas) + + def get_dir_name_for_plots(self, dir=None): + dir_name = '' + for knot in self.knot_summands: + if knot.knot_as_k_values[0] < 0: + dir_name += "inv_" + dir_name += "T_" + for k in knot.knot_as_k_values: + k = 2 * abs (k) + 1 + dir_name += str(k) + "_" + dir_name = dir_name[:-1] + print(dir_name) + dir_path = os.getcwd() + if dir is not None: + dir_path = os.path.join(dir_path, dir) + dir_path = os.path.join(dir_path, dir_name) + + if not os.path.isdir(dir_path): + os.mkdir(dir_path) + return dir_name + + def plot_sum_for_theta_vector(self, thetas, save_to_dir=False): + if save_to_dir: + if not os.path.isdir(PLOTS_DIR): + os.mkdir(PLOTS_DIR) + dir_name = self.get_dir_name_for_plots(dir=PLOTS_DIR) + save_path = os.path.join(os.getcwd(), PLOTS_DIR) + save_path = os.path.join(save_path, dir_name) + else: + save_path = None + for i, knot in enumerate(self.knot_summands): + knot.plot_summand_for_theta(thetas[i], save_path=save_path) + + return dir_name + + def plot_all_summands(self): + for knot in self.knot_summands: + knot.plot_summand() + + + @property + def knot_description(self): + return self._knot_description + + @property + def patt_k_list(self): + return self._patt_k_list + + @property + def patt_q_list(self): + return self._patt_q_list + + # q_order is LCM of all q values for pattern knots + @property + def q_order(self): + return self._q_order + @q_order.setter + def q_order(self, val): + self._q_order = val + + @property + def knot_sum_as_k_valus(self): + return self._knot_sum_as_k_valus + @knot_sum_as_k_valus.setter + def knot_sum_as_k_valus(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 q-vector. This implementation assumes that" + \ + " all last q values are prime numbers.\n" + \ + str(self._patt_q_list) + raise ValueError(msg) + self.q_order = LCM_list(self._patt_q_list) + + + def parse_thetas(self, *thetas): + summands_num = len(self.knot_sum_as_k_valus) + if not thetas: + thetas = summands_num * (0,) + elif len(thetas) == 1 and summands_num > 1: + if isinstance(thetas[0], Iterable): + if len(thetas[0]) >= summands_num: + thetas = thetas[0] + elif not thetas[0]: + thetas = summands_num * (0,) + elif thetas[0] == 0: + thetas = summands_num * (0,) + else: + msg = "This function takes at least " + str(summands_num) + \ + " arguments or no argument at all (" + str(len(thetas)) \ + + " given)." + raise TypeError(msg) + return tuple(thetas) + + @staticmethod def get_knot_descrption(knot_sum): description = "" @@ -473,22 +305,6 @@ class CableSum(): description = description[:-2] + ") # " return description[:-3] - - @staticmethod - def get_layers_from_formula(knot_formula): - k_indices = re.sub(r'[k-]', '', knot_formula) - k_indices = re.sub(r'\[\d+\]', lambda x: x.group()[1:-1], k_indices) - k_indices = eval(k_indices) - number_of_layers = max(len(lst) for lst in k_indices) - layers = [] - for i in range(1, number_of_layers + 1): - layer = set() - for lst in k_indices: - if len(lst) >= i: - layer.add(lst[-i]) - layers.append(layer) - return layers - def get_signature_as_function_of_theta(self, **key_args): if 'verbose' in key_args: verbose_default = key_args['verbose'] @@ -507,8 +323,8 @@ class CableSum(): twisted_part = SignatureFunction() # for each cable knot (summand) in cable sum apply theta - for i, knot in enumerate(self.knot_sum): - ssf = self.get_summand_signature_as_theta_function(*knot) + for i, knot in enumerate(self.knot_summands): + ssf = knot.signature_as_function_of_theta tp, up = ssf(thetas[i]) twisted_part += tp untwisted_part += up @@ -522,46 +338,9 @@ class CableSum(): return sf signature_as_function_of_theta.__doc__ =\ - signature_as_function_of_theta_docstring + signature_as_function_of_theta_docstring return signature_as_function_of_theta - def get_untwisted_part(self, *knot_as_k_values, theta=0): - patt_k = knot_as_k_values[-1] - ksi = 1/(2 * abs(patt_k) + 1) - - untwisted_part = 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]): - sf = CableSum.get_untwisted_signature_function(k) - shift = theta * ksi * 2^layer_num - right_shift = sf >> shift - left__shift = sf << shift - for _ in range(layer_num): - right_shift = right_shift.double_cover() - left__shift = left__shift.double_cover() - untwisted_part += right_shift + left__shift - return untwisted_part - - def get_summand_signature_as_theta_function(self, *knot_as_k_values): - - def get_summand_signture_function(theta): - - patt_k = knot_as_k_values[-1] - - # theta should not be larger than k for the pattern. - theta %= (2 * abs(patt_k) + 1) - theta = min(theta, 2 * abs(patt_k) + 1 - theta) - - twisted_part = self.get_blanchfield_for_pattern(patt_k, theta) - untwisted_part = self.get_untwisted_part(*knot_as_k_values, - theta=theta) - return twisted_part, untwisted_part - get_summand_signture_function.__doc__ = \ - get_summand_signture_function_docsting - - return get_summand_signture_function - def is_metabolizer(self, theta): # Check if square alternating difference # divided by last q value is integer. @@ -603,7 +382,7 @@ class CableSum(): return True def is_signature_big_for_all_metabolizers(self): - num_of_summands = len(self.knot_sum) + num_of_summands = len(self.knot_sum_as_k_valus) if num_of_summands % 4: f_name = self.is_signature_big_for_all_metabolizers.__name__ msg = "Function {}".format(f_name) + " is implemented only for " +\ @@ -622,6 +401,109 @@ class CableSum(): return True +class CableTemplate(): + + def __init__(self, knot_formula, q_vector=None, k_vector=None, + generate_q_vector=True, slice_candidate=True): + self._knot_formula = knot_formula + # q_i = 2 * k_i + 1 + if k_vector is not None: + self.k_vector = k_vector + elif q_vector is not None: + self.q_vector = q_vector + elif generate_q_vector: + self.q_vector = self.get_q_vector(knot_formula, slice_candidate) + + @property + def cable(self): + if self._cable is None: + msg = "q_vector for cable instance has not been set explicit. " + \ + "The variable is assigned a default value." + warnings.warn(msg) + self.fill_q_vector() + return self._cable + + def fill_q_vector(self, q_vector=None, slice=True): + if q_vector is None: + q_vector = self.get_q_vector(self.knot_formula) + self.q_vector = q_vector + + @property + def knot_formula(self): + return self._knot_formula + + @property + def k_vector(self): + return self._k_vector + @k_vector.setter + def k_vector(self, k): + self._k_vector = k + if self.extract_max(self.knot_formula) > len(k) - 1: + msg = "The vector for knot_formula evaluation is to short!" + msg += "\nk_vector " + str(k) + " \nknot_formula " \ + + str(self.knot_formula) + raise IndexError(msg) + + self.knot_sum_as_k_valus = eval(self.knot_formula) + self._cable = CableSum(self.knot_sum_as_k_valus) + + self._q_vector = [2 * k_val + 1 for k_val in k] + + @property + def q_vector(self): + return self._q_vector + @q_vector.setter + def q_vector(self, new_q_vector): + self.k_vector = [(q - 1)/2 for q in new_q_vector] + + @staticmethod + def extract_max(string): + numbers = re.findall(r'\d+', string) + numbers = map(int, numbers) + return max(numbers) + + @classmethod + def get_q_vector(cls, knot_formula, slice=True): + lowest_number = 2 + q_vector = [0] * (cls.extract_max(knot_formula) + 1) + P = Primes() + for layer in cls.get_layers_from_formula(knot_formula)[::-1]: + for el in layer: + q_vector[el] = P.next(lowest_number) + lowest_number = q_vector[el] + lowest_number *= 4 + return q_vector + + @staticmethod + def get_layers_from_formula(knot_formula): + k_indices = re.sub(r'[k-]', '', knot_formula) + k_indices = re.sub(r'\[\d+\]', lambda x: x.group()[1:-1], k_indices) + k_indices = eval(k_indices) + number_of_layers = max(len(lst) for lst in k_indices) + layers = [] + for i in range(1, number_of_layers + 1): + layer = set() + for lst in k_indices: + if len(lst) >= i: + layer.add(lst[-i]) + layers.append(layer) + return layers + + def add_with_shift(self, other): + shift = self.extract_max(self.knot_formula) + 1 + o_formula = re.sub(r'\d+', lambda x: str(int(x.group()) + shift), + other.knot_formula) + return self + CableTemplate(o_formula) + + + def __add__(self, other): + s_formula = self.knot_formula + o_formula = other.knot_formula + knot_formula = s_formula[:-1] + ",\n" + o_formula[1:] + cable_template = CableTemplate(knot_formula) + return cable_template + + def mod_one(n): return n - floor(n) @@ -690,18 +572,6 @@ CableSum.get_signature_as_function_of_theta.__doc__ = \ 6/7: 0 """ -SignatureFunction.__doc__ = \ - """ - This simple class encodes twisted and untwisted signature functions - of knots. Since the signature function is entirely encoded by its signature - jump, the class stores only information about signature jumps - in a dictionary self.jumps_counter. - The dictionary stores data of the signature jump as a key/values pair, - where the key is the argument at which the functions jumps - and value encodes the value of the jump. Remember that we treat - signature functions as defined on the interval [0,1). - """ - get_summand_signture_function_docsting = \ """ This function returns SignatureFunction for previously defined single @@ -724,42 +594,27 @@ signature_as_function_of_theta_docstring = \ Accept len(arg) arguments: for each cable one theta parameter. If call with no arguments, all theta parameters are set to be 0. """ +# +# CableSummand.get_blanchfield_for_pattern.__doc__ = \ +# """ +# Arguments: +# k_n: a number s.t. q_n = 2 * k_n + 1, where +# 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 twisted signature function +# for a given cable and theta/character +# Based on: +# Proposition 9.8. in Twisted Blanchfield Pairing +# (https://arxiv.org/pdf/1809.08791.pdf) +# """ -mod_one.__doc__ = \ - """ - Argument: - a number - Return: - the fractional part of the argument - Examples: - sage: mod_one(9 + 3/4) - 3/4 - sage: mod_one(-9 + 3/4) - 3/4 - sage: mod_one(-3/4) - 1/4 - """ - -CableSum.get_blanchfield_for_pattern.__doc__ = \ - """ - Arguments: - k_n: a number s.t. q_n = 2 * k_n + 1, where - 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 twisted signature function - for a given cable and theta/character - Based on: - Proposition 9.8. in Twisted Blanchfield Pairing - (https://arxiv.org/pdf/1809.08791.pdf) - """ - -CableSum.get_summand_signature_as_theta_function.__doc__ = \ - """ - Argument: - 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 - and a theta given as an argument - """ +# CableSummand.get_summand_signature_as_theta_function.__doc__ = \ +# """ +# Argument: +# 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 +# and a theta given as an argument +# """ diff --git a/main.sage b/main.sage index 2cbdde0..aa67704 100644 --- a/main.sage +++ b/main.sage @@ -12,7 +12,7 @@ import itertools as it import re import numpy as np - +attach("signature.sage") attach("cable_signature.sage") @@ -55,16 +55,20 @@ def main(arg=None): except (IndexError, TypeError): limit = None - global cable # , cab_2, cab_1 - # self.knot_formula = "[[k[0], k[1], k[3]], " + \ - # "[-k[1], -k[3]], " + \ - # "[k[2], k[3]], " + \ - # "[-k[0], -k[2], -k[3]]]" + # global cable_template , cable_template_2, cable_template_1 + knot_formula = "[[k[0], k[1], k[3]], " + \ + "[-k[1], -k[3]], " + \ + "[k[2], k[3]], " + \ + "[-k[0], -k[2], -k[3]]]" + template = CableTemplate(knot_formula, q_vector=[3, 5, 7, 11]) + cab = template.cable + # cab.plot_all_summands() + cab.plot_sum_for_theta_vector([2,1,1,1], save_to_dir=True) # knot_formula = config.knot_formula # q_vector = (3, 5, 7, 13) # q_vector = (3, 5, 7, 11) - + return formula_1 = "[[k[0], k[5], k[3]], " + \ "[-k[1], -k[3]], " + \ @@ -75,35 +79,37 @@ def main(arg=None): "[k[6], k[7]], " + \ "[-k[4], -k[6], -k[7]]]" q_vector = (5, 13, 19, 41,\ - 5, 17, 23, 43) - q_vector = (3, 7, 13, 19,\ + 7, 17, 23, 43) + q_vector_small = (3, 7, 13, 19,\ 5, 11, 17, 23) - cab_1 = CableSum(knot_formula=formula_1, q_vector=q_vector) - cab_2 = CableSum(knot_formula=formula_2, q_vector=q_vector) - cable = cab_1 + cab_2 + cable_template_1 = CableTemplate(knot_formula=formula_1) + cable_template_2 = CableTemplate(knot_formula=formula_2) + cable_template = cable_template_1 + cable_template_2 + cable_with_shift = cable_template_1.add_with_shift(cable_template_2) + print(cable_with_shift.knot_formula) + cable_template.fill_q_vector() + cable = cable_template.cable - - sf = cab_1.signature_as_function_of_theta(thetas=None) - # sf.tikz_plot("hoho.tex") - - # cab_1.is_signature_big_for_all_metabolizers() - sf = cab_1.signature_as_function_of_theta() - - sf = cable.signature_as_function_of_theta() - - sf = cable.signature_as_function_of_theta(4,4,4,4,0,0,0,0) + sf = cable(4,4,4,4,0,0,0,0) writer = SignatureWriter(sf) writer.plot(title="hoho") - cable.is_signature_big_for_all_metabolizers() + sf = cable_template.cable.signature_as_function_of_theta(4,1,1,4,0,0,0,0) + writer = SignatureWriter(sf) + writer.plot(title="hoho", color='red') - q_vector = CableSum.get_q_vector_alg_slice(formula_1[:-1] + ", " + formula_2[1:]) - cab_1 = CableSum(knot_formula=formula_1, q_vector=q_vector) - cab_2 = CableSum(knot_formula=formula_2, q_vector=q_vector) - cable = cab_1 + cab_2 - cable.is_signature_big_for_all_metabolizers() + cable_template.cable.is_signature_big_for_all_metabolizers() + + + cable_template_1 = CableTemplate(knot_formula=formula_1) + cable_template_2 = CableTemplate(knot_formula=formula_2) + cable_template = cable_template_1 + cable_template_2 + cable_template.cable.is_signature_big_for_all_metabolizers() + sf = cable_template.cable.signature_as_function_of_theta(4,4,4,4,0,0,0,0) + writer = SignatureWriter(sf) + writer.plot(title="hoho") diff --git a/signature.sage b/signature.sage index f53dcd6..7e60dc1 100644 --- a/signature.sage +++ b/signature.sage @@ -1,125 +1,328 @@ -#!/usr/bin/env python -import collections +#!/usr/bin/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 +# 9.11 (9.8) +# 9.15 (9.9) + + +class SignatureFunction(): + + def __init__(self, values=None, counter=None): + + # counter of signature jumps + if counter is None: + counter = Counter() + if values is None: + values = [] + for k, v in values: + counter[k] += v + + counter = Counter({k : v for k, v in counter.items() if v != 0}) + if any(k >= 1 for k in counter.keys()): + msg = "Signature function is defined on the interval [0, 1)." + raise ValueError(msg) + + counter[0] += 0 + counter[1] += 0 + self.jumps_counter = counter + + def __rshift__(self, shift): + # A shift of the signature functions corresponds to the rotation. + counter = Counter({mod_one(k + shift) : v \ + for k, v in self.jumps_counter.items()}) + return SignatureFunction(counter=counter) + + def __lshift__(self, shift): + return self.__rshift__(-shift) + + def __neg__(self): + counter = Counter() + counter.subtract(self.jumps_counter) + return SignatureFunction(counter=counter) + + def __add__(self, other): + counter = copy(self.jumps_counter) + counter.update(other.jumps_counter) + return SignatureFunction(counter=counter) + + def __sub__(self, other): + counter = copy(self.jumps_counter) + counter.subtract(other.jumps_counter) + return SignatureFunction(counter=counter) + + def __eq__(self, other): + return self.jumps_counter == other.jumps_counter + + def __str__(self): + result = ''.join([str(jump_arg) + ": " + str(jump) + "\n" + for jump_arg, jump in sorted(self.jumps_counter.items())]) + return result + + def __repr__(self): + result = ''.join([str(jump_arg) + ": " + str(jump) + ", " + for jump_arg, jump in sorted(self.jumps_counter.items())]) + return result[:-2] + "." + + def __call__(self, arg): + # return the value of the signature function at the point arg, i.e. + # sum of all signature jumps that occur before arg + items = self.jumps_counter.items() + result = [jump for jump_arg, jump in items if jump_arg < mod_one(arg)] + return 2 * sum(result) + self.jumps_counter[arg] + + def is_zero_everywhere(self): + return not any(self.jumps_counter.values()) + + def double_cover(self): + # to read values for t^2 + items = self.jumps_counter.items() + counter = Counter({(1 + k) / 2 : v for k, v in items}) + counter.update(Counter({k / 2 : v for k, v in items})) + return SignatureFunction(counter=counter) + + def square_root(self): + # to read values for t^(1/2) + counter = Counter() + for jump_arg, jump in self.jumps_counter.items(): + if jump_arg < 1/2: + counter[2 * jump_arg] = jump + return SignatureFunction(counter=counter) + + def minus_square_root(self): + # to read values for t^(1/2) + items = self.jumps_counter.items() + counter = Counter({mod_one(2 * k) : v for k, v in items if k >= 1/2}) + return SignatureFunction(counter=counter) + + def extremum(self, limit=None): + max = 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 + + 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())]) + + + + 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(title='subplot_tp', + subplot=True, + ax=axes_matrix[0][1]) + + up.plot(title='subplot_up', + subplot=True, + ax=axes_matrix[1][0], + color='red', + linestyle='dotted') + + sf.plot(title='subplot_up', + subplot=True, + ax=axes_matrix[0][0], + color='black') + + tp.plot(title='subplot_tp', + subplot=True, + ax=axes_matrix[1][1], + alpha=0.3) + + up.plot(title='subplot_up', + subplot=True, + ax=axes_matrix[1][1], + color='red', alpha=0.3, + linestyle='dotted') + + sf.plot(title='subplot_up', + subplot=True, + ax=axes_matrix[1][1], + color='black', + alpha=0.7,) + + fig.suptitle(title) + + plt.tight_layout() + if save_path is None: + save_path = os.path.join(os.getcwd(),"tmp.png") + save_path = Path(save_path) + save_path = save_path.with_suffix('.png') + + + # print(save_as) + + plt.savefig(save_path) + plt.close() + image = Image.open(save_path) + image.show() + + + pass + + + def plot(self, subplot=False, ax=None, save_as='sf', + title="", + alpha=1, + color='blue', + linestyle='solid'): + + 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] + xmax = keys[1:] + xmin = keys[:-1] + + ax.set(ylabel='signature function') + ax.set(title=title) + ax.hlines(y, xmin, xmax, color=color, linestyle=linestyle, alpha=alpha) + + if subplot: + return ax + + save_as += ".png" + plt.savefig(save_as) + plt.close() + image = Image.open(save_as) + image.show() + + + + def step_function_data(self): + # 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())] + return result + + def tikz_plot(self, save_as): + plt_sin = plot(sin(x), (x, 0, 2*pi)) + # plt_sin.show() + plt_sin.save("MyPic.pdf") + + return + # Draw the graph of the signature and transform it into TiKz. + # header of the LaTeX file + head = inspect.cleandoc( + r""" + \documentclass{standalone} + \usepackage{tikz} + \usetikzlibrary{calc} + \begin{document} + \begin{tikzpicture} + """) + + body = \ + r""" + %A piecewise linear function is drawn over the interval. + \draw (5,0) -- (6,-4); + %The axes are drawn. + \draw[latex-latex] ($(0,{-4*(2/5)}) +(0pt,-12.5pt)$) -- + ($(0,{4*(2/5)}) +(0pt,12.5pt)$) node[above right]{$y$}; + \draw[latex-latex] ($({-4*(2/5)},0) +(-12.5pt,0pt)$) -- + ($({12*(2/5)},0) +(12.5pt,0pt)$) node[below right]{$x$}; + """ + tail = \ + r""" + \end{tikzpicture} + \end{document} + """ + tikzpicture = re.sub(r' +', ' ', ''.join([head, body, tail])) + tikzpicture = re.sub(r'\n ', '\n', tikzpicture) + + with open("tmp.tex", "w") as f: + f.write(tikzpicture) + + data = self.step_function_data() + with open(save_as, "w") as f: + head = \ + r""" + \documentclass[tikz]{{standalone}} + %\usepackage{{tikz}} + \usetikzlibrary{{datavisualization}} + \usetikzlibrary{{datavisualization.formats.functions}} + %\usetikzlibrary{{calc}} + \begin{{document}} + \begin{{tikzpicture}} + \datavisualization[scientific axes, visualize as smooth line, + x axis={{ticks={{none,major={{at={{, {arg0} " as \\( {val0} \\ + %] + """.format(arg0=str(N(data[0][0] ,digits=4)), val0=str(data[0][0])) + f.write(head) + + + # f.write(", " + str(N(data[0][0],digits=4)) + " as \\(" + \ + # str(data[0][0]) + "\\)") + for jump_arg, jump in data[1:3]: + f.write(", " + str(N(jump_arg,digits=4)) + + " as \\(" + str(jump_arg) + "\\)") + f.write("}}}}\n") + f.write(" ]\n") + f.write("data [format=function]{\n") + f.write("var x : interval [0:1];\n") + f.write("func y = \\value x;\n") + f.write("};\n") + # close LaTeX enviroments + tail = \ + r""" + %}; + \end{tikzpicture} + \end{document} + """ + f.write(tail) def mod_one(n): - """This function returns the fractional part of some number.""" - if n >= 1: - return mod_one(n - 1) - if n < 0: - return mod_one(n + 1) - else: - return n + return n - floor(n) - -class av_signature_function(object): - ''' +SignatureFunction.__doc__ = \ + """ This simple class encodes twisted and untwisted signature functions of knots. Since the signature function is entirely encoded by its signature jump, the class stores only information about signature jumps - in a dictionary self.data. + in a dictionary self.jumps_counter. The dictionary stores data of the signature jump as a key/values pair, where the key is the argument at which the functions jumps - and value encodes the value of the jump. - Remember that we treat signature functions as defined on the interval [0,1). - ''' - def __init__(self,values=[]): - # We will store data of signature jumps here. - self.data = collections.defaultdict(int) - # values contain initial data of singature jumps - for jump_arg, jump in values: - assert 0 <= jump_arg and jump_arg < 1 - self.data[jump_arg] += jump + and value encodes the value of the jump. Remember that we treat + signature functions as defined on the interval [0,1). + """ - def value(self, arg): - # Compute the value of the signature function at the point arg. - # This requires summing all signature jumps that occur before arg. - assert 0 <= arg and arg < 1 - val = 0 - for jump_arg, jump in self.data.items(): - if jump_arg < arg: - val += 2 * jump - elif jump_arg == arg: - val += jump - return val - def total_sign_jump(self): - # Total signature jump is the sum of all jumps. - return sum([j[1] for j in self.to_list()]) - - def to_list(self): - # Return signature jumps formated as a list - return sorted(self.data.items(), key = lambda x: x[0]) - - def step_function_data(self): - # Transform the signature jump data to a format understandable - # by the plot function. - l = self.to_list() - vals = ([(d[0], sum(2 * j[1] for j in l[:l.index(d)+1])) for d in l] + - [(0,self.data[0]), (1,self.total_sign_jump())]) - return vals - - def plot(self): - # plot the signture function - plot_step_function(self.step_function_data()) - - def tikz_plot(self, file_name): - # Draw the graph of the signature and transform it into TiKz. - # header of the LaTeX file - output_file = open(file_name, "w") - output_file.write("\\documentclass[tikz]{standalone}\n") - output_file.write("\\usetikzlibrary{datavisualization,datavisualization.formats.functions}\n") - output_file.write("\\begin{document}\n") - output_file.write("\\begin{tikzpicture}\n") - data = sorted(self.step_function_data()) - output_file.write(" \\datavisualization[scientific axes,visualize as smooth line,\n") - output_file.write(" x axis={ticks={none,major={at={") - output_file.write(", " + str(N(data[0][0],digits=4)) + " as \\(" + str(data[0][0]) + "\\)") - for jump_arg,jump in data[1:]: - output_file.write(", " + str(N(jump_arg,digits=4)) + " as \\(" + str(jump_arg) + "\\)") - output_file.write("}}}}\n") - output_file.write(" ]\n") - output_file.write("data [format=function]{\n") - output_file.write("var x : interval [0:1];\n") - output_file.write("func y = \\value x;\n") - output_file.write("};\n") - # close LaTeX enviroments - output_file.write("\\end{tikzpicture}\n") - output_file.write("\\end{document}\n") - output_file.close() - - def __lshift__(self, shift): - # Shift of the signture functions correspond to the rotations. - return self.__rshift__(-shift) - - def __rshift__(self, shift): - new_data = list() - for jump_arg, jump in self.data.items(): - new_data.append((mod_one(jump_arg + shift),jump)) - return av_signature_function(new_data) - - def __sub__(self, other): - # we cn perform arithmetic operations on signature functions. - return self + other.__neg__() - - def __neg__(self): - for jump_arg in self.data.keys(): - self.data[jump_arg] *= (-1) - return self - - def __add__(self, other): - for jump_arg, jump in other.data.items(): - self.data[jump_arg] += jump - return self - - def __str__(self): - return '\n'.join([str(jump_arg) + ": " + str(jump) - for jump_arg, jump in self.data.items()]) - - def __repr__(self): - print self.__str__() - -def untw_signature(k): - # Return the signture function of the T_{2,2k+1} torus knot. - l = ([((2 * a + 1)/(4 * k + 2), -1) for a in range(k)] + - [((2 * a + 1)/(4 * k + 2), 1) for a in range(k + 1, 2 * k + 1)]) - return av_signature_function(l) +mod_one.__doc__ = \ + """ + Argument: + a number + Return: + the fractional part of the argument + Examples: + sage: mod_one(9 + 3/4) + 3/4 + sage: mod_one(-9 + 3/4) + 3/4 + sage: mod_one(-3/4) + 1/4 + """