Interface to SnapPy

SnapPy is an open source software for low-dimensional topology. From the home-page:

Note

SnapPy is a program for studying the topology and geometry of 3-manifolds, with a focus on hyperbolic structures. It runs on Mac OS X, Linux, and Windows, and combines a link editor and 3D-graphics for Dirichlet domains and cusp neighborhoods with a powerful command-line interface based on the Python programming language. You can see it in action, learn how to install it, and watch the tutorial.

The SnapPy interface will only work if the optional Sage package SnapPy is installed. The interface lets you send certain Sage objects to SnapPy, run SnapPy functions, import certain SnapPy expressions to Sage, or any combination of the above.

To send a Sage object sobj to SnapPy, call snappy(sobj). This exports the Sage object to SnapPy and returns a new Sage object wrapping the SnapPy expression/variable, so that you can use the SnapPy variable from within Sage. You can then call SnapPy functions on the new object; for example:

sage: A = AbelianGroup([5,15,0,0]); A
Multiplicative Abelian group isomorphic to C5 x C15 x Z x Z
sage: As = snappy(A); As
Z/5 + Z/15 + Z + Z
sage: As.order()
'infinite'
>>> from sage.all import *
>>> A = AbelianGroup([Integer(5),Integer(15),Integer(0),Integer(0)]); A
Multiplicative Abelian group isomorphic to C5 x C15 x Z x Z
>>> As = snappy(A); As
Z/5 + Z/15 + Z + Z
>>> As.order()
'infinite'

In the above example the order of the group is obtained using SnapPy’s order method.

To see SnapPy’s output you can simply print the SnapPy wrapper object. However if you want to import SnapPy’s output back to Sage, call the SnapPy wrapper object’s sage() method. This method returns a native Sage object:

sage: K = Knots().from_table(8, 21); K
Knot represented by 8 crossings
sage: Ks = snappy(K); Ks
<Link: 1 comp; 8 cross>
sage: Ks.goeritz_matrix()
[-2  1  0]
[ 1 -4  2]
[ 0  2  1]
sage: Ks.sage() == K
True
[Python]
>>> from sage.all import *
>>> K = Knots().from_table(Integer(8), Integer(21)); K
Knot represented by 8 crossings
>>> Ks = snappy(K); Ks
<Link: 1 comp; 8 cross>
>>> Ks.goeritz_matrix()
[-2  1  0]
[ 1 -4  2]
[ 0  2  1]
>>> Ks.sage() == K
True

If you want to run a SnapPy function and don’t already have the input in the form of a Sage object, then it might be simpler to input a string expr to snappy(expr). This string will be evaluated as if you had typed it into SnapPy:

sage: M1 = snappy("Manifold('m125')"); M1
m125(0,0)(0,0)
>>> from sage.all import *
>>> M1 = snappy("Manifold('m125')"); M1
m125(0,0)(0,0)

Alternatively, all constructors of SnapPy classes can be used directly as attributes of the interface:

sage: M2 = snappy.Manifold('m125'); M2
m125(0,0)(0,0)
sage: M1 == M2
True
[Python]
>>> from sage.all import *
>>> M2 = snappy.Manifold('m125'); M2
m125(0,0)(0,0)
>>> M1 == M2
True

Finally, if you just want to use a SnapPy command line from within Sage, the IPython magic function %snappy dumps you into an interactive command-line SnapPy session. As long as you work in this environment the prompt is snappy:. To finish the environment type CTRL+D:

sage: %snappy                                 # not tested

--> Switching to SnapPy <--

snappy: M = Manifold('9_42')
None
snappy: M.volume()
4.05686022423682
snappy: M.cusp_info('shape')
[-4.27893631592295 + 1.95728679749950*I]

--> Exiting back to Sage <--

sage:                                         # not tested
>>> from sage.all import *
>>> %snappy                                 # not tested

--> Switching to SnapPy <--

snappy: M = Manifold('9_42')
None
snappy: M.volume()
4.05686022423682
snappy: M.cusp_info('shape')
[-4.27893631592295 + 1.95728679749950*I]

