Skip to content

Commit

Permalink
Making maximal cusp area matrix the default for the respective method…
Browse files Browse the repository at this point in the history
…s. Also doing a lot of updating of documentation.
  • Loading branch information
unhyperbolic committed Nov 20, 2024
1 parent afea1b8 commit e50101f
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 209 deletions.
16 changes: 8 additions & 8 deletions cython/core/triangulation.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 <https://github.com/3-manifolds/SnapPy/blob/master/python/decorated_isosig.py>`_.
Expand Down Expand Up @@ -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`.
Expand Down
13 changes: 8 additions & 5 deletions doc_src/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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') <snappy.Manifold.cusp_area_matrix>`
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 <snappy.Triangulation.triangulation_isosig>`. Also
for :meth:`~snappy.Triangulation.triangulation_isosig`. Also
fixing a subtle bug where the filling coefficients returned by
:meth:`triangulation_isosig <snappy.Triangulation.triangulation_isosig>` were
not canonical when ``ignore_curve_orientations = True``.

- :meth:`canonical_retriangulation <snappy.Manifold.canonical_retriangulation>`
- :meth:`~snappy.Manifold.canonical_retriangulation`
and
:meth:`isometry_signature <snappy.Manifold.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::
Expand Down
213 changes: 125 additions & 88 deletions python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 <verify-primer>` 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 <verify-primer>`.
: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' "
Expand All @@ -365,39 +382,58 @@ 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::
>>> M = Manifold("4_1")
>>> 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 <verify-primer>` 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)
Expand All @@ -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 <verify-primer>` 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 [
Expand Down
4 changes: 2 additions & 2 deletions python/canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_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!
Expand Down
Loading

0 comments on commit e50101f

Please sign in to comment.