示例#1
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;
    }
}
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);
}