--> Exiting back to Sage <--

>>>                                         # not tested

Complicated translations

The sobj.sage() method tries to convert a SnapPy object to a Sage object. In many cases, it will just work. In particular, it should be able to convert expressions entirely consisting of:

  • numbers, i.e. integers, floats, complex numbers;

  • functions and named constants also present in Sage, where:

    • Sage knows how to translate the function or constant’s name from SnapPy’s, or

    • the Sage name for the function or constant is trivially related to SnapPy’s;

  • symbolic variables whose names don’t pathologically overlap with objects already defined in Sage.

This method will not work when SnapPy’s output includes:

  • strings;

  • functions unknown to Sage;

  • SnapPy functions with different parameters/parameter order to the Sage equivalent.

AUTHORS:

  • Sebastian Oehms (2026): first version.

class sage.interfaces.snappy.SnapPy(high_precision=False)[source]

Bases: PythonInternalInterface

Interface to the SnapPy interpreter.

EXAMPLES:

sage: K = Knots().from_table(8, 21)
sage: Ks = snappyhp(K); Ks
<Link: 1 comp; 8 cross>
sage: M = Ks.exterior()
sage: success, rho = M.verify_hyperbolicity(); success
True
>>> from sage.all import *
>>> K = Knots().from_table(Integer(8), Integer(21))
>>> Ks = snappyhp(K); Ks
<Link: 1 comp; 8 cross>
>>> M = Ks.exterior()
>>> success, rho = M.verify_hyperbolicity(); success
True

More examples can be found in the module header.

class sage.interfaces.snappy.SnapPyElement(parent, value, is_name=False, name=None)[source]

Bases: PythonInternalElement

Element class of the SnapPy interface.

Its instances are usually constructed via the instance call of its parent. It wrapes the SnapPy library for this object. In a session SnapPy methods can be obtained using tab completion.

EXAMPLES:

sage: K = Knots().from_table(8, 21)
sage: Ks = snappy(K)
sage: Ms = Ks.exterior(); Ms
unnamed link(0,0)
sage: type(Ms)
<class 'sage.interfaces.snappy.SnapPyElement'>
sage: Gs = Ms.fundamental_group(); Gs
Generators:
   a,b
Relators:
   aabABBAbABabbaBabbaBAbABBAb
sage: type(Gs)
<class 'sage.interfaces.snappy.SnapPyElement'>
sage: G = Gs.sage(); G
Finitely presented group < a, b | a^2*b*a^-1*b^-2*a^-1*b*a^-1*(b^-1*a*b^2*a)^2*b^-1*a^-1*b*a^-1*b^-2*a^-1*b >
sage: Fs = Ks.faces(); Fs
[[<CS 7, 3>, <CS 6, 3>, <CS 5, 1>, <CS 0, 0>], [<CS 7, 2>, <CS 0, 1>, <CS 1, 1>, <CS 2, 1>, <CS 3, 0>],
 [<CS 7, 1>, <CS 3, 1>, <CS 6, 1>], [<CS 7, 0>, <CS 6, 2>], [<CS 6, 0>, <CS 3, 2>, <CS 4, 0>, <CS 5, 0>],
 [<CS 5, 3>, <CS 4, 1>], [<CS 5, 2>, <CS 4, 2>, <CS 2, 3>, <CS 1, 3>, <CS 0, 3>],
 [<CS 4, 3>, <CS 3, 3>, <CS 2, 2>], [<CS 2, 0>, <CS 1, 2>], [<CS 1, 0>, <CS 0, 2>]]
