geometry_tools.representation
Work with group representations into finite-dimensional vector spaces, using numerical matrices.
To make a representation, instantiate the Representation
class, and
assign numpy arrays to group generators (which are lowercase letters
a-z). Use square brackets to access the image of a word in the
generators.
import numpy as np
from geometry_tools import representation
from geometry_tools.automata import fsa
rep = representation.Representation()
rep["a"] = np.array([
[3.0, 0.0],
[0.0, 1/3.0]
])
rep["aaa"]
array([[27. , 0. ],
[ 0. , 0.03703704]])
Generator inverses are automatically assigned to capital letters:
rep = representation.Representation()
rep["a"] = np.array([
[3.0, 0.0],
[0.0, 1/3.0]
])
rep["aA"]
array([[1., 0.],
[0., 1.]])
A common use-case for this class is to get a list of matrices
representing all elements in the group, up to a bounded word
length. The fastest way to do this is to use the built-in
Representation.freely_reduced_elements
method, which returns a numpy
array containing one matrix for each freely reduced word in the group
(up to a specified word length).
The array of matrices is not typically ordered lexicographically. To
get a list of words corresponding to the matrices returned, pass the
with_words
flag when calling
Representation.freely_reduced_elements
(see the documentation for
that function for details).
rep = representation.Representation()
rep["a"] = np.array([
[3.0, 0.0],
[0.0, 1/3.0]
])
rep["b"] = np.array([
[1.0, -1.0],
[1.0, 1.0]
]) / np.sqrt(2)
rep.freely_reduced_elements(6)
array([[[ 1.00000000e+00, 0.00000000e+00],
[ 0.00000000e+00, 1.00000000e+00]],
[[ 7.07106781e-01, 7.07106781e-01],
[-7.07106781e-01, 7.07106781e-01]],
[[ 2.12132034e+00, 2.12132034e+00],
[-2.35702260e-01, 2.35702260e-01]],
...,
[[-2.12132034e+00, -2.12132034e+00],
[ 2.35702260e-01, -2.35702260e-01]],
[[-2.35702260e-01, -2.35702260e-01],
[ 2.12132034e+00, -2.12132034e+00]],
[[-1.07699575e-16, -1.00000000e+00],
[ 1.00000000e+00, 7.51818954e-17]]])
You can speed up this process even more if you have access to a finite state automaton which provides a unique word for each element in your group.
For instance, to find the image of a Cayley ball of radius 10 under
the canonical representation of a (3,3,4) triangle group, you can use
the Representation.automaton_accepted
method as follows:
from geometry_tools import coxeter
from geometry_tools.automata import fsa
# create the representation and load the built-in automaton
rep = coxeter.TriangleGroup((3,3,4)).canonical_representation()
automaton = fsa.load_builtin('cox334.wa')
rep.automaton_accepted(automaton, 10)
array([[[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00],
[ 0.00000000e+00, 1.00000000e+00, 0.00000000e+00],
[ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]],
[[ 4.44089210e-16, -1.00000000e+00, 2.41421356e+00],
[ 1.00000000e+00, -1.00000000e+00, 1.00000000e+00],
[ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]],
[[-1.00000000e+00, 1.00000000e+00, 1.41421356e+00],
[ 0.00000000e+00, 1.00000000e+00, 0.00000000e+00],
[ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]],
...,
[[-2.47279221e+01, 2.57279221e+01, 2.23137085e+01],
[-2.91421356e+01, 2.91421356e+01, 2.71421356e+01],
[-1.50710678e+01, 1.50710678e+01, 1.40710678e+01]],
[[-7.24264069e+00, 7.24264069e+00, 6.82842712e+00],
[-1.06568542e+01, 1.16568542e+01, 9.24264069e+00],
[-5.82842712e+00, 6.82842712e+00, 4.82842712e+00]],
[[-2.47279221e+01, 2.57279221e+01, 2.23137085e+01],
[-1.06568542e+01, 1.16568542e+01, 9.24264069e+00],
[-3.05563492e+01, 3.29705627e+01, 2.67279221e+01]]])
1"""Work with group representations into finite-dimensional vector 2spaces, using numerical matrices. 3 4 5To make a representation, instantiate the `Representation` class, and 6assign numpy arrays to group generators (which are lowercase letters 7a-z). Use square brackets to access the image of a word in the 8generators. 9 10 11```python 12import numpy as np 13 14from geometry_tools import representation 15from geometry_tools.automata import fsa 16 17rep = representation.Representation() 18rep["a"] = np.array([ 19 [3.0, 0.0], 20 [0.0, 1/3.0] 21]) 22 23rep["aaa"] 24``` 25 26 27 28 29 array([[27. , 0. ], 30 [ 0. , 0.03703704]]) 31 32 33 34Generator inverses are automatically assigned to capital letters: 35 36 37```python 38rep = representation.Representation() 39rep["a"] = np.array([ 40 [3.0, 0.0], 41 [0.0, 1/3.0] 42]) 43 44rep["aA"] 45``` 46 47 48 49 50 array([[1., 0.], 51 [0., 1.]]) 52 53 54 55A common use-case for this class is to get a list of matrices 56representing all elements in the group, up to a bounded word 57length. The fastest way to do this is to use the built-in 58`Representation.freely_reduced_elements` method, which returns a numpy 59array containing one matrix for each freely reduced word in the group 60(up to a specified word length). 61 62The array of matrices is *not* typically ordered lexicographically. To 63get a list of words corresponding to the matrices returned, pass the 64`with_words` flag when calling 65`Representation.freely_reduced_elements` (see the documentation for 66that function for details). 67 68 69```python 70rep = representation.Representation() 71rep["a"] = np.array([ 72 [3.0, 0.0], 73 [0.0, 1/3.0] 74]) 75rep["b"] = np.array([ 76 [1.0, -1.0], 77 [1.0, 1.0] 78]) / np.sqrt(2) 79 80 81rep.freely_reduced_elements(6) 82``` 83 84 85 86 87 array([[[ 1.00000000e+00, 0.00000000e+00], 88 [ 0.00000000e+00, 1.00000000e+00]], 89 90 [[ 7.07106781e-01, 7.07106781e-01], 91 [-7.07106781e-01, 7.07106781e-01]], 92 93 [[ 2.12132034e+00, 2.12132034e+00], 94 [-2.35702260e-01, 2.35702260e-01]], 95 96 ..., 97 98 [[-2.12132034e+00, -2.12132034e+00], 99 [ 2.35702260e-01, -2.35702260e-01]], 100 101 [[-2.35702260e-01, -2.35702260e-01], 102 [ 2.12132034e+00, -2.12132034e+00]], 103 104 [[-1.07699575e-16, -1.00000000e+00], 105 [ 1.00000000e+00, 7.51818954e-17]]]) 106 107 108 109You can speed up this process even more if you have access to a finite 110state automaton which provides a unique word for each element in your 111group. 112 113For instance, to find the image of a Cayley ball of radius 10 under 114the canonical representation of a (3,3,4) triangle group, you can use 115the `Representation.automaton_accepted` method as follows: 116 117 118```python 119from geometry_tools import coxeter 120from geometry_tools.automata import fsa 121 122# create the representation and load the built-in automaton 123rep = coxeter.TriangleGroup((3,3,4)).canonical_representation() 124automaton = fsa.load_builtin('cox334.wa') 125 126rep.automaton_accepted(automaton, 10) 127``` 128 129 130 131 132 array([[[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00], 133 [ 0.00000000e+00, 1.00000000e+00, 0.00000000e+00], 134 [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]], 135 136 [[ 4.44089210e-16, -1.00000000e+00, 2.41421356e+00], 137 [ 1.00000000e+00, -1.00000000e+00, 1.00000000e+00], 138 [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]], 139 140 [[-1.00000000e+00, 1.00000000e+00, 1.41421356e+00], 141 [ 0.00000000e+00, 1.00000000e+00, 0.00000000e+00], 142 [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]], 143 144 ..., 145 146 [[-2.47279221e+01, 2.57279221e+01, 2.23137085e+01], 147 [-2.91421356e+01, 2.91421356e+01, 2.71421356e+01], 148 [-1.50710678e+01, 1.50710678e+01, 1.40710678e+01]], 149 150 [[-7.24264069e+00, 7.24264069e+00, 6.82842712e+00], 151 [-1.06568542e+01, 1.16568542e+01, 9.24264069e+00], 152 [-5.82842712e+00, 6.82842712e+00, 4.82842712e+00]], 153 154 [[-2.47279221e+01, 2.57279221e+01, 2.23137085e+01], 155 [-1.06568542e+01, 1.16568542e+01, 9.24264069e+00], 156 [-3.05563492e+01, 3.29705627e+01, 2.67279221e+01]]]) 157 158 """ 159 160import re 161import itertools 162 163import numpy as np 164import scipy.special 165 166from .automata import fsa 167from . import lie 168from . import utils 169from .utils import words 170 171if utils.SAGE_AVAILABLE: 172 from .utils import sagewrap 173 174class Representation: 175 """Model a representation for a finitely generated group 176 representation into GL(n). 177 """ 178 179 @property 180 def dim(self): 181 return self._dim 182 183 @property 184 def dtype(self): 185 return self._dtype 186 187 @property 188 def base_ring(self): 189 return self._base_ring 190 191 @staticmethod 192 def wrap_func(numpy_matrix): 193 return numpy_matrix 194 195 @staticmethod 196 def unwrap_func(wrapped_matrix): 197 return wrapped_matrix 198 199 @staticmethod 200 def array_wrap_func(numpy_array): 201 return numpy_array 202 203 def parse_word(self, word, simple=None): 204 if simple is None: 205 simple = self.parse_simple 206 207 if simple: 208 return word 209 210 return re.split("[()*]", word) 211 212 def __getitem__(self, word): 213 return self.element(word) 214 215 def element(self, word, parse_simple=True): 216 matrix = self._word_value(word, parse_simple) 217 return self.__class__.wrap_func(matrix) 218 219 def __init__(self, representation=None, 220 generator_names=None, 221 relations=[], 222 base_ring=None, 223 parse_simple=True, 224 dtype='float64'): 225 """ 226 Parameters 227 ---------- 228 representation : Representation 229 Representation to copy elements from 230 generator_names : iterable of strings 231 Names to use for the generators. These must be initialized 232 as arrays later to use the representation properly. 233 dtype : numpy dtype 234 Default data type for matrix entries. Defaults to 'float64'. 235 236 """ 237 238 self._dim = None 239 self._dtype = dtype 240 self._base_ring = base_ring 241 self.parse_simple = parse_simple 242 self.relations = [r for r in relations] 243 244 if representation is not None: 245 if base_ring is None: 246 self._base_ring = representation.base_ring 247 248 if generator_names is None: 249 generator_names = list(representation.generators) 250 251 self.generators = {} 252 253 # don't recompute inverses for an existing representation 254 for gen in generator_names: 255 self._set_generator(gen, representation.generators[gen], 256 compute_inverse=False) 257 258 self._dim = representation._dim 259 260 self.relations += [r for r in representation.relations] 261 262 else: 263 if generator_names is None: 264 generator_names = [] 265 266 self.generators = {name[0].lower():None 267 for name in words.asym_gens(generator_names)} 268 269 for gen in list(self.generators): 270 self.generators[gen.upper()] = None 271 272 def freely_reduced_elements(self, length, maxlen=True, 273 with_words=False): 274 """Return group elements, one for each freely reduced word 275 in the generators. 276 277 Parameters 278 ---------- 279 length : int 280 maximum length of a word to find a representation of 281 maxlen : bool 282 If `True` (the default), the returned array has one 283 element for each word up to length `length` 284 (inclusive). If `False`, only compute the image of words 285 of length exactly equal to `length`. 286 with_words : bool 287 If `True`, also return the list of words corresponding to 288 the computed matrices. 289 290 Returns 291 ------- 292 result : ndarray or tuple 293 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 294 where `elements` is an ndarray of shape `(k, n, n)`, 295 containing one matrix for each of the `k` freely reduced 296 words in the generators, and `words` is a python list of 297 strings, containing those words. If `with_words` is 298 `False`, just return `elements`. 299 300 """ 301 automaton = fsa.free_automaton(list(self.asym_gens())) 302 return self.automaton_accepted(automaton, length, 303 maxlen=maxlen, 304 with_words=with_words) 305 306 def free_words_of_length(self, length): 307 """Yield freely reduced words in the generators, of a specified length. 308 309 Parameters 310 ---------- 311 length : int 312 the length of words to return 313 314 Yields 315 ------ 316 word : string 317 All freely reduced words in the generators of length 318 `length`. 319 320 """ 321 322 if length == 0: 323 yield "" 324 else: 325 for word in self.free_words_of_length(length - 1): 326 for generator in self.generators: 327 if len(word) == 0 or generator != words.invert_gen(word[-1]): 328 yield word + generator 329 330 def free_words_less_than(self, length): 331 """Yield freely reduced words in the generators, up to a specified 332 length. 333 334 Parameters 335 ---------- 336 length : int 337 the maximum length of words to return 338 339 Yields 340 ------ 341 word : string 342 All freely reduced words in the generators, up to length 343 `length` (inclusive) 344 345 """ 346 for i in range(length): 347 for word in self.free_words_of_length(i): 348 yield word 349 350 def automaton_accepted(self, automaton, length, 351 maxlen=True, with_words=False, 352 start_state=None, end_state=None, 353 precomputed=None, edge_words=False): 354 """Return group elements representing words accepted by a 355 finite-state automaton. 356 357 Parameters 358 ---------- 359 automaton : automata.fsa.FSA 360 Finite-state automaton which determines the accepted words 361 length : int 362 Maximum length of a word to compute 363 maxlen : bool 364 if `True` (the default), compute representations of all 365 accepted words of length up to `length`. If `False`, only 366 compute representations of words whose length is exactly 367 `length`. 368 with_words : bool 369 Whether to return a list of accepted words along with the 370 computed array of images. 371 start_state: object 372 vertex of the automaton to use as the starting state for 373 all accepted words. If `None`, then the default start 374 vertex of the automaton is used. *Note*: at most one of 375 `start_state` and `end_state` can be specified. 376 end_state: object 377 vertex of the automaton where all accepted paths must 378 end. If `None`, then any ending state is allowed. *Note*: 379 at most one of `start_state` and `end_state` can be 380 specified. 381 precomputed: dict 382 dictionary giving precomputed values of this 383 function. Keys are tuples of then form `(length, state)`, 384 where `length` is an integer and `state` is a vertex of 385 the automaton. If `None`, use an empty dictionary. In 386 either case, the dictionary will be populated when the 387 function is called. 388 edge_words : bool 389 If True, view each label of the given automaton as a word 390 in the generators for this representation. Otherwise, 391 interpret the label as a single generator. 392 393 Returns 394 ------- 395 result : ndarray or tuple 396 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 397 where `elements` is an ndarray containing one 398 matrix for each accepted word, and `words` is a list of 399 strings containing the corresponding words. If 400 `with_words` is `False`, just return `elements`. 401 402 """ 403 404 if start_state is not None and end_state is not None: 405 raise ValueError("At most one of start_state and end_state " 406 "can be specified") 407 408 as_start = True 409 state = None 410 411 if start_state is not None: 412 state = start_state 413 as_start = True 414 415 if end_state is not None: 416 state = end_state 417 as_start = False 418 419 result = self._automaton_accepted(automaton, length, 420 maxlen=maxlen, 421 with_words=with_words, 422 state=state, 423 as_start=as_start, 424 precomputed=precomputed) 425 426 if with_words: 427 matrix_array, words = result 428 else: 429 matrix_array = result 430 431 wrapped_matrices = self.__class__.array_wrap_func(matrix_array) 432 433 if with_words: 434 return wrapped_matrices, words 435 436 return wrapped_matrices 437 438 def _automaton_accepted(self, automaton, length, 439 state=None, as_start=True, maxlen=True, 440 precomputed=None, with_words=False, 441 edge_words=False): 442 443 if precomputed is None: 444 precomputed = {} 445 446 if (length, state) in precomputed: 447 return precomputed[(length, state)] 448 449 empty_arr = np.array([]).reshape((0, self.dim, self.dim)) 450 451 if length == 0: 452 if state is None or as_start or state in automaton.start_vertices: 453 id_array = np.array([utils.identity(self.dim, 454 dtype=self.dtype, 455 base_ring=self.base_ring)]) 456 if with_words: 457 return (id_array, [""]) 458 return id_array 459 460 if with_words: 461 return (empty_arr, []) 462 return empty_arr 463 464 if state is None: 465 as_start = True 466 state = automaton.start_vertices[0] 467 468 if as_start: 469 adj_states = automaton.out_dict[state] 470 else: 471 adj_states = automaton.in_dict[state] 472 473 if len(adj_states) == 0 and not as_start: 474 if with_words: 475 return (empty_arr, []) 476 return empty_arr 477 478 479 matrix_list = [] 480 accepted_words = [] 481 for adj_state, labels in adj_states.items(): 482 for label in labels: 483 result = self._automaton_accepted( 484 automaton, length - 1, 485 state=adj_state, 486 as_start=as_start, 487 maxlen=maxlen, 488 precomputed=precomputed, 489 with_words=with_words 490 ) 491 if with_words: 492 matrices, words = result 493 if as_start: 494 words = [label + word for word in words] 495 else: 496 words = [word + label for word in words] 497 accepted_words += words 498 else: 499 matrices = result 500 501 if edge_words: 502 edge_elt = self._word_value(label) 503 else: 504 edge_elt = self.generators[label] 505 if as_start: 506 matrices = edge_elt @ matrices 507 else: 508 matrices = matrices @ edge_elt 509 matrix_list.append(matrices) 510 511 accepted_matrices = empty_arr 512 513 if len(matrix_list) > 0: 514 accepted_matrices = np.concatenate(matrix_list) 515 516 if maxlen: 517 additional_result = self._automaton_accepted( 518 automaton, 0, 519 state=state, 520 as_start=as_start, 521 with_words=with_words 522 ) 523 if with_words: 524 additional_mats, additional_words = additional_result 525 accepted_words = additional_words + accepted_words 526 else: 527 additional_mats = additional_result 528 529 accepted_matrices = np.concatenate( 530 [additional_mats, accepted_matrices] 531 ) 532 533 if with_words: 534 accepted = (accepted_matrices, accepted_words) 535 else: 536 accepted = accepted_matrices 537 538 precomputed[(length, state)] = accepted 539 return accepted 540 541 def elements(self, words): 542 """Get images of an iterable of words. 543 544 Parameters 545 ---------- 546 words : iterable of strings 547 words to find the image of under the representation 548 549 Returns 550 ------- 551 ndarray 552 numpy array of shape `(l, n, n)`, where `l` is the length 553 of `words` and `n` is the dimension of the representation. 554 555 """ 556 return self.__class__.array_wrap_func(np.array( 557 [self._word_value(word) for word in words] 558 )) 559 560 def asym_gens(self): 561 """Iterate over asymmetric group generator names for this 562 representation. 563 564 Only iterate over group generators (lowercase letters), not 565 semigroup generators (upper and lowercase letters). 566 567 Yields 568 ------ 569 gen : string 570 group generator names 571 572 """ 573 return words.asym_gens(self.generators.keys()) 574 575 def dual(self): 576 return self._compose( 577 lambda M: utils.invert(M).T 578 ) 579 580 def _word_value(self, word, parse_simple=None): 581 gen_list = self.parse_word(word, simple=parse_simple) 582 583 matrix = utils.identity(self._dim, dtype=self.dtype) 584 for gen in gen_list: 585 matrix = matrix @ self.generators[gen] 586 return matrix 587 588 def _set_generator(self, generator, matrix, compute_inverse=True, 589 base_ring=None): 590 591 shape = matrix.shape 592 593 if self._dim is None: 594 self._dim = shape[0] 595 if shape[0] != shape[1]: 596 raise ValueError("Matrices representing group elements must be square") 597 if shape[0] != self._dim: 598 raise ValueError( 599 "Every matrix in the representation must have the same shape" 600 ) 601 602 if re.search("[*()]", generator): 603 raise ValueError( 604 "The characters *, (, and ) are reserved and " 605 " cannot be used in generator names." 606 ) 607 608 if not re.search("[a-zA-Z]", generator): 609 raise ValueError( 610 "Generator names must contain at least one letter a-z or A-Z" 611 ) 612 613 if generator != generator.lower() and generator != generator.upper(): 614 raise ValueError( 615 "Generator names cannot be mixed-case" 616 ) 617 618 if base_ring is not None: 619 matrix = utils.change_base_ring(matrix, base_ring) 620 self._base_ring = base_ring 621 622 self.generators[generator] = matrix 623 if compute_inverse: 624 self.generators[words.invert_gen(generator)] = utils.invert(matrix) 625 626 # always update the dtype (we don't have a hierarchy for this) 627 self._dtype = matrix.dtype 628 629 def set_generator(self, generator, matrix, **kwargs): 630 self._set_generator(generator, 631 self.__class__.unwrap_func(matrix), 632 **kwargs) 633 634 def __setitem__(self, generator, matrix): 635 self.set_generator(generator, matrix, compute_inverse=True) 636 637 def change_base_ring(self, base_ring=None): 638 return self._compose( 639 lambda M: utils.change_base_ring(M, base_ring), 640 base_ring=base_ring 641 ) 642 643 def astype(self, dtype): 644 base_ring = self.base_ring 645 if dtype != np.dtype('O'): 646 base_ring = None 647 648 new_rep = self._compose( 649 lambda M: M.astype(dtype), 650 dtype=dtype 651 ) 652 new_rep._base_ring = base_ring 653 654 return new_rep 655 656 def conjugate(self, mat, inv_mat=None, unwrap=True, **kwargs): 657 if not unwrap: 658 return self._conjugate( 659 mat, inv_mat, **kwargs 660 ) 661 662 if inv_mat is not None: 663 inv_mat = self.__class__.unwrap_func(inv_mat) 664 665 return self._conjugate( 666 self.__class__.unwrap_func(mat), 667 inv_mat, **kwargs 668 ) 669 670 def _conjugate(self, mat, inv_mat=None, **kwargs): 671 if inv_mat is None: 672 inv_mat = utils.invert(mat, **kwargs) 673 674 return self._compose(lambda M: inv_mat @ M @ mat) 675 676 def _compose(self, hom, compute_inverses=False, dtype=None, 677 base_ring=None, **kwargs): 678 """Get a new representation obtained by composing this representation 679 with hom.""" 680 681 if dtype is None: 682 dtype = self.dtype 683 684 if base_ring is None: 685 base_ring = self.base_ring 686 687 composed_rep = self.__class__(dtype=dtype, base_ring=base_ring, 688 relations=self.relations, **kwargs) 689 690 if compute_inverses: 691 generator_iterator = self.asym_gens() 692 else: 693 generator_iterator = self.generators.keys() 694 695 for g in generator_iterator: 696 image = self.generators[g] 697 inv_image = self.generators[words.invert_gen(g)] 698 699 try: 700 composed = hom(image, inv=inv_image) 701 except TypeError: 702 composed = hom(image) 703 704 composed_rep._set_generator(g, composed, 705 compute_inverse=compute_inverses) 706 707 return composed_rep 708 709 def compose(self, hom, hom_in_wrapped=False, hom_out_wrapped=False, 710 **kwargs): 711 712 hom_1 = hom 713 if hom_in_wrapped: 714 hom_1 = (lambda M: hom(self.__class__.unwrap_func(M))) 715 716 hom_2 = hom_1 717 if hom_out_wrapped: 718 hom_2 = (lambda M: self.__class__.unwrap_func(hom_1(M))) 719 720 composed_rep = self._compose(hom_2, **kwargs) 721 722 return composed_rep 723 724 def _differential(self, word, generator=None, verbose=False): 725 if generator is not None: 726 if verbose: 727 print( 728 "computing differential of '{}' with respect to {}".format( 729 word, generator) 730 ) 731 word_diff = words.fox_word_derivative(generator, word) 732 matrix_diff = [ 733 coeff * self._word_value(word) 734 for word, coeff in word_diff.items() 735 ] 736 if len(matrix_diff) == 0: 737 return utils.zeros(self.dim, base_ring=self.base_ring) 738 739 return np.sum(matrix_diff, axis=0) 740 741 blocks = [self._differential(word, g, verbose=verbose) 742 for g in self.asym_gens()] 743 744 return np.concatenate(blocks, axis=-1) 745 746 def subgroup(self, generators, generator_names=None, relations=[], 747 compute_inverse=True): 748 subrep = self.__class__(relations=relations, 749 base_ring=self.base_ring, 750 dtype=self.dtype) 751 752 try: 753 generator_pairs = generators.items() 754 except AttributeError: 755 generator_pairs = zip(GENERATORS[:len(generators)], 756 generators) 757 758 if generator_names is not None: 759 generator_pairs = zip(generator_names, generators) 760 761 for g, word in generator_pairs: 762 subrep._set_generator(g, self._word_value(word), 763 compute_inverse=compute_inverse) 764 if not compute_inverse: 765 subrep._set_generator( 766 words.invert_gen(g), 767 self._word_value(words.formal_inverse(word)) 768 ) 769 770 return subrep 771 772 def _differentials(self, words, **kwargs): 773 blocks = [self._differential(word, **kwargs) for word in words] 774 return np.concatenate(blocks, axis=0) 775 776 def differential(self, word, **kwargs): 777 return self._differential(word, **kwargs) 778 779 def differentials(self, words, **kwargs): 780 return self._differentials(words, **kwargs) 781 782 def cocycle_matrix(self, **kwargs): 783 return self.differentials(self.relations, **kwargs) 784 785 def coboundary_matrix(self): 786 blocks = [utils.identity(self._dim, dtype=self.dtype, 787 base_ring=self.base_ring) - self.generators[gen] 788 for gen in self.asym_gens() ] 789 790 return np.concatenate(blocks, axis=0) 791 792 def gln_adjoint(self, base_ring=None, dtype=None, **kwargs): 793 if base_ring is None: 794 base_ring = self.base_ring 795 796 if dtype is None: 797 dtype = self.dtype 798 799 gln_adjoint = lie.hom.gln_adjoint( 800 base_ring=base_ring, dtype=dtype 801 ) 802 803 return self._compose(gln_adjoint, base_ring=base_ring, 804 dtype=dtype, **kwargs) 805 806 def sln_adjoint(self, base_ring=None, dtype=None, **kwargs): 807 if base_ring is None: 808 base_ring = self.base_ring 809 810 if dtype is None: 811 dtype = self.dtype 812 813 sln_adjoint = lie.hom.sln_adjoint( 814 base_ring=base_ring, dtype=dtype 815 ) 816 817 return self._compose(sln_adjoint, base_ring=base_ring, 818 dtype=dtype, **kwargs) 819 820 def tensor_product(self, rep): 821 """Return a tensor product of this representation with `rep`. 822 823 Parameters 824 ---------- 825 rep : Representation 826 Representation to tensor with. 827 828 Raises 829 ------ 830 ValueError 831 Raised if `self` and `rep` have differing generating sets. 832 833 Returns 834 ------- 835 tensor: Representation 836 Representation giving the tensor product of self with `rep`. 837 838 """ 839 if set(rep.generators) != set(self.generators): 840 raise ValueError( 841 "Cannot take a tensor product of a representation of groups with " 842 "different presentations" 843 ) 844 else: 845 product_rep = Representation() 846 for gen in self.asym_gens(): 847 tens = np.tensordot(self[gen], rep[gen], axes=0) 848 elt = np.concatenate(np.concatenate(tens, axis=1), axis=1) 849 product_rep[gen] = np.array(elt) 850 return product_rep 851 852 def symmetric_square(self): 853 """Return the symmetric square of this representation. 854 855 Returns 856 ------- 857 square : Representation 858 Symmetric square of `self`. 859 860 """ 861 862 tensor_rep = self.tensor_product(self) 863 incl = symmetric_inclusion(self._dim) 864 proj = symmetric_projection(self._dim) 865 square_rep = Representation() 866 for g in self.asym_gens(): 867 square_rep[g] = proj * tensor_rep[g] * incl 868 869 return square_rep 870 871class SageMatrixRepresentation(Representation): 872 @staticmethod 873 def wrap_func(numpy_matrix): 874 return sagewrap.sage_matrix(numpy_matrix) 875 876 @staticmethod 877 def unwrap_func(sage_matrix): 878 return np.array(sage_matrix) 879 880 @staticmethod 881 def array_wrap_func(numpy_array): 882 return sagewrap.sage_matrix_list(numpy_array) 883 884 def differential(self, word, **kwargs): 885 return sagewrap.sage_matrix(self._differential(word, **kwargs)) 886 887 def differentials(self, words, **kwargs): 888 return sagewrap.sage_matrix(self._differentials(words, **kwargs)) 889 890 def coboundary_matrix(self): 891 return sagewrap.sage_matrix( 892 Representation.coboundary_matrix(self) 893 ) 894 895 def compose(self, hom, hom_in_wrapped=True, 896 hom_out_wrapped=False, **kwargs): 897 return Representation.compose(self, hom, 898 hom_in_wrapped=hom_in_wrapped, 899 hom_out_wrapped=hom_out_wrapped, 900 **kwargs) 901 902def sym_index(i, j, n): 903 r"""Return coordinate indices for an isomorphism 904 $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^{\binom{n}{2} + n}$. 905 906 If $\{e_1, \ldots, e_n\}$ is the standard basis for $\mathbb{R}^n$, 907 the isomorphism is realized by giving $\mathrm{Sym}^2(\mathbb{R}^n)$ 908 the ordered basis 909 $$ 910 \{e_ne_n, e_{n-1}e_{n-1}, e_{n-1}e_n, 911 e_{n-1}e_{n-1}, e_{n-1}e_{n-2}, e_{n-1}e_{n-3}, \ldots \}. 912 $$ 913 Schematically this is given by the symmetric matrix 914 915 $$\begin{pmatrix} \ddots \\ 916 & 3 & 4 & 5 \\ 917 & & 1 & 2 \\ 918 & & & 0 919 \end{pmatrix}, 920 $$ 921 where the (i,j) entry of the matrix gives the index of basis 922 element $e_ie_j$. 923 924 Parameters 925 ---------- 926 i : int 927 index of one of the terms in the basis monomial $e_ie_j$ for the 928 symmetric square 929 j : int 930 index of the other term in the basis monomial $e_ie_j$ for the 931 symmetric square 932 n : int 933 dimension of the underlying vector space $\mathbb{R}^n$. 934 935 Returns 936 ------- 937 int 938 index of the corresponding basis vector in 939 $\mathbb{R}^{\binom{n}{2} + n}$. 940 941 """ 942 if i > j: 943 i, j = j, i 944 return int((n - i) * (n - i - 1) / 2 + (j - i)) 945 946def tensor_pos(i, n): 947 r"""Return coordinate indices for an isomorphism 948 $\mathbb{R}^{n^2} \to \mathbb{R}^n \otimes \mathbb{R}^n$. 949 950 If $\{e_1, \ldots, e_n\}$ is the standard basis for 951 $\mathbb{R}^n$, the isomorphism is realized by giving 952 $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis 953 $$ 954 \{e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, \} 955 $$ 956 represented schematically by the matrix 957 $$ 958 \begin{pmatrix} 959 0 & 1 & \ldots \\ 960 n & n + 1 & \ldots\\ 961 \vdots 962 \end{pmatrix}. 963 $$ 964 Here the (i, j) entry of the matrix gives the index of the basis 965 element $e_i \otimes e_j$. 966 967 The inverse of this isomorphism is given by `tensor_index`. 968 969 Parameters 970 ---------- 971 i : int 972 index of a basis vector in $\mathbb{R}^{n^2}$ 973 n : int 974 dimension of the underlying vector space $\mathbb{R}^n$ 975 976 Returns 977 ------- 978 tuple 979 tuple `(j, k)` determining the monomial $e_j \otimes e_k$ 980 mapped to by given the basis vector in $\mathbb{R}^{n^2}$. 981 982 """ 983 return int(i / n), i % n 984 985def tensor_index(i,j,n): 986 r"""Return coordinate indices for an isomorphism 987 $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathbb{R}^{n^2}$. 988 989 If $\{e_1, \ldots, e_n\}$ is the standard basis for 990 $\mathbb{R}^n$, the isomorphism is realized by giving 991 $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis 992 $$ 993 \{e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, \} 994 $$ 995 represented schematically by the matrix 996 $$ 997 \begin{pmatrix} 998 0 & 1 & \ldots \\ 999 n & n + 1 & \ldots\\ 1000 \vdots 1001 \end{pmatrix}. 1002 $$ 1003 Here the (i, j) entry of the matrix gives the index of the basis 1004 element $e_i \otimes e_j$. 1005 1006 The inverse of this isomorphism is given by `tensor_pos`. 1007 1008 Parameters 1009 ---------- 1010 i : int 1011 index of one of the terms in a basis vector $e_i \otimes e_j$. 1012 j : int 1013 index of the other term in a basis vector $e_i \times e_j$. 1014 n : int 1015 dimension of the underlying vector space $\mathbb{R}^n$ 1016 1017 Returns 1018 ------- 1019 int 1020 index of a basis vector in $\mathbb{R}^{n^2}$ mapped to by 1021 $e_i \otimes e_j$. 1022 """ 1023 return i * n + j 1024 1025def symmetric_inclusion(n): 1026 r"""Return a matrix representing the linear inclusion 1027 $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^n \otimes 1028 \mathbb{R}^n$. 1029 1030 $\mathrm{Sym}^2(\mathbb{R}^n)$ and 1031 $\mathbb{R}^n \otimes \mathbb{R}^n$ 1032 are respectively identified with 1033 $\mathbb{R}^{\binom{n}{2} + n}$ and $\mathbb{R}^{n^2}$ via the 1034 isomorphisms described in `sym_index`, `tensor_index`, and 1035 `tensor_pos`. 1036 1037 If $\{e_1, \ldots, e_n\}$ is the standard basis for 1038 $\mathbb{R}^n$, the returned matrix gives the linear map taking 1039 $e_ie_j$ to $\frac{1}{2}(e_i \otimes e_j + e_j \otimes e_i)$, 1040 with respect to the bases specified above. 1041 1042 Parameters 1043 ---------- 1044 n : int 1045 Dimension of the underlying vector space $\mathbb{R}^n$. 1046 1047 Returns 1048 ------- 1049 matrix : ndarray 1050 $n^2 \times \binom{n}{2} + n$ array defining this linear map. 1051 1052 """ 1053 incl_matrix = np.zeros((n * n, int(n * (n + 1) / 2))) 1054 for i in range(n): 1055 for j in range(n): 1056 si = sym_index(i, j, n) 1057 ti = tensor_index(i, j, n) 1058 incl_matrix[ti][si] = 1/2 + (i == j) * 1/2 1059 1060 return np.array(incl_matrix) 1061 1062def symmetric_projection(n): 1063 r"""Return a matrix representing the linear surjection 1064 $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathrm{Sym}^2(\mathbb{R}^n)$. 1065 1066 If $\mathbb{R}^n$ is given the standard basis $\{e_1, \ldots, 1067 e_n\}$, then this matrix represents the linear map determined by 1068 $e_i \otimes e_j \mapsto e_ie_j$. The spaces 1069 $\mathbb{R}^n \otimes \mathbb{R}^n$ and $\mathrm{Sym}^2(\mathbb{R}^n)$ 1070 are given the ordered bases determined by the functions 1071 `sym_index`, `tensor_index`, and `tensor_pos`. 1072 1073 Parameters 1074 ---------- 1075 n : int 1076 Dimension of the underlying vector space $\mathbb{R}^n$ 1077 1078 Returns 1079 ------- 1080 ndarray 1081 $\binom{n}{2} + n \times n$ matrix representing the linear map 1082 in the given bases. 1083 1084 """ 1085 proj_matrix = np.zeros((int(n * (n + 1) / 2), n * n)) 1086 for i in range(n * n): 1087 u, v = tensor_pos(i,n) 1088 proj_matrix[_sym_index(u, v, n)][i] = 1 1089 1090 return np.array(proj_matrix)
175class Representation: 176 """Model a representation for a finitely generated group 177 representation into GL(n). 178 """ 179 180 @property 181 def dim(self): 182 return self._dim 183 184 @property 185 def dtype(self): 186 return self._dtype 187 188 @property 189 def base_ring(self): 190 return self._base_ring 191 192 @staticmethod 193 def wrap_func(numpy_matrix): 194 return numpy_matrix 195 196 @staticmethod 197 def unwrap_func(wrapped_matrix): 198 return wrapped_matrix 199 200 @staticmethod 201 def array_wrap_func(numpy_array): 202 return numpy_array 203 204 def parse_word(self, word, simple=None): 205 if simple is None: 206 simple = self.parse_simple 207 208 if simple: 209 return word 210 211 return re.split("[()*]", word) 212 213 def __getitem__(self, word): 214 return self.element(word) 215 216 def element(self, word, parse_simple=True): 217 matrix = self._word_value(word, parse_simple) 218 return self.__class__.wrap_func(matrix) 219 220 def __init__(self, representation=None, 221 generator_names=None, 222 relations=[], 223 base_ring=None, 224 parse_simple=True, 225 dtype='float64'): 226 """ 227 Parameters 228 ---------- 229 representation : Representation 230 Representation to copy elements from 231 generator_names : iterable of strings 232 Names to use for the generators. These must be initialized 233 as arrays later to use the representation properly. 234 dtype : numpy dtype 235 Default data type for matrix entries. Defaults to 'float64'. 236 237 """ 238 239 self._dim = None 240 self._dtype = dtype 241 self._base_ring = base_ring 242 self.parse_simple = parse_simple 243 self.relations = [r for r in relations] 244 245 if representation is not None: 246 if base_ring is None: 247 self._base_ring = representation.base_ring 248 249 if generator_names is None: 250 generator_names = list(representation.generators) 251 252 self.generators = {} 253 254 # don't recompute inverses for an existing representation 255 for gen in generator_names: 256 self._set_generator(gen, representation.generators[gen], 257 compute_inverse=False) 258 259 self._dim = representation._dim 260 261 self.relations += [r for r in representation.relations] 262 263 else: 264 if generator_names is None: 265 generator_names = [] 266 267 self.generators = {name[0].lower():None 268 for name in words.asym_gens(generator_names)} 269 270 for gen in list(self.generators): 271 self.generators[gen.upper()] = None 272 273 def freely_reduced_elements(self, length, maxlen=True, 274 with_words=False): 275 """Return group elements, one for each freely reduced word 276 in the generators. 277 278 Parameters 279 ---------- 280 length : int 281 maximum length of a word to find a representation of 282 maxlen : bool 283 If `True` (the default), the returned array has one 284 element for each word up to length `length` 285 (inclusive). If `False`, only compute the image of words 286 of length exactly equal to `length`. 287 with_words : bool 288 If `True`, also return the list of words corresponding to 289 the computed matrices. 290 291 Returns 292 ------- 293 result : ndarray or tuple 294 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 295 where `elements` is an ndarray of shape `(k, n, n)`, 296 containing one matrix for each of the `k` freely reduced 297 words in the generators, and `words` is a python list of 298 strings, containing those words. If `with_words` is 299 `False`, just return `elements`. 300 301 """ 302 automaton = fsa.free_automaton(list(self.asym_gens())) 303 return self.automaton_accepted(automaton, length, 304 maxlen=maxlen, 305 with_words=with_words) 306 307 def free_words_of_length(self, length): 308 """Yield freely reduced words in the generators, of a specified length. 309 310 Parameters 311 ---------- 312 length : int 313 the length of words to return 314 315 Yields 316 ------ 317 word : string 318 All freely reduced words in the generators of length 319 `length`. 320 321 """ 322 323 if length == 0: 324 yield "" 325 else: 326 for word in self.free_words_of_length(length - 1): 327 for generator in self.generators: 328 if len(word) == 0 or generator != words.invert_gen(word[-1]): 329 yield word + generator 330 331 def free_words_less_than(self, length): 332 """Yield freely reduced words in the generators, up to a specified 333 length. 334 335 Parameters 336 ---------- 337 length : int 338 the maximum length of words to return 339 340 Yields 341 ------ 342 word : string 343 All freely reduced words in the generators, up to length 344 `length` (inclusive) 345 346 """ 347 for i in range(length): 348 for word in self.free_words_of_length(i): 349 yield word 350 351 def automaton_accepted(self, automaton, length, 352 maxlen=True, with_words=False, 353 start_state=None, end_state=None, 354 precomputed=None, edge_words=False): 355 """Return group elements representing words accepted by a 356 finite-state automaton. 357 358 Parameters 359 ---------- 360 automaton : automata.fsa.FSA 361 Finite-state automaton which determines the accepted words 362 length : int 363 Maximum length of a word to compute 364 maxlen : bool 365 if `True` (the default), compute representations of all 366 accepted words of length up to `length`. If `False`, only 367 compute representations of words whose length is exactly 368 `length`. 369 with_words : bool 370 Whether to return a list of accepted words along with the 371 computed array of images. 372 start_state: object 373 vertex of the automaton to use as the starting state for 374 all accepted words. If `None`, then the default start 375 vertex of the automaton is used. *Note*: at most one of 376 `start_state` and `end_state` can be specified. 377 end_state: object 378 vertex of the automaton where all accepted paths must 379 end. If `None`, then any ending state is allowed. *Note*: 380 at most one of `start_state` and `end_state` can be 381 specified. 382 precomputed: dict 383 dictionary giving precomputed values of this 384 function. Keys are tuples of then form `(length, state)`, 385 where `length` is an integer and `state` is a vertex of 386 the automaton. If `None`, use an empty dictionary. In 387 either case, the dictionary will be populated when the 388 function is called. 389 edge_words : bool 390 If True, view each label of the given automaton as a word 391 in the generators for this representation. Otherwise, 392 interpret the label as a single generator. 393 394 Returns 395 ------- 396 result : ndarray or tuple 397 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 398 where `elements` is an ndarray containing one 399 matrix for each accepted word, and `words` is a list of 400 strings containing the corresponding words. If 401 `with_words` is `False`, just return `elements`. 402 403 """ 404 405 if start_state is not None and end_state is not None: 406 raise ValueError("At most one of start_state and end_state " 407 "can be specified") 408 409 as_start = True 410 state = None 411 412 if start_state is not None: 413 state = start_state 414 as_start = True 415 416 if end_state is not None: 417 state = end_state 418 as_start = False 419 420 result = self._automaton_accepted(automaton, length, 421 maxlen=maxlen, 422 with_words=with_words, 423 state=state, 424 as_start=as_start, 425 precomputed=precomputed) 426 427 if with_words: 428 matrix_array, words = result 429 else: 430 matrix_array = result 431 432 wrapped_matrices = self.__class__.array_wrap_func(matrix_array) 433 434 if with_words: 435 return wrapped_matrices, words 436 437 return wrapped_matrices 438 439 def _automaton_accepted(self, automaton, length, 440 state=None, as_start=True, maxlen=True, 441 precomputed=None, with_words=False, 442 edge_words=False): 443 444 if precomputed is None: 445 precomputed = {} 446 447 if (length, state) in precomputed: 448 return precomputed[(length, state)] 449 450 empty_arr = np.array([]).reshape((0, self.dim, self.dim)) 451 452 if length == 0: 453 if state is None or as_start or state in automaton.start_vertices: 454 id_array = np.array([utils.identity(self.dim, 455 dtype=self.dtype, 456 base_ring=self.base_ring)]) 457 if with_words: 458 return (id_array, [""]) 459 return id_array 460 461 if with_words: 462 return (empty_arr, []) 463 return empty_arr 464 465 if state is None: 466 as_start = True 467 state = automaton.start_vertices[0] 468 469 if as_start: 470 adj_states = automaton.out_dict[state] 471 else: 472 adj_states = automaton.in_dict[state] 473 474 if len(adj_states) == 0 and not as_start: 475 if with_words: 476 return (empty_arr, []) 477 return empty_arr 478 479 480 matrix_list = [] 481 accepted_words = [] 482 for adj_state, labels in adj_states.items(): 483 for label in labels: 484 result = self._automaton_accepted( 485 automaton, length - 1, 486 state=adj_state, 487 as_start=as_start, 488 maxlen=maxlen, 489 precomputed=precomputed, 490 with_words=with_words 491 ) 492 if with_words: 493 matrices, words = result 494 if as_start: 495 words = [label + word for word in words] 496 else: 497 words = [word + label for word in words] 498 accepted_words += words 499 else: 500 matrices = result 501 502 if edge_words: 503 edge_elt = self._word_value(label) 504 else: 505 edge_elt = self.generators[label] 506 if as_start: 507 matrices = edge_elt @ matrices 508 else: 509 matrices = matrices @ edge_elt 510 matrix_list.append(matrices) 511 512 accepted_matrices = empty_arr 513 514 if len(matrix_list) > 0: 515 accepted_matrices = np.concatenate(matrix_list) 516 517 if maxlen: 518 additional_result = self._automaton_accepted( 519 automaton, 0, 520 state=state, 521 as_start=as_start, 522 with_words=with_words 523 ) 524 if with_words: 525 additional_mats, additional_words = additional_result 526 accepted_words = additional_words + accepted_words 527 else: 528 additional_mats = additional_result 529 530 accepted_matrices = np.concatenate( 531 [additional_mats, accepted_matrices] 532 ) 533 534 if with_words: 535 accepted = (accepted_matrices, accepted_words) 536 else: 537 accepted = accepted_matrices 538 539 precomputed[(length, state)] = accepted 540 return accepted 541 542 def elements(self, words): 543 """Get images of an iterable of words. 544 545 Parameters 546 ---------- 547 words : iterable of strings 548 words to find the image of under the representation 549 550 Returns 551 ------- 552 ndarray 553 numpy array of shape `(l, n, n)`, where `l` is the length 554 of `words` and `n` is the dimension of the representation. 555 556 """ 557 return self.__class__.array_wrap_func(np.array( 558 [self._word_value(word) for word in words] 559 )) 560 561 def asym_gens(self): 562 """Iterate over asymmetric group generator names for this 563 representation. 564 565 Only iterate over group generators (lowercase letters), not 566 semigroup generators (upper and lowercase letters). 567 568 Yields 569 ------ 570 gen : string 571 group generator names 572 573 """ 574 return words.asym_gens(self.generators.keys()) 575 576 def dual(self): 577 return self._compose( 578 lambda M: utils.invert(M).T 579 ) 580 581 def _word_value(self, word, parse_simple=None): 582 gen_list = self.parse_word(word, simple=parse_simple) 583 584 matrix = utils.identity(self._dim, dtype=self.dtype) 585 for gen in gen_list: 586 matrix = matrix @ self.generators[gen] 587 return matrix 588 589 def _set_generator(self, generator, matrix, compute_inverse=True, 590 base_ring=None): 591 592 shape = matrix.shape 593 594 if self._dim is None: 595 self._dim = shape[0] 596 if shape[0] != shape[1]: 597 raise ValueError("Matrices representing group elements must be square") 598 if shape[0] != self._dim: 599 raise ValueError( 600 "Every matrix in the representation must have the same shape" 601 ) 602 603 if re.search("[*()]", generator): 604 raise ValueError( 605 "The characters *, (, and ) are reserved and " 606 " cannot be used in generator names." 607 ) 608 609 if not re.search("[a-zA-Z]", generator): 610 raise ValueError( 611 "Generator names must contain at least one letter a-z or A-Z" 612 ) 613 614 if generator != generator.lower() and generator != generator.upper(): 615 raise ValueError( 616 "Generator names cannot be mixed-case" 617 ) 618 619 if base_ring is not None: 620 matrix = utils.change_base_ring(matrix, base_ring) 621 self._base_ring = base_ring 622 623 self.generators[generator] = matrix 624 if compute_inverse: 625 self.generators[words.invert_gen(generator)] = utils.invert(matrix) 626 627 # always update the dtype (we don't have a hierarchy for this) 628 self._dtype = matrix.dtype 629 630 def set_generator(self, generator, matrix, **kwargs): 631 self._set_generator(generator, 632 self.__class__.unwrap_func(matrix), 633 **kwargs) 634 635 def __setitem__(self, generator, matrix): 636 self.set_generator(generator, matrix, compute_inverse=True) 637 638 def change_base_ring(self, base_ring=None): 639 return self._compose( 640 lambda M: utils.change_base_ring(M, base_ring), 641 base_ring=base_ring 642 ) 643 644 def astype(self, dtype): 645 base_ring = self.base_ring 646 if dtype != np.dtype('O'): 647 base_ring = None 648 649 new_rep = self._compose( 650 lambda M: M.astype(dtype), 651 dtype=dtype 652 ) 653 new_rep._base_ring = base_ring 654 655 return new_rep 656 657 def conjugate(self, mat, inv_mat=None, unwrap=True, **kwargs): 658 if not unwrap: 659 return self._conjugate( 660 mat, inv_mat, **kwargs 661 ) 662 663 if inv_mat is not None: 664 inv_mat = self.__class__.unwrap_func(inv_mat) 665 666 return self._conjugate( 667 self.__class__.unwrap_func(mat), 668 inv_mat, **kwargs 669 ) 670 671 def _conjugate(self, mat, inv_mat=None, **kwargs): 672 if inv_mat is None: 673 inv_mat = utils.invert(mat, **kwargs) 674 675 return self._compose(lambda M: inv_mat @ M @ mat) 676 677 def _compose(self, hom, compute_inverses=False, dtype=None, 678 base_ring=None, **kwargs): 679 """Get a new representation obtained by composing this representation 680 with hom.""" 681 682 if dtype is None: 683 dtype = self.dtype 684 685 if base_ring is None: 686 base_ring = self.base_ring 687 688 composed_rep = self.__class__(dtype=dtype, base_ring=base_ring, 689 relations=self.relations, **kwargs) 690 691 if compute_inverses: 692 generator_iterator = self.asym_gens() 693 else: 694 generator_iterator = self.generators.keys() 695 696 for g in generator_iterator: 697 image = self.generators[g] 698 inv_image = self.generators[words.invert_gen(g)] 699 700 try: 701 composed = hom(image, inv=inv_image) 702 except TypeError: 703 composed = hom(image) 704 705 composed_rep._set_generator(g, composed, 706 compute_inverse=compute_inverses) 707 708 return composed_rep 709 710 def compose(self, hom, hom_in_wrapped=False, hom_out_wrapped=False, 711 **kwargs): 712 713 hom_1 = hom 714 if hom_in_wrapped: 715 hom_1 = (lambda M: hom(self.__class__.unwrap_func(M))) 716 717 hom_2 = hom_1 718 if hom_out_wrapped: 719 hom_2 = (lambda M: self.__class__.unwrap_func(hom_1(M))) 720 721 composed_rep = self._compose(hom_2, **kwargs) 722 723 return composed_rep 724 725 def _differential(self, word, generator=None, verbose=False): 726 if generator is not None: 727 if verbose: 728 print( 729 "computing differential of '{}' with respect to {}".format( 730 word, generator) 731 ) 732 word_diff = words.fox_word_derivative(generator, word) 733 matrix_diff = [ 734 coeff * self._word_value(word) 735 for word, coeff in word_diff.items() 736 ] 737 if len(matrix_diff) == 0: 738 return utils.zeros(self.dim, base_ring=self.base_ring) 739 740 return np.sum(matrix_diff, axis=0) 741 742 blocks = [self._differential(word, g, verbose=verbose) 743 for g in self.asym_gens()] 744 745 return np.concatenate(blocks, axis=-1) 746 747 def subgroup(self, generators, generator_names=None, relations=[], 748 compute_inverse=True): 749 subrep = self.__class__(relations=relations, 750 base_ring=self.base_ring, 751 dtype=self.dtype) 752 753 try: 754 generator_pairs = generators.items() 755 except AttributeError: 756 generator_pairs = zip(GENERATORS[:len(generators)], 757 generators) 758 759 if generator_names is not None: 760 generator_pairs = zip(generator_names, generators) 761 762 for g, word in generator_pairs: 763 subrep._set_generator(g, self._word_value(word), 764 compute_inverse=compute_inverse) 765 if not compute_inverse: 766 subrep._set_generator( 767 words.invert_gen(g), 768 self._word_value(words.formal_inverse(word)) 769 ) 770 771 return subrep 772 773 def _differentials(self, words, **kwargs): 774 blocks = [self._differential(word, **kwargs) for word in words] 775 return np.concatenate(blocks, axis=0) 776 777 def differential(self, word, **kwargs): 778 return self._differential(word, **kwargs) 779 780 def differentials(self, words, **kwargs): 781 return self._differentials(words, **kwargs) 782 783 def cocycle_matrix(self, **kwargs): 784 return self.differentials(self.relations, **kwargs) 785 786 def coboundary_matrix(self): 787 blocks = [utils.identity(self._dim, dtype=self.dtype, 788 base_ring=self.base_ring) - self.generators[gen] 789 for gen in self.asym_gens() ] 790 791 return np.concatenate(blocks, axis=0) 792 793 def gln_adjoint(self, base_ring=None, dtype=None, **kwargs): 794 if base_ring is None: 795 base_ring = self.base_ring 796 797 if dtype is None: 798 dtype = self.dtype 799 800 gln_adjoint = lie.hom.gln_adjoint( 801 base_ring=base_ring, dtype=dtype 802 ) 803 804 return self._compose(gln_adjoint, base_ring=base_ring, 805 dtype=dtype, **kwargs) 806 807 def sln_adjoint(self, base_ring=None, dtype=None, **kwargs): 808 if base_ring is None: 809 base_ring = self.base_ring 810 811 if dtype is None: 812 dtype = self.dtype 813 814 sln_adjoint = lie.hom.sln_adjoint( 815 base_ring=base_ring, dtype=dtype 816 ) 817 818 return self._compose(sln_adjoint, base_ring=base_ring, 819 dtype=dtype, **kwargs) 820 821 def tensor_product(self, rep): 822 """Return a tensor product of this representation with `rep`. 823 824 Parameters 825 ---------- 826 rep : Representation 827 Representation to tensor with. 828 829 Raises 830 ------ 831 ValueError 832 Raised if `self` and `rep` have differing generating sets. 833 834 Returns 835 ------- 836 tensor: Representation 837 Representation giving the tensor product of self with `rep`. 838 839 """ 840 if set(rep.generators) != set(self.generators): 841 raise ValueError( 842 "Cannot take a tensor product of a representation of groups with " 843 "different presentations" 844 ) 845 else: 846 product_rep = Representation() 847 for gen in self.asym_gens(): 848 tens = np.tensordot(self[gen], rep[gen], axes=0) 849 elt = np.concatenate(np.concatenate(tens, axis=1), axis=1) 850 product_rep[gen] = np.array(elt) 851 return product_rep 852 853 def symmetric_square(self): 854 """Return the symmetric square of this representation. 855 856 Returns 857 ------- 858 square : Representation 859 Symmetric square of `self`. 860 861 """ 862 863 tensor_rep = self.tensor_product(self) 864 incl = symmetric_inclusion(self._dim) 865 proj = symmetric_projection(self._dim) 866 square_rep = Representation() 867 for g in self.asym_gens(): 868 square_rep[g] = proj * tensor_rep[g] * incl 869 870 return square_rep
Model a representation for a finitely generated group representation into GL(n).
220 def __init__(self, representation=None, 221 generator_names=None, 222 relations=[], 223 base_ring=None, 224 parse_simple=True, 225 dtype='float64'): 226 """ 227 Parameters 228 ---------- 229 representation : Representation 230 Representation to copy elements from 231 generator_names : iterable of strings 232 Names to use for the generators. These must be initialized 233 as arrays later to use the representation properly. 234 dtype : numpy dtype 235 Default data type for matrix entries. Defaults to 'float64'. 236 237 """ 238 239 self._dim = None 240 self._dtype = dtype 241 self._base_ring = base_ring 242 self.parse_simple = parse_simple 243 self.relations = [r for r in relations] 244 245 if representation is not None: 246 if base_ring is None: 247 self._base_ring = representation.base_ring 248 249 if generator_names is None: 250 generator_names = list(representation.generators) 251 252 self.generators = {} 253 254 # don't recompute inverses for an existing representation 255 for gen in generator_names: 256 self._set_generator(gen, representation.generators[gen], 257 compute_inverse=False) 258 259 self._dim = representation._dim 260 261 self.relations += [r for r in representation.relations] 262 263 else: 264 if generator_names is None: 265 generator_names = [] 266 267 self.generators = {name[0].lower():None 268 for name in words.asym_gens(generator_names)} 269 270 for gen in list(self.generators): 271 self.generators[gen.upper()] = None
Parameters
- representation (Representation): Representation to copy elements from
- generator_names (iterable of strings): Names to use for the generators. These must be initialized as arrays later to use the representation properly.
- dtype (numpy dtype): Default data type for matrix entries. Defaults to 'float64'.
273 def freely_reduced_elements(self, length, maxlen=True, 274 with_words=False): 275 """Return group elements, one for each freely reduced word 276 in the generators. 277 278 Parameters 279 ---------- 280 length : int 281 maximum length of a word to find a representation of 282 maxlen : bool 283 If `True` (the default), the returned array has one 284 element for each word up to length `length` 285 (inclusive). If `False`, only compute the image of words 286 of length exactly equal to `length`. 287 with_words : bool 288 If `True`, also return the list of words corresponding to 289 the computed matrices. 290 291 Returns 292 ------- 293 result : ndarray or tuple 294 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 295 where `elements` is an ndarray of shape `(k, n, n)`, 296 containing one matrix for each of the `k` freely reduced 297 words in the generators, and `words` is a python list of 298 strings, containing those words. If `with_words` is 299 `False`, just return `elements`. 300 301 """ 302 automaton = fsa.free_automaton(list(self.asym_gens())) 303 return self.automaton_accepted(automaton, length, 304 maxlen=maxlen, 305 with_words=with_words)
Return group elements, one for each freely reduced word in the generators.
Parameters
- length (int): maximum length of a word to find a representation of
- maxlen (bool):
If
True
(the default), the returned array has one element for each word up to lengthlength
(inclusive). IfFalse
, only compute the image of words of length exactly equal tolength
. - with_words (bool):
If
True
, also return the list of words corresponding to the computed matrices.
Returns
- result (ndarray or tuple):
If
with_words
isTrue
,result
is a tuple(elements, words)
, whereelements
is an ndarray of shape(k, n, n)
, containing one matrix for each of thek
freely reduced words in the generators, andwords
is a python list of strings, containing those words. Ifwith_words
isFalse
, just returnelements
.
307 def free_words_of_length(self, length): 308 """Yield freely reduced words in the generators, of a specified length. 309 310 Parameters 311 ---------- 312 length : int 313 the length of words to return 314 315 Yields 316 ------ 317 word : string 318 All freely reduced words in the generators of length 319 `length`. 320 321 """ 322 323 if length == 0: 324 yield "" 325 else: 326 for word in self.free_words_of_length(length - 1): 327 for generator in self.generators: 328 if len(word) == 0 or generator != words.invert_gen(word[-1]): 329 yield word + generator
Yield freely reduced words in the generators, of a specified length.
Parameters
- length (int): the length of words to return
Yields
- word (string):
All freely reduced words in the generators of length
length
.
331 def free_words_less_than(self, length): 332 """Yield freely reduced words in the generators, up to a specified 333 length. 334 335 Parameters 336 ---------- 337 length : int 338 the maximum length of words to return 339 340 Yields 341 ------ 342 word : string 343 All freely reduced words in the generators, up to length 344 `length` (inclusive) 345 346 """ 347 for i in range(length): 348 for word in self.free_words_of_length(i): 349 yield word
Yield freely reduced words in the generators, up to a specified length.
Parameters
- length (int): the maximum length of words to return
Yields
- word (string):
All freely reduced words in the generators, up to length
length
(inclusive)
351 def automaton_accepted(self, automaton, length, 352 maxlen=True, with_words=False, 353 start_state=None, end_state=None, 354 precomputed=None, edge_words=False): 355 """Return group elements representing words accepted by a 356 finite-state automaton. 357 358 Parameters 359 ---------- 360 automaton : automata.fsa.FSA 361 Finite-state automaton which determines the accepted words 362 length : int 363 Maximum length of a word to compute 364 maxlen : bool 365 if `True` (the default), compute representations of all 366 accepted words of length up to `length`. If `False`, only 367 compute representations of words whose length is exactly 368 `length`. 369 with_words : bool 370 Whether to return a list of accepted words along with the 371 computed array of images. 372 start_state: object 373 vertex of the automaton to use as the starting state for 374 all accepted words. If `None`, then the default start 375 vertex of the automaton is used. *Note*: at most one of 376 `start_state` and `end_state` can be specified. 377 end_state: object 378 vertex of the automaton where all accepted paths must 379 end. If `None`, then any ending state is allowed. *Note*: 380 at most one of `start_state` and `end_state` can be 381 specified. 382 precomputed: dict 383 dictionary giving precomputed values of this 384 function. Keys are tuples of then form `(length, state)`, 385 where `length` is an integer and `state` is a vertex of 386 the automaton. If `None`, use an empty dictionary. In 387 either case, the dictionary will be populated when the 388 function is called. 389 edge_words : bool 390 If True, view each label of the given automaton as a word 391 in the generators for this representation. Otherwise, 392 interpret the label as a single generator. 393 394 Returns 395 ------- 396 result : ndarray or tuple 397 If `with_words` is `True`, `result` is a tuple `(elements, words)`, 398 where `elements` is an ndarray containing one 399 matrix for each accepted word, and `words` is a list of 400 strings containing the corresponding words. If 401 `with_words` is `False`, just return `elements`. 402 403 """ 404 405 if start_state is not None and end_state is not None: 406 raise ValueError("At most one of start_state and end_state " 407 "can be specified") 408 409 as_start = True 410 state = None 411 412 if start_state is not None: 413 state = start_state 414 as_start = True 415 416 if end_state is not None: 417 state = end_state 418 as_start = False 419 420 result = self._automaton_accepted(automaton, length, 421 maxlen=maxlen, 422 with_words=with_words, 423 state=state, 424 as_start=as_start, 425 precomputed=precomputed) 426 427 if with_words: 428 matrix_array, words = result 429 else: 430 matrix_array = result 431 432 wrapped_matrices = self.__class__.array_wrap_func(matrix_array) 433 434 if with_words: 435 return wrapped_matrices, words 436 437 return wrapped_matrices
Return group elements representing words accepted by a finite-state automaton.
Parameters
- automaton (automata.fsa.FSA): Finite-state automaton which determines the accepted words
- length (int): Maximum length of a word to compute
- maxlen (bool):
if
True
(the default), compute representations of all accepted words of length up tolength
. IfFalse
, only compute representations of words whose length is exactlylength
. - with_words (bool): Whether to return a list of accepted words along with the computed array of images.
- start_state (object):
vertex of the automaton to use as the starting state for
all accepted words. If
None
, then the default start vertex of the automaton is used. Note: at most one ofstart_state
andend_state
can be specified. - end_state (object):
vertex of the automaton where all accepted paths must
end. If
None
, then any ending state is allowed. Note: at most one ofstart_state
andend_state
can be specified. - precomputed (dict):
dictionary giving precomputed values of this
function. Keys are tuples of then form
(length, state)
, wherelength
is an integer andstate
is a vertex of the automaton. IfNone
, use an empty dictionary. In either case, the dictionary will be populated when the function is called. - edge_words (bool): If True, view each label of the given automaton as a word in the generators for this representation. Otherwise, interpret the label as a single generator.
Returns
542 def elements(self, words): 543 """Get images of an iterable of words. 544 545 Parameters 546 ---------- 547 words : iterable of strings 548 words to find the image of under the representation 549 550 Returns 551 ------- 552 ndarray 553 numpy array of shape `(l, n, n)`, where `l` is the length 554 of `words` and `n` is the dimension of the representation. 555 556 """ 557 return self.__class__.array_wrap_func(np.array( 558 [self._word_value(word) for word in words] 559 ))
Get images of an iterable of words.
Parameters
- words (iterable of strings): words to find the image of under the representation
Returns
- ndarray: numpy array of shape
(l, n, n)
, wherel
is the length ofwords
andn
is the dimension of the representation.
561 def asym_gens(self): 562 """Iterate over asymmetric group generator names for this 563 representation. 564 565 Only iterate over group generators (lowercase letters), not 566 semigroup generators (upper and lowercase letters). 567 568 Yields 569 ------ 570 gen : string 571 group generator names 572 573 """ 574 return words.asym_gens(self.generators.keys())
Iterate over asymmetric group generator names for this representation.
Only iterate over group generators (lowercase letters), not semigroup generators (upper and lowercase letters).
Yields
- gen (string): group generator names
657 def conjugate(self, mat, inv_mat=None, unwrap=True, **kwargs): 658 if not unwrap: 659 return self._conjugate( 660 mat, inv_mat, **kwargs 661 ) 662 663 if inv_mat is not None: 664 inv_mat = self.__class__.unwrap_func(inv_mat) 665 666 return self._conjugate( 667 self.__class__.unwrap_func(mat), 668 inv_mat, **kwargs 669 )
710 def compose(self, hom, hom_in_wrapped=False, hom_out_wrapped=False, 711 **kwargs): 712 713 hom_1 = hom 714 if hom_in_wrapped: 715 hom_1 = (lambda M: hom(self.__class__.unwrap_func(M))) 716 717 hom_2 = hom_1 718 if hom_out_wrapped: 719 hom_2 = (lambda M: self.__class__.unwrap_func(hom_1(M))) 720 721 composed_rep = self._compose(hom_2, **kwargs) 722 723 return composed_rep
747 def subgroup(self, generators, generator_names=None, relations=[], 748 compute_inverse=True): 749 subrep = self.__class__(relations=relations, 750 base_ring=self.base_ring, 751 dtype=self.dtype) 752 753 try: 754 generator_pairs = generators.items() 755 except AttributeError: 756 generator_pairs = zip(GENERATORS[:len(generators)], 757 generators) 758 759 if generator_names is not None: 760 generator_pairs = zip(generator_names, generators) 761 762 for g, word in generator_pairs: 763 subrep._set_generator(g, self._word_value(word), 764 compute_inverse=compute_inverse) 765 if not compute_inverse: 766 subrep._set_generator( 767 words.invert_gen(g), 768 self._word_value(words.formal_inverse(word)) 769 ) 770 771 return subrep
793 def gln_adjoint(self, base_ring=None, dtype=None, **kwargs): 794 if base_ring is None: 795 base_ring = self.base_ring 796 797 if dtype is None: 798 dtype = self.dtype 799 800 gln_adjoint = lie.hom.gln_adjoint( 801 base_ring=base_ring, dtype=dtype 802 ) 803 804 return self._compose(gln_adjoint, base_ring=base_ring, 805 dtype=dtype, **kwargs)
807 def sln_adjoint(self, base_ring=None, dtype=None, **kwargs): 808 if base_ring is None: 809 base_ring = self.base_ring 810 811 if dtype is None: 812 dtype = self.dtype 813 814 sln_adjoint = lie.hom.sln_adjoint( 815 base_ring=base_ring, dtype=dtype 816 ) 817 818 return self._compose(sln_adjoint, base_ring=base_ring, 819 dtype=dtype, **kwargs)
821 def tensor_product(self, rep): 822 """Return a tensor product of this representation with `rep`. 823 824 Parameters 825 ---------- 826 rep : Representation 827 Representation to tensor with. 828 829 Raises 830 ------ 831 ValueError 832 Raised if `self` and `rep` have differing generating sets. 833 834 Returns 835 ------- 836 tensor: Representation 837 Representation giving the tensor product of self with `rep`. 838 839 """ 840 if set(rep.generators) != set(self.generators): 841 raise ValueError( 842 "Cannot take a tensor product of a representation of groups with " 843 "different presentations" 844 ) 845 else: 846 product_rep = Representation() 847 for gen in self.asym_gens(): 848 tens = np.tensordot(self[gen], rep[gen], axes=0) 849 elt = np.concatenate(np.concatenate(tens, axis=1), axis=1) 850 product_rep[gen] = np.array(elt) 851 return product_rep
Return a tensor product of this representation with rep
.
Parameters
- rep (Representation): Representation to tensor with.
Raises
- ValueError: Raised if
self
andrep
have differing generating sets.
Returns
- tensor (Representation):
Representation giving the tensor product of self with
rep
.
853 def symmetric_square(self): 854 """Return the symmetric square of this representation. 855 856 Returns 857 ------- 858 square : Representation 859 Symmetric square of `self`. 860 861 """ 862 863 tensor_rep = self.tensor_product(self) 864 incl = symmetric_inclusion(self._dim) 865 proj = symmetric_projection(self._dim) 866 square_rep = Representation() 867 for g in self.asym_gens(): 868 square_rep[g] = proj * tensor_rep[g] * incl 869 870 return square_rep
Return the symmetric square of this representation.
Returns
- square (Representation):
Symmetric square of
self
.
872class SageMatrixRepresentation(Representation): 873 @staticmethod 874 def wrap_func(numpy_matrix): 875 return sagewrap.sage_matrix(numpy_matrix) 876 877 @staticmethod 878 def unwrap_func(sage_matrix): 879 return np.array(sage_matrix) 880 881 @staticmethod 882 def array_wrap_func(numpy_array): 883 return sagewrap.sage_matrix_list(numpy_array) 884 885 def differential(self, word, **kwargs): 886 return sagewrap.sage_matrix(self._differential(word, **kwargs)) 887 888 def differentials(self, words, **kwargs): 889 return sagewrap.sage_matrix(self._differentials(words, **kwargs)) 890 891 def coboundary_matrix(self): 892 return sagewrap.sage_matrix( 893 Representation.coboundary_matrix(self) 894 ) 895 896 def compose(self, hom, hom_in_wrapped=True, 897 hom_out_wrapped=False, **kwargs): 898 return Representation.compose(self, hom, 899 hom_in_wrapped=hom_in_wrapped, 900 hom_out_wrapped=hom_out_wrapped, 901 **kwargs)
Model a representation for a finitely generated group representation into GL(n).
Inherited Members
- Representation
- Representation
- dim
- dtype
- base_ring
- parse_word
- element
- parse_simple
- relations
- freely_reduced_elements
- free_words_of_length
- free_words_less_than
- automaton_accepted
- elements
- asym_gens
- dual
- set_generator
- change_base_ring
- astype
- conjugate
- subgroup
- cocycle_matrix
- gln_adjoint
- sln_adjoint
- tensor_product
- symmetric_square
903def sym_index(i, j, n): 904 r"""Return coordinate indices for an isomorphism 905 $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^{\binom{n}{2} + n}$. 906 907 If $\{e_1, \ldots, e_n\}$ is the standard basis for $\mathbb{R}^n$, 908 the isomorphism is realized by giving $\mathrm{Sym}^2(\mathbb{R}^n)$ 909 the ordered basis 910 $$ 911 \{e_ne_n, e_{n-1}e_{n-1}, e_{n-1}e_n, 912 e_{n-1}e_{n-1}, e_{n-1}e_{n-2}, e_{n-1}e_{n-3}, \ldots \}. 913 $$ 914 Schematically this is given by the symmetric matrix 915 916 $$\begin{pmatrix} \ddots \\ 917 & 3 & 4 & 5 \\ 918 & & 1 & 2 \\ 919 & & & 0 920 \end{pmatrix}, 921 $$ 922 where the (i,j) entry of the matrix gives the index of basis 923 element $e_ie_j$. 924 925 Parameters 926 ---------- 927 i : int 928 index of one of the terms in the basis monomial $e_ie_j$ for the 929 symmetric square 930 j : int 931 index of the other term in the basis monomial $e_ie_j$ for the 932 symmetric square 933 n : int 934 dimension of the underlying vector space $\mathbb{R}^n$. 935 936 Returns 937 ------- 938 int 939 index of the corresponding basis vector in 940 $\mathbb{R}^{\binom{n}{2} + n}$. 941 942 """ 943 if i > j: 944 i, j = j, i 945 return int((n - i) * (n - i - 1) / 2 + (j - i))
Return coordinate indices for an isomorphism $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^{\binom{n}{2} + n}$.
If ${e_1, \ldots, e_n}$ is the standard basis for $\mathbb{R}^n$, the isomorphism is realized by giving $\mathrm{Sym}^2(\mathbb{R}^n)$ the ordered basis $$ {e_ne_n, e_{n-1}e_{n-1}, e_{n-1}e_n, e_{n-1}e_{n-1}, e_{n-1}e_{n-2}, e_{n-1}e_{n-3}, \ldots }. $$ Schematically this is given by the symmetric matrix
$$\begin{pmatrix} \ddots \ & 3 & 4 & 5 \ & & 1 & 2 \ & & & 0 \end{pmatrix}, $$ where the (i,j) entry of the matrix gives the index of basis element $e_ie_j$.
Parameters
- i (int): index of one of the terms in the basis monomial $e_ie_j$ for the symmetric square
- j (int): index of the other term in the basis monomial $e_ie_j$ for the symmetric square
- n (int): dimension of the underlying vector space $\mathbb{R}^n$.
Returns
- int: index of the corresponding basis vector in $\mathbb{R}^{\binom{n}{2} + n}$.
947def tensor_pos(i, n): 948 r"""Return coordinate indices for an isomorphism 949 $\mathbb{R}^{n^2} \to \mathbb{R}^n \otimes \mathbb{R}^n$. 950 951 If $\{e_1, \ldots, e_n\}$ is the standard basis for 952 $\mathbb{R}^n$, the isomorphism is realized by giving 953 $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis 954 $$ 955 \{e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, \} 956 $$ 957 represented schematically by the matrix 958 $$ 959 \begin{pmatrix} 960 0 & 1 & \ldots \\ 961 n & n + 1 & \ldots\\ 962 \vdots 963 \end{pmatrix}. 964 $$ 965 Here the (i, j) entry of the matrix gives the index of the basis 966 element $e_i \otimes e_j$. 967 968 The inverse of this isomorphism is given by `tensor_index`. 969 970 Parameters 971 ---------- 972 i : int 973 index of a basis vector in $\mathbb{R}^{n^2}$ 974 n : int 975 dimension of the underlying vector space $\mathbb{R}^n$ 976 977 Returns 978 ------- 979 tuple 980 tuple `(j, k)` determining the monomial $e_j \otimes e_k$ 981 mapped to by given the basis vector in $\mathbb{R}^{n^2}$. 982 983 """ 984 return int(i / n), i % n
Return coordinate indices for an isomorphism $\mathbb{R}^{n^2} \to \mathbb{R}^n \otimes \mathbb{R}^n$.
If ${e_1, \ldots, e_n}$ is the standard basis for $\mathbb{R}^n$, the isomorphism is realized by giving $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis $$ {e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, } $$ represented schematically by the matrix $$ \begin{pmatrix} 0 & 1 & \ldots \ n & n + 1 & \ldots\ \vdots \end{pmatrix}. $$ Here the (i, j) entry of the matrix gives the index of the basis element $e_i \otimes e_j$.
The inverse of this isomorphism is given by tensor_index
.
Parameters
- i (int): index of a basis vector in $\mathbb{R}^{n^2}$
- n (int): dimension of the underlying vector space $\mathbb{R}^n$
Returns
- tuple: tuple
(j, k)
determining the monomial $e_j \otimes e_k$ mapped to by given the basis vector in $\mathbb{R}^{n^2}$.
986def tensor_index(i,j,n): 987 r"""Return coordinate indices for an isomorphism 988 $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathbb{R}^{n^2}$. 989 990 If $\{e_1, \ldots, e_n\}$ is the standard basis for 991 $\mathbb{R}^n$, the isomorphism is realized by giving 992 $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis 993 $$ 994 \{e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, \} 995 $$ 996 represented schematically by the matrix 997 $$ 998 \begin{pmatrix} 999 0 & 1 & \ldots \\ 1000 n & n + 1 & \ldots\\ 1001 \vdots 1002 \end{pmatrix}. 1003 $$ 1004 Here the (i, j) entry of the matrix gives the index of the basis 1005 element $e_i \otimes e_j$. 1006 1007 The inverse of this isomorphism is given by `tensor_pos`. 1008 1009 Parameters 1010 ---------- 1011 i : int 1012 index of one of the terms in a basis vector $e_i \otimes e_j$. 1013 j : int 1014 index of the other term in a basis vector $e_i \times e_j$. 1015 n : int 1016 dimension of the underlying vector space $\mathbb{R}^n$ 1017 1018 Returns 1019 ------- 1020 int 1021 index of a basis vector in $\mathbb{R}^{n^2}$ mapped to by 1022 $e_i \otimes e_j$. 1023 """ 1024 return i * n + j
Return coordinate indices for an isomorphism $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathbb{R}^{n^2}$.
If ${e_1, \ldots, e_n}$ is the standard basis for $\mathbb{R}^n$, the isomorphism is realized by giving $\mathbb{R}^n \otimes \mathbb{R}^n$ the ordered basis $$ {e_1 \otimes e_1, e_1 \otimes e_2, \ldots, e_1 \otimes e_n, e_2 \otimes e_1, \ldots, } $$ represented schematically by the matrix $$ \begin{pmatrix} 0 & 1 & \ldots \ n & n + 1 & \ldots\ \vdots \end{pmatrix}. $$ Here the (i, j) entry of the matrix gives the index of the basis element $e_i \otimes e_j$.
The inverse of this isomorphism is given by tensor_pos
.
Parameters
- i (int): index of one of the terms in a basis vector $e_i \otimes e_j$.
- j (int): index of the other term in a basis vector $e_i \times e_j$.
- n (int): dimension of the underlying vector space $\mathbb{R}^n$
Returns
- int: index of a basis vector in $\mathbb{R}^{n^2}$ mapped to by $e_i \otimes e_j$.
1026def symmetric_inclusion(n): 1027 r"""Return a matrix representing the linear inclusion 1028 $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^n \otimes 1029 \mathbb{R}^n$. 1030 1031 $\mathrm{Sym}^2(\mathbb{R}^n)$ and 1032 $\mathbb{R}^n \otimes \mathbb{R}^n$ 1033 are respectively identified with 1034 $\mathbb{R}^{\binom{n}{2} + n}$ and $\mathbb{R}^{n^2}$ via the 1035 isomorphisms described in `sym_index`, `tensor_index`, and 1036 `tensor_pos`. 1037 1038 If $\{e_1, \ldots, e_n\}$ is the standard basis for 1039 $\mathbb{R}^n$, the returned matrix gives the linear map taking 1040 $e_ie_j$ to $\frac{1}{2}(e_i \otimes e_j + e_j \otimes e_i)$, 1041 with respect to the bases specified above. 1042 1043 Parameters 1044 ---------- 1045 n : int 1046 Dimension of the underlying vector space $\mathbb{R}^n$. 1047 1048 Returns 1049 ------- 1050 matrix : ndarray 1051 $n^2 \times \binom{n}{2} + n$ array defining this linear map. 1052 1053 """ 1054 incl_matrix = np.zeros((n * n, int(n * (n + 1) / 2))) 1055 for i in range(n): 1056 for j in range(n): 1057 si = sym_index(i, j, n) 1058 ti = tensor_index(i, j, n) 1059 incl_matrix[ti][si] = 1/2 + (i == j) * 1/2 1060 1061 return np.array(incl_matrix)
Return a matrix representing the linear inclusion $\mathrm{Sym}^2(\mathbb{R}^n) \to \mathbb{R}^n \otimes \mathbb{R}^n$.
$\mathrm{Sym}^2(\mathbb{R}^n)$ and
$\mathbb{R}^n \otimes \mathbb{R}^n$
are respectively identified with
$\mathbb{R}^{\binom{n}{2} + n}$ and $\mathbb{R}^{n^2}$ via the
isomorphisms described in sym_index
, tensor_index
, and
tensor_pos
.
If ${e_1, \ldots, e_n}$ is the standard basis for $\mathbb{R}^n$, the returned matrix gives the linear map taking $e_ie_j$ to $\frac{1}{2}(e_i \otimes e_j + e_j \otimes e_i)$, with respect to the bases specified above.
Parameters
- n (int): Dimension of the underlying vector space $\mathbb{R}^n$.
Returns
- matrix (ndarray): $n^2 \times \binom{n}{2} + n$ array defining this linear map.
1063def symmetric_projection(n): 1064 r"""Return a matrix representing the linear surjection 1065 $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathrm{Sym}^2(\mathbb{R}^n)$. 1066 1067 If $\mathbb{R}^n$ is given the standard basis $\{e_1, \ldots, 1068 e_n\}$, then this matrix represents the linear map determined by 1069 $e_i \otimes e_j \mapsto e_ie_j$. The spaces 1070 $\mathbb{R}^n \otimes \mathbb{R}^n$ and $\mathrm{Sym}^2(\mathbb{R}^n)$ 1071 are given the ordered bases determined by the functions 1072 `sym_index`, `tensor_index`, and `tensor_pos`. 1073 1074 Parameters 1075 ---------- 1076 n : int 1077 Dimension of the underlying vector space $\mathbb{R}^n$ 1078 1079 Returns 1080 ------- 1081 ndarray 1082 $\binom{n}{2} + n \times n$ matrix representing the linear map 1083 in the given bases. 1084 1085 """ 1086 proj_matrix = np.zeros((int(n * (n + 1) / 2), n * n)) 1087 for i in range(n * n): 1088 u, v = tensor_pos(i,n) 1089 proj_matrix[_sym_index(u, v, n)][i] = 1 1090 1091 return np.array(proj_matrix)
Return a matrix representing the linear surjection $\mathbb{R}^n \otimes \mathbb{R}^n \to \mathrm{Sym}^2(\mathbb{R}^n)$.
If $\mathbb{R}^n$ is given the standard basis ${e_1, \ldots,
e_n}$, then this matrix represents the linear map determined by
$e_i \otimes e_j \mapsto e_ie_j$. The spaces
$\mathbb{R}^n \otimes \mathbb{R}^n$ and $\mathrm{Sym}^2(\mathbb{R}^n)$
are given the ordered bases determined by the functions
sym_index
, tensor_index
, and tensor_pos
.
Parameters
- n (int): Dimension of the underlying vector space $\mathbb{R}^n$
Returns
- ndarray: $\binom{n}{2} + n \times n$ matrix representing the linear map in the given bases.