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.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. 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!')