FuncResult compute_cusped_isometries( Triangulation *manifold0, Triangulation *manifold1, IsometryList **isometry_list, IsometryList **isometry_list_of_links) { Triangulation *copy_of_manifold0, *copy_of_manifold1; Isometry *partial_isometry_list, *new_isometry; Tetrahedron *tet0, *tet1; int i; /* * If manifold0 != manifold1 we are computing the isometries from one * manifold to another. We begin by making a copy of each manifold * and converting it to the canonical retriangulation of the * canonical cell decomposition. */ if (manifold0 != manifold1) { /* * Make copies of the manifolds. */ copy_triangulation(manifold0, ©_of_manifold0); copy_triangulation(manifold1, ©_of_manifold1); /* * Find the canonical triangulations of the copies. */ if (canonize(copy_of_manifold0) == func_failed || canonize(copy_of_manifold1) == func_failed) { free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); *isometry_list = NULL; *isometry_list_of_links = NULL; return func_failed; } } /* * If manifold0 == manifold1 we are computing the symmetries from * a manifold to itself. In this case we find the canonical * retriangulation of the canonical cell decomposition before making * the second copy. This serves two purposes: * * (1) It reduces the run time, because we avoid a redundant call * to canonize(). * * (2) It insures that the Tetrahedra will be listed in the same * order in the two manifolds. This makes it easy for the * symmetry group program to recognize the identity symmetry. * (Perhaps you are curious as to how the Tetrahedra could * possibly fail to be listed in the same order. In rare * cases canonize() must do some random retriangulation. * If this is done to two identical copies of a Triangulation * the final canonical retriangulations will of course be * combinatorially the same, but the Tetrahedra might be listed * in different orders and numbered differently.) */ else { /* manifold0 == manifold1 */ /* * Make one copy of the manifold. */ copy_triangulation(manifold0, ©_of_manifold0); /* * Find the canonical retriangulation. */ if (canonize(copy_of_manifold0) == func_failed) { free_triangulation(copy_of_manifold0); *isometry_list = NULL; *isometry_list_of_links = NULL; return func_failed; } /* * Make a second copy of the canonical retriangulation. */ copy_triangulation(copy_of_manifold0, ©_of_manifold1); } /* * Allocate space for the IsometryList and initialize it. */ *isometry_list = NEW_STRUCT(IsometryList); (*isometry_list)->num_isometries = 0; (*isometry_list)->isometry = NULL; /* * If isometry_list_of_links is not NULL, allocate and * initialize *isometry_list_of_links as well. */ if (isometry_list_of_links != NULL) { *isometry_list_of_links = NEW_STRUCT(IsometryList); (*isometry_list_of_links)->num_isometries = 0; (*isometry_list_of_links)->isometry = NULL; } /* * If the two manifolds don't have the same number of * Tetrahedra, then there can't possibly be any isometries * between the two. We just initialized the IsometryList(s) * to be empty, so if there aren't any isometries, we're * ready free the copies of the manifolds and return. */ if (copy_of_manifold0->num_tetrahedra != copy_of_manifold1->num_tetrahedra) { free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); return func_OK; } /* * partial_isometry_list will keep a linked list of the * Isometries we discover. When we're done we'll allocate * the array (*isometry_list)->isometry and copy the * addresses of the Isometries into it. For now, we * initialize partial_isometry_list to NULL to show that * the linked list is empty. */ partial_isometry_list = NULL; /* * Assign indices to the Tetrahedra in each manifold. */ number_the_tetrahedra(copy_of_manifold0); number_the_tetrahedra(copy_of_manifold1); /* * Try mapping an arbitrary but fixed Tetrahedron of * manifold0 ("tet0") to each Tetrahedron of manifold1 * in turn, with each possible Permutation. See which * of these maps extends to a global isometry. We're * guaranteed to find all possible isometries this way * (and typically quite a lot of other garbage as well). */ /* * Let tet0 be the first Tetrahedron on manifold0's list. */ tet0 = copy_of_manifold0->tet_list_begin.next; /* * Consider each Tetrahedron in manifold1. */ for (tet1 = copy_of_manifold1->tet_list_begin.next; tet1 != ©_of_manifold1->tet_list_end; tet1 = tet1->next) /* * Consider each of the 24 possible ways to map tet0 to tet1. */ for (i = 0; i < 24; i++) /* * Does mapping tet0 to tet1 via permutation_by_index[i] * define an isometry? */ if (attempt_isometry(copy_of_manifold0, tet0, tet1, permutation_by_index[i], NULL ) == func_OK) { /* * Copy the isometry to an Isometry data structure . . . */ copy_isometry(copy_of_manifold0, copy_of_manifold1, &new_isometry); /* * . . . add it to the partial_isometry_list . . . */ new_isometry->next = partial_isometry_list; partial_isometry_list = new_isometry; /* * . . . and increment the count. */ (*isometry_list)->num_isometries++; } /* * If some Isometries were found, allocate space for the * array (*isometry_list)->isometry and write the addresses * of the Isometries into it. */ make_isometry_array(*isometry_list, partial_isometry_list); /* * If isometry_list_of_links is not NULL, make a copy of * those Isometries which extend to the associated link * (i.e. those which take meridians to meridians). */ find_isometries_which_extend(*isometry_list, isometry_list_of_links); /* * Discard the copies of the manifolds. */ free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); return func_OK; }
static FuncResult compute_symmetry_group_without_polyhedron( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group) { int lower_bound, num_curves, i; DualOneSkeletonCurve **the_curves; Complex prev_length, filled_length; Triangulation *copy; /* * Without a polyhedron we can't get a length spectrum, * and without a length spectrum we can't know for sure * when we've found the whole symmetry group. */ *is_full_group = FALSE; /* * compute_closed_symmetry_group() will have already computed * the symmetry subgroup preserving the current core geodesic. * (compute_closed_symmetry_group() also checks that we have * precisely one cusp.) */ if (*symmetry_group == NULL) uFatalError("compute_symmetry_group_without_polyhedron", "symmetry_group_closed"); /* * Initialize a (possible trivial) lower_bound on the order * of the full symmetry group. */ lower_bound = symmetry_group_order(*symmetry_group); /* * What curves are available in the 1-skeleton? */ dual_curves(manifold, MAX_DUAL_CURVE_LENGTH, &num_curves, &the_curves); prev_length = Zero; for (i = 0; i < num_curves; i++) { /* * Get the filled_length of the_curves[i]. */ get_dual_curve_info(the_curves[i], NULL, &filled_length, NULL); /* * Work with the absolute value of the torsion. */ filled_length.imag = fabs(filled_length.imag); /* * Skip lengths which appear equal a previous length. * (Note: dual_curves() first sorts by filled_length, * then complete_length.) */ if (fabs(filled_length.real - prev_length.real) < LENGTH_EPSILON && fabs(filled_length.imag - prev_length.imag) < TORSION_EPSILON) continue; else prev_length = filled_length; /* * Let's work on a copy, and leave the original_manifold untouched. */ copy_triangulation(manifold, ©); /* * Proceed as in try_to_drill_curves(), but with the realization * that we don't know how many curves we have of each given length. */ try_to_drill_unknown_curves( ©, filled_length, &lower_bound, symmetry_group, symmetric_triangulation); /* * Free the copy. */ free_triangulation(copy); } free_dual_curves(num_curves, the_curves); return func_OK; }
static FuncResult find_geometric_solution( Triangulation **manifold) { int i; Triangulation *copy; /* * If it ain't broke, don't fix it. */ if (get_filled_solution_type(*manifold) == geometric_solution) return func_OK; /* * Save a copy in case we make the triangulation even worse, * e.g. by converting a nongeometric_solution to a degenerate_solution. */ copy_triangulation(*manifold, ©); /* * Attempt to find a geometric_solution. */ for (i = 0; i < MAX_RETRIANGULATIONS; i++) { randomize_triangulation(*manifold); if (get_filled_solution_type(*manifold) == geometric_solution) { free_triangulation(copy); return func_OK; } /* * Every so often try canonizing as a further * stimulus to randomization. */ if ((i%4) == 3) { proto_canonize(*manifold); if (get_filled_solution_type(*manifold) == geometric_solution) { free_triangulation(copy); return func_OK; } } } /* * What have we got left? */ switch (get_filled_solution_type(*manifold)) { case geometric_solution: free_triangulation(copy); return func_OK; case nongeometric_solution: free_triangulation(copy); return func_failed; default: /* * If we don't have at least a nongeometric_solution, * restore the original triangulation. */ free_triangulation(*manifold); *manifold = copy; return func_failed; } }
FuncResult compute_closed_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group) { FuncResult result; /* * Make sure the variables used to pass back our results * are all initially empty. */ if (*symmetry_group != NULL || *symmetric_triangulation != NULL) uFatalError("compute_closed_symmetry_group", "symmetry_group"); /* * compute_symmetry_group() should have passed us a 1-cusp * manifold with a Dehn filling on its cusp. */ if (get_num_cusps(manifold) != 1 || all_cusps_are_filled(manifold) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) { uFatalError("compute_closed_symmetry_group", "symmetry_group_closed"); } /* * For later convenience, change the basis on the cusp * so that the Dehn filling curve becomes a meridian. */ { MatrixInt22 basis_change[1]; current_curve_basis(manifold, 0, basis_change[0]); change_peripheral_curves(manifold, basis_change); } /* * At the very least, we can try to establish a (possibly trivial) * lower bound on the symmetry group by computing the group * which preserves the given core geodesic. */ { SymmetryGroup *dummy = NULL; if (compute_cusped_symmetry_group(manifold, &dummy, symmetry_group) == func_OK) { copy_triangulation(manifold, symmetric_triangulation); free_symmetry_group(dummy); /* we don't need dummy */ } else { /* * The only way compute_cusped_symmetry_group() may fail * is if a canonical cell decomposition cannot be found, * e.g. because the manifold is not hyperbolic. */ return func_failed; } } /* * For small to medium sized manifolds we should have * no trouble getting a Dirichlet domain. But if we can't, * then we want to muddle along as best we can without one. */ { WEPolyhedron *polyhedron; polyhedron = compute_polyhedron(manifold); if (polyhedron != NULL) { result = compute_symmetry_group_using_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group, polyhedron); free_Dirichlet_domain(polyhedron); } else result = compute_symmetry_group_without_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group); } return result; }
static void try_to_drill_curves( Triangulation *original_manifold, MergedMultiLength desired_curves, int *lower_bound, int *upper_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation) { Triangulation *manifold; Real old_volume, new_volume; int singularity_index; Complex core_length; SymmetryGroup *manifold_sym_grp = NULL, *link_sym_grp = NULL; int new_upper_bound; MergedMultiLength remaining_curves; int num_possible_images; /* * Let's work on a copy, and leave the original_manifold untouched. */ copy_triangulation(original_manifold, &manifold); /* * As a guard against creating weird triangulations, * do an "unnecessary" error check. */ old_volume = volume(manifold, NULL); /* * To assist in the bookkeeping, we make a copy of the MergedMultiLength * and use it to keep track of how many curves remain. */ remaining_curves = desired_curves; /* * In cases where the number of geodesics with positive torsion * does not equal the number with negative torsion (and both numbers * are nonzero), the manifold is clearly chiral, and we want to * drill only the curves with the lesser multiplicity. */ if (remaining_curves.pos_multiplicity > 0 && remaining_curves.neg_multiplicity > 0 && remaining_curves.pos_multiplicity != remaining_curves.neg_multiplicity) { /* * Supress the curves with the greater multiplicity. */ if (remaining_curves.pos_multiplicity > remaining_curves.neg_multiplicity) { remaining_curves.total_multiplicity -= remaining_curves.pos_multiplicity; remaining_curves.pos_multiplicity = 0; } else { remaining_curves.total_multiplicity -= remaining_curves.neg_multiplicity; remaining_curves.neg_multiplicity = 0; } } /* * Note the original number of curves in the set we'll be drilling. * It's the number of possible images of a given curve under the * action of the symmetry group. (In the case of differing positive * and negative multiplicity, the curves of lesser multiplicity * must be taken to themselves. Otherwise curves of positive * torsion could be taken to curves of negative torsion.) */ num_possible_images = remaining_curves.total_multiplicity; /* * The current core geodesic may or may not have the desired length. * If it doesn't, replace it with a new one which does. * Note that the absolute value of the torsion might be correct, * but the sign of the torsion might have been "suppressed" above. */ core_geodesic(manifold, 0, &singularity_index, &core_length, NULL); if ( /* * length is wrong */ fabs(desired_curves.length - core_length.real) > LENGTH_EPSILON || /* * torsion is wrong */ ( /* * Doesn't match desired positive torsion. */ ( /* * We're not looking for positive torsion. */ remaining_curves.pos_multiplicity == 0 || /* * It doesn't have the correct positive torsion. */ fabs(desired_curves.torsion - core_length.imag) > TORSION_EPSILON ) && /* * Doesn't match desired negative torsion. */ ( /* * We're not looking for negative torsion. */ remaining_curves.neg_multiplicity == 0 || /* * It doesn't have the correct negative torsion. */ fabs((-desired_curves.torsion) - core_length.imag) > TORSION_EPSILON ) && /* * Doesn't match desired zero torsion. */ ( /* * We're not looking for zero torsion. */ remaining_curves.zero_multiplicity == 0 || /* * It doesn't have zero torsion. */ fabs(core_length.imag) > ZERO_TORSION_EPSILON ) ) ) { if (drill_one_curve(&manifold, &remaining_curves) == func_failed || fill_first_cusp(&manifold) == func_failed) { free_triangulation(manifold); return; } } else /* core geodesic is already a desired curve */ { /* * The complex length program should never report a torsion * of -pi. (It always converts to +pi.) */ if (core_length.imag < -PI + PI_TORSION_EPSILON) uFatalError("try_to_drill_curves", "symmetry_group_closed"); if (core_length.imag > ZERO_TORSION_EPSILON) remaining_curves.pos_multiplicity--; else if (core_length.imag < -ZERO_TORSION_EPSILON) remaining_curves.neg_multiplicity--; else remaining_curves.zero_multiplicity--; remaining_curves.total_multiplicity--; } /* * At this point we have drilled out one curve of the correct length. * If the description is positively oriented, we may obtain an upper bound * on the order of the symmetry group. */ if (find_geometric_solution(&manifold) == func_OK) { if (compute_cusped_symmetry_group(manifold, &manifold_sym_grp, &link_sym_grp) != func_OK) { free_triangulation(manifold); return; } new_upper_bound = num_possible_images * symmetry_group_order(link_sym_grp); if (new_upper_bound < *upper_bound) *upper_bound = new_upper_bound; free_symmetry_group(manifold_sym_grp); free_symmetry_group(link_sym_grp); manifold_sym_grp = NULL; link_sym_grp = NULL; } /* * We have drilled out one curve of the correct length. * Keep drilling curves until we've got them all. */ while (remaining_curves.total_multiplicity > 0) if (drill_one_curve(&manifold, &remaining_curves) == func_failed) { free_triangulation(manifold); return; } /* * We could have a degenerate_solution if we drilled out * curves in the wrong isotopy classes. */ if (get_filled_solution_type(manifold) == degenerate_solution) { free_triangulation(manifold); return; } /* * Try to get a geometric_solution. */ (void) find_geometric_solution(&manifold); /* * Finish our "unnecessary" error check. */ new_volume = volume(manifold, NULL); if (fabs(new_volume - old_volume) > VOLUME_ERROR_EPSILON) { /* * If ever we're looking for pathological triangulations, * this would be a good place to set a breakpoint. */ free_triangulation(manifold); return; } /* * Check the symmetry group of what we have, and see whether it provides * a lower bound. If a geometric_solution was found, then we'll have * an upper bound as well, i.e. we'll know the symmetry group exactly. */ if (compute_cusped_symmetry_group(manifold, &manifold_sym_grp, &link_sym_grp) == func_failed) { free_triangulation(manifold); return; } if (symmetry_group_order(link_sym_grp) > *lower_bound) { *lower_bound = symmetry_group_order(link_sym_grp); free_symmetry_group(*symmetry_group); /* NULL is OK */ *symmetry_group = link_sym_grp; free_triangulation(*symmetric_triangulation); /* NULL is OK */ copy_triangulation(manifold, symmetric_triangulation); } if (get_filled_solution_type(manifold) == geometric_solution) { /* * Include an error check. */ new_upper_bound = symmetry_group_order(link_sym_grp); if (new_upper_bound < *upper_bound) *upper_bound = new_upper_bound; if (*upper_bound != *lower_bound) uFatalError("try_to_drill_curves", "symmetry_group_closed"); } free_symmetry_group(manifold_sym_grp); if (link_sym_grp != *symmetry_group) free_symmetry_group(link_sym_grp); free_triangulation(manifold); }
static void try_to_drill_unknown_curves( Triangulation **manifold, Complex desired_length, int *lower_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation) { Real old_volume; MergedMultiLength the_desired_curves; int singularity_index; Complex core_length; SymmetryGroup *manifold_sym_grp = NULL, *link_sym_grp = NULL; /* * We want to drill as many curves as possible whose length * is desired_length.real and whose torsion has absolute value * desired_length.imag. We don't know how many curves of this * complex length to expect, so we won't know when to stop. * (We stop when we can't find another such curve to drill, or * a drilling fails.) We compute the symmetry group at each * step, since drilling additional curves may sometimes decrease * the size of the symmetry subgroup. */ /* * As a guard against creating weird triangulations, * do an "unnecessary" error check. */ old_volume = volume(*manifold, NULL); /* * Mock up a MergedMultiLength to represent the desired_length * we want to drill. (desired_length.imag will always be nonnegative.) */ the_desired_curves.length = desired_length.real; the_desired_curves.torsion = desired_length.imag; the_desired_curves.pos_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.neg_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.zero_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.total_multiplicity = INFINITE_MULTIPLICITY; /* * The current core geodesic may or may not have the desired length. * If it doesn't, replace it with a new one which does. * Note that the absolute value of the torsion might be correct, * but the sign of the torsion might have been "suppressed" above. */ core_geodesic(*manifold, 0, &singularity_index, &core_length, NULL); if (fabs(desired_length.real - core_length.real ) > LENGTH_EPSILON || fabs(desired_length.imag - fabs(core_length.imag)) > TORSION_EPSILON) { /* * Try to drill a curve of the desired_length. */ if (drill_one_curve(manifold, &the_desired_curves) == func_failed || fill_first_cusp(manifold) == func_failed) return; } /* * At this point we've got one curve of the desired_length drilled. * Compute its symmetry subgroup, try to drill another curve, . . . * until we can no longer drill. */ do { /* * Don't mess with anything worse than a nongeometric_solution. */ if (get_filled_solution_type(*manifold) != geometric_solution && get_filled_solution_type(*manifold) != nongeometric_solution) break; /* * Make sure the volume is plausible, as a further guard against * weird solutions. (The symmetry subgroup would still be valid, * but we certainly don't want to proceed further.) */ if (fabs(volume(*manifold, NULL) - old_volume) > VOLUME_ERROR_EPSILON) break; /* * Compute the symmetry subgroup. */ if (compute_cusped_symmetry_group(*manifold, &manifold_sym_grp, &link_sym_grp) == func_failed) break; /* * Have we improved the lower bound? */ if (symmetry_group_order(link_sym_grp) > *lower_bound) { *lower_bound = symmetry_group_order(link_sym_grp); free_symmetry_group(*symmetry_group); /* NULL is OK */ *symmetry_group = link_sym_grp; free_triangulation(*symmetric_triangulation); /* NULL is OK */ copy_triangulation(*manifold, symmetric_triangulation); } free_symmetry_group(manifold_sym_grp); if (link_sym_grp != *symmetry_group) free_symmetry_group(link_sym_grp); manifold_sym_grp = NULL; link_sym_grp = NULL; } while (drill_one_curve(manifold, &the_desired_curves) == func_OK); }