plot matrices
This commit is contained in:
parent
bb11f93196
commit
a71888558e
@ -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
|
||||
# """
|
||||
|
62
main.sage
62
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")
|
||||
|
||||
|
||||
|
||||
|
433
signature.sage
433
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
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user