/** * Merges pairs of rows by making them identical and marking them as equivalent. For each merged * pair, also merges each pair of not yet merged images by the same generator, and so on * recursively, until no more pairs need to be merged. * * @param Q a list of row pairs to merge. * @param P the row equivalence classes (modified by this method). * @return the number of individual merges performed. */ private int performIdentifications(final LinkedList Q, final IntPartition P) { int count = 0; while (Q.size() > 0) { final IntPair pair = (IntPair) Q.removeFirst(); final int a = P.find(pair.getFirst()); final int b = P.find(pair.getSecond()); if (a == b) { continue; } P.unite(a, b); ++count; final int row_a[] = (int[]) this.table.get(a); final int row_b[] = (int[]) this.table.get(b); for (int g = 0; g < this.ngens; ++g) { final int ag = row_a[g]; final int bg = row_b[g]; if (ag == 0) { row_a[g] = bg; } else if (bg == 0) { row_b[g] = ag; } else if (!P.areEquivalent(ag, bg)) { Q.addLast(new IntPair(ag, bg)); } } this.table.set(b, row_a); } return count; }
/** * Compresses the table by collapsing each set of rows tagged as equivalent, i.e., representing * the same coset, into a single row. At this stage, equivalent rows are expected to have equal * contents, as should have been established by {@link #performIdentifications(LinkedList, * IntPartition)}. * * @param P a partition of the row set into equivalence classes. * @return a mapping of old to new row numbers. */ private int[] compressTable(final IntPartition P) { // --- initialize the mapping from old to new row numbers final int old2new[] = new int[this.table.size()]; // --- initlalize the new coset table final LinkedList newTable = new LinkedList(); // --- the row with number 0 is not used newTable.add(this.table.get(0)); // --- collect the rows for the new table and establish the mapping for (int i = 1; i < this.table.size(); ++i) { // --- get the representative for i's equivalence class final int ri = P.find(i); // --- use only the first row in each equivalence class if (old2new[ri] == 0) { old2new[ri] = newTable.size(); newTable.add(this.table.get(ri)); } // --- map i to the same row number as its representative old2new[i] = old2new[ri]; } // --- copy the new table into the old one this.table.clear(); while (newTable.size() > 0) { this.table.add(newTable.removeFirst()); } // --- finally, translate all entries into the new numbering scheme for (int i = 1; i < this.table.size(); ++i) { final int row[] = (int[]) this.table.get(i); for (int j = 0; j < this.ngens; ++j) { row[j] = old2new[row[j]]; } } // --- return the mapping from old to new row numbers return old2new; }
/** * Constructs a CosetTable instance with a limit on the number of table rows that may be present * at any stage of the construction process. Note that this number may be much higher than the * actual number of cosets, because multiple rows may be found correspond to a single coset long * after their construction. * * @param group the group. * @param subgroupGenerators generators of the subgroup. * @param sizeLimit the limit on the number of coset rows. */ public CosetAction(final FpGroup group, final List subgroupGenerators, final int sizeLimit) { // --- copy parameters to fields this.group = group; this.subgroupGenerators = Collections.unmodifiableList(subgroupGenerators); this.sizeLimit = sizeLimit; // --- extract generators and relators for group final List groupGenerators = group.getGenerators(); final List groupRelators = group.getRelators(); // --- set up translation tables this.ngens = 2 * groupGenerators.size(); this.idx2gen = new FreeWord[this.ngens]; this.gen2idx = new HashMap(); this.idx2invidx = new int[this.ngens]; int nu = 0; for (Iterator iter = groupGenerators.iterator(); iter.hasNext(); ) { final FreeWord g = (FreeWord) iter.next(); this.idx2gen[nu] = g; this.gen2idx.put(g, new Integer(nu)); this.idx2gen[nu + 1] = g.inverse(); this.gen2idx.put(g.inverse(), new Integer(nu + 1)); this.idx2invidx[nu] = nu + 1; this.idx2invidx[nu + 1] = nu; nu += 2; } // --- translate relators final List rels = new ArrayList(); for (Iterator iter = groupRelators.iterator(); iter.hasNext(); ) { final FreeWord r = (FreeWord) iter.next(); final int n = r.length(); for (int i = 0; i < n; ++i) { final FreeWord w = r.subword(i, n).times(r.subword(0, i)); rels.add(translateWord(w)); rels.add(translateWord(w.inverse())); } } // --- translate subgroup generators final List subgens = new ArrayList(); for (Iterator iter = subgroupGenerators.iterator(); iter.hasNext(); ) { final FreeWord generator = (FreeWord) iter.next(); subgens.add(translateWord(generator)); subgens.add(translateWord(generator.inverse())); } // --- set up a coset table with one dummy row and one row for the trivial coset this.table = new ArrayList(); this.table.add(new int[this.ngens]); this.table.add(new int[this.ngens]); // --- holds classes of rows known to correspond to the same coset IntPartition equivalences = new IntPartition(); // --- number of rows made invalid but not yet deleted int invalidRows = 0; // --- scan the table row by row, creating and deleting rows on the fly int i = 1; while (i < this.table.size()) { // --- proceed only if the current row is valid if (equivalences.find(i) != i) { ++i; continue; } // --- scan the current row final int row[] = (int[]) this.table.get(i); for (int j = 0; j < this.ngens; ++j) { // --- proceed only if the current column is empty if (row[j] != 0) { continue; } // --- log the result of this construction step if (LOGGING) { System.out.println("\ni = " + i + ", j = " + j); } // --- make a new row for the product with g final int newRow[] = new int[this.ngens]; this.table.add(newRow); // --- set the correspondences final int n = this.table.size(); row[j] = n - 1; newRow[this.idx2invidx[j]] = i; if (LOGGING) { System.out.println("after setting item:"); dumpTable(); } // --- scan relations to identify equivalent rows final LinkedList identify = new LinkedList(); for (Iterator iter = rels.iterator(); iter.hasNext(); ) { final int rel[] = (int[]) iter.next(); scanRelation(rel, n - 1, identify); } // --- scan subgroup generators final int one = equivalences.find(1); for (Iterator iter = subgens.iterator(); iter.hasNext(); ) { final int gen[] = (int[]) iter.next(); scanRelation(gen, one, identify); } if (LOGGING) { System.out.println("after scanning:"); dumpTable(); } // --- perform pending identifications invalidRows += performIdentifications(identify, equivalences); if (LOGGING) { System.out.println("after identifying:"); dumpTable(); } // --- quit if the limit on the number of rows is reached if (this.sizeLimit > 0 && n - invalidRows > this.sizeLimit) { throw new RuntimeException("table limit reached"); } // --- compress the table if necessary if (invalidRows > n / 2) { final int old2new[] = compressTable(equivalences); equivalences = new IntPartition(); i = old2new[i]; invalidRows = 0; if (LOGGING) { System.out.println("after compressing:"); dumpTable(); } } } // --- look at next row ++i; } // --- make a final cleanup compressTable(equivalences); if (LOGGING) { System.out.println("\nAfter final compression:"); dumpTable(); } // --- some finishing touches this.numberOfCosets = this.table.size() - 1; this.cosetRepresentatives = computeCosetRepresentatives(); }