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/link.py
ADDED
@@ -0,0 +1,4715 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# sage.doctest: needs sage.graphs sage.groups
|
3
|
+
r"""
|
4
|
+
Links
|
5
|
+
|
6
|
+
A knot is defined as embedding of the circle `\mathbb{S}^1` in the
|
7
|
+
3-dimensional sphere `\mathbb{S}^3`, considered up to ambient isotopy.
|
8
|
+
They represent the physical idea of a knotted rope, but with the
|
9
|
+
particularity that the rope is closed. That is, the ends of the
|
10
|
+
rope are joined.
|
11
|
+
|
12
|
+
A link is an embedding of one or more copies of `\mathbb{S}^1` in
|
13
|
+
`\mathbb{S}^3`, considered up to ambient isotopy. That is, a link
|
14
|
+
represents the idea of one or more tied ropes. Every knot is a link,
|
15
|
+
but not every link is a knot.
|
16
|
+
|
17
|
+
Generically, the projection of a link on `\RR^2` is a curve with
|
18
|
+
crossings. The crossings are represented to show which strand goes
|
19
|
+
over the other. This curve is called a planar diagram of the link.
|
20
|
+
If we remove the crossings, the resulting connected components are
|
21
|
+
segments. These segments are called the edges of the diagram.
|
22
|
+
|
23
|
+
REFERENCES:
|
24
|
+
|
25
|
+
- :wikipedia:`Knot_(mathematics)`
|
26
|
+
- [Col2013]_
|
27
|
+
- [KnotAtlas]_
|
28
|
+
|
29
|
+
.. SEEALSO::
|
30
|
+
|
31
|
+
There are also tables of link and knot invariants at web-pages
|
32
|
+
`KnotInfo <https://knotinfo.math.indiana.edu/>`__ and
|
33
|
+
`LinkInfo <https://linkinfo.sitehost.iu.edu>`__. These can be
|
34
|
+
used inside Sage after installing the optional package
|
35
|
+
``database_knotinfo`` (type ``sage -i database_knotinfo`` in a command shell,
|
36
|
+
see :mod:`~sage.knots.knotinfo`).
|
37
|
+
|
38
|
+
AUTHORS:
|
39
|
+
|
40
|
+
- Miguel Angel Marco Buzunariz
|
41
|
+
- Amit Jamadagni
|
42
|
+
- Sebastian Oehms (October 2020, add :meth:`get_knotinfo` and :meth:`is_isotopic`)
|
43
|
+
- Sebastian Oehms (May 2022): add :meth:`links_gould_polynomial`
|
44
|
+
- Sebastian Oehms (May 2023): change the convention about the ``pd_code`` from
|
45
|
+
clockwise to anti-clockwise (see :issue:`35665`).
|
46
|
+
"""
|
47
|
+
|
48
|
+
# ****************************************************************************
|
49
|
+
# Copyright (C) 2014 Miguel Angel Marco Buzunariz
|
50
|
+
# Amit Jamadagni
|
51
|
+
#
|
52
|
+
# This program is free software: you can redistribute it and/or modify
|
53
|
+
# it under the terms of the GNU General Public License as published by
|
54
|
+
# the Free Software Foundation, either version 2 of the License, or
|
55
|
+
# (at your option) any later version.
|
56
|
+
#
|
57
|
+
# https://www.gnu.org/licenses/
|
58
|
+
# ****************************************************************************
|
59
|
+
|
60
|
+
from copy import deepcopy, copy
|
61
|
+
from itertools import combinations
|
62
|
+
|
63
|
+
from sage.rings.integer_ring import ZZ
|
64
|
+
from sage.graphs.digraph import DiGraph
|
65
|
+
from sage.graphs.graph import Graph
|
66
|
+
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
|
67
|
+
from sage.misc.lazy_import import lazy_import
|
68
|
+
from sage.rings.integer import Integer
|
69
|
+
from sage.misc.flatten import flatten
|
70
|
+
from sage.misc.cachefunc import cached_method
|
71
|
+
from sage.structure.sage_object import SageObject
|
72
|
+
|
73
|
+
lazy_import("sage.functions.generalized", "sign")
|
74
|
+
lazy_import('sage.groups.braid', ['Braid', 'BraidGroup'])
|
75
|
+
lazy_import('sage.homology.chain_complex', 'ChainComplex')
|
76
|
+
lazy_import('sage.matrix.constructor', 'matrix')
|
77
|
+
lazy_import('sage.numerical.mip', 'MixedIntegerLinearProgram')
|
78
|
+
lazy_import("sage.symbolic.ring", "SR")
|
79
|
+
|
80
|
+
|
81
|
+
class Link(SageObject):
|
82
|
+
r"""
|
83
|
+
A link.
|
84
|
+
|
85
|
+
A link is an embedding of one or more copies of `\mathbb{S}^1` in
|
86
|
+
`\mathbb{S}^3`, considered up to ambient isotopy. That is, a link
|
87
|
+
represents the idea of one or more tied ropes. Every knot is a link,
|
88
|
+
but not every link is a knot.
|
89
|
+
|
90
|
+
A link can be created by using one of the conventions mentioned below:
|
91
|
+
|
92
|
+
Braid:
|
93
|
+
|
94
|
+
- The closure of a braid is a link::
|
95
|
+
|
96
|
+
sage: B = BraidGroup(8)
|
97
|
+
sage: L = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 3])); L
|
98
|
+
Link with 1 component represented by 9 crossings
|
99
|
+
sage: L = Link(B([1, 2, 1, -2, -1])); L
|
100
|
+
Link with 2 components represented by 5 crossings
|
101
|
+
|
102
|
+
.. NOTE::
|
103
|
+
|
104
|
+
The strands of the braid that have no crossings at all
|
105
|
+
are removed.
|
106
|
+
|
107
|
+
- Oriented Gauss Code:
|
108
|
+
|
109
|
+
Label the crossings from `1` to `n` (where `n` is the number of
|
110
|
+
crossings) and start moving along the link. Trace every component of
|
111
|
+
the link, by starting at a particular point on one component of the
|
112
|
+
link and writing down each of the crossings that you encounter until
|
113
|
+
returning to the starting point. The crossings are written with sign
|
114
|
+
depending on whether we cross them as over or undercrossing. Each
|
115
|
+
component is then represented as a list whose elements are the
|
116
|
+
crossing numbers. A second list of `+1` and `-1`'s keeps track of
|
117
|
+
the orientation of each crossing::
|
118
|
+
|
119
|
+
sage: L = Link([[[-1, 2, 3, -4, 5, -6, 7, 8, -2, -5, 6, 1, -8, -3, 4, -7]],
|
120
|
+
....: [-1, -1, -1, -1, 1, 1, -1, 1]])
|
121
|
+
sage: L
|
122
|
+
Link with 1 component represented by 8 crossings
|
123
|
+
|
124
|
+
For links there may be more than one component and the input is
|
125
|
+
as follows::
|
126
|
+
|
127
|
+
sage: L = Link([[[-1, 2], [-3, 4], [1, 3, -4, -2]], [-1, -1, 1, 1]])
|
128
|
+
sage: L
|
129
|
+
Link with 3 components represented by 4 crossings
|
130
|
+
|
131
|
+
- Planar Diagram (PD) Code:
|
132
|
+
|
133
|
+
The diagram of the link is formed by segments that are adjacent to
|
134
|
+
the crossings. Label each one of this segments with a positive number,
|
135
|
+
and for each crossing, write down the four incident segments. The
|
136
|
+
order of these segments is anti-clockwise, starting with the incoming
|
137
|
+
undercrossing.
|
138
|
+
|
139
|
+
There is no particular distinction between knots and links for
|
140
|
+
this input.
|
141
|
+
|
142
|
+
EXAMPLES:
|
143
|
+
|
144
|
+
One of the representations of the trefoil knot::
|
145
|
+
|
146
|
+
sage: L = Link([[1, 5, 2, 4], [5, 3, 6, 2], [3, 1, 4, 6]])
|
147
|
+
sage: L
|
148
|
+
Link with 1 component represented by 3 crossings
|
149
|
+
|
150
|
+
.. PLOT::
|
151
|
+
:width: 300 px
|
152
|
+
|
153
|
+
L = Link([[1, 5, 2, 4], [5, 3, 6, 2], [3, 1, 4, 6]])
|
154
|
+
sphinx_plot(L.plot())
|
155
|
+
|
156
|
+
One of the representations of the Hopf link::
|
157
|
+
|
158
|
+
sage: L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
159
|
+
sage: L
|
160
|
+
Link with 2 components represented by 2 crossings
|
161
|
+
|
162
|
+
.. PLOT::
|
163
|
+
:width: 300 px
|
164
|
+
|
165
|
+
L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
166
|
+
sphinx_plot(L.plot())
|
167
|
+
|
168
|
+
We can construct links from the braid group::
|
169
|
+
|
170
|
+
sage: B = BraidGroup(4)
|
171
|
+
sage: L = Link(B([-1, -1, -1, -2, 1, -2, 3, -2])); L
|
172
|
+
Link with 2 components represented by 8 crossings
|
173
|
+
|
174
|
+
.. PLOT::
|
175
|
+
:width: 300 px
|
176
|
+
|
177
|
+
B = BraidGroup(4)
|
178
|
+
L = Link(B([-1, -1, -1, -2, 1, -2, 3, -2]))
|
179
|
+
sphinx_plot(L.plot())
|
180
|
+
|
181
|
+
::
|
182
|
+
|
183
|
+
sage: L = Link(B([1, 2, 1, 3])); L
|
184
|
+
Link with 2 components represented by 4 crossings
|
185
|
+
|
186
|
+
.. PLOT::
|
187
|
+
:width: 300 px
|
188
|
+
|
189
|
+
B = BraidGroup(4)
|
190
|
+
L = Link(B([1, 2, 1, 3]))
|
191
|
+
sphinx_plot(L.plot())
|
192
|
+
|
193
|
+
We construct the "monster" unknot using a planar code, and
|
194
|
+
then construct the oriented Gauss code and braid representation::
|
195
|
+
|
196
|
+
sage: L = Link([[3,4,2,1], [8,7,1,9], [5,3,7,6], [4,5,6,18],
|
197
|
+
....: [17,18,8,19], [9,14,11,10], [10,11,13,12],
|
198
|
+
....: [12,13,15,19], [20,15,14,16], [16,2,17,20]])
|
199
|
+
sage: L.oriented_gauss_code()
|
200
|
+
[[[1, -4, 3, -1, 10, -9, 6, -7, 8, 5, 4, -3, 2, -6, 7, -8, 9, -10, -5, -2]],
|
201
|
+
[1, -1, 1, 1, 1, -1, -1, -1, -1, -1]]
|
202
|
+
sage: L.braid()
|
203
|
+
s0*s1^-3*s2^-1*s1*s3*s2^2*s1^-1*s0^-1*s2*s1^-1*s3^-1*s2*s1^-1
|
204
|
+
|
205
|
+
.. PLOT::
|
206
|
+
:width: 300 px
|
207
|
+
|
208
|
+
L = Link([[3,4,2,1], [8,7,1,9], [5,3,7,6], [4,5,6,18],
|
209
|
+
[17,18,8,19], [9,14,11,10], [10,11,13,12],
|
210
|
+
[12,13,15,19], [20,15,14,16], [16,2,17,20]])
|
211
|
+
sphinx_plot(L.plot())
|
212
|
+
|
213
|
+
We construct the Ochiai unknot by using an oriented Gauss code::
|
214
|
+
|
215
|
+
sage: L = Link([[[1,-2,-3,-8,-12,13,-14,15,-7,-1,2,-4,10,11,-13,12,
|
216
|
+
....: -11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]],
|
217
|
+
....: [-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]])
|
218
|
+
sage: L.pd_code()
|
219
|
+
[[10, 1, 11, 2], [2, 11, 3, 12], [3, 21, 4, 20], [12, 20, 13, 19],
|
220
|
+
[21, 1, 22, 32], [31, 23, 32, 22], [9, 24, 10, 25], [4, 30, 5, 29],
|
221
|
+
[23, 31, 24, 30], [28, 13, 29, 14], [17, 15, 18, 14], [5, 16, 6, 17],
|
222
|
+
[15, 6, 16, 7], [7, 26, 8, 27], [25, 8, 26, 9], [18, 27, 19, 28]]
|
223
|
+
|
224
|
+
.. PLOT::
|
225
|
+
:width: 300 px
|
226
|
+
|
227
|
+
L = Link([[[1,-2,-3,-8,-12,13,-14,15,-7,-1,2,-4,10,11,-13,12,
|
228
|
+
-11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]],
|
229
|
+
[-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]])
|
230
|
+
sphinx_plot(L.plot())
|
231
|
+
|
232
|
+
We construct the knot `7_1` and compute some invariants::
|
233
|
+
|
234
|
+
sage: B = BraidGroup(2)
|
235
|
+
sage: L = Link(B([1]*7))
|
236
|
+
|
237
|
+
.. PLOT::
|
238
|
+
:width: 300 px
|
239
|
+
|
240
|
+
B = BraidGroup(2)
|
241
|
+
L = Link(B([1]*7))
|
242
|
+
sphinx_plot(L.plot())
|
243
|
+
|
244
|
+
::
|
245
|
+
|
246
|
+
sage: L.alexander_polynomial()
|
247
|
+
t^-3 - t^-2 + t^-1 - 1 + t - t^2 + t^3
|
248
|
+
sage: L.jones_polynomial() # needs sage.symbolic
|
249
|
+
-t^10 + t^9 - t^8 + t^7 - t^6 + t^5 + t^3
|
250
|
+
sage: L.determinant()
|
251
|
+
7
|
252
|
+
sage: L.signature()
|
253
|
+
-6
|
254
|
+
|
255
|
+
The links here have removed components in which no strand is used::
|
256
|
+
|
257
|
+
sage: B = BraidGroup(8)
|
258
|
+
sage: b = B([1])
|
259
|
+
sage: L = Link(b)
|
260
|
+
sage: b.components_in_closure()
|
261
|
+
7
|
262
|
+
sage: L.number_of_components()
|
263
|
+
1
|
264
|
+
sage: L.braid().components_in_closure()
|
265
|
+
1
|
266
|
+
sage: L.braid().parent()
|
267
|
+
Braid group on 2 strands
|
268
|
+
|
269
|
+
.. WARNING::
|
270
|
+
|
271
|
+
Equality of knots is done by comparing the corresponding braids,
|
272
|
+
which may give false negatives.
|
273
|
+
|
274
|
+
.. NOTE::
|
275
|
+
|
276
|
+
The behavior of removing unused strands from an element of a
|
277
|
+
braid group may change without notice in the future. Do not
|
278
|
+
rely on this feature.
|
279
|
+
|
280
|
+
.. TODO::
|
281
|
+
|
282
|
+
Implement methods to creating new links from previously created links.
|
283
|
+
"""
|
284
|
+
|
285
|
+
def __init__(self, data):
|
286
|
+
r"""
|
287
|
+
Initialize ``self``.
|
288
|
+
|
289
|
+
TESTS::
|
290
|
+
|
291
|
+
sage: B = BraidGroup(8)
|
292
|
+
sage: L = Link(B([-1, -1, -1, -2,1, -2, 3, -2]))
|
293
|
+
sage: TestSuite(L).run()
|
294
|
+
sage: L = Link(B([1, 2, 1]))
|
295
|
+
sage: TestSuite(L).run()
|
296
|
+
sage: L = Link(B.one())
|
297
|
+
|
298
|
+
sage: L = Link([[1, 1, 2, 2]])
|
299
|
+
sage: TestSuite(L).run()
|
300
|
+
sage: L = Link([])
|
301
|
+
sage: L = Link([[], []])
|
302
|
+
|
303
|
+
sage: Link([[[-1, 2, -1, 2]], [1, 1, 1, 1]])
|
304
|
+
Traceback (most recent call last):
|
305
|
+
...
|
306
|
+
ValueError: invalid input: data is not a valid oriented Gauss code
|
307
|
+
|
308
|
+
sage: Link([[[-1, 2, 3, 4]]])
|
309
|
+
Traceback (most recent call last):
|
310
|
+
...
|
311
|
+
ValueError: invalid PD code: crossings must be represented by four segments
|
312
|
+
|
313
|
+
sage: L = Link([[1, 5, 2, 4], [5, 3, 6, 2], [3, 1, 4, 3]])
|
314
|
+
Traceback (most recent call last):
|
315
|
+
...
|
316
|
+
ValueError: invalid PD code: each segment must appear twice
|
317
|
+
|
318
|
+
Segments in PD code must be labelled by positive integers::
|
319
|
+
|
320
|
+
sage: code = [(2, 5, 3, 0), (4, 1, 5, 2), (0, 3, 1, 4)]
|
321
|
+
sage: Knot(code)
|
322
|
+
Traceback (most recent call last):
|
323
|
+
...
|
324
|
+
ValueError: invalid PD code: segment label 0 not allowed
|
325
|
+
|
326
|
+
sage: L = Link(5)
|
327
|
+
Traceback (most recent call last):
|
328
|
+
...
|
329
|
+
ValueError: invalid input: data must be either a list or a braid
|
330
|
+
|
331
|
+
Verify that :issue:`29692` is fixed::
|
332
|
+
|
333
|
+
sage: B = BraidGroup(5)
|
334
|
+
sage: L = Link(B([3,4,3,-4])); L
|
335
|
+
Link with 1 component represented by 4 crossings
|
336
|
+
sage: L.braid()
|
337
|
+
s0*s1*s0*s1^-1
|
338
|
+
|
339
|
+
PD code can be a list of 4-tuples::
|
340
|
+
|
341
|
+
sage: code = [(2, 5, 3, 6), (4, 1, 5, 2), (6, 3, 1, 4)]
|
342
|
+
sage: K = Knot(code); K.alexander_polynomial()
|
343
|
+
t^-1 - 1 + t
|
344
|
+
"""
|
345
|
+
if isinstance(data, list):
|
346
|
+
# either oriented Gauss or PD code
|
347
|
+
if len(data) != 2 or not all(isinstance(i, list) for i in data[0]):
|
348
|
+
# PD code
|
349
|
+
if any(len(i) != 4 for i in data):
|
350
|
+
raise ValueError("invalid PD code: crossings must be represented by four segments")
|
351
|
+
flat = flatten(data)
|
352
|
+
if 0 in flat:
|
353
|
+
raise ValueError("invalid PD code: segment label 0 not allowed")
|
354
|
+
if any(flat.count(i) != 2 for i in set(flat)):
|
355
|
+
raise ValueError("invalid PD code: each segment must appear twice")
|
356
|
+
self._pd_code = [list(vertex) for vertex in data]
|
357
|
+
self._oriented_gauss_code = None
|
358
|
+
self._braid = None
|
359
|
+
else:
|
360
|
+
# oriented Gauss code
|
361
|
+
flat = flatten(data[0])
|
362
|
+
if flat:
|
363
|
+
a, b = max(flat), min(flat)
|
364
|
+
if 2 * len(data[1]) != len(flat) or set(range(b, a + 1)) - set([0]) != set(flat):
|
365
|
+
raise ValueError("invalid input: data is not a valid oriented Gauss code")
|
366
|
+
self._oriented_gauss_code = data
|
367
|
+
self._pd_code = None
|
368
|
+
self._braid = None
|
369
|
+
|
370
|
+
else:
|
371
|
+
if isinstance(data, Braid):
|
372
|
+
# Remove all unused strands
|
373
|
+
support = sorted(set().union(*((abs(x), abs(x) + 1) for x in data.Tietze())))
|
374
|
+
d = {}
|
375
|
+
for i, s in enumerate(support):
|
376
|
+
d[s] = i + 1
|
377
|
+
d[-s] = -i - 1
|
378
|
+
if not support:
|
379
|
+
B = BraidGroup(2)
|
380
|
+
else:
|
381
|
+
B = BraidGroup(len(support))
|
382
|
+
self._braid = B([d[x] for x in data.Tietze()])
|
383
|
+
self._oriented_gauss_code = None
|
384
|
+
self._pd_code = None
|
385
|
+
|
386
|
+
else:
|
387
|
+
raise ValueError("invalid input: data must be either a list or a braid")
|
388
|
+
|
389
|
+
self._mirror = None # set on invocation of :meth:`mirror_image`
|
390
|
+
self._reverse = None # set on invocation of :meth:`reverse`
|
391
|
+
|
392
|
+
def arcs(self, presentation='pd'):
|
393
|
+
r"""
|
394
|
+
Return the arcs of ``self``.
|
395
|
+
|
396
|
+
Arcs are the connected components of the planar diagram.
|
397
|
+
|
398
|
+
INPUT:
|
399
|
+
|
400
|
+
- ``presentation`` -- one of the following:
|
401
|
+
|
402
|
+
* ``'pd'`` -- the arcs are returned as lists of parts in the PD code
|
403
|
+
* ``'gauss_code'`` -- the arcs are returned as pieces of the Gauss
|
404
|
+
code that start with a negative number, and end with the
|
405
|
+
following negative one; of there exist a closed arc,
|
406
|
+
it is returned as a list of positive numbers only
|
407
|
+
|
408
|
+
OUTPUT: list of lists representing the arcs based upon ``presentation``
|
409
|
+
|
410
|
+
EXAMPLES::
|
411
|
+
|
412
|
+
sage: K = Knot([[[1,-2,3,-1,2,-3]],[1,1,1]])
|
413
|
+
sage: K.arcs()
|
414
|
+
[[1, 2], [3, 4], [5, 6]]
|
415
|
+
sage: K.arcs(presentation='gauss_code')
|
416
|
+
[[-3, 1, -2], [-2, 3, -1], [-1, 2, -3]]
|
417
|
+
|
418
|
+
::
|
419
|
+
|
420
|
+
sage: L = Link([[1, 2, 3, 4], [3, 2, 1, 4]])
|
421
|
+
sage: L.arcs()
|
422
|
+
[[2, 4], [1], [3]]
|
423
|
+
sage: L.arcs(presentation='gauss_code')
|
424
|
+
[[-2, -1], [-1, -2], [2, 1]]
|
425
|
+
sage: L.gauss_code()
|
426
|
+
[[-1, -2], [2, 1]]
|
427
|
+
"""
|
428
|
+
if presentation == 'pd':
|
429
|
+
pd_code = self.pd_code()
|
430
|
+
G = DiGraph()
|
431
|
+
for e in set(flatten(pd_code)):
|
432
|
+
G.add_vertex(e)
|
433
|
+
for cr in zip(pd_code, self.orientation()):
|
434
|
+
if cr[1] == 1:
|
435
|
+
G.add_edge(cr[0][3], cr[0][1])
|
436
|
+
else:
|
437
|
+
G.add_edge(cr[0][1], cr[0][3])
|
438
|
+
res = []
|
439
|
+
for S in G.connected_components_subgraphs():
|
440
|
+
check = S.is_directed_acyclic(certificate=True)
|
441
|
+
if check[0]:
|
442
|
+
source = S.sources()[0]
|
443
|
+
sink = S.sinks()[0]
|
444
|
+
res.append(S.shortest_path(source, sink))
|
445
|
+
else:
|
446
|
+
res.append(check[1])
|
447
|
+
return res
|
448
|
+
elif presentation == 'gauss_code':
|
449
|
+
res = []
|
450
|
+
for comp in self.gauss_code():
|
451
|
+
if not any(i < 0 for i in comp):
|
452
|
+
res.append(comp)
|
453
|
+
else:
|
454
|
+
rescom = []
|
455
|
+
par = []
|
456
|
+
for i in comp:
|
457
|
+
par.append(i)
|
458
|
+
if i < 0:
|
459
|
+
rescom.append(copy(par))
|
460
|
+
par = [i]
|
461
|
+
rescom[0] = par + rescom[0]
|
462
|
+
res = res + rescom
|
463
|
+
return res
|
464
|
+
|
465
|
+
def fundamental_group(self, presentation='wirtinger'):
|
466
|
+
r"""
|
467
|
+
Return the fundamental group of the complement of ``self``.
|
468
|
+
|
469
|
+
INPUT:
|
470
|
+
|
471
|
+
- ``presentation`` -- string; one of the following:
|
472
|
+
|
473
|
+
* ``'wirtinger'`` -- (default) the Wirtinger presentation
|
474
|
+
(see :wikipedia:`Link_group`)
|
475
|
+
* ``'braid'`` -- the presentation is given by the braid action
|
476
|
+
on the free group (see chapter 2 of [Bir1975]_)
|
477
|
+
|
478
|
+
OUTPUT: a finitely presented group
|
479
|
+
|
480
|
+
EXAMPLES::
|
481
|
+
|
482
|
+
sage: L = Link([[1, 4, 3, 2], [3, 4, 1, 2]])
|
483
|
+
sage: L.fundamental_group()
|
484
|
+
Finitely presented group < x0, x1, x2 | x1*x0^-1*x2^-1*x0, x2*x0*x1^-1*x0^-1 >
|
485
|
+
sage: L.fundamental_group('braid')
|
486
|
+
Finitely presented group < x0, x1 | 1, 1 >
|
487
|
+
|
488
|
+
We can see, for instance, that the two presentations of the group
|
489
|
+
of the figure eight knot correspond to isomorphic groups::
|
490
|
+
|
491
|
+
sage: K8 = Knot([[[1, -2, 4, -3, 2, -1, 3, -4]], [1, 1, -1, -1]])
|
492
|
+
sage: GA = K8.fundamental_group(); GA
|
493
|
+
Finitely presented group < x0, x1, x2, x3 |
|
494
|
+
x2*x0*x3^-1*x0^-1, x0*x2*x1^-1*x2^-1,
|
495
|
+
x1*x3^-1*x2^-1*x3, x3*x1^-1*x0^-1*x1 >
|
496
|
+
sage: GB = K8.fundamental_group(presentation='braid'); GB
|
497
|
+
Finitely presented group
|
498
|
+
< x0, x1, x2 | x1*x2^-1*x1^-1*x0*x1*x2*x1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0^-1,
|
499
|
+
x1*x2^-1*x1^-1*x0*x1*x2*x1^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2*x1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-2,
|
500
|
+
x1*x2^-1*x1^-1*x0*x1*x2*x1^-1*x2^-1 >
|
501
|
+
sage: GA.simplified()
|
502
|
+
Finitely presented group
|
503
|
+
< x0, x1 | x1^-1*x0*x1*x0^-1*x1*x0*x1^-1*x0^-1*x1*x0^-1 >
|
504
|
+
sage: GB.simplified()
|
505
|
+
Finitely presented group
|
506
|
+
< x0, x2 | x2^-1*x0*x2^-1*x0^-1*x2*x0*x2^-1*x0*x2*x0^-1 >
|
507
|
+
"""
|
508
|
+
from sage.groups.free_group import FreeGroup
|
509
|
+
if presentation == 'braid':
|
510
|
+
b = self.braid()
|
511
|
+
F = FreeGroup(b.strands())
|
512
|
+
rels = [x * b / x for x in F.gens()]
|
513
|
+
return F.quotient(rels)
|
514
|
+
elif presentation == 'wirtinger':
|
515
|
+
arcs = self.arcs(presentation='pd')
|
516
|
+
F = FreeGroup(len(arcs))
|
517
|
+
rels = []
|
518
|
+
for crossing, orientation in zip(self.pd_code(), self.orientation()):
|
519
|
+
a = next(idx for idx, i in enumerate(arcs) if crossing[0] in i)
|
520
|
+
b = next(idx for idx, i in enumerate(arcs) if crossing[3] in i)
|
521
|
+
c = next(idx for idx, i in enumerate(arcs) if crossing[2] in i)
|
522
|
+
ela = F.gen(a)
|
523
|
+
elb = F.gen(b)
|
524
|
+
if orientation < 0:
|
525
|
+
elb = elb.inverse()
|
526
|
+
elc = F.gen(c)
|
527
|
+
rels.append(ela * elb / elc / elb)
|
528
|
+
return F.quotient(rels)
|
529
|
+
|
530
|
+
def _repr_(self) -> str:
|
531
|
+
r"""
|
532
|
+
Return a string representation.
|
533
|
+
|
534
|
+
OUTPUT: string representation
|
535
|
+
|
536
|
+
EXAMPLES::
|
537
|
+
|
538
|
+
sage: B = BraidGroup(8)
|
539
|
+
sage: L = Link(B([1, 2, 1, 2])); L
|
540
|
+
Link with 1 component represented by 4 crossings
|
541
|
+
|
542
|
+
sage: L = Link([[[-1, 2], [-3, 4], [1, 3, -4, -2]], [-1, -1, 1, 1]])
|
543
|
+
sage: L
|
544
|
+
Link with 3 components represented by 4 crossings
|
545
|
+
"""
|
546
|
+
number_of_components = self.number_of_components()
|
547
|
+
if number_of_components > 1:
|
548
|
+
plural = 's'
|
549
|
+
else:
|
550
|
+
plural = ''
|
551
|
+
pd_len = len(self.pd_code())
|
552
|
+
return 'Link with {} component{} represented by {} crossings'.format(number_of_components, plural, pd_len)
|
553
|
+
|
554
|
+
def __eq__(self, other):
|
555
|
+
r"""
|
556
|
+
Check equality.
|
557
|
+
|
558
|
+
TESTS::
|
559
|
+
|
560
|
+
sage: B = BraidGroup(8)
|
561
|
+
sage: L1 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 5, 4]))
|
562
|
+
sage: L2 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 5, 4]))
|
563
|
+
sage: L1 == L2
|
564
|
+
True
|
565
|
+
sage: L3 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2]))
|
566
|
+
sage: L1 == L3
|
567
|
+
False
|
568
|
+
"""
|
569
|
+
if not isinstance(other, self.__class__):
|
570
|
+
return False
|
571
|
+
if self._pd_code is not None:
|
572
|
+
if self.pd_code() == other.pd_code():
|
573
|
+
return True
|
574
|
+
if self._oriented_gauss_code is not None:
|
575
|
+
if self.oriented_gauss_code() == other.oriented_gauss_code():
|
576
|
+
return True
|
577
|
+
return self.braid() == other.braid()
|
578
|
+
|
579
|
+
def __hash__(self):
|
580
|
+
r"""
|
581
|
+
Return the hash of ``self``.
|
582
|
+
|
583
|
+
EXAMPLES::
|
584
|
+
|
585
|
+
sage: B = BraidGroup(8)
|
586
|
+
sage: L1 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 5, 4]))
|
587
|
+
sage: H = hash(L1) # needs sage.libs.braiding
|
588
|
+
"""
|
589
|
+
return hash(self.braid())
|
590
|
+
|
591
|
+
def __ne__(self, other):
|
592
|
+
r"""
|
593
|
+
Check inequality.
|
594
|
+
|
595
|
+
TESTS::
|
596
|
+
|
597
|
+
sage: B = BraidGroup(8)
|
598
|
+
sage: L1 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 5, 4]))
|
599
|
+
sage: L2 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2, 5, 4]))
|
600
|
+
sage: L1 != L2
|
601
|
+
False
|
602
|
+
sage: L3 = Link(B([-1, -1, -1, -2, 1, -2, 3, -2]))
|
603
|
+
sage: L1 != L3
|
604
|
+
True
|
605
|
+
"""
|
606
|
+
return not self.__eq__(other)
|
607
|
+
|
608
|
+
def braid(self, remove_loops=False):
|
609
|
+
r"""
|
610
|
+
Return a braid representation of ``self``.
|
611
|
+
|
612
|
+
INPUT:
|
613
|
+
|
614
|
+
- ``remove_loops`` -- boolean (default: ``False``); if set to ``True``
|
615
|
+
loops will be removed first. This can reduce the number of strands
|
616
|
+
needed for an ambient isotopic braid closure. However, this can lead
|
617
|
+
to a loss of the regular isotopy.
|
618
|
+
|
619
|
+
OUTPUT: an element in the braid group
|
620
|
+
|
621
|
+
.. WARNING::
|
622
|
+
|
623
|
+
For the unknot with no crossings, this returns the identity
|
624
|
+
of the braid group with 2 strands because this disregards
|
625
|
+
strands with no crossings.
|
626
|
+
|
627
|
+
EXAMPLES::
|
628
|
+
|
629
|
+
sage: L = Link([[2, 4, 1, 3], [4, 2, 3, 1]])
|
630
|
+
sage: L.braid()
|
631
|
+
s^2
|
632
|
+
sage: L = Link([[[-1, 2, -3, 1, -2, 3]], [-1, -1, -1]])
|
633
|
+
sage: L.braid()
|
634
|
+
s^-3
|
635
|
+
sage: L = Link([[1,7,2,8], [8,5,9,4], [3,10,4,9], [10,6,7,1], [5,2,6,3]])
|
636
|
+
sage: L.braid()
|
637
|
+
(s0*s1^-1)^2*s1^-1
|
638
|
+
|
639
|
+
using ``remove_loops=True``::
|
640
|
+
|
641
|
+
sage: L = Link([[2, 7, 1, 1], [7, 3, 9, 2], [4, 11, 3, 9], [11, 5, 5, 4]])
|
642
|
+
sage: L.braid()
|
643
|
+
s0*s1^-1*s2*s3^-1
|
644
|
+
sage: L.braid(remove_loops=True)
|
645
|
+
1
|
646
|
+
|
647
|
+
TESTS::
|
648
|
+
|
649
|
+
sage: L = Link([])
|
650
|
+
sage: L.braid()
|
651
|
+
1
|
652
|
+
sage: L = Link([[], []])
|
653
|
+
sage: L.braid()
|
654
|
+
1
|
655
|
+
|
656
|
+
Check that :issue:`25050` is solved::
|
657
|
+
|
658
|
+
sage: A = Link([[[1, 2, -2, -1, -3, -4, 4, 3]], [1, 1, 1, 1]])
|
659
|
+
sage: A.braid()
|
660
|
+
s0*s1*s2*s3
|
661
|
+
|
662
|
+
Check that :issue:`36884` is solved::
|
663
|
+
|
664
|
+
sage: L = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]])
|
665
|
+
sage: L.braid()
|
666
|
+
s0^3*s1*s0*s1^-1
|
667
|
+
sage: L.braid(remove_loops=True)
|
668
|
+
s^3
|
669
|
+
"""
|
670
|
+
if remove_loops:
|
671
|
+
L = self.remove_loops()
|
672
|
+
if L != self:
|
673
|
+
return L.braid(remove_loops=remove_loops)
|
674
|
+
|
675
|
+
if self._braid is not None:
|
676
|
+
return self._braid
|
677
|
+
|
678
|
+
from sage.groups.braid import BraidGroup
|
679
|
+
comp = self._isolated_components()
|
680
|
+
if len(comp) > 1:
|
681
|
+
L1 = Link(comp[0])
|
682
|
+
L2 = Link(flatten(comp[1:], max_level=1))
|
683
|
+
b1 = L1.braid(remove_loops=remove_loops)
|
684
|
+
b2 = L2.braid(remove_loops=remove_loops)
|
685
|
+
n1 = b1.parent().strands()
|
686
|
+
n2 = b2.parent().strands()
|
687
|
+
t1 = list(b1.Tietze())
|
688
|
+
t2 = [sign(x) * (abs(x) + n1) for x in b2.Tietze()]
|
689
|
+
B = BraidGroup(n1 + n2)
|
690
|
+
self._braid = B(t1 + t2)
|
691
|
+
return self._braid
|
692
|
+
|
693
|
+
pd_code = self.pd_code()
|
694
|
+
if not pd_code:
|
695
|
+
B = BraidGroup(2)
|
696
|
+
self._braid = B.one()
|
697
|
+
return self._braid
|
698
|
+
|
699
|
+
# look for possible Vogel moves, perform them and call recursively to the modified link
|
700
|
+
def idx(cross, edge):
|
701
|
+
r"""
|
702
|
+
Return the index of an edge in a crossing taking loops into account.
|
703
|
+
A loop appears as an edge which occurs twice in the crossing.
|
704
|
+
In all cases the second occurrence is the correct one needed in
|
705
|
+
the Vogel algorithm (see :issue:`36884`).
|
706
|
+
"""
|
707
|
+
i = cross.index(edge)
|
708
|
+
if cross.count(edge) > 1:
|
709
|
+
return cross.index(edge, i + 1)
|
710
|
+
return i
|
711
|
+
|
712
|
+
seifert_circles = self.seifert_circles()
|
713
|
+
newedge = max(flatten(pd_code)) + 1
|
714
|
+
for region in self.regions():
|
715
|
+
n = len(region)
|
716
|
+
for i in range(n - 1):
|
717
|
+
a = region[i]
|
718
|
+
seifcirca = [x for x in seifert_circles if abs(a) in x]
|
719
|
+
for j in range(i + 1, n):
|
720
|
+
b = region[j]
|
721
|
+
seifcircb = [x for x in seifert_circles if abs(b) in x]
|
722
|
+
if seifcirca != seifcircb and sign(a) == sign(b):
|
723
|
+
tails, heads = self._directions_of_edges()
|
724
|
+
|
725
|
+
newPD = [list(vertex) for vertex in pd_code]
|
726
|
+
if sign(a) == 1:
|
727
|
+
# -------------------------------------------------
|
728
|
+
# Visualize insertion of the two new crossings D, E
|
729
|
+
# \ /
|
730
|
+
# a\ /b existing edges, a down, b up
|
731
|
+
# D
|
732
|
+
# n3/ \n0 newedge + 3, newedge
|
733
|
+
# \ /
|
734
|
+
# E
|
735
|
+
# n1/ \n2 newedge + 1, newedge + 2
|
736
|
+
# / \
|
737
|
+
# C1 C2 existing crossings
|
738
|
+
# -------------------------------------------------
|
739
|
+
C1 = newPD[newPD.index(heads[a])]
|
740
|
+
C1[idx(C1, a)] = newedge + 1
|
741
|
+
C2 = newPD[newPD.index(tails[b])]
|
742
|
+
C2[idx(C2, b)] = newedge + 2
|
743
|
+
newPD.append([newedge + 3, newedge, b, a]) # D
|
744
|
+
newPD.append([newedge + 2, newedge, newedge + 3, newedge + 1]) # E
|
745
|
+
self._braid = Link(newPD).braid(remove_loops=remove_loops)
|
746
|
+
return self._braid
|
747
|
+
else:
|
748
|
+
# -------------------------------------------------
|
749
|
+
# Visualize insertion of the two new crossings D, E
|
750
|
+
# C1 C2 existing crossings
|
751
|
+
# \ /
|
752
|
+
# n1\ /n2 newedge + 1, newedge + 2
|
753
|
+
# D
|
754
|
+
# n3/ \n0 newedge + 3, newedge
|
755
|
+
# \ /
|
756
|
+
# E
|
757
|
+
# a/ \b existing edges, a up, b down
|
758
|
+
# / \
|
759
|
+
# -------------------------------------------------
|
760
|
+
C1 = newPD[newPD.index(heads[-a])]
|
761
|
+
C1[idx(C1, -a)] = newedge + 1
|
762
|
+
C2 = newPD[newPD.index(tails[-b])]
|
763
|
+
C2[idx(C2, -b)] = newedge + 2
|
764
|
+
newPD.append([newedge + 2, newedge + 1, newedge + 3, newedge]) # D
|
765
|
+
newPD.append([newedge + 3, -a, -b, newedge]) # E
|
766
|
+
self._braid = Link(newPD).braid(remove_loops=remove_loops)
|
767
|
+
return self._braid
|
768
|
+
|
769
|
+
# We are in the case where no Vogel moves are necessary.
|
770
|
+
G = DiGraph()
|
771
|
+
G.add_vertices([tuple(c) for c in seifert_circles])
|
772
|
+
for i, c in enumerate(pd_code):
|
773
|
+
if self.orientation()[i] == 1:
|
774
|
+
a = next(x for x in seifert_circles if c[3] in x)
|
775
|
+
b = next(x for x in seifert_circles if c[0] in x)
|
776
|
+
else:
|
777
|
+
a = next(x for x in seifert_circles if c[0] in x)
|
778
|
+
b = next(x for x in seifert_circles if c[1] in x)
|
779
|
+
G.add_edge(tuple(a), tuple(b))
|
780
|
+
|
781
|
+
# Get a simple path from a source to a sink in the digraph
|
782
|
+
it = G.all_paths_iterator(starting_vertices=G.sources(), ending_vertices=G.sinks(), simple=True)
|
783
|
+
ordered_cycles = next(it)
|
784
|
+
|
785
|
+
B = BraidGroup(len(ordered_cycles))
|
786
|
+
available_crossings = copy(pd_code)
|
787
|
+
oc_set = set(ordered_cycles[0])
|
788
|
+
for i, x in enumerate(pd_code):
|
789
|
+
if any(elt in oc_set for elt in x):
|
790
|
+
crossing = x
|
791
|
+
crossing_index = i
|
792
|
+
break
|
793
|
+
available_crossings.remove(crossing)
|
794
|
+
status = [None for i in ordered_cycles]
|
795
|
+
orientation = self.orientation()
|
796
|
+
if orientation[crossing_index] == 1:
|
797
|
+
b = B([1])
|
798
|
+
status[0] = crossing[2]
|
799
|
+
status[1] = crossing[1]
|
800
|
+
else:
|
801
|
+
b = B([-1])
|
802
|
+
status[0] = crossing[3]
|
803
|
+
status[1] = crossing[2]
|
804
|
+
counter = 0
|
805
|
+
while available_crossings:
|
806
|
+
possibles = [x for x in available_crossings if status[counter] in x]
|
807
|
+
if len(status) < counter + 2 or status[counter + 1] is not None:
|
808
|
+
possibles = [x for x in possibles if status[counter + 1] in x]
|
809
|
+
if possibles:
|
810
|
+
added = possibles[0]
|
811
|
+
if orientation[pd_code.index(added)] == 1:
|
812
|
+
b *= B([counter + 1])
|
813
|
+
status[counter] = added[2]
|
814
|
+
status[counter + 1] = added[1]
|
815
|
+
else:
|
816
|
+
b *= B([-counter - 1])
|
817
|
+
status[counter] = added[3]
|
818
|
+
status[counter + 1] = added[2]
|
819
|
+
if counter > 0:
|
820
|
+
counter -= 1
|
821
|
+
available_crossings.remove(added)
|
822
|
+
else:
|
823
|
+
counter += 1
|
824
|
+
self._braid = b
|
825
|
+
return b
|
826
|
+
|
827
|
+
def _directions_of_edges(self):
|
828
|
+
r"""
|
829
|
+
Return the directions of the edges given by the PD code of ``self``.
|
830
|
+
|
831
|
+
OUTPUT:
|
832
|
+
|
833
|
+
A tuple of two dictionaries. The first one assigns
|
834
|
+
each edge of the PD code to the crossing where it starts.
|
835
|
+
The second dictionary assigns it to where it ends.
|
836
|
+
|
837
|
+
EXAMPLES::
|
838
|
+
|
839
|
+
sage: L = Link([[1, 4, 2, 3], [2, 4, 1, 3]])
|
840
|
+
sage: tails, heads = L._directions_of_edges()
|
841
|
+
sage: tails
|
842
|
+
{1: [2, 4, 1, 3], 2: [1, 4, 2, 3], 3: [1, 4, 2, 3], 4: [2, 4, 1, 3]}
|
843
|
+
sage: heads
|
844
|
+
{1: [1, 4, 2, 3], 2: [2, 4, 1, 3], 3: [2, 4, 1, 3], 4: [1, 4, 2, 3]}
|
845
|
+
|
846
|
+
::
|
847
|
+
|
848
|
+
sage: L = Link([[1,4,2,5], [5,2,6,3], [3,6,4,1]])
|
849
|
+
sage: tails, heads = L._directions_of_edges()
|
850
|
+
sage: tails
|
851
|
+
{1: [3, 6, 4, 1],
|
852
|
+
2: [1, 4, 2, 5],
|
853
|
+
3: [5, 2, 6, 3],
|
854
|
+
4: [3, 6, 4, 1],
|
855
|
+
5: [1, 4, 2, 5],
|
856
|
+
6: [5, 2, 6, 3]}
|
857
|
+
sage: heads
|
858
|
+
{1: [1, 4, 2, 5],
|
859
|
+
2: [5, 2, 6, 3],
|
860
|
+
3: [3, 6, 4, 1],
|
861
|
+
4: [1, 4, 2, 5],
|
862
|
+
5: [5, 2, 6, 3],
|
863
|
+
6: [3, 6, 4, 1]}
|
864
|
+
|
865
|
+
::
|
866
|
+
|
867
|
+
sage: L = Link([[1,3,3,2], [2,5,5,4], [4,7,7,1]])
|
868
|
+
sage: tails, heads = L._directions_of_edges()
|
869
|
+
sage: tails
|
870
|
+
{1: [4, 7, 7, 1],
|
871
|
+
2: [1, 3, 3, 2],
|
872
|
+
3: [1, 3, 3, 2],
|
873
|
+
4: [2, 5, 5, 4],
|
874
|
+
5: [2, 5, 5, 4],
|
875
|
+
7: [4, 7, 7, 1]}
|
876
|
+
sage: heads
|
877
|
+
{1: [1, 3, 3, 2],
|
878
|
+
2: [2, 5, 5, 4],
|
879
|
+
3: [1, 3, 3, 2],
|
880
|
+
4: [4, 7, 7, 1],
|
881
|
+
5: [2, 5, 5, 4],
|
882
|
+
7: [4, 7, 7, 1]}
|
883
|
+
"""
|
884
|
+
tails = {}
|
885
|
+
heads = {}
|
886
|
+
pd_code = self.pd_code()
|
887
|
+
for C in pd_code:
|
888
|
+
tails[C[2]] = C
|
889
|
+
a = C[2]
|
890
|
+
D = C
|
891
|
+
while a not in heads:
|
892
|
+
next_crossing = [x for x in pd_code if a in x and x != D]
|
893
|
+
if not next_crossing:
|
894
|
+
heads[a] = D
|
895
|
+
tails[a] = D
|
896
|
+
if D[0] == a:
|
897
|
+
a = D[2]
|
898
|
+
elif D[3] == a:
|
899
|
+
a = D[1]
|
900
|
+
else:
|
901
|
+
a = D[3]
|
902
|
+
else:
|
903
|
+
heads[a] = next_crossing[0]
|
904
|
+
tails[a] = D
|
905
|
+
D = next_crossing[0]
|
906
|
+
a = D[(D.index(a) + 2) % 4]
|
907
|
+
|
908
|
+
unassigned = set(flatten(pd_code)).difference(set(tails))
|
909
|
+
while unassigned:
|
910
|
+
a = unassigned.pop()
|
911
|
+
for x in pd_code:
|
912
|
+
if a in x:
|
913
|
+
D = x
|
914
|
+
break
|
915
|
+
while a not in heads:
|
916
|
+
tails[a] = D
|
917
|
+
for x in pd_code:
|
918
|
+
if a in x and x != D:
|
919
|
+
next_crossing = x
|
920
|
+
break
|
921
|
+
heads[a] = next_crossing
|
922
|
+
D = next_crossing
|
923
|
+
a = D[(D.index(a) + 2) % 4]
|
924
|
+
if a in unassigned:
|
925
|
+
unassigned.remove(a)
|
926
|
+
return tails, heads
|
927
|
+
|
928
|
+
@cached_method
|
929
|
+
def _enhanced_states(self):
|
930
|
+
r"""
|
931
|
+
Return the enhanced states of the diagram.
|
932
|
+
|
933
|
+
Each enhanced state is represented as a tuple containing:
|
934
|
+
|
935
|
+
- A tuple with the type of smoothing made at each crossing (0 represents
|
936
|
+
a A-type smoothing, and 1 represents B-type).
|
937
|
+
|
938
|
+
- A tuple with the circles marked as negative. Each circle is
|
939
|
+
represented by the smoothings it goes through. Each smoothing
|
940
|
+
is represented by the indices of the two strands, and the
|
941
|
+
index of the chord, counted clockwise.
|
942
|
+
|
943
|
+
- A tuple with the circles marked as negative.
|
944
|
+
|
945
|
+
- The i-index (degree) corresponding to the state.
|
946
|
+
|
947
|
+
- the j-index (height) corresponding to the state.
|
948
|
+
|
949
|
+
EXAMPLES::
|
950
|
+
|
951
|
+
sage: K = Link([[[1,-2,3,-1,2,-3]],[-1,-1,-1]])
|
952
|
+
sage: K.pd_code()
|
953
|
+
[[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]]
|
954
|
+
sage: K._enhanced_states()
|
955
|
+
(((0, 0, 0),
|
956
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (5, 2, 8)), ((3, 6, 9), (6, 3, 8))),
|
957
|
+
(),
|
958
|
+
-3,
|
959
|
+
-9),
|
960
|
+
((0, 0, 0),
|
961
|
+
(((2, 5, 7), (5, 2, 8)), ((3, 6, 9), (6, 3, 8))),
|
962
|
+
(((1, 4, 7), (4, 1, 9)),),
|
963
|
+
-3,
|
964
|
+
-7),
|
965
|
+
((0, 0, 0),
|
966
|
+
(((1, 4, 7), (4, 1, 9)), ((3, 6, 9), (6, 3, 8))),
|
967
|
+
(((2, 5, 7), (5, 2, 8)),),
|
968
|
+
-3,
|
969
|
+
-7),
|
970
|
+
((0, 0, 0),
|
971
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (5, 2, 8))),
|
972
|
+
(((3, 6, 9), (6, 3, 8)),),
|
973
|
+
-3,
|
974
|
+
-7),
|
975
|
+
((0, 0, 0),
|
976
|
+
(((3, 6, 9), (6, 3, 8)),),
|
977
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (5, 2, 8))),
|
978
|
+
-3,
|
979
|
+
-5),
|
980
|
+
((0, 0, 0),
|
981
|
+
(((2, 5, 7), (5, 2, 8)),),
|
982
|
+
(((1, 4, 7), (4, 1, 9)), ((3, 6, 9), (6, 3, 8))),
|
983
|
+
-3,
|
984
|
+
-5),
|
985
|
+
((0, 0, 0),
|
986
|
+
(((1, 4, 7), (4, 1, 9)),),
|
987
|
+
(((2, 5, 7), (5, 2, 8)), ((3, 6, 9), (6, 3, 8))),
|
988
|
+
-3,
|
989
|
+
-5),
|
990
|
+
((0, 0, 0),
|
991
|
+
(),
|
992
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (5, 2, 8)), ((3, 6, 9), (6, 3, 8))),
|
993
|
+
-3,
|
994
|
+
-3),
|
995
|
+
((1, 0, 0),
|
996
|
+
(((3, 6, 9), (6, 3, 8)), ((4, 1, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8))),
|
997
|
+
(),
|
998
|
+
-2,
|
999
|
+
-7),
|
1000
|
+
((1, 0, 0),
|
1001
|
+
(((4, 1, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8)),),
|
1002
|
+
(((3, 6, 9), (6, 3, 8)),),
|
1003
|
+
-2,
|
1004
|
+
-5),
|
1005
|
+
((1, 0, 0),
|
1006
|
+
(((3, 6, 9), (6, 3, 8)),),
|
1007
|
+
(((4, 1, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8)),),
|
1008
|
+
-2,
|
1009
|
+
-5),
|
1010
|
+
((1, 0, 0),
|
1011
|
+
(),
|
1012
|
+
(((3, 6, 9), (6, 3, 8)), ((4, 1, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8))),
|
1013
|
+
-2,
|
1014
|
+
-3),
|
1015
|
+
((0, 1, 0),
|
1016
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (2, 6, 8), (3, 5, 8), (3, 6, 9))),
|
1017
|
+
(),
|
1018
|
+
-2,
|
1019
|
+
-7),
|
1020
|
+
((0, 1, 0),
|
1021
|
+
(((2, 5, 7), (2, 6, 8), (3, 5, 8), (3, 6, 9)),),
|
1022
|
+
(((1, 4, 7), (4, 1, 9)),),
|
1023
|
+
-2,
|
1024
|
+
-5),
|
1025
|
+
((0, 1, 0),
|
1026
|
+
(((1, 4, 7), (4, 1, 9)),),
|
1027
|
+
(((2, 5, 7), (2, 6, 8), (3, 5, 8), (3, 6, 9)),),
|
1028
|
+
-2,
|
1029
|
+
-5),
|
1030
|
+
((0, 1, 0),
|
1031
|
+
(),
|
1032
|
+
(((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (2, 6, 8), (3, 5, 8), (3, 6, 9))),
|
1033
|
+
-2,
|
1034
|
+
-3),
|
1035
|
+
((1, 1, 0),
|
1036
|
+
(((2, 6, 8), (3, 5, 8), (3, 6, 9), (4, 1, 9), (4, 2, 7), (5, 1, 7)),),
|
1037
|
+
(),
|
1038
|
+
-1,
|
1039
|
+
-5),
|
1040
|
+
((1, 1, 0),
|
1041
|
+
(),
|
1042
|
+
(((2, 6, 8), (3, 5, 8), (3, 6, 9), (4, 1, 9), (4, 2, 7), (5, 1, 7)),),
|
1043
|
+
-1,
|
1044
|
+
-3),
|
1045
|
+
((0, 0, 1),
|
1046
|
+
(((1, 3, 9), (1, 4, 7), (6, 3, 8), (6, 4, 9)), ((2, 5, 7), (5, 2, 8))),
|
1047
|
+
(),
|
1048
|
+
-2,
|
1049
|
+
-7),
|
1050
|
+
((0, 0, 1),
|
1051
|
+
(((2, 5, 7), (5, 2, 8)),),
|
1052
|
+
(((1, 3, 9), (1, 4, 7), (6, 3, 8), (6, 4, 9)),),
|
1053
|
+
-2,
|
1054
|
+
-5),
|
1055
|
+
((0, 0, 1),
|
1056
|
+
(((1, 3, 9), (1, 4, 7), (6, 3, 8), (6, 4, 9)),),
|
1057
|
+
(((2, 5, 7), (5, 2, 8)),),
|
1058
|
+
-2,
|
1059
|
+
-5),
|
1060
|
+
((0, 0, 1),
|
1061
|
+
(),
|
1062
|
+
(((1, 3, 9), (1, 4, 7), (6, 3, 8), (6, 4, 9)), ((2, 5, 7), (5, 2, 8))),
|
1063
|
+
-2,
|
1064
|
+
-3),
|
1065
|
+
((1, 0, 1),
|
1066
|
+
(((1, 3, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8), (6, 3, 8), (6, 4, 9)),),
|
1067
|
+
(),
|
1068
|
+
-1,
|
1069
|
+
-5),
|
1070
|
+
((1, 0, 1),
|
1071
|
+
(),
|
1072
|
+
(((1, 3, 9), (4, 2, 7), (5, 1, 7), (5, 2, 8), (6, 3, 8), (6, 4, 9)),),
|
1073
|
+
-1,
|
1074
|
+
-3),
|
1075
|
+
((0, 1, 1),
|
1076
|
+
(((1, 3, 9), (1, 4, 7), (2, 5, 7), (2, 6, 8), (3, 5, 8), (6, 4, 9)),),
|
1077
|
+
(),
|
1078
|
+
-1,
|
1079
|
+
-5),
|
1080
|
+
((0, 1, 1),
|
1081
|
+
(),
|
1082
|
+
(((1, 3, 9), (1, 4, 7), (2, 5, 7), (2, 6, 8), (3, 5, 8), (6, 4, 9)),),
|
1083
|
+
-1,
|
1084
|
+
-3),
|
1085
|
+
((1, 1, 1),
|
1086
|
+
(((1, 3, 9), (3, 5, 8), (5, 1, 7)), ((2, 6, 8), (4, 2, 7), (6, 4, 9))),
|
1087
|
+
(),
|
1088
|
+
0,
|
1089
|
+
-5),
|
1090
|
+
((1, 1, 1),
|
1091
|
+
(((2, 6, 8), (4, 2, 7), (6, 4, 9)),),
|
1092
|
+
(((1, 3, 9), (3, 5, 8), (5, 1, 7)),),
|
1093
|
+
0,
|
1094
|
+
-3),
|
1095
|
+
((1, 1, 1),
|
1096
|
+
(((1, 3, 9), (3, 5, 8), (5, 1, 7)),),
|
1097
|
+
(((2, 6, 8), (4, 2, 7), (6, 4, 9)),),
|
1098
|
+
0,
|
1099
|
+
-3),
|
1100
|
+
((1, 1, 1),
|
1101
|
+
(),
|
1102
|
+
(((1, 3, 9), (3, 5, 8), (5, 1, 7)), ((2, 6, 8), (4, 2, 7), (6, 4, 9))),
|
1103
|
+
0,
|
1104
|
+
-1))
|
1105
|
+
"""
|
1106
|
+
writhe = self.writhe()
|
1107
|
+
crossings = self.pd_code()
|
1108
|
+
ncross = len(crossings)
|
1109
|
+
smoothings = []
|
1110
|
+
nmax = max(flatten(crossings)) + 1
|
1111
|
+
for i in range(2 ** ncross):
|
1112
|
+
v = Integer(i).bits()
|
1113
|
+
v = v + (ncross - len(v)) * [0]
|
1114
|
+
G = Graph()
|
1115
|
+
for j, cr in enumerate(crossings):
|
1116
|
+
n = nmax + j
|
1117
|
+
if not v[j]:
|
1118
|
+
# For negative crossings, we go from undercrossings to the left
|
1119
|
+
G.add_edge((cr[1], cr[0], n), cr[0])
|
1120
|
+
G.add_edge((cr[1], cr[0], n), cr[1])
|
1121
|
+
G.add_edge((cr[3], cr[2], n), cr[2])
|
1122
|
+
G.add_edge((cr[3], cr[2], n), cr[3])
|
1123
|
+
else:
|
1124
|
+
# positive crossings, from undercrossing to the right
|
1125
|
+
G.add_edge((cr[0], cr[3], n), cr[0])
|
1126
|
+
G.add_edge((cr[0], cr[3], n), cr[3])
|
1127
|
+
G.add_edge((cr[2], cr[1], n), cr[2])
|
1128
|
+
G.add_edge((cr[2], cr[1], n), cr[1])
|
1129
|
+
sm = set(tuple(sorted(x for x in b if isinstance(x, tuple)))
|
1130
|
+
for b in G.connected_components(sort=False))
|
1131
|
+
iindex = (writhe - ncross + 2 * sum(v)) // 2
|
1132
|
+
jmin = writhe + iindex - len(sm)
|
1133
|
+
jmax = writhe + iindex + len(sm)
|
1134
|
+
smoothings.append((tuple(v), sm, iindex, jmin, jmax))
|
1135
|
+
states = [] # we got all the smoothings, now find all the states
|
1136
|
+
for sm in smoothings:
|
1137
|
+
for k in range(len(sm[1]) + 1):
|
1138
|
+
for circpos in combinations(sorted(sm[1]), k): # Add each state
|
1139
|
+
circneg = sm[1].difference(circpos)
|
1140
|
+
j = writhe + sm[2] + len(circpos) - len(circneg)
|
1141
|
+
states.append((sm[0], tuple(sorted(circneg)), tuple(circpos), sm[2], j))
|
1142
|
+
return tuple(states)
|
1143
|
+
|
1144
|
+
@cached_method
|
1145
|
+
def _khovanov_homology_cached(self, height, ring=ZZ):
|
1146
|
+
r"""
|
1147
|
+
Return the Khovanov homology of the link.
|
1148
|
+
|
1149
|
+
INPUT:
|
1150
|
+
|
1151
|
+
- ``height`` -- the height of the homology to compute
|
1152
|
+
- ``ring`` -- (default: ``ZZ``) the coefficient ring
|
1153
|
+
|
1154
|
+
OUTPUT:
|
1155
|
+
|
1156
|
+
The Khovanov homology of the Link in the given height. It is given
|
1157
|
+
as a tuple of key-value pairs, whose keys are the degrees.
|
1158
|
+
|
1159
|
+
.. NOTE::
|
1160
|
+
|
1161
|
+
This method is intended only as the cache for
|
1162
|
+
:meth:`khovanov_homology`.
|
1163
|
+
|
1164
|
+
EXAMPLES::
|
1165
|
+
|
1166
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]],[-1, -1, -1]])
|
1167
|
+
sage: K._khovanov_homology_cached(-5) # needs sage.modules
|
1168
|
+
((-3, 0), (-2, Z), (-1, 0), (0, 0))
|
1169
|
+
|
1170
|
+
The figure eight knot::
|
1171
|
+
|
1172
|
+
sage: L = Link([[1, 6, 2, 7], [5, 2, 6, 3], [3, 1, 4, 8], [7, 5, 8, 4]])
|
1173
|
+
sage: L._khovanov_homology_cached(-1) # needs sage.modules
|
1174
|
+
((-2, 0), (-1, Z), (0, Z), (1, 0), (2, 0))
|
1175
|
+
"""
|
1176
|
+
crossings = self.pd_code()
|
1177
|
+
ncross = len(crossings)
|
1178
|
+
states = [(_0, set(_1), set(_2), _3, _4)
|
1179
|
+
for (_0, _1, _2, _3, _4) in self._enhanced_states()]
|
1180
|
+
bases = {} # arrange them by (i,j)
|
1181
|
+
for st in states:
|
1182
|
+
i, j = st[3], st[4]
|
1183
|
+
if j == height:
|
1184
|
+
if (i, j) in bases:
|
1185
|
+
bases[i, j].append(st)
|
1186
|
+
else:
|
1187
|
+
bases[i, j] = [st]
|
1188
|
+
complexes = {}
|
1189
|
+
for (i, j), bij in bases.items():
|
1190
|
+
if (i + 1, j) in bases:
|
1191
|
+
m = matrix(ring, len(bij), len(bases[(i + 1, j)]))
|
1192
|
+
for ii in range(m.nrows()):
|
1193
|
+
V1 = bij[ii]
|
1194
|
+
for jj in range(m.ncols()):
|
1195
|
+
V2 = bases[(i + 1, j)][jj]
|
1196
|
+
V20 = V2[0]
|
1197
|
+
difs = [index for index, value in enumerate(V1[0])
|
1198
|
+
if value != V20[index]]
|
1199
|
+
if len(difs) == 1 and not (V2[2].intersection(V1[1]) or V2[1].intersection(V1[2])):
|
1200
|
+
m[ii, jj] = (-1)**sum(V2[0][x] for x in range(difs[0] + 1, ncross))
|
1201
|
+
# Here we have the matrix constructed, now we have to put it in the dictionary of complexes
|
1202
|
+
else:
|
1203
|
+
m = matrix(ring, len(bij), 0)
|
1204
|
+
complexes[i] = m.transpose()
|
1205
|
+
if (i - 1, j) not in bases:
|
1206
|
+
complexes[i - 1] = matrix(ring, len(bases[(i, j)]), 0)
|
1207
|
+
homologies = ChainComplex(complexes).homology()
|
1208
|
+
return tuple(sorted(homologies.items()))
|
1209
|
+
|
1210
|
+
def khovanov_homology(self, ring=ZZ, height=None, degree=None):
|
1211
|
+
r"""
|
1212
|
+
Return the Khovanov homology of the link.
|
1213
|
+
|
1214
|
+
INPUT:
|
1215
|
+
|
1216
|
+
- ``ring`` -- (default: ``ZZ``) the coefficient ring
|
1217
|
+
|
1218
|
+
- ``height`` -- the height of the homology to compute,
|
1219
|
+
if not specified, all the heights are computed
|
1220
|
+
|
1221
|
+
- ``degree`` -- the degree of the homology to compute,
|
1222
|
+
if not specified, all the degrees are computed
|
1223
|
+
|
1224
|
+
OUTPUT:
|
1225
|
+
|
1226
|
+
The Khovanov homology of the Link. It is given as a dictionary
|
1227
|
+
whose keys are the different heights. For each height, the
|
1228
|
+
homology is given as another dictionary whose keys are the degrees.
|
1229
|
+
|
1230
|
+
EXAMPLES::
|
1231
|
+
|
1232
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]],[-1, -1, -1]])
|
1233
|
+
sage: K.khovanov_homology() # needs sage.modules
|
1234
|
+
{-9: {-3: Z},
|
1235
|
+
-7: {-3: 0, -2: C2},
|
1236
|
+
-5: {-3: 0, -2: Z, -1: 0, 0: 0},
|
1237
|
+
-3: {-3: 0, -2: 0, -1: 0, 0: Z},
|
1238
|
+
-1: {0: Z}}
|
1239
|
+
|
1240
|
+
The figure eight knot::
|
1241
|
+
|
1242
|
+
sage: L = Link([[1, 6, 2, 7], [5, 2, 6, 3], [3, 1, 4, 8], [7, 5, 8, 4]])
|
1243
|
+
sage: L.khovanov_homology(height=-1) # needs sage.modules
|
1244
|
+
{-1: {-2: 0, -1: Z, 0: Z, 1: 0, 2: 0}}
|
1245
|
+
|
1246
|
+
The Hopf link::
|
1247
|
+
|
1248
|
+
sage: # needs sage.modules
|
1249
|
+
sage: B = BraidGroup(2)
|
1250
|
+
sage: b = B([1, 1])
|
1251
|
+
sage: K = Link(b)
|
1252
|
+
sage: K.khovanov_homology(degree=2)
|
1253
|
+
{2: {2: 0}, 4: {2: Z}, 6: {2: Z}}
|
1254
|
+
|
1255
|
+
TESTS:
|
1256
|
+
|
1257
|
+
Check that :issue:`31001` is fixed::
|
1258
|
+
|
1259
|
+
sage: # needs sage.modules
|
1260
|
+
sage: L = Link([])
|
1261
|
+
sage: L.khovanov_homology()
|
1262
|
+
{-1: {0: Z}, 1: {0: Z}}
|
1263
|
+
sage: L.khovanov_homology(height=-1)
|
1264
|
+
{-1: {0: Z}}
|
1265
|
+
sage: L.khovanov_homology(height=0)
|
1266
|
+
{}
|
1267
|
+
sage: L.khovanov_homology(QQ, height=1)
|
1268
|
+
{1: {0: Vector space of dimension 1 over Rational Field}}
|
1269
|
+
sage: L.khovanov_homology(GF(2), degree=0)
|
1270
|
+
{-1: {0: Vector space of dimension 1 over Finite Field of size 2},
|
1271
|
+
1: {0: Vector space of dimension 1 over Finite Field of size 2}}
|
1272
|
+
sage: L.khovanov_homology(degree=1)
|
1273
|
+
{}
|
1274
|
+
sage: L.khovanov_homology(degree=0, height=1)
|
1275
|
+
{1: {0: Z}}
|
1276
|
+
sage: L.khovanov_homology(degree=1, height=1)
|
1277
|
+
{}
|
1278
|
+
"""
|
1279
|
+
if not self.pd_code(): # special case for the unknot with no crossings
|
1280
|
+
from sage.homology.homology_group import HomologyGroup
|
1281
|
+
homs = {-1: {0: HomologyGroup(1, ring, [0])},
|
1282
|
+
1: {0: HomologyGroup(1, ring, [0])}}
|
1283
|
+
if height is not None:
|
1284
|
+
if height not in homs:
|
1285
|
+
return {}
|
1286
|
+
homs = {height: homs[height]}
|
1287
|
+
if degree is not None:
|
1288
|
+
homs = {ht: {degree: homs[ht][degree]} for ht in homs if degree in homs[ht]}
|
1289
|
+
return homs
|
1290
|
+
|
1291
|
+
if height is not None:
|
1292
|
+
heights = [height]
|
1293
|
+
else:
|
1294
|
+
heights = sorted(set(state[-1] for state in self._enhanced_states()))
|
1295
|
+
if degree is not None:
|
1296
|
+
homs = {j: dict(self._khovanov_homology_cached(j, ring)) for j in heights}
|
1297
|
+
homologies = {j: {degree: homs[j][degree]} for j in homs if degree in homs[j]}
|
1298
|
+
else:
|
1299
|
+
homologies = {j: dict(self._khovanov_homology_cached(j, ring)) for j in heights}
|
1300
|
+
return homologies
|
1301
|
+
|
1302
|
+
def oriented_gauss_code(self):
|
1303
|
+
r"""
|
1304
|
+
Return the oriented Gauss code of ``self``.
|
1305
|
+
|
1306
|
+
The oriented Gauss code has two parts:
|
1307
|
+
|
1308
|
+
a. the Gauss code
|
1309
|
+
|
1310
|
+
b. the orientation of each crossing
|
1311
|
+
|
1312
|
+
The following orientation was taken into consideration for
|
1313
|
+
construction of knots:
|
1314
|
+
|
1315
|
+
From the outgoing of the overcrossing if we move in the clockwise
|
1316
|
+
direction to reach the outgoing of the undercrossing then we label
|
1317
|
+
that crossing as `-1`.
|
1318
|
+
|
1319
|
+
From the outgoing of the overcrossing if we move in the anticlockwise
|
1320
|
+
direction to reach the outgoing of the undercrossing then we label
|
1321
|
+
that crossing as `+1`.
|
1322
|
+
|
1323
|
+
One more consideration we take in while constructing the orientation
|
1324
|
+
is the order of the orientation is same as the ordering of the
|
1325
|
+
crossings in the Gauss code.
|
1326
|
+
|
1327
|
+
.. NOTE::
|
1328
|
+
|
1329
|
+
Convention: under is denoted by `-1`, and over by `+1` in the
|
1330
|
+
crossing info.
|
1331
|
+
|
1332
|
+
EXAMPLES::
|
1333
|
+
|
1334
|
+
sage: L = Link([[1, 10, 2, 11], [6, 3, 7, 2], [3, 9, 4, 12],
|
1335
|
+
....: [9, 6, 10, 5], [8, 4, 5, 1], [11, 7, 12, 8]])
|
1336
|
+
sage: L.oriented_gauss_code()
|
1337
|
+
[[[-1, 2, -3, 5], [4, -2, 6, -5], [-4, 1, -6, 3]], [-1, 1, 1, 1, -1, -1]]
|
1338
|
+
sage: L = Link([[1, 3, 2, 4], [6, 2, 3, 1], [7, 5, 8, 4], [5, 7, 6, 8]])
|
1339
|
+
sage: L.oriented_gauss_code()
|
1340
|
+
[[[-1, 2], [-3, 4], [1, 3, -4, -2]], [-1, -1, 1, 1]]
|
1341
|
+
|
1342
|
+
sage: B = BraidGroup(8)
|
1343
|
+
sage: b = B([1, 1, 1, 1, 1])
|
1344
|
+
sage: L = Link(b)
|
1345
|
+
sage: L.oriented_gauss_code()
|
1346
|
+
[[[1, -2, 3, -4, 5, -1, 2, -3, 4, -5]], [1, 1, 1, 1, 1]]
|
1347
|
+
|
1348
|
+
TESTS::
|
1349
|
+
|
1350
|
+
sage: L = Link([])
|
1351
|
+
sage: L.oriented_gauss_code()
|
1352
|
+
[[], []]
|
1353
|
+
|
1354
|
+
sage: L = Link(BraidGroup(2).one())
|
1355
|
+
sage: L.oriented_gauss_code()
|
1356
|
+
[[], []]
|
1357
|
+
"""
|
1358
|
+
if self._oriented_gauss_code is not None:
|
1359
|
+
return self._oriented_gauss_code
|
1360
|
+
|
1361
|
+
pd = self.pd_code()
|
1362
|
+
orient = self.orientation()
|
1363
|
+
crossing_info = {}
|
1364
|
+
for i, j in enumerate(pd):
|
1365
|
+
if orient[i] == -1:
|
1366
|
+
crossing_info[(j[0], -1, i + 1)] = j[2]
|
1367
|
+
crossing_info[(j[1], 1, i + 1)] = j[3]
|
1368
|
+
elif orient[i] == 1:
|
1369
|
+
crossing_info[(j[0], -1, i + 1)] = j[2]
|
1370
|
+
crossing_info[(j[3], 1, i + 1)] = j[1]
|
1371
|
+
edges = {}
|
1372
|
+
cross_number = {}
|
1373
|
+
for i, j in crossing_info.items():
|
1374
|
+
edges[i[0]] = [j]
|
1375
|
+
if i[1] == 1:
|
1376
|
+
cross_number[i[0]] = i[2]
|
1377
|
+
elif i[1] == -1:
|
1378
|
+
cross_number[i[0]] = -i[2]
|
1379
|
+
edges_graph = DiGraph(edges)
|
1380
|
+
d = edges_graph.all_simple_cycles()
|
1381
|
+
code = []
|
1382
|
+
for i in d:
|
1383
|
+
l = [cross_number[j] for j in i]
|
1384
|
+
del l[-1]
|
1385
|
+
code.append(l)
|
1386
|
+
oriented_code = [code, orient]
|
1387
|
+
self._oriented_gauss_code = oriented_code
|
1388
|
+
return self._oriented_gauss_code
|
1389
|
+
|
1390
|
+
def pd_code(self):
|
1391
|
+
r"""
|
1392
|
+
Return the planar diagram code of ``self``.
|
1393
|
+
|
1394
|
+
The planar diagram is returned in the following format.
|
1395
|
+
|
1396
|
+
We construct the crossing by starting with the entering component
|
1397
|
+
of the undercrossing, move in the anti-clockwise direction (see the
|
1398
|
+
note below) and then generate the list. If the crossing is given by
|
1399
|
+
`[a, b, c, d]`, then we interpret this information as:
|
1400
|
+
|
1401
|
+
1. `a` is the entering component of the undercrossing;
|
1402
|
+
2. `b, d` are the components of the overcrossing;
|
1403
|
+
3. `c` is the leaving component of the undercrossing.
|
1404
|
+
|
1405
|
+
.. NOTE::
|
1406
|
+
|
1407
|
+
Until version 10.0 the convention to read the ``PD`` code has been
|
1408
|
+
to list the components in clockwise direction. As of version 10.1
|
1409
|
+
the convention has changed, since it was opposite to the usage in
|
1410
|
+
most other places.
|
1411
|
+
|
1412
|
+
Thus, if you use ``PD`` codes from former Sage releases with this
|
1413
|
+
version you should check for the correct mirror type.
|
1414
|
+
|
1415
|
+
EXAMPLES::
|
1416
|
+
|
1417
|
+
sage: L = Link([[[1, -2, 3, -4, 2, -1, 4, -3]], [1, 1, -1, -1]])
|
1418
|
+
sage: L.pd_code()
|
1419
|
+
[[6, 2, 7, 1], [2, 6, 3, 5], [8, 3, 1, 4], [4, 7, 5, 8]]
|
1420
|
+
|
1421
|
+
sage: B = BraidGroup(2)
|
1422
|
+
sage: b = B([1, 1, 1, 1, 1])
|
1423
|
+
sage: L = Link(b)
|
1424
|
+
sage: L.pd_code()
|
1425
|
+
[[2, 4, 3, 1], [4, 6, 5, 3], [6, 8, 7, 5], [8, 10, 9, 7], [10, 2, 1, 9]]
|
1426
|
+
sage: L = Link([[[2, -1], [1, -2]], [1, 1]])
|
1427
|
+
sage: L.pd_code()
|
1428
|
+
[[2, 4, 1, 3], [4, 2, 3, 1]]
|
1429
|
+
sage: L = Link([[1, 2, 3, 3], [2, 4, 5, 5], [4, 1, 7, 7]])
|
1430
|
+
sage: L.pd_code()
|
1431
|
+
[[1, 2, 3, 3], [2, 4, 5, 5], [4, 1, 7, 7]]
|
1432
|
+
|
1433
|
+
TESTS::
|
1434
|
+
|
1435
|
+
sage: L = Link([[], []])
|
1436
|
+
sage: L.pd_code()
|
1437
|
+
[]
|
1438
|
+
|
1439
|
+
sage: L = Link(BraidGroup(2).one())
|
1440
|
+
sage: L.pd_code()
|
1441
|
+
[]
|
1442
|
+
"""
|
1443
|
+
if self._pd_code is not None:
|
1444
|
+
return self._pd_code
|
1445
|
+
|
1446
|
+
if self._oriented_gauss_code is not None:
|
1447
|
+
oriented_gauss_code = self._oriented_gauss_code
|
1448
|
+
d_dic = {}
|
1449
|
+
if len(oriented_gauss_code[0]) > 1:
|
1450
|
+
d = flatten(oriented_gauss_code[0])
|
1451
|
+
for i, j in enumerate(d):
|
1452
|
+
d_dic[j] = [i + 1, i + 2]
|
1453
|
+
# here we collect the final component in each Gauss code
|
1454
|
+
last_component = [i[-1] for i in oriented_gauss_code[0]]
|
1455
|
+
first_component = [i[0] for i in oriented_gauss_code[0]]
|
1456
|
+
# here we correct the last_component
|
1457
|
+
for i, j in zip(last_component, first_component):
|
1458
|
+
d_dic[i][1] = d_dic[j][0]
|
1459
|
+
crossing_dic = {}
|
1460
|
+
for i, x in enumerate(oriented_gauss_code[1]):
|
1461
|
+
if x == -1:
|
1462
|
+
crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][0],
|
1463
|
+
d_dic[-(i + 1)][1], d_dic[i + 1][1]]
|
1464
|
+
elif x == 1:
|
1465
|
+
crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1],
|
1466
|
+
d_dic[-(i + 1)][1], d_dic[i + 1][0]]
|
1467
|
+
elif len(oriented_gauss_code[0]) == 1:
|
1468
|
+
for i, j in enumerate(oriented_gauss_code[0][0]):
|
1469
|
+
d_dic[j] = [i + 1, i + 2]
|
1470
|
+
d_dic[oriented_gauss_code[0][0][-1]][1] = 1
|
1471
|
+
crossing_dic = {}
|
1472
|
+
for i, x in enumerate(oriented_gauss_code[1]):
|
1473
|
+
if x == -1:
|
1474
|
+
crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][0],
|
1475
|
+
d_dic[-(i + 1)][1], d_dic[i + 1][1]]
|
1476
|
+
elif x == 1:
|
1477
|
+
crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1],
|
1478
|
+
d_dic[-(i + 1)][1], d_dic[i + 1][0]]
|
1479
|
+
else:
|
1480
|
+
crossing_dic = {}
|
1481
|
+
|
1482
|
+
pd = list(crossing_dic.values())
|
1483
|
+
self._pd_code = pd
|
1484
|
+
return self._pd_code
|
1485
|
+
|
1486
|
+
if self._braid is not None:
|
1487
|
+
strings = list(range(1, self._braid.strands() + 1))
|
1488
|
+
b = list(self._braid.Tietze())
|
1489
|
+
pd = []
|
1490
|
+
strings_max = strings[-1]
|
1491
|
+
for i in b:
|
1492
|
+
if i > 0:
|
1493
|
+
pd.append(
|
1494
|
+
[strings[i], strings_max + 2, strings_max + 1, strings[i - 1]])
|
1495
|
+
else:
|
1496
|
+
pd.append(
|
1497
|
+
[strings[abs(i) - 1], strings[abs(i)], strings_max + 2, strings_max + 1])
|
1498
|
+
strings[abs(i) - 1] = strings_max + 1
|
1499
|
+
strings[abs(i)] = strings_max + 2
|
1500
|
+
strings_max = strings_max + 2
|
1501
|
+
for i in pd:
|
1502
|
+
for j in range(4):
|
1503
|
+
if i[j] in strings:
|
1504
|
+
i[j] = strings.index(i[j]) + 1
|
1505
|
+
self._pd_code = pd
|
1506
|
+
return pd
|
1507
|
+
|
1508
|
+
raise AssertionError("invalid state")
|
1509
|
+
|
1510
|
+
def gauss_code(self):
|
1511
|
+
r"""
|
1512
|
+
Return the Gauss code of ``self``.
|
1513
|
+
|
1514
|
+
The Gauss code is generated by the following procedure:
|
1515
|
+
|
1516
|
+
a. Number the crossings from `1` to `n`.
|
1517
|
+
b. Select a point on the knot and start moving along the component.
|
1518
|
+
c. At each crossing, take the number of the crossing, along with
|
1519
|
+
sign, which is `-` if it is an undercrossing and `+` if it is a
|
1520
|
+
overcrossing.
|
1521
|
+
|
1522
|
+
EXAMPLES::
|
1523
|
+
|
1524
|
+
sage: L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
1525
|
+
sage: L.gauss_code()
|
1526
|
+
[[-1, 2], [1, -2]]
|
1527
|
+
|
1528
|
+
sage: B = BraidGroup(8)
|
1529
|
+
sage: L = Link(B([1, -2, 1, -2, -2]))
|
1530
|
+
sage: L.gauss_code()
|
1531
|
+
[[-1, 3, -4, 5], [1, -2, 4, -5, 2, -3]]
|
1532
|
+
|
1533
|
+
sage: L = Link([[[-1, 2], [-3, 4], [1, 3, -4, -2]], [-1, -1, 1, 1]])
|
1534
|
+
sage: L.gauss_code()
|
1535
|
+
[[-1, 2], [-3, 4], [1, 3, -4, -2]]
|
1536
|
+
"""
|
1537
|
+
return self.oriented_gauss_code()[0]
|
1538
|
+
|
1539
|
+
def dowker_notation(self):
|
1540
|
+
r"""
|
1541
|
+
Return the Dowker notation of ``self``.
|
1542
|
+
|
1543
|
+
Similar to the PD code we number the components, so every crossing
|
1544
|
+
is represented by four numbers. We focus on the incoming entities
|
1545
|
+
of the under and the overcrossing. It is the pair of incoming
|
1546
|
+
undercrossing and the incoming overcrossing. This information at
|
1547
|
+
every crossing gives the Dowker notation.
|
1548
|
+
|
1549
|
+
OUTPUT:
|
1550
|
+
|
1551
|
+
A list containing the pair of incoming under cross and the incoming
|
1552
|
+
over cross.
|
1553
|
+
|
1554
|
+
EXAMPLES::
|
1555
|
+
|
1556
|
+
sage: L = Link([[[-1, 2, -3, 4, 5, 1, -2, 6, 7, 3, -4, -7, -6,-5]],
|
1557
|
+
....: [-1, -1, -1, -1, 1, -1, 1]])
|
1558
|
+
sage: L.dowker_notation()
|
1559
|
+
[(1, 6), (7, 2), (3, 10), (11, 4), (14, 5), (13, 8), (12, 9)]
|
1560
|
+
|
1561
|
+
sage: B = BraidGroup(4)
|
1562
|
+
sage: L = Link(B([1, 2, 1, 2]))
|
1563
|
+
sage: L.dowker_notation()
|
1564
|
+
[(2, 1), (3, 5), (6, 4), (7, 9)]
|
1565
|
+
sage: L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
1566
|
+
sage: L.dowker_notation()
|
1567
|
+
[(1, 3), (4, 2)]
|
1568
|
+
"""
|
1569
|
+
pd = self.pd_code()
|
1570
|
+
orient = self.orientation()
|
1571
|
+
dn = [(i[0], i[1]) if orient[j] == -1 else (i[0], i[3])
|
1572
|
+
for j, i in enumerate(pd)]
|
1573
|
+
return dn
|
1574
|
+
|
1575
|
+
def _braid_word_components(self):
|
1576
|
+
r"""
|
1577
|
+
Return the disjoint braid components, if any, else return the braid
|
1578
|
+
of ``self``.
|
1579
|
+
|
1580
|
+
For example consider the braid ``[-1, 3, 1, 3]`` this can be viewed
|
1581
|
+
as a braid with components as ``[-1, 1]`` and ``[3, 3]``. There is no
|
1582
|
+
common crossing to these two (in sense there is a crossing between
|
1583
|
+
strand `1` and `2`, crossing between `3` and `4` but no crossing
|
1584
|
+
between strand `2` and `3`, so these can be viewed as independent
|
1585
|
+
components in the braid).
|
1586
|
+
|
1587
|
+
OUTPUT: list containing the components
|
1588
|
+
|
1589
|
+
EXAMPLES::
|
1590
|
+
|
1591
|
+
sage: B = BraidGroup(4)
|
1592
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1593
|
+
sage: L._braid_word_components()
|
1594
|
+
([-1, 1], [3, 3])
|
1595
|
+
sage: B = BraidGroup(8)
|
1596
|
+
sage: L = Link(B([-1, 3, 1, 5, 1, 7, 1, 6]))
|
1597
|
+
sage: L._braid_word_components()
|
1598
|
+
([-1, 1, 1, 1], [3], [5, 7, 6])
|
1599
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1600
|
+
sage: L._braid_word_components()
|
1601
|
+
([-2, 1, 1], [4, 4], [6])
|
1602
|
+
"""
|
1603
|
+
ml = list(self.braid().Tietze())
|
1604
|
+
if not ml:
|
1605
|
+
return tuple()
|
1606
|
+
|
1607
|
+
l = set(abs(k) for k in ml)
|
1608
|
+
missing1 = set(range(min(l), max(l) + 1)) - l
|
1609
|
+
if not missing1:
|
1610
|
+
return (ml,)
|
1611
|
+
|
1612
|
+
missing = sorted(missing1)
|
1613
|
+
x = [[] for i in range(len(missing) + 1)]
|
1614
|
+
for i, a in enumerate(missing):
|
1615
|
+
for j, mlj in enumerate(ml):
|
1616
|
+
if mlj != 0 and abs(mlj) < a:
|
1617
|
+
x[i].append(mlj)
|
1618
|
+
ml[j] = 0
|
1619
|
+
elif mlj != 0 and abs(mlj) > missing[-1]:
|
1620
|
+
x[-1].append(mlj)
|
1621
|
+
ml[j] = 0
|
1622
|
+
return tuple([a for a in x if a])
|
1623
|
+
|
1624
|
+
def _braid_word_components_vector(self):
|
1625
|
+
r"""
|
1626
|
+
The list from the :meth:`_braid_word_components` is flattened to
|
1627
|
+
give out the vector form.
|
1628
|
+
|
1629
|
+
OUTPUT: list containing braid word components
|
1630
|
+
|
1631
|
+
EXAMPLES::
|
1632
|
+
|
1633
|
+
sage: B = BraidGroup(4)
|
1634
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1635
|
+
sage: L._braid_word_components_vector()
|
1636
|
+
[-1, 1, 3, 3]
|
1637
|
+
sage: B = BraidGroup(8)
|
1638
|
+
sage: L = Link(B([-1, 3, 1, 5, 1, 7, 1, 6]))
|
1639
|
+
sage: L._braid_word_components_vector()
|
1640
|
+
[-1, 1, 1, 1, 3, 5, 7, 6]
|
1641
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1642
|
+
sage: L._braid_word_components_vector()
|
1643
|
+
[-2, 1, 1, 4, 4, 6]
|
1644
|
+
"""
|
1645
|
+
return flatten(self._braid_word_components())
|
1646
|
+
|
1647
|
+
def _homology_generators(self):
|
1648
|
+
r"""
|
1649
|
+
The set of generators for the first homology group of the connected
|
1650
|
+
Seifert surface of the given link.
|
1651
|
+
|
1652
|
+
This method uses the :meth:`_braid_word_components_vector` to generate
|
1653
|
+
the homology generators. The position of the repeated element w.r.t.
|
1654
|
+
the braid word component vector list is compiled into a list.
|
1655
|
+
|
1656
|
+
This is based on Lemma 3.1 in [Col2013]_.
|
1657
|
+
|
1658
|
+
OUTPUT:
|
1659
|
+
|
1660
|
+
A list of integers `i \in \{1, 2, \ldots, n-1\}` corresponding
|
1661
|
+
to the simple generators `s_i` that gives a homology generator or
|
1662
|
+
`0` if the position does not represent a generator.
|
1663
|
+
|
1664
|
+
EXAMPLES::
|
1665
|
+
|
1666
|
+
sage: # needs sage.modules
|
1667
|
+
sage: B = BraidGroup(4)
|
1668
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1669
|
+
sage: L._homology_generators()
|
1670
|
+
[1, 0, 3]
|
1671
|
+
sage: B = BraidGroup(8)
|
1672
|
+
sage: L = Link(B([-1, 3, 1, 5, 1, 7, 1, 6]))
|
1673
|
+
sage: L._homology_generators()
|
1674
|
+
[1, 2, 3, 0, 0, 0, 0]
|
1675
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1676
|
+
sage: L._homology_generators()
|
1677
|
+
[0, 2, 0, 4, 0]
|
1678
|
+
"""
|
1679
|
+
x = self._braid_word_components_vector()
|
1680
|
+
hom_gen = []
|
1681
|
+
for j in range(len(x) - 1):
|
1682
|
+
a = abs(x[j])
|
1683
|
+
for i in range(j + 1, len(x)):
|
1684
|
+
if a == abs(x[i]):
|
1685
|
+
hom_gen.append(i)
|
1686
|
+
break
|
1687
|
+
else:
|
1688
|
+
hom_gen.append(0)
|
1689
|
+
return hom_gen
|
1690
|
+
|
1691
|
+
@cached_method
|
1692
|
+
def seifert_matrix(self):
|
1693
|
+
r"""
|
1694
|
+
Return the Seifert matrix associated with ``self``.
|
1695
|
+
|
1696
|
+
ALGORITHM:
|
1697
|
+
|
1698
|
+
This is the algorithm presented in Section 3.3 of [Col2013]_.
|
1699
|
+
|
1700
|
+
OUTPUT:
|
1701
|
+
|
1702
|
+
The intersection matrix of a (not necessarily minimal) Seifert surface.
|
1703
|
+
|
1704
|
+
EXAMPLES::
|
1705
|
+
|
1706
|
+
sage: # needs sage.modules
|
1707
|
+
sage: B = BraidGroup(4)
|
1708
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1709
|
+
sage: L.seifert_matrix()
|
1710
|
+
[ 0 0]
|
1711
|
+
[ 0 -1]
|
1712
|
+
sage: B = BraidGroup(8)
|
1713
|
+
sage: L = Link(B([-1, 3, 1, 5, 1, 7, 1, 6]))
|
1714
|
+
sage: L.seifert_matrix()
|
1715
|
+
[ 0 0 0]
|
1716
|
+
[ 1 -1 0]
|
1717
|
+
[ 0 1 -1]
|
1718
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1719
|
+
sage: L.seifert_matrix()
|
1720
|
+
[-1 0]
|
1721
|
+
[ 0 -1]
|
1722
|
+
"""
|
1723
|
+
x = self._braid_word_components_vector()
|
1724
|
+
h = self._homology_generators()
|
1725
|
+
indices = [i for i, hi in enumerate(h) if hi]
|
1726
|
+
N = len(indices)
|
1727
|
+
A = matrix(ZZ, N, N, 0)
|
1728
|
+
for ni, i in enumerate(indices):
|
1729
|
+
hi = h[i]
|
1730
|
+
A[ni, ni] = -(x[i] + x[hi]).sign()
|
1731
|
+
for nj in range(ni + 1, N):
|
1732
|
+
j = indices[nj]
|
1733
|
+
if hi > h[j] or hi < j:
|
1734
|
+
continue
|
1735
|
+
if hi == j:
|
1736
|
+
if x[j] > 0:
|
1737
|
+
A[nj, ni] = 1
|
1738
|
+
else:
|
1739
|
+
A[ni, nj] = -1
|
1740
|
+
elif abs(x[i]) - abs(x[j]) == 1:
|
1741
|
+
A[nj, ni] = -1
|
1742
|
+
elif abs(x[j]) - abs(x[i]) == 1:
|
1743
|
+
A[ni, nj] = 1
|
1744
|
+
A.set_immutable()
|
1745
|
+
return A
|
1746
|
+
|
1747
|
+
@cached_method
|
1748
|
+
def number_of_components(self):
|
1749
|
+
r"""
|
1750
|
+
Return the number of connected components of ``self``.
|
1751
|
+
|
1752
|
+
OUTPUT: number of connected components
|
1753
|
+
|
1754
|
+
EXAMPLES::
|
1755
|
+
|
1756
|
+
sage: B = BraidGroup(4)
|
1757
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1758
|
+
sage: L.number_of_components()
|
1759
|
+
4
|
1760
|
+
sage: B = BraidGroup(8)
|
1761
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1762
|
+
sage: L.number_of_components()
|
1763
|
+
5
|
1764
|
+
sage: L = Link(B([1, 2, 1, 2]))
|
1765
|
+
sage: L.number_of_components()
|
1766
|
+
1
|
1767
|
+
sage: L = Link(B.one())
|
1768
|
+
sage: L.number_of_components()
|
1769
|
+
1
|
1770
|
+
"""
|
1771
|
+
G = Graph()
|
1772
|
+
pd = self.pd_code()
|
1773
|
+
if not pd:
|
1774
|
+
return ZZ.one()
|
1775
|
+
G.add_vertices(set(flatten(pd)))
|
1776
|
+
for c in pd:
|
1777
|
+
G.add_edge(c[0], c[2])
|
1778
|
+
G.add_edge(c[3], c[1])
|
1779
|
+
return G.connected_components_number()
|
1780
|
+
|
1781
|
+
def is_knot(self) -> bool:
|
1782
|
+
r"""
|
1783
|
+
Return ``True`` if ``self`` is a knot.
|
1784
|
+
|
1785
|
+
Every knot is a link but the converse is not true.
|
1786
|
+
|
1787
|
+
EXAMPLES::
|
1788
|
+
|
1789
|
+
sage: B = BraidGroup(4)
|
1790
|
+
sage: L = Link(B([1, 3, 1, -3]))
|
1791
|
+
sage: L.is_knot()
|
1792
|
+
False
|
1793
|
+
sage: B = BraidGroup(8)
|
1794
|
+
sage: L = Link(B([1, 2, 3, 4, 5, 6]))
|
1795
|
+
sage: L.is_knot()
|
1796
|
+
True
|
1797
|
+
"""
|
1798
|
+
return self.number_of_components() == 1
|
1799
|
+
|
1800
|
+
def genus(self):
|
1801
|
+
r"""
|
1802
|
+
Return the genus of ``self``.
|
1803
|
+
|
1804
|
+
EXAMPLES::
|
1805
|
+
|
1806
|
+
sage: B = BraidGroup(4)
|
1807
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1808
|
+
sage: L.genus()
|
1809
|
+
0
|
1810
|
+
sage: L = Link(B([1,3]))
|
1811
|
+
sage: L.genus()
|
1812
|
+
0
|
1813
|
+
sage: B = BraidGroup(8)
|
1814
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1815
|
+
sage: L.genus()
|
1816
|
+
0
|
1817
|
+
sage: L = Link(B([1, 2, 1, 2]))
|
1818
|
+
sage: L.genus()
|
1819
|
+
1
|
1820
|
+
"""
|
1821
|
+
b = self.braid().Tietze()
|
1822
|
+
if not b:
|
1823
|
+
return ZZ.zero()
|
1824
|
+
|
1825
|
+
B = self.braid().parent()
|
1826
|
+
x = self._braid_word_components()
|
1827
|
+
q = []
|
1828
|
+
s_tmp = []
|
1829
|
+
for xi in x:
|
1830
|
+
tmp = []
|
1831
|
+
b1 = min(abs(k) for k in xi)
|
1832
|
+
for xij in xi:
|
1833
|
+
if xij > 0:
|
1834
|
+
xij = xij - b1 + 1
|
1835
|
+
else:
|
1836
|
+
xij = xij + b1 - 1
|
1837
|
+
tmp.append(xij)
|
1838
|
+
s_tmp.append(B(tmp))
|
1839
|
+
s = []
|
1840
|
+
for i in s_tmp:
|
1841
|
+
b = i.Tietze()
|
1842
|
+
s.append(list(b))
|
1843
|
+
t = [Link(B(si)).number_of_components() for si in s]
|
1844
|
+
for i, j in enumerate(s):
|
1845
|
+
if not j:
|
1846
|
+
j.append(-2)
|
1847
|
+
for i in s:
|
1848
|
+
q2 = max(abs(k) + 1 for k in i)
|
1849
|
+
q.append(q2)
|
1850
|
+
g = [((2 - t[i]) + len(x[i]) - q[i]) / 2 for i in range(len(x))]
|
1851
|
+
return sum(g, ZZ.zero())
|
1852
|
+
|
1853
|
+
def signature(self):
|
1854
|
+
r"""
|
1855
|
+
Return the signature of ``self``.
|
1856
|
+
|
1857
|
+
This is defined as the signature of the symmetric matrix
|
1858
|
+
|
1859
|
+
.. MATH::
|
1860
|
+
|
1861
|
+
V + V^{t},
|
1862
|
+
|
1863
|
+
where `V` is the :meth:`Seifert matrix <seifert_matrix>`.
|
1864
|
+
|
1865
|
+
.. SEEALSO:: :meth:`omega_signature`, :meth:`seifert_matrix`
|
1866
|
+
|
1867
|
+
EXAMPLES::
|
1868
|
+
|
1869
|
+
sage: # needs sage.modules
|
1870
|
+
sage: B = BraidGroup(4)
|
1871
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1872
|
+
sage: L.signature()
|
1873
|
+
-1
|
1874
|
+
sage: B = BraidGroup(8)
|
1875
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1876
|
+
sage: L.signature()
|
1877
|
+
-2
|
1878
|
+
sage: L = Link(B([1, 2, 1, 2]))
|
1879
|
+
sage: L.signature()
|
1880
|
+
-2
|
1881
|
+
"""
|
1882
|
+
V = self.seifert_matrix()
|
1883
|
+
m = V + V.transpose()
|
1884
|
+
return ZZ.sum(j.real().sign() for j in m.eigenvalues())
|
1885
|
+
|
1886
|
+
def omega_signature(self, omega):
|
1887
|
+
r"""
|
1888
|
+
Compute the `\omega`-signature of ``self``.
|
1889
|
+
|
1890
|
+
INPUT:
|
1891
|
+
|
1892
|
+
- `\omega` -- a complex number of modulus 1; this is assumed to be
|
1893
|
+
coercible to ``QQbar``
|
1894
|
+
|
1895
|
+
This is defined as the signature of the Hermitian matrix
|
1896
|
+
|
1897
|
+
.. MATH::
|
1898
|
+
|
1899
|
+
(1 - \omega) V + (1 - \omega^{-1}) V^{t},
|
1900
|
+
|
1901
|
+
where `V` is the :meth:`Seifert matrix <seifert_matrix>`,
|
1902
|
+
as explained on page 122 of [Liv1993]_.
|
1903
|
+
|
1904
|
+
According to [Con2018]_, this is also known as the
|
1905
|
+
Levine-Tristram signature, the equivariant signature or the
|
1906
|
+
Tristram-Levine signature.
|
1907
|
+
|
1908
|
+
.. SEEALSO:: :meth:`signature`, :meth:`seifert_matrix`
|
1909
|
+
|
1910
|
+
EXAMPLES::
|
1911
|
+
|
1912
|
+
sage: # needs sage.modules sage.rings.number_field
|
1913
|
+
sage: B = BraidGroup(4)
|
1914
|
+
sage: K = Knot(B([1,1,1,2,-1,2,-3,2,-3]))
|
1915
|
+
sage: omega = QQbar.zeta(3)
|
1916
|
+
sage: K.omega_signature(omega)
|
1917
|
+
-2
|
1918
|
+
"""
|
1919
|
+
from sage.rings.qqbar import QQbar
|
1920
|
+
omega = QQbar(omega)
|
1921
|
+
V = self.seifert_matrix()
|
1922
|
+
m = (1 - omega) * V + (1 - omega.conjugate()) * V.transpose()
|
1923
|
+
return ZZ.sum(j.real().sign() for j in m.eigenvalues())
|
1924
|
+
|
1925
|
+
def alexander_polynomial(self, var='t'):
|
1926
|
+
r"""
|
1927
|
+
Return the Alexander polynomial of ``self``.
|
1928
|
+
|
1929
|
+
INPUT:
|
1930
|
+
|
1931
|
+
- ``var`` -- (default: ``'t'``) the variable in the polynomial
|
1932
|
+
|
1933
|
+
EXAMPLES:
|
1934
|
+
|
1935
|
+
We begin by computing the Alexander polynomial for the
|
1936
|
+
figure-eight knot::
|
1937
|
+
|
1938
|
+
sage: # needs sage.modules
|
1939
|
+
sage: B = BraidGroup(3)
|
1940
|
+
sage: L = Link(B([1, -2, 1, -2]))
|
1941
|
+
sage: L.alexander_polynomial()
|
1942
|
+
-t^-1 + 3 - t
|
1943
|
+
|
1944
|
+
The "monster" unknot::
|
1945
|
+
|
1946
|
+
sage: L = Link([[3,1,2,4],[8,9,1,7],[5,6,7,3],[4,18,6,5],
|
1947
|
+
....: [17,19,8,18],[9,10,11,14],[10,12,13,11],
|
1948
|
+
....: [12,19,15,13],[20,16,14,15],[16,20,17,2]])
|
1949
|
+
sage: L.alexander_polynomial() # needs sage.modules
|
1950
|
+
1
|
1951
|
+
|
1952
|
+
Some additional examples::
|
1953
|
+
|
1954
|
+
sage: # needs sage.modules
|
1955
|
+
sage: B = BraidGroup(2)
|
1956
|
+
sage: L = Link(B([1]))
|
1957
|
+
sage: L.alexander_polynomial()
|
1958
|
+
1
|
1959
|
+
sage: L = Link(B.one())
|
1960
|
+
sage: L.alexander_polynomial()
|
1961
|
+
1
|
1962
|
+
sage: B = BraidGroup(3)
|
1963
|
+
sage: L = Link(B([1, 2, 1, 2]))
|
1964
|
+
sage: L.alexander_polynomial()
|
1965
|
+
t^-1 - 1 + t
|
1966
|
+
|
1967
|
+
When the Seifert surface is disconnected, the Alexander
|
1968
|
+
polynomial is defined to be `0`::
|
1969
|
+
|
1970
|
+
sage: # needs sage.modules
|
1971
|
+
sage: B = BraidGroup(4)
|
1972
|
+
sage: L = Link(B([1,3]))
|
1973
|
+
sage: L.alexander_polynomial()
|
1974
|
+
0
|
1975
|
+
|
1976
|
+
TESTS::
|
1977
|
+
|
1978
|
+
sage: # needs sage.modules
|
1979
|
+
sage: B = BraidGroup(4)
|
1980
|
+
sage: L = Link(B([-1, 3, 1, 3]))
|
1981
|
+
sage: L.alexander_polynomial()
|
1982
|
+
0
|
1983
|
+
sage: L = Link(B([1,3,1,1,3,3]))
|
1984
|
+
sage: L.alexander_polynomial()
|
1985
|
+
0
|
1986
|
+
sage: B = BraidGroup(8)
|
1987
|
+
sage: L = Link(B([-2, 4, 1, 6, 1, 4]))
|
1988
|
+
sage: L.alexander_polynomial()
|
1989
|
+
0
|
1990
|
+
|
1991
|
+
.. SEEALSO:: :meth:`conway_polynomial`
|
1992
|
+
"""
|
1993
|
+
R = LaurentPolynomialRing(ZZ, var)
|
1994
|
+
# The Alexander polynomial of disjoint links are defined to be 0
|
1995
|
+
if len(self._braid_word_components()) > 1:
|
1996
|
+
return R.zero()
|
1997
|
+
t = R.gen()
|
1998
|
+
seifert_matrix = self.seifert_matrix()
|
1999
|
+
f = (seifert_matrix - t * seifert_matrix.transpose()).determinant()
|
2000
|
+
# could we use a charpoly here ? or faster determinant ?
|
2001
|
+
if f != 0:
|
2002
|
+
exp = f.exponents()
|
2003
|
+
return t ** ((-max(exp) - min(exp)) // 2) * f
|
2004
|
+
return f
|
2005
|
+
|
2006
|
+
def conway_polynomial(self):
|
2007
|
+
"""
|
2008
|
+
Return the Conway polynomial of ``self``.
|
2009
|
+
|
2010
|
+
This is closely related to the Alexander polynomial.
|
2011
|
+
|
2012
|
+
See :wikipedia:`Alexander_polynomial` for the definition.
|
2013
|
+
|
2014
|
+
EXAMPLES::
|
2015
|
+
|
2016
|
+
sage: # needs sage.modules
|
2017
|
+
sage: B = BraidGroup(3)
|
2018
|
+
sage: L = Link(B([1, -2, 1, -2]))
|
2019
|
+
sage: L.conway_polynomial()
|
2020
|
+
-t^2 + 1
|
2021
|
+
sage: Link([[1, 5, 2, 4], [3, 9, 4, 8], [5, 1, 6, 10],
|
2022
|
+
....: [7, 3, 8, 2], [9, 7, 10, 6]])
|
2023
|
+
Link with 1 component represented by 5 crossings
|
2024
|
+
sage: _.conway_polynomial()
|
2025
|
+
2*t^2 + 1
|
2026
|
+
sage: B = BraidGroup(4)
|
2027
|
+
sage: L = Link(B([1,3]))
|
2028
|
+
sage: L.conway_polynomial()
|
2029
|
+
0
|
2030
|
+
|
2031
|
+
.. SEEALSO:: :meth:`alexander_polynomial`
|
2032
|
+
"""
|
2033
|
+
alex = self.alexander_polynomial()
|
2034
|
+
L = alex.parent()
|
2035
|
+
R = L.polynomial_ring()
|
2036
|
+
if alex == 0:
|
2037
|
+
return R.zero()
|
2038
|
+
|
2039
|
+
t = L.gen()
|
2040
|
+
alex = alex(t**2)
|
2041
|
+
exp = alex.exponents()
|
2042
|
+
alex = t**((-max(exp) - min(exp)) // 2) * alex
|
2043
|
+
|
2044
|
+
conway = R.zero()
|
2045
|
+
t_poly = R.gen()
|
2046
|
+
binom = t - ~t
|
2047
|
+
while alex:
|
2048
|
+
M = max(alex.exponents())
|
2049
|
+
coeff = alex[M]
|
2050
|
+
alex -= coeff * binom**M
|
2051
|
+
conway += coeff * t_poly**M
|
2052
|
+
return conway
|
2053
|
+
|
2054
|
+
def khovanov_polynomial(self, var1='q', var2='t', torsion='T', ring=ZZ, base_ring=None):
|
2055
|
+
r"""
|
2056
|
+
Return the Khovanov polynomial of ``self``.
|
2057
|
+
|
2058
|
+
This is the Poincaré polynomial of the Khovanov homology.
|
2059
|
+
|
2060
|
+
INPUT:
|
2061
|
+
|
2062
|
+
- ``var1`` -- (default: ``'q'``) the first variable. Its exponents
|
2063
|
+
correspond to the height of Khovanov homology
|
2064
|
+
- ``var2`` -- (default: ``'t'``) the second variable. Its exponents
|
2065
|
+
correspond to the degree of Khovanov homology
|
2066
|
+
- ``torsion`` -- (default: ``'T'``) additional variable to indicate
|
2067
|
+
the torsion of the integral homology group corresponding to the
|
2068
|
+
monomial; monomials without it correspond to torsion free ``ring``
|
2069
|
+
modules; if it appears its exponents stands for the modulus of
|
2070
|
+
the torsion
|
2071
|
+
- ``ring`` -- (default: ``ZZ``) the ring of the homology. This will
|
2072
|
+
be transferred to :meth:`khovanov_homology`
|
2073
|
+
|
2074
|
+
Here we follow the conventions used in
|
2075
|
+
`KnotInfo <https://knotinfo.math.indiana.edu/descriptions/khovanov_unreduced_integral_polynomial.html>`__
|
2076
|
+
|
2077
|
+
OUTPUT:
|
2078
|
+
|
2079
|
+
A two or three (for integral homology) variate Laurent polynomial over
|
2080
|
+
``ZZ``, more precisely an instance of
|
2081
|
+
:class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`.
|
2082
|
+
|
2083
|
+
EXAMPLES::
|
2084
|
+
|
2085
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]],[-1, -1, -1]])
|
2086
|
+
sage: K.khovanov_polynomial() # needs sage.modules
|
2087
|
+
q^-1 + q^-3 + q^-5*t^-2 + q^-7*t^-2*T^2 + q^-9*t^-3
|
2088
|
+
sage: K.khovanov_polynomial(ring=GF(2)) # needs sage.modules
|
2089
|
+
q^-1 + q^-3 + q^-5*t^-2 + q^-7*t^-2 + q^-7*t^-3 + q^-9*t^-3
|
2090
|
+
|
2091
|
+
The figure eight knot::
|
2092
|
+
|
2093
|
+
sage: L = Link([[1, 6, 2, 7], [5, 2, 6, 3], [3, 1, 4, 8], [7, 5, 8, 4]])
|
2094
|
+
sage: L.khovanov_polynomial(var1='p') # needs sage.modules
|
2095
|
+
p^5*t^2 + p^3*t^2*T^2 + p*t + p + p^-1 + p^-1*t^-1
|
2096
|
+
+ p^-3*t^-1*T^2 + p^-5*t^-2
|
2097
|
+
sage: L.khovanov_polynomial(var1='p', var2='s', ring=GF(4)) # needs sage.modules sage.rings.finite_rings
|
2098
|
+
p^5*s^2 + p^3*s^2 + p^3*s + p*s + p + p^-1 + p^-1*s^-1
|
2099
|
+
+ p^-3*s^-1 + p^-3*s^-2 + p^-5*s^-2
|
2100
|
+
|
2101
|
+
The Hopf link::
|
2102
|
+
|
2103
|
+
sage: B = BraidGroup(2)
|
2104
|
+
sage: b = B([1, 1])
|
2105
|
+
sage: K = Link(b)
|
2106
|
+
sage: K.khovanov_polynomial() # needs sage.modules
|
2107
|
+
q^6*t^2 + q^4*t^2 + q^2 + 1
|
2108
|
+
|
2109
|
+
.. SEEALSO:: :meth:`khovanov_homology`
|
2110
|
+
"""
|
2111
|
+
if base_ring:
|
2112
|
+
ring = base_ring
|
2113
|
+
from sage.misc.superseded import deprecation
|
2114
|
+
deprecation(40149, "base_ring is deprecated, use argument ring instead.")
|
2115
|
+
|
2116
|
+
ch = ring.characteristic()
|
2117
|
+
integral = False
|
2118
|
+
if ch == 0 and not ring.is_field():
|
2119
|
+
integral = True
|
2120
|
+
L = LaurentPolynomialRing(ZZ, [var1, var2, torsion])
|
2121
|
+
else:
|
2122
|
+
L = LaurentPolynomialRing(ZZ, [var1, var2])
|
2123
|
+
coeff = {}
|
2124
|
+
kh = self.khovanov_homology(ring=ring)
|
2125
|
+
from sage.rings.infinity import infinity
|
2126
|
+
for h in kh:
|
2127
|
+
for d in kh[h]:
|
2128
|
+
H = kh[h][d]
|
2129
|
+
gens = {g: g.order() for g in H.gens()}
|
2130
|
+
if integral:
|
2131
|
+
tor_count = {}
|
2132
|
+
for g, tor in gens.items():
|
2133
|
+
if tor in tor_count:
|
2134
|
+
tor_count[tor] += 1
|
2135
|
+
else:
|
2136
|
+
tor_count[tor] = 1
|
2137
|
+
for tor, ell in tor_count.items():
|
2138
|
+
if tor is infinity:
|
2139
|
+
coeff[(h, d, 0)] = ell
|
2140
|
+
else:
|
2141
|
+
coeff[(h, d, tor)] = ell
|
2142
|
+
else:
|
2143
|
+
coeff[(h, d)] = len(gens)
|
2144
|
+
return L(coeff)
|
2145
|
+
|
2146
|
+
def determinant(self):
|
2147
|
+
r"""
|
2148
|
+
Return the determinant of ``self``.
|
2149
|
+
|
2150
|
+
EXAMPLES::
|
2151
|
+
|
2152
|
+
sage: # needs sage.modules
|
2153
|
+
sage: B = BraidGroup(4)
|
2154
|
+
sage: L = Link(B([-1, 2, 1, 2]))
|
2155
|
+
sage: L.determinant()
|
2156
|
+
1
|
2157
|
+
sage: B = BraidGroup(8)
|
2158
|
+
sage: L = Link(B([2, 4, 2, 3, 1, 2]))
|
2159
|
+
sage: L.determinant()
|
2160
|
+
3
|
2161
|
+
sage: L = Link(B([1]*16 + [2,1,2,1,2,2,2,2,2,2,2,1,2,1,2,-1,2,-2]))
|
2162
|
+
sage: L.determinant()
|
2163
|
+
65
|
2164
|
+
sage: B = BraidGroup(3)
|
2165
|
+
sage: Link(B([1, 2, 1, 1, 2])).determinant()
|
2166
|
+
4
|
2167
|
+
|
2168
|
+
TESTS::
|
2169
|
+
|
2170
|
+
sage: # needs sage.modules
|
2171
|
+
sage: B = BraidGroup(3)
|
2172
|
+
sage: Link(B([1, 2, 1, -2, -1])).determinant()
|
2173
|
+
0
|
2174
|
+
|
2175
|
+
REFERENCES:
|
2176
|
+
|
2177
|
+
- Definition 6.6.3 in [Cro2004]_
|
2178
|
+
"""
|
2179
|
+
V = self.seifert_matrix()
|
2180
|
+
m = V + V.transpose()
|
2181
|
+
return Integer(abs(m.det()))
|
2182
|
+
|
2183
|
+
def is_alternating(self) -> bool:
|
2184
|
+
r"""
|
2185
|
+
Return whether the given knot diagram is alternating.
|
2186
|
+
|
2187
|
+
Alternating diagram implies every overcross is followed by an
|
2188
|
+
undercross or the vice-versa.
|
2189
|
+
|
2190
|
+
We look at the Gauss code if the sign is alternating, ``True``
|
2191
|
+
is returned else the knot is not alternating ``False`` is returned.
|
2192
|
+
|
2193
|
+
.. WARNING::
|
2194
|
+
|
2195
|
+
This does not check if a knot admits an alternating diagram
|
2196
|
+
or not. Thus, this term is used differently than in some of
|
2197
|
+
the literature, such as in Hoste-Thistlethwaite table.
|
2198
|
+
|
2199
|
+
.. NOTE::
|
2200
|
+
|
2201
|
+
Links with more than one component are considered to not
|
2202
|
+
be alternating (knots) even when such a diagram exists.
|
2203
|
+
|
2204
|
+
EXAMPLES::
|
2205
|
+
|
2206
|
+
sage: B = BraidGroup(4)
|
2207
|
+
sage: L = Link(B([-1, -1, -1, -1]))
|
2208
|
+
sage: L.is_alternating()
|
2209
|
+
False
|
2210
|
+
sage: L = Link(B([1, -2, -1, 2]))
|
2211
|
+
sage: L.is_alternating()
|
2212
|
+
False
|
2213
|
+
sage: L = Link(B([-1, 3, 1, 3, 2]))
|
2214
|
+
sage: L.is_alternating()
|
2215
|
+
False
|
2216
|
+
sage: L = Link(B([1]*16 + [2,1,2,1,2,2,2,2,2,2,2,1,2,1,2,-1,2,-2]))
|
2217
|
+
sage: L.is_alternating()
|
2218
|
+
False
|
2219
|
+
sage: L = Link(B([-1,2,-1,2]))
|
2220
|
+
sage: L.is_alternating()
|
2221
|
+
True
|
2222
|
+
|
2223
|
+
We give the `5_2` knot with an alternating diagram and a
|
2224
|
+
non-alternating diagram::
|
2225
|
+
|
2226
|
+
sage: K5_2 = Link([[1, 4, 2, 5], [3, 8, 4, 9], [5, 10, 6, 1],
|
2227
|
+
....: [7, 2, 8, 3], [9, 6, 10, 7]])
|
2228
|
+
sage: K5_2.is_alternating()
|
2229
|
+
True
|
2230
|
+
|
2231
|
+
sage: K5_2b = Link(K5_2.braid())
|
2232
|
+
sage: K5_2b.is_alternating()
|
2233
|
+
False
|
2234
|
+
|
2235
|
+
TESTS:
|
2236
|
+
|
2237
|
+
Check that :issue:`31001` is fixed::
|
2238
|
+
|
2239
|
+
sage: L = Knot([])
|
2240
|
+
sage: L.is_alternating()
|
2241
|
+
True
|
2242
|
+
"""
|
2243
|
+
if not self.is_knot():
|
2244
|
+
return False
|
2245
|
+
x = self.gauss_code()
|
2246
|
+
if not x:
|
2247
|
+
return True
|
2248
|
+
s = [Integer(i).sign() for i in x[0]]
|
2249
|
+
return (s == [(-1) ** (i + 1) for i in range(len(x[0]))]
|
2250
|
+
or s == [(-1) ** i for i in range(len(x[0]))])
|
2251
|
+
|
2252
|
+
def orientation(self):
|
2253
|
+
r"""
|
2254
|
+
Return the orientation of the crossings of the link diagram
|
2255
|
+
of ``self``.
|
2256
|
+
|
2257
|
+
EXAMPLES::
|
2258
|
+
|
2259
|
+
sage: L = Link([[1, 2, 5, 4], [3, 7, 6, 5], [4, 6, 9, 8], [7, 11, 10, 9],
|
2260
|
+
....: [8, 10, 13, 1], [11, 3, 2, 13]])
|
2261
|
+
sage: L.orientation()
|
2262
|
+
[-1, 1, -1, 1, -1, 1]
|
2263
|
+
sage: L = Link([[1, 6, 2, 7], [7, 2, 8, 3], [3, 10, 4, 11], [11, 4, 12, 5],
|
2264
|
+
....: [14, 6, 1, 5], [13, 8, 14, 9], [12, 10, 13, 9]])
|
2265
|
+
sage: L.orientation()
|
2266
|
+
[-1, -1, -1, -1, 1, -1, 1]
|
2267
|
+
sage: L = Link([[1, 3, 3, 2], [2, 5, 5, 4], [4, 7, 7, 1]])
|
2268
|
+
sage: L.orientation()
|
2269
|
+
[-1, -1, -1]
|
2270
|
+
"""
|
2271
|
+
directions = self._directions_of_edges()[0]
|
2272
|
+
orientation = []
|
2273
|
+
for C in self.pd_code():
|
2274
|
+
if C[0] == C[3] or C[2] == C[1]:
|
2275
|
+
orientation.append(-1)
|
2276
|
+
elif C[3] == C[2] or C[0] == C[1]:
|
2277
|
+
orientation.append(1)
|
2278
|
+
elif directions[C[3]] == C:
|
2279
|
+
orientation.append(-1)
|
2280
|
+
else:
|
2281
|
+
orientation.append(1)
|
2282
|
+
return orientation
|
2283
|
+
|
2284
|
+
def seifert_circles(self):
|
2285
|
+
r"""
|
2286
|
+
Return the Seifert circles from the link diagram of ``self``.
|
2287
|
+
|
2288
|
+
Seifert circles are the circles obtained by smoothing all crossings
|
2289
|
+
respecting the orientation of the segments.
|
2290
|
+
|
2291
|
+
Each Seifert circle is represented as a list of the segments
|
2292
|
+
that form it.
|
2293
|
+
|
2294
|
+
EXAMPLES::
|
2295
|
+
|
2296
|
+
sage: L = Link([[[1, -2, 3, -4, 2, -1, 4, -3]], [1, 1, -1, -1]])
|
2297
|
+
sage: L.seifert_circles()
|
2298
|
+
[[1, 7, 5, 3], [2, 6], [4, 8]]
|
2299
|
+
sage: L = Link([[[-1, 2, 3, -4, 5, -6, 7, 8, -2, -5, 6, 1, -8, -3, 4, -7]],
|
2300
|
+
....: [-1, -1, -1, -1, 1, 1, -1, 1]])
|
2301
|
+
sage: L.seifert_circles()
|
2302
|
+
[[1, 13, 9, 3, 15, 5, 11, 7], [2, 10, 6, 12], [4, 16, 8, 14]]
|
2303
|
+
sage: L = Link([[[-1, 2, -3, 4, 5, 1, -2, 6, 7, 3, -4, -7, -6, -5]],
|
2304
|
+
....: [-1, -1, -1, -1, 1, -1, 1]])
|
2305
|
+
sage: L.seifert_circles()
|
2306
|
+
[[1, 7, 3, 11, 5], [2, 8, 14, 6], [4, 12, 10], [9, 13]]
|
2307
|
+
sage: L = Link([[1, 7, 2, 6], [7, 3, 8, 2], [3, 11, 4, 10], [11, 5, 12, 4],
|
2308
|
+
....: [14, 5, 1, 6], [13, 9, 14, 8], [12, 9, 13, 10]])
|
2309
|
+
sage: L.seifert_circles()
|
2310
|
+
[[1, 7, 3, 11, 5], [2, 8, 14, 6], [4, 12, 10], [9, 13]]
|
2311
|
+
sage: L = Link([[[-1, 2, -3, 5], [4, -2, 6, -5], [-4, 1, -6, 3]],
|
2312
|
+
....: [-1, 1, 1, 1, -1, -1]])
|
2313
|
+
sage: L.seifert_circles()
|
2314
|
+
[[1, 11, 8], [2, 7, 12, 4, 5, 10], [3, 9, 6]]
|
2315
|
+
|
2316
|
+
sage: B = BraidGroup(2)
|
2317
|
+
sage: L = Link(B([1, 1, 1]))
|
2318
|
+
sage: L.seifert_circles()
|
2319
|
+
[[1, 3, 5], [2, 4, 6]]
|
2320
|
+
|
2321
|
+
TESTS:
|
2322
|
+
|
2323
|
+
Check that :issue:`25050` is solved::
|
2324
|
+
|
2325
|
+
sage: A = Link([[[1, 2, -2, -1, -3, -4, 4, 3]], [1, 1, 1, 1]])
|
2326
|
+
sage: A.seifert_circles()
|
2327
|
+
[[3], [7], [1, 5], [2, 4], [6, 8]]
|
2328
|
+
"""
|
2329
|
+
pd = self.pd_code()
|
2330
|
+
available_segments = set(flatten(pd))
|
2331
|
+
# detect looped segments. They must be their own Seifert circles
|
2332
|
+
result = [[a] for a in available_segments
|
2333
|
+
if any(C.count(a) > 1 for C in pd)]
|
2334
|
+
|
2335
|
+
# remove the looped segments from the available
|
2336
|
+
for a in result:
|
2337
|
+
available_segments.remove(a[0])
|
2338
|
+
tails, heads = self._directions_of_edges()
|
2339
|
+
while available_segments:
|
2340
|
+
a = available_segments.pop()
|
2341
|
+
if heads[a] == tails[a]:
|
2342
|
+
result.append([a])
|
2343
|
+
else:
|
2344
|
+
C = heads[a]
|
2345
|
+
par = []
|
2346
|
+
while a not in par:
|
2347
|
+
par.append(a)
|
2348
|
+
posnext = C[(C.index(a) - 1) % 4]
|
2349
|
+
if tails[posnext] == C and [posnext] not in result:
|
2350
|
+
a = posnext
|
2351
|
+
else:
|
2352
|
+
a = C[(C.index(a) + 1) % 4]
|
2353
|
+
if a in available_segments:
|
2354
|
+
available_segments.remove(a)
|
2355
|
+
C = heads[a]
|
2356
|
+
result.append(par)
|
2357
|
+
return result
|
2358
|
+
|
2359
|
+
def regions(self):
|
2360
|
+
r"""
|
2361
|
+
Return the regions from the link diagram of ``self``.
|
2362
|
+
|
2363
|
+
Regions are obtained always turning left at each crossing.
|
2364
|
+
|
2365
|
+
Then the regions are represented as a list with the segments that form
|
2366
|
+
its boundary, with a sign depending on the orientation of the segment
|
2367
|
+
as part of the boundary.
|
2368
|
+
|
2369
|
+
EXAMPLES::
|
2370
|
+
|
2371
|
+
sage: L = Link([[[-1, +2, -3, 4, +5, +1, -2, +6, +7, 3, -4, -7, -6,-5]],
|
2372
|
+
....: [-1, -1, -1, -1, 1, -1, 1]])
|
2373
|
+
sage: L.regions()
|
2374
|
+
[[14, -5, 12, -9], [13, 9], [11, 5, 1, 7, 3], [10, -3, 8, -13],
|
2375
|
+
[6, -1], [4, -11], [2, -7], [-2, -6, -14, -8], [-4, -10, -12]]
|
2376
|
+
sage: L = Link([[[1, -2, 3, -4, 2, -1, 4, -3]],[1, 1, -1, -1]])
|
2377
|
+
sage: L.regions()
|
2378
|
+
[[8, 4], [7, -4, 1], [6, -1, -3], [5, 3, -8], [2, -5, -7], [-2, -6]]
|
2379
|
+
sage: L = Link([[[-1, +2, 3, -4, 5, -6, 7, 8, -2, -5, +6, +1, -8, -3, 4, -7]],
|
2380
|
+
....: [-1, -1, -1, -1, 1, 1, -1, 1]])
|
2381
|
+
sage: L.regions()
|
2382
|
+
[[16, 8, 14, 4], [15, -4], [13, -8, 1], [12, -1, -7], [11, 7, -16, 5],
|
2383
|
+
[10, -5, -15, -3], [9, 3, -14], [6, -11], [2, -9, -13], [-2, -12, -6, -10]]
|
2384
|
+
|
2385
|
+
sage: B = BraidGroup(2)
|
2386
|
+
sage: L = Link(B([-1, -1, -1]))
|
2387
|
+
sage: L.regions()
|
2388
|
+
[[6, -5], [5, 1, 3], [4, -3], [2, -1], [-2, -6, -4]]
|
2389
|
+
sage: L = Link([[[1, -2, 3, -4], [-1, 5, -3, 2, -5, 4]],
|
2390
|
+
....: [-1, 1, 1, -1, -1]])
|
2391
|
+
sage: L.regions()
|
2392
|
+
[[10, -4, -7], [9, 7, -3], [8, 3], [6, -9, -2], [5, 2, -8, 4],
|
2393
|
+
[1, -5], [-1, -10, -6]]
|
2394
|
+
sage: L = Link([[1, 3, 3, 2], [2, 4, 4, 5], [5, 6, 6, 7], [7, 8, 8, 1]])
|
2395
|
+
sage: L.regions()
|
2396
|
+
[[-3], [-4], [-6], [-8], [7, 1, 2, 5], [-1, 8, -7, 6, -5, 4, -2, 3]]
|
2397
|
+
|
2398
|
+
.. NOTE::
|
2399
|
+
|
2400
|
+
The link diagram is assumed to have only one completely isolated
|
2401
|
+
component. This is because otherwise some regions would have
|
2402
|
+
disconnected boundary.
|
2403
|
+
|
2404
|
+
TESTS::
|
2405
|
+
|
2406
|
+
sage: B = BraidGroup(6)
|
2407
|
+
sage: L = Link(B([1, 3, 5]))
|
2408
|
+
sage: L.regions()
|
2409
|
+
Traceback (most recent call last):
|
2410
|
+
...
|
2411
|
+
NotImplementedError: can only have one isolated component
|
2412
|
+
"""
|
2413
|
+
if len(self._isolated_components()) != 1:
|
2414
|
+
raise NotImplementedError("can only have one isolated component")
|
2415
|
+
pd = self.pd_code()
|
2416
|
+
if len(pd) == 1:
|
2417
|
+
if pd[0][0] == pd[0][3]:
|
2418
|
+
return [[-pd[0][2]], [pd[0][0]], [pd[0][2], -pd[0][0]]]
|
2419
|
+
else:
|
2420
|
+
return [[pd[0][2]], [-pd[0][0]], [-pd[0][2], pd[0][0]]]
|
2421
|
+
|
2422
|
+
tails, heads = self._directions_of_edges()
|
2423
|
+
available_edges = set(flatten(pd))
|
2424
|
+
|
2425
|
+
loops = [i for i in available_edges if heads[i] == tails[i]]
|
2426
|
+
available_edges = available_edges.union({-i for i in available_edges})
|
2427
|
+
regions = []
|
2428
|
+
|
2429
|
+
for edge in loops:
|
2430
|
+
cros = heads[edge]
|
2431
|
+
if cros[3] == edge:
|
2432
|
+
regions.append([edge])
|
2433
|
+
else:
|
2434
|
+
regions.append([-edge])
|
2435
|
+
available_edges.remove(edge)
|
2436
|
+
available_edges.remove(-edge)
|
2437
|
+
available_edges = sorted(available_edges)
|
2438
|
+
|
2439
|
+
while available_edges:
|
2440
|
+
edge = available_edges.pop()
|
2441
|
+
region = []
|
2442
|
+
while edge not in region:
|
2443
|
+
region.append(edge)
|
2444
|
+
if edge > 0:
|
2445
|
+
cros = heads[edge]
|
2446
|
+
ind = cros.index(edge)
|
2447
|
+
else:
|
2448
|
+
cros = tails[-edge]
|
2449
|
+
ind = cros.index(-edge)
|
2450
|
+
next_edge = cros[(ind - 1) % 4]
|
2451
|
+
if [next_edge] in regions:
|
2452
|
+
region.append(-next_edge)
|
2453
|
+
next_edge = cros[(ind + 1) % 4]
|
2454
|
+
elif [-next_edge] in regions:
|
2455
|
+
region.append(next_edge)
|
2456
|
+
next_edge = cros[(ind + 1) % 4]
|
2457
|
+
if tails[next_edge] == cros:
|
2458
|
+
edge = next_edge
|
2459
|
+
else:
|
2460
|
+
edge = -next_edge
|
2461
|
+
if edge in available_edges:
|
2462
|
+
available_edges.remove(edge)
|
2463
|
+
regions.append(region)
|
2464
|
+
return regions
|
2465
|
+
|
2466
|
+
def remove_loops(self):
|
2467
|
+
r"""
|
2468
|
+
Return an ambient isotopic link in which all loops are removed.
|
2469
|
+
|
2470
|
+
EXAMPLES::
|
2471
|
+
|
2472
|
+
sage: b = BraidGroup(4)((3, 2, -1, -1))
|
2473
|
+
sage: L = Link(b)
|
2474
|
+
sage: L.remove_loops()
|
2475
|
+
Link with 2 components represented by 2 crossings
|
2476
|
+
sage: K4 = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]])
|
2477
|
+
sage: K3 = K4.remove_loops()
|
2478
|
+
sage: K3.pd_code()
|
2479
|
+
[[1, 7, 2, 4], [3, 1, 4, 8], [7, 3, 8, 2]]
|
2480
|
+
sage: U = Link([[1, 2, 2, 1]])
|
2481
|
+
sage: U.remove_loops()
|
2482
|
+
Link with 1 component represented by 0 crossings
|
2483
|
+
"""
|
2484
|
+
pd = self.pd_code()
|
2485
|
+
new_pd = []
|
2486
|
+
loop_crossings = []
|
2487
|
+
for cr in pd:
|
2488
|
+
if len(set(cr)) == 4:
|
2489
|
+
new_pd.append(list(cr))
|
2490
|
+
else:
|
2491
|
+
loop_crossings.append(cr)
|
2492
|
+
if not loop_crossings:
|
2493
|
+
return self
|
2494
|
+
if not new_pd:
|
2495
|
+
# trivial knot
|
2496
|
+
return type(self)([])
|
2497
|
+
new_edges = {elt for cr in new_pd for elt in cr}
|
2498
|
+
for cr in loop_crossings:
|
2499
|
+
rem = {e for e in cr if e in new_edges}
|
2500
|
+
if len(rem) == 2:
|
2501
|
+
# put remaining edges together
|
2502
|
+
a, b = sorted(rem)
|
2503
|
+
for ncr in new_pd:
|
2504
|
+
if b in ncr:
|
2505
|
+
ncr[ncr.index(b)] = a
|
2506
|
+
break
|
2507
|
+
res = type(self)(new_pd)
|
2508
|
+
return res.remove_loops()
|
2509
|
+
|
2510
|
+
@cached_method
|
2511
|
+
def mirror_image(self):
|
2512
|
+
r"""
|
2513
|
+
Return the mirror image of ``self``.
|
2514
|
+
|
2515
|
+
EXAMPLES::
|
2516
|
+
|
2517
|
+
sage: g = BraidGroup(2).gen(0)
|
2518
|
+
sage: K = Link(g^3)
|
2519
|
+
sage: K2 = K.mirror_image(); K2
|
2520
|
+
Link with 1 component represented by 3 crossings
|
2521
|
+
sage: K2.braid()
|
2522
|
+
s^-3
|
2523
|
+
|
2524
|
+
.. PLOT::
|
2525
|
+
:width: 300 px
|
2526
|
+
|
2527
|
+
g = BraidGroup(2).gen(0)
|
2528
|
+
K = Link(g**3)
|
2529
|
+
sphinx_plot(K.plot())
|
2530
|
+
|
2531
|
+
.. PLOT::
|
2532
|
+
:width: 300 px
|
2533
|
+
|
2534
|
+
g = BraidGroup(2).gen(0)
|
2535
|
+
K = Link(g**3)
|
2536
|
+
sphinx_plot(K.mirror_image().plot())
|
2537
|
+
|
2538
|
+
::
|
2539
|
+
|
2540
|
+
sage: K = Knot([[[1, -2, 3, -1, 2, -3]], [1, 1, 1]])
|
2541
|
+
sage: K2 = K.mirror_image(); K2
|
2542
|
+
Knot represented by 3 crossings
|
2543
|
+
sage: K.pd_code()
|
2544
|
+
[[4, 2, 5, 1], [2, 6, 3, 5], [6, 4, 1, 3]]
|
2545
|
+
sage: K2.pd_code()
|
2546
|
+
[[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]]
|
2547
|
+
|
2548
|
+
.. PLOT::
|
2549
|
+
:width: 300 px
|
2550
|
+
|
2551
|
+
K = Link([[[1,-2,3,-1,2,-3]],[1,1,1]])
|
2552
|
+
sphinx_plot(K.plot())
|
2553
|
+
|
2554
|
+
.. PLOT::
|
2555
|
+
:width: 300 px
|
2556
|
+
|
2557
|
+
K = Link([[[1,-2,3,-1,2,-3]],[1,1,1]])
|
2558
|
+
K2 = K.mirror_image()
|
2559
|
+
sphinx_plot(K2.plot())
|
2560
|
+
|
2561
|
+
TESTS:
|
2562
|
+
|
2563
|
+
check that :issue:`30997` is fixed::
|
2564
|
+
|
2565
|
+
sage: L = Link([[6, 2, 7, 1], [5, 13, 6, 12], [8, 3, 9, 4],
|
2566
|
+
....: [2, 13, 3, 14], [14, 8, 15, 7], [11, 17, 12, 16],
|
2567
|
+
....: [9, 18, 10, 11], [17, 10, 18, 5], [4, 16, 1, 15]]) # L9n25{0}{0} from KnotInfo
|
2568
|
+
sage: Lmm = L.mirror_image().mirror_image()
|
2569
|
+
sage: L == Lmm
|
2570
|
+
True
|
2571
|
+
"""
|
2572
|
+
# Use the braid information if it is the shortest version
|
2573
|
+
# of what we have already computed
|
2574
|
+
if self._mirror:
|
2575
|
+
return self._mirror
|
2576
|
+
|
2577
|
+
if self._braid:
|
2578
|
+
lb = len(self._braid.Tietze())
|
2579
|
+
|
2580
|
+
if self._pd_code:
|
2581
|
+
lpd = len(self.pd_code())
|
2582
|
+
else:
|
2583
|
+
lpd = float('inf')
|
2584
|
+
|
2585
|
+
if self._oriented_gauss_code:
|
2586
|
+
logc = len(self.oriented_gauss_code()[-1])
|
2587
|
+
else:
|
2588
|
+
logc = float('inf')
|
2589
|
+
|
2590
|
+
if lb <= logc and lb <= lpd:
|
2591
|
+
self._mirror = type(self)(self._braid.mirror_image())
|
2592
|
+
self._mirror._mirror = self
|
2593
|
+
return self._mirror
|
2594
|
+
|
2595
|
+
# Otherwise we fallback to the PD code
|
2596
|
+
pd = [[a[0], a[3], a[2], a[1]] for a in self.pd_code()]
|
2597
|
+
self._mirror = type(self)(pd)
|
2598
|
+
self._mirror._mirror = self
|
2599
|
+
return self._mirror
|
2600
|
+
|
2601
|
+
def reverse(self):
|
2602
|
+
r"""
|
2603
|
+
Return the reverse of ``self``. This is the link obtained from ``self``
|
2604
|
+
by reverting the orientation on all components.
|
2605
|
+
|
2606
|
+
EXAMPLES::
|
2607
|
+
|
2608
|
+
sage: K3 = Knot([[5, 2, 4, 1], [3, 6, 2, 5], [1, 4, 6, 3]])
|
2609
|
+
sage: K3r = K3.reverse(); K3r.pd_code()
|
2610
|
+
[[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]]
|
2611
|
+
sage: K3 == K3r
|
2612
|
+
True
|
2613
|
+
|
2614
|
+
a non reversable knot::
|
2615
|
+
|
2616
|
+
sage: K8_17 = Knot([[6, 1, 7, 2], [14, 7, 15, 8], [8, 4, 9, 3],
|
2617
|
+
....: [2, 14, 3, 13], [12, 6, 13, 5], [4, 10, 5, 9],
|
2618
|
+
....: [16, 11, 1, 12], [10, 15, 11, 16]])
|
2619
|
+
sage: K8_17r = K8_17.reverse()
|
2620
|
+
sage: b = K8_17.braid(); b
|
2621
|
+
s0^2*s1^-1*(s1^-1*s0)^2*s1^-1
|
2622
|
+
sage: br = K8_17r.braid(); br
|
2623
|
+
s0^-1*s1*s0^-2*s1^2*s0^-1*s1
|
2624
|
+
|
2625
|
+
sage: # needs sage.libs.braiding
|
2626
|
+
sage: b.is_conjugated(br)
|
2627
|
+
False
|
2628
|
+
sage: b == br.reverse()
|
2629
|
+
False
|
2630
|
+
sage: b.is_conjugated(br.reverse())
|
2631
|
+
True
|
2632
|
+
sage: K8_17b = Link(b)
|
2633
|
+
sage: K8_17br = K8_17b.reverse()
|
2634
|
+
sage: bbr = K8_17br.braid(); bbr
|
2635
|
+
(s1^-1*s0)^2*s1^-2*s0^2
|
2636
|
+
sage: br == bbr
|
2637
|
+
False
|
2638
|
+
sage: br.is_conjugated(bbr)
|
2639
|
+
True
|
2640
|
+
"""
|
2641
|
+
if self._reverse:
|
2642
|
+
return self._reverse
|
2643
|
+
|
2644
|
+
b = self._braid
|
2645
|
+
if b and len(b.Tietze()) <= len(self.pd_code()):
|
2646
|
+
self._reverse = type(self)(self._braid.reverse())
|
2647
|
+
self._reverse._reverse = self
|
2648
|
+
return self._reverse
|
2649
|
+
|
2650
|
+
# Otherwise we fallback to the PD code
|
2651
|
+
pd = [[a[2], a[3], a[0], a[1]] for a in self.pd_code()]
|
2652
|
+
self._reverse = type(self)(pd)
|
2653
|
+
self._reverse._reverse = self
|
2654
|
+
return self._reverse
|
2655
|
+
|
2656
|
+
def writhe(self):
|
2657
|
+
r"""
|
2658
|
+
Return the writhe of ``self``.
|
2659
|
+
|
2660
|
+
EXAMPLES::
|
2661
|
+
|
2662
|
+
sage: L = Link([[[1, -2, 3, -4, 2, -1, 4, -3]],[1, 1, -1, -1]])
|
2663
|
+
sage: L.writhe()
|
2664
|
+
0
|
2665
|
+
sage: L = Link([[[-1, 2, -3, 4, 5, 1, -2, 6, 7, 3, -4, -7, -6,-5]],
|
2666
|
+
....: [-1, -1, -1, -1, 1, -1, 1]])
|
2667
|
+
sage: L.writhe()
|
2668
|
+
-3
|
2669
|
+
sage: L = Link([[[-1, 2, 3, -4, 5, -6, 7, 8, -2, -5, 6, 1, -8, -3, 4, -7]],
|
2670
|
+
....: [-1, -1, -1, -1, 1, 1, -1, 1]])
|
2671
|
+
sage: L.writhe()
|
2672
|
+
-2
|
2673
|
+
"""
|
2674
|
+
x = self.oriented_gauss_code()
|
2675
|
+
pos = x[1].count(1)
|
2676
|
+
neg = (-1) * x[1].count(-1)
|
2677
|
+
return pos + neg
|
2678
|
+
|
2679
|
+
def jones_polynomial(self, variab=None, skein_normalization=False, algorithm='jonesrep'):
|
2680
|
+
r"""
|
2681
|
+
Return the Jones polynomial of ``self``.
|
2682
|
+
|
2683
|
+
The normalization is so that the unknot has Jones polynomial `1`.
|
2684
|
+
If ``skein_normalization`` is ``True``, the variable of the result
|
2685
|
+
is replaced by a itself to the power of `4`, so that the result
|
2686
|
+
agrees with the conventions of [Lic1997]_ (which in particular differs
|
2687
|
+
slightly from the conventions used otherwise in this class), had
|
2688
|
+
one used the conventional Kauffman bracket variable notation directly.
|
2689
|
+
|
2690
|
+
If ``variab`` is ``None`` return a polynomial in the variable `A`
|
2691
|
+
or `t`, depending on the value ``skein_normalization``. In
|
2692
|
+
particular, if ``skein_normalization`` is ``False``, return the
|
2693
|
+
result in terms of the variable `t`, also used in [Lic1997]_.
|
2694
|
+
|
2695
|
+
ALGORITHM:
|
2696
|
+
|
2697
|
+
The calculation goes through one of two possible algorithms,
|
2698
|
+
depending on the value of ``algorithm``. Possible values are
|
2699
|
+
``'jonesrep'`` which uses the Jones representation of a braid
|
2700
|
+
representation of ``self`` to compute the polynomial of the
|
2701
|
+
trace closure of the braid, and ``statesum`` which recursively
|
2702
|
+
computes the Kauffman bracket of ``self``. Depending on how the
|
2703
|
+
link is given, there might be significant time gains in using
|
2704
|
+
one over the other. When the trace closure of the braid is
|
2705
|
+
``self``, the algorithms give the same result.
|
2706
|
+
|
2707
|
+
INPUT:
|
2708
|
+
|
2709
|
+
- ``variab`` -- variable (default: ``None``); the variable in the
|
2710
|
+
resulting polynomial; if unspecified, use either a default variable
|
2711
|
+
in `\ZZ[A,A^{-1}]` or the variable `t` in the symbolic ring
|
2712
|
+
|
2713
|
+
- ``skein_normalization`` -- boolean (default: ``False``); determines
|
2714
|
+
the variable of the resulting polynomial
|
2715
|
+
|
2716
|
+
- ``algorithm`` -- string (default: ``'jonesrep'``); algorithm to use
|
2717
|
+
and can be one of the following:
|
2718
|
+
|
2719
|
+
* ``'jonesrep'`` -- use the Jones representation of the braid
|
2720
|
+
representation
|
2721
|
+
|
2722
|
+
* ``'statesum'`` -- recursively computes the Kauffman bracket
|
2723
|
+
|
2724
|
+
OUTPUT:
|
2725
|
+
|
2726
|
+
If ``skein_normalization`` if ``False``, this returns an element
|
2727
|
+
in the symbolic ring as the Jones polynomial of the link might
|
2728
|
+
have fractional powers when the link is not a knot. Otherwise the
|
2729
|
+
result is a Laurent polynomial in ``variab``.
|
2730
|
+
|
2731
|
+
EXAMPLES:
|
2732
|
+
|
2733
|
+
The unknot::
|
2734
|
+
|
2735
|
+
sage: B = BraidGroup(9)
|
2736
|
+
sage: b = B([1, 2, 3, 4, 5, 6, 7, 8])
|
2737
|
+
sage: Link(b).jones_polynomial() # needs sage.symbolic
|
2738
|
+
1
|
2739
|
+
|
2740
|
+
The "monster" unknot::
|
2741
|
+
|
2742
|
+
sage: L = Link([[3,1,2,4],[8,9,1,7],[5,6,7,3],[4,18,6,5],
|
2743
|
+
....: [17,19,8,18],[9,10,11,14],[10,12,13,11],
|
2744
|
+
....: [12,19,15,13],[20,16,14,15],[16,20,17,2]])
|
2745
|
+
sage: L.jones_polynomial() # needs sage.symbolic
|
2746
|
+
1
|
2747
|
+
|
2748
|
+
The Ochiai unknot::
|
2749
|
+
|
2750
|
+
sage: L = Link([[[1,-2,-3,-8,-12,13,-14,15,-7,-1,2,-4,10,11,-13,12,
|
2751
|
+
....: -11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]],
|
2752
|
+
....: [-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]])
|
2753
|
+
sage: L.jones_polynomial() # long time # needs sage.symbolic
|
2754
|
+
1
|
2755
|
+
|
2756
|
+
Two unlinked unknots::
|
2757
|
+
|
2758
|
+
sage: B = BraidGroup(4)
|
2759
|
+
sage: b = B([1, 3])
|
2760
|
+
sage: Link(b).jones_polynomial() # needs sage.symbolic
|
2761
|
+
-sqrt(t) - 1/sqrt(t)
|
2762
|
+
|
2763
|
+
The Hopf link::
|
2764
|
+
|
2765
|
+
sage: B = BraidGroup(2)
|
2766
|
+
sage: b = B([-1,-1])
|
2767
|
+
sage: Link(b).jones_polynomial() # needs sage.symbolic
|
2768
|
+
-1/sqrt(t) - 1/t^(5/2)
|
2769
|
+
|
2770
|
+
Different representations of the trefoil and one of its mirror::
|
2771
|
+
|
2772
|
+
sage: B = BraidGroup(2)
|
2773
|
+
sage: b = B([-1, -1, -1])
|
2774
|
+
sage: Link(b).jones_polynomial(skein_normalization=True)
|
2775
|
+
-A^-16 + A^-12 + A^-4
|
2776
|
+
sage: Link(b).jones_polynomial() # needs sage.symbolic
|
2777
|
+
1/t + 1/t^3 - 1/t^4
|
2778
|
+
sage: B = BraidGroup(3)
|
2779
|
+
sage: b = B([-1, -2, -1, -2])
|
2780
|
+
sage: Link(b).jones_polynomial(skein_normalization=True)
|
2781
|
+
-A^-16 + A^-12 + A^-4
|
2782
|
+
sage: R.<x> = LaurentPolynomialRing(GF(2))
|
2783
|
+
sage: Link(b).jones_polynomial(skein_normalization=True, variab=x)
|
2784
|
+
x^-16 + x^-12 + x^-4
|
2785
|
+
sage: B = BraidGroup(3)
|
2786
|
+
sage: b = B([1, 2, 1, 2])
|
2787
|
+
sage: Link(b).jones_polynomial(skein_normalization=True)
|
2788
|
+
A^4 + A^12 - A^16
|
2789
|
+
|
2790
|
+
`K11n42` (the mirror of the "Kinoshita-Terasaka" knot) and `K11n34`
|
2791
|
+
(the mirror of the "Conway" knot) in [KnotAtlas]_::
|
2792
|
+
|
2793
|
+
sage: B = BraidGroup(4)
|
2794
|
+
sage: K11n42 = Link(B([1, -2, 3, -2, 3, -2, -2, -1, 2, -3, -3, 2, 2]))
|
2795
|
+
sage: K11n34 = Link(B([1, 1, 2, -3, 2, -3, 1, -2, -2, -3, -3]))
|
2796
|
+
sage: bool(K11n42.jones_polynomial() == K11n34.jones_polynomial()) # needs sage.symbolic
|
2797
|
+
True
|
2798
|
+
|
2799
|
+
The two algorithms for computation give the same result when the
|
2800
|
+
trace closure of the braid representation is the link itself::
|
2801
|
+
|
2802
|
+
sage: # needs sage.symbolic
|
2803
|
+
sage: L = Link([[[-1, 2, -3, 4, 5, 1, -2, 6, 7, 3, -4, -7, -6, -5]],
|
2804
|
+
....: [-1, -1, -1, -1, 1, -1, 1]])
|
2805
|
+
sage: jonesrep = L.jones_polynomial(algorithm='jonesrep')
|
2806
|
+
sage: statesum = L.jones_polynomial(algorithm='statesum')
|
2807
|
+
sage: bool(jonesrep == statesum)
|
2808
|
+
True
|
2809
|
+
|
2810
|
+
When we have thrown away unknots so that the trace closure of the
|
2811
|
+
braid is not necessarily the link itself, this is only true up to a
|
2812
|
+
power of the Jones polynomial of the unknot::
|
2813
|
+
|
2814
|
+
sage: B = BraidGroup(3)
|
2815
|
+
sage: b = B([1])
|
2816
|
+
sage: L = Link(b)
|
2817
|
+
sage: b.components_in_closure()
|
2818
|
+
2
|
2819
|
+
sage: L.number_of_components()
|
2820
|
+
1
|
2821
|
+
sage: b.jones_polynomial() # needs sage.symbolic
|
2822
|
+
-sqrt(t) - 1/sqrt(t)
|
2823
|
+
sage: L.jones_polynomial() # needs sage.symbolic
|
2824
|
+
1
|
2825
|
+
sage: L.jones_polynomial(algorithm='statesum') # needs sage.symbolic
|
2826
|
+
1
|
2827
|
+
|
2828
|
+
TESTS::
|
2829
|
+
|
2830
|
+
sage: L = Link([])
|
2831
|
+
sage: L.jones_polynomial(algorithm='statesum') # needs sage.symbolic
|
2832
|
+
1
|
2833
|
+
|
2834
|
+
sage: L.jones_polynomial(algorithm='other')
|
2835
|
+
Traceback (most recent call last):
|
2836
|
+
...
|
2837
|
+
ValueError: bad value of algorithm
|
2838
|
+
|
2839
|
+
Check that :issue:`31001` is fixed::
|
2840
|
+
|
2841
|
+
sage: L.jones_polynomial() # needs sage.symbolic
|
2842
|
+
1
|
2843
|
+
"""
|
2844
|
+
if algorithm == 'statesum':
|
2845
|
+
poly = self._bracket()
|
2846
|
+
t = poly.parent().gens()[0]
|
2847
|
+
writhe = self.writhe()
|
2848
|
+
jones = poly * (-t)**(-3 * writhe)
|
2849
|
+
# Switch to the variable A to have the result agree with the output
|
2850
|
+
# of the jonesrep algorithm
|
2851
|
+
A = LaurentPolynomialRing(ZZ, 'A').gen()
|
2852
|
+
jones = jones(A**-1)
|
2853
|
+
|
2854
|
+
if skein_normalization:
|
2855
|
+
if variab is None:
|
2856
|
+
return jones
|
2857
|
+
else:
|
2858
|
+
return jones(variab)
|
2859
|
+
else:
|
2860
|
+
if variab is None:
|
2861
|
+
variab = 't'
|
2862
|
+
# We force the result to be in the symbolic ring because of the expand
|
2863
|
+
return jones(SR(variab)**(ZZ.one() / ZZ(4))).expand()
|
2864
|
+
elif algorithm == 'jonesrep':
|
2865
|
+
braid = self.braid()
|
2866
|
+
# Special case for the trivial knot with no crossings
|
2867
|
+
if not braid.Tietze():
|
2868
|
+
if skein_normalization:
|
2869
|
+
return LaurentPolynomialRing(ZZ, 'A').one()
|
2870
|
+
else:
|
2871
|
+
return SR.one()
|
2872
|
+
return braid.jones_polynomial(variab, skein_normalization)
|
2873
|
+
|
2874
|
+
raise ValueError("bad value of algorithm")
|
2875
|
+
|
2876
|
+
@cached_method
|
2877
|
+
def _bracket(self):
|
2878
|
+
r"""
|
2879
|
+
Return the Kaufmann bracket polynomial of the diagram of ``self``.
|
2880
|
+
|
2881
|
+
Note that this is not an invariant of the link, but of the diagram.
|
2882
|
+
In particular, it is not invariant under Reidemeister I moves.
|
2883
|
+
|
2884
|
+
EXAMPLES::
|
2885
|
+
|
2886
|
+
sage: L = Link([[[-1, 2, 3, -4, 5, -6, 7, 8, -2, -5, 6, 1, -8, -3, 4, -7]],
|
2887
|
+
....: [-1, -1, -1, -1, 1, 1, -1, 1]])
|
2888
|
+
sage: L._bracket()
|
2889
|
+
-t^-10 + 2*t^-6 - t^-2 + 2*t^2 - t^6 + t^10 - t^14
|
2890
|
+
sage: L = Link([[2, 1, 3, 4], [4, 3, 1, 2]])
|
2891
|
+
sage: L._bracket()
|
2892
|
+
-t^-4 - t^4
|
2893
|
+
"""
|
2894
|
+
t = LaurentPolynomialRing(ZZ, 't').gen()
|
2895
|
+
pd_code = self.pd_code()
|
2896
|
+
if not pd_code:
|
2897
|
+
return t.parent().one()
|
2898
|
+
if len(pd_code) == 1:
|
2899
|
+
if pd_code[0][0] == pd_code[0][3]:
|
2900
|
+
return -t**(-3)
|
2901
|
+
else:
|
2902
|
+
return -t**3
|
2903
|
+
|
2904
|
+
cross = pd_code[0]
|
2905
|
+
rest = [list(vertex) for vertex in pd_code[1:]]
|
2906
|
+
a, b, c, d = cross
|
2907
|
+
if a == d and c == b and rest:
|
2908
|
+
return (~t + t**(-5)) * Link(rest)._bracket()
|
2909
|
+
elif a == b and c == d and len(rest) > 0:
|
2910
|
+
return (t + t**5) * Link(rest)._bracket()
|
2911
|
+
elif a == d:
|
2912
|
+
for cross in rest:
|
2913
|
+
if b in cross:
|
2914
|
+
cross[cross.index(b)] = c
|
2915
|
+
return -t**(-3) * Link(rest)._bracket()
|
2916
|
+
elif a == b:
|
2917
|
+
for cross in rest:
|
2918
|
+
if c in cross:
|
2919
|
+
cross[cross.index(c)] = d
|
2920
|
+
return -t**3 * Link(rest)._bracket()
|
2921
|
+
elif c == d:
|
2922
|
+
for cross in rest:
|
2923
|
+
if b in cross:
|
2924
|
+
cross[cross.index(b)] = a
|
2925
|
+
return -t**3 * Link(rest)._bracket()
|
2926
|
+
elif c == b:
|
2927
|
+
for cross in rest:
|
2928
|
+
if d in cross:
|
2929
|
+
cross[cross.index(d)] = a
|
2930
|
+
return -t**(-3) * Link(rest)._bracket()
|
2931
|
+
else:
|
2932
|
+
rest_2 = [list(vertex) for vertex in rest]
|
2933
|
+
for cross in rest:
|
2934
|
+
if b in cross:
|
2935
|
+
cross[cross.index(b)] = a
|
2936
|
+
if c in cross:
|
2937
|
+
cross[cross.index(c)] = d
|
2938
|
+
for cross in rest_2:
|
2939
|
+
if b in cross:
|
2940
|
+
cross[cross.index(b)] = c
|
2941
|
+
if d in cross:
|
2942
|
+
cross[cross.index(d)] = a
|
2943
|
+
return t * Link(rest)._bracket() + ~t * Link(rest_2)._bracket()
|
2944
|
+
|
2945
|
+
@cached_method
|
2946
|
+
def _isolated_components(self):
|
2947
|
+
r"""
|
2948
|
+
Return the PD codes of the isolated components of ``self``.
|
2949
|
+
|
2950
|
+
Isolated components are links corresponding to subdiagrams that
|
2951
|
+
do not have any common crossing.
|
2952
|
+
|
2953
|
+
EXAMPLES::
|
2954
|
+
|
2955
|
+
sage: L = Link([[1, 1, 2, 2], [3, 3, 4, 4]])
|
2956
|
+
sage: L._isolated_components()
|
2957
|
+
[[[1, 1, 2, 2]], [[3, 3, 4, 4]]]
|
2958
|
+
"""
|
2959
|
+
G = Graph()
|
2960
|
+
for c in self.pd_code():
|
2961
|
+
G.add_vertex(tuple(c))
|
2962
|
+
V = G.vertices(sort=True)
|
2963
|
+
setV = [set(c) for c in V]
|
2964
|
+
for i in range(len(V) - 1):
|
2965
|
+
for j in range(i + 1, len(V)):
|
2966
|
+
if setV[i].intersection(setV[j]):
|
2967
|
+
G.add_edge(V[i], V[j])
|
2968
|
+
return [[list(i) for i in j]
|
2969
|
+
for j in G.connected_components(sort=False)]
|
2970
|
+
|
2971
|
+
@cached_method
|
2972
|
+
def homfly_polynomial(self, var1=None, var2=None, normalization='lm'):
|
2973
|
+
r"""
|
2974
|
+
Return the HOMFLY polynomial of ``self``.
|
2975
|
+
|
2976
|
+
The HOMFLY polynomial `P(K)` of a link `K` is a Laurent polynomial
|
2977
|
+
in two variables defined using skein relations and for the unknot
|
2978
|
+
`U`, we have `P(U) = 1`.
|
2979
|
+
|
2980
|
+
INPUT:
|
2981
|
+
|
2982
|
+
- ``var1`` -- (default: ``'L'``) the first variable. If ``normalization``
|
2983
|
+
is set to ``az`` resp. ``vz`` the default is ``a`` resp. ``v``
|
2984
|
+
- ``var2`` -- (default: ``'M'``) the second variable. If ``normalization``
|
2985
|
+
is set to ``az`` resp. ``vz`` the default is ``z``
|
2986
|
+
- ``normalization`` -- (default: ``lm``) the system of coordinates
|
2987
|
+
and can be one of the following:
|
2988
|
+
|
2989
|
+
* ``'lm'`` -- corresponding to the Skein relation
|
2990
|
+
`L\cdot P(K _+) + L^{-1}\cdot P(K _-) + M\cdot P(K _0) = 0`
|
2991
|
+
|
2992
|
+
* ``'az'`` -- corresponding to the Skein relation
|
2993
|
+
`a\cdot P(K _+) - a^{-1}\cdot P(K _-) = z \cdot P(K _0)`
|
2994
|
+
|
2995
|
+
* ``'vz'`` -- corresponding to the Skein relation
|
2996
|
+
`v^{-1}\cdot P(K _+) - v\cdot P(K _-) = z \cdot P(K _0)`
|
2997
|
+
|
2998
|
+
where `P(K _+)`, `P(K _-)` and `P(K _0)` represent the HOMFLY
|
2999
|
+
polynomials of three links that vary only in one crossing;
|
3000
|
+
that is the positive, negative, or smoothed links respectively
|
3001
|
+
|
3002
|
+
OUTPUT: a Laurent polynomial over the integers
|
3003
|
+
|
3004
|
+
.. NOTE::
|
3005
|
+
|
3006
|
+
Use the ``'az'`` normalization to agree with the data
|
3007
|
+
in [KnotAtlas]_
|
3008
|
+
|
3009
|
+
Use the ``'vz'`` normalization to agree with the data
|
3010
|
+
`KnotInfo <http://www.indiana.edu/~knotinfo/>`__.
|
3011
|
+
|
3012
|
+
EXAMPLES:
|
3013
|
+
|
3014
|
+
We give some examples::
|
3015
|
+
|
3016
|
+
sage: g = BraidGroup(2).gen(0)
|
3017
|
+
sage: K = Knot(g^5)
|
3018
|
+
sage: K.homfly_polynomial() # needs sage.libs.homfly
|
3019
|
+
L^-4*M^4 - 4*L^-4*M^2 + 3*L^-4 - L^-6*M^2 + 2*L^-6
|
3020
|
+
|
3021
|
+
The Hopf link::
|
3022
|
+
|
3023
|
+
sage: L = Link([[1,4,2,3],[4,1,3,2]])
|
3024
|
+
sage: L.homfly_polynomial('x', 'y') # needs sage.libs.homfly
|
3025
|
+
-x^-1*y + x^-1*y^-1 + x^-3*y^-1
|
3026
|
+
|
3027
|
+
Another version of the Hopf link where the orientation
|
3028
|
+
has been changed. Therefore we substitute `x \mapsto L^{-1}`
|
3029
|
+
and `y \mapsto M`::
|
3030
|
+
|
3031
|
+
sage: L = Link([[1,3,2,4], [4,2,3,1]])
|
3032
|
+
sage: L.homfly_polynomial() # needs sage.libs.homfly
|
3033
|
+
L^3*M^-1 - L*M + L*M^-1
|
3034
|
+
sage: L = Link([[1,3,2,4], [4,2,3,1]])
|
3035
|
+
sage: L.homfly_polynomial(normalization='az') # needs sage.libs.homfly
|
3036
|
+
a^3*z^-1 - a*z - a*z^-1
|
3037
|
+
|
3038
|
+
The figure-eight knot::
|
3039
|
+
|
3040
|
+
sage: L = Link([[2,5,4,1], [5,3,7,6], [6,9,1,4], [9,7,3,2]])
|
3041
|
+
sage: L.homfly_polynomial() # needs sage.libs.homfly
|
3042
|
+
-L^2 + M^2 - 1 - L^-2
|
3043
|
+
sage: L.homfly_polynomial('a', 'z', 'az') # needs sage.libs.homfly
|
3044
|
+
a^2 - z^2 - 1 + a^-2
|
3045
|
+
|
3046
|
+
The "monster" unknot::
|
3047
|
+
|
3048
|
+
sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
|
3049
|
+
....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
|
3050
|
+
....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
|
3051
|
+
sage: L.homfly_polynomial() # needs sage.libs.homfly
|
3052
|
+
1
|
3053
|
+
|
3054
|
+
Comparison with KnotInfo::
|
3055
|
+
|
3056
|
+
sage: # needs sage.libs.homfly
|
3057
|
+
sage: KI = K.get_knotinfo(mirror_version=False); KI
|
3058
|
+
<KnotInfo.K5_1: '5_1'>
|
3059
|
+
sage: K.homfly_polynomial(normalization='vz') == KI.homfly_polynomial()
|
3060
|
+
True
|
3061
|
+
|
3062
|
+
The knot `9_6`::
|
3063
|
+
|
3064
|
+
sage: # needs sage.libs.homfly
|
3065
|
+
sage: B = BraidGroup(3)
|
3066
|
+
sage: K = Knot(B([-1,-1,-1,-1,-1,-1,-2,1,-2,-2]))
|
3067
|
+
sage: K.homfly_polynomial()
|
3068
|
+
L^10*M^4 - L^8*M^6 - 3*L^10*M^2 + 4*L^8*M^4 + L^6*M^6 + L^10
|
3069
|
+
- 3*L^8*M^2 - 5*L^6*M^4 - L^8 + 7*L^6*M^2 - 3*L^6
|
3070
|
+
sage: K.homfly_polynomial('a', 'z', normalization='az')
|
3071
|
+
-a^10*z^4 + a^8*z^6 - 3*a^10*z^2 + 4*a^8*z^4 + a^6*z^6 - a^10
|
3072
|
+
+ 3*a^8*z^2 + 5*a^6*z^4 - a^8 + 7*a^6*z^2 + 3*a^6
|
3073
|
+
|
3074
|
+
TESTS:
|
3075
|
+
|
3076
|
+
This works with isolated components::
|
3077
|
+
|
3078
|
+
sage: # needs sage.libs.homfly
|
3079
|
+
sage: L = Link([[[1, -1], [2, -2]], [1, 1]])
|
3080
|
+
sage: L2 = Link([[1, 4, 2, 3], [2, 4, 1, 3]])
|
3081
|
+
sage: L2.homfly_polynomial() # not tested (:issue:`39544`)
|
3082
|
+
-L*M^-1 - L^-1*M^-1
|
3083
|
+
sage: L.homfly_polynomial()
|
3084
|
+
-L*M^-1 - L^-1*M^-1
|
3085
|
+
sage: L.homfly_polynomial(normalization='az')
|
3086
|
+
a*z^-1 - a^-1*z^-1
|
3087
|
+
sage: L2.homfly_polynomial('α', 'ζ', 'az')
|
3088
|
+
α*ζ^-1 - α^-1*ζ^-1
|
3089
|
+
sage: L.homfly_polynomial(normalization='vz')
|
3090
|
+
-v*z^-1 + v^-1*z^-1
|
3091
|
+
sage: L2.homfly_polynomial('ν', 'ζ', 'vz')
|
3092
|
+
-ν*ζ^-1 + ν^-1*ζ^-1
|
3093
|
+
|
3094
|
+
Check that :issue:`30346` is fixed::
|
3095
|
+
|
3096
|
+
sage: L = Link([])
|
3097
|
+
sage: L.homfly_polynomial() # needs sage.libs.homfly
|
3098
|
+
1
|
3099
|
+
|
3100
|
+
REFERENCES:
|
3101
|
+
|
3102
|
+
- :wikipedia:`HOMFLY_polynomial`
|
3103
|
+
- http://mathworld.wolfram.com/HOMFLYPolynomial.html
|
3104
|
+
"""
|
3105
|
+
if not var1:
|
3106
|
+
if normalization == 'az':
|
3107
|
+
var1 = 'a'
|
3108
|
+
elif normalization == 'vz':
|
3109
|
+
var1 = 'v'
|
3110
|
+
else:
|
3111
|
+
var1 = 'L'
|
3112
|
+
if not var2:
|
3113
|
+
if normalization == 'lm':
|
3114
|
+
var2 = 'M'
|
3115
|
+
else:
|
3116
|
+
var2 = 'z'
|
3117
|
+
|
3118
|
+
L = LaurentPolynomialRing(ZZ, [var1, var2])
|
3119
|
+
if len(self._isolated_components()) > 1:
|
3120
|
+
if normalization == 'lm':
|
3121
|
+
fact = L({(1, -1): -1, (-1, -1): -1})
|
3122
|
+
elif normalization == 'az':
|
3123
|
+
fact = L({(1, -1): 1, (-1, -1): -1})
|
3124
|
+
elif normalization == 'vz':
|
3125
|
+
fact = L({(1, -1): -1, (-1, -1): 1})
|
3126
|
+
else:
|
3127
|
+
raise ValueError('normalization must be either `lm`, `az` or `vz`')
|
3128
|
+
fact = fact ** (len(self._isolated_components()) - 1)
|
3129
|
+
for i in self._isolated_components():
|
3130
|
+
fact = fact * Link(i).homfly_polynomial(var1, var2, normalization)
|
3131
|
+
return fact
|
3132
|
+
s = '{}'.format(self.number_of_components())
|
3133
|
+
ogc = self.oriented_gauss_code()
|
3134
|
+
if not ogc[0]:
|
3135
|
+
return L.one()
|
3136
|
+
for comp in ogc[0]:
|
3137
|
+
s += ' {}'.format(len(comp))
|
3138
|
+
for cr in comp:
|
3139
|
+
s += ' {} {}'.format(abs(cr) - 1, sign(cr))
|
3140
|
+
for i, cr in enumerate(ogc[1]):
|
3141
|
+
s += ' {} {}'.format(i, cr)
|
3142
|
+
from sage.libs.homfly import homfly_polynomial_dict
|
3143
|
+
dic = homfly_polynomial_dict(s)
|
3144
|
+
if normalization == 'lm':
|
3145
|
+
return L(dic)
|
3146
|
+
elif normalization == 'az':
|
3147
|
+
auxdic = {}
|
3148
|
+
for a in dic:
|
3149
|
+
if (a[0] + a[1]) % 4 == 0:
|
3150
|
+
auxdic[a] = dic[a]
|
3151
|
+
else:
|
3152
|
+
auxdic[a] = -dic[a]
|
3153
|
+
if self.number_of_components() % 2:
|
3154
|
+
return L(auxdic)
|
3155
|
+
else:
|
3156
|
+
return -L(auxdic)
|
3157
|
+
elif normalization == 'vz':
|
3158
|
+
h_az = self.homfly_polynomial(var1=var1, var2=var2, normalization='az')
|
3159
|
+
a, z = h_az.parent().gens()
|
3160
|
+
v = ~a
|
3161
|
+
return h_az.subs({a: v})
|
3162
|
+
else:
|
3163
|
+
raise ValueError('normalization must be either `lm`, `az` or `vz`')
|
3164
|
+
|
3165
|
+
def links_gould_polynomial(self, varnames='t0, t1'):
|
3166
|
+
r"""
|
3167
|
+
Return the Links-Gould polynomial of ``self``. See [MW2012]_, section 3
|
3168
|
+
and references given there. See also the docstring of
|
3169
|
+
:meth:`~sage.groups.braid.Braid.links_gould_polynomial`.
|
3170
|
+
|
3171
|
+
INPUT:
|
3172
|
+
|
3173
|
+
- ``varnames`` -- string (default: ``'t0, t1'``)
|
3174
|
+
|
3175
|
+
OUTPUT: a Laurent polynomial in the given variable names
|
3176
|
+
|
3177
|
+
EXAMPLES::
|
3178
|
+
|
3179
|
+
sage: Hopf = Link([[1, 3, 2, 4], [4, 2, 3, 1]])
|
3180
|
+
sage: Hopf.links_gould_polynomial() # needs sage.libs.singular
|
3181
|
+
-1 + t1^-1 + t0^-1 - t0^-1*t1^-1
|
3182
|
+
"""
|
3183
|
+
return self.braid().links_gould_polynomial(varnames=varnames)
|
3184
|
+
|
3185
|
+
def _coloring_matrix(self, n=None):
|
3186
|
+
r"""
|
3187
|
+
Return the coloring matrix of ``self``.
|
3188
|
+
|
3189
|
+
The coloring matrix is a matrix over a prime field
|
3190
|
+
whose right kernel gives the colorings of the diagram.
|
3191
|
+
|
3192
|
+
INPUT:
|
3193
|
+
|
3194
|
+
- ``n`` -- the number of colors to consider (if omitted the
|
3195
|
+
value of the determinant of ``self`` will be taken)
|
3196
|
+
|
3197
|
+
OUTPUT: a matrix over the residue class ring of integers modulo ``n``
|
3198
|
+
|
3199
|
+
EXAMPLES::
|
3200
|
+
|
3201
|
+
sage: # needs sage.libs.pari sage.modules
|
3202
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]], [1, 1, 1]])
|
3203
|
+
sage: K._coloring_matrix(3)
|
3204
|
+
[2 2 2]
|
3205
|
+
[2 2 2]
|
3206
|
+
[2 2 2]
|
3207
|
+
sage: K8 = Knot([[[1, -2, 4, -3, 2, -1, 3, -4]], [1, 1, -1, -1]])
|
3208
|
+
sage: K8._coloring_matrix(4)
|
3209
|
+
[2 0 3 3]
|
3210
|
+
[3 3 2 0]
|
3211
|
+
[0 3 3 2]
|
3212
|
+
[3 2 0 3]
|
3213
|
+
|
3214
|
+
REFERENCES:
|
3215
|
+
|
3216
|
+
- :wikipedia:`Fox_n-coloring`
|
3217
|
+
"""
|
3218
|
+
if not n:
|
3219
|
+
n = self.determinant()
|
3220
|
+
from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
|
3221
|
+
R = IntegerModRing(n)
|
3222
|
+
arcs = self.arcs(presentation='pd')
|
3223
|
+
di = len(arcs)
|
3224
|
+
M = matrix(R, di, di)
|
3225
|
+
crossings = self.pd_code()
|
3226
|
+
for i in range(di):
|
3227
|
+
crossing = crossings[i]
|
3228
|
+
for j in range(di):
|
3229
|
+
arc = arcs[j]
|
3230
|
+
if crossing[3] in arc:
|
3231
|
+
M[i, j] += 2
|
3232
|
+
if crossing[0] in arc:
|
3233
|
+
M[i, j] -= 1
|
3234
|
+
if crossing[2] in arc:
|
3235
|
+
M[i, j] -= 1
|
3236
|
+
return M
|
3237
|
+
|
3238
|
+
def is_colorable(self, n=None) -> bool:
|
3239
|
+
r"""
|
3240
|
+
Return whether the link is ``n``-colorable.
|
3241
|
+
|
3242
|
+
A link is ``n``-colorable if its arcs can be painted with
|
3243
|
+
``n`` colours, labeled from ``0`` to ``n - 1``, in such a way
|
3244
|
+
that at any crossing, the average of the indices of the
|
3245
|
+
undercrossings equals twice the index of the overcrossing.
|
3246
|
+
|
3247
|
+
INPUT:
|
3248
|
+
|
3249
|
+
- ``n`` -- the number of colors to consider (if omitted the
|
3250
|
+
value of the determinant of ``self`` will be taken)
|
3251
|
+
|
3252
|
+
EXAMPLES:
|
3253
|
+
|
3254
|
+
We show that the trefoil knot is 3-colorable::
|
3255
|
+
|
3256
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]], [1, 1, 1]])
|
3257
|
+
sage: K.is_colorable(3) # needs sage.libs.pari sage.modules
|
3258
|
+
True
|
3259
|
+
|
3260
|
+
But the figure eight knot is not::
|
3261
|
+
|
3262
|
+
sage: K8 = Link([[[1, -2, 4, -3, 2, -1, 3, -4]], [1, 1, -1, -1]])
|
3263
|
+
sage: K8.is_colorable(3) # needs sage.libs.pari sage.modules
|
3264
|
+
False
|
3265
|
+
|
3266
|
+
But it is colorable with respect to the value of its determinant::
|
3267
|
+
|
3268
|
+
sage: K8.determinant()
|
3269
|
+
5
|
3270
|
+
sage: K8.is_colorable()
|
3271
|
+
True
|
3272
|
+
|
3273
|
+
An examples with non prime determinant::
|
3274
|
+
|
3275
|
+
sage: K = Knots().from_table(6, 1)
|
3276
|
+
sage: K.determinant()
|
3277
|
+
9
|
3278
|
+
sage: K.is_colorable()
|
3279
|
+
True
|
3280
|
+
|
3281
|
+
REFERENCES:
|
3282
|
+
|
3283
|
+
- :wikipedia:`Fox_n-coloring`
|
3284
|
+
|
3285
|
+
- Chapter 3 of [Liv1993]_
|
3286
|
+
|
3287
|
+
.. SEEALSO:: :meth:`colorings` and :meth:`coloring_maps`
|
3288
|
+
"""
|
3289
|
+
M = self._coloring_matrix(n=n)
|
3290
|
+
if M.base_ring().is_field():
|
3291
|
+
return self._coloring_matrix(n=n).nullity() > 1
|
3292
|
+
else:
|
3293
|
+
# nullity is not implemented in this case
|
3294
|
+
return M.right_kernel_matrix().dimensions()[0] > 1
|
3295
|
+
|
3296
|
+
def colorings(self, n=None):
|
3297
|
+
r"""
|
3298
|
+
Return the ``n``-colorings of ``self``.
|
3299
|
+
|
3300
|
+
INPUT:
|
3301
|
+
|
3302
|
+
- ``n`` -- the number of colors to consider (if omitted the value
|
3303
|
+
of the determinant of ``self`` will be taken). Note that there
|
3304
|
+
are no colorings if n is coprime to the determinant of ``self``
|
3305
|
+
|
3306
|
+
OUTPUT:
|
3307
|
+
|
3308
|
+
a list with the colorings. Each coloring is represented as
|
3309
|
+
a dictionary that maps a tuple of the edges forming each arc
|
3310
|
+
(as in the PD code) to the index of the corresponding color.
|
3311
|
+
|
3312
|
+
EXAMPLES::
|
3313
|
+
|
3314
|
+
sage: K = Link([[[1, -2, 3, -1, 2, -3]], [1, 1, 1]])
|
3315
|
+
sage: K.colorings(3) # needs sage.libs.pari sage.modules
|
3316
|
+
[{(1, 2): 0, (3, 4): 1, (5, 6): 2},
|
3317
|
+
{(1, 2): 0, (3, 4): 2, (5, 6): 1},
|
3318
|
+
{(1, 2): 1, (3, 4): 0, (5, 6): 2},
|
3319
|
+
{(1, 2): 1, (3, 4): 2, (5, 6): 0},
|
3320
|
+
{(1, 2): 2, (3, 4): 0, (5, 6): 1},
|
3321
|
+
{(1, 2): 2, (3, 4): 1, (5, 6): 0}]
|
3322
|
+
sage: K.pd_code()
|
3323
|
+
[[4, 2, 5, 1], [2, 6, 3, 5], [6, 4, 1, 3]]
|
3324
|
+
sage: K.arcs('pd')
|
3325
|
+
[[1, 2], [3, 4], [5, 6]]
|
3326
|
+
|
3327
|
+
Note that ``n`` is not the number of different colors to be used. It
|
3328
|
+
can be looked upon the size of the color palette::
|
3329
|
+
|
3330
|
+
sage: K = Knots().from_table(9, 15)
|
3331
|
+
sage: cols = K.colorings(13); len(cols)
|
3332
|
+
156
|
3333
|
+
sage: max(cols[0].values())
|
3334
|
+
12
|
3335
|
+
sage: max(cols[13].values())
|
3336
|
+
9
|
3337
|
+
|
3338
|
+
REFERENCES:
|
3339
|
+
|
3340
|
+
- :wikipedia:`Fox_n-coloring`
|
3341
|
+
|
3342
|
+
- Chapter 3 of [Liv1993]_
|
3343
|
+
|
3344
|
+
.. SEEALSO:: :meth:`is_colorable` and :meth:`coloring_maps`
|
3345
|
+
"""
|
3346
|
+
from sage.modules.free_module import FreeModule
|
3347
|
+
M = self._coloring_matrix(n=n)
|
3348
|
+
KM = M.right_kernel_matrix()
|
3349
|
+
F = FreeModule(M.base_ring(), KM.dimensions()[0])
|
3350
|
+
K = [v * KM for v in F]
|
3351
|
+
res = set()
|
3352
|
+
arcs = self.arcs('pd')
|
3353
|
+
for coloring in K:
|
3354
|
+
colors = sorted(set(coloring))
|
3355
|
+
if len(colors) >= 2:
|
3356
|
+
res.add(tuple(coloring))
|
3357
|
+
return [{tuple(arc): col for arc, col in zip(arcs, c)}
|
3358
|
+
for c in sorted(res)]
|
3359
|
+
|
3360
|
+
def coloring_maps(self, n=None, finitely_presented=False):
|
3361
|
+
r"""
|
3362
|
+
Return the `n`-coloring maps of ``self``.
|
3363
|
+
|
3364
|
+
These are group homomorphisms from the fundamental group of
|
3365
|
+
``self`` to the `n`-th dihedral group.
|
3366
|
+
|
3367
|
+
INPUT:
|
3368
|
+
|
3369
|
+
- ``n`` -- the number of colors to consider (if omitted the value
|
3370
|
+
of the determinant of ``self`` will be taken). Note that there
|
3371
|
+
are no coloring maps if n is coprime to the determinant of ``self``
|
3372
|
+
|
3373
|
+
- ``finitely_presented`` -- boolean (default: ``False``); whether to
|
3374
|
+
choose the dihedral groups as finitely presented groups. If not set
|
3375
|
+
to ``True`` they are represented as permutation groups.
|
3376
|
+
|
3377
|
+
OUTPUT:
|
3378
|
+
|
3379
|
+
a list of group homomporhisms from the fundamental group of ``self``
|
3380
|
+
to the `n`-th dihedral group (represented according to the key
|
3381
|
+
argument ``finitely_presented``).
|
3382
|
+
|
3383
|
+
EXAMPLES::
|
3384
|
+
|
3385
|
+
sage: L5a1_1 = Link([[8, 2, 9, 1], [10, 7, 5, 8], [4, 10, 1, 9],
|
3386
|
+
....: [2, 5, 3, 6], [6, 3, 7, 4]])
|
3387
|
+
sage: L5a1_1.determinant()
|
3388
|
+
8
|
3389
|
+
sage: L5a1_1.coloring_maps(2)
|
3390
|
+
[Group morphism:
|
3391
|
+
From: Finitely presented group < x0, x1, x2, x3, x4 | x4*x1*x0^-1*x1^-1, x0*x4^-1*x3^-1*x4, x2*x0*x1^-1*x0^-1, x1*x3^-1*x2^-1*x3, x3*x2^-1*x4^-1*x2 >
|
3392
|
+
To: Dihedral group of order 4 as a permutation group,
|
3393
|
+
Group morphism:
|
3394
|
+
From: Finitely presented group < x0, x1, x2, x3, x4 | x4*x1*x0^-1*x1^-1, x0*x4^-1*x3^-1*x4, x2*x0*x1^-1*x0^-1, x1*x3^-1*x2^-1*x3, x3*x2^-1*x4^-1*x2 >
|
3395
|
+
To: Dihedral group of order 4 as a permutation group]
|
3396
|
+
sage: col_maps = L5a1_1.coloring_maps(4); len(col_maps)
|
3397
|
+
12
|
3398
|
+
sage: col_maps = L5a1_1.coloring_maps(5); len(col_maps)
|
3399
|
+
0
|
3400
|
+
sage: col_maps = L5a1_1.coloring_maps(12); len(col_maps)
|
3401
|
+
36
|
3402
|
+
sage: col_maps = L5a1_1.coloring_maps(); len(col_maps)
|
3403
|
+
56
|
3404
|
+
|
3405
|
+
applying the map::
|
3406
|
+
|
3407
|
+
sage: cm1 = col_maps[0]
|
3408
|
+
sage: gs = L5a1_1.fundamental_group().gens()
|
3409
|
+
sage: d = cm1(gs[0]); d
|
3410
|
+
(1,8)(2,7)(3,6)(4,5)
|
3411
|
+
sage: d.parent()
|
3412
|
+
Dihedral group of order 16 as a permutation group
|
3413
|
+
|
3414
|
+
using the finitely presented dihedral group::
|
3415
|
+
|
3416
|
+
sage: col_maps = L5a1_1.coloring_maps(2, finitely_presented=True)
|
3417
|
+
sage: d = col_maps[0](gs[1]); d
|
3418
|
+
b*a
|
3419
|
+
sage: d.parent()
|
3420
|
+
Finitely presented group < a, b | a^2, b^2, (a*b)^2 >
|
3421
|
+
|
3422
|
+
REFERENCES:
|
3423
|
+
|
3424
|
+
- :wikipedia:`Fox_n-coloring`
|
3425
|
+
|
3426
|
+
- Chapter 3 of [Liv1993]_
|
3427
|
+
|
3428
|
+
.. SEEALSO:: :meth:`is_colorable` and :meth:`colorings`
|
3429
|
+
"""
|
3430
|
+
if not n:
|
3431
|
+
n = self.determinant()
|
3432
|
+
|
3433
|
+
if finitely_presented:
|
3434
|
+
from sage.groups.finitely_presented_named import DihedralPresentation
|
3435
|
+
D = DihedralPresentation(n)
|
3436
|
+
else:
|
3437
|
+
from sage.groups.perm_gps.permgroup_named import DihedralGroup
|
3438
|
+
D = DihedralGroup(n)
|
3439
|
+
|
3440
|
+
a, b = D.gens()
|
3441
|
+
gr = self.fundamental_group()
|
3442
|
+
cols = self.colorings(n=n)
|
3443
|
+
maps = []
|
3444
|
+
for c in cols:
|
3445
|
+
t = list(c.values())
|
3446
|
+
ims = [b * a**i for i in t]
|
3447
|
+
maps.append(gr.hom(ims))
|
3448
|
+
return maps
|
3449
|
+
|
3450
|
+
def plot(self, gap=0.1, component_gap=0.5, solver=None,
|
3451
|
+
color='blue', **kwargs):
|
3452
|
+
r"""
|
3453
|
+
Plot ``self``.
|
3454
|
+
|
3455
|
+
INPUT:
|
3456
|
+
|
3457
|
+
- ``gap`` -- (default: 0.1) the size of the blank gap left for
|
3458
|
+
the crossings
|
3459
|
+
|
3460
|
+
- ``component_gap`` -- (default: 0.5) the gap between isolated
|
3461
|
+
components
|
3462
|
+
|
3463
|
+
- ``solver`` -- the linear solver to use, see
|
3464
|
+
:class:`~sage.numerical.mip.MixedIntegerLinearProgram`
|
3465
|
+
|
3466
|
+
- ``color`` -- string (default: ``'blue'``); a color or a coloring (as
|
3467
|
+
returned by :meth:`colorings`
|
3468
|
+
|
3469
|
+
The usual keywords for plots can be used here too.
|
3470
|
+
|
3471
|
+
EXAMPLES:
|
3472
|
+
|
3473
|
+
We construct the simplest version of the unknot::
|
3474
|
+
|
3475
|
+
sage: L = Link([[2, 1, 1, 2]])
|
3476
|
+
sage: L.plot() # needs sage.plot
|
3477
|
+
Graphics object consisting of ... graphics primitives
|
3478
|
+
|
3479
|
+
.. PLOT::
|
3480
|
+
:width: 300 px
|
3481
|
+
|
3482
|
+
B = BraidGroup(2)
|
3483
|
+
L = Link([[2, 1, 1, 2]])
|
3484
|
+
sphinx_plot(L.plot())
|
3485
|
+
|
3486
|
+
We construct a more interesting example of the unknot::
|
3487
|
+
|
3488
|
+
sage: L = Link([[2, 1, 4, 5], [3, 5, 6, 7], [4, 1, 9, 6], [9, 2, 3, 7]])
|
3489
|
+
sage: L.plot() # needs sage.plot
|
3490
|
+
Graphics object consisting of ... graphics primitives
|
3491
|
+
|
3492
|
+
.. PLOT::
|
3493
|
+
:width: 300 px
|
3494
|
+
|
3495
|
+
L = Link([[2,1,4,5], [3,5,6,7], [4,1,9,6], [9,2,3,7]])
|
3496
|
+
sphinx_plot(L.plot())
|
3497
|
+
|
3498
|
+
The "monster" unknot::
|
3499
|
+
|
3500
|
+
sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
|
3501
|
+
....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
|
3502
|
+
....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
|
3503
|
+
sage: L.plot() # needs sage.plot
|
3504
|
+
Graphics object consisting of ... graphics primitives
|
3505
|
+
|
3506
|
+
.. PLOT::
|
3507
|
+
:width: 300 px
|
3508
|
+
|
3509
|
+
L = Link([[3,1,2,4],[8,9,1,7],[5,6,7,3],[4,18,6,5],
|
3510
|
+
[17,19,8,18],[9,10,11,14],[10,12,13,11],
|
3511
|
+
[12,19,15,13],[20,16,14,15],[16,20,17,2]])
|
3512
|
+
sphinx_plot(L.plot())
|
3513
|
+
|
3514
|
+
The Ochiai unknot::
|
3515
|
+
|
3516
|
+
sage: L = Link([[[1,-2,-3,-8,-12,13,-14,15,-7,-1,2,-4,10,11,-13,12,
|
3517
|
+
....: -11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]],
|
3518
|
+
....: [-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]])
|
3519
|
+
sage: L.plot() # needs sage.plot
|
3520
|
+
Graphics object consisting of ... graphics primitives
|
3521
|
+
|
3522
|
+
.. PLOT::
|
3523
|
+
:width: 300 px
|
3524
|
+
|
3525
|
+
L = Link([[[1,-2,-3,-8,-12,13,-14,15,-7,-1,2,-4,10,11,-13,12,
|
3526
|
+
-11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]],
|
3527
|
+
[-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]])
|
3528
|
+
sphinx_plot(L.plot())
|
3529
|
+
|
3530
|
+
One of the representations of the trefoil knot::
|
3531
|
+
|
3532
|
+
sage: L = Link([[1, 5, 2, 4], [5, 3, 6, 2], [3, 1, 4, 6]])
|
3533
|
+
sage: L.plot() # needs sage.plot
|
3534
|
+
Graphics object consisting of 14 graphics primitives
|
3535
|
+
|
3536
|
+
.. PLOT::
|
3537
|
+
:width: 300 px
|
3538
|
+
|
3539
|
+
L = Link([[1, 5, 2, 4], [5, 3, 6, 2], [3, 1, 4, 6]])
|
3540
|
+
sphinx_plot(L.plot())
|
3541
|
+
|
3542
|
+
The figure-eight knot::
|
3543
|
+
|
3544
|
+
sage: L = Link([[2, 1, 4, 5], [5, 6, 7, 3], [6, 4, 1, 9], [9, 2, 3, 7]])
|
3545
|
+
sage: L.plot() # needs sage.plot
|
3546
|
+
Graphics object consisting of ... graphics primitives
|
3547
|
+
|
3548
|
+
.. PLOT::
|
3549
|
+
:width: 300 px
|
3550
|
+
|
3551
|
+
L = Link([[2,1,4,5], [5,6,7,3], [6,4,1,9], [9,2,3,7]])
|
3552
|
+
sphinx_plot(L.plot())
|
3553
|
+
|
3554
|
+
The knot `K11n121` in [KnotAtlas]_::
|
3555
|
+
|
3556
|
+
sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13],
|
3557
|
+
....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7],
|
3558
|
+
....: [22,18,1,17], [8,19,9,20], [21,14,22,15]])
|
3559
|
+
sage: L.plot() # needs sage.plot
|
3560
|
+
Graphics object consisting of ... graphics primitives
|
3561
|
+
|
3562
|
+
.. PLOT::
|
3563
|
+
:width: 300 px
|
3564
|
+
|
3565
|
+
L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13],
|
3566
|
+
[18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7],
|
3567
|
+
[22,18,1,17], [8,19,9,20], [21,14,22,15]])
|
3568
|
+
sphinx_plot(L.plot())
|
3569
|
+
|
3570
|
+
One of the representations of the Hopf link::
|
3571
|
+
|
3572
|
+
sage: L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
3573
|
+
sage: L.plot() # needs sage.plot
|
3574
|
+
Graphics object consisting of ... graphics primitives
|
3575
|
+
|
3576
|
+
.. PLOT::
|
3577
|
+
:width: 300 px
|
3578
|
+
|
3579
|
+
L = Link([[1, 4, 2, 3], [4, 1, 3, 2]])
|
3580
|
+
sphinx_plot(L.plot())
|
3581
|
+
|
3582
|
+
Plotting links with multiple isolated components::
|
3583
|
+
|
3584
|
+
sage: L = Link([[[-1, 2, -3, 1, -2, 3], [4, -5, 6, -4, 5, -6]],
|
3585
|
+
....: [1, 1, 1, 1, 1, 1]])
|
3586
|
+
sage: L.plot() # needs sage.plot
|
3587
|
+
Graphics object consisting of ... graphics primitives
|
3588
|
+
|
3589
|
+
.. PLOT::
|
3590
|
+
:width: 300 px
|
3591
|
+
|
3592
|
+
L = Link([[[-1,2,-3,1,-2,3], [4,-5,6,-4,5,-6]], [1,1,1,1,1,1]])
|
3593
|
+
sphinx_plot(L.plot())
|
3594
|
+
|
3595
|
+
If a coloring is passed, the different arcs are plotted with
|
3596
|
+
the corresponding colors (see :meth:`colorings`)::
|
3597
|
+
|
3598
|
+
sage: B = BraidGroup(4)
|
3599
|
+
sage: b = B([1,2,3,1,2,-1,-3,2,3])
|
3600
|
+
sage: L = Link(b)
|
3601
|
+
sage: L.plot(color=L.colorings()[0]) # needs sage.plot
|
3602
|
+
Graphics object consisting of ... graphics primitives
|
3603
|
+
|
3604
|
+
.. PLOT::
|
3605
|
+
:width: 300 px
|
3606
|
+
|
3607
|
+
B = BraidGroup(4)
|
3608
|
+
b = B([1, 2, 3, 1, 2, -1, -3, 2, 3])
|
3609
|
+
L = Link(b)
|
3610
|
+
sphinx_plot(L.plot(color=L.colorings()[0]))
|
3611
|
+
|
3612
|
+
TESTS:
|
3613
|
+
|
3614
|
+
Check that :issue:`20315` is fixed::
|
3615
|
+
|
3616
|
+
sage: # needs sage.plot
|
3617
|
+
sage: L = Link([[2,1,4,5], [5,6,7,3], [6,4,1,9], [9,2,3,7]])
|
3618
|
+
sage: L.plot(solver='GLPK')
|
3619
|
+
Graphics object consisting of ... graphics primitives
|
3620
|
+
sage: L.plot(solver='Coin') # optional - sage_numerical_backends_coin
|
3621
|
+
Graphics object consisting of ... graphics primitives
|
3622
|
+
sage: L.plot(solver='CPLEX') # optional - CPLEX
|
3623
|
+
Graphics object consisting of ... graphics primitives
|
3624
|
+
sage: L.plot(solver='Gurobi') # optional - Gurobi
|
3625
|
+
Graphics object consisting of ... graphics primitives
|
3626
|
+
"""
|
3627
|
+
pd_code = self.pd_code()
|
3628
|
+
if type(color) is not dict:
|
3629
|
+
coloring = {int(i): color for i in set(flatten(pd_code))}
|
3630
|
+
else:
|
3631
|
+
from sage.plot.colors import rainbow
|
3632
|
+
ncolors = max([int(i) for i in color.values()]) + 1
|
3633
|
+
arcs = self.arcs()
|
3634
|
+
rainb = rainbow(ncolors)
|
3635
|
+
coloring = {int(i): rainb[color[tuple(j)]] for j in arcs for i in j}
|
3636
|
+
comp = self._isolated_components()
|
3637
|
+
# Handle isolated components individually
|
3638
|
+
if len(comp) > 1:
|
3639
|
+
L1 = Link(comp[0])
|
3640
|
+
L2 = Link(flatten(comp[1:], max_level=1))
|
3641
|
+
P1 = L1.plot(gap, **kwargs)
|
3642
|
+
P2 = L2.plot(gap, **kwargs)
|
3643
|
+
xtra = P1.get_minmax_data()['xmax'] + component_gap - P2.get_minmax_data()['xmin']
|
3644
|
+
for P in P2:
|
3645
|
+
if hasattr(P, 'path'):
|
3646
|
+
for p in P.path[0]:
|
3647
|
+
p[0] += xtra
|
3648
|
+
for p in P.vertices:
|
3649
|
+
p[0] += xtra
|
3650
|
+
else:
|
3651
|
+
P.xdata = [p + xtra for p in P.xdata]
|
3652
|
+
return P1 + P2
|
3653
|
+
|
3654
|
+
if 'axes' not in kwargs:
|
3655
|
+
kwargs['axes'] = False
|
3656
|
+
if 'aspect_ratio' not in kwargs:
|
3657
|
+
kwargs['aspect_ratio'] = 1
|
3658
|
+
|
3659
|
+
from sage.plot.line import line
|
3660
|
+
from sage.plot.bezier_path import bezier_path
|
3661
|
+
from sage.plot.circle import circle
|
3662
|
+
|
3663
|
+
# Special case for the unknot
|
3664
|
+
if not pd_code:
|
3665
|
+
return circle((0, 0), ZZ.one() / ZZ(2), color=color, **kwargs)
|
3666
|
+
|
3667
|
+
# The idea is the same followed in spherogram, but using MLP instead of
|
3668
|
+
# network flows.
|
3669
|
+
# We start by computing a way to bend the edges left or right
|
3670
|
+
# such that the resulting regions are in fact closed regions
|
3671
|
+
# with straight angles, and using the minimal number of bends.
|
3672
|
+
regions = sorted(self.regions(), key=len)
|
3673
|
+
edges = list(set(flatten(pd_code)))
|
3674
|
+
edges.sort()
|
3675
|
+
MLP = MixedIntegerLinearProgram(maximization=False, solver=solver)
|
3676
|
+
# v will be the list of variables in the MLP problem. There will be
|
3677
|
+
# two variables for each edge counting the number of bendings needed.
|
3678
|
+
# The one with even index corresponds to the flow of this number from
|
3679
|
+
# the left-hand-side region to the right-hand-side region if the edge
|
3680
|
+
# is positive oriented. The one with odd index corresponds to the
|
3681
|
+
# flow in the opposite direction. For a negative oriented edge the
|
3682
|
+
# same is true but with exchanged directions. At the end, since we
|
3683
|
+
# are minimizing the total, only one of each will be nonzero.
|
3684
|
+
v = MLP.new_variable(nonnegative=True, integer=True)
|
3685
|
+
|
3686
|
+
def flow_from_source(e):
|
3687
|
+
r"""
|
3688
|
+
Return the flow variable from the source.
|
3689
|
+
"""
|
3690
|
+
if e > 0:
|
3691
|
+
return v[2 * edges.index(e)]
|
3692
|
+
else:
|
3693
|
+
return v[2 * edges.index(-e) + 1]
|
3694
|
+
|
3695
|
+
def flow_to_sink(e):
|
3696
|
+
r"""
|
3697
|
+
Return the flow variable to the sink.
|
3698
|
+
"""
|
3699
|
+
return flow_from_source(-e)
|
3700
|
+
|
3701
|
+
# one condition for each region
|
3702
|
+
lr = len(regions)
|
3703
|
+
for i in range(lr):
|
3704
|
+
r = regions[i]
|
3705
|
+
if i < lr - 1:
|
3706
|
+
# capacity of interior region, sink if positive, source if negative
|
3707
|
+
capacity = len(r) - 4
|
3708
|
+
else:
|
3709
|
+
# capacity of exterior region, only sink (added to fix :issue:`37587`).
|
3710
|
+
capacity = len(r) + 4
|
3711
|
+
flow = sum(flow_to_sink(e) - flow_from_source(e) for e in r)
|
3712
|
+
MLP.add_constraint(flow == capacity) # exterior region only sink
|
3713
|
+
|
3714
|
+
MLP.set_objective(MLP.sum(v.values()))
|
3715
|
+
MLP.solve()
|
3716
|
+
# we store the result in a vector s packing right bends as negative left ones
|
3717
|
+
values = MLP.get_values(v, convert=ZZ, tolerance=1e-3)
|
3718
|
+
s = [values[2 * i] - values[2 * i + 1] for i in range(len(edges))]
|
3719
|
+
# segments represents the different parts of the previous edges after bending
|
3720
|
+
segments = {e: [(e, i) for i in range(abs(s[edges.index(e)]) + 1)]
|
3721
|
+
for e in edges}
|
3722
|
+
pieces = {tuple(i): [i] for j in segments.values() for i in j}
|
3723
|
+
nregions = []
|
3724
|
+
for r in regions[:-1]: # interior regions
|
3725
|
+
nregion = []
|
3726
|
+
for e in r:
|
3727
|
+
if e > 0:
|
3728
|
+
rev = segments[e][:-1]
|
3729
|
+
sig = sign(s[edges.index(e)])
|
3730
|
+
nregion += [[a, sig] for a in rev]
|
3731
|
+
nregion.append([segments[e][-1], 1])
|
3732
|
+
else:
|
3733
|
+
rev = segments[-e][1:]
|
3734
|
+
rev.reverse()
|
3735
|
+
sig = sign(s[edges.index(-e)])
|
3736
|
+
nregion += [[a, -sig] for a in rev]
|
3737
|
+
nregion.append([segments[-e][0], 1])
|
3738
|
+
nregions.append(nregion)
|
3739
|
+
N = max(segments) + 1
|
3740
|
+
segments = [i for j in segments.values() for i in j]
|
3741
|
+
badregions = [nr for nr in nregions if any(-1 == x[1] for x in nr)]
|
3742
|
+
while badregions:
|
3743
|
+
badregion = badregions[0]
|
3744
|
+
a = 0
|
3745
|
+
while badregion[a][1] != -1:
|
3746
|
+
a += 1
|
3747
|
+
c = -1
|
3748
|
+
b = a
|
3749
|
+
while c != 2:
|
3750
|
+
if b == len(badregion) - 1:
|
3751
|
+
b = 0
|
3752
|
+
else:
|
3753
|
+
b += 1
|
3754
|
+
c += badregion[b][1]
|
3755
|
+
otherregion = [nr for nr in nregions
|
3756
|
+
if any(badregion[b][0] == x[0] for x in nr)]
|
3757
|
+
if len(otherregion) == 1:
|
3758
|
+
otherregion = None
|
3759
|
+
elif otherregion[0] == badregion:
|
3760
|
+
otherregion = otherregion[1]
|
3761
|
+
else:
|
3762
|
+
otherregion = otherregion[0]
|
3763
|
+
N1 = N
|
3764
|
+
N = N + 2
|
3765
|
+
N2 = N1 + 1
|
3766
|
+
segments.append(N1)
|
3767
|
+
segments.append(N2)
|
3768
|
+
if type(badregion[b][0]) in (int, Integer):
|
3769
|
+
segmenttoadd = [x for x in pieces
|
3770
|
+
if badregion[b][0] in pieces[x]]
|
3771
|
+
if len(segmenttoadd) > 0:
|
3772
|
+
pieces[segmenttoadd[0]].append(N2)
|
3773
|
+
else:
|
3774
|
+
pieces[tuple(badregion[b][0])].append(N2)
|
3775
|
+
|
3776
|
+
if a < b:
|
3777
|
+
r1 = badregion[:a] + [[badregion[a][0], 0], [N1, 1]] + badregion[b:]
|
3778
|
+
r2 = badregion[a + 1:b] + [[N2, 1], [N1, 1]]
|
3779
|
+
else:
|
3780
|
+
r1 = badregion[b:a] + [[badregion[a][0], 0], [N1, 1]]
|
3781
|
+
r2 = badregion[:b] + [[N2, 1], [N1, 1]] + badregion[a + 1:]
|
3782
|
+
|
3783
|
+
if otherregion:
|
3784
|
+
c = [x for x in otherregion if badregion[b][0] == x[0]]
|
3785
|
+
c = otherregion.index(c[0])
|
3786
|
+
otherregion.insert(c + 1, [N2, otherregion[c][1]])
|
3787
|
+
otherregion[c][1] = 0
|
3788
|
+
nregions.remove(badregion)
|
3789
|
+
nregions.append(r1)
|
3790
|
+
nregions.append(r2)
|
3791
|
+
badregions = [nr for nr in nregions if any(x[1] == -1 for x in nr)]
|
3792
|
+
MLP = MixedIntegerLinearProgram(maximization=False, solver=solver)
|
3793
|
+
v = MLP.new_variable(nonnegative=True, integer=True)
|
3794
|
+
for e in segments:
|
3795
|
+
MLP.set_min(v[e], 1)
|
3796
|
+
for r in nregions:
|
3797
|
+
horp = []
|
3798
|
+
horm = []
|
3799
|
+
verp = []
|
3800
|
+
verm = []
|
3801
|
+
direction = 0
|
3802
|
+
for se in r:
|
3803
|
+
if direction % 4 == 0:
|
3804
|
+
horp.append(v[se[0]])
|
3805
|
+
elif direction == 1:
|
3806
|
+
verp.append(v[se[0]])
|
3807
|
+
elif direction == 2:
|
3808
|
+
horm.append(v[se[0]])
|
3809
|
+
elif direction == 3:
|
3810
|
+
verm.append(v[se[0]])
|
3811
|
+
if se[1] == 1:
|
3812
|
+
direction += 1
|
3813
|
+
MLP.add_constraint(MLP.sum(horp) - MLP.sum(horm) == 0)
|
3814
|
+
MLP.add_constraint(MLP.sum(verp) - MLP.sum(verm) == 0)
|
3815
|
+
MLP.set_objective(MLP.sum(v.values()))
|
3816
|
+
MLP.solve()
|
3817
|
+
v = MLP.get_values(v)
|
3818
|
+
lengths = {piece: sum(v[a] for a in pieces[piece]) for piece in pieces}
|
3819
|
+
image = line([], **kwargs)
|
3820
|
+
crossings = {tuple(pd_code[0]): (0, 0, 0)}
|
3821
|
+
availables = pd_code[1:]
|
3822
|
+
used_edges = []
|
3823
|
+
ims = line([], **kwargs)
|
3824
|
+
while len(used_edges) < len(edges):
|
3825
|
+
cross_keys = list(crossings.keys())
|
3826
|
+
i = 0
|
3827
|
+
j = 0
|
3828
|
+
while cross_keys[i][j] in used_edges:
|
3829
|
+
if j < 3:
|
3830
|
+
j += 1
|
3831
|
+
else:
|
3832
|
+
j = 0
|
3833
|
+
i += 1
|
3834
|
+
c = cross_keys[i]
|
3835
|
+
e = c[j]
|
3836
|
+
kwargs['color'] = coloring[e]
|
3837
|
+
used_edges.append(e)
|
3838
|
+
direction = (crossings[c][2] + c.index(e)) % 4
|
3839
|
+
orien = self.orientation()[pd_code.index(list(c))]
|
3840
|
+
if s[edges.index(e)] < 0:
|
3841
|
+
turn = -1
|
3842
|
+
else:
|
3843
|
+
turn = 1
|
3844
|
+
lengthse = [lengths[(e, k)] for k in range(abs(s[edges.index(e)]) + 1)]
|
3845
|
+
if c.index(e) == 0 or (c.index(e) == 3 and orien == 1) or (c.index(e) == 1 and orien == -1):
|
3846
|
+
turn = -turn
|
3847
|
+
lengthse.reverse()
|
3848
|
+
tailshort = (c.index(e) % 2 == 0)
|
3849
|
+
x0 = crossings[c][0]
|
3850
|
+
y0 = crossings[c][1]
|
3851
|
+
im = []
|
3852
|
+
for l in lengthse:
|
3853
|
+
if direction == 0:
|
3854
|
+
x1 = x0 + l
|
3855
|
+
y1 = y0
|
3856
|
+
elif direction == 1:
|
3857
|
+
x1 = x0
|
3858
|
+
y1 = y0 + l
|
3859
|
+
elif direction == 2:
|
3860
|
+
x1 = x0 - l
|
3861
|
+
y1 = y0
|
3862
|
+
elif direction == 3:
|
3863
|
+
x1 = x0
|
3864
|
+
y1 = y0 - l
|
3865
|
+
im.append(([[x0, y0], [x1, y1]], l, direction))
|
3866
|
+
direction = (direction + turn) % 4
|
3867
|
+
x0 = x1
|
3868
|
+
y0 = y1
|
3869
|
+
direction = (direction - turn) % 4
|
3870
|
+
c2 = [ee for ee in availables if e in ee]
|
3871
|
+
if len(c2) == 1:
|
3872
|
+
availables.remove(c2[0])
|
3873
|
+
crossings[tuple(c2[0])] = (x1, y1, (direction - c2[0].index(e) + 2) % 4)
|
3874
|
+
c2 = [ee for ee in pd_code if e in ee and ee != list(c)]
|
3875
|
+
if not c2:
|
3876
|
+
headshort = not tailshort
|
3877
|
+
else:
|
3878
|
+
headshort = (c2[0].index(e) % 2 == 0)
|
3879
|
+
a = deepcopy(im[0][0])
|
3880
|
+
b = deepcopy(im[-1][0])
|
3881
|
+
|
3882
|
+
def delta(u, v):
|
3883
|
+
if u < v:
|
3884
|
+
return -gap
|
3885
|
+
if u > v:
|
3886
|
+
return gap
|
3887
|
+
return 0
|
3888
|
+
|
3889
|
+
if tailshort:
|
3890
|
+
im[0][0][0][0] += delta(a[1][0], im[0][0][0][0])
|
3891
|
+
im[0][0][0][1] += delta(a[1][1], im[0][0][0][1])
|
3892
|
+
if headshort:
|
3893
|
+
im[-1][0][1][0] -= delta(b[1][0], im[-1][0][0][0])
|
3894
|
+
im[-1][0][1][1] -= delta(b[1][1], im[-1][0][0][1])
|
3895
|
+
l = line([], **kwargs)
|
3896
|
+
c = 0
|
3897
|
+
p = im[0][0][0]
|
3898
|
+
if len(im) == 4 and max(x[1] for x in im) == 1:
|
3899
|
+
l = bezier_path([[im[0][0][0], im[0][0][1], im[-1][0][0], im[-1][0][1]]], **kwargs)
|
3900
|
+
p = im[-1][0][1]
|
3901
|
+
else:
|
3902
|
+
while c < len(im)-1:
|
3903
|
+
if im[c][1] > 1:
|
3904
|
+
(a, b) = im[c][0]
|
3905
|
+
if b[0] > a[0]:
|
3906
|
+
e = [b[0] - 1, b[1]]
|
3907
|
+
elif b[0] < a[0]:
|
3908
|
+
e = [b[0] + 1, b[1]]
|
3909
|
+
elif b[1] > a[1]:
|
3910
|
+
e = [b[0], b[1] - 1]
|
3911
|
+
elif b[1] < a[1]:
|
3912
|
+
e = [b[0], b[1] + 1]
|
3913
|
+
l += line((p, e), **kwargs)
|
3914
|
+
p = e
|
3915
|
+
if im[c+1][1] == 1 and c < len(im) - 2:
|
3916
|
+
xr = round(im[c+2][0][1][0])
|
3917
|
+
yr = round(im[c+2][0][1][1])
|
3918
|
+
xp = xr - im[c+2][0][1][0]
|
3919
|
+
yp = yr - im[c+2][0][1][1]
|
3920
|
+
q = [p[0] + im[c+1][0][1][0] - im[c+1][0][0][0] - xp,
|
3921
|
+
p[1] + im[c+1][0][1][1] - im[c+1][0][0][1] - yp]
|
3922
|
+
l += bezier_path([[p, im[c+1][0][0], im[c+1][0][1], q]], **kwargs)
|
3923
|
+
c += 2
|
3924
|
+
p = q
|
3925
|
+
else:
|
3926
|
+
if im[c+1][1] == 1:
|
3927
|
+
q = im[c+1][0][1]
|
3928
|
+
else:
|
3929
|
+
q = [im[c+1][0][0][0] + sign(im[c+1][0][1][0] - im[c+1][0][0][0]),
|
3930
|
+
im[c+1][0][0][1] + sign(im[c+1][0][1][1] - im[c+1][0][0][1])]
|
3931
|
+
l += bezier_path([[p, im[c+1][0][0], q]], **kwargs)
|
3932
|
+
p = q
|
3933
|
+
c += 1
|
3934
|
+
l += line([p, im[-1][0][1]], **kwargs)
|
3935
|
+
image += l
|
3936
|
+
ims += sum(line(a[0], **kwargs) for a in im)
|
3937
|
+
return image
|
3938
|
+
|
3939
|
+
def _markov_move_cmp(self, braid):
|
3940
|
+
r"""
|
3941
|
+
Return whether ``self`` can be transformed to the closure of ``braid``
|
3942
|
+
by a sequence of Markov moves.
|
3943
|
+
|
3944
|
+
More precisely it is checked whether the braid of ``self`` is conjugated
|
3945
|
+
to the given braid in the following sense. If both braids have the same
|
3946
|
+
number of strands it is checked if they are conjugated to each other in
|
3947
|
+
their common braid group (Markov move I). If the number of strands differs,
|
3948
|
+
the braid having less strands is extended by Markov moves II (appending
|
3949
|
+
the largest generator or its inverse recursively) until a common braid
|
3950
|
+
group can be achieved, where conjugation is tested.
|
3951
|
+
|
3952
|
+
Be aware, that a negative result does not ensure that ``self`` is not
|
3953
|
+
isotopic to the closure of ``braid``.
|
3954
|
+
|
3955
|
+
EXAMPLES::
|
3956
|
+
|
3957
|
+
sage: # needs sage.libs.braiding
|
3958
|
+
sage: b = BraidGroup(4)((1, 2, -3, 2, 2, 2, 2, 2, 2, -1, 2, 3, 2))
|
3959
|
+
sage: L = Link([[2, 5, 4, 1], [5, 7, 6, 4], [7, 9, 8, 6], [9, 11, 10, 8],
|
3960
|
+
....: [11, 13, 12, 10], [13, 15, 14, 12], [15, 17, 16, 14],
|
3961
|
+
....: [3, 19, 18, 17], [16, 18, 21, 1], [19, 3, 2, 21]])
|
3962
|
+
sage: L._markov_move_cmp(b) # both are isotopic to ``9_3``
|
3963
|
+
True
|
3964
|
+
sage: bL = L.braid(); bL
|
3965
|
+
s0^7*s1*s0^-1*s1
|
3966
|
+
sage: Lb = Link(b); Lb
|
3967
|
+
Link with 1 component represented by 13 crossings
|
3968
|
+
sage: Lb._markov_move_cmp(bL)
|
3969
|
+
True
|
3970
|
+
sage: L == Lb
|
3971
|
+
False
|
3972
|
+
sage: b.strands() > bL.strands()
|
3973
|
+
True
|
3974
|
+
|
3975
|
+
REFERENCES:
|
3976
|
+
|
3977
|
+
- :wikipedia:`Markov_theorem`
|
3978
|
+
"""
|
3979
|
+
sb = self.braid()
|
3980
|
+
sb_ind = sb.strands()
|
3981
|
+
|
3982
|
+
ob = braid
|
3983
|
+
ob_ind = ob.strands()
|
3984
|
+
|
3985
|
+
if sb_ind == ob_ind:
|
3986
|
+
return sb.is_conjugated(ob)
|
3987
|
+
|
3988
|
+
if sb_ind > ob_ind:
|
3989
|
+
# if the braid of self has more strands we have to perform
|
3990
|
+
# Markov II moves
|
3991
|
+
B = sb.parent()
|
3992
|
+
g = B.gen(ob_ind-1)
|
3993
|
+
ob = B(ob)
|
3994
|
+
if sb_ind > ob_ind+1:
|
3995
|
+
# proceed by recursion
|
3996
|
+
res = self._markov_move_cmp(ob*g)
|
3997
|
+
if not res:
|
3998
|
+
res = self._markov_move_cmp(ob*~g)
|
3999
|
+
else:
|
4000
|
+
res = sb.is_conjugated(ob*g)
|
4001
|
+
if not res:
|
4002
|
+
res = sb.is_conjugated(ob*~g)
|
4003
|
+
return res
|
4004
|
+
else:
|
4005
|
+
L = Link(ob)
|
4006
|
+
return L._markov_move_cmp(sb)
|
4007
|
+
|
4008
|
+
@cached_method
|
4009
|
+
def _knotinfo_matching_list(self):
|
4010
|
+
r"""
|
4011
|
+
Return a list of links from the KnotInfo and LinkInfo databases which match
|
4012
|
+
the properties of ``self`` as much as possible.
|
4013
|
+
|
4014
|
+
OUTPUT:
|
4015
|
+
|
4016
|
+
A tuple ``(l, proved)`` where ``l`` is the matching list and ``proved`` a boolean
|
4017
|
+
telling if the entries of ``l`` are checked to be isotopic to ``self`` or not.
|
4018
|
+
|
4019
|
+
EXAMPLES::
|
4020
|
+
|
4021
|
+
sage: KnotInfo.L5a1_0.inject()
|
4022
|
+
Defining L5a1_0
|
4023
|
+
sage: ML = L5a1_0.link()._knotinfo_matching_list(); ML # needs sage.libs.homfly
|
4024
|
+
([<KnotInfo.L5a1_0: 'L5a1{0}'>, <KnotInfo.L5a1_1: 'L5a1{1}'>], True)
|
4025
|
+
sage: ML == Link(L5a1_0.braid())._knotinfo_matching_list() # needs sage.libs.homfly
|
4026
|
+
True
|
4027
|
+
|
4028
|
+
Care is needed for links having non irreducible HOMFLY-PT polynomials::
|
4029
|
+
|
4030
|
+
sage: k4_1 = KnotInfo.K4_1.link()
|
4031
|
+
sage: k5_2 = KnotInfo.K5_2.link()
|
4032
|
+
sage: k = k4_1.connected_sum(k5_2)
|
4033
|
+
sage: k._knotinfo_matching_list() # optional - database_knotinfo # needs sage.libs.homfly
|
4034
|
+
([<KnotInfo.K9_12: '9_12'>], False)
|
4035
|
+
"""
|
4036
|
+
from sage.knots.knotinfo import KnotInfoSeries
|
4037
|
+
pd_code = self.pd_code()
|
4038
|
+
cr = len(pd_code)
|
4039
|
+
co = self.number_of_components()
|
4040
|
+
|
4041
|
+
# set the limits for the KnotInfoSeries
|
4042
|
+
if cr > 11 and co > 1:
|
4043
|
+
cr = 11
|
4044
|
+
cr = min(cr, 13)
|
4045
|
+
|
4046
|
+
Hp = self.homfly_polynomial(normalization='vz')
|
4047
|
+
|
4048
|
+
det = None
|
4049
|
+
if cr > 6:
|
4050
|
+
# for larger crossing numbers the KnotInfoSeries become very
|
4051
|
+
# large, as well. For better performance we restrict the cached
|
4052
|
+
# lists by the determinant and number of components.
|
4053
|
+
#
|
4054
|
+
# Since :meth:`determinant` is not implemented for proper links
|
4055
|
+
# we have to go back to the roots.
|
4056
|
+
ap = self.alexander_polynomial()
|
4057
|
+
det = Integer(abs(ap(-1)))
|
4058
|
+
|
4059
|
+
is_knot = self.is_knot()
|
4060
|
+
if is_knot and cr < 11:
|
4061
|
+
S = KnotInfoSeries(cr, True, None)
|
4062
|
+
l = S.lower_list(oriented=True, comp=co, det=det, homfly=Hp)
|
4063
|
+
else:
|
4064
|
+
# the result of :meth:`is_alternating` depends on the specific
|
4065
|
+
# diagram of the link. For example ``K11a_2`` is an alternating
|
4066
|
+
# knot but ``Link(KnotInfo.K11a_2.braid()).is_alternating()``
|
4067
|
+
# gives ``False``. Therefore, we have to take both series
|
4068
|
+
# into consideration.
|
4069
|
+
Sa = KnotInfoSeries(cr, is_knot, True)
|
4070
|
+
Sn = KnotInfoSeries(cr, is_knot, False)
|
4071
|
+
la = Sa.lower_list(oriented=True, comp=co, det=det, homfly=Hp)
|
4072
|
+
ln = Sn.lower_list(oriented=True, comp=co, det=det, homfly=Hp)
|
4073
|
+
l = sorted(set(la + ln))
|
4074
|
+
|
4075
|
+
br = self.braid()
|
4076
|
+
br_ind = br.strands()
|
4077
|
+
|
4078
|
+
res = []
|
4079
|
+
for L in l:
|
4080
|
+
if L.pd_notation() == pd_code:
|
4081
|
+
# pd_notation is unique in the KnotInfo database
|
4082
|
+
res.append(L)
|
4083
|
+
continue
|
4084
|
+
|
4085
|
+
Lbraid = L.braid()
|
4086
|
+
if Lbraid.strands() <= br_ind:
|
4087
|
+
if self._markov_move_cmp(Lbraid):
|
4088
|
+
res.append(L)
|
4089
|
+
|
4090
|
+
if res:
|
4091
|
+
if len(res) > 1 or res[0].is_unique():
|
4092
|
+
return res, True
|
4093
|
+
return l, False
|
4094
|
+
|
4095
|
+
def _knotinfo_matching_dict(self):
|
4096
|
+
r"""
|
4097
|
+
Return a dictionary mapping items of the enum :class:`~sage.knots.knotinfo.SymmetryMutant`
|
4098
|
+
to list of links from the KnotInfo and LinkInfo databases which match
|
4099
|
+
the properties of the according symmetry mutant of ``self`` as much as
|
4100
|
+
possible.
|
4101
|
+
|
4102
|
+
OUTPUT:
|
4103
|
+
|
4104
|
+
A pair (``match_lists, proves``) of dictionaries with keys from the
|
4105
|
+
enum :class:`~sage.knots.knotinfo.SymmetryMutant`. The first dictionary maps these keys to
|
4106
|
+
the corresponding matching list and ``proves`` maps them to booleans
|
4107
|
+
telling if the entries of the corresponding ``match_lists`` are checked
|
4108
|
+
to be isotopic to the symmetry mutant of ``self`` or not.
|
4109
|
+
|
4110
|
+
EXAMPLES::
|
4111
|
+
|
4112
|
+
sage: # needs sage.libs.homfly
|
4113
|
+
sage: KnotInfo.L4a1_0.inject()
|
4114
|
+
Defining L4a1_0
|
4115
|
+
sage: L4a1_0.link()._knotinfo_matching_dict()
|
4116
|
+
({<SymmetryMutant.itself: 's'>: [<KnotInfo.L4a1_0: 'L4a1{0}'>],
|
4117
|
+
<SymmetryMutant.reverse: 'r'>: [<KnotInfo.L4a1_0: 'L4a1{0}'>],
|
4118
|
+
<SymmetryMutant.mirror_image: 'm'>: [],
|
4119
|
+
<SymmetryMutant.concordance_inverse: 'c'>: []},
|
4120
|
+
{<SymmetryMutant.itself: 's'>: True,
|
4121
|
+
<SymmetryMutant.reverse: 'r'>: True,
|
4122
|
+
<SymmetryMutant.mirror_image: 'm'>: False,
|
4123
|
+
<SymmetryMutant.concordance_inverse: 'c'>: False})
|
4124
|
+
"""
|
4125
|
+
from sage.knots.knotinfo import SymmetryMutant
|
4126
|
+
mutant = {}
|
4127
|
+
mutant[SymmetryMutant.itself] = self
|
4128
|
+
mutant[SymmetryMutant.reverse] = self.reverse()
|
4129
|
+
mutant[SymmetryMutant.mirror_image] = self.mirror_image()
|
4130
|
+
mutant[SymmetryMutant.concordance_inverse] = mutant[SymmetryMutant.mirror_image].reverse()
|
4131
|
+
match_lists = {k: list(mutant[k]._knotinfo_matching_list()[0]) for k in mutant.keys()}
|
4132
|
+
proves = {k: mutant[k]._knotinfo_matching_list()[1] for k in mutant.keys()}
|
4133
|
+
return match_lists, proves
|
4134
|
+
|
4135
|
+
def get_knotinfo(self, mirror_version=True, unique=True):
|
4136
|
+
r"""
|
4137
|
+
Identify this link as an item of the KnotInfo database (if possible).
|
4138
|
+
|
4139
|
+
INPUT:
|
4140
|
+
|
4141
|
+
- ``mirror_version`` -- boolean (default: ``True``); if set to ``False``
|
4142
|
+
the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase`
|
4143
|
+
(by default the result is a tuple of the instance and an enum, see
|
4144
|
+
explanation of the output below)
|
4145
|
+
|
4146
|
+
- ``unique`` -- boolean (default: ``True``); this only affects the case
|
4147
|
+
where a unique identification is not possible. If set to ``False`` you
|
4148
|
+
can obtain a matching list (see explanation of the output below).
|
4149
|
+
|
4150
|
+
OUTPUT:
|
4151
|
+
|
4152
|
+
If ``self`` is a knot, then an element of the free monoid over prime
|
4153
|
+
knots constructed from the KnotInfo database is returned. More explicitly
|
4154
|
+
this is an element of :class:`~sage.knots.free_knotinfo_monoid.FreeKnotInfoMonoidElement`.
|
4155
|
+
Else a tuple ``(K, m)`` is returned where ``K`` is an instance of
|
4156
|
+
:class:`~sage.knots.knotinfo.KnotInfoBase` and ``m`` an instance of
|
4157
|
+
:class:`~sage.knots.knotinfo.SymmetryMutant` (for chiral links) specifying
|
4158
|
+
the symmetry mutant of ``K`` to which ``self`` is isotopic. The value of
|
4159
|
+
``m`` is ``unknown`` if it cannot be determined uniquely and the keyword
|
4160
|
+
option ``unique=False`` is given.
|
4161
|
+
|
4162
|
+
For proper links, if the orientation mutant cannot be uniquely determined,
|
4163
|
+
K will be a series of links gathering all links having the same unoriented
|
4164
|
+
name, that is an instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`.
|
4165
|
+
|
4166
|
+
If ``mirror_version`` is set to ``False`` then the result is just ``K``
|
4167
|
+
(that is: ``m`` is suppressed).
|
4168
|
+
|
4169
|
+
If it is not possible to determine a unique result
|
4170
|
+
a :exc:`NotImplementedError`
|
4171
|
+
will be raised. To avoid this you can set ``unique`` to ``False``. You
|
4172
|
+
will get a list of matching candidates instead.
|
4173
|
+
|
4174
|
+
.. NOTE::
|
4175
|
+
|
4176
|
+
The identification of proper links may fail to be unique due to the
|
4177
|
+
following fact: In opposite to the database for knots, there are pairs
|
4178
|
+
of oriented mutants of an unoriented link which are isotopic to each
|
4179
|
+
other. For example ``L5a1_0`` and ``L5a1_1`` is such a pair.
|
4180
|
+
|
4181
|
+
This is because all combinatorial possible oriented mutants are
|
4182
|
+
listed with individual names regardless whether they are pairwise
|
4183
|
+
non isotopic or not. In such a case the identification is not
|
4184
|
+
unique and therefore a series of the links will be returned which
|
4185
|
+
gathers all having the same unoriented name.
|
4186
|
+
|
4187
|
+
To obtain the individual oriented links being isotopic to ``self``
|
4188
|
+
use the keyword ``unique`` (see the examples for ``L2a1_1`` and
|
4189
|
+
``L5a1_0`` below).
|
4190
|
+
|
4191
|
+
EXAMPLES::
|
4192
|
+
|
4193
|
+
sage: # optional - database_knotinfo
|
4194
|
+
sage: L = Link([[4,1,5,2], [10,4,11,3], [5,17,6,16], [7,13,8,12],
|
4195
|
+
....: [18,10,19,9], [2,12,3,11], [13,21,14,20], [15,7,16,6],
|
4196
|
+
....: [22,17,1,18], [8,20,9,19], [21,15,22,14]])
|
4197
|
+
sage: L.get_knotinfo()
|
4198
|
+
KnotInfo['K11n_121m']
|
4199
|
+
sage: K = KnotInfo.K10_25
|
4200
|
+
sage: l = K.link()
|
4201
|
+
sage: l.get_knotinfo()
|
4202
|
+
KnotInfo['K10_25']
|
4203
|
+
sage: k11 = KnotInfo.K11n_82.link()
|
4204
|
+
sage: k11m = k11.mirror_image()
|
4205
|
+
sage: k11mr = k11m.reverse()
|
4206
|
+
sage: k11mr.get_knotinfo()
|
4207
|
+
KnotInfo['K11n_82m']
|
4208
|
+
sage: k11r = k11.reverse()
|
4209
|
+
sage: k11r.get_knotinfo()
|
4210
|
+
KnotInfo['K11n_82']
|
4211
|
+
sage: k11rm = k11r.mirror_image()
|
4212
|
+
sage: k11rm.get_knotinfo()
|
4213
|
+
KnotInfo['K11n_82m']
|
4214
|
+
|
4215
|
+
Knots with more than 13 and multi-component links having more than 11
|
4216
|
+
crossings cannot be identified. In addition non prime multi-component
|
4217
|
+
links or even links whose HOMFLY-PT polynomial is not irreducible cannot
|
4218
|
+
be identified::
|
4219
|
+
|
4220
|
+
sage: b, = BraidGroup(2).gens()
|
4221
|
+
sage: Link(b**13).get_knotinfo() # optional - database_knotinfo
|
4222
|
+
KnotInfo['K13a_4878']
|
4223
|
+
sage: Link(b**14).get_knotinfo() # needs libhomfly
|
4224
|
+
Traceback (most recent call last):
|
4225
|
+
...
|
4226
|
+
NotImplementedError: this link having more than 11 crossings cannot be determined
|
4227
|
+
|
4228
|
+
sage: Link([[1, 4, 2, 5], [3, 8, 4, 1], [5, 2, 6, 3],
|
4229
|
+
....: [6, 10, 7, 9], [10, 8, 9, 7]])
|
4230
|
+
Link with 2 components represented by 5 crossings
|
4231
|
+
sage: _.get_knotinfo() # needs sage.libs.homfly
|
4232
|
+
Traceback (most recent call last):
|
4233
|
+
...
|
4234
|
+
NotImplementedError: this (possibly non prime) link cannot be determined
|
4235
|
+
|
4236
|
+
Lets identify the monster unknot::
|
4237
|
+
|
4238
|
+
sage: # needs sage.libs.homfly
|
4239
|
+
sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5],
|
4240
|
+
....: [17,19,8,18], [9,10,11,14], [10,12,13,11],
|
4241
|
+
....: [12,19,15,13], [20,16,14,15], [16,20,17,2]])
|
4242
|
+
sage: L.get_knotinfo()
|
4243
|
+
KnotInfo['K0_1']
|
4244
|
+
|
4245
|
+
Usage of option ``mirror_version``::
|
4246
|
+
|
4247
|
+
sage: L.get_knotinfo(mirror_version=False) == KnotInfo.K0_1 # needs sage.libs.homfly
|
4248
|
+
True
|
4249
|
+
|
4250
|
+
Usage of option ``unique``::
|
4251
|
+
|
4252
|
+
sage: # optional - database_knotinfo
|
4253
|
+
sage: l = K.link(K.items.gauss_notation)
|
4254
|
+
sage: l.get_knotinfo()
|
4255
|
+
Traceback (most recent call last):
|
4256
|
+
...
|
4257
|
+
NotImplementedError: this link cannot be uniquely determined
|
4258
|
+
use keyword argument `unique` to obtain more details
|
4259
|
+
sage: l.get_knotinfo(unique=False)
|
4260
|
+
[KnotInfo['K10_25'], KnotInfo['K10_56']]
|
4261
|
+
sage: t = (1, -2, 1, 1, -2, 1, -2, -2)
|
4262
|
+
sage: l8 = Link(BraidGroup(3)(t))
|
4263
|
+
sage: l8.get_knotinfo()
|
4264
|
+
Traceback (most recent call last):
|
4265
|
+
...
|
4266
|
+
NotImplementedError: this link cannot be uniquely determined
|
4267
|
+
use keyword argument `unique` to obtain more details
|
4268
|
+
sage: l8.get_knotinfo(unique=False)
|
4269
|
+
[(<KnotInfo.L8a19_0_0: 'L8a19{0,0}'>, <SymmetryMutant.itself: 's'>),
|
4270
|
+
(<KnotInfo.L8a19_1_1: 'L8a19{1,1}'>, <SymmetryMutant.itself: 's'>)]
|
4271
|
+
sage: t = (2, -3, -3, -2, 3, 3, -2, 3, 1, -2, -2, 1)
|
4272
|
+
sage: l12 = Link(BraidGroup(5)(t))
|
4273
|
+
sage: l12.get_knotinfo()
|
4274
|
+
Traceback (most recent call last):
|
4275
|
+
...
|
4276
|
+
NotImplementedError: this link having more than 11 crossings
|
4277
|
+
cannot be uniquely determined
|
4278
|
+
use keyword argument `unique` to obtain more details
|
4279
|
+
sage: l12.get_knotinfo(unique=False)
|
4280
|
+
[(<KnotInfo.L10n36_0: 'L10n36{0}'>, <SymmetryMutant.unknown: '?'>),
|
4281
|
+
(<KnotInfo.L10n36_1: 'L10n36{1}'>, <SymmetryMutant.unknown: '?'>),
|
4282
|
+
(<KnotInfo.L10n59_0: 'L10n59{0}'>, <SymmetryMutant.itself: 's'>),
|
4283
|
+
(<KnotInfo.L10n59_1: 'L10n59{1}'>, <SymmetryMutant.itself: 's'>)]
|
4284
|
+
|
4285
|
+
Furthermore, if the result is a complete series of oriented links having
|
4286
|
+
the same unoriented name (according to the note above) the option can be
|
4287
|
+
used to achieve more detailed information::
|
4288
|
+
|
4289
|
+
sage: # needs sage.libs.homfly
|
4290
|
+
sage: L2a1 = Link(b**2)
|
4291
|
+
sage: L2a1.get_knotinfo()
|
4292
|
+
(Series of links L2a1, <SymmetryMutant.mixed: 'x'>)
|
4293
|
+
sage: L2a1.get_knotinfo(unique=False)
|
4294
|
+
[(<KnotInfo.L2a1_0: 'L2a1{0}'>, <SymmetryMutant.mirror_image: 'm'>),
|
4295
|
+
(<KnotInfo.L2a1_1: 'L2a1{1}'>, <SymmetryMutant.itself: 's'>)]
|
4296
|
+
|
4297
|
+
sage: # needs sage.libs.homfly
|
4298
|
+
sage: KnotInfo.L5a1_0.inject()
|
4299
|
+
Defining L5a1_0
|
4300
|
+
sage: l5 = Link(L5a1_0.braid())
|
4301
|
+
sage: l5.get_knotinfo()
|
4302
|
+
(Series of links L5a1, <SymmetryMutant.itself: 's'>)
|
4303
|
+
sage: _[0].inject()
|
4304
|
+
Defining L5a1
|
4305
|
+
sage: list(L5a1)
|
4306
|
+
[<KnotInfo.L5a1_0: 'L5a1{0}'>, <KnotInfo.L5a1_1: 'L5a1{1}'>]
|
4307
|
+
sage: l5.get_knotinfo(unique=False)
|
4308
|
+
[(<KnotInfo.L5a1_0: 'L5a1{0}'>, <SymmetryMutant.itself: 's'>),
|
4309
|
+
(<KnotInfo.L5a1_1: 'L5a1{1}'>, <SymmetryMutant.itself: 's'>)]
|
4310
|
+
|
4311
|
+
Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`)::
|
4312
|
+
|
4313
|
+
sage: for i in range(160, 166): # optional - database_knotinfo
|
4314
|
+
....: K = Knots().from_table(10, i)
|
4315
|
+
....: print('%s_%s' %(10, i), '--->', K.get_knotinfo())
|
4316
|
+
10_160 ---> KnotInfo['K10_160']
|
4317
|
+
10_161 ---> KnotInfo['K10_161m']
|
4318
|
+
10_162 ---> KnotInfo['K10_162']
|
4319
|
+
10_163 ---> KnotInfo['K10_163']
|
4320
|
+
10_164 ---> KnotInfo['K10_164']
|
4321
|
+
10_165 ---> KnotInfo['K10_165m']
|
4322
|
+
|
4323
|
+
Clarifying ther Perko series against `SnapPy
|
4324
|
+
<https://snappy.math.uic.edu/index.html>`__::
|
4325
|
+
|
4326
|
+
sage: import snappy # optional - snappy
|
4327
|
+
...
|
4328
|
+
|
4329
|
+
sage: # optional - database_knotinfo snappy
|
4330
|
+
sage: from sage.knots.knotinfo import KnotInfoSeries
|
4331
|
+
sage: KnotInfoSeries(10, True, True)
|
4332
|
+
Series of knots K10
|
4333
|
+
sage: _.inject()
|
4334
|
+
Defining K10
|
4335
|
+
sage: for i in range(160, 166):
|
4336
|
+
....: K = K10(i)
|
4337
|
+
....: k = K.link(K.items.name, snappy=True)
|
4338
|
+
....: print(k, '--->', k.sage_link().get_knotinfo())
|
4339
|
+
<Link 10_160: 1 comp; 10 cross> ---> KnotInfo['K10_160']
|
4340
|
+
<Link 10_161: 1 comp; 10 cross> ---> KnotInfo['K10_161m']
|
4341
|
+
<Link 10_162: 1 comp; 10 cross> ---> KnotInfo['K10_161']
|
4342
|
+
<Link 10_163: 1 comp; 10 cross> ---> KnotInfo['K10_162']
|
4343
|
+
<Link 10_164: 1 comp; 10 cross> ---> KnotInfo['K10_163']
|
4344
|
+
<Link 10_165: 1 comp; 10 cross> ---> KnotInfo['K10_164']
|
4345
|
+
sage: snappy.Link('10_166')
|
4346
|
+
<Link 10_166: 1 comp; 10 cross>
|
4347
|
+
sage: _.sage_link().get_knotinfo()
|
4348
|
+
KnotInfo['K10_165m']
|
4349
|
+
|
4350
|
+
Another pair of confusion (see the corresponding `Warning
|
4351
|
+
<http://katlas.math.toronto.edu/wiki/10_86>`__)::
|
4352
|
+
|
4353
|
+
sage: # optional - database_knotinfo snappy
|
4354
|
+
sage: Ks10_86 = snappy.Link('10_86')
|
4355
|
+
sage: Ks10_83 = snappy.Link('10_83')
|
4356
|
+
sage: Ks10_86.sage_link().get_knotinfo(unique=False)
|
4357
|
+
[KnotInfo['K10_83c'], KnotInfo['K10_83m']]
|
4358
|
+
sage: Ks10_83.sage_link().get_knotinfo(unique=False)
|
4359
|
+
[KnotInfo['K10_86'], KnotInfo['K10_86r']]
|
4360
|
+
|
4361
|
+
Non prime knots can be detected, as well::
|
4362
|
+
|
4363
|
+
sage: b = BraidGroup(4)((1, 2, 2, 2, -1, 2, 2, 2, -3, -3, -3))
|
4364
|
+
sage: Kb = Knot(b)
|
4365
|
+
sage: Kb.get_knotinfo() # optional - database_knotinfo, needs sage.libs.homfly
|
4366
|
+
KnotInfo['K3_1']^2*KnotInfo['K3_1m']
|
4367
|
+
|
4368
|
+
sage: K = Link([[4, 2, 5, 1], [8, 6, 9, 5], [6, 3, 7, 4], [2, 7, 3, 8],
|
4369
|
+
....: [10, 15, 11, 16], [12, 21, 13, 22], [14, 11, 15, 12], [16, 9, 17, 10],
|
4370
|
+
....: [18, 25, 19, 26], [20, 23, 21, 24], [22, 13, 23, 14], [24, 19, 25, 20],
|
4371
|
+
....: [26, 17, 1, 18]])
|
4372
|
+
sage: K.get_knotinfo() # optional - database_knotinfo, long time, needs sage.libs.homfly
|
4373
|
+
KnotInfo['K4_1']*KnotInfo['K9_2m']
|
4374
|
+
|
4375
|
+
TESTS::
|
4376
|
+
|
4377
|
+
sage: # optional - database_knotinfo
|
4378
|
+
sage: L = KnotInfo.L10a171_1_1_0
|
4379
|
+
sage: l = L.link(L.items.braid_notation)
|
4380
|
+
sage: l.get_knotinfo(unique=False)
|
4381
|
+
[(<KnotInfo.L10a171_0_1_0: 'L10a171{0,1,0}'>, <SymmetryMutant.unknown: '?'>),
|
4382
|
+
(<KnotInfo.L10a171_1_0_1: 'L10a171{1,0,1}'>, <SymmetryMutant.unknown: '?'>),
|
4383
|
+
(<KnotInfo.L10a171_1_1_0: 'L10a171{1,1,0}'>, <SymmetryMutant.unknown: '?'>),
|
4384
|
+
(<KnotInfo.L10a171_1_1_1: 'L10a171{1,1,1}'>, <SymmetryMutant.unknown: '?'>)]
|
4385
|
+
sage: KnotInfo.L10a151_0_0.link().get_knotinfo()
|
4386
|
+
Traceback (most recent call last):
|
4387
|
+
...
|
4388
|
+
NotImplementedError: this link cannot be uniquely determined (unknown chirality)
|
4389
|
+
use keyword argument `unique` to obtain more details
|
4390
|
+
sage: KnotInfo.L10a151_0_0.link().get_knotinfo(unique=False)
|
4391
|
+
[(<KnotInfo.L10a151_0_0: 'L10a151{0,0}'>, <SymmetryMutant.unknown: '?'>),
|
4392
|
+
(<KnotInfo.L10a151_0_1: 'L10a151{0,1}'>, <SymmetryMutant.unknown: '?'>),
|
4393
|
+
(<KnotInfo.L10a151_1_0: 'L10a151{1,0}'>, <SymmetryMutant.unknown: '?'>),
|
4394
|
+
(<KnotInfo.L10a151_1_1: 'L10a151{1,1}'>, <SymmetryMutant.unknown: '?'>)]
|
4395
|
+
|
4396
|
+
sage: L = KnotInfo.L6a2_0
|
4397
|
+
sage: L1 = L.link()
|
4398
|
+
sage: L2 = L.link(L.items.braid_notation)
|
4399
|
+
sage: L1.get_knotinfo() == L2.get_knotinfo() # optional - database_knotinfo, needs sage.libs.homfly
|
4400
|
+
True
|
4401
|
+
"""
|
4402
|
+
non_unique_hint = '\nuse keyword argument `unique` to obtain more details'
|
4403
|
+
from sage.knots.knotinfo import SymmetryMutant
|
4404
|
+
|
4405
|
+
def answer(L):
|
4406
|
+
r"""
|
4407
|
+
Return a single item of the KnotInfo database according to the keyword
|
4408
|
+
arguments ``mirror_version``.
|
4409
|
+
"""
|
4410
|
+
if not mirror_version:
|
4411
|
+
return L
|
4412
|
+
|
4413
|
+
def find_mutant(proved=True):
|
4414
|
+
r"""
|
4415
|
+
Return the according symmetry mutant from the matching list
|
4416
|
+
and removes the entry from the list.
|
4417
|
+
"""
|
4418
|
+
for k in match_lists:
|
4419
|
+
if proved:
|
4420
|
+
prove = proves[k] or any(proves[m] for m in k.matches(L))
|
4421
|
+
if not prove:
|
4422
|
+
continue
|
4423
|
+
if k.is_minimal(L):
|
4424
|
+
lk = match_lists[k]
|
4425
|
+
if L in lk:
|
4426
|
+
lk.remove(L)
|
4427
|
+
return k
|
4428
|
+
|
4429
|
+
sym_mut = None
|
4430
|
+
if SymmetryMutant.unknown.matches(L):
|
4431
|
+
if unique:
|
4432
|
+
raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)%s' % non_unique_hint)
|
4433
|
+
sym_mut = SymmetryMutant.unknown
|
4434
|
+
|
4435
|
+
if not sym_mut:
|
4436
|
+
sym_mut = find_mutant()
|
4437
|
+
|
4438
|
+
if not sym_mut:
|
4439
|
+
sym_mut = find_mutant(proved=False)
|
4440
|
+
|
4441
|
+
if not unique and not sym_mut:
|
4442
|
+
return None
|
4443
|
+
|
4444
|
+
if not sym_mut:
|
4445
|
+
# In case of a chiral link this means that the HOMFLY-PT
|
4446
|
+
# polynomial does not distinguish mirror images (see the above
|
4447
|
+
# example ``L10n36_0``).
|
4448
|
+
sym_mut = SymmetryMutant.unknown
|
4449
|
+
|
4450
|
+
if unique and sym_mut is SymmetryMutant.unknown:
|
4451
|
+
raise NotImplementedError('symmetry mutant of this link cannot be uniquely determined%s' % non_unique_hint)
|
4452
|
+
|
4453
|
+
if L.is_knot():
|
4454
|
+
from sage.knots.free_knotinfo_monoid import FreeKnotInfoMonoid
|
4455
|
+
FKIM = FreeKnotInfoMonoid()
|
4456
|
+
return FKIM((L, sym_mut))
|
4457
|
+
|
4458
|
+
return L, sym_mut
|
4459
|
+
|
4460
|
+
def answer_unori(S):
|
4461
|
+
r"""
|
4462
|
+
Return a series of oriented links having the same unoriented name
|
4463
|
+
according to the keyword ``mirror_version``.
|
4464
|
+
"""
|
4465
|
+
if not mirror_version:
|
4466
|
+
return S
|
4467
|
+
|
4468
|
+
sym_mut = [answer(L)[1] for L in S]
|
4469
|
+
if all(i is SymmetryMutant.mirror_image for i in sym_mut):
|
4470
|
+
# all matching links are mirrored to self
|
4471
|
+
return S, SymmetryMutant.mirror_image
|
4472
|
+
if all(i is SymmetryMutant.itself for i in sym_mut):
|
4473
|
+
# all matching links are self itself
|
4474
|
+
return S, SymmetryMutant.itself
|
4475
|
+
if any(i is SymmetryMutant.unknown for i in sym_mut):
|
4476
|
+
# unknown chirality for a matching link
|
4477
|
+
return S, SymmetryMutant.unknown
|
4478
|
+
# finally several mirror types match
|
4479
|
+
return S, SymmetryMutant.mixed
|
4480
|
+
|
4481
|
+
def answer_list(l):
|
4482
|
+
r"""
|
4483
|
+
Return a list of items of the KnotInfo database according to the keyword
|
4484
|
+
argument ``unique``.
|
4485
|
+
"""
|
4486
|
+
if not unique:
|
4487
|
+
ansl = []
|
4488
|
+
for L in l:
|
4489
|
+
a = answer(L)
|
4490
|
+
if a:
|
4491
|
+
ansl.append(a)
|
4492
|
+
return sorted(set(ansl))
|
4493
|
+
|
4494
|
+
if len(set(l)) == 1:
|
4495
|
+
return answer(l[0])
|
4496
|
+
|
4497
|
+
if not l[0].is_knot():
|
4498
|
+
S = l[0].series(oriented=True)
|
4499
|
+
if set(S) == set(l):
|
4500
|
+
return answer_unori(S)
|
4501
|
+
|
4502
|
+
raise NotImplementedError('this link cannot be uniquely determined%s' % non_unique_hint)
|
4503
|
+
|
4504
|
+
H = self.homfly_polynomial(normalization='vz')
|
4505
|
+
num_fac = sum(exp for f, exp in H.factor())
|
4506
|
+
if num_fac > 1 and self.is_knot():
|
4507
|
+
# we cannot be sure if this is a prime knot (see the example for the connected
|
4508
|
+
# sum of K4_1 and K5_2 in the doctest of :meth:`_knotinfo_matching_list`)
|
4509
|
+
# Therefor we calculate it directly in the free KnotInfo monoid
|
4510
|
+
from sage.knots.free_knotinfo_monoid import FreeKnotInfoMonoid
|
4511
|
+
FKIM = FreeKnotInfoMonoid()
|
4512
|
+
return FKIM.from_knot(self, unique=unique)
|
4513
|
+
|
4514
|
+
match_lists, proves = self._knotinfo_matching_dict()
|
4515
|
+
|
4516
|
+
# first add only proved matching lists
|
4517
|
+
proved = any(proves[k] for k in proves.keys())
|
4518
|
+
|
4519
|
+
l = []
|
4520
|
+
if proved and unique and self.is_knot():
|
4521
|
+
for k in match_lists.keys():
|
4522
|
+
if proves[k]:
|
4523
|
+
l += match_lists[k]
|
4524
|
+
else:
|
4525
|
+
# for multi-component links there could regularly be more than one
|
4526
|
+
# matching entry
|
4527
|
+
for k in match_lists.keys():
|
4528
|
+
l += match_lists[k]
|
4529
|
+
|
4530
|
+
if l and not unique:
|
4531
|
+
return answer_list(l)
|
4532
|
+
|
4533
|
+
if proved:
|
4534
|
+
return answer_list(l)
|
4535
|
+
|
4536
|
+
# here we come if we cannot be sure about the found result
|
4537
|
+
|
4538
|
+
uniq_txt = ('', '')
|
4539
|
+
if l:
|
4540
|
+
uniq_txt = (' uniquely', non_unique_hint)
|
4541
|
+
|
4542
|
+
cr = len(self.pd_code())
|
4543
|
+
if self.is_knot() and cr > 13:
|
4544
|
+
# we cannot not be sure if this link is recorded in the KnotInfo database
|
4545
|
+
raise NotImplementedError('this knot having more than 13 crossings cannot be%s determined%s' % uniq_txt)
|
4546
|
+
|
4547
|
+
if not self.is_knot() and cr > 11:
|
4548
|
+
# we cannot not be sure if this link is recorded in the KnotInfo database
|
4549
|
+
raise NotImplementedError('this link having more than 11 crossings cannot be%s determined%s' % uniq_txt)
|
4550
|
+
|
4551
|
+
if num_fac > 1:
|
4552
|
+
raise NotImplementedError('this (possibly non prime) link cannot be%s determined%s' % uniq_txt)
|
4553
|
+
|
4554
|
+
if not l:
|
4555
|
+
from sage.features.databases import DatabaseKnotInfo
|
4556
|
+
DatabaseKnotInfo().require()
|
4557
|
+
return l
|
4558
|
+
|
4559
|
+
return answer_list(l)
|
4560
|
+
|
4561
|
+
def is_isotopic(self, other) -> bool:
|
4562
|
+
r"""
|
4563
|
+
Check whether ``self`` is isotopic to ``other``.
|
4564
|
+
|
4565
|
+
INPUT:
|
4566
|
+
|
4567
|
+
- ``other`` -- another instance of :class:`Link`
|
4568
|
+
|
4569
|
+
EXAMPLES::
|
4570
|
+
|
4571
|
+
sage: l1 = Link([[2, 9, 3, 10], [4, 13, 5, 14], [6, 11, 7, 12],
|
4572
|
+
....: [8, 1, 9, 2], [10, 7, 11, 8], [12, 5, 13, 6],
|
4573
|
+
....: [14, 3, 1, 4]])
|
4574
|
+
sage: l2 = Link([[1, 8, 2, 9], [9, 2, 10, 3], [3, 14, 4, 1],
|
4575
|
+
....: [13, 4, 14, 5], [5, 12, 6, 13], [11, 6, 12, 7],
|
4576
|
+
....: [7, 10, 8, 11]])
|
4577
|
+
sage: l1.is_isotopic(l2) # needs sage.libs.braiding sage.libs.homfly
|
4578
|
+
True
|
4579
|
+
|
4580
|
+
sage: l3 = l2.mirror_image()
|
4581
|
+
sage: l1.is_isotopic(l3) # needs sage.libs.braiding sage.libs.homfly
|
4582
|
+
False
|
4583
|
+
|
4584
|
+
sage: # optional - database_knotinfo
|
4585
|
+
sage: L = KnotInfo.L7a7_0_0
|
4586
|
+
sage: L.series(oriented=True).inject()
|
4587
|
+
Defining L7a7
|
4588
|
+
sage: L == L7a7(0)
|
4589
|
+
True
|
4590
|
+
sage: l = L.link()
|
4591
|
+
sage: l.is_isotopic(L7a7(1).link()) # needs sage.libs.braiding sage.libs.homf;y
|
4592
|
+
Traceback (most recent call last):
|
4593
|
+
...
|
4594
|
+
NotImplementedError: comparison not possible!
|
4595
|
+
sage: l.is_isotopic(L7a7(2).link()) # needs sage.libs.braiding sage.libs.homf;y
|
4596
|
+
True
|
4597
|
+
sage: l.is_isotopic(L7a7(3).link()) # needs sage.libs.braiding sage.libs.homf;y
|
4598
|
+
False
|
4599
|
+
|
4600
|
+
Using verbosity::
|
4601
|
+
|
4602
|
+
sage: set_verbose(1)
|
4603
|
+
sage: l1.is_isotopic(l2) # needs sage.libs.braiding sage.libs.homf;y
|
4604
|
+
verbose 1 (... link.py, is_isotopic) identified by KnotInfo (KnotInfo.K7_2, SymmetryMutant.mirror_image)
|
4605
|
+
True
|
4606
|
+
sage: l1.is_isotopic(l3) # needs sage.libs.braiding sage.libs.homf;y
|
4607
|
+
verbose 1 (... link.py, is_isotopic) different Homfly-PT polynomials
|
4608
|
+
False
|
4609
|
+
sage: set_verbose(0)
|
4610
|
+
|
4611
|
+
TESTS:
|
4612
|
+
|
4613
|
+
Check that :issue:`37668` is fixed::
|
4614
|
+
|
4615
|
+
sage: L = KnotInfo.L6a2_0
|
4616
|
+
sage: L1 = L.link()
|
4617
|
+
sage: L2 = L.link(L.items.braid_notation)
|
4618
|
+
sage: set_verbose(1)
|
4619
|
+
sage: L1.is_isotopic(L2) # needs sage.libs.braiding sage.libs.homf;y
|
4620
|
+
verbose 1 (... link.py, is_isotopic) identified by KnotInfo uniquely (KnotInfo.L6a2_0, SymmetryMutant.itself)
|
4621
|
+
True
|
4622
|
+
sage: KnotInfo.K0_1.link().is_isotopic(KnotInfo.L2a1_0.link()) # needs sage.libs.braiding sage.libs.homf;y
|
4623
|
+
verbose 1 (... link.py, is_isotopic) different number of components
|
4624
|
+
False
|
4625
|
+
|
4626
|
+
sage: # optional - database_knotinfo, needs sage.libs.braiding sage.libs.homf;y
|
4627
|
+
sage: K = KnotInfo.K10_67
|
4628
|
+
sage: K1 = K.link()
|
4629
|
+
sage: K1r = K.link().reverse()
|
4630
|
+
sage: K1.is_isotopic(K1r)
|
4631
|
+
verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([<KnotInfo.K10_67: '10_67'>], SymmetryMutant.itself != [<KnotInfo.K10_67: '10_67'>], SymmetryMutant.reverse)
|
4632
|
+
False
|
4633
|
+
sage: KnotInfo.K10_25.link().is_isotopic(KnotInfo.K10_56.link())
|
4634
|
+
verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([<KnotInfo.K10_25: '10_25'>] != [<KnotInfo.K10_56: '10_56'>], SymmetryMutant.itself)
|
4635
|
+
False
|
4636
|
+
sage: KnotInfo.L8n2_0.link().is_isotopic(KnotInfo.L8n2_1.link())
|
4637
|
+
verbose 1 (... link.py, is_isotopic) identified by KnotInfoSeries ([<KnotInfo.L8n2_0: 'L8n2{0}'>, <KnotInfo.L8n2_1: 'L8n2{1}'>], SymmetryMutant.reverse)
|
4638
|
+
True
|
4639
|
+
sage: set_verbose(0)
|
4640
|
+
"""
|
4641
|
+
from sage.misc.verbose import verbose
|
4642
|
+
if not isinstance(other, Link):
|
4643
|
+
verbose('other is not a link')
|
4644
|
+
return False
|
4645
|
+
|
4646
|
+
if self == other:
|
4647
|
+
# surely isotopic
|
4648
|
+
verbose('identified by representation')
|
4649
|
+
return True
|
4650
|
+
|
4651
|
+
if self.number_of_components() != other.number_of_components():
|
4652
|
+
# surely non isotopic
|
4653
|
+
verbose('different number of components')
|
4654
|
+
return False
|
4655
|
+
|
4656
|
+
if self.homfly_polynomial() != other.homfly_polynomial():
|
4657
|
+
# surely non isotopic
|
4658
|
+
verbose('different Homfly-PT polynomials')
|
4659
|
+
return False
|
4660
|
+
|
4661
|
+
if self._markov_move_cmp(other.braid()):
|
4662
|
+
# surely isotopic
|
4663
|
+
verbose('identified via Markov moves')
|
4664
|
+
return True
|
4665
|
+
|
4666
|
+
slists, sproves = self._knotinfo_matching_dict()
|
4667
|
+
olists, oproves = other._knotinfo_matching_dict()
|
4668
|
+
proved_s = None
|
4669
|
+
proved_o = None
|
4670
|
+
for k in slists.keys():
|
4671
|
+
sl = slists[k]
|
4672
|
+
ol = olists[k]
|
4673
|
+
sp = sproves[k]
|
4674
|
+
op = oproves[k]
|
4675
|
+
if sp and op:
|
4676
|
+
if sorted(sl) == sorted(ol):
|
4677
|
+
if len(sl) == 1:
|
4678
|
+
verbose('identified by KnotInfo uniquely (%s, %s)' % (sl[0], k))
|
4679
|
+
return True
|
4680
|
+
elif not self.is_knot():
|
4681
|
+
if len({l.series(oriented=True) for l in sl}) == 1:
|
4682
|
+
# all matches are orientation mutants of each other
|
4683
|
+
verbose('identified by KnotInfoSeries (%s, %s)' % (sl, k))
|
4684
|
+
return True
|
4685
|
+
else:
|
4686
|
+
verbose('KnotInfoSeries non-unique (%s, %s)' % (sl, k))
|
4687
|
+
else:
|
4688
|
+
verbose('KnotInfo non-unique (%s, %s)' % (sl, k))
|
4689
|
+
else:
|
4690
|
+
common = [l for l in sl if l in ol]
|
4691
|
+
if common:
|
4692
|
+
# better don't trust
|
4693
|
+
verbose('KnotInfo common: %s' % common)
|
4694
|
+
else:
|
4695
|
+
verbose('unidentified by KnotInfo (%s != %s, %s)' % (sl, ol, k))
|
4696
|
+
return False
|
4697
|
+
elif sp:
|
4698
|
+
proved_s = (sl, k)
|
4699
|
+
elif op:
|
4700
|
+
proved_o = (ol, k)
|
4701
|
+
if proved_s and proved_o:
|
4702
|
+
sl, sk = proved_s
|
4703
|
+
ol, ok = proved_o
|
4704
|
+
verbose('unidentified by KnotInfo (%s, %s != %s, %s)' % (sl, sk, ol, ok))
|
4705
|
+
return False
|
4706
|
+
|
4707
|
+
for k in slists.keys():
|
4708
|
+
# second loop without provings
|
4709
|
+
sl = slists[k]
|
4710
|
+
ol = olists[k]
|
4711
|
+
if sorted(sl) == sorted(ol) and len(sl) == 1:
|
4712
|
+
verbose('identified by KnotInfo (%s, %s)' % (sl[0], k))
|
4713
|
+
return True
|
4714
|
+
|
4715
|
+
raise NotImplementedError('comparison not possible!')
|