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())
Root(id, rank, depth=0, v=None, neighbors=None)
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
id
rank
depth
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
Groupelement(id, rank, word)
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
id
rank
word
length
left
right
node
lex_node
inverse
def form_gen_root(form, k, root):
48def form_gen_root(form, k, root):
49        rank = len(form)
50        return sum([root[i] * form[i][k] for i in range(rank)])
def apply_gen_to_root(form, k, root):
53def apply_gen_to_root(form, k, root):
54        root[k] -= 2*form_gen_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):
244def word(w):
245        return ''.join([chr(ord('a')+x) for x in w])
def generate_automaton_coxeter_matrix(coxeter_matrix, lex_reduced=False):
247def generate_automaton_coxeter_matrix(coxeter_matrix, lex_reduced = False):
248        form = [[-math.cos(math.pi/m) if m > 0 else -1 for m in row] for row in coxeter_matrix]
249        rank = len(coxeter_matrix)
250        small_roots = find_small_roots(form)
251        return generate_automaton(small_roots, lex_reduced)
def even_graph(graph):
253def even_graph(graph):
254        rank = len(graph[0])
255        result = []
256        for node in graph:
257                newnode = {}
258                for i in range(rank):
259                        for j in range(rank):
260                                if node[i] and graph[node[i]][j]:
261                                        newnode[(i,j)] = graph[node[i]][j]
262                result.append(newnode)
263        return result