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
@@ -0,0 +1,1555 @@
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
+ from copy import copy
25
+ from pathlib import Path
26
+ import pickle
27
+ from typing import Any, Iterator
28
+
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) -> tuple[bool, Any]:
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) -> list[tuple[list, bool]]:
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
+ [([(0, 1), (1, 2), (2, 0)], True)]
114
+ sage: Q2 = ClusterQuiver(['A', [1, 2], 1])
115
+ sage: _triangles(Q2.digraph())
116
+ [([(1, 0), (1, 2), (2, 0)], False)]
117
+ sage: Q2.mutate(2)
118
+ sage: _triangles(Q2.digraph())
119
+ [([(1, 0), (2, 1), (0, 2)], True)]
120
+ """
121
+ from itertools import combinations
122
+ trians = []
123
+ for x in dg.vertices(sort=True):
124
+ nx = sorted(y for y in dg.neighbor_iterator(x) if x < y)
125
+ for y, z in combinations(nx, 2):
126
+ if dg.has_edge(y, z):
127
+ if dg.has_edge(x, y):
128
+ if dg.has_edge(z, x):
129
+ trians.append(([(x, y), (y, z), (z, x)], True))
130
+ else:
131
+ trians.append(([(x, y), (y, z), (x, z)], False))
132
+ else:
133
+ if dg.has_edge(z, x):
134
+ trians.append(([(y, x), (y, z), (z, x)], False))
135
+ else:
136
+ trians.append(([(y, x), (y, z), (x, z)], False))
137
+ elif dg.has_edge(z, y):
138
+ if dg.has_edge(x, y):
139
+ if dg.has_edge(z, x):
140
+ trians.append(([(x, y), (z, y), (z, x)], False))
141
+ else:
142
+ trians.append(([(x, y), (z, y), (x, z)], False))
143
+ else:
144
+ if dg.has_edge(z, x):
145
+ trians.append(([(y, x), (z, y), (z, x)], False))
146
+ else:
147
+ trians.append(([(y, x), (z, y), (x, z)], True))
148
+ return trians
149
+
150
+
151
+ def _all_induced_cycles_iter(dg) -> Iterator[tuple]:
152
+ """
153
+ Return an iterator for all induced oriented cycles of length
154
+ greater than or equal to 4 in the digraph ``dg``.
155
+
156
+ EXAMPLES::
157
+
158
+ sage: # needs sage.modules
159
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _all_induced_cycles_iter
160
+ sage: Q = ClusterQuiver(['A', [6, 0], 1]); Q
161
+ Quiver on 6 vertices of type ['D', 6]
162
+ sage: next(_all_induced_cycles_iter(Q.digraph()))
163
+ ([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)], True)
164
+ sage: Q.mutate(0)
165
+ sage: next(_all_induced_cycles_iter(Q.digraph()))
166
+ ([(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)], True)
167
+ sage: Q2 = ClusterQuiver(['A', [2, 3], 1])
168
+ sage: next(_all_induced_cycles_iter(Q2.digraph()))
169
+ ([(1, 0), (1, 2), (3, 2), (3, 4), (4, 0)], False)
170
+ """
171
+ dg_new = DiGraph(dg)
172
+ E = dg_new.edges(sort=True)
173
+ for v1, v2, label in E:
174
+ dg_new.add_edge((v2, v1, label))
175
+ induced_sets: list[set] = []
176
+ cycle_iter = dg_new.all_cycles_iterator(simple=True)
177
+ for cycle in cycle_iter:
178
+ if len(cycle) > 3:
179
+ cycle_set = set(cycle)
180
+ if not any(cycle_set.issuperset(induced_set) for induced_set in induced_sets):
181
+ induced_sets.append(cycle_set)
182
+ if len(cycle) > 4:
183
+ sg = dg.subgraph(cycle)
184
+ is_oriented = True
185
+ V = list(sg)
186
+ while is_oriented and V:
187
+ v = V.pop()
188
+ if not sg.in_degree(v) == 1:
189
+ is_oriented = False
190
+ yield (sg.edges(sort=True, labels=False), is_oriented)
191
+
192
+ # a debug function
193
+
194
+
195
+ def _false_return(s=False) -> str:
196
+ """
197
+ Return 'unknown'.
198
+
199
+ Written for potential debugging purposes.
200
+
201
+ EXAMPLES::
202
+
203
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _false_return
204
+ sage: _false_return()
205
+ 'unknown'
206
+ """
207
+ # Uncomment these three lines for debugging purposes.
208
+ # if s:
209
+ # print('DEBUG: error %s' % s)
210
+ return 'unknown'
211
+
212
+
213
+ def _reset_dg(dg, vertices, dict_in_out, del_vertices) -> None:
214
+ """
215
+ Delete the specified vertices (``del_vertices``) from the DiGraph ``dg``,
216
+ and the lists ``vertices`` and ``dict_in_out``.
217
+
218
+ Note that ``vertices`` and ``dict_in_out`` are the vertices of ``dg`` and a
219
+ dictionary of in- and out-degrees that depend on the digraph
220
+ ``dg`` but they are passed through as arguments so the function
221
+ can change their values.
222
+
223
+ EXAMPLES::
224
+
225
+ sage: # needs sage.modules
226
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _reset_dg
227
+ sage: dg = ClusterQuiver(['A', [2, 2], 1]).digraph(); dg
228
+ Digraph on 4 vertices
229
+ sage: vertices = list(dg)
230
+ sage: dict_in_out = {}
231
+ sage: for v in vertices: dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
232
+ sage: _reset_dg(dg, vertices, dict_in_out, [1])
233
+ sage: dg
234
+ Digraph on 3 vertices
235
+ sage: vertices
236
+ [0, 2, 3]
237
+ sage: dict_in_out
238
+ {0: (1, 0, 1), 2: (1, 0, 1), 3: (0, 2, 2)}
239
+ """
240
+ del_vertices = list(set(del_vertices))
241
+ for v in del_vertices:
242
+ if v in dg:
243
+ dg.delete_vertex(v)
244
+ else:
245
+ print(v)
246
+ print(dg.edges(sort=True))
247
+ vertices.remove(v)
248
+ del dict_in_out[v]
249
+ for v in vertices:
250
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
251
+
252
+
253
+ def _check_special_BC_cases(dg, n, check_letter_list, check_twist_list,
254
+ hope_letter_list, conn_vert_list=False):
255
+ """
256
+ Test if dg (on at most `n` vertices) is a quiver of type `A` or
257
+ `D` (as given in hope_letter_list) with conn_vert_list (if
258
+ given) as connecting vertices.
259
+
260
+ Since this is supposed to be run on a ``dg`` coming from a larger
261
+ quiver where vertices have already been removed (outside of the
262
+ connecting vertices), this program therefore recognizes the type
263
+ of the larger quiver as an `n`-vertex quiver of letter on
264
+ ``check_letter_list`` and twist on ``check_twist_list``. This
265
+ method is utilized in _connected_mutation_type to test for types
266
+ BC, BB, CC, BD, or CD.
267
+
268
+ EXAMPLES::
269
+
270
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _check_special_BC_cases
271
+ sage: dg = DiGraph(1)
272
+ sage: _check_special_BC_cases(dg, 3, ['BC'], [1], ['A'], [[0]])
273
+ ['BC', 2, 1]
274
+ sage: dg = DiGraph(2); dg.add_edge([0, 1])
275
+ sage: _check_special_BC_cases(dg, 4, ['BC'], [1], ['A'], [[0]])
276
+ ['BC', 3, 1]
277
+ sage: dg = DiGraph(2); dg.add_edge([0, 1])
278
+ sage: _check_special_BC_cases(dg, 4, ['BB'], [1], ['A'], [[0, 1]])
279
+ ['BB', 3, 1]
280
+ sage: _check_special_BC_cases(dg, 4, ['C', 'CD'], [None, None], ['A', 'D'], [[], [0]])
281
+ ['C', 4]
282
+ sage: dg.add_edges([[1, 2], [1, 3]])
283
+ sage: _check_special_BC_cases(dg, 4, ['C', 'CD'], [None, None], ['A', 'D'], [[], [0]])
284
+ ['CD', 3, 1]
285
+ """
286
+ # if dg is not connected, mutation type is not recognized.
287
+ if not dg.is_connected():
288
+ return 'unknown'
289
+ # divides into cases depending on whether or not a list 'conn_vert_list' of connecting vertices is given.
290
+ if conn_vert_list:
291
+ mut_type = _connected_mutation_type_AAtildeD(dg, ret_conn_vert=True)
292
+ # when 'conn_vert_list' is given, the output of _connected_mutation_type_AAtildeD is
293
+ # either 'unknown' or a pair (mut_type, conn_verts). Then, it is tested if the vertices can be glued together as desired.
294
+ if not mut_type == 'unknown':
295
+ mut_type, conn_verts = mut_type
296
+ else:
297
+ # when conn_vert_list == False, the output of _connected_mutation_type _AAtildeD is simply 'unknown' or the mutation type.
298
+ # no 'connecting vertices' need to be computed.
299
+ mut_type = _connected_mutation_type_AAtildeD(dg, ret_conn_vert=False)
300
+ conn_verts = []
301
+ # when the mutation type is recognized, program now tries more specifically to figure out 'letter' and 'twist'
302
+ if not mut_type == 'unknown':
303
+ for i in range(len(check_letter_list)):
304
+ check_letter = check_letter_list[i]
305
+ check_twist = check_twist_list[i]
306
+ hope_letter = hope_letter_list[i]
307
+ if conn_vert_list:
308
+ conn_vert = set(conn_vert_list[i])
309
+ else:
310
+ conn_vert = set()
311
+ # Now, tries to connect up the quiver components (keeping in mind ['D', 3] - ['A', 3] equivalence)
312
+ if hope_letter == 'D' and mut_type._letter == 'A' and mut_type._rank == 3 and not mut_type._twist:
313
+ hope_letter = 'A'
314
+ if conn_vert_list:
315
+ conn_verts = list(set(dg).difference(conn_verts))
316
+ if mut_type._letter == hope_letter and not mut_type._twist and conn_vert.issubset(conn_verts):
317
+ if len(check_letter) > 1:
318
+ check_twist = 1
319
+ if check_twist:
320
+ n -= 1
321
+ return QuiverMutationType([check_letter, n, check_twist])
322
+ return 'unknown'
323
+
324
+
325
+ def _connected_mutation_type(dg):
326
+ """
327
+ Assuming that ``dg`` is a connected digraph, checks the mutation
328
+ type of ``dg`` as a valued quiver.
329
+
330
+ EXAMPLES::
331
+
332
+ sage: # needs sage.modules
333
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type
334
+ sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
335
+ sage: dg = ClusterQuiver(['A', 3]).digraph(); _connected_mutation_type(dg)
336
+ ['A', 3]
337
+ sage: dg = ClusterQuiver(['D', 7]).digraph(); _connected_mutation_type(dg)
338
+ ['D', 7]
339
+ sage: dg = ClusterQuiver(['BC', 4, 1]).digraph(); _connected_mutation_type(dg)
340
+ ['BC', 4, 1]
341
+ """
342
+ dg = DiGraph(dg)
343
+ # defining some shorthands
344
+ n = dg.order()
345
+ edges = dg.edges(sort=True)
346
+ vertices = list(dg)
347
+ # initializing lists of the edges with labels (2, -1) or (1, -2); (4, -1) or (1, -4); or (2, -2), respectively
348
+ exc_labels = []
349
+ exc_labels41 = []
350
+ double_edges = []
351
+ # letter = None
352
+
353
+ # 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'.
354
+ dg.allow_multiple_edges(True)
355
+ for edge in edges:
356
+ label = edge[2]
357
+ if label not in [(1, -1), (2, -2), (1, -2), (2, -1), (4, -1), (1, -4)]:
358
+ # _false_return(i) is a simple function that simply returns 'unknown'. For debugging purposes, it
359
+ # can also output 'DEBUG: error i' if desired.
360
+ # this command is used many times in this code, something times without the argument i.
361
+ return _false_return(2)
362
+ elif label == (2, -2):
363
+ dg.set_edge_label(edge[0], edge[1], 1)
364
+ dg.add_edge(edge[0], edge[1], 1)
365
+ double_edges.append(edge)
366
+ if len(double_edges) > 1:
367
+ return _false_return()
368
+ elif label == (1, -1):
369
+ dg.set_edge_label(edge[0], edge[1], 1)
370
+ elif label in [(2, -1), (1, -2)]:
371
+ exc_labels.append(edge)
372
+ elif label in [(1, -4), (4, -1)]:
373
+ exc_labels41.append(edge)
374
+
375
+ # creating a dictionary of in-, out- and total degrees
376
+ dict_in_out = {}
377
+ for v in vertices:
378
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
379
+
380
+ if len(exc_labels) + len(exc_labels41) + len(double_edges) > 4:
381
+ return _false_return()
382
+
383
+ # test for the labels (4, -1) and (1, -4) which can only appear in affine type BC
384
+ if exc_labels41:
385
+ # tests a two-vertex quiver to see if it is of type ['BC', 1, 1]
386
+ if len(exc_labels41) == 1 and dict_in_out[exc_labels41[0][0]][2] == dict_in_out[exc_labels41[0][1]][2] == 1:
387
+ return QuiverMutationType(['BC', 1, 1])
388
+ # 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))].
389
+ if len(exc_labels41) == 1 and len(exc_labels) == 2:
390
+ bool2 = exc_labels41[0][2] == (4, -1) and exc_labels[0][2] == exc_labels[1][2] == (1, -2)
391
+ bool3 = exc_labels41[0][2] == (1, -4) and exc_labels[0][2] == exc_labels[1][2] == (2, -1)
392
+ if bool2 or bool3:
393
+ v1, v2, label = exc_labels41[0]
394
+ label1, label2 = exc_labels
395
+ # 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.
396
+ # the third vertex of the triangle T should be a connecting_vertex.
397
+ 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:
398
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
399
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[label1[1]]])
400
+ 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:
401
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
402
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[label1[0]]])
403
+ else:
404
+ return _false_return()
405
+ else:
406
+ return _false_return()
407
+ else:
408
+ return _false_return()
409
+
410
+ # the program now performs further tests in the case that there are no edges of type (1, -4) nor (4, -1)
411
+
412
+ # first test for affine type C: if there are 4 exceptional labels, test if both belong to triangles with leaves
413
+ if len(exc_labels) == 4:
414
+ exc_labels12 = [labl for labl in exc_labels if labl[2] == (1, -2)]
415
+ exc_labels21 = [labl for labl in exc_labels if labl[2] == (2, -1)]
416
+ # check that we have two labels of one kind and one label of the other
417
+ if len(exc_labels12) != 2 or len(exc_labels21) != 2:
418
+ return _false_return()
419
+
420
+ label121 = exc_labels12[0]
421
+ label122 = exc_labels12[1]
422
+ label211 = exc_labels21[0]
423
+ label212 = exc_labels21[1]
424
+
425
+ # affine type B
426
+ if label211[1] == label121[0] and label212[1] == label122[0]:
427
+ pass
428
+ elif label212[1] == label121[0] and label211[1] == label122[0]:
429
+ label211, label212 = label212, label211
430
+ # affine type C
431
+ elif label121[1] == label211[0] and label122[1] == label212[0]:
432
+ pass
433
+ elif label122[1] == label211[0] and label121[1] == label212[0]:
434
+ label211, label212 = label212, label211
435
+ # affine type BC
436
+ elif label121[1] == label211[0] and label212[1] == label122[0]:
437
+ pass
438
+ elif label121[1] == label212[0] and label211[1] == label122[0]:
439
+ label211, label212 = label212, label211
440
+ elif label122[1] == label211[0] and label212[1] == label121[0]:
441
+ label121, label122 = label122, label121
442
+ elif label122[1] == label212[0] and label211[1] == label121[0]:
443
+ pass
444
+ else:
445
+ return _false_return()
446
+
447
+ # tests for which configuration the two (1, -2) and two (2, -1) edges are in.
448
+ bool1 = dg.has_edge(label121[1], label211[0], 1) and dict_in_out[label211[1]][0] == dict_in_out[label211[1]][1] == 1
449
+ bool2 = dg.has_edge(label122[1], label212[0], 1) and dict_in_out[label212[1]][0] == dict_in_out[label212[1]][1] == 1
450
+ bool12 = not (label121[1] == label122[1] and label211[0] == label212[0])
451
+ bool3 = dg.has_edge(label211[1], label121[0], 1) and dict_in_out[label121[1]][0] == dict_in_out[label121[1]][1] == 1
452
+ bool4 = dg.has_edge(label212[1], label122[0], 1) and dict_in_out[label122[1]][0] == dict_in_out[label122[1]][1] == 1
453
+ bool34 = not (label211[1] == label212[1] and label121[0] == label122[0])
454
+ bool5 = dg.has_edge(label211[1], label121[0], 1) and dict_in_out[label121[1]][0] == dict_in_out[label121[1]][1] == 1
455
+ bool6 = dg.has_edge(label122[1], label212[0], 1) and dict_in_out[label212[1]][0] == dict_in_out[label212[1]][1] == 1
456
+ bool56 = not (label211[1] == label122[1] and label121[0] == label212[0])
457
+ bool7 = dg.has_edge(label212[1], label122[0], 1) and dict_in_out[label122[1]][0] == dict_in_out[label122[1]][1] == 1
458
+ bool8 = dg.has_edge(label121[1], label211[0], 1) and dict_in_out[label211[1]][0] == dict_in_out[label211[1]][1] == 1
459
+ bool78 = not (label212[1] == label121[1] and label122[0] == label211[0])
460
+
461
+ nb1 = len(set(dg.neighbors(label121[1])).intersection(dg.neighbors(label211[0]))) <= 1
462
+ nb2 = len(set(dg.neighbors(label122[1])).intersection(dg.neighbors(label212[0]))) <= 1
463
+ nb3 = len(set(dg.neighbors(label211[1])).intersection(dg.neighbors(label121[0]))) <= 1
464
+ nb4 = len(set(dg.neighbors(label212[1])).intersection(dg.neighbors(label122[0]))) <= 1
465
+
466
+ if bool1 and bool2 and bool12 and nb1 and nb2:
467
+ v1, v2 = label211[1], label212[1]
468
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
469
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
470
+ if bool3 and bool4 and bool34 and nb3 and nb4:
471
+ v1, v2 = label121[1], label122[1]
472
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
473
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
474
+ elif bool5 and bool6 and bool56 and nb2 and nb3:
475
+ v1, v2 = label121[1], label212[1]
476
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
477
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
478
+ elif bool7 and bool8 and bool78 and nb1 and nb4:
479
+ v1, v2 = label122[1], label211[1]
480
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
481
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
482
+ else:
483
+ return _false_return()
484
+
485
+ # first test for affine type C: if there are three exceptional labels, we must be in both cases below of the same construction
486
+ elif len(exc_labels) == 3:
487
+ exc_labels12 = [labl for labl in exc_labels if labl[2] == (1, -2)]
488
+ exc_labels21 = [labl for labl in exc_labels if labl[2] == (2, -1)]
489
+ # check that we have two labels of one kind and one label of the other
490
+ if exc_labels12 == [] or exc_labels21 == []:
491
+ return _false_return()
492
+ if len(exc_labels12) == 2:
493
+ label1, label2 = exc_labels12
494
+ label3 = exc_labels21[0]
495
+ if dict_in_out[label2[0]][2] == 1 or dict_in_out[label2[1]][2] == 1:
496
+ label1, label2 = label2, label1
497
+ if dict_in_out[label1[0]][2] == 1:
498
+ if label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1], label2[0], 1):
499
+ v1, v2 = label3[1], label2[0]
500
+ _reset_dg(dg, vertices, dict_in_out, [label2[1]])
501
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
502
+ return _false_return()
503
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
504
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[v1, v2]])
505
+ else:
506
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
507
+ elif label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1], label3[0], 1):
508
+ v1, v2 = label2[1], label3[0]
509
+ _reset_dg(dg, vertices, dict_in_out, [label3[1]])
510
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
511
+ return _false_return()
512
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
513
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'], [[v1, v2]])
514
+ else:
515
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
516
+ else:
517
+ return _false_return()
518
+ elif dict_in_out[label1[1]][2] == 1:
519
+ if label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1], label3[0], 1):
520
+ v1, v2 = label2[1], label3[0]
521
+ _reset_dg(dg, vertices, dict_in_out, [label3[1]])
522
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
523
+ return _false_return()
524
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
525
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[v1, v2]])
526
+ else:
527
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
528
+ elif label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1], label2[0], 1):
529
+ v1, v2 = label3[1], label2[0]
530
+ _reset_dg(dg, vertices, dict_in_out, [label2[1]])
531
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
532
+ return _false_return()
533
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
534
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'], [[v1, v2]])
535
+ else:
536
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
537
+ else:
538
+ return _false_return()
539
+ 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:
540
+ _reset_dg(dg, vertices, dict_in_out, [label1[1]])
541
+ return _check_special_BC_cases(dg, n, ['BD'], [1], ['D'])
542
+ 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:
543
+ _reset_dg(dg, vertices, dict_in_out, [label1[0]])
544
+ return _check_special_BC_cases(dg, n, ['CD'], [1], ['D'])
545
+ else:
546
+ return _false_return()
547
+ elif len(exc_labels21) == 2:
548
+ label1, label2 = exc_labels21
549
+ label3 = exc_labels12[0]
550
+ if dict_in_out[label2[0]][2] == 1 or dict_in_out[label2[1]][2] == 1:
551
+ label1, label2 = label2, label1
552
+ if dict_in_out[label1[1]][2] == 1:
553
+ if label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1], label2[0], 1):
554
+ v1, v2 = label3[1], label2[0]
555
+ _reset_dg(dg, vertices, dict_in_out, [label2[1]])
556
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
557
+ return _false_return()
558
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
559
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'], [[v1, v2]])
560
+ else:
561
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
562
+ elif label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1], label3[0], 1):
563
+ v1, v2 = label2[1], label3[0]
564
+ _reset_dg(dg, vertices, dict_in_out, [label3[1]])
565
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
566
+ return _false_return()
567
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
568
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[v1, v2]])
569
+ else:
570
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
571
+ else:
572
+ return _false_return()
573
+ elif dict_in_out[label1[0]][2] == 1:
574
+ if label3[1] == label2[0] and dict_in_out[label3[1]][2] == 2 and dg.has_edge(label2[1], label3[0], 1):
575
+ v1, v2 = label2[1], label3[0]
576
+ _reset_dg(dg, vertices, dict_in_out, [label3[1]])
577
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
578
+ return _false_return()
579
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
580
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'], [[v1, v2]])
581
+ else:
582
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
583
+ elif label2[1] == label3[0] and dict_in_out[label2[1]][2] == 2 and dg.has_edge(label3[1], label2[0], 1):
584
+ v1, v2 = label3[1], label2[0]
585
+ _reset_dg(dg, vertices, dict_in_out, [label2[1]])
586
+ if len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1))) > 0:
587
+ return _false_return()
588
+ elif len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2))) > 0:
589
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'], [[v1, v2]])
590
+ else:
591
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
592
+ else:
593
+ return _false_return()
594
+ 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:
595
+ _reset_dg(dg, vertices, dict_in_out, [label3[1]])
596
+ return _check_special_BC_cases(dg, n, ['BD'], [1], ['D'])
597
+ 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:
598
+ _reset_dg(dg, vertices, dict_in_out, [label3[0]])
599
+ return _check_special_BC_cases(dg, n, ['CD'], [1], ['D'])
600
+ else:
601
+ return _false_return()
602
+
603
+ # 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
604
+ # first test for affine type C: if there are two exceptional labels, they must belong to leaves
605
+ # first test for affine type B: if there are two exceptional labels, they must be...
606
+ elif len(exc_labels) == 2:
607
+ label1, label2 = exc_labels
608
+ if label1[1] == label2[0]:
609
+ pass
610
+ elif label2[1] == label1[0]:
611
+ label1, label2 = label2, label1
612
+ else:
613
+ # the exceptional case in affine type BC_2 is checked
614
+ if label2[2] == (1, -2) and label1[2] == (2, -1):
615
+ label1, label2 = label2, label1
616
+ if label1[2] == (1, -2) and label2[2] == (2, -1):
617
+ 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:
618
+ return QuiverMutationType(['BC', 2, 1])
619
+ 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:
620
+ return QuiverMutationType(['BC', 2, 1])
621
+ # the cases in affine type B/C are checked where the exceptional labels connect to leaves
622
+ v11, v12, label1 = label1
623
+ v21, v22, label2 = label2
624
+ if dict_in_out[v11][2] == 1:
625
+ in_out1 = 'out'
626
+ elif dict_in_out[v12][2] == 1:
627
+ in_out1 = 'in'
628
+ else:
629
+ return _false_return()
630
+ if dict_in_out[v21][2] == 1:
631
+ in_out2 = 'out'
632
+ elif dict_in_out[v22][2] == 1:
633
+ in_out2 = 'in'
634
+ else:
635
+ return _false_return()
636
+ if label1 == label2:
637
+ if in_out1 == in_out2 == 'in':
638
+ if label1 == (1, -2):
639
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
640
+ else:
641
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
642
+ elif in_out1 == in_out2 == 'out':
643
+ if label1 == (1, -2):
644
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
645
+ else:
646
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
647
+ else:
648
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
649
+ else:
650
+ if in_out1 == in_out2:
651
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
652
+ else:
653
+ if label1 == (1, -2):
654
+ if in_out1 == 'in':
655
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
656
+ else:
657
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
658
+ else:
659
+ if in_out1 == 'in':
660
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
661
+ else:
662
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
663
+
664
+ v1, v, label1 = label1
665
+ v, v2, label2 = label2
666
+ if dg.has_multiple_edges():
667
+ if all(edge == (v2, v1, 1) for edge in dg.multiple_edges()):
668
+ if dict_in_out[v2][2] == dict_in_out[v1][2] == 3:
669
+ _reset_dg(dg, vertices, dict_in_out, [v1, v2])
670
+ if label1 == (1, -2) and label2 == (2, -1):
671
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'], [[v]])
672
+ elif label1 == (2, -1) and label2 == (1, -2):
673
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'], [[v]])
674
+ else:
675
+ return _false_return()
676
+ elif dict_in_out[v][0] == dict_in_out[v][1] == 1:
677
+ dg.remove_multiple_edges()
678
+ dg = DiGraph(dg)
679
+ _reset_dg(dg, vertices, dict_in_out, [v])
680
+ 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)):
681
+ if label1 == (2, -1) and label2 == (1, -2):
682
+ return _check_special_BC_cases(dg, n, ['CD'], [1], ['A'])
683
+ elif label1 == (1, -2) and label2 == (2, -1):
684
+ return _check_special_BC_cases(dg, n, ['BD'], [1], ['A'])
685
+ else:
686
+ return _false_return()
687
+ else:
688
+ return _false_return()
689
+ else:
690
+ return _false_return()
691
+ elif not dict_in_out[v][0] == 1 or not dict_in_out[v][1] == 1:
692
+ return _false_return()
693
+ else:
694
+ if dg.has_edge(v2, v1, 1):
695
+ nr_same_neighbors = len(set(dg.neighbors_out(v1)).intersection(dg.neighbors_in(v2)))
696
+ nr_other_neighbors = len(set(dg.neighbors_out(v2)).intersection(dg.neighbors_in(v1)))
697
+ 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)])
698
+ if nr_same_neighbors + nr_other_neighbors + nr_contained_cycles > 2:
699
+ return _false_return()
700
+ if label1 == (2, -1) and label2 == (1, -2):
701
+ if n == 4 and (nr_same_neighbors == 2 or nr_other_neighbors == 1):
702
+ return QuiverMutationType(['CD', n - 1, 1])
703
+ # checks for affine A
704
+ if nr_same_neighbors + nr_other_neighbors > 1:
705
+ mt_tmp = _check_special_BC_cases(dg, n, ['C', 'CD'], [None, None], ['A', 'D'], [[], [v]])
706
+ else:
707
+ _reset_dg(dg, vertices, dict_in_out, [v])
708
+ mt_tmp = _check_special_BC_cases(dg, n, ['C', 'CD'], [None, None], ['A', 'D'])
709
+ if mt_tmp == 'unknown':
710
+ dg.delete_edges([[v2, v1], [v1, v], [v, v2]])
711
+ dg.add_edges([[v1, v2, 1], [v, v1, 1], [v2, v, 1]])
712
+ if nr_same_neighbors + nr_other_neighbors > 1:
713
+ # _reset_dg(dg, vertices, dict_in_out, [v])
714
+ return _check_special_BC_cases(dg, n, ['CD'], [None], ['D'], [[v]])
715
+ else:
716
+ return _check_special_BC_cases(dg, n, ['CD'], [None], ['D'])
717
+ else:
718
+ return mt_tmp
719
+ elif label1 == (1, -2) and label2 == (2, -1):
720
+ if n == 4 and (nr_same_neighbors == 2 or nr_other_neighbors == 1):
721
+ return QuiverMutationType(['BD', n - 1, 1])
722
+ # checks for affine A
723
+ if nr_same_neighbors + nr_other_neighbors > 1:
724
+ mt_tmp = _check_special_BC_cases(dg, n, ['B', 'BD'], [None, None], ['A', 'D'], [[], [v]])
725
+ else:
726
+ _reset_dg(dg, vertices, dict_in_out, [v])
727
+ mt_tmp = _check_special_BC_cases(dg, n, ['B', 'BD'], [None, None], ['A', 'D'])
728
+ if mt_tmp == 'unknown':
729
+ dg.delete_edges([[v2, v1], [v1, v], [v, v2]])
730
+ dg.add_edges([[v1, v2, 1], [v, v1, 1], [v2, v, 1]])
731
+ if nr_same_neighbors + nr_other_neighbors > 1:
732
+ # _reset_dg(dg, vertices, dict_in_out, [v])
733
+ return _check_special_BC_cases(dg, n, ['BD'], [None], ['D'], [[v]])
734
+ else:
735
+ return _check_special_BC_cases(dg, n, ['BD'], [None], ['D'])
736
+ else:
737
+ return mt_tmp
738
+ else:
739
+ return _false_return()
740
+ elif dict_in_out[v1][2] == 1 and dict_in_out[v2][2] == 1:
741
+ if label1 == (1, -2) and label2 == (1, -2):
742
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
743
+ elif label1 == (2, -1) and label2 == (2, -1):
744
+ return _check_special_BC_cases(dg, n, ['BC'], [1], ['A'])
745
+ elif label1 == (1, -2) and label2 == (2, -1):
746
+ return _check_special_BC_cases(dg, n, ['CC'], [1], ['A'])
747
+ elif label1 == (2, -1) and label2 == (1, -2):
748
+ return _check_special_BC_cases(dg, n, ['BB'], [1], ['A'])
749
+ else:
750
+ return _false_return()
751
+ 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:
752
+ _reset_dg(dg, vertices, dict_in_out, [v])
753
+ if n == 4 and (label1, label2) == ((2, -1), (1, -2)):
754
+ return _check_special_BC_cases(dg, n, ['CD'], [1], ['A'])
755
+ elif n > 4 and (label1, label2) == ((2, -1), (1, -2)):
756
+ return _check_special_BC_cases(dg, n, ['CD'], [1], ['D'])
757
+ elif n == 4 and (label1, label2) == ((1, -2), (2, -1)):
758
+ return _check_special_BC_cases(dg, n, ['BD'], [1], ['A'])
759
+ elif n > 4 and (label1, label2) == ((1, -2), (2, -1)):
760
+ return _check_special_BC_cases(dg, n, ['BD'], [1], ['D'])
761
+ else:
762
+ return _false_return()
763
+ else:
764
+ return _false_return()
765
+
766
+ # second tests for finite types B and C: if there is only one exceptional label, it must belong to a leaf
767
+ # also tests for affine type B: this exceptional label must belong to a leaf of a type D quiver
768
+ elif len(exc_labels) == 1:
769
+ label = exc_labels[0]
770
+ v_out = label[0]
771
+ v_in = label[1]
772
+ label = label[2]
773
+ if label == (1, -2):
774
+ if dict_in_out[v_in][0] == 1 and dict_in_out[v_in][1] == 0:
775
+ # _reset_dg(dg, vertices, dict_in_out, [v_in])
776
+ return _check_special_BC_cases(dg, n, ['B', 'BD'], [None, 1], ['A', 'D'], [[v_in], [v_in]])
777
+ elif dict_in_out[v_out][0] == 0 and dict_in_out[v_out][1] == 1:
778
+ # _reset_dg(dg, vertices, dict_in_out, [v_out])
779
+ return _check_special_BC_cases(dg, n, ['C', 'CD'], [None, 1], ['A', 'D'], [[v_out], [v_out]])
780
+ else:
781
+ return _false_return()
782
+ elif label == (2, -1):
783
+ if dict_in_out[v_out][0] == 0 and dict_in_out[v_out][1] == 1:
784
+ # _reset_dg(dg, vertices, dict_in_out, [v_out])
785
+ return _check_special_BC_cases(dg, n, ['B', 'BD'], [None, 1], ['A', 'D'], [[v_out], [v_out]])
786
+ elif dict_in_out[v_in][0] == 1 and dict_in_out[v_in][1] == 0:
787
+ # _reset_dg(dg, vertices, dict_in_out, [v_in])
788
+ return _check_special_BC_cases(dg, n, ['C', 'CD'], [None, 1], ['A', 'D'], [[v_in], [v_in]])
789
+ else:
790
+ return _false_return()
791
+
792
+ # if no edges of type (1, -2) nor (2, -1), then tests for type A, affine A, or D.
793
+ return _connected_mutation_type_AAtildeD(dg)
794
+
795
+
796
+ def _connected_mutation_type_AAtildeD(dg: DiGraph, ret_conn_vert=False):
797
+ """
798
+ Return mutation type of ClusterQuiver(dg) for DiGraph dg if it is
799
+ of type finite A, affine A, or finite D.
800
+
801
+ For all other types (including affine D), outputs 'unknown'
802
+
803
+ See [BPRS2009]_ and [Vat2008]_ (by Vatne) for theoretical details.
804
+
805
+ .. TODO::
806
+
807
+ Improve this algorithm to also recognize affine D.
808
+
809
+ INPUT:
810
+
811
+ - ``ret_conn_vert`` -- boolean (default: ``False``); if ``True``,
812
+ returns 'connecting vertices', technical information that is
813
+ used in the algorithm
814
+
815
+ A brief description of the algorithm::
816
+
817
+ Looks for a long_cycle (of length >= 4) in the digraph dg. If there is more than one than the mutation_type is 'unknown'.
818
+ 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.
819
+
820
+ If there is no long_cycle, then checks that there are no multiple edges, all triangles are oriented,
821
+ no vertices of valence higher than 4, that vertices of valence 4 are incident to two oriented triangles,
822
+ and that a vertex of valence 3 has exactly two incident arrows as part of an oriented triangle.
823
+ All these checks ensures that ClusterQuiver(dg) is of type A except for three exceptions that are also checked.
824
+
825
+ EXAMPLES::
826
+
827
+ sage: # needs sage.modules
828
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type_AAtildeD
829
+ sage: Q = ClusterQuiver(['A', [7, 0], 1]); Q.mutate([0, 1, 4])
830
+ sage: _connected_mutation_type_AAtildeD(Q.digraph(), ret_conn_vert=True)
831
+ [['D', 7], [0, 4]]
832
+ sage: Q2 = ClusterQuiver(['A', [5, 2], 1]); Q2.mutate([4, 5])
833
+ sage: _connected_mutation_type_AAtildeD(Q2.digraph())
834
+ ['A', [2, 5], 1]
835
+ sage: Q3 = ClusterQuiver(['E', 6]); Q3.mutate([5, 2, 1])
836
+ sage: _connected_mutation_type_AAtildeD(Q3.digraph(), ret_conn_vert=True)
837
+ 'unknown'
838
+ """
839
+ # naming the vertices
840
+ vertices = list(dg)
841
+ n = dg.order()
842
+
843
+ # Test if ClusterQuiver(dg) is of type D_n Type 1, i.e. A_{n-2} plus two leaves
844
+ if n > 3:
845
+ # check if any vertices have a neighborhood with two leaves. If so, prune the two leaves and retest on this smaller digraph.
846
+ # note that this step is unnecessary for digraphs with fewer than four vertices.
847
+ for v in vertices:
848
+ dead_neighbors = [v_n for v_n in dg.neighbors(v) if dg.degree(v_n) == 1]
849
+ if len(dead_neighbors) >= 2:
850
+ dg_tmp = DiGraph(dg)
851
+ dg_tmp.delete_vertices(dead_neighbors[:2])
852
+ type_tmp = _connected_mutation_type_AAtildeD(dg_tmp, ret_conn_vert=True)
853
+ if type_tmp == 'unknown':
854
+ return _false_return()
855
+ # if smaller digraph is of finite A type with v as a 'connecting vertex', then glueing back the two leaves yields type finite D.
856
+ if type_tmp[0].letter() == 'A' and type_tmp[0].is_finite():
857
+ if v in type_tmp[1]:
858
+ type_tmp[1].remove(v)
859
+ if n == 4:
860
+ type_tmp[1].extend(dead_neighbors[:2])
861
+ if ret_conn_vert:
862
+ return [QuiverMutationType(['D', n]), type_tmp[1]]
863
+ else:
864
+ return QuiverMutationType(['D', n])
865
+ # note that if v is not a 'connecting vertex' then we make no conclusion either way.
866
+ else:
867
+ return _false_return(3)
868
+ # 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
869
+ # at 'connecting vertices'.
870
+
871
+ # Exception 1 (Type 2 of D_n)
872
+ exception_graph1 = DiGraph()
873
+ exception_graph1.add_edges([(0, 1), (1, 2), (2, 3), (3, 0)])
874
+
875
+ # Exception 2 (Type 3 of D_n)
876
+ exception_graph2 = DiGraph()
877
+ exception_graph2.add_edges([(0, 1), (1, 2), (0, 3), (3, 2), (2, 0)])
878
+
879
+ # Let c_1 be a pair of 2-valent vertices and c_2 be a pair of two other vertices.
880
+ # If together, they make an induced 4-cycle and deleting c_1 yields two connected components,
881
+ # then retest of both components. If still connected after deleting c_1, then return 'unknown'.
882
+
883
+ # If on the other hand, (c1 and c2) is isomorphic to a triangulated square, then
884
+ # delete c1. This ensures that c2 is an edge of the triangulated square, and we delete
885
+ # it regardless of orientation. Then check if the digraph has exactly two connected
886
+ # components, and again this testing method is rerun on both components.
887
+
888
+ for c1 in Combinations([vertex for vertex in vertices if dg.degree(vertex) == 2], 2):
889
+ del_vertices = list(vertices)
890
+ del_vertices.remove(c1[0])
891
+ del_vertices.remove(c1[1])
892
+ for c2 in Combinations(del_vertices, 2):
893
+ comb = c1 + c2
894
+ sg = dg.subgraph(comb)
895
+
896
+ # Exception 1 case (4-cycle):
897
+ edges = sg.edges(sort=True, labels=False)
898
+ if (c1[0], c1[1]) not in edges and (c1[1], c1[0]) not in edges and sg.is_isomorphic(exception_graph1):
899
+ dg_tmp = DiGraph(dg)
900
+ dg_tmp.delete_vertices(c1)
901
+
902
+ components = dg_tmp.connected_components(sort=False)
903
+ # if not len(components) == 2:
904
+ if len(components) != 2:
905
+ return _false_return(4)
906
+ else:
907
+ dg_tmp1 = dg_tmp.subgraph(components[0])
908
+ type_tmp1 = _connected_mutation_type_AAtildeD(dg_tmp1, ret_conn_vert=True)
909
+ dg_tmp2 = dg_tmp.subgraph(components[1])
910
+ type_tmp2 = _connected_mutation_type_AAtildeD(dg_tmp2, ret_conn_vert=True)
911
+
912
+ if type_tmp1 == 'unknown' or type_tmp2 == 'unknown':
913
+ return _false_return()
914
+
915
+ # Assuming that the two components are recognized, initialize this in a format it can be returned as output
916
+ type_tmp = []
917
+ type_tmp.append([type_tmp1[0], type_tmp2[0]])
918
+ type_tmp[0].sort(key=str)
919
+ type_tmp.append(type_tmp1[1] + type_tmp2[1])
920
+ type_tmp[1].sort(key=str)
921
+
922
+ # Need to make sure the two vertices in c2 are both 'connecting vertices'.
923
+ if not set(c2).issubset(type_tmp[1]):
924
+ return _false_return(5)
925
+
926
+ 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():
927
+ if ret_conn_vert:
928
+ type_tmp[1].extend(c1)
929
+ # type_tmp[1].remove(c2[0])
930
+ # type_tmp[1].remove(c2[1])
931
+ return [QuiverMutationType(['D', n]), type_tmp[1]]
932
+ else:
933
+ return QuiverMutationType(['D', n])
934
+
935
+ # Exception 2 case (triangulated square):
936
+ if sg.is_isomorphic(exception_graph2):
937
+ dg_tmp = DiGraph(dg)
938
+ dg_tmp.delete_vertices(c1)
939
+ if tuple(c2) in dg_tmp.edges(sort=True, labels=False):
940
+ dg_tmp.delete_edge(tuple(c2))
941
+ else:
942
+ c2.reverse()
943
+ dg_tmp.delete_edge(tuple(c2))
944
+ components = dg_tmp.connected_components(sort=False)
945
+ if len(components) != 2:
946
+ return _false_return(7)
947
+ else:
948
+ dg_tmp1 = dg_tmp.subgraph(components[0])
949
+ type_tmp1 = _connected_mutation_type_AAtildeD(dg_tmp1, ret_conn_vert=True)
950
+
951
+ if type_tmp1 == 'unknown':
952
+ return _false_return()
953
+ dg_tmp2 = dg_tmp.subgraph(components[1])
954
+ type_tmp2 = _connected_mutation_type_AAtildeD(dg_tmp2, ret_conn_vert=True)
955
+
956
+ # Assuming that the two components are recognized, initialize this in
957
+ # a format it can be returned as output (just as above)
958
+ type_tmp = []
959
+ type_tmp.append([type_tmp1[0], type_tmp2[0]])
960
+ type_tmp[0].sort(key=str)
961
+ type_tmp.append(type_tmp1[1] + type_tmp2[1])
962
+ type_tmp[1].sort(key=str)
963
+ if type_tmp2 == 'unknown':
964
+ return _false_return()
965
+ if not set(c2).issubset(type_tmp[1]) and len(set(type_tmp[1]).intersection(c2)) == 1:
966
+ return _false_return(5.5)
967
+ 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():
968
+ if ret_conn_vert:
969
+ type_tmp[1].remove(c2[0])
970
+ type_tmp[1].remove(c2[1])
971
+ # type_tmp[1].extend(c1)
972
+ return [QuiverMutationType(['D', n]), type_tmp[1]]
973
+ else:
974
+ return QuiverMutationType(['D', n])
975
+
976
+ # The following tests are done regardless of the number of vertices in dg.
977
+ # If there are 1, 2, or 3 vertices in dg, we would have skipped above tests and gone directly here.
978
+
979
+ # Initialize a long_cycle.
980
+ long_cycle = False
981
+
982
+ # test that there is no triple-edge or higher multiplicity and that there is at most one double-edge.
983
+ if dg.has_multiple_edges():
984
+ multiple_edges = dg.multiple_edges(labels=False)
985
+ if len(multiple_edges) > 2:
986
+ return _false_return(14)
987
+ if len(multiple_edges) == 2:
988
+ # we think of the double-edge as a long_cycle, an unoriented 2-cycle.
989
+ long_cycle = [multiple_edges, ['A', n - 1, 1]]
990
+
991
+ # creating a dictionary of in-, out- and total degrees
992
+ dict_in_out = {}
993
+ for v in vertices:
994
+ dict_in_out[v] = (dg.in_degree(v), dg.out_degree(v), dg.degree(v))
995
+
996
+ # computing the absolute degree of dg
997
+ abs_deg = max([x[2] for x in list(dict_in_out.values())])
998
+
999
+ # edges = dg.edges(sort=True, labels=False)
1000
+
1001
+ # test that no vertex has valency more than 4
1002
+ if abs_deg > 4:
1003
+ return _false_return(16)
1004
+
1005
+ # constructing all oriented and unoriented triangles
1006
+ trians = _triangles(dg)
1007
+ oriented_trians = [trian[0] for trian in trians if trian[1]]
1008
+ unoriented_trians = [trian[0] for trian in trians if not trian[1]]
1009
+
1010
+ oriented_trian_edges = []
1011
+ for oriented_trian in oriented_trians:
1012
+ oriented_trian_edges.extend(oriented_trian)
1013
+
1014
+ # test that no edge is in more than two oriented triangles
1015
+ from collections import Counter
1016
+ edge_count = Counter(oriented_trian_edges)
1017
+ multiple_trian_edges = []
1018
+ for edge, count in edge_count.items():
1019
+ if count > 2:
1020
+ return _false_return(17)
1021
+ elif count == 2:
1022
+ multiple_trian_edges.append(edge)
1023
+ multiple_trian_edges = list(set(multiple_trian_edges))
1024
+
1025
+ # test that there at most three edges appearing in exactly two
1026
+ # oriented triangles
1027
+ count = len(multiple_trian_edges)
1028
+ if count >= 4:
1029
+ return _false_return(321)
1030
+
1031
+ # if two edges appearing in exactly two oriented triangles, test
1032
+ # that the two edges together determine a unique triangle
1033
+ if 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 = set.intersection(*map(set, test_triangles))
1038
+ if len(unique_triangle_set) != 1:
1039
+ return _false_return(19)
1040
+
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
+
1045
+ unique_triangle = unique_triangle_set.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]),
1225
+ connecting_vertices]
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
+ return long_cycle[1]
1233
+
1234
+
1235
+ @cached_function
1236
+ def load_data(n: int, user=True) -> dict:
1237
+ r"""
1238
+ Load a dict with keys being tuples representing exceptional
1239
+ QuiverMutationTypes, and with values being lists or sets
1240
+ containing all mutation equivalent quivers as dig6 data.
1241
+
1242
+ We check
1243
+
1244
+ - the data stored by the user (unless ``user=False`` was given)
1245
+ - and the data installed by the optional package ``database_mutation_class``.
1246
+
1247
+ INPUT:
1248
+
1249
+ - ``user`` -- boolean (default: ``True``); whether to look at user
1250
+ data. If not, only consider the optional package.
1251
+
1252
+ EXAMPLES::
1253
+
1254
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import load_data
1255
+ sage: load_data(2) # random - depends on the data the user has stored
1256
+ {('G', 2): [('AO', (((0, 1), (1, -3)), )), ('AO', (((0, 1), (3, -1)), ))]}
1257
+
1258
+ TESTS:
1259
+
1260
+ We test data from the ``database_mutation_class`` optional package::
1261
+
1262
+ sage: load_data(2, user=False) # optional - database_mutation_class
1263
+ {('G', 2): [('AO', (((0, 1), (1, -3)), )), ('AO', (((0, 1), (3, -1)), ))]}
1264
+ sage: D = load_data(3, user=False) # optional - database_mutation_class
1265
+ sage: sorted(D.items()) # optional - database_mutation_class
1266
+ [(('G', 2, -1),
1267
+ [('BH?', (((1, 2), (1, -3)), )),
1268
+ ('BGO', (((2, 1), (3, -1)), )),
1269
+ ('BW?', (((0, 1), (3, -1)), )),
1270
+ ('BP?', (((0, 1), (1, -3)), )),
1271
+ ('BP_', (((0, 1), (1, -3)), ((2, 0), (3, -1)))),
1272
+ ('BP_', (((0, 1), (3, -1)), ((1, 2), (1, -3)), ((2, 0), (2, -2))))]),
1273
+ (('G', 2, 1),
1274
+ [('BH?', (((1, 2), (3, -1)), )),
1275
+ ('BGO', (((2, 1), (1, -3)), )),
1276
+ ('BW?', (((0, 1), (1, -3)), )),
1277
+ ('BP?', (((0, 1), (3, -1)), )),
1278
+ ('BKO', (((1, 0), (3, -1)), ((2, 1), (1, -3)))),
1279
+ ('BP_', (((0, 1), (2, -2)), ((1, 2), (1, -3)), ((2, 0), (3, -1))))])]
1280
+ """
1281
+ from sage.env import DOT_SAGE, SAGE_SHARE
1282
+
1283
+ # we check
1284
+ # - if the data is stored by the user, and if this is not the case
1285
+ # - if the data is stored by the optional package install
1286
+ paths = [Path(SAGE_SHARE)]
1287
+ if user:
1288
+ paths.append(Path(DOT_SAGE))
1289
+ data = {}
1290
+ for path in paths:
1291
+ file = path / 'cluster_algebra_quiver' / f'mutation_classes_{n}.dig6'
1292
+ try:
1293
+ with open(file, 'rb') as fobj:
1294
+ data_new = pickle.load(fobj)
1295
+ except (OSError, FileNotFoundError, pickle.UnpicklingError):
1296
+ # File does not exist, corrupt pickle, wrong Python version...
1297
+ pass
1298
+ else:
1299
+ data.update(data_new)
1300
+ return data
1301
+
1302
+
1303
+ def _mutation_type_from_data(n: int, dig6, compute_if_necessary=True):
1304
+ r"""
1305
+ Return the mutation type from the given dig6 data by looking into
1306
+ the precomputed mutation types.
1307
+
1308
+ Attention: it is assumed that dig6 is the dig6 data of the
1309
+ canonical form of the given quiver!
1310
+
1311
+ EXAMPLES::
1312
+
1313
+ sage: # needs sage.modules
1314
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6
1315
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _mutation_type_from_data
1316
+ sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1317
+ sage: dg = ClusterQuiver(['F', 4]).canonical_label().digraph()
1318
+ sage: dig6 = _digraph_to_dig6(dg, hashable=True); dig6
1319
+ ('CCo?', (((1, 3), (2, -1)),))
1320
+ sage: _mutation_type_from_data(4, dig6)
1321
+ ['F', 4]
1322
+ """
1323
+ # we try to load the data from a library
1324
+ data = load_data(n)
1325
+ # if this didn't work, we construct all exceptional quivers with n vertices
1326
+ if compute_if_necessary and data == {}:
1327
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import save_quiver_data
1328
+ save_quiver_data(n, up_to=False, types='Exceptional', verbose=False)
1329
+ load_data.clear_cache()
1330
+ data = load_data(n)
1331
+ # finally, we check if the given quiver is in one of the exceptional mutation classes
1332
+ for mutation_type in data:
1333
+ if dig6 in data[mutation_type]:
1334
+ return QuiverMutationType(mutation_type)
1335
+ return 'unknown'
1336
+
1337
+
1338
+ def _mutation_type_test(n):
1339
+ """
1340
+ Test all quivers (of the given types) of rank n to check that
1341
+ mutation_type() works.
1342
+
1343
+ Affine type D does not return ``True`` since this test is not implemented.
1344
+
1345
+ EXAMPLES::
1346
+
1347
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _mutation_type_test
1348
+
1349
+ sage: _mutation_type_test(2) # long time
1350
+ True ('A', (1, 1), 1)
1351
+ True ('A', 2)
1352
+ True ('B', 2)
1353
+ True ('BC', 1, 1)
1354
+ True ('G', 2)
1355
+
1356
+ sage: _mutation_type_test(3) # long time
1357
+ True ('A', (2, 1), 1)
1358
+ True ('A', 3)
1359
+ True ('B', 3)
1360
+ True ('BB', 2, 1)
1361
+ True ('BC', 2, 1)
1362
+ True ('C', 3)
1363
+ True ('CC', 2, 1)
1364
+ True ('G', 2, -1)
1365
+ True ('G', 2, 1)
1366
+
1367
+ sage: _mutation_type_test(4) # not tested
1368
+ True ('A', (2, 2), 1)
1369
+ True ('A', (3, 1), 1)
1370
+ True ('A', 4)
1371
+ True ('B', 4)
1372
+ True ('BB', 3, 1)
1373
+ True ('BC', 3, 1)
1374
+ True ('BD', 3, 1)
1375
+ True ('C', 4)
1376
+ True ('CC', 3, 1)
1377
+ True ('CD', 3, 1)
1378
+ True ('D', 4)
1379
+ True ('F', 4)
1380
+ True ('G', 2, (1, 1))
1381
+ True ('G', 2, (1, 3))
1382
+ True ('G', 2, (3, 3))
1383
+
1384
+ sage: _mutation_type_test(5) # not tested
1385
+ True ('A', (3, 2), 1)
1386
+ True ('A', (4, 1), 1)
1387
+ True ('A', 5)
1388
+ True ('B', 5)
1389
+ True ('BB', 4, 1)
1390
+ True ('BC', 4, 1)
1391
+ True ('BD', 4, 1)
1392
+ True ('C', 5)
1393
+ True ('CC', 4, 1)
1394
+ True ('CD', 4, 1)
1395
+ False ('D', 4, 1)
1396
+ True ('D', 5)
1397
+ True ('F', 4, -1)
1398
+ True ('F', 4, 1)
1399
+ """
1400
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_classical_mutation_classes
1401
+ from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_digraph
1402
+ from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1403
+ data = _construct_classical_mutation_classes(n)
1404
+ keys = data.keys()
1405
+ for mutation_type in sorted(keys, key=str):
1406
+ mt = QuiverMutationType(mutation_type)
1407
+ print(all(ClusterQuiver(_dig6_to_digraph(dig6)).mutation_type() == mt for dig6 in data[mutation_type]), mutation_type)
1408
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_exceptional_mutation_classes
1409
+ data = _construct_exceptional_mutation_classes(n)
1410
+ keys = data.keys()
1411
+ for mutation_type in sorted(keys, key=str):
1412
+ mt = QuiverMutationType(mutation_type)
1413
+ print(all(ClusterQuiver(_dig6_to_digraph(dig6)).mutation_type() == mt for dig6 in data[mutation_type]), mutation_type)
1414
+
1415
+
1416
+ def _random_tests(mt, k, mut_class=None, nr_mut=5):
1417
+ """
1418
+ Provide random tests to find bugs in the mutation type methods.
1419
+
1420
+ INPUT:
1421
+
1422
+ - ``mt`` something that can be turned into a ``QuiverMutationType``
1423
+ - ``k`` -- integer; the number of tests performed for each quiver of rank ``n``
1424
+ - ``mut_class`` -- if given, this mutation class is used
1425
+ - ``nr_mut`` -- integer (default: 5); the number of mutations performed before
1426
+ testing
1427
+
1428
+ The idea of this random test is to start with a mutation type
1429
+ and compute its mutation class (or have this class given). Now,
1430
+ every quiver in this mutation class is slightly changed in order
1431
+ to obtain a matrix of the same type or something very similar.
1432
+ Now, the new type is computed and checked if it stays stable for
1433
+ ``nr_mut``'s many mutations.
1434
+
1435
+ TESTS::
1436
+
1437
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _random_tests
1438
+ sage: _random_tests(['A', 3], 1) # needs sage.modules
1439
+ testing ['A', 3]
1440
+ """
1441
+ from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
1442
+ from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_matrix, _matrix_to_digraph, _digraph_mutate, _edge_list_to_matrix
1443
+ import random
1444
+ if mut_class is None:
1445
+ mut_class = ClusterQuiver(mt).mutation_class(data_type='dig6')
1446
+ print("testing " + str(mt))
1447
+ for dig6 in mut_class:
1448
+ M_const = _dig6_to_matrix(dig6)
1449
+ nz = [(i, j) for i, j in M_const.nonzero_positions() if i > j]
1450
+ # performing k tests on the matrix M_const
1451
+ for i in range(k):
1452
+ M = copy(M_const)
1453
+ # every pair M[i, j], M[j, i] is possibly changed
1454
+ # while the property of being skew-symmetrizable is kept
1455
+ for i, j in nz:
1456
+ a, b = M[i, j], M[j, i]
1457
+ skew_sym = False
1458
+ while not skew_sym:
1459
+ ran = random.randint(1, 2)
1460
+ if ran == 1:
1461
+ M[i, j], M[j, i] = -M[j, i], -M[i, j]
1462
+ elif ran == 2:
1463
+ ran2 = random.randint(1, 8)
1464
+ if ran2 == 1:
1465
+ c, d = 1, -1
1466
+ elif ran2 == 2:
1467
+ c, d = 1, -2
1468
+ elif ran2 == 3:
1469
+ c, d = 2, -1
1470
+ elif ran2 == 4:
1471
+ c, d = 1, -3
1472
+ elif ran2 == 5:
1473
+ c, d = 3, -1
1474
+ elif ran2 == 6:
1475
+ c, d = 2, -2
1476
+ elif ran2 == 7:
1477
+ c, d = 1, -4
1478
+ elif ran2 == 8:
1479
+ c, d = 4, -1
1480
+ M[i, j], M[j, i] = c, d
1481
+ if M.is_skew_symmetrizable(positive=True):
1482
+ skew_sym = True
1483
+ else:
1484
+ M[i, j], M[j, i] = a, b
1485
+ # we now have a new matrix M
1486
+ # and a new digraph db
1487
+ dg = _matrix_to_digraph(M)
1488
+ mt = _connected_mutation_type(dg)
1489
+ mut = -1
1490
+ # we perform nr_mut many mutations
1491
+ for k in range(nr_mut):
1492
+ # while making sure that we do not mutate back
1493
+ mut_tmp = mut
1494
+ while mut == mut_tmp:
1495
+ mut = random.randint(0, dg.order() - 1)
1496
+ dg_new = _digraph_mutate(dg, mut)
1497
+ mt_new = _connected_mutation_type(dg_new)
1498
+ if mt != mt_new:
1499
+ print("FOUND ERROR!")
1500
+ print(_edge_list_to_matrix(dg.edges(sort=True),
1501
+ list(range(dg.order())), []))
1502
+ print("has mutation type " + str(mt) + " while it has mutation type " + str(mt_new) + " after mutating at " + str(mut) + ":")
1503
+ print(_edge_list_to_matrix(dg_new.edges(sort=True),
1504
+ list(range(dg.order())), []))
1505
+ return dg, dg_new
1506
+ else:
1507
+ dg = dg_new
1508
+
1509
+
1510
+ def _random_multi_tests(n, k, nr_mut=5):
1511
+ """
1512
+ Provide multiple random tests to find bugs in the mutation type methods.
1513
+
1514
+ INPUT:
1515
+
1516
+ - ``n`` -- integer; the rank of the mutation types to test
1517
+ - ``k`` -- integer; the number of tests performed for each quiver of rank ``n``
1518
+ - ``nr_mut`` -- integer (default: 5); the number of mutations performed before testing
1519
+
1520
+ TESTS::
1521
+
1522
+ sage: from sage.combinat.cluster_algebra_quiver.mutation_type import _random_multi_tests
1523
+ sage: _random_multi_tests(2, 1) # not tested
1524
+ testing ('A', (1, 1), 1)
1525
+ testing ('A', 2)
1526
+ testing ('B', 2)
1527
+ testing ('BC', 1, 1)
1528
+
1529
+ sage: _random_multi_tests(3, 1) # not tested
1530
+ testing ('A', (2, 1), 1)
1531
+ testing ('A', 3)
1532
+ testing ('B', 3)
1533
+ testing ('BB', 2, 1)
1534
+ testing ('BC', 2, 1)
1535
+ testing ('C', 3)
1536
+ testing ('CC', 2, 1)
1537
+
1538
+ sage: _random_multi_tests(4, 1) # not tested
1539
+ testing ('A', (2, 2), 1)
1540
+ testing ('A', (3, 1), 1)
1541
+ testing ('A', 4)
1542
+ testing ('B', 4)
1543
+ testing ('BB', 3, 1)
1544
+ testing ('BC', 3, 1)
1545
+ testing ('BD', 3, 1)
1546
+ testing ('C', 4)
1547
+ testing ('CC', 3, 1)
1548
+ testing ('CD', 3, 1)
1549
+ testing ('D', 4)
1550
+ """
1551
+ from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _construct_classical_mutation_classes
1552
+ mutation_classes = _construct_classical_mutation_classes(n)
1553
+ for mutation_type in sorted(mutation_classes, key=str):
1554
+ _random_tests(mutation_type, k,
1555
+ mut_class=mutation_classes[mutation_type], nr_mut=nr_mut)