geometry_tools.automata.coxeter_automaton
coxeter_automaton
Code to generate a finite-state automaton accepting geodesic words in Coxeter groups.
Code originally by Florian Stecker, with some small adaptations by Teddy Weisman
1"""coxeter_automaton 2 3Code to generate a finite-state automaton accepting geodesic words in 4Coxeter groups. 5 6Code originally by Florian Stecker, with some small adaptations by 7Teddy Weisman 8 9""" 10 11import numpy as np 12import math 13from copy import copy 14from collections import deque, defaultdict 15from . import fsa 16 17class Root: 18 def __init__(self, id, rank, depth = 0, v = None, neighbors = None): 19 self.id = id 20 self.rank = rank 21 self.depth = depth 22 if v: 23 self.v = v 24 else: 25 self.v = [0] * rank 26 if neighbors: 27 self.neighbors = neighbors 28 else: 29 self.neighbors = [None] * rank 30 31 def __copy__(self): 32 return Root(self.id, self.rank, self.depth, self.v.copy(), self.neighbors.copy()) 33 34class Groupelement: 35 def __init__(self, id, rank, word): 36 self.id = id 37 self.rank = rank 38 self.word = word 39 self.length = len(word) 40 self.left = [None]*rank 41 self.right = [None]*rank 42 self.node = None 43 self.lex_node = None 44 self.inverse = None 45 46# compute <alpha_k, beta> where alpha_k is one of the simple roots and beta any root 47def form_gen_root(form, k, root): 48 rank = len(form) 49 return sum([root[i] * form[i][k] for i in range(rank)]) 50 51# compute beta - 2<alpha_k, beta>alpha_k, i.e. the reflection of beta along alpha_k 52def apply_gen_to_root(form, k, root): 53 root[k] -= 2*form_gen_root(form, k, root) 54 55# find a sequence of generators to apply to obtain a negative root, from left to right 56# "startwith" argument can be used to force the first entry 57def find_word_to_negative(form, root_, startwith = None): 58 rank = len(form) 59 root = root_.copy() 60 word = [] 61 while not next(filter(lambda x: x < -1e-6, root), None): # while root has no negative entry 62 for k in range(rank): 63 if startwith and k != startwith: 64 continue 65 # avoiding 0 might be a problem for reducible groups? 66 f = form_gen_root(form, k, root) 67 if f > 1e-6: 68 apply_gen_to_root(form, k, root) 69 word.append(k) 70 break 71 startwith = None 72 return word 73 74# use find_word_to_negative() to find the root, if we already have it 75def find_root_from_vector(form, roots, vector): 76 rank = len(form) 77 for k in range(rank): 78 word = find_word_to_negative(form, vector, startwith = k) 79 80 if not word: 81 continue 82 83 rootobj = roots[word.pop()] 84 85 while len(word) > 0: 86 letter = word.pop() 87 if not rootobj.neighbors[letter]: 88 rootobj = None 89 break 90 else: 91 rootobj = rootobj.neighbors[letter] 92 93 if rootobj: 94 return rootobj 95 return None 96 97def find_small_roots(form): 98 rank = len(form) 99 small_roots = [] 100 101 # the simple roots are just the standard basis vectors 102 for i in range(rank): 103 r = Root(i, rank) 104 r.v[i] = 1 105 r.depth = 1 106 small_roots.append(r) 107 108 # find the other small roots by applying generators to all existing roots 109 # and using find_root_from_vector() to see if we already have it 110 # then add it if it is a small root = was obtained via a short edge (form between -1 and 0) 111 i = 0 112 while i < len(small_roots): 113 root = small_roots[i] 114 for k in range(rank): 115 newroot = root.v.copy() 116 apply_gen_to_root(form, k, newroot) 117 118 rootobj = find_root_from_vector(form, small_roots, newroot) 119 120 if rootobj: 121 root.neighbors[k] = rootobj 122 else: 123 f = form_gen_root(form, k, root.v) 124 if f > -1 + 1e-6 and f < -1e-6: # root is new and is a small root 125 rootobj = Root(len(small_roots), rank, root.depth+1, newroot) 126 small_roots.append(rootobj) 127 root.neighbors[k] = rootobj 128 i = i+1 129 return small_roots 130 131 132def apply_gen_to_node(small_roots, k, node, position, lex_reduced = False): 133 # if we want to get the lex reduced langauge 134 if lex_reduced: 135 for j in range(k): 136 if small_roots[j].neighbors[k] and position == small_roots[j].neighbors[k].id: 137 return 1 138 139 if position == k: 140 return 1 141 elif small_roots[position].neighbors[k]: 142 swappos = small_roots[position].neighbors[k].id 143 return node[swappos] 144 else: 145 return 0 146 147def generate_automaton(small_roots, lex_reduced = False): 148 nroots = len(small_roots) 149 rank = small_roots[0].rank 150 start = tuple([0]*nroots) 151 todo = deque([start]) 152 nodes = {start: 0} 153 levels = {start: 0} 154 edges = [] 155 id = 1 156 157 while todo: 158 node = todo.pop() 159 for k in range(rank): 160 if node[k] == 1: 161 continue 162 newnode = tuple( 163 apply_gen_to_node(small_roots, k, node, i, lex_reduced = lex_reduced) 164 for i in range(nroots)) 165 if not newnode in nodes: 166 nodes[newnode] = id 167 levels[newnode] = levels[node]+1 168 todo.appendleft(newnode) 169 id += 1 170 edges.append((nodes[node], nodes[newnode], k)) 171 172 graph = defaultdict(dict) 173 for (fr,to,gen) in edges: 174 graph[fr][gen] = to 175 176 return fsa.FSA(dict(graph), start_vertices=[0]) 177 178 179def enumerate_group(graph, graph_lex, max_len): 180 rank = len(graph[0]) 181 group = [Groupelement(0, rank, tuple())] 182 group[0].inverse = group[0] 183 group[0].node = group[0].lex_node = 0 184 185 i = 0 186 size = 1 187 while True: 188 current = group[i] 189 i+=1 190 191 # break if current has the max length we have, as that's when we would start adding elements 1 longer 192 if current.length >= max_len: 193 break 194 195 for gen, new_lex_node in filter(lambda x: x[1], enumerate(graph_lex[current.lex_node])): 196 new_element = Groupelement(size, rank, current.word + (gen,)) 197 new_element.lex_node = new_lex_node 198 new_element.node = graph[current.node][gen] 199 group.append(new_element) 200 size += 1 201 202 # w = w_1 t, w s = w_1 203 # right multiplication, if it decreases length 204 for k in range(rank): 205 if not graph[new_element.node][k]: 206 word = list(new_element.word) 207 longer_suffix = group[0] 208 while len(word) > 0: 209 letter = word.pop() 210 shorter_suffix = longer_suffix 211 longer_suffix = shorter_suffix.left[letter] 212 213 # w = w_1 t w_2, w_2 s_k = t w_2 214 # in the case word = [] longer_suffix could be None 215 if len(word) == 0 or shorter_suffix.right[k] == longer_suffix: 216 # finish word 217 while len(word) > 0: 218 shorter_suffix = shorter_suffix.left[word.pop()] 219 new_element.right[k] = shorter_suffix 220 shorter_suffix.right[k] = new_element 221 222 # find inverse and left multiply 223 inverse = group[0] 224 word = list(new_element.word) 225 while len(word) > 0: 226 inverse = inverse.right[word.pop()] 227 if not inverse: 228 break 229 if inverse: 230 new_element.inverse = inverse 231 inverse.inverse = new_element 232 for k in range(rank): 233 if inverse.right[k]: 234 other = inverse.right[k].inverse 235 new_element.left[k] = other 236 other.left[k] = new_element 237 if new_element.right[k]: 238 other = new_element.right[k].inverse 239 inverse.left[k] = other 240 other.left[k] = inverse 241 return group 242 243def word(w): 244 return ''.join([chr(ord('a')+x) for x in w]) 245 246def generate_automaton_coxeter_matrix(coxeter_matrix, lex_reduced = False): 247 form = [[-math.cos(math.pi/m) if m > 0 else -1 for m in row] for row in coxeter_matrix] 248 rank = len(coxeter_matrix) 249 small_roots = find_small_roots(form) 250 return generate_automaton(small_roots, lex_reduced) 251 252def even_graph(graph): 253 rank = len(graph[0]) 254 result = [] 255 for node in graph: 256 newnode = {} 257 for i in range(rank): 258 for j in range(rank): 259 if node[i] and graph[node[i]][j]: 260 newnode[(i,j)] = graph[node[i]][j] 261 result.append(newnode) 262 return result
class
Root:
18class Root: 19 def __init__(self, id, rank, depth = 0, v = None, neighbors = None): 20 self.id = id 21 self.rank = rank 22 self.depth = depth 23 if v: 24 self.v = v 25 else: 26 self.v = [0] * rank 27 if neighbors: 28 self.neighbors = neighbors 29 else: 30 self.neighbors = [None] * rank 31 32 def __copy__(self): 33 return Root(self.id, self.rank, self.depth, self.v.copy(), self.neighbors.copy())
class
Groupelement:
35class Groupelement: 36 def __init__(self, id, rank, word): 37 self.id = id 38 self.rank = rank 39 self.word = word 40 self.length = len(word) 41 self.left = [None]*rank 42 self.right = [None]*rank 43 self.node = None 44 self.lex_node = None 45 self.inverse = None
def
form_gen_root(form, k, root):
def
apply_gen_to_root(form, k, root):
def
find_word_to_negative(form, root_, startwith=None):
58def find_word_to_negative(form, root_, startwith = None): 59 rank = len(form) 60 root = root_.copy() 61 word = [] 62 while not next(filter(lambda x: x < -1e-6, root), None): # while root has no negative entry 63 for k in range(rank): 64 if startwith and k != startwith: 65 continue 66 # avoiding 0 might be a problem for reducible groups? 67 f = form_gen_root(form, k, root) 68 if f > 1e-6: 69 apply_gen_to_root(form, k, root) 70 word.append(k) 71 break 72 startwith = None 73 return word
def
find_root_from_vector(form, roots, vector):
76def find_root_from_vector(form, roots, vector): 77 rank = len(form) 78 for k in range(rank): 79 word = find_word_to_negative(form, vector, startwith = k) 80 81 if not word: 82 continue 83 84 rootobj = roots[word.pop()] 85 86 while len(word) > 0: 87 letter = word.pop() 88 if not rootobj.neighbors[letter]: 89 rootobj = None 90 break 91 else: 92 rootobj = rootobj.neighbors[letter] 93 94 if rootobj: 95 return rootobj 96 return None
def
find_small_roots(form):
98def find_small_roots(form): 99 rank = len(form) 100 small_roots = [] 101 102 # the simple roots are just the standard basis vectors 103 for i in range(rank): 104 r = Root(i, rank) 105 r.v[i] = 1 106 r.depth = 1 107 small_roots.append(r) 108 109 # find the other small roots by applying generators to all existing roots 110 # and using find_root_from_vector() to see if we already have it 111 # then add it if it is a small root = was obtained via a short edge (form between -1 and 0) 112 i = 0 113 while i < len(small_roots): 114 root = small_roots[i] 115 for k in range(rank): 116 newroot = root.v.copy() 117 apply_gen_to_root(form, k, newroot) 118 119 rootobj = find_root_from_vector(form, small_roots, newroot) 120 121 if rootobj: 122 root.neighbors[k] = rootobj 123 else: 124 f = form_gen_root(form, k, root.v) 125 if f > -1 + 1e-6 and f < -1e-6: # root is new and is a small root 126 rootobj = Root(len(small_roots), rank, root.depth+1, newroot) 127 small_roots.append(rootobj) 128 root.neighbors[k] = rootobj 129 i = i+1 130 return small_roots
def
apply_gen_to_node(small_roots, k, node, position, lex_reduced=False):
133def apply_gen_to_node(small_roots, k, node, position, lex_reduced = False): 134 # if we want to get the lex reduced langauge 135 if lex_reduced: 136 for j in range(k): 137 if small_roots[j].neighbors[k] and position == small_roots[j].neighbors[k].id: 138 return 1 139 140 if position == k: 141 return 1 142 elif small_roots[position].neighbors[k]: 143 swappos = small_roots[position].neighbors[k].id 144 return node[swappos] 145 else: 146 return 0
def
generate_automaton(small_roots, lex_reduced=False):
148def generate_automaton(small_roots, lex_reduced = False): 149 nroots = len(small_roots) 150 rank = small_roots[0].rank 151 start = tuple([0]*nroots) 152 todo = deque([start]) 153 nodes = {start: 0} 154 levels = {start: 0} 155 edges = [] 156 id = 1 157 158 while todo: 159 node = todo.pop() 160 for k in range(rank): 161 if node[k] == 1: 162 continue 163 newnode = tuple( 164 apply_gen_to_node(small_roots, k, node, i, lex_reduced = lex_reduced) 165 for i in range(nroots)) 166 if not newnode in nodes: 167 nodes[newnode] = id 168 levels[newnode] = levels[node]+1 169 todo.appendleft(newnode) 170 id += 1 171 edges.append((nodes[node], nodes[newnode], k)) 172 173 graph = defaultdict(dict) 174 for (fr,to,gen) in edges: 175 graph[fr][gen] = to 176 177 return fsa.FSA(dict(graph), start_vertices=[0])
def
enumerate_group(graph, graph_lex, max_len):
180def enumerate_group(graph, graph_lex, max_len): 181 rank = len(graph[0]) 182 group = [Groupelement(0, rank, tuple())] 183 group[0].inverse = group[0] 184 group[0].node = group[0].lex_node = 0 185 186 i = 0 187 size = 1 188 while True: 189 current = group[i] 190 i+=1 191 192 # break if current has the max length we have, as that's when we would start adding elements 1 longer 193 if current.length >= max_len: 194 break 195 196 for gen, new_lex_node in filter(lambda x: x[1], enumerate(graph_lex[current.lex_node])): 197 new_element = Groupelement(size, rank, current.word + (gen,)) 198 new_element.lex_node = new_lex_node 199 new_element.node = graph[current.node][gen] 200 group.append(new_element) 201 size += 1 202 203 # w = w_1 t, w s = w_1 204 # right multiplication, if it decreases length 205 for k in range(rank): 206 if not graph[new_element.node][k]: 207 word = list(new_element.word) 208 longer_suffix = group[0] 209 while len(word) > 0: 210 letter = word.pop() 211 shorter_suffix = longer_suffix 212 longer_suffix = shorter_suffix.left[letter] 213 214 # w = w_1 t w_2, w_2 s_k = t w_2 215 # in the case word = [] longer_suffix could be None 216 if len(word) == 0 or shorter_suffix.right[k] == longer_suffix: 217 # finish word 218 while len(word) > 0: 219 shorter_suffix = shorter_suffix.left[word.pop()] 220 new_element.right[k] = shorter_suffix 221 shorter_suffix.right[k] = new_element 222 223 # find inverse and left multiply 224 inverse = group[0] 225 word = list(new_element.word) 226 while len(word) > 0: 227 inverse = inverse.right[word.pop()] 228 if not inverse: 229 break 230 if inverse: 231 new_element.inverse = inverse 232 inverse.inverse = new_element 233 for k in range(rank): 234 if inverse.right[k]: 235 other = inverse.right[k].inverse 236 new_element.left[k] = other 237 other.left[k] = new_element 238 if new_element.right[k]: 239 other = new_element.right[k].inverse 240 inverse.left[k] = other 241 other.left[k] = inverse 242 return group
def
word(w):
def
generate_automaton_coxeter_matrix(coxeter_matrix, lex_reduced=False):
def
even_graph(graph):