plot matrices

This commit is contained in:
Maria Marchwicka 2020-11-05 15:10:12 +01:00
parent bb11f93196
commit a71888558e
3 changed files with 677 additions and 613 deletions

View File

@ -11,389 +11,47 @@ import inspect
# 9.11 (9.8) # 9.11 (9.8)
# 9.15 (9.9) # 9.15 (9.9)
PLOTS_DIR = "plots"
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)
class CableSummand(): class CableSummand():
pass
def __init__(self, knot_as_k_values):
self.knot_as_k_values = knot_as_k_values
self._knot_description = self.get_summand_descrption(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._signature_as_function_of_theta = None self._signature_as_function_of_theta = None
@property @staticmethod
def signature_as_function_of_theta(self): def get_summand_descrption(knot_as_k_values):
if self._signature_as_function_of_theta is None: description = ""
self._signature_as_function_of_theta = \ if knot_as_k_values[0] < 0:
self.get_signature_as_function_of_theta() description += "-"
return self._signature_as_function_of_theta description += "T("
for k in knot_as_k_values:
# knot encoding description += "2, " + str(2 * abs(k) + 1) + "; "
@property return description[:-2] + ")"
def knot_formula(self):
return self._knot_formula
@property @property
def knot_description(self): def knot_description(self):
return self._knot_description return self._knot_description
# knot encoding
@property @property
def knot_sum(self): def signature_as_function_of_theta(self):
return self._knot_sum if self._signature_as_function_of_theta is None:
@knot_sum.setter self._signature_as_function_of_theta = \
def knot_sum(self, knot_sum): self.get_summand_signature_as_theta_function()
self._knot_sum = knot_sum return self._signature_as_function_of_theta
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 __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: @classmethod
msg = "k_vectors are different. k-vector preserving addition is " +\ def get_blanchfield_for_pattern(cls, k_n, theta=0):
"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):
msg = "Theorem on which this function is based, assumes " +\ msg = "Theorem on which this function is based, assumes " +\
"theta < k, where q = 2*k + 1 for pattern knot T(p, q)." "theta < k, where q = 2*k + 1 for pattern knot T(p, q)."
if theta == 0: 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() return sf.square_root() + sf.minus_square_root()
k = abs(k_n) k = abs(k_n)
@ -449,6 +107,25 @@ class CableSum():
return SignatureFunction(values=results) 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 @staticmethod
def get_untwisted_signature_function(j): def get_untwisted_signature_function(j):
# return the signature function of the T_{2, 2k+1} torus knot # return the signature function of the T_{2, 2k+1} torus knot
@ -461,6 +138,161 @@ class CableSum():
return SignatureFunction(counter=counter) 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 @staticmethod
def get_knot_descrption(knot_sum): def get_knot_descrption(knot_sum):
description = "" description = ""
@ -473,22 +305,6 @@ class CableSum():
description = description[:-2] + ") # " description = description[:-2] + ") # "
return description[:-3] 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): def get_signature_as_function_of_theta(self, **key_args):
if 'verbose' in key_args: if 'verbose' in key_args:
verbose_default = key_args['verbose'] verbose_default = key_args['verbose']
@ -507,8 +323,8 @@ class CableSum():
twisted_part = SignatureFunction() twisted_part = SignatureFunction()
# for each cable knot (summand) in cable sum apply theta # for each cable knot (summand) in cable sum apply theta
for i, knot in enumerate(self.knot_sum): for i, knot in enumerate(self.knot_summands):
ssf = self.get_summand_signature_as_theta_function(*knot) ssf = knot.signature_as_function_of_theta
tp, up = ssf(thetas[i]) tp, up = ssf(thetas[i])
twisted_part += tp twisted_part += tp
untwisted_part += up untwisted_part += up
@ -522,46 +338,9 @@ class CableSum():
return sf return sf
signature_as_function_of_theta.__doc__ =\ signature_as_function_of_theta.__doc__ =\
signature_as_function_of_theta_docstring signature_as_function_of_theta_docstring
return signature_as_function_of_theta 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): def is_metabolizer(self, theta):
# Check if square alternating difference # Check if square alternating difference
# divided by last q value is integer. # divided by last q value is integer.
@ -603,7 +382,7 @@ class CableSum():
return True return True
def is_signature_big_for_all_metabolizers(self): 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: if num_of_summands % 4:
f_name = self.is_signature_big_for_all_metabolizers.__name__ f_name = self.is_signature_big_for_all_metabolizers.__name__
msg = "Function {}".format(f_name) + " is implemented only for " +\ msg = "Function {}".format(f_name) + " is implemented only for " +\
@ -622,6 +401,109 @@ class CableSum():
return True 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): def mod_one(n):
return n - floor(n) return n - floor(n)
@ -690,18 +572,6 @@ CableSum.get_signature_as_function_of_theta.__doc__ = \
6/7: 0 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 = \ get_summand_signture_function_docsting = \
""" """
This function returns SignatureFunction for previously defined single 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. Accept len(arg) arguments: for each cable one theta parameter.
If call with no arguments, all theta parameters are set to be 0. 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__ = \ # CableSummand.get_summand_signature_as_theta_function.__doc__ = \
""" # """
Argument: # Argument:
a number # n integers that encode a single cable, i.e.
Return: # values of q_i for T(2,q_0; 2,q_1; ... 2, q_n)
the fractional part of the argument # Return:
Examples: # a function that returns SignatureFunction for this single cable
sage: mod_one(9 + 3/4) # and a theta given as an argument
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
"""

View File

@ -12,7 +12,7 @@ import itertools as it
import re import re
import numpy as np import numpy as np
attach("signature.sage")
attach("cable_signature.sage") attach("cable_signature.sage")
@ -55,16 +55,20 @@ def main(arg=None):
except (IndexError, TypeError): except (IndexError, TypeError):
limit = None limit = None
global cable # , cab_2, cab_1 # global cable_template , cable_template_2, cable_template_1
# self.knot_formula = "[[k[0], k[1], k[3]], " + \
# "[-k[1], -k[3]], " + \
# "[k[2], k[3]], " + \
# "[-k[0], -k[2], -k[3]]]"
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 # knot_formula = config.knot_formula
# q_vector = (3, 5, 7, 13) # q_vector = (3, 5, 7, 13)
# q_vector = (3, 5, 7, 11) # q_vector = (3, 5, 7, 11)
return
formula_1 = "[[k[0], k[5], k[3]], " + \ formula_1 = "[[k[0], k[5], k[3]], " + \
"[-k[1], -k[3]], " + \ "[-k[1], -k[3]], " + \
@ -75,35 +79,37 @@ def main(arg=None):
"[k[6], k[7]], " + \ "[k[6], k[7]], " + \
"[-k[4], -k[6], -k[7]]]" "[-k[4], -k[6], -k[7]]]"
q_vector = (5, 13, 19, 41,\ q_vector = (5, 13, 19, 41,\
5, 17, 23, 43) 7, 17, 23, 43)
q_vector = (3, 7, 13, 19,\ q_vector_small = (3, 7, 13, 19,\
5, 11, 17, 23) 5, 11, 17, 23)
cab_1 = CableSum(knot_formula=formula_1, q_vector=q_vector) cable_template_1 = CableTemplate(knot_formula=formula_1)
cab_2 = CableSum(knot_formula=formula_2, q_vector=q_vector) cable_template_2 = CableTemplate(knot_formula=formula_2)
cable = cab_1 + cab_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 = cable(4,4,4,4,0,0,0,0)
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)
writer = SignatureWriter(sf) writer = SignatureWriter(sf)
writer.plot(title="hoho") 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:]) cable_template.cable.is_signature_big_for_all_metabolizers()
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.is_signature_big_for_all_metabolizers() 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")

View File

@ -1,125 +1,328 @@
#!/usr/bin/env python #!/usr/bin/python
import collections 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): def mod_one(n):
"""This function returns the fractional part of some number.""" return n - floor(n)
if n >= 1:
return mod_one(n - 1)
if n < 0:
return mod_one(n + 1)
else:
return n
SignatureFunction.__doc__ = \
class av_signature_function(object): """
'''
This simple class encodes twisted and untwisted signature functions This simple class encodes twisted and untwisted signature functions
of knots. Since the signature function is entirely encoded by its signature of knots. Since the signature function is entirely encoded by its signature
jump, the class stores only information about signature jumps 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, The dictionary stores data of the signature jump as a key/values pair,
where the key is the argument at which the functions jumps where the key is the argument at which the functions jumps
and value encodes the value of the jump. and value encodes the value of the jump. Remember that we treat
Remember that we treat signature functions as defined on the interval [0,1). 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
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): mod_one.__doc__ = \
# Total signature jump is the sum of all jumps. """
return sum([j[1] for j in self.to_list()]) Argument:
a number
def to_list(self): Return:
# Return signature jumps formated as a list the fractional part of the argument
return sorted(self.data.items(), key = lambda x: x[0]) Examples:
sage: mod_one(9 + 3/4)
def step_function_data(self): 3/4
# Transform the signature jump data to a format understandable sage: mod_one(-9 + 3/4)
# by the plot function. 3/4
l = self.to_list() sage: mod_one(-3/4)
vals = ([(d[0], sum(2 * j[1] for j in l[:l.index(d)+1])) for d in l] + 1/4
[(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)