passagemath-graphs 10.5.43__cp39-cp39-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 (258) hide show
  1. passagemath_graphs-10.5.43.dist-info/METADATA +293 -0
  2. passagemath_graphs-10.5.43.dist-info/RECORD +258 -0
  3. passagemath_graphs-10.5.43.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.5.43.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 +2552 -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 +125 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1556 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2262 -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 +534 -0
  25. sage/combinat/designs/database.py +5614 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-39-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-39-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-39-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 +548 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2243 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-39-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +966 -0
  44. sage/combinat/designs/resolvable_bibd.py +781 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-39-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/cartesian_product.py +493 -0
  57. sage/combinat/posets/d_complete.py +182 -0
  58. sage/combinat/posets/elements.py +273 -0
  59. sage/combinat/posets/forest.py +30 -0
  60. sage/combinat/posets/hasse_cython.cpython-39-aarch64-linux-gnu.so +0 -0
  61. sage/combinat/posets/hasse_cython.pyx +174 -0
  62. sage/combinat/posets/hasse_diagram.py +3678 -0
  63. sage/combinat/posets/incidence_algebras.py +796 -0
  64. sage/combinat/posets/lattices.py +5119 -0
  65. sage/combinat/posets/linear_extension_iterator.cpython-39-aarch64-linux-gnu.so +0 -0
  66. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  67. sage/combinat/posets/linear_extensions.py +1039 -0
  68. sage/combinat/posets/mobile.py +275 -0
  69. sage/combinat/posets/moebius_algebra.py +776 -0
  70. sage/combinat/posets/poset_examples.py +2131 -0
  71. sage/combinat/posets/posets.py +9169 -0
  72. sage/combinat/rooted_tree.py +1070 -0
  73. sage/combinat/shard_order.py +239 -0
  74. sage/combinat/tamari_lattices.py +384 -0
  75. sage/combinat/yang_baxter_graph.py +923 -0
  76. sage/databases/all__sagemath_graphs.py +1 -0
  77. sage/databases/knotinfo_db.py +1230 -0
  78. sage/ext_data/all__sagemath_graphs.py +1 -0
  79. sage/ext_data/graphs/graph_plot_js.html +330 -0
  80. sage/ext_data/kenzo/CP2.txt +45 -0
  81. sage/ext_data/kenzo/CP3.txt +349 -0
  82. sage/ext_data/kenzo/CP4.txt +4774 -0
  83. sage/ext_data/kenzo/README.txt +49 -0
  84. sage/ext_data/kenzo/S4.txt +20 -0
  85. sage/graphs/all.py +42 -0
  86. sage/graphs/asteroidal_triples.cpython-39-aarch64-linux-gnu.so +0 -0
  87. sage/graphs/asteroidal_triples.pyx +299 -0
  88. sage/graphs/base/all.py +1 -0
  89. sage/graphs/base/boost_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sage/graphs/base/boost_graph.pxd +106 -0
  91. sage/graphs/base/boost_graph.pyx +3045 -0
  92. sage/graphs/base/c_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  93. sage/graphs/base/c_graph.pxd +106 -0
  94. sage/graphs/base/c_graph.pyx +5096 -0
  95. sage/graphs/base/dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  96. sage/graphs/base/dense_graph.pxd +26 -0
  97. sage/graphs/base/dense_graph.pyx +757 -0
  98. sage/graphs/base/graph_backends.cpython-39-aarch64-linux-gnu.so +0 -0
  99. sage/graphs/base/graph_backends.pxd +5 -0
  100. sage/graphs/base/graph_backends.pyx +797 -0
  101. sage/graphs/base/overview.py +85 -0
  102. sage/graphs/base/sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  103. sage/graphs/base/sparse_graph.pxd +90 -0
  104. sage/graphs/base/sparse_graph.pyx +1653 -0
  105. sage/graphs/base/static_dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  106. sage/graphs/base/static_dense_graph.pxd +5 -0
  107. sage/graphs/base/static_dense_graph.pyx +1032 -0
  108. sage/graphs/base/static_sparse_backend.cpython-39-aarch64-linux-gnu.so +0 -0
  109. sage/graphs/base/static_sparse_backend.pxd +27 -0
  110. sage/graphs/base/static_sparse_backend.pyx +1580 -0
  111. sage/graphs/base/static_sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  112. sage/graphs/base/static_sparse_graph.pxd +37 -0
  113. sage/graphs/base/static_sparse_graph.pyx +1304 -0
  114. sage/graphs/bipartite_graph.py +2709 -0
  115. sage/graphs/centrality.cpython-39-aarch64-linux-gnu.so +0 -0
  116. sage/graphs/centrality.pyx +965 -0
  117. sage/graphs/cographs.py +519 -0
  118. sage/graphs/comparability.cpython-39-aarch64-linux-gnu.so +0 -0
  119. sage/graphs/comparability.pyx +813 -0
  120. sage/graphs/connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/connectivity.pxd +157 -0
  122. sage/graphs/connectivity.pyx +4813 -0
  123. sage/graphs/convexity_properties.cpython-39-aarch64-linux-gnu.so +0 -0
  124. sage/graphs/convexity_properties.pxd +16 -0
  125. sage/graphs/convexity_properties.pyx +827 -0
  126. sage/graphs/digraph.py +4410 -0
  127. sage/graphs/digraph_generators.py +1921 -0
  128. sage/graphs/distances_all_pairs.cpython-39-aarch64-linux-gnu.so +0 -0
  129. sage/graphs/distances_all_pairs.pxd +12 -0
  130. sage/graphs/distances_all_pairs.pyx +2938 -0
  131. sage/graphs/domination.py +1363 -0
  132. sage/graphs/dot2tex_utils.py +100 -0
  133. sage/graphs/edge_connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  134. sage/graphs/edge_connectivity.pyx +1215 -0
  135. sage/graphs/generators/all.py +1 -0
  136. sage/graphs/generators/basic.py +1769 -0
  137. sage/graphs/generators/chessboard.py +538 -0
  138. sage/graphs/generators/classical_geometries.py +1611 -0
  139. sage/graphs/generators/degree_sequence.py +235 -0
  140. sage/graphs/generators/distance_regular.cpython-39-aarch64-linux-gnu.so +0 -0
  141. sage/graphs/generators/distance_regular.pyx +2846 -0
  142. sage/graphs/generators/families.py +4749 -0
  143. sage/graphs/generators/intersection.py +565 -0
  144. sage/graphs/generators/platonic_solids.py +262 -0
  145. sage/graphs/generators/random.py +2623 -0
  146. sage/graphs/generators/smallgraphs.py +5741 -0
  147. sage/graphs/generators/world_map.py +724 -0
  148. sage/graphs/generic_graph.py +26395 -0
  149. sage/graphs/generic_graph_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  150. sage/graphs/generic_graph_pyx.pxd +34 -0
  151. sage/graphs/generic_graph_pyx.pyx +1626 -0
  152. sage/graphs/genus.cpython-39-aarch64-linux-gnu.so +0 -0
  153. sage/graphs/genus.pyx +623 -0
  154. sage/graphs/graph.py +9362 -0
  155. sage/graphs/graph_coloring.cpython-39-aarch64-linux-gnu.so +0 -0
  156. sage/graphs/graph_coloring.pyx +2284 -0
  157. sage/graphs/graph_database.py +1122 -0
  158. sage/graphs/graph_decompositions/all.py +1 -0
  159. sage/graphs/graph_decompositions/bandwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  160. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  161. sage/graphs/graph_decompositions/clique_separators.cpython-39-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/clique_separators.pyx +595 -0
  163. sage/graphs/graph_decompositions/cutwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  165. sage/graphs/graph_decompositions/fast_digraph.cpython-39-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  167. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  168. sage/graphs/graph_decompositions/graph_products.cpython-39-aarch64-linux-gnu.so +0 -0
  169. sage/graphs/graph_decompositions/graph_products.pyx +462 -0
  170. sage/graphs/graph_decompositions/modular_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  173. sage/graphs/graph_decompositions/slice_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  174. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.pyx +1080 -0
  176. sage/graphs/graph_decompositions/tree_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  177. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  179. sage/graphs/graph_decompositions/vertex_separation.cpython-39-aarch64-linux-gnu.so +0 -0
  180. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  181. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  182. sage/graphs/graph_editor.py +82 -0
  183. sage/graphs/graph_generators.py +3301 -0
  184. sage/graphs/graph_generators_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  185. sage/graphs/graph_generators_pyx.pyx +95 -0
  186. sage/graphs/graph_input.py +812 -0
  187. sage/graphs/graph_latex.py +2064 -0
  188. sage/graphs/graph_list.py +367 -0
  189. sage/graphs/graph_plot.py +1749 -0
  190. sage/graphs/graph_plot_js.py +338 -0
  191. sage/graphs/hyperbolicity.cpython-39-aarch64-linux-gnu.so +0 -0
  192. sage/graphs/hyperbolicity.pyx +1702 -0
  193. sage/graphs/hypergraph_generators.py +364 -0
  194. sage/graphs/independent_sets.cpython-39-aarch64-linux-gnu.so +0 -0
  195. sage/graphs/independent_sets.pxd +13 -0
  196. sage/graphs/independent_sets.pyx +402 -0
  197. sage/graphs/isgci.py +1033 -0
  198. sage/graphs/isoperimetric_inequalities.cpython-39-aarch64-linux-gnu.so +0 -0
  199. sage/graphs/isoperimetric_inequalities.pyx +453 -0
  200. sage/graphs/line_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/line_graph.pyx +627 -0
  202. sage/graphs/lovasz_theta.py +77 -0
  203. sage/graphs/matching.py +1633 -0
  204. sage/graphs/matching_covered_graph.py +3566 -0
  205. sage/graphs/orientations.py +1504 -0
  206. sage/graphs/partial_cube.py +459 -0
  207. sage/graphs/path_enumeration.cpython-39-aarch64-linux-gnu.so +0 -0
  208. sage/graphs/path_enumeration.pyx +2040 -0
  209. sage/graphs/pq_trees.py +1129 -0
  210. sage/graphs/print_graphs.py +201 -0
  211. sage/graphs/schnyder.py +865 -0
  212. sage/graphs/spanning_tree.cpython-39-aarch64-linux-gnu.so +0 -0
  213. sage/graphs/spanning_tree.pyx +1457 -0
  214. sage/graphs/strongly_regular_db.cpython-39-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/strongly_regular_db.pyx +3340 -0
  216. sage/graphs/traversals.cpython-39-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/traversals.pxd +9 -0
  218. sage/graphs/traversals.pyx +1871 -0
  219. sage/graphs/trees.cpython-39-aarch64-linux-gnu.so +0 -0
  220. sage/graphs/trees.pxd +15 -0
  221. sage/graphs/trees.pyx +310 -0
  222. sage/graphs/tutte_polynomial.py +713 -0
  223. sage/graphs/views.cpython-39-aarch64-linux-gnu.so +0 -0
  224. sage/graphs/views.pyx +794 -0
  225. sage/graphs/weakly_chordal.cpython-39-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/weakly_chordal.pyx +562 -0
  227. sage/groups/all__sagemath_graphs.py +1 -0
  228. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  229. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-39-aarch64-linux-gnu.so +0 -0
  231. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  233. sage/knots/all.py +6 -0
  234. sage/knots/free_knotinfo_monoid.py +507 -0
  235. sage/knots/gauss_code.py +291 -0
  236. sage/knots/knot.py +682 -0
  237. sage/knots/knot_table.py +284 -0
  238. sage/knots/knotinfo.py +2880 -0
  239. sage/knots/link.py +4682 -0
  240. sage/sandpiles/all.py +13 -0
  241. sage/sandpiles/examples.py +225 -0
  242. sage/sandpiles/sandpile.py +6365 -0
  243. sage/topology/all.py +22 -0
  244. sage/topology/cell_complex.py +1214 -0
  245. sage/topology/cubical_complex.py +1977 -0
  246. sage/topology/delta_complex.py +1806 -0
  247. sage/topology/filtered_simplicial_complex.py +744 -0
  248. sage/topology/moment_angle_complex.py +823 -0
  249. sage/topology/simplicial_complex.py +5161 -0
  250. sage/topology/simplicial_complex_catalog.py +86 -0
  251. sage/topology/simplicial_complex_examples.py +1692 -0
  252. sage/topology/simplicial_complex_homset.py +205 -0
  253. sage/topology/simplicial_complex_morphism.py +836 -0
  254. sage/topology/simplicial_set.py +4102 -0
  255. sage/topology/simplicial_set_catalog.py +55 -0
  256. sage/topology/simplicial_set_constructions.py +2954 -0
  257. sage/topology/simplicial_set_examples.py +865 -0
  258. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1556 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # sage.doctest: needs sage.graphs
