passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
sage/knots/knotinfo.py
ADDED
@@ -0,0 +1,2900 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# sage.doctest: needs sage.graphs sage.groups
|
3
|
+
r"""
|
4
|
+
Access to the KnotInfo database
|
5
|
+
|
6
|
+
This module contains the class :class:`KnotInfoBase` which is derived from
|
7
|
+
:class:`Enum` and provides knots and links listed in the databases at the
|
8
|
+
web-pages `KnotInfo <https://knotinfo.math.indiana.edu/>`__
|
9
|
+
and `LinkInfo <https://linkinfo.sitehost.iu.edu/>`__ as its items.
|
10
|
+
|
11
|
+
This interface contains a set of about twenty knots and links statically as
|
12
|
+
demonstration cases. The complete database can be installed as an optional Sage
|
13
|
+
package using
|
14
|
+
|
15
|
+
- ``sage -i database_knotinfo`` (does not install if the current version is present)
|
16
|
+
- ``sage -f database_knotinfo`` (installs even if the current version is present)
|
17
|
+
|
18
|
+
This will install a `Python wrapper <https://github.com/soehms/database_knotinfo#readme>`__
|
19
|
+
for the original databases in Sage. This wrapper performs an automatic progress
|
20
|
+
of version numbers. For more details and further install instructions please see
|
21
|
+
the corresponding web-page.
|
22
|
+
|
23
|
+
To perform all the doctests concerning the usage of the database on the installation
|
24
|
+
add the option ``-c``. In this case (for instance ``sage -f -c database_knotinfo``)
|
25
|
+
the installation breaks on failing tests.
|
26
|
+
|
27
|
+
The installation of the complete database will be necessary in order to have
|
28
|
+
access to all the properties recorded in the databases, as well.
|
29
|
+
|
30
|
+
If the entire database is installed as explained above, the import instructions
|
31
|
+
for :class:`KnotInfo` and :class:`KnotInfoSeries`, which can be seen in the opening
|
32
|
+
lines of the examples, are unnecessary.
|
33
|
+
|
34
|
+
Be aware that there are a couple of conventions used differently on KnotInfo as
|
35
|
+
in Sage.
|
36
|
+
|
37
|
+
For different conventions regarding normalization of the polynomial invariants see
|
38
|
+
the according documentation of :meth:`KnotInfoBase.homfly_polynomial`,
|
39
|
+
:meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`.
|
40
|
+
|
41
|
+
Also, note that the braid notation is used according to Sage, even thought in
|
42
|
+
the source where it is taken from, the braid generators are assumed to have a
|
43
|
+
negative crossing which would be opposite to the convention in Sage (see definition
|
44
|
+
3 of
|
45
|
+
:arxiv:`Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links" <math/0401051>`).
|
46
|
+
|
47
|
+
Furthermore, note that not all columns available in the database are visible on the web
|
48
|
+
pages. It is planned to remove non-visible columns from the database in the future (see
|
49
|
+
the `Python Wrapper <https://github.com/soehms/database_knotinfo#readme>`__ for
|
50
|
+
updated information).
|
51
|
+
|
52
|
+
EXAMPLES::
|
53
|
+
|
54
|
+
sage: L = KnotInfo.L4a1_0
|
55
|
+
sage: L.pd_notation()
|
56
|
+
[[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
|
57
|
+
sage: L.pd_notation(original=True)
|
58
|
+
'{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}'
|
59
|
+
sage: L.is_knot()
|
60
|
+
False
|
61
|
+
sage: L.num_components()
|
62
|
+
2
|
63
|
+
|
64
|
+
Items for knots need a leading ``K`` for technical reason::
|
65
|
+
|
66
|
+
sage: K = KnotInfo.K4_1
|
67
|
+
sage: K.is_knot()
|
68
|
+
True
|
69
|
+
|
70
|
+
Injecting the variable name into the namespace::
|
71
|
+
|
72
|
+
sage: KnotInfo.K5_1.inject()
|
73
|
+
Defining K5_1
|
74
|
+
sage: K5_1.dt_notation()
|
75
|
+
[6, 8, 10, 2, 4]
|
76
|
+
|
77
|
+
Defining a link from the original name string::
|
78
|
+
|
79
|
+
sage: KnotInfo('L6a1{1}').inject()
|
80
|
+
Defining L6a1_1
|
81
|
+
sage: L6a1_1.is_alternating()
|
82
|
+
True
|
83
|
+
|
84
|
+
Obtaining an instance of :class:`~sage.groups.braid.Braid`::
|
85
|
+
|
86
|
+
sage: L.braid()
|
87
|
+
s1^-2*s0^-1*s1*s0^-1
|
88
|
+
sage: type(_)
|
89
|
+
<class 'sage.groups.braid.BraidGroup_class_with_category.element_class'>
|
90
|
+
|
91
|
+
Obtaining an instance of :class:`Link`::
|
92
|
+
|
93
|
+
sage: l = L.link(); l
|
94
|
+
Link with 2 components represented by 4 crossings
|
95
|
+
sage: type(l)
|
96
|
+
<class 'sage.knots.link.Link'>
|
97
|
+
|
98
|
+
If you have `SnapPy <https://snappy.math.uic.edu/index.html>`__ installed inside
|
99
|
+
Sage, you can obtain an instance of :class:`~spherogram.links.links_base.Link`,
|
100
|
+
too::
|
101
|
+
|
102
|
+
sage: # optional - snappy
|
103
|
+
sage: L6 = KnotInfo.L6a1_0
|
104
|
+
sage: l6s = L6.link(snappy=True); l6s
|
105
|
+
...
|
106
|
+
<Link: 2 comp; 6 cross>
|
107
|
+
sage: type(l6s)
|
108
|
+
<class 'spherogram.links.invariants.Link'>
|
109
|
+
sage: l6 = L6.link()
|
110
|
+
sage: l6 == l6s.sage_link()
|
111
|
+
True
|
112
|
+
sage: L6.link(L6.items.name, snappy=True)
|
113
|
+
<Link L6a1: 2 comp; 6 cross>
|
114
|
+
sage: l6sn = _
|
115
|
+
sage: l6s == l6sn
|
116
|
+
False
|
117
|
+
sage: l6m = l6.mirror_image()
|
118
|
+
sage: l6sn.sage_link().is_isotopic(l6m)
|
119
|
+
True
|
120
|
+
|
121
|
+
But observe that the name conversion to SnapPy does not distinguish orientation
|
122
|
+
types::
|
123
|
+
|
124
|
+
sage: L6b = KnotInfo.L6a1_1
|
125
|
+
sage: L6b.link(L6b.items.name, snappy=True) # optional - snappy
|
126
|
+
<Link L6a1: 2 comp; 6 cross>
|
127
|
+
sage: _.PD_code() == l6sn.PD_code() # optional - snappy
|
128
|
+
True
|
129
|
+
|
130
|
+
Obtaining the HOMFLY-PT polynomial::
|
131
|
+
|
132
|
+
sage: L.homfly_polynomial()
|
133
|
+
-v^-1*z - v^-3*z - v^-3*z^-1 + v^-5*z^-1
|
134
|
+
sage: _ == l.homfly_polynomial(normalization='vz')
|
135
|
+
True
|
136
|
+
|
137
|
+
|
138
|
+
Obtaining the original string from the database for an arbitrary property::
|
139
|
+
|
140
|
+
sage: K[K.items.classical_conway_name] # optional - database_knotinfo
|
141
|
+
'4_1'
|
142
|
+
|
143
|
+
Further methods::
|
144
|
+
|
145
|
+
sage: K.crossing_number()
|
146
|
+
4
|
147
|
+
sage: K.gauss_notation()
|
148
|
+
[-1, 2, -3, 1, -4, 3, -2, 4]
|
149
|
+
sage: K.dt_notation()
|
150
|
+
[4, 6, 8, 2]
|
151
|
+
sage: K.determinant()
|
152
|
+
5
|
153
|
+
sage: K.symmetry_type()
|
154
|
+
'fully amphicheiral'
|
155
|
+
sage: _ == K[K.items.symmetry_type]
|
156
|
+
True
|
157
|
+
sage: K.is_reversible()
|
158
|
+
True
|
159
|
+
sage: K.is_amphicheiral()
|
160
|
+
True
|
161
|
+
sage: K.jones_polynomial() # needs sage.symbolic
|
162
|
+
t^2 - t - 1/t + 1/t^2 + 1
|
163
|
+
sage: K.kauffman_polynomial()
|
164
|
+
a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2
|
165
|
+
sage: K.alexander_polynomial()
|
166
|
+
t^2 - 3*t + 1
|
167
|
+
|
168
|
+
Using the ``column_type`` of a property::
|
169
|
+
|
170
|
+
sage: def select_column(i):
|
171
|
+
....: return i.column_type() != i.types.OnlyLinks and K[i] == 'Y'
|
172
|
+
sage: [i.column_name() for i in K.items if select_column(i)] # optional - database_knotinfo
|
173
|
+
['Alternating', 'Fibered', 'Quasialternating', 'Adequate']
|
174
|
+
|
175
|
+
You can launch web-pages attached to the links::
|
176
|
+
|
177
|
+
sage: # not tested
|
178
|
+
sage: K.diagram()
|
179
|
+
True
|
180
|
+
sage: L.diagram(single=True)
|
181
|
+
True
|
182
|
+
sage: L.knot_atlas_webpage()
|
183
|
+
True
|
184
|
+
sage: K.knotilus_webpage()
|
185
|
+
True
|
186
|
+
|
187
|
+
and the description web-pages of the properties::
|
188
|
+
|
189
|
+
sage: K.items.positive.description_webpage() # not tested
|
190
|
+
True
|
191
|
+
|
192
|
+
To see all the properties available in this interface you can use "tab-completion".
|
193
|
+
For example type ``K.items.`` and than hit the :kbd:`Tab` key. You can select the item
|
194
|
+
you want from the list. If you know some first letters type them first to obtain a
|
195
|
+
reduced selection list.
|
196
|
+
|
197
|
+
In a similar way you may select the knots and links. Here you have to type ``KnotInfo.``
|
198
|
+
or ``KnotInfo.L7`` before stroking the :kbd:`Tab` key. In the latter case the selection list
|
199
|
+
will be reduced to proper links with 7 crossings.
|
200
|
+
|
201
|
+
Finally there is a method :meth:`Link.get_knotinfo` of class :class:`Link` to find an instance
|
202
|
+
in the KnotInfo database::
|
203
|
+
|
204
|
+
sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
|
205
|
+
....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
|
206
|
+
....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
|
207
|
+
sage: L.get_knotinfo()
|
208
|
+
KnotInfo['K0_1']
|
209
|
+
|
210
|
+
|
211
|
+
REFERENCES:
|
212
|
+
|
213
|
+
- `KnotInfo <https://knotinfo.math.indiana.edu/>`__
|
214
|
+
- `LinkInfo <https://linkinfo.sitehost.iu.edu/>`__
|
215
|
+
|
216
|
+
|
217
|
+
AUTHORS:
|
218
|
+
|
219
|
+
- Sebastian Oehms August 2020: initial version
|
220
|
+
- Sebastian Oehms June 2022: add :meth:`conway_polynomial` and :meth:`khovanov_polynomial` (:issue:`33969`)
|
221
|
+
|
222
|
+
Thanks to Chuck Livingston and Allison Moore for their support. For further acknowledgments see the corresponding hompages.
|
223
|
+
"""
|
224
|
+
|
225
|
+
|
226
|
+
##############################################################################
|
227
|
+
# Copyright (C) 2020 Sebastian Oehms <seb.oehms@gmail.com>
|
228
|
+
#
|
229
|
+
# This program is free software: you can redistribute it and/or modify
|
230
|
+
# it under the terms of the GNU General Public License as published by
|
231
|
+
# the Free Software Foundation, either version 2 of the License, or
|
232
|
+
# (at your option) any later version.
|
233
|
+
# https://www.gnu.org/licenses/
|
234
|
+
##############################################################################
|
235
|
+
|
236
|
+
|
237
|
+
from enum import Enum
|
238
|
+
from sage.misc.cachefunc import cached_method
|
239
|
+
from sage.misc.lazy_import import lazy_import
|
240
|
+
from sage.misc.sage_eval import sage_eval
|
241
|
+
from sage.structure.sage_object import SageObject
|
242
|
+
from sage.structure.unique_representation import UniqueRepresentation
|
243
|
+
from sage.rings.integer_ring import ZZ
|
244
|
+
from sage.knots.knot import Knots
|
245
|
+
from sage.databases.knotinfo_db import KnotInfoColumns, db
|
246
|
+
|
247
|
+
lazy_import('sage.groups.braid', 'BraidGroup')
|
248
|
+
|
249
|
+
|
250
|
+
def eval_knotinfo(string, locals={}, to_tuple=True):
|
251
|
+
r"""
|
252
|
+
Preparse a string from the KnotInfo database and evaluate it by ``sage_eval``.
|
253
|
+
|
254
|
+
INPUT:
|
255
|
+
|
256
|
+
- ``string`` -- string that gives a value of some database entry
|
257
|
+
- ``locals`` -- dictionary of locals passed to ``sage_eval``
|
258
|
+
|
259
|
+
EXAMPLES::
|
260
|
+
|
261
|
+
sage: from sage.knots.knotinfo import eval_knotinfo
|
262
|
+
sage: L = KnotInfo.L4a1_0
|
263
|
+
sage: L.braid_notation(original=True)
|
264
|
+
'{3, {-2, -2, -1, 2, -1}}'
|
265
|
+
sage: eval_knotinfo(_)
|
266
|
+
(3, (-2, -2, -1, 2, -1))
|
267
|
+
sage: KnotInfo.K13a_1.kauffman_polynomial() # optional - database_knotinfo # indirect doctest
|
268
|
+
Traceback (most recent call last):
|
269
|
+
...
|
270
|
+
NotImplementedError: this value is not provided by the database
|
271
|
+
"""
|
272
|
+
if not string:
|
273
|
+
# An empty string in the database Excel spreadsheet indicates that
|
274
|
+
# the property is not provided for that particular knot or link.
|
275
|
+
raise NotImplementedError('this value is not provided by the database')
|
276
|
+
if to_tuple:
|
277
|
+
new_string = string.replace('{', '(')
|
278
|
+
new_string = new_string.replace('}', ')')
|
279
|
+
else:
|
280
|
+
new_string = string.replace('{', '[')
|
281
|
+
new_string = new_string.replace('}', ']')
|
282
|
+
new_string = new_string.replace(';', ',')
|
283
|
+
return sage_eval(new_string, locals=locals)
|
284
|
+
|
285
|
+
|
286
|
+
def knotinfo_int(string):
|
287
|
+
r"""
|
288
|
+
Preparse a string from the KnotInfo database representing an integer.
|
289
|
+
|
290
|
+
INPUT:
|
291
|
+
|
292
|
+
- ``string`` -- string that gives a value of some database entry
|
293
|
+
|
294
|
+
EXAMPLES::
|
295
|
+
|
296
|
+
sage: from sage.knots.knotinfo import knotinfo_int
|
297
|
+
sage: knotinfo_int('7')
|
298
|
+
7
|
299
|
+
sage: KnotInfo.K13a_1.braid_index() # optional - database_knotinfo # indirect doctest
|
300
|
+
Traceback (most recent call last):
|
301
|
+
...
|
302
|
+
NotImplementedError: this integer is not provided by the database
|
303
|
+
"""
|
304
|
+
if not string:
|
305
|
+
# an empty string in the Excel sheet of the database indicates that
|
306
|
+
# the property is not provided for this special knot or link.
|
307
|
+
raise NotImplementedError('this integer is not provided by the database')
|
308
|
+
else:
|
309
|
+
return int(string)
|
310
|
+
|
311
|
+
|
312
|
+
def knotinfo_bool(string):
|
313
|
+
r"""
|
314
|
+
Preparse a string from the KnotInfo database representing a boolean.
|
315
|
+
|
316
|
+
INPUT:
|
317
|
+
|
318
|
+
- ``string`` -- string that gives a value of some database entry
|
319
|
+
|
320
|
+
EXAMPLES::
|
321
|
+
|
322
|
+
sage: from sage.knots.knotinfo import knotinfo_bool
|
323
|
+
sage: knotinfo_bool('Y')
|
324
|
+
True
|
325
|
+
sage: KnotInfo.K13a_1.is_almost_alternating() # optional - database_knotinfo # indirect doctest
|
326
|
+
Traceback (most recent call last):
|
327
|
+
...
|
328
|
+
NotImplementedError: this boolean is not provided by the database
|
329
|
+
"""
|
330
|
+
if not string:
|
331
|
+
# an empty string in the Excel sheet of the database indicates that
|
332
|
+
# the property is not provided for this special knot or link.
|
333
|
+
raise NotImplementedError('this boolean is not provided by the database')
|
334
|
+
if string == 'Y':
|
335
|
+
return True
|
336
|
+
elif string == 'N':
|
337
|
+
return False
|
338
|
+
raise ValueError('%s is not a KnotInfo boolean')
|
339
|
+
|
340
|
+
|
341
|
+
class SymmetryMutant(Enum):
|
342
|
+
r"""
|
343
|
+
Enum to specify the symmetry mutant link of the prime link listed in the
|
344
|
+
KnotInfo and LinkInfo databases. From the KnotInfo description page:
|
345
|
+
|
346
|
+
If a knot is viewed as the oriented diffeomorphism
|
347
|
+
class of an oriented pair, `K = (S_3, S_1)`, with `S_i`
|
348
|
+
diffeomorphic to `S^i`, there are four oriented knots
|
349
|
+
associated to any particular knot `K`. In addition to
|
350
|
+
`K` itself, there is the reverse, `K^r = (S_3, -S_1)`,
|
351
|
+
the concordance inverse, `-K = (-S_3, -S_1)`, and the
|
352
|
+
mirror image, `K^m = (-S_3, S_1)`.
|
353
|
+
"""
|
354
|
+
itself = 's'
|
355
|
+
reverse = 'r'
|
356
|
+
concordance_inverse = 'c'
|
357
|
+
mirror_image = 'm'
|
358
|
+
mixed = 'x' # to be used in connection with KnotInfoSeries
|
359
|
+
unknown = '?'
|
360
|
+
|
361
|
+
def __gt__(self, other):
|
362
|
+
r"""
|
363
|
+
Implement comparison of different items in order to have ``sorted`` work.
|
364
|
+
|
365
|
+
EXAMPLES::
|
366
|
+
|
367
|
+
sage: from sage.knots.knotinfo import SymmetryMutant
|
368
|
+
sage: sorted(SymmetryMutant) # indirect doctest
|
369
|
+
[<SymmetryMutant.mixed: 'x'>,
|
370
|
+
<SymmetryMutant.itself: 's'>,
|
371
|
+
<SymmetryMutant.reverse: 'r'>,
|
372
|
+
<SymmetryMutant.mirror_image: 'm'>,
|
373
|
+
<SymmetryMutant.concordance_inverse: 'c'>,
|
374
|
+
<SymmetryMutant.unknown: '?'>]
|
375
|
+
"""
|
376
|
+
# We use the reversal of the alphabetical order of the values so that
|
377
|
+
# `itself` occurs before the mirrored cases
|
378
|
+
return self.value < other.value
|
379
|
+
|
380
|
+
def rev(self):
|
381
|
+
r"""
|
382
|
+
Return the reverse of ``self``.
|
383
|
+
|
384
|
+
EXAMPLES::
|
385
|
+
|
386
|
+
sage: from sage.knots.knotinfo import SymmetryMutant
|
387
|
+
sage: all( sym.rev().rev() == sym for sym in SymmetryMutant)
|
388
|
+
True
|
389
|
+
"""
|
390
|
+
if self is SymmetryMutant.itself:
|
391
|
+
return SymmetryMutant.reverse
|
392
|
+
elif self is SymmetryMutant.reverse:
|
393
|
+
return SymmetryMutant.itself
|
394
|
+
elif self is SymmetryMutant.mirror_image:
|
395
|
+
return SymmetryMutant.concordance_inverse
|
396
|
+
elif self is SymmetryMutant.concordance_inverse:
|
397
|
+
return SymmetryMutant.mirror_image
|
398
|
+
return self
|
399
|
+
|
400
|
+
def mir(self):
|
401
|
+
r"""
|
402
|
+
Return the mirror image of ``self``.
|
403
|
+
|
404
|
+
EXAMPLES::
|
405
|
+
|
406
|
+
sage: from sage.knots.knotinfo import SymmetryMutant
|
407
|
+
sage: all( sym.mir().mir() == sym for sym in SymmetryMutant)
|
408
|
+
True
|
409
|
+
"""
|
410
|
+
if self is SymmetryMutant.itself:
|
411
|
+
return SymmetryMutant.mirror_image
|
412
|
+
elif self is SymmetryMutant.reverse:
|
413
|
+
return SymmetryMutant.concordance_inverse
|
414
|
+
elif self is SymmetryMutant.mirror_image:
|
415
|
+
return SymmetryMutant.itself
|
416
|
+
elif self is SymmetryMutant.concordance_inverse:
|
417
|
+
return SymmetryMutant.reverse
|
418
|
+
return self
|
419
|
+
|
420
|
+
def matches(self, link):
|
421
|
+
r"""
|
422
|
+
Return the list of other symmetry mutants that give isotopic links
|
423
|
+
with respect to ``link`` and ``self``. For ``self`` is
|
424
|
+
``SymmetryMutant.unknown`` a boolean is returned which is ``True``
|
425
|
+
if the chirality of ``link`` is unknown.
|
426
|
+
|
427
|
+
EXAMPLES::
|
428
|
+
|
429
|
+
sage: from sage.knots.knotinfo import SymmetryMutant
|
430
|
+
sage: SymmetryMutant.itself.matches(KnotInfo.K6_1)
|
431
|
+
[<SymmetryMutant.reverse: 'r'>]
|
432
|
+
sage: SymmetryMutant.mirror_image.matches(KnotInfo.K6_1)
|
433
|
+
[<SymmetryMutant.concordance_inverse: 'c'>]
|
434
|
+
"""
|
435
|
+
rev = link.is_reversible()
|
436
|
+
achp = link.is_amphicheiral(positive=True)
|
437
|
+
ach = link.is_amphicheiral()
|
438
|
+
if self is SymmetryMutant.unknown:
|
439
|
+
if rev is None or ach is None or achp is None:
|
440
|
+
return True
|
441
|
+
else:
|
442
|
+
return False
|
443
|
+
res = []
|
444
|
+
if rev:
|
445
|
+
res.append(self.rev())
|
446
|
+
if achp:
|
447
|
+
res.append(self.mir())
|
448
|
+
if ach:
|
449
|
+
res.append(self.rev().mir())
|
450
|
+
return res
|
451
|
+
|
452
|
+
def is_minimal(self, link) -> bool:
|
453
|
+
r"""
|
454
|
+
Return whether ``self`` is minimal among its matching mutants.
|
455
|
+
|
456
|
+
EXAMPLES::
|
457
|
+
|
458
|
+
sage: from sage.knots.knotinfo import SymmetryMutant
|
459
|
+
sage: SymmetryMutant.itself.is_minimal(KnotInfo.K6_1)
|
460
|
+
True
|
461
|
+
sage: SymmetryMutant.concordance_inverse.is_minimal(KnotInfo.K6_1)
|
462
|
+
False
|
463
|
+
"""
|
464
|
+
if self in [SymmetryMutant.unknown, SymmetryMutant.mixed]:
|
465
|
+
return False
|
466
|
+
matches = self.matches(link)
|
467
|
+
return all(self < other for other in matches)
|
468
|
+
|
469
|
+
|
470
|
+
# ---------------------------------------------------------------------------------
|
471
|
+
# KnotInfoBase
|
472
|
+
# ---------------------------------------------------------------------------------
|
473
|
+
class KnotInfoBase(Enum):
|
474
|
+
r"""
|
475
|
+
Enum class to select the knots and links listed in the databases at the web-pages
|
476
|
+
`KnotInfo <https://knotinfo.math.indiana.edu/>`__ and `LinkInfo <https://linkinfo.sitehost.iu.edu/>`__.
|
477
|
+
|
478
|
+
EXAMPLES::
|
479
|
+
|
480
|
+
sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5]
|
481
|
+
['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1']
|
482
|
+
|
483
|
+
More examples and information can be seen in the module header
|
484
|
+
:mod:`~sage.knots.knotinfo` (by typing)::
|
485
|
+
|
486
|
+
sage: import sage.knots.knotinfo # not tested
|
487
|
+
sage: sage.knots.knotinfo? # not tested
|
488
|
+
|
489
|
+
TESTS:
|
490
|
+
|
491
|
+
sage: KnotInfo.K7_1.inject()
|
492
|
+
Defining K7_1
|
493
|
+
sage: TestSuite(K7_1).run()
|
494
|
+
"""
|
495
|
+
|
496
|
+
def __gt__(self, other):
|
497
|
+
r"""
|
498
|
+
Implement comparison of different items in order to have ``sorted`` work.
|
499
|
+
|
500
|
+
EXAMPLES::
|
501
|
+
|
502
|
+
sage: KnotInfo.L4a1_0 < KnotInfo.L4a1_1 # indirect doctest
|
503
|
+
True
|
504
|
+
sage: KnotInfo.L2a1_0 < KnotInfo.K3_1 # indirect doctest
|
505
|
+
False
|
506
|
+
sage: KnotInfo.K10_3 > KnotInfo.K3_1 # optional - database_knotinfo
|
507
|
+
True
|
508
|
+
"""
|
509
|
+
if self.__class__ is other.__class__:
|
510
|
+
tups = (not self.is_knot(), self.crossing_number(), self.value)
|
511
|
+
tupo = (not other.is_knot(), other.crossing_number(), other.value)
|
512
|
+
return tups > tupo
|
513
|
+
return NotImplemented
|
514
|
+
|
515
|
+
@property
|
516
|
+
def items(self):
|
517
|
+
r"""
|
518
|
+
Return an Enum class to select a column item of the KnotInfo database.
|
519
|
+
|
520
|
+
EXAMPLES::
|
521
|
+
|
522
|
+
sage: L = KnotInfo.L4a1_0
|
523
|
+
sage: it = L.items
|
524
|
+
sage: [i.name for i in it if i.name.startswith('braid')]
|
525
|
+
['braid_index', 'braid_length', 'braid_notation', 'braid_notation_old']
|
526
|
+
sage: L.items.dt_notation.column_name()
|
527
|
+
'DT Notation'
|
528
|
+
|
529
|
+
To check if the item is available for proper links or only knots type::
|
530
|
+
|
531
|
+
sage: it.gauss_notation.column_type()
|
532
|
+
<KnotInfoColumnTypes.KnotsAndLinks: 'B'>
|
533
|
+
sage: it.dt_notation.column_type()
|
534
|
+
<KnotInfoColumnTypes.OnlyKnots: 'K'>
|
535
|
+
|
536
|
+
To see the description of the item in your web browser type::
|
537
|
+
|
538
|
+
sage: it.gauss_notation.description_webpage() # not tested
|
539
|
+
True
|
540
|
+
"""
|
541
|
+
return db.columns()
|
542
|
+
|
543
|
+
@cached_method
|
544
|
+
def __getitem__(self, item):
|
545
|
+
r"""
|
546
|
+
EXAMPLES::
|
547
|
+
|
548
|
+
sage: L = KnotInfo.L4a1_0
|
549
|
+
sage: L[L.items.alternating]
|
550
|
+
'Y'
|
551
|
+
sage: L[L.items.arc_notation]
|
552
|
+
'{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}'
|
553
|
+
sage: L[L.items.braid_notation]
|
554
|
+
'{3, {-2, -2, -1, 2, -1}}'
|
555
|
+
sage: L[0]
|
556
|
+
Traceback (most recent call last):
|
557
|
+
...
|
558
|
+
KeyError: "item must be an instance of <enum 'KnotInfoColumns'>"
|
559
|
+
"""
|
560
|
+
if not isinstance(item, KnotInfoColumns):
|
561
|
+
raise KeyError('item must be an instance of %s' % (KnotInfoColumns))
|
562
|
+
if item.column_type() == item.types.OnlyLinks and self.is_knot():
|
563
|
+
raise KeyError('item not available for knots' % (KnotInfoColumns))
|
564
|
+
if item.column_type() == item.types.OnlyKnots and not self.is_knot():
|
565
|
+
raise KeyError('item not available for links' % (KnotInfoColumns))
|
566
|
+
|
567
|
+
l = db.read(item)
|
568
|
+
ind = db.read_row_dict()[self.name][0]
|
569
|
+
offset = 0
|
570
|
+
if item.column_type() == item.types.OnlyLinks:
|
571
|
+
offset = self._offset_knots()
|
572
|
+
|
573
|
+
return l[ind - offset]
|
574
|
+
|
575
|
+
def _offset_knots(self):
|
576
|
+
r"""
|
577
|
+
Return the list index of the first proper link in a combined
|
578
|
+
list containing knots and proper links together which is the
|
579
|
+
case for columns used for KnotInfo and LinkInfo in common.
|
580
|
+
This index is exactly the total number of knots recorded
|
581
|
+
in KnotInfo.
|
582
|
+
|
583
|
+
EXAMPLES::
|
584
|
+
|
585
|
+
sage: L = KnotInfo.L4a1_0
|
586
|
+
sage: L._offset_knots() # optional - database_knotinfo
|
587
|
+
12966
|
588
|
+
"""
|
589
|
+
return db.read_num_knots()
|
590
|
+
|
591
|
+
@cached_method
|
592
|
+
def _braid_group(self):
|
593
|
+
r"""
|
594
|
+
Return the braid group corresponding to the braid index
|
595
|
+
of ``self``.
|
596
|
+
|
597
|
+
EXAMPLES::
|
598
|
+
|
599
|
+
sage: L = KnotInfo.L4a1_0
|
600
|
+
sage: L._braid_group()
|
601
|
+
Braid group on 3 strands
|
602
|
+
"""
|
603
|
+
try:
|
604
|
+
n = self.braid_index()
|
605
|
+
except NotImplementedError:
|
606
|
+
bn = self.braid_notation()
|
607
|
+
n = max(abs(i) for i in bn) + 1
|
608
|
+
|
609
|
+
if n == 1:
|
610
|
+
return BraidGroup(2)
|
611
|
+
else:
|
612
|
+
return BraidGroup(n)
|
613
|
+
|
614
|
+
@cached_method
|
615
|
+
def _homfly_pol_ring(self, var1, var2):
|
616
|
+
r"""
|
617
|
+
Return the parent Laurent polynomial ring for the HOMFLY-PT
|
618
|
+
polynomial according to Sage's internal one.
|
619
|
+
|
620
|
+
EXAMPLES::
|
621
|
+
|
622
|
+
sage: L = KnotInfo.L4a1_1
|
623
|
+
sage: L._homfly_pol_ring('u', 'v')
|
624
|
+
Multivariate Laurent Polynomial Ring in u, v over Integer Ring
|
625
|
+
"""
|
626
|
+
K3_1 = Knots().from_table(3, 1)
|
627
|
+
return K3_1.homfly_polynomial(var1=var1, var2=var2).parent()
|
628
|
+
|
629
|
+
@cached_method
|
630
|
+
def pd_notation(self, original=False):
|
631
|
+
r"""
|
632
|
+
Return the value of column ``pd_notation`` for this
|
633
|
+
link as a Python list of Python lists. For more information
|
634
|
+
type ``KnotInfo.K0_1.items.pd_notation.description_webpage()``.
|
635
|
+
|
636
|
+
INPUT:
|
637
|
+
|
638
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
639
|
+
``True`` the original table entry is returned as a string
|
640
|
+
|
641
|
+
OUTPUT:
|
642
|
+
|
643
|
+
Python list of python lists each entry of the outer list
|
644
|
+
representing a crossing.
|
645
|
+
|
646
|
+
EXAMPLES::
|
647
|
+
|
648
|
+
sage: L = KnotInfo.L4a1_0
|
649
|
+
sage: L.pd_notation()
|
650
|
+
[[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
|
651
|
+
sage: L.pd_notation(original=True)
|
652
|
+
'{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}'
|
653
|
+
sage: K = KnotInfo.K4_1
|
654
|
+
sage: K.pd_notation()
|
655
|
+
[[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]]
|
656
|
+
"""
|
657
|
+
if self.is_knot():
|
658
|
+
pd_notation = self[self.items.pd_notation]
|
659
|
+
else:
|
660
|
+
pd_notation = self[self.items.pd_notation_vector]
|
661
|
+
|
662
|
+
if original:
|
663
|
+
return pd_notation
|
664
|
+
|
665
|
+
if not pd_notation:
|
666
|
+
# don't forget the unknot
|
667
|
+
return []
|
668
|
+
|
669
|
+
return eval_knotinfo(pd_notation, to_tuple=False)
|
670
|
+
|
671
|
+
@cached_method
|
672
|
+
def dt_notation(self, original=False):
|
673
|
+
r"""
|
674
|
+
Return the value of column ``dt_notation`` for this
|
675
|
+
link as a Python list of Python lists. For more information
|
676
|
+
type ``KnotInfo.K0_1.items.dt_notation.description_webpage()``.
|
677
|
+
|
678
|
+
INPUT:
|
679
|
+
|
680
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
681
|
+
``True`` the original table entry is returned as a string
|
682
|
+
|
683
|
+
OUTPUT:
|
684
|
+
|
685
|
+
Python list of python lists each entry of the outer list
|
686
|
+
representing a crossing.
|
687
|
+
|
688
|
+
EXAMPLES::
|
689
|
+
|
690
|
+
sage: L = KnotInfo.L4a1_0
|
691
|
+
sage: L.dt_notation()
|
692
|
+
[[6, 8], [2, 4]]
|
693
|
+
sage: L.dt_notation(original=True)
|
694
|
+
'[{6, 8}, {2, 4}]'
|
695
|
+
sage: L = KnotInfo.L4a1_0
|
696
|
+
sage: K = KnotInfo.K4_1
|
697
|
+
sage: K.dt_notation()
|
698
|
+
[4, 6, 8, 2]
|
699
|
+
"""
|
700
|
+
if self.is_knot():
|
701
|
+
dt_notation = self[self.items.dt_notation]
|
702
|
+
else:
|
703
|
+
dt_notation = self[self.items.dt_code]
|
704
|
+
|
705
|
+
if original:
|
706
|
+
return dt_notation
|
707
|
+
|
708
|
+
if not dt_notation:
|
709
|
+
# don't forget the unknot
|
710
|
+
return []
|
711
|
+
|
712
|
+
return eval_knotinfo(dt_notation, to_tuple=False)
|
713
|
+
|
714
|
+
@cached_method
|
715
|
+
def gauss_notation(self, original=False):
|
716
|
+
r"""
|
717
|
+
Return the value of column ``gauss_notation`` for this
|
718
|
+
link as a Python list of Python lists. For more information
|
719
|
+
type ``KnotInfo.K0_1.items.gauss_notation.description_webpage()``.
|
720
|
+
|
721
|
+
INPUT:
|
722
|
+
|
723
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
724
|
+
``True`` the original table entry is returned as a string
|
725
|
+
|
726
|
+
EXAMPLES::
|
727
|
+
|
728
|
+
sage: L = KnotInfo.L4a1_0
|
729
|
+
sage: L.gauss_notation()
|
730
|
+
[[1, -3, 2, -4], [3, -1, 4, -2]]
|
731
|
+
sage: L.gauss_notation(original=True)
|
732
|
+
'{{1, -3, 2, -4}, {3, -1, 4, -2}}'
|
733
|
+
"""
|
734
|
+
gauss_notation = self[self.items.gauss_notation]
|
735
|
+
if original:
|
736
|
+
return gauss_notation
|
737
|
+
|
738
|
+
if not gauss_notation:
|
739
|
+
# don't forget the unknot
|
740
|
+
return []
|
741
|
+
|
742
|
+
return eval_knotinfo(gauss_notation, to_tuple=False)
|
743
|
+
|
744
|
+
@cached_method
|
745
|
+
def braid_notation(self, original=False):
|
746
|
+
r"""
|
747
|
+
Return the value of column ``braid_notation`` for this
|
748
|
+
link as a Python tuple (Tietze form). For more information
|
749
|
+
type ``KnotInfo.K0_1.items.braid_notation.description_webpage()``.
|
750
|
+
|
751
|
+
INPUT:
|
752
|
+
|
753
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
754
|
+
``True`` the original table entry is returned as a string
|
755
|
+
|
756
|
+
OUTPUT:
|
757
|
+
|
758
|
+
Python tuple representing the braid whose closure is ``self``
|
759
|
+
in Tietze form.
|
760
|
+
|
761
|
+
..NOTE::
|
762
|
+
|
763
|
+
There has been a major change to braid representatives for
|
764
|
+
proper links since version 2021.10.1. The former braid
|
765
|
+
representatives can be obtained by the column
|
766
|
+
``braid_notation_old`` (see the final example below).
|
767
|
+
|
768
|
+
EXAMPLES::
|
769
|
+
|
770
|
+
sage: L = KnotInfo.L4a1_0
|
771
|
+
sage: L.braid_notation()
|
772
|
+
(-2, -2, -1, 2, -1)
|
773
|
+
sage: L.braid_notation(original=True)
|
774
|
+
'{3, {-2, -2, -1, 2, -1}}'
|
775
|
+
sage: L[L.items.braid_notation_old]
|
776
|
+
'{4, {1, -2, 3, -2, -1, -2, -3, -2}}'
|
777
|
+
|
778
|
+
TESTS:
|
779
|
+
|
780
|
+
Check that :issue:`33966` is fixed::
|
781
|
+
|
782
|
+
sage: KnotInfo.K0_1.braid_notation()
|
783
|
+
(1,)
|
784
|
+
"""
|
785
|
+
braid_notation = self[self.items.braid_notation]
|
786
|
+
if original:
|
787
|
+
return braid_notation
|
788
|
+
|
789
|
+
if not braid_notation:
|
790
|
+
# don't forget the unknot
|
791
|
+
return (1, )
|
792
|
+
|
793
|
+
braid_notation = eval_knotinfo(braid_notation)
|
794
|
+
if type(braid_notation) in (list, tuple):
|
795
|
+
# in some cases there are a pair of braid representations
|
796
|
+
# in the database. If this is the case we select the
|
797
|
+
# corresponding to the braid index.
|
798
|
+
if type(braid_notation[0]) is tuple:
|
799
|
+
i = self.braid_index()
|
800
|
+
for b in braid_notation:
|
801
|
+
if -i < min(b) and max(b) < i:
|
802
|
+
braid_notation = b
|
803
|
+
break
|
804
|
+
|
805
|
+
if not self.is_knot():
|
806
|
+
# in linkinfo the braid_notation includes the braid_index as
|
807
|
+
# first item of a pair
|
808
|
+
braid_notation = braid_notation[1]
|
809
|
+
return braid_notation
|
810
|
+
|
811
|
+
@cached_method
|
812
|
+
def braid_index(self):
|
813
|
+
r"""
|
814
|
+
Return the value of column ``braid_index`` for this
|
815
|
+
link as a Python int.
|
816
|
+
|
817
|
+
OUTPUT:
|
818
|
+
|
819
|
+
Python int giving the minimum of strands needed to
|
820
|
+
represent ``self`` as closure of a braid.
|
821
|
+
|
822
|
+
EXAMPLES::
|
823
|
+
|
824
|
+
sage: L = KnotInfo.L4a1_0
|
825
|
+
sage: L.braid_index()
|
826
|
+
3
|
827
|
+
sage: KnotInfo.K13a_1.inject() # optional - database_knotinfo
|
828
|
+
Defining K13a_1
|
829
|
+
sage: K13a_1.braid_index() # optional - database_knotinfo
|
830
|
+
Traceback (most recent call last):
|
831
|
+
...
|
832
|
+
NotImplementedError: this integer is not provided by the database
|
833
|
+
"""
|
834
|
+
if self.is_knot():
|
835
|
+
return knotinfo_int(self[self.items.braid_index])
|
836
|
+
else:
|
837
|
+
braid_notation = self[self.items.braid_notation]
|
838
|
+
braid_notation = eval_knotinfo(braid_notation)
|
839
|
+
return knotinfo_int(braid_notation[0])
|
840
|
+
|
841
|
+
@cached_method
|
842
|
+
def braid_length(self):
|
843
|
+
r"""
|
844
|
+
Return the value of column ``braid_length`` for this
|
845
|
+
link as a Python int.
|
846
|
+
|
847
|
+
OUTPUT:
|
848
|
+
|
849
|
+
Python int giving the minimum length of a braid word
|
850
|
+
needed to represent ``self`` as closure of a braid.
|
851
|
+
|
852
|
+
EXAMPLES::
|
853
|
+
|
854
|
+
sage: K = KnotInfo.K3_1
|
855
|
+
sage: K.braid_length()
|
856
|
+
3
|
857
|
+
"""
|
858
|
+
return knotinfo_int(self[self.items.braid_length])
|
859
|
+
|
860
|
+
@cached_method
|
861
|
+
def braid(self):
|
862
|
+
r"""
|
863
|
+
Return the braid notation of ``self`` as an instance of :class:`~sage.groups.braid.Braid`.
|
864
|
+
|
865
|
+
EXAMPLES::
|
866
|
+
|
867
|
+
sage: K = KnotInfo.K3_1
|
868
|
+
sage: K.braid()
|
869
|
+
s^3
|
870
|
+
sage: K.braid_notation()
|
871
|
+
(1, 1, 1)
|
872
|
+
sage: KnotInfo.K13n_1448.braid() # optional - database_knotinfo
|
873
|
+
s0^-1*s1*s2*s3*s4*s3^2*s2^-1*s1^-1*s0*s2^-1*s1*(s3*s2)^2*s4^-1*s3*s2*s1^-1*s3*s2^-1*s3
|
874
|
+
"""
|
875
|
+
return self._braid_group()(self.braid_notation())
|
876
|
+
|
877
|
+
@cached_method
|
878
|
+
def num_components(self):
|
879
|
+
r"""
|
880
|
+
Return the number of components of ``self``.
|
881
|
+
|
882
|
+
EXAMPLES::
|
883
|
+
|
884
|
+
sage: KnotInfo.L6a1_0.num_components()
|
885
|
+
2
|
886
|
+
"""
|
887
|
+
return db.read_row_dict()[self.name][1]
|
888
|
+
|
889
|
+
@cached_method
|
890
|
+
def crossing_number(self):
|
891
|
+
r"""
|
892
|
+
Return the minimal number of crossings of ``self``.
|
893
|
+
|
894
|
+
.. NOTE::
|
895
|
+
|
896
|
+
In contrast to the number of crossings displayed for instances
|
897
|
+
of :class:`Link` this number is the minimum over all possible
|
898
|
+
diagrams of the link. The number of crossings displayed in
|
899
|
+
the representation string of :class:`Link` refers to the
|
900
|
+
special diagram which could be larger.
|
901
|
+
|
902
|
+
EXAMPLES::
|
903
|
+
|
904
|
+
sage: KnotInfo.L4a1_0.crossing_number()
|
905
|
+
4
|
906
|
+
sage: KnotInfo.K3_1.crossing_number()
|
907
|
+
3
|
908
|
+
sage: Link(KnotInfo.L4a1_0.braid())
|
909
|
+
Link with 2 components represented by 5 crossings
|
910
|
+
"""
|
911
|
+
return knotinfo_int(self[self.items.crossing_number])
|
912
|
+
|
913
|
+
@cached_method
|
914
|
+
def determinant(self):
|
915
|
+
r"""
|
916
|
+
Return the determinant of ``self``.
|
917
|
+
|
918
|
+
From the KnotInfo description page:
|
919
|
+
|
920
|
+
The determinant of a knot is `\det(V + V^t)`, where `V` is a Seifert
|
921
|
+
matrix for the knot.
|
922
|
+
|
923
|
+
To read the complete description type
|
924
|
+
``KnotInfo.K0_1.items.determinant.description_webpage()``.
|
925
|
+
|
926
|
+
.. NOTE::
|
927
|
+
|
928
|
+
KnotInfo's value for the unknot ``0_1`` is zero. This is not
|
929
|
+
compatible with Sage's result (the value of the Alexander
|
930
|
+
polynomial at -1). Since this method is needed to identify
|
931
|
+
Sage links we take the according value in that case.
|
932
|
+
|
933
|
+
EXAMPLES::
|
934
|
+
|
935
|
+
sage: KnotInfo.L4a1_0.determinant()
|
936
|
+
4
|
937
|
+
sage: KnotInfo.K3_1.determinant()
|
938
|
+
3
|
939
|
+
sage: KnotInfo.K0_1.determinant()
|
940
|
+
1
|
941
|
+
"""
|
942
|
+
if self.crossing_number() == 0:
|
943
|
+
# see note above
|
944
|
+
return 1
|
945
|
+
return knotinfo_int(self[self.items.determinant])
|
946
|
+
|
947
|
+
@cached_method
|
948
|
+
def three_genus(self):
|
949
|
+
r"""
|
950
|
+
Return the three genus of ``self``.
|
951
|
+
|
952
|
+
From the KnotInfo description page:
|
953
|
+
|
954
|
+
The three-genus of a knot is defined to be the minimal genus of
|
955
|
+
a Seifert surface for a knot.
|
956
|
+
|
957
|
+
To read the complete description type
|
958
|
+
``KnotInfo.K0_1.items.three_genus.description_webpage()``.
|
959
|
+
|
960
|
+
EXAMPLES::
|
961
|
+
|
962
|
+
sage: KnotInfo.K5_2.three_genus() # optional - database_knotinfo
|
963
|
+
1
|
964
|
+
|
965
|
+
Note that this differs from the corresponding result in Sage
|
966
|
+
since the latter is obtained for a Seifert surface that does not
|
967
|
+
have the minimal genus::
|
968
|
+
|
969
|
+
sage: KnotInfo.K5_2.link().genus()
|
970
|
+
3
|
971
|
+
"""
|
972
|
+
return knotinfo_int(self[self.items.three_genus])
|
973
|
+
|
974
|
+
@cached_method
|
975
|
+
def signature(self):
|
976
|
+
r"""
|
977
|
+
Return the signature of ``self``.
|
978
|
+
|
979
|
+
From the KnotInfo description page:
|
980
|
+
|
981
|
+
The signature of a knot, `\sigma (K)`, is equal to `\sigma (V + V^t)`,
|
982
|
+
the signature of `V + V^t` where `V` is a Seifert matrix for the knot
|
983
|
+
and `V^t` is its transpose.
|
984
|
+
|
985
|
+
To read the complete description type
|
986
|
+
``KnotInfo.K0_1.items.signatur.description_webpage()``.
|
987
|
+
|
988
|
+
EXAMPLES::
|
989
|
+
|
990
|
+
sage: KnotInfo.K5_2.signature() # optional - database_knotinfo
|
991
|
+
-2
|
992
|
+
"""
|
993
|
+
return knotinfo_int(self[self.items.signature])
|
994
|
+
|
995
|
+
@cached_method
|
996
|
+
def is_knot(self) -> bool:
|
997
|
+
r"""
|
998
|
+
Return whether ``self`` is a knot or a proper link.
|
999
|
+
|
1000
|
+
EXAMPLES::
|
1001
|
+
|
1002
|
+
sage: KnotInfo.L7a1_0.is_knot() # optional - database_knotinfo
|
1003
|
+
False
|
1004
|
+
sage: KnotInfo.K6_3.is_knot()
|
1005
|
+
True
|
1006
|
+
"""
|
1007
|
+
return self.num_components() == 1
|
1008
|
+
|
1009
|
+
@cached_method
|
1010
|
+
def name_unoriented(self):
|
1011
|
+
r"""
|
1012
|
+
Return the part of the name of ``self`` which is independent on the
|
1013
|
+
orientation.
|
1014
|
+
|
1015
|
+
EXAMPLES::
|
1016
|
+
|
1017
|
+
sage: KnotInfo.L10a122_1_0.name_unoriented() # optional - database_knotinfo
|
1018
|
+
'L10a122'
|
1019
|
+
"""
|
1020
|
+
return self[self.items.name_unoriented]
|
1021
|
+
|
1022
|
+
@cached_method
|
1023
|
+
def symmetry_type(self):
|
1024
|
+
r"""
|
1025
|
+
Return the symmetry type of ``self``.
|
1026
|
+
|
1027
|
+
From the KnotInfo description page:
|
1028
|
+
|
1029
|
+
If a knot is viewed as the oriented diffeomorphism
|
1030
|
+
class of an oriented pair, `K = (S_3, S_1)`, with `S_i`
|
1031
|
+
diffeomorphic to `S^i`, there are four oriented knots
|
1032
|
+
associated to any particular knot `K`. In addition to
|
1033
|
+
`K` itself, there is the reverse, `K^r = (S_3, -S_1)`,
|
1034
|
+
the concordance inverse, `-K = (-S_3, -S_1)`, and the
|
1035
|
+
mirror image, `K^m = (-S_3, S_1)`. A knot is called
|
1036
|
+
reversible if `K = K^r`, negative amphicheiral if
|
1037
|
+
`K = -K`, and positive amphicheiral if `K = K^m`.
|
1038
|
+
|
1039
|
+
A knot possessing any two of these types of symmetry
|
1040
|
+
has all three. Thus, in the table, a knot is called
|
1041
|
+
reversible if that is the only type of symmetry it has,
|
1042
|
+
and likewise for negative amphicheiral. If it has none
|
1043
|
+
of these types of symmetry it is called chiral, and if
|
1044
|
+
it has all three it is called fully amphicheiral.
|
1045
|
+
|
1046
|
+
For prime knots with fewer than 12 crossings, all
|
1047
|
+
amphicheiral knots are negative amphicheiral.
|
1048
|
+
|
1049
|
+
EXAMPLES::
|
1050
|
+
|
1051
|
+
sage: KnotInfo.K6_1.series().inject()
|
1052
|
+
Defining K6
|
1053
|
+
sage: [(K.name, K.symmetry_type()) for K in K6]
|
1054
|
+
[('K6_1', 'reversible'),
|
1055
|
+
('K6_2', 'reversible'),
|
1056
|
+
('K6_3', 'fully amphicheiral')]
|
1057
|
+
"""
|
1058
|
+
if not self.is_knot():
|
1059
|
+
raise NotImplementedError('this is only available for knots')
|
1060
|
+
|
1061
|
+
symmetry_type = self[self.items.symmetry_type].strip() # for example K10_88 is a case with trailing whitespaces
|
1062
|
+
if not symmetry_type and self.crossing_number() == 0:
|
1063
|
+
return 'fully amphicheiral'
|
1064
|
+
return symmetry_type
|
1065
|
+
|
1066
|
+
@cached_method
|
1067
|
+
def is_reversible(self) -> bool:
|
1068
|
+
r"""
|
1069
|
+
Return whether ``self`` is reversible.
|
1070
|
+
|
1071
|
+
EXAMPLES::
|
1072
|
+
|
1073
|
+
sage: KnotInfo.K6_3.is_reversible()
|
1074
|
+
True
|
1075
|
+
|
1076
|
+
TESTS::
|
1077
|
+
|
1078
|
+
sage: KnotInfo.K10_67.is_reversible() # optional - database_knotinfo
|
1079
|
+
False
|
1080
|
+
sage: KnotInfo.L7a4_0.is_reversible() # optional - database_knotinfo
|
1081
|
+
"""
|
1082
|
+
if self.is_knot():
|
1083
|
+
symmetry_type = self.symmetry_type()
|
1084
|
+
if symmetry_type == 'reversible':
|
1085
|
+
return True
|
1086
|
+
if symmetry_type == 'fully amphicheiral':
|
1087
|
+
return True
|
1088
|
+
return False
|
1089
|
+
|
1090
|
+
# revert orientation
|
1091
|
+
b = self.braid()
|
1092
|
+
bt = list(b.Tietze())
|
1093
|
+
bt.reverse()
|
1094
|
+
br = b.parent()(tuple(bt))
|
1095
|
+
if b.is_conjugated(br):
|
1096
|
+
return True
|
1097
|
+
return None
|
1098
|
+
|
1099
|
+
@cached_method
|
1100
|
+
def is_amphicheiral(self, positive=False) -> bool:
|
1101
|
+
r"""
|
1102
|
+
Return whether ``self`` is amphicheiral.
|
1103
|
+
|
1104
|
+
INPUT:
|
1105
|
+
|
1106
|
+
- ``positive`` -- boolean (default: ``False``); whether to check
|
1107
|
+
if ``self`` is positive or negative amphicheiral (see documentation
|
1108
|
+
of :meth:`symmetry_type`)
|
1109
|
+
|
1110
|
+
OUTPUT: boolean or ``None`` if this cannot be determined
|
1111
|
+
|
1112
|
+
``True`` if ``self`` is fully or negative amphicheiral per default. If
|
1113
|
+
``positive`` is set to ``True`` than fully and positive amphicheiral
|
1114
|
+
links give ``True``.
|
1115
|
+
|
1116
|
+
.. NOTE::
|
1117
|
+
|
1118
|
+
For proper links this property is not provided in the database.
|
1119
|
+
Anyway, we support it here in this case, as well, except for a few
|
1120
|
+
items where it cannot be determined easily and where ``None``
|
1121
|
+
is returned as answer.
|
1122
|
+
|
1123
|
+
EXAMPLES::
|
1124
|
+
|
1125
|
+
sage: # optional - database_knotinfo
|
1126
|
+
sage: Kp = KnotInfo.K12a_427
|
1127
|
+
sage: Kp.is_amphicheiral()
|
1128
|
+
False
|
1129
|
+
sage: Kp.is_amphicheiral(positive=True)
|
1130
|
+
True
|
1131
|
+
sage: Kn = KnotInfo.K10_88
|
1132
|
+
sage: Kn.is_amphicheiral()
|
1133
|
+
True
|
1134
|
+
sage: Kn.is_amphicheiral(positive=True)
|
1135
|
+
False
|
1136
|
+
sage: KnotInfo.L4a1_0.is_amphicheiral()
|
1137
|
+
False
|
1138
|
+
sage: KnotInfo.L10n59_1.is_amphicheiral()
|
1139
|
+
True
|
1140
|
+
sage: KnotInfo.L10n36_0.inject()
|
1141
|
+
Defining L10n36_0
|
1142
|
+
sage: L10n36_0.is_amphicheiral() is None
|
1143
|
+
True
|
1144
|
+
"""
|
1145
|
+
if self.is_knot():
|
1146
|
+
symmetry_type = self.symmetry_type()
|
1147
|
+
if positive:
|
1148
|
+
if symmetry_type == 'positive amphicheiral':
|
1149
|
+
return True
|
1150
|
+
else:
|
1151
|
+
if symmetry_type == 'negative amphicheiral':
|
1152
|
+
return True
|
1153
|
+
|
1154
|
+
if symmetry_type == 'fully amphicheiral':
|
1155
|
+
return True
|
1156
|
+
return False
|
1157
|
+
|
1158
|
+
h = self.homfly_polynomial()
|
1159
|
+
v, z = h.parent().gens()
|
1160
|
+
hm = h.subs(v=~v, z=-z)
|
1161
|
+
if h != hm:
|
1162
|
+
return False
|
1163
|
+
|
1164
|
+
k = self.kauffman_polynomial()
|
1165
|
+
a, z = k.parent().gens()
|
1166
|
+
km = k.subs(a=~a)
|
1167
|
+
if k != km:
|
1168
|
+
return False
|
1169
|
+
|
1170
|
+
b = self.braid()
|
1171
|
+
bi = ~b
|
1172
|
+
if b.is_conjugated(bi):
|
1173
|
+
# at least negative amphicheiral
|
1174
|
+
if not positive:
|
1175
|
+
return True
|
1176
|
+
|
1177
|
+
# revert orientation (back)
|
1178
|
+
bit = list(bi.Tietze())
|
1179
|
+
bit.reverse()
|
1180
|
+
bm = b.parent()(tuple(bit))
|
1181
|
+
if b.is_conjugated(bm):
|
1182
|
+
if positive:
|
1183
|
+
return True
|
1184
|
+
|
1185
|
+
return None
|
1186
|
+
|
1187
|
+
@cached_method
|
1188
|
+
def is_hyperbolic(self) -> bool:
|
1189
|
+
r"""
|
1190
|
+
Return whether ``self`` is hyperbolic.
|
1191
|
+
|
1192
|
+
EXAMPLES::
|
1193
|
+
|
1194
|
+
sage: KnotInfo.K3_1.is_hyperbolic()
|
1195
|
+
False
|
1196
|
+
sage: KnotInfo.K5_2.is_hyperbolic()
|
1197
|
+
True
|
1198
|
+
"""
|
1199
|
+
geometric_type = self[self.items.geometric_type]
|
1200
|
+
if geometric_type == 'hyperbolic':
|
1201
|
+
return True
|
1202
|
+
return False
|
1203
|
+
|
1204
|
+
@cached_method
|
1205
|
+
def is_alternating(self) -> bool:
|
1206
|
+
r"""
|
1207
|
+
Return whether ``self`` is alternating.
|
1208
|
+
|
1209
|
+
EXAMPLES::
|
1210
|
+
|
1211
|
+
sage: KnotInfo.K5_2.is_alternating()
|
1212
|
+
True
|
1213
|
+
"""
|
1214
|
+
return knotinfo_bool(self[self.items.alternating])
|
1215
|
+
|
1216
|
+
@cached_method
|
1217
|
+
def is_almost_alternating(self) -> bool:
|
1218
|
+
r"""
|
1219
|
+
Return whether ``self`` is almost alternating.
|
1220
|
+
|
1221
|
+
EXAMPLES::
|
1222
|
+
|
1223
|
+
sage: KnotInfo.K5_2.is_almost_alternating() # optional - database_knotinfo
|
1224
|
+
False
|
1225
|
+
"""
|
1226
|
+
db._feature.require() # column not available in demo-version
|
1227
|
+
return knotinfo_bool(self[self.items.almost_alternating])
|
1228
|
+
|
1229
|
+
@cached_method
|
1230
|
+
def is_quasi_alternating(self) -> bool:
|
1231
|
+
r"""
|
1232
|
+
Return whether ``self`` is quasi alternating.
|
1233
|
+
|
1234
|
+
EXAMPLES::
|
1235
|
+
|
1236
|
+
sage: KnotInfo.K5_2.is_quasi_alternating() # optional - database_knotinfo
|
1237
|
+
True
|
1238
|
+
"""
|
1239
|
+
db._feature.require() # column not available in demo-version
|
1240
|
+
return knotinfo_bool(self[self.items.quasi_alternating])
|
1241
|
+
|
1242
|
+
@cached_method
|
1243
|
+
def is_adequate(self) -> bool:
|
1244
|
+
r"""
|
1245
|
+
Return whether ``self`` is adequate.
|
1246
|
+
|
1247
|
+
EXAMPLES::
|
1248
|
+
|
1249
|
+
sage: KnotInfo.K5_2.is_adequate() # optional - database_knotinfo
|
1250
|
+
True
|
1251
|
+
"""
|
1252
|
+
db._feature.require() # column not available in demo-version
|
1253
|
+
return knotinfo_bool(self[self.items.adequate])
|
1254
|
+
|
1255
|
+
@cached_method
|
1256
|
+
def is_positive(self) -> bool:
|
1257
|
+
r"""
|
1258
|
+
Return whether ``self`` is positive.
|
1259
|
+
|
1260
|
+
EXAMPLES::
|
1261
|
+
|
1262
|
+
sage: KnotInfo.K5_2.is_positive()
|
1263
|
+
True
|
1264
|
+
"""
|
1265
|
+
return knotinfo_bool(self[self.items.positive])
|
1266
|
+
|
1267
|
+
@cached_method
|
1268
|
+
def is_quasipositive(self) -> bool:
|
1269
|
+
r"""
|
1270
|
+
Return whether ``self`` is quasi-positive.
|
1271
|
+
|
1272
|
+
EXAMPLES::
|
1273
|
+
|
1274
|
+
sage: KnotInfo.K5_2.is_quasipositive() # optional - database_knotinfo
|
1275
|
+
True
|
1276
|
+
"""
|
1277
|
+
db._feature.require() # column not available in demo-version
|
1278
|
+
return knotinfo_bool(self[self.items.quasipositive])
|
1279
|
+
|
1280
|
+
@cached_method
|
1281
|
+
def is_strongly_quasipositive(self) -> bool:
|
1282
|
+
r"""
|
1283
|
+
Return whether ``self`` is strongly quasi-positive.
|
1284
|
+
|
1285
|
+
EXAMPLES::
|
1286
|
+
|
1287
|
+
sage: KnotInfo.K5_2.is_strongly_quasipositive() # optional - database_knotinfo
|
1288
|
+
True
|
1289
|
+
"""
|
1290
|
+
db._feature.require() # column not available in demo-version
|
1291
|
+
return knotinfo_bool(self[self.items.strongly_quasipositive])
|
1292
|
+
|
1293
|
+
@cached_method
|
1294
|
+
def is_positive_braid(self) -> bool:
|
1295
|
+
r"""
|
1296
|
+
Return whether ``self`` is a positive braid.
|
1297
|
+
|
1298
|
+
EXAMPLES::
|
1299
|
+
|
1300
|
+
sage: KnotInfo.K5_2.is_positive_braid() # optional - database_knotinfo
|
1301
|
+
False
|
1302
|
+
"""
|
1303
|
+
db._feature.require() # column not available in demo-version
|
1304
|
+
return knotinfo_bool(self[self.items.positive_braid])
|
1305
|
+
|
1306
|
+
@cached_method
|
1307
|
+
def is_fibered(self) -> bool:
|
1308
|
+
r"""
|
1309
|
+
Return whether ``self`` is fibered.
|
1310
|
+
|
1311
|
+
EXAMPLES::
|
1312
|
+
|
1313
|
+
sage: KnotInfo.K6_3.is_fibered()
|
1314
|
+
True
|
1315
|
+
"""
|
1316
|
+
return knotinfo_bool(self[self.items.fibered])
|
1317
|
+
|
1318
|
+
@cached_method
|
1319
|
+
def is_oriented(self) -> bool:
|
1320
|
+
r"""
|
1321
|
+
Return whether ``self`` is oriented.
|
1322
|
+
|
1323
|
+
EXAMPLES::
|
1324
|
+
|
1325
|
+
sage: KnotInfo.L6a2_1.is_oriented()
|
1326
|
+
True
|
1327
|
+
"""
|
1328
|
+
return not knotinfo_bool(self[self.items.unoriented])
|
1329
|
+
|
1330
|
+
@cached_method
|
1331
|
+
def cosmetic_crossing_conjecture_verified(self):
|
1332
|
+
r"""
|
1333
|
+
Return whether the Cosmetic Crossing Conjecture has been verified
|
1334
|
+
for ``self``.
|
1335
|
+
|
1336
|
+
From the KnotInfo `description page <https://knotinfo.math.indiana.edu/descriptions/cosmetic_crossing.html>`__:
|
1337
|
+
|
1338
|
+
A crossing change in a diagram of a knot ``K`` is called cosmetic if
|
1339
|
+
the resulting diagram also represents ``K``. The cosmetic crossing
|
1340
|
+
conjecture posits that for any knot ``K``, the only cosmetic crossing
|
1341
|
+
changes are nugatory, i.e. there exists an embedded 2-sphere in
|
1342
|
+
``S3`` which intersects K only at the two points of the relevant
|
1343
|
+
crossing. Conversely, it is not hard to see that any nugatory
|
1344
|
+
crossing change is cosmetic.
|
1345
|
+
|
1346
|
+
EXAMPLES::
|
1347
|
+
|
1348
|
+
sage: knots = [K for K in KnotInfo if K.is_knot() and K.crossing_number() < 10]
|
1349
|
+
sage: all(K.cosmetic_crossing_conjecture_verified() for K in knots)
|
1350
|
+
True
|
1351
|
+
"""
|
1352
|
+
cosmetic_crossing = self[self.items.cosmetic_crossing]
|
1353
|
+
if self.crossing_number() == 0:
|
1354
|
+
return True
|
1355
|
+
if not cosmetic_crossing or cosmetic_crossing == 'Unknown':
|
1356
|
+
return False
|
1357
|
+
if not knotinfo_bool(cosmetic_crossing):
|
1358
|
+
return True
|
1359
|
+
raise AssertionError(f'{self} is a counterexample to the cosmetic crossing conjecture')
|
1360
|
+
|
1361
|
+
@cached_method
|
1362
|
+
def homfly_polynomial(self, var1='v', var2='z', original=False):
|
1363
|
+
r"""
|
1364
|
+
Return the HOMFLY-PT polynomial according to the value of column
|
1365
|
+
``homfly_polynomial`` for this knot or link (in the latter case the
|
1366
|
+
column ``homflypt_polynomial`` is used) as an instance of the
|
1367
|
+
element class according to the output of :meth:`Link.homfly_polynomial`
|
1368
|
+
of :class:`Link`.
|
1369
|
+
|
1370
|
+
The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein
|
1371
|
+
relation (see the corresponding `KnotInfo description page
|
1372
|
+
<https://knotinfo.math.indiana.edu/descriptions/jones_homfly_kauffman_description/polynomial_defn.html)>`__):
|
1373
|
+
|
1374
|
+
.. MATH::
|
1375
|
+
|
1376
|
+
P(O) = 1,\,\,\, v^{-1} P(L_+) - v P(L_-) = z P(L_0)
|
1377
|
+
|
1378
|
+
INPUT:
|
1379
|
+
|
1380
|
+
- ``var1`` -- string (default: ``'v'``); for the name of the first variable
|
1381
|
+
- ``var2`` -- string (default: ``'z'``); for the name of the second variable
|
1382
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1383
|
+
``True`` the original table entry is returned as a string
|
1384
|
+
|
1385
|
+
OUTPUT:
|
1386
|
+
|
1387
|
+
A Laurent polynomial over the integers, more precisely an instance of
|
1388
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
1389
|
+
If ``original`` is set to ``True`` then a string is returned.
|
1390
|
+
|
1391
|
+
.. NOTE::
|
1392
|
+
|
1393
|
+
The skein-relation for the HOMFLY-PT polynomial given on KnotInfo
|
1394
|
+
does not match the default used in Sage. For comparison you have
|
1395
|
+
to use the keyword argument ``normalization='vz'`` on the side
|
1396
|
+
of Sage.
|
1397
|
+
|
1398
|
+
EXAMPLES::
|
1399
|
+
|
1400
|
+
sage: K3_1 = KnotInfo.K3_1
|
1401
|
+
sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1
|
1402
|
+
-v^4 + v^2*z^2 + 2*v^2
|
1403
|
+
sage: K3_1.homfly_polynomial(original=True)
|
1404
|
+
'(2*v^2-v^4)+v^2*z^2'
|
1405
|
+
sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='vz')
|
1406
|
+
True
|
1407
|
+
|
1408
|
+
for proper links::
|
1409
|
+
|
1410
|
+
sage: L4a1_1 = KnotInfo.L4a1_1
|
1411
|
+
sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1
|
1412
|
+
-x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1
|
1413
|
+
sage: _ == L4a1_1.link().homfly_polynomial('x', 'y', 'vz')
|
1414
|
+
True
|
1415
|
+
|
1416
|
+
check the skein-relation from the KnotInfo description page (applied to one
|
1417
|
+
of the positive crossings of the right-handed trefoil)::
|
1418
|
+
|
1419
|
+
sage: R = PK3_1.parent()
|
1420
|
+
sage: PO = R.one()
|
1421
|
+
sage: L2a1_1 = KnotInfo.L2a1_1
|
1422
|
+
sage: PL2a1_1 = L2a1_1.homfly_polynomial()
|
1423
|
+
sage: v, z = R.gens()
|
1424
|
+
sage: ~v*PK3_1 -v*PO == z*PL2a1_1
|
1425
|
+
True
|
1426
|
+
|
1427
|
+
TESTS::
|
1428
|
+
|
1429
|
+
sage: H = KnotInfo.L11n459_1_1_1.homfly_polynomial() # optional - database_knotinfo
|
1430
|
+
sage: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='vz')\
|
1431
|
+
....: for L in KnotInfo if L.crossing_number() < 7)
|
1432
|
+
True
|
1433
|
+
|
1434
|
+
REFERENCES:
|
1435
|
+
|
1436
|
+
- :wikipedia:`HOMFLY_polynomial`
|
1437
|
+
"""
|
1438
|
+
if self.is_knot():
|
1439
|
+
homfly_polynomial = self[self.items.homfly_polynomial]
|
1440
|
+
else:
|
1441
|
+
homfly_polynomial = self[self.items.homflypt_polynomial]
|
1442
|
+
|
1443
|
+
if original:
|
1444
|
+
return homfly_polynomial
|
1445
|
+
|
1446
|
+
R = self._homfly_pol_ring(var1, var2)
|
1447
|
+
if not homfly_polynomial and self.crossing_number() == 0:
|
1448
|
+
return R.one()
|
1449
|
+
|
1450
|
+
# As of February 2021 there is a wrong character for the link in the
|
1451
|
+
# last row of the database. This is removed here (see SPKG.rst and
|
1452
|
+
# the test above). Once this is fixed upstream, the following three
|
1453
|
+
# lines of code can be removed again:
|
1454
|
+
if self.value == 'L11n459{1,1,1}':
|
1455
|
+
if homfly_polynomial.endswith('}'):
|
1456
|
+
homfly_polynomial = homfly_polynomial.strip('}')
|
1457
|
+
|
1458
|
+
L, M = R.gens()
|
1459
|
+
lc = {'v': L, 'z': M}
|
1460
|
+
return eval_knotinfo(homfly_polynomial, locals=lc)
|
1461
|
+
|
1462
|
+
@cached_method
|
1463
|
+
def kauffman_polynomial(self, var1='a', var2='z', original=False):
|
1464
|
+
r"""
|
1465
|
+
Return the Kauffman polynomial according to the value of column
|
1466
|
+
``kauffman_polynomial`` for this knot or link as an instance of
|
1467
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
1468
|
+
|
1469
|
+
The Kauffman polynomial `F(L)` respectivlely its corresponding invariant
|
1470
|
+
under regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the
|
1471
|
+
writhe of the link `L` satisfies the following skein relation
|
1472
|
+
(see the corresponding `KnotInfo description page
|
1473
|
+
<https://knotinfo.math.indiana.edu/descriptions/jones_homfly_kauffman_description/polynomial_defn.html)>`__):
|
1474
|
+
|
1475
|
+
.. MATH::
|
1476
|
+
|
1477
|
+
\Delta(O) = 1,\,\,\, \Delta(L_+) - \Delta(L_-) = z (\Delta(L_0 + \Delta(L_{\infty}))
|
1478
|
+
|
1479
|
+
Furthermore, removing a curl of sign `\epsilon` leads to a multiplication
|
1480
|
+
of `\Delta(L)` with `a^{\epsilon}`.
|
1481
|
+
|
1482
|
+
INPUT:
|
1483
|
+
|
1484
|
+
- ``var1`` -- (default: ``'a'``) the first variable
|
1485
|
+
- ``var2`` -- (default: ``'z'``) the second variable
|
1486
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1487
|
+
``True`` the original table entry is returned as a string
|
1488
|
+
|
1489
|
+
OUTPUT:
|
1490
|
+
|
1491
|
+
A Laurent polynomial over the integers, more precisely an instance of
|
1492
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
1493
|
+
If ``original`` is set to ``False`` then a string is returned.
|
1494
|
+
|
1495
|
+
EXAMPLES::
|
1496
|
+
|
1497
|
+
sage: L = KnotInfo.L2a1_1
|
1498
|
+
sage: K = KnotInfo.K4_1
|
1499
|
+
|
1500
|
+
sage: L.kauffman_polynomial()
|
1501
|
+
a^-1*z - a^-1*z^-1 + a^-2 + a^-3*z - a^-3*z^-1
|
1502
|
+
sage: K.kauffman_polynomial()
|
1503
|
+
a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2
|
1504
|
+
|
1505
|
+
Comparison with Jones polynomial::
|
1506
|
+
|
1507
|
+
sage: # needs sage.symbolic
|
1508
|
+
sage: k = _
|
1509
|
+
sage: a, z = k.variables()
|
1510
|
+
sage: j = K.jones_polynomial(skein_normalization=True)
|
1511
|
+
sage: t, = j.variables()
|
1512
|
+
sage: k.subs(a=-t^3, z=~t+t) == j.subs(t=t^4)
|
1513
|
+
True
|
1514
|
+
|
1515
|
+
Check the skein relation::
|
1516
|
+
|
1517
|
+
sage: K3_1 = KnotInfo.K3_1
|
1518
|
+
sage: FK3_1 = K3_1.kauffman_polynomial()
|
1519
|
+
sage: FL2a1_1 = L.kauffman_polynomial()
|
1520
|
+
sage: z, a = FK3_1.variables()
|
1521
|
+
sage: ΔK3_1 = FK3_1 * a**K3_1.link().writhe()
|
1522
|
+
sage: ΔL2a1_1 = FL2a1_1 * a**L.link().writhe()
|
1523
|
+
sage: ΔO1p = a # unknot with one positive curl
|
1524
|
+
sage: ΔO2n = a**-2 # unknot with two negative curls
|
1525
|
+
sage: ΔK3_1 + ΔO1p == z*(ΔL2a1_1 + ΔO2n)
|
1526
|
+
True
|
1527
|
+
|
1528
|
+
REFERENCES:
|
1529
|
+
|
1530
|
+
- :wikipedia:`Kauffman_polynomial`
|
1531
|
+
"""
|
1532
|
+
kauffman_polynomial = self[self.items.kauffman_polynomial]
|
1533
|
+
|
1534
|
+
if original:
|
1535
|
+
return kauffman_polynomial
|
1536
|
+
|
1537
|
+
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
|
1538
|
+
R = LaurentPolynomialRing(ZZ, (var1, var2))
|
1539
|
+
if not kauffman_polynomial and self.crossing_number() == 0:
|
1540
|
+
return R.one()
|
1541
|
+
|
1542
|
+
a, z = R.gens()
|
1543
|
+
lc = {'a': a, 'z': z}
|
1544
|
+
return R(eval_knotinfo(kauffman_polynomial, locals=lc))
|
1545
|
+
|
1546
|
+
@cached_method
|
1547
|
+
def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, use_sqrt=False):
|
1548
|
+
r"""
|
1549
|
+
Return the Jones polynomial according to the value of column
|
1550
|
+
``jones_polynomial`` for this knot or link as an element of the symbolic
|
1551
|
+
ring :class:`~sage.symbolic.ring.SR` or an instance of
|
1552
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
|
1553
|
+
depending on the keyword ``skein_normalization``. Using the keyword
|
1554
|
+
``puiseux`` instead of an element of the symbolic ring an instance of
|
1555
|
+
:class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` can be
|
1556
|
+
returned.
|
1557
|
+
|
1558
|
+
The Jones polynomial `V(L)` of a link `L` satisfies the following skein
|
1559
|
+
relation (see the corresponding `KnotInfo description page
|
1560
|
+
<https://knotinfo.math.indiana.edu/descriptions/jones_homfly_kauffman_description/polynomial_defn.html)>`__):
|
1561
|
+
|
1562
|
+
.. MATH::
|
1563
|
+
|
1564
|
+
V(O) = 1,\,\,\, t^{-1} V(L_+) - t V(L_-) = (t^{\frac{1}{2}} - t^{-\frac{1}{2}}) V(L_0)
|
1565
|
+
|
1566
|
+
INPUT:
|
1567
|
+
|
1568
|
+
- ``variab`` -- variable (default: ``None``); used according to
|
1569
|
+
:meth:`Link.jones_polynomial`
|
1570
|
+
- ``skein_normalization`` -- boolean (default: ``False``); used
|
1571
|
+
according to :meth:`Link.jones_polynomial`
|
1572
|
+
- ``puiseux`` -- boolean (default: ``True``); only used in case
|
1573
|
+
``skein_normalization=False``. If set to ``True`` instead of an element
|
1574
|
+
of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries`
|
1575
|
+
is returned
|
1576
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1577
|
+
``True`` the original table entry is returned as a string
|
1578
|
+
- ``use_sqrt`` -- boolean (default: ``False``); see the note below
|
1579
|
+
|
1580
|
+
OUTPUT:
|
1581
|
+
|
1582
|
+
Depends on the keywords (in excluding order):
|
1583
|
+
|
1584
|
+
- ``original=True`` a string according to the original value from the
|
1585
|
+
database
|
1586
|
+
- ``skein_normalization=True`` a Laurent polynomial over the integers,
|
1587
|
+
more precisely an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
|
1588
|
+
- ``puiseux=True`` a puiseux series over the integers, more precisely an
|
1589
|
+
instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries`
|
1590
|
+
|
1591
|
+
In all other cases an element of the symbolic ring :class:`~sage.symbolic.ring.SR`.
|
1592
|
+
|
1593
|
+
.. NOTE::
|
1594
|
+
|
1595
|
+
There is a difference to Sage's conventions concerning the Jones
|
1596
|
+
polynomial in the case of proper links. KnotInfo does not display
|
1597
|
+
these polynomials in the indeterminate `t` used in the skein relation.
|
1598
|
+
Instead a variable `x` is used defined by `x^2 = t`. Sage uses `t` in
|
1599
|
+
both cases, knots and proper links. Thus, to obtain the Jones polynomial
|
1600
|
+
for a proper link in `t` you have to set the keyword ``use_sqrt``
|
1601
|
+
to ``True``.
|
1602
|
+
|
1603
|
+
EXAMPLES::
|
1604
|
+
|
1605
|
+
sage: K = KnotInfo.K4_1
|
1606
|
+
sage: Kj = K.jones_polynomial(); Kj # needs sage.symbolic
|
1607
|
+
t^2 - t - 1/t + 1/t^2 + 1
|
1608
|
+
sage: Kjs = K.jones_polynomial(skein_normalization=True); Kjs
|
1609
|
+
A^-8 - A^-4 + 1 - A^4 + A^8
|
1610
|
+
sage: Kjp = K.jones_polynomial(puiseux=True); Kjp
|
1611
|
+
t^-2 - t^-1 + 1 - t + t^2
|
1612
|
+
|
1613
|
+
for proper links::
|
1614
|
+
|
1615
|
+
sage: L = KnotInfo.L2a1_1
|
1616
|
+
sage: Lj = L.jones_polynomial(); Lj # needs sage.symbolic
|
1617
|
+
-x^5 - x
|
1618
|
+
sage: Ljt = L.jones_polynomial(use_sqrt=True); Ljt # needs sage.symbolic
|
1619
|
+
-t^(5/2) - sqrt(t)
|
1620
|
+
sage: Ljp = L.jones_polynomial(puiseux=True); Ljp
|
1621
|
+
-t^(1/2) - t^(5/2)
|
1622
|
+
sage: Ljs = L.jones_polynomial(skein_normalization=True); Ljs
|
1623
|
+
-A^2 - A^10
|
1624
|
+
sage: Lj.parent() # needs sage.symbolic
|
1625
|
+
Symbolic Ring
|
1626
|
+
sage: Ljt.parent() # needs sage.symbolic
|
1627
|
+
Symbolic Ring
|
1628
|
+
sage: Ljp.parent()
|
1629
|
+
Puiseux Series Ring in t over Integer Ring
|
1630
|
+
sage: Ljs.parent()
|
1631
|
+
Univariate Laurent Polynomial Ring in A over Integer Ring
|
1632
|
+
|
1633
|
+
Comparison with Sage's results::
|
1634
|
+
|
1635
|
+
sage: k = K.link()
|
1636
|
+
sage: kj = k.jones_polynomial() # needs sage.symbolic
|
1637
|
+
sage: bool(Kj == kj) # needs sage.symbolic
|
1638
|
+
True
|
1639
|
+
sage: kjs = k.jones_polynomial(skein_normalization=True)
|
1640
|
+
sage: Kjs == kjs
|
1641
|
+
True
|
1642
|
+
sage: l = L.link()
|
1643
|
+
sage: lj = l.jones_polynomial() # needs sage.symbolic
|
1644
|
+
sage: bool(Lj == lj) # needs sage.symbolic
|
1645
|
+
False
|
1646
|
+
sage: bool(Ljt == lj) # see note above # needs sage.symbolic
|
1647
|
+
True
|
1648
|
+
sage: ljs = l.jones_polynomial(skein_normalization=True)
|
1649
|
+
sage: Ljs == ljs
|
1650
|
+
True
|
1651
|
+
|
1652
|
+
Check the skein-relation from the KnotInfo description page (applied to one
|
1653
|
+
of the positive crossings of the right-handed trefoil)::
|
1654
|
+
|
1655
|
+
sage: K3_1 = KnotInfo.K3_1
|
1656
|
+
|
1657
|
+
sage: # needs sage.symbolic
|
1658
|
+
sage: K3_1j = K3_1.jones_polynomial()
|
1659
|
+
sage: L2a1_1j = Ljt # see note above
|
1660
|
+
sage: R = L2a1_1j.parent()
|
1661
|
+
sage: Oj = R(1)
|
1662
|
+
sage: t = R('t')
|
1663
|
+
sage: lhs = expand(~t*K3_1j - t*Oj)
|
1664
|
+
sage: rhs = expand((sqrt(t) - ~sqrt(t))*L2a1_1j)
|
1665
|
+
sage: bool(lhs == rhs)
|
1666
|
+
True
|
1667
|
+
|
1668
|
+
The same with the Puiseux series version::
|
1669
|
+
|
1670
|
+
sage: K3_1jp = K3_1.jones_polynomial(puiseux=True)
|
1671
|
+
sage: L2a1_1jp = Ljp
|
1672
|
+
sage: R = L2a1_1jp.parent()
|
1673
|
+
sage: Ojp = R(1)
|
1674
|
+
sage: t = R('t')
|
1675
|
+
sage: ~t*K3_1jp - t*Ojp == (t^(1/2)-~t^(1/2))*L2a1_1jp
|
1676
|
+
True
|
1677
|
+
|
1678
|
+
The same in the case of skein normalization (using `t = A^4`)::
|
1679
|
+
|
1680
|
+
sage: K3_1js = K3_1.jones_polynomial(skein_normalization=True)
|
1681
|
+
sage: L2a1_1js = L.jones_polynomial(skein_normalization=True)
|
1682
|
+
sage: Rs = K3_1js.parent()
|
1683
|
+
sage: Ojs = Rs.one()
|
1684
|
+
sage: A, = Rs.gens()
|
1685
|
+
sage: ~A^4*K3_1js - A^4*Ojs == (A^2-~A^2)*L2a1_1js
|
1686
|
+
True
|
1687
|
+
|
1688
|
+
REFERENCES:
|
1689
|
+
|
1690
|
+
- :wikipedia:`Jones_polynomial`
|
1691
|
+
"""
|
1692
|
+
jones_polynomial = self[self.items.jones_polynomial]
|
1693
|
+
|
1694
|
+
if original:
|
1695
|
+
return jones_polynomial
|
1696
|
+
|
1697
|
+
if skein_normalization:
|
1698
|
+
if not variab:
|
1699
|
+
variab = 'A'
|
1700
|
+
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
|
1701
|
+
R = LaurentPolynomialRing(ZZ, variab)
|
1702
|
+
else:
|
1703
|
+
if not variab:
|
1704
|
+
if use_sqrt or self.is_knot() or puiseux:
|
1705
|
+
variab = 't'
|
1706
|
+
else:
|
1707
|
+
variab = 'x'
|
1708
|
+
if puiseux:
|
1709
|
+
from sage.rings.puiseux_series_ring import PuiseuxSeriesRing # since PuiseuxPolynomial is not available, so far
|
1710
|
+
R = PuiseuxSeriesRing(ZZ, variab)
|
1711
|
+
else:
|
1712
|
+
from sage.symbolic.ring import SR
|
1713
|
+
R = SR
|
1714
|
+
|
1715
|
+
if not jones_polynomial and self.crossing_number() == 0:
|
1716
|
+
return R.one()
|
1717
|
+
|
1718
|
+
t = R(variab)
|
1719
|
+
if skein_normalization:
|
1720
|
+
if self.is_knot():
|
1721
|
+
lc = {'t': t**4}
|
1722
|
+
else:
|
1723
|
+
lc = {'x': t**2}
|
1724
|
+
else:
|
1725
|
+
if self.is_knot():
|
1726
|
+
lc = {'t': t}
|
1727
|
+
elif puiseux:
|
1728
|
+
lc = {'x': t**(1/2)}
|
1729
|
+
elif use_sqrt:
|
1730
|
+
from sage.misc.functional import sqrt
|
1731
|
+
lc = {'x': sqrt(t)}
|
1732
|
+
else:
|
1733
|
+
lc = {'x': t}
|
1734
|
+
|
1735
|
+
return R(eval_knotinfo(jones_polynomial, locals=lc))
|
1736
|
+
|
1737
|
+
@cached_method
|
1738
|
+
def alexander_polynomial(self, var='t', original=False, laurent_poly=False):
|
1739
|
+
r"""
|
1740
|
+
Return the Alexander polynomial according to the value of column
|
1741
|
+
``alexander_polynomial`` for this knot as an instance of
|
1742
|
+
:class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
|
1743
|
+
|
1744
|
+
It is obtained from the Seifert matrix `V` of ``self`` by the following
|
1745
|
+
formula (see the KnotInfo description web-page; to launch it see the
|
1746
|
+
example below):
|
1747
|
+
|
1748
|
+
.. MATH::
|
1749
|
+
|
1750
|
+
A(L) = \det(V -t V^t)
|
1751
|
+
|
1752
|
+
Here `V^t` stands for the transpose of `V`.
|
1753
|
+
|
1754
|
+
|
1755
|
+
INPUT:
|
1756
|
+
|
1757
|
+
- ``var`` -- (default: ``'t'``) the variable
|
1758
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1759
|
+
``True`` the original table entry is returned as a string
|
1760
|
+
- ``laurent_poly`` -- boolean (default: ``False``); see the note below
|
1761
|
+
|
1762
|
+
OUTPUT:
|
1763
|
+
|
1764
|
+
A polynomial over the integers, more precisely an instance of
|
1765
|
+
:class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
|
1766
|
+
If ``laurent_poly`` is set to ``True`` a Laurent polynomial
|
1767
|
+
over the integers, more precisely an instance of
|
1768
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial`
|
1769
|
+
is returned. If ``original`` is set to ``True`` then a string
|
1770
|
+
is returned.
|
1771
|
+
|
1772
|
+
.. NOTE::
|
1773
|
+
|
1774
|
+
As an invariant the Alexander polynomial is only unique up to
|
1775
|
+
a unit factor in the Laurent polynomial ring over the integers
|
1776
|
+
in the indeterminate `t`. While the normalization of the exponents
|
1777
|
+
in KnotInfo guarantees it to be a proper polynomial, this is
|
1778
|
+
not the case for the implementation in Sage. Use the keyword
|
1779
|
+
``laurent_poly`` to achiev a normalization according to Sage.
|
1780
|
+
But, still there may be a difference in sign (see the example below).
|
1781
|
+
|
1782
|
+
EXAMPLES::
|
1783
|
+
|
1784
|
+
sage: K = KnotInfo.K4_1
|
1785
|
+
sage: Ka = K.alexander_polynomial(); Ka
|
1786
|
+
t^2 - 3*t + 1
|
1787
|
+
|
1788
|
+
Comparison with Sage's results::
|
1789
|
+
|
1790
|
+
sage: k = K.link()
|
1791
|
+
sage: ka = k.alexander_polynomial(); ka
|
1792
|
+
-t^-1 + 3 - t
|
1793
|
+
sage: K.alexander_polynomial(laurent_poly=True)
|
1794
|
+
t^-1 - 3 + t
|
1795
|
+
sage: _ == -ka
|
1796
|
+
True
|
1797
|
+
|
1798
|
+
Launch the KnotInfo description web-page::
|
1799
|
+
|
1800
|
+
sage: K.items.alexander_polynomial.description_webpage() # not tested
|
1801
|
+
True
|
1802
|
+
"""
|
1803
|
+
alexander_polynomial = self[self.items.alexander_polynomial]
|
1804
|
+
|
1805
|
+
if original:
|
1806
|
+
return alexander_polynomial
|
1807
|
+
|
1808
|
+
if laurent_poly:
|
1809
|
+
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
|
1810
|
+
R = LaurentPolynomialRing(ZZ, var)
|
1811
|
+
else:
|
1812
|
+
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
|
1813
|
+
R = PolynomialRing(ZZ, var)
|
1814
|
+
|
1815
|
+
if not alexander_polynomial and self.crossing_number() == 0:
|
1816
|
+
return R.one()
|
1817
|
+
|
1818
|
+
t, = R.gens()
|
1819
|
+
lc = {'t': t}
|
1820
|
+
ap = R(eval_knotinfo(alexander_polynomial, locals=lc))
|
1821
|
+
if not laurent_poly or ap.is_constant():
|
1822
|
+
return ap
|
1823
|
+
|
1824
|
+
exp = ap.exponents()
|
1825
|
+
return t ** ((-max(exp) - min(exp)) // 2) * ap
|
1826
|
+
|
1827
|
+
@cached_method
|
1828
|
+
def conway_polynomial(self, var='t', original=False):
|
1829
|
+
r"""
|
1830
|
+
Return the Conway polynomial according to the value of column
|
1831
|
+
``conway_polynomial`` for this knot or link as an instance of
|
1832
|
+
:class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
|
1833
|
+
|
1834
|
+
It is obtained from the Seifert matrix `V` of ``self`` by the following
|
1835
|
+
formula (see the KnotInfo description web-page; to launch it see the
|
1836
|
+
example below):
|
1837
|
+
|
1838
|
+
.. MATH::
|
1839
|
+
|
1840
|
+
\nabla(L) = \det(t^{\frac{1}{2}} V -t^{\frac{-1}{2}} V^t)
|
1841
|
+
|
1842
|
+
Here `V^t` stands for the transpose of `V`.
|
1843
|
+
|
1844
|
+
|
1845
|
+
INPUT:
|
1846
|
+
|
1847
|
+
- ``var`` -- (default: ``'t'``) the variable
|
1848
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1849
|
+
``True`` the original table entry is returned as a string
|
1850
|
+
|
1851
|
+
OUTPUT:
|
1852
|
+
|
1853
|
+
A polynomial over the integers, more precisely an instance of
|
1854
|
+
:class:`~sage.rings.polynomial.polynomial_element.Polynomial`.
|
1855
|
+
If ``original`` is set to ``True`` then a string is returned.
|
1856
|
+
|
1857
|
+
EXAMPLES::
|
1858
|
+
|
1859
|
+
sage: K = KnotInfo.K4_1
|
1860
|
+
sage: Kc = K.conway_polynomial(); Kc
|
1861
|
+
-t^2 + 1
|
1862
|
+
sage: L = KnotInfo.L5a1_0
|
1863
|
+
sage: Lc = L.conway_polynomial(); Lc
|
1864
|
+
t^3
|
1865
|
+
|
1866
|
+
Comparison to Sage's results::
|
1867
|
+
|
1868
|
+
sage: Kc == K.link().conway_polynomial()
|
1869
|
+
True
|
1870
|
+
sage: Lc == L.link().conway_polynomial()
|
1871
|
+
True
|
1872
|
+
|
1873
|
+
Launch the KnotInfo description web-page::
|
1874
|
+
|
1875
|
+
sage: K.items.conway_polynomial.description_webpage() # not tested
|
1876
|
+
True
|
1877
|
+
"""
|
1878
|
+
conway_polynomial = self[self.items.conway_polynomial]
|
1879
|
+
|
1880
|
+
if original:
|
1881
|
+
return conway_polynomial
|
1882
|
+
|
1883
|
+
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
|
1884
|
+
R = PolynomialRing(ZZ, var)
|
1885
|
+
|
1886
|
+
if not conway_polynomial and self.crossing_number() == 0:
|
1887
|
+
return R.one()
|
1888
|
+
|
1889
|
+
t, = R.gens()
|
1890
|
+
lc = {'z': t}
|
1891
|
+
return R(eval_knotinfo(conway_polynomial, locals=lc))
|
1892
|
+
|
1893
|
+
@cached_method
|
1894
|
+
def khovanov_polynomial(self, var1='q', var2='t', torsion='T', ring=None, original=False,
|
1895
|
+
reduced=False, odd=False, base_ring=None):
|
1896
|
+
r"""
|
1897
|
+
Return the Khovanov polynomial according to the value of columns
|
1898
|
+
``khovanov_*`` for this knot or link as an instance of
|
1899
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
1900
|
+
|
1901
|
+
INPUT:
|
1902
|
+
|
1903
|
+
- ``var1`` -- (default: ``'q'``) the first variable; its exponents
|
1904
|
+
correspond to the height of Khovanov homology
|
1905
|
+
- ``var2`` -- (default: ``'t'``) the second variable; its exponents
|
1906
|
+
correspond to the degree of Khovanov homology
|
1907
|
+
- ``torsion`` -- (default: ``'T'``) additional variable to indicate
|
1908
|
+
the torsion of the integral homology group corresponding to the
|
1909
|
+
monomial; monomials without it correspond to torsion free ``ring``
|
1910
|
+
modules; if it appears, its exponents stands for the modulus of
|
1911
|
+
the torsion
|
1912
|
+
- ``ring`` -- (default: ``ZZ`` for knots and ``QQ`` for proper links)
|
1913
|
+
the ring for the homology
|
1914
|
+
- ``original`` -- boolean (default: ``False``); if set to
|
1915
|
+
``True`` the original table entry is returned as a string
|
1916
|
+
- ``reduced`` -- boolean (default: ``False``); if set to ``True``
|
1917
|
+
the reduced version of the homology is used
|
1918
|
+
- ``odd`` -- boolean (default: ``False``); if set to ``True``
|
1919
|
+
the odd version of the homology is used
|
1920
|
+
|
1921
|
+
OUTPUT:
|
1922
|
+
|
1923
|
+
A two or three (for integral homology) variate Laurent polynomial over
|
1924
|
+
``ZZ``, more precisely an instance of
|
1925
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
1926
|
+
If ``original`` is set to ``True`` then a string is returned.
|
1927
|
+
|
1928
|
+
.. NOTE ::
|
1929
|
+
|
1930
|
+
The data used for multi-component links were calculated with the program
|
1931
|
+
`KhoHo <https://github.com/AShumakovitch/KhoHo>`__.which uses the ``DT``
|
1932
|
+
notation. For knots data calculated with
|
1933
|
+
`KnotJob <https://www.maths.dur.ac.uk/users/dirk.schuetz/knotjob.html>`__
|
1934
|
+
are used. The latter program is more accurate in terms of orientation
|
1935
|
+
and reflection as it is based on ``PD`` code.
|
1936
|
+
|
1937
|
+
EXAMPLES::
|
1938
|
+
|
1939
|
+
sage: K = KnotInfo.K6_3
|
1940
|
+
sage: Kk = K.khovanov_polynomial(); Kk
|
1941
|
+
q^7*t^3 + q^5*t^3*T^2 + q^5*t^2 + q^3*t^2*T^2 + q^3*t^2 + q^3*t + q*t*T^2 + q*t
|
1942
|
+
+ 2*q + q^-1*T^2 + 2*q^-1 + q^-1*t^-1 + q^-3*t^-1*T^2 + q^-3*t^-1 + q^-3*t^-2
|
1943
|
+
+ q^-5*t^-2*T^2 + q^-5*t^-2 + q^-7*t^-3
|
1944
|
+
sage: L = KnotInfo.L5a1_0
|
1945
|
+
sage: Lk = L.khovanov_polynomial(); Lk
|
1946
|
+
q^4*t^2 + t + 2 + 2*q^-2 + q^-2*t^-1 + q^-4*t^-2 + q^-6*t^-2 + q^-8*t^-3
|
1947
|
+
sage: L.khovanov_polynomial(original=True)
|
1948
|
+
'2 + 2/q^2 + 1/(q^8*t^3) + 1/(q^6*t^2) + 1/(q^4*t^2) + 1/(q^2*t) + t + q^4*t^2'
|
1949
|
+
|
1950
|
+
Obtaining the reduced homology (for knots only)::
|
1951
|
+
|
1952
|
+
sage: Kkr = K.khovanov_polynomial(reduced=True); Kkr
|
1953
|
+
q^6*t^3 + 2*q^4*t^2 + 2*q^2*t + 3 + 2*q^-2*t^-1 + 2*q^-4*t^-2 + q^-6*t^-3
|
1954
|
+
sage: K.khovanov_polynomial(ring=QQ, reduced=True) == Kkr
|
1955
|
+
True
|
1956
|
+
sage: Kkr2 = K.khovanov_polynomial(var1='p', ring=GF(2), reduced=True); Kkr2
|
1957
|
+
p^6*t^3 + 2*p^4*t^2 + 2*p^2*t + 3 + 2*p^-2*t^-1 + 2*p^-4*t^-2 + p^-6*t^-3
|
1958
|
+
sage: KnotInfo.K8_19.inject() # optional database_knotinfo
|
1959
|
+
Defining K8_19
|
1960
|
+
sage: K8kr = K8_19.khovanov_polynomial(reduced=True); K8kr # optional database_knotinfo
|
1961
|
+
q^16*t^5 + q^12*t^4 + q^12*t^3 + q^10*t^2 + q^6
|
1962
|
+
|
1963
|
+
Obtaining the odd Khovanov homology (for knots only)::
|
1964
|
+
|
1965
|
+
sage: K.khovanov_polynomial(odd=True) == Kkr
|
1966
|
+
True
|
1967
|
+
sage: K.khovanov_polynomial(ring=QQ, odd=True) == Kkr
|
1968
|
+
True
|
1969
|
+
sage: K.khovanov_polynomial(var1='p', ring=GF(2), odd=True) == Kkr2
|
1970
|
+
True
|
1971
|
+
sage: K8ko = K8_19.khovanov_polynomial(odd=True); K8ko # optional database_knotinfo
|
1972
|
+
q^14*t^5*T^3 + q^16*t^5 + q^12*t^4*T^2 + q^10*t^2 + q^6
|
1973
|
+
sage: K8kr == K8ko # optional database_knotinfo
|
1974
|
+
False
|
1975
|
+
|
1976
|
+
Caution::
|
1977
|
+
|
1978
|
+
sage: Kk2 = K.khovanov_polynomial(ring=GF(2))
|
1979
|
+
Traceback (most recent call last):
|
1980
|
+
...
|
1981
|
+
NotImplementedError: unreduced Khovanov polynomial available only for integral homology
|
1982
|
+
|
1983
|
+
Comparison to Sage's results::
|
1984
|
+
|
1985
|
+
sage: Kk == K.link().khovanov_polynomial()
|
1986
|
+
True
|
1987
|
+
sage: Lk == L.link().khovanov_polynomial(ring=QQ)
|
1988
|
+
True
|
1989
|
+
|
1990
|
+
TESTS::
|
1991
|
+
|
1992
|
+
sage: KnotInfo.K0_1.inject()
|
1993
|
+
Defining K0_1
|
1994
|
+
sage: K0_1.khovanov_polynomial()
|
1995
|
+
q + q^-1
|
1996
|
+
sage: K0_1.khovanov_polynomial(reduced=True)
|
1997
|
+
1
|
1998
|
+
sage: K0_1.khovanov_polynomial(odd=True)
|
1999
|
+
1
|
2000
|
+
sage: K0_1.khovanov_polynomial(ring=GF(3), reduced=True)
|
2001
|
+
Traceback (most recent call last):
|
2002
|
+
...
|
2003
|
+
ValueError: characteristic 3 of ring is not valid
|
2004
|
+
sage: K0_1.khovanov_polynomial(ring=GF(3), odd=True)
|
2005
|
+
Traceback (most recent call last):
|
2006
|
+
...
|
2007
|
+
ValueError: characteristic 3 of ring is not valid
|
2008
|
+
sage: L.khovanov_polynomial(ring=GF(2))
|
2009
|
+
Traceback (most recent call last):
|
2010
|
+
...
|
2011
|
+
NotImplementedError: Khovanov polynomial not available for multi-component links for this ring
|
2012
|
+
|
2013
|
+
REFERENCES:
|
2014
|
+
|
2015
|
+
- :wikipedia:`Khovanov_homology`
|
2016
|
+
- :wikipedia:`Reduced_homology`
|
2017
|
+
- [ORS2013]_
|
2018
|
+
- `KnotInfo <https://knotinfo.math.indiana.edu/descriptions/khovanov_unreduced_integral_polynomial.html>`__
|
2019
|
+
"""
|
2020
|
+
if not ring:
|
2021
|
+
if self.is_knot():
|
2022
|
+
ring = ZZ
|
2023
|
+
else:
|
2024
|
+
from sage.rings.rational_field import QQ
|
2025
|
+
ring = QQ
|
2026
|
+
|
2027
|
+
if base_ring:
|
2028
|
+
ring = base_ring
|
2029
|
+
from sage.misc.superseded import deprecation
|
2030
|
+
deprecation(40149, "base_ring is deprecated, use argument ring instead.")
|
2031
|
+
|
2032
|
+
ch = ring.characteristic()
|
2033
|
+
integral = (ch == 0) and (not ring.is_field())
|
2034
|
+
if not self.is_knot():
|
2035
|
+
# KnotJob calculated results only available for knots
|
2036
|
+
if ch == 0 and ring.is_field():
|
2037
|
+
khovanov_polynomial = self[self.items.khovanov_polynomial]
|
2038
|
+
else:
|
2039
|
+
raise NotImplementedError('Khovanov polynomial not available for multi-component links for this ring')
|
2040
|
+
else:
|
2041
|
+
if reduced:
|
2042
|
+
if integral:
|
2043
|
+
khovanov_polynomial = self[self.items.khovanov_reduced_integral_polynomial]
|
2044
|
+
elif ch == 0:
|
2045
|
+
khovanov_polynomial = self[self.items.khovanov_reduced_rational_polynomial]
|
2046
|
+
elif ch == 2:
|
2047
|
+
khovanov_polynomial = self[self.items.khovanov_reduced_mod2_polynomial]
|
2048
|
+
else:
|
2049
|
+
raise ValueError('characteristic %s of ring is not valid' % ch)
|
2050
|
+
elif odd:
|
2051
|
+
if integral:
|
2052
|
+
khovanov_polynomial = self[self.items.khovanov_odd_integral_polynomial]
|
2053
|
+
elif ch == 0:
|
2054
|
+
khovanov_polynomial = self[self.items.khovanov_odd_rational_polynomial]
|
2055
|
+
elif ch == 2:
|
2056
|
+
khovanov_polynomial = self[self.items.khovanov_odd_mod2_polynomial]
|
2057
|
+
else:
|
2058
|
+
raise ValueError('characteristic %s of ring is not valid' % ch)
|
2059
|
+
else:
|
2060
|
+
if integral:
|
2061
|
+
khovanov_polynomial = self[self.items.khovanov_unreduced_integral_polynomial]
|
2062
|
+
else:
|
2063
|
+
raise NotImplementedError('unreduced Khovanov polynomial available only for integral homology')
|
2064
|
+
|
2065
|
+
if original:
|
2066
|
+
return khovanov_polynomial
|
2067
|
+
|
2068
|
+
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
|
2069
|
+
if integral:
|
2070
|
+
var_names = [var1, var2, torsion]
|
2071
|
+
else:
|
2072
|
+
var_names = [var1, var2]
|
2073
|
+
|
2074
|
+
R = LaurentPolynomialRing(ZZ, var_names)
|
2075
|
+
|
2076
|
+
if not khovanov_polynomial and self.crossing_number() == 0:
|
2077
|
+
if reduced or odd:
|
2078
|
+
return R.one()
|
2079
|
+
else:
|
2080
|
+
if integral:
|
2081
|
+
return R({(1, 0, 0): 1, (-1, 0, 0): 1})
|
2082
|
+
else:
|
2083
|
+
return R({(1, 0): 1, (-1, 0): 1})
|
2084
|
+
|
2085
|
+
if not khovanov_polynomial:
|
2086
|
+
# given just for links with less than 12 crossings
|
2087
|
+
raise NotImplementedError('Khovanov polynomial not available for this link')
|
2088
|
+
|
2089
|
+
from sage.repl.preparse import implicit_mul
|
2090
|
+
# since implicit_mul does not know about the choice of variable names
|
2091
|
+
# we have to insert * between them separately
|
2092
|
+
for i in ['q', 't', 'T', ')']:
|
2093
|
+
for j in ['q', 't', 'T', '(']:
|
2094
|
+
khovanov_polynomial = khovanov_polynomial.replace('%s%s' % (i, j), '%s*%s' % (i, j))
|
2095
|
+
khovanov_polynomial = implicit_mul(khovanov_polynomial)
|
2096
|
+
gens = R.gens_dict()
|
2097
|
+
lc = {}
|
2098
|
+
lc['q'] = gens[var1]
|
2099
|
+
lc['t'] = gens[var2]
|
2100
|
+
if integral:
|
2101
|
+
lc['T'] = gens[torsion]
|
2102
|
+
|
2103
|
+
return R(eval_knotinfo(khovanov_polynomial, locals=lc))
|
2104
|
+
|
2105
|
+
@cached_method
|
2106
|
+
def link(self, use_item=db.columns().pd_notation, snappy=False):
|
2107
|
+
r"""
|
2108
|
+
Return ``self`` as an instance of :class:`Link` or optional
|
2109
|
+
``spherogram.links.invariants.Link`` (SnapPy).
|
2110
|
+
|
2111
|
+
INPUT:
|
2112
|
+
|
2113
|
+
- ``use_item`` -- (default: ``self.items.pd_notation``)
|
2114
|
+
instance of :class:`KnotInfoColumns` to choose the column
|
2115
|
+
that should be used to construct the link. Allowed values
|
2116
|
+
are:
|
2117
|
+
|
2118
|
+
- ``self.items.pd_notation``
|
2119
|
+
- ``self.items.braid_notation``
|
2120
|
+
- ``self.items.name`` (only for ``snappy=True``)
|
2121
|
+
- ``self.items.dt_notation`` (only for knots and ``snappy=False``)
|
2122
|
+
- ``self.items.gauss_notation`` (only for knots and ``snappy=False``)
|
2123
|
+
|
2124
|
+
- ``snappy`` -- boolean (default: ``False``); if set to ``True``
|
2125
|
+
the target of the conversion is the ``pip`` installable
|
2126
|
+
package `SnapPy <https://snappy.math.uic.edu/index.html>`__
|
2127
|
+
(explicitely, ``spherogram.links.invariants.Link``).
|
2128
|
+
If SnapPy is not installed an :exc:`ImportError` is raised. To
|
2129
|
+
install SnapPy use ``sage -pip install snappy``.
|
2130
|
+
|
2131
|
+
.. NOTE::
|
2132
|
+
|
2133
|
+
We use the PD-notation to construct ``self`` as
|
2134
|
+
default. This ensures that the number of crossings
|
2135
|
+
displayed in the representation string of the link
|
2136
|
+
coincides with the crossing number as a topological
|
2137
|
+
invariant.
|
2138
|
+
|
2139
|
+
Furthermore, note that the mirror version may depend
|
2140
|
+
on the used KnotInfo-notation. For instance, regarding to
|
2141
|
+
the knot ``5_1`` the Gauss- and the DT-notation refer to
|
2142
|
+
the mirror image (see example below).
|
2143
|
+
|
2144
|
+
EXAMPLES::
|
2145
|
+
|
2146
|
+
sage: K = KnotInfo.K3_1
|
2147
|
+
sage: K.link()
|
2148
|
+
Knot represented by 3 crossings
|
2149
|
+
sage: _.braid()
|
2150
|
+
s^3
|
2151
|
+
sage: _ == K.braid()
|
2152
|
+
True
|
2153
|
+
|
2154
|
+
using ``dt_notation``::
|
2155
|
+
|
2156
|
+
sage: K.link(use_item=K.items.dt_notation)
|
2157
|
+
Knot represented by 3 crossings
|
2158
|
+
sage: _.braid()
|
2159
|
+
s^3
|
2160
|
+
|
2161
|
+
sage: L = KnotInfo.L4a1_0
|
2162
|
+
sage: L.link()
|
2163
|
+
Link with 2 components represented by 4 crossings
|
2164
|
+
|
2165
|
+
sage: L.link(use_item=L.items.dt_notation)
|
2166
|
+
Traceback (most recent call last):
|
2167
|
+
...
|
2168
|
+
ValueError: link construction using Columns.dt_notation not possible
|
2169
|
+
|
2170
|
+
using ``snappy``::
|
2171
|
+
|
2172
|
+
sage: L2 = KnotInfo.L2a1_1
|
2173
|
+
sage: l2 = L2.link()
|
2174
|
+
sage: l2s = L2.link(snappy=True).sage_link() # optional - snappy
|
2175
|
+
sage: l2 == l2s # optional - snappy
|
2176
|
+
True
|
2177
|
+
|
2178
|
+
but observe::
|
2179
|
+
|
2180
|
+
sage: K7 = KnotInfo.K7_2
|
2181
|
+
sage: k7s = K7.link(snappy=True); k7s # optional - snappy
|
2182
|
+
<Link: 1 comp; 7 cross>
|
2183
|
+
sage: k7sn = K7.link(K7.items.name, snappy=True); k7sn # optional - snappy
|
2184
|
+
<Link 7_2: 1 comp; 7 cross>
|
2185
|
+
sage: k7s.sage_link().is_isotopic(k7sn) # optional - snappy
|
2186
|
+
False
|
2187
|
+
sage: k7snm = k7sn.sage_link().mirror_image() # optional - snappy
|
2188
|
+
sage: k7s.sage_link().is_isotopic(k7snm) # optional - snappy
|
2189
|
+
True
|
2190
|
+
|
2191
|
+
using ``braid_notation``::
|
2192
|
+
|
2193
|
+
sage: L2.link(use_item=L.items.braid_notation) == l2
|
2194
|
+
True
|
2195
|
+
|
2196
|
+
observe::
|
2197
|
+
|
2198
|
+
sage: L.link(use_item=L.items.braid_notation)
|
2199
|
+
Link with 2 components represented by 5 crossings
|
2200
|
+
|
2201
|
+
sage: K6_1 = KnotInfo.K6_1
|
2202
|
+
sage: K6_1.link().braid() == K6_1.braid()
|
2203
|
+
False
|
2204
|
+
|
2205
|
+
also observe::
|
2206
|
+
|
2207
|
+
sage: K4_1 = KnotInfo.K4_1
|
2208
|
+
sage: K4_1.link().pd_code()
|
2209
|
+
[[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]]
|
2210
|
+
sage: K4_1.pd_notation()
|
2211
|
+
[[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]]
|
2212
|
+
|
2213
|
+
sage: K5_1 = KnotInfo.K5_1
|
2214
|
+
sage: K5_1.link().braid()
|
2215
|
+
s^5
|
2216
|
+
sage: K5_1.link(K5_1.items.dt_notation).braid()
|
2217
|
+
s^-5
|
2218
|
+
sage: K5_1.link(K5_1.items.gauss_notation).braid()
|
2219
|
+
s^-5
|
2220
|
+
"""
|
2221
|
+
if not isinstance(use_item, KnotInfoColumns):
|
2222
|
+
raise TypeError('%s must be an instance of %s' % (use_item, KnotInfoColumns))
|
2223
|
+
|
2224
|
+
if snappy:
|
2225
|
+
try:
|
2226
|
+
from snappy import Link
|
2227
|
+
except ImportError:
|
2228
|
+
raise ImportError('this option demands snappy to be installed')
|
2229
|
+
elif self.is_knot():
|
2230
|
+
from sage.knots.knot import Knot as Link
|
2231
|
+
else:
|
2232
|
+
from sage.knots.link import Link
|
2233
|
+
|
2234
|
+
if use_item == self.items.pd_notation:
|
2235
|
+
return Link(self.pd_notation())
|
2236
|
+
elif use_item == self.items.braid_notation:
|
2237
|
+
return Link(self.braid())
|
2238
|
+
elif use_item == self.items.name and snappy:
|
2239
|
+
if not self.is_knot():
|
2240
|
+
use_item = self.items.name_unoriented
|
2241
|
+
return Link(self[use_item])
|
2242
|
+
elif self.is_knot() and not snappy:
|
2243
|
+
# Construction via Gauss and DT-Code only possible for knots
|
2244
|
+
from sage.knots.knot import Knots
|
2245
|
+
if use_item == self.items.dt_notation:
|
2246
|
+
return Knots().from_dowker_code(self.dt_notation())
|
2247
|
+
elif use_item == self.items.gauss_notation:
|
2248
|
+
return Knots().from_gauss_code(self.gauss_notation())
|
2249
|
+
|
2250
|
+
raise ValueError('link construction using %s not possible' % use_item)
|
2251
|
+
|
2252
|
+
@cached_method
|
2253
|
+
def is_unique(self) -> bool:
|
2254
|
+
r"""
|
2255
|
+
Return whether there is no other isotopic link in the database or not.
|
2256
|
+
|
2257
|
+
OUTPUT: boolean or ``None`` if this cannot be determined
|
2258
|
+
|
2259
|
+
EXAMPLES::
|
2260
|
+
|
2261
|
+
sage: KnotInfo.L4a1_0.is_unique()
|
2262
|
+
True
|
2263
|
+
sage: KnotInfo.L5a1_0.is_unique()
|
2264
|
+
False
|
2265
|
+
sage: L = KnotInfo.L9a43_0_1 # optional - database_knotinfo
|
2266
|
+
sage: L.series(oriented=True).inject() # optional - database_knotinfo
|
2267
|
+
Defining L9a43
|
2268
|
+
sage: [(L,L.is_unique()) for L in L9a43] # optional - database_knotinfo
|
2269
|
+
[(<KnotInfo.L9a43_0_0: 'L9a43{0,0}'>, True),
|
2270
|
+
(<KnotInfo.L9a43_1_0: 'L9a43{1,0}'>, False),
|
2271
|
+
(<KnotInfo.L9a43_0_1: 'L9a43{0,1}'>, None),
|
2272
|
+
(<KnotInfo.L9a43_1_1: 'L9a43{1,1}'>, False)]
|
2273
|
+
"""
|
2274
|
+
# an isotopic pair must have the same unoriented name. So, we can focus
|
2275
|
+
# on such series
|
2276
|
+
if self.is_knot():
|
2277
|
+
return True
|
2278
|
+
S = self.series(oriented=True)
|
2279
|
+
hp = self.homfly_polynomial()
|
2280
|
+
Sl = S.list(homfly=hp)
|
2281
|
+
if len(Sl) == 1:
|
2282
|
+
return True
|
2283
|
+
kp = self.kauffman_polynomial()
|
2284
|
+
Sl = [L for L in Sl if L != self and L.kauffman_polynomial() == kp]
|
2285
|
+
if not Sl:
|
2286
|
+
return True
|
2287
|
+
|
2288
|
+
b = self.braid()
|
2289
|
+
for L in Sl:
|
2290
|
+
Lb = L.braid()
|
2291
|
+
if L.braid() == b:
|
2292
|
+
return False
|
2293
|
+
if Lb.is_conjugated(b):
|
2294
|
+
return False
|
2295
|
+
|
2296
|
+
return None
|
2297
|
+
|
2298
|
+
@cached_method
|
2299
|
+
def is_recoverable(self, unique=True) -> bool:
|
2300
|
+
r"""
|
2301
|
+
Return if ``self`` can be recovered from its conversion to Sage links
|
2302
|
+
using the ``pd_notation`` and the ``braid_notation`` and their
|
2303
|
+
mirror images.
|
2304
|
+
|
2305
|
+
The method is indirectly used by the ``TestSuite`` of the series of ``self``.
|
2306
|
+
|
2307
|
+
INPUT:
|
2308
|
+
|
2309
|
+
- ``unique`` -- boolean (default: ``True``); if set to ``False``
|
2310
|
+
it is only checked if ``self`` is among the recovered items
|
2311
|
+
|
2312
|
+
EXAMPLES::
|
2313
|
+
|
2314
|
+
sage: KnotInfo.L4a1_0.inject()
|
2315
|
+
Defining L4a1_0
|
2316
|
+
sage: L4a1_0.is_recoverable()
|
2317
|
+
True
|
2318
|
+
sage: L4a1_0.is_recoverable(unique=False)
|
2319
|
+
True
|
2320
|
+
sage: KnotInfo.L5a1_0.inject()
|
2321
|
+
Defining L5a1_0
|
2322
|
+
sage: L5a1_0.is_recoverable()
|
2323
|
+
False
|
2324
|
+
sage: L5a1_0.is_recoverable(unique=False)
|
2325
|
+
True
|
2326
|
+
|
2327
|
+
TESTS:
|
2328
|
+
|
2329
|
+
sage: KnotInfo.K12a_165.is_recoverable(unique=False) # optional - database_knotinfo, long time
|
2330
|
+
True
|
2331
|
+
"""
|
2332
|
+
def recover(sym_mut, braid):
|
2333
|
+
r"""
|
2334
|
+
Check if ``self`` can be recovered form its associated
|
2335
|
+
Sage link.
|
2336
|
+
"""
|
2337
|
+
if braid:
|
2338
|
+
l = self.link(self.items.braid_notation)
|
2339
|
+
else:
|
2340
|
+
l = self.link()
|
2341
|
+
if sym_mut is SymmetryMutant.mirror_image:
|
2342
|
+
l = l.mirror_image()
|
2343
|
+
elif sym_mut is SymmetryMutant.reverse:
|
2344
|
+
l = l.reverse()
|
2345
|
+
elif sym_mut is SymmetryMutant.concordance_inverse:
|
2346
|
+
l = l.mirror_image().reservse()
|
2347
|
+
|
2348
|
+
def check_result(res):
|
2349
|
+
r"""
|
2350
|
+
Check a single result from ``get_knotinfo``.
|
2351
|
+
"""
|
2352
|
+
if type(res) is tuple:
|
2353
|
+
L, s = res
|
2354
|
+
else:
|
2355
|
+
L, s = res.to_knotinfo()[0]
|
2356
|
+
if not isinstance(L, KnotInfoBase):
|
2357
|
+
return False
|
2358
|
+
if L != self:
|
2359
|
+
return False
|
2360
|
+
return s == sym_mut
|
2361
|
+
|
2362
|
+
try:
|
2363
|
+
res = l.get_knotinfo(unique=unique)
|
2364
|
+
except NotImplementedError:
|
2365
|
+
return False
|
2366
|
+
if unique:
|
2367
|
+
return check_result(res)
|
2368
|
+
else:
|
2369
|
+
return any(check_result(r) for r in res)
|
2370
|
+
|
2371
|
+
from sage.misc.misc import some_tuples
|
2372
|
+
if SymmetryMutant.unknown.matches(self):
|
2373
|
+
sym_muts = [SymmetryMutant.unknown]
|
2374
|
+
else:
|
2375
|
+
sym_muts = [s for s in SymmetryMutant if s.is_minimal(self)]
|
2376
|
+
return all(recover(sym, braid) for sym, braid in some_tuples(sym_muts, 2, 8))
|
2377
|
+
|
2378
|
+
def inject(self, verbose=True):
|
2379
|
+
"""
|
2380
|
+
Inject ``self`` with its name into the namespace of the
|
2381
|
+
Python code from which this function is called.
|
2382
|
+
|
2383
|
+
INPUT:
|
2384
|
+
|
2385
|
+
- ``verbose`` -- boolean (default: ``True``); whether to suppress
|
2386
|
+
the message printed on the invocation
|
2387
|
+
|
2388
|
+
EXAMPLES::
|
2389
|
+
|
2390
|
+
sage: KnotInfo.K5_2.inject()
|
2391
|
+
Defining K5_2
|
2392
|
+
sage: K5_2.is_alternating()
|
2393
|
+
True
|
2394
|
+
"""
|
2395
|
+
name = self.name
|
2396
|
+
if verbose:
|
2397
|
+
print("Defining %s" % (name))
|
2398
|
+
from sage.repl.user_globals import set_global
|
2399
|
+
set_global(name, self)
|
2400
|
+
|
2401
|
+
@cached_method
|
2402
|
+
def series(self, oriented=False):
|
2403
|
+
r"""
|
2404
|
+
Return the series of links ``self`` belongs to.
|
2405
|
+
|
2406
|
+
INPUT:
|
2407
|
+
|
2408
|
+
- ``oriented`` -- boolean (default: ``False``); it only affects proper
|
2409
|
+
links. By default the items of the series will be again series of
|
2410
|
+
links collecting all orientation mutants of an unoriented name. To
|
2411
|
+
obtain the series of the individual links this keyword has to be set
|
2412
|
+
to ``True``.
|
2413
|
+
|
2414
|
+
EXAMPLES::
|
2415
|
+
|
2416
|
+
sage: K5 = KnotInfo.K5_2.series()
|
2417
|
+
sage: K5(1)
|
2418
|
+
<KnotInfo.K5_1: '5_1'>
|
2419
|
+
sage: KnotInfo.L4a1_1.series().inject()
|
2420
|
+
Defining L4a
|
2421
|
+
sage: L4a(1)
|
2422
|
+
Series of links L4a1
|
2423
|
+
sage: KnotInfo.L4a1_1.series(oriented=True).inject()
|
2424
|
+
Defining L4a1
|
2425
|
+
sage: L4a(1) == L4a1
|
2426
|
+
True
|
2427
|
+
sage: L4a1(1)
|
2428
|
+
<KnotInfo.L4a1_1: 'L4a1{1}'>
|
2429
|
+
"""
|
2430
|
+
if oriented:
|
2431
|
+
S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating(), self.name_unoriented())
|
2432
|
+
else:
|
2433
|
+
S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating())
|
2434
|
+
return S
|
2435
|
+
|
2436
|
+
def diagram(self, single=False, new=0, autoraise=True):
|
2437
|
+
r"""
|
2438
|
+
Launch the diagram of ``self`` given on the KnotInfo web-page.
|
2439
|
+
|
2440
|
+
INPUT:
|
2441
|
+
|
2442
|
+
- ``single`` -- boolean (default: ``False``); if set to ``True`` only one
|
2443
|
+
diagram is shown
|
2444
|
+
- ``new`` -- integer according to :func:`open` of :mod:`webbrowser`
|
2445
|
+
(``0`` default, ``1`` new window, ``2`` new tab)
|
2446
|
+
- ``autoraise`` -- boolean (default: ``True``)
|
2447
|
+
|
2448
|
+
EXAMPLES::
|
2449
|
+
|
2450
|
+
sage: K = KnotInfo.K3_1
|
2451
|
+
sage: K.diagram() # not tested
|
2452
|
+
True
|
2453
|
+
sage: K.diagram(single=True) # not tested
|
2454
|
+
True
|
2455
|
+
"""
|
2456
|
+
import webbrowser
|
2457
|
+
if self.is_knot():
|
2458
|
+
filename = db.filename.knots
|
2459
|
+
else:
|
2460
|
+
filename = db.filename.links
|
2461
|
+
|
2462
|
+
if single:
|
2463
|
+
return webbrowser.open(filename.diagram_url(self[self.items.diagram], single=single), new=new, autoraise=autoraise)
|
2464
|
+
else:
|
2465
|
+
return webbrowser.open(filename.diagram_url(self[self.items.name]), new=new, autoraise=autoraise)
|
2466
|
+
|
2467
|
+
def knot_atlas_webpage(self, new=0, autoraise=True):
|
2468
|
+
r"""
|
2469
|
+
Launch the Knot Atlas web-page for ``self``.
|
2470
|
+
|
2471
|
+
INPUT:
|
2472
|
+
|
2473
|
+
- ``new`` -- integer according to :func:`open` of :mod:`webbrowser`
|
2474
|
+
(``0`` default, ``1`` new window, ``2`` new tab)
|
2475
|
+
- ``autoraise`` -- boolean (default: ``True``)
|
2476
|
+
|
2477
|
+
EXAMPLES::
|
2478
|
+
|
2479
|
+
sage: K = KnotInfo.K3_1
|
2480
|
+
sage: K.knot_atlas_webpage() # not tested
|
2481
|
+
True
|
2482
|
+
"""
|
2483
|
+
import webbrowser
|
2484
|
+
return webbrowser.open(self[self.items.knot_atlas_anon], new=new, autoraise=autoraise)
|
2485
|
+
|
2486
|
+
def knotilus_webpage(self, new=0, autoraise=True):
|
2487
|
+
r"""
|
2488
|
+
Launch the Knotilus web-page for ``self``.
|
2489
|
+
|
2490
|
+
INPUT:
|
2491
|
+
|
2492
|
+
- ``new`` -- integer according to :func:`open` of :mod:`webbrowser`
|
2493
|
+
(``0`` default, ``1`` new window, ``2`` new tab)
|
2494
|
+
- ``autoraise`` -- boolean (default: ``True``)
|
2495
|
+
|
2496
|
+
EXAMPLES::
|
2497
|
+
|
2498
|
+
sage: K = KnotInfo.K3_1
|
2499
|
+
sage: K.knotilus_webpage(new=1) # not tested
|
2500
|
+
True
|
2501
|
+
"""
|
2502
|
+
import webbrowser
|
2503
|
+
return webbrowser.open(self[self.items.knotilus_page_anon], new=new, autoraise=autoraise)
|
2504
|
+
|
2505
|
+
|
2506
|
+
# --------------------------------------------------------------------------------------------
|
2507
|
+
# KnotInfoSeries
|
2508
|
+
# --------------------------------------------------------------------------------------------
|
2509
|
+
class KnotInfoSeries(UniqueRepresentation, SageObject):
|
2510
|
+
r"""
|
2511
|
+
This class can be used to access knots and links via their index
|
2512
|
+
according to the series they belong to.
|
2513
|
+
|
2514
|
+
INPUT:
|
2515
|
+
|
2516
|
+
- ``crossing_number`` -- integer giving the crossing number of this series
|
2517
|
+
of links
|
2518
|
+
- ``is_knot`` -- boolean; whether this series is a series of knots
|
2519
|
+
or proper links
|
2520
|
+
- ``is_alternating`` -- boolean; whether this series is restricted to
|
2521
|
+
alternating links or not
|
2522
|
+
This is not relevant for knots with less than 11 crossings
|
2523
|
+
- ``name_unoriented`` -- string restricting the series to all links with
|
2524
|
+
that ``name_unoriented``
|
2525
|
+
|
2526
|
+
EXAMPLES::
|
2527
|
+
|
2528
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2529
|
+
sage: K6 = KnotInfoSeries(6, True, True); K6
|
2530
|
+
Series of knots K6
|
2531
|
+
sage: K6(3)
|
2532
|
+
<KnotInfo.K6_3: '6_3'>
|
2533
|
+
sage: list(K6)
|
2534
|
+
[<KnotInfo.K6_1: '6_1'>, <KnotInfo.K6_2: '6_2'>, <KnotInfo.K6_3: '6_3'>]
|
2535
|
+
sage: L6a = KnotInfoSeries(6, False, True); L6a
|
2536
|
+
Series of links L6a
|
2537
|
+
sage: L6a(2)
|
2538
|
+
Series of links L6a2
|
2539
|
+
sage: _.inject()
|
2540
|
+
Defining L6a2
|
2541
|
+
sage: list(L6a2)
|
2542
|
+
[<KnotInfo.L6a2_0: 'L6a2{0}'>, <KnotInfo.L6a2_1: 'L6a2{1}'>]
|
2543
|
+
sage: L6a2(0).series() == L6a
|
2544
|
+
True
|
2545
|
+
sage: L6a2(0) == L6a2('0')
|
2546
|
+
True
|
2547
|
+
"""
|
2548
|
+
|
2549
|
+
def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=None):
|
2550
|
+
r"""
|
2551
|
+
Python constructor.
|
2552
|
+
|
2553
|
+
EXAMPLES::
|
2554
|
+
|
2555
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2556
|
+
sage: L6a = KnotInfoSeries(6, False, True); L6a
|
2557
|
+
Series of links L6a
|
2558
|
+
"""
|
2559
|
+
self._crossing_number = crossing_number
|
2560
|
+
self._is_knot = is_knot
|
2561
|
+
self._is_alternating = is_alternating
|
2562
|
+
self._name_unoriented = name_unoriented
|
2563
|
+
|
2564
|
+
@cached_method
|
2565
|
+
def list(self, oriented=False, comp=None, det=None, homfly=None):
|
2566
|
+
r"""
|
2567
|
+
Return this series as a Python list.
|
2568
|
+
|
2569
|
+
INPUT:
|
2570
|
+
|
2571
|
+
- ``oriented`` -- boolean (default: ``False``); it only affects
|
2572
|
+
series of proper links. By default the list items of a series of proper
|
2573
|
+
links are again series of links collecting all orientation types of an
|
2574
|
+
unoriented name. To obtain the list of the individual links this
|
2575
|
+
keyword has to be set to ``True``.
|
2576
|
+
|
2577
|
+
- ``comp`` -- (default: ``None``) if given an integer for this
|
2578
|
+
keyword the list is restricted to links having the according number
|
2579
|
+
of components. This keyword implies ``oriented=True``.
|
2580
|
+
|
2581
|
+
- ``det`` -- (default: ``None``) if given an integer for this
|
2582
|
+
keyword the list is restricted to links having the according value
|
2583
|
+
for its determinant. This keyword implies ``oriented=True``.
|
2584
|
+
|
2585
|
+
- ``homfly`` -- (default: ``None``) if given a HOMFLY-PT polynomial
|
2586
|
+
having ``normalization='vz'`` for this keyword the list is restricted
|
2587
|
+
to links having the according value for its HOMFLY-PT
|
2588
|
+
polynomial. This keyword implies ``oriented=True``.
|
2589
|
+
|
2590
|
+
EXAMPLES::
|
2591
|
+
|
2592
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2593
|
+
sage: K6 = KnotInfoSeries(6, True, True); K6
|
2594
|
+
Series of knots K6
|
2595
|
+
sage: K6.list()
|
2596
|
+
[<KnotInfo.K6_1: '6_1'>, <KnotInfo.K6_2: '6_2'>, <KnotInfo.K6_3: '6_3'>]
|
2597
|
+
sage: KnotInfoSeries(2, False, True).inject()
|
2598
|
+
Defining L2a
|
2599
|
+
sage: L2a.list()
|
2600
|
+
[Series of links L2a1]
|
2601
|
+
sage: L2a.list(oriented=True)
|
2602
|
+
[<KnotInfo.L2a1_0: 'L2a1{0}'>, <KnotInfo.L2a1_1: 'L2a1{1}'>]
|
2603
|
+
"""
|
2604
|
+
if homfly is not None:
|
2605
|
+
# additional restriction to number of components, determinant and
|
2606
|
+
# HOMFLY-PT polynomial
|
2607
|
+
l = self.list(oriented=True, comp=comp, det=det)
|
2608
|
+
return [L for L in l if L.homfly_polynomial() == homfly]
|
2609
|
+
|
2610
|
+
if det is not None:
|
2611
|
+
# additional restriction to number of components and determinant
|
2612
|
+
l = self.list(oriented=True, comp=comp)
|
2613
|
+
return [L for L in l if L.determinant() == det]
|
2614
|
+
|
2615
|
+
if comp is not None:
|
2616
|
+
# additional restriction to number of components
|
2617
|
+
l = self.list(oriented=True)
|
2618
|
+
return [L for L in l if L.num_components() == comp]
|
2619
|
+
|
2620
|
+
# default case
|
2621
|
+
is_knot = self._is_knot
|
2622
|
+
cross_nr = self._crossing_number
|
2623
|
+
is_alt = self._is_alternating
|
2624
|
+
n_unori = self._name_unoriented
|
2625
|
+
|
2626
|
+
res = []
|
2627
|
+
curr_n_unori = None
|
2628
|
+
for K in KnotInfo:
|
2629
|
+
if K.is_knot() != is_knot:
|
2630
|
+
continue
|
2631
|
+
if K.crossing_number() != cross_nr:
|
2632
|
+
continue
|
2633
|
+
if not is_knot or cross_nr > 10:
|
2634
|
+
if K.is_alternating() != is_alt:
|
2635
|
+
continue
|
2636
|
+
if is_knot or oriented:
|
2637
|
+
res.append(K)
|
2638
|
+
else:
|
2639
|
+
this_n_unori = K.name_unoriented()
|
2640
|
+
if n_unori:
|
2641
|
+
if this_n_unori != n_unori:
|
2642
|
+
continue
|
2643
|
+
res.append(K)
|
2644
|
+
elif this_n_unori != curr_n_unori:
|
2645
|
+
if curr_n_unori:
|
2646
|
+
res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori))
|
2647
|
+
curr_n_unori = this_n_unori
|
2648
|
+
else:
|
2649
|
+
continue
|
2650
|
+
|
2651
|
+
if curr_n_unori:
|
2652
|
+
res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori))
|
2653
|
+
return res
|
2654
|
+
|
2655
|
+
@cached_method
|
2656
|
+
def lower_list(self, oriented=False, comp=None, det=None, homfly=None):
|
2657
|
+
r"""
|
2658
|
+
Return this series together with all series with smaller crossing number
|
2659
|
+
as a Python list.
|
2660
|
+
|
2661
|
+
INPUT:
|
2662
|
+
|
2663
|
+
- ``oriented`` -- boolean (default: ``False``); see the
|
2664
|
+
description for :meth:`list`
|
2665
|
+
|
2666
|
+
- ``comp`` -- (default: ``None``) see the description for
|
2667
|
+
:meth:`list`
|
2668
|
+
|
2669
|
+
- ``det`` -- (default: ``None``) see the description for
|
2670
|
+
:meth:`list`
|
2671
|
+
|
2672
|
+
- ``homfly`` -- (default: ``None``) see the description for
|
2673
|
+
:meth:`list`
|
2674
|
+
|
2675
|
+
EXAMPLES::
|
2676
|
+
|
2677
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2678
|
+
sage: KnotInfoSeries(5, True, True).lower_list()
|
2679
|
+
[<KnotInfo.K0_1: '0_1'>,
|
2680
|
+
<KnotInfo.K3_1: '3_1'>,
|
2681
|
+
<KnotInfo.K4_1: '4_1'>,
|
2682
|
+
<KnotInfo.K5_1: '5_1'>,
|
2683
|
+
<KnotInfo.K5_2: '5_2'>]
|
2684
|
+
sage: KnotInfoSeries(4, False, True).lower_list()
|
2685
|
+
[Series of links L2a1, Series of links L4a1]
|
2686
|
+
sage: KnotInfoSeries(4, False, True).lower_list(oriented=True)
|
2687
|
+
[<KnotInfo.L2a1_0: 'L2a1{0}'>,
|
2688
|
+
<KnotInfo.L2a1_1: 'L2a1{1}'>,
|
2689
|
+
<KnotInfo.L4a1_0: 'L4a1{0}'>,
|
2690
|
+
<KnotInfo.L4a1_1: 'L4a1{1}'>]
|
2691
|
+
"""
|
2692
|
+
l = []
|
2693
|
+
cr = self._crossing_number
|
2694
|
+
if cr > 0:
|
2695
|
+
LS = type(self)(cr - 1, self._is_knot, self._is_alternating, self._name_unoriented)
|
2696
|
+
l = LS.lower_list(oriented=oriented, comp=comp, det=det, homfly=homfly)
|
2697
|
+
return l + self.list(oriented=oriented, comp=comp, det=det, homfly=homfly)
|
2698
|
+
|
2699
|
+
def __repr__(self):
|
2700
|
+
r"""
|
2701
|
+
Return the representation string of ``self``.
|
2702
|
+
|
2703
|
+
EXAMPLES::
|
2704
|
+
|
2705
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2706
|
+
sage: KnotInfoSeries(6, True, True)
|
2707
|
+
Series of knots K6
|
2708
|
+
sage: _.__repr__()
|
2709
|
+
'Series of knots K6'
|
2710
|
+
"""
|
2711
|
+
if self._is_knot:
|
2712
|
+
return 'Series of knots %s' % (self._name())
|
2713
|
+
else:
|
2714
|
+
return 'Series of links %s' % (self._name())
|
2715
|
+
|
2716
|
+
def __getitem__(self, item):
|
2717
|
+
r"""
|
2718
|
+
Return the given ``item`` from the list of ``self``
|
2719
|
+
(making the Python build-in ``list`` work).
|
2720
|
+
|
2721
|
+
EXAMPLES::
|
2722
|
+
|
2723
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2724
|
+
sage: KnotInfoSeries(6, True, True).inject()
|
2725
|
+
Defining K6
|
2726
|
+
sage: list(K6) # indirect doctest
|
2727
|
+
[<KnotInfo.K6_1: '6_1'>, <KnotInfo.K6_2: '6_2'>, <KnotInfo.K6_3: '6_3'>]
|
2728
|
+
"""
|
2729
|
+
from sage.rings.integer import Integer
|
2730
|
+
if type(item) not in (int, Integer):
|
2731
|
+
raise ValueError('item must be an integer')
|
2732
|
+
l = self.list()
|
2733
|
+
max_item = len(l)
|
2734
|
+
if item < 0 or item > max_item:
|
2735
|
+
raise ValueError('item must be nonnegative and smaller than %s' % (max_item))
|
2736
|
+
|
2737
|
+
return l[item]
|
2738
|
+
|
2739
|
+
def __call__(self, item):
|
2740
|
+
r"""
|
2741
|
+
Return the given ``item`` from the list of ``self``
|
2742
|
+
(making the function call for ``self`` work).
|
2743
|
+
In contrast to ``__getitem__`` the first ``item``
|
2744
|
+
has to be ``1`` (not ``0``).
|
2745
|
+
|
2746
|
+
EXAMPLES::
|
2747
|
+
|
2748
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2749
|
+
sage: KnotInfoSeries(6, True, True).inject()
|
2750
|
+
Defining K6
|
2751
|
+
sage: K6(2) # indirect doctest
|
2752
|
+
<KnotInfo.K6_2: '6_2'>
|
2753
|
+
|
2754
|
+
sage: # optional - database_knotinfo
|
2755
|
+
sage: KnotInfo.L8a21_0_1_0.inject()
|
2756
|
+
Defining L8a21_0_1_0
|
2757
|
+
sage: L8a21_0_1_0.series().inject()
|
2758
|
+
Defining L8a
|
2759
|
+
sage: L8a(1)
|
2760
|
+
Series of links L8a1
|
2761
|
+
sage: L8a(21)(2) == L8a21_0_1_0
|
2762
|
+
True
|
2763
|
+
sage: L8a(21)('010') == L8a21_0_1_0
|
2764
|
+
True
|
2765
|
+
"""
|
2766
|
+
if self._name_unoriented:
|
2767
|
+
if isinstance(item, str):
|
2768
|
+
# allow input as dual number according to naming
|
2769
|
+
item = int(item, 2)
|
2770
|
+
return self[item]
|
2771
|
+
|
2772
|
+
from sage.rings.integer import Integer
|
2773
|
+
if type(item) not in (int, Integer):
|
2774
|
+
raise ValueError('item must be an integer')
|
2775
|
+
l = self.list()
|
2776
|
+
max_item = len(l) + 1
|
2777
|
+
if item < 1 or item > max_item:
|
2778
|
+
raise ValueError('item must be positive and smaller than %s' % (max_item))
|
2779
|
+
|
2780
|
+
return l[item-1]
|
2781
|
+
|
2782
|
+
def _name(self):
|
2783
|
+
r"""
|
2784
|
+
Return the name of the series.
|
2785
|
+
|
2786
|
+
EXAMPLES::
|
2787
|
+
|
2788
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
2789
|
+
sage: KnotInfoSeries(6, True, True)._name()
|
2790
|
+
'K6'
|
2791
|
+
"""
|
2792
|
+
is_knot = self._is_knot
|
2793
|
+
cross_nr = self._crossing_number
|
2794
|
+
is_alt = self._is_alternating
|
2795
|
+
n_unori = self._name_unoriented
|
2796
|
+
|
2797
|
+
alt = 'a'
|
2798
|
+
if not is_alt:
|
2799
|
+
alt = 'n'
|
2800
|
+
|
2801
|
+
if is_knot:
|
2802
|
+
if cross_nr > 10:
|
2803
|
+
res = 'K%s%s' % (cross_nr, alt)
|
2804
|
+
else:
|
2805
|
+
res = 'K%s' % (cross_nr)
|
2806
|
+
elif n_unori:
|
2807
|
+
res = '%s' % (n_unori)
|
2808
|
+
else:
|
2809
|
+
res = 'L%s%s' % (cross_nr, alt)
|
2810
|
+
return res
|
2811
|
+
|
2812
|
+
def is_recoverable(self, unique=True, max_samples=8) -> bool:
|
2813
|
+
r"""
|
2814
|
+
Return if all items of ``self`` can be recovered from its conversion to
|
2815
|
+
Sage links using the ``pd_notation`` and the ``braid_notation`` and their
|
2816
|
+
mirror images.
|
2817
|
+
|
2818
|
+
The method is indirectly used by the ``TestSuite``.
|
2819
|
+
|
2820
|
+
INPUT:
|
2821
|
+
|
2822
|
+
- ``unique`` -- boolean (default: ``True``); see
|
2823
|
+
:meth:`KnotInfoBase.is_recoverable`
|
2824
|
+
- ``max_samples`` -- nonnegative integer or ``infinity``
|
2825
|
+
(default: `8`); limits the number of items to check (random sample).
|
2826
|
+
If set to ``infinity`` then no limit is set.
|
2827
|
+
|
2828
|
+
EXAMPLES::
|
2829
|
+
|
2830
|
+
sage: KnotInfo.L4a1_0.series().inject()
|
2831
|
+
Defining L4a
|
2832
|
+
sage: L4a.is_recoverable()
|
2833
|
+
True
|
2834
|
+
sage: L4a.is_recoverable(unique=False)
|
2835
|
+
True
|
2836
|
+
sage: KnotInfo.L5a1_0.series().inject()
|
2837
|
+
Defining L5a
|
2838
|
+
sage: L5a.is_recoverable()
|
2839
|
+
False
|
2840
|
+
sage: L5a.is_recoverable(unique=False)
|
2841
|
+
True
|
2842
|
+
"""
|
2843
|
+
from sage.misc.misc import some_tuples
|
2844
|
+
l = self.list(oriented=True)
|
2845
|
+
bound = len(l)
|
2846
|
+
return all(L.is_recoverable(unique=unique) for L, in some_tuples(l, 1, bound, max_samples=max_samples))
|
2847
|
+
|
2848
|
+
def _test_recover(self, **options):
|
2849
|
+
r"""
|
2850
|
+
Method used by ``TestSuite``. Tests if all links of the series can be
|
2851
|
+
recovered from their conversion to Sage links. It uses :meth:`is_recoverable`.
|
2852
|
+
Thus, per default maximal `8` items (random sample) are tested. Use the
|
2853
|
+
option ``max_samples`` to choose another limit or test all
|
2854
|
+
(``max_samples=infinity``)
|
2855
|
+
|
2856
|
+
EXAMPLES::
|
2857
|
+
|
2858
|
+
sage: TestSuite(KnotInfo.L5a1_0.series()).run(verbose=True) # indirect doctest
|
2859
|
+
running ._test_category() . . . pass
|
2860
|
+
running ._test_new() . . . pass
|
2861
|
+
running ._test_not_implemented_methods() . . . pass
|
2862
|
+
running ._test_pickling() . . . pass
|
2863
|
+
running ._test_recover() . . . pass
|
2864
|
+
sage: TestSuite(KnotInfo.K6_1.series()).run(max_samples=infinity) # indirect doctest
|
2865
|
+
"""
|
2866
|
+
tester = options['tester']
|
2867
|
+
max_samples = tester._max_samples
|
2868
|
+
try:
|
2869
|
+
if max_samples:
|
2870
|
+
tester.assertTrue(self.is_recoverable(unique=False, max_samples=max_samples))
|
2871
|
+
else:
|
2872
|
+
tester.assertTrue(self.is_recoverable(unique=False))
|
2873
|
+
except ImportError:
|
2874
|
+
pass
|
2875
|
+
|
2876
|
+
def inject(self, verbose=True):
|
2877
|
+
r"""
|
2878
|
+
Inject ``self`` with its name into the namespace of the
|
2879
|
+
Python code from which this function is called.
|
2880
|
+
|
2881
|
+
INPUT:
|
2882
|
+
|
2883
|
+
- ``verbose`` -- boolean (default: ``True``); to suppress
|
2884
|
+
the message printed on the invocation
|
2885
|
+
|
2886
|
+
EXAMPLES::
|
2887
|
+
|
2888
|
+
sage: KnotInfoSeries(6, True, True).inject()
|
2889
|
+
Defining K6
|
2890
|
+
sage: K6(2)
|
2891
|
+
<KnotInfo.K6_2: '6_2'>
|
2892
|
+
"""
|
2893
|
+
name = self._name()
|
2894
|
+
if verbose:
|
2895
|
+
print("Defining %s" % (name))
|
2896
|
+
from sage.repl.user_globals import set_global
|
2897
|
+
set_global(name, self)
|
2898
|
+
|
2899
|
+
|
2900
|
+
KnotInfo = KnotInfoBase('KnotInfo', db.row_names())
|