Triangulation *Dirichlet_to_triangulation( WEPolyhedron *polyhedron) { /* * When the polyhedron represents a closed manifold, * try_Dirichlet_to_triangulation() drills out an arbitrary curve * to express the manifold as a Dehn filling. Usually the arbitrary * curve turns out to be isotopic to a geodesic, but not always. * Here we try several repetitions of try_Dirichlet_to_triangulation(), * if necessary, to obtain a hyperbolic Dehn filling. * Fortunately in almost all cases (about 95% in my informal tests) * try_Dirichlet_to_triangulation() get a geodesic on its first try. */ int count; Triangulation *triangulation; triangulation = try_Dirichlet_to_triangulation(polyhedron); count = MAX_TRIES; while ( --count >= 0 && triangulation != NULL && triangulation->solution_type[filled] != geometric_solution && triangulation->solution_type[filled] != nongeometric_solution) { free_triangulation(triangulation); triangulation = try_Dirichlet_to_triangulation(polyhedron); } return triangulation; }
static FuncResult fill_first_cusp( Triangulation **manifold) { Triangulation *new_manifold; int count; Boolean fill_cusp[2] = {TRUE, FALSE}; if (get_num_cusps(*manifold) != 2) uFatalError("fill_first_cusp", "symmetry_group_closed"); new_manifold = fill_cusps(*manifold, fill_cusp, get_triangulation_name(*manifold), FALSE); if (new_manifold == NULL) return func_failed; /* this seems unlikely */ /* * Usually the complete solution will be geometric, even if * the filled solution is not. But occasionally we'll get * a new_manifold which didn't simplify sufficiently, and * we'll need to rattle it around to get a decent triangulation. */ count = MAX_RANDOMIZATIONS; while (--count >= 0 && get_complete_solution_type(new_manifold) != geometric_solution) randomize_triangulation(new_manifold); free_triangulation(*manifold); *manifold = new_manifold; new_manifold = NULL; return func_OK; }
FuncResult compute_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link, Triangulation **symmetric_triangulation, Boolean *is_full_group) { Triangulation *simplified_manifold; FuncResult result; /* * Make sure the variables used to pass back our results * are all initially empty. */ if (*symmetry_group_of_manifold != NULL || *symmetry_group_of_link != NULL || *symmetric_triangulation != NULL) uFatalError("compute_symmetry_group", "symmetry_group"); /* * If the space isn't a manifold, return func_bad_input. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) return func_bad_input; /* * Whether the manifold is cusped or not, we want to begin * by getting rid of "unnecessary" cusps. */ simplified_manifold = fill_reasonable_cusps(manifold); if (simplified_manifold == NULL) return func_failed; /* * Split into cases according to whether the manifold is * closed or cusped (i.e. whether all cusps are filled or not). */ if (all_cusps_are_filled(simplified_manifold) == TRUE) result = compute_closed_symmetry_group( simplified_manifold, symmetry_group_of_manifold, symmetric_triangulation, is_full_group); else { result = compute_cusped_symmetry_group( simplified_manifold, symmetry_group_of_manifold, symmetry_group_of_link); *is_full_group = TRUE; } free_triangulation(simplified_manifold); return result; }
FuncResult compute_isometries( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric, IsometryList **isometry_list, IsometryList **isometry_list_of_links) { Triangulation *simplified_manifold0, *simplified_manifold1; IsometryList *the_isometry_list, *the_isometry_list_of_links; FuncResult result; /* * Make sure the variables used to pass back our results * are initially empty. */ if ((isometry_list != NULL && *isometry_list != NULL) || (isometry_list_of_links != NULL && *isometry_list_of_links != NULL)) uFatalError("compute_isometries", "isometry"); /* * If one of the spaces isn't a manifold, return func_bad_input. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold0) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold1) == FALSE) return func_bad_input; /* * Check whether the manifolds are obviously nonhomeomorphic. * * (In the interest of a robust, beyond-a-shadow-of-a-doubt algorithm, * stick to discrete invariants like the number of cusps and the * first homology group, and avoid real-valued invariants like * the volume which require judging when two floating point numbers * are equal.) [96/12/6 Comparing canonical triangulations * relies on having at least a vaguely correct hyperbolic structure, * so it should be safe to reject manifolds whose volumes differ * by more than, say, 0.01.] */ if (count_unfilled_cusps(manifold0) != count_unfilled_cusps(manifold1) || same_homology(manifold0, manifold1) == FALSE || ( manifold0->solution_type[filled] == geometric_solution && manifold1->solution_type[filled] == geometric_solution && fabs(volume(manifold0, NULL) - volume(manifold1, NULL)) > CRUDE_VOLUME_EPSILON)) { *are_isometric = FALSE; return func_OK; } /* * Whether the actual manifolds (after taking into account Dehn * fillings) have cusps or not, we want to begin by getting rid * of "unnecessary" cusps. */ simplified_manifold0 = fill_reasonable_cusps(manifold0); if (simplified_manifold0 == NULL) return func_failed; simplified_manifold1 = fill_reasonable_cusps(manifold1); if (simplified_manifold1 == NULL) return func_failed; /* * Split into cases according to whether the manifolds are * closed or cusped (i.e. whether all cusps are filled or not). * The above tests insure that either both are closed or both * are cusped. */ if (all_cusps_are_filled(simplified_manifold0) == TRUE) result = compute_closed_isometry( simplified_manifold0, simplified_manifold1, are_isometric); else { result = compute_cusped_isometries( simplified_manifold0, simplified_manifold1, &the_isometry_list, &the_isometry_list_of_links); if (result == func_OK) { *are_isometric = the_isometry_list->num_isometries > 0; if (isometry_list != NULL) *isometry_list = the_isometry_list; else free_isometry_list(the_isometry_list); if (isometry_list_of_links != NULL) *isometry_list_of_links = the_isometry_list_of_links; else free_isometry_list(the_isometry_list_of_links); } } /* * We no longer need the simplified manifolds. */ free_triangulation(simplified_manifold0); free_triangulation(simplified_manifold1); return result; }
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; } }
static FuncResult drill_one_curve( Triangulation **manifold, MergedMultiLength *remaining_curves) { int i; int num_curves; DualOneSkeletonCurve **the_curves; int desired_index; Complex filled_length; Triangulation *new_manifold; int count; /* * See what curves are drillable. */ dual_curves(*manifold, MAX_DUAL_CURVE_LENGTH, &num_curves, &the_curves); if (num_curves == 0) return func_failed; desired_index = -1; for (i = 0; i < num_curves; i++) { get_dual_curve_info(the_curves[i], NULL, &filled_length, NULL); if ( fabs(remaining_curves->length - filled_length.real) < LENGTH_EPSILON && fabs(remaining_curves->torsion - fabs(filled_length.imag)) < TORSION_EPSILON && ( ( remaining_curves->pos_multiplicity > 0 && filled_length.imag > ZERO_TORSION_EPSILON ) || ( remaining_curves->neg_multiplicity > 0 && filled_length.imag < -ZERO_TORSION_EPSILON ) || ( remaining_curves->zero_multiplicity > 0 && fabs(filled_length.imag) < ZERO_TORSION_EPSILON ) ) ) { desired_index = i; break; } } if (desired_index == -1) { free_dual_curves(num_curves, the_curves); return func_failed; } new_manifold = drill_cusp(*manifold, the_curves[desired_index], get_triangulation_name(*manifold)); if (new_manifold == NULL) { free_dual_curves(num_curves, the_curves); return func_failed; } /* * Usually the complete solution will be geometric, even if * the filled solution is not. But occasionally we'll get * a new_manifold which didn't simplify sufficiently, and * we'll need to rattle it around to get a decent triangulation. */ count = MAX_RANDOMIZATIONS; while (--count >= 0 && get_complete_solution_type(new_manifold) != geometric_solution) randomize_triangulation(new_manifold); /* * Set the new Dehn filling coefficient to (1, 0) * to recover the closed manifold. */ set_cusp_info(new_manifold, get_num_cusps(new_manifold) - 1, FALSE, 1.0, 0.0); do_Dehn_filling(new_manifold); free_dual_curves(num_curves, the_curves); free_triangulation(*manifold); *manifold = new_manifold; new_manifold = NULL; if (filled_length.imag > ZERO_TORSION_EPSILON) remaining_curves->pos_multiplicity--; else if (filled_length.imag < -ZERO_TORSION_EPSILON) remaining_curves->neg_multiplicity--; else remaining_curves->zero_multiplicity--; remaining_curves->total_multiplicity--; return func_OK; }
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); }