3
+ r"""
4
+ Helper functions for mutation types of quivers
5
+
6
+ This file contains helper functions for detecting the mutation type of
7
+ a cluster algebra or quiver.
8
+
9
+ For the compendium on the cluster algebra and quiver package see [MS2011]_
10
+
11
+ AUTHORS:
12
+
13
+ - Gregg Musiker
14
+ - Christian Stump
15
+ """
16
+
17
+ # ****************************************************************************
18
+ # Copyright (C) 2011 Gregg Musiker <musiker@math.mit.edu>
19
+ # Christian Stump <christian.stump@univie.ac.at>
20
+ #
21
+ # Distributed under the terms of the GNU General Public License (GPL)
22
+ # https://www.gnu.org/licenses/
23
+ # ****************************************************************************
24
+
25
+ from pathlib import Path
26
+ import pickle
27
+
28
+ from copy import copy
29
+
30
+ from sage.misc.cachefunc import cached_function
31
+ from sage.misc.flatten import flatten
32
+ from sage.graphs.digraph import DiGraph
33
+ from sage.combinat.combination import Combinations
34
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType
35
+
36
+
37
+ def is_mutation_finite(M, nr_of_checks=None):
38
+ r"""
39
+ Use a non-deterministic method by random mutations in various
40
+ directions. Can result in a wrong answer.
41
+
42
+ .. WARNING::
43
+
44
+ This method modifies the input matrix ``M``!
45
+
46
+ INPUT:
47
+
48
+ - ``nr_of_checks`` -- number of mutations applied (default: ``None``);
49
+ standard is 500*(number of vertices of self)
50
+
51
+ ALGORITHM:
52
+
53
+ A quiver is mutation infinite if and only if every edge label (a,-b) satisfy a*b > 4.
54
+ Thus, we apply random mutations in random directions
55
+
56
+ EXAMPLES::
57
+
58
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import is_mutation_finite
59
+
60
+ sage: Q = ClusterQuiver(['A',10]) # needs sage.modules
61
+ sage: M = Q.b_matrix() # needs sage.modules
62
+ sage: is_mutation_finite(M) # needs sage.modules
63
+ (True, None)
64
+
65
+ sage: # needs sage.modules
66
+ sage: Q = ClusterQuiver([(0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(6,7),(7,8),(2,9)])
67
+ sage: M = Q.b_matrix()
68
+ sage: is_mutation_finite(M) # random
69
+ (False, [9, 6, 9, 8, 9, 4, 0, 4, 5, 2, 1, 0, 1, 0, 7, 1, 9, 2, 5, 7, 8, 6, 3, 0, 2, 5, 4, 2, 6, 9, 2, 7, 3, 5, 3, 7, 9, 5, 9, 0, 2, 7, 9, 2, 4, 2, 1, 6, 9, 4, 3, 5, 0, 8, 2, 9, 5, 3, 7, 0, 1, 8, 3, 7, 2, 7, 3, 4, 8, 0, 4, 9, 5, 2, 8, 4, 8, 1, 7, 8, 9, 1, 5, 0, 8, 7, 4, 8, 9, 8, 0, 7, 4, 7, 1, 2, 8, 6, 1, 3, 9, 3, 9, 1, 3, 2, 4, 9, 5, 1, 2, 9, 4, 8, 5, 3, 4, 6, 8, 9, 2, 5, 9, 4, 6, 2, 1, 4, 9, 6, 0, 9, 8, 0, 4, 7, 9, 2, 1, 6])
70
+
71
+ Check that :issue:`19495` is fixed::
72
+
73
+ sage: dg = DiGraph(); dg.add_vertex(0); S = ClusterSeed(dg); S # needs sage.modules
74
+ A seed for a cluster algebra of rank 1
75
+ sage: S.is_mutation_finite() # needs sage.modules
76
+ True
77
+ """
78
+ import random
79
+ n = M.ncols()
80
+ if n <= 2:
81
+ return True, None
82
+ if nr_of_checks is None:
83
+ nr_of_checks = 1000 * n
84
+ k = random.randint(0, n - 1)
85
+ path = []
86
+ for i in range(nr_of_checks):
87
+ # avoid mutating back in the same direction
88
+ next_k = random.randint(0, n - 2)
89
+ if next_k >= k:
90
+ next_k += 1
91
+ k = next_k
92
+ M.mutate(k)
93
+ path.append(k)
94
+ for i, j in M.nonzero_positions():
95
+ if i < j and M[i, j] * M[j, i] < -4:
96
+ return False, path
97
+ return True, None
98
+
99
+
100
+ def _triangles(dg):
101
+ """
102
+ Return a list of all oriented triangles in the digraph ``dg``.
103
+
104
+ EXAMPLES::
105
+
106
+ sage: # needs sage.modules
107
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _triangles
108
+ sage: Q = ClusterQuiver(['A',3])
109
+ sage: _triangles(Q.digraph())
110
+ []
111
+ sage: Q.mutate([0,1])
112
+ sage: _triangles(Q.digraph())
113
+ [([(2, 0), (0, 1), (1, 2)], True)]
114
+ sage: Q2 = ClusterQuiver(['A',[1,2],1])
115
+ sage: _triangles(Q2.digraph())
116
+ [([(1, 2), (1, 0), (2, 0)], False)]
117
+ sage: Q2.mutate(2)
118
+ sage: _triangles(Q2.digraph())
119
+ [([(1, 0), (0, 2), (2, 1)], True)]
120
+ """
121
+ E = dg.edges(sort=True, labels=False)
122
+ V = list(dg)
123
+ trians = []
124
+ flat_trians = []
125
+ for e in E:
126
+ v1, v2 = e
127
+ for v in V:
128
+ if v not in e:
129
+ if (v, v1) in E:
130
+ if (v2, v) in E:
131
+ flat_trian = sorted([v,v1,v2])
132
+ if flat_trian not in flat_trians:
133
+ flat_trians.append( flat_trian )
134
+ trians.append( ( [(v,v1),(v1,v2),(v2,v)], True ) )
135
+ elif (v, v2) in E:
136
+ flat_trian = sorted([v,v1,v2])
137
+ if flat_trian not in flat_trians:
138
+ flat_trians.append( flat_trian )
139
+ trians.append( ( [(v,v1),(v1,v2),(v,v2)], False ) )
140
+ if (v1, v) in E:
141
+ if (v2, v) in E:
142
+ flat_trian = sorted([v,v1,v2])
143
+ if flat_trian not in flat_trians:
144
+ flat_trians.append( flat_trian )
145
+ trians.append( ( [(v1,v),(v1,v2),(v2,v)], False ) )
146
+ elif (v, v2) in E:
147
+ flat_trian = sorted([v,v1,v2])
148
+ if flat_trian not in flat_trians:
149
+ flat_trians.append( flat_trian )
150
+ trians.append( ( [(v1,v),(v1,v2),(v,v2)], False ) )
151
+ return trians
152
+
153
+
154
+ def _all_induced_cycles_iter(dg):
155
+ """
156
+ Return an iterator for all induced oriented cycles of length
157
+ greater than or equal to 4 in the digraph ``dg``.
158
+
159
+ EXAMPLES::
160
+
161
+ sage: # needs sage.modules
162
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _all_induced_cycles_iter
163
+ sage: Q = ClusterQuiver(['A',[6,0],1]); Q
164
+ Quiver on 6 vertices of type ['D', 6]
165
+ sage: next(_all_induced_cycles_iter(Q.digraph()))
166
+ ([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)], True)
167
+ sage: Q.mutate(0)
168
+ sage: next(_all_induced_cycles_iter(Q.digraph()))
169
+ ([(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)], True)
170
+ sage: Q2 = ClusterQuiver(['A',[2,3],1])
171
+ sage: next(_all_induced_cycles_iter(Q2.digraph()))
172
+ ([(1, 0), (1, 2), (3, 2), (3, 4), (4, 0)], False)
173
+ """
174
+ dg_new = DiGraph(dg)
175
+ E = dg_new.edges(sort=True)
176
+ for v1, v2, label in E:
177
+ dg_new.add_edge((v2, v1, label))
178
+ induced_sets = []
179
+ cycle_iter = dg_new.all_cycles_iterator(simple=True)
180
+ for cycle in cycle_iter:
181
+ if len(cycle) > 3:
182
+ cycle_set = set(cycle)
183
+ if not any(cycle_set.issuperset(induced_set) for induced_set in induced_sets):
184
+ induced_sets.append(cycle_set)
185
+ if len(cycle) > 4:
186
+ sg = dg.subgraph(cycle)
187
+ is_oriented = True
188
+ V = list(sg)
189
+ while is_oriented and V:
190
+ v = V.pop()
191
+ if not sg.in_degree(v) == 1:
192
+ is_oriented = False
193
+ yield (sg.edges(sort=True, labels=False), is_oriented)
194
+
195
+ # a debug function
196
+
197
+
198
+ def _false_return(s=False):
199
+ """
200
+ Return 'unknown'.
201
+
202
+ Written for potential debugging purposes.
203
+
204
+ EXAMPLES::
205
+
206
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _false_return
207
+ sage: _false_return()
208
+ 'unknown'
209
+ """
210
+ # Uncomment these three lines for debugging purposes.
211
+ # if s:
212
+ # print('DEBUG: error %s' % s)
213
+ return 'unknown'
214
+
215
+
216
+ def _reset_dg(dg, vertices, dict_in_out, del_vertices):
217
+ """
218
+ Delete the specified vertices (del_vertices) from the DiGraph dg,
219
+ and the lists vertices and dict_in_out.
220
+
221
+ Note that vertices and dict_in_out are the vertices of dg and a
222
+ dictionary of in- and out-degrees that depend on the digraph
223
+ ``dg`` but they are passed through as arguments so the function
224
+ can change their values.
225
+
226
+ EXAMPLES::
227
+
228
+ sage: # needs sage.modules
229
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _reset_dg
230
+ sage: dg = ClusterQuiver(['A',[2,2],1]).digraph(); dg
231
+ Digraph on 4 vertices
232
+ sage: vertices = list(dg)
233
+ sage: dict_in_out = {}
234
+ sage: for v in vertices: dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
235
+ sage: _reset_dg(dg,vertices, dict_in_out, [1])
236
+ sage: dg
237
+ Digraph on 3 vertices
238
+ sage: vertices
239
+ [0, 2, 3]
240
+ sage: dict_in_out
241
+ {0: (1, 0, 1), 2: (1, 0, 1), 3: (0, 2, 2)}
242
+ """
243
+ del_vertices = list(set(del_vertices))
244
+ for v in del_vertices:
245
+ if v in dg:
246
+ dg.delete_vertex(v)
247
+ else:
248
+ print(v)
249
+ print(dg.edges(sort=True))
250
+ vertices.remove(v)
251
+ del dict_in_out[v]
252
+ for v in vertices:
253
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
254
+
255
+
256
+ def _check_special_BC_cases(dg, n, check_letter_list, check_twist_list,
257
+ hope_letter_list, conn_vert_list=False):
258
+ """
259
+ Test if dg (on at most `n` vertices) is a quiver of type `A` or
260
+ `D` (as given in hope_letter_list) with conn_vert_list (if
261
+ given) as connecting vertices.
262
+
263
+ Since this is supposed to be run on a ``dg`` coming from a larger
264
+ quiver where vertices have already been removed (outside of the
265
+ connecting vertices), this program therefore recognizes the type
266
+ of the larger quiver as an `n`-vertex quiver of letter on
267
+ ``check_letter_list`` and twist on ``check_twist_list``. This
268
+ method is utilized in _connected_mutation_type to test for types
269
+ BC, BB, CC, BD, or CD.
270
+
271
+ EXAMPLES::
272
+
273
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _check_special_BC_cases
274
+ sage: dg = DiGraph(1)
275
+ sage: _check_special_BC_cases(dg, 3, ['BC'], [1], ['A'], [[0]])
276
+ ['BC', 2, 1]
277
+ sage: dg = DiGraph(2); dg.add_edge([0,1])
278
+ sage: _check_special_BC_cases(dg, 4, ['BC'], [1], ['A'], [[0]])
279
+ ['BC', 3, 1]
280
+ sage: dg = DiGraph(2); dg.add_edge([0,1])
281
+ sage: _check_special_BC_cases(dg, 4, ['BB'], [1], ['A'], [[0,1]])
282
+ ['BB', 3, 1]
283
+ sage: _check_special_BC_cases(dg, 4, ['C', 'CD'], [None, None], ['A', 'D'], [ [], [0] ])
284
+ ['C', 4]
285
+ sage: dg.add_edges([[1, 2], [1, 3]])
286
+ sage: _check_special_BC_cases(dg, 4, ['C', 'CD'], [None, None], ['A', 'D'], [ [], [0] ])
287
+ ['CD', 3, 1]
288
+ """
289
+ # if dg is not connected, mutation type is not recognized.
290
+ if not dg.is_connected():
291
+ return 'unknown'
292
+ # divides into cases depending on whether or not a list 'conn_vert_list' of connecting vertices is given.
293
+ if conn_vert_list:
294
+ mut_type = _connected_mutation_type_AAtildeD( dg, ret_conn_vert=True )
295
+ # when 'conn_vert_list' is given, the output of _connected_mutation_type_AAtildeD is
296
+ # either 'unknown' or a pair (mut_type, conn_verts). Then, it is tested if the vertices can be glued together as desired.
297
+ if not mut_type == 'unknown':
298
+ mut_type, conn_verts = mut_type
299
+ else:
300
+ # when conn_vert_list == False, the output of _connected_mutation_type _AAtildeD is simply 'unknown' or the mutation type.
301
+ # no 'connecting vertices' need to be computed.
302
+ mut_type = _connected_mutation_type_AAtildeD( dg, ret_conn_vert=False )
303
+ conn_verts = []
304
+ # when the mutation type is recognized, program now tries more specifically to figure out 'letter' and 'twist'
305
+ if not mut_type == 'unknown':
306
+ for i in range( len( check_letter_list ) ):
307
+ check_letter = check_letter_list[i]
308
+ check_twist = check_twist_list[i]
309
+ hope_letter = hope_letter_list[i]
310
+ if conn_vert_list:
311
+ conn_vert = set(conn_vert_list[i])
312
+ else:
313
+ conn_vert = set()
314
+ # Now, tries to connect up the quiver components (keeping in mind ['D',3] - ['A',3] equivalence)
315
+ if hope_letter == 'D' and mut_type._letter == 'A' and mut_type._rank == 3 and not mut_type._twist:
316
+ hope_letter = 'A'
317
+ if conn_vert_list:
318
+ conn_verts = list(set(dg).difference(conn_verts))
319
+ if mut_type._letter == hope_letter and not mut_type._twist and conn_vert.issubset(conn_verts):
320
+ if len(check_letter) > 1:
321
+ check_twist = 1
322
+ if check_twist:
323
+ n -= 1
324
+ return QuiverMutationType([check_letter, n, check_twist])
325
+ return 'unknown'
326
+
327
+
328
+ def _connected_mutation_type(dg):
329
+ """
330
+ Assuming that ``dg`` is a connected digraph, checks the mutation
331
+ type of ``dg`` as a valued quiver.
332
+
333
+ EXAMPLES::
334
+
335
+ sage: # needs sage.modules
336
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type
337
+ sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
338
+ sage: dg = ClusterQuiver(['A',3]).digraph(); _connected_mutation_type( dg )
339
+ ['A', 3]
340
+ sage: dg = ClusterQuiver(['D',7]).digraph(); _connected_mutation_type( dg )
341
+ ['D', 7]
342
+ sage: dg = ClusterQuiver(['BC',4,1]).digraph(); _connected_mutation_type( dg )
343
+ ['BC', 4, 1]
344
+ """
345
+ dg = DiGraph( dg )
346
+ # defining some shorthands
347
+ n = dg.order()
348
+ edges = dg.edges(sort=True)
349
+ vertices = list(dg)
350
+ # initializing lists of the edges with labels (2,-1) or (1,-2); (4,-1) or (1,-4); or (2,-2), respectively
351
+ exc_labels = []
352
+ exc_labels41 = []
353
+ double_edges = []
354
+ # letter = None
355
+
356
+ # replacing higher labels by multiple edges. Multiple edges and acyclic is a sign that quiver is infinite mutation type with the exception of A_tilde where there might be one multiple edge with multiplicity 2. Multiple edges is at least a sign that the quiver is of 'undetermined finite mutation type'.
357
+ dg.allow_multiple_edges( True )
358
+ for edge in edges:
359
+ label = edge[2]
360
+ if label not in [(1,-1),(2,-2),(1,-2),(2,-1),(4,-1),(1,-4)]:
361
+ # _false_return(i) is a simple function that simply returns 'unknown'. For debugging purposes, it
362
+ # can also output 'DEBUG: error i' if desired.
363
+ # this command is used many times in this code, something times without the argument i.
364
+ return _false_return(2)
365
+ elif label == (2,-2):
366
+ dg.set_edge_label( edge[0], edge[1], 1 )
367
+ dg.add_edge( edge[0], edge[1], 1 )
368
+ double_edges.append( edge )
369
+ if len( double_edges ) > 1:
370
+ return _false_return()
371
+ elif label == (1,-1):
372
+ dg.set_edge_label( edge[0], edge[1], 1 )
373
+ elif label in [(2,-1),(1,-2)]:
374
+ exc_labels.append( edge )
375
+ elif label in [(1,-4),(4,-1)]:
376
+ exc_labels41.append( edge )
377
+
378
+ # creating a dictionary of in-, out- and total degrees
379
+ dict_in_out = {}
380
+ for v in vertices:
381
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
382
+
383
+ if len( exc_labels ) + len( exc_labels41 ) + len( double_edges ) > 4:
384
+ return _false_return()
385
+
386
+ # test for the labels (4,-1) and (1,-4) which can only appear in affine type BC
387
+ if exc_labels41:
388
+ # tests a two-vertex quiver to see if it is of type ['BC',1,1]
389
+ if len(exc_labels41) == 1 and dict_in_out[exc_labels41[0][0]][2] == dict_in_out[exc_labels41[0][1]][2] == 1:
390
+ return QuiverMutationType(['BC',1,1])
391
+ # test if quiver contains a triangle T with edges [ (0, 1, (2, -1)), (2, 0, (2,-1)), (1, 2, (1, -4)) ] or [ (0, 1, (1, -2)), (2, 0, (1,-2)), (1, 2, (4, -1)) ].
392
+ if len(exc_labels41) == 1 and len(exc_labels) == 2:
393
+ bool2 = exc_labels41[0][2] == (4,-1) and exc_labels[0][2] == exc_labels[1][2] == (1,-2)
394
+ bool3 = exc_labels41[0][2] == (1,-4) and exc_labels[0][2] == exc_labels[1][2] == (2,-1)
395
+ if bool2 or bool3:
396
+ v1, v2, label = exc_labels41[0]
397
+ label1, label2 = exc_labels
398
+ # delete the two vertices associated to the edge with label (1,-4) or (4,-1) and test if the rest of the quiver is of type A.
399
+ # the third vertex of the triangle T should be a connecting_vertex.
400
+ if label1[1] == label2[0] and label2[1] == v1 and v2 == label1[0] and dict_in_out[v1][2] == dict_in_out[v2][2] == 2:
401
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
402
+ return _check_special_BC_cases( dg, n, ['BC'], [1], ['A'], [[label1[1]]] )
403
+ elif label1[0] == label2[1] and label1[1] == v1 and v2 == label2[0] and dict_in_out[v1][2] == dict_in_out[v2][2] == 2:
404
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
405
+ return _check_special_BC_cases( dg, n, ['BC'], [1], ['A'], [[label1[0]]] )
406
+ else:
407
+ return _false_return()
408
+ else:
409
+ return _false_return()
410
+ else:
411
+ return _false_return()
412
+
413
+ # the program now performs further tests in the case that there are no edges of type (1,-4) nor (4,-1)
414
+
415
+ # first test for affine type C: if there are 4 exceptional labels, test if both belong to triangles with leaves
416
+ if len( exc_labels ) == 4:
417
+ exc_labels12 = [labl for labl in exc_labels if labl[2] == (1, -2)]
418
+ exc_labels21 = [labl for labl in exc_labels if labl[2] == (2, -1)]
419
+ # check that we have two labels of one kind and one label of the other
420
+ if len( exc_labels12 ) != 2 or len( exc_labels21 ) != 2:
421
+ return _false_return()
422
+
423
+ label121 = exc_labels12[0]
424
+ label122 = exc_labels12[1]
425
+ label211 = exc_labels21[0]
426
+ label212 = exc_labels21[1]
427
+
428
+ # affine type B
429
+ if label211[1] == label121[0] and label212[1] == label122[0]:
430
+ pass
431
+ elif label212[1] == label121[0] and label211[1] == label122[0]:
432
+ label211, label212 = label212, label211
433
+ # affine type C
434
+ elif label121[1] == label211[0] and label122[1] == label212[0]:
435
+ pass
436
+ elif label122[1] == label211[0] and label121[1] == label212[0]:
437
+ label211, label212 = label212, label211
438
+ # affine type BC
439
+ elif label121[1] == label211[0] and label212[1] == label122[0]:
440
+ pass
441
+ elif label121[1] == label212[0] and label211[1] == label122[0]:
442
+ label211, label212 = label212, label211
443
+ elif label122[1] == label211[0] and label212[1] == label121[0]:
444
+ label121, label122 = label122, label121
445
+ elif label122[1] == label212[0] and label211[1] == label121[0]:
446
+ pass
447
+ else:
448
+ return _false_return()
449
+
450
+ # tests for which configuration the two (1,-2) and two (2,-1) edges are in.
451
+ bool1 = dg.has_edge(label121[1],label211[0],1) and dict_in_out[label211[1]][0] == dict_in_out[label211[1]][1] == 1
452
+ bool2 = dg.has_edge(label122[1],label212[0],1) and dict_in_out[label212[1]][0] == dict_in_out[label212[1]][1] == 1
453
+ bool12 = not ( label121[1] == label122[1] and label211[0] == label212[0] )
454
+ bool3 = dg.has_edge(label211[1],label121[0],1) and dict_in_out[label121[1]][0] == dict_in_out[label121[1]][1] == 1
455
+ bool4 = dg.has_edge(label212[1],label122[0],1) and dict_in_out[label122[1]][0] == dict_in_out[label122[1]][1] == 1
456
+ bool34 = not ( label211[1] == label212[1] and label121[0] == label122[0] )
457
+ bool5 = dg.has_edge(label211[1],label121[0],1) and dict_in_out[label121[1]][0] == dict_in_out[label121[1]][1] == 1
458
+ bool6 = dg.has_edge(label122[1],label212[0],1) and dict_in_out[label212[1]][0] == dict_in_out[label212[1]][1] == 1
459
+ bool56 = not ( label211[1] == label122[1] and label121[0] == label212[0] )
460
+ bool7 = dg.has_edge(label212[1],label122[0],1) and dict_in_out[label122[1]][0] == dict_in_out[label122[1]][1] == 1
461
+ bool8 = dg.has_edge(label121[1],label211[0],1) and dict_in_out[label211[1]][0] == dict_in_out[label211[1]][1] == 1
462
+ bool78 = not ( label212[1] == label121[1] and label122[0] == label211[0] )
463
+
464
+ nb1 = len( set(dg.neighbors(label121[1])).intersection(dg.neighbors(label211[0])) ) <= 1
465
+ nb2 = len( set(dg.neighbors(label122[1])).intersection(dg.neighbors(label212[0])) ) <= 1
466
+ nb3 = len( set(dg.neighbors(label211[1])).intersection(dg.neighbors(label121[0])) ) <= 1
467
+ nb4 = len( set(dg.neighbors(label212[1])).intersection(dg.neighbors(label122[0])) ) <= 1
468
+
469
+ if bool1 and bool2 and bool12 and nb1 and nb2:
470
+ v1,v2 = label211[1],label212[1]
471
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
472
+ return _check_special_BC_cases( dg, n, ['CC'], [1], ['A'] )
473
+ if bool3 and bool4 and bool34 and nb3 and nb4:
474
+ v1,v2 = label121[1],label122[1]
475
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
476
+ return _check_special_BC_cases( dg, n, ['BB'], [1], ['A'] )
477
+ elif bool5 and bool6 and bool56 and nb2 and nb3:
478
+ v1,v2 = label121[1],label212[1]
479
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
480
+ return _check_special_BC_cases( dg, n, ['BC'], [1], ['A'] )
481
+ elif bool7 and bool8 and bool78 and nb1 and nb4:
482
+ v1,v2 = label122[1],label211[1]
483
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
484
+ return _check_special_BC_cases( dg, n, ['BC'], [1], ['A'] )
485
+ else:
486
+ return _false_return()
487
+
488
+ # first test for affine type C: if there are three exceptional labels, we must be in both cases below of the same construction
489
+ elif len( exc_labels ) == 3:
490
+ exc_labels12 = [labl for labl in exc_labels if labl[2] == (1, -2)]
491
+ exc_labels21 = [labl for labl in exc_labels if labl[2] == (2, -1)]
492
+ # check that we have two labels of one kind and one label of the other
493
+ if exc_labels12 == [] or exc_labels21 == []:
494
+ return _false_return()
495
+ if len( exc_labels12 ) == 2:
496
+ label1,label2 = exc_labels12
497
+ label3 = exc_labels21[0]
498
+ if dict_in_out[label2[0]][2] == 1 or dict_in_out[label2[1]][2] == 1:
499
+ label1, label2 = label2, label1
500
+ if dict_in_out[label1[0]][2] == 1:
501
+ if label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1],label2[0],1):
502
+ v1,v2 = label3[1],label2[0]
503
+ _reset_dg( dg, vertices, dict_in_out, [label2[1]] )
504
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
505
+ return _false_return()
506
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
507
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'],[[v1,v2]] )
508
+ else:
509
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
510
+ elif label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1],label3[0],1):
511
+ v1,v2 = label2[1],label3[0]
512
+ _reset_dg( dg, vertices, dict_in_out, [label3[1]] )
513
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
514
+ return _false_return()
515
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
516
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'],[[v1,v2]] )
517
+ else:
518
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
519
+ else:
520
+ return _false_return()
521
+ elif dict_in_out[label1[1]][2] == 1:
522
+ if label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1],label3[0],1):
523
+ v1,v2 = label2[1],label3[0]
524
+ _reset_dg( dg, vertices, dict_in_out, [label3[1]] )
525
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
526
+ return _false_return()
527
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
528
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'],[[v1,v2]] )
529
+ else:
530
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
531
+ elif label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1],label2[0],1):
532
+ v1,v2 = label3[1],label2[0]
533
+ _reset_dg( dg, vertices, dict_in_out, [label2[1]] )
534
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
535
+ return _false_return()
536
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
537
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'],[[v1,v2]] )
538
+ else:
539
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
540
+ else:
541
+ return _false_return()
542
+ elif label1[1] == label2[1] == label3[0] and dict_in_out[label1[1]][2] == 3 and dg.has_edge(label3[1],label1[0],1) and dg.has_edge(label3[1],label2[0],1) and dict_in_out[label2[0]][2] == dict_in_out[label1[0]][2] == 2:
543
+ _reset_dg( dg, vertices, dict_in_out, [label1[1]] )
544
+ return _check_special_BC_cases( dg, n, ['BD'],[1],['D'] )
545
+ elif label1[0] == label2[0] == label3[1] and dict_in_out[label1[0]][2] == 3 and dg.has_edge(label1[1],label3[0],1) and dg.has_edge(label2[1],label3[0],1) and dict_in_out[label2[1]][2] == dict_in_out[label1[1]][2] == 2:
546
+ _reset_dg( dg, vertices, dict_in_out, [label1[0]] )
547
+ return _check_special_BC_cases( dg, n, ['CD'],[1],['D'] )
548
+ else:
549
+ return _false_return()
550
+ elif len( exc_labels21 ) == 2:
551
+ label1,label2 = exc_labels21
552
+ label3 = exc_labels12[0]
553
+ if dict_in_out[label2[0]][2] == 1 or dict_in_out[label2[1]][2] == 1:
554
+ label1, label2 = label2, label1
555
+ if dict_in_out[label1[1]][2] == 1:
556
+ if label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1],label2[0],1):
557
+ v1, v2 = label3[1], label2[0]
558
+ _reset_dg( dg, vertices, dict_in_out, [label2[1]] )
559
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
560
+ return _false_return()
561
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
562
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'],[[v1,v2]] )
563
+ else:
564
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
565
+ elif label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1],label3[0],1):
566
+ v1, v2 = label2[1], label3[0]
567
+ _reset_dg( dg, vertices, dict_in_out, [label3[1]] )
568
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
569
+ return _false_return()
570
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
571
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'],[[v1,v2]] )
572
+ else:
573
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
574
+ else:
575
+ return _false_return()
576
+ elif dict_in_out[label1[0]][2] == 1:
577
+ if label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1],label3[0],1):
578
+ v1, v2 = label2[1], label3[0]
579
+ _reset_dg( dg, vertices, dict_in_out, [label3[1]] )
580
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
581
+ return _false_return()
582
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
583
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'],[[v1,v2]] )
584
+ else:
585
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
586
+ elif label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1],label2[0],1):
587
+ v1, v2 = label3[1], label2[0]
588
+ _reset_dg( dg, vertices, dict_in_out, [label2[1]] )
589
+ if len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) ) > 0:
590
+ return _false_return()
591
+ elif len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) ) > 0:
592
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'],[[v1,v2]] )
593
+ else:
594
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
595
+ else:
596
+ return _false_return()
597
+ elif label1[0] == label2[0] == label3[1] and dict_in_out[label1[0]][2] == 3 and dg.has_edge(label1[1],label3[0],1) and dict_in_out[label1[1]][2] == 2 and dg.has_edge(label2[1],label3[0],1) and dict_in_out[label2[1]][2] == 2:
598
+ _reset_dg( dg, vertices, dict_in_out, [label3[1]] )
599
+ return _check_special_BC_cases( dg, n, ['BD'],[1],['D'] )
600
+ elif label1[1] == label2[1] == label3[0] and dict_in_out[label3[0]][2] == 3 and dg.has_edge(label3[1],label1[0],1) and dict_in_out[label1[0]][2] == 2 and dg.has_edge(label3[1],label2[0],1) and dict_in_out[label2[0]][2] == 2:
601
+ _reset_dg( dg, vertices, dict_in_out, [label3[0]] )
602
+ return _check_special_BC_cases( dg, n, ['CD'],[1],['D'] )
603
+ else:
604
+ return _false_return()
605
+
606
+ # first test for finite types B and C: if there are two exceptional labels, they must belong to an oriented triangle and the vertex between must be a leaf
607
+ # first test for affine type C: if there are two exceptional labels, they must belong to leaves
608
+ # first test for affine type B: if there are two exceptional labels, they must be...
609
+ elif len( exc_labels ) == 2:
610
+ label1, label2 = exc_labels
611
+ if label1[1] == label2[0]:
612
+ pass
613
+ elif label2[1] == label1[0]:
614
+ label1, label2 = label2, label1
615
+ else:
616
+ # the exceptional case in affine type BC_2 is checked
617
+ if label2[2] == (1, -2) and label1[2] == (2, -1):
618
+ label1, label2 = label2, label1
619
+ if label1[2] == (1, -2) and label2[2] == (2, -1):
620
+ if label1[1] == label2[1] and dict_in_out[label1[1]][2] == 2 and dict_in_out[label1[0]][2] == 1 and dict_in_out[label2[0]][2] == 1:
621
+ return QuiverMutationType(['BC',2,1])
622
+ elif label1[0] == label2[0] and dict_in_out[label1[0]][2] == 2 and dict_in_out[label1[1]][2] == 1 and dict_in_out[label2[1]][2] == 1:
623
+ return QuiverMutationType(['BC',2,1])
624
+ # the cases in affine type B/C are checked where the exceptional labels connect to leaves
625
+ v11, v12, label1 = label1
626
+ v21, v22, label2 = label2
627
+ if dict_in_out[v11][2] == 1:
628
+ in_out1 = 'out'
629
+ elif dict_in_out[v12][2] == 1:
630
+ in_out1 = 'in'
631
+ else:
632
+ return _false_return()
633
+ if dict_in_out[v21][2] == 1:
634
+ in_out2 = 'out'
635
+ elif dict_in_out[v22][2] == 1:
636
+ in_out2 = 'in'
637
+ else:
638
+ return _false_return()
639
+ if label1 == label2:
640
+ if in_out1 == in_out2 == 'in':
641
+ if label1 == (1,-2):
642
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
643
+ else:
644
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
645
+ elif in_out1 == in_out2 == 'out':
646
+ if label1 == (1,-2):
647
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
648
+ else:
649
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
650
+ else:
651
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
652
+ else:
653
+ if in_out1 == in_out2:
654
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
655
+ else:
656
+ if label1 == (1,-2):
657
+ if in_out1 == 'in':
658
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
659
+ else:
660
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
661
+ else:
662
+ if in_out1 == 'in':
663
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
664
+ else:
665
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
666
+
667
+ v1,v,label1 = label1
668
+ v,v2,label2 = label2
669
+ if dg.has_multiple_edges():
670
+ if all( edge == (v2,v1,1) for edge in dg.multiple_edges() ):
671
+ if dict_in_out[v2][2] == dict_in_out[v1][2] == 3:
672
+ _reset_dg( dg, vertices, dict_in_out, [v1,v2] )
673
+ if label1 == (1,-2) and label2 == (2,-1):
674
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'],[[v]] )
675
+ elif label1 == (2,-1) and label2 == (1,-2):
676
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'],[[v]] )
677
+ else:
678
+ return _false_return()
679
+ elif dict_in_out[v][0] == dict_in_out[v][1] == 1:
680
+ dg.remove_multiple_edges()
681
+ dg = DiGraph( dg )
682
+ _reset_dg( dg, vertices, dict_in_out, [v] )
683
+ if dict_in_out[v1][0] == dict_in_out[v1][1] == dict_in_out[v2][0] == dict_in_out[v2][1] == 1 and next(dg.neighbor_out_iterator(v1)) == next(dg.neighbor_in_iterator(v2)):
684
+ if label1 == (2,-1) and label2 == (1,-2):
685
+ return _check_special_BC_cases( dg, n, ['CD'],[1],['A'] )
686
+ elif label1 == (1,-2) and label2 == (2,-1):
687
+ return _check_special_BC_cases( dg, n, ['BD'],[1],['A'] )
688
+ else:
689
+ return _false_return()
690
+ else:
691
+ return _false_return()
692
+ else:
693
+ return _false_return()
694
+ elif not dict_in_out[v][0] == 1 or not dict_in_out[v][1] == 1:
695
+ return _false_return()
696
+ else:
697
+ if dg.has_edge(v2,v1,1):
698
+ nr_same_neighbors = len( set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)) )
699
+ nr_other_neighbors = len( set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)) )
700
+ nr_contained_cycles = len([ cycle for cycle, is_oriented in _all_induced_cycles_iter( dg ) if v1 in flatten(cycle) and v2 in flatten(cycle) ] )
701
+ if nr_same_neighbors + nr_other_neighbors + nr_contained_cycles > 2:
702
+ return _false_return()
703
+ if label1 == (2,-1) and label2 == (1,-2):
704
+ if n == 4 and (nr_same_neighbors == 2 or nr_other_neighbors == 1):
705
+ return QuiverMutationType(['CD',n-1,1])
706
+ # checks for affine A
707
+ if nr_same_neighbors + nr_other_neighbors > 1:
708
+ mt_tmp = _check_special_BC_cases( dg, n, ['C','CD'],[None,None],['A','D'],[[],[v]] )
709
+ else:
710
+ _reset_dg( dg, vertices, dict_in_out, [v] )
711
+ mt_tmp = _check_special_BC_cases( dg, n, ['C','CD'],[None,None],['A','D'] )
712
+ if mt_tmp == 'unknown':
713
+ dg.delete_edges([[v2,v1],[v1,v],[v,v2]])
714
+ dg.add_edges([[v1,v2,1],[v,v1,1],[v2,v,1]])
715
+ if nr_same_neighbors + nr_other_neighbors > 1:
716
+ #_reset_dg( dg, vertices, dict_in_out, [v] )
717
+ return _check_special_BC_cases( dg, n, ['CD'],[None],['D'],[[v]] )
718
+ else:
719
+ return _check_special_BC_cases( dg, n, ['CD'],[None],['D'] )
720
+ else:
721
+ return mt_tmp
722
+ elif label1 == (1,-2) and label2 == (2,-1):
723
+ if n == 4 and (nr_same_neighbors == 2 or nr_other_neighbors == 1):
724
+ return QuiverMutationType(['BD',n-1,1])
725
+ # checks for affine A
726
+ if nr_same_neighbors + nr_other_neighbors > 1:
727
+ mt_tmp = _check_special_BC_cases( dg, n, ['B','BD'],[None,None],['A','D'],[[],[v]] )
728
+ else:
729
+ _reset_dg( dg, vertices, dict_in_out, [v] )
730
+ mt_tmp = _check_special_BC_cases( dg, n, ['B','BD'],[None,None],['A','D'] )
731
+ if mt_tmp == 'unknown':
732
+ dg.delete_edges([[v2,v1],[v1,v],[v,v2]])
733
+ dg.add_edges([[v1,v2,1],[v,v1,1],[v2,v,1]])
734
+ if nr_same_neighbors + nr_other_neighbors > 1:
735
+ #_reset_dg( dg, vertices, dict_in_out, [v] )
736
+ return _check_special_BC_cases( dg, n, ['BD'],[None],['D'],[[v]] )
737
+ else:
738
+ return _check_special_BC_cases( dg, n, ['BD'],[None],['D'] )
739
+ else:
740
+ return mt_tmp
741
+ else:
742
+ return _false_return()
743
+ elif dict_in_out[v1][2] == 1 and dict_in_out[v2][2] == 1:
744
+ if label1 == (1,-2) and label2 == (1,-2):
745
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
746
+ elif label1 == (2,-1) and label2 == (2,-1):
747
+ return _check_special_BC_cases( dg, n, ['BC'],[1],['A'] )
748
+ elif label1 == (1,-2) and label2 == (2,-1):
749
+ return _check_special_BC_cases( dg, n, ['CC'],[1],['A'] )
750
+ elif label1 == (2,-1) and label2 == (1,-2):
751
+ return _check_special_BC_cases( dg, n, ['BB'],[1],['A'] )
752
+ else:
753
+ return _false_return()
754
+ elif dict_in_out[v][0] == dict_in_out[v][1] == 1 and dict_in_out[v1][0] == dict_in_out[v1][1] == 1 and dict_in_out[v2][0] == dict_in_out[v2][1] == 1:
755
+ _reset_dg( dg, vertices, dict_in_out, [v] )
756
+ if n == 4 and ( label1, label2 ) == ( (2,-1), (1,-2) ):
757
+ return _check_special_BC_cases( dg, n, ['CD'],[1],['A'] )
758
+ elif n > 4 and ( label1, label2 ) == ( (2,-1), (1,-2) ):
759
+ return _check_special_BC_cases( dg, n, ['CD'],[1],['D'] )
760
+ elif n == 4 and ( label1, label2 ) == ( (1,-2), (2,-1) ):
761
+ return _check_special_BC_cases( dg, n, ['BD'],[1],['A'] )
762
+ elif n > 4 and ( label1, label2 ) == ( (1,-2), (2,-1) ):
763
+ return _check_special_BC_cases( dg, n, ['BD'],[1],['D'] )
764
+ else:
765
+ return _false_return()
766
+ else:
767
+ return _false_return()
768
+
769
+ # second tests for finite types B and C: if there is only one exceptional label, it must belong to a leaf
770
+ # also tests for affine type B: this exceptional label must belong to a leaf of a type D quiver
771
+ elif len( exc_labels ) == 1:
772
+ label = exc_labels[0]
773
+ v_out = label[0]
774
+ v_in = label[1]
775
+ label = label[2]
776
+ if label == (1,-2):
777
+ if dict_in_out[ v_in ][0] == 1 and dict_in_out[ v_in ][1] == 0:
778
+ #_reset_dg( dg, vertices, dict_in_out, [v_in] )
779
+ return _check_special_BC_cases( dg, n, ['B','BD'],[None,1],['A','D'],[[v_in],[v_in]] )
780
+ elif dict_in_out[ v_out ][0] == 0 and dict_in_out[ v_out ][1] == 1:
781
+ #_reset_dg( dg, vertices, dict_in_out, [v_out] )
782
+ return _check_special_BC_cases( dg, n, ['C','CD'],[None,1],['A','D'],[[v_out],[v_out]] )
783
+ else:
784
+ return _false_return()
785
+ elif label == (2,-1):
786
+ if dict_in_out[ v_out ][0] == 0 and dict_in_out[ v_out ][1] == 1:
787
+ #_reset_dg( dg, vertices, dict_in_out, [v_out] )
788
+ return _check_special_BC_cases( dg, n, ['B','BD'],[None,1],['A','D'],[[v_out],[v_out]] )
789
+ elif dict_in_out[ v_in ][0] == 1 and dict_in_out[ v_in ][1] == 0:
790
+ #_reset_dg( dg, vertices, dict_in_out, [v_in] )
791
+ return _check_special_BC_cases( dg, n, ['C','CD'],[None,1],['A','D'],[[v_in],[v_in]] )
792
+ else:
793
+ return _false_return()
794
+
795
+ # if no edges of type (1,-2) nor (2,-1), then tests for type A, affine A, or D.
796
+ return _connected_mutation_type_AAtildeD(dg)
797
+
798
+
799
+ def _connected_mutation_type_AAtildeD(dg, ret_conn_vert=False):
800
+ """
801
+ Return mutation type of ClusterQuiver(dg) for DiGraph dg if it is
802
+ of type finite A, affine A, or finite D.
803
+
804
+ For all other types (including affine D), outputs 'unknown'
805
+
806
+ See [BPRS2009]_ and [Vat2008]_ (by Vatne) for theoretical details.
807
+
808
+ .. TODO::
809
+
810
+ Improve this algorithm to also recognize affine D.
811
+
812
+ INPUT:
813
+
814
+ - ``ret_conn_vert`` -- boolean (default: ``False``); if ``True``,
815
+ returns 'connecting vertices', technical information that is
816
+ used in the algorithm
817
+
818
+ A brief description of the algorithm::
819
+
820
+ Looks for a long_cycle (of length >= 4) in the digraph dg. If there is more than one than the mutation_type is 'unknown'.
821
+ Otherwise, checks if each edge of long_cycle connects to a type A quiver. If so, then ClusterQuiver(dg) is of type D or affine A.
822
+
823
+ If there is no long_cycle, then checks that there are no multiple edges, all triangles are oriented,
824
+ no vertices of valence higher than 4, that vertices of valence 4 are incident to two oriented triangles,
825
+ and that a vertex of valence 3 has exactly two incident arrows as part of an oriented triangle.
826
+ All these checks ensures that ClusterQuiver(dg) is of type A except for three exceptions that are also checked.
827
+
828
+ EXAMPLES::
829
+
830
+ sage: # needs sage.modules
831
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type_AAtildeD
832
+ sage: Q = ClusterQuiver(['A',[7,0],1]); Q.mutate([0,1,4])
833
+ sage: _connected_mutation_type_AAtildeD(Q.digraph(),ret_conn_vert=True)
834
+ [['D', 7], [0, 4]]
835
+ sage: Q2 = ClusterQuiver(['A',[5,2],1]); Q2.mutate([4,5])
836
+ sage: _connected_mutation_type_AAtildeD(Q2.digraph() )
837
+ ['A', [2, 5], 1]
838
+ sage: Q3 = ClusterQuiver(['E',6]); Q3.mutate([5,2,1])
839
+ sage: _connected_mutation_type_AAtildeD(Q3.digraph(),ret_conn_vert=True)
840
+ 'unknown'
841
+ """
842
+ # naming the vertices
843
+ vertices = list(dg)
844
+ n = dg.order()
845
+
846
+ # Test if ClusterQuiver(dg) is of type D_n Type 1, i.e. A_{n-2} plus two leaves
847
+ if n > 3:
848
+ # check if any vertices have a neighborhood with two leaves. If so, prune the two leaves and retest on this smaller digraph.
849
+ # note that this step is unnecessary for digraphs with fewer than four vertices.
850
+ for v in vertices:
851
+ dead_neighbors = [ v_n for v_n in dg.neighbors(v) if dg.degree(v_n) == 1 ]
852
+ if len( dead_neighbors ) >= 2:
853
+ dg_tmp = DiGraph( dg )
854
+ dg_tmp.delete_vertices( dead_neighbors[:2] )
855
+ type_tmp = _connected_mutation_type_AAtildeD( dg_tmp, ret_conn_vert=True )
856
+ if type_tmp == 'unknown':
857
+ return _false_return()
858
+ # if smaller digraph is of finite A type with v as a 'connecting vertex', then glueing back the two leaves yields type finite D.
859
+ if type_tmp[0].letter() == 'A' and type_tmp[0].is_finite():
860
+ if v in type_tmp[1]:
861
+ type_tmp[1].remove(v)
862
+ if n == 4:
863
+ type_tmp[1].extend(dead_neighbors[:2])
864
+ if ret_conn_vert:
865
+ return [ QuiverMutationType( ['D',n] ), type_tmp[1] ]
866
+ else:
867
+ return QuiverMutationType( ['D',n] )
868
+ # note that if v is not a 'connecting vertex' then we make no conclusion either way.
869
+ else:
870
+ return _false_return(3)
871
+ # Test if ClusterQuiver(dg) is of type D_n Type 2 or 3, i.e. two type A quivers plus a 4-cycle or triangulated square glued together
872
+ # at 'connecting vertices'.
873
+
874
+ # Exception 1 (Type 2 of D_n)
875
+ exception_graph1 = DiGraph()
876
+ exception_graph1.add_edges([(0,1),(1,2),(2,3),(3,0)])
877
+
878
+ # Exception 2 (Type 3 of D_n)
879
+ exception_graph2 = DiGraph()
880
+ exception_graph2.add_edges([(0,1),(1,2),(0,3),(3,2),(2,0)])
881
+
882
+ # Let c_1 be a pair of 2-valent vertices and c_2 be a pair of two other vertices.
883
+ # If together, they make an induced 4-cycle and deleting c_1 yields two connected components,
884
+ # then retest of both components. If still connected after deleting c_1, then return 'unknown'.
885
+
886
+ # If on the other hand, (c1 and c2) is isomorphic to a triangulated square, then
887
+ # delete c1. This ensures that c2 is an edge of the triangulated square, and we delete
888
+ # it regardless of orientation. Then check if the digraph has exactly two connected
889
+ # components, and again this testing method is rerun on both components.
890
+
891
+ for c1 in Combinations( [ vertex for vertex in vertices if dg.degree(vertex) == 2], 2 ):
892
+ del_vertices = list( vertices )
893
+ del_vertices.remove( c1[0] )
894
+ del_vertices.remove( c1[1] )
895
+ for c2 in Combinations( del_vertices, 2 ):
896
+ comb = c1 + c2
897
+ sg = dg.subgraph( comb )
898
+
899
+ # Exception 1 case (4-cycle):
900
+ edges = sg.edges(sort=True, labels=False)
901
+ if (c1[0], c1[1]) not in edges and (c1[1], c1[0]) not in edges and sg.is_isomorphic(exception_graph1):
902
+ dg_tmp = DiGraph(dg)
903
+ dg_tmp.delete_vertices(c1)
904
+
905
+ components = dg_tmp.connected_components(sort=False)
906
+ # if not len(components) == 2:
907
+ if len(components) != 2:
908
+ return _false_return(4)
909
+ else:
910
+ dg_tmp1 = dg_tmp.subgraph( components[0] )
911
+ type_tmp1 = _connected_mutation_type_AAtildeD( dg_tmp1, ret_conn_vert=True )
912
+ dg_tmp2 = dg_tmp.subgraph( components[1] )
913
+ type_tmp2 = _connected_mutation_type_AAtildeD( dg_tmp2, ret_conn_vert=True )
914
+
915
+ if type_tmp1 == 'unknown' or type_tmp2 == 'unknown':
916
+ return _false_return()
917
+
918
+ # Assuming that the two components are recognized, initialize this in a format it can be returned as output
919
+ type_tmp = []
920
+ type_tmp.append( [ type_tmp1[0], type_tmp2[0] ] )
921
+ type_tmp[0].sort(key=str)
922
+ type_tmp.append( type_tmp1[1] + type_tmp2[1] )
923
+ type_tmp[1].sort(key=str)
924
+
925
+ # Need to make sure the two vertices in c2 are both 'connecting vertices'.
926
+ if not set(c2).issubset(type_tmp[1]):
927
+ return _false_return(5)
928
+
929
+ if type_tmp[0][0].letter() == 'A' and type_tmp[0][0].is_finite() and type_tmp[0][1].letter() == 'A' and type_tmp[0][1].is_finite():
930
+ if ret_conn_vert:
931
+ type_tmp[1].extend(c1)
932
+ #type_tmp[1].remove(c2[0])
933
+ #type_tmp[1].remove(c2[1])
934
+ return [ QuiverMutationType( ['D',n] ), type_tmp[1] ]
935
+ else:
936
+ return QuiverMutationType( ['D',n] )
937
+
938
+ # Exception 2 case (triangulated square):
939
+ if sg.is_isomorphic( exception_graph2 ):
940
+ dg_tmp = DiGraph( dg )
941
+ dg_tmp.delete_vertices( c1 )
942
+ if tuple( c2 ) in dg_tmp.edges(sort=True, labels=False):
943
+ dg_tmp.delete_edge( tuple( c2 ) )
944
+ else:
945
+ c2.reverse()
946
+ dg_tmp.delete_edge( tuple( c2 ) )
947
+ components = dg_tmp.connected_components(sort=False)
948
+ if len(components) != 2:
949
+ return _false_return(7)
950
+ else:
951
+ dg_tmp1 = dg_tmp.subgraph( components[0] )
952
+ type_tmp1 = _connected_mutation_type_AAtildeD( dg_tmp1, ret_conn_vert=True )
953
+
954
+ if type_tmp1 == 'unknown':
955
+ return _false_return()
956
+ dg_tmp2 = dg_tmp.subgraph( components[1] )
957
+ type_tmp2 = _connected_mutation_type_AAtildeD( dg_tmp2, ret_conn_vert=True )
958
+
959
+ # Assuming that the two components are recognized, initialize this in
960
+ # a format it can be returned as output (just as above)
961
+ type_tmp = []
962
+ type_tmp.append( [ type_tmp1[0], type_tmp2[0] ] )
963
+ type_tmp[0].sort(key=str)
964
+ type_tmp.append( type_tmp1[1] + type_tmp2[1] )
965
+ type_tmp[1].sort(key=str)
966
+ if type_tmp2 == 'unknown':
967
+ return _false_return()
968
+ if not set(c2).issubset(type_tmp[1]) and len( set(type_tmp[1]).intersection(c2) ) == 1:
969
+ return _false_return(5.5)
970
+ if type_tmp[0][0].letter() == 'A' and type_tmp[0][0].is_finite() and type_tmp[0][1].letter() == 'A' and type_tmp[0][1].is_finite():
971
+ if ret_conn_vert:
972
+ type_tmp[1].remove(c2[0])
973
+ type_tmp[1].remove(c2[1])
974
+ #type_tmp[1].extend(c1)
975
+ return [ QuiverMutationType( ['D',n] ), type_tmp[1] ]
976
+ else:
977
+ return QuiverMutationType( ['D',n] )
978
+
979
+ # The following tests are done regardless of the number of vertices in dg.
980
+ # If there are 1, 2, or 3 vertices in dg, we would have skipped above tests and gone directly here.
981
+
982
+ # Initialize a long_cycle.
983
+ long_cycle = False
984
+
985
+ # test that there is no triple-edge or higher multiplicity and that there is at most one double-edge.
986
+ if dg.has_multiple_edges():
987
+ multiple_edges = dg.multiple_edges(labels=False)
988
+ if len(multiple_edges) > 2:
989
+ return _false_return(14)
990
+ elif len(multiple_edges) == 2:
991
+ # we think of the double-edge as a long_cycle, an unoriented 2-cycle.
992
+ long_cycle = [multiple_edges, ['A', n - 1, 1]]
993
+
994
+ # creating a dictionary of in-, out- and total degrees
995
+ dict_in_out = {}
996
+ for v in vertices:
997
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
998
+
999
+ # computing the absolute degree of dg
1000
+ abs_deg = max( [ x[2] for x in list( dict_in_out.values() ) ] )
1001
+
1002
+ # edges = dg.edges(sort=True, labels=False )
1003
+
1004
+ # test that no vertex has valency more than 4
1005
+ if abs_deg > 4:
1006
+ return _false_return(16)
1007
+ else:
1008
+ # constructing all oriented and unoriented triangles
1009
+ trians = _triangles( dg )
1010
+ oriented_trians = [ trian[0] for trian in trians if trian[1] ]
1011
+ unoriented_trians = [ trian[0] for trian in trians if not trian[1] ]
1012
+
1013
+ oriented_trian_edges = []
1014
+ for oriented_trian in oriented_trians:
1015
+ oriented_trian_edges.extend( oriented_trian )
1016
+
1017
+ # test that no edge is in more than two oriented triangles
1018
+ multiple_trian_edges = []
1019
+ for edge in oriented_trian_edges:
1020
+ count = oriented_trian_edges.count(edge)
1021
+ if count > 2:
1022
+ return _false_return(17)
1023
+ elif count == 2:
1024
+ multiple_trian_edges.append( edge )
1025
+ multiple_trian_edges = list(set(multiple_trian_edges))
1026
+
1027
+ # test that there at most three edges appearing in exactly two oriented triangles
1028
+ count = len(multiple_trian_edges)
1029
+ if count >= 4:
1030
+ return _false_return(321)
1031
+ # if two edges appearing in exactly two oriented triangles, test that the two edges together
1032
+ # determine a unique triangle
1033
+ elif count > 1:
1034
+ test_triangles = [[tuple(trian) for trian in oriented_trians
1035
+ if edge in trian]
1036
+ for edge in multiple_trian_edges]
1037
+ unique_triangle = set.intersection(*map(set, test_triangles))
1038
+ if len(unique_triangle) != 1:
1039
+ return _false_return(19)
1040
+ else:
1041
+ # if a long_cycle had previously been found, this unique oriented triangle is a second long_cycle, a contradiction.
1042
+ if long_cycle:
1043
+ return _false_return(20)
1044
+ else:
1045
+ unique_triangle = unique_triangle.pop()
1046
+ long_cycle = [ unique_triangle, QuiverMutationType( ['D',n] ) ]
1047
+ # if one edge appearing in exactly two oriented triangles, test that it is not a double-edge and then
1048
+ # test that either the third or fourth vertices (from the oriented triangles) is of degree 2.
1049
+ # Then initializes the long_cycle as this triangle including the degree 2 vertex, as long as no other long_cycles.
1050
+ elif count == 1 and not dg.has_multiple_edges() and multiple_trian_edges[0] not in dg.multiple_edges():
1051
+ multiple_trian_edge = multiple_trian_edges[0]
1052
+ neighbors = list(set(dg.neighbors( multiple_trian_edge[0] )).intersection(dg.neighbors( multiple_trian_edge[1] )))
1053
+ if dg.degree( neighbors[0] ) == 2:
1054
+ unique_triangle = [ multiple_trian_edge, ( multiple_trian_edge[1], neighbors[0] ), ( neighbors[0], multiple_trian_edge[0] ) ]
1055
+ elif dg.degree( neighbors[1] ) == 2:
1056
+ unique_triangle = [ multiple_trian_edge, ( multiple_trian_edge[1], neighbors[1] ), ( neighbors[1], multiple_trian_edge[0] ) ]
1057
+ else:
1058
+ return _false_return(201)
1059
+
1060
+ if long_cycle:
1061
+ # if a long_cycle had previously been found, then the specified oriented triangle is a second long_cycle, a contradiction.
1062
+ return _false_return(202)
1063
+ else:
1064
+ long_cycle = [ unique_triangle, QuiverMutationType( ['D',n] ) ]
1065
+
1066
+ # there can be at most 1 unoriented triangle and this triangle is the exceptional circle of type A_tilde
1067
+ if unoriented_trians:
1068
+ if len(unoriented_trians) == 1:
1069
+ if long_cycle:
1070
+ return _false_return(21)
1071
+ else:
1072
+ long_cycle = [ unoriented_trians[0], ['A',n-1,1] ]
1073
+ else:
1074
+ return _false_return(22)
1075
+
1076
+ for v in vertices:
1077
+ w = dict_in_out[v]
1078
+ if w[2] == 4:
1079
+ # if a vertex has valency 4 than the 4 neighboring edges must be contained in 2 oriented triangles
1080
+ if w[0] != 2:
1081
+ return _false_return(23)
1082
+ else:
1083
+ in_neighbors = dg.neighbors_in( v )
1084
+ out_neighbors = dg.neighbors_out( v )
1085
+ if len( out_neighbors ) == 1:
1086
+ out_neighbors.extend(out_neighbors)
1087
+ if len( in_neighbors ) == 1:
1088
+ in_neighbors.extend(in_neighbors)
1089
+
1090
+ if (in_neighbors[0], v) not in oriented_trian_edges:
1091
+ return _false_return(24)
1092
+ elif (in_neighbors[1], v) not in oriented_trian_edges:
1093
+ return _false_return(25)
1094
+ elif (v, out_neighbors[0]) not in oriented_trian_edges:
1095
+ return _false_return(26)
1096
+ elif (v, out_neighbors[1]) not in oriented_trian_edges:
1097
+ return _false_return(27)
1098
+
1099
+ # if a vertex has valency 3 than 2 of its neighboring edges must be contained in an oriented triangle and the remaining must not
1100
+ elif w[2] == 3:
1101
+ if w[0] == 1:
1102
+ in_neighbors = dg.neighbors_in( v )
1103
+ out_neighbors = dg.neighbors_out( v )
1104
+ if (in_neighbors[0],v) not in oriented_trian_edges:
1105
+ return _false_return(28)
1106
+ elif len( out_neighbors ) == 1:
1107
+ if (v,out_neighbors[0]) not in oriented_trian_edges:
1108
+ return _false_return(29)
1109
+ else:
1110
+ if (v,out_neighbors[0]) in oriented_trian_edges and (v,out_neighbors[1]) in oriented_trian_edges:
1111
+ if not long_cycle:
1112
+ return _false_return(30)
1113
+ if not long_cycle[1] == QuiverMutationType(['D',n]):
1114
+ return _false_return(31)
1115
+ if (v, out_neighbors[0]) not in long_cycle[0] and (v, out_neighbors[1]) not in long_cycle[0]:
1116
+ return _false_return(32)
1117
+ if (v,out_neighbors[0]) not in oriented_trian_edges and (v,out_neighbors[1]) not in oriented_trian_edges:
1118
+ return _false_return(33)
1119
+ elif w[0] == 2:
1120
+ in_neighbors = dg.neighbors_in( v )
1121
+ out_neighbors = dg.neighbors_out( v )
1122
+ if (v, out_neighbors[0]) not in oriented_trian_edges:
1123
+ return _false_return(34)
1124
+ elif len( in_neighbors ) == 1:
1125
+ if (in_neighbors[0],v) not in oriented_trian_edges:
1126
+ return _false_return(35)
1127
+ else:
1128
+ if (in_neighbors[0],v) in oriented_trian_edges and (in_neighbors[1],v) in oriented_trian_edges:
1129
+ if not long_cycle:
1130
+ return _false_return(36)
1131
+ if not long_cycle[1] == QuiverMutationType(['D',n]):
1132
+ return _false_return(37)
1133
+ if (in_neighbors[0], v) not in long_cycle[0] and (in_neighbors[1], v) not in long_cycle[0]:
1134
+ return _false_return(38)
1135
+ if (in_neighbors[0], v) not in oriented_trian_edges and (in_neighbors[1], v) not in oriented_trian_edges:
1136
+ return _false_return(39)
1137
+ else:
1138
+ return _false_return(40)
1139
+
1140
+ # there can exist at most one larger oriented or unoriented induced cycle
1141
+ # if it is oriented, we are in finite type D, otherwise we are in affine type A
1142
+
1143
+ # Above code found long_cycles would be an unoriented 2-cycle or an oriented triangle.
1144
+ # The method _all_induced_cycles_iter only looks for induced cycles on 4 or more vertices.
1145
+
1146
+ for cycle, is_oriented in _all_induced_cycles_iter( dg ):
1147
+ # if there already was a long_cycle and we found another one, then have a contradiction.
1148
+ if long_cycle:
1149
+ return _false_return(41)
1150
+ # otherwise, we obtain cases depending on whether or not the found long_cycle is oriented.
1151
+ elif is_oriented:
1152
+ long_cycle = [ cycle, QuiverMutationType(['D',n]) ]
1153
+ else:
1154
+ long_cycle = [ cycle, ['A',n-1,1] ]
1155
+ # if we haven't found a "long_cycle", we are in finite type A
1156
+ if not long_cycle:
1157
+ long_cycle = [[], QuiverMutationType(['A', n])]
1158
+
1159
+ # The 'connected vertices' are now computed.
1160
+ # Attention: 0-1-2 in type A_3 has connecting vertices 0 and 2, while in type D_3 it has connecting vertex 1;
1161
+ # this is not caught here.
1162
+ if ret_conn_vert:
1163
+ connecting_vertices = []
1164
+ o_trian_verts = flatten( oriented_trian_edges )
1165
+ long_cycle_verts = flatten( long_cycle[0] )
1166
+ for v in vertices:
1167
+ w = dict_in_out[v]
1168
+ # if the quiver consists of only one vertex, it is of type A_1 and the vertex is a connecting vertex
1169
+ if w[2] == 0:
1170
+ connecting_vertices.append( v )
1171
+ # if a vertex is a leaf in a type A quiver, it is a connecting vertex
1172
+ elif w[2] == 1:
1173
+ connecting_vertices.append( v )
1174
+ # if a vertex is of valence two and contained in an oriented 3-cycle, it is a connecting vertex
1175
+ elif w[0] == 1 and w[1] == 1:
1176
+ if v in o_trian_verts and v not in long_cycle_verts:
1177
+ connecting_vertices.append( v )
1178
+
1179
+ # post-parsing 1: if we are in the affine type A case, the two parameters for the non-oriented long cycle are computed
1180
+ if isinstance(long_cycle[1], list) and len( long_cycle[1] ) == 3 and long_cycle[1][0] == 'A' and long_cycle[1][2] == 1:
1181
+ tmp = list( long_cycle[0] )
1182
+ e = tmp.pop()
1183
+ cycle = [e]
1184
+ v = e[1]
1185
+ while tmp:
1186
+ e = next(x for x in tmp if v in x)
1187
+ if v == e[0]:
1188
+ cycle.append(e)
1189
+ v = e[1]
1190
+ else:
1191
+ v = e[0]
1192
+ tmp.remove( e )
1193
+
1194
+ tmp = list( cycle )
1195
+ if len( long_cycle[0] ) == 2:
1196
+ edge = long_cycle[0][0]
1197
+ sg = DiGraph( dg )
1198
+ sg. delete_vertices(edge)
1199
+ connected_components = sg.connected_components(sort=False)
1200
+ cycle = []
1201
+ if connected_components:
1202
+ cycle.append( ( edge[0], edge[1], len( connected_components[0] ) + 1 ) )
1203
+ else:
1204
+ cycle.append( ( edge[0], edge[1], 1 ) )
1205
+ else:
1206
+ for edge in tmp:
1207
+ sg = DiGraph( dg )
1208
+ sg. delete_vertices(edge)
1209
+ connected_components = sg.connected_components(sort=False)
1210
+ if len( connected_components ) == 2:
1211
+ #if len( list_intersection( [ connected_components[0], list_substract( long_cycle[0], [edge] )[0] ] ) ) > 0:
1212
+ if len( set(connected_components[0]).intersection( set(long_cycle[0]).difference([edge]).pop() ) ) > 0:
1213
+ cycle.remove(edge)
1214
+ cycle.append( (edge[0],edge[1], len( connected_components[1] ) + 1 ) )
1215
+ else:
1216
+ cycle.remove(edge)
1217
+ cycle.append( (edge[0],edge[1], len( connected_components[0] ) + 1 ) )
1218
+ else:
1219
+ cycle.remove(edge)
1220
+ cycle.append( (edge[0],edge[1], 1 ) )
1221
+ r = sum(x[2] for x in cycle)
1222
+ r = max(r, n - r)
1223
+ if ret_conn_vert:
1224
+ return [ QuiverMutationType( ['A',[r,n-r],1] ), connecting_vertices ]
1225
+ else:
1226
+ return QuiverMutationType( ['A',[r,n-r],1] )
1227
+
1228
+ # post-parsing 2: if we are in another type, it is returned
1229
+ else:
1230
+ if ret_conn_vert:
1231
+ return [ long_cycle[1], connecting_vertices ]
1232
+ else:
1233
+ return long_cycle[1]
1234
+
1235
+
1236
+ @cached_function
1237
+ def load_data(n, user=True):
1238
+ r"""
1239
+ Load a dict with keys being tuples representing exceptional
1240
+ QuiverMutationTypes, and with values being lists or sets
1241
+ containing all mutation equivalent quivers as dig6 data.
1242
+
1243
+ We check
1244
+
1245
+ - the data stored by the user (unless ``user=False`` was given)
1246
+ - and the data installed by the optional package ``database_mutation_class``.
1247
+
1248
+ INPUT:
1249
+
1250
+ - ``user`` -- boolean (default: ``True``); whether to look at user
1251
+ data. If not, only consider the optional package.
1252
+
1253
+ EXAMPLES::
1254
+
1255
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import load_data
1256
+ sage: load_data(2) # random - depends on the data the user has stored
1257
+ {('G', 2): [('AO', (((0, 1), (1, -3)),)), ('AO', (((0, 1), (3, -1)),))]}
1258
+
1259
+ TESTS:
1260
+
1261
+ We test data from the ``database_mutation_class`` optional package::
1262
+
1263
+ sage: load_data(2, user=False) # optional - database_mutation_class
1264
+ {('G', 2): [('AO', (((0, 1), (1, -3)),)), ('AO', (((0, 1), (3, -1)),))]}
1265
+ sage: D = load_data(3, user=False) # optional - database_mutation_class
1266
+ sage: sorted(D.items()) # optional - database_mutation_class
1267
+ [(('G', 2, -1),
1268
+ [('BH?', (((1, 2), (1, -3)),)),
1269
+ ('BGO', (((2, 1), (3, -1)),)),
1270
+ ('BW?', (((0, 1), (3, -1)),)),
1271
+ ('BP?', (((0, 1), (1, -3)),)),
1272
+ ('BP_', (((0, 1), (1, -3)), ((2, 0), (3, -1)))),
1273
+ ('BP_', (((0, 1), (3, -1)), ((1, 2), (1, -3)), ((2, 0), (2, -2))))]),
1274
+ (('G', 2, 1),
1275
+ [('BH?', (((1, 2), (3, -1)),)),
1276
+ ('BGO', (((2, 1), (1, -3)),)),
1277
+ ('BW?', (((0, 1), (1, -3)),)),
1278
+ ('BP?', (((0, 1), (3, -1)),)),
1279
+ ('BKO', (((1, 0), (3, -1)), ((2, 1), (1, -3)))),
1280
+ ('BP_', (((0, 1), (2, -2)), ((1, 2), (1, -3)), ((2, 0), (3, -1))))])]
1281
+ """
1282
+ from sage.env import DOT_SAGE, SAGE_SHARE
1283
+
1284
+ # we check
1285
+ # - if the data is stored by the user, and if this is not the case
1286
+ # - if the data is stored by the optional package install
1287
+ paths = [Path(SAGE_SHARE)]
1288
+ if user:
1289
+ paths.append(Path(DOT_SAGE))
1290
+ data = {}
1291
+ for path in paths:
1292
+ file = path / 'cluster_algebra_quiver' / f'mutation_classes_{n}.dig6'
1293
+ try:
1294
+ with open(file, 'rb') as fobj:
1295
+ data_new = pickle.load(fobj)
1296
+ except (OSError, FileNotFoundError, pickle.UnpicklingError):
1297
+ # File does not exist, corrupt pickle, wrong Python version...
1298
+ pass
1299
+ else:
1300
+ data.update(data_new)
1301
+ return data
1302
+
1303
+
1304
+ def _mutation_type_from_data(n, dig6, compute_if_necessary=True):
1305
+ r"""
1306
+ Return the mutation type from the given dig6 data by looking into
1307
+ the precomputed mutation types
1308
+
1309
+ Attention: it is assumed that dig6 is the dig6 data of the
1310
+ canonical form of the given quiver!
1311
+
1312
+ EXAMPLES::
1313
+
1314
+ sage: # needs sage.modules
1315
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6
1316
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _mutation_type_from_data
1317
+ sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1318
+ sage: dg = ClusterQuiver(['F',4]).canonical_label().digraph()
1319
+ sage: dig6 = _digraph_to_dig6(dg,hashable=True); dig6
1320
+ ('CCo?', (((1, 3), (2, -1)),))
1321
+ sage: _mutation_type_from_data(4,dig6)
1322
+ ['F', 4]
1323
+ """
1324
+ # we try to load the data from a library
1325
+ data = load_data(n)
1326
+ # if this didn't work, we construct all exceptional quivers with n vertices
1327
+ if compute_if_necessary and data == {}:
1328
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import save_quiver_data
1329
+ save_quiver_data(n, up_to=False, types='Exceptional', verbose=False)
1330
+ load_data.clear_cache()
1331
+ data = load_data(n)
1332
+ # finally, we check if the given quiver is in one of the exceptional mutation classes
1333
+ for mutation_type in data:
1334
+ if dig6 in data[ mutation_type ]:
1335
+ return QuiverMutationType( mutation_type )
1336
+ return 'unknown'
1337
+
1338
+
1339
+ def _mutation_type_test(n):
1340
+ """
1341
+ Test all quivers (of the given types) of rank n to check that
1342
+ mutation_type() works.
1343
+
1344
+ Affine type D does not return ``True`` since this test is not implemented.
1345
+
1346
+ EXAMPLES::
1347
+
1348
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _mutation_type_test
1349
+
1350
+ sage: _mutation_type_test(2) # long time
1351
+ True ('A', (1, 1), 1)
1352
+ True ('A', 2)
1353
+ True ('B', 2)
1354
+ True ('BC', 1, 1)
1355
+ True ('G', 2)
1356
+
1357
+ sage: _mutation_type_test(3) # long time
1358
+ True ('A', (2, 1), 1)
1359
+ True ('A', 3)
1360
+ True ('B', 3)
1361
+ True ('BB', 2, 1)
1362
+ True ('BC', 2, 1)
1363
+ True ('C', 3)
1364
+ True ('CC', 2, 1)
1365
+ True ('G', 2, -1)
1366
+ True ('G', 2, 1)
1367
+
1368
+ sage: _mutation_type_test(4) # not tested
1369
+ True ('A', (2, 2), 1)
1370
+ True ('A', (3, 1), 1)
1371
+ True ('A', 4)
1372
+ True ('B', 4)
1373
+ True ('BB', 3, 1)
1374
+ True ('BC', 3, 1)
1375
+ True ('BD', 3, 1)
1376
+ True ('C', 4)
1377
+ True ('CC', 3, 1)
1378
+ True ('CD', 3, 1)
1379
+ True ('D', 4)
1380
+ True ('F', 4)
1381
+ True ('G', 2, (1, 1))
1382
+ True ('G', 2, (1, 3))
1383
+ True ('G', 2, (3, 3))
1384
+
1385
+ sage: _mutation_type_test(5) # not tested
1386
+ True ('A', (3, 2), 1)
1387
+ True ('A', (4, 1), 1)
1388
+ True ('A', 5)
1389
+ True ('B', 5)
1390
+ True ('BB', 4, 1)
1391
+ True ('BC', 4, 1)
1392
+ True ('BD', 4, 1)
1393
+ True ('C', 5)
1394
+ True ('CC', 4, 1)
1395
+ True ('CD', 4, 1)
1396
+ False ('D', 4, 1)
1397
+ True ('D', 5)
1398
+ True ('F', 4, -1)
1399
+ True ('F', 4, 1)
1400
+ """
1401
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_classical_mutation_classes
1402
+ from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_digraph
1403
+ from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1404
+ data = _construct_classical_mutation_classes( n )
1405
+ keys = data.keys()
1406
+ for mutation_type in sorted(keys, key=str):
1407
+ mt = QuiverMutationType( mutation_type )
1408
+ print(all( ClusterQuiver(_dig6_to_digraph(dig6)).mutation_type() == mt for dig6 in data[mutation_type]), mutation_type)
1409
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_exceptional_mutation_classes
1410
+ data = _construct_exceptional_mutation_classes( n )
1411
+ keys = data.keys()
1412
+ for mutation_type in sorted(keys, key=str):
1413
+ mt = QuiverMutationType( mutation_type )
1414
+ print(all( ClusterQuiver(_dig6_to_digraph(dig6)).mutation_type() == mt for dig6 in data[mutation_type]), mutation_type)
1415
+
1416
+
1417
+ def _random_tests(mt, k, mut_class=None, nr_mut=5):
1418
+ """
1419
+ Provide random tests to find bugs in the mutation type methods.
1420
+
1421
+ INPUT:
1422
+
1423
+ - ``mt`` something that can be turned into a ``QuiverMutationType``
1424
+ - ``k`` -- integer; the number of tests performed for each quiver of rank ``n``
1425
+ - ``mut_class`` -- if given, this mutation class is used
1426
+ - ``nr_mut`` -- integer (default: 5); the number of mutations performed before
1427
+ testing
1428
+
1429
+ The idea of this random test is to start with a mutation type
1430
+ and compute its mutation class (or have this class given). Now,
1431
+ every quiver in this mutation class is slightly changed in order
1432
+ to obtain a matrix of the same type or something very similar.
1433
+ Now, the new type is computed and checked if it stays stable for
1434
+ ``nr_mut``'s many mutations.
1435
+
1436
+ TESTS::
1437
+
1438
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _random_tests
1439
+ sage: _random_tests( ['A',3], 1) # needs sage.modules
1440
+ testing ['A', 3]
1441
+ """
1442
+ from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1443
+ from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_matrix, _matrix_to_digraph, _digraph_mutate, _edge_list_to_matrix
1444
+ import random
1445
+ if mut_class is None:
1446
+ mut_class = ClusterQuiver(mt).mutation_class(data_type='dig6')
1447
+ print("testing " + str(mt))
1448
+ for dig6 in mut_class:
1449
+ M_const = _dig6_to_matrix( dig6 )
1450
+ nz = [ (i,j) for i,j in M_const.nonzero_positions() if i > j ]
1451
+ # performing k tests on the matrix M_const
1452
+ for i in range(k):
1453
+ M = copy( M_const )
1454
+ # every pair M[i,j],M[j,i] is possibly changed
1455
+ # while the property of being skew-symmetrizable is kept
1456
+ for i,j in nz:
1457
+ a,b = M[i,j],M[j,i]
1458
+ skew_sym = False
1459
+ while not skew_sym:
1460
+ ran = random.randint(1, 2)
1461
+ if ran == 1:
1462
+ M[i, j], M[j, i] = -M[j, i], -M[i, j]
1463
+ elif ran == 2:
1464
+ ran2 = random.randint(1, 8)
1465
+ if ran2 == 1:
1466
+ c, d = 1, -1
1467
+ elif ran2 == 2:
1468
+ c, d = 1, -2
1469
+ elif ran2 == 3:
1470
+ c, d = 2, -1
1471
+ elif ran2 == 4:
1472
+ c, d = 1, -3
1473
+ elif ran2 == 5:
1474
+ c, d = 3, -1
1475
+ elif ran2 == 6:
1476
+ c, d = 2, -2
1477
+ elif ran2 == 7:
1478
+ c, d = 1, -4
1479
+ elif ran2 == 8:
1480
+ c, d = 4, -1
1481
+ M[i, j], M[j, i] = c, d
1482
+ if M.is_skew_symmetrizable(positive=True):
1483
+ skew_sym = True
1484
+ else:
1485
+ M[i, j], M[j, i] = a, b
1486
+ # we now have a new matrix M
1487
+ # and a new digraph db
1488
+ dg = _matrix_to_digraph(M)
1489
+ mt = _connected_mutation_type(dg)
1490
+ mut = -1
1491
+ # we perform nr_mut many mutations
1492
+ for k in range(nr_mut):
1493
+ # while making sure that we do not mutate back
1494
+ mut_tmp = mut
1495
+ while mut == mut_tmp:
1496
+ mut = random.randint(0, dg.order() - 1)
1497
+ dg_new = _digraph_mutate(dg, mut)
1498
+ mt_new = _connected_mutation_type(dg_new)
1499
+ if mt != mt_new:
1500
+ print("FOUND ERROR!")
1501
+ print(_edge_list_to_matrix(dg.edges(sort=True),
1502
+ list(range(dg.order())), []))
1503
+ print("has mutation type " + str(mt) + " while it has mutation type " + str(mt_new) + " after mutating at " + str(mut) + ":")
1504
+ print(_edge_list_to_matrix(dg_new.edges(sort=True),
1505
+ list(range(dg.order())), []))
1506
+ return dg, dg_new
1507
+ else:
1508
+ dg = dg_new
1509
+
1510
+
1511
+ def _random_multi_tests(n, k, nr_mut=5):
1512
+ """
1513
+ Provide multiple random tests to find bugs in the mutation type methods.
1514
+
1515
+ INPUT:
1516
+
1517
+ - ``n`` -- integer; the rank of the mutation types to test
1518
+ - ``k`` -- integer; the number of tests performed for each quiver of rank ``n``
1519
+ - ``nr_mut`` -- integer (default: 5); the number of mutations performed before testing
1520
+
1521
+ TESTS::
1522
+
1523
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _random_multi_tests
1524
+ sage: _random_multi_tests(2,1) # not tested
1525
+ testing ('A', (1, 1), 1)
1526
+ testing ('A', 2)
1527
+ testing ('B', 2)
1528
+ testing ('BC', 1, 1)
1529
+
1530
+ sage: _random_multi_tests(3,1) # not tested
1531
+ testing ('A', (2, 1), 1)
1532
+ testing ('A', 3)
1533
+ testing ('B', 3)
1534
+ testing ('BB', 2, 1)
1535
+ testing ('BC', 2, 1)
1536
+ testing ('C', 3)
1537
+ testing ('CC', 2, 1)
1538
+
1539
+ sage: _random_multi_tests(4,1) # not tested
1540
+ testing ('A', (2, 2), 1)
1541
+ testing ('A', (3, 1), 1)
1542
+ testing ('A', 4)
1543
+ testing ('B', 4)
1544
+ testing ('BB', 3, 1)
1545
+ testing ('BC', 3, 1)
1546
+ testing ('BD', 3, 1)
1547
+ testing ('C', 4)
1548
+ testing ('CC', 3, 1)
1549
+ testing ('CD', 3, 1)
1550
+ testing ('D', 4)
1551
+ """
1552
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_classical_mutation_classes
1553
+ mutation_classes = _construct_classical_mutation_classes(n)
1554
+ for mutation_type in sorted(mutation_classes, key=str):
1555
+ _random_tests(mutation_type, k,
1556
+ mut_class=mutation_classes[mutation_type], nr_mut=nr_mut)