From e50101f4fb669942a0ec3fa80a159cb7fdd8d13a Mon Sep 17 00:00:00 2001 From: Matthias Goerner <1239022+unhyperbolic@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:17:09 -0800 Subject: [PATCH] Making maximal cusp area matrix the default for the respective methods. Also doing a lot of updating of documentation. --- cython/core/triangulation.pyx | 16 +-- doc_src/index.rst | 13 +- python/__init__.py | 213 ++++++++++++++++++------------- python/canonical.py | 4 +- python/cusps/cusp_area_matrix.py | 144 ++++++++++----------- python/drilling/__init__.py | 50 ++++---- python/isometry_signature.py | 6 +- python/len_spec/__init__.py | 29 +++-- python/test_cases.py | 27 ++++ 9 files changed, 293 insertions(+), 209 deletions(-) diff --git a/cython/core/triangulation.pyx b/cython/core/triangulation.pyx index 38089bea..8a97b234 100644 --- a/cython/core/triangulation.pyx +++ b/cython/core/triangulation.pyx @@ -2955,8 +2955,8 @@ cdef class Triangulation(): 'dLQbcccdero' When specifying :attr:`ignore_orientation = False`, the result - encodes the orientation (if orientable). This means that the result is - different if the triangulation is chiral:: + encodes the orientation (if orientable). Now the result is + different if we change the orientation of a chiral triangulation:: >>> T = Triangulation('m015') >>> T.triangulation_isosig(decorated=False, ignore_orientation=False) @@ -2986,14 +2986,14 @@ cdef class Triangulation(): #. Dehn-fillings (if present). - * We say that the Dehn-filling coefficients :math:`(m,l)` and - :math:`(-m, -l)` correspond to two different oriented - Dehn-fillings, but the same unoriented Dehn-filling. * By default, the decoration encodes the oriented Dehn-fillings. + That is, we also encodes the orientation of the peripheral curve + that is used for the Dehn-filling (this explanation only + works if the coefficients are integral). By specifying :attr:`ignore_filling_orientations = True`, the decoration encodes the unoriented Dehn-fillings. - That is, it normalizes the coefficients by picking a canonical - pair among :math:`(m,l)` and :math:`(-m,-l)`. + That is, it normalizes the Dehn-filling coefficients by picking + a canonical pair among :math:`(m,l)` and :math:`(-m,-l)`. Details of the encoding are explained in the `SnapPy source code `_. @@ -3084,7 +3084,7 @@ cdef class Triangulation(): part of the isomorphism signature. The decoration can still capture the the orientation. More, precisely, the result of :meth:`.triangulation_isosig` depends on - the orientation (if the triangulation is orientable and chiral) if one + the orientation (if the triangulation is orientable and chiral) if any of the following is true: #. :attr:`ignore_orientation = False`. diff --git a/doc_src/index.rst b/doc_src/index.rst index f57653bf..c0b05828 100644 --- a/doc_src/index.rst +++ b/doc_src/index.rst @@ -66,18 +66,21 @@ News :alt: Paper plane coming out of a cusp of m125 - A faster and more robust algorithm to the compute maximal cusp area matrix. - Example: - :meth:`Manifold("otet10_00027").cusp_area_matrix(method='maximal') ` + The new algorithm is now the default for + :meth:`~snappy.Manifold.cusp_area_matrix`, + :meth:`~snappy.Manifold.cusp_areas`, + :meth:`~snappy.Manifold.short_slopes` and + :meth:`~snappy.Manifold.cusp_translations`. - New options ``ignore_curves`` and ``ignore_filling_orientations`` - for :meth:`triangulation_isosig `. Also + for :meth:`~snappy.Triangulation.triangulation_isosig`. Also fixing a subtle bug where the filling coefficients returned by :meth:`triangulation_isosig ` were not canonical when ``ignore_curve_orientations = True``. - - :meth:`canonical_retriangulation ` + - :meth:`~snappy.Manifold.canonical_retriangulation` and - :meth:`isometry_signature ` fail with + :meth:`~snappy.Manifold.isometry_signature` fail with exceptions rather than silently returning ``None``. In particular, it now safe to compare isometry signatures (without further checks) to determine whether M and N are isometric hyperbolic manifolds:: diff --git a/python/__init__.py b/python/__init__.py index 7e333fae..27f89e2e 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -31,7 +31,7 @@ InsufficientPrecisionError, NonorientableManifoldError) -from typing import Union, Tuple, List +from typing import Union, Tuple, List, Optional # Subclass to be able to monkey-patch class Triangulation(_TriangulationLP): @@ -281,69 +281,86 @@ def isomorphisms_to(self, from .cusps import cusp_areas_from_matrix -def cusp_areas(manifold, policy='unbiased', - method='trigDependentTryCanonize', - verified=False, bits_prec=None, first_cusps=[]): +def cusp_areas(manifold, + policy : str = 'unbiased', + method : str = 'maximal', + verified : bool = False, + bits_prec : Optional[int] = None, + first_cusps : List[int] = []): """ - Picks areas for the cusps such that the corresponding cusp - neighborhoods are disjoint. By default, the ``policy`` is - ``unbiased`` which means that the cusp neighborhoods are blown up - simultaneously with a cusp neighborhood stopping to grow when it - touches another cusp neighborhood or itself:: + Returns a list of areas, one for each cusp. The cusp neighborhoods + defined by these areas are embedded and disjoint. Furthermore, these + neighborhoods are maximal in that they fail to be embedded or + disjoint if any cusp neighborhood is enlarged (unless :attr:`method` + is set to a value different from the default). + + There are different policies how these cusp neighborhoods are found. + + The default :attr:`policy` is ``unbiased``. This means that the + cusp neighborhoods are blown up simultaneously and a cusp neighborhood + stops growing when it touches any cusp neighborhood including itself:: >>> M = Manifold("s776") >>> M.cusp_areas() # doctest: +NUMERIC9 [2.64575131106459, 2.64575131106459, 2.64575131106459] - Alternatively, ``policy='greedy'`` means that the first cusp - neighborhood is blown up until it touches itself, then the second - cusp neighborhood is blown up until it touches itself or the first - cusp neighborhood, ...:: + Alternatively, :attr:`policy='greedy'` can be specified. This means + that the first cusp neighborhood is blown up until it touches itself, + then the second cusp neighborhood is blown up until it touches itself + or the first cusp neighborhood, and so on:: >>> M.cusp_areas(policy='greedy') # doctest: +NUMERIC9 [5.29150262212918, 1.32287565553230, 1.32287565553229] - To specify cusps to be blown up first, and in which order to blow - them up, set ``first_cusps`` to the appropriate list of cusps. + Use :attr:`first_cusps` to specify the order in which the cusp + neighborhoods are blown up:: - >>> M = Manifold('o9_44210') - >>> M.cusp_areas(policy='greedy') # doctest: +NUMERIC9 - [7.053940530873898, 3.2712450270, 2.7091590087] - >>> M.cusp_areas(policy='greedy', first_cusps=[]) # doctest: +NUMERIC9 - [7.053940530873898, 3.2712450270, 2.7091590087] - >>> M.cusp_areas(policy='greedy', first_cusps=[0,]) # doctest: +NUMERIC9 - [7.053940530873898, 3.2712450270, 2.7091590087] - >>> M.cusp_areas(policy='greedy', first_cusps=[0,1]) # doctest: +NUMERIC9 - [7.053940530873898, 3.2712450270, 2.7091590087] - >>> M.cusp_areas(policy='greedy', first_cusps=[0,1,2]) # doctest: +NUMERIC9 - [7.053940530873898, 3.2712450270, 2.7091590087] - >>> M.cusp_areas(policy='greedy', first_cusps=[0,2,1]) # doctest: +NUMERIC9 - [7.053940530873898, 2.3513135103, 3.7690945490] - >>> M.cusp_areas(policy='greedy', first_cusps=[1,]) # doctest: +NUMERIC9 - [4.0302253322, 5.725527974287718, 1.5478612583] - - ``cusp_areas`` is implemented using - :py:meth:`Manifold.cusp_area_matrix` and the same arguments - (``method``, ``verified``, ``bits_prec``) are accepted. For - example, verified computations are supported:: - - sage: M=Manifold("v2854") - sage: M.cusp_areas(verified=True) # doctest: +NUMERIC9 - [3.6005032476?, 3.6005032476?] + >>> M.cusp_areas(policy='greedy', first_cusps=[1,0,2]) # doctest: +NUMERIC9 + [1.32287565553230, 5.29150262212918, 1.32287565553229] + + An incomplete list can be given to :attr:`first_cusps`. In this case, + the list is automatically completed by appending the remaining cusps in + order. Thus, the above call is equivalent to:: + + >>> M.cusp_areas(policy='greedy', first_cusps=[1]) # doctest: +NUMERIC9 + [1.32287565553230, 5.29150262212918, 1.32287565553229] - If ``method='maximal'``, ``policy='unbiased'`` and - ``verified=True``, the result is an invariant of the manifold with - labeled cusps and the corresponding cusp neighborhoods are maximal - in that every cusp neighborhood is touching some (not necessarily - distinct) cusp neighborhood. + Under the hood, this method is using + :meth:`~snappy.Manifold.cusp_area_matrix`. - Area of the cusp neighborhood touching itself for a one-cusped - manifold:: + **Verified computation** - sage: M=Manifold("v1959") - sage: M.cusp_areas(method='maximal', verified=True, bits_prec=100) # doctest: +NUMERIC15 - [7.15679216175810579?] + If :attr:`verified = False`, floating-point issues can arise resulting in + incorrect values. The method can be made + :ref:`verified ` by passing :attr:`verified = True`:: + sage: M=Manifold("s776") + sage: M.cusp_areas(verified=True) # doctest: +NUMERIC9 + [2.64575131107?, 2.64575131107?, 2.64575131107?] + + :param verified: + Use :ref:`verified computation `. + :param bits_prec: + Precision used for computation. Increase if computation + did not succeed or a more precise result is desired. + :param method: + Passed to :meth:`~snappy.Manifold.cusp_area_matrix`. If set + to a value different from the default ``maximal``, the cusp + neighborhoods stop growing when the corresponding value + in the computed cusp area matrix is exceeded. At this point, + the cusp neighborhood might not necessarily touch any other + cusp neighborhood since we do not use the maximal cusp area + matrix. + :param policy: + Specifies process of choosing cusp neighborhoods. + Either ``unbiased`` or ``greedy``, see above. + :param first_cusps: + Preference order of cusps. + Only relevant if :attr:`policy='greedy'`, see above. + :return: + Areas of maximal embedded and disjoint cusp neighborhoods + (default). Or areas of some embedded and disjoint cusp + neighborhoods (if :attr:`method` switches to older algorithm). """ if policy not in ['unbiased', 'greedy']: raise ValueError("policy passed to cusp_areas must be 'unbiased' " @@ -365,26 +382,29 @@ def cusp_areas(manifold, policy='unbiased', def short_slopes(manifold, length=6, - policy='unbiased', method='trigDependentTryCanonize', - verified=False, bits_prec=None, first_cusps=[]): + policy : str = 'unbiased', + method : str = 'maximal', + verified : bool = False, + bits_prec : Optional[int] = None, + first_cusps : List[int] = []): """ - Picks disjoint cusp neighborhoods (using - :py:meth:`Manifold.cusp_areas`, thus the same arguments can be - used) and returns for each cusp the slopes that have length less - or equal to given ``length`` (defaults to 6) when measured on the - boundary of the cusp neighborhood:: + Returns a list of short slopes (for Dehn-fillings) for each cusp. + + That is, the method uses :meth:`~snappy.Manifold.cusp_areas` to find + (maximal) embedded and disjoint cusp neighborhoods. It uses the boundaries + of these cusp neighborhoods to measure the length of a peripheral curve. + For each cusp, it determines all simple peripheral curves shorter than + the given :attr:`length` (which defaults to 6). The result is a list + of the corresponding slopes for each cusp:: >>> M = Manifold("otet20_00022") >>> M.short_slopes() [[(1, 0), (-1, 1), (0, 1)], [(1, 0)]] - When ``verified=True``, the result is guaranteed - to contain all slopes of length less or equal to given ``length`` - (and could contain additional slopes if precision is not high - enough):: + It takes the same arguments as :meth:`~snappy.Manifold.cusp_areas`:: - sage: M.short_slopes(verified = True) - [[(1, 0), (-1, 1), (0, 1)], [(1, 0)]] + >>> M.short_slopes(policy = 'greedy') + [[(1, 0)], [(1, 0)]] The ten exceptional slopes of the figure-eight knot:: @@ -392,12 +412,28 @@ def short_slopes(manifold, >>> M.short_slopes() [[(1, 0), (-4, 1), (-3, 1), (-2, 1), (-1, 1), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1)]] - Two more slopes appear when increasing length to 2 pi:: + Two more slopes appear when increasing length to :math:`2\\pi`:: >>> M.short_slopes(length = 6.283185307179586) [[(1, 0), (-5, 1), (-4, 1), (-3, 1), (-2, 1), (-1, 1), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1)]] - When using verified computations, ``length`` is converted into the ``RealIntervalField`` of requested precision:: + **Verified computation** + + If :attr:`verified = False`, floating-point issues can arise resulting in + incorrect values. The method can be made + :ref:`verified ` by passing :attr:`verified = True`:: + + sage: M = Manifold("4_1") + sage: M.short_slopes(verified = True) + [[(1, 0), (-4, 1), (-3, 1), (-2, 1), (-1, 1), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1)]] + + If :attr:`verified = True`, the result is guaranteed to contain all short + slopes and might contain additional slopes (with lengths slightly longer + than the given :attr:`length` but this could not be proven using the + interval estimates). + + The given :attr:`length` is cast to a SageMath ``RealIntervalField`` of the + given precision if :attr:`verified = True`:: sage: from sage.all import pi sage: M.short_slopes(length = 2 * pi, verified = True, bits_prec = 100) @@ -420,42 +456,43 @@ def short_slopes(manifold, ManifoldHP.short_slopes = short_slopes -def cusp_translations(manifold, policy='unbiased', - method='trigDependentTryCanonize', - verified=False, bits_prec=None, first_cusps=[]): +def cusp_translations(manifold, + policy : str = 'unbiased', + method : str = 'maximal', + verified : bool = False, + bits_prec : Optional[int] = None, + first_cusps : List[int] = []): """ - Picks disjoint cusp neighborhoods and returns the respective - (complex) Euclidean translations of the meridian and longitude for - each cusp. The method takes the same arguments as - :py:meth:`Manifold.cusp_areas` and uses that method to pick the - cusp neighborhood. The result is a list of pairs, the second entry - corresponding to a longitude is always real:: + Returns a list of the (complex) Euclidean translations corresponding to the + meridian and longitude of each cusp. + + That is, the method uses :meth:`~snappy.Manifold.cusp_areas` to find + (maximal) embedded and disjoint cusp neighborhoods. It then uses the + boundaries of these cusp neighborhoods to measure the meridian and + longitude of each cusp. The result is a pair for each cusp. The first + entry of the pair corresponds to the meridian and is complex. The + second entry corresponds to the longitude and is always real:: >>> M = Manifold("s776") >>> M.cusp_translations() # doctest: +NUMERIC9 [(0.500000000000000 + 1.32287565553230*I, 2.00000000000000), (0.500000000000000 + 1.32287565553230*I, 2.00000000000000), (0.499999999999999 + 1.32287565553230*I, 2.00000000000000)] - Arguments such as ``policy='greedy'`` are interpreted the same way as - for :py:meth:`Manifold.cusp_areas`:: + It takes the same arguments as :meth:`~snappy.Manifold.cusp_areas`:: - >>> M.cusp_translations(policy = 'greedy', first_cusps = [], bits_prec = 100) # doctest: +NUMERIC21 + >>> M.cusp_translations(policy = 'greedy') # doctest: +NUMERIC9 [(0.70710678118654752440084436210 + 1.8708286933869706927918743662*I, 2.8284271247461900976033774484), (0.35355339059327376220042218105 + 0.93541434669348534639593718308*I, 1.4142135623730950488016887242), (0.35355339059327376220042218105 + 0.93541434669348534639593718308*I, 1.4142135623730950488016887242)] - and can return verified intervals:: - - sage: M.cusp_translations(method = 'maximal', verified = True) # doctest: +NUMERIC9 - [(0.50000000000? + 1.32287565553?*I, 2.00000000000?), (0.500000000000? + 1.32287565554?*I, 2.00000000000?), (0.500000000000? + 1.32287565554?*I, 2.00000000000?)] + **Verified computations** - that are guaranteed to contain the true translations of cusp neighborhoods - verified to be disjoint (the element corresponding to a longitude - is always in a ``RealIntervalField``). + If :attr:`verified = False`, floating-point issues can arise resulting in + incorrect values. The method can be made + :ref:`verified ` by passing :attr:`verified = True`:: - **Remark:** The default ``method = 'trigDependentTryCanonize'`` is - (potentially) non-deterministic and thus the result of - - [ M.cusp_translations()[i] for i in range(M.num_cusps()) ] + sage: M.cusp_translations(verified = True) # doctest: +NUMERIC9 + [(0.50000000000? + 1.32287565553?*I, 2.00000000000?), (0.500000000000? + 1.32287565554?*I, 2.00000000000?), (0.500000000000? + 1.32287565554?*I, 2.00000000000?)] - might not correspond to disjoint cusp neighborhoods. + Note that the first element of each pair is a SageMath ``ComplexIntervalField`` and + the second element a ``RealIntervalField``. """ return [ diff --git a/python/canonical.py b/python/canonical.py index e40b5961..a5996956 100644 --- a/python/canonical.py +++ b/python/canonical.py @@ -106,11 +106,11 @@ def _canonical_retriangulation( Interval arithmetic can only be used to verify the canonical cell decomposition if all cells are tetrahedral. For non-tetrahedral cells, the method automatically switches to - exact methods to verify the canonical cell decomposition. That is, using + exact methods to verify the canonical cell decomposition. That is, it uses snap-like methods (`LLL-algorithm `_) to guess a representation of the - shapes in the shape field, it then uses exact arithmetic to verify the + shapes in the shape field. It then uses exact arithmetic to verify the shapes form a valid geometric structure and compute the necessary tilts to verify the canonical cell decomposition. Note that this can take a long time! diff --git a/python/cusps/cusp_area_matrix.py b/python/cusps/cusp_area_matrix.py index 08bb301f..946810c7 100644 --- a/python/cusps/cusp_area_matrix.py +++ b/python/cusps/cusp_area_matrix.py @@ -2,79 +2,79 @@ from .trig_cusp_area_matrix import triangulation_dependent_cusp_area_matrix from ..verify.maximal_cusp_area_matrix import legacy_verified_maximal_cusp_area_matrix -def cusp_area_matrix(manifold, method='trigDependentTryCanonize', - verified=False, bits_prec=None): - r""" - This function returns a matrix that can be used to check whether - cusp neighborhoods of areas a\ :sub:`0`\ , ..., a\ :sub:`m-1` are - disjoint: the cusp neighborhoods about cusp i and j are - disjoint (respectively, the cusp neighborhood embeds if i and j - are equal) if a\ :sub:`i` * a\ :sub:`j` is less than or equal to - the entry (i,j) of the cusp area matrix. Note that the "if" - becomes "if and only if" if we pick the "maximal cusp area - matrix". - - This function can operate in different ways (determined by - ``method``). By default (``method='trigDependentTryCanonize'``), - it returns a result which can be suboptimal and non-deterministic - but is quicker to compute and sufficies for many applications:: - - >>> M = Manifold("s776") - >>> M.cusp_area_matrix() # doctest: +NUMERIC12 - [28.0000000000000 7.00000000000000 6.99999999999999] - [7.00000000000000 21.4375000000000 7.00000000000000] - [6.99999999999999 7.00000000000000 21.4375000000000] - - If ``method='maximal'`` is specified, the result is the "maximal - cusp area matrix", thus it is optimal and an invariant of the - manifold with labeled cusps. Note that the "maximal cusp area - matrix" is only available as verified computation and thus - requires passing ``verified = True``:: - - sage: M.cusp_area_matrix(method = 'maximal', verified=True) # doctest: +NUMERIC6 - [28.0000000000? 7.0000000000? 7.0000000000?] - [ 7.0000000000? 28.000000000? 7.00000000000?] - [ 7.0000000000? 7.00000000000? 28.00000000?] - - If ``verified = True`` is specified and ``method`` is not - ``maximal``, the entries are all guaranteed to be less than the - corresponding ones in the maximal cusp area matrix (more - precisely, the lower end point of the interval is guaranteed to be - less than the true value of the corresponding maximal cusp area - matrix entry):: - - sage: M.cusp_area_matrix(verified=True, bits_prec=70) # doctest: +NUMERIC15 - [ 28.000000000000000? 7.0000000000000000? 7.0000000000000000?] - [ 7.0000000000000000? 21.4375000000000000? 7.0000000000000000?] - [ 7.0000000000000000? 7.0000000000000000? 21.4375000000000000?] - - **For expert users** - - Besides the two values above, ``method`` can be ``trigDependent``: - this result is also fast to compute by making the assumption that - cusp neighborhoods are not only disjoint but also in "standard - form" with respect to the triangulation (i.e., when lifting of a - cusp neighborhood to a horoball in the universal cover, it - intersects a geodesic tetrahedron in three but not four - faces). ``trigDependentTryCanonize`` is similar to - ``trigDependent`` but tries to "proto-canonize" (a copy of) the - triangulation first since this often produces a matrix that is - closer to the maximal cusp area matrix, for example:: - - >>> M = Manifold("o9_35953") - >>> M.cusp_area_matrix(method = 'trigDependent') # doctest: +NUMERIC9 - [72.9848715318467 12.7560424258060] - [12.7560424258060 6.65567118002656] - >>> M.cusp_area_matrix(method = 'trigDependentTryCanonize') # doctest: +NUMERIC9 - [72.9848715318466 12.7560424258060] - [12.7560424258060 62.1043047674605] - - Compare to maximal area matrix:: - - sage: M.cusp_area_matrix(method = 'maximal', verified = True, bits_prec = 100) # doctest: +NUMERIC15 - [ 72.984871531846664? 12.7560424258059765562778?] - [12.7560424258059765562778? 62.104304767460978078?] +from typing import Optional +def cusp_area_matrix( + manifold, + method : str = 'maximal', + verified : bool = False, + bits_prec : Optional[int] = None): + """ + Returns the maximal cusp area matrix :math:`(A_{ij})` where + :math:`A_{ij}` is defined as follows. + Let :math:`C_i` and :math:`C_j` be the (open) cusp neighborhoods about cusp + :math:`i` and :math:`j`. Let :math:`A(C_i)` and :math:`A(C_j)` be the + areas of :math:`C_i` and :math:`C_j`, respectively. Then, :math:`C_i` + and :math:`C_j` are embedded (if :math:`i = j`) or disjoint (otherwise) + if and only if :math:`A(C_i)A(C_j) \\leq A_{ij}`. + + Here is an example:: + + >>> M = Manifold("L6a5") + >>> M.cusp_area_matrix() # doctest: +NUMERIC12 + [27.9999999999996 7.00000000000000 7.00000000000000] + [7.00000000000000 27.9999999999999 7.00000000000000] + [7.00000000000000 7.00000000000000 28.0000000000001] + + + **Faster lower bounds** + + This section can be skipped by most users! + + Prior to SnapPy version 3.2, the algorithm to compute the maximal cusp + area matrix was much slower and required :attr:`verified = True` and + SageMath. Thus, in prior versions, :attr:`method` defaulted to + ``trigDependentTryCanonize``. This meant, that, by default, + :meth:`~snappy.Manifold.cusp_area_matrix` only returned + (some) lower bounds for the maximal cusp area matrix entries. + + These lower bounds can still be accessed:: + + >>> M.cusp_area_matrix(method = 'trigDependentTryCanonize') + [21.4375000000000 7.00000000000000 7.00000000000000] + [7.00000000000000 28.0000000000000 7.00000000000000] + [7.00000000000000 7.00000000000000 28.0000000000000] + + If :attr:`method = 'trigDependent'` or + :attr:`method = 'trigDependenyTryCanonize'`, the result is triangulation + dependent or not even deterministic, respectively. + Furthermore, if :attr:`verified = True` is also set, while the left + endpoints of the intervals are lower bounds for the maximal cusp area + matrix entries, the right endpoints are meaningless and could be smaller + or larger than the maximal cusp area matrix entries. + + **Verified computation** + + If :attr:`verified = False`, floating-point issues can arise resulting in + incorrect values. The method can be made + :ref:`verified ` by passing :attr:`verified = True`:: + + sage: M.cusp_area_matrix(verified=True) # doctest: +NUMERIC3 + [ 28.0000? 7.000000000000? 7.00000000000?] + [7.000000000000? 28.000000? 7.00000000000?] + [ 7.00000000000? 7.00000000000? 28.00000?] + + :param verified: + Use :ref:`verified computation `. + :param bits_prec: + Precision used for computation. Increase if computation + did not succeed or a more precise result is desired. + :param method: + Switches to older algorithms giving lower bounds when + ``trigDependentTryCanonize`` and ``trigDependent``. + :return: + Maximal cusp area matrix (default) or lower bounds + (if :attr:`method` switches to older algorithm). """ if method == 'maximal': diff --git a/python/drilling/__init__.py b/python/drilling/__init__.py index 1814be3e..dee0e5af 100644 --- a/python/drilling/__init__.py +++ b/python/drilling/__init__.py @@ -32,34 +32,33 @@ def drill_word(manifold, verbose : bool = False) -> Manifold: """ Drills the geodesic corresponding to the given word in the unsimplified - fundamental group. + fundamental group. Here is an example:: >>> M = Manifold("m004") - >>> M.length_spectrum(1.2, include_words = True, grouped = False) - mult length topology parity word - 1 1.08707014499574 - 1.72276844987009*I circle + a - 1 1.08707014499574 + 1.72276844987009*I circle + bC + >>> M.length_spectrum_alt(max_len=1.2) # doctest: +NUMERIC9 + [Length Core curve Word + 1.08707014499574 + 1.72276844987009*I - bC, + 1.08707014499574 - 1.72276844987009*I - a] >>> N = M.drill_word('a') >>> N.identify() [m129(0,0)(0,0), 5^2_1(0,0)(0,0), L5a1(0,0)(0,0), ooct01_00001(0,0)(0,0)] - The last cusp of the new manifold corresponds to the drilled - geodesic and the longitude and meridian for that cusp are chosen such that - ``(1,0)``-filling results in the given (undrilled) manifold. The orientation - of the new longitude is chosen so that it is parallel to the closed geodesic. - That is, the new longitude is homotopic to the closed geodesic when embedding - the drilled manifold into the given manifold. + The last cusp of the resulting manifold corresponds to the drilled + geodesic. The longitude and meridian for that cusp are chosen such that + ``(1,0)``-filling the last cusp results in the given (undrilled) manifold:: - >>> N.dehn_fill((1,0),1) + >>> N.dehn_fill((1,0),-1) >>> M.is_isometric_to(N) True >>> N.cusp_info(1)['core_length'] # doctest: +NUMERIC9 1.08707014499574 - 1.72276844987009*I + + The orientation of the new longitude is chosen so that it is parallel to + the closed geodesic. That is, the new longitude is homotopic to the closed + geodesic when embedding the drilled manifold into the given manifold. - If the drilled geodesic coincides with a core curve of a filled cusp, the - cusp is unfilled instead and the longitude and meridian changed so that - the above again applies. The cusp order is also changed so that the unfilled - cusp becomes the last cusp. + If the given geodesic coincides with a core curve of a filled cusp, the + cusp is unfilled instead:: >>> M = Manifold("m004(2,3)") >>> M.volume() # doctest: +NUMERIC9 @@ -73,11 +72,18 @@ def drill_word(manifold, m004_drilled(0,0) >>> N.num_cusps() 1 - >>> N.dehn_fill((1,0)) + + In this case, the peripheral information is also + updated such that the above remark about ``(1,0)``-filling applies again:: + + >>> N.dehn_fill((1,0), -1) >>> N.volume() # doctest: +NUMERIC9 1.73712388065 - An example where we drill the core geodesic:: + That is, the longitude and meridian of the unfilled cusps are reinstalled + and the cusps reindexed so that the unfilled cusp becomes the last cusp. + + Here is another example where we drill the core geodesic:: >>> M = Manifold("v2986(3,4)") >>> N = M.drill_word('EdFgabcGEdFgaDcc') @@ -141,8 +147,8 @@ def drill_words(manifold, several geodesics simultaneously. It takes a list of words in the unsimplified fundamental group. - Here is an example where we drill two geodesics. One geodesic is the - core curve corresponding to the third cusp. The other geodesic is not + Here is an example where we drill two geodesics. One of the geodesics is + the core curve corresponding to the third cusp. The other geodesic is not a core curve:: >>> M=Manifold("t12047(0,0)(1,3)(1,4)(1,5)") @@ -171,8 +177,8 @@ def drill_words(manifold, We obtain the given (undrilled) manifold by ``(1,0)``-filling the last n cusps. - >>> N.dehn_fill((1,0), 3) - >>> N.dehn_fill((1,0), 4) + >>> N.dehn_fill((1,0), -2) + >>> N.dehn_fill((1,0), -1) >>> M.is_isometric_to(N) True >>> [ info.get('core_length') for info in N.cusp_info() ] # doctest: +NUMERIC9 diff --git a/python/isometry_signature.py b/python/isometry_signature.py index d312c787..c1983e49 100644 --- a/python/isometry_signature.py +++ b/python/isometry_signature.py @@ -80,9 +80,9 @@ def isometry_signature( If all cusps are filled, we are in the closed case. In this case, the isometry signature gives the resulting closed hyperbolic 3-manifold as - canonical surgery on a hyperbolic 1-cusped manifold which is encoded by its - isometry signature. Only orientable manifolds are supported in the closed - case. + canonical surgery on a hyperbolic 1-cusped manifold (which is encoded by + its isometry signature). Only orientable manifolds are supported in the + closed case. >>> M = Manifold("v2000(1,3)") >>> M.isometry_signature() diff --git a/python/len_spec/__init__.py b/python/len_spec/__init__.py index 4a1fb659..d15a8a86 100644 --- a/python/len_spec/__init__.py +++ b/python/len_spec/__init__.py @@ -72,9 +72,9 @@ def length_spectrum_alt_gen(manifold, This method uses a different algorithm than :meth:`length_spectrum `. In particular, - it does not compute the Dirichlet domain. It allows for + it does not compute the Dirichlet domain. It also allows for :ref:`verified computations `. - It is also implemented in python and thus + It is implemented in python and thus typically slower than :meth:`length_spectrum `. But there are also some cases where it is significantly faster. In particular, this applies to spun triangulations such as ``m004(21,10)``. @@ -100,7 +100,7 @@ def length_spectrum_alt_gen(manifold, After drilling and filling, less than a second to compute:: >>> N = M.drill_word('a') - >>> N.dehn_fill((1,0),-1) # This is isometric to m125(0,0)(34,55) + >>> N.dehn_fill((1,0),-1) # N is now isometric to o9_00639 but as a surgery m125(0,0)(34,55) >>> spec = N.length_spectrum_alt_gen() >>> next(spec) # doctest: +NUMERIC9 Length Core curve Word @@ -127,7 +127,8 @@ def length_spectrum_alt_gen(manifold, sage: next(spec).length.real().lower() # doctest: +NUMERIC12 0.94135129037387168886341739832 - To illustrate some pitfalls, here is a potential result of the method: + To illustrate some pitfalls, here is an example of a potential a result + of the method: +----------------------+-------+ | Real length interval | Word | @@ -159,11 +160,11 @@ def length_spectrum_alt_gen(manifold, the manifold:: sage: M = Manifold("m004") - sage: spec = M.length_spectrum_alt_gen(verified=True, bits_prec=100) + sage: spec = M.length_spectrum_alt_gen(verified=True) sage: g = next(spec) # g might or might not be shortest geodesic sage: systole = g.length.real() # But interval is large enough to contain systole - sage: systole # doctest: +NUMERIC21 - 1.08707014499573909978528? + sage: systole # doctest: +NUMERIC6 + 1.08707015? :param bits_prec: Precision used for the computation. Increase if computation did @@ -265,14 +266,24 @@ def length_spectrum_alt(manifold, to include the :attr:`count` shortest geodesics and might include additional geodesics. + **Verified systole** + + Even though, the first reported geodesic might not be the shortest, we + obtain an interval containing the systole as follows, also see + :meth:`~snappy.Manifold.length_spectrum_alt_gen`:: + + sage: M = Manifold("m004") + sage: M.length_spectrum_alt(count=1, verified=True, bits_prec=100)[0].length.real() # doctest: +NUMERIC21 + 1.0870701449957390997853? + :param count: Number of shortest geodesics to list. The actual result might contain additional geodesics. Exactly one of :attr:`count` and - :attr:`max_len` have to be specified. + :attr:`max_len` has to be specified. :param max_len: Cut-off length for geodesics. The actual result includes all geodesics up to the given length and might include additional - geodesics. Exactly one of :attr:`count` and :attr:`max_len` have + geodesics. Exactly one of :attr:`count` and :attr:`max_len` has to be specified. :param bits_prec: Precision used for the computation. Increase if computation did diff --git a/python/test_cases.py b/python/test_cases.py index 890991d0..7dbf226f 100644 --- a/python/test_cases.py +++ b/python/test_cases.py @@ -228,6 +228,33 @@ ... ValueError: Canonical retriangulation needs all cusps to be complete. +Cusp areas +---------- + +>>> M = Manifold('o9_44210') +>>> M.cusp_areas(policy='greedy') # doctest: +NUMERIC9 +[7.053940530873898, 3.2712450270, 2.7091590087] +>>> M.cusp_areas(policy='greedy', first_cusps=[]) # doctest: +NUMERIC9 +[7.053940530873898, 3.2712450270, 2.7091590087] +>>> M.cusp_areas(policy='greedy', first_cusps=[0,]) # doctest: +NUMERIC9 +[7.053940530873898, 3.2712450270, 2.7091590087] +>>> M.cusp_areas(policy='greedy', first_cusps=[0,1]) # doctest: +NUMERIC9 +[7.053940530873898, 3.2712450270, 2.7091590087] +>>> M.cusp_areas(policy='greedy', first_cusps=[0,1,2]) # doctest: +NUMERIC9 +[7.053940530873898, 3.2712450270, 2.7091590087] +>>> M.cusp_areas(policy='greedy', first_cusps=[0,2,1]) # doctest: +NUMERIC9 +[7.053940530873898, 2.3513135103, 3.7690945490] +>>> M.cusp_areas(policy='greedy', first_cusps=[1,]) # doctest: +NUMERIC9 +[2.30025338030798, 10.0315765558665, 0.883442685721903] + +Cusp translations +----------------- + +>>> M = Manifold("s776") +>>> M.cusp_translations(policy = 'greedy', first_cusps = [], bits_prec = 100) # doctest: +NUMERIC21 +[(0.70710678118654752440084436210 + 1.8708286933869706927918743662*I, 2.8284271247461900976033774484), (0.35355339059327376220042218105 + 0.93541434669348534639593718308*I, 1.4142135623730950488016887242), (0.35355339059327376220042218105 + 0.93541434669348534639593718308*I, 1.4142135623730950488016887242)] + + """ if not __doc__: