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)
class Representation:
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).

Representation( representation=None, generator_names=None, relations=[], base_ring=None, parse_simple=True, dtype='float64')
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'.
dim
180    @property
181    def dim(self):
182        return self._dim
dtype
184    @property
185    def dtype(self):
186        return self._dtype
base_ring
188    @property
189    def base_ring(self):
190        return self._base_ring
@staticmethod
def wrap_func(numpy_matrix):
192    @staticmethod
193    def wrap_func(numpy_matrix):
194        return numpy_matrix
@staticmethod
def unwrap_func(wrapped_matrix):
196    @staticmethod
197    def unwrap_func(wrapped_matrix):
198        return wrapped_matrix
@staticmethod
def array_wrap_func(numpy_array):
200    @staticmethod
201    def array_wrap_func(numpy_array):
202        return numpy_array
def parse_word(self, word, simple=None):
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)
def element(self, word, parse_simple=True):
216    def element(self, word, parse_simple=True):
217        matrix = self._word_value(word, parse_simple)
218        return self.__class__.wrap_func(matrix)
parse_simple
relations
def freely_reduced_elements(self, length, maxlen=True, with_words=False):
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 length length (inclusive). If False, only compute the image of words of length exactly equal to length.
  • with_words (bool): If True, also return the list of words corresponding to the computed matrices.
Returns
  • result (ndarray or tuple): If with_words is True, result is a tuple (elements, words), where elements is an ndarray of shape (k, n, n), containing one matrix for each of the k freely reduced words in the generators, and words is a python list of strings, containing those words. If with_words is False, just return elements.
def free_words_of_length(self, length):
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.
def free_words_less_than(self, 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)
def automaton_accepted( self, automaton, length, maxlen=True, with_words=False, start_state=None, end_state=None, precomputed=None, edge_words=False):
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 to length. If False, only compute representations of words whose length is exactly length.
  • 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 of start_state and end_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 of start_state and end_state can be specified.
  • precomputed (dict): dictionary giving precomputed values of this function. Keys are tuples of then form (length, state), where length is an integer and state is a vertex of the automaton. If None, 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
  • result (ndarray or tuple): If with_words is True, result is a tuple (elements, words), where elements is an ndarray containing one matrix for each accepted word, and words is a list of strings containing the corresponding words. If with_words is False, just return elements.
def elements(self, words):
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), where l is the length of words and n is the dimension of the representation.
def asym_gens(self):
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
def dual(self):
576    def dual(self):
577        return self._compose(
578            lambda M: utils.invert(M).T
579        )
def set_generator(self, generator, matrix, **kwargs):
630    def set_generator(self, generator, matrix, **kwargs):
631        self._set_generator(generator,
632            self.__class__.unwrap_func(matrix),
633            **kwargs)
def change_base_ring(self, base_ring=None):
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        )
def astype(self, dtype):
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
def conjugate(self, mat, inv_mat=None, unwrap=True, **kwargs):
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        )
def compose(self, hom, hom_in_wrapped=False, hom_out_wrapped=False, **kwargs):
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
def subgroup( self, generators, generator_names=None, relations=[], compute_inverse=True):
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
def differential(self, word, **kwargs):
777    def differential(self, word, **kwargs):
778        return self._differential(word, **kwargs)
def differentials(self, words, **kwargs):
780    def differentials(self, words, **kwargs):
781        return self._differentials(words, **kwargs)
def cocycle_matrix(self, **kwargs):
783    def cocycle_matrix(self, **kwargs):
784        return self.differentials(self.relations, **kwargs)
def coboundary_matrix(self):
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)
def gln_adjoint(self, base_ring=None, dtype=None, **kwargs):
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)
def sln_adjoint(self, base_ring=None, dtype=None, **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)
def tensor_product(self, rep):
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 and rep have differing generating sets.
Returns
  • tensor (Representation): Representation giving the tensor product of self with rep.
def symmetric_square(self):
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.
class SageMatrixRepresentation(Representation):
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).

@staticmethod
def wrap_func(numpy_matrix):
873    @staticmethod
874    def wrap_func(numpy_matrix):
875        return sagewrap.sage_matrix(numpy_matrix)
@staticmethod
def unwrap_func(sage_matrix):
877    @staticmethod
878    def unwrap_func(sage_matrix):
879        return np.array(sage_matrix)
@staticmethod
def array_wrap_func(numpy_array):
881    @staticmethod
882    def array_wrap_func(numpy_array):
883        return sagewrap.sage_matrix_list(numpy_array)
def differential(self, word, **kwargs):
885    def differential(self, word, **kwargs):
886        return sagewrap.sage_matrix(self._differential(word, **kwargs))
def differentials(self, words, **kwargs):
888    def differentials(self, words, **kwargs):
889        return sagewrap.sage_matrix(self._differentials(words, **kwargs))
def coboundary_matrix(self):
891    def coboundary_matrix(self):
892        return sagewrap.sage_matrix(
893            Representation.coboundary_matrix(self)
894        )
def compose(self, hom, hom_in_wrapped=True, hom_out_wrapped=False, **kwargs):
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)
def sym_index(i, j, n):
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}$.
def tensor_pos(i, 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}$.
def tensor_index(i, j, n):
 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$.
def symmetric_inclusion(n):
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.
def symmetric_projection(n):
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.