sage: type(Fs)
<class 'sage.interfaces.snappy.SnapPyElement'>
sage: F = Fs.sage(); F
[[<CS 7, 3>, <CS 6, 3>, <CS 5, 1>, <CS 0, 0>],
 [<CS 7, 2>, <CS 0, 1>, <CS 1, 1>, <CS 2, 1>, <CS 3, 0>],
 [<CS 7, 1>, <CS 3, 1>, <CS 6, 1>],
 [<CS 7, 0>, <CS 6, 2>],
 [<CS 6, 0>, <CS 3, 2>, <CS 4, 0>, <CS 5, 0>],
 [<CS 5, 3>, <CS 4, 1>],
 [<CS 5, 2>, <CS 4, 2>, <CS 2, 3>, <CS 1, 3>, <CS 0, 3>],
 [<CS 4, 3>, <CS 3, 3>, <CS 2, 2>],
 [<CS 2, 0>, <CS 1, 2>],
 [<CS 1, 0>, <CS 0, 2>]]
sage: Fs00 = Fs[0][0]; Fs00
<CS 7, 3>
sage: type(Fs00)
<class 'sage.interfaces.snappy.SnapPyElement'>
sage: F00 = Fs00.sage()
sage: type(F00)
<class 'spherogram.links.links_base.CrossingStrand'>
sage: Fs.sage()[0][0] == F00
True
>>> from sage.all import *
>>> K = Knots().from_table(Integer(8), Integer(21))
>>> Ks = snappy(K)
>>> Ms = Ks.exterior(); Ms
unnamed link(0,0)
>>> type(Ms)
<class 'sage.interfaces.snappy.SnapPyElement'>
>>> Gs = Ms.fundamental_group(); Gs
Generators:
   a,b
Relators:
   aabABBAbABabbaBabbaBAbABBAb
>>> type(Gs)
<class 'sage.interfaces.snappy.SnapPyElement'>
>>> G = Gs.sage(); G
Finitely presented group < a, b | a^2*b*a^-1*b^-2*a^-1*b*a^-1*(b^-1*a*b^2*a)^2*b^-1*a^-1*b*a^-1*b^-2*a^-1*b >
>>> Fs = Ks.faces(); Fs
[[<CS 7, 3>, <CS 6, 3>, <CS 5, 1>, <CS 0, 0>], [<CS 7, 2>, <CS 0, 1>, <CS 1, 1>, <CS 2, 1>, <CS 3, 0>],
 [<CS 7, 1>, <CS 3, 1>, <CS 6, 1>], [<CS 7, 0>, <CS 6, 2>], [<CS 6, 0>, <CS 3, 2>, <CS 4, 0>, <CS 5, 0>],
 [<CS 5, 3>, <CS 4, 1>], [<CS 5, 2>, <CS 4, 2>, <CS 2, 3>, <CS 1, 3>, <CS 0, 3>],
 [<CS 4, 3>, <CS 3, 3>, <CS 2, 2>], [<CS 2, 0>, <CS 1, 2>], [<CS 1, 0>, <CS 0, 2>]]
>>> type(Fs)
<class 'sage.interfaces.snappy.SnapPyElement'>
>>> F = Fs.sage(); F
[[<CS 7, 3>, <CS 6, 3>, <CS 5, 1>, <CS 0, 0>],
 [<CS 7, 2>, <CS 0, 1>, <CS 1, 1>, <CS 2, 1>, <CS 3, 0>],
 [<CS 7, 1>, <CS 3, 1>, <CS 6, 1>],
 [<CS 7, 0>, <CS 6, 2>],
 [<CS 6, 0>, <CS 3, 2>, <CS 4, 0>, <CS 5, 0>],
 [<CS 5, 3>, <CS 4, 1>],
 [<CS 5, 2>, <CS 4, 2>, <CS 2, 3>, <CS 1, 3>, <CS 0, 3>],
 [<CS 4, 3>, <CS 3, 3>, <CS 2, 2>],
 [<CS 2, 0>, <CS 1, 2>],
 [<CS 1, 0>, <CS 0, 2>]]
>>> Fs00 = Fs[Integer(0)][Integer(0)]; Fs00
<CS 7, 3>
>>> type(Fs00)
<class 'sage.interfaces.snappy.SnapPyElement'>
>>> F00 = Fs00.sage()
>>> type(F00)
<class 'spherogram.links.links_base.CrossingStrand'>
>>> Fs.sage()[Integer(0)][Integer(0)] == F00
True