Beispiel #1
0
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;
}
Beispiel #3
0
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;
}
Beispiel #4
0
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;
}
Beispiel #5
0
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, &copy_of_manifold0);
		copy_triangulation(manifold1, &copy_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, &copy_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, &copy_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 != &copy_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, &copy);

        /*
         *  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(    &copy,
                                        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, &copy);

    /*
     *  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);
}