1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255 |
- from sympy.combinatorics.free_groups import free_group
- from sympy.printing.defaults import DefaultPrinting
- from itertools import chain, product
- from bisect import bisect_left
- ###############################################################################
- # COSET TABLE #
- ###############################################################################
- class CosetTable(DefaultPrinting):
- # coset_table: Mathematically a coset table
- # represented using a list of lists
- # alpha: Mathematically a coset (precisely, a live coset)
- # represented by an integer between i with 1 <= i <= n
- # alpha in c
- # x: Mathematically an element of "A" (set of generators and
- # their inverses), represented using "FpGroupElement"
- # fp_grp: Finitely Presented Group with < X|R > as presentation.
- # H: subgroup of fp_grp.
- # NOTE: We start with H as being only a list of words in generators
- # of "fp_grp". Since `.subgroup` method has not been implemented.
- r"""
- Properties
- ==========
- [1] `0 \in \Omega` and `\tau(1) = \epsilon`
- [2] `\alpha^x = \beta \Leftrightarrow \beta^{x^{-1}} = \alpha`
- [3] If `\alpha^x = \beta`, then `H \tau(\alpha)x = H \tau(\beta)`
- [4] `\forall \alpha \in \Omega, 1^{\tau(\alpha)} = \alpha`
- References
- ==========
- .. [1] Holt, D., Eick, B., O'Brien, E.
- "Handbook of Computational Group Theory"
- .. [2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
- Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490.
- "Implementation and Analysis of the Todd-Coxeter Algorithm"
- """
- # default limit for the number of cosets allowed in a
- # coset enumeration.
- coset_table_max_limit = 4096000
- # limit for the current instance
- coset_table_limit = None
- # maximum size of deduction stack above or equal to
- # which it is emptied
- max_stack_size = 100
- def __init__(self, fp_grp, subgroup, max_cosets=None):
- if not max_cosets:
- max_cosets = CosetTable.coset_table_max_limit
- self.fp_group = fp_grp
- self.subgroup = subgroup
- self.coset_table_limit = max_cosets
- # "p" is setup independent of Omega and n
- self.p = [0]
- # a list of the form `[gen_1, gen_1^{-1}, ... , gen_k, gen_k^{-1}]`
- self.A = list(chain.from_iterable((gen, gen**-1) \
- for gen in self.fp_group.generators))
- #P[alpha, x] Only defined when alpha^x is defined.
- self.P = [[None]*len(self.A)]
- # the mathematical coset table which is a list of lists
- self.table = [[None]*len(self.A)]
- self.A_dict = {x: self.A.index(x) for x in self.A}
- self.A_dict_inv = {}
- for x, index in self.A_dict.items():
- if index % 2 == 0:
- self.A_dict_inv[x] = self.A_dict[x] + 1
- else:
- self.A_dict_inv[x] = self.A_dict[x] - 1
- # used in the coset-table based method of coset enumeration. Each of
- # the element is called a "deduction" which is the form (alpha, x) whenever
- # a value is assigned to alpha^x during a definition or "deduction process"
- self.deduction_stack = []
- # Attributes for modified methods.
- H = self.subgroup
- self._grp = free_group(', ' .join(["a_%d" % i for i in range(len(H))]))[0]
- self.P = [[None]*len(self.A)]
- self.p_p = {}
- @property
- def omega(self):
- """Set of live cosets. """
- return [coset for coset in range(len(self.p)) if self.p[coset] == coset]
- def copy(self):
- """
- Return a shallow copy of Coset Table instance ``self``.
- """
- self_copy = self.__class__(self.fp_group, self.subgroup)
- self_copy.table = [list(perm_rep) for perm_rep in self.table]
- self_copy.p = list(self.p)
- self_copy.deduction_stack = list(self.deduction_stack)
- return self_copy
- def __str__(self):
- return "Coset Table on %s with %s as subgroup generators" \
- % (self.fp_group, self.subgroup)
- __repr__ = __str__
- @property
- def n(self):
- """The number `n` represents the length of the sublist containing the
- live cosets.
- """
- if not self.table:
- return 0
- return max(self.omega) + 1
- # Pg. 152 [1]
- def is_complete(self):
- r"""
- The coset table is called complete if it has no undefined entries
- on the live cosets; that is, `\alpha^x` is defined for all
- `\alpha \in \Omega` and `x \in A`.
- """
- return not any(None in self.table[coset] for coset in self.omega)
- # Pg. 153 [1]
- def define(self, alpha, x, modified=False):
- r"""
- This routine is used in the relator-based strategy of Todd-Coxeter
- algorithm if some `\alpha^x` is undefined. We check whether there is
- space available for defining a new coset. If there is enough space
- then we remedy this by adjoining a new coset `\beta` to `\Omega`
- (i.e to set of live cosets) and put that equal to `\alpha^x`, then
- make an assignment satisfying Property[1]. If there is not enough space
- then we halt the Coset Table creation. The maximum amount of space that
- can be used by Coset Table can be manipulated using the class variable
- ``CosetTable.coset_table_max_limit``.
- See Also
- ========
- define_c
- """
- A = self.A
- table = self.table
- len_table = len(table)
- if len_table >= self.coset_table_limit:
- # abort the further generation of cosets
- raise ValueError("the coset enumeration has defined more than "
- "%s cosets. Try with a greater value max number of cosets "
- % self.coset_table_limit)
- table.append([None]*len(A))
- self.P.append([None]*len(self.A))
- # beta is the new coset generated
- beta = len_table
- self.p.append(beta)
- table[alpha][self.A_dict[x]] = beta
- table[beta][self.A_dict_inv[x]] = alpha
- # P[alpha][x] = epsilon, P[beta][x**-1] = epsilon
- if modified:
- self.P[alpha][self.A_dict[x]] = self._grp.identity
- self.P[beta][self.A_dict_inv[x]] = self._grp.identity
- self.p_p[beta] = self._grp.identity
- def define_c(self, alpha, x):
- r"""
- A variation of ``define`` routine, described on Pg. 165 [1], used in
- the coset table-based strategy of Todd-Coxeter algorithm. It differs
- from ``define`` routine in that for each definition it also adds the
- tuple `(\alpha, x)` to the deduction stack.
- See Also
- ========
- define
- """
- A = self.A
- table = self.table
- len_table = len(table)
- if len_table >= self.coset_table_limit:
- # abort the further generation of cosets
- raise ValueError("the coset enumeration has defined more than "
- "%s cosets. Try with a greater value max number of cosets "
- % self.coset_table_limit)
- table.append([None]*len(A))
- # beta is the new coset generated
- beta = len_table
- self.p.append(beta)
- table[alpha][self.A_dict[x]] = beta
- table[beta][self.A_dict_inv[x]] = alpha
- # append to deduction stack
- self.deduction_stack.append((alpha, x))
- def scan_c(self, alpha, word):
- """
- A variation of ``scan`` routine, described on pg. 165 of [1], which
- puts at tuple, whenever a deduction occurs, to deduction stack.
- See Also
- ========
- scan, scan_check, scan_and_fill, scan_and_fill_c
- """
- # alpha is an integer representing a "coset"
- # since scanning can be in two cases
- # 1. for alpha=0 and w in Y (i.e generating set of H)
- # 2. alpha in Omega (set of live cosets), w in R (relators)
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- f = alpha
- i = 0
- r = len(word)
- b = alpha
- j = r - 1
- # list of union of generators and their inverses
- while i <= j and table[f][A_dict[word[i]]] is not None:
- f = table[f][A_dict[word[i]]]
- i += 1
- if i > j:
- if f != b:
- self.coincidence_c(f, b)
- return
- while j >= i and table[b][A_dict_inv[word[j]]] is not None:
- b = table[b][A_dict_inv[word[j]]]
- j -= 1
- if j < i:
- # we have an incorrect completed scan with coincidence f ~ b
- # run the "coincidence" routine
- self.coincidence_c(f, b)
- elif j == i:
- # deduction process
- table[f][A_dict[word[i]]] = b
- table[b][A_dict_inv[word[i]]] = f
- self.deduction_stack.append((f, word[i]))
- # otherwise scan is incomplete and yields no information
- # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets where
- # coincidence occurs
- def coincidence_c(self, alpha, beta):
- """
- A variation of ``coincidence`` routine used in the coset-table based
- method of coset enumeration. The only difference being on addition of
- a new coset in coset table(i.e new coset introduction), then it is
- appended to ``deduction_stack``.
- See Also
- ========
- coincidence
- """
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- # behaves as a queue
- q = []
- self.merge(alpha, beta, q)
- while len(q) > 0:
- gamma = q.pop(0)
- for x in A_dict:
- delta = table[gamma][A_dict[x]]
- if delta is not None:
- table[delta][A_dict_inv[x]] = None
- # only line of difference from ``coincidence`` routine
- self.deduction_stack.append((delta, x**-1))
- mu = self.rep(gamma)
- nu = self.rep(delta)
- if table[mu][A_dict[x]] is not None:
- self.merge(nu, table[mu][A_dict[x]], q)
- elif table[nu][A_dict_inv[x]] is not None:
- self.merge(mu, table[nu][A_dict_inv[x]], q)
- else:
- table[mu][A_dict[x]] = nu
- table[nu][A_dict_inv[x]] = mu
- def scan(self, alpha, word, y=None, fill=False, modified=False):
- r"""
- ``scan`` performs a scanning process on the input ``word``.
- It first locates the largest prefix ``s`` of ``word`` for which
- `\alpha^s` is defined (i.e is not ``None``), ``s`` may be empty. Let
- ``word=sv``, let ``t`` be the longest suffix of ``v`` for which
- `\alpha^{t^{-1}}` is defined, and let ``v=ut``. Then three
- possibilities are there:
- 1. If ``t=v``, then we say that the scan completes, and if, in addition
- `\alpha^s = \alpha^{t^{-1}}`, then we say that the scan completes
- correctly.
- 2. It can also happen that scan does not complete, but `|u|=1`; that
- is, the word ``u`` consists of a single generator `x \in A`. In that
- case, if `\alpha^s = \beta` and `\alpha^{t^{-1}} = \gamma`, then we can
- set `\beta^x = \gamma` and `\gamma^{x^{-1}} = \beta`. These assignments
- are known as deductions and enable the scan to complete correctly.
- 3. See ``coicidence`` routine for explanation of third condition.
- Notes
- =====
- The code for the procedure of scanning `\alpha \in \Omega`
- under `w \in A*` is defined on pg. 155 [1]
- See Also
- ========
- scan_c, scan_check, scan_and_fill, scan_and_fill_c
- Scan and Fill
- =============
- Performed when the default argument fill=True.
- Modified Scan
- =============
- Performed when the default argument modified=True
- """
- # alpha is an integer representing a "coset"
- # since scanning can be in two cases
- # 1. for alpha=0 and w in Y (i.e generating set of H)
- # 2. alpha in Omega (set of live cosets), w in R (relators)
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- f = alpha
- i = 0
- r = len(word)
- b = alpha
- j = r - 1
- b_p = y
- if modified:
- f_p = self._grp.identity
- flag = 0
- while fill or flag == 0:
- flag = 1
- while i <= j and table[f][A_dict[word[i]]] is not None:
- if modified:
- f_p = f_p*self.P[f][A_dict[word[i]]]
- f = table[f][A_dict[word[i]]]
- i += 1
- if i > j:
- if f != b:
- if modified:
- self.modified_coincidence(f, b, f_p**-1*y)
- else:
- self.coincidence(f, b)
- return
- while j >= i and table[b][A_dict_inv[word[j]]] is not None:
- if modified:
- b_p = b_p*self.P[b][self.A_dict_inv[word[j]]]
- b = table[b][A_dict_inv[word[j]]]
- j -= 1
- if j < i:
- # we have an incorrect completed scan with coincidence f ~ b
- # run the "coincidence" routine
- if modified:
- self.modified_coincidence(f, b, f_p**-1*b_p)
- else:
- self.coincidence(f, b)
- elif j == i:
- # deduction process
- table[f][A_dict[word[i]]] = b
- table[b][A_dict_inv[word[i]]] = f
- if modified:
- self.P[f][self.A_dict[word[i]]] = f_p**-1*b_p
- self.P[b][self.A_dict_inv[word[i]]] = b_p**-1*f_p
- return
- elif fill:
- self.define(f, word[i], modified=modified)
- # otherwise scan is incomplete and yields no information
- # used in the low-index subgroups algorithm
- def scan_check(self, alpha, word):
- r"""
- Another version of ``scan`` routine, described on, it checks whether
- `\alpha` scans correctly under `word`, it is a straightforward
- modification of ``scan``. ``scan_check`` returns ``False`` (rather than
- calling ``coincidence``) if the scan completes incorrectly; otherwise
- it returns ``True``.
- See Also
- ========
- scan, scan_c, scan_and_fill, scan_and_fill_c
- """
- # alpha is an integer representing a "coset"
- # since scanning can be in two cases
- # 1. for alpha=0 and w in Y (i.e generating set of H)
- # 2. alpha in Omega (set of live cosets), w in R (relators)
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- f = alpha
- i = 0
- r = len(word)
- b = alpha
- j = r - 1
- while i <= j and table[f][A_dict[word[i]]] is not None:
- f = table[f][A_dict[word[i]]]
- i += 1
- if i > j:
- return f == b
- while j >= i and table[b][A_dict_inv[word[j]]] is not None:
- b = table[b][A_dict_inv[word[j]]]
- j -= 1
- if j < i:
- # we have an incorrect completed scan with coincidence f ~ b
- # return False, instead of calling coincidence routine
- return False
- elif j == i:
- # deduction process
- table[f][A_dict[word[i]]] = b
- table[b][A_dict_inv[word[i]]] = f
- return True
- def merge(self, k, lamda, q, w=None, modified=False):
- """
- Merge two classes with representatives ``k`` and ``lamda``, described
- on Pg. 157 [1] (for pseudocode), start by putting ``p[k] = lamda``.
- It is more efficient to choose the new representative from the larger
- of the two classes being merged, i.e larger among ``k`` and ``lamda``.
- procedure ``merge`` performs the merging operation, adds the deleted
- class representative to the queue ``q``.
- Parameters
- ==========
- 'k', 'lamda' being the two class representatives to be merged.
- Notes
- =====
- Pg. 86-87 [1] contains a description of this method.
- See Also
- ========
- coincidence, rep
- """
- p = self.p
- rep = self.rep
- phi = rep(k, modified=modified)
- psi = rep(lamda, modified=modified)
- if phi != psi:
- mu = min(phi, psi)
- v = max(phi, psi)
- p[v] = mu
- if modified:
- if v == phi:
- self.p_p[phi] = self.p_p[k]**-1*w*self.p_p[lamda]
- else:
- self.p_p[psi] = self.p_p[lamda]**-1*w**-1*self.p_p[k]
- q.append(v)
- def rep(self, k, modified=False):
- r"""
- Parameters
- ==========
- `k \in [0 \ldots n-1]`, as for ``self`` only array ``p`` is used
- Returns
- =======
- Representative of the class containing ``k``.
- Returns the representative of `\sim` class containing ``k``, it also
- makes some modification to array ``p`` of ``self`` to ease further
- computations, described on Pg. 157 [1].
- The information on classes under `\sim` is stored in array `p` of
- ``self`` argument, which will always satisfy the property:
- `p[\alpha] \sim \alpha` and `p[\alpha]=\alpha \iff \alpha=rep(\alpha)`
- `\forall \in [0 \ldots n-1]`.
- So, for `\alpha \in [0 \ldots n-1]`, we find `rep(self, \alpha)` by
- continually replacing `\alpha` by `p[\alpha]` until it becomes
- constant (i.e satisfies `p[\alpha] = \alpha`):w
- To increase the efficiency of later ``rep`` calculations, whenever we
- find `rep(self, \alpha)=\beta`, we set
- `p[\gamma] = \beta \forall \gamma \in p-chain` from `\alpha` to `\beta`
- Notes
- =====
- ``rep`` routine is also described on Pg. 85-87 [1] in Atkinson's
- algorithm, this results from the fact that ``coincidence`` routine
- introduces functionality similar to that introduced by the
- ``minimal_block`` routine on Pg. 85-87 [1].
- See Also
- ========
- coincidence, merge
- """
- p = self.p
- lamda = k
- rho = p[lamda]
- if modified:
- s = p[:]
- while rho != lamda:
- if modified:
- s[rho] = lamda
- lamda = rho
- rho = p[lamda]
- if modified:
- rho = s[lamda]
- while rho != k:
- mu = rho
- rho = s[mu]
- p[rho] = lamda
- self.p_p[rho] = self.p_p[rho]*self.p_p[mu]
- else:
- mu = k
- rho = p[mu]
- while rho != lamda:
- p[mu] = lamda
- mu = rho
- rho = p[mu]
- return lamda
- # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets
- # where coincidence occurs
- def coincidence(self, alpha, beta, w=None, modified=False):
- r"""
- The third situation described in ``scan`` routine is handled by this
- routine, described on Pg. 156-161 [1].
- The unfortunate situation when the scan completes but not correctly,
- then ``coincidence`` routine is run. i.e when for some `i` with
- `1 \le i \le r+1`, we have `w=st` with `s = x_1 x_2 \dots x_{i-1}`,
- `t = x_i x_{i+1} \dots x_r`, and `\beta = \alpha^s` and
- `\gamma = \alpha^{t-1}` are defined but unequal. This means that
- `\beta` and `\gamma` represent the same coset of `H` in `G`. Described
- on Pg. 156 [1]. ``rep``
- See Also
- ========
- scan
- """
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- # behaves as a queue
- q = []
- if modified:
- self.modified_merge(alpha, beta, w, q)
- else:
- self.merge(alpha, beta, q)
- while len(q) > 0:
- gamma = q.pop(0)
- for x in A_dict:
- delta = table[gamma][A_dict[x]]
- if delta is not None:
- table[delta][A_dict_inv[x]] = None
- mu = self.rep(gamma, modified=modified)
- nu = self.rep(delta, modified=modified)
- if table[mu][A_dict[x]] is not None:
- if modified:
- v = self.p_p[delta]**-1*self.P[gamma][self.A_dict[x]]**-1
- v = v*self.p_p[gamma]*self.P[mu][self.A_dict[x]]
- self.modified_merge(nu, table[mu][self.A_dict[x]], v, q)
- else:
- self.merge(nu, table[mu][A_dict[x]], q)
- elif table[nu][A_dict_inv[x]] is not None:
- if modified:
- v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]
- v = v*self.p_p[delta]*self.P[mu][self.A_dict_inv[x]]
- self.modified_merge(mu, table[nu][self.A_dict_inv[x]], v, q)
- else:
- self.merge(mu, table[nu][A_dict_inv[x]], q)
- else:
- table[mu][A_dict[x]] = nu
- table[nu][A_dict_inv[x]] = mu
- if modified:
- v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]*self.p_p[delta]
- self.P[mu][self.A_dict[x]] = v
- self.P[nu][self.A_dict_inv[x]] = v**-1
- # method used in the HLT strategy
- def scan_and_fill(self, alpha, word):
- """
- A modified version of ``scan`` routine used in the relator-based
- method of coset enumeration, described on pg. 162-163 [1], which
- follows the idea that whenever the procedure is called and the scan
- is incomplete then it makes new definitions to enable the scan to
- complete; i.e it fills in the gaps in the scan of the relator or
- subgroup generator.
- """
- self.scan(alpha, word, fill=True)
- def scan_and_fill_c(self, alpha, word):
- """
- A modified version of ``scan`` routine, described on Pg. 165 second
- para. [1], with modification similar to that of ``scan_anf_fill`` the
- only difference being it calls the coincidence procedure used in the
- coset-table based method i.e. the routine ``coincidence_c`` is used.
- See Also
- ========
- scan, scan_and_fill
- """
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- r = len(word)
- f = alpha
- i = 0
- b = alpha
- j = r - 1
- # loop until it has filled the alpha row in the table.
- while True:
- # do the forward scanning
- while i <= j and table[f][A_dict[word[i]]] is not None:
- f = table[f][A_dict[word[i]]]
- i += 1
- if i > j:
- if f != b:
- self.coincidence_c(f, b)
- return
- # forward scan was incomplete, scan backwards
- while j >= i and table[b][A_dict_inv[word[j]]] is not None:
- b = table[b][A_dict_inv[word[j]]]
- j -= 1
- if j < i:
- self.coincidence_c(f, b)
- elif j == i:
- table[f][A_dict[word[i]]] = b
- table[b][A_dict_inv[word[i]]] = f
- self.deduction_stack.append((f, word[i]))
- else:
- self.define_c(f, word[i])
- # method used in the HLT strategy
- def look_ahead(self):
- """
- When combined with the HLT method this is known as HLT+Lookahead
- method of coset enumeration, described on pg. 164 [1]. Whenever
- ``define`` aborts due to lack of space available this procedure is
- executed. This routine helps in recovering space resulting from
- "coincidence" of cosets.
- """
- R = self.fp_group.relators
- p = self.p
- # complete scan all relators under all cosets(obviously live)
- # without making new definitions
- for beta in self.omega:
- for w in R:
- self.scan(beta, w)
- if p[beta] < beta:
- break
- # Pg. 166
- def process_deductions(self, R_c_x, R_c_x_inv):
- """
- Processes the deductions that have been pushed onto ``deduction_stack``,
- described on Pg. 166 [1] and is used in coset-table based enumeration.
- See Also
- ========
- deduction_stack
- """
- p = self.p
- table = self.table
- while len(self.deduction_stack) > 0:
- if len(self.deduction_stack) >= CosetTable.max_stack_size:
- self.look_ahead()
- del self.deduction_stack[:]
- continue
- else:
- alpha, x = self.deduction_stack.pop()
- if p[alpha] == alpha:
- for w in R_c_x:
- self.scan_c(alpha, w)
- if p[alpha] < alpha:
- break
- beta = table[alpha][self.A_dict[x]]
- if beta is not None and p[beta] == beta:
- for w in R_c_x_inv:
- self.scan_c(beta, w)
- if p[beta] < beta:
- break
- def process_deductions_check(self, R_c_x, R_c_x_inv):
- """
- A variation of ``process_deductions``, this calls ``scan_check``
- wherever ``process_deductions`` calls ``scan``, described on Pg. [1].
- See Also
- ========
- process_deductions
- """
- table = self.table
- while len(self.deduction_stack) > 0:
- alpha, x = self.deduction_stack.pop()
- for w in R_c_x:
- if not self.scan_check(alpha, w):
- return False
- beta = table[alpha][self.A_dict[x]]
- if beta is not None:
- for w in R_c_x_inv:
- if not self.scan_check(beta, w):
- return False
- return True
- def switch(self, beta, gamma):
- r"""Switch the elements `\beta, \gamma \in \Omega` of ``self``, used
- by the ``standardize`` procedure, described on Pg. 167 [1].
- See Also
- ========
- standardize
- """
- A = self.A
- A_dict = self.A_dict
- table = self.table
- for x in A:
- z = table[gamma][A_dict[x]]
- table[gamma][A_dict[x]] = table[beta][A_dict[x]]
- table[beta][A_dict[x]] = z
- for alpha in range(len(self.p)):
- if self.p[alpha] == alpha:
- if table[alpha][A_dict[x]] == beta:
- table[alpha][A_dict[x]] = gamma
- elif table[alpha][A_dict[x]] == gamma:
- table[alpha][A_dict[x]] = beta
- def standardize(self):
- r"""
- A coset table is standardized if when running through the cosets and
- within each coset through the generator images (ignoring generator
- inverses), the cosets appear in order of the integers
- `0, 1, \dots, n`. "Standardize" reorders the elements of `\Omega`
- such that, if we scan the coset table first by elements of `\Omega`
- and then by elements of A, then the cosets occur in ascending order.
- ``standardize()`` is used at the end of an enumeration to permute the
- cosets so that they occur in some sort of standard order.
- Notes
- =====
- procedure is described on pg. 167-168 [1], it also makes use of the
- ``switch`` routine to replace by smaller integer value.
- Examples
- ========
- >>> from sympy.combinatorics import free_group
- >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
- >>> F, x, y = free_group("x, y")
- # Example 5.3 from [1]
- >>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
- >>> C = coset_enumeration_r(f, [])
- >>> C.compress()
- >>> C.table
- [[1, 3, 1, 3], [2, 0, 2, 0], [3, 1, 3, 1], [0, 2, 0, 2]]
- >>> C.standardize()
- >>> C.table
- [[1, 2, 1, 2], [3, 0, 3, 0], [0, 3, 0, 3], [2, 1, 2, 1]]
- """
- A = self.A
- A_dict = self.A_dict
- gamma = 1
- for alpha, x in product(range(self.n), A):
- beta = self.table[alpha][A_dict[x]]
- if beta >= gamma:
- if beta > gamma:
- self.switch(gamma, beta)
- gamma += 1
- if gamma == self.n:
- return
- # Compression of a Coset Table
- def compress(self):
- """Removes the non-live cosets from the coset table, described on
- pg. 167 [1].
- """
- gamma = -1
- A = self.A
- A_dict = self.A_dict
- A_dict_inv = self.A_dict_inv
- table = self.table
- chi = tuple([i for i in range(len(self.p)) if self.p[i] != i])
- for alpha in self.omega:
- gamma += 1
- if gamma != alpha:
- # replace alpha by gamma in coset table
- for x in A:
- beta = table[alpha][A_dict[x]]
- table[gamma][A_dict[x]] = beta
- table[beta][A_dict_inv[x]] == gamma
- # all the cosets in the table are live cosets
- self.p = list(range(gamma + 1))
- # delete the useless columns
- del table[len(self.p):]
- # re-define values
- for row in table:
- for j in range(len(self.A)):
- row[j] -= bisect_left(chi, row[j])
- def conjugates(self, R):
- R_c = list(chain.from_iterable((rel.cyclic_conjugates(), \
- (rel**-1).cyclic_conjugates()) for rel in R))
- R_set = set()
- for conjugate in R_c:
- R_set = R_set.union(conjugate)
- R_c_list = []
- for x in self.A:
- r = {word for word in R_set if word[0] == x}
- R_c_list.append(r)
- R_set.difference_update(r)
- return R_c_list
- def coset_representative(self, coset):
- '''
- Compute the coset representative of a given coset.
- Examples
- ========
- >>> from sympy.combinatorics import free_group
- >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
- >>> F, x, y = free_group("x, y")
- >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
- >>> C = coset_enumeration_r(f, [x])
- >>> C.compress()
- >>> C.table
- [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
- >>> C.coset_representative(0)
- <identity>
- >>> C.coset_representative(1)
- y
- >>> C.coset_representative(2)
- y**-1
- '''
- for x in self.A:
- gamma = self.table[coset][self.A_dict[x]]
- if coset == 0:
- return self.fp_group.identity
- if gamma < coset:
- return self.coset_representative(gamma)*x**-1
- ##############################
- # Modified Methods #
- ##############################
- def modified_define(self, alpha, x):
- r"""
- Define a function p_p from from [1..n] to A* as
- an additional component of the modified coset table.
- Parameters
- ==========
- \alpha \in \Omega
- x \in A*
- See Also
- ========
- define
- """
- self.define(alpha, x, modified=True)
- def modified_scan(self, alpha, w, y, fill=False):
- r"""
- Parameters
- ==========
- \alpha \in \Omega
- w \in A*
- y \in (YUY^-1)
- fill -- `modified_scan_and_fill` when set to True.
- See Also
- ========
- scan
- """
- self.scan(alpha, w, y=y, fill=fill, modified=True)
- def modified_scan_and_fill(self, alpha, w, y):
- self.modified_scan(alpha, w, y, fill=True)
- def modified_merge(self, k, lamda, w, q):
- r"""
- Parameters
- ==========
- 'k', 'lamda' -- the two class representatives to be merged.
- q -- queue of length l of elements to be deleted from `\Omega` *.
- w -- Word in (YUY^-1)
- See Also
- ========
- merge
- """
- self.merge(k, lamda, q, w=w, modified=True)
- def modified_rep(self, k):
- r"""
- Parameters
- ==========
- `k \in [0 \ldots n-1]`
- See Also
- ========
- rep
- """
- self.rep(k, modified=True)
- def modified_coincidence(self, alpha, beta, w):
- r"""
- Parameters
- ==========
- A coincident pair `\alpha, \beta \in \Omega, w \in Y \cup Y^{-1}`
- See Also
- ========
- coincidence
- """
- self.coincidence(alpha, beta, w=w, modified=True)
- ###############################################################################
- # COSET ENUMERATION #
- ###############################################################################
- # relator-based method
- def coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
- incomplete=False, modified=False):
- """
- This is easier of the two implemented methods of coset enumeration.
- and is often called the HLT method, after Hazelgrove, Leech, Trotter
- The idea is that we make use of ``scan_and_fill`` makes new definitions
- whenever the scan is incomplete to enable the scan to complete; this way
- we fill in the gaps in the scan of the relator or subgroup generator,
- that's why the name relator-based method.
- An instance of `CosetTable` for `fp_grp` can be passed as the keyword
- argument `draft` in which case the coset enumeration will start with
- that instance and attempt to complete it.
- When `incomplete` is `True` and the function is unable to complete for
- some reason, the partially complete table will be returned.
- # TODO: complete the docstring
- See Also
- ========
- scan_and_fill,
- Examples
- ========
- >>> from sympy.combinatorics.free_groups import free_group
- >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
- >>> F, x, y = free_group("x, y")
- # Example 5.1 from [1]
- >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
- >>> C = coset_enumeration_r(f, [x])
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... print(C.table[i])
- [0, 0, 1, 2]
- [1, 1, 2, 0]
- [2, 2, 0, 1]
- >>> C.p
- [0, 1, 2, 1, 1]
- # Example from exercises Q2 [1]
- >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
- >>> C = coset_enumeration_r(f, [])
- >>> C.compress(); C.standardize()
- >>> C.table
- [[1, 2, 3, 4],
- [5, 0, 6, 7],
- [0, 5, 7, 6],
- [7, 6, 5, 0],
- [6, 7, 0, 5],
- [2, 1, 4, 3],
- [3, 4, 2, 1],
- [4, 3, 1, 2]]
- # Example 5.2
- >>> f = FpGroup(F, [x**2, y**3, (x*y)**3])
- >>> Y = [x*y]
- >>> C = coset_enumeration_r(f, Y)
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... print(C.table[i])
- [1, 1, 2, 1]
- [0, 0, 0, 2]
- [3, 3, 1, 0]
- [2, 2, 3, 3]
- # Example 5.3
- >>> f = FpGroup(F, [x**2*y**2, x**3*y**5])
- >>> Y = []
- >>> C = coset_enumeration_r(f, Y)
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... print(C.table[i])
- [1, 3, 1, 3]
- [2, 0, 2, 0]
- [3, 1, 3, 1]
- [0, 2, 0, 2]
- # Example 5.4
- >>> F, a, b, c, d, e = free_group("a, b, c, d, e")
- >>> f = FpGroup(F, [a*b*c**-1, b*c*d**-1, c*d*e**-1, d*e*a**-1, e*a*b**-1])
- >>> Y = [a]
- >>> C = coset_enumeration_r(f, Y)
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... print(C.table[i])
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- # example of "compress" method
- >>> C.compress()
- >>> C.table
- [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
- # Exercises Pg. 161, Q2.
- >>> F, x, y = free_group("x, y")
- >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
- >>> Y = []
- >>> C = coset_enumeration_r(f, Y)
- >>> C.compress()
- >>> C.standardize()
- >>> C.table
- [[1, 2, 3, 4],
- [5, 0, 6, 7],
- [0, 5, 7, 6],
- [7, 6, 5, 0],
- [6, 7, 0, 5],
- [2, 1, 4, 3],
- [3, 4, 2, 1],
- [4, 3, 1, 2]]
- # John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson
- # Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490
- # from 1973chwd.pdf
- # Table 1. Ex. 1
- >>> F, r, s, t = free_group("r, s, t")
- >>> E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2])
- >>> C = coset_enumeration_r(E1, [r])
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... print(C.table[i])
- [0, 0, 0, 0, 0, 0]
- Ex. 2
- >>> F, a, b = free_group("a, b")
- >>> Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5])
- >>> C = coset_enumeration_r(Cox, [a])
- >>> index = 0
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... index += 1
- >>> index
- 500
- # Ex. 3
- >>> F, a, b = free_group("a, b")
- >>> B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \
- (a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4])
- >>> C = coset_enumeration_r(B_2_4, [a])
- >>> index = 0
- >>> for i in range(len(C.p)):
- ... if C.p[i] == i:
- ... index += 1
- >>> index
- 1024
- References
- ==========
- .. [1] Holt, D., Eick, B., O'Brien, E.
- "Handbook of computational group theory"
- """
- # 1. Initialize a coset table C for < X|R >
- C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
- # Define coset table methods.
- if modified:
- _scan_and_fill = C.modified_scan_and_fill
- _define = C.modified_define
- else:
- _scan_and_fill = C.scan_and_fill
- _define = C.define
- if draft:
- C.table = draft.table[:]
- C.p = draft.p[:]
- R = fp_grp.relators
- A_dict = C.A_dict
- p = C.p
- for i in range(len(Y)):
- if modified:
- _scan_and_fill(0, Y[i], C._grp.generators[i])
- else:
- _scan_and_fill(0, Y[i])
- alpha = 0
- while alpha < C.n:
- if p[alpha] == alpha:
- try:
- for w in R:
- if modified:
- _scan_and_fill(alpha, w, C._grp.identity)
- else:
- _scan_and_fill(alpha, w)
- # if alpha was eliminated during the scan then break
- if p[alpha] < alpha:
- break
- if p[alpha] == alpha:
- for x in A_dict:
- if C.table[alpha][A_dict[x]] is None:
- _define(alpha, x)
- except ValueError as e:
- if incomplete:
- return C
- raise e
- alpha += 1
- return C
- def modified_coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None,
- incomplete=False):
- r"""
- Introduce a new set of symbols y \in Y that correspond to the
- generators of the subgroup. Store the elements of Y as a
- word P[\alpha, x] and compute the coset table similar to that of
- the regular coset enumeration methods.
- Examples
- ========
- >>> from sympy.combinatorics.free_groups import free_group
- >>> from sympy.combinatorics.fp_groups import FpGroup
- >>> from sympy.combinatorics.coset_table import modified_coset_enumeration_r
- >>> F, x, y = free_group("x, y")
- >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
- >>> C = modified_coset_enumeration_r(f, [x])
- >>> C.table
- [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], [None, 1, None, None], [1, 3, None, None]]
- See Also
- ========
- coset_enumertation_r
- References
- ==========
- .. [1] Holt, D., Eick, B., O'Brien, E.,
- "Handbook of Computational Group Theory",
- Section 5.3.2
- """
- return coset_enumeration_r(fp_grp, Y, max_cosets=max_cosets, draft=draft,
- incomplete=incomplete, modified=True)
- # Pg. 166
- # coset-table based method
- def coset_enumeration_c(fp_grp, Y, max_cosets=None, draft=None,
- incomplete=False):
- """
- >>> from sympy.combinatorics.free_groups import free_group
- >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_c
- >>> F, x, y = free_group("x, y")
- >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
- >>> C = coset_enumeration_c(f, [x])
- >>> C.table
- [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
- """
- # Initialize a coset table C for < X|R >
- X = fp_grp.generators
- R = fp_grp.relators
- C = CosetTable(fp_grp, Y, max_cosets=max_cosets)
- if draft:
- C.table = draft.table[:]
- C.p = draft.p[:]
- C.deduction_stack = draft.deduction_stack
- for alpha, x in product(range(len(C.table)), X):
- if C.table[alpha][C.A_dict[x]] is not None:
- C.deduction_stack.append((alpha, x))
- A = C.A
- # replace all the elements by cyclic reductions
- R_cyc_red = [rel.identity_cyclic_reduction() for rel in R]
- R_c = list(chain.from_iterable((rel.cyclic_conjugates(), (rel**-1).cyclic_conjugates()) \
- for rel in R_cyc_red))
- R_set = set()
- for conjugate in R_c:
- R_set = R_set.union(conjugate)
- # a list of subsets of R_c whose words start with "x".
- R_c_list = []
- for x in C.A:
- r = {word for word in R_set if word[0] == x}
- R_c_list.append(r)
- R_set.difference_update(r)
- for w in Y:
- C.scan_and_fill_c(0, w)
- for x in A:
- C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
- alpha = 0
- while alpha < len(C.table):
- if C.p[alpha] == alpha:
- try:
- for x in C.A:
- if C.p[alpha] != alpha:
- break
- if C.table[alpha][C.A_dict[x]] is None:
- C.define_c(alpha, x)
- C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]])
- except ValueError as e:
- if incomplete:
- return C
- raise e
- alpha += 1
- return C
|