Source code for curver.kernel.permutation


''' A module for representing permutations in Sym(N). '''

from bisect import bisect
from itertools import combinations
from math import factorial
import numpy as np

[docs]class Permutation: ''' This represents a permutation on 0, 1, ..., N-1. ''' def __init__(self, perm): self.perm = perm assert set(self) == set(range(len(self))) def __str__(self): return str(self.perm) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.perm) def __getitem__(self, item): return self.perm[item] def __call__(self, item): return self[item] def __iter__(self): return iter(self.perm) def __len__(self): return len(self.perm) def __eq__(self, other): if isinstance(other, Permutation): if len(self) != len(other): raise ValueError('Cannot compare permutations defined over different number of elements') return self.perm == other.perm else: return NotImplemented def __hash__(self): return hash(tuple(self.perm))
[docs] def inverse(self): ''' Return the inverse of this permutation. ''' return Permutation(sorted(range(len(self)), key=self))
def __invert__(self): return self.inverse()
[docs] @classmethod def from_dict(cls, dictionary, ordering=None): ''' Return a Permutation from a dictionary. The order of the elements within the dictionary may also be specified. ''' if ordering is None: ordering = list(dictionary) index_lookup = dict((item, index) for index, item in enumerate(ordering)) return cls([index_lookup[dictionary[item]] for item in ordering])
[docs] def order(self): ''' Return the order of this permutation. ''' identity = Permutation(list(range(len(self)))) power = self i = 1 while True: if power == identity: return i i += 1 power = power * self raise RuntimeError('Should not be able to reach here')
def __mul__(self, other): if isinstance(other, Permutation): if len(self) != len(other): raise ValueError('Cannot compose permutations defined over different number of elements') return Permutation([self(other(i)) for i in range(len(self))]) else: return NotImplemented def __pow__(self, n): if n < 0: return (~self)**(-n) perm = self result = Permutation(list(range(len(self)))) while n: if n % 2 == 1: result = result * perm n = n - 1 perm = perm * perm n = n // 2 return result
[docs] @classmethod def from_index(cls, N, index): ''' Return the permutation in Sym(N) with the given index. ''' P = [] f = factorial(N) symbols = list(range(N)) while symbols: f = f // len(symbols) i, index = divmod(index, f) P.append(symbols[i]) symbols = symbols[:i] + symbols[i+1:] return cls(P)
[docs] def index(self): ''' Return the index of this permutation in the (sorted) list of all permutations on this many symbols. ''' symbols = sorted(self.perm) index = 0 for p in self: i = bisect(symbols, p) - 1 index = index * len(symbols) + i symbols = symbols[:i] + symbols[i+1:] return index
[docs] def matrix(self): ''' Return the corresponding permutation matrix. That is, a matrix M such that M * e_i == e_{self[i]}. ''' return np.array([[1 if i == j else 0 for j in self] for i in range(len(self))], dtype=object)
[docs] def is_even(self): ''' Return whether this permutation is the composition of an even number of transposiions. ''' return sum(1 if a > b else 0 for a, b in combinations(self, r=2) if a > b) % 2 == 0
[docs] def cycle_lengths(self): ''' Return the sorted list of cycle lengths of this Permutation. This is a total conjugacy invariant. ''' N = len(self) cycle_lengths = [] seen = set() for i in range(N): if i not in seen: image = self(i) seen.add(image) for j in range(1, N+1): if image == i: cycle_lengths.append(j) break image = self(image) seen.add(image) return sorted(cycle_lengths)
[docs] def is_conjugate_to(self, other): ''' Return whether this permutation in conjugate to other. Two permutations are conjugate iff they have the same cycle lengths. ''' assert isinstance(other, Permutation) if len(self) != len(other): return False return self.cycle_lengths() == other.cycle_lengths()