Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
79 views
unlisted
ubuntu2004
1
#### Functions for working with reflections, hyperplanes, cutting, and shards
2
3
4
# INPUT: A quiver.
5
# OUTPUT: The Cartan matrix associated to the undirected graph obtained by
6
# forgetting the orientation of the quiver.
7
def cartan(Q):
8
G = Q.to_undirected()
9
return 2*identity_matrix(len(G.vertices(sort=True))) - G.adjacency_matrix()
10
11
# INPUT: A Cartan matrix
12
# OUTPUT: A list of matrices representing the reflections associated to the Cartan matrix
13
def reflections(cartan):
14
rank = cartan.nrows()
15
reflection_list = []
16
for i in range(rank):
17
reflection = identity_matrix(QQ, rank) - (cartan * diagonal_matrix(QQ, [j == i for j in range(rank)]))
18
reflection_list.append(reflection)
19
return reflection_list
20
21
# INPUT: A Cartan matrix, an index (representing a simple root alpha_0),
22
# and a sequence of indices (representing a sequence of reflections s_1, s_2, ..., s_l)
23
# OUTPUT: If the expression which applies the reflections to the simple root is not positive, throws a ValueError.
24
# Otherwise, returns a pair consisting of the root s_l s_{l-1} ... s_1(alpha_0) (the "plane", as a 1 x n matrix)
25
# and a list of truncations s_l s_{l-1} ... s_{t+1}(alpha_t), which represent the roots cutting the hyperplane.
26
# (the "fractures", as 1 x n matrices)
27
def plane_and_fractures(cartan, starting_root, reflect_seq):
28
rank = cartan.nrows()
29
# Base case: no reflections
30
if len(reflect_seq) == 0:
31
return (Matrix([i == starting_root - 1 for i in range(rank)]), [])
32
# Recursive step
33
else:
34
reflects = reflections(cartan)
35
old_fractures = plane_and_fractures(cartan, starting_root, reflect_seq[:-1])
36
next_reflection = reflects[reflect_seq[-1] - 1]
37
new_plane = old_fractures[0] * next_reflection
38
difference = new_plane - old_fractures[0]
39
if not difference[0, reflect_seq[-1] - 1] > 0:
40
raise ValueError('This is not a positive expression, witnessed by the {0}th reflection.'.format(len(reflect_seq)))
41
else:
42
new_fractures = [s * next_reflection for s in old_fractures[1]] + [Matrix([i == reflect_seq[-1] - 1 for i in range(rank)])]
43
return (new_plane, new_fractures)
44
45
# INPUT: A pair (hyperplane H, a list of hyperplanes) where each hyperplane is
46
# specified by a 1 x n matrix.
47
# OUTPUT: A list of vectors defining a hyperplane arrangement in R^{n-1}
48
# isomorphic to the arrangement within H defined by its intersections
49
# with the other hyperplanes
50
def restricted_fractures(planes):
51
ambient_plane = list(list(planes[0])[0])
52
fractures = [list(list(p)[0]) for p in planes[1]]
53
scale = max(ambient_plane)
54
# find index at which coefficient of H is nonzero
55
normal_pivot = ambient_plane.index(scale)
56
ambient_plane.pop(normal_pivot)
57
ambient_plane_vector_trunc = vector(ambient_plane)
58
restricted_fractures = []
59
# for each other hyperplane, subtract appropriate multiple of H to zero out
60
# previously selected index
61
for plane in fractures:
62
rescale = plane[normal_pivot] / scale
63
plane.pop(normal_pivot)
64
restricted_fractures.append(vector(plane) - rescale*ambient_plane_vector_trunc)
65
return restricted_fractures
66
67
# INPUT: A list of vectors representing equations of hyperplanes
68
# and a point in the space those hyperplanes occupy
69
# OUTPUT: If the point lies on a hyperplane, throws a ValueError.
70
# Otherwise, returns a string of '+' and '-', indicating for each hyperplane
71
# (in the order they were provided) which side the point lies on
72
def manual_sign_vector(planes, point):
73
vector = ''
74
for p in planes:
75
eval = (Matrix(p) * point)[0]
76
if eval > 0:
77
vector += '+'
78
elif eval < 0:
79
vector += '-'
80
else:
81
raise ValueError('This point lies on a hyperplane.')
82
return vector
83
84
# INPUT: A Cartan matrix, an index (representing a simple root alpha_0),
85
# and a sequence of indices (representing a sequence of reflections s_1, s_2, ..., s_l)
86
# OUTPUT: A list of sign vectors describing the regions of the arrangement of shards within
87
# the hyperplane defined by s_l s_{l-1} ... s_1(alpha_0). The jth sign describes
88
# which side of the jth fracture (as output by plane_and_fractures) the region lies on.
89
def region_signs(cartan, starting_root, reflect_seq):
90
fractures = restricted_fractures(plane_and_fractures(cartan, starting_root, reflect_seq))
91
H = HyperplaneArrangements(QQ, tuple('x{0}'.format(i+1) for i in range(cartan.nrows() - 1)))
92
arr = H([[tuple(f), 0] for f in fractures])
93
sign_vectors = [manual_sign_vector(fractures, r.representative_point()) for r in arr.regions()]
94
return sign_vectors
95
96
# INPUT: A Cartan matrix, an index (representing a simple root alpha_0),
97
# a sequence of indices (representing a sequence of reflections s_1, s_2, ..., s_l),
98
# and a sequence of signs specifying a region of the arrangement of shards
99
# within the hyperplane defined by s_l s_{l-1} ... s_1(alpha_0), as output by region_signs.
100
# OUTPUT: A Polyhedron, the dual cone to the specified shard.
101
def dual_shard(cartan, start_root, reflect_seq, signs):
102
p_and_f = plane_and_fractures(cartan, start_root, reflect_seq)
103
root = p_and_f[0]
104
fracs = p_and_f[1]
105
ray_list = []
106
# specifying that the shard lies on one side of a hyperplane corresponds to
107
# adding a ray dual to that half-plane to the dual cone of the shard
108
for i in range(len(fracs)):
109
if signs[i] == '+':
110
ray_list.append(list(-fracs[i][0]))
111
elif signs[i] == '-':
112
ray_list.append(list(fracs[i][0]))
113
return Polyhedron(rays=ray_list, lines=[list(root[0])])
114
115
#### Functions for working with representations of preprojective algebras and reflection functors
116
117
# An enriched class for directed graphs in which each edge has a counterpart pointing the other direction.
118
class DoubleQuiver(sage.graphs.digraph.DiGraph):
119
120
# Constructor can take an existing DoubleQuiver (to prevent repeated edge doubling) or a general DiGraph.
121
# In the latter case it adds a reverse for every edge. In the new quiver, the original edge is renamed
122
# by adding a '0' to the end of its name, while its reverse counterpart is named by adding a '1' to the
123
# end of the original edge's name.
124
def __init__(self, digraph, multiedges=True, **kwargs):
125
if isinstance(digraph, DoubleQuiver):
126
super(DoubleQuiver, self).__init__(digraph, multiedges=True, **kwargs)
127
self._edge_pairs = digraph.edge_pairs()
128
else:
129
_edges = []
130
self._edge_pairs = {}
131
for e in digraph.edges(sort=True):
132
self._edge_pairs[(e[0], e[1], e[2] + '0')] = (e[1], e[0], e[2] + '1')
133
self._edge_pairs[(e[1], e[0], e[2] + '1')] = (e[0], e[1], e[2] + '0')
134
_edges.append((e[0], e[1], e[2] + '0'))
135
_edges.append((e[1], e[0], e[2] + '1'))
136
super(DoubleQuiver, self).__init__(_edges, multiedges=True, **kwargs)
137
138
# Take an edge as input and return its counterpart.
139
def flip(self, edge):
140
return self._edge_pairs[edge]
141
142
def edge_pairs(self):
143
return self._edge_pairs
144
145
146
# INPUT: A representation of a DoubleQuiver.
147
# OUTPUT: Whether the representation satisfies the relation necessary to be a representation of the preprojective algebra.
148
def satisfies_preproj_relation(rep):
149
q = rep.quiver()
150
for v in q.vertices(sort=True):
151
dim = rep.dimension(v)
152
# check preprojective algebra relation at vertex v
153
if not sum((rep.get_map(q.flip(e)) * rep.get_map(e) * (1 if e[2][-1] == '0' else -1)).matrix() for e in q.outgoing_edge_iterator(v)) == 0:
154
return False
155
return True
156
157
158
# INPUT: A list of vector spaces and an optional base ring.
159
# OUTPUT: A tuple (direct sum of spaces, list of inclusion maps as matrices, list of projection maps as matrices)
160
def direct_sum_with_maps(spaces, ring=QQ):
161
# empty sum
162
if len(spaces) == 0:
163
return (VectorSpace(ring, 0), [], [])
164
165
# proceed recursively, using sum of two vector spaces
166
else:
167
#ring = spaces[0].base_ring()
168
169
# take sum of all spaces but the last one
170
prelim_sum = direct_sum_with_maps(spaces[:-1])
171
172
# take sum of all spaces including the last one
173
full_sum = prelim_sum[0].direct_sum(spaces[-1])
174
175
# two useful block matrices: inclusion of prelim_sum into full_sum, and inclusion of last space into full_sum.
176
binary_sum_inclusion_maps = (End(prelim_sum[0]).identity().matrix().augment(Hom(prelim_sum[0], spaces[-1]).zero().matrix()), Hom(spaces[-1], prelim_sum[0]).zero().matrix().augment(End(spaces[-1]).identity().matrix()))
177
178
# compose the first of the above with inclusion maps into prelim_sum
179
list_of_inclusion_maps = [M * binary_sum_inclusion_maps[0] for M in prelim_sum[1]] + [binary_sum_inclusion_maps[1]]
180
181
# same as above, but for projection maps
182
binary_sum_projection_maps = ((End(prelim_sum[0]).identity().matrix().augment(Hom(prelim_sum[0], spaces[-1]).zero().matrix())).transpose(), (Hom(spaces[-1], prelim_sum[0]).zero().matrix().augment(End(spaces[-1]).identity().matrix())).transpose())
183
list_of_projection_maps = [binary_sum_projection_maps[0] * M for M in prelim_sum[2]] + [binary_sum_projection_maps[1]]
184
185
return (full_sum, list_of_inclusion_maps, list_of_projection_maps)
186
187
188
# INPUT: A representation of a DoubleQuiver, (the label of) a vertex of the quiver,
189
# and whether to require that the representation is in NoQuot (assumed by default)
190
# OUTPUT: If no_quot_check == True and the representation has S_i as a quotient,
191
# throws a ValueError. Otherwise, returns the representation obtained by applying
192
# the positive reflection functor at that vertex.
193
def reflection_functor_plus(rep, vertex, no_quot_check=True):
194
# check for preprojectivity
195
if not satisfies_preproj_relation(rep):
196
raise ValueError('This representation does not satisfy the preprojective relation.')
197
198
# extract underlying parameters of representation
199
base_ring = rep.base_ring()
200
Q = rep.quiver()
201
PQ = Q.path_semigroup()
202
spaces = {v: rep.get_space(v) for v in Q.vertices(sort=True)}
203
maps = {e: rep.get_map(e) for e in Q.edges(sort=True)}
204
205
206
# pull out the given vertex's space and the arrows / maps incident to the given vertex
207
Vi = spaces[vertex]
208
in_edges = Q.incoming_edges([vertex])
209
maps_in = [maps[e] for e in in_edges]
210
# make sure paired edges have the same index
211
out_edges = [Q.flip(e) for e in in_edges]
212
maps_out = [maps[e] for e in out_edges]
213
214
# sum together all incident spaces
215
Vdi = direct_sum_with_maps([spaces[e[0]] for e in in_edges], ring=base_ring)
216
217
218
# if the space at the given vertex is 0, there's nothing to do.
219
if Vdi[0].dimension() == 0 and rep.get_space(vertex).dimension() == 0:
220
return rep
221
222
# concatenate inward maps to get a map Vdi -> Vi
223
concat_matrix_in = sum(Vdi[2][i] * maps[in_edges[i]].matrix() * (1 if in_edges[i][2][-1] == '0' else -1) for i in range(len(in_edges)))
224
map_in = linear_transformation(Vdi[0], Vi, concat_matrix_in)
225
226
# concatenate outward maps to get a map Vi -> Vdi
227
concat_matrix_out = sum(maps[out_edges[i]].matrix() * Vdi[1][i] for i in range(len(out_edges)))
228
map_out = linear_transformation(Vi, Vdi[0], concat_matrix_out)
229
230
# if the sum of inward maps is not surjective, then rep has the simple at vertex as a quotient
231
if (no_quot_check and not map_in.is_surjective()):
232
raise ValueError('This representation has S_{0} as a quotient.'.format(vertex))
233
234
# compute kernel of map Vdi -> Vi
235
kernel = map_in.kernel()
236
237
# define new maps out of and into kernel
238
# UPDATE: the ".matrix()" at the end of each expression may be unnecessary for Sage's quiver representation
239
# constructor. I don't think this messes things up either way, though. Leaving it in for now.
240
new_maps_out = {out_edges[i]: linear_transformation(Vdi[0], spaces[out_edges[i][1]], Vdi[2][i]).restrict_domain(kernel).matrix() for i in range(len(out_edges))}
241
new_maps_in = {in_edges[i]: linear_transformation(spaces[in_edges[i][0]], Vdi[0], Vdi[1][i] * concat_matrix_in * concat_matrix_out * (1 if in_edges[i][2][-1] == '0' else -1)).restrict_codomain(kernel).matrix() for i in range(len(in_edges))}
242
243
# update space at given vertex
244
spaces.update({vertex: kernel})
245
246
# update maps incident to given vertex
247
maps.update(new_maps_out)
248
maps.update(new_maps_in)
249
250
# return reflected representation
251
new_rep = PQ.representation(base_ring, spaces, maps)
252
return new_rep
253
254
255
# INPUT: A representation of a DoubleQuiver, (the label of) a vertex of the quiver,
256
# and whether to require that the representation is in NoSub (assumed by default)
257
# OUTPUT: If no_sub_check == True and the representation has S_i as a submodule,
258
# throws a ValueError. Otherwise, returns the representation obtained by applying
259
# the negative reflection functor at that vertex.
260
def reflection_functor_minus(rep, vertex, no_sub_check=True):
261
# check for preprojectivity
262
if not satisfies_preproj_relation(rep):
263
raise ValueError('This representation does not satisfy the preprojective relation.')
264
265
# extract underlying parameters of representation
266
base_ring = rep.base_ring()
267
Q = rep.quiver()
268
PQ = Q.path_semigroup()
269
spaces = {v: rep.get_space(v) for v in Q.vertices(sort=True)}
270
maps = {e: rep.get_map(e) for e in Q.edges(sort=True)}
271
272
273
# pull out the given vertex's space and the arrows incident to the given vertex
274
Vi = spaces[vertex]
275
out_edges = Q.outgoing_edges([vertex])
276
maps_out = [maps[e] for e in out_edges]
277
# make sure paired edges have the same index
278
in_edges = [Q.flip(e) for e in out_edges]
279
maps_in = [maps[e] for e in in_edges]
280
281
# sum together all incident spaces
282
Vdi = direct_sum_with_maps([spaces[e[1]] for e in out_edges], ring=base_ring)
283
284
# if the space at the given vertex is 0, there's nothing to do.
285
if Vdi[0].dimension() == 0 and rep.get_space(vertex).dimension() == 0:
286
return rep
287
288
# concatenate inward maps (twisted by sign) to get a map Vdi -> Vi
289
concat_matrix_in = sum(Vdi[2][i] * maps[in_edges[i]].matrix() * (1 if in_edges[i][2][-1] == '0' else -1) for i in range(len(in_edges)))
290
map_in = linear_transformation(Vdi[0], Vi, concat_matrix_in)
291
292
# concatenate outward maps to get a map Vi -> Vdi
293
concat_matrix_out = sum(maps[out_edges[i]].matrix() * Vdi[1][i] for i in range(len(out_edges)))
294
map_out = linear_transformation(Vi, Vdi[0], concat_matrix_out)
295
296
# if the sum of outward maps is not injective, then rep has the simple at vertex as a submodule
297
if (no_sub_check and not map_out.is_injective()):
298
raise ValueError('This representation has S_{0} as a subrepresentation.'.format(vertex))
299
300
# compute cokernel of map Vi -> Vdi
301
cokernel, proj, lift = Vdi[0].quotient_abstract(map_out.image())
302
303
# define new maps out of and into kernel
304
# UPDATE: the ".matrix()" at the end of each expression may be unnecessary for Sage's quiver representation
305
# constructor. I don't think this messes things up either way, though. Leaving it in for now.
306
new_maps_out = {out_edges[i]: (linear_transformation(Vdi[0], spaces[out_edges[i][1]], Vdi[2][i]) * map_out * map_in * lift).matrix() for i in range(len(out_edges))}
307
new_maps_in = {in_edges[i]: (proj * linear_transformation(spaces[in_edges[i][0]], Vdi[0], Vdi[1][i]) * (1 if in_edges[i][2][-1] == '0' else -1)).matrix() for i in range(len(in_edges))}
308
309
# update space at given vertex
310
spaces.update({vertex: cokernel})
311
312
# update maps incident to given vertex
313
maps.update(new_maps_out)
314
maps.update(new_maps_in)
315
316
# return reflected representation
317
new_rep = PQ.representation(base_ring, spaces, maps)
318
return new_rep
319
320
321
# INPUT: A representation of a DoubleQuiver and a list of pairs (vertex, sign), where sign is either '+' or '-'.
322
# OUTPUT: The result of applying the sequence of reflection functors in left-to-right order.
323
def reflection_functor_seq(rep, vertices):
324
if len(vertices) == 0:
325
return rep
326
elif vertices[-1][1] == '+':
327
return reflection_functor_plus(reflection_functor_seq(rep, vertices[:-1]), vertices[-1][0])
328
elif vertices[-1][1] == '-':
329
return reflection_functor_minus(reflection_functor_seq(rep, vertices[:-1]), vertices[-1][0])
330
331
332
# INPUT: A (not necessarily doubled) quiver, a vertex, a list of vertices, and a string of '+' and '-'.
333
# OUTPUT: The representation obtained by starting with the simple at the first vertex and applying
334
# the sequence of reflection functors given by the other vertices and signs.
335
# NOTE: The output of this function may not actually be a shard module!
336
def shard_module(Q, start_vertex, reflect_vertices, sign_seq):
337
DQ = DoubleQuiver(Q)
338
PDQ = DQ.path_semigroup()
339
reflect_seq = list(zip(reflect_vertices, list(sign_seq)))
340
return reflection_functor_seq(PDQ.S(QQ, start_vertex), reflect_seq)
341
342
343
344
# INPUT: A pair (hyperplane H, a list of hyperplanes) where each hyperplane is
345
# given by a 1 x n matrix.
346
# OUTPUT: A list of lists, each of which consists of indices into the list in the input
347
# corresponding to hyperplanes which give the same result when intersected with H.
348
def walls(p_and_f):
349
wall_grouping = []
350
for i in range(len(p_and_f[1])):
351
independent = True
352
for j in range(len(wall_grouping)):
353
w = wall_grouping[j]
354
subsystem_matrix = Matrix([p_and_f[0][0], p_and_f[1][w[0]][0], p_and_f[1][i][0]])
355
if rank(subsystem_matrix) == 2:
356
wall_grouping[j] = w + [i]
357
independent = False
358
if independent:
359
wall_grouping = wall_grouping + [[i]]
360
return wall_grouping
361
362
363
# INPUT: A representation of a DoubleQuiver and a sequence of vertices of the quiver
364
# OUTPUT: A string of '+' and '-' such that the given representation can be obtained
365
# by another representation by applying reflection functors at the given indices with
366
# these signs.
367
# (In other words, we can apply reflection functors at the given sequence of indices
368
# in reverse order with the opposite signs, and the intermediate representations thus
369
# obtained will be NoQuot or NoSub, as appropriate.)
370
def undo_reflections(rep, reflect_seq):
371
if len(reflect_seq) == 0:
372
return ''
373
try:
374
new_rep = reflection_functor_minus(rep, reflect_seq[-1])
375
return undo_reflections(new_rep, reflect_seq[:-1]) + '+'
376
except ValueError:
377
new_rep = reflection_functor_plus(rep, reflect_seq[-1])
378
return undo_reflections(new_rep, reflect_seq[:-1]) + '-'
379
380
381
# Given a shard module:
382
# - finds reduced expression for root
383
# - finds module's sign sequence with respect to this reduced expression
384
# - for each wall associated to the reduced expression, flips those signs and tries computing
385
# the ensuing shard module (NOTE! This may be super inefficient, and for larger situations it may be
386
# better to implement this with convex geometry jiggery-pokery!)
387
# - also it depends on the truth of David & Hugh's conjecture, but oh well
388
# - return the reduced expression, the sign sequence, and the list of walls for which this works
389
390
# This method determines, given an expression for a representation in terms of reflection functors,
391
# the minimal flips we can make in the signs of those reflection functors
392
# such that the results are also legitimate sequences of reflection functors.
393
#
394
# INPUT: A representation of a DoubleQuiver
395
# and a pair (vertex, sequence of vertices) representing a positive expression
396
# (simple root, sequence of reflections) for beta, the dimension vector of the representation
397
# OUTPUT:
398
# - the provided expression (for legacy reasons)
399
# - a sequence of signs such that, if we start with the simple module associated
400
# to the simple root in our expression and apply the reflection functors
401
# associated to the reflections in our expression with these signs, we obtain
402
# the given representation
403
# - a collection of lists of indices representing signs we can flip in this sequence
404
# such that we produce another valid sequence of reflection functors.
405
# Each index represents an entry in the output of walls(), representing one or more hyperplanes
406
# which cut the hyperplane defined by our dimension vector in the same way.
407
# - for each list of indices in the previous output, the hyperplanes produced by plane_and_fractures
408
# which correspond to those indices
409
def shard_module_walls(module, chosen_expression):
410
root = module.dimension_vector()
411
G = module.quiver().to_undirected()
412
cartan_matrix = cartan(G)/2 + identity_matrix(len(G.vertices(sort=True)))
413
#if chosen_expression == None:
414
# expression = get_reduced_expression(cartan_matrix, root)
415
#else:
416
expression = chosen_expression
417
signs = undo_reflections(module, expression[1])
418
p_and_f = plane_and_fractures(cartan_matrix, expression[0], expression[1])
419
all_walls = walls(p_and_f)
420
421
border_walls = []
422
for w in all_walls:
423
new_signs = ''
424
for i in range(len(signs)):
425
if i in w:
426
if signs[i] == '+':
427
new_signs = new_signs + '-'
428
elif signs[i] == '-':
429
new_signs = new_signs + '+'
430
else:
431
new_signs = new_signs + signs[i]
432
try:
433
flipped_rep = shard_module(module.quiver(), expression[0], expression[1], new_signs)
434
border_walls.append(w)
435
except:
436
pass
437
return (expression, signs, border_walls, [p_and_f[1][w[0]] for w in border_walls])
438
439
440
441
442
443
444